本质上,大模型是一个文本到文本(word2word) 模型,并不存在函数调用能力. 我们一般说大模型的函数调用能力,其实指的是大模型在用户提供的问题与函数信息列表中,可以自主判断是否需要函数调用,并返回函数参数的能力.
但是就是这个能力,已经让 AI agent 应用大放异彩.有了这个能力,很多现实中不容易做到的已经变成可能,比如,一句话直接生成一个可运行的网站/游戏的那些AI编辑器. 又比如,我们可以通过大模型实现自然语言指令,转换为智能家居指令,从而达到智能控制硬件的目地.
在遵循指令方面,OpenAI 和 Claude 是这方面的佼佼者.在 deepseek v3/r1 未出现之前,国产的AI 大模型的指令能力非常的差,几乎是不可用状态,更不要说拿来做应用. 好在 deepseek v3/r1 出现之后,很多国产大模型的遵循指令能力已经大大地提高,或者或少,deepseek 功不可没.
为了演示大模型的函数调用能力,我这里以 qwen 作为例子(主要是它足够便宜,同时比较快). 如果是做生产应用,我还是推荐使用deepseek 做为首选,但是考虑到 deepseek 一直处于高负荷状态,最好有备选.
我们为什么需要函数调用?
一个很现实的例子,大模型无法知道你问的此时此刻是什么时间,什么天气,发生了什么事情. 原因是大模型的训练数据是有时效性的,哪怕现在是2025年,很多大模型的数据最新也可能最使用到了2023年.
因此,我们需要大模型”智能”地感知真实世界的信息数据.
函数调用的流程
函数调用的流程如下:
“mermaid graph TD A[用户提问] —> B{根据用户问题调用大模型} B —> C{工具信息} C{天气查询函数/当前时间函数} —> D{判断是否需要调用工具} D —是—> E[输出工具名称解析工具入参] D —否—> F[给出回复] E —> G{根据工具名称、入参调用工具} G —> H[输出工具调用结果] H —> I{根据工具结果、用户问题调用大模型} I —> J[返回大模型回复] J —> K[大模型回复] F —> K
我们要做的,就是实现上面这个流程.
## Rust 的实现
通义千问的官方 API 有两种风格,一种是 OpenAI 风格,一种是 DashScope 风格.
如果你想用 OpenAI 风格,我推荐使用这个库:
cargo add async-openai
如果使用 DashScope 风格,可以使用这个库:
cargo add async-dashscope
我这里使用 Dashscope 风格,以"现在是什么时间?" 作为例子,
我们需要实现一个获取时间的函数:
```rust
fn get_current_time() -> i64 {
let now = Utc::now();
now.to_string()
}
组装消息:
let mut messages = vec![MessageBuilder::default()
.role("user")
.content("现在是什么时间?")
.build()
.unwrap()];
为参数添加函数信息:
// add function call
let request = GenerationParamBuilder::default()
.model("qwen-turbo".to_string())
.input(InputBuilder::default().messages(messages.clone()).build()?)
.parameters(
ParametersBuilder::default()
.functions([FunctionCallBuilder::default()
.typ("function")
.function(
FunctionBuilder::default()
.name("get_current_time")
.description("return the current time")
.build()?,
)
.build()?])
// or call .tools(value)
.result_format("message")
.parallel_tool_calls(true)
.build()?,
)
.build()?;
带上用户消息和函数信息,我们第一次请求大模型:
let client = Client::default();
let response = client.generation().call(request).await?;
此时,大模型会自动判断是否需要调用函数工具,如果需要的话,会返回需要调用的函数名称以及参数,我们要基于此时的返回做相应的处理,我这里只做简单的处理:
let response_message = response.output.choices.unwrap().first().unwrap().message.clone();
// get function call arguments
if let Some(func_calls) = response_message.tool_calls {
for call in &func_calls {
if call.function.name == "get_current_time" {
let func_response = get_current_time();
messages.push(
MessageBuilder::default()
.role("user")
.content(func_response)
.build()?,
);
break;
}
}
拿到参数后,程序直接调用工具函数,返回数据,并附加到原来的信息中.然后,我们再次请求大模型,此时不要带上函数信息:
let request = GenerationParamBuilder::default()
.model("qwen-turbo".to_string())
.input(InputBuilder::default().messages(messages.clone()).build()?)
.build()?;
let response = client.generation().call(request).await?;
// 返回最终总结结果
dbg!(&response.output.text);
这样我们就得到了总结后的结果:
您提供的日期和时间是:**2025 年 6 月 5 日 16:00:00**。
现实中,不可能像上面的例子这样如此简单. 但是我们可以通过这个例子,扩展出实际可用的ai agent 应用.