Skip to Content
These docs describe donkey-kit v0.2.1. Release notes
FrameworksMS Agent Framework

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). Pass api="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 typed PolicyViolation such as PIIDetected.
  • Supported at connection_kwargs(). The client gets an async_client that sends through the SDK’s shared HTTP client, so per-run correlation, retries, spans, donkey.last_call and the jwt-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

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=ClientRequest
"chat_completions" (default)OpenAIChatCompletionClientPOST /chat/completions
"responses"OpenAIChatClientPOST /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 a ChatClientException.
  • 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. OpenAIChatClient and OpenAIChatCompletionClient both take model, base_url, api_key, and default_headers (model_id is not accepted). If the import fails or an upstream release renames a kwarg, chat_client() raises a NotImplementedError naming the class path or signature to check, rather than a raw ImportError or TypeError.
  • donkey.last_call is set in the context that made the call. A cold read (no call yet in this context) on a Donkey that resolved only adapters like this one reports status == LastCallStatus.UNOBSERVED, and OBSERVED once a call has returned.

See the error taxonomy for the full PolicyViolation hierarchy that policy_middleware() raises.

Last updated on