← Back to Docs

DIAGS dspex gap

Documentation for DIAGS_dspex_gap from the Ds ex repository.

Excellent. This is a perfect use case for architectural diagrams, as they can visually codify the current state and make the path forward much clearer. I will create a new set of diagrams based on my understanding of your DSPEx implementation, using the file structure and README you’ve provided.

I will explicitly highlight the “Todo” or “Planned” components based on your roadmap and the natural evolution from the Python version. I’ll use color-coding in the diagrams for clarity:

  • Green: Implemented and working.
  • Yellow: Partially implemented or foundational work is done, but needs enhancement.
  • Red/Orange: Planned but not yet implemented (Todos).

1 High-Level Conceptual Architecture of DSPEx

This diagram shows the main layers of your Elixir implementation, highlighting the central role of OTP services and the BEAM’s concurrency model.

graph TD subgraph UA["User Application"] A["DSPEx Program - Predict and future ReAct"] end subgraph DSP["DSPEx Framework (BEAM Native)"] subgraph SOA["Services - OTP Applications"] S1["services/config_manager.ex
ConfigManager GenServer"] S2["services/telemetry_setup.ex
TelemetrySetup GenServer"] S3["Finch HTTP Pool"] end subgraph PAE["Programming and Execution"] P["predict.ex
DSPEx.Predict"] PROG["program.ex
DSPEx.Program Behaviour"] end subgraph OT["Optimization - Teleprompter"] T["teleprompter/bootstrap_fewshot.ex
BootstrapFewShot"] T_TODO["More Teleprompters
MIPRO, BEACON, etc"] EVAL["evaluate.ex
DSPEx.Evaluate"] end subgraph CAL["Client and Adapter Layer"] CM["client_manager.ex
ClientManager GenServer"] ADP["adapter.ex
DSPEx.Adapter"] CM_CACHE["Cachex Todo
Response Caching"] CM_FUSE["Fuse Todo
Circuit Breaker"] end end subgraph ES["External Services"] LLM["LLM API: Gemini, OpenAI"] end A -- "Implements" --> PROG A -- "Is an instance of" --> P P -- "Uses" --> CM P -- "Uses" --> ADP CM -- "Uses" --> S1 CM -- "Uses" --> S3 P -- "Emits events to" --> S2 T -- "Optimizes" --> A T -- "Uses" --> EVAL CM_CACHE -- "Will be used by" --> CM CM_FUSE -- "Will protect calls from" --> CM CM -- "Makes HTTP calls to" --> LLM %% Elixir-inspired styling classDef setupPhase fill:#4e2a8e,stroke:#24292e,stroke-width:2px,color:#fff classDef concurrentEngine fill:#7c4dbd,stroke:#4e2a8e,stroke-width:2px,color:#fff classDef taskNode fill:#9b72d0,stroke:#4e2a8e,stroke-width:2px,color:#fff classDef processNode fill:#b89ce0,stroke:#4e2a8e,stroke-width:2px,color:#24292e classDef resultNode fill:#d4c5ec,stroke:#4e2a8e,stroke-width:1px,color:#24292e classDef aggregationPhase fill:#f5f5f5,stroke:#666,stroke-width:2px,color:#24292e classDef subgraphTitleTop fill:#e6e0f0,stroke:#b89ce0,stroke-width:2px,color:#24292e class A setupPhase class S1,S2,S3 concurrentEngine class P,PROG taskNode class T,EVAL processNode class CM,ADP resultNode class T_TODO,CM_CACHE,CM_FUSE aggregationPhase class LLM processNode class UA,DSP,SOA,PAE,OT,CAL,ES subgraphTitleTop %% Darker arrow styling for better visibility linkStyle default stroke:#24292e,stroke-width:2px

Architectural Insights & Todos:

  • Foundation First: You’ve correctly built on a solid foundation of OTP services. ConfigManager and TelemetrySetup are robust, BEAM-native replacements for Python’s global settings and ad-hoc logging.
  • GenServer is Key: Your ClientManager is the heart of the execution layer. It’s the stateful process that will manage connections, configuration, and resilience.
  • Todo: Enhance ClientManager: The current ClientManager is functional but is the designated place for significant resilience upgrades.
    • Caching: Integrate Cachex to implement response caching, which is critical for reducing costs and latency during optimization. This is a direct parallel to dspy.Cache.
    • Circuit Breaker: Integrate Fuse to protect against failing LLM API endpoints. This is a major advantage over the basic retry logic in Python’s litellm.
    • Rate Limiting: A GenServer is the perfect place to manage rate limits and implement exponential backoff strategies per-provider.
  • Todo: Expand Teleprompters: BootstrapFewShot is a great start. The next step is to implement more advanced optimizers like MIPRO or BEACON, which will further test the concurrency and resilience of your evaluation engine.

2 Core Primitives: The Signature and Program

This diagram shows how you’ve used Elixir’s metaprogramming capabilities to create a robust and compile-time-safe contract system.

graph TD subgraph CT["Compile-Time"] A["use DSPEx.Signature, question to answer"] B["signature.ex
DSPEx.Signature.Parser"] C["Metaprogramming
Macro Expansion"] end subgraph RT["Runtime"] D["QASignature Module
Generated Struct and Functions"] E["predict.ex
DSPEx.Predict"] F["program.ex
DSPEx.Program Behaviour"] EXTEND_TODO["Signature.extend/2 Todo
For ChainOfThought and ReAct"] end subgraph FP["Future Programs - Todo"] COT_TODO["DSPEx.ChainOfThought"] REACT_TODO["DSPEx.ReAct"] end A -- "Invokes at compile-time" --> B B -- "Generates AST for" --> C C -- "Defines" --> D E -- "Implements" --> F E -- "Is configured with" --> D D -- "Will be extended by" --> EXTEND_TODO EXTEND_TODO -- "Enables" --> COT_TODO EXTEND_TODO -- "Enables" --> REACT_TODO %% Elixir-inspired styling classDef setupPhase fill:#4e2a8e,stroke:#24292e,stroke-width:2px,color:#fff classDef concurrentEngine fill:#7c4dbd,stroke:#4e2a8e,stroke-width:2px,color:#fff classDef taskNode fill:#9b72d0,stroke:#4e2a8e,stroke-width:2px,color:#fff classDef processNode fill:#b89ce0,stroke:#4e2a8e,stroke-width:2px,color:#24292e classDef resultNode fill:#d4c5ec,stroke:#4e2a8e,stroke-width:1px,color:#24292e classDef aggregationPhase fill:#f5f5f5,stroke:#666,stroke-width:2px,color:#24292e classDef subgraphTitleTop fill:#e6e0f0,stroke:#b89ce0,stroke-width:2px,color:#24292e class A setupPhase class B,C concurrentEngine class D,E,F taskNode class EXTEND_TODO aggregationPhase class COT_TODO,REACT_TODO processNode class CT,RT,FP subgraphTitleTop %% Darker arrow styling for better visibility linkStyle default stroke:#24292e,stroke-width:2px

Architectural Insights & Todos:

  • Compile-Time Safety: This is a huge advantage. Using a macro (use DSPEx.Signature) to parse the signature string at compile time catches errors early, unlike Python’s runtime approach.
  • Behavior-Driven Design: DSPEx.Program provides a clean, consistent interface for all executable modules, which will be essential for composing them later.
  • Todo: Signature Extension: As your CLAUDE.md file correctly identifies, implementing a DSPEx.Signature.extend/2 function is a critical next step. This is necessary for building more complex modules like ChainOfThought, which programmatically add a reasoning field to a base signature.
  • Todo: Advanced Program Types: Once signature extension is complete, you can implement ChainOfThought, ReAct, and other composite modules from the DSPy paper. These are built by composing DSPEx.Predict modules, not by creating entirely new primitives.

3 The Execution Flow: Program.forward/3

This sequence diagram details the runtime flow of a prediction, emphasizing the interaction between your GenServers and the core logic.

sequenceDiagram participant User participant Program as DSPEx.Program participant Predict as DSPEx.Predict participant Adapter as DSPEx.Adapter participant ClientMgr as DSPEx.ClientManager participant LLM_API as External LLM API User->>+Program: Program.forward program inputs Program->>+Predict: Predict.forward program inputs opts Note over Predict,Adapter: format_messages Predict->>+Adapter: format_messages signature demos inputs Adapter-->>-Predict: ok messages Note over Predict,ClientMgr: make_request Predict->>ClientMgr: GenServer.call request messages opts ClientMgr->>LLM_API: HTTP POST via Finch LLM_API-->>ClientMgr: ok raw_response Note right of ClientMgr: Todo: Caching Cachex
Todo: Circuit Breaker Fuse
Todo: Rate Limiting ClientMgr-->>Predict: ok raw_response Note over Predict,Adapter: parse_response Predict->>+Adapter: parse_response signature raw_response Adapter-->>-Predict: ok outputs Predict-->>-Program: ok outputs Program-->>-User: ok outputs

Architectural Insights & Todos:

  • Clear Stages: The flow is cleanly divided into format -> request -> parse. This separation is fundamental to DSPy’s design and you’ve captured it well.
  • Centralized Client Logic: The ClientManager GenServer is the perfect place to encapsulate all external communication logic. It acts as a resilient gateway to the outside world.
  • Asynchronous by Nature: Although shown as a synchronous call for simplicity, GenServer.call is an asynchronous message-passing operation under the hood, making the system inherently non-blocking.
  • Todo: Comprehensive Resilience in ClientManager:
    • Caching: Before making the HTTP call, the ClientManager should check a cache like Cachex.
    • Circuit Breaker: The call to Req.post should be wrapped in a Fuse circuit breaker to prevent hammering a failing service.
    • Rate Limiting: The ClientManager can maintain state on recent call timestamps to enforce rate limits before making a request.

4 The Optimization Flow: BootstrapFewShot.compile/5

This diagram illustrates how your teleprompter leverages BEAM’s concurrency for efficient optimization.

graph TD subgraph IN["Inputs"] A["Student Program"] B["Teacher Program"] C["Trainset"] D["Metric Function"] end subgraph TP["Teleprompter: BootstrapFewShot.compile/5"] E["Start Compilation"] --> F["Task.async_stream trainset
Generate Bootstrap Candidates
Massively Concurrent"] F --> G["For each example call teacher.forward"] G --> H["Task.async_stream candidates
Evaluate Demonstrations"] H --> I["For each candidate call metric_fn"] I --> J["Filter demos by quality_threshold"] J --> K["Sort by score and select best k demos"] K --> L["Create new OptimizedProgram with selected demos"] end subgraph OUT["Output"] M["DSPEx.OptimizedProgram
Student plus Demos"] end A & B & C & D --> E L --> M %% Elixir-inspired styling classDef setupPhase fill:#4e2a8e,stroke:#24292e,stroke-width:2px,color:#fff classDef concurrentEngine fill:#7c4dbd,stroke:#4e2a8e,stroke-width:2px,color:#fff classDef taskNode fill:#9b72d0,stroke:#4e2a8e,stroke-width:2px,color:#fff classDef processNode fill:#b89ce0,stroke:#4e2a8e,stroke-width:2px,color:#24292e classDef resultNode fill:#d4c5ec,stroke:#4e2a8e,stroke-width:1px,color:#24292e classDef aggregationPhase fill:#f5f5f5,stroke:#666,stroke-width:2px,color:#24292e classDef subgraphTitleTop fill:#e6e0f0,stroke:#b89ce0,stroke-width:2px,color:#24292e class A,B,C,D setupPhase class E taskNode class F,H concurrentEngine class G,I,J,K processNode class L resultNode class M aggregationPhase class IN,TP,OUT subgraphTitleTop %% Darker arrow styling for better visibility linkStyle default stroke:#24292e,stroke-width:2px

Architectural Insights & Todos:

  • BEAM Superpower: Using Task.async_stream is the ideal way to implement this. It will dramatically outperform Python’s threading model for I/O-bound tasks like hitting an LLM API. You’ve correctly identified and implemented this key advantage.
  • Stateless Workers: Each task spawned by async_stream is a lightweight, isolated BEAM process, making the entire operation highly fault-tolerant. A single failed API call won’t crash the optimization run.
  • The OptimizedProgram Struct: Your dspex/optimized_program.ex is the correct functional approach to “compiling” a program. Instead of mutating the student program, you create a new, immutable struct that wraps the original program and its learned demonstrations.
  • Todo: Advanced Optimization Strategies:
    • The current BootstrapFewShot is a great first step. The roadmap should include more complex optimizers like MIPRO or BEACON from the DSPy paper, which involve iterative optimization loops. These will further benefit from Elixir’s concurrency and state management capabilities.
    • Distributed Evaluation: The README mentions this as a future goal. The Task.async_stream model can be extended to a multi-node setup using Node.spawn_link or a higher-level abstraction, making this a natural evolution for DSPEx.