资讯详情

LLM Skills工程实践:可测试、可调度、带契约的最小执行单元

📅 2026/9/15 3:31:13 | 华诺云谱 👁 阅读
LLM Skills工程实践:可测试、可调度、带契约的最小执行单元
1. 这不是“技能”这个词的字面意思而是LLM工程里一个具体、可落地、有标准接口的设计范式你点开这篇大概率是因为在Dify、LangChain、AutoGen或者Agentscope的文档里反复看到“Skills”这个词甚至在命令行里敲过npx skills add ...但翻遍中文资料要么是翻译成“技能”后戛然而止要么直接跳到Agent编排中间那层“到底什么是Skills”像被刻意抹掉了一样。我带团队落地过7个LLM Agent生产项目从金融客服到工业设备故障诊断所有稳定跑半年以上的系统底层都绕不开Skills这一层设计——它既不是Prompt Engineering的变体也不是Function Calling的别名而是一个明确边界、可独立测试、带类型契约、能被调度器统一管理的最小可执行单元。核心关键词就是LLM和Skills这两个词组合在一起在当前大模型工程实践中特指一种将领域动作封装为标准化接口的架构模式。它解决的是“让大模型真正动起来”的最后一公里问题模型可以理解“查订单”但谁来连接订单数据库模型能推理“生成周报”但PDF渲染逻辑放哪儿Skills就是那个“动手的人”。适合两类人细读一是正在用Dify/LangGraph搭Agent却卡在“怎么把真实API塞进去”的开发者二是想跳出Prompt调优、往工程化方向深挖的算法工程师。它不讲LLM原理不画抽象架构图只拆解你明天就能抄作业的定义、写法、测试方式和避坑点。2. Skills的本质不是功能模块而是带契约的“能力插槽”2.1 为什么不能直接写Function Calling——从三个真实翻车现场说起去年帮一家物流客户做运单状态追踪Agent初期直接用OpenAI的Function Calling对接他们的WMS系统。表面看很顺模型识别出“查单号123456”自动调用get_shipment_status函数返回“已签收”。但上线三天后崩溃了——不是模型出错而是WMS接口返回了新字段estimated_delivery_time模型没声明要这个字段Function Calling的schema校验直接拒绝解析整个链路中断。这是第一个坑Function Calling的schema是静态的、强绑定的而业务API永远在变。第二个坑更隐蔽。我们给某银行做信贷审批Agent把“查询征信报告”、“计算负债率”、“生成风控结论”三个动作全塞进一个credit_assessment函数里。结果模型总在不该调用的时候调用——比如用户问“今天天气怎么样”它也触发了征信查询。排查发现Function Calling的描述文本description对模型来说只是模糊提示没有强制约束力。这是第二个本质问题Function Calling缺乏运行时的语义隔离多个逻辑耦合在一个函数里模型无法精准区分“该不该调”和“调哪个”。第三个坑来自协作。前端团队用React写了个工单系统后端用Python写Skills中间需要传参。最初约定用JSON字符串结果前端传{order_id: ABC123}后端Python收到的是{order_id: ABC123}字符串不是dict.get(order_id)直接报错。调试两小时才发现是JSON序列化层级错了。这是第三个痛点跨语言、跨团队协作时没有统一的输入/输出契约光靠文档约定等于没约定。Skills正是为解决这三类问题而生。它不是换个名字重写Function Calling而是把“能力”本身变成一个可独立定义、验证、替换的实体。举个最简例子# 这是一个标准Skills定义以LangChain为例 from langchain_core.tools import BaseTool from pydantic import BaseModel, Field class OrderStatusInput(BaseModel): order_id: str Field(..., description必须是12位纯数字订单号例如123456789012) class GetOrderStatusTool(BaseTool): name get_order_status description 查询指定订单的最新物流状态仅支持国内快递单号 args_schema: type[BaseModel] OrderStatusInput def _run(self, order_id: str) - str: # 真实调用WMS API的逻辑 if not order_id.isdigit() or len(order_id) ! 12: return 订单号格式错误请检查是否为12位数字 # 模拟API调用 return f订单{order_id}状态已签收签收时间2024-06-15 14:23注意三个关键设计点输入强类型OrderStatusInput继承自PydanticBaseModelorder_id字段带Field(..., description...)这不仅是文档更是运行时校验依据。当用户输入order_idABCSkills框架会自动拦截并返回格式错误提示不会把脏数据传给下游API。能力边界清晰nameget_order_status是全局唯一标识description明确限定适用范围“仅支持国内快递单号”避免模型误用。契约即代码args_schema指向具体的Pydantic模型前端传JSON、后端Python解析、Java团队用Jackson反序列化只要遵循同一份Schema定义参数传递零歧义。提示Skills的name不是随便起的。在Dify中Skills名称会映射到工作流节点ID在Agentscope里它是Agent调度器查找能力的Key。一旦定名就相当于API的Endpoint路径改名等于接口变更需全链路同步。2.2 Skills与Function Calling、Plugin、Agent的区别一张表说清定位维度SkillsFunction CallingPluginAgent核心目标封装单一、原子性动作如“查天气”让模型调用预设函数扩展宿主应用功能如VS Code插件自主决策执行的完整智能体输入输出契约强类型Pydantic Schema运行时校验JSON Schema仅用于模型理解无运行时校验无统一契约依赖宿主规范通常无固定输入输出由内部规划决定可测试性可独立单元测试mock API后验证输入校验、错误处理无法单独测试必须集成模型调用链测试依赖宿主环境测试成本高需模拟完整决策流程复用粒度最小可复用单元一个Skills可被多个Agent复用函数级复用但耦合模型调用逻辑应用级复用实体级复用但配置复杂典型场景Dify工作流中的“工具节点”、LangChain的Tool、Agentscope的SkillOpenAI API的functions参数VS Code的Copilot插件、Notion AI插件AutoGen的GroupChatManager、Dify的Agent工作流关键结论Skills是面向LLM Agent工程化的基础设施层。它不解决“模型怎么思考”而是解决“思考完后手怎么动”。当你在Dify里拖拽一个“HTTP请求”节点背后就是一个Skills实例当你用npx skills add sandai-org/vidmuse-skills本质是下载一组预定义好的Skills包含Schema、实现、测试用例。它存在的意义就是把“让大模型干活”这件事从玄学调参变成可版本管理、可CI/CD、可灰度发布的软件工程实践。2.3 Skills的四大黄金设计原则从踩坑中总结的硬性约束基于7个项目沉淀我提炼出Skills设计必须遵守的四条铁律违反任何一条都会在后期引发维护灾难第一原子性原则一个Skills只做一件事且必须做完反例process_customer_complaint这个Skills内部包含“解析投诉内容→查询用户历史→生成回复草稿→发送邮件→更新CRM状态”五步。问题在于如果邮件服务宕机整个Skills失败但前两步解析、查询其实成功了状态却无法回滚。正解是拆成五个Skillsparse_complaint_text、fetch_user_history、draft_reply、send_email、update_crm_status。每个Skills失败互不影响上层Agent可灵活重试或降级。第二幂等性原则相同输入多次调用结果一致尤其对查询类Skills如get_stock_price必须保证无论调用1次还是100次返回结果相同缓存策略需在Skills内部实现而非依赖外部。对修改类Skills如update_inventory需设计idempotency_key参数确保重复提交不导致库存扣减两次。我在电商项目中吃过亏未加幂等键用户点两次“提交订单”库存少了2份。第三防御性原则输入校验前置错误信息对用户友好Skills的_run方法里第一行必须是输入校验。不要指望上游Agent过滤脏数据——Agent可能出错模型可能幻觉。校验规则要具体order_id必须12位数字email必须符合RFC 5322标准date_range的结束日期不能早于开始日期。错误提示不能是ValueError: invalid input而要是订单号格式错误请提供12位纯数字例如123456789012。用户和运维都靠这条信息快速定位。第四可观测性原则必须暴露结构化日志和性能指标每个Skills调用至少记录skills_name、input_hash输入参数的SHA256、statussuccess/failed、duration_ms、error_type网络超时/参数错误/API限流。我们在K8s集群里用Prometheus抓取这些指标当get_payment_status的失败率突增立刻能关联到支付网关升级事件。没有日志的Skills就像没有刹车的汽车——跑得快但不敢上路。注意这四条原则不是建议是上线红线。我们团队的CI流水线里git commit时会扫描Skills代码检测是否包含try...except Exception:裸捕获违反防御性、是否缺少duration_ms计时违反可观测性不通过则阻断合并。3. 从零手写一个Production-ready Skills以“实时汇率查询”为例3.1 为什么选汇率查询——它覆盖Skills所有关键挑战汇率查询看似简单却是检验Skills设计的“压力测试仪”外部依赖强必须调第三方API如exchangerate-api.com网络不稳定数据时效敏感1分钟前的汇率可能已失效需明确缓存策略输入组合多支持fromUSDtoCNY、fromEURtoJPYamount100等多种参数错误类型杂API密钥无效、请求超限、货币代码不存在、网络超时安全要求高API密钥不能硬编码需从环境变量注入。下面带你一步步写出可直接部署的Skills代码基于LangChain v0.1.x当前最主流版本适配Dify和Agentscope。3.2 Step 1定义输入Schema——用Pydantic划清能力边界from pydantic import BaseModel, Field, validator from typing import Optional, Literal class CurrencyPair(BaseModel): 货币对基础模型用于校验和标准化 from_currency: str Field( ..., min_length3, max_length3, patternr^[A-Z]{3}$, description源货币代码3位大写字母如USD ) to_currency: str Field( ..., min_length3, max_length3, patternr^[A-Z]{3}$, description目标货币代码3位大写字母如CNY ) validator(from_currency, to_currency) def validate_currency_code(cls, v): # 内置常用货币白名单防止模型幻觉出XXX valid_currencies {USD, CNY, EUR, JPY, GBP, CAD, AUD, CHF, HKD, SGD} if v not in valid_currencies: raise ValueError(f不支持的货币代码{v}仅支持{valid_currencies}) return v class ExchangeRateInput(BaseModel): 汇率查询Skills的完整输入Schema currency_pair: CurrencyPair Field(..., description要查询的货币对) amount: Optional[float] Field( None, ge0.01, le1000000.0, description可选换算金额默认为1.0 ) source: Literal[exchangerate-api, fixer] Field( exchangerate-api, description数据源目前仅支持exchangerate-api ) property def base_url(self) - str: 根据source返回对应API基础URL sources { exchangerate-api: https://v6.exchangerate-api.com/v6, fixer: https://api.fixer.io/v1.0 } return sources[self.source]这段代码的价值远超表面patternr^[A-Z]{3}$强制货币代码为3位大写杜绝usd、Usd等非法输入validator装饰器内置白名单校验比单纯正则更可靠XXX能过正则但不在白名单property将数据源选择转化为可扩展的URL映射未来加openexchangerates只需扩写sources字典amount的ge0.01和le1000000.0设定业务合理范围避免模型输入amount999999999999导致API拒绝。3.3 Step 2实现Skills主体——聚焦错误处理与缓存import os import time import requests from langchain_core.tools import BaseTool from langchain_core.callbacks import CallbackManagerForToolRun from typing import Optional, Dict, Any class GetExchangeRateTool(BaseTool): name get_exchange_rate description ( 查询两种货币之间的实时汇率支持换算指定金额。 注意仅支持USD/CNY/EUR/JPY/GBP/CAD/AUD/CHF/HKD/SGD之间的兑换。 示例查询100美元兑人民币输入{currency_pair: {from_currency: USD, to_currency: CNY}, amount: 100} ) args_schema: type[BaseModel] ExchangeRateInput # 缓存字典key为currency_pair字符串value为(汇率, 时间戳) _cache: Dict[str, tuple[float, float]] {} # 缓存有效期5分钟 _cache_ttl: int 300 def _is_cache_valid(self, cache_key: str) - bool: 检查缓存是否有效 if cache_key not in self._cache: return False rate, timestamp self._cache[cache_key] return time.time() - timestamp self._cache_ttl def _get_from_cache(self, cache_key: str) - float: 从缓存获取汇率 return self._cache[cache_key][0] def _set_cache(self, cache_key: str, rate: float): 设置缓存 self._cache[cache_key] (rate, time.time()) def _run( self, currency_pair: CurrencyPair, amount: Optional[float] None, source: str exchangerate-api, run_manager: Optional[CallbackManagerForToolRun] None, ) - str: # 步骤1输入校验防御性原则 try: # Pydantic已做基础校验此处补充业务逻辑校验 if currency_pair.from_currency currency_pair.to_currency: return f错误源货币和目标货币不能相同{currency_pair.from_currency} except Exception as e: return f输入校验失败{str(e)} # 步骤2构建缓存key cache_key f{currency_pair.from_currency}_{currency_pair.to_currency} # 步骤3尝试从缓存读取 if self._is_cache_valid(cache_key): rate self._get_from_cache(cache_key) result f缓存命中{currency_pair.from_currency}兑{currency_pair.to_currency}汇率为{rate:.4f} if amount is not None: result f{amount} {currency_pair.from_currency} ≈ {amount * rate:.2f} {currency_pair.to_currency} return result # 步骤4调用API带重试和超时 api_key os.getenv(EXCHANGE_RATE_API_KEY) if not api_key: return 错误汇率API密钥未配置请检查环境变量EXCHANGE_RATE_API_KEY url f{currency_pair.base_url}/{api_key}/latest/{currency_pair.from_currency} for attempt in range(3): # 最多重试3次 try: response requests.get(url, timeout5) response.raise_for_status() data response.json() # 步骤5解析API响应提取目标货币汇率 if conversion_rates not in data: return fAPI响应异常缺少conversion_rates字段原始响应{data} rates data[conversion_rates] if currency_pair.to_currency not in rates: return f错误API不支持兑换至{currency_pair.to_currency}可用货币{list(rates.keys())} rate float(rates[currency_pair.to_currency]) # 步骤6写入缓存 self._set_cache(cache_key, rate) # 步骤7构造返回结果 result f{currency_pair.from_currency}兑{currency_pair.to_currency}实时汇率为{rate:.4f} if amount is not None: result f{amount} {currency_pair.from_currency} ≈ {amount * rate:.2f} {currency_pair.to_currency} return result except requests.exceptions.Timeout: if attempt 2: return 错误汇率查询超时请稍后重试 time.sleep(1) # 指数退避第一次重试等待1秒 except requests.exceptions.ConnectionError: if attempt 2: return 错误无法连接汇率服务请检查网络 time.sleep(1) except requests.exceptions.HTTPError as e: status_code response.status_code if response in locals() else 0 if status_code 401: return 错误汇率API密钥无效请检查EXCHANGE_RATE_API_KEY elif status_code 429: return 错误汇率API请求超限请稍后重试 else: return fHTTP错误{e} except Exception as e: return f未知错误{str(e)}关键细节解析缓存策略_cache是类属性所有实例共享避免重复请求_cache_ttl300硬编码5分钟符合汇率业务特性高频但非毫秒级重试机制for attempt in range(3)time.sleep(1)实现简单指数退避比裸try-except更健壮错误分类对401密钥错误、429限流返回定制化提示运维可直接定位问题类型安全注入os.getenv(EXCHANGE_RATE_API_KEY)从环境变量读取符合12-Factor App原则Docker部署时通过-e EXCHANGE_RATE_API_KEYxxx注入。3.4 Step 3编写单元测试——Skills可交付的底线没有测试的Skills等于没写。以下测试覆盖核心场景import pytest from unittest.mock import patch, MagicMock from your_module import GetExchangeRateTool, CurrencyPair, ExchangeRateInput class TestGetExchangeRateTool: def setup_method(self): self.tool GetExchangeRateTool() def test_valid_input_returns_rate(self): 测试正常查询返回正确格式 # Mock requests.get mock_response MagicMock() mock_response.json.return_value { conversion_rates: {CNY: 7.25, EUR: 0.92} } with patch(requests.get, return_valuemock_response): result self.tool._run( currency_pairCurrencyPair(from_currencyUSD, to_currencyCNY), amount100 ) assert USD兑CNY实时汇率为7.2500 in result assert 100 USD ≈ 725.00 CNY in result def test_invalid_currency_code(self): 测试非法货币代码 with pytest.raises(ValueError): CurrencyPair(from_currencyUSD, to_currencyXYZ) def test_cache_hit(self): 测试缓存命中 # 先设置缓存 self.tool._cache[USD_CNY] (7.25, time.time()) # 直接调用_get_from_cache assert self.tool._get_from_cache(USD_CNY) 7.25 def test_api_timeout_retries(self): 测试超时重试 # Mock requests.get to raise Timeout on first two calls, succeed on third side_effect [ requests.exceptions.Timeout(), requests.exceptions.Timeout(), MagicMock(jsonlambda: {conversion_rates: {CNY: 7.25}}) ] with patch(requests.get, side_effectside_effect): result self.tool._run( currency_pairCurrencyPair(from_currencyUSD, to_currencyCNY) ) assert USD兑CNY实时汇率为7.2500 in result def test_missing_api_key(self): 测试API密钥缺失 with patch(os.getenv, return_valueNone): result self.tool._run( currency_pairCurrencyPair(from_currencyUSD, to_currencyCNY) ) assert 汇率API密钥未配置 in result运行pytest test_skills.py100%通过才是合格Skills。测试用例设计逻辑test_valid_input验证主流程test_invalid_currency_code验证Pydantic校验test_cache_hit验证缓存逻辑不依赖网络test_api_timeout_retries验证重试机制Mock网络异常test_missing_api_key验证安全兜底。实操心得我们团队规定每个Skills必须附带至少5个测试用例覆盖正常流、2个边界流、2个异常流。CI流水线里pytest --covyour_module覆盖率低于85%则构建失败。这不是形式主义——去年一个Skills因缺少超时测试上线后因网络抖动导致Agent线程阻塞影响了整条客服链路。4. Skills的工程化落地从本地测试到生产部署的全链路4.1 在Dify中接入Skills不是上传代码而是注册能力契约Dify的Skills管理界面Settings → Skills本质是一个契约注册中心。你上传的不是Python文件而是一个包含Schema定义、描述、测试用例的YAML包。以下是get_exchange_rate在Dify中的标准注册流程Step 1准备Skills包目录结构exchange-rate-skill/ ├── skill.yaml # 核心契约定义 ├── logo.png # 可选技能图标 ├── README.md # 使用说明 └── tests/ # 测试用例供Dify平台验证 └── test_basic.ymlStep 2编写skill.yaml——这才是Dify识别Skills的唯一依据# exchange-rate-skill/skill.yaml name: get_exchange_rate description: 查询两种货币之间的实时汇率支持换算指定金额 provider: custom icon: category: finance tags: [currency, exchange, rate] input_schema: type: object properties: currency_pair: type: object properties: from_currency: type: string minLength: 3 maxLength: 3 pattern: ^[A-Z]{3}$ description: 源货币代码3位大写字母 to_currency: type: string minLength: 3 maxLength: 3 pattern: ^[A-Z]{3}$ description: 目标货币代码3位大写字母 required: [from_currency, to_currency] amount: type: number minimum: 0.01 maximum: 1000000.0 description: 可选换算金额默认为1.0 required: [currency_pair] output_schema: type: string description: 返回格式化的汇率结果字符串注意Dify不执行你的Python代码它只读取input_schema生成前端表单并将用户输入按此Schema校验后转发给后端Skills服务你自己的Flask/FastAPI服务。skill.yaml里的input_schema必须与你Python代码中的ExchangeRateInput完全一致否则会出现“前端能填、后端收不到”的诡异问题。Step 3部署Skills后端服务——用FastAPI暴露标准接口# skills_api.py from fastapi import FastAPI, HTTPException, Depends from pydantic import BaseModel from typing import Dict, Any import os from your_skills_module import GetExchangeRateTool app FastAPI(titleLLM Skills Backend) # 初始化Skills实例 exchange_tool GetExchangeRateTool() class SkillsRequest(BaseModel): name: str arguments: Dict[str, Any] app.post(/invoke) async def invoke_skill(request: SkillsRequest): if request.name ! get_exchange_rate: raise HTTPException(status_code404, detailfSkills {request.name} not found) try: # 调用Skills的_run方法 result exchange_tool._run(**request.arguments) return {result: result} except Exception as e: raise HTTPException(status_code500, detailfSkills execution failed: {str(e)}) if __name__ __main__: import uvicorn uvicorn.run(app, host0.0.0.0:8000, port8000)部署命令# 构建Docker镜像 docker build -t llm-skills-backend . # 运行挂载环境变量 docker run -d \ -p 8000:8000 \ -e EXCHANGE_RATE_API_KEYyour_actual_key \ --name skills-backend \ llm-skills-backendStep 4在Dify中配置Skills连接Settings → Skills → Add Skill → Uploadexchange-rate-skill/目录在Skills列表中找到get_exchange_rate点击Edit填写Backend URLhttp://your-server-ip:8000/invoke保存后该Skills即可在Workflow中作为工具节点使用关键经验Dify的Skills调用是同步HTTP请求超时默认10秒。如果你的Skills内部有重试如汇率API超时重试3次×5秒必须确保总耗时10秒否则Dify会判定超时。我们在线上将timeout5改为timeout3重试次数减为2次确保P99耗时8秒。4.2 在Agentscope中复用Skills用Registry实现跨Agent能力共享Agentscope的Skills设计更激进——它要求Skills必须注册到全局Registry由Agent调度器统一管理。复用get_exchange_rate的步骤Step 1注册Skills到Agentscope Registry# register_skills.py from agentscope.skills import register_skill from your_skills_module import GetExchangeRateTool # 创建Skills实例 exchange_tool GetExchangeRateTool() # 注册到Agentscope register_skill( nameget_exchange_rate, funcexchange_tool._run, description查询两种货币之间的实时汇率..., args_schemaGetExchangeRateTool.args_schema ) print(Skills registered successfully!)Step 2在Agent中声明依赖并调用# my_agent.py from agentscope.agents import AgentBase from agentscope.message import Msg from agentscope.skills import skill_registry class FinanceAgent(AgentBase): def __init__(self, name: str): super().__init__(namename) # 声明需要的Skills self.skills [get_exchange_rate] def reply(self, x: dict None) - dict: # 解析用户需求 if 汇率 in x.get(content, ): # 调用注册的Skills result skill_registry.call( get_exchange_rate, currency_pair{from_currency: USD, to_currency: CNY}, amount100 ) return Msg(assistant, f查询结果{result}, roleassistant) return Msg(assistant, 请说明需要查询的货币对, roleassistant)Agentscope的优势在于Skills注册后所有Agent实例共享同一套能力无需重复部署。劣势是调试复杂——Skills错误会抛到Agent层需结合agentscope.log和Skills自身日志交叉分析。4.3 生产环境监控用Prometheus暴露Skills指标Skills的健康度直接决定Agent SLA。我们在每个Skills实现中注入指标埋点from prometheus_client import Counter, Histogram, Gauge # 定义指标 SKILLS_INVOCATIONS_TOTAL Counter( skills_invocations_total, Total number of Skills invocations, [skills_name, status] # 标签技能名、状态 ) SKILLS_DURATION_SECONDS Histogram( skills_duration_seconds, Skills execution duration in seconds, [skills_name] ) SKILLS_CACHE_HIT_RATIO Gauge( skills_cache_hit_ratio, Cache hit ratio for Skills, [skills_name] ) class GetExchangeRateTool(BaseTool): # ... 其他代码 ... def _run(self, **kwargs) - str: # 开始计时 start_time time.time() try: # 执行核心逻辑 result self._execute_logic(**kwargs) # 记录成功指标 SKILLS_INVOCATIONS_TOTAL.labels( skills_nameself.name, statussuccess ).inc() return result except Exception as e: # 记录失败指标 SKILLS_INVOCATIONS_TOTAL.labels( skills_nameself.name, statusfailed ).inc() raise e finally: # 记录耗时 duration time.time() - start_time SKILLS_DURATION_SECONDS.labels(skills_nameself.name).observe(duration) # 更新缓存命中率需在_get_from_cache中更新计数器 # ... 缓存统计逻辑 ...Prometheus配置prometheus.ymlscrape_configs: - job_name: llm-skills static_configs: - targets: [skills-backend:8000]Grafana看板可直观展示skills_invocations_total{skills_nameget_exchange_rate, statusfailed} / rate(skills_invocations_total{skills_nameget_exchange_rate}[1h])→ 失败率histogram_quantile(0.95, rate(skills_duration_seconds_bucket{skills_nameget_exchange_rate}[1h]))→ P95耗时skills_cache_hit_ratio{skills_nameget_exchange_rate}→ 缓存命中率。实战教训某次支付Skills失败率突增至15%通过指标下钻发现是get_payment_status的P95耗时从200ms飙升至2s进一步定位到数据库慢查询。没有这套监控问题会归因为“模型不稳定”浪费3天排查时间。5. Skills常见问题与排查技巧实录来自7个项目的血泪总结5.1 问题1模型总是不调用Skills或调用错误的Skills现象用户说“帮我查订单123456”模型返回“好的正在查询”但Skills日志无调用记录或用户说“查天气”模型却调用了get_exchange_rate。根因分析描述文本description质量差description是模型选择Skills的唯一依据。如果写成“查询信息”模型无法区分订单和天气必须写成“查询指定订单号的物流状态返回签收时间、承运商、当前位置”。Skills名称冲突get_order_status和get_weather都包含get_前缀模型易混淆。应采用动词名词领域后缀如query_order_tracking_info、fetch_current_weather_by_city。输入Schema过于宽泛args_schema若定义为{query: string}模型无法推断参数意图。排查步骤在Dify/LangChain中开启verboseTrue查看模型生成的tool_calls内容检查tool_calls中的name是否为你期望的Skills名如果name错误重写description加入更多领域限定词如“仅适用于物流单号不支持身份证号”如果name正确但无日志检查Skills后端服务是否收到HTTP请求用curl -X POST http://localhost:8000/invoke -d {name:xxx}测试。速查表现象可能原因验证方法解决方案模型不调用任何Skillsdescription太模糊或缺失查看tool_calls为空重写description增加具体输入输出示例模型调用A Skills但用户问BSkills名称语义重叠tool_calls中name为A重命名Skills增加领域后
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。