AI 应用为什么需要 Workflow?不要把复杂任务写成一串函数调用

📅 2026/7/11 6:54:50 👁️ 阅读次数 📝 编程学习
AI 应用为什么需要 Workflow?不要把复杂任务写成一串函数调用

上一篇文章讨论了 Prompt 管理。

Prompt 管理解决的是模型输入如何组织的问题。

它让 Prompt 不再散落在业务代码里,而是成为可以维护、复用和版本化的项目资产。

但当 Prompt 和 LLM 抽象层都准备好以后,AI 应用还会继续变复杂。

一开始,一个功能可能只是这样:

messages=prompt_manager.render_messages("summary",{"text":text})result=llm.chat(messages)returnresult.content

这很清楚。

但真实项目往往不是一次模型调用就结束。

比如一个文章处理任务,可能需要:

清洗输入文本。

判断文章类型。

选择合适 Prompt。

调用模型生成摘要。

校验摘要长度。

如果不符合要求,再调用模型重写。

再生成标题。

再提取关键词。

最后组合成结构化结果。

如果把这些全部写在一个函数里,代码很快就会变成很长的一串调用。

这时候,就需要 Workflow。

一、为什么简单函数调用一开始够用?

在 Demo 阶段,简单函数调用是最自然的写法。

例如:

defrun(text:str)->str:cleaned_text=clean_text(text)prompt=build_prompt(cleaned_text)result=llm.chat(prompt)returnresult.content

这段代码没有问题。

它短、直观、容易调试。

如果任务只有三四步,这样写完全可以。

但问题是,AI 应用一旦进入真实业务,就很少停留在三四步。

业务会不断增加新的要求。

比如:

  • 输入为空时要返回提示。
  • 文本太长时要切分。
  • 模型调用失败时要重试。
  • 输出格式不对时要重新生成。
  • 不同用户场景要走不同 Prompt。
  • 有些任务需要调用外部工具。
  • 有些任务需要保存中间结果。
  • 有些任务需要记录每一步耗时。

这些需求一加进来,简单函数就会开始膨胀。

最后可能变成一个很长的函数,里面充满if elsetry except和临时变量。

这不是某个开发者写得不好。

而是任务复杂度已经超过了普通函数链条适合表达的范围。

二、复杂 AI 任务为什么容易写乱?

复杂 AI 任务容易写乱,主要有几个原因。

第一,步骤多。

一次完整任务可能包含输入处理、Prompt 渲染、模型调用、结果解析、校验、重试、工具调用和最终输出。

第二,中间状态多。

每一步都会产生数据。

例如原始输入、清洗后输入、Prompt、模型响应、解析结果、校验结果、错误信息。

第三,分支多。

不同输入可能走不同路径。

例如短文本直接总结,长文本先切分再总结。

第四,失败场景多。

模型可能超时。

返回内容可能为空。

JSON 可能解析失败。

工具调用可能失败。

第五,调试困难。

当最终结果不符合预期时,需要知道到底是哪一步出了问题。

如果所有逻辑都写在一个函数里,排查会很痛苦。

所以,复杂 AI 任务需要一种更清晰的组织方式。

Workflow 的价值就在这里。

三、Workflow 解决的不是炫技问题

提到 Workflow,有些人会觉得这是把简单问题复杂化。

如果任务只有一次模型调用,确实不需要 Workflow。

但 Workflow 不是为了炫技。

它解决的是复杂任务的组织问题。

一个合理的 Workflow 至少应该带来几个好处。

第一,步骤清晰。

每个节点只负责一件事。

第二,状态清晰。

所有中间结果都放在统一上下文里。

第三,分支清晰。

不同条件对应不同执行路径。

第四,错误清晰。

每一步失败时都能知道失败位置和失败原因。

第五,调试清晰。

可以记录每个节点的输入、输出、耗时和状态。

第六,扩展清晰。

新增一个步骤,不一定要改动整个大函数。

所以,Workflow 的本质不是把代码写得更复杂。

而是让复杂任务的结构变得可见。

四、AI Workflow 通常有哪些节点?

AI Workflow 可以把任务拆成多个节点。

常见节点包括:

Input 节点: - 接收用户输入。 - 做基础校验。 - 标准化输入格式。 Preprocess 节点: - 清洗文本。 - 截断文本。 - 分段处理。 Prompt 节点: - 选择 Prompt 模板。 - 注入变量。 - 生成 messages。 LLM 节点: - 调用模型。 - 获取生成结果。 - 记录 token 和耗时。 Tool 节点: - 调用外部工具。 - 查询数据库。 - 检索知识库。 Condition 节点: - 判断是否继续执行。 - 判断走哪个分支。 - 判断是否需要重试。 Validator 节点: - 校验模型输出格式。 - 校验 JSON 字段。 - 校验长度和业务规则。 Output 节点: - 整理最终结果。 - 返回给调用方。 - 写入日志或数据库。

这些节点不是每个项目都必须有。

但它们代表了 AI 应用里常见的任务步骤。

Workflow 的作用,是把这些步骤组织起来。

五、不要让每个函数随便传参数

复杂任务里最容易乱的地方,是参数传递。

例如:

cleaned=clean_text(raw_text)prompt=build_prompt(cleaned,user_id,scene,lang)response=call_llm(prompt,model,timeout)parsed=parse_response(response,scene)validated=validate_result(parsed,raw_text,user_id)

刚开始还能接受。

但步骤越来越多时,每个函数都要传一堆参数。

有些参数只在某一步使用。

有些参数会被后续多个步骤使用。

有些参数是中间结果。

最后整个调用链会变得很难看。

更合理的方式,是设计统一的WorkflowContextWorkflowState

例如:

fromdataclassesimportdataclass,field@dataclassclassWorkflowState:raw_input:strcleaned_input:str|None=Noneprompt_messages:list[dict]|None=Nonellm_output:str|None=Noneparsed_result:dict|None=Noneerrors:list[str]=field(default_factory=list)

每个节点接收 state,处理后更新 state。

defclean_node(state:WorkflowState)->WorkflowState:state.cleaned_input=state.raw_input.strip()returnstate

这样状态流转会更清楚。

不是所有参数都在函数之间到处飞。

而是每一步都围绕同一个 state 进行处理。

六、一个最简单的 Workflow 可以怎么写?

第一版 Workflow 不需要很复杂。

可以先从顺序执行开始。

例如:

classWorkflow:def__init__(self):self.nodes=[]defadd_node(self,node):self.nodes.append(node)returnselfdefrun(self,state):fornodeinself.nodes:state=node(state)returnstate

然后定义节点:

defclean_node(state):state.cleaned_input=state.raw_input.strip()returnstatedefprompt_node(state):state.prompt_messages=prompt_manager.render_messages(name="summary",variables={"text":state.cleaned_input},)returnstatedefllm_node(state):response=llm.chat(state.prompt_messages)state.llm_output=response.contentreturnstate

组合起来:

workflow=(Workflow().add_node(clean_node).add_node(prompt_node).add_node(llm_node))state=WorkflowState(raw_input="一段文章内容")result=workflow.run(state)

这个版本很简单。

但它已经比一个巨大函数更清楚。

因为每个节点都有明确职责。

七、顺序 Workflow 适合哪些场景?

顺序 Workflow 适合步骤固定的任务。

例如:

  • 文章摘要生成。
  • 标题生成。
  • 简单问答处理。
  • 文本分类。
  • 关键词提取。
  • 固定格式报告生成。

这类任务的特点是:

执行顺序比较稳定。

分支不多。

每一步都依赖上一步结果。

例如文章摘要可以是:

输入文本 ↓ 清洗文本 ↓ 渲染 Prompt ↓ 调用 LLM ↓ 校验摘要 ↓ 输出结果

这种任务不需要一开始就上复杂 DAG。

一个简单的顺序 Workflow 就足够。

脚手架应该先支持这种基础能力。

因为大多数 AI 应用的第一批功能,都可以从顺序流程开始。

八、什么时候需要条件分支?

当不同输入需要走不同路径时,就需要条件分支。

例如:

如果文章很短,直接总结。

如果文章很长,先分段总结,再合并总结。

可以表示成:

输入文本 ↓ 判断长度 ├── 短文本:直接总结 └── 长文本:分段总结 -> 合并总结

代码里可以先用简单方式实现。

例如:

defroute_node(state):iflen(state.cleaned_input)>5000:state.route="long_text"else:state.route="short_text"returnstate

然后根据 route 执行不同节点。

第一版不一定要做得很抽象。

但要让分支逻辑有明确位置。

不要让分支散落在每个节点里。

如果每个节点都自己判断一堆条件,Workflow 也会变乱。

九、失败重试也应该进入 Workflow 视角

AI 任务里失败很常见。

模型调用可能失败。

输出格式可能不符合要求。

工具调用可能超时。

JSON 可能解析失败。

如果每个地方都自己写重试逻辑,项目会很乱。

Workflow 可以把失败重试看成流程的一部分。

例如:

调用 LLM ↓ 校验输出 ├── 通过:进入下一步 └── 失败:重新生成

代码可以先写成:

defvalidate_summary_node(state):ifnotstate.llm_output:state.errors.append("empty summary")state.need_retry=Truereturnstateiflen(state.llm_output)>200:state.errors.append("summary too long")state.need_retry=Truereturnstate state.need_retry=Falsereturnstate

然后 Workflow 根据need_retry决定是否再调用一次 LLM。

这样失败处理就不是临时补丁。

而是流程设计的一部分。

十、Workflow 需要可观测性

复杂 AI 任务很难只靠最终结果调试。

因为最终结果不好,不代表最后一步错了。

可能是输入清洗错了。

可能是 Prompt 变量错了。

可能是模型输出格式错了。

可能是某个工具返回了空结果。

所以 Workflow 需要记录每个节点的执行信息。

至少包括:

  • 节点名称。
  • 开始时间。
  • 结束时间。
  • 是否成功。
  • 错误信息。
  • 关键输入摘要。
  • 关键输出摘要。

例如:

{"workflow":"summary_workflow","node":"llm_node","success":true,"latency_ms":1200}

这些日志对调试非常重要。

开发环境可以记录更多细节。

生产环境要注意脱敏。

尤其是用户输入、API Key、隐私信息,不应该随意进入日志。

可观测性不是后期才做的事情。

Workflow 层一开始就应该预留记录能力。

十一、Workflow 和 Prompt、LLM 的关系

Workflow 不是替代 Prompt 管理,也不是替代 LLM 抽象层。

它们的职责不同。

Prompt 管理层负责构造模型输入。

LLM 抽象层负责调用模型。

Workflow 层负责任务编排。

例如:

defprompt_node(state):state.messages=prompt_manager.render_messages(name="summary",variables={"text":state.cleaned_input},)returnstatedefllm_node(state):state.llm_output=llm.chat(state.messages).contentreturnstate

这里 Workflow 只是把 Prompt 和 LLM 组织起来。

它不应该把 Prompt 字符串写死在节点里。

也不应该直接依赖某个模型供应商 SDK。

否则前面做的 Prompt 管理和 LLM 抽象都会失去意义。

好的 Workflow,应该建立在前面这些基础模块之上。

十二、Workflow 和 Agent 有什么区别?

Workflow 和 Agent 很容易被混在一起。

它们确实有关系,但重点不同。

Workflow 更偏确定性流程。

也就是说,开发者预先设计好步骤和顺序。

例如:

清洗输入 -> 渲染 Prompt -> 调用模型 -> 校验结果 -> 输出

Agent 更偏动态决策。

也就是说,系统根据目标、上下文和工具结果,动态决定下一步做什么。

例如:

分析问题 -> 选择工具 -> 调用工具 -> 观察结果 -> 决定下一步

简单理解:

Workflow 适合流程明确的任务。

Agent 适合路径不固定、需要动态决策的任务。

但它们不是对立关系。

Agent 内部也可以使用 Workflow。

Workflow 的某个节点也可以调用 Agent。

对于 AI Scaffold 来说,可以先把 Workflow 作为确定性任务编排层做好。

后续再讨论 Agent 层,会更自然。

十三、Workflow 在 AI Scaffold 里的位置

在 AI Scaffold 里,Workflow 可以作为核心模块之一。

项目结构可以这样设计:

app/ ├── main.py ├── config/ │ └── settings.py ├── llm/ │ ├── base.py │ └── factory.py ├── prompts/ │ └── summary/ │ └── v1.md ├── workflows/ │ ├── __init__.py │ ├── base.py │ ├── state.py │ └── summary_workflow.py └── tools/ └── search_tool.py

其中:

workflows/base.py放基础 Workflow 类。

workflows/state.py放状态对象。

summary_workflow.py放具体业务流程。

CLI 创建项目时,可以默认生成一个示例 Workflow。

例如:

workflows/summary_workflow.py

这样开发者创建项目后,就能看到一个完整链路:

配置系统提供运行参数。

Prompt 管理层生成模型输入。

LLM 抽象层调用模型。

Workflow 层组织任务步骤。

这比只生成一个main.py更适合真实项目。

十四、总结

当 AI 应用只是一次模型调用时,不一定需要 Workflow。

但当任务开始包含多个步骤、多个中间状态、多个分支和多个失败场景时,Workflow 就变得非常重要。

一个合理的 AI Workflow,至少应该做到:

  • 把复杂任务拆成清晰节点。
  • 使用统一 state 管理中间状态。
  • 支持顺序执行。
  • 支持条件分支。
  • 支持失败校验和重试。
  • 能记录每个节点的执行状态。
  • 和 Prompt 管理层保持边界。
  • 和 LLM 抽象层保持边界。
  • 为后续 Agent 层打基础。

对于 AI Scaffold 来说,Workflow 是从 Demo 走向工程化的重要一步。

它让 AI 任务不再是一串临时拼起来的函数调用,而是变成结构清晰、状态可追踪、失败可处理、后续可扩展的执行流程。

下一篇文章可以继续讨论 Agent:

当任务路径不再完全固定,需要系统根据目标、上下文和工具结果动态决定下一步时,Agent 应该如何在脚手架中设计。