Building Agent SDKs: JavaScript, Python, and REST Clients for Your Agent Platform
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.
SDKs Define Developer Experience
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.
Python SDK Design
The Python SDK should support both synchronous and asynchronous usage, handle streaming responses, and provide clear error types:
# 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:
with AgentPlatform(api_key="sk-...") as client:
response = client.chat("agent-123", "What are your business hours?")
print(response.message)
Async Client with Streaming
For production applications, provide an async client that supports streaming responses:
See AI Voice Agents Handle Real Calls
Book a free demo or calculate how much you can save with AI voice automation.
# 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())
Automatic Retry with Backoff
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
SDK Packaging and Distribution
Structure the SDK as a proper Python package with type hints and clear documentation:
# 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",
],
)
FAQ
How do I handle breaking API changes without breaking existing SDK users?
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.
Should I auto-generate SDKs from an OpenAPI spec or hand-write them?
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.
How do I test SDKs without making real API calls in CI?
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
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.