Callbacks Improvements

回调改进

3 分钟阅读

TL;DR:我们宣布改进了回调系统,该系统支持日志记录、追踪、流式输出,以及一些出色的第三方集成。这将更好地支持具有独立回调的并发运行、LangChain 组件的深度嵌套树的追踪,以及作用域限定为单个请求的回调处理程序(这对于在服务器上部署 LangChain 非常有用)。

背景

最初,我们在 LangChain 中将回调机制设计为用于非异步 Python 应用程序。现在我们支持 asyncio Python 用法以及 JavaScript/TypeScript 中的 LangChain,我们需要一些更好的抽象概念来适应这个新世界,在这个新世界中,许多并发的 LangChain 运行可以在同一线程或多个线程中进行。此外,很明显,在 Web 环境中使用 LangChain 的开发人员通常希望将回调限定为单个请求(例如,以便他们可以将其特定的句柄传递给 websocket)。

变更

我们对回调机制进行了一些更改,以解决这些问题

  • 您现在可以在构造函数参数中声明您想要的回调(这适用于所有运行),或者直接将它们传递给启动运行的 run / call / apply 方法。构造函数回调 将用于对该对象进行的所有调用,并且其作用域仅限于该对象,即,如果您将处理程序传递给 LLMChain 构造函数,则模型连接到该链时不会使用它。
  • 请求回调 将仅用于该特定请求以及它包含的所有子请求(例如,调用 LLMChain 会触发对模型的调用,该模型使用在 call() 方法中传递的相同处理程序)。这些都是显式传递的。举一个更具体的例子:当通过 run 将处理程序传递给 AgentExecutor 时,它将用于与代理以及代理执行中涉及的所有对象相关的所有回调,在本例中,这些对象是 ToolsLLMChainLLM。以前,要使用作用域限定为特定代理运行的回调,必须将该回调管理器附加到所有嵌套对象——这很繁琐、难看,并且难以重用对象。请参阅下面的 TypeScript 示例
// What had to be done before for run-scoped custom callbacks. Very tedious!
const executors = [];
for (let i = 0; i < 3; i += 1) {
  const callbackManager = new CallbackManager();
  callbackManager.addHandler(new ConsoleCallbackHandler());
  callbackManager.addHandler(new LangChainTracer());

  const model = new OpenAI({ temperature: 0, callbackManager });
  const tools = [new SerpAPI(), new Calculator()];
  for (const tool of tools) {
    tool.callbackManager = callbackManager;
  }
  const executor = await initializeAgentExecutor(
    tools,
    model,
    "zero-shot-react-description",
    true,
    callbackManager
  );
  executor.agent.llmChain.callbackManager = callbackManager;
  executors.push(executor);
}

const results = await Promise.all(
  executors.map((executor) => executor.call({ input }))
);
for (const result of results) {
  console.log(`Got output ${result.output}`);
}
  • Chains / LLMs / Chat Models / Agents / Tools 上的 _call_generate_run 和等效的异步方法现在接收一个名为 runManager 的第二个参数,该参数绑定到该运行,并包含可供该对象使用的日志记录方法(即 handleLLMNewToken)。这在构建自定义链时非常有用,例如,您可以在此处找到更多信息。
  • verbose 参数现在仅用作在 JS 中添加 ConsoleCallbackHandler 和在 Python 中添加 StdOutCallbackHandler 的快捷方式,后者将事件打印到 stdout。它不控制其他回调

追踪和其他回调现在只需在并发情况下工作。我们还添加了一个上下文管理器,使追踪特定运行变得更加容易。

重大更改和弃用

  • 任何依赖于全局回调或 LangChain 外部的全局追踪器(即 SharedCallbackManagerSharedTracer)的代码在 Python 包的 >0.0.153 版本中都会中断。
  • 现在已弃用将 CallbackManager 附加到对象,请使用 callbacks 参数传入处理程序列表。
  • verbose 标志现在仅控制 stdoutconsole 回调,而不控制其他回调。

灵感

当我们实施这些回调改进时,我们研究了一些现有的解决方案,这些解决方案最终影响了最终的 API,值得一提的是

  • Python logging 模块(和其他模块),它提供了一个 getChild 方法,该方法返回一个绑定到特定上下文的新记录器。这启发了新的 runManager.getChild(),您可以在实现自定义链时使用它来确保正确跟踪子运行。
  • Web 服务器框架,如 express,其中每个 HTTP 请求的所有特定于上下文的信息都作为函数参数显式传递,而不是作为某种全局变量提供。

我们还考虑了使用某种形式的异步上下文变量的替代方案,PythonNode.js 中存在其实现(但在其他 JS 环境中不存在)。最终,我们决定采用显式函数参数方法,因为它更易于调试,并且具有更好的跨平台兼容性(函数参数几乎可以在任何地方工作)。

如果您遇到任何问题,请告知我们,因为这是一项重大更改!