Sales analyst¶
Stage: the web app, langstage. Shows: a persistent Python session, Plotly
charts in the chat, and a written report built on the Canvas.
Ask a question about your data, get the answer with the work shown. This example is the analyst's loop: load the data, slice it until the change is explained, chart what matters, and write it up somewhere a colleague can read it.
What happens¶
- With the Canvas open, you ask: "What drove Q3 revenue, and is there anything I should worry about? Put the findings on the canvas."
- The agent loads
sales.csv, a year of weekly sales, in a Python session that persists between calls, and compares Q3 with Q2 by category, channel and region. - It charts what it finds with Plotly. Each chart appears in the chat as it's made, and goes on the Canvas with a short paragraph explaining it.
- The answer: Q3 revenue was up 12.4% on Q2, mostly apparel (+56.5%) and the online channel. The worry: footwear is the one category that shrank, and its return rate went up. Broken down by region, the rise is almost all the Northeast, where August and September returns spiked. The agent calls it a likely product or sizing issue and says to pull SKU-level data next.
- The Canvas ends up as a short report with an overview, the drivers, the concern and a conclusion, which you can export as Markdown.
The agent¶
83 lines in gallery/sales-analyst/agent.py,
and most of them are the prompt. It's a plain create_agent(...) built from parts the
langstage package provides:
run_python, a five-line tool around LangStage's notebook session (create_cell+execute_cell). Variables and DataFrames persist between calls, like cells in a notebook.display_inline, which renders a chart, table or image file in the chat.CanvasMiddleware, which gives the agent the Canvas tools and tells it how to build a report with sections, narrative and a conclusion..with_config(recursion_limit=80). An analysis plus a report takes more than LangGraph's default 25 steps.
"""Sales analyst: answers questions about a sales CSV with code, charts and a report.
It runs Python in a persistent session (pandas, Plotly), shows each chart inline in
the chat, and builds a short written report on LangStage's Canvas. The notebook
session, the inline display and the Canvas come from the `langstage` package; the
agent is a plain LangChain agent.
Run it (from this folder):
pip install langstage langchain-anthropic pandas plotly
export ANTHROPIC_API_KEY=... # or OPENROUTER_API_KEY=... (pip install langchain-openai)
langstage run --agent agent.py:graph --workspace workspace
Then ask: "What drove Q3? Anything I should worry about? Put the findings on the canvas."
"""
from __future__ import annotations
import os
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
from langchain_core.tools import tool
from langstage.middleware import CanvasMiddleware
from langstage.tools import create_cell, display_inline, execute_cell
def make_model():
"""Claude Sonnet 5: through OpenRouter when OPENROUTER_API_KEY is set, else directly."""
if os.environ.get("OPENROUTER_API_KEY"):
from langchain_openai import ChatOpenAI
return ChatOpenAI(
model="anthropic/claude-sonnet-5",
base_url="https://openrouter.ai/api/v1",
api_key=os.environ["OPENROUTER_API_KEY"],
)
return init_chat_model("anthropic:claude-sonnet-5")
@tool
def run_python(code: str) -> dict:
"""Run Python in the analysis session and return its output.
The session persists like a notebook: imports, variables and DataFrames from
earlier calls stay defined. pandas and plotly are installed. Returns stdout, the
value of the last expression, and the traceback if it failed.
"""
cell = create_cell(code) # LangStage's notebook session: one cell per call
out = execute_cell(cell["index"])
return {k: out[k] for k in ("status", "stdout", "result", "error") if out.get(k)}
SYSTEM_PROMPT = """You are a senior data analyst at Northwind Outfitters, an outdoor gear retailer.
The workspace holds `sales.csv`: one row per week x region x channel x category, with
`units`, `revenue` and `returned_units`. `week` is the Monday the week starts. The data
runs from October 2025 to September 2026. Quarters are calendar quarters: Q3 is
July to September 2026, compared with Q2 (April to June).
Analyze with run_python. Load the data once and reuse the DataFrame; do several
computations per call rather than one per call, and print compact tables.
Charts: build them with Plotly (plotly.express), use a clean template ("simple_white"),
title them with the finding, not the metric, and save each one with
`fig.write_json("charts/<name>.json")` (make the folder first; don't export images).
Show it in the chat with display_inline("charts/<name>.json"), and add it to the
canvas with add_to_canvas("charts/<name>.json", ...).
When a metric moves, find where before you explain why: break it down by region,
channel and week, and say whether the change is broad or concentrated. Don't claim a
pattern holds everywhere without checking each slice.
Be quantitative and brief. Lead with the answer. Quote numbers with units and the
comparison period ("+12% vs Q2"). Flag anything that looks wrong and say what you'd
check next. Two to four charts is plenty; don't chart what one sentence can say."""
graph = create_agent(
make_model(),
tools=[run_python, display_inline],
middleware=[CanvasMiddleware()],
system_prompt=SYSTEM_PROMPT,
name="Northwind Analyst",
).with_config(recursion_limit=80) # an analysis plus a report takes more than LangGraph's default 25 steps
Run it yourself¶
git clone https://github.com/dkedar7/langstage-docs
cd langstage-docs/gallery/sales-analyst
pip install langstage langchain-anthropic pandas plotly
export ANTHROPIC_API_KEY=...
langstage run --agent agent.py:graph --workspace workspace
Open the Canvas tab and ask your question. To use OpenRouter instead, set
OPENROUTER_API_KEY and pip install langchain-openai.
The data is synthetic, generated by
make_data.py
from a fixed seed. It has the patterns real retail data has, for the agent to find on
its own: seasonal camping and apparel peaks, a growing online share, one region
growing faster than the rest, and a footwear sizing problem in one region. Point the
agent at your own CSV by changing the first paragraph of its prompt.
About this recording¶
Recorded on 2026-10-11 with Claude Sonnet 5 (through OpenRouter), langstage 0.13.51
and langstage-core 1.0.47. The run is unscripted: the model wrote and ran all the code,
chose the charts and wrote the report. Stretches where the screen is waiting on the
model play at 3x; the finished report plays at real speed. The recorder is
demos/gallery/record_sales_analyst.py.