Route independent chat actions separately #319

Merged
m merged 2 commits from codex/chat-semantic-inventory into main 2026-08-25 02:43:11 +00:00
3 changed files with 48 additions and 7 deletions
+19 -7
View File
@@ -702,13 +702,25 @@ func main() {
researchBacked := false
if hasChatRoute(routePlan, chatRouteResearch) && semantic.Interpretation.Clarification != interaction.ClarificationRequired {
if planID, idErr := ids.New(); idErr == nil {
researchCtx, cancelResearch := context.WithTimeout(r.Context(), 8*time.Second)
result, researchErr := researchClient.SearchWeb(researchCtx, epistemics.WebSearchRequest{
Plan: epistemics.ResearchPlan{ID: epistemics.ResearchPlanID("plan:" + planID), Question: effectiveTaskMessage, SourceClasses: []string{"web-search"}, FreshnessPolicy: "current"},
PropositionKey: "question:" + string(utterance.ID), Query: effectiveTaskMessage,
})
cancelResearch()
planningReply, researchBacked = renderResearchReply(result, researchErr)
parts := interactiondomain.IndependentRequestParts(effectiveTaskMessage)
replies := make([]string, 0, len(parts))
for index, part := range parts {
researchCtx, cancelResearch := context.WithTimeout(r.Context(), 8*time.Second)
result, researchErr := researchClient.SearchWeb(researchCtx, epistemics.WebSearchRequest{
Plan: epistemics.ResearchPlan{ID: epistemics.ResearchPlanID(fmt.Sprintf("plan:%s:%d", planID, index)), Question: part, SourceClasses: []string{"web-search"}, FreshnessPolicy: "current"},
PropositionKey: fmt.Sprintf("question:%s:%d", utterance.ID, index), Query: part,
})
cancelResearch()
reply, backed := renderResearchReply(result, researchErr)
if backed {
researchBacked = true
}
if len(parts) > 1 {
reply = "По запросу «" + part + "»: " + reply
}
replies = append(replies, reply)
}
planningReply = strings.Join(replies, "\n\n")
if researchBacked {
semantic.Reply = planningReply
}
@@ -69,6 +69,13 @@ func TestStripAssistantVocative(t *testing.T) {
}
}
func TestIndependentRequestParts(t *testing.T) {
parts := IndependentRequestParts("Дед, узнай погоду в Сочи и найди ресторан")
if len(parts) != 2 || parts[0] != "узнай погоду в Сочи" || parts[1] != "найди ресторан" {
t.Fatalf("unexpected request parts: %#v", parts)
}
}
func TestTravelSearchClarification(t *testing.T) {
if got := TravelSearchClarification("Найди билеты", nil); got == "" {
t.Fatal("underspecified travel request must ask for missing route and date")
+22
View File
@@ -25,6 +25,28 @@ func StripAssistantVocative(message string) string {
return trimmed
}
// IndependentRequestParts keeps separate user actions visible to the
// orchestrator. It is intentionally limited to the ordinary coordinating
// conjunction; the cognitive route remains responsible for ambiguous prose.
func IndependentRequestParts(message string) []string {
cleaned := StripAssistantVocative(message)
parts := strings.Split(cleaned, " и ")
if len(parts) < 2 {
return []string{cleaned}
}
result := make([]string, 0, len(parts))
for _, part := range parts {
part = strings.TrimSpace(part)
if part != "" {
result = append(result, part)
}
}
if len(result) < 2 {
return []string{cleaned}
}
return result
}
// TravelSearchClarification keeps an incomplete travel request in dialogue
// instead of reducing it to an unrelated generic web search.
func TravelSearchClarification(message string, history []interaction.HumanUtterance) string {