Back to "LLMApi: API Πλαίσιο: Διατήρηση της συνέπειας μεταξύ των κλήσεων 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: API Πλαίσιο: Διατήρηση της συνέπειας μεταξύ των κλήσεων API Mock

Wednesday, 05 November 2025

ΣΗΜΕΙΩΣΗ: Το άρθρο του είναι κυρίως AI που παράγεται ως μέρος του πακέτου nuget μου ως έγγραφα απελευθέρωσης.

Είναι πολύ ενδιαφέρον γι' αυτό το έβαλα εδώ, αλλά αν αυτό είναι πρόβλημα για σένα, σε παρακαλώ, αγνόησέ το.

Εισαγωγή

**Κατά την κατασκευή και δοκιμή εφαρμογών, μια από τις μεγαλύτερες προκλήσεις με την παραδοσιακή mock APIs είναι η απάτριδα φύση τους.**Κάθε αίτημα επιστρέφει εντελώς τυχαία δεδομένα χωρίς σχέση με προηγούμενες κλήσεις.

Αν φέρεις έναν χρήστη με ταυτότητα 123, τότε φέρε τις παραγγελίες τους, δεν υπάρχει καμία εγγύηση ότι η εντολή θα αναφέρει την ίδια ταυτότητα χρήστη. Πλαίσιο API

Λύστε αυτό το πρόβλημα, δίνοντας σας mock API μια ανάμνηση.NuGetCity name (optional, probably does not need a translation)NuGetCity name (optional, probably does not need a translation)

Μπορείτε να βρείτε το

GitHub εδώ

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

για το έργο, όλος ο δημόσιος τομέας κ.λπ...

Το Πρόβλημα: Χάος Χωρίς Κράτηση

Παραδοσιακή mocking APIs παράγουν δεδομένα ανεξάρτητα για κάθε αίτηση:

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

Πρόσεξες το πρόβλημα;

Ο χρήστης είχε ταυτότητα 42, αλλά η παραγγελία επέστρεψε με userId 99.

Δεν υπάρχει συνέπεια μεταξύ των σχετικών κλήσεων.

The Solution: Contextual Memory

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

**Με τα πλαίσια API, η παρωδία API διατηρεί ένα κοινό πλαίσιο μεταξύ των σχετικών αιτημάτων:**Τώρα η LLM βλέπει την προηγούμενη κλήση χρήστη και δημιουργεί εντολές που αναφέρουν την ίδια ταυτότητα χρήστη και το ίδιο όνομα. **Τα δεδομένα σχηματίζουν μια συνεκτική ιστορία.**Πώς Λειτουργεί ΑρχιτεκτονικήΤο σύστημα πλαισίου αποτελείται από τρία κύρια συστατικά στοιχεία:

1.

ΠλαίσιοExtractorConcurrentDictionary:

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

- Αποσπά την ονομασία-πλαίσιο από την αίτηση

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

  • Διαχειρίζεται το πλαίσιο αποθήκευσης και ανάκτησης
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

- Συμπεριλαμβάνει το ιστορικό των υποδείξεων LLM

Αποθήκευση πλαισίου

Τα πλαίσια αποθηκεύονται στη μνήμη χρησιμοποιώντας ένα νήμα-ασφαλέςΑυτόματη ανακεφαλαίωση

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

Για να αποτρέψει το πλαίσιο να αυξηθεί επ' αόριστον και να υπερβαίνει τα όρια LLM, το σύστημα συνοψίζει αυτόματα παλιές κλήσεις όταν ο αριθμός υπερβαίνει τα 15:

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

Εξαγωγή κοινών δεδομένων

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

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

Ο διαχειριστής πλαισίου αφαιρεί αυτόματα κοινά αναγνωριστικά στοιχεία από τις απαντήσεις για να τα καταστήσει εύκολα προσβάσιμα:

Αυτό επιτρέπει στο σύστημα να παρακολουθεί το πιο πρόσφατο αναγνωριστικό χρήστη, αναγνωριστικό παραγγελίας, κ.λπ., καθιστώντας τα διαθέσιμα στο ιστορικό του πλαισίου.Χρήση πλαισίωνΤρεις τρόποι για να προσδιοριστεί το πλαίσιο

Μπορείτε να περάσετε το όνομα πλαίσιο με τρεις διαφορετικούς τρόπους, με αυτή τη σειρά προτεραιότητας:

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

1.

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

Παράμετρος ερωτήσεων

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

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

(υψηλότερη προτεραιότητα)

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

2.

Κεφαλίδα HTTP

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

Αιτούμενος φορέας

Υποστηριγμένοι τύποι τελικού σημείου

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

Τα πλαίσια λειτουργούν σε όλα τα επίπεδα.

όλα

Τύποι τελικών σημείων:

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

Streaming APIs

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
}

ΓράφημαQL

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)

Signer (διαμόρφωση)

DELETE /api/openapi/contexts/session-1

Περιπτώσεις Πραγματικής Παγκόσμιας Χρήσης

DELETE /api/openapi/contexts

Χρησιμοποίησε την περίπτωση 1: E-Commerce Flow

Προσομοιώστε μια πλήρη εμπειρία αγορών με συνεπή δεδομένα χρήστη και παραγγελίας:

Χρήση περίπτωσης 2: Προσομοίωση τιμής αποθέματος

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

Δημιουργία ρεαλιστικών κινήσεων των τιμών των αποθεμάτων αντί τυχαίων τιμών:

Χωρίς πλαίσιο, κάθε κλήση θα επέστρεφε ένα εντελώς τυχαίο σύμβολο και τιμή.

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}

Με τα συμφραζόμενα, η LLM διατηρεί το ίδιο απόθεμα και προσαρμόζει ρεαλιστικά τις τιμές.

Χρήση περίπτωσης 3: Πρόοδος της Πολιτείας του Παιχνιδιού

Πίστα προόδου παίκτη μέσω συνεδριών παιχνιδιού:

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

Διαχείριση πλαισίου API

Κατάλογος όλων των πλαισίων

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

Λήψη λεπτομερειών πλαισίου

Καθαρισμός ενός ειδικού πλαισίου

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

Καθαρισμός όλων των πλαισίων

Λεπτομέρειες εφαρμογής

GET /api/openapi/contexts/demo-session

Ενσωμάτωση σε Αναζητούντες

Κάθε χειριστής αιτήματος (Rest, Streaming, GraphQL, SignalR) ακολουθεί το ίδιο μοτίβο:

Πλαίσιο σε 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

Όταν υπάρχει ένα πλαίσιο, η ιστορία του περιλαμβάνεται στην εντολή LLM:

Η LLM βλέπει όλες τις προηγούμενες κλήσεις και δημιουργεί απαντήσεις που αναφέρουν τις ίδιες ταυτότητες, ονόματα και άλλα δεδομένα, διατηρώντας τη συνοχή.

Βέλτιστες Πρακτικές

  • Χρήση περιγραφικών ονομάτων πλαισίου

Καθαρισμός των πλαισίων όταν γίνεταιΤα πλαίσια παραμένουν στη μνήμη μέχρι να εκκαθαριστεί ρητά ή ο διακομιστής επανεκκινεί:

3.

OpenApiContextManagerShare Contexts Across Related EndpointsConcurrentDictionaryΧρησιμοποιήστε το ίδιο όνομα πλαισίου για όλες τις σχετικές κλήσεις:

4.

Παρακολούθηση του μεγέθους πλαισίου

Ελέγξτε λεπτομέρειες πλαισίου για να δείτε πόσες κλήσεις αποθηκεύονται:

  1. **Εάν έχετε πολλές κλήσεις (> 100), σκεφτείτε να καθαρίσετε και να ξεκινήσετε φρέσκα για να αποφύγετε άμεσα ζητήματα μήκους.**5.
  2. Συνδυάστε με το OpenAPI SpecsΓια τον μέγιστο ρεαλισμό, χρησιμοποιήστε τα συμφραζόμενα με τις προδιαγραφές OpenAPI:
  3. Εκτιμήσεις ΑπόδοσηςΧρήση μνήμης
  4. **Κάθε πλαίσιο αποθηκεύει:**Έως 15 πρόσφατες κλήσεις (πλήρες αίτημα/απάντηση)

Περίληψη παλαιότερων κλήσεων (συμπίεση)

Εξαγωγή κοινών δεδομένων (μικρό λεξικό)

Τυπική μνήμη ανά πλαίσιο

~50-200 KB ανάλογα με τα μεγέθη απόκρισης

logo

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