feat: wire answerability gate into ai runtime

This commit is contained in:
mlogclub
2026-05-02 14:21:49 +08:00
parent 0230e05dce
commit 8f968fe3ca
3 changed files with 79 additions and 27 deletions
@@ -186,6 +186,46 @@ func TestKnowledgeAnswerabilityGateEvaluateAllowsAnswerableDecisionAndProducesKn
}
}
func TestBuildRunMessagesReturnsFallbackWhenGateRejectsGrayZoneQuestion(t *testing.T) {
summary := &RunResult{}
question := "满足什么条件可以退款?"
gate := newTestKnowledgeAnswerabilityGate(newAnswerabilityRetrieverWithHit(), &fakeAnswerabilityChatModel{
response: `{"answerable": false, "reason": "retrieved snippets mention refunds but not the requested condition", "missingInfo": ["refund condition"]}`,
})
messages := buildRunMessages(context.Background(), newAnswerabilityGateRunInput(question, "1"), summary, nil, gate)
if !strings.Contains(summary.ReplyText, "建议你联系人工客服进一步确认。") {
t.Fatalf("expected human-support fallback, got %q", summary.ReplyText)
}
if messagesContainContent(messages, question) {
t.Fatalf("expected returned messages to omit current user message, got %#v", messages)
}
}
func TestBuildRunMessagesInjectsKnowledgeWhenGateAllows(t *testing.T) {
summary := &RunResult{}
question := "满足什么条件可以退款?"
gate := newTestKnowledgeAnswerabilityGate(newAnswerabilityRetrieverWithHit(), &fakeAnswerabilityChatModel{
response: `{"answerable": true, "reason": "refund condition is directly supported", "supportingChunkIds": ["101"]}`,
})
messages := buildRunMessages(context.Background(), newAnswerabilityGateRunInput(question, "1"), summary, nil, gate)
if summary.ReplyText != "" {
t.Fatalf("expected no fallback, got %q", summary.ReplyText)
}
if !messagesContainContent(messages, "知识库回答约束") {
t.Fatalf("expected knowledge instruction in messages, got %#v", messages)
}
if !messagesContainContent(messages, "购买后七天内且未使用可以退款。") {
t.Fatalf("expected knowledge context in messages, got %#v", messages)
}
if !messagesContainContent(messages, question) {
t.Fatalf("expected current user message in messages, got %#v", messages)
}
}
func newTestKnowledgeAnswerabilityGate(retriever knowledgeContextRetriever, chatModel model.BaseChatModel) *KnowledgeAnswerabilityGate {
return &KnowledgeAnswerabilityGate{
newRetriever: func(aiAgent models.AIAgent) knowledgeContextRetriever {
@@ -257,3 +297,12 @@ func (f *fakeAnswerabilityChatModel) Generate(ctx context.Context, input []*sche
func (f *fakeAnswerabilityChatModel) Stream(ctx context.Context, input []*schema.Message, opts ...model.Option) (*schema.StreamReader[*schema.Message], error) {
return nil, errors.New("stream is not implemented in fakeAnswerabilityChatModel")
}
func messagesContainContent(messages []*schema.Message, text string) bool {
for _, message := range messages {
if message != nil && strings.Contains(message.Content, text) {
return true
}
}
return false
}