前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
返回 AI 情报前线
All News · 全部资讯10183
  • 用 AI 编程智能体清理电视广告与预装应用
  • DeepSeek开放生产推理框架DeepInfer
  • 京东大促期间暂停外部安全测试
  • Coral NPU补齐矩阵单元与开源编译链
  • 向量检索与知识图谱如何协同支撑 RAG
  • 让编程助手接入浏览器完成调试闭环
  • 先验证分类器,再让 AI 排查运行轨迹
  • 录制模型响应,隔离评测代码与模型变动
  • 审查 AI 生成 PR,先核对任务边界
  • 用有效吞吐压测确定模型接口并发上限
  • Agent 改一个字段,如何证明操作正确
  • 用多任务决策模型自动分流 CVE 漏洞
  • 逐步核算 Token,定位 Agent 的结构性浪费
  • Agent 生成 SQL,如何控制重跑漂移
  • 审核超时后的任务释放与计费隔离
  • DeepSeek 开源大规模推理框架 DeepInfer
  • DeepInfer 开源,公开大规模推理部署能力
  • Cloudflare预览低价结构化抽取模型
  • Headroom 在本地压缩智能体上下文
  • 智能体改记忆,如何验证没有误伤
  • llama-server 工具控制与停止条件现兼容问题
  • 模型快十倍,智能体为何只快一点
  • 用决策模型做评测,先调分类阈值
  • 编码 Agent 实测:成本之外看交付细节
  • Mercury 3 发布:主打高速生成与百万上下文
  • 用 Node 拆解向量池化与检索质量
  • 补齐 Oracle 方言约束,轻量模型首答率跃升
  • 拆解 Claude Code 的完整智能体循环
  • 邮件 Agent 重试前先校验退信
  • 用信号队列约束 Agent 等待邮件
  • 用 Cloudflare 搭建 Agent 收信中枢
  • 一次字符串替换如何让线上文章翻倍
  • AI 搭项目失败,先确认代码是否拉取成功
  • 检索匹配再高,也救不了过期知识库
  • 本地模型帮你给常用命令起快捷名
  • MCP 无状态改版涉及哪些迁移工作
  • 检查 Supabase 服务端鉴权的信任来源
  • 流式 AI 接口延迟该如何拆分测量
  • 模型故障切换如何追踪实际调用路径
  • 百灵开放五款语音模型供开发者组合
  • Drex Max 为电脑操作智能体输出决策概率
  • 无人值守的 Claude 循环如何耗尽额度
  • Bedrock为Claude提供严格结构化输出
  • 错误码搜不准?给向量检索补上 BM25
  • 让 Agent 崩溃后续跑,并处理重复副作用
  • 用 TypeScript 搭建带人工审批的客服智能体
  • Copilot 模型策略支持 REST API 管理
  • Copilot多种使用场景启动模型弃用
  • 把框架说明随包交付,让编程智能体可用
  • Nx 恶意包如何借编程智能体窃取秘密
  • 用分层防护限制 Agent 越权与误操作
  • 已加载 51 / 10183
8.0
热点
AI SCORE
技术实践2026-10-20 15:26

llama-server 工具控制与停止条件现兼容问题

dev.to · AI#本地推理#工具调用#接口兼容
Editor brief · 编辑速览

作者在 llama.cpp b11717 与 Qwen3-0.6B 组合中发现,Anthropic 兼容端点未正确执行指定工具及禁用工具的请求参数。停止字符串还会匹配推理内容,可能导致模型尚未作答就结束请求。

文章思维导图
Knowledge map
拖拽缩放
Full translation

完整中文译文

简要结论:llama-server 的 Anthropic 兼容端点 /v1/messages 接受两个请求控制参数,却没有实际应用。强制指定工具的 tool_choice: {"type": "tool", "name": "get_time"} 被当成了“调用某个工具”:在同时提供 get_weather 和 get_time、用户询问天气的情况下,模型调用了 get_weather。tool_choice: {"type": "none"} 则完全没有生效,模型再次调用了 get_weather。没有报错,也没有日志。另外,stop_sequences 不仅会匹配模型的回答,也会匹配模型的推理内容。因此,推理模型如果在思考时写出了停止字符串,请求就会在给出回答之前结束:返回的只有一个 thinking 块,stop_reason 为 end_turn,而 stop_sequence 则标出了匹配到的字符串。这两个问题都在 llama.cpp b11717 搭配 Qwen3-0.6B 时观察到了。停止行为与 10 月 16 日提交的 vLLM 问题报告 vllm-project/vllm#62085 表现一致;在那份报告中,它导致 Claude Code 的 auto 模式无法正常工作。我没有找到 llama.cpp 中关于这两个问题的报告。

测试环境是一个用完即弃的 LXC 容器、b11717 发布版二进制文件,以及 Qwen3-0.6B-Q8_0.gguf:

llama-server -m Qwen3-0.6B-Q8_0.gguf --jinja -c 4096 --port 18090

提供两个工具 get_weather(city) 和 get_time(city),用户消息为“What is the weather in Vienna?”(维也纳的天气怎么样?),设置 temperature: 0,并逐一测试文档中列出的所有 tool_choice 形式:

anthropic tool_choice=null: ([('tool_use', 'get_weather', '{"city": "Vienna"}')], 'tool_use')
anthropic tool_choice={"type": "auto"}: ([('tool_use', 'get_weather', '{"city": "Vienna"}')], 'tool_use')
anthropic tool_choice={"type": "any"}: ([('tool_use', 'get_weather', '{"city": "Vienna"}')], 'tool_use')
anthropic tool_choice={"type": "tool", "name": "get_time"}: ([('tool_use', 'get_weather', '{"city": "Vienna"}')], 'tool_use')
anthropic tool_choice={"type": "tool", "name": "get_weather"}: ([('tool_use', 'get_weather', '{"city": "Vienna"}')], 'tool_use')
anthropic tool_choice={"type": "none"}: ([('tool_use', 'get_weather', '{"city": "Vienna"}')], 'tool_use')

六次请求返回的 stop_reason 都是 tool_use,工具调用也完全相同。按名称指定 get_time 没有改变结果,使用 none 也一样。服务器日志中没有任何一条与这些情况有关的记录。

同一台服务器上的 OpenAI 兼容端点也有类似问题,上游已经有人报告过(llama.cpp#30369):按名称指定函数的形式 {"type": "function", "function": {"name": "get_time"}} 最终调用了 get_weather,不过这一次日志明确记录了原因:

W Wrong type supplied for parameter 'tool_choice', using default value: [json.exception.type_error.302] type must be string, but is object

在这个端点上使用 tool_choice: "none" 时,服务器没有解析出工具调用,但模型仍然写出了工具调用,并把它作为文本放进了 content:'<tool_call>\n{"name": "get_weather", "arguments": {...'。

接下来,用一个会让 Qwen3 先思考的问题进行测试,并选择会出现在推理内容中的停止字符串。设置 temperature: 0.6,使用两个 seed,测试两个端点:

stop='answer should be' seed 1 OpenAI finish=stop content='' reasoning_len=1639 tail='...7 is the only number that can divide it. Therefore, the '
   Anthropic stop_reason=end_turn stop_sequence='answer should be' blocks=[('thinking', 807, '...7 is indeed a prime number. Therefore, the ')]
stop='prime' seed 1 OpenAI finish=stop content='' reasoning_len=42 tail='Okay, so the question is asking if 7 is a '
   Anthropic stop_reason=end_turn stop_sequence='prime' blocks=[('thinking', 40, 'Okay, so the question is whether 7 is a ')]

seed 2 的表现相同。推理恰好在停止字符串之前中断,回答要么为空(OpenAI),要么根本不存在(Anthropic 只返回一个 thinking 块,没有 text 块),而请求却报告正常结束。不设置停止字符串时,同样的请求会在生成约 1,660 个字符的推理内容后,回答 <block>yes</block>。

Anthropic 端点的响应还存在自相矛盾:stop_sequence 指出了匹配到的序列,但 stop_reason 却是 end_turn,而不是 stop_sequence。在另一个测试中,停止字符串 </block> 截断了可见回答,也出现了同样的情况。

为什么这个问题很容易被忽略

每个响应的格式都是合法的。调用了错误工具,仍然是一次合法的工具调用。如果只定义了一个工具,或者模型恰好自己选中了被强制指定的工具,这个 bug 就完全看不出来。这里的 get_weather 也确实是回答用户问题的正确选择,所以强制工具测试之所以能暴露问题,是因为我们刻意强制指定了一个不适合该问题的工具。

停止字符串的问题看起来像是模型自身的毛病。空回答加上 finish_reason: stop,很容易被理解为“模型认为自己已经完成了”,而小型推理模型确实有时会絮絮叨叨,或者在奇怪的地方停下。唯一的线索是:推理在句子中途结束,而且恰好停在本该开始输出停止字符串的位置。

对于连接本地服务器的 Agent 工具,这两个问题尤其重要。Claude Code 可以连接任何 Anthropic 兼容端点。根据 vllm#62085,它的 auto 模式会发送一个带有 stop_sequences: ["</block>"] 的分类器请求。在我的测试中,Qwen3 从未在推理内容里写出 </block>,所以我没有在这里观察到那个具体请求失败。不过,只要停止字符串可能出现在模型的思考过程中,就会触发这个问题。

实际发生了什么

对于 tool_choice,llama-server 会先把 Anthropic 请求转换成内部使用的 OpenAI 风格格式,相关代码位于 tools/server/server-chat.cpp。查看源码中的转换逻辑(master 和 b10703)可以发现,{"type": "tool", "name": ...} 会变成 OpenAI 的 "required",工具名称被丢弃;而 {"type": "none"} 没有对应的映射,因此保留默认值 "auto"。"required" 的含义是“调用这些工具中的一个”,模型确实照做了,但没有任何信息告诉它应该调用哪一个。上面的测试结果与此吻合:any 和按名称指定工具的形式,行为完全相同。

对于停止字符串,服务器在 process_token 中检查时,匹配范围是该 slot 已生成的全部文本,具体调用是 tools/server/server-context.cpp 中的 find_stopping_strings(slot.generated.text, ...)。推理与回答的拆分发生得更晚,要等到构建响应时才进行。因此,无论停止字符串出现在生成文本的什么位置,包括推理部分,都会被匹配到;随后,服务器只根据停止字符串之前已经生成的内容构建响应。

Anthropic API 是否规定 stop_sequences 也应作用于 thinking,这是一个合理的问题,我还没有确认。但空回答显然不是客户端设置停止序列时想要的结果,而且,只要响应设置了 stop_sequence,就应该同时返回 stop_reason: "stop_sequence"。

这两个问题都没有可用的服务器配置项来解决。客户端可以采取以下措施:

  • tool_choice:不要依赖 /v1/messages 来强制指定工具。如果某一步必须使用某个特定工具,就在该次请求的 tools 中只提供这一个工具,并设置 {"type": "any"}。既然 any 确实能表达“调用某个工具”,这样应该就能强制调用唯一的工具,不过这里没有测试过。要阻止工具调用,应从请求中移除 tools,而不是发送 {"type": "none"}。

  • 对推理模型使用 stop_sequences:选择模型在思考时不会写出的停止字符串,例如自定义输出格式的结束标签;或者,对依赖停止字符串的请求关闭推理。在 llama-server 中,--reasoning-budget 0 可以在整个服务器范围内关闭推理。本轮测试没有验证这一点。

  • 检测这种失败:如果响应没有 text 块(或 content 为空),同时又设置了 stop_sequence,就应将其视为这里描述的失败,而不是一次空回答,并在移除停止字符串后重试。

工具包中的 check-llama-anthropic-controls.sh 会向服务器发送四个小请求,分别报告各个控制参数的行为。在这台服务器上,结果如下:

  IGNORED  tool_choice {type: tool, name: get_time} -> called ['get_weather']
  IGNORED  tool_choice {type: none} -> called ['get_weather']
  MATCHED IN REASONING  stop_sequences ['prime'] ended the reply inside thinking: blocks ['thinking'], stop_reason 'end_turn', stop_sequence 'prime'
  note     stop_sequence is 'prime' but stop_reason is 'end_turn'

退出码为 1。如果模型不调用工具,或者不进行推理(这里使用的是 gemma-3-1b),脚本会报告测试无法得出结论,并以退出码 2 结束,而不是判定通过。

值得推广的测试习惯

兼容层本质上是一种翻译,而翻译可能悄无声息地丢失信息。当服务器声称支持另一家厂商的 API 时,要测试你真正依赖的参数,并且让测试中的正确结果与默认行为产生不同的输出:强制调用模型原本不会选择的工具,选择一个模型会在你不希望它停止的位置写出的停止字符串。如果正确行为和错误行为给出相同的输出,这样的测试证明不了任何事。

Ollama 上也出现过同样的模式:控制参数被接受,随后却被模型的推理悄悄覆盖——一个 thinking 模型在直接回答时跳过了 JSON schema。

本文对应的修复方案已收录进工具包,是一份经过测试、可以直接运行的脚本。

如需采取进一步行动,可以考虑屏蔽此人和/或举报滥用行为。

Original source

本文由 AI 翻译整理自 dev.to · AI,原文版权归原作者所有。

阅读英文原文
上一篇
智能体改记忆,如何验证没有误伤
下一篇
模型快十倍,智能体为何只快一点