llm_toolkit_expertise/
fragment.rs1use schemars::JsonSchema;
7use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
14#[serde(tag = "type", content = "content")]
15pub enum KnowledgeFragment {
16 Logic {
18 instruction: String,
20 #[serde(default, skip_serializing_if = "Vec::is_empty")]
22 steps: Vec<String>,
23 },
24
25 Guideline {
27 rule: String,
29 #[serde(default, skip_serializing_if = "Vec::is_empty")]
31 anchors: Vec<Anchor>,
32 },
33
34 QualityStandard {
36 criteria: Vec<String>,
38 passing_grade: String,
40 },
41
42 ToolDefinition(serde_json::Value),
44
45 Text(String),
47}
48
49impl KnowledgeFragment {
50 pub fn to_prompt(&self) -> String {
52 match self {
53 KnowledgeFragment::Logic { instruction, steps } => {
54 let mut prompt = format!("## Logic\n{}\n", instruction);
55 if !steps.is_empty() {
56 prompt.push_str("\n### Steps:\n");
57 for (i, step) in steps.iter().enumerate() {
58 prompt.push_str(&format!("{}. {}\n", i + 1, step));
59 }
60 }
61 prompt
62 }
63 KnowledgeFragment::Guideline { rule, anchors } => {
64 let mut prompt = format!("## Guideline\n{}\n", rule);
65 if !anchors.is_empty() {
66 prompt.push_str("\n### Examples:\n");
67 for anchor in anchors {
68 prompt.push_str(&anchor.to_prompt());
69 prompt.push('\n');
70 }
71 }
72 prompt
73 }
74 KnowledgeFragment::QualityStandard {
75 criteria,
76 passing_grade,
77 } => {
78 let mut prompt = String::from("## Quality Standard\n\n### Criteria:\n");
79 for criterion in criteria {
80 prompt.push_str(&format!("- {}\n", criterion));
81 }
82 prompt.push_str(&format!("\n### Passing Grade:\n{}\n", passing_grade));
83 prompt
84 }
85 KnowledgeFragment::ToolDefinition(value) => {
86 format!(
87 "## Tool Definition\n```json\n{}\n```\n",
88 serde_json::to_string_pretty(value).unwrap_or_else(|_| "{}".to_string())
89 )
90 }
91 KnowledgeFragment::Text(text) => {
92 format!("{}\n", text)
93 }
94 }
95 }
96
97 pub fn type_label(&self) -> &'static str {
99 match self {
100 KnowledgeFragment::Logic { .. } => "Logic",
101 KnowledgeFragment::Guideline { .. } => "Guideline",
102 KnowledgeFragment::QualityStandard { .. } => "Quality",
103 KnowledgeFragment::ToolDefinition(_) => "Tool",
104 KnowledgeFragment::Text(_) => "Text",
105 }
106 }
107
108 pub fn summary(&self) -> String {
110 match self {
111 KnowledgeFragment::Logic { instruction, .. } => truncate(instruction, 50),
112 KnowledgeFragment::Guideline { rule, .. } => truncate(rule, 50),
113 KnowledgeFragment::QualityStandard { criteria, .. } => {
114 if criteria.is_empty() {
115 "No criteria".to_string()
116 } else {
117 format!("{} criteria", criteria.len())
118 }
119 }
120 KnowledgeFragment::ToolDefinition(_) => "Tool definition".to_string(),
121 KnowledgeFragment::Text(text) => truncate(text, 50),
122 }
123 }
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
130pub struct Anchor {
131 pub context: String,
133 pub positive: String,
135 pub negative: String,
137 pub reason: String,
139}
140
141impl Anchor {
142 pub fn to_prompt(&self) -> String {
144 format!(
145 "**Context:** {}\n✅ **Good:** {}\n❌ **Bad:** {}\n**Why:** {}",
146 self.context, self.positive, self.negative, self.reason
147 )
148 }
149}
150
151fn truncate(s: &str, max_len: usize) -> String {
153 if s.len() <= max_len {
154 s.to_string()
155 } else {
156 format!("{}...", &s[..max_len.saturating_sub(3)])
157 }
158}
159
160#[cfg(test)]
161mod tests {
162 use super::*;
163
164 #[test]
165 fn test_logic_fragment_to_prompt() {
166 let fragment = KnowledgeFragment::Logic {
167 instruction: "Test instruction".to_string(),
168 steps: vec!["Step 1".to_string(), "Step 2".to_string()],
169 };
170 let prompt = fragment.to_prompt();
171 assert!(prompt.contains("## Logic"));
172 assert!(prompt.contains("Test instruction"));
173 assert!(prompt.contains("1. Step 1"));
174 assert!(prompt.contains("2. Step 2"));
175 }
176
177 #[test]
178 fn test_guideline_fragment_to_prompt() {
179 let anchor = Anchor {
180 context: "Test context".to_string(),
181 positive: "Good example".to_string(),
182 negative: "Bad example".to_string(),
183 reason: "Because".to_string(),
184 };
185 let fragment = KnowledgeFragment::Guideline {
186 rule: "Test rule".to_string(),
187 anchors: vec![anchor],
188 };
189 let prompt = fragment.to_prompt();
190 assert!(prompt.contains("## Guideline"));
191 assert!(prompt.contains("Test rule"));
192 assert!(prompt.contains("Good example"));
193 assert!(prompt.contains("Bad example"));
194 }
195
196 #[test]
197 fn test_fragment_type_label() {
198 let logic = KnowledgeFragment::Logic {
199 instruction: "Test".to_string(),
200 steps: vec![],
201 };
202 assert_eq!(logic.type_label(), "Logic");
203
204 let text = KnowledgeFragment::Text("Test".to_string());
205 assert_eq!(text.type_label(), "Text");
206 }
207
208 #[test]
209 fn test_truncate() {
210 assert_eq!(truncate("short", 10), "short");
211 assert_eq!(truncate("this is a very long text", 10), "this is...");
212 }
213}