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
LLMApi: Contexte de l'API: Maintenir la cohérence entre les appels d'API Mock
Wednesday, 05 November 2025
NOTE: Cet article est principalement AI généré dans le cadre de mon paquet nuget en tant que documentation de diffusion.
Introduction
Lorsque vous construisez et testez des applications, l'une des plus grandes difficultés avec les API-moquettes traditionnelles est leur nature instaurée. Chaque demande retourne des données complètement aléatoires sans rapport aux appels antérieursM SK2 Si vous reçois un utilisateur ayant une ID 123, alors reçoivez leurs commandesMska4 làM Ska5 il n'y a aucune garantie que la commande fera référence à cette même ID d'utilisateurMSKA6
Contextes de l'API Résoudre ce problème en donnant à votre API modèle une mémoire.
Vous pouvez trouver le GitHub ici pour le projet, tous les domaines publics, etc.
Le problème: Chaos sans état
Les API modèles traditionnels génèrent des données indépendamment de chaque demande:
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!)
Remarquez le problème? L'utilisateur avait une ID 42, mais l'ordonnance est retournée avec la userId 99. Il n'y a pas de cohérence entre les appels connexesM SK4
La solution: Memory contextuelle
Avec Contextes API,, l’API-moquette maintient un contexte partagé à travers les demandes connexes:
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!)
Maintenant, le LLM voit l’appel de l’utilisateur précédent et génère des commandes qui renvoient à la même ID et au même nom d’utilisateur.
Comment cela fonctionne
Architecture
Le système de contexte comprend trois composantes principales:
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
1. ContextExtractor - Extrait le nom du contexte de la demande 2. OpenApiContextManager - Génie l'entreposage et la récupération du contexte 3. PromptBuilder - Comprend l'historique du contexte dans les prompts LLM
Entreposage du contexte
Les contextes sont stockés dans la mémoire -à l'aide d'un thread -safe ConcurrentDictionary:
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);
}
}
}
Sommaire automatique
Pour empêcher le contexte de croître indéfiniment et d'excéder les limites des tokens LLM,, le système résume automatiquement les anciens appels lorsque le nombre dépasse M SK1
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);
}
Extraction de données partagées
Le gestionnaire de contexte extrait automatiquement des identifiants communs des réponses pour les rendre facilement accessibles:
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
}
}
Cela permet au système de suivre l’ID d’utilisateur le plus récent, ID d’ordonnance, etc.
Utilisation de contextes
Trois façons de spécifier le contexte
Vous pouvez transmettre le nom du contexte de trois façons différentes, avec cette ordonnance de priorité:
Paramètre de la demande 1. (priorité la plus élevéeM SK1
GET /api/mock/users?context=my-session
GET /api/mock/users?api-context=my-session
2. Intitulé HTTP
GET /api/mock/users
X-Api-Context: my-session
3. Organe de demande
POST /api/mock/orders
Content-Type: application/json
{
"context": "my-session",
"shape": {"orderId": 0, "userId": 0}
}
Types de point final appuyés
Contextes fonctionnent à travers tous types de point final:
REST APIs
GET /api/mock/users/123?context=session-1
Appareils d'aide à la diffusion
GET /api/mock/stream/stock-prices?context=trading-session
Accept: text/event-stream
GraphQL
POST /graphql?context=my-app
Content-Type: application/json
{
"query": "{ users { id name } }"
}
SignalR (par la configuration)
{
"mostlylucid.mockllmapi": {
"HubContexts": [
{
"Name": "stock-ticker",
"Description": "Real-time stock prices",
"ApiContextName": "stocks-session",
"Shape": "{\"symbol\":\"string\",\"price\":0}"
}
]
}
}
Cases d'utilisation mondiale Real-
Case d'utilisation 1: E-Commerce Flow
Simulation d’une expérience complète de l’achat avec des données uniformes sur les utilisateurs et les commandes:
### 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, ...}
Utiliser le cas 2: Simulation du prix des stocks
Gérer des mouvements réalistes du prix des actions au lieu de valeurs aléatoires:
### 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
Sans contexte, chaque appel retournerait un symbole et un prix complètement aléatoires. Avec le contexte , la LLM maintient le même stock et ajuste les prix de façon réalisteM SK3
Utiliser le cas 3: Progression de l'état du jeu
Suivi des progrès du joueur dans les sessions de jeu:
### 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
Context Management API
Liste de tous les contextes
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
}
Obtenir les détails du contexte
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)
Supprimer un contexte spécifique
DELETE /api/openapi/contexts/session-1
Supprimer tous les contextes
DELETE /api/openapi/contexts
Détails de mise en œuvre
Intégration dans les gestionnaires de demandes
Chacun des gestionnaires de demande (REST, StreamingM SK2 GraphQL , SignalRMSC4 suit le même schéma
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;
}
Contexte dans les prompts LLM
Lorsqu'un contexte existe, son histoire est incluse dans le prompt LLM:
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}
Le LLM voit tous les appels antérieurs et génère des réponses qui renvoient aux mêmes ID, nomsM SK1 et autres données, en conservant la cohérence .
Pratiques exemplaires
1. Utiliser des noms de contexte descriptifs
Bad: ?context=test1
Good: ?context=user-checkout-flow-jan15
2. Supprimer les contextes lorsque cela est fait
Les contextes persistent dans la mémoire jusqu’à ce que le serveur soit explicitement nettoyé ou relancé:
### After completing your test scenario
DELETE /api/openapi/contexts/user-checkout-flow-jan15
3. Contextes partagés à travers les points finals connexes
Utiliser le même nom de contexte pour tous les appels connexes:
GET /api/mock/users?context=demo-session
GET /api/mock/orders?context=demo-session
GET /api/mock/shipping?context=demo-session
4. Taille du contexte de contrôle
Vérifiez les détails du contexte pour voir combien d'appels sont stockés:
GET /api/openapi/contexts/demo-session
Si vous avez beaucoup d’appels (>100), envisagez de débloquer et de commencer de nouveau pour éviter des problèmes de longueur immédiate
5. Combinez avec les spécifications OpenAPI
Pour un réalisme maximal, utiliser des contextes avec les spécifications OpenAPI:
### 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
Considérations relatives au rendement
Utilisation de la mémoire
Chaque contexte stocke:
- Jusqu’à 15 appels récents (requête complèteM SK2réponse)
- Résumé des appels anciens (compressed)
- Extrait de données partagées (small dictionary)
Mémoire type par contexte: ~50-200 Kb selon les tailles de la réponse
Sécurité des fils
OpenApiContextManager utilisations ConcurrentDictionary pour le thread-entreposage sécuritaire. Plusieurs demandes peuvent être lues en toute sécuritéM SK2écrire des contextes simultanément .
Limites du token LLM
L’historique du contexte est inclus dans chaque prompt. Si vous avez des réponses très importantes ou plusieurs appels, vous pouvez approcher les limites de tokenM SK2 La sommation automatique aide à prévenir ce type d’appels , mais pour des séances très longuesSSK4 envisagez de nettoyer périodiquement le contexteMSC5
Limitations
- En-Memory seulement - Contextes sont perdus lors du redémarrage du serveur
- Aucune persistance - Ne convient pas à l'entreposage de données de production
- Un seul serveur - Contextes ne synchronisent pas ' entre plusieurs instances de serveur
- LLM dépendant - La qualité de la conformité dépend de l’aptitude du LLM à suivre les instructions
Conclusion
Contextes de l’API Transformer des APIs modèles instaurés en simulations instaurées qui maintiennent la cohérence entre les appels connexes. Que vous testiez un flux d’e-commerce -, simuliez une situation réelle
L’extraction automatique des données partagées, la synthèse intelligente, et l’intégration sans fil entre tous les types de points d’entrée rendent les contextes un outil puissant pour le test et le développement réalistes d’API .
Pour des scénarios plus avancés, combinaison de contextes avec les spécifications OpenAPI pour créer pleinement-APIs mock caractéristiques qui se comportent comme la chose réelle .