| queryA | Query data from a semantic model. Call inspect(reference=".", entity_type="model") first to see available columns and measures, and search (with the entities you plan to use and/or a free-text question) to surface saved learnings and example queries before finalizing a query. The query argument takes one of three forms: Model name (string) — run a query-backed saved model by name, e.g. "monthly_revenue"
(honors variables; every other setting comes from the stored query). Query object (dict) — a single query; per-field documentation is on the SlayerQuery schema. Multi-stage list (list of query objects) — a DAG of stages. Every entry except the last
MUST carry a name; the last entry is the root whose rows are returned. Stages reference
one another by that name — as a source_model or via a join in an inline ModelExtension —
and the engine orders them topologically. An inner stage's result columns become plain
columns of the outer stage (dotted paths flatten: stores.name -> stores__name); a
stage may reference only what its own source defines or what a prior stage projected —
define before you reference. Use stages when a whole result set must be re-queried,
joined, or reused; single-query nesting and computed dimensions already cover
re-aggregation.
Expressions — one language, used in measures, computed dimensions, filters, and order. The
same expression returns its value as a measure, groups by it as a computed dimension, masks
as a filter (routed automatically to WHERE / HAVING / post-aggregation), and sorts in order. Aggregations are function calls over a column or a same-model scalar expression:
count(*), sum(total), sum(amount - cost), percentile(price, p=0.95).
Available: sum, avg (both take window='90d' for trailing time windows), min, max, count,
count_distinct, count_distinct_approx, median, percentile(x, p=),
weighted_avg(x, weight=col), stddev_samp, stddev_pop, var_samp, var_pop,
corr(x, other=col), covar_samp(x, other=col), covar_pop(x, other=col),
first(x[, time_col]) / last(x[, time_col]) (earliest/latest record's value per group),
plus model-defined custom aggregations. Write count_distinct(x), never count(distinct x). All aggregations support partition_by= (bare names: partition_by=region,
partition_by=[region, city], partition_by=[] for the grand total), computing the
aggregate at that coarser grain; the result is broadcast over the missing dimensions. Combine aggregations with arithmetic and transforms — missing dimensions broadcast on
both sides. E.g. with dimensions ["city", "region"], the measure
{"formula": "sum(total) / sum(total, partition_by=region)", "name": "share_of_region"}
is each city's share of its region's total. Aggregations nest: "avg(sum(total, partition_by=[region, city]), partition_by=[region])"
averages the per-city totals within each region. The top-level partition_by must be a
subset of the query's dimensions; inner aggregations' partition_by need not be. An
outer aggregation's parameters must be determined by the operand's grain — a cell
value at that grain, e.g. weight=count(id, partition_by=[region, city]), or a column
that grain fixes; any other row column is a typed error. Transforms wrap aggregated expressions: cumsum(x); change(x) / change_pct(x)
(period-over-period delta / % change — calendar-aware and partition-safe, prefer these
for growth); time_shift(x, -1[, 'year']) (the shifted value itself, for custom
arithmetic); lag(x, n) / lead(x, n) (row-position shift, NULL at edges); first(x) /
last(x) (broadcast the earliest/latest bucket's value); consecutive_periods(predicate)
(trailing run length; the predicate may be row-level, e.g. status = 'paid'); rank(x),
dense_rank(x), percent_rank(x), ntile(x, n=N) (rank family — optional partition_by=, no
time dimension needed). All other transforms require a time_dimensions entry.
Transforms nest in either order (change(cumsum(x))). Not supported: a row-level column
mixed into a composite or nested input of time_shift / change / change_pct, or mixed
with another aggregation's value inside one aggregation source. Cross-model: reference any joined model's field as model_name.field_name (or a
longer dotted path) and the engine figures out the join paths, avoiding fan-outs and
chasm traps — each aggregation computes over its own model's rows exactly once;
ambiguous routes error naming the candidates, and result keys use the full routed path.
An aggregation sliced by a dimension not attributable to it broadcasts its value with a
warning — see to_many_handling to attribute or error instead.
Method — decompose the question into blocks first: every qualifier, projected column,
filter, grouping, unit, rounding, and ordering hint is one block, and each must map to a
named column/measure/filter/dimension. Never drop a qualifier because no entity matched —
search for it, else encode it as an expression or an inline ModelExtension column;
reference already-encoded quantities by name rather than re-deriving their logic. Pin
explicitly rather than guessing: which aggregation ("typical" is not automatically avg vs
median), the grouping column and raw-vs-standardized labels, each aggregate's scope (all
rows vs a filtered subset), sort column + direction + tie-break, NULL handling, units and
rounding, exact numeric constants. "How many / count of" -> a scalar count(*); "which /
list / show" -> the rows. Project exactly the columns the question names — no extras,
none missing. Filter literals — build every ==/in/like predicate on a text column from that column's
sampled values (inspect it), never a guessed spelling; samples are a top-N snapshot, so
when a needed literal is absent verify it (e.g. a distinct-values query) rather than
assume either way. Compare case/whitespace-insensitively in the FILTER
position only, never on a projected, grouped, or join-key column; abbreviations that
case-folding can't unify go in the IN-set. Apply only the transformations
(TRIM/ROUND/CAST/dedup) the question or a governing definition requires. Verify — run the exact final query and read the result (show_sql=true when unsure): row
count plausible; no dimension-only GROUP BY when you wanted per-record rows
(distinct_dimension_values: false); sort column + direction as asked; each aggregate's
scope right; NULL behavior intended; string values carry the expected casing. On a wrong
result, change ONE variable at a time — two changes per attempt make the outcome
uninterpretable. Query-object fields taking the functional time-granularity form
gran(col) — gran one of second, minute, hour, day, week,
week_sunday, month, quarter, year:
dimensions: group-by columns; a granularity call such as
month(created_at) buckets that timestamp, equivalent to a
time_dimensions entry (and orderable as month(created_at)).
time_dimensions: time-bucketed group-bys — {"dimension": ..., "granularity": ...} dicts, or the string form month(created_at).
main_time_dimension: which time dimension time-ordered transforms key off. Top-level arguments (siblings of query, NOT fields inside it):
variables: Values for {placeholder} substitutions in filters / model SQL. Also
settable per query object; precedence: runtime (top-level) > named-stage >
outer-query > model.query_variables.
show_sql: When true, include the generated SQL in the response for debugging.
dry_run: When true, generate and return the SQL without executing it.
explain: When true, run EXPLAIN ANALYZE and return the query plan.
format: Output format — "markdown" (default, compact) | "json" | "csv". Case-insensitive. Without an explicit limit the response is capped at 20 rows with a truncation notice. Example: query(query={"source_model": "orders", "dimensions": ["status"],
"measures": [{"formula": "count(*)"}], "filters": ["status == 'completed'"]}) |
| models_summaryA | Brief summary of all (non-hidden) models in a datasource. DEV-1549: compact-by-default rendering. Under compact=True
each model section emits its name, description, the column count
(Columns: N), the comma-separated measure NAMES
(Measures: a, b, c) and the Joins to: list — no
per-column table, no per-measure formula block. Pass
compact=False to restore the verbose markdown / JSON shape
with full column and measure payloads. Args:
datasource_name: Name of the datasource (from list_datasources).
format: Output format — "markdown" (default, compact and
LLM-friendly) or "json" (structured array of model summaries).
Case-insensitive.
compact: Default True — drop per-column / per-measure detail.
Set False to surface the full per-model tables. |
| inspect_modelA | DEPRECATED: use the inspect tool. Return a complete-yet-compact view of a semantic model. Always emitted (regardless of sections): model header + description,
metadata bullets (data_source, sql_table, default_time_dimension,
hidden, row_count), backing-query structure for query-backed models,
and — when show_sql=True — the custom SQL block, model-level
filters, and the cached backing-query SQL. Section-gated parts (subset selectable via sections): columns — unified row-level columns table with a sampled
column (distinct values for string/boolean, min .. max for
number/date/time, or top20 ... (N distinct) for high-
cardinality categoricals).
measures — named-formula library.
aggregations — custom aggregation definitions. The formula
column and the sql field of each params[] entry are gated
by show_sql.
joins — join definitions.
samples — live sample-data query (COUNT(*) plus one
aggregation per column).
learnings — learning-only memories whose canonical entities
reference this model.
When a section is omitted from sections: columns, measures,
aggregations and joins collapse to a one-line backticked CSV
of names; samples and learnings are dropped entirely.
A footer at the end of the response lists what was trimmed and how
to fetch more. Args:
model_name: Name of the model to inspect.
num_rows: Max sample-data rows (default: 3).
show_sql: When true, include the generated SQL for the sample-data
query, the custom SQL block, model-level filters, the cached
backing-query SQL, and aggregation formulas/param SQL.
format: Output format — "markdown" (default) or "json".
Case-insensitive.
sections: Subset of ["columns", "measures", "aggregations", "joins", "samples", "learnings"]. Default (None
or empty list) renders all six. Unknown names are ignored
with a warning line at the end of the response. A non-empty
list of only unknown names resolves to no sections (not
all six) — "all sections" is reserved for None/[] so
a typo can't silently trigger the full expensive payload.
descriptions_max_chars: When set, every description field (model,
column, measure, aggregation) longer than this is truncated
with a ... [truncated] suffix. Must be >= 0. None
(default) means no truncation. |
| inspectA | Inspect EXACTLY one entity by reference and kind, a homogeneous
BATCH when reference is a list — or the whole COLLECTION at a kind
when reference is omitted / None. A clean point-lookup: no fusion / ranking / cypher, and no bundled
memories. Use search instead when you want an entity surfaced in
context (with related memories and ranked neighbours). Before using a column as a filter, projection, group-by, or join
key, inspect it and read its Description: (the schema author's
intent) and Sample values: (the stored literal forms — a top-N
sample, indicative rather than exhaustive; build text predicates
from these, never a guessed spelling). Never pick a column from its
name alone. Collection (DEV-1667): omit reference (or pass None / [])
to list a whole kind. entity_type="model" lists all models grouped
by datasource (compact=True: one terse line per model; compact=False:
the full per-model tables). entity_type="datasource" lists all
datasources. Only model / datasource support the collection
view; other kinds raise. This subsumes models_summary /
list_datasources. Batch (DEV-1612): pass a list of references that all share the one
entity_type. Returns one rendered block per id, in input order,
each echoing its resolved canonical id (a ## <canonical> header in
markdown; a JSON array under format="json"). Per-id resolution
errors are isolated — one bad id does not sink the batch (in JSON it
becomes a {"reference": ..., "error": ...} element). A single
str keeps its byte-for-byte single output; a one-element list is
still batch-framed. Args:
reference: The entity reference, or a list of references (batch).
Accepts canonical forms (mydb, mydb.orders,
mydb.orders.amount), bare names, join paths
(orders.customers.region → resolved to the owning model),
and memory:<id> for memories. Normalised via the shared
resolver; the normalised canonical id is echoed in the JSON
shape.
entity_type: REQUIRED. One of datasource, model,
column, measure, aggregation, memory.
Disambiguates the 3-part canonical collision (a name
shared by, e.g., a column and an aggregation) and asserts
the resolved kind — a mismatch returns a detailed error.
compact: When true (default): description-only for
column/measure/aggregation/datasource/memory; for
entity_type="model" a cheap schema skeleton (column /
measure / aggregation names + join targets, zero DB calls).
False returns the full render (and, for the datasource kind,
a per-model skeleton for each visible model).
format: "markdown" (default) or "json".
num_rows: Sample-data rows for entity_type="model". Ignored
(with a warning) for other kinds.
show_sql: Include generated SQL for entity_type="model".
Ignored (with a warning) for datasource/memory; a silent
no-op for column/measure/aggregation.
sections: Section subset for entity_type="model". Ignored
(with a warning) for other kinds.
descriptions_max_chars: Truncate description fields to this many
characters. Applies to every kind. |
| create_modelA | Create a new semantic model, either from a database table or from a query. Host a column/measure on the model whose row grain is 1:1 with what
it describes — not merely one where its input columns live. Choose
join keys by column Description (author intent); on ties take the
shortest declared join path (long chains through lookup/log tables
fan out rows). Encode definitions in dependency order, referencing
already-defined entities by name rather than re-deriving them inline;
in row-level SQL parenthesise weighted sums in comparisons
((a*w1 + b*w2) > t). From a table or sql query (provide sql_table or sql):
create_model(name="orders", sql_table="public.orders", data_source="mydb",
columns=[...], measures=[...]) From a query (provide query):
create_model(name="monthly_summary", query={"source_model": "orders",
"measures": ["count(*)", "sum(amount)"],
"time_dimensions": [{"dimension": "created_at", "granularity": "month"}]})
Columns are auto-introspected from the query result. Args:
name: Unique model name (lowercase, underscores).
sql_table: Database table name, e.g. "public.orders".
sql: Alternative to sql_table — a custom SQL expression for the model's source.
data_source: Name of the datasource (from list_datasources).
description: What this model represents.
columns: List of column definitions. Each: {"name": "col", "sql": "col", "type": "string"}.
Types: string, number, time, date, boolean. Optional fields: primary_key,
unique (single-column uniqueness that is not the PK; primary_key
already implies it), allowed_aggregations (whitelist), filter
(CASE WHEN inside aggregation), label, description, hidden,
meta.
measures: List of named formula definitions on the model. Each:
{"name": "aov", "formula": "sum(revenue) / count(*)", "label": "...",
"description": "...", "meta": {...}}.
Queries can reference these by bare name (e.g. {"formula": "aov"}).
meta is an optional opaque dict for caller bookkeeping
(e.g. linking the formula back to a source identifier).
query: A SLayer query dict (or list of stage dicts for a multi-stage backing
query). When provided, the query is saved as the model's source_queries
and the model becomes query-backed. Mutually exclusive with sql_table, sql,
columns, and measures.
variables: Default values for {var} placeholders in the backing query.
Saved as query_variables on the model. Only meaningful when query
is provided. |
| edit_modelA | Edit an existing model in a single call — update metadata, upsert columns/measures/aggregations/joins,
manage filters, and remove entities. Host a column/measure on the model whose row grain is 1:1 with what
it describes — not merely one where its input columns live. Choose
join keys by column Description (author intent); on ties take the
shortest declared join path (long chains through lookup/log tables
fan out rows). Encode definitions in dependency order, referencing
already-defined entities by name rather than re-deriving them inline;
in row-level SQL parenthesise weighted sums in comparisons
((a*w1 + b*w2) > t). Args:
model_name: Name of the model to edit.
description: New model description.
data_source: Lookup key — the datasource the model belongs to.
Required when the same name exists in multiple datasources
(otherwise the priority list / single-match rules apply).
new_data_source: Move the model to a different datasource (rare;
renames its storage location). Pass None (default) to
leave the data_source unchanged.
default_time_dimension: Default time dimension (a column of type date/time) for
time-dependent transforms.
sql_table: Database table name. Setting this clears sql and source_queries.
sql: Custom SQL expression for the model source. Setting this clears sql_table and source_queries.
source_queries: Replace the model's backing query with this list of stages.
Each stage is a SlayerQuery dict; non-final stages must have a name.
Setting this clears sql_table and sql, makes the model query-backed,
and refreshes the cached columns and backing_query_sql.
query_variables: Replace the model's default {var} placeholder values for
its backing query. Pass null/None to clear. Only meaningful for
query-backed models.
hidden: Whether this model is hidden from discovery.
meta: Arbitrary JSON metadata for the model (replaces existing meta). Pass null/None to clear.
columns: Columns to create or update (upsert by name). Each dict:
{"name": "col", "type": "string", "sql": "col", "description": "...",
"primary_key": false, "unique": false, "hidden": false,
"allowed_aggregations": ["sum", "avg"],
"filter": "status = 'active'", "label": "..."}.
If a column with this name exists, only the provided fields are updated.
Types: string, number, time, date, boolean.
unique marks single-column uniqueness that is not the primary key
(primary_key already implies it); it is used to infer join
cardinality.
measures: Named formula measures to create or update (upsert by name). Each dict:
{"name": "aov", "formula": "sum(revenue) / count(*)", "label": "...",
"description": "...", "meta": {...}}.
Queries can reference these by bare name (e.g. {"formula": "aov"}).
meta is an optional opaque dict for caller bookkeeping.
aggregations: Aggregations to create or update (upsert by name). Each dict:
{"name": "weighted_avg", "formula": "SUM({value} * {weight}) / NULLIF(SUM({weight}), 0)",
"params": [{"name": "weight", "sql": "quantity"}], "description": "...",
"meta": {...}}.
meta is an optional opaque dict for caller bookkeeping.
joins: Joins to create or update (upsert by target_model). Each dict:
{"target_model": "customers", "join_pairs": [["customer_id", "id"]],
"cardinality": "many_to_one", "description": "...", "meta": {...}}.
A composite key is one join with several join_pairs entries, not
one join per column. cardinality is the join's arity read
source->target, one of one_to_one / one_to_many /
many_to_one / many_to_many; omit it when undetermined. It is
descriptive metadata only — it changes neither join_type nor
query results.
add_filters: SQL filter strings to add (e.g. ["deleted_at IS NULL"]). Duplicates ignored.
remove_filters: SQL filter strings to remove (exact match).
remove: Named entities to delete, keyed by type:
{"columns": ["col_name"], "measures": ["measure_name"],
"aggregations": ["agg_name"], "joins": ["target_model_name"]}.
Removals are processed before upserts. Example — update a column and add a named measure:
edit_model(model_name="orders",
columns=[{"name": "status", "type": "string"}],
measures=[{"name": "aov", "formula": "sum(revenue) / count(*)"}])
Example — remove a measure:
edit_model(model_name="orders", remove={"measures": ["old_metric"]}) |
| create_datasourceA | Create a database connection, verify it, and auto-ingest models. Use ${ENV_VAR} syntax in credentials to reference environment variables. Args:
name: Unique datasource name.
type: Database type — postgres, mysql, sqlite, bigquery, or snowflake.
host: Database host (default: localhost).
port: Database port (e.g. 5432 for Postgres).
database: Database name.
username: Database username.
password: Database password.
connection_string: Full connection string as alternative to individual fields.
schema_name: Default schema name. Also used as the single schema for auto-ingestion.
schemas: Comma-separated schemas to ingest. Mutually exclusive with schema_name / all_schemas.
all_schemas: Ingest every non-system schema. Mutually exclusive with schema_name / schemas.
auto_ingest: Automatically ingest models from the database schema (default: true). Set to false to skip. Example: create_datasource(name="mydb", type="postgres", host="localhost", port=5432, database="app", username="user", password="pass") |
| list_datasourcesA | List all configured database connections (names and types only, credentials are not shown). Use describe_datasource for connection details and status. |
| describe_datasourceA | Show datasource details: connection status, available schemas, and (by default) the tables in the given or default schema. Use this after create_datasource to verify the connection and explore
what's queryable before calling ingest_datasource_models. Args:
name: Datasource name (from list_datasources).
list_tables: If True (default), append a list of tables from the
schema named by schema_name (or the dialect's default
schema when empty).
schema_name: Database schema to list tables from (e.g. "public").
Empty uses the dialect default. Ignored when list_tables=False. |
| edit_datasourceB | Update a datasource's metadata. Args:
name: Datasource name to update.
description: New description for the datasource. |
| delete_modelA | Delete a semantic model. Args:
name: Model name to delete.
data_source: Datasource the model belongs to. Required when the
same name exists in multiple datasources (otherwise the
priority list / single-match rules apply). |
| validate_modelsA | Diff persisted SLayer models against the live database schema(s). Returns a JSON-serialized list of pending delete operations
(column drops, measure drops, join drops, filter removals, whole
models) needed to keep stored models valid against the current
live state. Read-only — does not modify storage. Args:
data_source: Datasource name to validate. When omitted, every
datasource is validated concurrently and results are
concatenated. |
| recommend_root_modelA | Recommend the root model (query source_model) for a set of
model.column / model.metric items, and give each item's
join-qualified reference path from that root. Introspects the join graph and picks the model from which every
requested item is reachable (LEFT joins are directional; INNER
joins traverse both ways), minimizing total join hops. The returned
paths are ready to drop into a query whose source_model is the
recommended root — e.g. a joined column comes back as
customers.regions.name and a root-owned one as status;
aggregation spellings (sum(revenue) / revenue:sum) are preserved. When no single model reaches everything, root_model is null and
coverage lists the best partial roots so you can split the
request into a multi-stage query. Call this once your item list is final, not as a schema browser —
explore with search / inspect first. Args:
items: entity references (orders.revenue, customers.name,
orders.revenue:sum / sum(orders.revenue), bare aov for a saved metric...).
data_source: optional datasource scope; when omitted, names
resolve via the datasource-priority list. All items must
resolve to a single datasource.
root_hint: optional intended root — a bare model name or
<data_source>.<model> within the resolved datasource.
Honored when it reaches every item (overriding the min-hops
pick, so you can force a bridge model that owns none of the
items); otherwise the auto-pick is used and a warning
explains why. Resolved after the datasource is determined,
so it cannot pick the datasource.
format: "markdown" (default) or "json". |
| delete_datasourceB | Delete a datasource configuration. Args:
name: Datasource name to delete. |
| ingest_datasource_modelsA | Auto-discover tables in a database and create / additively update semantic models from them. Idempotent (DEV-1356): re-runs are additive only. New columns and joins
are appended to existing models; existing column / join definitions
are never overwritten. After the additive pass, returns the pending
validate_models deletes alongside the additions. Args:
datasource_name: Name of an existing datasource (from list_datasources).
include_tables: Comma-separated list of table names to include. If empty, all tables are ingested.
schema_name: A single database schema to inspect (e.g. "public"). Empty uses the default schema.
schemas: Comma-separated schemas to inspect. Mutually exclusive with schema_name / all_schemas.
all_schemas: Ingest every non-system schema. Mutually exclusive with schema_name / schemas. |
| set_datasource_priorityA | Configure how SLayer disambiguates bare model names that exist in
multiple datasources. When two datasources both define a model named users, calling
edit_model("users") (no data_source=) is ambiguous. SLayer
walks this priority list and picks the first datasource that has
the requested name. If none of the candidates appear in the list,
an AmbiguousModelError is raised. Args:
priority: Datasource names, most-preferred first. Each entry
must already exist (run list_datasources first). Pass
an empty list to clear the priority. |
| get_datasource_priorityA | Return the configured datasource priority list (most-preferred
first), or [] if none is set. |
| save_memoryA | Save an agent memory: a free-form note plus the SLayer
entities it concerns. linked_entities accepts either:
a list of entity reference strings — each item is resolved to
the canonical <datasource>.<model>[.<leaf>] form. Bare
names use the datasource priority list; ambiguous bare-column
matches are rejected. memory:<id> is also valid here
(cross-memory references; the target memory must exist). a SlayerQuery (dict) — entities are auto-extracted from
source_model, dimensions, time_dimensions,
measures, and filters; resolution warnings are
non-fatal. The query itself is stored alongside the
learning, so the memory surfaces in search's
example_queries list (vs the memories list for
entity-list memories).
DEV-1428: id is an optional canonical memory id. Omit to
auto-allocate a monotonic int-shaped id ("1", "2", ...);
supply a string for a stable user-controlled id
("kb.policy.42"). Charset excludes :, /, ?,
#, whitespace. Duplicate id → unconditional upsert,
created_at preserved. Returns the assigned memory_id (string), the canonical
entities stored, and any non-fatal warnings. Cascade-on-delete: when a model / datasource / measure is
deleted, every memory:<id> and <ds>.<model>[.<leaf>]
reference under it is automatically stripped from every other
memory's entities list. Memories with zero entities after
the strip are kept (the learning text stands alone). Search is lenient: stale entity tags in saved memories are
filtered out at retrieval time rather than raising. Args:
learning: The note text. Required, non-empty.
linked_entities: List of entity strings, or an inline
SlayerQuery payload.
id: Optional canonical memory id (see above). Examples:
save_memory(
learning="orders.is_returned in {0,1,NULL}; treat NULL as not returned",
linked_entities=["orders.is_returned"],
) save_memory(
learning="Paid revenue by status",
linked_entities={
"source_model": "orders",
"measures": [{"formula": "sum(amount)"}],
"filters": ["status = 'paid'"],
},
id="kb.paid-revenue",
)
|
| forget_memoryA | Delete a memory by id. Cascades: every other memory's memory:<id> reference to
this id is automatically stripped from its entities list. Args:
id: The memory_id returned by save_memory. Accepts
strings (the canonical form, including user-supplied
"kb.policy"-style ids) as well as legacy ints
(coerced to their decimal string form). Raises a friendly error if the id is invalid or the memory does
not exist. |
| searchA | Up to three-channel semantic search over memories + canonical entities. Call this BEFORE query to surface any notes or example
queries previously saved against the entities you're
considering. Discovery, not detail: hits come back as one-line descriptions —
pick candidate ids here, then read their full bodies with
inspect (batching same-kind ids in one call). A broad
compact=False search drags full renders into cached context on
every later turn for no added signal. Channel 1 (entity-overlap BM25 over memories): runs when
entities and/or query is supplied. Memories whose
canonical entity tags overlap the resolved input are ranked. Channel 2 (tantivy full-text over memories ∪ entities): runs
when question is supplied. The in-memory index covers every
memory + every searchable entity (datasource / non-hidden model /
non-hidden column / named measure / aggregation). Channel 3 (dense embedding similarity, optional): runs when
question is supplied AND the advanced_search extra is
installed AND a provider API key is configured for the active
embedding model. Cosine similarity between the question
embedding and persisted entity/memory embeddings. Skipped with
a single warning into SearchResponse.warnings when any
precondition fails — tantivy + BM25 continue to work. All hits (memories, example queries, entities) are fused via
Reciprocal Rank Fusion (k=60) into a single ranked
results list capped at max_results. Empty input (no entities, no query, no question) returns the
newest memories capped at max_results, with a warning. Args:
entities: Canonical entity reference strings.
query: Optional SlayerQuery (dict). Entities are
auto-extracted to broaden channel-1 input.
question: Free-text query for the tantivy full-text channel.
datasource: Optional datasource name. When set, scope all
three channels to that one datasource. Entity hits are
limited to docs rooted at the datasource (exact match
or dotted-path descendant). Memories surface when any
of their tagged entities is rooted at the datasource —
a memory spanning multiple datasources surfaces from
each. BM25 / IDF stats reflect only the filtered subset.
Unknown datasource raises ValueError.
max_results: Maximum total number of hits to return (default 10).
cypher_filter: Optional openCypher MATCH query returning
… AS id that pre-filters all three channels to the
returned canonical IDs — narrow to one kind so
max_results isn't spent on an RRF-fused mix of
memories, columns, measures, and models. When
advanced_search is not installed, only simple
MATCH (n:Label1:Label2) RETURN n.id AS id patterns are
supported as a kind filter (multi-label uses union
semantics; allowed labels: Memory, Datasource, Model,
ModelColumn, Measure, Aggregation — use ModelColumn,
not Column, which resolves only on the naive fallback). |