
在开发或使用 AI Agent 的过程中,你有没有遇到过这种让人哭笑不得的场景? 当你满怀期待地给 Agent 框架(比如 Openclaw)下达指令:“帮我查看一下本机的 Downloads 目录”,并让它调用最新的大模型(比如 Kimi 2.5)来执行时,终端并没有如预期般列出文件,而是冷冰冰地吐出了一串“乱码”般的神奇代码:
<|tool_calls_section_begin|> <|tool_call_begin|> functions.exec:0 <|tool_call_argument_begin|> {"command": "ls -la ~/Downloads", "description": "List Downloads directory contents"} <|tool_call_end|> <|tool_calls_section_end|>
指令没执行,大模型反而把底层的“暗号”直接念出来了。这是为什么? 今天,我们就以这个真实的 Bug 为切入点,扒一扒大语言模型(LLM)与 Agent 框架之间是如何协同工作的,以及Function Calling(函数调用)的底层技术原理。
大模型是如何“长出手脚”的?
在分析 Bug 之前,我们必须先弄懂一个核心概念。 大语言模型(LLM)本质上是一个“被关在小黑屋里的超级大脑”。它上知天文下知地理,但它无法联网、无法读取你的本地文件、更无法直接敲击键盘执行命令。 那么,Agent 框架是如何让 LLM 拥有执行能力,变成全能助手的呢?答案就是Function Calling(函数调用 / 工具调用)。 一个完整的 Function Calling 流程通常包含以下 6 个标准步骤:
- 1.工具注册 (Tool Registration):开发者(或 Openclaw 框架)会在给大模型的 Prompt 中附加一个“工具箱说明书”。告诉大模型:“我这里有一个 functions.exec 工具,可以执行 Shell 命令,你需要给我返回特定格式的 JSON 参数。”
- 2.大模型决策 (Model Decision):LLM 接收到用户的自然语言请求(“查看目录”),分析后发现自己办不到,必须借助 functions.exec 工具。此时,LLM 会停止生成人类对话,转而生成一段包含工具名称和参数的指令文本。
- 3.框架拦截与解析 (Interception & Parsing):Agent 框架在后台实时监控 LLM 的输出。一旦发现它输出了工具指令,就会立刻将其拦截,不展示给用户,并提取出其中的 JSON 参数。
- 4.本地执行 (Execution):Agent 框架在本地环境中运行提取出的代码(例如 ls -la ~/Downloads)。
- 5.结果回传 (Feedback):框架将本地终端的执行结果打包,再次发送给 LLM:“这是你要的执行结果,请过目。”
- 6.最终输出 (Final Output):LLM 看到结果后,将其总结成人类易读的自然语言,最终输出给用户。 弄懂了这个标准流程,我们再回头看刚才的 Bug,答案就呼之欲出了。
为什么指令被直接打印了?
在这个案例中,Kimi 2.5 其实已经极其出色地完成了第 2 步(大模型决策)。它准确地判断出需要调用 functions.exec 工具,并完美生成了带参数的 JSON。问题出在第 3 步:框架拦截失败。Openclaw 没能认出这段指令,直接把它当成普通聊天文本输出了,导致后续的本地执行全部断裂。 为什么会拦截失败?核心原因在于大语言模型生态中的“方言”冲突。
1. OpenAI 的“普通话”标准
目前,市面上绝大多数开源 Agent 框架(包括 Openclaw)都是基于 OpenAI 的 API 标准开发的。在 OpenAI 的规范中,如果模型决定调用工具,它会在 API 的响应体(Response JSON)中,将工具参数放在一个专门的字段里(例如 finish_reason: "tool_calls",参数以纯 JSON 形式存在于 tool_calls 数组中)。框架解析起来非常明确,直接读取 JSON 字段即可。
2. Kimi 2.5 的“私有方言” (Special Tokens)
当你通过某些方式调用 Kimi(或类似非 OpenAI 原生模型)时,它可能使用了底层的Special Tokens(特殊控制符)来表达工具调用的意图。 正如你看到的那样,它用 <|tool_calls_section_begin|> 和 <|tool_call_end|> 将指令强行包裹在了普通文本流(Message Content)中输出。
3. 解析器的“漏网之鱼”
Openclaw 框架的解析器在等待 OpenAI 标准的结构化 JSON,结果却在普通的聊天文本流里迎面撞上了 Kimi 的 <|xxx|> 标签。因为代码里没有针对这种特殊标记的正则表达式,Openclaw 就傻乎乎地认为:“哦,这就是 Kimi 想对人类说的话”,于是原封不动地打印在了屏幕上。
如何修复这类兼容性 Bug?
遇到不同模型与框架的“接口不匹配”问题,作为开发者,我们有以下几种典型的解决思路:
方案一:在 API 接入层做“翻译” (推荐)如果你是通过 OneAPI、LiteLLM 等中间件代理调用的 Kimi 接口,请确保开启了严格的“OpenAI 格式对齐”功能。中间件会在底层拦截 Kimi 的特殊 Token,将其翻译成 OpenAI 标准的 tool_calls 结构再传给 Openclaw。
方案二:升级或更换 Agent 框架检查 Openclaw 是否有最新版本,开源社区通常会快速跟进主流大模型(如 Moonshot)的原生格式兼容。或者在配置文件中明确指定 model_type 为 Moonshot 专属的解析器。
方案三:硬核玩家的“源码魔改” 如果你懂 Python,这其实是一个极佳的练手机会。直接深入 Openclaw 的源码,找到处理大模型返回数据流(Response Stream)的函数,加上一段简单的正则拦截逻辑:
# 伪代码示例
import re
response_text = get_llm_response()
match = re.search(r'xxxxx',
response_text,
re.DOTALL)
if match:
json_str = match.group(1)
# 解析 JSON 并
#手动触发 subprocess.run() 执行命令
else:
# 正常打印文本
总结
大模型 Agent 正在经历野蛮生长的阶段。我们看到的各种“智障”表现,往往不是因为大模型不够聪明,而是连接“大脑(LLM)”与“四肢(执行环境)”的“神经系统(Agent 框架解析器)”出现了信号不兼容。 理解了 Function Calling 的底层逻辑,再遇到这类满屏吐代码的 Bug,你就能从容不迫地顺藤摸瓜,成为掌控 Agent 的真正极客!
本文技术原理解析基于大语言模型工具调用通用规范。如果你在开发 Agent 时也踩过类似的坑,欢迎在评论区分享你的填坑经验!
夜雨聆风