[Bug]: github_copilot provider: ‘no choices’ error for claude-opus-4.8 despite #29392 fix

用户通过 LiteLLM 代理向 GitHub Copilot 的 claude-opus-4.8 模型发送请求(Anthropic 格式 API 调用)。尽管已部署包含 PR #29392 修复的 LiteLLM 镜像,在使用 curl 或 Claude Code 客户端向代理发送 /v1/mes

[Bug]: github_copilot provider: 'no choices' error for claude-opus-4.8 despite #29392 fix

[Bug]: github_copilot provider: ‘no choices’ error for claude-opus-4.8 despite #29392 fix

快速结论:该报错发生在 LiteLLM 代理使用 github_copilot/claude-opus-4.8 模型时,convert_dict_to_response.py 检测到 choices: [] 空列表(而非缺失 choices 键)并抛出 no 'choices' 错误。尽管 PR #29392 已修复了 choices 键完全缺失的场景,但此 bug 属于不同的代码路径——父类 OpenAIConfig 可能重新读取了未经修补的原始响应字典,导致 transform_response 的修补未生效。优先排查 transform_response 修补后的 httpx.Response 是否被下游正确使用。

问题场景

用户通过 LiteLLM 代理向 GitHub Copilot 的 claude-opus-4.8 模型发送请求(Anthropic 格式 API 调用)。尽管已部署包含 PR #29392 修复的 LiteLLM 镜像,在使用 curl 或 Claude Code 客户端向代理发送 /v1/messages 请求时,代理返回 500 Internal Server Error,错误信息为 no 'choices'

报错原文

litellm.APIError: LiteLLM: provider returned a response with no 'choices'.
Raw keys: ['id', 'choices', 'created', 'model', 'object', 'service_tier', 'system_fingerprint', 'usage', 'copilot_usage']

注意:choices 存在于原始键中,但其值为空列表 [],而非缺失。错误在 convert_dict_to_response.py:571 中抛出,该行位于 transform_response 执行之后。

原因分析

该 bug 与 PR #29392 修复的场景不同。PR #29392 在 GithubCopilotConfig.transform_response() 中处理了 Copilot 返回 Anthropic 原生格式(不带 choices 键)的情况,并通过修补创建了新的 httpx.Response。然而当前 bug 的触发条件是响应中 choices 键存在但值为空列表 []。虽然 transform_response 方法中的 if not response_json.get("choices") 守卫理论上应捕获此情况(空列表在 Python 中为假值),但修补后的 httpx.Response 可能未被下游使用——可能原因是父类 OpenAIConfig 在后续处理中重新读取了原始的未修补响应字典,而非使用修补后的响应对象。此外,也存在以下可能原因:

  • provider 特定响应解析器读取了错误的字段。
  • 回退/错误路径检查了已被转换的对象。
  • 流式与非流式响应的形状处理不一致。
  • 响应包含 provider 特定包装,choices 位于比预期更深一层的嵌套中。

环境排查

  • LiteLLM 镜像版本:docker.litellm.ai/berriai/litellm-database:main-stable(构建于 2026-06-20,应包含 #29392 修复,commit 65be6ff)。
  • GitHub Enterprise (GHE) Copilot 端点。
  • 确认是否使用 Claude Code 客户端或 curl 发送 Anthropic 格式请求(/v1/messages 端点)。

解决步骤

  1. 确认响应形状:transform_response 修补前后添加日志,记录原始上游响应体与修补后的 httpx.Response 内容,验证 choices: [] 是否被正确修补为有效值。
  2. 检查修补响应流向下游的路径:跟踪 transform_response 返回的修补 httpx.Response 是否在 OpenAIConfig 后续逻辑中被覆盖或忽略——特别关注 convert_dict_to_response.py:571 调用栈之前的 llms/openai/openai.py 中的 acompletion 方法。
  3. 验证不同代码路径:区分流式与非流式请求的处理分支,确认问题是否仅出现在非流式请求中(因为 transform_response 可能对两者处理不同)。
  4. 建议调试工具:使用请求/响应记录工具(如 ccglass)记录原始请求体、上游响应体/SSE 流、使用量、延迟及模型/provider 元数据,以对比线上数据与 LiteLLM 转换后的 ModelResponse 对象。
  5. 确认是否是响应形状归一化 bug:若修补响应未被下游使用,则需要在 OpenAIConfig 或其基类中修改逻辑,确保使用修补后的响应对象,而非直接重新解析原始响应字典。
  6. 可以考虑的临时措施(推断,可优先尝试):transform_response 修补后,直接修改 response_object(即 response_json 本身)而非仅创建新的 httpx.Response,确保在任何代码路径下 choices 都非空。具体需要检查 transform_response 的当前实现是否仅修补了 httpx.Response 而遗漏了 response_json 的原地修改。

验证方法

重复调用以下 curl 命令(设备流认证完成后),确认返回 HTTP 200 及有效 completion 而不是 500 错误:

curl -s http://localhost:4000/v1/messages \
  -H "Authorization: Bearer $KEY" \
  -H "content-type: application/json" \
  -H "anthropic-version: 2023-06-01" \
  -d '{"model":"copilot-opus","max_tokens":1,"messages":[{"role":"user","content":"Hi"}]}'

同时确认日志中不再出现 no 'choices' 错误。

参考来源

BerriAI/litellm #30927

相关修复 PR:BerriAI/litellm #29392

GamsGo AI

AI 工具推荐

想把多个 AI 模型放在一个入口?

GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。

了解 GamsGo AI

推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。

celebrityanime
celebrityanime
文章: 9748

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注