☰
Agent SDK 实战:从业务拆解到工作流编排的落地指南
2026/10/1 12:47:39 网站建设 项目流程

1. 从"能聊"到"能干活":Agent SDK 到底解决了什么

很多人第一次接触 Agent 这个概念,都是从聊天机器人开始的。你问它一句,它回你一句,看起来挺聪明,但真到了业务场景里就露馅了——它没法查你的数据库,没法调你的接口,没法在任务失败时重试,更没法把"用户发来一张发票图片"这件事自动走完"识别→校验→录入→通知财务"的完整链路。这就是"能聊"和"能干活"之间的鸿沟。

Agent SDK 这类工具要解决的核心问题,就是把大模型的推理能力和真实世界的工具调用能力缝合在一起。它给模型一套"手脚":可以调用函数、访问外部数据、执行多步决策,并且在每一步之后根据结果决定下一步做什么。OpenAI Agents SDK 和 LangGraph 是当前两条主流路线,前者偏向轻量、开箱即用,后者偏向图结构、可控性强。选哪个不是重点,重点是你要理解它们共同的那套心智模型:Agent = 模型 + 工具 + 循环 + 状态。

我见过太多团队在这个阶段踩坑。他们兴冲冲地接了一个 SDK,写了个 demo,发现"哇,能自动查天气了",然后就想直接上生产。结果一遇到真实业务的多分支、多轮次、需要人工介入的场景,整个流程就崩了。原因很简单:demo 是线性的,业务是网状的。所以这篇文章不打算给你灌一堆概念,而是从"怎么把一个真实业务场景拆成 Agent 能执行的工作流"这个角度,把整条链路讲透。

这篇文章适合谁?如果你已经会写 Python,能看懂函数和类,但对"怎么让 AI 真正下地干活"还没找到抓手,那这篇就是写给你的。如果你已经在用 LangChain 但觉得链路太死板,想升级到更灵活的 Agent 编排,也能从里面找到可复用的思路。我会尽量把每一步的"为什么"讲清楚,而不是只丢一段代码让你抄。

2. 业务场景拆解:先想清楚"哪些步骤该交给模型"

2.1 判断一个场景适不适合 Agent 化

不是所有业务都值得做成 Agent。我的经验是,用三个问题快速筛一遍:

  • 这个流程里有没有"需要理解自然语言或非结构化信息"的环节?比如从一段客户描述里提取意图、从一张截图里读出关键字段。如果有,Agent 有优势;如果全是结构化数据的固定计算,那用普通脚本更稳更便宜。
  • 这个流程的步骤数是不是超过三步,且步骤之间有依赖?单步任务直接调一次模型就行,没必要上 Agent 框架。三步以上、后一步依赖前一步结果的,才值得编排。
  • 失败之后能不能自动重试或降级?如果每一步失败都必须人工介入,那 Agent 的价值会被大幅削弱,因为它的核心优势就是自主决策和自愈。

举个具体例子。假设你要做一个"客户询价自动响应"的流程:客户发来一段文字描述需求,系统要识别产品类别、查询库存和价格、生成报价单、发邮件给客户。这里面"识别产品类别"和"生成报价单文案"适合交给模型,"查询库存价格"适合做成工具函数,"发邮件"是确定性动作。这就是一个典型的混合流程,非常适合 Agent 化。

2.2 把流程画成"节点 + 边"再动手写代码

在写任何代码之前,我强烈建议你先在纸上或者白板上把流程画成图。每个"节点"是一个动作,每条"边"是动作之间的流转条件。这一步看起来土,但能帮你省下大量返工。

以询价流程为例,节点大致是:

  1. 接收客户消息(入口)
  2. 意图识别与信息抽取(模型节点)
  3. 判断信息是否完整(条件边)
  4. 查询库存与价格(工具节点)
  5. 生成报价文案(模型节点)
  6. 发送邮件(工具节点)
  7. 记录日志并结束(出口)

其中第 3 步会产生两条边:信息完整就走第 4 步,不完整就回到第 2 步追问客户。这个"回环"就是 Agent 和普通脚本最大的区别——它能根据状态决定往回走还是往前走。

提示:画图的时候一定要把"异常出口"也画出来。比如查询库存接口超时了怎么办?是重试三次还是直接转人工?这些分支如果不提前想清楚,代码写到一半会非常痛苦。

2.3 定义清楚每个节点的输入输出契约

这是最容易被忽略、但后期最要命的一步。每个节点的输入是什么、输出是什么、输出用什么数据结构,必须提前定死。因为 Agent 框架在节点之间传递的是状态对象,如果契约不清晰,模型很容易在中间步骤"自由发挥",导致下游节点拿到一堆没法解析的文本。

我的做法是给每个节点定义一个明确的返回结构,能用结构化输出就用结构化输出。比如意图识别节点,不要让它返回一段话,而是返回一个 JSON:

{ "intent": "inquiry", "product_category": "industrial_pump", "quantity": 20, "missing_fields": ["delivery_date"] }

这样下游节点可以直接读字段,而不是去猜模型这段话什么意思。OpenAI Agents SDK 支持结构化输出,LangGraph 里也可以用 Pydantic 模型约束状态,两者都能做到。

3. 环境搭建:Python 侧最容易翻车的几个地方

3.1 Python 版本与虚拟环境的选择

Agent 相关的库更新非常快,很多新特性只在较新的 Python 版本上可用。我的建议是直接用 Python 3.10 或 3.11,不要用 3.8、3.9 这种偏老的版本,因为部分依赖会要求 3.10+ 的类型语法。3.12 也可以,但偶尔会遇到某些库还没适配的情况,生产环境求稳的话 3.11 是甜点区。

虚拟环境一定要建,不要图省事装在全局。用 venv 就够了:

python -m venv .venv source .venv/bin/activate # Linux / macOS .venv\Scripts\activate # Windows

装完之后先升级 pip,这一步很多人跳过,结果装包时各种奇怪的编译错误:

python -m pip install --upgrade pip

3.2 依赖安装与常见报错

核心依赖通常包括 Agent 框架本身、模型调用 SDK、以及做数据校验的 pydantic。安装时最容易出问题的是版本冲突,尤其是 pydantic 的 v1 和 v2 不兼容。如果你项目里还用了别的老库,很可能出现"这个库要 pydantic v1,那个库要 v2"的情况。

我的处理原则是:新项目一律用 pydantic v2,遇到不兼容的老库就找替代品或者升级。装完之后用下面这行验证一下:

python -c "import pydantic; print(pydantic.VERSION)"

如果打印出来是 2.x 开头,就对了。另外,如果你在 Windows 上装某些带 C 扩展的库报错,多半是缺编译工具,装一个 Visual Studio Build Tools 基本能解决。Linux 上则通常是缺 python3-dev 和 build-essential。

3.3 API 密钥与配置管理

密钥千万不要硬编码在代码里。用环境变量或者 .env 文件管理,配合 python-dotenv 读取。一个典型的 .env 长这样:

MODEL_API_KEY=your_key_here MODEL_BASE_URL=https://your_endpoint LOG_LEVEL=INFO

然后在代码入口处加载:

from dotenv import load_dotenv import os load_dotenv() api_key = os.getenv("MODEL_API_KEY")

注意:.env 文件一定要加进 .gitignore,我见过不止一个团队把密钥提交到仓库里,后来不得不紧急轮换。这种低级错误一旦发生,排查起来很浪费时间。

4. 用 Agent SDK 编排工作流:从单 Agent 到多节点协作

4.1 单 Agent 模式的适用边界

最简单的 Agent 就是一个模型加几个工具,让它自己决定调哪个。OpenAI Agents SDK 里定义一个 Agent 大概是这样:

from agents import Agent, function_tool @function_tool def query_inventory(product_id: str) -> dict: """查询指定产品的库存和价格""" # 实际业务里这里会调数据库或内部接口 return {"product_id": product_id, "stock": 120, "price": 89.5} agent = Agent( name="询价助手", instructions="你负责处理客户询价,先抽取产品信息,再查询库存,最后生成报价。", tools=[query_inventory], )

这种模式适合步骤少、分支少的场景。一旦流程超过五六个步骤,或者需要严格的控制流(比如"必须先校验再查询"),单 Agent 就会变得不可控——它可能跳过校验直接查询,也可能在某个环节反复绕圈。这时候就得上图结构。

4.2 用 LangGraph 把流程显式建模成状态机

LangGraph 的核心思想是把 Agent 流程建模成一张有向图,每个节点是一个函数,边决定流转。它的好处是控制流完全由你掌握,模型只在需要它的节点里发挥作用。

先定义状态。状态是一个在节点间传递的字典,通常用 TypedDict 或 Pydantic 模型:

from typing import TypedDict, List class InquiryState(TypedDict): raw_message: str product_category: str quantity: int missing_fields: List[str] quote_text: str status: str

然后定义节点函数。每个节点接收状态、返回状态的更新部分:

def extract_info(state: InquiryState) -> dict: # 这里调用模型做信息抽取 result = call_model_for_extraction(state["raw_message"]) return { "product_category": result["category"], "quantity": result["quantity"], "missing_fields": result["missing"], } def check_completeness(state: InquiryState) -> str: if state["missing_fields"]: return "ask_more" return "query"

注意check_completeness返回的是字符串,这个字符串会被用作条件边的判断依据。这就是 LangGraph 里"条件边"的用法——根据当前状态决定下一步走哪个节点。

4.3 条件边与循环:让流程会"回头"

把节点和边组装成图:

from langgraph.graph import StateGraph, END graph = StateGraph(InquiryState) graph.add_node("extract", extract_info) graph.add_node("ask_more", ask_customer) graph.add_node("query", query_inventory_node) graph.add_node("quote", generate_quote) graph.add_node("send", send_email) graph.set_entry_point("extract") graph.add_conditional_edges("extract", check_completeness, { "ask_more": "ask_more", "query": "query", }) graph.add_edge("ask_more", "extract") # 追问后回到抽取节点 graph.add_edge("query", "quote") graph.add_edge("quote", "send") graph.add_edge("send", END) app = graph.compile()

这里最关键的是graph.add_edge("ask_more", "extract")这条边,它构成了一个循环:信息不全就追问,追问完重新抽取,直到信息完整才往下走。这个循环必须设置上限,否则模型可能永远觉得信息不全,陷入死循环。我的做法是在状态里加一个retry_count,超过三次就强制转人工。

提示:所有带循环的 Agent 流程都必须有"熔断"机制。可以是重试次数上限,也可以是超时时间。没有熔断的循环在生产环境里就是定时炸弹。

4.4 多 Agent 协作:什么时候该拆,什么时候不该拆

当流程里出现明显不同的"角色"时,可以考虑拆成多个 Agent。比如一个负责理解客户意图,一个负责查数据,一个负责写文案。每个 Agent 有自己的 instructions 和工具集,职责单一,调试起来也更容易。

但我要泼一盆冷水:不要为了拆而拆。我见过有人把本来一个 Agent 能搞定的事情拆成五个,结果节点之间传递状态的开销比干活还大,而且一旦某个环节出错,排查链路长得让人崩溃。判断标准很简单:如果两个角色的工具集完全不重叠、instructions 差异很大,那就拆;如果只是步骤不同但用的是同一套工具,那没必要拆。

5. 工具调用与状态管理:Agent 真正"下地干活"的关键

5.1 工具函数的写法与参数校验

工具是 Agent 和真实世界交互的接口。写工具函数有几个硬性要求:

  • 函数名和 docstring 要清晰,因为模型是靠这些来判断什么时候调用它的。名字叫do_stuff的工具,模型永远不知道该不该用。
  • 参数类型要明确,用类型注解。模型会根据类型生成参数,类型模糊会导致传参错误。
  • 返回值要结构化,最好是 dict 或 Pydantic 模型,方便下游解析。
  • 要有错误处理,工具内部抛异常要捕获并返回可读的错误信息,而不是让整个流程崩掉。
@function_tool def query_price(product_id: str, quantity: int) -> dict: """根据产品ID和数量查询单价与总价。 Args: product_id: 产品唯一标识 quantity: 采购数量,必须为正整数 """ if quantity <= 0: return {"error": "数量必须大于0"} try: unit_price = fetch_price_from_db(product_id) return { "unit_price": unit_price, "total": unit_price * quantity, } except Exception as e: return {"error": f"查询失败: {str(e)}"}

5.2 状态在节点间怎么传才不乱

状态管理的核心原则是:只传必要信息,不传中间过程。很多人喜欢把模型的原始输出、思考过程、临时变量全塞进状态里,结果状态对象越来越臃肿,节点之间互相污染。

我的做法是把状态分成两层:一层是"业务状态",只放最终需要的结果字段;另一层是"调试信息",单独存日志,不进状态。这样状态对象始终干净,节点函数也容易测试。

另外,LangGraph 里节点返回的是"状态更新",不是完整状态。框架会自动把更新合并进去。这个机制要理解清楚,否则你会困惑为什么节点里读到的状态和返回的不一样。

5.3 让模型输出稳定结构化数据的技巧

模型输出不稳定是 Agent 落地最大的痛点之一。同一个输入,今天返回 JSON,明天返回一段带解释的文字。解决办法有几个:

第一,用框架自带的结构化输出能力。OpenAI Agents SDK 支持指定 output_type,LangGraph 里可以配合 with_structured_output。让模型在解码层面就受约束,比事后用正则去抠要可靠得多。

第二,在 prompt 里给明确的 schema 示例。不要只说"返回 JSON",而是把完整的字段名、类型、示例都写出来。

第三,加一层校验和重试。拿到输出后先用 Pydantic 校验,不通过就把错误信息喂回给模型让它重试。这个重试逻辑最好封装成一个通用函数,所有需要结构化输出的节点都复用它。

from pydantic import BaseModel, ValidationError class ExtractedInfo(BaseModel): category: str quantity: int missing: list[str] def extract_with_retry(text: str, max_retry: int = 3) -> ExtractedInfo: for i in range(max_retry): raw = call_model(text) try: return ExtractedInfo.model_validate_json(raw) except ValidationError as e: text = f"{text}\n\n上次输出有误:{e}\n请重新输出合法JSON。" raise RuntimeError("结构化输出重试超限")

6. 实测中的坑:那些文档不会告诉你的问题

6.1 模型"自作主张"跳过步骤

这是最常见的问题。你明明设计了"先校验再查询"的流程,但模型在某个节点里直接把两步合并了,或者干脆跳过了校验。根本原因是模型倾向于"尽快给出答案",而不是"严格按流程走"。

解决办法是把关键步骤做成独立的工具节点,而不是让模型在一个节点里自由发挥。也就是说,能用代码强制的顺序,就不要交给模型判断。模型只负责它真正擅长的部分——理解和生成,流程控制交给图结构。

6.2 工具调用参数类型对不上

模型生成的参数经常和函数签名对不上。比如函数要 int,模型传了个字符串 "20";函数要 list,模型传了个逗号分隔的字符串。这类问题在测试阶段不容易发现,因为模型有时候恰好传对了。

我的经验是在工具函数入口做一次强制类型转换和校验,把容错做在工具层,而不是指望模型每次都传对。同时把参数描述写得更具体,比如"quantity 是一个整数,例如 20,不要传字符串"。

6.3 长流程中的上下文膨胀

流程一长,状态里积累的信息越来越多,每次调用模型都要把整个状态塞进 prompt,token 消耗飙升,而且模型容易被无关信息干扰。解决办法是给每个节点只传它需要的那部分状态,而不是整个状态对象。LangGraph 里可以在节点函数里只取需要的字段,OpenAI Agents SDK 里则可以通过精简 instructions 和上下文来控制。

6.4 错误处理与重试的边界

重试不是万能的。有些错误重试一百次也没用,比如参数本身就不合法;有些错误重试一次就好,比如网络抖动。我的分类原则是:

错误类型处理方式
网络超时、限流指数退避重试,最多3次
参数校验失败不重试,返回错误让上游修正
模型输出格式错误带错误信息重试,最多3次
业务规则冲突不重试,转人工
工具内部异常记录日志,返回可读错误

这张表建议直接做成代码里的错误处理策略,而不是每次遇到问题临时判断。

7. 从跑通到上线:还需要补哪些工程能力

7.1 日志与可观测性

Agent 流程最大的调试难点是"你不知道它中间想了什么"。所以日志必须打全:每个节点的输入、输出、耗时、调用的工具、模型的原始返回,都要记下来。最好给每次流程执行分配一个 trace_id,这样出问题时能把整条链路串起来看。

我一般会在状态里放一个 trace_id,每个节点打日志时都带上它。查询日志时按 trace_id 过滤,整条执行路径一目了然。这个习惯在排查线上问题时能救命。

7.2 人工介入节点

再智能的 Agent 也有搞不定的时候。设计流程时一定要留"转人工"的出口。可以是某个条件触发,也可以是重试超限后自动触发。人工介入节点通常做两件事:把当前状态展示给人工,让人工补充或修正,然后把修正后的状态重新注入流程继续执行。

这个能力在早期尤其重要,因为你对模型行为的预期往往不准,有了人工兜底,至少业务不会中断。

7.3 成本与性能的平衡

Agent 流程的 token 消耗比单次调用高得多,因为每一步都要带上下文。控制成本的手段有几个:能不用模型的节点就不用,比如纯数据查询和格式转换;给每个节点的 prompt 做精简,去掉冗余说明;对简单任务用小模型,复杂任务才用大模型。

性能方面,节点能并行就并行。比如"查库存"和"查物流"如果互不依赖,可以同时发起,而不是串行等待。LangGraph 支持并行节点,用好了能显著缩短整体耗时。

7.4 测试策略

Agent 的测试和普通代码不一样,因为输出有随机性。我的做法是分三层测:第一层测工具函数,这是确定性的,用单元测试覆盖;第二层测单个节点的输出结构,只校验格式和关键字段,不校验具体文案;第三层测整条流程,用一批真实场景的输入跑端到端,人工检查结果是否合理。

第三层测试最花时间,但最有价值。我通常会攒一个"回归用例集",每次改流程都跑一遍,看看有没有把之前能跑通的场景搞坏。这个习惯能避免大量"改一处崩三处"的问题。

8. 一个可复用的落地节奏

如果你现在手上就有一个业务场景想 Agent 化,我建议按这个节奏走:先用纸笔把流程画成节点和边,标出哪些节点用模型、哪些用代码;然后搭一个最小可运行版本,只跑通主干路径,不管异常分支;接着把工具函数一个个补上,每个都单独测通;再补条件边和循环,加上熔断;最后加日志、人工介入和错误处理,做端到端回归。

这个顺序的好处是每一步都有可验证的产出,不会出现"写了一堆代码但不知道对不对"的情况。我自己做过的几个 Agent 项目,凡是按这个节奏来的,上线都比较顺;凡是上来就写代码、边写边想的,后期返工都很惨。

最后分享一个我踩过的坑:不要一开始就追求"全自动"。我早期做过一个流程,想让 Agent 从接收需求到发合同全自动完成,结果因为中间某个环节模型判断失误,发出去一份错误报价,差点造成实际损失。后来改成关键节点必须人工确认,反而跑得更稳,业务方也更愿意用。自动化的价值不在于"无人",而在于"把人从重复劳动里解放出来,去做真正需要判断的事"。这个认知转变,比任何技术选型都重要。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询