From af8ec71bb836595d74ad8856ff69152b90d8badb Mon Sep 17 00:00:00 2001 From: mlogclub Date: Sat, 9 May 2026 23:38:42 +0800 Subject: [PATCH] fix: continue agent flow on knowledge retrieval errors --- .../ai/runtime/executor/answerability_gate.go | 2 +- .../executor/answerability_gate_test.go | 32 +++++++++++++++++-- .../ai/runtime/executor/knowledge_guard.go | 26 +++++++++++++++ 3 files changed, 56 insertions(+), 4 deletions(-) diff --git a/internal/ai/runtime/executor/answerability_gate.go b/internal/ai/runtime/executor/answerability_gate.go index 6cd97f4..f33812a 100644 --- a/internal/ai/runtime/executor/answerability_gate.go +++ b/internal/ai/runtime/executor/answerability_gate.go @@ -180,7 +180,7 @@ func (g *KnowledgeAnswerabilityGate) retrieveKnowledge(ctx context.Context, stat retrieveOptions.QueryPreview = preview(req.UserMessage.Content, 120) result, err := retriever.RetrieveContextByOptions(ctx, retrieveOptions, query) if err != nil { - state.FallbackReply = resolveKnowledgeHumanSupportFallback(req.AIAgent) + state.Decision = buildKnowledgeRetrievalErrorDecision(req.AIAgent, knowledgeIDs) state.ErrorMessage = err.Error() state.recordAnswerability(answerabilityStatusUnanswerable, "knowledge retrieval failed", err) return state, nil diff --git a/internal/ai/runtime/executor/answerability_gate_test.go b/internal/ai/runtime/executor/answerability_gate_test.go index 4b6a9cd..67ea1cd 100644 --- a/internal/ai/runtime/executor/answerability_gate_test.go +++ b/internal/ai/runtime/executor/answerability_gate_test.go @@ -248,7 +248,7 @@ func TestKnowledgePolicyEvaluateSkipsRuntimeActionIntent(t *testing.T) { } } -func TestKnowledgePolicyEvaluateFallsBackOnRetrieverError(t *testing.T) { +func TestKnowledgePolicyEvaluateInjectsRetrievalErrorInstructionWithoutFallback(t *testing.T) { collector := callbacks.NewRuntimeTraceCollector() gate := newTestKnowledgePolicyGate(&fakeKnowledgeContextRetriever{ knowledgeBaseIDs: []int64{1}, @@ -263,8 +263,14 @@ func TestKnowledgePolicyEvaluateFallsBackOnRetrieverError(t *testing.T) { t.Fatalf("Evaluate returned error: %v", err) } - if !strings.Contains(state.FallbackReply, "我暂时没有找到足够准确的信息") { - t.Fatalf("expected configured fallback on retrieval error, got %q", state.FallbackReply) + if state.FallbackReply != "" { + t.Fatalf("expected no direct fallback on retrieval error, got %q", state.FallbackReply) + } + if len(state.Decision.Instructions) != 1 { + t.Fatalf("expected one retrieval-error instruction, got %d", len(state.Decision.Instructions)) + } + if !strings.Contains(state.Decision.Instructions[0].Content, "知识库检索暂时不可用") { + t.Fatalf("unexpected retrieval-error instruction: %q", state.Decision.Instructions[0].Content) } if collector.Data.Answerability.Status != answerabilityStatusUnanswerable { t.Fatalf("unexpected status: %q", collector.Data.Answerability.Status) @@ -273,3 +279,23 @@ func TestKnowledgePolicyEvaluateFallsBackOnRetrieverError(t *testing.T) { t.Fatalf("unexpected reason: %q", collector.Data.Answerability.Reason) } } + +func TestBuildRunMessagesContinuesAgentFlowWhenRetrievalFails(t *testing.T) { + summary := &RunResult{} + gate := newTestKnowledgePolicyGate(&fakeKnowledgeContextRetriever{ + knowledgeBaseIDs: []int64{1}, + err: errors.New("vector store unavailable"), + }) + + messages := buildRunMessages(context.Background(), newKnowledgePolicyRunInput("你好", "1"), summary, nil, gate) + + if summary.ReplyText != "" { + t.Fatalf("expected no early fallback reply, got %q", summary.ReplyText) + } + if !messagesContainContent(messages, "知识库检索暂时不可用") { + t.Fatalf("expected retrieval-error instruction in messages: %#v", messages) + } + if !messagesContainContent(messages, "你好") { + t.Fatalf("expected current user message to remain in messages: %#v", messages) + } +} diff --git a/internal/ai/runtime/executor/knowledge_guard.go b/internal/ai/runtime/executor/knowledge_guard.go index 7be5472..654cc7c 100644 --- a/internal/ai/runtime/executor/knowledge_guard.go +++ b/internal/ai/runtime/executor/knowledge_guard.go @@ -53,6 +53,19 @@ func buildKnowledgeNoContextDecision(aiAgent models.AIAgent, knowledgeBaseIDs [] } } +func buildKnowledgeRetrievalErrorDecision(aiAgent models.AIAgent, knowledgeBaseIDs []int64) knowledgeGuardDecision { + if len(knowledgeBaseIDs) == 0 { + return knowledgeGuardDecision{} + } + instruction := buildKnowledgeRetrievalErrorInstruction(resolveKnowledgeFallbackReply(aiAgent)) + if instruction == "" { + return knowledgeGuardDecision{} + } + return knowledgeGuardDecision{ + Instructions: []*schema.Message{schema.SystemMessage(instruction)}, + } +} + func resolveKnowledgeFallbackReply(aiAgent models.AIAgent) string { if reply := strings.TrimSpace(aiAgent.FallbackMessage); reply != "" { return reply @@ -96,3 +109,16 @@ func buildKnowledgeNoContextInstruction(fallbackReply string) string { "3. 如果用户询问业务事实、规则、价格、流程、配置、时效、承诺、售后、退款、权限或政策,不得编造答案,必须明确回复:" + fallbackReply + "\n" + "4. 不得输出知识库未提供的具体事实、流程、承诺、价格、时效或政策。" } + +func buildKnowledgeRetrievalErrorInstruction(fallbackReply string) string { + fallbackReply = strings.TrimSpace(fallbackReply) + if fallbackReply == "" { + fallbackReply = "当前知识库暂无明确信息。" + } + return "知识库检索状态:知识库检索暂时不可用,当前没有可用的知识库资料。\n" + + "回复策略:\n" + + "1. 如果用户只是寒暄、问候、感谢、确认或结束语,可以自然、简短地回复,不要使用知识库兜底话术。\n" + + "2. 如果用户表达不清楚或缺少上下文,应追问具体场景、对象、报错信息或操作步骤。\n" + + "3. 如果用户询问业务事实、规则、价格、流程、配置、时效、承诺、售后、退款、权限或政策,不得编造答案,必须明确回复:" + fallbackReply + "\n" + + "4. 不得输出知识库未提供的具体事实、流程、承诺、价格、时效或政策。" +}