PromptHub
Back to Blog
Developer Tools Artificial Intelligence

Stop Writing Prompts by Hand! Use DSPy.ts Instead

B

Bright Coding

Author

15 min read 190 views
Stop Writing Prompts by Hand! Use DSPy.ts Instead

Stop Writing Prompts by Hand! Use DSPy.ts Instead

What if every prompt you've ever written is technical debt waiting to explode? Here's the brutal truth that keeps senior engineers awake at night: prompts are code you cannot refactor. They're fragile strings scattered across your codebase, brittle to model changes, impossible to version meaningfully, and when your LLM provider updates their API or tweaks their base model, your carefully crafted prompt engineering crumbles like wet cardboard. You've been there—tweaking adjectives, adding "think step by step," praying that "as a helpful assistant" still works tomorrow. It's madness. But what if you could stop treating prompts as precious artifacts and start treating them as optimizable programs? Enter DSPy.ts—the TypeScript port of Stanford's revolutionary DSPy framework that transforms how you build AI systems. This isn't another wrapper around OpenAI's API. This is a fundamental shift: you declare what you want, define how to measure success, and let an optimizer find the best way to get there. No more prompt whispering. No more artisanal string concatenation. Just typed signatures, composable modules, and intelligent optimization that remembers what worked across runs. Built by rUv and powered by the vector-native AgentDB memory layer, DSPy.ts is what happens when you take cutting-edge AI research and make it actually usable in production TypeScript codebases.

What is DSPy.ts?

DSPy.ts is the TypeScript implementation of DSPy, Stanford NLP's framework for programming—rather than prompting—language models. Created by rUv and actively maintained at github.com/ruvnet/dspy.ts, this port brings the full power of declarative AI programming to the JavaScript↗ Bright Coding Blog ecosystem with end-to-end TypeScript types, Node.js and browser compatibility, and a memory-first architecture that the original Python↗ Bright Coding Blog version simply doesn't offer.

The core philosophy is radical in its simplicity: separate the what from the how. You declare a signature—a typed contract specifying inputs and outputs—and compose it into modules and pipelines. Then you hand an optimizer a metric (a function that scores outputs) plus a handful of examples. The optimizer experiments with different instructions and demonstrations, measuring each attempt against your metric, and returns an OptimizedModule that outperforms anything you'd hand-craft. The TypeScript port adds crucial production features: genuine type safety from signature declaration through to result consumption, the ability to run anywhere JavaScript runs, and deep integration with AgentDB—a vector database with HNSW search, RaBitQ quantization, and hierarchical memory tiers that persists optimizer trials, ReAct↗ Bright Coding Blog reflexions, and LM responses so subsequent runs warm-start from prior learning.

This trending now because teams are hitting the wall with traditional prompt engineering. As LLMs proliferate across production systems, the maintenance burden of prompt strings has become unsustainable. DSPy.ts offers an escape hatch: treat your LM interactions as optimizable software components with measurable quality, versioned improvements, and cross-run memory.

Key Features That Change Everything

Composable, Typed Modules — DSPy.ts provides PredictModule, ChainOfThought, ReAct, ReActReflexion, RetrieveModule, and Pipeline as first-class building blocks. Each accepts a typed signature once and enforces end-to-end TypeScript types on inputs and outputs. No more any typing your way through LLM responses. The signature becomes the contract, and the compiler enforces it.

Self-Optimizing with Real Optimizers — Three production optimizers ship ready: BootstrapFewShot for quick wins from labeled and bootstrapped demonstrations; MIPROv2 for sophisticated instruction proposal and seeded search over instruction-demo combinations; and GEPA (Genetic-Pareto) for evolutionary prompt optimization that maintains a Pareto frontier of candidates. Each optimizer is deterministic per seed and supports save()/load() for reproducible pipelines.

GEPA: Genetic-Pareto Prompt Evolution — This is where DSPy.ts gets genuinely innovative. GEPA scores each example individually, maintains a Pareto frontier of prompt candidates, reflects on each candidate's weakest examples, mutates strategically, and admits new candidates only if they're non-dominated. The frontier persists to AgentDB and re-runs warm-start from prior evolution. This isn't gradient descent—it's selection pressure on prompts.

Experience Replay Across Runs — Here's the killer feature stock DSPy lacks: MIPROv2 persists each compile's winning instruction, keyed by a task fingerprint. A later compile() on a similar task warm-starts from what worked before. No cold starts. No relearning. Your optimizer gets smarter over time, not just within a session.

Input-Conditioned Few-Shot at RuntimeBootstrapFewShot with dynamicDemos doesn't pick a fixed demonstration set. It performs vector search at inference time to find the demos nearest to your current input, adapting its behavior contextually without recompilation.

Built-in RAG with MMR DiversityRetrieveModule over AgentDB supports Maximal-Marginal-Relevance re-ranking, giving you diverse, relevant passages to feed downstream modules. Declarative Retrieve → ChainOfThought pipelines in three lines of code.

ReAct Reflexion with Skill PromotionReActReflexion doesn't just reason and act—it remembers. It recalls lessons from failed attempts via AgentDB, records episodes after execution, and promotes repeatedly successful tool sequences to reusable skills. Your agent literally learns from its mistakes.

AgentDB Memory Architecture — HNSW vector search, RaBitQ 1-bit quantization (~32× compression), hierarchical tiers (working/short/long), ReasoningBank with semantic retrieval, and SAFLA for knowledge base evolution. This isn't bolted-on; it's foundational.

Fuzzy LM CacheCachingLM wraps any driver and serves generate() from an AgentDB vector cache. Near-identical prompts (cosine ≥ threshold) become cache hits, with TTL and options awareness. Cut your API costs dramatically.

Causal-Chain ObservabilityCompilationTracer records optimizer runs and trials with causedBy relationships, persisted to AgentDB. Optional MLflow integration when available. Debug your optimization like you debug your code.

Real-World Use Cases Where DSPy.ts Dominates

Production RAG Systems That Improve Themselves — You're building a customer support bot. Traditional approach: craft prompts, hope they work, monitor, manually tweak. With DSPy.ts: define a Retrieve → ChainOfThought pipeline, provide a metric (did the user accept the answer? was a ticket created?), feed historical Q&A pairs as trainset, run MIPROv2.compile(). The system finds better instructions and demonstrations automatically. Next quarter, recompile with new data—it warm-starts from prior success.

Multi-Step Agent Workflows with Memory — Your research agent needs to search, synthesize, and cite sources. ReActReflexion handles the reasoning-acting loop while AgentDB remembers which search strategies worked for which query types. Successful sequences get promoted to skills. Failed attempts teach the agent what to avoid. The agent accumulates expertise.

Classification at Scale with Optimized Few-ShotSentiment analysis, intent classification, content moderation—anywhere you need consistent structured output. Define the signature, provide labeled examples, let BootstrapFewShot with dynamicDemos select contextually relevant demonstrations per input. The optimizer finds the instruction phrasing that maximizes your F1 score, not someone's intuition about "helpful assistant" tone.

Cost-Optimized LLM Applications — Wrap your production LM driver with CachingLM. Similar prompts hit the vector cache instead of the API. Use cheaper models for initial optimization, promote to expensive models for final compilation. The DummyLM provider lets you develop and test your entire pipeline without spending a cent on API calls.

Cross-Project Prompt Reuse — Because optimizers save()/load() and AgentDB persists across runs, your team's accumulated prompt optimization becomes transferable institutional knowledge. New project with similar task structure? Warm-start from the prior project's optimizer state. This is how you scale AI development beyond individual heroics.

Step-by-Step Installation & Setup Guide

Getting started with DSPy.ts is deliberately minimal—you don't need to configure vector stores or memory layers to begin. The framework layers complexity only when you need it.

Basic Installation:

npm install dspy.ts

That's it for core functionality. The package includes TypeScript definitions; no separate @types package needed.

Configure Your Language Model:

import { configureLM, DummyLM } from 'dspy.ts';

// For development and testing — no API costs
configureLM(new DummyLM());

// For production, swap to your provider:
// configureLM(new OpenAIDriver({ apiKey: process.env.OPENAI_API_KEY }));
// configureLM(new AnthropicDriver({ apiKey: process.env.ANTHROPIC_API_KEY }));
// configureLM(new ONNXDriver({ modelPath: '...' })); // local inference

Optional: AgentDB Setup for Memory Features:

import { AgentDBClient } from 'dspy.ts';

const store = new AgentDBClient({
  vectorDimension: 384,        // Match your embedding model
  storage: { inMemory: true }  // or persistent storage config
});
await store.init();

The inMemory: true option is perfect for development and CI. For production, configure persistent storage appropriate to your deployment environment.

Environment Considerations:

  • Node.js 18+ recommended for native fetch and crypto APIs
  • Browser: Works with appropriate bundler configuration; tree-shake unused modules
  • TypeScript 5.7+ for best type inference on signature declarations

REAL Code Examples from DSPy.ts

Let's examine actual code patterns from the repository, with detailed explanations of what makes each powerful.

Example 1: Basic Signature Declaration with ChainOfThought

import {
  ChainOfThought,
  configureLM,
  DummyLM,
} from 'dspy.ts';

// Configure a test LM — swap for production provider
configureLM(new DummyLM());

// 1. Declare a typed signature: question (string) → answer (string)
const qa = new ChainOfThought<{ question: string }, { answer: string }>({
  name: 'QA',  // Identifier for tracing and caching
  signature: {
    // Explicit input contract with runtime validation hints
    inputs:  [{ name: 'question', type: 'string', required: true }],
    // Explicit output contract — the LM must produce this structure
    outputs: [{ name: 'answer',   type: 'string', required: true }],
  },
});

// The signature is now type-safe: TypeScript knows qa.run() expects
// { question: string } and returns Promise<{ answer: string }>

This exemplifies the DSPy.ts philosophy: types as contracts. The generic parameters <{ question: string }, { answer: string }> aren't decorative—they propagate through the entire pipeline. Your IDE autocompletes inputs, catches mismatches at compile time, and the runtime signature metadata enables the optimizer to reason about structure. The name field becomes the cache key and tracing identifier, making observability automatic.

Example 2: Optimization with BootstrapFewShot

import { BootstrapFewShot } from 'dspy.ts';

// Define a metric: 1.0 for exact match, 0.3 for any answer, 0 for empty
const metric = (
  _in: { question: string },      // input (unused in this metric)
  out: { answer: string },         // model output
  gold?: { answer: string }        // optional ground truth
) =>
  gold && out.answer?.trim() === gold.answer 
    ? 1           // Perfect match — full score
    : out.answer 
      ? 0.3       // Has answer but wrong — partial credit
      : 0;        // Empty or missing — no score

// Training examples: mix of labeled and unlabeled
const trainset = [
  // Labeled examples — optimizer knows the correct answer
  { input: { question: 'capital of France?' }, output: { answer: 'Paris' } },
  { input: { question: '2 + 2?' },             output: { answer: '4' } },
  // Unlabeled example — optimizer will bootstrap by running the program
  // and keeping outputs that score well against other examples' patterns
  { input: { question: 'largest planet?' } },
];

// Compile: optimizer searches for best instructions and demonstrations
const compiled = await new BootstrapFewShot(metric).compile(qa, trainset);

// The compiled module is optimized — run it on new inputs
const { answer } = await compiled.run({ question: 'capital of Italy?' });

The metric function is your quality definition—the optimizer's north star. Notice the unlabeled example: BootstrapFewShot will execute the program on "largest planet?", score the output using the metric against patterns from labeled data, and retain high-scoring results as bootstrapped demonstrations. This is semi-supervised optimization—you don't need exhaustive labels. The compiled result is a new module with optimized internals, ready for production inference.

Example 3: RAG Pipeline with RetrieveModule

import { AgentDBClient, RetrieveModule } from 'dspy.ts';

// Initialize vector store with 384-dim embeddings (all-MiniLM-L6-v2 size)
const store = new AgentDBClient({ 
  vectorDimension: 384, 
  storage: { inMemory: true } 
});
await store.init();

// Store knowledge — automatically embedded and indexed
await store.storeText('Paris is the capital of France.');
await store.storeText('Rome is the capital of Italy.');

// Configure retrieval with MMR diversity re-ranking
const retrieve = new RetrieveModule({ 
  client: store, 
  k: 3,              // Return top 3 passages
  useMMR: true       // Maximal Marginal Relevance: diverse, not just similar
});

// Retrieve contextually relevant passages
const { passages, context } = await retrieve.run({ 
  query: 'what is the capital of Italy?' 
});

// passages: [{ text: 'Rome is the capital of Italy.', score: 0.98, ... }, ...]
// context: concatenated string ready for downstream module consumption

// Typical pipeline: feed context into ChainOfThought
// new ChainOfThought<{ question: string, context: string }, { answer: string }>(...)

This demonstrates declarative RAG: three lines to configure retrieval, automatic embedding, and diversity-aware re-ranking. The context output is formatted for direct injection into a downstream signature. The useMMR: true flag prevents the common RAG failure mode where top-k results are near-duplicates—instead you get genuinely different perspectives on the query.

Example 4: Cross-Run Learning with MIPROv2 and Experience Replay

import { MIPROv2, CompilationTracer } from 'dspy.ts';

// Dedicated store for optimizer memory — survives process restarts
const replay = new AgentDBClient({ 
  vectorDimension: 64,           // Smaller dims for instruction embeddings
  storage: { inMemory: true } 
});
await replay.init();

// Tracer records causal chains: which trial caused which improvement
const tracer = new CompilationTracer({ store: replay });

// MIPROv2 with experience replay enabled
const opt = new MIPROv2(metric, { 
  numTrials: 12,           // Budget: 12 optimization attempts
  replayStore: replay,     // PERSIST: winning instructions stored here
  tracer                    // OBSERVE: full causal chain recorded
});

await opt.compile(qa, trainset);

// Later — new process, similar task fingerprint:
const opt2 = new MIPROv2(metric, { 
  numTrials: 12, 
  replayStore: replay  // Same store — contains prior learnings
});
await opt2.compile(qa, trainset);

// opt2.result.warmStarted === true  
// The optimizer began from the prior best instruction, not from scratch!

This is meta-learning for prompt optimization. The replayStore persists not just the final result but the optimization trajectory. The task fingerprint (derived from signature structure and metric characteristics) enables matching across semantically similar tasks. The CompilationTracer with its causedBy relationships lets you audit why the optimizer made each choice—essential for debugging and regulatory compliance.

Advanced Usage & Best Practices

Seed Everything for Reproducibility — All optimizers accept a seed option. In production, version control your seeds alongside your code. This makes optimization deterministic and bisectable when quality regressions occur.

Tiered Memory for Long-Running Agents — Use AgentDB's hierarchical tiers deliberately: working for current conversation context, short for session summaries, long for accumulated skills and knowledge. Call evictTier() with appropriate maxAgeMs to prevent unbounded growth.

Metric Design is Critical — Your metric is the optimizer's objective function. A bad metric optimizes the wrong thing. Prefer continuous scores over binary where possible—BootstrapFewShot's minScore threshold lets you control quality vs. quantity tradeoffs. Log metric distributions to detect optimization pathologies.

Cache Strategy for Cost Control — Wrap production LMs with CachingLM, but tune cosineThreshold carefully. Too high: no hits. Too low: stale responses. Start at 0.95 and adjust based on hit rate logs. Set TTLs based on your data freshness requirements.

Composition Over Monoliths — Prefer small, focused signatures composed via Pipeline over giant do-everything prompts. Each module optimizes independently, failures are isolated, and you get granular observability. The Pipeline module captures per-step timing and errors automatically.

Comparison with Alternatives

Capability DSPy.ts LangChain LlamaIndex Raw SDK
Prompt optimization ✅ Automatic via optimizers ❌ Manual only ❌ Manual only ❌ Manual only
Type-safe signatures ✅ End-to-end generics ⚠️ Partial ⚠️ Partial ❌ None
Cross-run learning ✅ AgentDB experience replay ❌ None ❌ None ❌ None
Built-in vector RAG ✅ AgentDB with MMR 🔧 Requires integration ✅ Core feature ❌ Build yourself
Reflexion / skill learning ✅ ReActReflexion 🔧 Custom implementation ❌ Not available ❌ Build yourself
LM caching ✅ Fuzzy vector cache 🔧 Custom implementation ❌ Not available ❌ Build yourself
Browser + Node ✅ Both ✅ Both ✅ Both ✅ Both
TypeScript-native ✅ Built in TS 🔧 JS-first, TS added 🔧 Python-first 🔧 Varies
Observability ✅ Causal-chain tracing 🔧 LangSmith (separate) 🔧 Custom ❌ Build yourself
Open source / self-hostable ✅ MIT, fully local ✅ (Apache-2) ✅ (MIT) N/A

Why choose DSPy.ts? If you're building production systems where prompt quality directly impacts business metrics, and you need that quality to improve systematically rather than through heroic manual effort. LangChain excels at rapid prototyping with pre-built chains; LlamaIndex dominates pure retrieval use cases. DSPy.ts occupies the optimization layer neither addresses—making your LM programs self-improving, observable, and maintainable at scale.

FAQ

Is DSPy.ts production-ready? The core modules and optimizers are stable with CI coverage. Some advanced integrations (direct agentdb.ReflexionMemory delegation) are actively being wired. Check github.com/ruvnet/dspy.ts/issues for current status.

Do I need to use AgentDB? Absolutely not. AgentDB powers advanced features (experience replay, reflexion, caching), but basic optimization works with zero configuration. Install, define signatures, compile—AgentDB is opt-in.

Can I use my existing vector database? Currently RetrieveModule requires AgentDBClient. The architecture is modular; community contributions for Pinecone, Weaviate, or pgvector adapters would be welcome.

How does this compare to Python DSPy? Feature parity for core optimization. TypeScript-native types, browser support, and AgentDB integration are DSPy.ts additions. Python DSPy has broader ecosystem integrations today.

What's the performance overhead? Compilation is offline—your optimized module runs with negligible overhead at inference. CachingLM typically reduces latency and cost for repeated or similar queries.

Can I optimize for latency, not just accuracy? Not directly yet—this is on the roadmap (multi-objective optimization for GEPA). Currently, optimize for quality, then apply CachingLM and model selection for latency.

How do I contribute? MIT license, open issues and PRs at github.com/ruvnet/dspy.ts. The roadmap includes Bayesian surrogates, broader test coverage, and ESLint 9 migration.

Conclusion

The era of artisanal prompt engineering is ending. DSPy.ts gives you the tools to treat language models as optimizable components—declared with types, measured with metrics, and improved automatically through principled search. The integration with AgentDB means your systems don't just optimize; they remember, accumulating knowledge across runs and promoting successful strategies to reusable skills.

If you're still hand-crafting prompts and hoping they'll survive the next model update, you're building on quicksand. Install DSPy.ts today, define your first signature, and let the optimizer show you what you've been missing. The future of AI development isn't better prompt writers—it's better systems for making prompts obsolete.

Star the repo, try the examples, and join the evolution at github.com/ruvnet/dspy.ts.

Comments (0)

Comments are moderated before appearing.

No comments yet. Be the first to share your thoughts!

Recommended Prompts

View All
All tools