Skip to main content

lc_core/runnables/
config.rs

1// src/core/runnables/config.rs
2//! Runnable execution configuration.
3
4use serde_json::Value;
5use std::collections::HashMap;
6use std::sync::Arc;
7use uuid::Uuid;
8
9use lc_callbacks::CallbackManager;
10
11use super::cancellation::CancellationToken;
12
13/// Runnable execution configuration.
14#[derive(Debug, Clone, Default)]
15pub struct RunnableConfig {
16    /// Tags for filtering and tracking.
17    pub tags: Vec<String>,
18
19    /// Metadata - custom data (JSON serializable).
20    pub metadata: HashMap<String, Value>,
21
22    /// Max concurrency for batch operations.
23    pub max_concurrency: Option<usize>,
24
25    /// Run ID for tracking.
26    pub run_id: Option<Uuid>,
27
28    /// Run name for debugging.
29    pub run_name: Option<String>,
30
31    /// Callback manager for tracing and monitoring.
32    pub callbacks: Option<Arc<CallbackManager>>,
33
34    /// Cancellation token for aborting long-running operations.
35    pub cancellation_token: Option<CancellationToken>,
36
37    /// Sampling temperature override for the current call.
38    ///
39    /// When set, takes precedence over the model's own configured
40    /// temperature. This is how wrapper layers (e.g. `LLMClient`) make
41    /// `with_temperature` effective through a trait object (providers Q2).
42    pub temperature: Option<f32>,
43
44    /// Max-tokens override for the current call.
45    ///
46    /// When set, takes precedence over the model's own configured max
47    /// tokens (providers Q2).
48    pub max_tokens: Option<usize>,
49}
50
51impl RunnableConfig {
52    /// Creates an empty configuration.
53    pub fn new() -> Self {
54        Self::default()
55    }
56
57    /// Adds a tag.
58    pub fn with_tag(mut self, tag: impl Into<String>) -> Self {
59        self.tags.push(tag.into());
60        self
61    }
62
63    /// Adds metadata.
64    pub fn with_metadata(mut self, key: impl Into<String>, value: Value) -> Self {
65        self.metadata.insert(key.into(), value);
66        self
67    }
68
69    /// Sets max concurrency.
70    pub fn with_max_concurrency(mut self, max: usize) -> Self {
71        self.max_concurrency = Some(max);
72        self
73    }
74
75    /// Sets run ID.
76    pub fn with_run_id(mut self, id: Uuid) -> Self {
77        self.run_id = Some(id);
78        self
79    }
80
81    /// Sets run name.
82    pub fn with_run_name(mut self, name: impl Into<String>) -> Self {
83        self.run_name = Some(name.into());
84        self
85    }
86
87    /// Sets callback manager.
88    pub fn with_callbacks(mut self, callbacks: Arc<CallbackManager>) -> Self {
89        self.callbacks = Some(callbacks);
90        self
91    }
92
93    /// Sets cancellation token.
94    pub fn with_cancellation_token(mut self, token: CancellationToken) -> Self {
95        self.cancellation_token = Some(token);
96        self
97    }
98
99    /// Sets a sampling temperature override for the call.
100    pub fn with_temperature(mut self, temperature: f32) -> Self {
101        self.temperature = Some(temperature);
102        self
103    }
104
105    /// Sets a max-tokens override for the call.
106    pub fn with_max_tokens(mut self, max_tokens: usize) -> Self {
107        self.max_tokens = Some(max_tokens);
108        self
109    }
110
111    /// Checks if cancellation has been requested.
112    pub fn is_cancelled(&self) -> bool {
113        self.cancellation_token
114            .as_ref()
115            .is_some_and(|t| t.is_cancelled())
116    }
117
118    /// Merges two configurations (later overrides earlier).
119    pub fn merge(mut self, other: RunnableConfig) -> Self {
120        // Merge tags as an order-preserving union (dedup without sorting).
121        for tag in other.tags {
122            if !self.tags.iter().any(|existing| existing == &tag) {
123                self.tags.push(tag);
124            }
125        }
126
127        // Merge metadata (override)
128        self.metadata.extend(other.metadata);
129
130        // Override other fields
131        if other.max_concurrency.is_some() {
132            self.max_concurrency = other.max_concurrency;
133        }
134        if other.run_id.is_some() {
135            self.run_id = other.run_id;
136        }
137        if other.run_name.is_some() {
138            self.run_name = other.run_name;
139        }
140        if other.cancellation_token.is_some() {
141            self.cancellation_token = other.cancellation_token;
142        }
143        if other.temperature.is_some() {
144            self.temperature = other.temperature;
145        }
146        if other.max_tokens.is_some() {
147            self.max_tokens = other.max_tokens;
148        }
149
150        // Merge callbacks: append `other`'s handlers instead of replacing the
151        // whole manager (Q8), so observers configured at different layers of
152        // the pipeline all keep firing.
153        if let (Some(self_cb), Some(other_cb)) = (&self.callbacks, &other.callbacks) {
154            self.callbacks = Some(Arc::new(self_cb.merge_with(other_cb)));
155        } else if other.callbacks.is_some() {
156            self.callbacks = other.callbacks;
157        }
158
159        self
160    }
161}