Live
Microsoft Agent Framework
Microsoft Agent Framework gets a governed chat client pointed at the Omni Gateway LLM proxy, plus policy middleware that ends a run on a governance rejection with the SDK’s typed refusal.
What you get
- A native
agent_framework.openai.OpenAIChatCompletionClient, which calls the Chat Completions API (POST /chat/completions). Passapi="responses"for the Responses client (see Which API). The supported and tested agent-framework versions are on Version compatibility. policy_middleware(), which ends a run on a proxy refusal with a typedPolicyViolationsuch asPIIDetected.- Supported at
connection_kwargs(). The client gets anasync_clientthat sends through the SDK’s shared HTTP client, so per-run correlation, retries, spans,donkey.last_calland thejwt-mode JWT apply (see Notes).
Listed upstream. Microsoft Agent Framework lists DDK as “Donkey Development Kit” under Middleware in its community projects catalog.
Install
pip install "donkey-kit[agent_framework]"Quickstart
Python
from donkey_kit.integrations.agent_framework import chat_client
llm = chat_client("gpt-4o")llm is a real agent_framework.openai.OpenAIChatClient instance.
Three ways to construct
1. Off a shared Donkey instance:
from donkey_kit import Donkey
async with Donkey.from_env() as donkey:
llm = donkey.agent_framework.chat_client("gpt-4o")2. Module-level factory (shortest):
from donkey_kit.integrations.agent_framework import chat_client
llm = chat_client("gpt-4o")3. Governed kwargs, native constructor:
from donkey_kit import Donkey
from agent_framework.openai import OpenAIChatClient
async with Donkey.from_env() as donkey:
llm = OpenAIChatClient(
model="gpt-4o",
**donkey.agent_framework.connection_kwargs(),
)Manual equivalent
from agent_framework.openai import OpenAIChatClient
llm = OpenAIChatClient(
model=..., # `model`, not `model_id`
base_url=..., # from DONKEY_LLM_PROXY_URL, no /v1 suffix
api_key=...,
default_headers=..., # client_id / client_secret header pair
async_client=..., # an AsyncOpenAI on the SDK's shared client
)async_client is present when the openai package is installed. The
constructor uses it as given, so a base_url passed to chat_client() gets a
client built on that URL; the URL must pass the
https:// rule.
The same kwargs work with OpenAIChatCompletionClient, the Chat Completions
client.
Which API
agent-framework-openai has two OpenAI-compatible chat clients, and
chat_client() picks one with api=:
api= | Client | Request |
|---|---|---|
"chat_completions" (default) | OpenAIChatCompletionClient | POST /chat/completions |
"responses" | OpenAIChatClient | POST /responses |
The default is Chat Completions because it is the one route every upstream behind an OpenAI-format proxy serves. That was verified live on OpenAI, Azure OpenAI, Bedrock-Anthropic and Gemini.
/responses works fully only on an OpenAI upstream:
- Azure OpenAI answers it with
404 Resource not found, which reaches you as aChatClientException. - Bedrock and Gemini answer it through the gateway’s transcoder, which streams it only partially.
On a proxy that routes to OpenAI, you can still ask for the Responses client:
llm = chat_client("openai/gpt-4.1-mini", api="responses")Both clients work with policy_middleware(), and the local simulator serves
both routes.
Policy middleware
donkey.agent_framework.policy_middleware() returns a chat middleware for
Agent(..., middleware=[...]):
from agent_framework import Agent
from donkey_kit import Donkey, PIIDetected
async with Donkey.from_env() as donkey:
agent = Agent(
client=donkey.agent_framework.chat_client("gpt-4o"),
middleware=[donkey.agent_framework.policy_middleware()],
)
try:
await agent.run("...")
except PIIDetected as err:
print(err.correlation_id, err.entities)Without it, a proxy refusal reaches you as Agent Framework’s generic
ChatClientException. With it, the middleware runs the call inside the SDK’s
typed-refusal bridge, and
the run ends with the typed error,
for example PIIDetected or TokenBudgetExceeded. The error carries the
correlation and call ids that were sent, and keeps the original exception on
.framework_error. This also works for streaming runs
(agent.run(..., stream=True)): the typed error is raised while you iterate
the stream. The chat client sends with retries off, so a refused request is
sent once.
A gateway that cannot be reached surfaces as GatewayUnavailable, the error
the transport raised under Agent Framework’s wrapper. Errors that are not from
the gateway pass through unchanged. A donkey.run() block or a
@donkey.governed function applies the same bridge, so a run without the
middleware still ends typed. The middleware is marked with Agent Framework’s
@chat_middleware decorator. That is confirmed offline against the
tested agent-framework version, with no live
round-trip yet. If the decorator is
missing from your installed version, policy_middleware() raises a
NotImplementedError naming it.
Notes
- Constructor signature.
OpenAIChatClientandOpenAIChatCompletionClientboth takemodel,base_url,api_key, anddefault_headers(model_idis not accepted). If the import fails or an upstream release renames a kwarg,chat_client()raises aNotImplementedErrornaming the class path or signature to check, rather than a rawImportErrororTypeError. donkey.last_callis set in the context that made the call. A cold read (no call yet in this context) on aDonkeythat resolved only adapters like this one reportsstatus == LastCallStatus.UNOBSERVED, andOBSERVEDonce a call has returned.
See the error taxonomy for the full PolicyViolation hierarchy
that policy_middleware() raises.