|  | 
|  | 1 | +from __future__ import annotations | 
|  | 2 | + | 
|  | 3 | +from typing import Any | 
|  | 4 | + | 
| 1 | 5 | import pytest | 
|  | 6 | +from openai.types.responses import ResponseOutputMessage, ResponseOutputText | 
| 2 | 7 | from pydantic import BaseModel | 
| 3 | 8 | 
 | 
| 4 |  | -from agents import Agent, AgentBase, FunctionTool, RunContextWrapper | 
|  | 9 | +from agents import ( | 
|  | 10 | +    Agent, | 
|  | 11 | +    AgentBase, | 
|  | 12 | +    FunctionTool, | 
|  | 13 | +    MessageOutputItem, | 
|  | 14 | +    RunConfig, | 
|  | 15 | +    RunContextWrapper, | 
|  | 16 | +    RunHooks, | 
|  | 17 | +    Runner, | 
|  | 18 | +    Session, | 
|  | 19 | +    TResponseInputItem, | 
|  | 20 | +) | 
|  | 21 | +from agents.tool_context import ToolContext | 
| 5 | 22 | 
 | 
| 6 | 23 | 
 | 
| 7 | 24 | class BoolCtx(BaseModel): | 
| @@ -205,3 +222,159 @@ async def custom_extractor(result): | 
| 205 | 222 |     tools = await orchestrator.get_all_tools(context) | 
| 206 | 223 |     assert len(tools) == 1 | 
| 207 | 224 |     assert tools[0].name == "custom_tool_name" | 
|  | 225 | + | 
|  | 226 | + | 
|  | 227 | +@pytest.mark.asyncio | 
|  | 228 | +async def test_agent_as_tool_returns_concatenated_text(monkeypatch: pytest.MonkeyPatch) -> None: | 
|  | 229 | +    """Agent tool should use default text aggregation when no custom extractor is provided.""" | 
|  | 230 | + | 
|  | 231 | +    agent = Agent(name="storyteller") | 
|  | 232 | + | 
|  | 233 | +    message = ResponseOutputMessage( | 
|  | 234 | +        id="msg_1", | 
|  | 235 | +        role="assistant", | 
|  | 236 | +        status="completed", | 
|  | 237 | +        type="message", | 
|  | 238 | +        content=[ | 
|  | 239 | +            ResponseOutputText( | 
|  | 240 | +                annotations=[], | 
|  | 241 | +                text="Hello world", | 
|  | 242 | +                type="output_text", | 
|  | 243 | +                logprobs=None, | 
|  | 244 | +            ) | 
|  | 245 | +        ], | 
|  | 246 | +    ) | 
|  | 247 | + | 
|  | 248 | +    result = type( | 
|  | 249 | +        "DummyResult", | 
|  | 250 | +        (), | 
|  | 251 | +        {"new_items": [MessageOutputItem(agent=agent, raw_item=message)]}, | 
|  | 252 | +    )() | 
|  | 253 | + | 
|  | 254 | +    async def fake_run( | 
|  | 255 | +        cls, | 
|  | 256 | +        starting_agent, | 
|  | 257 | +        input, | 
|  | 258 | +        *, | 
|  | 259 | +        context, | 
|  | 260 | +        max_turns, | 
|  | 261 | +        hooks, | 
|  | 262 | +        run_config, | 
|  | 263 | +        previous_response_id, | 
|  | 264 | +        conversation_id, | 
|  | 265 | +        session, | 
|  | 266 | +    ): | 
|  | 267 | +        assert starting_agent is agent | 
|  | 268 | +        assert input == "hello" | 
|  | 269 | +        return result | 
|  | 270 | + | 
|  | 271 | +    monkeypatch.setattr(Runner, "run", classmethod(fake_run)) | 
|  | 272 | + | 
|  | 273 | +    tool = agent.as_tool( | 
|  | 274 | +        tool_name="story_tool", | 
|  | 275 | +        tool_description="Tell a short story", | 
|  | 276 | +        is_enabled=True, | 
|  | 277 | +    ) | 
|  | 278 | + | 
|  | 279 | +    assert isinstance(tool, FunctionTool) | 
|  | 280 | +    tool_context = ToolContext(context=None, tool_name="story_tool", tool_call_id="call_1") | 
|  | 281 | +    output = await tool.on_invoke_tool(tool_context, '{"input": "hello"}') | 
|  | 282 | + | 
|  | 283 | +    assert output == "Hello world" | 
|  | 284 | + | 
|  | 285 | + | 
|  | 286 | +@pytest.mark.asyncio | 
|  | 287 | +async def test_agent_as_tool_custom_output_extractor(monkeypatch: pytest.MonkeyPatch) -> None: | 
|  | 288 | +    """Custom output extractors should receive the RunResult from Runner.run.""" | 
|  | 289 | + | 
|  | 290 | +    agent = Agent(name="summarizer") | 
|  | 291 | + | 
|  | 292 | +    message = ResponseOutputMessage( | 
|  | 293 | +        id="msg_2", | 
|  | 294 | +        role="assistant", | 
|  | 295 | +        status="completed", | 
|  | 296 | +        type="message", | 
|  | 297 | +        content=[ | 
|  | 298 | +            ResponseOutputText( | 
|  | 299 | +                annotations=[], | 
|  | 300 | +                text="Original text", | 
|  | 301 | +                type="output_text", | 
|  | 302 | +                logprobs=None, | 
|  | 303 | +            ) | 
|  | 304 | +        ], | 
|  | 305 | +    ) | 
|  | 306 | + | 
|  | 307 | +    class DummySession(Session): | 
|  | 308 | +        session_id = "sess_123" | 
|  | 309 | + | 
|  | 310 | +        async def get_items(self, limit: int | None = None) -> list[TResponseInputItem]: | 
|  | 311 | +            return [] | 
|  | 312 | + | 
|  | 313 | +        async def add_items(self, items: list[TResponseInputItem]) -> None: | 
|  | 314 | +            return None | 
|  | 315 | + | 
|  | 316 | +        async def pop_item(self) -> TResponseInputItem | None: | 
|  | 317 | +            return None | 
|  | 318 | + | 
|  | 319 | +        async def clear_session(self) -> None: | 
|  | 320 | +            return None | 
|  | 321 | + | 
|  | 322 | +    dummy_session = DummySession() | 
|  | 323 | + | 
|  | 324 | +    class DummyResult: | 
|  | 325 | +        def __init__(self, items: list[MessageOutputItem]) -> None: | 
|  | 326 | +            self.new_items = items | 
|  | 327 | + | 
|  | 328 | +    run_result = DummyResult([MessageOutputItem(agent=agent, raw_item=message)]) | 
|  | 329 | + | 
|  | 330 | +    async def fake_run( | 
|  | 331 | +        cls, | 
|  | 332 | +        starting_agent, | 
|  | 333 | +        input, | 
|  | 334 | +        *, | 
|  | 335 | +        context, | 
|  | 336 | +        max_turns, | 
|  | 337 | +        hooks, | 
|  | 338 | +        run_config, | 
|  | 339 | +        previous_response_id, | 
|  | 340 | +        conversation_id, | 
|  | 341 | +        session, | 
|  | 342 | +    ): | 
|  | 343 | +        assert starting_agent is agent | 
|  | 344 | +        assert input == "summarize this" | 
|  | 345 | +        assert context is None | 
|  | 346 | +        assert max_turns == 7 | 
|  | 347 | +        assert hooks is hooks_obj | 
|  | 348 | +        assert run_config is run_config_obj | 
|  | 349 | +        assert previous_response_id == "resp_1" | 
|  | 350 | +        assert conversation_id == "conv_1" | 
|  | 351 | +        assert session is dummy_session | 
|  | 352 | +        return run_result | 
|  | 353 | + | 
|  | 354 | +    monkeypatch.setattr(Runner, "run", classmethod(fake_run)) | 
|  | 355 | + | 
|  | 356 | +    async def extractor(result) -> str: | 
|  | 357 | +        assert result is run_result | 
|  | 358 | +        return "custom output" | 
|  | 359 | + | 
|  | 360 | +    hooks_obj = RunHooks[Any]() | 
|  | 361 | +    run_config_obj = RunConfig(model="gpt-4.1-mini") | 
|  | 362 | + | 
|  | 363 | +    tool = agent.as_tool( | 
|  | 364 | +        tool_name="summary_tool", | 
|  | 365 | +        tool_description="Summarize input", | 
|  | 366 | +        custom_output_extractor=extractor, | 
|  | 367 | +        is_enabled=True, | 
|  | 368 | +        run_config=run_config_obj, | 
|  | 369 | +        max_turns=7, | 
|  | 370 | +        hooks=hooks_obj, | 
|  | 371 | +        previous_response_id="resp_1", | 
|  | 372 | +        conversation_id="conv_1", | 
|  | 373 | +        session=dummy_session, | 
|  | 374 | +    ) | 
|  | 375 | + | 
|  | 376 | +    assert isinstance(tool, FunctionTool) | 
|  | 377 | +    tool_context = ToolContext(context=None, tool_name="summary_tool", tool_call_id="call_2") | 
|  | 378 | +    output = await tool.on_invoke_tool(tool_context, '{"input": "summarize this"}') | 
|  | 379 | + | 
|  | 380 | +    assert output == "custom output" | 
0 commit comments