Skip to main content

vv_agent/prompt/builder/
section.rs

1use std::sync::{Arc, Mutex};
2
3use serde_json::{Map, Value};
4
5#[derive(Clone)]
6pub struct PromptSection {
7    pub id: String,
8    stable: bool,
9    source: String,
10    cache_hint: Option<String>,
11    metadata: Map<String, Value>,
12    compute: Arc<dyn Fn() -> String + Send + Sync>,
13    cached_value: Arc<Mutex<Option<String>>>,
14}
15
16impl PromptSection {
17    pub fn new(
18        id: impl Into<String>,
19        compute: impl Fn() -> String + Send + Sync + 'static,
20        stable: bool,
21    ) -> Self {
22        Self {
23            id: id.into(),
24            stable,
25            source: String::new(),
26            cache_hint: None,
27            metadata: Map::new(),
28            compute: Arc::new(compute),
29            cached_value: Arc::new(Mutex::new(None)),
30        }
31    }
32
33    pub fn constant(id: impl Into<String>, text: impl Into<String>, stable: bool) -> Self {
34        let text = text.into();
35        Self::new(id, move || text.clone(), stable)
36    }
37
38    pub fn source(mut self, source: impl Into<String>) -> Self {
39        self.source = source.into();
40        self
41    }
42
43    pub fn cache_hint(mut self, cache_hint: impl Into<String>) -> Self {
44        self.cache_hint = Some(cache_hint.into());
45        self
46    }
47
48    pub fn metadata(mut self, key: impl Into<String>, value: Value) -> Self {
49        self.metadata.insert(key.into(), value);
50        self
51    }
52
53    pub fn get_value(&self) -> String {
54        if self.stable {
55            let mut cached = self.cached_value.lock().expect("prompt section cache");
56            if let Some(value) = cached.as_ref() {
57                return value.clone();
58            }
59            let value = (self.compute)();
60            *cached = Some(value.clone());
61            return value;
62        }
63        (self.compute)()
64    }
65
66    pub fn invalidate(&self) {
67        let mut cached = self.cached_value.lock().expect("prompt section cache");
68        *cached = None;
69    }
70
71    pub fn to_metadata(&self) -> Option<Value> {
72        let text = self.get_value().trim().to_string();
73        if text.is_empty() {
74            return None;
75        }
76        let mut payload = Map::from_iter([
77            ("id".to_string(), Value::String(self.id.clone())),
78            ("text".to_string(), Value::String(text)),
79            ("stable".to_string(), Value::Bool(self.stable)),
80        ]);
81        if !self.source.is_empty() {
82            payload.insert("source".to_string(), Value::String(self.source.clone()));
83        }
84        if let Some(cache_hint) = &self.cache_hint {
85            payload.insert("cache_hint".to_string(), Value::String(cache_hint.clone()));
86        }
87        if !self.metadata.is_empty() {
88            payload.insert("metadata".to_string(), Value::Object(self.metadata.clone()));
89        }
90        Some(Value::Object(payload))
91    }
92
93    pub fn stable(&self) -> bool {
94        self.stable
95    }
96}