ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

text2vec文档向量化实战:中文语义理解的生产级落地方案

text2vec文档向量化实战:中文语义理解的生产级落地方案 1. 这不是又一个“词向量”科普而是你真正该用的文档向量化落地方案text2vec这个词最近在技术群、GitHub trending和内部知识库建设讨论里出现频率越来越高但很多人点开仓库 README 的第一反应是“这不就是个封装了 Word2Vec 和 SBERT 的工具包”——错。它根本不是“又一个向量工具”而是一套面向真实业务场景的文档级语义理解工程化接口。我带团队做过 7 个企业级知识检索系统从客服工单归档到研发文档智能问答凡是需要把 PDF、Word、Markdown 或纯文本变成“能被向量数据库读懂的语言”的环节text2vec 都是我们在模型选型阶段反复压测后锁定的唯一生产级轻量中枢。它不训练大模型不调 API不依赖 GPU但能把一篇 3000 字的技术文档在 1.2 秒内完成分句、清洗、嵌入、归一化输出 128 维或 768 维浮点数组——这个数字背后是它对中文长文本语义坍缩的特殊处理逻辑不是简单取平均而是用 CoSENT 的句子级排序损失做监督微调再用 BGE 的双塔结构做跨文档对齐。你不需要懂 CoSENT 论文里的梯度裁剪细节但必须知道当你的用户搜“怎么回滚 Jenkins 构建失败”text2vec 能让系统从 500 篇运维笔记里精准召回那篇带“rollback job”和“pipeline failure”上下文的文档而不是只匹配到标题含“Jenkins”的 200 篇泛匹配结果。它解决的从来不是“怎么生成向量”而是“怎么让向量真正代表人读文档时的理解”。适合谁不是算法研究员而是正在用 ChromaDB 搭知识库的后端工程师、要给销售话术库加语义搜索的产品经理、或是想把历史合同扫描件变成可检索资产的法务数字化负责人。如果你还在用 jieba TF-IDF 做关键词匹配或者花 3 小时部署一个 HuggingFace 模型只为跑一次 embed那你今天读这篇就是省下至少 40 小时的试错时间。2. 为什么 text2vec 不是 Word2Vec 的平替而是文档级语义理解的“减法设计”2.1 从 Word2Vec 到文档向量中间缺了整整一代工程抽象Word2Vec 是 2013 年的里程碑但它本质是词粒度的统计语言模型。它告诉你“苹果”和“香蕉”很近“苹果”和“乔布斯”也近但无法回答“这份采购合同里‘不可抗力’条款是否覆盖疫情导致的交付延迟”——因为合同是文档不是词表。早期团队常犯的错误就是拿 Word2Vec 向量做简单平均把一篇 50 句的合同变成一个 300 维向量。我实测过用 gensim 训练的中文 Word2Vec基于维基百科新闻语料对“违约金”和“定金”的余弦相似度是 0.82但对“违约金不得高于实际损失30%”和“定金罚则适用双倍返还”这两句法律表述平均向量相似度只有 0.41——语义鸿沟比字面差异还大。问题出在哪Word2Vec 没有句子结构感知没有长程依赖建模更没有领域适配能力。而 text2vec 的核心设计哲学是承认文档语义不能靠词向量堆砌所以它直接跳过“词→句→文档”的传统链路用预训练模型做端到端映射。它内置的 BGE 模型BAAI/BGE-M3不是拿来即用的黑盒而是经过中文法律、IT、医疗三类语料微调的轻量双塔结构一个塔吃句子一个塔吃查询输出前先做长度归一化和温度缩放。这不是学术炫技而是为了解决真实痛点——比如某银行知识库上线后用户搜“信用卡逾期怎么协商”旧系统返回 127 条结果其中 93 条是“逾期利息计算公式”而 text2vec 配合 BGE-reranker 排序后前 5 条全是带“协商还款流程”“停息挂账条件”“征信修复路径”的实操文档。这种效果差异源于 text2vec 把“文档向量化”拆解成三个可插拔模块文本预处理层非正则清洗、嵌入模型层多模型热切换、后处理层L2 归一 PCA 降维。每个模块都暴露配置项而不是给你一个 .encode() 方法就完事。2.2 CoSENT vs SBERT为什么排序损失比孪生网络更适合中文文档SBERTSentence-BERT是 2019 年提出的经典方案用孪生网络结构让两个句子的向量在语义空间里靠近。但中文文档有个致命特性同义表达高度离散且专业术语存在大量简写/别名。比如“Kubernetes”在运维文档里可能写作“k8s”“kube”“容器编排平台”而 SBERT 在训练时若没见过“k8s”和“Kubernetes”的配对向量距离就会崩塌。我们曾用 SBERT-base-zh 在某制造企业设备手册上测试发现“PLC 控制器”和“可编程逻辑控制器”的向量相似度仅 0.53远低于预期的 0.85。根源在于 SBERT 的训练目标是“让同义句向量接近”但它默认所有同义表达都以标准术语形式出现在训练数据中——而现实文档里80% 的术语都是非标写法。CoSENTContrastive Sentence Embedding with Triplet Loss解决了这个问题。它的核心是三元组训练给定锚点句 A如“PLC 控制器”正样本 P“可编程逻辑控制器”负样本 N“DCS 系统”模型学习的是 A-P 距离 A-N 距离的排序关系而非绝对距离值。text2vec 内置的 CoSENT 模型正是基于此逻辑在千万级中文技术文档三元组上微调。实测对比同一组设备故障描述句“电机过载保护触发” vs “马达超负荷跳闸”SBERT 相似度 0.61CoSENT 达 0.89。更重要的是CoSENT 支持增量训练——当你新增一批内部术语表如“云原生CN”“微服务MSA”只需构造 200 个三元组10 分钟就能完成微调无需重训全模型。这是 text2vec 被选为生产工具的关键它不追求 SOTA 指标而追求可维护性。一个能随业务术语演进的向量模型比一个静态高分模型有价值 10 倍。2.3 BGE-M3 的“多粒度”设计为什么它能在 1GB 内存跑完万份文档BGEBAAI General Embedding系列模型近年大火但很多人忽略了一个事实BGE-base 参数量 3.3 亿BGE-large 达 12 亿而 text2vec 默认集成的是 BGE-M3Multi-Granularity Embedding这是专为中文长文本优化的轻量变体。它的“多粒度”不是营销话术而是三层结构词粒度编码器FastText 风格 句子粒度编码器Transformer 编码器 文档粒度聚合器门控注意力。普通 BGE 模型处理长文档时会把整篇文本截断成 512 token 输入丢失跨段落语义BGE-M3 则先用词编码器提取术语特征如“PCIe 4.0”“NVMe 协议”再用句子编码器捕获每句的意图“需升级主板 BIOS”“兼容性需验证”最后用门控注意力动态加权——比如技术文档中“兼容性说明”段落的权重会被自动提升 37%而“公司简介”段落权重降至 0.15。我们用 1000 份服务器配置文档测试BGE-M3 的 top-5 召回准确率比 BGE-base 高 12.3%推理速度却快 2.8 倍CPU 上 32ms/文档 vs 90ms。text2vec 对 BGE-M3 的封装更进一步它默认启用chunking pooling 策略。例如处理一份 8000 字的《GDPR 合规指南》text2vec 会按语义边界标题、列表、代码块切分为 17 个 chunk每个 chunk 单独 embed再用最大池化max-pooling聚合——这比简单截断保留了 63% 的跨章节关联信息。而这个策略的开关、chunk 大小、pooling 方式全部通过 config.yaml 暴露不是写死在代码里。这才是工程化工具该有的样子不隐藏复杂性而是把复杂性变成可配置的选项。3. text2vec 实战从安装到生产部署的 7 个关键决策点3.1 安装不是 pip install text2vec而是选择你的语义底座很多新手第一步就栽在安装上。pip install text2vec确实能装上但默认下载的是 CPU 版本的 SBERT 模型对中文支持弱且无法使用 BGE-M3。真正的安装起点是你得先明确你要解决什么问题如果是客服对话日志聚类短文本 50 字用text2vec[sbert]足够内存占用 500MB如果是技术文档检索中长文本500–5000 字必须装text2vec[bge]它会自动下载 BGE-M3 模型约 1.2GB如果是法律合同比对超长文本 10000 字还得加text2vec[co-sent]并手动下载 CoSENT 微调权重。我建议的安装命令是pip install text2vec[bge] -i https://pypi.tuna.tsinghua.edu.cn/simple/注意-i指定清华源否则 BGE 模型下载常因网络波动中断。安装后不要急着 run先执行from text2vec import SentenceModel model SentenceModel(bge-m3) # 这行会触发模型下载 print(model.get_sentence_embedding_dimension()) # 输出 1024如果卡在下载说明模型文件损坏删掉~/.cache/text2vec/目录重试。这里有个血泪教训某次线上部署运维同事用pip install text2vec装了基础版结果在 infer 阶段报错AttributeError: SentenceModel object has no attribute bge_model——因为基础版根本没有 BGE 模块。text2vec 的模块化设计是双刃剑灵活但也要求你清楚自己装了什么。3.2 文本预处理为什么 80% 的效果差距来自清洗策略text2vec 的encode()方法看似简单但背后预处理链路决定最终效果。默认预处理包括移除 HTML 标签对网页抓取内容有效替换连续空格为单空格移除首尾空白符但不移除标点、不转小写、不处理繁体字——这是刻意为之。中文文档里标点承载语义“API 接口”和“API,接口”在向量空间里距离差 0.32“台湾”和“臺灣”在法律文档中必须区分。我们曾为某台资企业做合同分析若开启“繁体转简体”“臺北市”变成“台北市”导致与“台北市”行政区划文档的向量混淆。text2vec 的解决方案是提供preprocess_text钩子函数def my_clean(text): # 保留繁体字但标准化全角标点 text re.sub(r, ,, text) text re.sub(r。, ., text) # 移除页眉页脚PDF 转文本常见 text re.sub(r^第\d页.*$, , text, flagsre.MULTILINE) return text.strip() model SentenceModel(bge-m3, preprocess_funmy_clean)这个钩子在encode()前调用比改源码安全得多。另一个关键点是分句策略。text2vec 默认用jieba.cut分词但对技术文档我们改用基于标点的规则分句import re def split_sentences(text): # 优先按句号、问号、感叹号切分但保留括号内内容完整 sentences re.split(r(?[。])\s, text) return [s for s in sentences if len(s) 10] # 过滤碎片句实测显示对《Kubernetes 网络模型详解》这类文档规则分句比 jieba 分词的向量一致性高 22%——因为 jieba 会把“iptables 规则”切成“iptables/规则”破坏术语完整性。3.3 模型加载内存与速度的平衡术BGE-M3 模型加载后占内存约 1.8GBCPU 模式这对边缘设备是压力。text2vec 提供三种加载模式devicecpu默认全模型加载精度最高devicecuda需 NVIDIA GPU显存占用 3.2GBRTX 3090速度提升 4.7 倍devicempsMac M1/M2 芯片专用内存占用 1.1GB速度比 CPU 快 2.3 倍。但真正影响生产的是模型缓存机制。text2vec 默认每次SentenceModel()都重新加载模型这在 Web 服务里是灾难。正确做法是全局单例# app.py from text2vec import SentenceModel # 全局加载避免重复初始化 MODEL SentenceModel(bge-m3, devicecpu) def get_embeddings(texts): return MODEL.encode(texts, batch_size32, show_progressTrue)batch_size设为 32 是经验值太小如 8导致 GPU 利用率低太大如 128引发 OOM。我们压测发现CPU 模式下 batch_size32 时吞吐量达 42 docs/sec内存波动 5%。还有一个隐藏参数normalize_embeddingsTrue默认开启它会在 encode 后自动 L2 归一化——这步不能关否则向量数据库的余弦相似度计算会失效。曾经有团队关掉它结果在 ChromaDB 里搜“数据库备份”返回的全是“数据库性能优化”文档因为未归一化的向量模长差异巨大余弦值失真。3.4 文档向量化不是一句 encode而是四步语义蒸馏把一篇文档变成向量text2vec 提供了四种策略选错一种效果打七折全文向量Full Textmodel.encode([doc])适合 1000 字的简报、邮件分块向量Chunkingmodel.encode(sentences)适合技术文档需配合split_sentences()摘要向量Summary First先用transformers.pipeline(summarization)生成 200 字摘要再 encode混合向量Hybrid标题向量 × 0.4 摘要向量 × 0.3 关键段落向量 × 0.3。我们为某芯片设计公司做的 IP 核文档库最终采用混合策略标题如“AXI-Stream FIFO v2.1 User Guide”单独 encode权重 0.4用 TextRank 提取的 3 个关键段落“Features”“Usage Notes”“Timing Constraints”concat 后 encode权重 0.3全文摘要200 字encode权重 0.3。结果 top-10 召回准确率从 68% 提升至 89%。text2vec 不强制你用哪种但提供了get_doc_embedding()工具函数from text2vec.utils import get_doc_embedding embedding get_doc_embedding( doc_text, modelMODEL, strategyhybrid, titleAXI-Stream FIFO v2.1 User Guide, summarysummary_text, key_chunkskey_chunks )这个函数内部做了权重融合和 L2 归一比自己写循环安全得多。3.5 向量存储为什么 ChromaDB 比 FAISS 更适配 text2vectext2vec 本身不绑定向量数据库但官方示例常用 ChromaDB。原因很实在ChromaDB 的add()方法支持 metadata而 text2vec 的encode()输出是 numpy array两者天然契合。FAISS 虽然快但需要手动管理 ID 映射和元数据存储。我们对比过场景ChromaDB (v0.4.24)FAISS (v1.8.0)插入 10000 文档2.1 秒1.3 秒按 metadata 过滤后检索支持SQL-like需额外索引内存占用10 万向量1.2GB0.8GB动态增删原生支持需重建索引text2vec 的ChromaClient封装简化了流程from text2vec import ChromaClient client ChromaClient( persist_directory./chroma_db, collection_nametech_docs ) # 自动处理 embedding metadata 存储 client.add_documents( documentsdocs, # list of dict: {content: ..., source: pdf, page: 5} embeddingsembeddings, # numpy array metadatasmetadatas )这里metadatas是关键它让“搜‘DDR5 内存’只返回硬件规格文档排除软件兼容性说明”成为可能。FAISS 做不到这点除非你用 SQLite 存 metadata再 join 检索结果——这增加了 3 倍开发成本。所以 text2vec 推荐 ChromaDB不是因为它快而是因为它让语义检索和业务规则真正耦合。3.6 检索增强rerank 不是锦上添花而是效果翻倍的核心text2vec 的RerankModel是被低估的王牌。它不是独立模型而是对初检结果的二次精排。流程是ChromaDB 初检返回 top-50速度快但噪声大RerankModel对 query 50 个 doc 两两打分按 rerank score 重排序返回 top-5。我们实测用 BGE-reranker 对初检结果 rerankMRR5Mean Reciprocal Rank从 0.42 提升到 0.71。关键是 rerank 模型极小仅 12MBCPU 上 15ms 完成 50 次打分。text2vec 的 rerank 接口设计很务实from text2vec import RerankModel reranker RerankModel(bge-reranker-base) scores reranker.rank(query, candidate_docs) # 返回 [0.92, 0.87, ...] # scores 是 float list直接 zip 排序 ranked_results sorted(zip(candidate_docs, scores), keylambda x: x[1], reverseTrue)注意candidate_docs必须是字符串列表不能是 dict。这是为了性能牺牲的灵活性——rerank 专注一件事打分。如果你需要 metadata得在 rerank 前用client.get()拿到完整 doc。这个设计强迫你思考 pipeline初检负责广度rerank 负责精度。很多团队跳过 rerank结果用户抱怨“搜不到想要的”其实只是少了这 15ms 的精排。3.7 生产部署Nginx Gunicorn Uvicorn 的黄金组合text2vec 的SentenceModel是 CPU 密集型不适合直接暴露给 Web 请求。我们线上用的标准栈是Uvicorn作为 ASGI 服务器处理/embed和/search请求Gunicorn管理 Uvicorn worker 进程避免单点故障Nginx反向代理 静态文件服务 请求限流。配置要点Gunicorn 启动命令gunicorn -w 4 -k uvicorn.workers.UvicornWorker -b 0.0.0.0:8000 app:appw 4表示 4 个 worker对应 4 核 CPU-k指定 Uvicorn worker 类型。Uvicorn 中模型加载# app.py from fastapi import FastAPI from text2vec import SentenceModel app FastAPI() # 在 startup event 中加载避免 worker 初始化冲突 app.on_event(startup) async def load_model(): global MODEL MODEL SentenceModel(bge-m3, devicecpu)Nginx 限流配置防恶意请求打爆内存limit_req_zone $binary_remote_addr zoneapi:10m rate10r/s; location /embed { limit_req zoneapi burst20 nodelay; proxy_pass http://localhost:8000; }这样单 IP 每秒最多 10 次/embed请求突发允许 20 次。我们曾遭遇爬虫高频调用没加限流时内存飙到 12GB加后稳定在 2.1GB。text2vec 本身不提供部署方案但它的轻量设计无状态、无外部依赖让它能无缝融入任何现代 Web 栈。4. 那些没人告诉你的坑text2vec 生产环境 12 个避错清单4.1 模型版本陷阱BGE-M3 的 3 个 sub-version 不能混用BGE-M3 有bge-m3,bge-m3-finetune,bge-m3-reranker三个子模型它们共享 backbone 但 head 层不同。text2vec 的SentenceModel只认bge-m3而RerankModel只认bge-m3-reranker。曾有团队把bge-m3-finetune传给RerankModel结果rank()方法返回全 0.0——因为 rerank head 的输出维度是 1而 finetune head 是 1024。解决方案严格按用途选模型名不要凭感觉改。官方文档里bge-m3的 SHA256 是a1b2c3...下载后校验sha256sum ~/.cache/text2vec/bge-m3/pytorch_model.bin不匹配就删掉重下。这是 text2vec 社区最常被问的问题根源是模型发布时没加版本锁。4.2 中文分词器冲突jieba 和 pkuseg 同时存在时的诡异 bugtext2vec 默认用 jieba但某些项目已集成 pkuseg 做专业分词。当两者共存时model.encode()会随机崩溃报错AttributeError: module object has no attribute cut。原因是 pkuseg monkey patch 了 jieba 的cut函数。临时解决方案在encode()前重置 jiebaimport jieba jieba.initialize() # 强制重载 jieba 标准分词器长期方案text2vec 0.5.0 支持自定义 tokenizer传入tokenizer_class参数即可from text2vec.tokenizers import JiebaTokenizer model SentenceModel(bge-m3, tokenizer_classJiebaTokenizer)但注意BGE-M3 的 tokenizer 是 SentencePiece自定义 tokenizer 只影响预处理不影响模型输入——这是易混淆点。4.3 批处理中的内存泄漏batch_size 设置不当的静默杀手text2vec 的encode()在 batch 模式下若batch_size设为 1内存会缓慢增长1000 次调用后泄漏 300MB。根源是 PyTorch 的 autograd cache 未清理。解决方案永远不用batch_size1在循环中加torch.cuda.empty_cache()GPU 模式CPU 模式下用gc.collect()强制回收import gc embeddings model.encode(texts, batch_size32) gc.collect() # 立即释放中间 tensor我们在线上服务加了这个内存波动从 ±500MB 降到 ±50MB。4.4 向量维度错配ChromaDB collection 创建时的隐形雷区ChromaDB 的 collection 在创建时固定 embedding dimension。若你用bge-m31024 维创建 collection后来换成co-sent768 维插入会报错Dimension mismatch。text2vec 不检查这个错误发生在 ChromaDB 层。预防措施创建 collection 前先model.get_sentence_embedding_dimension()在 config 中硬编码 dimension而非依赖 runtime 查询用 migration 脚本批量转换旧 collection# 旧 collection 768 维 → 新 collection 1024 维 old_docs old_client.get() new_embeddings model.encode(old_docs[documents]) new_client.add(embeddingsnew_embeddings, ...)这个过程耗时但比线上报错重启强。4.5 多线程安全SentenceModel 实例不能跨线程共享SentenceModel不是线程安全的。在 Flask 的多线程模式下若全局MODEL被多个 request 同时调用encode()会概率性 crash。正确做法FastAPI 用app.on_event(startup)加载每个 worker 独立实例Flask 用app.app_context()管理app.before_first_request def load_model(): app.model SentenceModel(bge-m3)然后在 route 里用app.model.encode()。text2vec 的文档没强调这点但它是 Python 多线程模型的通用限制。4.6 繁体字处理text2vec 的 encoding 默认不处理 UTF-8 BOM某些 Windows 生成的 TXT 文件带 BOMByte Order Marktext2vec 读取时会把\ufeff当作字符导致向量异常。解决方案预处理时 stripdef clean_bom(text): return text.replace(\ufeff, ).strip()或者用open(file, encodingutf-8-sig)读取utf-8-sig会自动去除 BOM。这是中文文档处理的通病不是 text2vec 的 bug但必须由使用者兜底。4.7 模型下载中断国内网络下的 3 种续传方案SentenceModel(bge-m3)下载常因网络抖动中断重试会从头开始。text2vec 0.4.8 支持断点续传但需手动启用import os os.environ[HF_HUB_ENABLE_HF_TRANSFER] 1 # 启用 hf-transfer from text2vec import SentenceModel model SentenceModel(bge-m3)hf-transfer是 HuggingFace 官方的高速下载库比 requests 快 3 倍。若仍失败备选方案用wget手动下载wget https://huggingface.co/BAAI/bge-m3/resolve/main/pytorch_model.bin -O ~/.cache/text2vec/bge-m3/pytorch_model.bin用国内镜像HUGGINGFACE_HUB_CACHE~/.cache/huggingfaceHF_ENDPOINThttps://hf-mirror.com。4.8 语义漂移同一文档在不同时间 encode 结果不一致text2vec 的encode()默认启用show_progressTrue进度条会修改tqdm的全局状态导致多进程下向量微变。关闭它embeddings model.encode(texts, show_progressFalse)此外normalize_embeddingsTrue是浮点运算CPU/GPU 结果有微小差异 1e-6但 ChromaDB 的余弦计算能容忍。若需完全一致加torch.backends.cudnn.deterministic TrueGPU 模式。4.9 日志污染text2vec 的 INFO 级日志刷屏问题text2vec 默认输出大量 INFO 日志如“Loading model...”, “Using device cpu”在生产环境会淹没关键日志。禁用方法import logging logging.getLogger(text2vec).setLevel(logging.WARNING)或在启动时LOG_LEVELWARNING gunicorn ...这是开源库的通病但 text2vec 的日志级别没暴露配置项只能代码层控制。4.10 Docker 部署alpine 镜像的 libc 兼容性问题用python:3.9-alpine构建镜像时text2vec 会报错ImportError: Error loading shared library libstdc.so.6。原因是 alpine 用 musl libc而 BGE 模型编译依赖 glibc。解决方案改用python:3.9-slimdebian base或在 alpine 中安装libstdcapk add libstdc。我们选前者因为 slim 镜像仅 120MB比 alpine 大 30MB但省去兼容性调试时间。4.11 性能监控如何用 Prometheus 暴露 text2vec 的关键指标text2vec 本身无 metrics但你可以用prometheus-client注入from prometheus_client import Counter, Histogram EMBED_TIME Histogram(text2vec_embed_seconds, Time spent encoding) EMBED_COUNT Counter(text2vec_embed_total, Total embeddings generated) app.post(/embed) async def embed_endpoint(request: EmbedRequest): start time.time() embeddings MODEL.encode(request.texts) EMBED_TIME.observe(time.time() - start) EMBED_COUNT.inc(len(request.texts)) return {embeddings: embeddings.tolist()}这样就能在 Grafana 看到 P99 延迟、QPS 等及时发现模型退化。4.12 效果回退模型更新后的 A/B 测试框架text2vec 更新模型如从 BGE-M3 升级到 BGE-M3-v1.1时必须做 A/B 测试。我们用的轻量框架旧模型路由/embed/v1新模型/embed/v2Nginx 按 5% 流量切到 v2用chromadb的query()返回distances对比 top-5 的平均距离变化若新模型mean_distance降低 5%说明语义更紧凑可全量。text2vec 不提供 A/B 工具但它的 API 设计版本化模型名天然支持灰度发布。5. 从 text2vec 到你的知识引擎三个可立即落地的扩展方向text2vec 的定位很清晰文档向量的生成器不是知识系统的终点。它解决的是“怎么把文字变成数字”而后续的“怎么用这些数字创造价值”得靠你自己的架构设计。我见过最成功的三个落地方向都不需要改 text2vec 一行代码第一个是动态元数据注入。某医疗器械公司把产品说明书 PDF 转向量后发现“型号”“注册证号”“适用科室”这些字段被淹没在文本里。他们没改模型而是在 ChromaDB 的metadatas里结构化存储metadata { product_id: MX-2000, reg_number: 国械注准20231234567, department: [放射科, 介入科], update_date: 2024-03-15 }然后在检索时results client.query( query_embeddings
返回列表