在构建智能应用时,流式处理(Streaming)是一种非常重要的能力,特别是当涉及到实时模型推理和工具调用的场景。本文将介绍如何在流式上下文中处理工具调用,深入解析其核心原理,并通过代码示例展示具体实现方式。最终,我们将和大家探讨其实际应用场景及实践建议。
1. 技术背景介绍
在一些生成式 AI 应用中,经常需要结合工具(如数学运算、数据库操作)来扩展模型的能力。为了在流式处理中支持工具调用,LangChain 提供了流式工具调用接口。这种接口可以将工具调用的各部分逐步生成并发送,实现高效的流式交互。
流式工具调用的核心是 ToolCallChunk,它表示工具调用的一个分块(chunk)。这些分块会逐步拼接,直到工具调用的完整参数和返回值生成完毕。
2. 核心原理解析
在流式工具调用中:
- 工具调用的信息(如工具名、参数)可能被拆分成多个 ToolCallChunk。
- 每个 ToolCallChunk 包括以下字段:
name
: 工具的名称(可能为空)。args
: 工具的参数(可能为空,逐步生成)。id
: 调用的唯一标识符。index
: 分块的序号,用于拼接。
此外,每条消息(Message)会包含以下字段:
.tool_calls
: 已解析的完整工具调用。.tool_call_chunks
: 未完全解析的工具调用分块。
流式处理通过监听这些分块,逐步累加工具调用的状态。
3. 代码实现演示
以下代码演示如何在流式上下文中使用工具调用。
3.1 定义工具和模型
我们定义了两个简单的数学工具 add
和 multiply
,并注册到模型上。
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. 实践建议
- 工具设计要高内聚:确保每个工具功能单一,便于流式调用。
- 优先支持流式框架:尽量使用支持流式传输的模型或接口,例如 LangChain 的
astream
方法。 - 日志监控和调试:在开发阶段,打印和分析
tool_call_chunks
和tool_calls
有助于理解调用逻辑。 - 异常处理:关注分块解析中的边界情况,确保工具调用的完整性。
如果遇到问题欢迎在评论区交流。