* Python: Fix hosted MCP replay producing orphan function_call_output Resolves part of #5546. After a turn ran a hosted MCP / Foundry-toolbox-MCP tool, the next turn's replayed input array carried a function_call_output with an mcp_* call_id and no matching function_call, and the Responses API returned a 400. Two layers covered here: * Chat-client serialize layer (packages/openai): adds mcp_server_tool_call and mcp_server_tool_result cases to _prepare_message_for_openai and _prepare_content_for_openai. Pairs are coalesced via a post-pass into a single mcp_call input item carrying both arguments and output. Orphan results are dropped (debug-logged) rather than serialized as orphan function_call_output, which is what the Responses API rejected. * Host read layer (packages/foundry_hosting): _item_to_message and _output_item_to_message now route custom_tool_call_output whose call_id.startswith("mcp_") to Content.from_mcp_server_tool_result. Non-mcp_ call_ids continue to produce Content.from_function_result. Symmetric with the host write-side choice for hosted-MCP results. Two further fixes (agentserver SDK additions, host write-side single-item emission) remain tracked on the issue and depend on an SDK release. * Python: Fix pyright unknown-type in _stringify_mcp_output cast(Sequence[Any], output) after the isinstance check so pyright stops flagging the loop variable as unknown. Also normalizes a couple of em-dashes in docstrings I introduced in the prior commit. * Python: Harden _stringify_mcp_output for dict-shaped MCP outputs Address Copilot review on PR #5581. Today the helper falls back to str() for any non-string, non-text-attribute entry, which produces Python repr (single-quoted dicts) for the canonical MCP raw-JSON text-content shape `{"type": "text", "text": "..."}` and any other dict-shaped output. Three small changes: * List-entry path: prefer plain string entries, then `.text` attribute (Content objects), then `entry["text"]` for Mapping entries in the canonical MCP shape, then JSON-encode anything else. * Final fallback: `json.dumps(output, default=str)` so Mappings and scalars produce valid JSON rather than Python repr. * Two new unit tests covering the dict-with-text shape and the non-text-dict JSON fallback. * Python: Suppress mypy redundant-cast on _stringify_mcp_output narrowing The cast is needed by pyright (reportUnknownVariableType) but mypy considers it redundant after the preceding isinstance narrowing. Pyright's behavior is correct for the strict-mode reporting we run, so keep the cast and silence mypy on the line.
agent-framework-openai
OpenAI integration for Microsoft Agent Framework.
This package provides:
OpenAIChatClientfor the OpenAI Responses APIOpenAIChatCompletionClientfor the Chat Completions APIOpenAIEmbeddingClientfor 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.
OpenAIChatClientuses the Responses API and is the preferred general-purpose chat client.OpenAIChatCompletionClientuses 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_MODELOpenAIChatCompletionClient:OPENAI_CHAT_COMPLETION_MODEL->OPENAI_MODELOpenAIEmbeddingClient: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_MODELOpenAIChatCompletionClient:AZURE_OPENAI_CHAT_COMPLETION_MODEL->AZURE_OPENAI_MODELOpenAIEmbeddingClient: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")