← Back to Ds ex

COUPLING ANALYSIS

Documentation for COUPLING_ANALYSIS from the Ds ex repository.

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:

  1. Broader Adoption: Other Elixir ML projects could adopt ElixirML
  2. Community Contribution: External contributors to the foundation
  3. Focused Development: Clear separation of concerns
  4. 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:

  1. Minimal coupling - easy to extract
  2. High reusability - valuable for broader ecosystem
  3. Strategic value - positions ElixirML as universal ML foundation
  4. Clean architecture - proper separation of concerns
  5. Future growth - enables ecosystem development

Why Keep Together:

  1. Convenience - but at cost of strategic value
  2. Simpler deployment - but limits reusability
  3. 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