1. 概述
利用大型语言模型(LLM),我们可以检索大量有用的信息。我们可以学习关于任何事物的许多新知识,并基于互联网上已有的数据获得答案。我们可以让它们处理输入数据并执行各种操作。但如果我们让模型调用API来准备输出呢?
为此,我们可以使用函数调用(Function Calling)。函数调用使大型语言模型能够交互并操作数据,执行计算,或获取超出其固有文本能力的信息。
本文将探讨函数调用是什么,以及如何利用它将大型语言模型与我们内部的业务逻辑集成。作为模型提供方,我们将使用 Mistral AI 的 API。
Mistral AI 致力于为开发者和企业提供开放且可移植的生成式 AI 模型。我们既可以用它来处理简单的提示,也可以用来实现函数调用集成。
要开始使用 Mistral API,首先需要获取 API 密钥。这一步读者自行去网上搜索。
我们先从一个简单的提示开始。我们将请求 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 response = client.send(request, HttpResponse.BodyHandlers.ofString());
String responseBody = response.body();
logger.info("Model response: " + responseBody);
Assertions.assertThat(responseBody)
.containsIgnoringCase("healthy");
}
我们创建了一个 HTTP 请求并发送到/chat/completions端点。然后,我们使用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}}
函数调用的示例为复杂,且在调用之前需要做大量准备工作。我们将在后面部分中详细了解。
下面来看几个使用 Mistral API 进行函数调用的示例。借助 Spring AI,我们可以省去很多准备工作,让框架帮我们完成。
所需的依赖项位于 Spring 里程碑版本仓库中。我们将其添加到 pom.xml
文件中:
spring-milestones
Spring milestones
https://repo.spring.io/milestone
添加Mistral API的集成依赖
org.springframework.ai
spring-ai-mistral-ai-spring-boot-starter
0.8.1
把上面获取到的key配置到属性文件
spring:
ai:
mistralai:
api-key: ${MISTRAL_AI_API_KEY}
chat:
options:
model: mistral-small-latest
在我们的演示示例中,我们将创建一个函数,根据患者的 ID 返回患者的健康状态。
让我们先创建患者记录:
public record Patient(String patientId) {
}
再创建一个健康状态记录
public record HealthStatus(String status) {
}
然后,创建一个配置类
@Configuration
public class MistralAIFunctionConfiguration {
public static final Map 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 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
类,将retrievePatientHealthStatus()函数添加到测试的Spring上下文中。同时,我们注入了
MistralAiChatClient
,该客户端会由 Spring AI Starter 自动实例化。
在请求聊天 API 时,我们指定了包含某个患者 ID 的提示文本,以及用于获取健康状态的函数名称。随后调用了 API,并验证响应中包含了预期的健康状态。
此外,我们还记录了整个响应文本,内容如下:
The patient with id P004 has increased blood pressure.
我们还可以指定多个函数,AI 会根据我们发送的提示决定使用哪个函数。
为了演示这一点,让我们扩展一下 HealthStatus 记录:
public record HealthStatus(String status, LocalDate changeDate) {
}
我们添加了状态上次变更的日期。
现在,让我们修改配置类:
@Configuration
public class MistralAIFunctionConfiguration {
public static final Map 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 retrievePatientHealthStatus() {
return (patient) -> HEALTH_DATA.get(patient).status();
}
@Bean
@Description("Get when patient health status was updated")
public Function retrievePatientHealthStatusChangeDate() {
return (patient) -> HEALTH_DATA.get(patient).changeDate();
}
}
我们为每个状态项填写了变更日期。同时,我们还创建了 retrievePatientHealthStatusChangeDate()
函数,用于返回状态变更日期的信息。
下面来看如何使用这两个新函数与 Mistral API 进行交互:
@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.
函数调用是扩展大型语言模型功能的绝佳工具。我们还可以利用它将大型语言模型与我们的业务逻辑进行集成。
在本教程中,我们探讨了如何通过调用一个或多个自定义函数来实现基于大型语言模型的流程。通过这种方法,我们能够开发与AI API深度集成的现代应用程
关注我不迷路,系列化的给您提供当代程序员需要掌握的现代AI工具和框架