Skip to main content

allama/
identity.rs

1use std::any::{Any, TypeId};
2use std::sync::Arc;
3
4/// Shared provider identity surface for chat, embedding, and decision
5/// providers: the provider's name, and typed lookup of the objects it exposes.
6///
7/// [`provider_name`](Self::provider_name) returns the provider identity
8/// string used for logs, tracing, errors, fixtures, and
9/// [`ChatResponse::provider`](crate::ChatResponse::provider) (for example
10/// `"anthropic"` or `"openai"`). It is an open string surface rather than a
11/// closed enum so wrappers and custom providers can preserve their existing
12/// naming, and it borrows from the provider so names can come from
13/// configuration. Callers should not treat it as a stable capability-dispatch
14/// mechanism.
15///
16/// [`downcast_any`](Self::downcast_any) reaches typed objects a provider
17/// exposes (most often the concrete provider itself, for its
18/// provider-specific methods) through wrappers and type erasure. Call it
19/// through [`ProviderIdentityExt::downcast_ref`].
20///
21/// # Provider name and namespace
22///
23/// A provider has two identifiers, and they need not be equal:
24///
25/// - the **provider name** ([`provider_name`](Self::provider_name)) says who
26///   served a call, for people and telemetry;
27/// - the **namespace** ([`ProviderOptions::NAMESPACE`](crate::ProviderOptions::NAMESPACE),
28///   [`ProviderDataEntry::NAMESPACE`](crate::ProviderDataEntry::NAMESPACE))
29///   keys the provider's typed options and the opaque state it stores in
30///   [`ProviderData`](crate::ProviderData). The namespace is the origin of
31///   that state: a provider replays only state under its own namespace.
32///
33/// Gemini is the example where they differ: its provider name is `gemini`
34/// and its namespace is `google-ai`. Several OpenAI-compatible endpoints
35/// share options under one namespace while each reports its own name. Every
36/// built-in provider crate exports both as `PROVIDER_NAME` and `NAMESPACE`.
37///
38/// [`reads_namespace`](Self::reads_namespace) answers which namespaces a
39/// provider reads, so code that holds only a provider (one built from a
40/// model address, behind wrappers) can keep the state it uses, with
41/// [`retain_provider_state_for`](crate::history::retain_provider_state_for).
42///
43/// # Implementing
44///
45/// - [`provider_name`](Self::provider_name) is required: a provider names
46///   itself, and a wrapper forwards the name of the provider it wraps
47///   (`self.inner.provider_name()`).
48/// - [`reads_namespace`](Self::reads_namespace) is required: a provider
49///   answers for its own namespace, and a wrapper forwards the question
50///   (`self.inner.reads_namespace(namespace)`).
51/// - A provider exposes itself, and optionally objects it owns, with
52///   [`expose`].
53/// - A wrapper forwards the query to every provider it wraps:
54///   `self.inner.downcast_any(id)`. A wrapper that does not forward hides
55///   the providers it wraps.
56///
57/// Wrappers are transparent: they forward every question about the provider
58/// (its name, the namespaces it reads, [`downcast_any`](Self::downcast_any),
59/// its capabilities, its limits, and request validation) and do not answer
60/// for themselves. The methods without a sensible wrapper default (the name,
61/// the namespaces and the capability answers) are required, so a wrapper
62/// cannot forget them silently; the rest have defaults that are right for
63/// providers and wrong for wrappers.
64/// `allama_conformance::assert_wrapper` (and its per-trait variants)
65/// checks a wrapper against all of them, and that its calls pass the
66/// provider's answers through unchanged.
67pub trait ProviderIdentity: Send + Sync {
68    /// Returns the provider name: who serves calls made through this
69    /// provider.
70    fn provider_name(&self) -> &str;
71
72    /// Whether this provider reads the [provider data](crate::ProviderData)
73    /// under `namespace`.
74    ///
75    /// A namespace names the only provider that may read an entry: the
76    /// origin of the state a provider replays, and the addressee of
77    /// settings a caller attaches to a request item. A provider answers
78    /// `true` for the namespace it mints and reads state under (Gemini for
79    /// `google-ai`, an OpenAI-compatible preset for `openai-compat:groq`,
80    /// both OpenAI endpoints for `openai`) and `false` for every other
81    /// namespace, including options namespaces that hold no provider data
82    /// (`openai-responses`, `allama-http`) and the `allama` namespace, which
83    /// allama itself reads.
84    ///
85    /// It is a question, not an identifier: a wrapper answers for the
86    /// providers that may serve its calls, which one namespace cannot
87    /// name. A wrapper forwards it, and a
88    /// [`FallbackProvider`](crate::wrappers::FallbackProvider) reads what
89    /// either side reads, since either may serve the next turn.
90    ///
91    /// Required, like [`provider_name`](Self::provider_name): no default is
92    /// right for both a provider and a wrapper, and a wrong answer silently
93    /// removes state a provider needs from a conversation
94    /// ([`retain_provider_state_for`](crate::history::retain_provider_state_for)).
95    fn reads_namespace(&self, namespace: &str) -> bool;
96
97    /// Returns the object of type `id` this provider exposes, if any.
98    ///
99    /// Concrete providers usually expose themselves (see [`expose`]);
100    /// wrappers forward the query to the providers they wrap. The default
101    /// exposes nothing.
102    fn downcast_any(&self, id: TypeId) -> Option<&(dyn Any + Send + Sync)> {
103        let _ = id;
104        None
105    }
106}
107
108/// Typed access to [`ProviderIdentity::downcast_any`].
109pub trait ProviderIdentityExt: ProviderIdentity {
110    /// Borrow the `T` this provider, or a provider it wraps, exposes.
111    ///
112    /// Like `anyhow::Error::downcast_ref`, this looks through layers: it
113    /// reaches a concrete provider (and its provider-specific methods)
114    /// through wrappers and [`DynChatProvider`](crate::DynChatProvider):
115    ///
116    /// ```
117    /// # #[cfg(feature = "mock")] {
118    /// use allama::mock::MockProvider;
119    /// use allama::wrappers::{RetryPolicy, RetryingProvider};
120    /// use allama::{DynChatProvider, ProviderIdentityExt};
121    ///
122    /// let retrying = RetryingProvider::new(MockProvider::empty(), RetryPolicy::default());
123    /// let provider = DynChatProvider::new(retrying);
124    /// assert!(provider.downcast_ref::<MockProvider>().is_some());
125    /// # }
126    /// ```
127    ///
128    /// - **Calls through the result skip the wrappers.** The reference points
129    ///   at the inner provider, so its methods run without the retries,
130    ///   timeouts, tracing, or recording around it.
131    /// - **The first match wins.** A [`FallbackProvider`] answers from its
132    ///   primary first; when both sides have the same type, reach each one
133    ///   with [`primary`] and [`fallback`].
134    /// - **Discovery is for code handed an already-wrapped provider.** An
135    ///   application that builds the stack can keep a clone (or an [`Arc`])
136    ///   of the concrete provider before wrapping it.
137    ///
138    /// [`FallbackProvider`]: crate::wrappers::FallbackProvider
139    /// [`primary`]: crate::wrappers::FallbackProvider::primary
140    /// [`fallback`]: crate::wrappers::FallbackProvider::fallback
141    fn downcast_ref<T: Any + Send + Sync>(&self) -> Option<&T> {
142        self.downcast_any(TypeId::of::<T>())?.downcast_ref()
143    }
144}
145
146impl<P: ProviderIdentity + ?Sized> ProviderIdentityExt for P {}
147
148/// Answer a [`downcast_any`](ProviderIdentity::downcast_any) query with
149/// `value` when `id` is its type.
150///
151/// A concrete provider exposes itself, and optionally objects it owns:
152///
153/// ```
154/// use std::any::{Any, TypeId};
155/// use allama::{ProviderIdentity, ProviderIdentityExt, expose};
156///
157/// struct MyProvider {
158///     config: MyConfig,
159/// }
160///
161/// struct MyConfig {
162///     region: &'static str,
163/// }
164///
165/// impl ProviderIdentity for MyProvider {
166///     fn provider_name(&self) -> &str {
167///         "mine"
168///     }
169///
170///     fn reads_namespace(&self, namespace: &str) -> bool {
171///         namespace == "mine"
172///     }
173///
174///     fn downcast_any(&self, id: TypeId) -> Option<&(dyn Any + Send + Sync)> {
175///         expose(self, id).or_else(|| expose(&self.config, id))
176///     }
177/// }
178///
179/// let provider = MyProvider { config: MyConfig { region: "eu" } };
180/// assert_eq!(provider.downcast_ref::<MyConfig>().unwrap().region, "eu");
181/// ```
182///
183/// A wrapper forwards the query instead: `self.inner.downcast_any(id)`. To
184/// make a wrapper discoverable as well, answer
185/// `expose(self, id).or_else(|| self.inner.downcast_any(id))`, which needs
186/// `P: 'static` on the `impl`. This is discouraged: a caller that looks a
187/// wrapper up depends on how the stack was built, which a wrapper should
188/// not make observable.
189pub fn expose<T: Any + Send + Sync>(value: &T, id: TypeId) -> Option<&(dyn Any + Send + Sync)> {
190    (id == TypeId::of::<T>()).then_some(value as &(dyn Any + Send + Sync))
191}
192
193impl<T> ProviderIdentity for &T
194where
195    T: ProviderIdentity + ?Sized,
196{
197    fn provider_name(&self) -> &str {
198        T::provider_name(*self)
199    }
200
201    fn reads_namespace(&self, namespace: &str) -> bool {
202        T::reads_namespace(*self, namespace)
203    }
204
205    fn downcast_any(&self, id: TypeId) -> Option<&(dyn Any + Send + Sync)> {
206        T::downcast_any(*self, id)
207    }
208}
209
210impl<T> ProviderIdentity for Box<T>
211where
212    T: ProviderIdentity + ?Sized,
213{
214    fn provider_name(&self) -> &str {
215        T::provider_name(self.as_ref())
216    }
217
218    fn reads_namespace(&self, namespace: &str) -> bool {
219        T::reads_namespace(self.as_ref(), namespace)
220    }
221
222    fn downcast_any(&self, id: TypeId) -> Option<&(dyn Any + Send + Sync)> {
223        T::downcast_any(self.as_ref(), id)
224    }
225}
226
227impl<T> ProviderIdentity for Arc<T>
228where
229    T: ProviderIdentity + ?Sized,
230{
231    fn provider_name(&self) -> &str {
232        T::provider_name(self.as_ref())
233    }
234
235    fn reads_namespace(&self, namespace: &str) -> bool {
236        T::reads_namespace(self.as_ref(), namespace)
237    }
238
239    fn downcast_any(&self, id: TypeId) -> Option<&(dyn Any + Send + Sync)> {
240        T::downcast_any(self.as_ref(), id)
241    }
242}
243
244#[cfg(test)]
245mod tests {
246    use super::*;
247
248    struct Named;
249
250    impl ProviderIdentity for Named {
251        fn provider_name(&self) -> &str {
252            "named"
253        }
254
255        fn reads_namespace(&self, namespace: &str) -> bool {
256            namespace == "named-state"
257        }
258    }
259
260    struct Exposed {
261        value: u32,
262    }
263
264    impl ProviderIdentity for Exposed {
265        fn provider_name(&self) -> &str {
266            "exposed"
267        }
268
269        fn reads_namespace(&self, _namespace: &str) -> bool {
270            false
271        }
272
273        fn downcast_any(&self, id: TypeId) -> Option<&(dyn Any + Send + Sync)> {
274            expose(self, id).or_else(|| expose(&self.value, id))
275        }
276    }
277
278    #[test]
279    fn concrete_impl_returns_configured_name() {
280        assert_eq!(Named.provider_name(), "named");
281    }
282
283    #[test]
284    fn ref_forwarding_delegates() {
285        let named = Named;
286        let borrowed: &Named = &named;
287        assert_eq!(borrowed.provider_name(), "named");
288    }
289
290    #[test]
291    fn box_forwarding_delegates() {
292        let boxed: Box<dyn ProviderIdentity> = Box::new(Named);
293        assert_eq!(boxed.provider_name(), "named");
294    }
295
296    #[test]
297    fn arc_forwarding_delegates() {
298        let arced: Arc<dyn ProviderIdentity> = Arc::new(Named);
299        assert_eq!(arced.provider_name(), "named");
300    }
301
302    #[test]
303    fn pointers_forward_reads_namespace() {
304        let named = Named;
305        let borrowed: &Named = &named;
306        let boxed: Box<dyn ProviderIdentity> = Box::new(Named);
307        let arced: Arc<dyn ProviderIdentity> = Arc::new(Named);
308        for provider in [borrowed as &dyn ProviderIdentity, &boxed, &arced] {
309            assert!(provider.reads_namespace("named-state"));
310            assert!(!provider.reads_namespace("named"));
311        }
312    }
313
314    #[test]
315    fn exposed_objects_are_found_through_pointers() {
316        let exposed = Exposed { value: 7 };
317        assert_eq!(exposed.downcast_ref::<u32>(), Some(&7));
318        assert!(exposed.downcast_ref::<Exposed>().is_some());
319        assert!(exposed.downcast_ref::<String>().is_none());
320        let borrowed = &exposed;
321        assert!(
322            <&Exposed as ProviderIdentity>::downcast_any(&borrowed, TypeId::of::<u32>()).is_some()
323        );
324
325        let arced: Arc<dyn ProviderIdentity> = Arc::new(Exposed { value: 1 });
326        assert_eq!(arced.downcast_ref::<u32>(), Some(&1));
327        let boxed: Box<dyn ProviderIdentity> = Box::new(Exposed { value: 2 });
328        assert_eq!(boxed.downcast_ref::<u32>(), Some(&2));
329
330        assert!(Named.downcast_ref::<Named>().is_none());
331    }
332}