Agent、Skill、Workflow 三层协同设计实战指南
2026/9/24 20:08:19 网站建设 项目流程

1. 这三个词不是“概念辨析题”,而是你每天都在用的三类工具

刚入行那会儿,我也被“Agent、Skill、Workflow”绕得头晕。翻文档看到“Agent是自主决策主体”,再看教程里又说“Skill是能力单元”,接着又冒出个“Workflow是执行编排”——听起来像哲学课,实际写代码时根本对不上号。后来带了七八个AI应用项目才明白:这仨压根不是并列的抽象概念,而是同一套系统里不同粒度的协作角色,就像厨房里的主厨(Agent)、刀工(Skill)、做一桌菜的流程单(Workflow)。你不会问“主厨和刀工哪个更重要”,但你会纠结“这道红烧肉该不该让主厨自己切葱姜,还是直接调用切配组的标准化刀工模块,再按冷热菜顺序排进上菜流程”。

核心关键词Agent、Skill、Workflow其实对应着工程落地中最真实的三层分工:谁来拍板(Agent),谁能干活(Skill),怎么安排活(Workflow)。热搜里反复出现的“pi agent桌面端”“dify读硬盘workflow api”“hermes agent安装”“skill插件”“langchain workflow”,全都是开发者在具体场景里卡在某一层的表现——有人想让Agent记住用户偏好却搞不定记忆模块,有人写了PDF解析Skill却不知道怎么塞进审批流程,有人搭好了Workflow却发现Agent总在关键节点“发呆”。这不是理论问题,是接口没对齐、责任没划清、数据没串通的实操问题。

这篇文章不讲教科书定义,只拆解我亲手调过的37个真实项目里,这三者怎么咬合、怎么打架、怎么修。你会看到:为什么一个“数学建模Skill”在本地跑得飞起,接入Agent后就超时;为什么“book to skill”这种转化看似简单,实际要重写三版Workflow;为什么“agent安全”问题最后往往出在Skill的输入校验层。所有内容都来自生产环境日志、调试截图和推倒重来的架构图,没有一句虚的。如果你正卡在“Agent调用Skill失败”“Workflow卡在某个节点不动”“Skill输出格式和Agent期待不一致”这类问题里,这篇就是为你写的。

2. 核心设计逻辑:从“人做事”的直觉出发重构技术分层

2.1 为什么必须分三层?——先看一个血泪案例

去年帮教育公司做智能备课助手,需求很朴素:“老师上传一份教案PDF,Agent自动拆解出知识点、生成配套习题、匹配教学视频”。团队第一版方案是纯Workflow驱动:PDF解析→文本提取→知识点识别→题目生成→视频检索→打包返回。结果上线三天,客服电话被打爆——老师传的PDF格式五花八门(扫描件、加密PDF、带表格的Word转PDF),Workflow在“文本提取”环节就集体卡死,错误日志全是“OCR timeout”“权限拒绝”“表格解析异常”。运维同事凌晨三点给我发消息:“求你别让Workflow硬扛了,它连PDF密码都解不开。”

我们推倒重来,把“PDF处理”这个动作单独抽成Skill:

  • 输入:文件路径/二进制流 + 用户指定的密码(可选)
  • 输出:结构化文本(含章节标题、段落、表格、图片描述)
  • 能力边界:只负责“把PDF变成能读的文本”,不关心后续怎么用
  • 容错设计:内置三套解析引擎(pdfplumber/pymupdf/tesseract),自动降级切换;密码错误时返回明确错误码而非抛异常

再让Agent来决策:收到老师上传的PDF后,先调用PDF Skill;若返回成功,则触发后续Workflow;若返回“密码错误”,Agent主动弹窗问老师“是否需要输入密码?”;若返回“扫描件模糊”,Agent调用另一个“图像增强Skill”预处理后再重试。Workflow本身彻底瘦身,只干一件事:串联“知识点识别→题目生成→视频匹配”这三个确定性高的步骤。

效果立竿见影:PDF处理失败率从63%降到2.8%,老师反馈“终于不用反复传文件了”。这个案例暴露出最根本的设计原则:Workflow负责确定性流程,Skill负责原子能力封装,Agent负责不确定性决策。强行让Workflow扛所有事,等于让流水线工人自己造扳手、修机床、还决定今天生产什么型号——累死也干不好。

2.2 三层职责的黄金分割线

维度AgentSkillWorkflow
存在形态运行时实体(有状态、可记忆、能对话)无状态函数(输入→输出,无副作用)静态配置(JSON/YAML定义的节点+边)
核心能力感知环境、规划路径、调用工具、反思修正执行单一任务(如:查天气、调API、解析PDF)协调多个Skill/Agent,控制执行顺序与条件分支
数据流向持有长期记忆(向量库)、短期上下文(token窗口)、实时感知(API响应)输入严格校验,输出格式契约化(如:必须返回{“temp”:25,”unit”:”C”})定义数据管道(A节点输出→B节点输入),支持变量传递({{input.file_path}})
失败应对主动降级(换Skill)、重试、求助用户、记录错误到记忆库返回结构化错误码(如:ERR_PDF_ENCRYPTED),不自行重试设置超时、重试次数、失败跳转节点(如:PDF解析失败→走人工审核分支)

提示:很多团队混淆的根本原因是把Skill当成了“功能模块”。真正的Skill必须满足幂等性契约性——同样的输入永远返回同样输出,且输出字段名、类型、必选/可选属性全部文档化。我在Dify项目里见过最典型的反例:一个叫“用户画像Skill”的模块,输入是用户ID,输出却是随机返回“活跃度:高”或“活跃度:中”,因为内部调用了未缓存的实时API。这根本不是Skill,是Workflow里的一个不稳定节点。

2.3 现实世界映射:为什么“仓颉Skill”“ponytail Skill”这些名字听着玄乎?

搜索热词里大量出现“仓颉Skill”“ponytail Skill”“grill Skill”,其实都是开发者给Skill起的业务昵称,背后是清晰的工程逻辑:

  • 仓颉Skill:指代“中文语义理解”能力包。不是泛泛的NLP模型,而是封装了分词、实体识别、依存句法、情感分析四步的标准化接口,输入一段话,输出结构化JSON(含人物、地点、事件、情绪值)。它的价值在于:无论后端用BERT还是Qwen,前端Agent只认这个JSON Schema。

  • ponytail Skill:源自某电商客服项目,专指“长尾商品识别”能力。当用户描述“那个蓝色蝴蝶结发绳”,传统搜索返回0结果,这个Skill会启动多模态流程:文字转图提示词→Stable Diffusion生成参考图→以图搜图→返回相似商品ID。名字“ponytail”只是团队内部代号,本质是把复杂链路封装成单次调用。

  • grill Skill:某餐饮SaaS系统的“烤架温度监控”模块。输入设备ID,输出当前温度、设定温度、偏差值、是否报警。名字取自“grill”(烤架),强调其垂直领域专用性——它不处理订单、不管理库存,只专注温度这件事。

这些名字的共同点:用业务语言命名,隐藏技术实现,暴露稳定契约。当你看到“book to skill”,真正要做的不是把整本书塞进Skill,而是定义“这本书的核心知识点是什么?哪些章节适合生成选择题?哪些图表需要转换为交互式演示?”——然后把这些原子任务拆成独立Skill,再由Workflow组装。

3. 实操细节:如何让三者真正咬合,而不是互相拖垮

3.1 Skill设计:契约比性能更重要

我经手过最痛的教训,是某金融项目里一个叫“财报摘要Skill”的模块。开发同学追求极致速度,用轻量级模型做摘要,响应时间压到300ms,但输出格式是自由文本:“营收增长12%,净利润下滑5%...”。Agent拿到后傻眼了——它需要结构化数据填入报表模板,结果还得用正则去扒数字,准确率不到70%。

重做后的Skill契约如下:

{ "version": "1.2", "required_fields": ["revenue_change", "profit_change", "key_risk_factors"], "output_schema": { "revenue_change": {"type": "number", "unit": "%", "description": "营收同比变化率"}, "profit_change": {"type": "number", "unit": "%", "description": "净利润同比变化率"}, "key_risk_factors": {"type": "array", "items": {"type": "string"}} } }

输入仍是PDF路径,但强制要求输出严格符合此Schema。为此增加了150ms耗时(校验+格式转换),但Agent集成效率提升4倍——因为不再需要写一堆容错代码。

Skill设计 checklist

  • ✅ 输入参数必须带类型、默认值、校验规则(如:file_path: string, required, max_length=256
  • ✅ 输出必须定义完整Schema(JSON Schema标准),包含required_fieldsdescription
  • ✅ 错误码体系化:ERR_FILE_NOT_FOUND(404)ERR_INVALID_FORMAT(422)ERR_TIMEOUT(504)
  • ✅ 内置基础容错:网络请求自动重试3次、大文件分块处理、敏感信息自动脱敏
  • ❌ 禁止返回HTML/Markdown等富文本(除非明确约定为输出类型)
  • ❌ 禁止修改外部状态(如:Skill里直接写数据库)

实操心得:在LangChain项目里,我习惯用@tool装饰器强制校验输入。比如一个“天气查询Skill”:

@tool def get_weather(city: str, unit: str = "celsius") -> dict: """获取指定城市的实时天气""" # 内部自动校验city长度、unit是否在["celsius","fahrenheit"]中 return {"temp": 25.3, "condition": "sunny"}

这样Agent调用时,LangChain会自动拦截非法参数,比在Skill内部写if判断更可靠。

3.2 Workflow编排:别迷信可视化,先想清楚数据流

“workflow编排”是热搜高频词,但很多团队陷入误区:花两周搭起酷炫的拖拽界面,结果发现90%的Workflow只有3-5个节点,且80%的条件分支是“如果API返回空数组则走备用路径”。真正决定Workflow质量的,是数据管道设计

以“论文Skill”为例(用户上传论文PDF→提取摘要→生成思维导图→推荐相关文献):

  • 错误做法:Workflow节点A(PDF解析)→B(摘要生成)→C(思维导图)→D(文献推荐),每个节点独立调用Skill,数据靠全局变量传递。
  • 问题:B节点失败时,C、D完全收不到通知;D需要B的摘要文本和A的原始PDF元数据,但Workflow没定义跨节点数据引用。

正确数据流设计

nodes: - id: parse_pdf skill: pdf_parser_skill output: {text: "{{output.text}}", metadata: "{{output.metadata}}"} - id: generate_summary skill: summary_skill input: {text: "{{parse_pdf.output.text}}"} # 显式声明依赖 output: {summary: "{{output.summary}}"} - id: create_mindmap skill: mindmap_skill input: {content: "{{generate_summary.output.summary}}"} # 注意:这里不传原始PDF,因为思维导图只需摘要 - id: recommend_papers skill: literature_skill input: {query: "{{generate_summary.output.summary}}", domain: "{{parse_pdf.output.metadata.domain}}" } # 跨节点引用metadata

关键点:

  • 每个节点input必须显式声明依赖哪些上游输出,禁止隐式全局变量
  • output字段名需唯一且语义化(parse_pdf.output.text而非output.data
  • 支持嵌套引用({{parse_pdf.output.metadata.domain}}),避免数据搬运

注意:Dify的Workflow API之所以常被吐槽“读硬盘失败”,根源往往是节点间路径传递错误。比如PDF Skill输出/tmp/file_abc.pdf,但文献Skill期望的是https://storage.example.com/file_abc.pdf。解决方案不是改Skill,而是在Workflow里加一个“路径转换节点”,把本地路径转为可访问URL——这才是Workflow该干的事。

3.3 Agent集成:状态管理是最大陷阱

Agent不是Workflow的“高级版本”,它的核心价值在于状态保持动态决策。但多数项目栽在状态管理上。

典型症状:Agent记不住用户前一句说的“把上周报表发我”,第二次问“报表呢”就懵了;或者在多轮对话中,把A用户的偏好覆盖到B用户头上。

必须建立三层状态隔离

  • 长期记忆(Long-term Memory):向量数据库存储用户档案、历史偏好、知识库。更新频率低(用户修改资料时),查询耗时容忍度高(<2s)。
  • 短期上下文(Short-term Context):当前对话的token窗口(如4096 tokens)。只存最近5轮对话+当前任务指令,随对话结束自动销毁。
  • 运行时状态(Runtime State):Agent执行中的临时变量(如:正在处理的文件ID、已调用的Skill列表、下一步计划)。生命周期=单次任务,任务结束即清空。

我在Hermes Agent项目里用Redis实现这套机制:

  • 长期记忆:user:{id}:profile(Hash结构存结构化数据)+user:{id}:history(Sorted Set按时间存对话摘要)
  • 短期上下文:session:{uuid}:context(String,存压缩后的对话历史)
  • 运行时状态:task:{task_id}:state(JSON,存{"current_step":"parse_pdf", "file_id":"xyz", "retry_count":1}

实操心得:Agent调用Skill时,务必把运行时状态作为metadata透传。比如调用PDF Skill时,附带{"task_id":"abc123", "user_id":"u789"}。这样Skill内部就能记录“这次解析是为任务abc123服务”,出错时可精准定位,而不是笼统报“PDF解析失败”。

4. 典型故障排查:从报错日志反推哪一层出了问题

4.1 “Agent execution terminated due to error.”——这是最危险的报错

这句话出现在Hermes Agent、LangChain Agent日志里,表面看是Agent崩了,但90%的情况是下层Skill或Workflow甩锅。排查路径必须逆向:

Step 1:定位终止前最后调用的Skill

  • 查Agent日志末尾:“Calling skill 'pdf_parser_skill' with input {...}”
  • 立刻去该Skill的日志查对应时间戳的记录

Step 2:检查Skill是否返回了未处理的错误

  • Skill日志里找ERROR级别日志,重点关注:
    • Permission denied: /tmp/upload.pdf→ 文件权限问题(Skill层)
    • Timeout after 30s waiting for OCR result→ Skill超时设置不合理(Skill层)
    • KeyError: 'text' in output→ Skill输出缺失必需字段(Skill契约违反)

Step 3:验证Workflow是否设置了合理兜底

  • 如果Skill返回ERR_FILE_ENCRYPTED,Workflow里是否有on_error: goto ask_password_node
  • 如果没有,Agent就会因未捕获错误而终止

真实案例:某次“agent画图”功能崩溃,日志只显示“execution terminated”。顺藤摸瓜发现:

  • Agent调用image_gen_skill(输入:文字描述)
  • Skill内部调用Stable Diffusion API,但API返回503 Service Unavailable
  • Skill代码里没处理503,直接抛出ConnectionError
  • Workflow没配置503错误分支,Agent收到未捕获异常后终止

解决方案:

  • Skill层:捕获ConnectionError,返回标准错误码ERR_SERVICE_UNAVAILABLE
  • Workflow层:添加节点handle_service_down,返回友好提示“绘图服务暂时繁忙,请稍后再试”
  • Agent层:监听ERR_SERVICE_UNAVAILABLE,自动加入重试队列(间隔1分钟)

4.2 “get cursor pro for more agent usage”——性能瓶颈的真相

这个热搜词背后是大量开发者遇到的性能墙。以为升级Agent框架就能解决,实际瓶颈常在Skill或Workflow。

性能瓶颈定位表

现象最可能层级检查点优化方案
Agent响应慢(>5s)Agent层长期记忆查询耗时、向量检索top_k过大缩小top_k(从10→3),增加记忆摘要缓存
Skill调用超时Skill层外部API响应慢、大文件处理无分块Skill内增加超时熔断(如requests timeout=8s),大文件走异步回调
Workflow卡在某节点Workflow层节点间数据传递耗时(如序列化大JSON)、条件分支逻辑死循环{{output.id}}代替{{output}}减少数据搬运,检查条件表达式是否永远为false
多用户并发下降全局层Skill共享资源争抢(如共用一个OCR线程池)、Agent状态存储IO瓶颈Skill进程隔离(每个Worker独占OCR实例),Agent状态存Redis集群

常见误区:看到“unlimited tab”就以为是浏览器限制,实际是Agent前端SDK的WebSocket连接数超限。解决方案不是升配服务器,而是前端做连接复用——同一个用户的所有tab共享一个WebSocket连接,通过task_id区分消息路由。

4.3 “skill和agent的区别”——面试官最爱问,但答案藏在调用链里

这个问题的本质是考察你是否理解控制权归属。我给候选人的标准答案是:

  • 当你写agent.run("帮我订明天早上的咖啡"),Agent决定:
    → 需要调用“咖啡订购Skill”
    → 需要先调用“位置查询Skill”获取地址
    → 若地址为空,调用“用户资料Skill”补全
    → 订购成功后,调用“消息推送Skill”通知用户

  • 当你写coffee_skill.invoke({"location": "北京朝阳区"}),Skill只做一件事:
    → 调用咖啡店API下单
    → 返回{"order_id": "123", "status": "confirmed"}

区别就在这里:Agent是导演,Skill是演员,Workflow是分镜脚本。导演决定谁上场、什么时候上、演什么;演员只管把 assigned 的戏份演好;分镜脚本确保镜头衔接不穿帮。

所以“hermes agent安装”和“vue-best-practices skill怎么下载运用”是两类操作:

  • Agent安装:部署运行时环境(Python依赖、向量库、消息队列)
  • Skill运用:把Skill代码放入skills/目录,注册到Agent的技能仓库,配置Workflow节点指向它

5. 避坑指南:那些没人明说但会让你加班到凌晨的细节

5.1 Skill的“隐形依赖”比代码还致命

一个叫“codex用的检索文献Skill”的项目,本地测试完美,上线后总返回空结果。排查三天才发现:Skill代码里有一行import nltk,而nltk的停用词库默认从nltk_data目录加载。开发机上手动下载过,但Docker镜像里没挂载这个目录,导致所有文本处理都失效。

Skill依赖检查清单

  • ✅ 所有import的包必须在requirements.txt明确定义版本(nltk==3.8.1,非nltk>=3.0
  • ✅ 外部数据文件(词典、模型权重)必须打包进镜像或通过环境变量指定路径
  • ✅ 系统级依赖(如libpoppler用于PDF解析)需在Dockerfile中apt-get install
  • ✅ 时间/时区设置:Skill里用datetime.now()必须指定timezone.utc,避免Agent所在服务器时区混乱

5.2 Workflow的“变量污染”是静默杀手

某政务系统Workflow设计:节点A(用户登录)→B(获取权限)→C(展示数据)。测试时一切正常,上线后发现A节点的用户token被B节点意外修改,导致C节点鉴权失败。

根源是Workflow引擎的变量作用域设计缺陷:所有节点共享同一个context对象,B节点执行context["token"] = new_token时,覆盖了A节点的原始值。

安全写法

# 错误:直接修改context - id: get_permission script: | context["token"] = generate_new_token() # 污染全局 # 正确:只输出新字段,不修改原context - id: get_permission skill: auth_skill output: {new_token: "{{output.token}}"} # 新字段独立存储 - id: show_data input: {user_token: "{{get_permission.output.new_token}}" } # 显式传参

5.3 Agent的“记忆幻觉”比模型幻觉更难 debug

Agent说“您昨天提到喜欢科幻小说”,但用户根本没说过。查记忆库发现:某次用户问“推荐几本科幻小说”,Agent把问题本身当成了用户偏好存进了长期记忆。

记忆写入守则

  • ✅ 只存用户明确声明的偏好(如:“我喜欢东野圭吾”、“我不吃香菜”)
  • ✅ 对提问类语句,存为“意图”而非“事实”({"intent": "book_recommendation", "genre": "scifi"}
  • ✅ 每次写入记忆前,用小模型做意图分类(question/statement/command),statement才考虑存入
  • ❌ 禁止将Skill返回结果直接存入记忆(如:PDF Skill返回的摘要,不能自动当用户知识)

我在opencode skill项目里加了一层记忆过滤器:

def filter_for_memory(text: str) -> Optional[dict]: # 用轻量模型判断是否为偏好声明 if llm_classify(text) == "preference": return {"raw_text": text, "timestamp": time.time()} return None # 不存入记忆

5.4 “ai agent”和“agent智能体”——中文术语混乱的代价

搜索热词里同时存在“ai agent”和“agent智能体”,这不仅是翻译问题,更是架构认知偏差。

  • AI Agent:特指基于LLM的智能体,核心能力是语言理解与生成,决策依赖prompt engineering和few-shot learning。
  • Agent智能体:中文语境下常指传统软件Agent(如JADE、JADE框架),基于BDI(Belief-Desire-Intention)模型,用Java/Scala编写,强调形式化逻辑推理。

混用后果:团队用LangChain搭AI Agent,却按BDI模型设计意图识别模块,结果发现LLM根本不吃那一套规则。正确做法是:

  • AI Agent:用Chain-of-Thought提示词引导推理,用ReAct框架做工具调用
  • 传统Agent:用Prolog写规则引擎,用FIPA ACL协议通信

最后分享一个小技巧:在项目文档里,统一用英文术语(Agent/Skill/Workflow),中文括号标注(Agent(智能体)/Skill(能力单元)/Workflow(工作流))。这样既避免歧义,又方便工程师搜索源码——毕竟grep -r "agent"grep -r "智能体"靠谱得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询