1. 三个词被混着用,是时候把它们掰扯清楚了
如果你最近半年在技术社区里泡着,一定被三个词反复轰炸:Agent、Skill、Workflow。招聘JD上写着“熟悉Agent开发”,技术群里有人问“Skill和Agent到底啥区别”,老板开会说“咱们得搞个Workflow编排”。听起来都跟智能自动化沾边,但真要让你说清楚它们各自是什么、什么时候用哪个,很多人脑子里其实是一团浆糊。
我自己从去年开始陆续做了几个Agent相关的项目,踩过不少坑,也走过“把Workflow硬说成Agent”的弯路。这篇文章就是把我对这三个概念的理解彻底摊开,从底层逻辑到实际选型,从代码结构到常见误区,尽量讲透。不管你是刚接触这个概念的新手,还是已经在做Agent开发但总觉得边界模糊的老手,应该都能从里面找到一些有用的东西。
先说结论性的判断:Agent是决策主体,Skill是能力单元,Workflow是执行路径。这三者不是互斥关系,而是不同层次的抽象。一个完整的智能系统里,它们往往同时存在,只是各司其职。但如果你在架构设计的时候把它们搞混了,后果就是系统要么过度工程化,要么根本跑不起来。
下面我会从概念拆解开始,逐步深入到架构设计、实操落地、常见坑和排查技巧。每一部分都会给出具体的判断标准和操作建议,不是泛泛而谈。
2. 概念拆解:三者到底在解决什么问题
2.1 Agent的本质是一个循环决策器
Agent这个词被用得太泛了。有人说Agent就是能调用工具的LLM,有人说Agent得有记忆、有规划能力,还有人说Agent必须能自主分解任务。这些说法都对,但都不够本质。
我理解的Agent,核心就一件事:它是一个“感知-决策-执行-观察”的循环。你给它一个目标,它自己决定下一步做什么,执行完之后看结果,再决定下一步。这个循环可以跑一轮就结束,也可以跑几十轮直到任务完成。
用伪代码表示大概长这样:
while not task_completed: observation = get_current_state() thought = llm_reason(goal, observation, memory) action = select_action(thought, available_tools) result = execute(action) memory.update(observation, action, result)关键点在于“自己决定下一步”。如果你把每一步都写死了,那它就不是Agent,而是一个Workflow。这个区别在后面会反复提到,因为它直接决定了你的系统架构。
Agent的典型特征包括:有自主决策能力、能根据环境反馈调整策略、通常需要记忆机制、可能涉及多轮推理。常见的Agent框架有LangChain的Agent模块、AutoGPT类的自主循环、还有各种基于ReAct模式的实现。
2.2 Skill是可复用的能力封装
Skill这个概念其实比Agent更早存在,只是在Agent热潮里被重新包装了。你可以把它理解为一个原子化的能力单元,输入明确、输出明确、职责单一。
比如“查询天气”是一个Skill,“发送邮件”是一个Skill,“从PDF里提取表格”也是一个Skill。它不关心什么时候被调用、被谁调用,只关心你给我正确的输入,我还你正确的输出。
Skill的设计原则跟微服务很像:高内聚、低耦合、接口清晰。一个好的Skill应该具备这些特征:
- 输入输出schema明确:用JSON Schema或者Pydantic模型定义清楚,调用方不需要读源码就知道怎么用
- 无状态或状态可控:尽量不依赖全局状态,如果必须有状态,要显式管理
- 错误处理完善:超时、重试、降级策略要内置,不能把异常直接抛给上层
- 可独立测试:不依赖Agent或Workflow就能单独跑测试用例
在实际项目里,Skill通常以函数、API、插件的形式存在。比如你给一个Agent配置了十个工具,每个工具本质上就是一个Skill。Agent负责决定“什么时候用哪个Skill”,Skill负责“怎么把这个事干好”。
2.3 Workflow是确定性的执行编排
Workflow的核心是预定义的执行路径。你事先把步骤和条件分支都定义好,系统按照这个路径一步步执行。它不关心“下一步该做什么”这种问题,因为答案已经写在配置里了。
Workflow的典型形态包括:DAG(有向无环图)、状态机、管道链。常见的工具有Airflow、Prefect、LangChain的Chain、Dify的Workflow编排等。
Workflow的优势在于可控性和可观测性。因为路径是确定的,所以你可以精确知道每一步的输入输出,方便调试、监控、重试。但它的劣势也很明显:无法处理未预见的场景。如果用户输入超出了你预设的分支条件,Workflow就卡住了。
一个典型的Workflow长这样:
steps: - name: extract_text skill: pdf_extractor input: "{{user_uploaded_file}}" - name: classify skill: text_classifier input: "{{extract_text.output}}" - name: route condition: "{{classify.label}} == 'invoice'" then: process_invoice else: process_general这里每一步都是确定的,没有“LLM自己决定下一步”的空间。这就是Workflow和Agent最本质的区别。
2.4 一张表看清三者的边界
| 维度 | Agent | Skill | Workflow |
|---|---|---|---|
| 核心职责 | 决策与规划 | 执行单一能力 | 编排执行路径 |
| 控制方式 | LLM自主决策 | 被调用时执行 | 预定义规则驱动 |
| 确定性 | 低,每次可能不同 | 高,输入决定输出 | 高,路径固定 |
| 适用场景 | 开放式任务 | 原子能力复用 | 标准化流程 |
| 典型实现 | ReAct、Plan-Execute | 函数、API、插件 | DAG、状态机 |
| 调试难度 | 高 | 低 | 中 |
| 成本 | 高(多次LLM调用) | 低 | 低到中 |
这张表建议存下来,每次架构评审的时候拿出来对一遍,能避免很多无谓的争论。
3. 架构选型:什么时候用哪个,什么时候组合用
3.1 先判断任务是否“开放式”
选型的第一个判断点是:这个任务的完成路径是否事先可知。
如果答案是“完全可知”,比如“用户上传发票,提取金额,填入表格,发送确认邮件”,那这就是一个Workflow,不需要Agent。用Agent反而会增加不确定性和成本。
如果答案是“部分可知但会有分支”,比如“用户上传文档,根据文档类型走不同的处理流程”,那可以用Workflow做主干,在分支判断的地方引入LLM做分类,但整体还是Workflow架构。
如果答案是“完全不可知”,比如“帮我调研一下这个行业的最新趋势并写一份报告”,那必须用Agent。因为你不确定需要搜哪些关键词、看哪些来源、怎么组织内容,这些决策只能交给Agent在运行时做。
我自己的经验法则是:能用Workflow解决的,绝不上Agent。Agent的灵活性和不确定性是双刃剑,在需要稳定交付的场景里,Workflow的确定性比Agent的灵活性值钱得多。
3.2 Skill的复用策略决定了系统上限
不管你最终选Agent还是Workflow,Skill层的设计都是重中之重。因为Skill是真正干活的部分,它的质量直接决定了系统能做什么。
我在项目里总结了一个Skill分层的做法:
- 基础Skill层:跟具体业务无关的通用能力,比如HTTP请求、文件读写、文本切分、向量检索。这层尽量用现成的库或API,不要自己造轮子。
- 领域Skill层:跟业务相关的原子能力,比如“查询订单状态”、“计算保费”、“解析合同条款”。这层需要自己开发,但要做到跟具体Agent/Workflow解耦。
- 组合Skill层:把多个基础Skill组合成一个稍大的能力单元,比如“从邮件里提取附件并解析”。这层是可选的,主要是为了减少Agent的决策负担。
这样分层的好处是,当你从Workflow迁移到Agent,或者从Agent降级到Workflow时,Skill层不需要大改,只需要调整上层的编排逻辑。
3.3 混合架构才是常态
实际项目里,纯粹的Agent或纯粹的Workflow都很少见。更常见的是混合架构:Workflow做主干,Agent做分支,Skill做执行。
举个例子,一个客服系统可能是这样的:
- 用户发来消息(Workflow入口)
- 意图分类(Skill,用LLM做分类)
- 如果是“查订单”,走固定的查询Workflow
- 如果是“投诉建议”,交给Agent处理,因为投诉内容千变万化,需要Agent自己决定怎么安抚、怎么记录、怎么升级
- 如果是“产品咨询”,走RAG Workflow,检索知识库后生成回答
这个架构里,Workflow保证了80%的常见场景稳定高效,Agent兜底了20%的复杂场景。Skill层被两者共用,避免了重复开发。
4. 实操落地:从零搭一个Agent+Skill+Workflow的混合系统
4.1 环境准备与依赖选型
假设我们要做一个“智能文档处理助手”,能接收用户上传的文档,自动分类、提取信息、生成摘要,遇到复杂文档时能自主决定处理策略。
技术栈选型如下:
- Agent框架:LangChain的AgentExecutor或者自己写ReAct循环。如果团队对LangChain比较熟,直接用它的Agent模块;如果想要更轻量的控制,自己写也不复杂。
- Skill层:用Python函数+ Pydantic模型定义输入输出。每个Skill独立成一个模块,方便测试和复用。
- Workflow引擎:简单的场景用LangChain的SequentialChain就够了;复杂的用Prefect或Airflow。如果不想引入太重的外部依赖,自己写一个基于状态机的轻量编排器也行。
- LLM接入:用OpenAI API或者国内的通义千问、智谱等。注意做好超时和重试。
- 向量库:如果涉及RAG,用Chroma或Milvus。小规模场景Chroma足够。
安装依赖:
pip install langchain langchain-openai pydantic chromadb prefect4.2 Skill层的具体实现
先定义几个核心Skill。每个Skill都是一个独立的Python函数,用Pydantic定义输入输出。
from pydantic import BaseModel, Field from typing import List class DocumentInput(BaseModel): file_path: str = Field(description="文档的本地路径") file_type: str = Field(description="文档类型,如pdf、docx、txt") class DocumentOutput(BaseModel): text: str = Field(description="提取出的纯文本") page_count: int = Field(description="页数") metadata: dict = Field(description="文档元信息") def extract_document_text(input: DocumentInput) -> DocumentOutput: """从文档中提取纯文本,支持pdf、docx、txt""" if input.file_type == "pdf": import fitz doc = fitz.open(input.file_path) text = "\n".join(page.get_text() for page in doc) return DocumentOutput(text=text, page_count=len(doc), metadata={}) elif input.file_type == "docx": from docx import Document doc = Document(input.file_path) text = "\n".join(p.text for p in doc.paragraphs) return DocumentOutput(text=text, page_count=1, metadata={}) else: with open(input.file_path, "r", encoding="utf-8") as f: text = f.read() return DocumentOutput(text=text, page_count=1, metadata={})这个Skill的输入输出非常明确,不依赖任何全局状态,可以独立测试。这就是好的Skill设计。
再定义一个分类Skill:
class ClassifyInput(BaseModel): text: str = Field(description="待分类的文本") categories: List[str] = Field(description="候选类别列表") class ClassifyOutput(BaseModel): label: str = Field(description="分类结果") confidence: float = Field(description="置信度") def classify_text(input: ClassifyInput) -> ClassifyOutput: """用LLM对文本进行分类""" from langchain_openai import ChatOpenAI llm = ChatOpenAI(model="gpt-4o-mini", temperature=0) prompt = f"将以下文本分类到这些类别之一:{input.categories}\n\n文本:{input.text[:2000]}\n\n只输出类别名称。" result = llm.invoke(prompt) return ClassifyOutput(label=result.content.strip(), confidence=0.9)注意这里我把LLM调用封装在Skill内部,而不是让Agent直接调LLM。这样做的好处是分类逻辑可以独立优化和测试,Agent只需要知道“有个分类Skill可用”就行。
4.3 Workflow编排的实现
对于标准流程,我们用Workflow串起来:
from prefect import flow, task @task def step_extract(file_path: str, file_type: str): return extract_document_text(DocumentInput(file_path=file_path, file_type=file_type)) @task def step_classify(text: str): return classify_text(ClassifyInput(text=text, categories=["合同", "发票", "报告", "其他"])) @task def step_summarize(text: str): from langchain_openai import ChatOpenAI llm = ChatOpenAI(model="gpt-4o-mini") return llm.invoke(f"用三句话总结以下文档:\n\n{text[:3000]}").content @flow def standard_document_flow(file_path: str, file_type: str): doc = step_extract(file_path, file_type) category = step_classify(doc.text) summary = step_summarize(doc.text) return {"category": category.label, "summary": summary, "page_count": doc.page_count}这个Workflow的路径是确定的:提取→分类→摘要。每一步的输入输出都清晰可控,调试起来非常方便。
4.4 Agent的接入与决策逻辑
当Workflow遇到无法处理的文档时(比如分类置信度低于阈值,或者文档类型不在预设类别里),就交给Agent。
from langchain.agents import Tool, AgentExecutor, create_react_agent from langchain_openai import ChatOpenAI tools = [ Tool(name="extract_text", func=lambda x: extract_document_text(DocumentInput(file_path=x, file_type="pdf")).text, description="从PDF提取文本"), Tool(name="classify", func=lambda x: classify_text(ClassifyInput(text=x, categories=["合同", "发票", "报告", "其他"])).label, description="对文本分类"), Tool(name="summarize", func=lambda x: step_summarize(x), description="总结文本"), Tool(name="search_knowledge", func=lambda x: "知识库检索结果...", description="检索内部知识库"), ] llm = ChatOpenAI(model="gpt-4o", temperature=0) agent = create_react_agent(llm, tools, prompt) executor = AgentExecutor(agent=agent, tools=tools, max_iterations=10, verbose=True) def handle_complex_document(file_path: str): return executor.invoke({"input": f"处理这个文档:{file_path}。先提取文本,判断类型,如果是不常见的类型,尝试检索知识库看有没有相关处理指南,最后给出摘要和处理建议。"})Agent在这里的作用是:当标准Workflow覆盖不了时,它能自己决定调用哪些Skill、按什么顺序调用、是否需要多轮尝试。这就是Agent的价值所在。
4.5 完整调用链路与参数计算
整个系统的入口逻辑:
def process_document(file_path: str, file_type: str): # 先走标准Workflow result = standard_document_flow(file_path, file_type) # 判断是否需要Agent介入 if result["category"] == "其他" or result.get("confidence", 1.0) < 0.7: agent_result = handle_complex_document(file_path) return {"mode": "agent", "result": agent_result} return {"mode": "workflow", "result": result}关于成本,我实测下来的数据是:标准Workflow处理一份文档大约消耗2000-3000 token,成本在0.01-0.02元;Agent处理同样文档因为多轮推理,token消耗在8000-15000,成本在0.05-0.1元。所以能用Workflow就不用Agent,这个原则在成本上体现得非常明显。
5. 常见误区与排查技巧实录
5.1 把Workflow包装成Agent的后果
我见过不少项目,明明是一个固定的三步流程,非要套一个Agent壳子,让LLM决定每一步做什么。结果就是:本来100%成功的流程,变成了80%成功,因为LLM偶尔会“发挥创意”跳过某一步或者改变顺序。
判断标准很简单:如果你能把执行路径画成一张没有环的图,那它就是Workflow,不要用Agent。Agent只适用于那些你画不出完整路径的场景。
5.2 Skill粒度太粗或太细都会出问题
Skill太粗,比如一个Skill干十件事,那Agent调用的时候很容易出错,因为输入参数太多、职责不清晰。Skill太细,比如每个HTTP请求都封装成一个Skill,那Agent的决策空间会爆炸,它会在几十个Skill里挑花眼。
我的经验是:一个Skill对应一个“人类能一句话说清楚的操作”。比如“查询订单”是一句话,“发送邮件”是一句话,“提取PDF文本”是一句话。如果一个Skill需要三句话才能描述清楚,那它可能该拆了。
5.3 Agent循环不终止的排查思路
Agent最常见的故障就是“执行终止错误”或者无限循环。排查步骤:
- 检查max_iterations设置:默认值可能太小或太大。太小会导致任务没完成就退出,太大会导致无限循环消耗token。建议设置在5-15之间。
- 检查工具返回结果:如果工具总是返回空或者报错,Agent会反复重试同一个工具。确保每个Skill都有明确的错误返回格式。
- 检查prompt里的停止条件:ReAct prompt里要明确告诉Agent“当你认为任务完成时,输出Final Answer”。如果prompt写得模糊,Agent不知道什么时候该停。
- 加超时和熔断:在AgentExecutor外面包一层超时控制,超过30秒强制终止,避免资源浪费。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| Agent反复调用同一工具 | 工具返回结果不明确 | 查看verbose日志 | 优化Skill返回格式,增加状态标识 |
| Workflow卡在某一步 | 输入不符合预期 | 检查上一步输出 | 增加输入校验和默认值 |
| Skill调用超时 | 外部API不稳定 | 查看超时日志 | 增加重试和降级策略 |
| Agent决策质量差 | prompt不够清晰 | 检查prompt模板 | 增加few-shot示例和约束条件 |
| 成本超预期 | Agent调用过于频繁 | 统计token消耗 | 能用Workflow的不用Agent |
| 分类准确率低 | 类别定义模糊 | 人工检查分类结果 | 细化类别定义,增加训练样本 |
5.5 几个我踩过的坑
第一个坑是在Workflow里嵌了Agent,但没做超时控制。结果有一次Agent卡住了,整个Workflow等了五分钟才超时,用户体验极差。后来我在所有Agent调用外面都加了timeout=30的参数。
第二个坑是Skill的输入输出没有用schema约束。早期我直接用dict传参,结果Agent有时候传错字段名,Skill直接报KeyError。后来全部改成Pydantic模型,Agent传错参数会得到明确的校验错误,它自己就能纠正。
第三个坑是把LLM调用散落在各处。一开始我在Agent、Workflow、Skill里都直接调LLM,后来发现成本无法统计、prompt无法统一管理。后来我封装了一个统一的LLM调用层,所有LLM请求都走这个层,方便加缓存、限流、监控。
6. 从选型到上线的完整决策清单
6.1 架构决策的五个关键问题
在动手写代码之前,先回答这五个问题:
- 任务的执行路径是否完全可知?是→Workflow,否→继续
- 不可知的部分是否可以用LLM分类后走固定分支?是→Workflow+LLM分类,否→Agent
- Agent的决策空间是否可控?如果可用Skill超过20个,考虑先做Skill分组或增加路由层
- 失败后的兜底策略是什么?Agent失败后是转人工还是走默认Workflow?
- 成本预算是多少?Agent的单次成本通常是Workflow的3-5倍,提前算好账
6.2 上线前的检查项
- 所有Skill都有独立的单元测试,覆盖率>80%
- Workflow的每个分支都有对应的测试用例
- Agent的max_iterations和timeout都已设置
- 有完整的日志记录,能追溯每一次决策
- 有成本监控面板,能实时看到token消耗
- 有降级方案,Agent不可用时能自动切到Workflow
6.3 后续扩展的方向
这套架构搭好之后,扩展起来其实很灵活。想增加新能力,就加Skill;想优化流程,就调Workflow;想让系统更智能,就优化Agent的prompt和工具描述。三者之间的边界清晰,改一处不会影响其他部分。
我目前正在尝试的一个方向是让Agent自己生成Workflow。就是对于一些重复出现的复杂任务,Agent第一次用自主决策完成之后,把执行路径记录下来,下次遇到类似任务直接走生成的Workflow。这样系统会越用越快、越用越便宜。这个思路还在验证阶段,等跑通了再单独写一篇。
另外一个方向是Skill的自动发现和注册。现在每加一个Skill都要手动注册到Agent的工具列表里,未来可以做成扫描目录自动注册,减少人工操作。
这两个方向的核心逻辑都是一样的:让确定性的部分越来越确定,让Agent只处理真正需要智能的部分。这也是我从头到尾一直在强调的原则。