tdfs4ds.agent.graph¶
tdfs4ds.agent.graph
¶
LangGraph-based orchestration for the tdfs4ds consumer agent.
Flow::
START -> node_classify -> node_detect_domain -> node_recognize_entity -> [conditional on intent] -> node_skill_* -> node_synthesize -> END
_GROUP_PRONOUN_TOKENS
module-attribute
¶
_GROUP_PRONOUN_TOKENS = ('these features', 'those features', 'the features', 'the feature group', 'the ones', 'the ones i just', 'i just saw', 'the same features', 'ces features', 'ces variables', 'celles', 'celles-ci', 'celles-là', 'les features', 'le groupe', 'le même groupe')
_ALGORITHM_UNAVAILABLE_INTENT
module-attribute
¶
_MCP_INTENT_UNAVAILABLE_INTENT
module-attribute
¶
_ALGORITHM_QUESTION_RE
module-attribute
¶
_ALGORITHM_QUESTION_RE = compile('(algorithm|algorithme|hyper.?param|model code|code du mod|python package|paquet|sklearn|scikit|machine.?learning model|mod[eè]le de machine learning|quel mod[eè]le|which model|what model)', IGNORECASE)
_EXPLAIN_SHAPE_RE
module-attribute
¶
_EXPLAIN_SHAPE_RE = compile('\\b(how\\s+is|how\\s+are|how\\s+do(?:es)?|computed|calculat|formula|logic|derived|comment\\s+est|calcul|logique|formule)\\b', IGNORECASE)
_LINEAGE_SHAPE_RE
module-attribute
¶
_LINEAGE_SHAPE_RE = compile("(where\\s+(does|do|is)|come[s]?\\s+from|depend[s]?\\s+on|upstream|downstream|consum|impact|lineage|d[' ]?o[uù]\\s+vien|d[eé]pend|en\\s+amont|en\\s+aval|lignage)", IGNORECASE)
_DEFINITION_SHAPE_RE
module-attribute
¶
_DEFINITION_SHAPE_RE = compile("(what\\s+is|what\\s+are|what\\s+does|define|meaning|signif|qu[' ]?est|quelle?\\s+est|d[eé]finition)", IGNORECASE)
_FRESHNESS_SHAPE_RE
module-attribute
¶
_FRESHNESS_SHAPE_RE = compile('(fresh|up.?to.?date|last\\s+(refresh|updat|run)|stale|à\\s+jour|fra[iî]ch|derni[eè]re?\\s+(mise|exécution))', IGNORECASE)
_DATASET_PENDING_ACTIONS
module-attribute
¶
_DATASET_PENDING_ACTIONS = {'awaiting_feature_selection', 'awaiting_feature_confirmation', 'awaiting_dataset_kind', 'awaiting_dataset_name', 'awaiting_version_selection', 'awaiting_entity_clarification'}
_BUILD_REQUEST_RE
module-attribute
¶
_BUILD_REQUEST_RE = compile('\\b(build|create|make|generate|cr[ée]e[rz]?|cr[ée]ons|construi[st]?|construire|g[ée]n[èe]re[rz]?)\\b', IGNORECASE)
_CLARIFICATION_PENDING_ACTION
module-attribute
¶
_FEATURE_ENTITY_CLARIFICATION_PENDING_ACTION
module-attribute
¶
_NEW_QUESTION_RE
module-attribute
¶
_NEW_QUESTION_RE = compile('^\\s*(what|which|how|where|who|why|when|list|show|give|tell|is|are|can|could|do|does|quel|quelle|quels|quelles|comment|où|qui|pourquoi|quand|liste|listez|montre|montrez|donne|donnez|est-ce|peux|pouvez)\\b', IGNORECASE)
_FEATURE_INVENTORY_RE
module-attribute
¶
_FEATURE_INVENTORY_RE = compile('^\\s*(?:what|which)\\s+(?:are\\s+(?:all\\s+)?the\\s+|are\\s+)?(?:all\\s+)?(?:the\\s+)?(?:available\\s+|existing\\s+|registered\\s+)?features?\\b(?:\\s+(?:are|do\\s+we\\s+have|exist|available|registered|in\\s+the\\s+(?:feature\\s+)?store))*\\s*\\??\\s*$|^\\s*(?:list|show|give)(?:\\s+me)?\\s+(?:all\\s+)?(?:the\\s+)?(?:available\\s+)?features\\s*\\??\\s*$|^\\s*quelles?\\s+sont\\s+les\\s+(?:features|caract[ée]ristiques)(?:\\s+disponibles)?\\s*\\??\\s*$|^\\s*(?:liste[rz]?|montre[rz]?)(?:-moi)?\\s+(?:toutes\\s+)?les\\s+features\\s*\\??\\s*$', IGNORECASE)
_BARE_SAMPLE_RE
module-attribute
¶
_BARE_SAMPLE_RE = compile("^\\s*(?:(?:can\\s+you\\s+|could\\s+you\\s+|please\\s+)?(?:give|show|get)(?:\\s+me)?\\s+)?(?:a\\s+|an\\s+|some\\s+|a\\s+few\\s+|\\d{1,3}\\s+)?(?:sample|samples|rows?|lines?|records?|extract|preview|example\\s+rows?)(?:\\s+(?:of|from)\\s+(?:it|this|that|them|the\\s+data(?:set)?|this\\s+dataset|that\\s+dataset))?(?:\\s*,?\\s*please)?\\s*[?.!]?\\s*$|^\\s*(?:(?:donne|montre)[sz]?(?:[-\\s]moi)?\\s+)?(?:un\\s+|des\\s+|quelques\\s+|\\d{1,3}\\s+)?(?:[ée]chantillons?|extraits?|lignes?|exemples?\\s+de\\s+lignes?)(?:\\s+(?:de\\s+(?:ce|cet|ces)\\s+(?:dataset|donn[ée]es)|du\\s+dataset))?(?:\\s*,?\\s*s'il\\s+(?:te|vous)\\s+pla[iî]t)?\\s*[?.!]?\\s*$", IGNORECASE)
_DATASET_INVENTORY_RE
module-attribute
¶
_DATASET_INVENTORY_RE = compile('^\\s*(?:what|which|list|show|give|tell)\\b[^?]*\\bdatasets\\b[^?]*\\??\\s*$|^\\s*(?:quels?|quelles?|liste[rz]?|montre[rz]?|donne[rz]?)\\b[^?]*\\b(?:datasets|jeux de donn[ée]es)\\b[^?]*\\??\\s*$', IGNORECASE)
_DATASET_SCOPED_RE
module-attribute
¶
_DATASET_SCOPED_RE = compile('\\b(expos\\w*|contain\\w*|includ\\w*|with|for|of|has|have|avec|pour|contien\\w*|incluan\\w*|qui ont)\\b', IGNORECASE)
_OBJECT_PRONOUN_RE
module-attribute
¶
_OBJECT_PRONOUN_RE = compile('\\b(?:this|that|the\\s+same|its?)\\s+(?:source\\s+)?(?:table|view|object|source)s?\\b|\\b(?:cette|cet|ce|la\\s+m[êe]me)\\s+(?:table|vue|objet|source)s?\\b', IGNORECASE)
_LINEAGE_WORDS_RE
module-attribute
¶
_LINEAGE_WORDS_RE = compile('\\b(?:lineage|lignage|upstream|downstream|amont|aval|depends?|dependenc\\w+|d[ée]pend\\w*|used\\s+by|uses?|feeds?|fed|comes?\\s+from|provien\\w*|alimente\\w*|utilis\\w+\\s+par|impact\\w*)\\b', IGNORECASE)
_SOURCE_DATA_REROUTE
module-attribute
¶
_SOURCE_DATA_REROUTE = {'SEARCH', 'OUT_OF_SCOPE', 'DEFINITION', 'SUMMARY', 'HELP', 'DATA_QUERY', 'COMPOUND'}
_TRAINING_RE
module-attribute
¶
_INVENTORY_FILLER
module-attribute
¶
_INVENTORY_FILLER = frozenset({'dataset', 'datasets', 'data', 'set', 'sets', 'feature', 'features', 'model', 'models', 'used', 'use', 'using', 'available', 'registered', 'exist', 'exists', 'existing', 'train', 'trained', 'training', 'build', 'built', 'store', 'jeux', 'jeu', 'donnees', 'données', 'quels', 'quelles', 'sont', 'pour', 'les', 'des', 'disponibles', 'utilises', 'utilisés', 'entrainer', 'entraîner', 'modele', 'modèle', 'modeles', 'modèles', 'ont', 'ete', 'été', 'dans', 'sur', 'une', 'qui', 'servent', 'serve', 'serves'})
_REFINE_RE
module-attribute
¶
_REFINE_RE = compile("^\\s*(?:i\\s+meant|i\\s+mean|i\\s+would\\s+like|i'?d\\s+like|i\\s+want|what\\s+about|how\\s+about|and|only|rather|just|but|for|instead|je\\s+voulais\\s+dire|je\\s+veux\\s+dire|je\\s+voudrais|plut[ôo]t|et|seulement|uniquement|pour|mais)\\b", IGNORECASE)
_LIST_ITEM_RE
module-attribute
¶
_LIST_ITEM_RE = compile('^\\s*(?:[-*]\\s+)?(?:\\*\\*)?(\\d{1,2})(?:\\*\\*)?[.)](?:\\*\\*)?\\s+(.+?)\\s*$')
_SINGLE_CHOICE_RE
module-attribute
¶
_SINGLE_CHOICE_RE = compile("^\\s*(?:the\\s+|le\\s+|la\\s+)?(?:(?:option|choice|number|num[ée]ro|n°|no\\.?|#)\\s*)?(\\d{1,2})\\s*(?:st|nd|rd|th|e|er|[eè]me)?\\s*[.)]?\\s*(?:one|please|s'il\\s+(?:te|vous)\\s+pla[iî]t)?\\s*[.!]?\\s*$", IGNORECASE)
_NUMBERED_ANSWER_RE
module-attribute
¶
_ORDINALS
module-attribute
¶
_ORDINALS = {'first': 1, '1st': 1, 'premier': 1, 'première': 1, 'premiere': 1, 'second': 2, '2nd': 2, 'deuxième': 2, 'deuxieme': 2, 'seconde': 2, 'third': 3, '3rd': 3, 'troisième': 3, 'troisieme': 3, 'fourth': 4, '4th': 4, 'quatrième': 4, 'quatrieme': 4, 'fifth': 5, '5th': 5, 'cinquième': 5, 'cinquieme': 5, 'last': -1, 'dernier': -1, 'dernière': -1, 'derniere': -1}
_ORDINAL_REF_RE
module-attribute
¶
_ORDINAL_REF_RE = compile("\\b(?:the|le|la|l')\\s*(" + join(sorted(_ORDINALS, key=len, reverse=True)) + ')(?:\\s+(?:one|feature|item|dataset|column|row|entry)\\b|(?=\\s*(?:[?.!,;]|$)))', IGNORECASE)
_POINTING_WORD_RE
module-attribute
¶
_POINTING_WORD_RE = compile('\\b(?:it|its|them|they|these|those|this\\s+one|that\\s+one|the\\s+dataset|this\\s+dataset|that\\s+dataset|ce\\s+dataset|cet?\\s+objet|ces\\s+datasets|ils?|elles?|ça|cela|celui-ci|celle-ci)\\b', IGNORECASE)
_FRESHNESS_WORDS_RE
module-attribute
¶
_FRESHNESS_WORDS_RE = compile('\\b(?:up\\s+to\\s+date|up-to-date|fresh|freshness|stale|last\\s+(?:refresh\\w*|update\\w*|run)|refreshed|updated|à\\s+jour|fra[iî]che?|dernière\\s+mise\\s+à\\s+jour)\\b', IGNORECASE)
_POINTING_INTENTS
module-attribute
¶
_POINTING_INTENTS = {'FRESHNESS', 'DEFINITION', 'LINEAGE', 'USAGE', 'EXPLAIN', 'SUMMARY', 'DATA_QUERY'}
_REFERENCE_WORD_RE
module-attribute
¶
_REFERENCE_WORD_RE = compile('(?i)\\b(it|its|that|this|these|those|them|they|same|above|previous|ce|cet|cette|ces|celui|celle|ceux|celles|cela|ça|il|elle|ils|elles)\\b')
_PRONOUNS
module-attribute
¶
_PRONOUNS = {'it', 'this', 'that', 'they', 'them', 'these', 'those', 'this one', 'that one', 'this feature', 'that feature', 'the feature', 'these features', 'those features', 'il', 'elle', 'ça', 'ca', 'cela', 'ceci', 'celle-ci', 'celui-ci', 'cette feature', 'ce feature', 'ces features', 'la feature', 'le feature'}
_ROUTING_STOP_WORDS
module-attribute
¶
_ROUTING_STOP_WORDS = {'what', 'is', 'the', 'a', 'an', 'does', 'mean', 'how', 'why', 'this', 'that', 'it', 'are', 'was', 'these', 'those', 'feature', 'features', 'tell', 'about', 'from', 'with', 'and', 'or', 'for', 'show', 'give', 'list', 'explain', 'define', 'qui', 'est', 'la', 'le', 'les', 'un', 'une', 'de', 'du', 'des', 'cette', 'ce', 'que', 'quel', 'quelle', 'ces', 'sont', 'quoi', 'comment'}
_COMPARE_RE
module-attribute
¶
_COMPARE_RE = compile('\\b(similar|same\\s+as|the\\s+same|identical|equivalent|equal\\s+to|compar\\w*|differen\\w*|versus|vs|match(?:es|ing)?|correspond\\w*|semblable|similaire|identique|m[eê]me|[ée]quivalent\\w*|diff[ée]ren\\w*|corresp\\w*)\\b', IGNORECASE)
_DASHBOARD_HINT_TOKENS
module-attribute
¶
_DASHBOARD_HINT_TOKENS = ('dashboard', 'report', 'select', 'top ', 'highest', 'lowest', 'query for', 'build a', 'join', 'rows', 'filter', 'list of')
_AS_OF_INTENTS
module-attribute
¶
_AS_OF_INTENTS = {'SUMMARY', 'DATA_DOMAINS', 'DEFINITION', 'DATASET', 'USAGE', 'SEARCH', 'FRESHNESS', 'LINEAGE', 'COMPOUND'}
_INTENT_NODE_MAP
module-attribute
¶
_INTENT_NODE_MAP = {'SEARCH': node_skill_search, 'DEFINITION': node_skill_definition, 'USAGE': node_skill_usage, 'FRESHNESS': node_skill_freshness, 'SUMMARY': node_skill_summary, 'DATA_DOMAINS': node_skill_data_domains, 'FEASIBILITY': node_skill_feasibility, 'HELP': node_skill_help, 'RECAP': node_skill_recap, 'LINEAGE': node_skill_lineage, 'EXPLAIN': node_skill_explain, 'DATASET': node_skill_dataset, 'BUILD_DATASET': node_skill_build_dataset, 'PLOT': node_skill_plot, 'VALUES': node_skill_values, 'OUT_OF_SCOPE': node_skill_out_of_scope}
_SKILL_LABEL
module-attribute
¶
_SKILL_LABEL = {'SEARCH': 'skill_search', 'DEFINITION': 'skill_definition', 'USAGE': 'skill_usage', 'FRESHNESS': 'skill_freshness', 'SUMMARY': 'skill_summary', 'LINEAGE': 'skill_lineage', 'EXPLAIN': 'skill_explain', 'ALGORITHM': 'skill_algorithm', 'DATASET': 'skill_dataset', 'BUILD_DATASET': 'skill_build_dataset', 'DATA_QUERY': 'skill_data_query', 'MCP_TOOLS': 'skill_mcp_tools'}
AgentState
¶
Bases: TypedDict
_features_df
¶
feature_catalog(all_data_domains).to_pandas(), from the turn snapshot.
intent_classifier
¶
intent_classifier(question: str, intent_model=None, intent_descriptions: dict = None, context_hint: Optional[str] = None)
Classify a user question into one of the available intents.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
question
|
str
|
User question in natural language (French or English). |
required |
intent_model
|
Pydantic model for structured output. When |
None
|
|
intent_descriptions
|
dict
|
|
None
|
context_hint
|
Optional[str]
|
Optional context about recently mentioned features/datasets (e.g., "Last feature: provider_z_score in entity CLAIM_ID"). Added to the system prompt to help resolve vague follow-up questions. |
None
|
Returns:
| Type | Description |
|---|---|
|
An instance of |
|
|
|
get_data_domain
¶
List all DATA_DOMAINs available in the feature store.
Returns:
| Type | Description |
|---|---|
DataFrame
|
DataFrame with columns |
detect_data_domain_for_object
¶
detect_data_domain_for_object(object_name: str, prefer_domain: Optional[str] = None) -> Optional[str]
Return the DATA_DOMAIN that contains object_name as a feature name.
Searches the full feature catalog (all data domains). Falls back to the process catalog (VIEW_NAME) if the feature is not found by name.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
object_name
|
str
|
Feature or view name to locate. |
required |
prefer_domain
|
Optional[str]
|
When the object exists in multiple domains, return this one if it is among the matches. Used to prevent silent context switches on ambiguous feature names (e.g. "Claims" appearing in multiple feature stores). |
None
|
Returns:
| Type | Description |
|---|---|
Optional[str]
|
Matching DATA_DOMAIN string, or |
Cross-store federation limit (v0.3.1.148): the catalog scanned here is the
federated UNION across the primary + read-only stores, but resolution returns a
bare DATA_DOMAIN with NO store tag (and _with_domain only switches
tdfs4ds.DATA_DOMAIN, not the active store). With DISJOINT stores a domain
uniquely identifies its store, so this is correct. If two stores share a domain
name, _pick returns the first match (primary rows sort first → primary
wins); making resolution store-aware is the documented follow-up.
skill_search
¶
Find features or datasets matching a business need expressed in natural language.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
question
|
str
|
User question describing the business need. |
required |
Returns:
| Type | Description |
|---|---|
dict
|
Dict with key |
skill_compose_dashboard
¶
Multi-feature dataset composition for dashboard-style requests.
Decomposes the question into a feature list, then re-uses
:func:skill_dataset multi-feature logic to find datasets exposing all
of them (or report a JOIN gap when none does).
Returns the same shape as multi-feature skill_dataset plus the
extracted feature_names so the synthesiser can confirm what was matched.
skill_definition
¶
skill_definition(question: str, object_name: str, hint_entity_name: Optional[str] = None, hint_table_name: Optional[str] = None, accumulated_context: Optional[dict] = None, _fuzzy_corrected: bool = False) -> dict
Explain what a feature, column, or dataset is in plain business language.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
question
|
str
|
User question. |
required |
object_name
|
str
|
Name of the feature, column, or dataset to define. |
required |
hint_entity_name
|
Optional[str]
|
Entity name remembered from a previous turn — used to auto-disambiguate when the same feature name exists across entities. |
None
|
hint_table_name
|
Optional[str]
|
Source table/view remembered from a prior EXPLAIN turn — used to narrow column-level BD lookups to the right table when the column is not a registered feature. |
None
|
Returns:
| Type | Description |
|---|---|
dict
|
Dict with |
skill_definition_multi
¶
skill_definition_multi(question: str, feature_names: list, hint_entity_name: Optional[str] = None, accumulated_context: Optional[dict] = None, column_sources: Optional[dict] = None) -> dict
Unified multi-feature definition using a shared context graph.
Calls _build_multi_object_context() once to resolve all features and fetch shared documentation (process views, datasets, entities), then makes a single LLM call with the full unified context. No consolidation pass needed.
skill_usage
¶
skill_usage(question: str, object_name: str, accumulated_context: Optional[dict] = None, entity_name: Optional[str] = None) -> dict
Advise a consumer on how to safely use a feature in their work.
Enumerates ALL datasets that expose the feature (not just one) and emits a paste-ready SELECT snippet for each so the consumer can pick the right view for their dashboard.
Implements fast-path caching: queries accumulated_context_graph first (cache hit → return immediately), falls back to catalog scans (cache miss).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
question
|
str
|
User question about usage (Tableau, Excel, regulatory, etc.). |
required |
object_name
|
str
|
Name of the feature or dataset. |
required |
accumulated_context
|
Optional[dict]
|
The accumulated_context_graph from state (used for cache). |
None
|
entity_name
|
Optional[str]
|
Entity name for disambiguation (used in cache lookups). |
None
|
Returns:
| Type | Description |
|---|---|
dict
|
Dict with |
dict
|
|
dict
|
|
skill_freshness
¶
skill_freshness(question: str, object_name: str, hint_entity_name: Optional[str] = None, accumulated_context: Optional[dict] = None, _fuzzy_corrected: bool = False) -> dict
Tell the consumer whether a feature or dataset is up to date and reliable.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
question
|
str
|
User question about freshness / reliability. |
required |
object_name
|
str
|
Name of the feature or dataset. |
required |
hint_entity_name
|
Optional[str]
|
Entity name remembered from a previous turn — used to auto-disambiguate when the same feature name exists across entities. |
None
|
Returns:
| Type | Description |
|---|---|
dict
|
Dict with |
dict
|
|
skill_freshness_for_feature_list
¶
skill_freshness_for_feature_list(question: str, feature_names: list, entity_name: Optional[str] = None) -> dict
Aggregate freshness across a list of feature names referenced by a group pronoun.
Used when the user says "are these features up to date?" without naming any of them explicitly. Looks each feature up in the catalog, collects its process_id, pulls follow_up_report for each, and returns a combined last-successful-run / days-since / failure verdict.
skill_summary
¶
skill_summary(question: str, domain: str = None, object_name: str = None, entity_filter: str = None, accumulated_context: dict = None, _entity_fanned: bool = False) -> dict
Give a high-level summary of available features for a domain or a specific dataset.
When object_name resolves to a registered dataset, the response is scoped to that dataset's feature list and the resolved dataset context is returned in the result so the caller can persist it in agent state for follow-up EXPLAIN turns.
When entity_filter is given (or object_name resolves to an entity type), the feature list is scoped to that single entity — answering "what features belong to X?".
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
question
|
str
|
User question. |
required |
domain
|
str
|
Therapeutic domain to filter by (optional). |
None
|
object_name
|
str
|
Dataset, entity, or feature name mentioned by the user (optional). |
None
|
entity_filter
|
str
|
Entity name to scope the feature list (optional). |
None
|
Returns:
| Type | Description |
|---|---|
dict
|
Dict with |
skill_lineage
¶
skill_lineage(question: str, object_name: str, hint_view_name: Optional[str] = None, accumulated_context: Optional[dict] = None, entity_name: Optional[str] = None, hint_table_name: Optional[str] = None) -> dict
Return the upstream data dependencies of a feature, object, or column.
Walks the dependency graph starting from the registered process view, and returns a structured list of upstream nodes (tables and views) with their types. Does not require LLM — the graph is deterministic.
When object_name is not a feature/dataset/process view (e.g. a plain
documented table, view, or a non-feature column), it falls back to the
persisted lineage graph (FS_LINEAGE_GRAPH) to position ANY documented
object. A column is first resolved to its owning object (TABLE_NAME)
and that object is positioned instead — see _position_object_from_graph.
Implements fast-path caching: queries accumulated_context_graph first (cache hit → return immediately), falls back to expensive DDL scans (cache miss → rebuild and merge results for next turn).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
question
|
str
|
User question (used to determine response language). |
required |
object_name
|
str
|
Feature, object (table/view/dataset), or column name. |
required |
hint_view_name
|
Optional[str]
|
Pre-resolved view name from a prior turn (skips catalog lookup). |
None
|
accumulated_context
|
Optional[dict]
|
The accumulated_context_graph from state (used for cache). |
None
|
entity_name
|
Optional[str]
|
Entity name for disambiguation (used in cache lookups). |
None
|
hint_table_name
|
Optional[str]
|
Owning object for a column (from a prior EXPLAIN/DEFINITION
turn via |
None
|
Returns:
| Type | Description |
|---|---|
dict
|
Dict with |
dict
|
|
dict
|
|
skill_explain
¶
skill_explain(question: str, object_name: str, hint_view_name: Optional[str] = None, hint_dataset_features: Optional[list] = None) -> dict
Explain how a feature is calculated by reading its process view DDL.
Fetches the SQL definition of the registered process view and asks the LLM to translate the logic into plain business language — no SQL jargon.
When hint_dataset_features is provided and object_name is empty, explains all features from the current dataset context (up to 10) in a single LLM call.
When object_name contains comma-separated feature names, explains each one.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
question
|
str
|
User question. |
required |
object_name
|
str
|
Feature or view name. May be comma-separated for multiple features. |
required |
hint_view_name
|
Optional[str]
|
Pre-resolved view name from a prior turn (skips catalog lookup). |
None
|
hint_dataset_features
|
Optional[list]
|
Feature list from a prior SUMMARY turn on a dataset. |
None
|
Returns:
| Type | Description |
|---|---|
dict
|
Dict with |
skill_algorithm
¶
Explain the machine-learning model behind a feature.
Walks the tdfs4ds → tdstone2 chain: resolve the feature's process view, detect the Script-Table-Operator scoring view in its DDL, parse the scoring-mapper UUID, resolve the trained model → model arguments → Python code, and report the algorithm, packages, hyperparameters, model inputs and training-partition status in business language.
Distinct from :func:skill_explain (which explains the SQL formula): this
skill explains the ML model (algorithm / code / packages / hyperparameters).
Returns a dict carrying answer (so the synthesiser returns it directly)
plus structured fields (algorithm, packages, hyperparameters,
model_inputs, n_trained_partitions, training_time_available …) and
error on the failure paths.
skill_dataset
¶
Find datasets that expose one or more features.
When object_name contains comma-separated feature names, returns per-feature dataset lists plus an intersection of datasets that contain all features.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
question
|
str
|
User question. |
required |
object_name
|
str
|
Feature name(s) to look up, comma-separated for multi-feature. |
required |
Returns:
| Type | Description |
|---|---|
dict
|
Single-feature: |
dict
|
Multi-feature: |
dict
|
Status values: |
dict
|
|
skill_dataset_for_concept
¶
The datasets a phrase that names no feature refers to (status='concept_match').
recognized comes from :func:_recognize_concept: a dataset (by its name or
its documentation) is answered with its features; features matched by their
description are answered with the datasets exposing them. The answer is
written by code and says how the phrase was matched.
skill_propose_features
¶
skill_propose_features(use_case: str, entity_name: str, all_features: list, top_n: int = 20) -> dict
Propose the most relevant features for a given use case.
When all_features has more than top_n entries the LLM selects the
most relevant ones. When there are top_n or fewer, all are returned
directly without an LLM call.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
use_case
|
str
|
Free-text description of the business need (user's message). |
required |
entity_name
|
str
|
Entity the features belong to (context for the LLM). |
required |
all_features
|
list
|
Full list of |
required |
top_n
|
int
|
Maximum features to propose. |
20
|
Returns:
| Type | Description |
|---|---|
dict
|
Dict with |
dict
|
|
skill_check_dataset_name
¶
Check whether a proposed dataset name is already taken in the catalog.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
proposed_name
|
str
|
Dataset name proposed by the user. |
required |
feature_name
|
str
|
Feature for which the dataset is to be created (context only). |
required |
Returns:
| Type | Description |
|---|---|
dict
|
Dict with |
dict
|
|
dict
|
|
skill_build_dataset
¶
skill_build_dataset(entity_name: str, selected_features: dict, dataset_name: str, schema_name: Optional[str] = None, return_query: bool = False, kind: Optional[dict] = None, join_type: Optional[str] = None, document: Optional[bool] = None, feature_offsets: Optional[dict] = None) -> dict
Create, register and (when a model is configured) document a dataset.
kind comes from :func:parse_dataset_kind: current (default) builds a
snapshot valid now, as_of a snapshot at that instant, history one row
per entity and period of constant values (build_historical_dataset,
window start / end / duration / delay, or the full history).
time_manager (kind["time_manager"] = 'db.view') is a snapshot
that follows that TimeManager's business date. The view is
created in the view database; the database refuses it when the user may not
create it, and that refusal is returned whole (error).
join_type ('INNER' / 'FULL OUTER', None = the builders' default) and
document (None = when an instruct model is configured) are for callers
that let the user choose (the admin app's dataset builder), as is
feature_offsets (lags and leads, see build_dataset).
Returns status (dataset_created, creation_failed,
no_features_resolved or query_returned), dataset_name,
schema, feature_count, kind, dataset_id (when registered),
registered and doc_status (success, failed, skipped).
resolve_features_for_dataset
¶
Resolve a list of {name, description} feature dicts to the selected_features format.
Calls get_feature_versions() and splits results into clean (single-version) and conflict (multi-version) groups.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
entity_name
|
str
|
Entity the features belong to. |
required |
feature_list
|
list
|
List of {name, description} dicts (as produced by skill_propose_features). |
required |
Returns:
| Name | Type | Description |
|---|---|---|
tuple
|
Tuple of (selected_features_dict, conflicts_list). |
|
selected_features_dict |
tuple
|
{feature_name: uuid_str} ready for build_dataset(). |
conflicts_list |
tuple
|
[{feature_name, versions: [{process_id, process_view_name}]}]. |
parse_dataset_kind
¶
Read the kind of dataset the user asks for, and its parameters. Pure.
Returns {"kind": "current" | "as_of" | "history" | None, "as_of", "start",
"end", "duration", "full", "problem"}. problem names what is missing or
unreadable (unrecognised, as_of_needs_date, history_needs_window,
ambiguous_date, unreadable_date, unreadable_duration); the kind is
complete when it is None. Menu numbers 1 / 2 / 3 are accepted.
check_new_dataset_name
¶
May a new dataset be created under the name in reply?
status: name_available, invalid_name (not a plain identifier),
name_taken (a registered dataset), object_exists (a table or view of
that name already exists in the view database: creating would replace it) or
name_check_failed (the database could not be read - never treated as free).
synthesize_answer
¶
synthesize_answer(question: str, skill_result: dict, intent: str, resolved_feature_list: Optional[list] = None, accumulated_context: Optional[dict] = None) -> str
Convert structured skill output into a plain-language response.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
question
|
str
|
Original user question. |
required |
skill_result
|
dict
|
Dict returned by the skill function. |
required |
intent
|
str
|
The classified intent (SEARCH, DEFINITION, etc.). |
required |
resolved_feature_list
|
Optional[list]
|
Optional list of feature names from current/prior turns (for group context). |
None
|
accumulated_context
|
Optional[dict]
|
Optional accumulated context graph with cached metadata. |
None
|
Returns:
| Type | Description |
|---|---|
str
|
A natural language answer suitable for a non-technical business user. |
node_skill_mcp_tools
¶
LangGraph node: delegate to MCP server tools.
Dashboard-shaped questions are short-circuited to the grounded
:func:skill_compose_dashboard path FIRST — even with MCP enabled — so the
answer is composed from verified datasets/columns instead of free-form MCP
SQL that can fabricate column names. Falls through to MCP when the question
does not decompose into a feature composition.
_recognize_object_type
¶
Identify what type of object name is by querying catalogs.
Returns a dict with keys: type ("feature"|"dataset"|"entity"|"data_domain"|"process"|"unknown"), entity_name, view_name, process_id.
concepts: when no catalog name matches exactly, also try what the phrase
refers to (:func:_recognize_concept: a dataset by its name's words or its
documentation, or features by their description). concepts="datasets":
datasets only, no feature-documentation step (no vector search). Exact names
always win.
_known_catalog_names
¶
Return the union of registered feature, dataset and BD object names (uppercase).
Used to filter vector-search hits so that fabricated names (embedding-similar but not actually registered) never reach the LLM context. Empty set on failure means no filtering is applied (fail-open).
_best_name_similarity
¶
Return {token: (best_score, best_name)} — the closest catalog name and
its similarity score for each token.
Size-adaptive backend (the candidate set drives the choice, per design):
- small set (len(known_names) <= tdfs4ds.FUZZY_INDB_THRESHOLD): Python
difflib.SequenceMatcher ratio — no DB round-trip, ideal for the small
per-turn-cached catalogs the consumer agent uses (e.g. 52 names);
- large set: Teradata in-database StringSimilarity (jaro_winkler), scored
set-based in one query — scales to thousands of names.
known_names is an iterable of catalog names (any case). Matching is
case-insensitive. Fail-open: on any error returns {} (caller treats as no
fuzzy match, i.e. exact-only).
_rejected_intent_value
¶
The intent the model returned that the schema refused, if it can be read.
Pydantic renders it as input_value='DATA_QUERY', once per rejected literal.
Best-effort and never raises: it only chooses a better fallback message, so a
miss degrades to the generic one rather than to a wrong answer.
_safe_fallback_intent
¶
An intent that is certainly registered, for use when classification fails.
SEARCH when available — it is the one skill that answers a vague question
usefully — else any registered intent. None when nothing is known, which
tells the caller to re-raise rather than invent a route.
_looks_like_algorithm_question
¶
True when the question is about the ML model/algorithm/packages/hyperparameters.
_resolve_compound_sub_intents
¶
Return an ordered list of >=2 sub-intent dicts for a COMPOUND question, else [].
Prefers the classifier's own sub_intents. Falls back to a deterministic
backstop for the common pairs (DEFINITION/EXPLAIN/FRESHNESS + LINEAGE, EXPLAIN +
ALGORITHM) so a small model that emits a single intent still produces the
compound. Validates each sub-intent's name; collapses to [] when only one
distinct sub-intent survives (single-purpose question — unchanged behaviour).
_rescue_out_of_scope
¶
Rescue an in-scope question the classifier wrongly sent to OUT_OF_SCOPE.
Returns (object_name, intent) when the question names a KNOWN catalog
feature/object, else (None, None) (leave OUT_OF_SCOPE as-is). Intent is
EXPLAIN for a "how/formula" shape, otherwise DEFINITION. Deterministic and
grounded in _known_catalog_names so genuine off-topic questions still refuse.
_normalize_source_tables
¶
Drop source-table entries that are not real FROM/JOIN base tables of view_sql.
Prevents subquery aliases (e.g. flags, base_data) from being stored in the
accumulated context graph as real tables. Fail-open: returns the input unchanged
when view_sql is missing or the real-table set can't be derived. Handles both a
list of names and a list of {name: …} dicts.
_merge_accumulated_context
¶
Non-destructive deep merge of structural relationship updates into the accumulated graph.
Each section is a flat dict keyed by an uppercase identifier. New entries overwrite existing ones so the map always reflects the latest resolution. Focus pointers (_focus, _focus_dataset, _focus_entity) and user_context are updated atomically. User preferences are merged, with new values overriding old ones. Freshness timelines and search results are merged entry-by-entry (new overwrites old).
_build_context_update
¶
_build_context_update(skill_result: dict, domain: Optional[str], current_acc: Optional[dict] = None) -> dict
Extract structural relationships and discoverable payload from a skill result.
Reads resolved_triplet (single-feature path) and context_graph.features (multi-feature path) to populate features, processes, datasets, and entities. Also extracts and merges discoverable payload: - From EXPLAIN: calculation_sql, source_columns, explanation, docs_sources - From LINEAGE: view_sql, source_tables, node metadata (type, depth, features) - From USAGE: entity_keys, select_template - From FRESHNESS: last_successful_run, days_since_update, has_recent_failure
current_acc is the accumulated context graph before this update. When
provided, existing feature-entry fields (e.g. dataset_names,
business_description) are inherited so that skills which do not return
those fields (e.g. EXPLAIN) do not accidentally overwrite them with empty values.
_looks_like_build_request
¶
True when the reply asks to build / create the dataset (EN/FR).
_is_new_question
¶
True when a reply to "which did you mean?" is really a new question.
A short reply ("2", "the summary one", "first") is an answer to the clarification. A longer one that ends with "?" or opens like a question ("what are the data domains?") is a new request: it must be classified on its own, not matched against the pending options — otherwise the agent re-offers the same clarification and the user is stuck (seen live).
_focus_dataset
¶
The registered dataset the conversation is about, as db.name, or None.
Read from the remembered object (_focus), resolved like a dataset named
in a question: exactly, or by :func:_fuzzy_match_dataset (which also reads
"customer RFM dataset" as ds_customer_rfm). Fail-open: None.
_is_dataset_inventory_question
¶
True for "which datasets are there?" - not "which datasets expose X?".
_names_documented_object
¶
The classifier's object is a documented table/view/column or a catalog name (then the question is about THAT object, not a search). Fail-open: True on a read error, so a misread never reroutes.
_entity_candidates
¶
[{entity, domain, n_features}] of the entities that have features.
domain keeps that data domain's entities when it has any (else every
domain). Fail-open: [] when the catalog cannot be read.
_choose_entity
¶
(candidate, matches): the one candidate text names (or a numbered
reply "2"), and every candidate carrying the named entity (several data
domains can share a name; domain breaks the tie).
_inventory_filters
¶
{"topic": [words], "purpose": "train"} of a dataset inventory question,
each key only when present (so a plain inventory stays {"entity": ...}).
The entity's own words are not a topic.
_dataset_inventory_request
¶
{"entity": name or None, "topic": [...], "purpose": ...} when question
asks for the dataset inventory, else None.
"what are the datasets?" -> every dataset; "datasets for CustomerID" -> those on CustomerID; "what are these datasets?" -> those on the conversation's entity (focus_entity). "Which datasets expose X?" (X not an entity) is a lookup, not an inventory: None. "Which datasets are used to train the propensity models?" keeps the topic words and the training purpose; a scoped question naming no entity is an inventory only when it asks for training sets.
_scoped_follow_up
¶
A short follow-up that only narrows the previous request to an entity.
"I meant for CustomerID?", "what about CustomerID", "pour CustomerID" after a dataset list, a dataset build or a feature listing redo THAT request for the entity, instead of being classified on its own (seen live: answered as a definition of CustomerID). Returns the classify output, or None.
_says_historian
¶
The reply points at a historian table ("these data are in the historian"): it completes a pending chart request, even when worded as a question.
_skill_registered
¶
A skill of that intent is installed (its ca-*/SKILL.md is in the catalog).
_plot_skill_registered
¶
The PLOT skill is installed (its ca-plot SKILL.md is in the catalog).
_numbered_items
¶
The last numbered list of text: {number: item}, numbered from 1 on
consecutive numbers; {} when there is none. Markdown emphasis removed.
_expand_numbered_reply
¶
A reply made of the numbers of a list the previous answer offered, written
out with the items it points at; None when it is anything else.
"2" -> the second item. "1 monthly, 2 last year" -> "<item 1>: monthly;
<item 2>: last year" (answers to numbered questions). Every number must be
an item of the list, and the reply must start with one.
_listed_items
¶
The names the previous answer listed, in order: its last numbered list
(the item's first name or name, else its text before ':'), else the
first name of each table row.
_resolve_ordinal_reference
¶
question with "the second one" replaced by the second item the previous
answer listed; None when there is no ordinal reference or no such item.
_with_name
¶
question with its first reference word replaced by name; the question
with name appended when it has none ("give me a sample" -> "... of name").
_listed_follow_up
¶
_listed_follow_up(question: str, previous_intent: Optional[str], previous_answer: Optional[str], intent: Optional[str], object_name: Optional[str]) -> Optional[dict]
The object a follow-up points at, from the list the previous answer gave.
After an inventory ("what datasets are available?"), "is it up to date?" or
"give me a sample" names nothing; the previous answer's items are what "it"
means. One item -> {"intent", "object_name", "question"}; several ->
{"intent", "choices": [{"label", "intent", "object_name"}]} (asked as a
numbered question). None when the question names something, the previous
answer listed nothing, or the question is not about one object. Pure.
_pointing_update
¶
The classify result for a follow-up pointing at the previous answer's list, or
None: {"intent", "object_name", "question"} for one item, {"intent",
"object_name": None, "pending_action", "pending_action_context"} to ask among
several. Used after classification and when classification failed.
_resolve_clarification_choice
¶
Match the user's reply against the stored clarification options.
Accepts: - A 1-based index ("1", "2", "option 2", "the second one") - A fuzzy label/object match (substring or difflib similarity)
Returns the matched interpretation dict, or None when unresolvable.
_ambiguous_feature_pending
¶
Build the state-update dict for an ambiguous-feature entity clarification.
Called by node_skill_definition when skill_definition returns error='ambiguous_feature'. Stores the entity list, original object name, and origin intent so that the next turn's classify can resolve the reply deterministically without an LLM round-trip.
Factored into a helper so other skills (USAGE, FRESHNESS, …) can trigger the same pending flow with a single call when they encounter the same error.
_fuzzy_resolve_from_column_sources
¶
Try to resolve a vague column/table reference from a prior EXPLAIN result.
Splits the question into tokens and checks whether any token appears as a substring in a known column name or source table name. Returns the matched name only when exactly one candidate matches (to avoid false positives on common words).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
question
|
str
|
Raw user question. |
required |
col_sources
|
dict
|
|
required |
Returns:
| Type | Description |
|---|---|
Optional[str]
|
The matched column name or source table name, or |
_carried_object_suspect
¶
True when the classifier's object is the remembered one, not the question's.
All must hold: a context hint was given; the object equals a remembered object (case-insensitive); the question does not contain it; the question has no reference word. Otherwise the classifier's answer stands.
_clean_object_name
¶
Strip markdown/quote wrappers and a trailing annotation around a name.
The intent classifier (or user) sometimes wraps the object name in markdown
backticks/quotes (`provider_z_score`) or appends a parenthetical
annotation (provider_z_score (from flags)). Catalog lookups are exact,
case-insensitive matches, so either form matches nothing and DEFINITION
wrongly falls through to "feature not found / did you mean …".
Strips, in order: surrounding backticks/quotes/whitespace, then ONE trailing
(...) group, then surrounding wrappers again. Only the surrounding
wrappers and a trailing parenthetical are removed — interior characters are
never touched (feature names contain _ and digits; composite entities use
commas like claim_id,region; names never contain parentheses). Falls back
to the wrapper-only result if stripping the parenthetical would empty the name.
Returns the input unchanged when falsy (None / "").
_split_object_names
¶
Split a multi-object string into a clean list of names.
Handles the "A and B" / "A & B" / "A, B" forms the classifier
emits for multi-feature/-dataset/-domain questions, normalising the
conjunctions to commas, then cleaning each element with
:func:_clean_object_name (strips wrappers + a trailing parenthetical per
element — the classify-node clean runs on the whole comma-joined string and
cannot reach per-element annotations). Mirrors the inline idiom used in
node_skill_definition so multi-name parsing is consistent across skills.
Returns [] for a falsy input.
_comparison_names
¶
The documented items a comparison question names, in order (>= 2), else [].
"is the MCE_NUMBER of HCF_ACCESS.HCF_FIBER_VW similar to the MACHINE_NR of the
Historian?" -> ['MCE_NUMBER', 'MACHINE_NR']. An item is a catalog name
(known_names: features, datasets, processes) or, for identifier-shaped words
(underscore, digit or all capitals), a documented column: column_lookup(words)
returns the subset that are documented columns (one read). Qualified names
(DB.TABLE) are owners, not compared items. Pure when column_lookup is None.
_documented_column_names
¶
The words that are documented column names (one business-dictionary read).
_extract_feature_list_for_routing
¶
Extract the catalog feature names a question lists, for RECAP→DEFINITION re-routing.
Tokenizes the raw question (commas / newlines / whitespace), cleans each token
(_clean_object_name strips wrappers + a trailing (from flags) style
annotation), drops stop-words and tokens shorter than 3 chars, and keeps a
token when it EXACTLY matches a known catalog name OR fuzzy-matches one with
high confidence (>= tdfs4ds.FUZZY_NAME_MATCH_CUTOFF, default 0.90 — a real
typo, not a coincidental word).
Typo'd tokens are returned as the user typed them (after cleaning), so the
downstream skill_definition_multi per-element fuzzy path emits the
"did you mean …?" correction rather than the guard auto-correcting silently.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
question
|
Raw user question text. |
required | |
known_names
|
Iterable of catalog names (any case). |
required | |
fuzzy_fn
|
Optional |
None
|
Returns:
| Type | Description |
|---|---|
|
|
|
|
tokens in question order (deduped, as-typed); |
|
|
question contains a ':' before the list (e.g. "what are these features: …"). |
|
|
Empty list when nothing matches (caller leaves intent unchanged). |
_node_classify_impl
¶
_node_classify_impl(state: AgentState, intent_model, intent_descriptions: dict, mcp_enabled: Optional[bool] = None) -> dict
Classify, then settle the turn's valid time (0.3.1.240).
The date the question names (or the conversation's, or none) is set before
classification - domain detection already reads the catalogs - and settled
after it with the classifier's as_of. It lives in resolved_as_of and,
for the turn, in consumer_agent._AS_OF (reset by node_synthesize).
_node_classify_core
¶
_node_classify_core(state: AgentState, intent_model, intent_descriptions: dict, mcp_enabled: Optional[bool] = None) -> dict
Core classify logic — extracted so it can be closed over a dynamic model.
Short-circuits the LLM classifier for all dataset-creation pending states so that the user's reply is routed directly to the dataset skill without being misclassified.
When the intent is DEFINITION but no object_name was extracted, attempts a
fuzzy match against resolved_column_sources (populated by a prior EXPLAIN
turn) so that vague follow-ups like "what does the date mean?" resolve to the
correct column or source table without requiring the user to quote the exact name.
Extracts user preferences (e.g., currency, output format) and merges them into accumulated_preferences for all downstream skills to access.
node_classify
¶
Run intent classification using the hardwired full-intent model (legacy path).
node_detect_domain
¶
Detect the DATA_DOMAIN for the requested object and list all available domains.
DATA_DOMAIN is sticky: once resolved for the thread, it is only changed when the current turn extracts an explicit object_name AND detect_data_domain_for_object returns a domain that actually owns it. This prevents silent context switches on vague follow-up questions.
_with_domain
¶
Context manager that temporarily sets tdfs4ds.DATA_DOMAIN.
_extract_triplet
¶
Pull resolved_triplet from a skill result if present and non-empty.
_skill_with_triplet
¶
Build state-update dict from a skill result, propagating triplet and dataset context when resolved.
Focus pointers (_focus, _focus_entity, _focus_dataset) are written into the accumulated_context_graph rather than as top-level state fields.
_hint_view
¶
Return the remembered view_name only when this turn has no explicit object.
When the user names something explicitly, we re-resolve rather than reuse the stale hint so that a new feature mentioned in the same thread is handled correctly.
_effective_object
¶
Return the object name: explicit this turn > last focus in accumulated graph.
_get_entity_name_from_state
¶
Extract entity name from state for fast-path queries (skills can use this for disambiguation).
_capture_feature_group_from_search
¶
Populate resolved_feature_list / resolved_entity_name from a
SEARCH or compose-dashboard skill result.
Walks the skill result for feature_names (composition path) or
matches (regular search) and lifts the feature names into
resolved_feature_list. When all matched features share the same
entity in the catalog, also remembers resolved_entity_name. This
keeps multi-turn group-pronoun questions ("are these features fresh?")
in lockstep with the SUMMARY path.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
updates
|
dict
|
Dict containing the |
required |
Returns:
| Type | Description |
|---|---|
dict
|
The same |
_make_feature_group
¶
Build a _recent_feature_group record for the focus history.
Only returns a record for genuine multi-feature groups (>1 feature); a
single-feature turn is not a group and must never evict the history. The
label_tokens carry words from the question that qualify the group
(e.g. "pharmacy", "provider") so a later group-pronoun reference can match
on the qualifier even when no entity was resolved.
_entity_group_from_question
¶
When the question names a known entity ("...for pharmacies", "provider
behaviour"), return (entity_name, [all feature names for that entity])
from the live catalog.
SEARCH match sets vary with the LLM, so a group reference like "the pharmacy
features I just saw" is far more robustly grounded against the full
catalog feature set for the matched entity than against the LLM's selected
subset. Returns None when no single entity is clearly named.
node_skill_search
¶
Execute the search skill.
When the question looks like a dashboard-composition request (multiple
feature concepts + dashboard/SELECT/top-N tokens), runs
:func:skill_compose_dashboard first to find a dataset exposing the whole
set. If decomposition yields fewer than 2 features, falls back to the
regular SEARCH path.
Also captures the resulting feature names into resolved_feature_list so
follow-up group-pronoun questions ("are these features fresh?") can
aggregate freshness without the user having to re-state the names.
_apply_definition_enrichment
¶
Mutate result in-place: extend explanation with approved plugin knowledge.
Scans $TDFS4DS_SKILLS_FOLDER for SKILL.md files that carry an
enrichment-for: DEFINITION field and whose enrichment-match keywords
appear in the base explanation. When matches are found, makes a second LLM
call that presents the grounded base answer followed by the approved domain
knowledge. The enriched text replaces result["explanation"] and
"PLUGIN_ENRICHMENT" is added to result["docs_sources"].
This function is a no-op when:
- $TDFS4DS_SKILLS_FOLDER is not set or contains no enrichment skills.
- The result has no "explanation" key or is a cached answer.
- Any exception occurs (always non-fatal — the base answer is preserved).
_column_owner_from_definition
¶
The table a DEFINITION answer placed a non-feature column in, or None.
Only a column answer counts (no resolved feature), and only when every
documented row of that column names the same table: two tables would be a
guess. Returns the bare table name, the form resolved_column_sources holds.
node_skill_definition
¶
Execute the definition skill.
For comma-separated object names (multi-object question — features, datasets, or a mix), calls skill_definition once per name and combines the explanations into a single answer with per-object section headers. The last successfully resolved triplet is propagated to state for follow-up turns.
node_skill_usage
¶
Execute the usage skill with fast-path caching.
_resolve_group_from_history
¶
Resolve a qualified group reference against the focus history.
For "the pharmacy-level features I just saw", the live _focus_feature_list
may have been overwritten by an intervening single-feature turn. This scans
_recent_feature_groups (most-recent first) and returns the group whose
entity or label tokens match a qualifier word in the question. Returns
{"features": [...], "entity": ...} on a unique match, else None.
_question_has_group_qualifier
¶
True when a group reference is scoped by an entity/qualifier word.
"the features I just saw" is unqualified; "the pharmacy-level features I just saw" is qualified. A qualifier is a token from a known entity name (from the focus history or accumulated entities). Qualified-but-unresolvable group references must NOT fall back to a sticky single feature.
node_skill_freshness
¶
Execute the freshness skill.
When the user's question is a group pronoun ("are these features up to date", "the pharmacy features I just saw") and a feature list is remembered from a previous turn, aggregates freshness across the whole group instead of trying to resolve a single object.
node_skill_feasibility
¶
Execute the FEASIBILITY skill — read-only use-case capability assessment.
Identifies relevant existing features and missing-but-derivable capabilities for a described use case, WITHOUT creating a dataset. Direct-answer skill.
node_skill_data_domains
¶
Execute the DATA_DOMAINS skill — list/describe feature-store data domains.
No domain named → inventory all DATA_DOMAINs. One or more named (comma/and
separated) → per-domain feature count + entity breakdown, typo-tolerant.
Direct-answer skill (the skill builds its own answer).
node_skill_help
¶
Execute the HELP skill — explain the feature store's conceptual model.
Direct-answer (no Teradata query); the skill builds its own answer.
node_skill_values
¶
Execute the VALUES skill: an entity's values or a feature's extremes, read in the database. Direct-answer skill (the answer is written by code).
node_skill_plot
¶
Execute the PLOT skill - a chart drawn in the database by TD_PLOT.
Direct-answer skill: the answer is written by code and the chart travels in
skill_result["charts"] like an MCP chart. Data domain and entity are
passed explicitly (no _with_domain): the feature is resolved by triplet.
_complete_plot_request
¶
The pending chart request with the user's reply added. A number names one of the periods offered ("period 1"): it is replaced by that feature's name, which the plot skill then finds in the text.
node_skill_compound
¶
Orchestrate a COMPOUND (multi-purpose) question — run each sub-intent in order.
Generalises the hardcoded EXPLAIN+ALGORITHM dual-intent pattern: for every
sub-intent the classifier emitted, dispatch to the SAME skill node it would
normally route to, synthesize that sub-answer to text, and join the parts with
a --- divider. Sequential sub-intents (depends_on_prev) thread the prior
sub-question's resolved owning object forward (column→object→lineage), reusing
skill_lineage's hint_table_name via resolved_column_sources. Accumulated
context is merged from every sub-result so both subjects stay pronoun-referenceable.
node_skill_recap
¶
Execute the RECAP skill — recap features/datasets from conversation context.
Reads directly from accumulated_context_graph; no Teradata queries.
node_skill_out_of_scope
¶
Execute the OUT_OF_SCOPE skill — polite refusal for off-topic questions.
No-op node; the answer is rendered directly in synthesize_answer.
node_skill_lineage
¶
Execute the lineage skill with fast-path caching.
Supports multi-feature comma-separated lineage via context_graph consolidation.
node_skill_explain
¶
Execute the explain skill and persist column→source-table mapping for follow-up DEFINITION turns.
node_pre_skill_explain
¶
No-op node emitted before node_skill_explain to show a live trace step immediately.
node_skill_algorithm
¶
Execute the ALGORITHM skill (explain the ML model behind a feature).
Mirrors node_skill_explain: resolves the effective object, runs the skill in
the active domain, and merges the resolved feature triplet + context update.
The skill returns its final answer directly, so no second LLM pass.
Dual-intent (EXPLAIN + ALGORITHM): when the question asks both how the formula works ("how is X computed?") AND about the ML model ("what algorithm is behind it?"), this node runs EXPLAIN first and prepends its answer. This handles compound questions without changing the graph structure.
node_skill_dataset
¶
Execute the dataset availability skill — handles a 4-step creation flow.
Step 0 (no pending): look up datasets that expose the feature.
Step 1 (awaiting_feature_selection): user described their use case →
LLM proposes top-20 relevant features (or all if ≤ 20).
Step 2 (awaiting_feature_confirmation): user replied 'ok' or 'all' →
record selection and ask for a dataset name.
Step 3 (awaiting_dataset_name): user provided a name → validate it.
node_skill_build_dataset
¶
Execute the BUILD_DATASET creation flow: the dataset is created, registered and (when a model is configured) documented - nobody else has to do it.
Step 0 (no pending): locate the entity, propose features. Step 0a (awaiting_feature_selection, from a DATASET lookup that found none): propose features of the feature's own (domain, entity). Step 1 (awaiting_feature_confirmation): 'ok' / 'all' → resolve versions. Step 2 (awaiting_version_selection): pick a version per conflicting feature. Step 3 (awaiting_dataset_kind): current snapshot, snapshot at a date, or history over a window - asked with the parameters it needs. Step 4 (awaiting_dataset_name): check the name (catalog and database) and build. The database refuses what the user may not create; that refusal is reported whole.
_ask_entity
¶
Ask on which entity to build the dataset (the reply is read by step 0b).
_propose_for_entity
¶
Propose the features of cand (an entity of a data domain); the entity
becomes the conversation's (_focus_entity) and its domain the active one.
node_recognize_entity
¶
Identify the catalog type of each named object before skill dispatch.
Populates recognized_entities (feature/dataset/process/unknown per name)
and sets secondary_intent = "DATASET" when a DEFINITION question also
asks about dataset availability.
_search_concept
¶
(phrase, recognition) of a SEARCH turn that found nothing, when the
phrase is a concept the catalog resolves to a dataset; else None.
The phrase is the classified object, else each acronym of the question. Only a dataset counts here: features matched by their description are what SEARCH itself looks for, so its own answer stands.
_recognized_concept
¶
The recognition of name when it is a concept (matched by words or
documentation, not by an exact catalog name), else None.
_render_clarification_prompt
¶
_render_clarification_prompt(question: str, interpretations: list[dict], re_offer: bool = False, listed: bool = False) -> str
Build the numbered clarification answer shown to the user.
A fixed EN/FR template, not an LLM call: the options are already written, and asking a model to restate them made the language drift (an English question answered in French, seen live) for no gain. Same choice as the ambiguous-feature entity clarification.
node_synthesize
¶
Synthesize, then open the answer with the valid time it was read at.
_node_synthesize_core
¶
Synthesize a natural language answer from the skill result.
When a secondary DATASET intent was detected by node_recognize_entity, also runs skill_dataset and appends a dataset-availability section to the answer. When the classifier flagged low confidence with multiple interpretations, renders a clarification prompt instead and sets the pending_action for the next turn.
build_graph
¶
Build and compile the LangGraph StateGraph.
The skill set is loaded from the ca-* SKILL.md files bundled with the
package (via :func:tdfs4ds.consumer_agent_skill_catalog). Only skills
whose intent is in _INTENT_NODE_MAP are registered as graph nodes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
skills
|
Optional list of intent names (e.g. |
None
|
|
mcp_enabled
|
bool
|
When |
False
|
Returns:
| Type | Description |
|---|---|
|
A compiled LangGraph with MemorySaver checkpointer for multi-turn support. |
Raises:
| Type | Description |
|---|---|
ValueError
|
When no consumer-agent skills are found in the package. |
_build_trace_md
¶
Build a markdown trace of the agent execution from the final state.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
dict
|
Final state dict returned by the graph after invocation. |
required |
Returns:
| Type | Description |
|---|---|
str
|
Markdown-formatted string describing intent, skill, and result. |
_build_context_graph_pyvis
¶
Build an interactive Pyvis network graph from the agent's context state.
Visualises the accumulated context as a knowledge graph with: - Draggable nodes with physics-based layout - Rich hover tooltips for nodes (type + name) and edges (relationship description) - HTML legend showing all 6 node types
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
dict
|
Final state dict from the graph. |
required |
Returns:
| Type | Description |
|---|---|
Optional[str]
|
HTML string wrapping an iframe with the graph + legend, or None if no context/pyvis unavailable. |
_get_graph
¶
Return the shared compiled graph, building it once per unique skill subset.
The graph is cached by (frozenset(skills), plugin_key, mcp_enabled) so
that different subsets and MCP states each get their own compiled graph, but
all of them share one checkpointer (:func:_shared_checkpointer), so a
conversation keeps its memory when the MCP toggle or the skill set changes.
reset_consumer_agent
¶
Clear the graph singleton cache.
Call this after adding or removing consumer-agent SKILL.md files so that the
next :func:consumer_agent call picks up the new skill set.
_run_config
¶
LangGraph run config of one turn: the thread, the token counter, and
a tracer when tdfs4ds.TRACING asks for one (:mod:tdfs4ds.utils.tracing).
The token counter (consumer_agent.turn_token_callback) records every
model call of the turn, the classifier included; it only counts.
_merge_token_usage
¶
Conversation token usage = the previous turns' plus this turn's.
turn is a :meth:TokenTracker._aggregate result for the calls of this turn
only (total_input / total_output / by_skill). The deltas used to be
read as input_tokens / output_tokens, keys totals() never had, so
every turn counted 0, while by_skill added the whole session's figures.
consumer_agent
¶
consumer_agent(question: str, thread_id: str = 'default', return_trace: bool = False, return_context: bool = False, skills: Optional[List[str]] = None, mcp_enabled: bool = False) -> Union[str, tuple]
Run the LangGraph agent for a single conversational turn.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
question
|
str
|
User question in natural language (French or English). |
required |
thread_id
|
str
|
Conversation thread ID for multi-turn memory. |
'default'
|
return_trace
|
bool
|
When True, includes trace_md in the return tuple. |
False
|
return_context
|
bool
|
When True, includes accumulated_context_graph in the return tuple. |
False
|
skills
|
Optional[List[str]]
|
Optional list of intent names (e.g. |
None
|
mcp_enabled
|
bool
|
When |
False
|
Returns:
| Type | Description |
|---|---|
Union[str, tuple]
|
|
Union[str, tuple]
|
|
Union[str, tuple]
|
|
Union[str, tuple]
|
|
consumer_agent_stream
¶
consumer_agent_stream(question: str, thread_id: str = 'default', skills: Optional[List[str]] = None, mcp_enabled: bool = False)
Generator variant of consumer_agent for real-time step streaming.
Yields:
| Type | Description |
|---|---|
|
|
|
|
where partial_state is the state accumulated so far (all fields set by |
|
|
nodes that have already run). |
|
|
|
The MemorySaver checkpointer behaves identically with .stream() as
with .invoke() — multi-turn memory is fully preserved.