symbi_runtime/reasoning/
knowledge_executor.rs1use std::sync::Arc;
8
9use async_trait::async_trait;
10
11use crate::reasoning::circuit_breaker::CircuitBreakerRegistry;
12use crate::reasoning::executor::ActionExecutor;
13use crate::reasoning::knowledge_bridge::KnowledgeBridge;
14use crate::reasoning::loop_types::{LoopConfig, Observation, ProposedAction};
15use crate::types::AgentId;
16
17pub struct KnowledgeAwareExecutor {
20 inner: Arc<dyn ActionExecutor>,
21 bridge: Arc<KnowledgeBridge>,
22 agent_id: AgentId,
23}
24
25impl KnowledgeAwareExecutor {
26 pub fn new(
27 inner: Arc<dyn ActionExecutor>,
28 bridge: Arc<KnowledgeBridge>,
29 agent_id: AgentId,
30 ) -> Self {
31 Self {
32 inner,
33 bridge,
34 agent_id,
35 }
36 }
37}
38
39#[async_trait]
40impl ActionExecutor for KnowledgeAwareExecutor {
41 async fn execute_actions(
42 &self,
43 actions: &[ProposedAction],
44 config: &LoopConfig,
45 circuit_breakers: &CircuitBreakerRegistry,
46 ) -> Vec<Observation> {
47 let mut knowledge_actions = Vec::new();
49 let mut regular_actions = Vec::new();
50
51 for action in actions {
52 if let ProposedAction::ToolCall {
53 name,
54 call_id,
55 arguments,
56 ..
57 } = action
58 {
59 if KnowledgeBridge::is_knowledge_tool(name) {
60 knowledge_actions.push((call_id.clone(), name.clone(), arguments.clone()));
61 } else {
62 regular_actions.push(action.clone());
63 }
64 } else {
65 regular_actions.push(action.clone());
66 }
67 }
68
69 let mut observations = Vec::new();
70
71 for (call_id, name, arguments) in &knowledge_actions {
73 let result = self
74 .bridge
75 .handle_tool_call(&self.agent_id, name, arguments)
76 .await;
77
78 match result {
79 Ok(content) => {
80 observations.push(Observation::tool_result(call_id, content));
81 }
82 Err(err) => {
83 observations.push(Observation::tool_error(call_id, err));
84 }
85 }
86 }
87
88 if !regular_actions.is_empty() {
90 let inner_obs = self
91 .inner
92 .execute_actions(®ular_actions, config, circuit_breakers)
93 .await;
94 observations.extend(inner_obs);
95 }
96
97 observations
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104 use crate::reasoning::executor::DefaultActionExecutor;
105 use crate::reasoning::loop_types::LoopConfig;
106
107 #[tokio::test]
110 async fn test_regular_actions_delegated() {
111 let inner = Arc::new(DefaultActionExecutor::default());
114 let config = LoopConfig::default();
115 let circuit_breakers = CircuitBreakerRegistry::default();
116
117 let actions = vec![ProposedAction::ToolCall {
119 call_id: "c1".into(),
120 name: "web_search".into(),
121 arguments: r#"{"q":"test"}"#.into(),
122 }];
123
124 let obs = inner
125 .execute_actions(&actions, &config, &circuit_breakers)
126 .await;
127 assert_eq!(obs.len(), 1);
128 assert!(!obs[0].is_error);
129 }
130
131 #[test]
132 fn test_knowledge_tool_detection() {
133 assert!(KnowledgeBridge::is_knowledge_tool("recall_knowledge"));
134 assert!(KnowledgeBridge::is_knowledge_tool("store_knowledge"));
135 assert!(!KnowledgeBridge::is_knowledge_tool("web_search"));
136 }
137}