Ohhnews

分类导航

$ cd ..
Baeldung原文

利用 Spring AI 实现可解释的 AI 智能体:捕获 LLM 工具调用推理过程

#spring ai#人工智能#大语言模型#可解释性#工具调用

1. 概述

当我们构建具备工具调用(Tool-calling)能力的 AI 智能体时,通常能看到大模型(LLM)选择了哪个工具,却无法得知它为何做出该决定。这种洞察力的缺失增加了调试难度,降低了可观测性,并限制了用户对 AI 驱动系统的信任。对于生产级的智能体而言,理解模型的推理过程是必不可少的。可解释 AI 智能体通过在工具选择期间捕获 LLM 的额外上下文来解决这一问题。

在本文中,我们将通过一个实际示例来了解“工具参数增强器”(Tool Argument Augmenter)。我们将探讨如何在工具调用期间捕获 LLM 的推理过程,以及如何在 Spring AI 应用程序中使用这些数据。

2. 工具调用的难题

当模型仅凭训练数据无法可靠回答问题时,我们会使用工具调用。例如,当 LLM 需要实时数据(如当前价格或用户特定信息)、需要访问外部系统(如数据库或内部服务),或者必须触发操作(如创建记录或发送通知)时,我们都会用到它。

在 Spring AI 中,模型通过工具调用将工作委托给应用程序代码,而 LLM 则专注于理解用户请求并生成最终响应。假设我们的应用程序提供了两个工具:

$ java
@Tool(description = "获取患者健康状况")
public String retrievePatientHealthStatus(String patientId) {
    return HEALTH_DATA.get(patientId).status();
}
@Tool(description = "获取患者健康状况更新时间")
public LocalDate retrievePatientHealthStatusChangeDate(String patientId) {
    return HEALTH_DATA.get(patientId).changeDate();
}

通过 @Tool 注解,我们将方法标记为可供 LLM 调用的工具。当我们向应用程序询问“患者状态是否稳定?”时,幕后流程如下:

  • Spring AI 将两个工具的定义(包括输入模式)发送给 LLM。
  • LLM 分析请求并评估可用工具。
  • LLM 决定调用 retrievePatientHealthStatus
  • LLM 返回一个包含所需参数的工具调用请求。
  • 工具管理器调度并执行选定的工具。
  • 工具将结果返回给 LLM,LLM 随后生成最终响应。

从应用程序的角度来看,我们只能看到工具被调用了。问题在于,我们无法看到该选择背后的推理过程。这种推理缺失限制了可观测性,并使调试变得困难。 我们可以确认调用了哪个工具,但无法解释 LLM 为何选择它。Spring AI 的“工具参数增强器”正是为了解决这一局限而设计的。

3. 工具参数增强器

工具参数增强器在标准工具调用之上添加了一层可解释性。我们可以动态地使用额外参数来扩展工具的 JSON Schema。 这些参数捕获了应用程序所需的元数据,例如推理过程、见解或置信度。工具本身保持不变,且无需感知这种增强。通过 @ToolParam,我们可以描述各个方法参数,以便模型理解必须提供哪些输入:

$ java
@ToolParam(description = """
  你调用此工具的逐步推理过程以及你的预期。
  提供你选择调用特定工具的证据。
  """, required = true)
String innerThought

启用工具参数增强器后,工具调用流程变为:

  • 我们询问“患者状态是否稳定?”。
  • Spring AI 将 retrievePatientHealthStatus()retrievePatientHealthStatusChangeDate() 的工具定义发送给“工具调用顾问”(Tool Call Advisor)。
  • 工具参数增强器拦截这两个工具定义。
  • 增强器使用 innerThought 参数扩展每个工具的 JSON Schema。
  • Spring AI 将增强后的工具架构发送给 LLM。
  • LLM 决定调用 retrievePatientHealthStatus(),并返回一个包含原始参数以及增强参数 innerThought 的工具调用请求,解释了选择该工具的原因。
  • 增强器提取 innerThought 并将其转发给消费者,用于日志记录、内存存储或分析。
  • Spring AI 仅使用预期的参数调用 retrievePatientHealthStatus()
  • LLM 使用工具结果生成最终响应。

这种方法捕获了 LLM 选择特定工具的原因。 我们可以记录推理过程、将其存储为长期记忆,或将其用于调试和分析。同时,我们保持了工具的简洁性和可重用性,在不更改现有工具契约的情况下,增强了智能体行为的可解释性和可信度。

4. 患者健康状况检查器示例

让我们实现一个简单的“患者健康状况检查器”应用程序。我们将提供几个工具来获取不同类型的患者健康状况信息,然后让用户询问有关不同患者的问题,LLM 将决定调用哪个工具来提供所需信息。

4.1. 依赖项

首先添加 spring-ai-starter-model-openai 依赖:

$ xml
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-openai</artifactId>
    <version>${spring-ai.version}</version>
</dependency>

该依赖项已包含底层的 Spring AI 类,并提供了我们在此应用程序中使用的 OpenAI 模型集成。

4.2. 工具规范

创建一个 PatientHealthInformationTools 类。该类将公开 AI 智能体可以调用的工具方法,以检索患者健康信息。它充当 LLM 与我们内部健康数据源之间的桥梁:

$ java
public class PatientHealthInformationTools {
    public static final Map<String, HealthStatus> HEALTH_DATA = Map.of(
      "P001", new HealthStatus("健康", LocalDate.ofYearDay(2025, 100)),
      "P002", new HealthStatus("有咳嗽", LocalDate.ofYearDay(2025, 200)),
      "P003", new HealthStatus("健康", LocalDate.ofYearDay(2025, 300)),
      "P004", new HealthStatus("血压升高", LocalDate.ofYearDay(2025, 350)),
      "P005", new HealthStatus("健康", LocalDate.ofYearDay(2026, 10)));
      
    @Tool(description = "获取患者健康状况")
    public String retrievePatientHealthStatus(String patientId) {
        return HEALTH_DATA.get(patientId).status();
    }
    @Tool(description = "获取患者健康状况更新时间")
    public LocalDate retrievePatientHealthStatusChangeDate(String patientId) {
        return HEALTH_DATA.get(patientId).changeDate();
    }
}

4.3. 智能体推理 DTO

现在引入 AgentThinking DTO。我们使用此对象来捕获模型在工具选择过程中的推理。它有助于使工具调用的决策过程更加透明且易于分析:

$ java
public record AgentThinking(
    @ToolParam(description = """
      你调用此工具的逐步推理过程以及你的预期。
      提供你选择调用特定工具的证据。
      """, required = true)
    String innerThought,
    @ToolParam(description = "在此工具选择上的置信度(低、中、高)", required = true)
    String confidence) {
}

4.4. PatientHealthStatusService

创建 PatientHealthStatusService该服务协调 LLM 调用并集成我们的增强工具逻辑:

$ java
@Service
public class PatientHealthStatusService {
    private static final Logger log = LoggerFactory.getLogger(PatientHealthStatusService.class);
    private final ChatClient chatClient;
    
    @Autowired
    public PatientHealthStatusService(OpenAiChatModel model) {
        AugmentedToolCallbackProvider<AgentThinking> provider = AugmentedToolCallbackProvider
          .<AgentThinking>builder()
          .toolObject(new PatientHealthInformationTools())
          .argumentType(AgentThinking.class)
          .argumentConsumer(event -> {
              AgentThinking thinking = event.arguments();
              log.info("已选择工具: {}\n LLM 推理: {}\n 置信度: {}",
                      event.toolDefinition().name(), thinking.innerThought(), thinking.confidence());
          })
          .build();
        chatClient = ChatClient.builder(model)
          .defaultToolCallbacks(provider)
          .build();
    }
    
    public String getPatientStatusInformation(String prompt) {
        log.info("输入请求: {}", prompt);
        return chatClient.prompt(prompt)
          .call()
          .content();
    }
}

我们创建了一个附带工具的 AugmentedToolCallbackProvider 实例,注入了 AgentThinking DTO,并添加了日志逻辑以打印每次调用的推理详情。

4.5. 测试服务

最后,测试 PatientHealthStatusService 以验证工具选择是否正确,以及推理元数据是否被正确捕获:

$ java
@Test
void givenPatientHealthStatusService_whenAskingPatientHealthStatusAndChangeDate_thenResponseShouldContainExpectedInformation() {
    String healthStatusResponse = statusService
      .getPatientStatusInformation("患者 P002 的健康状况如何?");
    
    assertThat(healthStatusResponse).contains("咳嗽");
    
    String healthStatusChangeDateResponse = statusService
      .getPatientStatusInformation("患者 P002 的健康状况是什么时候更改的?");
    
    assertThat(healthStatusChangeDateResponse).contains("July 19, 2025");
}

日志输出示例:

[INFO] 已选择工具: retrievePatientHealthStatus
 LLM 推理: 我正在调用此工具来获取患者 P002 的当前健康状况,了解其健康状况至关重要。
 置信度: 高

[INFO] 已选择工具: retrievePatientHealthStatusChangeDate
 LLM 推理: 我需要找出患者 P002 的健康状况最后一次更新的时间,以了解其当前的健康状况及可能影响其护理的近期变化。此工具专门用于检索患者上次健康状况更改的日期。
 置信度: 高

5. 工具调用链示例

有时我们需要调用一系列工具。例如,根据患者姓名获取健康状况。我们添加一个新工具:

$ java
@Tool(description = "根据患者姓名获取患者 ID")
public String retrievePatientId(String patientName) {
    return PATIENTS_IDS.get(patientName);
}

当 LLM 无法直接获取状态时,它会先调用 retrievePatientId 获取 ID,再调用 retrievePatientHealthStatus 获取状态。日志将完整记录这一推理链,帮助我们优化提示词并避免不必要的调用。

6. 结论

在本文中,我们探讨了如何使用“工具参数增强器”使 AI 集成具备可解释性。我们在不修改工具实现的情况下,捕获了模型在工具选择期间的推理过程。

通过这种方法,我们提高了 LLM 工具调用决策的可观测性,并为提示词优化收集了宝贵的反馈。此外,我们还可以利用推理数据进行审计、监控或分析,从而优化智能体随时间的表现。代码示例可在 GitHub 上获取。