package main import ( "bytes" "context" "encoding/json" "fmt" "net/http" "net/url" "git.cin.su/m/Assistant/contracts/authority" "git.cin.su/m/Assistant/contracts/capability" "git.cin.su/m/Assistant/contracts/cognitive" "git.cin.su/m/Assistant/contracts/identity" "git.cin.su/m/Assistant/contracts/interaction" interactiondomain "git.cin.su/m/Assistant/modules/interaction/domain" "git.cin.su/m/Assistant/platform/ids" ) type cognitiveInterpretationClient struct { baseURL *url.URL client *http.Client } func newCognitiveInterpretationClient(rawURL string, client *http.Client) (cognitiveInterpretationClient, error) { base, err := url.Parse(rawURL) if err != nil || base.Scheme == "" || base.Host == "" { return cognitiveInterpretationClient{}, fmt.Errorf("invalid Cognitive Runtime URL") } if client == nil { client = http.DefaultClient } return cognitiveInterpretationClient{baseURL: base, client: client}, nil } func (c cognitiveInterpretationClient) Interpret(ctx context.Context, request cognitive.GatewayInterpretationRequest) (cognitive.GatewayInterpretationResponse, error) { target := *c.baseURL target.Path = "/v1/interpretations" payload, err := json.Marshal(request) if err != nil { return cognitive.GatewayInterpretationResponse{}, err } upstream, err := http.NewRequestWithContext(ctx, http.MethodPost, target.String(), bytes.NewReader(payload)) if err != nil { return cognitive.GatewayInterpretationResponse{}, err } upstream.Header.Set("Content-Type", "application/json") response, err := c.client.Do(upstream) if err != nil { return cognitive.GatewayInterpretationResponse{}, err } defer response.Body.Close() if response.StatusCode != http.StatusOK { return cognitive.GatewayInterpretationResponse{}, fmt.Errorf("cognitive interpretation status %d", response.StatusCode) } var result cognitive.GatewayInterpretationResponse if err := json.NewDecoder(response.Body).Decode(&result); err != nil { return cognitive.GatewayInterpretationResponse{}, err } return result, nil } func shouldRequestBroaderInterpretation(result interactiondomain.SemanticResult) bool { // The built-in advisory channel is the default conversational path. A // simple question must not first wait for the slower, strict JSON // interpretation route: that route exists for ambiguity and work routing, // not as a prerequisite for talking to Domovoy. if isSimpleLocalQuestion(result) { return false } switch result.Interpretation.Applicability { case interaction.SemanticNeedsBroaderInterpret, interaction.SemanticLowConfidence, interaction.SemanticMixedIntent: return true default: return false } } func isSimpleLocalQuestion(result interactiondomain.SemanticResult) bool { if result.Interpretation.SpeechAct != interaction.SpeechActQuestion || result.Interpretation.Clarification == interaction.ClarificationRequired { return false } for _, hint := range result.CapabilityHints { switch hint { case "MODIFY_CODE", "RUN_TESTS", "SEND_EMAIL", "SCHEDULE_REMINDER", "READ_DATABASE", "QUERY_LOGS": return false } } return true } func buildGatewayInterpretationRequest(session identity.CreatorSession, utterance interaction.HumanUtterance, semantic interactiondomain.SemanticResult, projectNames []string) cognitive.GatewayInterpretationRequest { requestID, _ := ids.New() return cognitive.GatewayInterpretationRequest{ Version: cognitive.CreatorAdvisoryV1, RequestID: cognitive.RequestID(requestID), ActorID: session.ActorID, PrincipalID: session.PrincipalID, ScopeID: authority.ScopeID("domovoy-global-scope"), Message: utterance.Content, Locale: "ru", MaxOutputBytes: 4096, DataPolicy: capability.DataPolicyLocalOnly, DeterministicSpeechAct: string(semantic.Interpretation.SpeechAct), DeterministicSecondary: string(semantic.Interpretation.SecondarySpeechAct), DeterministicConfidence: semantic.Interpretation.ConfidenceMillis, DeterministicApplicability: cognitive.InterpretationApplicability(semantic.Interpretation.Applicability), RequestedOutcome: semantic.Interpretation.RequestedOutcome, ProjectNames: projectNames, } } func interactionCandidateFromGateway(utteranceID interaction.HumanUtteranceID, candidate cognitive.GatewayInterpretationCandidate, now interface{ UnixMilli() int64 }) (interaction.SemanticCandidate, error) { id, err := ids.New() if err != nil { return interaction.SemanticCandidate{}, err } return interaction.SemanticCandidate{ ID: interaction.SemanticCandidateID(id), UtteranceID: utteranceID, Source: interaction.CandidateSourceCognitive, SelectionState: interaction.CandidateSelected, PrimarySpeechAct: interaction.SpeechAct(candidate.PrimarySpeechAct), SecondarySpeechAct: interaction.SpeechAct(candidate.SecondarySpeechAct), ConfidenceMillis: candidate.ConfidenceMillis, Applicability: interaction.SemanticApplicability(candidate.Applicability), RequestedOutcome: candidate.RequestedOutcome, Clarification: interaction.ClarificationState(candidate.Clarification), NeedsCognition: candidate.NeedsCognition, SelectionReason: candidate.SelectionReason, CapabilityHints: capabilityHintStrings(candidate.CapabilityHints), RecordedAtUnixMillis: now.UnixMilli(), }, nil } func capabilityHintStrings(values []capability.CapabilityKind) []string { result := make([]string, 0, len(values)) for _, value := range values { if value == "" { continue } result = append(result, string(value)) } return result }