fix(knowledge): focus formal citation snippets

This commit is contained in:
key
2026-07-31 01:17:15 +08:00
parent ca61b1f36f
commit 72c56e7c96
4 changed files with 64 additions and 7 deletions
@@ -74,12 +74,50 @@ public final class AihrFormalPolicyClassifier {
if (query.isEmpty() || evidence.isEmpty()) {
return false;
}
for (EvidenceProfile profile : EVIDENCE_PROFILES) {
if (containsAny(query, profile.questionSignals())) {
return containsAny(evidence, profile.evidenceSignals());
}
EvidenceProfile profile = matchingProfile(query);
return profile == null || containsAny(evidence, profile.evidenceSignals());
}
/**
* Keep the visible citation centered on the same deterministic anchor used
* by the formal-evidence gate instead of returning an unrelated document prefix.
*/
public static String relevantEvidenceSnippet(String question, String evidenceText, int maxLength) {
String evidence = evidenceText == null ? "" : evidenceText.trim();
if (evidence.isEmpty() || maxLength <= 0) {
return "";
}
return true;
if (evidence.length() <= maxLength) {
return evidence;
}
EvidenceProfile profile = matchingProfile(question == null ? "" : question.trim());
if (profile == null || maxLength < 3) {
return evidence.substring(0, maxLength);
}
int anchorIndex = profile.evidenceSignals().stream()
.mapToInt(evidence::indexOf)
.filter(index -> index >= 0)
.min()
.orElse(-1);
if (anchorIndex < 0) {
return evidence.substring(0, maxLength);
}
int windowLength = maxLength - 2;
int start = Math.max(0, anchorIndex - Math.min(120, windowLength / 3));
int end = Math.min(evidence.length(), start + windowLength);
if (end == evidence.length()) {
start = Math.max(0, end - windowLength);
}
return (start > 0 ? "…" : "")
+ evidence.substring(start, end).trim()
+ (end < evidence.length() ? "…" : "");
}
private static EvidenceProfile matchingProfile(String question) {
return EVIDENCE_PROFILES.stream()
.filter(profile -> containsAny(question, profile.questionSignals()))
.findFirst()
.orElse(null);
}
private static EvidenceProfile evidence(List<String> questionSignals, List<String> evidenceSignals) {
@@ -650,7 +650,10 @@ public class AihrKnowledgeQueryService {
|| AihrFormalPolicyClassifier.isRelevantFormalEvidence(queryText, row.snippet()))
.map(row -> new Citation(
row.spaceCode(), row.sourceType(), row.docId(), row.title(),
truncate(row.snippet(), 500), row.fragmentId()
formalPolicyOnly
? AihrFormalPolicyClassifier.relevantEvidenceSnippet(queryText, row.snippet(), 500)
: truncate(row.snippet(), 500),
row.fragmentId()
))
.toList();
}
@@ -72,4 +72,20 @@ class AihrFormalPolicyClassifierTest {
assertFalse(AihrFormalPolicyClassifier.isRelevantFormalEvidence(
installmentQuestion, " "));
}
@Test
void formalCitationSnippetKeepsTheMatchedAnchorVisible() {
String question = "装修产生的废料该找谁、怎么约清运、能不能收费?";
String evidence = "无关前言。".repeat(120)
+ "装修垃圾应登记后预约清运,收费标准须公示。"
+ "无关附录。".repeat(120);
String snippet = AihrFormalPolicyClassifier.relevantEvidenceSnippet(question, evidence, 500);
assertTrue(snippet.length() <= 500);
assertTrue(snippet.contains("装修垃圾"));
assertTrue(snippet.contains("清运"));
assertTrue(snippet.startsWith("…"));
assertTrue(snippet.endsWith("…"));
}
}