use serde_json::Value;
use std::collections::HashMap;
use std::sync::Arc;
use uuid::Uuid;
use lc_callbacks::{CallbackManager, RunTree, RunType};
use super::cancellation::CancellationToken;
pub const RUN_META_PARENT_RUN_ID: &str = "__lc_parent_run_id";
pub const RUN_META_TRACE_ID: &str = "__lc_trace_id";
pub fn run_tree_from_config(
name: impl Into<String>,
run_type: RunType,
inputs: serde_json::Value,
config: Option<&RunnableConfig>,
) -> RunTree {
let mut run = RunTree::new(name, run_type, inputs);
let Some(cfg) = config else {
return run;
};
for tag in &cfg.tags {
run = run.with_tag(tag.clone());
}
for (key, value) in &cfg.metadata {
if key == RUN_META_PARENT_RUN_ID || key == RUN_META_TRACE_ID {
continue;
}
run = run.with_metadata(key.clone(), value.clone());
}
if let Some(parent) = cfg
.metadata
.get(RUN_META_PARENT_RUN_ID)
.and_then(|v| v.as_str())
{
match Uuid::parse_str(parent) {
Ok(id) => run.parent_run_id = Some(id),
Err(_) => {
log::warn!("ignoring invalid {RUN_META_PARENT_RUN_ID} '{parent}' in RunnableConfig")
}
}
}
if let Some(trace) = cfg.metadata.get(RUN_META_TRACE_ID).and_then(|v| v.as_str()) {
match Uuid::parse_str(trace) {
Ok(id) => run.trace_id = Some(id),
Err(_) => {
log::warn!("ignoring invalid {RUN_META_TRACE_ID} '{trace}' in RunnableConfig")
}
}
}
run
}
#[derive(Debug, Clone, Default)]
pub struct RunnableConfig {
pub tags: Vec<String>,
pub metadata: HashMap<String, Value>,
pub max_concurrency: Option<usize>,
pub run_id: Option<Uuid>,
pub run_name: Option<String>,
pub callbacks: Option<Arc<CallbackManager>>,
pub cancellation_token: Option<CancellationToken>,
pub temperature: Option<f32>,
pub max_tokens: Option<usize>,
pub configurable: HashMap<String, Value>,
}
impl RunnableConfig {
pub fn new() -> Self {
Self::default()
}
pub fn with_tag(mut self, tag: impl Into<String>) -> Self {
self.tags.push(tag.into());
self
}
pub fn with_metadata(mut self, key: impl Into<String>, value: Value) -> Self {
self.metadata.insert(key.into(), value);
self
}
pub fn with_max_concurrency(mut self, max: usize) -> Self {
self.max_concurrency = Some(max);
self
}
pub fn with_run_id(mut self, id: Uuid) -> Self {
self.run_id = Some(id);
self
}
pub fn with_run_name(mut self, name: impl Into<String>) -> Self {
self.run_name = Some(name.into());
self
}
pub fn with_callbacks(mut self, callbacks: Arc<CallbackManager>) -> Self {
self.callbacks = Some(callbacks);
self
}
pub fn with_cancellation_token(mut self, token: CancellationToken) -> Self {
self.cancellation_token = Some(token);
self
}
pub fn with_temperature(mut self, temperature: f32) -> Self {
self.temperature = Some(temperature);
self
}
pub fn with_max_tokens(mut self, max_tokens: usize) -> Self {
self.max_tokens = Some(max_tokens);
self
}
pub fn with_configurable(mut self, key: impl Into<String>, value: Value) -> Self {
self.configurable.insert(key.into(), value);
self
}
pub fn configurable_value(&self, key: &str) -> Option<&Value> {
self.configurable.get(key)
}
pub fn is_cancelled(&self) -> bool {
self.cancellation_token
.as_ref()
.is_some_and(|t| t.is_cancelled())
}
pub fn merge(mut self, other: RunnableConfig) -> Self {
for tag in other.tags {
if !self.tags.iter().any(|existing| existing == &tag) {
self.tags.push(tag);
}
}
self.metadata.extend(other.metadata);
self.configurable.extend(other.configurable);
if other.max_concurrency.is_some() {
self.max_concurrency = other.max_concurrency;
}
if other.run_id.is_some() {
self.run_id = other.run_id;
}
if other.run_name.is_some() {
self.run_name = other.run_name;
}
if other.cancellation_token.is_some() {
self.cancellation_token = other.cancellation_token;
}
if other.temperature.is_some() {
self.temperature = other.temperature;
}
if other.max_tokens.is_some() {
self.max_tokens = other.max_tokens;
}
if let (Some(self_cb), Some(other_cb)) = (&self.callbacks, &other.callbacks) {
self.callbacks = Some(Arc::new(self_cb.merge_with(other_cb)));
} else if other.callbacks.is_some() {
self.callbacks = other.callbacks;
}
self
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn configurable_roundtrip() {
let cfg = RunnableConfig::new().with_configurable("session_id", json!("s1"));
assert_eq!(cfg.configurable_value("session_id"), Some(&json!("s1")));
assert_eq!(cfg.configurable_value("missing"), None);
}
#[test]
fn configurable_merge_overrides() {
let base = RunnableConfig::new().with_configurable("which", json!("a"));
let other = RunnableConfig::new().with_configurable("which", json!("b"));
let merged = base.merge(other);
assert_eq!(merged.configurable_value("which"), Some(&json!("b")));
assert_eq!(merged.configurable_value("none"), None);
}
}