artificial_core/provider/
chat_complete.rs

1use std::{future::Future, pin::Pin};
2
3use crate::{
4    error::Result,
5    generic::{GenericChatCompletionResponse, GenericFunctionSpec, GenericMessage},
6    model::Model,
7};
8use futures_core::stream::Stream;
9
10/// A **backend** turns a chat prompt into a network call to a concrete provider
11/// (OpenAI, Ollama, Anthropic, …) and parses the structured chat response.
12///
13/// The trait is intentionally minimal:
14///
15/// * **One associated type** – the in-memory `Message` representation this
16///   provider accepts.
17/// * **One async-ish method** – `chat_complete`, which performs a *single*
18///   non-streaming round-trip and returns a value whose type is dictated by
19///   the `PromptTemplate`.
20pub trait ChatCompletionProvider: Send + Sync {
21    /// Chat message type consumed by this backend.
22    type Message: Send + Sync + 'static;
23
24    /// Execute the chat prompt and deserialize the provider’s reply into
25    /// `P::Output`.
26    fn chat_complete<'s, M>(
27        &'s self,
28        params: ChatCompleteParameters<M>,
29    ) -> Pin<
30        Box<dyn Future<Output = Result<GenericChatCompletionResponse<GenericMessage>>> + Send + 's>,
31    >
32    where
33        M: Into<Self::Message> + Clone + Send + Sync + 's;
34}
35
36/// A provider that can deliver the model’s answer **incrementally**.
37///
38/// The stream yields UTF-8 text *deltas* (similar to OpenAI’s SSE format).
39/// Tool-call and richer payload support can be layered on later by
40/// introducing a dedicated enum – starting with plain text keeps the API
41/// minimal and backend-agnostic.
42pub trait StreamingChatProvider: ChatCompletionProvider {
43    /// The item type returned on the stream.  For now it is plain UTF-8 text
44    /// chunks, but back-ends are free to wrap it in richer enums if needed.
45    type Delta<'s>: Stream<Item = Result<String>> + Send + 's
46    where
47        Self: 's;
48
49    /// Start a streaming chat completion.
50    fn chat_complete_stream<'s, M>(&'s self, params: ChatCompleteParameters<M>) -> Self::Delta<'s>
51    where
52        M: Into<Self::Message> + Clone + Send + Sync + 's;
53}
54
55#[derive(Debug, Clone)]
56pub struct ChatCompleteParameters<M: Clone> {
57    pub messages: Vec<M>,
58    pub model: Model,
59    pub tools: Option<Vec<GenericFunctionSpec>>,
60    pub temperature: Option<f64>,
61    pub response_format: Option<serde_json::Value>,
62}
63
64impl<M: Clone> ChatCompleteParameters<M> {
65    pub fn new(messages: Vec<M>, model: Model) -> Self {
66        Self {
67            messages,
68            model,
69            tools: None,
70            temperature: None,
71            response_format: None,
72        }
73    }
74
75    pub fn messages(&self) -> &Vec<M> {
76        &self.messages
77    }
78
79    pub fn model(&self) -> Model {
80        self.model.clone()
81    }
82
83    pub fn tools(&self) -> Option<&Vec<GenericFunctionSpec>> {
84        self.tools.as_ref()
85    }
86
87    pub fn with_temperature(mut self, temperature: f64) -> Self {
88        self.temperature = Some(temperature);
89        self
90    }
91
92    pub fn with_response_format(mut self, response_format: serde_json::Value) -> Self {
93        self.response_format = Some(response_format);
94        self
95    }
96
97    pub fn with_tools(mut self, tools: Vec<GenericFunctionSpec>) -> Self {
98        self.tools = Some(tools);
99        self
100    }
101}