Skip to main content

Provider

Trait Provider 

Source
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:

  1. Transform Request: Convert unified request to provider format
  2. Execute: Make the actual API call
  3. 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§

Source

fn name(&self) -> &str

Provider name (e.g., “openai”, “anthropic”).

Source

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.

Source

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
Source

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§

Source

fn retry_config(&self) -> RetryConfig

Get retry configuration.

Override to customize retry behavior for this provider.

Source

fn capabilities(&self) -> Capabilities

Get provider capabilities.

Override to specify what features this provider supports.

Source

fn timeout(&self) -> Duration

Get default timeout.

Source

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.
});

Implementors§