diff --git a/cmd/core-runtime/cognitive_interpretation.go b/cmd/core-runtime/cognitive_interpretation.go index 90956b8..3b9a6a7 100644 --- a/cmd/core-runtime/cognitive_interpretation.go +++ b/cmd/core-runtime/cognitive_interpretation.go @@ -36,7 +36,7 @@ func (c cognitiveInterpretationClient) ReviewResponse(ctx context.Context, sessi if err != nil { return "", err } - prompt := "Ты редактор ответа Домового. Проверь черновик по смыслу и подготовь естественный ответ человеку. Учитывай весь запрос, включая недостающие параметры для конкретного результата. Если данных не хватает, задай один короткий полезный уточняющий вопрос, например откуда и куда ехать. Не выдавай поисковые сниппеты как выполненное действие и не показывай служебные URL поисковой выдачи. Сохрани проверяемые факты, названия источников, прямые ссылки и ограничения. Не выдумывай факты и не утверждай невыполненные действия. Верни только готовый ответ без разбора проверки, служебных слов и упоминания моделей. Если черновик уже адекватен, верни его без лишних добавлений.\n\nСообщение пользователя:\n" + userMessage + "\n\nЧерновик ответа:\n" + draft + prompt := "Ты редактор финального ответа Домового. Проверь черновик по смыслу и подготовь естественный ответ человеку. Учитывай весь запрос, включая недостающие параметры для конкретного результата. Если данных не хватает, задай один короткий полезный уточняющий вопрос, например откуда и куда ехать. Не выдавай поисковые сниппеты как выполненное действие и не показывай служебные URL поисковой выдачи. Сохрани проверяемые факты, названия источников, прямые ссылки и ограничения. Не выдумывай факты и не утверждай невыполненные действия. Сначала внутренне проверь, что ответ начинается с содержания, решающего вопрос. Представление, имя помощника, приветствие или предложение дополнительной помощи допустимы только если пользователь сам спросил об этом; иначе убери их. Верни только готовый ответ без разбора проверки, служебных слов и упоминания моделей. Если черновик уже адекватен, верни его без лишних добавлений.\n\nСообщение пользователя:\n" + userMessage + "\n\nЧерновик ответа:\n" + draft context := []cognitive.ContextExcerpt{} if strings.TrimSpace(personalMemory) != "" { context = append(context, cognitive.ContextExcerpt{ID: "personal-memory", SourceKind: "derived-personal-memory", SourceID: "interaction:human-utterances", Revision: 1, Sensitivity: cognitive.SensitivityPrivateAllowed, Text: personalMemory}) diff --git a/cmd/core-runtime/cognitive_interpretation_test.go b/cmd/core-runtime/cognitive_interpretation_test.go index db19f43..0706f0c 100644 --- a/cmd/core-runtime/cognitive_interpretation_test.go +++ b/cmd/core-runtime/cognitive_interpretation_test.go @@ -25,6 +25,9 @@ func TestReviewResponseUsesCognitiveGatewayAndReturnsOnlyDraftAnswer(t *testing. if request.Message == "" || request.Meaning.SpeechAct == "" { t.Fatalf("review request did not carry semantic context: %+v", request) } + if !strings.Contains(request.Message, "ответ начинается с содержания") || !strings.Contains(request.Message, "только если пользователь сам спросил") { + t.Fatalf("review request lacks the conversational quality contract: %q", request.Message) + } if len(request.Context) != 1 || !strings.Contains(request.Context[0].Text, "красный цвет") { t.Fatalf("review request did not carry compact personal memory: %+v", request.Context) } diff --git a/cmd/core-runtime/main.go b/cmd/core-runtime/main.go index 02a829e..8326e9b 100644 --- a/cmd/core-runtime/main.go +++ b/cmd/core-runtime/main.go @@ -799,6 +799,7 @@ func main() { } researchBacked := false researchAttempted := false + stableKnowledgeReply := false if hasChatRoute(routePlan, chatRouteResearch) && semantic.Interpretation.Clarification != interaction.ClarificationRequired && shouldRunGeneralResearch(semantic.InformationRequestKind) { researchAttempted = true if planID, idErr := ids.New(); idErr == nil { @@ -830,6 +831,7 @@ func main() { cancelAnswer() if answerErr == nil && stableKnowledgeAnswer(answer) { reply, backed = answer, true + stableKnowledgeReply = true } } cancelResearch() @@ -868,6 +870,7 @@ func main() { cancelAnswer() if answerErr == nil && stableKnowledgeAnswer(answer) { planningReply = answer + stableKnowledgeReply = true needsCognition = false } } @@ -995,7 +998,7 @@ func main() { stableSemanticReply := mediaAttempted || identityReply != "" || memoryInstructionReply != "" || creativeReply != "" || (creativeFallbackReply != "" && !cognitiveAvailable) || semantic.InformationRequestKind != "" || locationReply != "" || deterministicSpeechAct == interaction.SpeechActConversational || deterministicSpeechAct == interaction.SpeechActConfirmation || deterministicSpeechAct == interaction.SpeechActRejection if reviewErr != nil { log.Printf("cognitive response review unavailable: %v", reviewErr) - } else if reviewMayReplacePlanningReply(stableSemanticReply, researchAttempted, researchBacked, reviewedReply, planningReply) { + } else if reviewMayReplacePlanningReply(stableSemanticReply, stableKnowledgeReply, researchAttempted, researchBacked, reviewedReply, planningReply) { planningReply = reviewedReply } else if reviewedReply != planningReply { log.Printf("cognitive response review proposed stable semantic rewrite; preserving semantic reply") @@ -2012,7 +2015,14 @@ func reviewPreservesResearchEvidence(reviewed, draft string) bool { // reviewMayReplacePlanningReply keeps ownership of a concrete capability // result with that capability. A review may improve source-backed research, // but must not turn an honest no-result into an invented clarification. -func reviewMayReplacePlanningReply(stableSemanticReply, researchAttempted, researchBacked bool, reviewed, draft string) bool { +func reviewMayReplacePlanningReply(stableSemanticReply, stableKnowledgeReply, researchAttempted, researchBacked bool, reviewed, draft string) bool { + // A stable-knowledge answer is a model-produced draft rather than an owned + // capability result. It therefore goes through a second cognitive pass for + // conversational quality, while identity, memory, media and action outcomes + // remain immutable at this boundary. + if stableKnowledgeReply { + return true + } if stableSemanticReply { return false } diff --git a/cmd/core-runtime/main_test.go b/cmd/core-runtime/main_test.go index de31027..39632a2 100644 --- a/cmd/core-runtime/main_test.go +++ b/cmd/core-runtime/main_test.go @@ -102,18 +102,21 @@ func TestResearchIdentifiersExcludeSearchDate(t *testing.T) { } func TestResponseReviewPreservesAttemptedResearchOutcome(t *testing.T) { - if reviewMayReplacePlanningReply(false, true, false, "Уточните аэропорт.", "Пока не смог получить проверяемый ответ.") { + if reviewMayReplacePlanningReply(false, false, true, false, "Уточните аэропорт.", "Пока не смог получить проверяемый ответ.") { t.Fatal("review replaced an honest unbacked research outcome") } - if !reviewMayReplacePlanningReply(false, true, true, "Расписание. Источник: https://example.test", "Расписание. Источник: https://example.test") { + if !reviewMayReplacePlanningReply(false, false, true, true, "Расписание. Источник: https://example.test", "Расписание. Источник: https://example.test") { t.Fatal("review should be able to improve a source-backed research outcome") } - if reviewMayReplacePlanningReply(false, true, true, "Расписание без ссылки", "Расписание. Источник: https://example.test") { + if reviewMayReplacePlanningReply(false, false, true, true, "Расписание без ссылки", "Расписание. Источник: https://example.test") { t.Fatal("review dropped the research evidence") } - if !reviewMayReplacePlanningReply(false, false, false, "Нормальный ответ", "Черновик") { + if !reviewMayReplacePlanningReply(false, false, false, false, "Нормальный ответ", "Черновик") { t.Fatal("ordinary conversation review was unexpectedly blocked") } + if !reviewMayReplacePlanningReply(true, true, false, false, "Санкт-Петербург — город в России.", "Меня зовут Домовой. Санкт-Петербург — город в России.") { + t.Fatal("stable-knowledge draft must be eligible for semantic quality review") + } } func TestBearerTokenRequiresBearerScheme(t *testing.T) {