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
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.