
基于大模型应用落地,我们最先接触的方案就是RAG检索增强生成。基本一开始只简单接入向量数据库,把文档切片向量化存储,依靠相似度匹配召回文本交给大模型。上线后很快会暴露出一系列棘手问题:向量检索只能做到 “语义相似匹配”,无法识别实体之间的关联关系。
大家都遇到过很常见的场景:知识库存储大量企业人员、项目、合同资料。当提问 “参与A项目的员工负责了哪些相关合同”,纯向量检索只能找到零散文本,无法自动梳理人员、项目、合同三者的关联链路;大模型只能依靠文本片段自行推测关系,极易产生事实幻觉。想要解决这类关联推理类问题,单纯依靠向量数据库远远不够。图数据库擅长存储实体、关系、属性,天然适配网状关联数据;向量数据库擅长高维向量近似相似度搜索。二者不存在替代关系,而是互补组合。
当向量数据库的语义检索能力,搭配图数据库的关系推理能力,再结合大模型理解自然语言、总结生成内容的能力,就能搭建一套既能语义查找资料,又能挖掘实体关联、具备逻辑推理能力的智能系统。

文本、图片、音频无法直接被计算机做语义对比,需要借助Embedding嵌入模型,将非结构化数据转化为固定维度的浮点数组,也就是向量。语义越接近的数据,对应的向量在高维空间中距离越近。
传统关系型数据库、普通 KV 数据库不擅长高维向量检索。如果使用暴力遍历方式计算向量距离,数据量上涨后查询延迟会指数级增长。向量数据库应运而生,核心目标就是高效完成大规模向量近似最近邻搜索(ANN)。
向量数据库内置多种索引算法:HNSW、IVF_FLAT、SCANN等,通过牺牲极小精度,大幅降低检索耗时。除向量检索基础能力外,主流向量数据库(Chroma、Pinecone、Qdrant)还支持:向量与标量混合过滤、动态数据增删改查、分片扩容、持久化存储。
向量数据库核心优势:
向量数据库天然短板:
简易向量嵌入应用示例:
from sentence_transformers import SentenceTransformer
import numpy as np
# 加载开源嵌入模型
model = SentenceTransformer("all-MiniLM-L6-v2")
# 原始文档文本
docs = [
"张三参与智慧城市建设项目",
"智慧城市项目签订三号工程合同",
"张三负责三号合同现场管理工作"
]
# 生成向量
embeddings = model.encode(docs)
print("向量维度:", embeddings.shape)
# 输出:向量维度: (3, 384)图数据库以图模型存储数据,基础单元分为节点(实体)、边(关系)、属性。节点代表客观实体:人员、项目、产品、设备;边代表实体之间定向关系:参与、负责、签订、归属。每条节点和边都可以挂载自定义属性。
行业主流图数据库分为两类:一类支持 Cypher 查询语言(Neo4j),上手简单,适合中小型知识图谱快速搭建;另一类原生分布式图数据库(NebulaGraph、TuGraph),支撑十亿级别节点与边,面向大规模生产环境。
图查询语言可以轻松实现路径遍历、关联筛选、多层关系挖掘。例如查询:找到张三参与的所有项目,以及项目关联的全部合同。这条需求只需要简短语句完成链路检索。
图数据库核心优势:
图数据库天然短板:
Neo4j基础Cypher示例:
# 创建实体与关系
CREATE (p:Person{name:"张三"})-[:JOIN]->(proj:Project{name:"智慧城市项目"})
-[:SIGN]->(contract:Contract{name:"三号工程合同"})
# 查询张三参与项目关联的合同
MATCH (p:Person{name:"张三"})-[:JOIN]->(proj:Project)-[:SIGN]->(c:Contract)
RETURN proj.name,c.name很多技术选型误区,就是试图用一种数据库解决所有问题。这里清晰划清边界:
二者不是竞争关系,而是互补。在大模型应用架构中各司其职,共同提供数据支撑。
绝大多数初创项目落地 AI 应用,首选方案:文档切片→向量化→存入向量库→用户问题向量化,检索topK文本送入大模型。这套架构实现简单,落地门槛低,但随着业务复杂度提升,局限性持续暴露。
第一,缺乏关联视角。假设知识库有多份相互关联文档,分散存储:
第二,无法精准溯源实体关系。当用户提出具备链式逻辑问题,例如 “找出所有和A项目存在合作关系的供应商,梳理合作时间线”:
第三,无法支持结构化深度查询。如果业务需要统计、链路查询、多层实体挖掘,纯向量RAG很难高效实现。反复调用大模型文本分析,成本高、稳定性差。
如果只搭建知识图谱 + 图数据库,不搭配向量库,同样存在明显瓶颈:
首先,原始非结构化文本无法直接检索。
其次,实体名称模糊匹配困难:
大模型本身不掌握私有业务数据,所有知识来源于Prompt传入内容。
想要兼顾语义检索与关系推理,架构上需要打通向量数据库、图数据库。形成一套工作流: 自然语言问题 → 大模型理解意图 → 分支 1:向量库检索相关原始文档;分支 2:抽取实体,图库查询关联链路;汇总两类结果 → 整合上下文送入大模型生成答案。

流程详细说明:
方案一:松耦合双库独立部署
向量数据库、图数据库分别独立部署,不存在底层联动,通过业务代码完成数据互通。 数据流:

优势:架构简单、易于开发调试;两个组件可以独立扩容、独立运维;组件选型灵活,可以自由组合向量数据库+Neo4j、Qdrant+NebulaGraph。
劣势:需要维护两套数据写入逻辑;需要保证向量库文档和图谱实体数据一致性,存在数据同步成本。
方案二:向量嵌入内置到图数据库
Neo4j、NebulaGraph 等图数据库支持为节点增加向量属性。实体名称、实体描述生成向量,存储在节点属性中。 查询方式:用户问题向量,和所有实体向量做相似度匹配,实现模糊实体匹配(实体链接),找到目标节点后再执行图路径查询。

优势:只维护一套存储,架构极简;非常适合实体检索、知识问答场景。
劣势:图数据库向量检索性能弱于专业向量数据库;不适合存储海量文档向量,仅适合实体描述向量,无法承载大规模文档检索场景。
方案三:向量数据库关联图谱ID
文档入库向量库时,文档中抽取出来的实体绑定图谱节点ID;向量库每条向量携带实体ID标签。检索到文档之后,直接根据标签ID联动图数据库查询实体关联信息。

优势:文档与实体强关联,可以精准根据检索到的文档实体展开图谱查询,减少无效图库查询;查询效率更高。
劣势:写入逻辑更加复杂,数据链路更长,需要做好 ID 映射管理。
绝大多数工程落地,松耦合架构是性价比最高的选择,下文示例代码也基于松耦合模式实现。
2.1 数据预处理阶段(离线/准实时) 原始文档 → 文本清洗 → 文本切片:

2.2 用户查询在线推理阶段 用户输入问题 →:

2.3 两步并行检索 → 融合 → 生成

核心价值:同时利用语义相似(向量库)与结构关系(图库)两路信息,互补支撑精准回答。
双库架构下,Prompt不能简单堆砌信息,需要分层组织:
以下参考资料分为两部分: 【1.相关原始文档片段】 {向量检索结果} 【2.实体关联关系】 {图库查询得到的关系链路} 要求:回答优先参考原始文档内容,实体关联关系用来梳理逻辑脉络;不允许编造不存在的实体和关系,如果资料不足直接说明,禁止猜测。 用户问题:{query}
区分两类信息来源,引导大模型优先使用原文,同时利用图谱理清实体关联,有效降低幻觉。
示例采用松耦合架构:Qdrant 向量数据库 + Neo4j 图数据库 + OpenAI Embedding实践;
pip install qdrant-client neo4j sentence-transformers以下示例实现了一个基于Qdrant向量数据库的知识检索模块:使用SentenceTransformer将文本编码为384维向量,批量写入Qdrant集合,并提供向量相似度搜索接口,支持通过查询语句快速检索最相关的文档内容,为RAG等应用场景提供底层向量检索能力。
from qdrant_client import QdrantClient
from qdrant_client.models import PointStruct, VectorParams, Distance
from sentence_transformers import SentenceTransformer
# 初始化向量模型
embed_model = SentenceTransformer("all-MiniLM-L6-v2")
vector_dim = 384
# 连接Qdrant向量库
qdrant_client = QdrantClient(host="127.0.0.1", port=6333)
collection_name = "knowledge_docs"
# 创建集合
if not qdrant_client.collection_exists(collection_name):
qdrant_client.create_collection(
collection_name=collection_name,
vectors_config=VectorParams(size=vector_dim, distance=Distance.COSINE)
)
# 测试文档
texts = [
"张三参与智慧城市建设项目",
"智慧城市项目签订三号工程合同",
"张三负责三号合同现场管理工作"
]
vectors = embed_model.encode(texts)
# 批量写入向量库
points = []
for idx, (text, vec) in enumerate(zip(texts, vectors)):
points.append(PointStruct(id=idx, vector=vec, payload={"text": text}))
qdrant_client.upsert(collection_name=collection_name, points=points)
# 向量检索函数
def search_vector_db(query: str, topk=3):
query_vec = embed_model.encode(query)
res = qdrant_client.search(
collection_name=collection_name,
query_vector=query_vec,
limit=topk
)
return [hit.payload["text"] for hit in res]以下示例实现了一个基于Neo4j图数据库的人物关系查询模块:通过Bolt协议连接Neo4j实例,使用 Cypher查询语言匹配指定人物的所有关联节点与关系类型,将查询结果格式化为"人名-关系-目标名"的三元组列表返回,适用于知识图谱构建与实体关系探索场景。
from neo4j import GraphDatabase
driver = GraphDatabase.driver("bolt://127.0.0.1:7687", auth=("neo4j", "password"))
def graph_query_person(person_name: str):
cypher = """
MATCH (p:Person{name:$name})-r->(target)
RETURN p.name,type(r),target.name
"""
with driver.session() as session:
result = session.run(cypher, name=person_name)
records = []
for rec in result:
records.append(f"{rec[0]} {rec[1]} {rec[2]}")
return records该示例实现了一个混合检索(Hybrid Search)上下文组装函数:同时调用向量数据库做语义检索、图数据库做实体关系查询,将两路结果拼接为结构化上下文,供大模型生成回答时使用,实现"语义相似 + 关系推理"的多源信息融合。
def hybrid_search(user_query: str, entity_name: str):
# 1.向量检索文档
doc_result = search_vector_db(user_query)
# 2.图数据库查询关联关系
graph_result = graph_query_person(entity_name)
context = f"""
【相关文档素材】
{chr(10).join(doc_result)}
【实体关联关系】
{chr(10).join(graph_result)}
"""
return context
# 调用测试
if __name__ == "__main__":
ctx = hybrid_search("张三参与了哪些项目与合同", "张三")
print(ctx)松耦合架构最大痛点:向量库文档更新、删除后,图数据库对应的实体三元组需要同步变更,如果两边数据不一致,大模型会获取相互矛盾信息。可行优化方案:
知识图谱质量上限由信息抽取效果决定。短文本抽取难度较低;长文档、非规范会议纪要,大模型很容易漏实体、错识别关系。优化策略:

随着文档数量、实体规模上涨,双库并发查询会产生延迟。
并不是所有 AI 应用都需要立刻上双库架构。给大家清晰选型标准:
适合向量库 + 图库融合场景:
不需要搭建图数据库:
切勿盲目搭建知识图谱,后续长期维护成本极高,业务却用不上关系查询,造成资源浪费。技术架构需要匹配业务需求,避免过度设计。
企业内部包含人员、项目、合同、供应商、流程文档。
产业链上下游企业构成网状关系:
用户可以查询某企业关联的所有关联公司,同时检索相关新闻公告,辅助风险研判。
查询某药物相关临床试验文献,以及药物与疾病、不良反应之间关联关系:
向量数据库擅长语义相似度检索,处理海量非结构化文本;图数据库擅长实体关系挖掘与路径推理,二者能力互补,单纯向量RAG在关联推理场景存在明显短板,容易引发大模型幻觉;只使用图数据库无法直接检索原始文本,依赖高质量结构化抽取。向量库 + 图库 + 大模型融合架构,通过同时提供原始文本素材与结构化关联事实,有效提升答案准确性,拓展AI应用能力边界。
随着大模型技术持续发展,多模态嵌入、原生图向量一体化数据库正在快速发展。未来会出现更多原生支持图结构 + 向量检索的存储引擎,降低双库运维复杂度。与此同时,Agent智能体技术兴起,结合图谱路径规划、向量检索工具调用,可以打造具备自主信息检索、多步推理能力的智能 Agent。
技术永远只是工具。向量数据库、图数据库、大模型最终都是为业务服务。理解每种组件能力边界,合理组合搭建架构,用最低复杂度方案解决业务问题,才是技术落地最重要的思路。
该示例整合了前面所有模块,构建了一个完整的对话式审批助手:通过Qdrant向量库与 Neo4j图数据库实现混合检索,调用LLM生成回答,并配套长期记忆管理(去重、压缩、衰减、冲突检测、版本更新)与反馈闭环(bad case筛选、人工标注、评测集沉淀),形成"检索→生成→记忆→反馈"的完整闭环架构。
import time
import uuid
import math
import numpy as np
from dataclasses import dataclass, field
from typing import List, Optional, Tuple
from qdrant_client import QdrantClient
from qdrant_client.models import PointStruct, VectorParams, Distance
from sentence_transformers import SentenceTransformer
from neo4j import GraphDatabase
# ============================================================
# 1. 记忆模块
# ============================================================
@dataclass
class MemoryItem:
memory_id: str = field(default_factory=lambda: str(uuid.uuid4()))
content: str = ""
embedding: Optional[List[float]] = None
create_time: float = field(default_factory=time.time)
last_access_time: float = field(default_factory=time.time)
weight: float = 1.0
tags: List[str] = field(default_factory=list)
source: str = "chat"
version: int = 1
is_valid: bool = True
class BaseMemoryStore:
def __init__(self):
self.memory_pool: dict[str, MemoryItem] = {}
def add_memory(self, item: MemoryItem):
self.memory_pool[item.memory_id] = item
def get_all(self) -> List[MemoryItem]:
return list(self.memory_pool.values())
def cosine_similarity(vec1: List[float], vec2: List[float]) -> float:
a = np.array(vec1)
b = np.array(vec2)
return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)))
def deduplicate_memory(new_mem: MemoryItem, store: BaseMemoryStore, threshold=0.85) -> bool:
"""返回 True 表示存在重复,不入库"""
if not new_mem.embedding:
return False
for mem in store.get_all():
if not mem.embedding or not mem.is_valid:
continue
if cosine_similarity(new_mem.embedding, mem.embedding) >= threshold:
mem.last_access_time = time.time()
return True
return False
def compress_memory_content(raw_text: str, level: str = "light") -> str:
if len(raw_text) < 120:
return raw_text
# 此处替换为真实 LLM 调用
return f"【{level}压缩结果】{raw_text[:60]}..."
def update_memory(store: BaseMemoryStore, target_id: str, new_content: str):
if target_id not in store.memory_pool:
return None
old_mem = store.memory_pool[target_id]
old_mem.is_valid = False
new_mem = MemoryItem(
content=new_content,
tags=old_mem.tags.copy(),
source=old_mem.source,
version=old_mem.version + 1
)
store.add_memory(new_mem)
return new_mem
def decay_weight(mem: MemoryItem, decay_lambda=0.0001, min_weight=0.1):
delta = time.time() - mem.last_access_time
new_weight = mem.weight * math.exp(-decay_lambda * delta)
mem.weight = max(new_weight, min_weight)
mem.last_access_time = time.time()
return mem.weight
def detect_memory_conflict(mem_list: List[MemoryItem]) -> List[Tuple[str, str]]:
conflict_pairs = []
for i in range(len(mem_list)):
for j in range(i + 1, len(mem_list)):
m1, m2 = mem_list[i], mem_list[j]
# 此处调用 LLM 判断冲突
# if llm.check_conflict(m1.content, m2.content) == "冲突":
# conflict_pairs.append((m1.memory_id, m2.memory_id))
return conflict_pairs
# ============================================================
# 2. 向量检索模块(Qdrant)
# ============================================================
embed_model = SentenceTransformer("all-MiniLM-L6-v2")
vector_dim = 384
qdrant_client = QdrantClient(host="127.0.0.1", port=6333)
collection_name = "knowledge_docs"
if not qdrant_client.collection_exists(collection_name):
qdrant_client.create_collection(
collection_name=collection_name,
vectors_config=VectorParams(size=vector_dim, distance=Distance.COSINE)
)
def index_documents(texts: List[str]):
vectors = embed_model.encode(texts)
points = [
PointStruct(id=idx, vector=vec.tolist(), payload={"text": text})
for idx, (text, vec) in enumerate(zip(texts, vectors))
]
qdrant_client.upsert(collection_name=collection_name, points=points)
def search_vector_db(query: str, topk=3):
query_vec = embed_model.encode(query)
res = qdrant_client.search(
collection_name=collection_name,
query_vector=query_vec.tolist(),
limit=topk
)
return [hit.payload["text"] for hit in res]
# ============================================================
# 3. 图数据库模块(Neo4j)
# ============================================================
driver = GraphDatabase.driver("bolt://127.0.0.1:7687", auth=("neo4j", "password"))
def graph_query_person(person_name: str):
cypher = """
MATCH (p:Person{name:$name})-r->(target)
RETURN p.name, type(r), target.name
"""
with driver.session() as session:
result = session.run(cypher, name=person_name)
return [f"{rec[0]} {rec[1]} {rec[2]}" for rec in result]
# ============================================================
# 4. 混合检索 + 上下文组装
# ============================================================
def hybrid_search(user_query: str, entity_name: str):
doc_result = search_vector_db(user_query)
graph_result = graph_query_person(entity_name)
context = f"""
【相关文档素材】
{chr(10).join(doc_result)}
【实体关联关系】
{chr(10).join(graph_result)}
"""
return context
# ============================================================
# 5. 反馈闭环模块
# ============================================================
class FeedbackPipeline:
def __init__(self):
self.candidate_bad_cases = []
self.labeled_samples = []
def auto_filter_bad_case(self, log_item: dict) -> bool:
if log_item.get("user_dislike"):
return True
if len(log_item.get("response", "")) < 10:
return True
return False
def add_labeled_sample(self, sample):
self.labeled_samples.append(sample)
self.export_to_eval_set()
def export_to_eval_set(self):
# 此处替换为真实存储逻辑
print(f"导出 {len(self.labeled_samples)} 条样本到评测集")
# ============================================================
# 6. 审批助手主流程
# ============================================================
class ApprovalAssistant:
def __init__(self):
self.memory_store = BaseMemoryStore()
self.feedback = FeedbackPipeline()
def chat(self, user_query: str, entity_name: str = "张三"):
# 1. 混合检索,组装上下文
context = hybrid_search(user_query, entity_name)
# 2. 调用 LLM 生成回答(此处模拟)
response = f"根据检索结果,{user_query} 的相关信息如下:\n{context}"
# 3. 记忆写入(去重 + 压缩)
new_mem = MemoryItem(content=response, source="approval_chat")
if not deduplicate_memory(new_mem, self.memory_store):
compressed = compress_memory_content(response)
new_mem.content = compressed
self.memory_store.add_memory(new_mem)
# 4. 记忆衰减(对所有记忆执行)
for mem in self.memory_store.get_all():
decay_weight(mem)
return response
def handle_feedback(self, log_item: dict):
if self.feedback.auto_filter_bad_case(log_item):
self.feedback.candidate_bad_cases.append(log_item)
print("⚠️ 已标记为疑似 bad case,待人工标注")
# ============================================================
# 7. 测试入口
# ============================================================
if __name__ == "__main__":
# 初始化文档索引
index_documents([
"张三参与智慧城市建设项目",
"智慧城市项目签订三号工程合同",
"张三负责三号合同现场管理工作",
])
# 启动助手
assistant = ApprovalAssistant()
# 用户提问
answer = assistant.chat("张三参与了哪些项目与合同", entity_name="张三")
print(answer)
# 模拟用户负反馈
assistant.handle_feedback({
"query": "张三参与了哪些项目与合同",
"response": answer,
"user_dislike": True
})原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。