首页 / 文章

Claude Agent SDK vs OpenAI Agents SDK vs Google ADK:一个智能体,三种实现方式

Claude Agent SDK、OpenAI Agents SDK与Google ADK:同一个营养标签生成工具被开发了三次,分别由哪些组件负责处理任务,还有哪些部分需要自行编写。

发布者:REACTAPP.TOP阅读约 37 分钟8560 词

来源: So Which Agent SDK Should You Build With?

如今,每家大型人工智能供应商都会提供智能体SDK,其宣传话术几乎如出一辙:把你的工具和提示词交给我们,我们会自动完成整个流程——包括工具调用、结构化输出、会话管理以及追踪功能。既然营销内容如此相似,那么究竟是什么让这些套件各有不同?又该选择哪一种来开发呢?

要找出答案,我们就用同样的智能体分别通过Anthropic的Claude Agent SDK、OpenAI的Agents SDK以及Google的Agent Development Kit(ADK)进行了三次开发。这个智能体并非玩具,而是一个能够读取照片、运用多种工具进行分析、给出结论,并且随着看到更多标签而不断改进的“营养标签调查员”。既然使用的是同一个智能体,只是采用了不同的SDK,那么结果上的任何差异都应归因于SDK本身,而非问题所在。

简而言之:这三者几乎在核心的推理循环这一难点上完全一致,而在其周边的一切方面则存在差异。它们在工具的声明方式、图像的导入方式以及是否提供文本输出等方面有所不同。最能体现差异的是,它们在那些无需用户自行处理的功能上也有区别。读完之后,你就能知道哪种工具套件适合哪种任务,以及选定某一种后还需要自己完成多少工作。

另有一条线索贯穿所有三种构建版本。该智能体被设计为能够从经验中学习:它会记住自己已经检查过的产品以及那些已确定处理方法的添加剂名称,并将这些信息保存到磁盘上,这样后续运行时就能减少对模型的依赖,加快处理速度,同时行为也更可预测。模型的权重永远不会改变,真正积累技能的是智能体本身。现有的任何 SDK 都无法直接提供这一功能,因此它成为了一个很好的工具,帮助我们明确框架的边界以及自身工程工作的起点。

首先,什么是智能体?

普通语言模型并非智能体。它可以被视作一种函数,输入文本和图像后输出文本。不同调用之间没有状态延续,它既不能执行代码,也不能获取网页内容,甚至无法再次调用自身。如果你给它一份营养标签,它会告诉你它会如何进行调查,但实际上这种调查永远不会发生。

智能体则是带有工具的循环运行模型。其工作模式始终不变。你为模型设定目标以及可供使用的工具列表,它要么给出最终结果,要么提出调用工具的请求,比如“查询E211信息”。如果它请求使用某工具,就会有人执行该工具并返回结果,随后模型会再次启动。这个循环会不断重复,直到出现最终答案为止。

整个流程就是如此:模型做出决策,工具执行操作,得到结果后模型再次决策。我们的检测流程会循环多次,先查找产品信息,再针对每种添加剂进行检测,接着进行“更健康选择”评估、等级判定,最后进行综合分析。在每一个环节,模型都会根据刚刚获取的信息来选择下一步操作。正是这一点让系统具备了自主性,也解释了为何仅用 for 循环封装的一次 API 调用并不符合要求。

注意模型实际输出的内容:只有“我想要用这些参数调用 check_sfa_additive。”其他组件需要将这一请求转化为实际的函数调用,从而推动整个流程运转。那个组件就是harness,它所包含的底层实现远比大多数人想象的要复杂。它可分为三层:开发层用于构建智能体,运行时层用于在生产环境中操作智能体,而控制平面则用于管理整个智能体集群。正是开发层让整个循环开始运转,这些SDK也位于这一层。

从开发层开始,这里负责构建单个智能体。它包括循环结构本身(调用模型、检测工具调用请求、重复执行直至完成,或在轮次限制或预算耗尽时安全退出),以及角色设定,即告诉模型其身份与行为方式的系统提示。围绕这些要素的是工具规范,它们以模型期望的格式描述每个工具;还有工具执行部分,负责实际运行相应的函数,可以是MCP服务器,也可以是文件读取之类的内置功能,并捕获输出结果。MCP客户端使智能体能够访问第三方服务器。上下文管理功能负责保存运行过程中的对话记录、处理图像信息,以及在记录过长时进行截断。提供商连接部分则涵盖特定供应商的认证机制、端点地址以及请求格式。 除此之外,还有结构化输出,它会对最终答案进行约束并依据固定格式进行校验;以及多智能体工作流,这类工作流能够以顺序、并行或循环的方式串联多个智能体。

SDK让这一切变得简单。从运行脚本转变为运营服务时,运行时层便出现了,同时还伴随着更多需求。代理需要托管式执行环境,即具备会话隔离功能的端点,以便其他系统能够调用;它还需要工具网关、重试机制、超时处理以及凭证管理功能,这样在上游API出现故障时仍能确保工具调用的可靠性;此外还需要托管内存,使得会话和长期状态能够存储在进程存活期间且可在不同实例间共享。代理在调用工具时还需要工作负载身份标识,以避免使用个人密钥进行调用;同时还需要可观测性功能,包括能够显示哪些工具被触发、使用了什么以及为何如此的追踪信息和时间戳。 为代理自身的代码或浏览器工具添加沙箱环境,实现能够在崩溃后从上一步完成的状态继续运行的持久循环,并通过端点背后的并发处理与自动扩缩容功能来实现性能扩展。

其下方是控制平面,它能够同时管理多个代理。通过注册表,代理可以在运行时被发布和发现,而无需在代码中直接相互连接。每个代理都需要在目录中拥有一个可归属的身份标识,同时还需要有明确规定的权限范围,而非仅凭默认设置。事后需要审计与合规日志来记录整个代理集群的运行情况,对共享内存的存储内容进行数据治理,此外还需设置成本与配额控制,包括预算限制和速率限制。代理之间可以通过诸如A2A这样的开放协议进行通信。由于工具的输出会直接反馈到模型中,因此防护机制和注入防御非常重要,而评估功能则能判断答案是否正确,而不仅仅是运行是否完成。 用于工具和MCP服务器的注册表可处理目录管理、版本控制、发现、资源池化以及每台服务器的认证功能;而智能提示词版本控制功能则允许你固定、对比和回滚提示词,从而确保运行结果的可重复性。简而言之,这些工具包都是现成的解决方案。Claude、OpenAI和Google都提供了完整的框架,包括流程控制、工具集成、上下文处理以及服务提供商连接功能,你只需编写其中有趣的部分:自己的工具、提示词以及输出格式。ADK的名字比较特别,尽管它的功能与其余工具相同,因此下文中提到的“SDK”大致涵盖了这三种工具。

他们的分歧在于提供哪些组件、每个组件的功能定位如何,以及何时结束该组件的作用而由代码接手。最清晰的做法是在每个组件中分别构建一个代理。一旦三个代理都准备就绪,就可以完整规划整个组件体系,并明确标注每个组件负责的功能以及需要由代码处理的部分。

测试中的代理:营养标签分析器

要进行公平的测试,就必须让这些组件执行其设计初衷的任务:通过多个工具进行推理,每一步都依赖于上一步的结果。仅使用框架封装后的模型调用并不算数,真正的分析工作是由该分析器完成的。给它一张营养标签的照片,它就会执行五项分析任务。

  1. 它通过模型自身的视觉功能读取产品标签,提取产品名称、完整的成分及添加剂列表、每100毫升的糖分和饱和脂肪含量,以及该产品是否属于饮料。照片是唯一的真实信息来源,没有其他产品数据库可供参考。
  2. 对于检测到的每一种添加剂,无论标签上标注的是E471还是“大豆卵磷脂”,它都会查阅新加坡食品局公布的允许使用的添加剂清单,并说明该添加剂的状况及相关的消费者警示信息。
  3. 它会查询data.gov.sg上的HPB数据集,以确认该产品是否带有“更健康选择”标志。
  4. 对于饮料类产品,它会根据糖分和饱和脂肪含量来确定其预期的A/B/C/D级营养等级。
  5. 最后,它会根据所有检测结果给出绿色、琥珀色或红色的分级判定,每项判定都会注明产生该结果的工具或标签,并在开头说明模型的推理过程。

它也不会每次都从零开始。它会记住之前看到过的内容并不断学习,因此昨天查看过的产品以及曾经难以识别的添加剂名称,下次都会出现在列表中。详情稍后介绍。

因此,这个测试平台具备长度可变的测试循环、真实的新加坡数据源、图像输入功能、可在超市货架上直接核实的检测结果,以及每次运行都会略有改进的智能分析模块。

确保比较的公平性

任何非SDK的组件都必须保持完全一致。这三个演示程序都调用同一个LiteLLM代理,这是一个兼容OpenAI的网关,同时也为Claude SDK提供Anthropic的/v1/messages接口。一个根级的.env文件存储了每个程序的密钥、基础URL以及模型名称,这些模型分别为Claude Sonnet 4.6、GPT-5.4和Gemini 3.5 Flash。各个SDK指向该代理的方式能够透露出很多信息。

每个演示都独立存在于自己的 uv 项目中,因此无需安装其他项目的依赖即可运行单个演示。SFA添加剂表是根据新加坡食品局发布的《允许使用的添加剂》PDF文件一次性生成的,生成了 sfa_additives.json 文件(约540条记录,其中约270条带有E编号),随后该文件被原封不动地复制到每个项目中。测试用图片也完全相同:两种真实的新加坡产品,即Milo 3in1冲调包和Marigold HL牛奶盒。

这样一来,真正由SDK决定的就只剩下这些方面:工具的定义方式、图片的添加方式、模型的连接方式、是否有结构化输出以及如何查看运行结果。

在介绍代码之前先提个醒。下面的代码片段包含一个 recall_product 工具和一个 e_number_hint 参数。它们是后面提到的记忆功能与自我改进功能的基础,目前可以先忽略。

Anthropic 的 Claude Agent SDK

Claude Agent SDK 基于 Claude Code 构建,而 Claude Code 正是许多人已经在终端中使用的智能体平台,这一选择决定了整个系统的走向。你无需从基础元素开始构建循环结构,而是可以直接使用经过充分测试的完整智能体框架,并为其提供所需的工具与提示词。内置工具(Read、Write、Edit、Bash、WebFetch)已经准备就绪,权限模型与上下文管理功能也已实现,而自定义工具则可通过运行在你自身进程中的 MCP 服务器进行集成。所有操作均通过 anyio 以异步方式执行。

你购买的是一个真正可立即使用的计算机操作代理。凡是需要读取文件、运行Shell命令、编辑代码或浏览内容的功能,都能以生产级标准在第一天就实现,因为这与驱动Claude Code的机制相同。MCP属于优先支持的类型,因此你编写的自定义工具在Claude Code、Claude Desktop以及你自己的应用程序中都能无需修改即可使用。在这三者中,它的代理运行时最为成熟。

成本体现在工具的配置上,你需要手动填写名称、描述和架构信息:

@tool(
    "check_sfa_additive",
    "Check whether a food additive is permitted by the Singapore Food Agency, "
    "using the full SFA permitted-additives list parsed from the official PDF. "
    "Accepts an E-number ('E211') OR a name ('Sodium Benzoate', 'Soy Lecithin'). "
    "If you pass a name and also know its E/INS number, set e_number_hint — an "
    "unrecognised name that the hint resolves is REMEMBERED for next time.",
    {"additive": str, "e_number_hint": str},
)
async def check_sfa_additive(args: dict) -> dict:
    raw = args["additive"].strip()
    hint = args.get("e_number_hint", "").strip()
    entry = _resolve_additive(raw)
    if not entry and hint:
        entry = _resolve_additive(hint)
        if entry:  # learn: this label name -> this number, persisted to disk
            num = entry["e_number"] or entry.get("ins") or ""
            _MEMORY["learned_aliases"][_norm(raw)] = re.sub(r"\(.*\)", "", num).lower()
            _save_memory(_MEMORY)
    return _ok(_format_entry(entry, raw))

那个 {arg: type} 字典需要输入的字符比其他两个工具集更多,但却不会产生任何歧义。这些工具通过进程内的MCP服务器提供,模型在引用它们时会加上 mcp__<server>__<tool> 前缀,因此在填写 allowed_tools 时必须记住这一点:

server = create_sdk_mcp_server(
    name="sg-nutrition-tools",
    version="1.0.0",
    tools=[recall_product, check_sfa_additive, check_hcs, calculate_nutri_grade],
)

options = ClaudeAgentOptions(
    system_prompt=SYSTEM_PROMPT,
    mcp_servers={"sg": server},
    allowed_tools=[
        "mcp__sg__recall_product",
        "mcp__sg__check_sfa_additive",
        "mcp__sg__check_hcs",
        "mcp__sg__calculate_nutri_grade",
    ],
    model=CLAUDE_MODEL,
    permission_mode="bypassPermissions",
    max_turns=20,
    # Point the bundled CLI at the LiteLLM proxy's Anthropic endpoint.
    env={
        **os.environ,
        "ANTHROPIC_BASE_URL": BASE_URL,
        "ANTHROPIC_AUTH_TOKEN": API_KEY,
    },
)

图片也以同样的自助方式添加。不存在 image= 参数。query() 可以接收消息字典的异步可迭代对象,因此你需要自行构建原始的 Claude 原生内容块,即文本块与图片块组合在一起:

async def message_stream():
    yield {
        "type": "user",
        "message": {"role": "user", "content": [
            {"type": "text", "text": "Investigate this nutrition label."},
            {"type": "image", "source": {"type": "base64", "media_type": mime, "data": b64}},
        ]},
    }

async with ClaudeSDKClient(options=options) as client:
    await client.query(message_stream())
    async for message in client.receive_response():
        ...  # stream ToolUseBlock / TextBlock as they arrive

其核心机制是模型接线方式。SDK会驱动随附的Claude Code CLI,该工具使用Anthropic定义的消息格式,因此可通过环境变量(ANTHROPIC_BASE_URL和ANTHROPIC_AUTH_TOKEN)将其重定向到代理服务器。无需传递任何客户端对象。优点在于整个流程完全透明:每当有新的ToolUseBlock数据到达时都会被打印出来,从而可以实时观察每个添加步骤的完整过程。以下是针对Milo样本的运行结果:

sausheong claude-demo % uv run python main.py ../milo.jpeg

Investigating image: ../milo.jpeg
────────────────────────────────────────────────────────────
I'll start by reading the label carefully, then run all checks in parallel.

**Reading from the label:**
- **Product:** Nestlé MILO 3in1
- **Net weight:** 27g powder sachet (add hot water to prepare — this is a **powder, not a ready-to-drink beverage**)
- **Ingredients:** Sugar, PROTOMAL® Malt Extract (Contains Barley), Skimmed Milk (Cow's Milk), Cocoa, Palm Oil, Minerals (Dicalcium Phosphate, Disodium Phosphate, Ferric Pyrophosphate), Resistant Dextrin, Flavourings, Vitamins (C, B3, D, B6, B2, B12)
- **Identifiable additives:** Dicalcium Phosphate (E341), Disodium Phosphate (E339), Ferric Pyrophosphate; Flavourings (generic — cannot identify specific ones)

Kicking off recall + HCS check + all additive checks simultaneously:
  → tool: recall_product({'product_name': 'Nestlé MILO 3in1'})
  → tool: check_hcs({'product_name': 'MILO 3in1'})
  → tool: check_sfa_additive({'additive': 'Dicalcium Phosphate', 'e_number_hint': 'E341'})
  → tool: check_sfa_additive({'additive': 'Disodium Phosphate', 'e_number_hint': 'E339'})
  → tool: check_sfa_additive({'additive': 'Ferric Pyrophosphate', 'e_number_hint': 'E450'})
All checks returned. Note: this is a **powder**, not a ready-to-drink beverage, so Nutri-Grade (which requires per-100ml liquid values) does **not apply** — I will skip `calculate_nutri_grade`.

---

## Reasoning

**Step 1 — Product recall.** This product has been seen before. The prior verdict is consistent with what I read now — I'll treat this fresh investigation as the authoritative one and use the prior as a cross-check.

**Step 2 — Label reading.**
- MILO 3in1 is a **27g powder sachet** — you add hot water to make a drink. It is classified as a "Tonic Food Drink" on the label. Because it is sold as a dry powder (not a pre-packaged ready-to-drink liquid), Nutri-Grade per-100ml calculation does not apply.
- Nutritional values per serving (27g): Energy 110 kcal, Total Fat ~2.4g, Saturated Fat ~1.2g, Total Sugars ~13.8g (of which Added Sugars ~10g), Sodium ~38mg.
- **Sugar is the #1 ingredient** — it appears first in the ingredient list, before the malt extract.

**Step 3 — Additives.**
- **Dicalcium Phosphate (E341):** SFA-permitted ✅ — used as a mineral/calcium source.
- **Disodium Phosphate (E339):** SFA-permitted ✅ — used as an acidity regulator/mineral.
- **Ferric Pyrophosphate:** SFA-permitted ✅ — used as an iron supplement/mineral source.
- **Resistant Dextrin:** A dietary fibre ingredient (prebiotic fibre), not a regulated additive — no E number, no SFA check needed.
- **Flavourings:** Generic label — cannot identify specific flavouring compounds; SFA check not possible.
- **Vitamins (C, B3, D, B6, B2, B12):** Added as nutritional fortification, not food additives in the regulatory sense. Riboflavin (B2) carries E101 when used as a colourant, but here it's explicitly declared as a vitamin supplement — standard and expected.

**Step 4 — Healthier Choice Symbol.** HCS certified ✅ (MILO 3in1 Activ-Go appears multiple times in the HPB dataset — `check_hcs`).

**Step 5 — Sugar content reality check.** Each 27g sachet contains ~13.8g total sugars (~10g added sugars). That's more than **half the sachet's weight is sugar**, and exceeds the WHO daily added-sugar guideline of 25g in less than one cup. This is label-read, not a tool finding.

---

## Verdict

### 🟢 GREEN — No Concern
- **All three declared additives are SFA-permitted** (Dicalcium Phosphate E341, Disodium Phosphate E339, Ferric Pyrophosphate) — `check_sfa_additive` ×3.
- **Carries the Healthier Choice Symbol** — HPB has certified MILO 3in1 Activ-Go — `check_hcs`.
- **Vitamins and minerals fortified** — B2, B3, B6, B12, C, D, plus calcium and iron — label.
- **Contains barley malt extract** — a whole-grain source of energy — label.
- **Resistant Dextrin** (prebiotic dietary fibre) is a functional ingredient, not a concern — label.

### 🟡 AMBER — Worth Knowing
- **Palm Oil present** — a saturated fat source. Saturated fat is 1.2g per sachet (~4.4g per 100g of powder). Not alarming at one serving, but worth knowing if you have multiple cups a day — label.
- **"Flavourings" are not specified** — cannot determine whether natural or artificial. No SFA check was possible — label.
- **Nutri-Grade does not apply here** — this is sold as a powder. If you reconstitute it and compare the drink to ready-to-drink alternatives, it would likely land in Grade C or D territory based on sugar content, but no regulatory grade is required on the packet.

### 🔴 RED — Genuinely Concerning
- **Sugar is the #1 ingredient** — it appears first in the list, meaning it is the dominant ingredient by weight. Each sachet delivers **~13.8g total sugars / ~10g added sugars** — label. That's 40% of the WHO's recommended 25g/day added-sugar limit in a single cup. If you're drinking 2–3 sachets a day (as Nestlé's own label suggests for daily energy), you are consuming **20–30g of added sugar from MILO alone**, likely exceeding the daily limit.
- **Marketing framing vs. nutritional reality:** The packet prominently says "Nourishing Energy" and shows vitamins, but the sugar load is the dominant nutritional feature. The fortification (vitamins, minerals) does not offset this — label.

**Bottom line for a Singapore shopper:** MILO 3in1 is safe from an additives and regulatory standpoint — everything checks out with SFA and HPB. But it is a **sugar-heavy product** dressed up in health language. Treat it as an occasional treat rather than a daily nutritious breakfast drink, especially for children.

缺陷所在。该平台没有原生结构化输出功能,因此只能获得文本,用户需自行解析或手动指定格式,这正是其与 OpenAI SDK 相比最大的日常使用差异。架构定义需手动完成,因此每个功能都需要编写更多代码并保持同步。模型连接通过环境实现,而目标代理必须提供 Anthropic 格式的 /v1/messages 接口,因为仅有兼容 OpenAI 的接口是不够的。应将 allowed_tools 列表视为预先批准清单,而非沙箱机制:它的意思是“跳过这些工具的确认提示”,而非“智能体只能使用这些工具”,那些将其误认为是白名单的新用户会感到意外。忽略 mcp__sg__ 前缀的话,该工具将直接未被批准,且不会产生任何错误提示。 由于是在后台启动 Claude Code 运行时而非直接进行 API 调用,因此其资源占用也会更高。

何时选择它:当智能体需要操作机器时,比如读写文件、执行 shell 命令、编辑代码库或浏览网页,任何能够借助内置的 Claude Code 工具而无需自行构建沙箱的任务都适合使用它。如果你已经在使用 MCP,或者正在开发类似 Claude Code 体验的应用,它也是自然的选择。其流式处理机制便于监控,非常适合长时间运行的智能体。

何时选择它:当你需要一个简洁的、可编程的函数调用循环来返回结构化对象时,因为这正是 OpenAI SDK 的强项所在。如果你无法提供 Anthropic 格式的接口,或者对依赖项的体积有严格要求,那就选择其他方案。像“调用三个工具并返回一个 Pydantic 对象”这样的简单任务其实不需要那么复杂的架构。

OpenAI Agents SDK

OpenAI的SDK是一个轻量级且刻意遵循Python风格的封装层,完全依托于Python语言本身。类型提示会转化为工具结构,文档字符串会变为工具描述,而Pydantic模型则成为输出契约。它不规定项目的结构布局,也没有独立的运行时环境或MCP机制。你只需编写函数和模型,构建一个Agent,然后将其传递给Runner.run。其目标就是让Python函数与可运行的智能体之间的距离尽可能短。

它的最大优势在于支持类型化的往返处理。指定 output_type=YourModel 后,返回的将是经过验证的 Python 对象,而非你期望的 JSON 格式数据;SDK 会先输入提示词,解析回复内容,对其进行验证,若结构不符合要求则会再次发送给模型。类型提示可直接提供架构定义,再加上 OpenAI 自有后端提供的免费追踪控制台,使得这种方式成为构建结构化、可观测智能体的最快途径。对于“接收此输入并返回类型化结果”的任务,没有其他工具能如此直接高效。

这些工具完全无需自行创建架构:

@function_tool
async def check_sfa_additive(additive: str, e_number_hint: str = "") -> str:
    """Check whether a food additive is permitted by the Singapore Food Agency.

    Looks the additive up in the full SFA permitted-additives list (parsed from
    the official SFA PDF). Accepts either an E-number or a plain-English name as
    printed on a Singapore label.

    Args:
        additive: An E-number ('E211', 'e211', 'en:e211') OR an additive name
            ('Sodium Benzoate', 'Soy Lecithin', 'MSG').
        e_number_hint: Optional. If you pass a name and also know its E/INS
            number, supply it here. If the name isn't recognised but the number
            is, the mapping is REMEMBERED so the name resolves directly next time.    """
    ...

装饰器会读取类型提示以及文档字符串中的Args部分,从而生成JSON模式,因此无需手动维护任何内容。

其真正优势体现在输出结果上。判定结果会以Pydantic模型的形式呈现:

class NutritionVerdict(BaseModel):
    reasoning: str          # declared first: the model works out loud before committing
    product_name: str
    summary: str
    green: list[Finding]
    amber: list[Finding]
    red: list[Finding]
    recommendation: str

使用 output_type=NutritionVerdict 时,模型会直接返回一个已验证的对象,无需任何解析步骤,也无需冒险猜测。将 reasoning 放在首位是个不错的技巧:结构化的输出同时起到了临时记录的作用,这样模型在确定结论之前可以先清晰地思考,而你也能将整理好的对象与推理过程保存在一起。

在三者中,模型连接与图像输入是最简洁的:

client = AsyncOpenAI(base_url=BASE_URL, api_key=API_KEY)
model = OpenAIChatCompletionsModel(model=OPENAI_MODEL, openai_client=client)
agent = Agent(name="SG Nutrition Investigator", model=model,
              instructions=INSTRUCTIONS, output_type=NutritionVerdict,
              tools=[recall_product, check_sfa_additive, check_hcs, calculate_nutri_grade])

user_input = [{"role": "user", "content": [
    {"type": "input_text", "text": "Investigate this nutrition label."},
    {"type": "input_image", "image_url": _data_url(image_path)},  # base64 data URL
]}]
result = await Runner.run(agent, input=user_input)
verdict: NutritionVerdict = result.final_output

那是一个针对代理的 AsyncOpenAI 客户端,被封装在 OpenAIChatCompletionsModel 中,通过 input_image 参数传递照片,非常简单。

需要注意的问题。 默认情况下工具调用是并行执行的,因此模型会同时启动多个 check_sfa_additive 调用。我们的调用是异步的且没有副作用,这是个优势。但如果工具会修改共享状态、保持非重入连接或依赖执行顺序,就会出现异常,且故障会间歇性出现且受时间影响,是最糟糕的情况。要么让工具具备并发安全性,要么限制模型的行为。

使用追踪功能也有其弊端。虽然控制面板非常出色,但追踪数据会发送到 OpenAI 自身的接收端点,而在第三方代理背后,该代理生成的密钥无法用于身份验证。因此,只需执行 set_tracing_disabled(True),这个最强大的可观测性工具就会因一行代码而消失。若不在 api.openai.com 环境下,就只能依赖 OpenTelemetry 或自建的日志系统。还有其他问题存在:在代理背后需要使用 OpenAIChatCompletionsModel,因为默认的 Responses API 路径指向的是 OpenAI 的端点,选择错误的模型会导致令人困惑的错误。此外,output_type 的结构必须符合严格模式规则,因此具有深度嵌套或大量联合类型的模型可能会引发验证重试;正因如此,Finding 和 NutritionVerdict 的结构才保持简单。 包装盒内不附带任何计算机使用工具,因此除非自行添加,否则无法使用文件系统、命令行或浏览器。

以下是Milo小包的输出结果:

sausheong openai-demo % uv run python main.py ../milo.jpeg

Investigating image: ../milo.jpeg
────────────────────────────────────────────────────────────
Product: Nestlé MILO 3 in 1 Activ-Go

Reasoning: I read the sachet as **Nestlé MILO 3 in 1 Activ-Go**. The front says “goodness of malt, milk & cocoa” and the back says “MILO Ionic Food Drink,” so this is a **powdered drink mix**, not a ready-to-drink beverage. Because it is not sold as a beverage with per-100ml nutrition for the prepared drink as consumed, I did **not** calculate Nutri-Grade; that tool is only for beverages.

I first checked whether this product had been investigated before. A prior record existed, and it broadly matched what I can read here: a sweetened powdered malt-cocoa-milk drink sachet with permitted phosphate additives, relatively high in sugar for a single sachet, and not found in the HCS database. I used that only as background and then re-read this label fresh.

From the nutrition panel, I can read:
- Serving size: **1 sachet (27g)**
- Servings per package: **1**
- Per 100g / per serving shown side by side
- **Total sugars: 50.1g per 100g; 13.8g per serving**
- **Saturated fat: 0.5g per 100g; 1.2g per serving**
This is a powder, so the relevant direct reading is per 100g, not per 100ml.

I can read the ingredient list as:
**Sugar, Protomalt™ Malt Extract (contains barley), skimmed milk/cow’s milk, cocoa, palm oil, minerals (dicalcium phosphate, disodium phosphate), fibre (pyrophosphate), resistant dextrin, flavourings, vitamins (C, B3, D, B6, B2, B12).**

For additives, I checked each identifiable additive individually where possible:
- **Dicalcium phosphate**: checked and found **permitted by SFA**. That lands in GREEN.
- **Disodium phosphate**: checked and found **permitted by SFA**. That also lands in GREEN.
- The label also contains the broader terms **“phosphates”** and **“pyrophosphate”/“fibre (pyrophosphate)”** in wording around ingredients, but the image does not clearly identify a specific additive code for the pyrophosphate entry, and the generic umbrella term “phosphates” is not reliable enough on its own. I therefore do not treat the generic term as confirmed permitted just from the broad word alone. I only rely on the specifically named phosphates I could check.
- **Flavourings** is too generic to identify a specific additive, so I could not check it individually.
- The vitamins and minerals are nutrients rather than red-flag food additives in the SFA additive sense.

I also checked for the **Healthier Choice Symbol** using the product name. It was **not found** in the HCS database.

Main nutritional takeaways from the label:
- Sugar is the **first ingredient**, which usually means it is the largest ingredient by weight.
- The product contains **50.1g sugar per 100g** powder, and **13.8g sugar per 27g sachet**. That is quite a lot for one small sachet drink.
- Saturated fat is **1.2g per sachet**, which is not extreme, but the bigger concern here is sugar.

So the overall picture is: the identifiable phosphate additives appear permitted, but nutritionally this is still a **sweetened convenience drink mix** rather than a lower-sugar everyday option.

Summary: This is a powdered MILO drink sachet, not a ready-to-drink beverage. The clearly identifiable phosphate additives on the label are permitted by SFA, but sugar is the first ingredient and the product contains 50.1g sugar per 100g, or 13.8g per 27g sachet. It was not found in the Healthier Choice Symbol database.

🟢 Dicalcium phosphate is permitted by SFA.  [check_sfa_additive]
🟢 Disodium phosphate is permitted by SFA.  [check_sfa_additive]
🟢 This is a powdered drink mix rather than a ready-to-drink beverage, so Nutri-Grade was not applicable here.  [label]
🟡 Generic terms such as “flavourings” and the unclear “fibre (pyrophosphate)” wording are not specific enough to confirm exact additive identities from the image alone.  [label]
🟡 The product was not found in the Healthier Choice Symbol database.  [check_hcs]
🟡 Saturated fat is 1.2g per 27g sachet (0.5g per 100g listed on the label panel image).  [label]
🔴 Sugar is the first ingredient on the label.  [label]
🔴 Total sugars are 50.1g per 100g and 13.8g per 27g sachet, which is high for a small single-serve drink mix.  [label]

Recommendation: Fine as an occasional sachet drink if convenience matters, but not the best everyday choice if you are trying to reduce sugar. If you drink MILO often, consider using plain MILO powder with your own milk and less sugar, or choose a lower-sugar version.

何时选择它:当任务需要结构化处理,如分类、提取、评分或生成结论,且使用结构化数据比文本更高效、同时希望尽量减少 Python 代码量时。喜欢类型提示的团队会很喜欢它,借助 OpenAI 的后端,免费的追踪功能让多步骤智能体的调试变得十分便捷。

何时跳过它:当你的工具无法实现并发安全且你不想处理默认的并行机制时;当你需要可观测性但处于非 OpenAI 的网关之后(此时追踪功能会消失);或者当智能体必须直接使用文件系统及命令行工具时,而这正是 Claude 的强项。对于多个在明确控制流下协同工作的智能体,ADK 更为合适,那就是第三种工具套件。

Google 的智能体开发套件 (ADK)

在这三个框架中,ADK才是真正的框架,它有明确的设计理念,而其他两个则更为简洁。它提供了代理、会话、Runner、SessionService,以及通过SequentialAgent、ParallelAgent和循环代理实现的明确的多代理工作流。预设的项目结构使其工具能够轻松定位代理,而adk web则提供了一个带有事件检查器的本地聊天界面。这些工具均为普通的Python函数,采用Google风格的文档字符串进行说明,既没有装饰器也没有模式字典,而LiteLlm封装使其成为最不受提供商限制的选择。

其设计理念是面向系统的架构而非脚本。当“代理”实际上指多个代理时,比如由规划器将任务分配给各个执行者、串行处理流程或并行分发结构,ADK会将这些作为可组合的一等组件提供,从而省去你手动编写控制流的麻烦。它内置了会话功能,调试界面在同类产品中最为出色,而且其与供应商无关的模型层可避免你绑定于某一家供应商。该框架专为发展成复杂的、具有状态管理的多代理应用而设计。

这里的工具定义最为简洁:

async def check_sfa_additive(additive: str, e_number_hint: str = "") -> dict:
    """Check whether a food additive is permitted under Singapore Food Agency rules.

    Looks the additive up in the full SFA permitted-additives list (parsed from
    the official SFA PDF). Accepts either an E-number or a plain-English name as
    printed on a Singapore label.

    Args:
        additive: An E-number ('E211', 'e211', 'en:e211') OR an additive name
            ('Sodium Benzoate', 'Soy Lecithin', 'MSG').
        e_number_hint: Optional E/INS number for when you pass a name. If the
            name isn't recognised but the number is, the mapping is REMEMBERED
            so the name resolves directly next time.

    Returns:
        dict with the input, resolved e_number/name, permitted status, schedule
        and any consumer note. found=False if it is not in the SFA list.
    """
    ...

该架构是基于那种类似 Google 风格的 Args 和 Returns 文档字符串构建的,因此其质量至关重要。模糊的文档字符串会直接影响模型对工具的选择,所以文档字符串实际上就是契约。

为了让 adk web 能找到你的 root_agent,项目需要具备一定的包结构:

adk-demo/
  sg_nutrition_investigator/
    __init__.py      # from . import agent
    agent.py         # tools + root_agent
  main.py            # standalone Runner + InMemorySessionService
root_agent = LlmAgent(
    name="sg_nutrition_investigator",
    model=LiteLlm(model="openai/" + GEMINI_MODEL, api_base=BASE_URL, api_key=API_KEY),
    instruction=INSTRUCTION,
    tools=[FunctionTool(recall_product), FunctionTool(check_sfa_additive),
           FunctionTool(check_hcs), FunctionTool(calculate_nutri_grade)],
)
# image input — the cleanest of the three:
message = types.Content(role="user", parts=[
    types.Part(text="Investigate this nutrition label."),
    types.Part.from_bytes(data=Path(image_path).read_bytes(), mime_type=mime),
])

该模型通过ADK的LiteLlm封装层进行处理,其中openai/前缀使得LiteLlm能够使用与OpenAI兼容的协议与代理通信。在这三种方式中,使用types.Part.from_bytes传递图像是最简洁的,而adk web则在这次对比中提供了最出色的内置开发体验。

需要注意的问题。最令人担忧的是权重问题。在 adk web 之外,需要在发送第一条消息之前配置 Runner、SessionService,以及明确的应用、用户和会话 ID,相比其他框架所需的几行代码,这要繁琐得多。布局结构也同样重要。__init__.py 中的 from . import agent 这一行是 adk web 发现 root_agent 的方式,任何偏离该结构的做法都会导致无法发现。litellm 会为未使用的后端生成探测信息(虽然无害,但会显示 bedrock 和 sagemaker 的“没有名为 botocore 的模块”警告),而 ADK 则会在其上叠加 [EXPERIMENTAL] 的架构警告,最终导致在导入时就需要同时屏蔽这两类警告。 结构化输出也更麻烦,因为 Gemini 的 response_schema 在代理层后的处理比 OpenAI 的 output_type 更复杂,所以这个演示采用了基于提示的文本判定方式。公平地说,这同样受代理层和 ADK 的影响。最后一点:在 LiteLlm 加上 Gemini-flash 的组合方案中,对于相同的提示,该智能体对其回忆内容的描述比其他方案更少,这表明行为不仅取决于 SDK,还与模型及连接方式有关。

Milo 小包的输出结果为:

sausheong adk-demo % uv run python main.py ../milo.jpeg

Investigating image: ../milo.jpeg
────────────────────────────────────────────────────────────
  → tool: recall_product({'product_name': 'Nestle MILO 3 in 1'})
  → tool: check_sfa_additive({'e_number_hint': 'E341', 'additive': 'Dicalcium Phosphate'})
  → tool: check_sfa_additive({'additive': 'Disodium Phosphate', 'e_number_hint': 'E339'})
  → tool: check_sfa_additive({'additive': 'Ferric Pyrophosphate'})
  → tool: check_hcs({'product_name': 'Nestle MILO 3 in 1'})
  → tool: calculate_nutri_grade({'saturated_fat_per_100ml': 0.5, 'sugar_per_100ml': 6.1})
## Reasoning

We investigated the label of **Nestle MILO 3 in 1 (with Activ-Go)**. Here is our step-by-step analysis:

1. **Product Categorisation & Reconstitution**:
   * This is a dry beverage powder sold in single-serving 27g sachets.
   * The nutritional information panel provides two columns: "Per Serving 27g" (the dry sachet) and "Per 100ml*" (as reconstituted by adding 200ml of hot water).
   * For the **Singapore Nutri-Grade** system, the grading is calculated based on the beverage *as reconstituted* according to the directions.

2. **Sugar & Saturated Fat Analysis (Per 100ml prepared)**:
   * **Total Sugars**: **6.1g per 100ml** (of which 4.8g is Added Sugar).
   * **Saturated Fat**: **0.5g per 100ml**.
   * Feeding these values into the Nutri-Grade calculator yields **Nutri-Grade C** (due to the sugar content being > 5g/100ml). Grade C beverages are classified as "less healthy" and must display the Nutri-Grade label at retail.

3. **Ingredients & Additives Analysis**:
   * **Sugar is the #1 Ingredient**: Refined sugar is listed first on the ingredients list, meaning it is the largest ingredient by weight in this packet—even ahead of malt extract and milk powder.
   * **Additives**:
     * **Dicalcium Phosphate** (E341(ii)) and **Disodium Phosphate** (E339(ii)) are SFA-approved food additives used as stabilizers and emulsifiers.
     * **Ferric Pyrophosphate** is an SFA-approved mineral nutrient used to fortify the drink with Iron.
   * **Healthier Choice Symbol (HCS)**: The packet displays the red HCS logo. This is because it is formulated to be "25% lower in sugar" *compared to regular malted beverages*. However, in absolute terms, it still carries enough sugar to land in Nutri-Grade C.

---

## Verdict

### 🟢 Green Findings
* **Fortified with Vitamins & Minerals** `[label]`: Contains B-vitamins (B2, B3, B6, B12), Vitamin C, Vitamin D, Calcium, and Iron (via *Ferric Pyrophosphate* `[check_sfa_additive]`).
* **Contains Soluble Fibre** `[label]`: Features *Resistant Dextrin*, a prebiotic soluble dietary fibre (1.1g per serving) that supports gut health.
* **SFA-Approved Additives** `[check_sfa_additive]`: Phosphates used for stabilizing and emulsifying (*Dicalcium Phosphate* and *Disodium Phosphate*) are fully permitted.
* **Healthier Choice Certified** `[check_hcs]`: Carries Singapore's HCS logo for having at least 25% less sugar than standard malted chocolate drinks.

### 🟡 Amber Findings
* **Nutri-Grade C** `[calculate_nutri_grade]`: When prepared as instructed, it gets a **Grade C** because of its **6.1g of sugar per 100ml**. Grade C drinks are in the "less healthy" bracket.
* **Contains Palm Oil** `[label]`: Palm oil is added to give a rich mouthfeel, contributing **1.2g of saturated fat per serving** (0.5g/100ml).

### 🔴 Red Findings
* **Sugar is the Main Ingredient** `[label]`: Sugar is listed as the very first ingredient on the label, which means there is more plain sugar by weight than malt extract (*PROTOMALT*) or milk powder.
* **High Sugar Load per Cup** `[label]`: A single small 27g sachet contains **13.8g of total sugar** (equivalent to about **3 teaspoons of sugar**), of which **10.8g** (approx. 2.5 teaspoons) is added refined sugar. Drinking multiple cups a day can quickly max out your recommended daily limit for added sugars.

在构建以下类型的系统时应选择 ADK:多个相互协作的智能体、明确的顺序、并行或循环工作流程、真实的会话状态,以及在不同服务提供商之间切换模型的能力。adk web 工具在开发阶段就能带来价值,而随着项目的扩展,其架构优势也会逐步体现,弥补前期投入的成本。

仅在存在单个简单智能体时才无需使用 ADK。运行器和会话的模板代码以及规定的结构布局都会带来不必要的开销,且这些成本永远无法通过其他方式弥补。若需要最快速的实现方式,可使用 OpenAI SDK;而对于具备内置工具的计算机智能体,则可选择 Claude 的相关框架。ADK 的复杂性只有在处理大规模系统时才有意义,否则它就只是超出需求的框架而已。

运行演示

三个代理都已构建完成,以下是运行它们的方法。每个都是一个 uv 项目,它们都会读取包含代理密钥、基础 URL 以及三个模型名称的根级 .env 文件:

cd openai-demo        # or claude-demo, or adk-demo
uv run python main.py ../milo.jpeg
uv run python main.py ../hl.jpeg

若要从 PDF 重新生成 SFA 表格,请运行 uv run --with pdfplumber python build_additives.py,并通过传递路径提供您自己的标签照片。随附的两个标签均为新加坡真实产品,其评分也与预期相符:牛奶被评为 Nutri-Grade B,而麦乐氏粉状产品冲调后会被评为 C 级。

超越单次处理

仅展示一次完整处理的演示会掩盖其中有趣的部分。在各类 SDK 中添加三种形状相同的元素,能让智能体更加真实,同时也能反映底层 SDK 的特性。

真实数据。这种添加剂检测工具绝非只有七条词的简易词典。build_additives.py会解析官方的SFA PDF文件,该文件中的三个表格采用三种不同的列布局(这本身就颇具挑战性),最终提取出约540种添加剂。在加载时,每个演示版本都会生成一个标准化名称索引,使得标签上的“谷氨酸钠”能与PDF中的正式名称“Monosodium L-glutamate”对应起来。以基础编号为键的E/INS编号索引则确保“500”仍能关联到“500(ii)”。还有一个简短的映射表用于处理PDF中未出现的真实俚语,如“MSG”和“维生素C”。凡是确实不在列表中的项目会直接显示为“不在列表中”,而绝不会出现错误的“允许使用”提示。

记忆与学习。这两者最好结合在一起理解:

  • 解释。每个智能体都会展示其推理过程:在OpenAI的案例中表现为输入式的reasoning字段,而在Claude和ADK中则表现为以## Reasoning为标题的章节。
  • 回忆。首先会运行recall_product工具,调出之前对该产品的任何判定结果,每次运行都会保存自己的判定记录。如果对同一标签进行两次扫描,第二次扫描会将自己与第一次的结论进行比对。
  • 学习。如果某个标签输出的名称表格无法识别,而模型通过e_number_hint提供了正确的数字,那么该名称与数字的对应关系就会被保存在小的agent_memory.json文件中。下次运行时,无需再次询问模型即可直接识别该名称。

这就是系统层面的学习。权重永远不会改变,但智能体能够通过经验不断优化自身的数据与工具。这是一种切实可行、易于实现的学习方式,与微调不同——这些工具包在运行时都不会进行微调操作。每个工具包都包含一个可用于生产环境内存管理的会话层(OpenAI的SQLiteSession、Claude的会话存储机制、ADK的SessionService),而JSON文件则让演示内容更具便携性且便于查看。

它读取的标签越多,性能就越好。那些模型将成分名称映射为数字的照片能为查找功能提供新的别名,而每一个被分析过的产品都会成为下一次运行时可以调用的信息。当“磷酸二钠”首次出现时,系统会依赖模型将其映射为E339;之后,确定性表格便可以直接识别该名称,从而实现更快速、更低成本的处理,并且无需再次调用模型。随着产品数量的增加,agent_memory.json会逐渐发展成由智能体自行编写的别名词典及判定历史记录。只要让多个智能体共享同一个内存文件,其中一个智能体学到的名称就能被所有智能体知晓。模型保持不变,而智能体则可以不断进化。

在实际测试中的最后一个细节是:当要求输入带有名称的E编号时,三种模型都能可靠地完成转换,无需任何辅助即可将“磷酸二钠”转换为E339。这是循环机制在发挥作用——模型具备的通用知识弥补了确定性查找的不足,同时系统会记住转换结果。

各方负责提供什么

在构建、运行并扩展了这三种智能体之后,就可以通过对比每个SDK实际提供的功能与需要手动实现的功能,来梳理整个架构的构成。可以想象一个网格,绿色表示该功能由SDK提供,琥珀色表示部分提供,红色则表示需要自行实现。这些组件同样分为三层:用于构建智能体的开发层、用于运行智能体的运行时层,以及用于管理整个智能体集群的控制层。SDK们属于第一层。

查看开发层的情况,几乎所有项目状态均为绿色;在构建智能体这一方面,各工具包的结论几乎完全一致。而再往下看两层,情况则完全相反:边缘处仅有少数绿色或琥珀色的单元(ADK的工作流与评估功能,以及OpenAI的追踪功能),其余部分则都是三者共有的红色状态。这种对比才是真正的关键发现。在构建智能体方面它们意见一致,而智能体的运行与管理则需由用户自行处理。

SDK的不足之处

开发层正是SDK让工作变得更简单的地方。而运行时层和控制层在SDK中大多缺失,实际上这些也不是SDK应该提供的功能。

先从演示构建过程中出现的缺陷说起。其中一个就是工具网关,运行时层会将其与普通的工具调用区分开来。该框架会忠实调用你的工具,但并不会确保其稳定性。例如,data.gov.sg的HCS API有时会返回带有HTML内容的503状态码,而直接使用response.json()就会导致整个运行过程崩溃(这是我们惨痛的教训)。通过引入_safe_json辅助函数、设置十秒的超时时间以及“不在列表中”时的备用处理方式,这个问题得到了解决,这样一来,上游服务的不稳定问题就变成了可处理的异常,而不会再导致代理程序崩溃。工具内部的重试机制、退避策略以及错误处理方式都由你自行实现,而这些本应是网关应该承担的任务。

第二个差异在于管理内存。这三种方式都包含会话层,即某个节点上的对话状态,但它不同于应用程序将数据存储在可供整个系统访问的位置。此处生成的agent_memory.json(包括学到的别名、recall_product的历史记录,以及通过每个标签不断优化智能体的回写数据)属于上层领域的逻辑,而“运行得越多,性能越好”并非工具框架所能提供的功能。基于真实数据的处理也是如此。将SFA PDF解析为540多个结构化数据项、构建标准化索引和基数查询表:这些均不在SDK的职责范围内,而为智能体提供真实数据需要自行设计流程,而非简单地切换开关即可完成。

工具的并发安全性问题比单纯未处理的情况更为严重,因为问题根源就在于工具框架本身。默认情况下,OpenAI SDK会并行执行各个独立的工具调用,确保这些工具的安全性则需由开发者自行负责。还有一件较为平常的事:生成结果并将其保存到任意位置,这属于普通的应用代码,SDK并不会干预这一过程。

从运行时层来看,代理首先需要托管执行功能,即能够通过终端会话之外的方式被调用,并且要支持多任务同时运行以及相应的扩展能力。它还需要工作负载标识,这是一种用于向工具提供认证信息的机制,这样调用就能以特定工作负载的名义进行,而非通过隐藏在环境变量中的API密钥。此外,它还需要可持久化、可恢复的循环结构,这样在运行过程中出现故障时可以继续执行而非重新启动整个流程。笔记本电脑上的脚本并不具备这些功能,SDK也并未宣称能够提供它们。随着负载的增加,运行时还必须能够扩展代理的数量。

在控制平面中,这三者几乎都不起作用。评估就是第一个例子:“运行无误”并不意味着“结果正确”,而且如何正确衡量代理的质量在很大程度上取决于你。ADK由于其评估框架而属于部分例外情况,而另外两种工具则没有给出明确答案。

接下来是约束机制和提示注入防御。每种工具的输出都是返回给模型的不可信输入,而标签图片或API响应中可能包含“忽略你的规则”之类的指令。由于覆盖范围不均且没有自动化机制,因此威胁模型需要由你自己来构建。

接下来是带有权限范围限定的代理身份功能。这些工具包允许你设置代理角色,但却未明确说明该代理作为目录主体的身份以及其可访问的范围,因此将代理与真实所有者绑定并限制其访问权限的工作就落在了你身上。

连接至单个MCP服务器则是另一回事,因为这三者都提供了相应的客户端。不过,负责对工具进行分类和版本管理、并通过发现功能、资源池、每台服务器的凭证验证及健康检查来管理多台MCP服务器的工具与MCP注册表功能,依然需要由你自行实现。

在更高一层级的代理注册表中也存在同样的问题,那里会发布代理信息,以便其他人在运行时能够发现并调用它们,而非通过硬编码的方式实现。ADK属于部分例外,它支持A2A和代理卡片功能,而另外两种则仍需由你自行处理。

最后是成本与配额管理,即用于记录模型集群操作行为的审计日志、对共享数据存储的管理以及高效的版本控制功能。系统提示词只是你拥有的字符串,而模型本身具有随机性,因此对提示词进行固定、对比和回滚是必须由你自己严格执行的一项工作。

这些并非批评之意。将开发范围限制在智能体循环及其相关组件上确实是一个合理的界限。不过这也意味着,选择合适的SDK大概就能解决生产环境智能体所需条件的一半。

那么应该使用哪种SDK呢?

回到这些工具包本身,以下是用一句话概括各自特点。如果需要智能体操作计算机、处理文件、使用命令行、编写代码或浏览网页,应选择 Claude Agent SDK——它拥有最成熟的运行时环境,需通过手动定义架构,不支持类型化输出,也没有环境变量配置功能。若需要结构化的输入输出且仅需少量 Python 代码,可选择 OpenAI Agents SDK——它在类型化数据往返处理方面速度最快,但需接受默认的并行工具设置以及通过代理导致的追踪功能缺失。当多个智能体需要通过基于真实会话的明确工作流进行协作时,应选择 Google ADK——它的开发界面最为强大,模型选择也最为丰富,不过对于单独运行的简单智能体而言则显得过于复杂。

实际上,这些都是不同的工具,而非同一套体系下的三个版本。由于核心部分——即对各种工具的推理流程——是通用的,因此相同的智能体可以被构建到所有这些工具中。它们的差异在于围绕核心的架构设计,而这一设计应当成为你做出选择的标准。

三次独立构建带来的启示

通过三次独立构建智能体,我们得出了一个明确的结论:智能体循环本身已经是一个得到解决的、标准化的问题,这三套工具都能很好地实现它。真正的差异在于架构设计,比如是手动编写架构还是自动推断架构、输出的是自由文本还是结构化数据,以及是使用集成式运行时、轻量级客户端还是完整框架。这些都属于适配性问题,而非质量问题。只需根据任务需求选择合适的SDK——无论是用于操作计算机的Claude、用于生成结构化结果的OpenAI,还是用于多智能体系统的ADK——这三者中的任何一种都能很好地满足你的需求。

最重要的教训在于边界所在,而这三层则展示了边界的分布位置。开发层由相关工具包掌控,它们能在下午之内将其交付给你。运行时层与控制平面层——即实际操作智能体以及管理整个智能体集群的功能——则归你所有,也是大部分工程工作需要投入的地方。只有在添加了SDK无法提供的功能之后,该分析工具才真正发挥作用:用于提供参考的解析后的SFA表格、通常由工具网关负责的故障处理机制,以及通过每个标签不断优化其性能的内存机制。因此,先根据开发层的匹配度选择SDK,然后再为下面的两层准备预算。

在这背后还隐藏着一个更宏大的故事,它存在于那些更底层的架构中。由于单智能体构建现已成为常规技术,竞争焦点便转向了谁来运行智能体以及谁来管理它们。新的产品正在这些领域涌现:诸如 AWS Bedrock AgentCore 和 Google 的 Gemini Enterprise Agent Runtime 这样的运行时环境,能够承载上述三种工具套件生成的任何智能体;还有 Microsoft 的 Agent 365 与 Google Enterprise 这样的控制平面系统,负责对整个智能体集群进行注册、识别与管控。这些产品几乎逐行对应着该架构中的各个组成部分。问题在于,每家云服务提供商都希望拥有这三层架构,因此你今天选择的 SDK 可能会让你在未来倾向于某家供应商的运行时环境和管理系统。

相关阅读

  • 2026年的AI智能体内存技术:Mem0、Letta、Zep与各类云平台的对比 —— 梳理2026年的智能体内存技术:CoALA内存类型、Mem0、Letta、Zep,以及各类云平台的相关服务、基准测试、数据污染风险,还有哪种设计适合哪种应用场景。
  • 智能体在生产环境中的工具使用与函数调用方法 —— 一家银行的人工智能智能体错误调用了付费API并编造了电话号码。本文将介绍最基本的工具组合、参数来源追踪、三级重试机制以及针对不同工具的监控指标。
  • 智能体内存快照、技能、衰减与分支范围控制 — 为编程智能体设计的嵌入式向量数据库内存层新增的四种功能:可合并的便携式快照、技能激活、带冲突检测的衰减机制,以及基于分支范围的内存管理。
  • 基于 Gemini 3.1 和 ADK 的五智能体客服系统:分诊、调研、执行、质量检测与协调功能 — 利用 Google ADK 和 Gemini 3.1 构建多智能体客服系统:结构化任务传递、质量检测修正循环、Agent Engine 或 Cloud Run 平台、会话管理及成本控制。
  • 通过技能提升智能体能力:重用、筛选、修复与验证 —— 一种包含四个环节的设计,帮助智能体提升自身技能:保存可用代码、优化描述、提出经过审核的修复方案,并在交付前验证输出结果。
  • 利用 Qdrant 的多向量技术与句子隔离功能将 RAG 的令牌成本降低 67% —— 一种用于法律检索的 RAG 流水线,它在 Qdrant 内部通过 ColBERT 重新对内容进行排序、隔离匹配的句子,从而以一定的延迟为代价实现 67.1% 的令牌成本削减。
  • 每位开发者都应保存的可重复使用AI智能体提示语 — 一个涵盖代码审查、测试、调试、设计、文档、Git操作及学习领域的分类提示语库,以及能让任何智能体提示语发挥更好作用的五个习惯。