ARTICLE DETAIL

资讯详情

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

Agent工具调用全解析:从Function Calling到权限控制的工程实践

Agent工具调用全解析:从Function Calling到权限控制的工程实践 很多人以为Agent就是“大模型多轮对话”直到自己上手做才发现真正让Agent从“聊天机器人”变成“能干活的数字员工”的不是模型本身的推理能力而是它能不能安全、准确、可控地调用外部工具。函数调用Function Calling和工具集设计是Agent开发里绕不开的核心环节。这篇内容对应我教程的7.2节把Agent工具调用体系从设计到落地的完整链路拆开讲一遍包含工具协议定义、工具集架构、权限控制模型以及我在实际项目中踩过的坑。适合正在做Agent应用开发、或者准备系统学习Agent架构的工程师。哪怕你只是听说过Function Calling但没实际写过也可以从中拿到一套可以直接落地的方案。1. 工具调用的本质为什么Agent必须“长手”1.1 仅靠大模型独自工作边界在哪里先明确一个基础认知大模型本身是“语言脑”它的强项是理解、推理、生成文本但它天生触碰不到外部世界的实时数据。举个例子你问GPT类模型“帮我查一下订单OD20240615的物流状态”它能给出的只有基于训练数据的高概率猜测不可能真实读取业务库。即便你问“今天北京天气”它也没有实时感知能力只能胡编或者给一段“需要联网”的说明。这就是LLM能力的硬边界它只能做“知道”的事情不能做“发生”的事情。一旦需求涉及查询实时状态、执行业务操作、读写数据、调用内部API模型就成了“眼高手低”的纸上谈兵者——什么都懂但什么都干不了。函数调用机制正是为解决这个边界问题而生。它的本质是模型在推理时并不直接执行操作而是产出一个“调用意图”——明确告诉系统“我想调用某个工具、传入这些参数”。真正执行动作的是外部代码模型拿到的则是工具返回的结果再基于结果继续进行推理和回答。1.2 函数调用的完整循环模型、代码、结果之间如何“接力”我见过不少新手把Function Calling理解成“给模型发一个工具列表然后等它返回JSON”这其实只看到了表面。真正完整的调用循环至少要经历以下阶段第一段是意图识别。系统把定义好的工具集合tools作为上下文连同用户问题一并交给模型。模型根据用户请求判断是否需要调用工具如果需要就以结构化格式返回一个函数调用请求包含函数名和参数。第二段是外部执行。应用层接收到模型返回的调用请求后校验参数合法性、检查用户权限、执行真实操作比如查数据库、调HTTP接口拿到真实返回值。第三段是结果回填。把工具执行结果重新拼接给模型此时模型会结合原始问题和工具结果生成最终的自然语言回答。多轮工具调用时这个循环会反复迭代直到模型判断不再需要调用任何工具。这个“推理-执行-回填”的闭环是Agent区别于普通对话应用的分水岭。模型负责“思考”代码负责“行动”两边通过结构化的协议完成协作。1.3 为什么说工具集设计决定了Agent能力的上限工具调用链路建立之后一个Agent能做什么、做得多好、能覆盖多少场景几乎完全取决于它拥有什么工具、这些工具被如何编排。模型本身的能力是底座但工具集才是让底座发挥价值的“技能树”。能力边界等于“底座模型推理能力”与“工具集丰富度”的叠加而不是单纯由模型体积决定。一个只有“查天气”工具的Agent和一个拥有邮件、日历、订单、客诉、CRM、数据看板十余种工具的Agent即便底层是同一个模型实际交付的业务价值也完全不在一个量级。这就像给同一个程序员配上不同的开发环境只给记事本和给他完整IDE、接口文档、沙箱环境产出效率天差地别。所以工具集设计不是“把API接进来”那么简单。它是一门关于标准化、边界划分、容错、安全与可观测性的系统工程。这也是为什么大厂Agent平台都在强调千人千面的自定义工具能力——工具生态某种程度上就是Agent生态的核心资产。2. 工具集设计的方法论从“能用”到“好用”2.1 工具定义的核心为模型提供“可理解”的接口在给Agent设计工具时首先要建立一个认知工具的调用者是模型不是程序员。这意味着工具定义不能像普通API文档那样默认读者具备业务背景。模型只能通过函数名、描述、参数结构来“猜测”工具用途所以描述必须直白、无歧义、覆盖边界情况。我推荐用JSON Schema作为工具协议标准。原因很简单这是目前主流模型厂商包括OpenAI、Anthropic、以及国内主流大模型都支持的格式生态成熟序列化方便校验工具也丰富。一个标准的工具定义长这样tools [ { type: function, function: { name: query_order_status, description: 根据订单号查询订单的当前状态。仅用于查询订单流转信息不包含退改操作。, parameters: { type: object, properties: { order_id: { type: string, description: 订单号格式为OD后跟8位数字例如OD20240615 } }, required: [order_id], additionalProperties: False } } } ]这里有几个容易被忽略的细节。additionalProperties: False能约束模型不要多传乱七八糟的字段required必须写明否则模型可能省略参数导致执行端报错description要尽量包含“允许做什么、不做什么”的边界说明。实践表明描述写得清楚与否直接决定工具调用的准确率有时候同样的工具描述优化后准确率能从60%提到90%以上。2.2 工具命名与聚合如何避免“工具海”淹没模型判断很多团队做工具集时最容易犯的错是“越做越多”。今天加一个查订单的明天加一个查物流的后天再拆一个查售后的最后tools参数里塞了几十个函数。模型的处理窗口有限工具越多选择准确率越低响应延迟也越高——模型要对每个候选工具逐一比对推理时间会显著增加。我的建议是聚合粒度优先动态加载兜底。不是所有工具都要无脑放进上下文。优先设计粒度适中的原子工具比如“查询订单信息”“更新订单状态”“创建售后工单”归类合并而不是“按订单号查客户姓名”“按订单号查商品列表”“按订单号查收货地址”这样碎片化拆解。将领域内强相关的字段归入同一个工具用结构化参数做区分比单独拆分更高效。当工具数量超过15个左右时还需要考虑动态工具加载机制先根据用户意图做一次粗筛选只把候选工具列表传给模型而不是每次全量塞入。这个可以用关键词召回、向量检索或者模型先做一次“意图路由”来实现。2.3 工具选型的边界哪些事该让Agent做哪些不该工具集设计里最难的不是技术而是“取舍”。我在实际项目中总结了一条核心原则高频、低风险、规则清晰的操作优先工具化低频、高风险、需要人工判断的操作要么不做工具要么加入强审批环节。比如“查询订单状态”“查询库存数量”就属于高频低风险适合纳入而“批量修改价格”“删除用户数据”这类事务即便规则清晰也必须走审批流甚至根本不该暴露给Agent自主执行。还有一类操作要特别警惕会产生外部副作用的操作例如发送邮件、扣款、创建工单、修改数据库。这类工具不是说不能接而是必须配套幂等设计、确认机制和操作审计。我见过一个项目Agent误调用了“发送营销短信”工具因为用户说了一句“帮我联系一下这个客户”结果把整个客户列表都发了一遍短信。这个教训后面会展开讲。3. 从零落地Function Calling实战一个带工具的Agent3.1 场景设定设计一个“客户运营助手Agent”这一节我们用实际代码演示一个完整可跑的Agent。场景设为“客户运营助手”需要支持以下工具查询客户基本信息等级、积分、联系方式查询客户历史订单近30天订单列表及状态创建售后工单发送通知消息短信/站内信这四个工具覆盖了“读取型”和“写入型”两类操作正好能引出权限控制的话题。我们先实现读取型工具的调用闭环。3.2 工具执行层的标准实现注册表模式在实际工程项目里我不会把工具逻辑写成一堆if-else。更推荐用注册表模式统一维护一个工具字典按名称映射到具体执行函数再通过统一的执行入口做参数校验、鉴权和调用。import json # 工具执行函数 def query_customer_info(customer_id: str) - dict: # 模拟数据库查询 db { C001: {name: 张明, level: gold, points: 12000}, C002: {name: 李丽, level: silver, points: 3500}, } return db.get(customer_id, {error: customer not found}) def query_customer_orders(customer_id: str, days: int 30) - list: # 模拟订单查询 orders { C001: [ {order_id: OD20240615, amount: 299.0, status: delivered}, {order_id: OD20240702, amount: 159.0, status: shipping}, ] } return orders.get(customer_id, [])[:int(days) // 30] # 工具注册表 TOOL_REGISTRY { query_customer_info: { function: query_customer_info, definition: { type: function, function: { name: query_customer_info, description: 根据客户ID查询客户的基本信息包括姓名、会员等级、积分等。, parameters: { type: object, properties: { customer_id: { type: string, description: 客户ID例如C001 } }, required: [customer_id] } } } }, query_customer_orders: { function: query_customer_orders, definition: { type: function, function: { name: query_customer_orders, description: 查询客户最近一段时间内的历史订单列表返回订单号、金额和状态。, parameters: { type: object, properties: { customer_id: { type: string, description: 客户ID例如C001 }, days: { type: integer, description: 查询最近N天的订单默认30天, default: 30 } }, required: [customer_id] } } } } } def execute_tool(tool_name: str, arguments: dict): 统一工具执行入口查注册表→调用函数→返回结果 if tool_name not in TOOL_REGISTRY: raise ValueError(fUnknown tool: {tool_name}) func TOOL_REGISTRY[tool_name][function] return func(**arguments)这个设计的好处是新增工具时只需在注册表里加一条映射执行入口和鉴权逻辑完全不用改。对于工具数量达到几十个的Agent项目这种架构的可维护性优势非常明显。3.3 对话循环将Function Calling接入完整Agent流程有了工具注册表接下来需要把模型调用和工具执行串联成闭环。我们用一个简化的Agent循环来演示from openai import OpenAI client OpenAI(api_keyyour-api-key) def run_agent(user_message: str, max_iterations: int 5): messages [{role: user, content: user_message}] # 从注册表提取工具定义列表 tools [TOOL_REGISTRY[name][definition] for name in TOOL_REGISTRY] for _ in range(max_iterations): response client.chat.completions.create( modelgpt-4o, messagesmessages, toolstools, tool_choiceauto ) msg response.choices[0].message # 判断模型是否需要调用工具 if not msg.tool_calls: return msg.content # 执行工具调用 messages.append(msg) for tool_call in msg.tool_calls: func_name tool_call.function.name func_args json.loads(tool_call.function.arguments) result execute_tool(func_name, func_args) messages.append({ role: tool, tool_call_id: tool_call.id, content: json.dumps(result, ensure_asciiFalse) }) return Agent execution reached max iterations # 测试 print(run_agent(帮我看一下客户C001的基本信息以及他最近30天的订单情况))这段代码看起来简单但已经覆盖了核心闭环模型返回工具调用意图 - 应用层执行 - 结果回填 - 模型继续推理。注意tool_choiceauto让模型自主决定是否调用工具如果你确定某个场景必须调用某个工具也可以设成{type: function, function: {name: xxx}}强制指定。3.4 多工具协同模型如何在一个问题里串联多个工具上图场景只涉及单次工具调用。但真实需求往往是多步的用户问“C001客户的积分能抵现多少顺便帮他把订单OD20240615的售后工单创建了”这需要Agent先查客户信息、再查订单、最后创建工单连续三次工具调用。模型在面对这类复杂请求时会自动在多个工具间“规划”调用顺序——先调查询工具拿上下文再基于结果调用写入工具。这也是Agent很惊艳的地方它不只是执行单步指令而是像人一样“先查后办”。但这里就引出一个关键风险多工具串联会让每一次独立调用都有机会出错并且出错会被放大。比如查完客户积分后模型得到的积分数字是“12000”如果工具描述里没说明“每100积分抵1元”它可能完全不知道换算规则导致后续判断全错。所以工具之间的“数据契约”也要在描述里讲清楚必要时直接在工具输出的内容里带上计算所需的所有信息。4. 权限控制工具调用的“刹车系统”4.1 为什么权限控制是Agent落地的生死线如果你只是在自己电脑上跑一个“查天气”的Demo聊权限控制可能有点小题大作。但一旦Agent要接入企业系统、要执行真实业务操作权限控制就从“可选优化”变成了“生死线”。原因很直接Agent的每次工具调用都可能是真实的钱、真实的客户、真实的数据。而且大模型存在幻觉和“过度执行”的倾向——它可能把用户的模糊表述理解成具体指令进而执行了不该执行的操作。我在前文提到的“把所有客户都发了短信”的案例就是权限控制缺失的典型Agent拿到了“发送短信”工具的访问权但没有任何约束告诉它“一次最多发给多少人”“是否需要用户二次确认”。权限控制的核心目标有两个一是阻止Agent越权操作——它不能调用当前用户无权使用的工具二是拦截高风险操作——即便用户有权也需要额外确认才能执行。4.2 分级权限模型四级权限体系实战我推荐在Agent项目中采用一套四级权限模型。这个模型不是凭空想出来的而是参考了实际企业内部系统工具设计的通用实践并在几个中型Agent项目中验证过稳定性。级别名称特征典型工具审批要求L0无权限完全不可见/不可调用内部管理接口禁止接入L1只读权限查询数据不产生副作用查客户、查订单、查库存无需确认L2低风险操作有副作用但可控可撤销创建草稿、保存配置用户确认一次L3高风险操作不可逆/涉及资金或隐私发送短信、扣款、删除数据强校验人工审批对应到代码实现就是给工具注册表增加permission_level字段并在execute_tool入口加一道权限检查def execute_tool(tool_name: str, arguments: dict, user_context: dict None): tool_meta TOOL_REGISTRY.get(tool_name) if not tool_meta: raise ValueError(fUnknown tool: {tool_name}) # 权限检查 required_level tool_meta.get(permission_level, L1) user_level user_context.get(permission_level, L1) if user_context else L1 if level_to_int(required_level) level_to_int(user_level): raise PermissionError(fUser lacks permission to call {tool_name}) # L2/L3 工具调用前做二次确认 if required_level in (L2, L3): confirm user_context.get(confirmed_tools, []) if tool_name not in confirm: return {status: need_confirmation, message: f请确认是否执行{tool_name}} func tool_meta[function] return func(**arguments)这个实现里有两个关键点。第一permission_level要放在工具元数据里而不是硬编码在执行函数里——这样新增工具时权限策略一目了然。第二L2/L3工具返回“需要确认”的状态码而不是直接抛异常。这样Agent能理解“操作被拦截”并在对话里向用户说明需要确认而不是直接报错终止。4.3 白名单、用户确认与操作审计的三重保障分级模型解决的是“谁能调用什么”的问题但权限控制要想真正严密还需要配合三重保障机制。第一重是工具白名单。系统层面维护一个“当前会话可用工具”的白名单不在名单里的工具即使被模型选中执行层也要拒绝。这样即使模型被恶意prompt注入诱导也无法调用预设之外的敏感功能。白名单要基于会话动态生成不同用户进入系统时拿到的是完全不同的工具集。第二重是用户确认机制。对于L2/L3工具不能只靠模型“自觉”——Agent在返回调用意图后系统应拦截真实执行先把“待确认操作”返回给前端让用户看到“即将发送短信给客户张明内容xxx”点击确认后系统才真正执行。这个交互做得好不好直接决定用户对Agent的信任感。第三重是操作审计日志。每一次工具调用包括调用者、调用时间、参数、执行结果、确认人、确认时间都必须完整记录。这不仅是合规要求更是排查问题的关键手段。Agent一旦出了“事故”没有审计日志排查起来就像大海捞针。4.4 上下文约束与Token级别的权限控制除了工具级别的权限控制还有一个容易忽略的维度上下文级别的约束。Agent在对话过程中会累计大量的上下文信息如果权限控制只针对“工具调用”这一环而忽略了上下文携带的数据就可能出现数据越权。举个例子A客户问“我家订单发到哪了”Agent在工具层通过客户A的ID查询了订单信息。但如果Agent同时有“模糊查询”能力模型可能在上下文里保留了其他客户的订单快照在后续回复中无意泄露。要解决这个有两类手段一类是数据脱敏工具返回值在进入上下文前对手机号、身份证号等敏感字段做打码处理。除非后续步骤确实需要明文比如发送短信否则一律脱敏。另一类是会话隔离每个用户会话只挂载对应权限的工具实例不同会话之间内存和索引完全隔离。如果Agent有记忆功能更要注意跨用户记忆隔离不能出现用A客户的历史数据推理B客户需求的情况。5. 一次完整的实战给Agent装一套带权限的办公协同工具5.1 场景与工具集规划这一节我们做一个综合性更强的练习设计一个“办公室行政助手Agent”负责处理员工日常行政事务。目标工具包括查询会议室空闲状态只读L1预订会议室写入L2需确认发送会议通知邮件写入L3需审批限频提交采购申请并生成工单写入L2这样的工具集覆盖了“查询-预订-通知-审批”的完整闭环也能复现最常见的权限控制场景。先说明人员角色普通员工权限级别为L2行政主管为L3。5.2 工具注册与权限配置完整代码我们还是沿用注册表模式但这次把权限元数据纳入注册表结构TOOL_REGISTRY { query_room_availability: { function: query_room_availability, permission_level: L1, definition: { type: function, function: { name: query_room_availability, description: 查询指定时间段的会议室空闲情况返回可用会议室列表、位置和容纳人数。, parameters: { type: object, properties: { start_time: {type: string, description: 开始时间格式YYYY-MM-DD HH:MM}, end_time: {type: string, description: 结束时间格式YYYY-MM-DD HH:MM} }, required: [start_time, end_time] } } } }, book_meeting_room: { function: book_meeting_room, permission_level: L2, definition: { type: function, function: { name: book_meeting_room, description: 预订会议室需要指定会议室ID、时间段和参会人数。预订成功后返回会议编号。, parameters: { type: object, properties: { room_id: {type: string, description: 会议室ID}, start_time: {type: string, description: 开始时间格式YYYY-MM-DD HH:MM}, end_time: {type: string, description: 结束时间格式YYYY-MM-DD HH:MM}, attendee_count: {type: integer, description: 参会人数} }, required: [room_id, start_time, end_time, attendee_count] } } } }, send_notification_email: { function: send_notification_email, permission_level: L3, definition: { type: function, function: { name: send_notification_email, description: 向指定邮箱发送会议通知邮件。邮件内容包括会议时间、地点和日程。, parameters: { type: object, properties: { to_address: {type: string, description: 收件人邮箱地址}, subject: {type: string, description: 邮件主题}, content: {type: string, description: 邮件正文内容} }, required: [to_address, subject, content] } } } } }可以看到工具的执行函数本体并不关心权限权限完全由注册表元数据控制。这带来一个重要的工程收益工具函数可以被不同的Agent复用只要在各自的注册表里定义不同的权限级别即可。同样是“发送通知邮件”这个函数给普通员工用是L3需审批给行政助理用可能是L2需确认给系统管理员用甚至可以是L1直接执行。5.3 权限中间件在调用入口统一做拦截真正的Agent项目里我不会在每个Agent代码里复制权限检查逻辑。更合理的做法是把权限控制做成一个独立的中间件或装饰器对所有工具调用统一生效。下面是一个装饰器实现import functools import time def permission_required(level: str, confirm_required: bool False): def decorator(func): functools.wraps(func) def wrapper(*args, **kwargs): request kwargs.get(request_context) if not request: raise PermissionError(Missing request context) user_level request.get(user_level, L1) if level_to_int(level) level_to_int(user_level): raise PermissionError(f权限不足: 需要{level}级别当前用户{user_level}级别) # 限流控制 rate_key f{func.__name__}:{request.get(user_id)}:{time.strftime(%Y%m%d%H)} if check_rate_limit(rate_key, max_calls10): raise RateLimitError(请求过于频繁请稍后再试) return func(*args, **kwargs) return wrapper return decorator permission_required(L3, confirm_requiredTrue) def send_notification_email(to_address: str, subject: str, content: str, request_contextNone): # 实际发送邮件的逻辑 return {status: sent, to: to_address, message_id: MSG20240715001}这个中间件做了三件事权限校验、调用限流、执行函数。其中限流是我特别要强调的一点——很多权限事故不是因为用户“没权限”而是因为Agent在一次请求里反复调用同一个外部接口导致下游系统被刷爆。为每个工具设置单位时间内的调用上限是成本极低的保护手段。5.4 权限确认流程的交互设计实现权限控制后还有一个产品层面的问题当Agent返回“需要确认”时前端怎么展示这个环节做不好用户会非常困惑。我的方案是使用结构化中间状态。Agent在执行层拦截到L2/L3操作时不返回普通文本而是返回一个标准化的确认请求对象。前端识别到这个对象后渲染成卡片一行操作摘要调用哪个工具、关键参数是什么、两个按钮确认/取消用户点击确认后才发起真实工具调用。{ type: confirmation_request, tool_name: send_notification_email, params: { to_address: zhangmingexample.com, subject: 会议通知7月16日项目周会, content: 各位好本周项目周会定于周三上午10点在A301会议室召开... }, risk_level: high, tips: 该操作将向外部邮箱发送一封邮件请确认收件人和内容无误 }好的交互设计能让用户在“无感知的安全”和“有感知的授权”之间取得平衡。低风险操作无感放行高风险操作清晰提示这是权限控制的体验底线。6. 踩坑实录工具调用中最常见的问题与排查技巧6.1 模型死活不调用工具怎么办这是我被问得最多的问题之一。模型不调用工具通常有三个原因。第一个是工具描述模糊。模型搞不清这个工具到底是干嘛的、什么时候该用。排查方法很简单把工具定义单独拿给模型看让它描述“什么情况下会选用这个工具”。如果模型的回答犹豫不决说明描述需要优化。第二个是参数格式不匹配。模型可能生成了正确的工具名但参数的枚举值不合法比如把会议室ID写成了“A301”而不是系统里的“room_301”。这时可以在工具描述的parameters里加上枚举约束enum: [room_301, room_302]。强约束比让模型自由发挥可靠得多。第三个是提示词里缺少引导。有时候不是模型不会而是它觉得自己“直接回答”也能解决问题。这时需要在System Prompt里显式声明工具的优先级比如“当你需要查询实时信息或执行操作时必须调用工具不能凭记忆回答”。这属于提示词工程层面的功课。6.2 工具返回结果格式不统一模型解析混乱项目初期工具返回值的格式往往五花八门有的返回纯字符串有的返回JSON数组有的返回嵌套对象。模型在回填结果时费力地“理解”这些格式一旦理解偏差后续推理就会跑偏。解决方案是统一工具返回契约。我推荐所有工具都返回一个标准包装结构{ success: true, data: { ... }, error: null }如果失败success为falseerror携带可读的错误信息。模型看到success: false后能主动向用户解释“查询失败原因是什么”而不是拿着半个结果硬编。这个看似微小的标准化能显著提升多轮工具调用的稳定性。6.3 如何防止Agent在工具调用中“反复横跳”还有一种常见故障Agent在多个工具之间来回调用始终无法收敛。比如查了会议室又查查完了不订用户催了又查就是不执行预订。这类问题的根源往往是工具化的粒度不合理或prompt里缺少决策规则。我的做法是在System Prompt里增加“执行偏好”一旦获取了必要信息应尽快执行后续操作避免重复查询如果所有必要信息已经齐备而用户没有额外要求默认继续完成完整任务。此外在代码层设置max_iterations上限并添加超时机制防止Agent陷入死循环。6.4 权限误拦截如何平衡安全与效率权限控制太严格Agent动不动就弹“需要确认”用户体验会很差太宽松又可能出事故。这中间的度很难拿捏。我常用的优化手段是动态权限升级同一个工具根据当前用户的会话状态、操作频率、行为风险评分动态调整需要的确认级别。比如同一个用户一天内已经确认过3次类似操作第4次可以只做“轻提示”而非“强确认”。这个策略需要在安全和体验之间反复调参每一类工具都要单独设置。7. 工具集的可观测性让Agent的每一步都可追溯7.1 工具调用日志的结构化设计Agent的调试难度远超传统应用因为它是“推理-执行”的混合体。模型为什么选择调用这个工具为什么传了这几个参数这些问题如果不做结构化日志排查起来基本靠猜。我的日志方案包含四个核心字段session_id会话ID、tool_name工具名、input_args入参、output_summary出参摘要。每次工具调用追加一条记录同时记录模型当时的推理轨迹。用OpenTelemetry这类框架做链路追踪会很方便把LLM调用和工具执行串成一条完整的trace。7.2 成本与性能监控工具调用是隐形开销工具调用不是免费的每一次调用都意味着一次额外的LLM往返模型发起调用请求、回填结果后再次推理Token消耗和延迟都会翻倍。一个多工具串联的任务Token开销可能是一个简单对话的5到10倍。因此工具集设计阶段就要把成本因素纳入考量。一方面控制工具数量和调用轮次避免无效调用另一方面对高频工具的结果做缓存比如会议室空闲状态每分钟刷新一次完全没必要每次实时查。性能指标上重点监控tool_call_time工具调用占总响应时间的比例如果超过70%说明链路存在优化空间。8. 我与工具集设计的一些个人心得这个章节放在最后讲一些没法放进前文框架里的软性经验。工具集设计这件事技术本身并不难——会定义JSON Schema、会写执行函数半天就能跑通一个Demo。难的是“对边界的理解”。我见过太多团队一上来就追求工具数量多把能接的接口全接进来结果模型选择准确率暴跌、权限漏洞百出、维护成本飙升。反过来那些做得好的Agent项目工具集往往克制而精准每个工具都有明确的业务价值和清晰的边界。在设计工具时我给自己定了几条规矩。第一工具描述必须经过“模型测试”不要让开发者自己觉得“应该没问题”就上线。第二任何会对外产生副作用的工具第一天就要接上权限控制不要想着“先跑通再加”。权限这种设计一旦前期缺失后期加的时候要重构很多东西。第三工具返回结果必须标准化宁可损失一点灵活性也要保证模型能稳定解析。Agent开发这个领域迭代速度非常快。今天的主流方案几个月后可能就过时了。但工具调用、权限控制、可观测性这些底层关切不会过时——它们是Agent真正“干活”时绕不开的地基。希望这篇内容能帮你在自己动手做Agent时把地基打得稳一些。
返回列表