langchain_rust/agent/open_ai_tools/
agent.rs1use std::collections::HashMap;
2use std::sync::Arc;
3
4use async_trait::async_trait;
5use serde_json::json;
6
7use crate::{
8 agent::{Agent, AgentError},
9 chain::Chain,
10 fmt_message, fmt_placeholder, fmt_template, message_formatter,
11 prompt::{HumanMessagePromptTemplate, MessageFormatterStruct, PromptArgs},
12 schemas::{
13 agent::{AgentAction, AgentEvent, AgentFinish, LogTools},
14 messages::Message,
15 ToolCall,
16 },
17 template_jinja2,
18 tools::Tool,
19};
20
21pub struct OpenAiToolAgent {
22 pub(crate) chain: Box<dyn Chain>,
23 pub(crate) tools: Vec<Arc<dyn Tool>>,
24}
25
26impl OpenAiToolAgent {
27 pub fn create_prompt(prefix: &str) -> Result<MessageFormatterStruct, AgentError> {
28 let prompt = message_formatter![
29 fmt_message!(Message::new_system_message(prefix)),
30 fmt_placeholder!("chat_history"),
31 fmt_template!(HumanMessagePromptTemplate::new(template_jinja2!(
32 "{{input}}",
33 "input"
34 ))),
35 fmt_placeholder!("agent_scratchpad")
36 ];
37
38 Ok(prompt)
39 }
40
41 fn construct_scratchpad(
42 &self,
43 intermediate_steps: &[(AgentAction, String)],
44 ) -> Result<Vec<Message>, AgentError> {
45 let mut thoughts: Vec<Message> = Vec::new();
46
47 let mut tools_ai_message_seen: HashMap<String, ()> = HashMap::default();
48 for (action, observation) in intermediate_steps {
49 let LogTools { tool_id, tools } = serde_json::from_str(&action.log)?;
52 let tools_vec: Vec<ToolCall> = serde_json::from_str(&tools)?;
53
54 if tools_ai_message_seen.insert(tools, ()).is_none() {
58 thoughts.push(Message::new_ai_message_with_tool_calls("", &tools_vec));
59 }
60
61 thoughts.push(Message::new_tool_message(observation, tool_id));
64 }
65
66 Ok(thoughts)
67 }
68}
69
70#[async_trait]
71impl Agent for OpenAiToolAgent {
72 async fn plan(
73 &self,
74 intermediate_steps: &[(AgentAction, String)],
75 inputs: PromptArgs,
76 ) -> Result<AgentEvent, AgentError> {
77 let mut inputs = inputs.clone();
78 let scratchpad = self.construct_scratchpad(intermediate_steps)?;
79 inputs.insert("agent_scratchpad".to_string(), json!(scratchpad));
80 let result = self.chain.call(inputs).await?;
81 let calls = if result.has_tool_calls() {
84 Some(result.tool_calls)
85 } else {
86 ToolCall::parse_list(&result.generation).filter(|c| !c.is_empty())
87 };
88 let Some(calls) = calls else {
89 return Ok(AgentEvent::Finish(AgentFinish {
90 output: result.generation,
91 }));
92 };
93
94 let tools = ToolCall::list_to_string(&calls);
96 let mut actions: Vec<AgentAction> = Vec::new();
97 for call in calls {
98 let log = LogTools {
99 tool_id: call.id,
100 tools: tools.clone(),
101 };
102 actions.push(AgentAction {
103 tool: call.name,
104 tool_input: call.arguments,
105 log: serde_json::to_string(&log)?, });
107 }
108 Ok(AgentEvent::Action(actions))
109 }
110
111 fn get_tools(&self) -> Vec<Arc<dyn Tool>> {
112 self.tools.clone()
113 }
114}