数据截至 (上游 commit a4eaba4a56f9)
两个入口 — query() 与 ClaudeSDKClient
本章讲:SDK 给你两个门,怎么选、各自的消息循环长什么样、它们内部其实共用同一套机制。
1. 先看结论:该用哪个
| 维度 | query() | ClaudeSDKClient |
|---|---|---|
| 交互方向 | 单向:一次性发完、收完 | 双向:随时发、随时收 |
| 状态 | 无状态,每次独立 | 有状态,维持一条连接 |
| 能否打断 | 否 | 能(interrupt()) |
| 能否中途换模型/权限 | 否 | 能(set_model / set_permission_mode) |
| 典型场景 | 脚本、批处理、CI、一问一答 | 聊天 UI、REPL、需要看响应再决定下一步 |
| 代码形态 | async for m in query(...) | async with ClaudeSDKClient() as c: |
这张对照表直接抄自两者的 docstring(query.py:24 与 client.py:34 都专门写了"何时用我 / 何时用另一个")。
一句话选择: 输入一次给全、不用中途插话 → query();需要来回、需要打断、需要根据回答再发下一句 → ClaudeSDKClient。
2. query():一发一收的迭代器
2.1 它要解决的小问题
最常见的需求就是"问一句、把回答流式读出来"。query() 把连接、初始化、收尾全藏起来,只暴露一个异步迭代器。
2.2 原理演示
# 示意,非源码:query() 的使用骨架
from claude_agent_sdk import query, ClaudeAgentOptions
options = ClaudeAgentOptions(system_prompt="You are terse", max_turns=1)
async for message in query(prompt="Tell me a joke", options=options):
print(message) # 逐条产出:可能是 AssistantMessage、ResultMessage 等
2.3 真实实现
query() 本身极薄——它只是把活儿转给 InternalClient.process_query(query.py:118-126):
# 真实源码 query.py:118
if options is None:
options = ClaudeAgentOptions()
client = InternalClient()
async for message in client.process_query(prompt=prompt, options=options, transport=transport):
yield message
关键点:prompt 可以是 str(一次性),也可以是 AsyncIterable[dict](流式多条,但仍是单向——全发完才收)。prompt 的字典结构在 docstring 里写死了:{"type":"user","message":{...}}(query.py:47)。
2.4 一个易错点:query() 也"永远 streaming"
很多人以为 str prompt 走的是"非流式"路径。实则不然:InternalClient 里创建 Query 时写死 is_streaming_mode=True(_internal/client.py:137,注释明说"Always streaming internally, matching TypeScript SDK")。差别只在于:字符串 prompt 会在 initialize 之后作为一条 user 消息写进 stdin,然后 spawn_task(query.wait_for_result_and_end_input()) 等第一个 result 到了再关 stdin(_internal/client.py:181-186)。
3. ClaudeSDKClient:有状态的连接
3.1 它要解决的小问题
聊天类应用需要:连上一次、来回多轮、根据 Claude 的回答 再决定发什么、必要时打断。query() 的"发完才收"满足不了,于是有了 ClaudeSDKClient。
3.2 典型用法(真实来自 examples/streaming_mode.py)
# 示意,非源码:交互循环的骨架
async with ClaudeSDKClient(options=options) as client:
await client.query("What's the capital of France?")
async for msg in client.receive_response(): # 收到 ResultMessage 自动停
if isinstance(msg, AssistantMessage):
for block in msg.content:
if isinstance(block, TextBlock):
print("Claude:", block.text)
# 还能继续 await client.query("下一句") ...