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 const MAX_STEPS: usize = 5;
44 const SUMMARY_THRESHOLD: usize = 10;
45
46 if intermediate_steps.len() > SUMMARY_THRESHOLD {
47 let summary_msg = self.create_summary_message(
48 &intermediate_steps[..intermediate_steps.len() - MAX_STEPS],
49 )?;
50 thoughts.push(summary_msg);
51
52 for step in &intermediate_steps[intermediate_steps.len() - MAX_STEPS..] {
53 step.append_to_conversation(&mut thoughts)?;
54 }
55 } else {
56 for step in intermediate_steps {
57 step.append_to_conversation(&mut thoughts)?;
58 }
59 }
60
61 Ok(thoughts)
62 }
63
64 fn create_summary_message(
65 &self,
66 old_steps: &[impl IntermediateStep],
67 ) -> Result<Message, AgentError> {
68 let summary = format!(
69 "Previous {} steps summary: [Summarized execution history with {} actions completed]",
70 old_steps.len(),
71 old_steps.len()
72 );
73
74 Ok(Message::new_system_message(&summary))
75 }
76
77 fn process_chunk_delta(
78 chunk: &serde_json::Value,
79 model_output: &mut String,
80 tool_call_acc: &mut ToolCallAccumulator,
81 has_tool_calls: &mut bool,
82 ) -> Vec<AgentEventChunk> {
83 let mut events = Vec::new();
84
85 let Some(choices) = chunk.get("choices").and_then(|c| c.as_array()) else {
86 return events;
87 };
88
89 let Some(choice) = choices.first() else {
90 return events;
91 };
92
93 let Some(delta) = choice.get("delta") else {
94 return events;
95 };
96
97 if let Some(content) = delta.get("content").and_then(|c| c.as_str()) {
99 if !content.is_empty() {
100 model_output.push_str(content);
101 events.push(AgentEventChunk::Delta(DeltaEvent::Content(
102 content.to_string(),
103 )));
104 }
105 } else if let Some(_tool_calls) = delta.get("tool_calls").and_then(|tc| tc.as_array()) {
106 *has_tool_calls = true;
107 let tool_call_chunk = tool_call_acc.accumulate(delta);
108 events.push(AgentEventChunk::Delta(DeltaEvent::Action(tool_call_chunk)));
109 }
110
111 if let Some(finish_reason) = choice.get("finish_reason").and_then(|f| f.as_str()) {
113 let final_event = Self::handle_finish_reason(
114 finish_reason,
115 tool_call_acc,
116 *has_tool_calls,
117 model_output,
118 );
119 events.push(AgentEventChunk::Final(final_event));
120 }
121
122 events
123 }
124
125 fn handle_finish_reason(
126 finish_reason: &str,
127 tool_call_acc: &mut ToolCallAccumulator,
128 has_tool_calls: bool,
129 model_output: &str,
130 ) -> AgentEvent {
131 match finish_reason {
132 "tool_calls" => {
133 let action = tool_call_acc.take_action();
134 AgentEvent::Action(vec![action])
135 }
136 "stop" => {
137 if has_tool_calls {
138 let action = tool_call_acc.take_action();
139 AgentEvent::Action(vec![action])
140 } else {
141 AgentEvent::Finish(AgentFinish {
142 output: model_output.to_string(),
143 })
144 }
145 }
146 _ => AgentEvent::Finish(AgentFinish {
147 output: model_output.to_string(),
148 }),
149 }
150 }
151}
152
153#[async_trait]
154impl Agent for OpenAIMcpAgent {
155 async fn plan(
156 &self,
157 intermediate_steps: &[(AgentAction, String)],
158 inputs: PromptArgs,
159 ) -> Result<AgentEvent, AgentError> {
160 let mut inputs = inputs.clone();
161 let scratchpad = self.construct_scratchpad(intermediate_steps)?;
162 inputs.insert("agent_scratchpad".to_string(), json!(scratchpad));
163 let output = self.chain.call(inputs).await?.generation;
164
165 match serde_json::from_str::<Vec<FunctionCallResponse>>(&output) {
166 Ok(tools) => {
167 let mut actions = Vec::with_capacity(tools.len());
168 for tool in tools {
169 let log = LogTools {
171 tool_id: tool.id,
172 tools: output.clone(),
173 };
174 actions.push(AgentAction {
175 tool: tool.function.name,
176 tool_input: tool.function.arguments,
177 log: serde_json::to_string(&log)?,
178 });
179 }
180 return Ok(AgentEvent::Action(actions));
181 }
182 Err(_) => return Ok(AgentEvent::Finish(AgentFinish { output })),
183 }
184 }
185
186 fn get_tools(&self) -> Vec<Arc<dyn Tool>> {
187 self.tools.clone()
188 }
189}
190
191#[async_trait]
192impl AgentExt for OpenAIMcpAgent {
193 async fn plan_with_steps(
194 &self,
195 steps: &[impl IntermediateStep],
196 inputs: PromptArgs,
197 ) -> Result<AgentEvent, AgentError> {
198 let mut inputs = inputs.clone();
199 let scratchpad = self.construct_scratchpad(steps)?;
200 inputs.insert("agent_scratchpad".to_string(), json!(scratchpad));
201 let output = self.chain.call(inputs).await?.generation;
202
203 match serde_json::from_str::<Vec<FunctionCallResponse>>(&output) {
204 Ok(tools) => {
205 let mut actions = Vec::with_capacity(tools.len());
206 for tool in tools {
207 let log = LogTools {
209 tool_id: tool.id,
210 tools: output.clone(),
211 };
212 actions.push(AgentAction {
213 tool: tool.function.name,
214 tool_input: tool.function.arguments,
215 log: serde_json::to_string(&log)?,
216 });
217 }
218 return Ok(AgentEvent::Action(actions));
219 }
220 Err(_) => return Ok(AgentEvent::Finish(AgentFinish { output })),
221 }
222 }
223
224 async fn plan_stream(
225 &self,
226 steps: &[impl IntermediateStep],
227 inputs: PromptArgs,
228 ) -> Result<AgentStream, AgentError> {
229 use async_stream::stream;
230 use futures_util::StreamExt;
231
232 let mut inputs = inputs.clone();
233 let scratchpad = self.construct_scratchpad(steps)?;
234 inputs.insert("agent_scratchpad".to_string(), json!(scratchpad));
235
236 let mut chain_stream = self.chain.stream(inputs).await?;
237 let mut model_output = String::new();
238 let mut tool_call_acc = ToolCallAccumulator::new();
239 let mut has_tool_calls = false;
240
241 let s = stream! {
242 while let Some(chunk_result) = chain_stream.next().await {
243 let chunk = match chunk_result {
244 Ok(chunk) => chunk,
245 Err(e) => {
246 yield Err(e);
247 return;
248 }
249 };
250
251 let events = Self::process_chunk_delta(&chunk.value, &mut model_output, &mut tool_call_acc, &mut has_tool_calls);
253
254 for event in events {
255 let is_final = matches!(event, AgentEventChunk::Final(_));
257 yield Ok(event);
258
259 if is_final {
260 return;
261 }
262 }
263 }
264
265 match has_tool_calls {
266 true => {
267 let action = tool_call_acc.take_action();
268 yield Ok(AgentEventChunk::Final(AgentEvent::Action(vec![action])))
269 },
270 false => yield Ok(AgentEventChunk::Final(AgentEvent::Finish(AgentFinish { output: model_output })))
271 }
272 };
273
274 Ok(Box::pin(s) as AgentStream)
275 }
276}
277
278struct ToolCallAccumulator {
279 name: Option<String>,
280 args: String,
281 id: Option<String>,
282}
283
284impl ToolCallAccumulator {
285 fn new() -> Self {
286 Self {
287 name: None,
288 args: String::new(),
289 id: None,
290 }
291 }
292
293 fn accumulate(&mut self, delta: &serde_json::Value) -> AgentAction {
294 let mut args_chunk = String::default();
295
296 if let Some(tool_call) = delta
297 .get("tool_calls")
298 .and_then(|v| v.as_array())
299 .and_then(|v| v.first())
300 {
301 if let Some(function) = tool_call.get("function") {
302 if let Some(name) = function.get("name").and_then(|n| n.as_str()) {
303 self.name = Some(name.to_string());
304 }
305 if let Some(args) = function.get("arguments").and_then(|a| a.as_str()) {
306 self.args.push_str(args);
307 args_chunk = args.to_string();
308 }
309 }
310 if let Some(id) = tool_call.get("id").and_then(|i| i.as_str()) {
311 if !id.is_empty() {
312 self.id = Some(id.to_string());
313 }
314 }
315 };
316
317 self.to_action_chunk(args_chunk)
318 }
319
320 fn take_action(&mut self) -> AgentAction {
321 let args = std::mem::take(&mut self.args);
322 self.to_action_chunk(args)
323 }
324
325 fn to_action_chunk(&self, args_chunk: String) -> AgentAction {
326 let processed_args = if args_chunk.trim().is_empty() {
327 "{}".to_string()
328 } else {
329 args_chunk
330 };
331
332 let function_call_response = serde_json::json!({
333 "id": self.id.clone(),
334 "type": "function",
335 "function": {
336 "name": self.name.clone(),
337 "arguments": processed_args
338 }
339 });
340
341 let tools_array = serde_json::json!([function_call_response]);
343 let tools_output = serde_json::to_string(&tools_array).unwrap_or_else(|_| {
344 "[{{\"error\": \"Failed to serialize function call\"}}]".to_string()
345 });
346
347 let log_tools = LogTools {
348 tool_id: self.id.clone().unwrap_or_default(),
349 tools: tools_output,
350 };
351
352 let log_str = serde_json::to_string(&log_tools).unwrap_or_else(|_| {
353 format!(
355 "{{\"tool_id\": \"{}\", \"tools\": \"[]\"}}",
356 self.id.clone().unwrap_or_default()
357 )
358 });
359
360 AgentAction {
361 tool: self.name.clone().unwrap_or_default(),
362 tool_input: processed_args,
363 log: log_str,
364 }
365 }
366}