两种等待发生在不同位置
async with 管理资源的异步进入和退出;async for 消费一个异步可迭代对象,每次取下一项都可能需要等待。它们并不表示循环体内部自动并行。
实验沿用 boot_agent_sandbox 名称,但把真实沙箱替换成字符串;不连接外部服务。chunks 只模拟两段数据,不模拟网络协议。
import asyncio
from contextlib import asynccontextmanager
@asynccontextmanager
async def boot_agent_sandbox():
print("enter")
try:
yield "sandbox"
finally:
print("exit")
async def chunks():
for value in ("A", "B"):
await asyncio.sleep(0)
yield value
async def exercise():
async with boot_agent_sandbox() as sandbox:
print(sandbox)
async for chunk in chunks():
print(chunk)
if __name__ == "__main__":
asyncio.run(exercise())
下载并运行本节 example.py。只使用 Python 标准库;推荐 Python 3.12,最低 Python 3.11。
运行结果:
enter
sandbox
A
B
exit
先看上下文管理器
带 @asynccontextmanager 的异步生成器,在 yield 之前准备资源,把 yield 的值交给 as 后的变量;退出作用域后继续执行后面的清理。finally 使异常退出也有机会清理。
Slime 的 boot_agent_sandbox() 在准备成功后 yield 沙箱,退出时调用 __aexit__()。不要把进入、使用和回收分散到互不关联的调用里理解。
再看异步迭代
async def 中出现 yield,这个函数就产生异步生成器。调用它得到的对象用于异步迭代,不能像普通协程那样直接 await chunks()。
Slime 的流式 rollout 在 async for raw_line in response.aiter_lines() 中逐段处理服务返回的数据;Python 语法本身不绑定 HTTPX 或某种模型服务。
展开 Slime 源码:boot_agent_sandbox
boot_agent_sandboxexamples/coding_agent_rl/generate.py · L123–L134
快照 2f2318653f6f794dddd321eff7c9d4b7b174643f· 查看固定版本源码
CONFIG.boot_retries,
type(e).__name__,
str(e)[:200],
)
await asyncio.sleep(1 + attempt + random.random())
if sb is None:
assert last_err is not None
raise last_err
try:
yield sb
finally:
await sb.__aexit__(None, None, None)展开 Slime 源码:流式响应读取
流式响应读取slime/rollout/sglang_streaming_rollout.py · L123–L138
快照 2f2318653f6f794dddd321eff7c9d4b7b174643f· 查看固定版本源码
sample, "sglang_generate_stream", attrs={"max_new_tokens": sampling_params["max_new_tokens"]}
) as span:
async with client.stream("POST", url, json=payload, headers=headers) as response:
response.raise_for_status()
async for raw_line in response.aiter_lines():
if not raw_line or not raw_line.startswith("data:"):
continue
data_str = raw_line[len("data:") :].strip()
if not data_str or data_str == "[DONE]":
continue
try:
chunk = json.loads(data_str)
except json.JSONDecodeError:
logger.warning("sglang_streaming: skipping non-JSON chunk: %r", data_str[:120])
continue
复述检查
await 等一个结果;async for 逐个等结果;async with 管理资源进入和退出时的等待。async for 的循环体仍然按顺序推进,除非里面另行安排并发任务。
语义核对:asynccontextmanager、异步迭代。
本节对应的全部源码位置
下面把同类用法在 Slime 中的每个扫描位置接回本节讲解;点击文件名可查看完整源码快照。重复出现的 API 共用解释,具体输入与清理由所在函数决定。
展开本节 63 条源码记录
| 记录 / 讲解 | 源码位置 | 原代码 |
|---|---|---|
| occ-000003 | examples/coding_agent_rl/generate.py:28 <module> | from contextlib import asynccontextmanager |
| occ-000005 | examples/coding_agent_rl/generate.py:95 <module> | @asynccontextmanager |
| occ-000006 | examples/coding_agent_rl/generate.py:96 <module> | async def boot_agent_sandbox(image: str, instance_id: str) -> AsyncIterator[E2BSandbox]: |
| occ-000008 | examples/coding_agent_rl/generate.py:108 boot_agent_sandbox | async with _BOOT_SEM: |
| occ-000016 | examples/coding_agent_rl/generate.py:132 boot_agent_sandbox | yield sb |
| occ-000020 | examples/coding_agent_rl/generate.py:202 generate | async with asyncio.timeout(CONFIG.rollout_guard_sec): |
| occ-000022 | examples/coding_agent_rl/generate.py:203 generate | async with boot_agent_sandbox(md["image"], instance_id) as sb: |
| occ-000102 | examples/coding_agent_rl/swe.py:269 _grade_scaleswe | async with E2BSandbox(image) as ev: |
| occ-000148 | examples/coding_agent_rl/swe.py:465 _grade_swebench | async with E2BSandbox(image) as ev: |
| occ-000197 | examples/retool/generate_with_retool.py:192 execute_predictions | async with SEMAPHORE: |
| occ-000217 | examples/retool/tool_sandbox.py:342 ToolRegistry.execute_tool | async with SEMAPHORE: |
| occ-000229 | examples/search-r1/generate_with_search.py:129 execute_predictions | async with SEMAPHORE: |
| occ-000240 | examples/search-r1/google_search_server.py:52 fetch | async with semaphore: |
| occ-000241 | examples/search-r1/google_search_server.py:54 fetch | async with session.get(url, headers=headers) as response: |
| occ-000248 | examples/search-r1/google_search_server.py:68 fetch_all | async with aiohttp.ClientSession(timeout=timeout, connector=connector) as session: |
| occ-000253 | examples/search-r1/google_search_server.py:99 google_search | async with aiohttp.ClientSession(**session_kwargs) as session: |
| occ-000254 | examples/search-r1/google_search_server.py:100 google_search | async with session.post( |
| occ-000265 | examples/search-r1/local_search_server.py:66 local_search | async with aiohttp.ClientSession(**session_kwargs) as session: |
| occ-000266 | examples/search-r1/local_search_server.py:67 local_search | async with session.post(search_url, json=payload, timeout=timeout_obj) as resp: |
| occ-000402 | slime/agent/adapters/common.py:475 call_sglang_generate | async with aiohttp.ClientSession(timeout=timeout) as sess, sess.post( |
| occ-000413 | slime/agent/adapters/common.py:521 _abort_sglang_request | async with aiohttp.ClientSession(timeout=timeout) as session: |
| occ-000415 | slime/agent/adapters/common.py:525 _abort_sglang_request.abort_worker | async with session.post(f"{url}/abort_request", json={"rid": rid}): |
| occ-000416 | slime/agent/adapters/common.py:530 _abort_sglang_request | async with session.get(f"{sglang_url}/workers") as response: |
| occ-000612 | slime/backends/megatron_utils/server/megatron_server.py:452 _build_http_app.update_from_disk | async with update_lock: |
| occ-000624 | slime/backends/megatron_utils/server/megatron_server.py:504 _build_http_app.update_from_disk | async with update_lock: |
| occ-000671 | slime/backends/sglang_utils/server_control.py:38 get_live_router_workers | async with httpx.AsyncClient(timeout=timeout, trust_env=False) as client: |
| occ-000838 | slime/data/transport.py:376 run_rollout_io | async with _async_limits[key]: |
| occ-001073 | slime/rollout/on_policy_distillation.py:26 reward_func | async with aiohttp.ClientSession(**session_kwargs) as session: |
| occ-001074 | slime/rollout/on_policy_distillation.py:27 reward_func | async with session.post(args.rm_url, json=payload) as resp: |
| occ-001078 | slime/rollout/rm_hub/__init__.py:43 remote_rm | async with session.post(args.rm_url, json=payload) as resp: |
| occ-001132 | slime/rollout/sglang_rollout.py:329 generate_and_rm | async with state.semaphore: |
| occ-001182 | slime/rollout/sglang_streaming_rollout.py:125 generate_streaming | async with client.stream("POST", url, json=payload, headers=headers) as response: |
| occ-001183 | slime/rollout/sglang_streaming_rollout.py:127 generate_streaming | async for raw_line in response.aiter_lines(): |
| occ-001353 | slime_plugins/rollout_buffer/rollout_buffer_example.py:140 get_rollout_data | async with aiohttp.ClientSession() as session: |
| occ-001354 | slime_plugins/rollout_buffer/rollout_buffer_example.py:142 get_rollout_data | async with session.post( |
| occ-001483 | tests/test_agent/test_adapters.py:170 test_anthropic_messages_nonstream_records_token_segments.run_case | async with FakeSGLangServer([[(-0.1, 101), (-0.2, 102)]]) as sglang: |
| occ-001492 | tests/test_agent/test_adapters.py:206 test_openai_chat_completions_nonstream_records_token_segments.run_case | async with FakeSGLangServer([[(-0.3, 201)]]) as sglang: |
| occ-001501 | tests/test_agent/test_adapters.py:240 test_anthropic_messages_streams_blocks.run_case | async with FakeSGLangServer([[(-0.1, 301)]]) as sglang: |
| occ-001511 | tests/test_agent/test_adapters.py:274 test_openai_chat_completions_streams_chunks_until_done.run_case | async with FakeSGLangServer([[(-0.1, 401)]]) as sglang: |
| occ-001520 | tests/test_agent/test_adapters.py:315 test_anthropic_multiturn_wire_roundtrip_and_token_capture.run_case | async with FakeSGLangServer([[(-0.5, 700), (-0.5, 701)], [(-0.4, 800)]]) as sglang: |
| occ-001531 | tests/test_agent/test_adapters.py:385 test_max_turns_per_sid_returns_429.run_case | async with FakeSGLangServer([[(-0.1, 501)], [(-0.1, 502)]]) as sglang: |
| occ-001541 | tests/test_agent/test_agent_rollout_cpu.py:27 <module> | import contextlib |
| occ-001543 | tests/test_agent/test_agent_rollout_cpu.py:58 <module> | @contextlib.asynccontextmanager |
| occ-001544 | tests/test_agent/test_agent_rollout_cpu.py:59 <module> | async def _timeout_shim(_delay): |
| occ-001545 | tests/test_agent/test_agent_rollout_cpu.py:60 _timeout_shim | yield |
| occ-001551 | tests/test_agent/test_agent_rollout_cpu.py:114 _anthropic_agent | async with aiohttp.ClientSession(trust_env=False) as sess: |
| occ-001552 | tests/test_agent/test_agent_rollout_cpu.py:116 _anthropic_agent | async with sess.post( |
| occ-001569 | tests/test_agent/test_agent_rollout_cpu.py:242 _codex_agent | async with aiohttp.ClientSession(trust_env=False) as sess: |
| occ-001570 | tests/test_agent/test_agent_rollout_cpu.py:244 _codex_agent | async with sess.post( |
| occ-002042 | tests/test_server_control.py:264 test_http_transport_preserves_streams_timeouts_and_connection_budget.check | async with httpx.AsyncClient(transport=http_utils._ShardedHTTPTransport(concurrency)) as client: |
| occ-002044 | tests/test_server_control.py:268 test_http_transport_preserves_streams_timeouts_and_connection_budget.check.request | async with client.stream("POST", url, content=body) as response: |
| occ-002118 | tests/test_streaming_rollout.py:6 <module> | from contextlib import asynccontextmanager, nullcontext |
| occ-002122 | tests/test_streaming_rollout.py:170 _http_stream | async def chunk_lines(): |
| occ-002123 | tests/test_streaming_rollout.py:172 _http_stream.chunk_lines | yield "data: " + json.dumps(chunk) |
| occ-002124 | tests/test_streaming_rollout.py:176 _http_stream | @asynccontextmanager |
| occ-002125 | tests/test_streaming_rollout.py:177 _http_stream | async def stream(method, url, json, headers): |
| occ-002126 | tests/test_streaming_rollout.py:182 _http_stream.stream | yield response |
| occ-002131 | tests/test_streaming_rollout.py:332 test_server_abort_keeps_last_observed_prefix | async def lines(): |
| occ-002132 | tests/test_streaming_rollout.py:334 test_server_abort_keeps_last_observed_prefix.lines | yield "data: " + json.dumps(_chunk("a", [[-0.1, 11, None]], 1)) |
| occ-002138 | tests/test_streaming_rollout.py:358 test_stream_cancellation_closes_request_and_keeps_prefix | async def lines(): |
| occ-002139 | tests/test_streaming_rollout.py:359 test_stream_cancellation_closes_request_and_keeps_prefix.lines | yield "data: " + json.dumps( |
| occ-002164 | tests/test_streaming_rollout.py:419 test_unrelated_stream_cancellation_propagates | async def lines(): |
| occ-002169 | tests/test_streaming_rollout.py:422 test_unrelated_stream_cancellation_propagates.lines | yield "unreachable" |
专题首页 · 上一节 · 下一节:11 · ContextVar:让并发任务保留各自的上下文 · 源码与教程对照 · 全部用法