用 AI Agent 处理复杂流程:先写 Workflow,再写 Prompt

Posted on Fri 06 February 2026 in Journal

Abstract 用 AI Agent 处理复杂流程:先写 Workflow,再写 Prompt
Authors Walter Fan
Category Journal
Status v1.0
Updated 2026-02-06
License CC-BY-NC-ND 4.0

短大纲(给忙人)

展开看看 - **先把问题说清楚**:复杂流程的"复杂"不是步骤多,而是不确定性多(输入乱、分支多、工具杂、回滚难) - **Super Agent 做三件事**:识别任务 → 分派任务 → 汇总成果(别亲自下场写每一步) - **Sub Agent 遵循 SRP**:一个 SubAgent 只对一种能力负责,输入/输出可测试、可替换 - **SOLID 全家桶**:SRP 只是开头,OCP/LSP/ISP/DIP 在 Agent 系统里一样管用 - **A2A 通讯用结构化数据**:别靠自然语言"意会",要有 schema、版本、错误码、可追踪 id - **Prompt 要做版本管理**:别塞数据库,直接用目录 + Git;可 diff、可 review、可回滚 - **Workflow:先设计,再执行**:把流程当"程序"写出来(DAG/状态机/重试策略),然后让 Agent 去跑 - **MCP:把工具接入标准化**:SubAgent 通过 MCP 用工具,Super Agent 只管调度与权限边界 - **LangGraph 实战 Demo**:用 Supervisor + SubAgent 模式实现一个临床论文写作 Workflow

开头先扎心:你不是缺 Prompt,你是缺"流程的骨架"

我见过很多团队上来就"加 Prompt"。流程越复杂,Prompt 越长,最后像一锅粥:谁都能往里加一勺酱油,但没人知道锅里到底是什么味儿。

你想用 AI Agent 处理一个复杂流程,比如:

  • 新需求来了:要查历史文档、看代码、跑测试、写变更说明、发通知、顺手再写个 FAQ
  • 安全审计来了:要拉取日志、关联工单、抽取证据、生成报告,还要对"不可披露信息"做脱敏
  • 或者更日常的:写一篇文章,图要渲染、引用要校验、素材要归档、还得保持风格不油腻

这些事不难在"步骤",难在"意外":

  • 输入不干净:同一个字段有人叫 repo 有人叫 repository,还夹着半截中文
  • 分支很多:失败后怎么办?要不要重试?重试要不要换策略?
  • 工具一堆:本地脚本、API、MCP server、数据库、Git、CI......每个都有自己的脾气
  • 结果要可审计:出了问题你得能回答"为什么是这个结论""哪一步用的什么依据"

所以我现在更倾向一句话:先写 Workflow,再写 Prompt。


1) 先把角色分清:Super Agent 与 SubAgent,不是"大模型分身术"

"Super Agent / SubAgent" 这个分法,很多人觉得只是换了个名字。其实不是——它的核心目的是把复杂度关进笼子里

Super Agent:像项目经理,但更像"编排器"

Super Agent 的核心职责很简单,三件事:

  • 识别与拆分任务:把"做一件大事"拆成"可交付的小事"
  • 分派与编排执行顺序(workflow):谁先谁后、并行还是串行、失败怎么兜底
  • 汇总与验收成果:把 SubAgent 的输出拼成可交付物,并做一致性检查

它最重要的能力不是"写得好",而是"管得住"。

SubAgent:SRP(单一职责)不是口号,是救命

SubAgent 我建议强行遵循 SRP:一个 SubAgent 只负责一个领域能力。例如:

  • ResearchAgent:只负责检索与引用(输出来源、摘要、置信度)
  • CodeAgent:只负责改代码/跑命令(输出 diff、测试结果)
  • DocAgent:只负责把结构化输入写成文档(输出 markdown + 章节结构)
  • DiagramAgent:只负责把 diagram code block 渲染成图片(输出路径 + 校验)

好处很现实:

  • 可替换:某个 SubAgent 拉胯就换模型/换实现,不影响整个系统
  • 可测试:给定输入,输出是否符合 schema?能不能做 golden test?
  • 可控权限:需要网络的、能写文件的、能访问敏感数据的,权限边界更清晰

不只是 SRP:SOLID 五原则在 Agent 系统里全能用

SRP 只是开胃菜。写过后端服务的人对 SOLID 不会陌生——好消息是,这五条原则搬到 AI Agent 系统里几乎可以直接用。

OCP(开闭原则):加能力 = 加文件/加节点,不改已有代码

要新增一个工具(比如从 PubMed 扩展到 ClinicalTrials.gov),挂一个新 MCP Server 就行,SubAgent 代码不用动。要更新某个 Agent 的 prompt,改文件、发 PR,其他 Agent 完全无感。要加一个新 SubAgent,在 StateGraph 上加一个节点,已有节点不用碰。

LSP(里氏替换原则):遵守契约的 Agent 可以随时互换

只要新的 SubAgent 遵守同一套 A2A schema(输入/输出格式一致),就能无感替换。比如把 literature_agent 底层从 GPT-4o-mini 换成 Claude,Supervisor 完全不需要知道——它只关心"给你 topic,你还我 references 列表"。

ISP(接口隔离原则):给 Agent 最小够用的工具集

writer_agent 只有写作工具,不给它搜索工具——它不需要、也不应该自己去搜索。如果你把所有 tools 都塞给一个 Agent,它就容易"跑偏":本来该写论文的,看到有搜索工具就自己去搜了一轮,产出不可预期。

DIP(依赖倒置原则):依赖抽象(契约),不依赖具体实现

Supervisor 不依赖"literature_agent 是用 GPT-4o 跑的"这个细节,它只依赖 A2A 消息契约。SubAgent 不依赖"工具是直接调 API 还是走 MCP",它只依赖 @tool 接口。Workflow 不依赖"用 LangGraph 还是 Temporal",它只依赖 DAG 定义。

速查表放在这里,方便你对照检查自己的 Agent 系统:

原则 Agent 系统中的含义 反面教材
SRP 一个 SubAgent 只负责一个领域能力 一个 Agent 又搜索又写作又审稿
OCP 加新能力 = 加文件/加节点 每次加功能都要改 Supervisor 的 prompt
LSP 遵守 A2A 契约即可互换 换个模型就要改调用方代码
ISP 每个 Agent 只拿自己需要的 tools 所有 tools 全塞给每个 Agent
DIP Agent 之间依赖契约,不依赖实现 Supervisor 里硬编码 SubAgent 的模型名

2) A2A(Agent-to-Agent)通讯:别靠"自然语言默契",要靠契约

先对齐一下术语:A2A 在不同团队里含义不完全一样。Google 2025 年发布了一个 Agent2Agent Protocol 开放协议,专门解决跨平台 Agent 互操作的问题。我这篇文章里说的 A2A 更宽泛,泛指"Agent 与 Agent 之间的消息交互"——不管你用 Google 的协议、自研的 schema,还是 LangGraph 的 MessagesState,核心诉求是一样的:让 Agent 之间的沟通有契约、可追踪、能重试

如果你让 SubAgent 用自然语言随便回一句"我做完了",Super Agent 很快会陷入两种尴尬:

  • 要么不敢用:因为不知道这个结果靠不靠谱、缺不缺字段
  • 要么瞎用:因为不知道错误在哪、重试该怎么做

解决办法很工程化:结构化消息 + schema + 版本

一个能落地的 A2A 消息模板(JSON)

下面这个模板我自己很常用,你可以按需删减,但别丢掉关键字段(id/correlation_id/status/artifact/error)。

{
  "protocol": "a2a.v1",
  "id": "msg_20260206_000123",
  "correlation_id": "run_20260206_210000",
  "sender": "SuperAgent",
  "receiver": "ResearchAgent",
  "intent": "collect_references",
  "input": {
    "topic": "ai agent workflow",
    "constraints": {
      "language": "zh-CN",
      "no_bare_url": true
    }
  },
  "status": "ok",
  "output": {
    "references": [
      {
        "title": "Model Context Protocol - Specification",
        "url": "https://modelcontextprotocol.io/specification/latest",
        "notes": "MCP 规范入口,包含 lifecycle、tools、resources、prompts 等"
      }
    ]
  },
  "artifact": [
    { "type": "markdown", "path": "content/journal/xxx.md" }
  ],
  "error": null,
  "metrics": { "latency_ms": 812, "tokens_in": 1200, "tokens_out": 350 }
}

你会发现它很"无聊"。但工程上,无聊就是稳定

A2A 的两个关键习惯

  • 强制输出 schema:哪怕 SubAgent 内部写得再自由,最终对外输出必须过 schema 校验
  • 对失败分类:比如 VALIDATION_ERROR(输入不合规)、TOOL_ERROR(工具失败)、TIMEOUT(超时),不同错误的重试策略不一样

3) Prompt 版本管理:别用数据库,用目录 + 文件 + Git

有人喜欢把 prompt 存数据库,方便后台管理系统在线编辑。我的看法相反——直接用目录 + 文件 + Git。原因很朴素:

  • 可 diff:这句改了什么,一眼就能看到
  • 可 review:PR 上看 Prompt 变更,比在后台管理系统点点点靠谱
  • 可回滚:线上出事,git revert 可能比你"回忆昨晚改了啥"更快

一个我觉得舒服的目录结构如下:

prompts/
  super/
    router.v1.md
    evaluator.v1.md
  agents/
    research/
      system.v1.md
      user_template.v1.md
    coder/
      system.v1.md
      user_template.v1.md
    writer/
      system.v1.md
      outline.v1.md
      draft.v1.md
schemas/
  a2a/
    message.v1.json
workflows/
  blog_write.v1.yaml
  audit_report.v1.yaml

再加一条"反直觉"的小建议:Prompt 的版本号不要追求语义化得太完美。 先做到"能回滚、能对比、能定位",比"v2.3.4"更重要。


4) Workflow:先设计(声明式),再执行(命令式)

"Agent 先写个程序:design workflow,再执行程序 execute workflow"——这个节奏,本质上就是工程界反复验证过的经典套路:

  • Terraform:先写声明,再 apply
  • CI Pipeline:先写 YAML,再 run
  • Temporal / Airflow:先定义 DAG,再调度执行

Agent workflow 也一样:先把流程写成"能跑的结构",再让模型去跑

一个最小可用的 workflow(DAG 思路)

比如写文章这件事,workflow 可以是:

  • collect_notes(收集要点)
  • make_outline(生成大纲)
  • draft(写正文)
  • render_diagrams(渲染图)
  • verify_links(检查链接)
  • finalize(补 Summary、License、修改时间)

这里给一张结构图,帮助你把职责边界直观看清楚。

@startuml
skinparam componentStyle rectangle
skinparam shadowing false
skinparam rectangleBorderColor #555555

rectangle "Super Agent\n(Orchestrator)" as SA #FFD700
rectangle "Workflow Definition\n(DAG / State Machine)" as WF #FFEEBB

rectangle "SubAgents" as SUB #E8F5E9 {
  rectangle "ResearchAgent\n(SRP: references)" as RA #C8E6C9
  rectangle "CodeAgent\n(SRP: changes/tests)" as CA #C8E6C9
  rectangle "DocAgent\n(SRP: writing)" as DA #C8E6C9
  rectangle "DiagramAgent\n(SRP: render)" as GA #C8E6C9
}

rectangle "A2A Message Contract\n(schema + version)" as A2A #E3F2FD
rectangle "Prompt Repo\n(files + git)" as PR #E3F2FD

rectangle "MCP Servers\n(tools/resources/prompts)" as MCP #F3E5F5
rectangle "Workspace\n(files/artifacts)" as WS #FFF3E0

SA --> WF : design + execute
SA --> A2A : enforce contract
SA --> PR : load prompts
SA --> RA : dispatch tasks
SA --> CA
SA --> DA
SA --> GA

RA --> MCP : search/tools
CA --> MCP : git/build/tools
DA --> PR : templates
GA --> WS : images

MCP --> WS : read/write (scoped)
@enduml

Super Agent + MCP + Workflow 架构

执行层面的关键:重试策略要"写在流程里",别写在 Prompt 里

Prompt 里写"如果失败请重试三次",听起来很美,但它不可观测、也不可审计。 正确姿势是:把重试、超时、兜底策略写在 workflow 引擎里,让执行层负责。


5) MCP:把"工具接入"标准化,别让每个 Agent 都发明一遍轮子

MCP(Model Context Protocol)我更愿意把它看作:Agent 连接外部世界的"标准插口"

没有 MCP 之前,每个 SubAgent 要用一个外部工具,你得自己写一套"连接 + 鉴权 + 参数序列化 + 错误处理"的胶水代码。5 个 Agent 用 5 个工具,就是 25 种组合,维护起来想哭。

有了 MCP 之后:

  • SubAgent 需要工具,只管调 tool.xxx(),不关心底层怎么连、怎么鉴权
  • Super Agent 不用关心"调用某个工具要不要先登录、token 放哪"
  • 工具能力以 "MCP Server" 的形式独立部署:文件系统、数据库、PubMed、Git、CI、图渲染......想加就加,想换就换

MCP 协议本身很简洁,核心就三类能力:Tools(可调用的函数)、Resources(可读取的数据源)、Prompts(可复用的提示模板)。Agent 通过 JSON-RPC 和 MCP Server 通信,Server 端可以用任何语言实现——Python、TypeScript、Go、Rust 都有官方 SDK。

如果你已经有了一个能用的内部 API 或脚本,把它包装成 MCP Server 通常只需要几十行代码。后面第 8 节的 demo 里有一个具体的例子(mcp_pubmed_search),可以参考。

详细文档和参考实现我放在文末的扩展阅读里。


6) 把它们串起来:一段 A2A 序列,看看"复杂流程"怎么被管住

下面这张序列图不是为了好看,而是为了回答两个问题:

1) 每一步都能定位是谁产出的 2) 失败后怎么恢复(重试哪个节点,而不是整锅重来)

@startuml
autonumber
skinparam participantBackgroundColor #FAFAFA
skinparam participantBorderColor #555555
participant "SuperAgent\n(Orchestrator)" as SA #FFD700
participant "ResearchAgent" as RA #C8E6C9
participant "DocAgent" as DA #C8E6C9
participant "DiagramAgent" as GA #C8E6C9
participant "MCP Server" as MCP #F3E5F5
participant "Workspace" as WS #FFF3E0

SA -> RA : A2A(request)\nintent=collect_references\nschema=a2a.v1
RA -> MCP : tool.search(query)
MCP --> RA : results (structured)
RA --> SA : A2A(response)\nreferences[] + confidence

SA -> DA : A2A(request)\nintent=draft_article\ninput=notes+refs
DA --> SA : A2A(response)\nmarkdown_draft

SA -> GA : A2A(request)\nintent=render_diagrams\ninput=code_blocks
GA -> WS : write images
GA --> SA : A2A(response)\nimages[]

SA -> MCP : tool.verify_links(urls)
MCP --> SA : ok / broken[]

SA -> WS : write final markdown
@enduml

A2A + MCP 的执行序列


7) 一份"能抄就抄"的落地清单

你需要先做的 7 件事(按优先级)

  • 定义 SubAgent 清单:每个 SubAgent 的 SRP 是什么?输入/输出是什么?
  • 定义 A2A schema:至少包含 idcorrelation_idstatusoutputerror
  • 把 Prompt 放进仓库:目录结构固定、命名统一、PR 审核机制
  • 写 workflow 文件:DAG/状态机都行,关键是"可读、可追踪、可重跑"
  • 接入 MCP 工具:先从最常用的 2-3 个开始(文件、搜索、Git/CI)
  • 加上可观测性:每个节点的输入/输出摘要、耗时、失败原因(注意脱敏)
  • 设计失败恢复:节点级重试、人工介入点、回滚策略(别全靠"再跑一次")

两个常见坑(我踩过的那种)

  • 坑 1:SubAgent 职责漂移 今天让它"查资料",明天又让它"顺便写总结",最后它变成一个小号 Super Agent。

  • 坑 2:把流程控制写进 Prompt Prompt 里越写越多"如果......则......否则......",最后你得到的是一个不可测试的"语言版状态机"。


8) 动手试一试:用 LangGraph 编排一篇临床医学论文

前面讲了一堆方法论,你可能已经有点痒了:说得好听,代码呢?

我女儿从事临床医学研究,她写论文时需要用到 AI Agent 来辅助她完成论文的写作。因此,我选择了临床医学论文写作作为示例,因为它天然就是"复杂流程"的标本——文献要从 PubMed 捞、统计分析不能瞎编、正文必须 IMRAD(Introduction-Methods-Results-And-Discussion)结构、最后还得过 CONSORT/STROBE 合规审查。少了哪一步,审稿人都会把你稿子打回来。

下面用 LangGraph 实现这个多 Agent 工作流,把前面的概念全用上:

  • Super Agent -> LangGraph 的 supervisor 节点(论文主编,决定下一步该谁干活)
  • SubAgent(SRP) -> literature_agentstats_agentwriter_agentcompliance_agent,各管各的
  • A2A 结构化通讯 -> LangGraph 的 MessagesState(共享的、有类型的消息列表)
  • Workflow 先设计再执行 -> 整张 StateGraph 就是声明式的 DAG,compile() 之后才执行

先看一眼这个 workflow 的全貌

@startuml
skinparam shadowing false
skinparam activityBackgroundColor #f5f5f5
skinparam activityBorderColor #555555

(*) --> "==Supervisor==\n(论文主编)" #FFD700
"==Supervisor==\n(论文主编)" --> "LiteratureAgent\nPubMed 文献检索" #C8E6C9
"LiteratureAgent\nPubMed 文献检索" --> "==Supervisor==\n(论文主编)"

"==Supervisor==\n(论文主编)" --> "StatsAgent\n统计分析" #BBDEFB
"StatsAgent\n统计分析" --> "==Supervisor==\n(论文主编)"

"==Supervisor==\n(论文主编)" --> "WriterAgent\nIMRAD 撰写" #FFE0B2
"WriterAgent\nIMRAD 撰写" --> "==Supervisor==\n(论文主编)"

"==Supervisor==\n(论文主编)" --> "ComplianceAgent\nCONSORT 合规审查" #E1BEE7
"ComplianceAgent\nCONSORT 合规审查" --> "==Supervisor==\n(论文主编)"

"==Supervisor==\n(论文主编)" --> "END\n输出终稿" #F5F5F5
@enduml

LangGraph 临床论文写作 Workflow

核心思路:Supervisor 就是"论文通讯作者" -- 它不亲自写每一节,但它决定"接下来把球传给谁"、"这一轮结果够不够格",以及"活干完了,投稿"。

完整代码(可直接跑)

依赖pip install langgraph langchain-core langchain-openai(需要 OPENAI_API_KEY 环境变量)

"""
clinical_paper_workflow.py
用 LangGraph 实现临床医学论文多 Agent 写作工作流

场景:写一篇 RCT(随机对照试验)论文,比较两种降压药的疗效
流程:文献检索 -> 统计分析 -> IMRAD 撰写 -> CONSORT 合规审查

概念映射:
  Super Agent   -> supervisor(通讯作者 / 论文主编)
  SubAgent(SRP) -> literature / stats / writer / compliance
  A2A 通讯      -> MessagesState(结构化消息链)
  Workflow      -> StateGraph(声明式 DAG)
"""

from langgraph.prebuilt import create_react_agent
from langgraph.graph import StateGraph, START, END, MessagesState
from langchain_core.tools import tool


# =========================================================
# 1. 定义每个 SubAgent 的专属 Tools
# =========================================================

@tool
def search_pubmed(query: str, max_results: int = 5) -> str:
    """Search PubMed for clinical research papers."""
    # 真实场景接 NCBI E-utilities API 或 MCP PubMed server
    return (
        f"[PubMed results for '{query}', top {max_results}]\n"
        "1. PMID:38901234 - 'Amlodipine vs Losartan in Stage 2 HTN: "
        "A 12-month RCT' (Lancet 2025) - N=480, primary: 24h-ABPM\n"
        "2. PMID:38876543 - 'Meta-analysis of CCB vs ARB in elderly HTN' "
        "(BMJ 2024) - 12 RCTs, N=8,320\n"
        "3. PMID:38765432 - 'CONSORT 2025 updated checklist for RCTs' "
        "(Ann Intern Med 2025)\n"
        "4. PMID:38654321 - 'Renal outcomes in ARB-treated diabetic HTN' "
        "(NEJM 2024) - N=1,200\n"
        "5. PMID:38543210 - 'Adverse events profiling: CCB vs ARB classes' "
        "(Pharmacotherapy 2025) - systematic review"
    )

@tool
def run_stats_analysis(design: str, groups: str, endpoint: str) -> str:
    """Run statistical analysis on clinical trial data."""
    # 真实场景接 R/Python stats 脚本,或 MCP 数据分析 server
    return (
        f"[Statistical Report: {design}]\n"
        f"Groups: {groups}\n"
        f"Primary endpoint: {endpoint}\n\n"
        "Results:\n"
        "- Amlodipine group (n=240): mean SBP reduction 18.3 mmHg (SD 6.2)\n"
        "- Losartan group (n=240): mean SBP reduction 16.7 mmHg (SD 5.8)\n"
        "- Between-group diff: 1.6 mmHg (95% CI: 0.5-2.7, p=0.012)\n"
        "- NNT for target BP (<130/80): 14\n"
        "- Adverse events: peripheral edema 12.1% vs 3.3% (p<0.001)\n"
        "- Kaplan-Meier 12-month adherence: 82% vs 89% (log-rank p=0.04)\n"
        "- Sensitivity analysis (per-protocol): consistent with ITT"
    )

@tool
def write_imrad_section(section: str, data: str) -> str:
    """Write a section of the paper in IMRAD format."""
    # 真实场景用更强的模型 + 领域 prompt 模板
    templates = {
        "introduction": (
            "## Introduction\n\n"
            "Hypertension affects over 1.28 billion adults worldwide (WHO 2023). "
            "Current guidelines recommend CCBs and ARBs as first-line agents, "
            "yet head-to-head RCTs comparing amlodipine and losartan remain scarce...\n"
            f"(Based on literature: {data[:100]}...)"
        ),
        "methods": (
            "## Methods\n\n"
            "### Study design\n"
            "Prospective, randomized, double-blind, active-controlled trial.\n"
            "### Participants\n"
            "Adults 40-75 y with stage 2 HTN (SBP 140-179 mmHg). "
            "Key exclusion: secondary HTN, eGFR <30.\n"
            "### Randomization\n"
            "1:1 allocation via computer-generated sequence, "
            "stratified by diabetes status.\n"
            f"(Stats plan: {data[:80]}...)"
        ),
        "results": (
            "## Results\n\n"
            f"{data}\n\n"
            "Figure 1: Kaplan-Meier adherence curves.\n"
            "Table 1: Baseline characteristics (well-balanced, p>0.05 for all)."
        ),
        "discussion": (
            "## Discussion\n\n"
            "Our findings suggest a statistically significant but clinically modest "
            "advantage of amlodipine over losartan in 24h SBP reduction. "
            "However, the higher edema rate and lower adherence in the amlodipine "
            "group warrant individualized treatment decisions...\n"
            f"(Contextualized with: {data[:80]}...)"
        ),
    }
    return templates.get(section.lower(), f"## {section}\n\n{data}")

@tool
def check_consort_compliance(manuscript: str) -> str:
    """Check manuscript against CONSORT 2025 checklist."""
    # 真实场景:逐条比对 CONSORT 25 项 + 流程图
    return (
        "CONSORT 2025 Compliance Report:\n\n"
        "PASS [1] Title: identified as randomised trial\n"
        "PASS [2] Abstract: structured, includes key numbers\n"
        "PASS [3a] Background: scientific rationale stated\n"
        "WARN [6a] Outcomes: secondary endpoints not fully pre-specified\n"
        "PASS [7a] Sample size: calculation provided (power=0.80, alpha=0.05)\n"
        "PASS [8a] Randomization: sequence generation described\n"
        "WARN [11a] Blinding: assessor blinding not explicitly confirmed\n"
        "PASS [13a] Flow diagram: CONSORT flow present\n"
        "PASS [16] Ancillary analyses: sensitivity analysis included\n"
        "FAIL [23] Registration: trial registry number missing\n\n"
        "Score: 22/25 items PASS, 2 WARN, 1 FAIL\n"
        "Action required: add trial registration number (e.g. NCT0xxxxxxx)"
    )


# =========================================================
# 2. 定义四个 SubAgent(SRP:一个 Agent 只干一件事)
# =========================================================

literature_agent = create_react_agent(
    model="openai:gpt-4o-mini",
    tools=[search_pubmed],
    prompt=(
        "你是 LiteratureAgent,只负责在 PubMed 检索相关临床文献。"
        "输出结构化的参考列表:PMID、标题、期刊、年份、样本量、关键结论。"
        "不要写论文正文,不要做统计分析。"
    ),
    name="literature_agent",
)

stats_agent = create_react_agent(
    model="openai:gpt-4o-mini",
    tools=[run_stats_analysis],
    prompt=(
        "你是 StatsAgent,只负责对临床试验数据做统计分析。"
        "输出:主要终点结果、p 值、置信区间、效应量、敏感性分析。"
        "不要写论文正文,不要检索文献。"
    ),
    name="stats_agent",
)

writer_agent = create_react_agent(
    model="openai:gpt-4o-mini",
    tools=[write_imrad_section],
    prompt=(
        "你是 WriterAgent,根据文献和统计结果撰写论文。"
        "严格使用 IMRAD 结构(Introduction, Methods, Results, Discussion)。"
        "学术语言,客观严谨,引用须标注 PMID。"
        "不要自己编造数据,只用已有的文献和统计结果。"
    ),
    name="writer_agent",
)

compliance_agent = create_react_agent(
    model="openai:gpt-4o-mini",
    tools=[check_consort_compliance],
    prompt=(
        "你是 ComplianceAgent,按 CONSORT 2025 清单审查论文手稿。"
        "逐条检查并输出:PASS / WARN / FAIL + 具体修改建议。"
        "不要自己改写论文,只输出审查报告。"
    ),
    name="compliance_agent",
)


# =========================================================
# 3. 定义 Supervisor(通讯作者:编排 + 质控)
# =========================================================

supervisor_agent = create_react_agent(
    model="openai:gpt-4o-mini",
    tools=[],  # supervisor 不需要 tools,它靠路由决策
    prompt=(
        "你是 Supervisor(通讯作者),管理四个 Agent:\n"
        "1. literature_agent - 负责 PubMed 文献检索\n"
        "2. stats_agent - 负责统计分析\n"
        "3. writer_agent - 负责 IMRAD 论文撰写\n"
        "4. compliance_agent - 负责 CONSORT 合规审查\n\n"
        "标准流程:\n"
        "  Step 1: literature_agent 检索相关 RCT 和 meta-analysis\n"
        "  Step 2: stats_agent 分析试验数据\n"
        "  Step 3: writer_agent 按 IMRAD 撰写各章节\n"
        "  Step 4: compliance_agent 做 CONSORT 合规检查\n\n"
        "每次只分派一个任务。如果合规审查有 FAIL 项,"
        "把修改意见转给 writer_agent 修订,然后重新审查。\n"
        "全部 PASS 或仅剩 WARN 后,输出最终结论并结束。"
    ),
    name="supervisor",
)


# =========================================================
# 4. 组装 StateGraph -- 声明式 workflow
# =========================================================

workflow = (
    StateGraph(MessagesState)
    .add_node(
        supervisor_agent,
        destinations=(
            "literature_agent", "stats_agent",
            "writer_agent", "compliance_agent", END
        ),
    )
    .add_node(literature_agent)
    .add_node(stats_agent)
    .add_node(writer_agent)
    .add_node(compliance_agent)
    .add_edge(START, "supervisor")
    # 每个 SubAgent 完成后回到 supervisor
    .add_edge("literature_agent", "supervisor")
    .add_edge("stats_agent", "supervisor")
    .add_edge("writer_agent", "supervisor")
    .add_edge("compliance_agent", "supervisor")
    .compile()
)


# =========================================================
# 5. 执行 -- 一句话触发整个论文写作流程
# =========================================================

if __name__ == "__main__":
    result = workflow.invoke(
        {
            "messages": [
                {
                    "role": "user",
                    "content": (
                        "请帮我写一篇 RCT 论文:比较氨氯地平与氯沙坦"
                        "治疗 2 期高血压的 12 个月疗效和安全性。"
                        "样本量 480 例,主要终点为 24 小时动态血压。"
                    ),
                }
            ]
        }
    )
    for msg in result["messages"]:
        role = getattr(msg, "type", "unknown")
        content = getattr(msg, "content", "")
        if content:
            print(f"[{role}] {content[:200]}")
            print("---")

几个值得注意的设计细节

为什么拆成四个 Agent 而不是一个大 Prompt?

你当然可以把"检索 + 统计 + 写作 + 合规"塞进一个 system prompt。但那样一来:改 CONSORT 检查项就得动整个 prompt,牵一发而动全身;stats_agent 想换个更擅长数值推理的模型(比如 o3)?没门,因为和写作共用一个上下文;合规审查 FAIL 了?只能整篇重跑,而不是只让 writer_agent 改那一节。

拆开之后,这些问题都不存在了——每个 Agent 独立演进、独立替换、独立重试。这就是 SRP 在 Agent 场景下的实际收益。

Supervisor 的"循环修订"能力

注意 supervisor prompt 里的这段:"如果合规审查有 FAIL 项,把修改意见转给 writer_agent 修订,然后重新审查"。这不是线性流水线,而是带反馈环的 DAG -- LangGraph 天然支持,因为每个 SubAgent 完成后都回到 supervisor,由它决定是"往下走"还是"打回去"。

MessagesState 就是"穷人版 A2A"

LangGraph 的 MessagesState 是一个共享的结构化消息列表。每个节点读到的输入、写出的输出都挂在这条链上 -- 天然带了顺序、角色标记(谁说的)和内容。如果你需要更严格的 schema 校验,可以继承 MessagesState 加自己的字段:

from typing import List, Optional

class ClinicalPaperState(MessagesState):
    """扩展 state,让论文写作的 A2A 数据更结构化"""
    references: List[dict]         # LiteratureAgent: PMID + 摘要
    stats_report: str              # StatsAgent: 统计分析报告
    manuscript_sections: dict      # WriterAgent: {"intro": ..., "methods": ...}
    consort_score: Optional[dict]  # ComplianceAgent: {pass: 22, warn: 2, fail: 1}
    revision_round: int            # 第几轮修订(可观测性)

Prompt 怎么从文件加载?

把前面第 3 节讲的 "Prompt 版本管理" 接上:

from pathlib import Path

def load_prompt(agent_name: str, version: str = "v1") -> str:
    """从 prompts/ 目录加载对应版本的 system prompt"""
    path = Path(f"prompts/agents/{agent_name}/system.{version}.md")
    return path.read_text(encoding="utf-8")

# 用法:升级 CONSORT 检查清单时,只需要改 compliance 的 prompt 文件
compliance_agent = create_react_agent(
    model="openai:gpt-4o-mini",
    tools=[check_consort_compliance],
    prompt=load_prompt("compliance", "v2"),  # CONSORT 2025 版
    name="compliance_agent",
)

MCP 怎么接进来?

LangGraph 的 @tool 装饰器可以包装任何 Python 函数。如果你的 PubMed 检索工具跑在 MCP Server 上,只需要写一个适配层:

from mcp import ClientSession

@tool
async def mcp_pubmed_search(query: str, max_results: int = 5) -> str:
    """通过 MCP Server 检索 PubMed(替代直接调 NCBI API)"""
    async with ClientSession(transport) as session:
        result = await session.call_tool(
            "pubmed_search",
            arguments={"query": query, "max_results": max_results}
        )
        return result.content[0].text

这样 literature_agent 代码不用改,只是 tool 的实现从"直接调 NCBI E-utilities"变成了"走 MCP 协议" -- 接口不变,通道标准化。同理,stats_agent 的统计脚本也可以包成 MCP Server,让 R/Python 后端跑真实的 t-test / Cox regression。


总结:复杂流程的解法,是"工程化地管理不确定性"

如果你只记住一句话:Super Agent 负责编排与验收,SubAgent 负责 SRP 能力,A2A 用契约沟通,Prompt 用 Git 管版本,Workflow 先声明后执行,MCP 把工具接入标准化。

你目前团队里最想用 Agent 处理的"复杂流程"是什么?是代码审查、安全审计、内容生产,还是别的什么?欢迎留言聊聊——说不定你的场景就是下一篇文章的素材。

最后给你一张思维导图(方便复盘/分享给同事)。

@startmindmap
<style>
mindmapDiagram {
  node {
    BackgroundColor #FAFAFA
  }
  :depth(0) {
    BackgroundColor #FFD700
  }
  :depth(1) {
    BackgroundColor #E3F2FD
  }
  :depth(2) {
    BackgroundColor #F5F5F5
  }
}
</style>
* 用 AI Agent 处理复杂流程
** 先定义角色
*** Super Agent
**** 识别/拆分任务
**** 编排 workflow(顺序/并行/重试)
**** 汇总成果 + 验收一致性
*** SubAgent(SRP)
**** Research:资料与引用
**** Code:改动与测试
**** Doc:写作与结构化输出
**** Diagram:渲染与产物管理
** SOLID 设计原则
*** SRP:一个 Agent 一个职责
*** OCP:加能力 = 加节点/加文件
*** LSP:遵守契约即可互换
*** ISP:最小够用的工具集
*** DIP:依赖契约,不依赖实现
** A2A:结构化通讯
*** schema + version
*** correlation_id 可追踪
*** error 分类 + 策略化重试
** Prompt:文件化版本管理
*** prompts/ 目录
*** Git diff/review/rollback
** Workflow:先设计再执行
*** DAG/状态机
*** 超时/重试/兜底写在执行层
** MCP:工具接入标准化
*** tools/resources/prompts
*** 权限边界更清晰
** LangGraph 实战
*** Supervisor + SubAgent
*** MessagesState = A2A
*** @tool + MCP 适配
** 观测与安全
*** 日志脱敏
*** 最小权限
*** 产物可审计
@endmindmap

用 AI Agent 处理复杂流程:思维导图


扩展阅读


本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。