langfuse/prompts/text.rs
1//! Text prompt client with `{{variable}}` template compilation.
2
3use std::collections::HashMap;
4
5use langfuse_core::error::LangfuseError;
6
7/// A compiled text prompt. Holds the raw template and supports `{{variable}}` substitution.
8#[derive(Debug, Clone)]
9pub struct TextPromptClient {
10 /// Prompt name.
11 pub name: String,
12 /// Prompt version.
13 pub version: i32,
14 /// Raw template string containing `{{variable}}` placeholders.
15 pub template: String,
16 /// Arbitrary configuration attached to the prompt.
17 pub config: serde_json::Value,
18 /// Labels associated with this prompt version (e.g. `["production"]`).
19 pub labels: Vec<String>,
20 /// Tags for categorisation.
21 pub tags: Vec<String>,
22 /// Whether this prompt was served from an expired cache entry (fallback).
23 pub is_fallback: bool,
24}
25
26impl TextPromptClient {
27 /// Compile the prompt by replacing `{{variable}}` placeholders with values from `variables`.
28 ///
29 /// Returns [`LangfuseError::PromptCompilation`] if a placeholder references a variable
30 /// that is not present in the map.
31 pub fn compile(&self, variables: &HashMap<String, String>) -> Result<String, LangfuseError> {
32 compile_template(&self.template, variables)
33 }
34}
35
36/// Shared template compilation logic.
37///
38/// Scans `template` for `{{name}}` patterns and replaces each with the corresponding
39/// value from `variables`. Whitespace inside the braces is trimmed so `{{ name }}` works
40/// identically to `{{name}}`.
41pub(crate) fn compile_template(
42 template: &str,
43 variables: &HashMap<String, String>,
44) -> Result<String, LangfuseError> {
45 let mut result = String::with_capacity(template.len());
46 let mut rest = template;
47
48 while let Some(start) = rest.find("{{") {
49 // Push everything before the opening braces.
50 result.push_str(&rest[..start]);
51
52 let after_open = &rest[start + 2..];
53 let end = after_open
54 .find("}}")
55 .ok_or_else(|| LangfuseError::PromptCompilation {
56 variable: "unclosed {{".into(),
57 })?;
58
59 let var_name = after_open[..end].trim();
60 let value = variables
61 .get(var_name)
62 .ok_or_else(|| LangfuseError::PromptCompilation {
63 variable: var_name.to_owned(),
64 })?;
65
66 result.push_str(value);
67 rest = &after_open[end + 2..];
68 }
69
70 // Append any remaining text after the last placeholder.
71 result.push_str(rest);
72 Ok(result)
73}