Eval bug: Regression: Tool calls still returned in content field as JSON string instead of tool_calls array

在 llama.cpp 的 llama-server 开启 Jinja 模板( LLAMA_ARG_JINJA=1 )并使用支持工具调用的模型时,模型偶尔会把工具调用以 JSON 字符串形式写进 content 字段( finish_reason: stop ),而不是生成规范的 tool_call

快速结论:在 llama.cpp 的 llama-server 开启 Jinja 模板(LLAMA_ARG_JINJA=1)并使用支持工具调用的模型时,模型偶尔会把工具调用以 JSON 字符串形式写进 content 字段(finish_reason: stop),而不是生成规范的 tool_calls 数组(finish_reason: tool_calls)。优先确认模型输出本身是否已经跑偏,再排查 llama.cpp 的模板/解析逻辑。

适用环境:Issue 中确认的环境为:macOS(Darwin 24.5.0,arm64)、Apple clang 17.0.0 编译、Metal 后端、llama.cpp version 5894 (494c5899)、Mac Studio 64GB(10 CPU / 32 GPU)。模型为 Devstral-Small-2507-UD-Q8_K_XL.gguf,服务端为 llama-server,关键参数包括 LLAMA_ARG_CTX_SIZE=16384、LLAMA_ARG_JINJA=1、LLAMA_ARG_FA=1、tool_choice: auto。评论中还提到 Mistral-Small-3.2-24B-Instruct-2506、Qwen2.5-Coder-3B-Instruct 等模型出现类似现象。Python、CUDA、PyTorch 版本在 Issue 中没有给出。

最快修复方案:暂无确认的一步修复方案。该 Issue 以 stale 状态关闭,评论明确指出“this was not completed”,后续仍有多位用户报告同类问题,因此没有经过验证的修复步骤。

注意事项:目前无法仅从服务端配置断定问题一定来自 llama.cpp。评论提出两种可能:模型本身被训练成偶尔在 content 中输出工具调用,或者 llama.cpp 在解析/模板处理时偶发失败。由于故障是间歇性的(同一配置下有时正常、有时异常),单次成功不能证明已修复;需要有失败样本才能进一步定位。

问题场景

用户通过 llama-server 提供 OpenAI 兼容 API,开启 LLAMA_ARG_JINJA=1,加载 Devstral-Small-2507 GGUF 模型,并在请求中传入多个 OpenAI 格式的 tools 定义及 tool_choice: auto。期望返回符合 OpenAI 结构的 tool_calls 数组,但实际有时返回的 message.content 是一段 JSON 字符串,其中包含 tool_calls 字段,finish_reason 为 stop。评论区的复现显示:同一会话里前一次工具调用解析正常,后一次却退化成 content 中的 JSON 文本。

报错原文

Eval bug: Regression: Tool calls still returned in content field as JSON string instead of tool_calls array

finish_reason: stop
content: "{\n  \"tool_calls\": [\n    {\n      \"name\": \"json_set\",\n      \"arguments\": {\n        \"path\": \"$.game_scores.G02.scores.Sottocastello\",\n        \"value\": 45.12\n      },\n      \"id\": \"74e27f938\"\n    }\n  ]\n}"

expected:
finish_reason: tool_calls
message:
  content: null
  tool_calls:
  - type: function
    function:
      name: json_set
      arguments: '{"path":"$.game_scores.G02.status","value":"completed"}'
    id: i011r411j

原因分析

可能的根因有两类。第一类:模型在生成时偶尔没有按 chat template 期望的格式输出工具调用,而是把一段看起来像 tool_calls 的 JSON 写进了普通回复内容,llama.cpp 因此按普通文本处理,返回 finish_reason: stop。评论中观察到的现象(一次正常的 ToolCall,紧接着一次 Message 包裹的 JSON 字符串)也支持“模型输出本身漂移”这一可能。第二类:llama.cpp 在 Jinja 工具模板渲染或工具调用解析环节偶发判断失败,导致本应识别为工具调用的输出被当成 content。由于 Issue 未附带 first bad commit,也没有维护者给出确认结论,无法断定属于哪一种。

环境排查

  • 确认 llama-server --version 输出,记录具体 build 版本(Issue 为 5894 / 494c5899)。
  • 确认 LLAMA_ARG_JINJA 是否开启,以及所用模型是否带有可用的工具调用 chat template。
  • 确认模型文件及来源(Issue 使用 unsloth/Devstral-Small-2507-GGUF;评论涉及 Mistral-Small-3.2-24B-Instruct-2506、Qwen2.5-Coder-3B-Instruct 等)。
  • 确认请求中的 tools 和 tool_choice 是否为 OpenAI 兼容格式、是否被完整传入。
  • 确认 LLAMA_ARG_CTX_SIZE 等上下文参数,Issue 使用 16384。
  • 确认操作系统与后端(Issue 为 macOS + Metal);如在其他平台复现,需分别记录。

解决步骤

  1. 先固定复现条件:使用与 Issue 相同的模型、Jinja 开启配置和包含多个 tools 的 OpenAI 格式请求,连续多次调用,记录每次响应的 finish_reason 与 message.content,确认是否为间歇性出现。
  2. 保留至少一份失败请求/响应样本(与 Issue 中 request/response YAML 类似),以及对应的 llama-server 日志,便于判断是模型输出格式问题还是解析问题。
  3. 观察模型原始输出:如果失败样本中模型本身已经生成 {"tool_calls": [...]} 这样的文本,则更可能是模型输出漂移;如果模型输出符合模板格式但仍被放进 content,则更可能是 llama.cpp 解析问题。此步骤为排查方向,Issue 中未给出确认结论。
  4. 对照 Issue #12256、PR #12291 的相关修复,确认当前 build 是否包含对应改动;如不确定,可优先尝试升级到包含该修复的较新版本(可优先尝试,Issue 未验证)。
  5. 若无法升级,可优先尝试在应用侧检测 finish_reason: stop 且 content 可解析为包含 tool_calls 的 JSON 时,做兼容性后处理(可优先尝试,属于规避手段,不是根因修复)。
  6. 将失败样本和日志补充到 Issue 或新开 Issue,说明 build 版本、模型、请求内容与失败频率,帮助维护者定位。

验证方法

在相同模型、相同 Jinja 配置和相同请求下连续执行多次工具调用请求,确认每次响应均为 finish_reason: tool_calls、content: null,且工具调用位于 tool_calls 数组中,不再出现 content 内嵌 JSON 字符串的情况。由于问题是间歇性的,建议至少多次重复并覆盖此前出现过失败的同类请求,单次成功不足以确认修复。

参考来源

ggml-org/llama.cpp #14697

GamsGo AI

AI 工具推荐

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

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

了解 GamsGo AI

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

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 30011

发表回复

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