langchain_rust/agent/open_ai_tools/
agent.rs1use std::collections::HashMap;
2use std::sync::Arc;
3
4use async_trait::async_trait;
5use futures::StreamExt;
6use serde_json::json;
7
8use crate::{
9 agent::{
10 agent::{box_plan_events, PlanEvent, PlanEventStream},
11 Agent, AgentError,
12 },
13 chain::Chain,
14 fmt_message, fmt_placeholder, fmt_template,
15 language_models::{GenerateResult, LLMEvent, TokenUsage},
16 message_formatter,
17 prompt::{HumanMessagePromptTemplate, MessageFormatterStruct, PromptArgs, DEFAULT_IMAGES_KEY},
18 schemas::{
19 agent::{AgentAction, AgentEvent, AgentFinish, LogTools},
20 messages::Message,
21 ToolCall,
22 },
23 template_jinja2,
24 tools::Tool,
25};
26
27pub struct OpenAiToolAgent {
28 pub(crate) chain: Box<dyn Chain>,
29 pub(crate) tools: Vec<Arc<dyn Tool>>,
30}
31
32impl OpenAiToolAgent {
33 pub fn create_prompt(prefix: &str) -> Result<MessageFormatterStruct, AgentError> {
34 let prompt = message_formatter![
35 fmt_message!(Message::new_system_message(prefix)),
36 fmt_placeholder!("chat_history"),
37 fmt_template!(
38 HumanMessagePromptTemplate::new(template_jinja2!("{{input}}", "input"))
39 .with_images(DEFAULT_IMAGES_KEY)
40 ),
41 fmt_placeholder!("agent_scratchpad")
42 ];
43
44 Ok(prompt)
45 }
46
47 fn construct_scratchpad(
48 &self,
49 intermediate_steps: &[(AgentAction, String)],
50 ) -> Result<Vec<Message>, AgentError> {
51 let mut thoughts: Vec<Message> = Vec::new();
52
53 let mut tools_ai_message_seen: HashMap<String, ()> = HashMap::default();
54 for (action, observation) in intermediate_steps {
55 let LogTools { tool_id, tools } = serde_json::from_str(&action.log)?;
58 let tools_vec: Vec<ToolCall> = serde_json::from_str(&tools)?;
59
60 if tools_ai_message_seen.insert(tools, ()).is_none() {
64 thoughts.push(Message::new_ai_message_with_tool_calls("", &tools_vec));
65 }
66
67 thoughts.push(Message::new_tool_message(observation, tool_id));
70 }
71
72 Ok(thoughts)
73 }
74}
75
76#[async_trait]
77impl Agent for OpenAiToolAgent {
78 async fn plan(
79 &self,
80 intermediate_steps: &[(AgentAction, String)],
81 inputs: PromptArgs,
82 ) -> Result<AgentEvent, AgentError> {
83 Ok(self.plan_with_usage(intermediate_steps, inputs).await?.0)
84 }
85
86 async fn plan_with_usage(
87 &self,
88 intermediate_steps: &[(AgentAction, String)],
89 inputs: PromptArgs,
90 ) -> Result<(AgentEvent, Option<TokenUsage>), AgentError> {
91 let inputs = self.plan_inputs(intermediate_steps, inputs)?;
92 let mut result = self.chain.call(inputs).await?;
93 let usage = result.tokens.take();
94 Ok((Self::event_from_result(result)?, usage))
95 }
96
97 fn plan_stream<'a>(
100 &'a self,
101 intermediate_steps: &'a [(AgentAction, String)],
102 inputs: PromptArgs,
103 ) -> PlanEventStream<'a> {
104 box_plan_events(async_stream::try_stream! {
105 let inputs = self.plan_inputs(intermediate_steps, inputs)?;
106 let mut events = self.chain.call_stream(inputs);
107 let mut held = String::new();
111 let mut hold: Option<bool> = None;
112 while let Some(event) = events.next().await {
113 match event? {
114 LLMEvent::Text(text) => {
115 if hold == Some(false) {
116 yield PlanEvent::Text(text);
117 continue;
118 }
119 held.push_str(&text);
120 if let Some(first) = held.trim_start().chars().next() {
121 hold = Some(first == '[');
122 if first != '[' {
123 yield PlanEvent::Text(std::mem::take(&mut held));
124 }
125 }
126 }
127 LLMEvent::Done(mut result) => {
128 let usage = result.tokens.take();
129 let event = Self::event_from_result(result)?;
130 if matches!(event, AgentEvent::Finish(_)) && !held.is_empty() {
131 yield PlanEvent::Text(held);
132 }
133 if let Some(usage) = usage {
134 yield PlanEvent::Usage(usage);
135 }
136 yield PlanEvent::Done(event);
137 return;
138 }
139 }
140 }
141 Err(AgentError::OtherError("LLM stream ended without a result".into()))?;
142 })
143 }
144
145 fn get_tools(&self) -> Vec<Arc<dyn Tool>> {
146 self.tools.clone()
147 }
148}
149
150impl OpenAiToolAgent {
151 fn plan_inputs(
152 &self,
153 intermediate_steps: &[(AgentAction, String)],
154 mut inputs: PromptArgs,
155 ) -> Result<PromptArgs, AgentError> {
156 let scratchpad = self.construct_scratchpad(intermediate_steps)?;
157 inputs.insert("agent_scratchpad".to_string(), json!(scratchpad));
158 Ok(inputs)
159 }
160
161 fn event_from_result(result: GenerateResult) -> Result<AgentEvent, AgentError> {
162 let calls = if result.has_tool_calls() {
165 Some(result.tool_calls)
166 } else {
167 ToolCall::parse_list(&result.generation).filter(|c| !c.is_empty())
168 };
169 let Some(calls) = calls else {
170 return Ok(AgentEvent::Finish(AgentFinish {
171 output: result.generation,
172 }));
173 };
174
175 let tools = ToolCall::list_to_string(&calls);
177 let mut actions: Vec<AgentAction> = Vec::new();
178 for call in calls {
179 let log = LogTools {
180 tool_id: call.id,
181 tools: tools.clone(),
182 };
183 actions.push(AgentAction {
184 tool: call.name,
185 tool_input: call.arguments,
186 log: serde_json::to_string(&log)?, });
188 }
189 Ok(AgentEvent::Action(actions))
190 }
191}