1. 概述
大型语言模型(LLM)能帮我们获取海量信息,学习新知识,并基于互联网数据生成答案。我们还可以要求它处理输入数据并执行各种操作。但如果想让模型通过调用 API 来生成输出呢?
这时就需要用到函数调用(Function Calling)。函数调用让 LLM 能够突破纯文本能力的限制,与外部数据交互、执行计算或获取信息。
本文将深入探讨函数调用的原理,以及如何利用它将 LLM 与业务逻辑集成。我们将使用 Mistral AI API 作为模型提供商。
2. Mistral AI API
Mistral AI 专注于为开发者和企业提供开放可移植的生成式 AI 模型,既支持简单提示,也支持函数调用集成。
2.1. 获取 API 密钥
使用 Mistral API 的第一步是获取 API 密钥。访问 API 密钥管理控制台:
⚠️ 激活密钥前需配置计费信息,或使用试用资格(如果可用):
完成配置后,点击 "Create new key" 按钮即可获取 Mistral API 密钥。
2.2. 基础使用示例
先从简单提示开始。我们让 Mistral API 返回患者健康状态列表:
@Test
void givenHttpClient_whenSendTheRequestToChatAPI_thenShouldBeExpectedWordInResponse() throws IOException, InterruptedException {
String apiKey = System.getenv("MISTRAL_API_KEY");
String apiUrl = "https://api.mistral.ai/v1/chat/completions";
String requestBody = "{"
+ "\"model\": \"mistral-large-latest\","
+ "\"messages\": [{\"role\": \"user\", "
+ "\"content\": \"What the patient health statuses can be?\"}]"
+ "}";
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(apiUrl))
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.header("Authorization", "Bearer " + apiKey)
.POST(HttpRequest.BodyPublishers.ofString(requestBody))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
String responseBody = response.body();
logger.info("Model response: " + responseBody);
Assertions.assertThat(responseBody)
.containsIgnoringCase("healthy");
}
我们向 /chat/completions
接口发送 HTTP 请求,并将 API 密钥作为授权头。响应包含元数据和内容:
Model response: {"id":"585e3599275545c588cb0a502d1ab9e0","object":"chat.completion",
"created":1718308692,"model":"mistral-large-latest",
"choices":[{"index":0,"message":{"role":"assistant","content":"Patient health statuses can be
categorized in various ways, depending on the specific context or medical system being used.
However, some common health statuses include:
1.Healthy: The patient is in good health with no known medical issues.
...
10.Palliative: The patient is receiving care that is focused on relieving symptoms and improving quality of life, rather than curing the underlying disease.",
"tool_calls":null},"finish_reason":"stop","logprobs":null}],
"usage":{"prompt_tokens":12,"total_tokens":291,"completion_tokens":279}}
而函数调用的示例更复杂,需要大量准备工作。我们将在下一节深入探讨。
3. Spring AI 集成
来看几个使用 Mistral API 进行函数调用的示例。借助 Spring AI 框架,我们可以跳过繁琐的准备工作,让框架代劳。
3.1. 依赖配置
所需依赖位于 Spring Milestone 仓库。在 pom.xml
中添加:
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring milestones</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
然后添加 Mistral API 集成依赖:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mistral-ai-spring-boot-starter</artifactId>
<version>0.8.1</version>
</dependency>
3.2. 基础配置
将之前获取的 API 密钥添加到配置文件:
spring:
ai:
mistralai:
api-key: ${MISTRAL_AI_API_KEY}
chat:
options:
model: mistral-small-latest
至此,Mistral API 的基础配置已完成。
3.3. 单函数场景
我们的演示案例:根据患者 ID 返回健康状态。
首先创建患者 record:
public record Patient(String patientId) {
}
再创建健康状态 record:
public record HealthStatus(String status) {
}
接下来创建配置类:
@Configuration
public class MistralAIFunctionConfiguration {
public static final Map<Patient, HealthStatus> HEALTH_DATA = Map.of(
new Patient("P001"), new HealthStatus("Healthy"),
new Patient("P002"), new HealthStatus("Has cough"),
new Patient("P003"), new HealthStatus("Healthy"),
new Patient("P004"), new HealthStatus("Has increased blood pressure"),
new Patient("P005"), new HealthStatus("Healthy"));
@Bean
@Description("Get patient health status")
public Function<Patient, HealthStatus> retrievePatientHealthStatus() {
return (patient) -> new HealthStatus(HEALTH_DATA.get(patient).status());
}
}
这里我们定义了患者健康数据集,并创建了 retrievePatientHealthStatus()
方法,根据患者 ID 返回健康状态。
现在测试该函数:
@Import(MistralAIFunctionConfiguration.class)
@ExtendWith(SpringExtension.class)
@SpringBootTest
public class MistralAIFunctionCallingManualTest {
@Autowired
private MistralAiChatModel chatClient;
@Test
void givenMistralAiChatClient_whenAskChatAPIAboutPatientHealthStatus_thenExpectedHealthStatusIsPresentInResponse() {
var options = MistralAiChatOptions.builder()
.withFunction("retrievePatientHealthStatus")
.build();
ChatResponse paymentStatusResponse = chatClient.call(
new Prompt("What's the health status of the patient with id P004?", options));
String responseContent = paymentStatusResponse.getResult().getOutput().getContent();
logger.info(responseContent);
Assertions.assertThat(responseContent)
.containsIgnoringCase("has increased blood pressure");
}
}
我们导入了 MistralAIFunctionConfiguration
将函数注册到 Spring 上下文,并注入了 Spring AI 自动创建的 MistralAiChatClient
。
请求中指定了包含患者 ID 的提示文本和函数名。调用 API 后验证响应包含预期健康状态。日志输出如下:
The patient with id P004 has increased blood pressure.
3.4. 多函数场景
我们还可以注册多个函数,让 AI 根据提示内容智能选择调用哪个。
为演示此功能,扩展 HealthStatus
record:
public record HealthStatus(String status, LocalDate changeDate) {
}
添加了状态变更日期字段。
修改配置类:
@Configuration
public class MistralAIFunctionConfiguration {
public static final Map<Patient, HealthStatus> HEALTH_DATA = Map.of(
new Patient("P001"), new HealthStatus("Healthy",
LocalDate.of(2024,1, 20)),
new Patient("P002"), new HealthStatus("Has cough",
LocalDate.of(2024,3, 15)),
new Patient("P003"), new HealthStatus("Healthy",
LocalDate.of(2024,4, 12)),
new Patient("P004"), new HealthStatus("Has increased blood pressure",
LocalDate.of(2024,5, 19)),
new Patient("P005"), new HealthStatus("Healthy",
LocalDate.of(2024,6, 1)));
@Bean
@Description("Get patient health status")
public Function<Patient, String> retrievePatientHealthStatus() {
return (patient) -> HEALTH_DATA.get(patient).status();
}
@Bean
@Description("Get when patient health status was updated")
public Function<Patient, LocalDate> retrievePatientHealthStatusChangeDate() {
return (patient) -> HEALTH_DATA.get(patient).changeDate();
}
}
我们为每个状态项添加了变更日期,并创建了 retrievePatientHealthStatusChangeDate()
方法返回状态变更日期。
测试多函数调用:
@Test
void givenMistralAiChatClient_whenAskChatAPIAboutPatientHealthStatusAndWhenThisStatusWasChanged_thenExpectedInformationInResponse() {
var options = MistralAiChatOptions.builder()
.withFunctions(
Set.of("retrievePatientHealthStatus",
"retrievePatientHealthStatusChangeDate"))
.build();
ChatResponse paymentStatusResponse = chatClient.call(
new Prompt(
"What's the health status of the patient with id P005",
options));
String paymentStatusResponseContent = paymentStatusResponse.getResult()
.getOutput().getContent();
logger.info(paymentStatusResponseContent);
Assertions.assertThat(paymentStatusResponseContent)
.containsIgnoringCase("healthy");
ChatResponse changeDateResponse = chatClient.call(
new Prompt(
"When health status of the patient with id P005 was changed?",
options));
String changeDateResponseContent = changeDateResponse.getResult().getOutput().getContent();
logger.info(changeDateResponseContent);
Assertions.assertThat(paymentStatusResponseContent)
.containsIgnoringCase("June 1, 2024");
}
我们指定了两个函数名,并发送了两个不同提示:先询问患者健康状态,再询问状态变更时间。验证结果包含预期信息,日志输出如下:
The patient with id P005 is currently healthy.
The health status of the patient with id P005 was changed on June 1, 2024.
4. 总结
函数调用是扩展 LLM 功能的利器,能实现 LLM 与业务逻辑的无缝集成。
本文展示了如何通过调用单个或多个函数实现基于 LLM 的流程。利用此方法,我们可以构建与 AI API 深度集成的现代化应用。
完整源代码请参考 GitHub 仓库。