artificial-openai 0.4.0

OpenAI backend adapter for the Artificial prompt-engineering SDK
Documentation
use std::sync::Arc;

use artificial_core::{
    generic::{GenericChatCompletionResponse, GenericUsageReport, ResponseContent},
    provider::{ChatCompleteParameters, ChatCompletionProvider},
};

use crate::{
    OpenAiAdapter,
    api_v1::{ChatCompletionMessage, FinishReason},
    error::OpenAiError,
};

impl ChatCompletionProvider for OpenAiAdapter {
    type Message = ChatCompletionMessage;

    fn chat_complete<'p, M>(
        &self,
        params: ChatCompleteParameters<M>,
    ) -> std::pin::Pin<
        Box<
            dyn Future<
                    Output = artificial_core::error::Result<
                        artificial_core::generic::GenericChatCompletionResponse<
                            artificial_core::generic::GenericMessage,
                        >,
                    >,
                > + Send
                + 'p,
        >,
    >
    where
        M: Into<Self::Message> + Send + Sync + 'p,
    {
        let client = Arc::clone(&self.client);

        Box::pin(async move {
            let request = params.try_into()?;

            let mut response = client.chat_completion(request).await?;

            let usage_report = GenericUsageReport {
                prompt_tokens: response.usage.prompt_tokens as i64,
                completion_tokens: response.usage.completion_tokens as i64,
                total_tokens: response.usage.total_tokens as i64,
            };

            let Some(first_choice) = response.choices.pop() else {
                return Err(OpenAiError::Format("response has no choices".into()).into());
            };

            match &first_choice.finish_reason {
                Some(FinishReason::ToolCalls) => {
                    let response = GenericChatCompletionResponse {
                        content: ResponseContent::ToolCalls(first_choice.message.into()),
                        usage: Some(usage_report),
                    };
                    Ok(response)
                }
                None | Some(FinishReason::Stop) => {
                    let response = GenericChatCompletionResponse {
                        content: ResponseContent::Finished(first_choice.message.into()),
                        usage: Some(usage_report),
                    };
                    Ok(response)
                }
                Some(other) => Err(OpenAiError::Format(format!(
                    "unhandled finish reason on API: {other:?}"
                ))
                .into()),
            }
        })
    }
}