package domain import ( "errors" "strings" "time" "unicode/utf8" "git.cin.su/m/Assistant/contracts/identity" "git.cin.su/m/Assistant/contracts/interaction" "git.cin.su/m/Assistant/platform/ids" ) const MaxHumanUtteranceBytes = 16 << 10 var ( ErrEmptyMessage = errors.New("empty human message") ErrMessageTooLarge = errors.New("human message too large") ErrInvalidPrincipal = errors.New("invalid interaction principal") ) type Clock func() time.Time func NewConversation(actorID identity.ActorID, principalID identity.PrincipalID, now time.Time) (interaction.Conversation, error) { if actorID == "" || principalID == "" { return interaction.Conversation{}, ErrInvalidPrincipal } id, err := ids.New() if err != nil { return interaction.Conversation{}, err } millis := now.UnixMilli() return interaction.Conversation{ ID: interaction.ConversationID(id), ActorID: actorID, PrincipalID: principalID, Channel: interaction.ChannelWebChat, State: interaction.ConversationOpen, CreatedAtUnixMillis: millis, UpdatedAtUnixMillis: millis, }, nil } func NewHumanUtterance(conversation interaction.Conversation, request interaction.ChatMessageRequest, now time.Time) (interaction.HumanUtterance, interaction.OriginalHumanIntent, []interaction.InteractionRelation, error) { message := strings.TrimSpace(request.Message) if message == "" { return interaction.HumanUtterance{}, interaction.OriginalHumanIntent{}, nil, ErrEmptyMessage } if len(message) > MaxHumanUtteranceBytes || !utf8.ValidString(message) { return interaction.HumanUtterance{}, interaction.OriginalHumanIntent{}, nil, ErrMessageTooLarge } utteranceID, err := ids.New() if err != nil { return interaction.HumanUtterance{}, interaction.OriginalHumanIntent{}, nil, err } intentID, err := ids.New() if err != nil { return interaction.HumanUtterance{}, interaction.OriginalHumanIntent{}, nil, err } millis := now.UnixMilli() speechAct := ClassifySpeechAct(message, request.CorrectsUtteranceID) utterance := interaction.HumanUtterance{ ID: interaction.HumanUtteranceID(utteranceID), ConversationID: conversation.ID, ActorID: conversation.ActorID, PrincipalID: conversation.PrincipalID, Channel: interaction.ChannelWebChat, ReceivedAtUnixMillis: millis, CorrelationID: request.CorrelationID, Content: message, ContentRef: "interaction:utterance:" + string(utteranceID), SpeechAct: speechAct, InterpretationState: interaction.InterpretationInterpreted, CorrectsUtteranceID: request.CorrectsUtteranceID, } intent := interaction.OriginalHumanIntent{ ID: interaction.OriginalHumanIntentID(intentID), UtteranceID: utterance.ID, RecordedAtUnixMillis: millis, Text: message, } var relations []interaction.InteractionRelation if request.CorrectsUtteranceID != "" || speechAct == interaction.SpeechActCorrection { relationID, err := ids.New() if err != nil { return interaction.HumanUtterance{}, interaction.OriginalHumanIntent{}, nil, err } relations = append(relations, interaction.InteractionRelation{ ID: interaction.InteractionRelationID(relationID), FromUtteranceID: utterance.ID, ToUtteranceID: request.CorrectsUtteranceID, Kind: interaction.RelationCorrects, RecordedAtUnixMillis: millis, }) } return utterance, intent, relations, nil } func ClassifySpeechAct(message string, corrects interaction.HumanUtteranceID) interaction.SpeechAct { lower := strings.ToLower(strings.TrimSpace(message)) switch { case corrects != "" || strings.Contains(lower, "исправ") || strings.Contains(lower, "не так") || strings.HasPrefix(lower, "нет,"): return interaction.SpeechActCorrection case strings.Contains(lower, "?") || strings.HasPrefix(lower, "что ") || strings.HasPrefix(lower, "как ") || strings.HasPrefix(lower, "почему ") || strings.HasPrefix(lower, "кто ") || strings.Contains(lower, "объясни"): return interaction.SpeechActQuestion case strings.Contains(lower, "проверь") || strings.Contains(lower, "исслед") || strings.Contains(lower, "разбер"): return interaction.SpeechActRequestToInvestigate case strings.Contains(lower, "сделай") || strings.Contains(lower, "запусти") || strings.Contains(lower, "создай") || strings.Contains(lower, "настрой") || strings.Contains(lower, "добавь") || strings.Contains(lower, "измени") || strings.Contains(lower, "поменяй") || strings.Contains(lower, "используй") || strings.Contains(lower, "удали") || strings.Contains(lower, "удалить") || strings.Contains(lower, "напомни") || strings.Contains(lower, "подключи"): return interaction.SpeechActDirective case strings.HasPrefix(lower, "я решил") || strings.Contains(lower, "решение"): return interaction.SpeechActDecision case strings.Contains(lower, "предпочита") || strings.Contains(lower, "лучше"): return interaction.SpeechActPreference case strings.HasPrefix(lower, "думаю") || strings.Contains(lower, "гипотез"): return interaction.SpeechActHypothesis case lower == "да" || strings.HasPrefix(lower, "да,") || strings.Contains(lower, "подтверж"): return interaction.SpeechActConfirmation case lower == "нет" || strings.HasPrefix(lower, "нет,") || strings.Contains(lower, "отмен"): return interaction.SpeechActRejection case strings.Contains(lower, "привет") || strings.Contains(lower, "здравств"): return interaction.SpeechActConversational default: return interaction.SpeechActAssertion } } func NeedsCognition(act interaction.SpeechAct) bool { return act == interaction.SpeechActQuestion || act == interaction.SpeechActRequestToInvestigate } func FallbackText(act interaction.SpeechAct) string { switch act { case interaction.SpeechActDirective: return "Я понял действие как просьбу, но без отдельного Authority/Commit шага не выполняю мутацию." case interaction.SpeechActAssertion: return "Я записал это как утверждение человека. Это не считается проверенным фактом." case interaction.SpeechActCorrection: return "Исправление записано отдельной репликой; история не переписана." case interaction.SpeechActQuestion, interaction.SpeechActRequestToInvestigate: return "Когнитивный маршрут сейчас недоступен, но сообщение сохранено и может быть обработано позже." default: return "Сообщение записано." } }