P1:chat 不能原样透传 OpenAI 工具调用响应,pi 这类 coding agent 接不上(dissect3) #29
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
背景
下游项目 dissect3 用 pi(开源 coding agent)做 KernelBench 题。pi 跑在不能联网的沙箱里,它的模型请求先到 dissect3 核心里的一个本地代理(
dissect3/src/d3/engine/proxy.py),代理再转发到中转站 new-api。代理现在自己逐块转发 SSE,没有任何超时、重试或换源。2026-10-01 试点实测,中转站上游不稳:MiniMax-M3 的流式回复中途会断(中转站在流里发
data: {"error":{"code":"upstream_stream_read_error","message":"Upstream response stream was interrupted","type":"upstream_error"}},随后仍发带 usage 的 chunk 和data: [DONE];另一类 errorMessage 为Upstream request failed),还有中段几十秒不出 token 的沉默。直连中转站的 3 条真题请求全部在约 300 秒处被断,最长沉默 62 秒。负责人决定:代理里凡是 PolyGateway 能做的(重试、退避、对冲、活性超时、并发闸)一律交给 PolyGateway,不在 dissect3 里另写。按 1.3.8 的现状接不上,原因是 PolyGateway 只交回纯文本补全,而 coding agent 的每一轮回答几乎都是工具调用。下面的行号都对
site-packages/polygateway(1.3.8,cf6e3fa)核过。dissect2 能用,是因为它的 agent 只发纯文本、从不用 tool_calls。缺口一:响应里的工具调用、finish_reason、响应 id 被丢弃
_sse_delta(transports/openai_compat.py:65-85)只取delta.content与delta.reasoning_content,delta.tool_calls被丢掉。LLMResponse(types.py:426-487)没有 tool_calls、finish_reason、响应 id 字段;raw里只留了 usage(openai_compat.py:645)。tool_calls/stop/length)决定 pi 是继续还是结束;响应 id 用于我们账本与引擎侧记录逐条对账。缺口二:只有工具调用、没有文字的回答被当成空补全重试
_reject_empty_completion(openai_compat.py:673-685)在 content 为空时抛 TransientError。工具调用型回答 content 常常为空,按现状会被反复重试直到耗尽,一条正常回答都交不回来。缺口三:流里的 error 帧被跳过
中转站的
{"error":…}帧没有 choices,_sse_delta在:76-77直接返回 None。之后:没有 [DONE] 时按missing_done重试(:656-671),这是对的;但上面实测的中转站在 error 帧之后还会发 [DONE],这时截断的半截结果会被当成成功交回,错误被静默吞掉。需要的能力
messages原样发出(含role: tool消息、assistant 消息里的tool_calls与reasoning_content),tools、tool_choice、max_tokens、temperature、reasoning_effort等经 overlay 原样发出(reasoning_effort由调用方直接给值,例如 glm 的max,不经受管推理档位映射,thinking.py:475-497现在会拒绝 raw 推理控制与受管档位并存)。响应侧,交回完整的 assistant 消息:content、reasoning_content、按 index 拼好的 tool_calls(id、type、function.name、function.arguments)、finish_reason、响应 id、usage 原文。流式时由库内读完整个流、拼好再交回即可,不需要逐块回调。error键)一律视为这次尝试失败、按瞬时错误重试,不论后面有没有 [DONE]。HEDGE__STALL_AFTER_S)、TTFT / inter-token / 总超时与并发闸,透传模式同样生效。不需要的:失败尝试的计费(dissect3 负责人决定,外部环境不稳造成的花费最后本来要剔除,不必记);美元预算(dissect3 代理自己做);逐块回调与保活(代理自己给 pi 发 SSE 注释行保活)。
验收
tool_calls:交回的消息里两个 tool_calls 的 id、name、arguments 与上游拼接结果逐字一致,finish_reason 与响应 id 正确,不触发重试。role: tool结果作为 messages 发出:上游收到的请求体里这些消息逐字段不变。reasoning_effort作为 raw 字段原样出现在上游请求体里。其他
async chat(client.py:360)。dissect3 的代理是 socketserver 线程模型,我们打算单开一个事件循环线程用run_coroutine_threadsafe调用;如果库有推荐的同步用法,请在文档里写一句。dissect3 侧在本 issue 解决之前不改代理,试点暂停等待。
已在 PolyGateway 1.3.9 解决并正式发布;本条关闭仅指库侧能力,不代表已修改或部署 dissect3 代理。
chat(..., response_mode="openai"),通过LLMResponse.completion获取 assistant 消息、按 index 聚合的并行 tool_calls、finish_reason、上游响应 ID 和 usage;工具参数逐字保留,纯工具响应不再误判为空。请求中的 assistant/tool 历史消息及 raw-only 推理参数沿既有路径透传。Transport.complete必须适配新增response_mode参数。受旧版吞错影响的文本缓存集合在升级首次读写前须更换 namespace/salt(含 per-call 覆盖),库不自动识别旧污染。run_coroutine_threadsafe桥接说明,没有新增同步 API。发布验收:main/tag 指向
22609c90d65a986f7a75560bc536442ba52c6bdb;合并后新跑 lint、格式、依赖门及全套 1828 passed / 23 skipped / 110 deselected,95% 覆盖。复用本任务此前相同生产实现的6项真实网关测试(工具双轮流式/非流式+chat4项,全部通过),之后仅文档/版本变化;不重复全模型 thinking 矩阵。registry 的 wheel/sdist 均已实际下载,来源和散列对上;隔离 pip 安装后检查1.3.9版本、协议 API、全部Python源码与LICENSE一致。包页面正文、仓库关联与Release均已匿名核验。
24ef2992d2b0e5c9f9d0930b536b9c428500f9c5cfaf5a838d54e7b568215e19pip install --index-url https://gitea.iomgaa.online/api/packages/iomgaa/pypi/simple/ "polygateway==1.3.9"(若缺依赖请从已配置的通用索引安装依赖;私有索引不镜像所有依赖)。