本文档聚焦 Java 迁移最关键的四件事:
- 当前 Python 服务的真实请求链路是什么
- 外部 API 具体怎么调用,payload/headers/响应字段要读哪些
- 当前“向量数据库”实际上是什么,不是什么
- Java 版本应该先兼容什么,再升级什么
这个项目本质上是一个轻量级 RAG 问答服务,而不是一个完整的 Agent 平台。
当前主链路只有一条:
- 接收
POST /chat - 调用 SiliconFlow
embeddings生成 query 向量 - 在本地知识数据中做相似度检索
- 组装
system prompt + context + history + query - 调用 SiliconFlow
chat/completions - 返回回答并写入内存会话
当前关键入口:
- 在线服务:server.py
- 离线入库:scripts/ingest.py
POST /chat 的处理顺序如下:
- 接收请求体字段:
query、session_id、new_session - 若
new_session=true,清空该session_id的历史 - 调用
POST /embeddings将query转成向量 - 在本地知识库中做 TopK 检索
- 过滤掉相似度
<= 0.2的候选片段 - 读取 System_prompt.txt
- 组装
messages - 调用
POST /chat/completions - 将本轮
user/assistant追加到内存会话 - 返回:
{
"response": "LLM 最终回复"
}当前服务的会话管理是“进程内内存会话”,不是 Redis,也不是数据库持久化。
这意味着:
- 只靠
session_id关联上下文 - 服务重启后历史会全部丢失
- 多实例部署时,不同实例之间不会共享会话
- 当前实现只保留最近
HISTORY_LIMIT=10轮,也就是最近 20 条消息(user + assistant)
Java 迁移时如果要先做“行为兼容”,这里建议先保持一致;如果要做生产版,再升级成 Redis 或数据库会话。
离线脚本执行顺序如下:
- 扫描
doc/*.md - 按 Markdown 标题切片
- 每个片段调用
POST /embeddings - 产出如下结构:
{
"text": "[标题1 > 标题2]\n正文内容",
"vector": [0.12, -0.03, 0.56],
"source": "FAQ-Connection.md"
}- 最终写入
data/knowledge.pkl
当前 Python 项目并没有接入 Pinecone、Milvus、Qdrant、pgvector 这类真正的向量数据库。
它当前的实现本质上是:
- 离线把知识片段和 embedding 写进
data/knowledge.pkl - 服务启动时把文件整体加载到内存
- 用
numpy把所有vector组装成矩阵 - 对 query 向量和知识向量都做 L2 归一化
- 用点积计算余弦相似度
- 取 TopK=3,再做阈值过滤
Java 第一版如果目标是“迁移后行为基本一致”,建议保留:
| 项目 | 当前值 |
|---|---|
| 检索条数 | TopK = 3 |
| 相似度算法 | 归一化后点积,等价于余弦相似度 |
| 阈值 | score > 0.2 |
| 知识源文件 | data/knowledge.pkl |
当前仓库里的知识文件虽然叫 knowledge.pkl,但它并不是一个适合长期跨语言依赖的存储格式。
主要原因:
pickle是 Python 私有序列化格式,Java 不能直接安全复用pickle天然不适合做对外交换格式- 如果把不可信文件直接
pickle.load,存在反序列化风险
因此,Java 迁移建议把它视为“过渡期内部产物”,而不是长期契约。
先把知识库转成 JSON 或数据库表,Java 服务自行加载并计算相似度。
建议至少保留这些字段:
{
"id": "chunk_001",
"source": "FAQ-Connection.md",
"header_path": "FAQ > Connection",
"content": "正文内容",
"text": "[FAQ > Connection]\n正文内容",
"embedding": [0.12, -0.03, 0.56]
}优点:
- 最接近当前 Python 实现
- Java 迁移成本最低
- 易于对照现网行为排查问题
缺点:
- 数据量扩大后,检索性能和维护性都一般
- 仍然需要 Java 自己维护内存索引
更推荐在 Java 中抽象成以下组件:
EmbeddingClient负责生成 query 向量VectorStore负责 ANN / cosine searchRerankClient负责精排,可选PromptBuilder负责拼接 promptLlmClient负责调 LLM
这样后续切到 pgvector、Milvus、Qdrant 时不会重写整条链路。
下面的 payload 以“当前项目实际依赖的最小字段”为准,不写那些当前代码没有用到的可选参数。
官方文档:
当前项目实际使用的请求头:
Authorization: Bearer {SILICONFLOW_API_KEY}
Content-Type: application/json当前项目实际发送的 payload:
{
"model": "deepseek-ai/DeepSeek-V3",
"messages": [
{
"role": "system",
"content": "系统提示词 + Context"
},
{
"role": "user",
"content": "用户问题"
}
],
"stream": false,
"temperature": 0.7
}当前项目实际依赖的响应字段:
{
"choices": [
{
"message": {
"content": "最终回复"
}
}
]
}Java 迁移建议:
- 保留
model - 保留
messages - 保留
stream=false - 保留
temperature=0.7 - 第一版先不要改 prompt 结构
- 保留“多个模型顺序降级”的逻辑
- 将
4xx视为非重试错误,将5xx/ 网络错误视为可降级错误
对应代码位置:
官方文档:
当前项目实际使用的 payload:
{
"model": "Qwen/Qwen3-Embedding-0.6B",
"input": "需要向量化的文本",
"encoding_format": "float"
}当前项目实际读取的响应字段:
{
"data": [
{
"embedding": [0.12, -0.03, 0.56]
}
]
}Java 迁移建议:
- 第一版继续使用
encoding_format = float input先保持单字符串,不必一开始就做批量- 后续如果需要批量入库,再切换成数组输入
- Embedding 维度不要写死,直接按返回值存储
官方文档:
现状说明:
- 当前 Python 项目 没有接入 rerank
- 但 Java 迁移时很建议预留这层,因为它正好能改善“Embedding 召回后排序不准”的问题
推荐接入位置:
- 先从向量库召回 TopN,例如 10~20 条
- 再调用
rerank - 只把 rerank 后前 3 条送进最终 prompt
推荐 payload:
{
"model": "BAAI/bge-reranker-v2-m3",
"query": "用户问题",
"documents": [
"候选片段1",
"候选片段2",
"候选片段3"
],
"top_n": 3,
"return_documents": true
}职责建议:
Embedding负责粗召回Rerank负责精排LLM负责最终生成
如果后续目标是提升准确率,rerank 往往是最值得新增的一层。
用户请求
-> /chat
-> embeddings(query)
-> 本地向量检索 Top3
-> chat/completions(messages)
-> 返回 response
用户请求
-> /chat
-> embeddings(query)
-> vector search topN
-> rerank(query, documents)
-> prompt builder
-> chat/completions(messages)
-> 返回 response
如果目标是“先迁移再优化”,先走 6.1;如果目标是“直接做长期版本”,优先走 6.2。
建议至少拆成下面几个模块:
| 模块 | 职责 |
|---|---|
ChatController |
提供 /chat 接口 |
ChatService |
编排完整 RAG 流程 |
EmbeddingClient |
调用 SiliconFlow embeddings |
VectorStore |
查询向量数据库或本地向量索引 |
RerankClient |
调用 SiliconFlow rerank,可选 |
PromptBuilder |
拼接 system/context/history/query |
LlmClient |
调用 SiliconFlow chat/completions |
SessionStore |
保存会话上下文 |
如果要做到“迁移后行为基本一致”,至少保证下面这些点不要先变:
/chat请求和响应结构不变- 仍然使用
session_id维护多轮上下文 - Embedding 模型不变
- LLM 模型配置和降级顺序不变
- prompt 拼接结构不变
- TopK=3 不变
- 相似度阈值
0.2不变
推荐按下面顺序做,而不是一开始就重构所有层:
- 先把外部 API client 在 Java 中跑通
- 再把知识文件从
pickle换成 Java 可读格式 - 然后复刻当前
/chat行为 - 最后再引入 rerank、Redis 会话、真正的向量数据库
这样风险最小,也最容易对照 Python 现网结果做回归。
这次迁移最核心的并不是 FastAPI 改成 Spring Boot,而是把下面三层重新落好:
- 外部 API 调用层:
embeddings、chat/completions、可选rerank - 向量检索层:从
knowledge.pkl + numpy升级成 Java 可维护的存储方案 - RAG 编排层:保证 query -> recall -> rerank -> prompt -> generate 这条链路稳定
如果只求最快迁移:
- 先复刻当前逻辑
- 再把
knowledge.pkl替换成 Java 可读数据源
如果目标是长期版本:
- 直接引入真正的向量数据库
- 同时预留 rerank 与持久化会话层