外观
一文搞懂 AI 智能体技能体系:按需加载与组合实战
问题背景:为什么单体 Agent 会失控
很多团队做智能体的第一版,都是把所有能力塞进一个巨型系统提示词:查数据库、发邮件、调搜索、算报表、写代码……提示词越写越长,工具列表越堆越多。结果是 token 成本飙升,模型在一堆不相关工具里频繁误选,新增一个能力还要改动核心提示词,回归测试苦不堪言。
本质问题在于:我们把「技能」当成了「提示词的一部分」,而不是「可独立治理的资产」。一个成熟的 AI 智能体应当像操作系统一样——平时只加载内核,用到某个能力时才把对应技能挂载进来,并能把多个技能编排成一条智能体工作流。
技能(Skill)与普通工具(Function/Tool)的区别在于粒度:工具是一个原子函数,而技能是「工具 + 专用提示词 + 输入输出契约 + 使用时机说明」的打包单元。比如「生成周报」是一个技能,内部可能组合了查询数据库、检索文档、画图三个工具。
方案对比:三种技能架构选型
| 维度 | 全量内置式 | 固定流水线 | 技能注册表 + 按需加载 |
|---|---|---|---|
| 上下文占用 | 高,全部技能常驻 | 中,链路固定 | 低,仅加载命中技能 |
| 灵活性 | 靠模型自由发挥 | 低,难以应对开放任务 | 高,运行时动态组合 |
| 新增成本 | 改核心提示词 | 改流程图与代码 | 注册一个技能包即可 |
| 适用场景 | 能力少、任务封闭 | 流程稳定的合规场景 | 能力多、任务开放的助手 |
下面这张图展示了按需加载的核心链路:用户意图先经过技能路由,命中后从注册表加载技能包,再由编排器完成技能组合与执行。
实操步骤:用 Python 搭建可运行的技能注册表
下面用一段不依赖第三方库的代码演示核心机制。每个技能声明名称、触发描述、所需工具和专属提示词;注册表负责根据关键词与描述做轻量路由,编排器负责加载并按计划顺序执行技能组合。生产环境可把打分逻辑替换为向量检索或一次轻量 LLM 调用。
python
from dataclasses import dataclass, field
from typing import Callable
@dataclass
class Skill:
"""一个可独立加载的 AI 技能包"""
name: str
# 供路由判断“何时使用”的描述
description: str
# 该技能专属的系统提示词,只在加载时进入上下文
prompt: str
# 技能内部可调用的原子工具
tools: dict[str, Callable] = field(default_factory=dict)
keywords: list[str] = field(default_factory=list)
def run_tool(self, tool_name: str, **kwargs):
if tool_name not in self.tools:
raise ValueError(f"技能 {self.name} 不包含工具 {tool_name}")
return self.tools[tool_name](**kwargs)
class SkillRegistry:
"""技能注册表:登记、检索、按需加载"""
def __init__(self):
self._skills: dict[str, Skill] = {}
def register(self, skill: Skill):
self._skills[skill.name] = skill
def route(self, user_input: str, top_k: int = 2) -> list[Skill]:
"""极简打分:关键词命中 + 描述词重叠,生产可换成语义向量检索"""
scored = []
text = user_input.lower()
for skill in self._skills.values():
score = sum(1 for kw in skill.keywords if kw in text)
score += sum(1 for w in skill.description.lower().split()
if len(w) > 2 and w in text)
if score > 0:
scored.append((score, skill))
scored.sort(key=lambda x: x[0], reverse=True)
return [s for _, s in scored[:top_k]]
# ---- 定义两个原子工具与技能 ----
def query_sales(region: str) -> dict:
return {"region": region, "revenue": 128000, "orders": 430}
def render_chart(title: str, data: dict) -> str:
return f"[图表] {title}:{data}"
report_skill = Skill(
name="sales_report",
description="查询销售数据并生成结构化分析报告",
prompt="你是销售分析专家,先给结论,再列关键指标,最后给一条行动建议。",
tools={"query_sales": query_sales, "render_chart": render_chart},
keywords=["销售", "报表", "业绩", "revenue"],
)
notify_skill = Skill(
name="notify",
description="把结果发送到群聊或邮件",
prompt="你是通知助手,标题不超过20字,正文用要点呈现。",
tools={},
keywords=["发送", "通知", "邮件", "分享"],
)
registry = SkillRegistry()
registry.register(report_skill)
registry.register(notify_skill)
class SkillOrchestrator:
"""编排器:加载命中技能并组合执行"""
def __init__(self, registry: SkillRegistry):
self.registry = registry
def plan_and_run(self, user_input: str) -> str:
skills = self.registry.route(user_input)
if not skills:
return "未匹配到可用技能,进入通用对话模式。"
# 只把命中技能的提示词拼进上下文,实现按需加载
loaded_prompts = [s.prompt for s in skills]
print("加载技能:", [s.name for s in skills])
print("激活提示词:", loaded_prompts)
# 示例:若同时命中报表与通知,则串联成工作流
if "sales_report" in [s.name for s in skills]:
data = report_skill.run_tool("query_sales", region="华东")
chart = report_skill.run_tool("render_chart",
title="华东销售概览", data=data)
if any(s.name == "notify" for s in skills):
return f"已生成报告并发送通知:{chart}"
return f"报告生成完成:{chart}"
return f"已加载技能:{[s.name for s in skills]}"
if __name__ == "__main__":
orchestrator = SkillOrchestrator(registry)
print(orchestrator.plan_and_run("帮我做一份华东销售业绩报表并发送到群里"))运行后可以看到,注册表只激活了 sales_report 与 notify 两个技能,它们的专属提示词在本次请求才进入上下文,从而把无关技能彻底排除在模型视野之外。
技能组合:从链式调用到声明式工作流
简单场景用顺序编排即可,但真实智能体工作流往往包含条件分支与人工确认。建议把组合关系写成声明式计划,而不是散落在 if/else 中:
yaml
# 声明式技能组合计划:销售周报工作流
name: weekly_sales_brief
steps:
- skill: sales_report
action: query_sales
output: raw_data
- skill: sales_report
action: render_chart
input: { title: "周报图表", data: "${raw_data}" }
- skill: notify
when: "${raw_data.revenue} > 100000" # 条件分支
require_approval: true # 高额动作需人工确认这种写法有三个好处:一是技能可复用,同一个 notify 能被多条工作流引用;二是可观测,每一步的输入输出都能在日志里追踪;三是可治理,权限、审批、超时等策略可以挂在步骤级别统一管控。
避坑总结:实战中的高频陷阱
第一,路由描述写成功能罗列,导致技能误召回。 常见现象是用户随口说一句「上次那个报表不用发了」,模型却加载了通知技能。修正方法是在技能描述里同时写清「何时使用」和「何时不要使用」,并为路由补充否定示例;对于取消、否定类意图,应先做意图判别再进入技能匹配。
第二,把全部技能元数据塞进上下文,按需加载形同虚设。 有些实现虽然做了注册表,却把几十个技能的完整提示词都放进系统消息。正确做法是上下文里只保留「名称 + 一句话描述」的精简清单,命中后再注入该技能的完整提示词与工具签名,把 token 花在刀刃上。
第三,技能之间隐式共享可变状态,组合时数据串味。 比如两个技能都读写同一个全局变量,并行执行时出现覆盖。应通过编排器显式传递数据,用 ${raw_data} 这类引用声明依赖,保证每个技能输入不可变、输出可序列化,便于重试与缓存。
第四,高风险技能缺少权限与确认闸门。 发送邮件、删除数据、下单支付这类技能一旦被错误触发代价很大。务必在技能元数据中标注风险等级,在工作流步骤上强制 require_approval,并在工具层做二次校验,而不是只靠提示词约束模型。
技能体系的本质,是把智能体的「聪明」与工程上的「可控」解耦:模型负责理解与决策,注册表负责供给能力,编排器负责秩序与边界。当技能可以被独立注册、按需加载、安全组合时,智能体才真正具备了持续生长的工程基础。
免责声明
本工具与文章内容仅用于学习测试场景,优化建议仅供参考,请勿直接用于生产环境,由此产生的风险自行承担。