Skip to content

AI Agents

lumen.ai.agents

__all__ = ['Agent', 'AnalysisAgent', 'BaseCodeAgent', 'ChatAgent', 'DbtslAgent', 'DeckGLAgent', 'DocumentListAgent', 'DocumentSummarizerAgent', 'SQLAgent', 'SourceAgent', 'TableListAgent', 'ValidationAgent', 'VegaLiteAgent', 'hvPlotAgent'] module-attribute

Agent

Bases: Viewer, ToolUser, ContextProvider

Agents are actors responsible for taking a user query and performing a particular task, either by adding context or generating outputs.

Agents have access to an LLM and are given context and can solve tasks by executing a series of prompts or by rendering contents such as forms or widgets to gather user input.

agents = param.List(doc='\n List of agents this agent can invoke.') class-attribute instance-attribute

debug = param.Boolean(default=False, doc='\n Whether to enable verbose error reporting.') class-attribute instance-attribute

llm = param.ClassSelector(class_=Llm, doc='\n The LLM implementation to query.') class-attribute instance-attribute

user = param.String(default='Lumen', doc='\n The name of the user that will be respond to the user query.') class-attribute instance-attribute

applies(context) async classmethod

Additional checks to determine if the agent should be used.

respond(messages, context, step_title=None) async

Provides a response to the user query.

The type of the response may be a simple string or an object.

Arguments

messages: list[Message] The list of messages corresponding to the user query and any other system messages to be included. context: TContext A mapping containing context for the agent to perform its task. step_title: str | None If the Agent response is part of a longer query this describes the step currently being processed.

AnalysisAgent

Bases: BaseLumenAgent

analyses = param.List([]) class-attribute instance-attribute

conditions = param.List(default=['Use for custom analysis, advanced analytics, or domain-specific methods', "Use when the user query matches one of the available analyses' name or description below.", "Include the selected analysis' required cols in the instructions", 'NOT for simple queries or basic visualizations']) class-attribute instance-attribute

input_schema = AnalysisInputs class-attribute instance-attribute

output_schema = AnalysisOutputs class-attribute instance-attribute

prompts = param.Dict(default={'main': {'template': PROMPTS_DIR / 'AnalysisAgent' / 'main.jinja2', 'response_model': make_analysis_model}}) class-attribute instance-attribute

purpose = param.String(default='Perform custom analyses that are reliable and repeatable.') class-attribute instance-attribute

user = param.String(default='Analysis') class-attribute instance-attribute

respond(messages, context, step_title=None) async

BaseCodeAgent

Bases: BaseViewAgent

Base class for view agents that can generate and execute code.

Subclasses must: - Set _executor_class to the appropriate CodeExecutor subclass - Implement _generate_code_spec() for their specific code generation flow

code_execution = param.Selector(default='disabled', objects=['disabled', 'prompt', 'llm', 'allow'], doc="\n Code execution mode for generating visualizations via code:\n - disabled: No code execution; generate declarative specs only (safe for production)\n - prompt: Generate code, prompt user for permission to execute\n - llm: Generate code, validate with LLM safety check, then execute\n - allow: Generate and execute code without user confirmation\n\n ⚠️ WARNING: The 'prompt', 'llm', and 'allow' modes execute LLM-generated code and\n must NEVER be enabled in production environments with access to secrets, credentials,\n or sensitive data.\n ", allow_refs=True) class-attribute instance-attribute

code_execution_enabled property

Whether code execution is enabled (any mode except 'disabled').

ChatAgent

Bases: Agent

ChatAgent provides general information about available data and other topics to the user. When data is available, it acts as an analyst providing insights and interpretations.

conditions = param.List(default=["Use for general conversation that doesn't require fetching or querying data", 'Use for technical questions about programming, functions, methods, libraries, or APIs', "Use when user asks to 'explain', 'interpret', 'analyze', 'summarize', or 'comment on' existing data in context", "NOT when user asks to 'query', 'filter', 'calculate', 'aggregate', or 'transform' data", 'NOT for creating new data transformations - only for explaining data that already exists']) class-attribute instance-attribute

output_schema = ChatOutputs class-attribute instance-attribute

prompts = param.Dict(default={'main': {'template': PROMPTS_DIR / 'ChatAgent' / 'main.jinja2'}}) class-attribute instance-attribute

purpose = param.String(default='\n Provides conversational assistance and interprets existing results.\n Handles general questions, technical documentation, and programming help.\n When data has been retrieved, explains findings in accessible terms.') class-attribute instance-attribute

respond(messages, context, step_title=None) async

DbtslAgent

Bases: BaseLumenAgent, DbtslMixin

Responsible for creating and executing queries against a dbt Semantic Layer to answer user questions about business metrics.

conditions = param.List(default=['Always use this when dbtsl_metaset is available']) class-attribute instance-attribute

output_schema = DbtslOutputs class-attribute instance-attribute

prompts = param.Dict(default={'main': {'response_model': DbtslQueryParams, 'template': PROMPTS_DIR / 'DbtslAgent' / 'main.jinja2'}, 'revise_output': {'response_model': RetrySpec, 'template': PROMPTS_DIR / 'BaseLumenAgent' / 'revise_output.jinja2'}}) class-attribute instance-attribute

purpose = param.String(default='\n Responsible for displaying data to answer user queries about\n business metrics using dbt Semantic Layers. This agent can compile\n and execute metric queries against a dbt Semantic Layer.') class-attribute instance-attribute

requires = param.List(default=['source', 'dbtsl_metaset'], readonly=True) class-attribute instance-attribute

source = param.ClassSelector(class_=BaseSQLSource, doc='\n The source associated with the dbt Semantic Layer.') class-attribute instance-attribute

user = param.String(default='DBT') class-attribute instance-attribute

respond(messages, context, step_title=None) async

Responds to user messages by generating and executing a dbt Semantic Layer query.

DeckGLAgent

Bases: BaseCodeAgent

Agent for generating DeckGL 3D map visualizations.

Supports two generation modes: - When code_execution is 'disabled': Generate DeckGL JSON specs directly - When code_execution is enabled: Generate PyDeck Python code, execute safely, convert to spec

conditions = param.List(default=['Use for 3D geographic visualizations, map-based data, or when user requests DeckGL/deck.gl', 'Use for large-scale geospatial data with latitude/longitude coordinates', 'Use for hexbin aggregations, heatmaps, or 3D extruded visualizations on maps', "Can render a geometry column (GeoDataFrame polygons/lines/points) on a basemap, suited to 3D/extruded maps or geometry whose schema is 'geographic' (a lat/lon CRS) so the basemap gives real-world context", 'NOT when the table only names places (a state or country column) without geometry or lat/lon, since deck.gl has nothing to draw and cannot fetch boundaries']) class-attribute instance-attribute

default_map_style = 'https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json' class-attribute instance-attribute

prompts = param.Dict(default={'main': {'response_model': DeckGLSpec, 'template': PROMPTS_DIR / 'DeckGLAgent' / 'main.jinja2'}, 'main_pydeck': {'response_model': PyDeckSpec, 'template': PROMPTS_DIR / 'DeckGLAgent' / 'main_pydeck.jinja2'}, 'code_safety': {'response_model': CodeSafetyCheck, 'template': PROMPTS_DIR / 'DeckGLAgent' / 'code_safety.jinja2'}, 'revise_output': {'response_model': RetrySpec, 'template': PROMPTS_DIR / 'DeckGLAgent' / 'revise_output.jinja2'}}) class-attribute instance-attribute

purpose = param.String(default='Generates DeckGL 3D map visualizations from geographic data.') class-attribute instance-attribute

user = param.String(default='DeckGL') class-attribute instance-attribute

view_type = DeckGLView class-attribute instance-attribute

respond(messages, context, step_title=None) async

Generate a DeckGL visualization.

DocumentListAgent

Bases: BaseListAgent

The DocumentListAgent lists all available documents provided by the user.

conditions = param.List(default=['Use when user asks to list or see all available documents', 'NOT when user asks about specific document content']) class-attribute instance-attribute

input_schema = DocumentListInputs class-attribute instance-attribute

purpose = param.String(default='\n Displays a list of all available documents.') class-attribute instance-attribute

user = param.String(default='Documents') class-attribute instance-attribute

applies(context) async classmethod

DocumentSummarizerAgent

Bases: BaseLumenAgent

Synthesizes relevant document context into a user-focused summary.

conditions = param.List(default=['Use when user asks questions answerable from documentation or uploaded metadata files', 'Use for summarization of documentation', 'NOT when user only asks to list available documents']) class-attribute instance-attribute

input_schema = DocumentSummarizerInputs class-attribute instance-attribute

not_with = param.List(default=['DbtslAgent', 'SQLAgent']) class-attribute instance-attribute

output_schema = DocumentSummarizerOutputs class-attribute instance-attribute

prompts = param.Dict(default={'main': {'template': PROMPTS_DIR / 'DocumentSummarizerAgent' / 'main.jinja2'}}) class-attribute instance-attribute

purpose = param.String(default='\n Synthesizes relevant documents into an answer focused on the user query.\n Also renders a document editor with tabbed previews of matched files.') class-attribute instance-attribute

user = param.String(default='Summarizer') class-attribute instance-attribute

applies(context) async classmethod

respond(messages, context, step_title=None) async

SQLAgent

Bases: BaseLumenAgent

clean_data = param.Boolean(default=True, doc='\n Profile a bounded sample of each query result and, when it contains data-quality problems,\n spend one extra LLM call rewriting the query to clean them up.\n Profiling is deterministic and reuses the frame validation already\n fetched, so a clean result costs nothing; set to False to always take\n the query exactly as first written.') class-attribute instance-attribute

conditions = param.List(default=['Use for querying, filtering, aggregating, or transforming data with SQL', "Use for calculations that require executing SQL (e.g., 'calculate average', 'sum by category')", "Use when user asks to 'show', 'get', 'fetch', 'query', 'find', 'filter', 'calculate', 'aggregate', or 'transform' data", 'Use after external data has been fetched and the user expects the fetched fields/rows to be presented, selected, filtered, or otherwise queried', 'Use when user asks about data quality, completeness, missing values, duplicates, or outliers', "NOT when user asks to 'explain', 'interpret', 'analyze', 'summarize', or 'comment on' existing data", 'NOT useful if the user is using the same data for plotting']) class-attribute instance-attribute

exclusions = param.List(default=['dbtsl_metaset']) class-attribute instance-attribute

input_schema = SQLInputs class-attribute instance-attribute

llm_tools = param.List(default=[make_apply_filter_llm_tool]) class-attribute instance-attribute

max_request_llm_calls = param.Integer(default=40, bounds=(1, None), doc='\n Maximum LLM calls one SQL request may spend across all attempts, tool\n rounds and revisions. Checked before each attempt.') class-attribute instance-attribute

not_with = param.List(default=['DbtslAgent', 'MetadataLookup', 'TableListAgent']) class-attribute instance-attribute

output_schema = SQLOutputs class-attribute instance-attribute

prompts = param.Dict(default={'main': {'response_model': make_sql_model, 'template': PROMPTS_DIR / 'SQLAgent' / 'main.jinja2'}, 'clean_data': {'response_model': SQLCleanup, 'template': PROMPTS_DIR / 'SQLAgent' / 'clean_data.jinja2'}, 'revise_output': {'response_model': RetrySpec, 'template': PROMPTS_DIR / 'SQLAgent' / 'revise_output.jinja2'}}) class-attribute instance-attribute

purpose = param.String(default='\n Creates and executes SQL queries to retrieve, filter, aggregate, or transform data.\n Handles table joins, WHERE clauses, GROUP BY, calculations, and other SQL operations.\n Generates new data pipelines from SQL transformations.\n Profiles every result for data-quality problems (missing values, duplicates,\n placeholder numbers, outliers) and reports them.') class-attribute instance-attribute

query_timeout = param.Number(default=60, bounds=(0, None), allow_None=True, doc="\n Seconds a query run on the model's behalf may take before it is\n abandoned, or None to wait indefinitely. A timed out query is\n revised once; a second timeout ends the request.") class-attribute instance-attribute

request_timeout = param.Number(default=300, bounds=(0, None), allow_None=True, doc='\n Wall-clock limit in seconds for one SQL request across all attempts.\n None disables the limit.') class-attribute instance-attribute

schema_max_tokens = param.Integer(default=4000, allow_None=True, doc='\n Token budget for the data summary in the prompt. Tables beyond it are\n listed with names and types only, and the model loads the rest with\n load_table_schemas.') class-attribute instance-attribute

schema_tables_shown = param.Integer(default=25, bounds=(1, None), doc='\n Most relevant tables described in the data summary; the rest are\n listed by name. schema_max_tokens usually binds first.') class-attribute instance-attribute

stats_wait = param.Number(default=10, allow_None=True, doc='\n Seconds to wait for column statistics that are still being computed\n before answering with names and types; profiling continues in the\n background for later questions. None waits until they are ready.') class-attribute instance-attribute

user = param.String(default='SQL') class-attribute instance-attribute

respond(messages, context, step_title=None) async

Generate SQL in one step (catalog browse, schema load, optional exploration, then structured query).

revise(instruction, messages, context, view=None, spec=None, language=None, errors=None, max_retries=2, **kwargs) async

SourceAgent

Bases: Agent

Fetches data from external APIs, Python callables, or URL templates configured via source controls.

The agent reads available actions from SourceLookup context, wraps the relevant callables as FunctionTool instances, and passes them to the LLM. The LLM decides which action to call and fills in parameter values. Results are normalized to DataFrames and registered as DuckDBSource entries for downstream agents (e.g. SQLAgent, hvPlotAgent) to consume.

conditions = param.List(default=['Use when no existing data source can answer the query and external source controls are configured', 'Use when the user asks for data from an external API or service', 'Use when the user provides a URL to read, fetch, discuss, or extract content from — the content must be fetched before any other agent can address it', "Use when the loaded data does not cover the requested scope (e.g. user asks for 'whole month' but data summary only shows a week)", 'Prefer over metadata discovery when no data sources are loaded but source controls exist', 'This agent fetches and registers data or indexes page content as documents — always follow with a presentation, query, or chat step', 'For HTML pages, this agent extracts both tabular data and page text (indexed as a searchable document for ChatAgent)', 'Output is queryable tabular data and/or indexed documents — enables filtering, aggregation, joins, transformation, and conversation by downstream agents', "NOT for listing configured external source actions or answering 'what data is available' before a concrete dataset request", 'NOT when the loaded data already covers the requested scope and user just wants to query/visualize it']) class-attribute instance-attribute

input_schema = SourceInputs class-attribute instance-attribute

output_schema = SourceOutputs class-attribute instance-attribute

prompts = param.Dict(default={'main': {'template': PROMPTS_DIR / 'SourceAgent' / 'main.jinja2'}}) class-attribute instance-attribute

purpose = param.String(default='\n Fetches data from configured external sources (APIs, Python callables,\n URL templates). Discovers relevant endpoints, fills parameters via\n LLM, executes the call, and registers the resulting DataFrame for\n downstream querying and visualization. For HTML pages, also indexes\n page text as a searchable document for conversational agents.') class-attribute instance-attribute

user = param.String(default='Source') class-attribute instance-attribute

applies(context) async classmethod

respond(messages, context, step_title=None) async

TableListAgent

Bases: BaseListAgent

The TableListAgent lists all available data and lets the user pick one.

conditions = param.List(default=["Use when user explicitly asks to 'list data', 'show available data', or 'what data do you have'", 'NOT for showing actual data contents, querying, or analyzing data', 'NOT for describing data sources or telling what the data is about']) class-attribute instance-attribute

input_schema = TableListInputs class-attribute instance-attribute

not_with = param.List(default=['DbtslAgent', 'SQLAgent']) class-attribute instance-attribute

purpose = param.String(default='\n Displays a list of all available data & datasets. Not useful for identifying which dataset to use for analysis.') class-attribute instance-attribute

user = param.String(default='Tables') class-attribute instance-attribute

applies(context) async classmethod

ValidationAgent

Bases: Agent

ValidationAgent focuses solely on validating whether the executed plan fully answered the user's original query. It identifies missing elements and suggests next steps when validation fails.

conditions = param.List(default=['Use to validate whether executed plans fully answered user queries', 'Use to identify missing elements from the original user request', 'NOT for data analysis, pattern identification, or technical programming questions']) class-attribute instance-attribute

input_schema = ValidationInputs class-attribute instance-attribute

output_schema = ValidationOutputs class-attribute instance-attribute

prompts = param.Dict(default={'main': {'template': PROMPTS_DIR / 'ValidationAgent' / 'main.jinja2', 'response_model': QueryCompletionValidation, 'tools': []}}) class-attribute instance-attribute

purpose = param.String(default="\n Validates whether executed plans fully answered the user's original query.\n Identifies missing elements, assesses completeness, and suggests next steps\n when validation fails. Acts as a quality gate for plan execution.") class-attribute instance-attribute

user = param.String(default='Validation') class-attribute instance-attribute

respond(messages, context, step_title=None) async

VegaLiteAgent

Bases: BaseCodeAgent

conditions = param.List(default=['Use for publication-ready visualizations or when user specifically requests Vega-Lite charts', 'Use for polished charts intended for presentation or sharing', 'Use for a choropleth whose table names places (states, countries) but holds no geometry or coordinates, since the boundaries can be looked up and joined by name']) class-attribute instance-attribute

doc_pages_per_slug = param.Integer(default=2, bounds=(1, None), doc='\n Maximum number of retrieved sections allowed from any single\n documentation page. Without a cap, similarity collapses onto one page:\n a "bar chart" query returns five near-identical mark-bar variants while\n the transform and encoding pages that would broaden the answer sit just\n below the cut.') class-attribute instance-attribute

exclude_spec_constructs = param.List(default=['facet', 'repeat', 'concat', 'hconcat', 'vconcat', 'row', 'column'], item_type=str, doc='\n Vega-Lite composition keys that disqualify a retrieved example spec.\n The declarative prompt tells the model not to pack several plots into one\n entry with these, so injecting a reference spec that uses them puts the\n documentation at odds with the instructions. Set to [] to keep every\n retrieved spec -- appropriate for the Altair path, whose prompt permits\n composition within a single chart entry.') class-attribute instance-attribute

n_doc_pages = param.Integer(default=5, bounds=(0, None), doc='\n Number of documentation sections to inject into the prompt.\n Set to 0 to disable documentation lookup entirely.') class-attribute instance-attribute

prompts = param.Dict(default={'main': {'response_model': VegaLiteSpec, 'template': PROMPTS_DIR / 'VegaLiteAgent' / 'main.jinja2'}, 'main_altair': {'response_model': AltairSpec, 'template': PROMPTS_DIR / 'VegaLiteAgent' / 'main_altair.jinja2'}, 'code_safety': {'response_model': CodeSafetyCheck, 'template': PROMPTS_DIR / 'VegaLiteAgent' / 'code_safety.jinja2'}, 'interaction_polish': {'response_model': VegaLiteSpecUpdate, 'template': PROMPTS_DIR / 'VegaLiteAgent' / 'interaction_polish.jinja2'}, 'annotate_plot': {'response_model': VegaLiteSpecUpdate, 'template': PROMPTS_DIR / 'VegaLiteAgent' / 'annotate_plot.jinja2'}, 'revise_output': {'response_model': RetrySpec, 'template': PROMPTS_DIR / 'VegaLiteAgent' / 'revise_output.jinja2'}}) class-attribute instance-attribute

purpose = param.String(default='Generates one or more vega-lite plot specifications from the input data pipeline.') class-attribute instance-attribute

reserve_base_form = param.Boolean(default=True, doc='\n Guarantee one retrieved section is the base form of a chart type, when\n the candidate pool contains one. A bare request like "plot a bar chart"\n matches every bar variant about equally -- similarity across the top\n candidates spans less than the noise floor -- so the plain section loses\n to specialisations that simply have more text to match on. Sections are\n flagged deterministically at build time from their title, which is why\n this reserves a slot rather than nudging a score.') class-attribute instance-attribute

user = param.String(default='Vega') class-attribute instance-attribute

vector_store_path = param.Path(default=None, check_exists=False, doc='\n Path to a custom vector store for storing and retrieving Vega-Lite examples;\n if not provided a default store will be used depending on the LLM--\n OpenAIEmbeddings for OpenAI LLM or NumpyEmbeddings for all others.') class-attribute instance-attribute

view_type = VegaLiteView class-attribute instance-attribute

annotate(instruction, messages, context, spec, view=None) async

Apply annotations based on user request.

Parameters:

Name Type Description Default
instruction str

User's description of what to annotate

required
messages list[Message]

Chat history for context

required
context TContext

Session context

required
spec dict

The current VegaLite specification (full dict with 'spec' key)

required
view VegaLiteEditor | None

Optional VegaLite editor to export plot image from for vision analysis.

None

Returns:

Type Description
str

Updated specification with annotations

respond(messages, context, step_title=None) async

Generates one or more VegaLite visualizations using a progressive building approach with real-time updates.

revise(feedback, messages, context, view=None, spec=None, language=None, errors=None, **kwargs) async

Revise a VegaLite specification based on user feedback.

This override adds: 1. Doc examples from vector store 2. Plot image for vision analysis (if view is a VegaLiteEditor)

Parameters:

Name Type Description Default
feedback str

User's feedback or instruction for revision.

required
messages list[Message]

Chat message history.

required
context TContext

Session context.

required
view LumenEditor | None

The editor containing the current spec to revise.

None
spec str | None

The spec string (used if view is None).

None
language str | None

The spec language (used if view is None).

None
errors list[str] | None

List of errors to include in context.

None
**kwargs

Additional arguments passed to parent revise.

{}

Returns:

Type Description
str

The revised YAML specification.

hvPlotAgent

Bases: BaseViewAgent

conditions = param.List(default=['Use for exploratory data analysis, interactive plots, and dynamic filtering', 'Use for quick, iterative data visualization during analysis', "Can render a geometry column (GeoDataFrame polygons/lines/points) as a 2D choropleth, shading shapes by a value column without a basemap; fits geometry whose schema is not 'geographic' (a projected or absent CRS) or when a basemap adds no context"]) class-attribute instance-attribute

prompts = param.Dict(default={'main': {'template': PROMPTS_DIR / 'hvPlotAgent' / 'main.jinja2'}}) class-attribute instance-attribute

purpose = param.String(default='Generates a plot of the data given a user prompt.') class-attribute instance-attribute

user = param.String(default='hvPlot') class-attribute instance-attribute

view_type = hvPlotView class-attribute instance-attribute

analysis

AnalysisAgent

Bases: BaseLumenAgent

analyses = param.List([]) class-attribute instance-attribute
conditions = param.List(default=['Use for custom analysis, advanced analytics, or domain-specific methods', "Use when the user query matches one of the available analyses' name or description below.", "Include the selected analysis' required cols in the instructions", 'NOT for simple queries or basic visualizations']) class-attribute instance-attribute
input_schema = AnalysisInputs class-attribute instance-attribute
output_schema = AnalysisOutputs class-attribute instance-attribute
prompts = param.Dict(default={'main': {'template': PROMPTS_DIR / 'AnalysisAgent' / 'main.jinja2', 'response_model': make_analysis_model}}) class-attribute instance-attribute
purpose = param.String(default='Perform custom analyses that are reliable and repeatable.') class-attribute instance-attribute
user = param.String(default='Analysis') class-attribute instance-attribute
respond(messages, context, step_title=None) async

AnalysisInputs

Bases: ContextModel

data instance-attribute
pipeline instance-attribute

AnalysisOutputs

Bases: ContextModel

analysis instance-attribute
pipeline instance-attribute
view instance-attribute

make_analysis_model(analyses)

base

Agent

Bases: Viewer, ToolUser, ContextProvider

Agents are actors responsible for taking a user query and performing a particular task, either by adding context or generating outputs.

Agents have access to an LLM and are given context and can solve tasks by executing a series of prompts or by rendering contents such as forms or widgets to gather user input.

agents = param.List(doc='\n List of agents this agent can invoke.') class-attribute instance-attribute
debug = param.Boolean(default=False, doc='\n Whether to enable verbose error reporting.') class-attribute instance-attribute
llm = param.ClassSelector(class_=Llm, doc='\n The LLM implementation to query.') class-attribute instance-attribute
user = param.String(default='Lumen', doc='\n The name of the user that will be respond to the user query.') class-attribute instance-attribute
applies(context) async classmethod

Additional checks to determine if the agent should be used.

respond(messages, context, step_title=None) async

Provides a response to the user query.

The type of the response may be a simple string or an object.

Arguments

messages: list[Message] The list of messages corresponding to the user query and any other system messages to be included. context: TContext A mapping containing context for the agent to perform its task. step_title: str | None If the Agent response is part of a longer query this describes the step currently being processed.

base_code

Base class for agents that execute LLM-generated code.

This provides a clean inheritance hierarchy: BaseViewAgent └── BaseCodeAgent ├── VegaLiteAgent └── (future code-executing agents)

BaseCodeAgent

Bases: BaseViewAgent

Base class for view agents that can generate and execute code.

Subclasses must: - Set _executor_class to the appropriate CodeExecutor subclass - Implement _generate_code_spec() for their specific code generation flow

code_execution = param.Selector(default='disabled', objects=['disabled', 'prompt', 'llm', 'allow'], doc="\n Code execution mode for generating visualizations via code:\n - disabled: No code execution; generate declarative specs only (safe for production)\n - prompt: Generate code, prompt user for permission to execute\n - llm: Generate code, validate with LLM safety check, then execute\n - allow: Generate and execute code without user confirmation\n\n ⚠️ WARNING: The 'prompt', 'llm', and 'allow' modes execute LLM-generated code and\n must NEVER be enabled in production environments with access to secrets, credentials,\n or sensitive data.\n ", allow_refs=True) class-attribute instance-attribute
code_execution_enabled property

Whether code execution is enabled (any mode except 'disabled').

base_list

BaseListAgent

Bases: Agent

Abstract base class for agents that display a list of items to the user.

purpose = param.String(default='\n Renders a list of items to the user and lets the user pick one.') class-attribute instance-attribute
respond(messages, context, step_title=None) async

base_lumen

SPEC_TYPE_MAP = {'sql': 'query', 'yaml': 'specification', 'json': 'specification', 'vega-lite': 'visualization spec'} module-attribute

BaseLumenAgent

Bases: Agent

prompts = param.Dict(default={'explain_output': {'template': PROMPTS_DIR / 'BaseLumenAgent' / 'explain_output.jinja2'}, 'revise_output': {'response_model': RetrySpec, 'template': PROMPTS_DIR / 'BaseLumenAgent' / 'revise_output.jinja2'}}) class-attribute instance-attribute
user = param.String(default='Lumen') class-attribute instance-attribute
explain(instruction, messages, context, view=None, spec=None, language=None) async

Explain the current spec in plain language, streaming chunks.

revise(instruction, messages, context, view=None, spec=None, language=None, errors=None, **kwargs) async

Retry the output by line, allowing the user to provide instruction on why the output was not satisfactory, or an error.

base_view

BaseViewAgent

Bases: BaseLumenAgent

input_schema = ViewInputs class-attribute instance-attribute
output_schema = ViewOutputs class-attribute instance-attribute
prompts = param.Dict(default={'main': {'template': PROMPTS_DIR / 'BaseViewAgent' / 'main.jinja2'}}) class-attribute instance-attribute
respond(messages, context, step_title=None) async

Generates a visualization based on user messages and the current data pipeline.

ViewInputs

Bases: ContextModel

data instance-attribute
metaset instance-attribute
pipeline instance-attribute
table instance-attribute

ViewOutputs

Bases: ContextModel

view instance-attribute

chat

ChatAgent

Bases: Agent

ChatAgent provides general information about available data and other topics to the user. When data is available, it acts as an analyst providing insights and interpretations.

conditions = param.List(default=["Use for general conversation that doesn't require fetching or querying data", 'Use for technical questions about programming, functions, methods, libraries, or APIs', "Use when user asks to 'explain', 'interpret', 'analyze', 'summarize', or 'comment on' existing data in context", "NOT when user asks to 'query', 'filter', 'calculate', 'aggregate', or 'transform' data", 'NOT for creating new data transformations - only for explaining data that already exists']) class-attribute instance-attribute
output_schema = ChatOutputs class-attribute instance-attribute
prompts = param.Dict(default={'main': {'template': PROMPTS_DIR / 'ChatAgent' / 'main.jinja2'}}) class-attribute instance-attribute
purpose = param.String(default='\n Provides conversational assistance and interprets existing results.\n Handles general questions, technical documentation, and programming help.\n When data has been retrieved, explains findings in accessible terms.') class-attribute instance-attribute
respond(messages, context, step_title=None) async

ChatOutputs

Bases: ContextModel

chat instance-attribute

dbtsl

DbtslAgent

Bases: BaseLumenAgent, DbtslMixin

Responsible for creating and executing queries against a dbt Semantic Layer to answer user questions about business metrics.

conditions = param.List(default=['Always use this when dbtsl_metaset is available']) class-attribute instance-attribute
output_schema = DbtslOutputs class-attribute instance-attribute
prompts = param.Dict(default={'main': {'response_model': DbtslQueryParams, 'template': PROMPTS_DIR / 'DbtslAgent' / 'main.jinja2'}, 'revise_output': {'response_model': RetrySpec, 'template': PROMPTS_DIR / 'BaseLumenAgent' / 'revise_output.jinja2'}}) class-attribute instance-attribute
purpose = param.String(default='\n Responsible for displaying data to answer user queries about\n business metrics using dbt Semantic Layers. This agent can compile\n and execute metric queries against a dbt Semantic Layer.') class-attribute instance-attribute
requires = param.List(default=['source', 'dbtsl_metaset'], readonly=True) class-attribute instance-attribute
source = param.ClassSelector(class_=BaseSQLSource, doc='\n The source associated with the dbt Semantic Layer.') class-attribute instance-attribute
user = param.String(default='DBT') class-attribute instance-attribute
respond(messages, context, step_title=None) async

Responds to user messages by generating and executing a dbt Semantic Layer query.

DbtslOutputs

Bases: SQLOutputs

dbtsl_metaset instance-attribute

DbtslQueryParams

Bases: BaseModel

Model for dbtsl.client.query() parameters.

chain_of_thought = Field(description="You are a world-class dbt Semantic Layer expert. Think step by step about\n what metrics are needed, what dimensions to group by, what time granularity\n to use, and any filters that should be applied; if filters are applied, include those\n filtered dimensions in group_by. If there are errors, mention how you'll address the errors.\n ") class-attribute instance-attribute
expr_slug = Field(description='Give the query a concise, but descriptive, slug that includes the metrics\n and dimensions used, e.g. monthly_revenue_by_region. The slug must be unique.') class-attribute instance-attribute
group_by = Field(default_factory=list, description="A list of dimensions to group by, e.g. ['metric_time__month'], must include dimensions from where.") class-attribute instance-attribute
limit = Field(default=None, description='The maximum number of rows to return.') class-attribute instance-attribute
metrics = Field(default_factory=list, description="A list of metrics to include in the query, e.g. ['revenue']") class-attribute instance-attribute
order_by = Field(default_factory=list, description="A list of columns or expressions to order the results by, e.g. ['metric_time__month']") class-attribute instance-attribute
where = Field(default_factory=list, description="A list of conditions to filter the results; dimensions referenced here must also be in group_by, e.g. ['metric_time__month >= date_trunc('month', '2024-09-30'::date)']") class-attribute instance-attribute

deck_gl

DeckGL Agent for generating 3D map visualizations.

This agent generates deck.gl visualizations either: - Directly as JSON specs (when code_execution is disabled) - Via PyDeck Python code execution (when code_execution is enabled)

DeckGLAgent

Bases: BaseCodeAgent

Agent for generating DeckGL 3D map visualizations.

Supports two generation modes: - When code_execution is 'disabled': Generate DeckGL JSON specs directly - When code_execution is enabled: Generate PyDeck Python code, execute safely, convert to spec

conditions = param.List(default=['Use for 3D geographic visualizations, map-based data, or when user requests DeckGL/deck.gl', 'Use for large-scale geospatial data with latitude/longitude coordinates', 'Use for hexbin aggregations, heatmaps, or 3D extruded visualizations on maps', "Can render a geometry column (GeoDataFrame polygons/lines/points) on a basemap, suited to 3D/extruded maps or geometry whose schema is 'geographic' (a lat/lon CRS) so the basemap gives real-world context", 'NOT when the table only names places (a state or country column) without geometry or lat/lon, since deck.gl has nothing to draw and cannot fetch boundaries']) class-attribute instance-attribute
default_map_style = 'https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json' class-attribute instance-attribute
prompts = param.Dict(default={'main': {'response_model': DeckGLSpec, 'template': PROMPTS_DIR / 'DeckGLAgent' / 'main.jinja2'}, 'main_pydeck': {'response_model': PyDeckSpec, 'template': PROMPTS_DIR / 'DeckGLAgent' / 'main_pydeck.jinja2'}, 'code_safety': {'response_model': CodeSafetyCheck, 'template': PROMPTS_DIR / 'DeckGLAgent' / 'code_safety.jinja2'}, 'revise_output': {'response_model': RetrySpec, 'template': PROMPTS_DIR / 'DeckGLAgent' / 'revise_output.jinja2'}}) class-attribute instance-attribute
purpose = param.String(default='Generates DeckGL 3D map visualizations from geographic data.') class-attribute instance-attribute
user = param.String(default='DeckGL') class-attribute instance-attribute
view_type = DeckGLView class-attribute instance-attribute
respond(messages, context, step_title=None) async

Generate a DeckGL visualization.

DeckGLSpec

Bases: EscapeBaseModel

Response model for DeckGL JSON spec generation (declarative mode).

chain_of_thought = Field(description='Explain your design choices for the 3D visualization:\n - What geographic patterns does this data reveal?\n - Which layer type best represents the data (HexagonLayer, ScatterplotLayer, etc.)?\n - What visual encodings (elevation, color, radius) highlight key insights?\n Keep response to 1-2 sentences.', examples=['The data shows population density clustering around urban centers—HexagonLayer with elevation encoding count will create an intuitive 3D cityscape effect.', 'Point locations with varying magnitudes suggest ScatterplotLayer with radius encoding value and color gradient for intensity.']) class-attribute instance-attribute
json_spec = Field(description='A DeckGL JSON specification. Must include:\n - initialViewState with latitude, longitude, zoom, pitch, bearing\n - layers array with @@type for each layer\n Do NOT include \'data\' in layers - it will be injected automatically.\n Use @@= syntax for accessors, e.g. "getPosition": "@@=[longitude, latitude]"\n ') class-attribute instance-attribute

PyDeckSpec

Bases: BaseModel

Response model for PyDeck code generation.

chain_of_thought = Field(default='', description='Explain your design choices for the 3D visualization:\n - What geographic patterns does this data reveal?\n - Which layer type best represents the data?\n - What visual encodings highlight key insights?\n Keep response to 1-2 sentences.', examples=['The data shows CO2 emissions by plant location—HexagonLayer aggregating emissions with elevation shows concentration hotspots.', 'Wind turbine locations with capacity data—ScatterplotLayer with radius proportional to capacity reveals infrastructure distribution.']) class-attribute instance-attribute
code = Field(description='Python code that creates a PyDeck visualization.\n Requirements:\n - Import pydeck as `pdk`\n - Data is available as `df` (pandas DataFrame)\n - Column names have spaces replaced with underscores and non-alphanumeric chars removed\n - Must assign final deck to variable `deck`\n - Do NOT call .to_html(), .show(), or any I/O methods\n ```\n ') class-attribute instance-attribute

document_list

DocumentListAgent

Bases: BaseListAgent

The DocumentListAgent lists all available documents provided by the user.

conditions = param.List(default=['Use when user asks to list or see all available documents', 'NOT when user asks about specific document content']) class-attribute instance-attribute
input_schema = DocumentListInputs class-attribute instance-attribute
purpose = param.String(default='\n Displays a list of all available documents.') class-attribute instance-attribute
user = param.String(default='Documents') class-attribute instance-attribute
applies(context) async classmethod

DocumentListInputs

Bases: ContextModel

metaset instance-attribute

document_summarizer

DocumentSummarizerAgent

Bases: BaseLumenAgent

Synthesizes relevant document context into a user-focused summary.

conditions = param.List(default=['Use when user asks questions answerable from documentation or uploaded metadata files', 'Use for summarization of documentation', 'NOT when user only asks to list available documents']) class-attribute instance-attribute
input_schema = DocumentSummarizerInputs class-attribute instance-attribute
not_with = param.List(default=['DbtslAgent', 'SQLAgent']) class-attribute instance-attribute
output_schema = DocumentSummarizerOutputs class-attribute instance-attribute
prompts = param.Dict(default={'main': {'template': PROMPTS_DIR / 'DocumentSummarizerAgent' / 'main.jinja2'}}) class-attribute instance-attribute
purpose = param.String(default='\n Synthesizes relevant documents into an answer focused on the user query.\n Also renders a document editor with tabbed previews of matched files.') class-attribute instance-attribute
user = param.String(default='Summarizer') class-attribute instance-attribute
applies(context) async classmethod
respond(messages, context, step_title=None) async

DocumentSummarizerInputs

Bases: ContextModel

metaset instance-attribute

DocumentSummarizerOutputs

Bases: ContextModel

document_summary instance-attribute
view instance-attribute

hvplot

hvPlotAgent

Bases: BaseViewAgent

conditions = param.List(default=['Use for exploratory data analysis, interactive plots, and dynamic filtering', 'Use for quick, iterative data visualization during analysis', "Can render a geometry column (GeoDataFrame polygons/lines/points) as a 2D choropleth, shading shapes by a value column without a basemap; fits geometry whose schema is not 'geographic' (a projected or absent CRS) or when a basemap adds no context"]) class-attribute instance-attribute
prompts = param.Dict(default={'main': {'template': PROMPTS_DIR / 'hvPlotAgent' / 'main.jinja2'}}) class-attribute instance-attribute
purpose = param.String(default='Generates a plot of the data given a user prompt.') class-attribute instance-attribute
user = param.String(default='hvPlot') class-attribute instance-attribute
view_type = hvPlotView class-attribute instance-attribute

source

SourceAgent — fetches external data via source control actions.

Modeled on :class:SQLAgent: given the :class:SourceLookup context (which actions are relevant), constructs :class:FunctionTool instances from the callables and passes them to the LLM. The LLM fills in parameters and calls the function. Results are normalized to DataFrames and registered as new :class:DuckDBSource entries in context.

DATA_SUMMARY_MAX_TOKENS = 1200 module-attribute

SourceAgent

Bases: Agent

Fetches data from external APIs, Python callables, or URL templates configured via source controls.

The agent reads available actions from SourceLookup context, wraps the relevant callables as FunctionTool instances, and passes them to the LLM. The LLM decides which action to call and fills in parameter values. Results are normalized to DataFrames and registered as DuckDBSource entries for downstream agents (e.g. SQLAgent, hvPlotAgent) to consume.

conditions = param.List(default=['Use when no existing data source can answer the query and external source controls are configured', 'Use when the user asks for data from an external API or service', 'Use when the user provides a URL to read, fetch, discuss, or extract content from — the content must be fetched before any other agent can address it', "Use when the loaded data does not cover the requested scope (e.g. user asks for 'whole month' but data summary only shows a week)", 'Prefer over metadata discovery when no data sources are loaded but source controls exist', 'This agent fetches and registers data or indexes page content as documents — always follow with a presentation, query, or chat step', 'For HTML pages, this agent extracts both tabular data and page text (indexed as a searchable document for ChatAgent)', 'Output is queryable tabular data and/or indexed documents — enables filtering, aggregation, joins, transformation, and conversation by downstream agents', "NOT for listing configured external source actions or answering 'what data is available' before a concrete dataset request", 'NOT when the loaded data already covers the requested scope and user just wants to query/visualize it']) class-attribute instance-attribute
input_schema = SourceInputs class-attribute instance-attribute
output_schema = SourceOutputs class-attribute instance-attribute
prompts = param.Dict(default={'main': {'template': PROMPTS_DIR / 'SourceAgent' / 'main.jinja2'}}) class-attribute instance-attribute
purpose = param.String(default='\n Fetches data from configured external sources (APIs, Python callables,\n URL templates). Discovers relevant endpoints, fills parameters via\n LLM, executes the call, and registers the resulting DataFrame for\n downstream querying and visualization. For HTML pages, also indexes\n page text as a searchable document for conversational agents.') class-attribute instance-attribute
user = param.String(default='Source') class-attribute instance-attribute
applies(context) async classmethod
respond(messages, context, step_title=None) async

SourceInputs

Bases: ContextModel

source_actions instance-attribute
source_controls instance-attribute

SourceOutputs

Bases: ContextModel

data instance-attribute
metaset instance-attribute
pipeline instance-attribute
source instance-attribute
table instance-attribute

sql

DISTINCT_VALUES_LIMIT = 50 module-attribute

EMPTY_RESULT_HINT = 'The query returned no rows. If the filters match the question, no rows is a valid result and the same query may be returned unchanged; otherwise verify the filter values.' module-attribute

EXPLORATION_MAX_ROWS = 1000 module-attribute

EXPLORATION_MAX_TOKENS = 1200 module-attribute

EXPLORATION_PREVIEW_COLS = 25 module-attribute

EXPLORATION_PREVIEW_ROWS = 5 module-attribute

MAIN_TOOL_ROUNDS = 6 module-attribute

NUMERIC_CAST_EDITS = {'duckdb': '`TRY_CAST("col" AS DOUBLE)` where numbers are stored as text', 'snowflake': '`TRY_CAST("col" AS DOUBLE)` where numbers are stored as text', 'tsql': '`TRY_CAST("col" AS FLOAT)` where numbers are stored as text', 'bigquery': '`SAFE_CAST(col AS FLOAT64)` where numbers are stored as text', 'sqlite': '`CAST("col" AS REAL)` where numbers are stored as text, only when every value is numeric (SQLite turns other text into 0)', 'postgres': '`CAST(NULLIF(TRIM("col"), \'\') AS DOUBLE PRECISION)` where numbers are stored as text, only when every non-blank value is numeric (the cast fails otherwise)'} module-attribute

PROMPT_OTHER_TABLES = 25 module-attribute

REVISE_TOOL_ROUNDS = 3 module-attribute

ROW_GENERATING_FUNCTIONS = (sqlglot.exp.Unnest, sqlglot.exp.Explode) module-attribute

SCHEMA_MAX_TOKENS = 3000 module-attribute

SOURCE_PROFILE_MAX_TABLES = 3 module-attribute

SUBMIT_SQL_DESCRIPTION = "Submit the final SQL query as your answer. The system executes it: if it fails, the error comes back as this tool's result so you can fix the query and submit again; if it succeeds, you are done. Call this instead of replying with text." module-attribute

VALIDATION_MAX_ROWS = PROFILE_SAMPLE_ROWS module-attribute

RequestBudget

Caps the LLM calls one SQL request spends across all of its attempts.

calls property
max_calls = max_calls instance-attribute
check()
last_error()

SQLAgent

Bases: BaseLumenAgent

clean_data = param.Boolean(default=True, doc='\n Profile a bounded sample of each query result and, when it contains data-quality problems,\n spend one extra LLM call rewriting the query to clean them up.\n Profiling is deterministic and reuses the frame validation already\n fetched, so a clean result costs nothing; set to False to always take\n the query exactly as first written.') class-attribute instance-attribute
conditions = param.List(default=['Use for querying, filtering, aggregating, or transforming data with SQL', "Use for calculations that require executing SQL (e.g., 'calculate average', 'sum by category')", "Use when user asks to 'show', 'get', 'fetch', 'query', 'find', 'filter', 'calculate', 'aggregate', or 'transform' data", 'Use after external data has been fetched and the user expects the fetched fields/rows to be presented, selected, filtered, or otherwise queried', 'Use when user asks about data quality, completeness, missing values, duplicates, or outliers', "NOT when user asks to 'explain', 'interpret', 'analyze', 'summarize', or 'comment on' existing data", 'NOT useful if the user is using the same data for plotting']) class-attribute instance-attribute
exclusions = param.List(default=['dbtsl_metaset']) class-attribute instance-attribute
input_schema = SQLInputs class-attribute instance-attribute
llm_tools = param.List(default=[make_apply_filter_llm_tool]) class-attribute instance-attribute
max_request_llm_calls = param.Integer(default=40, bounds=(1, None), doc='\n Maximum LLM calls one SQL request may spend across all attempts, tool\n rounds and revisions. Checked before each attempt.') class-attribute instance-attribute
not_with = param.List(default=['DbtslAgent', 'MetadataLookup', 'TableListAgent']) class-attribute instance-attribute
output_schema = SQLOutputs class-attribute instance-attribute
prompts = param.Dict(default={'main': {'response_model': make_sql_model, 'template': PROMPTS_DIR / 'SQLAgent' / 'main.jinja2'}, 'clean_data': {'response_model': SQLCleanup, 'template': PROMPTS_DIR / 'SQLAgent' / 'clean_data.jinja2'}, 'revise_output': {'response_model': RetrySpec, 'template': PROMPTS_DIR / 'SQLAgent' / 'revise_output.jinja2'}}) class-attribute instance-attribute
purpose = param.String(default='\n Creates and executes SQL queries to retrieve, filter, aggregate, or transform data.\n Handles table joins, WHERE clauses, GROUP BY, calculations, and other SQL operations.\n Generates new data pipelines from SQL transformations.\n Profiles every result for data-quality problems (missing values, duplicates,\n placeholder numbers, outliers) and reports them.') class-attribute instance-attribute
query_timeout = param.Number(default=60, bounds=(0, None), allow_None=True, doc="\n Seconds a query run on the model's behalf may take before it is\n abandoned, or None to wait indefinitely. A timed out query is\n revised once; a second timeout ends the request.") class-attribute instance-attribute
request_timeout = param.Number(default=300, bounds=(0, None), allow_None=True, doc='\n Wall-clock limit in seconds for one SQL request across all attempts.\n None disables the limit.') class-attribute instance-attribute
schema_max_tokens = param.Integer(default=4000, allow_None=True, doc='\n Token budget for the data summary in the prompt. Tables beyond it are\n listed with names and types only, and the model loads the rest with\n load_table_schemas.') class-attribute instance-attribute
schema_tables_shown = param.Integer(default=25, bounds=(1, None), doc='\n Most relevant tables described in the data summary; the rest are\n listed by name. schema_max_tokens usually binds first.') class-attribute instance-attribute
stats_wait = param.Number(default=10, allow_None=True, doc='\n Seconds to wait for column statistics that are still being computed\n before answering with names and types; profiling continues in the\n background for later questions. None waits until they are ready.') class-attribute instance-attribute
user = param.String(default='SQL') class-attribute instance-attribute
respond(messages, context, step_title=None) async

Generate SQL in one step (catalog browse, schema load, optional exploration, then structured query).

revise(instruction, messages, context, view=None, spec=None, language=None, errors=None, max_retries=2, **kwargs) async

SQLCleanup

Bases: BaseModel

A revision of an existing query that removes data-quality problems from its result.

chain_of_thought = Field(description='\n Which findings you are fixing and which you are leaving alone, in one sentence.') class-attribute instance-attribute
query = Field(description='\n The rewritten SQL query. Return the original query byte-for-byte unchanged\n when no finding clearly calls for one of the allowed edits; leaving the\n query alone is a correct and expected answer.') class-attribute instance-attribute

SQLInputs

Bases: ContextModel

data instance-attribute
metaset instance-attribute
source instance-attribute
sources instance-attribute
sql instance-attribute
visible_slugs instance-attribute

SQLOutputs

Bases: ContextModel

data instance-attribute
pipeline instance-attribute
sql instance-attribute
table instance-attribute

SQLQuery

Bases: BaseModel

A single SQL query with its associated metadata.

query = Field(description="\n One, correct, valid SQL query that answers the user's question;\n should only be one query and do NOT add extraneous comments; no multiple semicolons.") class-attribute instance-attribute
table_slug = Field(description='\n A short, unique, descriptive snake_case slug (3-5 words max) describing\n WHAT the resulting data contains. Do NOT include the source table name\n in the slug — provenance is tracked separately.\n Follow the naming style of existing derived tables (shown in the data\n summary with `derived_from`/`step` annotations).\n Examples: avg_sst_by_season, top_5_athletes_2020, revenue_by_region.\n Ensure the slug does not duplicate any existing table names or slugs.\n ') class-attribute instance-attribute

build_distinct_values_sql(table_expr, column, dialect, like=None, limit=DISTINCT_VALUES_LIMIT)

Top limit + 1 values of column by frequency; the extra row reveals truncation.

catalog_exceeds_prompt(metaset, tables_shown)

Whether some tables are missing from the data summary, so browsing the catalog can find more.

execute_distinct_values(table, column, like=None, *, sources, metaset=None, limit=DISTINCT_VALUES_LIMIT, timeout=None) async

execute_exploration_sql(source, sql_query, *, sources, timeout=None) async

Run a read-only SQL statement on the named Lumen source and return a text preview of results.

Intended for LLM tool calls: the model supplies the logical source name and sql_query; the sources map must be provided by the caller (e.g. closed over when building the tool).

Parameters:

Name Type Description Default
source str | None

Name of the data source (key prefix before the source/table separator in slugs). May be omitted when there is a single source.

required
sql_query str

SQL to execute (SELECT or WITH only).

required
sources dict[tuple[str, str], Source]

Mapping from (source_name, table_name) to :class:~lumen.sources.base.Source instances.

required
timeout float

Seconds after which the query is abandoned and reported as an error.

None

Returns:

Type Description
str

Tabular preview, or an error message string if execution fails.

format_exploration_result(df, *, capped=False)

Render an exploration query result as a compact preview for the LLM.

Reports the fetched row count (or a lower bound if capped), column dtypes, and a few example rows. Markdown omits index and alignment padding.

Only the handful of cells actually rendered is converted to pandas, so a polars or pyarrow result reaches the model without the whole frame being copied. The dtypes reported are the preview's for that reason, which is also what keeps a pandas caller's output exactly as it was.

make_apply_filter_llm_tool(context)

Expose :func:make_apply_filter_tool as a context-gated llm_tools entry.

Returns the tool only when a pipeline is present in working memory, so it is offered to the LLM exactly when there is an existing exploration to filter.

make_apply_filter_tool(pipeline)

Build a :class:~lumen.ai.tools.FunctionTool that filters pipeline.

The tool lets the chat agent narrow the data already loaded in the current exploration -- subsetting an xarray coordinate dimension (time/lat/ lon/level) or a tabular column. It adds a :class:~lumen.filters.base.Filter to the pipeline (the same machinery as the manual "Add Filter" UI), which subsets the data without modifying the SQL query.

Known limitation: this tool is registered on :class:SQLAgent, whose response also emits a SQL query. A single "filter" request can therefore both apply this in-place pipeline filter to the current exploration and open a new exploration whose SQL carries an equivalent WHERE clause. Giving the tool a dedicated, non-SQL-emitting home is tracked as follow-up work.

make_browse_data_catalog_tool(metaset)

Tool wrapping :meth:~lumen.ai.schemas.Metaset.table_list for incremental catalog browsing.

make_distinct_values_tool(sources, metaset=None, timeout=None)

Tool that lists the most frequent values of one column, optionally filtered by a pattern.

A focused alternative to free-form exploration for the most common reason to explore: finding the exact stored spelling of a literal before filtering on it.

make_load_table_schemas_tool(metaset, stats_timeout=60)

Tool returning full column statistics for specific catalog tables, computing them if the prompt only had names and types.

make_run_exploration_sql_tool(sources, timeout=None)

Build a :class:~lumen.ai.tools.FunctionTool that runs :func:execute_exploration_sql for sources.

make_source_table_model(sources)

make_sql_model(sources)

Create a SQL query model with source/table validation.

Parameters:

Name Type Description Default
sources list[tuple[str, str]]

List of (source_name, table_name) tuples for tables with full schemas.

required

make_table_model(sources)

Create a table model with constrained table choices.

numeric_cast_edit(dialect)

referenced_columns(sql_query, dialect=None)

Lower-cased names of the columns sql_query references.

None when every column is referenced (a * projection) or the query cannot be parsed, so callers fall back to considering all columns.

resolve_exploration_source(source, sources)

Resolve the source argument of an exploration tool call.

Weak models often pass a table name, a generic source/<table> or a differently cased name instead of the source name, so any reference that identifies one source unambiguously is accepted.

resolve_source_table(reference, sources)

Resolve a model-supplied table reference to its source and table name (see :func:resolve_table_slug).

sql_contains_aggregates(sql_query, dialect=None)

Whether sql_query collapses many input rows into fewer output rows.

Such a query has already collapsed its inputs by the time it returns, so nothing about the result shows what those inputs carried: a -9999 placeholder is already inside SUM, and a blank string is already a GROUP BY key. Profiling the result therefore says nothing about the data the numbers came from, and the source rows have to be profiled separately -- otherwise a chart of SUM(revenue) silently inherits every placeholder in the table it was built from.

Unparseable SQL returns False: the caller only uses this to decide whether to spend an extra query, and guessing "yes" would spend it on every failed parse.

sql_is_scalar_aggregate(sql_query, dialect=None)

Whether the outermost SELECT collapses everything into a single row.

True for SELECT COUNT(*) FROM t or SELECT MAX(x) / MIN(x) FROM t: aggregates in every projection and no GROUP BY. Unparseable SQL returns False.

Aggregates inside a subquery or wrapped in a row-generating function do not count: SELECT (SELECT MAX(b.v) FROM b WHERE b.id = a.id) FROM a and SELECT UNNEST(LIST(x)) FROM t return many rows.

summarize_tool_calls(events, max_calls=10, max_chars=400)

Condense an attempt's tool calls into what a retry needs to know.

A retry that sees what was already looked up can go straight to fixing the query instead of repeating the discovery that preceded the failure.

validate_read_only_sql(sql_query, dialect)

story

ProseEdit

Bases: BaseModel

chain_of_thought = Field(description='In one sentence, how the paragraph should change to satisfy the instruction.') class-attribute instance-attribute
prose = Field(description='The rewritten paragraph in Markdown, keeping any facts that are still accurate.') class-attribute instance-attribute

Story

Bases: BaseModel

blocks = Field(description='The report as a flowing blog post: alternating prose blocks and chart/table blocks, in reading order.') class-attribute instance-attribute
chain_of_thought = Field(description='In 1-2 sentences, outline the narrative arc that ties the charts and tables together.') class-attribute instance-attribute
title = Field(description='A short, descriptive title for the report story.') class-attribute instance-attribute

StoryAgent

Bases: LLMUser

Writes a "story" that reads like a blog post: short prose paragraphs interleaved with the charts and tables a user selected for export.

prompts = param.Dict(default={'main': {'template': PROMPTS_DIR / 'StoryAgent' / 'main.jinja2', 'response_model': Story}, 'rewrite': {'template': PROMPTS_DIR / 'StoryAgent' / 'rewrite.jinja2', 'response_model': ProseEdit}}) class-attribute instance-attribute
rewrite_prose(prose, instruction, catalog='') async

Rewrite a single paragraph of the story following the user's instruction.

write_story(catalog, guidance='', title='') async

Write the blog-post story that weaves prose around the catalogued views.

StoryBlock

Bases: BaseModel

prose = Field(default='', description='A narrative paragraph in Markdown. Leave empty when this block places a chart or table instead.') class-attribute instance-attribute
view = Field(default=None, description='The number of the chart or table to place here, taken from the catalog. Leave null for a prose block.') class-attribute instance-attribute

StoryState

Bases: Parameterized

The report's generated story: its versions and which one is shown.

blocks = param.List(default=[], doc='\n The (kind, value) blocks of the story currently shown.') class-attribute instance-attribute
outline = param.List(default=[], doc='\n The arranged section and heading order applied to the export.') class-attribute instance-attribute
title = param.String(default='', doc='\n The title of the story currently shown.') class-attribute instance-attribute
version = param.Integer(default=0, doc='\n Index of the version currently shown.') class-attribute instance-attribute
versions = param.List(default=[], doc='\n Every generated story, kept so regenerating never discards manual edits.') class-attribute instance-attribute

build_catalog(views) async

Number each chart/table so the story can reference them by their catalog number.

Deliberately omits the view's title: it is the pipeline step name (e.g. "Prepare data", "Create pie chart"), which would leak into the story. The data summary describes what each figure actually shows instead.

table_list

TableListAgent

Bases: BaseListAgent

The TableListAgent lists all available data and lets the user pick one.

conditions = param.List(default=["Use when user explicitly asks to 'list data', 'show available data', or 'what data do you have'", 'NOT for showing actual data contents, querying, or analyzing data', 'NOT for describing data sources or telling what the data is about']) class-attribute instance-attribute
input_schema = TableListInputs class-attribute instance-attribute
not_with = param.List(default=['DbtslAgent', 'SQLAgent']) class-attribute instance-attribute
purpose = param.String(default='\n Displays a list of all available data & datasets. Not useful for identifying which dataset to use for analysis.') class-attribute instance-attribute
user = param.String(default='Tables') class-attribute instance-attribute
applies(context) async classmethod

TableListInputs

Bases: ContextModel

metaset instance-attribute
source instance-attribute
visible_slugs instance-attribute

validation

QueryCompletionValidation

Bases: BaseModel

Validation of whether the executed plan answered the user's query

chain_of_thought = Field(description='Restate intent and results succinctly; then explain your reasoning as to why you will be answering yes or no.') class-attribute instance-attribute
correct = Field(description='True if query correctly solves user request, otherwise False.') class-attribute instance-attribute
missing_elements = Field(default_factory=list, description="List of specific elements from the user's query that weren't addressed") class-attribute instance-attribute
suggestions = Field(default_factory=list, description='Suggestions for additional steps that could complete the query if not fully answered') class-attribute instance-attribute

ValidationAgent

Bases: Agent

ValidationAgent focuses solely on validating whether the executed plan fully answered the user's original query. It identifies missing elements and suggests next steps when validation fails.

conditions = param.List(default=['Use to validate whether executed plans fully answered user queries', 'Use to identify missing elements from the original user request', 'NOT for data analysis, pattern identification, or technical programming questions']) class-attribute instance-attribute
input_schema = ValidationInputs class-attribute instance-attribute
output_schema = ValidationOutputs class-attribute instance-attribute
prompts = param.Dict(default={'main': {'template': PROMPTS_DIR / 'ValidationAgent' / 'main.jinja2', 'response_model': QueryCompletionValidation, 'tools': []}}) class-attribute instance-attribute
purpose = param.String(default="\n Validates whether executed plans fully answered the user's original query.\n Identifies missing elements, assesses completeness, and suggests next steps\n when validation fails. Acts as a quality gate for plan execution.") class-attribute instance-attribute
user = param.String(default='Validation') class-attribute instance-attribute
respond(messages, context, step_title=None) async

ValidationInputs

Bases: ContextModel

chat instance-attribute
data instance-attribute
listing instance-attribute
sql instance-attribute
view instance-attribute

ValidationOutputs

Bases: ContextModel

validation_result instance-attribute

vega_lite

AltairChartSpec

Bases: BaseModel

A single Altair chart within a (potentially multi-chart) response.

code = Field(description="Python code that creates an Altair chart.\n Requirements:\n - Import altair as `alt`\n - Data is available as `df` (pandas DataFrame)\n - Must assign final chart to variable `chart`\n - Do NOT call .to_dict(), .save(), .display() or any I/O methods\n - Use 'container' for width to make charts responsive\n ") class-attribute instance-attribute
title = Field(default='', description='Short, human-readable label for this chart, used as its tab or section heading when several charts are returned.') class-attribute instance-attribute

AltairSpec

Bases: BaseModel

Response model for Altair code generation.

chain_of_thought = Field(default='', description="Explain your design choices based on visualization theory:\n - What story does this data tell?\n - What's the most compelling insight or trend (for the title)?\n - Which visual encodings (position, color, size) best reveal patterns?\n - When several charts are warranted, why each one earns its place.\n Keep response to 1-2 sentences.", examples=['The data reveals US dominance in Winter Olympic hosting—a horizontal bar chart sorted descending makes comparison immediate, with the leader highlighted in a distinct color.', 'This time series shows a 40% revenue spike in Q3 2024—a line chart with point markers reveals the trend clearly.']) class-attribute instance-attribute
charts = Field(min_length=1, description='One or more chart specifications. Return several charts only when the request spans distinct metrics or relationships that should not share a single plot; otherwise return exactly one.') class-attribute instance-attribute

ChartSpec

Bases: BaseModel

A single Vega-Lite chart within a (potentially multi-chart) response.

title = Field(default='', description='Short, human-readable label for this chart, used as its tab or section heading when several charts are returned.') class-attribute instance-attribute
yaml_spec = Field(description='A basic vega-lite YAML specification with core plot elements only (mark, basic x/y encoding). Skip $schema and data fields.') class-attribute instance-attribute

VegaLiteAgent

Bases: BaseCodeAgent

conditions = param.List(default=['Use for publication-ready visualizations or when user specifically requests Vega-Lite charts', 'Use for polished charts intended for presentation or sharing', 'Use for a choropleth whose table names places (states, countries) but holds no geometry or coordinates, since the boundaries can be looked up and joined by name']) class-attribute instance-attribute
doc_pages_per_slug = param.Integer(default=2, bounds=(1, None), doc='\n Maximum number of retrieved sections allowed from any single\n documentation page. Without a cap, similarity collapses onto one page:\n a "bar chart" query returns five near-identical mark-bar variants while\n the transform and encoding pages that would broaden the answer sit just\n below the cut.') class-attribute instance-attribute
exclude_spec_constructs = param.List(default=['facet', 'repeat', 'concat', 'hconcat', 'vconcat', 'row', 'column'], item_type=str, doc='\n Vega-Lite composition keys that disqualify a retrieved example spec.\n The declarative prompt tells the model not to pack several plots into one\n entry with these, so injecting a reference spec that uses them puts the\n documentation at odds with the instructions. Set to [] to keep every\n retrieved spec -- appropriate for the Altair path, whose prompt permits\n composition within a single chart entry.') class-attribute instance-attribute
n_doc_pages = param.Integer(default=5, bounds=(0, None), doc='\n Number of documentation sections to inject into the prompt.\n Set to 0 to disable documentation lookup entirely.') class-attribute instance-attribute
prompts = param.Dict(default={'main': {'response_model': VegaLiteSpec, 'template': PROMPTS_DIR / 'VegaLiteAgent' / 'main.jinja2'}, 'main_altair': {'response_model': AltairSpec, 'template': PROMPTS_DIR / 'VegaLiteAgent' / 'main_altair.jinja2'}, 'code_safety': {'response_model': CodeSafetyCheck, 'template': PROMPTS_DIR / 'VegaLiteAgent' / 'code_safety.jinja2'}, 'interaction_polish': {'response_model': VegaLiteSpecUpdate, 'template': PROMPTS_DIR / 'VegaLiteAgent' / 'interaction_polish.jinja2'}, 'annotate_plot': {'response_model': VegaLiteSpecUpdate, 'template': PROMPTS_DIR / 'VegaLiteAgent' / 'annotate_plot.jinja2'}, 'revise_output': {'response_model': RetrySpec, 'template': PROMPTS_DIR / 'VegaLiteAgent' / 'revise_output.jinja2'}}) class-attribute instance-attribute
purpose = param.String(default='Generates one or more vega-lite plot specifications from the input data pipeline.') class-attribute instance-attribute
reserve_base_form = param.Boolean(default=True, doc='\n Guarantee one retrieved section is the base form of a chart type, when\n the candidate pool contains one. A bare request like "plot a bar chart"\n matches every bar variant about equally -- similarity across the top\n candidates spans less than the noise floor -- so the plain section loses\n to specialisations that simply have more text to match on. Sections are\n flagged deterministically at build time from their title, which is why\n this reserves a slot rather than nudging a score.') class-attribute instance-attribute
user = param.String(default='Vega') class-attribute instance-attribute
vector_store_path = param.Path(default=None, check_exists=False, doc='\n Path to a custom vector store for storing and retrieving Vega-Lite examples;\n if not provided a default store will be used depending on the LLM--\n OpenAIEmbeddings for OpenAI LLM or NumpyEmbeddings for all others.') class-attribute instance-attribute
view_type = VegaLiteView class-attribute instance-attribute
annotate(instruction, messages, context, spec, view=None) async

Apply annotations based on user request.

Parameters:

Name Type Description Default
instruction str

User's description of what to annotate

required
messages list[Message]

Chat history for context

required
context TContext

Session context

required
spec dict

The current VegaLite specification (full dict with 'spec' key)

required
view VegaLiteEditor | None

Optional VegaLite editor to export plot image from for vision analysis.

None

Returns:

Type Description
str

Updated specification with annotations

respond(messages, context, step_title=None) async

Generates one or more VegaLite visualizations using a progressive building approach with real-time updates.

revise(feedback, messages, context, view=None, spec=None, language=None, errors=None, **kwargs) async

Revise a VegaLite specification based on user feedback.

This override adds: 1. Doc examples from vector store 2. Plot image for vision analysis (if view is a VegaLiteEditor)

Parameters:

Name Type Description Default
feedback str

User's feedback or instruction for revision.

required
messages list[Message]

Chat message history.

required
context TContext

Session context.

required
view LumenEditor | None

The editor containing the current spec to revise.

None
spec str | None

The spec string (used if view is None).

None
language str | None

The spec language (used if view is None).

None
errors list[str] | None

List of errors to include in context.

None
**kwargs

Additional arguments passed to parent revise.

{}

Returns:

Type Description
str

The revised YAML specification.

VegaLiteSpec

Bases: EscapeBaseModel

chain_of_thought = Field(description="Explain your design choices based on visualization theory:\n - What story does this data tell?\n - What's the most compelling insight or trend (for the title)?\n - What additional context adds value without repeating the title (for the subtitle)?\n - Which visual encodings (position, color, size) best reveal patterns?\n - Should color highlight specific insights or remain neutral?\n - When several charts are warranted, why each one earns its place.\n Keep response to 1-2 sentences.", examples=["The data reveals US dominance in Winter Olympic hosting (4 times vs France's 3)—title should emphasize this leadership. Position encoding via horizontal bars sorted descending makes comparison immediate, neutral blue keeps focus on counts rather than categories, and the subtitle can note the 23-country spread to add context without redundancy.", "This time series shows a 40% revenue spike in Q3 2024—the key trend for the title. A line chart with position encoding (time→x, revenue→y) reveals the pattern, endpoint labels eliminate need for constant grid reference making it cleaner, and color remains neutral since there's one series; the subtitle should explain what drove the spike (e.g., 'Three offshore projects') to add insight."]) class-attribute instance-attribute
charts = Field(min_length=1, description='One or more chart specifications. Return several charts only when the request spans distinct metrics or relationships that should not share a single plot; otherwise return exactly one.') class-attribute instance-attribute

VegaLiteSpecUpdate

Bases: BaseModel

chain_of_thought = Field(description="Explain what changes you're making to the Vega-Lite spec and why. Keep to 1-2 sentences.", examples=['Adding tooltips to show exact values on hover for better interactivity.', 'Swapping x and y axes to create horizontal bars as requested.']) class-attribute instance-attribute
yaml_update = Field(description="Partial YAML with ONLY modified properties (unchanged values omitted).\n Respect your step's scope; don't override previous steps.") class-attribute instance-attribute