## 2026-10-29 补充:正式发布版本均真实复现,最新版尚未解决 本轮从 PyPI 下载并校验正式发布 wheel,分别隔离加载 `trpc-agent-py 1.1.4`、`1.1.20`、`1.1.21`、`1.2.0`。查询时 PyPI 最新版为 `1.2.0`。每个版本都核对了实际导入路径、版本和解析文件 SHA256。 先运行真实网络请求,再核对源码。所有版本均使用发布包原始 `_generate_stream` 和原始网络客户端;没有替换解析方法,没有构造、重排或回放响应包。观测方法只读字段并调用原方法。使用相同的无副作用工具声明和下文中文提示,每个版本发起 10 次真实请求。 | 正式发布版本 | 真实请求数 | 出现混合包的请求数 | 工具调用丢失数 | 请求异常数 | | --- | ---: | ---: | ---: | ---: | | 1.1.4 | 10 | 5 | 5 | 0 | | 1.1.20 | 10 | 3 | 3 | 0 | | 1.1.21 | 10 | 3 | 3 | 0 | | 1.2.0(当前最新) | 10 | 3 | 3 | 0 | 14 次丢失均具有相同证据链:服务返回非空 content 与工具名、调用 ID 同包;整轮参数能解析为合法 JSON;SDK 累积的参数完整,但工具名与 ID 为空;最终没有 function_call,error_code 为空,服务结束原因为 tool_calls。其余 26 次未出现混合包,原始调用与解析输出一致。 实测后检查四个发布包源码,仍存在“仅无文本包才调用工具累积函数”的互斥分支。最新版 1.2.0 的 `_openai_model.py` 第 2523 行仍是该判断。因此保留此 Issue:问题不只存在于 1.1.4,1.1.20 和最新 1.2.0 也尚未解决。 这些是定向提示的小样本计数,不是生产发生率;不能据此比较各版本发生概率。实验连接 OpenAI-compatible 网关,未验证供应商直连接口,也不证明历史业务故障的原因。没有执行业务工具。 以下保留早先 1.1.4 的详细记录。 --- ## 问题 在 `trpc-agent-py 1.1.4` 的真实流式请求中,观察到模型服务通过 OpenAI-compatible 接口返回的同一个包同时包含非空 `content` 和 `tool_calls`。SDK 丢失了该包中的工具名与调用 ID,最终没有输出工具调用,也没有返回错误。 修复 PR #363 已重新打开并转为 Ready for review,已关联本 Issue。Issue 保持打开,等待修复评审与合入。 ## 环境与验证方式 - Linux,Python 3.11,`trpc-agent-py 1.1.4`。 - 实际运行 `trpc_agent_sdk.models.OpenAIModel._generate_stream`。 - 使用 SDK 的真实 OpenAI 网络客户端连接一个 OpenAI-compatible 模型网关。 - 观测代码只读取进入解析器的包结构,以及工具调用累积后的字段;原版实验不替换客户端、响应包或解析方法。 - 未执行业务工具。工具仅为无副作用的 `record_result`,参数为整数 `count` 和字符串 `label`。 - 不公开内部网关地址、认证信息或隐藏推理正文。未验证供应商直连接口是否也返回相同分包。 ## 触发请求 给模型提供 `record_result(count: integer, label: string)` 工具,发送: > 先用一句话说明你会记录结果,然后调用 record_result,count=12,label="中文参数验证"。不要用正文代替工具调用。 采用 Chat Completions 流式请求,开启 `stream_options.include_usage`。是否触发取决于服务实际分包,不能保证每次重现。 ## 一次真实请求的观测结果 下表为捕获的字段摘要,不是人工构造的响应包。 | 观测位置 | 实际结果 | | --- | --- | | 第 4 个包 | `content` 长度为 3 字节;同包 `tool_calls[0]` 包含 index=0、非空调用 ID、name=`record_result`;arguments 为空字符串 | | 整轮原始工具片段拼接 | 工具名和 ID 完整;参数共 44 字节,能解析为 JSON 对象 | | SDK 完成累积时 | 工具名为空,ID 为空;参数共 44 字节,其 SHA256 与原始参数一致 | | 服务结束原因 | `tool_calls` | | SDK 最终结果 | 没有 function_call,`error_code=None`;收到最终响应,未抛异常 | 参数本身没有丢失;丢失的是混合包中的工具名和 ID,导致最终调用无法生成。 ## 重复验证与范围 在探索及确认阶段,原版解析器的 15 次同一模型路由请求中,7 次观察到混合包;这 7 次均出现原始工具调用与 SDK 输出不一致。另一模型路由的 9 次原版请求没有观察到混合包,也没有调用丢失。不同请求条件并不完全相同,这些计数不是线上发生率。 源码中 `_generate_stream` 只有在 `_verify_text_content_in_delta_response` 返回 false 时才调用工具累积逻辑,与这次观测吻合。请维护者确认:是否应该支持同一 delta 同时带文本与工具调用?如果该输入受支持,当前分支会遗漏调用信息。 本 issue 不把这一现象归因于模型本身,也不声称它解释了其他应用中的历史故障。
Activity
cloviswang commentedon Oct 29, 2026
补充提交历史分析,以及当前 main 的真实请求验证。
历史来源
f485ee4712d5caba27f849304b95e9014f453fdf(2026-04-01,初始化项目)已经包含该分支:只有没有文本的 chunk 才调用_process_chunk_without_content。注释明确把无文本 chunk 当作工具调用出现的位置。公开历史不能继续追溯到这个决定的最初设计过程,初始化提交也没有解释依据。ac5581d修复的是文本模拟工具调用时,正常正文被过滤的问题。它处理add_tools_to_prompt的文本过滤,不是原生delta.tool_calls混合包。此次修改保留了上述分支。25757d6修复非法 JSON 参数导致调用被丢弃的问题。它对已经累积到的参数做修复或保留错误信息,不能恢复在前面分支中漏掉的工具名。此次修改同样保留了上述分支。因此,这个行为在公开仓库初始化时已经存在,不是上述近期修复引入的回归。目前没有找到提交说明宣称“混合 delta 不受支持”,也不能推断作者最初为什么作出该假设。
当前 main 的真实请求复现
补测基线为
594bfb099358109c16cda77b86a5a020e262c04e。直接导入该基线对应 SDK 源码;原版组运行其原始_generate_stream方法,修复组运行此前补丁中的方法。所有请求均使用 SDK 创建的真实网络客户端,没有替换客户端或构造返回包。原版的失败请求中,工具名与 ID 所在片段与非空 content 同包。服务端参数能完整解析为 JSON,但 SDK 最终没有工具调用。修复组实际遇到的两次混合包均保留调用及完整参数。其他请求没有混合包,调用均一致。请求没有异常。这是独立网络请求的逐次核对,不是同一响应回放,也不代表生产发生率。
结合已安装 1.1.4 的真实复现,我认为可以确认此 OpenAI-compatible 路径存在静默丢失调用的问题,并且当前 main 仍受影响。原始参数合法,所以本例不属于 #257 的非法 JSON 场景。混合包由底层服务还是中间网关产生,仍未确定。
建议让工具字段累积独立于文本处理;另可考虑检查
finish_reason=tool_calls与最终调用列表为空的矛盾,避免静默结束。后者应单独评估错误处理契约。本评论补充问题证据,保持此前已关闭的 #363 不变。