Repository navigation
Python: Support response_format in GitHubCopilotAgent - #9810
Dineshsuriya D (droideronline) wants to merge 2 commits into
Conversation
Forward response_format as response_schema to the Copilot SDK send and send_and_wait calls, keep it out of create_session kwargs, and parse AgentResponse.value for streaming and non-streaming runs.
There was a problem hiding this comment.
🟡 Changes recommended
Structured-output parsing can incorrectly use trailing subagent text instead of the root answer, while mapping schemas and aliased models can trigger serialization or validation failures.
3 open issues
What changed in this PR
Adds opt-in structured output support to GitHubCopilotAgent, bringing it closer to other Agent Framework providers and addressing #9809.
Changes:
- Accepts Pydantic models or JSON Schema mappings through
response_format. - Forwards schemas to streaming and non-streaming SDK calls, excludes them from session creation, and enables
AgentResponse.valueparsing. - Adds tests for schema forwarding, default options, unstructured requests, and streaming results.
| File | Description |
|---|---|
python/packages/github_copilot/tests/test_github_copilot_agent.py |
Adds structured-output tests for both response modes. |
python/packages/github_copilot/agent_framework_github_copilot/_agent.py |
Adds the option, SDK forwarding, and structured response parsing. |
Review effort: Balanced
| def _finalize(updates: Sequence[AgentResponseUpdate]) -> AgentResponse: | ||
| return AgentResponse.from_updates(updates) | ||
| response_format = (options or {}).get("response_format") or self._default_options.get("response_format") | ||
| return AgentResponse.from_updates(updates, output_format_type=response_format) |
There was a problem hiding this comment.
Fixed. The streaming path now accumulates deltas from root events only (no agent_id) and parses the value from the last root message; sub-agent text still streams. Non-streaming uses the SDK's send_and_wait, which already ignores sub-agent events. Regression test added.
| response_event = await copilot_session.send_and_wait( | ||
| prompt, | ||
| attachments=attachments, | ||
| response_schema=response_format, |
There was a problem hiding this comment.
Fixed. Mapping schemas are recursively converted to plain dicts before both send_and_wait and send; Pydantic model classes are passed through unchanged. Test added with MappingProxyType.
| finish_reason=cast(Any, finish_reason), | ||
| usage_details=usage_details, | ||
| additional_properties={"model": model} if model else None, | ||
| response_format=response_format, |
There was a problem hiding this comment.
Fixed. The agent now parses with by_alias=True, by_name=False, matching the SDK's typed parser. If that parse fails, the response falls back to core lazy parsing so .value raises as before. Test added for an aliased model.
Parse the streaming structured value from root-agent text only, convert mapping schemas to plain dicts before they reach the SDK, and validate Pydantic models by alias to match the schema the SDK requests.


Motivation & Context
GitHubCopilotAgentignoredresponse_format, so structured output was unavailable even though the Copilot SDK supports it (github/copilot-sdk#2590, in SDK v1.0.15 and later, within the existing>=1.0.16pin).Description & Review Guide
response_format(Pydantic model type or JSON Schema mapping) toGitHubCopilotOptions.response_schematosend_and_wait(non-streaming) andsend(streaming). Per-run options overridedefault_options.response_formatis stripped from thecreate_sessionkwargs so it no longer reaches the SDK session factory.AgentResponse.valueis parsed from the final text: viaresponse_format=on the non-streaming response andoutput_format_type=in the streaming finalizer.response_formatthe SDK receivesresponse_schema=None, which is the existing behavior.Related Issue
Fixes #9809
AI Assistance
AI assistance details: implementation and tests.
Contribution Checklist
breaking changelabel (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.