microsoft / microsoft/agent-framework
Python: [Bug]: Harness Agent drops structured value on the streaming path with tool approval
- Dominant language
- Python
- Stars
- 13.6k
- Forks
- 2.3k
- Avg merge
- 2d 45m
- Merged PRs (30d)
- 358
Description
### 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
```markdown
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
```markdown
```
### Package Versions
agent-framework-core: 1.12.1
### Python Version
_No response_
### Additional Context
_No response_
Contributor guide
Assessment
This issue has not been assessed yet.