Skip to main content

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}