AI Agent 工程化实践:State、MCP 与 Skill Discovery
-
- 前言
- 一张图理解 Agent 工程化
- 1. 为什么 Agent 工程化不是“多接几个工具”?
-
- 一个简单 Agent 会遇到的真实问题
- 2. LangGraph State:如何避免长对话中的上下文漂移?
-
- 2.1 什么是上下文漂移?
- 2.2 State 的作用是什么?
- 2.3 为什么不能只靠聊天记录?
- 2.4 State 应该拆成哪些部分?
- 2.5 状态更新比“对话总结”更重要
- 2.6 用 checkpoint 处理长任务
- 2.7 让不同节点只看必要信息
- 2.8 小结
- 3. MCP Resource Metadata:模型如何知道资源什么时候该用?
-
- 3.1 MCP 可以理解成什么?
- 3.2 Resource Metadata 是什么?
- 3.3 为什么 Metadata 很关键?
- 3.4 一个天气资源的 Metadata 示例
- 3.5 一个智能家居资源的 Metadata 示例
- 3.6 好的 Metadata 应该包含什么?
- 3.7 小结
- 4. Skill、Tool、MCP 到底有什么区别?
-
- 4.1 Tool:能执行的具体动作
- 4.2 Skill:教模型怎么完成一类任务
- 4.3 MCP:让模型用统一方式连接外部资源和工具
- 4.4 三者区别表
- 4.5 一个完整流转例子
- 4.6 常见误区
-
- 误区 1:Skill 就是 Tool
- 误区 2:有 MCP 就不需要 Skill
- 误区 3:Skill 越多越好
- 5. Skill 太多怎么办:Discovery、按需加载和权限控制
-
- 5.1 为什么不能把所有 Skill 都塞进 System Prompt?
- 5.2 问题 1:上下文预算被占用
- 5.3 问题 2:候选太多,模型更容易选错
- 5.4 问题 3:相似 Skill 会互相干扰
- 5.5 Skill Discovery 的推荐流程
- 5.6 第一步:先做领域分组
- 5.7 第二步:只召回 Top-K
- 5.8 第三步:Skill 描述要写正例和反例
- 5.9 第四步:高风险 Skill 不要自动触发
- 5.10 第五步:记录使用日志,持续优化
- 5.11 小结
- 6. 一个完整例子:周末旅行助手内部如何流转?
-
- 6.1 第一步:更新 State
- 6.2 第二步:Skill Discovery
- 6.3 第三步:加载相关 Skill
- 6.4 第四步:通过 MCP 查询资源
- 6.5 第五步:得到 Observation 并更新 State
- 6.6 第六步:生成计划并验证
- 6.7 第七步:用户修改需求
- 6.8 完整链路总结
- 7. 落地时可以用的设计清单
-
- 7.1 State 设计清单
- 7.2 MCP Resource Metadata 设计清单
- 7.3 Skill 设计清单
- 7.4 Skill Discovery 设计清单
- 8. 最后总结
前言
最近看到一些遇到一些关于 AI Agent 工程化的问题,我觉得挺有意思的,所以想要详细去介绍一下,下面会讲解的时候会举一些例子,如果表述的不恰当,大家多包涵呢。
这些AI Agent 工程化的问题表面上是在问 LangGraph、MCP、Skills、Skill Discovery,但本质上是在问同一件事:
当大模型不再只是聊天,而是要连续完成任务、调用工具、读取资源、记住状态时,我们应该如何把它组织成一个可靠的系统?
很多人刚开始做 AI 应用时,会觉得 Agent 就是:
大模型 + 工具调用 + Prompt
但真正做复杂一点的任务后,很快会遇到一些问题:
- 对话变长后,模型忘了用户最初的目标。
- 用户中途改了需求,模型还在按照旧需求做事。
- 系统接入了很多外部资源,但模型不知道什么时候该用哪个。
- Tool、Skill、MCP 混在一起,架构边界越来越乱。
- Skill 数量越来越多,全部塞进上下文后,模型反而更容易选错。
所以这篇文章想用比较容易理解的方式,聊 4 个 AI Agent 工程化中很重要的问题:
1. LangGraph State:长对话和长任务中,如何避免上下文漂移?
2. MCP Resource Metadata:模型如何知道某个资源什么时候该用?
3. Skill、Tool、MCP:这几个概念到底有什么区别?
4. Skill Discovery:Skill 变多后,如何避免上下文溢出和路由混乱?
为了让这些概念不那么抽象,本文尽量不用很专业的业务场景举例,而是用大家更熟悉的例子,比如:
- 周末旅行助手
- 做饭助手
- 点外卖助手
- 智能家居助手
- 购物清单助手
这些例子虽然简单,但背后的工程问题和复杂 Agent 系统一样。
一张图理解 Agent 工程化
可以先看一个简化版 Agent 系统:
#mermaid-svg-uW6tclUjaAkaPSBh{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-uW6tclUjaAkaPSBh .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-uW6tclUjaAkaPSBh .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-uW6tclUjaAkaPSBh .error-icon{fill:#552222;}#mermaid-svg-uW6tclUjaAkaPSBh .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-uW6tclUjaAkaPSBh .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-uW6tclUjaAkaPSBh .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-uW6tclUjaAkaPSBh .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-uW6tclUjaAkaPSBh .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-uW6tclUjaAkaPSBh .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-uW6tclUjaAkaPSBh .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-uW6tclUjaAkaPSBh .marker{fill:#333333;stroke:#333333;}#mermaid-svg-uW6tclUjaAkaPSBh .marker.cross{stroke:#333333;}#mermaid-svg-uW6tclUjaAkaPSBh svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-uW6tclUjaAkaPSBh p{margin:0;}#mermaid-svg-uW6tclUjaAkaPSBh .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-uW6tclUjaAkaPSBh .cluster-label text{fill:#333;}#mermaid-svg-uW6tclUjaAkaPSBh .cluster-label span{color:#333;}#mermaid-svg-uW6tclUjaAkaPSBh .cluster-label span p{background-color:transparent;}#mermaid-svg-uW6tclUjaAkaPSBh .label text,#mermaid-svg-uW6tclUjaAkaPSBh span{fill:#333;color:#333;}#mermaid-svg-uW6tclUjaAkaPSBh .node rect,#mermaid-svg-uW6tclUjaAkaPSBh .node circle,#mermaid-svg-uW6tclUjaAkaPSBh .node ellipse,#mermaid-svg-uW6tclUjaAkaPSBh .node polygon,#mermaid-svg-uW6tclUjaAkaPSBh .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-uW6tclUjaAkaPSBh .rough-node .label text,#mermaid-svg-uW6tclUjaAkaPSBh .node .label text,#mermaid-svg-uW6tclUjaAkaPSBh .image-shape .label,#mermaid-svg-uW6tclUjaAkaPSBh .icon-shape .label{text-anchor:middle;}#mermaid-svg-uW6tclUjaAkaPSBh .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-uW6tclUjaAkaPSBh .rough-node .label,#mermaid-svg-uW6tclUjaAkaPSBh .node .label,#mermaid-svg-uW6tclUjaAkaPSBh .image-shape .label,#mermaid-svg-uW6tclUjaAkaPSBh .icon-shape .label{text-align:center;}#mermaid-svg-uW6tclUjaAkaPSBh .node.clickable{cursor:pointer;}#mermaid-svg-uW6tclUjaAkaPSBh .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-uW6tclUjaAkaPSBh .arrowheadPath{fill:#333333;}#mermaid-svg-uW6tclUjaAkaPSBh .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-uW6tclUjaAkaPSBh .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-uW6tclUjaAkaPSBh .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-uW6tclUjaAkaPSBh .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-uW6tclUjaAkaPSBh .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-uW6tclUjaAkaPSBh .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-uW6tclUjaAkaPSBh .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-uW6tclUjaAkaPSBh .cluster text{fill:#333;}#mermaid-svg-uW6tclUjaAkaPSBh .cluster span{color:#333;}#mermaid-svg-uW6tclUjaAkaPSBh div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-uW6tclUjaAkaPSBh .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-uW6tclUjaAkaPSBh rect.text{fill:none;stroke-width:0;}#mermaid-svg-uW6tclUjaAkaPSBh .icon-shape,#mermaid-svg-uW6tclUjaAkaPSBh .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-uW6tclUjaAkaPSBh .icon-shape p,#mermaid-svg-uW6tclUjaAkaPSBh .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-uW6tclUjaAkaPSBh .icon-shape .label rect,#mermaid-svg-uW6tclUjaAkaPSBh .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-uW6tclUjaAkaPSBh .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-uW6tclUjaAkaPSBh .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-uW6tclUjaAkaPSBh :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
用户提出目标
状态管理 State
任务规划 Planner
Skill Discovery
加载相关 Skill
通过 MCP 访问资源和工具
执行动作
观察结果 Observation
最终回答或交付结果
这张图里有几个关键点:
- State 负责保存当前任务的真实状态。
- Planner 负责任务拆解。
- Skill Discovery 负责从很多技能里找出当前最相关的技能。
- MCP 负责把外部资源和工具标准化地暴露给模型。
- Observation 不是一句普通聊天内容,而是工具执行后的事实反馈。
如果这些部分没有设计好,Agent 很容易变成一个“看起来很聪明,但做事不稳定”的系统。
1. 为什么 Agent 工程化不是“多接几个工具”?
我们先从一个生活化例子开始。
假设你做了一个“周末旅行助手”,用户说:
我这周末想去杭州玩两天,预算 800 元,不想去太挤的地方,帮我安排一下。
如果只是普通聊天机器人,它可能直接生成一段行程:
第一天去西湖,第二天去灵隐寺,晚上吃本地菜。
这当然能回答,但它不一定真的能完成任务。
一个更像 Agent 的系统,需要做更多事情:
1. 记住用户目标:周末去杭州,两天,预算 800 元,避开拥挤景点。
2. 查询天气:周末是否下雨。
3. 查询交通:高铁、地铁、打车成本。
4. 查询景点:开放时间、距离、热度。
5. 规划路线:每天怎么走更顺。
6. 控制预算:交通、餐饮、门票不要超支。
7. 用户改需求时更新计划。
这就不只是“模型会说话”了,而是一个带状态、资源、工具和计划的系统。
一个简单 Agent 会遇到的真实问题
继续这个旅行助手例子。
用户一开始说:
预算 800 元,玩两天。
过了一会儿又说:
其实我只有周日一天,而且我爸妈也一起去,别安排太累的路线。
如果系统只是把所有聊天记录塞给模型,模型可能会混乱:
- 它可能还按“两天”规划。
- 它可能还按“年轻人暴走路线”规划。
- 它可能忘了预算。
- 它可能不知道“只有周日一天”是最新需求。
这就是上下文漂移。
所以 Agent 工程化的第一件事,不是多接工具,而是让系统知道:
当前目标是什么?
哪些信息是最新的?
哪些信息已经被用户修改了?
哪些事实来自工具查询?
哪些只是模型的临时假设?
这就是 LangGraph State 这类设计要解决的问题。
2. LangGraph State:如何避免长对话中的上下文漂移?
2.1 什么是上下文漂移?
上下文漂移可以理解成:
对话或任务变长后,模型逐渐偏离真实目标,或者把旧信息、新信息、猜测和事实混在一起。
它在实际 Agent 系统里非常常见。
比如旅行助手中:
第 1 轮:用户说想去杭州两天。
第 2 轮:系统规划了两天路线。
第 3 轮:用户说只剩一天时间。
第 4 轮:用户说要带父母,路线不要太累。
第 5 轮:用户问,那上午去哪比较合适?
如果模型只是看完整聊天记录,它可能会被历史内容干扰。
它看到“杭州两天”,也看到“只剩一天”,还看到“父母一起去”。如果没有清晰的状态管理,它不一定能稳定判断哪个是最新约束。
2.2 State 的作用是什么?
State 可以理解成 Agent 的“结构化工作记忆”。
它不是普通聊天记录,而是把任务中真正重要的信息单独存起来。
比如这个旅行助手,可以维护这样的 State:
{
"user_goal": "规划周日杭州一日游",
"latest_constraints": {
"budget": 800,
"people": ["用户", "父亲", "母亲"],
"duration": "1 day",
"avoid": ["太拥挤的景点", "长时间步行"],
"preference": ["轻松", "适合长辈"]
},
"deprecated_constraints": [
"原计划是两天,但用户后来改成一天"
],
"known_facts": {
"weather": "待查询",
"transport": "待查询"
},
"current_plan": [],
"open_questions": [
"是否需要当天往返?"
]
}
这样后续每个节点都不用重新读完整聊天记录,而是读取这个结构化 State。
2.3 为什么不能只靠聊天记录?
聊天记录的问题是:它是线性的、混杂的。
里面同时有:
- 用户的真实需求。
- 用户已经废弃的旧需求。
- 模型提出过但未确认的建议。
- 工具返回的事实。
- 系统临时猜测。
- 中途失败的方案。
如果全都混在一起,模型就要自己判断哪些内容还有效。
这在短对话里问题不大,但在长任务里会越来越危险。
2.4 State 应该拆成哪些部分?
一个比较清晰的 Agent State 可以拆成:
goal:当前最终目标。
constraints:用户明确约束。
preferences:用户偏好。
facts:已经确认的事实。
assumptions:模型暂时假设。
plan:当前计划。
completed_steps:已经完成的步骤。
evidence:工具或资源返回的证据。
errors:执行中出现的问题。
version:需求版本。
用 Python 类型描述大概是:
from typing import TypedDict
class TravelAgentState(TypedDict):
goal: str
constraints: dict
preferences: list[str]
facts: dict
assumptions: list[str]
plan: list[str]
completed_steps: list[str]
evidence: list[dict]
errors: list[str]
requirement_version: int
2.5 状态更新比“对话总结”更重要
很多系统会做长对话总结,但总结不等于状态管理。
比如下面这种总结其实帮助有限:
用户想去杭州旅行,后来又改了一些需求。
这句话太模糊。
更好的方式是把它更新成结构化状态:
{
"requirement_version": 3,
"goal": "规划杭州周日一日游",
"active_constraints": {
"duration": "1 day",
"people": 3,
"budget": 800,
"walking_intensity": "low"
},
"inactive_constraints": [
{
"value": "2 days",
"reason": "用户后来改为周日一天"
}
]
}
这样系统不会再被旧信息带偏。
2.6 用 checkpoint 处理长任务
如果 Agent 要完成多步任务,最好设置 checkpoint。
旅行助手可以这样:
checkpoint_1:确认用户目标和约束。
checkpoint_2:查询天气和交通信息。
checkpoint_3:筛选候选景点。
checkpoint_4:生成路线。
checkpoint_5:检查预算和强度。
checkpoint_6:输出最终计划。
如果第 4 步发现路线太累,不需要重来所有步骤,只需要回到“筛选景点”或“生成路线”那一步。
2.7 让不同节点只看必要信息
Agent 系统里,不同节点不应该看到完全相同的上下文。
例如:
规划节点:需要看目标、约束、偏好。
查询节点:只需要看查询参数。
验证节点:需要看计划、预算、步行强度。
总结节点:只需要看最终状态和关键证据。
这能减少模型被无关信息干扰。
2.8 小结
LangGraph State 这类设计解决的是:
不要让模型在一大段聊天记录里猜当前状态。
要把当前状态显式、结构化、可更新地保存下来。
一句话:
长任务里,聊天记录是历史,State 才是当前事实。
3. MCP Resource Metadata:模型如何知道资源什么时候该用?
3.1 MCP 可以理解成什么?
MCP 可以简单理解成一种“标准连接方式”。
它让模型可以连接外部资源和工具,比如:
- 天气信息
- 地图路线
- 日历
- 商品价格
- 餐厅菜单
- 家里的智能设备
- 个人购物清单
如果没有统一协议,每接一个资源都要单独写一套连接逻辑,系统会越来越乱。
MCP 的价值在于:
用统一方式把外部资源、工具和提示暴露给模型。
3.2 Resource Metadata 是什么?
Resource Metadata 就是资源的说明信息。
但它不是给人看的普通介绍,而是给模型做判断用的结构化说明。
它告诉模型:
这个资源是什么?
什么时候适合用?
什么时候不适合用?
需要什么输入?
会返回什么输出?
信息是否实时?
有没有风险或权限限制?
3.3 为什么 Metadata 很关键?
还是看旅行助手。
系统里可能有这些资源:
weather_resource:天气信息
map_resource:地图和路线
calendar_resource:用户日历
restaurant_resource:餐厅信息
ticket_resource:门票信息
用户说:
周日去杭州,帮我安排轻松一点的路线。
模型应该知道:
- 要查天气,因为天气影响出行。
- 要查地图,因为路线要轻松。
- 要查景点开放时间,因为周日有些地方可能限流或闭馆。
- 不一定要查餐厅,除非用户提到吃饭偏好。
- 不应该查用户日历,除非用户授权并且需要确认时间冲突。
这些判断不能只靠模型自己猜,最好通过 Resource Metadata 明确告诉它。
3.4 一个天气资源的 Metadata 示例
{
"id": "weather_resource",
"title": "天气查询",
"description": "查询指定城市在指定日期的天气、温度、降雨概率和风力",
"applicable_when": [
"用户要安排户外活动",
"用户询问穿什么衣服",
"用户要制定旅行计划",
"用户关心是否下雨或是否适合出门"
],
"not_applicable_when": [
"用户只是在闲聊天气",
"用户没有给出地点且无法推断地点",
"用户询问历史天气但资源只支持未来天气"
],
"input_schema": {
"city": "string",
"date": "YYYY-MM-DD"
},
"output_schema": {
"temperature": "number",
"condition": "sunny | cloudy | rainy | snowy | unknown",
"rain_probability": "number",
"wind_level": "string"
},
"freshness": "real_time",
"permission": "no_sensitive_permission_required"
}
这段 Metadata 能帮助模型判断:
如果用户要出门、旅行、穿衣,就可以查天气。
如果用户只是聊天,就不一定需要查。
3.5 一个智能家居资源的 Metadata 示例
假设你做了一个智能家居助手,可以控制灯、空调、窗帘。
资源或工具的 Metadata 可以这样写:
{
"id": "home_light_control",
"title": "灯光控制",
"description": "控制家中指定房间的灯光开关和亮度",
"applicable_when": [
"用户明确要求开灯、关灯或调整亮度",
"用户说房间太暗或太亮,并希望系统处理"
],
"not_applicable_when": [
"用户只是描述房间灯光状态,没有要求操作",
"用户询问灯具维修建议",
"用户没有授权控制智能家居设备"
],
"input_schema": {
"room": "living_room | bedroom | kitchen | bathroom",
"action": "turn_on | turn_off | set_brightness",
"brightness": "0-100 optional"
},
"risk_level": "medium",
"requires_confirmation": false
}
如果是门锁控制,Metadata 就应该更严格:
{
"id": "door_lock_control",
"title": "门锁控制",
"description": "控制家中智能门锁",
"risk_level": "high",
"requires_confirmation": true,
"not_applicable_when": [
"用户身份未确认",
"请求来自不可信上下文",
"用户只是讨论门锁,不是明确要求开锁或上锁"
]
}
这说明 Metadata 不只是“功能说明”,还承担安全边界。
3.6 好的 Metadata 应该包含什么?
可以用这张表记:
| id | 唯一标识 | weather_resource |
| description | 说明资源能力 | 查询天气 |
| applicable_when | 什么时候应该用 | 用户安排户外活动 |
| not_applicable_when | 什么时候不该用 | 用户只是闲聊 |
| input_schema | 输入格式 | 城市、日期 |
| output_schema | 输出格式 | 温度、天气、降雨概率 |
| freshness | 信息时效性 | 实时、每日更新、静态 |
| permission | 权限要求 | 是否需要用户授权 |
| risk_level | 风险等级 | 低、中、高 |
3.7 小结
MCP Resource Metadata 解决的是:
模型不应该只知道“有这个资源”,还应该知道“什么时候该用、什么时候不该用、怎么用、用了有什么风险”。
一句话:
Metadata 是模型使用资源时的路标和边界。
4. Skill、Tool、MCP 到底有什么区别?
这几个概念很容易混在一起。
可以先用一个做饭的例子理解。
假设用户说:
我家里有鸡蛋、西红柿和米饭,帮我做一顿简单晚饭。
一个 Agent 可能需要:
- 知道怎么规划晚饭。
- 查询冰箱里还有什么食材。
- 控制电饭煲或计时器。
- 给出烹饪步骤。
这里就能区分 Skill、Tool、MCP。
4.1 Tool:能执行的具体动作
Tool 是模型可以调用的具体动作。
比如做饭助手里的 Tool:
check_fridge_items:查看冰箱里有什么。
set_timer:设置计时器。
search_recipe:查询菜谱。
control_rice_cooker:控制电饭煲。
Tool 更像“工具箱里的工具”。
它解决的问题是:
模型能做什么动作?
例如:
{
"tool_name": "set_timer",
"arguments": {
"minutes": 10,
"label": "西红柿炒蛋收汁"
}
}
4.2 Skill:教模型怎么完成一类任务
Skill 不是一个具体动作,而是一套做事方法。
比如“做一顿简单晚饭”这个 Skill 可以写:
simple_dinner_planning:
1. 先确认现有食材。
2. 再判断用户有没有忌口。
3. 优先选择步骤少、失败率低的菜。
4. 如果有主食,搭配一个蛋白质和一个蔬菜。
5. 给出按时间排序的操作步骤。
6. 不要推荐用户没有食材的菜,除非明确说明需要购买。
Skill 解决的问题是:
模型应该按什么流程做事?
它更像菜谱、操作手册、工作流。
4.3 MCP:让模型用统一方式连接外部资源和工具
MCP 更像一种连接标准。
它把各种资源和工具统一暴露给模型。
在做饭助手里,MCP server 可能暴露:
Resources:
– fridge_inventory:冰箱库存
– recipe_database:菜谱库
– nutrition_info:营养信息
Tools:
– set_timer:设置计时器
– control_rice_cooker:控制电饭煲
– add_to_shopping_list:加入购物清单
MCP 解决的问题是:
外部资源和工具如何被标准化接入模型?
4.4 三者区别表
| Tool | 能执行什么动作 | 刀、锅、计时器 | 设置计时器、查冰箱 |
| Skill | 应该怎么完成任务 | 菜谱、流程手册 | 如何规划一顿晚饭 |
| MCP | 如何连接外部资源和工具 | 厨房的统一接口和插座标准 | 连接冰箱库存、菜谱库、电饭煲 |
4.5 一个完整流转例子
用户说:
我今晚想吃点清淡的,家里有鸡蛋、青菜和米饭,帮我安排一下。
系统内部可以这样工作:
1. 识别任务类型:晚饭规划。
2. Skill Discovery 找到 simple_dinner_planning。
3. 加载 Skill,知道要先确认食材和偏好。
4. 通过 MCP 读取 fridge_inventory。
5. 调用 search_recipe 工具找简单菜谱。
6. 生成晚饭方案。
7. 如果需要计时,调用 set_timer。
对应关系:
Skill:simple_dinner_planning,告诉模型怎么规划。
MCP:连接冰箱库存、菜谱库和计时器。
Tool:search_recipe、set_timer 等具体动作。
State:保存用户偏好、已有食材、当前计划。
4.6 常见误区
误区 1:Skill 就是 Tool
不准确。
Tool 是动作,Skill 是方法。
比如:
set_timer 是 Tool。
如何安排做饭步骤是 Skill。
误区 2:有 MCP 就不需要 Skill
不准确。
MCP 只是把外部资源和工具接进来,但模型仍然需要 Skill 来知道正确流程。
就像厨房里有锅、有刀、有食材,不代表一个人就知道怎么做菜。
误区 3:Skill 越多越好
也不准确。
Skill 太多后,如果没有 Discovery 和治理,会出现:
- 模型不知道该选哪个。
- 上下文被 Skill 描述占满。
- 相似 Skill 互相干扰。
- 高风险 Skill 被误触发。
这就进入下一个问题:Skill Discovery。
5. Skill 太多怎么办:Discovery、按需加载和权限控制
5.1 为什么不能把所有 Skill 都塞进 System Prompt?
假设你做了一个很强的生活助手,慢慢加了很多 Skill:
做饭规划 Skill
旅行规划 Skill
外卖推荐 Skill
购物清单 Skill
智能家居 Skill
健身计划 Skill
穿衣建议 Skill
预算管理 Skill
日程安排 Skill
电影推荐 Skill
…
一开始只有几个 Skill,直接写进 System Prompt 好像没问题。
但当 Skill 变成几十个、几百个时,就会出问题。
5.2 问题 1:上下文预算被占用
每个 Skill 都需要描述:
这个 Skill 是什么?
什么时候用?
什么时候不用?
输入输出是什么?
有什么风险?
如果几十个 Skill 都写进去,会占用大量上下文。
更糟糕的是,系统可能会截断这些描述。
被截断后,模型可能只看到:
travel_planning:帮助用户安排旅行…
shopping_planning:帮助用户安排购物…
dinner_planning:帮助用户安排晚饭…
但看不到关键边界:
什么时候不用?
是否需要用户确认?
是否能自动下单?
是否涉及付款?
5.3 问题 2:候选太多,模型更容易选错
用户说:
我今晚不想出门,想吃点热乎的,预算 30 元以内。
这个请求可能涉及:
- 做饭规划
- 外卖推荐
- 购物清单
- 预算管理
如果模型看到 200 个 Skill,它可能会犹豫。
更好的做法是先做 Discovery,只召回少量候选:
候选 Skill:
1. dinner_planning
2. food_delivery_recommendation
3. budget_filtering
然后再让模型选择或组合。
5.4 问题 3:相似 Skill 会互相干扰
比如你有这些 Skill:
food_recommendation:推荐吃什么。
dinner_planning:安排晚饭。
recipe_search:查菜谱。
takeout_selection:选择外卖。
它们都和“吃”有关。
用户只说:
晚上吃什么?
模型很难判断到底该用哪个。
所以 Skill 描述必须写清楚边界。
不推荐:
food_recommendation:
帮助用户解决吃饭问题。
dinner_planning:
帮助用户规划晚饭。
推荐:
recipe_based_dinner:
当用户想在家做饭,并且有食材或愿意购买食材时使用。
不要用于用户明确想点外卖的场景。
takeout_selection:
当用户想点外卖,或不想做饭时使用。
不要用于用户想学习做菜的场景。
5.5 Skill Discovery 的推荐流程
一个更合理的 Skill Discovery 流程是:
#mermaid-svg-mWF2vhVOFrwnmWYk{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-mWF2vhVOFrwnmWYk .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-mWF2vhVOFrwnmWYk .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-mWF2vhVOFrwnmWYk .error-icon{fill:#552222;}#mermaid-svg-mWF2vhVOFrwnmWYk .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-mWF2vhVOFrwnmWYk .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-mWF2vhVOFrwnmWYk .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-mWF2vhVOFrwnmWYk .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-mWF2vhVOFrwnmWYk .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-mWF2vhVOFrwnmWYk .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-mWF2vhVOFrwnmWYk .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-mWF2vhVOFrwnmWYk .marker{fill:#333333;stroke:#333333;}#mermaid-svg-mWF2vhVOFrwnmWYk .marker.cross{stroke:#333333;}#mermaid-svg-mWF2vhVOFrwnmWYk svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-mWF2vhVOFrwnmWYk p{margin:0;}#mermaid-svg-mWF2vhVOFrwnmWYk .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-mWF2vhVOFrwnmWYk .cluster-label text{fill:#333;}#mermaid-svg-mWF2vhVOFrwnmWYk .cluster-label span{color:#333;}#mermaid-svg-mWF2vhVOFrwnmWYk .cluster-label span p{background-color:transparent;}#mermaid-svg-mWF2vhVOFrwnmWYk .label text,#mermaid-svg-mWF2vhVOFrwnmWYk span{fill:#333;color:#333;}#mermaid-svg-mWF2vhVOFrwnmWYk .node rect,#mermaid-svg-mWF2vhVOFrwnmWYk .node circle,#mermaid-svg-mWF2vhVOFrwnmWYk .node ellipse,#mermaid-svg-mWF2vhVOFrwnmWYk .node polygon,#mermaid-svg-mWF2vhVOFrwnmWYk .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-mWF2vhVOFrwnmWYk .rough-node .label text,#mermaid-svg-mWF2vhVOFrwnmWYk .node .label text,#mermaid-svg-mWF2vhVOFrwnmWYk .image-shape .label,#mermaid-svg-mWF2vhVOFrwnmWYk .icon-shape .label{text-anchor:middle;}#mermaid-svg-mWF2vhVOFrwnmWYk .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-mWF2vhVOFrwnmWYk .rough-node .label,#mermaid-svg-mWF2vhVOFrwnmWYk .node .label,#mermaid-svg-mWF2vhVOFrwnmWYk .image-shape .label,#mermaid-svg-mWF2vhVOFrwnmWYk .icon-shape .label{text-align:center;}#mermaid-svg-mWF2vhVOFrwnmWYk .node.clickable{cursor:pointer;}#mermaid-svg-mWF2vhVOFrwnmWYk .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-mWF2vhVOFrwnmWYk .arrowheadPath{fill:#333333;}#mermaid-svg-mWF2vhVOFrwnmWYk .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-mWF2vhVOFrwnmWYk .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-mWF2vhVOFrwnmWYk .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-mWF2vhVOFrwnmWYk .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-mWF2vhVOFrwnmWYk .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-mWF2vhVOFrwnmWYk .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-mWF2vhVOFrwnmWYk .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-mWF2vhVOFrwnmWYk .cluster text{fill:#333;}#mermaid-svg-mWF2vhVOFrwnmWYk .cluster span{color:#333;}#mermaid-svg-mWF2vhVOFrwnmWYk div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-mWF2vhVOFrwnmWYk .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-mWF2vhVOFrwnmWYk rect.text{fill:none;stroke-width:0;}#mermaid-svg-mWF2vhVOFrwnmWYk .icon-shape,#mermaid-svg-mWF2vhVOFrwnmWYk .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-mWF2vhVOFrwnmWYk .icon-shape p,#mermaid-svg-mWF2vhVOFrwnmWYk .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-mWF2vhVOFrwnmWYk .icon-shape .label rect,#mermaid-svg-mWF2vhVOFrwnmWYk .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-mWF2vhVOFrwnmWYk .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-mWF2vhVOFrwnmWYk .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-mWF2vhVOFrwnmWYk :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
用户请求
意图粗分类
按领域筛选 Skill
召回 Top-K 候选 Skill
根据正反例消歧
过滤高风险 Skill
加载完整 Skill
执行任务
记录使用结果
5.6 第一步:先做领域分组
不要在所有 Skill 里全局搜索。
先粗分领域:
饮食类:
– recipe_based_dinner
– takeout_selection
– shopping_list_generation
出行类:
– travel_planning
– route_selection
– weather_aware_schedule
家居类:
– light_control
– air_conditioner_control
– cleaning_schedule
娱乐类:
– movie_recommendation
– weekend_activity
用户说:
周末想出去玩,但不想走太多路。
先进入出行类,而不是让所有 Skill 都参与竞争。
5.7 第二步:只召回 Top-K
Top-K 的意思是只选最相关的几个候选。
例如:
用户请求:周末想出去玩,但不想走太多路。
Top-K Skill:
1. travel_planning
2. weather_aware_schedule
3. route_selection
这比把几十个 Skill 全部交给模型更稳定。
5.8 第三步:Skill 描述要写正例和反例
一个好 Skill 不应该只写“我能做什么”,还要写“我不能做什么”。
例如:
travel_planning:
用于用户想安排旅行、周末出行、城市游玩路线的场景。
适合处理预算、时间、同行人、路线强度等约束。
不要用于用户只是询问某个地点的单独信息。
不要用于用户已经明确只需要查天气的场景。
另一个:
weather_query:
用于用户询问天气、穿衣、是否适合出门等场景。
不要用于完整旅行规划,除非它作为 travel_planning 的辅助步骤。
这样模型更容易消歧。
5.9 第四步:高风险 Skill 不要自动触发
有些 Skill 可能会产生真实世界影响。
比如:
place_order:下单。
pay_bill:付款。
unlock_door:开门锁。
send_message:发送消息。
change_schedule:修改日程。
这些 Skill 不应该轻易由模型自动触发。
比如用户说:
帮我看看今晚吃什么。
模型可以推荐外卖,但不能直接下单。
如果用户说:
就点这个吧。
系统也应该确认:
你确认要下单这份套餐吗?价格 28 元,送到默认地址。
这就是权限控制。
5.10 第五步:记录使用日志,持续优化
Skill Discovery 不是写完就结束。
系统应该记录:
某个 Skill 被召回了多少次?
实际被使用了多少次?
用户是否取消或纠正?
执行结果是否成功?
是否经常误触发?
是否长期没人用?
如果一个 Skill 经常被召回但很少使用,说明它的描述可能太宽泛。
如果两个 Skill 经常同时被召回,说明它们可能边界重叠。
如果一个 Skill 长期没人用,可以移出默认列表。
5.11 小结
Skill 太多时,核心不是“怎么塞进上下文”,而是:
怎么让当前任务只看到少量、清晰、可区分、低风险的 Skill。
一句话:
Skill Discovery 的目标不是展示所有能力,而是帮模型缩小选择范围。
6. 一个完整例子:周末旅行助手内部如何流转?
我们把前面的概念串起来。
用户说:
我周日想带爸妈去杭州玩一天,预算 800 元以内,不想走太多路,最好别太晒。
6.1 第一步:更新 State
系统先把用户目标写入 State:
{
"goal": "规划杭州周日一日游",
"constraints": {
"date": "Sunday",
"city": "杭州",
"people": ["用户", "父亲", "母亲"],
"budget": 800,
"walking_intensity": "low",
"avoid": ["暴晒", "长时间步行"]
},
"preferences": ["轻松", "适合长辈"],
"facts": {},
"plan": [],
"requirement_version": 1
}
6.2 第二步:Skill Discovery
系统从很多 Skill 中召回候选:
Top-K Skill:
1. travel_planning
2. weather_aware_schedule
3. route_selection
不会召回:
movie_recommendation
recipe_based_dinner
light_control
因为它们和当前任务无关。
6.3 第三步:加载相关 Skill
加载 travel_planning 后,模型知道旅行规划应该考虑:
时间
预算
同行人
路线强度
天气
交通
景点开放时间
备选方案
加载 weather_aware_schedule 后,模型知道:
如果天气太热或下雨,要优先选择室内、阴凉、步行少的路线。
6.4 第四步:通过 MCP 查询资源
模型通过 MCP 访问资源:
weather_resource:查询周日杭州天气。
map_resource:查询路线距离和交通时间。
place_resource:查询景点开放时间和拥挤程度。
这些资源都有 Metadata,模型知道什么时候该用它们。
6.5 第五步:得到 Observation 并更新 State
假设天气资源返回:
{
"city": "杭州",
"date": "Sunday",
"condition": "sunny",
"temperature": 34,
"rain_probability": 10
}
系统更新 State:
{
"facts": {
"weather": {
"condition": "sunny",
"temperature": 34,
"risk": "hot"
}
}
}
这时路线规划就应该避开暴晒和长距离步行。
6.6 第六步:生成计划并验证
系统生成计划后,不应该直接输出。
还要验证:
预算是否小于 800?
是否适合长辈?
是否步行太多?
是否避开高温暴晒?
是否有备选方案?
如果发现步行时间太长,就回到路线筛选阶段重新规划。
6.7 第七步:用户修改需求
用户又说:
我妈临时不去了,预算改成 500 元。
系统不能只是把这句话追加到聊天记录里。
它应该更新 State:
{
"requirement_version": 2,
"constraints": {
"people": ["用户", "父亲"],
"budget": 500
},
"deprecated_constraints": [
{
"field": "people",
"old_value": ["用户", "父亲", "母亲"],
"reason": "用户更新同行人"
},
{
"field": "budget",
"old_value": 800,
"reason": "用户更新预算"
}
]
}
然后重新检查预算和路线。
6.8 完整链路总结
大模型
MCP 资源
Skill Discovery
State
用户
大模型
MCP 资源
Skill Discovery
State
用户
#mermaid-svg-UqCZwpvlTpiBsE0M{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-UqCZwpvlTpiBsE0M .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-UqCZwpvlTpiBsE0M .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-UqCZwpvlTpiBsE0M .error-icon{fill:#552222;}#mermaid-svg-UqCZwpvlTpiBsE0M .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-UqCZwpvlTpiBsE0M .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-UqCZwpvlTpiBsE0M .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-UqCZwpvlTpiBsE0M .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-UqCZwpvlTpiBsE0M .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-UqCZwpvlTpiBsE0M .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-UqCZwpvlTpiBsE0M .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-UqCZwpvlTpiBsE0M .marker{fill:#333333;stroke:#333333;}#mermaid-svg-UqCZwpvlTpiBsE0M .marker.cross{stroke:#333333;}#mermaid-svg-UqCZwpvlTpiBsE0M svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-UqCZwpvlTpiBsE0M p{margin:0;}#mermaid-svg-UqCZwpvlTpiBsE0M .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-UqCZwpvlTpiBsE0M text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-UqCZwpvlTpiBsE0M .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-UqCZwpvlTpiBsE0M .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-UqCZwpvlTpiBsE0M .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-UqCZwpvlTpiBsE0M .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-UqCZwpvlTpiBsE0M #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-UqCZwpvlTpiBsE0M .sequenceNumber{fill:white;}#mermaid-svg-UqCZwpvlTpiBsE0M #sequencenumber{fill:#333;}#mermaid-svg-UqCZwpvlTpiBsE0M #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-UqCZwpvlTpiBsE0M .messageText{fill:#333;stroke:none;}#mermaid-svg-UqCZwpvlTpiBsE0M .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-UqCZwpvlTpiBsE0M .labelText,#mermaid-svg-UqCZwpvlTpiBsE0M .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-UqCZwpvlTpiBsE0M .loopText,#mermaid-svg-UqCZwpvlTpiBsE0M .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-UqCZwpvlTpiBsE0M .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-UqCZwpvlTpiBsE0M .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-UqCZwpvlTpiBsE0M .noteText,#mermaid-svg-UqCZwpvlTpiBsE0M .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-UqCZwpvlTpiBsE0M .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-UqCZwpvlTpiBsE0M .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-UqCZwpvlTpiBsE0M .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-UqCZwpvlTpiBsE0M .actorPopupMenu{position:absolute;}#mermaid-svg-UqCZwpvlTpiBsE0M .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-UqCZwpvlTpiBsE0M .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-UqCZwpvlTpiBsE0M .actor-man circle,#mermaid-svg-UqCZwpvlTpiBsE0M line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-UqCZwpvlTpiBsE0M :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
提出旅行目标和约束
根据目标查找相关 Skill
加载旅行规划相关 Skill
查询天气、路线、地点信息
返回 Observation 并更新事实
提供最新状态
写入候选计划
验证预算、强度和天气约束
输出最终旅行方案
这个例子里,几个概念的关系很清楚:
State:保存当前目标、约束、事实和计划。
Skill:告诉模型如何做旅行规划。
MCP:让模型连接天气、地图、地点等资源。
Tool:具体查询天气、路线、地点的动作。
Skill Discovery:从很多技能中选出当前需要的少数几个。
7. 落地时可以用的设计清单
如果你之后要做一个 Agent 系统,可以用下面这个清单检查。
7.1 State 设计清单
- 是否把当前目标单独保存?
- 是否区分用户约束、模型假设和工具事实?
- 用户修改需求时,是否能标记旧需求失效?
- 是否有 requirement_version?
- 是否有 checkpoint?
- 每个节点是否只读取必要状态?
7.2 MCP Resource Metadata 设计清单
- 资源是否有清晰描述?
- 是否写了 applicable_when?
- 是否写了 not_applicable_when?
- 是否定义输入输出结构?
- 是否说明信息时效性?
- 是否标记权限和风险等级?
- 高风险动作是否需要确认?
7.3 Skill 设计清单
- Skill 是不是一套做事方法,而不是一个具体工具?
- Skill 名称是否具体?
- 是否写清楚什么时候使用?
- 是否写清楚什么时候不要使用?
- 是否和其他 Skill 边界重叠?
- 是否包含高风险动作?
7.4 Skill Discovery 设计清单
- 是否先按领域分组?
- 是否只召回 Top-K?
- 是否有消歧逻辑?
- 是否过滤高风险 Skill?
- 是否记录召回和使用日志?
- 是否定期清理长期不用的 Skill?
8. 最后总结
Agent 工程化真正难的地方,不是让模型回答得更像人,而是让模型在复杂任务中做事更稳定。
这需要几个关键设计:
用 State 管住任务状态。
用 MCP 标准化连接资源和工具。
用 Metadata 告诉模型资源的适用边界。
用 Skill 描述做事方法。
用 Skill Discovery 控制模型看到的技能数量。
用权限和日志保证系统可控、可追踪、可优化。
如果只靠一个很长的 Prompt,系统很快会失控。
更可靠的做法是:
把状态结构化;
把资源标准化;
把技能模块化;
把选择过程收窄;
把高风险动作管住;
把每次执行记录下来。
一句话总结:
AI Agent 不是“模型加一堆工具”,而是一个由状态、资源、工具、技能、路由和权限共同组成的工程系统。
当我们开始这样设计 Agent 时,它才不只是一个能聊天的 Demo,而是更接近一个可以长期维护、持续扩展、稳定工作的系统。



