Python: Preserve refusals as marked text - #7992
Python: Preserve refusals as marked text#7992Eduard van Valkenburg (eavanvalkenburg) wants to merge 4 commits into
Conversation
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Adds durable typed refusal content across Python core, providers, hosting, and DevUI.
Changes:
- Introduces
Content.from_refusal(...)with serialization, aggregation, and display support. - Preserves native refusal shapes for OpenAI-compatible paths and text fallbacks elsewhere.
- Adds cross-package tests, UI support, documentation, and ADR 0036.
Reviewed changes
Copilot reviewed 49 out of 51 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
python/packages/openai/tests/openai/test_openai_chat_completion_client.py |
Tests Chat Completions refusal conversion. |
python/packages/openai/tests/openai/test_openai_chat_client.py |
Tests Responses refusal conversion. |
python/packages/openai/AGENTS.md |
Documents OpenAI refusal handling. |
python/packages/openai/agent_framework_openai/_chat_completion_client.py |
Parses and replays Chat Completions refusals. |
python/packages/openai/agent_framework_openai/_chat_client.py |
Preserves Responses refusal parts and deltas. |
python/packages/hosting-responses/tests/hosting_responses/test_parsing.py |
Tests hosted refusal parsing and streaming. |
python/packages/hosting-responses/README.md |
Documents refusal round-tripping. |
python/packages/hosting-responses/agent_framework_hosting_responses/_parsing.py |
Converts hosted refusal payloads and events. |
python/packages/hosting-a2a/tests/hosting_a2a/test_conversion.py |
Tests refusal text fallback. |
python/packages/hosting-a2a/agent_framework_hosting_a2a/_conversion.py |
Maps refusals to A2A text. |
python/packages/gemini/tests/test_gemini_client.py |
Tests Gemini refusal fallback. |
python/packages/gemini/agent_framework_gemini/_chat_client.py |
Maps refusals to Gemini text parts. |
python/packages/foundry_hosting/tests/test_responses.py |
Tests Foundry refusal events and parsing. |
python/packages/foundry_hosting/README.md |
Documents Foundry refusal support. |
python/packages/foundry_hosting/agent_framework_foundry_hosting/_responses.py |
Streams and converts native refusal content. |
python/packages/devui/tests/devui/test_mapper.py |
Tests refusal event mapping. |
python/packages/devui/tests/devui/test_conversations.py |
Tests refusal conversation persistence. |
python/packages/devui/frontend/src/types/openai.ts |
Adds OpenAI refusal event and content types. |
python/packages/devui/frontend/src/types/agent-framework.ts |
Adds framework refusal types and guard. |
python/packages/devui/frontend/src/stores/devuiStore.ts |
Handles refusal deltas in debug state. |
python/packages/devui/frontend/src/services/streaming-state.ts |
Persists streaming refusal previews. |
python/packages/devui/frontend/src/services/api.ts |
Tracks refusal deltas during streaming. |
python/packages/devui/frontend/src/components/layout/debug-panel.tsx |
Displays refusal stream events. |
python/packages/devui/frontend/src/components/features/workflow/workflow-view.tsx |
Renders workflow refusal output. |
python/packages/devui/frontend/src/components/features/workflow/execution-timeline.tsx |
Includes refusal text in timelines. |
python/packages/devui/frontend/src/components/features/agent/message-renderers/OpenAIContentRenderer.tsx |
Renders refusal message parts. |
python/packages/devui/frontend/src/components/features/agent/agent-view.tsx |
Handles streamed and restored refusals. |
python/packages/devui/AGENTS.md |
Documents DevUI refusal mapping. |
python/packages/devui/agent_framework_devui/models/__init__.py |
Exports refusal protocol models. |
python/packages/devui/agent_framework_devui/_mapper.py |
Emits native refusal stream events. |
python/packages/devui/agent_framework_devui/_conversations.py |
Stores and retrieves refusal parts. |
python/packages/core/tests/core/test_types.py |
Tests refusal semantics and aggregation. |
python/packages/core/tests/core/test_sessions.py |
Tests serialized refusal history. |
python/packages/core/AGENTS.md |
Documents refusal content support. |
python/packages/core/agent_framework/_types.py |
Defines the core refusal representation. |
python/packages/chatkit/tests/test_streaming.py |
Tests ChatKit refusal fallback. |
python/packages/chatkit/agent_framework_chatkit/_streaming.py |
Streams refusals as ChatKit text. |
python/packages/bedrock/tests/test_bedrock_client.py |
Tests Bedrock refusal fallback. |
python/packages/bedrock/agent_framework_bedrock/_chat_client.py |
Maps refusals to Bedrock text blocks. |
python/packages/anthropic/tests/test_anthropic_client.py |
Tests Anthropic refusal fallback. |
python/packages/anthropic/agent_framework_anthropic/_chat_client.py |
Maps refusals to Anthropic text blocks. |
python/packages/ag-ui/tests/ag_ui/test_run.py |
Tests AG-UI refusal conversion. |
python/packages/ag-ui/agent_framework_ag_ui/_workflow_run.py |
Includes refusals in workflow text. |
python/packages/ag-ui/agent_framework_ag_ui/_run_common.py |
Emits refusals as AG-UI text events. |
python/packages/a2a/tests/test_a2a_executor.py |
Tests executor refusal conversion. |
python/packages/a2a/tests/test_a2a_agent.py |
Tests agent refusal conversion. |
python/packages/a2a/agent_framework_a2a/_agent.py |
Maps refusals to A2A text parts. |
python/packages/a2a/agent_framework_a2a/_a2a_executor.py |
Handles refusal output in A2A execution. |
docs/decisions/0036-python-refusal-content.md |
Records the refusal representation decision. |
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
MAF Automated Review — Iteration 1
Result: Findings reported
Scope: full PR (2 commit(s)): 941cc0120512, 35f30a931d9a
Model: gpt-5.6-sol
Overview
The review found 5 verified inline finding(s).
Reviewed the supplied pull-request change set across correctness, security/reliability, architecture, and failure behavior.
5 verified findings remained after source verification (2 high, 3 medium) across 4 files. Details are attached to the affected lines below.
Affected areas: python/packages/devui/frontend/src/services/streaming-state.ts, python/packages/foundry_hosting/agent_framework_foundry_hosting/_responses.py, python/packages/hosting-responses/agent_framework_hosting_responses/_parsing.py, python/packages/openai/agent_framework_openai/_chat_completion_client.py
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
/review |
There was a problem hiding this comment.
MAF Automated Review — Iteration 2
Result: No findings
Scope: 34 net-new commit(s): dc7714295fff, 52a376453994, 34d1ac70b2fc, 178f365dc69e, 44c8cf6a2012, 6ed829cf713e, f7e78c689bb9, 341d0ec35bbc, 947d933f2385, de02975ee606, 6d532cf77e26, edfe115ea06b, 1aca2a95e3b9, 4c0bff8b77d5, a617392cc79b, 5996105a1bf2, d2a934d53530, 018dea570b84, 11d628d60ff2, 6a0773ba2180, 8b0aea17663c, 2955888d5512, 9ca957148efd, 0822dd7495d9, 575b408e0ec8, 362972afb701, b03792465563, 486b5d0267f8, ab9ce3d4649d, e2f7db207cb6, 8ea6f8345dbd, baf0ea5252eb, 5715b358b324, 60dbe8f701b9
Model: gpt-5.6-sol
Overview
The change consistently represents refusals as text carrying an experimental marker, preserves that marker through serialization and storage, excludes refusals from structured-output parsing, and reconstructs native refusal shapes at OpenAI, hosting, Foundry, and DevUI boundaries. The added tests cover visibility, persistence, provider round-tripping, and refusal streaming. After reconciling the proposed risks against those guards, the supported mixed-content issue duplicates existing feedback and the remaining candidates lack a demonstrated publishable impact.
Reviewed the supplied incremental change set across correctness, security/reliability, architecture, and failure behavior.
No publishable findings remained after source verification for this scope.
Motivation & Context
Native provider refusals are currently flattened into ordinary Python text content. That keeps refusal wording visible, but loses the provider semantic across durable history, native provider replay, Responses-compatible hosting, and DevUI conversation/UI state. This change preserves that signal without adding a stable content discriminator before its broader usage and semantics are understood.
Description & Review Guide
Content(type="text")and carry the experimental, serializableadditional_properties["model_output_kind"] == "refusal"marker. Structured-output extraction skips marked refusal text, while normal message/response text and coalescing remain unchanged. OpenAI Responses, Chat Completions, Foundry hosting, Hosting Responses, and DevUI inspect the marker to reconstruct native refusal fields, parts, and streaming events. ADR 0039 records the metadata choice and the Microsoft.Extensions.AIErrorContent(ErrorCode="Refusal")alternative.Related Issue
N/A — follow-up from the Responses conversion consistency sweep
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.