Delibera:基于 C#/.NET 10 的多模型审议框架,新增工具调用与成本门控
techbuzzz/Delibera
Delibera 是一个 C#/.NET 10 框架,让多个 LLM 在结构化轮次中辩论、互相批评,再由 Chairman 综合出最终裁决,并配有基于 Qdrant 或 PostgreSQL/pgvector 的 Knowledge Keeper(RAG)。
⚖️ Thoughtful AI Decisions
Collective decision making through structured AI deliberation — with RAG, pgvector, Knowledge Keeper, 🛠️ Operator (MCP tools), Chairman, 🔥 Context Compression, ✂️ AutoChunking, 💉 Dependency Injection & 📋 Execution Logging
📖 Overview
Delibera is a C# / .NET 10 framework that orchestrates multi-model deliberations between LLMs. Multiple AI models reason through a question across structured rounds, critique each other's answers, and a Chairman weighs the arguments to synthesise a balanced final verdict — enriched by a Knowledge Keeper backed by Qdrant or PostgreSQL/pgvector (RAG), with intelligent context compression to minimise token usage.
The name comes from deliberation — the careful weighing of evidence and viewpoints before reaching a decision. Delibera brings that discipline to AI, helping teams reach thoughtful, well-reasoned outcomes rather than single-model guesses.
✨ Key Features
| Feature | Description |
|---|---|
| 🏛️ Multi-Model Councils | Orchestrate any number of LLM participants across structured debate rounds |
| ⚖️ Chairman Synthesis | A dedicated moderator opens, regulates, and synthesises the final verdict |
| 📚 Knowledge Keeper (RAG) | Per-round semantic retrieval with structured, cited responses |
| 🛠️ Operator (MCP Tools) | A micro-agent that delegates tasks to MCP servers (web, files, Marp, Notion, …) on demand during the debate |
| 🐘 Qdrant + pgvector | Pluggable vector stores — use a dedicated DB or your existing PostgreSQL |
| 🗜️ Context Compression | 4 strategies (Semantic, Deduplication, Summarization, Hybrid) applied to every round prompt — measured at 11.7–12.1% prompt-token savings |
| ✂️ AutoChunking | Progressive disclosure of large documents across rounds — respects model context windows |
| 🌐 Distributed Debates | IDebateOrchestrator with local and Redis backends — parallelize debates across machines |
| 💾 Result Caching | IDebateCache with in-memory, file, and Redis backends — skip re-running identical debates |
| 🖥️ Delibera.Server | ASP.NET Core 10 Minimal API with REST + SSE streaming for debates over HTTP — now published as its own NuGet package |
| 🔧 Tool Use (Function Calling) | IToolProvider + AIFunction — members call tools mid-debate. Native function calling where the provider supports it, [[TOOL: …]] markers otherwise. Ships filesystem, HTTP and MCP providers |
| 💸 Cost Gates & Rate Limits | Hard spend ceilings and per-model call limits. A breach returns a degraded result carrying what was spent — never a thrown exception |
| 🔀 Debate Diff | baseline.Diff(candidate) — word-level Markdown/HTML comparison of two runs, matched by round number and member name |
| ⌨️ delibera CLI | run, resume, compare, benchmark from the shell |
| 📡 gRPC Transport | Server-streaming debate events over gRPC, layered on IDebateOrchestrator so caching and distribution behave identically |
| 💉 Dependency Injection | AddDelibera() extension for IServiceCollection with full options binding |
| 📋 Execution Logging | ExecutionLog model with LogLevel — Chairman, KK, Compression & participant events |
| 📁 Separate File Output | Export result.md, statistics.md, and logs.md independently |
| 🔌 Interface-First | Clean abstractions for providers, factories, builders and executors |
| 🤝 Microsoft.Extensions.AI | First-class support for IChatClient / IEmbeddingGenerator — plug in OpenAI, Azure OpenAI, Ollama or any compatible backend, with middleware (function calling, logging) |
| 🛑 Cooperative Cancellation | Every public async method accepts a CancellationToken; a host shutdown or user cancel aborts the debate mid-flight (rounds, LLM calls, MCP tools, RAG, file saves) |
| 🧱 Modern C# 15 (preview) | Built on .NET 10 with LangVersion=preview, file-scoped namespaces, records, span/SIMD hot paths |
🆕 What's new in v10.5.1
Closes all six open issues and adds one fix that only a live database could surface. No breaking changes — everything is opt-in and no public interface gained a member. Full account: docs/WhatsNew-v10.5.1.md.
| Feature | Description |
|---|---|
| 🔧 Tool Use | IToolProvider + AIFunction. CouncilBuilder.WithTools(...). Native function calling where the provider wraps a real IChatClient; the [[TOOL: name {json}]] marker protocol elsewhere — because a string-only adapter cannot carry structured tool traffic. Loop is owned by FunctionInvokingChatClient, bounded by WithMaxToolIterations(n). Ships FileSystemToolProvider (rooted, traversal-rejecting), HttpToolProvider (host allow-list, refuses plain HTTP), McpToolProvider. Calls land on DebateRound.ToolCalls / DebateResult.ToolCalls. |
| 💸 Cost Gates & Rate Limits | WithCostLimit(decimal, CostLimitBehavior) and WithTokenBudget(long); WithRateLimit(n, window). DebateResult.CostEstimate reports spend per member with an IsEstimate flag. A denial returns a degraded result carrying the spend, never an exception — a ceiling that throws leaves you with no record of what was spent. Build() throws if a money ceiling is configured without a price registry, because that combination can never be enforced. |
| 🔀 Debate Diff | baseline.Diff(candidate) → DebateDiff with word-level **added** / ~~removed~~ diffs, verdict similarity, and explicit lists of rounds or members present on only one side. ToMarkdown(), ToHtml(), SaveToHtmlAsync(path). Rounds match by number and members by name, so a 4-round run against a 3-round run reports the missing round instead of shifting every later comparison. |
| ⌨️ delibera CLI | delibera run | resume | compare | benchmark on System.CommandLine. compare diffs two saved results; benchmark reports the spread across runs rather than a single number. |
| 📡 gRPC | Delibera.Grpc + Delibera.Grpc.Client, layered on top of IDebateOrchestrator so caching and distributed execution behave identically. Server streaming ends with exactly one terminal event. |
| 📦 NuGet GA | Delibera.Core, Delibera.Server and Delibera.Redis are all publishable now. The workflow fails if any package ships without its README or icon. |
Two defects fixed here were invisible to the unit tests, because the tests used fakes:
| Fix | Description |
|---|---|
| 🐘 pgvector could not write or search | AddWithValue(new Vector(...)) boxes the value, so Npgsql had no type to map and the first upsert threw InvalidCastException. Declaring NpgsqlDbType.Unknown fails too — UseVector() only registers a mapping for the Npgsql version Pgvector was built against. The embedding now travels as pgvector's text form with a ::vector cast, understood by any version. Qdrant was unaffected. |
| 🪞 Idempotent vector indexing | BaseRagProvider assigned Guid.NewGuid() per chunk, and both stores use the id as an upsert key — so re-indexing appended. Three runs over 24 chunks left 72 points. The id is now derived from the chunk's identity. Verified on live Qdrant and pgvector: three runs of a 6-chunk document leave 6. |
🆕 What's new in v10.5.0
Five defects found by measuring real deliberations against Ollama Cloud. Two of them made headline features silently non-functional — full evidence in docs/performance-measurements.md.
| Fix | Description |
|---|---|
| 🗜️ Compression now runs | CompressTextAsync was public API that nothing in the pipeline called; TokenStats stayed null and ten runs reported 0.00% saved at 0 ms overhead. The compressor is now applied to each round prompt and every pass is logged. Measured 11.7–12.1% — still well under the 30–70% previously claimed, which the docs now say. |
| 🌐 Ollama Cloud works at all | The provider defaulted to num_predict: -1, which the endpoint rejects outright (max_tokens must be positive). Every request failed before generating a token. A non-positive cap is now omitted from the request. |
| ⚠️ Degradation is visible | A failed member used to be substituted into the transcript as "[ERROR: ...]" — read by the Chairman as an opinion. Failures are now excluded from the round and reported as DebateResult.FailedMembers + IsDegraded. |
| 🔍 Empty responses explained | New OllamaEmptyResponseException separates "the budget went into reasoning" from "the model returned nothing", with the remedy in the message. |
| 🧠 Reasoning is controllable | OllamaProvider(enableThinking:) sends Think explicitly; retryOnBudgetExhaustion retries once with a larger budget when generation was cut off mid-thought. |
| ⏱️ Durations were negative | TotalDuration came out as -0.0s on every debate because StartedAt was initialised after CompletedAt was stamped. |
🆕 What's new in v10.3.0
| Feature | Description |
|---|---|
| 🌐 Distributed Debates | IDebateOrchestrator interface with LocalDebateOrchestrator (single-process) and RedisDebateOrchestrator (Redis pub/sub for round dispatch + result collection). DebateHandle, DebateOrchestrationStatus, DebateRoundEvent, DebateWorkerService, RedisOrchestratorOptions, RedisOrchestratorExtensions. Wire via ICouncilBuilder.WithOrchestrator(IDebateOrchestrator). |
| 💾 Result Caching | IDebateCache interface with InMemoryDebateCache, FileDebateCache, RedisDebateCache. CacheBehavior enum (UseCache, BypassCache, RefreshCache). DebateCacheKeyGenerator for deterministic cache keys. Cache metadata on DebateResult (CacheHit, CacheKey, CachedAt). ICouncilBuilder.WithCacheBehavior() / WithCache(). DI extensions + OTEL counter delibera.cache.hits/misses. |
| 🖥️ Delibera.Server | ASP.NET Core 10 Minimal API hosting Delibera over HTTP. REST API (POST /api/debates, GET /api/debates/{id}, DELETE /api/debates/{id}), SSE streaming via GET /api/debates/{id}/stream powered by IDebateOrchestrator.StreamAsync(), background queue via IHostedService + System.Threading.Channels. |
| ⚠️ Breaking Changes | Moderator → Chairman; removed IDebateStrategyWithOptions; ModelCapabilities non-nullable (use IsUnknown); RagProviderFactory → VectorStoreFactory; removed DebateStatus.Paused, DebateOrchestrationStatus.Pending; SseDebateStreamWriter rewritten for IDebateOrchestrator.StreamAsync(); removed DebateRecord._channel/RoundWriter/RoundReader. |
See CHANGELOG.md for the full release notes, docs/performance-measurements.md for what was measured, and docs/ROADMAP.md for the roadmap.
💡 Use Cases
The same council shape fits a lot of decisions. These are the ones that measurably benefit from multiple models disagreeing rather than one model answering twice.
| Use case | Council shape | Why a council earns its cost |
|---|---|---|
| Architecture decision | 3 experts + chairman, 4 rounds, RAG over your ADRs | Different models surface different failure modes; the Chairman is forced to name where they disagree. Measured: an architecture verdict flagged two risks nobody raised — a licence change and the unasked question why the migration was wanted. |
| Code review before a human | Code specialist + 2 generalists, 3 rounds | Catches the two things a linter misses: a wrong fix and an unstated assumption. Measured: the cheapest roster answered "name the one defect" with 78,000 characters and still picked the weaker of two available answers. |
| Incident post-mortem | 3 experts, chairman, RAG over runbooks + chrome-devtools-mcp for live evidence |
Forces the "what do we know vs. what do we assume" split into the record. |
| Security and compliance review | 3 experts, chairman, IsDegraded checked |
A member that fails is a hole in the review, not a footnote — IsDegraded makes that impossible to miss. |
| Vendor and tool selection | 3 experts + chairman, criteria in the prompt | Turns a preference argument into an explicit trade-off table with dissent preserved. |
| Research with citations | Knowledge Keeper over your docs, 4 rounds | Measured: retrieval returned the right passage (top score 0.738) and verdicts cited documents by line range. |
| On-call decision support | 2–3 experts, 2 rounds, cached | Same incident shape twice costs one debate: measured cache hit served in 0.0 s against 154–209 s uncached. |
| Prompt and agent design review | 3 experts, chairman | Finds the prompt that lets the most confident model win rather than the best-reasoned one. |
When not to use it: single-fact lookups, anything where one strong model and a tool call beats three opinions, and latency budgets under a second. A measured 4-round debate runs 154–209 s.
📑 Table of Contents
- Use Cases
- Quick Start
- Dependency Injection
- Operator (MCP Tools)
- Context Compression
- AutoChunking
- RAG Integration
- Debate Strategies
- Output Files Structure
- Cancellation Support
- Measured Behaviour
- ConsoleApp Examples
- Installation & Build
- Architecture
- Contributing
- License
🚀 Quick Start
Prerequisites & Models
- .NET 10 SDK (≥ 10.0.301) — the project targets
net10.0and builds withLangVersion=previewto enable C# 15 features. See docs/NET10-Upgrade.md for the full migration notes. - A running Ollama instance — local (
ollama serve) or Ollama Cloud (just an API key, no install). - The minimal set of models listed below.
- Optional — for the Operator role: Node.js + npx to launch MCP servers
(e.g.
@playwright/mcp,@marp-team/marp-cli). See Operator (MCP Tools).
🟢 Minimal — for a tiny debate (≈ 2 GB total)
Suitable for smoke-testing, low-end hardware, and quick CLI runs.
| Purpose | Model | Size | Pull command |
|---|---|---|---|
| Council member | llama3.2:1b |
1.3 GB | ollama pull llama3.2:1b |
| Council member | qwen2.5:1.5b |
1.1 GB | ollama pull qwen2.5:1.5b |
| Embeddings (RAG) | nomic-embed-text |
274 MB | ollama pull nomic-embed-text |
🟡 Standard — recommended for most use cases (≈ 7 GB total)
Good reasoning quality with low latency. This is the default set used throughout the README.
| Purpose | Model | Size | Pull command |
|---|---|---|---|
| Council member | llama3.2:3b |
2.0 GB | ollama pull llama3.2:3b |
| Council member | qwen2.5:7b |
4.7 GB | ollama pull qwen2.5:7b |
| Embeddings (RAG) | nomic-embed-text |
274 MB | ollama pull nomic-embed-text |
🔴 High-Performance — for production-grade deliberations (≈ 30+ GB)
Heavier local models, recommended on GPUs with ≥ 24 GB VRAM or on Ollama Cloud.
| Purpose | Model | Size | Pull command |
|---|---|---|---|
| Council member | llama3.1:8b |
4.9 GB | ollama pull llama3.1:8b |
| Council member | qwen2.5:14b |
9.0 GB | ollama pull qwen2.5:14b |
| Council member | mistral:7b |
4.4 GB | ollama pull mistral:7b |
| Chairman | qwen2.5:14b (or larger) |
9.0 GB | ollama pull qwen2.5:14b |
| Embeddings (RAG) | nomic-embed-text |
274 MB | ollama pull nomic-embed-text |
💡 Ollama Cloud has the same model names but doesn't require local disk space — you only need an API key. See the Configuration section for setting it.
Installation
dotnet add package Delibera.Core
Minimal Example
using Delibera.Core.Council; using Delibera.Core.Providers; using var factory = new ProviderFactory(); var ollama = factory.CreateOllama("http://localhost:11434"); var result = await new CouncilBuilder() .AddMember("llama3.2:3b", ollama, "Analyst") .AddMember("qwen2.5:7b", ollama, "Strategist") .SetChairman(Chairman.CreateStandard("qwen2.5:7b", ollama)) .WithStandardDebate() .WithSystemPrompt("You are a software architecture expert.") .WithUserPrompt("Microservices vs Monolith for a 5-person startup?") .WithMaxRounds(4) .SaveResultTo("./deliberation.md") .Build() .ExecuteAsync(); Console.WriteLine(result.FinalVerdict);
📄 See docs/QuickStart.md for a step-by-step walkthrough.
Run
dotnet run
Delibera will run a structured, multi-round debate and write the full transcript and the
Chairman's verdict to deliberation.md.
💉 Dependency Injection
Register all Delibera services in one line:
using Delibera.Core.DependencyInjection; // Option A: With configuration binding (binds the "Delibera" section) services.AddDelibera(configuration, "Delibera"); // Option B: With options delegate services.AddDelibera(options => { options.Strategy = "Standard"; options.MaxRounds = 4; options.Temperature = 0.7f; options.Compression.Enabled = true; options.Compression.Strategy = "Hybrid"; options.Compression.TargetRatio = 0.5; }); // Option C: Defaults only services.AddDelibera();
Resolves these interfaces from DI:
| Interface | Implementation | Lifetime |
|---|---|---|
ILLMProviderFactory |
ProviderFactory |
Singleton |
IVectorStoreFactory |
VectorStoreFactory |
Singleton |
ICompressionFactory |
CompressionService |
Singleton |
ICouncilBuilder |
CouncilBuilder |
Transient |
Configuration (appsettings.json)
{
"Delibera": {
"Strategy": "Standard",
"MaxRounds": 4,
"Temperature": 0.7,
"SystemPrompt": "You are a knowledgeable AI expert participating in a council debate.",
"Providers": {
"DefaultType": "Ollama",
"DefaultEndpoint": "http://localhost:11434",
"ApiKey": "",
"EmbeddingModel": "nomic-embed-text"
},
"Compression": {
"Enabled": true,
"Strategy": "Hybrid",
"TargetRatio": 0.5,
"EnableCache": true,
"MaxCacheEntries": 256
},
"Rag": {
"Enabled": false,
"ProviderType": "Qdrant",
"Host": "localhost",
"Port": 6334,
"CollectionName": "council_knowledge",
"ConnectionString": null
},
"Output": {
"Directory": "./debate_results",
"SeparateFiles": true,
"FilePrefix": null
}
}
}To use Ollama Cloud, set Providers:DefaultEndpoint to https://api.ollama.com and put your
key in Providers:ApiKey (or OllamaCloud:ApiKey in the DeliberaApp section used by the
console app — see ConsoleApp Examples).
🛠️ Operator (MCP Tools)
The Operator is a lightweight micro-agent that connects the council to the outside world through MCP (Model Context Protocol) servers. It exposes whatever tools those servers provide — web browsing, file system access, Marp presentation generation, Notion, PostgreSQL, etc. — to the debate participants.
How it works
-
The Operator connects to one or more MCP servers and discovers their tools on
InitializeAsync. -
Participants are told (in their system prompt) what the Operator can do, and can delegate a task at any moment during the debate by writing a marker in their message:
[[OPERATOR: open https://modelcontextprotocol.io and summarize what MCP is]] -
The Operator interprets the request with its own (cheaper) LLM model, picks and calls the right MCP tools, interprets the results, and returns a concise answer that is injected into the next round.
-
If the council uses context compression, the Operator can reuse the same strategy to compress large tool outputs before they re-enter the debate. Its
DisposeAsyncis aValueTask(.NET 10IAsyncDisposable) so MCP clients are released without aTaskallocation.
All Operator interactions are recorded per round and rendered in the final Markdown report under a 🛠️ Operator Interactions block.
Configuring MCP servers
using Delibera.Core.Models; var servers = new[] { // stdio transport — launches a local MCP server process McpServerConfig.Stdio( name: "browser", command: "npx", arguments: new[] { "-y", "@playwright/mcp@latest", "--headless" }), McpServerConfig.Stdio( name: "marp", command: "npx", arguments: new[] { "-y", "@marp-team/marp-cli", "--server", "./out" }), // …or an HTTP/SSE transport for a remote MCP server // McpServerConfig.Http( // name: "remote", // endpoint: "https://my-mcp-host.example.com/mcp", // additionalHeaders: new Dictionary<string, string> { ["Authorization"] = "Bearer <token>" }), };
| Server | Transport | Launch command | What it provides |
|---|---|---|---|
🌐 browser |
stdio | npx -y @playwright/mcp@latest --headless |
Site navigation, page reading, clicks, screenshots |
🎯 marp |
stdio | npx -y @marp-team/marp-cli --server <dir> |
Generating presentations (HTML/PDF/PPTX) from Markdown |
Quick usage (inside a council)
using Delibera.Core.Council; using Delibera.Core.Providers.LLM; var ollama = new OllamaProvider("http://localhost:11434"); var council = new CouncilBuilder() .AddMember("llama3.2:3b", ollama, "Optimist") .AddMember("qwen2.5:7b", ollama, "Skeptic") .SetChairman(Chairman.CreateStandard("qwen2.5:7b", ollama)) // Operator uses its own cheaper model; reuseCompression shares the council's compressor .WithOperator("llama3.2:3b", ollama, servers, reuseCompression: true) .WithStandardDebate() .WithUserPrompt("Research the latest .NET 10 features and prepare a short summary.") .WithMaxRounds(4) .Build() .ExecuteAsync();
Prefer to build the Operator yourself? Pass a pre-built instance:
using Delibera.Core.Council; using Delibera.Core.Interfaces; using Delibera.Core.Models; using Delibera.Core.Providers.Mcp; var @operator = new Operator( new CouncilMember("llama3.2:3b", ollama, "Operator"), new IMcpClient[] { new McpClientAdapter(servers[0]), new McpClientAdapter(servers[1]) }, compressor: null, // optional IContextCompressor compressionOptions: null); // optional CompressionOptions var council = new CouncilBuilder() /* …members… */ .WithOperator(@operator) .Build();
Direct usage (without a council)
await using var @operator = new Operator( new CouncilMember("llama3.2:3b", ollama, "Operator"), new IMcpClient[] { new McpClientAdapter(servers[0]), new McpClientAdapter(servers[1]) }); await @operator.InitializeAsync(); var result = await @operator.ExecuteTaskAsync( "Open https://modelcontextprotocol.io and briefly summarize what MCP is."); Console.WriteLine(result.FinalAnswer);
Dependency Injection
Configure the Operator declaratively in appsettings.json under Delibera:Operator:
{
"Delibera": {
"Operator": {
"Enabled": true,
"ModelName": "llama3.2:3b",
"ReuseCompression": true,
"McpServers": [
{
"Name": "browser",
"Transport": "Stdio",
"Command": "npx",
"Arguments": [ "-y", "@playwright/mcp@latest", "--headless" ]
},
{
"Name": "remote",
"Transport": "Http",
"Endpoint": "https://my-mcp-host.example.com/mcp",
"AdditionalHeaders": { "Authorization": "Bearer <token>" }
}
]
}
}
}▶️ A complete, runnable demo lives in
OperatorMcpToolsExample.cs. Run it withdotnet run --project src/Delibera.ConsoleApp -- --operator-mcp. For the full technical write-up, see docs/NET10-Upgrade.md.
🗜️ Context Compression
Automatically compress context between deliberation rounds — 11.7–12.1% of prompt tokens measured, without losing meaning.
| Strategy | How It Works | Best For |
|---|---|---|
| Semantic | Embeds sentences, ranks by relevance to topic, keeps top-N | Large knowledge contexts |
| Deduplication | Removes semantically similar sentences across participants | Multi-model debates with overlap |
| Summarization | LLM produces a concise summary preserving key facts | Maximum compression ratio |
| Hybrid | Dedup → Semantic → Summarize pipeline | Best overall quality |
| None | Pass-through (when disabled) | Debugging |
using Delibera.Core.Compression; using Delibera.Core.Providers.LLM; var ollama = new OllamaProvider("http://localhost:11434"); var embeddings = new OllamaEmbeddingProvider(ollama, "nomic-embed-text"); var result = await new CouncilBuilder() .AddMember("llama3.2:3b", ollama, "Analyst") .AddMember("qwen2.5:7b", ollama, "Strategist") .SetChairman(Chairman.CreateStandard("qwen2.5:7b", ollama)) .WithCompression(CompressionStrategy.Hybrid, llmProvider: ollama, modelName: "llama3.2:3b", embeddingProvider: embeddings) .WithCompressionOptions(new CompressionOptions { TargetRatio = 0.5 }) .WithCompressionCache() .WithUserPrompt("Analyze our architecture options...") .WithMaxRounds(4) .Build() .ExecuteAsync(); Console.WriteLine(result.TokenStats?.ToSummary());
Compression Pipeline
IContextCompressor
├── SemanticCompressor ← Embedding-based sentence ranking
├── DeduplicationCompressor ← Similarity-based duplicate removal
├── SummarizationCompressor ← LLM-powered summarization
├── HybridCompressor ← Multi-stage pipeline (Dedup → Semantic → Summarize)
└── PassThroughCompressor ← No-op (when disabled)
CompressionFactory ← Static factory (Create by enum or string)
CompressionService ← DI-friendly wrapper around CompressionFactory
CompressionCache ← SHA-256 keyed LRU cache
TokenCounter ← Heuristic token estimation
✂️ AutoChunking
Automatically split large knowledge documents (contracts, reports, articles) into context-window-sized chunks distributed across debate rounds via progressive disclosure. When a document exceeds the smallest model's context window, the orchestrator creates a chunking plan and distributes chunks evenly — every model receives a complete view by the final round.
How it works
- Model discovery — queries each model's context window via
GetModelCapabilitiesAsync()(Ollama/api/show, or the built-in registry of 40+ models). - Overhead calculation — system prompt + user question + response buffer + history.
- Chunking plan — splits the document on semantic boundaries (Markdown headers →
paragraphs → sentences), each chunk ≤
minWindow − overhead − safetyMargin. - Progressive disclosure — Round 1 gets chunks 1..N/3, Round 2 gets N/3+1..2N/3, etc.
Each round sees
[Chunk X/Y] SectionTitlemarkers + a summary of previous rounds.
Three configuration paths
// Path 1: Fluent API var executor = new CouncilBuilder() .WithAutoChunking(new AutoChunkingOptions { Strategy = ChunkingStrategy.SemanticBoundary, SafetyMargin = 0.15, MaxChunksPerRound = 3 }) /* …members, chairman, knowledge… */ .Build(); // Path 2: CouncilOptions snapshot (from DI or manual) var options = new CouncilOptions { AutoChunking = new AutoChunkingConfig { Enabled = true, Strategy = "SemanticBoundary" } }; var executor = new CouncilBuilder(options) // or .WithOptions(options) /* … */ .Build(); // Path 3: Lambda configuration var executor = new CouncilBuilder() .WithOptions(o => { o.AutoChunking.Enabled = true; o.AutoChunking.MaxChunksPerRound = 2; }) /* … */ .Build();
Configuration (appsettings.json)
{
"Delibera": {
"AutoChunking": {
"Enabled": true,
"Strategy": "SemanticBoundary",
"SafetyMargin": 0.15,
"MaxChunksPerRound": 3,
"EnableMapReduce": true,
"EnableProgressiveDisclosure": true,
"ModelContextWindows": {
"my-custom-model": 65536
}
}
}
}Model Context Window Registry
// Query known models var window = ModelContextWindowRegistry.GetContextWindow("llama3.2"); // → 131072 var window = ModelContextWindowRegistry.GetContextWindow("phi3:mini"); // → 4096 // Register custom models ModelContextWindowRegistry.Register("my-fine-tuned-model", 65536);
▶️ Run the demo:
dotnet run --project src/Delibera.ConsoleApp -- --autochunking
📚 RAG Integration
Use a dedicated Qdrant instance or your existing PostgreSQL/pgvector database as a vector store.
using Delibera.Core.Council; using Delibera.Core.Models; using Delibera.Core.Providers.LLM; using Delibera.Core.Providers.RAG; var ollama = new OllamaProvider("http://localhost:11434"); var embeddings = new OllamaEmbeddingProvider(ollama, "nomic-embed-text"); // pgvector — just add a connection string var ragFactory = new VectorStoreFactory(); var rag = ragFactory.CreatePgVector( embeddings, "Host=localhost;Database=council_vectors;Username=postgres;Password=postgres"); await rag.IndexDocumentAsync("my_collection", documentText); var results = await rag.SearchAsync("my_collection", "query", limit: 5); // Wire into a Knowledge Keeper var kkMember = new CouncilMember("llama3.2:3b", ollama, "Knowledge Keeper"); var keeper = new KnowledgeKeeper(rag, kkMember, "my_knowledge"); await keeper.IndexFileAsync("./docs/architecture.md");
IRagProvider
├── QdrantRagProvider
│ └── QdrantVectorStore ← Qdrant gRPC
└── PgVectorRagProvider
└── PgVectorStore ← PostgreSQL/pgvector
IEmbeddingProvider
└── OllamaEmbeddingProvider
The Knowledge Keeper is then attached to the council via WithKnowledgeKeeper(...) — see
the RAG example for a full working demo.
🗣️ Debate Strategies
| Strategy | Flow | Use Case |
|---|---|---|
| StandardDebate | Initial → Critique → Improved → Verdict | General analysis |
| CritiqueDebate | Position → Attack → Defence → Judge | Hypothesis testing |
| ConsensusDebate | Perspectives → Common Ground → Consensus → Facilitator | Optimal solution search |
Each strategy is implemented as an IDebateStrategy — see
src/Delibera.Core/Debate/ for the full source.
Design Patterns
| Pattern | Usage |
|---|---|
| Factory | ProviderFactory, VectorStoreFactory, CompressionFactory, Chairman |
| Strategy | IDebateStrategy, IContextCompressor |
| Builder | CouncilBuilder fluent API |
| Template Method | DebateScenario abstract base class |
| Cache | CompressionCache with SHA-256 keys |
| Observer | OnRoundCompleted event on CouncilExecutor |
📁 Output Files Structure
Each deliberation can be exported as a single file or as three separate Markdown documents:
var result = await executor.ExecuteAsync(); // Save to 3 separate files var (resultPath, statsPath, logsPath) = await result.SaveAllAsync("./output"); // Creates: debate_20260604_120000_result.md // debate_20260604_120000_statistics.md // debate_20260604_120000_logs.md // Or save individually await result.SaveToMarkdownAsync("result.md"); await result.SaveStatisticsAsync("statistics.md"); await result.SaveLogsAsync("logs.md");
| File | Contents |
|---|---|
*_result.md |
Full deliberation transcript, rounds, and the Chairman's final verdict |
*_statistics.md |
Token usage statistics with per-round breakdown |
*_logs.md |
Execution logs (ExecutionLog) for Chairman, KK, compression & participants |
🛑 Cancellation Support
Every public async method in Delibera honors a CancellationToken cooperatively. A single
cancel signal aborts the entire pipeline — rounds, chairman synthesis, LLM calls, MCP tools,
RAG queries and even file writes — via OperationCanceledException.
using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(5)); var result = await executor.ExecuteAsync(cts.Token);
ASP.NET Core / Worker Services can link the token to IHostApplicationLifetime.ApplicationStopping
via the Delibera.Core.Extensions.CouncilExecutorLifetimeExtensions.ExecuteAsync(executor, lifetime, ct)
helper. Console apps can wire Console.CancelKeyPress to the same CancellationTokenSource.
The Delibera.ConsoleApp includes a --cancellation demo that exercises this end-to-end.
See the Cancellation section in Delibera.Core README
for the full table of cancellable operations and the linked-token pattern.
📏 Measured Behaviour
Numbers below come from real deliberations against Ollama Cloud, not estimates. Method, caveats and the full evidence are in docs/performance-measurements.md.
| What | Measured |
|---|---|
| Framework overhead | 0.0 s — time no model call explains, across every run |
| Debate wall time | 154–209 s for 4 rounds, 3 members + chairman |
| Throughput | 840–1,144 chars/s, flat — wall time tracks output volume, not Delibera |
| Round cost profile | Critique + refinement are 62–77% of a debate; round 1 is the cheapest |
| Context compression | 11.7–12.1% prompt tokens saved |
| Knowledge Keeper | 3 retrieval queries per debate, steady across topics; probe top score 0.738 |
| Operator (MCP) | 1–3 delegated tasks per debate, scaling with how much the question needs looking up |
| Cache hit | 0.0 s against 154–209 s uncached |
| Output balance | One verbose model produced 68.8% of all member text — roster composition mattered more than the framework |
Two honest caveats worth knowing before you copy a number:
- Compression saves ~12%, not the 30–70% this README used to claim. The old figure was never measured. v10.5.0 corrects it.
- A debate is not fast. 154–209 s is the honest cost. If that does not fit your budget, use two rounds, three members, or the cache.
💻 ConsoleApp Examples
The repository ships with Delibera.ConsoleApp, a runnable demo
project that exercises every feature. Run it from the repo root:
# Clone the repository git clone https://github.com/techbuzzz/Delibera.git cd Delibera/src/Delibera.ConsoleApp # Run a specific example dotnet run -- --di # Dependency Injection dotnet run -- --separate-files # Save result.md, statistics.md, logs.md dotnet run -- --compression # Context compression demo dotnet run -- --multiprovider # Multi-provider (cloud + local) council dotnet run -- --rag # Qdrant-backed RAG with Knowledge Keeper dotnet run -- --pgvector # pgvector-backed RAG dotnet run -- --operator # Operator role basics (MCP tools) dotnet run -- --operator-mcp # 🆕 Operator with browser + Marp MCP servers dotnet run -- --autochunking # 🆕 AutoChunking demo (large documents) # Or run the full default demo (reads appsettings.json) dotnet run
The console app reads appsettings.json which uses
the DeliberaApp section — by default it points at Ollama Cloud and shows you how to wire
multiple providers, RAG, and compression in one place.
🛠️ Installation & Build
Clone & Build
git clone https://github.com/techbuzzz/Delibera.git cd Delibera # Build the entire solution dotnet build --configuration Release # Run the console demo cd src/Delibera.ConsoleApp dotnet run
One-Command Infrastructure (Docker Compose)
Spin up the API + Web UI + Redis + Qdrant + PostgreSQL/pgvector in one shot. (Ollama is intentionally not included — install it natively so the GPU driver is used directly.)
# From the repo root docker compose up -d # Or, with Ollama on GPU inside Docker: docker compose --profile ollama up -d
Then open http://localhost:3000 for the web UI and http://localhost:5200 for the API.
| Service | URL | Purpose |
|---|---|---|
| Web UI (Nuxt 4) | http://localhost:3000 |
Debates: create, live rounds, verdict |
| REST API | http://localhost:5200 |
REST endpoints under /api/v1 |
| MCP endpoint | http://localhost:5200/mcp |
Model Context Protocol tools |
| Qdrant (REST / gRPC) | localhost:6333 / :6334 |
Vector store (used by Delibera) |
| PostgreSQL | localhost:5432 |
pgvector RAG store |
| Redis | internal only | Debate state (opt-in) |
Default credentials: postgres / postgres, database council_vectors.
⚠️ Security — read this before changing a port binding.
The Delibera API is unauthenticated and unthrottled. Anyone who can reach the port can start debates that spend real LLM credits on your provider account. Every published port is bound to
127.0.0.1for exactly this reason. Do not change those bindings to0.0.0.0unless you have put an authenticating reverse proxy in front first.
If you already have these services running natively, just skip
docker composeand point the console app at them —appsettings.jsondefaults tolocalhost.
Pre-built images (no clone required)
curl -O https://raw.githubusercontent.com/techbuzzz/Delibera/main/deploy/docker-compose.hub.yml docker compose -f docker-compose.hub.yml up -d
Pulls techbuzzz/delibera-server and techbuzzz/delibera-webui from Docker Hub. Set
DELIBERA_VERSION=10.5.1 to pin a specific release.
Web UI
src/Delibera.WebUI is a Nuxt 4 application covering the debate workflow:
list, create, live SSE rounds, verdict with token stats, Markdown export, cancel.
Two things are worth knowing if you extend it:
- The browser never calls the API directly. Every request goes through a Nitro server-side
proxy (
/api/delibera/**), because the API registers no CORS policy and a cross-origin browser call would be blocked. SSE is streamed through, not buffered — the test suite asserts a chunk arrives before the upstream stream ends. VerdictDtois mostly a shell.DebateMapperpopulates onlyrecommendationandrawJson;confidence,riskLevel,risksandconditionsare never filled in. The structured output that does exist is template-specific and lives insideverdict.rawJson.
Run it locally against a host API:
cd src/Delibera.WebUI npm ci npm run dev # http://localhost:3000, proxies to http://localhost:5200
Redis (optional)
Set Delibera:Redis:Enabled=true (or DELIBERA_REDIS_ENABLED=true) to have the server publish
round events to a Redis Stream instead of keeping them in process. That lets several API instances
serve the same debates, and enables the Redis result cache via CacheEnabled.
It is off by default. It is also not distributed turn execution —
DebateWorkerService is a documented no-op placeholder, so each debate still runs in the process
that accepted it. See src/Delibera.Redis/README.md.
Manual Alternative (one container at a time)
# Run with Qdrant (Docker) docker run -d -p 6333:6333 -p 6334:6334 qdrant/qdrant # Run with pgvector (Docker) docker run -d -p 5432:5432 -e POSTGRES_PASSWORD=postgres pgvector/pgvector:pg16 # Then enable the pgvector extension docker exec -it <container> psql -U postgres -d council_vectors -c "CREATE EXTENSION IF NOT EXISTS vector;"
NuGet Dependencies
| Package | Purpose |
|---|---|
OllamaSharp |
Ollama API client |
Qdrant.Client |
Qdrant vector DB gRPC client |
Npgsql |
PostgreSQL ADO.NET provider |
Pgvector |
pgvector type support for Npgsql |
ModelContextProtocol |
MCP client for the Operator role |
Microsoft.Extensions.AI |
Unified IChatClient / IEmbeddingGenerator AI abstractions & middleware |
Microsoft.Extensions.* |
Configuration, DI and Options |
StackExchange.Redis |
Redis client for distributed debates and caching |
🤝 Microsoft.Extensions.AI
Delibera integrates with Microsoft.Extensions.AI
(v10.7.0) — the standard .NET abstraction layer for generative-AI services. This lets you drive a
council with any backend that ships an IChatClient (OpenAI, Azure OpenAI, Ollama, Anthropic,
LM Studio / LocalAI / vLLM, …) and compose a middleware pipeline (function/tool invocation,
logging, caching, telemetry) — all without writing a provider per vendor.
What you get
| Type | Role |
|---|---|
ChatClientLLMProvider |
Adapts any IChatClient to Delibera's ILLMProvider (with streaming via ChatStreamAsync) |
EmbeddingGeneratorProvider |
Adapts any IEmbeddingGenerator<string, Embedding<float>> to IEmbeddingProvider for RAG |
MicrosoftAIExtensions |
Bridge helpers: AsLLMProvider(), AsEmbeddingProvider(), AsChatClient(), WithMiddleware() |
ProviderFactory.CreateFromChatClient(...) |
Build a cached provider straight from an IChatClient |
AddDeliberaChatClient(...) / AddDeliberaEmbeddingGenerator(...) |
Register the standard AI services in DI |
Use any IChatClient as a Delibera provider
using Delibera.Core.Extensions; using Microsoft.Extensions.AI; // 1) Bring your own IChatClient. For OpenAI: // dotnet add package Microsoft.Extensions.AI.OpenAI IChatClient client = new OpenAI.Chat.ChatClient("gpt-4o-mini", apiKey).AsIChatClient(); // 2) (optional) compose middleware — function calling + logging client = client.WithMiddleware(enableFunctionInvocation: true, loggerFactory); // 3) expose it to Delibera ILLMProvider provider = client.AsLLMProvider("OpenAI"); var executor = new CouncilBuilder() .AddMember("gpt-4o-mini", provider, "Architect") .AddMember("gpt-4o-mini", provider, "Skeptic") .SetChairman(Chairman.CreateStandard("gpt-4o-mini", provider)) .WithStandardDebate() .WithUserPrompt("Modular monolith or microservices for a 5-person team?") .Build();
Ollama works out of the box because OllamaSharp's OllamaApiClient natively implements
IChatClient and IEmbeddingGenerator:
using var ollama = new OllamaProvider("http://localhost:11434"); IChatClient chat = ollama.AsChatClient(); // ready for middleware ILLMProvider provider = chat.AsLLMProvider("Ollama"); IEmbeddingProvider embeddings = ollama.AsEmbeddingGenerator().AsEmbeddingProvider("nomic-embed-text");
Streaming
await foreach (var chunk in provider.ChatStreamAsync("gpt-4o-mini", systemPrompt, userPrompt)) Console.Write(chunk);
ChatStreamAsync is an additive default method on ILLMProvider — providers backed by an
IChatClient stream token-by-token, while older providers transparently fall back to a single call.
Dependency Injection
services.AddDeliberaChatClient( sp => new OpenAI.Chat.ChatClient("gpt-4o-mini", apiKey) .AsIChatClient() .WithMiddleware(enableFunctionInvocation: true), providerName: "OpenAI"); services.AddDeliberaEmbeddingGenerator( sp => /* your IEmbeddingGenerator */, modelName: "text-embedding-3-small");
Try it:
dotnet run --project src/Delibera.ConsoleApp -- --msai
🏛️ Architecture
Delibera.Core
├── Council/ ← CouncilBuilder, CouncilExecutor, Chairman, KnowledgeKeeper, Operator
├── Debate/ ← StandardDebate, CritiqueDebate, ConsensusDebate
├── Orchestration/ ← IDebateOrchestrator, LocalDebateOrchestrator, DebateHandle, DebateRoundEvent
├── Cache/ ← IDebateCache, InMemoryDebateCache, FileDebateCache, CacheBehavior, DebateCacheKeyGenerator
├── Compression/ ← Semantic / Deduplication / Summarization / Hybrid
├── Chunking/ ← AutoChunker, AutoChunkingOrchestrator, AutoChunkingOptions
├── Providers/
│ ├── LLM/ ← OllamaProvider, ChatClientLLMProvider, EmbeddingGeneratorProvider
│ ├── RAG/ ← QdrantRagProvider, PgVectorRagProvider
│ └── Mcp/ ← McpClientAdapter (Operator ↔ MCP servers)
├── Extensions/ ← MicrosoftAIExtensions (IChatClient ↔ ILLMProvider bridges)
├── DependencyInjection/ ← AddDelibera() / AddDeliberaChatClient() + CouncilOptions
├── Knowledge/ ← MarkdownKnowledgeBase
├── Models/ ← CouncilMember, DebateResult, DebateRound, TokenStatistics, ...
└── Interfaces/ ← ILLMProvider, IRagProvider, IContextCompressor, IOperator, IMcpClient, ...
Delibera.Redis
├── Orchestration/ ← RedisDebateOrchestrator, DebateWorkerService, RedisOrchestratorOptions, RedisOrchestratorExtensions
└── Cache/ ← RedisDebateCache
Delibera.Server
├── Endpoints/ ← Minimal API endpoints (POST /api/debates, GET /stream, etc.)
├── Services/ ← DebateOrchestrationService, SseDebateStreamWriter
└── Configuration/ ← AddDeliberaServer(), DeliberaServerOptions
🤝 Contributing
Contributions are welcome! Please read CONTRIBUTING.md for guidelines on development setup, coding standards, and the pull-request process.
📄 License
Delibera is released under the MIT License.
Copyright © 2026 Delibera Project.
⚖️ Delibera — Thoughtful AI Decisions
Built with care for AI-powered collective intelligence
来源:GitHub · github.com
