前言
大家好,我是去年开始研究 RAG 的后端开发。
本地跑通一个 RAG 知识库听着简单------不就是"文档切分 → 向量化 → 检索 → LLM 生成"吗?但真正动手时,各种奇奇怪怪的坑一个接一个。
花了 2 周踩坑,终于把系统跑通了。 这篇文章把我踩过的 5 个大坑整理出来,避免大家再走弯路。
坑 1:LM Studio API Token 被坑了一整天 🔥
调用 http://localhost:1234/api/v1/chat 时,开始完全不报错------能正常返回。后来突然开始报:
css
An LM Studio API token is required to make requests to this server,
but none was provided using the Authorization header
我以为 Token 过期了,重启 LM Studio、重新生成 Token,还是一样。
最后发现:LM Studio 有两套 API!
| API 类型 | 端点 | Token |
|---|---|---|
| Local Server API | /v1/chat/completions |
✅ 需要 Bearer Token |
| Developer API | /api/v1/chat |
部分版本免 Token |
java
// ✅ 正确:Local Server API + Bearer Token
String url = lmstudioUrl + "/v1/chat/completions";
Request request = new Request.Builder()
.url(url)
.addHeader("Authorization", "Bearer " + apiKey) // 关键!
.addHeader("Content-Type", "application/json")
.post(RequestBody.create(jsonBody, MediaType.parse("application/json")))
.build();
// ❌ 错误:Developer API 免 Token 的写法,不通用
String url = lmstudioUrl + "/api/v1/chat";
关键点:看清楚你调的是哪套 API,Token 策略完全不同!
坑 2:Embedding 向量维度不匹配,Qdrant 存储报错 🔥
向量化存储时报错:
vbnet
Response: BadRequest:{"status":{"error":"Wrong vector dimension...
我的 Embedding 模型是 text-embedding-bge-base-zh-v1.5,理论上输出 768 维向量。但 Qdrant 创建 Collection 时,我用了默认维度 1024。
先测一次向量维度:
json
curl http://localhost:1234/v1/embeddings \
-H "Authorization: Bearer $LM_API_TOKEN" \
-d '{"model":"text-embedding-bge-base-zh-v1.5","input":["测试文本"]}'
返回结果里数组长度就是真实维度。
创建 Qdrant Collection 时指定正确维度:
scss
// ✅ 正确
qdrantClient.createCollection(
CollectionName,
VectorParams.builder()
.size(768) // 和 Embedding 模型输出维度一致!
.distance(Distance.Cosine)
.build()
);
// ❌ 错误:用错了维度
.size(1024) // 导致存储失败
避坑建议: 把向量维度写成配置项:
yaml
# application.yml
qdrant:
vector-dimension: 768
坑 3:文本切分把句子拦腰截断,回答质量断崖式下降 🔥
早期用的是纯长度切分:
arduino
// ❌ 简单粗暴的切分------把句子拦腰截断
String chunk = text.substring(i, i + 1000);
结果:检索出来的片段经常是半截句子,LLM 完全无法理解。
比如切到"公司成立于 201"------LLM 根本不知道后面是什么。
scss
public List<String> chunkText(String text) {
List<String> chunks = new ArrayList<>();
StringBuilder current = new StringBuilder();
// ✅ 按句子边界分割(中文:。!?;\n)
String[] sentences = text.split("(?<=[。!?.!?;;\n])");
for (String sentence : sentences) {
// 当前块加上这句超过阈值
if (current.length() + sentence.length() > chunkSize) {
// 保存当前块
if (current.length() > 0) {
chunks.add(current.toString().trim());
}
// 保留重叠部分(避免丢失上下文)
int overlapLen = Math.min(chunkOverlap, current.length());
current = new StringBuilder(
current.toString().substring(current.length() - overlapLen)
);
}
current.append(sentence);
}
if (current.length() > 0) {
chunks.add(current.toString().trim());
}
return chunks;
}
加上重叠(Overlap)效果更好:
ini
Chunk1: [句子1][句子2][句子3]...
Chunk2: [...句子3][句子4][句子5]... ← 重叠部分保证上下文连续
坑 4:LM Studio Embedding 接口不稳定(/v1/embeddings) 🔥
部分版本的 LM Studio,/v1/embeddings 接口返回错误:
json
{"error": "model not found"}
但明明 v1/models 里能看到这个模型。
原因
LM Studio 对 Embedding 模型的支持比较微妙,有些 GGUF 格式的 Embedding 模型在部分版本中不稳定。
解决方案
换成 Ollama 运行 Embedding
bash
# 安装 Ollama
winget install Ollama.Ollama
# 下载 Embedding 模型(nomic-embed-text,约 274MB)
ollama pull nomic-embed-text
# 启动
ollama serve
修改配置:
yaml
# application.yml
ollama:
base-url: http://localhost:1234 # 如果 LM Studio 在这里
embeddings-model: nomic-embed-text
# 改成 Ollama:
ollama:
base-url: http://localhost:11434
embeddings-model: nomic-embed-text
坑 5:向量检索相似度阈值设错,噪声答案满天飞 🔥
系统上线后,经常出现"答非所问"的情况------检索出来的片段和用户问题根本不相关,但 LLM 硬是"强行回答"了。
看了日志,发现检索出来的片段相似度只有 0.1~0.2(理论上 0.5+ 才算相关),但都被塞进 Prompt 里了。
scss
public List<SearchResult> search(String query, int topK) {
List<SearchResult> allResults = qdrantClient.search(...);
// ✅ 加阈值过滤:只返回相似度 >= 0.3 的结果
return allResults.stream()
.filter(r -> r.getScore() >= 0.3)
.limit(topK)
.collect(Collectors.toList());
}
不同 Embedding 模型相似度分布不同,建议先跑几个测试:
| Embedding 模型 | 优质答案阈值 |
|---|---|
| bge-base-zh-v1.5 | ≥ 0.5 |
| nomic-embed-text | ≥ 0.6 |
| text-embedding-v2 | ≥ 0.55 |
总结一下: 5 个坑一次排雷
| 坑 | 原因 | 解决 |
|---|---|---|
| LM Studio Token 不一致 | 两套 API 混用 | 统一用 Local Server API + Bearer Token |
| 向量维度不匹配 | Qdrant 维度写错 | 先测试实际维度再创建 Collection |
| 文本切分截断句子 | 纯长度切分 | 按句子边界切分 + 重叠 |
| Embedding 接口不稳定 | LM Studio 版本问题 | 换成 Ollama 或在线 API |
| 噪声答案满天飞 | 无阈值过滤 | 加 score >= threshold 过滤 |