エージェント開発者のための非同期 Python
asyncio の基礎、async def、await、イベントループなど、非同期処理のメンタルモデルを学びます。
「エージェント開発者のための非同期 Python」はCoddyKit上の無料AI Agentsレッスンです。 これはレッスン1/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはAI Agents学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 AI Agentsコースには全4レッスンが含まれています。
エージェントに非同期処理を使う理由
エージェントは、LLM API、Webリクエスト、データベースクエリなど、I/Oバウンドの呼び出しを多数実行します。同期コードでは、これらの呼び出しが完了するまで何もせずに待機します。非同期コードでは、待機中に別の処理を実行できるため、スループットが大幅に向上します。
非同期処理の基本:async def と await
async defはコルーチン関数を宣言します。awaitは、待機中の処理が完了するまで実行を一時停止し、その間にイベントループが他のコルーチンを実行できるようにします。
import asyncio
async def fetch_data(source: str) -> str:
print(f'Starting fetch from {source}')
await asyncio.sleep(1) # Simulates a network call
print(f'Finished fetch from {source}')
return f'Data from {source}'
async def main():
# Sequential: takes 2 seconds total
result1 = await fetch_data('source-A')
result2 = await fetch_data('source-B')
print('Sequential results:', result1, result2)
asyncio.run(main())
# asyncio.run() starts the event loop and runs main()
# It is the entry point for async programsイベントループ
イベントループはasyncioの中核です。コルーチンとI/Oコールバックのキューを管理し、それらの準備ができた時点で実行します。すべての非同期コードは、単一スレッド上のイベントループ内で実行されます。
import asyncio
async def task_a():
print('Task A: start')
await asyncio.sleep(2)
print('Task A: done')
async def task_b():
print('Task B: start')
await asyncio.sleep(1)
print('Task B: done')
async def main():
# asyncio.gather runs both tasks concurrently
# Total time: ~2 seconds (not 3)
await asyncio.gather(task_a(), task_b())
print('Both tasks complete')
# Expected output order:
# Task A: start
# Task B: start
# Task B: done <- after 1s
# Task A: done <- after 2s
# Both tasks complete
asyncio.run(main())コルーチンとスレッドの違い
コルーチンは協調的に動作し、awaitで明示的に制御を譲ります。スレッドはプリエンプティブに動作し、OSがいつでもスレッドを切り替えられます。コルーチンは軽量で、I/O処理ではGILの問題がなく、動作を把握しやすいという利点があります。
import asyncio
import threading
import time
# Thread approach: multiple OS threads
def thread_worker(name):
print(f'Thread {name}: start')
time.sleep(1) # Blocks the thread
print(f'Thread {name}: done')
threads = [threading.Thread(target=thread_worker, args=(i,)) for i in range(3)]
for t in threads:
t.start()
for t in threads:
t.join()
print('---')
# Coroutine approach: single-threaded event loop
async def coro_worker(name):
print(f'Coro {name}: start')
await asyncio.sleep(1) # Suspends, does NOT block other coroutines
print(f'Coro {name}: done')
async def main():
await asyncio.gather(*[coro_worker(i) for i in range(3)])
asyncio.run(main())
# Both approaches run 3 tasks in ~1 second total, but coroutines use one threadasyncio.run()のエントリーポイント
asyncio.run()は新しいイベントループを作成し、指定されたコルーチンが完了するまで実行してから、ループを閉じます。Python 3.7以降の非同期プログラムで標準的に使用するエントリーポイントです。
import asyncio
async def agent_main():
print('Agent starting')
# All agent async work goes here
results = await asyncio.gather(
asyncio.sleep(0.1), # Simulated LLM call
asyncio.sleep(0.1), # Simulated DB query
)
print('Agent done')
return 'complete'
# Run the agent
result = asyncio.run(agent_main())
print('Result:', result)
# WRONG: calling asyncio.run() inside an already-running event loop
# In Jupyter notebooks, use: await agent_main() directly
# Or: nest_asyncio.apply() then asyncio.run()よくある間違い:非同期コンテキストでのブロッキング
非同期コード内では、ブロッキング関数(time.sleep、requests.get、同期的なファイルI/O)を決して呼び出さないでください。イベントループ全体がブロックされ、すべての並行性が失われます。
import asyncio
import time
import httpx
# WRONG: blocks the event loop
async def bad_agent_step():
time.sleep(2) # Blocks all other coroutines
# requests.get(url) # Also blocks - do NOT use requests in async code
return 'done'
# RIGHT: use async equivalents
async def good_agent_step():
await asyncio.sleep(2) # Suspends, other coroutines can run
async with httpx.AsyncClient() as client:
response = await client.get('https://api.example.com/data')
return response.text
# For CPU-intensive work: use run_in_executor
import concurrent.futures
async def cpu_intensive_step(data: str):
loop = asyncio.get_event_loop()
with concurrent.futures.ProcessPoolExecutor() as pool:
result = await loop.run_in_executor(pool, expensive_cpu_fn, data)
return result
def expensive_cpu_fn(data):
# CPU-bound work runs in separate process
return data.upper()
print('Blocking vs non-blocking patterns demonstrated')awaitの付け忘れ:サイレントバグ
awaitを付け忘れてもエラーは発生せず、結果の代わりにコルーチンオブジェクトが返されます。これは、後続処理でのエラーや空の結果を引き起こすサイレントバグです。
import asyncio
async def get_answer() -> str:
await asyncio.sleep(0.1)
return 'The answer is 42'
async def bad_call():
result = get_answer() # WRONG: forgot await
print(type(result)) # <class 'coroutine'> - not a string!
# Using result as a string here causes AttributeError or wrong behavior
return result
async def good_call():
result = await get_answer() # CORRECT
print(type(result)) # <class 'str'>
return result
async def main():
bad = await bad_call()
print('Bad result:', bad) # coroutine object, not the string
good = await good_call()
print('Good result:', good) # 'The answer is 42'
# Clean up the uncollected coroutine
if asyncio.iscoroutine(bad):
bad.close()
asyncio.run(main())ネストされたイベントループの問題
すでに実行中のイベントループ(JupyterやFastAPIなど)の中でasyncio.run()を呼び出すと、RuntimeErrorが発生します。解決策は、awaitを直接使用するか、ノートブックではnest_asyncioを使用することです。
import asyncio
async def my_agent_coroutine():
await asyncio.sleep(0.1)
return 'done'
# In FastAPI or other async frameworks, the event loop is already running
# Use await directly in async endpoints:
async def fastapi_endpoint():
# WRONG inside async context:
# result = asyncio.run(my_agent_coroutine()) # RuntimeError!
# CORRECT: just await
result = await my_agent_coroutine()
return result
# In Jupyter notebooks: install nest_asyncio
# import nest_asyncio
# nest_asyncio.apply()
# Then asyncio.run() works
# Detect if running in event loop:
def run_agent(coro):
try:
loop = asyncio.get_running_loop()
# Already in async context
import concurrent.futures
with concurrent.futures.ThreadPoolExecutor() as pool:
future = pool.submit(asyncio.run, coro)
return future.result()
except RuntimeError:
# No running loop
return asyncio.run(coro)
print('Nested event loop solution defined')asyncio.create_task
asyncio.create_task()を使用すると、コルーチンをすぐに待機せず、実行するようスケジュールできます。これにより、複数のタスクを開始し、後で完了を待機できます。
import asyncio
async def background_job(job_id: int) -> str:
await asyncio.sleep(0.5)
return f'Job {job_id} completed'
async def main():
# Start all tasks without waiting
task1 = asyncio.create_task(background_job(1))
task2 = asyncio.create_task(background_job(2))
task3 = asyncio.create_task(background_job(3))
# Do other work while tasks run
print('Tasks started, doing other work...')
await asyncio.sleep(0.1)
print('Other work done')
# Now wait for all tasks
results = await asyncio.gather(task1, task2, task3)
print('All results:', results)
# Or wait for the first to complete
task_a = asyncio.create_task(background_job(4))
task_b = asyncio.create_task(background_job(5))
done, pending = await asyncio.wait([task_a, task_b], return_when=asyncio.FIRST_COMPLETED)
for t in pending:
t.cancel() # Cancel remaining tasks
print('First result:', done.pop().result())
asyncio.run(main())非同期コンテキストマネージャー
多くの非同期ライブラリでは、async withを使う非同期コンテキストマネージャーを使用します。これにより、非同期コード内で接続やリソースの適切なセットアップと後処理が保証されます。
import asyncio
import httpx
async def fetch_multiple_urls(urls: list) -> list:
# async with ensures the client is properly closed
async with httpx.AsyncClient(timeout=10.0) as client:
# Fetch all URLs concurrently
tasks = [client.get(url) for url in urls]
responses = await asyncio.gather(*tasks, return_exceptions=True)
results = []
for url, response in zip(urls, responses):
if isinstance(response, Exception):
results.append({'url': url, 'error': str(response)})
else:
results.append({'url': url, 'status': response.status_code})
return results
# Async generators for streaming
async def stream_agent_events():
events = ['thinking', 'searching', 'generating', 'done']
for event in events:
await asyncio.sleep(0.2) # Simulate event arrival
yield event
async def consume_stream():
async for event in stream_agent_events():
print(f'Event: {event}')
asyncio.run(consume_stream())非同期コードでのエラー処理
asyncio.gather(..., return_exceptions=True)を使用すると、バッチ全体を中断せずに個々のタスクの失敗を捕捉できます。各結果について、例外の型を確認してください。
import asyncio
async def might_fail(task_id: int) -> str:
await asyncio.sleep(0.1)
if task_id == 2:
raise ValueError(f'Task {task_id} failed')
return f'Task {task_id} succeeded'
async def robust_gather():
tasks = [might_fail(i) for i in range(1, 5)]
# return_exceptions=True: exceptions are returned as values, not raised
results = await asyncio.gather(*tasks, return_exceptions=True)
successes = []
failures = []
for i, result in enumerate(results):
if isinstance(result, Exception):
failures.append({'task': i + 1, 'error': str(result)})
else:
successes.append(result)
print(f'Succeeded: {len(successes)}, Failed: {len(failures)}')
print('Failures:', failures)
return successes, failures
asyncio.run(robust_gather())理解度チェック:非同期Python
エージェント開発における非同期Pythonについての理解度を確認します。
非同期Pythonのまとめ
エージェント開発者が押さえるべき非同期Pythonの主なルールは次のとおりです。I/Oバウンドの処理にはasync defとawaitを使用すること、非同期コンテキストでブロッキング関数を決して呼び出さないこと、並列実行にはasyncio.gather()を使用すること、障害に強い並列呼び出しにはreturn_exceptions=Trueを使用すること、リソース管理にはasync withを使用すること、CPU負荷の高い処理はプロセスプールエグゼキューターで実行することです。
よくある質問
「エージェント開発者のための非同期 Python」レッスンは無料ですか?
はい。「エージェント開発者のための非同期 Python」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、AI Agentsコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 AI Agentsコースには全4レッスンが含まれています。
「エージェント開発者のための非同期 Python」で何を学びますか?
asyncio の基礎、async def、await、イベントループなど、非同期処理のメンタルモデルを学びます。 ブラウザで直接実行するハンズオンコードでAI Agentsを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
AI Agentsを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのAI Agentsは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン1/4です。
「エージェント開発者のための非同期 Python」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このAI Agentsレッスンでコードを書いて実行できますか?
はい。すべてのAI Agentsレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- エージェント開発者のための非同期 Python
- イベントキューとメッセージブローカー
- ノンブロッキングなツールの並列実行
- 非同期エージェントフレームワーク:LangChain とその先