はじめに
七尾百合子さん、お誕生日 288日目 おめでとうございます! nikkieです。
AWS ミリしらでも Strands Agents は使い出せます!
3行でAIエージェントを作ったときに引っかかったことを掘り下げました。
目次
入出力と計算の分離、してますか?
みのるんさんのハンズオン記事を元に Strands Agents を触りました。タキクゥーン
from strands import Agent from strands.models.openai import OpenAIModel agent = Agent(model=OpenAIModel(model_id="gpt-4o")) agent("JAWS-UG主催のAI Builders Dayはどこで開催される?")
これだけで GPT-4o を使ったAIエージェントができたのですが、引っかかるポイントが。
ただ入出力と計算が分離できてないのはいただけないですね。
print(agent("..."))であってほしいです
要はagent("...")が中で OpenAI の API の呼び出しと、print をどちらもやっていて、返り値がないように見えたということです。
再利用しにくいのではないかという疑義が生じました。
Callback Handlers
上記コードの2行だと分かりづらいですが、結論としては入出力と計算は分離できています!
agent = Agent(model=OpenAIModel(model_id="gpt-4o")) agent("JAWS-UG主催のAI Builders Dayはどこで開催される?")
理解の肝は「Callback Handlers」
デフォルトではPrintingCallbackHandlerとなります。
Agentのcallback_handler引数にNoneを指定することで、callback handler を無効化できます。
agent = Agent(model=OpenAIModel(model_id="gpt-4o"), callback_handler=None) agent("JAWS-UG主催のAI Builders Dayはどこで開催される?")
デフォルトのPrintingCallbackHandlerの代わりに無効化したので、このコードは(OpenAI API を呼び出してますが)なにも出力しません
入出力と計算が分離されていることを示す実装例です。
Agent.__call__()の返り値はAgentResultです1。
その__str__()により、最終応答のテキストがprintされます2。
実装callback_handler.py
https://github.com/strands-agents/sdk-python/blob/v1.20.0/src/strands/handlers/callback_handler.py
This module provides handlers for formatting and displaying events from the agent.
3つの callback handler が実装されています。
PrintingCallbackHandlerverbose_tool_use引数がデフォルトでTrueです。「Tool #1」のような出力もしてくれます
CompositeCallbackHandler- 複数の callback handler を組み合わせられる仕組みが用意されています。拡張性3は申し分なし!
null_callback_handler- 何も出力しません
Agentの引数に応じて以下のように選択されます。
https://github.com/strands-agents/sdk-python/blob/v1.20.0/src/strands/agent/agent.py#L189-L198
if isinstance(callback_handler, _DefaultCallbackHandlerSentinel): self.callback_handler = PrintingCallbackHandler() elif callback_handler is None: self.callback_handler = null_callback_handler else: self.callback_handler = callback_handler
callback_handler引数が指定されていない場合4、PrintingCallbackHandlercallback_handler引数がNoneと指定された場合、null_callback_handlercallback_handler引数がNone以外で指定されている(=ユーザがハンドラを渡している)場合、それが設定されます
ちなみにcallback_handlerが呼ばれているのはこちらです(stream_async())。
https://github.com/strands-agents/sdk-python/blob/v1.20.0/src/strands/agent/agent.py#L586-L598
終わりに
Strands Agents で Agent インスタンスを agent("...") と呼び出しただけで回答が出力されるのは、Callback Handlers によるものでした。
- Agent 初期化時に、デフォルトでは
PrintingCallbackHandlerが設定されるagent("...")で回答が出力されるのはこのためagent("...")が LLM の呼び出しと返り値のprintをどちらもやっているわけではない。AgentResultを返している
Agent(callback_handler=None)で callback handler を無効化できる- その場合でも出力するには
print(agent("..."))となる
- その場合でも出力するには
懸念が解消したので、ガッツリ使っていけそうです。
samples にはいくつか callback handler の例もありそうでした
- https://github.com/strands-agents/sdk-python/blob/v1.20.0/src/strands/agent/agent.py#L321↩
- https://github.com/strands-agents/sdk-python/blob/v1.20.0/src/strands/agent/agent_result.py#L37-L57 具体例として https://github.com/strands-agents/sdk-python/blob/v1.20.0/tests/strands/agent/test_agent_result.py#L55-L68↩
- 類例 ↩
- sentinel の実装です ↩