# Request/Response Transformation for AI APIs **Last updated:** 2026-06-24 **Status:** GA **Category:** API Management & AI Gateway **Type:** reference **Source:** https://learn.microsoft.com/azure/api-management/genai-gateway-capabilities --- ## Innhold - [Introduksjon](#introduksjon) - [Model-agnostiske API-schemaer](#model-agnostiske-api-schemaer) - [Header Rewriting](#header-rewriting) - [Payload-transformasjon](#payload-transformasjon) - [Error Response Normalization](#error-response-normalization) - [Versjonstranslasjon](#versjonstranslasjon) - [Policy Fragments for Reuse](#policy-fragments-for-reuse) - [Bicep: Oppsett av transformasjons-API](#bicep-oppsett-av-transformasjons-api) - [Referanser](#referanser) - [For Cosmo](#for-cosmo) ## Introduksjon Azure API Management (APIM) tilbyr over 75 innebygde policies for transformasjon av foresporsler og svar. Nar organisasjoner eksponerer AI-modeller gjennom APIM som AI gateway, blir transformasjon av request og response kritisk for a standardisere grensesnittet mellom ulike AI-backends (Azure OpenAI, Microsoft Foundry, tredjeparts LLM-er) og konsumerende applikasjoner. Ved a implementere model-agnostiske API-schemaer kan man bytte ut underliggende modeller uten a bryte klientkontrakter. For norsk offentlig sektor er dette spesielt relevant: organisasjoner som Direktoratet for digital tjenesteutvikling, NAV og Skatteetaten kan etablere et standardisert AI-API-lag som abstraherer bort leverandoravhengigheter. Dette stotter prinsippet om leverandoruavhengighet fra Digitaliseringsdirektoratets arkitekturprinsipper, og gir fleksibilitet til a bytte mellom Azure OpenAI, Microsoft Foundry-modeller og fremtidige norske sprakmodeller uten endringer i klientapplikasjoner. Transformasjonspolicies i APIM opererer i fire faser: inbound (request fra klient), backend (request til backend), outbound (response fra backend) og on-error. Denne referansen dekker praktiske monstre for a bygge et robust, model-agnostisk AI-API-lag med APIM-policies. --- ## Model-agnostiske API-schemaer ### Problemet med leverandorspesifikke API-er Ulike AI-leverandorer bruker forskjellige API-formater: | Leverandor | Endpoint-format | Auth-metode | Response-struktur | |------------|----------------|-------------|-------------------| | Azure OpenAI | `/openai/deployments/{id}/chat/completions` | API Key / Entra ID | `choices[].message.content` | | Microsoft Foundry | `/models/chat/completions` | Managed Identity | `choices[].message.content` | | Anthropic | `/v1/messages` | API Key | `content[].text` | | Google Vertex AI | `/v1/projects/{id}/locations/{loc}/publishers/google/models/{model}:predict` | OAuth 2.0 | `predictions[]` | | Open-source (vLLM) | `/v1/chat/completions` | Custom | `choices[].message.content` | > **Innebygd alternativ — unified model API (Preview):** APIM tilbyr nå en _unified model API_ som gir facade-mønsteret nedenfor som innebygd funksjon: den eksponerer flere LLM-backends (OpenAI Chat Completions + Anthropic Messages) bak ett OpenAI-kompatibelt endepunkt, med automatisk format-translasjon, modell-aliaser, failover på tvers av leverandører og felles policy-governance. Vurder denne før du bygger custom transformasjon-policies manuelt. Foreløpig Preview (i classic-tiers via AI Gateway early-release-kanal). Se [Create and manage a unified model API](https://learn.microsoft.com/azure/api-management/unified-model-api). ### Designmonster: Facade API Schema Definer et internt standardskjema som alle AI-API-er mapper til: ```json { "model": "string", "messages": [ { "role": "system | user | assistant", "content": "string" } ], "parameters": { "temperature": 0.7, "max_tokens": 1000, "top_p": 1.0 }, "metadata": { "request_id": "string", "tenant_id": "string", "application": "string" } } ``` ### APIM Policy: Route basert pa modellnavn ```xml 2024-08-01-preview ``` --- ## Header Rewriting ### Autentiseringsheader-transformasjon Nar APIM fungerer som AI gateway, ma den ofte transformere autentiseringsheadere mellom klientens format og backendets format: ```xml @("Bearer " + (string)context.Variables["msi-access-token"]) ``` ### Tracking- og korrelasjonsheadere For observability og sporbarhet, legg til standardiserte headere: ```xml @(Guid.NewGuid().ToString()) @(context.RequestId.ToString()) @(context.Subscription?.Name ?? "unknown") @(context.Request.Headers.GetValueOrDefault("x-app-id", "unspecified")) @(context.Request.Headers.GetValueOrDefault("x-request-id", "")) @{ var body = context.Response.Body.As(preserveContent: true); return body?["model"]?.ToString() ?? "unknown"; } ``` ### Standard headere for AI-API-er | Header | Retning | Formal | |--------|---------|--------| | `x-request-id` | Request/Response | Unik foresporsels-ID for sporing | | `x-correlation-id` | Request/Response | Korrelasjon pa tvers av tjenester | | `x-tenant-id` | Request | Identifiserer leietaker/abonnement | | `x-model-used` | Response | Hvilken modell som behandlet foresporselen | | `x-token-usage` | Response | Token-forbruk for fakturering | | `x-processing-time-ms` | Response | Backend-behandlingstid | | `x-rate-limit-remaining` | Response | Gjenverende rate limit | --- ## Payload-transformasjon ### Transformere request fra standardformat til leverandorspesifikt Bruk `set-body` policy med Liquid-template eller C#-uttrykk: ```xml @{ var inbound = context.Request.Body.As(); var messages = (JArray)inbound["messages"]; string systemPrompt = ""; var userMessages = new JArray(); foreach (var msg in messages) { if (msg["role"]?.ToString() == "system") { systemPrompt = msg["content"]?.ToString(); } else { userMessages.Add(msg); } } var parameters = (JObject)inbound["parameters"] ?? new JObject(); var transformed = new JObject { ["model"] = inbound["model"], ["max_tokens"] = parameters["max_tokens"] ?? 1024, ["system"] = systemPrompt, ["messages"] = userMessages }; if (parameters["temperature"] != null) transformed["temperature"] = parameters["temperature"]; return transformed.ToString(); } ``` ### Transformere response fra leverandorformat til standardformat ```xml @{ var response = context.Response.Body.As(preserveContent: true); var content = response["content"] as JArray; string text = content?[0]?["text"]?.ToString() ?? ""; var normalized = new JObject { ["id"] = response["id"], ["object"] = "chat.completion", ["model"] = response["model"], ["choices"] = new JArray { new JObject { ["index"] = 0, ["message"] = new JObject { ["role"] = "assistant", ["content"] = text }, ["finish_reason"] = response["stop_reason"]?.ToString() == "end_turn" ? "stop" : response["stop_reason"] } }, ["usage"] = new JObject { ["prompt_tokens"] = response["usage"]?["input_tokens"], ["completion_tokens"] = response["usage"]?["output_tokens"], ["total_tokens"] = (int)(response["usage"]?["input_tokens"] ?? 0) + (int)(response["usage"]?["output_tokens"] ?? 0) } }; return normalized.ToString(); } ``` --- ## Error Response Normalization ### Standardisert feilformat Ulike AI-backends returnerer feil i forskjellige formater. Normaliser til et konsistent format: ```xml application/json @{ var retryAfter = context.Response.Headers.GetValueOrDefault("Retry-After", "60"); return new JObject { ["error"] = new JObject { ["code"] = "rate_limit_exceeded", ["message"] = "Token eller request rate limit er overskredet. Prov igjen etter angitt tid.", ["type"] = "rate_limit_error", ["retry_after_seconds"] = int.Parse(retryAfter), ["request_id"] = context.RequestId.ToString() } }.ToString(); } @{ return new JObject { ["error"] = new JObject { ["code"] = "model_overloaded", ["message"] = "AI-modellen er midlertidig overbelastet. Foresporselen vil automatisk forsokes pa nytt.", ["type"] = "server_error", ["request_id"] = context.RequestId.ToString() } }.ToString(); } @{ return new JObject { ["error"] = new JObject { ["code"] = "content_filtered", ["message"] = "Foresporselen ble blokkert av innholdsfilter. Vennligst reformuler.", ["type"] = "content_policy_error", ["request_id"] = context.RequestId.ToString() } }.ToString(); } @{ return new JObject { ["error"] = new JObject { ["code"] = "internal_error", ["message"] = "En uventet feil oppstod. Kontakt systemadministrator.", ["type"] = "api_error", ["status_code"] = context.Response.StatusCode, ["request_id"] = context.RequestId.ToString() } }.ToString(); } ``` ### Standard feilkoder for AI-API-er | HTTP-kode | Feilkode | Beskrivelse | |-----------|----------|-------------| | 400 | `invalid_request` | Ugyldig foresporselsformat | | 400 | `content_filtered` | Innholdsfilter utlost | | 401 | `authentication_error` | Ugyldig eller manglende autentisering | | 403 | `authorization_error` | Ingen tilgang til denne modellen | | 404 | `model_not_found` | Modellen finnes ikke | | 429 | `rate_limit_exceeded` | For mange foresporsler | | 500 | `internal_error` | Intern serverfeil | | 503 | `model_overloaded` | Modellen er overbelastet | --- ## Versjonstranslasjon ### Handtere flere API-versjoner med transformasjon Nar AI-API-er utvikler seg, kan APIM oversette mellom gammel og ny versjon: ```xml @{ var body = context.Request.Body.As(preserveContent: true); // v1 used "prompt" field, v2 uses "messages" if (body["prompt"] != null && body["messages"] == null) { var messages = new JArray { new JObject { ["role"] = "user", ["content"] = body["prompt"] } }; body.Remove("prompt"); body["messages"] = messages; } // v1 used "max_tokens_to_sample", v2 uses "max_tokens" if (body["max_tokens_to_sample"] != null) { body["max_tokens"] = body["max_tokens_to_sample"]; body.Remove("max_tokens_to_sample"); } return body.ToString(); } ``` ### Content validation for AI requests ```xml application/json {"error":{"code":"invalid_request","message":"Field 'messages' is required and must be non-empty."}} application/json {"error":{"code":"payload_too_large","message":"Request body exceeds 128KB limit."}} ``` --- ## Policy Fragments for Reuse APIM stotter policy fragments for gjenbruk av transformasjonslogikk: ```xml @(Guid.NewGuid().ToString()) @(context.RequestId.ToString()) @(DateTime.UtcNow.ToString("o")) ``` Bruk fragmentet i policies: ```xml ``` --- ## Bicep: Oppsett av transformasjons-API ```bicep resource apiManagement 'Microsoft.ApiManagement/service@2023-09-01-preview' existing = { name: apimName } resource aiApi 'Microsoft.ApiManagement/service/apis@2023-09-01-preview' = { parent: apiManagement name: 'ai-gateway-api' properties: { displayName: 'AI Gateway API' path: 'ai' protocols: [ 'https' ] subscriptionRequired: true subscriptionKeyParameterNames: { header: 'x-api-key' query: 'api-key' } apiType: 'http' } } resource chatOperation 'Microsoft.ApiManagement/service/apis/operations@2023-09-01-preview' = { parent: aiApi name: 'chat-completions' properties: { displayName: 'Chat Completions' method: 'POST' urlTemplate: '/chat/completions' request: { headers: [ { name: 'Content-Type' type: 'string' required: true defaultValue: 'application/json' } ] } responses: [ { statusCode: 200 description: 'Successful completion' } ] } } ``` --- ## Referanser - [Policies in Azure API Management](https://learn.microsoft.com/en-us/azure/api-management/api-management-howto-policies) -- oversikt over policy-konseptet - [API Management policy reference - Transformation](https://learn.microsoft.com/en-us/azure/api-management/api-management-policies#transformation) -- komplett liste over transformasjonspolicies - [Set body policy](https://learn.microsoft.com/en-us/azure/api-management/set-body-policy) -- detaljert dokumentasjon for set-body - [Set header policy](https://learn.microsoft.com/en-us/azure/api-management/set-header-policy) -- header-manipulering - [Rewrite URI policy](https://learn.microsoft.com/en-us/azure/api-management/rewrite-uri-policy) -- URL-omskriving - [AI gateway in Azure API Management](https://learn.microsoft.com/en-us/azure/api-management/genai-gateway-capabilities) -- AI gateway-oversikt - [Policy fragments in API Management](https://learn.microsoft.com/en-us/azure/api-management/policy-fragments) -- gjenbrukbare policy-fragmenter - [Tutorial: Transform and protect your API](https://learn.microsoft.com/en-us/azure/api-management/transform-api) -- hands-on tutorial ## For Cosmo - **Bruk denne referansen** nar kunden onsker a bygge et model-agnostisk AI-API-lag som abstraherer bort leverandoravhengigheter, eller nar de trenger a standardisere feilhandtering pa tvers av AI-backends. - Anbefal alltid policy fragments for transformasjonslogikk som gjenbrukes pa tvers av flere API-er -- dette reduserer vedlikeholdsbyrden betydelig. - For norsk offentlig sektor, fremhev at model-agnostiske fasader stotter leverandoruavhengighet i trad med Digitaliseringsdirektoratets prinsipper. - Vurder a kombinere transformasjonspolicies med `validate-content` policy for a sikre at bade inngangs- og utgangsdata overholder definerte JSON-schemaer. - For organisasjoner som bruker flere AI-leverandorer (Azure OpenAI + Anthropic + open-source), er facade-monsteret med APIM en arkitekturforsterkning som gir fleksibilitet uten a eksponere backend-kompleksitet til konsumenter.