Bootstrap

深入解析如何在流式上下文中处理工具调用

在构建智能应用时,流式处理(Streaming)是一种非常重要的能力,特别是当涉及到实时模型推理和工具调用的场景。本文将介绍如何在流式上下文中处理工具调用,深入解析其核心原理,并通过代码示例展示具体实现方式。最终,我们将和大家探讨其实际应用场景及实践建议。


1. 技术背景介绍

在一些生成式 AI 应用中,经常需要结合工具(如数学运算、数据库操作)来扩展模型的能力。为了在流式处理中支持工具调用,LangChain 提供了流式工具调用接口。这种接口可以将工具调用的各部分逐步生成并发送,实现高效的流式交互。

流式工具调用的核心是 ToolCallChunk,它表示工具调用的一个分块(chunk)。这些分块会逐步拼接,直到工具调用的完整参数和返回值生成完毕。


2. 核心原理解析

在流式工具调用中:

  • 工具调用的信息(如工具名、参数)可能被拆分成多个 ToolCallChunk
  • 每个 ToolCallChunk 包括以下字段:
    • name: 工具的名称(可能为空)。
    • args: 工具的参数(可能为空,逐步生成)。
    • id: 调用的唯一标识符。
    • index: 分块的序号,用于拼接。

此外,每条消息(Message)会包含以下字段:

  • .tool_calls: 已解析的完整工具调用。
  • .tool_call_chunks: 未完全解析的工具调用分块。

流式处理通过监听这些分块,逐步累加工具调用的状态。


3. 代码实现演示

以下代码演示如何在流式上下文中使用工具调用。

3.1 定义工具和模型

我们定义了两个简单的数学工具 addmultiply,并注册到模型上。

from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
import os
from getpass import getpass

# 定义两个工具函数:加法和乘法
@tool
def add(a: int, b: int) -> int:
    """Adds a and b."""
    return a + b

@tool
def multiply(a: int, b: int) -> int:
    """Multiplies a and b."""
    return a * b

# 注册工具
tools = [add, multiply]

# 配置 OpenAI 模型
os.environ["OPENAI_API_KEY"] = getpass("Enter your OpenAI API Key: ")
llm = ChatOpenAI(model="gpt-3.5-turbo-0125", temperature=0)
llm_with_tools = llm.bind_tools(tools)

3.2 流式处理工具调用

通过流式处理查询 What is 3 * 12? Also, what is 11 + 49?,我们可以分块打印工具调用的生成过程。

import asyncio

# 查询问题并流式处理
query = "What is 3 * 12? Also, what is 11 + 49?"

# 异步流式输出工具调用的块内容
async def stream_tool_calls():
    async for chunk in llm_with_tools.astream(query):
        print(chunk.tool_call_chunks)

# 执行流式查询
asyncio.run(stream_tool_calls())

输出示例:

[]
[{'name': 'Multiply', 'args': '', 'id': 'call_xyz1', 'index': 0}]
[{'name': None, 'args': '{"a"', 'id': None, 'index': 0}]
[{'name': None, 'args': ': 3, ', 'id': None, 'index': 0}]
[{'name': 'Multiply', 'args': '{"a": 3, "b": 12}', 'id': 'call_xyz1', 'index': 0}]
...

3.3 累积工具调用

为了拼接并解析完整的工具调用,我们可以累积每个分块的内容。

async def accumulate_tool_calls():
    first = True
    gathered = None

    async for chunk in llm_with_tools.astream(query):
        if first:
            gathered = chunk
            first = False
        else:
            gathered = gathered + chunk  # 累积分块

        # 打印已经解析的工具调用
        print(gathered.tool_calls)

# 流式累积查询
asyncio.run(accumulate_tool_calls())

输出示例:

[]
[{'name': 'Multiply', 'args': {}, 'id': 'call_xyz1'}]
[{'name': 'Multiply', 'args': {'a': 3, 'b': 12}, 'id': 'call_xyz1'}]
[{'name': 'Multiply', 'args': {'a': 3, 'b': 12}, 'id': 'call_xyz1'}, {'name': 'Add', 'args': {}, 'id': 'call_xyz2'}]
...

4. 应用场景分析

4.1 实时对话助手

在实时生成式对话中,用户可能希望模型能逐步提供答案。例如,计算问题的中间过程输出可通过流式工具调用实现。

4.2 数据分析和监控

当处理数据查询(如数据库执行计划)时,工具调用的过程可以被实时监控,帮助用户理解模型的执行逻辑。

4.3 多步骤任务的分步执行

在多步骤任务中,每一步都可以通过流式工具调用独立执行并输出,有利于调试和改进。


5. 实践建议

  1. 工具设计要高内聚:确保每个工具功能单一,便于流式调用。
  2. 优先支持流式框架:尽量使用支持流式传输的模型或接口,例如 LangChain 的 astream 方法。
  3. 日志监控和调试:在开发阶段,打印和分析 tool_call_chunkstool_calls 有助于理解调用逻辑。
  4. 异常处理:关注分块解析中的边界情况,确保工具调用的完整性。

如果遇到问题欢迎在评论区交流。

;