artificial_core/
client.rs

1//! Generic, lightweight client that executes a [`PromptTemplate`] against a
2//! single concrete [`Backend`].
3//!
4//! The client is **generic over the backend type `B`**, so the compiler
5//! guarantees that:
6//! * The prompt’s `Message` type matches what the backend expects.
7//! * No dynamic dispatch or object-safety hurdles appear in user code.
8//!
9//! ```rust
10//! use artificial_core::{ArtificialClient, generic::{GenericMessage, GenericRole},
11//!                      template::*, model::*};
12//!
13//! struct Hello;
14//!
15//! impl PromptTemplate for Hello {
16//!     type Output         = serde_json::Value;
17//!     const MODEL: Model  = Model::OpenAi(OpenAiModel::Gpt4o);
18//! }
19//!
20//! impl IntoPrompt for Hello {
21//!     type Message = GenericMessage;
22//!     fn into_prompt(self) -> Vec<Self::Message> {
23//!         vec![GenericMessage::new("Say hello!".into(), GenericRole::User)]
24//!     }
25//! }
26//!
27//! # fn main() {}
28//! ```
29//!
30//! Any backend crate (e.g. `artificial-openai`, `artificial-ollama`) just
31//! implements Provider traits and the same client works out of the box.
32use std::{future::Future, pin::Pin, sync::Arc};
33
34use crate::{
35    error::Result,
36    generic::{GenericChatCompletionResponse, StreamingEventsProvider},
37    provider::{
38        ChatCompleteParameters, ChatCompletionProvider, PromptExecutionProvider,
39        StreamingChatProvider,
40    },
41    template::{IntoPrompt, PromptTemplate},
42};
43
44/// A client bound to a single provider.
45///
46/// Clone the client if you need to share it across tasks—`B` controls whether
47/// that’s cheap (e.g. wraps an `Arc`) or a deep copy.
48#[derive(Debug, Clone)]
49pub struct ArtificialClient<B> {
50    backend: Arc<B>,
51}
52
53impl<B> ArtificialClient<B>
54where
55    B: PromptExecutionProvider,
56{
57    /// Create a new client that delegates all calls to `backend`.
58    pub fn new(backend: B) -> Self {
59        Self {
60            backend: Arc::new(backend),
61        }
62    }
63
64    /// Access the underlying backend (e.g. to tweak provider-specific settings).
65    pub fn backend(&self) -> &B {
66        &self.backend
67    }
68}
69
70impl<B: PromptExecutionProvider> PromptExecutionProvider for ArtificialClient<B> {
71    type Message = B::Message;
72
73    fn prompt_execute<'a, 'p, P>(
74        &'a self,
75        prompt: P,
76    ) -> Pin<Box<dyn Future<Output = Result<GenericChatCompletionResponse<P::Output>>> + Send + 'p>>
77    where
78        'a: 'p,
79        P: PromptTemplate + Send + Sync + 'p,
80        <P as IntoPrompt>::Message: Into<Self::Message>,
81    {
82        let backend = Arc::clone(&self.backend);
83        Box::pin(async move { backend.prompt_execute(prompt).await })
84    }
85}
86
87impl<B: ChatCompletionProvider> ChatCompletionProvider for ArtificialClient<B> {
88    type Message = B::Message;
89
90    fn chat_complete<'s, M>(
91        &'s self,
92        params: ChatCompleteParameters<M>,
93    ) -> Pin<
94        Box<
95            dyn Future<
96                    Output = Result<GenericChatCompletionResponse<crate::generic::GenericMessage>>,
97                > + Send
98                + 's,
99        >,
100    >
101    where
102        M: Into<Self::Message> + Clone + Send + Sync + 's,
103    {
104        self.backend.chat_complete(params)
105    }
106}
107
108impl<B: StreamingChatProvider> StreamingChatProvider for ArtificialClient<B> {
109    type Message = B::Message;
110
111    type Delta<'s>
112        = B::Delta<'s>
113    where
114        Self: 's;
115
116    fn chat_complete_stream<'s, M>(&'s self, params: ChatCompleteParameters<M>) -> Self::Delta<'s>
117    where
118        M: Into<Self::Message> + Clone + Send + Sync + 's,
119    {
120        self.backend.chat_complete_stream(params)
121    }
122}
123
124impl<B: StreamingEventsProvider> StreamingEventsProvider for ArtificialClient<B> {
125    type EventStream<'s>
126        = B::EventStream<'s>
127    where
128        Self: 's;
129
130    fn chat_complete_events_stream<'s, M>(
131        &'s self,
132        params: crate::provider::ChatCompleteParameters<M>,
133    ) -> Self::EventStream<'s>
134    where
135        M: Into<Self::Message> + Clone + Send + Sync + 's,
136    {
137        self.backend.chat_complete_events_stream(params)
138    }
139}