从零实现 ChromaDB + ChatGPT Plugin 最简集成方案:FastChat 与 NextChat 效率提升实战
背景痛点:传统对话系统为何“越聊越慢”
把大模型接进业务系统后,很多团队第一步就是外挂知识库。可真正上线才发现:
- 冷启动延迟:Milvus 这类重量级向量库要先起 etcd、minio,K8s 拉起 Pod 平均 35 s,高峰期扩容一次就超时。
- 多模态瓶颈:PDF、图片同时入库时,传统方案把文件转 base64 塞进 PostgreSQL,再做 base64→向量,链路长、I/O 翻倍。
- 长历史膨胀:NextChat 默认把 30 轮对话全带在 prompt,token 数指数级上涨,GPU 显存 24 G 直接打满,用户侧体感“越聊越卡”。
以上问题归根结底是“向量检索”与“对话管理”两层没有同构部署,网络往返 + 序列化把 RT 抬高了一个量级。
技术选型:为什么 FastChat 场景下我选了 ChromaDB
| 维度 | ChromaDB | Milvus | Pinecone |
|---|---|---|---|
| 部署形态 | 单二进制 + SQLite,无额外依赖 | 8 个组件,K8s 起步 | 全托管,VPC 穿透 |
| 冷启动 | <3 s | 30–40 s | 0 s(托管) |
| 过滤能力 | metadata JSON 索引,支持 >、<、in | 支持,但需建索引后 reload | 仅支持 = 与 in |
| 本地吞吐 | 1 k QPS(batch=100,dim=1536) | 3 k QPS | 2 k QPS(money 换) |
| 开源协议 | Apache-2.0 | Apache-2.0 | 商用闭源 |
FastChat 作为“本地优先”的推理框架,诉求是“能随推理 Pod 一起横向扩”,而不是再拉一条向量库专线。ChromaDB 的嵌入式模式让“推理 + 检索”跑在同一容器,网络 0 跳,正好命中痛点。
核心实现:30 分钟跑通“ChromaDB + ChatGPT Plugin”
下面以 FastChat v0.2.33、NextChat v2.11 为例,给出最小可运行链路。全部代码均跑在 Python 3.10,OpenAI embedding model=text-embedding-ada-002。
步骤 1:在 FastChat 侧启动 ChromaDB 嵌入式实例
# chroma_server.py
import chromadb
from chromadb.config import Settings
client = chromadb.PersistentClient(
path="./chroma_data",
settings=Settings(anonymized_telemetry=False)
)
# 提前创建 collection,避免运行时竞争
client.get_or_create_collection(
name="fastchat_kb",
metadata={"hnsw:space": "cosine"}
)
把上面文件打到推理镜像,CMD 加一条 python chroma_server.py & 即可。容器启动后 2 s 内完成建库。
步骤 2:embedding 预处理与批量插入
# embed_insert.py
import openai, chromadb, tiktoken, math, time
from chromadb.utils import embedding_functions
EMB = embedding_functions.OpenAIEmbeddingFunction(
api_key=openai.api_key, model_name="text-embedding-ada-002"
)
client = chromadb.PersistentClient(path="./chroma_data")
col = client.get_collection("fastchat_kb", embedding_function=EMB)
def num_tokens(text):
return len(tiktoken.encoding_for_model("gpt-3.5-turbo").encode(text))
def insert_docs(docs, batch_size=256):
"""
时间复杂度:O(N/batch_size * T),T 为单 batch 网络 RTT
实测 5 万段文本(avg 180 token):
batch=64 耗时 412 s,rate_limit 429 频发
batch=256 耗时 108 s,无 429
"""
for i in range(0, len(docs), batch_size):
batch = docs[i:i+batch_size]
texts = [b["text"] for b in batch]
metas = [{"title": b["title"], "len": num_tokens(b["text"])} for b in batch]
ids = [b["id"] for b in batch]
col.add(documents=texts, metadatas=metas, ids=ids)
time.sleep(0.2) # 保守避让 rate limit
要点:
- 用 tiktoken 先算长度再入库,方便下游按 token 数过滤。
- batch_size 256 是 Ada-002 的“安全上限”,再大容易 429;sleep 0.2 s 让峰值 QPS≤10,稳过限流。
步骤 3:FastChat 插件化改造——把检索做成一个 Tool
FastChat 的 openai_api_protocol.py 预留了 tools 字段,仿照官方 plugin 规范,新增 retrieval_tool.py:
# retrieval_tool.py
def search_chroma(query: str, topk=5, max_tokens=1500):
"""
检索 + 截断,保证返回总 token < max_tokens
时间复杂度:O(topk * logN) # HNSW 近似
"""
res = col.query(query_texts=[query], n_results=topk)
acc, tk = [], 0
for txt, meta in zip(res["documents"][0], res["metadatas"][0]):
t = meta["len"]
if tk + t > max_tokens:
break
acc.append(txt)
tk += t
return "\n".join(acc)
在 generate_stream 函数里把 search_chroma 当 tool 调用,prompt 模板里加一段 Context: {search_result},即可让模型“看得见”知识库。
步骤 4:NextChat 侧零代码接入
NextChat 已支持 OpenAI plugin 的 openapi.yaml 规范。把 FastChat 的 http://<inference>:8000/v1 填到“接口地址”,插件列表里勾选 retrieval_tool,前端无需改一行代码即可在对话中自动带上下文。

性能优化:把吞吐再拉高 3×
batch_size 对吞吐的影响(测试机:16 vCPU, 64 G, SATA SSD)
| batch_size | 平均 QPS | p99 延迟 | 内存占用 |
|---|---|---|---|
| 16 | 260 | 280 ms | 1.2 G |
| 64 | 580 | 210 ms | 1.4 G |
| 256 | 1020 | 180 ms | 1.9 G |
| 512 | 1050 | 175 ms | 3.4 G |
结论:256 是“性价比”拐点,再大吞吐不再线性,反而内存陡增。
长对话历史内存管理技巧
- 滑动窗口 + 向量去重:把用户前 8 轮用 text-hash 去重后再入库,减少 30 % 冗余向量。
- 显式级截断:NextChat 默认
max_history_tokens=4096,可在前端设置里降到 2048,GPU 显存直接省 1.7 G。 - ChromaDB 的
collection.delete(where={"len": {"$gt": 800}})定时清超长片段,避免 HNSW 图膨胀。
避坑指南:版本和限流
- 版本兼容
ChromaDB 0.4 之后把Collection.add()的embedding参数改名成embeddings,FastChat 老插件会 500。统一锁死版本:chromadb==0.4.15 openai==0.27.8。 - OpenAI rate limit
采用“指数退避 + 令牌桶”双保险:- 桶容量 10,每秒回充 3,突发 10 请求内直接过;
- 遇到 429 时退避 2 ^ retry 秒,最大 32 s。
上线两周未出现一次限流导致的入库失败。
- 多进程竞争
嵌入式 ChromaDB 只支持单写。FastChat 多 worker 时,用filelock把add()串行化,写吞吐降到 1/3,但读仍并发,整体可接受。
延伸思考:用 metadata 玩出更多检索花样
- 时间衰减搜索:把消息时间戳写进 metadata,`where={"timestamp触达读者,让技术内容更具吸引力。
更多推荐


所有评论(0)