2026/5/18 1:54:50
网站建设
项目流程
花都网站建设哪家好,一个公司如何做多个网站备案,手机建网站 教程,营销型网站的要素最近#xff0c;在GitHub上发现一个宝藏项目Project_Golem 。一直以来#xff0c;RAG 是解决知识时效性、事实性问题的核心方案#xff0c;但RAG 调试的黑盒却一直是个问题#xff1a;我们只能看到相似度分数#xff0c;却无从知晓文档在向量空间的实际分布#xff0c;更…最近在GitHub上发现一个宝藏项目Project_Golem 。一直以来RAG 是解决知识时效性、事实性问题的核心方案但RAG 调试的黑盒却一直是个问题我们只能看到相似度分数却无从知晓文档在向量空间的实际分布更搞不懂为什么是这些文档被召回、为什么核心文档会漏召 / 误召调优全凭经验瞎猜。那么到底是embedding模型选错了chunking大小不合理还是检索过程的索引算法选的有问题Project_Golem 的出现提供了一些新的解决思路 通过 UMAP 降维 Three.js 渲染我们可以将高维向量空间映射为 3D 可视化界面让不同语义分块的空间分布、 RAG 的检索轨迹变得清晰可见从而高效找到问题所在。但原版 Project_Golem仅适用于小规模演示无法满足生产级需求。因此在本文中我们将结合Project_Golem以及 Milvus 2.6.8 的改造升级解决了原架构的技术瓶颈并让这套可视化方案具备了实时性、可扩展性和工程化能力。01Project_Golem 是什么有什么痛点想要理解 Project_Golem 的价值我们要先搞懂 RAG 调试黑盒的本质问题向量空间的高维性导致人类无法直观感知。我们将文本转化为 768/1536 维的向量后这些向量会在高维空间中形成聚类 —— 语义相似的文本向量会聚集在一起语义无关的则会远离。但高维空间无法被人类直接观察开发者能获取的只有两个信息一是查询向量与文档向量的余弦相似度分数二是最终被召回的文档列表。这就导致了三个典型的调优问题召回效果差时无法判断是embedding 模型的问题文本向量化时语义丢失还是检索策略的问题索引 / 参数设置不合理文档漏召时不知道目标文档的向量在空间中处于什么位置是否与查询向量属于同一聚类出现误召时无法解释为什么无关文档的向量会与查询向量产生高相似度是文本拆分问题还是向量分布问题。我们看不到过程也就没办法找到问题根源。而 Project_Golem 的核心就是把这个看不见的高维向量空间通过 UMAP 算法将 768/1536 维的高维向量降维至 3 维再利用 Three.js 完成 3D 空间渲染让所有文档向量以节点形式呈现在 3D 界面中语义相似的节点会自然聚集形成簇在线阶段当用户发起查询时先在高维空间计算余弦相似度完成检索再根据返回的文档索引在 3D 界面中 点亮对应的节点检索结果的空间位置自然就能一目了然。但原版的Project_Golem设计更偏向技术验证和演示当文档量达到 10 万、100 万级时其架构缺陷就会暴露主要集中在静态数据、内存性能、工程能力三个方面。静态数据无法支持在线业务的增量更新原版架构中新增文档后需要重新生成完整的 npy 向量文件并重跑全量 UMAP 降维再更新 JSON 坐标文件。仅仅是10 万条文档的 UMAP 单核计算就需要 5-10 分钟若是百万级文档耗时会呈指数级增长。这就意味着这套方案无法对接实时更新的业务数据比如资讯、产品手册、用户对话等只能用于静态文档的可视化演示。内存与性能瓶颈暴力搜索效率低以 768 维 float32 向量为例10 万条向量会占用 305MB 内存100 万条直接达到 3GB而原版架构采用NumPy 暴力搜索时间复杂度为 O (n)单次查询在百万条数据下的延迟会超过 1 秒远达不到在线服务的毫秒级响应要求。工程能力需进一步优化原版架构没有集成 HNSW、IVF 等主流的 ANN 近邻索引算法也不支持标量过滤、多租户隔离、混合检索等生产环境必需的特性。比如实际业务中我们需要按照文档类别、发布时间、权限等级等标量条件过滤检索结果而原版架构完全无法实现只能做纯向量检索与实际生产需求有些脱节。02Milvus Project_Golem如何升级改造原版 Project_Golem 的根本问题在于数据流的断裂新增文档→重生成 npy→重跑 UMAP→更新 JSON整个链路串行且耗时没有实现检索与可视化的解耦也没有生产级的向量数据库做底层支撑。而 Milvus 作为国内主流的云原生向量数据库尤其是 2.6.8 版本引入的Streaming Node特性恰好精准解决了原版架构的痛点同时为可视化方案提供了工程化、规模化的底层能力。针对实时性问题Milvus 2.6.8 的 Streaming Node 无需依赖 Kafka/Pulsar 等外部消息队列就能实现实时数据注入、增量索引更新—— 新增文档后写入即可查询检索索引会自动实时更新彻底摆脱了全量重跑的困境。同时Milvus 实现了可视化层与检索层的完全解耦检索层由 Milvus 负责高维向量的实时检索、索引优化可视化层仅需根据 Milvus 返回的索引在 3D 界面中完成节点点亮两层互不干扰各自迭代优化。改造后我们依然保留了原版的双路核心逻辑同时将检索层全面替换为 Milvus让整个方案具备生产级能力两条路径的具体设计如下1.检索路径毫秒级实时响应OpenAI embedding 生成查询向量 → 写入 Milvus Collection → Milvus AUTOINDEX 自动优化索引 → 实时余弦相似度检索并返回文档索引2.可视化路径当前实现适配小规模演示数据导入时生成 UMAP 3D 坐标n_neighbors30, min_dist0.1→ 固化到 golem_cortex.json → 前端根据 Milvus 返回的索引点亮对应 3D 节点而在规模化扩展方面当前的混合架构已适配 1 万条以内的演示场景若要支持百万级文档的动态更新还能通过三个步骤实现增量可视化让方案真正落地生产触发机制监听 Milvus Collection 的插入事件当累计新增文档超过 1000 条时触发 UMAP 增量更新避免频繁计算增量降维使用 UMAP 的 transform () 方法将新向量直接映射到已有 3D 空间不去重跑全量 fit大幅降低计算耗时前端同步通过 WebSocket 向前端推送更新后的 JSON 坐标片段前端动态添加新节点无需刷新整个 3D 界面。此外Milvus 2.6.8 的混合检索能力向量 全文 标量过滤还为可视化方案预留了丰富的扩展空间 —— 后续可在 3D 界面中叠加关键词高亮、类别过滤、时间筛选等交互功能让 RAG 调试的维度更丰富。03实战落地Project_GolemMilvus 的完整部署与交互演示改造后的 Project_Golem 已开源至 GitHub我们以Milvus 官方文档为数据集一步步实现 RAG 检索的 3D 可视化整个过程基于 DockerPython零基础也能快速上手。完整项目仓库地址https://github.com/yinmin2020/Project_Golem_Milvus准备条件Docker 20.10 Docker Compose 2.0Python 3.11OpenAI API Key数据集Milvus 官方文档 markdown 文件1.部署 milvus下载docker-compose.ymlwget https://github.com/milvus-io/milvus/releases/download/v2.6.8/milvus-standalone-docker-compose.yml -O docker-compose.yml启动Milvus检查端口映射19530:19530docker-compose up -d验证服务启动docker ps | grep milvus应该看到3个容器milvus-standalone, milvus-etcd, milvus-minio2.核心实现2.1 适配 Milvus 部分ingest.py说明支持最多 8 个类别超出部分会循环使用颜色from pymilvus import MilvusClientfrom pymilvus.milvus_client.index import IndexParamsfrom openai import OpenAIfrom langchain_text_splitters import RecursiveCharacterTextSplitterimport umapfrom sklearn.neighbors import NearestNeighborsimport jsonimport numpy as npimport osimport glob--- CONFIG ---MILVUS_URI http://localhost:19530COLLECTION_NAME golem_memoriesJSON_OUTPUT_PATH ./golem_cortex.json数据文件夹用户把 md 文件放在这里DATA_DIR ./dataOpenAI Embedding ConfigOPENAI_API_KEY os.getenv(OPENAI_API_KEY)OPENAI_BASE_URL https://api.openai.com/v1 #OPENAI_EMBEDDING_MODEL text-embedding-3-small1536 dimensionsEMBEDDING_DIM 1536颜色映射自动轮转分配颜色COLORS [[0.29, 0.87, 0.50],Green[0.22, 0.74, 0.97],Blue[0.60, 0.20, 0.80],Purple[0.94, 0.94, 0.20],Gold[0.98, 0.55, 0.00],Orange[0.90, 0.30, 0.40],Red[0.40, 0.90, 0.90],Cyan[0.95, 0.50, 0.90],Magenta]def get_embeddings(texts):Batch embedding using OpenAI APIclient OpenAI(api_keyOPENAI_API_KEY, base_urlOPENAI_BASE_URL)embeddings []batch_size 100OpenAI allows multiple texts per requestfor i in range(0, len(texts), batch_size):batch texts[i:i batch_size]response client.embeddings.create(modelOPENAI_EMBEDDING_MODEL,inputbatch)embeddings.extend([item.embedding for item in response.data])print(f ↳ Embedded {min(i batch_size, len(texts))}/{len(texts)}...)return np.array(embeddings)def load_markdown_files(data_dir):Load all markdown files from the data directorymd_files glob.glob(os.path.join(data_dir, **/*.md), recursiveTrue)if not md_files:print(f ❌ ERROR: No .md files found in {data_dir})print(f Create a {data_dir} folder and put your markdown files there.)print(f Example: {data_dir}/doc1.md, {data_dir}/docs/doc2.md)return Nonedocs []print(f\n FOUND {len(md_files)} MARKDOWN FILES:)for i, file_path in enumerate(md_files):filename os.path.basename(file_path)相对于 data_dir 的路径作为类别rel_path os.path.relpath(file_path, data_dir)category os.path.dirname(rel_path) if os.path.dirname(rel_path) else defaultwith open(file_path, r, encodingutf-8) as f:content f.read()docs.append({title: filename,text: content,cat: category,path: file_path})print(f {i1}. [{category}] {filename})return docsdef ingest_dense():print(f PROJECT GOLEM - NEURAL MEMORY BUILDER)print(f * 50)if not OPENAI_API_KEY:print( ❌ ERROR: OPENAI_API_KEY environment variable not set!)print( Run: export OPENAI_API_KEYyour-key-here)returnprint(f ↳ Using OpenAI Embedding: {OPENAI_EMBEDDING_MODEL})print(f ↳ Embedding Dimension: {EMBEDDING_DIM})print(f ↳ Data Directory: {DATA_DIR})1. Load local markdown filesdocs load_markdown_files(DATA_DIR)if docs is None:return2. Split documents into chunksprint(f\n PROCESSING DOCUMENTS...)splitter RecursiveCharacterTextSplitter(chunk_size800, chunk_overlap50)chunks []raw_texts []colors []chunk_titles []categories []for doc in docs:doc_chunks splitter.create_documents([doc[text]])cat_index hash(doc[cat]) % len(COLORS)for i, chunk in enumerate(doc_chunks):chunks.append({text: chunk.page_content,title: doc[title],cat: doc[cat]})raw_texts.append(chunk.page_content)colors.append(COLORS[cat_index])chunk_titles.append(f{doc[title]} (chunk {i1}))categories.append(doc[cat])print(f ↳ Created {len(chunks)} text chunks from {len(docs)} documents)3. Generate embeddingsprint(f\n GENERATING EMBEDDINGS...)vectors get_embeddings(raw_texts)4. 3D Projection (UMAP)print(\n CALCULATING 3D MANIFOLD...)reducer umap.UMAP(n_components3, n_neighbors30, min_dist0.1, metriccosine)embeddings_3d reducer.fit_transform(vectors)5. Wiring (KNN)print( ↳ Wiring Synapses (finding connections)...)nbrs NearestNeighbors(n_neighbors8, metriccosine).fit(vectors)distances, indices nbrs.kneighbors(vectors)6. Prepare output datacortex_data []milvus_data []for i in range(len(chunks)):cortex_data.append({id: i,title: chunk_titles[i],cat: categories[i],pos: embeddings_3d[i].tolist(),col: colors[i],nbs: indices[i][1:].tolist()})milvus_data.append({id: i,text: chunks[i][text],title: chunk_titles[i],category: categories[i],vector: vectors[i].tolist()})with open(JSON_OUTPUT_PATH, w) as f:json.dump(cortex_data, f)7. Store vectors in Milvusprint(\n STORING IN MILVUS...)client MilvusClient(uriMILVUS_URI)Drop existing collection if it existsif client.has_collection(COLLECTION_NAME):print(f ↳ Dropping existing collection {COLLECTION_NAME}...)client.drop_collection(COLLECTION_NAME)Create new collectionprint(f ↳ Creating collection {COLLECTION_NAME} (dim{EMBEDDING_DIM})...)client.create_collection(collection_nameCOLLECTION_NAME,dimensionEMBEDDING_DIM)Insert dataprint(f ↳ Inserting {len(milvus_data)} vectors...)client.insert(collection_nameCOLLECTION_NAME,datamilvus_data)Create index for faster searchprint( ↳ Creating index...)index_params IndexParams()index_params.add_index(field_namevector,index_typeAUTOINDEX,metric_typeCOSINE)client.create_index(collection_nameCOLLECTION_NAME,index_paramsindex_params)print(f\n✅ CORTEX GENERATED SUCCESSFULLY!)print(f {len(chunks)} memory nodes stored in Milvus)print(f Cortex data saved to: {JSON_OUTPUT_PATH})print(f Run python GolemServer.py to start the server)if __name__ __main__:ingest_dense()2.2 前端可视化部分GolemServer.py)from flask import Flask, request, jsonify, send_from_directoryfrom openai import OpenAIfrom pymilvus import MilvusClientimport jsonimport osimport sys--- CONFIG ---Explicitly set the folder to where this script is locatedBASE_DIR os.path.dirname(os.path.abspath(__file__))OpenAI Embedding ConfigOPENAI_API_KEY os.getenv(OPENAI_API_KEY)OPENAI_BASE_URL https://api.openai.com/v1OPENAI_EMBEDDING_MODEL text-embedding-3-smallMilvus ConfigMILVUS_URI http://localhost:19530COLLECTION_NAME golem_memoriesThese match the files generated by ingest.pyJSON_FILE golem_cortex.jsonUPDATED: Matches your new repo filenameHTML_FILE index.htmlapp Flask(__name__, static_folderBASE_DIR)print(f\n PROJECT GOLEM SERVER)print(f Serving from: {BASE_DIR})--- DIAGNOSTICS ---Check if files exist before startingmissing_files []if not os.path.exists(os.path.join(BASE_DIR, JSON_FILE)):missing_files.append(JSON_FILE)if not os.path.exists(os.path.join(BASE_DIR, HTML_FILE)):missing_files.append(HTML_FILE)if missing_files:print(f ❌ CRITICAL ERROR: Missing files in this folder:)for f in missing_files:print(f - {f})print( Did you run python ingest.py successfully?)sys.exit(1)else:print(f ✅ Files Verified: Cortex Map found.)Check API Keyif not OPENAI_API_KEY:print(f ❌ CRITICAL ERROR: OPENAI_API_KEY environment variable not set!)print( Run: export OPENAI_API_KEYyour-key-here)sys.exit(1)print(f ↳ Using OpenAI Embedding: {OPENAI_EMBEDDING_MODEL})print( ↳ Connecting to Milvus...)milvus_client MilvusClient(uriMILVUS_URI)Verify collection existsif not milvus_client.has_collection(COLLECTION_NAME):print(f ❌ CRITICAL ERROR: Collection {COLLECTION_NAME} not found in Milvus.)print( Did you run python ingest.py successfully?)sys.exit(1)Initialize OpenAI clientopenai_client OpenAI(api_keyOPENAI_API_KEY, base_urlOPENAI_BASE_URL)--- ROUTES ---app.route(/)def root():Force serve the specific HTML filereturn send_from_directory(BASE_DIR, HTML_FILE)app.route(/)def serve_static(filename):return send_from_directory(BASE_DIR, filename)app.route(/query, methods[POST])def query_brain():data request.jsontext data.get(query, )if not text: return jsonify({indices: []})print(f Query: {text})Get query embedding from OpenAIresponse openai_client.embeddings.create(modelOPENAI_EMBEDDING_MODEL,inputtext)query_vec response.data[0].embeddingSearch in Milvusresults milvus_client.search(collection_nameCOLLECTION_NAME,data[query_vec],limit50,output_fields[id])Extract indices and scoresindices [r[id] for r in results[0]]scores [r[distance] for r in results[0]]return jsonify({indices: indices,scores: scores})if __name__ __main__:print( ✅ SYSTEM ONLINE: http://localhost:8000)app.run(port8000)3.下载数据集存放指定目录https://github.com/milvus-io/milvus-docs/tree/v2.6.x/site/en4. 启动项目4.1 文本向量化映射到 3D 空间python ingest.py4.2 启动前端服务python GolemServer.py5.可视化交互前端接收检索结果后根据相似度分数映射节点亮度保持原颜色不变以维持类别簇的视觉连续性。同时绘制从查询点到命中节点的半透明连线摄像机平滑聚焦到激活簇所在区域。5.1 案例 1领域内匹配查询“Milvus 支持哪些索引类型”可视化反馈3D 空间中标记为“INDEXES”类别的红色簇中约 15 个节点亮度显著增强2-3 倍命中节点包括index_types.md、hnsw_index.md、ivf_index.md等文档的 chunk前端绘制从查询向量位置到这些节点的半透明连线镜头平滑聚焦到红色簇区域5.2 案例 2领域外查询的拒绝表现查询“KFC 优惠套餐多少钱”可视化反馈空间中所有节点保持原色仅有微弱的尺寸波动1.1 倍命中节点分散在多个不同颜色的簇中无明显聚集模式摄像机未触发聚焦行为因未达到阈值 0.504写在最后Project_Golem 结合 Milvus 的改造升级本质上是一个实验性但极具参考意义的项目它的核心价值并非只是实现了 RAG 检索的 3D 可视化更是为行业解决RAG 可解释性问题提供了全新的技术思路。在这套方案之前RAG 调优是 “凭经验、看结果、瞎调参”而在这套方案之后开发者能通过可视化界面完成三个核心调优动作观察语义空间结构判断 embedding 模型的向量化效果看语义相似的文档是否形成合理聚类定位检索策略问题分析漏召 / 误召的原因是索引参数设置不合理还是文本分块导致的语义碎片化验证调优效果调优后能直观看到向量空间的变化、检索轨迹的优化让调优有了可量化、可可视化的依据。相信随着向量数据库的不断发展以及可解释性技术的持续迭代RAG 调试的黑盒问题会被彻底解决让大模型应用的落地更高效、更稳定。阅读推荐 Milvus印度最大电商平台如何打造服务两亿月活用户的商品比价系统 Claude通过Cowork实现模型主动记忆要如何复现我们还需要RAG吗 Skills 比MCP好在哪儿如何用Milvus-Skills 搭建知识库 熠智AIMilvus:从Embedding 到数据处理、问题重写电商AI客服架构怎么搭 官宣Milvus开源语义高亮模型告别饱和检索帮RAG、agent剪枝80%上下文 都有混合检索与智能路由了谁还在给RAG赛博哭坟