LLMAPI: API-kontexter: Upprätthållande av överensstämmelse över Mock API-anrop
OBS! Artikeln är främst AI genererad som en del av mitt nugetpaket som utgivningsdokumentation.
Det är ganska intressant så jag la det här men om det är ett problem för dig, var snäll och ignorera det.
Inledning
**När man bygger och testar applikationer är en av de största utmaningarna med traditionella falska API:er deras statslösa natur.**Varje begäran returnerar helt slumpmässiga data utan samband med tidigare samtal.
Lös detta problem genom att ge ditt falska API ett minne.HämtaHämta
Du kan hitta
GitHub här
sequenceDiagram
participant Client
participant MockAPI
participant LLM
Client->>MockAPI: GET /users/1
MockAPI->>LLM: Generate user data
LLM-->>MockAPI: {"id": 42, "name": "Alice"}
MockAPI-->>Client: User data
Client->>MockAPI: GET /orders?userId=42
MockAPI->>LLM: Generate order data
LLM-->>MockAPI: {"userId": 99, ...}
MockAPI-->>Client: Order data (userId mismatch!)
för projektet, alla offentliga domäner etc...
Problemet: Kaos utan tillstånd
Traditionella prototypa API:er genererar data oberoende för varje begäran:
sequenceDiagram
participant Client
participant MockAPI
participant Context as Context Manager
participant LLM
Client->>MockAPI: GET /users/1?context=session-1
MockAPI->>Context: Get history for "session-1"
Context-->>MockAPI: (empty - first call)
MockAPI->>LLM: Generate user (no context)
LLM-->>MockAPI: {"id": 42, "name": "Alice"}
MockAPI->>Context: Store: GET /users/1 → {"id": 42, ...}
MockAPI-->>Client: User data
Client->>MockAPI: GET /orders?context=session-1
MockAPI->>Context: Get history for "session-1"
Context-->>MockAPI: Previous call: user with id=42, name=Alice
MockAPI->>LLM: Generate order (with context history)
LLM-->>MockAPI: {"userId": 42, "customerName": "Alice", ...}
MockAPI->>Context: Store: GET /orders → {"userId": 42, ...}
MockAPI-->>Client: Order data (consistent!)
Märker du problemet?
Användaren hade ID 42, men beställningen kom tillbaka med userId 99.
Det finns ingen överensstämmelse mellan relaterade samtal.
Lösningen: Sammanhangsminne
graph TD
A[HTTP Request] --> B[ContextExtractor]
B --> C{Context Name?}
C -->|Yes| D[OpenApiContextManager]
C -->|No| E[Generate without context]
D --> F[Retrieve Context History]
F --> G[PromptBuilder]
E --> G
G --> H[LLM]
H --> I[Response]
I --> J{Context Name?}
J -->|Yes| K[Store in Context]
J -->|No| L[Return Response]
K --> L
**Med API Contexts upprätthåller prototyp API ett delat sammanhang över relaterade förfrågningar:**Nu ser LLM det tidigare användarsamtalet och genererar order som refererar till samma användar-ID och namn. **Uppgifterna bildar en sammanhängande historia.**Hur den fungerar ArkitekturSammanhangssystemet består av tre huvudkomponenter:
1. Vad är det som händer?
SammanhangsextractorConcurrentDictionary:
public class OpenApiContextManager
{
private readonly ConcurrentDictionary<string, ApiContext> _contexts;
private const int MaxRecentCalls = 15;
private const int SummarizeThreshold = 20;
public void AddToContext(
string contextName,
string method,
string path,
string? requestBody,
string responseBody)
{
var context = _contexts.GetOrAdd(contextName, _ => new ApiContext
{
Name = contextName,
CreatedAt = DateTimeOffset.UtcNow,
RecentCalls = new List<RequestSummary>(),
SharedData = new Dictionary<string, string>(),
TotalCalls = 0
});
context.RecentCalls.Add(new RequestSummary
{
Timestamp = DateTimeOffset.UtcNow,
Method = method,
Path = path,
RequestBody = requestBody,
ResponseBody = responseBody
});
ExtractSharedData(context, responseBody);
if (context.RecentCalls.Count > MaxRecentCalls)
{
SummarizeOldCalls(context);
}
}
}
- Extraherar kontextnamnet från begäran
Två.
graph LR
A[20+ Calls] --> B[Keep 15 Most Recent]
A --> C[Summarize Older Calls]
B --> D[Full Request/Response]
C --> E[Summary: 'GET /users - called 5 times']
D --> F[Included in LLM Prompt]
E --> F
private void SummarizeOldCalls(ApiContext context)
{
var toSummarize = context.RecentCalls
.Take(context.RecentCalls.Count - MaxRecentCalls)
.ToList();
var summary = new StringBuilder();
summary.AppendLine($"Earlier calls ({toSummarize.Count}):");
var groupedByPath = toSummarize
.GroupBy(c => $"{c.Method} {c.Path.Split('?')[0]}");
foreach (var group in groupedByPath)
{
summary.AppendLine($" {group.Key} - called {group.Count()} time(s)");
}
context.ContextSummary = summary.ToString();
context.RecentCalls.RemoveRange(0, toSummarize.Count);
}
OpenApiContextManager
- Hanterar sammanhangslagring och hämtning
private void ExtractSharedData(ApiContext context, string responseBody)
{
using var doc = JsonDocument.Parse(responseBody);
var root = doc.RootElement;
if (root.ValueKind == JsonValueKind.Array && root.GetArrayLength() > 0)
{
var firstItem = root[0];
ExtractValueIfExists(context, firstItem, "id", "lastId");
ExtractValueIfExists(context, firstItem, "userId", "lastUserId");
ExtractValueIfExists(context, firstItem, "name", "lastName");
ExtractValueIfExists(context, firstItem, "email", "lastEmail");
}
else if (root.ValueKind == JsonValueKind.Object)
{
ExtractValueIfExists(context, root, "id", "lastId");
ExtractValueIfExists(context, root, "userId", "lastUserId");
ExtractValueIfExists(context, root, "name", "lastName");
// ... more common patterns
}
}
- Vad är det som händer?
Snabbt bygge
- Inkluderar sammanhangshistorik i LLM-meddelanden
Sammanhangslagring
Sammanhang lagras i minnet med hjälp av en trådsäkerAutomatisk sammanfattning
GET /api/mock/users?context=my-session
GET /api/mock/users?api-context=my-session
För att förhindra att sammanhang växer på obestämd tid och överskrider LLM token gränser, sammanfattar systemet automatiskt gamla samtal när antalet överstiger 15:
GET /api/mock/users
X-Api-Context: my-session
Utdrag av delade data
POST /api/mock/orders
Content-Type: application/json
{
"context": "my-session",
"shape": {"orderId": 0, "userId": 0}
}
Sammanhangshanteraren extraherar automatiskt vanliga identifierare från svaren för att göra dem lättillgängliga:
Detta gör det möjligt för systemet att spåra den senaste användar-ID, order-ID, etc., vilket gör dem tillgängliga i sammanhangshistoriken.Använda sammanhangTre sätt att ange sammanhang
Du kan skicka det sammanhangsnamn på tre olika sätt, med denna prioritetsordning:
GET /api/mock/users/123?context=session-1
1. Vad är det som händer?
GET /api/mock/stream/stock-prices?context=trading-session
Accept: text/event-stream
Förfrågans parameter
POST /graphql?context=my-app
Content-Type: application/json
{
"query": "{ users { id name } }"
}
(högst prioritet)
{
"mostlylucid.mockllmapi": {
"HubContexts": [
{
"Name": "stock-ticker",
"Description": "Real-time stock prices",
"ApiContextName": "stocks-session",
"Shape": "{\"symbol\":\"string\",\"price\":0}"
}
]
}
}
Två.
HTTP- huvud
- Vad är det som händer?
### 1. Create user
POST /api/mock/users?context=checkout-flow
{
"shape": {
"userId": 0,
"name": "string",
"email": "string",
"address": {"street": "string", "city": "string"}
}
}
### Response: {"userId": 42, "name": "Alice", ...}
### 2. Create cart (will reference same user)
POST /api/mock/cart?context=checkout-flow
{
"shape": {
"cartId": 0,
"userId": 0,
"items": [{"productId": 0, "quantity": 0}]
}
}
### Response: {"cartId": 123, "userId": 42, ...}
### 3. Create order (consistent user and cart)
POST /api/mock/orders?context=checkout-flow
{
"shape": {
"orderId": 0,
"userId": 0,
"cartId": 0,
"total": 0
}
}
### Response: {"orderId": 789, "userId": 42, "cartId": 123, ...}
Begärande organ
Stödda slutpunktstyper
### First call - establishes baseline
GET /api/mock/stocks?context=market-data
&shape={"symbol":"string","price":0,"volume":0}
### Response: {"symbol": "ACME", "price": 145.50, "volume": 10000}
### Second call - price changes realistically
GET /api/mock/stocks?context=market-data
&shape={"symbol":"string","price":0,"volume":0}
### Response: {"symbol": "ACME", "price": 146.20, "volume": 12000}
### Notice: Same symbol, price increased by $0.70 (realistic)
### Third call - continues the trend
GET /api/mock/stocks?context=market-data
&shape={"symbol":"string","price":0,"volume":0}
### Response: {"symbol": "ACME", "price": 145.80, "volume": 11500}
### Notice: Price fluctuates but stays in realistic range
Sammanhangen fungerar över hela världen
alla
Typer av effektmått:
### Start game
POST /api/mock/game/start?context=game-session-123
{
"shape": {
"playerId": 0,
"level": 0,
"health": 0,
"score": 0,
"inventory": []
}
}
### Response: {"playerId": 42, "level": 1, "health": 100, "score": 0}
### Complete quest
POST /api/mock/game/quest?context=game-session-123
{
"shape": {
"playerId": 0,
"level": 0,
"score": 0,
"reward": {"item": "string", "value": 0}
}
}
### Response: {"playerId": 42, "level": 2, "score": 500,
### "reward": {"item": "Sword", "value": 100}}
### Notice: Same player, level increased, score increased
### Get stats
GET /api/mock/game/player?context=game-session-123
&shape={"playerId":0,"level":0,"health":0,"score":0}
### Response: {"playerId": 42, "level": 2, "health": 100, "score": 500}
### Notice: Consistent with quest completion
REST API: er
Strömmande API: er
GET /api/openapi/contexts
### Response:
{
"contexts": [
{
"name": "session-1",
"totalCalls": 5,
"recentCallCount": 5,
"sharedDataCount": 3,
"createdAt": "2025-01-15T10:00:00Z",
"lastUsedAt": "2025-01-15T10:05:00Z",
"hasSummary": false
}
],
"count": 1
}
DiagramQL
GET /api/openapi/contexts/session-1
### Response shows full context including:
### - All recent calls with timestamps
### - Extracted shared data (IDs, names, emails)
### - Summary of older calls (if any)
SignalR (via konfiguration)
DELETE /api/openapi/contexts/session-1
Real-World användningsfall
DELETE /api/openapi/contexts
Användningsfall 1: E-handelsflöde
Simulera en komplett shoppingupplevelse med konsekventa användar- och orderdata:
Användningsfall 2: Lagerprissimulering
public async Task<string> HandleRequestAsync(
string method,
string fullPathWithQuery,
string? body,
HttpRequest request,
HttpContext context,
CancellationToken cancellationToken = default)
{
// 1. Extract context name from request
var contextName = _contextExtractor.ExtractContextName(request, body);
// 2. Get context history if context specified
var contextHistory = !string.IsNullOrWhiteSpace(contextName)
? _contextManager.GetContextForPrompt(contextName)
: null;
// 3. Build prompt with context history
var prompt = _promptBuilder.BuildPrompt(
method, fullPathWithQuery, body, shapeInfo,
streaming: false, contextHistory: contextHistory);
// 4. Get response from LLM
var response = await _llmClient.GetCompletionAsync(prompt, cancellationToken);
// 5. Store in context if context name provided
if (!string.IsNullOrWhiteSpace(contextName))
{
_contextManager.AddToContext(
contextName, method, fullPathWithQuery, body, response);
}
return response;
}
Generera realistiska aktiekursrörelser istället för slumpmässiga värden:
Utan sammanhang skulle varje samtal returnera en helt slumpmässig symbol och pris.
TASK: Generate a varied mock API response.
RULES: Output ONLY valid JSON. No markdown, no comments.
API Context: session-1
Total calls in session: 3
Shared data to maintain consistency:
lastId: 42
lastName: Alice
lastEmail: [email protected]
Recent API calls:
[10:00:05] GET /users/42
Response: {"id": 42, "name": "Alice", "email": "[email protected]"}
[10:00:12] GET /orders?userId=42
Response: {"orderId": 123, "userId": 42, "items": [...]}
Generate a response that maintains consistency with the above context.
Method: POST
Path: /shipping/123
Body: {"orderId": 123}
Mot bakgrund av detta upprätthåller LLM samma aktie och justerar priserna realistiskt.
Användningsfall 3: Spelstatsprogression
Spåra spelarens framsteg genom spelsessioner:
Bad: ?context=test1
Good: ?context=user-checkout-flow-jan15
API för sammanhangshantering
Lista alla sammanhang
### After completing your test scenario
DELETE /api/openapi/contexts/user-checkout-flow-jan15
Hämta bakgrundsinformation
Rensa ett specifikt sammanhang
GET /api/mock/users?context=demo-session
GET /api/mock/orders?context=demo-session
GET /api/mock/shipping?context=demo-session
Rensa alla sammanhang
Genomförandebestämmelser
GET /api/openapi/contexts/demo-session
Integrering i efterfrågehanterare
Varje begäran hanterare (REST, Streaming, GraphQL, SignalR) följer samma mönster:
Sammanhang i LLM Prompts
### Load spec with context
POST /api/openapi/specs
{
"name": "petstore",
"source": "https://petstore3.swagger.io/api/v3/openapi.json",
"basePath": "/petstore",
"contextName": "petstore-session"
}
### All petstore endpoints will share the same context
När det finns ett sammanhang ingår dess historia i LLM-prompten:
LLM ser alla tidigare ansökningsomgångar och genererar svar som refererar till samma ID, namn och andra data, med bibehållen överensstämmelse.
Bästa praxis
-
- Vad är det som händer?
- Använd beskrivande sammanhangsnamn
- Två.
Rensa sammanhang när de är klaraSammanhangen kvarstår i minnet tills servern uttryckligen rensas eller startas om:
3. Vad är det som händer?
OpenApiContextManagerDela sammanhang över relaterade slutpunkterConcurrentDictionaryAnvänd samma sammanhangsnamn för alla relaterade samtal:
Fyra.
Övervaka sammanhangsstorlek
Kontrollera sammanhangsdetaljer för att se hur många samtal som lagras:
- **Om du har många samtal (>100), överväga att rensa och börja om på nytt för att undvika snabba längdproblem.**För att uppnå detta mål har kommissionen beslutat att inleda ett förfarande enligt artikel 108.2 i fördraget om Europeiska unionens funktionssätt (nedan kallat EUF-fördraget).
- Kombinera med OpenAPI- specifikationerFör maximal realism, använd sammanhang med OpenAPI specs:
- PrestandaövervägandenMinnesanvändning
- **Varje sammanhang lagrar:**Upp till 15 nya samtal (fullständig begäran/svar)
Sammanfattning av äldre samtal (komprimerade)
Utdragna delade data (litet lexikon)
Typiskt minne per sammanhang
: ~50-200 KB beroende på responsstorlekar