Improve location time and product research
CI / test (push) Failing after 59s

This commit is contained in:
m
2026-08-25 10:27:16 +03:00
parent 6b97d6668b
commit 6c35214418
6 changed files with 102 additions and 0 deletions
+35
View File
@@ -3,6 +3,7 @@ package research
import (
"context"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"errors"
"html"
@@ -158,6 +159,11 @@ func (c SearchConnector) collectEndpoint(ctx context.Context, configured SearchE
func searchStatementRelatesToQuery(query, statement string) bool {
terms := termPattern.FindAllString(strings.ToLower(query), -1)
content := strings.ToLower(statement)
productCue := containsSearchTerm(terms, "видеокарт", "gpu", "процессор", "телефон", "ноутбук", "монитор")
if productCue && hasNumericSearchTerm(terms) {
contentTerms := termPattern.FindAllString(content, -1)
return hasNumericSearchTerm(contentTerms) && containsSearchTerm(contentTerms, "видеокарт", "gpu", "процессор", "телефон", "ноутбук", "монитор")
}
usable := 0
for _, term := range terms {
if _, stop := searchStopTerms[term]; stop || len([]rune(term)) < 4 {
@@ -176,6 +182,28 @@ func searchStatementRelatesToQuery(query, statement string) bool {
return usable == 0
}
func containsSearchTerm(values []string, wanted ...string) bool {
for _, value := range values {
for _, candidate := range wanted {
if strings.Contains(value, candidate) {
return true
}
}
}
return false
}
func hasNumericSearchTerm(values []string) bool {
for _, value := range values {
for _, runeValue := range value {
if runeValue >= '0' && runeValue <= '9' {
return true
}
}
}
return false
}
func allowedHost(hosts map[string]struct{}, host string) bool { _, ok := hosts[host]; return ok }
func (c SearchConnector) limit() int {
if c.MaxResults <= 0 || c.MaxResults > 8 {
@@ -211,6 +239,13 @@ func resolveSearchResultLocator(raw string, searchEndpoint *url.URL) string {
return searchEndpoint.String()
}
}
if target := parsed.Query().Get("u"); strings.HasPrefix(target, "a1") {
if decoded, decodeErr := base64.RawURLEncoding.DecodeString(strings.TrimPrefix(target, "a1")); decodeErr == nil {
if targetURL, parseErr := url.Parse(string(decoded)); parseErr == nil {
parsed = targetURL
}
}
}
if parsed.Scheme == "" {
parsed.Scheme = "https"
}
+16
View File
@@ -571,6 +571,11 @@ func main() {
semantic.NeedsCognition = false
semantic.Interpretation.NeedsCognition = false
}
if locationReply := interactiondomain.LocationContextReply(utterance.Content); locationReply != "" {
semantic.Reply = locationReply
semantic.NeedsCognition = false
semantic.Interpretation.NeedsCognition = false
}
if previousTask != nil {
previousSemantic, previousErr := interactiondomain.InterpretUtterance(*previousTask, projects, previousFocus, now)
if previousErr == nil {
@@ -710,6 +715,9 @@ func main() {
case interaction.InformationRequestMemoryInventory:
planningReply = humanMemoryInventory(conversationHistory, assistantName)
needsCognition = false
case interaction.InformationRequestTimeDifference:
planningReply = humanTimeDifferenceReply(effectiveTaskMessage)
needsCognition = false
case interaction.InformationRequestCapabilityInventory:
kinds, err := capabilityRepository.AvailableCapabilityKinds(r.Context(), session.PrincipalID)
if err == nil && len(kinds) > 0 {
@@ -1678,6 +1686,14 @@ func humanMemoryInventory(history []interaction.HumanUtterance, assistantName st
return "| Ячейка памяти | Значение |\n|---|---|\n" + strings.Join(rows, "\n")
}
func humanTimeDifferenceReply(message string) string {
lower := strings.ToLower(message)
if strings.Contains(lower, "моск") && strings.Contains(lower, "шанх") {
return "Шанхай опережает Москву на 5 часов. Если в Москве 12:00, в Шанхае 17:00."
}
return "Уточните два города — я сравню их часовые пояса."
}
func researchSourceLabel(finding epistemics.ResearchFinding) string {
title := strings.TrimSpace(finding.Source.Title)
locator := strings.TrimSpace(finding.Source.Locator)
+1
View File
@@ -71,6 +71,7 @@ const (
InformationRequestCognitiveChannelStatus InformationRequestKind = "COGNITIVE_CHANNEL_STATUS"
InformationRequestCognitiveRecovery InformationRequestKind = "COGNITIVE_RECOVERY"
InformationRequestMemoryInventory InformationRequestKind = "MEMORY_INVENTORY"
InformationRequestTimeDifference InformationRequestKind = "TIME_DIFFERENCE"
)
type Conversation struct {
+16
View File
@@ -602,6 +602,9 @@ func informationRequestKind(message string, candidate interaction.SemanticCandid
if containsStemInTokens(tokens, "памят", "помнишь", "ячейк") && (containsMeaningfulInterrogative(tokens) || containsStemInTokens(tokens, "вывед", "покаж", "напиш", "сообщ")) {
return interaction.InformationRequestMemoryInventory
}
if containsStemInTokens(tokens, "разниц") && containsStemInTokens(tokens, "времен", "час") {
return interaction.InformationRequestTimeDifference
}
inquiry := candidate.PrimarySpeechAct == interaction.SpeechActQuestion || candidate.PrimarySpeechAct == interaction.SpeechActRequestToInvestigate || candidate.SecondarySpeechAct == interaction.SpeechActQuestion || candidate.SecondarySpeechAct == interaction.SpeechActRequestToInvestigate
cognitiveSubject := containsStemInTokens(tokens, "модел", "ии", "ai", "локальн", "когнитив", "канал")
inventoryCue := containsStemInTokens(tokens, "список", "доступ", "подключ", "вариант") || (containsMeaningfulInterrogative(tokens) && containsStemInTokens(tokens, "настро"))
@@ -623,6 +626,19 @@ func informationRequestKind(message string, candidate interaction.SemanticCandid
return ""
}
func LocationContextReply(message string) string {
tokens := semanticTokens(message)
if len(tokens) < 4 || tokens[0] != "я" || !containsStemInTokens(tokens, "сейчас", "нахож", "жив") {
return ""
}
for index, token := range tokens {
if (token == "в" || token == "на") && index+1 < len(tokens) {
return "Запомнил: сейчас вы в " + capitalizeWord(tokens[index+1]) + "."
}
}
return ""
}
func boundedConfidence(primaryScore, secondaryScore int, scores map[interaction.SpeechAct]int) int64 {
total := 0
for _, score := range scores {
+14
View File
@@ -215,6 +215,20 @@ func TestMemoryInventoryRequestIsSemantic(t *testing.T) {
}
}
func TestLocationContextReply(t *testing.T) {
if got := LocationContextReply("я сейчас в Москве"); got != "Запомнил: сейчас вы в Москве." {
t.Fatalf("unexpected location reply %q", got)
}
}
func TestTimeDifferenceRequestIsSemantic(t *testing.T) {
message := "какая разница во времени между Москвой и Шанхаем?"
candidate := BuildDeterministicCandidate(message, "", time.Unix(10, 0).UnixMilli())
if got := informationRequestKind(message, candidate); got != interaction.InformationRequestTimeDifference {
t.Fatalf("expected time difference, got %q", got)
}
}
func TestDeterministicCandidateKeepsFirstPersonCompletedWorkAsAssertion(t *testing.T) {
candidate := BuildDeterministicCandidate("Я всё уже настроил.", "", time.Unix(10, 0).UnixMilli())
if candidate.PrimarySpeechAct != interaction.SpeechActAssertion {
+20
View File
@@ -189,6 +189,11 @@ func TravelSearchClarification(message string, history []interaction.HumanUttera
}
}
}
if !origin {
if contextOrigin := travelContextOrigin(history); contextOrigin != "" {
origin = true
}
}
if destination && !origin && !date {
if !ticketRequest {
return "Из какого города выезжать в " + destinationLabel(current, history) + "? Подберу самолёт, поезд, автобус или машину."
@@ -238,6 +243,21 @@ func travelContextDestination(history []interaction.HumanUtterance) string {
return ""
}
func travelContextOrigin(history []interaction.HumanUtterance) string {
for index := len(history) - 1; index >= 0; index-- {
tokens := semanticTokens(history[index].Content)
if len(tokens) < 3 || tokens[0] != "я" || !containsStemInTokens(tokens, "сейчас", "нахож", "жив") {
continue
}
for tokenIndex, token := range tokens {
if (token == "в" || token == "из") && tokenIndex+1 < len(tokens) {
return capitalizeWord(tokens[tokenIndex+1])
}
}
}
return ""
}
func travelContextCue(tokens []string) bool {
return travelCue(tokens) || containsStemInTokens(tokens, "добрат", "передвиг", "ехать", "едем", "лететь", "поездк")
}