写点什么

深入解析 Spring AI 框架:在 Java 应用中实现智能化交互的关键

  • 2024-12-05
    福建
  • 本文字数:4006 字

    阅读完需:约 13 分钟

众所周知,Java 是一种面向对象的编程语言,因此不论我们调用什么 AI 接口,从业务的角度来看,它本质上只是一个接口,而 AI 则充当了一个第三方对接平台。然而,值得注意的是,AI 的聊天回复往往不适用于对象,因为这些回复无法直接返回格式化的 JSON 数据。这一问题导致 Spring 无法将其转化为实体类,从而无法真正融入业务流程。


今天,我们将探讨 Spring AI 框架是如何有效解决这一挑战的。通过深入分析框架的设计和实现,我们希望为大家展示如何将 AI 能力顺利整合到 Java 应用中,推动业务的进一步发展。


除此之外,function call 函数回调也是 AI 技术的一个重要特性。那么,Spring AI 是如何应对这一挑战的呢?今天,我们将深入探讨这个问题,解析 Spring AI 框架如何有效处理函数回调,从而增强 AI 与 Java 程序之间的交互能力。


实体化类


实体类在 Java 程序中扮演着不可或缺的角色,无论是进行内部操作,还是将数据返回给前端的 RESTful 接口,实体类都是业务中信息传递的核心。在 Spring AI 框架中,我们可以有效地控制 AI 的回答,以确保其能够正确映射到实体类。接下来,我们将探讨 Spring AI 是如何实现这一功能的,基本用法如下:


@GetMapping("/ai-Entity")ActorFilms generationByEntity() {    ActorFilms actorFilms = chatClient.prompt()            .user("Generate the filmography for a random actor.")            .call()            .entity(ActorFilms.class);    return actorFilms;}
复制代码


源码分析


在这里,我们不再直接调用 content 方法,而是选择使用 entity 方法作为返回类型。这一变化意味着我们需要重点关注 entity 的实现及其在整个流程中的作用。接下来,让我们通过代码示例来深入分析这一关键部分:


public <T> T entity(Class<T> type) {      Assert.notNull(type, "the class must be non-null");      var boc = new BeanOutputConverter<T>(type);      return doSingleWithBeanOutputConverter(boc);}
复制代码


这里使用了一个名为 BeanOutputConverter 的转换器。接下来,我们来详细查看一下 doSingleWithBeanOutputConverter 方法的具体实现。


        private <T> T doSingleWithBeanOutputConverter(StructuredOutputConverter<T> boc) {            var chatResponse = doGetObservableChatResponse(this.request, boc.getFormat());            var stringResponse = chatResponse.getResult().getOutput().getContent();            return boc.convert(stringResponse);        }
复制代码


在这里,我们要讨论的 doGetObservableChatResponse 方法主要负责与第三方 API 的交互过程。由于我们在之前的讲解中已经对聊天调用 API 方法的实现进行了详细分析,因此这次我们就不再深入探讨其具体内容,而是集中于方法的核心功能和应用场景。


实体类提示词限制


在这里,我们来查看一下 boc.getFormat() 方法。这个方法返回一段提示词,而这些提示词会根据不同的类型而有所区别。为了更好地理解,我们可以具体分析一下单个 Bean 实体类所对应的提示词格式。



具体如下:


    public String getFormat() {        String template = """                Your response should be in JSON format.                Do not include any explanations, only provide a RFC8259 compliant JSON response following this format without deviation.                Do not include markdown code blocks in your response.                Remove the ```json markdown from the output.                Here is the JSON Schema instance your output must adhere to:                ```%s```                """;        return String.format(template, this.jsonSchema);    }
复制代码


这其实非常简单。通过使用提示词来明确限制 AI 返回的格式,可以最大程度地确保其输出符合我们的要求。这种方式使得 Spring 能够有效地进行解析,而 jsonSchema 则仅仅是我们传递的实体类的各种信息。


封装实体类


boc.convert 方法负责将数据封装成实体类的过程。具体来说,它会接收原始数据,并通过内部逻辑进行转换,以生成符合我们定义的实体类结构。



从表面上看,我们可以清晰地看出该过程涉及到 JSON 序列化,它将数据封装成我们所期望的对象格式。然而,需要注意的是,虽然 AI 的提示词旨在尽量限制其回复内容,以使其尽可能符合我们的要求,但由于各种因素的影响,我们无法保证其返回的格式会完全按照预设进行。


因此,为了确保程序的稳健性和可靠性,在此过程中引入了异常捕获机制。这一机制能够有效地处理潜在的格式不一致或错误,从而确保应用在面对不符合预期的数据时,能够平稳运行而不至于崩溃。


函数回调


AI 目前能够发挥一定作用,主要得益于模型的函数调用功能。如果仅仅依靠训练模型进行聊天回答,其实际价值是相对有限的,因为这种方式的成本非常高,很多企业难以承受。然而,随着函数回调功能的引入,AI 可以实时访问和利用各种数据,包括实时数据和业务数据,使其能够根据提供的信息进行更为精准和有效的回答,从而具备了实质性的业务能力。


接下来,我们来看看 Spring AI 是如何实现这一点的。


基本用法


了解了之前的 Spring AI 用法文章后,你大概已经掌握了如何创建一个 Function 函数。接下来,我们将直接深入探讨如何将这个函数添加到我们的项目中。


@PostMapping("/ai-function")ChatDataPO functionGenerationByText(@RequestParam("userInput")  String userInput) {    String content = this.myChatClientWithSystem.prompt()            .user(userInput)            .functions("CurrentWeather")            .call()            .content();    log.info("content: {}", content);    ChatDataPO chatDataPO = ChatDataPO.builder().code("text").data(ChildData.builder().text(content).build()).build();;    return chatDataPO;}
复制代码


在我们的项目中,functions 函数允许添加多种功能,不仅仅局限于单一工具的调用。例如,在可视化智能体的应用中,如千帆 AppBuilder,我们可以观察到思考轮数的运用,这其中涉及了多个工具的调用。这种方式为我们的智能体提供了更丰富的功能和灵活性。


接下来,我们将进行一次函数的调用,以实际展示其效果。



在这里,我们使用了一个固定的 30 度作为示例值,但你可以在函数方法内部通过接口调用其他第三方服务来获取实时数据。因此,通过集成外部数据源,你可以实现更为智能和适应性强的功能。


源码分析


还记得我们之前讨论过的内容吗?在回答的最后,我们会进行一次判断,以确定当前的输出是否为函数调用。这一过程是确保系统能够准确识别和执行函数的重要环节。接下来,我们将展示相关的源码,以便更深入地理解这一机制的具体实现:


if (isToolCall(chatResponse,        Set.of(ChatCompletionFinishReason.TOOL_CALLS.name(), ChatCompletionFinishReason.STOP.name()))) {    var toolCallConversation = handleToolCalls(prompt, chatResponse);    // Recursively call the call method with the tool call message    // conversation that contains the call responses.    return this.call(new Prompt(toolCallConversation, prompt.getOptions()));}
复制代码


我也在这里设置了一个断点,以便大家可以清楚地看到这一过程。这一断点帮助我们确认,返回的结果完全是由 AI 生成的。在这个基础上,我们会进行进一步的判断,以决定是否需要调用函数工具。



接下来,我们将进入函数调用的过程。这一步骤至关重要,因为函数的返回值将被再次提供给 AI,作为后续回答的参考。我们来看看具体是如何进行函数调用的。虽然我已经找到了相关的源码,但为了让大家更容易理解这个过程,我将提供一张可视化的图片。这张图片将清晰地展示函数调用的流程,以及返回值是如何被整合进 AI 的回答中的。



发送这些参数的原因在于,在发起请求时已经设置了相关限制。以下是我截取下来的请求参数:


tools=[FunctionTool[type=FUNCTION, function=Function[description=获取指定地点的天气情况, name=CurrentWeather, parameters={$schema=https://json-schema.org/draft/2020-12/schema, type=object, properties={location={type=string}, unit={type=string, enum=[C, F]}}}]]]


目前几乎所有第三方 AI 接口都提供了一个名为 tools 的参数,用于传递我们需要的参数。以 OpenAI 为例:



调用函数接口


由于我们的函数实现了 @FunctionalInterface 接口,因此 call 这一行实际上会调用我们定义的 apply 接口。鉴于我们的参数是一个实体记录,系统会对其进行 JSON 转化和封装,随后再进行调用。具体过程如下所示:


    public String call(String functionArguments) {
// Convert the tool calls JSON arguments into a Java function request object. I request = fromJson(functionArguments, inputType);
// extend conversation with function response. return this.andThen(this.responseConverter).apply(request); }
复制代码


因此,即使所有操作都已结束,如果在下次 AI 判断中仍然需要调用工具,系统将继续进行循环,直到所有问题都得到完整的回答为止。这种设计确保了整个过程的连贯性和完整性。


总结


在这次探讨中,我们深入挖掘了 Spring AI 框架如何与 Java 程序完美结合,提升业务能力。随着 AI 技术的不断发展,其在 Java 应用中的整合成为了提升开发效率和用户体验的关键。我们不仅分析了实体类的映射与控制,还探讨了函数回调的强大功能,展示了如何通过 Spring AI 有效处理这些复杂交互。


希望这些分析能够激发你对 AI 应用的灵感,并促使你在自己的项目中大胆尝试,将 AI 技术融入到业务流程中。期待未来能看到大家的创意实现和应用!


文章转载自:努力的小雨

原文链接:https://www.cnblogs.com/guoxiaoyu/p/18441709

体验地址:http://www.jnpfsoft.com/?from=infoq

用户头像

还未添加个人签名 2023-06-19 加入

还未添加个人简介

评论

发布
暂无评论
深入解析Spring AI框架:在Java应用中实现智能化交互的关键_Java_不在线第一只蜗牛_InfoQ写作社区