pub trait Provider: Send + Sync {
// Required methods
fn name(&self) -> &str;
fn transform_request(
&self,
req: &CompletionRequest,
) -> Result<ProviderRequest>;
fn execute<'life0, 'async_trait>(
&'life0 self,
req: ProviderRequest,
) -> Pin<Box<dyn Future<Output = Result<ProviderResponse>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait;
fn transform_response(
&self,
resp: ProviderResponse,
) -> Result<CompletionResponse>;
// Provided methods
fn retry_config(&self) -> RetryConfig { ... }
fn capabilities(&self) -> Capabilities { ... }
fn timeout(&self) -> Duration { ... }
fn execute_stream<'life0, 'async_trait>(
&'life0 self,
_req: ProviderRequest,
) -> Pin<Box<dyn Future<Output = Result<Box<dyn Stream<Item = Result<CompletionChunk>> + Send + Unpin>>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait { ... }
}Expand description
Trait for LLM providers.
Providers implement this trait to support different LLM APIs while presenting a unified interface to the rest of SimpleAgents.
§Architecture
The provider trait follows a three-phase architecture:
- Transform Request: Convert unified request to provider format
- Execute: Make the actual API call
- Transform Response: Convert provider response to unified format
This design allows for:
- Maximum flexibility in provider-specific transformations
- Clean separation between protocol logic and business logic
- Easy testing and mocking
§Example Implementation
use simple_agent_type::provider::{Provider, ProviderRequest, ProviderResponse};
use simple_agent_type::request::CompletionRequest;
use simple_agent_type::response::{CompletionResponse, CompletionChoice, FinishReason, Usage};
use simple_agent_type::message::Message;
use simple_agent_type::error::Result;
use async_trait::async_trait;
struct MyProvider;
#[async_trait]
impl Provider for MyProvider {
fn name(&self) -> &str {
"my-provider"
}
fn transform_request(&self, _req: &CompletionRequest) -> Result<ProviderRequest> {
Ok(ProviderRequest::new("http://example.com"))
}
async fn execute(&self, _req: ProviderRequest) -> Result<ProviderResponse> {
Ok(ProviderResponse::new(200, serde_json::json!({"ok": true})))
}
fn transform_response(&self, _resp: ProviderResponse) -> Result<CompletionResponse> {
Ok(CompletionResponse {
id: "resp_1".to_string(),
model: "dummy".to_string(),
choices: vec![CompletionChoice {
index: 0,
message: Message::assistant("ok"),
finish_reason: FinishReason::Stop,
logprobs: None,
}],
usage: Usage::new(1, 1),
created: None,
provider: Some(self.name().to_string()),
healing_metadata: None,
})
}
}
let provider = MyProvider;
let request = CompletionRequest::builder()
.model("gpt-4")
.message(Message::user("Hello!"))
.build()
.unwrap();
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async {
let provider_request = provider.transform_request(&request).unwrap();
let provider_response = provider.execute(provider_request).await.unwrap();
let response = provider.transform_response(provider_response).unwrap();
assert_eq!(response.content(), Some("ok"));
});Required Methods§
Sourcefn transform_request(&self, req: &CompletionRequest) -> Result<ProviderRequest>
fn transform_request(&self, req: &CompletionRequest) -> Result<ProviderRequest>
Transform unified request to provider-specific format.
This method converts the standardized CompletionRequest into
the provider’s native API format.
Sourcefn execute<'life0, 'async_trait>(
&'life0 self,
req: ProviderRequest,
) -> Pin<Box<dyn Future<Output = Result<ProviderResponse>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn execute<'life0, 'async_trait>(
&'life0 self,
req: ProviderRequest,
) -> Pin<Box<dyn Future<Output = Result<ProviderResponse>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Execute request against provider API.
This method makes the actual HTTP request to the provider. Implementations should handle:
- Authentication (API keys, tokens)
- Rate limiting
- Network errors
- Provider-specific error codes
Sourcefn transform_response(
&self,
resp: ProviderResponse,
) -> Result<CompletionResponse>
fn transform_response( &self, resp: ProviderResponse, ) -> Result<CompletionResponse>
Transform provider response to unified format.
This method converts the provider’s native response format into
the standardized CompletionResponse.
Provided Methods§
Sourcefn retry_config(&self) -> RetryConfig
fn retry_config(&self) -> RetryConfig
Get retry configuration.
Override to customize retry behavior for this provider.
Sourcefn capabilities(&self) -> Capabilities
fn capabilities(&self) -> Capabilities
Get provider capabilities.
Override to specify what features this provider supports.
Sourcefn execute_stream<'life0, 'async_trait>(
&'life0 self,
_req: ProviderRequest,
) -> Pin<Box<dyn Future<Output = Result<Box<dyn Stream<Item = Result<CompletionChunk>> + Send + Unpin>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn execute_stream<'life0, 'async_trait>(
&'life0 self,
_req: ProviderRequest,
) -> Pin<Box<dyn Future<Output = Result<Box<dyn Stream<Item = Result<CompletionChunk>> + Send + Unpin>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Execute streaming request against provider API.
This method returns a stream of completion chunks for streaming responses. Not all providers support streaming - default implementation returns an error.
§Arguments
req: The provider-specific request
§Returns
A boxed stream of Result
§Example
use simple_agent_type::provider::{Provider, ProviderRequest, ProviderResponse};
use simple_agent_type::request::CompletionRequest;
use simple_agent_type::response::{CompletionResponse, CompletionChunk, CompletionChoice, FinishReason, Usage};
use simple_agent_type::message::Message;
use simple_agent_type::error::Result;
use async_trait::async_trait;
use futures_core::Stream;
use std::pin::Pin;
use std::task::{Context, Poll};
struct EmptyStream;
impl Stream for EmptyStream {
type Item = Result<CompletionChunk>;
fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
Poll::Ready(None)
}
}
struct StreamingProvider;
#[async_trait]
impl Provider for StreamingProvider {
fn name(&self) -> &str {
"streaming-provider"
}
fn transform_request(&self, _req: &CompletionRequest) -> Result<ProviderRequest> {
Ok(ProviderRequest::new("http://example.com"))
}
async fn execute(&self, _req: ProviderRequest) -> Result<ProviderResponse> {
Ok(ProviderResponse::new(200, serde_json::json!({"ok": true})))
}
fn transform_response(&self, _resp: ProviderResponse) -> Result<CompletionResponse> {
Ok(CompletionResponse {
id: "resp_1".to_string(),
model: "dummy".to_string(),
choices: vec![CompletionChoice {
index: 0,
message: Message::assistant("ok"),
finish_reason: FinishReason::Stop,
logprobs: None,
}],
usage: Usage::new(1, 1),
created: None,
provider: None,
healing_metadata: None,
})
}
async fn execute_stream(
&self,
_req: ProviderRequest,
) -> Result<Box<dyn Stream<Item = Result<CompletionChunk>> + Send + Unpin>> {
Ok(Box::new(EmptyStream))
}
}
let provider = StreamingProvider;
let request = CompletionRequest::builder()
.model("gpt-4")
.message(Message::user("Hello!"))
.build()
.unwrap();
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async {
let provider_request = provider.transform_request(&request).unwrap();
let _stream = provider.execute_stream(provider_request).await.unwrap();
// Use StreamExt::next to consume the stream in real usage.
});