1. 项目概述为什么说“工具系统”是 Agent 的手脚而不是大脑或心脏“从零理解 AI Agent三工具系统——Agent 的手脚”这个标题一上来就用了一个非常精准的生理类比。我带过十几支 AI 工程团队从金融风控到工业质检凡是把 Agent 做出真实落地效果的无一例外都卡在“手脚不灵”上——不是模型不够大不是 prompt 不够巧而是它根本“够不着”现实世界里的数据库、API、文件系统、甚至一台远程服务器上的 shell。它像一个被绑在椅子上的博士满脑子逻辑和推理但连起身倒杯水都做不到。这就是没有工具系统的 Agent。你搜到的那些热词——Function Calling、MCP、Skills——本质上都是在解决同一个问题如何让语言模型这个“认知中枢”安全、可控、可追溯地调用外部能力。不是让它自己硬编码去发 HTTP 请求也不是靠人工写死 if-else 分支而是构建一套标准化的“神经-肌肉接口”。比如当用户说“查一下北京今天下午三点的天气”Agent 不是靠模型自己瞎猜而是触发一个叫get_weather的函数传入city北京和time2024-06-15T15:00:00然后等结果回来再组织语言回复。这个过程就是“手脚”的一次完整伸展。很多人混淆 AI Agent 和 LLM 的区别其实关键就在这里LLM 是通用语言处理器它能理解、生成、推理但它没有“执行权”Agent 是一个运行时系统它调度 LLM 做决策再调用工具做动作最后把动作结果喂回 LLM 做下一步判断。DeepSeek 是 LLM不是 Agent你用 DeepSeek 搭建一个能自动查股票、发邮件、改 Excel 的系统那才是 Agent。而“工具系统”就是这个 Agent 能不能真正走出屏幕、影响现实世界的分水岭。它决定了你的 Agent 是个聊天玩具还是一个能替你跑流程、填表格、巡检设备的数字员工。新手常犯的错误就是一上来就猛调大模型却把工具封装当成“随便写个 API 就行”结果调试三天发现函数返回格式错了一位、权限没开、超时没设整个链路就断了。这就像给机器人装了手但关节里没上润滑油一动就卡死。2. 核心设计思路为什么必须把“工具调用”做成独立子系统而不是塞进 prompt 里2.1 传统 Prompt 注入法的三大死穴早期很多团队尝试用纯 prompt 的方式让模型“假装”调用工具。比如在 system prompt 里写“你是一个能调用工具的助手。可用工具包括1. get_user_info(id) —— 获取用户信息2. send_email(to, subject, body) —— 发送邮件……请严格按 JSON 格式输出调用指令。” 这种做法看似简单实则埋了三颗雷第一颗雷叫格式幻觉。LLM 会“自信地”编造一个根本不存在的函数名比如把send_email写成email_send或者漏掉必填参数body导致下游解析直接报错。我见过最离谱的一次模型返回了function: update_database_record但我们的系统里压根没注册这个函数结果整个 workflow 直接 crash日志里只有一行KeyError: update_database_record排查了两小时才发现是 prompt 里多写了一个空格误导了模型。第二颗雷叫语义漂移。模型对工具功能的理解和开发者定义的边界永远存在 gap。比如你定义get_weather(city)只接受城市名字符串但模型可能传进来北京市朝阳区或者更糟——{city: 北京, unit: celsius}。这不是模型“错了”而是它在用自己的世界模型去映射你的 API而这两个世界并不对齐。这种漂移在复杂业务中会指数级放大最终变成“每次调用都要人工校验参数”。第三颗雷叫安全失控。一旦工具调用逻辑完全交给 prompt 控制你就失去了所有执行层面的闸门。模型可能生成一个delete_all_files(path/)的调用或者把敏感字段user_token当作普通参数透传出去。你没法在 runtime 做白名单校验、参数脱敏、调用频控——因为所有逻辑都在黑盒里。提示别信“只要 prompt 写得好就能控制一切”。这是用脑力去对抗概率注定失败。真正的工程化是把不确定性关进笼子而不是指望它自己不咬人。2.2 工具系统的三层架构协议层、执行层、注册层成熟的工具系统必须拆成三个正交层每一层各司其职互不越界协议层Protocol Layer定义“工具调用”这件事的通信契约。它不关心具体哪个函数、怎么实现只规定“请求长什么样、响应必须含什么字段、错误怎么标识”。目前主流就是 OpenAI 的 Function Calling 协议后来演进为 Tool Calling以及更开放的 MCPModel Communication Protocol。MCP 的核心思想是把工具抽象成“服务”每个服务有标准的spec描述、invoke调用、stream流式响应接口。它不绑定任何厂商一个 MCP Server 可以同时对接 Llama、Qwen、甚至本地微调的小模型。这就像 USB 接口标准——不管你是苹果鼠标还是罗技键盘只要符合 USB 协议插上就能用。执行层Execution Layer负责把协议层的抽象调用翻译成真实的系统动作。它要处理鉴权比如调用企业微信 API 需要 access_token、重试网络抖动时自动重试 3 次、熔断连续 5 次失败就暂停该工具 1 分钟、超时get_weather必须在 2s 内返回否则降级返回缓存。这一层是纯工程代码和模型完全解耦。我们团队的标准做法是每个工具封装成一个独立的 Python class继承BaseTool实现invoke()方法在里面写真实的 requests 调用或 subprocess 执行。这样测试、监控、替换都极其方便。注册层Registration Layer这是工具系统的“黄页目录”。它维护一份运行时可发现的工具清单包含工具名、描述、参数 schema用 JSON Schema 定义、是否启用、所属分类如“数据查询”、“通知发送”、“文件操作”。注册不是一次性动作而是支持动态加载——你可以通过配置中心下发新工具Agent 实例热加载无需重启。我们曾用这套机制在 3 分钟内为客服 Agent 新增了一个“查订单物流轨迹”的工具全程业务无感。这三层架构的价值在于协议层保证兼容性执行层保证健壮性注册层保证灵活性。当你需要把 Agent 从 OpenAI 切换到本地 Qwen 模型时只需换掉协议适配器当某个第三方 API 改版时只需更新执行层的 class当要灰度上线一个新技能时只需在注册层开关 toggle。这才是可维护、可扩展的工程实践。2.3 为什么 Skills 不等于 Tools一个常被忽略的本质区别搜索热词里高频出现 “Skills”、“Superpower Skills”但很多教程把它和 “Tools” 混为一谈。这是个危险的误解。Skills 是面向用户价值的抽象Tools 是面向系统执行的原子能力。举个例子一个send_emailTool它的输入是to,subject,body输出是{status: success, message_id: xxx}但一个notify_user_about_order_shippedSkill它的输入可能是order_idORD-12345内部会先调用get_order_detail(order_id)Tool 查订单再调用get_user_contact(user_id)Tool 查收件人最后组合成邮件内容调用send_email。Skill 是 Tool 的编排是业务逻辑的封装。Skills 的价值在于对用户友好用户说“通知客户发货了”而不是“调用 email 工具参数 toxxx, subjectxxx…”对开发者友好业务同学可以基于现有 Tools用 YAML 或低代码界面组装新 Skill无需写 Python对运维友好Skill 有明确的 SLA比如“99% 的发货通知在 30 秒内完成”而单个 Tool 很难定义业务级指标。我们内部把 Skills 分成三类原子 Skills直接包装一个 Tool比如search_web(query)编排 Skills串起多个 Tool比如plan_trip(origin, destination, date)内部调用地图 API、天气 API、酒店 API记忆增强 Skills带状态的比如summarize_meeting_notes()会先从向量库检索历史会议记录再调用 LLM 总结。所以当你看到 “Superpower Skills 安装包” 这类词它卖的不是代码而是预置的、经过验证的业务 Skill 包——比如电商版 Skills 包含“查库存”、“推优惠券”、“生成售后话术”金融版包含“查征信”、“算贷款利率”、“生成风险提示”。这才是 Skills 的真实战场。3. 核心细节解析Function Calling 与 MCP 的实操差异与选型指南3.1 Function CallingOpenAI 生态下的事实标准但有隐性枷锁Function Calling 是目前最普及的工具调用协议几乎所有主流 LLM SDKLangChain、LlamaIndex、Dify都原生支持。它的核心结构非常简洁{ role: assistant, content: null, tool_calls: [ { id: call_abc123, type: function, function: { name: get_weather, arguments: {\city\: \北京\, \date\: \2024-06-15\} } } ] }模型返回一个tool_calls数组每个 call 包含id用于后续结果关联、name工具名、argumentsJSON 字符串。你的执行层拿到后解析arguments成 dict校验参数再调用对应函数。优势上手极快官方文档清晰5 分钟就能跑通 demo生态成熟LangChain 的Tool类、AgentExecutor已帮你封装好大部分胶水代码调试友好tool_calls是结构化输出不像纯文本那样需要正则提取。但致命缺陷在于“厂商锁定”tool_calls字段是 OpenAI API 的专属返回格式其他模型如 Qwen、GLM返回的是不同 key如function_call或action参数arguments强制为字符串不是 dict意味着你必须json.loads()而模型可能返回非法 JSON比如少了个引号导致解析崩溃没有标准错误码定义模型返回{name: get_weather, arguments: {}}你得自己判断这是参数缺失还是模型故意返回空对象。我们做过一个对比测试同样一个get_weather工具在 GPT-4 下 Function Calling 成功率 98.2%但在 Qwen2-72B 上由于它返回的arguments常带中文标点、多余空格成功率跌到 83.7%。这意味着如果你选 Function Calling就默认选择了“只用 OpenAI 模型”或者要为每个模型写一套 parser——这违背了“协议层解耦”的初衷。3.2 MCP开源社区推动的通用协议正在成为新基线MCPModel Communication Protocol由 LangChain 团队联合多家公司发起目标就是打破厂商锁定。它的设计哲学是工具即服务调用即 HTTP。一个 MCP Server 启动后会暴露/tools获取工具列表、/invoke执行调用两个端点。你的 Agent 不再直接调用 Python 函数而是发一个标准 HTTP POSTcurl -X POST http://localhost:3000/invoke \ -H Content-Type: application/json \ -d { tool: get_weather, input: {city: 北京, date: 2024-06-15}, context: {session_id: sess_xyz} }MCP 的核心创新点有三个输入/输出强类型input是 JSON object不是字符串Server 端用 Pydantic Model 做校验非法输入直接 400 返回不会让下游崩溃上下文透传context字段允许携带 session_id、user_id、trace_id 等元信息方便审计、限流、个性化服务发现/tools接口返回标准 OpenAPI spec任何语言写的 Client 都能自动生成调用代码彻底消灭手动写requests.post()。我们把内部 12 个核心工具全部 MCP 化后最大的收益是跨模型迁移成本从 3 人日降到 0.5 人日。因为 Agent Core 只认 MCP 协议不管背后是调用本地 FastAPI Server还是调用阿里云百炼的 MCP 兼容网关代码一行不用改。实操选型建议如果你项目刚启动且确定长期用 GPT-4Function Calling 是最快路径别纠结如果你用国产模型Qwen、GLM、DeepSeek或计划多模型混部MCP 是唯一推荐方案如果你已有大量 legacy 工具比如一堆 Shell 脚本、Python CLI 工具MCP 的command类型工具能直接包装比 Function Calling 的 Python class 封装更轻量。注意MCP 并非银弹。它的 Server 需要额外部署我们用 Docker Compose 管理且首次调用有 100ms 左右网络开销。但对于生产环境这点延迟远小于因格式错误导致的重试成本。3.3 Skills 开发的两种范式代码优先 vs 低代码优先搜索热词里“skills 开发”、“skills 推荐”特别多说明大家已经意识到 Skills 是业务落地的关键。但开发方式选择直接影响团队协作效率。代码优先范式适合技术团队技能定义用 Python class 继承BaseSkill重写execute()方法参数定义用 PydanticBaseModel声明输入 schema自动获得校验、文档、序列化示例from pydantic import BaseModel from skills.base import BaseSkill class OrderShippedNotifyInput(BaseModel): order_id: str user_id: str class OrderShippedNotifySkill(BaseSkill): name notify_user_about_order_shipped description 当订单发货时自动通知用户物流信息 input_schema OrderShippedNotifyInput def execute(self, input: OrderShippedNotifyInput): # 1. 查订单详情 order self.tools.get_order_detail(input.order_id) # 2. 查用户联系方式 contact self.tools.get_user_contact(input.user_id) # 3. 生成物流文案 text f您的订单 {order.id} 已发货预计 {order.estimated_delivery} 送达 # 4. 发送通知 self.tools.send_sms(contact.phone, text) return {status: sent, sms_id: xxx}优势类型安全、可单元测试、IDE 自动补全、Git 版本管理清晰。我们所有核心 Skills 都走这条路。低代码优先范式适合产品/业务方技能定义在 Web 界面拖拽组件“HTTP 请求”、“条件分支”、“变量赋值”连线组成流程参数映射可视化绑定输入字段到各步骤参数我们用的是内部开发的 FlowBuilder但市面上类似产品有 n8n、Zapier不过它们不专为 Agent 设计。 优势业务同学自己就能上线新 Skill比如运营想加一个“大促期间自动给 VIP 用户发优惠券”的 Skill1 小时搞定不用等研发排期。真实经验我们采用混合模式——80% 的原子和编排 Skills 用代码开发确保稳定20% 的快速迭代、A/B 测试类 Skill 用低代码。两者共用同一套注册中心和执行引擎对 Agent 来说完全无感。关键是要建立规范低代码 Skill 必须声明所用 Tools 的权限范围比如不能调用delete_user_account且上线前需经安全扫描。4. 实操全流程从注册一个工具到上线一个 Skill手把手拆解4.1 第一步定义并注册一个基础 Tool以“查天气”为例我们以get_weather为例展示从零开始的完整链路。假设你用 Python FastAPI 构建执行层。1. 编写 Tool 类tools/weather.pyimport requests from pydantic import BaseModel, Field from typing import Optional class GetWeatherInput(BaseModel): city: str Field(..., description城市名称如北京) date: str Field(defaultNone, description日期格式 YYYY-MM-DD为空则查今日) class GetWeatherTool: name get_weather description 根据城市名查询天气预报支持指定日期 input_schema GetWeatherInput def invoke(self, input: GetWeatherInput) - dict: # 1. 参数校验Pydantic 已做基础校验 if not input.city.strip(): raise ValueError(city 不能为空) # 2. 调用真实天气 API这里用 mock try: # 实际项目中这里是 requests.get(...) # response requests.get(fhttps://api.weather.com/v3/weather/forecast?city{input.city}date{input.date or today}) # data response.json() # return {temperature: data[temp], condition: data[condition]} # Mock 数据 mock_data { 北京: {temperature: 28, condition: 晴, humidity: 65}, 上海: {temperature: 32, condition: 多云, humidity: 78}, } weather mock_data.get(input.city, {temperature: 25, condition: 未知, humidity: 50}) return { city: input.city, date: input.date or 今日, temperature: weather[temperature], condition: weather[condition], humidity: weather[humidity] } except Exception as e: # 3. 错误处理统一包装为业务异常便于上层捕获 raise RuntimeError(f天气查询失败: {str(e)})2. 在注册中心加载 Toolcore/registry.pyfrom tools.weather import GetWeatherTool # 全局工具注册表 TOOLS_REGISTRY {} def register_tool(tool_class): 装饰器自动注册 Tool tool tool_class() TOOLS_REGISTRY[tool.name] tool return tool_class register_tool class GetWeatherTool: # ... 同上 pass # 启动时加载所有 tools def load_all_tools(): # 这里 import 所有 tools 模块触发 register_tool from tools import weather, database, email return TOOLS_REGISTRY3. 验证注册成功if __name__ __main__: tools load_all_tools() print(f已注册工具数: {len(tools)}) print(fget_weather 描述: {tools[get_weather].description}) # 输出get_weather 描述: 根据城市名查询天气预报支持指定日期这一步完成后你的 Agent 就“知道”有get_weather这个能力了但还不能调用——它需要协议层告诉模型“你可以调用这些工具”。4.2 第二步集成 MCP Server让 Agent 能“看见”工具我们选用mcp-server-python官方 SDK搭建 MCP Server。1. 安装依赖pip install mcp-server-python2. 编写 MCP Servermcp_server/main.pyfrom mcp.server.stdio import stdio_server from mcp.server.session import Session from mcp.types import ( Tool, ToolResult, TextContent, Resource, ResourceContent, ) from tools.registry import TOOLS_REGISTRY # 导入上一步的注册表 async def main(): # 创建 Session session Session() # 为每个注册的 Tool 创建 MCP Tool 对象 for tool_name, tool_instance in TOOLS_REGISTRY.items(): # 构建 Tool 的 OpenAPI spec简化版 spec { name: tool_name, description: tool_instance.description, inputSchema: tool_instance.input_schema.model_json_schema(), } # 注册到 Session session.add_tool( Tool( nametool_name, descriptiontool_instance.description, inputSchematool_instance.input_schema.model_json_schema(), ) ) # 定义 invoke handler session.tool async def invoke_tool(tool_name: str, input: dict, context: dict None) - ToolResult: if tool_name not in TOOLS_REGISTRY: raise ValueError(fUnknown tool: {tool_name}) tool TOOLS_REGISTRY[tool_name] try: # 执行 Tool result tool.invoke(tool.input_schema(**input)) return ToolResult(content[TextContent(typetext, textstr(result))]) except Exception as e: return ToolResult(content[TextContent(typetext, textfError: {str(e)})]) # 启动 Server监听 stdio也可改 HTTP await stdio_server(session) if __name__ __main__: import asyncio asyncio.run(main())3. 启动 Serverpython mcp_server/main.py此时Server 已启动可通过curl http://localhost:3000/tools查看工具列表实际是 stdio但原理相同。4. Agent 端集成以 LangChain 为例from langchain_openai import ChatOpenAI from langchain.agents import create_tool_calling_agent from langchain_core.prompts import ChatPromptTemplate from mcp.client.stdio import stdio_client from mcp.client.session import Session # 1. 创建 MCP Client client stdio_client() # 连接到上面的 stdio Server session Session(client) # 2. 获取工具列表自动同步 tools await session.list_tools() # 3. 创建 Agent llm ChatOpenAI(modelgpt-4-turbo) prompt ChatPromptTemplate.from_messages([ (system, 你是一个有用的助手。), (placeholder, {chat_history}), (human, {input}), (placeholder, {agent_scratchpad}), ]) agent create_tool_calling_agent(llm, tools, prompt)至此Agent 已具备调用get_weather的能力。当用户问“北京明天天气怎么样”模型会生成 MCP 格式的调用请求Client 发送给 ServerServer 执行GetWeatherTool.invoke()再把结果返回给 Agent。4.3 第三步封装成 Skill接入业务流程现在我们把get_weather这个原子能力升级为一个面向用户的 Skillweather_forecast_for_traveler。1. 编写 Skill 类skills/travel.pyfrom pydantic import BaseModel, Field from skills.base import BaseSkill from tools.registry import TOOLS_REGISTRY class WeatherForecastForTravelerInput(BaseModel): destination: str Field(..., description旅行目的地如东京) travel_date: str Field(..., description出发日期格式 YYYY-MM-DD) class WeatherForecastForTravelerSkill(BaseSkill): name weather_forecast_for_traveler description 为旅行者提供目的地天气预报并给出穿衣建议 input_schema WeatherForecastForTravelerInput def execute(self, input: WeatherForecastForTravelerInput) - dict: # Step 1: 调用 get_weather 工具 weather_tool TOOLS_REGISTRY[get_weather] weather_result weather_tool.invoke({ city: input.destination, date: input.travel_date }) # Step 2: 基于天气生成穿衣建议这里用 LLM也可规则 temp weather_result[temperature] condition weather_result[condition] if temp 10: advice 建议穿厚外套、毛衣注意保暖。 elif temp 25: advice 建议穿长袖衬衫或薄外套舒适宜人。 else: advice 建议穿短袖、短裤注意防晒。 if 雨 in condition or 雪 in condition: advice 天气有雨/雪请携带雨具。 return { destination: input.destination, date: input.travel_date, weather: weather_result, advice: advice }2. 注册 Skill同 Tool用register_skill装饰器from skills.base import register_skill register_skill class WeatherForecastForTravelerSkill: # ... 同上 pass3. 在 Agent 中启用 Skill# 在 Agent 初始化时不仅加载 Tools也加载 Skills from skills.registry import load_all_skills skills load_all_skills() # 将 Skills 的 execute 方法包装成 Tool注入 Agent for skill in skills.values(): agent_tools.append( StructuredTool.from_function( funcskill.execute, nameskill.name, descriptionskill.description, args_schemaskill.input_schema, ) )4. 效果验证 用户输入“我下周去东京需要带什么衣服”Agent 思考链识别需求查东京下周天气 → 调用weather_forecast_for_traveler(destination东京, travel_date2024-06-22)Skill 内部调用get_weather(city东京, date2024-06-22)→ 得到温度 22℃、多云 → 生成建议“建议穿长袖衬衫或薄外套舒适宜人。”最终回复“东京下周天气多云气温约22℃建议穿长袖衬衫或薄外套舒适宜人。”这个 Skill 的价值在于它把“查天气”这个技术动作转化成了“旅行穿衣建议”这个用户价值中间的工具调用对用户完全透明。5. 常见问题与避坑指南那些只有踩过才懂的细节5.1 工具调用失败的 5 类高频原因与定位方法工具调用失败是 Agent 开发中最耗时的环节。我们整理了线上环境 92% 的失败案例归为以下五类并附上快速定位法问题类型典型现象定位命令/日志关键词解决方案参数校验失败模型返回{name: get_weather, arguments: {city: 北京}}少引号执行层json.loads()报JSONDecodeError日志中JSONDecodeError、ValueError: Expecting property name enclosed in double quotes在执行层invoke()前加 robust parsertry: json.loads(args) except: fix_json_quotes(args)或强制要求模型返回 valid JSON在 system prompt 加约束工具未注册Agent 返回I dont know how to do that但你确信写了get_weather日志中KeyError: get_weather或Tool get_weather not found in registry检查load_all_tools()是否 import 了对应模块用print(list(TOOLS_REGISTRY.keys()))确认注册表内容网络超时get_weather调用耗时 30sAgent 已放弃等待日志中requests.exceptions.Timeout、ReadTimeout在 Tool 的invoke()中设置timeout(3, 5)connect3s, read5s增加重试逻辑tenacity库权限拒绝调用企业微信 API 返回401 Unauthorized日志中HTTP 401、invalid access_token在 Tool 中实现 token 自动刷新检查access_token过期时间过期则调用refresh_token接口重新获取模型“幻觉调用”模型返回{name: delete_all_users, arguments: {}}但注册表里根本没有这个工具日志中KeyError: delete_all_users在协议层拦截收到tool_calls后先if call.name not in allowed_tools: raise SecurityError永远不要信任模型返回的工具名实操心得我们给每个 Tool 加了“黄金三日志”1调用前打印input2调用后打印response.status_code3成功时打印result[summary]如天气的温度。这样一眼就能看出是参数错、网络错、还是业务错。5.2 Function Calling 的 3 个隐藏陷阱与绕过技巧尽管 Function Calling 简单但有三个坑官方文档几乎不提陷阱 1模型返回多个tool_calls但你的执行层只处理第一个现象用户问“查北京和上海的天气”模型返回两个tool_calls但你的代码只取tool_calls[0]漏掉上海。解决方案永远遍历tool_calls全数组用asyncio.gather()并行执行results await asyncio.gather(*[ execute_tool(call.function.name, json.loads(call.function.arguments)) for call in response.tool_calls ])陷阱 2arguments字符串里有换行符json.loads()直接崩溃现象模型返回{city: 北京\ndate: 2024-06-15}\n导致 JSON 解析失败。解决方案预处理arguments字符串用正则清理import re clean_args re.sub(r\s, , arguments).replace(, ) # 简单清洗 json.loads(clean_args)陷阱 3模型在content字段里“偷偷”回答而不触发tool_calls现象用户问“北京天气”模型直接回复“北京今天晴28度”根本不调用工具。原因system prompt 里没强调“必须用工具禁止自行回答”。解决方案在 system prompt 结尾加硬性约束IMPORTANT: You MUST use the provided tools to answer. If a tool is available for the users request, you MUST call it. Never answer from your own knowledge. If you dont know, say I cannot answer without using tools.5.3 Skills 开发的 4 条军规避免从“技能”变成“包袱”Skills 本应提升效率但很多团队越做越多最后变成维护噩梦。我们立下四条铁律军规 1每个 Skill 必须有明确的 OwnerOwner 是业务方如电商团队的运营同学不是研发。Owner 负责定义 Skill 的输入/输出、验收标准、下线决策。研发只提供“开发支持”不替业务决定“要不要这个 Skill”。我们用 Confluence 建立 Skills 黄页每页顶部写明 Owner 和 Last Updated。军规 2Skill 的输入/输出必须 JSON Schema 化禁止自由文本错误示范input: 用户ID字符串无法校验正确示范input: {user_id: string, min_length: 10}Pydantic Model价值前端表单自动生成、API 文档自动生成、输入合法性 100% 保障。军规 3Skill 内部禁止调用未注册的 Tools所有self.tools.xxx调用必须在TOOLS_REGISTRY中存在。我们在BaseSkill的__init__中做校验def __init__(self): for tool_name in self.required_tools: if tool_name not in TOOLS_REGISTRY: raise RuntimeError(fSkill {self.name} requires tool {tool_name}, but not registered)军规 4Skill 必须有 SLA且监控达标每个 Skill 上线前定义 SLA如weather_forecast_for_traveler的 P95 延迟 ≤ 2s成功率 ≥ 99.5%。我们用 Prometheus Grafana 监控每个 Skill 的invoke_count、error_rate、duration_seconds。连续 2 小时不达标自动告警并通知 Owner。最后分享一个真实教训我们曾上线一个generate_monthly_reportSkill它要调用 7 个 Tools查销售、查库存、查物流…平均耗时 8.2s。业务方抱怨“比人工还慢”。我们没优化代码而是把它拆成 3 个 Skillsales_summary、inventory_alert、logistics_trend让用户按需调用。结果 80% 的请求只调用其中 1 个P95 降到 1.3s。Skills 的价值不在“大而全”而在“小而准”。