fusion

package
v1.0.0-beta.120 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 29, 2026 License: MIT Imports: 8 Imported by: 0

Documentation

Overview

Package fusion is the generic deterministic-fusion engine: fan-out sub-query dispatch, dedup, rank, and budget enforcement over a GraphQueryClient. It is a pure leaf package — no NATS, no research-domain types (Intent, ExecutionOutput, RouteAction). Domain-coupled callers (processor/research-graph-execute) compose this engine and wrap the result in their own payload types.

Index

Constants

View Source
const (
	// DefaultMaxParallelism caps concurrent sub-query execution.
	// Keeps fan-out from saturating the responding gateways under
	// a wide-decompose scenario; operators can raise for staging
	// environments with more capacity.
	DefaultMaxParallelism = 8

	// DefaultMaxResultsPerSubquery caps per-sub-query result count
	// before ranking + budget enforcement run. Prevents a single
	// runaway sub-query from dominating the evidence array.
	DefaultMaxResultsPerSubquery = 50

	// DefaultBudgetTokens is the fallback evidence-budget when the
	// caller doesn't supply one. Should never normally fire when
	// callers supply a domain default, but acts as a safety net.
	DefaultBudgetTokens = 4000

	// DefaultPerSubqueryTimeoutFraction is the fraction of the
	// overall context deadline each sub-query gets as its per-call
	// timeout. 0.5 leaves slack for fan-out overhead and ranking.
	DefaultPerSubqueryTimeoutFraction = 0.5

	// DefaultExecuteTimeout is the fallback per-query timeout when
	// the caller's context has no deadline. Used only to bound
	// per-sub-query execution when ctx has no deadline set.
	DefaultExecuteTimeout = 60 * time.Second
)

Default engine constants. Callers that pass zero-value FuseOptions fields fall through to these.

Variables

This section is empty.

Functions

This section is empty.

Types

type BM25Args

type BM25Args struct {
	Query string `json:"query"`
	Limit int    `json:"limit,omitempty"`
}

BM25Args carries the text query + result cap.

type EntityStateArgs

type EntityStateArgs struct {
	EntityIDs []string `json:"entity_ids"`
}

EntityStateArgs carries IDs to fetch.

type Evidence

type Evidence struct {
	// EntityID is the graph entity this evidence refers to. Required.
	EntityID string `json:"entity_id"`

	// Tier is the retrieval tier that surfaced this hit: "0" (rules /
	// predicate queries), "1" (BM25), or "2" (neural — deferred to
	// Phase 2). Operators may use it to filter evidence by retrieval
	// method.
	Tier string `json:"tier"`

	// Source is the retrieval source within the tier — e.g.
	// "classifier", "predicate_walk", "bm25_index". Required.
	Source string `json:"source"`

	// Score is the within-tier ranking score (higher = better).
	// Cross-tier comparison is not meaningful in Phase 1 (per-tier
	// ordering + recency tie-break); Phase 2 may add a learned ranker.
	Score float64 `json:"score,omitempty"`

	// SnippetText is a short inline preview (prompt-injection
	// friendly). Omit when the evidence has no readable preview.
	SnippetText string `json:"snippet_text,omitempty"`

	// ObjectStoreRef is the ObjectStore key for the full body when
	// the evidence has bulk content. Empty when the evidence is fully
	// expressed in the EntityID + triples on the graph.
	ObjectStoreRef string `json:"objectstore_ref,omitempty"`
}

Evidence is one item in a fused evidence set — a single entity hit or evidence snippet the retrieval fan-out surfaced. Provenance is mandatory: every evidence item carries enough metadata (EntityID + Tier + Source) for the caller to verify it back against the graph and for downstream consumers (e.g. synthesize_answer's quote-back validation) to reject fabricated refs.

ObjectStoreRef is set when the body of the evidence (long text, document, etc.) lives in ObjectStore; consumers read it via that ref. SnippetText carries a short inline preview for prompt-injection readability without triggering ObjectStore round-trips on every hit.

func (*Evidence) Validate

func (e *Evidence) Validate() error

Validate checks the required fields and rejects unknown tier values.

type FuseOptions

type FuseOptions struct {
	// MaxParallelism caps concurrent sub-query execution. 0 uses DefaultMaxParallelism.
	MaxParallelism int

	// MaxResultsPerSubquery caps result count per sub-query before
	// ranking + budget enforcement run. 0 uses DefaultMaxResultsPerSubquery.
	MaxResultsPerSubquery int

	// BudgetTokens is the estimated-token budget for the returned
	// evidence set. 0 uses DefaultBudgetTokens.
	BudgetTokens int
}

FuseOptions controls the engine's concurrency, per-subquery result cap, and evidence budget. Zero values fall through to the Default* constants.

type FuseResult

type FuseResult struct {
	// Evidence is the kept evidence set after dedup, sort, and budget
	// enforcement. May be empty (degraded path or zero queries).
	Evidence []Evidence

	// Degraded flags incomplete fan-out — e.g. one or more sub-query
	// errors or a timeout. The caller should surface this to operators
	// or downstream LLM consumers as low-confidence input.
	Degraded bool

	// DegradedReason explains why Degraded is set. Multiple per-sub-query
	// failures are joined with "; ".
	DegradedReason string

	// BudgetTokensUsed is the estimated token cost of the kept evidence
	// array after budget enforcement. Useful for operator observability
	// and tuning budget parameters.
	BudgetTokensUsed int
}

FuseResult is the engine's output: dedup'd + ranked + budget-enforced evidence, plus observability fields.

func Fuse

func Fuse(ctx context.Context, gq GraphQueryClient, queries []SubQuery, opts FuseOptions, logger *slog.Logger) (FuseResult, error)

Fuse runs the materialized sub-query set in parallel, deduplicates by EntityID (across tiers), sorts by tier + score + entity-ID tie-break, enforces the token budget, and returns a FuseResult.

gq must not be nil. An empty queries slice is not an error — it returns FuseResult{Degraded: true, DegradedReason: "..."}.

Per-sub-query failures are degrading, NOT chain-failing: they set FuseResult.Degraded and append to DegradedReason without cancelling sibling sub-queries. The only hard-fail is a parent-context cancellation, which returns a non-nil error.

The logger parameter is optional; nil uses slog.Default().

type GraphQueryClient

type GraphQueryClient interface {
	EntityState(ctx context.Context, args EntityStateArgs, tier, source string, limit int) ([]Evidence, error)
	PredicateWalk(ctx context.Context, args PredicateWalkArgs, tier, source string, limit int) ([]Evidence, error)
	TemporalRange(ctx context.Context, args TemporalRangeArgs, tier, source string, limit int) ([]Evidence, error)
	BM25(ctx context.Context, args BM25Args, tier, source string, limit int) ([]Evidence, error)
}

GraphQueryClient is the narrow retrieval surface the fusion engine consumes. Production implementations wrap NATS-direct subjects; tests substitute in-memory fakes so the test matrix doesn't need a live graph stack.

Per-method semantics:

  • EntityState: returns the current Triples for the named entities, projected into Evidence (one per entity).
  • PredicateWalk: from each seed, returns Evidence for entities reachable within MaxHops via Predicates (empty Predicates = all).
  • TemporalRange: returns Evidence for entities within the time window, optionally filtered by topic.
  • BM25: text search via graph-query's existing BM25 surface.

All methods MUST stamp Tier + Source on every returned Evidence (taken from the SubQuery they were dispatched for) so provenance stays honest end-to-end. The engine does NOT re-stamp.

type PredicateWalkArgs

type PredicateWalkArgs struct {
	Seeds      []string `json:"seeds"`
	Predicates []string `json:"predicates,omitempty"`
	MaxHops    int      `json:"max_hops,omitempty"`
}

PredicateWalkArgs carries seed IDs + optional predicate filter. MaxHops 0 resolves to 1 at the executor.

type SubQuery

type SubQuery struct {
	Type   SubQueryType `json:"type"`
	Tier   string       `json:"tier"`   // "0" (predicate) or "1" (BM25)
	Source string       `json:"source"` // e.g. "walk_seeds:drone-001"; surfaces on Evidence.Source

	// Per-type args. Exactly one is populated based on Type — the
	// executor switch enforces. Optional fields allow tests + future
	// templates to omit per-type primitives that don't apply.
	EntityState   *EntityStateArgs   `json:"entity_state,omitempty"`
	PredicateWalk *PredicateWalkArgs `json:"predicate_walk,omitempty"`
	TemporalRange *TemporalRangeArgs `json:"temporal_range,omitempty"`
	BM25          *BM25Args          `json:"bm25,omitempty"`
}

SubQuery is a typed retrieval request. Each variant carries the minimum fields its tier executor needs; the Type discriminator drives dispatch in the engine's executeSubQuery.

Tier and Source travel with the SubQuery so the per-tier executor can stamp them onto every Evidence it produces without re-deriving — keeps provenance honest end-to-end.

func (SubQuery) Validate

func (q SubQuery) Validate() error

Validate returns a non-nil error when required per-type fields are missing. Called by the materializer before fan-out so a malformed sub-query surfaces before any retrieval work runs.

type SubQueryType

type SubQueryType string

SubQueryType is the closed set of Tier 0+1 primitives. Phase 2 adds spatial_polygon + neural; Phase 1 stays minimal.

const (
	// SubQueryTypeEntityState fetches current state of named
	// entities. Tier 0; used for walk_seeds anchoring + decompose's
	// entity_type axis when classifier candidates are present.
	SubQueryTypeEntityState SubQueryType = "entity_state"

	// SubQueryTypePredicateWalk traverses predicate(s) from seed
	// entities. Tier 0; used for walk_seeds neighborhood expansion +
	// decompose's free-form axis fallback.
	SubQueryTypePredicateWalk SubQueryType = "predicate_walk"

	// SubQueryTypeTemporalRange queries entities within a time
	// window. Tier 0; used for decompose's time axis. Phase 1 ships
	// the type but the executor falls through to BM25 on topic when
	// graph-index-temporal isn't wired — operators see the
	// "temporal degrade" hint on the produced Evidence.
	SubQueryTypeTemporalRange SubQueryType = "temporal_range"

	// SubQueryTypeBM25 text-searches via graph-query's existing
	// BM25 surface. Tier 1; always added to widen coverage beyond
	// purely-structural retrieval (intent-shaped routing can miss
	// keyword matches the classifier surfaced).
	SubQueryTypeBM25 SubQueryType = "bm25"
)

type TemporalRangeArgs

type TemporalRangeArgs struct {
	// Start / End are RFC3339 strings; timezone handling stays in the
	// executor where the upstream graph-index-temporal surface lives.
	Start string `json:"start"`
	End   string `json:"end"`
	Topic string `json:"topic,omitempty"`
}

TemporalRangeArgs carries start/end + an optional topic filter. Empty Topic widens to all entities in the window (may be heavy; callers should always pass a topic in practice).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL