mostlylucid.mockllmapi 1.2.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package mostlylucid.mockllmapi --version 1.2.0
                    
NuGet\Install-Package mostlylucid.mockllmapi -Version 1.2.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="mostlylucid.mockllmapi" Version="1.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="mostlylucid.mockllmapi" Version="1.2.0" />
                    
Directory.Packages.props
<PackageReference Include="mostlylucid.mockllmapi" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add mostlylucid.mockllmapi --version 1.2.0
                    
#r "nuget: mostlylucid.mockllmapi, 1.2.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package mostlylucid.mockllmapi@1.2.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=mostlylucid.mockllmapi&version=1.2.0
                    
Install as a Cake Addin
#tool nuget:?package=mostlylucid.mockllmapi&version=1.2.0
                    
Install as a Cake Tool

mostlylucid.mockllmapi

A lightweight ASP.NET Core middleware for generating realistic mock API responses using local LLMs (via Ollama). Add intelligent mock endpoints to any project with just 2 lines of code!

NuGet NuGet


v1.2.0 Update - NO BREAKING CHANGES

Full Backward Compatibility Guaranteed! All existing code continues to work exactly as before.

What's New:

  • Native GraphQL Support - POST to /graphql with standard GraphQL queries
  • Fully Modular Architecture - Use only the protocols you need (REST, GraphQL, SSE, SignalR)
  • Polly Resilience Policies - Built-in exponential backoff retry and circuit breaker patterns (enabled by default)
  • 30-40% Reduced Memory - When using modular setup with single protocol
  • See MODULAR_EXAMPLES.md for modular usage patterns

Migration Notes:

  • Old method names (Addmostlylucid_mockllmapi, Mapmostlylucid_mockllmapi) still work but are deprecated
  • Recommended: Use new names (AddLLMockApi, MapLLMockApi) - or use modular methods like AddLLMockRest()
  • All examples below use the new recommended method names

Features

This package provides four independent features - use any combination you need:

1. REST API Mocking

  • Super Simple: AddLLMockApi() + MapLLMockApi("/api/mock") = instant mock API
  • Shape Control: Specify exact JSON structure via header, query param, or request body
  • All HTTP Methods: Supports GET, POST, PUT, DELETE, PATCH
  • Wildcard Routing: Any path under your chosen endpoint works

2. GraphQL API Mocking

  • Native GraphQL Support: POST to /api/mock/graphql with standard GraphQL queries
  • Query-Driven Shapes: The GraphQL query itself defines the response structure
  • Variables & Operations: Full support for variables, operation names, and fragments
  • Proper Error Handling: Returns GraphQL-formatted errors with data and errors fields

3. Server-Sent Events (SSE) Streaming

  • Progressive Streaming: SSE support with progressive JSON generation
  • Real-time Updates: Stream data token-by-token to clients
  • Works standalone: No REST API setup required

4. SignalR Real-Time Streaming

  • WebSocket Streaming: Continuous real-time mock data via SignalR
  • Multiple Contexts: Run multiple independent data streams simultaneously
  • Lifecycle Management: Start/stop contexts dynamically with management API
  • Works standalone: No REST API setup required

Common Features

  • Configurable: appsettings.json or inline configuration
  • Highly Variable Data: Each request/update generates completely different realistic data
  • NuGet Package: Easy to add to existing projects

Quick Start

Installation

dotnet add package mostlylucid.mockllmapi

Prerequisites

  1. Install .NET 8.0 SDK or later
  2. Install Ollama and pull a model:
    ollama pull llama3
    

Choosing an LLM Model

This package was developed and tested with llama3 (8B parameters), which provides excellent results for all features. However, it works with any Ollama-compatible model:

Model Size Speed Quality Best For
llama3 (default) 8B Fast Excellent General use, production
mistral:7b 7B Fast Excellent Alternative to llama3
phi3 3.8B Very Fast Good Quick prototyping
tinyllama 1.1B Ultra Fast Basic Resource-constrained environments
Model-Specific Configuration

For llama3 or mistral:7b (Recommended):

{
  "ModelName": "llama3",  // or "mistral:7b"
  "Temperature": 1.2      // High variety, diverse outputs
}

For smaller models (phi3, tinyllama):

{
  "ModelName": "tinyllama",
  "Temperature": 0.7      // Lower temperature for stability
}

Why Temperature Matters:

  • Larger models (7B+) can handle high temperatures (1.0-1.5) while maintaining valid JSON
  • Smaller models (<4B) need lower temperatures (0.6-0.8) to avoid:
    • Invalid JSON syntax (missing quotes, brackets)
    • Truncated responses with ellipsis ("...")
    • Hallucinated field names or structures
  • Lower temperature = more predictable output, less variety
  • Higher temperature = more creative output, more variety (but riskier for small models)
Installation
# Recommended (best quality)
ollama pull llama3

# Alternative options
ollama pull mistral:7b
ollama pull phi3
ollama pull tinyllama

Important Limitations:

  • Smaller models (tinyllama, phi3) work but may:
    • Generate simpler/less varied data
    • Struggle with complex GraphQL queries
    • Need more retry attempts
    • Work best with simple queries and small response sizes
  • All models can struggle with:
    • Very complex/deeply nested GraphQL queries (>5 levels deep)
    • Many fields per object (>10 fields)
    • Large array requests - Limit to 2-5 items for reliability
  • If seeing errors about truncated JSON ("...") or comments ("//"):
    • Lower temperature to 0.8 or below
    • Simplify your GraphQL query (fewer fields, less nesting)
    • Increase MaxRetryAttempts to 5 or more

Basic Usage

Program.cs:

using mostlylucid.mockllmapi;

var builder = WebApplication.CreateBuilder(args);

// Add LLMock API services (all protocols: REST, GraphQL, SSE)
builder.Services.AddLLMockApi(builder.Configuration);

var app = builder.Build();

// Map mock endpoints at /api/mock (includes REST, GraphQL, SSE)
app.MapLLMockApi("/api/mock");

app.Run();

appsettings.json:

{
  "mostlylucid.mockllmapi": {
    "BaseUrl": "http://localhost:11434/v1/",
    "ModelName": "llama3",
    "Temperature": 1.2
  }
}

That's it! Now all requests to /api/mock/** return intelligent mock data.

Configuration Options

{
  "mostlylucid.mockllmapi": {
    "BaseUrl": "http://localhost:11434/v1/",
    "ModelName": "llama3",
    "Temperature": 1.2,
    "TimeoutSeconds": 30,
    "EnableVerboseLogging": false,
    "CustomPromptTemplate": null,

    // Resilience Policies (enabled by default)
    "EnableRetryPolicy": true,
    "MaxRetryAttempts": 3,
    "RetryBaseDelaySeconds": 1.0,
    "EnableCircuitBreaker": true,
    "CircuitBreakerFailureThreshold": 5,
    "CircuitBreakerDurationSeconds": 30
  }
}

Resilience Policies

New in v1.2.0: Built-in Polly resilience policies protect your application from LLM service failures!

The package includes two resilience patterns enabled by default:

Exponential Backoff Retry

  • Automatically retries failed LLM requests with exponential delays (1s, 2s, 4s...)
  • Includes jitter to prevent thundering herd problems
  • Handles connection errors, timeouts, and non-success status codes
  • Default: 3 attempts with 1 second base delay

Circuit Breaker

  • Opens after consecutive failures to prevent cascading failures
  • Stays open for a configured duration before allowing test requests
  • Three states: Closed (normal), Open (rejecting), Half-Open (testing)
  • Default: Opens after 5 consecutive failures, stays open for 30 seconds

Configuration:

{
  "mostlylucid.mockllmapi": {
    // Enable/disable retry policy
    "EnableRetryPolicy": true,
    "MaxRetryAttempts": 3,
    "RetryBaseDelaySeconds": 1.0,  // Actual delays: 1s, 2s, 4s (exponential)

    // Enable/disable circuit breaker
    "EnableCircuitBreaker": true,
    "CircuitBreakerFailureThreshold": 5,  // Open after 5 consecutive failures
    "CircuitBreakerDurationSeconds": 30   // Stay open for 30 seconds
  }
}

Logging:

The resilience policies log all retry attempts and circuit breaker state changes:

[Warning] LLM request failed (attempt 2/4). Retrying in 2000ms. Error: Connection refused
[Error] Circuit breaker OPENED after 5 consecutive failures. All LLM requests will be rejected for 30 seconds
[Information] Circuit breaker CLOSED. LLM requests will be attempted normally

When to Adjust:

  • Slow LLM? Increase MaxRetryAttempts or RetryBaseDelaySeconds
  • Aggressive recovery? Reduce CircuitBreakerDurationSeconds
  • Many transient errors? Increase CircuitBreakerFailureThreshold
  • Disable for local testing? Set both EnableRetryPolicy and EnableCircuitBreaker to false

Via Code

builder.Services.Addmostlylucid.mockllmapi(options =>
{
    options.BaseUrl = "http://localhost:11434/v1/";
    options.ModelName = "mixtral";
    options.Temperature = 1.5;
    options.TimeoutSeconds = 60;
});

Custom Endpoint Patterns

// Default: /api/mock/** and /api/mock/stream/**
app.Mapmostlylucid.mockllmapi("/api/mock");

// Custom pattern
app.Mapmostlylucid.mockllmapi("/demo");
// Creates: /demo/** and /demo/stream/**

// Without streaming
app.Mapmostlylucid.mockllmapi("/api/mock", includeStreaming: false);

Usage Examples

Basic Request

curl http://localhost:5000/api/mock/users?limit=5

Returns realistic user data generated by the LLM.

With Shape Control

curl -X POST http://localhost:5000/api/mock/orders \
  -H "X-Response-Shape: {\"orderId\":\"string\",\"total\":0.0,\"items\":[{\"sku\":\"string\",\"qty\":0}]}" \
  -H "Content-Type: application/json" \
  -d '{"customerId":"cus_123"}'

LLM generates data matching your exact shape specification.

Streaming (SSE - Server-Sent Events)

SSE streaming is part of the REST API - just enable it when mapping endpoints:

// SSE streaming is automatically available at /api/mock/stream/**
app.MapLLMockApi("/api/mock", includeStreaming: true);

Usage:

curl -N http://localhost:5000/api/mock/stream/products?category=electronics \
  -H "Accept: text/event-stream"

Returns Server-Sent Events as JSON is generated token-by-token:

data: {"chunk":"{","done":false}
data: {"chunk":"\"id\"","done":false}
data: {"chunk":":","done":false}
data: {"chunk":"123","done":false}
...
data: {"content":"{\"id\":123,\"name\":\"Product\"}","done":true,"schema":"{...}"}

JavaScript Example:

const eventSource = new EventSource('/api/mock/stream/users?limit=5');

eventSource.onmessage = (event) => {
    const data = JSON.parse(event.data);

    if (data.done) {
        console.log('Complete:', data.content);
        eventSource.close();
    } else {
        console.log('Chunk:', data.chunk);
    }
};

With Shape Control:

curl -N "http://localhost:5000/api/mock/stream/orders?shape=%7B%22id%22%3A0%2C%22items%22%3A%5B%5D%7D"

The streaming endpoint supports all the same features as regular endpoints:

  • Shape control (query param, header, or body)
  • JSON Schema support
  • Custom prompts
  • All HTTP methods (GET, POST, PUT, DELETE, PATCH)

GraphQL API Mocking

New in v1.2.0: Native GraphQL support with query-driven mock data generation!

LLMock API includes built-in GraphQL endpoint support. Unlike REST endpoints where you specify shapes separately, GraphQL queries naturally define the exact structure they expect - the query IS the shape.

Quick Start with GraphQL

The GraphQL endpoint is automatically available when you map the LLMock API:

app.MapLLMockApi("/api/mock", includeGraphQL: true); // GraphQL enabled by default

This creates a GraphQL endpoint at /api/mock/graphql.

Basic Usage

Simple Query:

curl -X POST http://localhost:5000/api/mock/graphql \
  -H "Content-Type: application/json" \
  -d '{"query": "{ users { id name email role } }"}'

Response:

{
  "data": {
    "users": [
      { "id": 1, "name": "Alice Johnson", "email": "alice@example.com", "role": "admin" },
      { "id": 2, "name": "Bob Smith", "email": "bob@example.com", "role": "user" }
    ]
  }
}

With Variables

curl -X POST http://localhost:5000/api/mock/graphql \
  -H "Content-Type: application/json" \
  -d '{
    "query": "query GetUser($userId: ID!) { user(id: $userId) { id name email } }",
    "variables": { "userId": "12345" },
    "operationName": "GetUser"
  }'

Nested Queries

GraphQL's power shines with nested data:

{
  company {
    name
    employees {
      id
      firstName
      lastName
      department {
        name
        location
      }
      projects {
        id
        title
        status
        milestones {
          title
          dueDate
          completed
        }
      }
    }
  }
}

The LLM generates realistic data matching your exact query structure - including all nested relationships.

JavaScript Client Example

async function fetchGraphQL(query, variables = {}) {
    const response = await fetch('/api/mock/graphql', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ query, variables })
    });

    const result = await response.json();

    if (result.errors) {
        console.error('GraphQL errors:', result.errors);
    }

    return result.data;
}

// Usage
const data = await fetchGraphQL(`
    query GetProducts($category: String) {
        products(category: $category) {
            id
            name
            price
            inStock
            reviews {
                rating
                comment
            }
        }
    }
`, { category: 'electronics' });

Error Handling

GraphQL errors are returned in standard format:

{
  "data": null,
  "errors": [
    {
      "message": "Invalid GraphQL request format",
      "extensions": {
        "code": "INTERNAL_SERVER_ERROR"
      }
    }
  ]
}

How It Works

  1. Parse Request: Extract GraphQL query, variables, and operation name
  2. Build Prompt: Send the query structure to the LLM with instructions to generate matching data
  3. Generate Data: LLM creates realistic data that exactly matches the query fields
  4. Wrap Response: Returns data in GraphQL format: { "data": {...} }

Key Advantages

  • No Shape Specification Needed: The GraphQL query defines the structure
  • Type Safety: Queries explicitly request fields by name
  • Nested Relationships: Natural support for complex, nested data structures
  • Standard Format: Works with any GraphQL client library
  • Realistic Data: LLM generates contextually appropriate data for each field

Testing GraphQL

Use the included LLMApi.http file which contains 5 ready-to-use GraphQL examples:

  • Simple user query
  • Query with variables
  • Nested fields with arrays
  • E-commerce product catalog
  • Complex organizational data

See the GraphQL examples in LLMApi.http for complete working examples.

GraphQL Configuration

Token Limits and Model Selection

GraphQL responses can become large with deeply nested queries. To prevent JSON truncation errors, configure the GraphQLMaxTokens option:

{
  "MockLlmApi": {
    "GraphQLMaxTokens": 300  // Recommended: 200-300 for reliability
  }
}

Token Limit Guidelines:

Model Recommended Max Tokens Notes
llama3 300-500 Best balance of speed and complexity
mistral:7b 300-500 Handles nested structures well
phi3 200-300 Keep queries simple
tinyllama 150-200 Use shallow queries only

Why Lower Is Better:

  • The prompt prioritizes correctness over length
  • Lower limits force the LLM to generate simpler, complete JSON
  • Higher limits risk truncated responses (invalid JSON)
  • If you see "Invalid JSON from LLM" errors, reduce GraphQLMaxTokens

For Complex Nested Queries:

  1. Use larger models (llama3, mistral:7b)
  2. Increase GraphQLMaxTokens to 500-1000
  3. Keep array sizes small (2 items max by default)
  4. Monitor logs for truncation warnings

Example configuration for complex queries:

{
  "MockLlmApi": {
    "ModelName": "llama3",           // Larger model
    "GraphQLMaxTokens": 800,          // Higher limit for nested data
    "Temperature": 1.2
  }
}

SignalR Real-Time Data Streaming

LLMock API includes optional SignalR support for continuous, real-time mock data generation. This is perfect for:

  • Dashboard prototypes requiring live updates
  • Testing real-time UI components
  • Demos with constantly changing data
  • WebSocket/SignalR integration testing

Quick Start with SignalR

SignalR works independently - you don't need the REST API endpoints to use SignalR streaming.

1. Minimal SignalR-only setup:

using mostlylucid.mockllmapi;

var builder = WebApplication.CreateBuilder(args);

// Add SignalR services (no REST API needed!)
builder.Services.AddLLMockSignalR(builder.Configuration);

var app = builder.Build();

app.UseRouting();

// Map SignalR hub and management endpoints
app.MapLLMockSignalR("/hub/mock", "/api/mock");

app.Run();

Optional: Add REST API too

If you also want the REST API endpoints, add these lines:

// Add core LLMock API services (optional)
builder.Services.AddLLMockApi(builder.Configuration);

// Map REST API endpoints (optional)
app.MapLLMockApi("/api/mock", includeStreaming: true);

2. Configure in appsettings.json:

{
  "MockLlmApi": {
    "BaseUrl": "http://localhost:11434/v1/",
    "ModelName": "llama3",
    "Temperature": 1.2,

    "SignalRPushIntervalMs": 5000,
    "HubContexts": [
      {
        "Name": "weather",
        "Description": "Weather data with temperature, condition, humidity, and wind speed"
      },
      {
        "Name": "stocks",
        "Description": "Stock market data with symbol, current price, change percentage, and trading volume"
      }
    ]
  }
}

3. Connect from client:

// Using @microsoft/signalr
const connection = new signalR.HubConnectionBuilder()
    .withUrl("/hub/mock")
    .withAutomaticReconnect()
    .build();

// Subscribe to a context
connection.on("DataUpdate", (message) => {
    console.log(`${message.context}:`, message.data);
    // message.data contains generated JSON matching the shape
    // message.timestamp is unix timestamp in ms
});

await connection.start();
await connection.invoke("SubscribeToContext", "weather");

Hub Context Configuration

Each hub context simulates a complete API request and generates data continuously:

{
  "Name": "orders",                 // Context name (SignalR group identifier)
  "Description": "Order data..."    // Plain English description (LLM generates JSON from this)
  // Optional:
  // "IsActive": true,              // Start in active/stopped state (default: true)
  // "Shape": "{...}",              // Explicit JSON shape or JSON Schema
  // "IsJsonSchema": false           // Auto-detected if not specified
}

Recommended: Use Plain English Descriptions

Let the LLM automatically generate appropriate JSON structures:

{
  "Name": "sensors",
  "Description": "IoT sensor data with device ID, temperature, humidity, battery level, and last reading timestamp"
}

The LLM automatically generates an appropriate JSON schema from your description - no manual Shape required!

Dynamic Context Creation API

Create and manage SignalR contexts at runtime using the management API:

Create Context
POST /api/mock/contexts
Content-Type: application/json

{
  "name": "crypto",
  "description": "Cryptocurrency prices with symbol, USD price, 24h change percentage, and market cap"
}

Response:

{
  "message": "Context 'crypto' registered successfully",
  "context": {
    "name": "crypto",
    "description": "Cryptocurrency prices...",
    "method": "GET",
    "path": "/crypto",
    "shape": "{...generated JSON schema...}",
    "isJsonSchema": true
  }
}
List All Contexts
GET /api/mock/contexts

Response:

{
  "contexts": [
    {
      "name": "weather",
      "description": "Realistic weather data with temperature, conditions, humidity, and wind speed for a single location",
      "method": "GET",
      "path": "/weather/current",
      "shape": "{...}"
    },
    {
      "name": "crypto",
      "description": "Cryptocurrency prices...",
      "shape": "{...}"
    }
  ],
  "count": 2
}

Note: The list endpoint merges contexts configured in appsettings.json with any dynamically created contexts at runtime. Descriptions from appsettings are included even if those contexts have not yet been dynamically registered.

Get Specific Context
GET /api/mock/contexts/weather

Response:

{
  "name": "weather",
  "method": "GET",
  "path": "/weather/current",
  "shape": "{\"temperature\":0,\"condition\":\"string\"}",
  "isJsonSchema": false
}
Delete Context
DELETE /api/mock/contexts/crypto

Response:

{
  "message": "Context 'crypto' deleted successfully"
}
Start Context (Resume Data Generation)
POST /api/mock/contexts/crypto/start

Response:

{
  "message": "Context 'crypto' started successfully"
}

Starts generating data for a stopped context without affecting connected clients.

Stop Context (Pause Data Generation)
POST /api/mock/contexts/crypto/stop

Response:

{
  "message": "Context 'crypto' stopped successfully"
}

Stops generating new data but keeps the context registered. Clients remain connected but receive no updates until started again.

Complete Client Example

<!DOCTYPE html>
<html>
<head>
    <script src="https://cdn.jsdelivr.net/npm/@microsoft/signalr@8.0.0/dist/browser/signalr.min.js"></script>
</head>
<body>
    <h1>Live Weather Data</h1>
    <div id="weather-data"></div>

    <script>
        const connection = new signalR.HubConnectionBuilder()
            .withUrl("/hub/mock")
            .withAutomaticReconnect()
            .build();

        connection.on("DataUpdate", (message) => {
            if (message.context === "weather") {
                const weatherDiv = document.getElementById("weather-data");
                weatherDiv.innerHTML = `
                    <h2>Current Weather</h2>
                    <p>Temperature: ${message.data.temperature}°F</p>
                    <p>Condition: ${message.data.condition}</p>
                    <p>Humidity: ${message.data.humidity}%</p>
                    <p>Updated: ${new Date(message.timestamp).toLocaleTimeString()}</p>
                `;
            }
        });

        connection.start()
            .then(() => {
                console.log("Connected to SignalR hub");
                return connection.invoke("SubscribeToContext", "weather");
            })
            .then(() => {
                console.log("Subscribed to weather context");
            })
            .catch(err => console.error(err));
    </script>
</body>
</html>

Dynamic Context Creation from UI

async function createDynamicContext() {
    // Create the context
    const response = await fetch("/api/mock/contexts", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
            name: "stocks",
            description: "Stock market data with ticker symbol, current price, daily change percentage, and trading volume"
        })
    });

    const result = await response.json();
    console.log("Context created:", result.context);

    // Subscribe to receive data
    await connection.invoke("SubscribeToContext", "stocks");
    console.log("Now receiving live stock data!");
}

SignalR Hub Methods

The MockLlmHub supports the following methods:

SubscribeToContext(string context)

  • Subscribes the client to receive data updates for a specific context
  • Client will receive DataUpdate events with generated data
  • New in v1.1.0: Automatically increments the context's connection count

UnsubscribeFromContext(string context)

  • Unsubscribes the client from a context
  • Client will no longer receive updates for that context
  • New in v1.1.0: Automatically decrements the context's connection count

Events received by client:

DataUpdate - Contains generated mock data

{
    context: "weather",       // Context name
    method: "GET",            // Simulated HTTP method
    path: "/weather/current", // Simulated path
    timestamp: 1699564820000, // Unix timestamp (ms)
    data: {                   // Generated JSON matching the shape
        temperature: 72,
        condition: "Sunny",
        humidity: 45,
        windSpeed: 8
    }
}

Subscribed - Confirmation of subscription

{
    context: "weather",
    message: "Subscribed to weather"
}

Unsubscribed - Confirmation of unsubscription

{
    context: "weather",
    message: "Unsubscribed from weather"
}

Configuration Options

{
  "MockLlmApi": {
    "SignalRPushIntervalMs": 5000,  // Interval between data pushes (ms)
    "HubContexts": [...]             // Array of pre-configured contexts
  }
}

JSON Schema Support

Hub contexts support both simple JSON shapes and full JSON Schema:

Simple Shape:

{
  "Name": "users",
  "Shape": "{\"id\":0,\"name\":\"string\",\"email\":\"string\"}"
}

JSON Schema:

{
  "Name": "products",
  "Shape": "{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"number\"},\"name\":{\"type\":\"string\"},\"price\":{\"type\":\"number\"}},\"required\":[\"id\",\"name\",\"price\"]}",
  "IsJsonSchema": true
}

The system auto-detects JSON Schema by looking for $schema, type, or properties fields.

Architecture

graph TD
    Client[SignalR Client] -->|Subscribe| Hub[MockLlmHub]
    Hub -->|Join Group| Group[SignalR Group]
    BG[Background Service] -->|Generate Data| LLM[Ollama LLM]
    LLM -->|JSON Response| BG
    BG -->|Push Data| Group
    Group -->|DataUpdate Event| Client
    API[Management API] -->|CRUD| Manager[DynamicHubContextManager]
    Manager -->|Register/Unregister| BG

Components:

  • MockLlmHub: SignalR hub handling client connections and subscriptions
  • MockDataBackgroundService: Hosted service continuously generating data
  • DynamicHubContextManager: Thread-safe manager for runtime context registration
  • HubContextConfig: Configuration model for each data context

Use Cases

1. Dashboard Prototyping

// Subscribe to multiple data sources
await connection.invoke("SubscribeToContext", "sales");
await connection.invoke("SubscribeToContext", "traffic");
await connection.invoke("SubscribeToContext", "alerts");
// Now receiving live updates for all three!

2. IoT Simulation

{
  "Name": "sensors",
  "Description": "IoT temperature sensors with device ID, current temperature, battery percentage, and signal strength",
  "Path": "/iot/sensors"
}

3. Financial Data

{
  "Name": "trading",
  "Description": "Real-time stock trades with timestamp, symbol, price, volume, and buyer/seller IDs",
  "Path": "/trading/live"
}

4. Gaming Leaderboard

{
  "Name": "leaderboard",
  "Description": "Gaming leaderboard with player name, score, rank, level, and country",
  "Path": "/game/leaderboard"
}

Context Lifecycle Management

New in v1.1.0: Full lifecycle control over SignalR contexts with real-time status tracking!

Each context has the following properties:

  • IsActive: Whether the context is generating data (default: true)
  • ConnectionCount: Number of currently connected clients (auto-tracked)

Contexts can be in two states:

  • Active: Background service generates new data every SignalRPushIntervalMs (default: 5 seconds)
  • Stopped: Context remains registered, clients stay connected, but no new data is generated

Key Features:

  • Start/stop contexts without disconnecting clients
  • Real-time connection count tracking
  • Status badges showing active/stopped state
  • Duplicate context prevention
  • Contexts from appsettings.json automatically appear in the UI

Response Caching for SignalR: New in v1.1.0: Intelligent caching reduces LLM load and improves consistency!

  • Batch Prefilling: Generates 5-10 responses at once (configurable via MaxCachePerKey)
  • Per-Context Queues: Each context maintains its own cache of pre-generated responses
  • Background Refilling: Automatically refills cache when it drops below 50% capacity
  • Smart Scheduling: Minimizes LLM calls while maintaining data freshness

This significantly reduces LLM load for high-frequency contexts, especially with multiple clients.

Example Workflow:

# Create a context
POST /api/mock/contexts
{ "name": "metrics", "description": "Server metrics" }

# Stop data generation (clients remain connected)
POST /api/mock/contexts/metrics/stop

# Resume data generation
POST /api/mock/contexts/metrics/start

# Remove context entirely
DELETE /api/mock/contexts/metrics

Demo Applications

The package includes two complete demo applications with interactive web interfaces featuring full context management:

SignalR Demo (/) — Real-Time Data Streaming with Management UI

New in v1.1.0: Enhanced 3-column layout with full context lifecycle management!

Features:

  • Create Context Panel: Enter plain English descriptions, system generates appropriate JSON schema
  • Active Contexts Panel:
    • Live list of all contexts with status badges (Active/Stopped)
    • Automatically displays contexts from appsettings.json on startup
    • Real-time connection count for each context
    • Per-context controls: Connect, Disconnect, Start, Stop, Delete
    • Auto-refresh on changes
  • Live Data Panel: Real-time updates from subscribed contexts (every 5 seconds)
    • JSON Syntax Highlighting: Beautiful dark-themed display with color-coded elements
    • Keys (red), strings (green), numbers (orange), booleans (cyan), null (purple)
    • Clean, readable format with proper timestamp display
    • No external dependencies - lightweight built-in highlighter

Quick-Start Examples: One-click buttons for 5 pre-configured scenarios:

  • IoT Sensors: Temperature sensors with device ID, readings, battery percentage
  • Stock Market: Real-time prices with ticker, price, change percentage, volume
  • E-commerce Orders: Orders with ID, customer, items array, status, total
  • Server Metrics: Monitoring data with hostname, CPU, memory, disk, connections
  • Gaming Leaderboard: Player stats with name, score, rank, level, country

Perfect for: Dashboards, live monitoring, IoT simulations, real-time feeds, prototyping

SSE Streaming Demo (/Streaming) — Progressive JSON Generation

New in v1.1.0: Quick-start example buttons for instant streaming!

Features:

  • Configure HTTP method, path, and optional JSON shape
  • Watch JSON being generated token-by-token in real-time
  • Live statistics: chunk count, duration, data size
  • Connection status indicators

Quick-Start Examples: One-click buttons for 4 streaming scenarios:

  • User List: Array of user objects with ID, name, email
  • Product Catalog: Product inventory with SKU, name, price, stock status
  • Order Details: Nested orders with customer info and items array
  • Weather Data: Current conditions with temperature, humidity, wind speed

Perfect for: Observing LLM generation, debugging shapes, understanding streaming behavior, testing SSE

Run the demos:

cd LLMApi
dotnet run

Navigate to:

  • http://localhost:5116 - SignalR real-time data streaming with management UI
  • http://localhost:5116/Streaming - SSE progressive generation

Both demos include:

  • Comprehensive inline documentation
  • Interactive quick-start examples
  • Code snippets for integration
  • Real-time status indicators
  • Full context lifecycle controls

Advanced Features

Response Schema Echo (Shape) Export

You can optionally have the middleware echo back the JSON shape/schema that was used to generate the mock response.

Configuration:

  • Option: IncludeShapeInResponse (bool, default false)
  • Per-request override: add query parameter includeSchema=true (or 1)
  • Header emitted: X-Response-Schema (only when a shape was provided and size ≤ 4000 chars)
  • Streaming: the final SSE event includes a schema field when enabled

Examples:

  • Enable globally (appsettings.json):
{
  "mostlylucid.mockllmapi": {
    "IncludeShapeInResponse": true
  }
}
  • Enable per request (overrides config):
curl "http://localhost:5000/api/mock/users?shape=%7B%22id%22%3A0%2C%22name%22%3A%22string%22%7D&includeSchema=true"

Response includes header:

X-Response-Schema: {"id":0,"name":"string"}
  • Streaming: final event contains schema field when enabled
...
data: {"content":"{full json}","done":true,"schema":{"id":0,"name":"string"}}

Notes:

  • If no shape was provided (via query/header/body), the header is not added
  • Very large shapes (> 4000 chars) are not added to the header to avoid transport issues, but normal response continues

Use cases:

  • Client-side TypeScript type generation
  • API documentation and schema validation
  • Debugging shape parsing
  • Runtime validation

Custom Prompt Templates

Override the default prompts with your own:

{
  "mostlylucid.mockllmapi": {
    "CustomPromptTemplate": "Generate mock data for {method} {path}. Body: {body}. Use seed: {randomSeed}"
  }
}

Available placeholders:

  • {method} - HTTP method (GET, POST, etc.)
  • {path} - Full request path with query string
  • {body} - Request body
  • {randomSeed} - Generated random seed (GUID)
  • {timestamp} - Unix timestamp
  • {shape} - Shape specification (if provided)

Multiple Instances

Mount multiple mock APIs with different configurations:

// Development data with high randomness
builder.Services.Addmostlylucid.mockllmapi("Dev", options =>
{
    options.Temperature = 1.5;
    options.ModelName = "llama3";
});

// Stable test data
builder.Services.Addmostlylucid.mockllmapi("Test", options =>
{
    options.Temperature = 0.3;
    options.ModelName = "llama3";
});

app.Mapmostlylucid.mockllmapi("/api/dev");
app.Mapmostlylucid.mockllmapi("/api/test");

Shape Specification

Three ways to control response structure:

  1. Header (recommended): X-Response-Shape: {"field":"type"}
  2. Query param: ?shape=%7B%22field%22%3A%22type%22%7D (URL-encoded JSON)
  3. Body field: {"shape": {...}, "actualData": ...}

Caching Multiple Responses via Shape

You can instruct the middleware to pre-generate and cache multiple response variants for a specific request/shape by adding a special field inside the shape object: "$cache": N.

  • The cache key is derived from HTTP method + path (including query) + the sanitized shape (with $cache removed) using System.IO.Hashing XXHash64.
  • Up to N responses are prefetched from the LLM and stored in-memory (capped by MaxCachePerKey in options; default 5).
  • Subsequent non-streaming requests for the same key are served from a depleting queue; when exhausted, a fresh batch of N is prefetched automatically.
  • Streaming endpoints are not cached.

Examples

  • Header shape: X-Response-Shape: {"$cache":3,"orderId":"string","status":"string","items":[{"sku":"string","qty":0}]}

  • Body shape: { "shape": { "$cache": 5, "invoiceId": "string", "customer": { "id": "string", "name": "string" }, "items": [ { "sku": "string", "qty": 0, "price": 0.0 } ], "total": 0.0 } }

  • Query param (URL-encoded): ?shape=%7B%22%24cache%22%3A2%2C%22users%22%3A%5B%7B%22id%22%3A0%2C%22name%22%3A%22string%22%7D%5D%7D

Configuration

  • MaxCachePerKey (int, default 5): caps the number requested by "$cache" per key.

Notes

  • The "$cache" hint is removed from the shape before it is sent to the LLM.
  • If "$cache" is omitted or 0, the request behaves as before (no caching/warmup).
  • Cached variants are kept in-memory for the app lifetime; restart clears the cache.

Testing

Use the included LLMApi.http file with:

  • Visual Studio / Rider HTTP client
  • VS Code REST Client extension
  • Any HTTP client

Testing

The project includes comprehensive unit tests:

# Run all tests
dotnet test

# Run with detailed output
dotnet test --verbosity detailed

Test Coverage:

  • Body reading (empty, JSON content)
  • Shape extraction (query param, header, body field, precedence)
  • Prompt generation (randomness, shape inclusion, streaming modes)
  • Request building (temperature, model, messages)
  • Edge cases (invalid JSON, missing data)

Architecture

graph LR
    Client[Client] -->|HTTP Request| API[LLMApi<br/>Minimal API]
    API -->|Chat Completion| Ollama[Ollama API<br/>localhost:11434]
    Ollama -->|Inference| Model[llama3 Model]
    Model -->|Response| Ollama
    Ollama -->|JSON/Stream| API
    API -->|JSON/SSE| Client

    API -.->|uses| Helper[AutoApiHelper]

    style API fill:#4CAF50
    style Helper fill:#2196F3
    style Model fill:#FF9800

Request Flow

sequenceDiagram
    participant C as Client
    participant A as LLMApi
    participant H as AutoApiHelper
    participant O as Ollama
    participant M as llama3

    C->>A: GET/POST/PUT/DELETE /api/auto/**
    A->>H: Extract context (method, path, body, shape)
    H->>H: Generate random seed + timestamp
    H->>H: Build prompt with randomness
    H-->>A: Prompt + temperature=1.2
    A->>O: POST /v1/chat/completions
    O->>M: Run inference
    M-->>O: Generated JSON
    O-->>A: Response
    A-->>C: JSON Response

Shape Control Flow

flowchart TD
    Start[Request Arrives] --> CheckQuery{Shape in<br/>Query Param?}
    CheckQuery -->|Yes| UseQuery[Use Query Shape]
    CheckQuery -->|No| CheckHeader{Shape in<br/>Header?}
    CheckHeader -->|Yes| UseHeader[Use Header Shape]
    CheckHeader -->|No| CheckBody{Shape in<br/>Body Field?}
    CheckBody -->|Yes| UseBody[Use Body Shape]
    CheckBody -->|No| NoShape[No Shape Constraint]

    UseQuery --> BuildPrompt[Build Prompt]
    UseHeader --> BuildPrompt
    UseBody --> BuildPrompt
    NoShape --> BuildPrompt

    BuildPrompt --> AddRandom[Add Random Seed<br/>+ Timestamp]
    AddRandom --> SendLLM[Send to LLM]

    style UseQuery fill:#4CAF50
    style UseHeader fill:#4CAF50
    style UseBody fill:#4CAF50
    style NoShape fill:#FFC107

Projects:

  • mostlylucid.mockllmapi: NuGet package library
  • LLMApi: Demo application
  • LLMApi.Tests: xUnit test suite (13 tests)

Why Use mostlylucid.mockllmapi?

  • Rapid Prototyping: Frontend development without waiting for backend
  • Demos: Show realistic data flows without hardcoded fixtures
  • Testing: Generate varied test data for edge cases
  • API Design: Experiment with response shapes before implementing
  • Learning: Example of LLM integration in .NET minimal APIs
  • Zero Maintenance: No database, no state, no mock data files to maintain

Building the NuGet Package

cd mostlylucid.mockllmapi
dotnet pack -c Release

Package will be in bin/Release/mostlylucid.mockllmapi.{version}.nupkg

Contributing

This is a sample project demonstrating LLM-powered mock APIs. Feel free to fork and customize!

License

This is free and unencumbered software released into the public domain. See LICENSE for details or visit unlicense.org.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.2.1 166 12/5/2025
2.1.0 232 11/6/2025
2.1.0-beta1 173 11/6/2025
2.0.0-rc3 170 11/6/2025
2.0.0-rc2 172 11/6/2025
2.0.0-rc1 176 11/6/2025
1.7.2 185 11/6/2025
1.6.1 181 11/5/2025
1.5.0 174 11/5/2025
1.2.1 176 11/4/2025
1.2.0 175 11/4/2025
1.1.4 179 11/4/2025
1.1.3 175 11/4/2025
1.0.6 181 11/2/2025
1.0.5 177 11/2/2025
1.0.4 181 11/2/2025
1.0.2 179 11/2/2025
1.0.1 183 11/2/2025
0.0.3 181 11/2/2025

Latest: v1.2.0 — Native GraphQL support! Query-driven mock data generation with variables, nested fields, and proper error handling. Shared JSON extraction utility. See RELEASE_NOTES.md for full details.