# LLMApi: OpenAPI Dynamic Mock Generator: Laden van elke Spec, Mock elke API

<!--category-- AI, LLM, ASP.NET Core, API, Nuget, mockllmapi, LLMApi, SignalR, AI-Article-->
<datetime class="hidden">2025-11-05T15:15</datetime>

> OPMERKING: Dit artikel is voornamelijk AI gegenereerd als onderdeel van mijn nuget pakket als release documentatie.

# Het is best interessant dus ik heb het hier neergezet maar als dat een probleem voor je is, negeer het dan alsjeblieft.

Inleiding

Heb je ooit een API moeten testen die nog niet klaar was?

[![Of wilde offline te ontwikkelen zonder het halen van tarieflimieten?](https://img.shields.io/nuget/v/mostlylucid.mockllmapi.svg)](https://www.nuget.org/packages/mostlylucid.mockllmapi)
[![Met de OpenAPI Dynamic Mock Generator kunt u elke OpenAPI specificatie laden en maakt u onmiddellijk een volledig functionele spot API met realistische, LLM-gegenereerde gegevens.](https://img.shields.io/nuget/dt/mostlylucid.mockllmapi.svg)](https://www.nuget.org/packages/mostlylucid.mockllmapi)

Geen configuratiebestanden.[Geen handmatige eindpuntcreatie.](https://github.com/scottgal/LLMApi)Richt het gewoon op een OpenAPI spec en begin met het maken van verzoeken.

![NuGet](openapi.png)

[TOC]

## NuGet

U kunt de

```mermaid
graph TD
    A[Your App] --> B{External API}
    B -->|Not Ready| C[Development Blocked]
    B -->|Rate Limited| D[Can't Test Freely]
    B -->|Requires Auth| E[Complex Setup]
    B -->|Expensive| F[Cost Concerns]
    B -->|Unreliable| G[Flaky Tests]
```

GitHub hier

- voor het project, alle publieke domein, enz...
- OpenApi-beheer
- Het probleem: API afhankelijkheden Blokontwikkeling

## Moderne toepassingen zijn afhankelijk van tientallen externe API's.

Tijdens de ontwikkeling wordt u geconfronteerd met verschillende uitdagingen:

1. Traditionele oplossingen omvatten:
2. Handmatig schrijven van spotreacties (vervelend, wordt verouderd)
3. Recording/replaying HTTP verkeer (brittle, moeilijk te onderhouden)
4. Met behulp van harde gecodeerde armaturen (onrealistisch, heeft geen betrekking op rand gevallen)

```mermaid
sequenceDiagram
    participant Dev as Developer
    participant System as Mock System
    participant Spec as OpenAPI Spec
    participant LLM as Local LLM

    Dev->>System: Load spec from URL/file
    System->>Spec: Parse OpenAPI document
    Spec-->>System: Endpoints, schemas, descriptions
    System->>System: Register dynamic routes

    Dev->>System: GET /petstore/pet/123
    System->>LLM: Generate data for "Pet" schema
    LLM-->>System: Realistic pet data
    System-->>Dev: {"id": 123, "name": "Max", ...}
```

## De oplossing: Dynamische OpenAPI Mocking

### Richt het systeem op elke OpenAPI spec, en het automatisch:

Ontleedt de specificatie

```mermaid
graph TB
    A[HTTP Request] --> B{Route Matches?}
    B -->|No| C[404 Not Found]
    B -->|Yes| D[DynamicOpenApiManager]
    D --> E[Find Matching Endpoint]
    E --> F[OpenApiRequestHandler]
    F --> G[Extract Schema from Spec]
    G --> H[Build LLM Prompt]
    H --> I[PromptBuilder]
    I --> J[Include Context?]
    J -->|Yes| K[OpenApiContextManager]
    J -->|No| L[LLM Client]
    K --> L
    L --> M[Get Response]
    M --> N[JsonExtractor]
    N --> O[Return Mock Data]
```

**Ontdekt alle eindpunten**

1. **Genereert realistische spotgegevens met behulp van een LLM**Serveert de spot API op uw lokale machine
2. **Hoe het werkt**Overzicht architectuur
3. **Het OpenAPI-systeem bestaat uit verschillende gecoördineerde componenten:**Sleutelcomponenten:
4. **DynamicOpenApiManager**- Beheert geladen specs en route matching
5. **OpenApiSpecLoader**- Fetches en parses OpenAPI documenten

### OpenApiRequestHandler

- Genereert responsen voor overeenkomende eindpunten

**PromptBuilder**

```http
POST /api/openapi/specs
Content-Type: application/json

{
  "name": "petstore",
  "source": "https://petstore3.swagger.io/api/v3/openapi.json",
  "basePath": "/petstore"
}
```

**- Maakt LLM-prompts aan van OpenAPI schema's**

```http
POST /api/openapi/specs
Content-Type: application/json

{
  "name": "my-api",
  "source": "./specs/my-api.yaml",
  "basePath": "/api/v1"
}
```

**OpenApiContextManager**

```http
POST /api/openapi/specs
Content-Type: application/json

{
  "name": "inline-api",
  "source": "data:application/json;base64,eyJvcGVuYXBpIjoiMy...",
  "basePath": "/api"
}
```

### - Onderhoudt consistentie tussen gesprekken (optioneel)

Specificaties laden

```csharp
public async Task<SpecLoadResult> LoadSpecAsync(
    string name,
    string source,
    string? basePath = null,
    string? contextName = null)
{
    // 1. Use scoped service factory for OpenApiSpecLoader
    using var scope = _scopeFactory.CreateScope();
    var specLoader = scope.ServiceProvider
        .GetRequiredService<OpenApiSpecLoader>();

    // 2. Load the OpenAPI document
    var document = await specLoader.LoadSpecAsync(source);

    // 3. Determine base path from spec or parameter
    var effectiveBasePath = basePath
        ?? document.Servers?.FirstOrDefault()?.Url
        ?? "/api";

    // 4. Store spec with configuration
    var config = new OpenApiSpecConfig
    {
        Name = name,
        Source = source,
        Document = document,
        BasePath = effectiveBasePath,
        ContextName = contextName,
        LoadedAt = DateTimeOffset.UtcNow
    };

    _specs.AddOrUpdate(name, config, (_, __) => config);

    // 5. Notify listeners via SignalR
    await NotifySpecLoaded(name, effectiveBasePath);

    return new SpecLoadResult
    {
        Name = name,
        BasePath = effectiveBasePath,
        EndpointCount = CountEndpoints(document),
        Success = true
    };
}
```

### Specificaties kunnen uit drie bronnen worden geladen:

1.

```csharp
public OpenApiEndpointMatch? FindMatchingEndpoint(string path, string method)
{
    // Try each loaded spec
    foreach (var spec in _specs.Values)
    {
        // Remove base path prefix
        var relativePath = path;
        if (path.StartsWith(spec.BasePath))
        {
            relativePath = path.Substring(spec.BasePath.Length);
        }

        // Find matching path in OpenAPI document
        var (pathTemplate, operation) = FindOperation(
            spec.Document,
            relativePath,
            method);

        if (operation != null)
        {
            return new OpenApiEndpointMatch
            {
                Spec = spec,
                PathTemplate = pathTemplate,
                Operation = operation,
                Method = ParseMethod(method)
            };
        }
    }

    return null;
}
```

### URL-adres op afstand

2.

```csharp
public async Task<string> HandleRequestAsync(
    HttpContext context,
    OpenApiDocument document,
    string path,
    OperationType method,
    OpenApiOperation operation,
    string? contextName = null,
    CancellationToken cancellationToken = default)
{
    // 1. Extract request body
    var requestBody = await ReadRequestBodyAsync(context.Request);

    // 2. Get success response schema
    var shape = ExtractResponseSchema(operation);

    // 3. Get context history if using contexts
    var contextHistory = !string.IsNullOrWhiteSpace(contextName)
        ? _contextManager.GetContextForPrompt(contextName)
        : null;

    // 4. Build prompt from OpenAPI metadata
    var description = operation.Summary ?? operation.Description;
    var prompt = _promptBuilder.BuildPrompt(
        method.ToString(),
        path,
        requestBody,
        new ShapeInfo { Shape = shape },
        streaming: false,
        description: description,
        contextHistory: contextHistory);

    // 5. Get response from LLM
    var rawResponse = await _llmClient.GetCompletionAsync(
        prompt,
        cancellationToken);

    // 6. Extract clean JSON
    var jsonResponse = JsonExtractor.ExtractJson(rawResponse);

    // 7. Store in context if configured
    if (!string.IsNullOrWhiteSpace(contextName))
    {
        _contextManager.AddToContext(
            contextName,
            method.ToString(),
            path,
            requestBody,
            jsonResponse);
    }

    return jsonResponse;
}
```

### Lokaal bestand

3.

```csharp
private string? ExtractResponseSchema(OpenApiOperation operation)
{
    // Look for successful response (2xx)
    var successResponse = operation.Responses
        .FirstOrDefault(r => r.Key.StartsWith("2"))
        .Value;

    if (successResponse == null)
        return null;

    // Get JSON content
    var jsonContent = successResponse.Content
        .FirstOrDefault(c => c.Key.Contains("json"))
        .Value;

    if (jsonContent?.Schema == null)
        return null;

    // Convert OpenAPI schema to JSON Schema
    return ConvertToJsonSchema(jsonContent.Schema);
}

private string ConvertToJsonSchema(OpenApiSchema schema)
{
    // Recursively build JSON Schema representation
    var builder = new StringBuilder();
    builder.Append("{");

    if (schema.Type != null)
    {
        builder.Append($"\"type\":\"{schema.Type}\"");
    }

    if (schema.Properties?.Count > 0)
    {
        builder.Append(",\"properties\":{");
        var props = schema.Properties
            .Select(p => $"\"{p.Key}\":{ConvertToJsonSchema(p.Value)}");
        builder.Append(string.Join(",", props));
        builder.Append("}");
    }

    if (schema.Items != null)
    {
        builder.Append(",\"items\":");
        builder.Append(ConvertToJsonSchema(schema.Items));
    }

    builder.Append("}");
    return builder.ToString();
}
```

## Data URL (Base64 gecodeerd)

### spec Loadproces

Dit is wat er gebeurt als je een spec laadt:

**Dynamic Route Matching**

```http
POST /api/openapi/specs
Content-Type: application/json

{
  "name": "petstore",
  "source": "https://petstore3.swagger.io/api/v3/openapi.json",
  "basePath": "/petstore"
}
```

**Wanneer een verzoek arriveert, komt het systeem overeen met alle geladen specificaties:**

```json
{
  "name": "petstore",
  "basePath": "/petstore",
  "endpointCount": 19,
  "endpoints": [
    {"path": "/petstore/pet", "method": "POST"},
    {"path": "/petstore/pet/{petId}", "method": "GET"},
    {"path": "/petstore/pet/findByStatus", "method": "GET"},
    ...
  ],
  "success": true
}
```

**Behandeling van verzoeken**

Zodra een passend eindpunt is gevonden, genereert de begeleider een respons:

```http
### Get a pet by ID
GET /petstore/pet/123

### Response (auto-generated):
{
  "id": 123,
  "name": "Max",
  "category": {
    "id": 1,
    "name": "Dogs"
  },
  "photoUrls": [
    "https://example.com/max1.jpg"
  ],
  "tags": [
    {"id": 1, "name": "friendly"},
    {"id": 2, "name": "trained"}
  ],
  "status": "available"
}
```

```http
### Find pets by status
GET /petstore/pet/findByStatus?status=available

### Response (auto-generated array):
[
  {
    "id": 42,
    "name": "Buddy",
    "status": "available",
    ...
  },
  {
    "id": 43,
    "name": "Luna",
    "status": "available",
    ...
  }
]
```

**Schema extractie**

```http
GET /api/openapi/specs/petstore

### Shows full details:
### - All endpoints
### - Load time
### - Context configuration
### - Base path
```

**Het systeem haalt responsschema's uit OpenAPI definities:**

```http
POST /api/openapi/specs/petstore/reload
```

**Gebruik in de reële wereld**

```http
DELETE /api/openapi/specs/petstore
```

### Voorbeeld: Petstore API

Laten we de klassieke Petstore API bespotten:

```http
### Load Petstore at /petstore
POST /api/openapi/specs
{"name": "petstore", "source": "...", "basePath": "/petstore"}

### Load GitHub API at /github
POST /api/openapi/specs
{"name": "github", "source": "...", "basePath": "/github"}

### Load Stripe API at /stripe
POST /api/openapi/specs
{"name": "stripe", "source": "...", "basePath": "/stripe"}

### All three APIs now available simultaneously:
GET /petstore/pet/123
GET /github/users/octocat
GET /stripe/customers/cus_123
```

### Stap 1: Laad de spec

Respons:

```http
POST /api/openapi/specs
Content-Type: application/json

{
  "name": "petstore",
  "source": "https://petstore3.swagger.io/api/v3/openapi.json",
  "basePath": "/petstore",
  "contextName": "petstore-session"
}
```

Stap 2: Gebruik de eindpunten van Mock

```http
### Create a pet
POST /petstore/pet
{"name": "Max", "status": "available"}

### Response: {"id": 42, "name": "Max", "status": "available"}

### Get the pet (will reference same ID and name)
GET /petstore/pet/42

### Response: {"id": 42, "name": "Max", "status": "available"}
### Notice: Consistent ID and name from context
```

## Nu zijn alle 19 eindpunten beschikbaar:

Stap 3: Inspecteer de spec

```http
POST /api/openapi/test
Content-Type: application/json

{
  "specName": "petstore",
  "path": "/pet/123",
  "method": "GET"
}

### Returns mock response without affecting routes
```

Stap 4: Herladen als spec verandert

- Stap 5: Verwijderen wanneer voltooid
- Meerdere Specificaties Tegelijkertijd
- U kunt meerdere specificaties tegelijk laden, elk met zijn eigen basispad:

## Specificaties met contexten

Voor nog meer realisme, geef een context aan een spec:`http://localhost:5116/OpenApi`:

```mermaid
graph TD
    A[OpenAPI Manager UI] --> B[Load Spec Section]
    A --> C[Spec List]
    A --> D[Context Viewer]

    B --> E[URL Input]
    B --> F[JSON Input]
    B --> G[Context Configuration]

    C --> H[Spec Card]
    H --> I[Reload Button]
    H --> J[Delete Button]
    H --> K[View Endpoints]

    D --> L[Active Contexts]
    L --> M[Context Details]
    L --> N[Clear Context]
```

**Nu hebben alle petstore eindpunten dezelfde context:**

- **Eindpunt testen**Met het testeindpunt kunt u een eindpunt proberen zonder een echt verzoek te doen:
- **Dit is nuttig voor:**Evaluatie van de antwoorden vóór integratie
- **Testen van specifieke eindpunten in isolatie**Schema-problemen debuggen
- **Beheers-UI**Voor visueel beheer, bezoek
- **Functies:**Sleep-and-drop
- **spec bestand uploaden**Live endpoint discovery

## - Bekijk alle eindpunten direct

### Testen met één klik

- Test elk eindpunt met een knop

```yaml
# OpenAPI Spec
/pet/{petId}:
  get:
    parameters:
      - name: petId
        in: path
        schema:
          type: integer
```

```http
GET /petstore/pet/123
### LLM receives: "Generate data for Pet with petId=123"
### Response: {"id": 123, ...}
```

### Realtime-kennisgevingen

- SignalR-updates bij het laden van specificaties

```yaml
/pet/findByStatus:
  get:
    parameters:
      - name: status
        in: query
        schema:
          type: string
          enum: [available, pending, sold]
```

```http
GET /petstore/pet/findByStatus?status=available
### LLM receives: "Generate array of Pets with status=available"
### Response: [{"status": "available", ...}, ...]
```

### Syntaxismarkering

- Prachtige JSON response display

```yaml
/pet:
  post:
    requestBody:
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Pet'
```

```http
POST /petstore/pet
Content-Type: application/json

{"name": "Max", "status": "available"}

### LLM receives: "Generate response for creating Pet with name=Max, status=available"
### Response: {"id": 42, "name": "Max", "status": "available"}
```

### Contextbeheer

- Zicht en duidelijke contexten

```yaml
/pet/{petId}:
  get:
    summary: Find pet by ID
    description: Returns a single pet based on the ID provided
```

Geavanceerde functies

### Padparameters

Padparameters worden automatisch uitgepakt:

```yaml
responses:
  '200':
    description: Successful operation
    content:
      application/json:
        schema:
          $ref: '#/components/schemas/Pet'
  '404':
    description: Pet not found
```

Parameters opvragen

## Query parameters beïnvloeden de respons:

Verzoekorganen

```csharp
public async Task NotifySpecLoaded(string name, string basePath)
{
    await _hubContext.Clients.All.SendAsync("SpecLoaded", new
    {
        name,
        basePath,
        timestamp = DateTimeOffset.UtcNow
    });
}

public async Task NotifySpecDeleted(string name)
{
    await _hubContext.Clients.All.SendAsync("SpecDeleted", new
    {
        name,
        timestamp = DateTimeOffset.UtcNow
    });
}
```

POST/PUT-lichamen zijn opgenomen in de prompt:

```javascript
const connection = new signalR.HubConnectionBuilder()
    .withUrl('/hubs/openapi')
    .build();

connection.on('SpecLoaded', (data) => {
    showNotification(`Spec "${data.name}" loaded at ${data.basePath}`, 'success');
    refreshSpecList();
});

connection.on('SpecDeleted', (data) => {
    showNotification(`Spec "${data.name}" deleted`, 'info');
    refreshSpecList();
});
```

## Beschrijvingen en samenvattingen

OpenAPI beschrijvingen begeleiden de LLM:

- Deze zijn opgenomen in de prompt en helpen de LLM het doel van het eindpunt te begrijpen.
- Responsstatuscodes
- Het systeem maakt gebruik van de eerste succesvolle (2xx) respons:
- Alleen de 200 schema wordt gebruikt voor de mock-generatie (404s zijn momenteel niet gesimuleerd).
- SignalR real-time updates

Wanneer specs worden geladen/verwijderd, ontvangt de UI real-time meldingen via SignalR:

## JavaScript UI code:

### Ondersteuning voor formatteren

```http
 Bad:  {"name": "spec1", ...}
 Good: {"name": "github-v3", ...}
```

### Het systeem ondersteunt:

OpenAPI 3.0.x

```http
### Good separation
/petstore/...
/github/...
/stripe/...

### Bad (conflicts!)
/api/... (multiple specs)
```

### OpenAPI 3.1.x

Swagger 2.0

```http
POST /api/openapi/specs/my-api/reload
```

### JSON-formaat

```http
POST /api/openapi/specs
{
  "name": "petstore",
  "source": "...",
  "contextName": "test-session"
}

### Now all petstore calls maintain consistency
```

### YAML-formaat

Zowel JSON als YAML specs worden automatisch gedetecteerd en ontleed.

```http
DELETE /api/openapi/specs/old-spec
```

## Beste praktijken

1. **1.**Gebruik Descriptive Spec Names
2. **2.**Passende basispaden instellen
3. **Conflicten vermijden door gebruik te maken van unieke basispaden:**3.
4. **Herladen wanneer Specificaties wijzigen**Als uw OpenAPI-spec bijgewerkt is, herlaad deze dan:
5. **4.**Contexts gebruiken voor gerelateerde oproepen

## 5.

Opruimen na het testen`/api/mock`Verwijder specificaties die u niet meer gebruikt:

```http
### OpenAPI-based (from spec)
GET /petstore/pet/123
### Uses Pet schema from OpenAPI spec

### Regular mock (shape-based)
GET /api/mock/custom?shape={"id":0,"name":"string"}
### Uses explicit shape parameter
```

Beperkingen

## Responsstatuscodes

### - Alleen succesvolle (2xx) reacties worden bespot

Authenticatie

```csharp
private readonly ConcurrentDictionary<string, OpenApiSpecConfig> _specs = new();
```

### - Auth headers worden geaccepteerd maar niet gevalideerd

`DynamicOpenApiManager`Validatie

### - Verzoek validatie tegen schema's is niet afgedwongen

Staat

```http
### Send these simultaneously
POST /api/openapi/specs {"name": "spec1", ...}
POST /api/openapi/specs {"name": "spec2", ...}
POST /api/openapi/specs {"name": "spec3", ...}
```

- Geen werkelijke database; gegevens worden telkens opnieuw gegenereerd (tenzij met behulp van contexten)

## Prestaties

### - LLM generatie voegt latency (~100-500ms per aanvraag)

**Integratie met reguliere Mock-eindpunten**OpenAPI-specs werken naast reguliere

**eindpunten:**

- Beiden gebruiken dezelfde onderliggende LLM maar verschillen in hoe het schema wordt verstrekt.
- Optimalisatie van de prestaties
- Caching
- Geladen specs worden gecached in het geheugen:

### Levensduur van de dienst

**is een singleton, dus specs blijven geladen voor de levensduur van de toepassing.**Parallelle spec laden

**Meerdere specificaties kunnen parallel worden geladen:**

- Alle drie zullen ze parallel laden, niet achtereenvolgens.`/petstore` + `/pet/123` = `/petstore/pet/123`
- Problemen oplossen
- Spec zal niet laden

### Probleem:

**Spec laden mislukt**Oplossingen:

**Controleer of de URL toegankelijk is**

- Controleren of het bestand bestaat (voor lokale paden)
- Zorg ervoor dat de JSON/YAML geldig is
- Zoek naar CORS-problemen (voor externe URL's)

## Eindpunt niet gevonden

Probleem:

404 bij verwacht eindpunt

- **Oplossingen:**Verifiëren van het basispad:
- **Controleer de spec definieert eigenlijk dat pad**Zorg ervoor dat de HTTP methode overeenkomt (GET vs POST)
- **Antwoord komt niet overeen met schema**Probleem:
- **Gegenereerde gegevens komen niet overeen met het verwachte schema**Oplossingen:
- **Controleer of het schema in de spec juist is**Controleer of je kijkt naar de juiste reactie (200 vs 201)

Onthoud: LLM-generatie is probabilistisch, niet deterministisch

Conclusie