feat(ultraplan-local): v1.6.0 — /ultraresearch-local deep research command
Add /ultraresearch-local for structured research combining local codebase analysis with external knowledge via parallel agent swarms. Produces research briefs with triangulation, confidence ratings, and source quality assessment. New command: /ultraresearch-local with modes --quick, --local, --external, --fg. New agents: research-orchestrator (opus), docs-researcher, community-researcher, security-researcher, contrarian-researcher, gemini-bridge (all sonnet). New template: research-brief-template.md. Integration: --research flag in /ultraplan-local accepts pre-built research briefs (up to 3), enriches the interview and exploration phases. Planning orchestrator cross-references brief findings during synthesis. Design principle: Context Engineering — right information to right agent at right time. Research briefs are structured artifacts in the pipeline: ultraresearch → brief → ultraplan --research → plan → ultraexecute. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
commit
baa2d0220b
488 changed files with 213221 additions and 0 deletions
|
|
@ -0,0 +1,571 @@
|
|||
# Request/Response Transformation for AI APIs
|
||||
|
||||
**Last updated:** 2026-02
|
||||
**Status:** GA
|
||||
**Category:** API Management & AI Gateway
|
||||
|
||||
---
|
||||
|
||||
## 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 Statens vegvesen, 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` |
|
||||
|
||||
### 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
|
||||
<policies>
|
||||
<inbound>
|
||||
<base />
|
||||
<!-- Parse request body -->
|
||||
<set-variable name="requestBody"
|
||||
value="@(context.Request.Body.As<JObject>(preserveContent: true))" />
|
||||
<set-variable name="modelName"
|
||||
value="@(((JObject)context.Variables["requestBody"])["model"]?.ToString())" />
|
||||
|
||||
<!-- Route to correct backend based on model -->
|
||||
<choose>
|
||||
<when condition="@(((string)context.Variables["modelName"]).StartsWith("gpt-"))">
|
||||
<set-backend-service backend-id="azure-openai-backend" />
|
||||
<rewrite-uri template="/openai/deployments/{modelName}/chat/completions" />
|
||||
<set-query-parameter name="api-version" exists-action="override">
|
||||
<value>2024-08-01-preview</value>
|
||||
</set-query-parameter>
|
||||
</when>
|
||||
<when condition="@(((string)context.Variables["modelName"]).StartsWith("claude-"))">
|
||||
<set-backend-service backend-id="anthropic-backend" />
|
||||
<rewrite-uri template="/v1/messages" />
|
||||
</when>
|
||||
<otherwise>
|
||||
<set-backend-service backend-id="foundry-backend" />
|
||||
<rewrite-uri template="/models/chat/completions" />
|
||||
</otherwise>
|
||||
</choose>
|
||||
</inbound>
|
||||
</policies>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Header Rewriting
|
||||
|
||||
### Autentiseringsheader-transformasjon
|
||||
|
||||
Nar APIM fungerer som AI gateway, ma den ofte transformere autentiseringsheadere mellom klientens format og backendets format:
|
||||
|
||||
```xml
|
||||
<policies>
|
||||
<inbound>
|
||||
<base />
|
||||
<!-- Remove client API key and use managed identity -->
|
||||
<set-header name="api-key" exists-action="delete" />
|
||||
<set-header name="Ocp-Apim-Subscription-Key" exists-action="delete" />
|
||||
|
||||
<!-- Authenticate with managed identity to Azure OpenAI -->
|
||||
<authentication-managed-identity
|
||||
resource="https://cognitiveservices.azure.com/"
|
||||
output-token-variable-name="msi-access-token" />
|
||||
<set-header name="Authorization" exists-action="override">
|
||||
<value>@("Bearer " + (string)context.Variables["msi-access-token"])</value>
|
||||
</set-header>
|
||||
</inbound>
|
||||
</policies>
|
||||
```
|
||||
|
||||
### Tracking- og korrelasjonsheadere
|
||||
|
||||
For observability og sporbarhet, legg til standardiserte headere:
|
||||
|
||||
```xml
|
||||
<policies>
|
||||
<inbound>
|
||||
<base />
|
||||
<!-- Add correlation headers -->
|
||||
<set-header name="x-request-id" exists-action="skip">
|
||||
<value>@(Guid.NewGuid().ToString())</value>
|
||||
</set-header>
|
||||
<set-header name="x-correlation-id" exists-action="skip">
|
||||
<value>@(context.RequestId.ToString())</value>
|
||||
</set-header>
|
||||
<set-header name="x-tenant-id" exists-action="override">
|
||||
<value>@(context.Subscription?.Name ?? "unknown")</value>
|
||||
</set-header>
|
||||
<set-header name="x-source-application" exists-action="override">
|
||||
<value>@(context.Request.Headers.GetValueOrDefault("x-app-id", "unspecified"))</value>
|
||||
</set-header>
|
||||
</inbound>
|
||||
<outbound>
|
||||
<base />
|
||||
<!-- Forward correlation headers to client -->
|
||||
<set-header name="x-request-id" exists-action="override">
|
||||
<value>@(context.Request.Headers.GetValueOrDefault("x-request-id", ""))</value>
|
||||
</set-header>
|
||||
<set-header name="x-model-used" exists-action="override">
|
||||
<value>@{
|
||||
var body = context.Response.Body.As<JObject>(preserveContent: true);
|
||||
return body?["model"]?.ToString() ?? "unknown";
|
||||
}</value>
|
||||
</set-header>
|
||||
</outbound>
|
||||
</policies>
|
||||
```
|
||||
|
||||
### 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
|
||||
<policies>
|
||||
<inbound>
|
||||
<base />
|
||||
<!-- Transform standard format to Anthropic API format -->
|
||||
<set-body>@{
|
||||
var inbound = context.Request.Body.As<JObject>();
|
||||
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();
|
||||
}</set-body>
|
||||
</inbound>
|
||||
</policies>
|
||||
```
|
||||
|
||||
### Transformere response fra leverandorformat til standardformat
|
||||
|
||||
```xml
|
||||
<policies>
|
||||
<outbound>
|
||||
<base />
|
||||
<!-- Normalize Anthropic response to OpenAI-compatible format -->
|
||||
<choose>
|
||||
<when condition="@(context.Request.Headers.GetValueOrDefault("x-backend-type", "") == "anthropic")">
|
||||
<set-body>@{
|
||||
var response = context.Response.Body.As<JObject>(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();
|
||||
}</set-body>
|
||||
</when>
|
||||
</choose>
|
||||
</outbound>
|
||||
</policies>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Response Normalization
|
||||
|
||||
### Standardisert feilformat
|
||||
|
||||
Ulike AI-backends returnerer feil i forskjellige formater. Normaliser til et konsistent format:
|
||||
|
||||
```xml
|
||||
<policies>
|
||||
<on-error>
|
||||
<base />
|
||||
<set-header name="Content-Type" exists-action="override">
|
||||
<value>application/json</value>
|
||||
</set-header>
|
||||
|
||||
<!-- Map backend-specific errors to standard format -->
|
||||
<choose>
|
||||
<!-- Rate limit exceeded -->
|
||||
<when condition="@(context.Response.StatusCode == 429)">
|
||||
<set-body>@{
|
||||
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();
|
||||
}</set-body>
|
||||
<set-status code="429" reason="Rate Limit Exceeded" />
|
||||
</when>
|
||||
|
||||
<!-- Model overloaded -->
|
||||
<when condition="@(context.Response.StatusCode == 503)">
|
||||
<set-body>@{
|
||||
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();
|
||||
}</set-body>
|
||||
<set-status code="503" reason="Service Unavailable" />
|
||||
</when>
|
||||
|
||||
<!-- Content filter triggered -->
|
||||
<when condition="@(context.Response.StatusCode == 400 &&
|
||||
context.Response.Body.As<string>(preserveContent: true).Contains("content_filter"))">
|
||||
<set-body>@{
|
||||
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();
|
||||
}</set-body>
|
||||
<set-status code="400" reason="Content Filtered" />
|
||||
</when>
|
||||
|
||||
<!-- Generic error -->
|
||||
<otherwise>
|
||||
<set-body>@{
|
||||
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();
|
||||
}</set-body>
|
||||
<set-status code="500" reason="Internal Server Error" />
|
||||
</otherwise>
|
||||
</choose>
|
||||
</on-error>
|
||||
</policies>
|
||||
```
|
||||
|
||||
### 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
|
||||
<policies>
|
||||
<inbound>
|
||||
<base />
|
||||
<set-variable name="apiVersion"
|
||||
value="@(context.Request.Headers.GetValueOrDefault("api-version",
|
||||
context.Request.Url.Query.GetValueOrDefault("api-version", "2024-08-01")))" />
|
||||
|
||||
<!-- Transform v1 format to v2 format -->
|
||||
<choose>
|
||||
<when condition="@(((string)context.Variables["apiVersion"]).StartsWith("2023-"))">
|
||||
<set-body>@{
|
||||
var body = context.Request.Body.As<JObject>(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();
|
||||
}</set-body>
|
||||
</when>
|
||||
</choose>
|
||||
</inbound>
|
||||
</policies>
|
||||
```
|
||||
|
||||
### Content validation for AI requests
|
||||
|
||||
```xml
|
||||
<policies>
|
||||
<inbound>
|
||||
<base />
|
||||
<!-- Validate required fields -->
|
||||
<choose>
|
||||
<when condition="@{
|
||||
var body = context.Request.Body.As<JObject>(preserveContent: true);
|
||||
return body?["messages"] == null || ((JArray)body["messages"]).Count == 0;
|
||||
}">
|
||||
<return-response>
|
||||
<set-status code="400" reason="Bad Request" />
|
||||
<set-header name="Content-Type" exists-action="override">
|
||||
<value>application/json</value>
|
||||
</set-header>
|
||||
<set-body>{"error":{"code":"invalid_request","message":"Field 'messages' is required and must be non-empty."}}</set-body>
|
||||
</return-response>
|
||||
</when>
|
||||
</choose>
|
||||
|
||||
<!-- Enforce max message length -->
|
||||
<choose>
|
||||
<when condition="@(context.Request.Body.As<string>(preserveContent: true).Length > 128000)">
|
||||
<return-response>
|
||||
<set-status code="413" reason="Payload Too Large" />
|
||||
<set-header name="Content-Type" exists-action="override">
|
||||
<value>application/json</value>
|
||||
</set-header>
|
||||
<set-body>{"error":{"code":"payload_too_large","message":"Request body exceeds 128KB limit."}}</set-body>
|
||||
</return-response>
|
||||
</when>
|
||||
</choose>
|
||||
</inbound>
|
||||
</policies>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Policy Fragments for Reuse
|
||||
|
||||
APIM stotter policy fragments for gjenbruk av transformasjonslogikk:
|
||||
|
||||
```xml
|
||||
<!-- Fragment: ai-standard-headers -->
|
||||
<fragment>
|
||||
<set-header name="x-request-id" exists-action="skip">
|
||||
<value>@(Guid.NewGuid().ToString())</value>
|
||||
</set-header>
|
||||
<set-header name="x-correlation-id" exists-action="skip">
|
||||
<value>@(context.RequestId.ToString())</value>
|
||||
</set-header>
|
||||
<set-header name="x-timestamp" exists-action="override">
|
||||
<value>@(DateTime.UtcNow.ToString("o"))</value>
|
||||
</set-header>
|
||||
</fragment>
|
||||
```
|
||||
|
||||
Bruk fragmentet i policies:
|
||||
|
||||
```xml
|
||||
<policies>
|
||||
<inbound>
|
||||
<base />
|
||||
<include-fragment fragment-id="ai-standard-headers" />
|
||||
<!-- Additional inbound policies -->
|
||||
</inbound>
|
||||
</policies>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue