Merge pull request 'Route conversational questions to local cognition' (#274) from codex/chat-semantic-inventory into main
CI / test (push) Successful in 55s

Merge PR #274: route conversational questions to local cognition
This commit was merged in pull request #274.
This commit is contained in:
m
2026-08-24 17:05:59 +00:00
3 changed files with 60 additions and 2 deletions
+46 -1
View File
@@ -158,7 +158,7 @@ func TestChatMessageDoesNotExposeUnverifiedQuestionAsFact(t *testing.T) {
_ = json.NewEncoder(w).Encode(identity.CreatorSession{ActorID: "actor-1", PrincipalID: "principal-1"})
case r.Method == http.MethodPost && r.URL.Path == "/v1/creator/chat/messages":
w.WriteHeader(http.StatusCreated)
_ = json.NewEncoder(w).Encode(interaction.ChatMessageResponse{ConversationID: "conversation-1", UtteranceID: "utterance-1", SpeechAct: interaction.SpeechActQuestion, NeedsCognition: true, FallbackText: "небезопасная модельная догадка"})
_ = json.NewEncoder(w).Encode(interaction.ChatMessageResponse{ConversationID: "conversation-1", UtteranceID: "utterance-1", SpeechAct: interaction.SpeechActQuestion, NeedsCognition: true, CapabilityHints: []capability.CapabilityKind{capability.CapabilityKindWebResearch}, FallbackText: "небезопасная модельная догадка"})
default:
t.Fatalf("unexpected core route %s %s", r.Method, r.URL.Path)
}
@@ -189,6 +189,51 @@ func TestChatMessageDoesNotExposeUnverifiedQuestionAsFact(t *testing.T) {
}
}
func TestChatMessageUsesLocalAdvisoryForOrdinaryConversationalQuestion(t *testing.T) {
core := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch {
case r.Method == http.MethodGet && r.URL.Path == "/v1/creator/sessions/current":
_ = json.NewEncoder(w).Encode(identity.CreatorSession{ActorID: "actor-1", PrincipalID: "principal-1"})
case r.Method == http.MethodPost && r.URL.Path == "/v1/creator/chat/messages":
w.WriteHeader(http.StatusCreated)
_ = json.NewEncoder(w).Encode(interaction.ChatMessageResponse{ConversationID: "conversation-1", UtteranceID: "utterance-1", SpeechAct: interaction.SpeechActQuestion, NeedsCognition: true, FallbackText: "fallback"})
default:
t.Fatalf("unexpected core route %s %s", r.Method, r.URL.Path)
}
}))
defer core.Close()
cognitiveRuntime := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/v1/advisories" {
t.Fatalf("unexpected cognitive path %s", r.URL.Path)
}
var request cognitive.GatewayAdvisoryRequest
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
t.Fatal(err)
}
_ = json.NewEncoder(w).Encode(cognitive.GatewayAdvisoryResponse{Version: cognitive.CreatorAdvisoryV1, RequestID: request.RequestID, AdvisoryText: "Я рядом. Давай поговорим.", FinishReason: cognitive.FinishCompleted})
}))
defer cognitiveRuntime.Close()
handler, err := newHandler(core.URL, "http://continuity", "http://commit", core.Client(), cognitiveRuntime.URL)
if err != nil {
t.Fatal(err)
}
request := httptest.NewRequest(http.MethodPost, "/api/chat/messages", strings.NewReader(`{"message":"Как ты?"}`))
request.Header.Set("Authorization", "Bearer session-token")
request.Header.Set("Content-Type", "application/json")
recorder := httptest.NewRecorder()
handler.ServeHTTP(recorder, request)
if recorder.Code != http.StatusOK {
t.Fatalf("expected status 200, got %d body=%s", recorder.Code, recorder.Body.String())
}
var response browserChatResponse
if err := json.NewDecoder(recorder.Body).Decode(&response); err != nil {
t.Fatal(err)
}
if response.ReplyText != "Я рядом. Давай поговорим." || response.Cognition != string(cognitive.FinishCompleted) {
t.Fatalf("unexpected conversational response: %#v", response)
}
}
func TestChatMessageReturnsHumanCapabilityMetadata(t *testing.T) {
core := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch {
+13 -1
View File
@@ -468,7 +468,7 @@ func (g *gateway) handleChatMessage(w http.ResponseWriter, r *http.Request) {
saved.NeedsCognition = false
}
}
if saved.SpeechAct == interaction.SpeechActQuestion && saved.InformationRequestKind == "" && !saved.ResearchBacked && !saved.WorkPlanned && saved.Clarification != interaction.ClarificationRequired && !asksAttention(browserRequest.Message) {
if requiresVerifiedQuestion(saved, browserRequest.Message) {
reply = truthfulVerificationRequiredReply()
cognition = "verification_required"
saved.NeedsCognition = false
@@ -514,6 +514,18 @@ func (g *gateway) handleChatMessage(w http.ResponseWriter, r *http.Request) {
})
}
func requiresVerifiedQuestion(saved interaction.ChatMessageResponse, message string) bool {
if saved.SpeechAct != interaction.SpeechActQuestion || saved.InformationRequestKind != "" || saved.ResearchBacked || saved.WorkPlanned || saved.Clarification == interaction.ClarificationRequired || asksAttention(message) {
return false
}
for _, hint := range saved.CapabilityHints {
if hint == capability.CapabilityKindWebResearch || hint == capability.CapabilityKindVerifyFact {
return true
}
}
return false
}
func truthfulVerificationRequiredReply() string {
return "Я не могу сейчас подтвердить этот факт по проверенному источнику, поэтому не буду придумывать ответ. Для проверки используйте официальный сайт соответствующей службы или надёжный сервис с текущими данными; когда подключён research-маршрут, Домовой сможет проверить это сам."
}
@@ -11,6 +11,7 @@
"paths": [
"cmd/cognitive-worker/**",
"cmd/core-runtime/**",
"cmd/creator-web/**",
"adapters/models/**",
"contracts/cognitive/**",
"modules/interaction/domain/**",