lc_agents/function_calling/
agent.rs1use crate::{AgentAction, AgentError, AgentFinish, AgentOutput, AgentStep, BaseAgent, ToolInput};
8use async_trait::async_trait;
9use lc_core::language_models::{BaseChatModel, LLMResult};
10use lc_core::tools::{to_tool_definition, BaseTool, ToolCall, ToolDefinition};
11use lc_providers::ProviderError;
12use lc_schema::Message;
13use std::collections::HashMap;
14use std::sync::Arc;
15
16pub struct FunctionCallingAgent {
22 llm: Arc<dyn BaseChatModel<Error = ProviderError> + Send + Sync>,
24
25 tools: Vec<Arc<dyn BaseTool>>,
27
28 system_prompt: Option<String>,
30}
31
32impl FunctionCallingAgent {
33 pub fn new<L>(llm: L, tools: Vec<Arc<dyn BaseTool>>, system_prompt: Option<String>) -> Self
44 where
45 L: BaseChatModel + Send + Sync + 'static,
46 L::Error: Into<ProviderError>,
47 {
48 let wrapped = lc_providers::ChatModelWrapper::new(llm);
50
51 let tool_definitions: Vec<ToolDefinition> = tools
52 .iter()
53 .map(|t| to_tool_definition(t.as_ref()))
54 .collect();
55
56 let llm_with_tools: Arc<dyn BaseChatModel<Error = ProviderError> + Send + Sync> = wrapped
59 .bind_tools(tool_definitions)
60 .map(|boxed| {
61 Arc::from(boxed) as Arc<dyn BaseChatModel<Error = ProviderError> + Send + Sync>
62 })
63 .unwrap_or_else(|| Arc::new(wrapped));
64
65 Self {
66 llm: llm_with_tools,
67 tools,
68 system_prompt,
69 }
70 }
71
72 pub fn from_arc(
76 llm: Arc<dyn BaseChatModel<Error = ProviderError> + Send + Sync>,
77 tools: Vec<Arc<dyn BaseTool>>,
78 system_prompt: Option<String>,
79 ) -> Self {
80 let tool_definitions: Vec<ToolDefinition> = tools
81 .iter()
82 .map(|t| to_tool_definition(t.as_ref()))
83 .collect();
84
85 let llm_with_tools = llm
86 .bind_tools(tool_definitions)
87 .map(|boxed| {
88 Arc::from(boxed) as Arc<dyn BaseChatModel<Error = ProviderError> + Send + Sync>
89 })
90 .unwrap_or(llm);
91
92 Self {
93 llm: llm_with_tools,
94 tools,
95 system_prompt,
96 }
97 }
98
99 pub fn tools_count(&self) -> usize {
101 self.tools.len()
102 }
103
104 pub fn system_prompt(&self) -> Option<&str> {
106 self.system_prompt.as_deref()
107 }
108
109 fn build_messages(
111 &self,
112 inputs: &HashMap<String, String>,
113 intermediate_steps: &[AgentStep],
114 ) -> Vec<Message> {
115 let mut messages = Vec::new();
116
117 let system_content = self
118 .system_prompt
119 .clone()
120 .unwrap_or_else(|| "你是一个助手,可以使用工具回答问题。".to_string());
121 messages.push(Message::system(&system_content));
122
123 let default_input = String::new();
124 let input = inputs.get("input").unwrap_or(&default_input);
125 messages.push(Message::human(input));
126
127 for step in intermediate_steps {
128 let tool_call = ToolCall::new(
129 &step.action.log,
130 &step.action.tool,
131 match &step.action.tool_input {
132 ToolInput::String { value: s } => s.clone(),
133 ToolInput::Object { value: v } => {
134 serde_json::to_string(v).unwrap_or_else(|_| v.to_string())
135 }
136 },
137 );
138 messages.push(Message::ai_with_tool_calls("", vec![tool_call]));
139 messages.push(Message::tool(&step.action.log, &step.observation));
140 }
141
142 messages
143 }
144}
145
146#[async_trait]
147impl BaseAgent for FunctionCallingAgent {
148 async fn plan(
149 &self,
150 intermediate_steps: &[AgentStep],
151 inputs: &HashMap<String, String>,
152 ) -> Result<AgentOutput, AgentError> {
153 let messages = self.build_messages(inputs, intermediate_steps);
154
155 let result: LLMResult = self
156 .llm
157 .chat(messages, None)
158 .await
159 .map_err(|e| AgentError::Other(format!("LLM 调用失败: {}", e)))?;
160
161 if let Some(tool_calls) = &result.tool_calls {
162 if !tool_calls.is_empty() {
163 let actions: Vec<AgentAction> = tool_calls
164 .iter()
165 .map(|call| {
166 let tool_input = match serde_json::from_str::<serde_json::Value>(
167 &call.function.arguments,
168 ) {
169 Ok(v) => ToolInput::Object { value: v },
170 Err(_) => ToolInput::String {
171 value: call.function.arguments.clone(),
172 },
173 };
174
175 AgentAction {
176 tool: call.function.name.clone(),
177 tool_input,
178 log: call.id.clone(),
179 }
180 })
181 .collect();
182
183 if actions.len() == 1 {
184 return Ok(AgentOutput::Action(actions.into_iter().next().unwrap()));
185 } else {
186 return Ok(AgentOutput::Actions(actions));
187 }
188 }
189 }
190
191 Ok(AgentOutput::Finish(AgentFinish::new(
192 result.content.clone(),
193 String::new(),
194 )))
195 }
196
197 fn get_allowed_tools(&self) -> Option<Vec<&str>> {
198 Some(self.tools.iter().map(|t| t.name()).collect())
199 }
200}
201
202impl std::fmt::Debug for FunctionCallingAgent {
203 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
204 f.debug_struct("FunctionCallingAgent")
205 .field("tools_count", &self.tools.len())
206 .field("system_prompt", &self.system_prompt)
207 .finish()
208 }
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214 use lc_providers::{OpenAIChat, OpenAIConfig};
215 use lc_tools::Calculator;
216
217 fn create_test_config() -> OpenAIConfig {
218 OpenAIConfig::new("test_key").with_base_url("http://localhost:8080/v1")
219 }
220
221 #[test]
222 fn test_function_calling_agent_creation() {
223 let config = create_test_config();
224 let llm = OpenAIChat::new(config);
225 let tools: Vec<Arc<dyn BaseTool>> = vec![Arc::new(Calculator::new())];
226
227 let agent = FunctionCallingAgent::new(llm, tools, None);
228 assert_eq!(agent.tools.len(), 1);
229 }
230
231 #[test]
232 fn test_get_allowed_tools() {
233 let config = create_test_config();
234 let llm = OpenAIChat::new(config);
235 let tools: Vec<Arc<dyn BaseTool>> = vec![Arc::new(Calculator::new())];
236
237 let agent = FunctionCallingAgent::new(llm, tools, None);
238
239 assert_eq!(agent.tools.len(), 1);
240 assert!(agent.system_prompt.is_none());
241 }
242
243 #[test]
244 fn test_new_with_system_prompt() {
245 let config = create_test_config();
246 let llm = OpenAIChat::new(config);
247 let tools: Vec<Arc<dyn BaseTool>> = vec![Arc::new(Calculator::new())];
248
249 let agent = FunctionCallingAgent::new(llm, tools, Some("你是一个数学助手".to_string()));
250
251 assert_eq!(agent.system_prompt, Some("你是一个数学助手".to_string()));
252 }
253
254 #[test]
255 fn test_build_messages_empty() {
256 let config = create_test_config();
257 let llm = OpenAIChat::new(config);
258 let tools: Vec<Arc<dyn BaseTool>> = vec![Arc::new(Calculator::new())];
259
260 let agent = FunctionCallingAgent::new(llm, tools, None);
261
262 let mut inputs = HashMap::new();
263 inputs.insert("input".to_string(), "计算 2 + 3".to_string());
264
265 let messages = agent.build_messages(&inputs, &[]);
266
267 assert_eq!(messages.len(), 2);
268 assert_eq!(messages[0].content, "你是一个助手,可以使用工具回答问题。");
269 assert_eq!(messages[1].content, "计算 2 + 3");
270 }
271
272 #[test]
273 fn test_build_messages_with_history() {
274 let config = create_test_config();
275 let llm = OpenAIChat::new(config);
276 let tools: Vec<Arc<dyn BaseTool>> = vec![Arc::new(Calculator::new())];
277
278 let agent = FunctionCallingAgent::new(llm, tools, None);
279
280 let mut inputs = HashMap::new();
281 inputs.insert("input".to_string(), "继续计算".to_string());
282
283 let steps = vec![AgentStep::new(
284 AgentAction {
285 tool: "calculator".to_string(),
286 tool_input: ToolInput::String {
287 value: "2 + 3".to_string(),
288 },
289 log: "call_123".to_string(),
290 },
291 "5".to_string(),
292 )];
293
294 let messages = agent.build_messages(&inputs, &steps);
295
296 assert_eq!(messages.len(), 4);
297 assert!(messages[2].has_tool_calls());
298 }
299
300 #[test]
301 fn test_from_arc_creation() {
302 let config = create_test_config();
303 let llm = OpenAIChat::new(config);
304 let llm_arc: Arc<dyn BaseChatModel<Error = ProviderError> + Send + Sync> =
305 lc_providers::wrap_chat_model(llm);
306 let tools: Vec<Arc<dyn BaseTool>> = vec![Arc::new(Calculator::new())];
307
308 let agent = FunctionCallingAgent::from_arc(llm_arc, tools, Some("test".into()));
309 assert_eq!(agent.tools.len(), 1);
310 assert_eq!(agent.system_prompt, Some("test".to_string()));
311 }
312}