Skip to content

LangChain4j

理论概述

介绍

LangChain4j 等价于 LangChain For Java

LangChain4j 的目标是简化将 LLM 集成到 Java 应用程序中的过程。

具体方式如下:

  1. 统一 API: LLM 提供商(如 OpenAI 或 Google Vertex AI)和嵌入(向量)存储(如 Pinecone 或 Milvus) 使用专有 API。LangChain4j 提供统一的 API,避免了学习和实现每个特定 API 的需求。 要尝试不同的 LLM 或嵌入存储,您可以在它们之间轻松切换,无需重写代码。 LangChain4j 目前支持 15+ 个流行的 LLM 提供商20+ 个嵌入存储
  2. 全面的工具箱: 自 2023 年初以来,社区一直在构建众多 LLM 驱动的应用程序, 识别常见的抽象、模式和技术。LangChain4j 将这些提炼成一个即用型包。 我们的工具箱包含从低级提示模板、聊天记忆管理和函数调用 到高级模式如代理和 RAG 的工具。 对于每个抽象,我们提供一个接口以及基于常见技术的多个即用型实现。 无论您是在构建聊天机器人还是开发包含从数据摄取到检索完整管道的 RAG, LangChain4j 都提供多种选择。
  3. 丰富的示例: 这些示例展示了如何开始创建各种 LLM 驱动的应用程序, 提供灵感并使您能够快速开始构建。

LangChain4j 始于 2023 年初 ChatGPT 热潮期间。 我们注意到与众多 Python 和 JavaScript LLM 库和框架相比,缺少 Java 对应物, 我们必须解决这个问题! 虽然我们的名字中有"LangChain",但该项目是 LangChain、Haystack、 LlamaIndex 和更广泛社区的想法和概念的融合,并加入了我们自己的创新。

我们积极关注社区发展,旨在快速整合新技术和集成, 确保您保持最新状态。 该库正在积极开发中。虽然一些功能仍在开发中, 但核心功能已经就位,让您现在就可以开始构建 LLM 驱动的应用程序!

为了更容易集成,LangChain4j 还包括与 QuarkusSpring Boot 的集成。

AB法则(Before | After)

随着人工智能(AI)技术的迅猛发展,越来越多的开发者开始将目光投向AI应用的开发。然而,目前市场上大多数AI框架和工具如LangChain、PyTorch等主要支持Python,而Java开发者常常面临工具缺乏和学习门槛较高的问题,但是不用担心,谁让Java/Spring群体强大那?,O(∩_∩)O

任何一个框架/XXX云服务器,想要大面积推广,应该不会忘记庞大的Spring社区和Java程序员

Before(没有 LangChain4j 之前)

image-20250724185120589

各干各的

After(有 LangChain4j 之后)

image-20250724185201404

官网

能干嘛

LLM 大模型能干嘛

image-20250724185519361

  • controller
  • service
  • dao/mapper

LLM 大模型应用技术架构

image-20250724185558979

去哪下

怎么玩

大模型开发分类

image-20250725183517344

产品定位

image-20250725183538316

永远的HelloWorld

前置约定

LangChain4j支持的各种大模型语言模型(LLMS)

本次以阿里百炼平台(通义千问)为主并辅以DeepSeek模型

配置门道和关键点

  • 所有调用均基于 OpenAI 协议或者 SpringBoot 官方推荐整合规则,实现一致的接口设计与规范,确保了多模型切换的便利性,提供高度可拓展的开发支持

阿里云百炼平台入口

接入阿里百炼平台的通义模型:https://bailian.console.aliyun.com

大模型调用三件套

  1. 获得 api-key

    先登录并创建API-key

    image-20250726174346286

    选择想要的模型,点击查看详情

    image-20250726174411279

  2. 获得模型名

    点击模型广场,点击想要的模型,点击查看详情

    image-20250726174411279

    选择你想要的模型复制下来

    image-20250726174543338

    例如:模型名:qwen-plus

  3. 获得 baseUrl 开发地址

    点击查看api参考

    image-20250726174810710

    image-20250726174840160

假设你要换一个模型实例

image-20250726175149199

总结

IDEA 工具中建 project 父工程

LangChain4j

image-20250726175820182

pom

xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.lazy</groupId>
  <artifactId>LangChain4j</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>pom</packaging>

  <name>LangChain4j父工程</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>17</java.version>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
    <!-- Spring Boot -->
    <spring-boot.version>3.5.0</spring-boot.version>
    <!-- Spring AI -->
    <spring-ai.version>1.0.0</spring-ai.version>
    <!-- Spring AI Alibaba -->
    <spring-ai-alibaba.version>1.0.0-M6.1</spring-ai-alibaba.version>
    <!-- langchain4j -->
    <langchain4j.version>1.0.1</langchain4j.version>
    <!--langchain4j-community 引入阿里云百炼平台依赖管理清单-->
    <langchain4j-community.version>1.0.1-beta6</langchain4j-community.version>
    <!-- maven plugin -->
    <maven-deploy-plugin.version>3.1.1</maven-deploy-plugin.version>
    <flatten-maven-plugin.version>1.3.0</flatten-maven-plugin.version>
    <maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
  </properties>

  <dependencyManagement>
    <dependencies>
      <!-- Spring Boot -->
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-dependencies</artifactId>
        <version>${spring-boot.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
      <!-- Spring AI -->
      <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-bom</artifactId>
        <version>${spring-ai.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
      <!-- Spring AI Alibaba -->
      <dependency>
        <groupId>com.alibaba.cloud.ai</groupId>
        <artifactId>spring-ai-alibaba-starter</artifactId>
        <version>${spring-ai-alibaba.version}</version>
      </dependency>
      <!--langchain4j的依赖清单,加载BOM后所有langchain4j版本号可以被统一管理起来
      https://docs.langchain4j.dev/get-started
      -->
      <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-bom</artifactId>
        <version>${langchain4j.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
      <!--引入阿里云百炼平台依赖管理清单
     https://docs.langchain4j.dev/integrations/language-models/dashscope
     -->
      <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-community-bom</artifactId>
        <version>${langchain4j-community.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <version>${spring-boot.version}</version>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-deploy-plugin</artifactId>
        <version>${maven-deploy-plugin.version}</version>
        <configuration>
          <skip>true</skip>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>${maven-compiler-plugin.version}</version>
        <configuration>
          <release>${java.version}</release>
          <compilerArgs>
            <compilerArg>-parameters</compilerArg>
          </compilerArgs>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>flatten-maven-plugin</artifactId>
        <version>${flatten-maven-plugin.version}</version>
        <inherited>true</inherited>
        <executions>
          <execution>
            <id>flatten</id>
            <phase>process-resources</phase>
            <goals>
              <goal>flatten</goal>
            </goals>
            <configuration>
              <updatePomFile>true</updatePomFile>
              <flattenMode>ossrh</flattenMode>
              <pomElements>
                <distributionManagement>remove</distributionManagement>
                <dependencyManagement>remove</dependencyManagement>
                <repositories>remove</repositories>
                <scm>keep</scm>
                <url>keep</url>
                <organization>resolve</organization>
              </pomElements>
            </configuration>
          </execution>
          <execution>
            <id>flatten.clean</id>
            <phase>clean</phase>
            <goals>
              <goal>clean</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

  <repositories>
    <repository>
      <id>spring-milestones</id>
      <name>Spring Milestones</name>
      <url>https://repo.spring.io/milestone</url>
      <snapshots>
        <enabled>false</enabled>
      </snapshots>
    </repository>
    <repository>
      <id>spring-snapshots</id>
      <name>Spring Snapshots</name>
      <url>https://repo.spring.io/snapshot</url>
      <releases>
        <enabled>false</enabled>
      </releases>
    </repository>
    <repository>
      <id>aliyunmaven</id>
      <name>aliyun</name>
      <url>https://maven.aliyun.com/repository/public</url>
    </repository>
  </repositories>

  <pluginRepositories>
    <pluginRepository>
      <id>public</id>
      <name>aliyun nexus</name>
      <url>https://maven.aliyun.com/repository/public</url>
      <releases>
        <enabled>true</enabled>
      </releases>
      <snapshots>
        <enabled>false</enabled>
      </snapshots>
    </pluginRepository>
  </pluginRepositories>

</project>

image-20250726181619866

创建一个子maven项目LangChain4j-helloWorld

pom

xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.lazy</groupId>
        <artifactId>LangChain4j</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <name>LangChain4j入门案例之HelloWorld</name>
    <artifactId>LangChain4j-helloWorld</artifactId>
    <packaging>jar</packaging>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>


    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--langchain4j-open-ai 基础-->
        <!--所有调用均基于 OpenAI 协议标准,实现一致的接口设计与规范LangChain4j 提供与许多 LLM 提供商的集成
        从最简单的开始方式是从 OpenAI 集成开始https://docs.langchain4j.dev/get-started    -->
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-open-ai</artifactId>
        </dependency>
        <!--langchain4j 高阶-->
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j</artifactId>
        </dependency>
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!--test-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

image-20250726180458415

yaml

yaml
server:
  port: 9001
spring:
  application:
    name: LangChain4j-helloWorld

业务类

  • ApiKey不建议明文,需配置进环境变量

    • 修改环境变量

      image-20250726181133054

  • 以防不生效,建议重启IDEA

  • 如果遇到这种情况,重启电脑试试

    image-20250726182712048

LLMConfig

java
package com.lazy.config;

import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class LLMConfig {

    @Bean
    public ChatModel chatModelQWen() {
        return OpenAiChatModel.builder()
                .apiKey(System.getenv("aliQWen-api"))//从系统环境变量获得key
                .modelName("qwen-plus")//模型名称
                .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")//模型接口地址
                .build();
    }
}

image-20250726181642117

HelloLangChainController

java
package com.lazy.controller;

import dev.langchain4j.model.chat.ChatModel;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloLangChainController {

    @Resource
    private ChatModel chatModelQWen;

    @GetMapping("/langchain4j/hello")
    public String hello(@RequestParam(value = "question",defaultValue = "你是谁?") String question) {
        String result = chatModelQWen.chat(question);
        System.out.println("通过LangChain4j调用QWen返回结果:"+result);
        return result;
    }
}

测试

image-20250726183121805

image-20250726183235487

image-20250726183301694

如何同时存在多种大模型在系统里共存使用?

DeepSeek API 文档

https://platform.deepseek.com/usage

API 文档:https://api-docs.deepseek.com/zh-cn/

如果没有申请 API-key 先申请 api-key

大模型调用三件套

  • 获得 Api-key

    image-20250727170829705

  • 获得模型名

    image-20250727170919576

  • 获得 baseUrl 开发地址

    image-20250727170957194

备注:

​ 通过指定 model='deepseek-chat' 即可调用 DeepSeek-V3 (普通)

​ 通过指定 model='deepseek-reasoner'即可调用 DeepSeek-R1(深度)。

多模型共存使用

新建module

LangChain4j-02multi-model-together

pom

xml
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!--langchain4j-open-ai 基础-->
    <!--所有调用均基于 OpenAI 协议标准,实现一致的接口设计与规范LangChain4j 提供与许多 LLM 提供商的集成
    从最简单的开始方式是从 OpenAI 集成开始https://docs.langchain4j.dev/get-started    -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-open-ai</artifactId>
    </dependency>
    <!--langchain4j 高阶-->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j</artifactId>
    </dependency>
    <!--lombok-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <!--test-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

yaml

yaml
server:
  port: 9002
spring:
  application:
    name: LangChain4j-02multi-model-together

业务类

LLMConfig

java
package com.lazy.config;

import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class LLMConfig {

    @Bean(name = "QWen")
    public ChatModel chatModelQWen() {
        return OpenAiChatModel.builder()
                .apiKey(System.getenv("aliQWen-api"))//从系统环境变量获得key
                .modelName("qwen-plus")//模型名称
                .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")//模型接口地址
                .build();
    }

    @Bean(name = "DeepSeek")
    public ChatModel chatModelDeepSeek() {
        return OpenAiChatModel.builder()
                .apiKey(System.getenv("deepseek-api"))
                .modelName("deepseek-chat")
                //.modelName("deepseek-reasoner") //深度思考
                .baseUrl("https://api.deepseek.com/v1")
                .build();
    }
}

image-20250727172227837

MultiModelController

java
package com.lazy.controller;

import dev.langchain4j.model.chat.ChatModel;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MultiModelController {

    @Resource(name = "QWen")
    private ChatModel chatModelQWen;

    @Resource(name = "DeepSeek")
    private ChatModel chatModelDeepSeek;

    @GetMapping("/multimodel/qwen")
    public String QWen(@RequestParam(name = "prompt",defaultValue = "你是谁") String prompt) {
        String chatQWen = chatModelQWen.chat(prompt);
        System.out.println("模型返回结果:\t"+chatQWen);
        return chatQWen;
    }

    @GetMapping("/multimodel/deepseek")
    public String DeepSeek(@RequestParam(name = "prompt",defaultValue = "你是谁") String prompt) {
        String chatDeepSeek = chatModelDeepSeek.chat(prompt);
        System.out.println("模型返回结果:\t"+chatDeepSeek);
        return chatDeepSeek;
    }
}

启动项目,测试!

image-20250727172904730

image-20250727172945597

访问deepseek报错了,重启试试

image-20250727173310184

我们充点钱再试试!

image-20250727174049537

原生整合和SpringBoot整合

官网

  • 中文:https://docs.langchain4j.info/tutorials/spring-boot-integration
  • 英文:https://docs.langchain4j.dev/tutorials/spring-boot-integration

image-20250727175750576

  • 低阶 api VS 高阶 api

    • SpringBoot 整合低阶 api 所需要的 pom

      xml
      <dependency>
          <groupId>dev.langchain4j</groupId>
          <artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>
          <version>1.0.0-beta3</version>
      </dependency>

      image-20250727175946749

    • SpringBoot 整合高阶 api 所需要的 pom

      image-20250727180214932

    • 总结 image-20250727180250524

LangChain4j 原生 VS LangChain4j-Boot整合

  • LangChain4j 原生整合

    image-20250727180515893

    LangChain4j-Boot整合

    image-20250727180543192

    总结

    image-20250727180250524

    LangChain4j 原生集成与使用 LangChain4j-Boot(Spring Boot Starter)进行整合的主要区别在于 易用性、配置管理、自动装配和与 Spring 生态的集成深度。以下是详细对比:

    1. 依赖管理
    • 原生 LangChain4j

      • 需手动添加核心库及模型提供者(如 OpenAI、HuggingFace)的依赖。

      • 示例 Maven 依赖:

        xml
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-core</artifactId>
            <version>0.31.0</version>
        </dependency>
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-open-ai</artifactId>
            <version>0.31.0</version>
        </dependency>
    • LangChain4j-Boot

      • 通过单一 Starter 依赖自动引入核心库和常用模块。

      • 示例:

        xml
        <dependency>
            <groupId>io.github.microutils</groupId>
            <artifactId>langchain4j-boot-spring-boot-starter</artifactId>
            <version>0.1.0</version> <!-- 版本可能变化 -->
        </dependency>
    1. 配置方式
    • 原生 LangChain4j

      • 手动编码创建组件(如 OpenAiChatModel),需硬编码 API Key 或自行管理配置。

        java
        OpenAiChatModel model = OpenAiChatModel.builder()
            .apiKey("sk-...")
            .modelName("gpt-4")
            .build();
    • LangChain4j-Boot

      • 通过 application.yml/application.properties 集中配置,支持多模型切换。

        yaml
        langchain4j:
          openai:
            api-key: ${OPENAI_API_KEY}
            model: gpt-4-turbo
          huggingface:
            api-key: ${HF_API_KEY}
      • 自动注入 Bean,无需手动构建:

        java
        @Autowired
        private ChatModel chatModel; // 根据配置自动选择 OpenAI/HuggingFace
  1. 自动装配与扩展
  • 原生 LangChain4j

    • 需自行管理组件生命周期(如注入 ChatMemoryTool)。
      • 需手动集成 Spring(如通过 @Bean 工厂方法)。
  • LangChain4j-Boot

    • 自动装配:根据配置创建 ChatModelEmbeddingModel 等 Bean。

    • 工具集成:自动扫描 @Tool 注解的 Bean 并注入 AiServices

      java
        @Service
        @Tool("查询用户信息")
        public class UserService {
            public String getUserEmail(String username) { ... }
        }
      • 对话内存:自动配置 ChatMemory(如 InMemoryChatMemory)并关联会话。
  1. Spring 生态集成
  • 原生 LangChain4j

    • 与 Spring 无深度整合,需自行实现:
      • 配置绑定(如 @ConfigurationProperties)。
      • 异常处理、健康检查。
  • LangChain4j-Boot

    • 配置绑定:通过 @ConfigurationProperties 映射配置项。

    • 健康检查:提供 /health 端点验证模型连接状态。

    • AOP 支持:通过注解管理对话上下文(如 @MemoryId 标识用户会话):

      java
        @Service
        public class ChatService {
            @Autowired private Assistant assistant;
        
            public String chat(String userId, String message) {
                return assistant.chat(userId, message); // 自动关联 userId 的对话历史
            }
        }
  1. 生产就绪特性
  • LangChain4j-Boot 额外提供:
    • 监控指标:集成 Micrometer 统计 Token 使用量、延迟等。
    • 异常处理:统一处理模型调用异常(如 ModelNotFoundException)。
    • 多模型切换:通过配置动态切换模型(如测试用 OpenAI,生产用 Azure OpenAI)。
  1. 代码简洁性对比
  • 原生实现示例(手动管理依赖和配置):

    java
      @Bean
      public ChatModel openAiChatModel() {
          return OpenAiChatModel.builder()
                  .apiKey(env.getProperty("openai.api-key"))
                  .modelName("gpt-4")
                  .build();
      }
    • LangChain4j-Boot 实现(零配置):
    java
      @Service
      public class MyAssistant {
          @Autowired private ChatModel chatModel; // 自动注入
         
          public String answer(String question) {
              return chatModel.generate(question);
          }
      }

总结:如何选择?

场景推荐方式
快速原型验证、小型项目原生 LangChain4j
Spring Boot 项目,需生产部署LangChain4j-Boot
需要动态配置/多模型支持LangChain4j-Boot
深度集成 Spring 生态(AOP、监控)LangChain4j-Boot

LangChain4j-Boot 的本质:通过 Spring Boot Starter 机制封装原生 LangChain4j,提供自动配置、依赖管理、生态集成,显著提升开发效率,降低样板代码量。

案例

moduleLangChain4j-03boot-integration

pom

xml
<properties>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!--langchain4j原生 基础-->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-open-ai</artifactId>
    </dependency>
    <!--langchain4j原生 高阶-->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j</artifactId>
    </dependency>
    <!--1 LangChain4j 整合boot底层支持-->
    <!--   https://docs.langchain4j.dev/tutorials/spring-boot-integration  -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>
    </dependency>
    <!--2 LangChain4j 整合boot高阶支持-->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-spring-boot-starter</artifactId>
    </dependency>
    <!--lombok-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <!--test-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

低阶api

java
package com.lazy.controller;


import dev.langchain4j.model.chat.ChatModel;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class PopularIntegrationController {

    @Resource
    private ChatModel chatModelQWen;

    @GetMapping("/langchain4j/chat")
    public String chatQWen(@RequestParam(value = "prompt",defaultValue = "你是谁") String prompt) {
        return chatModelQWen.chat(prompt);
    }
}

测试

image-20250728175234465

高阶api

ChatAssistantService

java
package com.lazy.service;

import dev.langchain4j.service.spring.AiService;

@AiService
public interface ChatAssistantService {

    String chat(String prompt);
}

DeclarativeAIController

java
package com.lazy.controller;

import com.lazy.service.ChatAssistantService;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class DeclarativeAIController {

    @Resource
    private ChatAssistantService chatAssistantService;

    @GetMapping("/declarative/chat")
    public String declarativeChat(@RequestParam(value = "prompt",defaultValue = "你是谁") String prompt) {
        return chatAssistantService.chat(prompt);
    }
}

测试

image-20250728175842671

高阶api集成官网解释

image-20250728175932197

低阶和高阶API

LangChain4j在两个抽象层提供不同的api

image-20250728181931183

低阶api

ChatLanguageModelUserMessageAiMessageEmbeddingStoreEmbedding

优点:是可以自由组合适宜于各个组件但编码量高

  • ChatModel

    image-20250728182226123

    • low-level(低阶api)模型api,提供各种chat方法用于对话,可以接受单个或多个消息

    • ChatModel 提供的一种极其简便的方法

      java
      default String chat(String userMessage) {
          ChatRequest chatRequest = ChatRequest.builder()
                  .messages(UserMessage.from(userMessage))
                  .build();
      
          ChatResponse chatResponse = chat(chatRequest);
          return chatResponse.aiMessage().text();
      }
      java
      @GetMapping(value = "/langchain4j/hello")
      public String hello(@RequestParam(value = "prompt", defaultValue = "你是谁") String prompt)
      {
          String result = chatModel.chat(prompt);
      
          System.out.println("通过langchain4j调用模型返回结果:\n"+result);
      
          return result;
      }

高阶api

image-20250728182606770

  • AiServices、Tools等
  • 程序员自己定义接口,通过AiServices 类里面的方法实现,优点是api封装度比较高,减少了代码的复杂性,但仍可以进行灵活的微调

image-20250728182945998

创建由提供的聊天模型支持的 AI 服务(所提供接口的实现)。这种方便的方法可用于创建简单的 AI 服务。对于更复杂的情况,请使用 builder。

LangChian4j提供大模型接口、提示词模板、结构化输出、对话记忆、文档加载、文档分割、向量模型、向量存储等组件

image-20250728183545824

代码案例

低阶api(token使用情况)

大模型中的 Token VS Web开发中的Token

  • 大模型中的token

    image-20250729175655254

  • Web开发中的token

    在web开发中,“token”通常指的是用于认证和授权的一种加密字符串。它被用来确保用户身份的安全验证,比如JWT(JSON Web Token)。这类token一般由服务器生成,并发给客户端保存(例如存储在浏览器的本地存储或cookie中),之后每次请求都需要携带这个token来证明用户的身份。

  • 区别

    • 目的不同:大模型中的token是为了将文本分割成可处理的单元,便于进行计算;而web开发中的token主要用于安全地传递用户身份信息
    • 生成方式不同:前者通过特定的算法(如BPE)对文本进行分割得到;后者则通常是通过加密算法生成的唯一字符串。
    • 应用场景不同:前者应用于文本分析、机器翻译等NLP任务;后者多见于用户登录系统、API访问控制等领域。

新建moduleLangChain4j-04low-high-api

pom

xml
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-open-ai</artifactId>
    </dependency>
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
</dependencies>

LLMConfig

java
package com.lazy.config;

import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class LLMConfig {

    @Bean(name = "QWen")
    public ChatModel chatModelQWen() {
        return OpenAiChatModel.builder()
                .apiKey(System.getenv("aliQWen-api"))//从系统环境变量获得key
                .modelName("qwen-plus")//模型名称
                .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")//模型接口地址
                .build();
    }

    @Bean(name = "DeepSeek")
    public ChatModel chatModelDeepSeek() {
        return OpenAiChatModel.builder()
                .apiKey(System.getenv("deepseek-api"))
                .modelName("deepseek-chat")
                //.modelName("deepseek-reasoner") //深度思考
                .baseUrl("https://api.deepseek.com/v1")
                .build();
    }
}

LowApiController

java
package com.lazy.controller;

import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.chat.response.ChatResponse;
import dev.langchain4j.model.output.TokenUsage;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class LowApiController {

    @Resource(name = "DeepSeek")
    private ChatModel chatModelQWen;

    @GetMapping("/lowapi/api")
    public String api(@RequestParam(name = "prompt", defaultValue = "你是谁") String prompt) {
        ChatResponse chatResponse = chatModelQWen.chat(UserMessage.userMessage(prompt));//使用带返回结果的api
        String text = chatResponse.aiMessage().text();
        System.out.println("大模型调用返回结果:" + text);
        TokenUsage token = chatResponse.tokenUsage();
        System.out.println("本次调用结果消耗的token:" + token);
        return text + "\t\n" + token;
    }
}

测试

image-20250729180254807

高阶api

image-20250729182807600

AI Service 是如何工作的?

image-20250729182854035

配置

  1. 定义 AI Service 接口

    java
    package com.lazy.service;
    
    public interface ChatAssistant {
        String chat(String prompt);
    }
  2. LLMConfig 类配置调用三件套

    java
    package com.lazy.config;
    
    import dev.langchain4j.model.chat.ChatModel;
    import dev.langchain4j.model.openai.OpenAiChatModel;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class LLMConfig
    {
        @Bean
        public ChatModel chatModelQwen()
        {
            return OpenAiChatModel.builder()
                            .apiKey(System.getenv("aliQWen-api"))
                            .modelName("qwen-plus")
                            .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
                    .build();
        }
    
    }
  3. AI Service 对应接口实现类落地

    java
    package com.lazy.config;
    
    import com.atguigu.study.service.ChatAssistant;
    import dev.langchain4j.model.chat.ChatModel;
    import dev.langchain4j.model.openai.OpenAiChatModel;
    import dev.langchain4j.service.AiServices;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    /**
     * @auther zzyybs@126.com
     * @Date 2025-05-27 22:04
     * @Description: 知识出处 https://docs.langchain4j.dev/get-started
     */
    @Configuration
    public class LLMConfig
    {
        @Bean(name = "qwen")
        public ChatModel chatModelQwen()
        {
            return OpenAiChatModel.builder()
                        .apiKey(System.getenv("aliQWen-api"))
                        .modelName("qwen-plus")
                        .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
                    .build();
        }
     
    public interface ChatAssistant{
        String chat(String prompt);
    }
    
        @Bean
        public ChatAssistant chatAssistant(@Qualifier("qwen") ChatModel chatModelQwen)
        {
            return AiServices.create(ChatAssistant.class, chatModelQwen);
        }
    }

HighApiController

java
package com.lazy.controller;

import com.lazy.service.ChatAssistant;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HighApiController {

    @Resource
    private ChatAssistant chatAssistant;

    @GetMapping("/high/highapi")
    public String highApi(@RequestParam(value = "prompt",defaultValue = "你是谁") String prompt) {
        return chatAssistant.chat(prompt);
    }
}

测试

image-20250729183709764

注意:

@AiService 注解是与SpringBoot整合的包才能使用,当前我们没有引用于SpringBoot整合的包,使用的是langchain4j原生的包,所以不能使用@AiService注解

模型参数配置

官网:

module:LangChain4j-05model-parameters

pom

xml
<properties>
    <maven.compiler.target>17</maven.compiler.target>
    <maven.compiler.source>17</maven.compiler.source>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- langchain4j 基础 -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-open-ai</artifactId>
    </dependency>
    <!-- langchain4j 高阶 -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j</artifactId>
    </dependency>
    <!-- hutool-all -->
    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
</dependencies>

yaml

yaml
server:
  port: 9003
spring:
  application:
    name: LangChain4j-05model-parameters

LLMConfig

java
package com.lazy.config;

import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class LLMConfig {

    @Bean
    public ChatModel chatModelQWen() {
        return OpenAiChatModel.builder()
                .apiKey(System.getenv("aliQWen-api"))//从系统环境变量获得key
                .modelName("qwen-plus")//模型名称
                .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")//模型接口地址
                .build();
    }
}

Controller

java
package com.lazy.controller;

import dev.langchain4j.model.chat.ChatModel;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ModelParameterController {

    @Resource
    private ChatModel chatModel;

    @GetMapping("/modelparam/config")
    public String config(@RequestParam(value = "prompt",defaultValue = "你是谁") String prompt) {
        return chatModel.chat(prompt);
    }
}

日志配置(Logging)

https://docs.langchain4j.info/tutorials/logging

  1. 引入日志包

    image-20250730164249220

  2. application.yaml,开启日志

    image-20250730164408784

    yaml
    logging:
      level:
        dev:
          langchain4j: DEBUG
  3. 开启日志

    java
    package com.lazy.config;
    
    import dev.langchain4j.model.chat.ChatModel;
    import dev.langchain4j.model.openai.OpenAiChatModel;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class LLMConfig {
    
        @Bean
        public ChatModel chatModelQWen() {
            return OpenAiChatModel.builder()
                    .apiKey(System.getenv("aliQWen-api"))//从系统环境变量获得key
                    .modelName("qwen-plus")//模型名称
                    .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")//模型接口地址
                    .logRequests(true) //开启请求日志
                    .logResponses(true) //开启响应日志
                    .build();
        }
    }

    测试

    image-20250730164745583

    image-20250730164906389

监控(observability)

https://docs.langchain4j.info/tutorials/observability

某些 ChatLanguageModelStreamingChatLanguageModel 的实现 (参见"可观测性"列)允许配置 ChatModelListener(多个)来监听事件,例如:

  • 向 LLM 发送的请求
  • 来自 LLM 的响应
  • 错误

我们就可以定义监听器,来监听这些事件的发生,如果发生意外,好做及时的处理!

结论

image-20250730174441031

他是如何工作的?

  • 监听器被指定为 List<ChatModelListener>,并按照迭代顺序调用。
  • 监听器同步调用,并在同一线程中调用。有关流式处理情况的更多详细信息,请参见下文。 第二个监听器直到第一个监听器返回后才会被调用。
  • ChatModelListener.onRequest() 方法在调用 LLM 提供商 API 之前立即调用。
  • ChatModelListener.onRequest() 方法每个请求只调用一次。 如果在调用 LLM 提供商 API 时发生错误并进行重试, ChatModelListener.onRequest() 将***不会***为每次重试调用。
  • ChatModelListener.onResponse() 方法只调用一次, 在从 LLM 提供商收到成功响应后立即调用。
  • ChatModelListener.onError() 方法只调用一次。 如果在调用 LLM 提供商 API 时发生错误并进行重试, ChatModelListener.onError() 将***不会***为每次重试调用。
  • 如果从 ChatModelListener 方法之一抛出异常, 它将以 WARN 级别记录。后续监听器的执行将照常继续。
  • 通过 ChatModelRequestContextChatModelResponseContextChatModelErrorContext 提供的 ChatRequest 是最终请求,包含在 ChatLanguageModel 上配置的默认 ChatRequestParameters 和特定于请求的 ChatRequestParameters 合并在一起。
  • 对于 StreamingChatLanguageModelChatModelListener.onResponse()ChatModelListener.onError() 在与 ChatModelListener.onRequest() 不同的线程上调用。 线程上下文目前不会自动传播,因此您可能希望使用 attributes 映射 从 ChatModelListener.onRequest() 传播任何必要的数据到 ChatModelListener.onResponse()ChatModelListener.onError()
  • 对于 StreamingChatLanguageModelChatModelListener.onResponse()StreamingChatResponseHandler.onCompleteResponse() 被调用之前调用。ChatModelListener.onError()StreamingChatResponseHandler.onError() 被调用之前调用。

案例

在上面的代码进行改造

创建一个MyChatModelLinstener监听器,并实现ChatModelLinstener接口

java
package com.lazy.listener;

import cn.hutool.core.util.IdUtil;
import dev.langchain4j.model.chat.listener.ChatModelErrorContext;
import dev.langchain4j.model.chat.listener.ChatModelListener;
import dev.langchain4j.model.chat.listener.ChatModelRequestContext;
import dev.langchain4j.model.chat.listener.ChatModelResponseContext;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class MyChatModelListener implements ChatModelListener {
    @Override
    public void onRequest(ChatModelRequestContext requestContext) {
        String requestId = IdUtil.simpleUUID();
        requestContext.attributes().put("traceId", requestId);
        log.info("请求参数requestContext:{}", requestContext+":"+requestId);
    }

    @Override
    public void onResponse(ChatModelResponseContext responseContext) {
        Object traceId = responseContext.attributes().get("traceId");
        log.info("返回结果responseContext:{}",traceId);
    }

    @Override
    public void onError(ChatModelErrorContext errorContext) {
        log.info("发生错误了errorContext:{}",errorContext);
    }
}

将监听器,添加到大模型配置类里面

LLMConfig

java
package com.lazy.config;

import com.lazy.listener.MyChatModelListener;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.List;

@Configuration
public class LLMConfig {

    @Bean
    public ChatModel chatModelQWen() {
        return OpenAiChatModel.builder()
                .apiKey(System.getenv("aliQWen-api"))//从系统环境变量获得key
                .modelName("qwen-plus")//模型名称
                .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")//模型接口地址
                .logRequests(true) //开启请求日志
                .logResponses(true) //开启响应日志
                .listeners(List.of(new MyChatModelListener()))//将我们写入的监听器,添加到里面
                .build();
    }
}

由于listeners方法传递的是List集合,所以我们使用List.of添加List集合里面在传入

image-20250730175000674

测试

image-20250730175108302

image-20250730175301176

重试机制(Retry Configuration)

image-20250730175824264

image-20250730180026248

改造LLMConfig,添加重试和超时配置

java
package com.lazy.config;

import com.lazy.listener.MyChatModelListener;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.time.Duration;
import java.util.List;

@Configuration
public class LLMConfig {

    @Bean
    public ChatModel chatModelQWen() {
        return OpenAiChatModel.builder()
                .apiKey(System.getenv("aliQWen-api"))//从系统环境变量获得key
                .modelName("qwen-plus")//模型名称
                .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")//模型接口地址
                .logRequests(true) //开启请求日志
                .logResponses(true) //开启响应日志
                .listeners(List.of(new MyChatModelListener()))
                .maxRetries(3)//最大重试次数
                .timeout(Duration.ofMillis(2000))//超时时间2秒
                .build();
    }
}

测试

正常的情况

image-20250730180457391

异常情况

image-20250730181200571

发现重试了4次,因为,重试的时候不包含第一次请求的

超时控制(timeout)

向大模型发送请求,如过在指定的时间内没有收到响应,该请求被中断并且报request timed out(请求超时)

在重试机制演示了超时控制,所以不在演示

自定义头

LangChain4j 中,customHeaders 是一个用于向 HTTP 请求添加**自定义请求头(HTTP Headers)**的配置选项。它主要用于与外部 API(如 OpenAI、Azure OpenAI、Hugging Face 等模型服务)交互时,传递额外的元数据或认证信息。

核心作用:

  1. 自定义认证:添加 API 密钥、Token 或其他认证凭据。
  2. 传递元数据:添加业务相关的标识(如请求来源、版本号)。
  3. 兼容特定服务:适配某些 API 的特殊头部要求(如 x-api-key)。

案例

改造LLMConfig

java
package com.lazy.config;

import com.lazy.listener.MyChatModelListener;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.time.Duration;
import java.util.List;
import java.util.Map;

@Configuration
public class LLMConfig {

    @Bean
    public ChatModel chatModelQWen() {
        return OpenAiChatModel.builder()
                .apiKey(System.getenv("aliQWen-api"))//从系统环境变量获得key
                .modelName("qwen-plus")//模型名称
                .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")//模型接口地址
                .logRequests(true) //开启请求日志
                .logResponses(true) //开启响应日志
                .listeners(List.of(new MyChatModelListener()))
                .maxRetries(3)//最大重试次数
                .timeout(Duration.ofMillis(2000))//超时时间2秒
                .customHeaders(Map.of(
                        "X-Custom-Header", "value1",
                        "User-Origin", "my-app"
                )) //自定义头部信息
                .build();
    }
}

测试

image-20250730183615607

注意事项:

  1. 优先级:若与 LangChain4j 默认头部(如 Authorization)冲突,customHeaders 中的值会覆盖默认值
  2. 安全性:避免在 customHeaders 中硬编码敏感信息(建议通过环境变量注入)。
  3. 服务兼容性:不同 API 对头部的支持不同(如 OpenAI 用 Authorization,Azure 用 api-key)。
  4. 自定义头是添加到请求头上的,而不是响应头!

多模态视觉理解

“多模态视觉” 是指 计算机视觉技术与其他感知模态(如语言、声音、触觉等)相结合 的研究和应用领域。它的核心思想是模仿人类感知世界的方式——我们并非仅仅依靠眼睛看,而是综合运用视觉、听觉、触觉、语言理解等多种感官信息来理解和交互环境。

就是利用图片和文字跟 AI 进行对话!

所有支持的语言模型比较表

提供商流式处理工具 (同步/流式)JSON SchemaJSON 模式支持的模态 (输入)可观测性可自定义 HTTP 客户端本地部署支持原生镜像备注
Amazon Bedrock (Converse API)✅/✅文本, 图像, PDF
Amazon Bedrock (Invoke API)✅/❌文本
Anthropic✅/✅文本, 图像🆘 #2469
Azure OpenAI✅/✅文本, 图像
ChatGLM文本
DashScope✅/✅文本, 图像, 音频
GitHub Models✅/✅🔜 #1911文本, 图像
Google AI Gemini✅/✅文本, 图像, 音频, 视频, PDF
Google Vertex AI Gemini✅/✅🆘 #1717文本, 图像, 音频, 视频, PDF
Google Vertex AI PaLM 2文本
Hugging Face文本
Jlama✅/✅文本
LocalAI✅/✅文本, 图像, 音频
Mistral AI✅/✅文本🆘 #2524
Ollama✅/✅文本, 图像
OpenAI✅/✅文本, 图像, 音频兼容:Ollama, LM Studio, GPT4All, 等兼容:Groq, 等
Qianfan✅/✅文本
Cloudflare Workers AI文本
Zhipu AI✅/✅文本, 图像

图例:

  • ✅ 表示"支持"
  • 🆘 表示"尚未支持;请帮助我们实现它"
  • 🔜 表示"正在实现中;请等待"
  • ❌ 表示"LLM 提供商不支持"

image-20250731163201758

image-20250731163500077

选择模型

我们用阿里云百炼平台,地址:https://bailian.console.aliyun.com/

image-20250731163938529

模型名字,点击要选择的模型,查看详情

image-20250731164021669

模型的api地址,点击查看api参考

image-20250731164108219

image-20250731164130447

code 案例

example 1(LangChain4j 原生)

module:LangChain4j-06chat-image

pom

xml
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-open-ai</artifactId>
    </dependency>
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

引入图片到resources/static/image

image-20250731170056487

LLMConfig

java
package com.lazy.config;

import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class LLMConfig {

    @Bean
    public ChatModel chatModelQWen() {
        return OpenAiChatModel.builder()
                .apiKey(System.getenv("aliQWen-api"))
                //qwen-vl-max 是一个多模态大模型,支持图片和文本的结合输入,适用于视觉-语言任务。
                .modelName("qwen-vl-max")
                .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
                .build();
    }
}

编码步骤

  1. 图片转码:通过Base64编码将图片转化为字符串
  2. 提示词指定:结合ImageContentTextContent一起发送到模型处理
  3. API调用:使用OpenAiChatModel来构建请求,并通过chat()方法调用模型。请求内容包括文本提示和图片,模型会根据输入返回分析结果。
  4. 解析与输出:从ChatResponse中获取AI大模型的回复,打印出处理后的结果。

controller

java
package com.lazy.controller;

import dev.langchain4j.data.message.ImageContent;
import dev.langchain4j.data.message.TextContent;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.chat.response.ChatResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;
import java.util.Base64;

@RestController
public class ImageModelController {

    @Value("classpath:static/yejing.png")
    private Resource resource;

    @Autowired
    private ChatModel chatModel;

    @GetMapping("/image/call")
    public String call() throws IOException {
        //1. 图片转码:通过Base64编码将图片转化为字符串
        byte[] byteArray = resource.getContentAsByteArray();
        //将图片转成base64编码
        String base64Data = Base64.getEncoder().encodeToString(byteArray);
        //2.提示词指定:结合ImageContent和TextContent一起发送到模型处理
        UserMessage userMessage = UserMessage.from(
                TextContent.from("从图片你能得到什么信息?"),
                ImageContent.from(base64Data,"image/png")
        );
        //3.解析与输出:从ChatResponse中获取AI大模型的回复,打印出处理后的结果。
        ChatResponse chatResponse = chatModel.chat(userMessage);
        String result = chatResponse.aiMessage().text();
        System.out.println(result);
        return result;
    }
}

测试

image-20250731172941409

image-20250731173034251

为什么用AiMessage?

image-20250731172105810

example 2(LangChain4j跟第三方整合)

地址:https://docs.langchain4j.info/integrations/language-models/dashscope

结合阿里巴巴通义万相进行图像理解,其支持视觉+语言的多模态任务

image-20250731175525908

切换通义万相-文生图模型wanx2.1-t2i-turbo

image-20250731180551041

两种方式

  1. 原生

    image-20250731175727807

  2. 集成SpringBoot

    image-20250731175746397

我们在父工程的pom文件引入,物料管理清单

xml
<langchain4j-community.version>1.0.1-beta6</langchain4j-community.version>
<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-community-bom</artifactId>
    <version>${langchain4j-community.version}</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>

在子项目下引入

xml
<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-community-dashscope-spring-boot-starter</artifactId>
</dependency>

修改LLMConfig

java
package com.lazy.config;

import dev.langchain4j.community.model.dashscope.WanxImageModel;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class LLMConfig {

    /**
     * 测试通义万象来实现图片生成,
     * @return
     */
    @Bean
    public WanxImageModel wanxImageModel(){
        return WanxImageModel.builder()
                .apiKey(System.getenv("aliQWen-api"))
                .modelName("wanx2.1-t2i-turbo") //图片生成 https://help.aliyun.com/zh/model-studio/text-to-image
                .build();
    }
}

WanxImageModelController

java
package com.lazy.controller;

import com.alibaba.dashscope.aigc.imagesynthesis.ImageSynthesis;
import com.alibaba.dashscope.aigc.imagesynthesis.ImageSynthesisParam;
import com.alibaba.dashscope.aigc.imagesynthesis.ImageSynthesisResult;
import com.alibaba.dashscope.exception.NoApiKeyException;
import com.alibaba.dashscope.utils.JsonUtils;
import dev.langchain4j.community.model.dashscope.WanxImageModel;
import dev.langchain4j.data.image.Image;
import dev.langchain4j.model.output.Response;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class WanxImageModelController {

    @Autowired
    private WanxImageModel wanxImageModel;

    @GetMapping("/image/create")
    public String createImageModel() {
        Response<Image> imageResponse = wanxImageModel.generate("风景照");
        System.out.println(imageResponse.content().url());
        return imageResponse.content().toString();
    }

    @GetMapping("/image/create2")
    public String create2ImageModel() {
        String prompt = "图片风格为3D渲染,中国风,一个巨大的展开的卷轴在空中飘浮,金银色调,玉髓雕刻," +
                "色彩细腻,广角镜头,blender 渲染,丰富细节,超高清";
        ImageSynthesisParam param = ImageSynthesisParam.builder()
                .apiKey(System.getenv("aliQWen-api"))
                .model(ImageSynthesis.Models.WANX_V1) //wanx_v1模型
                .prompt(prompt) //图片要求
                .style("<watercolor>") //水彩画
                .n(1)  //数量
                .size("1024*1024") //大小
                .build();
        ImageSynthesis imageSynthesis = new ImageSynthesis();
        ImageSynthesisResult result;
        try {
            result = imageSynthesis.call(param);
        } catch (NoApiKeyException e) {
            throw new RuntimeException(e);
        }
        return JsonUtils.toJson(result);
    }
}

测试

image-20250731182312800

image-20250731182343441

image-20250731182834973

image-20250731182905130

流式输出(streamingoutput)

网址:https://docs.langchain4j.info/tutorials/response-streaming

是什么?

  • 是一种逐步返回大模型生成结果的技术,生成一点返回一点,允许服务器将响应内容分批次实时传输给客户端,而不是等待全部内容生成完毕后再一次性返回。这种机制能显著提升用户体验,尤其适用于大模型响应较慢的场景(如生成长文本或复杂推理结果)。

image-20250801184653680

低阶api

建module:LangChain4j-07chat-stream

pom

xml
<properties>
    <maven.compiler.target>17</maven.compiler.target>
    <maven.compiler.source>17</maven.compiler.source>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-open-ai</artifactId>
    </dependency>
</dependencies>

yaml

yaml
server:
  port: 9007
  # 设置响应的字符编码,避免流式返回输出乱码
  servlet:
    encoding:
      charset: utf-8
      enabled: true # 启用http编码支持
      force: true #  HTTP 请求和响应上强制编码到配置的字符集
spring:
  application:
    name: LangChain4j-07chat-stream

LLMConfig

java
package com.lazy.config;

import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.chat.StreamingChatModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.model.openai.OpenAiStreamingChatModel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class LLMConfig {


    /**
     * 普通对话
     * @return
     */
    @Bean
    public ChatModel chatModelQWen(){
        return OpenAiChatModel.builder()
                .apiKey(System.getenv("aliQWen-api"))
                .modelName("qwen-plus")
                .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
                .build();
    }
    /**
     * 配置流式输出
     * @return
     */
    @Bean
    public StreamingChatModel streamingChatModelQWen() {
        return OpenAiStreamingChatModel.builder()
                .apiKey(System.getenv("aliQWen-api"))
                .modelName("qwen-plus")
                .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
                .build();
    }
}

StreamChatModelController

java
package com.lazy.controller;

import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.chat.StreamingChatModel;
import dev.langchain4j.model.chat.response.ChatResponse;
import dev.langchain4j.model.chat.response.StreamingChatResponseHandler;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class StreamChatModelController {

    @Resource
    private ChatModel chatModelQWen;

    @Resource
    private StreamingChatModel streamingChatModel;

    @GetMapping("/chat")
    public String getStreamingChatModel(@RequestParam(value = "prompt",
            defaultValue = "说一下北京有什么好玩的地方?") String prompt) {
        return chatModelQWen.chat(prompt);
    }

    @GetMapping("/streaming")
    public void streaming(@RequestParam(value = "prompt",
            defaultValue = "说一下北京有什么好玩的地方?") String prompt) {
         streamingChatModel.chat(prompt,new StreamingChatResponseHandler() {

             @Override
             public void onPartialResponse(String partialResponse) {
                 System.out.println(partialResponse);
             }

             @Override
             public void onCompleteResponse(ChatResponse completeResponse) {
                 System.out.println(completeResponse);
             }

             @Override
             public void onError(Throwable throwable) {
                 System.out.println(throwable);
             }
         });
    }
}

测试

普通的

image-20250801191108273

流水输出

image-20250801191136710

高阶api(结合webFlux编程)

pom

xml
<properties>
    <maven.compiler.target>17</maven.compiler.target>
    <maven.compiler.source>17</maven.compiler.source>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- 高阶api -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j</artifactId>
    </dependency>
    <!-- langchain4j 结合web Flux编程 -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-reactor</artifactId>
    </dependency>
</dependencies>

yaml

yaml
server:
  port: 9007
  # 设置响应的字符编码,避免流式返回输出乱码
  servlet:
    encoding:
      charset: utf-8
      enabled: true # 启用http编码支持
      force: true #  HTTP 请求和响应上强制编码到配置的字符集
spring:
  application:
    name: LangChain4j-07chat-stream

LLMConfig

java
package com.lazy.config;

import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.chat.StreamingChatModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.model.openai.OpenAiStreamingChatModel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class LLMConfig {

    /**
     * 普通对话
     * @return
     */
    @Bean
    public ChatModel chatModelQWen(){
        return OpenAiChatModel.builder()
                .apiKey(System.getenv("aliQWen-api"))
                .modelName("qwen-plus")
                .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
                .build();
    }
    /**
     * 配置流式输出
     * @return
     */
    @Bean
    public StreamingChatModel streamingChatModelQWen() {
        return OpenAiStreamingChatModel.builder()
                .apiKey(System.getenv("aliQWen-api"))
                .modelName("qwen-plus")
                .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
                .build();
    }
}

StreamingChatModelController

java
package com.lazy.controller;

import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.chat.StreamingChatModel;
import dev.langchain4j.model.chat.response.ChatResponse;
import dev.langchain4j.model.chat.response.StreamingChatResponseHandler;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

@RestController
public class StreamChatModelFluxController {

    @Resource
    private ChatModel chatModel;

    @Resource
    private StreamingChatModel streamingChatModel;

    @GetMapping("/flux/chat")
    public Flux<String> chat(@RequestParam(value = "prompt"
            ,defaultValue = "北京有什么好玩的地方") String prompt) {
        String chat = chatModel.chat(prompt);
        return Flux.just(chat);
    }

    @GetMapping("/flux/streaming")
    public Flux<String> stringFlux(@RequestParam(value = "prompt"
            ,defaultValue = "西安有什么好玩的地方?") String prompt){
        return Flux.create(fluxSink -> streamingChatModel.chat(prompt, new StreamingChatResponseHandler() {
            @Override
            public void onPartialResponse(String partialResponse) {
                fluxSink.next(partialResponse);
            }

            @Override
            public void onCompleteResponse(ChatResponse completeResponse) {
                fluxSink.complete();
            }

            @Override
            public void onError(Throwable error) {
                fluxSink.error(error);
            }
        }));
    }
}

测试

image-20250801192733281

流式输出

image-20250801192819786

接口式编程(结合Flux)

pom

xml
<properties>
    <maven.compiler.target>17</maven.compiler.target>
    <maven.compiler.source>17</maven.compiler.source>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- 高阶api -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j</artifactId>
    </dependency>
    <!-- langchain4j 结合web Flux编程 -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-reactor</artifactId>
    </dependency>
</dependencies>

yaml

yaml
server:
  port: 9007
  # 设置响应的字符编码,避免流式返回输出乱码
  servlet:
    encoding:
      charset: utf-8
      enabled: true # 启用http编码支持
      force: true #  HTTP 请求和响应上强制编码到配置的字符集
spring:
  application:
    name: LangChain4j-07chat-stream

LLMConfig

java
package com.lazy.config;

import com.lazy.service.ChatAssistant;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.chat.StreamingChatModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.model.openai.OpenAiStreamingChatModel;
import dev.langchain4j.service.AiServices;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class LLMConfig {


    /**
     * 普通对话
     * @return
     */
    @Bean
    public ChatModel chatModelQWen(){
        return OpenAiChatModel.builder()
                .apiKey(System.getenv("aliQWen-api"))
                .modelName("qwen-plus")
                .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
                .build();
    }
    /**
     * 配置流式输出
     * @return
     */
    @Bean
    public StreamingChatModel streamingChatModelQWen() {
        return OpenAiStreamingChatModel.builder()
                .apiKey(System.getenv("aliQWen-api"))
                .modelName("qwen-plus")
                .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
                .build();
    }

    /**
     * 流式接口
     * @return
     */
    @Bean
    public ChatAssistant assistantStreaming(StreamingChatModel streamingChatModel){
        return AiServices.create(ChatAssistant.class,streamingChatModel);
    }
}

ChatAssistant

java
package com.lazy.service;

import reactor.core.publisher.Flux;

public interface ChatAssistant {
    Flux<String> fluxChat(String prompt);
}

StreamingChatModelWithInterfaceController

java
package com.lazy.controller;

import com.lazy.service.ChatAssistant;
import dev.langchain4j.model.chat.ChatModel;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

@RestController
public class StreamingChatModelWithInterfaceController {

    @Resource
    private ChatAssistant chatAssistant;

    @Resource
    private ChatModel chatModel;

    @GetMapping("/interface/chat")
    public String chat(@RequestParam(value = "prompt",
            defaultValue = "介绍一下广州") String prompt){
        return chatModel.chat(prompt);
    }

    @GetMapping("/interface/streaming")
    public Flux<String> streaming(@RequestParam(value = "prompt",
            defaultValue = "介绍一下深圳") String prompt){
        return chatAssistant.fluxChat(prompt);
    }
}

测试

image-20250801194559792

流式输出

image-20250801203906441

记忆缓存

官网:https://docs.langchain4j.info/tutorials/chat-memory

image-20250802165149740

记忆缓存是聊天系统中的一个重要组件,用于存储和管理对话的上下文信息。它的主要作用是让AI助手能够“记住”之前的对话内容,从而提供连贯和个性化的回复。

image-20250802165419653

image-20250802165504428

持久化

默认情况下,ChatMemory实现在内存中存储ChatMessage

如果需要持久化,可以实现自定义的ChatMemoryStore, 将ChatMessage存储在您选择的任何持久化存储中:

java
class PersistentChatMemoryStore implements ChatMemoryStore {

        @Override
        public List<ChatMessage> getMessages(Object memoryId) {
          // TODO: 实现通过内存ID从持久化存储中获取所有消息。
          // 可以使用ChatMessageDeserializer.messageFromJson(String)和
          // ChatMessageDeserializer.messagesFromJson(String)辅助方法
          // 轻松地从JSON反序列化聊天消息。
        }

        @Override
        public void updateMessages(Object memoryId, List<ChatMessage> messages) {
            // TODO: 实现通过内存ID更新持久化存储中的所有消息。
            // 可以使用ChatMessageSerializer.messageToJson(ChatMessage)和
            // ChatMessageSerializer.messagesToJson(List<ChatMessage>)辅助方法
            // 轻松地将聊天消息序列化为JSON。
        }

        @Override
        public void deleteMessages(Object memoryId) {
          // TODO: 实现通过内存ID删除持久化存储中的所有消息。
        }
    }

ChatMemory chatMemory = MessageWindowChatMemory.builder()
        .id("12345")
        .maxMessages(10)
        .chatMemoryStore(new PersistentChatMemoryStore())
        .build();

每当向ChatMemory添加新的ChatMessage时,都会调用updateMessages()方法。 这通常在与LLM的每次交互中发生两次: 一次是添加新的UserMessage时,另一次是添加新的AiMessage时。 updateMessages()方法预期会更新与给定内存ID关联的所有消息。 ChatMessage可以单独存储(例如,每条消息一条记录/行/对象) 或一起存储(例如,整个ChatMemory一条记录/行/对象)。

备注

请注意,从ChatMemory中淘汰的消息也将从ChatMemoryStore中淘汰。 当消息被淘汰时,会调用updateMessages()方法, 传入不包含被淘汰消息的消息列表。

ChatMemory的用户请求所有消息时,会调用getMessages()方法。 这通常在与LLM的每次交互中发生一次。 Object memoryId参数的值对应于创建ChatMemory时指定的id。 它可以用来区分多个用户和/或对话。 getMessages()方法预期会返回与给定内存ID关联的所有消息。

当调用ChatMemory.clear()时,会调用deleteMessages()方法。 如果您不使用此功能,可以将此方法留空。

两种主要的ChatMemory实现类

  1. MessageWindowChatMemory
    • 较简单的一种,MessageWindowChatMemory,作为滑动窗口运行, 保留最近的N条消息,并淘汰不再适合的旧消息。 然而,由于每条消息可能包含不同数量的令牌, MessageWindowChatMemory主要用于快速原型设计。
  2. TokenWindowChatMemory
    • 更复杂的选项是TokenWindowChatMemory, 它也作为滑动窗口运行,但专注于保留最近的N令牌, 根据需要淘汰旧消息。 消息是不可分割的。如果一条消息不适合,它会被完全淘汰。 TokenWindowChatMemory需要一个Tokenizer来计算每个ChatMessage中的令牌数。

大模型中的Token VS Web开发中的Token

  • 大模型中的token

    image-20250802165751231

  • Web开发中的token

    image-20250802165810527

  • 区别

    • 目的不同:大模型中的token是为了将文本分割成可处理的单元,便于进行计算;而Web开发中的token主要用于安全地传递用户身份信息
    • 生成方式不同:前者通过特定的算法(如BPE)对文本进行分割得到;后者则通常是通过加密算法生成的唯一字符串。
    • 应用场景不同:前者应用于文本分析、机器翻译等NLP任务;后者多见于用户登录系统、API 访问控制等领域。

案例

新建moduleLangChain4j-08chat-memory

pom

xml
<properties>
    <maven.compiler.taget>17</maven.compiler.taget>
    <maven.compiler.souce>17</maven.compiler.souce>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- langchain4j low-api -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-open-ai</artifactId>
    </dependency>
    <!-- langchain4j high-api -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j</artifactId>
    </dependency>
</dependencies>

yaml

yaml
server:
  port: 9008
  servlet:
    encoding:
      charset: utf-8
      enabled: true
      force: true
spring:
  application:
    name: LangChain4j-08chat-memory

ChatAssistant

java
package com.lazy.service;

public interface ChatAssistant {
    /**
     * 普通聊天对话
     * @param prompt 消息
     * @return 回复
     */
    String chat(String prompt);
}

ChatMemoryAssistant

java
package com.lazy.service;

import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.UserMessage;

public interface ChatMemoryAssistant {
    /**
     * 带记忆缓存聊天
     * @param userId 用户id
     * @param prompt 消息
     * @return 回复
     */
    String chatMemory(@MemoryId Long userId, @UserMessage String prompt);
}

更换模型

image-20250802174808541

LLMConfig

java
package com.lazy.config;

import com.lazy.service.ChatAssistant;
import com.lazy.service.ChatMemoryAssistant;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.memory.chat.TokenWindowChatMemory;
import dev.langchain4j.model.TokenCountEstimator;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.model.openai.OpenAiTokenCountEstimator;
import dev.langchain4j.service.AiServices;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class LLMConfig {

    @Bean
    public ChatModel chatModel() {
        return OpenAiChatModel.builder()
                .apiKey(System.getenv("aliQWen-api"))
                .modelName("qwen-long")
                .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
                .build();
    }

    @Bean(name = "chatAssistant")
    public ChatAssistant chatAssistant(ChatModel chatModel) {
        return AiServices.create(ChatAssistant.class, chatModel);
    }

    @Bean(name = "chatMessageMemoryChatMemory")
    public ChatMemoryAssistant chatMessageWindowChatMemory(ChatModel chatModel) {
        return AiServices.builder(ChatMemoryAssistant.class)
                .chatModel(chatModel)
                // 每个memoryId对应创建一个ChatMemory,withMaxMessages(int maxMessages) 保存maxMessages条消息
                // 如果超过 maxMessages 条消息,将会执行MessageWindowChatMemory类中的clear方法清除消息
                .chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(100))
                .build();
    }

    @Bean(name = "chatTokenWindowChatMemory")
    public ChatMemoryAssistant chatMemoryAssistant(ChatModel chatModel) {
        TokenCountEstimator tokenCountEstimator = new OpenAiTokenCountEstimator("gpt-4");
        return AiServices.builder(ChatMemoryAssistant.class)
                .chatModel(chatModel)
                /**
                 * withMaxTokens(int maxTokens, TokenCountEstimator tokenCountEstimator)
                 *  maxTokens:要保留的最大token数。聊天内存将保留尽可能多的最新消息,
                 *	以容纳 maxTokens。信息是不可分割的。如果旧消息不适合,则将其完全逐出。
                 *  tokenCountEstimator:负责计算消息中的token
                 */
                .chatMemoryProvider(memoryId ->
                        TokenWindowChatMemory.withMaxTokens(1000,tokenCountEstimator))
                .build();
    }
}

ChatMemoryController

java
package com.lazy.controller;

import com.lazy.service.ChatAssistant;
import com.lazy.service.ChatMemoryAssistant;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ChatMemoryController {

    @Resource(name = "chatAssistant")
    private ChatAssistant chatAssistant;

    @Resource(name = "chatMessageMemoryChatMemory")
    private ChatMemoryAssistant chatMessageMemoryChatMemory;

    @Resource(name = "chatTokenWindowChatMemory")
    private ChatMemoryAssistant chatTokenWindowChatMemory;

    @GetMapping("/chat")
    public String chat() {
        String answer01 = "你好,我叫张三";
        String assistant = chatAssistant.chat(answer01);
        String answer02 = "我叫什么?";
        String assistant2 = chatAssistant.chat(answer02);
        return result(answer01,assistant,answer02,assistant2);
    }

    @GetMapping("/chatmemory/chat1")
    public String chatMessageMemoryChatMemory() {
        String answer01 = "你好,我叫张三";
        String assistant = chatMessageMemoryChatMemory.chatMemory(1L,answer01);
        String answer02 = "我叫什么?";
        String assistant2 = chatMessageMemoryChatMemory.chatMemory(1L,answer02);
        return result(answer01,assistant,answer02,assistant2);
    }

    @GetMapping("/chatmemory/chat2")
    public String chatTokenWindowChatMemory() {
        String answer01 = "你好,我叫Java";
        String assistant = chatTokenWindowChatMemory.chatMemory(1L,answer01);
        String answer02 = "我叫什么?";
        String assistant2 = chatTokenWindowChatMemory.chatMemory(1L,answer02);
        return result(answer01,assistant,answer02,assistant2);
    }

    private String result(String... answer01){
        StringBuilder result = new StringBuilder();
        for (String item : answer01) {
            result.append(item).append("<br/>");
        }
        return result.toString();
    }
}

测试

没有聊天记忆

image-20250802183623223

带聊天记忆MessageMemoryChatMemory

image-20250802183708934

带聊天记忆TokenWindowChatMemory

image-20250802183747877

提示词工程

什么是LangChain4j提示词?

LangChain4j 中的提示词(Prompt)用于引导模型生成特定输出。它们可以是简单的文本字符串,也可以是包含多种信息的复杂结构。

从普通的提问 过渡到 提示词

聊天和语言模型 | LangChain4j 中文文档

是什么?

  • 目前有五种类型的聊天消息,每个消息的“来源”对应一种

    • 五大类型

      image-20250803163217704

      1. SystemMessage

        这是来自系统的消息。通常,作为开发人员,你应该定义这条消息的内容。通常,会在这里写关于LLM在这次对话的角色(例如:医生、律师)、应该如何表现、以什么风格回答等指令。LLM 被训练成比其他类型的消息更关注 SystemMessage,所以要小心,最好不要给最终用户自由访问权限来定义或注入一些输入到SystemMessage中。通常,它位于对话的开始。

        地址:https://docs.langchain4j.info/tutorials/ai-services#systemmessage

      2. UserMessage

        这是来自用户的消息。用户可以是你的应用程序的最终用户(一个人)或应用程序本身。根据LLM支持的模态,UserMessage可以只包含文本(String),或其他模态

        地址:https://docs.langchain4j.info/tutorials/ai-services#usermessage

        image-20250803170726171

      3. AiMessage

        这是由 AI 生成的消息,通常是对 UserMessage 的回应。 正如您可能已经注意到的,generate 方法返回一个包装在 Response 中的 AiMessageAiMessage 可以包含文本响应(String)或执行工具的请求(ToolExecutionRequest)。 我们将在另一节中探讨工具。

      4. ToolExecutionResultMessage

        这是ToolExecutionRequest的结果

      5. CustomMessage

        这是一个自定义消息,可以包含任意属性。这种消息类型只能由支持他的ChatModel实现使用(目前只有 Ollama)

image-20250803165619126

能干嘛

利用LangChain4J框架构建一个专业的法律/医疗/保险/教育等咨询助手。

这个助手将专注于回答中国法律相关问题,对其他领域的咨询则会礼貌地拒绝。

学习角色设定和提示词模板的使用,这是实现这个功能的两个关键要素。

角色设定:塑造AI助手的专业身份

一句话:打造专业的限定能力范围和作用边界的AI助手

code案例

设计要求:

  1. 使用SystemMessage明确定义助手的角色和能力范围,将其限定在法律咨询领域。在LangChain4j中,我们主要利用SystemMessage来实现这一点,SystemMessage具有高优先级,能有效地指导模型的整体行为。
  2. 利用提示词模版(@UserMessage,@)精确控制输入和期望的输出格式,确保问题被正确理解和回答

moduleLangChain4j-09chat-prompt

pom

xml
<properties>
    <maven.compiler.target>17</maven.compiler.target>
    <maven.compiler.source>17</maven.compiler.source>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-open-ai</artifactId>
    </dependency>
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>5.8.38</version>
    </dependency>
</dependencies>

yaml

yaml
server:
  port: 9009
  servlet:
    encoding:
      charset: UTF-8
      force: true
      enabled: true
spring:
  application:
    name: LangChain4j-09chat-prompt

第一组code

@SystemMessage+@UserMessage+@V

新建接口LawAssistant

  • 提示词模版:精确控制输入输出
  • 支持多个输入参数和条件
  • SystemMessage具有高优先级,能有效地指导模型的整体行为
  • 使用@UserMssage@V注解

LawAssistant

java
package com.lazy.service;

import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;

public interface LawAssistant {

    @SystemMessage("你是一位专业的中国法律顾问,只能回答与中国法律相关的问题" +
            "输出限制:对于其他领域的问题禁止回答,直接返回'抱歉,我只能回答于中国法律相关的问题'")
    @UserMessage("请回答以下法律问题:{{question}},字数控制在{{length}}以内,输出格式为{{format}}") //{{question}},{{length}},{{format}}占位符
    String chat(@V("question") String question, @V("length") int length, @V("format") String format);
}

LLMConfig

java
package com.lazy.config;

import com.lazy.service.LawAssistant;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.service.AiServices;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class LLMConfig {

    @Bean
    public ChatModel chatModel(){
        return OpenAiChatModel.builder()
                .apiKey(System.getenv("aliQWen-api"))
                .modelName("qwen-long")
                .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
                .build();
    }

    @Bean
    public LawAssistant lawAssistant(ChatModel chatModel){
        return AiServices.create(LawAssistant.class, chatModel);
    }
}

LawPromptController

java
package com.lazy.controller;

import cn.hutool.core.date.DateUtil;
import com.lazy.service.LawAssistant;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ChatPromptController {

    @Resource
    private LawAssistant lawAssistant;

    @GetMapping("/chatprompt/test1")
    public String test1() {
        String law = lawAssistant.chat("中国的法律缺陷包含哪些?", 2000, "md");
        System.out.println(law);
        String java = lawAssistant.chat("什么是java", 2000, "html");
        System.out.println(java);
        String chat = lawAssistant.chat("解释一下什么是中国法律", 2000, "json");
        return "success:"+ DateUtil.now() + "<br/>chat1:"+law+"<br/>chat2:"+java+"<br/>chat3:"+chat;
    }
}

image-20250803180851315

第二组code

新建带着@StrcturedPrompt的业务实体类

LLMConfig

java
package com.lazy.config;

import com.lazy.service.LawAssistant;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.service.AiServices;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class LLMConfig {

    @Bean
    public ChatModel chatModel(){
        return OpenAiChatModel.builder()
                .apiKey(System.getenv("aliQWen-api"))
                .modelName("qwen-long")
                .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
                .build();
    }

    @Bean
    public LawAssistant lawAssistant(ChatModel chatModel){
        return AiServices.create(LawAssistant.class, chatModel);
    }
}

LawPrompt

java
package com.lazy.entities;

import dev.langchain4j.model.input.structured.StructuredPrompt;
import lombok.Data;

@Data
@StructuredPrompt("根据中国法律{{legal}}法律,解答以下问题,字数控制在{{length}}以内,输出格式为{{format}}")
public class LawPrompt {
    private String legal;
    private Integer length;
    private String format;
}

LawAssistant

java
package com.lazy.service;

import com.lazy.entities.LawPrompt;
import dev.langchain4j.service.SystemMessage;

public interface LawAssistant {

    @SystemMessage("你是一位专业的中国法律顾问,只回答与中国法律相关的问题" +
            "输出限制:对于其他领域的问题禁止回答,直接返回'抱歉,我只能回答于中国法律相关的问题'")
    String chat(LawPrompt lawPrompt);
}

ChatPromptController

java
package com.lazy.controller;

import cn.hutool.core.date.DateUtil;
import com.lazy.entities.LawPrompt;
import com.lazy.service.LawAssistant;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ChatPromptController {

    @Resource
    private LawAssistant lawAssistant;

    @GetMapping("/chatprompt/test2")
    public String test2() {
        String chat = lawAssistant.chat(new LawPrompt("知识产权", "TRIPS协议?",2000, "md"));
        String chat1 = lawAssistant.chat(new LawPrompt("java", "Date类里面有哪些方法?", 2000, "html"));
        return "success;"+DateUtil.now()+"<br/>chat1:"+chat+"<br/>chat2:"+chat1;
    }
}

image-20250803182254979

第三组code

LangChain4j中有两个对象PromptTemplate以及Prompt用来实现提示词相关功能

LLMConfig

java
package com.lazy.config;

import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class LLMConfig {

    @Bean
    public ChatModel chatModel(){
        return OpenAiChatModel.builder()
                .apiKey(System.getenv("aliQWen-api"))
                .modelName("qwen-long")
                .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
                .build();
    }
}

LawPromptController

java
package com.lazy.controller;

import cn.hutool.core.date.DateUtil;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.chat.response.ChatResponse;
import dev.langchain4j.model.input.Prompt;
import dev.langchain4j.model.input.PromptTemplate;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

@RestController
public class ChatPromptController {

    @Resource
    private ChatModel chatModel;

    @GetMapping("/chatprompt/test3")
    public String test3() {
        //默认 PromptTemplate 构造使用 it 属性作为默认占位符
        String role = "资深程序员";
        String question = "编程中有多少种设计模式";
        // 1、构造PromptTemplate 模版
        PromptTemplate template = PromptTemplate.from("你是一个{{it}}助手,{{question}}怎么办?");
        // 2、由 PromptTemplate 生成 Prompt
        Prompt prompt = template.apply(Map.of("it", role, "question", question));
        // 3、Prompt 提示词编程 UserMessage
        UserMessage userMessage = prompt.toUserMessage();
        // 4、调用大模型
        ChatResponse chatResponse = chatModel.chat(userMessage);
        System.out.println(chatResponse.aiMessage().text());
        return "success:"+DateUtil.now()+"<br/>chat:"+chatResponse.aiMessage().text();
    }
}

image-20250803183431452

注意:

image-20250803183749355

第一个变量名称占位符必须为it

持久化

"持久化"就是将聊天重要的东西保存到MysqlRedis、消息中间件...

地址:https://docs.langchain4j.info/tutorials/chat-memory

image-20250803185526791

想要持久化就需要实现ChatMemoryStore接口,并配置到我们的Spring IOC容器里

code案例

设计要求:将客户和大模型的对话问答保存进redis进行持久化记忆留存

modelLangChain4j-10chat-persistence

pom

xml
<properties>
    <maven.comiper.target>17</maven.comiper.target>
    <maven.comiper.source>17</maven.comiper.source>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-open-ai</artifactId>
    </dependency>
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>5.8.38</version>
    </dependency>
</dependencies>

yaml

yaml
server:
  port: 9010
  servlet:
    encoding:
      charset: UTF-8
      enabled: true
      force: true
spring:
  application:
    name: LangChain4j-10chat-persistence
  data:
    redis:
      database: 0
      host: 192.168.0.136
      port: 6379
      connect-timeout: 3s # 连接超时
      timeout: 2s #读取超时
      password: redis

ChatPersistenceAssistant

java
package com.lazy.service;

import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.UserMessage;

public interface ChatPersistenceAssistant {

    /**
     * 聊天
     * @param memoryId 用户id
     * @param message 消息
     * @return 回复信息
     */
    String chat(@MemoryId Long memoryId, @UserMessage String message);
}

RedisConfig

java
package com.lazy.config;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;


@Configuration
public class RedisConfig
{
    /**
     * RedisTemplate配置
     * redis序列化的工具配置类,下面这个请一定开启配置
     * 127.0.0.1:6379> keys *
     * 1) "ord:102"  序列化过
     * 2) "\xac\xed\x00\x05t\x00\aord:102"   野生,没有序列化过
     * this.redisTemplate.opsForValue(); //提供了操作string类型的所有方法
     * this.redisTemplate.opsForList(); // 提供了操作list类型的所有方法
     * this.redisTemplate.opsForSet(); //提供了操作set的所有方法
     * this.redisTemplate.opsForHash(); //提供了操作hash表的所有方法
     * this.redisTemplate.opsForZSet(); //提供了操作zset的所有方法
     * @param redisConnectionFactor
     * @return
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactor)
    {
        RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();

        redisTemplate.setConnectionFactory(redisConnectionFactor);
        //设置key序列化方式string
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        //设置value的序列化方式json,使用GenericJackson2JsonRedisSerializer替换默认序列化
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());

        //设置key序列化方式hash
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());

        redisTemplate.afterPropertiesSet();

        return redisTemplate;
    }
}

RedisMemoryStore

java
package com.lazy.config;

import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.ChatMessageDeserializer;
import dev.langchain4j.data.message.ChatMessageSerializer;
import dev.langchain4j.store.memory.chat.ChatMemoryStore;
import jakarta.annotation.Resource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
public class RedisMemoryStore implements ChatMemoryStore {

    public static final String CHAT_MEMORY_PREFIX = "CHAT_MEMORY:";

    @Resource
    private RedisTemplate<String,String> redisTemplate;


    @Override
    public List<ChatMessage> getMessages(Object memoryId)
    {
        String retValue = redisTemplate.opsForValue().get(CHAT_MEMORY_PREFIX + memoryId);

        return  ChatMessageDeserializer.messagesFromJson(retValue);//将 JSON 字符串反序列化为List<ChatMessage>
    }

    @Override
    public void updateMessages(Object memoryId, List<ChatMessage> messages)
    {
        //将 JSON 字符串序列化为redis,更新
        redisTemplate.opsForValue().set(CHAT_MEMORY_PREFIX + memoryId, ChatMessageSerializer.messagesToJson(messages));
    }

    @Override
    public void deleteMessages(Object memoryId)
    {
        redisTemplate.delete(CHAT_MEMORY_PREFIX + memoryId);
    }
}

LLMConfig

java
package com.lazy.config;

import com.lazy.service.ChatPersistenceAssistant;
import dev.langchain4j.memory.chat.ChatMemoryProvider;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.service.AiServices;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class LLMConfig {

    @Resource
    private RedisMemoryStore redisMemoryStore;

    @Bean
    public ChatModel chatModel() {
        return OpenAiChatModel.builder()
                .apiKey(System.getenv("alibaba"))
                .apiKey(System.getenv("aliQWen-api"))
                .modelName("qwen-long")
                .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
                .build();
    }

    @Bean
    public ChatPersistenceAssistant chatPersistenceAssistant(ChatModel chatModel) {
        ChatMemoryProvider chatMemoryProvider = maxMemoryId ->
                MessageWindowChatMemory.builder()
                        .id(maxMemoryId)
                        .maxMessages(100) //最大消息
                        .chatMemoryStore(redisMemoryStore)//ChatMemoryStore
                        .build();
        return AiServices.builder(ChatPersistenceAssistant.class)
                .chatModel(chatModel)
                .chatMemoryProvider(chatMemoryProvider)
                .build();
    }
}

ChatPersistenceAssistantController

java
package com.lazy.controller;

import cn.hutool.core.date.DateUtil;
import com.lazy.service.ChatPersistenceAssistant;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ChatPersistenceAssistantController {

    @Resource
    private ChatPersistenceAssistant chatPersistenceAssistant;

    @GetMapping("/chat")
    public String chat() {
        String chat1 = chatPersistenceAssistant.chat(1L, "你好,我的名字叫Mysql");
        String chat2 = chatPersistenceAssistant.chat(2L, "你好,我的名字叫Oracle");
        String result1 = chatPersistenceAssistant.chat(1L, "我的名字叫什么?");
        String result2 = chatPersistenceAssistant.chat(2L, "我的名字叫什么?");
        return "success:"+ DateUtil.now() +"<br/>" +
                "user1:你好,我的名字叫Mysql<br/>" +
                "chat1:"+chat1+"<br/>user2:你好,我的名字叫Oracle<br/>" +
                "chat2:"+chat2+"<br/>user1:我的名字叫什么?<br/>" +
                "chat1:"+result1+"<br/>user2:我的名字叫什么?<br/>"+
                "chat2"+result2;
    }
}

测试

image-20250803195533385

image-20250803200103659

image-20250803200134240

Tools(Function Calling)

地址:https://docs.langchain4j.info/tutorials/tools

是什么?

一些 LLM 除了生成文本外,还可以触发操作。

  • 有一个被称为"工具"或"函数调用"的概念。 它允许 LLM 在必要时调用一个或多个可用的工具,通常由开发者定义。 工具可以是任何东西:网络搜索、调用外部 API 或执行特定代码片段等。 LLM 实际上不能自己调用工具;相反,它们在响应中表达调用特定工具的意图(而不是以纯文本形式响应)。 作为开发者,我们应该使用提供的参数执行这个工具,并将工具执行的结果反馈回来。
  • 例如,我们知道 LLM 本身在数学计算方面并不擅长。 如果您的用例涉及偶尔的数学计算,您可能希望为 LLM 提供一个"数学工具"。 通过在请求中向 LLM 声明一个或多个工具, 如果它认为合适,它可以决定调用其中一个工具。 给定一个数学问题和一组数学工具,LLM 可能会决定为了正确回答问题, 它应该首先调用提供的数学工具之一。

一句话:给大模型配一个调用其他外部Util工具类

LLM的智能与外部工具或API无缝连接

  • 大语言模型(LLMs)不仅仅是文本生成的能手,它们还能触发并调用第三方函数,比如查询微信、调用支付宝、查看顺丰快递单据号等等...
  • 重要提示:LLM本身并不执行函数,它只是指示应该调用哪个函数以及如何调用

LangChain4j 提供了两个抽象级别来使用工具:

  • 低级别,使用 ChatLanguageModelToolSpecification API
  • 高级别,使用 AI 服务和带有 @Tool 注解的 Java 方法

低阶API

在低级别,您可以使用 ChatLanguageModelchat(ChatRequest) 方法。 StreamingChatLanguageModel 中也存在类似的方法。

您可以在创建 ChatRequest 时指定一个或多个 ToolSpecification

ToolSpecification 是一个包含工具所有信息的对象:

  • 工具的 name(名称)
  • 工具的 description(描述)
  • 工具的 parameters(参数)及其描述

建议提供尽可能多的工具信息: 清晰的名称、全面的描述以及每个参数的描述等。

高级工具 API

在高级抽象层面,您可以使用 @Tool 注解任何 Java 方法, 并在创建 AI 服务时指定它们。

AI 服务会自动将这些方法转换为 ToolSpecification, 并在每次与 LLM 交互的请求中包含它们。 当 LLM 决定调用工具时,AI 服务将自动执行相应的方法, 并将方法的返回值(如果有)发送回 LLM。 您可以在 DefaultToolExecutor 中找到实现细节。

具体细节请查阅:https://docs.langchain4j.info/tutorials/tools

低阶API code

在低级别,您可以使用 ChatLanguageModelchat(ChatRequest) 方法。 StreamingChatLanguageModel 中也存在类似的方法。

moduleLangChain4j-11chat-functioncalling

pom

xml
<properties>
    <maven.compiler.target>17</maven.compiler.target>
    <maven.compiler.source>17</maven.compiler.source>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-open-ai</artifactId>
    </dependency>
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j</artifactId>
    </dependency>
    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>5.8.38</version>
    </dependency>
</dependencies>

yaml

yaml
server:
  port: 9011
spring:
  application:
    name: LangChain4j-11chat-functioncalling

FuctionCallingAssistant

java
package com.lazy.service;

public interface FunctionCallingAssistant {

    /**
     * 客户指令:出差住宿发票开票,
     *  开发票格式:
     *      公司:xx
     *      税号:xx
     *      金额:xx.00
     * @param message 发票信息
     * @return
     */
    String chat(String message);
}

LLMConfig

java
package com.lazy.config;

import com.lazy.service.FunctionCallingAssistant;
import dev.langchain4j.agent.tool.ToolSpecification;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.chat.request.json.JsonObjectSchema;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.tool.ToolExecutor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.List;
import java.util.Map;

@Configuration
public class LLMConfig {

    @Bean
    public ChatModel chatModel(){
        return OpenAiChatModel.builder()
                .apiKey(System.getenv("aliQWen-api"))
                .modelName("qwen-plus")//模型名称
                .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")//模型接口地址
                .build();
    }

    @Bean
    public FunctionCallingAssistant functionCallingAssistant(ChatModel chatModel){
        ToolSpecification toolSpecification = ToolSpecification.builder()
                .name("开发票助手")
                .description("根据用户提交的信息,开具发票")
                .parameters(JsonObjectSchema.builder()
                        .addStringProperty("companyName", "公司名称")
                        .addStringProperty("dutyNumber","税号序列")
                        .addStringProperty("amount","开票金额,保留两位有效数字")
                        .build())
                .build();
        ToolExecutor toolExecutor =  (toolExecutionRequest,memoryId)->{
            System.out.println(toolExecutionRequest.id());//编号
            System.out.println(toolExecutionRequest.name());//名称
            String arguments = toolExecutionRequest.arguments();
            System.out.println("arguments=>"+arguments);//参数
            return "开具发票成功!";
        };
        return AiServices.builder(FunctionCallingAssistant.class)
                .chatModel(chatModel)
                .tools(Map.of(toolSpecification, toolExecutor))
                .build();
    }
}

FunctionCallingController

java
package com.lazy.controller;

import cn.hutool.core.date.DateUtil;
import com.lazy.service.FunctionCallingAssistant;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class FunctionCallingController {

    @Resource
    private FunctionCallingAssistant assistant;

    @GetMapping("/function/calling")
    public String functionCalling() {
        String chat = assistant.chat("开张发票,公司:xx有限公司,税号:123,金额:190.00");
        return "success:"+ DateUtil.now()+"\n"+chat;
    }
}

测试:

image-20250804170600512

image-20250804170701150

高阶API code

使用注解@Tool,可以更方便地集成函数调用,只需将java方法标注为@Tool,LangChain4j就会自动将其转换为ToolSpecification

在低阶api的项目上继续进行编码

引入httpclient5,完整依赖

pom

xml
<properties>
    <maven.compiler.target>17</maven.compiler.target>
    <maven.compiler.source>17</maven.compiler.source>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-open-ai</artifactId>
    </dependency>
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j</artifactId>
    </dependency>
    <!-- 调用天气查询用到 -->
    <dependency>
        <groupId>org.apache.httpcomponents.client5</groupId>
        <artifactId>httpclient5</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>5.8.38</version>
    </dependency>
</dependencies>

WeatherService

java
package com.lazy.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Service
public class WeatherService {

    public static final String BASE_URL = "http://t.weather.itboy.net/api/weather/city/";//天气url

    public JsonNode weather(Integer cityCode) throws JsonProcessingException {
        // 创建httpsClients实例
        var httpClients = HttpClients.createDefault();
        // 创建请求工厂并将其设置给RestTemplate,开启微服务调用和风天气开发服务
        HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClients);
        // 微服务调用
        String response = new RestTemplate(factory).getForObject(BASE_URL + cityCode, String.class);
        // 解析JSON响应获得第3方和风天气返回的天气预报信息
        return new ObjectMapper().readTree(response);
    }
}

InvoicekoHandler

java
package com.lazy.service;

import dev.langchain4j.agent.tool.P;
import dev.langchain4j.agent.tool.Tool;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class InvoiceHandler {

    @Tool("根据用户提交的开票信息进行开票")
    public String handle(@P("公司名称") String companyName,
                         @P("税号") String dutyNumber,
                         @P("金额保留两位有效数字") String amount,
                         @P("城市编号") Integer cityCode) throws Exception
    {
        log.info("companyName =>>>> {} dutyNumber =>>>> {} amount =>>>> {}", companyName, dutyNumber, amount);
        //----------------------------------
        // 这块写自己的业务逻辑,调用redis/rabbitmq/kafka/mybatis/顺丰单据/医疗化验报告/支付接口等第3方
        //----------------------------------
        System.out.println(new WeatherService().weather(cityCode));
        return "开票成功";
    }
}

LLMConfig

java
package com.lazy.config;

import com.lazy.service.FunctionCallingAssistant;
import com.lazy.service.InvoiceHandler;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.service.AiServices;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class LLMConfig {

    @Bean
    public ChatModel chatModel(){
        return OpenAiChatModel.builder()
                .apiKey(System.getenv("aliQWen-api"))
                .modelName("qwen-plus")//模型名称
                .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")//模型接口地址
                .build();
    }

    @Bean
    public FunctionCallingAssistant functionCallingAssistant(ChatModel chatModel){
        return AiServices.builder(FunctionCallingAssistant.class)
                .chatModel(chatModel)
                .tools(new InvoiceHandler())
                .build();
    }
}

FunctionCallingController

java
package com.lazy.controller;

import cn.hutool.core.date.DateUtil;
import com.lazy.service.FunctionCallingAssistant;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class FunctionCallingController {

    @Resource
    private FunctionCallingAssistant assistant;

    @GetMapping("/function/calling/weather")
    public String functionCallingWithWeather() {
        String chat = assistant.chat("开张发票,公司:xx有限公司,税号:123,金额:190.00,城市编号:101010100");
        return "success:"+ DateUtil.now()+"\n"+chat;
    }
}

测试:

image-20250804181417721

image-20250804181440973

向量数据库

什么是向量?

​ 在数学中,向量(也称为欧几里得向量、几何向量),指具有大小(magnitude)和方向的量。它可以形象化地表示为带箭头的线段。箭头所指:代表向量的方向;线段长度:代表向量的大小。与向量对应的量叫做数量(物理学中称标量),数量(或标量)只有大小,没有方向。

image-20250805163627045

向量化是什么?

向量化是将数据转换为向量形式的技术,旨在提高计算效率,尤其在数据处理和机器学习中具有重要作用。

向量化的定义

向量化是指将其他格式的数据(如文本、图像、视频、音频等)转换为向量形式的过程。向量化使得数据能够被计算机更高效地处理,尤其是在机器学习和深度学习模型中,输入数据必须以向量的形式提供,以便于模型的计算和学习。

  • 案例1

    • 维度

      image-20250805163908107

    • 如何确定相似

      image-20250805163933445

  • 案例2

    • 对比图片

      image-20250805164015200

    • 维度

      image-20250805164032114

能干嘛?

image-20250805171523409

将文本、图像和视频转换称为向量(Vectors)的浮点数数组,在VectorStore中,查询与传统关系数据库不同。它们执行相似性搜索,而不是精确匹配。当给定一个向量作为查询时,VectorStore返回与查询向量“相似”的向量

指征特点

  1. 捕捉复杂的词汇关系(如语义相似性、同义词、多义词)超越传统词模型的简单计数方式
  2. 动态嵌入模型(如BERT)可根据上下文生成不同的词向量

小总结

  1. 将文本映射到高维度空间中的点,使语义相似的文本在这个空间中距离较近。例如,“肯德基”和 “麦当劳”的向量可能会比“肯德基”和 “新疆大盘鸡”的向量更接近。

向量化3件套

  1. Embedding Model模型简介

    地址:https://docs.langchain4j.info/tutorials/rag#embedding-model

    image-20250805170254044

  2. Embedding Store存储简介

    地址:https://docs.langchain4j.dev/tutorials/rag#embedding-store

    image-20250805170419032

    image-20250805170316255

    所支持的向量数据库

    嵌入存储存储元数据通过元数据过滤移除嵌入
    内存存储
    AlloyDB for Postgres
    Astra DB
    Azure AI 搜索
    Azure CosmosDB Mongo vCore
    Azure CosmosDB NoSQL
    Cassandra
    Chroma
    ClickHouse
    Cloud SQL for Postgres
    Coherence
    Couchbase
    DuckDB
    Elasticsearch
    Infinispan
    Milvus
    MongoDB Atlas
    Neo4j
    OpenSearch
    Oracle
    PGVector
    Pinecone
    Qdrant
    Redis
    Tablestore
    Vearch
    Vespa
    Weaviate
  3. EmbeddingSearchRequest查询

    地址:https://docs.langchain4j.dev/tutorials/rag#embeddingsearchrequest

    image-20250805170521620

    ![6da46415ed4d787040e5da77f840bb4b](D:\QQtemp\Tencent Files\3304621732\nt_qq\nt_data\Pic\2025-08\Ori\6da46415ed4d787040e5da77f840bb4b.png)

EmbeddingSearchResult 代表在一个 EmbeddingStore 中的搜索结果。它包含 EmbeddingMatch 的列表。

EmbeddingMatch 代表一个匹配的 Embedding ,包含其相关性分数、ID 和原始嵌入数据(通常是 TextSegment )。

怎么玩

Text search :文本搜索

Recommend movies:推荐电影

Match images and caption:匹配图片和标题

Group similar items:将相似项目归类

code 案例

本次项目用到了Qdrant安装教程:https://blog.csdn.net/qq_44866828/article/details/148893132

Qdrant介绍:https://qdrant.org.cn/documentation/overview

什么是Qdrant

​ 向量数据库是一种相对较新的方式,用于与来自不透明机器学习模型(如深度学习架构)派生的抽象数据表示进行交互。这些表示通常被称为向量或嵌入(embeddings),它们是用于训练机器学习模型完成诸如情感分析、语音识别、目标检测等任务的数据的压缩版本。

​ 这些新数据库在许多应用中表现出色,例如语义搜索推荐系统,在这里,我们将学习市场上最受欢迎且增长最快的向量数据库之一:Qdrant

使用Docker按照Qdrant

cmd
docker run -p 6333:6333 -p 6334:6334 qdrant/qdrant 
# 其他
docker run -p 6333:6333 -p 6334:6334 qdrant/qdrant --service web-ui.enabled=true

#端口 6333:用于HTTP API,浏览器web界面
#端口 6334:用于gRPC API

安装成功:

image-20250805181259271

本次用到的大模型为:通用文本向量-v3

image-20250805184101926

moduleLangChain4j-12chat-embedding

pom

xml
<properties>
    <maven.compiler.target>17</maven.compiler.target>
    <maven.compiler.source>17</maven.compiler.source>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-open-ai</artifactId>
    </dependency>
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j</artifactId>
    </dependency>
    <!--添加qdrant向量数据库-->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-qdrant</artifactId>
    </dependency>
</dependencies>

yaml

yaml
server:
  port: 9012
spring:
  application:
    name: LangChain4j-12chat-embedding

image-20250805190149186

LLMConfig

java
package com.lazy.config;

import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.openai.OpenAiEmbeddingModel;
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.store.embedding.qdrant.QdrantEmbeddingStore;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class LLMConfig {

    @Bean
    public EmbeddingModel embeddingModel() {
        return OpenAiEmbeddingModel.builder()
                .apiKey(System.getenv("aliQWen-api"))
                .modelName("text-embedding-v3")
                .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
                .build();
    }

    /**
     * 创建 Qdrant 客户端
     * @return
     */
    @Bean
    public QdrantClient qdrantClient() {
        QdrantGrpcClient qdrantGrpcClient = QdrantGrpcClient.newBuilder("127.0.0.1",
                        6334, false)
                .build();
        return new QdrantClient(qdrantGrpcClient);
    }

    /**
     * 创建 EmbeddingStore 存储
     * @return
     */
    @Bean
    public EmbeddingStore<TextSegment> embeddingStore(){
        return QdrantEmbeddingStore.builder()
                .host("127.0.0.1")
                .port(6334)
                .collectionName("test-qdrant")
                .build();
    }
}

EmbeddingController

java
package com.lazy.controller;

import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.output.Response;
import dev.langchain4j.store.embedding.EmbeddingSearchRequest;
import dev.langchain4j.store.embedding.EmbeddingSearchResult;
import dev.langchain4j.store.embedding.EmbeddingStore;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.grpc.Collections;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import static dev.langchain4j.store.embedding.filter.MetadataFilterBuilder.metadataKey;


@RestController
public class EmbeddingController {

    @Resource
    private EmbeddingModel embeddingModel;

    @Resource
    private QdrantClient qdrantClient;

    @Resource
    private EmbeddingStore<TextSegment> embeddingStore;

    /**
     * 文本向量化测试,看看形成向量后的文本
     * @return
     */
    @GetMapping("/embedding/embed")
    public String embed() {
        String prompt = """
                   咏鸡
                鸡鸣破晓光,
                红冠映朝阳。
                金羽披霞彩,
                昂首步高岗。
                """;
        Response<Embedding> embeddingResponse = embeddingModel.embed(prompt);
        System.out.println(embeddingResponse);
        return embeddingResponse.content().toString();//返回生成向量化的内容
    }

    /**
     * 新建向量数据库实例和创建索引:test-qdrant
     */
    @GetMapping("/embedding/createCollection")
    public void createCollection() {
        var vectorParams = Collections.VectorParams.newBuilder()
                .setDistance(Collections.Distance.Cosine)
                .setSize(1024)
                .build();
        /**
         * Qdrant 支持以下最常见的指标类型
         *  点积:Dot
         *  余弦相似度:Cosine
         *  欧几里得距离:Euclid
         *  曼哈顿距离:Manhattan
         */
        qdrantClient.createCollectionAsync("test-qdrant",vectorParams);
    }

    /**
     * 往向量数据库新增文本记录
     * @return
     */
    @GetMapping("/embedding/add")
    public String add() {
        String prompt = """
                   咏鸡
                鸡鸣破晓光,
                红冠映朝阳。
                金羽披霞彩,
                昂首步高岗。
                """;
        TextSegment textSegment = TextSegment.from(prompt);
        textSegment.metadata().put("author","lazy");
        Embedding embedding = embeddingModel.embed(textSegment).content();
        String result = embeddingStore.add(embedding,textSegment);
        System.out.println(result);
        return result;
    }

    /**
     * 模糊查询
     * @return
     */
    @GetMapping("/embedding/query1")
    public String query1() {
        Embedding queryEmbedding = embeddingModel.embed("咏鸡说的是什么").content();//写入要查询的文本
        //使用EmbeddingSearchRequest,写入要查询到embedding和返回的最大条数
        EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
                .queryEmbedding(queryEmbedding)
                .maxResults(1) //返回最多的条数
                .build();
        //使用embeddingStore执行查询操作
        EmbeddingSearchResult<TextSegment> search = embeddingStore.search(embeddingSearchRequest);
        if (search.matches().get(0).embedded().text().isEmpty()) {
            return "没有匹配到数据!";
        }
        return search.matches().get(0).embedded().text();
    }

    /**
     * 带过滤器查询
     * @return
     */
    @GetMapping("/embedding/query2")
    public String query2() {
        Embedding embedding = embeddingModel.embed("咏鸡说的是什么").content();
        EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
                .queryEmbedding(embedding)
                .filter(metadataKey("author").isEqualTo("lazy"))//匹配author是lazy
                .maxResults(1) //返回最多的条数
                .build();
        EmbeddingSearchResult<TextSegment> search = embeddingStore.search(embeddingSearchRequest);
        return search.matches().get(0).embedded().text();
    }
}

测试

image-20250805191900645

image-20250805211152871

image-20250805211256794

image-20250805215409034

image-20250805215421625

检索增强生成RAG

地址:https://docs.langchain4j.info/tutorials/rag

LLM 的知识仅限于它已经训练过的数据。 如果你想让 LLM 了解特定领域的知识或专有数据,你可以:

什么是 RAG?

简单来说,RAG 是一种在发送给 LLM 之前,从你的数据中找到并注入相关信息片段到提示中的方法。 这样 LLM 将获得(希望是)相关信息,并能够使用这些信息回复, 这应该会降低产生幻觉的概率。

幻觉?

  • 已读乱回
  • 已读不回
  • 似是而非

核心理念

RAG技术就像给AI大模型装上了【实时百科大脑】,为了让大模型获得足够的上下文,以便获得更加广泛的信息源,通过先查资料后回答的机制,让AI摆脱传统模型的“知识遗忘和幻觉回复”的困境。就是类似于考试时有不懂的,给你准备了小抄。

能干嘛

通过引入外部知识源来增强LLM的输出能力,传统的LLM通常基于其训练数据生成响应,但这些数据可能过时或不够全面。RAG允许模型在生成答案之前,从特定的知识库中检索相关信息,从而提供更准确和上下文相关的回答。

怎么玩

RAG 过程分为两个不同的阶段:索引和检索。 LangChain4j 为这两个阶段提供了工具。

核心API

地址:https://docs.langchain4j.dev/tutorials/rag#core-rag-apis

主要内容

  • EmbeddingStoreIngestor 组织结构分析

    image-20250806161227283

    image-20250806161240596

  • Document Loader文档加载器

    • ``FileSystemDocumentLoader`: 从文件系统加载文档

    • UrlDocumentLoader: 从 URL 加载文档

    • AmazonS3DocumentLoader: 从 Amazon S3 加载文档

    • AzureBlobStorageDocumentLoader: 从 Azure Blob 存储加载文档

    • GitHubDocumentLoader: 从 GitHub 仓库加载文档

    • TencentCosDocumentLoader: 从腾讯云 COS 加载文档

      image-20250806161447190

  • Document Parser 文档解析器

    image-20250806161704055

  • Document Transformer 文档转换器

    DocumentTransformer 用于对文档执行各种转换,如清理、过滤、增强或总结。

    image-20250806161819758

  • Document Splitter 文档分割器

    • DocumentByParagraphSplitter : 按段落拆分

    • DocumentBySentenceSplitter: 按句子拆分

    • DocumentByWordSplitter: 按单词拆分

    • DocumentByCharacterSplitter: 按字符拆分

    • DocumentByRegexSplitter: 按正则表达式拆分

      image-20250806162038085

使用LangChain4j构建RAG的一般步骤

  1. 加载文档:使用适当DocumentLoaderDocumentParse加载文档
  2. 转换文档:使用DocumentTransformer清理或增强文档(可选)
  3. 拆分文档:使用DocumentSplitter将文档拆分为更小的片段(可选)
  4. 嵌入文档:使用EmbeddingModel将文档片段转换为嵌入变量
  5. 存储嵌入:使用EmbeddingStoreIngestor存储嵌入向量
  6. 检索相关内容:根据用户查询,从EmbeddingStore检索最相关的文档片段
  7. 生成响应:将检索到的相关内容与用户查询一起提供给语言模型,生成最终响应

code(初级)

需求说明

​ 某系统涉及后续自动化维护,需要根据响应码让大模型启动自迭代/自维护模式

根据alibaba的java开发规范来确定返回

image-20250806164431708

moduleLangChain4j-13chat-rag

pom

xml
<properties>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- langchain4j 初阶 -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-open-ai</artifactId>
    </dependency>
    <!-- langchain4j 高阶 -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j</artifactId>
    </dependency>
    <!-- easy-rag -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-easy-rag</artifactId>
    </dependency>
</dependencies>

yaml

yaml
server:
  port: 9013
  servlet:
    encoding:
      charset: UTF-8
      enabled: true
      force: true
spring:
  application:
    name: LangChain4j-13chat-rag

RagAssistant

java
package com.lazy.service;

public interface RagAssistant {

    /**
     * 聊天
     * @param message 消息
     * @return {@link String}
     */
    String chat(String message);
}

LLMConfig

java
package com.lazy.config;

import com.lazy.service.RagAssistant;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.model.openai.OpenAiEmbeddingModel;
import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class LLMConfig {

    @Bean
    public ChatModel chatModel() {
        return OpenAiChatModel.builder()
                .apiKey(System.getenv("aliQWen-api"))
                .modelName("qwen-plus")//模型名称
                .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
                .build();
    }

    /**
     * 在内存中存储
     *  需要预处理文档并将其存储在专门的嵌入存储(也称为矢量数据库)中。当用户提出问题时,这对于快速找到相关信息是必要的。
     *  我们可以使用我们支持的 15 多个嵌入存储中的任何一个,但为了简单起见,我们将使用内存中的嵌入存储:
     */
    @Bean
    public InMemoryEmbeddingStore<TextSegment> inMemoryEmbeddingStore(){
        return new InMemoryEmbeddingStore<>();
    }

    @Bean
    public RagAssistant ragAssistant(ChatModel chatModel, EmbeddingStore<TextSegment> embeddingStore) {
        return AiServices.builder(RagAssistant.class)
                .chatModel(chatModel)
                .chatMemory(MessageWindowChatMemory.withMaxMessages(50)) //最大条消息
                .contentRetriever(EmbeddingStoreContentRetriever.from(embeddingStore))//引入外部知识源
                .build();
    }
}

RagController

java
package com.lazy.controller;

import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.loader.FileSystemDocumentLoader;
import dev.langchain4j.data.document.parser.apache.tika.ApacheTikaDocumentParser;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.store.embedding.EmbeddingStoreIngestor;
import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.FileInputStream;
import java.io.FileNotFoundException;

@RestController
public class RagController {

    @Resource
    private InMemoryEmbeddingStore<TextSegment> inMemoryEmbeddingStore;

    @Resource
    private ChatModel chatModel;

    @GetMapping("/rag/add")
    public String addRag() throws FileNotFoundException {
//        Document document = FileSystemDocumentLoader
//                .loadDocument("C:\\Users\\MacBookPro\\Downloads\\阿里巴巴java开发规范(嵩山版).pdf");//第一种方式
        Document document = new ApacheTikaDocumentParser().parse(new FileInputStream(
                "C:\\Users\\MacBookPro\\Downloads\\阿里巴巴java开发规范(嵩山版).pdf"));//第二种方式
        EmbeddingStoreIngestor.ingest(document, inMemoryEmbeddingStore);//添加文档到内存里面
        return chatModel.chat("DTO是什么?");
    }
}

测试:

image-20250806172449724

image-20250806174643310

MCP协议

为什么有MCP

大模型需要 MCP(Model Context Protocol,模型上下文协议)是为了解决传统集成方式下 LLM(大语言模型)与外部世界连接的低效和碎片化问题。以下是核心痛点和 MCP 的解决方案分析:

⚠️ 一、无 MCP 前的核心痛点

  1. 数据孤岛与静态知识局限
    • LLM 依赖训练时的静态数据,无法访问实时信息(如最新天气、股价、航班动态),导致回答过时或错误。
    • 例如:旅行规划场景中,AI 无法查询实时机票价格或酒店房态,只能提供基于历史数据的建议。
  2. 集成复杂度高(M×N 问题)
    • 点对点集成:每个工具(如数据库、API)需单独开发适配接口。若存在 M 个模型和 N 个工具,需开发 M×N 个定制连接,维护成本爆炸式增长。
    • 协议碎片化:不同工具使用不同接口(SQL、REST API、GraphQL),开发者需学习多种协议,小公司难以接入高德地图等闭源服务。
  3. 动态性与灵活性缺失
    • 工具功能变更(如新增参数)需重写集成代码,无法自动适配。
    • 缺乏统一发现机制:AI 无法动态感知可用工具,需硬编码工具描述到提示词中。
  4. 安全与维护风险
    • 分散的权限管理和认证机制增加安全漏洞风险。
    • 紧耦合设计导致单点故障:某接口变更可能引发全线崩溃。

二、MCP 的解决方案与核心价值

MCP 通过 标准化协议 重构 LLM 与外部资源的交互方式,类比为 “AI 世界的 USB-C 接口”:

痛点维度传统方案MCP 方案优势
集成复杂度M×N 定制适配工具一次封装 → 所有 AI 自动兼容成本从 O(M×N) 降至 O(M+N)110
动态扩展硬编码接口,变更需重写代码工具主动声明能力,Client 自动发现2无缝兼容升级
实时性依赖轮询,延迟显著毫秒级数据流更新(如 SSE 推送)2决策滞后从小时级压缩到秒级
开发效率每对接新工具需数月小公司 1 天完成调试(如高德地图)2降低 90% 开发时间
安全与标准化分散权限管理统一认证框架(OAuth2/API Key)69集中化风险控制

💡 MCP 核心架构

  1. MCP Server
    • 封装工具/数据源(如天气查询、支付接口),暴露标准化能力(list_toolscall_tool)。
    • 示例:创建天气查询 Server,仅需 10 行 Python 代码(使用 @tool 装饰器)。
  2. MCP Client
    • 嵌入 LLM 应用(如 Claude Desktop),动态发现可用工具并组装提示词。
    • 自动转换用户请求为 MCP 标准调用(JSON-RPC 2.0 格式)。
  3. 协议层
    • 传输无关:支持 HTTP/Stdio/WebSocket,统一本地工具与远程 API 调用。
    • 错误处理:结构化错误码替代 HTTP 状态码,提升健壮性。

image-20250806181105577

MCP入门概念

  • 是什么?

    • MCP协议官网:https://modelcontextprotocol.io/docs/getting-started/intro

      MCP是一种开放协议,它标准化了应用程序向大型语言模型(LLMs)提供上下文的方式。将MCP想象成AI应用的USB-C端口。就像USB-C提供了一种标准化的方式将您的设备连接到各种外围设备和配件一样,MCP提供了一种标准化的方式将AI模型连接到不同的数据源和工具。MCP使您能够在LLMs之上构建代理和复杂的流程,并将您的模型与外界连接起来。

      MCP提供:

      • 一个不断增长的预构建集成列表,您的LLM可以直接插入其中

      • 一种为AI应用构建自定义集成的方式

      • 一个任何人都可以自由实施和使用的开放协议

      • 在不同应用之间切换的灵活性,并随身携带您的上下文

    • LangChain4j支持MCP协议官网:https://docs.langchain4j.info/tutorials/mcp

      • LangChain4j 支持模型上下文协议 (MCP),用于与符合 MCP 的服务器通信,这些服务器可以提供和执行工具。有关该协议的一般信息可以在 MCP 网站 上找到。

        该协议指定了两种传输类型,两种都受支持:

        • HTTP:客户端请求一个 SSE 通道来接收来自服务器的事件,然后通过 HTTP POST 请求发送命令。
        • stdio:客户端可以将 MCP 服务器作为本地子进程运行,并通过标准输入/输出直接与其通信。
    • 一句话

      • 大模型版的OpenFeignOpenFeign用于微服务之间通讯,MCP用户大模型之间的通讯

        MCP就像是AI世界的"万能适配器"。

        想象你有很多不同类型的服务和数据库,每个都有自己独特的"说话方式"。AI需要和这些服务交流时就很麻烦因为要学习每个服务的"语言"。

        MCP解决了这个问题 - 它就像一个统一的翻译官,让AI只需学一种"语言"就能和所有服务交流。

        这样开发者不用为每个服务单独开发连接方式,AI也能更容易获取它需要的信息。

        如果你是一个后端同学,那么应该接触或听说过gRPC。gRPC通过标准化的通信方式可以实现不同语言开发的服务之间进行通信,那么MCP专门为AI模型设计的"翻译官和接口管理器",让AI能以统一方式与各种应用或数据源交互。

能干嘛?

  • 提供了一种标准化的方式来连接LLMs需要的上下文,MCP就类似于一个Agent时代的Type-C协议,希望能将不同来源的数据、工具、服务统一起来供大模型调用

没有MCP的时候,自己整自己的

image-20250807155426250

有了MCP,统一了标准

image-20250807155452972

总结

MCP 厉害的地方在于,不用重复造轮子。

过去每个软件(比如微信、Excel)都要单独给 AI 做接口,

现在 MCP 统一了标准,就像所有电器都用 USB-C 充电口,AI 一个接口就能连接所有工具

MCP就是比FunctionCalling的更高一级抽象,也是实现智能体Agent的基础

举个例子:

image-20250807170137391

解释

image-20250807170159553

怎么玩?

地址:http://mcp.so/zh

image-20250807184506099

MCP知识架构

MCP遵循客户端-服务端包含以下几个核心部分

image-20250807170302534

  • MCP主机(MCP Hosts):发起请求的AI应用程序,比如聊天机器人、AI驱动的IDE
  • MCP客户端(MCP Clients):在主机程序内部,与MCP服务器保持1:1的连接
  • MCP服务端(MCP Servers):为MCP客户端提供上下文、工具和提示信息
  • 本地资源(Local Resources):本地计算机可供MCP服务器安全访问的资源,如文件、数据库。
  • 远程资源(Remote Resources):MCP服务器可以连接到的远程资源,如通过API提供的数据

MCP通信协议中,一般有两种模式

image-20250807170726771

  1. STDIO(标准输入/输出)

    支持标准输入和输出流通信,主要用于本地集成、命令行工具等场景。

  2. SSE(Server-Sent Events)

    支持使用HTTP POST请求进行服务器到客户端流式处理,以实现客户端到服务器的通信。

两者对比

image-20250807170924432

案例实战

需求说明

前置环境

API-Key配置环境变量里面,配置完成之后一定要重启,不然报错!

如何编写MCP程序?

地址:https://docs.langchain4j.info/tutorials/mcp

image-20250807191610869

moduleLangChain4j-14chat-mcp

pom

xml
<properties>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- 实现流式输出 -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-reactor</artifactId>
    </dependency>
    <!--DashScope (Qwen)接入阿里云百炼平台
       https://docs.langchain4j.dev/integrations/language-models/dashscope
   -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-community-dashscope-spring-boot-starter</artifactId>
    </dependency>
    <!-- MCP Client依赖 -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-mcp</artifactId>
    </dependency>
</dependencies>

yaml

yaml
server:
  port: 9014
  servlet:
    encoding:
      charset: UTF-8
      enabled: true
      force: true
spring:
  application:
    name: LangChain4j-14chat-mcp
langchain4j:
  community:
    dashscope:
      streaming-chat-model:
        api-key: ${aliQWen-api}
        model-name: qwen-plus
      chat-model:
        api-key: ${aliQWen-api}
        model-name: qwen-plus
# 只有日志级别调整为debug级别,同时配置以上 langchain 日志输出开关才有效
logging:
  level:
    dev:
      langchain4j: debug

McpService

java
package com.service;

import reactor.core.publisher.Flux;

public interface McpService {
    Flux<String> chat(String question);
}

McpCallServerController

java
package com.lazy.controller;

import com.lazy.service.McpService;
import dev.langchain4j.mcp.McpToolProvider;
import dev.langchain4j.mcp.client.DefaultMcpClient;
import dev.langchain4j.mcp.client.McpClient;
import dev.langchain4j.mcp.client.transport.McpTransport;
import dev.langchain4j.mcp.client.transport.stdio.StdioMcpTransport;
import dev.langchain4j.model.chat.StreamingChatModel;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.tool.ToolProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

import java.util.List;
import java.util.Map;

@RestController
public class McpCallServerController
{
    @Autowired
    private StreamingChatModel streamingChatModel;

    @GetMapping("/mcp/chat")
    public Flux<String> chat(@RequestParam("question") String question)
    {
        /**1.构建McpTransport协议
         *
         * 1.1 cmd:启动 Windows 命令行解释器。
         * 1.2 /c:告诉 cmd 执行完后面的命令后关闭自身。
         * 1.3 npx:npx = npm execute package,Node.js 的一个工具,用于执行 npm 包中的可执行文件。
         * 1.4 -y 或 --yes:自动确认操作(类似于默认接受所有提示)。
         * 1.5 @baidumap/mcp-server-baidu-map:要通过 npx 执行的 npm 包名
         * 1.6 BAIDU_MAP_API_KEY 是访问百度地图开放平台API的AK
         */
        McpTransport transport = new StdioMcpTransport.Builder()
                .command(List.of("cmd", "/c", "npx", "-y", "@baidumap/mcp-server-baidu-map"))
                .environment(Map.of("BAIDU_MAP_API_KEY", System.getenv("BAIDU_MAP_API_KEY")))
                .build();

        // 2.构建McpClient客户端
        McpClient mcpClient = new DefaultMcpClient.Builder()
                .transport(transport)
                .build();

        // 3.创建工具集和原生的FunctionCalling类似
        ToolProvider toolProvider = McpToolProvider.builder()
                .mcpClients(mcpClient)
                .build();

        // 4.通过AiServivces给我们自定义接口McpService构建实现类并将工具集和大模型赋值给AiService
        McpService mcpService = AiServices.builder(McpService.class)
                .streamingChatModel(streamingChatModel)
                .toolProvider(toolProvider)
                .build();

        // 5.调用我们定义的HighApi接口,通过大模型对百度mcpserver调用
        return mcpService.chat(question);
    }
}

先尝试在cmd 中执行 npx -y @baidumap/mcp-server-baidu-map 命令,查看 MCP 服务端是否能够正常启动。如果不能正常启动,需要查看错误信息并解决。我在此遇到问题,错误信息是「请求 npm 淘宝源下的 baidu map mcp server 失败,原因是证书过期」,于是我将 npm 镜像源又切回默认,然后再尝试执行,提示 Baidu Map MCP Server running on stdio,正常启动 MCP 服务端。

测试

image-20250808160104933

image-20250808160228423

复习

  • Function Calling,为了让大模型使用Util工具

  • RAG,为了让大模型获取足够的上下文

  • MCP,为了让大模型之间的调用

    image-20250807213432451

    image-20250807213443872

    image-20250807213536712

    原理说明

    image-20250807213549809