Skip to content

Python: [Bug]: Harness Agent drops structured value on the streaming path with tool approval #7418

Description

@lmx-2077

Description

What happened?

ToolApprovalMiddleware._process_stream finalizes the outer stream with AgentResponse.from_updates without passing output_format_type:

return ResponseStream(_stream(), finalizer=AgentResponse.from_updates)

Because from_updates receives the text updates but no response_format, the final AgentResponse.value is None for streaming runs that combine tool approval with a structured-output schema.

The non-streaming path is unaffected because it returns the inner AgentResponse directly.

What did you expect to happen?

response.value should be parsed into the Pydantic model / schema supplied via response_format, matching the non-harness streaming path in _agents.py:1229.

Steps to reproduce the issue

  1. Create a create_harness_agent(...) with stream=True.
  2. Register a tool that requires approval.
  3. Pass options={"response_format": SomePydanticModel} to agent.run.
  4. Inspect response.value — it is None.

Same "per-run concept lost by a re-wrapping layer" family as #7402 and #7236.

Code Sample

from typing import Any

import asyncio
from pydantic import BaseModel

from agent_framework import (
    AgentSession,
    BaseChatClient,
    ChatResponse,
    Message,
    create_harness_agent,
)


class Answer(BaseModel):
    answer: str


class EchoTool:
    @tool(approval_mode="always_require")
    async def echo(self, text: str) -> str:
        return text


class FakeStreamingClient(BaseChatClient):
    async def _inner_get_response(self, *, messages, stream, options, **kwargs):
        return ChatResponse(
            messages=[Message("assistant", ['{"answer": "42"}'])]
        )


async def main():
    agent = create_harness_agent(
        client=FakeStreamingClient(),
        tools=[EchoTool().echo],
        stream=True,
    )
    session = AgentSession()

    response = await agent.run(
        "Call echo and return an Answer.",
        session=session,
        options={"response_format": Answer},
    )

    print(type(response.value))  # Currently: NoneType
    assert isinstance(response.value, Answer)  # Fails today


asyncio.run(main())

Error Messages / Stack Traces

Package Versions

agent-framework-core: 1.12.1

Python Version

No response

Additional Context

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    likely-fixedpythonUsage: [Issues, PRs], Target: PythontriageUsage: [Issues], Target: All issues that still need to be triaged

    Type

    Projects

    Status
    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions