feat(examples): Add func call support (#168)

Some works about simple-chat.
1. fix some bugs.
2. add func call optional.

Signed-off-by: jokemanfire <hu.dingyang@zte.com.cn>
This commit is contained in:
jokemanfire 2025-05-12 13:52:57 +08:00 • committed by GitHub
commit 6de444c62f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -4,10 +4,11 @@ use std::{
};
use anyhow::Result;
use serde_json;
use crate::{
client::ChatClient,
model::{CompletionRequest, Message},
model::{CompletionRequest, Message, ToolFunction},
tool::{Tool as ToolTrait, ToolSet},
};
@ -36,7 +37,84 @@ impl ChatSession {
self.tool_set.tools()
}
pub async fn chat(&mut self) -> Result<()> {
pub async fn analyze_tool_call(&mut self, response: &Message) {
let mut tool_calls_func = Vec::new();
if let Some(tool_calls) = response.tool_calls.as_ref() {
for tool_call in tool_calls {
if tool_call._type == "function" {
tool_calls_func.push(tool_call.function.clone());
}
}
} else {
// check if message contains tool call
if response.content.contains("Tool:") {
let lines: Vec<&str> = response.content.split('\n').collect();
// simple parse tool call
let mut tool_name = None;
let mut args_text = Vec::new();
let mut parsing_args = false;
for line in lines {
if line.starts_with("Tool:") {
tool_name = line.strip_prefix("Tool:").map(|s| s.trim().to_string());
parsing_args = false;
} else if line.starts_with("Inputs:") {
parsing_args = true;
} else if parsing_args {
args_text.push(line.trim());
}
}
if let Some(name) = tool_name {
tool_calls_func.push(ToolFunction {
name,
arguments: args_text.join("\n"),
});
}
}
}
// call tool
for tool_call in tool_calls_func {
println!("tool call: {:?}", tool_call);
let tool = self.tool_set.get_tool(&tool_call.name);
if let Some(tool) = tool {
// call tool
let args = serde_json::from_str::<serde_json::Value>(&tool_call.arguments)
.unwrap_or_default();
match tool.call(args).await {
Ok(result) => {
if result.is_error.is_some_and(|b| b) {
self.messages
.push(Message::user("tool call failed, mcp call error"));
} else {
result.content.iter().for_each(|content| {
if let Some(content_text) = content.as_text() {
let json_result = serde_json::from_str::<serde_json::Value>(
&content_text.text,
)
.unwrap_or_default();
let pretty_result =
serde_json::to_string_pretty(&json_result).unwrap();
println!("call tool result: {}", pretty_result);
self.messages.push(Message::user(format!(
"call tool result: {}",
pretty_result
)));
}
});
}
}
Err(e) => {
println!("tool call failed: {}", e);
self.messages
.push(Message::user(format!("tool call failed: {}", e)));
}
}
} else {
println!("tool not found: {}", tool_call.name);
}
}
}
pub async fn chat(&mut self, support_tool: bool) -> Result<()> {
println!("welcome to use simple chat client, use 'exit' to quit");
loop {
@ -56,20 +134,23 @@ impl ChatSession {
}
self.messages.push(Message::user(&input));
// prepare tool list
let tools = self.tool_set.tools();
let tool_definitions = if !tools.is_empty() {
Some(
tools
.iter()
.map(|tool| crate::model::Tool {
name: tool.name(),
description: tool.description(),
parameters: tool.parameters(),
})
.collect(),
)
let tool_definitions = if support_tool {
// prepare tool list
let tools = self.tool_set.tools();
if !tools.is_empty() {
Some(
tools
.iter()
.map(|tool| crate::model::Tool {
name: tool.name(),
description: tool.description(),
parameters: tool.parameters(),
})
.collect(),
)
} else {
None
}
} else {
None
};
@ -84,65 +165,11 @@ impl ChatSession {
// send request
let response = self.client.complete(request).await?;
if let Some(choice) = response.choices.first() {
println!("AI: {}", choice.message.content);
self.messages.push(choice.message.clone());
// check if message contains tool call
if choice.message.content.contains("Tool:") {
let lines: Vec<&str> = choice.message.content.split('\n').collect();
// simple parse tool call
let mut tool_name = None;
let mut args_text = Vec::new();
let mut parsing_args = false;
for line in lines {
if line.starts_with("Tool:") {
tool_name = line.strip_prefix("Tool:").map(|s| s.trim().to_string());
parsing_args = false;
} else if line.starts_with("Inputs:") {
parsing_args = true;
} else if parsing_args {
args_text.push(line.trim());
}
}
if let Some(name) = tool_name {
if let Some(tool) = self.tool_set.get_tool(&name) {
println!("calling tool: {}", name);
// simple handle args
let args_str = args_text.join("\n");
let args = match serde_json::from_str(&args_str) {
Ok(v) => v,
Err(_) => {
// try to handle args as string
serde_json::Value::String(args_str)
}
};
// call tool
match tool.call(args).await {
Ok(result) => {
println!("tool result: {}", result);
// add tool result to dialog
self.messages.push(Message::user(result));
}
Err(e) => {
println!("tool call failed: {}", e);
self.messages
.push(Message::user(format!("tool call failed: {}", e)));
}
}
} else {
println!("tool not found: {}", name);
}
}
}
}
// get choice
let choice = response.choices.first().unwrap();
println!("AI > {}", choice.message.content);
// analyze tool call
self.analyze_tool_call(&choice.message).await;
}
Ok(())