AI Agent工具设计五原则:让LLM稳定准确调用API

📅 2026/7/2 17:25:59 👁️ 阅读次数 📝 编程学习
AI Agent工具设计五原则:让LLM稳定准确调用API

1. 项目概述:为什么“工具设计”才是AI Agent落地的真正分水岭

你有没有试过给一个AI Agent塞进十几个工具——数据库查询、天气API、邮件发送、PDF解析、日程同步、代码执行……结果它要么死循环调用同一个工具,要么在关键步骤上完全无视你精心准备的工具描述,甚至把“发送邮件”和“删除文件”两个动作混为一谈?我做过不下37个生产级Agent项目,从金融风控助手到医疗问诊中台,踩过的坑里,82%不是出在模型选型或提示词工程上,而是卡在工具(Tool)的设计本身。这篇说的“5 Tool Design Secrets”,不是玄学口诀,是我在真实交付场景中反复验证、被客户验收单签字确认过的五条硬性设计原则。它们不讲大道理,只解决一个最朴素的问题:让AI Agent能稳定、准确、可预测地调用你的工具。关键词“AI Agents”“Tool Design”“LLM Integration”贯穿始终,适合正在搭建RAG+Agent混合系统的产品经理、想把内部系统接入大模型的后端工程师,以及被“Agent总不按预期工作”折磨到凌晨三点的算法同学。它不教你怎么写system prompt,也不聊什么Orchestrator架构图,就聚焦在——你写在tools = [...]列表里的那几行JSON Schema,到底该怎么写才不翻车。

这五条秘密,每一条都对应一个真实血泪现场:比如某次给省级政务平台做政策解读Agent,我们花两周调优了function calling的temperature和top_p,最后发现根本问题是工具参数名用了doc_id而文档系统实际认的是document_uuid;又比如某电商客服Agent上线首日,因工具返回字段名is_successsuccess_flag混用,导致37%的订单状态查询被判定为失败并触发人工兜底,成本激增。这些都不是模型能力问题,是工具契约(Tool Contract)没立好。所以这篇文章不谈“未来趋势”,只讲今天下午你改完代码就能上线生效的实操逻辑。它面向的是已经能把LLM跑起来、但总在工具集成环节卡壳的实战派——你不需要从零学LangChain,但需要知道为什么你写的get_user_profile工具,AI就是不肯调用。

2. 工具设计底层逻辑:从“人类可用”到“机器可解”的三重跃迁

2.1 为什么90%的工具定义本质上是“对人类友好的错误”

先看一个典型反例——某团队为CRM系统设计的update_contact_info工具:

{ "name": "update_contact_info", "description": "更新联系人信息。请提供联系人ID和要修改的字段。", "parameters": { "type": "object", "properties": { "contact_id": {"type": "string", "description": "联系人的唯一标识"}, "fields_to_update": {"type": "object", "description": "要更新的字段对象,例如{'phone': '138xxxx', 'email': 'a@b.com'}"} }, "required": ["contact_id"] } }

这段定义看起来很规范,对吧?但它在三个层面彻底失效:

  • 语义模糊层fields_to_update是任意object,AI无法推断哪些字段合法。当用户说“把张三的邮箱改成test@demo.com”,AI可能生成{"phone": "test@demo.com"}——因为它根本不知道CRM系统只允许更新emailphoneaddress这三个字段。
  • 类型失真层contact_id标为string,但实际数据库要求是16位UUID格式(如a1b2c3d4-e5f6-7890-g1h2-i3j4k5l6m7n8)。AI生成"123"这种值,接口直接400报错,而错误反馈又不会回传给LLM用于修正。
  • 契约断裂层:工具返回值未定义。AI调用后收到{"status": "ok", "updated_at": "2024-05-20T10:30:00Z"},但它无法判断这是成功还是部分成功(比如邮箱更新了但电话没变),更无法提取updated_at用于后续时间敏感操作。

这暴露了工具设计的第一个致命误区:把工具当API文档写,而不是当机器可执行的契约写。人类开发者看fields_to_update能脑补出字段列表,但LLM没有上下文记忆,它只能基于当前token概率采样。所以工具设计的第一重跃迁,是把“人类可读的描述”变成“机器可解的约束”。

2.2 三重跃迁模型:从Schema到Runtime的全链路对齐

真正的工具设计,必须完成以下三重跃迁:

跃迁层级人类视角机器视角关键动作失败后果
L1:语义到结构“更新联系人信息”必须明确contact_id+email(string) +phone(string) +address(string) 四个固定字段将自由文本描述转为枚举式参数定义,禁用object/anyOf等模糊类型AI生成非法字段名,如{"fax": "xxx"},后端直接拒绝
L2:结构到实例“contact_id是字符串”contact_id必须匹配正则^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$,且长度严格36字符在Schema中嵌入正则校验长度约束,而非仅靠type声明AI生成"abc123",触发400错误,无重试机制,任务中断
L3:实例到反馈“更新成功”返回值必须包含{"result": "success", "updated_fields": ["email"], "timestamp": "ISO8601"},且result值限定为["success", "partial_success", "failed"]定义强类型返回Schema,包含状态码、变更摘要、时间戳,禁止自由文本AI收到{"msg": "OK!"},无法解析成功状态,误判为失败并重复调用

我经手的37个项目里,89%的线上故障源于L2跃迁缺失——开发团队认为“后端会校验”,但忘了LLM调用是异步的,错误发生在函数执行后,而LLM的决策闭环必须在调用前完成。所以我的第一条秘密,就是强制在工具定义层完成L1-L2-L3的全链路对齐。这不是增加工作量,是把本该在运行时抛给用户的错误,提前到设计时由Schema捕获。就像汽车安全带,不是限制司机自由,是把碰撞风险前置化解。

2.3 实战验证:同一功能的两种定义对比

我们以“查询用户最近3笔订单”为例,对比传统写法与跃迁后写法:

传统写法(故障率63%):

{ "name": "get_recent_orders", "description": "获取用户最近的订单列表", "parameters": { "type": "object", "properties": { "user_id": {"type": "string"}, "limit": {"type": "integer", "default": 3} }, "required": ["user_id"] } }

跃迁后写法(故障率<2%):

{ "name": "get_recent_orders", "description": "获取指定用户最近N笔订单(N≤10)。返回订单ID、状态、金额、创建时间。", "parameters": { "type": "object", "properties": { "user_id": { "type": "string", "description": "用户唯一标识,必须为16位十六进制UUID,格式:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", "minLength": 36, "maxLength": 36 }, "limit": { "type": "integer", "description": "返回订单数量,范围1-10", "minimum": 1, "maximum": 10, "default": 3 } }, "required": ["user_id"], "additionalProperties": false }, "returns": { "type": "object", "properties": { "result": { "type": "string", "enum": ["success", "user_not_found", "internal_error"] }, "orders": { "type": "array", "items": { "type": "object", "properties": { "order_id": {"type": "string"}, "status": {"type": "string", "enum": ["pending", "shipped", "delivered", "cancelled"]}, "amount": {"type": "number", "multipleOf": 0.01}, "created_at": {"type": "string", "format": "date-time"} }, "required": ["order_id", "status", "amount", "created_at"] } }, "timestamp": {"type": "string", "format": "date-time"} }, "required": ["result", "timestamp"] } }

差异点在于:

  • user_id增加了patternminLength/maxLength,堵死非法输入;
  • limitminimum/maximum锁定范围,避免AI生成999导致超时;
  • additionalProperties: false禁止AI传入sort_by等未定义字段;
  • returns明确定义了所有可能返回值及结构,让AI能精准解析结果。

提示:很多团队忽略returns定义,认为“LLM自己能理解返回内容”。实测数据显示,未定义returns的工具,AI对返回状态的解析准确率仅为41%,而定义后提升至98.7%。这不是玄学,是让LLM的token预测有明确锚点。

3. 五大设计秘密详解:每一条都来自线上事故复盘

3.1 秘密一:参数命名必须与领域实体1:1映射,禁用任何抽象代称

这是最常被忽视却影响最深的一条。某银行项目中,我们定义了一个get_account_balance工具,参数名为acct_num。但核心系统实际字段叫account_number,且数据库索引只建在account_number上。AI生成{"acct_num": "123456"},工具层做字符串映射acct_num → account_number,看似可行。但问题来了:当用户说“查尾号5678的账户余额”,AI可能生成{"acct_num": "5678"}(只取尾号),而account_number是16位数字,导致全表扫描超时。

正确做法是参数名即实体名

{ "name": "get_account_balance", "parameters": { "type": "object", "properties": { "account_number": { "type": "string", "description": "银行账户号码,16位纯数字,不可省略前导零", "pattern": "^\\d{16}$" } }, "required": ["account_number"] } }

为什么有效?

  • 消除歧义account_numberacct_num更无歧义,AI不会联想到account_idaccount_code
  • 强化约束pattern直接绑定业务规则,AI生成"123"会被Schema校验拦截;
  • 降低心智负担:前端、后端、Agent开发团队看到同一名称,无需额外文档对齐。

我坚持所有工具参数名必须满足:在公司内部数据字典中能直接搜到同名字段。如果字典里叫customer_phone,工具参数绝不能叫phone_number。曾有个团队为“获取客户信息”工具定义了cid参数,结果测试时发现,销售系统用cid,客服系统用customer_id,BI系统用cust_no——三个系统根本不是同一实体。最后我们花了三天统一数据字典,才让Agent调用成功率从54%升到99.2%。

注意:别信“AI能自动映射”的说法。LLM没有数据库schema知识,它的映射基于训练数据中的统计共现,而acct_numaccount_number在公开语料中几乎不共现。强行映射等于把业务逻辑交给概率。

3.2 秘密二:每个工具必须有且仅有一个明确的“主谓宾”动作,禁止复合动词

看这个反例:update_user_profile_and_send_notification。名字长达5个单词,它违反了两个铁律:

  • 主谓宾不唯一:“更新”和“发送”是两个独立动作,AI无法判断哪个是主目标;
  • 副作用不可控:通知发送失败是否影响资料更新?失败时应重试还是回滚?工具契约无法定义。

正确拆分方式

  • update_user_profile:只负责更新数据库,返回{"result": "success", "updated_fields": ["email"]}
  • send_notification:只负责发消息,接收{"recipient": "user_id", "template_id": "welcome_v2", "context": {"email": "a@b.com"}}

拆分后优势:

  • 可组合性:Agent可先调update_user_profile,成功后再调send_notification,失败时可单独重试通知;
  • 可观测性:监控系统能分别统计资料更新成功率(99.8%)和通知送达率(92.1%),定位瓶颈;
  • 可测试性:单元测试只需Mock一个工具,而非模拟整个业务流。

某SaaS公司曾用复合工具process_payment_and_issue_invoice,上线后发现支付成功但发票生成失败时,财务系统收不到任何告警,因为错误被吞在复合工具里。拆成process_paymentissue_invoice后,他们加了简单告警:“issue_invoice失败率>5%时短信通知财务主管”,问题响应时间从平均8小时降到17分钟。

3.3 秘密三:返回值必须包含“机器可操作的状态摘要”,禁用自由文本消息

这是线上故障的头号来源。某物流Agent工具get_package_status返回:

{"message": "您的包裹已签收,签收人:张三,时间:2024-05-20 14:30"}

AI看到message,无法提取结构化状态。当用户问“包裹到了吗?”,AI可能回答“签收人是张三”,而非“已签收”。更糟的是,当message变成“系统繁忙,请稍后再试”,AI无法区分这是临时错误(可重试)还是永久错误(需人工介入)。

正确返回模式

{ "status": "delivered", "status_code": 200, "delivery_details": { "signatory": "张三", "signed_at": "2024-05-20T14:30:00+08:00", "location": "北京市朝阳区XX大厦前台" }, "timestamp": "2024-05-20T14:30:05+08:00" }

其中:

  • status有限枚举值["pending", "in_transit", "delivered", "returned", "failed"]),AI可直接用于条件分支;
  • status_code是HTTP标准码,便于与现有监控系统对接;
  • delivery_details结构化子对象,字段名与业务术语一致,AI可精准提取;
  • timestamp提供绝对时间点,支持后续时效计算(如“签收已超24小时”)。

我们给某快递公司重构工具时,将所有message字段替换为status+details,Agent对用户问题的回答准确率从68%提升到94%,因为AI不再需要“理解”自然语言,而是直接查status值做if-else。

3.4 秘密四:工具必须内置“防御性默认值”,且默认值需通过业务验证

很多工具定义里写"default": 10,但没人验证过“10”是否符合业务。某电商搜索工具search_productslimit默认为100,结果每次用户没说数量,AI就拉100条商品,前端渲染卡顿,用户投诉率飙升。

防御性默认值三原则

  1. 最小必要原则:默认值必须是满足基础场景的最小值。搜索默认limit: 10(一页显示量),而非100
  2. 业务安全原则:默认值必须通过业务侧压测。timeout_ms默认设为3000,需验证99.9%请求在此时间内完成;
  3. 可审计原则:所有默认值必须在工具文档中标注“经XX业务验证”,如"default": 10, "description": "默认返回10条(经首页搜索AB测试验证,CTR提升12%)"

某金融风控工具assess_risk_score,最初threshold默认为0.5。但实际业务中,0.5会导致高风险客户漏过。我们联合风控团队做了三个月数据回溯,确定0.72是最佳阈值(平衡误杀率与漏杀率),于是工具默认值改为0.72,并写明“依据2024Q1欺诈样本集验证”。

实操心得:在CI/CD流程中加入“默认值审计”步骤。用脚本扫描所有工具定义,检查default字段是否存在,若存在则校验其是否在enum列表中或满足minimum/maximum约束。我们用这个脚本发现了17个未经验证的默认值,其中3个直接导致线上资损。

3.5 秘密五:工具必须声明“幂等性等级”,并提供对应的调用保障

幂等性不是可选项,是Agent可靠性的基石。某支付工具charge_customer未声明幂等性,AI因网络抖动重试三次,用户被扣款三次。事后才发现,工具没实现idempotency_key机制。

幂等性等级定义

等级定义示例Agent调用策略
Level 0(非幂等)同一参数多次调用产生不同结果send_sms_otp(每次发新验证码)绝对禁止重试,失败即终止
Level 1(请求级幂等)idempotency_key参数时幂等charge_customer(需传idempotency_key重试时必须复用原key
Level 2(语义级幂等)无额外参数也幂等get_user_profile(只读)可安全重试,不限次数

工具定义中必须显式声明

{ "name": "charge_customer", "idempotency_level": "level_1", "parameters": { "type": "object", "properties": { "idempotency_key": { "type": "string", "description": "幂等性键,格式:req_{unix_timestamp}_{uuid4},用于防止重复扣款" } } } }

我们给某支付网关设计工具时,强制所有Level 1工具在description中写明key生成规则,并在Agent SDK中内置key生成器(自动生成req_1716201234_abc123...)。上线后重复扣款投诉归零。

4. 实操落地:从定义到上线的七步工作流

4.1 步骤一:业务实体对齐(耗时占比35%,决定成败)

这不是技术活,是跨部门对齐。召集业务方、DBA、前端、后端、Agent工程师,开一场2小时对齐会,输出《工具实体映射表》:

业务场景业务术语数据库字段API路径Agent工具名参数名是否必填示例值
查订单订单号order_id/api/orders/{id}get_order_by_idorder_id"ord_20240520_abc123"
改地址收货地址shipping_address/api/users/{uid}/addressupdate_shipping_addressshipping_address{"street": "XX路1号", "city": "北京"}

关键动作

  • 所有列必须由业务方当场确认,拒绝“大概”“应该”等模糊表述;
  • 示例值必须来自生产环境真实数据,禁用"test123"等假数据;
  • 对“是否必填”有争议时,以数据库NOT NULL约束为准。

我见过最惨案例:某团队跳过此步,工具参数名用cust_id,结果业务方说“我们系统里没有cust_id,只有client_code”,返工三天。

4.2 步骤二:Schema初稿编写(使用OpenAPI 3.1规范)

不用手写JSON,用Swagger Editor或Stoplight Studio。重点检查:

  • 所有string类型必须有patternenum
  • 所有number必须有multipleOf(如金额multipleOf: 0.01);
  • required数组必须与业务必填项100%一致;
  • additionalProperties: false全局启用。

避坑技巧:在description中写明业务规则,而非技术规则。错:“type: string”;对:“type: string, 格式:16位UUID,示例:a1b2c3d4-e5f6-7890-g1h2-i3j4k5l6m7n8”。

4.3 步骤三:LLM兼容性测试(用GPT-4-turbo实测)

写5个典型用户指令,用gpt-4-turbo测试工具调用效果:

  • 指令1:“查用户u-123的最近3笔订单”
  • 指令2:“把订单ord-456的状态改成已发货”
  • 指令3:“给我张三的手机号,他ID是u-789”
  • 指令4:“查ID为abc的订单”(故意输错格式)
  • 指令5:“查用户u-123的订单,只要金额大于100的”

观察:

  • AI是否生成了正确的工具名?
  • 参数值是否符合pattern约束?(如order_id是否带ord_前缀)
  • 对错误指令(指令4),AI是否拒绝调用,而非生成非法值?

我们用此法在初稿阶段就淘汰了23%的工具定义,避免后期返工。

4.4 步骤四:工具服务层实现(Node.js/Python示例)

以Python FastAPI为例,工具get_order_by_id实现:

from pydantic import BaseModel, Field, field_validator from fastapi import HTTPException import re class GetOrderByIdRequest(BaseModel): order_id: str = Field( ..., description="订单ID,格式:ord_YYYYMMDD_xxx,示例:ord_20240520_abc123", pattern=r"^ord_\d{8}_\w{3,10}$" ) @field_validator('order_id') def validate_order_id(cls, v): if not re.match(r"^ord_\d{8}_\w{3,10}$", v): raise ValueError("order_id格式错误") return v @app.post("/tools/get_order_by_id") def get_order_by_id(request: GetOrderByIdRequest): # 业务逻辑 order = db.query(Order).filter(Order.id == request.order_id).first() if not order: return {"status": "not_found", "timestamp": datetime.now().isoformat()} return { "status": "success", "order": { "id": order.id, "status": order.status, "amount": float(order.amount), "created_at": order.created_at.isoformat() }, "timestamp": datetime.now().isoformat() }

关键点

  • Pydantic模型直接复用工具Schema,保证前后端一致;
  • @field_validator做业务级校验(如检查订单是否存在),而非仅靠Schema;
  • 返回值结构与工具定义的returns完全一致。

4.5 步骤五:Agent集成测试(用LangGraph实测)

在LangGraph中构建最小流程:

user_input → tool_node(get_order_by_id) → result_parser → final_answer

测试用例:

  • 正常流程:输入“查ord_20240520_abc123”,验证是否返回status: success
  • 异常流程:输入“查abc123”,验证是否返回status: not_found且不崩溃;
  • 边界流程:输入“查ord_20240520_abc123”,但数据库无此订单,验证是否返回预设错误态。

我们用pytest写了127个集成测试,覆盖所有工具的正常/异常/边界场景,CI中失败即阻断发布。

4.6 步骤六:线上灰度与指标监控

上线不全量,用Header灰度:

  • X-Agent-Version: v1.2的请求走新工具;
  • 其他走旧逻辑。

监控四大黄金指标:

指标健康阈值告警方式定位问题
工具调用成功率>99.5%企业微信告警接口超时/500错误
参数校验失败率<0.1%日志审计AI生成非法参数
状态解析准确率>98%每日报表returns定义不匹配
幂等键冲突率0%实时告警Level 1工具未传key

某次上线,参数校验失败率突增至0.8%,查日志发现AI在order_id里传了"ORD_20240520_ABC123"(大写),而pattern是小写。立刻修复Schema,加"description": "字母必须小写"

4.7 步骤七:持续迭代(建立工具健康度评分)

每月用公式计算工具健康度:

健康度 = (调用成功率 × 0.4) + (状态解析准确率 × 0.3) + (参数校验失败率倒数 × 0.2) + (文档完整度 × 0.1)
  • 文档完整度:descriptionpatternenumreturns四项齐全得1分,缺一项扣0.25分。

健康度<0.85的工具,进入优化队列。我们用此法在半年内将平均工具健康度从0.71提升到0.94。

5. 常见问题与独家排查技巧

5.1 问题速查表:AI总不调用你的工具?

现象最可能原因排查命令/方法解决方案
AI完全忽略工具,用自身知识回答工具description太短(<20字)或含模糊词(“相关”“一些”)llm.invoke("列出你能调用的所有工具名")重写description,用主动动词+具体名词,如“查订单:根据订单ID返回订单详情(含状态、金额、时间)”
AI调用工具但参数值非法(如user_id"test"parameters缺少pattern/minLength约束curl -X POST手动发非法值,看是否被Schema拦截在Pydantic/FastAPI中加@field_validator,或在OpenAPI中补全约束
AI调用后无法解析返回值,答非所问returns未定义,或返回JSON与定义不符curl调用工具,用jq '.status'验证字段存在强制在工具服务层用Pydantic模型序列化返回值,确保100%匹配
工具调用成功但Agent流程卡住工具返回status: "success",但AI期待"result": "success"llm.invoke("工具返回{'status':'success'},这意味着成功吗?")修改returns定义,或在Agent层加适配器转换字段名
同一工具被高频重试(>5次/秒)未声明幂等性等级,或Level 0工具被重试查日志中idempotency_key是否重复显式声明idempotency_level,Level 0工具禁用重试策略

5.2 独家技巧:用“工具温度计”快速诊断

我自制了一个tool_thermometer.py脚本,输入工具定义JSON,输出健康评分:

python tool_thermometer.py --file tools/get_order.json # 输出: # ✅ 参数约束:92/100(缺pattern,但有enum) # ✅ 返回定义:100/100(status+details+timestamp齐全) # ⚠️ 描述质量:65/100(含模糊词“相关信息”) # ❌ 幂等性:0/100(未声明idempotency_level) # 总分:64/100 → 建议优先修复幂等性声明

脚本逻辑:

  • parameters中每个string字段检查patternenum,缺一项扣20分;
  • returns检查是否含status(枚举)、details(object)、timestamp(string),缺一项扣25分;
  • description用jieba分词,过滤“相关”“一些”“可能”等模糊词,每出现一次扣10分;
  • idempotency_level字段存在且值为level_0/level_1/level_2得100分,否则0分。

团队用此脚本在需求评审会上当场打分,健康度<80的工具不进入开发排期。

5.3 高频误区纠正:那些年我们信过的“真理”

误区1:“工具越多,Agent越聪明”
真相:工具数量与Agent能力非正相关。某项目塞了42个工具,但87%的请求只用到3个。我们砍掉35个低频工具,将剩余7个的description重写、pattern补全,任务完成率反升11%。工具贵精不贵多,一个定义完美的工具,胜过十个模糊工具。

误区2:“LLM会自动处理大小写/空格/前缀”
真相:LLM没有字符串标准化能力。"ORD_123""ord_123"在token层面完全不同。某次user_id参数,AI生成"U-123"(大写U),而数据库存"u-123",查询为空。解决方案:在工具服务层加v.lower(),并在description中写明“字母必须小写”。

误区3:“返回值越详细越好”
真相:冗余字段增加LLM解析负担。某工具返回{"data": {"user": {"name": "张三", "age": 30, "city": "北京", "job": "工程师", "salary": 25000, "department": "研发部", "manager": "李四", "hire_date": "2020-01-01"}}},AI常忽略"city"去提取"job"。简化为{"name": "张三", "city": "北京", "job": "工程师"}后,城市提取准确率从73%升到99%。只返回Agent下一步决策必需的字段。

误区4:“测试用例覆盖所有参数组合”
真相:穷举组合不现实。某工具5个参数,每个2个值,组合达32种。我们只测:①所有必填参数正常值;②每个必填参数非法值(各1个);③所有可选参数各1个正常值。用这7个用例,捕获了92%的线上问题。聚焦“必填参数的合法性”和“核心业务路径”,而非数学完备性。

6. 经验沉淀:我在37个项目中总结的六条铁律

这六条不是理论,是我在客户现场、凌晨三点的Slack频道、以及被退回的PR评论里,用真金白银换来的认知:

铁律一:工具定义即产品合同,签之前必须三方会审
“三方”指业务方(懂需求)、后端(懂实现)、Agent工程师(懂LLM)。少一方,合同就无效。某次漏了业务方,工具get_policy_coverage返回{"max_amount": 1000000},但业务方说“保额单位是万元”,AI当成100万处理,报价翻10倍。会审时业务方当场拍板:“所有金额单位统一为‘元’,加字段amount_unit: "CNY"”。

铁律二:永远假设AI会生成最坏的输入
不要想“AI应该不会这么傻”,要想“如果AI生成'../../../../etc/passwd',我的工具会怎样”。我们在所有file_path参数加`pattern: "^[a-zA-Z0-9_./