【LangChain4J-03】Springboot 项目搭建框架

【LangChain4J-03】Springboot 项目搭建框架

  • [🚀 LangChain4J--Springboot 项目搭建框架](#🚀 LangChain4J--Springboot 项目搭建框架)
    • [🧭 一、环境准备](#🧭 一、环境准备)
      • [🔧 本地模型准备(必须)](#🔧 本地模型准备(必须))
    • [📦 二、创建项目 & 引入依赖](#📦 二、创建项目 & 引入依赖)
    • [⚙️ 三、配置文件 `application.yml`](#⚙️ 三、配置文件 application.yml)
    • [🗂️ 四、项目结构](#🗂️ 四、项目结构)
    • [🧩 五、整体架构:单一对话入口 + 策略模式 + 模板方法 + 工厂](#🧩 五、整体架构:单一对话入口 + 策略模式 + 模板方法 + 工厂)
    • [🔥 六、对话 API 调用:从请求到模型](#🔥 六、对话 API 调用:从请求到模型)
      • [6.1 控制器 `ChatController`(只做三件事)](#6.1 控制器 ChatController(只做三件事))
      • [6.2 业务层 `ChatService`(路由 + 编排)](#6.2 业务层 ChatService(路由 + 编排))
      • [6.3 抽象基类 `AbstractChatHandler`(模板方法)](#6.3 抽象基类 AbstractChatHandler(模板方法))
      • [6.4 工厂 `ChatHandlerFactory`](#6.4 工厂 ChatHandlerFactory)
    • [🎛️ 七、七种对话模式(`ChatMode` 枚举)](#🎛️ 七、七种对话模式(ChatMode 枚举))
    • [🌊 八、流式输出与「真中断」机制(重点)](#🌊 八、流式输出与「真中断」机制(重点))
      • [8.1 流式(`POST /api/chat/stream`,SSE)------ 可「接口中断」](#8.1 流式(POST /api/chat/stream,SSE)—— 可「接口中断」)
      • [8.2 非流式(`POST /api/chat`,`DeferredResult`)------ 需「显式取消信号」](#8.2 非流式(POST /api/chatDeferredResult)—— 需「显式取消信号」)
      • [8.3 为什么两种中断方式不同?](#8.3 为什么两种中断方式不同?)
    • [🧠 九、记忆 / 人设 / 上下文裁剪 / 清空 机制](#🧠 九、记忆 / 人设 / 上下文裁剪 / 清空 机制)
    • [🎚️ 十、参数体系:采样参数 + L7 全参数自定义](#🎚️ 十、参数体系:采样参数 + L7 全参数自定义)
      • [10.1 采样参数(`params` 模式)](#10.1 采样参数(params 模式))
      • [10.2 L7 全参数自定义(`custom` 模式)](#10.2 L7 全参数自定义(custom 模式))
      • [10.3 三套通用模板(精准 / 通用 / 创意)的区别与效果](#10.3 三套通用模板(精准 / 通用 / 创意)的区别与效果)
    • [📦 十一、DTO 与校验 / 全局异常](#📦 十一、DTO 与校验 / 全局异常)
      • [统一请求 / 响应](#统一请求 / 响应)
      • [校验 & 异常](#校验 & 异常)
    • [🖥️ 十二、前端聊天页面(前后端不分离)](#🖥️ 十二、前端聊天页面(前后端不分离))
    • [▶️ 十三、运行与调试](#▶️ 十三、运行与调试)
      • [不用前端的 curl 调试示例(单一对话入口)](#不用前端的 curl 调试示例(单一对话入口))
      • [SSE 流式(逐 token)](#SSE 流式(逐 token))
      • 接口速查表
    • [🚨 十四、常见问题排查](#🚨 十四、常见问题排查)
    • [📎 附录 A · LangChain4j 注解(声明式 API)与命令式对比](#📎 附录 A · LangChain4j 注解(声明式 API)与命令式对比)
      • [A.1 两路 API 总览](#A.1 两路 API 总览)
      • [A.2 声明式注解一览(含义与作用)](#A.2 声明式注解一览(含义与作用))
      • [A.3 声明式完整案例](#A.3 声明式完整案例)
      • [A.4 命令式(本项目)等价写法](#A.4 命令式(本项目)等价写法)
      • [A.5 声明式 vs 命令式 对比](#A.5 声明式 vs 命令式 对比)
      • [A.6 小结](#A.6 小结)

🚀 LangChain4J--Springboot 项目搭建框架

📘 本文档记录 Spring Boot 3 + JDK 17 + LangChain4j + Ollama(qwen3:4b-instruct) 的整合过程与当前代码架构。

目标:先把「项目搭建」和「接口调试」吃透,不展开 RAG / Tools / 向量库等进阶知识点。

💡 阅读提示:文中 紫色 为注解名,蓝色 为 API/类名,橙色 为配置参数,红色 为注意事项。


🧭 一、环境准备

组件 版本 / 要求 说明
JDK 17 LangChain4j 1.x 与 Spring Boot 3.x 均要求 Java 17+
Spring Boot 3.4.5 3.x 线,适配 JDK 17
LangChain4j 1.10.0-beta18 1.x 全部为 beta,但核心 API 已稳定
Ollama 最新版 本地大模型运行载体
模型 qwen3:4b-instruct 本地运行的对话模型
Maven 3.8+ 构建工具

🔧 本地模型准备(必须)

bash 复制代码
# 1) 启动 Ollama 服务(默认监听 http://localhost:11434)
ollama serve

# 2) 拉取并确认模型可用
ollama pull qwen3:4b-instruct
ollama list          # 能看到 qwen3:4b-instruct 即正常

⚠️ 务必先保证 Ollama 在跑且模型已拉取 ,否则后端调用会报 Connection refusedmodel not found


📦 二、创建项目 & 引入依赖

本 Demo 采用 Maven 多模块 结构:父工程 allen-ai 统一管版本,子模块 langchain4j-springboot-demo 只声明自己需要的依赖(不带版本号)。

xml 复制代码
<!-- 📁 父工程 allen-ai/pom.xml:集中管理依赖与版本 -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.4.5</version>
    <relativePath/>
</parent>

<groupId>com.example</groupId>
<artifactId>allen-ai</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>            <!-- 父/聚合工程必须是 pom 打包 -->
<modules>
    <module>langchain4j-springboot-demo</module>
</modules>

<properties>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
    <langchain4j.version>1.10.0-beta18</langchain4j.version>
    <!-- 另含 mybatis-plus / knife4j / sa-token / poi / elasticsearch / docx4j 等其它模块用版本 -->
</properties>

<!-- ★ 只做"版本管理":dependencyManagement 声明版本,子 module 继承,自身不写版本 -->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-spring-boot-starter</artifactId>
            <version>${langchain4j.version}</version>
        </dependency>
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-ollama-spring-boot-starter</artifactId>
            <version>${langchain4j.version}</version>
        </dependency>
    </dependencies>
</dependencyManagement>
xml 复制代码
<!-- 📁 子模块 langchain4j-springboot-demo/pom.xml:只声明依赖,不写版本 -->
<parent>
    <groupId>com.example</groupId>
    <artifactId>allen-ai</artifactId>
    <version>1.0.0</version>
</parent>

<dependencies>
    <!-- Web:REST 接口 + 静态页面(前后端不分离) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- 🔧 核心 Starter:扫描 @AiService、自动装配 ChatModel / StreamingChatModel -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-spring-boot-starter</artifactId>
    </dependency>

    <!-- 🔧 Ollama 接入 Starter:按 yml 自动创建 OllamaChatModel / OllamaStreamingChatModel -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-ollama-spring-boot-starter</artifactId>
    </dependency>

    <!-- 参数校验:Jakarta Validation(@NotNull / @NotBlank / 自定义约束)+ 全局异常 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>

    <!-- Lombok:@Slf4j / @Data 等(编译期注解处理器,运行时不需要) -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

💡 langchain4j-spring-boot-starter 负责把带 @AiService 的接口变成 Spring Bean,并自动装配 ChatModel / StreamingChatModellangchain4j-ollama-spring-boot-starter 依据 application.yml 里的 langchain4j.ollama.* 创建模型 Bean,无需手写 new OllamaChatModel()
📌 版本说明:LangChain4j 的 1.x 在 Maven 上以 beta 形式发布(如本 Demo 使用的 1.10.0-beta18 ),这是官方常态,并非不稳定。自 1.0 起模型接口已由旧名 ChatLanguageModel.generate() 更名为 ChatModel.chat()ChatRequest/ChatResponse 也迁移到 dev.langchain4j.model.chat.request|response 包;本 Demo 已按此新 API 编写。


⚙️ 三、配置文件 application.yml

yaml 复制代码
server:
  port: 8088

langchain4j:
  ollama:
    chat-model:                      # 🔖 非流式 ChatModel 配置(由 starter 自动创建 Bean)
      base-url: http://localhost:11434
      model-name: qwen3:4b-instruct
      temperature: 0.7
      top-p: 0.9
      max-tokens: 2048
      num-ctx: 8192
      log-requests: true
      log-responses: true
    streaming-chat-model:            # 🔖 流式 StreamingChatModel 配置(Ollama starter 要求显式配置才会注册该 Bean)
      base-url: http://localhost:11434
      model-name: qwen3:4b-instruct
      temperature: 0.7
      top-p: 0.9
      max-tokens: 2048
      num-ctx: 8192
      log-requests: true
      log-responses: true

app:
  chat:
    memory-max-messages: 20         # 🟠 记忆滑动窗口:每个 session 最多保留 20 条消息
    default-temperature: 0.7        # 🟠 采样参数默认值(/params 模式按请求覆盖,null 时回退到这里)
    default-top-p: 0.9
    default-max-tokens: 512

logging:
  level:
    root: INFO
  charset:
    console: UTF-8                  # 🟠 保证控制台(启动横幅等中文)以 UTF-8 输出,避免 GBK 终端乱码

🔑 重点参数说明:

  • langchain4j.ollama.chat-model / streaming-chat-model:Ollama starter 会据此分别创建 ChatModelStreamingChatModel 两个 Bean。流式需要显式配置 streaming-chat-model 命名空间,否则该 Bean 不会被注册。
  • memory-max-messages:记忆窗口,自动裁剪更早消息,防止 Token 超限。
  • default-temperature / default-top-p / default-max-tokens:业务默认值,由 AppProperties 配置类读取(不再用散落的 @Value)。

🗂️ 四、项目结构

复制代码
allen-ai/                                                  ← 父工程(Maven 多模块,集中管版本)
├── pom.xml
└── langchain4j-springboot-demo/                           ← 子模块(只声明依赖,不写版本)
    ├── pom.xml
    ├── src/main/java/com/example/langchain4j/
    │   ├── Langchain4jDemoApplication.java                # 启动类
    │   ├── StartupNotifier.java                           # 启动成功横幅(@Order(MAX_VALUE))
    │   ├── config/
    │   │   ├── AppProperties.java                         # app.* 业务配置绑定
    │   │   ├── OllamaModelProperties.java                 # langchain4j.ollama.chat-model.* 绑定
    │   │   └── ChatMemoryConfig.java                      # ChatMemoryProvider(记忆工厂 + 缓存)
    │   ├── controller/
    │   │   └── ChatController.java                        # 单一对话入口 + 流式 + 元数据 + 清空
    │   ├── service/
    │   │   └── ChatService.java                           # 路由编排(按 mode 取 handler)
    │   ├── chat/                                          # ← 对话处理核心(按职责分子包)
    │   │   ├── handler/                                   # 策略族 + 工厂 + 流式回调契约
    │   │   │   ├── AbstractChatHandler.java               # 抽象基类 / 模板方法
    │   │   │   ├── BasicChatHandler.java                  # 模式 BASIC
    │   │   │   ├── AnnotationChatHandler.java             # 模式 ANNOTATION
    │   │   │   ├── TemplateChatHandler.java               # 模式 TEMPLATE
    │   │   │   ├── SystemChatHandler.java                 # 模式 SYSTEM
    │   │   │   ├── ParamsChatHandler.java                 # 模式 PARAMS
    │   │   │   ├── MemoryChatHandler.java                 # 模式 MEMORY
    │   │   │   ├── CustomChatHandler.java                 # 模式 CUSTOM(L7 全参数自定义)
    │   │   │   ├── ChatHandlerFactory.java                # 工厂:枚举 → 处理器
    │   │   │   └── StreamCallback.java                    # 流式输出回调接口
    │   │   ├── client/
    │   │   │   └── OllamaStreamClient.java                # 直连 Ollama 的可中断流式客户端
    │   │   └── support/
    │   │       ├── ChatTokenResult.java                   # 非流式结果载体
    │   │       ├── ChatCancellationRegistry.java          # 非流式取消信号注册表
    │   │       └── CustomParamCatalog.java                # L7 全参数目录 + 模板预设
    │   ├── dto/                                          # ← 请求/响应对象(按业务域分子包)
    │   │   ├── chat/     ChatRequestDTO / ChatResponse / ChatModeInfo
    │   │   ├── param/    ParamSpec / ParamPreset
    │   │   ├── user/     UserInfoDTO
    │   │   ├── session/  ClearRequest / ClearResponse
    │   │   └── common/   ErrorResponse
    │   ├── enums/
    │   │   ├── ChatMode.java                              # 对话模式枚举(含前端元数据)
    │   │   ├── OllamaField.java                           # Ollama /api/chat 协议字段名(去魔法值)
    │   │   ├── ParamField.java                            # 自定义参数 wire key(去魔法值)
    │   │   └── UserInfo.java                              # 预设用户(下拉选 sessionId)
    │   ├── exception/
    │   │   └── GlobalExceptionHandler.java                # 统一异常 → 友好 ErrorResponse
    │   ├── validation/
    │   │   ├── ValidChatRequest.java                      # 类级交叉校验注解
    │   │   └── ChatRequestValidator.java                  # 校验逻辑(记忆模式需 sessionId)
    │   └── util/
    │       └── NumberUtils.java                           # 采样参数回退默认值 / 范围裁剪
    └── src/main/resources/
        ├── application.yml
        ├── logback-spring.xml
        ├── system-prompt.txt                              # 固定全局人设(小Lang)
        └── static/
            └── chat.html                                  # 前端聊天页(前后端不分离)

🧩 五、整体架构:单一对话入口 + 策略模式 + 模板方法 + 工厂

当前代码不再 为每种模式各写一个 @AiService 接口 / 各写一个 *Request。而是:

一个统一入口 → 由 mode 枚举决定走哪条「策略处理器」,所有模式共用同一套请求/响应对象。
渲染错误: Mermaid 渲染失败: Parse error on line 4: ...erFactory.getHandler(mode)] D --> E{ -----------------------^ Expecting 'SQE', 'DOUBLECIRCLEEND', 'PE', '-)', 'STADIUMEND', 'SUBROUTINEEND', 'PIPE', 'CYLINDEREND', 'DIAMOND_STOP', 'TAGEND', 'TRAPEND', 'INVTRAPEND', 'UNICODE_TEXT', 'TEXT', 'TAGSTART', got 'PS'

  • 策略模式(Strategy) :每个模式是一个 AbstractChatHandler 子类,只实现 buildMessages()(构造发给模型的消息)等钩子,个性都在这里。
  • 模板方法(Template Method)AbstractChatHandler.chat() / stream() 是不可变的「一次对话」骨架(建消息 → 建请求 → 流式拉取 → 收尾写记忆),易变部分留给子类。
  • 工厂模式(Factory)ChatHandlerFactory 启动时收集所有 handler,用 EnumMap<ChatMode, AbstractChatHandler> 建立「枚举 → 处理器」映射。调用方只持有一个工厂,新增模式只需新增一个 Handler(开闭原则)。
  • 直连 Ollama 客户端(可中断) :用 OllamaStreamClient 替代 StreamingChatModel,以获得「前端中断后端生成」的能力(见第八章)。

🔥 六、对话 API 调用:从请求到模型

6.1 控制器 ChatController(只做三件事)

接收规范 DTO → 委派 ChatService → 封装响应。提供以下端点(路径前缀 /api/chat):

端点 方法 说明
/ POST 单一对话入口(非流式,可中断) ,由 mode 决定类型
/stream POST (text/event-stream) 单一对话入口(SSE 流式,可中断)
/cancel POST 非流式中断信号 (带 requestId,置位 cancelled)
/modes GET 模式元数据(供前端展示各模式区别)
/custom-params GET L7 全参数目录(供前端渲染自定义弹窗)
/presets GET 通用参数模板预设(精准/通用/创意)
/users GET 预设用户列表(供前端下拉选 sessionId)
/clear POST 清空指定 session 的记忆

非流式用 DeferredResult 异步执行(阻塞式生成不能占 Tomcat 请求线程);流式用 SseEmitter。两者都把 cancelledBooleanSupplier)一路传到 OllamaStreamClient,用于中断。

6.2 业务层 ChatService(路由 + 编排)

java 复制代码
@Service
public class ChatService {
    private final ChatHandlerFactory handlerFactory;
    private final ChatMemoryProvider memoryProvider;

    public ChatResponse chat(ChatRequestDTO req, BooleanSupplier cancelled) {
        AbstractChatHandler handler = handlerFactory.getHandler(req.getMode()); // 工厂按枚举取策略
        ChatTokenResult result = handler.chat(req, cancelled);                  // 模板方法:非流式
        // 记忆模式把 sessionId 回传前端......
        return ChatResponse.of(result.getText(), sessionId);
    }

    public void stream(ChatRequestDTO req, StreamCallback callback, BooleanSupplier cancelled) {
        AbstractChatHandler handler = handlerFactory.getHandler(req.getMode());
        handler.stream(req, callback, cancelled);                              // 模板方法:流式
    }

    public void clearMemory(String sessionId) {
        memoryProvider.get(sessionId).clear();                                 // 与处理器共用同一记忆缓存
    }
}

6.3 抽象基类 AbstractChatHandler(模板方法)

java 复制代码
public abstract class AbstractChatHandler {
    protected final ChatModel chatModel;
    protected final StreamingChatModel streamingChatModel;
    protected final OllamaModelProperties modelProps;
    protected final AppProperties appProps;
    @Autowired protected OllamaStreamClient streamingClient;   // 直连 Ollama 的可中断客户端

    // 模板方法(final,固定骨架)
    public final ChatTokenResult chat(ChatRequestDTO req, BooleanSupplier cancelled) { /* ... */ }
    public final void stream(ChatRequestDTO req, StreamCallback cb, BooleanSupplier cancelled) { /* ... */ }

    // 抽象方法(策略核心):构造发给模型的消息
    protected abstract List<ChatMessage> buildMessages(ChatRequestDTO req);
    // 钩子方法(默认实现,子类按需重写)
    protected ChatRequest buildRequest(List<ChatMessage> messages, ChatRequestDTO req) { /* 默认无采样参数 */ }
    protected void beforeChat(ChatRequestDTO req) { }
    protected void afterChat(ChatRequestDTO req, String answer) { }   // 记忆模式重写:把回答写回记忆
    public abstract ChatMode supportedMode();
}

关键点:

  • buildRequest() 默认不带采样参数;PARAMS 模式重写它以加 temperature/topP/maxOutputTokensCUSTOM 模式重写它来应用全部自定义参数。
  • 流式/非流式都复用 OllamaStreamClient.stream(request, cancelled, handler):读取 NDJSON、逐 token 回调;cancelled 为真时立即关闭底层连接(Ollama 真正停止),且不调用 afterChat(跳过记忆写入,避免保存被打断的答案)。

6.4 工厂 ChatHandlerFactory

java 复制代码
@Component
public class ChatHandlerFactory {
    private final Map<ChatMode, AbstractChatHandler> handlers;
    @Autowired
    public ChatHandlerFactory(List<AbstractChatHandler> allHandlers) {
        this.handlers = new EnumMap<>(ChatMode.class);
        for (AbstractChatHandler h : allHandlers) handlers.put(h.supportedMode(), h);
    }
    public AbstractChatHandler getHandler(ChatMode mode) {
        AbstractChatHandler h = handlers.get(mode);
        if (h == null) throw new IllegalArgumentException("不支持的对话模式:" + mode);
        return h;
    }
}

🎛️ 七、七种对话模式(ChatMode 枚举)

前端模式下拉、模式说明面板、默认选中项都来自 ChatMode 枚举(每个枚举自带面向前端的元数据:label / description / features / requiresSession / supportsParams / customizable)。

code 标签 演示重点 需要 sessionId 可调采样参数(3项) 全参数自定义(L7)
basic L1 · 基础单轮对话 最朴素:直接 UserMessage,无系统提示、无记忆
annotation L2 · @AiService 注解封装 叠加固定轻量人设(简洁分点),等价于 @AiService+@SystemMessage 的效果
template L3 · 模板参数对话 把 message 当模板变量拼进固定话术,演示 Prompt Template
system L4 · 系统人设对话 SystemMessage 设定专家人设(资深 Java 架构师)
params L5 · 采样参数调节 单次请求覆盖 temperature / topP / maxTokens
memory L6 · 带记忆多轮对话 sessionId 隔离 + 滑动窗口记忆 + 全局人设
custom L7 · 全参数自定义 在 L6 基础上开放全部对话参数可调(见第十章) ✅(16项)

实现位置:每个模式对应一个 com.example.langchain4j.chat.handler.*ChatHandler,只需实现 buildMessages()(与必要钩子)。例如:

java 复制代码
@Component
public class BasicChatHandler extends AbstractChatHandler {
    @Override public ChatMode supportedMode() { return ChatMode.BASIC; }
    @Override
    protected List<ChatMessage> buildMessages(ChatRequestDTO req) {
        return List.of(UserMessage.from(req.getMessage()));   // 最底层 API 等价物
    }
}
java 复制代码
// L5 采样参数:重写 buildRequest 钩子,覆盖采样参数
@Override
protected ChatRequest buildRequest(List<ChatMessage> messages, ChatRequestDTO req) {
    AppProperties.Chat chat = appProps.getChat();
    double temperature = NumberUtils.clampTemperature(
            NumberUtils.resolve(req.getTemperature(), chat.getDefaultTemperature()));
    // ... topP / maxTokens 同理(null 回退默认值,再做 [0,2] / [0,1] / >=1 裁剪)
    return ChatRequest.builder().messages(messages)
            .parameters(ChatRequestParameters.builder()
                .modelName(modelProps.getModelName())
                .temperature(temperature).topP(topP).maxOutputTokens(maxTokens).build())
            .build();
}

🌊 八、流式输出与「真中断」机制(重点)

早期版本用 LangChain4j 的 StreamingChatModel.chat()fire-and-forget ,拿不到可取消句柄:前端 abort 只能断开「前端↔后端」,后端↔Ollama 仍会算到自然结束(浪费算力,且被打断的完整答案还会被写进记忆)。

为此本项目自建 OllamaStreamClient ,用 JDK HttpClient 直连 Ollama /api/chat(stream:true),逐行解析 NDJSON,从而获得「可中断」能力。

8.1 流式(POST /api/chat/stream,SSE)------ 可「接口中断」

  • 前端用 AbortController.signal 发起 fetchemitter.onError(客户端断开)立即置 cancelled=true
  • OllamaStreamClient 读循环每次迭代检查 cancelled,为真则关闭 InputStream 断开 Ollama 连接,Ollama 真正停止生成 ,且不调用 afterChat(跳过记忆写入)。
  • 每收到一个 token 通过 StreamCallback.onToken 推给前端;结束推 done 事件(含文本 + token 数)。
java 复制代码
// ChatController.chatStream 片段
SseEmitter emitter = new SseEmitter(120_000L);
AtomicBoolean cancelled = new AtomicBoolean(false);
emitter.onError(e -> cancelled.set(true));     // 前端 abort → 连接断开 → 这里感知
emitter.onCompletion(() -> cancelled.set(true));
chatService.stream(req, new SseStreamCallback(emitter), cancelled::get);

8.2 非流式(POST /api/chatDeferredResult)------ 需「显式取消信号」

⚠️ 非流式是「生成完才返回一段 JSON」,服务端在生成期间从不向客户端写字节 ,Tomcat 无法在生成中途感知客户端断开(只能到最后写响应时才发现 Connection reset by peer,此时 Ollama 已跑完)。因此单纯的前端 abort 不足以让后端停止。

解决方式:带外 RPC 取消信号

  • 前端发起非流式请求时附带 requestId;点击「停止」时额外调用 POST /api/chat/cancel?requestId=...
  • 后端 ChatCancellationRegistry.cancel(requestId) 把对应 AtomicBoolean 置 true;OllamaStreamClient 检测到后立刻断开 Ollama 连接(真正停止),并跳过记忆写入。
  • 请求正常结束 / 出错 / 断开后都会 registry.remove(requestId) 清理,避免内存泄漏。
java 复制代码
// 非流式入口:注册 requestId → cancelled,异步执行
String requestId = (req.getRequestId() != null && !req.getRequestId().isBlank())
        ? req.getRequestId() : UUID.randomUUID().toString();
AtomicBoolean cancelled = cancellationRegistry.register(requestId);
DeferredResult<ChatResponse> dr = new DeferredResult<>(300_000L);
dr.onCompletion(() -> cancellationRegistry.remove(requestId));
dr.onError(e -> cancellationRegistry.remove(requestId));
CompletableFuture.supplyAsync(() -> chatService.chat(req, cancelled::get))
        .whenComplete((resp, ex) -> { /* 设置 dr 结果(友好错误) */ });

8.3 为什么两种中断方式不同?

维度 流式 /stream 非流式 /chat
响应形态 持续 flush 每个 token 生成完才返回一整段 JSON
能否靠「连接断开」感知中断 ✅ 可以(断连即时可感知) ❌ 不可以(中途不写字节)
触发中断的机制 前端 abort → emitter.onError(接口中断,可行) 前端另发 POST /cancel(带外 RPC 信号)
真正停 Ollama 的动作 关闭 OllamaStreamClientInputStream 同左(由 cancelled 标志驱动)

💡 一句话:流式靠连接断开就能接口中断;非流式因为单段 JSON 无法中途感知断连,只能显式发取消信号。 二者真正停 Ollama 的动作相同------断连接 + 跳过 afterChat 落库。


🧠 九、记忆 / 人设 / 上下文裁剪 / 清空 机制

固定全局人设(小Lang)

MemoryChatHandler / CustomChatHandlerbuildMessages() 里注入 system-prompt.txt 的内容(统一从 classpath 读取,全局只维护这一份),模型始终以「小Lang」身份作答;CUSTOM 模式若前端传了 system 参数则覆盖默认人设。

多用户隔离

ChatMemoryConfig 定义 ChatMemoryProvider:收到 sessionId → 建一个 MessageWindowChatMemory,并用 ConcurrentHashMap 缓存。前端通过 GET /api/chat/users 拿到预设用户(张三/李四...),以下拉单选方式选定 sessionId不同 sessionId 的对话互不可见

自动上下文裁剪

MessageWindowChatMemory.builder().maxMessages(20) 只保留最近 20 条消息,更早的自动丢弃 → 上下文有上限 → 避免 Token 超限。窗口大小由 app.chat.memory-max-messages 控制。

清空记忆

ChatMemoryConfigConcurrentHashMap 缓存每个 sessionId 对应的 ChatMemory 实例;ChatService.clearMemory() 注入同一个 ChatMemoryProvider,调用 memoryProvider.get(sessionId).clear() 删除该会话全部历史。前端「🧹 清空记忆」按钮即调用 POST /api/chat/clear

⚠️ LangChain4j 1.x 已移除旧版 ChatMemoryAccess.evictChatMemory(),请勿再使用。


🎚️ 十、参数体系:采样参数 + L7 全参数自定义

10.1 采样参数(params 模式)

PARAMS 模式在 buildRequest() 里覆盖 temperature / topP / maxOutputTokens,未传(null)时回退到 AppProperties 默认值;NumberUtils 负责 null 回退 + 范围裁剪([0,2] / [0,1] / >=1)。

10.2 L7 全参数自定义(custom 模式)

CustomChatHandlerbuildRequest() 里用 OllamaChatRequestParameters.Builder 应用前端传来的 customParams(key→值),数值安全解析、未配置项沿用默认值。

CustomParamCatalog 维护参数目录模板预设,并通过两个 GET 接口下发给前端:

  • GET /api/chat/custom-paramsList<ParamSpec>:16 个参数(label/类型/范围/默认值/作用说明),前端据此渲染「拖动横条 / 输入框」弹窗。
  • GET /api/chat/presetsList<ParamPreset>:三套通用模板(精准 / 通用 / 创意),取值集中在后端维护,前端不再写死。

⚠️ Ollama 不支持 OpenAI 风格参数presence_penalty / frequency_penalty 进入 OllamaChatRequestParameters 后会在 chat() 阶段抛 UnsupportedFeatureException(导致 500)。因此 CustomChatHandlerbuildRequest()刻意跳过这两个参数(仅打日志说明「已跳过」),不进入请求。


10.3 三套通用模板(精准 / 通用 / 创意)的区别与效果

GET /api/chat/presets 返回三套 ParamPreset,取值集中维护在 CustomParamCatalog.presets()。前端点击模板按钮即把这套取值整体套用到全部 16 个参数 ,省去逐项调参。三套的本质差异是「系统提示词 + 采样参数 」组合取向不同------用同一句提问 讲讲春天,你会看到截然不同的回答风格。

参数取值对照表(取自代码,非臆造)

参数(wire key) 🎯 精准 precision ⚖️ 通用 general 🎨 创意 creative
系统提示词 system 严谨精准、只基于事实、避免发散 (空 → 沿用默认「小Lang」人设) 富有想象力、鼓励发散、比喻/故事化
温度 temperature 0.1 0.7 1.2
核采样 top_p 0.1 0.9 1.0
取词数 top_k 1 40 80
最小概率 min_p 0.0 0.0 0.05
最大生成长度 max_tokens 256 512 1024
重复惩罚 repeat_penalty 1.0 1.1 1.0
话题/词频惩罚 presence/frequency 0.0 / 0.0 0.0 / 0.0 0.5 / 0.5
随机种子 seed 42(固定,可复现) 0(随机) 0(随机)
上下文窗口 num_ctx 2048 4096 8192

各自效果与适用场景

  • 🎯 精准模式temperature=0.1 + top_p=0.1 + top_k=1 + 固定 seed=42 → 采样空间被压到极窄,输出高度确定、可复现、贴近训练分布、几乎不发散max_tokens=256 限定短回答;专属系统提示词强制「只基于事实、避免主观推测」。适合事实问答、代码生成、确定性任务、需要结果可复现的场景。
  • ⚖️ 通用模式 :默认人设 + temperature=0.7 / top_p=0.9 / top_k=40 → 标准平衡档,日常对话的「中庸」表现,既不太死板也不太飘。num_ctx=4096 提供适中上下文。绝大多数场景的默认选择
  • 🎨 创意模式temperature=1.2 + top_p=1.0 + top_k=80 + min_p=0.05 + max_tokens=1024 + num_ctx=8192 → 采样空间最宽、输出新颖、独特、不拘一格 ,允许比喻与故事化,且能写更长、num_ctx 也最大以容纳长上下文。适合头脑风暴、文案/诗歌、发散性写作

⚠️ 关于「话题/词频惩罚」在 Ollama 下的真相 :三套模板都给 presence_penalty / frequency_penalty 赋了值(创意模式还设到 0.5),但这两个是 OpenAI 风格参数,Ollama 不支持CustomChatHandlerbuildRequest() 中会刻意跳过 (仅打日志),因此它们在 Ollama 上实际不生效 。创意模式的「发散感」主要靠 temperature / top_k / top_p / min_p 这些真正生效的参数体现,而不是靠这两个惩罚值。

💡 一句话区分:精准 = 低温度 + 固定种子 + 短输出 + 事实人设;通用 = 默认平衡;创意 = 高温度 + 宽采样 + 长输出 + 发散人设。 真正在 Ollama 上起作用的差异集中在这几组采样参数与系统提示词上。


📦 十一、DTO 与校验 / 全局异常

统一请求 / 响应

  • ChatRequestDTO(dto.chat):所有模式共用,含 mode(枚举) / message / sessionId / temperature,topP,maxTokens / stream / requestId(非流式中断关联) / customParams(Map)。@ValidChatRequest 做类级交叉校验:记忆模式必须带 sessionId
  • ChatResponse(dto.chat):content / sessionId / tokenCount / error,提供 of() / error() 工厂。
  • 其余:ChatModeInfoParamSpecParamPresetClearRequestClearResponseUserInfoDTO(dto 下各业务域子包)、ErrorResponse(dto.common)。

校验 & 异常

  • ValidChatRequest + ChatRequestValidator:类级约束(记忆模式需 sessionId),与单字段 @NotNull/@NotBlank 互补。
  • GlobalExceptionHandler(@RestControllerAdvice):所有异常收敛为 ErrorResponsemessage 一定是友好中文说明,绝不回传原始堆栈AsyncRequestNotUsableException / ClientAbortException(中断副产物)降级为 DEBUG,避免噪声。流式错误则直接在 StreamCallback.onError 里以友好事件推给前端。

🖥️ 十二、前端聊天页面(前后端不分离)

页面位于 src/main/resources/static/chat.html,启动后即自动映射为 http://localhost:8088/chat.html

功能:

  • 左侧「对话模式」面板按 GET /modes 渲染 7 种模式(默认选中 L7 全参数自定义);
  • 右侧配置面板:记忆模式出现 sessionId 下拉(来自 GET /users)、params 模式出现 temperature/topP/maxTokens 输入、custom 模式出现「自定义参数」弹窗(来自 GET /custom-paramsGET /presets);
  • 聊天区以气泡展示多轮对话,回车发送、Shift+Enter 换行;
  • 发送按钮为圆形图标:回答未结束时点击即中断 (流式走 SSE 断连;非流式额外发 /cancel);
  • 思考与回答期间,在信息下方显示转圈加载效果,回答完成后移除;
  • 错误统一展示友好文案(如「接口报错」),不暴露原始异常。

✅ 因为是同 origin 的静态资源 + REST 接口,无需任何跨域(CORS) 配置,启动即可用。


▶️ 十三、运行与调试

bash 复制代码
# 终端 1:启动 Ollama
ollama serve
ollama pull qwen3:4b-instruct      # 若已拉取可跳过

# 终端 2:启动项目(二选一)
mvn spring-boot:run
# 或在 IDEA 直接运行 Langchain4jDemoApplication

# 浏览器打开
# http://localhost:8088/chat.html

🚀 启动成功提示StartupNotifier@Component + ApplicationRunner + @Order(MAX_VALUE))在 Web 端口就绪后打印友好横幅,含聊天页地址、模型名、接口前缀、记忆窗口与 Ollama 前置条件;端口取自 WebServerApplicationContext 真实运行端口。横幅用 ANSI 转义(直接走 System.out),logback-spring.xml 负责控制台日志着色与 UTF-8。

不用前端的 curl 调试示例(单一对话入口)

bash 复制代码
# 非流式:基础单轮
curl -X POST http://localhost:8088/api/chat \
  -H 'Content-Type: application/json' \
  -d '{"mode":"basic","message":"你好"}'

# 非流式:系统人设(L4)
curl -X POST http://localhost:8088/api/chat \
  -H 'Content-Type: application/json' \
  -d '{"mode":"system","message":"用 Java 写个单例模式"}'

# 非流式:采样参数(L5,temperature 拉到 1.2 看更发散的输出)
curl -X POST http://localhost:8088/api/chat \
  -H 'Content-Type: application/json' \
  -d '{"mode":"params","message":"给我一句诗","temperature":1.2,"topP":0.95,"maxTokens":64}'

# 非流式:带记忆(L6),先问名字再用同一 sessionId 追问
curl -X POST http://localhost:8088/api/chat \
  -H 'Content-Type: application/json' \
  -d '{"mode":"memory","sessionId":"user-1","message":"我叫小明"}'
curl -X POST http://localhost:8088/api/chat \
  -H 'Content-Type: application/json' \
  -d '{"mode":"memory","sessionId":"user-1","message":"我叫什么?"}'

# 非流式:L7 全参数自定义(自定义 system 与 temperature)
curl -X POST http://localhost:8088/api/chat \
  -H 'Content-Type: application/json' \
  -d '{"mode":"custom","sessionId":"user-1","message":"你好",
       "customParams":{"system":"你是个严谨的助手","temperature":0.1}}'

# 清空记忆
curl -X POST http://localhost:8088/api/chat/clear \
  -H 'Content-Type: application/json' -d '{"sessionId":"user-1"}'

SSE 流式(逐 token)

bash 复制代码
curl -N -X POST http://localhost:8088/api/chat/stream \
  -H 'Content-Type: application/json' \
  -d '{"mode":"basic","message":"讲个冷笑话"}'
# 事件形如:event:token data:xxx   ......   event:done data:{"text":"...","tokens":42}

接口速查表

路径 方法 关键字段 说明
/api/chat POST mode,message,sessionId?,temperature?,topP?,maxTokens?,customParams?,requestId? 非流式单一入口(可中断)
/api/chat/stream POST (text/event-stream) 同上(不含 requestId SSE 流式单一入口(可中断)
/api/chat/cancel POST requestId 非流式中断信号
/api/chat/modes GET --- 模式元数据
/api/chat/custom-params GET --- L7 全参数目录
/api/chat/presets GET --- 通用参数模板预设
/api/chat/users GET --- 预设用户(sessionId 下拉)
/api/chat/clear POST sessionId 清空记忆

🚨 十四、常见问题排查

  • Connection refused: localhost/11434
    → Ollama 没启动,执行 ollama serve
  • model not found
    → 执行 ollama pull qwen3:4b-instruct,并确认 application.ymlmodel-name 与本地一致。
  • ⚠️ qwen3 回复里带 <think:6124c78e>...</think:6124c78e> 思考块
    → qwen3 系列默认带思考过程;纯聊天无影响,若做结构化解析可关闭思考(模型侧参数)或后处理剔除。
  • ⚠️ 长对话偶发 Token 超限
    → 调小 app.chat.memory-max-messages,或确认 num-ctx 足够。
  • ⚠️ 非流式点「停止」没立刻停
    → 非流式靠 POST /api/chat/cancel?requestId= 带外信号中断;前端已自动发送,后端收到后才真正断开 Ollama(与流式靠断连不同,见第八章)。
  • ⚠️ L7 自定义里 presence_penalty / frequency_penalty 不生效
    → 这两个是 OpenAI 风格参数,Ollama 不支持;后端在 CustomChatHandler 中刻意跳过(仅打日志),不会出现 500。
  • ❌ 编译报找不到 @AiService / @V
    → 确认 langchain4j-spring-boot-starter 已引入;@AiServicedev.langchain4j.service.spring@Vdev.langchain4j.service

📎 附录 A · LangChain4j 注解(声明式 API)与命令式对比

前面章节全部采用 命令式(Imperative) 写法:自己组装 ChatMessage、自己调 ChatModel.chat(...) / OllamaStreamClient.stream(...)

LangChain4j 还提供一条 声明式(Declarative / AiServices) 路线:用「接口 + 注解」描述意图,由框架在启动时生成代理实现并注册为 Spring Bean,省去大量样板代码。

本附录讲清这套注解「是什么、干什么、怎么写」,并与本项目命令式写法做对比,方便你按需取舍。

A.1 两路 API 总览

路线 核心思路 本项目的落点
声明式(AiServices) 定义接口 + 注解 → 框架生成实现 Bean 早期版本用过;现未采用
命令式(直接 API) 手动建消息 → ChatModel.chat(...) / OllamaStreamClient 当前全部采用(见第五、六章)

💡 本项目刻意选命令式的三个硬理由:

  1. 可中断流式 (第八章):@AiService 的流式返回 TokenStream 是 fire-and-forget,拿不到可取消句柄,前端 abort 只能断前端↔后端,后端↔Ollama 仍算完;命令式用 OllamaStreamClient 能真正断开 Ollama 并跳过记忆写入。
  2. 细粒度控制 :采样参数、customParams、请求前后钩子(beforeChat/afterChat)需要完全自己掌控。
  3. 单一入口 + mode 路由 :七种模式共用一个 ChatRequestDTO,由 ChatHandlerFactorymode 分发到不同策略处理器;声明式通常是「一个接口服务一个用途」,不利于这种统一路由。

A.2 声明式注解一览(含义与作用)

紫色 为注解名,蓝色 为所在包。

注解 所在包 含义与作用 常用属性
@AiService dev.langchain4j.service.spring 标记一个接口 为 AI 服务。Spring Boot starter 启动时扫描 classpath,对接口生成代理实现,自动装配上下文中的 ChatModel/StreamingChatModel/ChatMemoryProvider/ToolProvider 等,并注册为 Spring Bean ---
@SystemMessage dev.langchain4j.service 设定系统提示(角色 / 规则 / 语气)。可加在接口或方法上 value() 直接写提示词;fromResource() 读 classpath 资源文件(如 system-prompt.txt);delimiter() 多段拼接分隔符
@UserMessage dev.langchain4j.service 定义用户消息模板 。单参数可用 {``{it}},多参数配合 @V{``{变量名}} value() / fromResource()@SystemMessage
@V (Variable) dev.langchain4j.service 方法参数 绑定为 prompt 模板变量,供 {``{变量名}} 引用 value() = 变量名(与 @UserMessage/@SystemMessage 中的 {``{...}} 对应)
@MemoryId dev.langchain4j.service 标记参数为会话 / 记忆隔离 key ,框架据此找到该用户/会话对应的 ChatMemory ---
@Moderate dev.langchain4j.service 开启内容审核:每次调用除 LLM 外并行调用审核模型,过滤违规输入/输出(需提供 moderation model) ---
@UserName dev.langchain4j.service 把参数注入到 UserMessagename 字段,标识发言人 ---
@Tool dev.langchain4j.service.tool 标记 @Component 类中的方法为 AI 可调用工具(函数调用),框架自动发现并接线 value() = 给模型看的工具描述(务必写清)
@P (Parameter) dev.langchain4j.service.tool 描述工具参数(给 AI 看),让模型理解参数含义 value() = 参数说明
@Description / @Name dev.langchain4j.service.tool 给工具 / 参数 / 枚举值加自然语言说明,提升模型选择与使用准确率 ---

📌 记忆接线提醒:声明式下要让 @MemoryId 生效,上下文里需有 ChatMemoryProvider Bean(本项目的 ChatMemoryConfig 就提供了它);否则声明式服务默认无记忆。


A.3 声明式完整案例

下面用 Spring Boot 风格演示「系统人设 + 用户模板 + 会话隔离 + 工具调用 + 流式」的完整声明式写法。

java 复制代码
// 1) AI 服务接口:纯注解描述,无实现类
@AiService
public interface Assistant {

    // 系统人设从 classpath 资源读取;sessionId 做记忆隔离;用户消息来自方法参数
    @SystemMessage(fromResource = "system-prompt.txt")
    String chat(@MemoryId String sessionId, @UserMessage String userMessage);

    // 用户消息模板:@V("topic") 把参数绑定到 {{topic}}
    @SystemMessage("你是一个严谨的百科助手")
    @UserMessage("用一句话解释:{{topic}}")
    String explain(@MemoryId String sessionId, @V("topic") String topic);

    // 流式:方法返回 TokenStream,由框架做 SSE 推进
    TokenStream streamChat(@MemoryId String sessionId, @UserMessage String userMessage);
}

// 2) 工具:被框架自动发现并接线给 Assistant
@Component
public class WeatherTools {
    @Tool("查询指定城市的当前天气")
    public String getWeather(@P("城市名称,例如 北京") String city) {
        return "[" + city + "] 晴 26℃"; // 这里接真实天气 API
    }
}

// 3) 控制器:像用普通 @Service 一样注入调用
@RestController
@RequestMapping("/assistant")
public class AssistantController {
    @Autowired Assistant assistant;

    @PostMapping("/chat")
    public String chat(@RequestParam String sessionId, @RequestParam String msg) {
        return assistant.chat(sessionId, msg);   // 框架内部:拼 System+Memory+User → 调 ChatModel
    }

    @PostMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public void stream(@RequestParam String sessionId, @RequestParam String msg) {
        assistant.streamChat(sessionId, msg)
                 .onNext(token -> { /* 推送 token */ })
                 .onComplete(response -> { /* 收尾 */ })
                 .onError(Throwable::printStackTrace)
                 .start();
    }
}

要点:

  • 只写接口和注解 ,没有 new OllamaChatModel()、没有手工拼 List<ChatMessage> ------ 这些全部由 @AiService 代理完成。
  • 流式靠返回 TokenStream.start(),按 onNext/onComplete/onError 订阅。

A.4 命令式(本项目)等价写法

同一套「系统人设 + 记忆 + 用户消息 + 调用」在命令式下,正是 AbstractChatHandler.buildMessages() 做的事:

java 复制代码
// 等价于「@AiService + @SystemMessage(fromResource) + @MemoryId + @UserMessage」的命令式写法
@Override
protected List<ChatMessage> buildMessages(ChatRequestDTO req) {
    List<ChatMessage> messages = new ArrayList<>();

    // ← @SystemMessage(fromResource="system-prompt.txt")
    messages.add(SystemMessage.from(loadSystemPrompt()));

    // ← @MemoryId:从记忆缓存取该 session 的历史(多轮隔离)
    ChatMemory memory = memoryProvider.get(req.getSessionId());
    if (memory != null) messages.addAll(memory.messages());

    // ← @UserMessage:用户本轮输入
    messages.add(UserMessage.from(req.getMessage()));
    return messages;
}

// 调模型(非流式骨架,见 6.3 模板方法)
ChatResponse resp = chatModel.chat(ChatRequest.builder().messages(messages).build());
// 流式则交给 OllamaStreamClient.stream(request, cancelled, callback)(见第八章)

对照表(注解 → 命令式等价物):

声明式注解 命令式等价代码
@SystemMessage(fromResource=...) SystemMessage.from(读取 classpath 资源)
@UserMessage("...{``{x}}...") + @V("x") UserMessage.from(手动 String.format / 模板拼接)
@MemoryId memoryProvider.get(sessionId).messages() 注入历史
@Moderate 自己接 moderation model 并前后调用
@Tool / @P 自己实现 ToolSpecification + ToolExecutor(或 ToolProvider
TokenStream 流式 OllamaStreamClient.stream(...) + StreamCallback

A.5 声明式 vs 命令式 对比

维度 声明式(@AiService) 命令式(本项目)
样板代码 极少(只写接口+注解) 较多(自己拼消息/请求/回调)
上手速度 快,适合「一个接口一个用途」 稍慢,但结构清晰、易扩展
系统/用户提示 注解直接写,模板变量优雅 自己读资源 + 字符串拼接
记忆/多轮 @MemoryId 自动接线(需 Provider Bean) 自己取 ChatMemory 注入历史
采样参数调节 需额外配置/默认固定 buildRequest() 钩子自由覆盖(见第七章)
流式 TokenStream,但不可取消 OllamaStreamClient可真中断(见第八章)
多模式/策略复用 一个接口服务一个用途,难统一路由 一个入口 + mode + 工厂分发(见第五、六章)
工具 / RAG 注解自动接线,开箱即用 需自己接 ToolProvider / RetrievalAugmentor
调试/可观测 代理层黑盒,断点难打 每一步都在自己代码里,断点自由
适用场景 快速原型、单用途 AI 接口、RAG/Tools 开箱集成 统一对话平台、需中断/细控/多模式共存

A.6 小结

  • @AiService 是 LangChain4j 的高层声明式入口 :接口 + @SystemMessage/@UserMessage/@V/@MemoryId/@Tool 等注解,框架自动生成实现并接线 ChatModel/记忆/工具。
  • 省样板、上手快、RAG/Tools 开箱即用 ,但流式不可取消、细粒度控制弱、不利于统一路由
  • 本项目因为要「单一入口 + 七模式 + 可中断流式 + 全参数自定义 」,选择了命令式,把每条注解对应的能力都亲手实现了一遍(见 A.4 对照表)。
  • 两条路线底层都是 ChatModel/StreamingChatModel ,可混用:例如工具调用用 @Tool 省事、核心对话用命令式拿中断能力。

✅ 到此,项目搭建 + 单一对话入口 + 七种模式(策略/模板/工厂)+ 可中断流式直连 Ollama + 记忆/人设/裁剪/清空 + L7 全参数自定义 + 前端页面均已跑通;本附录补充了 LangChain4j 声明式注解体系及其与本项目命令式写法的完整对比。后续再进阶 RAG / Tools 等。

相关推荐
Query*1 小时前
Agent 开发之项目 AI Native 化:通过大模型与 RAG 赋予产品智能能力
java·人工智能·ai
君顾11 小时前
AI新零售线上商城系统实战:架构设计与开发全流程指南
java·开发语言·零售
吾皇斯巴达1 小时前
O_DIRECT与gcsfuse的go程序中实现内存页面对齐
开发语言·后端·golang
qq_485015211 小时前
MyBatis-Plus 3.x FieldStrategy 作用
java·数据库·mybatis
lhldsg1 小时前
课程排课系统实战指南:从数据库设计到算法调优全流程解析
java·数据库·算法·小程序
光电的一只菜鸡1 小时前
高通tuning中eis需要调什么
java·开发语言·前端
骇客野人2 小时前
SpringBoot + RocketMQ 异步解耦与最终一致性解决方案
spring boot·rocketmq·java-rocketmq
API快乐传递者2 小时前
1688 跨境电商 API 接口实战指南:从寻源到代采的全链路技术方案
java·前端·数据库
孔明click332 小时前
不想写代码,但想要集成一个登录页面?Sa-Token-Quick-Login 帮你实现!
java·sa-token·开源·springboot·登录·权限·权限认证