feat(aihr): add evidence grading, clarification loop and route exits to ask agent

- narrow amount-semantics clarification to adjudicative intents only
- persist pending clarification on conversation and resolve follow-up
  replies as ANSWERED/GAVE_UP/NEW_TOPIC (max one round per field)
- add AihrEvidenceGradingService: FULL/PARTIAL/GUIDANCE/ROUTE arbitration,
  policy-claim post-validation, reference answers labeled as non-formal
- unblock retrieval source gating; tag citations with evidence tier,
  keep PUBLISHED/HUMAN_VERIFIED/effective-date authorization gates
- replace operator-facing no-evidence copy with actionable route exits
  (human help, direct finance channel, web research)
- allow fragment-less down feedback for own NO_EVIDENCE queries so
  ROUTE answers can escalate to human help (fragment_id nullable)
- migrations: aihr_20260804_pending_clarification_mysql8.sql,
  aihr_20260804_feedback_fragment_nullable_mysql8.sql
This commit is contained in:
2026-08-05 00:52:52 +08:00
parent 09b1085d9e
commit 6cc2ac73e5
21 changed files with 1260 additions and 96 deletions
@@ -0,0 +1,31 @@
-- Allow fragment-less answer feedback for zero-citation (NO_EVIDENCE) answers. Additive and
-- idempotent on MySQL 8: the unique key keeps its definition; NULL fragment rows are deduplicated
-- in application code because MySQL unique indexes do not deduplicate NULLs.
SET @aihr_feedback_table_exists := (
SELECT COUNT(*)
FROM information_schema.tables
WHERE table_schema = DATABASE()
AND table_name = 'aihr_knowledge_answer_feedback'
);
SET @aihr_feedback_fragment_nullable := (
SELECT COUNT(*)
FROM information_schema.columns
WHERE table_schema = DATABASE()
AND table_name = 'aihr_knowledge_answer_feedback'
AND column_name = 'fragment_id'
AND is_nullable = 'YES'
);
SET @aihr_feedback_fragment_ddl := IF(
@aihr_feedback_table_exists = 1 AND @aihr_feedback_fragment_nullable = 0,
'ALTER TABLE `aihr_knowledge_answer_feedback` MODIFY COLUMN `fragment_id` bigint DEFAULT NULL COMMENT ''知识片段ID;无依据回答的反馈为空''',
'SELECT 1'
);
PREPARE aihr_feedback_fragment_stmt FROM @aihr_feedback_fragment_ddl;
EXECUTE aihr_feedback_fragment_stmt;
DEALLOCATE PREPARE aihr_feedback_fragment_stmt;
SET @aihr_feedback_table_exists := NULL;
SET @aihr_feedback_fragment_nullable := NULL;
SET @aihr_feedback_fragment_ddl := NULL;
@@ -0,0 +1,30 @@
-- Pending clarification state for the knowledge Q&A clarification loop. Additive and idempotent on MySQL 8.
-- The conversation row keeps at most one suspended clarification; it is resolved (answered,
-- given up or replaced by a new topic) on the next turn and never survives the 30-minute expiry.
SET @aihr_pending_conversation_exists := (
SELECT COUNT(*)
FROM information_schema.tables
WHERE table_schema = DATABASE()
AND table_name = 'aihr_knowledge_conversation'
);
SET @aihr_pending_clarification_exists := (
SELECT COUNT(*)
FROM information_schema.columns
WHERE table_schema = DATABASE()
AND table_name = 'aihr_knowledge_conversation'
AND column_name = 'pending_clarification_json'
);
SET @aihr_pending_clarification_ddl := IF(
@aihr_pending_conversation_exists = 1 AND @aihr_pending_clarification_exists = 0,
'ALTER TABLE `aihr_knowledge_conversation` ADD COLUMN `pending_clarification_json` varchar(1000) DEFAULT NULL COMMENT ''挂起的澄清'' AFTER `state_json`',
'SELECT 1'
);
PREPARE aihr_pending_clarification_stmt FROM @aihr_pending_clarification_ddl;
EXECUTE aihr_pending_clarification_stmt;
DEALLOCATE PREPARE aihr_pending_clarification_stmt;
SET @aihr_pending_conversation_exists := NULL;
SET @aihr_pending_clarification_exists := NULL;
SET @aihr_pending_clarification_ddl := NULL;