202 lines
6.6 KiB
Go
202 lines
6.6 KiB
Go
package cognitive
|
|
|
|
import (
|
|
"errors"
|
|
"sort"
|
|
"strings"
|
|
|
|
"git.cin.su/m/Assistant/contracts/capability"
|
|
)
|
|
|
|
var ErrInvalidContextBudget = errors.New("context budget must be positive")
|
|
|
|
type ContextCandidate struct {
|
|
Excerpt ContextExcerpt
|
|
Relevance int
|
|
Lane string
|
|
ContextScopeRef string
|
|
FreshnessState string
|
|
Negative bool
|
|
}
|
|
|
|
type ContextCompileInput struct {
|
|
Candidates []ContextCandidate
|
|
DataPolicy capability.DataPolicy
|
|
MaxBytes int
|
|
TaskRef string
|
|
FocusFrameRef string
|
|
ContextScopeRef string
|
|
FreshnessRequirement string
|
|
RiskClass string
|
|
TokenBudget int
|
|
}
|
|
|
|
type ContextOmission struct {
|
|
ID ContextExcerptID
|
|
Reason string
|
|
}
|
|
|
|
// ContextInclusion records the bounded compiler decision without exposing
|
|
// source text. It is trace-only metadata and is never user-facing output.
|
|
type ContextInclusion struct {
|
|
ID ContextExcerptID
|
|
Lane string
|
|
Reason string
|
|
}
|
|
|
|
type ContextBundle struct {
|
|
Excerpts []ContextExcerpt
|
|
Omissions []ContextOmission
|
|
Bytes int
|
|
Trace ContextAssemblyTrace
|
|
}
|
|
|
|
type ContextAssemblyTrace struct {
|
|
Included []ContextExcerptID
|
|
Inclusions []ContextInclusion
|
|
Excluded []ContextOmission
|
|
Coverage string
|
|
CompilerVersion string
|
|
}
|
|
|
|
type IterativeRetrievalLane string
|
|
|
|
const (
|
|
IterativeRetrievalCurrentFact IterativeRetrievalLane = "CURRENT_FACT"
|
|
IterativeRetrievalPositiveExperience IterativeRetrievalLane = "POSITIVE_EXPERIENCE"
|
|
IterativeRetrievalNegativeExperience IterativeRetrievalLane = "NEGATIVE_EXPERIENCE"
|
|
IterativeRetrievalContradiction IterativeRetrievalLane = "CONTRADICTION"
|
|
IterativeRetrievalHistoricalAnalogy IterativeRetrievalLane = "HISTORICAL_ANALOGY"
|
|
)
|
|
|
|
// IterativeRetrievalRequest is the model-visible, storage-agnostic intent.
|
|
// Principal, Scope, Focus and policy are deliberately absent: Core binds them
|
|
// from the already-authorized request and never accepts them from a model.
|
|
type IterativeRetrievalRequest struct {
|
|
Query string
|
|
SemanticSubject string
|
|
Lane IterativeRetrievalLane
|
|
Reason string
|
|
RemainingBytes int
|
|
Pass int
|
|
}
|
|
|
|
// IterativeRetrievalBudget is a Core-enforced per-request budget.
|
|
type IterativeRetrievalBudget struct {
|
|
MaxRounds int
|
|
MaxFragments int
|
|
MaxAddedBytes int
|
|
MaxLatencyMillis int64
|
|
}
|
|
|
|
// IterativeRetrievalEnvelope is the trusted continuation input assembled by
|
|
// Core. It is never produced by a provider or model adapter.
|
|
type IterativeRetrievalEnvelope struct {
|
|
Request IterativeRetrievalRequest
|
|
PrincipalID string
|
|
ContextScopeRef string
|
|
FocusFrameRef string
|
|
TaskRef string
|
|
FreshnessRequirement string
|
|
DataPolicy capability.DataPolicy
|
|
Budget IterativeRetrievalBudget
|
|
SeenExcerptIDs []ContextExcerptID
|
|
}
|
|
type IterativeRetrievalResponse struct {
|
|
Candidates []ContextCandidate
|
|
Exhausted bool
|
|
Reason string
|
|
AddedBytes int
|
|
}
|
|
|
|
// CompileContext deterministically selects permitted, relevant excerpts for a
|
|
// gateway request. It carries no provider identity and performs no I/O.
|
|
func CompileContext(input ContextCompileInput) (ContextBundle, error) {
|
|
if input.MaxBytes <= 0 {
|
|
return ContextBundle{}, ErrInvalidContextBudget
|
|
}
|
|
candidates := append([]ContextCandidate(nil), input.Candidates...)
|
|
bundle := ContextBundle{Excerpts: []ContextExcerpt{}, Omissions: []ContextOmission{}}
|
|
seen := make(map[ContextExcerptID]struct{}, len(candidates))
|
|
eligible := make([]ContextCandidate, 0, len(candidates))
|
|
for _, candidate := range candidates {
|
|
excerpt := candidate.Excerpt
|
|
if _, ok := seen[excerpt.ID]; ok {
|
|
bundle.Omissions = append(bundle.Omissions, ContextOmission{ID: excerpt.ID, Reason: "duplicate"})
|
|
continue
|
|
}
|
|
seen[excerpt.ID] = struct{}{}
|
|
if input.ContextScopeRef != "" && candidate.ContextScopeRef != input.ContextScopeRef {
|
|
bundle.Omissions = append(bundle.Omissions, ContextOmission{ID: excerpt.ID, Reason: "context_scope"})
|
|
continue
|
|
}
|
|
if reason := permittedContextReason(excerpt, input.DataPolicy); reason != "" {
|
|
bundle.Omissions = append(bundle.Omissions, ContextOmission{ID: excerpt.ID, Reason: reason})
|
|
continue
|
|
}
|
|
if strings.TrimSpace(excerpt.Text) == "" {
|
|
bundle.Omissions = append(bundle.Omissions, ContextOmission{ID: excerpt.ID, Reason: "empty"})
|
|
continue
|
|
}
|
|
eligible = append(eligible, candidate)
|
|
}
|
|
candidates = eligible
|
|
sort.SliceStable(candidates, func(i, j int) bool {
|
|
if candidates[i].Relevance != candidates[j].Relevance {
|
|
return candidates[i].Relevance > candidates[j].Relevance
|
|
}
|
|
return string(candidates[i].Excerpt.ID) < string(candidates[j].Excerpt.ID)
|
|
})
|
|
included := make(map[ContextExcerptID]struct{}, len(candidates))
|
|
include := func(candidate ContextCandidate, reason string) bool {
|
|
excerpt := candidate.Excerpt
|
|
bytes := len([]byte(excerpt.Text))
|
|
if bundle.Bytes+bytes > input.MaxBytes {
|
|
return false
|
|
}
|
|
bundle.Excerpts = append(bundle.Excerpts, excerpt)
|
|
bundle.Bytes += bytes
|
|
bundle.Trace.Included = append(bundle.Trace.Included, excerpt.ID)
|
|
bundle.Trace.Inclusions = append(bundle.Trace.Inclusions, ContextInclusion{ID: excerpt.ID, Lane: candidate.Lane, Reason: reason})
|
|
included[excerpt.ID] = struct{}{}
|
|
return true
|
|
}
|
|
// Preserve one candidate from each semantic lane before filling by score so
|
|
// many similar current facts cannot erase a relevant failure or conflict.
|
|
for _, lane := range []string{"current-fact", "positive-experience", "negative-experience", "contradiction", "historical-analogy", "interaction"} {
|
|
for _, candidate := range candidates {
|
|
if candidate.Lane == lane && include(candidate, "lane_coverage") {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
for _, candidate := range candidates {
|
|
if _, ok := included[candidate.Excerpt.ID]; ok {
|
|
continue
|
|
}
|
|
if !include(candidate, "salience") {
|
|
bundle.Omissions = append(bundle.Omissions, ContextOmission{ID: candidate.Excerpt.ID, Reason: "budget"})
|
|
}
|
|
}
|
|
bundle.Trace.Excluded = append([]ContextOmission(nil), bundle.Omissions...)
|
|
bundle.Trace.CompilerVersion = "context-compiler-v2-additive"
|
|
bundle.Trace.Coverage = "BOUNDED"
|
|
return bundle, nil
|
|
}
|
|
|
|
func permittedContextReason(excerpt ContextExcerpt, policy capability.DataPolicy) string {
|
|
if excerpt.Sensitivity == SensitivitySecret {
|
|
return "secret"
|
|
}
|
|
if excerpt.Sensitivity == SensitivityUnknown {
|
|
return "unknown_sensitivity"
|
|
}
|
|
if policy == capability.DataPolicyExternalMessageOnly {
|
|
return "policy_message_only"
|
|
}
|
|
if policy == capability.DataPolicyExternalMinimizedContext && excerpt.Sensitivity != SensitivityPublic {
|
|
return "policy_public_only"
|
|
}
|
|
return ""
|
|
}
|