# LLMApi: OpenAPI Dynamic Mock Generator: Charger n'importe quelle spécification, Mock n'importe quelle API

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

> REMARQUE: Cet article est principalement généré par l'IA dans le cadre de mon paquet nuget comme documentation de publication.

# C'est assez intéressant donc je l'ai mis ici mais si c'est un problème pour vous s'il vous plaît ignorez-le.

Présentation

Avez-vous déjà eu besoin de tester contre une API qui n'était pas encore prête ?

[![Ou a voulu se développer hors ligne sans frapper les limites de taux?](https://img.shields.io/nuget/v/mostlylucid.mockllmapi.svg)](https://www.nuget.org/packages/mostlylucid.mockllmapi)
[![L'OpenAPI Dynamic Mock Generator vous permet de charger n'importe quelle spécification OpenAPI et crée instantanément une API simulée entièrement fonctionnelle avec des données LLM réalistes.](https://img.shields.io/nuget/dt/mostlylucid.mockllmapi.svg)](https://www.nuget.org/packages/mostlylucid.mockllmapi)

Pas de fichiers de configuration.[Pas de création manuelle d'extrémités.](https://github.com/scottgal/LLMApi)Il suffit de le pointer à une spécification OpenAPI et commencer à faire des requêtes.

![NuGet](openapi.png)

[TOC]

## NuGet

Vous pouvez trouver le

```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 ici

- pour le projet, tout domaine public etc...
- Gestionnaire OpenApi
- Le problème : les dépendances de l'API bloquent le développement

## Les applications modernes dépendent de dizaines d'API externes.

Au cours du développement, vous êtes confrontés à plusieurs défis :

1. Les solutions traditionnelles comprennent:
2. Écrire manuellement des réponses fictives (pédagogiques, deviennent obsolètes)
3. Enregistrement/rejouage du trafic HTTP (faible, difficile à maintenir)
4. Utilisation d'appareils codés en dur (inréaliste, ne couvre pas les boîtiers de bord)

```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", ...}
```

## La solution : le mocking dynamique OpenAPI

### Pointez le système sur n'importe quelle spécification OpenAPI, et il automatiquement:

Analyse la spécification

```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]
```

**Découvre tous les paramètres**

1. **Génére des données de simulation réalistes à l'aide d'un LLM**Servez l'API simulée sur votre machine locale
2. **Comment ça marche**Vue d'ensemble de l'architecture
3. **Le système OpenAPI se compose de plusieurs composantes coordonnées:**Composantes clés :
4. **DynamicOpenApiManager**- Gère les spécifications chargées et les correspondances d'itinéraire
5. **OpenApiSpecLoader**- Fiches et analyses des documents OpenAPI

### OpenApiRequestHandler

- Génére des réponses pour les paramètres appariés

**PromptBuilder**

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

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

**- Création d'invites LLM à partir de schémas OpenAPI**

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

### - Maintient la cohérence entre les appels (facultatif)

Chargement des spécifications

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

### Les spécifications peuvent être chargées à partir de trois sources :

1. Le Conseil de l'Europe a adopté une résolution du Conseil de l'Europe sur la situation des droits de l'homme dans le monde.

```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 distante

2. Le Président. — L'ordre du jour appelle le rapport (doc.

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

### Fichier local

3. Les droits de l'homme sont garantis par le Pacte international relatif aux droits économiques, sociaux et culturels.

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

## URL de données (encodée Base64)

### Processus de chargement des spécifications

Voici ce qui se passe lorsque vous chargez une spécification:

**Correspondance dynamique de la route**

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

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

**Lorsqu'une requête arrive, le système la compare à toutes les spécifications chargées:**

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

**Traitement des demandes**

Une fois qu'un paramètre correspondant est trouvé, le gestionnaire génère une réponse:

```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",
    ...
  }
]
```

**Extraction de schéma**

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

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

**Le système extrait les schémas de réponse des définitions OpenAPI :**

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

**Utilisation réelle dans le monde**

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

### Exemple : API Petstore

Passons à pied en nous moquant de l'API classique de Petstore :

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

### Étape 1: Chargez la spécification

Réponse :

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

Étape 2: Utilisez les points d'extrémité 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
```

## Maintenant, les 19 paramètres sont disponibles:

Étape 3 : Inspecter la spécification

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

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

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

Étape 4 : Recharger si la spécification change

- Étape 5: Supprimer une fois fait
- Caractéristiques multiples Simultanément
- Vous pouvez charger plusieurs spécifications à la fois, chacune avec son propre chemin de base:

## Spécifications avec contextes

Pour encore plus de réalisme, assignez un contexte à une spécification :`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]
```

**Maintenant, tous les paramètres Petstore partagent le même contexte:**

- **Mise à l'essai du point d'extrémité**Le paramètre test vous permet d'essayer un paramètre sans faire une vraie demande:
- **Ceci est utile pour:**Prévisualiser les réponses avant l'intégration
- **Essai de paramètres spécifiques isolés**Problèmes de schéma de débogage
- **Régime d'assurance-chômage pour la gestion**Pour la gestion visuelle, visitez
- **Caractéristiques:**Faire glisser-déposer
- **Téléchargement du fichier spec**Découverte d'un endpoint en direct

## - Voir tous les paramètres instantanément

### Essais en un clic

- Tester n'importe quel point d'arrêt avec un bouton

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

### Notifications en temps réel

- Mises à jour SignalR lorsque les spécifications sont chargées

```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", ...}, ...]
```

### Mise en évidence de la syntaxe

- Magnifique écran de réponse JSON

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

### Gestion du contexte

- Vue et contextes clairs

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

Caractéristiques avancées

### Paramètres de trajectoire

Les paramètres de chemin sont automatiquement extraits :

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

Paramètres de requête

## Les paramètres de requête influencent la réponse:

Organismes demandeurs

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

Les corps POST/PUT sont inclus dans l'invite:

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

## Descriptions et résumés

Les descriptions OpenAPI guident le LLM :

- Ceux-ci sont inclus dans l'invite, aidant le LLM à comprendre le but du paramètre.
- Codes d'état de la réponse
- Le système utilise la première réponse réussie (2xx) :
- Seul le schéma 200 est utilisé pour la génération simulée (404s ne sont pas actuellement simulés).
- Mises à jour en temps réel du signalR

Lorsque les spécifications sont chargées/supprimées, l'interface utilisateur reçoit des notifications en temps réel via SignalR :

## Code d'interface utilisateur JavaScript & #160;:

### Support de format

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

### Le système prend en charge:

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

### Format JSON

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

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

### Format YAML

Les spécifications JSON et YAML sont automatiquement détectées et analysées.

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

## Meilleures pratiques

1. **1. Le Conseil de l'Europe a adopté une résolution du Conseil de l'Europe sur la situation des droits de l'homme dans le monde.**Utiliser les noms de spécifications descriptifs
2. **2. Le Président. — L'ordre du jour appelle le rapport (doc.**Définir les voies de base appropriées
3. **Éviter les conflits en utilisant des chemins de base uniques :**3. Les droits de l'homme sont garantis par le Pacte international relatif aux droits économiques, sociaux et culturels.
4. **Recharger lorsque les spécifications changent**Si votre spécification OpenAPI est mise à jour, rechargez-la :
5. **4. Le Président. — L'ordre du jour appelle le rapport (doc.**Utiliser les contextes pour les appels connexes

## 5.

Nettoyer après l'essai`/api/mock`Enlever les spécifications que vous n'utilisez plus :

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

Limitations

## Codes d'état de la réponse

### - Seules les réponses réussies (2xx) sont truquées

Authentification

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

### - Les en-têtes d'auth sont acceptés mais non validés

`DynamicOpenApiManager`Validation

### - La validation de la demande par rapport aux schémas n'est pas appliquée

État

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

- Pas de base de données réelle; les données sont générées à chaque fois (à moins d'utiliser des contextes)

## Résultats

### - La génération LLM ajoute de la latence (~100-500ms par demande)

**Intégration avec les points d'extrémité Mock réguliers**Les spécifications OpenAPI fonctionnent en parallèle avec les spécifications régulières

**critères d' évaluation:**

- Les deux utilisent le même LLM sous-jacent, mais diffèrent dans la façon dont le schéma est fourni.
- Optimisation des performances
- Cache
- Les spécifications chargées sont mises en cache en mémoire :

### Durée de service

**est un simpleton, donc les spécifications restent chargées pour la durée de vie de l'application.**Chargement parallèle des spécifications

**Plusieurs spécifications peuvent être chargées en parallèle:**

- Tous les trois vont se charger en parallèle, pas séquentiellement.`/petstore` + `/pet/123` = `/petstore/pet/123`
- Dépannage
- Spec ne sera pas chargé

### Problème :

**Défauts de chargement des spécifications**Solutions :

**Vérifiez que l'URL est accessible**

- Vérifier l'existence du fichier (pour les chemins locaux)
- S'assurer que le JSON/YAML est valide
- Recherchez les problèmes CORS (pour les URL distantes)

## Point d'arrivée non trouvé

Problème :

404 sur le critère d'évaluation prévu

- **Solutions :**Vérifier le chemin de base :
- **Vérifiez que la spécification définit réellement ce chemin**S'assurer que la méthode HTTP correspond (GET vs POST)
- **La réponse ne correspond pas au schéma**Problème :
- **Les données générées ne correspondent pas au schéma attendu**Solutions :
- **Vérifiez si le schéma de la spécification est correct**Vérifiez que vous regardez la bonne réponse (200 vs 201)

Rappelez-vous : la génération LLM est probabiliste, pas déterministe

Le présent règlement entre en vigueur le vingtième jour suivant celui de sa publication au Journal officiel de l'Union européenne.