177 lines
6.6 KiB
Go
177 lines
6.6 KiB
Go
package domain
|
|
|
|
import (
|
|
"errors"
|
|
"strings"
|
|
|
|
"git.cin.su/m/Assistant/contracts/epistemics"
|
|
"git.cin.su/m/Assistant/platform/ids"
|
|
)
|
|
|
|
var ErrInvalidExperience = errors.New("invalid experience")
|
|
var ErrInvalidExpectation = errors.New("invalid expectation")
|
|
var ErrInvalidReflectionCandidate = errors.New("invalid reflection candidate")
|
|
|
|
func NewSystemExpectation(value epistemics.SystemExpectation) (epistemics.SystemExpectation, error) {
|
|
if value.ID == "" {
|
|
id, err := ids.New()
|
|
if err != nil {
|
|
return epistemics.SystemExpectation{}, err
|
|
}
|
|
value.ID = epistemics.SystemExpectationID(id)
|
|
}
|
|
if value.OriginalIntentID == "" || value.SourceUtteranceID == "" || value.ExpectedOutcome == "" ||
|
|
strings.TrimSpace(value.ExpectedEffect) == "" || value.ConfidenceMillis < 0 || value.ConfidenceMillis > 1000 ||
|
|
value.RecordedAtUnixMillis < 1 {
|
|
return epistemics.SystemExpectation{}, ErrInvalidExpectation
|
|
}
|
|
if value.EvidenceBasis == nil {
|
|
value.EvidenceBasis = []epistemics.EvidenceRef{}
|
|
}
|
|
return value, nil
|
|
}
|
|
|
|
func NewExperienceRecord(value epistemics.ExperienceRecord) (epistemics.ExperienceRecord, error) {
|
|
if value.ID == "" {
|
|
id, err := ids.New()
|
|
if err != nil {
|
|
return epistemics.ExperienceRecord{}, err
|
|
}
|
|
value.ID = epistemics.ExperienceRecordID(id)
|
|
}
|
|
if value.SourceKind == "" || value.OriginalIntentID == "" || value.SourceUtteranceID == "" ||
|
|
value.HelpMethod == "" || value.ExpectedOutcome == "" || value.ActualOutcome == "" ||
|
|
value.ConfidenceMillis < 0 || value.ConfidenceMillis > 1000 || value.RecordedAtUnixMillis < 1 {
|
|
return epistemics.ExperienceRecord{}, ErrInvalidExperience
|
|
}
|
|
if value.ContextRefs == nil {
|
|
value.ContextRefs = []epistemics.ContextRef{}
|
|
}
|
|
if value.VerificationEvidence == nil {
|
|
value.VerificationEvidence = []epistemics.EvidenceRef{}
|
|
}
|
|
return value, nil
|
|
}
|
|
|
|
func ReflectionForExperience(value epistemics.ExperienceRecord) (epistemics.ReflectionCandidate, bool, error) {
|
|
normalized, err := NewExperienceRecord(value)
|
|
if err != nil {
|
|
return epistemics.ReflectionCandidate{}, false, err
|
|
}
|
|
value = normalized
|
|
trigger, ok := reflectionTrigger(value)
|
|
if !ok {
|
|
return epistemics.ReflectionCandidate{}, false, nil
|
|
}
|
|
id, err := ids.New()
|
|
if err != nil {
|
|
return epistemics.ReflectionCandidate{}, false, err
|
|
}
|
|
candidate := epistemics.ReflectionCandidate{
|
|
ID: epistemics.ReflectionCandidateID(id),
|
|
Trigger: trigger,
|
|
State: epistemics.ReflectionCandidateOpen,
|
|
SourceExperienceID: value.ID,
|
|
OriginalIntentID: value.OriginalIntentID,
|
|
SourceUtteranceID: value.SourceUtteranceID,
|
|
Summary: reflectionSummary(trigger, value),
|
|
EvidenceRefs: reflectionEvidence(value),
|
|
RequiresCognition: reflectionNeedsCognition(trigger),
|
|
RecordedAtUnixMillis: value.RecordedAtUnixMillis,
|
|
Revision: 1,
|
|
}
|
|
if err := ValidateReflectionCandidate(candidate); err != nil {
|
|
return epistemics.ReflectionCandidate{}, false, err
|
|
}
|
|
return candidate, true, nil
|
|
}
|
|
|
|
func reflectionNeedsCognition(trigger epistemics.ReflectionTriggerKind) bool {
|
|
switch trigger {
|
|
case epistemics.ReflectionUserCorrection, epistemics.ReflectionUnexpectedOutcome, epistemics.ReflectionFailure, epistemics.ReflectionLowConfidence, epistemics.ReflectionContradiction, epistemics.ReflectionRepeatedDegradation:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func ValidateReflectionCandidate(value epistemics.ReflectionCandidate) error {
|
|
if value.ID == "" || value.Trigger == "" || value.State == "" || value.SourceExperienceID == "" ||
|
|
value.OriginalIntentID == "" || value.SourceUtteranceID == "" || strings.TrimSpace(value.Summary) == "" ||
|
|
value.RecordedAtUnixMillis < 1 || value.Revision < 1 {
|
|
return ErrInvalidReflectionCandidate
|
|
}
|
|
if value.EvidenceRefs == nil {
|
|
value.EvidenceRefs = []epistemics.EvidenceRef{}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func FastPathDeclineExperience(intent epistemics.OriginalIntentRef, utterance epistemics.SourceUtteranceRef, skill epistemics.SkillRef, reason string, millis int64) (epistemics.ExperienceRecord, error) {
|
|
outcome := epistemics.OutcomeNotApplicable
|
|
switch reason {
|
|
case string(epistemics.OutcomeLowConfidence):
|
|
outcome = epistemics.OutcomeLowConfidence
|
|
case string(epistemics.OutcomeOutOfDistribution):
|
|
outcome = epistemics.OutcomeOutOfDistribution
|
|
case string(epistemics.OutcomeConflict):
|
|
outcome = epistemics.OutcomeConflict
|
|
case string(epistemics.OutcomeStale):
|
|
outcome = epistemics.OutcomeStale
|
|
}
|
|
return NewExperienceRecord(epistemics.ExperienceRecord{
|
|
SourceKind: epistemics.ExperienceSourceInteraction,
|
|
OriginalIntentID: intent,
|
|
SourceUtteranceID: utterance,
|
|
HelpMethod: epistemics.HelpMethodDeterministicFastPath,
|
|
SkillRef: skill,
|
|
ExpectedOutcome: epistemics.OutcomeSuccess,
|
|
ActualOutcome: outcome,
|
|
OutcomeDetail: "fast path declined and returned to broader reasoning: " + reason,
|
|
ConfidenceMillis: 1000,
|
|
RecordedAtUnixMillis: millis,
|
|
VerificationEvidence: []epistemics.EvidenceRef{"fast-path-decline"},
|
|
})
|
|
}
|
|
|
|
func reflectionTrigger(value epistemics.ExperienceRecord) (epistemics.ReflectionTriggerKind, bool) {
|
|
switch value.ActualOutcome {
|
|
case epistemics.OutcomeUserCorrected:
|
|
return epistemics.ReflectionUserCorrection, true
|
|
case epistemics.OutcomeUnexpected:
|
|
return epistemics.ReflectionUnexpectedOutcome, true
|
|
case epistemics.OutcomeFailed:
|
|
return epistemics.ReflectionFailure, true
|
|
case epistemics.OutcomeLowConfidence:
|
|
return epistemics.ReflectionLowConfidence, true
|
|
case epistemics.OutcomeOutOfDistribution, epistemics.OutcomeConflict, epistemics.OutcomeStale:
|
|
return epistemics.ReflectionSampledAudit, true
|
|
default:
|
|
return "", false
|
|
}
|
|
}
|
|
|
|
func reflectionSummary(trigger epistemics.ReflectionTriggerKind, value epistemics.ExperienceRecord) string {
|
|
if value.OutcomeDetail != "" {
|
|
return string(trigger) + ": " + value.OutcomeDetail
|
|
}
|
|
return string(trigger) + " for experience " + string(value.ID)
|
|
}
|
|
|
|
func reflectionEvidence(value epistemics.ExperienceRecord) []epistemics.EvidenceRef {
|
|
refs := append([]epistemics.EvidenceRef{}, value.VerificationEvidence...)
|
|
if value.HumanResponseRef != "" {
|
|
refs = append(refs, epistemics.EvidenceRef("continuity:human-response:"+value.HumanResponseRef))
|
|
}
|
|
if value.CorrectionUtteranceID != "" {
|
|
refs = append(refs, epistemics.EvidenceRef("interaction:correction:"+value.CorrectionUtteranceID))
|
|
}
|
|
if value.CapabilityUsageObservationRef != "" {
|
|
refs = append(refs, epistemics.EvidenceRef("capability:usage:"+value.CapabilityUsageObservationRef))
|
|
}
|
|
if value.CapabilityGapRef != "" {
|
|
refs = append(refs, epistemics.EvidenceRef("capability:gap:"+value.CapabilityGapRef))
|
|
}
|
|
return refs
|
|
}
|