just_llm_client/client/mod.rs
1//! Unified chat client and backend factory.
2//!
3//! Two building blocks on top of the concrete backend adapters:
4//!
5//! - [`BackendFactory`] dispatches a backend family string to that backend's
6//! [`LlmBackend::new`] constructor — a composable `family ->
7//! constructor` primitive with no caching or held configuration.
8//! - [`ChatClient`] pairs per-call request defaults (model, system prompt) with a shared
9//! [`LlmBackend`](crate::LlmBackend) and derefs to `dyn LlmBackend` so chat and capability
10//! methods are reachable directly.
11//!
12//! Backends can also be constructed directly via
13//! [`LlmBackend::new`] (the trait constructor, with the trait in scope),
14//! or from a pre-built provider client via each backend's `from_provider_client`.
15
16mod factory;
17
18use std::{ops::Deref, sync::Arc};
19
20use crate::{
21 provider::LlmBackend,
22 types::chat::{ChatCompletionRequest, ChatMessage},
23};
24
25pub use factory::BackendFactory;
26
27/// Per-call defaults for constructing a [`ChatClient`].
28#[derive(Clone, Debug, PartialEq, Eq)]
29pub struct ChatClientOptions {
30 model: String,
31 system_prompt: Option<String>,
32}
33
34impl ChatClientOptions {
35 /// Create options with a required default model.
36 pub fn new(model: impl Into<String>) -> Self {
37 Self {
38 model: model.into(),
39 system_prompt: None,
40 }
41 }
42
43 /// Add a default system prompt that will be injected into requests built by the client.
44 pub fn with_system_prompt(mut self, system_prompt: impl Into<String>) -> Self {
45 self.system_prompt = Some(system_prompt.into());
46 self
47 }
48
49 /// Returns the configured default model.
50 pub fn model(&self) -> &str {
51 &self.model
52 }
53
54 /// Returns the configured default system prompt.
55 pub fn system_prompt(&self) -> Option<&str> {
56 self.system_prompt.as_deref()
57 }
58}
59
60/// Thin facade that pairs per-call default request values with a shared backend.
61///
62/// Constructed via [`ChatClient::new`], typically with a backend produced by a
63/// [`BackendFactory`] or by [`LlmBackend::new`] directly. Implements
64/// [`Deref`] to [`dyn LlmBackend`](crate::LlmBackend) so the direct and prepared chat execution
65/// paths, plus capability negotiation methods, are accessible directly.
66#[derive(Clone)]
67pub struct ChatClient {
68 model: String,
69 system_prompt: Option<String>,
70 backend: Arc<dyn LlmBackend>,
71}
72
73impl ChatClient {
74 /// Create a client pairing per-call defaults with a shared backend.
75 ///
76 /// The backend identity (family) is available via [`family`](crate::Identifiable::family)
77 /// through the [`Deref`] to [`LlmBackend`].
78 pub fn new(backend: Arc<dyn LlmBackend>, options: ChatClientOptions) -> Self {
79 Self {
80 model: options.model,
81 system_prompt: options.system_prompt,
82 backend,
83 }
84 }
85
86 /// Returns the resolved model string (explicit or provider default).
87 pub fn model(&self) -> &str {
88 &self.model
89 }
90
91 /// Returns the configured default system prompt, if any.
92 pub fn system_prompt(&self) -> Option<&str> {
93 self.system_prompt.as_deref()
94 }
95
96 /// Returns the client with a new default model.
97 pub fn with_model(mut self, model: impl Into<String>) -> Self {
98 self.model = model.into();
99 self
100 }
101
102 /// Returns the client with a new default system prompt.
103 pub fn with_system_prompt(mut self, system_prompt: impl Into<String>) -> Self {
104 self.system_prompt = Some(system_prompt.into());
105 self
106 }
107
108 /// Returns the client without a default system prompt.
109 pub fn clear_system_prompt(mut self) -> Self {
110 self.system_prompt = None;
111 self
112 }
113
114 /// Create a request pre-filled with this client's default model and system prompt.
115 #[must_use = "the returned request must be passed to a backend method to have any effect"]
116 pub fn create_request(&self, messages: Vec<ChatMessage>) -> ChatCompletionRequest {
117 let mut request = ChatCompletionRequest::new(self.model.clone(), messages);
118 if let Some(system_prompt) = &self.system_prompt {
119 request = request.with_system_prompt(system_prompt.clone());
120 }
121 request
122 }
123}
124
125impl Deref for ChatClient {
126 type Target = dyn crate::LlmBackend;
127
128 fn deref(&self) -> &Self::Target {
129 &*self.backend
130 }
131}
132
133impl std::fmt::Debug for ChatClient {
134 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135 f.debug_struct("ChatClient")
136 .field("family", &self.backend.family())
137 .field("model", &self.model)
138 .field("has_system_prompt", &self.system_prompt.is_some())
139 .finish()
140 }
141}