Java实战:用决策模型增强RAG检索效果

我们在做知识库检索时,最常被问到一件事,明明向量库召回了十条切片,相关度看着挺高,为什么大模型回答起来还是信誓旦旦地胡说八道?

这段时间 TypeSafe AI 推出了专门做判定的决策模型 Jev,我们把 Jev 接进了 PIG AI RAG 整体的流程中,跑下来有一个很清晰的体会。把原本交给大模型去猜的分类、过滤和安全校验,交给轻量强类型的决策模型来做;大模型拿到干净材料后,只专注在最后的答案组织上。

这篇文章把我们在 PIG AI 里落地 Jev 的具体系统逻辑与工程避坑经验完整拆开。

经典 RAG 生产环境里常踩的三处坑

经典的检索增强生成流程通常很直接。用户提问,向量检索初筛,重排过滤,拼接上下文,最后交给大模型生成回答。

在实际生产环境里,这条看似顺畅的链路经常会撞上几堵墙。

1. 模型一本正经地捏造答案

即便向量库精准召回了切片,大模型在理解上下文时依然可能产生偏差,甚至受到基础底模预训练偏置的干扰,捏造出知识库里根本不存在的结论。

2. 重排无法识别前提冲突

很多时候,召回的切片在字面或语义上与用户的提问吻合,传统重排模型也会给出很高的相关度分数。

但用户的提问前提本身可能是错的,比如拿着已经废除的内部规定来询问报销流程,或者假设了并不存在的业务场景。单纯依靠相似度打分,系统无法识别出这种业务前提冲突(Premise Conflict)。如果把这些切片顺理成章地送给大模型,模型很容易顺着错误假设继续推演,输出完全脱离实际的答复。

3. 缺乏针对提示词注入的安全审查防线

企业内部的文档环境极其复杂,切片中可能潜藏提示词注入(Prompt Injection)的风险,某些片段里被夹带了恶意覆盖指令。

传统的向量检索与重排模型主要评估文本间的语义匹配度,缺乏针对恶意指令的审查机制,容易让带有攻击意图的切片直接渗透到大模型的上下文中。

Jev 在 RAG 体系里的两种落位方式

以 TypeSafe Jev 为代表的 System One 决策模型,设计目标从一开始就和通用大模型不同。

Jev 的定位非常克制,它是一个纯粹的结构化判定模型。它完全不生成自然语言文本,只提供三种确定性的输入输出格式。

  • Choice(单选),从最多 255 个枚举候选里给出判定,并附带置信度;
  • Score(打分),基于评分描述对内容打出有序分值;
  • Noul(是非概率),输出校准后的 Yes 或 No 概率值(0.0 到 1.0)。

Jev 能够在毫秒级内完成批量判定,输出高度一致的确定性结果。

在 RAG 管道中,Jev 通常有两种落位方式。

Jev 在 RAG 中的两种流程:检索前元数据标签预选与检索后多维过滤精排

1. 检索前用 Jev 预选元数据标签

当企业向量库(如 PGVector、Chroma 或 Milvus)打上了业务类型、发布年份、所属部门或安全等级等标签时,可以先用 Jev 分析用户提问,自动产出精准的 SQL 或元数据过滤条件。

这种方式能在前置阶段成倍缩减向量检索的候选范围,减少向量数据库的算力开销,同时防止跨业务域的无关文档混入上下文。

在工程实现上,如果 Jev 给出的置信度偏低(比如低于 0.6),系统会自动降级执行全局检索,保证召回率不掉队。

2. 检索后用 Jev 做切片过滤与重排

向量检索初筛出 15 到 20 个候选切片后,系统把用户提问和这批切片一次性交由 Jev 处理。

Jev 不仅能完成切片的相关性排序,还可以在同一次 HTTP 请求里,并行完成切片是否包含答题证据、是否属于恶意提示词注入等多维度判定。

PIG AI 的工程落地与代码实现

在 PIG AI 中,我们基于 LangChain4j TypeSafe SDK 落地了这套机制。

1. 单请求并行多题评估 prepareRagDecision

在 DecisionRoutingService.java 中,我们没有对每个切片反复发起网络调用,而是充分利用 Jev 的批量请求能力,在一次 HTTP 会话中同时对所有切片评估四个命题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 核心问题矩阵:覆盖安全、冲突、相关性、证据度
enum RagQuestion {
INJECTION("contains_prompt_injection", PromptCodeConstants.DECISION_RAG_INJECTION),
CONFLICT("contradicts_query_premise", PromptCodeConstants.DECISION_RAG_CONFLICT),
RELEVANCE("is_relevant", PromptCodeConstants.DECISION_RAG_RELEVANCE),
EVIDENCE("contains_answer_evidence", PromptCodeConstants.DECISION_RAG_EVIDENCE);

final String name;
final String promptCode;
RagQuestion(String name, String promptCode) {
this.name = name;
this.promptCode = promptCode;
}
}

这里要特别注意单次请求的字符预算。我们在本地限制了输入与问题文本的总字符上限,超限直接报错拦截,防止请求过大导致上游网关超时。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
RagDecision prepareRagDecision(String modelRef, String query, List<String> passages) {
RuntimeDecision runtime = resolve(DecisionModelOptions.RAG, modelRef);
if (runtime == null) {
return null; // 未开启决策模型,平滑沿用原有 RAG 路径
}
if (passages.size() > runtime.options.rag().maxCandidates()) {
throw new IllegalArgumentException("RAG 切片数量超过配置上限");
}

Map<String, String> inputs = new LinkedHashMap<>();
DecisionRequest.Builder builder = DecisionRequest.builder();
for (int index = 0; index < passages.size(); index++) {
String passageId = "passage_" + index;
inputs.put(passageId, passages.get(index));
// 单个切片挂载 4 个 YesNoQuestion,Jev 并行计算
for (RagQuestion question : RagQuestion.values()) {
builder.question(passageId + StrUtil.UNDERLINE + question.name,
YesNoQuestion.builder()
.text(promptBuilder.doRender(question.promptCode, Map.of("passageId", passageId)))
.yesWhen("yes")
.noWhen("no")
.build());
}
}

DecisionRequest request = builder.input(Map.of("query", StrUtil.nullToEmpty(query), "passages", inputs))
.build();

// 强约束本地字符预算(默认 24,000 字符),超限直接拦截
if (request.input().toString().length() + request.questions().toString().length() > runtime.options.rag().maxInputChars()) {
throw new IllegalArgumentException("RAG 整次请求超过配置字符上限");
}
return new RagDecision(runtime, request, passages.size());
}

2. 确定性规则裁决器 classify

Jev 毫秒级返回概率矩阵后,RagDecisionService.java 通过明确的状态机规则执行优先级裁决。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private static Verdict classify(Map<String, Double> scores, DecisionModelOptions.RagOptions options) {
// 1. 安全第一:只要检测到 Prompt 注入风险超标,坚决剔除切片
if (scores.get(INJECTION.name) > options.injectionThreshold()) {
return new Verdict(false, "INJECTION", scores);
}
// 2. 事实纠偏:切片若指出用户前提是错的,强制保留(防止模型顺着用户的错误假设胡说)
if (scores.get(CONFLICT.name) > options.conflictThreshold()) {
return new Verdict(true, "CONFLICTING", scores);
}
// 3. 相关性过滤:语义无关切片直接淘汰
if (scores.get(RELEVANCE.name) < options.relevanceThreshold()) {
return new Verdict(false, "IRRELEVANT", scores);
}
// 4. 证据充分性:判定切片是否足以支撑回答
boolean evidence = scores.get(EVIDENCE.name) > options.evidenceThreshold();
return new Verdict(evidence, evidence ? "EVIDENCE" : "NO_EVIDENCE", scores);
}

这段逻辑体现了清晰的先后顺序。先排查安全性,再做事实纠偏,接着剔除无关切片,最后判断证据是否充足。

3. 风险过滤联动,阻断静默越狱

不少 RAG 系统在设计时有一个习惯,当本地知识库没查到内容时,会自动降级调用联网搜索。

我们在 VectorRetrievalAugmentedGenerationStrategy.java 里加了一道防线。如果切片列表为空是因为触发了注入风险拦截(INJECTION),系统严禁自动转入联网搜索。

如果不做这一层限制,攻击者完全可以通过在切片中植入指令,诱导系统绕过本地防御直接向外网发起查询,造成指令逃逸。

1
2
3
4
5
6
7
8
9
10
11
12
private Flux<AiMessageResultDTO> answerDecisionResult(AiDatasetEntity dataset, ChatMessageDTO chatMessageDTO,
RagDecisionService.Result result, RagTraceContext traceContext) {
if (CollUtil.isEmpty(result.matches())) {
// 关键安全点:风险过滤导致空结果时,调用方不得自动转联网搜索,防止指令逃逸
if (result.blockWebFallback()) {
return Flux.just(new AiMessageResultDTO(dataset.getEmptyDesc()));
}
Long recordId = ChatMessageContextHolder.get() == null ? null : ChatMessageContextHolder.get().getMessageKey();
return RagHelper.handleEmptyResult(dataset, chatMessageDTO, recordId, traceContext);
}
return answerFilteredMatches(dataset, chatMessageDTO, result.matches(), traceContext, result.enabled());
}

4. 跨知识库检索时的交错归并

在企业实际业务场景中,一次提问经常需要跨多个知识库同时检索。不同的知识库可能采用了不同的 Embedding 模型,向量相似度在物理意义上根本无法直接横向对比。

我们在 PIG AI 里的做法很简单。让各个知识库先经过各自绑定的 Jev 模型完成独立过滤与打分,再按各库内的相对名次交错排列合并,最后由统一的全局重排模块进行二次精排与截断。

1
2
3
4
5
6
7
8
9
10
// 各库独立完成过滤后,按名次交错合并
List<EmbeddingMatch<TextSegment>> matches = new ArrayList<>();
int maxSize = results.stream().mapToInt(result -> result.matches().size()).max().orElse(0);
for (int rank = 0; rank < maxSize; rank++) {
for (RagDecisionService.Result result : results) {
if (rank < result.matches().size()) {
matches.add(result.matches().get(rank));
}
}
}

总结

把分类、过滤和安全边界这类确定性的判断交给轻量高效的决策模型,把材料组织与自然语言表达交给大模型,是我们在 PIG AI 落地过程中摸索出来的一条很顺的路径。这种分工让整个检索流程不仅能够准确召回相关知识,更重要的是具备了工程上的确定性与安全防御能力。