Files
agent-framework/python/packages/openai
T
Evan Mattson 5e8fe0be1f Python: Stop emitting duplicate reasoning content from OpenAI response.reasoning_text.done and response.reasoning_summary_text.done events (#5162)
* Fix reasoning text done events duplicating streamed delta content (#5157)

The OpenAI Responses API sends both reasoning_text.delta (incremental
chunks) and reasoning_text.done (full accumulated text) events. The
chat client was emitting Content for both, causing ag-ui to append the
full done text onto already-accumulated delta text, producing
duplicated reasoning output.

Stop emitting Content for reasoning_text.done and
reasoning_summary_text.done events, matching how output_text.done is
already handled (not emitted). The deltas contain all the content;
the done event is redundant.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(openai): emit reasoning done content as fallback when no deltas observed (#5157)

Address PR review feedback:
- Track item_ids that received reasoning deltas via seen_reasoning_delta_item_ids set
- Emit content from done events only when no deltas were received for the
  item_id, preventing silent content loss on stream resumption
- Add comment documenting code_interpreter done event asymmetry
- Replace redundant ag-ui test with deduplication-focused test
- Add integration test for delta+done sequence in OpenAI chat client tests
- Add fallback path tests for done events without preceding deltas

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Address review feedback for #5157: Python: [Bug]: "type": "response.reasoning_text.delta" and "response.reasoning_text.done" both get exposed as "text_reasoning"

* Fix AG-UI reasoning streaming to use proper Start/End pattern (#5157)

_emit_text_reasoning now follows the same streaming pattern as _emit_text:
- Emits ReasoningStartEvent/ReasoningMessageStartEvent only on the first
  delta for a given message_id
- Emits only ReasoningMessageContentEvent for subsequent deltas
- Defers ReasoningMessageEndEvent/ReasoningEndEvent until
  _close_reasoning_block is called (on content type switch or end-of-run)

This produces the correct protocol pattern:
  ReasoningStartEvent
    ReasoningMessageStartEvent
    ReasoningMessageContentEvent(delta1)
    ReasoningMessageContentEvent(delta2)
    ReasoningMessageEndEvent
  ReasoningEndEvent

Instead of wrapping every delta in a full Start→End sequence.

Backward compatibility is preserved: calling _emit_text_reasoning without
a flow argument still produces the full sequence per call.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix import ordering lint error in AG-UI test file (#5157)

Move inline import of TextMessageContentEvent to the top-level import
block and ensure alphabetical ordering to satisfy ruff I001 rule.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix mypy error: rename loop variable to avoid type conflict with WorkflowEvent

The 'event' variable was already typed as WorkflowEvent[Any] from the
async for loop at line 590. Reusing it in the _close_reasoning_block
loop (which returns list[BaseEvent]) caused an incompatible assignment
error. Renamed to 'reasoning_evt' to avoid the conflict.

Fixes #5162

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Address review feedback for #5157: review comment fixes

* narrow test result reporting to explicit pytest JUnit XML

* Fix test args

* Fix pytest-results-action in merge workflow and remove committed test artifacts

Apply the same JUnit XML fix from python-tests.yml to python-merge-tests.yml:
add --junitxml=pytest.xml to all test commands and narrow the results action
path from ./python/**.xml to ./python/pytest.xml. Also remove accidentally
committed pytest.xml and python-coverage.xml and add them to .gitignore.

---------

Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
5e8fe0be1f · 2026-04-09 22:44:59 +00:00
History
..

agent-framework-openai

OpenAI integration for Microsoft Agent Framework.

This package provides:

  • OpenAIChatClient for the OpenAI Responses API
  • OpenAIChatCompletionClient for the Chat Completions API
  • OpenAIEmbeddingClient for embeddings

Installation

pip install agent-framework-openai

Which chat client should I use?

Use OpenAIChatClient for new work unless you specifically need the Chat Completions API.

  • OpenAIChatClient uses the Responses API and is the preferred general-purpose chat client.
  • OpenAIChatCompletionClient uses the Chat Completions API and is mainly for compatibility with existing Chat Completions-based integrations.

The previous deprecated Responses alias has been removed. Use OpenAIChatClient directly.

Environment variables

OpenAI

These variables are used when the client is configured for OpenAI:

Variable Purpose
OPENAI_API_KEY OpenAI API key
OPENAI_ORG_ID OpenAI organization ID
OPENAI_BASE_URL Custom OpenAI-compatible base URL
OPENAI_MODEL Generic fallback model
OPENAI_CHAT_MODEL Preferred model for OpenAIChatClient
OPENAI_CHAT_COMPLETION_MODEL Preferred model for OpenAIChatCompletionClient
OPENAI_EMBEDDING_MODEL Preferred model for OpenAIEmbeddingClient

Model lookup order:

  • OpenAIChatClient: OPENAI_CHAT_MODEL -> OPENAI_MODEL
  • OpenAIChatCompletionClient: OPENAI_CHAT_COMPLETION_MODEL -> OPENAI_MODEL
  • OpenAIEmbeddingClient: OPENAI_EMBEDDING_MODEL -> OPENAI_MODEL

These model variables are only consulted when you do not pass model= directly. In other words, OpenAIChatClient(model="...") ignores OPENAI_CHAT_MODEL, and OpenAIChatCompletionClient(model="...") ignores OPENAI_CHAT_COMPLETION_MODEL.

Azure OpenAI

These variables are used when the client is configured for Azure OpenAI:

Variable Purpose
AZURE_OPENAI_ENDPOINT Azure OpenAI resource endpoint
AZURE_OPENAI_BASE_URL Full Azure OpenAI base URL (.../openai/v1)
AZURE_OPENAI_API_KEY Azure OpenAI API key
AZURE_OPENAI_API_VERSION Azure OpenAI API version
AZURE_OPENAI_MODEL Generic fallback deployment
AZURE_OPENAI_CHAT_MODEL Preferred deployment for OpenAIChatClient
AZURE_OPENAI_CHAT_COMPLETION_MODEL Preferred deployment for OpenAIChatCompletionClient
AZURE_OPENAI_EMBEDDING_MODEL Preferred deployment for OpenAIEmbeddingClient

Deployment lookup order:

  • OpenAIChatClient: AZURE_OPENAI_CHAT_MODEL -> AZURE_OPENAI_MODEL
  • OpenAIChatCompletionClient: AZURE_OPENAI_CHAT_COMPLETION_MODEL -> AZURE_OPENAI_MODEL
  • OpenAIEmbeddingClient: AZURE_OPENAI_EMBEDDING_MODEL -> AZURE_OPENAI_MODEL

For Azure routing, the same rule applies: the client-specific deployment variable is checked first, then the generic AZURE_OPENAI_MODEL fallback. Passing model= overrides both environment variables.

When both OpenAI and Azure environment variables are present, the generic clients prefer OpenAI when OPENAI_API_KEY is configured. To use Azure explicitly, pass azure_endpoint or credential.

OpenAI example

from agent_framework.openai import OpenAIChatClient

client = OpenAIChatClient(model="gpt-4.1")

Azure OpenAI example

from azure.identity.aio import AzureCliCredential

from agent_framework.openai import OpenAIChatClient

client = OpenAIChatClient(
    model="my-responses-deployment",
    azure_endpoint="https://my-resource.openai.azure.com",
    credential=AzureCliCredential(),
)

ChatClient vs ChatCompletionClient

Use OpenAIChatClient when you want the Responses API as your default chat surface.

Use OpenAIChatCompletionClient when you specifically need the Chat Completions API:

from agent_framework.openai import OpenAIChatCompletionClient

client = OpenAIChatCompletionClient(model="gpt-4o-mini")