Back to "llmapi: API-kontekstit: Johdonmukaisuuden säilyttäminen Mock API-puheluiden välillä"

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-kontekstit: Johdonmukaisuuden säilyttäminen Mock API-puheluiden välillä

Wednesday, 05 November 2025

HUOMAUTUS: THH:n artikkeli on ensisijaisesti tekoälyä, joka on syntynyt osana nyyttipakettiani julkaisudokumenttina.

Se on aika mielenkiintoinen, joten laitoin sen tähän, mutta jos se on ongelma sinulle, ole hyvä ja jätä se huomiotta.

Johdanto

**Sovelluksia rakennettaessa ja testattaessa yksi suurimmista haasteista perinteisillä pilkkarajapihoilla on niiden kansalaisuudettomuus.**Jokainen pyyntö palauttaa täysin satunnaisia tietoja, joilla ei ole mitään yhteyttä aiempiin puheluihin.

Jos haet käyttäjän tunnisteella 123, sitten haet heidän tilauksensa, ei ole mitään takeita siitä, että tilaus viittaa samaan käyttäjätunnukseen. API-kontekstit

Ratkaise tämä ongelma antamalla pilkkarajapintasi API-muistiin.NuGetNuGet

Voit löytää

GitHub tässä.

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

projektia varten kaikki julkisuus ym....

Ongelma: valtioton kaaos

Perinteiset pilkkurajapinnat tuottavat dataa itsenäisesti jokaisesta pyynnöstä:

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

Huomaatko ongelman?

Käyttäjällä oli henkilöllisyystodistus 42, mutta tilaus tuli käyttäjätunnuksella 99.

Asiaan liittyvien puheluiden välillä ei ole johdonmukaisuutta.

Ratkaisu: Kontekstimuisti

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 Contexts -sovelluksen avulla pilkullinen API ylläpitää yhteistä kontekstia eri pyyntöjen välillä:**Nyt LLM näkee aiemman käyttäjäpuhelun ja tuottaa tilauksia, joissa viitataan samaan käyttäjätunnukseen ja nimeen. **Tiedot muodostavat johdonmukaisen tarinan.**Miten se toimii ArkkitehtuuriKontekstijärjestelmä koostuu kolmesta pääosasta:

1 Täysosuma

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

- Ottaa yhteydenimen pyynnöstä

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

  • Hallitsee kontekstien säilytystä ja noutamista
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

- Sisältää taustatiedot LLM:n ohjeissa

Kontekstivarasto

Kontekstit säilytetään muistissa kierteisen turvan avullaAutomaattinen yhteenveto

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

Jotta asiayhteys ei kasva loputtomasti ja ylittäisi LLM-tunnusten rajoja, järjestelmä tiivistää automaattisesti vanhat puhelut, kun luku ylittää 15:

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

Jaettujen tietojen poiminta

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

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

Kontekstipäällikkö poimii vastausten joukosta automaattisesti yhteisiä tunnisteita, jotta ne ovat helposti saatavilla:

Näin järjestelmä voi seurata viimeisintä käyttäjätunnusta, tilaustunnuksia jne., jolloin ne ovat saatavilla kontekstihistoriassa.Kontekstien käyttöKolme tapaa määritellä konteksti

Kontekstin nimen voi siirtää kolmella eri tavalla:

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

1 Täysosuma

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

Kyselyparametri

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

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

(korkein prioriteetti)

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

2.

HTTP-otsikko

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

Pyynnön esittävä elin

Tuetut päätetapahtumatyypit

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

Kontekstit toimivat

Kaikki

päätetapahtumatyypit:

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

Virrataan sovellusliittymiä

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
}

GrafQL

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 (konfiguraation kautta)

DELETE /api/openapi/contexts/session-1

Todellisia käyttötapauksia

DELETE /api/openapi/contexts

Käytä Tapaus 1: Verkkokaupan virta

Simuloi täydellinen ostoskokemus yhdenmukaisilla käyttäjä- ja tilaustiedoilla:

Käyttö Tapaus 2: Varastohinnan simulointi

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

Luo realistisia osakkeiden hintaliikkeitä satunnaisten arvojen sijaan:

Ilman asiayhteyttä jokainen puhelu palauttaisi täysin satunnaisen symbolin ja hinnan.

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}

Asiayhteyteen liittyen LLM ylläpitää samaa osaketta ja mukauttaa hintoja realistisesti.

Käytä tapausta 3: Pelitilan eteneminen

Seuraa pelaajaa pelisessioissa:

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

Kontekstinhallinnan API

Luettele kaikki taustat

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

Hanki taustatiedot

Tyhjennä erityinen konteksti

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

Tyhjennä kaikki taustat

Täytäntöönpanoa koskevat yksityiskohtaiset tiedot

GET /api/openapi/contexts/demo-session

Integrointi pyyntöjen käsittelijöihin

Jokainen pyynnön käsittelijä (REST, Streaming, GraphQL, SignalR) noudattaa samaa kaavaa:

Konteksti LLM:n prompteissa

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

Kun asiayhteys on olemassa, sen historia on sisällytetty LLM:n ohjeeseen:

LLM näkee kaikki aiemmat puhelut ja tuottaa vastauksia, jotka viittaavat samoihin tunnisteisiin, nimiin ja muihin tietoihin ja säilyttävät johdonmukaisuuden.

Parhaita käytäntöjä

  • 1 Täysosuma
  • Käytä kuvailevia kontekstinimiä

Selkeitä kontekstit, kun ne on tehtyKontekstit säilyvät muistissa, kunnes se on nimenomaisesti poistettu tai palvelin käynnistyy uudelleen:

3.

OpenApiContextManagerJaa kontekstit toisiinsa liittyvien päätepisteiden yliConcurrentDictionaryKäytä samaa kontekstinimeä kaikissa asiaan liittyvissä puheluissa:

Nelonen

Seuraa kontekstikokoa

Tarkista asiayhteystiedot ja katso, kuinka monta puhelua on tallennettu:

  1. **Jos sinulla on useita puheluita (> 100), harkitse selvittämistä ja uuden aloittamista, jotta vältyt pikaisilta pituusongelmilta.**5.
  2. Yhdistä OpenAPI-spektrometritKäytä mahdollisimman realismia OpenAPI:n spesifikaatioiden yhteydessä:
  3. Suorituskykyä koskevia huomioitaMuistinkäyttö
  4. **Jokaisessa kontekstissa säilytetään:**Enintään 15 viimeisintä puhelua (täydellinen pyyntö/vastaus)

Yhteenveto vanhoista puheluista (paineistettu)

Otettu jaettua dataa (pieni sanakirja)

Tyypillinen muisti kontekstia kohden

: ~50-200 KB vastekoosta riippuen

logo

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