Steg 9 (R4): unified migrate-corpus.mjs --write over engineering/governance/ infrastructure/security. 327 filer mutert, verified=null, prosa byte-identisk (fra første ## seksjon), advisor urørt (0 endringer). To applier-fixes oppdaget under kjøring (TDD, RED→GREEN): - insertHeaderFields: anker faller nå tilbake når en meta-linje selv passerer 500B (2 filer pakket et avsnitt i **Status:** → Type/Source landet utenfor scan-vinduet, applierens post-write-assertion fanget + restaurerte). - normalizeStaleVerified: fjerner nå ALLE stale non-date **Verified:** i 500B-vinduet, inkl. stray body-dup rett under --- (9 mlops-genaiops-filer var ellers falskt "verified"/fresh, droppet fra worklist). Operatør-godkjent utvidelse av carve-out; kun stray metadata-linjer, aldri prosa. test-transform-criterion: precondition oppdatert til post-migrasjons-sannhet (fila bærer nå Source). Suite 728/728 grønn.
407 lines
14 KiB
Markdown
407 lines
14 KiB
Markdown
# Agent Latency Optimization and Performance Tuning
|
|
|
|
**Last updated:** 2026-02
|
|
**Status:** GA
|
|
**Category:** Agent Orchestration & Automation
|
|
**Type:** reference
|
|
|
|
---
|
|
|
|
## Innhold
|
|
|
|
- [Introduksjon](#introduksjon)
|
|
- [Kjernekomponenter](#kjernekomponenter)
|
|
- [Latency-anatomi for agentsystemer](#latency-anatomi-for-agentsystemer)
|
|
- [Response Streaming](#response-streaming)
|
|
- [Request Batching](#request-batching)
|
|
- [Prefetching Strategies](#prefetching-strategies)
|
|
- [Semantic Caching med APIM](#semantic-caching-med-apim)
|
|
- [Async-Awaitable Patterns](#async-awaitable-patterns)
|
|
- [Model Selection for Latency](#model-selection-for-latency)
|
|
- [Norsk offentlig sektor](#norsk-offentlig-sektor)
|
|
- [Beslutningsrammeverk](#beslutningsrammeverk)
|
|
- [For Cosmo](#for-cosmo)
|
|
|
|
## Introduksjon
|
|
|
|
Responstid er en kritisk kvalitetsfaktor for AI-agenter. Brukere forventer sub-sekund initial respons og fullstendige svar innen få sekunder. I multi-agent-systemer akkumuleres latency gjennom hver orkestreringsbeslutning, modellkall, verktøyinvokasjon og data-retrieval. Uten bevisst optimalisering kan en agent som involverer 3-4 LLM-kall raskt nå 15-30 sekunders total responstid, noe som er uakseptabelt for interaktive scenarier.
|
|
|
|
Azure OpenAI tilbyr flere mekanismer for latency-optimalisering: modellvalg (GPT-4o mini for lavest latency), streaming for opplevd rask respons, prompt-caching for gjentatte forespørsler, og batching for asynkrone workloads. I tillegg gir Azure API Management som AI Gateway mulighet for intelligent routing, semantic caching og request-deduplication.
|
|
|
|
For agentsystemer spesifikt er de største latency-driverne antall sekvensielle LLM-kall, størrelsen på kontekstvinduer, og ventetid på eksterne verktøy. Parallellisering av uavhengige operasjoner, prefetching av sannsynlige data-behov, og async-patterns for verktøybruk er de mest effektive optimaliseringene.
|
|
|
|
## Kjernekomponenter
|
|
|
|
| Komponent | Formål | Teknologi |
|
|
|-----------|--------|-----------|
|
|
| Streaming | Reduser opplevd latency | Azure OpenAI streaming, SSE |
|
|
| Prompt Caching | Reduser time-to-first-token for gjentatte prefixer | Azure OpenAI prompt caching |
|
|
| Request Batching | Samle bulk-operasjoner | Azure OpenAI Batch API |
|
|
| Semantic Caching | Cache semantisk like forespørsler | APIM semantic caching policy |
|
|
| Model Selection | Velg riktig modell for oppgavens krav | GPT-4o mini, GPT-4o, Model Router |
|
|
| Async Patterns | Parallelliser uavhengige operasjoner | C# async/await, Python asyncio |
|
|
|
|
## Latency-anatomi for agentsystemer
|
|
|
|
### Typisk breakdown
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────┐
|
|
│ Total agent response: ~8-15 sekunder (uten opt.) │
|
|
│ │
|
|
│ Router LLM-kall: ~0.5-1.5s │
|
|
│ RAG retrieval: ~0.3-1.0s │
|
|
│ Specialist LLM-kall: ~2-5s │
|
|
│ Tool invocation (API): ~0.5-3s │
|
|
│ Response formatting: ~0.5-1.5s │
|
|
│ Content filtering: ~0.1-0.3s │
|
|
│ │
|
|
│ Etter optimalisering: ~2-5 sekunder │
|
|
└─────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### Latency-metrikker
|
|
|
|
| Metrikk | Beskrivelse | Måling |
|
|
|---------|------------|--------|
|
|
| Time to First Token (TTFT) | Tid til første token i streamed respons | Azure Monitor, streaming |
|
|
| End-to-End Request Time | Total tid for komplett respons | API Gateway metrics |
|
|
| Token Generation Rate | Tokens per sekund under generering | Calculated metric |
|
|
| Tool Call Latency | Tid brukt på verktøyinvokasjoner | Custom spans |
|
|
| Orchestration Overhead | Tid brukt i routing/orkestrering | Custom spans |
|
|
|
|
## Response Streaming
|
|
|
|
### Implementering med Semantic Kernel
|
|
|
|
```csharp
|
|
// Streaming reduserer opplevd latency dramatisk
|
|
public async IAsyncEnumerable<string> StreamAgentResponse(
|
|
ChatCompletionAgent agent,
|
|
string userMessage,
|
|
AgentThread thread)
|
|
{
|
|
var message = new ChatMessageContent(
|
|
AuthorRole.User, userMessage);
|
|
|
|
await foreach (var chunk in
|
|
agent.InvokeStreamingAsync(message, thread))
|
|
{
|
|
if (!string.IsNullOrEmpty(chunk.Content))
|
|
{
|
|
yield return chunk.Content;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Bruk med SignalR for web-klienter
|
|
public class AgentHub : Hub
|
|
{
|
|
public async Task SendMessage(string query)
|
|
{
|
|
await foreach (var token in
|
|
_orchestrator.StreamAgentResponse(query))
|
|
{
|
|
await Clients.Caller.SendAsync("ReceiveToken", token);
|
|
}
|
|
await Clients.Caller.SendAsync("ResponseComplete");
|
|
}
|
|
}
|
|
```
|
|
|
|
### Streaming med Azure OpenAI direkte
|
|
|
|
```python
|
|
import asyncio
|
|
from openai import AsyncAzureOpenAI
|
|
|
|
client = AsyncAzureOpenAI(
|
|
azure_endpoint=os.environ["AZURE_OPENAI_ENDPOINT"],
|
|
api_key=os.environ["AZURE_OPENAI_KEY"],
|
|
api_version="2024-12-01-preview"
|
|
)
|
|
|
|
async def stream_response(messages: list):
|
|
stream = await client.chat.completions.create(
|
|
model="gpt-4o",
|
|
messages=messages,
|
|
stream=True,
|
|
# Optimaliseringer
|
|
max_tokens=500, # Begrens generering
|
|
temperature=0.3, # Lavere = raskere konvergens
|
|
)
|
|
|
|
async for chunk in stream:
|
|
if chunk.choices[0].delta.content:
|
|
yield chunk.choices[0].delta.content
|
|
```
|
|
|
|
## Request Batching
|
|
|
|
### Azure OpenAI Batch API
|
|
|
|
For ikke-interaktive workloads tilbyr Batch API 50% kostnadsreduksjon og høyere throughput:
|
|
|
|
```python
|
|
# Batch API for bulk-operasjoner (24-timers SLA)
|
|
import json
|
|
from openai import AzureOpenAI
|
|
|
|
client = AzureOpenAI(
|
|
azure_endpoint=os.environ["AZURE_OPENAI_ENDPOINT"],
|
|
api_key=os.environ["AZURE_OPENAI_KEY"],
|
|
api_version="2024-12-01-preview"
|
|
)
|
|
|
|
# Opprett batch-fil med JSONL
|
|
batch_requests = []
|
|
for i, query in enumerate(evaluation_queries):
|
|
batch_requests.append({
|
|
"custom_id": f"eval-{i}",
|
|
"method": "POST",
|
|
"url": "/chat/completions",
|
|
"body": {
|
|
"model": "gpt-4o-mini",
|
|
"messages": [
|
|
{"role": "system", "content": "Evaluer følgende..."},
|
|
{"role": "user", "content": query}
|
|
],
|
|
"max_tokens": 200
|
|
}
|
|
})
|
|
|
|
# Skriv JSONL-fil
|
|
with open("batch_input.jsonl", "w") as f:
|
|
for req in batch_requests:
|
|
f.write(json.dumps(req) + "\n")
|
|
|
|
# Last opp og start batch
|
|
file = client.files.create(
|
|
file=open("batch_input.jsonl", "rb"),
|
|
purpose="batch"
|
|
)
|
|
batch = client.batches.create(
|
|
input_file_id=file.id,
|
|
endpoint="/chat/completions",
|
|
completion_window="24h"
|
|
)
|
|
```
|
|
|
|
## Prefetching Strategies
|
|
|
|
### Proaktiv data-henting
|
|
|
|
```python
|
|
import asyncio
|
|
|
|
class PrefetchingOrchestrator:
|
|
"""Parallelliser data-henting med LLM-klassifisering"""
|
|
|
|
async def process_query(self, query: str) -> str:
|
|
# Start routing og data-henting PARALLELT
|
|
routing_task = asyncio.create_task(
|
|
self.classify_intent(query)
|
|
)
|
|
# Prefetch de mest sannsynlige datakildene
|
|
common_data_task = asyncio.create_task(
|
|
self.fetch_common_context(query)
|
|
)
|
|
|
|
# Vent på routing-resultat
|
|
routing = await routing_task
|
|
common_data = await common_data_task
|
|
|
|
# Hent spesialisert data basert på routing
|
|
specialized_data = await self.fetch_specialized_data(
|
|
routing.target_agent, query
|
|
)
|
|
|
|
# Kombiner kontekst og generer svar
|
|
context = {**common_data, **specialized_data}
|
|
return await self.generate_response(
|
|
routing.target_agent, query, context
|
|
)
|
|
|
|
async def fetch_common_context(self, query: str) -> dict:
|
|
"""Hent data som sannsynligvis trengs uansett agent"""
|
|
user_profile, recent_history = await asyncio.gather(
|
|
self.get_user_profile(),
|
|
self.get_recent_interactions(limit=3)
|
|
)
|
|
return {
|
|
"user_profile": user_profile,
|
|
"recent_history": recent_history
|
|
}
|
|
```
|
|
|
|
## Semantic Caching med APIM
|
|
|
|
### APIM Semantic Cache Policy
|
|
|
|
```xml
|
|
<!-- Azure API Management semantic caching for AI-forespørsler -->
|
|
<policies>
|
|
<inbound>
|
|
<!-- Sjekk semantic cache før videresending -->
|
|
<azure-openai-semantic-cache-lookup
|
|
score-threshold="0.90"
|
|
embeddings-backend-id="embedding-backend"
|
|
embeddings-backend-auth="system-assigned" />
|
|
</inbound>
|
|
<outbound>
|
|
<!-- Lagre respons i cache for fremtidige like forespørsler -->
|
|
<azure-openai-semantic-cache-store
|
|
duration="3600" />
|
|
</outbound>
|
|
</policies>
|
|
```
|
|
|
|
### Cache-invalidering
|
|
|
|
```python
|
|
class AgentCacheManager:
|
|
"""Håndter cache-invalidering for agent-systemer"""
|
|
|
|
def __init__(self, redis_client):
|
|
self.redis = redis_client
|
|
|
|
async def cache_response(
|
|
self, query_embedding: list, response: str, ttl: int = 3600
|
|
):
|
|
key = self._embedding_to_key(query_embedding)
|
|
await self.redis.setex(key, ttl, response)
|
|
|
|
async def invalidate_on_knowledge_update(
|
|
self, updated_sources: list
|
|
):
|
|
"""Når kunnskapsbase oppdateres, invalider relaterte cacher"""
|
|
# Fjern alle cache-entries som refererer til oppdaterte kilder
|
|
for source in updated_sources:
|
|
pattern = f"agent_cache:*:{source}:*"
|
|
keys = await self.redis.keys(pattern)
|
|
if keys:
|
|
await self.redis.delete(*keys)
|
|
|
|
async def invalidate_on_model_change(self):
|
|
"""Ved modellbytte, flush hele cachen"""
|
|
await self.redis.flushdb()
|
|
```
|
|
|
|
## Async-Awaitable Patterns
|
|
|
|
### Parallell verktøyinvokasjon
|
|
|
|
```csharp
|
|
// Parallelliser uavhengige tool calls
|
|
public class OptimizedAgentToolHandler
|
|
{
|
|
public async Task<ToolResults> ExecuteToolsParallel(
|
|
IEnumerable<ToolCall> toolCalls)
|
|
{
|
|
// Grupper verktøykall etter avhengigheter
|
|
var independentCalls = toolCalls
|
|
.Where(t => !t.HasDependencies)
|
|
.ToList();
|
|
var dependentCalls = toolCalls
|
|
.Where(t => t.HasDependencies)
|
|
.ToList();
|
|
|
|
// Kjør uavhengige kall parallelt
|
|
var parallelResults = await Task.WhenAll(
|
|
independentCalls.Select(tool =>
|
|
ExecuteToolWithTimeout(tool, TimeSpan.FromSeconds(5))
|
|
)
|
|
);
|
|
|
|
// Kjør avhengige kall sekvensielt
|
|
var sequentialResults = new List<ToolResult>();
|
|
foreach (var tool in dependentCalls)
|
|
{
|
|
var result = await ExecuteToolWithTimeout(
|
|
tool, TimeSpan.FromSeconds(5));
|
|
sequentialResults.Add(result);
|
|
}
|
|
|
|
return new ToolResults(parallelResults, sequentialResults);
|
|
}
|
|
|
|
private async Task<ToolResult> ExecuteToolWithTimeout(
|
|
ToolCall tool, TimeSpan timeout)
|
|
{
|
|
using var cts = new CancellationTokenSource(timeout);
|
|
try
|
|
{
|
|
return await tool.ExecuteAsync(cts.Token);
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
return ToolResult.Timeout(tool.Name);
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## Model Selection for Latency
|
|
|
|
### Modell-latency sammenligning
|
|
|
|
| Modell | TTFT (median) | Tokens/sek | Anbefalt bruk |
|
|
|--------|---------------|------------|----------------|
|
|
| gpt-4o-mini | ~200ms | ~120 | Routing, klassifisering, enkle svar |
|
|
| gpt-4o | ~400ms | ~80 | Komplekse resonneringer, RAG |
|
|
| gpt-4.1 | ~350ms | ~90 | Generell agent-bruk |
|
|
| gpt-4.1-mini | ~180ms | ~130 | Høyvolum, lav-latency |
|
|
| gpt-4.1-nano | ~100ms | ~150 | Ultra-lav latency, enkel klassifisering |
|
|
|
|
### Tiered Model Strategy
|
|
|
|
```python
|
|
# Velg modell basert på oppgavens kompleksitet
|
|
MODEL_TIERS = {
|
|
"routing": "gpt-4.1-nano", # Ultra-rask routing
|
|
"simple_qa": "gpt-4o-mini", # Enkle spørsmål
|
|
"rag_synthesis": "gpt-4o", # RAG med resonnering
|
|
"complex_analysis": "gpt-4.1", # Dype analyser
|
|
"evaluation": "gpt-4o-mini", # Batch-evaluering
|
|
}
|
|
|
|
async def select_model_for_task(task_type: str, context: dict) -> str:
|
|
base_model = MODEL_TIERS.get(task_type, "gpt-4o-mini")
|
|
|
|
# Override basert på kontekst
|
|
if context.get("token_count", 0) > 4000:
|
|
# Store kontekster trenger kraftigere modell
|
|
return "gpt-4o"
|
|
if context.get("requires_reasoning", False):
|
|
return "gpt-4.1"
|
|
|
|
return base_model
|
|
```
|
|
|
|
## Norsk offentlig sektor
|
|
|
|
| Aspekt | Krav | Latency-implikasjon |
|
|
|--------|------|---------------------|
|
|
| Data residency | Azure Norway East | +20-50ms vs. West Europe |
|
|
| Content filtering | Obligatorisk for offentlig sektor | +100-200ms per request |
|
|
| Audit logging | Full logging av alle kall | +10-30ms overhead |
|
|
| VNet isolation | Private endpoints | +5-15ms for DNS resolution |
|
|
| Token-grenser | Budget-begrensninger | Bruk mindre modeller der mulig |
|
|
|
|
## Beslutningsrammeverk
|
|
|
|
| Scenario | Anbefaling | Begrunnelse |
|
|
|----------|------------|-------------|
|
|
| Chat-bot med < 2s krav | Streaming + gpt-4o-mini + semantic cache | Lavest opplevd latency |
|
|
| Multi-agent med 3+ steg | Parallelliser uavhengige steg + prefetching | Reduserer sekvensielle ventetider |
|
|
| Høy-volum asynkront | Batch API + gpt-4o-mini | 50% kostnadsreduksjon, 24h SLA |
|
|
| RAG med store dokumenter | Prompt caching + chunk-optimalisering | Reduser TTFT for store prompts |
|
|
| Global distribusjon | APIM multi-region + Front Door | Nearest-region routing |
|
|
|
|
## For Cosmo
|
|
|
|
- **Streaming er alltid-på for interaktive agenter** -- det er den enkelttiltaket som mest forbedrer brukeropplevelsen, selv om total tid forblir lik.
|
|
- **Model tiering er obligatorisk** for kostnadseffektiv latency-optimalisering -- bruk nano/mini for routing og klassifisering, gpt-4o for kompleks resonnering.
|
|
- **Parallelliser aggressivt**: Prefetch data mens routing pågår, kjør uavhengige verktøykall parallelt, og bruk async/await konsekvent.
|
|
- **Semantic caching i APIM** gir dramatisk forbedring for repetitive forespørsler -- spesielt i kundestøtte-scenarier der mange brukere stiller lignende spørsmål.
|
|
- **Mål alltid TTFT og total latency separat** -- TTFT driver brukeropplevelse, total latency driver kostnad. Optimaliser begge men prioriter TTFT for interaktive scenarier.
|