Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ Reference implementations provided in this repository demonstrate common use-cas
- [**Get User Info Server**](servers/get-user-info) - Access and return enriched user profile information from authentication providers or internal systems.
- [**SQL Chat Server**](servers/sql) - Connect to SQL databases and automatically generate, execute, and optimize queries based on your database schema and natural language input. Enables chat-based data exploration, leveraging external Retrieval-Augmented Generation (RAG) for advanced query assistance.
- [**External RAG Tool Server**](servers/external-rag) - Connect and execute your own Retrieval-Augmented Generation (RAG) pipelines as callable API tools. Easily integrate custom or third-party RAG flows, providing structured access and modular composition for knowledge-intensive applications.
- [**Bilig WorkPaper Formula Readback Server**](servers/bilig-workpaper) - Write spreadsheet model inputs, recalculate formulas, and return verified before/after readback proof without Excel or browser automation.

(More examples and reference implementations will be actively developed and continually updated.)

Expand Down
7 changes: 7 additions & 0 deletions compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ services:
context: ./servers/time
ports:
- 8083:8000
bilig-workpaper-server:
build:
context: ./servers/bilig-workpaper
ports:
- 8084:8000
environment:
BILIG_BASE_URL: ${BILIG_BASE_URL:-https://bilig.proompteng.ai}

volumes:
memory:
12 changes: 12 additions & 0 deletions servers/bilig-workpaper/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
FROM python:3.12-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY main.py .

EXPOSE 8000

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
85 changes: 85 additions & 0 deletions servers/bilig-workpaper/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Bilig WorkPaper OpenAPI Tool Server

A FastAPI tool server for spreadsheet-backed formula calculations through Bilig WorkPaper.

Use it when an agent or OpenAPI-compatible client needs to write one workbook input, recalculate formulas, and verify computed readback without opening Excel, Google Sheets, or a browser.

## Quickstart

```bash
git clone https://github.com/open-webui/openapi-servers
cd openapi-servers/servers/bilig-workpaper

pip install -r requirements.txt
uvicorn main:app --host 0.0.0.0 --reload
```

Open the docs:

```text
http://localhost:8000/docs
```

## Docker

```bash
docker compose up
```

## Configuration

By default, this server calls the hosted Bilig demo:

```text
https://bilig.proompteng.ai
```

Set `BILIG_BASE_URL` to point at a self-hosted Bilig app:

```bash
export BILIG_BASE_URL=http://localhost:4321
```

## Endpoint

```text
POST /forecast/readback
```

Request:

```json
{
"sheetName": "Inputs",
"address": "B3",
"value": 0.4
}
```

Response:

```json
{
"verified": true,
"editedCell": "Inputs!B3",
"before": {
"expectedArr": 60000,
"targetGap": -34000
},
"after": {
"expectedArr": 96000,
"targetGap": 5600
},
"checks": {
"formulasPersisted": true,
"restoredMatchesAfter": true,
"computedOutputChanged": true
},
"source": "Bilig WorkPaper",
"github": "https://github.com/proompteng/bilig"
}
```

## Why This Exists

Spreadsheet logic often drives pricing, forecasts, approvals, and payout rules. UI automation can click the wrong cell, and cached XLSX values can be stale. This server gives OpenAPI-compatible agents a narrow, typed API that returns proof that formulas recalculated after the edit.
7 changes: 7 additions & 0 deletions servers/bilig-workpaper/compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
services:
bilig-workpaper-server:
build: .
ports:
- "8084:8000"
environment:
BILIG_BASE_URL: ${BILIG_BASE_URL:-https://bilig.proompteng.ai}
162 changes: 162 additions & 0 deletions servers/bilig-workpaper/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import os
from typing import Any

import requests
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, Field


DEFAULT_BILIG_BASE_URL = "https://bilig.proompteng.ai"

app = FastAPI(
title="Bilig WorkPaper Formula Readback API",
version="0.1.0",
description=(
"OpenAPI tool server for writing one Bilig WorkPaper input cell, "
"recalculating formulas, and returning verified computed readback."
),
)

app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)


class FormulaReadbackRequest(BaseModel):
sheetName: str = Field(
"Inputs",
description="Worksheet containing the editable forecast input.",
examples=["Inputs"],
)
address: str = Field(
"B3",
description="A1-style input cell address to edit before recalculation.",
examples=["B3"],
)
value: float = Field(
0.4,
description="Numeric value to write before formula readback.",
examples=[0.4],
)


class FormulaSnapshot(BaseModel):
expectedArr: float | None = Field(None, description="Computed expected ARR value.")
targetGap: float | None = Field(None, description="Computed gap to target value.")


class FormulaChecks(BaseModel):
formulasPersisted: bool = Field(
..., description="Formula definitions persisted after serialization."
)
restoredMatchesAfter: bool = Field(
..., description="Restored workbook readback matched post-edit values."
)
computedOutputChanged: bool = Field(
..., description="Dependent formula output changed after the input edit."
)


class FormulaReadbackResponse(BaseModel):
verified: bool = Field(
..., description="True only when Bilig verified the formula readback proof."
)
editedCell: str | None = Field(
None, description="Edited worksheet cell, for example Inputs!B3."
)
before: FormulaSnapshot
after: FormulaSnapshot
checks: FormulaChecks
source: str = Field("Bilig WorkPaper", description="Readback source.")
github: str = Field(
"https://github.com/proompteng/bilig", description="Bilig project URL."
)


def bilig_base_url() -> str:
base_url = os.getenv("BILIG_BASE_URL", DEFAULT_BILIG_BASE_URL).rstrip("/")
if not base_url.startswith(("http://", "https://")):
raise HTTPException(
status_code=500, detail="BILIG_BASE_URL must start with http:// or https://"
)
return base_url


def compact_proof(proof: dict[str, Any]) -> FormulaReadbackResponse:
if proof.get("verified") is not True:
raise HTTPException(
status_code=502,
detail={"message": "Bilig returned an unverified response", "proof": proof},
)

before = proof.get("before") if isinstance(proof.get("before"), dict) else {}
after = proof.get("after") if isinstance(proof.get("after"), dict) else {}
checks = proof.get("checks") if isinstance(proof.get("checks"), dict) else {}

return FormulaReadbackResponse(
verified=True,
editedCell=proof.get("editedCell"),
before=FormulaSnapshot(
expectedArr=before.get("expectedArr"),
targetGap=before.get("targetGap"),
),
after=FormulaSnapshot(
expectedArr=after.get("expectedArr"),
targetGap=after.get("targetGap"),
),
checks=FormulaChecks(
formulasPersisted=checks.get("formulasPersisted") is True,
restoredMatchesAfter=checks.get("restoredMatchesAfter") is True,
computedOutputChanged=checks.get("computedOutputChanged") is True,
),
)


@app.get("/health", summary="Health check")
def health() -> dict[str, str]:
return {"status": "ok"}


@app.post(
"/forecast/readback",
response_model=FormulaReadbackResponse,
summary="Write one forecast input and return verified formula readback",
)
def forecast_readback(request: FormulaReadbackRequest) -> FormulaReadbackResponse:
try:
response = requests.post(
f"{bilig_base_url()}/api/workpaper/n8n/forecast",
json={
"sheetName": request.sheetName,
"address": request.address.upper(),
"value": request.value,
},
headers={
"Accept": "application/json",
"User-Agent": "OpenWebUI-Bilig-WorkPaper-Server/0.1",
},
timeout=30,
)
response.raise_for_status()
proof = response.json()
except requests.exceptions.RequestException as error:
raise HTTPException(
status_code=503, detail=f"Error connecting to Bilig: {error}"
) from error
except ValueError as error:
raise HTTPException(
status_code=502, detail=f"Bilig returned invalid JSON: {error}"
) from error

if not isinstance(proof, dict):
raise HTTPException(
status_code=502,
detail=f"Expected JSON object from Bilig, received {type(proof).__name__}",
)

return compact_proof(proof)
4 changes: 4 additions & 0 deletions servers/bilig-workpaper/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
fastapi
uvicorn[standard]
pydantic
requests