By Sagar Shankaran, Founder of CallSphere
Use the Builder pattern to create fluent, validated, and immutable agent configurations — replacing sprawling constructors with readable step-by-step builder classes.
Key takeaways
AI agents often require complex configuration: model selection, temperature, system prompts, tool registrations, memory backends, retry policies, guardrails, and more. Passing all of these as constructor parameters creates unwieldy function signatures. Worse, it makes it easy to forget a required parameter or misconfigure optional ones.
The Builder pattern solves this by providing a step-by-step, fluent API for constructing complex objects. Each method sets one aspect of the configuration and returns the builder itself, enabling method chaining. A final build() call validates everything and produces an immutable configuration object.
First, define the target configuration as a frozen dataclass — once built, it cannot be modified:
Hear it before you finish reading
Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.
flowchart LR
INPUT(["User intent"])
PARSE["Parse plus<br/>classify"]
PLAN["Plan and tool<br/>selection"]
AGENT["Agent loop<br/>LLM plus tools"]
GUARD{"Guardrails<br/>and policy"}
EXEC["Execute and<br/>verify result"]
OBS[("Trace and metrics")]
OUT(["Outcome plus<br/>next action"])
INPUT --> PARSE --> PLAN --> AGENT --> GUARD
GUARD -->|Pass| EXEC --> OUT
GUARD -->|Fail| AGENT
AGENT --> OBS
style AGENT fill:#4f46e5,stroke:#4338ca,color:#fff
style GUARD fill:#f59e0b,stroke:#d97706,color:#1f2937
style OBS fill:#ede9fe,stroke:#7c3aed,color:#1e1b4b
style OUT fill:#059669,stroke:#047857,color:#fff
from dataclasses import dataclass, field
from typing import Callable, Any
@dataclass(frozen=True)
class ToolDefinition:
name: str
description: str
handler: Callable
parameters_schema: dict
@dataclass(frozen=True)
class AgentConfig:
name: str
model: str
system_prompt: str
temperature: float
max_tokens: int
tools: tuple[ToolDefinition, ...]
memory_backend: str | None
max_retries: int
timeout_seconds: int
guardrails: tuple[str, ...]
def describe(self) -> str:
return (
f"Agent '{self.name}' using {self.model} "
f"with {len(self.tools)} tools, "
f"memory={self.memory_backend or 'none'}"
)
class AgentConfigBuilder:
def __init__(self):
self._name: str | None = None
self._model: str = "gpt-4o"
self._system_prompt: str = "You are a helpful assistant."
self._temperature: float = 0.7
self._max_tokens: int = 4096
self._tools: list[ToolDefinition] = []
self._memory_backend: str | None = None
self._max_retries: int = 3
self._timeout_seconds: int = 30
self._guardrails: list[str] = []
def with_name(self, name: str) -> "AgentConfigBuilder":
self._name = name
return self
def with_model(self, model: str) -> "AgentConfigBuilder":
allowed = {"gpt-4o", "gpt-4o-mini", "claude-sonnet-4-20250514",
"claude-haiku-35"}
if model not in allowed:
raise ValueError(
f"Unknown model '{model}'. Allowed: {allowed}"
)
self._model = model
return self
def with_system_prompt(self, prompt: str) -> "AgentConfigBuilder":
if len(prompt) > 10000:
raise ValueError("System prompt exceeds 10000 chars")
self._system_prompt = prompt
return self
def with_temperature(self, temp: float) -> "AgentConfigBuilder":
if not 0.0 <= temp <= 2.0:
raise ValueError("Temperature must be between 0.0 and 2.0")
self._temperature = temp
return self
def with_max_tokens(self, tokens: int) -> "AgentConfigBuilder":
self._max_tokens = tokens
return self
def add_tool(self, name: str, description: str,
handler: Callable,
parameters_schema: dict | None = None,
) -> "AgentConfigBuilder":
tool = ToolDefinition(
name=name,
description=description,
handler=handler,
parameters_schema=parameters_schema or {},
)
self._tools.append(tool)
return self
def with_memory(self, backend: str) -> "AgentConfigBuilder":
valid = {"redis", "sqlite", "in_memory", "postgres"}
if backend not in valid:
raise ValueError(f"Unknown memory backend: {backend}")
self._memory_backend = backend
return self
def with_retries(self, count: int) -> "AgentConfigBuilder":
self._max_retries = max(0, count)
return self
def with_timeout(self, seconds: int) -> "AgentConfigBuilder":
self._timeout_seconds = seconds
return self
def add_guardrail(self, rule: str) -> "AgentConfigBuilder":
self._guardrails.append(rule)
return self
def build(self) -> AgentConfig:
# Validation
if not self._name:
raise ValueError("Agent name is required")
if not self._system_prompt.strip():
raise ValueError("System prompt cannot be empty")
# Check for duplicate tool names
tool_names = [t.name for t in self._tools]
if len(tool_names) != len(set(tool_names)):
raise ValueError("Duplicate tool names detected")
return AgentConfig(
name=self._name,
model=self._model,
system_prompt=self._system_prompt,
temperature=self._temperature,
max_tokens=self._max_tokens,
tools=tuple(self._tools),
memory_backend=self._memory_backend,
max_retries=self._max_retries,
timeout_seconds=self._timeout_seconds,
guardrails=tuple(self._guardrails),
)
def search_web(query: str) -> str:
return f"Results for: {query}"
def read_file(path: str) -> str:
return f"Contents of: {path}"
config = (
AgentConfigBuilder()
.with_name("research-assistant")
.with_model("gpt-4o")
.with_system_prompt(
"You are a research assistant that finds and "
"synthesizes information from multiple sources."
)
.with_temperature(0.3)
.with_max_tokens(8192)
.add_tool("search", "Search the web", search_web)
.add_tool("read_file", "Read a local file", read_file)
.with_memory("redis")
.with_retries(3)
.with_timeout(60)
.add_guardrail("Never share personal information")
.add_guardrail("Always cite sources")
.build()
)
print(config.describe())
# Agent 'research-assistant' using gpt-4o with 2 tools, memory=redis
Create factory methods for common configurations:
class AgentPresets:
@staticmethod
def fast_and_cheap() -> AgentConfigBuilder:
return (
AgentConfigBuilder()
.with_model("gpt-4o-mini")
.with_temperature(0.5)
.with_max_tokens(2048)
.with_retries(1)
.with_timeout(15)
)
@staticmethod
def high_quality() -> AgentConfigBuilder:
return (
AgentConfigBuilder()
.with_model("gpt-4o")
.with_temperature(0.2)
.with_max_tokens(8192)
.with_retries(3)
.with_timeout(60)
)
# Start from a preset and customize
config = (
AgentPresets.high_quality()
.with_name("legal-reviewer")
.with_system_prompt("You are a legal document reviewer.")
.add_guardrail("Flag any potentially non-compliant clauses")
.build()
)
Keyword arguments work for simple configurations but break down when you have validation rules that depend on combinations of parameters, when you want to enforce required fields at build time rather than runtime, or when you need preset configurations that users can extend. The builder gives you all of this with a readable, self-documenting API.
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.
Using @dataclass(frozen=True) prevents attribute reassignment after creation. For deeper immutability, use tuples instead of lists for collection fields (as shown with tools and guardrails). This ensures that neither the config object nor its contents can be accidentally modified after construction.
Add a to_builder() method on AgentConfig that creates a new AgentConfigBuilder pre-populated with the current configuration values. This lets you create variations of existing configs without starting from scratch.
#AgentDesignPatterns #BuilderPattern #Python #Configuration #AgenticAI #LearnAI #AIEngineering

Written by
Sagar Shankaran· Founder, CallSphere
LinkedInSagar Shankaran is the founder of CallSphere, where he builds production AI voice and chat agents deployed across healthcare, hospitality, real estate, and home services. He writes about agentic AI, LLM engineering, and shipping voice agents that handle real calls in production.
See how AI voice agents work for your industry. Live demo available -- no signup required.
The 2026 desktop AI agent landscape — ServiceNow Project Arc, Anthropic Claude offerings, OpenAI agents, and Google Mariner. A buyer's map.
Step-by-step build of a working agent with the OpenAI Agents SDK — Agent class, tools, handoffs, tracing — plus an eval pipeline that catches regressions before merge.
An agentic-AI perspective on Anthropic Skills system, covering orchestration patterns, tool use, and how agent tooling fits production agent stacks.
Enterprise CIO Guide perspective on Comet's general-availability launch put an agentic browser in front of millions of consumers, and it works better than the demos suggested.
Enterprise CIO Guide perspective on Harvey AI's enterprise rollout numbers show legal agents have moved past the pilot stage at AmLaw 100 firms.
Enterprise CIO Guide perspective on Hippocratic AI's deployment numbers show healthcare voice agents are moving from pilot to production across major US health systems.
© 2026 CallSphere Inc. All rights reserved.
Made within San Francisco