LLMApi: API Contexts: Consistentie behouden over Mock API-oproepen (Nederlands (Dutch))

LLMApi: API Contexts: Consistentie behouden over Mock API-oproepen

Wednesday, 05 November 2025

//

12 minute read

OPMERKING: THis artikel is voornamelijk AI gegenereerd als onderdeel van mijn nuget pakket als release documentatie.

Het is best interessant dus ik heb het hier neergezet maar als dat een probleem voor je is, negeer het dan alsjeblieft.

Inleiding

**Bij het bouwen en testen van toepassingen, is een van de grootste uitdagingen met traditionele spot API's hun staatloze aard.**Elk verzoek geeft volledig willekeurige gegevens terug zonder relatie tot eerdere oproepen.

Als je een gebruiker met ID 123 ophaalt, haal dan hun bestellingen op, er is geen garantie dat de bestelling naar dezelfde gebruikers-ID verwijst. API-contexten

Los dit probleem op door je spot API een geheugen te geven.NuGetNuGet

U kunt de

GitHub hier

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!)

voor het project, alle publieke domein, enz...

Het probleem: Staatsloze Chaos

Traditionele spot-API's genereren gegevens onafhankelijk van elkaar voor elke aanvraag:

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!)

Zie je het probleem?

De gebruiker had ID 42, maar de bestelling kwam terug met userId 99.

Er is geen samenhang tussen gerelateerde gesprekken.

De oplossing: Contextueel geheugen

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

**Met API Contexts onderhoudt de spot API een gedeelde context over gerelateerde verzoeken:**Nu ziet de LLM de vorige gebruikersgesprekken en genereert orders die verwijzen naar dezelfde gebruikers-ID en -naam. **De gegevens vormen een samenhangend verhaal.**Hoe het werkt ArchitectuurHet contextsysteem bestaat uit drie hoofdcomponenten:

1.

ContextExtractorConcurrentDictionary:

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);
        }
    }
}

- Uittreksel van de contextnaam uit het verzoek

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

  • Beheert context opslag en ophalen
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
    }
}

PromptBuilder

- Inclusief contextgeschiedenis in LLM-prompts

Context-opslag

Contexten worden opgeslagen in-geheugen met behulp van een thread-safeAutomatische samenvatting

GET /api/mock/users?context=my-session
GET /api/mock/users?api-context=my-session

Om te voorkomen dat de context oneindig groeit en de LLM tokenlimieten overschrijdt, vat het systeem automatisch oude oproepen samen wanneer de telling groter is dan 15:

GET /api/mock/users
X-Api-Context: my-session

Gedeelde gegevenswinning

POST /api/mock/orders
Content-Type: application/json

{
  "context": "my-session",
  "shape": {"orderId": 0, "userId": 0}
}

De contextmanager haalt automatisch gemeenschappelijke identificaties uit antwoorden om ze gemakkelijk toegankelijk te maken:

Hierdoor kan het systeem de meest recente gebruikers-ID, order-ID, enz. bijhouden, waardoor ze beschikbaar zijn in de contextgeschiedenis.Contexts gebruikenDrie manieren om context op te geven

U kunt de contextnaam op drie verschillende manieren doorgeven, met deze rangorde:

GET /api/mock/users/123?context=session-1

1.

GET /api/mock/stream/stock-prices?context=trading-session
Accept: text/event-stream

Zoekparameter

POST /graphql?context=my-app
Content-Type: application/json

{
  "query": "{ users { id name } }"
}

(hoogste prioriteit)

{
  "mostlylucid.mockllmapi": {
    "HubContexts": [
      {
        "Name": "stock-ticker",
        "Description": "Real-time stock prices",
        "ApiContextName": "stocks-session",
        "Shape": "{\"symbol\":\"string\",\"price\":0}"
      }
    ]
  }
}

2.

HTTP-header

### 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, ...}

Verzoeksinstantie

Ondersteunde eindpunttypes

### 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

Contexten werken doorheen

alle

eindpunttypes:

### 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's

Streaming API's

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
}

GraphQL

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 configuratie)

DELETE /api/openapi/contexts/session-1

Real-World Use Cases

DELETE /api/openapi/contexts

Use Case 1: E-Commerce Flow

Simuleer een complete winkelervaring met consistente gebruikers- en bestelgegevens:

Use Case 2: Stock Price Simulation

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;
}

Creëer realistische koersbewegingen in plaats van willekeurige waarden:

Zonder context zou elke oproep een volledig willekeurig symbool en prijs teruggeven.

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}

De LLM behoudt in dit verband dezelfde voorraad en past de prijzen realistisch aan.

Use Case 3: Game State Progression

Track player voortgang door middel van game sessies:

 Bad:  ?context=test1
 Good: ?context=user-checkout-flow-jan15

Contextbeheer API

Alle contexten tonen

### After completing your test scenario
DELETE /api/openapi/contexts/user-checkout-flow-jan15

Contextdetails ophalen

Een specifieke context wissen

GET /api/mock/users?context=demo-session
GET /api/mock/orders?context=demo-session
GET /api/mock/shipping?context=demo-session

Alle contexten wissen

Uitvoeringsdetails

GET /api/openapi/contexts/demo-session

Integratie in verzoekafhandelaars

Elke aanvraag handler (REST, Streaming, GraphQL, SignalR) volgt hetzelfde patroon:

Context in 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

Wanneer er een context bestaat, wordt de geschiedenis ervan opgenomen in de LLM prompt:

De LLM ziet alle eerdere oproepen en genereert reacties die verwijzen naar dezelfde ID's, namen en andere gegevens, het handhaven van consistentie.

Beste praktijken

  • Descriptieve contextnamen gebruiken

Contexts wissen wanneer voltooidContexts blijven bestaan in het geheugen totdat de server expliciet is opgeruimd of herstart:

3.

OpenApiContextManagerContext delen over gerelateerde eindpuntenConcurrentDictionaryGebruik dezelfde contextnaam voor alle gerelateerde oproepen:

4.

Beeldschermcontextgrootte

Controleer context details om te zien hoeveel gesprekken worden opgeslagen:

  1. **Als je veel telefoontjes hebt (>100), overweeg dan opruimen en opnieuw beginnen om prompte lengte problemen te voorkomen.**5.
  2. Combineer met OpenAPI SpecificatiesVoor maximale realisme, gebruik contexten met OpenAPI specs:
  3. PrestatieoverwegingenGeheugengebruik
  4. **Elke context slaat op:**Tot 15 recente oproepen (volledig verzoek/antwoord)

Samenvatting van oudere gesprekken (gecomprimeerd)

Uitgelezen gedeelde gegevens (klein woordenboek)

Typisch geheugen per context

: ~50-200 KB afhankelijk van de responsgrootte

Finding related posts...
logo

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