Back to "LLMApi: Contextes de l'API: Maintenir la cohérence entre les appels d'API Mock"

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: Contextes de l'API: Maintenir la cohérence entre les appels d'API Mock

Wednesday, 05 November 2025

NOTE: Son article est principalement généré par l'IA dans le cadre de mon paquet nuget comme documentation de libération.

C'est assez intéressant donc je l'ai mis ici mais si c'est un problème pour vous s'il vous plaît ignorez-le.

Présentation

**Lors de la construction et de la mise à l'essai d'applications, l'un des plus grands défis avec les API simulées traditionnelles est leur nature apatride.**Chaque requête retourne des données complètement aléatoires sans rapport avec les appels précédents.

Si vous récupérez un utilisateur avec ID 123, puis récupérer leurs commandes, il n'y a aucune garantie que la commande fera référence au même ID utilisateur. Contextes de l'API

résoudre ce problème en donnant à votre API simulée une mémoire.NuGetNuGet

Vous pouvez trouver le

GitHub ici

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

pour le projet, tout domaine public etc...

Le problème : le chaos apatride

Les API simulées traditionnelles génèrent des données indépendamment pour chaque demande :

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

Tu as remarqué le problème ?

L'utilisateur avait ID 42, mais la commande est revenue avec userId 99.

Il n'y a pas de cohérence entre les appels.

La solution : la mémoire contextuelle

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

**Avec les contextes d'API, l'API simulée maintient un contexte partagé entre les requêtes connexes :**Maintenant, le LLM voit l'appel d'utilisateur précédent et génère des commandes qui font référence au même identifiant d'utilisateur et au même nom. **Les données forment une histoire cohérente.**Comment ça marche ArchitectureLe système de contexte se compose de trois éléments principaux:

1. Le Conseil de l'Europe a adopté une résolution du Conseil de l'Europe sur la situation des droits de l'homme dans le monde.

ContexteExtracteurConcurrentDictionary:

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

- Extrait le nom du contexte de la requête

  1. Le Président. — L'ordre du jour appelle le rapport (doc.
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

  • Gestion du stockage et de la récupération du contexte
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. Les droits de l'homme sont garantis par le Pacte international relatif aux droits économiques, sociaux et culturels.

PromptBuilder

- Inclut l'historique du contexte dans les invites LLM

Stockage du contexte

Les contextes sont stockés en mémoire à l'aide d'un thread-safeRésumation automatique

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

Pour éviter que le contexte ne augmente indéfiniment et dépasse les limites des jetons LLM, le système résume automatiquement les appels anciens lorsque le nombre dépasse 15 :

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

Extraction de données partagées

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

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

Le gestionnaire de contexte extrait automatiquement les identifiants communs des réponses pour les rendre facilement accessibles:

Cela permet au système de suivre l'identifiant utilisateur le plus récent, l'identifiant de commande, etc., en les rendant disponibles dans l'historique du contexte.Utilisation des contextesTrois façons de préciser le contexte

Vous pouvez passer le nom du contexte de trois façons différentes, avec cet ordre de priorité :

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

1. Le Conseil de l'Europe a adopté une résolution du Conseil de l'Europe sur la situation des droits de l'homme dans le monde.

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

Paramètre de requête

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

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

(la priorité la plus élevée)

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

2. Le Président. — L'ordre du jour appelle le rapport (doc.

En-tête HTTP

  1. Les droits de l'homme sont garantis par le Pacte international relatif aux droits économiques, sociaux et culturels.
### 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, ...}

Organe de demande

Types d'extrémité pris en charge

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

Les contextes de travail à travers

tous

types de paramètres:

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

API REST

Rupture des API

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
}

GraphiqueQL

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 (par configuration)

DELETE /api/openapi/contexts/session-1

Cas d'utilisations dans le monde réel

DELETE /api/openapi/contexts

Cas d'utilisation 1 : Débit du commerce électronique

Simuler une expérience d'achat complète avec des données utilisateur et de commande cohérentes:

Cas d'utilisation 2: Simulation du prix des stocks

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

Générer des mouvements réalistes des cours des actions au lieu de valeurs aléatoires:

Sans contexte, chaque appel retournerait un symbole et un prix complètement aléatoires.

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}

Dans le contexte, la LLM maintient le même stock et ajuste les prix de façon réaliste.

Cas d'utilisation 3: Progression de l'état du jeu

Suivre les progrès des joueurs à travers les sessions de jeu:

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

API de gestion de contexte

Liste de tous les contextes

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

Obtenir les détails du contexte

Effacer un contexte spécifique

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

Effacer tous les contextes

Détails de la mise en œuvre

GET /api/openapi/contexts/demo-session

Intégration dans les gestionnaires de demandes

Chaque gestionnaire de requête (REST, Streaming, GraphQL, SignalR) suit le même schéma :

Contexte dans les appels d'offres LLM

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

Lorsqu'un contexte existe, son histoire est incluse dans l'invite LLM :

Le LLM voit tous les appels précédents et génère des réponses qui renvoient aux mêmes identifiants, noms et autres données, en maintenant la cohérence.

Meilleures pratiques

    1. Le Conseil de l'Europe a adopté une résolution du Conseil de l'Europe sur la situation des droits de l'homme dans le monde.
  • Utiliser les noms de contexte descriptif
    1. Le Président. — L'ordre du jour appelle le rapport (doc.

Effacer les contextes lorsqu'ils sont exécutésLes contextes persistent en mémoire jusqu'à ce que le serveur soit explicitement effacé ou redémarre :

3. Les droits de l'homme sont garantis par le Pacte international relatif aux droits économiques, sociaux et culturels.

OpenApiContextManagerPartager les contextes à travers les points d'arrivée connexesConcurrentDictionaryUtilisez le même nom de contexte pour tous les appels connexes :

4. Le Président. — L'ordre du jour appelle le rapport (doc.

Surveiller la taille du contexte

Vérifiez les détails du contexte pour voir combien d'appels sont stockés :

  1. **Si vous avez beaucoup d'appels (>100), envisagez d'ouvrir et de commencer frais pour éviter les problèmes de longueur rapide.**5.
  2. Combiner avec OpenAPI SpecsPour un réalisme maximum, utilisez les contextes avec les spécifications OpenAPI :
  3. Considérations de performanceUtilisation de la mémoire
  4. **Chaque contexte stocke :**Jusqu'à 15 appels récents (demande complète/réponse)

Résumé des appels plus anciens (comprimés)

Données partagées extraites (petit dictionnaire)

Mémoire typique par contexte

: ~50-200 KB selon la taille de la réponse

logo

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