Back to "LLMAPI: API-kontexter: Upprätthållande av överensstämmelse över Mock API-anrop"

This is a viewer only at the moment see the article on how this works.

To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk

This is a preview from the server running through my markdig pipeline

AI AI-Article API ASP.NET Core LLM LLMApi mockllmapi Nuget

LLMAPI: API-kontexter: Upprätthållande av överensstämmelse över Mock API-anrop

Wednesday, 05 November 2025

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.

Om du hämtar en användare med ID 123, sedan hämta sina beställningar, det finns ingen garanti för att ordern kommer att hänvisa till samma användar-ID. API- sammanhang

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
    }
}
  1. 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

  1. 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

    1. 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:

  1. **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).
  2. Kombinera med OpenAPI- specifikationerFör maximal realism, använd sammanhang med OpenAPI specs:
  3. PrestandaövervägandenMinnesanvändning
  4. **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

logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.