active_call/playbook/
mod.rs1use anyhow::{Result, anyhow};
2use serde::{Deserialize, Serialize};
3use std::{collections::HashMap, path::Path};
4use tokio::fs;
5use crate::EouOption;
6use crate::media::recorder::RecorderOption;
7use crate::media::vad::VADOption;
8use crate::synthesis::SynthesisOption;
9use crate::transcription::TranscriptionOption;
10
11#[derive(Debug, Deserialize, Serialize, Clone, Default)]
12#[serde(rename_all = "camelCase")]
13pub struct PlaybookConfig {
14 pub asr: Option<TranscriptionOption>,
15 pub tts: Option<SynthesisOption>,
16 pub llm: Option<LlmConfig>,
17 pub vad: Option<VADOption>,
18 pub denoise: Option<bool>,
19 pub recorder: Option<RecorderOption>,
20 pub extra: Option<HashMap<String, String>>,
21 pub eou: Option<EouOption>,
22}
23
24#[derive(Debug, Deserialize, Serialize, Clone, Default)]
25#[serde(rename_all = "camelCase")]
26pub struct LlmConfig {
27 pub provider: String,
28 pub model: Option<String>,
29 pub base_url: Option<String>,
30 pub api_key: Option<String>,
31 pub prompt: Option<String>,
32 pub greeting: Option<String>,
33}
34
35#[derive(Debug, Clone)]
36pub struct Playbook {
37 pub config: PlaybookConfig,
38}
39
40impl Playbook {
41 pub async fn load<P: AsRef<Path>>(path: P) -> Result<Self> {
42 let content = fs::read_to_string(path).await?;
43 if !content.starts_with("---") {
44 return Err(anyhow!("Missing front matter"));
45 }
46
47 let parts: Vec<&str> = content.splitn(3, "---").collect();
48 if parts.len() < 3 {
49 return Err(anyhow!("Invalid front matter format"));
50 }
51
52 let yaml_str = parts[1];
53 let prompt = parts[2].trim().to_string();
54
55 let mut config: PlaybookConfig = serde_yaml::from_str(yaml_str)?;
56 if let Some(llm) = config.llm.as_mut() {
57 llm.prompt = Some(prompt);
58 }
59
60 Ok(Self { config })
61 }
62}
63
64pub mod dialogue;
65pub mod handler;
66pub mod runner;
67
68pub use dialogue::DialogueHandler;
69pub use handler::{LlmHandler, RagRetriever};
70pub use runner::PlaybookRunner;