Skip to main content

juncture_tracing/
types.rs

1//! Types for server metadata and LLM caching policies
2//!
3//! This module provides types for deployment metadata and LLM response caching configuration.
4
5use serde::{Deserialize, Serialize};
6use std::fmt;
7
8/// Server deployment metadata for observability
9///
10/// Contains optional information about the deployment environment that can be
11/// attached to traces and metrics for better observability in multi-instance
12/// deployments.
13///
14/// # Examples
15///
16/// ```
17/// use juncture_tracing::types::ServerInfo;
18///
19/// let info = ServerInfo {
20///     assistant_id: Some("asst_123".to_string()),
21///     deployment: Some("production".to_string()),
22///     ..Default::default()
23/// };
24/// ```
25#[derive(Clone, Debug, Default, Deserialize, Serialize)]
26#[serde(rename_all = "camelCase")]
27pub struct ServerInfo {
28    /// Assistant ID for multi-assistant deployments
29    pub assistant_id: Option<String>,
30
31    /// Graph ID identifying the deployed graph
32    pub graph_id: Option<String>,
33
34    /// Authenticated user (if applicable)
35    pub user: Option<String>,
36
37    /// Deployment environment identifier
38    pub deployment: Option<String>,
39
40    /// Service version
41    pub version: Option<String>,
42
43    /// Instance ID for multi-instance deployments
44    pub instance_id: Option<String>,
45}
46
47impl ServerInfo {
48    /// Create a new empty `ServerInfo`
49    ///
50    /// # Examples
51    ///
52    /// ```
53    /// use juncture_tracing::types::ServerInfo;
54    ///
55    /// let info = ServerInfo::new();
56    /// assert!(info.assistant_id.is_none());
57    /// ```
58    #[must_use]
59    pub fn new() -> Self {
60        Self::default()
61    }
62
63    /// Set the assistant ID
64    ///
65    /// # Examples
66    ///
67    /// ```
68    /// use juncture_tracing::types::ServerInfo;
69    ///
70    /// let info = ServerInfo::new().with_assistant_id("asst_123");
71    /// assert_eq!(info.assistant_id, Some("asst_123".to_string()));
72    /// ```
73    #[must_use]
74    pub fn with_assistant_id(mut self, id: impl Into<String>) -> Self {
75        self.assistant_id = Some(id.into());
76        self
77    }
78
79    /// Set the graph ID
80    ///
81    /// # Examples
82    ///
83    /// ```
84    /// use juncture_tracing::types::ServerInfo;
85    ///
86    /// let info = ServerInfo::new().with_graph_id("graph_456");
87    /// assert_eq!(info.graph_id, Some("graph_456".to_string()));
88    /// ```
89    #[must_use]
90    pub fn with_graph_id(mut self, id: impl Into<String>) -> Self {
91        self.graph_id = Some(id.into());
92        self
93    }
94
95    /// Set the user
96    ///
97    /// # Examples
98    ///
99    /// ```
100    /// use juncture_tracing::types::ServerInfo;
101    ///
102    /// let info = ServerInfo::new().with_user("user@example.com");
103    /// assert_eq!(info.user, Some("user@example.com".to_string()));
104    /// ```
105    #[must_use]
106    pub fn with_user(mut self, user: impl Into<String>) -> Self {
107        self.user = Some(user.into());
108        self
109    }
110
111    /// Set the deployment environment
112    ///
113    /// # Examples
114    ///
115    /// ```
116    /// use juncture_tracing::types::ServerInfo;
117    ///
118    /// let info = ServerInfo::new().with_deployment("production");
119    /// assert_eq!(info.deployment, Some("production".to_string()));
120    /// ```
121    #[must_use]
122    pub fn with_deployment(mut self, deployment: impl Into<String>) -> Self {
123        self.deployment = Some(deployment.into());
124        self
125    }
126
127    /// Set the version
128    ///
129    /// # Examples
130    ///
131    /// ```
132    /// use juncture_tracing::types::ServerInfo;
133    ///
134    /// let info = ServerInfo::new().with_version("1.0.0");
135    /// assert_eq!(info.version, Some("1.0.0".to_string()));
136    /// ```
137    #[must_use]
138    pub fn with_version(mut self, version: impl Into<String>) -> Self {
139        self.version = Some(version.into());
140        self
141    }
142
143    /// Set the instance ID
144    ///
145    /// # Examples
146    ///
147    /// ```
148    /// use juncture_tracing::types::ServerInfo;
149    ///
150    /// let info = ServerInfo::new().with_instance_id("pod-abc123");
151    /// assert_eq!(info.instance_id, Some("pod-abc123".to_string()));
152    /// ```
153    #[must_use]
154    pub fn with_instance_id(mut self, id: impl Into<String>) -> Self {
155        self.instance_id = Some(id.into());
156        self
157    }
158}
159
160/// Cache policy for LLM response caching
161///
162/// Controls how LLM responses are cached, including the ability to customize
163/// the cache key generation function.
164///
165/// # Examples
166///
167/// ```
168/// use juncture_tracing::types::LlmCachePolicy;
169///
170/// let policy = LlmCachePolicy::default();
171/// assert!(policy.key_func.is_none());
172/// ```
173#[derive(Default)]
174pub struct LlmCachePolicy {
175    /// Optional custom cache key function
176    ///
177    /// If `None`, the default key function using `(model, messages_hash)` is used.
178    pub key_func: Option<LlmCacheKeyFn>,
179}
180
181/// Type alias for LLM cache key function
182type LlmCacheKeyFn = std::sync::Arc<dyn Fn(&LlmCacheKeyInput) -> String + Send + Sync>;
183
184impl fmt::Debug for LlmCachePolicy {
185    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
186        f.debug_struct("LlmCachePolicy")
187            .field(
188                "key_func",
189                if self.key_func.is_some() {
190                    &"Some(custom function)"
191                } else {
192                    &"None"
193                },
194            )
195            .finish()
196    }
197}
198
199impl LlmCachePolicy {
200    /// Create a new cache policy with the default key function
201    ///
202    /// # Examples
203    ///
204    /// ```
205    /// use juncture_tracing::types::LlmCachePolicy;
206    ///
207    /// let policy = LlmCachePolicy::new();
208    /// assert!(policy.key_func.is_none());
209    /// ```
210    #[must_use]
211    pub fn new() -> Self {
212        Self::default()
213    }
214
215    /// Set a custom cache key function
216    ///
217    /// # Parameters
218    ///
219    /// * `f` - Function that generates cache keys from input
220    ///
221    /// # Examples
222    ///
223    /// ```
224    /// use juncture_tracing::types::{LlmCachePolicy, LlmCacheKeyInput};
225    /// use serde_json::json;
226    ///
227    /// let policy = LlmCachePolicy::new().with_key_func(|input| {
228    ///     format!("{}:{}", input.model, input.messages.len())
229    /// });
230    ///
231    /// let key_input = LlmCacheKeyInput {
232    ///     model: "gpt-4".to_string(),
233    ///     messages: vec![],
234    ///     tools: vec![],
235    ///     config: None,
236    /// };
237    /// assert!(policy.key_func.as_ref().map(|f| f(&key_input)).is_some());
238    /// ```
239    #[must_use]
240    #[allow(
241        clippy::type_complexity,
242        reason = "Function pointer type is necessary for the cache key API"
243    )]
244    pub fn with_key_func<F>(mut self, f: F) -> Self
245    where
246        F: Fn(&LlmCacheKeyInput) -> String + Send + Sync + 'static,
247    {
248        self.key_func = Some(std::sync::Arc::new(f));
249        self
250    }
251}
252
253/// Input for LLM cache key generation
254///
255/// Contains the parameters that influence cache key generation for LLM calls.
256///
257/// # Examples
258///
259/// ```
260/// use juncture_tracing::types::LlmCacheKeyInput;
261/// use serde_json::json;
262///
263/// let input = LlmCacheKeyInput {
264///     model: "gpt-4".to_string(),
265///     messages: vec![json!({"role": "user", "content": "Hello"})],
266///     tools: vec![],
267///     config: None,
268/// };
269/// ```
270#[derive(Clone, Debug)]
271pub struct LlmCacheKeyInput {
272    /// Model name
273    pub model: String,
274
275    /// Messages in the conversation
276    pub messages: Vec<serde_json::Value>,
277
278    /// Tools available in the call
279    pub tools: Vec<serde_json::Value>,
280
281    /// Optional call configuration
282    ///
283    /// Reserved for future use with `CallOptions` type.
284    /// Currently unused but kept for API compatibility.
285    pub config: Option<()>,
286}
287
288#[cfg(test)]
289mod tests {
290    use super::*;
291    use serde_json::json;
292
293    #[test]
294    fn test_server_info_default() {
295        let info = ServerInfo::default();
296        assert!(info.assistant_id.is_none());
297        assert!(info.graph_id.is_none());
298        assert!(info.user.is_none());
299        assert!(info.deployment.is_none());
300        assert!(info.version.is_none());
301        assert!(info.instance_id.is_none());
302    }
303
304    #[test]
305    fn test_server_info_builder() {
306        let info = ServerInfo::new()
307            .with_assistant_id("asst_123")
308            .with_graph_id("graph_456")
309            .with_user("user@example.com")
310            .with_deployment("production")
311            .with_version("1.0.0")
312            .with_instance_id("pod-abc123");
313
314        assert_eq!(info.assistant_id, Some("asst_123".to_string()));
315        assert_eq!(info.graph_id, Some("graph_456".to_string()));
316        assert_eq!(info.user, Some("user@example.com".to_string()));
317        assert_eq!(info.deployment, Some("production".to_string()));
318        assert_eq!(info.version, Some("1.0.0".to_string()));
319        assert_eq!(info.instance_id, Some("pod-abc123".to_string()));
320    }
321
322    #[test]
323    fn test_server_info_serialization() {
324        let info = ServerInfo {
325            assistant_id: Some("asst_123".to_string()),
326            deployment: Some("production".to_string()),
327            ..Default::default()
328        };
329
330        let json = serde_json::to_string(&info).unwrap();
331        let deserialized: ServerInfo = serde_json::from_str(&json).unwrap();
332
333        assert_eq!(deserialized.assistant_id, info.assistant_id);
334        assert_eq!(deserialized.deployment, info.deployment);
335    }
336
337    #[test]
338    fn test_llm_cache_policy_default() {
339        let policy = LlmCachePolicy::default();
340        assert!(policy.key_func.is_none());
341    }
342
343    #[test]
344    fn test_llm_cache_policy_with_custom_func() {
345        let policy = LlmCachePolicy::new()
346            .with_key_func(|input| format!("custom:{}:{}", input.model, input.messages.len()));
347
348        assert!(policy.key_func.is_some());
349
350        let input = LlmCacheKeyInput {
351            model: "gpt-4".to_string(),
352            messages: vec![json!({}), json!({})],
353            tools: vec![],
354            config: None,
355        };
356
357        let key = policy.key_func.as_ref().unwrap()(&input);
358        assert_eq!(key, "custom:gpt-4:2");
359    }
360
361    #[test]
362    fn test_llm_cache_policy_debug() {
363        let policy_without = LlmCachePolicy::default();
364        let debug_str = format!("{policy_without:?}");
365        assert!(debug_str.contains("None"));
366
367        let policy_with = LlmCachePolicy::new().with_key_func(|_| "key".to_string());
368        let debug_str = format!("{policy_with:?}");
369        assert!(debug_str.contains("Some"));
370    }
371
372    #[test]
373    fn test_llm_cache_key_input() {
374        let input = LlmCacheKeyInput {
375            model: "claude-3".to_string(),
376            messages: vec![json!({"role": "user"})],
377            tools: vec![],
378            config: None,
379        };
380
381        assert_eq!(input.model, "claude-3");
382        assert_eq!(input.messages.len(), 1);
383        assert!(input.tools.is_empty());
384        assert!(input.config.is_none());
385    }
386}
387
388// Rust guideline compliant 2026-05-19