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: контексти API: збереження сумісності між викликами Mock API
Wednesday, 05 November 2025
ЗАУВАЖЕННЯ: в основному статтю створено ШІ як частину пакунка nuget як документацію для випуску.
Це досить цікаво, тому я поставив його тут, але якщо це проблема для вас, будь ласка, проігноруйте її.
Вступ
**Під час створення та тестування програм, однією з найбільших проблем традиційного імітованого 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: [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, 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.
Спостерігати за розміром контексту
Позначте пункти контексту, щоб побачити кількість дзвінків, які зберігатимуться:
- **Якщо у вас є багато викликів ( >100), зверніться до розділу про чистку і почніть з нуля, щоб уникнути проблем з швидкою тривалістю.**5.
- Об' єднати з Спектрами OpenAPIДля максимального реалізму скористайтеся контекстами з параметрами OpenAPI:
- Обмірковування швидкодіїВикористання пам' яті
- **Кожен з сховищ контексту:**До 15 недавніх викликів (повна просьба/спонс)
Резюме старих викликів (стиснутих)
Видобування спільних даних (малий словник)
Типова пам' ять на контекст
: ~50- 200 КБ залежно від розмірів відповіді