F5.2 F5

query() vs ClaudeSDKClient:无状态 vs 有状态

Agent SDK 给你两个接口。选哪个归结为一个问题:下一次交互需不需要知道上一次发生了什么?

query():发完即忘

query() 把一个 prompt 作为独立操作处理。不需要建立会话、不需要清理、没有残留状态。你调用它,它跑完整的 agentic 循环(包括工具调用),然后返回结果。下一次 query() 调用对上一次做了什么一无所知。

这适合自包含的任务:分类一个文档、从发票里提取数据、回答一个独立的问题。每个操作本身就是完整的。

ClaudeSDKClient:跨轮次的上下文

ClaudeSDKClient 在一个会话内的多次 query() 调用之间维持对话状态。你说”看看 auth.ts”,然后接着问”这个函数在哪里被调用了”,client 知道你说的是哪个函数——它保留了第一次交换的上下文。

它作为 async context manager 使用(async with ClaudeSDKClient(...) as client),自动管理会话生命周期。状态在会话期间存在于内存中。进程死了,状态就没了——没有自动的磁盘持久化。要扛过重启需要开发者自己实现持久化。

两个都支持工具

一个关键误解:“query() 不能用工具。“错了。query() 在单次调用内跑完整的 agentic 循环。如果任务需要调 Read,再调 Grep,再调 Edit 才能完成,这些全在一次 query() 调用内发生。工具使用不限于 ClaudeSDKClient

区别在于跨调用的状态,不是调用内的能力。两个接口都支持工具,都接受 ClaudeAgentOptions 配置,都用同一个模型。

选对接口

  • 100 个独立文档要处理 → 100 次 query() 调用。每个自包含。用 ClaudeSDKClient 会累积无关的跨文档上下文。
  • 客服对话有后续问题ClaudeSDKClient。后续问题引用之前的上下文。
  • 一次性工单分类query()。分类完就走。
  • 调试会话(“读这个文件,再查那个函数”)→ ClaudeSDKClient。每一步都基于上一步。

不要为了”一致性”在简单任务上用 ClaudeSDKClient——它多了不必要的会话生命周期复杂性。也不要用 query() 手动拼接历史记录来做多轮——那正是 ClaudeSDKClient 自动化的事。


一句话总结: 独立的单次任务用 query(),需要跨调用上下文的多轮交互用 ClaudeSDKClient——两者都支持工具和完整的 agentic 循环。