By Sagar Shankaran, Founder of CallSphere
Design and build developer-friendly SDKs for an AI agent platform, covering API client generation, error handling patterns, streaming support, and versioning strategies that maintain backward compatibility.
Key takeaways
The quality of your SDK determines how quickly developers integrate your agent platform. A great SDK reduces integration from hours to minutes. A bad SDK generates support tickets. The best agent platform SDKs feel like natural extensions of the developer's language — Pythonic in Python, idiomatic in JavaScript, and consistent with the conventions developers already know.
The non-obvious lesson is that SDK design is API design. If your SDK feels awkward, the underlying API is probably wrong. Fix the API first, then the SDK writes itself.
The Python SDK should support both synchronous and asynchronous usage, handle streaming responses, and provide clear error types:
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
# agentplatform/client.py — Python SDK core client
import httpx
from typing import Optional, AsyncIterator
from dataclasses import dataclass
class AgentPlatformError(Exception):
def __init__(self, status_code: int, message: str, error_code: str = None):
self.status_code = status_code
self.message = message
self.error_code = error_code
super().__init__(f"[{status_code}] {message}")
class AuthenticationError(AgentPlatformError):
pass
class RateLimitError(AgentPlatformError):
def __init__(self, retry_after: int, **kwargs):
self.retry_after = retry_after
super().__init__(**kwargs)
class NotFoundError(AgentPlatformError):
pass
@dataclass
class ChatResponse:
message: str
conversation_id: str
agent_id: str
tool_calls: list
tokens_used: int
latency_ms: float
class AgentPlatform:
"""Synchronous client for the Agent Platform API."""
BASE_URL = "https://api.agentplatform.com/v1"
def __init__(self, api_key: str, base_url: str = None, timeout: float = 30.0):
self.api_key = api_key
self.base_url = base_url or self.BASE_URL
self._client = httpx.Client(
base_url=self.base_url,
headers={"X-API-Key": api_key, "Content-Type": "application/json"},
timeout=timeout,
)
def chat(
self,
agent_id: str,
message: str,
conversation_id: Optional[str] = None,
metadata: Optional[dict] = None,
) -> ChatResponse:
payload = {"message": message}
if conversation_id:
payload["conversation_id"] = conversation_id
if metadata:
payload["metadata"] = metadata
resp = self._request("POST", f"/agents/{agent_id}/chat", json=payload)
return ChatResponse(**resp)
def list_agents(self, page: int = 1, per_page: int = 20) -> dict:
return self._request("GET", "/agents", params={"page": page, "per_page": per_page})
def get_agent(self, agent_id: str) -> dict:
return self._request("GET", f"/agents/{agent_id}")
def _request(self, method: str, path: str, **kwargs) -> dict:
resp = self._client.request(method, path, **kwargs)
if resp.status_code == 401:
raise AuthenticationError(401, "Invalid API key")
if resp.status_code == 404:
raise NotFoundError(404, "Resource not found")
if resp.status_code == 429:
retry_after = int(resp.headers.get("Retry-After", 60))
raise RateLimitError(
retry_after=retry_after, status_code=429, message="Rate limit exceeded"
)
if resp.status_code >= 400:
body = resp.json()
raise AgentPlatformError(
resp.status_code, body.get("detail", "Unknown error"), body.get("code")
)
return resp.json()
def close(self):
self._client.close()
def __enter__(self):
return self
def __exit__(self, *args):
self.close()
Notice the context manager support. This ensures proper connection cleanup and lets developers write:
Hear it before you finish reading
Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.
with AgentPlatform(api_key="sk-...") as client:
response = client.chat("agent-123", "What are your business hours?")
print(response.message)
For production applications, provide an async client that supports streaming responses:
# agentplatform/async_client.py — Async SDK with streaming
import httpx
from typing import AsyncIterator
class AsyncAgentPlatform:
"""Asynchronous client with streaming support."""
BASE_URL = "https://api.agentplatform.com/v1"
def __init__(self, api_key: str, base_url: str = None, timeout: float = 30.0):
self.api_key = api_key
self.base_url = base_url or self.BASE_URL
self._client = httpx.AsyncClient(
base_url=self.base_url,
headers={"X-API-Key": api_key, "Content-Type": "application/json"},
timeout=timeout,
)
async def chat(self, agent_id: str, message: str, **kwargs) -> ChatResponse:
payload = {"message": message, **kwargs}
resp = await self._request("POST", f"/agents/{agent_id}/chat", json=payload)
return ChatResponse(**resp)
async def chat_stream(
self, agent_id: str, message: str, **kwargs
) -> AsyncIterator[str]:
payload = {"message": message, "stream": True, **kwargs}
async with self._client.stream(
"POST",
f"/agents/{agent_id}/chat",
json=payload,
) as resp:
if resp.status_code >= 400:
body = await resp.aread()
raise AgentPlatformError(resp.status_code, body.decode())
async for line in resp.aiter_lines():
if line.startswith("data: "):
chunk = line[6:]
if chunk == "[DONE]":
break
yield chunk
async def _request(self, method: str, path: str, **kwargs) -> dict:
resp = await self._client.request(method, path, **kwargs)
if resp.status_code == 401:
raise AuthenticationError(401, "Invalid API key")
if resp.status_code == 429:
retry_after = int(resp.headers.get("Retry-After", 60))
raise RateLimitError(
retry_after=retry_after, status_code=429, message="Rate limit exceeded"
)
if resp.status_code >= 400:
body = resp.json()
raise AgentPlatformError(resp.status_code, body.get("detail", "Unknown error"))
return resp.json()
async def close(self):
await self._client.aclose()
async def __aenter__(self):
return self
async def __aexit__(self, *args):
await self.close()
Usage with streaming:
import asyncio
async def main():
async with AsyncAgentPlatform(api_key="sk-...") as client:
async for chunk in client.chat_stream("agent-123", "Explain your pricing"):
print(chunk, end="", flush=True)
print() # Final newline
asyncio.run(main())
Production SDKs must handle transient failures gracefully:
# agentplatform/retry.py — Retry logic with exponential backoff
import time
import random
from functools import wraps
def with_retry(max_retries=3, base_delay=1.0, max_delay=30.0):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
last_exception = None
for attempt in range(max_retries + 1):
try:
return func(*args, **kwargs)
except RateLimitError as e:
last_exception = e
delay = e.retry_after
except AgentPlatformError as e:
if e.status_code < 500:
raise # Client errors are not retryable
last_exception = e
delay = min(base_delay * (2 ** attempt) + random.uniform(0, 1), max_delay)
if attempt < max_retries:
time.sleep(delay)
raise last_exception
return wrapper
return decorator
Structure the SDK as a proper Python package with type hints and clear documentation:
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.
# setup.py (or pyproject.toml) — SDK packaging
from setuptools import setup, find_packages
setup(
name="agentplatform",
version="1.2.0",
packages=find_packages(),
install_requires=["httpx>=0.25.0"],
python_requires=">=3.9",
description="Official Python SDK for the Agent Platform API",
author="Agent Platform Team",
url="https://github.com/agentplatform/python-sdk",
classifiers=[
"Programming Language :: Python :: 3",
"Typing :: Typed",
],
)
Use API versioning in the URL path (/v1/, /v2/) and maintain SDK versions that map to API versions. When you release a new API version, release a new major SDK version. Continue supporting the old SDK version with security patches for at least 12 months. In the SDK, default to the latest API version but allow users to pin a specific version.
Use a hybrid approach. Auto-generate the low-level HTTP client and request/response types from your OpenAPI spec, then hand-write the high-level convenience methods, error handling, and streaming logic on top. Pure auto-generated SDKs feel robotic and miss language-specific idioms. Pure hand-written SDKs drift out of sync with the API.
Use recorded HTTP interactions with a library like vcrpy for Python or nock for Node.js. Record real API responses once, then replay them in CI. This catches serialization bugs and response format changes without requiring live API access. Also maintain a small integration test suite that runs against a staging environment on a weekly schedule.
#SDKDesign #DeveloperExperience #APIClients #Python #JavaScript #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.
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.
Smolagents lets agents write Python instead of JSON. Why code-as-action reduces tool errors and where the security trade-offs are for production deployments.
Modal turns a Python function into autoscaling serverless compute with optional GPU. Deploy a LiveKit Agent with one command and get pay-per-second billing.
A developer experience audit of CallSphere vs Vapi from first call to production rollout — onboarding, APIs, debugging, and operations scored side by side.
Pydantic AI's April release tightens the typed-agent loop and adds structured tool definitions. Why type-safe agents reduce production bugs and speed iteration.
Index a knowledge base with text-embedding-3-large into ChromaDB, expose a retrieve tool to your voice agent, and ground every answer in real documents — full Python tutorial.
© 2026 CallSphere Inc. All rights reserved.
Made within San Francisco