Skip to content
Learn Agentic AI10 min read0 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:

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:

See AI Voice Agents Handle Real Calls

Book a free demo or calculate how much you can save with AI voice automation.

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.

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 this article
C

CallSphere Team

Expert insights on AI voice agents and customer communication automation.

Try CallSphere AI Voice Agents

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