138 lines
6.9 KiB
Go
138 lines
6.9 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"git.cin.su/m/Assistant/contracts/capability"
|
|
"git.cin.su/m/Assistant/contracts/cognitive"
|
|
)
|
|
|
|
func (g cognitiveGateway) interpretationHandler(w http.ResponseWriter, r *http.Request) {
|
|
var request cognitive.GatewayInterpretationRequest
|
|
if !decodeJSON(w, r, 80<<10, &request) {
|
|
return
|
|
}
|
|
if request.Version != cognitive.CreatorAdvisoryV1 || request.RequestID == "" || request.ActorID == "" || request.PrincipalID == "" || request.ScopeID == "" || strings.TrimSpace(request.Message) == "" || request.MaxOutputBytes < 1 {
|
|
http.Error(w, "invalid interpretation request", http.StatusBadRequest)
|
|
return
|
|
}
|
|
maxOutputBytes := request.MaxOutputBytes
|
|
if g.allowCanary && g.canaryMaxOutputBytes > 0 && maxOutputBytes > g.canaryMaxOutputBytes {
|
|
maxOutputBytes = g.canaryMaxOutputBytes
|
|
}
|
|
requirement := capability.CognitiveRequirement{
|
|
CapabilityID: creatorAdvisoryCapabilityID,
|
|
OwnerPrincipalID: request.PrincipalID,
|
|
DataPolicy: request.DataPolicy,
|
|
RequiredModalities: []capability.ModelModality{capability.ModalityText},
|
|
RequiredCapabilities: []capability.ModelCapability{capability.CapabilityAdvisory},
|
|
AllowCanary: g.allowCanary,
|
|
InputBytes: int64(len(request.Message)),
|
|
MaxOutputBytes: int64(maxOutputBytes),
|
|
}
|
|
selected, err := g.resolver.Resolve(r.Context(), requirement)
|
|
if err != nil {
|
|
writeInterpretationResponse(w, cognitive.GatewayInterpretationResponse{Version: request.Version, RequestID: request.RequestID, FinishReason: cognitive.FinishProviderUnavailable})
|
|
return
|
|
}
|
|
provider := g.provider(selected)
|
|
if provider == nil {
|
|
writeInterpretationResponse(w, cognitive.GatewayInterpretationResponse{Version: request.Version, RequestID: request.RequestID, FinishReason: cognitive.FinishProviderUnavailable})
|
|
return
|
|
}
|
|
invocation := cognitive.AdvisoryRequest{
|
|
Version: request.Version,
|
|
RequestID: request.RequestID,
|
|
ActorID: request.ActorID,
|
|
PrincipalID: request.PrincipalID,
|
|
ScopeID: request.ScopeID,
|
|
Message: interpretationPrompt(request),
|
|
Locale: request.Locale,
|
|
MaxOutputBytes: maxOutputBytes,
|
|
ProviderBindingID: selected.Binding.ID,
|
|
ProviderBindingRevision: selected.Binding.Revision,
|
|
ProviderInstanceID: selected.Provider.ID,
|
|
ModelDescriptorID: selected.Model.ID,
|
|
ProviderModelID: selected.Model.ProviderModelID,
|
|
}
|
|
started := g.now()
|
|
response, invokeErr := provider.Advise(r.Context(), invocation)
|
|
if invokeErr != nil {
|
|
response = cognitive.AdvisoryResponse{Version: request.Version, RequestID: request.RequestID, ProviderBindingID: selected.Binding.ID, ProviderBindingRevision: selected.Binding.Revision, FinishReason: cognitive.FinishInvalidProviderResponse}
|
|
}
|
|
g.observe(r.Context(), selected, response, started)
|
|
if response.FinishReason != cognitive.FinishCompleted {
|
|
writeInterpretationResponse(w, cognitive.GatewayInterpretationResponse{Version: request.Version, RequestID: request.RequestID, FinishReason: response.FinishReason, Usage: response.Usage})
|
|
return
|
|
}
|
|
candidate, ok := parseInterpretationCandidate(response.AdvisoryText)
|
|
if !ok {
|
|
writeInterpretationResponse(w, cognitive.GatewayInterpretationResponse{Version: request.Version, RequestID: request.RequestID, FinishReason: cognitive.FinishInvalidStructuredOutput, Usage: response.Usage})
|
|
return
|
|
}
|
|
writeInterpretationResponse(w, cognitive.GatewayInterpretationResponse{Version: request.Version, RequestID: request.RequestID, Candidate: candidate, FinishReason: cognitive.FinishCompleted, Usage: response.Usage})
|
|
}
|
|
|
|
func interpretationPrompt(request cognitive.GatewayInterpretationRequest) string {
|
|
projects := "none"
|
|
if len(request.ProjectNames) > 0 {
|
|
projects = strings.Join(request.ProjectNames, ", ")
|
|
}
|
|
return fmt.Sprintf(`Return only strict JSON with keys primary_speech_act, secondary_speech_act, confidence_millis, applicability, requested_outcome, needs_cognition, clarification, selection_reason, capability_hints.
|
|
Allowed speech acts: DIRECTIVE, ASSERTION, DECISION, PREFERENCE, QUESTION, HYPOTHESIS, CORRECTION, REQUEST_TO_INVESTIGATE, CONFIRMATION, REJECTION, CANCELLATION, CONVERSATIONAL, UNKNOWN.
|
|
Allowed applicability: APPLICABLE, LOW_CONFIDENCE, MIXED_INTENT, NEEDS_BROADER_INTERPRETATION, CLARIFICATION_REQUIRED.
|
|
Allowed clarification: NOT_REQUIRED or REQUIRED.
|
|
Capability hints may only contain known capability kinds like COGNITIVE_REASONING, MODIFY_CODE, RUN_TESTS, SEND_EMAIL, SCHEDULE_REMINDER, VERIFY_FACT, INSPECT_PROJECT, READ_DATABASE, QUERY_LOGS.
|
|
Interpret the human utterance semantically. Do not claim action execution. Do not treat punctuation alone as decisive. If uncertain, set applicability accordingly.
|
|
Message: %q
|
|
Deterministic primary: %s
|
|
Deterministic secondary: %s
|
|
Deterministic confidence: %d
|
|
Deterministic applicability: %s
|
|
Deterministic requested outcome: %q
|
|
Relevant project names: %s`, request.Message, request.DeterministicSpeechAct, request.DeterministicSecondary, request.DeterministicConfidence, request.DeterministicApplicability, request.RequestedOutcome, projects)
|
|
}
|
|
|
|
func parseInterpretationCandidate(text string) (cognitive.GatewayInterpretationCandidate, bool) {
|
|
start := strings.Index(text, "{")
|
|
end := strings.LastIndex(text, "}")
|
|
if start < 0 || end <= start {
|
|
return cognitive.GatewayInterpretationCandidate{}, false
|
|
}
|
|
var candidate cognitive.GatewayInterpretationCandidate
|
|
if err := json.Unmarshal([]byte(text[start:end+1]), &candidate); err != nil {
|
|
return cognitive.GatewayInterpretationCandidate{}, false
|
|
}
|
|
if !validSpeechAct(candidate.PrimarySpeechAct) || candidate.ConfidenceMillis < 0 || candidate.ConfidenceMillis > 1000 {
|
|
return cognitive.GatewayInterpretationCandidate{}, false
|
|
}
|
|
if candidate.SecondarySpeechAct != "" && !validSpeechAct(candidate.SecondarySpeechAct) {
|
|
return cognitive.GatewayInterpretationCandidate{}, false
|
|
}
|
|
switch candidate.Applicability {
|
|
case cognitive.InterpretationApplicable, cognitive.InterpretationLowConfidence, cognitive.InterpretationMixedIntent, cognitive.InterpretationNeedsBroader, cognitive.InterpretationClarificationRequired:
|
|
default:
|
|
return cognitive.GatewayInterpretationCandidate{}, false
|
|
}
|
|
if candidate.Clarification != "NOT_REQUIRED" && candidate.Clarification != "REQUIRED" {
|
|
return cognitive.GatewayInterpretationCandidate{}, false
|
|
}
|
|
return candidate, true
|
|
}
|
|
|
|
func validSpeechAct(value string) bool {
|
|
switch value {
|
|
case "DIRECTIVE", "ASSERTION", "DECISION", "PREFERENCE", "QUESTION", "HYPOTHESIS", "CORRECTION", "REQUEST_TO_INVESTIGATE", "CONFIRMATION", "REJECTION", "CANCELLATION", "CONVERSATIONAL", "UNKNOWN":
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func writeInterpretationResponse(w http.ResponseWriter, response cognitive.GatewayInterpretationResponse) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(response)
|
|
}
|