OrionBelt Semantic Layer
About
API-first engine and MCP server that transforms declarative YAML model definitions into optimized SQL for Postgres, Snowflake, ClickHouse, Dremio, and Databricks
Details
- Author
- ralfbecher
- Categories
- Database, Other
Jump to
Setup
Install OrionBelt Semantic Layer in your MCP client (Claude Desktop, Cursor, Windsurf, and others).
Repository: https://github.com/ralfbecher/orionbelt-semantic-layer
Follow the installation instructions in the repository README, then restart your MCP client.
API-first engine and MCP server that transforms declarative YAML model definitions into optimized SQL for Postgres, Snowflake, ClickHouse, Dremio, and Databricks
Define your metrics once in YAML. Let agents and BI tools query them without ever touching your schema.
Asemantic sidecar: it rides alongside the systems you already run instead of replacing them.
Ask an LLM to write SQL against a raw star schema and sooner or later it joins two fact tables and hands you a revenue number inflated by a factor of eight. It looks right. Nobody catches it.
OrionBelt is asemantic sidecar. You declare dimensions, measures, metrics, and joins in version-controlled YAML. OrionBelt compiles them into dialect-specific SQL through a real AST, and routes multi-fact queries through a Composite Fact Layer planner thatblocks the join paths that produce fan traps. Agents and BI tools ask for"Total Revenue" by "Country". They never see a table name.
No BI tool in the middle. No runtime lock-in. Point it at what you already have.
Here is TPC-DS query 98. Two measures over the same column, identical but for one line:Class Revenueis pinned to a coarser grain than the query asks for.
measures: Store Sales Amount: columns: [{dataObject: Store Sales, column: Ext Sales Price}] aggregation: sum Class Revenue: columns: [{dataObject: Store Sales, column: Ext Sales Price}] aggregation: sum grain: {mode: FIXED, keepOnly: [Class]} # <- pin to Class, ignore query grain metrics: Revenue Ratio: expression: "{[Store Sales Amount]} 100.0 / {[Class Revenue]}"
That onegrainline is what becomesSUM(...) OVER (PARTITION BY "Class")below.
The query names business concepts. No tables, no joins, no SQL:
select: dimensions: [Item ID, Item Description, Category, Class, Current Price] measures: [Store Sales Amount, Revenue Ratio] where: - {field: Category, op: inlist, value: [Sports, Books, Home]} - {field: Order Date, op: between, value: ["1999-02-22", "1999-03-24"]}
pip install orionbelt-semantic-layer obsl compile tpcds.obml.yml -q Q98.yml -d duckdb
WITH "base" AS ( SELECT "Item"."i_item_id" AS "Item ID", "Item"."i_item_desc" AS "Item Description", "Item"."i_category" AS "Category", "Item"."i_class" AS "Class", "Item"."i_current_price" AS "Current Price", CAST(SUM("Store Sales"."ss_ext_sales_price") AS DECIMAL(18, 2)) AS "Store Sales Amount", SUM("Store Sales"."ss_ext_sales_price") AS "Class Revenue" FROM "main"."store_sales" AS "Store Sales" LEFT JOIN "main"."item" AS "Item" ON "Store Sales"."ss_item_sk" = "Item"."i_item_sk" LEFT JOIN "main"."date_dim" AS "Date" ON "Store Sales"."ss_sold_date_sk" = "Date"."d_date_sk" WHERE "Item"."i_category" IN ('Sports', 'Books', 'Home') AND "Date"."d_date" BETWEEN '1999-02-22' AND '1999-03-24' GROUP BY ALL ) SELECT "Item ID" AS "Item ID", "Item Description" AS "Item Description", "Category" AS "Category", "Class" AS "Class", "Current Price" AS "Current Price", "Store Sales Amount" AS "Store Sales Amount", "Store Sales Amount" 100.0 / NULLIF(SUM("Class Revenue") OVER (PARTITION BY "Class"), 0) AS "Revenue Ratio" FROM "base" AS "base" ORDER BY "Category" ASC, "Class" ASC, "Item ID" ASC, "Item Description" ASC, "Revenue Ratio" ASC
You did not write the join path, the window function over an aggregate, theNULLIFguard, or one table name. Change-d duckdbto-d snowflakeand the same two files compile for Snowflake, or for any of eight dialects.
This is checked, not asserted.40 TPC-DS queries are built against a single OBML model and compared row by row against each engine's own reference SQL: 39 of 40 match on DuckDB at sf=1, 37 of 40 on ClickHouse at sf=10. Every one of the remaining differences traces to a reference variant rather than a compilation error, and each is documented. Seethe sweep, or the queries inexamples/tpcds_queries/.
The same model serves every surface you already use:
- Your BI tool, over the PostgreSQL wire protocol on:5432. Tableau, Power BI, Superset, DBeaver, andpsqlconnect with the Postgres driver they already ship. Dremio federates it as a Postgres source.
- YourAI agents, over MCP. Works with Claude, Cursor, Copilot, and Windsurf.
- Your code, over REST, Arrow Flight SQL, or PEP 249 drivers.
Compiles to BigQuery, ClickHouse, Databricks, Dremio, DuckDB/MotherDuck, MySQL, PostgreSQL, and Snowflake.
OrionBelt is a sidecar, not a platform. It compiles a YAML model into correct SQL and exposes it over the protocols you already use. It does not run a cluster, own your cache, or ask you to adopt a cloud.
- Agents query your data and a silently wrong number is unacceptable. Multi-fact queries route through a Composite Fact Layer planner that blocks fan-trap join paths instead of quietly summing across them.
- You want your metric definitions in reviewable YAML, with no JavaScript or Python in the model layer.
- Your BI tool should connect over the Postgres driver it already ships, with no new connector to install and no vendor runtime in the path.
- You self-host, across more than one engine, and want one model to compile for all of them.
- You need pre-aggregation and caching tuned for high-concurrency dashboards at scale.Cubehas years of production hardening there that OrionBelt does not.
- Your metrics already live in dbt and your team is happy there.MetricFlowkeeps them where they are.
- You want an exploratory analysis language rather than a serving layer.Malloyis a better fit.
Try the live demowith a pre-loaded model, oropen the Colab notebookand run it against TPC-H data.
Try it in 30 seconds·Claude Desktop / MCP·Why OrionBelt?·Features·Example·Documentation·Roadmap·Commercial·Development
Open the Live Demo— Gradio UI with a pre-loaded example model. Paste a query, pick a dialect, see SQL instantly.
Want to try the PostgreSQL wire surface?Cloud Run is HTTPS-only, so the public demo can't expose ports 5432 (pgwire) or 8815 (Flight SQL). Spin the same demo up locally in two commands — it includes the baked-inorionbelt_1_commerceDuckDB dataset and the full OBSQL surface:
docker run --rm -d --name orionbelt-demo \ -p 8080:8080 -p 5432:5432 -p 8815:8815 \ -e PGWIRE_ENABLED=true \ -e FLIGHT_ENABLED=true \ ralforion/orionbelt-semantic-layer-api:latest # REST + Gradio UI: http://localhost:8080/ui # pgwire (any psql / DBeaver / Tableau / Power BI): psql "host=localhost port=5432 user=obsl dbname=orionbelt_1_commerce sslmode=disable" \ -c 'SELECT "Client Name", "Total Sales" LIMIT 5' # Flight SQL smoke test: uv run python examples/obsql.py 'SELECT "Client Name", "Total Sales" LIMIT 5' docker stop orionbelt-demo
The container ships withPGWIRE_AUTH_MODE=trust(default), so it's safe forlocalhostbutnotsafe to expose to the public internet. For exposed deployments, setAUTH_MODE=api_key(shipped in v2.12.0): pgwire then negotiates SCRAM-SHA-256 (or cleartext over TLS) against the shared key store.
from orionbelt.parser import ReferenceResolver, TrackedLoader from orionbelt.compiler.pipeline import CompilationPipeline from orionbelt.models.query import QueryObject, QuerySelect model_yaml = """ version: 1.0 dataObjects: Orders: code: ORDERS columns: Price: { code: PRICE, abstractType: float } Country: { code: COUNTRY, abstractType: string } dimensions: Country: dataObject: Orders column: Country resultType: string measures: Total Revenue: resultType: float aggregation: sum expression: "{[Orders].[Price]}" """ loader = TrackedLoader() raw, source_map = loader.load_string(model_yaml) resolver = ReferenceResolver() model, result = resolver.resolve(raw, source_map) query = QueryObject(select=QuerySelect(dimensions=["Country"], measures=["Total Revenue"])) pipeline = CompilationPipeline() output = pipeline.compile(query, model, "postgres") print(output.sql)
SELECT "Orders"."COUNTRY" AS "Country", CAST(SUM("Orders"."PRICE") AS NUMERIC(18, 2)) AS "Total Revenue" FROM ORDERS AS "Orders" GROUP BY "Orders"."COUNTRY"
No env file needed — the compilation pipeline is stateless.
orionbelt-api # REST API on :8000 (Swagger UI at /docs, Gradio UI at /ui) orionbelt-ui # standalone Gradio UI on :7860 (connects to API on :8000) FLIGHT_ENABLED=true orionbelt-api # API + Arrow Flight SQL on :8815 (DBeaver, Tableau, Power BI) PGWIRE_ENABLED=true orionbelt-api # API + PostgreSQL wire on :5432 (Tableau, DBeaver, Superset, psql, Dremio source)
uv run orionbelt-api # REST API on :8000 (Swagger UI at /docs, Gradio UI at /ui) uv run orionbelt-ui # standalone Gradio UI on :7860 (connects to API on :8000) FLIGHT_ENABLED=true uv run orionbelt-api # API + Arrow Flight SQL on :8815 (DBeaver, Tableau, Power BI) PGWIRE_ENABLED=true uv run orionbelt-api # API + PostgreSQL wire on :5432 (Tableau, DBeaver, Superset, psql, Dremio source)
Use theobslCLI(no server needed - compiles in-process):
obsl validate model.yaml # lint a model (exit 1 on error, CI-friendly) obsl compile model.yaml -q query.json -d snowflake # print the generated SQL obsl compile model.yaml --sql 'SELECT "Region", "Sales" FROM model' # ... or from an OBSQL string obsl describe model.yaml # overview of data objects + artefacts obsl diagram model.yaml # Mermaid ER diagram obsl convert obml-to-osi model.yaml # OBML -> OSI (and osi-to-obml) obsl execute -q query.json --server http://host # run against a deployed model (omit MODEL)
Smoke-test the Flight SQL surfacewithout a BI tool:
uv run python examples/obsql.py 'SELECT version()' uv run python examples/obsql.py 'SHOW TABLES' uv run python examples/obsql.py 'SELECT "Region", "Total Sales" FROM sales LIMIT 5' # Multi-model deployment? Pick the model with -m: uv run python examples/obsql.py -m sales 'SHOW TABLES' uv run python examples/obsql.py --list # discover loaded models via REST
OBSQL— OrionBelt Semantic QL — is the SQL surface BI tools and humans actually write. Bare labels,MEASURE()markers, or matching aggregate wrappers; aggregation-match validation;WITH ROLLUP/WITH CUBE; no escape hatch to raw warehouse SQL. Same language overArrow Flight SQL(v2.4+) andPostgreSQL wire(v2.5+):
PGWIRE_ENABLED=true uv run orionbelt-api & # Every BI tool already ships a Postgres ODBC/JDBC driver — point yours at :5432 psql "host=localhost port=5432 user=obsl dbname=sales sslmode=disable" \ -c 'SELECT "Region", "Total Sales" LIMIT 5' # All three measure forms compile to the same vendor SQL: psql "..." -c 'SELECT "Region", "Total Sales" FROM sales LIMIT 5' -- bare psql "..." -c 'SELECT "Region", MEASURE("Total Sales") FROM sales LIMIT 5' -- explicit marker psql "..." -c 'SELECT "Region", SUM("Total Sales") FROM sales LIMIT 5' -- matching aggregate
See theOBSQL referencefor the full grammar.
Stage 1 — Zero-config start(models loaded later via API or UI):
docker run -p 8080:8080 ralforion/orionbelt-semantic-layer-api
Openhttp://localhost:8080/docsto explore the API.
Stage 2 — Realistic setupwith docker compose:
# docker-compose.yml services: api: image: ralforion/orionbelt-semantic-layer-api:2.25.0 ports: ["8080:8080"] env_file: .env volumes: - ./models:/app/models:ro environment: MODEL_FILES: /app/models/my-model.obml.yml ui: image: ralforion/orionbelt-semantic-layer-ui:2.25.0 ports: ["7860:7860"] environment: API_BASE_URL: http://api:8080
See.env.templatefor the full environment variable reference.
- API_SERVER_HOSTis already0.0.0.0inside the container — no override needed.
- MCP via stdio does not work in Docker. Use theMCP HTTP clientfor containerized deployments.
- Mount models to/app/models(or any path) and setMODEL_FILES(comma-separated paths) to pre-load on startup.
- For production, pin a version tag (:2.25.0) rather than:latest.
The MCP server is a separate thin client that delegates to the REST API:
Add to your Claude Desktopclaude_desktop_config.json:
{ "mcpServers": { "orionbelt": { "command": "uvx", "args": ["orionbelt-semantic-layer-mcp"] } } }
Also works with Copilot, Cursor, and Windsurf. See theMCP repofor full setup options.
- OBML Format— YAML-based semantic models with data objects, dimensions, measures, metrics, and joins
- Cross-Schema Queries— model data objects across multiple databases and schemas in a single model
- Static Model Filters— mandatory WHERE conditions baked into the model, auto-applied with join extension
- OBSL Graph & SPARQL— RDF graph export and read-only SPARQL querying for every loaded model
- OSI Interoperability— bidirectional conversion between OBML and the Open Semantic Interchange format, now developed asApache Ossie (incubating)
- 8 SQL Dialects— BigQuery, ClickHouse, Databricks, Dremio, DuckDB/MotherDuck, MySQL, Postgres, Snowflake
- AST-Based Generation— custom SQL AST ensures correct, injection-safe SQL (not string templates)
- Star Schema & CFL— automatic join resolution with Composite Fact Layer for multi-fact queries
- Data Types & Precision— automatic CAST wrapping with dialect-specific type rendering and precision clamping
- Display Formatting— number format patterns (#,##0.00,0.00%) on measures/metrics with locale-aware rendering
- Timezone Settings— auto-detect database session timezone withdefaultTimezonefallback and ISO 8601 serialization
- sqlglot Validation— post-generation syntax check across all supported dialects
- REST API— FastAPI endpoints for model management, validation, compilation, and execution
- MCP Server—separate thin clientfor Claude, Copilot, Cursor, Windsurf
- AI Integrations— LangChain, OpenAI Agents SDK, CrewAI, Google ADK, Vercel AI SDK, n8n, ChatGPT
- Gradio UI— interactive web interface for model editing, query testing, and ER diagrams
- DB-API 2.0 + Flight SQL— PEP 249 drivers and Arrow Flight SQL server for DBeaver, Tableau, Power BI; ships withexamples/obsql.py, a tiny terminal CLI for testing the Flight surface without a BI tool
- PostgreSQL Wire Protocol(v2.5.0+) — native Postgres-protocol surface on:5432. Every BI tool already ships a Postgres ODBC/JDBC driver, so the user side is "point your existing connection at OBSL and go" — Tableau, DBeaver, Superset, Power BI, plainpsql, andDremio as a federated Postgres source(Dremio → OBSL → optionally back to Dremio's lakehouse, full circle)
- Model Health on Load— every model load returns ahealthblock with orphan dataObjects, fan-trap risks, and unreachable dimensions — agents skip the defensive second round trip
- Query Plan Endpoint—POST /query/planreturns the planner's understanding (planner choice, physical tables, join path,would_compile) without compiling SQL or executing; opt-ininclude_database_explainadds the warehouse's raw EXPLAIN
- Structured Warnings— everywarningslist across the API uses a stable{code, severity, message, path, hint, context}shape with a documented code taxonomy; agents branch on codes instead of parsing messages
- Fuzzy/findRecovery— when a search produces no exact or synonym hits, deterministic Levenshtein + trigram fallback returns near-miss candidates with scores and reasons
- Model Examples— optional OBMLexamples:block of canonical queries;GET /examples(with?intent=filtering) gives agents one-round-trip discovery of what a model is designed to answer
- Source-level freshness contracts— declarerefresh:blocks ondataObjectentries (interval / heartbeat / static); the cache derives query TTLs from the contracts of the physical tables a query touched, not from caller guesses
- Heartbeat invalidation— onePOST /v1/heartbeatto a physical table invalidates every cached query that depends on it, across every dataObject and session
- DuckDB metadata + Parquet results— file-backed cache with type-precise serialization, lazy expiration, LRU capacity sweep; opt-in viaCACHE_BACKEND=file
- Inverts the Cube/dbt/Looker pattern— contracts live on the source, not the semantic abstraction; one source of truth across every cube/explore/saved query reading the table
- Source-Position Errors— validation errors report exact YAML line and column
- ER Diagrams— interactive Mermaid diagrams with zoom and download (MD/PNG/Turtle)
- Session Management— TTL-scoped sessions with thread-safe model isolation
- JSON Schema— full OBML and query schema for IDE autocompletion (yaml-language-server)
# yaml-language-server: $schema=https://raw.githubusercontent.com/ralforion/orionbelt-semantic-layer/main/schema/obml-schema.json version: 1.0 dataObjects: Customers: code: CUSTOMERS database: WAREHOUSE schema: PUBLIC columns: Customer ID: { code: CUSTOMER_ID, abstractType: string } Country: { code: COUNTRY, abstractType: string } Orders: code: ORDERS database: WAREHOUSE schema: PUBLIC columns: Order Customer ID: { code: CUSTOMER_ID, abstractType: string } Price: { code: PRICE, abstractType: float } Quantity: { code: QUANTITY, abstractType: int } joins: - joinType: many-to-one joinTo: Customers columnsFrom: [Order Customer ID] columnsTo: [Customer ID] dimensions: Country: dataObject: Customers column: Country resultType: string measures: Revenue: resultType: float aggregation: sum expression: "{[Orders].[Price]} {[Orders].[Quantity]}" dataType: "decimal(18, 2)"
# Create a session curl -s -X POST http://localhost:8080/v1/sessions | jq .session_id # -> "a1b2c3d4" # Load the model curl -s -X POST http://localhost:8080/v1/sessions/a1b2c3d4/models \ -H "Content-Type: application/json" \ -d '{"model_yaml": "..."}' | jq .model_id # -> "abcd1234" # Compile a query curl -s -X POST http://localhost:8080/v1/sessions/a1b2c3d4/query/sql \ -H "Content-Type: application/json" \ -d '{"model_id":"abcd1234","query":{"select":{"dimensions":["Country"],"measures":["Revenue"]}},"dialect":"postgres"}' \ | jq -r .sql
SELECT "Customers"."COUNTRY" AS "Country", CAST(SUM("Orders"."PRICE" "Orders"."QUANTITY") AS NUMERIC(18, 2)) AS "Revenue" FROM WAREHOUSE.PUBLIC.ORDERS AS "Orders" LEFT JOIN WAREHOUSE.PUBLIC.CUSTOMERS AS "Customers" ON "Orders"."CUSTOMER_ID" = "Customers"."CUSTOMER_ID" GROUP BY "Customers"."COUNTRY"
Changedialecttobigquery,clickhouse,databricks,dremio,duckdb,mysql, orsnowflakefor dialect-specific SQL.
- SQL Compiler— side-by-side OBML model and query editors with syntax highlighting, 8 dialect selector, one-click compilation with formatted SQL output and query explain
- Query Execution— execute compiled queries against a connected database, view results with locale-aware number formatting, response metadata panel, TSV download and clipboard copy (requiresQUERY_EXECUTE=true)
- ER Diagram— interactive Mermaid ER diagram with zoom, column toggle, and download (MD/PNG/Turtle)
- Ontology Graph— interactive vis-network visualization of the OBML graph (data objects, dimensions, measures, metrics, joins) with toggleable layers and adjustable node spacing
- Editor Toolbar— clear, undo, redo, upload, download, and copy buttons on all code editors
- OSI Import/Export— convert between OBML and OSI formats
- Dark/Light Mode— toggle via header button, state persisted across sessions
Embedded mode— the UI is mounted at/uion the API server:
pip install orionbelt-semantic-layer && orionbelt-api # -> UI at http://localhost:8000/ui
Standalone mode— run API and UI as separate processes:
orionbelt-api # API on :8000 orionbelt-ui # UI on :7860 (connects to API on :8000) API_BASE_URL=http://remote-api:8080 orionbelt-ui # point UI to a remote API
OrionBelt Semantic Layer is open by default — the OSS distribution has full parity on the shipped v2.6 surface and is production-grade for self-hosted use. For teams that want production support, a managed runtime, or embedded analytics terms, RALFORION offers:
- Embedded analytics license— relicensing terms for shipping OBSL inside a commercial product
- Commercial cloud offering— managed OrionBelt runtime with SLAs
- Enterprise features— capabilities tailored for enterprise deployments
- Consulting + support— implementation, modeling, and production support
An ontology-based MCP server that analyzes relational database schemas and generates RDF/OWL ontologies. Together with OrionBelt Semantic Layer, it enables AI assistants to navigate your data landscape through ontologies and compile safe, dialect-aware analytical SQL.
Contributing to OrionBelt or running from source:
git clone https://github.com/ralforion/orionbelt-semantic-layer.git cd orionbelt-semantic-layer uv sync # install all deps (dev, docs, ui, flight, drivers) uv run orionbelt-api # start API on :8000
# Quality uv run pytest # run tests uv run ruff check src/ # lint uv run ruff format src/ tests/ # format uv run mypy src/ # type check # Docs uv sync --extra docs && uv run mkdocs serve # docs on :8080
OrionBelt® is a registered trademark of RALFORION d.o.o.
Licensed under theBusiness Source License 1.1. The Licensed Work will convert to Apache License 2.0 on 2030-03-16.
By contributing to this project, you agree to theContributor License Agreement.
For commercial licensing inquiries, contact:licensing@ralforion.com
Official MCP server for dbt (data build tool) providing integration with dbt Core/Cloud CLI, project metadata discovery, model information, and semantic layer querying capabilities.
Query and analyze data with MotherDuck and local DuckDB
Connect your LLM to the Firebolt Data Warehouse for data querying and analysis.
Sign in to leave a review
Use Google, GitHub, or an email account so ratings stay tied to real people.
No reviews posted yet.





