---
title: "The Strategy Pattern: Swappable Agent Behaviors Based on Runtime Context"
description: "Implement the Strategy pattern to dynamically swap AI agent behaviors at runtime — supporting A/B testing, context-driven model selection, and flexible agent configuration."
canonical: https://callsphere.ai/blog/strategy-pattern-swappable-agent-behaviors-runtime-context
category: "Learn Agentic AI"
tags: ["Agent Design Patterns", "Strategy Pattern", "Python", "A/B Testing", "Agentic AI"]
author: "CallSphere Team"
published: 2026-03-17T00:00:00.000Z
updated: 2026-05-07T09:43:43.817Z
---

# 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:

```mermaid
flowchart LR
    PR(["PR opened"])
    UNIT["Unit tests"]
    EVAL["Eval harness
PromptFoo or Braintrust"]
    GOLD[("Golden set
200 tagged cases")]
    JUDGE["LLM as judge
plus regex graders"]
    SCORE["Aggregate score
and per slice"]
    GATE{"Score regress
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
```

```python
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

```python
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

```python
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:

```python
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

---

Source: https://callsphere.ai/blog/strategy-pattern-swappable-agent-behaviors-runtime-context
