面试官问:做一个工具,到底选 Workflow 还是 Agent?
本文基于“高德一面:Workflow 和 Agent 的差异”与《AI Handbook》中的 Harness 文章整理。核心问题是:任务应该由代码编排,还是交给模型动态决策?下文进一步把 Workflow、Agent、Harness、LangGraph 和 ReAct Agent 放到同一张架构图里说明。
先给面试答案
如果需求的步骤、输入输出和异常分支可以提前描述,我会优先使用 Workflow;只有当任务需要根据中间结果动态拆解、选择工具、重试或改变计划时,才引入 Agent。生产系统通常不是二选一,而是“确定性 Workflow 包住受控的 Agent 节点”:代码负责边界、状态、权限、预算和副作用,模型负责不确定的判断。
判断标准不是“Agent 更先进”,而是下面这个问题:
下一步行动能不能在系统设计时写成稳定的规则?
- 能写成规则:Workflow。
- 规则只能覆盖大多数情况,剩下的情况需要理解语义并临场决定:在局部节点使用 Agent。
- 连工具集合、步骤数量和完成条件都高度不确定:使用 Agent,但必须放进可靠的 Harness 中。
这句话既回答了“选哪个”,也说明了为什么不能把所有功能都做成一个自由循环的 Agent。
一、先把五个词分开
很多面试争论来自概念混用。它们处在不同抽象层,不是五种互斥产品。
| 概念 | 解决的问题 | 谁决定下一步 | 典型形态 |
|---|---|---|---|
| Tool | 完成一个可调用动作 | 调用方 | search()、create_ticket() |
| Workflow | 把已知步骤可靠地编排起来 | 代码、规则、状态机 | DAG、状态机、流水线 |
| Agent | 在不确定环境中决定做什么 | 模型策略(受约束) | 规划 → 调工具 → 观察 → 再规划 |
| Harness | 让 Agent 能安全、可观测、可恢复地运行 | 运行时和平台 | 上下文、循环、权限、预算、审批、日志 |
| LangGraph | 用图来实现状态、节点和转移 | 图定义 + 节点逻辑 | Workflow 图或 Agent 循环图 |
| ReAct Agent | 一种让模型交替推理和行动的策略 | 模型输出 action | Thought/Action/Observation 循环 |
可以用下面的分层来记忆:
业务目标
│
├── Workflow:固定的流程和分支
│ └── Agent 节点:在局部做动态决策
│
└── Agent:模型驱动的循环策略
└── Harness:把循环变成可运行的生产系统
LangGraph 是承载上面两类编排的图运行时;ReAct 是 Agent 节点常用的决策模式。因此,“LangGraph 还是 ReAct Agent”不是同一层的选择:前者偏运行时和编排,后者偏行为模式。可以用 LangGraph 实现一个 ReAct Agent,也可以在 LangGraph 中只写确定性的 Workflow。
二、Workflow 和 Agent 的共同基础
Workflow 和 Agent 不是两个完全无关的世界。参考文章把两者的共同点归纳为五类,这些共同点也解释了为什么它们经常被组合在同一个系统中:
- 都是目标导向的系统。 都需要明确输入、可衡量的输出,以及围绕目标组织起来的一组动作。
- 都要和外部工具或系统交互。 Workflow 节点可能调用邮件、数据库和微服务;Agent 可能调用搜索、代码执行和 API。
- 都可以把 LLM 作为执行单元。 Workflow 可以让 LLM 做分类、抽取或摘要;Agent 则通常让 LLM 参与下一步决策。
- 都需要状态管理和错误处理。 运行到哪一步、拿到了什么中间结果、失败后怎么重试和超时,不能因为用了模型就消失。
- 都应该模块化和可复用。 Workflow 把步骤封装成节点,Agent 把能力封装成有契约的工具。
共同点意味着:选择 Workflow 并不等于拒绝 LLM,选择 Agent 也不等于放弃代码编排。真正的分野在于谁拥有控制流的决策权。
三、五个核心差异:谁决定下一步
| 差异维度 | Workflow | Agent |
|---|---|---|
| 决策权 | 开发者在设计阶段预先定义路径和分支 | 模型在运行时根据目标、上下文和工具结果决定下一步 |
| 灵活性与可预测性 | 行为透明、稳定、易测试、易审计 | 适应开放任务,但同一问题可能走不同轨迹 |
| 面对不确定性的能力 | 适合已知问题,重点是自动执行 | 适合未知问题,需要探索、试错和根据反馈调整 |
| 成本结构 | 前期设计成本高,运行成本通常较低 | 工具和提示设计成本高,运行时 token、延迟和评测成本更高 |
| 失败模式 | 节点报错、超时、格式错误,通常容易定位 | 选错工具、参数错误、误判完成、重复循环,必须分析轨迹 |
可以把它概括成一句话:Workflow 是在设计时穷举路径,Agent 是在运行时生成路径。
四、Workflow:把确定性放在代码里
Workflow 的特点是:节点、数据结构、转移条件和失败策略大体已知。模型可以参与某个节点(例如抽取字段或生成文案),但不能随意改变整个系统的控制流。
以“报销单审核”为例,流程可以写成:
上传发票
→ OCR/字段抽取
→ 金额与发票真伪校验
→ 查询员工额度
→ 规则判定
├─ 通过 → 写入财务系统
├─ 超额 → 转人工审批
└─ 材料缺失 → 返回补充清单伪代码如下,重点是状态和转移由程序掌握:
def reimbursement_workflow(request):
state = extract_fields(request)
state = validate_invoice(state)
if state.errors:
return ask_for_supplement(state.errors)
state.limit = query_employee_limit(state.employee_id)
decision = apply_policy(state) # 可审计、可复现的规则
if decision == "approve":
return write_finance_system(state)
if decision == "manual_review":
return create_approval_task(state)
return reject(state)Workflow 的优势
- 可预测:同样的输入经过同样的分支,方便回归测试。
- 可审计:每个节点、条件和副作用都有明确责任人。
- 低延迟、低成本:不必让模型为已知步骤反复思考。
- 易于控制风险:权限、事务、重试和幂等可以写在代码里。
Workflow 也不是“完全不用模型”
常见的 Workflow 节点仍然会调用模型,例如把邮件分类成 退款/投诉/咨询,或从合同中抽取结构化字段。关键区别在于:模型只负责一个有边界的函数,输出经过 schema 校验后才会进入下一步;它不拥有任意调用工具和修改流程的权力。
五、Agent:把不确定性放进闭环
Agent 适合目标明确、路径未知的任务。例如“调查这次线上故障并给出修复建议”:需要先查监控,再看发布记录,可能还要查询日志、对比配置;不同证据会改变下一步。
最小 Agent 循环可以表示为:
state = init_state(user_goal)
for turn in range(MAX_TURNS):
decision = model.decide(
goal=state.goal,
context=state.context,
tools=allowed_tools,
)
if decision.type == "final":
return decision.answer
tool_result = run_tool_safely(decision.tool, decision.arguments)
state = state.observe(tool_result)
return stop_with_budget_exceeded(state)模型在每一轮回答的不是最终答案,而是“现在要不要调用哪个工具、参数是什么、拿到结果后是否继续”。这带来灵活性,也带来新的失败模式:工具选错、参数错误、循环不止、证据不足仍然下结论、成本失控。
什么时候值得引入 Agent
下面的信号越多,越适合使用 Agent:
- 用户目标是自然语言,无法枚举成少量意图。
- 需要在多个工具或数据源之间路由,且路由取决于中间结果。
- 任务需要多跳检索、比较、反思或动态重试。
- 工具的调用顺序不是固定的,失败后可能换策略。
- 能接受一定的延迟和非确定性,并且有评测和人工兜底。
如果只是“接收表单 → 调三个 API → 返回结果”,Agent 往往是过度设计。
六、Harness:Agent 外面那层真正的工程
运行一个模型循环并不等于交付一个 Agent。Harness 可以理解为 Agent 的运行时外壳,负责把模型的“想法”放在一个可控的执行环境中。它至少应包含以下能力:
《AI Handbook》给出的一个很有用的心智模型是:
Model → Harness → Environment模型提供推理和工具调用意图,环境提供文件、进程、网络、数据库等真实资源,Harness 则负责把观察、行动、反馈和完成验证组织成一个闭环。它更像“底盘”,而不是模型本身:模型是引擎,Harness 负责转向、制动、权限和恢复。
Harness 的三层定位
- 模型层(Model):提供语言理解、推理和工具调用能力。
- Harness 层:服务于单个 Agent,负责观察、行动、反馈、验证、权限和记忆。
- Agent Runtime 层:服务于多个会话或多个 Agent,负责调度、租户隔离、持久化、治理和控制面。
Harness 和 Agent Runtime 是互补关系:前者关注“一个 Agent 这一次任务执行得是否可靠”,后者关注“很多 Agent 如何被系统化地运营”。
Harness 的六项核心职责
| 职责 | 具体问题 | 典型实现 |
|---|---|---|
| 观察 Observation | 模型应该看到哪些环境事实? | 文件状态、进程输出、用户输入、API 响应结构化 |
| 行动 Action | 模型提出的动作如何安全执行? | 工具审批、沙箱隔离、并发调度、副作用记录 |
| 反馈 Feedback | 执行结果如何回到下一轮上下文? | 结果回写、错误重试、摘要压缩、中间状态 |
| 完成验证 Completion | 如何确认任务真的完成,而不是模型说完成? | Schema 校验、测试、证据包、审计结果 |
| 权限 Permission | Agent 能操作到什么边界? | Allowlist/Blocklist、文件/网络/进程控制、人工审批 |
| 记忆 Memory | 中断后能否继续,跨会话能否保持状态? | Session 持久化、长期记忆、Checkpoint、Lineage |
这六项职责是区分“只提供调用封装的框架”和“真正的 Harness”的关键。后者还要把状态、环境、安全和恢复纳入同一个执行闭环。
把这些职责落到工程实现,至少要有下面这份清单:
| Harness 能力 | 要回答的工程问题 | 最小实现 |
|---|---|---|
| 上下文管理 | 本轮应该看到哪些信息? | 历史裁剪、摘要、检索结果、状态 schema |
| 工具边界 | 模型能调用什么? | 白名单、JSON Schema、超时、权限注入 |
| 执行循环 | 何时继续、何时停止? | 最大轮数、最大 token/费用、终止条件 |
| 状态与记忆 | 中断后能否恢复? | checkpoint、任务状态、短期/长期记忆分层 |
| 安全与审批 | 哪些动作必须由人确认? | 读写权限、敏感操作审批、租户隔离 |
| 可靠性 | 工具失败怎么办? | 重试、退避、幂等键、补偿动作 |
| 可观测性 | 出错时能否解释? | trace、模型输入输出、工具参数和结果、成本 |
| 评测 | 升级模型后是否变好? | 轨迹评测、任务成功率、工具准确率、回放 |
从框架到 Harness:状态和环境都要持久化
普通框架往往围绕一次请求、一次调用或一张图组织;Harness 面向的是可能运行数小时、数天甚至跨会话的任务。因此抽象会发生变化:
| 维度 | 传统框架 | Harness |
|---|---|---|
| 核心抽象 | Chain、Graph、Pipeline | Episode、Session、Task |
| 状态模型 | 单次请求或进程内状态 | 持久化会话、Checkpoint、Session Lineage |
| 执行环境 | 调用方进程内 | Sandbox、Gateway、定时调度和隔离工作区 |
| 失败处理 | 抛异常、简单 Retry | Resume、Rollback、补偿和人工接管 |
| 安全模型 | Prompt Guardrail | 权限审批、能力隔离、文件/网络/进程审计 |
| 可观测性 | Print 或普通日志 | 结构化 Trace、证据包和审计追踪 |
这也是 Harness 和普通 Agent 框架的边界:框架让 Agent“能跑起来”,Harness 让 Agent“在真实环境里长期、可恢复、可追责地运行”。
Always-on Agent 的安全边界
当 Agent 从一次性问答变成长时间运行的任务,消息、记忆、技能、调度器和 Shell 可能被折叠到同一个权限边界里,安全风险会明显扩大:
- Prompt Injection:外部消息、文档或工具结果可能诱导 Agent 改变原始目标。
- Confused Deputy:Agent 持有合法权限,却被欺骗去执行用户本不想做的操作。
- 权限逃逸:只在 Shell 层做拦截不够,还要覆盖文件、网络和进程级能力。
- Checkpoint 泄露:快照和会话记忆可能包含密钥、个人信息或业务数据。
因此高风险工具应采用最小权限、显式审批、作用域隔离和完整审计;“模型说要执行”不能直接等同于“系统允许执行”。
这也是“先做 Harness,再谈 Agent 能力”的原因:没有预算、权限、停止条件和日志的 Agent,演示时很聪明,生产中却难以负责。
七、LangGraph 和 ReAct Agent 到底是什么关系
1. ReAct 是一种 Agent 行为模式
ReAct(Reasoning + Acting)的核心是交替进行推理和行动:
用户目标
→ 推理:当前缺什么信息?
→ 行动:调用 search_logs(query=...)
→ 观察:得到日志片段
→ 推理:是否足够?下一步查发布记录还是结束?
→ 行动/最终回答早期实现常把 Thought / Action / Observation 写在提示词文本中;现在的模型通常通过 tool calling 返回结构化 action,但控制思想没有改变。ReAct 规定的是“模型如何在循环中做决定”,并没有规定 checkpoint、权限、部署或业务状态如何实现。
2. LangGraph 是状态图运行时
LangGraph 把应用表示成:
- State:任务目标、消息、工具结果、错误、预算等共享状态。
- Node:一个可执行步骤,可以是普通函数、模型调用或工具调用。
- Edge:节点之间的固定或条件转移。
- Checkpointer:保存状态,使任务可以暂停、恢复、重放。
- Interrupt/Human-in-the-loop:在敏感节点前暂停等待人工决定。
固定流程可以写成 A → B → C,动态 Agent 则写成 model → tool → model 的条件循环:模型返回工具调用就走工具节点,返回最终答案就结束,超过预算则转失败节点。同一个图运行时同时覆盖 Workflow 和 Agent。
3. 两者的对应关系
| 维度 | LangGraph | ReAct Agent |
|---|---|---|
| 抽象层 | 编排/运行时 | 决策/提示与循环策略 |
| 关注点 | 状态、节点、边、恢复、并发、人工介入 | 推理、行动、观察、再推理 |
| 是否必须用模型 | 不必须,纯代码图也可以 | 必须依赖模型做下一步决策 |
| 是否能表达固定流程 | 能 | 不擅长,容易引入不必要的非确定性 |
| 是否能表达动态工具调用 | 能,通过条件边和循环 | 是,正是它的典型用途 |
| 生产能力 | 可接入 Harness 能力 | 需要外部 Harness 才可靠 |
因此可以有三种常见组合:
- LangGraph + 普通节点:规则审批、数据同步等纯 Workflow。
- LangGraph + ReAct 节点:图负责状态和安全边界,ReAct 负责局部动态决策。
- 其他运行时 + ReAct:只要能实现工具循环,也可以不用 LangGraph;但要自己补齐持久化、观测和人工介入。
面试中不要说“LangGraph 就是 ReAct”,更准确的说法是:LangGraph 可以承载 ReAct,ReAct 是 LangGraph 中一种常见的 Agent 节点逻辑。
八、一个可落地的混合架构
继续以“故障排查工具”为例。第一版不需要让 Agent 控制全部系统:
接收问题
→ 鉴权与租户校验(代码)
→ 结构化故障对象(模型,schema 约束)
→ 选择排查策略(Workflow 条件分支)
├─ 已知错误码 → 查知识库 → 生成说明
├─ 线上异常 → 进入 ReAct 排查节点
└─ 高风险操作 → 人工审批
→ 输出带证据的报告(代码校验 + 模型生成)ReAct 节点只开放只读工具:query_metrics、search_logs、get_deployments。它不能直接执行回滚;如果模型提出回滚建议,图会转到 human_approval,审批通过后才调用独立的 rollback_service。
一个简化的 LangGraph 风格伪代码如下:
class State(TypedDict):
question: str
incident: dict
evidence: list[dict]
next_step: str
budget: int
def classify(state):
incident = extract_incident(state["question"])
return {"incident": incident}
def route(state):
if state["incident"].get("error_code"):
return "known_error"
if state["incident"].get("service"):
return "react_investigator"
return "ask_clarification"
def react_investigator(state):
# 在 Harness 的工具白名单、轮数和预算内运行 ReAct
return run_react_loop(state, tools=[query_metrics, search_logs, get_deployments])
graph.add_node("classify", classify)
graph.add_node("known_error", known_error_lookup)
graph.add_node("react_investigator", react_investigator)
graph.add_node("ask_clarification", ask_clarification)
graph.add_conditional_edges("classify", route)这里的关键不是某个 API 名称,而是职责分离:
- 外层图决定哪些路径存在、哪些工具可见、何时需要人。
- Agent 节点决定当前路径下的下一次查询。
- 工具服务负责真正的权限、参数校验和副作用控制。
九、选择方案的决策表
| 问题 | Workflow | Agent | 推荐判断 |
|---|---|---|---|
| 步骤是否固定? | 固定步骤 | 动态步骤 | 固定就选 Workflow |
| 工具数量是否有限且已知? | 是 | 可能动态扩展 | 工具越稳定越偏 Workflow |
| 是否允许重复尝试? | 代码定义重试 | 模型选择策略 | 失败策略已知就写代码 |
| 是否有资金、写库、发消息等副作用? | 容易加审批 | 需要额外约束 | 高风险动作放在 Workflow/Harness |
| 延迟和成本是否严格? | 更容易保证 | 可能多轮调用 | SLA 严格时优先 Workflow |
| 评测能否写成固定断言? | 容易 | 需要轨迹评测 | 可断言就优先 Workflow |
| 用户问题是否开放? | 需要大量规则 | 更适合 | 开放目标才引入 Agent |
实践中可以采用“确定性优先,动态性局部化”的渐进路线:
- 先用 Workflow 验证业务价值,建立输入、输出、错误和评测集。
- 找出最难用规则覆盖的一个节点,把它封装成 Agent,并限制工具集合。
- 为 Agent 加上 Harness:预算、停止条件、权限、checkpoint、trace 和人工审批。
- 用真实轨迹比较 Agent 与规则版本,再决定是否扩大自治范围。
十、常见误区
误区 1:模型参与了,就叫 Agent
抽取、分类、改写等单次 LLM 调用仍然可以是 Workflow 节点。是否是 Agent,关键看模型是否拥有“选择下一步行动”的控制权。
误区 2:用了 LangGraph,就自动获得 Agent 能力
LangGraph 只是编排和状态运行时。图里全是固定节点时,它就是 Workflow;只有模型根据状态选择工具或路径时,才体现 Agent 行为。
误区 3:ReAct 的 Thought 必须暴露给用户
ReAct 是内部决策轨迹。对外应返回可验证的结论、引用和执行摘要,不要把未经审查的内部推理当作产品输出。生产日志也应遵循隐私和最小记录原则。
误区 4:给 Agent 越多工具越强
工具越多,选择空间、错误参数和权限风险越大。应按任务动态暴露最小工具集,并为每个工具定义 schema、超时、幂等和错误契约。
误区 5:只评估最终答案
Agent 可能“碰巧答对”,但走了危险或昂贵的路径。除了答案正确率,还要评估工具选择、参数正确率、轨迹长度、成本、延迟、拒答和越权率。
十一、面试时可以这样完整回答
我会先把需求拆成确定性部分和不确定性部分。确定的步骤、校验、权限和有副作用的操作用 Workflow 或状态图实现;只有需要根据中间结果动态拆解、选择工具和恢复策略的部分才使用 Agent。Agent 本身采用受控的 ReAct 循环,外面用 Harness 管理上下文、工具白名单、预算、最大轮数、checkpoint、日志和人工审批。LangGraph 是承载这套状态图和循环的运行时,它既能实现普通 Workflow,也能承载 ReAct Agent;ReAct 是决策模式,不是 LangGraph 的同义词。上线前我会用固定用例和真实轨迹评测成功率、工具调用正确率、延迟、成本与越权风险,再逐步扩大 Agent 的自治范围。
这段回答体现了四个层次:业务决策、编排方式、Agent 策略和生产运行时。面试官继续追问时,可以分别展开,而不会陷入“框架选型就是架构设计”的误区。
参考资料
- 参考文章一:高德一面:Workflow 和 Agent 的差异你都说不清楚,怎么做 agent 开发?
- AI Handbook:Runtime / Harness
- LangGraph 文档
- ReAct:Synergizing Reasoning and Acting in Language Models
本文使用“Workflow、Agent、Harness”的工程化视角整理上述资料;示例代码为说明职责边界的伪代码,具体 LangGraph API 应以项目所使用的版本文档为准。