我花几周把 Qwen3-235B 接入 OpenClaw,踩了 22 个坑,全记录在这里
文章类型:实战踩坑记录 适合人群:想用国产大模型 + OpenClaw 搭建个人 AI 助手的开发者 源码:github.com/bisdom-cell…
先说结论
直接连 Qwen3 和 OpenClaw,必然失败。
不是因为 Qwen3 不行,而是两者之间存在 5 个根本性的协议不匹配,每一个都会导致系统完全不可用。我花了几周时间逐一解决,最终在 Mac Mini 上跑起了一套完整的个人 AI 助手系统:
- ✅ 10 个定时任务(新闻监控、天气简报、arXiv 论文追踪、投资简报)
- ✅ 5 个自定义 Skill(知识库读写、天气查询、网页摄入)
- ✅ WhatsApp 实时推送
- ✅ 每次工具调用 30-60 秒,系统稳定
本文把所有踩坑经验和解决方案完整记录下来,希望后来者少走弯路。
为什么选 Qwen3-235B?
OpenClaw 原生支持 OpenAI API,接 GPT-4 是开箱即用的。但我想用自托管的国产模型,原因很实际:
- 成本:自托管 GPU 的 token 费用远低于 GPT-4 API# 我花几周把 Qwen3-235B 接入 OpenClaw,踩了 22 个坑,全记录在这里
文章类型:实战踩坑记录 适合人群:想用国产大模型 + OpenClaw 搭建个人 AI 助手的开发者 源码:github.com/bisdom-cell…
先说结论
直接连 Qwen3 和 OpenClaw,必然失败。
不是因为 Qwen3 不行,而是两者之间存在 5 个根本性的协议不匹配,每一个都会导致系统完全不可用。我花了几周时间逐一解决,最终在 Mac Mini 上跑起了一套完整的个人 AI 助手系统:
- ✅ 10 个定时任务(新闻监控、天气简报、arXiv 论文追踪、投资简报)
- ✅ 5 个自定义 Skill(知识库读写、天气查询、网页摄入)
- ✅ WhatsApp 实时推送
- ✅ 每次工具调用 30-60 秒,系统稳定
本文把所有踩坑经验和解决方案完整记录下来,希望后来者少走弯路。
为什么选 Qwen3-235B?
OpenClaw 原生支持 OpenAI API,接 GPT-4 是开箱即用的。但我想用自托管的国产模型,原因很实际:
- 成本:自托管 GPU 的 token 费用远低于 GPT-4 API
- 能力:235B 参数量,W8A8 量化后推理效率极高
- Tool Calling:Qwen3 原生支持 Function Calling,不需要 prompt 工程
- 上下文:262K 超长窗口,复杂 cron 任务不会截断
问题在于,Qwen3 虽然支持 OpenAI 兼容 API,但和 OpenClaw 之间存在大量细节不兼容。
架构:双层中间件
直接连接行不通,解决方案是在中间插两层:
WhatsApp
↕
OpenClaw Gateway (Port 18789) ← 用户界面层
↕
Tool Proxy (Port 5002) ← 中间件层(核心,本文重点)
↕
Adapter (Port 5001) ← 适配层
↕
Remote Qwen3-235B API ← 推理层
| 层级 | 组件 | 核心职责 |
|---|---|---|
| 用户界面层 | OpenClaw Gateway | WhatsApp 消息收发、工具执行、会话管理 |
| 中间件层 | Tool Proxy | 工具过滤、Schema 简化、参数修复、SSE 转换、请求截断 |
| 适配层 | Adapter | 认证转换、User-Agent 伪装、参数过滤 |
| 推理层 | Qwen3 API | 大模型推理 + 原生 Tool Calling |
两层中间件对 OpenClaw 和 Qwen3 都完全透明,不需要修改任何一方的代码。
5 个根本性挑战
挑战 1:工具选择错误(最先遇到,最难发现)
现象:Qwen3 频繁调用不存在的工具,或者明明应该用 web_fetch 却去调用 browser。
根因:OpenClaw 默认提供 24 个工具。Qwen3 的 Tool Calling 训练数据偏向少量精准工具,工具数量过多会导致注意力分散。
解决方案:在 Tool Proxy 层用白名单过滤,24 个工具 → 12 个核心工具。
关键:必须用精确名称匹配,不能用正则或模糊匹配。任何意外漏网的工具都会破坏系统。
ALLOWED_TOOLS = {
"web_search", "web_fetch", "read", "write", "edit",
"exec", "browser", "tts", "memory_search",
"memory_get", "cron", "message"
}
def filter_and_simplify_tools(tools: list) -> list:
return [t for t in tools if t.get("function", {}).get("name") in ALLOWED_TOOLS]
挑战 2:复杂 Schema 导致 API 400 错误
现象:调用 Qwen3 API 时频繁返回 400,没有任何有用的错误说明。
根因:OpenClaw 的工具 Schema 包含复杂嵌套结构(oneOf、anyOf、嵌套 object 等),Qwen3 的 xinference 后端解析失败。
解决方案:将所有工具 Schema 替换为只包含必需参数的最简版本。
# 原始 Schema(复杂,Qwen3 解析失败)
# "parameters": {"type": "object", "properties": {"url": {...}, "method": {...}, "headers": {...}, ...}}
# 简化 Schema(只保留必需参数)
SIMPLIFIED_SCHEMAS = {
"web_fetch": {
"type": "function",
"function": {
"name": "web_fetch",
"description": "Fetch the content of a web page or URL.",
"parameters": {
"type": "object",
"properties": {
"url": {"type": "string", "description": "The full URL to fetch"}
},
"required": ["url"]
}
}
},
# ... 其他 11 个工具
}
挑战 3:参数名称不匹配
现象:工具调用请求发出去了,但 OpenClaw 说参数缺失,任务失败。
根因:Qwen3 喜欢用 file_path 而不是 path,用 file_content 而不是 content,用 search_query 而不是 query。
解决方案:在 Tool Proxy 的响应处理层做参数别名映射。
PARAM_ALIASES = {
"file_path": "path",
"filename": "path",
"filepath": "path",
"file_content": "content",
"text_content": "content",
"search_query": "query",
"search_term": "query",
"cmd": "command",
"link": "url",
# ... 20+ 条规则
}
注意:tts 工具的 text 参数和 message 工具的 content 参数需要豁免,否则会被错误地映射。
挑战 4:响应格式不兼容(最隐蔽的问题)
现象:系统完全没有响应,日志里没有任何报错。
根因:OpenClaw 期望 SSE(Server-Sent Events)流式响应,但 Qwen3 返回标准 JSON。两种格式完全不同,OpenClaw 接到 JSON 后静默丢弃。
解决方案:在 Tool Proxy 中实现 JSON → SSE 转换器。
def json_response_to_sse(response_json: dict):
response_id = response_json.get("id", f"chatcmpl-{int(time.time())}")
choices = response_json.get("choices", [])
message = choices[0].get("message", {})
tool_calls = message.get("tool_calls") or []
# 1. 发送 role 初始化 chunk
yield f"data: {json.dumps({'choices': [{'delta': {'role': 'assistant'}, 'finish_reason': None}]})}\n\n"
# 2. 如果有工具调用,分块发送
if tool_calls:
for i, tc in enumerate(tool_calls):
# 发送函数名
yield f"data: {json.dumps({'choices': [{'delta': {'tool_calls': [{'index': i, 'function': {'name': tc['function']['name'], 'arguments': ''}}]}}]})}\n\n"
# 分块发送参数(每块 30 字符)
args_str = json.dumps(tc['function'].get('arguments', {}))
for j in range(0, len(args_str), 30):
yield f"data: {json.dumps({'choices': [{'delta': {'tool_calls': [{'index': i, 'function': {'arguments': args_str[j:j+30]}}]}}]})}\n\n"
yield f"data: {json.dumps({'choices': [{'delta': {}, 'finish_reason': 'tool_calls'}]})}\n\n"
# 3. 发送结束标志
yield "data: [DONE]\n\n"
挑战 5:请求体超过 280KB 限制
现象:随着对话变长,某天突然开始频繁返回 HTTP 500,没有任何错误说明。
根因:Qwen3 API(xinference 后端)有约 280KB 的请求体硬限制,超过后返回 500 且不提示原因。随着对话历史积累,请求体会越来越大。
解决方案:在 Tool Proxy 中实现基于字节数的消息截断(注意:不能用字符数,CJK 文字是多字节的)。
MAX_REQUEST_BYTES = 200 * 1024 # 200KB,比 280KB 留出足够缓冲
def truncate_messages_by_bytes(messages: list) -> list:
def get_bytes(msgs):
return len(json.dumps(msgs, ensure_ascii=False).encode('utf-8'))
if get_bytes(messages) <= MAX_REQUEST_BYTES:
return messages
# 保留系统消息(必须),从最旧的普通消息开始删除
system_msgs = [m for m in messages if m.get("role") == "system"]
other_msgs = [m for m in messages if m.get("role") != "system"]
while other_msgs and get_bytes(system_msgs + other_msgs) > MAX_REQUEST_BYTES:
other_msgs.pop(0) # 删最旧的
return system_msgs + other_msgs
Adapter 层:三个关键配置
Adapter(5001)负责把 Tool Proxy 的请求转发给 Qwen3,有三个非直觉但关键的配置:
1. User-Agent 伪装
HEADERS = {
"Content-Type": "application/json",
"Authorization": f"Bearer {API_KEY}",
"User-Agent": "curl/8.0", # 关键:xinference 对不同 UA 的处理不同
}
听起来很奇怪,但这是真实踩过的坑——某些 xinference 部署对 Python requests 默认 UA 的响应格式与 curl 不同。
2. 参数白名单过滤
ALLOWED_PARAMS = {
"model", "messages", "tools", "tool_choice",
"temperature", "max_tokens", "stream",
"top_p", "top_k", "frequency_penalty", "presence_penalty"
}
def filter_request_params(data: dict) -> dict:
return {k: v for k, v in data.items() if k in ALLOWED_PARAMS}
OpenClaw 会在请求里加一些非标准字段,Qwen3 接到后返回 400。
3. 强制非流式
filtered_data["stream"] = False # SSE 转换由 Tool Proxy 处理
cron 任务的三条铁律
这是系统稳定运行后总结出的最重要经验,和模型无关,适用于所有接入 OpenClaw 的模型:
铁律 1:每个 cron 任务工具调用数 ≤ 2 次
Qwen3-235B 每次工具调用需要 30-60 秒。超过 2 次工具调用的任务极易超时(默认 600 秒)。
铁律 2:工具数量 ≤ 12
工具越多,模型选择准确率越低。12 个是实测出的甜点值。
铁律 3:请求体 ≤ 200KB
距离 280KB 的硬限制留出足够缓冲。对话历史要定期清理:
rm -f ~/.openclaw/agents/main/sessions/*.jsonl
22 条踩坑记录(按严重程度)
🔴 严重(会导致系统完全不可用)
| # | 问题 | 根因 | 解决方案 |
|---|---|---|---|
| 1 | HTTP 500,无错误信息 | 请求体超过 280KB | Tool Proxy 截断到 200KB |
| 2 | 工具调用完全失败 | 24 个工具导致 Qwen3 混乱 | 过滤为 12 个精准工具 |
| 3 | OpenClaw 无响应 | Qwen3 返回 JSON,OpenClaw 期望 SSE | 实现 JSON→SSE 转换器 |
| 4 | 502 Bad Gateway 频发 | 对话历史超过 200KB | 定期清理 sessions/*.jsonl |
| 5 | API 返回 400 | 传入了不支持的参数 | Adapter 层过滤非标准参数 |
🟡 中等(功能受损但不崩溃)
| # | 问题 | 根因 | 解决方案 |
|---|---|---|---|
| 6 | 工具参数解析失败 | Qwen3 用 file_path 而不是 path | 参数别名映射 |
| 7 | cron 任务不推送到 WhatsApp | 缺少 delivery: announce 配置 | 所有 cron 任务添加该字段 |
| 8 | cron 任务不执行 | 使用了 systemEvent 类型 | 改为 agentTurn 类型 |
| 9 | 知识库写入成功但文件不存在 | Qwen3 描述操作而不实际执行 | Skill 强制工具调用 + read 验证 |
| 10 | cron 超时(360秒) | 工具调用链太长 | 每任务限制 1-2 次工具调用 |
🟢 轻微(可绕过)
| # | 问题 | 根因 | 解决方案 |
|---|---|---|---|
| 11 | 天气查询失败(403) | 气象局官网有反爬 | 改用 wttr.in(无限制) |
| 12 | RSS 链接是代理 URL | Google News RSS 特性 | prompt 强制要求提取 <link> 字段 |
| 13 | Qwen3 自作主张换搜索源 | 模型忽略 URL 限制 | prompt 末尾强调"必须使用指定 URL" |
| 14 | arxiv 搜索误匹配 | all:glm 匹配广义线性模型 | 改用引号精确匹配 all:"ChatGLM" |
| 15 | Tailscale 被企业防火墙封锁 | IT 部门封锁 DERP 服务器 | 改用 ZeroTier |
| 16 | sudo 进程被挂起 | 后台 sudo 需要 tty 输入密码 | 用 heredoc 方式执行 |
| 17 | 知识库文件丢失 | 归档路径指向移动硬盘 | 统一使用 ~/.kb/ 本地路径 |
完整架构图
验证方法
检查服务状态
lsof -ti :18789 && echo "Gateway ✅" || echo "Gateway ❌"
lsof -ti :5001 && echo "Adapter ✅" || echo "Adapter ❌"
lsof -ti :5002 && echo "Proxy ✅" || echo "Proxy ❌"
直接测试 Tool Proxy
curl -X POST http://localhost:5002/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer test" \
-d '{
"model": "YOUR-MODEL-ID",
"messages": [{"role": "user", "content": "用web_fetch抓取 https://wttr.in/Hong+Kong?format=3 告诉我天气"}],
"stream": false
}'
健康检查
curl http://localhost:5002/health
# 返回:允许的工具列表、最大请求字节数、Adapter 状态
最重要的三条原则
回顾整个系统,最核心的三条规律:
-
工具数量 ≤ 12 — 超过后模型工具选择准确率显著下降,这不是 Qwen3 的问题,所有大模型都一样
-
每任务工具调用 ≤ 2 次 — 超过后超时风险指数级上升,30s × 3 次调用 = 90s,加上推理时间很容易超过 600s
-
请求体 ≤ 200KB(字节) — 距离 280KB 硬限制留出缓冲,注意要用字节计算不是字符计数
源码和完整文档
所有代码已开源,包含:
tool_proxy.py— 完整中间件实现(~350 行,双语注释)adapter.py— 完整适配器实现(~280 行,双语注释)docs/GUIDE.md— 中英双语完整指南restart.sh— 一键重启脚本
GitHub:github.com/bisdom-cell…
欢迎 Star、提 Issue、或者在评论区交流。如果你也在搭类似的系统,很想听听你们踩过什么其他的坑。
如果这篇文章对你有帮助,点个赞是最好的鼓励 👍
- 能力:235B 参数量,W8A8 量化后推理效率极高
- Tool Calling:Qwen3 原生支持 Function Calling,不需要 prompt 工程
- 上下文:262K 超长窗口,复杂 cron 任务不会截断
问题在于,Qwen3 虽然支持 OpenAI 兼容 API,但和 OpenClaw 之间存在大量细节不兼容。
架构:双层中间件
直接连接行不通,解决方案是在中间插两层:
WhatsApp
↕
OpenClaw Gateway (Port 18789) ← 用户界面层
↕
Tool Proxy (Port 5002) ← 中间件层(核心,本文重点)
↕
Adapter (Port 5001) ← 适配层
↕
Remote Qwen3-235B API ← 推理层
| 层级 | 组件 | 核心职责 |
|---|---|---|
| 用户界面层 | OpenClaw Gateway | WhatsApp 消息收发、工具执行、会话管理 |
| 中间件层 | Tool Proxy | 工具过滤、Schema 简化、参数修复、SSE 转换、请求截断 |
| 适配层 | Adapter | 认证转换、User-Agent 伪装、参数过滤 |
| 推理层 | Qwen3 API | 大模型推理 + 原生 Tool Calling |
两层中间件对 OpenClaw 和 Qwen3 都完全透明,不需要修改任何一方的代码。
5 个根本性挑战
挑战 1:工具选择错误(最先遇到,最难发现)
现象:Qwen3 频繁调用不存在的工具,或者明明应该用 web_fetch 却去调用 browser。
根因:OpenClaw 默认提供 24 个工具。Qwen3 的 Tool Calling 训练数据偏向少量精准工具,工具数量过多会导致注意力分散。
解决方案:在 Tool Proxy 层用白名单过滤,24 个工具 → 12 个核心工具。
关键:必须用精确名称匹配,不能用正则或模糊匹配。任何意外漏网的工具都会破坏系统。
python
ALLOWED_TOOLS = {
"web_search", "web_fetch", "read", "write", "edit",
"exec", "browser", "tts", "memory_search",
"memory_get", "cron", "message"
}
def filter_and_simplify_tools(tools: list) -> list:
return [t for t in tools if t.get("function", {}).get("name") in ALLOWED_TOOLS]
挑战 2:复杂 Schema 导致 API 400 错误
现象:调用 Qwen3 API 时频繁返回 400,没有任何有用的错误说明。
根因:OpenClaw 的工具 Schema 包含复杂嵌套结构(oneOf、anyOf、嵌套 object 等),Qwen3 的 xinference 后端解析失败。
解决方案:将所有工具 Schema 替换为只包含必需参数的最简版本。
python
# 原始 Schema(复杂,Qwen3 解析失败)
# "parameters": {"type": "object", "properties": {"url": {...}, "method": {...}, "headers": {...}, ...}}
# 简化 Schema(只保留必需参数)
SIMPLIFIED_SCHEMAS = {
"web_fetch": {
"type": "function",
"function": {
"name": "web_fetch",
"description": "Fetch the content of a web page or URL.",
"parameters": {
"type": "object",
"properties": {
"url": {"type": "string", "description": "The full URL to fetch"}
},
"required": ["url"]
}
}
},
# ... 其他 11 个工具
}
挑战 3:参数名称不匹配
现象:工具调用请求发出去了,但 OpenClaw 说参数缺失,任务失败。
根因:Qwen3 喜欢用 file_path 而不是 path,用 file_content 而不是 content,用 search_query 而不是 query。
解决方案:在 Tool Proxy 的响应处理层做参数别名映射。
python
PARAM_ALIASES = {
"file_path": "path",
"filename": "path",
"filepath": "path",
"file_content": "content",
"text_content": "content",
"search_query": "query",
"search_term": "query",
"cmd": "command",
"link": "url",
# ... 20+ 条规则
}
注意:tts 工具的 text 参数和 message 工具的 content 参数需要豁免,否则会被错误地映射。
挑战 4:响应格式不兼容(最隐蔽的问题)
现象:系统完全没有响应,日志里没有任何报错。
根因:OpenClaw 期望 SSE(Server-Sent Events)流式响应,但 Qwen3 返回标准 JSON。两种格式完全不同,OpenClaw 接到 JSON 后静默丢弃。
解决方案:在 Tool Proxy 中实现 JSON → SSE 转换器。
python
def json_response_to_sse(response_json: dict):
response_id = response_json.get("id", f"chatcmpl-{int(time.time())}")
choices = response_json.get("choices", [])
message = choices[0].get("message", {})
tool_calls = message.get("tool_calls") or []
# 1. 发送 role 初始化 chunk
yield f"data: {json.dumps({'choices': [{'delta': {'role': 'assistant'}, 'finish_reason': None}]})}\n\n"
# 2. 如果有工具调用,分块发送
if tool_calls:
for i, tc in enumerate(tool_calls):
# 发送函数名
yield f"data: {json.dumps({'choices': [{'delta': {'tool_calls': [{'index': i, 'function': {'name': tc['function']['name'], 'arguments': ''}}]}}]})}\n\n"
# 分块发送参数(每块 30 字符)
args_str = json.dumps(tc['function'].get('arguments', {}))
for j in range(0, len(args_str), 30):
yield f"data: {json.dumps({'choices': [{'delta': {'tool_calls': [{'index': i, 'function': {'arguments': args_str[j:j+30]}}]}}]})}\n\n"
yield f"data: {json.dumps({'choices': [{'delta': {}, 'finish_reason': 'tool_calls'}]})}\n\n"
# 3. 发送结束标志
yield "data: [DONE]\n\n"
挑战 5:请求体超过 280KB 限制
现象:随着对话变长,某天突然开始频繁返回 HTTP 500,没有任何错误说明。
根因:Qwen3 API(xinference 后端)有约 280KB 的请求体硬限制,超过后返回 500 且不提示原因。随着对话历史积累,请求体会越来越大。
解决方案:在 Tool Proxy 中实现基于字节数的消息截断(注意:不能用字符数,CJK 文字是多字节的)。
python
MAX_REQUEST_BYTES = 200 * 1024 # 200KB,比 280KB 留出足够缓冲
def truncate_messages_by_bytes(messages: list) -> list:
def get_bytes(msgs):
return len(json.dumps(msgs, ensure_ascii=False).encode('utf-8'))
if get_bytes(messages) <= MAX_REQUEST_BYTES:
return messages
# 保留系统消息(必须),从最旧的普通消息开始删除
system_msgs = [m for m in messages if m.get("role") == "system"]
other_msgs = [m for m in messages if m.get("role") != "system"]
while other_msgs and get_bytes(system_msgs + other_msgs) > MAX_REQUEST_BYTES:
other_msgs.pop(0) # 删最旧的
return system_msgs + other_msgs
Adapter 层:三个关键配置
Adapter(5001)负责把 Tool Proxy 的请求转发给 Qwen3,有三个非直觉但关键的配置:
1. User-Agent 伪装
python
HEADERS = {
"Content-Type": "application/json",
"Authorization": f"Bearer {API_KEY}",
"User-Agent": "curl/8.0", # 关键:xinference 对不同 UA 的处理不同
}
听起来很奇怪,但这是真实踩过的坑——某些 xinference 部署对 Python requests 默认 UA 的响应格式与 curl 不同。
2. 参数白名单过滤
python
ALLOWED_PARAMS = {
"model", "messages", "tools", "tool_choice",
"temperature", "max_tokens", "stream",
"top_p", "top_k", "frequency_penalty", "presence_penalty"
}
def filter_request_params(data: dict) -> dict:
return {k: v for k, v in data.items() if k in ALLOWED_PARAMS}
OpenClaw 会在请求里加一些非标准字段,Qwen3 接到后返回 400。
3. 强制非流式
python
filtered_data["stream"] = False # SSE 转换由 Tool Proxy 处理
cron 任务的三条铁律
这是系统稳定运行后总结出的最重要经验,和模型无关,适用于所有接入 OpenClaw 的模型:
铁律 1:每个 cron 任务工具调用数 ≤ 2 次
Qwen3-235B 每次工具调用需要 30-60 秒。超过 2 次工具调用的任务极易超时(默认 600 秒)。
铁律 2:工具数量 ≤ 12
工具越多,模型选择准确率越低。12 个是实测出的甜点值。
铁律 3:请求体 ≤ 200KB
距离 280KB 的硬限制留出足够缓冲。对话历史要定期清理:
bash
rm -f ~/.openclaw/agents/main/sessions/*.jsonl
22 条踩坑记录(按严重程度)
🔴 严重(会导致系统完全不可用)
| # | 问题 | 根因 | 解决方案 |
|---|---|---|---|
| 1 | HTTP 500,无错误信息 | 请求体超过 280KB | Tool Proxy 截断到 200KB |
| 2 | 工具调用完全失败 | 24 个工具导致 Qwen3 混乱 | 过滤为 12 个精准工具 |
| 3 | OpenClaw 无响应 | Qwen3 返回 JSON,OpenClaw 期望 SSE | 实现 JSON→SSE 转换器 |
| 4 | 502 Bad Gateway 频发 | 对话历史超过 200KB | 定期清理 sessions/*.jsonl |
| 5 | API 返回 400 | 传入了不支持的参数 | Adapter 层过滤非标准参数 |
🟡 中等(功能受损但不崩溃)
| # | 问题 | 根因 | 解决方案 |
|---|---|---|---|
| 6 | 工具参数解析失败 | Qwen3 用 file_path 而不是 path | 参数别名映射 |
| 7 | cron 任务不推送到 WhatsApp | 缺少 delivery: announce 配置 | 所有 cron 任务添加该字段 |
| 8 | cron 任务不执行 | 使用了 systemEvent 类型 | 改为 agentTurn 类型 |
| 9 | 知识库写入成功但文件不存在 | Qwen3 描述操作而不实际执行 | Skill 强制工具调用 + read 验证 |
| 10 | cron 超时(360秒) | 工具调用链太长 | 每任务限制 1-2 次工具调用 |
🟢 轻微(可绕过)
| # | 问题 | 根因 | 解决方案 |
|---|---|---|---|
| 11 | 天气查询失败(403) | 气象局官网有反爬 | 改用 wttr.in(无限制) |
| 12 | RSS 链接是代理 URL | Google News RSS 特性 | prompt 强制要求提取 <link> 字段 |
| 13 | Qwen3 自作主张换搜索源 | 模型忽略 URL 限制 | prompt 末尾强调"必须使用指定 URL" |
| 14 | arxiv 搜索误匹配 | all:glm 匹配广义线性模型 | 改用引号精确匹配 all:"ChatGLM" |
| 15 | Tailscale 被企业防火墙封锁 | IT 部门封锁 DERP 服务器 | 改用 ZeroTier |
| 16 | sudo 进程被挂起 | 后台 sudo 需要 tty 输入密码 | 用 heredoc 方式执行 |
| 17 | 知识库文件丢失 | 归档路径指向移动硬盘 | 统一使用 ~/.kb/ 本地路径 |
完整架构图
Show Image
验证方法
检查服务状态
bash
lsof -ti :18789 && echo "Gateway ✅" || echo "Gateway ❌"
lsof -ti :5001 && echo "Adapter ✅" || echo "Adapter ❌"
lsof -ti :5002 && echo "Proxy ✅" || echo "Proxy ❌"
直接测试 Tool Proxy
bash
curl -X POST http://localhost:5002/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer test" \
-d '{
"model": "YOUR-MODEL-ID",
"messages": [{"role": "user", "content": "用web_fetch抓取 https://wttr.in/Hong+Kong?format=3 告诉我天气"}],
"stream": false
}'
健康检查
bash
curl http://localhost:5002/health
# 返回:允许的工具列表、最大请求字节数、Adapter 状态
最重要的三条原则
回顾整个系统,最核心的三条规律:
- 工具数量 ≤ 12 — 超过后模型工具选择准确率显著下降,这不是 Qwen3 的问题,所有大模型都一样
- 每任务工具调用 ≤ 2 次 — 超过后超时风险指数级上升,30s × 3 次调用 = 90s,加上推理时间很容易超过 600s
- 请求体 ≤ 200KB(字节) — 距离 280KB 硬限制留出缓冲,注意要用字节计算不是字符计数
源码和完整文档
所有代码已开源,包含:
tool_proxy.py— 完整中间件实现(~350 行,双语注释)adapter.py— 完整适配器实现(~280 行,双语注释)docs/GUIDE.md— 中英双语完整指南restart.sh— 一键重启脚本
GitHub:github.com/bisdom-cell…
欢迎 Star、提 Issue、或者在评论区交流。如果你也在搭类似的系统,很想听听你们踩过什么其他的坑。
如果这篇文章对你有帮助,点个赞是最好的鼓励 👍