Agent 如何触发 Skill?Skill 和 Tool 的区别
先说个让人困惑的问题
刚接触 Agent 开发的时候,我经常被两个概念搞混:Tool 和 Skill。
文档里写的是"Agent 可以调用 Tool",但实际开发时又会看到"Skill 编排"、"Skill 触发"。这俩到底啥关系?是同一个东西换了个名字,还是真的有层级差异?
后来在 NVC 项目里踩了不少坑,才算真正搞明白。今天把这块梳理清楚,顺便聊聊 Agent 触发 Skill 的几种模式。
Tool:最小执行单元
Tool 是 Agent 与外部世界交互的最小单元。说白了,它就是一个可以被调用的函数。
python
# 一个典型的 Tool 定义
def search_database(query: str, limit: int = 10) -> list[dict]:
"""搜索数据库中的记录
Args:
query: 搜索关键词
limit: 返回结果数量限制
Returns:
匹配的记录列表
"""
results = db.search(query, limit=limit)
return results
Tool 有几个关键特征:
| 特征 | 说明 |
|---|---|
| 原子性 | 只做一件事,不会拆成更小的步骤 |
| 声明式描述 | 必须有清晰的函数签名和 docstring |
| 被动调用 | 自己不会主动执行,等别人来调 |
| 无状态 | 调用之间不保留状态(理想情况下) |
LLM 能用 Tool,靠的是 Function Calling 机制。LLM 看到 Tool 的描述,理解参数含义,然后决定要不要调、怎么传参。
用户: "帮我查一下最近的订单"
↓
LLM: 看到有 search_orders Tool,参数是 user_id 和 date_range
↓
LLM: 生成 function call: search_orders(user_id="xxx", date_range="last_7_days")
↓
系统: 执行函数,返回结果
↓
LLM: 把结果组织成自然语言回复用户
这个过程很直白,但有个问题:如果完成一个任务需要多步操作呢?
比如"帮我订一张明天去上海的机票",拆解下来可能是:
- 查询航班信息
- 筛选合适的航班
- 检查座位 availability
- 创建订单
- 处理支付
每一步都是一个 Tool 调用,但它们之间有先后依赖,还有条件分支(没座位怎么办?支付失败怎么办?)。这时候就需要更高层的抽象了。
Skill:编排好的执行流程
Skill 是一组编排好的多步骤执行流程。如果说 Tool 是单个乐器,那 Skill 就是一段乐谱。
python
# 一个 Skill 的伪代码示例
def book_flight_skill(user_request: str) -> BookingResult:
"""订机票的完整流程"""
# Step 1: 解析意图
intent = parse_travel_intent(user_request)
# Step 2: 查询航班
flights = search_flights(
origin=intent.origin,
destination=intent.destination,
date=intent.date
)
# Step 3: 筛选推荐
recommended = filter_and_rank(flights, user.preferences)
# Step 4: 确认选择(可能需要用户交互)
selected = confirm_selection(recommended)
# Step 5: 创建订单
order = create_order(selected, user.payment_info)
# Step 6: 返回结果
return BookingResult(order=order, confirmation=order.confirmation_code)
Skill 的特征和 Tool 形成对比:
| 维度 | Tool | Skill |
|---|---|---|
| 粒度 | 原子操作 | 复合流程 |
| 是否有状态 | 无状态 | 可以维护上下文 |
| 调用方式 | 直接函数调用 | 流程编排 |
| 是否可组合 | 是(被 Skill 组合) | 是(可以包含其他 Skill) |
| 错误处理 | 单步异常 | 流程级容错和重试 |
一个 Skill 内部可能调用多个 Tool,也可能调用其他 Skill。这种嵌套组合的能力,让 Agent 的行为变得灵活而强大。
Agent 触发 Skill 的三种模式
理解了 Tool 和 Skill 的区别,接下来看 Agent 是怎么触发 Skill 的。这里有三种典型模式:
模式一:LLM 直接触发 Tool(Function Calling)
最简单的情况,LLM 直接决定调用哪个 Tool:
Tool LLM User Tool LLM User #mermaid-svg-1hjVFSeYUSAn8dmx{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-1hjVFSeYUSAn8dmx .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-1hjVFSeYUSAn8dmx .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-1hjVFSeYUSAn8dmx .error-icon{fill:#552222;}#mermaid-svg-1hjVFSeYUSAn8dmx .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-1hjVFSeYUSAn8dmx .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-1hjVFSeYUSAn8dmx .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-1hjVFSeYUSAn8dmx .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-1hjVFSeYUSAn8dmx .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-1hjVFSeYUSAn8dmx .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-1hjVFSeYUSAn8dmx .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-1hjVFSeYUSAn8dmx .marker{fill:#333333;stroke:#333333;}#mermaid-svg-1hjVFSeYUSAn8dmx .marker.cross{stroke:#333333;}#mermaid-svg-1hjVFSeYUSAn8dmx svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-1hjVFSeYUSAn8dmx p{margin:0;}#mermaid-svg-1hjVFSeYUSAn8dmx .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-1hjVFSeYUSAn8dmx text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-1hjVFSeYUSAn8dmx .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-1hjVFSeYUSAn8dmx .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-1hjVFSeYUSAn8dmx .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-1hjVFSeYUSAn8dmx .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-1hjVFSeYUSAn8dmx #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-1hjVFSeYUSAn8dmx .sequenceNumber{fill:white;}#mermaid-svg-1hjVFSeYUSAn8dmx #sequencenumber{fill:#333;}#mermaid-svg-1hjVFSeYUSAn8dmx #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-1hjVFSeYUSAn8dmx .messageText{fill:#333;stroke:none;}#mermaid-svg-1hjVFSeYUSAn8dmx .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-1hjVFSeYUSAn8dmx .labelText,#mermaid-svg-1hjVFSeYUSAn8dmx .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-1hjVFSeYUSAn8dmx .loopText,#mermaid-svg-1hjVFSeYUSAn8dmx .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-1hjVFSeYUSAn8dmx .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-1hjVFSeYUSAn8dmx .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-1hjVFSeYUSAn8dmx .noteText,#mermaid-svg-1hjVFSeYUSAn8dmx .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-1hjVFSeYUSAn8dmx .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-1hjVFSeYUSAn8dmx .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-1hjVFSeYUSAn8dmx .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-1hjVFSeYUSAn8dmx .actorPopupMenu{position:absolute;}#mermaid-svg-1hjVFSeYUSAn8dmx .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-1hjVFSeYUSAn8dmx .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-1hjVFSeYUSAn8dmx .actor-man circle,#mermaid-svg-1hjVFSeYUSAn8dmx line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-1hjVFSeYUSAn8dmx :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} "今天天气怎么样"分析意图,决定调用天气 Toolget_weather(city="北京"){"temp": 28, "condition": "晴"}"北京今天 28 度,晴天"
这种模式适合单步任务,简单直接。但问题是:LLM 本身不执行复杂流程,它只负责"点菜",不负责"做菜"。
模式二:编排层触发 Skill(Orchestrator 调度)
当任务复杂度上来后,需要一个编排层来管理流程:
#mermaid-svg-Osuz6MN7C4LUEvXp{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-Osuz6MN7C4LUEvXp .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Osuz6MN7C4LUEvXp .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Osuz6MN7C4LUEvXp .error-icon{fill:#552222;}#mermaid-svg-Osuz6MN7C4LUEvXp .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Osuz6MN7C4LUEvXp .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Osuz6MN7C4LUEvXp .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Osuz6MN7C4LUEvXp .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Osuz6MN7C4LUEvXp .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Osuz6MN7C4LUEvXp .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Osuz6MN7C4LUEvXp .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Osuz6MN7C4LUEvXp .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Osuz6MN7C4LUEvXp .marker.cross{stroke:#333333;}#mermaid-svg-Osuz6MN7C4LUEvXp svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Osuz6MN7C4LUEvXp p{margin:0;}#mermaid-svg-Osuz6MN7C4LUEvXp .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Osuz6MN7C4LUEvXp .cluster-label text{fill:#333;}#mermaid-svg-Osuz6MN7C4LUEvXp .cluster-label span{color:#333;}#mermaid-svg-Osuz6MN7C4LUEvXp .cluster-label span p{background-color:transparent;}#mermaid-svg-Osuz6MN7C4LUEvXp .label text,#mermaid-svg-Osuz6MN7C4LUEvXp span{fill:#333;color:#333;}#mermaid-svg-Osuz6MN7C4LUEvXp .node rect,#mermaid-svg-Osuz6MN7C4LUEvXp .node circle,#mermaid-svg-Osuz6MN7C4LUEvXp .node ellipse,#mermaid-svg-Osuz6MN7C4LUEvXp .node polygon,#mermaid-svg-Osuz6MN7C4LUEvXp .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Osuz6MN7C4LUEvXp .rough-node .label text,#mermaid-svg-Osuz6MN7C4LUEvXp .node .label text,#mermaid-svg-Osuz6MN7C4LUEvXp .image-shape .label,#mermaid-svg-Osuz6MN7C4LUEvXp .icon-shape .label{text-anchor:middle;}#mermaid-svg-Osuz6MN7C4LUEvXp .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Osuz6MN7C4LUEvXp .rough-node .label,#mermaid-svg-Osuz6MN7C4LUEvXp .node .label,#mermaid-svg-Osuz6MN7C4LUEvXp .image-shape .label,#mermaid-svg-Osuz6MN7C4LUEvXp .icon-shape .label{text-align:center;}#mermaid-svg-Osuz6MN7C4LUEvXp .node.clickable{cursor:pointer;}#mermaid-svg-Osuz6MN7C4LUEvXp .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Osuz6MN7C4LUEvXp .arrowheadPath{fill:#333333;}#mermaid-svg-Osuz6MN7C4LUEvXp .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Osuz6MN7C4LUEvXp .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Osuz6MN7C4LUEvXp .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Osuz6MN7C4LUEvXp .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Osuz6MN7C4LUEvXp .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Osuz6MN7C4LUEvXp .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Osuz6MN7C4LUEvXp .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Osuz6MN7C4LUEvXp .cluster text{fill:#333;}#mermaid-svg-Osuz6MN7C4LUEvXp .cluster span{color:#333;}#mermaid-svg-Osuz6MN7C4LUEvXp 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-Osuz6MN7C4LUEvXp .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Osuz6MN7C4LUEvXp rect.text{fill:none;stroke-width:0;}#mermaid-svg-Osuz6MN7C4LUEvXp .icon-shape,#mermaid-svg-Osuz6MN7C4LUEvXp .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Osuz6MN7C4LUEvXp .icon-shape p,#mermaid-svg-Osuz6MN7C4LUEvXp .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Osuz6MN7C4LUEvXp .icon-shape .label rect,#mermaid-svg-Osuz6MN7C4LUEvXp .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Osuz6MN7C4LUEvXp .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Osuz6MN7C4LUEvXp .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Osuz6MN7C4LUEvXp :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 订机票
查订单
退款
用户请求
Orchestrator
意图识别
book_flight Skill
query_order Skill
refund Skill
search_flights Tool
create_order Tool
process_payment Tool
query_database Tool
validate_refund Tool
initiate_refund Tool
Orchestrator 是一个独立的调度层,它:
- 接收用户请求
- 识别意图,决定调用哪个 Skill
- 把 Skill 的执行结果返回给用户
这种模式下,LLM 可以完全不参与 Skill 的触发过程。Orchestrator 用规则引擎或简单模型就能搞定意图识别,速度快、成本低。
模式三:LLM 触发 Tool 组合(多轮 Agent Loop)
这是最灵活的模式,LLM 在一个循环中逐步调用 Tool,自己决定下一步做什么:
python
def agent_loop(user_request: str, max_iterations: int = 10):
"""Agent 执行循环"""
messages = [{"role": "user", "content": user_request}]
tools = get_available_tools()
for i in range(max_iterations):
# LLM 决定下一步
response = llm.chat(messages, tools=tools)
# 如果 LLM 决定结束
if response.finish_reason == "stop":
return response.content
# 如果 LLM 要调用 Tool
if response.tool_calls:
for tool_call in response.tool_calls:
result = execute_tool(tool_call)
messages.append({
"role": "tool",
"content": result,
"tool_call_id": tool_call.id
})
# 继续循环,让 LLM 看到 Tool 结果后决定下一步
return "达到最大迭代次数"
这种模式下,LLM 既是决策者又是执行者。它根据每一步的结果,动态决定下一步做什么。灵活性最高,但也最不可控------LLM 可能陷入循环,或者调用了不该调用的 Tool。
NVC 项目实战:模式路由与工具映射
在实际项目中,我踩过一个坑:让 LLM 自己选 Tool,准确率只有 70% 左右。用户说"我想练习观察",LLM 有时候会调成 rag_search 而不是 practice_start。
后来我们设计了一套模式路由 + 意图预路由机制,把 Skill 的触发从"LLM 自己选"变成了"系统根据场景分配"。
NVC 的 10 个 Tool
NVC 项目定义了 10 个 Tool,每个都是一个独立的 Spring Bean:
| Tool 名称 | 职责 |
|---|---|
| RagSearchTool | RAG 知识检索,从 pgvector 向量库中搜索 NVC 相关知识 |
| WikiSearchTool | Wiki 搜索,查找用户生成的 NVC 实践笔记 |
| WikiWriteTool | Wiki 写入,异步生成 NVC 实践 Wiki |
| ProfileQueryTool | 用户档案查询,获取用户能力画像 |
| ProfileUpdateTool | 用户档案更新,记录职业、偏好等信息 |
| DashboardQueryTool | 练习数据查询,统计练习次数、得分等 |
| EvaluateNvcTool | NVC 表达评估,判断是否符合四步法 |
| PracticeStartTool | 开始练习,初始化练习会话 |
| ScenarioGenerateTool | 场景生成,AI 生成冲突场景 |
| ScenarioSearchTool | 场景搜索,从场景库中检索 |
ModeRouter:三种模式路由
NVC 支持三种对话模式,每种模式下 Agent 可用的 Tool 集合和行为策略不同:
java
// 三种模式路由器
FreeDialogRouter // 自由对话模式,默认 DIALOGUE_GUIDE,每 5 轮触发评估
ScenarioRouter // 场景驱动模式,CREATED 阶段生成场景,每 5 轮检查低分触发评估
StructuredRouter // 结构化四步模式:观察→感受→需求→请求
这样做的好处是:LLM 看到的 Tool 列表是精简的、模式相关的 。在结构化练习模式下,LLM 根本看不到 dashboard_query 这个 Tool,自然就不会调错。
IntentRouter:绕过 LLM 的意图预路由
更进一步,对于一些明确的意图,我们甚至不让 LLM 参与决策:
java
// IntentRouter.java - 在 LLM 调用前,通过正则匹配快速识别高置信度意图
public AgentResult execute(PracticeContext context) {
IntentMatch match = intentRouter.match(context.getUserMessage());
if (match != null && match.getConfidence() >= 0.8) {
return toolExecutor.executeDirectly(match.getToolName(), match.getArgs());
}
return reactLoop(context);
}
IntentRouter 的匹配规则:
| 意图 | 匹配模式 | 对应 Tool |
|---|---|---|
| profile_update | "我是/我的职业是/帮我记录到档案" | ProfileUpdateTool |
| profile_query | "看看档案/查看档案" | ProfileQueryTool |
| dashboard_query | "练习数据/练习统计" | DashboardQueryTool |
这个设计的考量是:高频的档案和数据查询可以用正则快速匹配,省掉 LLM 推理的 2-3 秒延迟。匹配不上再走 ReAct 循环。
完整流程
把上面的组件串起来:
#mermaid-svg-acNsN27asT6kcFG8{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-acNsN27asT6kcFG8 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-acNsN27asT6kcFG8 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-acNsN27asT6kcFG8 .error-icon{fill:#552222;}#mermaid-svg-acNsN27asT6kcFG8 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-acNsN27asT6kcFG8 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-acNsN27asT6kcFG8 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-acNsN27asT6kcFG8 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-acNsN27asT6kcFG8 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-acNsN27asT6kcFG8 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-acNsN27asT6kcFG8 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-acNsN27asT6kcFG8 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-acNsN27asT6kcFG8 .marker.cross{stroke:#333333;}#mermaid-svg-acNsN27asT6kcFG8 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-acNsN27asT6kcFG8 p{margin:0;}#mermaid-svg-acNsN27asT6kcFG8 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-acNsN27asT6kcFG8 .cluster-label text{fill:#333;}#mermaid-svg-acNsN27asT6kcFG8 .cluster-label span{color:#333;}#mermaid-svg-acNsN27asT6kcFG8 .cluster-label span p{background-color:transparent;}#mermaid-svg-acNsN27asT6kcFG8 .label text,#mermaid-svg-acNsN27asT6kcFG8 span{fill:#333;color:#333;}#mermaid-svg-acNsN27asT6kcFG8 .node rect,#mermaid-svg-acNsN27asT6kcFG8 .node circle,#mermaid-svg-acNsN27asT6kcFG8 .node ellipse,#mermaid-svg-acNsN27asT6kcFG8 .node polygon,#mermaid-svg-acNsN27asT6kcFG8 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-acNsN27asT6kcFG8 .rough-node .label text,#mermaid-svg-acNsN27asT6kcFG8 .node .label text,#mermaid-svg-acNsN27asT6kcFG8 .image-shape .label,#mermaid-svg-acNsN27asT6kcFG8 .icon-shape .label{text-anchor:middle;}#mermaid-svg-acNsN27asT6kcFG8 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-acNsN27asT6kcFG8 .rough-node .label,#mermaid-svg-acNsN27asT6kcFG8 .node .label,#mermaid-svg-acNsN27asT6kcFG8 .image-shape .label,#mermaid-svg-acNsN27asT6kcFG8 .icon-shape .label{text-align:center;}#mermaid-svg-acNsN27asT6kcFG8 .node.clickable{cursor:pointer;}#mermaid-svg-acNsN27asT6kcFG8 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-acNsN27asT6kcFG8 .arrowheadPath{fill:#333333;}#mermaid-svg-acNsN27asT6kcFG8 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-acNsN27asT6kcFG8 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-acNsN27asT6kcFG8 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-acNsN27asT6kcFG8 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-acNsN27asT6kcFG8 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-acNsN27asT6kcFG8 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-acNsN27asT6kcFG8 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-acNsN27asT6kcFG8 .cluster text{fill:#333;}#mermaid-svg-acNsN27asT6kcFG8 .cluster span{color:#333;}#mermaid-svg-acNsN27asT6kcFG8 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-acNsN27asT6kcFG8 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-acNsN27asT6kcFG8 rect.text{fill:none;stroke-width:0;}#mermaid-svg-acNsN27asT6kcFG8 .icon-shape,#mermaid-svg-acNsN27asT6kcFG8 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-acNsN27asT6kcFG8 .icon-shape p,#mermaid-svg-acNsN27asT6kcFG8 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-acNsN27asT6kcFG8 .icon-shape .label rect,#mermaid-svg-acNsN27asT6kcFG8 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-acNsN27asT6kcFG8 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-acNsN27asT6kcFG8 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-acNsN27asT6kcFG8 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 命中且置信度>=0.8
未命中
用户消息
IntentRouter
正则匹配?
直接执行 Tool
ModeRouter
选择对话模式
AgentLoop + 模式 Tool 集
LLM 推理
执行 Tool
返回结果
这套架构在 NVC 项目里跑了大半年,意图识别准确率从 70% 提升到了 95% 以上。
几个容易踩的坑
坑一:Tool 描述写得太抽象
python
# 反例:LLM 看不懂
def process_data(data, config):
"""处理数据"""
pass
# 正例:描述具体
def filter_orders(orders: list, status: str, date_range: str) -> list:
"""根据状态和日期范围筛选订单
Args:
orders: 订单列表
status: 订单状态,可选值: pending, paid, shipped, completed, cancelled
date_range: 日期范围,格式: "2024-01-01~2024-01-31"
Returns:
符合条件的订单列表
"""
pass
Tool 的描述是 LLM 理解它的唯一依据。写得越具体,LLM 调用得越准。
坑二:Skill 没有错误处理
python
# 反例:Skill 里没有容错
def book_flight_skill(request):
flights = search_flights(request.origin, request.destination)
selected = flights[0] # 直接取第一个,万一没有航班呢?
create_order(selected)
# 正例:每一步都有错误处理
def book_flight_skill(request):
flights = search_flights(request.origin, request.destination)
if not flights:
return Result.error("没有找到符合条件的航班")
selected = flights[0]
order_result = create_order(selected)
if order_result.is_error():
return Result.error(f"下单失败: {order_result.error_message}")
return Result.success(order_result.data)
Skill 是流程级的抽象,必须考虑各种异常情况。Tool 可以抛异常让上层处理,但 Skill 必须自己兜底。
坑三:Agent Loop 没有退出条件
python
# 反例:可能无限循环
while True:
response = llm.chat(messages, tools=tools)
if response.tool_calls:
execute_tools(response.tool_calls)
# 正例:设置最大迭代次数
for i in range(MAX_ITERATIONS):
response = llm.chat(messages, tools=tools)
if response.finish_reason == "stop":
return response.content
if response.tool_calls:
execute_tools(response.tool_calls)
return "抱歉,处理过程中遇到问题,请稍后重试"
LLM 有时候会陷入循环,反复调用同一个 Tool。没有退出条件,Agent 就会一直跑下去。
总结
Tool 和 Skill 的区别,本质上是粒度的区别:
- Tool 是原子操作,一个 Tool 只做一件事
- Skill 是复合流程,一个 Skill 协调多个 Tool 完成一个任务
Agent 触发 Skill 的三种模式,各有适用场景:
- LLM 直接触发 Tool:适合简单任务,一个 Tool 就能搞定
- 编排层触发 Skill:适合流程固定、对可靠性要求高的场景
- LLM 触发 Tool 组合:适合复杂、需要动态决策的场景
NVC 项目的实践表明:不是所有场景都需要 LLM 来决策。用 ModeRouter 限制 LLM 的选择范围,用 IntentRouter 处理明确意图,反而能获得更好的效果。
Agent 的核心不是"让 LLM 做所有事",而是在合适的地方用合适的机制。LLM 擅长理解和生成,但不擅长精确执行。把 LLM 放在它擅长的位置,其他事情交给确定性的代码,这才是好架构。