use serde::{Deserialize, Serialize};
use crate::completion::CompletionRequest;
use crate::error::{EncodeError, ProviderError};
use crate::operation::Completion;
use crate::providers::openai::responses_api::streaming::{ResponsesDecoder, ResponsesEvent};
use crate::providers::openai::responses_api::wire::Responses;
use crate::providers::openai::responses_api::{
ResponsesToolDefinition, SystemInstructionsPlacement,
};
use crate::wire::document::Reassemble;
use crate::wire::{Decoder, Descriptor, Encoded, Flow, Mode, Out, Wire, WireEvent, WireFrame};
use super::OpenAIConfig;
use super::chat::{Chat, ChatDecoder, ChatEvent};
macro_rules! on_route {
($chosen:expr, $wire:ident => $ask:expr) => {
match $chosen {
Self::Chat($wire) => $ask,
Self::Responses($wire) => $ask,
}
};
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum Route {
Chat,
Responses,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum OpenAiWire {
Chat(Chat),
Responses(Responses),
}
impl OpenAiWire {
pub fn new(provider: OpenAIConfig, model: impl Into<String>) -> Self {
let model = model.into();
let route = provider.route.unwrap_or_else(|| {
provider
.dialect
.quirks
.hooks
.and_then(|hooks| hooks.model_route)
.map_or_else(|| provider.completion_route(), |route| route(&model))
});
match route {
Route::Chat => Self::Chat(Chat::new(provider, model)),
Route::Responses => Self::Responses(Responses::new(provider, model)),
}
}
pub(crate) fn encode_with_headers(
&self,
request: CompletionRequest,
mode: Mode,
headers: impl FnOnce(
&OpenAIConfig,
&CompletionRequest,
http::request::Builder,
) -> http::request::Builder,
) -> Result<Encoded, EncodeError> {
on_route!(self, wire => wire.encode_with_headers(request, mode, headers))
}
pub fn provider(&self) -> &OpenAIConfig {
on_route!(self, wire => &wire.provider)
}
pub fn with_strict_tools(self) -> Self {
self.on_chat(Chat::with_strict_tools)
.on_responses(Responses::with_strict_tools)
}
pub fn with_tool_result_array_content(self) -> Self {
self.on_chat(Chat::with_tool_result_array_content)
}
pub fn with_tool(self, tool: impl Into<ResponsesToolDefinition>) -> Self {
self.on_responses(|wire| wire.with_tool(tool))
}
pub fn with_tools<I, Tool>(self, tools: I) -> Self
where
I: IntoIterator<Item = Tool>,
Tool: Into<ResponsesToolDefinition>,
{
self.on_responses(|wire| wire.with_tools(tools))
}
pub fn with_system_instructions_placement(
self,
placement: SystemInstructionsPlacement,
) -> Self {
self.on_responses(|wire| wire.with_system_instructions_placement(placement))
}
pub fn with_system_instructions_as_messages(self) -> Self {
self.on_responses(Responses::with_system_instructions_as_messages)
}
fn on_chat(self, option: impl FnOnce(Chat) -> Chat) -> Self {
match self {
Self::Chat(wire) => Self::Chat(option(wire)),
responses => responses,
}
}
fn on_responses(self, option: impl FnOnce(Responses) -> Responses) -> Self {
match self {
Self::Responses(wire) => Self::Responses(option(wire)),
chat => chat,
}
}
}
impl From<Chat> for OpenAiWire {
fn from(wire: Chat) -> Self {
Self::Chat(wire)
}
}
impl From<Responses> for OpenAiWire {
fn from(wire: Responses) -> Self {
Self::Responses(wire)
}
}
impl Wire for OpenAiWire {
type Op = Completion;
type Payload = crate::wire::Encoded;
type Frame = crate::wire::WireFrame;
type Decoder<'id> = OpenAiDecoder;
type Reassembler = OpenAiReassembler;
fn describe(&self) -> Descriptor<'_> {
on_route!(self, wire => wire.describe())
}
fn encode(&self, request: CompletionRequest, mode: Mode) -> Result<Encoded, EncodeError> {
on_route!(self, wire => wire.encode(request, mode))
}
fn decoder<'id>(&self) -> Self::Decoder<'id> {
match self {
Self::Chat(wire) => OpenAiDecoder::Chat(wire.decoder()),
Self::Responses(wire) => OpenAiDecoder::Responses(wire.decoder()),
}
}
fn reassembler(&self) -> Self::Reassembler {
match self {
Self::Chat(wire) => OpenAiReassembler::Chat(wire.reassembler()),
Self::Responses(wire) => OpenAiReassembler::Responses(wire.reassembler()),
}
}
}
pub enum OpenAiReassembler {
Chat(<Chat as Wire>::Reassembler),
Responses(<Responses as Wire>::Reassembler),
}
impl Default for OpenAiReassembler {
fn default() -> Self {
Self::Chat(Default::default())
}
}
impl crate::wire::document::Serves<crate::operation::Completion> for OpenAiReassembler {}
impl Reassemble<WireFrame> for OpenAiReassembler {
fn absorb(&mut self, frame: &WireFrame) {
on_route!(self, document => document.absorb(frame));
}
fn finish(self) -> serde_json::Value {
on_route!(self, document => document.finish())
}
}
pub enum OpenAiEvent {
Chat(ChatEvent),
Responses(ResponsesEvent),
}
pub enum OpenAiDecoder {
Chat(ChatDecoder),
Responses(ResponsesDecoder),
}
impl<'id> Decoder<'id, Completion> for OpenAiDecoder {
type Event = OpenAiEvent;
fn classify(&self, frame: WireFrame) -> WireEvent<OpenAiEvent> {
match self {
Self::Chat(decoder) => decoder.classify(frame).map(OpenAiEvent::Chat),
Self::Responses(decoder) => decoder.classify(frame).map(OpenAiEvent::Responses),
}
}
fn decode(
&mut self,
event: OpenAiEvent,
out: Out<'id, Completion>,
) -> Result<Flow, ProviderError> {
match (self, event) {
(Self::Chat(decoder), OpenAiEvent::Chat(event)) => decoder.decode(event, out),
(Self::Responses(decoder), OpenAiEvent::Responses(event)) => decoder.decode(event, out),
(Self::Chat(_), OpenAiEvent::Responses(_))
| (Self::Responses(_), OpenAiEvent::Chat(_)) => Ok(Flow::More),
}
}
fn eof(&mut self, out: Out<'id, Completion>) -> Result<Flow, ProviderError> {
on_route!(self, decoder => decoder.eof(out))
}
}
#[cfg(test)]
mod tests;