ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

深入理解 Function Calling:让 AI Agent Harness Engineering 精准操作外部系统的底层逻辑

深入理解 Function Calling:让 AI Agent Harness Engineering 精准操作外部系统的底层逻辑 1. 为什么 Function Calling 是 Agent 操作外部系统的关键Function Calling函数调用是大模型原生支持的一种结构化输出能力模型不再只吐自然语言而是按你预先定义的 JSON Schema输出「要调用哪个函数、传什么参数」。它让 AI Agent 从「只会聊天」变成「能真正动手」——查数据库、调接口、改工单、控设备都靠这一层协议打通。在 AI Agent Harness Engineering智能体工程化里Harness 指的是包裹在模型外面的那套「驾驭层」工具注册、参数校验、权限控制、多轮调度、日志审计。Function Calling 就是这套驾驭层与外部系统之间的标准接口。没有它你只能靠 Prompt 诱导模型输出固定格式格式错误率高、参数经常缺字段有了它模型输出受约束解码限制结构合法性能到 90% 以上。这篇面向需要为 Agent 接入工具调用的开发者。我会先讲清楚底层机制再给一套可直接复制的config.toml与settings.json骨架然后通过 TaoToken 统一 Key/API 通道完成一次真实的工具调用配置最后发起一次外部系统调用并检查返回结果与日志。全程可跟做不需要你已经有现成的 Agent 框架。适合谁正在给 Agent 接工具的后端/全栈开发者、做企业内部助理或智能客服的工程同学、想把 RPA 和 LLM 结合起来的自动化玩家。前置知识只需要你会写一点 Python 或 Node能看懂 JSON 和 TOML 配置。2. 前置准备用 TaoToken 统一 Key 与 API 通道在写配置之前先把「模型从哪来」这件事解决掉。Function Calling 要求模型本身支持工具调用能力不同厂商的接口字段、返回结构、鉴权方式都不一样。如果每个模型都单独接一遍Harness 层会变得非常难维护。我的做法是用 TaoToken 作为统一入口一个 Key、一个 API 地址兼容主流模型的对话与工具调用接口。这样 Harness 层只需要对接一套协议切换模型时改配置即可不用动业务代码。你需要准备的东西一个 TaoToken 账号登录后在控制台创建 API Key本地能跑 Python 3.9 或 Node 18一个你想让 Agent 操作的外部系统接口本文用一个模拟的「工单查询」HTTP 接口演示。关键地址先记下来后面配置里会用到官网入口https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentAPI 基地址https://taotoken.net/api创建 Keyhttps://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_campaignrewrite接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_campaignrewrite注意API 基地址不要加 UTM 参数直接写https://taotoken.net/api即可否则部分 SDK 会把查询串拼进请求路径导致 404。拿到 Key 之后先别急着写 Agent。用一条 curl 确认通道是通的这一步能帮你排除掉 80% 的「配置没错但就是调不通」问题curl https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -H Content-Type: application/json \ -d { model: gpt-4o-mini, messages: [{role: user, content: 只回复两个字通了}] }返回里能看到choices[0].message.content就说明 Key 和通道都正常。如果返回 401检查 Key 有没有复制完整返回 404检查 base URL 是不是被加了多余路径。3. 可复制配置config.toml 与 settings.json 骨架Harness Engineering 的核心思路是「配置与代码分离」模型通道、工具元数据、权限策略都放在配置文件里代码只负责调度。下面这套骨架你可以直接抄。3.1 config.toml模型通道与运行参数# config.toml [llm] # 统一走 TaoToken 通道切换模型只改 model 字段 base_url https://taotoken.net/api api_key_env TAOTOKEN_API_KEY # 从环境变量读取不要硬编码 model gpt-4o-mini timeout_seconds 30 max_retries 2 [agent] max_tool_rounds 5 # 多轮工具调用上限防止死循环 tool_choice auto # auto / required / none parallel_tool_calls true # 允许一次返回多个工具调用 [logging] level INFO # 每次工具调用都落盘排障时按 trace_id 检索 file ./logs/agent_tool_calls.log record_arguments true record_result true [tools.registry] # 工具元数据文件Harness 启动时加载 path ./settings.json这里几个参数值得展开说。max_tool_rounds是保命参数模型偶尔会陷入「调用→结果不理想→再调用」的循环设成 5 基本够用。parallel_tool_calls打开后模型可以在一次响应里返回多个工具调用请求Harness 并行执行能明显降低多工具场景的总耗时。record_arguments建议一直开着参数错误是 Function Calling 最高频的故障没有参数日志根本没法定位。3.2 settings.json工具元数据与权限{ tools: [ { name: query_ticket_status, description: 查询指定工单的处理状态、处理人和预计完成时间。仅允许查询用户自己的工单。, parameters: { type: object, properties: { ticket_id: { type: string, description: 工单ID格式为 TK- 加6位数字例如 TK-123456 }, user_id: { type: string, description: 发起查询的用户ID格式为 U- 加数字用于权限校验 } }, required: [ticket_id, user_id] }, endpoint: http://127.0.0.1:8000/api/ticket/status, method: POST, required_permission: ticket:query, timeout_seconds: 8 } ], permissions: { default_user: [ticket:query] } }工具描述description的写法直接决定模型选工具的准确率。我踩过的坑是描述写得太笼统比如「查询工单」模型在有多个相似工具时会乱选。正确做法是把「能做什么、不能做什么、参数格式示例」都写进去像上面ticket_id的格式说明能显著降低参数生成错误。required_permission是 Harness 层的权限闸门。模型可以「想」调用任何工具但真正执行前必须过权限校验。高风险操作转账、删除、改权限建议再加一道人工确认不要完全交给模型判断。4. 验证请求发起一次外部系统调用并检查结果配置就绪后写一个最小 Harness 跑通全链路。下面这段 Python 用 OpenAI 兼容 SDK 对接 TaoToken 通道读取上面的配置完成一次真实的工具调用。4.1 最小 Harness 实现# harness.py import json, os, logging, tomllib, requests from openai import OpenAI # 读取配置 with open(config.toml, rb) as f: cfg tomllib.load(f) logging.basicConfig( levelcfg[logging][level], filenamecfg[logging][file], format%(asctime)s %(levelname)s %(message)s ) client OpenAI( base_urlcfg[llm][base_url], api_keyos.environ[cfg[llm][api_key_env]], timeoutcfg[llm][timeout_seconds], ) with open(cfg[tools][registry][path]) as f: registry json.load(f) TOOLS [{type: function, function: { name: t[name], description: t[description], parameters: t[parameters] }} for t in registry[tools]] def execute_tool(name, args): 执行工具调用带权限校验和日志 tool next(t for t in registry[tools] if t[name] name) perm tool.get(required_permission) if perm and perm not in registry[permissions][default_user]: logging.warning(permission denied: %s, name) return {error: fpermission denied: {perm}} logging.info(tool_call name%s args%s, name, json.dumps(args, ensure_asciiFalse)) resp requests.request( tool[method], tool[endpoint], jsonargs, timeouttool[timeout_seconds] ) result resp.json() logging.info(tool_result name%s result%s, name, json.dumps(result, ensure_asciiFalse)) return result def run(user_query): messages [{role: user, content: user_query}] for _ in range(cfg[agent][max_tool_rounds]): resp client.chat.completions.create( modelcfg[llm][model], messagesmessages, toolsTOOLS, tool_choicecfg[agent][tool_choice], ) msg resp.choices[0].message if not msg.tool_calls: return msg.content messages.append(msg) for call in msg.tool_calls: args json.loads(call.function.arguments) result execute_tool(call.function.name, args) messages.append({ role: tool, tool_call_id: call.id, content: json.dumps(result, ensure_asciiFalse), }) return 达到最大工具调用轮数任务未完成 if __name__ __main__: print(run(帮我查一下工单 TK-123456 的状态我是用户 U-7890))4.2 模拟外部系统为了验证链路起一个本地 mock 服务当「外部系统」# mock_server.py from fastapi import FastAPI app FastAPI() app.post(/api/ticket/status) def ticket_status(payload: dict): return { ticket_id: payload.get(ticket_id), status: 处理中, handler: 张三, estimated_completion: 2025-06-01 18:00:00 }启动uvicorn mock_server:app --port 8000然后跑python harness.py。4.3 检查返回结果与日志预期输出类似工单 TK-123456 目前状态为「处理中」处理人是张三预计完成时间为 2025-06-01 18:00:00。同时logs/agent_tool_calls.log里应该能看到两条记录一条tool_call记录模型生成的参数一条tool_result记录外部系统返回。这两条日志是排障的核心依据——参数对不对看第一条外部系统返回什么看第二条。如果模型没有触发工具调用直接回了自然语言先检查tool_choice是不是被设成了none再检查工具描述是否足够清晰。如果触发了但参数是空的多半是required字段没写全或者参数描述缺少格式示例。5. 本篇常见错误排查5.1 401 / 403鉴权失败最常见的原因是 Key 没读到。检查TAOTOKEN_API_KEY环境变量是否导出echo $TAOTOKEN_API_KEY以及config.toml里的api_key_env名字是否和实际环境变量一致。403 则通常是权限问题检查required_permission是否在permissions.default_user里。5.2 404路径拼接错误如果 base URL 写成了带/v1或带查询串的形式SDK 再拼一次/chat/completions就会 404。统一写https://taotoken.net/api让 SDK 自己补路径。5.3 模型不调用工具只回自然语言三个排查方向一是tool_choice被设成none二是工具description太模糊模型判断不出该不该用三是用户 query 本身不需要工具比如「你好」这种。可以先用一句明确需要外部数据的 query 测试比如「查一下工单 TK-123456」。5.4 参数校验失败missing required property模型生成的 JSON 缺了必填字段。解决方式是在参数description里写清楚格式和示例并在 Harness 层做一次 Pydantic 校验校验失败时把错误信息作为tool消息回传给模型让它重新生成。不要直接抛异常中断那样用户体验很差。5.5 工具调用死循环模型反复调用同一个工具、拿到相同结果还不罢休。max_tool_rounds是第一道防线第二道是在 Harness 里记录「同一工具同一参数」的调用次数超过 2 次就中断并返回提示。日志里如果看到连续多条相同tool_call基本就是这个问题。5.6 超时与重试外部系统慢的时候timeout_seconds设太小会频繁失败设太大又拖垮整体响应。经验值是 3~10 秒配合 2 次指数退避重试。重试仍失败时把错误信息回传给模型让它决定是换工具还是告知用户而不是直接 500。6. 下一步把 Harness 接到你的真实系统跑通上面这条链路后你已经有了一个能操作外部系统的最小 Agent。接下来可以做的几件事把 mock 服务换成你真实的业务接口注意在 Harness 层加输入过滤防止模型生成的参数被拼进 SQL 或命令里。工具数量超过 10 个之后考虑用向量检索先筛出 Top 5 相关工具再传给模型能明显提升选择准确率并省上下文。多轮调用场景下把每轮的tool_call_id和结果都存下来方便回溯。如果你要长期跑编码类或 Agent 类任务可以了解下 Coding Plan按量计费更适合高频调用https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_campaignrewrite想先在网页上验证模型对某个工具 Schema 的理解是否准确用模型对话快速试几轮最省事https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_campaignrewrite接入过程中遇到字段对不上、返回结构不一致的问题直接翻接入文档里面有各接口的完整字段说明https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_campaignrewrite最后一句实在话Function Calling 的坑大多不在模型而在你的工具描述和参数 Schema 写得够不够清楚。把description当成写给一个聪明但完全不了解你系统的同事看准确率会肉眼可见地涨。
返回列表