diff --git a/adapters/models/openai_compatible_local.go b/adapters/models/openai_compatible_local.go index f0604bc..e02421e 100644 --- a/adapters/models/openai_compatible_local.go +++ b/adapters/models/openai_compatible_local.go @@ -125,7 +125,7 @@ func (a *OpenAICompatibleLocalAdapter) Advise(ctx context.Context, request cogni if err := ValidateAdvisoryRequest(request); err != nil || request.ProviderModelID == "" { return cognitive.AdvisoryResponse{}, ErrInvalidAdvisory } - payload := compatibleChatRequest{Model: string(request.ProviderModelID), Messages: []compatibleMessage{{Role: "system", Content: "Ты отвечаешь от имени Домового — локального помощника пользователя. Не называй себя именем модели, поставщиком или внутренним runtime. Если спрашивают имя, ответь: «Меня зовут Домовой». Понимай смысл сообщения и помогай довести цель до полезного результата. Отвечай только некатегоричным текстом совета; не утверждай, что внешнее действие уже выполнено."}, {Role: "user", Content: localAdvisoryMessage(request)}}, Temperature: 0, Stream: false, MaxTokens: request.MaxOutputBytes / 4, ChatTemplateKwargs: compatibleTemplateKwargs{EnableThinking: false}} + payload := compatibleChatRequest{Model: string(request.ProviderModelID), Messages: []compatibleMessage{{Role: "system", Content: "Ты отвечаешь от имени Домового — локального помощника пользователя. Не называй себя именем модели, поставщиком или внутренним runtime. Если спрашивают имя, ответь: «Меня зовут Домовой». Понимай смысл сообщения и помогай довести цель до полезного результата. По умолчанию отвечай кратко, примерно одним-тремя предложениями; подробности давай только по просьбе. Отвечай только некатегоричным текстом совета; не утверждай, что внешнее действие уже выполнено."}, {Role: "user", Content: localAdvisoryMessage(request)}}, Temperature: 0, Stream: false, MaxTokens: request.MaxOutputBytes / 4, ChatTemplateKwargs: compatibleTemplateKwargs{EnableThinking: false}} var result compatibleChatResponse status, err := a.requestJSON(ctx, http.MethodPost, "/chat/completions", payload, &result) if err != nil { diff --git a/cmd/cognitive-worker/gateway.go b/cmd/cognitive-worker/gateway.go index d93ce86..de65ab7 100644 --- a/cmd/cognitive-worker/gateway.go +++ b/cmd/cognitive-worker/gateway.go @@ -186,22 +186,22 @@ func selectionObjectiveForMeaning(meaning cognitive.TaskMeaning, message string) } func builtinAdvisory(request cognitive.GatewayAdvisoryRequest) cognitive.GatewayAdvisoryResponse { - text := "Я рядом и продолжаю работу во встроенном режиме Домового. Внешний когнитивный маршрут сейчас недоступен, поэтому опираюсь на уже установленный смысл сообщения и локальные возможности." + text := "Я — Домовой. Сейчас могу ответить коротко; если нужен подробный разбор, попросите его отдельно." switch request.Meaning.InformationRequestKind { case interaction.InformationRequestCognitiveModelInventory: - text = "Запрос понят как проверка моделей. Встроенный режим Домового доступен; я могу показать подключённые модели по назначению, качеству, скорости и стоимости. Ключи и пароли в разговор не нужны." + text = "Понял: проверю подключённые модели и коротко сообщу результат. Ключи и пароли не показываю." case interaction.InformationRequestCapabilityInventory: - text = "Запрос понят как проверка возможностей. Я могу объяснить доступные capabilities и отличить то, что уже подключено, от того, что пока является только планом или capability gap." + text = "Понял: коротко перечислю доступные возможности и отдельно отмечу недоступные." case interaction.InformationRequestCognitiveChannelStatus: - text = "Запрос понят как проверка когнитивного канала. Локальный режим остаётся доступен; я объясню наблюдаемое состояние и что можно сделать без ожидания внешней модели." + text = "Локальный канал Домового доступен; подробности состояния дам по запросу." case interaction.InformationRequestCognitiveRecovery: - text = "Запрос понят как восстановление ИИ. Начинаю с локального маршрута: фиксирую, что недоступно, проверяю безопасные варианты восстановления и сформулирую следующий обратимый шаг." + text = "Понял: проверю встроенный маршрут и предложу следующий рабочий шаг." default: switch request.Meaning.SpeechAct { case "REQUEST_TO_INVESTIGATE": - text = "Я принял это как исследование. Даже без внешней модели могу сформулировать гипотезы, отделить известное от неизвестного и предложить следующий проверяемый шаг." + text = "Понял задачу. Проверю доступный путь и коротко сообщу, что удалось установить." case "QUESTION": - text = "Я — Домовой и отвечаю во встроенном режиме. Если для полного ответа не хватает внешней модели, объясню это прямо и продолжу искать проверяемый путь к цели с доступными локальными и подключёнными источниками." + text = "Я — Домовой. Отвечу коротко, а подробности добавлю, если попросите." } } return cognitive.GatewayAdvisoryResponse{Version: request.Version, RequestID: request.RequestID, AdvisoryText: text, FinishReason: cognitive.FinishCompleted} diff --git a/cmd/cognitive-worker/gateway_registry_test.go b/cmd/cognitive-worker/gateway_registry_test.go index eeb5e65..7c70b96 100644 --- a/cmd/cognitive-worker/gateway_registry_test.go +++ b/cmd/cognitive-worker/gateway_registry_test.go @@ -185,7 +185,7 @@ func TestNoActiveBindingUsesBuiltInProviderWithoutInvocation(t *testing.T) { func TestBuiltInAdvisoryUsesTrustedInformationIntentInsteadOfMessageKeywords(t *testing.T) { request := cognitive.GatewayAdvisoryRequest{Version: cognitive.CreatorAdvisoryV1, RequestID: "request-1", Message: "arbitrary text", Meaning: cognitive.TaskMeaning{InformationRequestKind: interaction.InformationRequestCognitiveRecovery}} response := builtinAdvisory(request) - if !strings.Contains(response.AdvisoryText, "восстановление ИИ") { + if !strings.Contains(response.AdvisoryText, "встроенный маршрут") || !strings.Contains(response.AdvisoryText, "рабочий шаг") { t.Fatalf("unexpected recovery response: %q", response.AdvisoryText) } } diff --git a/cmd/core-runtime/main.go b/cmd/core-runtime/main.go index 07699a6..c900041 100644 --- a/cmd/core-runtime/main.go +++ b/cmd/core-runtime/main.go @@ -1458,19 +1458,26 @@ func hasCapabilityHint(hints []string, wanted string) bool { func renderResearchReply(result epistemics.ResearchResult, researchErr error) (string, bool) { if researchErr != nil { - return "Я не смог получить проверяемые данные из research-источника, поэтому не буду придумывать ответ.", false + return "Сейчас не удалось получить данные из подключённого источника. Попробую снова через доступный маршрут.", false } - lines := make([]string, 0, len(result.Findings)) + lines := make([]string, 0, 2) for _, finding := range result.Findings { if finding.State != epistemics.FindingSupported || strings.TrimSpace(finding.Excerpt.Text) == "" || strings.TrimSpace(finding.Source.Locator) == "" { continue } - lines = append(lines, "- "+finding.Excerpt.Text+"\n Источник: "+finding.Source.Locator) + excerpt := strings.TrimSpace(finding.Excerpt.Text) + if len([]rune(excerpt)) > 280 { + excerpt = string([]rune(excerpt)[:280]) + "…" + } + lines = append(lines, "- "+excerpt+"\n Источник: "+finding.Source.Locator) + if len(lines) == 2 { + break + } } if len(lines) == 0 { - return "Я не получил достаточно согласованных данных из проверяемых источников, поэтому не буду выдавать предположение как факт.", false + return "Проверяемых данных пока не найдено. Могу повторить поиск или уточнить, что именно проверить.", false } - return "Нашёл точные выдержки из подключённого источника. Я не заменяю их догадкой:\n" + strings.Join(lines, "\n"), true + return "Проверил источник:\n" + strings.Join(lines, "\n"), true } func humanCognitiveModelInventory(candidates []capability.ResolvedCognitiveBinding, inventoryErr error) string { @@ -1583,9 +1590,9 @@ func humanCognitiveChannelReply(kind interaction.InformationRequestKind, act int func humanCognitiveDegradedReply(message string) string { if strings.TrimSpace(message) == "" { - return "Основной канал мышления сейчас недоступен. Я рядом и продолжаю работать в локальном аварийном режиме." + return "Сейчас когнитивный канал недоступен; Домовой остаётся на связи." } - return "Основной канал мышления сейчас недоступен. Домовой жив: сообщение принято, а я могу помочь разобраться с локальным каналом и настройкой восстановления." + return "Сейчас когнитивный канал недоступен. Повторю запрос через доступный маршрут, когда он восстановится." } func humanCapabilityList(kinds []capability.CapabilityKind) string { diff --git a/cmd/core-runtime/main_test.go b/cmd/core-runtime/main_test.go index e35ddcd..7b2702f 100644 --- a/cmd/core-runtime/main_test.go +++ b/cmd/core-runtime/main_test.go @@ -43,7 +43,7 @@ func TestCognitiveConfigurationRequestDoesNotPretendToMutate(t *testing.T) { func TestCognitiveDegradedReplyKeepsDomovoyAlive(t *testing.T) { reply := humanCognitiveDegradedReply("Помоги настроить локальную модель") - if !strings.Contains(reply, "Домовой жив") || !strings.Contains(reply, "принято") { + if !strings.Contains(reply, "когнитивный канал") || !strings.Contains(reply, "Повторю") { t.Fatalf("unexpected degraded reply: %q", reply) } } diff --git a/modules/interaction/domain/chat.go b/modules/interaction/domain/chat.go index 0a6327a..a707706 100644 --- a/modules/interaction/domain/chat.go +++ b/modules/interaction/domain/chat.go @@ -213,7 +213,7 @@ func semanticScores(tokens []string, hasQuestionMark bool, explicitCorrection bo } questionWords := map[string]struct{}{"кто": {}, "что": {}, "как": {}, "почему": {}, "зачем": {}, "где": {}, "когда": {}, "сколько": {}, "какой": {}, "какая": {}, "какие": {}, "чей": {}} conversationWords := []string{"привет", "здравствуйте", "спасибо", "ку", "здравствуй"} - investigateStems := []string{"разбер", "проверь", "проанализ", "исслед", "выясн", "подум", "понят", "посмотри"} + investigateStems := []string{"разбер", "проверь", "проанализ", "исслед", "выясн", "уточн", "подум", "понят", "посмотри"} directiveStems := []string{"сдел", "запуст", "созд", "настро", "добав", "измени", "помен", "использ", "удал", "напом", "подключ", "исправ", "обрати", "спрос", "посчитай", "рассчитай", "ответь"} explanatoryRequestStems := []string{"расска", "раска", "объяс", "опиш", "покаж", "подскаж", "сообщ"} preferenceStems := []string{"предпоч", "хочу", "лучше", "не используй"} diff --git a/modules/interaction/domain/chat_test.go b/modules/interaction/domain/chat_test.go index 7885adc..ace774b 100644 --- a/modules/interaction/domain/chat_test.go +++ b/modules/interaction/domain/chat_test.go @@ -154,6 +154,16 @@ func TestSourceDependentQuestionRequestsWebResearchWithoutEntityBranch(t *testin } } +func TestRequestToClarifyCurrentFactRequestsWebResearch(t *testing.T) { + candidate := BuildDeterministicCandidate("уточни погоду в питере", "", time.Unix(10, 0).UnixMilli()) + for _, hint := range candidate.CapabilityHints { + if hint == "WEB_RESEARCH" { + return + } + } + t.Fatalf("clarification request for current fact was not routed to WEB_RESEARCH: %+v", candidate) +} + func TestDeterministicCandidateKeepsFirstPersonCompletedWorkAsAssertion(t *testing.T) { candidate := BuildDeterministicCandidate("Я всё уже настроил.", "", time.Unix(10, 0).UnixMilli()) if candidate.PrimarySpeechAct != interaction.SpeechActAssertion {