Skip to content

Mock LLM Responder — C# source

Generate deterministic mock LLM API responses - chat completion JSON, SSE event streams with chunk timing, and a replay curl - for testing clients without an API key.

This is the C# implementation — the same logic the interactive tool runs, in a shareable, citable form.

// mock-llm-responder — C# port: deterministic mock LLM responses (seeded PRNG + token math).
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

static class MockLlmResponder
{
    static readonly string[] PoemWords = {
        "cosmos", "nebula", "quantum", "signal", "photon", "drift", "orbit", "vector",
        "cipher", "lumen", "aurora", "echo", "helix", "nova", "pulse", "tide",
        "vertex", "zenith", "quasar", "ion", "halo", "flux", "prism", "comet",
    };
    // FNV-1a 32-bit hash — turns the spec into a deterministic seed / id.
    static uint HashString(string s)
    {
        uint h = 0x811c9dc5;
        foreach (char c in s) { h ^= c; h *= 0x01000193; }
        return h;
    }
    // mulberry32 — tiny seeded PRNG; same seed, same sequence, forever.
    static Func<double> Mulberry32(uint seed)
    {
        return () => {
            seed += 0x6d2b79f5;
            uint t = (seed ^ (seed >> 15)) * (seed | 1);
            t = (t + ((t ^ (t >> 7)) * (t | 61))) ^ t;
            return (t ^ (t >> 14)) / 4294967296.0;
        };
    }
    // ~4 chars per token, floor of 1 — deterministic, no tokenizer needed.
    static int TokenCount(string text) =>
        text.Length == 0 ? 0 : Math.Max(1, (text.Length + 3) / 4);
    // Cut text so it fits in maxTokens tokens (4 chars each).
    static string TruncateToTokens(string text, int maxTokens)
    {
        if (TokenCount(text) <= maxTokens) return text;
        return text[..(maxTokens * 4)].TrimEnd();
    }
    // One poem line of 5-7 vocabulary words.
    static string MakeLine(Func<double> rng)
    {
        int n = 5 + (int)(rng() * 3);
        var words = new string[n];
        for (int i = 0; i < n; i++) words[i] = PoemWords[(int)(rng() * PoemWords.Length)];
        return string.Join(" ", words);
    }
    // Poem-ish lorem, grown line by line until the token budget is full.
    static string BuildPoem(uint seed, int maxTokens)
    {
        var rng = Mulberry32(seed);
        string text = "";
        for (;;)
        {
            string candidate = text.Length == 0 ? MakeLine(rng) : text + "\n" + MakeLine(rng);
            if (text.Length != 0 && TokenCount(candidate) > maxTokens) break;
            text = candidate;
        }
        return TruncateToTokens(text, maxTokens);
    }
    // Split content into stream chunks. Chunks reassemble to the exact content.
    static List<string> ChunkContent(string content, int perChunk)
    {
        var words = Regex.Matches(content, @"\S+\s*");
        var chunks = new List<string>();
        for (int i = 0; i < words.Count; i += perChunk)
            chunks.Add(string.Concat(Enumerable.Range(i, Math.Min(perChunk, words.Count - i))
                .Select(j => words[j].Value)));
        return chunks;
    }
    static void Main()
    {
        const string spec = "streamed-lorem|mock-gpt-4o-mini|24";
        string poem = BuildPoem(HashString(spec + "|poem"), 24);
        Console.WriteLine($"id=chatcmpl-mock-{HashString(spec + "|id"):x8}");
        Console.WriteLine(poem);
        Console.WriteLine($"tokens={TokenCount(poem)} chunks={ChunkContent(poem, 4).Count}");
    }
}

Also available in 13 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →