LangChain4j 框架模仿豆包实现智能对话系统:架构与功能详解

系统整体架构设计

基于 LangChain4j 框架构建的智能对话系统采用 "前后端分离 + 大模型中枢" 的三层架构设计,实现了与豆包类似的智能交互体验。系统架构图如下所示:

java 复制代码
┌──────────────────────────────────────────────────────────┐
│                        前端展示层                         │
│  ┌─────────┐  ┌─────────┐  ┌─────────┐  ┌─────────┐      │
│  │ 对话界面 │  │ 输入组件 │  │ 历史记录 │  │ 用户操作 │    │
│  └─────────┘  └─────────┘  └─────────┘  └─────────┘      │
├──────────────────────────────────────────────────────────┤
│                      通信与协议层                         │
│  ┌────────────┐  ┌────────────┐  ┌────────────┐          │
│  │  SSSE      │  │  REST API  │  │  消息格式   │          │
│  └────────────┘  └────────────┘  └────────────┘          │
├──────────────────────────────────────────────────────────┤
│                        后端逻辑层                         │
│  ┌─────────┐  ┌─────────┐  ┌─────────┐  ┌─────────┐      │
│  │ 模型调用 │  │ 上下文  │  │ 提示工程 │  │ 检索增强 │     │
│  │ (LLM)   │  │ 管理    │  │  模块   │  │  (RAG)  │       │
│  └─────────┘  └─────────┘  └─────────┘  └─────────┘      │
├──────────────────────────────────────────────────────────┤
│                      数据持久层                           │
│  ┌─────────┐  ┌─────────┐  ┌─────────┐  ┌─────────┐      │
│  │ 对话历史 │  │ 向量存储 │  │ 知识库  │  │ 用户信息 │     │
│  └─────────┘  └─────────┘  └─────────┘  └─────────┘      │
└──────────────────────────────────────────────────────────┘

图 1:LangChain4j 智能对话系统架构图

该架构的核心优势在于:

  • 前后端解耦:前端采用 Vue3 构建交互界面,后端基于 LangChain4j 处理 AI 逻辑,分工明确
  • 大模型能力封装:通过 LangChain4j 标准化接口,屏蔽不同 LLM 提供商的差异
  • 企业级扩展:支持集群部署、负载均衡和数据持久化,满足高并发场景

前端功能模块详解

1. 对话界面模块

前端界面采用 Vue3 实现,模仿豆包的 UI 设计风格,主要包含三大区域:

顶部导航区,中部对话区以及底部输入区。

html 复制代码
<template>
    <!-- 整体容器 -->
    <div class="doubao-layout">
        <!-- 1. 顶部导航区 -->
        <header class="doubao-header">
            <div class="header-inner">
                <!-- 标题与新对话 -->
                <div class="left-group ">
                    <el-button type="primary" icon="Plus" size="mini" class="mr-4" @click="handleNewChat">
                        新对话
                    </el-button>
                    <h2 class="header-title">{{ chatTitle }}</h2>
                </div>

                <!-- 操作与头像 -->
                <div class="right-group">
                    <el-icon @click="handleShare">
                        <Share />
                    </el-icon>
                    <el-icon @click="handleFavorite">
                        <Star />
                    </el-icon>
                    <el-avatar size="medium"
                        src="https://img0.baidu.com/it/u=2648433959,1760892301&fm=253&fmt=auto&app=120&f=JPEG?w=500&h=500"
                        class="cursor-pointer" @click="handleUserCenter" />
                </div>
            </div>
        </header>

        <!-- 2. 对话内容区 -->
        <div class="chat-container">
            <!-- 对话内容区 -->
            <section class="doubao-chat" ref="chatContainer">
                <div class="chat-inner">
                    <!-- 对话历史列表 -->
                    <div v-for="(msg, index) in chatHistory" :key="index" class="message-container">
                        <!-- 用户消息 - 居右对齐 -->
                        <div v-if="msg.isUser" class="user-message">
                            <div class="message-bubble">
                                <div style="display: flex;align-items: center;" class="message-content">
                                    <p style="text-align: left;margin-right: 7px;" class="message-text">{{ msg.content
                                    }}</p>
                                    <el-avatar size="medium"
                                        src="https://img0.baidu.com/it/u=2648433959,1760892301&fm=253&fmt=auto&app=120&f=JPEG?w=500&h=500"
                                        class="avatar-fixed" @click="handleUserCenter" />
                                </div>
                            </div>
                        </div>

                        <!-- AI消息 - 居左对齐 -->
                        <div v-else class="ai-message">
                            <div class="message-bubble">
                                <div style="display: flex;align-items: center;" class="message-content">
                                    <el-avatar size="medium" src="/doubao.png" @click="handleUserCenter"
                                        class="avatar-fixed" />
                                    <p style="text-align: left;margin-left: 7px;" class="message-text">{{ msg.content }}
                                    </p>
                                </div>
                            </div>
                        </div>
                    </div>

                </div>
            </section>
        </div>


        <!-- 3. 输入对话框 -->
        <footer class="doubao-input">
            <div class="input-inner">
                <el-input v-model="prompt" class="input-main" placeholder="请输入内容..." clearable
                    @keyup.enter="sendMessage" />
                <el-button type="primary" icon="Search" size="mini" class="ml-2" @click="sendMessage"
                    style="margin-left: 10px;">
                    发送
                </el-button>
            </div>
        </footer>
    </div>
</template>
核心交互逻辑:
  • 自动滚动 :通过监听chatHistory变化,使用scrollTop = scrollHeight实现新消息自动定位
  • 对话标题 :根据历史记录动态更新标题,使用computed属性实现响应式
  • 用户操作:集成分享(复制链接)、收藏(引导快捷键)、新建对话等功能

2. 状态管理模块

采用组合式 API 管理对话状态,核心代码如下:

javascript 复制代码
import { ref, computed, watch } from 'vue';
import { useRoute } from 'vue-router';
import { ElMessage } from 'element-plus';

// 对话状态
const chatHistory = ref([]);
const prompt = ref('');
const isLoading = ref(false);
const currentAIResponse = ref('');

// 动态标题
const route = useRoute();
const chatTitle = computed(() => {
  if (chatHistory.value.length > 1) {
    const firstUserMsg = chatHistory.value.find(msg => msg.isUser);
    return firstUserMsg?.content || '新对话';
  }
  return '新对话';
});

// 自动滚动
const chatContainer = ref(null);
const scrollToBottom = () => {
  if (chatContainer.value) {
    chatContainer.value.scrollTop = chatContainer.value.scrollHeight;
  }
};
watch(chatHistory, () => nextTick(scrollToBottom));

3. 主要功能展示

创建新对话:

对话详情:

后端核心功能实现

后端基于 LangChain4j 框架构建,核心功能模块包括:

配置文件:

java 复制代码
server:
  port: 9000
  servlet:
    encoding:
      charset: UTF-8
      enabled: true
      force: true


# openai相关配置
openai:
  apiKey: #替换成自己的apiKey
  model: gpt-4o-mini
  baseUrl: https://api.chatanywhere.tech/v1/
  temperature: 0.7
  # 单条对话最多存储多少条对话历史记录
  maxMessages: 100


spring:
  application:
    name: llm_app
  datasource:
    druid:
      url: jdbc:mysql://localhost:3306/llm_app?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
      username: # 数据库用户名
      password: # 数据库密码 不存储到数据库就不需要
      driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
  data:
    redis:
      password: # 你的redis密码 如果设置了
      database: 1
      host: localhost
      port: 6379
      timeout: 5000

1. 大模型交互模块

通过LLMConfig进行统一配置,统一管理大模型调用,支持多模型切换:

java 复制代码
package com.example.config;

import com.example.entity.ChatLLM;
import com.example.service.*;
import dev.langchain4j.memory.chat.ChatMemoryProvider;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.memory.chat.TokenWindowChatMemory;
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.model.openai.OpenAiTokenCountEstimator;
import dev.langchain4j.service.AiServices;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @Author: znly
 * @Description:
 * @Date: 2025/7/2 9:08
 */
@Configuration
public class LLMConfig {

    @Autowired
    private ApplicationContext context;

    @Resource
    private RedisChatMemoryStore redisChatMemoryStore;

    @Autowired
    private OpenAIProperties openAIProperties;

    @Bean
    public ChatModel chatModel() {
        return OpenAiChatModel.builder()
                .modelName(openAIProperties.getModel())
                .baseUrl(openAIProperties.getBaseUrl())
                .apiKey(openAIProperties.getApiKey())
                .temperature(openAIProperties.getTemperature())
                .build();
    }


    /**
     * 创建一个流式模型
     *
     * @return
     */
    @Bean
    public StreamingChatModel streamingChatModel() {
        return OpenAiStreamingChatModel.builder()
                .modelName(openAIProperties.getModel())
                .baseUrl(openAIProperties.getBaseUrl())
                .apiKey(openAIProperties.getApiKey())
                .temperature(openAIProperties.getTemperature())
                .maxTokens(openAIProperties.getMaxTokens())
                .build();
    }

    @Bean
    public ChatLLMService chatLLMService(StreamingChatModel streamingChatModel) {
        return AiServices.builder(ChatLLMService.class)
                .streamingChatModel(streamingChatModel)
                .chatMemoryProvider(memoryId -> {
                    return MessageWindowChatMemory.withMaxMessages(10);
                })
                .build();
    }

    @Bean(name = "chatMessageWindowChatMemory")
    public ChatMemoryAssistant chatMessageWindowChatMemory(ChatModel chatModel) {
        return AiServices.builder(ChatMemoryAssistant.class)
                .chatModel(chatModel)
                .chatMemoryProvider(memoryId -> {
                    return MessageWindowChatMemory.withMaxMessages(10);
                })
                .build();
    }

    @Bean(name = "chatTokenWindowChatMemory")
    public ChatMemoryAssistant chatTokenWindowChatMemory(ChatModel chatModel) {
        return AiServices.builder(ChatMemoryAssistant.class)
                .chatModel(chatModel)
                .chatMemoryProvider(memoryId -> {
                    return TokenWindowChatMemory.withMaxTokens(1000, new OpenAiTokenCountEstimator("gpt-4o-mini"));
                })
                .build();
    }

    @Bean(name = "chatRedisChatMemory")
    public ChatMemoryAssistant chatRedisChatMemory(ChatModel chatModel) {
        ChatMemoryProvider chatMemoryProvider = memoryId -> MessageWindowChatMemory.builder()
                .id(memoryId)
                .maxMessages(10)
                .chatMemoryStore(redisChatMemoryStore)
                .build();

        return AiServices.builder(ChatMemoryAssistant.class)
                .chatModel(chatModel)
                .chatMemoryProvider(chatMemoryProvider)
                .build();
    }

    @Bean(name = "chatExternal")
    public ChatLLMServiceSimple chatLLMServiceExternal(ChatModel chatModel) {
        return AiServices.builder(ChatLLMServiceSimple.class)
                .chatModel(chatModel)
                .tools(new WeatherService())
                .build();
    }

    @Bean(name = "chatLLM")
    public ChatMemoryAssistant chatLLM(ChatModel chatModel) {
        ChatMemoryProvider chatMemoryProvider = memoryId -> MessageWindowChatMemory.builder()
                .id(memoryId)
                .maxMessages(10)
                .chatMemoryStore(redisChatMemoryStore)
                .build();

        return AiServices.builder(ChatMemoryAssistant.class)
                .chatModel(chatModel)
                .chatMemoryProvider(chatMemoryProvider)
                .tools(new ExternalService())
                .build();
    }

    @Bean(name = "chatLLMStream")
    public ChatMemoryAssistant chatLLMStream(StreamingChatModel streamingChatModel) {
        ChatMemoryProvider chatMemoryProvider = memoryId -> MessageWindowChatMemory.builder()
                .id(memoryId)
                .maxMessages(openAIProperties.getMaxMessages())
                .chatMemoryStore(redisChatMemoryStore)
                .build();

        return AiServices.builder(ChatMemoryAssistant.class)
                .streamingChatModel(streamingChatModel)
                .chatMemoryProvider(chatMemoryProvider)
                .tools(new ExternalService())
                .build();
    }


}

2. 上下文管理模块

实现对话历史的存储与检索,支持会话级和用户级上下文:

本系统通过redis实现临时存储,不存储至数据库,可以在配置文件中中对存储上限进行动态修改。

java 复制代码
@Bean(name = "chatLLMStream")
    public ChatMemoryAssistant chatLLMStream(StreamingChatModel streamingChatModel) {
        ChatMemoryProvider chatMemoryProvider = memoryId -> MessageWindowChatMemory.builder()
                .id(memoryId)
                .maxMessages(openAIProperties.getMaxMessages())
                .chatMemoryStore(redisChatMemoryStore)
                .build();

        return AiServices.builder(ChatMemoryAssistant.class)
                .streamingChatModel(streamingChatModel)
                .chatMemoryProvider(chatMemoryProvider)
                .tools(new ExternalService())
                .build();
    }

3. 提示工程模块

封装提示词模板与解析逻辑,提升大模型输出质量:

例如,可以利用@SystemMessage来定义你的提示词,确保大模型只回复相关领域的知识内容。

java 复制代码
package com.example.service;

import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import reactor.core.publisher.Flux;

/**
 * @Author: znly
 * @Description:
 * @Date: 2025/7/3 10:43
 */
public interface ChatMemoryAssistant {

    /**
     * 带记忆缓存的对话
     * @param userId
     * @param prompt
     * @return
     */
@SystemMessage("你是一位本科和研究生均毕业于北京大学的专业后端开发工程师,拥有十年大厂后端开发工作经验,你的主要编程语言是java和python。" +
"你只能回答你的业务领域内的问题,如果问题涉及到其他领域请回复不知道")
    String chat(@MemoryId String memoryId, @UserMessage String prompt);

    Flux<String> chatStream(@MemoryId String memoryId, @UserMessage String prompt);

}

4. 函数调用

针对大模型对于相关城市天气,当天时间等需要联网查询,或者是用户自己相关的知识,可以通过@Tool这个注解来实现函数调用。

例如:针对天气和时间问题,会调用以下两个函数来执行,大模型不会直接回答。

java 复制代码
package com.example.service;

import dev.langchain4j.agent.tool.P;
import dev.langchain4j.agent.tool.Tool;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

/**
 * @Author: znly
 * @Description: 外部服务类
 * @Date: 2025/7/3 22:18
 */
@Service
public class ExternalService {

    @Tool(value = "今天天气怎么样")
    public String getWeather(@P("城市") String city) {
        System.out.println("城市:" + city);
        //调用外部 服务

        return "今天" + city + "的天气是晴天";
    }

    @Tool(value = "今天日期是多少")
    public String getDate() {
        System.out.println("今天日期" + LocalDate.now().toString());
        return LocalDate.now().toString();
    }

    @Tool(value = "告诉我现在的北京时间")
    public String getTime() {
        // 获取当前时间
        LocalDateTime now = LocalDateTime.now();
        // 定义日期时间格式
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        // 格式化时间
        String formattedDateTime = now.format(formatter);
        // 输出格式化后的时间
        System.out.println("当前时间: " + formattedDateTime);
        return "当前时间是:" + formattedDateTime;
    }
}

5. 检索增强生成 (RAG) 模块

结合向量检索与大模型生成,提升专业领域回答准确性:

java 复制代码
// 文档加载与处理
DocumentLoader loader = new FileSystemDocumentLoader("knowledge-base");
DocumentParser parser = new MarkdownDocumentParser();
TextSplitter splitter = new RecursiveCharacterTextSplitter(
    RecursiveCharacterTextSplitterConfig.builder()
        .chunkSize(800)
        .chunkOverlap(100)
        .build()
);
List<Document> documents = splitter.split(parser.parse(loader.load()));

// 向量存储
EmbeddingModel embeddingModel = OpenAiEmbeddingModel.builder()
    .apiKey(API_KEY)
    .build();
EmbeddingStore embeddingStore = PineconeEmbeddingStore.builder()
    .apiKey(PINECONE_KEY)
    .environment("us-west1-gcp")
    .indexName("knowledge-index")
    .build();
embeddingStore.storeDocuments(documents, embeddingModel);

// RAG服务
Rag rag = Rag.builder()
    .embeddingModel(embeddingModel)
    .embeddingStore(embeddingStore)
    .documentLoader(loader)
    .textSplitter(splitter)
    .build();

// 生成回答
String userQuery = "如何配置LangChain4j的RAG模块?";
List<Document> relevantDocs = rag.relatedDocuments(userQuery, 3);
String response = rag.chain()
    .promptTemplate(PromptTemplate.builder()
        .template("根据以下文档回答用户问题:\n{documents}\n用户问题:{userQuery}\n回答:")
        .build())
    .languageModel(model)
    .build()
    .invoke(Map.of("documents", relevantDocs, "userQuery", userQuery));

技术亮点与创新点

1. 多模态对话增强

在标准文本对话基础上,扩展支持:

  • 富文本处理:解析 Markdown 格式输出,支持加粗、列表等样式
  • 代码块处理:自动识别代码块并高亮显示,提升技术对话体验
  • 数学公式支持:通过 KaTeX 渲染 LaTeX 公式,满足学术交流需求

2. 智能对话控制

实现多种对话策略:

  • 追问策略:当用户问题不明确时,自动生成追问提示(如 "你能具体说明一下吗?")
  • 多轮上下文:智能截断过长对话历史,保留关键信息(基于令牌数统计)
  • 敏感词过滤:集成内容安全模块,自动识别并处理敏感信息

3. 企业级能力集成

与企业系统深度整合:

  • OA 系统集成:对接企业 OA 系统,自动提取日程、审批等信息
  • CRM 集成:根据客户历史订单生成个性化推荐
  • 知识库对接:连接企业内部知识库,提供专业领域支持

应用场景与典型案例

1. 企业智能客服

某电商平台集成该系统后,实现:

  • 85% 的常见问题自动解答,减少客服人力成本
  • 基于购买历史的个性化推荐,提升转化率 15%
  • 多轮对话上下文保持,客户满意度提升 20%

2. 技术支持助手

为软件开发团队提供:

  • 代码问题解答(基于官方文档和项目代码库)
  • 错误日志分析与解决方案推荐
  • 技术选型建议(如 "Spring Boot 与 Quarkus 如何选择")

3. 学术研究助手

服务科研人员:

  • 文献摘要生成与关键信息提取
  • 实验方案设计建议
  • 论文写作辅助(语法检查、引用建议)

总结与未来规划

基于 LangChain4j 构建的智能对话系统,充分发挥了 Java 的企业级优势和 LangChain4j 的大模型集成能力,在功能完整性、性能表现和可扩展性方面均达到生产级水平。系统不仅实现了豆包的核心对话功能,还通过 RAG 技术、多模态支持和企业级集成,拓展了智能对话的应用边界。

未来规划包括:

  1. 多模态增强:支持语音、图像输入输出,实现更自然的交互
  2. 联邦学习集成:保护企业数据隐私,支持跨机构协作
  3. 边缘计算优化:针对边缘设备进行模型轻量化和推理优化
  4. 低代码平台:提供可视化流程编排工具,降低使用门槛

该系统的成功实践证明,Java 与 LangChain4j 的组合不仅适用于企业级大模型应用开发,还能在用户体验、性能优化和系统集成方面超越传统 Python 方案,为智能对话技术的工业化落地提供了新的技术路径。