Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0acbe53fad | ||
|
|
e634bb946a | ||
|
|
c7ad996cae |
@@ -0,0 +1,90 @@
|
||||
package epistemics
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"git.cin.su/m/Assistant/contracts/world"
|
||||
)
|
||||
|
||||
type VolatilityMode string
|
||||
|
||||
const (
|
||||
VolatilityImmutable VolatilityMode = "IMMUTABLE"
|
||||
VolatilityVersionBound VolatilityMode = "VERSION_BOUND"
|
||||
VolatilityVerySlow VolatilityMode = "VERY_SLOW"
|
||||
VolatilitySlow VolatilityMode = "SLOW"
|
||||
VolatilityMedium VolatilityMode = "MEDIUM"
|
||||
VolatilityFast VolatilityMode = "FAST"
|
||||
VolatilityEphemeral VolatilityMode = "EPHEMERAL"
|
||||
VolatilityCustom VolatilityMode = "CUSTOM"
|
||||
)
|
||||
|
||||
type TemporalApplicability struct {
|
||||
ObservedAtUnixMillis int64
|
||||
ValidFromUnixMillis *int64
|
||||
ValidUntilUnixMillis *int64
|
||||
LastVerifiedAtUnixMillis *int64
|
||||
Volatility VolatilitySpec
|
||||
VersionBindings []world.VersionBinding
|
||||
}
|
||||
type VolatilitySpec struct {
|
||||
Mode VolatilityMode
|
||||
ExpectedChangeIntervalMillis *int64
|
||||
RevalidateAfterMillis *int64
|
||||
HardStaleAfterMillis *int64
|
||||
InvalidateOnVersionChange bool
|
||||
RevalidationPolicyRef string
|
||||
}
|
||||
type FreshnessAssessmentState string
|
||||
|
||||
const (
|
||||
FreshnessAssessmentCurrent FreshnessAssessmentState = "CURRENT"
|
||||
FreshnessAssessmentAcceptable FreshnessAssessmentState = "ACCEPTABLE"
|
||||
FreshnessAssessmentAging FreshnessAssessmentState = "AGING"
|
||||
FreshnessAssessmentStale FreshnessAssessmentState = "STALE"
|
||||
FreshnessAssessmentVersionMismatch FreshnessAssessmentState = "VERSION_MISMATCH"
|
||||
FreshnessAssessmentHistoricalOnly FreshnessAssessmentState = "HISTORICAL_ONLY"
|
||||
FreshnessAssessmentUnknown FreshnessAssessmentState = "UNKNOWN"
|
||||
)
|
||||
|
||||
type FreshnessAssessment struct {
|
||||
SourceKnowledgeRef string
|
||||
TaskRef string
|
||||
State FreshnessAssessmentState
|
||||
Reason string
|
||||
AgeMillis int64
|
||||
VersionMatchState string
|
||||
RevalidationAttemptRef string
|
||||
AssessedAtUnixMillis int64
|
||||
}
|
||||
type RevalidationRequest struct {
|
||||
ID string
|
||||
SourceKnowledgeRef string
|
||||
TaskRef string
|
||||
RequiredVersionRefs []world.SemanticReference
|
||||
RequestedAtUnixMillis int64
|
||||
}
|
||||
type RevalidationResponse struct {
|
||||
RequestID string
|
||||
Assessment FreshnessAssessment
|
||||
NewEvidenceRefs []EvidenceRef
|
||||
CompletedAtUnixMillis int64
|
||||
}
|
||||
|
||||
func (v VolatilitySpec) Validate() error {
|
||||
if v.Mode == "" {
|
||||
return fmt.Errorf("volatility mode is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (t TemporalApplicability) Validate() error {
|
||||
if t.ObservedAtUnixMillis < 1 {
|
||||
return fmt.Errorf("observed time is required")
|
||||
}
|
||||
return t.Volatility.Validate()
|
||||
}
|
||||
func (a FreshnessAssessment) Validate() error {
|
||||
if a.SourceKnowledgeRef == "" || a.TaskRef == "" || a.State == "" || a.AgeMillis < 0 || a.AssessedAtUnixMillis < 1 {
|
||||
return fmt.Errorf("freshness assessment is incomplete")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -122,6 +122,15 @@ type ExperienceRecord struct {
|
||||
VerificationEvidence []EvidenceRef
|
||||
ConfidenceMillis int64
|
||||
RecordedAtUnixMillis int64
|
||||
ProblemStatement string
|
||||
AttemptRef string
|
||||
WhyAttempted string
|
||||
ApplicabilityConditions []string
|
||||
ContextScopeRef string
|
||||
EpisodeRef string
|
||||
GoalRef string
|
||||
TaskRef string
|
||||
WhySucceededOrFailed string
|
||||
}
|
||||
|
||||
// ReflectionCandidate records that broader reflection may be warranted.
|
||||
@@ -163,3 +172,13 @@ type BeliefAssessment struct {
|
||||
EvidenceRefs []EvidenceRef
|
||||
AssessedAtUnixMillis int64
|
||||
}
|
||||
|
||||
type EpisodeID ids.ID
|
||||
type Episode struct {
|
||||
ID EpisodeID
|
||||
PrincipalID string
|
||||
ExperienceRefs []ExperienceRecordID
|
||||
Summary string
|
||||
Outcome OutcomeClass
|
||||
RecordedAtUnixMillis int64
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package work
|
||||
|
||||
import "git.cin.su/m/Assistant/contracts/world"
|
||||
|
||||
type FreshnessRequirementMode string
|
||||
type FreshnessFallbackPolicy string
|
||||
|
||||
const (
|
||||
FreshnessAny FreshnessRequirementMode = "ANY"
|
||||
FreshnessPreferCurrent FreshnessRequirementMode = "PREFER_CURRENT"
|
||||
FreshnessRequireCurrent FreshnessRequirementMode = "REQUIRE_CURRENT"
|
||||
FreshnessRequireWithinAge FreshnessRequirementMode = "REQUIRE_WITHIN_AGE"
|
||||
FreshnessRequireVersionMatch FreshnessRequirementMode = "REQUIRE_VERSION_MATCH"
|
||||
FreshnessLiveRequired FreshnessRequirementMode = "LIVE_REQUIRED"
|
||||
FallbackAllowStaleDisclosure FreshnessFallbackPolicy = "ALLOW_STALE_WITH_DISCLOSURE"
|
||||
FallbackHypothesisOnly FreshnessFallbackPolicy = "USE_AS_HYPOTHESIS_ONLY"
|
||||
FallbackWaitForSource FreshnessFallbackPolicy = "WAIT_FOR_SOURCE"
|
||||
FallbackAskOwner FreshnessFallbackPolicy = "ASK_OWNER"
|
||||
FallbackStopUnknown FreshnessFallbackPolicy = "STOP_WITH_UNKNOWN"
|
||||
)
|
||||
|
||||
type TaskFreshnessRequirement struct {
|
||||
Mode FreshnessRequirementMode
|
||||
MaximumAgeMillis *int64
|
||||
RequiredVersionRefs []world.SemanticReference
|
||||
RevalidationRequired bool
|
||||
FallbackPolicy FreshnessFallbackPolicy
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
{"task_file":"dev/tasks/GEN2-006-normalize-epistemics-evidence-propositions-claims-and-belief-assessments.json","task_id":"GEN2-006","task_revision":1}
|
||||
{"task_file":"dev/tasks/GEN2-009-build-memory-retrieval-fabric-as-rebuildable-projections.json","task_id":"GEN2-009","task_revision":1}
|
||||
@@ -0,0 +1,29 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"git.cin.su/m/Assistant/contracts/epistemics"
|
||||
"git.cin.su/m/Assistant/platform/ids"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func BuildEpisode(experiences []epistemics.ExperienceRecord, summary string, at int64) (epistemics.Episode, error) {
|
||||
if len(experiences) == 0 || strings.TrimSpace(summary) == "" || at < 1 {
|
||||
return epistemics.Episode{}, ErrInvalidExperience
|
||||
}
|
||||
refs := make([]epistemics.ExperienceRecordID, 0, len(experiences))
|
||||
outcome := epistemics.OutcomeUnknown
|
||||
for _, e := range experiences {
|
||||
if _, err := NewExperienceRecord(e); err != nil {
|
||||
return epistemics.Episode{}, err
|
||||
}
|
||||
refs = append(refs, e.ID)
|
||||
if e.ActualOutcome != epistemics.OutcomeSuccess {
|
||||
outcome = e.ActualOutcome
|
||||
}
|
||||
}
|
||||
id, err := ids.New()
|
||||
if err != nil {
|
||||
return epistemics.Episode{}, err
|
||||
}
|
||||
return epistemics.Episode{ID: epistemics.EpisodeID(id), ExperienceRefs: refs, Summary: summary, Outcome: outcome, RecordedAtUnixMillis: at}, nil
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package domain_test
|
||||
|
||||
import (
|
||||
"git.cin.su/m/Assistant/contracts/epistemics"
|
||||
"git.cin.su/m/Assistant/modules/epistemics/domain"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEpisodeIsRebuildableFromExperience(t *testing.T) {
|
||||
e := epistemics.ExperienceRecord{ID: "e", SourceKind: epistemics.ExperienceSourceInteraction, OriginalIntentID: "i", SourceUtteranceID: "u", HelpMethod: epistemics.HelpMethodFallback, ExpectedOutcome: epistemics.OutcomeSuccess, ActualOutcome: epistemics.OutcomeFailed, RecordedAtUnixMillis: 1}
|
||||
ep, err := domain.BuildEpisode([]epistemics.ExperienceRecord{e}, "failed attempt", 1)
|
||||
if err != nil || len(ep.ExperienceRefs) != 1 || ep.Outcome != epistemics.OutcomeFailed {
|
||||
t.Fatalf("episode=%#v err=%v", ep, err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package domain
|
||||
|
||||
import "git.cin.su/m/Assistant/contracts/epistemics"
|
||||
|
||||
// AssessFreshness receives the Work-owned requirement as primitive policy
|
||||
// inputs, keeping the Epistemics owner independent of Work package types.
|
||||
func AssessFreshness(applicability epistemics.TemporalApplicability, mode string, maximumAgeMillis *int64, nowMillis int64) epistemics.FreshnessAssessment {
|
||||
age := nowMillis - applicability.ObservedAtUnixMillis
|
||||
if age < 0 {
|
||||
age = 0
|
||||
}
|
||||
state := epistemics.FreshnessAssessmentCurrent
|
||||
reason := "observation is within applicability window"
|
||||
if applicability.ValidUntilUnixMillis != nil && nowMillis > *applicability.ValidUntilUnixMillis {
|
||||
state, reason = epistemics.FreshnessAssessmentStale, "valid-until elapsed"
|
||||
}
|
||||
if applicability.Volatility.HardStaleAfterMillis != nil && age > *applicability.Volatility.HardStaleAfterMillis {
|
||||
state, reason = epistemics.FreshnessAssessmentStale, "hard volatility window elapsed"
|
||||
}
|
||||
if applicability.Volatility.RevalidateAfterMillis != nil && age > *applicability.Volatility.RevalidateAfterMillis && state == epistemics.FreshnessAssessmentCurrent {
|
||||
state, reason = epistemics.FreshnessAssessmentAging, "revalidation window elapsed"
|
||||
}
|
||||
if maximumAgeMillis != nil && age > *maximumAgeMillis {
|
||||
state, reason = epistemics.FreshnessAssessmentStale, "task maximum age exceeded"
|
||||
}
|
||||
if mode == "LIVE_REQUIRED" {
|
||||
state, reason = epistemics.FreshnessAssessmentHistoricalOnly, "live source required"
|
||||
}
|
||||
return epistemics.FreshnessAssessment{SourceKnowledgeRef: "knowledge", TaskRef: "task", State: state, Reason: reason, AgeMillis: age, AssessedAtUnixMillis: nowMillis}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package domain_test
|
||||
|
||||
import (
|
||||
"git.cin.su/m/Assistant/contracts/epistemics"
|
||||
"git.cin.su/m/Assistant/modules/epistemics/domain"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFreshnessIsTaskRelative(t *testing.T) {
|
||||
hard := int64(100)
|
||||
app := epistemics.TemporalApplicability{ObservedAtUnixMillis: 1, Volatility: epistemics.VolatilitySpec{Mode: epistemics.VolatilityVersionBound, HardStaleAfterMillis: &hard}}
|
||||
current := domain.AssessFreshness(app, "ANY", nil, 50)
|
||||
historical := domain.AssessFreshness(app, "LIVE_REQUIRED", nil, 50)
|
||||
if current.State != epistemics.FreshnessAssessmentCurrent || historical.State != epistemics.FreshnessAssessmentHistoricalOnly {
|
||||
t.Fatalf("got %v and %v", current.State, historical.State)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package retrieval
|
||||
|
||||
import "sort"
|
||||
|
||||
type Lane string
|
||||
|
||||
const (
|
||||
LaneSemantic Lane = "SEMANTIC"
|
||||
LaneEntity Lane = "ENTITY"
|
||||
LaneTemporal Lane = "TEMPORAL"
|
||||
LaneGoal Lane = "GOAL"
|
||||
LanePositiveExperience Lane = "POSITIVE_EXPERIENCE"
|
||||
LaneNegativeExperience Lane = "NEGATIVE_EXPERIENCE"
|
||||
LaneContradiction Lane = "CONTRADICTION"
|
||||
LaneSkill Lane = "SKILL"
|
||||
)
|
||||
|
||||
// RetrievalDocument is disposable read-side projection data. SourceRef and
|
||||
// Owner preserve provenance; this type never becomes canonical meaning.
|
||||
type RetrievalDocument struct {
|
||||
ID string
|
||||
SourceRef string
|
||||
Owner string
|
||||
PrincipalID string
|
||||
ContextScopeRef string
|
||||
VersionRef string
|
||||
Lane Lane
|
||||
Text string
|
||||
Relevance int
|
||||
Freshness int
|
||||
Confidence int
|
||||
Salience int
|
||||
Cost int
|
||||
}
|
||||
|
||||
type Query struct {
|
||||
PrincipalID string
|
||||
ContextScopeRef string
|
||||
VersionRef string
|
||||
Lanes []Lane
|
||||
Limit int
|
||||
}
|
||||
|
||||
func FilterAndRank(documents []RetrievalDocument, query Query) []RetrievalDocument {
|
||||
filtered := make([]RetrievalDocument, 0, len(documents))
|
||||
for _, doc := range documents {
|
||||
if doc.PrincipalID != query.PrincipalID || (query.ContextScopeRef != "" && doc.ContextScopeRef != query.ContextScopeRef) || (query.VersionRef != "" && doc.VersionRef != query.VersionRef) || !containsLane(query.Lanes, doc.Lane) {
|
||||
continue
|
||||
}
|
||||
filtered = append(filtered, doc)
|
||||
}
|
||||
sort.SliceStable(filtered, func(i, j int) bool {
|
||||
left := filtered[i].Relevance + filtered[i].Freshness + filtered[i].Confidence + filtered[i].Salience - filtered[i].Cost
|
||||
right := filtered[j].Relevance + filtered[j].Freshness + filtered[j].Confidence + filtered[j].Salience - filtered[j].Cost
|
||||
if left != right {
|
||||
return left > right
|
||||
}
|
||||
return filtered[i].ID < filtered[j].ID
|
||||
})
|
||||
if query.Limit > 0 && len(filtered) > query.Limit {
|
||||
filtered = filtered[:query.Limit]
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
func containsLane(lanes []Lane, lane Lane) bool {
|
||||
if len(lanes) == 0 {
|
||||
return true
|
||||
}
|
||||
for _, v := range lanes {
|
||||
if v == lane {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type ProjectionBuilder interface {
|
||||
Build() ([]RetrievalDocument, error)
|
||||
}
|
||||
|
||||
func Rebuild(builders ...ProjectionBuilder) ([]RetrievalDocument, error) {
|
||||
var result []RetrievalDocument
|
||||
for _, builder := range builders {
|
||||
docs, err := builder.Build()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result = append(result, docs...)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package retrieval_test
|
||||
|
||||
import (
|
||||
"git.cin.su/m/Assistant/runtime/retrieval"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestScopeAndVersionFilterBeforeRanking(t *testing.T) {
|
||||
docs := []retrieval.RetrievalDocument{{ID: "wrong-company", PrincipalID: "a", ContextScopeRef: "b", VersionRef: "v", Lane: retrieval.LaneSemantic, Relevance: 100}, {ID: "right", PrincipalID: "a", ContextScopeRef: "a", VersionRef: "v", Lane: retrieval.LaneNegativeExperience, Relevance: 1}, {ID: "wrong-version", PrincipalID: "a", ContextScopeRef: "a", VersionRef: "old", Lane: retrieval.LaneSemantic, Relevance: 100}}
|
||||
got := retrieval.FilterAndRank(docs, retrieval.Query{PrincipalID: "a", ContextScopeRef: "a", VersionRef: "v", Lanes: []retrieval.Lane{retrieval.LaneSemantic, retrieval.LaneNegativeExperience}, Limit: 10})
|
||||
if len(got) != 1 || got[0].ID != "right" {
|
||||
t.Fatalf("got %#v", got)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user