当前位置: 首页 > news >正文

AI应用五层架构:Prompt、Function Call、Skill、Agent与MCP的职责边界

1. 这五个词不是同级概念,而是四层嵌套关系

你刷到过太多“Prompt工程师速成课”“Agent开发入门指南”“MCP协议详解”这类标题,点进去却发现讲的全是同一套东西——比如用一段带角色设定的文本调用大模型,再配上几个if-else判断,就号称实现了“智能Agent”。结果跑两天就崩:提示词超长报错、函数调用返回空、技能注册失败、MCP服务连不上……最后在日志里反复看到那句刺眼的context overflow: prompt too large for the model,却不知道问题出在哪一层。

这不是你代码写得差,是根本没理清这五个词的真实层级关系。它们不是并列的“技术名词”,而是一条从表层指令到底层协议的完整执行链:
Prompt 是输入形态,Function Call 是执行动作,Skill 是能力封装,Agent 是调度中枢,MCP 是跨系统通信标准
就像你不会把“菜单上的菜名”“后厨切配动作”“厨师的拿手绝活”“餐厅经理的排班逻辑”和“美团外卖API协议”混为一谈——它们各自解决不同层面的问题,强行拉平理解,必然导致设计失焦、调试混乱、上线即崩。

我去年带一个电商客服Agent项目,团队初期就栽在这上面:前端同学猛堆Prompt模板,以为加个“请用JSON格式输出”就能让模型自动调用订单查询接口;后端同学直接硬编码Function Call参数,结果模型返回的字段名和实际API要求的对不上;运维同学部署MCP Server时发现配置文件里写的skill_id和Skill仓库注册的ID大小写不一致,整个服务卡死在初始化阶段。三天没跑通一个完整流程,最后我们关掉所有IDE,用白板画了五层架构图,才真正看清每个词该管什么、不该管什么。

提示:别急着写代码。先问自己三个问题:

  • 当前问题是否能用更简单的Prompt优化解决?(比如加few-shot示例)
  • 是否必须触发外部系统?(比如查数据库、调支付接口)
  • 这个能力是否会被多个Agent复用?(比如“查物流”技能)
    答案依次为“是→否→否”,说明你只需要优化Prompt;答“是→是→否”,说明你需要Function Call;答“是→是→是”,才轮到Skill和Agent设计。

这五个词的混淆,本质是AI应用开发中“职责边界模糊”的典型症状。接下来我会一层一层拆解,不讲虚的定义,只告诉你:在哪一层该做什么、为什么必须这么做、踩过哪些坑、怎么一眼识别当前问题属于哪一层

2. Prompt:不是万能胶,而是精确制导的引信

很多人把Prompt当成“给模型喂的饲料”,越多越好、越细越好。于是出现这种经典反模式:

你是一个资深电商客服专家,精通所有商品知识、售后政策、物流规则。请严格遵守以下要求: 1. 回答必须控制在150字以内 2. 使用中文,语气亲切但专业 3. 若用户询问订单状态,请先确认订单号格式(8位数字),再调用getOrderStatus函数 4. 若用户要求退货,请说明需提供开箱视频和发票照片 5. 所有回答结尾必须带emoji 6. 避免使用“可能”“大概”等模糊词汇 7. ……(后面还有12条格式约束)

这段Prompt长达437字,表面看很“专业”,实则埋了三颗雷:

  • 第一颗雷:模型根本记不住。主流模型上下文窗口虽标称128K,但实际有效记忆长度远低于此。测试显示,当Prompt超过200字,模型对末尾指令的遵循率断崖式下跌——它不是“不想执行”,是压根没把“结尾必须带emoji”这个指令加载进工作记忆。
  • 第二颗雷:和Function Call强耦合却无容错。第3条要求“调用getOrderStatus函数”,但没定义函数不存在时的fallback逻辑。结果模型在非结构化对话中突然生成{"function": "getOrderStatus", "parameters": {"order_id": "abc"}},而实际API只接受纯数字ID,直接500报错。
  • 第三颗雷:把本该由Skill层处理的业务规则塞进Prompt。比如“需提供开箱视频和发票照片”是退货政策,属于领域知识,应固化在Skill的校验逻辑里,而非靠模型每次临场发挥。

真正的Prompt工程,核心是做减法。我团队现在写Prompt只保留三个必选项:

  1. 角色锚定(Role Anchor):用10字内定义身份,如电商客服(仅处理订单与物流)。括号里的限定词比长篇大论更有效——它直接过滤掉模型的“知识幻觉”倾向。
  2. 任务指令(Task Directive):动词开头,单句完成,如解析用户消息,提取订单号和查询意图。绝不出现“如果…那么…”这类条件句,那是Function Call或Skill的职责。
  3. 输出契约(Output Contract):明确格式+字段+约束,如JSON格式:{"intent": "query_status", "order_id": "纯数字字符串(8位)"}。字段名必须和下游Function Call的参数名完全一致,大小写敏感。

我们做过AB测试:同一组用户咨询,用“精简Prompt(平均98字)+健壮Function Call”方案,准确率92.3%;用“冗长Prompt(平均386字)+简单Function Call”方案,准确率仅67.1%。差距不是模型能力问题,是Prompt越臃肿,模型越容易在噪声中丢失关键指令。

注意:当你的Prompt开始出现“请确保”“务必注意”“绝对不能”这类强制性措辞时,基本可以判定——你正在用Prompt解决本该由代码解决的问题。立刻停手,把这部分逻辑下移到Function Call或Skill层。

3. Function Call:不是API调用,而是模型与世界的握手协议

很多教程把Function Call讲成“让模型调用API”,这严重误导。Function Call的本质,是模型输出结构化意图,由运行时环境执行真实动作。模型本身不会发HTTP请求,它只生成一个JSON对象,比如:

{ "name": "get_order_status", "arguments": {"order_id": "12345678"} }

这个JSON必须被框架捕获、校验、转换为真实API调用,再把结果注入下一轮上下文。中间任何一环断裂,就会出现prompt has no outputs这类诡异错误。

我们踩过最深的坑,是混淆了“模型能生成”和“系统能执行”。某次上线前测试,模型稳定输出{"name": "refund_apply", "arguments": {"order_id": "12345678", "reason": "商品破损"}},但生产环境一直报错。排查三天才发现:本地开发环境用的是Mock API,返回字段是{"status": "success", "refund_id": "REF123"};而生产API实际返回{"code": 0, "data": {"id": "REF123"}}。模型生成的Function Call没问题,但框架解析响应的代码没适配生产字段,导致后续流程卡死。

Function Call的设计,必须守住三条铁律:

  • 命名一致性:Function name必须全局唯一且语义清晰。我们禁用queryget这类泛动词,强制用get_order_statusapply_refund等具体动作+宾语组合。曾因两个Skill都注册了search函数,导致模型随机调用错误服务。
  • 参数契约化:arguments必须是扁平JSON对象,禁止嵌套结构。模型对深层嵌套的解析极不稳定。例如{"user": {"id": "123", "info": {"level": "vip"}}}极易被简化为{"user_id": "123"}。我们要求所有参数展平为{"user_id": "123", "user_level": "vip"}
  • 错误熔断机制:Function Call失败时,绝不能静默吞掉错误。必须返回标准化错误对象,如{"error": "ORDER_NOT_FOUND", "message": "订单12345678不存在"},并由Agent层决定重试、降级或转人工。我们曾因忽略这点,导致用户反复询问“我的订单呢”,而系统循环返回空结果。

工具选型上,我们放弃早期自研的Function Router,改用OpenAI官方Function Calling规范(现演进为Tool Calling)。不是因为它多先进,而是它的错误码体系、超时重试、参数校验逻辑已被千万次生产验证。自研方案省下的2天开发时间,最终在排查兼容性问题上花了17天。

关键经验:Function Call的调试,永远从“模型输出”和“框架接收”两端同时抓。用日志打桩:

  • 模型侧:记录原始tool_calls数组内容
  • 框架侧:记录解析后的function_nameparsed_arguments
    两者不一致?问题在模型微调或Prompt设计;一致但执行失败?问题在API对接或错误处理逻辑。

4. Skill:不是功能模块,而是可插拔的能力原子

当多个Agent都需要“查物流”能力时,你会怎么做?复制粘贴Function Call代码到每个Agent?还是写个公共库?这些都不是Skill的正确打开方式。Skill的本质,是将Function Call、业务规则、错误处理、监控埋点打包成独立部署的最小能力单元,通过标准协议(如MCP)被任意Agent动态发现和调用。

我们最初犯的错,是把Skill当成“带注释的函数”。比如物流查询Skill,代码里硬编码了快递鸟API的密钥、超时时间、重试次数。结果运营部门要求切换到顺丰API时,要修改所有引用该Skill的Agent代码,发布周期从2小时拉长到3天。

真正的Skill设计,必须解耦三层:

  1. 能力契约层(Contract):定义Skill能做什么、输入输出格式、SLA承诺。我们用YAML描述,如:
    id: logistics-tracker-v2 version: 1.3.0 description: 查询国内主流快递实时物流轨迹 input_schema: order_id: string # 8位数字 carrier_code: enum[SF, YD, ZTO, STO] output_schema: status: enum[DELIVERED, IN_TRANSIT, PICKUP_FAILED] last_update: datetime sla: p95_latency_ms: 800 error_rate: <0.5%
  2. 实现层(Implementation):纯业务逻辑,不包含任何环境配置。API密钥、超时时间等通过环境变量注入,启动时由Skill Runtime加载。
  3. 接入层(Adapter):负责将MCP协议请求转换为内部调用,并将结果按MCP格式返回。这一层屏蔽了所有网络细节。

Skill的部署形态也颠覆传统认知:它不是常驻进程,而是按需加载的容器化服务。我们用Kubernetes Job管理Skill实例——当Agent首次调用logistics-tracker-v2时,调度器拉起一个Pod,执行完即销毁。好处是资源利用率提升63%,且彻底避免“一个Skill内存泄漏拖垮整个Agent集群”的灾难。

最值得分享的实战技巧:Skill的版本灰度策略。新版本Skill上线时,我们不直接替换旧版,而是让Agent按流量比例分发请求。比如v2.0版本先承接5%流量,监控其错误率、延迟是否达标;达标后再逐步提升至100%。这让我们在一次快递鸟API变更中,零感知地完成了全量迁移——旧版Skill处理剩余95%请求,新版处理5%并持续验证,直到确认稳定。

警惕伪Skill:如果你的“Skill”需要修改Agent代码才能接入,或者无法独立启停、监控、升级,那它只是个普通函数,不是Skill。真正的Skill,应该像USB设备一样——插上即用,拔掉无感。

5. Agent:不是智能体,而是多技能协同的指挥官

把Agent想象成“会思考的机器人”是最大误区。它既不思考,也不决策,它只是一个严格遵循预设规则的调度引擎。它的核心价值,从来不是“多聪明”,而是“多可靠”——在复杂、不确定的环境中,确保每个步骤按预期执行,并在异常时优雅降级。

我们设计Agent时,彻底抛弃了“自主规划”这类高风险模式。取而代之的是三段式确定性流程

  1. 意图解析(Intent Parsing):用精简Prompt + 少量Few-shot示例,将用户消息映射到预定义意图集。比如“我的快递到哪了?”{"intent": "QUERY_LOGISTICS", "slots": {"order_id": "12345678"}}。绝不允许模型自由生成意图名,全部来自Skill Registry的白名单。
  2. 技能编排(Skill Orchestration):根据意图匹配Skill,检查依赖关系。例如QUERY_LOGISTICS需要先调用validate_orderSkill验证订单有效性,再调用logistics-tracker。编排逻辑写死在Agent配置中,不交给模型推理。
  3. 结果合成(Response Synthesis):将Skill返回的结构化数据,用极简Prompt注入最终回复。如基于{logistics_data},用口语化中文告诉用户快递状态,不超过2句话

这种“笨办法”带来的稳定性,远超任何“自主Agent”方案。上线半年,我们的客服Agent平均无故障运行时间达142小时,而同期采用LLM Planner方案的竞品,平均2.3小时就因规划错误进入死循环。

Agent的致命陷阱,是过度信任模型的“推理能力”。某次我们尝试让Agent自主决定是否需要调用apply_refundSkill——模型分析用户消息后,输出{"should_refund": true, "confidence": 0.87}。结果遇到用户说“我再想想”,模型仍以0.87置信度触发退款,造成资损。后来我们砍掉所有“should_”类判断,改为固定规则:只有用户消息明确包含“我要退货”“申请退款”等关键词,且订单状态满足shipped,才允许调用。

Agent的监控指标也必须务实:我们不看“规划成功率”,只盯三个黄金指标:

  • Skill调用成功率:反映底层服务健康度
  • 意图解析准确率:反映Prompt和Few-shot质量
  • 端到端延迟P95:反映整体链路效率

当这三个指标同时恶化,问题一定在MCP层或基础设施;若仅第一个下降,问题在Skill;若仅第二个下降,问题在Prompt。这种归因逻辑,让故障定位时间从平均47分钟缩短到8分钟。

实操建议:从第一天起,就给Agent装上“刹车系统”。在所有Skill调用前插入pre_check钩子,检查订单状态、用户权限、库存余量等硬性条件。宁可让一次调用失败,也不要让错误结果流入下游。我们甚至在apply_refund前强制校验“该订单7天内无退货记录”,看似保守,却避免了92%的恶意退款请求。

6. MCP:不是又一个协议,而是Agent生态的交通规则

MCP(Model Context Protocol)常被误读为“Agent间的通信协议”,这就像把TCP/IP说成“电脑之间的聊天工具”。MCP的真实定位,是为异构Skill服务建立统一的发现、调用、监控标准,让不同团队、不同语言、不同云厂商开发的Skill,能在同一Agent平台上即插即用。

我们早期用自定义HTTP API对接Skill,很快陷入泥潭:

  • A团队的Skill返回{"code": 200, "data": {...}}
  • B团队的Skill返回{"success": true, "result": {...}}
  • C团队的Skill用gRPC,但Agent只支持REST
  • D团队的Skill要求JWT鉴权,而E团队用API Key

每次接入新Skill,都要写一堆Adapter代码。更糟的是,当A团队升级Skill v2.0,返回结构变成{"status": "ok", "payload": {...}},所有调用方集体崩溃。

MCP的解法很朴素:强制所有Skill提供标准元数据接口,并遵循统一请求/响应格式。比如,任何MCP Skill必须暴露/.well-known/mcp端点,返回:

{ "mcp_version": "1.0", "capabilities": ["logistics-tracker", "order-validator"], "endpoints": { "logistics-tracker": { "method": "POST", "path": "/v1/logistics", "input_schema": { ... }, "output_schema": { ... } } } }

Agent通过这个元数据,自动生成调用客户端,无需人工编写Adapter。我们接入第17个Skill时,开发耗时从平均1.5天降至15分钟。

MCP的威力,在于它把“协议适配”这个脏活,从Agent开发者手里,移交给了Skill开发者。后者必须在开发阶段就遵循MCP规范,否则无法注册到Skill Registry。这种“向左移”的质量管控,让我们的Skill平均可用率从83%提升至99.2%。

但MCP不是银弹。我们踩过最大的坑,是忽视了协议版本兼容性。MCP 1.0规定错误响应必须是{"error": {"code": "INVALID_INPUT", "message": "xxx"}},而某团队升级到MCP 1.1后,改成{"errors": [{"code": "..."}]}。Agent框架未做版本协商,直接解析失败。解决方案很简单:在MCP握手阶段,强制声明支持的版本范围,并在Skill Registry中维护版本兼容矩阵。

关键提醒:MCP Server不是“必须自建”的组件。我们评估过所有方案后,选择托管在云厂商的MCP Gateway服务上。理由很现实:自建MCP Server要投入3人月开发+持续运维,而托管服务年费不到这个成本的1/5,且SLA承诺99.95%。在AI应用早期,把精力花在业务逻辑上,比造轮子明智得多。

7. 五层联动的实战诊断:从报错日志定位问题根源

当你看到auto-compaction failed (context overflow: prompt too large for the model),别急着删Prompt。这是系统在报警,但报警位置未必是问题源头。我整理了一套五层联动诊断法,用真实日志片段演示如何快速归因:

场景:用户问“订单12345678的快递到哪了”,Agent返回空结果,日志出现上述报错。

7.1 第一步:锁定报错发生层

查看完整日志上下文,找到报错前的最后一行有效输出:

[INFO] Agent received user message: "订单12345678的快递到哪了" [INFO] Intent parsed: {"intent": "QUERY_LOGISTICS", "slots": {"order_id": "12345678"}} [INFO] Skill 'logistics-tracker-v2' selected [INFO] MCP request sent to http://mcp-gateway/skills/logistics-tracker-v2 [ERROR] auto-compaction failed (context overflow: prompt too large for the model)

关键线索:报错发生在MCP request sent之后,但MCP response received之前。说明问题不在Prompt或Agent层,而在MCP网关或Skill服务本身

7.2 第二步:检查MCP网关日志

登录MCP Gateway控制台,筛选logistics-tracker-v2的请求:

[WARN] Skill 'logistics-tracker-v2' returned 504 Gateway Timeout [INFO] Request body size: 12.4MB (exceeds limit 10MB)

真相浮现:Skill返回了12.4MB的物流轨迹详情(含1000+节点),远超MCP网关10MB限制。问题根源在Skill层——它没有对返回数据做裁剪。

7.3 第三步:验证Skill行为

直接调用Skill的健康检查端点:

curl http://logistics-skill:8080/health # 返回 {"status":"ok","version":"1.3.0","max_response_size_mb":10}

确认Skill声明了10MB限制,但实际返回超标。翻看Skill代码,发现它调用快递鸟API时,未设置show_path=10参数,导致返回全量历史节点。

7.4 第四步:修复与验证

  • Skill层:在API调用参数中添加show_path=10,限制最多返回10个物流节点
  • MCP层:在网关配置中增加response_size_limit_mb: 10硬性拦截
  • Agent层:添加Fallback逻辑——当MCP调用失败,返回“正在查询,请稍候”而非空结果

修复后,同一请求的日志变为:

[INFO] Agent received user message: "订单12345678的快递到哪了" [INFO] Intent parsed: {"intent": "QUERY_LOGISTICS", "slots": {"order_id": "12345678"}} [INFO] Skill 'logistics-tracker-v2' selected [INFO] MCP request sent to http://mcp-gateway/skills/logistics-tracker-v2 [INFO] MCP response received: {"status": "IN_TRANSIT", "last_update": "2024-05-20T14:22:33Z"} [INFO] Response synthesized: "您的快递已在派送中,预计今天18:00前送达!"

这个案例揭示了一个残酷事实:90%的“Agent故障”,实际是Skill或MCP层的基建缺陷。而开发者总在Prompt里反复删减,徒劳无功。学会用五层视角看日志,比背一百个Prompt技巧更救命。

终极心法:当问题出现,按此顺序排查——

  1. 看日志时间戳:报错前最后成功执行的是哪一层?
  2. 查对应层监控:该层的错误率、延迟、资源使用率是否异常?
  3. 抓网络包/日志:确认上下游传递的数据是否符合该层契约?
  4. 隔离验证:绕过上层,直接调用该层下游,是否复现问题?
    守住这个顺序,你就能在10分钟内,从context overflow定位到Skill代码里一行缺失的参数。
http://www.gsyq.cn/news/1585700.html

相关文章:

  • Python项目自动化工具Nox:10分钟掌握环境管理与CI/CD集成
  • 千问Agent vs 微信AI:轻量级智能体的跨平台任务执行实战
  • Bouncy Castle性能优化与安全实践:10个关键技巧提升Java加密效率
  • Claude Code VS Code插件配置全指南:从零部署到多模型接入
  • 深度解析日程邀请钓鱼攻击:从iCalendar协议到企业安全防御实战
  • DroidFrida:Android设备上的动态代码插桩与Hook实战指南
  • Trae:面向AI原生开发的工程化IDE
  • 基于强化学习的RAG检索策略自适应优化:从原理到工程实践
  • MATLAB可视化教学:动态演示微积分核心概念与工程应用
  • MATLAB/Simulink图表高质量插入Word全攻略:从手动导出到自动化报告
  • 双交互光标系统:提升多任务效率的人机交互新范式
  • 基于ESP8266的可堆叠物联网设备设计:从模块化架构到稳定部署
  • SKILL:可编程的AI写作风格协议栈
  • Codex不是本地大模型,而是轻量级本地AI编程代理系统
  • Phish AI API实战:集成AI钓鱼邮件检测,构建自动化安全响应
  • C语言指针本质:内存地址操作与工程实践指南
  • 润乾自助报表Copilot:垂直领域AI助手的工程化实践
  • 恶意代码逆向分析实战指南:从工具链搭建到样本解剖
  • OWASP Juice Shop实战:GDPR数据保护合规演练与漏洞挖掘
  • OpenClaw本地AI工作流:开源LLM前端与技能调度中枢
  • NIM不是API平台:国产大模型GLM-4.7/M2.1本地部署全链路解析
  • 智谱AI批量文生图:从API调用到生产级调度的完整工程实践
  • Clawdbot:面向开发者的数据采集基础设施
  • 零基础入门漏洞挖掘:从网络协议到SRC实战的完整技能栈
  • MATLAB外部进程管理:从system命令到.NET Process与COM自动化
  • 多智能体LLM在量化投资中的应用:架构、自适应集成与因子轮动
  • 本地部署Qwen+Ollama+LangChain全链路实战指南
  • AI驱动的RBAC工程化流水线:从设计稿到权限就绪代码
  • MATLAB/Simulink机器人仿真:从数字孪生到代码部署的工程实践
  • Simulink建模四层框架:从意图到验证的系统工程实践