--- name: ai-engineering description: "Comprehensive 16-skill suite for AI engineers: RAG pipelines, Vector DBs, Multi-Agent orchestration, MCP servers, Guardrails, and LLM evaluation." license: MIT metadata: author: jgamaraalv repo: jgamaraalv/delivery-loop version: "1.0.0" ---
AI Engineering Skills Suite
A complete production-grade toolkit for building modern AI applications, autonomous agents, and LLM systems. Covers 16 key engineering areas from RAG and vector databases to MCP servers, multi-agent orchestration, guardrails, and automated evaluation.
Subgroups Included
1. RAG Pipeline Builder — Design end-to-end retrieval-augmented generation pipelines: semantic chunking, metadata schemas, dense/sparse retrieval, and reranking. 2. Vector Database Setup — Configure and index vector databases including Pinecone, Chroma, Qdrant, Weaviate, Milvus, and pgvector with cosine/dot-product similarity. 3. Embedding Pipeline Builder — Build production embedding generation pipelines with token batching, rate-limit retries, dimensionality reduction, and caching. 4. Doc to Vector Dataset Generator — Transform messy raw PDFs, markdown, HTML, and Notion pages into clean, structured chunk datasets ready for embedding. 5. Multi-Agent System Orchestrator — Orchestrate multi-agent architectures with dynamic task delegation, inter-agent messaging, shared memory, and conflict resolution. 6. Agent Workflow & Orchestration Planner — Design reliable multi-step agent plans with tool dependency graphs, state machines, self-correction loops, and fallback strategies. 7. Tool & Function Calling Schema Designer — Design robust, unambiguous JSON schemas for LLM tool calling with parameter typing, strict constraints, and error recovery. 8. Structured Output & Schema Extractor — Enforce guaranteed JSON outputs from LLMs using Zod schemas, Pydantic models, JSON mode, and schema repair extractors. 9. Model Context Protocol (MCP) Server Builder — Build custom MCP servers with standard tool schemas, resources, prompts, and transport handlers (stdio/SSE) for Claude and agents. 10. LangChain & LangGraph Workflow Builder — Construct complex agentic pipelines with LangGraph state graphs, cyclic workflows, human-in-the-loop checkpoints, and tool executors. 11. Prompt Template & Output Contract Builder — Craft modular, production-ready system prompts with XML tagging, few-shot exemplars, delimiters, and negative constraints. 12. Prompt Regression & Diff Tester — Run automated regression test suites comparing prompt iterations against golden test matrices with semantic similarity metrics. 13. Guardrails & Safety Filter Builder — Implement real-time content moderation, prompt injection detection, PII redactors (Presidio), and hallucination checks. 14. Evaluation Harness & Golden Dataset Benchmark — Evaluate LLM accuracy and reasoning using LLM-as-a-judge rubrics, RAG triad metrics (faithfulness, answer relevance), and golden test sets. 15. Cost & Latency Optimizer — Cut inference costs and latency using semantic prompt caching, model routing (cascade to smaller models), and prompt compression. 16. LLM Failure Debugger & Hallucination Diagnostic — Trace, diagnose, and isolate LLM failures: context overflow, attention degradation, hallucinated tool calls, and format violations.
Subgroup 1: RAG Pipeline Builder
RAG Pipeline Builder
Design end-to-end RAG pipelines for accurate document retrieval and generation.
Pipeline Architecture
Documents → Chunking → Embedding → Vector Store → Retrieval → Reranking → GenerationChunking Strategy
# Semantic chunking (recommended)
from langchain.text_splitter import RecursiveCharacterTextSplittersplitter = RecursiveCharacterTextSplitter( chunk_size=1000, # Characters per chunk chunk_overlap=200, # Overlap between chunks separators=["\n\n", "\n", ". ", " ", ""], length_function=len, )
chunks = splitter.split_text(document.text)
Add metadata to each chunk
for i, chunk in enumerate(chunks): chunks[i] = { "text": chunk, "metadata": { "source": document.filename, "page": calculate_page(i), "chunk_id": f"{document.id}_chunk_{i}", } } ```
Metadata Schema
interface ChunkMetadata {
// Source information
document_id: string;
source: string;
url?: string;// Location page?: number; section?: string; chunk_index: number;
// Content classification content_type: "text" | "code" | "table" | "list"; language?: string;
// Timestamps created_at: Date; updated_at: Date;
// Retrieval optimization keywords: string[]; summary?: string; importance_score?: number; } ```
Vector Store Setup
# Pinecone example
import pinecone
from langchain.vectorstores import Pinecone
from langchain.embeddings import OpenAIEmbeddingspinecone.init(api_key="...", environment="...")
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vectorstore = Pinecone.from_documents( documents=chunks, embedding=embeddings, index_name="knowledge-base", namespace="production", ) ```
Retrieval Strategies
# Hybrid search (dense + sparse)
def hybrid_retrieval(query: str, k: int = 5):
# Dense retrieval (semantic)
dense_results = vectorstore.similarity_search(query, k=k*2)Sparse retrieval (keyword - BM25)
sparse_results = bm25_search(query, k=k*2)
Combine and rerank
combined = reciprocal_rank_fusion(dense_results, sparse_results)
return combined[:k]
Metadata filtering
results = vectorstore.similarity_search( query, k=5, filter={ "content_type": "code", "language": "python", } ) ```
Reranking
from sentence_transformers import CrossEncoderreranker = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')
def rerank_results(query: str, results: List[Document], top_k: int = 3): # Score each result against query pairs = [(query, doc.page_content) for doc in results] scores = reranker.predict(pairs)
Sort by score
scored_results = list(zip(results, scores)) scored_results.sort(key=lambda x: x[1], reverse=True)
return [doc for doc, score in scored_results[:top_k]] ```
Query Enhancement
# Query expansion
def expand_query(query: str) -> str:
expansion_prompt = f"""
Generate 3 alternative phrasings of this query:
"{query}"Return as JSON array of strings. """ alternatives = llm(expansion_prompt) return [query] + alternatives
Multi-query retrieval
def multi_query_retrieval(query: str, k: int = 5): queries = expand_query(query) all_results = []
for q in queries: results = vectorstore.similarity_search(q, k=k) all_results.extend(results)
Deduplicate and rerank
unique_results = deduplicate(all_results) return rerank_results(query, unique_results, k) ```
Evaluation Plan
# Define golden dataset
golden_dataset = [
{
"query": "How do I authenticate users?",
"expected_docs": ["auth_guide.md", "user_management.md"],
"relevant_chunks": ["chunk_123", "chunk_456"],
},
]Metrics
def evaluate_retrieval(dataset): results = { "precision": [], "recall": [], "mrr": [], # Mean Reciprocal Rank "ndcg": [] # Normalized Discounted Cumulative Gain }
for item in dataset: retrieved = retrieval_fn(item["query"]) retrieved_ids = [doc.metadata["chunk_id"] for doc in retrieved]
Calculate metrics
relevant = set(item["relevant_chunks"]) retrieved_set = set(retrieved_ids)
precision = len(relevant & retrieved_set) / len(retrieved_set) recall = len(relevant & retrieved_set) / len(relevant)
results["precision"].append(precision) results["recall"].append(recall)
return {k: sum(v)/len(v) for k, v in results.items()} ```
Context Window Management
def fit_context_window(chunks: List[Document], max_tokens: int = 4000):
"""Select chunks that fit in context window"""
total_tokens = 0
selected_chunks = []for chunk in chunks: chunk_tokens = count_tokens(chunk.page_content) if total_tokens + chunk_tokens <= max_tokens: selected_chunks.append(chunk) total_tokens += chunk_tokens else: break
return selected_chunks ```
Best Practices
- Chunk size: 500-1000 chars for general text
- Overlap: 10-20% overlap between chunks
- Metadata: Rich metadata for filtering
- Hybrid search: Combine semantic + keyword
- Reranking: Cross-encoder for final ranking
- Evaluation: Golden dataset with metrics
- Context management: Don't exceed model limits
Output Checklist
- Chunking strategy defined
- Metadata schema documented
- Vector store configured
- Retrieval algorithm implemented
- Reranking pipeline added
- Query enhancement (optional)
- Context window management
- Evaluation dataset created
- Metrics implementation
- Performance baseline established
Subgroup 2: Vector Database Setup
Vector Database Setup
Configure vector databases for semantic search and AI applications.
Core Workflow
- Choose database: Select based on requirements
- Setup connection: Configure client
- Generate embeddings: Create vector representations
- Index documents: Store with metadata
- Query vectors: Semantic similarity search
- Optimize: Tune for performance
Database Comparison
| Database | Type | Best For | Scaling |
|---|---|---|---|
| Pinecone | Managed | Production, no ops | Automatic |
| Chroma | Embedded/Server | Development, local | Manual |
| pgvector | PostgreSQL ext | Existing Postgres | With Postgres |
| Qdrant | Self-hosted | Full control | Manual |
| Weaviate | Managed/Self | GraphQL-like API | Both |
Embeddings Generation
OpenAI Embeddings
// embeddings/openai.ts
import OpenAI from 'openai';const openai = new OpenAI();
export async function generateEmbedding(text: string): Promise<number[]> { const response = await openai.embeddings.create({ model: 'text-embedding-3-small', // or text-embedding-3-large input: text, });
return response.data[0].embedding; }
export async function generateEmbeddings(texts: string[]): Promise<number[][]> { const response = await openai.embeddings.create({ model: 'text-embedding-3-small', input: texts, });
return response.data.map((d) => d.embedding); } ```
Batch Processing
// embeddings/batch.ts
const BATCH_SIZE = 100;export async function batchGenerateEmbeddings( texts: string[] ): Promise<number[][]> { const embeddings: number[][] = [];
for (let i = 0; i < texts.length; i += BATCH_SIZE) { const batch = texts.slice(i, i + BATCH_SIZE); const batchEmbeddings = await generateEmbeddings(batch); embeddings.push(...batchEmbeddings);
// Rate limiting if (i + BATCH_SIZE < texts.length) { await new Promise((resolve) => setTimeout(resolve, 100)); } }
return embeddings; } ```
Pinecone Setup
Installation & Config
npm install @pinecone-database/pinecone// db/pinecone.ts
import { Pinecone } from '@pinecone-database/pinecone';const pinecone = new Pinecone({ apiKey: process.env.PINECONE_API_KEY!, });
// Get or create index export async function getIndex(indexName: string) { const indexes = await pinecone.listIndexes();
if (!indexes.indexes?.find((i) => i.name === indexName)) { await pinecone.createIndex({ name: indexName, dimension: 1536, // OpenAI embedding dimension metric: 'cosine', spec: { serverless: { cloud: 'aws', region: 'us-east-1', }, }, });
// Wait for index to be ready await new Promise((resolve) => setTimeout(resolve, 60000)); }
return pinecone.Index(indexName); } ```
Upsert & Query
// db/pinecone-ops.ts
import { getIndex } from './pinecone';
import { generateEmbedding, generateEmbeddings } from '../embeddings/openai';const index = await getIndex('my-index');
interface Document { id: string; content: string; metadata: Record<string, any>; }
// Upsert documents export async function upsertDocuments( documents: Document[], namespace = 'default' ) { const embeddings = await generateEmbeddings(documents.map((d) => d.content));
const vectors = documents.map((doc, i) => ({ id: doc.id, values: embeddings[i], metadata: { content: doc.content, ...doc.metadata, }, }));
// Upsert in batches const BATCH_SIZE = 100; for (let i = 0; i < vectors.length; i += BATCH_SIZE) { const batch = vectors.slice(i, i + BATCH_SIZE); await index.namespace(namespace).upsert(batch); } }
// Query similar documents export async function querySimilar( query: string, options: { topK?: number; namespace?: string; filter?: Record<string, any>; } = {} ) { const { topK = 5, namespace = 'default', filter } = options;
const queryEmbedding = await generateEmbedding(query);
const results = await index.namespace(namespace).query({ vector: queryEmbedding, topK, includeMetadata: true, filter, });
return results.matches?.map((match) => ({ id: match.id, score: match.score, content: match.metadata?.content, metadata: match.metadata, })); }
// Delete documents export async function deleteDocuments(ids: string[], namespace = 'default') { await index.namespace(namespace).deleteMany(ids); }
// Delete by filter export async function deleteByFilter( filter: Record<string, any>, namespace = 'default' ) { await index.namespace(namespace).deleteMany({ filter }); } ```
Chroma Setup
Installation & Config
npm install chromadb// db/chroma.ts
import { ChromaClient, OpenAIEmbeddingFunction } from 'chromadb';const client = new ChromaClient({ path: process.env.CHROMA_URL || 'http://localhost:8000', });
const embedder = new OpenAIEmbeddingFunction({ openai_api_key: process.env.OPENAI_API_KEY!, openai_model: 'text-embedding-3-small', });
export async function getCollection(name: string) { return client.getOrCreateCollection({ name, embeddingFunction: embedder, metadata: { 'hnsw:space': 'cosine' }, }); } ```
Chroma Operations
// db/chroma-ops.ts
import { getCollection } from './chroma';const collection = await getCollection('documents');
// Add documents (Chroma generates embeddings) export async function addDocuments(documents: Document[]) { await collection.add({ ids: documents.map((d) => d.id), documents: documents.map((d) => d.content), metadatas: documents.map((d) => d.metadata), }); }
// Query export async function query(queryText: string, nResults = 5) { const results = await collection.query({ queryTexts: [queryText], nResults, });
return results.ids[0].map((id, i) => ({ id, content: results.documents?.[0][i], metadata: results.metadatas?.[0][i], distance: results.distances?.[0][i], })); }
// Query with filter export async function queryWithFilter( queryText: string, filter: Record<string, any>, nResults = 5 ) { const results = await collection.query({ queryTexts: [queryText], nResults, where: filter, });
return results; }
// Update document export async function updateDocument(id: string, content: string, metadata?: Record<string, any>) { await collection.update({ ids: [id], documents: [content], metadatas: metadata ? [metadata] : undefined, }); }
// Delete export async function deleteDocuments(ids: string[]) { await collection.delete({ ids }); } ```
pgvector Setup
Installation
npm install pg pgvector-- Enable extension
CREATE EXTENSION vector;-- Create table CREATE TABLE documents ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), content TEXT NOT NULL, metadata JSONB, embedding vector(1536), created_at TIMESTAMP DEFAULT NOW() );
-- Create index for similarity search CREATE INDEX ON documents USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);
-- Or use HNSW (better for production) CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops); ```
pgvector Operations
// db/pgvector.ts
import { Pool } from 'pg';
import pgvector from 'pgvector/pg';const pool = new Pool({ connectionString: process.env.DATABASE_URL, });
// Register pgvector type await pgvector.registerType(pool);
// Insert document
export async function insertDocument(
content: string,
embedding: number[],
metadata?: Record<string, any>
) {
const result = await pool.query(
INSERT INTO documents (content, embedding, metadata)
VALUES ($1, $2, $3)
RETURNING id,
[content, pgvector.toSql(embedding), metadata]
);
return result.rows[0].id; }
// Similarity search
export async function searchSimilar(
queryEmbedding: number[],
limit = 5,
threshold = 0.7
) {
const result = await pool.query(
SELECT id, content, metadata,
1 - (embedding <=> $1) as similarity
FROM documents
WHERE 1 - (embedding <=> $1) > $2
ORDER BY embedding <=> $1
LIMIT $3,
[pgvector.toSql(queryEmbedding), threshold, limit]
);
return result.rows; }
// Search with metadata filter
export async function searchWithFilter(
queryEmbedding: number[],
filter: Record<string, any>,
limit = 5
) {
const result = await pool.query(
SELECT id, content, metadata,
1 - (embedding <=> $1) as similarity
FROM documents
WHERE metadata @> $2
ORDER BY embedding <=> $1
LIMIT $3,
[pgvector.toSql(queryEmbedding), filter, limit]
);
return result.rows; }
// Hybrid search (vector + full-text)
export async function hybridSearch(
queryEmbedding: number[],
textQuery: string,
limit = 5
) {
const result = await pool.query(
SELECT id, content, metadata,
(1 - (embedding <=> $1)) * 0.7 +
ts_rank(to_tsvector(content), plainto_tsquery($2)) * 0.3 as score
FROM documents
WHERE to_tsvector(content) @@ plainto_tsquery($2)
OR 1 - (embedding <=> $1) > 0.5
ORDER BY score DESC
LIMIT $3,
[pgvector.toSql(queryEmbedding), textQuery, limit]
);
return result.rows; } ```
Qdrant Setup
npm install @qdrant/js-client-rest// db/qdrant.ts
import { QdrantClient } from '@qdrant/js-client-rest';const client = new QdrantClient({ url: process.env.QDRANT_URL, apiKey: process.env.QDRANT_API_KEY, });
// Create collection export async function createCollection(name: string) { await client.createCollection(name, { vectors: { size: 1536, distance: 'Cosine', }, }); }
// Upsert points export async function upsertPoints( collection: string, points: Array<{ id: string; vector: number[]; payload: Record<string, any>; }> ) { await client.upsert(collection, { points: points.map((p) => ({ id: p.id, vector: p.vector, payload: p.payload, })), }); }
// Search export async function search( collection: string, vector: number[], limit = 5, filter?: Record<string, any> ) { const results = await client.search(collection, { vector, limit, filter: filter ? { must: Object.entries(filter).map(([key, value]) => ({ key, match: { value }, })), } : undefined, with_payload: true, });
return results; } ```
Document Processing Pipeline
// pipeline/ingest.ts
import { RecursiveCharacterTextSplitter } from 'langchain/text_splitter';
import { generateEmbeddings } from '../embeddings/openai';
import { upsertDocuments } from '../db/pinecone-ops';interface RawDocument { id: string; content: string; source: string; metadata?: Record<string, any>; }
export async function ingestDocuments(documents: RawDocument[]) { const splitter = new RecursiveCharacterTextSplitter({ chunkSize: 1000, chunkOverlap: 200, });
const chunks: Array<{ id: string; content: string; metadata: Record<string, any>; }> = [];
for (const doc of documents) { const splits = await splitter.splitText(doc.content);
splits.forEach((text, index) => {
chunks.push({
id: ${doc.id}-chunk-${index},
content: text,
metadata: {
source: doc.source,
documentId: doc.id,
chunkIndex: index,
...doc.metadata,
},
});
});
}
// Upsert in batches await upsertDocuments(chunks);
return { totalChunks: chunks.length }; } ```
Best Practices
- Choose the right dimension: Match embedding model
- Use namespaces: Organize data logically
- Add metadata: Enable filtering
- Batch operations: Reduce API calls
- Index appropriately: HNSW for speed, IVF for memory
- Monitor performance: Track latency and recall
- Cache embeddings: Avoid regenerating
- Use hybrid search: Combine vector and keyword
Output Checklist
Every vector database setup should include:
- Database client configured
- Embedding generation function
- Collection/index creation
- Document upsert with metadata
- Similarity search function
- Filtered search capability
- Batch processing for large datasets
- Delete/update operations
- Error handling
- Performance monitoring
Subgroup 3: Embedding Pipeline Builder
Embedding Pipeline Builder
Build production-ready document embedding and retrieval pipelines.
Core Workflow
- Load documents: Ingest from various sources
- Preprocess text: Clean and normalize
- Chunk documents: Split into optimal sizes
- Generate embeddings: Create vector representations
- Index vectors: Store in vector database
- Optimize retrieval: Tune for accuracy
Pipeline Architecture
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Loader │───▶│ Preprocessor │───▶│ Chunker │
└─────────────┘ └─────────────┘ └─────────────┘
│
▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Retriever │◀───│ Indexer │◀───│ Embedder │
└─────────────┘ └─────────────┘ └─────────────┘Document Loading
Multi-Source Loader
// pipeline/loaders.ts
import { readFile, readdir } from 'fs/promises';
import { join, extname } from 'path';
import pdf from 'pdf-parse';
import mammoth from 'mammoth';interface LoadedDocument { id: string; content: string; metadata: { source: string; type: string; title?: string; createdAt?: Date; [key: string]: any; }; }
export class DocumentLoader { async loadFile(filePath: string): Promise<LoadedDocument> { const ext = extname(filePath).toLowerCase(); const content = await this.extractContent(filePath, ext);
return { id: this.generateId(filePath), content, metadata: { source: filePath, type: ext.slice(1), }, }; }
async loadDirectory(dirPath: string): Promise<LoadedDocument[]> { const files = await readdir(dirPath, { recursive: true }); const documents: LoadedDocument[] = [];
for (const file of files) {
const filePath = join(dirPath, file);
try {
const doc = await this.loadFile(filePath);
documents.push(doc);
} catch (error) {
console.error(Failed to load ${filePath}:, error);
}
}
return documents; }
private async extractContent(filePath: string, ext: string): Promise<string> { const buffer = await readFile(filePath);
switch (ext) { case '.txt': case '.md': return buffer.toString('utf-8');
case '.pdf': const pdfData = await pdf(buffer); return pdfData.text;
case '.docx': const result = await mammoth.extractRawText({ buffer }); return result.value;
case '.json': const json = JSON.parse(buffer.toString('utf-8')); return this.flattenJson(json);
default:
throw new Error(Unsupported file type: ${ext});
}
}
private flattenJson(obj: any, prefix = ''): string { const parts: string[] = [];
for (const [key, value] of Object.entries(obj)) {
const path = prefix ? ${prefix}.${key} : key;
if (typeof value === 'object' && value !== null) {
parts.push(this.flattenJson(value, path));
} else {
parts.push(${path}: ${value});
}
}
return parts.join('\n'); }
private generateId(source: string): string {
return doc_${Buffer.from(source).toString('base64url').slice(0, 16)};
}
}
```
Web Loader
// pipeline/web-loader.ts
import { JSDOM } from 'jsdom';
import { Readability } from '@mozilla/readability';export class WebLoader { async loadUrl(url: string): Promise<LoadedDocument> { const response = await fetch(url); const html = await response.text();
const dom = new JSDOM(html, { url }); const reader = new Readability(dom.window.document); const article = reader.parse();
return { id: this.generateId(url), content: article?.textContent || '', metadata: { source: url, type: 'webpage', title: article?.title, byline: article?.byline, }, }; }
async loadSitemap(sitemapUrl: string): Promise<LoadedDocument[]> { const response = await fetch(sitemapUrl); const xml = await response.text();
const urlMatches = xml.match(/<loc>([^<]+)<\/loc>/g) || []; const urls = urlMatches.map((match) => match.replace('<loc>', '').replace('</loc>', '') );
const documents: LoadedDocument[] = [];
for (const url of urls.slice(0, 100)) { // Limit for safety
try {
const doc = await this.loadUrl(url);
documents.push(doc);
await new Promise((r) => setTimeout(r, 1000)); // Rate limit
} catch (error) {
console.error(Failed to load ${url}:, error);
}
}
return documents; } } ```
Text Preprocessing
// pipeline/preprocessor.ts
export class TextPreprocessor {
process(text: string): string {
return this.pipeline(text, [
this.normalizeWhitespace,
this.removeSpecialCharacters,
this.normalizeUnicode,
this.removeExcessiveNewlines,
]);
}private pipeline(text: string, transforms: Array<(t: string) => string>): string { return transforms.reduce((t, fn) => fn(t), text); }
private normalizeWhitespace(text: string): string { return text .replace(/\t/g, ' ') .replace(/[ ]+/g, ' ') .trim(); }
private removeSpecialCharacters(text: string): string { return text .replace(/[^\w\s\n.,!?;:'"()-]/g, '') .replace(/\s+/g, ' '); }
private normalizeUnicode(text: string): string { return text.normalize('NFKC'); }
private removeExcessiveNewlines(text: string): string { return text.replace(/\n{3,}/g, '\n\n'); } } ```
Text Chunking
Smart Chunker
// pipeline/chunker.ts
export interface Chunk {
id: string;
content: string;
metadata: {
documentId: string;
chunkIndex: number;
startChar: number;
endChar: number;
[key: string]: any;
};
}export interface ChunkerOptions { chunkSize: number; chunkOverlap: number; separators?: string[]; }
export class RecursiveChunker { private options: ChunkerOptions; private separators: string[];
constructor(options: ChunkerOptions) { this.options = options; this.separators = options.separators || [ '\n\n', // Paragraphs '\n', // Lines '. ', // Sentences ', ', // Clauses ' ', // Words '', // Characters ]; }
chunk(document: LoadedDocument): Chunk[] { const chunks: Chunk[] = []; const textChunks = this.splitText(document.content, 0);
textChunks.forEach((text, index) => {
chunks.push({
id: ${document.id}_chunk_${index},
content: text.content,
metadata: {
documentId: document.id,
chunkIndex: index,
startChar: text.start,
endChar: text.end,
...document.metadata,
},
});
});
return chunks; }
private splitText( text: string, separatorIndex: number ): Array<{ content: string; start: number; end: number }> { const separator = this.separators[separatorIndex]; const { chunkSize, chunkOverlap } = this.options;
// If text fits in chunk size, return as is if (text.length <= chunkSize) { return [{ content: text, start: 0, end: text.length }]; }
// Try splitting with current separator const parts = separator ? text.split(separator) : text.split(''); const results: Array<{ content: string; start: number; end: number }> = []; let currentChunk = ''; let currentStart = 0; let position = 0;
for (const part of parts) { const partWithSep = part + (separator || '');
if ((currentChunk + partWithSep).length > chunkSize) { if (currentChunk) { // If chunk is still too large, try next separator if (currentChunk.length > chunkSize && separatorIndex < this.separators.length - 1) { const subChunks = this.splitText(currentChunk, separatorIndex + 1); results.push(...subChunks.map((c) => ({ ...c, start: c.start + currentStart, end: c.end + currentStart, }))); } else { results.push({ content: currentChunk.trim(), start: currentStart, end: position, }); }
// Start new chunk with overlap const overlapText = this.getOverlapText(currentChunk, chunkOverlap); currentChunk = overlapText + partWithSep; currentStart = position - overlapText.length; } else { currentChunk = partWithSep; } } else { currentChunk += partWithSep; }
position += partWithSep.length; }
// Don't forget the last chunk if (currentChunk.trim()) { results.push({ content: currentChunk.trim(), start: currentStart, end: position, }); }
return results; }
private getOverlapText(text: string, overlapSize: number): string { if (overlapSize === 0) return ''; return text.slice(-overlapSize); } } ```
Semantic Chunker
// pipeline/semantic-chunker.ts
import { generateEmbedding } from '../embeddings';export class SemanticChunker { private similarityThreshold: number; private minChunkSize: number; private maxChunkSize: number;
constructor(options: { similarityThreshold?: number; minChunkSize?: number; maxChunkSize?: number; } = {}) { this.similarityThreshold = options.similarityThreshold || 0.8; this.minChunkSize = options.minChunkSize || 100; this.maxChunkSize = options.maxChunkSize || 2000; }
async chunk(document: LoadedDocument): Promise<Chunk[]> { // Split into sentences const sentences = this.splitIntoSentences(document.content);
// Generate embeddings for each sentence const embeddings = await Promise.all( sentences.map((s) => generateEmbedding(s)) );
// Group similar sentences const groups = this.groupBySimilarity(sentences, embeddings);
// Create chunks from groups
return groups.map((group, index) => ({
id: ${document.id}_semantic_${index},
content: group.join(' '),
metadata: {
documentId: document.id,
chunkIndex: index,
chunkType: 'semantic',
...document.metadata,
},
}));
}
private splitIntoSentences(text: string): string[] { return text .split(/(?<=[.!?])\s+/) .filter((s) => s.length > 10); }
private groupBySimilarity(sentences: string[], embeddings: number[][]): string[][] { const groups: string[][] = []; let currentGroup: string[] = []; let currentLength = 0;
for (let i = 0; i < sentences.length; i++) { const sentence = sentences[i];
if (currentGroup.length === 0) { currentGroup.push(sentence); currentLength = sentence.length; continue; }
// Check similarity with previous sentence const similarity = this.cosineSimilarity(embeddings[i], embeddings[i - 1]);
if ( similarity > this.similarityThreshold && currentLength + sentence.length < this.maxChunkSize ) { currentGroup.push(sentence); currentLength += sentence.length; } else { if (currentLength >= this.minChunkSize) { groups.push(currentGroup); } currentGroup = [sentence]; currentLength = sentence.length; } }
if (currentGroup.length > 0) { groups.push(currentGroup); }
return groups; }
private cosineSimilarity(a: number[], b: number[]): number { let dotProduct = 0; let normA = 0; let normB = 0;
for (let i = 0; i < a.length; i++) { dotProduct += a[i] * b[i]; normA += a[i] * a[i]; normB += b[i] * b[i]; }
return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB)); } } ```
Embedding Generation
// pipeline/embedder.ts
import OpenAI from 'openai';
import pLimit from 'p-limit';const openai = new OpenAI();
export interface EmbeddedChunk extends Chunk { embedding: number[]; }
export class Embedder { private model: string; private batchSize: number; private concurrency: number;
constructor(options: { model?: string; batchSize?: number; concurrency?: number; } = {}) { this.model = options.model || 'text-embedding-3-small'; this.batchSize = options.batchSize || 100; this.concurrency = options.concurrency || 5; }
async embedChunks(chunks: Chunk[]): Promise<EmbeddedChunk[]> { const limit = pLimit(this.concurrency); const batches = this.createBatches(chunks);
const embeddedBatches = await Promise.all( batches.map((batch) => limit(() => this.embedBatch(batch)) ) );
return embeddedBatches.flat(); }
private createBatches(chunks: Chunk[]): Chunk[][] { const batches: Chunk[][] = []; for (let i = 0; i < chunks.length; i += this.batchSize) { batches.push(chunks.slice(i, i + this.batchSize)); } return batches; }
private async embedBatch(chunks: Chunk[]): Promise<EmbeddedChunk[]> { const response = await openai.embeddings.create({ model: this.model, input: chunks.map((c) => c.content), });
return chunks.map((chunk, i) => ({ ...chunk, embedding: response.data[i].embedding, })); } } ```
Complete Pipeline
// pipeline/index.ts
import { DocumentLoader } from './loaders';
import { TextPreprocessor } from './preprocessor';
import { RecursiveChunker } from './chunker';
import { Embedder } from './embedder';
import { VectorStore } from './vectorstore';export interface PipelineOptions { chunkSize?: number; chunkOverlap?: number; embeddingModel?: string; namespace?: string; }
export class EmbeddingPipeline { private loader: DocumentLoader; private preprocessor: TextPreprocessor; private chunker: RecursiveChunker; private embedder: Embedder; private vectorStore: VectorStore;
constructor(options: PipelineOptions = {}) { this.loader = new DocumentLoader(); this.preprocessor = new TextPreprocessor(); this.chunker = new RecursiveChunker({ chunkSize: options.chunkSize || 1000, chunkOverlap: options.chunkOverlap || 200, }); this.embedder = new Embedder({ model: options.embeddingModel, }); this.vectorStore = new VectorStore(options.namespace); }
async ingestFile(filePath: string): Promise<{ chunks: number }> { // Load const document = await this.loader.loadFile(filePath);
return this.processDocument(document); }
async ingestDirectory(dirPath: string): Promise<{ documents: number; chunks: number }> { const documents = await this.loader.loadDirectory(dirPath); let totalChunks = 0;
for (const doc of documents) { const result = await this.processDocument(doc); totalChunks += result.chunks; }
return { documents: documents.length, chunks: totalChunks }; }
async ingestUrl(url: string): Promise<{ chunks: number }> { const webLoader = new WebLoader(); const document = await webLoader.loadUrl(url);
return this.processDocument(document); }
private async processDocument(document: LoadedDocument): Promise<{ chunks: number }> { // Preprocess document.content = this.preprocessor.process(document.content);
// Chunk const chunks = this.chunker.chunk(document);
// Embed const embeddedChunks = await this.embedder.embedChunks(chunks);
// Store await this.vectorStore.upsert(embeddedChunks);
return { chunks: embeddedChunks.length }; }
async query(query: string, topK = 5): Promise<Chunk[]> { return this.vectorStore.search(query, topK); } }
// Usage const pipeline = new EmbeddingPipeline({ chunkSize: 1000, chunkOverlap: 200, namespace: 'knowledge-base', });
// Ingest documents await pipeline.ingestDirectory('./documents'); await pipeline.ingestUrl('https://docs.example.com');
// Query const results = await pipeline.query('How do I configure authentication?'); ```
Best Practices
- Chunk size: Balance context vs noise (500-1500 chars)
- Overlap: 10-20% prevents context loss
- Preprocessing: Clean but preserve meaning
- Batch embeddings: Reduce API calls
- Add metadata: Enable filtering
- Semantic chunking: For high-quality retrieval
- Hybrid search: Combine vector and keyword
- Monitor quality: Test retrieval accuracy
Output Checklist
Every embedding pipeline should include:
- Multi-format document loading
- Text preprocessing and cleaning
- Smart chunking with overlap
- Batch embedding generation
- Metadata preservation
- Vector store indexing
- Semantic search capability
- Error handling and logging
- Progress tracking
- Retrieval quality testing
Subgroup 4: Doc to Vector Dataset Generator
Doc-to-Vector Dataset Generator
Transform documents into high-quality vector search datasets.
Pipeline Steps
- Extract text from various formats (PDF, DOCX, HTML)
- Clean text (remove noise, normalize)
- Chunk strategically (semantic boundaries)
- Add metadata (source, timestamps, classification)
- Deduplicate (near-duplicate detection)
- Quality check (length, content validation)
- Export JSONL (one chunk per line)
Text Extraction
# PDF extraction
import pymupdfdef extract_pdf(filepath: str) -> str: doc = pymupdf.open(filepath) text = "" for page in doc: text += page.get_text() return text
Markdown extraction
def extract_markdown(filepath: str) -> str: with open(filepath) as f: return f.read() ```
Text Cleaning
import redef clean_text(text: str) -> str: # Remove extra whitespace text = re.sub(r'\s+', ' ', text)
Remove page numbers
text = re.sub(r'Page \d+', '', text)
Remove URLs (optional)
text = re.sub(r'http\S+', '', text)
Normalize unicode
text = text.encode('utf-8', 'ignore').decode('utf-8')
return text.strip() ```
Semantic Chunking
def semantic_chunk(text: str, max_chunk_size: int = 1000) -> List[str]:
"""Chunk at semantic boundaries (paragraphs, sentences)"""
# Split by paragraphs first
paragraphs = text.split('\n\n')chunks = [] current_chunk = ""
for para in paragraphs: if len(current_chunk) + len(para) <= max_chunk_size: current_chunk += para + "\n\n" else: if current_chunk: chunks.append(current_chunk.strip()) current_chunk = para + "\n\n"
if current_chunk: chunks.append(current_chunk.strip())
return chunks ```
Metadata Extraction
def extract_metadata(filepath: str, chunk: str, chunk_idx: int) -> dict:
return {
"source": filepath,
"chunk_id": f"{hash(filepath)}_{chunk_idx}",
"chunk_index": chunk_idx,
"char_count": len(chunk),
"word_count": len(chunk.split()),
"created_at": datetime.now().isoformat(),Content classification
"has_code": bool(re.search(r'``|def |class |function', chunk)),
"has_table": bool(re.search(r'\|.*\|', chunk)),
"language": detect_language(chunk),
}
`
Deduplication
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similaritydef deduplicate_chunks(chunks: List[dict], threshold: float = 0.95) -> List[dict]: """Remove near-duplicate chunks""" texts = [c["text"] for c in chunks]
Compute TF-IDF vectors
vectorizer = TfidfVectorizer() vectors = vectorizer.fit_transform(texts)
Compute pairwise similarity
similarity_matrix = cosine_similarity(vectors)
Find duplicates
to_remove = set() for i in range(len(chunks)): if i in to_remove: continue for j in range(i+1, len(chunks)): if similarity_matrix[i][j] > threshold: to_remove.add(j)
Return unique chunks
return [c for i, c in enumerate(chunks) if i not in to_remove] ```
Quality Checks
def quality_check(chunk: dict) -> bool:
"""Validate chunk quality"""
text = chunk["text"]Min length check
if len(text) < 50: return False
Max length check
if len(text) > 5000: return False
Content check (not just numbers/symbols)
alpha_ratio = sum(c.isalpha() for c in text) / len(text) if alpha_ratio < 0.5: return False
Language check (English only)
if chunk["metadata"]["language"] != "en": return False
return True ```
JSONL Export
import jsondef export_jsonl(chunks: List[dict], output_path: str): """Export chunks as JSONL (one JSON object per line)""" with open(output_path, 'w') as f: for chunk in chunks: f.write(json.dumps(chunk) + '\n')
Example output format
{ "text": "Chunk text content here...", "metadata": { "source": "docs/auth.md", "chunk_id": "abc123_0", "chunk_index": 0, "char_count": 542, "word_count": 89, "has_code": true } } ```
Complete Pipeline
def process_documents(input_dir: str, output_path: str):
all_chunks = []Process each document
for filepath in glob(f"{input_dir}/**/*.md"): # Extract and clean text = extract_markdown(filepath) text = clean_text(text)
Chunk
chunks = semantic_chunk(text)
Add metadata
for i, chunk in enumerate(chunks): chunk_obj = { "text": chunk, "metadata": extract_metadata(filepath, chunk, i) }
Quality check
if quality_check(chunk_obj): all_chunks.append(chunk_obj)
Deduplicate
unique_chunks = deduplicate_chunks(all_chunks)
Export
export_jsonl(unique_chunks, output_path)
print(f"Processed {len(unique_chunks)} chunks") ```
Best Practices
- Chunk at semantic boundaries
- Rich metadata for filtering
- Deduplicate aggressively
- Quality checks prevent garbage
- JSONL format for streaming
- Version your datasets
Output Checklist
- Text extraction from all formats
- Cleaning pipeline implemented
- Semantic chunking strategy
- Metadata schema defined
- Deduplication logic
- Quality validation checks
- JSONL export format
- Dataset statistics logged
Subgroup 5: Multi-Agent System Orchestrator
AI Agent Orchestrator
Build coordinated multi-agent systems for complex task automation.
Core Workflow
- Define agents: Create specialized agents
- Design workflow: Plan agent coordination
- Implement handoffs: Agent-to-agent communication
- Add shared memory: Persistent context
- Create supervisor: Orchestrate execution
- Monitor execution: Track agent activities
Agent Architecture
Agent Definition
// agents/base.ts
import { ChatOpenAI } from '@langchain/openai';
import { SystemMessage, HumanMessage, AIMessage } from '@langchain/core/messages';export interface AgentConfig { name: string; role: string; systemPrompt: string; tools?: Tool[]; model?: string; }
export interface AgentResponse { content: string; toolCalls?: ToolCall[]; nextAgent?: string; completed?: boolean; }
export class Agent { private model: ChatOpenAI; private config: AgentConfig; private messageHistory: BaseMessage[] = [];
constructor(config: AgentConfig) { this.config = config; this.model = new ChatOpenAI({ modelName: config.model || 'gpt-4-turbo-preview', temperature: 0.7, }); }
async execute(input: string, context?: Record<string, any>): Promise<AgentResponse> { const systemMessage = new SystemMessage( this.buildSystemPrompt(context) );
const messages = [ systemMessage, ...this.messageHistory, new HumanMessage(input), ];
const response = await this.model.invoke(messages, { tools: this.config.tools, });
this.messageHistory.push(new HumanMessage(input)); this.messageHistory.push(new AIMessage(response.content as string));
return this.parseResponse(response); }
private buildSystemPrompt(context?: Record<string, any>): string { let prompt = this.config.systemPrompt;
if (context) {
prompt += \n\nContext:\n${JSON.stringify(context, null, 2)};
}
return prompt; }
private parseResponse(response: any): AgentResponse { // Parse tool calls and determine next actions return { content: response.content as string, toolCalls: response.tool_calls, completed: response.content?.includes('[TASK_COMPLETE]'), }; }
clearHistory() { this.messageHistory = []; } } ```
Specialized Agents
// agents/specialists.ts
import { Agent, AgentConfig } from './base';- export const ResearchAgent = new Agent({
- name: 'researcher',
- role: 'Research Specialist',
- systemPrompt: `You are a research specialist. Your job is to:
- Search for and gather relevant information
- Analyze sources and extract key insights
- Summarize findings clearly
- Cite sources when possible
When you have gathered sufficient information, include [TASK_COMPLETE] in your response. If you need help from another agent, specify: [HANDOFF:agent_name]`, tools: [searchTool, webScrapeTool], });
- export const WriterAgent = new Agent({
- name: 'writer',
- role: 'Content Writer',
- systemPrompt: `You are a professional content writer. Your job is to:
- Create engaging, well-structured content
- Adapt tone and style to the target audience
- Incorporate research and data effectively
- Edit and refine for clarity
Use the research provided to create compelling content. When complete, include [TASK_COMPLETE].`, });
- export const ReviewerAgent = new Agent({
- name: 'reviewer',
- role: 'Quality Reviewer',
- systemPrompt: `You are a quality reviewer. Your job is to:
- Review content for accuracy and clarity
- Check for errors and inconsistencies
- Suggest improvements
- Approve or request revisions
Provide specific feedback. If approved, include [APPROVED]. If revisions needed, include [REVISIONS_NEEDED] with specific changes.`, });
- export const PlannerAgent = new Agent({
- name: 'planner',
- role: 'Task Planner',
- systemPrompt: `You are a task planner. Your job is to:
- Break down complex tasks into subtasks
- Identify which specialist agent should handle each subtask
- Create an execution order
- Track progress
Output a structured plan in JSON format:
{
"goal": "...",
"steps": [
{ "step": 1, "agent": "researcher", "task": "..." },
{ "step": 2, "agent": "writer", "task": "..." }
]
},
});
Orchestrator
Simple Sequential Orchestrator
// orchestrator/sequential.ts
import { Agent } from '../agents/base';interface WorkflowStep { agent: Agent; task: string; inputFrom?: string; }
export class SequentialOrchestrator { private agents: Map<string, Agent> = new Map(); private results: Map<string, string> = new Map();
registerAgent(name: string, agent: Agent) { this.agents.set(name, agent); }
async execute(workflow: WorkflowStep[]): Promise<Record<string, string>> { for (const step of workflow) { const agent = step.agent;
// Get input from previous step if specified
let input = step.task;
if (step.inputFrom && this.results.has(step.inputFrom)) {
input = ${step.task}\n\nPrevious output:\n${this.results.get(step.inputFrom)};
}
console.log(Executing: ${agent.name} - ${step.task});
const result = await agent.execute(input); this.results.set(agent.name, result.content);
console.log(Completed: ${agent.name});
}
return Object.fromEntries(this.results); } }
// Usage const orchestrator = new SequentialOrchestrator(); orchestrator.registerAgent('researcher', ResearchAgent); orchestrator.registerAgent('writer', WriterAgent); orchestrator.registerAgent('reviewer', ReviewerAgent);
const results = await orchestrator.execute([ { agent: ResearchAgent, task: 'Research the latest AI trends in 2024' }, { agent: WriterAgent, task: 'Write a blog post about AI trends', inputFrom: 'researcher' }, { agent: ReviewerAgent, task: 'Review the blog post', inputFrom: 'writer' }, ]); ```
Supervisor Orchestrator
// orchestrator/supervisor.ts
import { ChatOpenAI } from '@langchain/openai';
import { Agent } from '../agents/base';interface AgentRegistry { [name: string]: { agent: Agent; description: string; }; }
export class SupervisorOrchestrator { private supervisor: ChatOpenAI; private agents: AgentRegistry = {}; private sharedContext: Record<string, any> = {}; private maxIterations = 10;
constructor() { this.supervisor = new ChatOpenAI({ modelName: 'gpt-4-turbo-preview', temperature: 0, }); }
registerAgent(name: string, agent: Agent, description: string) { this.agents[name] = { agent, description }; }
async execute(task: string): Promise<string> { let iteration = 0; let currentTask = task; const history: string[] = [];
while (iteration < this.maxIterations) { iteration++;
// Supervisor decides next action const decision = await this.supervise(currentTask, history);
if (decision.complete) { return decision.finalResponse!; }
// Execute selected agent const { agent } = this.agents[decision.nextAgent!]; const result = await agent.execute(decision.agentTask!, this.sharedContext);
// Update shared context
this.sharedContext[decision.nextAgent!] = result.content;
history.push(${decision.nextAgent}: ${result.content});
// Check for handoff
if (result.nextAgent) {
currentTask = Continue with: ${result.content};
}
}
throw new Error('Max iterations reached'); }
private async supervise(
task: string,
history: string[]
): Promise<{
complete: boolean;
finalResponse?: string;
nextAgent?: string;
agentTask?: string;
}> {
const agentList = Object.entries(this.agents)
.map(([name, { description }]) => - ${name}: ${description})
.join('\n');
const prompt = `You are a supervisor coordinating AI agents.
Available agents: ${agentList}
Task: ${task}
History: ${history.join('\n')}
Decide the next action. Respond in JSON: { "thought": "your reasoning", "complete": false, "nextAgent": "agent_name", "agentTask": "specific task for the agent" }
Or if the task is complete: { "thought": "your reasoning", "complete": true, "finalResponse": "the final answer" }`;
const response = await this.supervisor.invoke([{ role: 'user', content: prompt }]); return JSON.parse(response.content as string); } } ```
Parallel Agent Execution
// orchestrator/parallel.ts
export class ParallelOrchestrator {
private agents: Map<string, Agent> = new Map();async executeParallel( tasks: Array<{ agentName: string; task: string }> ): Promise<Map<string, string>> { const results = new Map<string, string>();
await Promise.all(
tasks.map(async ({ agentName, task }) => {
const agent = this.agents.get(agentName);
if (!agent) throw new Error(Agent ${agentName} not found);
const result = await agent.execute(task); results.set(agentName, result.content); }) );
return results; }
async fanOutFanIn( task: string, agentNames: string[], aggregator: Agent ): Promise<string> { // Fan out: same task to multiple agents const parallelResults = await this.executeParallel( agentNames.map((name) => ({ agentName: name, task })) );
// Fan in: aggregate results
const aggregatedInput = Array.from(parallelResults.entries())
.map(([name, result]) => ${name}:\n${result})
.join('\n\n---\n\n');
const finalResult = await aggregator.execute(
Synthesize these perspectives:\n\n${aggregatedInput}
);
return finalResult.content; } } ```
Shared Memory
// memory/shared.ts
import { Redis } from 'ioredis';export class SharedMemory { private redis: Redis; private prefix: string;
constructor(sessionId: string) {
this.redis = new Redis(process.env.REDIS_URL!);
this.prefix = agent:${sessionId}:;
}
async set(key: string, value: any, ttl?: number): Promise<void> { const serialized = JSON.stringify(value); if (ttl) { await this.redis.setex(this.prefix + key, ttl, serialized); } else { await this.redis.set(this.prefix + key, serialized); } }
async get<T>(key: string): Promise<T | null> { const value = await this.redis.get(this.prefix + key); return value ? JSON.parse(value) : null; }
async append(key: string, item: any): Promise<void> { const list = (await this.get<any[]>(key)) || []; list.push(item); await this.set(key, list); }
async getConversation(): Promise<Message[]> { return (await this.get<Message[]>('conversation')) || []; }
async addMessage(message: Message): Promise<void> { await this.append('conversation', message); }
async getAgentOutputs(): Promise<Record<string, string>> { return (await this.get<Record<string, string>>('outputs')) || {}; }
async setAgentOutput(agent: string, output: string): Promise<void> { const outputs = await this.getAgentOutputs(); outputs[agent] = output; await this.set('outputs', outputs); }
async clear(): Promise<void> { const keys = await this.redis.keys(this.prefix + '*'); if (keys.length > 0) { await this.redis.del(...keys); } } } ```
Event-Driven Agent Communication
// events/agent-events.ts
import { EventEmitter } from 'events';export class AgentEventBus extends EventEmitter { private static instance: AgentEventBus;
static getInstance(): AgentEventBus { if (!this.instance) { this.instance = new AgentEventBus(); } return this.instance; }
emitAgentMessage(from: string, to: string, message: any) {
this.emit(message:${to}, { from, message, timestamp: new Date() });
}
emitAgentComplete(agent: string, result: any) { this.emit('agent:complete', { agent, result, timestamp: new Date() }); }
emitAgentError(agent: string, error: Error) { this.emit('agent:error', { agent, error, timestamp: new Date() }); }
onMessage(agentName: string, handler: (message: any) => void) {
this.on(message:${agentName}, handler);
}
onAnyComplete(handler: (event: any) => void) { this.on('agent:complete', handler); } }
// Usage const eventBus = AgentEventBus.getInstance();
// Agent listens for messages
eventBus.onMessage('writer', async ({ from, message }) => {
console.log(Writer received from ${from}:, message);
const result = await WriterAgent.execute(message);
eventBus.emitAgentComplete('writer', result);
});
// Orchestrator listens for completions
eventBus.onAnyComplete(({ agent, result }) => {
console.log(${agent} completed:, result.content);
});
```
Workflow Definition
// workflows/definition.ts
interface WorkflowDefinition {
name: string;
description: string;
agents: string[];
steps: WorkflowStep[];
errorHandling: 'retry' | 'fallback' | 'abort';
}const ContentCreationWorkflow: WorkflowDefinition = { name: 'content-creation', description: 'Create and publish content', agents: ['planner', 'researcher', 'writer', 'reviewer'], steps: [ { id: 'plan', agent: 'planner', input: '{task}', outputKey: 'plan', }, { id: 'research', agent: 'researcher', input: 'Research for: {plan.topic}', outputKey: 'research', parallel: true, }, { id: 'write', agent: 'writer', input: 'Write about {plan.topic} using research: {research}', outputKey: 'draft', dependsOn: ['research'], }, { id: 'review', agent: 'reviewer', input: 'Review: {draft}', outputKey: 'review', loop: { condition: 'review.approved === false', maxIterations: 3, backTo: 'write', }, }, ], errorHandling: 'retry', }; ```
Best Practices
- Single responsibility: Each agent has one clear role
- Clear handoffs: Explicit agent-to-agent communication
- Shared context: Use memory for persistent state
- Iteration limits: Prevent infinite loops
- Error handling: Graceful degradation
- Observability: Log all agent actions
- Testing: Test agents individually and together
- Timeout handling: Prevent stuck agents
Output Checklist
Every agent system should include:
- Agent base class with common functionality
- Specialized agents with clear roles
- Orchestrator for coordination
- Shared memory system
- Event-based communication
- Workflow definitions
- Error handling and retries
- Iteration limits
- Logging and monitoring
- Agent handoff protocol
Subgroup 6: Agent Workflow & Orchestration Planner
Agent Orchestration Planner
Design robust multi-step agent systems with tools and error handling.
Agent Architecture
User Query → Planning → Tool Selection → Tool Execution → Result Synthesis → Response
↓ ↓ ↓ ↓
Memory Retry Logic Validation Cost TrackingAgent Loop Pattern
from typing import List, Dict, Anyclass Agent: def __init__(self, tools: List[Tool], max_iterations: int = 5): self.tools = tools self.max_iterations = max_iterations self.memory = [] self.cost_tracker = CostTracker()
def run(self, query: str) -> str: self.memory.append({"role": "user", "content": query})
for iteration in range(self.max_iterations): # Decide next action action = self.plan_next_action()
if action["type"] == "final_answer": return action["content"]
Execute tool
result = self.execute_tool(action["tool"], action["params"])
Track cost
self.cost_tracker.add(result["cost"])
Check budget
if self.cost_tracker.exceeds_limit(): return self.budget_exceeded_response()
Add to memory
self.memory.append({ "role": "tool", "tool": action["tool"], "result": result["data"] })
return "Max iterations reached"
def plan_next_action(self) -> Dict: prompt = self.build_planning_prompt() response = llm(prompt) return parse_action(response) ```
Tool Orchestration
TOOL_ORDER = {
"search_web": 1, # Always try search first
"query_database": 2, # Then database
"call_api": 3, # Then external APIs
"generate_content": 4, # Finally generate
}def select_tools(query: str, available_tools: List[Tool]) -> List[Tool]: """Select and order tools based on query""" # Use LLM to select relevant tools tool_selection_prompt = f""" Given this query: "{query}"
Which of these tools are needed? {[t.name for t in available_tools]}
Return JSON array of tool names in execution order. """
selected_names = json.loads(llm(tool_selection_prompt)) selected_tools = [t for t in available_tools if t.name in selected_names]
Sort by predefined order
selected_tools.sort(key=lambda t: TOOL_ORDER.get(t.name, 999))
return selected_tools ```
Retry & Fallback Logic
def execute_with_retry(tool: Tool, params: Dict, max_retries: int = 3):
"""Execute tool with exponential backoff retry"""
for attempt in range(max_retries):
try:
result = tool.execute(params)
return {"success": True, "data": result}
except ToolError as e:
if attempt == max_retries - 1:
# Try fallback tool
fallback = get_fallback_tool(tool.name)
if fallback:
return execute_with_retry(fallback, params, 1)return {"success": False, "error": str(e)}
Wait before retry
time.sleep(2 ** attempt)
FALLBACK_TOOLS = { "search_web": "query_database", "call_api": "use_cached_data", } ```
State Management
class AgentState:
def __init__(self):
self.memory = []
self.tool_results = {}
self.costs = 0.0
self.iteration = 0def add_message(self, role: str, content: str): self.memory.append({"role": role, "content": content})
def add_tool_result(self, tool_name: str, result: Any): self.tool_results[tool_name] = result
def get_context(self) -> str: """Build context from memory for next LLM call""" return "\n".join([ f"{msg['role']}: {msg['content']}" for msg in self.memory[-5:] # Last 5 messages ]) ```
Budget & Cost Controls
class CostTracker:
def __init__(self, max_cost: float = 1.0):
self.max_cost = max_cost
self.total_cost = 0.0
self.breakdown = {}def add(self, cost: float, category: str = "llm"): self.total_cost += cost self.breakdown[category] = self.breakdown.get(category, 0) + cost
def exceeds_limit(self) -> bool: return self.total_cost >= self.max_cost
def remaining(self) -> float: return self.max_cost - self.total_cost
Use in agent
if cost_tracker.exceeds_limit(): return f"Budget limit reached. Used ${cost_tracker.total_cost:.4f}" ```
Orchestration Diagram
graph TD
A[User Query] --> B[Plan Action]
B --> C{Action Type?}
C -->|Tool Call| D[Execute Tool]
C -->|Final Answer| E[Return Response]
D --> F[Validate Result]
F -->|Success| G[Update Memory]
F -->|Failure| H[Retry/Fallback]
H --> D
G --> I{Budget OK?}
I -->|Yes| B
I -->|No| J[Budget Exceeded]
J --> EPlanning Prompt
def build_planning_prompt(state: AgentState) -> str:
return f"""
You are an agent that can use tools to answer questions.Available tools: {json.dumps([t.schema for t in tools], indent=2)}
Conversation history: {state.get_context()}
- Based on the conversation, decide your next action:
- Call a tool (specify tool name and parameters)
- Provide final answer
If calling a tool, respond with: {{"action": "tool_call", "tool": "tool_name", "params": {{...}}}}
If providing final answer, respond with: {{"action": "final_answer", "content": "your answer"}}
Think step by step about what information you need. """ ```
Multi-Agent Coordination
class MultiAgentSystem:
def __init__(self):
self.agents = {
"researcher": ResearchAgent(),
"coder": CodeAgent(),
"reviewer": ReviewAgent(),
}def run(self, task: str): # Researcher gathers information context = self.agents["researcher"].run(task)
Coder generates solution
code = self.agents["coder"].run(f"{task}\nContext: {context}")
Reviewer validates
review = self.agents["reviewer"].run(f"Review this code:\n{code}")
if review["approved"]: return code else: # Iterate with feedback return self.agents["coder"].run( f"Fix this code based on feedback:\n{review['feedback']}" ) ```
Best Practices
- Limit iterations: Prevent infinite loops
- Budget controls: Track and limit costs
- Tool validation: Verify tool outputs
- Error handling: Graceful fallbacks
- State persistence: Save progress
- Observability: Log all actions
- Human-in-loop: Critical decisions
Output Checklist
- Agent loop implementation
- Tool selection logic
- Retry & fallback strategies
- State management
- Cost tracking
- Budget limits
- Orchestration diagram
- Planning prompts
- Error handling
- Observability/logging
Subgroup 7: Tool & Function Calling Schema Designer
Tool/Function Schema Designer
Design robust tool schemas that LLMs can reliably invoke.
Function Schema Format
// OpenAI function calling format
const searchDocsTool = {
type: "function",
function: {
name: "search_documentation",
description:
"Search through product documentation using semantic search. Use this when the user asks about features, how-tos, or troubleshooting.",
parameters: {
type: "object",
properties: {
query: {
type: "string",
description: "The search query, phrased as a question or keywords",
},
filters: {
type: "object",
properties: {
category: {
type: "string",
enum: ["api", "guides", "tutorials", "troubleshooting"],
description: "Filter by documentation category",
},
version: {
type: "string",
description: "Filter by product version (e.g., 'v2.0')",
},
},
},
max_results: {
type: "integer",
minimum: 1,
maximum: 10,
default: 5,
description: "Maximum number of results to return",
},
},
required: ["query"],
},
},
};Typed Interfaces
// TypeScript types matching schema
interface SearchDocsParams {
query: string;
filters?: {
category?: "api" | "guides" | "tutorials" | "troubleshooting";
version?: string;
};
max_results?: number;
}// Implementation async function search_documentation( params: SearchDocsParams ): Promise<SearchResult[]> { const { query, filters = {}, max_results = 5 } = params;
// Implementation return await vectorStore.search(query, { filter: filters, limit: max_results, }); } ```
Validation Strategy
import { z } from "zod";// Zod schema for runtime validation const searchDocsSchema = z.object({ query: z.string().min(1, "Query cannot be empty"), filters: z .object({ category: z .enum(["api", "guides", "tutorials", "troubleshooting"]) .optional(), version: z.string().optional(), }) .optional(), max_results: z.number().int().min(1).max(10).default(5), });
// Validate before execution function validateAndExecute(toolName: string, params: unknown) { const validated = searchDocsSchema.parse(params); return search_documentation(validated); } ```
Tool Registry
export const TOOLS = {
search_documentation: {
schema: searchDocsTool,
implementation: search_documentation,
validator: searchDocsSchema,
},
create_ticket: {
schema: createTicketTool,
implementation: create_ticket,
validator: createTicketSchema,
},
// ... more tools
};// Execute tool safely
async function executeTool(name: string, params: unknown) {
const tool = TOOLS[name];
if (!tool) throw new Error(Unknown tool: ${name});
const validated = tool.validator.parse(params); return tool.implementation(validated); } ```
Example Calls
// Example 1: Simple search
{
"name": "search_documentation",
"parameters": {
"query": "How do I authenticate API requests?"
}
}// Example 2: With filters { "name": "search_documentation", "parameters": { "query": "rate limiting", "filters": { "category": "api", "version": "v2.0" }, "max_results": 3 } } ```
Error Handling
interface ToolResult {
success: boolean;
data?: any;
error?: {
code: string;
message: string;
};
}async function safeExecuteTool(
name: string,
params: unknown
): Promise<ToolResult> {
try {
const data = await executeTool(name, params);
return { success: true, data };
} catch (error) {
if (error instanceof z.ZodError) {
return {
success: false,
error: {
code: "VALIDATION_ERROR",
message: Invalid parameters: ${error.message},
},
};
}
return {
success: false,
error: {
code: "EXECUTION_ERROR",
message: error.message,
},
};
}
}
```
Best Practices
- Clear descriptions: Explain when to use the tool
- Specific types: Use enums, ranges, patterns
- Sensible defaults: Reduce required parameters
- Validate rigorously: Don't trust LLM output
- Error messages: Help LLM correct mistakes
- Example calls: Show success cases
- Type safety: TypeScript interfaces
Output Checklist
- JSON schema defined
- TypeScript interface
- Validation with Zod
- Implementation function
- Error handling
- Example calls (3+)
- Tool registry entry
- Documentation
Subgroup 8: Structured Output & Schema Extractor
Structured Output Extractor
Extract reliable, typed data from LLM responses.
Core Workflow
- Define schema: Create data structure
- Choose method: Function calling vs prompting
- Generate response: Call LLM with structure
- Validate output: Parse and verify
- Handle errors: Retry or fallback
Methods Comparison
| Method | Reliability | Flexibility | Best For |
|---|---|---|---|
| OpenAI JSON Mode | High | Medium | Simple JSON |
| Function Calling | Very High | High | Complex schemas |
| Instructor | Very High | High | Python/TS apps |
| Zod + Prompting | Medium | High | Custom parsing |
OpenAI Structured Outputs
JSON Mode
// extractors/json-mode.ts
import OpenAI from 'openai';const openai = new OpenAI();
interface ExtractedData { name: string; email: string; phone?: string; company?: string; }
export async function extractContactInfo(text: string): Promise<ExtractedData> {
const response = await openai.chat.completions.create({
model: 'gpt-4-turbo-preview',
response_format: { type: 'json_object' },
messages: [
{
role: 'system',
content: Extract contact information from text. Return JSON with:
{
"name": "string",
"email": "string",
"phone": "string or null",
"company": "string or null"
},
},
{ role: 'user', content: text },
],
});
return JSON.parse(response.choices[0].message.content!); } ```
Structured Outputs (Beta)
// extractors/structured.ts
import OpenAI from 'openai';
import { z } from 'zod';
import { zodResponseFormat } from 'openai/helpers/zod';const ContactSchema = z.object({ name: z.string().describe('Full name of the contact'), email: z.string().email().describe('Email address'), phone: z.string().nullable().describe('Phone number if available'), company: z.string().nullable().describe('Company name if mentioned'), role: z.string().nullable().describe('Job title or role'), });
type Contact = z.infer<typeof ContactSchema>;
export async function extractContact(text: string): Promise<Contact> { const response = await openai.beta.chat.completions.parse({ model: 'gpt-4o-2024-08-06', messages: [ { role: 'system', content: 'Extract contact information from the provided text.', }, { role: 'user', content: text }, ], response_format: zodResponseFormat(ContactSchema, 'contact'), });
return response.choices[0].message.parsed!; } ```
Function Calling
Define Functions
// extractors/function-calling.ts
import OpenAI from 'openai';const openai = new OpenAI();
const functions = [ { name: 'extract_entities', description: 'Extract named entities from text', parameters: { type: 'object', properties: { people: { type: 'array', items: { type: 'object', properties: { name: { type: 'string' }, role: { type: 'string' }, organization: { type: 'string' }, }, required: ['name'], }, }, organizations: { type: 'array', items: { type: 'object', properties: { name: { type: 'string' }, type: { type: 'string', enum: ['company', 'nonprofit', 'government', 'other'] }, }, required: ['name'], }, }, locations: { type: 'array', items: { type: 'object', properties: { name: { type: 'string' }, type: { type: 'string', enum: ['city', 'country', 'address', 'other'] }, }, required: ['name'], }, }, dates: { type: 'array', items: { type: 'object', properties: { text: { type: 'string' }, normalized: { type: 'string', format: 'date' }, }, required: ['text'], }, }, }, required: ['people', 'organizations', 'locations', 'dates'], }, }, ];
export async function extractEntities(text: string) { const response = await openai.chat.completions.create({ model: 'gpt-4-turbo-preview', messages: [ { role: 'system', content: 'Extract all named entities from the provided text.', }, { role: 'user', content: text }, ], functions, function_call: { name: 'extract_entities' }, });
const functionCall = response.choices[0].message.function_call; return JSON.parse(functionCall!.arguments); } ```
Tool Use Pattern
// extractors/tools.ts
import OpenAI from 'openai';const tools: OpenAI.Chat.ChatCompletionTool[] = [ { type: 'function', function: { name: 'classify_intent', description: 'Classify the user intent from their message', parameters: { type: 'object', properties: { intent: { type: 'string', enum: ['question', 'complaint', 'feedback', 'request', 'other'], }, confidence: { type: 'number', minimum: 0, maximum: 1, }, entities: { type: 'object', properties: { product: { type: 'string' }, issue: { type: 'string' }, sentiment: { type: 'string', enum: ['positive', 'negative', 'neutral'] }, }, }, suggestedAction: { type: 'string', }, }, required: ['intent', 'confidence'], }, }, }, ];
export async function classifyMessage(message: string) { const response = await openai.chat.completions.create({ model: 'gpt-4-turbo-preview', messages: [{ role: 'user', content: message }], tools, tool_choice: { type: 'function', function: { name: 'classify_intent' } }, });
const toolCall = response.choices[0].message.tool_calls?.[0]; return JSON.parse(toolCall!.function.arguments); } ```
Zod Schema Validation
With Instructor-like Pattern
// extractors/zod-extractor.ts
import { z } from 'zod';
import OpenAI from 'openai';const openai = new OpenAI();
// Define schema const ProductReviewSchema = z.object({ productName: z.string(), rating: z.number().min(1).max(5), pros: z.array(z.string()), cons: z.array(z.string()), summary: z.string().max(200), wouldRecommend: z.boolean(), sentiment: z.enum(['positive', 'negative', 'mixed', 'neutral']), });
type ProductReview = z.infer<typeof ProductReviewSchema>;
// Generate JSON schema from Zod function zodToJsonSchema(schema: z.ZodObject<any>) { // Simplified - use zod-to-json-schema in production const shape = schema.shape; const properties: Record<string, any> = {}; const required: string[] = [];
for (const [key, value] of Object.entries(shape)) { const zodType = value as z.ZodTypeAny; properties[key] = zodTypeToJson(zodType); if (!zodType.isOptional()) { required.push(key); } }
return { type: 'object', properties, required }; }
export async function extractReview(reviewText: string): Promise<ProductReview> { const schema = zodToJsonSchema(ProductReviewSchema);
const response = await openai.chat.completions.create({
model: 'gpt-4-turbo-preview',
response_format: { type: 'json_object' },
messages: [
{
role: 'system',
content: Extract a structured product review. Return JSON matching this schema:
${JSON.stringify(schema, null, 2)},
},
{ role: 'user', content: reviewText },
],
});
const data = JSON.parse(response.choices[0].message.content!);
// Validate with Zod return ProductReviewSchema.parse(data); } ```
With Retry on Validation Error
// extractors/retry.ts
export async function extractWithRetry<T>(
schema: z.ZodSchema<T>,
prompt: string,
text: string,
maxRetries = 3
): Promise<T> {
let lastError: Error | null = null;for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await openai.chat.completions.create({
model: 'gpt-4-turbo-preview',
response_format: { type: 'json_object' },
messages: [
{ role: 'system', content: prompt },
{ role: 'user', content: text },
// Add error context on retry
...(lastError
? [
{
role: 'user' as const,
content: Previous attempt failed validation: ${lastError.message}. Please fix.,
},
]
: []),
],
});
const data = JSON.parse(response.choices[0].message.content!);
return schema.parse(data);
} catch (error) {
if (error instanceof z.ZodError) {
lastError = new Error(
error.errors.map((e) => ${e.path.join('.')}: ${e.message}).join(', ')
);
} else {
throw error;
}
}
}
throw new Error(Failed after ${maxRetries} attempts: ${lastError?.message});
}
```
Complex Extraction Patterns
Hierarchical Data
// extractors/hierarchical.ts
const DocumentSchema = z.object({
title: z.string(),
authors: z.array(
z.object({
name: z.string(),
affiliation: z.string().optional(),
})
),
abstract: z.string(),
sections: z.array(
z.object({
heading: z.string(),
content: z.string(),
subsections: z
.array(
z.object({
heading: z.string(),
content: z.string(),
})
)
.optional(),
})
),
references: z.array(
z.object({
authors: z.array(z.string()),
title: z.string(),
year: z.number(),
source: z.string().optional(),
})
),
keywords: z.array(z.string()),
});Multi-step Extraction
// extractors/multistep.ts
export async function extractComplex(document: string) {
// Step 1: Extract structure
const structure = await extract(
z.object({
sections: z.array(z.string()),
hasReferences: z.boolean(),
}),
'Identify the document structure',
document
);// Step 2: Extract each section
const sections = await Promise.all(
structure.sections.map((section) =>
extract(
z.object({
heading: z.string(),
summary: z.string(),
keyPoints: z.array(z.string()),
}),
Extract details from section: ${section},
document
)
)
);
// Step 3: Extract references if present let references = []; if (structure.hasReferences) { references = await extract( z.array( z.object({ authors: z.array(z.string()), title: z.string(), year: z.number(), }) ), 'Extract all references', document ); }
return { sections, references }; } ```
Streaming Structured Output
// extractors/streaming.ts
import { zodToJsonSchema } from 'zod-to-json-schema';export async function* streamExtract<T>( schema: z.ZodSchema<T>, prompt: string, text: string ): AsyncGenerator<{ partial: any; complete: boolean }> { const jsonSchema = zodToJsonSchema(schema);
const stream = await openai.chat.completions.create({
model: 'gpt-4-turbo-preview',
response_format: { type: 'json_object' },
messages: [
{
role: 'system',
content: ${prompt}\n\nReturn JSON matching: ${JSON.stringify(jsonSchema)},
},
{ role: 'user', content: text },
],
stream: true,
});
let fullContent = '';
for await (const chunk of stream) { const content = chunk.choices[0]?.delta?.content || ''; fullContent += content;
// Try to parse partial JSON try { const partial = JSON.parse(fullContent); yield { partial, complete: false }; } catch { // JSON not complete yet } }
// Final parse and validate const final = JSON.parse(fullContent); const validated = schema.parse(final); yield { partial: validated, complete: true }; } ```
Error Handling
// extractors/error-handling.ts
export class ExtractionError extends Error {
constructor(
message: string,
public readonly raw: string,
public readonly validationErrors?: z.ZodError
) {
super(message);
this.name = 'ExtractionError';
}
}export async function safeExtract<T>( schema: z.ZodSchema<T>, prompt: string, text: string ): Promise<{ success: true; data: T } | { success: false; error: ExtractionError }> { try { const data = await extractWithRetry(schema, prompt, text); return { success: true, data }; } catch (error) { if (error instanceof z.ZodError) { return { success: false, error: new ExtractionError('Validation failed', text, error), }; } return { success: false, error: new ExtractionError( error instanceof Error ? error.message : 'Unknown error', text ), }; } } ```
Best Practices
- Use structured outputs: OpenAI's native feature when available
- Define clear schemas: Descriptive field names and descriptions
- Validate with Zod: Type-safe runtime validation
- Retry on failure: With error context
- Use function calling: For complex schemas
- Handle edge cases: Null values, empty arrays
- Stream for UX: Partial results during extraction
- Test with variations: Different input formats
Output Checklist
Every structured extraction should include:
- Zod schema with descriptions
- Appropriate extraction method (JSON/function/tools)
- Runtime validation
- Retry logic with error context
- Error handling and typing
- Streaming support (if needed)
- Edge case handling
- Type-safe return values
- Comprehensive tests
Subgroup 9: Model Context Protocol (MCP) Server Builder
MCP Server Builder
Create Model Context Protocol servers to extend Claude's capabilities with custom tools and resources.
Core Workflow
- Define purpose: Identify what capabilities to add
- Choose transport: stdio (local) or HTTP/SSE (remote)
- Design tools: Define tool schemas and handlers
- Add resources: Optional file/data access
- Create prompts: Optional reusable prompts
- Test locally: Verify with MCP inspector
- Deploy: Configure for Claude Desktop or API
MCP Architecture Overview
┌─────────────┐ MCP Protocol ┌─────────────┐
│ Claude │◄────────────────────►│ MCP Server │
│ (Host) │ JSON-RPC over stdio │ (Your App) │
└─────────────┘ └─────────────┘
│
▼
┌───────────┐
│ Tools │
│ Resources │
│ Prompts │
└───────────┘Project Setup
TypeScript MCP Server
# Create project
mkdir my-mcp-server && cd my-mcp-server
npm init -yInstall dependencies
npm install @modelcontextprotocol/sdk zod
Dev dependencies
npm install -D typescript @types/node tsx ```
// package.json
{
"name": "my-mcp-server",
"version": "1.0.0",
"type": "module",
"main": "dist/index.js",
"bin": {
"my-mcp-server": "./dist/index.js"
},
"scripts": {
"build": "tsc",
"dev": "tsx watch src/index.ts",
"start": "node dist/index.js"
}
}// tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"declaration": true
},
"include": ["src/**/*"]
}Basic Server Structure
// src/index.ts
#!/usr/bin/env nodeimport { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
// Create server instance const server = new Server( { name: "my-mcp-server", version: "1.0.0", }, { capabilities: { tools: {}, resources: {}, prompts: {}, }, } );
// Define tools const TOOLS = [ { name: "get_weather", description: "Get current weather for a location", inputSchema: { type: "object" as const, properties: { location: { type: "string", description: "City name or coordinates", }, units: { type: "string", enum: ["celsius", "fahrenheit"], default: "celsius", }, }, required: ["location"], }, }, { name: "search_database", description: "Search the internal database", inputSchema: { type: "object" as const, properties: { query: { type: "string", description: "Search query", }, limit: { type: "number", default: 10, }, }, required: ["query"], }, }, ];
// List tools handler server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: TOOLS }; });
// Call tool handler server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params;
switch (name) { case "get_weather": { const { location, units = "celsius" } = args as { location: string; units?: string; };
// Implement your logic here const weather = await fetchWeather(location, units);
return { content: [ { type: "text", text: JSON.stringify(weather, null, 2), }, ], }; }
case "search_database": { const { query, limit = 10 } = args as { query: string; limit?: number };
const results = await searchDatabase(query, limit);
return { content: [ { type: "text", text: JSON.stringify(results, null, 2), }, ], }; }
default:
throw new Error(Unknown tool: ${name});
}
});
// Start server async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("MCP Server running on stdio"); }
main().catch(console.error);
// Helper functions (implement your logic) async function fetchWeather(location: string, units: string) { // Your implementation return { location, temperature: 22, units, condition: "sunny" }; }
async function searchDatabase(query: string, limit: number) { // Your implementation return { query, results: [], total: 0 }; } ```
Tools
Tool Schema Definition
// src/tools/index.ts
import { z } from "zod";// Define input schemas with Zod for validation export const GetWeatherSchema = z.object({ location: z.string().describe("City name or coordinates"), units: z.enum(["celsius", "fahrenheit"]).default("celsius"), });
export const SearchSchema = z.object({ query: z.string().min(1).describe("Search query"), filters: z .object({ category: z.string().optional(), dateFrom: z.string().optional(), dateTo: z.string().optional(), }) .optional(), limit: z.number().min(1).max(100).default(10), });
// Convert Zod schema to JSON Schema for MCP export function zodToJsonSchema(schema: z.ZodObject<any>) { // Use zod-to-json-schema package in production return schema; } ```
Tool Handler Pattern
// src/tools/handlers.ts
import { z } from "zod";type ToolHandler<T extends z.ZodSchema> = ( args: z.infer<T> ) => Promise<{ content: Array<{ type: string; text: string }> }>;
export function createToolHandler<T extends z.ZodSchema>( schema: T, handler: (args: z.infer<T>) => Promise<any> ): ToolHandler<T> { return async (rawArgs) => { // Validate input const args = schema.parse(rawArgs);
// Execute handler const result = await handler(args);
// Format response return { content: [ { type: "text", text: typeof result === "string" ? result : JSON.stringify(result, null, 2), }, ], }; }; }
// Usage
export const handleGetWeather = createToolHandler(
GetWeatherSchema,
async ({ location, units }) => {
const response = await fetch(
https://api.weather.com/v1/current?location=${location}&units=${units}
);
return response.json();
}
);
```
Resources
Static Resources
// src/resources/index.ts
const RESOURCES = [
{
uri: "config://app-settings",
name: "Application Settings",
description: "Current application configuration",
mimeType: "application/json",
},
{
uri: "file://docs/readme",
name: "Documentation",
description: "Project documentation",
mimeType: "text/markdown",
},
];// List resources handler server.setRequestHandler(ListResourcesRequestSchema, async () => { return { resources: RESOURCES }; });
// Read resource handler server.setRequestHandler(ReadResourceRequestSchema, async (request) => { const { uri } = request.params;
switch (uri) { case "config://app-settings": return { contents: [ { uri, mimeType: "application/json", text: JSON.stringify(getAppSettings(), null, 2), }, ], };
case "file://docs/readme": const content = await fs.readFile("./README.md", "utf-8"); return { contents: [ { uri, mimeType: "text/markdown", text: content, }, ], };
default:
throw new Error(Unknown resource: ${uri});
}
});
```
Dynamic Resources
// Resources with templates (e.g., database records)
const RESOURCE_TEMPLATES = [
{
uriTemplate: "db://users/{userId}",
name: "User Profile",
description: "Get user by ID",
mimeType: "application/json",
},
];server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => { return { resourceTemplates: RESOURCE_TEMPLATES }; });
server.setRequestHandler(ReadResourceRequestSchema, async (request) => { const { uri } = request.params;
// Parse URI to extract parameters const userMatch = uri.match(/^db:\/\/users\/(\w+)$/); if (userMatch) { const userId = userMatch[1]; const user = await db.users.findById(userId);
return { contents: [ { uri, mimeType: "application/json", text: JSON.stringify(user, null, 2), }, ], }; }
throw new Error(Unknown resource: ${uri});
});
```
Prompts
Reusable Prompts
// src/prompts/index.ts
const PROMPTS = [
{
name: "code_review",
description: "Review code for best practices and issues",
arguments: [
{
name: "language",
description: "Programming language",
required: true,
},
{
name: "focus",
description: "What to focus on (security, performance, style)",
required: false,
},
],
},
{
name: "explain_error",
description: "Explain an error message and suggest fixes",
arguments: [
{
name: "error",
description: "The error message",
required: true,
},
{
name: "context",
description: "Additional context about what you were doing",
required: false,
},
],
},
];// List prompts handler server.setRequestHandler(ListPromptsRequestSchema, async () => { return { prompts: PROMPTS }; });
// Get prompt handler server.setRequestHandler(GetPromptRequestSchema, async (request) => { const { name, arguments: args } = request.params;
switch (name) {
case "code_review":
return {
description: "Code review prompt",
messages: [
{
role: "user",
content: {
type: "text",
text: Please review the following ${args?.language || "code"} code.
${args?.focus ? Focus particularly on: ${args.focus}` : ""}
- Look for:
- Bugs and potential issues
- Security vulnerabilities
- Performance improvements
- Code style and best practices
- Suggestions for improvement`,
- },
- },
- ],
- };
case "explain_error": return { description: "Error explanation prompt", messages: [ { role: "user", content: { type: "text", text: `I encountered this error:
\\\
${args?.error}
\\\
${args?.context ? Context: ${args.context} : ""}
- Please explain:
- What this error means
- Common causes
- How to fix it
- How to prevent it in the future`,
- },
- },
- ],
- };
default:
throw new Error(Unknown prompt: ${name});
}
});
```
Error Handling
// src/utils/errors.ts
import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";export class ToolError extends Error { constructor( message: string, public code: ErrorCode = ErrorCode.InternalError ) { super(message); this.name = "ToolError"; }
toMcpError(): McpError { return new McpError(this.code, this.message); } }
// Usage in handlers
server.setRequestHandler(CallToolRequestSchema, async (request) => {
try {
const { name, arguments: args } = request.params;
// ... handle tool
} catch (error) {
if (error instanceof ToolError) {
throw error.toMcpError();
}
if (error instanceof z.ZodError) {
throw new McpError(
ErrorCode.InvalidParams,
Invalid parameters: ${error.errors.map((e) => e.message).join(", ")}
);
}
throw new McpError(
ErrorCode.InternalError,
error instanceof Error ? error.message : "Unknown error"
);
}
});
```
Claude Desktop Configuration
// ~/Library/Application Support/Claude/claude_desktop_config.json (macOS)
// %APPDATA%\Claude\claude_desktop_config.json (Windows)
{
"mcpServers": {
"my-mcp-server": {
"command": "node",
"args": ["/path/to/my-mcp-server/dist/index.js"],
"env": {
"API_KEY": "your-api-key"
}
},
"npx-server": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/allowed/dir"]
}
}
}HTTP/SSE Transport
// src/http-server.ts
import express from "express";
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";const app = express(); const server = new Server(/* ... */);
// SSE endpoint for MCP app.get("/mcp", async (req, res) => { const transport = new SSEServerTransport("/mcp/messages", res); await server.connect(transport); });
// Message endpoint app.post("/mcp/messages", express.json(), async (req, res) => { // Handle incoming messages });
app.listen(3000, () => { console.log("MCP HTTP server running on port 3000"); }); ```
Testing
MCP Inspector
# Install MCP inspector
npx @modelcontextprotocol/inspectorRun your server with inspector
npx @modelcontextprotocol/inspector node dist/index.js ```
Unit Tests
// tests/tools.test.ts
import { describe, it, expect } from "vitest";
import { handleGetWeather } from "../src/tools/handlers";describe("get_weather tool", () => { it("returns weather data for valid location", async () => { const result = await handleGetWeather({ location: "New York", units: "celsius", });
expect(result.content[0].type).toBe("text"); const data = JSON.parse(result.content[0].text); expect(data).toHaveProperty("temperature"); });
it("throws error for invalid location", async () => { await expect( handleGetWeather({ location: "", units: "celsius" }) ).rejects.toThrow(); }); }); ```
Complete Example: GitHub MCP Server
// src/index.ts
#!/usr/bin/env nodeimport { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; import { Octokit } from "@octokit/rest";
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
const server = new Server( { name: "github-mcp", version: "1.0.0" }, { capabilities: { tools: {} } } );
const TOOLS = [ { name: "list_issues", description: "List issues in a GitHub repository", inputSchema: { type: "object" as const, properties: { owner: { type: "string", description: "Repository owner" }, repo: { type: "string", description: "Repository name" }, state: { type: "string", enum: ["open", "closed", "all"], default: "open" }, }, required: ["owner", "repo"], }, }, { name: "create_issue", description: "Create a new issue", inputSchema: { type: "object" as const, properties: { owner: { type: "string" }, repo: { type: "string" }, title: { type: "string" }, body: { type: "string" }, labels: { type: "array", items: { type: "string" } }, }, required: ["owner", "repo", "title"], }, }, ];
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params;
switch (name) { case "list_issues": { const { owner, repo, state = "open" } = args as any; const { data } = await octokit.issues.listForRepo({ owner, repo, state });
return { content: [{ type: "text", text: JSON.stringify( data.map((i) => ({ number: i.number, title: i.title, state: i.state })), null, 2 ), }], }; }
case "create_issue": { const { owner, repo, title, body, labels } = args as any; const { data } = await octokit.issues.create({ owner, repo, title, body, labels });
return {
content: [{
type: "text",
text: Created issue #${data.number}: ${data.html_url},
}],
};
}
default:
throw new Error(Unknown tool: ${name});
}
});
const transport = new StdioServerTransport(); server.connect(transport); ```
Best Practices
- Validate inputs: Use Zod or similar for schema validation
- Handle errors gracefully: Return meaningful error messages
- Use environment variables: Never hardcode secrets
- Rate limit external calls: Respect API limits
- Log appropriately: Use stderr for logs (stdout is for MCP)
- Document tools well: Clear descriptions help Claude use them correctly
- Keep tools focused: Single responsibility per tool
- Test with inspector: Verify behavior before deployment
Output Checklist
Every MCP server should include:
- Server with name and version
- Capabilities declaration (tools/resources/prompts)
- Tool schemas with descriptions
- Input validation with clear error messages
- Proper error handling and MCP error codes
- Environment variable configuration
- Claude Desktop config example
- MCP inspector testing
- Unit tests for handlers
- README with setup instructions
Subgroup 10: LangChain & LangGraph Workflow Builder
LangChain Workflow Builder
Build powerful LLM applications with chains, agents, and retrieval-augmented generation.
Core Workflow
- Setup LangChain: Install and configure
- Create chains: Build processing pipelines
- Add memory: Enable conversation context
- Define tools: Extend agent capabilities
- Implement RAG: Add knowledge retrieval
- Deploy: Production-ready setup
Installation
npm install langchain @langchain/openai @langchain/communityBasic Chains
Simple LLM Chain
// chains/simple.ts
import { ChatOpenAI } from '@langchain/openai';
import { ChatPromptTemplate } from '@langchain/core/prompts';
import { StringOutputParser } from '@langchain/core/output_parsers';const model = new ChatOpenAI({ modelName: 'gpt-4-turbo-preview', temperature: 0.7, });
const prompt = ChatPromptTemplate.fromMessages([ ['system', 'You are a helpful assistant that {task}.'], ['human', '{input}'], ]);
const chain = prompt.pipe(model).pipe(new StringOutputParser());
// Usage const result = await chain.invoke({ task: 'summarizes text concisely', input: 'Summarize this article: ...', }); ```
Sequential Chain
// chains/sequential.ts
import { RunnableSequence } from '@langchain/core/runnables';// Chain 1: Extract key points const extractChain = ChatPromptTemplate.fromMessages([ ['system', 'Extract the key points from the following text.'], ['human', '{text}'], ]).pipe(model).pipe(new StringOutputParser());
// Chain 2: Summarize key points const summarizeChain = ChatPromptTemplate.fromMessages([ ['system', 'Create a brief summary from these key points.'], ['human', '{keyPoints}'], ]).pipe(model).pipe(new StringOutputParser());
// Combined chain const fullChain = RunnableSequence.from([ { keyPoints: extractChain, originalText: (input) => input.text, }, { summary: summarizeChain, keyPoints: (input) => input.keyPoints, }, ]);
const result = await fullChain.invoke({ text: 'Long article...' }); // { summary: '...', keyPoints: '...' } ```
Branching Chain
// chains/branching.ts
import { RunnableBranch } from '@langchain/core/runnables';const classifyChain = ChatPromptTemplate.fromMessages([ ['system', 'Classify the query as: question, complaint, or feedback'], ['human', '{query}'], ]).pipe(model).pipe(new StringOutputParser());
const questionChain = ChatPromptTemplate.fromMessages([ ['system', 'Answer this question helpfully.'], ['human', '{query}'], ]).pipe(model).pipe(new StringOutputParser());
const complaintChain = ChatPromptTemplate.fromMessages([ ['system', 'Respond empathetically to this complaint.'], ['human', '{query}'], ]).pipe(model).pipe(new StringOutputParser());
const feedbackChain = ChatPromptTemplate.fromMessages([ ['system', 'Thank the user for their feedback.'], ['human', '{query}'], ]).pipe(model).pipe(new StringOutputParser());
const routingChain = RunnableSequence.from([ { classification: classifyChain, query: (input) => input.query, }, RunnableBranch.from([ [(input) => input.classification.includes('question'), questionChain], [(input) => input.classification.includes('complaint'), complaintChain], feedbackChain, // Default ]), ]); ```
Memory & Conversation
Buffer Memory
// memory/conversation.ts
import { BufferMemory } from 'langchain/memory';
import { ConversationChain } from 'langchain/chains';const memory = new BufferMemory({ returnMessages: true, memoryKey: 'history', });
const chain = new ConversationChain({ llm: model, memory, prompt: ChatPromptTemplate.fromMessages([ ['system', 'You are a helpful assistant.'], new MessagesPlaceholder('history'), ['human', '{input}'], ]), });
// Conversation maintains context await chain.invoke({ input: 'My name is Alice' }); await chain.invoke({ input: 'What is my name?' }); // Remembers Alice ```
Window Memory
// memory/window.ts
import { BufferWindowMemory } from 'langchain/memory';const memory = new BufferWindowMemory({ k: 5, // Keep last 5 exchanges returnMessages: true, memoryKey: 'history', }); ```
Summary Memory
// memory/summary.ts
import { ConversationSummaryMemory } from 'langchain/memory';const memory = new ConversationSummaryMemory({ llm: model, memoryKey: 'history', }); // Summarizes conversation to save tokens ```
Persistent Memory with Redis
// memory/redis.ts
import { BufferMemory } from 'langchain/memory';
import { RedisChatMessageHistory } from '@langchain/community/stores/message/redis';const memory = new BufferMemory({
chatHistory: new RedisChatMessageHistory({
sessionId: user:${userId}:session:${sessionId},
client: redisClient,
ttl: 3600, // 1 hour
}),
returnMessages: true,
memoryKey: 'history',
});
```
Tools & Agents
Define Custom Tools
// tools/custom.ts
import { DynamicTool, DynamicStructuredTool } from '@langchain/core/tools';
import { z } from 'zod';// Simple tool const searchTool = new DynamicTool({ name: 'search', description: 'Search the web for information', func: async (query: string) => { const results = await searchAPI.search(query); return JSON.stringify(results); }, });
// Structured tool with schema const calculatorTool = new DynamicStructuredTool({ name: 'calculator', description: 'Perform mathematical calculations', schema: z.object({ expression: z.string().describe('Mathematical expression to evaluate'), }), func: async ({ expression }) => { try { const result = eval(expression); // Use safer math parser in production return String(result); } catch { return 'Error: Invalid expression'; } }, });
// Database query tool const dbQueryTool = new DynamicStructuredTool({ name: 'query_database', description: 'Query the database for user or order information', schema: z.object({ table: z.enum(['users', 'orders', 'products']), filter: z.record(z.string()).optional(), limit: z.number().default(10), }), func: async ({ table, filter, limit }) => { const results = await db[table].findMany({ where: filter, take: limit, }); return JSON.stringify(results); }, }); ```
Create Agent
// agents/react.ts
import { createReactAgent, AgentExecutor } from 'langchain/agents';
import { pull } from 'langchain/hub';// Get standard ReAct prompt const prompt = await pull('hwchase17/react');
// Create agent const agent = await createReactAgent({ llm: model, tools: [searchTool, calculatorTool, dbQueryTool], prompt, });
// Create executor const executor = new AgentExecutor({ agent, tools: [searchTool, calculatorTool, dbQueryTool], verbose: true, maxIterations: 5, });
// Run agent const result = await executor.invoke({ input: 'What is the square root of the number of users in our database?', }); ```
OpenAI Functions Agent
// agents/openai-functions.ts
import { createOpenAIFunctionsAgent, AgentExecutor } from 'langchain/agents';const prompt = ChatPromptTemplate.fromMessages([ ['system', 'You are a helpful assistant with access to tools.'], new MessagesPlaceholder('chat_history'), ['human', '{input}'], new MessagesPlaceholder('agent_scratchpad'), ]);
const agent = await createOpenAIFunctionsAgent({ llm: new ChatOpenAI({ modelName: 'gpt-4-turbo-preview' }), tools: [searchTool, calculatorTool], prompt, });
const executor = new AgentExecutor({ agent, tools: [searchTool, calculatorTool], memory: new BufferMemory({ returnMessages: true, memoryKey: 'chat_history', }), }); ```
RAG Pipeline
Document Loading
// rag/loader.ts
import { DirectoryLoader } from 'langchain/document_loaders/fs/directory';
import { PDFLoader } from 'langchain/document_loaders/fs/pdf';
import { TextLoader } from 'langchain/document_loaders/fs/text';
import { JSONLoader } from 'langchain/document_loaders/fs/json';const loader = new DirectoryLoader('./documents', { '.pdf': (path) => new PDFLoader(path), '.txt': (path) => new TextLoader(path), '.json': (path) => new JSONLoader(path), });
const docs = await loader.load(); ```
Text Splitting
// rag/splitter.ts
import { RecursiveCharacterTextSplitter } from 'langchain/text_splitter';const splitter = new RecursiveCharacterTextSplitter({ chunkSize: 1000, chunkOverlap: 200, separators: ['\n\n', '\n', ' ', ''], });
const chunks = await splitter.splitDocuments(docs); ```
Vector Store
// rag/vectorstore.ts
import { OpenAIEmbeddings } from '@langchain/openai';
import { PineconeStore } from '@langchain/pinecone';
import { Pinecone } from '@pinecone-database/pinecone';const embeddings = new OpenAIEmbeddings();
const pinecone = new Pinecone(); const index = pinecone.Index(process.env.PINECONE_INDEX!);
// Create vector store from documents const vectorStore = await PineconeStore.fromDocuments(chunks, embeddings, { pineconeIndex: index, namespace: 'documents', });
// Or connect to existing const existingStore = await PineconeStore.fromExistingIndex(embeddings, { pineconeIndex: index, namespace: 'documents', }); ```
RAG Chain
// rag/chain.ts
import { createRetrievalChain } from 'langchain/chains/retrieval';
import { createStuffDocumentsChain } from 'langchain/chains/combine_documents';const retriever = vectorStore.asRetriever({ k: 5, // Return top 5 results searchType: 'similarity', });
const prompt = ChatPromptTemplate.fromMessages([ [ 'system', `Answer questions based on the following context. If the answer is not in the context, say so.
Context: {context}`, ], ['human', '{input}'], ]);
const combineDocsChain = await createStuffDocumentsChain({ llm: model, prompt, });
const ragChain = await createRetrievalChain({ retriever, combineDocsChain, });
const result = await ragChain.invoke({ input: 'What is our refund policy?', }); // { answer: '...', context: [Document, Document, ...] } ```
Conversational RAG
// rag/conversational.ts
import { createHistoryAwareRetriever } from 'langchain/chains/history_aware_retriever';const contextualizePrompt = ChatPromptTemplate.fromMessages([ ['system', 'Given chat history and question, reformulate as standalone question.'], new MessagesPlaceholder('chat_history'), ['human', '{input}'], ]);
const historyAwareRetriever = await createHistoryAwareRetriever({ llm: model, retriever, rephrasePrompt: contextualizePrompt, });
const qaPrompt = ChatPromptTemplate.fromMessages([ ['system', 'Answer based on context:\n\n{context}'], new MessagesPlaceholder('chat_history'), ['human', '{input}'], ]);
const questionAnswerChain = await createStuffDocumentsChain({ llm: model, prompt: qaPrompt, });
const conversationalRagChain = await createRetrievalChain({ retriever: historyAwareRetriever, combineDocsChain: questionAnswerChain, }); ```
Streaming
// streaming/stream.ts
const stream = await chain.stream({ input: 'Tell me a story' });for await (const chunk of stream) { process.stdout.write(chunk); }
// With callbacks const result = await chain.invoke( { input: 'Tell me a story' }, { callbacks: [ { handleLLMNewToken(token: string) { process.stdout.write(token); }, }, ], } ); ```
Production Setup
// config/langchain.ts
import { ChatOpenAI } from '@langchain/openai';export const model = new ChatOpenAI({ modelName: process.env.OPENAI_MODEL || 'gpt-4-turbo-preview', temperature: 0.7, maxTokens: 4096, timeout: 60000, maxRetries: 3, cache: true, });
// With rate limiting import Bottleneck from 'bottleneck';
const limiter = new Bottleneck({ minTime: 100, // 10 requests per second maxConcurrent: 5, });
export async function invokeWithRateLimit(chain: any, input: any) { return limiter.schedule(() => chain.invoke(input)); } ```
Best Practices
- Use LCEL: LangChain Expression Language for composability
- Add memory strategically: Balance context vs tokens
- Define clear tool descriptions: Help the agent choose correctly
- Chunk documents properly: Optimize for retrieval
- Use streaming: Better UX for long responses
- Cache embeddings: Reduce API costs
- Monitor token usage: Track and optimize
- Handle errors gracefully: Retry and fallback
Output Checklist
Every LangChain implementation should include:
- Model configuration with retries
- Prompt templates with variables
- Chain composition with LCEL
- Memory for conversation context
- Tools with clear descriptions
- Agent with iteration limits
- RAG with proper chunking
- Streaming for UX
- Error handling
- Rate limiting for production
Subgroup 11: Prompt Template & Output Contract Builder
Prompt Template Builder
Build robust, reusable prompt templates with clear contracts and consistent outputs.
Core Components
System Prompt: Role, persona, constraints, output format User Prompt: Task, context, variables, examples Few-Shot Examples: Input/output pairs demonstrating desired behavior Output Contract: Strict format specification (JSON schema, Markdown structure) Style Rules: Tone, verbosity, formatting preferences Guardrails: Do's and don'ts, safety constraints
System Prompt Template
# System Prompt: Code Review AssistantYou are an expert code reviewer specializing in {language} and {framework}. Your role is to provide constructive, actionable feedback on code quality, best practices, and potential issues.
Output Format
Provide your review in the following JSON structure:
{
"summary": "Brief 1-2 sentence overview",
"issues": [
{
"severity": "critical|major|minor",
"line": number,
"message": "Description of the issue",
"suggestion": "How to fix it"
}
],
"strengths": ["List of positive aspects"],
"overall_score": 1-10
}
```Style Guidelines
- Be constructive and specific
- Cite line numbers for issues
- Provide actionable suggestions
- Balance criticism with praise
- Use professional, respectful tone
Constraints
- Do NOT suggest unnecessary refactors
- Do focus on correctness, security, performance
- Do NOT be overly pedantic about style
- Do consider the context and project requirements
User Prompt Template with Variables
// prompt-templates/code-review.ts
export const codeReviewPrompt = (variables: {
language: string;
framework: string;
code: string;
context?: string;
}) => `
Please review the following ${variables.language} code:${variables.context ? Context: ${variables.context}\n : ''}
\\\${variables.language}
${variables.code}
\\\
Provide a thorough code review following the output format specified in the system prompt. `;
// Usage const prompt = codeReviewPrompt({ language: 'typescript', framework: 'React', code: userSubmittedCode, context: 'This is a production component for user authentication', }); ````
Few-Shot Examples
# Few-Shot ExamplesExample 1: Good Code
Input:
function calculateTotal(items: Item[]): number {
return items.reduce((sum, item) => sum + item.price, 0);
}
```Output:
{
"summary": "Clean, type-safe implementation with no issues found.",
"issues": [],
"strengths": [
"Type safety with TypeScript",
"Functional approach with reduce",
"Clear, descriptive naming"
],
"overall_score": 9
}Example 2: Code with Issues
Input:
function calc(arr) {
let total = 0;
for (var i = 0; i < arr.length; i++) {
total = total + arr[i].price;
}
return total;
}Output:
{
"summary": "Functional but has type safety and modern syntax issues.",
"issues": [
{
"severity": "major",
"line": 1,
"message": "Missing type annotations for parameters and return value",
"suggestion": "Add types: function calc(arr: Item[]): number"
},
{
"severity": "minor",
"line": 3,
"message": "Using 'var' instead of 'let' or 'const'",
"suggestion": "Replace 'var' with 'let': for (let i = 0; ...)"
}
],
"strengths": ["Logic is correct", "Handles empty array case"],
"overall_score": 6
}Output Contracts
// Define strict output schema
import { z } from 'zod';export const codeReviewSchema = z.object({ summary: z.string().min(10).max(200), issues: z.array(z.object({ severity: z.enum(['critical', 'major', 'minor']), line: z.number().int().positive(), message: z.string(), suggestion: z.string(), })), strengths: z.array(z.string()), overall_score: z.number().int().min(1).max(10), });
// Validate LLM output export const parseCodeReview = (output: string) => { try { const parsed = JSON.parse(output); return codeReviewSchema.parse(parsed); } catch (error) { throw new Error('Invalid code review output format'); } }; ````
Template Variables
export interface PromptVariables {
// Required
required_field: string;// Optional with defaults optional_field?: string;
// Constrained values severity_level: "low" | "medium" | "high";
// Numeric with ranges max_tokens: number; // 1-4096 }
export const buildPrompt = (vars: PromptVariables): string => { // Validate variables if (!vars.required_field) { throw new Error("required_field is required"); }
// Set defaults const optional = vars.optional_field ?? "default value";
// Build prompt
return Task: ${vars.required_field}
Options: ${optional}
Severity: ${vars.severity_level};
};
```
Style Rules
## Tone Guidelines- Professional: Formal language, no slang
- Friendly: Conversational but respectful
- Technical: Precise terminology, assume expertise
- Educational: Explain concepts, teach as you go
Verbosity Levels
- Concise: 1-2 sentences, bullet points
- Standard: 1 paragraph per point
- Detailed: Full explanations with examples
- Comprehensive: Deep dive with references
Formatting Preferences
- Use markdown headers for structure
- Bold important terms
- Code blocks for technical content
- Lists for enumeration
- Tables for comparisons
`
Do's and Don'ts
## Do's✓ Provide specific, actionable feedback ✓ Include code examples when relevant ✓ Reference line numbers for issues ✓ Suggest concrete improvements ✓ Balance criticism with praise ✓ Consider context and constraints
Don'ts
✗ Don't be vague ("this is bad") ✗ Don't suggest unnecessary rewrites ✗ Don't ignore security issues ✗ Don't be overly pedantic ✗ Don't assume unlimited resources ✗ Don't make assumptions without context ```
Prompt Chaining
// Multi-step prompts
export const chainedPrompts = {
step1_analyze: (code: string) => `
Analyze this code and identify potential issues:
${code}List issues in JSON array format with severity and description. `,
step2_suggest: (issues: Issue[]) => ` Given these code issues: ${JSON.stringify(issues)}
Provide detailed fix suggestions for each issue. `,
step3_summarize: (suggestions: Suggestion[]) =>
Summarize these code review suggestions into a final report:
${JSON.stringify(suggestions)}
,
};
// Execute chain const issues = await llm(chainedPrompts.step1_analyze(code)); const suggestions = await llm(chainedPrompts.step2_suggest(issues)); const report = await llm(chainedPrompts.step3_summarize(suggestions)); ```
Version Control
// Track prompt versions
export const PROMPT_VERSIONS = {
"v1.0": {
system: "Original system prompt...",
user: (vars) => `Original user prompt...`,
deprecated: false,
},
"v1.1": {
system: "Improved system prompt with better constraints...",
user: (vars) => `Updated user prompt...`,
deprecated: false,
changes: "Added JSON schema validation, improved examples",
},
"v1.0-deprecated": {
system: "...",
user: (vars) => `...`,
deprecated: true,
deprecation_reason: "Replaced by v1.1 with better output format",
},
};// Use specific version const prompt = PROMPT_VERSIONS["v1.1"]; ```
Testing Prompts
// Test cases for prompt validation
const testCases = [
{
input: { code: "function test() {}", language: "javascript" },
expected: {
hasIssues: false,
scoreRange: [8, 10],
},
},
{
input: { code: "func test(arr) { return arr[0] }", language: "javascript" },
expected: {
hasIssues: true,
minIssues: 2,
severities: ["major", "minor"],
},
},
];// Run tests for (const test of testCases) { const output = await llm(buildPrompt(test.input)); const parsed = parseCodeReview(output);
if (test.expected.hasIssues) { assert(parsed.issues.length >= test.expected.minIssues); } if (test.expected.scoreRange) { assert(parsed.overall_score >= test.expected.scoreRange[0]); assert(parsed.overall_score <= test.expected.scoreRange[1]); } } ```
Best Practices
- Clear instructions: Be explicit about what you want
- Output contracts: Define strict schemas
- Few-shot examples: Show, don't just tell
- Variable validation: Check inputs before building prompts
- Version tracking: Maintain prompt history
- Test thoroughly: Validate against edge cases
- Iterate: Improve based on real outputs
- Document constraints: Explain limitations
Output Checklist
- System prompt with role and constraints
- User prompt template with variables
- Output format specification (JSON schema)
- 3+ few-shot examples (good and bad)
- Style guidelines documented
- Do's and don'ts list
- Variable validation logic
- Output parsing/validation
- Test cases for prompt
- Version tracking system
Subgroup 12: Prompt Regression & Diff Tester
Prompt Regression Tester
Systematically test prompt changes to prevent regressions.
Test Case Format
{
"test_cases": [
{
"id": "test_001",
"input": "Summarize this article",
"context": "Article text here...",
"expected_behavior": "Concise 2-3 sentence summary",
"baseline_output": "Output from v1.0 prompt",
"must_include": ["main point", "conclusion"],
"must_not_include": ["opinion", "speculation"]
}
]
}Comparison Framework
def compare_prompts(old_prompt, new_prompt, test_cases):
results = {
"test_cases": [],
"summary": {
"total": len(test_cases),
"improvements": 0,
"regressions": 0,
"unchanged": 0,
},
"breakages": []
}for test in test_cases: old_output = llm(old_prompt.format(test)) new_output = llm(new_prompt.format(test))
comparison = { "test_id": test["id"], "old_output": old_output, "new_output": new_output, "diff": compute_diff(old_output, new_output), "scores": { "old": score_output(old_output, test), "new": score_output(new_output, test), }, "verdict": classify_change(old_output, new_output, test) }
results["test_cases"].append(comparison) results["summary"][comparison["verdict"]] += 1
if comparison["verdict"] == "regressions": results["breakages"].append(analyze_breakage(comparison, test))
return results ```
Stability Metrics
def calculate_stability_metrics(results):
return {
"output_stability": measure_output_consistency(results),
"format_stability": check_format_preservation(results),
"constraint_adherence": check_constraints(results),
"behavioral_consistency": measure_behavior_delta(results),
}def measure_output_consistency(results): """How similar are outputs between versions?""" similarities = [] for result in results["test_cases"]: sim = semantic_similarity( result["old_output"], result["new_output"] ) similarities.append(sim) return sum(similarities) / len(similarities) ```
Breakage Analysis
def analyze_breakage(comparison, test_case):
"""Identify why the new prompt failed"""
reasons = []new_out = comparison["new_output"]
Missing required content
for keyword in test_case.get("must_include", []): if keyword.lower() not in new_out.lower(): reasons.append(f"Missing required content: '{keyword}'")
Contains forbidden content
for keyword in test_case.get("must_not_include", []): if keyword.lower() in new_out.lower(): reasons.append(f"Contains forbidden content: '{keyword}'")
Format violations
if not check_format(new_out, test_case.get("expected_format")): reasons.append("Output format violation")
Length issues
expected_length = test_case.get("expected_length") if expected_length: actual_length = len(new_out.split()) if abs(actual_length - expected_length) > expected_length * 0.3: reasons.append(f"Length deviation: expected ~{expected_length}, got {actual_length}")
return { "test_id": test_case["id"], "reasons": reasons, "old_output": comparison["old_output"][:100], "new_output": comparison["new_output"][:100], } ```
Fix Suggestions
def suggest_fixes(breakages):
"""Generate fix suggestions based on breakage patterns"""
suggestions = []Group breakages by reason
reason_groups = {} for breakage in breakages: for reason in breakage["reasons"]: if reason not in reason_groups: reason_groups[reason] = [] reason_groups[reason].append(breakage["test_id"])
Generate suggestions
for reason, test_ids in reason_groups.items(): if "Missing required content" in reason: suggestions.append({ "issue": reason, "affected_tests": test_ids, "suggestion": "Add explicit instruction in prompt to include this content", "example": f"Make sure to mention {reason.split(':')[1]} in your response." }) elif "format violation" in reason: suggestions.append({ "issue": reason, "affected_tests": test_ids, "suggestion": "Add stricter format constraints to prompt", "example": "Output must follow this exact format: ..." })
return suggestions ```
Report Generation
# Prompt Regression ReportSummary
- Total tests: 50
- Improvements: 5 (10%)
- Regressions: 3 (6%)
- Unchanged: 42 (84%)
Stability Metrics
- Output stability: 0.87
- Format stability: 0.95
- Constraint adherence: 0.94
Regressions (3)
test_005: Missing required content
Old output: "The main benefit is cost savings..." New output: "This approach provides flexibility..." Issue: Missing required keyword 'cost' Fix: Add explicit instruction: "Mention cost implications in your response"
Recommended Actions
- Revert changes that caused regressions (tests: 005, 012, 023)
- Add format constraints for JSON output
- Run full test suite before deployment
`
Best Practices
- Test with diverse inputs
- Compare across multiple runs (LLMs are stochastic)
- Track metrics over time
- Automate in CI/CD
- Review all regressions before deploy
- Maintain test case library
Output Checklist
- Test cases defined (30+)
- Comparison runner
- Stability metrics
- Breakage analyzer
- Fix suggestions
- Diff visualizer
- Automated report
- CI integration
Subgroup 13: Guardrails & Safety Filter Builder
Guardrails & Safety Filter Builder
Build comprehensive safety systems for LLM applications.
Safety Layers
- Input filtering: Block malicious prompts
- Output filtering: Redact sensitive data
- Topic constraints: Policy-based refusals
- PII detection: Mask personal information
- Prompt injection: Detect manipulation attempts
PII Detection & Redaction
import re
from presidio_analyzer import AnalyzerEngine
from presidio_anonymizer import AnonymizerEngineanalyzer = AnalyzerEngine() anonymizer = AnonymizerEngine()
def redact_pii(text: str) -> str: # Detect PII results = analyzer.analyze( text=text, language='en', entities=["EMAIL_ADDRESS", "PHONE_NUMBER", "CREDIT_CARD", "SSN"] )
Anonymize
anonymized = anonymizer.anonymize(text, results) return anonymized.text
Example: "My email is john@example.com" → "My email is <EMAIL_ADDRESS>"
`
Prompt Injection Detection
def detect_prompt_injection(user_input: str) -> bool:
"""Detect common prompt injection patterns"""
patterns = [
r'ignore (previous|above) instructions',
r'disregard (all|any) (prior|previous)',
r'you are now',
r'new instructions',
r'system:',
r'override',
]for pattern in patterns: if re.search(pattern, user_input, re.IGNORECASE): return True
return False
Block if detected
if detect_prompt_injection(user_input): return "I cannot process that request." ```
Topic Constraints
# Define allowed/disallowed topics
POLICY = {
"allowed_topics": [
"product_features",
"troubleshooting",
"billing",
"account_management"
],
"disallowed_topics": [
"medical_advice",
"legal_advice",
"financial_advice",
"politics",
"violence"
],
"requires_disclaimer": [
"security_practices",
"data_privacy"
]
}Classify topic
def classify_topic(query: str) -> str: classification_prompt = f""" Classify this query into one of these topics: {', '.join(POLICY['allowed_topics'] + POLICY['disallowed_topics'])}
Query: {query}
Return only the topic name. """ return llm(classification_prompt)
Check policy
def check_policy(query: str) -> dict: topic = classify_topic(query)
if topic in POLICY["disallowed_topics"]: return { "allowed": False, "reason": f"Cannot provide {topic}", "refusal": REFUSAL_TEMPLATES[topic] }
return {"allowed": True, "topic": topic} ```
Refusal Templates
REFUSAL_TEMPLATES = {
"medical_advice": """
I cannot provide medical advice. Please consult with a healthcare
professional for medical concerns.
""",
"legal_advice": """
I cannot provide legal advice. For legal matters, please consult
with a qualified attorney.
""",
"out_of_scope": """
I'm designed to help with product documentation and support.
This question is outside my area of expertise.
""",
}def refuse_safely(reason: str) -> str: return REFUSAL_TEMPLATES.get(reason, REFUSAL_TEMPLATES["out_of_scope"]) ```
Output Validation
def validate_output(output: str) -> dict:
"""Check output before returning to user"""
issues = []Check for PII
pii_results = analyzer.analyze(output, language='en') if pii_results: issues.append("Contains PII") output = redact_pii(output)
Check for banned phrases
banned_phrases = ["password", "api key", "secret"] for phrase in banned_phrases: if phrase.lower() in output.lower(): issues.append(f"Contains banned phrase: {phrase}")
Check toxicity
toxicity_score = toxicity_classifier(output) if toxicity_score > 0.7: issues.append("High toxicity detected")
return { "safe": len(issues) == 0, "issues": issues, "sanitized_output": output } ```
Complete Guardrail Pipeline
def apply_guardrails(user_input: str) -> dict:
# 1. Input validation
if detect_prompt_injection(user_input):
return {
"allowed": False,
"response": "Invalid request detected."
}2. Policy check
policy_check = check_policy(user_input) if not policy_check["allowed"]: return { "allowed": False, "response": policy_check["refusal"] }
3. Generate response
response = llm(user_input)
4. Output validation
validation = validate_output(response) if not validation["safe"]: return { "allowed": True, "response": validation["sanitized_output"], "warnings": validation["issues"] }
return { "allowed": True, "response": response } ```
Best Practices
- Layer multiple defenses
- Log all blocked requests
- Provide helpful refusals
- Redact, don't reject when possible
- Regular pattern updates
- Human review of edge cases
Output Checklist
- PII detection implemented
- Prompt injection detection
- Topic classification
- Policy constraints defined
- Refusal templates written
- Output validation
- Logging/monitoring
- Test cases for bypasses
Subgroup 14: Evaluation Harness & Golden Dataset Benchmark
Evaluation Harness
Build systematic evaluation frameworks for LLM applications.
Golden Dataset Format
[
{
"id": "test_001",
"category": "code_generation",
"input": "Write a Python function to reverse a string",
"expected_output": "def reverse_string(s: str) -> str:\n return s[::-1]",
"rubric": {
"correctness": 1.0,
"style": 0.8,
"documentation": 0.5
},
"metadata": {
"difficulty": "easy",
"tags": ["python", "strings"]
}
}
]Scoring Rubrics
from typing import Dict, Anydef score_exact_match(actual: str, expected: str) -> float: """Binary score: 1.0 if exact match, 0.0 otherwise""" return 1.0 if actual.strip() == expected.strip() else 0.0
def score_semantic_similarity(actual: str, expected: str) -> float: """Cosine similarity of embeddings""" actual_emb = get_embedding(actual) expected_emb = get_embedding(expected) return cosine_similarity(actual_emb, expected_emb)
def score_contains_keywords(actual: str, keywords: List[str]) -> float: """Percentage of required keywords present""" found = sum(1 for kw in keywords if kw.lower() in actual.lower()) return found / len(keywords)
def score_with_llm(actual: str, expected: str, rubric: Dict[str, float]) -> Dict[str, float]: """Use LLM as judge""" prompt = f""" Grade this output on a scale of 0-1 for each criterion:
Expected: {expected} Actual: {actual}
Criteria: {', '.join(rubric.keys())}
Return JSON with scores. """ return json.loads(llm(prompt)) ```
Test Runner
class EvaluationHarness:
def __init__(self, dataset_path: str):
self.dataset = self.load_dataset(dataset_path)
self.results = []def run_evaluation(self, model_fn): for test_case in self.dataset: # Generate output actual = model_fn(test_case["input"])
Score
scores = self.score_output( actual, test_case["expected_output"], test_case["rubric"] )
Record result
self.results.append({ "test_id": test_case["id"], "category": test_case["category"], "scores": scores, "passed": self.check_threshold(scores, test_case), "actual_output": actual, })
return self.generate_report()
def score_output(self, actual, expected, rubric): return { "exact_match": score_exact_match(actual, expected), "semantic_similarity": score_semantic_similarity(actual, expected), **score_with_llm(actual, expected, rubric) }
def check_threshold(self, scores, test_case): min_scores = test_case.get("min_scores", {}) for metric, threshold in min_scores.items(): if scores.get(metric, 0) < threshold: return False return True ```
Thresholds & Pass Criteria
# Define thresholds per category
THRESHOLDS = {
"code_generation": {
"correctness": 0.9,
"style": 0.7,
},
"summarization": {
"semantic_similarity": 0.8,
"brevity": 0.7,
},
"classification": {
"exact_match": 1.0,
}
}def check_test_passed(result: Dict) -> bool: category = result["category"] thresholds = THRESHOLDS.get(category, {})
for metric, threshold in thresholds.items(): if result["scores"].get(metric, 0) < threshold: return False
return True ```
Regression Report
def generate_regression_report(baseline_results, current_results):
report = {
"summary": {},
"regressions": [],
"improvements": [],
"unchanged": 0
}for baseline, current in zip(baseline_results, current_results): assert baseline["test_id"] == current["test_id"]
baseline_passed = baseline["passed"] current_passed = current["passed"]
if baseline_passed and not current_passed: report["regressions"].append({ "test_id": baseline["test_id"], "category": baseline["category"], "baseline_scores": baseline["scores"], "current_scores": current["scores"], }) elif not baseline_passed and current_passed: report["improvements"].append(baseline["test_id"]) else: report["unchanged"] += 1
report["summary"] = { "total_tests": len(baseline_results), "regressions": len(report["regressions"]), "improvements": len(report["improvements"]), "unchanged": report["unchanged"], }
return report ```
Continuous Evaluation
# Run evaluation on every commit
def ci_evaluation():
harness = EvaluationHarness("golden_dataset.json")
results = harness.run_evaluation(production_model)Check for regressions
baseline = load_baseline("baseline_results.json") report = generate_regression_report(baseline, results)
Fail CI if regressions
if report["summary"]["regressions"] > 0: print(f"❌ {report['summary']['regressions']} regressions detected!") sys.exit(1)
print("✅ All tests passed!") ```
Best Practices
- Representative dataset: Cover edge cases
- Multiple metrics: Don't rely on one score
- Human validation: Review LLM judge scores
- Version datasets: Track changes over time
- Automate in CI: Catch regressions early
- Regular updates: Add new test cases
Output Checklist
- Golden dataset created (50+ examples)
- Multiple scoring functions
- Pass/fail thresholds defined
- Test runner implemented
- Regression comparison
- Report generation
- CI integration
- Baseline established
Subgroup 15: Cost & Latency Optimizer
Cost & Latency Optimizer
Optimize LLM applications for cost and performance.
Cost Breakdown Analysis
class CostAnalyzer:
def __init__(self):
self.costs = {
"llm_calls": 0,
"embeddings": 0,
"tool_calls": 0,
}
self.counts = {
"llm_calls": 0,
"embeddings": 0,
}def track_llm_call(self, tokens_in: int, tokens_out: int): # GPT-4 pricing cost = (tokens_in / 1000) * 0.03 + (tokens_out / 1000) * 0.06 self.costs["llm_calls"] += cost self.counts["llm_calls"] += 1
def report(self): return { "total_cost": sum(self.costs.values()), "breakdown": self.costs, "avg_cost_per_call": self.costs["llm_calls"] / self.counts["llm_calls"], } ```
Caching Strategy
import hashlib
from functools import lru_cacheclass LLMCache: def __init__(self, redis_client): self.cache = redis_client self.ttl = 3600 # 1 hour
def get_cache_key(self, prompt: str, model: str) -> str: content = f"{model}:{prompt}" return f"llm_cache:{hashlib.sha256(content.encode()).hexdigest()}"
def get(self, prompt: str, model: str): key = self.get_cache_key(prompt, model) return self.cache.get(key)
def set(self, prompt: str, model: str, response: str): key = self.get_cache_key(prompt, model) self.cache.setex(key, self.ttl, response)
Usage
cache = LLMCache(redis_client)
def cached_llm_call(prompt: str, model: str = "gpt-4"): # Check cache cached = cache.get(prompt, model) if cached: return cached
Call LLM
response = llm(prompt, model=model)
Cache result
cache.set(prompt, model, response)
return response ```
Model Selection
MODEL_PRICING = {
"gpt-4": {"input": 0.03, "output": 0.06},
"gpt-3.5-turbo": {"input": 0.0005, "output": 0.0015},
"claude-3-opus": {"input": 0.015, "output": 0.075},
"claude-3-sonnet": {"input": 0.003, "output": 0.015},
}def select_model_by_complexity(query: str) -> str: """Use cheaper models for simple queries""" # Classify complexity complexity = classify_complexity(query)
if complexity == "simple": return "gpt-3.5-turbo" # 60x cheaper elif complexity == "medium": return "claude-3-sonnet" else: return "gpt-4"
def classify_complexity(query: str) -> str: # Simple heuristics if len(query) < 100 and "?" in query: return "simple" elif any(word in query.lower() for word in ["analyze", "complex", "detailed"]): return "complex" return "medium" ```
Prompt Optimization
def optimize_prompt(prompt: str) -> str:
"""Reduce token count while preserving meaning"""
optimizations = [
# Remove extra whitespace
lambda p: re.sub(r'\s+', ' ', p),Remove examples if not critical
lambda p: p.split("Examples:")[0] if "Examples:" in p else p,
Use abbreviations
lambda p: p.replace("For example", "E.g."), ]
for optimize in optimizations: prompt = optimize(prompt)
return prompt.strip()
Example: 500 tokens → 350 tokens = 30% cost reduction
`
Batching
async def batch_llm_calls(prompts: List[str], batch_size: int = 5):
"""Process multiple prompts in parallel"""
results = []for i in range(0, len(prompts), batch_size): batch = prompts[i:i + batch_size]
Parallel execution
batch_results = await asyncio.gather(*[ llm_async(prompt) for prompt in batch ])
results.extend(batch_results)
return results
10 sequential calls: ~30 seconds
# 10 batched calls (5 parallel): ~6 seconds ```
Latency Hotspot Analysis
import timeclass LatencyTracker: def __init__(self): self.timings = {}
def track(self, operation: str): def decorator(func): def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) duration = time.time() - start
if operation not in self.timings: self.timings[operation] = [] self.timings[operation].append(duration)
return result return wrapper return decorator
def report(self): return { op: { "count": len(times), "total": sum(times), "avg": sum(times) / len(times), "p95": sorted(times)[int(len(times) * 0.95)] } for op, times in self.timings.items() }
Usage
tracker = LatencyTracker()
@tracker.track("llm_call") def call_llm(prompt): return llm(prompt)
After 100 calls
print(tracker.report()) # {"llm_call": {"avg": 2.3, "p95": 4.1, ...}} ```
Optimization Recommendations
def generate_recommendations(cost_analysis, latency_analysis):
recs = []High LLM costs
if cost_analysis["costs"]["llm_calls"] > 10: recs.append({ "issue": "High LLM costs", "recommendation": "Implement caching for repeated queries", "impact": "50-80% cost reduction", })
if cost_analysis["avg_cost_per_call"] > 0.01: recs.append({ "issue": "Using expensive model for all queries", "recommendation": "Use gpt-3.5-turbo for simple queries", "impact": "60% cost reduction", })
High latency
if latency_analysis["llm_call"]["avg"] > 3: recs.append({ "issue": "High LLM latency", "recommendation": "Batch parallel calls, use streaming", "impact": "50% latency reduction", })
return recs ```
Streaming for Faster TTFB
async def streaming_llm(prompt: str):
"""Stream tokens as they're generated"""
async for chunk in llm_stream(prompt):
yield chunk
# User sees partial response immediatelyTime to First Byte: ~200ms (streaming) vs ~2s (waiting for full response)
`
Best Practices
- Cache aggressively: Identical queries cached
- Model selection: Use cheaper models when possible
- Prompt optimization: Reduce unnecessary tokens
- Batching: Parallel execution for throughput
- Streaming: Faster perceived latency
- Monitor costs: Track per-user, per-feature
- Set budgets: Alert on anomalies
Output Checklist
- Cost tracking implementation
- Caching layer
- Model selection logic
- Prompt optimization
- Batching for parallel calls
- Latency tracking
- Hotspot analysis
- Optimization recommendations
- Budget alerts
- Performance dashboard
Subgroup 16: LLM Failure Debugger & Hallucination Diagnostic
LLM Debugger
Systematically diagnose and fix LLM output issues.
Failure Taxonomy
class FailureType(Enum):
HALLUCINATION = "hallucination"
FORMAT_VIOLATION = "format_violation"
CONSTRAINT_BREAK = "constraint_break"
REASONING_ERROR = "reasoning_error"
TOOL_MISUSE = "tool_misuse"
REFUSAL = "unexpected_refusal"
INCOMPLETE = "incomplete_output"Root Cause Analysis
def diagnose_failure(input: str, output: str, expected: dict) -> dict:
"""Identify why LLM output failed"""issues = []
Check format
if expected.get("format") == "json": try: json.loads(output) except: issues.append({ "type": FailureType.FORMAT_VIOLATION, "details": "Invalid JSON output" })
Check required fields
if expected.get("required_fields"): for field in expected["required_fields"]: if field not in output: issues.append({ "type": FailureType.INCOMPLETE, "details": f"Missing required field: {field}" })
Check constraints
if expected.get("max_length"): if len(output) > expected["max_length"]: issues.append({ "type": FailureType.CONSTRAINT_BREAK, "details": f"Output too long: {len(output)} > {expected['max_length']}" })
Check for hallucination indicators
if contains_hallucination_markers(output): issues.append({ "type": FailureType.HALLUCINATION, "details": "Contains fabricated information" })
return { "has_issues": len(issues) > 0, "issues": issues, "primary_issue": issues[0] if issues else None }
def contains_hallucination_markers(output: str) -> bool: """Detect common hallucination patterns""" markers = [ r'According to.*that doesn\'t exist', r'In \d{4}.*before that year', r'contradicts itself', ] return any(re.search(marker, output, re.IGNORECASE) for marker in markers) ```
Prompt Fixes
PROMPT_FIXES = {
FailureType.FORMAT_VIOLATION: """
Add strict format instructions:
"Output MUST be valid JSON with this exact structure:
```json
{{"field1": "value", "field2": 123}}Do not add any text before or after the JSON." """,
FailureType.HALLUCINATION: """
Add grounding instructions: "Base your response ONLY on the provided context. If information is not in the context, say 'I don't have that information.' Never make up facts or details." """,
FailureType.CONSTRAINT_BREAK: """
Add explicit constraints: "Your response must be:
- Maximum 200 words
- No code examples
- Professional tone only"
- """,
- FailureType.REASONING_ERROR: """
- Add step-by-step reasoning:
- "Think through this step by step:
- First, identify...
- Then, consider...
- Finally, conclude..."
- """,
- }
def suggest_prompt_fix(diagnosis: dict, current_prompt: str) -> str: """Generate improved prompt based on diagnosis""" primary_issue = diagnosis["primary_issue"] if not primary_issue: return current_prompt
fix = PROMPT_FIXES[primary_issue["type"]]
return f"""{current_prompt}
IMPORTANT INSTRUCTIONS: {fix} """
Tool Improvements
def suggest_tool_fixes(diagnosis: dict, tool_schema: dict) -> dict:
"""Improve tool schema to prevent misuse"""fixes = {}
for issue in diagnosis["issues"]: if issue["type"] == FailureType.TOOL_MISUSE: # Make required parameters more explicit fixes["parameters"] = { **tool_schema["parameters"], "description": "REQUIRED: " + tool_schema["parameters"].get("description", "") }
Add examples
fixes["examples"] = [ { "params": {"query": "example search"}, "description": "Use for finding information" } ]
return {tool_schema, fixes} ````
Test Case Generation
def generate_test_cases(diagnosis: dict, failed_input: str, failed_output: str) -> List[dict]:
"""Create regression test cases from failures"""test_cases = []
Test case for the specific failure
test_cases.append({ "input": failed_input, "expected_behavior": "Should not " + diagnosis["primary_issue"]["details"], "validation": lambda output: not has_same_issue(output, diagnosis), })
Edge cases based on failure type
if diagnosis["primary_issue"]["type"] == FailureType.FORMAT_VIOLATION: test_cases.append({ "input": failed_input, "validation": lambda output: is_valid_json(output), "description": "Output must be valid JSON" })
Similar inputs that might trigger same issue
similar_inputs = generate_similar_inputs(failed_input) for inp in similar_inputs: test_cases.append({ "input": inp, "validation": lambda output: not has_same_issue(output, diagnosis), })
return test_cases ```
Debugging Workflow
def debug_llm_output(
prompt: str,
output: str,
expected: dict,
context: dict = {}
) -> dict:
"""Complete debugging workflow"""1. Diagnose issue
diagnosis = diagnose_failure(prompt, output, expected)
if not diagnosis["has_issues"]: return {"status": "ok", "diagnosis": diagnosis}
2. Generate fixes
fixed_prompt = suggest_prompt_fix(diagnosis, prompt)
3. Create test cases
test_cases = generate_test_cases(diagnosis, prompt, output)
4. Test fix
test_output = llm(fixed_prompt) fix_works = diagnose_failure(fixed_prompt, test_output, expected)["has_issues"] == False
return { "status": "issues_found", "diagnosis": diagnosis, "fixes": { "prompt": fixed_prompt, "fix_verified": fix_works, }, "test_cases": test_cases, "recommendations": generate_recommendations(diagnosis, context) } ```
Interactive Debugging
def interactive_debug():
"""Interactive debugging session"""
print("LLM Debugger - Interactive Mode")prompt = input("Enter prompt: ") expected_output = input("Expected output format (json/text): ")
output = llm(prompt) print(f"\nGenerated output:\n{output}\n")
if input("Does this look correct? (y/n): ").lower() == 'n': diagnosis = diagnose_failure(prompt, output, {"format": expected_output})
print("\nDiagnosis:") for issue in diagnosis["issues"]: print(f"- {issue['type'].value}: {issue['details']}")
print("\nSuggested fix:") print(suggest_prompt_fix(diagnosis, prompt)) ```
Best Practices
- Reproduce consistently: Multiple runs
- Isolate variables: Test prompt changes one at a time
- Check examples: Are few-shot examples clear?
- Validate constraints: Are they explicit enough?
- Test edge cases: Boundary conditions
- Log everything: Inputs, outputs, issues
- Iterate systematically: Track what fixes work
Output Checklist
- Failure taxonomy defined
- Root cause analyzer
- Prompt fix suggestions
- Tool schema improvements
- Test case generator
- Fix verification
- Debugging workflow
- Recommendations engine
- Interactive debugger
- Logging system