目录

选框架的焦虑

一、编排到底是什么

二、七种编排模式

三、生产环境绕不开的问题

四、几条编排经验

总结


选框架的焦虑

想做Agent编排,打开搜索框,LangGraph、CrewAI、AutoGen、OpenAI Agents SDK、Google ADK、Dify……每个都说自己是最佳选择。

看看PyPI的下载数据(2026年7月):

框架 月下载量 GitHub Stars
LangGraph 6680万 37.7K
OpenAI Agents SDK 3156万 28K
Google ADK 1561万 20.8K
CrewAI 1087万 55.9K

数据摆在这,很多人会想”选LangGraph就对了,下载量最高”。

Anthropic在《Building Effective Agents》里说了一句被很多人忽略的话:“最成功的实现不用复杂框架,而是用简单、可组合的模式。”

根据经验,这句话是对的。我最终选了LangGraph,但选的过程没那么纠结,因为不管用哪个框架,你要解决的问题是一样的:流程怎么组织、工具怎么设计、异常怎么处理。框架只是帮你省了写胶水代码的功夫,Agent好不好用,取决于你对编排的理解。

框架是工具,不是答案。 决定Agent编排质量的是三件事:用什么模式组织流程、怎么设计工具让模型选对、上线后怎么处理会话并发和错误。这三个问题和你选哪个框架无关,但和你的Agent能不能跑进生产环境有关。

这篇文章讲的就是这三件事。


一、编排到底是什么

一个类比

编排像菜谱,框架像厨具。

菜谱决定:先切什么、再炒什么、火候多大、什么时候放盐。厨具决定:用什么刀、什么锅、什么灶。大多数人纠结用什么厨具,但真正决定菜好不好吃的是菜谱。

类比到Agent:编排决定先识别意图、再选工具、再执行、再生成回答。框架决定用LangGraph还是自己写循环。

Workflow vs Agent

这是编排中最核心的一对概念。但它们不是非此即彼,而是一个光谱的两端。

Workflow:流程确定,每一步预定义,LLM在固定节点做生成或判断。Anthropic的定义是”LLMs and tools are orchestrated through predefined code paths”。

Agent:流程不确定,LLM自己决定下一步做什么。Anthropic的定义是”LLMs dynamically direct their own processes and tool usage”。

# 光谱的左端:Workflow,流程完全确定
def workflow(user_input):
    intent = classify_intent(user_input)
    if intent == "query":
        result = query_database(user_input)
    else:
        result = search_knowledge(user_input)
    return format_response(result)

# 光谱的右端:Agent,流程完全不确定
def agent(user_input, tools):
    messages = [{"role": "user", "content": user_input}]
    while True:
        response = llm.chat(messages, tools=tools)
        if response.tool_calls:
            result = execute_tool(response.tool_calls[0])
            messages.append({"role": "tool", "content": result})
        else:
            return response.content

大多数生产系统在光谱中间:Workflow定义主流程,关键节点用Agent处理不确定性。先判断你的任务需要多大的灵活性,再决定用哪种。


二、七种编排模式

业界总结了几种核心编排模式。其中五种来自Anthropic的《Building Effective Agents》(2024年12月),另外两种来自学术界和工程实践。

全景

模式 来源 一句话 适合场景
Prompt Chaining Anthropic 步骤串联,前一步输出是后一步输入 流程确定的任务
Routing Anthropic 分类输入,导向不同处理逻辑 多意图混合场景
Parallelization Anthropic 多个LLM同时处理子任务,结果聚合 独立子任务可并行
ReAct Yao et al. 2022 思考→行动→观察,循环往复 流程不确定,需灵活调工具
Plan-and-Execute LangGraph 先规划再执行,失败时重新规划 复杂多步骤任务
Orchestrator-workers Anthropic 中央LLM动态拆解任务分配给worker 子任务不预定义
Evaluator-Optimizer Anthropic 一个生成,另一个评估,循环优化 对输出质量要求高

七种编排模式

这七种不是七选一。大多数生产系统是混合模式:主流程用Prompt Chaining或Routing,关键节点用ReAct,独立子任务用Parallelization并行,质量要求高的地方加Evaluator-Optimizer。

Anthropic的建议:从最简单的模式开始。 能用Prompt Chaining解决就用它,需要灵活调用工具再加ReAct。先用LLM API直接写代码理解原理,再考虑用框架。

下面展开讲三种最常用的模式。另外四种简要说明:

  • Parallelization(并行化):多个子任务同时执行,结果聚合。适合独立子任务可并行的场景,比如同时搜索多个数据源再合并结果。
  • Plan-and-Execute(规划执行):先让LLM生成完整计划,再逐步执行,失败时重新规划。适合复杂多步骤任务,比如”帮我做一份季度分析报告”。
  • Orchestrator-workers(编排者-工人):中央LLM动态拆解任务分配给worker,适合子任务不预定义的场景,比如”帮我调研这5个竞品”。
  • Evaluator-Optimizer(评估优化):一个LLM生成,另一个评估,循环优化直到满意。适合对输出质量要求高的场景,比如生成技术文档后自动审阅修订。

这四种模式各有适用场景,但在实际项目中,大多数系统用前三种(Chaining/Routing/ReAct)就能覆盖80%的需求。后四种是进阶选项,等你遇到具体问题再学不迟。

Prompt Chaining:步骤串联

最简单的编排方式。任务分解为序列步骤,每步处理前一步的输出。

举个例子,政策解读:

用户输入:"2024年产业园申报有什么要求?"
  → 步骤1:提取关键词("2024""产业园""申报""要求")
  → 步骤2:根据关键词搜索政策文档
  → 步骤3:根据文档内容生成解读
  → 步骤4:格式化输出
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate

llm = ChatOpenAI(model="deepseek-v4-flash")

extract_prompt = ChatPromptTemplate.from_template(
    "从以下问题中提取搜索关键词,返回JSON数组:\n{question}"
)

answer_prompt = ChatPromptTemplate.from_template(
    "根据以下政策文档,回答用户问题。引用文档时标注来源。\n"
    "文档:{docs}\n问题:{question}"
)

def chain(question: str) -> str:
    keywords = (extract_prompt | llm).invoke({"question": question})
    docs = search_docs(parse_keywords(keywords))
    answer = (answer_prompt | llm).invoke({"docs": docs, "question": question})
    return answer

Anthropic特别提到,可以在步骤之间加”门控”(programmatic checks),比如检查中间结果是否符合预期格式,不符合就重试。这比让LLM自己判断”上一步对不对”可靠得多。

Routing:路由分发

分类输入,导向专门的后续处理。Anthropic的原话是”Without this workflow, optimizing for one kind of input can hurt performance on other inputs”,不分类,优化一种输入就会伤害另一种。

VALID_ROUTES = {"knowledge", "data", "chat", "report"}

def route(user_input: str) -> str:
    prompt = f"""判断用户意图,返回一个词:
- knowledge:知识问答(问政策、问规定)
- data:数据查询(问数字、问统计)
- report:报告生成(要求生成分析)
- chat:闲聊

用户输入:{user_input}
只返回一个词,不要解释。"""
    intent = llm.invoke(prompt).content.strip()
    return intent if intent in VALID_ROUTES else "chat"

def handle(user_input: str) -> str:
    target = route(user_input)
    handlers = {
        "knowledge": knowledge_graph,
        "data": data_graph,
        "report": report_graph,
        "chat": chat_graph
    }
    return handlers.get(target, chat_graph).invoke(user_input)

关键设计:分类器要轻量(用小模型、简单Prompt),分类结果要记录(方便分析路由准确率),要有默认路由(分类失败时的兜底)。

ReAct:工具循环

来源:Yao et al.的论文《ReAct: Synergizing Reasoning and Acting in Language Models》(arXiv:2210.03629,ICLR 2023)。

LLM交替生成推理痕迹(Thought)和执行动作(Action),根据环境反馈(Observation)调整后续行为。

用户:"帮我查一下山东省2024年产业园的申报情况"
思考:需要先搜索产业园列表
行动:search_park(region="山东", year=2024)
观察:找到12个产业园
思考:用户可能想看汇总
行动:analyze_park_status(parks=[...])
观察:8个已申报,3个待申报,1个未开始
生成回答
def react(user_input: str, tools: list, max_iterations: int = 10) -> str:
    messages = [
        {"role": "system", "content": "你是一个数据查询助手。"},
        {"role": "user", "content": user_input}
    ]

    for i in range(max_iterations):
        response = llm.chat(messages, tools=tools)

        if not response.tool_calls:
            return response.content

        for call in response.tool_calls:
            result = execute_tool(call.name, call.arguments)
            messages.append({
                "role": "tool",
                "tool_call_id": call.id,
                "content": json.dumps(result, ensure_ascii=False)
            })

        # 防止上下文溢出
        if count_tokens(messages) > MAX_CONTEXT_TOKENS * 0.7:
            messages = truncate_messages(messages, keep_recent=6)

    return "抱歉,处理过程超过限制,请简化您的问题。"

ReAct的局限性:每步都需要完整的LLM调用,延迟和成本随步骤数线性增长;推理链越长,后续步骤越容易放大前面的错误。研究还发现(arXiv:2604.02155),更多推理不等于更好的推理。在小模型上,简短推理链比长推理链准确率高45%。


三、生产环境绕不开的问题

Demo阶段不会遇到的问题,上线后一个接一个。

3.1 会话与并发

Demo阶段会话存在内存里,刷新就没了。生产环境会话要持久化,支持恢复。同时还要处理并发。用户发了一个问题,Agent正在处理,用户又发了一个新问题,两个请求同时修改会话状态,结果互相覆盖。

import asyncio

# 会话快照:每个请求结束后保存状态
@app.post("/chat")
async def chat(request: ChatRequest):
    session = session_manager.get_or_create(request.session_id)
    messages = session.get_messages()

    # 获取会话级锁,防止并发
    lock = session_locks.get_lock(request.session_id)
    if lock.locked():
        return sse_event("error", "上一个问题还在处理中,请稍等")

    try:
        acquired = await asyncio.wait_for(lock.acquire(), timeout=30)
        if not acquired:
            return sse_event("error", "获取锁超时,请重试")
        try:
            result = await run_agent(request.message, messages)
            session.save_turn({
                "user_message": request.message,
                "tool_calls": result.tool_calls,
                "assistant_message": result.content,
                "timestamp": now()
            })
            return sse_stream(result)
        finally:
            lock.release()
    except asyncio.TimeoutError:
        return sse_event("error", "获取锁超时,请重试")

不只是保存消息,还要保存工具调用记录、PlanTrace、用户反馈。这些数据是后续排查问题和优化的基础。

3.2 取消机制:用户等不及了点取消

Agent调了3个工具,用户等了10秒不耐烦了,点了取消。取消是协作式的:设置取消标志,当前节点主动检查。

class CancelFlag:
    def __init__(self):
        self._flags: dict[str, bool] = {}

    def set(self, request_id: str):
        self._flags[request_id] = True

    def is_cancelled(self, request_id: str) -> bool:
        return self._flags.get(request_id, False)

# 每个节点检查取消标志
async def think_node(state: AgentState):
    if cancel_flag.is_cancelled(state["request_id"]):
        return {"status": "cancelled", "message": "用户取消了请求"}
    response = await llm.achat(state["messages"])
    return {"messages": [response]}

取消后会话状态保留,用户可以继续新的对话,不会丢失之前的上下文。

3.3 错误处理:不能所有错误同一种处理

Demo阶段try-catch一切,出错就报”系统错误”。生产环境需要两类错误分别处理。

基础设施错误:超时、限流、权限,四级分类:

async def execute_with_retry(fn, *args, max_retries=3):
    for attempt in range(max_retries):
        try:
            return await fn(*args)
        except TimeoutError:
            if attempt < max_retries - 1:
                await asyncio.sleep(2 ** attempt)
                continue
            raise
        except RateLimitError:
            return await fn.with_model("deepseek-v4-flash")(*args)
        except PermissionError:
            return "抱歉,您没有权限执行此操作"
        except Exception as e:
            if "token" in str(e).lower() and "limit" in str(e).lower():
                return await fn.with_truncated_history()(*args)
            raise
    return "处理过程遇到问题,请简化您的问题后重试"
错误类型 处理策略 例子
可重试 指数退避重试 LLM超时、网络抖动
可降级 换策略 LLM拒绝→改写Prompt、Token超限→截断历史
不可重试 直接告知用户 权限不够、数据不存在
强制停止 最大迭代限制 死循环、Token爆炸

Agent特有错误,比基础设施错误更难排查,因为它们不会抛异常,而是”静默失败”:

Agent错误分类

错误类型 表现 检测方法
工具幻觉 模型编造了不存在的工具名或参数 对比tools_selected和实际工具列表
工具误选 选了错的工具但没报错 PlanTrace里看工具选择和用户意图是否匹配
推理错误 逻辑链断了,后续步骤全部错 检查中间结果是否符合预期格式/范围
错误累积 前一步的错在后续步骤中放大 对比每步的输出质量,发现退化趋势

一个真实场景:用户问”帮我查山东产业园的数据”,Agent选了search_policy工具而不是query_data。没有报错,返回了一堆政策文档,用户以为数据查完了。这种错误只有看PlanTrace才能发现。

研究表明(arXiv:2510.07248),工具幻觉的一个主要来源是”schema misalignment”,模型在预训练时记住了某些命名习惯,和你定义的工具名冲突。解决方案之一是让工具名更符合模型的预训练分布(比如用search_documents而不是doc_qry)。

研究还发现(arXiv:2604.02155),推理错误和推理长度是非单调关系。更多推理不等于更好的推理。在小模型上,简短推理链比长推理链准确率高45%。过长的推理链中,28%是选错了工具,18%是编造了工具。

这些Agent特有错误不会出现在try-catch里,只能通过PlanTrace和评测集来发现。

3.4 成本控制:一个简单问题调了8次LLM

用户问”销售额怎么样”,Agent不知道”怎么样”什么意思,于是自己决定查趋势、查同比、查分区域、查异常。一个简单问题,调了8次LLM,token消耗5000+。

几个控制策略

# 1. 简单问题用Workflow,不用Agent Loop
def handle_simple_query(message: str) -> str:
    intent = classify_intent(message)
    if intent in ["greeting", "simple_query"]:
        return direct_answer(message)
    return agent_loop(message)

# 2. 分级模型:简单任务用小模型
def get_model_for_task(complexity: str):
    models = {
        "simple": "deepseek-v4-flash",      # 便宜
        "medium": "deepseek-v4-pro",         # 中等
        "complex": "deepseek-v4-reasoning"   # 贵但强
    }
    return ChatOpenAI(model=models.get(complexity, models["simple"]))

# 3. 对话历史压缩
def compress_history(messages: list, max_tokens: int = 4000):
    if count_tokens(messages) <= max_tokens:
        return messages
    system_msgs = [m for m in messages if m["role"] == "system"]
    recent = messages[-6:]  # 最近3轮
    middle = messages[len(system_msgs):-6]
    if middle:
        summary = llm.invoke(f"总结以下对话的要点:{middle}").content
        return system_msgs + [{"role": "system", "content": f"历史摘要:{summary}"}] + recent
    return system_msgs + recent

成本控制要从设计开始,不是上线后发现账单太高再改。


四、几条编排经验

工具设计决定Agent效果

Anthropic的经验:工具说明书的质量直接影响Agent的效果。很多时候Agent效果不好,不是模型不行,是工具定义有问题。

工具说明书四段式

search_policy:
  name: search_policy
  description: 搜索政策文档
  parameters:
    query:
      type: string
      required: true
      description: 搜索关键词
    region:
      type: string
      required: false
      description: 地区筛选
  returns: |
    {"results": [{"title": "标题", "content": "内容", "source": "来源"}]}

设计原则:

  • 一件事一个工具:不要一个工具做太多事,Agent会选错
  • 参数明确:类型、必填、校验规则都要定义,参数说明不清Agent会传错
  • 返回结构化:返回JSON,不要返回自然语言
  • 幂等:同一请求重放多次,副作用只生效一次

Demo阶段用硬编码规则选工具:

# 硬编码规则:扩展难、回归难
if "政策" in query or "文件" in query:
    use search_policy()
elif "数据" in query or "统计" in query:
    use query_data()

生产环境用工具说明书,让LLM自己选:

# 新增工具只需加说明书
tools = [
    Tool(name="search_policy", description="搜索政策文档", ...),
    Tool(name="query_data", description="查询统计数据", ...),
]

好处是新增工具不需要改规则,LLM根据说明书自己判断。选错了可以查PlanTrace,反过来优化说明书。

意图消歧常被低估

用户问”销售额怎么样”,至少有5种理解:要趋势?要同比?要分区域?要异常检测?要排名?

换一个更好的模型不一定能解决这个问题。研究表明,工具选择失败(本质上是意图理解失败)占Agent全部失败的30%以上(arXiv:2604.02155),而通过优化工具命名和描述(不换模型)就能减少80%的工具幻觉(arXiv:2510.07248)。

四种策略:

策略 做法 适合场景
不追问 用最常见的理解直接回答 简单问题、容错率高
给选项 给2-3个选项让用户选 关键决策
先粗后细 先给粗略回答,再追问细化 探索性问题
历史推断 根据对话历史推断意图 多轮对话

追问太多用户烦,追问太少答非所问。平衡点是:简单问题不追问,关键决策给选项,探索性问题先粗后细。

一个实操建议:把意图消歧的策略写进System Prompt。比如”当用户问题有多种理解时,先给最常见的一种回答,然后列出其他可能的理解让用户选择”。这比换一个更大的模型便宜得多。

可观测是迭代的基础

Agent出了问题,不知道哪里出的、为什么出的,就没法优化。

最小可观测方案:记录每一问的完整PlanTrace。

plan_trace = {
    "request_id": "req-2026-07-21-001",
    "user_query": "上个月销售额多少",
    "intent": "data_query",
    "tools_selected": ["query_sales"],
    "tool_calls": [
        {
            "tool": "query_sales",
            "args": {"period": "last_month"},
            "result": {"total": 1234567},
            "latency_ms": 230,
            "error": None
        }
    ],
    "final_answer": "上个月销售额为123.46万元",
    "token_usage": {"input": 1200, "output": 350},
    "total_latency_ms": 1850
}

有了PlanTrace,排查问题就容易了:

现象 看PlanTrace哪个字段
选错工具 tools_selected 和用户意图是否匹配
工具返回空 tool_calls.result
回答不对 final_answer 和 tool_calls 的结果对比
响应太慢 tool_calls[].latency_ms 找到慢的那步
成本太高 token_usage 看哪步消耗最多

PlanTrace不只是调试工具,更是迭代的基础。收集线上PlanTrace,按意图分类,找到高频失败模式,针对性优化工具说明书或Prompt。这是Agent持续改进的闭环。


从哪里开始

看完这些,第一步做什么?

选一个核心业务场景,用Prompt Chaining写最小版本。 不要一上来就搞多Agent、Evaluator-Optimizer这些复杂模式。找一个你最熟悉的业务场景(比如政策查询、数据统计),用最简单的串联流程跑通。

加上PlanTrace。 从第一版就记录每一问的工具调用、输入输出、耗时。这不只是调试工具,更是后续优化的基础。没有PlanTrace,你永远不知道Agent为什么出错。

用评测集验证。 准备10-20个典型问题和期望答案,每次改Prompt或工具后跑一遍。不需要复杂的评测框架,一个脚本够了。核心是建立”改了什么→效果变好还是变差”的反馈循环。

遇到具体问题再加模式。 意图分不清加Routing,子任务可并行加Parallelization,输出质量不稳定加Evaluator-Optimizer。每加一个模式,都要有明确的问题驱动,不要为了”架构好看”而加。


总结

从”选框架”到”理解模式”。 框架会变,但编排模式不会变。七种核心模式不依赖任何特定框架。Anthropic说得对:最成功的实现不用复杂框架,而是用简单、可组合的模式。

从”Demo能跑”到”生产能用”。 会话管理、并发控制、错误分级、成本控制,这些Demo阶段不会遇到的问题,才是Agent能不能上线的关键。Agent特有的错误(工具幻觉、推理错误、错误累积)比基础设施错误更难排查,需要通过PlanTrace和评测集来发现。

每个人的情况不同,以上是根据实际项目总结的一些理解,不是标准答案。关键是找到适合自己团队的方式。欢迎评论区交流~


相关关键词:AI Agent编排、LangGraph、LangChain、Agent框架对比、生产级Agent、ReAct模式、Agent开发实战

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐