The days of being locked into a single large language model provider are over. Modern agentic systems need the flexibility to choose the right model for each task—cheaper models for simple queries, high-reasoning models for complex analysis, or even local models to cut costs or preserve privacy. The Open Agent SDK tackles this challenge head-on with a unified architecture that abstracts provider differences while retaining fine-grained control over runtime behavior.
At the core of this approach lies the LLMClient protocol, a standardized interface that defines how agents interact with language models regardless of their origin. This design allows developers to switch providers with minimal configuration changes while dynamically adjusting parameters like reasoning depth and budget constraints during active sessions. Let’s examine how this protocol works and how different client implementations handle the translation between provider-specific APIs.
The LLMClient Protocol: A Universal Interface for Diverse Models
The LLMClient protocol serves as the contract between agents and language model providers, ensuring consistent interaction patterns across different backends. It defines two essential methods:
public protocol LLMClient: Sendable {
nonisolated func sendMessage(
model: String,
messages: [[String: Any]],
maxTokens: Int,
system: String?,
tools: [[String: Any]]?,
toolChoice: [String: Any]?,
thinking: [String: Any]?,
temperature: Double?
) async throws -> [String: Any]
nonisolated func streamMessage(
model: String,
messages: [[String: Any]],
maxTokens: Int,
system: String?,
tools: [[String: Any]]?,
toolChoice: [String: Any]?,
thinking: [String: Any]?,
temperature: Double?
) async throws -> AsyncThrowingStream<SSEEvent, Error>
}The protocol’s parameters cover the full spectrum of LLM API capabilities: model selection, message history, token limits, system prompts, tool definitions, and generation parameters. A critical design choice ensures all responses—regardless of the originating provider—are normalized into Anthropic’s response format. This standardization means the agent’s processing logic remains provider-agnostic, handling responses uniformly whether they come from Anthropic’s native API or an OpenAI-compatible service.
For streaming operations, the SDK uses AsyncThrowingStream with a structured event system that mirrors Anthropic’s Server-Sent Events (SSE) format:
public enum SSEEvent: @unchecked Sendable {
case messageStart(message: [String: Any])
case contentBlockStart(index: Int, contentBlock: [String: Any])
case contentBlockDelta(index: Int, delta: [String: Any])
case contentBlockStop(index: Int)
case messageDelta(delta: [String: Any], usage: [String: Any])
case messageStop
case ping
case error(data: [String: Any])
}This event structure captures all possible streaming response scenarios, from initial message framing to incremental content delivery and tool interactions, providing a consistent interface for real-time processing.
AnthropicClient: Native Integration with Claude’s Ecosystem
The AnthropicClient represents the native implementation of the LLMClient protocol for Anthropic’s Claude models. Built with Swift’s concurrency-safe actor pattern, it handles requests to Anthropic’s Messages API endpoint (/v1/messages) with proper authentication headers:
public actor AnthropicClient: LLMClient {
private let apiKey: String
private let baseURL: URL
private let urlSession: URLSession
public init(apiKey: String, baseURL: String? = nil, urlSession: URLSession? = nil) {
self.apiKey = apiKey
self.baseURL = URL(string: baseURL ?? ")!
self.urlSession = urlSession ?? URLSession.shared
}
private nonisolated func buildRequest(body: [String: Any]) throws -> URLRequest {
var request = URLRequest(url: URL(string: baseURL.absoluteString + "/v1/messages")!)
request.httpMethod = "POST"
request.timeoutInterval = 300
request.setValue(apiKey, forHTTPHeaderField: "x-api-key")
request.setValue("2023-06-01", forHTTPHeaderField: "anthropic-version")
request.setValue("application/json", forHTTPHeaderField: "content-type")
request.httpBody = try JSONSerialization.data(withJSONObject: body, options: [])
return request
}
}Requests include essential headers like x-api-key and the specific anthropic-version timestamp. A security feature masks API keys in error logs to prevent accidental exposure:
let safeMessage = errorMessage.replacingOccurrences(of: apiKey, with: "***")The client natively supports Anthropic’s Extended Thinking feature by passing through the thinking parameter configuration directly to the API request when provided.
OpenAIClient: Bridging the Gap to Compatible Providers
The OpenAIClient acts as the transformer between provider-agnostic Anthropic format and OpenAI-compatible APIs like GLM, Ollama, or OpenRouter. It accepts Anthropic-formatted parameters, converts them to OpenAI Chat Completion API specifications, processes the request, and converts responses back to Anthropic format—all transparently to the agent.
public actor OpenAIClient: LLMClient {
private let apiKey: String
private let baseURL: URL
public init(apiKey: String, baseURL: String? = nil, urlSession: URLSession? = nil) {
self.apiKey = apiKey
self.baseURL = URL(string: baseURL ?? ")!
self.urlSession = urlSession ?? URLSession.shared
}
}Requests target the /chat/completions endpoint with standard Bearer token authentication, making it compatible with any provider supporting the OpenAI-compatible chat interface. The client handles several critical format conversions:
- System message placement: Anthropic treats system prompts as a top-level parameter, while OpenAI requires it as the first
role: "system"message in the conversation history - Tool result representation: Anthropic consolidates multiple tool results in a single user message’s content array, whereas OpenAI formats each tool interaction separately
- Response normalization: All OpenAI responses are converted to the Anthropic message structure before being returned to the agent
This conversion layer ensures agents can leverage the entire ecosystem of OpenAI-compatible models without modifying their core logic, while maintaining consistent behavior across different providers.
Practical Implications for Agent Development
The Open Agent SDK’s multi-provider architecture delivers several key advantages for developers building intelligent agent systems:
- Cost optimization: Dynamically switch between premium reasoning models and budget-friendly alternatives based on task complexity or budget constraints
- Flexibility: Support diverse deployment scenarios from cloud APIs to local model instances without code changes
- Consistency: Maintain uniform processing logic regardless of the underlying provider or model
- Extensibility: Easily add new providers by implementing the
LLMClientprotocol without affecting existing agent code
As the LLM ecosystem continues evolving, architectures like this that prioritize abstraction and flexibility will become increasingly valuable. The Open Agent SDK demonstrates how strategic protocol design and careful normalization layers can future-proof agent systems against provider lock-in while enabling sophisticated runtime control.
AI summary
Geliştiriciler için çoklu LLM sağlayıcılarını tek bir SDK ile yönetmek artık mümkün. Open Agent SDK’nın sunduğu esneklik ve standartlaştırma hakkında detaylı bilgiler.