简介:这是一套面向农业园林智能化从业者、自然语言处理技术研究人员及植保信息化方案设计者的DeepSeek应用型方案,聚焦杂草种类识别与绿色生态防除,给出基于实体抽取和语义检索的完整技术落地路径。资源为单个PDF文档,共539页、约14.96MB,文档目录支持章节跳转,并配有书签大纲,全书51个大章节,便于按主题快速定位。已有58人学习下载。内容系统覆盖杂草样本采集、数据预处理、标注体系构建与校验、特征工程、实体抽取模型的训练、微调与蒸馏、实体消歧与标准化映射、语义检索语料库构建、文本向量化及索引优化等环节,还包含损失函数设计、模型轻量化、向量空间相似度计算等关键技术细节,从数据到模型再到检索应用形成完整闭环,适合作为农业智能化项目方案设计、技术选型或教学参考。
1. 农业园林杂草识别与生态防除,为什么落到实体抽取和语义检索上
做智慧农业或园林养护的同行,应该都遇到过这种场景:一线工人发来一张照片,文字描述是“草坪里贴地爬的草,叶子像花生叶,开小黄花”,让你判断这是什么草、怎么除。图像识别模型给个“疑似菊科”的概率,人工翻图鉴要翻半天,最终防除方案还是凭经验拍脑袋。这个标题给的思路是把 DeepSeek 接进来,用实体抽取把口语化描述变成结构化字段,再用语义检索去杂草知识库里找匹配条目,最后让大模型生成一份可落地的生态防除方案。适合的人群很明确:正在做智慧农业知识库、园林养护系统,或者想把 LLM 接到垂直领域资料库上的开发者。核心价值不是“更准的识别”,而是把“一段人话”完整变成“一份可执行的防除工单”。
2. 实体抽取与语义检索的原理和选型:为什么这套组合能搞定杂草识别
2.1 实体抽取:把“人话描述”变成结构化字段,DeepSeek 比规则词典更抗造
先说要解决的第一个问题。杂草描述有个特点:口语化严重、别名极多。拿马唐来说,正式名叫马唐(Digitaria sanguinalis),但农户叫它“抓地龙”“鸡爪草”,草坪养护工可能直接说“那种贴着地长的草”。传统的规则词典方案,需要把每一个别名、每一种说法都维护进词表,且描述稍微绕一点就抽不出来。实体抽取要做的,是把“路边那种叶子像花生、开小黄花的草”这种句子,抽成 candidate_names、morphology、habitat、season 这样的结构化字段,供后续检索使用。
常见做法是直接调用 DeepSeek API 完成抽取,而不是本地训练 NER 模型。原因有两个:一是垂直领域训练 NER 需要大量标注数据,而杂草描述的标注成本不比收集知识条目低;二是 DeepSeek 这类模型对口语和别名有很强的泛化能力,你只需要把字段约束写清楚。下面是我常用的一个抽取函数:
import json from openai import OpenAI # DeepSeek API 兼容 OpenAI 协议,换 base_url 即可 client = OpenAI( api_key="sk-你的密钥", base_url="https://api.deepseek.com" ) def extract_weed_entities(text: str) -> dict: prompt = f"""你是植物分类学助手。从下面的杂草描述中抽取实体,只输出JSON,不允许输出别的: - "candidate_names": 可能的杂草名(含俗名、别名),数组 - "morphology": 形态特征(叶形、花色、株高、根系),字符串 - "habitat": 生境(旱地/水田/路边/草坪),字符串 - "season": 发生季节,字符串 描述:{text} 只输出JSON。""" resp = client.chat.completions.create( model="deepseek-chat", messages=[{"role": "user", "content": prompt}], temperature=0.1, max_tokens=500, response_format={"type": "json_object"} ) return json.loads(resp.choices[0].message.content) sample = "草坪里长了那种贴地爬的草,叶子有点像花生叶,开小黄花,一丛一丛的,夏天特别多" print(extract_weed_entities(sample))这段代码的重点在三个参数上。temperature 必须压到 0.1 附近,抽取类任务要的是稳定输出,温度高了同一句话两次抽出的字段能差出半个物种。response_format="json_object" 强制模型走 JSON 通道,但有个前提:提示词里必须出现“JSON”字样,否则部分服务端会报错。如果你的服务端不支持这个参数,就删掉它,靠提示词里“只输出JSON”约束,效果略差但也能用。max_tokens 给 500 够用,因为我们要的不是长文,是紧凑字段。
实际跑下来的体会是:DeepSeek 对形态特征这类描述性字段的抽取质量明显高于关键词匹配,尤其是在“叶子像花生叶”“贴地爬”这种比喻式描述上。不过它偶尔会把“马唐”抽成“马唐草”这种带后缀的变体,后面第 5 章会专门讲怎么处理。
2.2 语义检索:别名和比喻式描述,关键词搜索根本召不回
实体抽取拿到字段之后,下一步是拿这些字段去知识库里找对应的杂草条目。这里有个容易踩的坑:用关键词拼接去搜,比如 candidate_names 里是“马唐草”,而知识库里写的是“马唐”,BM25 在分词上或许能沾边,但如果描述是“叶子像花生叶”,关键词搜索就完全无能为力了。所以检索环节要换成语义检索,也就是把查询和知识条目都映射成向量,用余弦相似度找最接近的候选。
向量模型我一般用 BAAI/bge-large-zh-v1.5,中文效果稳,CPU 上也能跑。资料条目少于 1000 条时,直接用 numpy 算内积就行,不必上 Milvus 那些重组件。下面是最小可运行的检索代码:
from sentence_transformers import SentenceTransformer import numpy as np # 中文embedding模型,首次运行会下载权重 model = SentenceTransformer("BAAI/bge-large-zh-v1.5") corpus = [ "马唐(Digitaria sanguinalis):禾本科马唐属,一年生草本,茎匍匐,叶鞘疏松抱茎,喜湿,夏秋发生。", "酢浆草(Oxalis corniculata):酢浆草科酢浆草属,多年生草本,茎匍匐或斜升,三出复叶,春夏花黄。", "香附子(Cyperus rotundus):莎草科,多年生草本,具地下块茎,喜湿润,夏秋季发生为主。" ] corpus_embeddings = model.encode(corpus, normalize_embeddings=True) query = "叶子像花生叶、开小黄花的贴地草" query_embedding = model.encode([query], normalize_embeddings=True)[0] scores = corpus_embeddings @ query_embedding # 已归一化,点积即余弦相似度 best_idx = np.argmax(scores) print(f"最可能的杂草:{corpus[best_idx]},相似度:{scores[best_idx]:.4f}")这里有两个参数值得说。normalize_embeddings=True 一定要开,不开的话内积算出来是向量长度加权的,不同文本长度会干扰相似度排序。top_k 的取值在落地时一般取 5,取多了后面大模型生成方案时上下文太杂,取少了又容易漏掉正确答案。至于相似度阈值,我通常设 0.6 到 0.7,低于 0.6 的检索结果宁可不要,直接让大模型说“不确定”,比硬凑一个物种强。
2.3 混合检索:向量召回为主,BM25 兜底,RRF 融合排序
纯向量检索在垂直领域有个短板:对“马唐”“香附子”这类专有名词,如果 embedding 模型在训练时没见过或见得少,向量相似度会偏低。这时候纯向量检索会漏,而 BM25 靠词频能稳稳命中。所以正式系统里我一般做混合检索——向量召回和 BM25 召回各出一份候选,再按倒数排名融合(RRF)排序。
RRF 的公式不复杂:score = 1/(k + rank_i) 在多个列表里累加,k 一般取 60,实现起来也就十来行。逻辑是并行的:一路走向量库,一路走关键词,最后把两路的文档 ID 按 RRF 合并。经验值是向量召回占七成权重,关键词占三成;如果知识库里俗名特别多,关键词权重可以再提一点。这一步不用写成死代码,理解公式后,在你的向量库客户端里加一个 rank_fusion 函数就行。
查询构造也有讲究。不要直接把用户原话丢给 embedding 模型,也不要只拿 candidate_names 去查。我常用的查询模板是:先拿 candidate_names 做一道过滤器,比如“马唐”精确匹配知识库里的名称字段,命中就直接进入候选;没命中,就把 morphology 加 habitat 拼成一句“禾本科,匍匐茎,叶鞘抱茎,生于草坪”,拿这句去做向量检索。这样做的原因是:用户原话粒度太粗,噪声大;而形态特征字段恰恰是 embedding 模型最擅长处理的自然语言。分阶段检索还能顺带解决一个实际问题——多杂草同时发生的情况,比如“草坪里有马唐还有地锦”,实体抽取会抽出两个 candidate_names,这时候要分别检索,再合并去重,最后喂给生成环节。
3. 539页PDF怎么变成可检索的知识库:解析、切分与入库
3.1 PDF解析:先判断是文字版还是扫描版,别上来就 OCR
收到一份 539 页的杂草资料 PDF,第一件事不是急着跑代码,而是先判断它是文字版还是扫描版。判断方法很简单,用 PyMuPDF 抽几页文本,看看每页文字量。文字版每页能抽出几百字,扫描版只有几个空行。这一步决定了后面要不要接 OCR,OCR 的耗时和成本完全不一样。
import fitz # PyMuPDF doc = fitz.open("weed_handbook.pdf") print(f"总页数:{doc.page_count}") scan_flag = 0 for i in range(min(10, doc.page_count)): text = doc[i].get_text("text") if len(text.strip()) < 50: scan_flag += 1 print(f"第{i+1}页疑似扫描版,提取到{len(text.strip())}字符") if scan_flag > 3: print("结论:这份PDF以扫描页为主,需要走OCR管线") else: print("结论:文字版,直接抽文本即可")pdfplumber 和 PyMuPDF 之间我选 PyMuPDF,原因是速度快,539 页抽全量文本几秒就完事,而且 get_text("text") 拿到的版式顺序大体符合阅读顺序。如果是印刷质量差的扫描件,常见做法是先用 PaddleOCR 或 RapidOCR 过一遍,输出带坐标的文本块,再按坐标从上到下、从左到右重组段落。OCR 参数里影响最大的是识别语言模型,农业资料里繁体字和生僻植物名很多,默认的中文模型不够,要额外挂繁体识别模型。
3.2 段落切分:按标题层级切,不要按固定字符数硬切
PDF 抽出来的是一长串连续文本,不能直接拿去向量化。常见翻车点是固定 chunk_size 切分,比如一刀切成 800 字符一段——把“马唐”的形态描述和“防除方法”切成两段,检索时只能召回半截信息,大模型生成方案时就会缺内容。正确做法是按标题层级切分,让每个 chunk 尽量是一个完整的知识条目。
import re from typing import List def split_by_heading(text: str) -> List[str]: # 匹配"一、""1.""(一)"等中文章节标题 pattern = re.compile( r'^([一二三四五六七八九十]+、|\d+\.|([一二三四五六七八九十]+))\s*\S', re.M ) positions = [m.start() for m in pattern.finditer(text)] if not positions: return [text] if len(text) > 50 else [] chunks = [] for i, pos in enumerate(positions): end = positions[i + 1] if i + 1 < len(positions) else len(text) chunk = text[pos:end].strip() if len(chunk) > 20: chunks.append(chunk) return chunks chunks = split_by_heading(full_text) print(f"切分出 {len(chunks)} 个知识块,平均长度约 {sum(len(c) for c in chunks) // len(chunks)} 字符")切分逻辑有三个要点。一是正则里的 ^ 配合 re.M,按行首匹配标题,不会把正文里的“1.5%浓度”这类数字误判成新章节。二是最小长度过滤,20 字符以下的标题行会被丢掉。三是切分后每个 chunk 还要过一遍实体抽取,把标题里的杂草名抽出来,后续检索时既可以用向量相似度,又可以用名称字段做硬匹配。切分粒度直接影响召回质量,这也是整个方案里最吃经验的环节。我的习惯是切完随手抽 50 个 chunk 人工扫一遍,看看有没有把“酢浆草”和“红花酢浆草”切进同一段,这是最常见的切分错误。
3.3 向量化入库与数据质量检查:脏数据进库,检索必翻车
chunk 准备好之后就是向量化入库。用第 2 章 2.2 里的 embedding 模型对每个 chunk 编码,然后写入向量库。数据量在几千条量级,Chroma 或 FAISS 本地文件就能扛住;上了几万条,再考虑 Milvus 或 Qdrant。Chroma 的 upsert 接口比较顺手,把 id、document、embedding、metadata 一次写进去:
import chromadb client = chromadb.PersistentClient(path="./weed_kb") collection = client.get_or_create_collection( name="weed_species", metadata={"hnsw:space": "cosine"} # 向量距离用余弦 ) for idx, chunk in enumerate(chunks): entities = extract_weed_entities(chunk[:500]) # 只取前500字符做实体抽取,省token vec = model.encode([chunk], normalize_embeddings=True)[0] collection.upsert( ids=[f"chunk_{idx}"], documents=[chunk], embeddings=[vec.tolist()], metadatas=[{ "species": "、".join(entities.get("candidate_names", [])), "habitat": entities.get("habitat", "") }] )这里有个我自己踩过的坑:实体抽取不要对全量 chunk 做,先截断到 500 字符。因为知识条目本身可能上千字,全量丢给 LLM 抽取,单次 token 消耗大,且后面的防除措施、用药剂量这些内容会干扰名称字段的抽取。只抽开头部分,命中率反而更高。入库后的质量检查分两步:第一步抽 50 个 chunk 人工核验文本是否完整、是否串页;第二步做一轮“检索自检”,拿 20 条已知的杂草描述去查询,看召回列表里正确条目排第几位。这步不用写复杂脚本,向量库的 query 接口循环跑一遍,把 top5 结果打出来看一眼就行。这一步是整条链路里最便宜的后悔药,数据脏进去,后面所有检索和生成都会跟着脏。
4. 生态防除方案生成:让 DeepSeek 输出能直接执行的工作单
4.1 方案Prompt设计:识别结论、物理防除、生物防除、化学防除四段式
检索做完,拿到了 3 到 5 条知识条目,接下来的任务是把这些条目和用户描述一起交给 DeepSeek,生成生态防除方案。这里最关键的不是模型能力,而是输出约束。生态防除方案的严肃性在于:它涉及药剂推荐,一旦大模型编造一个不存在的药名,或推荐了禁用药,落地时是要出事故的。所以 prompt 里必须明确四件事:识别结论要有依据、防除措施按物理到化学的优先级排列、化学防除只允许引用资料里出现的药剂、不确定时必须承认不确定。
def generate_control_plan(query: str, retrieved_chunks: list) -> str: context = "\n".join( f"[资料{i+1}] {chunk}" for i, chunk in enumerate(retrieved_chunks) ) prompt = f"""你是一名农业园林杂草绿色防除专家。基于提供的知识资料,为用户描述生成生态防除方案。 要求: 1. 先给出杂草种类识别结论,列出判断依据(形态、生境、发生季节)。 2. 防除措施按优先级排列:物理防除 > 生物防除 > 化学防除。 3. 化学防除只能推荐资料中出现的药剂,标注用法用量和安全间隔期。 4. 资料不足以确定种类时,明确说"未确定",给保守的广谱建议,禁止编造。 5. 输出用Markdown小标题,不要输出资料原文。 用户描述:{query} 知识资料: {context} 输出格式: ## 识别结论 ## 发生规律 ## 生态防除方案 ### 物理防除 ### 生物防除 ### 化学防除 ## 注意事项""" resp = client.chat.completions.create( model="deepseek-chat", messages=[{"role": "user", "content": prompt}], temperature=0.3, max_tokens=1200 ) return resp.choices[0].message.content这段代码的核心参数是 temperature=0.3。比实体抽取的 0.1 高一点,因为方案生成需要一定的行文组织能力,但也不能高过 0.5,一高模型就开始自由发挥,出现过把“草甘膦异丙胺盐”写成“草甘膦异丙胺”这种丢字的情况。max_tokens 给 1200,一份带六个部分的方案基本够用。检索到的知识条目最多塞 4 到 5 条,多了上下文里信息互相干扰,生成质量反而下降。这里复用第 2 章的 client,模型名和密钥保持一致即可。
4.2 上下文组装:检索条目怎么拼才不会让模型“串味”
拼 context 也有讲究。我见过最糙的做法是把所有检索结果一股脑塞进去,结果模型把马唐的防除措施安到了酢浆草头上。正确做法是给每条资料标注来源序号,并按相似度降序排列,让最匹配的条目离 prompt 最近,模型对越靠后的内容记忆越浅,优先消费前面的。
还有个细节:如果同一个物种检索出两份资料,一份是形态描述,一份是防除措施,应该在组装时合并成一条“该物种完整档案”,而不是作为两个独立条目。这能减少模型在两条矛盾资料之间做出错误选择的可能性。组装好后,建议在发给模型之前先人工看一眼 context,这一步虽然原始,但我发现它能提前拦下八成检索质量问题。
4.3 输出兜底校验:白名单黑名单,别把命交给大模型的自觉
再强的 prompt 约束也防不住偶发的幻觉,所以要加一道输出后校验。黑名单放禁用药和高毒农药名,白名单放资料里出现过的合规药剂名。校验不复杂,用正则和集合操作就能做:
import re BANNED = {"甲胺磷", "对硫磷", "克百威", "涕灭威", "百草枯"} # 高毒/禁用农药示例 def validate_plan(plan: str, retrieved_chunks: list) -> list: errors = [] for name in BANNED: if name in plan: errors.append(f"出现禁用农药:{name}") # 从资料里收集带剂型的药剂名,作为本次生成的白名单 allowed = set() for chunk in retrieved_chunks: for match in re.findall(r"[\u4e00-\u9fa5]{2,6}(?:乳油|水剂|可湿性粉剂|颗粒剂)", chunk): allowed.add(match) # 提取方案里的药剂名做比对 candidates = re.findall(r"[\u4e00-\u9fa5]{2,6}(?:乳油|水剂|可湿性粉剂|颗粒剂)", plan) for c in candidates: if c not in allowed and c not in BANNED: errors.append(f"药剂'{c}'未在资料中出现,疑似编造") return errors errors = validate_plan(plan, retrieved_chunks) if errors: print("方案校验未通过,重新生成或人工介入:", errors)这段代码的实际使用方式是:校验未通过时不直接改文案,而是带着错误信息重新调用生成函数,并在 prompt 里追加一句“上次生成出现了禁用或编造药剂,请重新生成且只引用资料原文”。这种“校验+重生成”的循环,比单纯靠提示词管用。化学防除这块永远是安全红线,输出有疑问时宁可打回重来,也不要带着错误下发到养护现场。
注意:黑白名单校验只是最后一层保险。防除方案涉及药剂实际使用,正式交付前务必由有植保资质的人复核。
除了黑白名单,还要对整个方案做一致性校验。比如识别结论段落里写的是“马唐”,化学防除段落里推荐的却是针对阔叶杂草的药剂,这明显是串味了。一致性校验可以简单到用一个关键词集合:把识别结论里的物种名抽出来,检查化学防除段落里是否出现了“该物种”相关的药剂名。没出现就标记“存疑”。这一步在代码里就是一次字符串包含判断,但能拦下相当一部分低级错误。方案生成出去之前,我习惯把整份方案通读一遍再交付,大模型的东西越接近生产,人工复核越不能省。
5. 常见问题与避坑:实体抽取错漏、检索召回差、方案生成翻车
5.1 实体抽取把“马唐”抽成“马唐草”,检索直接漏掉
现象:candidate_names 输出“马唐草”,知识库里词条是“马唐”,向量检索虽然能沾边,但相似度会掉到阈值以下,正确条目排到第 8 位,直接被截断。
原因:大模型对指小后缀有很强的生成倾向,而且部分资料原文本身就是“马唐草”这种俗称,模型分不清哪个是标准名。
解决:建一个“俗名-标准名”归并表,对 candidate_names 里的每一项做归一化,规则是去掉“草”“菜”这类单字后缀再比对标准名;再给向量检索降阈值到 0.6,让这类同义表达能漏进来。更好的做法是在实体抽取的提示词里加一句“优先输出资料中的标准学名,别名放在候选列表里”,能显著减少问题发生频率。
5.2 top_k 取太大,方案生成时“串味”
现象:检索召回 8 条,里面混着马唐、稗草、狗尾草三条近缘禾本科杂草,DeepSeek 生成的方案里,防除措施在三个物种间跳来跳去。
原因:向量检索的 top_k 设置过大,近缘物种在向量空间里天然靠近,长尾召回的干扰条目全挤进了 context。
解决:top_k 调回 5,并且在组装 context 时做一个“物种去重”——如果多个条目都命中“禾本科”,只保留相似度最高的一条作为代表。这一步用 metadata 里的 species 字段按相似度分组,每组取最高分。实测这样处理后,生成方案的物种一致性有明显改观。
5.3 大模型编造“除草威”这种不存在的药名
现象:化学防除段落里出现了资料里从未出现过的药剂名称,看起来像是“除草剂”和某个常见药名的混搭。
原因:训练语料里这类词汇不少,模型在生成时按概率组合出了貌似合理的药名。这是 RAG 系统最典型的翻车点,因为是“看似合理但实际不存在”。
解决:第 4 章的白名单校验在这里派上用场。只允许输出出现在检索资料药剂字段中的名称,出现白名单之外的药名一律打回重生成。另外可以做一个“药剂名词典”抽出来单独建索引,生成前把检索到的药剂名列表一起塞进 prompt,效果比单纯靠校验更积极。
5.4 本地部署 DeepSeek 后推理延迟高,现场等不起
现象:想省 API 费用,用本地部署的 DeepSeek 模型跑实体抽取,每次调用要等十几秒,一线用户根本等不了。
原因:没用 vLLM 这类推理框架优化,或者模型量化等级太低,显存带宽跑不满。
解决:追求平稳体验就先用 DeepSeek API,按 token 计费对偶发查询更划算;一定要本地部署,就用 vLLM 加载 AWQ 或 GPTQ 量化模型,batch 推理吞吐量和延迟都会有数量级改善。另一个常见做法是:检索和实体抽取走轻量模型,只有最后的方案生成走 DeepSeek,把最贵的推理留到最后一步。这几个环节串起来,单次查询的总耗时能压到 5 秒以内,一线用户基本能接受。
注意:本地部署前先确认推理框架对量化格式的兼容性,vLLM 对 AWQ 支持较稳,GPTQ 需要看对应 CUDA 版本,否则启动阶段就会报错。
5.5 PDF 扫描版抽出来全是乱码,知识库直接废了
现象:抽样的 10 页里 7 页是扫描图,直接 get_text("text") 返回的都是空白或乱码,向量化出来的向量完全没有语义。
原因:资料 PDF 本身是扫描件,文字以图像形式存在,没有文本层。
解决:先走 OCR 管线。RapidOCR 部署轻、CPU 可跑,对印刷体的识别效果足够;识别出坐标后按坐标重组段落。OCR 参数上要注意把“方向分类”打开,扫描件有不少是横竖混排的表格页。OCR 过的文本一定要人工抽检,农业资料里的生僻字和多音字是 OCR 重灾区,一个“藨”字识别错,整个条目在语义检索里就查不到了。
5.6 实体抽取的 JSON 输出偶发字段缺失
现象:抽取出来的 JSON 少了一个字段,比如 season 拿不到,导致切分入库时 metadata 里没有季节信息,后续按季节筛选功能失效。
原因:response_format 强制 JSON 时,模型如果没理解字段定义,会省略它觉得无关的字段。
解决:在提示词里给每个字段一个示例值,比如 "season": "春夏季"。另外在解析 JSON 后做一次 schema 校验,缺字段就给默认空字符串,不要让它抛异常中断整个流水线。这是血泪经验:一次批量入库里混着 30% 的无 season 记录,按季节检索时全被过滤掉,排查了半天才发现是解析端缺了容错。
6. 进阶调优:评估指标体系与 RAG 检索质量的自检方法
系统上线前,先建一套三层的评估口径。实体抽取层抽 50 条描述人工标注字段,算字段级 F1,低于 0.85 就不要放量跑;检索层准备 20 组“描述-正确物种”对,算 Recall@5,看正确条目在不在 top5 里,低于 0.8 说明知识库切分或 embedding 选型有问题;方案生成层,让有植保背景的人对 20 份生成方案做“可执行性”打分,不要求文字漂亮,要求药剂名合规、用量有出处。这三层指标跑通一遍,再谈上线。
调优时有个实用技巧:用 DeepSeek 自己产数据标注样例。先人工标注 10 个杂草描述,把标注结果当作 few-shot 样例喂给模型,让它批量标注剩下的几百条,产出的标注再抽 50 条人工复核。这一招能大幅压低数据准备成本,比纯手工快一个量级。RAG 的检索质量自检,我习惯做法是写一个 bad case 复盘脚本:每星期把线上日志里的低分查询捞出来,人工看看是切分问题还是 embedding 问题,修复方式各不相同——切分问题改切分逻辑,embedding 问题换模型。
本地部署这块,如果团队有 GPU 资源,用 vLLM 跑 DeepSeek 的量化版,吞吐量比原生 transformers 高不少;没有 GPU 就老老实实调 API。我的习惯是:正式环境走 API,内部测试用本地量化版,两套都跑通,省得到部署的时候才想起 API 有配额限制。这套方案的瓶颈从来不在模型,而在知识库质量——539 页 PDF 切得干不干净、实体归一化做没做,决定了识别准不准。我的经验是给知识库建设留足时间,宁可检索慢一周,也不让脏数据进库。希望帮到你。
本文还有配套的精品资源,点击获取