146 lines
5.6 KiB
Go
146 lines
5.6 KiB
Go
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
|
|
}
|