tdfs4ds.agent.chatbot¶
tdfs4ds.agent.chatbot
¶
Gradio-based chatbot interface for the tdfs4ds consumer agent.
Provides a simple web UI that non-technical users can interact with directly from a Jupyter notebook or as a standalone server.
Usage in a notebook::
from tdfs4ds.agent.chatbot import launch_chatbot
demo = launch_chatbot() # opens inline + new-tab link
Standalone::
python -m tdfs4ds.agent.chatbot # starts on port 7860
_STEP_LABELS
module-attribute
¶
_STEP_LABELS: dict = {'classify': 'Classifying intent', 'detect_domain': 'Detecting data domain', 'recognize_entity': 'Recognizing entities', 'skill_search': 'Searching features', 'skill_definition': 'Looking up definition', 'skill_usage': 'Checking usage', 'skill_freshness': 'Checking data freshness', 'skill_summary': 'Summarizing dataset', 'skill_lineage': 'Building lineage graph', 'pre_skill_explain': 'Analyzing formula', 'skill_explain': 'Explaining process SQL', 'node_skill_dataset': 'Looking up datasets', 'skill_data_query': 'Querying data', 'synthesize': 'Synthesizing answer'}
_SKILL_NODES
module-attribute
¶
_SKILL_NODES = frozenset({'recognize_entity', 'skill_search', 'skill_definition', 'skill_usage', 'skill_freshness', 'skill_summary', 'skill_lineage', 'pre_skill_explain', 'skill_explain', 'node_skill_dataset', 'skill_data_query'})
_LIVE_HEADER
module-attribute
¶
_TRACE_PLACEHOLDER
module-attribute
¶
_TD_CSS
module-attribute
¶
_TD_CSS = '\n:root {\n --td-orange: #FF5F00;\n --td-navy: #00233C;\n --td-surface: #F5F7FA;\n --td-border: #E3E8EE;\n}\n.gradio-container { background: var(--td-surface) !important; color: var(--td-navy); }\n.gradio-container, .gradio-container * { font-family: system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif; }\n/* Hero banner */\n#td-hero { text-align:center; padding: 12px 0 4px; }\n#td-hero .td-wordmark { color: var(--td-navy); font-weight: 800; font-size: 1.4rem; letter-spacing:-.5px; }\n#td-hero .td-wordmark .dot { color: var(--td-orange); }\n#td-hero .td-sub { color:#5A6B7B; font-size:.95rem; }\n/* Left-oriented tab nav (sidebar look) */\n.td-shell .tab-nav, .td-shell [role="tablist"] {\n flex-direction: column !important; align-items: stretch !important;\n min-width: 210px; max-width: 230px; gap: 4px; border:none !important;\n background:#fff; border-radius:14px; padding:10px; height:fit-content;\n box-shadow: 0 1px 3px rgba(0,35,60,.06);\n}\n.td-shell .tab-nav button, .td-shell [role="tab"] {\n text-align:left !important; justify-content:flex-start !important;\n border:none !important; border-radius:10px !important; padding:10px 14px !important;\n color: var(--td-navy) !important; font-weight:600; background:transparent !important;\n}\n.td-shell .tab-nav button.selected, .td-shell [role="tab"][aria-selected="true"] {\n background: #EAF0F6 !important; color: var(--td-navy) !important;\n}\n.td-shell .tab-nav button.selected::before {\n content:""; display:inline-block; width:4px; height:18px; border-radius:3px;\n background: var(--td-orange); margin-right:10px; vertical-align:middle;\n}\n/* Cards / groups */\n.gr-group, .gr-box, .block.padded { border-radius:14px !important; }\n/* Primary buttons in Teradata orange */\nbutton.primary, .gr-button-primary {\n background: var(--td-orange) !important; border-color: var(--td-orange) !important; color:#fff !important;\n}\n/* Chat + skills pane cards */\n#td-chat-col, #td-skills-pane { background:#fff; border-radius:14px; padding:12px; box-shadow:0 1px 3px rgba(0,35,60,.06); }\n#td-skills-pane h3 { margin-top:4px; color:var(--td-navy); }\n/* Skills pane horizontal collapse */\n#td-skills-pane { overflow:hidden; transition: max-width 0.2s ease; }\n#td-skills-pane.td-skills-collapsed { max-width: 52px !important; min-width: 0 !important; }\n#td-skills-pane.td-skills-collapsed > div > :not(#td-skills-toggle) { display:none !important; }\n/* Resizable viz iframe wrappers */\n.td-viz-resizable { resize:vertical; overflow:auto; min-height:400px; }\n.td-viz-resizable iframe { width:100%; height:100%; min-height:400px; }\n/* Full-width tabs */\n.tabitem > .gap, .tabitem { width: 100% !important; }\n'
_SKILL_AUTHORING_GUIDE
module-attribute
¶
_SKILL_AUTHORING_GUIDE = '\nA **plugin skill** is a subdirectory inside `TDFS4DS_SKILLS_FOLDER`.\nEach subdirectory needs at minimum a `SKILL.md` file (frontmatter + description).\nOptionally add a `skill.py` with a `node(state)` function for custom logic.\n\n---\n\n### `SKILL.md` structure\n\n```\n---\nname: My skill name # Human-readable label shown in this pane\nintent: MY_INTENT # UPPER_CASE token the classifier emits\ndescription: >\n One or two sentences the intent classifier reads to decide when to\n route here. Be specific: name the question shape and list SKIP cases.\nargument-hint: [object name] # (optional) shown as a tooltip in the UI\nallowed-tools: Read, Grep # (optional) tools this skill may call\n---\n\nLonger explanation of what the skill does (not shown to the classifier).\n\n## Queries\n...\n\n## Returns\n...\n\n## Classifier Examples\n\n- "What is the regulatory status of X?" -> MY_INTENT, object_name=X\n- "Quel est le statut réglementaire de X ?" -> MY_INTENT, object_name=X\n```\n\n---\n\n### `skill.py` (optional — for custom logic)\n\n```python\n# skill.py\ndef node(state: dict) -> dict:\n """Called by the agent graph when intent == MY_INTENT."""\n obj = state.get("object_name") or state.get("resolved_object_name") or ""\n # ... your logic here ...\n answer = f"Regulatory status of {obj}: ..."\n # Setting \'answer\' bypasses the synthesize step (no extra LLM call).\n return {"skill_result": {"answer": answer, "docs_sources": ["MY_SOURCE"]}}\n```\n\nKey rules:\n- `state` is the LangGraph `AgentState` dict — read `object_name`, `resolved_data_domain`, `resolved_feature_triplet`, etc.\n- Return a dict with `skill_result` key. Set `skill_result["answer"]` for a direct response (no second LLM pass). Leave it unset to let `synthesize_answer` render the result.\n- Fail-open: return `{"skill_result": {"error": "my_error_code", "details": "..."}}` on errors.\n- Access Teradata via `import tdfs4ds; tdfs4ds.feature_catalog()`, `tdfs4ds.process_catalog()`, or `import teradataml as tdml; tdml.DataFrame.from_query(...)`.\n\n---\n\n### Minimal working example\n\nDirectory layout:\n```\nTDFS4DS_SKILLS_FOLDER/\n└── ca-status/\n ├── SKILL.md\n └── skill.py ← optional\n```\n\n`ca-status/SKILL.md`:\n```\n---\nname: Regulatory status\nintent: STATUS\ndescription: >\n User asks about the regulatory or compliance status of a feature or dataset.\n SKIP when the user asks for a definition or formula — use ca-definition or ca-explain.\n---\n\n## Classifier Examples\n\n- "Is total_amount compliant with GDPR?" -> STATUS, object_name=total_amount\n```\n\n`ca-status/skill.py`:\n```python\ndef node(state):\n obj = state.get("object_name") or ""\n return {"skill_result": {"answer": f"No compliance record found for \'{obj}\'.",\n "docs_sources": []}}\n```\n\nAfter saving, click **Refresh list** and the new skill appears. The agent will route matching questions to it automatically.\n'
_TD_STATIC_DIR
module-attribute
¶
_CHAT_SUGGESTIONS
module-attribute
¶
_CHAT_SUGGESTIONS = ['Which data domains exist?', 'What features are available?', 'Which datasets can I use?']
_CHAT_PLACEHOLDER
module-attribute
¶
_CHAT_PLACEHOLDER = "<div style='text-align:center'><div class='td-ph-title'>Ask the Feature Store</div><div class='td-ph-sub'>Features, datasets, how a value is computed, where it comes from, whether it is up to date, or how things stood on a past date.</div></div>"
_COPY_CHAT_JS
module-attribute
¶
_COPY_CHAT_JS = "\n(history) => {\n const text = (c) => typeof c === 'string' ? c\n : Array.isArray(c) ? c.map(p => (p && (p.text || p.content)) || '').join('\\n')\n : (c && (c.text || c.value)) || '';\n const parts = (history || []).map(m =>\n (m.role === 'user' ? '**You:** ' : '**Assistant:**\\n\\n') + text(m.content));\n const md = '# Feature Store conversation — ' + new Date().toLocaleString() + '\\n\\n'\n + parts.join('\\n\\n---\\n\\n') + '\\n';\n const fallback = () => {\n const ta = document.createElement('textarea');\n ta.value = md; ta.style.position = 'fixed'; ta.style.opacity = '0';\n document.body.appendChild(ta); ta.select();\n try { document.execCommand('copy'); } catch (e) {}\n ta.remove();\n };\n try {\n if (navigator.clipboard && window.isSecureContext) navigator.clipboard.writeText(md).catch(fallback);\n else fallback();\n } catch (e) { fallback(); }\n return [];\n}\n"
_CHAT_CSS
module-attribute
¶
_CHAT_CSS = '\n.gradio-container { max-width: none !important; margin: 0 !important; padding: 0 12px !important;\n font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif !important; }\nfooter { display: none !important; }\n/* The conversation is the page, not a boxed panel (as in the app). */\n#td-chatbot, #td-chatbot .bubble-wrap { border: none !important; box-shadow: none !important;\n background: transparent !important; }\n/* Empty conversation: the heading near the top, the suggestions as pills under it. */\n#td-chatbot .placeholder-content { justify-content: flex-start !important; padding-top: 8vh; }\n#td-chatbot .placeholder { flex: 0 0 auto !important; height: auto !important; }\n#td-chatbot .examples { display: flex !important; flex-wrap: wrap; justify-content: center; gap: 8px;\n max-width: 860px; margin: 22px auto 0 !important; grid-template-columns: none !important; }\n#td-chatbot .example { width: auto !important; min-height: 0 !important; flex: 0 1 auto !important;\n border: 1px solid var(--border-color-primary) !important; background: var(--background-fill-primary) !important;\n border-radius: 999px !important; padding: 8px 14px !important; font-size: .88rem !important;\n box-shadow: none !important; }\n#td-chatbot .example:hover { border-color: #FF5F00 !important; }\n#td-chatbot .example * { font-size: .88rem !important; }\n/* Messages in a centred column; the agent\'s bubble takes its width. */\n#td-chatbot .message-wrap { max-width: 900px; width: 100%; margin: 0 auto !important; }\n#td-chatbot .message-row.bot-row { max-width: 100% !important; width: 100%; }\n#td-chatbot .bot-row .flex-wrap, #td-chatbot .message.bot { flex: 1 1 auto; max-width: 100% !important; }\n#td-chatbot .message.bot { padding: 12px 16px !important; }\n#td-chatbot .bot ul { list-style: disc !important; }\n#td-chatbot .avatar-container { border: none !important; background: transparent !important;\n box-shadow: none !important; }\n/* The composer and its foot line are centred, 860px at most, as in the app. */\n#td-composer, #td-composer-foot { max-width: 860px; width: 100%; margin-left: auto !important;\n margin-right: auto !important; }\n#td-composer .block, #td-composer .form, #td-composer .container { border: none !important;\n box-shadow: none !important; background: transparent !important; padding: 0 !important; }\n.td-acc { border: none !important; background: transparent !important; box-shadow: none !important;\n max-width: 860px; margin: 0 auto !important; }\n.td-acc .label-wrap, .td-acc button { font-size: .8rem !important; color: var(--body-text-color-subdued) !important; }\n#td-chat-head { align-items: center; gap: 8px; flex-wrap: nowrap;\n border-bottom: 1px solid var(--border-color-primary); padding: 2px 4px 8px; }\n#td-chat-head > div:first-child { flex: 1 1 auto !important; min-width: 0 !important; }\n.td-head-title { font-weight: 700; color: var(--body-text-color); font-size: 1rem; }\n.td-beta { background: var(--background-fill-secondary); color: var(--body-text-color-subdued);\n border-radius: 999px; padding: 2px 8px; font-size: .72rem; font-weight: 600; margin-left: 6px; }\n.td-head-btn { flex: none !important; width: auto !important; min-width: 0 !important; white-space: nowrap !important;\n border: 1px solid var(--border-color-primary) !important; background: var(--background-fill-primary) !important;\n color: var(--body-text-color) !important; box-shadow: none !important; font-weight: 600 !important;\n padding: 6px 12px !important; border-radius: 8px !important; }\n.td-head-btn:hover { border-color: #FF5F00 !important; }\n#td-chat-col .message.user { background: #00233C !important;\n border-radius: 18px 18px 4px 18px !important; border: none !important; }\n.dark #td-chat-col .message.user { background: #FF5F00 !important; }\n#td-chat-col .message.user, #td-chat-col .message.user * { color: #fff !important; }\n#td-chat-col .message.bot { background: var(--background-fill-primary) !important;\n border: 1px solid var(--border-color-primary) !important; border-radius: 4px 18px 18px 18px !important;\n box-shadow: 0 1px 2px rgba(0,35,60,.06), 0 4px 16px rgba(0,35,60,.06) !important; }\n#td-chat-col .bot table { border-collapse: collapse; font-size: .88rem; }\n#td-chat-col .bot th, #td-chat-col .bot td { border: 1px solid var(--border-color-primary); padding: 5px 10px;\n text-align: left; }\n#td-chat-col .bot th, #td-chat-col .bot th * { background: #00233C !important; color: #fff !important; }\n#td-chat-col .bot tr:nth-child(even) td { background: var(--background-fill-secondary); }\n#td-chat-col .bot code { color: #FF5F00; background: var(--background-fill-secondary); border-radius: 4px;\n padding: 1px 4px; }\n#td-chat-col .bot pre code { color: inherit; background: transparent; }\n#td-composer { border: 1px solid var(--border-color-primary); border-radius: 22px; padding: 4px 4px 4px 14px;\n background: var(--background-fill-primary); align-items: center; flex-wrap: nowrap;\n box-shadow: 0 1px 2px rgba(0,35,60,.06), 0 4px 16px rgba(0,35,60,.06); }\n#td-composer:focus-within { border-color: #FF5F00; }\n#td-composer textarea, #td-composer .wrap, #td-composer label { border: none !important; box-shadow: none !important;\n background: transparent !important; }\n#td-send { flex: none !important; border-radius: 50% !important; width: 40px !important; height: 40px !important;\n min-width: 40px !important; background: #FF5F00 !important; color: #fff !important; border: none !important;\n padding: 0 !important; }\n#td-composer-foot { align-items: center; gap: 12px; }\n.td-foot, #td-step { color: var(--body-text-color-subdued); font-size: .78rem; }\n.td-ph-title { font-size: 1.9rem; font-weight: 300; color: var(--body-text-color); }\n.td-ph-sub { color: var(--body-text-color-subdued); margin-top: 4px; }\n#td-graph-col { border-left: 1px solid var(--border-color-primary); padding-left: 10px; }\n'
_TD_MD_CSS
module-attribute
¶
_TD_MD_CSS = "<style>.td-md { font-family: system-ui, -apple-system, Segoe UI, sans-serif; color:#2c3e50; }.td-md h1, .td-md h2, .td-md h3 { color:#00233C; }.td-md h2 { border-bottom:2px solid #FF5F00; padding-bottom:4px; margin-top:18px; }.td-md h3 { margin-top:14px; }.td-md p { margin:6px 0; }.td-md pre { background:#0f172a; color:#e2e8f0; padding:12px 16px; border-radius:8px; overflow-x:auto; margin:8px 0; font-size:.85rem; }.td-md code { font-family:'Courier New', monospace; font-size:.88em; color:#c0392b; }.td-md pre code { color:#e2e8f0; background:transparent; }.td-md table { border-collapse:collapse; width:100%; margin:10px 0; font-size:.9rem; }.td-md th, .td-md td { border:1px solid #E3E8EE; padding:6px 12px; text-align:left; }.td-md th { background:#00233C; color:#fff; font-weight:600; }.td-md tr:nth-child(even) { background:#f8fafc; }.td-md blockquote { border-left:4px solid #FF5F00; margin:0; padding:4px 12px; background:#f8fafc; color:#555; }.td-md strong { color:#00233C; }.td-md th * { color:#fff; }.td-md ul, .td-md ol { margin:6px 0; padding-left:24px; }.td-md li { margin:2px 0; }.td-md.inline p { display:inline; margin:0; }</style>"
_EV_METRICS
module-attribute
¶
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.
_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. |
build_vector_index
¶
Build or refresh the three vector-store collections from the business dictionary tables.
Dispatches to the configured backend (tdfs4ds.VECTOR_STORE_BACKEND):
'chroma' (default) or 'teradata'.
On Chroma, each feature store has its own three collections
(chroma_collection_name). stores lists the stores to index (database
names or descriptors; default: every store in tdfs4ds.read_stores(), the
primary first). A store that is not the primary is read through its public
FS_V_BUSINESS_DICTIONARY_* views, so read rights are enough, and nothing
is written to its database. stores is ignored by the Teradata backend,
which indexes the primary only.
progress, when given, is called with one dict per step: store,
source, collection (1-based), n_collections, done, total
(documents to embed in that collection, None before it is known) and
detail. A failing callback never stops the build.
Returns, on Chroma, {'stores': [{'store', 'collections': {source:
{'embedded', 'skipped', 'error'}}}]}.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
force_rebuild
|
bool
|
If True, delete existing collections and re-index from
scratch. On Chroma this needs no special privileges. On Teradata
it destroys and recreates each collection, which requires admin
privileges ( |
False
|
charts_to_panel_html
¶
Render a list of {"title","html"} charts as stacked sandboxed iframes.
wrap_iframe
¶
Wrap a full HTML document in a sandboxed iframe for safe embedding.
Uses sandbox="allow-scripts allow-downloads" (Plotly needs JS; the chart
toolbar downloads its PNG / SVG) and deliberately omits allow-same-origin
so the embedded content runs in a unique, isolated origin. The HTML is
escaped for the srcdoc attribute.
_format_step
¶
Return a human-readable progress label for the given graph node.
_format_live_trace_line
¶
Return one markdown table row for the live accumulating trace panel.
_chat_fn
¶
Synchronous chat function compatible with Gradio Chatbot (messages format).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
message
|
str
|
User message text. |
required |
history
|
list[dict]
|
List of message dicts with 'role' and 'content' keys. |
required |
Returns:
| Type | Description |
|---|---|
tuple[list[dict], str]
|
Tuple of (updated history, empty string to clear the input box). |
_list_plugin_skills
¶
List plugin skills found in TDFS4DS_SKILLS_FOLDER.
Returns a list of {name, intent, description, path} dicts (one per skill
directory holding a SKILL.md). Empty when the folder is unset/missing.
_safe_skill_slug
¶
Normalise a skill name to a safe directory slug (no traversal).
_import_plugin_skill_md
¶
Write md_text as ${TDFS4DS_SKILLS_FOLDER}/<name>/SKILL.md.
Returns a status string. Requires TDFS4DS_SKILLS_FOLDER to be set to an
existing directory and name to yield a non-empty safe slug.
_suggestion_facts
¶
What the starter questions name, read from the connected store now.
Three small reads of the primary store, never the agent's per-turn cache (a cache read outside a turn can belong to the store used before a switch):
- the current processes (domain, view, feature names);
- the last completed run of each process (
FS_FOLLOW_UP); - the documented columns of those process views (business dictionary).
The feature named is a documented one whose process ran most recently, so
"How is X computed?" and "Is X up to date?" both have an answer; its domain
is the one suggested. Returns {} when no store is connected or a read
fails.
chat_suggestions
¶
Starter questions, named after real objects of the connected store.
Shared by the admin app's chat (/ui/chat/suggestions) and the Gradio chat,
and computed on every call (a few small reads, see :func:_suggestion_facts)
so a store switch shows at once. Never a hard-coded store name (0.3.1.237).
Fail-open to generic questions.
_chat_theme
¶
The Teradata look for the Gradio chat (orange primary, slate neutral).
The font is set by the CSS, not here: with plain strings in font=, Gradio
6.11 once failed serving the page while comparing the theme with its built-in
ones ('str' object has no attribute 'name').
chat_style_kwargs
¶
css/theme (and the avatar's folder) for target, when it takes them.
Gradio 6 reads the style from launch() / mount_gradio_app() and ignores
it on gr.Blocks (with a warning); older versions read it from gr.Blocks.
_blocks_style_kwargs
¶
Style for gr.Blocks only where launch cannot take it (Gradio < 6).
_chatbot_kwargs
¶
gr.Chatbot arguments, each passed only when this Gradio has it.
_build_chat_ui
¶
_build_chat_ui(initial_thread_id: str, with_skills_pane: bool = True, chat_height=620, suggestions=None) -> dict
Build the chat components + wiring inside the current gr.Blocks context.
When with_skills_pane is False the right-hand "Additional skills" column is
omitted (used by :func:build_chat_blocks, where skills are managed by the
surrounding FastAPI shell). The returned dict then carries None for every
skills_* key.
Must be called while a gr.Blocks (or layout) context is active. Returns a
dict of the components an embedding app may need to drive from the outside
(the chatbot, trace, viz panel, step indicator, thread-id state, input box).
The behaviour here is identical to the pre-refactor inline body of
:func:launch_chatbot — extracted verbatim so both launch_chatbot and
launch_chatbot_app share one implementation.
launch_chatbot
¶
launch_chatbot(title: str = 'Feature Store Assistant', description: str = 'Ask questions about available features, datasets, definitions, data freshness, or usage guidance — in French or English.', port: int = 7860, public_base_url: Optional[str] = None, inline: bool = True, share: bool = False, thread_id: Optional[str] = None) -> gr.Blocks
Launch a Gradio chatbot interface for the consumer agent.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
title
|
str
|
Title displayed at the top of the UI. |
'Feature Store Assistant'
|
description
|
str
|
Subtitle / description text. |
'Ask questions about available features, datasets, definitions, data freshness, or usage guidance — in French or English.'
|
port
|
int
|
HTTP port for the Gradio server. |
7860
|
public_base_url
|
Optional[str]
|
If running behind JupyterHub, the public base URL
(e.g. |
None
|
inline
|
bool
|
Whether to display an inline iframe in Jupyter. |
True
|
share
|
bool
|
Whether to create a public Gradio share link. |
False
|
thread_id
|
Optional[str]
|
Fixed conversation thread ID. If None, a new UUID is generated per session. |
None
|
Returns:
| Type | Description |
|---|---|
Blocks
|
The Gradio Blocks demo instance. |
launch_chatbot_with_index
¶
launch_chatbot_with_index(port: int = 7860, force_rebuild: bool = False, title: str = None, description: str = None, public_base_url: Optional[str] = None, inline: bool = True, share: bool = False, thread_id: Optional[str] = None) -> gr.Blocks
Build the vector index then launch the Gradio chatbot in one call.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
port
|
int
|
HTTP port for the Gradio server. |
7860
|
force_rebuild
|
bool
|
If True, wipe and rebuild the Chroma index from scratch. If False (default), only index new rows (incremental). |
False
|
title
|
str
|
Title displayed at the top of the UI. |
None
|
description
|
str
|
Subtitle / description text. |
None
|
public_base_url
|
Optional[str]
|
JupyterHub public base URL for proxy-aware links. |
None
|
inline
|
bool
|
Whether to display an inline iframe in Jupyter. |
True
|
share
|
bool
|
Whether to create a public Gradio share link. |
False
|
thread_id
|
Optional[str]
|
Fixed conversation thread ID (None = new UUID per session). |
None
|
Returns:
| Type | Description |
|---|---|
Blocks
|
The Gradio Blocks demo instance. |
build_chat_blocks
¶
build_chat_blocks(thread_id: Optional[str] = None, *, title: str = 'Feature Store Assistant', subtitle: Optional[str] = None, chat_height='calc(100vh - 260px)') -> 'gr.Blocks'
Build the chat-only gr.Blocks with the admin app's chat look.
Used at /chatbot (mounted under the FastAPI shell via
:func:gradio.mount_gradio_app) and by :func:launch_chatbot in notebooks.
The skills pane and Parameters are handled by the admin shell, so they are
omitted. Gradio 6 takes the style from launch() / mount_gradio_app():
pass :func:chat_style_kwargs there.
_score_badge
¶
Return an inline HTML badge span coloured by score (1-5 int or None).
_qopt_load_frame
¶
Return a pandas DataFrame of processes with stored EXPLAIN scores.
Columns: DATA_DOMAIN, VIEW_NAME, PROCESS_ID, USER_SCORE, OPTIMIZATION_SCORE, N_STEPS, N_SPOOL_OBJECTS. Fail-open: returns an empty frame on any error.
_md_to_html
¶
Render a Markdown string to styled HTML using the real markdown package.
Handles GFM pipe tables, fenced code, inline code, bold/italic and lists —
unlike the various hand-rolled regex renderers it replaces. Scoped via the
.td-md class. inline=True keeps short single-line content (e.g. a
warning item or a judge rationale cell) from forcing a block paragraph.
with_css=False omits the <style> block when the caller emits it once
for many fragments. Fail-open: returns an escaped <pre> on any error.
_qopt_detail_md
¶
Kept for backwards compatibility — delegates to _qopt_detail_html.
_qopt_detail_html
¶
Return a styled HTML fragment with query detail, scores, warnings and recs.
_qopt_render_report
¶
Render the optimizer full-markdown report as styled HTML.
Structure:
1. Hero banner (score delta, strategy, token count)
2. Side-by-side SQL comparison (current left, optimized right)
3. Summary paragraph (inline, not collapsible)
4. Collapsible
_qopt_report_html_export
¶
Build a self-contained styled HTML page for the optimizer report.
_dash_inventory_html
¶
Return KPI card HTML from the catalog DataFrames.
_entity_key_html
¶
Entity columns, each tagged PI (primary index) / P (partitioning column)
of the entity's storage tables. PI* / P*: only some tables agree.
_dash_tree_html
¶
Return a pure-HTML/CSS expandable tree: DATA_DOMAIN → ENTITY → features.
key_roles: {entity_key: {"tables": n, "pi": {COL: k}, "part": {COL: k}}}
(store_consistency.entity_key keys) tags each entity column as primary
index / partitioning column of its storage tables. None = no tags (the
output is then unchanged).
_dash_bar_figs
¶
Return list of Plotly bar figures for per-domain counts.
_dash_lineage_counts_fig
¶
Return a Plotly table figure with upstream/downstream lineage counts per object.
Reads the persisted FS_LINEAGE_GRAPH once, then counts ancestors/descendants for each registered process view and dataset. Fail-open: returns an empty figure when the graph is unavailable.
_dash_tech_tables_frame
¶
Return a pandas DataFrame summarising the feature tables (catalog only).
_dash_tech_tables_fig
¶
Return a Plotly table figure for the feature-tables summary.
_dash_run_history_figs
¶
Return (last5_fig, duration_fig, cadence_fig, reliability_fig) Plotly figures.
Reads the follow-up log for all processes (bounded window: most recent 50 runs per process or last 90 days), then computes stats. Fail-open: returns None for figures when data is missing.
pid_to_view is an optional {PROCESS_ID: VIEW_NAME} dict; when provided the
x-axis labels use the human-readable view name instead of the raw UUID.
_dash_storage_counts
¶
Per-table CURRENT VALIDTIME + NONSEQUENCED VALIDTIME row counts (+ DBC size).
Refactored core of the live row-count pass (formerly inlined in
_dash_compute_counts). Fast on a healthy store (stats up to date, PI +
partitions). Optionally scoped to a list of domains and/or to the feature
tables of a list of processes (pid_to_tables maps PROCESS_ID → set of
"DB.TABLE" the process writes to). emit is a callable(str) per-table
progress hook; structured PROGRESS k/N lines drive the SSE bar.
Returns a pandas DataFrame with columns:
Database, Table, Domains, Entities, # Features, CurrentRows, NonseqRows,
HistoryRows, SizeBytes. Fail-open per table (missing counts → None).
_dash_storage_treemap_fig
¶
Treemap: data domain → entity → table → feature, sized by stored rows.
A single FS_T_* table physically stores several features in one table, so
the table's NonseqRows is split evenly across its features for the leaf size
(noted in the hover). Colour encodes the history ratio (HistoryRows/NonseqRows).
_dash_storage_stacked_fig
¶
Per-table stacked bar: current-valid rows + history rows (= nonsequenced).
_dash_orphans
¶
Detect FEATURE_IDs present in an FS_T_* table but absent from the catalog.
For each storage table, the catalog assigns a set of FEATURE_IDs; any DISTINCT FEATURE_ID physically in the table but not in that set is an orphan (a feature that was de-registered without purging its stored rows). Read-only. Returns (status_msg, plotly_table_fig_or_None, orphan_rows_list).
_dash_load_followup
¶
Load + normalise the follow-up log into a pandas frame (shared by the run-history figures). Returns an empty frame on any failure (fail-open).
_dash_run_timeline_fig
¶
Per-run timeline (Gantt): each run a bar START→END, coloured by status.
_dash_run_calendar_fig
¶
Day × process status heatmap (1 = all ok, 0 = any failure that day).
_dash_run_detail_fig
¶
Full run log for one process as a Plotly table (incl. APPLIED_FILTER).
_execution_starts
¶
Start times of a process's runs, sorted: one rule for the whole package,
:func:tdfs4ds.process_store.process_followup.execution_starts (a FilterManager
run writes one row per segment, so gaps between rows say nothing about how
often the process runs).
_dash_insights
¶
Compute 'remarkable fact' callouts. Returns a list of {icon,title,detail}.
dsf: the dataset-features frame already read (at the report's valid
time); read from the current catalog when not given.
_dash_compute_counts_standalone
¶
Module-level live storage-count pass (used by the serve panel / SSE route).
Returns (status, counts_df, treemap_html, stacked_html, orphans_html,
counts_table_html, orphan_rows) — counts_df (pandas) and orphan_rows
(list of dicts) are the raw data the exportable report consumes.
_fig_to_iframe
¶
Convert a Plotly figure to a sandboxed iframe HTML string.
_ev_scenarios_summary_html_standalone
¶
Compact styled HTML table summarising a scenario set (module-level twin
of the launch_chatbot_app closure _ev_scenarios_summary_html).
_ev_summary_chart_standalone
¶
Plotly per-skill bar chart from evaluation results, as iframe HTML.
Module-level twin of the launch_chatbot_app closure _ev_summary_chart
so :mod:tdfs4ds.serve.panels can reuse it without Gradio.
_ev_aggregate_by_skill
¶
Average the 5 metrics per skill/category across scenarios → {label: {metric: avg}}.
_ev_radar_chart_standalone
¶
Plotly radar (Scatterpolar) over the 5 metrics, one trace per skill — as iframe HTML.
Clearer than the grouped bar for comparing the 5 evaluation dimensions at a glance.
_ev_score_table_standalone
¶
Color-coded per-skill score table (5 metrics + average) as HTML.
_reset_agent
¶
Drop the cached consumer-agent graph + memory so config changes take effect.
launch_chatbot_app
¶
launch_chatbot_app(port: int = 7860, server_name: str = '0.0.0.0', title: str = 'Feature Store Assistant', description: str = None, inline: bool = False, share: bool = False, thread_id: Optional[str] = None) -> gr.Blocks
Launch the consumer-agent chatbot with a Teradata-styled tabbed interface.
The layout uses a left-oriented tab nav (sidebar look) + a hero header,
themed via :data:_TD_CSS (Teradata orange/navy palette). Tabs:
- 💬 Chatbot — the conversational feature-store assistant (left) plus a
right Additional skills pane that lists the plugin skills in
TDFS4DS_SKILLS_FOLDERand can import/export a skill'sSKILL.md. - 🔗 Lineage — interactive pyvis dependency graph of the full persisted
lineage (
FS_LINEAGE_GRAPH). Click Refresh lineage graph to load. - 🧪 Evaluation — generate / import / run evaluation scenarios + report.
-
⚙️ Parameters — live admin controls:
-
Feature store database — switch the active feature store. The target is validated with
tdfs4ds.connect(database=..., create_if_missing=False); a database that does not contain a feature store is rejected (no switch). - Vector store — backend (chroma/teradata) + Chroma path, and a
Refresh/Rebuild button (
build_vector_index(force_rebuild=True)). - Instruct model / Embedding model — provider, model, URL, API key,
proxy (and embedding dimension), applied to the
tdfs4dsglobals.
Applying any change that affects the data or the model resets the agent
(reset_consumer_agent()), rebuilds the index when needed, rotates the
conversation thread, and clears the chat — so the session starts clean.
The existing :func:launch_chatbot / :func:launch_chatbot_with_index are
unchanged; this is an additive wrapper.
Returns:
| Type | Description |
|---|---|
Blocks
|
The Gradio Blocks demo instance. |