Installation
Get ditto-ai running in seconds.
via npm/bun
bun add ditto-ai
npm install ditto-ai
pnpm add ditto-aiRequirements
- Node/Bun: LTS or newer
- TypeScript: 5.0+
- Effect: ^3.0.0 (included)
- Zod: ^3.0.0 (optional)
API Reference
Complete type definitions and exports.
Main Exports
dittoClient(options)
Creates a Ditto client instance.
createDittoWorkerHandler(opts?)
Creates a Cloudflare Worker handler.
DittoError
Error type thrown by ditto operations.
Types
type Strategy = "consensus" | "cooperative";
interface DittoClientOptions {
endpoint: string;
headers?: Record<string, string>;
fetchImpl?: typeof fetch;
}
interface DittoRequest<T> {
prompt: string;
models: string[];
strategy?: Strategy;
temperature?: number;
maxRetries?: number;
metadata?: Record<string, unknown>;
}
interface DittoClientResponse<T> {
result: T;
responses?: Record<string, string>;
structured?: MergedStructuredResult;
}
interface MergedStructuredResult {
summary: string;
intent: DittoIntent;
confidence: number;
needsClarification: boolean;
supportingModels: string[];
responses: StructuredModelResult[];
}
type DittoIntent = "answer" | "clarification" | "refusal" | "unknown";Client API
How to use ditto-ai from your application.
Creating a Client
import { dittoClient } from "ditto-ai";
const ditto = dittoClient({
endpoint: "https://your-worker.workers.dev/llm",
headers: {
"Authorization": "Bearer token" // optional
}
});Making Requests
// Basic string merge
const response = await ditto({
prompt: "What is 2+2?",
models: [
"@cf/meta/llama-3.1-8b-instruct",
"@cf/mistral/mistral-7b-instruct"
],
strategy: "consensus"
});
console.log(response.result); // merged string
console.log(response.responses); // individual model outputs
console.log(response.structured); // intent, confidence, etc.DittoClientResponse Structure
result: string
The merged output from all models
responses?: Record<string, string>
Each model's raw output: @cf/meta/llama-3.1-8b-instruct → response
structured?: MergedStructuredResult
Analyzed merge: intent, confidence, supporting models
Server Setup
Configure Ditto in your Cloudflare Worker.
Worker Configuration
// worker/index.ts
import { DittoJob, createDittoWorkerHandler } from "ditto-ai/server";
// Export DittoJob for Durable Object binding
export { DittoJob };
type Env = {
DITTO_JOB: DurableObjectNamespace<DittoJob>;
AI: {
run: (model: string, input: { prompt: string }) => Promise<{ response?: string }>;
};
};
const dittoHandler = createDittoWorkerHandler({
defaultStrategy: "consensus"
});
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext) {
const url = new URL(request.url);
if (url.pathname === "/llm") {
return dittoHandler.fetch(request, env, ctx);
}
return new Response("Not found", { status: 404 });
}
};Wrangler Configuration
# wrangler.toml
# AI binding (REQUIRED)
[[ai]]
binding = "AI"
# Durable Object binding (REQUIRED)
[[durable_objects.bindings]]
name = "DITTO_JOB"
class_name = "DittoJob"Environment Bindings
env.AI
Required. Cloudflare AI binding. Must have .run(model, { prompt }) method.
env.DITTO_JOB
Required. Durable Object namespace for per-request job orchestration. Provides extreme durability and idempotency.
Error Handling
Ditto throws typed errors with detailed context.
DittoError
class DittoError extends Error {
type: string; // e.g. "BadRequest", "InternalError"
message: string; // Human-readable message
status: number; // HTTP status code
details?: unknown; // Additional context
}Error Types
BadRequest (400)
Invalid prompt, models, or request format
InternalError (500)
Job execution failed, model unavailable, or system error
JobError
Specific error from Durable Object execution
Error Handling Example
import { dittoClient, DittoError } from "ditto-ai";
try {
const response = await ditto({
prompt: "What is 2+2?",
models: ["@cf/meta/llama-3.1-8b-instruct"]
});
} catch (error) {
if (error instanceof DittoError) {
console.error(`Error: ${error.type} (${error.status})`);
console.error(`Message: ${error.message}`);
console.error(`Details: ${JSON.stringify(error.details)}`);
} else {
console.error("Unknown error:", error);
}
}Merge Strategies
How Ditto combines multiple model outputs.
Consensus (v1)
All models answer the same prompt. Responses are analyzed for intent and merged into a single output.
Flow:
1. Run all models in parallel
2. Classify each response: answer | clarification | refusal | unknown
3. Estimate confidence for each response
4. Find winning intent (majority vote weighted by confidence)
5. Merge summaries of winning responsesStructured Analysis
Every response is analyzed and the merge result includes:
summary: string; // Merged output, max 5 sentences
intent: DittoIntent; // What the merged response is doing
confidence: 0-1; // How confident in this response
supportingModels: string[]; // Which models voted for winning intentCooperative
Models execute sequentially, with each model seeing all previous outputs. Perfect for multi-step workflows where models build on each other's work.
// Model 1 gets: "What is 2+2?"
// Model 2 gets: "What is 2+2?
Previous responses:
1. 4
Build on these responses:"
// Model 3 gets: "What is 2+2?
Previous responses:
1. 4
2. The answer is four
Build on these responses:"
const response = await ditto({
prompt: "What is 2+2?",
models: ["@cf/meta/llama-3.1-8b-instruct", "@cf/mistral/mistral-7b-instruct"],
strategy: "cooperative"
});Examples
Real-world patterns for common use cases.
Email Summarization
import { dittoClient } from "ditto-ai";
const ditto = dittoClient({
endpoint: "https://my-worker.workers.dev/llm"
});
async function summarizeEmail(emailBody: string) {
const response = await ditto({
prompt: `Summarize this email in 1-2 sentences:
${emailBody}`,
models: [
"@cf/meta/llama-3.1-8b-instruct",
"@cf/mistral/mistral-7b-instruct"
],
strategy: "consensus"
});
console.log("Summary:", response.result);
console.log("Confidence:", response.structured?.confidence);
console.log("Needs clarification:", response.structured?.needsClarification);
return response.result;
}Content Moderation
async function moderateContent(text: string) {
const response = await ditto({
prompt: `Is this content harmful? Answer only: yes, no, or unclear.
Content: ${text}`,
models: [
"@cf/meta/llama-3.1-70b-instruct",
"@cf/mistral/mistral-7b-instruct",
"@cf/qwen/qwen-2.5-14b-instruct"
],
strategy: "consensus"
});
// Only block if consensus is confident
if (response.result.toLowerCase().includes("yes") &&
response.structured?.confidence > 0.75) {
return { action: "block", confidence: response.structured.confidence };
}
return { action: "allow" };
}Multi-Step Workflow (Cooperative)
async function analyzeDocument(document: string) {
// Cooperative: Each model builds on previous outputs
const response = await ditto({
prompt: `Analyze this document step by step:
${document}`,
models: [
"@cf/meta/llama-3.1-8b-instruct", // Step 1: Extract key facts
"@cf/mistral/mistral-7b-instruct", // Step 2: Build on facts, add context
"@cf/qwen/qwen-2.5-14b-instruct" // Step 3: Synthesize final analysis
],
strategy: "cooperative"
});
// Each model's output builds on the previous ones
console.log("Final analysis:", response.result);
console.log("All steps:", response.responses);
return response.result;
}Data Extraction
async function extractData(document: string) {
const response = await ditto({
prompt: `Extract the company name from this document:
${document}
Answer with ONLY the company name.`,
models: [
"@cf/meta/llama-3.1-8b-instruct",
"@cf/mistral/mistral-7b-instruct",
"@cf/qwen/qwen-2.5-14b-instruct"
],
strategy: "consensus"
});
// All supporting models agree = high confidence extraction
return {
company: response.result.trim(),
agreementCount: response.structured?.supportingModels.length,
confidence: response.structured?.confidence
};
}Questions?
Check the GitHub issues or open a new one.