Skip to content

fix: build hvPlotAgent response model explicitly and add colormap options - #1935

Draft
ghostiee-11 wants to merge 4 commits into
holoviz:mainfrom
ghostiee-11:fix/hvplot-agent-model-and-colors
Draft

fix: build hvPlotAgent response model explicitly and add colormap options#1935
ghostiee-11 wants to merge 4 commits into
holoviz:mainfrom
ghostiee-11:fix/hvplot-agent-model-and-colors

Conversation

@ghostiee-11

Copy link
Copy Markdown
Collaborator

hvPlotAgent could not build its structured-output model at all: param_to_pydantic walks parent classes and every subclass transitively, so from hvPlotUIView it reached Panel's whole class graph and failed differently on each run. _get_model is called on every response, so the agent raised rather than degraded.

This builds the model with create_model instead, listing fields explicitly and deriving kind from the view's own Selector. Since the model was being rebuilt anyway it also adds cmap, cnorm, colorbar and color, with the colormap offered as linear/diverging/categorical/cyclic and resolved to a concrete name before it reaches the view.

Fixes #1931

@codecov

codecov Bot commented Jul 20, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 71.47%. Comparing base (70b0dfd) to head (6e0245f).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1935      +/-   ##
==========================================
+ Coverage   71.37%   71.47%   +0.09%     
==========================================
  Files         199      199              
  Lines       34686    34755      +69     
==========================================
+ Hits        24758    24841      +83     
+ Misses       9928     9914      -14     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment thread lumen/ai/agents/hvplot.py Outdated
Comment thread lumen/ai/agents/hvplot.py
Comment on lines +32 to +117
class hvPlotSpec(BaseModel):
"""Fields that do not depend on the data schema."""

chain_of_thought: str = Field(
description="Your thought process behind the plot."
)

cmap: Literal["linear", "diverging", "categorical", "cyclic"] | None = Field(
default=None,
description=(
"Colormap to use when a column is mapped to colour. Pick 'diverging' "
"for values around a meaningful centre such as anomalies or deviations, "
"'categorical' for unordered categories, 'cyclic' for wrapping values "
"such as wind direction or hour of day, and 'linear' otherwise."
),
)

cnorm: Literal["linear", "log", "eq_hist"] | None = Field(
default=None,
description="Colour scale normalization. Use 'log' for values spanning orders of magnitude.",
)

colorbar: bool | None = Field(
default=None,
description="Whether to show a colorbar. Leave unset to let hvPlot decide.",
)

geo: bool = Field(
default=False,
description="Whether the data is geographic and should be plotted on a map.",
)

limit: int | None = Field(
default=None,
ge=0,
description="Maximum number of rows to plot. Leave unset to plot all rows.",
)

title: str | None = Field(
default=None,
description="Title describing what the plot shows.",
)


def make_hvplot_model(view_type: type[hvPlotUIView], schema: dict[str, Any]) -> type[BaseModel]:
"""
Build the structured-output model for a given view type and data schema.

The column fields are restricted to the columns actually present so the
model cannot reference a column that does not exist, and `kind` is derived
from the view's own Selector so the two cannot drift apart.
"""
kinds = tuple(view_type.param["kind"].objects)
columns = tuple(schema)
# Literal[()] is not a valid annotation, so fall back to a plain string
# when the schema is empty.
column = Literal[columns] if columns else str

return create_model(
"hvPlotSpecWithColumns",
by=(
list[column] | None,
Field(default=None, description="Columns to split into separate series."),
),
color=(
column | None,
Field(default=None, description="Column to map onto colour."),
),
groupby=(
list[column] | None,
Field(default=None, description="Columns to group by into widgets."),
),
kind=(
Literal[kinds],
Field(description="The kind of plot to generate."),
),
x=(
column | None,
Field(default=None, description="Column to plot on the x-axis."),
),
y=(
column | None,
Field(default=None, description="Column to plot on the y-axis."),
),
__base__=hvPlotSpec,
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a better way? Rather than duplicating the args, can we read the signature from https://github.com/holoviz/hvplot/blob/451910e7a17ba9e9ecd4115b30d529cc2cb9dfbb/hvplot/converter.py#L866

It's missing typing at this moment, but maybe could add typing?

@ahuang11
ahuang11 marked this pull request as draft July 21, 2026 22:45
@ghostiee-11
ghostiee-11 force-pushed the fix/hvplot-agent-model-and-colors branch from 55eb30a to 7b8a9c1 Compare July 22, 2026 05:00
…ions

param_to_pydantic walks parent classes and every subclass transitively, so
starting from hvPlotUIView it reached panel.viewable.Viewer and from there
the whole Panel class graph. Since the subclass set is iterated in id-hash
order the conversion failed differently on each run, with NotImplementedError,
PydanticSchemaGenerationError or RecursionError. _get_model is called on every
response, so the agent raised rather than degraded.

Build the model with create_model instead, listing the fields explicitly and
deriving kind from the view's own Selector so the two cannot drift. The
reasoning field is now dropped from the spec, which previously reached the
view and raised in the explorer.

While the model was being rebuilt, add cmap, cnorm, colorbar and color. The
colormap is offered as linear/diverging/categorical/cyclic, which is easier
to choose from than the full list of colormaps, and resolved to a concrete
name before it reaches the view because hvplot.ui.Colormapping.cmap is a
Selector that rejects the semantic names. Colouring by a non-numeric column
also picks a named categorical colormap, since hvPlot would otherwise default
to a list of colours that the same Selector rejects.

Fixes holoviz#1931
Each of these fails if its guard is removed: the semantic colormap has to be
resolved to a concrete name, the reasoning field has to be dropped before the
spec reaches the view, colouring by a non-numeric column needs a named
colormap, and the large-data branch must not overwrite a normalization the
model asked for.
Moves the hvplot.ui import up top and takes the cmap kinds and cnorm options
from DEFAULT_CMAPS and Colormapping.cnorm, so they cannot drift from hvPlot.

The rest of the fields stay written out: HoloViewsConverter.__init__ has 77
arguments and no type annotations, so its signature cannot type a structured
output model, and only a small subset is worth putting in front of an LLM.
@ghostiee-11
ghostiee-11 force-pushed the fix/hvplot-agent-model-and-colors branch from 41acca4 to 6e0245f Compare July 22, 2026 05:05
@ghostiee-11
ghostiee-11 marked this pull request as ready for review July 22, 2026 07:19
@ahuang11

Copy link
Copy Markdown
Contributor

Do you have a solution for #1935 (comment)

@ahuang11
ahuang11 marked this pull request as draft July 22, 2026 23:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

hvPlotAgent cannot express any color options, so hvPlot's colormap defaults are unreachable

2 participants