Skip to main content

rig_compose/
instructions.rs

1//! Agent persona / instruction set.
2//!
3//! [`Instructions`] is plain data — no LLM dependency. Cognition
4//! backends (e.g. `RigCognition` in azreal) consume it to produce a
5//! system prompt and constrain output shape; offline backends can
6//! ignore it.
7//!
8//! This is the seam that lets a user reuse an agent's *machinery* (the
9//! pipeline, sampler, skills, memory) while supplying their own
10//! persona, examples, and response schema.
11
12use serde::{Deserialize, Serialize};
13use serde_json::Value;
14
15/// Static persona + response contract for a cognition backend.
16#[derive(Debug, Clone, Default, Serialize, Deserialize)]
17pub struct Instructions {
18    /// System prompt. Sent verbatim as the system message.
19    pub system_prompt: String,
20    /// Optional JSON schema the backend must conform to. Backends that
21    /// support structured outputs should enforce this; others may use
22    /// it as a hint in the system prompt.
23    pub response_schema: Option<Value>,
24    /// Few-shot examples. Each entry is `(user_message, assistant_reply)`.
25    /// Empty for zero-shot prompting.
26    pub examples: Vec<(String, String)>,
27    /// Free-form metadata (model name, temperature hint, persona tag).
28    /// Backends decide which keys, if any, to honour.
29    pub metadata: Value,
30}
31
32impl Instructions {
33    pub fn new(system_prompt: impl Into<String>) -> Self {
34        Self {
35            system_prompt: system_prompt.into(),
36            response_schema: None,
37            examples: Vec::new(),
38            metadata: Value::Null,
39        }
40    }
41
42    pub fn with_response_schema(mut self, schema: Value) -> Self {
43        self.response_schema = Some(schema);
44        self
45    }
46
47    pub fn with_example(mut self, user: impl Into<String>, assistant: impl Into<String>) -> Self {
48        self.examples.push((user.into(), assistant.into()));
49        self
50    }
51
52    pub fn with_metadata(mut self, metadata: Value) -> Self {
53        self.metadata = metadata;
54        self
55    }
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61    use serde_json::json;
62
63    #[test]
64    fn builder_chains() {
65        let i = Instructions::new("you are a detector")
66            .with_response_schema(json!({"type": "object"}))
67            .with_example("hi", "hello")
68            .with_metadata(json!({"model": "x"}));
69        assert_eq!(i.system_prompt, "you are a detector");
70        assert!(i.response_schema.is_some());
71        assert_eq!(i.examples.len(), 1);
72        assert_eq!(i.metadata["model"], "x");
73    }
74
75    #[test]
76    fn round_trips_serde() {
77        let i = Instructions::new("x").with_example("a", "b");
78        let s = serde_json::to_string(&i).unwrap();
79        let back: Instructions = serde_json::from_str(&s).unwrap();
80        assert_eq!(back.examples.len(), 1);
81    }
82}