Introduction
What if your business users could simply ask a question about their SAP S/4HANA data and get an interactive chart back without writing a single query, opening SAP Analysis for Office, or waiting for a new WebI report to be built?
That is exactly what I built: a prompt-driven analytics agent that sits on top of SAP BusinessObjects (BOBJ) and lets users explore S/4 CDS View data through natural language. In this post I will walk through the architecture, the two AI-powered flows at the heart of it, and what I learned along the way.
The Problem
SAP S/4 CDS View queries contain rich analytical data, but consuming that data typically requires either:
- A pre-built WebI report — static, and requires BI developer involvement to change, or
- Direct access to S/4 CDS views via Analysis for Office or other reporting tools — technical, not suitable for casual users.
Business users are stuck waiting. BI analysts are stuck building reports for every new question. This agent breaks that cycle.
What I Built
A browser-based agent - no SAP GUI, no thick client, that:
- Connects to BOBJ via the REST API using standard username/password
- Browses your existing WebI reports and lets you pick one as the analytical starting point, just as a base
- Reads the S/4 CDS View query structure — dimensions, measures, variable prompts — directly from the report's data provider
- Generates seed prompts using Claude: one clickable question per report block, written in plain business language
- Answers free-form questions by translating natural language into drill/filter parameters and re-executing the query
- Renders results as interactive Plotly charts (bar, line, pie, scatter) or HTML tables — all styled in SAP's brand palette
Screen Flow
The agent guides users through four screens from login to live interactive charts. Below are screenshots from a real session using a S/4HANA sflight data report.
Screen 1 — Login
The user enters the BOBJ host URL, authentication type, username and password. The agent calls the BOBJ REST logon endpoint and stores the session token server-side.
Figure 1 — Login screen: BOBJ host and credentials
Screen 2 — Report Picker
After login the user browses the BOBJ folder tree. Folders expand on click; .wid Web Intelligence documents are selectable. A preview panel shows the document name and ID before importing.
Figure 2 — Report picker: expanding the BOBJ folder tree
Figure 3 — A WebI report selected and ready to import
Screen 3a — Variable Prompts
Before the query is executed, the agent reads the report's mandatory and optional variable prompts and renders them as a form. The user fills in values (or leaves optional fields blank) and clicks Run Report.
Figure 4 —Variable prompt form: Departure and Arrival Airport filters
Screen 3b — Analytics
Once the report runs, the user lands on the main analytics screen. The left panel shows AI-generated seed prompts (one per report block) and a free-form chat input. The right panel shows the result, initially as a table, then as charts as the user explores.
Figure 5 — Initial table result after report execution, with seed prompt generated on the left
Figure 6 — Seed prompt panel: AI-generated prompt and free-form chat input
Interactive Charts in Action
Clicking a seed prompt or typing a free-form question triggers the query agent. Claude translates the natural language request into a structured query plan, the data is re-fetched from BOBJ, and Plotly renders the result instantly.
Figure 7 — Bar chart: Available Seats by Airline, generated from a seed prompt click
Figure 8 — Pie chart: Maximum Economy Capacity by Airline, from a follow-up question
Architecture
The stack is intentionally lean. All data access goes through BOBJ's REST API — the agent never connects to SAP S/4 directly. The queries are executed by BOBJ internally, which means existing security, authorizations, and variable prompts all work exactly as they do in WebI.
Browser (htmx + Plotly.js)
│
▼
FastAPI Python App
├── bobj_client.py → SAP BOBJ REST API (Raylight)
├── import_agent.py → Claude API (seed prompt generation)
├── query_agent.py → Claude API (NL → query plan)
├── renderer.py → Plotly charts + HTML tables
└── server.py → FastAPI routes + session state
The UI is a single HTML file using htmx for server-driven updates and vanilla JS for screen transitions, no frontend framework, no build step.
Two AI Flows
Flow 1 — Import: Turning a Report into Seed Prompts
When a user selects a WebI report, the agent calls four BOBJ REST endpoints to extract the block structure, Data provider dimensions and measures (with technical names and labels), and the Variable prompts.
This metadata is sent to Claude with a strict system prompt: generate one seed prompt per report block in plain business language, using only the dimensions and measures available in the schema.
For the flight data report, Claude produced:
- Seed Prompt 1 — "Show Available Seats by Airline as Bar Chart"
- Seed Prompt 2 — "Show the Maximum Capacity by Airline as Pie Chart"
These are not hardcoded — they are generated fresh from whatever report the user picks.
Flow 2 — Query: Natural Language to Parameters
Whether the user clicks a seed prompt or types their own question, the same flow runs:
- Claude receives the user message, the CDS View schema (dimension and measure tech names + labels), active variable values, and the last 20 conversation turns.
- Claude outputs a structured JSON query plan specifying data provider, filters, drilldown dimensions, measures, and chart type.
- The server validates every dimension and measure tech name against the cached schema before touching BOBJ. If Claude hallucinated a field name, a one-time retry sends the error back for self-correction.
- The validated plan is applied via BOBJ REST API and the result is rendered as a Plotly chart.
An example query plan output from Claude:
{
"data_provider": "DP1",
"filters": [
{"dimension": "0DEPAP", "operator": "eq", "value": "FRA"}
],
"drilldown": ["0AIRLINE"],
"measures": ["AVAILABLE_SEATS"],
"chart_type": "bar",
"summary": "Available seats grouped by airline for flights departing Frankfurt."
}
The conversation is stateful, follow-up questions like 'Show as a pie chart instead' or 'Filter to Lufthansa only' carry full history and refine the previous query without starting over.
Key Engineering Decisions
- Technical names over labels — SAP technical names are the key to reliable LLM queries. Passing both tech_name and label to Claude — and requiring it to output only tech_name — dramatically reduces hallucination. The model maps 'airline' in the user's question to 0AIRLINE in the schema without guessing.
- Variable scope injection — Variable values must be part of every query context. If the user ran the report filtered to Frankfurt, every subsequent query includes that filter automatically. Injecting them as 'active variable filters' in the system prompt ensures consistent, scoped results.
- The ## cell problem — The BOBJ REST API returns ## for cells where the value is too large to display in the current drill state. The renderer detects and gracefully handles these rather than charting a string as a number.
- Security by design — No S/4 direct connection, no RFC. BOBJ's Raylight API is the only integration point, which means all existing S/4 authorizations and security apply automatically.
Project Structure
bobj-s4cds-agent/
bobj_client.py # BOBJ REST API client
import_agent.py # Seed prompt generation (Claude)
query_agent.py # NL → Query plan (Claude)
renderer.py # Plotly charts + HTML tables
server.py # FastAPI routes + session state
static/
index.html # Single-page UI (htmx + Plotly.js)
tests/
test_bobj_client.py
test_import_agent.py
test_query_agent.py
test_renderer.py
test_server.py
requirements.txt
To run locally:
pip install -r requirements.txt
export BOBJ_HOST=https://your-bobj-host
export ANTHROPIC_BASE_URL=https://your-hai-proxy/anthropic/
export ANTHROPIC_AUTH_TOKEN=your-token
uvicorn server:app --reloadWhat's Next
This is v1 and deliberately scoped. Things could be added:
- SAP SSO / SAML authentication — so users don't need a separate BOBJ password
- Multi-data-provider joins — combine dimensions from different CDS Views in one chart
- Saved queries / bookmarks — let users save useful prompts for quick reuse
- Scheduled delivery — email a chart to stakeholders on a schedule
Closing Thoughts
The core idea here is simple: the CDS View query and its metadata are already a well-structured schema. An LLM is very good at mapping natural language to a structured schema. Connect the two with a thin validation layer and you get a surprisingly capable analytics interface — one that feels natural to business users and does not require a BI developer to answer every new question.
If you are working with SAP BOBJ and S/4 or BW, I hope this gives you a useful starting point. I hope you find this article informative and interesting.