LLMapi: контексти API: збереження сумісності між викликами Mock API (Українська (Ukrainian))

LLMapi: контексти API: збереження сумісності між викликами Mock API

Wednesday, 05 November 2025

//

11 minute read

ЗАУВАЖЕННЯ: в основному статтю створено ШІ як частину пакунка nuget як документацію для випуску.

Це досить цікаво, тому я поставив його тут, але якщо це проблема для вас, будь ласка, проігноруйте її.

Вступ

**Під час створення та тестування програм, однією з найбільших проблем традиційного імітованого API є їхня бездержавна природа.**Кожен запит повертає повністю випадкові дані без зв' язку з попередніми викликами.

Якщо ви отримаєте користувача з ідентифікатором 123, тоді отримати їх замовлення, немає гарантій, що порядок буде посилатися на той самий ідентифікатор користувача. Контексти API

Вирішіть цю проблему, надавши вашому уявному API пам'яті.NuGetNuGet

Ви можете знайти

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

для проекту, для всіх публічних доменів і т. д....

Проблема: безвихідний Хаос

Традиційні API для висміювання створюють дані незалежно від кожного запиту:

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.

Нет соответствия между связанными звонками.

Вирішення: контекстна пам' ять

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, глузливий API підтримує спільний контекст через пов' язані з ним запити:**Тепер LLM бачить попередній виклик користувача і створює порядок, що посилається на той самий ІД і ім' я користувача. **Ці дані утворюють послідовну історію.**Як це працює АрхітектураКонтекстна система складається з трьох основних компонентів:

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

- Видобуває назву контексту з запиту

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

Запитувати розробника

- Включає історію контексту у запрошеннях 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 API

Потокові 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
}

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)

SignR (за допомогою налаштувань)

DELETE /api/openapi/contexts/session-1

Випадки використання у реальному світі

DELETE /api/openapi/contexts

Випадок використання 1: Імітований потік

Імітує повний досвід закупівлі за допомогою відповідних даних користувача і порядку:

Симуляція ринкових курсів у випадку використання 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: alice@example.com

Recent API calls:
  [10:00:05] GET /users/42
    Response: {"id": 42, "name": "Alice", "email": "alice@example.com"}
  [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, SignR) виконує той самий шаблон:

Контекст у запитах 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

Якщо контекст існує, його історію включено до запиту LLM:

LLM бачить всі попередні виклики і створює відповіді, які посилаються на однакові ІД, імена та інші дані, підтримуючи послідовність.

Найкращі вправи

  • Використовувати описовані назви контексту

Спорожняти контексти, коли виконаноКонтексти зберігаються у пам' яті до явного спорожнення або перезапуску сервера:

3.

OpenApiContextManagerСпільний доступ до контексту між пов'язаними точками кінцяConcurrentDictionaryВикористовувати ту саму назву контексту для всіх пов' язаних викликів:

4.

Спостерігати за розміром контексту

Позначте пункти контексту, щоб побачити кількість дзвінків, які зберігатимуться:

  1. **Якщо у вас є багато викликів ( >100), зверніться до розділу про чистку і почніть з нуля, щоб уникнути проблем з швидкою тривалістю.**5.
  2. Об' єднати з Спектрами OpenAPIДля максимального реалізму скористайтеся контекстами з параметрами OpenAPI:
  3. Обмірковування швидкодіїВикористання пам' яті
  4. **Кожен з сховищ контексту:**До 15 недавніх викликів (повна просьба/спонс)

Резюме старих викликів (стиснутих)

Видобування спільних даних (малий словник)

Типова пам' ять на контекст

: ~50- 200 КБ залежно від розмірів відповіді

Finding related posts...
logo

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