1. 从一次“翻车”的Agent设计说起
最近在折腾一个基于Claude的智能客服Agent,想让它能处理一些简单的工单查询。我的第一反应,也是很多人的直觉:把所有东西都塞进system prompt里。我洋洋洒洒写了近两千字的prompt,从公司介绍、产品分类、常见问题FAQ,到工单处理流程、礼貌用语规范,甚至把几个核心API的调用示例都贴了进去。心想,这下总该万无一失了吧?
结果呢?这个Agent的表现堪称灾难。让它查个用户订单状态,它要么答非所问,在回复里复述一遍公司价值观;要么在处理一个简单请求时,突然开始尝试调用一个完全不相关的API,并陷入逻辑混乱。更糟糕的是,每次我试图增加一点新功能,比如让它能根据用户ID拉取历史记录,我就得去修改那个已经臃肿不堪的system prompt,然后祈祷这次调整不会破坏之前好不容易调教好的其他功能。整个系统的响应速度也明显变慢,成本还高得吓人。
这次经历让我彻底反思:我们是不是把system prompt当成了一个“万能知识垃圾场”?为什么一个理论上更“智能”的Agent,表现反而不如一个功能单一的脚本?问题的核心,就在于对System Prompt、Skills(或Functions/Tools)以及知识这三者角色与边界的混淆。今天,我们就来彻底厘清这件事:Agent为什么需要Skills,以及如何正确地构建一个模块化、可维护的智能体。
2. 拆解Agent的“大脑”:System Prompt的本质与能力边界
要理解为什么不能把所有东西都塞进system prompt,首先得明白system prompt到底是什么,以及大语言模型(LLM)是如何处理它的。
2.1 System Prompt:角色的定义与推理的引导
你可以把system prompt理解为给AI模型的一份“入职须知”或“角色扮演剧本”。它的核心作用不是存储知识,而是设定上下文、定义行为准则、框定回答范围。当你写下“你是一个专业的客服助手”时,你是在激活模型内部与“专业性”、“客服”相关的模式和行为倾向。
LLM在处理prompt时,本质上是在进行概率预测。它根据你的输入(包括system prompt和user message),预测最可能出现的下一个词序列。system prompt作为前置条件,强烈地影响了这个预测的起始方向。但它有几个关键的限制:
- 有限的注意力窗口:尽管上下文窗口越来越大(如128K、200K),但模型对信息的“有效注意力”并非均匀分布。过于冗长的system prompt会导致关键指令被淹没在文本海洋中,模型可能只记住了开头和结尾,而忽略了中间的重要细节。这就是所谓的“中间部分衰减”现象。
- 指令冲突与混淆:当你在一个prompt里同时下达“简洁回答技术问题”和“详细解释概念给新手”这两种可能冲突的指令时,模型需要额外的逻辑来判断当前场景适用哪一条,这增加了推理的不确定性和出错概率。
- 知识检索效率低下:让模型从一大段文本中寻找特定信息(比如“产品A的SKU编码规则是什么”),类似于让你在不带目录和索引的千页手册里找一句话。即使信息存在,检索和提取的准确率也会随着文本长度增加而急剧下降。
2.2 当System Prompt沦为“知识库”:代价是什么?
把我最初那个臃肿的客服prompt作为反面教材,我们来分析一下把知识硬塞进system prompt的具体代价:
- 性能下降:更长的prompt意味着每次API调用都需要传输和处理更多的tokens。这直接转化为更长的响应时间(Latency)和更高的使用成本(尤其是按token计费的模型)。对于一个需要频繁交互的Agent来说,这是不可接受的。
- 可靠性降低:复杂、多目标的prompt更容易导致模型“精神分裂”。它可能在一个对话中突然切换角色,或者将适用于A场景的规则错误地应用到B场景。调试变得极其困难,因为你很难定位是prompt中哪一部分指令导致了问题。
- 可维护性灾难:这是最致命的一点。业务逻辑、产品信息、API规则都是动态变化的。每次更新都需要去修改那个核心的、牵一发而动全身的system prompt。没有版本控制,没有模块化,任何修改都像是在走钢丝,很容易引入新的bug或导致原有功能退化。
- 知识更新滞后:把静态知识写进prompt,意味着知识更新周期与Agent部署周期绑定。今天产品价格变了,你必须重新修改prompt、测试、再部署。无法实现实时或准实时的知识更新。
所以,一个清晰的结论是:System Prompt应该专注于“怎么想”和“怎么答”(行为与流程),而不是“知道什么”(具体知识)。那么,具体的“知识”和“能力”应该放在哪里?答案就是Skills。
3. Skills:Agent的“瑞士军刀”与“外部大脑”
如果说system prompt定义了Agent的“人格”和“思维方式”,那么Skills就是它可随时取用的“工具包”和“外部记忆体”。Skills(在不同框架中也可能被称为Tools、Functions、Capabilities)是一种将复杂能力或动态信息封装成标准化接口的模块。
3.1 Skills的核心价值:能力扩展与状态隔离
为什么Skills是必须的?因为它解决了system prompt无法解决的几个根本问题:
- 突破模型的固有知识边界与时效性:LLM的训练数据是有截止日期的,它不知道今天天气、你的私人邮件、最新的股价或数据库里的实时数据。通过Search Skill、Weather Skill、Database Query Skill,Agent获得了感知和影响外部世界的能力。
- 执行确定性的操作:让LLM直接生成一段代码来操作文件或发送邮件是危险且低效的。通过封装好的File Write Skill或Send Email Skill,LLM只需要生成符合格式要求的调用参数(如
{“action”: “send_email”, “to”: “user@example.com”, “subject”: “...”, “body”: “...”}),由Skill来负责安全、可靠地执行。这大大降低了出错率。 - 实现复杂的、多步骤的逻辑:有些操作涉及多个系统或需要条件判断。例如,“如果用户满意度低于阈值,则创建一条高危工单并通知经理”。你可以将这个逻辑封装成一个
CreateAlertTicketSkill,Agent只需触发它并传入用户ID和评分,所有后续复杂流程都在Skill内部完成。 - 保持System Prompt的纯净与稳定:当所有具体操作和知识查询都委托给Skills后,你的system prompt可以变得非常简洁和稳定。它只需要描述清楚:“你是XX助手,当用户需要你做某件事时,你可以使用相应的工具(Skills)。这是你可用的工具列表及其描述。” 这样一来,修改一个Skill(比如更新查询API)完全不会影响Agent的核心推理逻辑。
3.2 实战解析:一个Skill的诞生记
以我的客服Agent改造为例,我首先拆解出了几个核心Skill:
QueryOrderSkill:根据订单号查询状态。背后连接的是订单数据库的API。SearchKBSkill:根据用户问题,在知识库(如Elasticsearch)中进行语义搜索,返回相关答案片段。CreateTicketSkill:在工单系统(如Jira、Zendesk)中创建新的工单。GetUserProfileSkill:根据用户ID获取基本信息,用于个性化服务。
我们以QueryOrderSkill为例,看看它的典型实现和与Agent的交互流程:
Skill定义(以OpenAI Function Calling格式为例):
{ “name”: “query_order_status”, “description”: “根据提供的订单号,查询订单的当前状态、物流信息及预计送达时间。如果订单号无效或无法找到,返回错误信息。”, “parameters”: { “type”: “object”, “properties”: { “order_id”: { “type”: “string”, “description”: “用户的订单编号,通常以‘ORD’开头,后接8位数字。” } }, “required”: [“order_id”] } }Agent与Skill的协作流程:
- 用户输入:“帮我查一下订单ORD20240815001到哪了。”
- Agent推理:简洁的system prompt让Agent识别出这是“查询订单状态”的意图。它决定调用
query_order_status这个skill。 - 参数生成:Agent根据对话历史,提取出关键参数
order_id: “ORD20240815001”,并按照Skill定义的JSON格式组织好。 - Skill执行:框架(如LangChain、Semantic Kernel)或你自己写的后端服务接收到这个调用,执行真正的业务逻辑:验证订单号格式、调用数据库或内部API、获取物流信息。
- 结果返回:Skill将执行结果(成功则返回状态信息,失败则返回错误原因)以结构化数据(JSON)的形式返回给Agent。
- Agent组织回复:Agent收到结果后,用自然语言将信息组织成一段友好的回复给用户:“您的订单ORD20240815001已发货,目前正在运输中,预计明天下午送达。”
整个过程中,Agent不需要知道数据库的IP地址、API的鉴权方式、SQL查询语句怎么写。它只关心“意图识别-选择工具-提供参数-解释结果”这个高级逻辑链。这就是关注点分离带来的巨大优势。
4. 架构演进:从Monolithic Prompt到MCP驱动的模块化生态
随着Agent复杂度的提升,管理和集成越来越多的Skills本身也成了挑战。这就引出了当前最热门的一个概念:MCP(Model Context Protocol)。
4.1 MCP是什么?为什么它是下一个关键拼图?
你可以把MCP理解为Skills的“应用商店”和“统一连接器”。它是由Anthropic等公司推动的一个开放协议,旨在标准化AI模型(如Claude)与外部工具、数据源(即Skills)之间的交互方式。
在没有MCP之前,每个Agent框架(LangChain, LlamaIndex, Semantic Kernel)、每个AI应用(Cursor, Windsurf, CodeBuddy)都需要自己定义一套集成Skills的方式。开发者要为不同的平台重复开发功能相似的Skill,适配成本很高。
MCP的核心思想是:
- 标准化:定义一套统一的协议,用于声明(Server提供哪些Skills)、发现(Client查找可用Skills)和调用(Client请求Server执行某个Skill)Skills。
- 解耦:Skill提供者(MCP Server)和Skill消费者(MCP Client,如CodeBuddy、Claude Desktop)完全分离。一个写好的、能查询数据库的MCP Server,可以同时被多个不同的AI客户端使用。
- 动态性:Skills可以动态加载和卸载,无需重启主应用。这为实现真正的“插件化”生态奠定了基础。
4.2 如何将MCP Skills集成到你的Agent中?
以热搜词中提到的“将搜索类MCP服务器(如tavily-mcp、brave-search-mcp)添加进codex的详细步骤”为例,这其实反映了大家对于利用现成、强大Skills的迫切需求。虽然“codex”可能是一个泛指或特定工具,但集成MCP Skills的通用流程是相似的:
启动MCP Server:首先,你需要运行一个实现了MCP协议的服务器。例如,
tavily-mcp就是一个将Tavily搜索API封装成MCP Skill的服务器。你通常可以通过Docker或直接运行一个脚本启动它。启动后,这个Server会在一个本地端口(如localhost:8080)上监听,等待Client连接。# 假设使用某个MCP Server的示例 docker run -p 8080:8080 ghcr.io/tavily/mcp-server配置你的Agent框架/客户端:你需要让你使用的Agent开发框架或AI客户端知道这个MCP Server的存在。不同的工具配置方式不同。
- 在Claude Desktop或Cursor中:通常通过编辑一个配置文件(如
claude_desktop_config.json或Cursor的设置)来添加MCP Server的连接信息(名称、命令行或TCP连接地址)。 - 在自定义的Agent项目中(使用LangChain等):你需要使用对应的MCP集成库(如
@modelcontextprotocol/sdk)来创建一个MCP Client,连接到Server,并将其提供的Tools加载到你的Agent实例中。这个过程通常涉及一个load_skill或add_tool的步骤。
- 在Claude Desktop或Cursor中:通常通过编辑一个配置文件(如
验证与使用:配置完成后,在你的Agent对话中,当你提出需要搜索的需求时,Agent会自动显示出可用的搜索Skill(可能叫
search_web或tavily_search),并能在获得授权后调用它获取实时信息。
一个重要的认知转变:在MCP生态下,你作为Agent开发者,很多时候不再需要从零开始编写每一个Skill。你可以像搭积木一样,组合使用来自社区的各种高质量MCP Server(数据库连接、绘图、代码分析、学术搜索等),快速赋予你的Agent强大的能力。你的核心工作,变成了设计高效的system prompt来协调这些Skills,并处理更上层的业务逻辑和对话流。
5. 设计指南:如何规划你的Agent技能体系?
理解了Why和What之后,我们来谈谈How。如何为一个具体的Agent项目设计合理的Skills体系?这里有一套可落地的决策框架。
5.1 决策树:什么该进Prompt,什么该成Skill?
面对一个功能需求,你可以通过以下问题来判断它的归属:
- 是静态的、泛化的行为准则吗?->放入System Prompt
- 例如:“始终用中文回复”、“保持友好和乐于助人的态度”、“如果用户问题模糊,主动询问澄清”。
- 是动态的、需要计算或查询的信息吗?->封装成Skill
- 例如:查询天气、股价、数据库记录、最新新闻。
- 是确定性的、有潜在风险的操作吗?->封装成Skill
- 例如:发送邮件、写入文件、调用付费API、操作硬件。
- 是复杂的、包含多个步骤的业务流程吗?->封装成Skill
- 例如:“用户退款流程”、“生成周报并邮件发送”。
- 是可能频繁变更的业务规则或数据吗?->封装成Skill(或连接外部知识库)
- 例如:产品价格表、促销活动规则、公司组织架构。
5.2 System Prompt的最佳实践:少即是多
一个优秀的system prompt应该像宪法,提纲挈领,而不是像法律条文汇编,事无巨细。
- 核心三要素:
- 角色(Role):清晰定义“你是谁”。(例如:“你是一个专注于电商售后问题的AI客服专家。”)
- 目标(Goal):明确“你的核心任务是什么”。(例如:“你的目标是快速、准确地解决用户的订单、物流和退款问题,提升用户满意度。”)
- 约束与风格(Constraints & Style):规定“你该如何行事”。(例如:“仅使用我提供的工具(Skills)来获取信息和执行操作。对于工具无法处理的问题,如实告知用户并建议其联系人工客服。回复需简洁、专业、富有同理心。”)
- Skills描述集成:在prompt末尾,用清晰的结构列出所有可用Skills的名称和简短、精准的描述。例如:“你可以使用以下工具:1.
query_order: 查询订单状态。2.search_kb: 在知识库中搜索问题答案。...” 让LLM对工具库有一个全局概览。
5.3 Skills设计原则:高内聚、低耦合、好描述
- 功能单一(Single Responsibility):一个Skill只做好一件事。
SearchProductSkill和CalculateShippingSkill应该分开,而不是合并成一个HandleOrderSkill。这有利于复用和调试。 - 接口清晰(Clear Interface):参数的名称和描述要尽可能无歧义。好的描述能极大降低LLM调用时的参数解析错误。对比:
- 差的描述:
“user_input”: “用户输入” - 好的描述:
“search_query”: “用户想要搜索的产品关键词或问题描述,请从对话中精确提取。”
- 差的描述:
- 健壮性(Robustness):Skill内部要有完善的错误处理(如网络超时、API限流、无效输入),并总是返回结构化的结果,包括成功状态和数据(或错误信息)。避免把未处理的异常抛给LLM,这会导致对话崩溃。
- 安全性(Security):对执行写操作或访问敏感数据的Skill,必须实施严格的权限校验和审计日志。切勿让LLM拥有不受限制的“万能钥匙”。
6. 避坑指南:Agent开发中的常见反模式与解决方案
结合我自己的踩坑经验和其他开发者的常见问题,这里有几个需要极力避免的反模式:
反模式1:在Prompt里写“伪代码”或复杂逻辑判断
- 错误做法:在system prompt里写“如果用户问题包含‘价格’一词,就去调用查价API;如果包含‘状态’,就去调用查询状态API...”。
- 问题:LLM不擅长执行这种精确的字符串匹配和逻辑分支,极易出错或遗漏。这本质上是将本应由程序逻辑处理的规则,错误地交给了概率模型。
- 正确做法:将“意图识别”这个复杂任务交给LLM本身,或者更专业的NLU(自然语言理解)模块。你的system prompt只需引导LLM去“思考用户想要什么”,然后由LLM自主决定调用哪个Skill。Skill的描述本身就是最好的意图识别指引。
反模式2:Skill返回原始、冗长的数据
- 错误做法:
QueryDatabaseSkill直接返回一个包含20个字段的JSON行数据给LLM。 - 问题:浪费token,干扰LLM的注意力,可能导致它抓不住重点。
- 正确做法:在Skill内部对数据进行清洗、过滤和摘要。只返回LLM生成回复所必需的关键信息。例如,查询用户信息,只返回
{“name”: “张三”, “会员等级”: “黄金”, “最近订单时间”: “2024-08-10”},而不是完整的用户档案。
反模式3:忽视Skill的上下文管理
- 场景:用户说“把上面的文件保存一下”。LLM需要知道“上面的文件”指代的是哪个文件。
- 问题:如果Skill设计是
save_file(file_path),LLM可能无法从当前对话中推断出具体的file_path。 - 解决方案:这需要系统层面的设计。要么在对话中让Agent主动询问澄清(“您想保存哪个文件?”),要么维护一个会话级的上下文状态(如最近提到的文件列表),并将这个状态作为隐含信息传递给Skill调用逻辑。更高级的做法是设计具有“会话记忆”能力的Skill。
反模式4:盲目追求Skills的数量
- 现象:给Agent加载了30多个Skills,觉得功能强大。
- 问题:过多的选择会让LLM陷入“选择困难症”,增加不必要的推理负担,也可能导致误调用。同时,管理和维护成本激增。
- 建议:遵循最小可用原则。根据Agent的核心场景,只提供最必要的Skills。对于不常用的高级功能,可以考虑设计二级菜单或通过自然语言指令动态启用。
Agent的设计,本质上是一个软件架构问题。将庞大的、单一的system prompt拆分为一个精炼的“指挥中心”(system prompt)和一系列专业的“执行单元”(Skills),是构建稳定、高效、可扩展智能体的必由之路。MCP这类协议的出现,正在让Skills的共享和复用变得像导入一个开源库一样简单。下一次当你开始设计一个Agent时,不妨先问自己:这个功能,是应该成为它思考方式的一部分(Prompt),还是它手边的一件工具(Skill)?想清楚这一点,你的Agent项目就成功了一半。