OpenAI Agents SDKのサンプルコードを読む（Agents as tools）

# 読んだサンプルコード
* [agents_as_tools.py](https://github.com/openai/openai-agents-python/blob/main/examples/agent_patterns/agents_as_tools.py)
    * OpenAI Agents SDKのCommon agentic patternsの[Agents as tools](https://github.com/openai/openai-agents-python/blob/main/examples/agent_patterns/README.md#agents-as-tools)のサンプル

# Agents as toolsとは
* エージェントをツールとして使用すること
* ツールとして呼び出されたエージェントは独立して動作し、結果を元のエージェントに返す
* その結果を次のステップで使用することができる
* Handoffsとの違い
    * Handoffsでは引き継ぎ先のエージェントが過去の会話履歴を参照し、引き継ぎ後の会話のオーナーとなるが、Agents as toolsでは別のエージェントをツールとして呼び出す
        * 以下が公式の説明
        > The mental model for handoffs is that the new agent "takes over". It sees the previous conversation history, and owns the conversation from that point onwards. However, this is not the only way to use agents. You can also use agents as a tool - the tool agent goes off and runs on its own, and then returns the result to the original agent.
        > 
        > For example, you could model the translation task above as tool calls instead: rather than handing over to the language-specific agent, you could call the agent as a tool, and then use the result in the next step. This enables things like translating multiple languages at once.
        > 
        > See the [agents_as_tools.py](https://github.com/openai/openai-agents-python/blob/main/examples/agent_patterns/agents_as_tools.py) file for an example of this.
        
        （[Agents as tools](https://github.com/openai/openai-agents-python/blob/main/examples/agent_patterns/README.md#agents-as-tools)より）

# agents_as_tools.py

```python:agents_as_tools.py
import asyncio

from agents import Agent, ItemHelpers, MessageOutputItem, Runner, trace

"""
This example shows the agents-as-tools pattern. The frontline agent receives a user message and
then picks which agents to call, as tools. In this case, it picks from a set of translation
agents.
"""
```
* `import`
    * 以下については後述
        * `ItemHelpers`
        * `MessageOutputItem`
* docstring
    * frontlineのagentがユーザーのメッセージを受け、どのサブagentをツールとして呼び出すかを選択する。このサンプルコードではスペイン語、フランス語、イタリア語のそれぞれのエージェントから選択する。


```python:agents_as_tools.py
spanish_agent = Agent(
    name="spanish_agent",
    instructions="You translate the user's message to Spanish",
    handoff_description="An english to spanish translator",
)

french_agent = Agent(
    name="french_agent",
    instructions="You translate the user's message to French",
    handoff_description="An english to french translator",
)

italian_agent = Agent(
    name="italian_agent",
    instructions="You translate the user's message to Italian",
    handoff_description="An english to italian translator",
)
```
* ツールとして呼び出すサブエージェントのインスタンス生成
    * コンストラクタの引数について
        * `name`はエージェントの名前
        * `instructions`はシステムプロンプト
        * `handoff_description`については以下の[公式説明](https://github.com/openai/openai-agents-python/blob/f9763495b86afcf0c421451a92200e1141fa8dcb/src/agents/agent.py#L97)がある
            ```python:agent.py
                handoff_description: str | None = None
                """A description of the agent. This is used when the agent is used as a handoff, so that an
                LLM knows what it does and when to invoke it.
                """
            ```
            * この`handoff_description`はhandoffとして使用される際に使われるとのこと。今回はtoolとしての呼び出しなので実際には使用されないものと思われる。

```python:agents_as_tools.py
orchestrator_agent = Agent(
    name="orchestrator_agent",
    instructions=(
        "You are a translation agent. You use the tools given to you to translate."
        "If asked for multiple translations, you call the relevant tools in order."
        "You never translate on your own, you always use the provided tools."
    ),
    tools=[
        spanish_agent.as_tool(
            tool_name="translate_to_spanish",
            tool_description="Translate the user's message to Spanish",
        ),
        french_agent.as_tool(
            tool_name="translate_to_french",
            tool_description="Translate the user's message to French",
        ),
        italian_agent.as_tool(
            tool_name="translate_to_italian",
            tool_description="Translate the user's message to Italian",
        ),
    ],
)
```
* `orchestrator_agent`の生成
* 各サブエージェントを`as_tool()`メソッドを使用してTool型で登録している

```python:agents_as_tools.py
synthesizer_agent = Agent(
    name="synthesizer_agent",
    instructions="You inspect translations, correct them if needed, and produce a final concatenated response.",
)
```
* `synthesizer_agent`の生成
* このエージェントは`orchestrator_agent`の会話履歴を受け取り翻訳のチェックを行い、必要であれば修正を行う

```python:agents_as_tools.py
async def main():
    msg = input("Hi! What would you like translated, and to which languages? ")
```
* キーボード入力で翻訳元のテキストと翻訳先の言語を受け取る
```python:agents_as_tools.py
    # Run the entire orchestration in a single trace
    with trace("Orchestrator evaluator"):
        orchestrator_result = await Runner.run(orchestrator_agent, msg)

        for item in orchestrator_result.new_items:
            if isinstance(item, MessageOutputItem):
                text = ItemHelpers.text_message_output(item)
                if text:
                    print(f"  - Translation step: {text}")
```
* 一連の会話を単一のTrace内に記録する
* `orchestrator_agent`の実行
* `result.new_items`（`list[RunItem]`）内の`MessageOutputItem`型の要素のみを`print`している
    * `MessageOutputItem`は`RunItemBase`を継承するクラスでLLMからの1件のメッセージを表す
    * `ItemHelpers.text_message_output()`は`MessageOutputItem`型の引数を取りそこからテキストコンテンツを抽出して返す
```python:agents_as_tools.py
        synthesizer_result = await Runner.run(
            synthesizer_agent, orchestrator_result.to_input_list()
        )

    print(f"\n\nFinal response:\n{synthesizer_result.final_output}")
```
* `synthesizer_agent`の実行
* 先に実行した`orchestrator_agent`の会話履歴をinputとして使っている
* このエージェントが翻訳をチェックし、必要であれば修正を行う
* 最終アウトプットを`print`で表示
```python:agents_as_tools.py
if __name__ == "__main__":
    asyncio.run(main())
```