在 Spring I/O 2025 大会上使用 Spring AI 和 Vaadin 的真实世界 AI 模式,作者:Marcus Hellberg / Thomas Vitale




内容概要

本次演讲探讨了如何将人工智能 (AI) 功能集成到 Java 应用程序中,从实验阶段真正走向生产环境。演讲内容涵盖了多种常见的 AI 模式,包括:为大型语言模型 (LLM) 提供短期和长期记忆;通过工具调用 (Tool Calling) 将 LLM 与你的 API 集成并构建高级代理 (Agent);使用护栏 (Guardrail) 保护 AI 工作流免受提示词注入攻击和敏感数据泄露;通过多模态 (Multimodality) 技术实现与模型的文本、图像、音频和视频交互;以及利用先进的检索技术从你自己的数据中为 LLM 增强上下文。演讲通过现场演示,展示了如何使用 Spring AI 和 Vaadin 实现这些模式,为开发者提供了可立即在项目中应用的实践见解,无论工作负载是在本地还是在云端运行。


目录

  • 演讲与嘉宾介绍

  • Spring AI 和 Vaadin 概述

  • 使用 Spring AI 与 LLM 进行基础交互

  • 通过聊天记忆解决 LLM 的无状态问题

  • 配置系统提示词和流式响应

  • 为输入和输出实现护栏

  • 利用 AI 实现预定义操作和提示词工程

  • 情感分析与结构化输出

  • 检索增强生成 (RAG)

  • 多模态与图像的结构化输出

  • 工具、代理与模型概念协议 (MCP)

  • 现场演示:AI 修改应用程序的 CSS

  • 总结与后续议程预告


演讲与嘉宾介绍

欢迎来到我们的演讲。这次分享将非常实用,主要围绕真实世界中的 AI 模式展开。

Marcus Hellberg: 我是 Marcus Hellberg,另一位是 Thomas Vitale。感谢大家的参与。我的名字是 Marcus Hellberg,在一家名为 Vaadin 的公司工作。我们主要帮助 Java 开发者构建 Web 应用程序。在 Web 和 Java 的交叉领域,我已经工作了将近 20 年。我算了一下,今年是 Java 诞生 30 周年,也恰好是我作为职业 Java 程序员的第 20 年,所以今年意义非凡。我最近刚成为 Java Champion,这很酷。不过,我还需要一位更有成就的演讲者搭档,所以来看看他这些了不起的履历。

Thomas Vitale: 大家好,我是 Thomas Vitale,在一家名为 Systematic 的丹麦软件公司担任软件工程师。我对 Java 和云原生相关的任何事物都充满热情,几年前写过一本关于这个主题的书。现在,我正与我的朋友 Mauricio Salatino 一起,开始一段新的旅程,撰写一本关于 Kubernetes 开发者体验的新书。不过今天,我们还是来聊聊 Spring AI 和 Vaadin 吧。


Spring AI 和 Vaadin 概述

Marcus Hellberg: 是的,我们今天演讲的重点绝对是 AI 部分,也就是 Spring AI。我们使用 Vaadin 仅仅是作为一个 UI 层,这样我们就能有一个有趣的交互界面。所以,我们不会在幻灯片上花费太多时间,而是直接进入代码,看看这一切是如何工作的。

我们之前做了一个小调查,在座有多少人使用过 Spring AI?大概有三分之一,或者四分之一。很好,希望我们能给其他人展示一些新东西。

我们会展示一个项目,稍后会分享 GitHub 链接,大家可以自己去探索和尝试。这个项目本质上是一个 Spring Boot 项目,毕竟我们是在 Spring I/O 大会。我们使用的是刚刚发布的 Spring AI 1.0.0 版本。Thomas 为这个版本做出了巨大贡献,所以我们今天演示的很多功能都要感谢他。

Thomas Vitale: 是的,那是一段非常有趣的旅程。

Marcus Hellberg: 我们还使用了 Vaadin。此外,我们今天将使用 OpenAI,但得益于 Spring AI 的设计,它为底层模型提供了一个很好的抽象层,让我们能用相似的方式与不同模型交互。因此,我们可以先用一个模型开始,如果需要,之后再换成另一个。

我们还会简单介绍一下模型概念协议 (MCP) 和检索增强生成 (RAG)。Thomas,你想介绍一下你添加的那些 Aronia 依赖吗?

Thomas Vitale: 为了获得良好的开发者体验,我们用到了我最近创建的一个名为 Aronia 的项目。它能帮我们获得运行应用所需的所有开发服务 (Dev Services),比如一个 PostgreSQL 数据库和一个 OpenTelemetry 后端。如果你在本地运行 AI,还可以使用 Lama。这一切都是开箱即用的,无需编写任何代码或配置,只需正常运行你的应用程序即可。我们还完全集成了 OpenTelemetry,因为可观测性在 AI 开发中至关重要,它能帮助我们理解底层发生的一切。

Marcus Hellberg: 是的,这样我们就把样板代码的数量降到了最低。我们真正需要定义的关键配置只有 API 密钥。就像 Josh 说过的,千万不要泄露它,要使用环境变量。我们将使用 GPT-4o,这是一个很好的多模态模型,适用于我们所有的示例。我们还有一个嵌入模型 (Embedding Model),后面会用到。


使用 Spring AI 与 LLM 进行基础交互

对于不熟悉 Vaadin 的朋友,我来简单介绍一下。Vaadin 视图就是这个样子。它能让你完全用 Java 构建 Web 应用程序。在 Vaadin 中,一切都是组件 (Component),你可以把组件放进布局 (Layout) 中。在这个例子里,我们从一个垂直布局开始,并将它映射到应用的根路径。界面上有一个消息列表和一个消息输入框。

有趣的是 Spring AI。我们在这里注入了一个 ChatClient.Builder。每个 Vaadin 视图都是一个 Spring Bean,所以可以像其他 Bean 一样注入依赖。我们从最基础的开始,直接调用 builder.build(),不加任何其他东西,这样就可以和模型交互了。我添加了一个监听器,每当我们提交新消息时,它会首先获取消息内容,然后把它添加到消息列表中,这样我们就能看到自己刚刚输入了什么。

接着,我们会调用聊天客户端 (Chat Client),告诉它我们要发起一个提示 (Prompt),并传入用户的消息。然后我们进行一次阻塞式调用,获取返回的内容。这个内容是一个字符串,拿到之后,我们再把它添加到消息列表中。整个过程看起来就是这样:我们输入内容,等待一会儿,然后模型就会响应。这一切都是用 Java 实现的,没有写一行 JavaScript,这都归功于 Vaadin。


通过聊天记忆解决 LLM 的无状态问题

Marcus Hellberg: 这里需要注意的一点是,与 LLM 交互时,它是无状态的 (Stateless)。它不记得任何事情。我们发送的每一个请求,对它来说都是一个全新的世界。比如,如果我说:“你好,我叫 Marcus。” 然后再问:“我叫什么名字?” 它会回答:“我不知道你的名字,你还没告诉我。” 它就像个骗子。

当然,对我们来说它像在说谎,但实际上是因为每个请求都是独立的。我们需要以某种方式给它提供上下文。实现这一点的方法是添加一个所谓的顾问 (Advisor),具体来说是 MessageChatMemoryAdvisor。在我输入代码的时候,Thomas 你能解释一下 Advisor 是什么吗?

Thomas Vitale: 当然。你可以把 Advisor 理解为拦截器 (Interceptor)。每次我们调用模型之前,都可以在发送请求前执行不同的操作。今天我们会看到好几个例子。第一个就是引入记忆 (Memory)。借助 Spring AI,应用程序有责任来追踪与模型的对话历史。这样,每次调用模型时,我们不仅可以传递当前的问题,还可以附加上下文,也就是过去的对话内容,让模型了解历史交互。

Marcus Hellberg: 好的。我们添加了一个 MessageChatMemoryAdvisor,它会在内存中保留最近的 N 条消息。现在,如果我再说:“我叫 Marcus。” 然后问:“我叫什么名字?” 它就能够记住了。配置起来非常简单,也很有用。我记得去年和负责这部分的 Christian 聊天时,他问我们是否应该为聊天添加某种形式的记忆功能,我当时的回答是:“当然,我们绝对需要这个。” 幸运的是,现在我们有了这个功能。


配置系统提示词和流式响应

Marcus Hellberg: 最后,我想展示一下如何设置系统提示词 (System Prompt)。告诉 AI 它应该如何表现,它的角色是什么,这非常有用。我们来给它一个默认的系统提示词,告诉它:“你是一位资深的 Vaadin 专家。请用暴躁和讽刺的语气回答所有问题,并且要非常啰嗦。” 这样它就成了一个典型的资深开发者,为我们提供关于 Vaadin 的“热心”建议。我们用了 Spring Boot DevTools 来重新加载应用。

现在,如果我问:“如何构建一个表格 (Grid)?” 这会产生两个效果。首先,希望它能遵守我们的指令。其次,你会看到一个很长的进度条,好像卡住了似的,用户体验并不好。如果你用过 ChatGPT 或类似工具,你会知道它们是流式 (Streaming) 返回响应的。

我们来看看怎么实现流式响应。Thomas,你能讲讲什么时候该用,什么时候不该用吗?

Thomas Vitale: 在 Spring AI 中,所有的模型抽象都同时支持阻塞式 (Blocking) 和流式交互。刚才我们看到的就是阻塞式交互:你提问,等完整的响应生成后一次性返回。我们也可以流式返回。目前,Spring AI 通过响应式编程 (Reactive) 来支持流式传输。所以,即使你用的是传统的命令式 Spring Web 应用,也别忘了添加 Spring WebFlux 依赖,因为它基于 Reactor 技术栈。当我们引入流式传输后,就可以将 Spring AI 的流式能力与 Vaadin 的流式支持结合起来,就像使用 ChatGPT 一样,逐个令牌 (Token) 地获得答案。当答案被生成时,文本就会一点点地显示出来。Marcus 现在做的就是把聊天客户端从标准的命令式调用改成流式调用。

Marcus Hellberg: 是的。现在我们不再调用 call(),而是调用 stream(),然后订阅返回的令牌流。每收到一个令牌,我们就把它追加到响应项中。现在如果我再问:“如何创建一个表格?” 答案就会流式传输到 UI 上。对于这种需要返回长文本的场景,用户体验会好很多,因为用户不必等到所有内容都生成完毕,就可以开始阅读开头部分。


为输入和输出实现护栏

Thomas Vitale: 接下来,我想谈谈护栏 (Guardrail),这是一个非常重要但有时会被忽视的话题。刚才你分享了个人信息,我就在想,这安全吗?我们来试一个问题,比如:“谁住在 Plume 55 号?” 我在这个键盘上找不到问号,但模型不在乎。我问了一个关于具体地址的问题,模型告诉我这是 Jean Valjean。这个回答甚至很糟糕,完全是幻觉。我想得到关于《悲惨世界》的答案,结果却得到了《雷蒙·斯尼奇的不幸历险》。这确实是一次不幸的经历。

但真正糟糕的是它在泄露个人信息。尤其是在使用云端模型时,我们必须确保不泄露未经授权的敏感信息,这可能是个人信息,也可能是公司内部的机密信息。模型本身不会区分,它只会处理我们发送给它的任何信息。因此,我们需要在应用层面做些什么。目前的代码中,虽然可以设置一些选项,但没有办法过滤发送给模型的信息。

通常,在构建标准应用时,我们会对所有外部输入进行验证,无论是用户的提问还是模型的回答。但对于大型语言模型,验证变得更加困难。我们如何判断一个问题是否合规呢?

一种方法是在架构中引入一个新组件:输入护栏 (Input Guardrail)。在将问题发送给模型之前,我们可以进行多项检查,比如是否存在敏感信息、机密信息,甚至有害内容,以及是否符合公司政策。如果护栏判定问题不合规,我们就直接阻止这个请求,不调用大语言模型。如果合规,请求才会继续。

但即使这样,返回的响应也可能有问题。所以我们还需要一个输出护栏 (Output Guardrail)。这既是为了安全,也是为了验证。大型语言模型具有不确定性 (Non-deterministic),如果我们无法确保结果的有效性和正确性,就很难在真实世界的应用中引入这种不确定性组件。输出护栏可以帮助验证响应的正确性,甚至尝试修正它。如果发现问题,它可以再次向模型提问,或者要求模型重试。如果结果仍然不可接受,就阻止响应返回给用户。

我们来看看如何在 Spring AI 中定义输入和输出护栏。我们已经了解了 Advisor,它可以拦截请求和响应,这正是实现护栏的基础。我现在添加一个默认的 Advisor。

Marcus Hellberg: Thomas 真的知道怎么在我这台键盘布局不同的电脑上打字,相信我,虽然他看起来像今天才开始学打字,但事实并非如此。

Thomas Vitale: 好了,我们定义了一个 Advisor。但问题是,我没有办法通过编程方式判断输入是否合规,所以我打算用另一个语言模型来做这件事。当然,你应该使用一个在本地或公司基础设施内部署的模型,否则用云端模型来检查发往云端模型的数据,就没什么意义了。

我们有了一个名为 SensitiveDataInputGuardrailAdvisor 的 Advisor。它利用 Advisor API 拦截聊天客户端的请求,并提取信息进行验证。在这个例子里,我让一个模型去检查问题中是否包含受 GDPR 保护的敏感信息。

Marcus Hellberg: 所以你是把敏感信息发送给一个外部模型,来检查你是否应该把它发送给另一个外部模型?

Thomas Vitale: 完全正确,逻辑完美。当然,就像我说的,你应该在本地的 Lama 模型或内部环境中做这件事。如果检查通过,请求就进入下一个 Advisor;如果被拒绝,就直接返回“计算机说不”。我们来测试一下。

我们来试一个看起来很正常的问题,比如:“我刚申请了一份工作,需要一份个人简介。我叫 Moss,在 IT 支持部门工作,我的电话是 0118999...” 谁知道这个梗?“计算机说不。” 这么多打字的努力白费了。好了,护栏起作用了。现在如果我尝试输入包含电话号码或地址的信息,请求就会被拒绝。这对于真实世界的应用来说至关重要。


利用 AI 实现预定义操作和提示词工程

Marcus Hellberg: 到目前为止,我们和模型的互动大多是聊天。我觉得我们可以做些更有趣的事情。与其说有趣,不如说我想强调一点:很多人容易陷入一种思维定式,认为 AI 就等同于我们刚刚使用的聊天界面。但要从聊天界面中获得最大价值,需要用户善于提问,也就是所谓的提示词工程 (Prompting)。然而,在企业客户中,并非所有人都具备撰写优秀提示词的技术能力。

我想强调的是,作为工程师,我们可以承担起提供优秀提示词的责任,并将它们隐藏在按钮或预定义的操作背后。比如,我们可以分析一份 Sprint 规划会议的笔记。有人记录了会议内容,我们想对它进行总结,就可以通过一个预设好的提示词来完成。或者,我们想识别出待办事项 (Action Points),看看谁被分配了什么任务。这些功能本身可能不那么令人兴奋,但重点在于,作为开发者,我们可以完成提示词工程的繁重工作,设计出能够根据给定输入稳定产出高质量结果的提示词。

在这个例子中,我只是在两个不同的系统消息之间切换。我为它们设计了很棒的系统消息。当消息输入后,我们可以让模型选择是进行总结,还是使用识别待办事项的系统消息来提取相关信息。这里的核心思想不是展示一个惊人的创意,而是告诉你,AI 也可以隐藏在一些预定义的操作之后。


情感分析与结构化输出

Marcus Hellberg: 与此非常相似的是情感分析 (Sentiment Analysis)。假设我们有一家披萨店,收到了很多顾客反馈。我们可能没有时间一一回复,所以希望快速分析这些反馈。通过点击一个按钮,我们可以触发 AI 来分析每条反馈是正面的还是负面的。比如 Lisa 显然吃得不开心。我们还可以利用 AI 预先生成一个回复,这样我们的客服团队就能对 Lisa 的糟糕体验表示同情。

看一下实现方式,其实非常直接。我希望大家在看这些例子时,已经开始发现其中的模式了:我们基本上在重复做着类似的事情。我们同样使用了 ChatClient.Builder。这里比较有趣的一点是,我们还使用了模板 (Template)。我创建了一个带有占位符的模板用于处理评论,并告诉 AI:“分析这条评论的情感,并且我希望得到一个 SentimentResponse 格式的结果。”

我在这里定义了一个记录 (Record),它明确了我希望返回的数据结构。因为我们是在 Java 应用中,处理一个 Java 枚举 (Enum) 比处理一个需要额外解析的普通字符串要容易得多。Thomas 稍后会更深入地探讨这一点。同样地,我把这个功能预置在一个按钮后面,并且作为开发者,我还可以定义生成回复时的语气和风格。这样,客服人员就不需要知道如何撰写完美的提示词,也不需要重复做同样的工作。我们可以把这些功能集成到应用中,为他们提供帮助。


检索增强生成 (RAG)

Marcus Hellberg: 我想我们需要谈谈 RAG,因为它现在非常火,而且我听说这部分就是你写的。

Thomas Vitale: 是的。到目前为止,我们主要看到了两种模式。一种是直接提供聊天机器人界面,让终端用户与大语言模型直接交互。另一种是聊天交互在后台进行,由我们开发者负责处理,用户界面上并不直接暴露聊天功能。我们可以将系统提示词和指令内置其中,以提升用户体验。

但到目前为止,我们处理的都是模型已经知道的信息。每个模型都是基于特定的数据集训练的,超出这个范围的信息,它就一无所知了,比如时事。举个例子,如果我问:“Spring I/O 2025 在什么时候举行?” 模型的知识截止日期是 2024 年 6 月,所以它不知道之后发生的事情,给出的答案自然是错的。

我们该如何解决这个问题呢?我们还是回到聊天客户端的功能上。之前我展示了如何用护栏拦截请求并执行操作。或许我们可以利用这个机制,将模型不知道的额外信息作为提示词的一部分传递给它。否则,我就得去 Spring.io 网站,复制整个网页内容再粘贴给模型,我可不想每次都这么麻烦。

Marcus Hellberg: 我在和企业客户合作时经常遇到这个问题。他们有大量与公司相关的内部数据,这些数据不在公共互联网上,LLM 自然也无法学习到。因此,在构建业务应用时,几乎总是需要为对话提供自定义的上下文,才能让对话有意义。

Thomas Vitale: 是的。除非我们想自己训练或微调模型,否则就需要从外部提供这些信息。我们可以在应用中引入一个新的构建块,用这些上下文来增强提示词。这些信息可以来自不同来源,最常见的是向量存储 (Vector Store),但实际上也可以是任何数据源,比如调用网络搜索引擎。

在这个例子中,我们进行的是所谓的语义搜索 (Semantic Search)。我们在应用中使用了 Postgres 作为向量存储。我们在存储文本的同时,也存储了文本的向量表示,这是一种多维数值向量,我们称之为嵌入 (Embedding)。它用数字来表示信息的含义。通过这种方式,我们可以从向量存储中检索出与特定问题最相关的信息。

这个模式被称为检索增强生成 (Retrieval Augmented Generation, RAG)。它包括一个检索步骤,我们用检索结果来增强提示词,然后再进行生成步骤,也就是带着所有这些信息去调用模型。我们将再次使用 Advisor 来实现这一点。我来定义一个新的 RetrievalAugmentationAdvisor

首先,我们需要指定数据源。我们使用的是 PostgreSQL,所以可以定义一个文档检索器 (Document Retriever)。在 Spring AI 中,Document 是用于表示任何类型数据的 API,可以是文本、图像、音频或视频。因为我用的是向量存储,所以就用 VectorStoreDocumentRetriever。它需要一个 VectorStore 实例,这是 Spring AI 对支持向量的任何数据存储的抽象,比如 Postgres,但你也可以轻松换成其他数据库而无需修改代码。

我们来测试一下。Spring AI 是一个非常新的项目,两天前才正式发布。如果我直接问它关于 Spring AI 的问题,它很可能会答错。所以,我预先将一份包含 Spring AI 文档的 PDF 文件作为上下文添加了进去。这份 PDF 被转换成嵌入并存储在 PostgreSQL 中。现在,如果我问:“在 Spring AI 中如何从向量存储中检索数据?” 我们得到了正确的答案。它提到了我刚刚使用的 VectorStoreDocumentRetriever,还给出了一些如何进一步定制的提示,比如通过相似度搜索 (Similarity Search)。

想象一下,所有这些向量都在一个多维空间里,向量之间的距离越近,它们的含义就越相似。我们可以通过设置一个阈值 (Threshold) 来定义相似的范围,并指定返回结果的数量。

现在,如果我问一个不同的问题,比如:“什么是 Vaadin?” 它会回答不知道。这是为什么呢?因为 RetrievalAugmentationAdvisor 的设计初衷是尽可能保证稳定性,避免意外。使用 RAG 时,如果找不到用于回答问题的上下文信息,模型可能会产生幻觉或使用其他信息来回答。这个 Advisor 内置了一个选项,如果数据库中没有检索到相关数据,它会默认回答“我不知道”。

不过,我们可以自定义这个行为。通过 ContextualQueryAugmenter,我们可以允许上下文为空。这样设置后,聊天客户端就可以回答任何问题了。它会首先尝试使用 RAG,如果在我提供的数据中找到了具体信息,就用它来回答;否则,它会回退到使用自己已有的知识。


多模态与图像的结构化输出

Thomas Vitale: 我们一直在处理文本,但在实际应用中,我们很少只处理非结构化文本,因为模型总是返回非结构化文本。虽然可以把它当作字符串来用,但这并不理想。我们需要一种可以被程序化处理的东西来构建有意义的应用。

我们来谈谈多模态 (Multimodality)。提问时,我们不限于文本,还可以传递图像、音频或视频。同时,我们需要给模型一些指令,让它能正确理解这些内容,并以我们需要的格式返回响应。比如,我希望返回一张图片,或者一个可以映射到 Java 枚举的文本。Spring AI 开箱即用地支持这些功能,它会自动为模型提供格式化指令,并将非结构化文本转换为 Java 对象,这就是我们所说的结构化输出 (Structured Output)。

我们来看一个例子。我们可以上传一张图片,让模型从中提取信息,并以结构化格式返回。这张图片上有很多信息,包括姓名、公司、邮箱和 T 恤尺码。我想以编程方式使用这些信息,比如给每个人寄送 T 恤。首先,我需要用 LLM 将这些信息提取为文本格式,但我还希望它以结构化的形式返回,比如显示在一个表格里。

要实现这一点,需要两个要素。首先,我需要在提示词中传递图像数据。可以看到,用户消息中既包含了文本指令,也包含了图像。然后,在调用时,我使用了 entity() 方法并传入一个 Java 类。Spring AI 在底层会增强我的提示词,告诉模型如何返回一个可以被解析为该 Java 对象的答案,通常是使用 JSON 格式。因为模型具有不确定性,有时这会失败。所以我引入了一个输出护栏,它不为安全或隐私,而是确保模型的输出可以被正确地反序列化为 Java 对象。如果失败,它会尝试重新提示模型来修正错误。


工具、代理与模型概念协议 (MCP)

Marcus Hellberg: 我们时间不多了,但我们要完成所有计划的内容。接下来要看的是工具 (Tools)、代理 (Agents) 和模型概念协议 (MCP) 这些热门词汇。到目前为止,我们谈论的都是调用 LLM 并利用其已有知识,或者通过 RAG 增强上下文。现在,我们来看看如何赋予它工具,让它能为我们执行实际操作。我觉得这才是真正有趣的地方。

通过工具调用 (Tool Calling),我们可以让 LLM 调用我们提供的工具。比如,从我们的数据库中获取数据,以便回答关于产品的问题,然后将这些信息用于其响应。具体来说,当用户提问时,我们会在发给 LLM 的消息中附带说明:“顺便说一下,如果你需要,可以使用以下这些工具。” 如果我问:“我的数据库里最贵的产品是哪个?” 它会发现有一个工具可以查询数据库,于是它会先调用这个工具,然后根据返回的信息来回答。

再来看模型概念协议 (MCP),它实际上是一种发布工具供他人使用的方式。这和之前的想法很类似,我们发布一套工具供 LLM 调用,但这次,这些工具可能由你或其他人提供。现在有很多公司提供用于日历、问题管理系统等各种场景的工具。我们可以使用别人提供的工具,但对 LLM 来说,它们都只是一套可用的工具。

用代码来展示是最好的方式。在我的代码里,除了常规的客户端构建过程,我唯一增加的就是在构建器中设置了一组工具。这些工具由我的 ProductService 提供。我在服务的一些方法上添加了注解,明确告诉 AI:“这些是你可以调用的工具。” 这些注解就像大提示词中的小提示词,为方法和参数提供了上下文,让模型知道如何使用它们。通过这种方式,我们就可以与数据库进行交互了。

我在界面上模拟了一个数据库的实时视图。如果我问:“我们最贵的产品是什么?” LLM 当然不知道,因为它的训练数据里没有我们的数据库。但它看到有一个可以查询数据库的工具,于是它先调用工具,然后回答说:“是 Laptop Pro 15。” 接着,我可以说:“太贵了,把它改名为 Laptop Pro 5k dollar。” 现在,我们让它去修改数据库里的数据。这可能不是个好主意。

Thomas Vitale: 是的,这非常安全。

Marcus Hellberg: 只是为了展示它确实做到了。需要记住的是,你暴露给 LLM 的任何东西,都应该像对待一个对外开放的服务一样。在 Java 代码中做好所有检查,确保执行操作的用户有权限,传入的值是有效的。但你或许已经能看到其中的潜力了:如果你能赋予 LLM 业务上下文,并给它一些访问或修改业务数据的工具,它就能开始为你做事,这就有点像一个代理 (Agent) 了。

Thomas Vitale: 你还可以用所有常规的 Spring 功能来增强这些工具。比如,用 Spring Security 注解来限制只有特定角色的用户才能调用某些工具,或者添加事务注解。它能完美地融入整个 Spring 生态系统。所以,在实现这些功能时,不要忘了那些优秀的设计模式,不仅要考虑 AI,还要考虑安全性、性能和稳定性。

Marcus Hellberg: 是的,基本上,别学我刚才的做法,但可以借鉴其中的思想。


现场演示:AI 修改应用程序的 CSS

Marcus Hellberg: 好了,最后几分钟,我想用一个非常有趣的演示来结束,这个演示可能会出各种壮观的岔子。

Thomas Vitale: 是的,让我们跳出应用系统的边界,与外部世界互动吧。

Marcus Hellberg: 我不仅让 AI 访问了我的数据库,现在我还要让它访问我的文件系统,来实时编辑我们正在运行的这个程序。这听起来是个绝妙的主意。我使用了模型概念协议的文件系统服务,并特别允许它访问我的前端目录,也就是 CSS 文件的位置,因为谁有时间写 CSS 呢?

我们去 Spring I/O 网站,截个图,然后对 AI 说:“嘿,AI,做一个主题,让我们的应用看起来像这样。” 我们拍下照片,上传到我们的应用里,然后等待。处理图片需要一些时间。在这期间,我们可以看看幕后发生了什么。我还是用的工具。我给它提供了一个更详细的系统提示词,解释了如何使用 Vaadin 应用主题的各种参数。这些参数通过默认系统提示词传入。一个小技巧是,你可以直接用 @Value 注解注入资源文件,这样就能处理更复杂的系统提示词。

好了,我们的新主题完成了,正好时间也快到了。它说:“我们的设计直觉绝对出众。调色板和风格创造了一个美丽、专业而又温馨的氛围。干得漂亮,品味非凡,富有创意。” 它在夸自己呢!不,是我们,是我们监督了这一切。现在可以看到,这个新主题已经应用到了所有地方。不过暗色主题没生效。我之前试的时候,得到了一个更好的亮色和暗色主题。这再次说明 AI 系统是不确定的。你可能需要多试几次。这个故事的寓意是,你可以用 AI 来做各种非常酷的事情。


总结与后续议程预告

Marcus Hellberg: 希望我们给你们提供了一些好主意,也可能是一些坏主意。Thomas,你要宣传一下明天的议程吗?

Thomas Vitale: 是的。今天我只展示了 RAG 的一些基本功能,但明天我还有一个议程,会更深入地探讨检索增强生成的所有细节,包括构成 RAG 的不同组件,以及如何将工具和代理引入 RAG,实现更高级的功能。如果你感兴趣,明天见。我们还会稍微探讨一下 Spring AI 的可观测性。

Marcus Hellberg: 是的,时间是明天 15:30,应该就在这个房间。谢谢大家。

Thomas Vitale: 谢谢。


AI 前线

如何在 Azure Kubernetes 服务中运行 Postgres 数据库并将其与 Node.js Express 应用程序集成

2026-1-3 2:07:34

AI 前线

EmbeddingGemma:Google 新一代高效嵌入模型

2026-1-3 2:07:59

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索