nav.groups.providers

OpenAI Adapter

The OpenAI-compatible chat and embedding adapter: request building, response parsing, error mapping.

OpenAI Adapter

The OpenAI-compatible chat and embedding adapter.

OpenAiChatAdapter and OpenAiEmbeddingAdapter implement ChatProvider and EmbeddingProvider respectively, translating behest's provider-neutral types to OpenAI's REST API and back.

The full files are src/adapt/openai/chat.rs, src/adapt/openai/embed.rs, and src/adapt/openai/convert.rs.

Chat adapter

pub struct OpenAiChatAdapter {
    id: ProviderId,
    http: HttpClient,
    config: ProviderHttpConfig,
}

impl OpenAiChatAdapter {
    pub fn new(config: ProviderHttpConfig) -> Result<Self, ProviderError>;
}

#[async_trait]
impl ChatProvider for OpenAiChatAdapter {
    fn id(&self) -> ProviderId;
    fn capabilities(&self) -> ProviderCapabilities;
    async fn complete(&self, request: ChatRequest) -> ProviderResult<ChatResponse>;
    async fn stream(&self, request: ChatRequest) -> ProviderResult<ChatStream>;
}

Capabilities

ProviderCapabilities {
    chat: true,
    chat_stream: true,
    tool_calls: true,
    vision: true,
    structured_output: true,
    context_overflow_retry: true,
}

Request building

convert.rs maps ChatRequest → OpenAI's CreateChatCompletionRequest:

  • MessageChatCompletionMessageParam (system / user / assistant / tool).
  • ContentPart::Text → text content; ContentPart::ImageUrlimage_url content part.
  • ToolSpecChatCompletionTool with function type and JSON Schema parameters.
  • ToolChoice::Auto"auto"; ToolChoice::Required"required"; ToolChoice::None"none".

Response parsing

convert.rs maps OpenAI's CreateChatCompletionResponseChatResponse:

  • choices[0].messageMessage::Assistant.
  • choices[0].message.tool_callsVec<ToolCall>.
  • choices[0].finish_reasonFinishReason.
  • usageTokenUsage.

Streaming

The streaming adapter uses SseParser (from src/adapt/sse.rs) to parse OpenAI's SSE stream. Each data: line is deserialised as a ChatCompletionChunk; text deltas are concatenated, tool-call argument deltas are merged.

Error mapping

convert.rs maps HTTP status codes and OpenAI error types to ProviderError:

HTTP statusOpenAI error typeProviderError
401invalid_api_keyAuthentication
429rate_limit_exceededRateLimited
500server_errorOverloaded
400context_length_exceededBadRequest (with is_context_overflow() == true)
400otherBadRequest
network errorTransport

Embedding adapter

pub struct OpenAiEmbeddingAdapter {
    id: ProviderId,
    http: HttpClient,
    config: ProviderHttpConfig,
}

impl OpenAiEmbeddingAdapter {
    pub fn new(config: ProviderHttpConfig) -> Result<Self, ProviderError>;
}

#[async_trait]
impl EmbeddingProvider for OpenAiEmbeddingAdapter {
    fn id(&self) -> ProviderId;
    fn capabilities(&self) -> ProviderCapabilities;
    async fn embed(&self, request: EmbeddingRequest) -> ProviderResult<EmbeddingResponse>;
}

Shared infrastructure

Both adapters share:

  • HttpClient — a reqwest::Client configured with the adapter's timeouts and TLS settings.
  • SseParser — a streaming SSE parser that handles OpenAI's data: [DONE] sentinel.
  • ProviderHttpConfig — the configuration surface (base URL, API key, timeouts).

Feature gate

[dependencies]
behest = { version = "0.4", features = ["openai"] }

The adapter is compiled only when the openai feature is enabled. The default_factory_registry() registers it under "provider.openai.chat" and "provider.openai.embedding".

See also

Related components

Edit this page on GitHub →