194 lines
9.2 KiB
Go
194 lines
9.2 KiB
Go
package domain
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.cin.su/m/Assistant/contracts/identity"
|
|
"git.cin.su/m/Assistant/contracts/interaction"
|
|
"git.cin.su/m/Assistant/contracts/situation"
|
|
)
|
|
|
|
func TestInterpretUtteranceRecordsExplicitProjectFocus(t *testing.T) {
|
|
result := interpretForTest(t, "Сейчас работаем с УПО.", nil, "")
|
|
if result.ActiveFocus == nil || result.ActiveFocus.ProjectID != "upo" {
|
|
t.Fatalf("expected explicit UPO focus, got %#v", result.ActiveFocus)
|
|
}
|
|
if result.Interpretation.SpeechAct != interaction.SpeechActAssertion {
|
|
t.Fatalf("expected assertion, got %s", result.Interpretation.SpeechAct)
|
|
}
|
|
if result.Projects[0].Provenance != situation.ProjectResolutionExplicit {
|
|
t.Fatalf("expected explicit provenance, got %#v", result.Projects)
|
|
}
|
|
}
|
|
|
|
func TestInterpretUtteranceAssertionIsNotVerifiedFact(t *testing.T) {
|
|
result := interpretForTest(t, "На сервере сейчас свободно 300 ГБ.", nil, "")
|
|
if result.Interpretation.SpeechAct != interaction.SpeechActAssertion {
|
|
t.Fatalf("expected assertion, got %s", result.Interpretation.SpeechAct)
|
|
}
|
|
if result.Interpretation.AssertionVerification != "UNVERIFIED" || !strings.Contains(result.Reply, "не проверенный факт") {
|
|
t.Fatalf("expected unverified human assertion, got %#v reply=%q", result.Interpretation, result.Reply)
|
|
}
|
|
}
|
|
|
|
func TestInterpretUtteranceDirectiveRecordsOutcomeWithoutExecution(t *testing.T) {
|
|
result := interpretForTest(t, "Поменяй базу на УПО1.", activeFocus("upo", "УПО"), "")
|
|
if result.Interpretation.SpeechAct != interaction.SpeechActDirective {
|
|
t.Fatalf("expected directive, got %s", result.Interpretation.SpeechAct)
|
|
}
|
|
if result.Interpretation.RequestedOutcome == "" || !strings.Contains(result.Reply, "Ничего не выполнено") {
|
|
t.Fatalf("expected non-executed requested outcome, got %#v reply=%q", result.Interpretation, result.Reply)
|
|
}
|
|
}
|
|
|
|
func TestInterpretUtteranceCorrectionIsAppendOnlySupersedes(t *testing.T) {
|
|
result := interpretForTest(t, "Нет, используй УПО2.", activeFocus("upo", "УПО"), "utterance-previous")
|
|
if result.Interpretation.SpeechAct != interaction.SpeechActCorrection {
|
|
t.Fatalf("expected correction, got %s", result.Interpretation.SpeechAct)
|
|
}
|
|
if len(result.Relations) != 1 || result.Relations[0].Kind != interaction.RelationSupersedes || result.Relations[0].ToUtteranceID != "utterance-previous" {
|
|
t.Fatalf("expected supersedes relation, got %#v", result.Relations)
|
|
}
|
|
}
|
|
|
|
func TestInterpretUtteranceQuestionAboutCurrentWorkUsesFocus(t *testing.T) {
|
|
result := interpretForTest(t, "Что мы сейчас делаем?", activeFocus("assistant", "Assistant"), "")
|
|
if result.Interpretation.SpeechAct != interaction.SpeechActQuestion {
|
|
t.Fatalf("expected question, got %s", result.Interpretation.SpeechAct)
|
|
}
|
|
if !strings.Contains(result.Reply, "Assistant") {
|
|
t.Fatalf("expected focus-aware reply, got %q", result.Reply)
|
|
}
|
|
if result.NeedsCognition {
|
|
t.Fatalf("focus question should be answered from canonical focus without cognition")
|
|
}
|
|
}
|
|
|
|
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 {
|
|
t.Fatalf("expected inherited Assistant focus, got %#v", result.Projects)
|
|
}
|
|
}
|
|
|
|
func TestProjectFocusIsContextualEvidenceNotGlobalMode(t *testing.T) {
|
|
explicit := interpretForTest(t, "Сейчас работаем с УПО.", nil, "")
|
|
if explicit.ActiveFocus == nil || explicit.ActiveFocus.ProjectID != "upo" {
|
|
t.Fatalf("expected explicit UPO focus, got %#v", explicit.ActiveFocus)
|
|
}
|
|
focusedQuestion := interpretForTest(t, "Почему тормозит база?", activeFocus("upo", "УПО"), "")
|
|
if len(focusedQuestion.Projects) != 1 || focusedQuestion.Projects[0].ProjectID != "upo" || focusedQuestion.Projects[0].Provenance != situation.ProjectResolutionInheritedFocus {
|
|
t.Fatalf("database follow-up can use previous project as contextual evidence, got %#v", focusedQuestion.Projects)
|
|
}
|
|
unrelated := interpretForTest(t, "Помоги ответить на письмо бухгалтеру.", activeFocus("upo", "УПО"), "")
|
|
if len(unrelated.Projects) != 0 {
|
|
t.Fatalf("unrelated mail intent must not inherit UPO focus, got %#v", unrelated.Projects)
|
|
}
|
|
returned := interpretForTest(t, "Вернемся к УПО.", activeFocus("assistant", "Assistant"), "")
|
|
if returned.ActiveFocus == nil || returned.ActiveFocus.ProjectID != "upo" {
|
|
t.Fatalf("expected explicit return to UPO, got %#v", returned.ActiveFocus)
|
|
}
|
|
}
|
|
|
|
func TestInterpretUtteranceMultiProjectReferenceRoles(t *testing.T) {
|
|
result := interpretForTest(t, "Посмотри, как это сделано в Assistant, и сделай аналогично в УПО.", nil, "")
|
|
if len(result.Projects) != 2 {
|
|
t.Fatalf("expected two project references, got %#v", result.Projects)
|
|
}
|
|
roles := map[situation.ProjectID]interaction.ProjectReferenceRole{}
|
|
for _, ref := range result.Projects {
|
|
roles[ref.ProjectID] = ref.Role
|
|
}
|
|
if roles["assistant"] != interaction.ProjectRoleReferenceSource || roles["upo"] != interaction.ProjectRolePrimary {
|
|
t.Fatalf("unexpected roles: %#v", roles)
|
|
}
|
|
}
|
|
|
|
func TestInterpretUtteranceAmbiguousDestructiveRequiresClarification(t *testing.T) {
|
|
result := interpretForTest(t, "Удали туда старую базу.", activeFocus("assistant", "Assistant"), "")
|
|
if result.Interpretation.Clarification != interaction.ClarificationRequired {
|
|
t.Fatalf("expected clarification, got %#v", result.Interpretation)
|
|
}
|
|
if result.NeedsCognition {
|
|
t.Fatalf("ambiguous destructive directive must not require cognition")
|
|
}
|
|
}
|
|
|
|
func TestInterpretUtterancePunctuationFreeRussianQuestionStillRequestsBroaderInterpretation(t *testing.T) {
|
|
result := interpretForTest(t, "ты кто", nil, "")
|
|
if result.Interpretation.SpeechAct != interaction.SpeechActQuestion {
|
|
t.Fatalf("expected question, got %#v", result.Interpretation)
|
|
}
|
|
if !result.NeedsCognition {
|
|
t.Fatalf("expected cognition route for punctuation-free identity question, got %#v", result)
|
|
}
|
|
}
|
|
|
|
func TestInterpretUtterancePunctuationFreeArithmeticStillRequestsCognition(t *testing.T) {
|
|
result := interpretForTest(t, "сколько будет 2 + 2", nil, "")
|
|
if result.Interpretation.SpeechAct != interaction.SpeechActQuestion {
|
|
t.Fatalf("expected question, got %#v", result.Interpretation)
|
|
}
|
|
if !result.NeedsCognition {
|
|
t.Fatalf("expected cognition route for punctuation-free arithmetic, got %#v", result)
|
|
}
|
|
}
|
|
|
|
func TestInterpretUtteranceModalExplanationRequestDefersToCognition(t *testing.T) {
|
|
for _, message := range []string{"можешь расказать об утре", "расскажи об утре", "про утро расскажешь"} {
|
|
result := interpretForTest(t, message, nil, "")
|
|
if result.Interpretation.SpeechAct != interaction.SpeechActQuestion && result.Interpretation.SpeechAct != interaction.SpeechActRequestToInvestigate {
|
|
t.Fatalf("expected cognition-worthy interpretation for %q, got %#v", message, result.Interpretation)
|
|
}
|
|
if !result.NeedsCognition {
|
|
t.Fatalf("expected cognition route for %q, got %#v", message, result)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestInterpretUtteranceNamedModelRequestIsDirectiveNeedingCognition(t *testing.T) {
|
|
result := interpretForTest(t, "обратись к модели gemma4 и спроси сколько звезд на небе", nil, "")
|
|
if result.Interpretation.SpeechAct != interaction.SpeechActDirective {
|
|
t.Fatalf("expected directive, got %#v", result.Interpretation)
|
|
}
|
|
if !result.NeedsCognition {
|
|
t.Fatalf("expected cognition route for named-model request, got %#v", result)
|
|
}
|
|
}
|
|
|
|
func interpretForTest(t *testing.T, message string, previous *situation.ActiveProjectFocus, corrects interaction.HumanUtteranceID) SemanticResult {
|
|
t.Helper()
|
|
conversation := interaction.Conversation{ID: "conversation-1", ActorID: "actor-1", PrincipalID: "principal-1", Channel: interaction.ChannelWebChat, State: interaction.ConversationOpen}
|
|
utterance, _, _, err := NewHumanUtterance(conversation, interaction.ChatMessageRequest{Message: message, CorrectsUtteranceID: corrects}, time.Unix(100, 0))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
result, err := InterpretUtterance(utterance, projectRegistryForTest(), previous, time.Unix(100, 0))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return result
|
|
}
|
|
|
|
func projectRegistryForTest() []situation.ProjectDescriptor {
|
|
return []situation.ProjectDescriptor{
|
|
{ID: "assistant", DisplayName: "Assistant", Aliases: []situation.ProjectAlias{"assistant", "ассистент", "домовой"}},
|
|
{ID: "upo", DisplayName: "УПО", Aliases: []situation.ProjectAlias{"упо", "upo"}},
|
|
}
|
|
}
|
|
|
|
func activeFocus(projectID situation.ProjectID, displayName string) *situation.ActiveProjectFocus {
|
|
return &situation.ActiveProjectFocus{
|
|
ActorID: identity.ActorID("actor-1"),
|
|
PrincipalID: identity.PrincipalID("principal-1"),
|
|
ProjectID: projectID,
|
|
ProjectDisplayName: displayName,
|
|
Provenance: situation.ProjectResolutionExplicit,
|
|
ConfidenceMillis: 980,
|
|
SourceUtteranceID: "utterance-focus",
|
|
UpdatedAtUnixMillis: time.Unix(90, 0).UnixMilli(),
|
|
}
|
|
}
|