Files
Assistant/modules/epistemics/domain/experience.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
}