Skip to content
Learn Agentic AI
Learn Agentic AI10 min read19 views

The Strategy Pattern: Swappable Agent Behaviors Based on Runtime Context

Implement the Strategy pattern to dynamically swap AI agent behaviors at runtime — supporting A/B testing, context-driven model selection, and flexible agent configuration.

The Problem with Hardcoded Behaviors

Imagine an AI agent that summarizes text. Today it uses GPT-4o, but tomorrow you want to test Claude, and next week you want to switch to a local model for cost savings. If the model choice is hardcoded, every change requires modifying the agent code. The Strategy pattern solves this by extracting the variable behavior into interchangeable strategy objects, letting you swap implementations without touching the agent logic.

Defining the Strategy Interface

The strategy interface defines the contract that all implementations must follow:

Hear it before you finish reading

Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.

Try Live Demo →
flowchart LR
    PR(["PR opened"])
    UNIT["Unit tests"]
    EVAL["Eval harness<br/>PromptFoo or Braintrust"]
    GOLD[("Golden set<br/>200 tagged cases")]
    JUDGE["LLM as judge<br/>plus regex graders"]
    SCORE["Aggregate score<br/>and per slice"]
    GATE{"Score regress<br/>more than 2 percent?"}
    BLOCK(["Block merge"])
    MERGE(["Merge to main"])
    PR --> UNIT --> EVAL --> GOLD --> JUDGE --> SCORE --> GATE
    GATE -->|Yes| BLOCK
    GATE -->|No| MERGE
    style EVAL fill:#4f46e5,stroke:#4338ca,color:#fff
    style GATE fill:#f59e0b,stroke:#d97706,color:#1f2937
    style BLOCK fill:#dc2626,stroke:#b91c1c,color:#fff
    style MERGE fill:#059669,stroke:#047857,color:#fff
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Any

@dataclass
class StrategyResult:
    content: str
    model_used: str
    tokens_used: int
    cost_estimate: float
    metadata: dict

class CompletionStrategy(ABC):
    @abstractmethod
    def name(self) -> str:
        pass

    @abstractmethod
    def execute(self, system_prompt: str,
                user_message: str) -> StrategyResult:
        pass

    @abstractmethod
    def estimated_cost(self, input_tokens: int) -> float:
        pass

Implementing Concrete Strategies

import openai
import anthropic

class OpenAIStrategy(CompletionStrategy):
    def __init__(self, model: str = "gpt-4o"):
        self.model = model
        self.client = openai.OpenAI()

    def name(self) -> str:
        return f"openai-{self.model}"

    def execute(self, system_prompt: str,
                user_message: str) -> StrategyResult:
        response = self.client.chat.completions.create(
            model=self.model,
            messages=[
                {"role": "system", "content": system_prompt},
                {"role": "user", "content": user_message},
            ],
        )
        usage = response.usage
        return StrategyResult(
            content=response.choices[0].message.content,
            model_used=self.model,
            tokens_used=usage.total_tokens,
            cost_estimate=self.estimated_cost(usage.prompt_tokens),
            metadata={"finish_reason": response.choices[0].finish_reason},
        )

    def estimated_cost(self, input_tokens: int) -> float:
        rates = {"gpt-4o": 0.005, "gpt-4o-mini": 0.00015}
        return (input_tokens / 1000) * rates.get(self.model, 0.005)

class AnthropicStrategy(CompletionStrategy):
    def __init__(self, model: str = "claude-sonnet-4-20250514"):
        self.model = model
        self.client = anthropic.Anthropic()

    def name(self) -> str:
        return f"anthropic-{self.model}"

    def execute(self, system_prompt: str,
                user_message: str) -> StrategyResult:
        response = self.client.messages.create(
            model=self.model,
            max_tokens=2048,
            system=system_prompt,
            messages=[{"role": "user", "content": user_message}],
        )
        tokens = response.usage.input_tokens + response.usage.output_tokens
        return StrategyResult(
            content=response.content[0].text,
            model_used=self.model,
            tokens_used=tokens,
            cost_estimate=self.estimated_cost(response.usage.input_tokens),
            metadata={"stop_reason": response.stop_reason},
        )

    def estimated_cost(self, input_tokens: int) -> float:
        return (input_tokens / 1000) * 0.003

The Agent with Swappable Strategy

class SummarizationAgent:
    def __init__(self, strategy: CompletionStrategy):
        self._strategy = strategy

    @property
    def strategy(self) -> CompletionStrategy:
        return self._strategy

    @strategy.setter
    def strategy(self, new_strategy: CompletionStrategy):
        print(f"Switching strategy: {self._strategy.name()} "
              f"-> {new_strategy.name()}")
        self._strategy = new_strategy

    def summarize(self, text: str) -> StrategyResult:
        return self._strategy.execute(
            system_prompt="Summarize the following text concisely.",
            user_message=text,
        )

# Start with OpenAI
agent = SummarizationAgent(OpenAIStrategy("gpt-4o-mini"))
result = agent.summarize("Long article text here...")
print(f"Used: {result.model_used}, Cost: ${result.cost_estimate:.4f}")

# Swap to Anthropic at runtime
agent.strategy = AnthropicStrategy()
result = agent.summarize("Long article text here...")
print(f"Used: {result.model_used}, Cost: ${result.cost_estimate:.4f}")

Dynamic Strategy Selection and A/B Testing

You can select strategies based on runtime context or run A/B tests:

import random

class StrategySelector:
    def __init__(self):
        self.strategies: dict[str, CompletionStrategy] = {}
        self.ab_test_weights: dict[str, float] = {}

    def register(self, strategy: CompletionStrategy,
                 weight: float = 1.0):
        self.strategies[strategy.name()] = strategy
        self.ab_test_weights[strategy.name()] = weight

    def select_by_context(self, context: dict) -> CompletionStrategy:
        if context.get("requires_reasoning"):
            return self.strategies.get(
                "openai-gpt-4o",
                list(self.strategies.values())[0],
            )
        if context.get("budget_sensitive"):
            cheapest = min(
                self.strategies.values(),
                key=lambda s: s.estimated_cost(1000),
            )
            return cheapest
        return self.select_ab_test()

    def select_ab_test(self) -> CompletionStrategy:
        names = list(self.ab_test_weights.keys())
        weights = list(self.ab_test_weights.values())
        chosen = random.choices(names, weights=weights, k=1)[0]
        return self.strategies[chosen]

selector = StrategySelector()
selector.register(OpenAIStrategy("gpt-4o"), weight=0.3)
selector.register(OpenAIStrategy("gpt-4o-mini"), weight=0.5)
selector.register(AnthropicStrategy(), weight=0.2)

strategy = selector.select_by_context({"budget_sensitive": True})

FAQ

How do I track which strategy performs best in A/B testing?

Log the strategy name, input hash, output quality score, latency, and cost for every request. Aggregate these metrics over time and compare strategies on the dimensions that matter most to your use case — quality, speed, or cost. Use statistical significance tests before declaring a winner.

Should every agent use the Strategy pattern?

No. Use it when the behavior genuinely varies — different models, different prompt templates, different APIs. If an agent always uses the same model and prompt, adding a strategy abstraction creates unnecessary complexity. Apply the pattern when you have at least two concrete implementations or anticipate needing to swap behaviors.

Still reading? Stop comparing — try CallSphere live.

CallSphere ships complete AI voice agents per industry — 14 tools for healthcare, 10 agents for real estate, 4 specialists for salons. See how it actually handles a call before you book a demo.

Can I compose multiple strategies together?

Yes. Create a CompositeStrategy that runs multiple strategies and picks the best result (ensemble approach) or chains them sequentially (pipeline approach). This combines the Strategy pattern with other patterns for more sophisticated behavior.


#AgentDesignPatterns #StrategyPattern #Python #ABTesting #AgenticAI #LearnAI #AIEngineering

Share

Try CallSphere AI Voice Agents

See how AI voice agents work for your industry. Live demo available -- no signup required.