把 LangChain agent 迁到聚合站:工具调用与流式能力过代理后还剩哪些
用 LangChain 的工具调用 agent 接聚合站的 OpenAI 兼容端点,判断哪些能力在代理层会退化:流式增量、工具调用参数拼装、多轮消息回传、usage 与错误结构。给出逐步迁移与逐模型验收的检查清单。
迁移前先明确一件事
聚合站对外提供的是 OpenAI 兼容端点。LangChain 侧的 ChatOpenAI 只需更换 baseurl 和 apikey 就能接上,代码形态基本不用改。真正需要评估的是:请求经过一层代理,被转发到 Claude、GPT、DeepSeek、Qwen、GLM、Kimi 等不同后端时,哪些行为会与直连官方 API 不一致。
答案不是"能用/不能用"的二元判断,而是逐项能力分模型标记。下面按一次完整的工具调用循环逐段看。
第一步:把端点换掉并跑通最小循环
先用 ChatOpenAI 指向聚合站,跑一个只有单个工具的最小 agent。这一步的目的是确认链路通了,而不是确认质量。
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
model="<聚合站里的模型标识>",
base_url="https://<聚合站域名>/v1",
api_key="<你的 key>",
streaming=True,
)
要点:
model用聚合站文档里给出的标识,不要沿用官方模型名去猜。- 第一次连接先关掉
streaming,确认非流式路径下的工具调用能返回结构化结果,再打开流式排查。 - 用
bindtools绑定工具,让 LangChain 走标准的tools/toolcalls字段,而不是自定义函数调用格式。
工具调用的参数是流式拼出来的
这是代理层最容易出问题的地方。流式响应里,工具调用的 arguments 是一段段 JSON 片段陆续到达的,客户端(这里是 LangChain 的解析器)负责累加拼装。
观察点:
- 增量片段是否带正确的
index,能否被正确归到对应的 tool_call。 - 结束标记是否完整,解析器能否判断参数已收齐。
- 参数里的非 ASCII 字符(中文、emoji)在分片处被切断时是否仍能拼回合法 JSON。
如果某个后端在流式下工具调用参数拼装失败,可以先把该模型从流式 agent 路由里摘出来,改走非流式,或在 LangChain 层用 astream_events 观察事件序列定位问题。
多轮消息回传:工具结果怎么塞回去
工具执行完,结果要以 tool 角色回传给模型,并带上对应的 toolcallid。这是标准 OpenAI 消息结构,LangChain 的 agent executor 会自动处理。
代理层会动的点:
- 消息顺序与角色映射。转发到非 OpenAI 后端时要重构成对方的消息格式,重构后
toolcallid是否仍能对应上。 - 并行工具调用。一次返回多个 tool_call 时,多个 tool 结果回传的顺序和配对是否被保持。
- 长对话里的历史裁剪。如果聚合站对上下文长度有自己的处理,历史消息被截断后可能出现孤立的 tool 消息,触发后端报错。
验收方法:造一个会触发两个并行工具调用的 prompt,看第二轮请求是否被正确接受。
流式的三层:文本、工具、事件
流式能力实际上有三层,要分开验收:
- 文本增量。
stream=True下首 token 是否能到、增量是否连续。 - 工具调用增量。如上节所述,参数分片拼装。
- LangChain 事件流。
astreamevents能否拿到onchatmodelstream、ontoolstart、ontoolend等事件,前端想按步骤渲染就必须依赖这一层。
后两层依赖第一层的原始 SSE 是否符合 OpenAI 规范。如果某个后端在代理下的原始事件格式被转换过,LangChain 的解析器可能仍能读出文本,但事件粒度会变粗,步骤级 UI 就渲染不出来。
逐后端标记能力退化
建议维护一张表,不凭印象,每个模型跑同一组用例后填:
- 非流式工具调用:通过 / 不通过
- 流式文本增量:通过 / 不通过
- 流式工具参数拼装:通过 / 降级 / 不通过
- 并行工具调用:通过 / 不通过
- 多轮 tool 消息回传:通过 / 不通过
- usage 字段:完整 / 缺失 / 只在非流式下返回
- 错误结构:标准 OpenAI error 体 / 包装过 / 纯文本
差异往往不在"能不能调工具",而在流式细节和 usage 上。把这张表贴在项目里,路由逻辑就有依据。
错误与重试要重新写
直连官方 API 时,你对 429、超时、上下文超长的错误码有稳定预期。过代理后多了一层,错误来源变多:
- 上游模型返回的错误,被代理原样透传还是重新包装。
- 代理自身的错误(key 失效、余额不足、上游暂不可用)。
LangChain 的重试逻辑基于异常类型和状态码。如果代理把所有上游错误统一成同一个状态码,max_retries 的行为就会失真。做法是在 ChatOpenAI 外再包一层自己的错误归一化,按响应体内容而不是仅凭状态码分类。
usage 与成本观察
流式响应下 usage 字段经常缺失,这是 OpenAI 兼容实现里的常见现象,不一定是代理造成的。计费按官方价 ×1.3 扣费,所以你需要能对账:
- 非流式调用拿 usage 做基准。
- 流式场景靠聚合站账单页核对总量。
- 在 agent 里把每轮调用的模型标识和 token 用量记进日志,便于按模型看成本分布。
迁移检查清单
- 用聚合站给的模型标识替换
model,改base_url。 - 关闭流式,跑通单工具循环。
- 打开流式,用
astream_events观察文本、工具、事件三层。 - 造并行工具调用用例,验收多轮回传。
- 制造一次上游错误和一次本地 key 错误,检查错误结构。
- 对每个后端模型重复 2–5,填进能力表。
- 按能力表决定哪些模型走流式 agent,哪些走非流式。
工具调用和流式这两项能力,过代理后不会整体消失,但会在分片拼装、事件粒度、错误结构这些细节上按后端分化。迁移的工作量主要花在验收和路由,而不是改代码。