)
1. 为什么我要用 Qwen3.7 搭一个能“动手”的智能体Qwen3.7 Function Calling 是通义千问系列在工具调用方向上的核心能力它让模型不再只是“聊天”而是能输出结构化的函数调用意图由你的程序去执行真实操作。AI 智能体Agent说白了就是“会自己决定调用哪个工具、传什么参数、拿到结果再继续推理”的程序而 Function Calling 就是它和外部世界之间的那根线。这套方案适合谁适合想从零做一个运维助手、数据查询机器人、工作流触发器的开发者尤其是那些不想一上来就啃 LangChain 全家桶、只想先把“模型调用工具”这条链路跑通的人。我试过的第一个版本是让模型查天气然后发邮件结果卡在参数校验上整整一个下午。后来才明白Function Calling 的难点从来不在模型而在你怎么定义工具、怎么接住模型返回的 JSON、怎么处理多轮和重试。这篇就按“从 0 到 1”的顺序把工具注册、参数校验、多轮调用、错误重试全部走一遍配置和代码都可以直接复制。整个链路里模型侧我用 Qwen3.7通道侧我用 TaoToken 统一 Key 和 API 地址这样本地调试和后面换模型都不用改业务代码。下面从环境准备开始。2. TaoToken 前置统一 Key 与 API 通道怎么配TaoToken 在这里的角色是“统一入口”——你不需要为每个模型单独记一套 base_url 和 key而是用同一个 Key 走同一个 API 地址模型名在请求里区分。官网是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 地址是 https://taotoken.net/api 注意 API 地址不带 UTM 参数。第一步去控制台创建 Key。打开 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 登录后在 API Keys 页面新建一个 Key复制出来先存到环境变量里别硬编码进代码。如果你还没决定用哪个模型可以先去模型对话页面 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 试一下 Qwen3.7 的工具调用表现确认返回格式符合预期再写代码。第二步把 Key 写进环境变量。Linux/macOS 下export TAOTOKEN_API_KEYsk-你的keyWindows PowerShell$env:TAOTOKEN_API_KEYsk-你的key第三步确认接入文档里的 base_url 和鉴权方式。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里面写了兼容 OpenAI SDK 的调用方式。TaoToken 的 API 地址是 https://taotoken.net/api 在 OpenAI SDK 里 base_url 填这个SDK 会自动拼 /v1/chat/completions。如果你用的是原生 httpx就要自己拼完整路径。这里有个容易踩的点base_url 结尾不要多加斜杠也不要写成 https://taotoken.net/api/v1 再让 SDK 拼一次否则会变成 /api/v1/v1/chat/completions。我建议统一用 OpenAI SDK让它处理路径拼接。3. 可复制配置config.toml 骨架与工具注册先把配置和工具定义分离这样后面加工具不用动主逻辑。我用的 config.toml 骨架如下[llm] base_url https://taotoken.net/api api_key_env TAOTOKEN_API_KEY model qwen3.7 timeout 60 max_retries 3 [agent] max_tool_rounds 5 tool_timeout 15 strict_schema true [[tools]] name get_weather description 获取指定城市的实时天气信息 required [city] [[tools.params]] name city type string description 城市名称如北京、上海 [[tools.params]] name unit type string enum [celsius, fahrenheit] description 温度单位默认摄氏度 [[tools]] name send_report description 将生成的报告发送到指定邮箱 required [email, content] [[tools.params]] name email type string description 收件人邮箱 [[tools.params]] name content type string description 报告正文这个骨架里max_tool_rounds控制多轮调用的上限防止模型陷入“调工具→再调工具”的死循环strict_schema打开后参数校验不通过直接抛错而不是让模型猜。工具定义里required和enum是 Qwen3.7 比较在意的字段缺了容易报 schema 解析失败。读取配置并转成 OpenAI tools 格式的代码import os import tomllib from openai import OpenAI with open(config.toml, rb) as f: cfg tomllib.load(f) client OpenAI( api_keyos.environ[cfg[llm][api_key_env]], base_urlcfg[llm][base_url], timeoutcfg[llm][timeout], ) def build_tools(cfg): tools [] for t in cfg[tools]: props {} for p in t.get(params, []): prop {type: p[type], description: p[description]} if enum in p: prop[enum] p[enum] props[p[name]] prop tools.append({ type: function, function: { name: t[name], description: t[description], parameters: { type: object, properties: props, required: t[required], }, }, }) return tools TOOLS build_tools(cfg)注意parameters里type、properties、required三个字段一个都不能少这是 Qwen3.7 对 JSON Schema 的硬要求。我一开始只写了 properties模型直接返回 400排查了半天才发现是 schema 不完整。4. 验证请求多轮调用与错误重试的完整跑通工具定义好了接下来是主循环。核心逻辑是发请求 → 模型返回 tool_calls → 本地执行 → 把结果塞回 messages → 再发请求 → 直到模型不再要求调工具。import json import time def execute_tool(name, args): if name get_weather: return {city: args[city], temp: 26, unit: args.get(unit, celsius)} if name send_report: return {status: sent, to: args[email]} raise ValueError(funknown tool: {name}) def validate_args(tool_def, args): required tool_def[function][parameters][required] for key in required: if key not in args: raise ValueError(fmissing required param: {key}) return True def run_agent(user_input): messages [ {role: system, content: 你是一个运维助手可以查询天气并发送报告。}, {role: user, content: user_input}, ] tool_map {t[function][name]: t for t in TOOLS} for round_idx in range(cfg[agent][max_tool_rounds]): resp client.chat.completions.create( modelcfg[llm][model], messagesmessages, toolsTOOLS, tool_choiceauto, ) msg resp.choices[0].message messages.append(msg) if not msg.tool_calls: return msg.content for tc in msg.tool_calls: name tc.function.name try: args json.loads(tc.function.arguments) validate_args(tool_map[name], args) result execute_tool(name, args) except Exception as e: result {error: str(e)} messages.append({ role: tool, tool_call_id: tc.id, content: json.dumps(result, ensure_asciiFalse), }) return 达到最大工具调用轮数已停止。 print(run_agent(查一下北京今天的天气然后把报告发给 admincompany.com))跑通后你会看到模型先返回两个 tool_calls一个 get_weather 一个 send_report本地执行完把结果回传模型再生成最终的自然语言回复。这里的关键是tool_call_id必须和请求里的 id 一一对应否则模型会报“找不到对应的工具结果”。错误重试我放在外层针对网络类错误做指数退避def call_with_retry(**kwargs): last_err None for i in range(cfg[llm][max_retries]): try: return client.chat.completions.create(**kwargs) except Exception as e: last_err e time.sleep(2 ** i) raise last_err把主循环里的client.chat.completions.create换成call_with_retry即可。注意参数校验失败不要重试那是 schema 问题重试多少次都一样直接返回错误信息给模型让它修正参数。5. 本篇常见错排查清单报错一400 Bad Request提示 schema 不合法。检查每个工具的 parameters 是否包含 type、properties、required 三个字段。Qwen3.7 对格式要求比上一代严少一个就拒。另外 enum 的值必须是字符串数组不能混类型。报错二模型返回的 arguments 不是合法 JSON。偶尔会出现模型输出带 markdown 代码块包裹的情况。稳妥做法是在 json.loads 之前先 strip 掉json 和或者用正则提取第一个 { 到最后一个 } 之间的内容。报错三多工具并行调用时结果顺序错乱。不要依赖 tool_calls 的返回顺序去匹配结果一定用 tool_call_id 关联。我踩过这个坑两个工具的结果串了模型基于错误数据继续推理输出完全跑偏。报错四上下文越来越长token 消耗飞快。每轮工具调用都会往 messages 里追加消息多轮之后历史很长。建议在每轮结束后对历史做裁剪只保留最近 N 轮和系统提示或者把工具结果做摘要再塞回去。报错五base_url 拼错导致 404。TaoToken 的 API 地址是 https://taotoken.net/api 用 OpenAI SDK 时不要再手动加 /v1。如果报 404先打印 client.base_url 确认实际请求地址。报错六Key 没读到报 401。确认环境变量名和 config.toml 里的 api_key_env 一致且是在同一个 shell 会话里 export 的。用echo $TAOTOKEN_API_KEY确认一下。6. 下一步从单机脚本到长期运行的 Agent上面这套跑通后你已经有了一个能调用外部工具的智能体雏形。接下来要解决的是“长期运行”的问题——比如定时任务、多用户并发、工具权限隔离。如果你打算把它做成一个持续跑的编码助手或者自动化 Agent可以了解一下 Coding Plan地址是 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 里面有针对长时代码任务的额度方案。如果你只是想先验证模型在工具调用上的表现模型对话页面 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 可以直接试。Key 的管理和新建在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 接入细节看文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。最后说一个我踩过的坑工具的执行一定要加超时和沙箱。模型只是“建议”你调用真正执行的是你的代码如果工具里连了数据库或者发了 HTTP 请求超时没设好整个 Agent 就卡死在那里。把 tool_timeout 配上执行层用线程池包一层比什么都实在。