1. 从一条假情报说起:AI幻觉为什么能骗过情报分析系统
2024年底到2025年初,圈子里流传过一个让人后背发凉的故事:某情报分析系统在整合多源信息时,因为大语言模型(LLM)产生幻觉,把一段虚构的“船只动向”当成了真实情报,差点触发一次海上拦截行动。虽然最终被人工复核拦下,但这件事在OSINT(开源情报)和SIGINT(信号情报)圈子里炸开了锅——大家突然意识到,AI幻觉不再是聊天机器人胡说八道那么简单,它已经能渗透进高 stakes 的决策链条。
我做了七八年数据分析和情报系统相关的工作,早期用传统NLP做实体抽取和关系图谱,近三年全面转向LLM和RAG(检索增强生成)架构。说实话,第一次听到这个案例时我并不意外。因为在我自己搭建的多个OSINT聚合系统里,LLM编造细节、混淆时间线、把不同来源的信息缝合在一起的情况,几乎每周都会遇到。区别只在于,我的系统后面有人工复核兜底,而那个案例里的系统,自动化程度太高了。
这篇文章不打算复述那个具体事件——涉及的信息太敏感,也没必要。我想做的是,把这个案例背后真正值得技术人关注的东西拆开:AI幻觉在情报类RAG系统中到底怎么产生的?为什么传统的“检索+生成”架构挡不住它?以及,如果你正在搭建类似的OSINT/SIGINT分析系统,有哪些实操层面的防御手段可以落地。
适合读这篇的人:正在做RAG项目、对LLM可靠性有要求的开发者;做OSINT聚合、舆情分析、威胁情报的技术负责人;以及任何想把LLM接入生产决策链路的工程师。我会尽量少讲空泛的“AI伦理”,多讲代码层面、架构层面、Prompt层面的具体做法。
2. 幻觉不是bug,是LLM的“出厂设置”
2.1 为什么LLM会“一本正经地胡说八道”
要理解AI幻觉,得先接受一个反直觉的事实:LLM的本质是概率续写机器,不是知识数据库。它做的事情,是根据上文预测下一个token最可能是什么。当你问它“某艘船现在在哪”,它并不是在数据库里查询,而是在计算“在‘某艘船现在在’这个上下文后面,哪个地点词出现的概率最高”。
这就解释了为什么幻觉往往出现在这些场景:
- 训练数据里没有的信息:模型没见过,但它不会说“我不知道”,而是根据语言模式编一个最像答案的答案。
- 时间敏感的信息:模型的知识有截止日期,问它最近的事件,它要么拒答,要么用旧信息拼凑。
- 需要精确数字的场景:坐标、吨位、航速、时间戳,这些一旦编错,后果比编错一个形容词严重得多。
我实测过一个典型的例子:给模型一段关于某海域船只活动的模糊描述,问它“这艘船的目的地是哪里”。模型会非常自信地给出一个具体港口名,甚至配上经纬度。但如果你追问“这个信息来自哪段原文”,它就开始含糊其辞。幻觉的可怕之处不在于错,而在于错得极其自信、极其具体。
2.2 情报场景下,幻觉的破坏力被放大了
普通聊天场景里,幻觉顶多让你得到一个错误答案。但在OSINT/SIGINT场景里,幻觉会沿着一条链路放大:
- 信息聚合阶段:LLM从多个来源抽取实体和事件,把A来源的船名和B来源的时间、C来源的地点缝合在一起,生成一条看似完整的新情报。
- 交叉验证阶段:如果系统用另一个LLM去“验证”这条情报,而验证模型又基于同样的幻觉逻辑,就会形成“幻觉共振”——两个模型互相确认一个不存在的事实。
- 决策输出阶段:这条被“验证”过的假情报进入分析报告,人类分析师看到的是结构清晰、来源标注完整的内容,很容易降低警惕。
那个险些引发拦截的案例,大概率就是这条链路走通了。单点幻觉不可怕,可怕的是系统架构让幻觉有了“合法身份”。
2.3 RAG不是万能药:检索增强的边界在哪
很多人以为上了RAG就能解决幻觉。RAG的思路确实对:先检索真实文档,再让LLM基于检索结果生成答案,相当于给模型开卷考试。但实操下来,RAG只能缓解幻觉,不能消除幻觉,原因有几个:
- 检索本身会失败:如果知识库里没有相关信息,检索器返回的是“最相似”的文档,而不是“正确”的文档。LLM拿到不相关的上下文,照样能编。
- LLM会忽略检索结果:当检索内容和模型内部知识冲突时,模型有时会选择相信自己“记住”的东西。这在需要精确匹配的场景里是致命的。
- 多跳推理会引入新幻觉:RAG系统经常需要多轮检索和推理,每一轮都可能引入偏差,误差累积。
我在一个船舶轨迹分析项目里做过统计:纯LLM抽取实体,准确率大概在70%左右;加上RAG后提升到85%;但剩下的15%错误里,有相当一部分是“检索到了正确文档,但LLM生成时篡改了细节”。RAG解决的是“有没有依据”的问题,没解决“依据用没用对”的问题。
3. 拆解情报类RAG系统的核心架构与风险点
3.1 一个典型的OSINT/SIGINT RAG流水线长什么样
先把这个案例背后可能的技术栈还原一下。一个完整的情报分析RAG系统,通常包含这几层:
| 层级 | 功能 | 常用技术 | 幻觉风险点 |
|---|---|---|---|
| 数据采集层 | 抓取公开信息、信号数据、卫星AIS等 | 爬虫、API对接、流处理 | 数据源本身不可靠 |
| 预处理层 | 清洗、去重、实体抽取 | 规则引擎、NER模型 | 抽取错误被下游放大 |
| 索引层 | 切块、向量化、建索引 | 嵌入模型、向量数据库 | 切块不当导致上下文丢失 |
| 检索层 | 语义检索、关键词检索、混合检索 | BM25、向量相似度、重排序 | 检索到不相关文档 |
| 生成层 | 基于检索结果生成答案 | LLM、Prompt工程 | 幻觉、篡改、缝合 |
| 验证层 | 事实核查、交叉验证 | 规则、另一个LLM、人工 | 验证模型本身有幻觉 |
那个出事的系统,问题很可能出在生成层和验证层的衔接上。生成层产出了一条缝合了多个来源的“新情报”,验证层没有能力识别这种缝合,反而因为格式规整、来源标注齐全而放行。
3.2 检索层:切块策略决定了幻觉的“原材料”质量
RAG系统的第一道防线是检索。但很多人把精力花在选向量数据库上,忽略了更基础的问题:文档怎么切块。
情报类文档有个特点:信息密度不均匀。一份船舶动态报告里,可能前80%是背景描述,后20%才是关键的时间、位置、航向。如果你按固定长度切块,关键信息可能被切散,或者和无关内容混在一起。
我试过几种切块策略,实测下来比较稳的是语义切块+重叠窗口:
# 基于语义相似度的切块示例(简化版) from langchain.text_splitter import RecursiveCharacterTextSplitter splitter = RecursiveCharacterTextSplitter( chunk_size=512, chunk_overlap=128, # 重叠128字符,防止关键信息被切断 separators=["\n\n", "\n", "。", ";", ",", " "], length_function=len, )但语义切块也不是银弹。对于结构化程度高的情报数据(比如AIS报文),更好的做法是按事件切块:一条船的一次位置更新作为一个chunk,保留时间戳、坐标、船名等元数据。这样检索时可以用元数据过滤,减少不相关文档的干扰。
注意:切块大小没有万能值。512 token适合大多数场景,但如果你的文档里关键信息很分散,可以试试256;如果文档逻辑连贯性强,1024也可以。关键是做A/B测试,看哪种切法在检索准确率上表现最好。
3.3 生成层:Prompt里必须写死的几条规则
生成层是幻觉的重灾区。我的经验是,不要指望模型“自觉”不编造,要在Prompt里用硬规则约束它。
以下是我在情报类RAG系统里常用的Prompt模板核心部分:
你是一个情报分析助手。你的任务是基于提供的【检索结果】回答问题。 规则: 1. 只使用【检索结果】中的信息,不得引入任何外部知识。 2. 如果【检索结果】中没有足够信息回答问题,直接回答“根据现有资料无法确认”。 3. 每个事实性陈述后面必须标注来源编号,格式为[来源X]。 4. 不得对信息进行推测、补全或缝合。如果不同来源信息冲突,分别列出并标注冲突。 5. 涉及时间、坐标、数量等精确信息时,必须原文引用,不得改写。 【检索结果】 {context} 【问题】 {question}这几条规则里,第3条和第4条最关键。要求标注来源,能让下游验证层快速定位信息出处;禁止缝合,能防止模型把不同来源的信息拼成一条假情报。
但Prompt不是万能的。我实测发现,即使写了“不得引入外部知识”,模型在遇到检索结果模糊时,还是有概率“脑补”。这时候需要输出格式约束来兜底。
3.4 验证层:用规则引擎给LLM上枷锁
验证层是最后一道防线。我的做法是规则引擎+LLM复核+人工抽检三层。
规则引擎负责硬性检查:
- 输出中的每个实体(船名、地名、时间)是否在检索结果中出现过?
- 如果出现,是否标注了正确的来源编号?
- 时间线是否自洽?比如“到达时间”不能早于“出发时间”。
- 坐标是否在合理范围内?
这些检查用简单的字符串匹配和正则就能做,成本低、速度快。任何一条不通过,直接打回生成层重新生成,或者标记为“待人工复核”。
LLM复核负责软性检查:用另一个模型(最好是不同厂商的)去判断“生成内容是否忠实于检索结果”。但这里有个坑:复核模型本身也可能有幻觉。所以复核结果只能作为参考,不能作为最终决策依据。
人工抽检是底线。对于高 stakes 的情报输出,必须保留人工复核环节。那个出事的案例,如果人工复核环节没有被绕过,大概率不会走到拦截那一步。
4. 实操:搭建一个抗幻觉的OSINT分析原型
4.1 环境准备与工具选型
这一节我带你搭一个最小可用的抗幻觉OSINT分析原型。技术栈选择如下:
- LLM:任何支持结构化输出的模型都可以,我用的是本地部署的开源模型,避免数据外泄。
- 向量数据库:Chroma或Qdrant,轻量、易部署。
- 编排框架:LangChain或LlamaIndex,我选LangChain,生态成熟。
- 嵌入模型:BGE-M3或类似的多语言模型,对中英文混合文档友好。
- 验证层:Python规则引擎+第二个LLM实例。
安装依赖:
pip install langchain langchain-community chromadb sentence-transformers pip install pydantic fastapi uvicorn提示:如果你的数据涉及敏感信息,强烈建议本地部署LLM和嵌入模型。API调用虽然方便,但数据出域的风险在情报场景里不可接受。
4.2 数据预处理:把非结构化情报变成可检索的块
假设你有一批OSINT报告,格式是纯文本或Markdown。预处理的目标是切成带元数据的chunk。
import hashlib from datetime import datetime from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain.schema import Document def preprocess_intel_report(raw_text: str, source_name: str, report_date: str): """ 将一份情报报告切块并附加元数据 """ splitter = RecursiveCharacterTextSplitter( chunk_size=512, chunk_overlap=128, separators=["\n\n", "\n", "。", ";", ",", " "], ) chunks = splitter.split_text(raw_text) documents = [] for i, chunk in enumerate(chunks): # 为每个chunk生成唯一ID chunk_id = hashlib.md5(f"{source_name}_{i}_{chunk[:50]}".encode()).hexdigest()[:12] doc = Document( page_content=chunk, metadata={ "source": source_name, "report_date": report_date, "chunk_index": i, "chunk_id": chunk_id, "ingested_at": datetime.now().isoformat(), } ) documents.append(doc) return documents这里的关键是元数据要足够丰富。source、report_date、chunk_id这三个字段,在后续检索和验证时都会用到。特别是chunk_id,它是追溯信息出处的唯一标识。
4.3 检索层实现:混合检索+重排序
单一向量检索在情报场景下不够用,因为很多关键信息是精确匹配(船名、编号、坐标)。我通常用BM25+向量检索+重排序的三段式。
from langchain.retrievers import BM25Retriever, EnsembleRetriever from langchain_community.vectorstores import Chroma from langchain_community.embeddings import HuggingFaceEmbeddings def build_hybrid_retriever(documents, persist_dir="./chroma_db"): """ 构建混合检索器:BM25 + 向量检索 """ # 向量检索 embeddings = HuggingFaceEmbeddings(model_name="BAAI/bge-m3") vectorstore = Chroma.from_documents( documents=documents, embedding=embeddings, persist_directory=persist_dir, ) vector_retriever = vectorstore.as_retriever( search_kwargs={"k": 10} ) # BM25检索 bm25_retriever = BM25Retriever.from_documents(documents) bm25_retriever.k = 10 # 集成检索器,权重可调 ensemble_retriever = EnsembleRetriever( retrievers=[bm25_retriever, vector_retriever], weights=[0.4, 0.6], # BM25权重稍低,向量权重稍高 ) return ensemble_retriever权重设置是个经验活。如果你的查询里经常包含精确的船名、编号,BM25权重可以调到0.5甚至更高;如果查询偏语义化,向量权重调高。我一般从0.4/0.6开始,根据实际效果微调。
检索到文档后,还需要重排序。重排序模型(如BGE-Reranker)能显著提升Top-K的准确率。这一步不能省,因为检索器返回的Top-10里,可能只有前3个是真正相关的。
4.4 生成层实现:带来源标注的受控生成
生成层的核心是强制模型标注来源。我用Pydantic定义输出结构,让模型按格式返回。
from pydantic import BaseModel, Field from typing import List, Optional from langchain.output_parsers import PydanticOutputParser class IntelStatement(BaseModel): """一条情报陈述""" content: str = Field(description="情报内容,必须忠实于检索结果") source_ids: List[str] = Field(description="来源chunk_id列表") confidence: str = Field(description="置信度:high/medium/low") conflicts: Optional[str] = Field( default=None, description="如果不同来源冲突,在此说明" ) class IntelReport(BaseModel): """完整的情报分析输出""" statements: List[IntelStatement] = Field(description="情报陈述列表") unable_to_confirm: List[str] = Field( default_factory=list, description="无法确认的问题列表" ) parser = PydanticOutputParser(pydantic_object=IntelReport) INTEL_PROMPT = """你是一个情报分析助手。基于以下检索结果回答问题。 {format_instructions} 规则: 1. 只使用检索结果中的信息,不得引入外部知识。 2. 每条陈述必须标注来源chunk_id。 3. 如果检索结果不足以回答问题,在unable_to_confirm中列出。 4. 不得推测、补全或缝合不同来源的信息。 5. 时间、坐标、数量等精确信息必须原文引用。 检索结果: {context} 问题:{question} """用Pydantic约束输出格式的好处是,解析失败可以直接触发重试。如果模型返回的JSON不符合结构,或者source_ids里出现了检索结果中不存在的chunk_id,系统可以自动打回重生成。
4.5 验证层实现:规则引擎兜底
验证层用纯Python实现,不依赖LLM,保证确定性。
import re from typing import List, Dict class IntelValidator: """情报输出验证器""" def __init__(self, retrieved_chunks: List[Dict]): # retrieved_chunks: [{"chunk_id": "...", "content": "..."}] self.chunk_map = {c["chunk_id"]: c["content"] for c in retrieved_chunks} self.all_content = " ".join(self.chunk_map.values()) def validate_source_ids(self, report) -> List[str]: """检查所有source_ids是否真实存在""" errors = [] for stmt in report.statements: for sid in stmt.source_ids: if sid not in self.chunk_map: errors.append(f"来源ID不存在: {sid}") return errors def validate_entities(self, report) -> List[str]: """检查陈述中的关键实体是否在来源中出现""" errors = [] # 提取船名、地名等实体(简化版,实际可用NER模型) entity_pattern = re.compile(r'[\u4e00-\u9fa5]{2,10}(?:号|舰|船|港|岛|礁)') for stmt in report.statements: entities = entity_pattern.findall(stmt.content) for entity in entities: if entity not in self.all_content: errors.append( f"实体'{entity}'未在检索结果中出现,疑似幻觉" ) return errors def validate_time_consistency(self, report) -> List[str]: """检查时间线是否自洽""" errors = [] # 提取时间戳(简化版) time_pattern = re.compile(r'\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}') for stmt in report.statements: times = time_pattern.findall(stmt.content) if len(times) >= 2: # 检查时间顺序是否合理 parsed = sorted(times) if parsed != times: # 这里只是示例,实际逻辑要根据业务规则 pass return errors def validate_all(self, report) -> Dict: """执行所有验证""" return { "source_id_errors": self.validate_source_ids(report), "entity_errors": self.validate_entities(report), "time_errors": self.validate_time_consistency(report), }这个验证器能拦住大部分低级幻觉:编造不存在的来源、引入检索结果里没有的实体、时间线矛盾。对于更隐蔽的“缝合式幻觉”(每个实体都真实存在,但组合方式是错的),规则引擎无能为力,需要人工复核或更复杂的交叉验证。
4.6 完整流程串联
把上面几层串起来:
def analyze_intel_query(query: str, retriever, llm, max_retries=3): """ 完整的情报分析流程 """ # 1. 检索 retrieved_docs = retriever.get_relevant_documents(query) retrieved_chunks = [ {"chunk_id": d.metadata["chunk_id"], "content": d.page_content} for d in retrieved_docs ] # 2. 构建上下文 context = "\n\n".join([ f"[来源{c['chunk_id']}]\n{c['content']}" for c in retrieved_chunks ]) # 3. 生成(带重试) for attempt in range(max_retries): try: prompt = INTEL_PROMPT.format( format_instructions=parser.get_format_instructions(), context=context, question=query, ) response = llm.invoke(prompt) report = parser.parse(response.content) # 4. 验证 validator = IntelValidator(retrieved_chunks) validation_result = validator.validate_all(report) has_errors = any(validation_result.values()) if not has_errors: return { "status": "success", "report": report, "validation": validation_result, } else: # 有错误,记录并重试 print(f"第{attempt+1}次验证失败: {validation_result}") except Exception as e: print(f"第{attempt+1}次生成失败: {e}") # 重试耗尽,标记为待人工复核 return { "status": "needs_human_review", "report": report if 'report' in locals() else None, "validation": validation_result if 'validation_result' in locals() else None, }这个流程的核心思想是:自动化能处理的自动处理,处理不了的明确标记出来,绝不强行输出。那个出事的系统,很可能就是缺少了“标记待复核”这一步,让有问题的输出直接流到了决策层。
5. 常见问题与排查技巧实录
5.1 检索到了正确文档,但LLM还是编,怎么办
这是最让人头疼的情况。检索结果明明包含正确答案,模型却生成了一个错误版本。我遇到过几次,排查下来通常是这几个原因:
原因一:上下文太长,关键信息被淹没。如果检索返回了10个chunk,每个512 token,总共5000+ token的上下文,模型很容易忽略中间部分的信息。解决办法是减少检索数量,提高精度。Top-3通常比Top-10效果好,前提是重排序做得好。
原因二:Prompt里的规则太多,模型顾此失彼。我试过在一个Prompt里塞十几条规则,结果模型反而更容易出错。后来精简到5条核心规则,效果明显提升。规则不在多,在于每条都能被模型理解和执行。
原因三:模型本身的能力边界。有些开源模型在长上下文和指令遵循上确实弱一些。如果条件允许,换一个在指令遵循上表现更好的模型,或者用更大的参数版本。
排查技巧:把检索结果和模型输出并排打印出来,逐句对比。如果模型输出里的某个事实在检索结果中找不到对应,那就是幻觉;如果找得到但被改写了,那就是指令遵循问题。
5.2 多轮对话场景下,幻觉会累积
RAG多轮对话有个隐蔽的坑:上一轮的幻觉会污染下一轮的上下文。比如第一轮模型编了一个错误的时间,第二轮用户追问“这个时间准确吗”,模型会基于自己编的时间继续编。
我的做法是每轮对话都重新检索,不把历史对话直接塞进上下文。历史对话只用来理解指代和意图,事实性内容一律从检索结果重新获取。
def multi_turn_query(history, current_query, retriever, llm): """ 多轮对话:历史只用于理解意图,事实从检索获取 """ # 用历史对话改写当前查询,补全指代 rewritten_query = rewrite_query_with_history(history, current_query) # 用改写后的查询重新检索 retrieved_docs = retriever.get_relevant_documents(rewritten_query) # 生成时只使用检索结果,不使用历史对话中的事实 # ...5.3 问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决思路 |
|---|---|---|---|
| 模型编造不存在的来源 | Prompt未强制标注来源 | 检查输出中的source_id | 用Pydantic约束输出结构 |
| 检索到正确文档但输出错误 | 上下文过长/规则过多 | 对比检索结果和输出 | 减少Top-K,精简Prompt |
| 多轮对话幻觉累积 | 历史对话污染上下文 | 检查每轮检索结果 | 每轮重新检索,历史只用于改写查询 |
| 时间/坐标被改写 | 模型“顺手”润色 | 精确匹配原文 | Prompt中强调原文引用 |
| 不同来源信息被缝合 | 模型自动“整合” | 检查来源标注 | 禁止缝合,冲突分别列出 |
| 验证模型也产生幻觉 | 验证模型与生成模型同源 | 换一个模型验证 | 规则引擎+人工复核兜底 |
5.4 几个踩过的坑
坑一:以为温度调低就能消除幻觉。温度调低确实能减少随机性,但不能消除幻觉。幻觉的根源是模型在“不知道”时选择编造,而不是随机性。我试过temperature=0,模型照样编。
坑二:以为换更大的模型就能解决。大模型在知识广度上更好,但在指令遵循和事实一致性上,不一定比小模型强。我实测过几个不同规模的模型,在“是否忠实于检索结果”这个指标上,差异没有想象中大。
坑三:忽略了嵌入模型的语言匹配。如果你的文档是中英文混合,嵌入模型必须支持多语言。用纯英文嵌入模型处理中文文档,检索准确率会大幅下降,间接导致幻觉增加。
坑四:没有做检索结果的去重。如果知识库里有重复内容,检索会返回多个相似chunk,浪费上下文窗口,还容易让模型混淆。预处理阶段一定要做去重。
6. 从架构层面降低幻觉风险:几个值得尝试的方向
6.1 Agentic RAG:让模型自己决定检索什么
传统RAG是“一次检索,一次生成”。Agentic RAG的思路是让模型自己判断:当前信息够不够?需不需要再检索?检索什么关键词?
这个思路在情报分析场景下特别有用,因为情报查询往往是多跳的。比如“某船最近一次停靠的港口所在国家最近有什么海事活动”,需要先查船的位置,再查港口,再查海事活动。传统RAG一次检索搞不定,Agentic RAG可以分步检索。
但Agentic RAG也引入了新风险:模型可能选择错误的检索路径,或者在多步推理中累积幻觉。我的建议是,在每一步都加验证,不要让模型“一口气”跑完多步。
6.2 知识图谱+RAG:用结构化约束非结构化
纯文本RAG的弱点在于,信息之间的关系是隐式的。知识图谱可以把实体和关系显式化,检索时不仅返回文本,还返回实体关系路径。
比如,从文本中抽取出“船A—停靠—港口B—位于—国家C”这样的三元组,存入图数据库。查询时,先用图查询找到相关实体,再用文本RAG补充细节。这样能大幅减少“缝合式幻觉”,因为实体关系是结构化存储的,不依赖模型生成。
6.3 多模型交叉验证:用不同模型互相“挑刺”
用一个模型生成,用另一个不同厂商、不同架构的模型验证。两个模型同时产生相同幻觉的概率,比单个模型低得多。
但要注意,验证模型不能和生成模型共享相同的检索结果和Prompt,否则会形成“幻觉共振”。验证模型应该独立检索、独立生成,然后对比两者的输出差异。
6.4 人在回路:高 stakes 场景的最终防线
不管技术多先进,高 stakes 的情报决策必须保留人工复核。那个出事的案例,如果人工复核环节没有被自动化流程绕过,大概率不会走到拦截那一步。
人工复核不一定要逐条检查,可以设计成异常触发式:系统自动标记低置信度、来源冲突、实体未匹配的输出,人工只复核这些异常项。这样既保证了安全,又不会让人工成为瓶颈。
7. 写在最后:几个我反复验证过的原则
做情报类RAG系统这几年,踩过的坑比写过的代码还多。如果只能记住几条原则,我会选这几条:
第一,永远不要相信LLM的“自信”。模型输出越具体、越流畅,越要警惕。具体和流畅不等于准确,在情报场景里,模糊但诚实比精确但编造有价值得多。
第二,验证层的成本远低于幻觉的代价。多写几百行验证代码,多跑几个规则检查,比起一条假情报引发的后果,成本可以忽略不计。
第三,自动化程度越高,人工复核越要保留。全自动流水线看起来很酷,但在高 stakes 场景里,一个“待人工复核”的标记,可能比一百行自动化代码更有价值。
第四,RAG是手段,不是目的。不要为了用RAG而用RAG。如果你的场景里精确匹配就能解决问题,用规则引擎可能比LLM更可靠。
最后分享一个我最近在试的小技巧:在Prompt里加一句“如果你不确定,请回答‘不确定’,这不会被视为失败”。实测下来,模型说“不确定”的频率明显提高,而编造的情况减少了。让模型知道“承认不知道”是被允许的,比逼它“必须回答”要安全得多。