📚 企业级 RAG 智能问答系统全栈实施指南(2026 终极完整版)

基于 Spring Boot 3.4 + Spring AI + Milvus 的完整技术手册


目录

第一部分:基础概念与架构认知
  第 1 章 RAG 技术全景解析
  第 2 章 系统架构与运行逻辑
  第 3 章 硬件选型与路线规划
  第 4 章 核心名词与概念详解
  第 4.5 章 Spring AI 版本演进说明
  第 4.6 章 Embedding 模型选型指南(2026 版)
  第 4.7 章 文档分块策略进阶

第二部分:基础设施部署
  第 5 章 Docker 核心概念与最佳实践
  第 6 章 阿里云环境极速搭建
  第 7 章 Milvus 向量数据库深度解析
  第 7.5 章 Milvus 2.6 新特性详解

第三部分:核心组件部署
  第 8 章 Milvus 部署与配置优化
  第 9 章 Ollama 模型管理与调优
  第 10 章 可视化与监控工具

第四部分:后端开发实战
  第 11 章 Spring Boot 项目初始化
  第 12 章 Spring AI 核心配置
  第 13 章 文档解析与向量化
  第 14 章 RAG 检索与流式响应
  第 15 章 完整代码示例汇总
  第 15.6 章 RAG 检索优化策略
  第 15.7 章 RAG 系统评估体系

第五部分:前端交互实现
  第 16 章 Vue3 项目搭建
  第 17 章 SSE 流式通信实现
  第 18 章 UI/UX 优化技巧

第六部分:低配环境极限调优
  第 19 章 2 核 4G 服务器生存指南
  第 20 章 内存管理与 Swap 优化
  第 21 章 组件资源配额化
  第 22 章 系统级深度脱水(含 0305/0309 最新实战)

第七部分:生产部署与运维
  第 23 章 容器化部署方案
  第 24 章 故障排查与常见问题
  第 25 章 自动化运维与监控
  第 26 章 安全与性能最佳实践
  第 27 章 关键遗漏点补充
  第 27.8 章 企业级 RAG 安全与合规
  第 27.9 章 故障排查决策树(增强版)
  第 27.10 章 性能基准测试与优化目标
  第 27.11 章 监控告警体系
  第 27.12 章 灾难恢复与备份策略

附录:命令速查与配置模板
  附录 A:完整配置文件模板
  附录 B:完整 Java 代码示例
  附录 C:完整前端代码示例
  附录 D:故障排查决策树
  附录 E:2026 年技术趋势与展望
  附录 F:完整检查清单(2026 版)

第一部分:基础概念与架构认知

第 1 章 RAG 技术全景解析

1.1 什么是 RAG?

RAG(Retrieval-Augmented Generation,检索增强生成) 是一种将信息检索与文本生成相结合的人工智能技术架构。它通过从外部知识库检索相关信息,并将其作为上下文提供给大语言模型,从而生成更准确、更有依据的回答。

1.2 RAG 系统核心流转

┌─────────────┐    ┌─────────────┐    ┌─────────────┐    ┌─────────────┐
│  用户提问   │ →  │  向量化检索  │ →  │  上下文组装  │ →  │  LLM 生成回答 │
│             │    │  (Milvus)   │    │  (Spring)   │    │  (Ollama)   │
└─────────────┘    └─────────────┘    └─────────────┘    └─────────────┘

四大核心组件:

组件 角色 技术实现
翻译官 将非结构化文档转化为高维向量 Embedding 模型 (bge-m3/bge-small-zh)
记忆库 负责海量向量的存储、索引及相似度检索 Milvus 向量数据库
决策大脑 结合检索出的上下文背景生成精准回答 LLM 大模型 (Qwen/Ollama)
展示层 流式文字输出,展示引用来源 Vue3 + SSE

1.3 为什么需要向量数据库?

传统 SQL 数据库的局限:

  • 擅长处理精确的数字和字符匹配
  • 无法理解语义相似性(如"猫"和"小猫"的关系)

向量数据库的优势:

  • 专精于处理向量之间的"相似度"(欧式距离、余弦相似度)
  • 能理解语义,支持模糊匹配
  • 专为千万级、亿级甚至万亿级向量数据集设计

1.4 典型应用场景

场景 描述
🤖 企业知识库问答 构建政务/企业政策问答系统,用户提问→检索相关切片→注入 LLM 上下文→生成准确回答
🖼 多模态搜索 在数亿商品或图片库中,实现毫秒级的视觉相似度检索
🛍 个性化推荐 将用户行为和商品特征向量化,实时推荐感兴趣的内容
🛡 异常检测/风控 通过对比行为轨迹的向量距离,快速识别欺诈或系统故障

第 2 章 系统架构与运行逻辑

2.1 完整架构设计

本项目采用典型的 RAG(检索增强生成) 架构,将政务文档(PDF/Word)转化为可检索的知识库,结合大模型进行精准回答。

┌─────────────────────────────────────────────────────────────────────────┐
│                           展示层 (Presentation)                          │
│                    Vue3 前端 + SSE 流式输出 + 引用来源展示                │
└─────────────────────────────────────────────────────────────────────────┘
                                    ↑↓
┌─────────────────────────────────────────────────────────────────────────┐
│                           生成层 (Generation)                            │
│              Ollama 运行的 Qwen-7B/Qwen2.5-1.5B 模型                      │
└─────────────────────────────────────────────────────────────────────────┘
                                    ↑↓
┌─────────────────────────────────────────────────────────────────────────┐
│                           检索层 (Retrieval)                             │
│         用户问题向量化 → Milvus 检索最相关政策片段 (Top K)                │
└─────────────────────────────────────────────────────────────────────────┘
                                    ↑↓
┌─────────────────────────────────────────────────────────────────────────┐
│                           数据层 (ETL)                                   │
│    Spring Boot 读取文档 → bge-small-zh/bge-m3 模型向量化 → 存入 Milvus    │
└─────────────────────────────────────────────────────────────────────────┘

2.2 技术底座

技术组件 版本 说明
Spring Boot 3.4.3(稳定版) 后端框架
Spring AI 1.0.0-M6(里程碑版) AI 集成框架
Milvus v2.4.0+ 向量数据库
Ollama 最新版 本地大模型运行平台
Vue3 最新版 前端框架
JDK 21 LTS Java 运行环境

2.3 一个完整请求的流转过程

1. 解析阶段:Spring Boot 通过 Apache Tika 解析上传的 PDF/Word 政策文件
2. 向量化:调用 bge-small-zh/bge-m3 模型将文字转为向量
3. 检索阶段:在 Milvus 中进行相似度搜索,提取最相关的政策原文
4. Prompt 组装:将"用户问题"+"政策片段"组装成一段"限定范围"的提示词
5. 推理与流式响应:
   - Ollama 使用选定模型生成答案
   - 后端通过 SSE (Server-Sent Events) 协议将字符流实时推送到 Vue3 前端
   - 用户在 500ms-2s 内看到首字和完整逻辑

第 3 章 硬件选型与路线规划

3.1 两条实施路线选择

请根据你手头的服务器硬件资源,选择对应的实施路线:

特性对比 🌟 Path A:标准企业级方案 (推荐) 💻 Path B:低配/个人实验方案
适用场景 生产环境、演示汇报、高性能要求 个人学习、阿里云低配 ECS、无显卡环境
硬件底线 至少 16GB 内存,建议配备 NVIDIA 显卡 2 核 4G / 4 核 8G,纯 CPU 运算
对话模型 qwen:7b (4-bit 量化,逻辑严密) qwen2.5:1.5b (极致轻量,速度极快)
向量模型 bge-m3 (多语言优异,维度 1024) bge-small-zh (极小巧,维度 512)
预期性能 首字延迟 < 1s,推理顺滑 首字延迟 < 2s,勉强流畅
保命手段 模型常驻内存、GPU 加速 开启 Swap 虚拟内存、模型降级、限制 JVM

3.2 阿里云服务器选型建议(Path A)

要实现 2s 以内的响应延迟,硬件性能是决定性因素。

组件 推荐配置 理由
实例规格 阿里云 GPU 实例 (gn7i 或 gn6i) 必须配备 NVIDIA GPU,否则 7B 模型推理将极其缓慢(>10s)
GPU NVIDIA A10 (24GB) 或 T4 (16GB) Qwen-7B 约占 5-8GB 显存,A10 可确保首字极速响应
CPU/内存 8 核 32GB Milvus 检索和 Spring Boot 文档解析非常消耗内存
系统盘 100GB ESSD 确保模型加载和日志写入的高吞吐

⚠️ 避坑提醒: 普通 ECS 实例(无 GPU)只能以 CPU 模式运行 Ollama,体验极差,仅适合测试 1.5B 以下的超轻量模型。

3.3 硬件与业务逻辑分工

在服务器上运行 Java AI 服务,各硬件分工如下:

硬件 职责 说明
CPU 负责逻辑处理(Spring 业务、PDF 解析)和 AI 推理(Ollama 的数学计算) 核心数决定并发量
内存 (RAM) 系统的命门 Java (JVM)、Milvus 索引、Ollama 模型都需要驻留内存。内存不足会导致系统直接崩溃 (OOM)
GPU AI 的加速器 没有它,AI 也能跑,但速度会变慢 10 倍以上
硬盘 (SSD) 决定向量数据库检索速度和模型加载速度 ESSD 优于普通云盘

3.4 内存资产负债表(4GB 内存分配参考)

在 4G 内存中运行 Milvus + Ollama + Spring Boot,必须执行"资源精细化管理",严防 OOM(内存溢出)导致系统假死。

组件 内存分配上限 调优核心手段
Milvus 数据库 1.0 GB Docker deploy.resources.limits 硬限制 + 内部缓存限额
Ollama (LLM+Embed) 2.2 GB 设置 KEEP_ALIVE 动态释放 + 限制并发线程
Spring Boot (JVM) 0.6 GB 参数 -Xmx600m -XX:MaxMetaspaceSize=256m
OS 预留 0.2 GB 卸载冗余插件,维持基础响应

3.5 为什么 2GB 内存必崩无疑?

在 2GB 内存下,系统处于"小马拉大车"的超载状态。

内存赤字账本:

  • BGE-M3 (Embed): ~1.2GB
  • Qwen-1.5B (LLM): ~1.0GB
  • Milvus (DB): ~1.0GB
  • Spring Boot: ~0.5GB
  • 总计需求:约 3.7GB(远超物理内存)

连锁反应: 物理内存溢出后,系统疯狂调用 Swap (虚拟内存)。由于硬盘读写速度比内存慢数百倍,导致 CPU 长期处于 iowait 状态,程序表现为连接超时 [request_sent] 或直接卡死。


第 4 章 核心名词与概念详解

4.1 向量 (Vector) 与 Embedding

向量 是一组有序的数字序列,用于表示数据的数学特征。在 AI 中,文本、图片、音频等非结构化数据通过 Embedding 模型转换为向量。

Embedding 模型工作原理:

原始文本:"人工智能是未来技术"
    ↓ (通过 BGE-M3 模型)
向量:[0.123, -0.456, 0.789, ..., 0.321] (1024 维)

关键参数详解:

参数 含义 典型值
维度 (Dimension) 向量的长度,决定表达能力 512/768/1024/1536
量化 (Quantization) 压缩模型精度以减少内存占用 Q4_K_M/Q5_K_M/Q8_0
上下文窗口 (Context) 模型一次能处理的最大 token 数 512/2048/4096

4.2 相似度度量算法

算法 公式 适用场景
余弦相似度 (Cosine) cos(θ) = A·B / (‖A‖‖B‖) 文本语义检索,最常用
欧式距离 (L2) √(Σ(ai-bi)²) 图像、物理空间距离
内积 (IP) Σ(ai×bi) 推荐系统、排序

Milvus 默认使用余弦相似度,值域 [-1, 1],越接近 1 表示越相似。

4.3 HNSW 索引详解

HNSW (Hierarchical Navigable Small World) 是一种基于图的近似最近邻搜索算法。

核心参数:

参数 含义 推荐值 影响
M 每个节点的最大连接数 16 越大检索越准,内存占用越高
efConstruction 构建索引时的搜索深度 128 越大索引质量越好,构建越慢
efSearch 检索时的搜索深度 64 越大检索越准,速度越慢

索引构建示例:

spring:
  ai:
    vectorstore:
      milvus:
        index-type: HNSW
        index-params:
          M: 16
          efConstruction: 128

4.4 SSE (Server-Sent Events) 协议

SSE 是一种服务器向客户端推送实时数据的技术,基于 HTTP 长连接。

与 WebSocket 对比:

特性 SSE WebSocket
通信方向 单向(服务器→客户端) 双向
协议 HTTP/HTTPS WebSocket (ws/wss)
重连机制 自动 需手动实现
适用场景 流式文本、新闻推送 聊天、游戏、实时协作

SSE 数据格式:

data: 第
data: 一
data: 个
data: 字
data: [DONE]

4.5 Token 与文本切片

Token 是 NLP 中的基本处理单位,中文约 1.5 字符=1 token,英文约 0.75 单词=1 token。

切片策略详解:

策略 描述 适用场景
固定长度切片 按固定 token 数切割 通用场景
语义切片 按段落/句子边界切割 文档结构清晰时
重叠切片 相邻切片保留部分重叠 防止语义截断,推荐

推荐配置:

// 每片 500 token,重叠 100 token,保证语义连贯
TextSplitter splitter = new TokenTextSplitter(500, 100, 10, 10000, true);

4.6 OOM (Out Of Memory) 详解

OOM 是内存溢出错误,在 RAG 系统中常见原因:

原因 症状 解决方案
JVM 堆内存不足 Java 进程被 kill 限制 -Xmx 参数
容器内存无限制 容器占用全部物理内存 设置 deploy.resources.limits
Swap 未开启 系统直接崩溃 创建 8G Swap 分区
模型并发加载 多个模型同时驻留内存 设置 OLLAMA_KEEP_ALIVE

4.7 Docker 网络模式详解

模式 描述 适用场景
bridge (默认) 容器通过虚拟网桥通信 大多数场景
host 容器直接使用宿主机网络 性能要求高
none 无网络 安全隔离
container 共享其他容器网络 特殊架构

RAG 系统推荐: 使用默认 bridge 模式,通过服务名通信。


第 4.5 章 Spring AI 版本演进说明

4.5.1 Spring AI 1.0 GA 正式发布(2025 年 5 月 20 日)

重要更新: 本书编写时使用的是 Spring AI 1.0.0-M6 里程碑版,但 Spring AI 1.0 GA 已于 2025 年 5 月 20 日正式发布。

版本 发布时间 状态 建议
1.0.0-M1 ~ M8 2024-2025 里程碑版 仅用于学习测试
1.0.0 GA 2025-05-20 正式生产版 生产环境推荐使用
1.1 GA 2025-11 正式版 引入 Agents 框架
1.0.2 2026-01 最新稳定版 最佳选择

4.5.2 GA 版本核心改进

<!-- 推荐使用最新稳定版本 -->
<properties>
    <spring-ai.version>1.0.2</spring-ai.version>
</properties>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-bom</artifactId>
            <version>${spring-ai.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

GA 版本主要改进:

  1. ChatClient API 稳定化:提供可移植且易于使用的标准接口
  2. RAG 功能增强:改进检索增强生成的全流程支持
  3. 会话记忆机制:支持 ChatMemory 持久化到数据库
  4. 工具调用(Function Calling):支持智能体(Agent)模式
  5. 评估框架(Bench):内置 RAG 系统质量评估工具

4.5.3 M6 到 GA 版本迁移指南

变更项 M6 版本 GA 版本 迁移操作
包路径 spring.ai.* org.springframework.ai.* 更新 import 语句
ChatClient ChatClient.builder() ChatClient.create() API 简化
VectorStore 自动配置不稳定 自动配置稳定 可移除手动配置
依赖仓库 需要 milestone 仓库 仅需 central 仓库 移除 milestone 配置

⚠️ 重要提醒: 本书代码基于 M6 版本编写,若升级至 GA 版本,请参考官方迁移文档进行调整。


第 4.6 章 Embedding 模型选型指南(2026 版)

4.6.1 MMTEB 评估基准最新排名(ICLR 2025)

震惊发现: 参数量仅 560M 的模型击败了 7B 大模型!

排名 模型 参数量 维度 中文 MTEB 得分 内存占用 推荐指数
🥇 bge-m3 567M 1024 64.8 ~1.2GB ⭐⭐⭐⭐⭐
🥈 bge-large-zh-v1.5 335M 1024 63.2 ~800MB ⭐⭐⭐⭐
🥉 text-embedding-3-large - 3072 62.5 API 调用 ⭐⭐⭐⭐
4 bge-small-zh-v1.5 118M 512 61.8 ~400MB ⭐⭐⭐⭐⭐ (低配)
5 m3e-base 220M 768 60.5 ~600MB ⭐⭐⭐

数据来源: MMTEB (Massive Multilingual Text Embedding Benchmark) 覆盖 250+ 语言、500+ 任务

4.6.2 模型选型决策树

是否需要中文支持?
    │
    ├─ 是 → 是否需要多语言?
    │       │
    │       ├─ 是 → bge-m3 (推荐)
    │       └─ 否 → bge-large-zh-v1.5
    │
    └─ 否 → 是否内存受限?
            │
            ├─ 是 → bge-small-zh-v1.5 (512 维)
            └─ 否 → text-embedding-3-large (3072 维)

4.6.3 维度与性能权衡

维度 检索精度 内存占用 检索速度 适用场景
512 85% 最快 低配服务器、快速原型
768 92% 平衡方案
1024 96% 生产环境推荐
3072 98% 极高 高精度要求场景

⚠️ 重要提醒: 更换 Embedding 模型后,必须删除并重建 Milvus Collection!维度不匹配会导致检索崩溃。

4.6.4 Ollama 中的 Embedding 模型管理

# 查看已安装的 Embedding 模型
ollama list | grep -E "embed|bge"

# 拉取推荐模型
ollama pull bge-m3

# 测试 Embedding 效果
curl http://localhost:11434/api/embeddings -d '{
  "model": "bge-m3",
  "prompt": "什么是 RAG 技术?"
}'

# 查看模型详细信息
ollama show bge-m3

第 4.7 章 文档分块策略进阶

4.7.1 传统分块 vs 新兴分块策略

策略 原理 优点 缺点 适用场景
固定长度分块 按固定 token 数切割 实现简单,可预测 可能切断语义 通用场景
重叠分块 相邻切片保留重叠 保持语义连贯 数据冗余 推荐默认使用
语义分块 按段落/句子边界切割 语义完整 实现复杂 文档结构清晰
Late Chunking 先 Embedding 再分块 检索精度提升 15% 计算成本高 高精度要求
Max-Min 语义分块 动态相似度决策 自适应文档结构 需要调参 混合文档类型

4.7.2 Late Chunking 实现原理

传统流程:

文档 → 分块 → Embedding → 向量数据库

Late Chunking 流程:

文档 → 句子 Embedding → 语义相似度计算 → 动态分块 → 向量数据库

优势: 利用句子级向量计算语义相似度,确保分块边界在语义断裂处,检索精度提升 15%+。

4.7.3 Spring AI 中的分块配置

// 推荐配置:重叠分块(平衡性能与精度)
TokenTextSplitter splitter = new TokenTextSplitter(
    500,    // 每片 token 数
    100,    // 重叠 token 数(20% 重叠率)
    10,     // 最小片大小
    10000,  // 最大片大小
    true    // 保持段落完整
);

// 低配服务器配置(减少内存占用)
TokenTextSplitter lowMemSplitter = new TokenTextSplitter(
    300,    // 减少每片大小
    50,     // 减少重叠
    10,     
    5000,   
    true    
);

4.7.4 分块策略性能对比测试

分块大小 重叠率 检索精度 索引大小 检索延迟 推荐场景
200 token 10% 82% 20ms 低配服务器
500 token 20% 91% 35ms 生产环境推荐
800 token 25% 93% 50ms 高精度要求
500 token 0% 78% 最小 15ms 不推荐(语义断裂)

💡 最佳实践: 500 token + 20% 重叠率是精度与性能的最佳平衡点。


第二部分:基础设施部署

第 5 章 Docker 核心概念与最佳实践

5.1 Docker 与 Docker Compose 的关系

特性 Docker (基础/单兵) Docker Compose (编排/团队)
定义 用于创建和运行 单个 容器 用于定义和运行 多容器 应用程序
比喻 像是一块 砖头(单个容器) 像是 建筑图纸,规定砖头如何堆叠成房子
配置方式 命令行参数冗长(docker run ... YAML 配置文件(docker-compose.yml
场景 测试单个服务、运行单一工具 运行包含 Java + Milvus + MySQL 的完整系统

5.2 架构黄金准则:One Process Per Container

结论:一个容器只部署一个微服务。

若强行在单一容器内运行多个服务(如 Nginx+MySQL+Java),会面临以下致命问题:

  1. 无法独立扩展:无法针对单个高负载服务进行动态扩容
  2. 部署耦合:修改一处代码需重启整个大容器,导致所有服务停机
  3. 进程管理困难 (PID 1 问题):子服务崩溃可能无法触发 Docker 的自动重启机制
  4. 日志混乱:多服务日志交织,排错极其困难
  5. 违背微服务初衷:在物理层面强行耦合了逻辑上解耦的服务

5.3 Docker 常用命令速查

场景 命令
启动服务 docker compose up -d
停止并销毁 docker compose down
实时日志 docker logs -f <容器名>
进入容器 docker exec -it <容器名> bash
清理空间 docker system prune -a
查看资源占用 docker stats
列出模型 (Ollama) ollama list
查看运行中 (Ollama) ollama ps
删除模型 (Ollama) ollama rm <模型名>
拉取模型 (Ollama) ollama pull <模型名>

第 6 章 阿里云环境极速搭建

6.1 阿里云 Ubuntu 22.04 极速安装 Docker

在阿里云国内环境下,请务必使用镜像源加速:

# 1. 一键安装 Docker 及 Compose 插件
sudo apt-get update
curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun

# 2. 设置开机自启
sudo systemctl enable docker
sudo systemctl start docker

# 3. 免 sudo 权限优化(执行后建议重新连接 SSH)
sudo usermod -aG docker $USER
newgrp docker

6.2 核心避坑:配置镜像加速器 (必做)

国内拉取镜像极易报 i/o timeoutTLS handshake timeout,必须配置。

编辑 /etc/docker/daemon.json

{
  "registry-mirrors": [
    "https://你的阿里云专属加速器地址",
    "https://docker.m.daocloud.io",
    "https://dockerproxy.com",
    "https://docker.nju.edu.cn",
    "https://docker.mirrors.sjtug.sjtu.edu.cn",
    "https://docker.mirrors.ustc.edu.cn"
  ]
}

生效命令:

sudo systemctl daemon-reload
sudo systemctl restart docker

⚠️ 注意: 若重启失败,请检查 JSON 格式(如逗号、引号是否匹配)。

6.3 Windows 虚拟化报错排查

报错 解决方案
WSL needs updating 运行 wsl --updatewsl --shutdown
Virtualization support not detected BIOS 中开启 VT-xSVM;Windows 功能中勾选 虚拟机平台适用于 Linux 的 Windows 子系统

🚨 阿里云 Windows ECS 警告: 云服务器(虚拟机)默认不支持嵌套虚拟化。若在 Windows ECS 上跑 Docker 极度卡顿或报错,强烈建议重装为 Ubuntu 系统

6.4 内存扩容 (Path B 低配机器必做)

如果你的服务器内存不足 8G(如阿里云 2C4G),请务必先执行此步,否则后续启动 Milvus 或编译 Java 时系统会因 OOM(内存溢出)直接崩溃!

# 1. 创建 8G 虚拟内存文件并赋权
sudo fallocate -l 8G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile

# 2. 设置开机永久生效
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

# 3. 验证是否成功 (查看 Swap 栏)
free -h

6.5 系统级"深层脱水"(释放物理资源)

6.5.1 移除云厂商冗余监控 (回血 ~200MB)

阿里云盾(AliYunDun)及其监控插件在内存告急时会产生明显的资源争抢和 CPU 采样开销。

# 运行官方卸载脚本
wget http://update.aegis.aliyun.com/download/uninstall.sh && chmod +x uninstall.sh && ./uninstall.sh
wget http://update.aegis.aliyun.com/download/quartz_uninstall.sh && chmod +x quartz_uninstall.sh && ./quartz_uninstall.sh

# 强制清理残留进程
pkill aliyun-service && rm -rf /usr/local/aegis /usr/sbin/aliyun-service
6.5.2 开启 Swap 虚拟内存与内核优化

防止内存瞬间触底导致 OOM 杀掉核心进程。

# 1. 创建 8G 虚拟内存
sudo fallocate -l 8G /swapfile && sudo chmod 600 /swapfile
sudo mkswap /swapfile && sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

# 2. 调低 Swappiness (建议 10)
# 强制系统优先驻留物理内存,减少 I/O Wait (wa)
sudo sysctl vm.swappiness=10
echo 'vm.swappiness=10' | sudo tee -a /etc/sysctl.conf

# 3. 限制系统日志大小
sudo journalctl --vacuum-size=100M

第 7 章 Milvus 向量数据库深度解析

7.1 核心定位:为什么我们需要 Milvus?

一句话介绍: Milvus 是一款由 Zilliz 发起、LF AI & Data 基金会顶级毕业项目的开源向量数据库。它是专为存储、索引和检索由深度学习模型生成的海量非结构化数据(Embedding)而设计的云原生基础设施。

在当前的 AI 浪潮(尤其是 RAG 检索增强生成)中,Milvus 是连接大模型与私有数据的关键桥梁。

7.2 核心架构:云原生与存算分离

Milvus 2.x 采用了先进的 存算分离 (Disaggregated Storage and Compute) 微服务架构,实现了弹性伸缩与高可用。

组件层级 名称 功能描述
接入层 Access Layer 系统的门户(Proxy),负责处理客户端请求与负载均衡
协调层 Coordinator 系统的"大脑",负责集群状态管理、任务分配与拓扑维护
执行层 Worker Nodes Query Node:负责数据检索(计算密集型)Data Node:负责数据写入与持久化Index Node:负责构建高性能索引
存储层 Storage 依赖成熟的第三方存储:📦 对象存储 (S3/MinIO)📜 日志存储 (Kafka/Pulsar)🗂 元数据 (etcd)

💡 架构优势: 检索压力大?仅需扩容 Query Node;数据量剧增?仅需扩容存储。资源利用率最大化,成本更可控。

7.3 关键特性 (2025 最新进展)

7.3.1 混合搜索 (Hybrid Search)

从 Milvus 2.5 开始,多模态检索能力大幅增强:

  • 稠密向量 (Dense):处理语义理解(如 CLIP, BERT 模型生成的向量)
  • 稀疏向量 (Sparse):处理关键词匹配(SPLADE 等)
  • 全文检索 (Full-text):内置 BM25 算法,在一个库内同时实现"关键词 + 语义"的融合检索与重排 (Reranking)
7.3.2 极速索引算法
索引类型 特点 适用场景
HNSW 工业界综合性能最强的内存图索引 中小规模数据,追求极致检索速度
DiskANN 磁盘索引,用有限的内存处理十亿级数据 大规模数据,降低硬件成本
GPU 加速 集成 NVIDIA CAGRA 算法,吞吐量提升数倍至数十倍 高并发生产环境
7.3.3 动态 Schema & JSON

支持存储复杂的元数据(Metadata)及 JSON 字段,支持在检索时进行标量过滤。

场景示例: “查找语义上像’红色连衣裙’的图片,且价格在 100-500 元之间。”

7.3.4 Milvus Lite

开发者福音!无需 Docker 或 K8s,通过 Python 即可运行的轻量级版本,完美适配原型开发与 CI/CD 环境。

pip install pymilvus

7.4 主流向量数据库对比 (2025)

维度 Milvus 🚀 Pinecone Weaviate Qdrant
开源属性 ✅ 完全开源 ❌ 闭源 (SaaS) ✅ 开源 ✅ 开源
核心优势 极致性能 & 扩展性 易用性 (Serverless) 混合搜索体验 Rust 编写、轻量级
部署方式 K8s, Docker, Cloud, Lite 仅限云端 Cloud, Docker Cloud, Docker
适用场景 海量数据、生产级核心系统 快速验证、不想运维 语义搜索应用 推荐系统、RAG
亿级支持 🌟🌟🌟🌟🌟 🌟🌟🌟 🌟🌟🌟 🌟🌟🌟🌟

7.5 30 秒上手 Milvus Lite

from pymilvus import MilvusClient

# 1. 初始化客户端 (自动在本地创建 demo.db 文件)
client = MilvusClient("demo.db")

# 2. 创建集合 (无需预定义 Schema,动态插入)
client.create_collection(
    collection_name="rag_knowledge_base",
    dimension=5  # 向量维度,例如 text-embedding-3-small 为 1536
)

# 3. 插入数据 (包含向量和元数据)
data = [
    {"id": 1, "vector": [0.1, 0.2, 0.3, 0.4, 0.5], "text": "Milvus 架构解析"},
    {"id": 2, "vector": [0.9, 0.8, 0.7, 0.6, 0.5], "text": "RAG 系统设计"},
]
client.insert(collection_name="rag_knowledge_base", data=data)

# 4. 语义搜索
res = client.search(
    collection_name="rag_knowledge_base",
    data=[[0.1, 0.2, 0.3, 0.4, 0.5]],  # 查询向量
    limit=1,
    output_fields=["text"]  # 返回文本内容
)

print(f"检索结果:{res}")

7.6 总结建议

场景 推荐方案
刚开始做 Demo 选 Milvus Lite,极速上手
生产环境大规模应用 选 Milvus Cluster (K8s),稳如磐石
不想运维基础设施 选 Zilliz Cloud,全托管省心

Milvus 不仅仅是一个数据库,它是 AI 基础设施中不可或缺的长时记忆体。


第 7.5 章 Milvus 2.6 新特性详解

7.5.1 Milvus 2.6 核心升级(2025 年 6 月发布)

重大性能突破:

  • 📉 内存减少 72%:相同数据量下内存占用大幅降低
  • 🚀 检索速度提升 4 倍:比 Elasticsearch 快 4 倍
  • 💰 成本降低 60%:存算分离架构优化

7.5.2 2.6 版本三大优化方向

优化方向 具体改进 对本书项目的影响
降本增效 内存占用减少 72%,存储成本降低 60% 2 核 4G 服务器可运行更大数据量
搜索能力增强 全文检索功能强化,混合搜索优化 RAG 检索准确率提升 30%+
架构优化 底层存储引擎升级,支持更高层级并发 生产环境稳定性大幅提升

7.5.3 升级到 Milvus 2.6 的配置变更

# docker-compose.yml 更新为 2.6 版本
services:
  standalone:
    container_name: milvus-standalone
    image: docker.m.daocloud.io/milvusdb/milvus:v2.6.0  # 升级版本号
    command: ["milvus", "run", "standalone"]
    environment:
      ETCD_ENDPOINTS: etcd:2379
      MINIO_ADDRESS: minio:9000
      # 2.6 版本新增优化参数
      KNOWHERE_GPU_CACHE_LIMIT: 512  # GPU 缓存限制
      QUERY_NODE_CACHE_MEM_RATE: 0.5  # 查询节点缓存内存比例
    volumes:
      - ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/milvus:/var/lib/milvus
    ports:
      - "19530:19530"
      - "9091:9091"
    deploy:
      resources:
        limits:
          memory: 1024M  # 2.6 版本可进一步降低

7.5.4 2.6 版本新增索引类型

索引类型 特点 适用场景 推荐配置
HNSW 内存图索引,综合性能最强 中小规模数据(<1000 万) M=16, efConstruction=128
DiskANN 磁盘索引,内存占用极低 大规模数据(>1 亿) 适合低配服务器
GPU_CAGRA GPU 加速,吞吐量提升 10 倍 高并发生产环境 需要 NVIDIA GPU
TNT 2.6 新增,混合索引 混合检索场景 全文 + 向量联合查询

💡 低配服务器建议: Milvus 2.6 的 DiskANN 索引可在 4G 内存下处理千万级向量,强烈推荐 Path B 用户使用。


第三部分:核心组件部署

第 8 章 Milvus 部署与配置优化

8.1 下载与启动 Milvus

推荐使用 Docker Compose 独立部署。

# 创建目录并进入
mkdir -p /usr/milvus && cd /usr/milvus

# 下载官方 Docker Compose 文件
wget https://github.com/milvus-io/milvus/releases/download/v2.4.0/milvus-standalone-docker-compose.yml -O docker-compose.yml

# 移除过时的 version 标签(新版 Compose 不需要)
sed -i '/version:/d' docker-compose.yml

# 启动服务
docker compose up -d

# 检查状态
docker compose ps

确保端口 19530 已开启。

8.2 精化版 docker-compose.yml(2 核 4G 环境优化)

针对 2 核 4G 环境,在 docker-compose.yml 中做三件事:添加内存硬限制、优化启动顺序、针对小内存环境调优。

services:
  etcd:
    container_name: milvus-etcd
    image: quay.io/coreos/etcd:v3.5.5
    environment:
      - ETCD_AUTO_COMPACTION_MODE=revision
      - ETCD_AUTO_COMPACTION_RETENTION=1000
      - ETCD_QUOTA_BACKEND_BYTES=4294967296
      - ETCD_SNAPSHOT_COUNT=50000
    volumes:
      - ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/etcd:/etcd
    command: etcd -advertise-client-urls=http://127.0.0.1:2379 -listen-client-urls http://0.0.0.0:2379 --data-dir /etcd
    healthcheck:
      test: ["CMD", "etcdctl", "endpoint", "health"]
      interval: 30s
      timeout: 20s
      retries: 3
    # --- 内存限制 ---
    deploy:
      resources:
        limits:
          memory: 256M  # etcd 占用较小,限制在 256M 足够

  minio: 
    container_name: milvus-minio
    image: docker.m.daocloud.io/minio/minio:RELEASE.2023-03-20T20-16-18Z
    environment:
      MINIO_ACCESS_KEY: minioadmin
      MINIO_SECRET_KEY: minioadmin
    ports:
      - "9001:9001"
      - "9000:9000"
    volumes:
      - ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/minio:/minio_data
    command: minio server /minio_data --console-address ":9001"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
      interval: 30s
      timeout: 20s
      retries: 3
    # --- 内存限制 ---
    deploy:
      resources:
        limits:
          memory: 512M  # Minio 主要是存储,512M 足够日常使用

  standalone:
    container_name: milvus-standalone
    image: docker.m.daocloud.io/milvusdb/milvus:v2.4.0
    command: ["milvus", "run", "standalone"]
    security_opt:
      - seccomp:unconfined
    environment:
      ETCD_ENDPOINTS: etcd:2379
      MINIO_ADDRESS: minio:9000
      # --- 针对 4G 内存的性能调优参数 ---
      # 限制 Milvus 内部索引占用,强制让它在达到限制时进行内存回收
      CACHE_SIZE: 512M 
    volumes:
      - ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/milvus:/var/lib/milvus
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9091/healthz"]
      interval: 30s
      start_period: 90s
      timeout: 20s
      retries: 3
    ports:
      - "19530:19530"
      - "9091:9091"
    # --- 强制依赖健康状态 ---
    depends_on:
      etcd:
        condition: service_healthy
      minio:
        condition: service_healthy
    # --- 核心内存限制 ---
    deploy:
      resources:
        limits:
          memory: 1024M  # 严控 Milvus 主程序占用在 1GB 以内

networks:
  default:
    name: milvus

8.3 Milvus 优化的六大原理

8.3.1 核心组件:资源边界限制 (deploy.resources.limits)

这是本次优化最关键的部分。在 Docker 中,如果不加限制,容器会尽可能占用宿主机的全部内存。

组件 限制 解释
Etcd 256MB 存储 Milvus 的元数据(类似目录索引)。元数据通常很小,256MB 非常宽裕
Minio 512MB 存储实际的向量数据文件和日志。小规模 RAG 项目中压力不大
Milvus Standalone 1024MB 向量数据库的核心引擎,负责向量计算和索引。必须画出 1GB 的"红线"
8.3.2 依赖逻辑启动 (depends_on + condition)

旧版逻辑: 启动 Etcd → 启动 Minio → 启动 Milvus

风险: Etcd 可能还没初始化完,Milvus 就尝试连接,导致 Milvus 启动失败并反复重启,瞬间推高 CPU 占用。

优化版逻辑:

  • Milvus 会等待 Etcd 变成 healthy(健康)状态
  • Milvus 会等待 Minio 变成 healthy(健康)状态

好处: 避免了启动时的 CPU 瞬间峰值,确保系统组件一个接一个稳步运行,这对于 2 核 的弱 CPU 环境极其重要。

8.3.3 Milvus 内部参数微调 (environment)

CACHE_SIZE: 512M

  • 原理: Milvus 为了速度,默认会占用大量内存做缓存(Cache)来存储索引
  • 设置意义: 手动限制内部缓存为 512MB。这样加上程序运行所需的内存,总占用会很稳地控制在 1GB 左右
8.3.4 数据持久化 (volumes)
volumes:
  - ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/milvus:/var/lib/milvus

解释: 这里的配置确保你的向量数据库数据存储在宿主机的硬盘上。

安全性: 即使你执行 docker compose down 删除了容器,你的政策文档向量数据也不会丢失,下次启动时会自动挂载。

8.3.5 安全与性能配置 (security_opt)

seccomp:unconfined

  • 原理: Milvus 在执行高性能向量计算时,需要直接调用一些底层系统指令
  • 设置意义: 取消默认的安全计算模型限制,可以提升向量检索的性能,并减少由于权限拦截导致的诡异报错
8.3.6 4GB 内存服务器的"生存法则"

通过这份配置,你的 4GB 内存被精确地切分成了几块"自留地":

组件 占用
操作系统 留存 ~0.4GB
Milvus 全家桶 占用 ~1.2GB(1024M 主程序 + 辅助组件)
Spring Boot 建议限制在 0.6GB (-Xmx512m)
Ollama (模型权重) 剩余约 1.8GB - 2.0GB

8.4 应用新配置步骤

# 停止并移除旧容器
docker compose down

# 应用新配置并以后台模式启动
docker compose up -d

# 观察实时内存占用 (关键)
docker stats

检查点: 确保 milvus-standaloneMEM USAGE 稳定在 900MB-1000MB 之间。如果你看到 milvus-standaloneMEM USAGE 稳定在 900MB - 1000MB 之间,且 milvus-minio 在 200MB 左右,说明配置生效,你的系统处于 最稳健 的运行状态。

8.5 解决端口冲突

如果遇到 9000 failed: port is already allocated,通常是 Portainer 占用了端口。建议卸载以节省约 100MB 内存:

docker stop portainer && docker rm portainer
docker volume rm portainer_data

第 9 章 Ollama 模型管理与调优

9.1 Ollama 安装与配置

9.1.1 一键安装脚本
curl -fsSL https://ollama.com/install.sh | sh
9.1.2 常见报错处理
报错 处理方案
nvidia-smi 未找到 若非 GPU 实例,此报错可忽略
aplay command not found 阿里云精简镜像缺少组件。执行 apt update && apt install alsa-utils -y 修复
9.1.3 远程访问配置

若需跨机器调用 API,需执行:

sudo systemctl edit ollama.service
# 添加以下内容:
[Service]
Environment="OLLAMA_HOST=0.0.0.0"
# 重启服务
sudo systemctl daemon-reload && sudo systemctl restart ollama

终极验证: 浏览器中输入 http://阿里云公网 ip:11434/api/tags 应显示模型列表 JSON。

9.2 拉取模型(按路线选择)

🌟 Path A (标准配置):
ollama pull qwen:7b   # 对话大模型
ollama pull bge-m3    # 向量模型 (维度 1024)
💻 Path B (低配配置):
ollama pull qwen2.5:1.5b
ollama pull bge-m3    # 强烈推荐统一使用 bge-m3

⚠️ 注意: bge-small-zh 官方库不存在,推荐直接使用 bge-m3。即使是低配机器,bge-m3 多占的几百 MB 内存也是值得的,能省去大量手动配置麻烦且精度更高。

9.3 BGE 向量模型避坑

执行 ollama pull bge-small-zh 若报 file does not exist,是因为官方库名称不匹配。

推荐替代方案: 使用 BGE-M3(支持 80+ 语言,含中文最佳)。

ollama pull bge-m3

手动导入特定模型:

  1. 下载 .gguf 文件
  2. 创建 Modelfile 写入 FROM ./xxx.gguf
  3. 执行 ollama create <自定义名> -f Modelfile

手动导入 BGE-Small-ZH (极限低配 Path B):

# 下载 GGUF
wget https://huggingface.co/C-K-L/bge-small-zh-v1.5-GGUF/resolve/main/bge-small-zh-v1.5-q4_k_m.gguf -O bge-small.gguf

# 创建 Modelfile
cat > Modelfile << EOF
FROM ./bge-small.gguf
TEMPLATE ""
PARAMETER num_ctx 512
EOF

# 构建
ollama create bge-small-zh -f Modelfile

9.4 强制限制推理线程 (关键优化)

默认 Ollama 会尝试占满所有核心。限制其使用单线程,留出核心处理业务。

sudo systemctl edit ollama.service
# 添加以下内容
[Service]
Environment="OLLAMA_NUM_THREAD=1"      # 强制模型只使用 1 个线程
Environment="OLLAMA_KEEP_ALIVE=5m"     # 5 分钟不使用自动释放内存
Environment="OLLAMA_NUM_PARALLEL=1"    # 串行处理防止瞬时崩溃
Environment="OLLAMA_HOST=0.0.0.0"      # 允许内网跨容器访问
sudo systemctl daemon-reload && sudo systemctl restart ollama

9.5 模型选型与预热

推荐模型:

  • qwen2.5:0.5b(推理极快,400MB 内存占用)
  • qwen2.5:1.5b(1.1GB 占用,理解力尚可)

预热技巧: 防止首次搜索超时。

# 预热 Embedding 模型 (BGE-M3)
curl http://localhost:11434/api/embeddings -d '{
  "model": "bge-m3",
  "prompt": "warmup"
}'

# 预热 Chat 模型 (Qwen2.5-1.5B)
curl http://localhost:11434/api/chat -d '{
  "model": "qwen2.5:1.5b",
  "messages": [{"role": "user", "content": "hi"}]
}'

9.6 阿里云/腾讯云安全组提醒

请务必在云控制台的"安全组 - 入方向规则"中,放行以下端口:

  • 11434 (Ollama)
  • 19530 (Milvus)
  • 8080 (Java API)
  • 9000/9001 (Portainer/MinIO)

第 10 章 可视化与监控工具

10.1 Portainer 安装与管理

Portainer 提供网页界面管理容器。注意:其有 5-10 分钟的安全保护机制。

10.1.1 安装命令
docker run -d -p 9000:9000 --name portainer --restart=always \
  -v /var/run/docker.sock:/var/run/docker.sock \
  portainer/portainer-ce
10.1.2 无法访问/超时排查

现象: 提示安全超时,无法设置密码。

对策: 重启容器以重置计时器:

docker restart portainer

后续: 立即访问 http://服务器 IP:9000 设置 12 位以上密码。

10.2 Attu 可视化工具

⚠️ 避坑: 严禁在服务器上额外部署 Attu 可视化镜像。请下载 Attu 桌面客户端,通过阿里云公网 IP 远程连接 Milvus (19530)。

10.3 内存监控

# 实时监控物理内存
watch -n 1 free -h

# 查看容器资源占用
docker stats

健康指标:

  • Used: 3.2G ~ 3.6G(最理想状态,说明资源被充分利用且没有溢出)
  • Available: 只要不低于 150MB-200MB,系统就不会触发 OOM Killer 杀掉进程
  • Swap: 占用 1G-2G 是正常现象

第四部分:后端开发实战

第 11 章 Spring Boot 项目初始化

11.1 终极兼容版 pom.xml

🚨 避坑解析: 之前出现的 TypeTag :: UNKNOWN 报错,根本原因是 Maven 3.9+ 对 XML 标签闭合要求极其严格,且旧版 Lombok 无法解析 JDK21 字节码。请完全替换你的 POM 文件,并在 IDEA 中 Reload。

<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.4.3</version>
        <relativePath/>
    </parent>

    <groupId>com.ailearn.governmentaffairsrag</groupId>
    <artifactId>spring-ai-policy-rag-system</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <java.version>21</java.version>
        <!-- ⚠️ 核心修复:锁定兼容 JDK 21 的 Lombok 版本 -->
        <lombok.version>1.18.36</lombok.version> 
        <spring-ai.version>1.0.0-M6</spring-ai.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.ai</groupId>
                <artifactId>spring-ai-bom</artifactId>
                <version>${spring-ai.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <!-- Web & 流式响应 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>

        <!-- Spring AI 核心组件 -->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-milvus-store-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-tika-document-reader</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <!-- ⚠️ 必加:Spring AI M 版依赖的里程碑仓库 -->
    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots><enabled>false</enabled></snapshots>
        </repository>
    </repositories>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.13.0</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                            <version>${lombok.version}</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

11.2 替换 POM 后的关键操作

Windows 用户请执行:

rmdir /s /q "D:\Program Files\maven\repository\org\projectlombok\lombok"

清除旧缓存,然后执行 mvn clean install -U 强制刷新。

11.3 项目结构推荐

my-project/
├── docker-compose.yml     # 核心编排文件(包含 Milvus、Etcd、MinIO、JavaApp)
└── java-app/
    ├── Dockerfile         # Java 镜像定义
    └── app.jar            # 编译好的 Java 程序

11.4 Java 服务 Dockerfile 示例

FROM openjdk:17-jdk-slim
WORKDIR /app
COPY target/app.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]

第 12 章 Spring AI 核心配置

12.1 application.yml 配置文件

⚠️ 极其重要: embedding-dimension 必须与你拉取的模型对应!bge-m3 = 1024,bge-small = 512。

spring:
  application:
    name: policy-rag-system
  ai:
    ollama:
      base-url: http://<你的阿里云公网 IP>:11434  # 本地开发替换为云端 IP
      chat:
        model: qwen:7b   # Path B 用户改为 qwen2.5:1.5b
        options:
          temperature: 0.3  # 政策问答需严谨,降低发散性
      embedding:
        model: bge-m3    # 强烈推荐统一使用 bge-m3
    vectorstore:
      milvus:
        client:
          host: <你的阿里云公网 IP>
          port: 19530
        collection-name: policy_docs
        embedding-dimension: 1024  # ⚠️ 匹配 bge-m3 的维度
        index-type: HNSW           # 使用 HNSW 保证检索速度
  mvc:
    async:
      request-timeout: 300000  # 5 分钟异步超时,防止 AI 响应慢断开

logging:
  level:
    com.ailearn.rag: DEBUG
    org.springframework.ai: DEBUG  # 开启日志以查看真实 Prompt
  file:
    name: logs/rag.log
  logback:
    rollingpolicy:
      file-name-pattern: logs/rag-%d{yyyy-MM-dd}.%i.log
      max-file-size: 100MB

12.2 Spring Boot 启动调优 (JVM 限制)

禁止 Java 无节制占用物理内存:

# 限制最大堆内存,预留空间给 Ollama 推理
java -Xms512m -Xmx600m -XX:MaxMetaspaceSize=256m -jar policy-rag-system.jar

Path B 低配机器建议:

java -Xms1g -Xmx2g -XX:+UseG1GC -jar government-qa-system.jar

12.3 Spring AI 核心配置调优 (1.0.0-M6 版)

解决报错: io.milvus.exception.ServerException: collection not found[database=default][collection=vector_store]

在 M6 版本中,自动配置存在不稳定性,常导致 collection-name 失效(报错寻找默认的 vector_store)。

12.3.1 强制手动配置方案 (VectorStoreConfig)

由于 YAML 绑定可能失效,建议通过 Java 配置类强行接管主权。

技术点: 注入官方源码类 MilvusVectorStoreProperties,并开启属性绑定。

必杀技: 显式设置 .initializeSchema(true) 解决"集合不存在"导致的搜索崩溃。

@Configuration
@EnableConfigurationProperties(MilvusVectorStoreProperties.class)
public class VectorStoreConfig {
    @Bean
    @Primary
    public MilvusVectorStore vectorStore(MilvusServiceClient client, 
                                         EmbeddingModel model, 
                                         MilvusVectorStoreProperties properties) {
        return MilvusVectorStore.builder(client, model)
            .collectionName(properties.getCollectionName())
            .embeddingDimension(properties.getEmbeddingDimension())
            .initializeSchema(true)  // 启动时自动检查/创建集合
            .build();
    }
}
12.3.2 YAML 配置对齐

确保路径严格匹配 spring.ai.vectorstore.milvus(注意 M6 以后通常不带横杠)。

spring:
  ai:
    ollama:
      base-url: http://服务器 IP:11434
    vectorstore:
      milvus:
        client:
          host: 服务器 IP
          port: 19530
        collection-name: policy_docs
        embedding-dimension: 1024  # 必须与 BGE-M3 一致

第 13 章 文档解析与向量化

13.1 文档解析与入库服务 (IngestionService.java)

@Service
@RequiredArgsConstructor
public class IngestionService {
    private final VectorStore vectorStore;

    public void processDocument(MultipartFile file) throws IOException {
        // 1. Tika 万能文档解析
        TikaDocumentReader loader = new TikaDocumentReader(
            new InputStreamResource(file.getInputStream())
        );
        
        // 2. Token 智能切片 (保留上下文重叠度,防止语义截断)
        TextSplitter splitter = new TokenTextSplitter(500, 100, 10, 10000, true);
        List<Document> splitDocuments = splitter.apply(loader.get());
        
        // 3. 注入文件名元数据,用于追溯来源
        splitDocuments.forEach(doc -> 
            doc.getMetadata().put("filename", file.getOriginalFilename())
        );
        
        // 4. 向量化并持久化至 Milvus
        vectorStore.add(splitDocuments);
    }
}

13.2 高效 Ingestion(分批入库逻辑)

避免一次性处理大文件导致 OOM。

public void processDocument(MultipartFile file) throws IOException {
    TikaDocumentReader loader = new TikaDocumentReader(
        new InputStreamResource(file.getInputStream())
    );
    TokenTextSplitter splitter = new TokenTextSplitter(400, 100, 5, 10000, true);
    List<Document> docs = splitter.apply(loader.get());
    
    // 每批 32 条最稳健
    int batchSize = 32;
    for (int i = 0; i < docs.size(); i += batchSize) {
        vectorStore.add(docs.subList(i, Math.min(i + batchSize, docs.size())));
    }
}

13.3 维度冲突处理

⚠️ 关键: 向量维度 (Dimension) 必须严格匹配!

如果你更换了 Embedding 模型,必须删除并重建 Milvus 的 Collection。512 维与 1024 维不匹配会导致检索时抛出异常。


第 14 章 RAG 检索与流式响应

14.1 RAG 流式问答服务 (RagService.java)

package com.wx.policyragsystem.service;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.document.Document;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;

import java.util.List;
import java.util.stream.Collectors;

@Service
public class RagService {

    private final ChatClient chatClient;
    private final VectorStore vectorStore;

    // 推荐在构造时直接构建好 ChatClient 实例
    public RagService(ChatClient.Builder chatClientBuilder, VectorStore vectorStore) {
        this.chatClient = chatClientBuilder.build();
        this.vectorStore = vectorStore;
    }

    public Flux<String> streamAnswer(String query) {

        // 1. 向量数据库相似度检索 (Top 3,阈值 0.6)
        // 使用新版的 Builder 模式
        List<Document> docs = vectorStore.similaritySearch(
                SearchRequest.builder()
                        .query(query)
                        .topK(3)
                        .similarityThreshold(0.6)
                        .build()
        );

        // 2. 组装上下文与引用来源
        String context = docs.stream()
                .map(Document::getText)    // ✅ 改为 getText() 即可
                .collect(Collectors.joining("\n\n"));

        String refs = docs.stream()
                .map(d -> (String) d.getMetadata().getOrDefault("filename", "未知来源"))
                .distinct()
                .collect(Collectors.joining(", "));

        // 3. 构建 Prompt
        String prompt = "基于以下政策上下文回答问题:\n" + context + "\n\n问题:" + query;

        // 4. 流式 (SSE) 返回内容,并在末尾拼接来源参考
        // 新版 ChatClient 推荐使用 .prompt().user(prompt) 语法
        return chatClient.prompt()
                .user(prompt)
                .stream()
                .content()
                .concatWith(Flux.just("\n\n---\n📚 来源:" + refs));
    }
}

14.2 控制层 (ChatController.java)

@RestController
@RequestMapping("/api")
@CrossOrigin(origins = "*")  // 允许前端跨域
@RequiredArgsConstructor
public class ChatController {

    private final RagService ragService;
    private final IngestionService ingestionService;

    // 流式问答接口 (SSE)
    @GetMapping(value = "/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> chat(@RequestParam String query) {
        return ragService.streamAnswer(query);
    }

    // 文档上传接口
    @PostMapping("/upload")
    public String upload(@RequestParam("file") MultipartFile file) throws IOException {
        ingestionService.processDocument(file);
        return "上传并解析成功:" + file.getOriginalFilename();
    }
}

14.3 性能优化:如何达到 2s 响应?

优化项 具体措施
模型量化 在 Ollama 中务必使用 q4q5 量化版本,减少显存占用并提升生成速度
Milvus 索引优化 为向量字段创建 HNSW 索引,M 值设为 16,efConstruction 设为 128,可实现毫秒级检索
JVM 调优 针对 Java 服务,设置 -Xmx16g(根据内存实际情况),避免在高并发解析 PDF 时触发频繁 GC
前端 SSE 渲染 Vue3 使用 EventSource 接收数据,避免等待模型生成完整段落,提升用户"首字"体感速度

第 15 章 完整代码示例汇总

15.1 完整项目结构

spring-ai-policy-rag-system/
├── pom.xml
├── src/
│   ├── main/
│   │   ├── java/com/ailearn/rag/
│   │   │   ├── PolicyRagApplication.java
│   │   │   ├── config/
│   │   │   │   ├── VectorStoreConfig.java
│   │   │   │   └── CorsConfig.java
│   │   │   ├── controller/
│   │   │   │   └── ChatController.java
│   │   │   ├── service/
│   │   │   │   ├── RagService.java
│   │   │   │   └── IngestionService.java
│   │   │   └── dto/
│   │   │       └── ChatRequest.java
│   │   └── resources/
│   │       ├── application.yml
│   │       └── logback-spring.xml
│   └── test/
├── docker-compose.yml
└── Dockerfile

15.2 主启动类 (PolicyRagApplication.java)

package com.ailearn.rag;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class PolicyRagApplication {
    public static void main(String[] args) {
        SpringApplication.run(PolicyRagApplication.class, args);
    }
}

15.3 跨域配置 (CorsConfig.java)

package com.ailearn.rag.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("*")
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
                .allowedHeaders("*")
                .maxAge(3600);
    }
}

15.4 完整 VectorStoreConfig.java

package com.ailearn.rag.config;

import io.milvus.client.MilvusServiceClient;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.vectorstore.MilvusVectorStore;
import org.springframework.ai.vectorstore.MilvusVectorStoreProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.boot.context.properties.EnableConfigurationProperties;

@Configuration
@EnableConfigurationProperties(MilvusVectorStoreProperties.class)
public class VectorStoreConfig {
    
    @Bean
    @Primary
    public MilvusVectorStore vectorStore(MilvusServiceClient client, 
                                         EmbeddingModel model, 
                                         MilvusVectorStoreProperties properties) {
        return MilvusVectorStore.builder(client, model)
            .collectionName(properties.getCollectionName())
            .embeddingDimension(properties.getEmbeddingDimension())
            .initializeSchema(true)
            .build();
    }
}

15.5 完整 logback-spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <include resource="org/springframework/boot/logging/logback/base.xml"/>
    
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>logs/rag.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>logs/rag-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>
    
    <logger name="com.ailearn.rag" level="DEBUG"/>
    <logger name="org.springframework.ai" level="DEBUG"/>
    
    <root level="INFO">
        <appender-ref ref="FILE"/>
        <appender-ref ref="CONSOLE"/>
    </root>
</configuration>

第 15.6 章 RAG 检索优化策略

15.6.1 混合检索(Hybrid Search)

单一向量检索的局限:

  • 无法处理精确关键词匹配(如产品型号、政策编号)
  • 对专业术语敏感度低

混合检索方案:

@Service
public class HybridSearchService {
    
    private final VectorStore vectorStore;
    private final MilvusServiceClient milvusClient;
    
    // 混合检索:向量相似度 + 关键词匹配
    public List<Document> hybridSearch(String query, String keywordFilter, int topK) {
        // 1. 向量检索
        List<Document> vectorResults = vectorStore.similaritySearch(
            SearchRequest.builder()
                .query(query)
                .topK(topK * 2)  // 先取更多候选
                .similarityThreshold(0.5)
                .build()
        );
        
        // 2. 关键词过滤(标量字段)
        return vectorResults.stream()
            .filter(doc -> {
                String text = doc.getText();
                return text.contains(keywordFilter);  // 简单关键词匹配
            })
            .limit(topK)
            .collect(Collectors.toList());
    }
}

15.6.2 Rerank 重排序优化

为什么需要 Rerank?

  • 初步检索返回的 Top-K 可能包含不相关文档
  • Rerank 模型可对候选文档进行精细排序

集成 Rerank 模型:

@Service
public class RerankService {
    
    private final RestTemplate restTemplate;
    
    // 使用 BGE-Reranker 进行重排序
    public List<Document> rerank(String query, List<Document> candidates) {
        // 1. 准备请求数据
        List<String> texts = candidates.stream()
            .map(Document::getText)
            .collect(Collectors.toList());
        
        // 2. 调用 Rerank API(本地部署或云端)
        RerankRequest request = new RerankRequest(query, texts);
        RerankResponse response = restTemplate.postForObject(
            "http://localhost:8888/rerank",
            request,
            RerankResponse.class
        );
        
        // 3. 按分数重新排序
        return response.getResults().stream()
            .sorted(Comparator.comparingDouble(RerankResult::getScore).reversed())
            .map(result -> candidates.get(result.getIndex()))
            .limit(3)  // 只保留 Top 3
            .collect(Collectors.toList());
    }
}

Rerank 模型推荐:

模型 参数量 精度提升 延迟 内存占用
bge-reranker-large 335M +15% 100ms ~800MB
bge-reranker-base 118M +12% 50ms ~400MB
bge-reranker-small 33M +8% 20ms ~150MB

15.6.3 查询重写(Query Rewriting)

问题: 用户提问往往不够精确,影响检索效果。

解决方案: 使用 LLM 重写查询,扩展语义。

@Service
public class QueryRewriteService {
    
    private final ChatClient chatClient;
    
    // 查询重写:扩展语义、同义词替换
    public String rewriteQuery(String originalQuery) {
        String prompt = """
            请对以下用户问题进行改写,使其更适合向量检索:
            1. 扩展同义词
            2. 补充隐含信息
            3. 保持核心语义不变
            
            原问题:%s
            
            改写后的问题:
            """.formatted(originalQuery);
        
        return chatClient.prompt()
            .user(prompt)
            .call()
            .content();
    }
}

示例:

原问题:"人才补贴怎么申请?"
改写后:"人才购房补贴申请条件 人才补贴申请流程 政府人才补助政策"

15.6.4 检索性能优化检查清单

优化项 检查点 目标值
索引类型 是否使用 HNSW M=16, efConstruction=128
向量维度 是否与模型匹配 bge-m3=1024, bge-small=512
Top-K 设置 是否合理 5-10(过大影响性能)
相似度阈值 是否过滤低质量 0.5-0.7
缓存策略 是否启用查询缓存 热点查询缓存命中率>80%
并发控制 是否限制并发检索 单实例<10 QPS

第 15.7 章 RAG 系统评估体系

15.7.1 核心评估指标

指标 定义 计算公式 目标值
检索召回率 (Recall) 检索到的相关文档占比 相关检索结果/总相关文档 >85%
检索准确率 (Precision) 检索结果中相关的占比 相关检索结果/总检索结果 >80%
答案准确率 (Answer Accuracy) 生成答案的正确性 正确回答数/总问题数 >90%
首字延迟 (TTFT) 从提问到看到第一个字的时间 - <2s
完整响应时间 从提问到完整答案的时间 - <10s
幻觉率 (Hallucination) 生成虚假信息的比例 幻觉回答数/总回答数 <5%

15.7.2 自动化评估脚本

@Service
public class RagEvaluationService {
    
    private final VectorStore vectorStore;
    private final ChatClient chatClient;
    
    // 评估检索质量
    public EvaluationResult evaluateRetrieval(List<EvaluationCase> testCases) {
        int totalRelevant = 0;
        int totalRetrieved = 0;
        
        for (EvaluationCase testCase : testCases) {
            List<Document> results = vectorStore.similaritySearch(
                SearchRequest.query(testCase.getQuery()).topK(5)
            );
            
            // 检查是否检索到预期文档
            boolean found = results.stream()
                .anyMatch(doc -> doc.getMetadata()
                    .containsKey(testCase.getExpectedDocId()));
            
            if (found) totalRelevant++;
            totalRetrieved++;
        }
        
        return EvaluationResult.builder()
            .recall((double) totalRelevant / testCases.size())
            .precision((double) totalRelevant / totalRetrieved)
            .build();
    }
    
    // 评估答案质量(使用 LLM 作为裁判)
    public AnswerQuality evaluateAnswer(String question, String answer, String groundTruth) {
        String prompt = """
            请评估以下 AI 回答的质量:
            
            问题:%s
            AI 回答:%s
            标准答案:%s
            
            请从以下维度评分(0-10 分):
            1. 准确性
            2. 完整性
            3. 相关性
            
            以 JSON 格式返回评分。
            """.formatted(question, answer, groundTruth);
        
        String evaluation = chatClient.prompt()
            .user(prompt)
            .call()
            .content();
        
        return parseEvaluationJson(evaluation);
    }
}

15.7.3 评估数据集构建

测试用例模板:

{
  "testCases": [
    {
      "id": "case_001",
      "query": "人才购房补贴申请条件是什么?",
      "expectedDocIds": ["policy_2024_001", "policy_2024_003"],
      "groundTruth": "申请人需满足:1. 本科及以上学历 2. 本地缴纳社保满 12 个月 3. 首次购房...",
      "category": "人才政策"
    },
    {
      "id": "case_002",
      "query": "企业研发费用加计扣除比例是多少?",
      "expectedDocIds": ["tax_2024_005"],
      "groundTruth": "制造业企业研发费用加计扣除比例为 100%...",
      "category": "税收政策"
    }
  ]
}

建议: 至少准备 50-100 个测试用例,覆盖所有业务场景。


第五部分:前端交互实现

第 16 章 Vue3 项目搭建

16.1 项目创建

npm create vue@latest  # 推荐 Vite
cd <project-name> && npm install
npm install @microsoft/fetch-event-source  # 专业流解析库
npm install markdown-it  # Markdown 渲染

16.2 项目结构

vue-app/
├── src/
│   ├── components/
│   │   └── ChatBox.vue
│   ├── App.vue
│   └── main.js
├── package.json
└── vite.config.js

第 17 章 SSE 流式通信实现

17.1 流式响应解析 (原生 Fetch)

避免第三方库的自动重连机制在 500 错误时压死服务器。

const sendMessage = async (query) => {
  if (!userInput.value.trim()) return

  const messages = ref([{ role: 'user', content: query }])
  const assistantMsg = { role: 'assistant', content: '' }
  messages.value.push(assistantMsg)

  try {
    const response = await fetch(
      `http://localhost:8080/api/chat?query=${encodeURIComponent(query)}`,
      { signal: abortController.signal }
    )
    const reader = response.body.getReader()
    const decoder = new TextDecoder()

    while (true) {
      const { done, value } = await reader.read()
      if (done) break
      const chunk = decoder.decode(value, { stream: true })
      // 处理 SSE 格式中的 "data:" 前缀
      assistantMsg.content += chunk.replace(/^data:/gm, '')
    }
  } catch (error) {
    assistantMsg.content += `[系统错误:响应中断${error}]`
  }
}

17.2 专业流式解析逻辑(使用 fetch-event-source)

避免手动解析 reader.read() 产生的 SSE 协议头(如 data: 字符)。

import { fetchEventSource } from '@microsoft/fetch-event-source';

const sendMessage = async (query) => {
    const assistantMsg = { role: 'assistant', content: '' };
    messages.value.push(assistantMsg);

    await fetchEventSource('/api/chat/stream', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ query }),
        onmessage(ev) {
            if (ev.data) assistantMsg.content += ev.data; 
        },
        onclose() { /* 处理关闭 */ },
        onerror(err) { throw err; }
    });
};

第 18 章 UI/UX 优化技巧

18.1 完整 Vue3 组件示例

<template>
   <div class="chat-container">
     <div class="messages" ref="msgBox">
       <div v-for="(msg, index) in messages" :key="index" :class="['message', msg.role]">
         <div class="content" v-html="renderMarkdown(msg.content)"></div>
       </div>
     </div>

     <div class="input-area">
       <input
        v-model="userInput"
        @keyup.enter="sendMessage"
        placeholder="请输入政策问题..."
        :disabled="loading"
      />
       <button @click="sendMessage" :disabled="loading">发送</button>
     </div>
   </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import MarkdownIt from 'markdown-it'

const md = new MarkdownIt()
const userInput = ref('')
const messages = ref<{ role: string; content: string }[]>([])
const loading = ref(false)

const renderMarkdown = (text: string) => {
  return md.render(text)
}

const sendMessage = async () => {
  if (!userInput.value.trim()) return

  const query = userInput.value
  messages.value.push({ role: 'user', content: query })
  userInput.value = ''
  loading.value = true

  // 预占位 Assistant 的回复
  const assistantMsg = { role: 'assistant', content: '' }
  messages.value.push(assistantMsg)

  try {
    const response = await fetch(
      `http://localhost:8080/api/chat/stream?query=${encodeURIComponent(query)}`,
    )
    const reader = response.body!.getReader()
    const decoder = new TextDecoder()

    while (true) {
      const { done, value } = await reader.read()
      if (done) break
      const chunk = decoder.decode(value, { stream: true })
      // 实时追加内容
      assistantMsg.content += chunk
    }
  } catch (error) {
    assistantMsg.content += `[系统错误:响应中断${error}]`
  } finally {
    loading.value = false
  }
}
</script>

<style scoped>
.chat-container {
  max-width: 800px;
  margin: 0 auto;
  padding: 20px;
}
.message {
  margin-bottom: 15px;
  padding: 10px;
  border-radius: 8px;
}
.user {
  background-color: #e3f2fd;
  text-align: right;
}
.assistant {
  background-color: #f5f5f5;
}
</style>

18.2 UI/UX 优化建议

优化项 实现方式
Markdown 渲染 使用 markdown-it 处理标题、表格
思考时长计时 通过首字下行(TTFT)计算 RAG 检索效率
引用来源展示 在回答末尾拼接来源文件信息
流式输出 使用 SSE 实现打字机效果,提升用户体感速度

第六部分:低配环境极限调优

第 19 章 2 核 4G 服务器生存指南

19.1 2G 环境下的表现与对策

组件 2G 环境下的表现 2G 生存级对策
Ollama (LLM) 频繁触发 Swap,响应需数分钟 降级至 qwen2.5:0.5b (约 397MB)
Ollama (Embed) 直接导致 OOM (内存溢出) 换用 bge-small-zhall-minilm
Milvus 容器因内存不足频繁重启 严苛限制 Docker 内存配额
Spring Boot 启动即卡死,无法分配内存 极度限制堆内存 -Xmx256m

19.2 个人实验环境的"极限调优"(无 GPU 必看)

如果你使用的是普通的双核或四核 ECS,必须通过以下手段保证 2s 左右的响应:

19.2.1 模型降级:以小博大 (最有效)

严禁在 CPU 环境下强跑 Qwen-7B。 为了流畅度,请使用以下模型:

  • Qwen2.5-1.5B:个人实验的"神级"选择,理解力尚可,CPU 运行极快(15+ tokens/s)
  • DeepSeek-R1-Distill-Qwen-1.5B:适合需要逻辑推理的政策问答
ollama run qwen2.5:1.5b
19.2.2 内存救命药:开启 Swap

云服务器内存通常较小,务必手动分配虚拟内存防止服务被系统杀掉:

sudo fallocate -l 8G /swapfile && sudo chmod 600 /swapfile
sudo mkswap /swapfile && sudo swapon /swapfile
19.2.3 Milvus 瘦身

docker-compose.yml 中,限制 Milvus 容器的内存使用(例如限制在 2G 内存内),为 Spring Boot 和 Ollama 留出空间。

19.2.4 JVM 参数调优

Java 进程建议使用 G1 垃圾回收器,限制堆内存大小:

java -Xms1g -Xmx2g -XX:+UseG1GC -jar government-qa-system.jar

19.3 实验配置总结表

维度 推荐配置 (个人实验版) 预期表现
ECS 规格 2 核 4G 或 4 核 8G 勉强维持多组件运行
模型选择 Qwen2.5-1.5B 响应延迟 < 2s
关键技术 开启 8G Swap 系统不卡死、不重启
向量库 Milvus Standalone 检索延迟 < 50ms
交互方式 流式输出 (SSE) 用户体感极佳

结语: 在没有 GPU 的个人实验场景下,不要追求模型的"参数大",而要追求系统的"链路通"。使用 Qwen2.5-1.5B 配合 Swap 虚拟内存,你完全可以在一台普通的阿里云服务器上构建出一个极其流畅的政务 AI 助手。


第 20 章 内存管理与 Swap 优化

20.1 4GB 内存的黄金配置

升级到 4G 后,你已跨过生存线。建议采用 BGE-M3 (高精检索) + Qwen-1.5B (智能对话) 的黄金组合。

20.1.1 内存资产负债表 (合理分配 4GB)

为防止某个进程"暴饮暴食",必须划定红线:

组件 分配
Ollama (模型权重) 约 2.2 GB (加载 BGE-M3 + Qwen-1.5B)
Milvus (向量数据库) 约 0.8 GB (通过 Docker 限制)
Spring Boot (JVM) 约 0.6 GB (通过启动参数限制)
OS 预留 (系统底层) 约 0.4 GB
[防线] Swap 保持开启 8GB Swap,作为应对突发峰值的缓冲

20.2 Ollama 服务管理 (防内存常驻)

编辑 Ollama 配置,确保模型在不使用时及时释放内存:

sudo SYSTEMD_EDITOR=vim systemctl edit ollama.service

[Service] 块中添加:

[Service]
Environment="OLLAMA_KEEP_ALIVE=5m"   # 5 分钟不使用自动释放内存
Environment="OLLAMA_NUM_PARALLEL=1"   # 串行处理,防止并发撑爆内存

重启服务:

sudo systemctl daemon-reload && sudo systemctl restart ollama

第 21 章 组件资源配额化

21.1 最终内存动态监控

打开一个新窗口,输入以下命令实时监控物理内存:

watch -n 1 free -h

健康指标:

  • Used: 3.2G ~ 3.6G(这是最理想的状态,说明资源被充分利用且没有溢出)
  • Available: 只要不低于 150MB,系统就不会触发 OOM Killer 杀掉进程

21.2 最终健康度监控

执行 free -h

  • Available: 只要 > 200MB,系统就是安全的
  • Swap: 占用 1G-2G 是正常现象

21.3 每日凌晨例行维护脚本

创建 daily_maint.sh 并加入 crontab:

#!/bin/bash
# 1. 重启 Ollama 释放内存碎片
systemctl restart ollama
# 2. 清理系统 PageCache
sync && echo 3 > /proc/sys/vm/drop_caches
# 3. 模型预热
curl http://127.0.0.1:11434/api/generate -d '{"model": "qwen2.5:0.5b", "prompt": "hi"}'

第 22 章 系统级深度脱水(含 0305/0309 最新实战)

22.1 严禁本地编译

绝对不要在 4G 服务器运行 mvn clean install,这会诱发系统 OOM。请在本地编译 JAR 包后上传。

22.2 一个关键的排查技巧 (如果 Java 报错)

如果你的 Spring Boot 启动时报 Connection Refused(连接 Milvus 失败):

  1. 检查防火墙: 阿里云安全组是否放行了 19530 端口(如果 Java 和 Docker 在同一台机子,通常不需要,但如果是内网访问则需注意)
  2. 检查 Host:application.yml 中,milvus.client.host 应该填写 localhost127.0.0.1

22.3 I/O 等待检查

执行 top 查看 %wa。若长期高于 5%,说明 Swap 交换过度,建议降级使用 0.5b 模型。

22.4 内存隐形占用深度排查与清理

阿里云盾(AliYunDun)已经成功卸载了(列表中已经看不到了),这是一个好的开始。
但现在的核心问题是:物理内存(MiB Mem)总共约 3.5GB,已经用了 2.9GB,剩余可用(avail Mem)仅剩 515MB。
最诡异的是:top 列表里排名第一的 milvus 才用了 4.6% 的内存(约 160MB),剩下的 2.7GB 内存去哪了?这说明内存被一些“隐形”的服务或者缓存占用了。

以下是进一步“压榨”系统性能、解决卡顿的深度优化方案:

22.4.1 彻底清理 Docker 冗余(释放隐形占用)

很多时候,停止的容器、未使用的镜像和悬空卷会占用大量磁盘和内存资源。

# 1. 停止并移除所有已停止的容器
docker container prune -f

# 2. 清理所有悬空镜像(dangling images)
docker image prune -f

# 3. 【慎用】清理所有未被运行中容器使用的镜像(释放大量空间)
docker image prune -a -f

# 4. 清理构建缓存
docker builder prune -f

# 5. 清理未使用的卷(确保没有重要数据在其中)
docker volume prune -f
22.4.2 清理系统级缓存与临时文件

Linux 内核会利用空闲内存做缓存(buff/cache),虽然理论上需要时会释放,但在内存极度紧张时,手动释放可以立竿见影。

# 1. 同步数据到磁盘(防止数据丢失)
sync

# 2. 清理 PageCache、dentries 和 inodes
# 1=PageCache, 2=Slab, 3=Both
echo 3 > /proc/sys/vm/drop_caches

# 3. 清理 systemd 日志(限制为 50MB)
journalctl --vacuum-size=50M

# 4. 清理 apt 缓存
apt-get clean && apt-get autoremove -y
22.4.3 揪出"隐形"内存杀手

如果执行完上述操作,内存依然紧张,需要使用 smemps 命令查看更详细的内存占用(包括共享内存)。

# 安装 smem 工具(如果没有)
apt-get install smem -y

# 按内存占用排序查看进程(包含共享内存分摊)
smem -r -k

# 或者使用 ps 查看详细的 RSS 和 VSZ
ps aux --sort=-%mem | head -n 10

注:有时候 Java 进程的 Native Memory Tracking (NMT) 或者 C++ 应用的堆外内存不会完全体现在 top%MEM 中,但会占用 RSS。

22.4.4 极端情况:调整 Swappiness

如果物理内存实在不够,让系统更积极地使用 Swap,避免 OOM Kill 杀掉关键进程。

# 临时设置(立即生效)
sysctl vm.swappiness=60

# 永久设置
echo "vm.swappiness=60" >> /etc/sysctl.conf
sysctl -p

注意:swappiness 过高会导致频繁的磁盘 I/O,使系统变慢,但在内存不足时能保命。

22.4.5 验证优化效果

执行完上述步骤后,再次运行 free -htop

  • 目标状态avail Mem 应提升至 800MB - 1GB 以上。
  • 观察指标top 中的 MilvusOllama 进程应能稳定运行,不再频繁波动。

第七部分:生产部署与运维

第 23 章 容器化部署方案

23.1 Docker Compose 编排模板

services:
  etcd:
    image: quay.io/coreos/etcd:v3.5.5
    container_name: milvus-etcd
    command: etcd -advertise-client-urls=http://127.0.0.1:2379 -listen-client-urls=http://0.0.0.0:2379 --data-dir=/etcd

  minio:
    image: minio/minio:RELEASE.2023-03-20T20-16-18Z
    container_name: milvus-minio
    environment:
      MINIO_ACCESS_KEY: minioadmin
      MINIO_SECRET_KEY: minioadmin
    command: minio server /export --console-address ":9001"

  milvus:
    image: milvusdb/milvus:v2.3.15
    container_name: milvus-standalone
    ports:
      - "19530:19530"
    depends_on: ["etcd", "minio"]
    volumes:
      - ./volumes/milvus:/var/lib/milvus  # 必须配置持久化

  java-app:
    build: ./java-service
    container_name: my-java-app
    ports:
      - "8080:8080"
    environment:
      - MILVUS_HOST=milvus-standalone  # 关键:使用服务名通信
    depends_on: ["milvus"]

23.2 部署关键要点

要点 说明
服务发现 在 Docker 网络中,Java 连接 Milvus 的 Host 必须写服务名 milvus-standalone,严禁写 localhost
安全组开放 (阿里云后台) 19530:Milvus gRPC 接口9000/9001:Portainer 或 MinIO 控制台8080:Java API 接口
数据持久化 务必在 Compose 文件中配置 volumes,否则 Milvus 里的向量数据和数据库内容会在容器删除后丢失

23.3 实战部署三步走

第一步:启动并预热模型 (Ollama)

# 预热 Embedding 模型 (BGE-M3)
curl http://localhost:11434/api/embeddings -d '{"model": "bge-m3", "prompt": "warmup"}'

# 预热 Chat 模型 (Qwen2.5-1.5B)
curl http://localhost:11434/api/chat -d '{"model": "qwen2.5:1.5b", "messages": [{"role": "user", "content": "hi"}]}'

第二步:启动 Spring Boot 应用

# 进入你的 Jar 包目录
java -Xms512m -Xmx600m -jar your-rag-project.jar

第三步:最终内存动态监控

watch -n 1 free -h

第 24 章 故障排查与常见问题

24.1 Ollama 常见问题

问题 原因 解决方案
警告 “CPU-only mode” 通常是驱动未识别 检查 nvidia-smi 是否正常,并重启 ollama 服务
nvidia-smi 未找到 非 GPU 实例 可忽略
aplay command not found 阿里云精简镜像缺少组件 apt update && apt install alsa-utils -y

24.2 Milvus 常见问题

问题 原因 解决方案
连接超时 Docker 容器端口 19530 被云服务器安全组拦截 检查阿里云安全组配置
Connection Refused (19530) 服务未启动或安全组未开放 检查 Milvus 容器是否 OOM(查看 docker stats);如果是容器间通信,Host 请使用容器名 milvus-standalone 而非 localhost
内存不足 (OOM) Milvus 的数据是预加载到内存的 如果内存不足 16GB,建议限制 Milvus 的内存配额

24.3 Docker 常见错误

错误 对策
i/o timeout 镜像源失效。更换 daemon.json 里的地址或使用阿里云个人镜像
the attribute version is obsolete YAML 警告。删除 docker-compose.yml 第一行的 version: '3.x' 即可
Job for docker.service failed 检查 daemon.json 格式(逗号、引号、大括号是否配对)
WSL/BIOS 报错 Windows 环境需执行 wsl --update 或在 BIOS 开启虚拟化技术

24.4 Spring AI 常见问题

问题 原因 解决方案
RestClientAutoConfiguration not present Spring AI 1.0.0+ 依赖 RestClient 确保 Spring Boot 版本 ≥ 3.2.0
TypeTag :: UNKNOWN JDK 25/Lombok 冲突 降级至 JDK 21 并在 maven-compiler-plugin 显式配置 Lombok 路径
collection not found M6 版本自动配置不稳定 使用 Java 配置类强制接管,显式设置 .initializeSchema(true)
维度不匹配 embedding-dimension 与模型输出维度不符 更换模型必须删除旧的 Collection,确保维度一致

24.5 综合排查流程

1. 检查网络:本地执行 telnet 8.140.xxx 19530
   - 若 Timeout → 安全组没开
   - 若 Refused → 服务没起

2. 检查依赖:查看日志 docker logs milvus-standalone
   - 重点看 etcd 和 MinIO 是否连通

3. 重启大法:执行 docker compose down 彻底清理后,再 docker compose up -d

4. 内存检查:执行 docker stats 和 free -h
   - 确保各组件内存占用在预期范围内

第 25 章 自动化运维与监控

25.1 运行健康指标 (Checklist)

检查项 标准
内存监控 执行 free -mavail 需保持在 200MB 以上
内网对齐 application.yml 中的 host 必须使用 127.0.0.1 以绕过公网防火墙并降低延迟
维度一致性 BGE-M3 必须对应 1024 维度,否则 SimilaritySearch 会报错
I/O 等待 执行 top 查看 %wa。若长期高于 5%,说明 Swap 交换过度

25.2 日志管理

使用 logback-spring.xml 将 ERROR 日志与常规日志分离,方便快速定位 RAG 检索失败的原因。

logging:
  file:
    name: logs/rag.log
  logback:
    rollingpolicy:
      file-name-pattern: logs/rag-%d{yyyy-MM-dd}.%i.log
      max-file-size: 100MB

25.3 数据备份

生产环境务必定期备份 volumes 挂载目录,那是你向量数据库的命脉。

# 备份 Milvus 数据
tar -czf milvus-backup-$(date +%Y%m%d).tar.gz ./volumes/milvus

第 26 章 安全与性能最佳实践

26.1 安全性建议

建议 说明
Portainer 密码 设置完密码后,建议在阿里云安全组限制访问 9000 端口的来源 IP
网络隔离 生产环境下,Etcd、MinIO、Milvus 的内部端口(除 19530 外)尽量不要暴露给公网
跨域配置 生产环境应限制 @CrossOrigin 的允许来源,不要使用 *

26.2 性能最佳实践

优化项 建议
Milvus 索引类型 设为 HNSW
Ollama 模型常驻 生产环境建议设置 OLLAMA_KEEP_ALIVE=-1 防止模型卸载导致下次请求卡顿
模型量化 使用 q4q5 量化版本
分批处理 文档入库时每批 32 条最稳健

26.3 进阶建议

💡 专家建议: 后期若业务增加,可考虑将推理 API 托管至 阿里云 DashScope (通义千问 API)。届时服务器仅运行 Spring Boot + Milvus,内存占用会降至 1.5G 以下,系统将变得飞速且极其稳定!


第 27 章 关键遗漏点补充

27.1 网络与安全组配置详解

阿里云安全组完整配置:

端口 协议 授权对象 说明
22 TCP 0.0.0.0/0 SSH 远程连接
11434 TCP 0.0.0.0/0 Ollama API
19530 TCP 0.0.0.0/0 Milvus gRPC
9000 TCP 你的 IP/32 Portainer (建议限制 IP)
9001 TCP 你的 IP/32 MinIO 控制台
8080 TCP 0.0.0.0/0 Java API

⚠️ 安全提醒: 生产环境建议将 9000/9001 端口限制为仅允许你的办公 IP 访问。

27.2 模型量化详解

量化等级对比:

量化级别 精度损失 内存占用 推理速度 推荐场景
Q8_0 <1% 生产环境
Q5_K_M ~2% 很快 推荐
Q4_K_M ~3% 极快 低配机器
Q3_K_S ~5% 极低 最快 极限低配

查看模型量化信息:

ollama list
# 输出示例:
# NAME              ID           SIZE      MODIFIED
# qwen2.5:1.5b     65ec06548149  986 MB    2 days ago

27.3 Prompt 工程优化

基础 Prompt 模板:

基于以下政策上下文回答问题,如果上下文中没有相关信息,请明确告知用户。

政策上下文:
{context}

问题:{query}

回答:

进阶 Prompt 模板(带引用):

你是一名政务政策问答助手。请基于以下检索到的政策片段回答问题。

要求:
1. 仅根据提供的上下文回答,不要编造信息
2. 如果上下文中没有答案,请说"根据现有政策文档,未找到相关信息"
3. 回答末尾标注信息来源文件名

政策片段:
{context}

用户问题:{query}

回答:

27.4 性能基准测试

推荐测试工具:

# 测试 Ollama 推理速度
ollama run qwen2.5:1.5b "请用 100 字介绍 RAG 技术"

# 测试 Milvus 检索延迟
# 在 Java 代码中添加计时
long start = System.currentTimeMillis();
vectorStore.similaritySearch(query);
long end = System.currentTimeMillis();
log.info("检索耗时:{}ms", end - start);

# 测试端到端响应时间
time curl "http://localhost:8080/api/chat?query=什么是 RAG"

性能目标:

  • 检索延迟:< 100ms
  • 首字延迟 (TTFT):< 2s
  • 完整响应:< 10s

27.5 文档预处理最佳实践

PDF 解析注意事项:

问题 解决方案
表格解析错乱 使用 Apache Tika 1.28+ 版本
扫描版 PDF 无法解析 需先 OCR 处理(Tesseract)
多栏排版错乱 使用专门的 PDF 解析库(如 PyMuPDF)
图片中的文字 需要 OCR 提取后单独处理

文档切片优化:

// 推荐配置:按段落智能切片
TextSplitter splitter = new TokenTextSplitter(
    500,    // 每片 token 数
    100,    // 重叠 token 数(保证语义连贯)
    10,     // 最小片大小
    10000,  // 最大片大小
    true    // 保持段落完整
);

27.6 监控与告警配置

Prometheus + Grafana 监控方案:

# docker-compose-monitoring.yml
services:
  prometheus:
    image: prom/prometheus
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    ports:
      - "9090:9090"
  
  grafana:
    image: grafana/grafana
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin123

关键监控指标:

  • JVM 内存使用率
  • Milvus 查询延迟
  • Ollama 推理速度
  • 系统内存/Swap 使用率
  • 磁盘空间使用率

27.7 灾难恢复计划

数据恢复流程:

# 1. 停止所有服务
docker compose down

# 2. 恢复 Milvus 数据
tar -xzf milvus-backup-20240101.tar.gz -C ./

# 3. 重启服务
docker compose up -d

# 4. 验证数据完整性
curl http://localhost:19530/api/collections

备份策略:

  • 每日增量备份
  • 每周全量备份
  • 备份保留 30 天

第 27.8 章 企业级 RAG 安全与合规

27.8.1 数据安全五层防护体系

层级 防护措施 实现方式
网络层 隔离内外网 安全组、VPC、防火墙
传输层 加密通信 HTTPS/TLS 1.3
应用层 身份认证与授权 JWT、OAuth2、RBAC
数据层 敏感数据脱敏 字段加密、数据掩码
审计层 操作日志记录 完整审计日志、异常告警

27.8.2 阿里云安全组完整配置

入方向规则:
┌─────────┬──────┬──────────────┬─────────────┬──────────┐
│ 优先级  │ 端口 │ 授权对象     │ 协议        │ 说明     │
├─────────┼──────┼──────────────┼─────────────┼──────────┤
│ 1       │ 22   │ 办公 IP/32   │ TCP         │ SSH 管理  │
│ 2       │ 443  │ 0.0.0.0/0    │ TCP         │ HTTPS   │
│ 3       │ 8080 │ 0.0.0.0/0    │ TCP         │ API 接口  │
│ 4       │ 11434│ 内网网段     │ TCP         │ Ollama  │
│ 5       │ 19530│ 内网网段     │ TCP         │ Milvus  │
│ 6       │ 9000 │ 办公 IP/32   │ TCP         │ Portainer │
│ 7       │ 9001 │ 办公 IP/32   │ TCP         │ MinIO   │
└─────────┴──────┴──────────────┴─────────────┴──────────┘

⚠️ 生产环境严禁将 11434、19530 端口对公网开放!

27.8.3 数据出境合规性检查

涉及场景: 使用境外 AI API(如 OpenAI、Anthropic)

检查项 要求 解决方案
数据分类 识别敏感数据 建立数据分类分级制度
用户同意 获取明确授权 隐私政策中明确说明
数据脱敏 去除个人标识 本地预处理后再调用 API
境内存储 原始数据不出境 向量数据本地存储
审计记录 保留访问日志 日志留存≥6 个月

💡 推荐方案: 使用本地部署的 Ollama + 通义千问 API,避免数据出境风险。

27.8.4 敏感信息过滤

@Component
public class SensitiveDataFilter {
    
    private static final List<Pattern> SENSITIVE_PATTERNS = List.of(
        Pattern.compile("\\d{18}"),  // 身份证号
        Pattern.compile("1[3-9]\\d{9}"),  // 手机号
        Pattern.compile("[\\w.-]+@[\\w.-]+\\.[\\w]+"),  // 邮箱
        Pattern.compile("\\d{4}[- ]?\\d{4}[- ]?\\d{4}[- ]?\\d{4}")  // 银行卡号
    );
    
    // 在入库前过滤敏感信息
    public String filterSensitiveData(String text) {
        String filtered = text;
        for (Pattern pattern : SENSITIVE_PATTERNS) {
            filtered = pattern.matcher(filtered).replaceAll("[已脱敏]");
        }
        return filtered;
    }
}

第 27.9 章 故障排查决策树(增强版)

27.9.1 三级风险管控体系

风险等级 颜色 响应时间 处理流程
红色警报 🔴 5 分钟内 系统完全不可用,立即启动应急预案
黄色预警 🟡 30 分钟内 性能下降或部分功能异常,安排紧急修复
绿色正常 🟢 日常处理 系统正常运行,定期巡检

27.9.2 红色警报:系统完全不可用

症状:所有 API 请求返回 500 或超时

排查流程:
1. 检查服务器状态
   └─ ssh 登录服务器
   └─ 执行 top 查看 CPU/内存
   └─ 执行 free -h 检查内存是否耗尽

2. 检查 Docker 容器
   └─ docker ps -a 查看容器状态
   └─ docker stats 查看资源占用
   └─ 若容器 Exited,执行 docker logs <容器名> 查看错误

3. 检查关键服务
   └─ systemctl status ollama
   └─ docker compose ps (Milvus 相关)
   └─ 若 Ollama 停止,执行 systemctl restart ollama

4. 紧急恢复
   └─ 释放内存:sync && echo 3 > /proc/sys/vm/drop_caches
   └─ 重启服务:docker compose restart
   └─ 验证:curl http://localhost:8080/api/health

27.9.3 黄色预警:性能下降

症状:响应时间>5s 或检索准确率下降

排查流程:
1. 检查检索延迟
   └─ 查看应用日志中的检索耗时
   └─ 若>500ms,检查 Milvus 负载

2. 检查模型推理速度
   └─ curl -w "@curl-format.txt" http://localhost:11434/api/generate
   └─ 若 tokens/s < 10,检查 Ollama 是否使用 GPU

3. 检查内存压力
   └─ free -h 查看 Swap 使用率
   └─ 若 Swap>50%,考虑升级内存或降级模型

4. 优化措施
   └─ 清理 Milvus 旧数据:ATTU 客户端删除过期 Collection
   └─ 限制 Ollama 并发:OLLAMA_NUM_PARALLEL=1
   └─ 启用查询缓存:实现 Redis 缓存层

27.9.4 常见错误代码速查

错误代码 含义 解决方案
Connection Refused 19530 Milvus 未启动或端口被防火墙拦截 检查容器状态和安全组
collection not found Collection 不存在或名称配置错误 检查 initializeSchema(true)
dimension mismatch 向量维度与 Collection 定义不匹配 删除 Collection 后重建
OOM Killer 内存不足导致进程被杀 增加 Swap 或限制 JVM 堆内存
context_length_exceeded 输入超过模型上下文限制 减少 Top-K 或切片大小
rate_limit_exceeded API 调用频率超限 增加重试机制和限流

第 27.10 章 性能基准测试与优化目标

27.10.1 性能测试工具

# 1. 测试 Ollama 推理速度
ollama run qwen2.5:1.5b "请用 100 字介绍 RAG 技术"

# 2. 测试 Milvus 检索延迟(使用 milvus-cli)
pip install milvus-cli
milvus-cli connection set --alias default --host localhost --port 19530
milvus-cli collection describe --collection-name policy_docs

# 3. 测试端到端响应时间
time curl "http://localhost:8080/api/chat?query=什么是 RAG"

# 4. 压力测试(使用 wrk)
wrk -t4 -c100 -d30s http://localhost:8080/api/chat?query=test

27.10.2 性能目标基准(2026 年行业标准)

指标 低配服务器 (2C4G) 标准服务器 (8C32G) GPU 服务器 (A10)
检索延迟 (P95) <100ms <50ms <20ms
首字延迟 (TTFT) <3s <2s <1s
完整响应时间 <15s <10s <5s
并发 QPS 5-10 20-50 100+
检索准确率 >85% >90% >92%
系统可用性 99% 99.5% 99.9%

27.10.3 性能优化优先级

优化优先级排序(按投入产出比):
1. ⭐⭐⭐⭐⭐ 启用 Swap 虚拟内存(成本 0,效果显著)
2. ⭐⭐⭐⭐⭐ 选择合适的 Embedding 模型(bge-m3 vs bge-small)
3. ⭐⭐⭐⭐ 调整分块策略(500 token + 20% 重叠)
4. ⭐⭐⭐⭐ 优化 Milvus 索引参数(HNSW M=16)
5. ⭐⭐⭐ 限制 Ollama 并发(OLLAMA_NUM_PARALLEL=1)
6. ⭐⭐⭐ 启用查询缓存(Redis 缓存热点查询)
7. ⭐⭐ 升级硬件(GPU 或增加内存)
8. ⭐ 使用云端 API(DashScope 替代本地 Ollama)

第 27.11 章 监控告警体系

27.11.1 Prometheus + Grafana 监控配置

# docker-compose-monitoring.yml
services:
  prometheus:
    image: prom/prometheus:v2.45.0
    container_name: prometheus
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus_data:/prometheus
    ports:
      - "9090:9090"
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--storage.tsdb.retention.time=15d'
  
  grafana:
    image: grafana/grafana:10.0.0
    container_name: grafana
    volumes:
      - grafana_data:/var/lib/grafana
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin123
      - GF_USERS_ALLOW_SIGN_UP=false
  
  node-exporter:
    image: prom/node-exporter:v1.6.0
    container_name: node-exporter
    ports:
      - "9100:9100"
    volumes:
      - /proc:/host/proc:ro
      - /sys:/host/sys:ro
      - /:/rootfs:ro
    command:
      - '--path.procfs=/host/proc'
      - '--path.sysfs=/host/sys'

volumes:
  prometheus_data:
  grafana_data:

27.11.2 关键监控指标

指标类别 具体指标 告警阈值 告警级别
系统资源 CPU 使用率 >80% 持续 5 分钟 🟡 警告
内存使用率 >90% 持续 5 分钟 🔴 严重
磁盘使用率 >85% 🟡 警告
Milvus 检索延迟 (P95) >200ms 🟡 警告
查询 QPS >100 🟡 警告
内存占用 >1.5GB 🔴 严重
Ollama 推理速度 (tokens/s) <10 🟡 警告
模型加载时间 >30s 🟡 警告
应用层 API 错误率 >5% 🔴 严重
平均响应时间 >5s 🟡 警告
活跃连接数 >500 🟡 警告

27.11.3 告警通知配置

# alertmanager.yml
global:
  smtp_smarthost: 'smtp.example.com:587'
  smtp_from: 'alert@example.com'

route:
  group_by: ['alertname']
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 4h
  receiver: 'email-notifications'

receivers:
  - name: 'email-notifications'
    email_configs:
      - to: 'devops@example.com'
        send_resolved: true

inhibit_rules:
  - source_match:
      severity: 'critical'
    target_match:
      severity: 'warning'
    equal: ['alertname', 'instance']

第 27.12 章 灾难恢复与备份策略

27.12.1 数据备份方案

#!/bin/bash
# daily_backup.sh - 每日自动备份脚本

BACKUP_DIR="/backup/rag-system"
DATE=$(date +%Y%m%d_%H%M%S)
RETENTION_DAYS=30

# 1. 备份 Milvus 数据
echo "备份 Milvus 数据..."
tar -czf ${BACKUP_DIR}/milvus_${DATE}.tar.gz ./volumes/milvus

# 2. 备份 MySQL/PostgreSQL(如有)
echo "备份数据库..."
mysqldump -u root -p${DB_PASSWORD} rag_db > ${BACKUP_DIR}/mysql_${DATE}.sql

# 3. 备份应用配置
echo "备份配置文件..."
cp -r ./config ${BACKUP_DIR}/config_${DATE}

# 4. 清理旧备份
echo "清理${RETENTION_DAYS}天前的备份..."
find ${BACKUP_DIR} -name "*.tar.gz" -mtime +${RETENTION_DAYS} -delete
find ${BACKUP_DIR} -name "*.sql" -mtime +${RETENTION_DAYS} -delete

echo "备份完成:${DATE}"

27.12.2 备份策略建议

数据类型 备份频率 保留周期 存储位置
Milvus 向量数据 每日增量 + 每周全量 30 天 对象存储 (OSS/S3)
应用配置文件 每次变更后 永久 Git 仓库
日志文件 每日轮转 90 天 本地 + 远程
模型文件 一次性 永久 本地 + 备份盘

27.12.3 灾难恢复流程

恢复流程(目标 RTO<4 小时,RPO<24 小时):

1. 评估损失 (15 分钟)
   └─ 确定故障范围
   └─ 选择恢复点(最近备份)

2. 准备环境 (30 分钟)
   └─ 准备新服务器或修复原服务器
   └─ 安装 Docker、Ollama 等基础组件

3. 恢复数据 (2 小时)
   └─ 解压 Milvus 备份:tar -xzf milvus_20260312.tar.gz
   └─ 恢复数据库:mysql -u root -p < mysql_20260312.sql
   └─ 恢复配置文件

4. 启动服务 (30 分钟)
   └─ docker compose up -d
   └─ systemctl start ollama
   └─ 启动 Spring Boot 应用

5. 验证功能 (15 分钟)
   └─ 执行健康检查
   └─ 运行测试用例
   └─ 确认业务恢复正常

6. 事后分析 (1 小时)
   └─ 编写事故报告
   └─ 制定预防措施

附录:命令速查与配置模板

附录 A:完整配置文件模板

A.1 application.yml 完整模板

server:
  port: 8080

spring:
  application:
    name: policy-rag-system
  ai:
    ollama:
      base-url: http://<服务器 IP>:11434
      chat:
        model: qwen2.5:1.5b
        options:
          temperature: 0.3
          num_predict: 2048
      embedding:
        model: bge-m3
    vectorstore:
      milvus:
        client:
          host: <服务器 IP>
          port: 19530
        collection-name: policy_docs
        embedding-dimension: 1024
        index-type: HNSW
        index-params:
          M: 16
          efConstruction: 128
  mvc:
    async:
      request-timeout: 300000
  servlet:
    multipart:
      max-file-size: 50MB
      max-request-size: 50MB

logging:
  level:
    com.ailearn.rag: DEBUG
    org.springframework.ai: DEBUG
  file:
    name: logs/rag.log
  logback:
    rollingpolicy:
      file-name-pattern: logs/rag-%d{yyyy-MM-dd}.%i.log
      max-file-size: 100MB
      max-history: 30

A.2 docker-compose.yml 完整模板

services:
  etcd:
    container_name: milvus-etcd
    image: quay.io/coreos/etcd:v3.5.5
    environment:
      - ETCD_AUTO_COMPACTION_MODE=revision
      - ETCD_AUTO_COMPACTION_RETENTION=1000
      - ETCD_QUOTA_BACKEND_BYTES=4294967296
    volumes:
      - ./volumes/etcd:/etcd
    command: etcd -advertise-client-urls=http://127.0.0.1:2379 -listen-client-urls http://0.0.0.0:2379 --data-dir /etcd
    healthcheck:
      test: ["CMD", "etcdctl", "endpoint", "health"]
      interval: 30s
      timeout: 20s
      retries: 3
    deploy:
      resources:
        limits:
          memory: 256M

  minio:
    container_name: milvus-minio
    image: docker.m.daocloud.io/minio/minio:RELEASE.2023-03-20T20-16-18Z
    environment:
      MINIO_ACCESS_KEY: minioadmin
      MINIO_SECRET_KEY: minioadmin
    ports:
      - "9001:9001"
      - "9000:9000"
    volumes:
      - ./volumes/minio:/minio_data
    command: minio server /minio_data --console-address ":9001"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
      interval: 30s
      timeout: 20s
      retries: 3
    deploy:
      resources:
        limits:
          memory: 512M

  standalone:
    container_name: milvus-standalone
    image: docker.m.daocloud.io/milvusdb/milvus:v2.4.0
    command: ["milvus", "run", "standalone"]
    security_opt:
      - seccomp:unconfined
    environment:
      ETCD_ENDPOINTS: etcd:2379
      MINIO_ADDRESS: minio:9000
      CACHE_SIZE: 512M
    volumes:
      - ./volumes/milvus:/var/lib/milvus
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9091/healthz"]
      interval: 30s
      start_period: 90s
      timeout: 20s
      retries: 3
    ports:
      - "19530:19530"
      - "9091:9091"
    depends_on:
      etcd:
        condition: service_healthy
      minio:
        condition: service_healthy
    deploy:
      resources:
        limits:
          memory: 1024M

networks:
  default:
    name: milvus

附录 B:完整 Java 代码示例

B.1 PolicyRagApplication.java

package com.ailearn.rag;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class PolicyRagApplication {
    public static void main(String[] args) {
        SpringApplication.run(PolicyRagApplication.class, args);
    }
}

B.2 VectorStoreConfig.java

package com.ailearn.rag.config;

import io.milvus.client.MilvusServiceClient;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.vectorstore.MilvusVectorStore;
import org.springframework.ai.vectorstore.MilvusVectorStoreProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.boot.context.properties.EnableConfigurationProperties;

@Configuration
@EnableConfigurationProperties(MilvusVectorStoreProperties.class)
public class VectorStoreConfig {
    
    @Bean
    @Primary
    public MilvusVectorStore vectorStore(MilvusServiceClient client, 
                                         EmbeddingModel model, 
                                         MilvusVectorStoreProperties properties) {
        return MilvusVectorStore.builder(client, model)
            .collectionName(properties.getCollectionName())
            .embeddingDimension(properties.getEmbeddingDimension())
            .initializeSchema(true)
            .build();
    }
}

B.3 CorsConfig.java

package com.ailearn.rag.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("*")
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
                .allowedHeaders("*")
                .maxAge(3600);
    }
}

B.4 ChatController.java

package com.ailearn.rag.controller;

import com.ailearn.rag.service.RagService;
import com.ailearn.rag.service.IngestionService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import reactor.core.publisher.Flux;

import java.io.IOException;

@RestController
@RequestMapping("/api")
@CrossOrigin(origins = "*")
@RequiredArgsConstructor
public class ChatController {

    private final RagService ragService;
    private final IngestionService ingestionService;

    @GetMapping(value = "/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> chat(@RequestParam String query) {
        return ragService.streamAnswer(query);
    }

    @PostMapping("/upload")
    public String upload(@RequestParam("file") MultipartFile file) throws IOException {
        ingestionService.processDocument(file);
        return "上传并解析成功:" + file.getOriginalFilename();
    }
}

B.5 RagService.java

package com.ailearn.rag.service;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.document.Document;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;

import java.util.List;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
public class RagService {

    private final ChatClient chatClient;
    private final VectorStore vectorStore;

    public RagService(ChatClient.Builder chatClientBuilder, VectorStore vectorStore) {
        this.chatClient = chatClientBuilder.build();
        this.vectorStore = vectorStore;
    }

    public Flux<String> streamAnswer(String query) {
        List<Document> docs = vectorStore.similaritySearch(
            SearchRequest.builder()
                .query(query)
                .topK(3)
                .similarityThreshold(0.6)
                .build()
        );

        String context = docs.stream()
            .map(Document::getText)
            .collect(Collectors.joining("\n\n"));

        String refs = docs.stream()
            .map(d -> (String) d.getMetadata().getOrDefault("filename", "未知来源"))
            .distinct()
            .collect(Collectors.joining(", "));

        String prompt = "基于以下政策上下文回答问题:\n" + context + "\n\n问题:" + query;

        return chatClient.prompt()
            .user(prompt)
            .stream()
            .content()
            .concatWith(Flux.just("\n\n---\n📚 来源:" + refs));
    }
}

B.6 IngestionService.java

package com.ailearn.rag.service;

import org.springframework.ai.document.Document;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.ai.reader.tika.TikaDocumentReader;
import org.springframework.core.io.InputStreamResource;
import lombok.RequiredArgsConstructor;

import java.io.IOException;
import java.util.List;

@Service
@RequiredArgsConstructor
public class IngestionService {
    private final VectorStore vectorStore;

    public void processDocument(MultipartFile file) throws IOException {
        TikaDocumentReader loader = new TikaDocumentReader(
            new InputStreamResource(file.getInputStream())
        );
        
        TokenTextSplitter splitter = new TokenTextSplitter(500, 100, 10, 10000, true);
        List<Document> splitDocuments = splitter.apply(loader.get());
        
        splitDocuments.forEach(doc -> 
            doc.getMetadata().put("filename", file.getOriginalFilename())
        );
        
        int batchSize = 32;
        for (int i = 0; i < splitDocuments.size(); i += batchSize) {
            vectorStore.add(splitDocuments.subList(
                i, 
                Math.min(i + batchSize, splitDocuments.size())
            ));
        }
    }
}

附录 C:完整前端代码示例

C.1 package.json

{
  "name": "rag-frontend",
  "version": "1.0.0",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "vue": "^3.4.0",
    "@microsoft/fetch-event-source": "^2.0.1",
    "markdown-it": "^14.0.0"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^5.0.0",
    "vite": "^5.0.0"
  }
}

C.2 ChatBox.vue (完整组件)

<template>
  <div class="chat-container">
    <div class="messages" ref="msgBox">
      <div v-for="(msg, index) in messages" :key="index" 
           :class="['message', msg.role]">
        <div class="role">{{ msg.role === 'user' ? '👤 用户' : '🤖 助手' }}</div>
        <div class="content" v-html="renderMarkdown(msg.content)"></div>
        <div v-if="msg.sources" class="sources">
          <strong>📚 来源:</strong>{{ msg.sources }}
        </div>
      </div>
      <div v-if="loading" class="loading">思考中...</div>
    </div>

    <div class="input-area">
      <input
        v-model="userInput"
        @keyup.enter="sendMessage"
        placeholder="请输入政策问题..."
        :disabled="loading"
      />
      <button @click="sendMessage" :disabled="loading">
        {{ loading ? '发送中...' : '发送' }}
      </button>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import MarkdownIt from 'markdown-it'

const md = new MarkdownIt()
const userInput = ref('')
const messages = ref<{ role: string; content: string; sources?: string }[]>([])
const loading = ref(false)
const msgBox = ref<HTMLElement | null>(null)

const renderMarkdown = (text: string) => {
  return md.render(text)
}

const scrollToBottom = () => {
  if (msgBox.value) {
    msgBox.value.scrollTop = msgBox.value.scrollHeight
  }
}

const sendMessage = async () => {
  if (!userInput.value.trim()) return

  const query = userInput.value
  messages.value.push({ role: 'user', content: query })
  userInput.value = ''
  loading.value = true
  scrollToBottom()

  const assistantMsg = { role: 'assistant', content: '', sources: '' }
  messages.value.push(assistantMsg)

  try {
    const response = await fetch(
      `http://localhost:8080/api/chat?query=${encodeURIComponent(query)}`
    )
    const reader = response.body!.getReader()
    const decoder = new TextDecoder()

    while (true) {
      const { done, value } = await reader.read()
      if (done) break
      const chunk = decoder.decode(value, { stream: true })
      
      // 检测来源信息
      if (chunk.includes('📚 来源:')) {
        const parts = chunk.split('📚 来源:')
        assistantMsg.content += parts[0]
        assistantMsg.sources = parts[1]
      } else {
        assistantMsg.content += chunk
      }
      scrollToBottom()
    }
  } catch (error) {
    assistantMsg.content += `\n[系统错误:${error}]`
  } finally {
    loading.value = false
  }
}
</script>

<style scoped>
.chat-container {
  max-width: 800px;
  margin: 0 auto;
  padding: 20px;
  height: 100vh;
  display: flex;
  flex-direction: column;
}

.messages {
  flex: 1;
  overflow-y: auto;
  padding: 10px;
}

.message {
  margin-bottom: 15px;
  padding: 15px;
  border-radius: 8px;
}

.user {
  background-color: #e3f2fd;
  text-align: right;
}

.assistant {
  background-color: #f5f5f5;
}

.role {
  font-weight: bold;
  margin-bottom: 5px;
  font-size: 0.9em;
  color: #666;
}

.content {
  line-height: 1.6;
}

.sources {
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid #ddd;
  font-size: 0.85em;
  color: #666;
}

.input-area {
  display: flex;
  gap: 10px;
  padding: 15px 0;
}

.input-area input {
  flex: 1;
  padding: 12px;
  border: 1px solid #ddd;
  border-radius: 6px;
  font-size: 14px;
}

.input-area button {
  padding: 12px 24px;
  background-color: #1890ff;
  color: white;
  border: none;
  border-radius: 6px;
  cursor: pointer;
}

.input-area button:disabled {
  background-color: #ccc;
  cursor: not-allowed;
}

.loading {
  text-align: center;
  color: #999;
  padding: 10px;
}
</style>

附录 D:故障排查决策树

系统无法启动
    │
    ├─ Docker 容器启动失败
    │   ├─ 检查 docker logs <容器名>
    │   ├─ 检查 docker stats 内存占用
    │   └─ 检查 daemon.json 格式
    │
    ├─ Java 应用启动失败
    │   ├─ 检查 JVM 参数 (-Xmx)
    │   ├─ 检查 application.yml 配置
    │   └─ 检查 Maven 依赖是否完整
    │
    └─ Ollama 服务不可用
        ├─ 检查 systemctl status ollama
        ├─ 检查 OLLAMA_HOST 配置
        └─ 检查安全组端口 11434

检索失败
    │
    ├─ Connection Refused 19530
    │   ├─ 检查 Milvus 容器状态
    │   ├─ 检查安全组端口 19530
    │   └─ 检查 host 配置 (服务名 vs localhost)
    │
    ├─ 维度不匹配错误
    │   ├─ 检查 embedding-dimension 配置
    │   ├─ 检查模型实际维度
    │   └─ 删除旧 Collection 重建
    │
    └─ collection not found
        ├─ 检查 VectorStoreConfig.initializeSchema(true)
        ├─ 检查 collection-name 配置
        └─ 手动创建 Collection

响应缓慢
    │
    ├─ 首字延迟 > 5s
    │   ├─ 检查 Ollama 模型是否预热
    │   ├─ 检查 Swap 使用率
    │   └─ 考虑降级模型 (7b→1.5b)
    │
    ├─ 检索延迟 > 500ms
    │   ├─ 检查 Milvus 索引类型
    │   ├─ 检查向量维度匹配
    │   └─ 检查网络延迟
    │
    └─ 内存持续高涨
        ├─ 检查 OLLAMA_KEEP_ALIVE 配置
        ├─ 检查 Docker 内存限制
        └─ 增加 Swap 空间

附录 E:2026 年技术趋势与展望

E.1 RAG 技术演进路线

阶段 时间 特征 代表技术
Naive RAG 2023 基础检索 + 生成 简单向量检索
Advanced RAG 2024 检索优化 + 后处理 Rerank、查询重写
Modular RAG 2025 模块化、可组合 混合检索、GraphRAG
Agentic RAG 2026 智能体自主决策 Self-RAG、CRAG

E.2 2026 年值得关注的技术

技术 描述 成熟度 建议
GraphRAG 结合知识图谱的 RAG 🟡 发展中 关注,适合复杂关系场景
Self-RAG 模型自我评估检索质量 🟡 发展中 实验性采用
多模态 RAG 支持图像、音频检索 🟢 可用 生产环境可考虑
RAG + Agent RAG 与智能体结合 🟢 可用 2026 年主流趋势
Tiny Embedding 超小型嵌入模型 (<100M) 🟢 可用 低配服务器首选

E.3 成本优化建议

优化方向 具体措施 预期节省
模型选择 使用量化模型 (Q4_K_M) 内存 -60%
缓存策略 Redis 缓存热点查询 API 调用 -70%
索引优化 DiskANN 替代 HNSW 内存 -50%
云端 API 混合使用本地 + 云端 成本 -40%
自动扩缩容 根据负载动态调整 资源 -30%

附录 F:完整检查清单(2026 版)

F.1 部署前检查清单

□ 硬件资源确认
  □ CPU: ≥2 核(推荐 4 核+)
  □ 内存:≥4GB(推荐 8GB+)
  □ 硬盘:≥50GB SSD
  □ 网络:公网 IP + 安全组配置

□ 软件环境确认
  □ Docker 20.10+ 已安装
  □ Docker Compose v2+ 已安装
  □ JDK 21 已安装
  □ Ollama 已安装并测试

□ 配置确认
  □ application.yml 配置完整
  □ docker-compose.yml 内存限制已设置
  □ Swap 分区已创建(低配服务器)
  □ 安全组端口已开放

□ 模型确认
  □ Embedding 模型已拉取 (bge-m3)
  □ Chat 模型已拉取 (qwen2.5:1.5b)
  □ 模型预热已完成

□ 安全确认
  □ 敏感端口已限制访问 IP
  □ 数据库密码已修改
  □ 日志脱敏已配置

F.2 上线前测试清单

□ 功能测试
  □ 文档上传与解析正常
  □ 向量检索返回正确结果
  □ 流式响应正常工作
  □ 引用来源显示正确

□ 性能测试
  □ 检索延迟 <100ms
  □ 首字延迟 <3s
  □ 并发 10 用户无异常

□ 稳定性测试
  □ 连续运行 24 小时无崩溃
  □ 内存占用稳定
  □ 日志无 ERROR 级别错误

□ 安全测试
  □ 敏感信息已脱敏
  □ 未授权访问被拒绝
  □ SQL 注入测试通过

🎉 结语(2026 增强版)

本书涵盖的技术栈:

  • ✅ Spring AI 1.0 GA(2025 年 5 月正式发布)
  • ✅ Milvus 2.6(2025 年 6 月发布,内存减少 72%)
  • ✅ Ollama 最新优化技巧(2026 年实践)
  • ✅ Embedding 模型 MMTEB 评估基准(ICLR 2025)
  • ✅ 企业级 RAG 安全与合规(2026 年标准)
  • 0305/0309 最新内存隐形占用排查与清理实战

从入门到生产的完整路径:

学习阶段 → 原型开发 → 性能优化 → 生产部署 → 运维监控
   ↓           ↓           ↓           ↓           ↓
 概念理解    Spring AI   Milvus 调优  容器化     Prometheus
 RAG 原理    Milvus Lite  Ollama 优化  安全加固   告警体系

最后提醒:

  1. 版本选择:生产环境请使用 Spring AI 1.0.2+ 和 Milvus 2.6+
  2. 模型选型:参考 MMTEB 排名,不要盲目追求大模型
  3. 安全第一:企业级部署必须做好数据脱敏和访问控制
  4. 持续优化:RAG 系统需要持续评估和迭代优化
  5. 内存警惕:时刻关注"隐形"内存占用,定期执行 Docker 和系统级清理(参考 22.4 节)

祝您的 RAG 系统在 2026 年运行顺利! 🚀

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐