ElixirML ↔ DSPEx Coupling Analysis
Executive Summary
After analyzing the coupling between lib/elixir_ml
and lib/dspex
, I found a well-designed, loosely-coupled architecture with clear separation of concerns. ElixirML should remain as a separate, reusable foundation that can be extracted into its own library. The coupling is minimal and follows good dependency inversion principles.
Coupling Analysis
🔄 DSPEx → ElixirML Dependencies (Strong, Expected)
DSPEx heavily depends on ElixirML as its foundation layer:
# lib/dspex.ex - Main API integration
alias ElixirML.{Variable, Schema, Process}
# lib/dspex/signature.ex - Schema validation
alias ElixirML.{Schema, Variable}
# lib/dspex/program.ex - Variable system integration
:float -> ElixirML.Variable.float(unquote(name), unquote(opts))
:choice -> ElixirML.Variable.choice(unquote(name), choices, ...)
# lib/dspex/builder.ex - Fluent API integration
alias ElixirML.{Variable, Schema}
# lib/dspex/application.ex - Process orchestration
ElixirML.Process.Orchestrator
Analysis: This is healthy, expected coupling. DSPEx is built on top of ElixirML as its foundation.
🔄 ElixirML → DSPEx Dependencies (Minimal, Problematic)
ElixirML has very minimal dependencies on DSPEx:
# lib/elixir_ml/schema.ex - Documentation reference only
# "Enhanced schema system for ElixirML/DSPEx with ML-specific types"
# lib/elixir_ml/resources/program.ex - Single comment
# "Creates a program from a DSPEx signature and configuration."
# lib/elixir_ml/variable/ml_types.ex - Phantom module references
ElixirML.Adapter.JSON, # ❌ These modules don't exist
ElixirML.Reasoning.Predict, # ❌ These modules don't exist
Analysis: The reverse coupling is minimal but problematic - it references non-existent modules that should be in DSPEx.
Reusability Assessment
✅ ElixirML is Highly Reusable
ElixirML’s core components are framework-agnostic:
1. Variable System - Universal Parameter Optimization
# Works with ANY optimization framework
space = ElixirML.Variable.Space.new()
|> ElixirML.Variable.Space.add_variable(
ElixirML.Variable.float(:temperature, range: {0.0, 2.0})
)
# Could be used with:
# - LangChain Elixir port
# - Semantic Kernel Elixir
# - Custom ML frameworks
# - Traditional ML libraries
2. Schema Engine - ML-Native Validation
# Domain-agnostic ML validation
defmodule MyFramework.Schema do
use ElixirML.Schema
defschema ModelOutput do
field :prediction, :probability
field :confidence, :confidence_score
field :embedding, :embedding, dimensions: 768
end
end
3. Process Orchestrator - Advanced Supervision
# Reusable process management for any ML workload
ElixirML.Process.Orchestrator.execute_pipeline(
my_custom_pipeline,
inputs,
strategy: :parallel
)
4. Resource Framework - Declarative Management
# Ash-inspired resources for any domain
defmodule MyFramework.Resources.Model do
use ElixirML.Resource
attributes do
attribute :name, :string
attribute :parameters, :map
end
end
🎯 Strategic Value of Separation
Separating ElixirML would provide significant strategic value:
- Broader Adoption: Other Elixir ML projects could adopt ElixirML
- Community Contribution: External contributors to the foundation
- Focused Development: Clear separation of concerns
- Ecosystem Growth: Enable ElixirML-based tools and libraries
Architectural Assessment
✅ Well-Designed Separation
The current structure follows excellent architectural principles:
lib/
├── dspex/ # Application layer (DSPy implementation)
│ ├── signature.ex # DSPy-specific interfaces
│ ├── program.ex # DSPy-specific behavior
│ └── teleprompter/# DSPy-specific optimization
└── elixir_ml/ # Foundation layer (reusable)
├── variable/ # Universal optimization
├── schema/ # ML-native validation
├── process/ # Advanced supervision
└── resource/ # Declarative management
Benefits of Current Structure:
- ✅ Clear separation of concerns
- ✅ Foundation can evolve independently
- ✅ DSPEx focuses on DSPy-specific features
- ✅ ElixirML provides reusable primitives
Issues to Resolve
🚨 Phantom Module References
ElixirML references modules that don’t exist:
# lib/elixir_ml/variable/ml_types.ex - BROKEN REFERENCES
ElixirML.Adapter.JSON, # Should be DSPEx.Adapter.JSON
ElixirML.Reasoning.ChainOfThought, # Should be DSPEx.Predict.ChainOfThought
Solution: Move these to DSPEx or create proper abstractions:
# Option 1: Use behavior-based abstractions
Variable.module(:adapter,
behavior: DSPEx.Adapter, # ✅ Proper abstraction
modules: [DSPEx.Adapter.JSON, DSPEx.Adapter.Chat]
)
# Option 2: Make them configurable
def adapter(name, opts \\ []) do
adapters = Keyword.get(opts, :adapters, [
# Let the consumer specify the actual modules
])
end
🔧 Minor Documentation Coupling
Some documentation unnecessarily couples the libraries:
# lib/elixir_ml/schema.ex
@moduledoc """
Enhanced schema system for ElixirML/DSPEx # ❌ Unnecessary coupling
"""
# Should be:
@moduledoc """
Enhanced schema system for ML applications with type validation
"""
Recommendations
🎯 Recommendation: Separate into Independent Library
ElixirML should be extracted into a separate, reusable library for these reasons:
1. Strategic Benefits
- Broader ecosystem impact: Other Elixir ML projects can adopt it
- Community growth: External contributors to foundation
- Clear value proposition: “Universal ML foundation for Elixir”
- Future-proofing: Foundation can evolve independently
2. Technical Benefits
- Focused testing: Foundation tested independently
- Cleaner dependencies: No circular references
- Better documentation: Foundation docs separate from DSPy docs
- Versioning independence: Foundation and DSPEx can have different release cycles
3. Minimal Risk
- Low coupling: Very few dependencies to break
- Clear interfaces: Well-defined API boundaries
- Gradual migration: Can be done incrementally
📋 Migration Plan
Phase 1: Fix Phantom References (Week 1)
# Fix broken module references in ml_types.ex
def adapter(name, opts \\ []) do
# Remove hardcoded module references
# Use behavior-based abstractions instead
Variable.module(name,
behavior: Keyword.fetch!(opts, :behavior),
modules: Keyword.fetch!(opts, :modules)
)
end
Phase 2: Remove Documentation Coupling (Week 1)
# Clean up documentation to be framework-agnostic
@moduledoc """
Universal ML foundation for Elixir applications
"""
Phase 3: Extract to Separate Repository (Week 2-3)
# Create new repository
mix new elixir_ml --sup
# Move lib/elixir_ml/* to new repository
# Update DSPEx to depend on elixir_ml package
# Update mix.exs dependencies
Phase 4: Publish as Hex Package (Week 4)
# mix.exs in DSPEx project
defp deps do
[
{:elixir_ml, "~> 0.1.0"}, # ✅ Clean dependency
# ... other deps
]
end
🏗️ Alternative: Keep Together with Clear Boundaries
If you prefer to keep them together (valid choice), ensure:
1. Fix Phantom References
# Make ml_types.ex configurable
def standard_ml_config(opts \\ []) do
adapters = Keyword.get(opts, :adapters, [])
reasoning = Keyword.get(opts, :reasoning, [])
# Consumer provides the actual modules
end
2. Clear Module Organization
lib/
├── elixir_ml/ # Foundation (could be extracted)
└── dspex/ # Application (depends on foundation)
3. Independent Testing
# Test foundation independently
mix test test/elixir_ml/
# Test integration
mix test test/dspex/
Conclusion
Recommendation: Extract ElixirML into separate library 🎯
Why Separate:
- ✅ Minimal coupling - easy to extract
- ✅ High reusability - valuable for broader ecosystem
- ✅ Strategic value - positions ElixirML as universal ML foundation
- ✅ Clean architecture - proper separation of concerns
- ✅ Future growth - enables ecosystem development
Why Keep Together:
- ❌ Convenience - but at cost of strategic value
- ❌ Simpler deployment - but limits reusability
- ❌ Faster iteration - but creates coupling debt
The “Optuna Parallel”
Remember Omar’s insight: DSPy should be “more like Optuna”. Optuna itself is built on a foundational optimization library that others can use. ElixirML could be the foundational ML library that enables the “Optuna for LLMs” vision.
Final Recommendation: Extract ElixirML as separate library - it aligns with the strategic vision and provides maximum value to the Elixir ecosystem.
Analysis Date: 2025-06-20
Coupling Level: LOW (Easy to separate)
Reusability: HIGH (Valuable as standalone)
Recommendation: EXTRACT TO SEPARATE LIBRARY