From 36fec6c05cdf7aaf413ffe6b9437f07b03fc7b6b Mon Sep 17 00:00:00 2001 From: Mikhail Date: Tue, 18 Aug 2026 00:04:10 +0300 Subject: [PATCH] DEV-000072: classify identity naming preference as directive and avoid assertion --- ...ive_chat_dev072_acceptance_2026-08-17.json | 166 ++++++++++++++++ ...ion_2026-08-17_after_acceptance_scope.json | 184 ++++++++++++++++++ modules/interaction/domain/chat.go | 23 +++ modules/interaction/domain/chat_test.go | 19 ++ modules/interaction/domain/semantics.go | 6 +- modules/interaction/domain/semantics_test.go | 10 + 6 files changed, 407 insertions(+), 1 deletion(-) create mode 100644 docs/evidence/live_chat_dev072_acceptance_2026-08-17.json create mode 100644 docs/evidence/live_chat_dev072_production_2026-08-17_after_acceptance_scope.json diff --git a/docs/evidence/live_chat_dev072_acceptance_2026-08-17.json b/docs/evidence/live_chat_dev072_acceptance_2026-08-17.json new file mode 100644 index 0000000..da06888 --- /dev/null +++ b/docs/evidence/live_chat_dev072_acceptance_2026-08-17.json @@ -0,0 +1,166 @@ +[ + { + "id": "A", + "status": 200, + "message": "ты кто", + "conversation_id": "2ec5b5b9c3e742a7a3dce894020da7ca", + "utterance_id": "86fb129b40d2f2fbb4ab798d863bf35d", + "speech_act": "QUESTION", + "cognition": "COMPLETED", + "clarification": "NOT_REQUIRED", + "project_provenance": "", + "active_focus": "", + "capability_status": "", + "capability_kind": "", + "delegation_id": "", + "delegation_state": "", + "work_planned": false, + "reminder_scheduled": false, + "reply": "Я искусственный интеллект, созданный для помощи. Как я могу быть полезен сейчас?" + }, + { + "id": "B", + "status": 200, + "message": "сколько будет 2 + 2", + "conversation_id": "2ec5b5b9c3e742a7a3dce894020da7ca", + "utterance_id": "570663950030add34f71cedb24f4256d", + "speech_act": "QUESTION", + "cognition": "COMPLETED", + "clarification": "NOT_REQUIRED", + "project_provenance": "", + "active_focus": "", + "capability_status": "", + "capability_kind": "", + "delegation_id": "", + "delegation_state": "", + "work_planned": false, + "reminder_scheduled": false, + "reply": "2 + 2 equals 4." + }, + { + "id": "C", + "status": 200, + "message": "как с утра", + "conversation_id": "2ec5b5b9c3e742a7a3dce894020da7ca", + "utterance_id": "008192deff274c8ab7678c180ab7e04d", + "speech_act": "QUESTION", + "cognition": "COMPLETED", + "clarification": "NOT_REQUIRED", + "project_provenance": "", + "active_focus": "", + "capability_status": "", + "capability_kind": "", + "delegation_id": "", + "delegation_state": "", + "work_planned": false, + "reminder_scheduled": false, + "reply": "Утро - это прекрасное время для начала нового дня. Начните с здорового питания, которое поможет вам начать день с бодрости и энергии. Не забудьте о hydratation, выпив适量的水. Уделите время саморазвитию, например, чтению или медитации, чтобы зарядить позитивом весь день." + }, + { + "id": "D", + "status": 200, + "message": "проведи анализ: почему тормозит база, и потом добавь в задачу пометку для проверки", + "conversation_id": "2ec5b5b9c3e742a7a3dce894020da7ca", + "utterance_id": "4607460cd39d273d99ae50a7d8315517", + "speech_act": "QUESTION", + "cognition": "not_requested", + "clarification": "REQUIRED", + "project_provenance": "", + "active_focus": "", + "capability_status": "", + "capability_kind": "", + "delegation_id": "", + "delegation_state": "", + "work_planned": false, + "reminder_scheduled": false, + "reply": "У меня есть несколько возможных ожиданий. Уточните, о каком именно речь: дедлайн в пятницу; дедлайн в пятницу; дедлайн в пятницу" + }, + { + "id": "E", + "message": "исправь прошлую формулировку", + "status": 502, + "body": "chat unavailable\n" + }, + { + "id": "F", + "message": "обратись к модели gemma4 и спроси сколько звезд на небе", + "status": 502, + "body": "chat unavailable\n" + }, + { + "id": "G", + "status": 200, + "message": "просто напомни, что нужно проверить", + "conversation_id": "2ec5b5b9c3e742a7a3dce894020da7ca", + "utterance_id": "89b69b8251258a689f38de98552cbed4", + "speech_act": "QUESTION", + "cognition": "not_requested", + "clarification": "REQUIRED", + "project_provenance": "", + "active_focus": "", + "capability_status": "", + "capability_kind": "", + "delegation_id": "", + "delegation_state": "", + "work_planned": false, + "reminder_scheduled": false, + "reply": "Нужно короткое уточнение, прежде чем выбирать capability или строить более тяжёлый маршрут." + }, + { + "id": "H", + "status": 200, + "message": "Запомни тебя зовут Домовой, я тебя буду звать Домовым", + "conversation_id": "2ec5b5b9c3e742a7a3dce894020da7ca", + "utterance_id": "0c3bcf9b8f423e93b1b255aa33a557db", + "speech_act": "ASSERTION", + "cognition": "not_requested", + "clarification": "NOT_REQUIRED", + "project_provenance": "", + "active_focus": "", + "capability_status": "", + "capability_kind": "", + "delegation_id": "", + "delegation_state": "", + "work_planned": false, + "reminder_scheduled": false, + "reply": "Записал как утверждение человека. Это пока не проверенный факт." + }, + { + "id": "H2", + "status": 200, + "message": "для меня ты Домовой", + "conversation_id": "2ec5b5b9c3e742a7a3dce894020da7ca", + "utterance_id": "7847420f28c85894400c6a5be3e760d0", + "speech_act": "ASSERTION", + "cognition": "not_requested", + "clarification": "NOT_REQUIRED", + "project_provenance": "", + "active_focus": "", + "capability_status": "", + "capability_kind": "", + "delegation_id": "", + "delegation_state": "", + "work_planned": false, + "reminder_scheduled": false, + "reply": "Записал как утверждение человека. Это пока не проверенный факт." + }, + { + "id": "H3", + "status": 200, + "message": "давай я буду звать тебя Домовым", + "conversation_id": "2ec5b5b9c3e742a7a3dce894020da7ca", + "utterance_id": "085e4194a61cbfd097118553342e063a", + "speech_act": "ASSERTION", + "cognition": "not_requested", + "clarification": "NOT_REQUIRED", + "project_provenance": "", + "active_focus": "", + "capability_status": "", + "capability_kind": "", + "delegation_id": "", + "delegation_state": "", + "work_planned": false, + "reminder_scheduled": false, + "reply": "Записал как утверждение человека. Это пока не проверенный факт." + } +] diff --git a/docs/evidence/live_chat_dev072_production_2026-08-17_after_acceptance_scope.json b/docs/evidence/live_chat_dev072_production_2026-08-17_after_acceptance_scope.json new file mode 100644 index 0000000..de44b8d --- /dev/null +++ b/docs/evidence/live_chat_dev072_production_2026-08-17_after_acceptance_scope.json @@ -0,0 +1,184 @@ +[ + { + "id": "A", + "status": 200, + "message": "ты кто", + "conversation_id": "5e5ba4546dc20a4f6180d674a276fb5a", + "utterance_id": "7d14f020fc2134f0ba995c35ebe31523", + "speech_act": "QUESTION", + "cognition": "COMPLETED", + "clarification": "NOT_REQUIRED", + "reply": "Я искусственный интеллект, созданный для помощи. Как я могу быть полезен сейчас?", + "work": false, + "capability_status": "", + "capability_kind": "", + "delegation_state": "", + "project_provenance": "", + "active_focus": "" + }, + { + "id": "B", + "status": 200, + "message": "сколько будет 2 + 2", + "conversation_id": "5e5ba4546dc20a4f6180d674a276fb5a", + "utterance_id": "0a739b4810420e0eb0615d8d4cd4b60b", + "speech_act": "QUESTION", + "cognition": "COMPLETED", + "clarification": "NOT_REQUIRED", + "reply": "2 + 2 equals 4.", + "work": false, + "capability_status": "", + "capability_kind": "", + "delegation_state": "", + "project_provenance": "", + "active_focus": "" + }, + { + "id": "C", + "status": 200, + "message": "как с утра", + "conversation_id": "5e5ba4546dc20a4f6180d674a276fb5a", + "utterance_id": "4463c0cc31a552a23c0c0ba4eff06554", + "speech_act": "QUESTION", + "cognition": "COMPLETED", + "clarification": "NOT_REQUIRED", + "reply": "Утро - это прекрасное время для начала нового дня. Начните с здорового питания, которое поможет вам начать день с бодрости и энергии. Не забудьте о hydratation, выпив适量的水. Уделите время саморазвитию, например, чтению или медитации, чтобы зарядить позитивом весь день.", + "work": false, + "capability_status": "", + "capability_kind": "", + "delegation_state": "", + "project_provenance": "", + "active_focus": "" + }, + { + "id": "D", + "status": 200, + "message": "проведи анализ: почему тормозит база, и потом добавь в задачу пометку для проверки", + "conversation_id": "5e5ba4546dc20a4f6180d674a276fb5a", + "utterance_id": "c80c1eb2ce08edca3add568825c444d7", + "speech_act": "QUESTION", + "cognition": "not_requested", + "clarification": "REQUIRED", + "reply": "У меня есть несколько возможных ожиданий. Уточните, о каком именно речь: дедлайн в пятницу; дедлайн в пятницу; дедлайн в пятницу", + "work": false, + "capability_status": "", + "capability_kind": "", + "delegation_state": "", + "project_provenance": "EXPLICIT", + "active_focus": "УПО" + }, + { + "id": "E", + "status": 502, + "message": "исправь прошлую формулировку", + "error": "request_failed" + }, + { + "id": "F", + "status": 502, + "message": "обратись к модели gemma4 и спроси сколько звезд на небе", + "error": "request_failed" + }, + { + "id": "G", + "status": 200, + "message": "просто напомни, что нужно проверить", + "conversation_id": "5e5ba4546dc20a4f6180d674a276fb5a", + "utterance_id": "0519382bed09ae5ea9c9a3b2f2af2641", + "speech_act": "QUESTION", + "cognition": "not_requested", + "clarification": "REQUIRED", + "reply": "Нужно короткое уточнение, прежде чем выбирать capability или строить более тяжёлый маршрут.", + "work": false, + "capability_status": "", + "capability_kind": "", + "delegation_state": "", + "project_provenance": "", + "active_focus": "" + }, + { + "id": "H", + "status": 200, + "message": "Запомни тебя зовут Домовой", + "conversation_id": "5e5ba4546dc20a4f6180d674a276fb5a", + "utterance_id": "aae512074bf81adb405c0c80331c9096", + "speech_act": "ASSERTION", + "cognition": "not_requested", + "clarification": "NOT_REQUIRED", + "reply": "Записал как утверждение человека. Это пока не проверенный факт.", + "work": false, + "capability_status": "", + "capability_kind": "", + "delegation_state": "", + "project_provenance": "", + "active_focus": "" + }, + { + "id": "H2", + "status": 200, + "message": "для меня ты Домовой", + "conversation_id": "5e5ba4546dc20a4f6180d674a276fb5a", + "utterance_id": "78a171bb2f405e72f98f3b19d7422514", + "speech_act": "ASSERTION", + "cognition": "not_requested", + "clarification": "NOT_REQUIRED", + "reply": "Записал как утверждение человека. Это пока не проверенный факт.", + "work": false, + "capability_status": "", + "capability_kind": "", + "delegation_state": "", + "project_provenance": "", + "active_focus": "" + }, + { + "id": "H3", + "status": 200, + "message": "давай я буду звать тебя Домовым", + "conversation_id": "5e5ba4546dc20a4f6180d674a276fb5a", + "utterance_id": "382bd2310ed4a86f671f02c45afe3aee", + "speech_act": "ASSERTION", + "cognition": "not_requested", + "clarification": "NOT_REQUIRED", + "reply": "Записал как утверждение человека. Это пока не проверенный факт.", + "work": false, + "capability_status": "", + "capability_kind": "", + "delegation_state": "", + "project_provenance": "", + "active_focus": "" + }, + { + "id": "H4", + "status": 200, + "message": "Вернемся к УПО", + "conversation_id": "5e5ba4546dc20a4f6180d674a276fb5a", + "utterance_id": "8cb57dcf2599947673d1c1a4bb154266", + "speech_act": "ASSERTION", + "cognition": "not_requested", + "clarification": "NOT_REQUIRED", + "reply": "Фокус переключён: УПО.", + "work": false, + "capability_status": "", + "capability_kind": "", + "delegation_state": "", + "project_provenance": "EXPLICIT", + "active_focus": "УПО" + }, + { + "id": "I", + "status": 200, + "message": "помоги ответить на письмо бухгалтеру", + "conversation_id": "5e5ba4546dc20a4f6180d674a276fb5a", + "utterance_id": "ebeacce8340779490936e702b67543fa", + "speech_act": "ASSERTION", + "cognition": "not_requested", + "clarification": "NOT_REQUIRED", + "reply": "Сейчас у меня ещё не подключена возможность отправлять почту. Я зафиксировал capability gap, без ложного выполнения.", + "work": false, + "capability_status": "UNAVAILABLE", + "capability_kind": "SEND_EMAIL", + "delegation_state": "", + "project_provenance": "", + "active_focus": "" + } +] \ No newline at end of file diff --git a/modules/interaction/domain/chat.go b/modules/interaction/domain/chat.go index e0868ea..cab5295 100644 --- a/modules/interaction/domain/chat.go +++ b/modules/interaction/domain/chat.go @@ -125,6 +125,10 @@ func BuildDeterministicCandidate(message string, corrects interaction.HumanUtter tokens := semanticTokens(message) hasQuestionMark := strings.Contains(message, "?") scores := semanticScores(tokens, hasQuestionMark, corrects != "") + if isIdentityNamingPreferenceDirective(tokens) { + scores[interaction.SpeechActDirective] += 5 + scores[interaction.SpeechActPreference] += 4 + } primary, primaryScore := topAct(scores) secondary, secondaryScore := secondAct(scores, primary) if (startsWithModalRequest(tokens) || startsWithExplanatoryRequest(tokens, "расска", "раска", "объяс", "опиш", "покаж", "подскаж")) && @@ -170,6 +174,22 @@ func BuildDeterministicCandidate(message string, corrects interaction.HumanUtter return candidate } +func isIdentityNamingPreferenceDirective(tokens []string) bool { + if !containsToken(tokens, "ты") && !containsToken(tokens, "тебя") && !containsToken(tokens, "тебе") && !containsToken(tokens, "вам") { + return false + } + if containsStemInTokens(tokens, "запомни", "назови", "назвать", "назвав", "зови", "название", "называ", "обраща", "обращай", "обращайся", "звать", "зов", "зовут") { + return true + } + if containsStemInTokens(tokens, "домов", "assist", "ассист", "assistant", "assistant") { + return true + } + if containsStemInTokens(tokens, "буду", "будешь") && containsStemInTokens(tokens, "звать", "обращ", "называть") { + return true + } + return false +} + func semanticTokens(message string) []string { lower := strings.ToLower(strings.TrimSpace(message)) replacer := strings.NewReplacer(".", " ", ",", " ", "!", " ", "?", " ", ":", " ", ";", " ", "(", " ", ")", " ", "\"", " ", "'", " ", "«", " ", "»", " ", "+", " + ", "-", " - ", "/", " / ") @@ -369,6 +389,9 @@ func applicabilityFor(primary, secondary interaction.SpeechAct, primaryScore, se } func needsCognitionForCandidate(primary, secondary interaction.SpeechAct, tokens []string) bool { + if primary == interaction.SpeechActDirective && secondary == interaction.SpeechActPreference && isIdentityNamingPreferenceDirective(tokens) { + return false + } if primary == interaction.SpeechActQuestion || primary == interaction.SpeechActRequestToInvestigate { return true } diff --git a/modules/interaction/domain/chat_test.go b/modules/interaction/domain/chat_test.go index b1b5253..0178e45 100644 --- a/modules/interaction/domain/chat_test.go +++ b/modules/interaction/domain/chat_test.go @@ -93,6 +93,25 @@ func TestDeterministicCandidateTreatsModalExplanationRequestsAsCognitionWorthWit } } +func TestDeterministicCandidateClassifiesIdentityNamingPreferenceAsDirective(t *testing.T) { + for _, message := range []string{ + "Запомни тебя зовут Домовой, я тебя буду звать Домовым", + "я буду звать тебя Домовым", + "давай я буду звать тебя Домовой", + "теперь буду обращаться к тебе как к Домовой", + "запомни, я называю тебя Домовым", + "для меня ты Домовой", + } { + candidate := BuildDeterministicCandidate(message, "", time.Unix(10, 0).UnixMilli()) + if candidate.PrimarySpeechAct != interaction.SpeechActDirective { + t.Fatalf("expected directive for %q, got %+v", message, candidate) + } + if candidate.NeedsCognition { + t.Fatalf("expected no cognition for naming preference by default for %q, got %+v", message, candidate) + } + } +} + func TestDeterministicCandidateTreatsModelRequestAsDirectiveNeedingCognition(t *testing.T) { candidate := BuildDeterministicCandidate("обратись к модели gemma4 и спроси сколько звезд на небе", "", time.Unix(10, 0).UnixMilli()) if candidate.PrimarySpeechAct != interaction.SpeechActDirective { diff --git a/modules/interaction/domain/semantics.go b/modules/interaction/domain/semantics.go index 37cf4c7..4bd30bb 100644 --- a/modules/interaction/domain/semantics.go +++ b/modules/interaction/domain/semantics.go @@ -30,6 +30,10 @@ func InterpretUtterance(utterance interaction.HumanUtterance, projects []situati deterministicCandidate := BuildDeterministicCandidate(utterance.Content, utterance.CorrectsUtteranceID, millis) deterministicCandidate.UtteranceID = utterance.ID selectedCandidate := deterministicCandidate + assertionVerification := string(epistemics.VerificationNotApplicable) + if selectedCandidate.PrimarySpeechAct == interaction.SpeechActAssertion { + assertionVerification = string(epistemics.VerificationUnverified) + } result := SemanticResult{ Interpretation: interaction.UtteranceInterpretation{ ID: interaction.InterpretationID(interpretationID), @@ -38,7 +42,7 @@ func InterpretUtterance(utterance interaction.HumanUtterance, projects []situati SecondarySpeechAct: selectedCandidate.SecondarySpeechAct, ConfidenceMillis: selectedCandidate.ConfidenceMillis, Source: string(selectedCandidate.Source), - AssertionVerification: string(epistemics.VerificationUnverified), + AssertionVerification: assertionVerification, RequestedOutcome: selectedCandidate.RequestedOutcome, Clarification: selectedCandidate.Clarification, NeedsCognition: selectedCandidate.NeedsCognition, diff --git a/modules/interaction/domain/semantics_test.go b/modules/interaction/domain/semantics_test.go index ad8c2f1..7e8e1dc 100644 --- a/modules/interaction/domain/semantics_test.go +++ b/modules/interaction/domain/semantics_test.go @@ -66,6 +66,16 @@ func TestInterpretUtteranceQuestionAboutCurrentWorkUsesFocus(t *testing.T) { } } +func TestInterpretUtteranceIdentityNamingPreferenceNotAssertion(t *testing.T) { + result := interpretForTest(t, "Запомни тебя зовут Домовой, я тебя буду звать Домовым", nil, "") + if result.Interpretation.SpeechAct != interaction.SpeechActDirective { + t.Fatalf("expected directive for naming preference, got %s", result.Interpretation.SpeechAct) + } + if result.Interpretation.AssertionVerification == "UNVERIFIED" { + t.Fatalf("expected no assertion semantics path for naming preference, got %#v", result.Interpretation) + } +} + func TestInterpretUtteranceInheritedFocusForThere(t *testing.T) { result := interpretForTest(t, "Добавь туда новую настройку.", activeFocus("assistant", "Assistant"), "") if len(result.Projects) != 1 || result.Projects[0].ProjectID != "assistant" || result.Projects[0].Provenance != situation.ProjectResolutionInheritedFocus {