刚接触大模型应用开发的读者,往往会对 Agent 类项目产生一种错觉:只要把模型 API 接入进来,再给它几个提示词,就能得到一个会自己思考、自己执行任务的智能体。真正动手搭建 AI Agent 系统时才会发现,问题远不止“调用模型”这么简单。
我近期梳理了大量生产级 Agent 项目后,发现尽管不同团队使用的框架、模型和场景千差万别,但人们构建 AI Agent 系统的方式基本可以归纳为三种:工作流驱动的固定编排、单智能体自主循环、多智能体协作。三种方式没有绝对的优劣,区别在于你希望把多少控制权交给模型,以及你愿意为“自主性”付出多少稳定性成本。
本文会从原理开始,逐个拆解三种构建方式,并配合可运行的 Python 示例,方便你在自己的项目中对照选型。
1. 背景:我们常说的 Agent,到底是什么
如果你只把 Agent 理解成“一个大模型聊天机器人”,后面讨论构建方式就很容易跑偏。AI Agent 系统的核心特征是:模型不再只完成一次“输入文本 -> 输出文本”的问答,而是作为一个决策大脑,通过行动、观察结果、再决策的方式去完成一个复杂目标。
举个例子,一个“客服 Agent”需要做到:
- 理解用户问题属于咨询、售后还是投诉;
- 根据问题类型选择对应话术模板;
- 判断是自动回复还是转人工;
- 调取订单接口查询用户订单状态;
- 根据查询结果生成下一步回复。
在这个过程里,模型可能需要输出多次内容,也可能需要调用外部工具,还可能因为中途信息不足而重新规划。把这些环节组织起来的方法,就是 Agent 系统的构建方式。
由此可以看出,Agent 的本质不是“更聪明的模型”,而是“更聪明的系统结构”。模型负责语言理解和决策,系统结构负责流程控制、工具调用、状态管理和纠错。
理解了这一点,再看市面上的 Agent 框架,就会明白它们本质上都是在帮你实现某一种构建方式。
2. 构建 AI Agent 系统前必须理解的四个基础概念
在进入三种构建方式之前,先花一点时间统一四个基础概念,后面看代码会轻松很多。
2.1 工具
工具(Tool)是 Agent 连接外部世界的手段。比如查询天气、调用搜索接口、操作数据库、发送邮件等。从技术实现看,工具本质上是一个带名称、描述、参数约束的函数。模型本身不能直接执行代码,它通过理解工具描述,生成一次“调用请求”,再由系统层执行真正的外部函数。
2.2 模型调用
Agent 系统无论多复杂,底层依然是模型的输入输出。你可能在一次完整任务中多次调用模型:先让模型做计划,再让模型针对每一步做判断,最后让模型汇总结果。这些模型调用是否有序、是否可回退,决定了 Agent 系统的稳定性。
2.3 上下文
上下文是模型在一次对话中能看到的所有信息,包括用户输入、开发者设定、工具调用记录、工具返回结果,以及之前生成的内容。Agent 系统里的“记忆”大多就是通过把历史记录继续拼接到后续请求中实现的。上下文越长,模型越可能注意不到关键信息,消费的 token 也越多,因此必须做精心管理。
2.4 编排层
编排层是 Agent 系统的骨架,负责决定“什么时候调用模型”“模型输出后怎么处理”“是否需要执行工具”“整个流程何时结束”。你可以把编排层写成普通 Python 代码,也可以使用 LangGraph、AutoGen、CrewAI 这类专门框架,但底层逻辑是差别不大的。
之所以先强调这四个概念,是因为三种 Agent 构建方式在工具、模型调用、上下文、编排层上的侧重完全不同。
3. 方式一:通过工作流固定编排模型调用
第一种构建方式,是先用代码画好一条固定流程,再把模型作为流程里的“智能节点”嵌入。这种方式的特征是:流程完全由开发者控制,模型只在特定环节参与决策。
3.1 方式一的核心思路
很多误解认为 Agent 必须完全自主。实际上,业务中大量可用的 Agent 系统都是半自主的,开发者先用规则把任务拆分成确定的阶段,再在每个阶段用模型完成分类、抽取、生成等子任务。
这种构建方式的优势非常明显:
- 每一步做什么是确定的,便于测试;
- 流程边界清晰,模型输出不会使整个系统偏离目标;
- 单次模型调用上下文短,token 成本低、响应速度快;
- 可以在任意节点插入规则校验或人工审批,适合企业场景。
缺点是:改动流程需要改代码,灵活度不够;当任务种类非常多时,开发者几乎要把所有情况都写成分支,维护成本会上升。
下面我以一个简单的客服分流系统为例,演示这种构建方式。
3.2 客服分流系统的完整示例
先整理一个公共封装脚本,之后三种构建方式的代码都会复用它。
# llm.py import os from openai import OpenAI client = OpenAI( api_key=os.getenv("LLM_API_KEY"), # 如果你的模型服务商提供兼容地址,可以在这里配置 base_url=os.getenv("LLM_BASE_URL"), ) def chat(messages, tools=None): """基础模型调用封装。""" resp = client.chat.completions.create( model=os.getenv("LLM_MODEL", "gpt-4o-mini"), messages=messages, tools=tools, ) return resp.choices[0].message使用前,需要在环境变量中配置你的模型服务商密钥。实际项目中,更推荐用配置中心或者密钥管理服务保存密钥,不要硬编码在代码中。
然后编写客服分流系统:
# workflow_demo.py import json from llm import chat def classify_user_message(user_msg: str) -> str: """ 第一步:让模型给用户问题分类。 在这一步,模型只负责做一件事:分类。 """ messages = [ { "role": "system", "content": "你是客服系统的分类器。只输出 JSON,不要解释。" 'JSON 格式:{"category": "咨询|售后|投诉|其他"}', }, {"role": "user", "content": user_msg}, ] resp = chat(messages) text = resp.content.strip() # 兼容模型偶尔输出 ```json 包裹的情况 if text.startswith("```"): text = text.strip("`") if text.startswith("json"): text = text[4:] result = json.loads(text) return result["category"] def need_human(category: str, user_msg: str) -> bool: """ 第二步:使用规则判断是否需要转人工。 这里的判断不依赖模型,而是由开发者写死规则。 """ if category == "投诉": return True sensitive_words = ["退款失败", "人工", "投诉", "主管"] if any(word in user_msg for word in sensitive_words): return True return False def draft_reply(category: str, user_msg: str) -> str: """ 第三步:让模型根据分类结果生成回复。 由于分类已经确定,模型生成时上下文中只有对应的模板约束。 """ template_map = { "咨询": "你是一个售前咨询客服,回答要简洁清楚,并引导用户介绍具体需求。", "售后": "你是一个售后客服,回答要体现解决问题的步骤,涉及订单时请建议用户提供订单号。", "其他": "你是一个通用客服,请礼貌告知用户你已记录问题,并请用户补充必要信息。", } messages = [ {"role": "system", "content": template_map[category]}, {"role": "user", "content": user_msg}, ] resp = chat(messages) return resp.content.strip() def customer_service_flow(user_msg: str): """ 主流程:固定编排。 分类 -> 规则判断是否需要人工 -> 生成回复。 """ print(f"用户问题:{user_msg}") category = classify_user_message(user_msg) print(f"模型分类结果:{category}") if need_human(category, user_msg): print("处理结果:转人工客服处理") return reply = draft_reply(category, user_msg) print(f"自动回复:{reply}") if __name__ == "__main__": customer_service_flow("我买的东西迟迟不发货,想申请退款但是失败了,帮我转人工")运行这个脚本,它会启动一条客服流程:
用户问题:我买的东西迟迟不发货,想申请退款但是失败了,帮我转人工 模型分类结果:售后 处理结果:转人工客服处理为什么把流程拆成三次独立操作,而不是直接让模型输出最终答案?因为在真实业务中,分类结果会被写入日志用于统计,是否需要人工也必须由企业规则决定,不能完全让模型拍板。
这个示例非常有代表性。你会发现,模型在其中的作用被限制在“分类”和“基于固定上下文写回复”的小环节里,流程整体是确定可控的。这种编码方式仍然是今天企业落地 Agent 系统最常用的方式,不要因为听起来不够“智能”就小看它。
3.3 方式一的适用场景
工作流驱动的 Agent 适合以下情况:
- 业务流程基本确定,例如工单分类、客服接待、审批助手。
- 任务子步骤清晰,每个步骤都可以单独验收。
- 对出错容忍度低,需要在关键节点加入规则校验。
- 团队刚接触 Agent,希望先跑通一条最小链路。
如果你希望系统具备一定灵活性,可以在“分类”后用一张规则表或配置表来控制后续走向。相比把所有决策都交给模型,这种方式更容易追溯问题。
4. 方式二:单 Agent 自主循环
第二种构建方式,是做一个“感知-决策-行动”的循环:模型接收目标,自行判断需要调用哪些工具,根据工具结果决定下一步动作,直到它认为任务完成。
这种方式的流程图可以简单表示为:
用户输入 ↓ [模型推理] ←→ [如果模型请求调用工具,则执行工具并返回结果] ↓ 模型输出最终答案最关键的区别在于:方式一中决策链条由代码控制,方式二中由模型决定下一步执行什么操作。
4.1 单 Agent 自主循环的原理
这个思路在学术上接近 ReAct 模式。模型每一步能看到自己的历史思维和工具返回结果,可以连续多次调用不同工具。系统层需要完成三件事:
- 将全部工具通过函数描述传给模型;
- 解析模型返回的 tool_calls,执行实际函数;
- 把工具执行结果作为新的消息追加进对话上下文,再次交给模型判断。
这个循环会一直进行,直到模型返回一条“没有工具调用意图”的普通文本,代表它认为已经可以给出最终答案。
业界很多 Agent 框架里所谓的 Agent,底层就是这样一个循环。框架帮你封装了工具解析与消息拼接,核心逻辑并不难理解。
4.2 最小可运行示例:天气查询 Agent
这里实现一个非常简化的自助 Agent。为演示安全,天气数据使用本地预置字典,不发起真实外部请求。
# react_agent_demo.py import json from llm import chat # 1. 定义工具函数与描述 TOOLS = [ { "type": "function", "function": { "name": "get_weather", "description": "查询中国部分城市的当前天气情况", "parameters": { "type": "object", "properties": { "city": { "type": "string", "description": "城市名,例如:北京", } }, "required": ["city"], }, }, } ] WEATHER_DB = { "北京": "晴,气温 13°C,北风 2 级", "上海": "小雨,气温 17°C,东风 3 级", "深圳": "多云,气温 22°C,南风 2 级", } def execute_tool(name: str, arguments: str) -> str: """执行工具。这里只内置了两个简单函数作为演示。""" args = json.loads(arguments) if name == "get_weather": city = args["city"] weather = WEATHER_DB.get(city, "暂无该城市数据") return json.dumps({"city": city, "weather": weather}, ensure_ascii=False) raise ValueError(f"未知工具: {name}") def run_agent(user_input: str, max_steps: int = 5) -> str: messages = [ { "role": "system", "content": "你是一个智能助手,可以根据用户问题调用工具。当你知道答案后,直接告诉用户。", }, {"role": "user", "content": user_input}, ] for step in range(max_steps): print(f"--- 第 {step + 1} 次模型调用 ---") resp = chat(messages, tools=TOOLS) # 将模型消息加入上下文 messages.append(resp) # 模型没有请求调用工具,说明任务完成,直接返回内容 if not resp.tool_calls: return resp.content.strip() # 如果模型请求调用工具,则逐个执行并把结果加入上下文 for tool_call in resp.tool_calls: func_name = tool_call.function.name func_args = tool_call.function.arguments print(f"模型请求调用工具:{func_name},参数:{func_args}") result = execute_tool(func_name, func_args) messages.append( { "role": "tool", "tool_call_id": tool_call.id, "content": result, } ) return "已达到最大调用轮次,请稍后再试。" if __name__ == "__main__": answer = run_agent("北京今天天气怎么样?上海呢?") print(f"最终回答:{answer}")这个 Agent 会在内部执行多次工具调用,模型发现用户同时问两个城市时,往往会连续调用两次get_weather,拿到结果后再汇总输出。
这里的核心在于,模型自己决定调用哪个工具、什么时候结束。系统层只能提供工具,无法预先写死工具调用顺序。这种动态行为就是“自主性”的来源。
4.3 阻塞机制和安全性
自主循环虽然灵活,但容易带来三个工程问题:
- 循环无法停止:模型可能会反复调用同一个工具,因此必须设置
max_steps硬性上限。 - 工具参数不可控:模型可能把用户输入里的恶意内容填入参数,例如“删除所有订单”,如果工具本身没有做权限校验就会出问题。这就要求工具实现时必须做参数校验,而不是信任模型生成的内容。
- token 消耗无法预估:模型每次思考都会把之前的完整消息再次发送,一个稍复杂的问题可能消耗数千甚至上万 token,需要在产品层做预算控制。
单 Agent 自主循环适合工具数量不多、任务边界比较开放、需要模型自动探索工具组合的场景。例如自然语言查询数据报表、根据用户描述创建日程、连接多个内部 API 完成工作流。
5. 方式三:多智能体协作系统
当任务规模变大,单个 Agent 需要掌握的知识、工具和上下文越来越多,很容易出现上下文超长、决策混乱、工具冲突等问题。这时就轮到第三种构建方式登场:拆成多个各司其职的 Agent,由它们协作完成同一个目标。
5.1 Plan-and-Execute 多智能体协作模式
多智能体协作有很多种组织形态。常见的有:
- Plan-and-Execute:先由一个规划 Agent 将任务拆解成执行步骤,再由执行 Agent 逐步实现。
- Manager/Worker:主管 Agent 负责任务拆分和结果验收,工作 Agent 负责具体执行。
- Peer Network:多个专业 Agent 相互对话和辩论,这种方式偏研究性质,生产落地难度较大。
这里重点演示 Plan-and-Execute,因为它最容易理解,也最容易在企业项目中落地。
5.2 Plan-and-Execute 完整示例
假设我们需要一个“项目调研 Agent”,用户提出一个调研主题后,由一个规划 Agent 拆解出调研步骤,然后每步交给一个执行 Agent 生成内容,最后汇总 Agent 整理成一份报告。
# multi_agent_demo.py import json from llm import chat def parse_json(text: str) -> dict: """解析模型返回的 JSON,兼容代码块包裹。""" if text.startswith("```"): text = text.strip("`") if text.startswith("json"): text = text[4:] return json.loads(text) def planner_agent(task: str) -> list: """规划 Agent:将任务拆解为不超过 5 个可执行的子任务。""" messages = [ { "role": "system", "content": "你是项目负责人,负责将复杂任务拆解为清晰的执行步骤。" '只输出 JSON 数组,数组元素为字符串,例如:["调研背景", "分析竞品", "总结建议"]', }, {"role": "user", "content": f"请将以下任务拆解为执行步骤:{task}"}, ] resp = chat(messages) steps = parse_json(resp.content) return steps def executor_agent(step: str, context: str) -> str: """执行 Agent:针对某一步骤生成详细内容。""" messages = [ { "role": "system", "content": "你是专业领域研究员。你需要根据任务步骤输出内容全面、结论具体的调研报告章节。", }, { "role": "user", "content": f"整体任务背景:{context}\n当前需要完成的步骤:{step}", }, ] resp = chat(messages) return resp.content.strip() def final_report_agent(task: str, sections: list) -> str: """汇总 Agent:将多个执行结果整理成完整报告。""" joined_sections = "\n\n".join( [f"## 章节:{s['step']}\n{s['content']}" for s in sections] ) messages = [ { "role": "system", "content": "你是资深主编,负责将多个章节整理为一份逻辑通顺、层级分明的报告," "不要新增不存在的结论。", }, { "role": "user", "content": f"原始调研任务:{task}\n\n各步骤执行结果如下:\n{joined_sections}", }, ] resp = chat(messages) return resp.content.strip() def run_multi_agent_task(task: str): print("=== 规划 Agent 开始拆解任务 ===") steps = planner_agent(task) print(f"拆解后的步骤:{steps}") context = task sections = [] for i, step in enumerate(steps, start=1): print(f"=== 执行 Agent 正在完成任务 {i}/{len(steps)}:{step} ===") section_content = executor_agent(step, context) sections.append({"step": step, "content": section_content}) print("=== 汇总 Agent 正在生成最终报告 ===") report = final_report_agent(task, sections) return report if __name__ == "__main__": result = run_multi_agent_task("围绕快消行业售后服务 AI 助手做一份竞品调研") print(result)这个代码展示了一个典型的多 Agent 编排过程:先拆解、再并行或顺序执行、最后汇总。每一步的上下文都被限制在局部,每个 Agent 只关心自己负责的那部分,避免了单个大模型上下文过载。
5.3 多智能体协作的注意点
多 Agent 系统看起来更“高级”,但工程复杂度和成本会成倍上升。
首先是多次模型调用带来的成本与延迟。一个 5 步任务可能需要 7 次以上的模型调用,稍有不慎就会超时或超预算。
其次是错误传播问题。规划 Agent 如果拆错步骤,执行 Agent 再努力也会跟着错。因此多 Agent 系统需要在一开始就加入“规划校验”或“人工确认”环节。
最后是上下文隔离与传递。每个子 Agent 执行结果要经过统一格式化的消息进入汇总 Agent。如果不做格式化,很容易出现某个步骤信息丢失,最终报告缺内容。
多 Agent 协作适合的任务包括:研究报告自动生成、代码多文件项目生成、需要多方知识配合的复杂客服系统,以及企业内部流程自动化。
6. 三种构建方式对比与选型
三种方式不是互斥的,很多真实系统本身就会混用:先用工作流搭建主干流程,在某个复杂环节里放入一个自主循环 Agent,多个自主 Agent 再组成协作网络。但从架构规划角度,先明确主要用哪种方式,能大幅降低开发风险。
下面用一张表格来对比三种方式:
| 对比维度 | 工作流驱动 | 单 Agent 自主循环 | 多智能体协作 |
|---|---|---|---|
| 可控性 | 高,步骤由代码定义 | 中,模型决定内部步骤 | 中低,需要额外设计协作协议 |
| 开发复杂度 | 低,适合从零起步 | 中,需要处理工具循环 | 高,需要处理多角色通信 |
| Token 开销 | 低 | 中 | 高 |
| 调试难度 | 低,可逐步打断点 | 中,需要记录工具调用链 | 高,需要追踪每个 Agent |
| 灵活性 | 低 | 中 | 高 |
| 业务落地速度 | 最快 | 较快 | 较慢 |
| 典型任务 | 客服分流、工单审批 | 单工具组复杂任务 | 报告生成、复杂项目拆解 |
如何选型?我建议按以下顺序思考:
- 业务步骤是否能在需求阶段全部列清楚?如果能,优先选择工作流驱动。
- 业务中是否存在模型自行决定工具顺序才能解决的任务?如果是,引入单 Agent 循环。
- 任务是否大到需要并行处理或不同专业分工?这时才考虑多 Agent 协作。
- 团队如果刚接触 Agent,不要直接从多 Agent 开始,先用方式一跑通一条链路,再逐步向自主化演进。
记住一点:可控性是 Agent 系统进入生产的关键指标。一个偶尔聪明但难以排查的系统,在业务中很难长期存活。
7. 常见问题与排查思路
无论你选择哪一种构建方式,在开发和测试阶段都会遇到类似问题。下面列举几个高频坑位:
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 模型返回内容不是合法 JSON | Prompt 没有约束输出格式;模型答非所问 | 在 System Prompt 中强调只输出 JSON;增加解析容错;考虑使用结构化输出接口 |
| Agent 陷入工具调用死循环 | 没有设置最大步数;工具结果无法满足模型判断“完成”的条件 | 必须设置 max_steps;检查工具返回信息是否清楚 |
| 工具返回结果没有进入上下文 | 调用工具后漏掉 role=tool 的消息拼接 | 检查代码中是否将工具结果通过 tool_call_id 关联到原消息 |
| 上下文越来越长导致费用失控 | 每轮循环携带全部历史消息 | 设置上下文窗口;对工具结果做摘要;任务完成后清理无关内容 |
| 多 Agent 汇总时信息丢失 | 子 Agent 输出过长,汇总 Agent 抓不住重点 | 在汇总前对章节内容做摘要;要求执行 Agent 按固定 markdown 结构输出 |
| 模型执行了危险工具 | 工具列表放得太宽,缺少权限校验 | 按最小权限原则开放工具;在工具函数内部做二次鉴权 |
排查 Agent 问题时,最重要的习惯就是记录完整链路。把每次模型输入、输出、工具调用参数、工具返回结果都打印成结构化日志。不要只在出错时打印,否则很难定位是模型问题、工具问题还是上下文问题。
8. AI Agent 系统工程化落地的几个实践建议
掌握三种构建方式之后,下一步是把它们真正落地到工程里。以下是我在实际项目中比较关注的经验,这里作为通用工程建议分享给你。
8.1 将 Agent 编排和业务逻辑分离
不要在一个 Python 文件里既写 Agent 循环,又写业务规则。建议把代码拆成三层:
- 模型调用层:封装 chat、工具调用等 API;
- Agent 编排层:管理循环、流程状态、Agent 间通信;
- 业务层:真正的工具实现,包含权限校验和业务规则。
这样无论后续切换模型服务商还是调整编排方式,改动范围都会小很多。
8.2 给每个 Agent 配置独立的 System Prompt
在多 Agent 系统中,每个 Agent 的职责必须独立描述,不要让一个长 Prompt 承担所有角色。要让每个 Agent 知道:你是谁、你要完成什么任务、输入是什么格式、输出必须是什么格式、遇到异常怎么处理。
8.3 优先使用结构化输出
模型生成自由文本虽然方便,但在 Agent 编排中非常容易被下游解析失败。无论是分类、规划还是计划结果,都尽量要求模型按 JSON 输出,并对 JSON 做解析兜底。如果模型服务商支持结构化输出功能,可以优先开启。
8.4 最小权限地开放工具
只向模型暴露当前任务必需的工具。工具函数内部也应该再次校验参数,比如城市必填、用户必须登录、订单必须属于当前用户。不要信任模型生成的参数。
8.5 建立 AI Agent 的可观测性
每个 Agent 系统都建议加入 trace 日志,至少要记录以下内容:
- 每轮模型请求的消息体大小;
- 工具调用的输入和输出摘要;
- 总耗时、总 token 数;
- Agent 的最终结论来自哪几步。
当线上出现“AI 乱答”时,能快速回放完整执行记录,比让模型复述自己的行为可靠得多。
8.6 从最小闭环开始迭代
如果你今天就要开始设计 AI Agent 系统,我的建议非常明确:不要一上来就搭建华丽的多 Agent 平台,而是从方式一搭建一条包含两个模型节点的最小闭环。比如“用户输入 -> 模型分类 -> 规则判断 -> 模型生成结果”。把这个闭环跑稳定,加入日志和校验,再逐步引入工具调用和自主循环。
9. 总结
梳理一下,人们对 AI Agent 系统的构建方式可以如此理解:方式是工作流驱动的、确定性较强的编排;第二种是让模型在循环中自主选择工具的 Agent 模式;第三种则是把复杂任务拆解给多个 Agent 协同的网状结构。三者分别代表了可控、灵活和规模三种不同的追求。
从工程落地看,工作流驱动构建方式最容易被业务团队接受,单 Agent 自主循环适合需要灵活调用工具的场景,而多 Agent 协作则更适合复杂研究型任务或企业内部大型自动化流程。在实际选型中,没有最好的架构,只有当前业务阶段最适合的架构。
如果你正在准备做一个 AI Agent 相关的产品,建议先把三种方式的核心代码都在本地跑一遍。当你亲手调试过工具调用循环,亲眼看到多 Agent 汇总时的信息损耗,未来的架构判断会踏实很多。后续如果遇到具体的 Agent 工程问题,也欢迎继续交流。