package main import ( "context" "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), Objective: selectionObjectiveForInterpretation(request), } selected, err := g.resolver.Resolve(r.Context(), requirement) if err != nil { writeInterpretationResponse(w, g.interpretWithBuiltin(r.Context(), request, maxOutputBytes)) return } provider := g.provider(selected) if provider == nil { writeInterpretationResponse(w, g.interpretWithBuiltin(r.Context(), request, maxOutputBytes)) 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 (g cognitiveGateway) interpretWithBuiltin(ctx context.Context, request cognitive.GatewayInterpretationRequest, maxOutputBytes int) cognitive.GatewayInterpretationResponse { if g.builtinLocal == nil || g.builtinLocal.Provider == nil { return deterministicInterpretationResponse(request) } 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: "builtin-local-binding-v1", ProviderBindingRevision: 1, ProviderInstanceID: "builtin-local-lifeboat-v1", ModelDescriptorID: "builtin-local-model-v1", ProviderModelID: "domovoy-lifeboat"} started := g.now() response, err := g.builtinLocal.Provider.Advise(ctx, invocation) if response.RequestID == "" { response.RequestID = request.RequestID } if response.Version == "" { response.Version = request.Version } if err != nil { response = cognitive.AdvisoryResponse{Version: request.Version, RequestID: request.RequestID, ProviderBindingID: "builtin-local-binding-v1", ProviderBindingRevision: 1, FinishReason: cognitive.FinishInvalidProviderResponse} } g.observeBuiltin(ctx, g.builtinLocal, response, started) if err != nil || response.FinishReason != cognitive.FinishCompleted { return deterministicInterpretationResponse(request) } candidate, ok := parseInterpretationCandidate(response.AdvisoryText) if !ok { return deterministicInterpretationResponse(request) } return cognitive.GatewayInterpretationResponse{Version: request.Version, RequestID: request.RequestID, Candidate: candidate, FinishReason: cognitive.FinishCompleted, Usage: response.Usage} } // deterministicInterpretationResponse keeps the semantic route alive when the // local model cannot produce strict JSON in time. It does not invent meaning: // it carries forward the already-recorded deterministic interpretation and // leaves the advisory answer to the built-in gateway path. func deterministicInterpretationResponse(request cognitive.GatewayInterpretationRequest) cognitive.GatewayInterpretationResponse { speechAct := request.DeterministicSpeechAct if !validSpeechAct(speechAct) { speechAct = "UNKNOWN" } applicability := request.DeterministicApplicability switch applicability { case cognitive.InterpretationApplicable, cognitive.InterpretationLowConfidence, cognitive.InterpretationMixedIntent, cognitive.InterpretationNeedsBroader, cognitive.InterpretationClarificationRequired: default: applicability = cognitive.InterpretationLowConfidence } confidence := request.DeterministicConfidence if confidence < 0 { confidence = 0 } if confidence > 1000 { confidence = 1000 } candidate := cognitive.GatewayInterpretationCandidate{ PrimarySpeechAct: speechAct, SecondarySpeechAct: request.DeterministicSecondary, ConfidenceMillis: confidence, Applicability: applicability, RequestedOutcome: request.RequestedOutcome, NeedsCognition: true, Clarification: "NOT_REQUIRED", SelectionReason: "deterministic interpretation retained; built-in structured interpretation was unavailable", } if applicability == cognitive.InterpretationClarificationRequired { candidate.NeedsCognition = false candidate.Clarification = "REQUIRED" } return cognitive.GatewayInterpretationResponse{Version: request.Version, RequestID: request.RequestID, Candidate: candidate, FinishReason: cognitive.FinishCompleted} } func selectionObjectiveForInterpretation(request cognitive.GatewayInterpretationRequest) capability.SelectionObjective { if request.RequestedOutcome != "" || request.DeterministicApplicability == cognitive.InterpretationNeedsBroader || request.DeterministicApplicability == cognitive.InterpretationMixedIntent || request.DeterministicApplicability == cognitive.InterpretationLowConfidence { return capability.SelectionQuality } if request.DeterministicConfidence >= 800 && request.DeterministicSpeechAct != "QUESTION" { return capability.SelectionSpeed } return capability.SelectionBalanced } 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) }