skilllite_agent/
evolution.rs1use anyhow::Result;
7
8use skilllite_evolution::feedback::{DecisionInput, FeedbackSignal as EvolutionFeedbackSignal};
9use skilllite_evolution::{strip_think_blocks, EvolutionLlm, EvolutionMessage};
10
11use super::llm::LlmClient;
12use super::types::{ChatMessage, ExecutionFeedback, FeedbackSignal};
13
14pub struct EvolutionLlmAdapter<'a> {
16 pub llm: &'a LlmClient,
17}
18
19#[async_trait::async_trait]
20impl EvolutionLlm for EvolutionLlmAdapter<'_> {
21 async fn complete(
22 &self,
23 messages: &[EvolutionMessage],
24 model: &str,
25 temperature: f64,
26 ) -> Result<String> {
27 let chat_messages: Vec<ChatMessage> = messages
28 .iter()
29 .map(|m| ChatMessage {
30 role: m.role.clone(),
31 content: m.content.clone(),
32 tool_calls: None,
33 tool_call_id: None,
34 name: None,
35 })
36 .collect();
37
38 let response = self
39 .llm
40 .chat_completion(model, &chat_messages, None, Some(temperature))
41 .await?;
42
43 let msg = response.choices.first().map(|c| &c.message);
44 let content = msg.and_then(|m| m.content.as_deref()).unwrap_or("").trim();
45 let has_reasoning_field = msg.and_then(|m| m.reasoning_content.as_ref()).is_some();
46
47 if has_reasoning_field {
48 Ok(content.to_string())
50 } else {
51 Ok(strip_think_blocks(content).to_string())
53 }
54 }
55}
56
57pub fn execution_feedback_to_decision_input(feedback: &ExecutionFeedback) -> DecisionInput {
59 DecisionInput {
60 total_tools: feedback.total_tools,
61 failed_tools: feedback.failed_tools,
62 replans: feedback.replans,
63 elapsed_ms: feedback.elapsed_ms,
64 task_completed: feedback.task_completed,
65 task_description: feedback.task_description.clone(),
66 rules_used: feedback.rules_used.clone(),
67 tools_detail: feedback
68 .tools_detail
69 .iter()
70 .map(|t| skilllite_evolution::feedback::ToolExecDetail {
71 tool: t.tool.clone(),
72 success: t.success,
73 })
74 .collect(),
75 }
76}
77
78pub fn to_evolution_feedback(signal: FeedbackSignal) -> EvolutionFeedbackSignal {
80 match signal {
81 FeedbackSignal::ExplicitPositive => EvolutionFeedbackSignal::ExplicitPositive,
82 FeedbackSignal::ExplicitNegative => EvolutionFeedbackSignal::ExplicitNegative,
83 FeedbackSignal::Neutral => EvolutionFeedbackSignal::Neutral,
84 }
85}
86
87pub use skilllite_evolution::feedback;
89pub use skilllite_evolution::seed;
90pub use skilllite_evolution::{
91 check_auto_rollback, format_evolution_changes, on_shutdown, query_changes_by_txn,
92 run_evolution, EvolutionMode,
93};
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98 use crate::types::{ExecutionFeedback, ToolExecDetail};
99
100 #[test]
101 fn test_execution_feedback_to_decision_input_preserves_rules_used() {
102 let feedback = ExecutionFeedback {
103 total_tools: 2,
104 failed_tools: 0,
105 replans: 1,
106 iterations: 3,
107 elapsed_ms: 1200,
108 context_overflow_retries: 0,
109 task_completed: true,
110 task_description: Some("test task".to_string()),
111 rules_used: vec!["rule.alpha".to_string(), "rule.beta".to_string()],
112 tools_detail: vec![ToolExecDetail {
113 tool: "read_file".to_string(),
114 success: true,
115 }],
116 };
117
118 let input = execution_feedback_to_decision_input(&feedback);
119 assert_eq!(
120 input.rules_used,
121 vec!["rule.alpha".to_string(), "rule.beta".to_string()]
122 );
123 }
124
125 #[test]
126 fn test_execution_feedback_to_decision_input_preserves_tools_detail() {
127 let feedback = ExecutionFeedback {
128 total_tools: 2,
129 failed_tools: 1,
130 replans: 1,
131 iterations: 3,
132 elapsed_ms: 1200,
133 context_overflow_retries: 0,
134 task_completed: false,
135 task_description: Some("another test task".to_string()),
136 rules_used: vec!["rule.gamma".to_string()],
137 tools_detail: vec![
138 ToolExecDetail {
139 tool: "list_directory".to_string(),
140 success: true,
141 },
142 ToolExecDetail {
143 tool: "write_file".to_string(),
144 success: false,
145 },
146 ],
147 };
148
149 let input = execution_feedback_to_decision_input(&feedback);
150 assert_eq!(input.tools_detail.len(), 2);
151 assert_eq!(input.tools_detail[0].tool, "list_directory".to_string());
152 assert!(input.tools_detail[0].success);
153 assert_eq!(input.tools_detail[1].tool, "write_file".to_string());
154 assert!(!input.tools_detail[1].success);
155 }
156}