花一周时间把 Qwen3-235B 接入 OpenClaw,踩了 22 个坑,全记录在这里

0 阅读19分钟

我花几周把 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 GatewayWhatsApp 消息收发、工具执行、会话管理
中间件层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 条踩坑记录(按严重程度)

🔴 严重(会导致系统完全不可用)

#问题根因解决方案
1HTTP 500,无错误信息请求体超过 280KBTool Proxy 截断到 200KB
2工具调用完全失败24 个工具导致 Qwen3 混乱过滤为 12 个精准工具
3OpenClaw 无响应Qwen3 返回 JSON,OpenClaw 期望 SSE实现 JSON→SSE 转换器
4502 Bad Gateway 频发对话历史超过 200KB定期清理 sessions/*.jsonl
5API 返回 400传入了不支持的参数Adapter 层过滤非标准参数

🟡 中等(功能受损但不崩溃)

#问题根因解决方案
6工具参数解析失败Qwen3 用 file_path 而不是 path参数别名映射
7cron 任务不推送到 WhatsApp缺少 delivery: announce 配置所有 cron 任务添加该字段
8cron 任务不执行使用了 systemEvent 类型改为 agentTurn 类型
9知识库写入成功但文件不存在Qwen3 描述操作而不实际执行Skill 强制工具调用 + read 验证
10cron 超时(360秒)工具调用链太长每任务限制 1-2 次工具调用

🟢 轻微(可绕过)

#问题根因解决方案
11天气查询失败(403)气象局官网有反爬改用 wttr.in(无限制)
12RSS 链接是代理 URLGoogle News RSS 特性prompt 强制要求提取 <link> 字段
13Qwen3 自作主张换搜索源模型忽略 URL 限制prompt 末尾强调"必须使用指定 URL"
14arxiv 搜索误匹配all:glm 匹配广义线性模型改用引号精确匹配 all:"ChatGLM"
15Tailscale 被企业防火墙封锁IT 部门封锁 DERP 服务器改用 ZeroTier
16sudo 进程被挂起后台 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 状态

最重要的三条原则

回顾整个系统,最核心的三条规律:

  1. 工具数量 ≤ 12 — 超过后模型工具选择准确率显著下降,这不是 Qwen3 的问题,所有大模型都一样

  2. 每任务工具调用 ≤ 2 次 — 超过后超时风险指数级上升,30s × 3 次调用 = 90s,加上推理时间很容易超过 600s

  3. 请求体 ≤ 200KB(字节) — 距离 280KB 硬限制留出缓冲,注意要用字节计算不是字符计数


源码和完整文档

所有代码已开源,包含:

  • tool_proxy.py — 完整中间件实现(~350 行,双语注释)
  • adapter.py — 完整适配器实现(~280 行,双语注释)
  • docs/GUIDE.md — 中英双语完整指南
  • restart.sh — 一键重启脚本

GitHubgithub.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 GatewayWhatsApp 消息收发、工具执行、会话管理
中间件层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 条踩坑记录(按严重程度)

🔴 严重(会导致系统完全不可用)

#问题根因解决方案
1HTTP 500,无错误信息请求体超过 280KBTool Proxy 截断到 200KB
2工具调用完全失败24 个工具导致 Qwen3 混乱过滤为 12 个精准工具
3OpenClaw 无响应Qwen3 返回 JSON,OpenClaw 期望 SSE实现 JSON→SSE 转换器
4502 Bad Gateway 频发对话历史超过 200KB定期清理 sessions/*.jsonl
5API 返回 400传入了不支持的参数Adapter 层过滤非标准参数

🟡 中等(功能受损但不崩溃)

#问题根因解决方案
6工具参数解析失败Qwen3 用 file_path 而不是 path参数别名映射
7cron 任务不推送到 WhatsApp缺少 delivery: announce 配置所有 cron 任务添加该字段
8cron 任务不执行使用了 systemEvent 类型改为 agentTurn 类型
9知识库写入成功但文件不存在Qwen3 描述操作而不实际执行Skill 强制工具调用 + read 验证
10cron 超时(360秒)工具调用链太长每任务限制 1-2 次工具调用

🟢 轻微(可绕过)

#问题根因解决方案
11天气查询失败(403)气象局官网有反爬改用 wttr.in(无限制)
12RSS 链接是代理 URLGoogle News RSS 特性prompt 强制要求提取 <link> 字段
13Qwen3 自作主张换搜索源模型忽略 URL 限制prompt 末尾强调"必须使用指定 URL"
14arxiv 搜索误匹配all:glm 匹配广义线性模型改用引号精确匹配 all:"ChatGLM"
15Tailscale 被企业防火墙封锁IT 部门封锁 DERP 服务器改用 ZeroTier
16sudo 进程被挂起后台 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 状态

最重要的三条原则

回顾整个系统,最核心的三条规律:

  1. 工具数量 ≤ 12 — 超过后模型工具选择准确率显著下降,这不是 Qwen3 的问题,所有大模型都一样
  2. 每任务工具调用 ≤ 2 次 — 超过后超时风险指数级上升,30s × 3 次调用 = 90s,加上推理时间很容易超过 600s
  3. 请求体 ≤ 200KB(字节)  — 距离 280KB 硬限制留出缓冲,注意要用字节计算不是字符计数

源码和完整文档

所有代码已开源,包含:

  • tool_proxy.py — 完整中间件实现(~350 行,双语注释)
  • adapter.py — 完整适配器实现(~280 行,双语注释)
  • docs/GUIDE.md — 中英双语完整指南
  • restart.sh — 一键重启脚本

GitHubgithub.com/bisdom-cell…

欢迎 Star、提 Issue、或者在评论区交流。如果你也在搭类似的系统,很想听听你们踩过什么其他的坑。


如果这篇文章对你有帮助,点个赞是最好的鼓励 👍