fix(knowledge): reject irrelevant formal citations

This commit is contained in:
key
2026-07-31 01:02:48 +08:00
parent 218d6e38c9
commit ca61b1f36f
5 changed files with 90 additions and 6 deletions
@@ -28,6 +28,23 @@ public final class AihrFormalPolicyClassifier {
"车辆被占位", "车辆堵住", "停车冲突", "安全险情", "突发事件"
);
private static final List<EvidenceProfile> EVIDENCE_PROFILES = List.of(
evidence(List.of("日常巡检"), List.of("日常巡检", "巡检", "巡视", "巡查", "检查频次")),
evidence(List.of("强降雨", "防汛"), List.of("强降雨", "暴雨", "防汛", "汛期", "预警")),
evidence(List.of("大件垃圾", "装修垃圾", "装修产生"),
List.of("大件垃圾", "装修垃圾", "垃圾清运", "清运", "装修废料")),
evidence(List.of("报修受理"), List.of("报修", "维修", "回访")),
evidence(List.of("物业费账单", "计费期间", "费用构成"),
List.of("物业费", "物业服务费", "账单", "计费", "收费标准")),
evidence(List.of("分期缴费"), List.of("分期", "缓缴")),
evidence(List.of("长期无人居住", "空置房"), List.of("无人居住", "空置房", "空关房", "空房")),
evidence(List.of("维修一直没进展"), List.of("维修", "报修", "催办", "进展")),
evidence(List.of("欠费", "催费"), List.of("欠费", "催费", "物业费", "物业服务费")),
evidence(List.of("车辆被占位", "车辆堵住", "停车冲突"),
List.of("占用他人车位", "占位", "堵住", "车辆停放", "停车")),
evidence(List.of("安全险情", "突发事件"), List.of("险情", "突发事件", "应急", "上报"))
);
private AihrFormalPolicyClassifier() {
}
@@ -46,7 +63,33 @@ public final class AihrFormalPolicyClassifier {
return containsAny(value, FORMAL_TERMS) || containsAny(value, CONTROLLED_DOMAINS);
}
/**
* A formal source is not sufficient by itself: the cited fragment must also contain
* a controlled-domain anchor for the question. Unknown policy domains keep the
* governance-only behavior until a deterministic profile is defined.
*/
public static boolean isRelevantFormalEvidence(String question, String evidenceText) {
String query = question == null ? "" : question.trim();
String evidence = evidenceText == null ? "" : evidenceText.trim();
if (query.isEmpty() || evidence.isEmpty()) {
return false;
}
for (EvidenceProfile profile : EVIDENCE_PROFILES) {
if (containsAny(query, profile.questionSignals())) {
return containsAny(evidence, profile.evidenceSignals());
}
}
return true;
}
private static EvidenceProfile evidence(List<String> questionSignals, List<String> evidenceSignals) {
return new EvidenceProfile(questionSignals, evidenceSignals);
}
private static boolean containsAny(String value, List<String> terms) {
return terms.stream().anyMatch(value::contains);
}
private record EvidenceProfile(List<String> questionSignals, List<String> evidenceSignals) {
}
}
@@ -311,7 +311,8 @@ public class AihrKnowledgeQueryService {
SearchResponse legacy = sopService.searchAuthorized(new SearchRequest(
request.queryText(), request.category(), request.position(), retrievalSource, request.limit()),
principal == null ? "" : principal.extPartyId(), spaceIds);
List<Citation> citations = citations(app.tenantId(), spaceIds, legacy.snippets(), formalPolicyOnly);
List<Citation> citations = citations(
app.tenantId(), spaceIds, legacy.snippets(), formalPolicyOnly, request.queryText());
boolean noEvidence = citations.isEmpty();
String answer = formalPolicyOnly && noEvidence
? "当前已批准且在有效期内的正式资料中没有找到足够依据,不能使用访谈、经验材料或草稿补全政策答案。"
@@ -590,7 +591,7 @@ public class AihrKnowledgeQueryService {
}
private List<Citation> citations(String tenantId, Set<Long> allowedSpaceIds, List<SnippetResponse> snippets,
boolean formalPolicyOnly) {
boolean formalPolicyOnly, String queryText) {
if (snippets == null || snippets.isEmpty()) {
return List.of();
}
@@ -638,11 +639,20 @@ public class AihrKnowledgeQueryService {
formalSourceClause),
(rs, rowNum) -> new Citation(
rs.getString("space_code"), "DOCUMENT", rs.getString("doc_id"), rs.getString("title"),
truncate(rs.getString("content"), 500), rs.getLong("fragment_id")
rs.getString("content"), rs.getLong("fragment_id")
), args.toArray());
Map<Long, Citation> byId = new LinkedHashMap<>();
rows.forEach(row -> byId.put(row.fragmentId(), row));
return fragmentIds.stream().map(byId::get).filter(java.util.Objects::nonNull).toList();
return fragmentIds.stream()
.map(byId::get)
.filter(java.util.Objects::nonNull)
.filter(row -> !formalPolicyOnly
|| AihrFormalPolicyClassifier.isRelevantFormalEvidence(queryText, row.snippet()))
.map(row -> new Citation(
row.spaceCode(), row.sourceType(), row.docId(), row.title(),
truncate(row.snippet(), 500), row.fragmentId()
))
.toList();
}
private List<String> spaceCodes(String tenantId, Set<Long> spaceIds) {
@@ -2916,8 +2916,14 @@ public class AihrSopSeedService {
category, queryText, limitValue, allowedKnowledgeIds, formalPolicyOnly);
List<KnowledgeHit> textHits = rrf(keywordHits, fulltextHits(
category, queryText, limitValue, allowedKnowledgeIds, formalPolicyOnly), limitValue);
return rerankHits(queryText, rrf(textHits, vectorHits(
List<KnowledgeHit> ranked = rerankHits(queryText, rrf(textHits, vectorHits(
category, queryText, limitValue, allowedKnowledgeIds, formalPolicyOnly), limitValue));
if (!formalPolicyOnly) {
return ranked;
}
return ranked.stream()
.filter(hit -> AihrFormalPolicyClassifier.isRelevantFormalEvidence(queryText, hit.content()))
.toList();
}
private static String knowledgeScopeSql(String alias, Set<Long> allowedKnowledgeIds) {
@@ -47,4 +47,29 @@ class AihrFormalPolicyClassifierTest {
assertTrue(AihrFormalPolicyClassifier.requiresFormalSource(
"mobile_uni_agent_formal_policy", "继续说明"));
}
@Test
void formalEvidenceMustMatchTheControlledDomainQuestion() {
String installmentQuestion = "住户提出分期缴费时,申请条件、审批角色和留痕要求是什么?";
assertFalse(AihrFormalPolicyClassifier.isRelevantFormalEvidence(
installmentQuestion, "姿态端正,自然大方,工作手机应保持畅通。"));
assertFalse(AihrFormalPolicyClassifier.isRelevantFormalEvidence(
installmentQuestion, "节水型小区申报后,由相关部门组织评价。"));
assertFalse(AihrFormalPolicyClassifier.isRelevantFormalEvidence(
installmentQuestion, "车辆停放不得占用他人车位。"));
assertFalse(AihrFormalPolicyClassifier.isRelevantFormalEvidence(
installmentQuestion, "欠费住户由生活顾问定期提醒缴费。"));
assertTrue(AihrFormalPolicyClassifier.isRelevantFormalEvidence(
installmentQuestion, "申请分期缴费须由项目负责人审批,并保留审批记录。"));
assertTrue(AihrFormalPolicyClassifier.isRelevantFormalEvidence(
"装修产生的废料该找谁、怎么约清运、能不能收费?",
"装修垃圾应登记后预约清运,收费标准须公示。"));
assertTrue(AihrFormalPolicyClassifier.isRelevantFormalEvidence(
"车辆被占位或堵住时,物业怎么协调才不会升级冲突?",
"车辆停放不得占用他人车位,发现占位应先联系车主。"));
assertFalse(AihrFormalPolicyClassifier.isRelevantFormalEvidence(
installmentQuestion, " "));
}
}