数据截至 (上游 commit d02db1ee7c41)
LLM 客户端:单结构化工具调用与野模型容错
30 秒导读: page-agent 要在浏览器里驱动任意一个 OpenAI 兼容模型——从 GPT-5 到 Qwen、DeepSeek、Kimi、本地小模型。这些模型对"函数调用"的支持参差不齐:有的不认
tool_choice,有的把参数字符串化两遍,有的干脆把 JSON 塞进content。本章讲 page-agent 怎么用一个宏工具(MacroTool)+ 一层请求补丁 + 一层响应修复,把这堆"野模型"统一成"每步吐出一个结构化动作"的可靠管道。
本章聚焦"模型这一环":动作怎么打包成单次工具调用、请求怎么按模型定制、响应错了怎么掰正。它不讲外层的 ReAct 循环编排(见 01-agent-loop),也不讲每个动作在 DOM 上具体做了什么(见 03-tools-and-actions)。
1. 这是什么(零基础也能懂)
一句话定义
page-agent 的 LLM 层做一件事:每一步,让模型只输出一个结构化对象——先是几句反思,然后是一个动作——并保证不管模型多不规范,拿到的东西都能被程序可靠地解析和执行。
它要解决的真实困境
假设你写了个网页 agent,想让它支持用户"自带模型"(BYOK)。用户填个 baseURL 和 model 就能跑。问题来了:
- 用户填的可能是
gpt-5.2,也可能是qwen-max、deepseek-chat、claude-opus、甚至某个本地 Ollama 小模型。 - OpenAI 的
/chat/completions有"函数调用"标准,但每家实现都有脾气:- Qwen 默认开思考链、要显式关;
- DeepSeek 不接受
tool_choice字段; - Claude 的
tool_choice格式和 OpenAI 不一样; - 小模型经常不调用工具,而是把答案当普通文本写在
content里; - 有的模型把工具参数 JSON 字符串化两次,或多包一层
{name, arguments}。
如果每接一个模型就写一套 if-else,代码会烂掉。page-agent 的答案是:把差异集中到两个可控的点——发请求前的 modelPatch,收响应后的 normalizeResponse——中间的核心逻辑对所有模型一视同仁。