rmcp_agent/agent/
core.rs

1use std::sync::Arc;
2
3use async_trait::async_trait;
4use langchain_rust::agent::{Agent, AgentError};
5use langchain_rust::chain::Chain;
6use langchain_rust::prompt::{HumanMessagePromptTemplate, MessageFormatterStruct, PromptArgs};
7use langchain_rust::schemas::{
8    AgentAction, AgentEvent, AgentFinish, FunctionCallResponse, LogTools, Message,
9};
10use langchain_rust::tools::Tool;
11use langchain_rust::{
12    fmt_message, fmt_placeholder, fmt_template, message_formatter, template_jinja2,
13};
14use serde_json::json;
15
16use crate::agent::extension::{AgentEventChunk, AgentExt, AgentStream, DeltaEvent};
17use crate::agent::intermediate::IntermediateStep;
18
19pub struct OpenAIMcpAgent {
20    pub chain: Box<dyn Chain>,
21    pub tools: Vec<Arc<dyn Tool>>,
22}
23
24impl OpenAIMcpAgent {
25    pub(crate) fn create_prompt(prefix: &str) -> MessageFormatterStruct {
26        let message = Message::new_system_message(prefix);
27        let template = HumanMessagePromptTemplate::new(template_jinja2!("{{input}}", "input"));
28
29        message_formatter![
30            fmt_message!(message),
31            fmt_template!(template),
32            fmt_placeholder!("chat_history"),
33            fmt_placeholder!("agent_scratchpad")
34        ]
35    }
36
37    pub fn construct_scratchpad(
38        &self,
39        intermediate_steps: &[impl IntermediateStep],
40    ) -> Result<Vec<Message>, AgentError> {
41        let mut thoughts: Vec<Message> = vec![];
42
43        for step in intermediate_steps {
44            step.append_to_conversation(&mut thoughts)?;
45        }
46
47        Ok(thoughts)
48    }
49
50    fn process_chunk_delta(
51        chunk: &serde_json::Value,
52        model_output: &mut String,
53        tool_call_acc: &mut ToolCallAccumulator,
54        has_tool_calls: &mut bool,
55    ) -> Vec<AgentEventChunk> {
56        let mut events = Vec::new();
57
58        let Some(choices) = chunk.get("choices").and_then(|c| c.as_array()) else {
59            return events;
60        };
61
62        let Some(choice) = choices.first() else {
63            return events;
64        };
65
66        let Some(delta) = choice.get("delta") else {
67            return events;
68        };
69
70        // Handle tool calls
71        if let Some(_tool_calls) = delta.get("tool_calls").and_then(|tc| tc.as_array()) {
72            *has_tool_calls = true;
73            let tool_call_chunk = tool_call_acc.accumulate(delta);
74            events.push(AgentEventChunk::Delta(DeltaEvent::Action(tool_call_chunk)));
75        } else if let Some(content) = delta.get("content").and_then(|c| c.as_str()) {
76            if !content.is_empty() {
77                model_output.push_str(content);
78                events.push(AgentEventChunk::Delta(DeltaEvent::Content(
79                    content.to_string(),
80                )));
81            }
82        }
83
84        // Handle finish reason
85        if let Some(finish_reason) = choice.get("finish_reason").and_then(|f| f.as_str()) {
86            let final_event = Self::handle_finish_reason(
87                finish_reason,
88                tool_call_acc,
89                *has_tool_calls,
90                model_output,
91            );
92            events.push(AgentEventChunk::Final(final_event));
93        }
94
95        events
96    }
97
98    fn handle_finish_reason(
99        finish_reason: &str,
100        tool_call_acc: &mut ToolCallAccumulator,
101        has_tool_calls: bool,
102        model_output: &str,
103    ) -> AgentEvent {
104        match finish_reason {
105            "tool_calls" => {
106                let action = tool_call_acc.take_action();
107                AgentEvent::Action(vec![action])
108            }
109            "stop" => {
110                if has_tool_calls {
111                    let action = tool_call_acc.take_action();
112                    AgentEvent::Action(vec![action])
113                } else {
114                    AgentEvent::Finish(AgentFinish {
115                        output: model_output.to_string(),
116                    })
117                }
118            }
119            _ => AgentEvent::Finish(AgentFinish {
120                output: model_output.to_string(),
121            }),
122        }
123    }
124}
125
126#[async_trait]
127impl Agent for OpenAIMcpAgent {
128    async fn plan(
129        &self,
130        intermediate_steps: &[(AgentAction, String)],
131        inputs: PromptArgs,
132    ) -> Result<AgentEvent, AgentError> {
133        let mut inputs = inputs.clone();
134        let scratchpad = self.construct_scratchpad(intermediate_steps)?;
135        inputs.insert("agent_scratchpad".to_string(), json!(scratchpad));
136        let output = self.chain.call(inputs).await?.generation;
137
138        match serde_json::from_str::<Vec<FunctionCallResponse>>(&output) {
139            Ok(tools) => {
140                let mut actions = Vec::with_capacity(tools.len());
141                for tool in tools {
142                    //Log tools will be send as log
143                    let log = LogTools {
144                        tool_id: tool.id,
145                        tools: output.clone(),
146                    };
147                    actions.push(AgentAction {
148                        tool: tool.function.name,
149                        tool_input: tool.function.arguments,
150                        log: serde_json::to_string(&log)?,
151                    });
152                }
153                return Ok(AgentEvent::Action(actions));
154            }
155            Err(_) => return Ok(AgentEvent::Finish(AgentFinish { output })),
156        }
157    }
158
159    fn get_tools(&self) -> Vec<Arc<dyn Tool>> {
160        self.tools.clone()
161    }
162}
163
164#[async_trait]
165impl AgentExt for OpenAIMcpAgent {
166    async fn plan_with_steps(
167        &self,
168        steps: &[impl IntermediateStep],
169        inputs: PromptArgs,
170    ) -> Result<AgentEvent, AgentError> {
171        let mut inputs = inputs.clone();
172        let scratchpad = self.construct_scratchpad(steps)?;
173        inputs.insert("agent_scratchpad".to_string(), json!(scratchpad));
174        let output = self.chain.call(inputs).await?.generation;
175
176        match serde_json::from_str::<Vec<FunctionCallResponse>>(&output) {
177            Ok(tools) => {
178                let mut actions = Vec::with_capacity(tools.len());
179                for tool in tools {
180                    // Log tools will be sent as log
181                    let log = LogTools {
182                        tool_id: tool.id,
183                        tools: output.clone(),
184                    };
185                    actions.push(AgentAction {
186                        tool: tool.function.name,
187                        tool_input: tool.function.arguments,
188                        log: serde_json::to_string(&log)?,
189                    });
190                }
191                return Ok(AgentEvent::Action(actions));
192            }
193            Err(_) => return Ok(AgentEvent::Finish(AgentFinish { output })),
194        }
195    }
196
197    async fn plan_stream(
198        &self,
199        steps: &[impl IntermediateStep],
200        inputs: PromptArgs,
201    ) -> Result<AgentStream, AgentError> {
202        use async_stream::stream;
203        use futures_util::StreamExt;
204
205        let mut inputs = inputs.clone();
206        let scratchpad = self.construct_scratchpad(steps)?;
207        inputs.insert("agent_scratchpad".to_string(), json!(scratchpad));
208
209        let mut chain_stream = self.chain.stream(inputs).await?;
210        let mut model_output = String::new();
211        let mut tool_call_acc = ToolCallAccumulator::new();
212        let mut has_tool_calls = false;
213
214        let s = stream! {
215            while let Some(chunk_result) = chain_stream.next().await {
216                let chunk = match chunk_result {
217                    Ok(chunk) => chunk,
218                    Err(e) => {
219                        yield Err(e);
220                        return;
221                    }
222                };
223
224                // Process chunk and get events
225                let events = Self::process_chunk_delta(&chunk.value, &mut model_output, &mut tool_call_acc, &mut has_tool_calls);
226
227                for event in events {
228                    // Check if this is a final event that should end the stream
229                    let is_final = matches!(event, AgentEventChunk::Final(_));
230                    yield Ok(event);
231
232                    if is_final {
233                        return;
234                    }
235                }
236            }
237
238            match has_tool_calls {
239                true => {
240                    let action = tool_call_acc.take_action();
241                    yield Ok(AgentEventChunk::Final(AgentEvent::Action(vec![action])))
242                },
243                false => yield Ok(AgentEventChunk::Final(AgentEvent::Finish(AgentFinish { output: model_output })))
244            }
245        };
246
247        Ok(Box::pin(s) as AgentStream)
248    }
249}
250
251struct ToolCallAccumulator {
252    name: Option<String>,
253    args: String,
254    id: Option<String>,
255}
256
257impl ToolCallAccumulator {
258    fn new() -> Self {
259        Self {
260            name: None,
261            args: String::new(),
262            id: None,
263        }
264    }
265
266    fn accumulate(&mut self, delta: &serde_json::Value) -> AgentAction {
267        let mut args_chunk = String::default();
268
269        if let Some(tool_call) = delta
270            .get("tool_calls")
271            .and_then(|v| v.as_array())
272            .and_then(|v| v.first())
273        {
274            if let Some(function) = tool_call.get("function") {
275                if let Some(name) = function.get("name").and_then(|n| n.as_str()) {
276                    self.name = Some(name.to_string());
277                }
278                if let Some(args) = function.get("arguments").and_then(|a| a.as_str()) {
279                    self.args.push_str(args);
280                    args_chunk = args.to_string();
281                }
282            }
283            if let Some(id) = tool_call.get("id").and_then(|i| i.as_str()) {
284                if !id.is_empty() {
285                    self.id = Some(id.to_string());
286                }
287            }
288        };
289
290        self.to_action_chunk(args_chunk)
291    }
292
293    fn take_action(&mut self) -> AgentAction {
294        let args = std::mem::take(&mut self.args);
295        self.to_action_chunk(args)
296    }
297
298    fn to_action_chunk(&self, args_chunk: String) -> AgentAction {
299        let function_call_response = serde_json::json!({
300            "id": self.id.clone(),
301            "type": "function",
302            "function": {
303                "name": self.name.clone(),
304                "arguments": args_chunk
305            }
306        });
307
308        // Construct tool call array (consistent with non-streaming method)
309        let tools_array = serde_json::json!([function_call_response]);
310        let tools_output = serde_json::to_string(&tools_array).unwrap_or_else(|_| {
311            "[{{\"error\": \"Failed to serialize function call\"}}]".to_string()
312        });
313
314        let log_tools = LogTools {
315            tool_id: self.id.clone().unwrap_or_default(),
316            tools: tools_output,
317        };
318
319        let log_str = serde_json::to_string(&log_tools).unwrap_or_else(|_| {
320            // If serialization fails, return a simple format
321            format!(
322                "{{\"tool_id\": \"{}\", \"tools\": \"[]\"}}",
323                self.id.clone().unwrap_or_default()
324            )
325        });
326
327        AgentAction {
328            tool: self.name.clone().unwrap_or_default(),
329            tool_input: args_chunk,
330            log: log_str,
331        }
332    }
333}