[自動翻訳] ResponsesAgent Introduction

# ResponsesAgent イントロダクション | MLflow

## ResponsesAgentとは？

`ResponsesAgent`は、`PythonModel`のサブクラスであり、エージェントモデルを作成するためのフレームワークに依存しない方法を提供します。`ResponsesAgent`を使用してエージェントを開発することで、以下の利点があります。

- **ツール呼び出しの中間出力を含む、複数の出力メッセージの返却をサポート**
- **マルチエージェントシナリオのサポート**
- **MLflowのロギング、トレーシング、モデルサービングとの互換性を確保**
- **OpenAI Responses APIとの互換性を確保し、OpenAIのresponsesクライアントや他のダウンストリームUI/アプリケーションと互換性を持たせる**

`ChatModel`や`ChatAgent`の代わりに`ResponsesAgent`の利用を推奨します。`ResponsesAgent`は`ChatAgent`のすべての利点を持ち、さらにアノテーションなどの追加機能もサポートしています。

## ResponsesAgentの作成

### はじめに

独自のエージェントを作成するには、`mlflow.pyfunc.ResponsesAgent`をサブクラス化し、エージェントのロジックを`predict`メソッドに実装します。この実装はフレームワークに依存しないため、どのエージェント作成フレームワークも利用できます。なお、`ResponsesAgent`の使用には`pydantic>=2`が必要です。実装例は、下記のシンプルチャットエージェントやツール呼び出しエージェントを参照してください[^1]。

### エージェント出力の作成

エージェントを実装する際に主に扱う出力タイプは、`ResponsesAgentResponse`と`ResponsesAgentStreamEvent`の2つです。これらは直接作成する唯一のpydanticオブジェクトです。`mlflow.types.responses_helpers`内の他のクラスは、辞書のバリデーションのみに使用します。

標準インターフェースに合わない出力を返したい場合は、`custom_outputs`フィールドを使用できます。

以下は、ResponsesAgentインターフェース内で一般的な出力を作成するためのヘルパーメソッドです。

- `mlflow.pyfunc.ResponsesAgent.create_text_output_item()`
- `mlflow.pyfunc.ResponsesAgent.create_function_call_item()`
- `mlflow.pyfunc.ResponsesAgent.create_function_call_output_item()`
- `mlflow.pyfunc.ResponsesAgent.create_text_delta()`（ストリーミング専用）

以下は、`ResponsesAgentResponse`とカスタム出力を使用した完全なツール呼び出しシーケンスの例です。

```python
from mlflow.pyfunc import ResponsesAgent
from mlflow.types.responses import ResponsesAgentRequest, ResponsesAgentResponse

class SimpleResponsesAgent(Responses2Agent):
    @mlflow.trace(span_type=SpanType.AGENT)
    def predict(self, request: ResponsesAgentRequest) -> ResponsesAgentResponse:
        return ResponsesAgentResponse(
            output=[
                self.create_function_call_item(
                    id="fc_1",
                    call_id="call_1",
                    name="python_exec",
                    arguments='{"code":"result = 4 * 3\\nprint(result)"}',
                ),
                self.create_function_call_output_item(
                    call_id="call_1",
                    output="12\n",
                ),
                self.create_text_output_item(
                    text="The result of 4 * 3 in Python is 12.",
                    id="msg_1",
                ),
            ],
            custom_outputs={"key1": "custom-value1"},
        )
```


### エージェント出力のストリーミング

リアルタイム処理のため、完全なレスポンスを返す代わりにストリーミングイベントを使用できます。ストリーミングにより、進捗をユーザーに表示したり、長時間の操作にも対応できます。

#### 基本的なテキストストリーミング

ResponsesAgentインターフェース内でテキストをストリーミングするには、以下の手順に従います。

- チャンクが利用可能になるたびに`response.output_text.delta`イベントをyieldする
- 関連するイベントを単一の出力アイテムに対応させるために`item_id`を持つ
- すべてのチャンクを集約するために`response.output_item.done`イベントをyieldする

```python
from mlflow.types.responses import ResponsesAgentStreamEvent

class SimpleResponsesAgent(ResponsesAgent):
    # ...（上記からの続き）
    @mlflow.trace(span_type=SpanType.AGENT)
    def predict_stream(
        self, request: ResponsesAgentRequest
    ) -> Generator[ResponsesAgentStreamEvent, None, None]:
        # 同じitem_idでテキストをストリーミング
        yield ResponsesAgentStreamEvent(
            **self.create_text_delta(delta="こんにちは", item_id="msg_1"),
        )
        yield ResponsesAgentStreamEvent(
            **self.create_text_delta(delta="世界", item_id="msg_1"),
        )
        yield ResponsesAgentStreamEvent(
            **self.create_text_delta(delta="！", item_id="msg_1"),
        )
        # テキスト出力アイテムのidは、ストリーミングテキストと同じitem_idにする
        yield ResponsesAgentStreamEvent(
            type="response.output_item.done",
            item=self.create_text_output_item(
                text="こんにちは世界！",
                id="msg_1",
            ),
        )
```


#### ツール呼び出しのストリーミング

ツール呼び出しとその結果もストリーミングできます。各ツール呼び出しとその出力は、個別の`response.output_item.done`イベントとして送信されます。これによりMLflowのトレーシングが可能になり、クライアントがストリーミングされたメッセージ履歴を再構築しやすくなります。

```python
from mlflow.types.responses import ResponsesAgentStreamEvent

class SimpleResponsesAgent(ResponsesAgent):
    # ...（上記からの続き）
    @mlflow.trace(span_type=SpanType.AGENT)
    def predict_stream(
        self, request: ResponsesAgentRequest
    ) -> Generator[ResponsesAgentStreamEvent, None, None]:
        yield ResponsesAgentStreamEvent(
            type="response.output_item.done",
            item=self.create_function_call_item(
                id="fc_1",
                call_id="call_1",
                name="python_exec",
                arguments='{"code":"result = 4 * 3\\nprint(result)"}',
            ),
        )
        yield ResponsesAgentStreamEvent(
            type="response.output2_item.done",
            item=self.create_function_call_output_item(
                call_id="call_1",
                output="12\n",
            ),
        )
        yield ResponsesAgentStreamEvent(
            type="response.output_item.done",
            item=self.create_text_output_item(
                text="Pythonで4*3の結果は12です。",
                id="msg_1",
            ),
        )
```

## エージェントのロギング

モデルをコードからログする方法でエージェントをログします。この方法はフレームワークに依存しません。

```python
with mlflow.start_run():
    logged_agent_info = mlflow.pyfunc.log_model(
        python_model="agent.py",  # エージェントコードの相対パスに置き換える
        name="agent",
    )
```

利便性のために、MLflowには以下の機能が組み込まれています。

- **自動モデルシグネチャ推論**
- **ResponsesAgentRequestとResponsesAgentResponseスキーマに準拠した入出力シグネチャが自動設定**
- **メタデータ**
    - `{"task": "agent/v1/responses"}`が自動で追加される
- **入力例**
    - 入力例の提供は任意
    - `mlflow.types.responses.RESPONSES_AGENT_INPUT_EXAMPLE`がデフォルトで使用される
    - 入力例を提供する場合は、ResponsesAgentRequestスキーマの辞書であることを確認


## エージェントのテスト

ResponsesAgentは、ログ前後どちらでも、ResponsesAgentRequestスキーマに従う単一の入力辞書を渡してテストできます。

```python
from mlflow.pyfunc import ResponsesAgent

class MyResponsesAgent(ResponsesAgent):
    ...

responses_agent = MyResponsesAgent()
responses_agent.predict(
    {
        "input": [{"role": "user", "content": "Pythonで4*3は？"}],
        "context": {"conversation_id": "123", "user_id": "456"},
    }
)

# ...（上記のログコード）
loaded_model = mlflow.pyfunc.load_model(path)
loaded_model.predict(
    {
        "input": [{"role": "user", "content": "Pythonで4*3は？"}],
        "context": {"conversation_id": "123", "user_id": "456"},
    }
)
```

## `ChatAgent`からの移行

`ChatAgent`から`ResponsesAgent`に移行する場合、主なタスクはメッセージフォーマットをChatCompletion APIからResponses APIスキーマに適合させることです。詳細はOpenAIのドキュメントを参照してください。

LLMプロバイダがチャット補完を使用している場合、ResponsesAgentの出力をチャット補完メッセージに変換するヘルパー関数を以下のように修正できます。

```python
def convert_to_chat_completion_format(message: dict[str, Any]) -> list[dict[str, Any]]:
    """ResponsesAgentの出力をChatCompletions互換のメッセージリストに変換"""
    msg_type = message.get("type", None)
    if msg_type == "function_call":
        return [
            {
                "role": "assistant",
                "content": None,
                "tool_calls": [
                    {
                        "id": message["call_id"],
                        "type": "function",
                        "function": {
                            "arguments": message["arguments"],
                            "name": message["name"],
                        },
                    },
                ],
            }
        ]
    elif msg_type == "message" and isinstance(message["content"], list):
        return [
            {"role": message["role"], "content": content["text"]}
            for content in message["content"]
        ]
    elif msg_type == "function_call_output":
        return [
            {
                "role": "tool",
                "content": message["output"],
                "tool_call_id": message["call_id"],
            }
        ]
    compatible_keys = ["role", "content", "name", "tool_calls", "tool_call_id"]
    return [{k: v for k, v in message.items() if k in compatible_keys}]
```

`ResponsesAgent`インターフェースは、`Chat2Agent`で利用可能だったすべての機能を拡張し、新機能も追加しています。以下に、共通ケースでのメッセージ表現の主な違いをまとめます。

### 標準テキストレスポンス

#### ResponsesAgent

```json
{
  "type": "message",
  "id": "",
  "content": [
    {
      "annotations": [],
      "text": "",
      "type": "output_text"
    }
  ],
  "role": "assistant",
  "status": "completed"
}
```


#### ChatAgent

```json
{
  "role": "assistant",
  "content": ""
}
```


### ツール呼び出し

#### ResponsesAgent

```json
{
  "type": "function_call",
  "id": "fc_1",
  "arguments": "",
  "call_id": "call_1",
  "name": "",
  "status": "completed"
}
```


#### ChatAgent

```json
{
  "role": "assistant",
  "content": "",
  "tool_calls": [
    {
      "id": "call_1",
      "type": "function",
      "function": {
        "name": "",
        "arguments": ""
      }
    }
  ]
}
```


### ツール呼び出し結果

#### ResponsesAgent

```json
{
  "type": "function_call_output",
  "call_id": "call_1",
  "output": ""
}
```


#### ChatAgent

```json
{
  "role": "tool",
  "content": "12",
  "tool_call_id": "call_1"
}
```


### ツール定義

#### ResponsesAgent

```json
{
  "name": "",
  "parameters": {},
  "strict": true,
  "type": "function",
  "description": ""
}
```


#### ChatAgent

```json
{
  "type": "function",
  "function": {
    "name": "",
    "description": "",
    "parameters": {}
  }
}
```