← Back to 001 initial

101 claude

Documentation for 101_claude from the Ds ex repository.

DSPy Architecture Deep Dive for Elixir/BEAM Port

Executive Summary

DSPy is a sophisticated framework for programming language models declaratively through composable modules, automatic optimization, and structured prompting. A port to Elixir/BEAM would leverage OTP’s supervision trees, lightweight processes, and fault tolerance to create a more robust and scalable version.

Core Architectural Components

1. Signature System - The Foundation

Current Python Architecture:

  • Uses Pydantic models with metaclasses to define input/output schemas
  • Supports custom types (Image, Audio, Tool, etc.) with serialization
  • Field-level validation and type coercion
  • Instruction embedding at the type level

BEAM/OTP Translation:

  • Replace with Ecto-style schemas or custom structs with behaviours
  • Leverage pattern matching for type validation
  • Use GenServer registries for signature management
  • Custom types as modules implementing a common behaviour
defmodule DSPy.Signature do
  @callback fields() :: %{atom() => DSPy.Field.t()}
  @callback instructions() :: String.t()
end

2. Module System - Composable Components

Current Python Architecture:

  • Base Module class with forward() method
  • Automatic parameter discovery via named_parameters()
  • Deep copy semantics for optimization
  • Callback system for instrumentation

BEAM/OTP Translation:

  • Modules as GenServers with standardized call/3 interface
  • Process hierarchy mirroring module composition
  • Supervision trees for fault tolerance
  • ETS tables for parameter storage and sharing
defmodule DSPy.Module do
  @callback forward(inputs :: map(), opts :: keyword()) :: 
    {:ok, outputs :: map()} | {:error, reason :: term()}
  
  # Each module instance runs in its own process
  use GenServer
end

3. Language Model Abstraction

Current Python Architecture:

  • LM class with provider abstraction
  • Async/sync calling patterns
  • Caching layer with disk/memory tiers
  • Token usage tracking

BEAM/OTP Translation:

  • LM providers as GenServer pools
  • Built-in backpressure via process mailboxes
  • Distributed caching with :ets and :mnesia
  • Circuit breaker pattern for external API calls
defmodule DSPy.LM.Pool do
  use DynamicSupervisor
  # Pool of LM workers with load balancing
end

defmodule DSPy.LM.Worker do
  use GenServer
  # Individual LM instance with circuit breaker
end

4. Adapter Pattern - Format Translation

Current Python Architecture:

  • ChatAdapter, JSONAdapter for different LM interfaces
  • Message formatting and parsing logic
  • Custom type serialization
  • Error handling and retries

BEAM/OTP Translation:

  • Adapters as stateless modules or GenServers
  • Pipeline pattern for message transformation
  • Streaming support via GenStage
  • Supervisor-based retry mechanisms

5. Optimization/Teleprompting System

Current Python Architecture:

  • Multiple optimization strategies (COPRO, Bootstrap, etc.)
  • Parallel evaluation with threading
  • Complex state management during optimization
  • Metric-driven optimization loops

BEAM/OTP Translation:

  • Each optimization run as a supervised process tree
  • Task distribution via Task.Supervisor
  • Persistent state in ETS/Mnesia for large optimizations
  • Real-time progress monitoring via Phoenix LiveView
defmodule DSPy.Teleprompt.Supervisor do
  use Supervisor
  
  # Supervises optimization process, evaluation workers, and state management
end

Key Architectural Patterns for BEAM

1. Process-Per-Module Architecture

Instead of object instances, each DSPy module would run in its own process:

# Module composition creates process hierarchies
%{
  "question_answerer" => #PID<0.123.0>,
  "retriever" => #PID<0.124.0>,
  "chain_of_thought" => #PID<0.125.0>
}

Benefits:

  • Fault isolation (one module failure doesn’t crash others)
  • Natural parallelism
  • Memory isolation
  • Built-in monitoring and restart capabilities

2. Supervision Trees for Fault Tolerance

DSPy.Application.Supervisor
├── DSPy.LM.Supervisor
   ├── Provider.OpenAI.Pool
   ├── Provider.Anthropic.Pool
   └── DSPy.Cache.Manager
├── DSPy.Module.Supervisor (DynamicSupervisor)
   ├── Module.Instance.{uuid1}
   ├── Module.Instance.{uuid2}
   └── ...
└── DSPy.Teleprompt.Supervisor
    ├── Optimization.{run_id}
    └── Evaluation.Pool

3. Event-Driven Architecture

Replace callback system with Phoenix PubSub:

# Module execution events
Phoenix.PubSub.broadcast(DSPy.PubSub, "module:events", {
  :module_start, 
  %{module_id: module_id, inputs: inputs, timestamp: timestamp}
})

4. Streaming and Backpressure

Use GenStage for streaming LM responses:

defmodule DSPy.LM.Stream do
  use GenStage
  
  # Handles streaming responses with built-in backpressure
end

Memory and State Management

1. ETS for Fast Local Caching

  • Signature registries
  • Module parameter storage
  • Local LM response cache

2. Mnesia for Distributed State

  • Optimization history
  • Training data
  • Cross-node module sharing

3. Process Dictionary for Request Context

  • Trace information
  • Request-scoped settings
  • Callback state

Concurrency and Parallelism Advantages

1. Natural Parallelism

  • Module evaluation across multiple processes
  • Parallel optimization candidate testing
  • Concurrent LM requests with different providers

2. Backpressure Handling

  • Process mailboxes provide natural rate limiting
  • GenStage for streaming operations
  • Circuit breakers for external API protection

3. Distributed Computing

  • Spread optimization across multiple nodes
  • Distributed caching with Mnesia
  • Node-level fault tolerance

Error Handling and Recovery

1. Let It Crash Philosophy

  • Module failures are isolated and recoverable
  • Supervisor strategies for different failure modes
  • Graceful degradation patterns

2. Circuit Breaker Pattern

  • Protect against LM API failures
  • Automatic retry with exponential backoff
  • Health check monitoring

3. Poison Message Handling

  • Dead letter queues for problematic inputs
  • Automatic quarantine and analysis
  • Human-in-the-loop recovery workflows

Configuration and Settings

Replace global settings with:

1. Application Environment

config :dspy,
  default_lm: DSPy.LM.OpenAI,
  cache_ttl: :timer.minutes(30),
  max_retries: 3

2. Process-Local Configuration

# Per-process overrides
Process.put(:dspy_config, %{temperature: 0.7})

3. Dynamic Configuration

# Runtime configuration changes
DSPy.Config.update(:default_lm, DSPy.LM.Anthropic)

API Design Patterns

1. Pipeline-Based Composition

result = 
  inputs
  |> DSPy.Module.call(retriever)
  |> DSPy.Module.call(chain_of_thought)
  |> DSPy.Module.call(answer_formatter)

2. Supervision Tree Building

{:ok, program_pid} = DSPy.Program.start_link([
  {:retriever, DSPy.Retrieve, [k: 5]},
  {:cot, DSPy.ChainOfThought, [signature: "question -> answer"]},
  {:formatter, DSPy.Format, [template: "Answer: {{answer}}"]}
])

3. Stream Processing

inputs
|> DSPy.Stream.from_enumerable()
|> DSPy.Stream.through_module(question_processor)
|> DSPy.Stream.batch(50)
|> DSPy.Stream.to_list()

Key Benefits of BEAM Architecture

  1. Fault Tolerance: Module failures don’t crash the entire system
  2. Scalability: Lightweight processes enable massive concurrency
  3. Distribution: Natural support for multi-node deployments
  4. Monitoring: Built-in process monitoring and health checking
  5. Hot Code Upgrades: Update modules without system downtime
  6. Resource Management: Process-level memory and CPU isolation
  7. Backpressure: Built-in flow control prevents system overload

Implementation Considerations

1. State Persistence

  • Use GenServer state for module parameters
  • ETS for frequently accessed data
  • Mnesia for distributed/persistent state

2. Type System Integration

  • Leverage Elixir’s pattern matching
  • Consider using TypedStruct for structured data
  • Implement runtime type checking where needed

3. Interoperability

  • NIFs for performance-critical components
  • Ports for Python model integration
  • GenStage for streaming data processing

This architecture would make DSPy more robust, scalable, and naturally distributed while maintaining its core declarative programming model for language models.