Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/unsupported-boundary.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Classification:
| LLM Token Based Rate Limit `429` response: `x-token-limit`, `x-token-remaining`, and `x-token-reset` | `core.budget.Budget`; `core.errors.classify` | **Documented and public** | The [LLM Token Based Rate Limit policy](https://docs.mulesoft.com/gateway/latest/policies-included-llm-token-rate-limit) documents all three headers and the reset unit. The SDK's rejection fixture is `VERIFIED (LIVE)` in `verified-apis.md` §4. |
| LLM PII Detection `403` rejection body (`error.type = "pii_detected"`) | `core.errors.classify` | **Documented and public** | The [LLM PII Detection policy](https://docs.mulesoft.com/gateway/latest/policies-included-llm-pii-detection) documents the rejection body. The SDK also captured it live; see `verified-apis.md` §4. |
| Regex Prompt Guard body and Injection Protection response header | `core.errors.classify` | **Documented and public** | MuleSoft documents `matched_patterns` in [Regex Prompt Guard](https://docs.mulesoft.com/gateway/latest/policies-included-regex-prompt-guard) and `x-injection-protection: blocked` in [Injection Protection](https://docs.mulesoft.com/gateway/latest/policies-included-injection-protection). These discriminators are documented but still pending direct live capture; see `verified-apis.md` §4. |
| Azure Content Safety and Amazon Bedrock Guardrails rejection headers and bodies | `core.errors.classify` | **Documented and public** | The [Azure Content Safety](https://docs.mulesoft.com/gateway/latest/policies-included-azure-content-safety) and [Amazon Bedrock Guardrails](https://docs.mulesoft.com/gateway/latest/policies-included-bedrock-guardrails) policy pages document their action/reason headers and `403` bodies. These shapes are pending direct live capture; see `verified-apis.md` §4. |
| Azure Content Safety and Amazon Bedrock Guardrails rejection headers and bodies | `core.errors.classify` | **Documented and public** | The [Azure Content Safety](https://docs.mulesoft.com/gateway/latest/policies-included-azure-content-safety) and [Amazon Bedrock Guardrails](https://docs.mulesoft.com/gateway/latest/policies-included-bedrock-guardrails) policy pages document their action/reason headers and `403` bodies. Both are now live-captured — Azure 2026-09-22, Bedrock 2026-09-24 (#568); see `verified-apis.md` §4. |
| Upstream provider error pass-through: non-`429` `4xx` nested error envelope; generic `5xx` status family | `core.errors.classify` | **Documented but no SLA for third-party use** | A real Model Proxy passed an OpenAI `400 model_not_found` envelope through verbatim, including `code`, `type`, and `param` (`VERIFIED (LIVE)`, `verified-apis.md` §4). MuleSoft's public Model Proxy page does not state a compatibility contract for provider error envelopes, whose schema ultimately belongs to the upstream provider. The SDK maps any generic `5xx` to `UpstreamModelError` by status only and assumes no body shape. Owner: DDK maintainers; retain the captured provider fixture and classify changed envelopes conservatively. |
| Successful-response budget window in `x-llm-proxy-ratelimit` prose | `core.budget.Budget` | **Documented but no SLA for third-party use** | Captured from a real Model Proxy and parsed fail-open: an absent or changed sentence is ignored, never fatal. The exact prose contract is `VERIFIED (LIVE)` in `verified-apis.md` §4 but is not stated in the public policy page. Owner: DDK maintainers; re-capture on gateway-policy changes. |
| Gateway identity and routing response metadata: `x-request-id`, `x-envoy-decorator-operation`, and `x-llm-proxy-{routing-type,routing-fallback,llm-provider,llm-model}` | `core.lastcall.LastCall`; `core.transport.DonkeyAsyncClient` | **Documented but no SLA for third-party use** | Captured from a real Model Proxy (`verified-apis.md` §2 and §3). Parsers are fail-open: missing or unrecognised values become `None`, and only an explicit `routing-fallback: true` changes retry behavior. Owner: DDK maintainers; contract changes require new fixtures before parser changes. |
Expand Down
28 changes: 16 additions & 12 deletions docs/verified-apis.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@
> removed. The regex-prompt-guard (`403` +
> `matched_patterns`) and Azure content-safety (`403` + `…-action: reject`)
> rejection shapes were also `VERIFIED (LIVE)` on 2026-09-22 against the deployed
> `ddk-injection-guard` / `ddk-azure-content-safety` proxies (#253). Still
> `ddk-injection-guard` / `ddk-azure-content-safety` proxies (#253), and the
> Bedrock Guardrails content-safety shape (same `…-action: reject` family) on
> 2026-09-24 against `ddk-bedrock-guardrails` (#568). Still
> `UNVERIFIED`: the Injection Protection body (`x-injection-protection: blocked`,
> a distinct policy — no proxy deployed) and any other content-moderation /
> Bedrock-guardrail shape (§4, pending live capture #253), and the **framework
> a distinct policy — no proxy deployed) and any other unrecognised
> content-moderation shape (§4, pending live capture #253), and the **framework
> constructor/binding names** (§§8–10). §11
> records A2D shapes
> (`VERIFIED-SHAPE-ONLY`), used only to validate SDK value types; no blocked
Expand Down Expand Up @@ -91,7 +93,7 @@ request against the deployed gateway.
| Item | Where used | Status | Verified value | Date | Source |
|---|---|---|---|---|---|
| Ingress base URL the SDK targets | `llm/client.py` | VERIFIED (LIVE) | `https://<ingress-gw-host>/<instance-path>/` — e.g. `https://agent-network-ingress-gw-zovwbn.jeg62f.usa-e2.cloudhub.io/openai-sdk/`. **No `/v1` at the ingress**; OpenAI path segment appended directly (`/responses`, and OpenAI-native routes) | 2026-08-28 | live probe |
| Ingress **Format** (wire protocol the proxy accepts) — Anthropic-native route (#304) | `integrations/anthropic.py`, `llm/client.py` | UNVERIFIED (live) — **but documented** | Model Proxy supports **three selectable ingress Formats**, fixed at creation and unchangeable: **OpenAI** (works with all providers; multi-routing + fallback), **Gemini** (native Gemini format; single-provider), and **Anthropic** (native Anthropic API format for direct Claude requests; single-provider, no multi-routing/fallback). So the Anthropic-native route **exists as a product capability** — correcting the earlier "OpenAI-only, native route maybe-absent" assumption. Caveats keeping this UNVERIFIED for the SDK: (a) it must be *provisioned* that way — every DDK proxy is `Format=OpenAI`, so `donkey.anthropic.client()` pointed at them reaches Claude only as an *upstream provider*, not natively; (b) the **exact Anthropic ingress path** (docs show `/chat/completions` + `/responses` for OpenAI but do not state the Anthropic path — likely `/v1/messages`) and its **live behavior** are not yet captured against a real `Format=Anthropic` proxy. Per §12.8 (LLM data-plane, category 3) this stays behind the adapter's one-time warning until a live capture. Live capture blocked on provisioning a `Format=Anthropic` proxy (#304). The **Gemini** Format is now live-verified — see the next row. | 2026-09-23 | `docs.mulesoft.com/general/model-proxy` ("Supported Endpoint Formats"); live control-plane read of `ddk-bedrock-anthropic-model-routing` (instance `21192625`, DDK/Sandbox) confirming it is `Format=OpenAI`/model-based |
| Ingress **Format** — Anthropic-native route (#304) | `integrations/anthropic.py`, `llm/client.py` | VERIFIED (LIVE) | A proxy provisioned `Format=Anthropic` exposes a **native Anthropic Messages ingress** at `POST /<base-path>/v1/messages`. The Anthropic request shape (header `anthropic-version: 2023-06-01`, body `{"model":…,"max_tokens":N,"messages":[{"role":"user","content":…}]}`) → **200** with a native Anthropic body (`type:"message"`, `role:"assistant"`, `content[].text`, `stop_reason:"end_turn"`, `usage.input_tokens`/`output_tokens`/`service_tier`) plus native `anthropic-ratelimit-*` / `request-id` / `anthropic-organization-id` response headers. It is a **passthrough, not a transcode** — gateway headers `x-llm-proxy-llm-provider: anthropic`, `x-llm-proxy-llm-model: <model>`, `x-llm-proxy-request-success` (`routingType` stays `model-based`; enabled by the per-upstream `routing[].upstreams[].llmConfigs.format: anthropic` field). An **OpenAI-shaped** request to `/<base-path>/chat/completions` → **404** (empty body) — the OpenAI route is simply not served on a native-Anthropic ingress (differs from Gemini's 400; both prove the ingress rejects the OpenAI wire format). Same `client_id`/`client_secret` CIE auth (no `client_id` → 401 "Client ID is not present", `www-authenticate: Client-ID-Enforcement`). Native ingress is **single-route only** (no multi-routing/fallback — that stays OpenAI-only); the upstream must speak native Anthropic (`provider: anthropic`/`api.anthropic.com` — **Bedrock is not usable** here). The `donkey.anthropic` adapter targets this route, but the SDK's own DDK proxies are `Format=OpenAI` — point `base_url` at a `Format=Anthropic` proxy to use the native surface. The roadmap "OAuth Client ID/Secret for configuring LLMs" is an **upstream/control-plane credential** concern and does **not** change this verified consumer `client_id`/`client_secret` request-header pair. | 2026-09-24 | live probe against `ddk-anthropic-inbound` (instance `21194086`, DDK/Sandbox) — captured in `python/tests/fixtures/anypoint/anthropic_inbound/` |
| Ingress **Format** — Gemini-native route (#540) | `llm/client.py` (no SDK adapter — see note) | VERIFIED (LIVE) | A proxy provisioned `Format=Gemini` exposes a **native Gemini ingress** at `POST /<base-path>/models/<model>:generateContent`. The Gemini request shape (`{"contents":[{"role":"user","parts":[{"text":…}]}]}`) → **200** with a native Gemini body (`candidates[].content.parts[].text`, `role:"model"`, `usageMetadata` incl. `thoughtsTokenCount`, `modelVersion`, `responseId`). It is a **passthrough, not a transcode** — response header `x-llm-proxy-model-based-routing-success: "Request passed through without model-based routing."` (`routingType` stays `model-based`; enabled by the per-upstream `routing[].upstreams[].llmConfigs.format: gemini` field). An **OpenAI-shaped** request to `/<base-path>/chat/completions` → **400** with a native-Gemini error envelope (a JSON *array* `[{"error":{"code":400,"status":"INVALID_ARGUMENT",…}}]`), confirming the ingress does not accept the OpenAI wire format. Same `client_id`/`client_secret` CIE auth (no `client_id` → 401 "Client ID is not present"). Native ingress is **single-route only** (no multi-routing/fallback — that stays OpenAI-only). **No SDK adapter ships** for this: a `google-genai` adapter is demand-driven (#540/#244, "do not guess it"); Gemini also remains reachable as an *upstream provider* behind an OpenAI ingress (supported-providers row below). | 2026-09-23 | live probe against `ddk-gemini-inbound` (instance `21193369`, DDK/Sandbox) — captured in `python/tests/fixtures/anypoint/gemini_inbound/` |
| Endpoint/API surface | `llm/client.py` | VERIFIED (LIVE) | OpenAI **Responses API** works (`POST /openai-sdk/responses`, body `{model, input}`). Upstream registered as `https://api.openai.com/v1/`; `proxyUri http://0.0.0.0:8081/openai-sdk` | 2026-08-28 | `api:describe`, live probe |
| Auth: header name / model | `core/transport.py`, `core/config.py` | VERIFIED (LIVE) | **`client_id` + `client_secret` request headers** (NOT bearer) — enforced by `client-id-enforcement` 1.3.3. A consumer **credential pair**, mapped to an Anypoint client application | 2026-08-28 | live probe + `policy:list` |
Expand Down Expand Up @@ -300,22 +302,24 @@ is these rows, not an `Unverified(...)` flip:
| Shape | HTTP | Discriminator | Maps to | Source |
|---|---|---|---|---|
| Regex Prompt Guard | `403` | top-level `matched_patterns` list (flat-string `error`) | `PromptInjectionBlocked` (`policy="regex-prompt-guard"`) | [Regex Prompt Guard policy](https://docs.mulesoft.com/gateway/latest/policies-included-regex-prompt-guard) (v1.11.4); VERIFIED (LIVE) 2026-09-22 on `ddk-injection-guard` (instance 21179713) |
| Content safety / guardrails | `403` | `x-llm-proxy-azure-content-safety-action` / `x-llm-proxy-bedrock-guardrail-action` == `reject`; reasons in the sibling `…-reason` header | `ContentSafetyBlocked` (parses `categories`) | [Azure Content Safety policy](https://docs.mulesoft.com/gateway/latest/policies-included-azure-content-safety) (v1.13.0); [Amazon Bedrock Guardrails policy](https://docs.mulesoft.com/gateway/latest/policies-included-bedrock-guardrails) (v1.13.0); VERIFIED (LIVE) 2026-09-22 on `ddk-azure-content-safety` (instance 21180957, Azure verified; Bedrock still documented-only) |
| Content safety / guardrails | `403` | `x-llm-proxy-azure-content-safety-action` / `x-llm-proxy-bedrock-guardrail-action` == `reject`; reasons in the sibling `…-reason` header | `ContentSafetyBlocked` (parses `categories`) | [Azure Content Safety policy](https://docs.mulesoft.com/gateway/latest/policies-included-azure-content-safety) (v1.13.0); [Amazon Bedrock Guardrails policy](https://docs.mulesoft.com/gateway/latest/policies-included-bedrock-guardrails) (v1.13.0); VERIFIED (LIVE) 2026-09-22 on `ddk-azure-content-safety` (instance 21180957) + 2026-09-24 on `ddk-bedrock-guardrails` (#568) |
| Unrecognised refusal (fall-through) | any non-429 `4xx` | matches **none** of the discriminators above; no nested `error` envelope; no `www-authenticate` (e.g. an unrecognised `403`) | generic `PolicyViolation` (`policy="unknown"`), message says **shape unconfirmed** and names the observed status + `x-llm-proxy-*` headers | UNVERIFIED — no known contract; capture + type via #184/#253 |

The Regex Prompt Guard (row 7) and Azure Content Safety (row 8) shapes are now
LIVE-captured (2026-09-22, #253). Still **uncaptured**: the **Injection
The Regex Prompt Guard (row 7) and Content Safety (row 8) shapes are now
LIVE-captured — Regex Prompt Guard and Azure Content Safety on 2026-09-22
(#253), and Bedrock Guardrails on 2026-09-24 (#568), the sibling vendor of the
same row-8 `…-action: reject` family. Still **uncaptured**: the **Injection
Protection** body (`x-injection-protection: blocked`, a policy *distinct* from
Regex Prompt Guard — no proxy running it is deployed) and any **other
content-moderation / federated-guardrail** shape (e.g. Bedrock Guardrails).
unrecognised content-moderation / federated-guardrail** shape.
Rather than guess a type for them, `classify()` surfaces any such unrecognised
refusal **honestly** — a `PolicyViolation` whose message states the shape is
unconfirmed and whose remediation asks the operator to file the observed
status/headers/body so the shape can be typed (#184). This is the last row
above; it is deliberately **not** an `AuthError`, even for a `403`, once the
verified `www-authenticate` auth shape is excluded. Closing these last shapes
needs an Injection Protection (and a Bedrock Guardrails) proxy deployed to
capture against; that residual keeps #253 open.
verified `www-authenticate` auth shape is excluded. Closing the last shape
needs an Injection Protection proxy deployed to capture against; that residual
keeps #253 open.

**Budget window emission — two forms, keyed on outcome not status class
(LIVE-VERIFIED).** The gateway signals its token-rate-limit window in two
Expand Down Expand Up @@ -442,7 +446,7 @@ provisioning API. Exact REST calls behind the CLI are now recorded in §12
| Google ADK | `google.adk.models.lite_llm.LiteLlm(model="openai/…", api_base, extra_headers)` | UNVERIFIED | — | — | — |
| MS Agent Framework | `agent_framework.openai.OpenAIChatClient(model, base_url, api_key, default_headers)` | VERIFIED | Class path confirmed; the constructor kwarg is `model=` — `model_id` is **not** accepted. `base_url`/`api_key`/`default_headers` all accepted, so `connection_kwargs()` is unchanged. Pinned to agent-framework 1.19.0. | 2026-09-22 | #520 (`verify_frameworks.py --only agent_framework`) |
| OpenAI Agents SDK | `agents.OpenAIChatCompletionsModel(model, openai_client=AsyncOpenAI(...))` | UNVERIFIED | — | — | — |
| Anthropic SDK | `anthropic.AsyncAnthropic(base_url, api_key, default_headers, http_client)` — model id per-call; the proxy's Anthropic-native ingress **Format is documented** but its exact path + live behavior are UNVERIFIED (see §2, #304) | UNVERIFIED | — | — | — |
| Anthropic SDK | `anthropic.AsyncAnthropic(base_url, api_key, default_headers, http_client)` — model id per-call; the proxy's Anthropic-native ingress route is now **LIVE-verified at `POST /<base-path>/v1/messages`** (see §2, #304) and requires a `Format=Anthropic` proxy. Row stays UNVERIFIED for the **constructor signature** only (#34). | UNVERIFIED | — | — | — |
| CrewAI | `crewai.LLM(model="openai/…", base_url, api_key, extra_headers)` | UNVERIFIED | — | — | — |
| LlamaIndex | `llama_index.llms.openai_like.OpenAILike(is_chat_model=True)` | UNVERIFIED | — | — | — |
| Strands | `strands.models.openai.OpenAIModel(client_args={...})` | UNVERIFIED | — | — | — |
Expand Down
19 changes: 9 additions & 10 deletions python/examples/anthropic/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,15 @@ and the model id is a per-call argument (`c.messages.create(model=..., ...)`),
not a constructor one. So this adapter exposes `client()` rather than the
`model(...)` factory the OpenAI-compatible adapters use.

**Unverified dependency (docs/verified-apis.md §2, #304).** MuleSoft Model Proxy
offers a native **Anthropic** ingress Format — one of three selectable Formats
(OpenAI / Gemini / Anthropic) fixed at proxy creation — so the Anthropic-native
route is a real product capability. But every DDK proxy is `Format=OpenAI`, so
**Proxy route (docs/verified-apis.md §2, #304).** MuleSoft Model Proxy offers a
native **Anthropic** ingress Format — one of three selectable Formats (OpenAI /
Gemini / Anthropic) fixed at proxy creation. That route is now **LIVE-verified**:
a `Format=Anthropic` proxy serves the Anthropic Messages API natively at
`POST /<base-path>/v1/messages` (an OpenAI-shaped `/chat/completions` request 404s
there). Usage caveat: the SDK's own default DDK proxies are `Format=OpenAI`, so
this client pointed at them reaches Claude only as an *upstream provider*, not
natively; and the exact Anthropic ingress path (likely `/v1/messages`) + live
behavior are not yet captured against a `Format=Anthropic` proxy. Override
`base_url` via `**kw` to point at a `Format=Anthropic` proxy to use the native
surface. The first `client()` call emits a one-time unverified-route warning;
this example does not make a live call.
natively. Override `base_url` via `**kw` to point at a `Format=Anthropic` proxy to
use the native surface. This example does not make a live call.

> 📖 **Prefer reading to running?** The canonical walkthrough — install,
> configure, and the manual equivalent — is in the docs:
Expand Down Expand Up @@ -52,7 +51,7 @@ import httpx
from anthropic import AsyncAnthropic

c = AsyncAnthropic(
base_url=DONKEY_LLM_PROXY_URL, # UNVERIFIED path/behavior: use a Format=Anthropic proxy
base_url=DONKEY_LLM_PROXY_URL, # native /v1/messages route LIVE-verified: use a Format=Anthropic proxy
api_key="unused", # proxy enforces client_id/client_secret headers
default_headers={
"client_id": DONKEY_LLM_PROXY_CLIENT_ID,
Expand Down
Loading
Loading