共计 2291 个字符,预计需要花费 6 分钟才能阅读完成。
背景痛点
对话记录的特点
ChatGPT 生成的对话记录通常具有以下特点:

- 非结构化数据:对话内容自由格式,包含文本、代码片段、甚至多媒体引用
- 上下文关联性:多轮对话间存在逻辑依赖,单独检索单条记录可能失去意义
- 元数据丰富:包含时间戳、角色标记(user/assistant)、会话 ID 等
原始 JSON 存储的问题
直接存储原始 API 返回的 JSON 会带来以下问题:
- 存储膨胀:重复的元数据和结构标记可能占 50% 以上空间
- 查询低效:全文本扫描 JSON 字段的 LIKE 查询无法利用索引
- 维护困难:API 版本升级可能导致数据结构变化
技术方案对比
存储介质选型
| 类型 | 优势 | 劣势 | 适用场景 |
|---|---|---|---|
| PostgreSQL | 事务支持完善,复杂查询能力强 | 需要严格 Schema 设计 | 需要 ACID 保证的核心数据 |
| MongoDB | 灵活文档结构,水平扩展容易 | 内存消耗大,聚合查询性能一般 | 快速迭代的原型阶段 |
| S3 | 成本极低,无限容量 | 无法直接查询内容 | 冷数据归档 |
| Elasticsearch | 实时全文检索,支持语义相似度 | 运维复杂度高 | 高频检索的生产环境 |
Elasticsearch 映射设计
# 建议的索引映射(Python 字典格式)mapping = {
"properties": {"session_id": {"type": "keyword"},
"turn_index": {"type": "integer"},
"timestamp": {"type": "date"},
"role": {"type": "keyword"},
"content": {
"type": "text",
"analyzer": "ik_max_word", # 中文分词
"fields": {"keyword": {"type": "keyword"}
}
},
"entities": {"type": "nested"} # 提取的命名实体
}
}
代码实现
数据清洗管道
import re
from datetime import datetime
def clean_content(text):
"""移除敏感信息和标准化格式"""
# 移除 API 密钥等敏感信息
text = re.sub(r'sk-[a-zA-Z0-9]{24}', '[REDACTED]', text)
# 标准化时间戳
text = re.sub(r'\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}',
lambda m: datetime.strptime(m.group(), '%Y-%m-%dT%H:%M:%S').isoformat(),
text)
return text
def extract_entities(text):
"""使用 spaCy 或类似库提取实体"""
# 实现示例省略...
return ["实体 1", "实体 2"]
批量写入优化
from elasticsearch import Elasticsearch, helpers
es = Elasticsearch()
def bulk_index(docs, index_name):
actions = [
{
"_index": index_name,
"_source": {"session_id": doc["session_id"],
"content": clean_content(doc["content"]),
"entities": extract_entities(doc["content"])
# 其他字段...
}
}
for doc in docs
]
# 使用 helpers.bulk 自动处理批量和重试
success, _ = helpers.bulk(es, actions, stats_only=True)
return success
检索实现
def semantic_search(query, index_name, size=5):
"""带权重调整的 BM25 搜索"""
body = {
"query": {
"bool": {
"should": [
{
"match": {
"content": {
"query": query,
"boost": 2.0 # 内容字段权重更高
}
}
},
{"term": {"entities": {"value": query, "boost": 1.5}}}
]
}
}
}
return es.search(index=index_name, body=body, size=size)
生产环境考量
冷热数据分离
- 热数据层:SSD 存储的 Elasticsearch 集群,保留最近 3 个月数据
- 温数据层:HDD 存储的 Elasticsearch,保留 3 -12 个月数据
- 冷数据层:压缩后存入 S3,通过 Glacier 进一步降低成本
性能测试结果
| 数据量 | 简单查询(ms) | 复杂聚合(ms) |
|---|---|---|
| 10 万 | 23 | 45 |
| 100 万 | 47 | 128 |
| 1000 万 | 82 | 423 |
避坑指南
分片策略
- 每个分片不超过 50GB 数据
- 分片数 = 数据总量 / 30GB(向上取整)
- 避免单个节点持有过多分片(建议每 1GB 堆内存对应 20-25 个分片)
中文分词
- ik_max_word:细粒度拆分(适合内容检索)
- ik_smart:粗粒度拆分(适合精准匹配)
- 避免使用默认 standard 分词器处理中文
异步写入
- 使用
refresh=wait_for参数保证写入后立即可查 - 监控写入队列,避免堆积(
/_cat/thread_pool?v) - 批量写入时控制请求体大小(建议 5 -15MB)
延伸思考
跨会话语义关联
如何识别不同会话中关于同一主题的讨论?可能的思路:
- 使用 Embedding 模型生成向量表示
- 通过聚类算法发现关联话题
- 构建会话图谱(Conversation Graph)
对话摘要实验
建议尝试以下工作流:
- 用 LLM 生成每段对话的摘要
- 将摘要与原内容共同索引
- 检索时同时匹配原始内容和摘要
- 评估召回率 / 准确率的变化
正文完
发表至: 技术方案
近两天内
