Skip to main content

Build the DSPy solver

The DSPy solver transforms puzzle-solving from rule-based logic into learned reasoning patterns. By using Chain-of-Thought prompting, our AI learns to analyze word relationships, consider context clues, and develop strategic approaches that improve with training data.

Solver module structure

The ConnectionsSolver module represents the core intelligence of our system. Rather than hard-coding puzzle-solving rules, it uses DSPy's Chain-of-Thought framework to enable the language model to reason through puzzles step-by-step:

dspy_modules/solver.py
class ConnectionsSolver(dspy.Module):
"""DSPy module for solving Connections puzzles."""

def __init__(self):
self.predict = dspy.ChainOfThought(
"rules, available_words, history_feedback, guess_index -> guess"
)
self.logic = ConnectionsGameLogic()

A single Chain-of-Thought predictor takes structured inputs (game rules, available words, guess history, and attempt number) and outputs reasoned guesses. This signature defines the "contract" between the language model and our puzzle domain.

The integration with ConnectionsGameLogic is crucial. While the AI handles reasoning and pattern recognition, the game logic enforces rules, validates guesses, and provides structured feedback. This separation allows the AI to focus on what it does best (understanding relationships between words) while ensuring game mechanics remain consistent and reliable.

Puzzle-solving loop

The heart of our solver is an iterative reasoning loop that mimics how a human might approach these puzzles. The solver makes educated guesses, learning from feedback, and refining strategy based on previous attempts:

dspy_modules/solver.py
        while not state.finished and guess_index < 10:  # Safety limit
guess_index += 1

# Get available words
available_words = self.logic.get_remaining_words(state)
available_words_str = ", ".join(sorted(available_words))

# Generate guess using DSPy Chain-of-Thought
prediction = self.predict(
rules=GAME_RULES,
available_words=available_words_str,
history_feedback=" | ".join(history_feedback)
if history_feedback
else "No previous guesses",
guess_index=guess_index,
)

# Process the guess and update game state
result = self.logic.process_guess(state, prediction.guess.strip())
history_feedback.append(
f"Guess {guess_index}: '{prediction.guess.strip()}' -> {result}"
)

This loop demonstrates the power of structured AI reasoning. In each iteration, the solver receives a complete picture of the current game state: which words are still available, what previous guesses were made and their outcomes, and which attempt this represents. The Chain-of-Thought predictor uses this rich context to generate not just a guess, but the reasoning behind it.

The feedback mechanism is critical for learning. When the solver makes a guess like "PARIS, LONDON, MADRID, ROME" for a capitals group, it receives immediate feedback: "CORRECT" if right, "INCORRECT" with remaining guesses if wrong, or "INVALID_RESPONSE" with detailed error information if the guess is malformed. This feedback becomes part of the context for subsequent guesses, allowing the AI to learn from mistakes within a single puzzle.

Model deployment

The transition from development to production requires careful orchestration of model creation, configuration, and evaluation. Our Dagster component handles this complexity through well-defined assets that manage the entire model lifecycle:

src/project_dspy/components/ds_py_model_builder.py
            # Configure DSPy
dspy_resource.configure_dspy()

# Create baseline model
model = ConnectionsSolver()

# Save baseline model
model_path = dspy_resource.save_model(
model, f"{self.model_name}_baseline", "latest"
)

This deployment pattern follows software engineering best practices:

  • The DSPy resource ensures consistent language model configuration across all environments.
  • The solver instance is created with a clean state for reproducible results.
  • The model is immediately persisted to disk for reliable retrieval during optimization and evaluation phases.

The baseline model serves as our performance benchmark. Before we apply any optimization techniques, we need to understand how well the "vanilla" Chain-of-Thought approach performs. This baseline becomes crucial for measuring the effectiveness of subsequent optimizations and ensuring that our improvements are meaningful rather than just random variation.

Next steps