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
225 changes: 225 additions & 0 deletions Guardrails/bilig_workpaper_formula_readback.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Bilig WorkPaper Formula Readback With CrewAI\n",
"\n",
"This quickstart shows a CrewAI tool pattern for spreadsheet-backed business logic. The tool writes one input cell in a Bilig WorkPaper forecast model, recalculates dependent formulas, verifies readback, and returns a compact proof object.\n",
"\n",
"Use this when an agent needs spreadsheet logic without driving Excel, Google Sheets, or a browser."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Install Dependencies\n",
"\n",
"The deterministic smoke test only calls the Bilig API. The CrewAI agent run also needs an LLM key such as `OPENAI_API_KEY`."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"%pip install -U -q crewai pydantic"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Define The Bilig WorkPaper Tool\n",
"\n",
"The tool fails closed: if Bilig does not return `verified: true`, the call raises instead of letting the agent continue with stale or guessed formula values."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from __future__ import annotations\n",
"\n",
"import json\n",
"import os\n",
"from typing import Any\n",
"from urllib.parse import urljoin\n",
"from urllib.request import Request, urlopen\n",
"\n",
"from crewai import Agent, Crew, Process, Task\n",
"from crewai.tools import BaseTool\n",
"from pydantic import BaseModel, Field\n",
"\n",
"\n",
"DEFAULT_BASE_URL = \"https://bilig.proompteng.ai\"\n",
"DEFAULT_SHEET_NAME = \"Inputs\"\n",
"DEFAULT_ADDRESS = \"B3\"\n",
"DEFAULT_VALUE = 0.4\n",
"\n",
"\n",
"class ForecastFormulaInput(BaseModel):\n",
" sheet_name: str = Field(default=DEFAULT_SHEET_NAME, description=\"Worksheet containing the editable forecast input.\")\n",
" address: str = Field(default=DEFAULT_ADDRESS, description=\"A1-style input cell address to edit before recalculation.\")\n",
" value: float = Field(default=DEFAULT_VALUE, description=\"Numeric value to write before formula readback.\")\n",
"\n",
"\n",
"class BiligWorkPaperForecastTool(BaseTool):\n",
" name: str = \"Bilig WorkPaper forecast formula readback\"\n",
" description: str = (\n",
" \"Edit one forecast workbook input, recalculate formulas, and return a \"\n",
" \"verified before/after readback proof from Bilig WorkPaper.\"\n",
" )\n",
" args_schema: type[BaseModel] = ForecastFormulaInput\n",
" base_url: str = Field(default=DEFAULT_BASE_URL)\n",
" timeout: int = Field(default=30)\n",
"\n",
" def _run(self, sheet_name: str = DEFAULT_SHEET_NAME, address: str = DEFAULT_ADDRESS, value: float = DEFAULT_VALUE) -> str:\n",
" proof = call_bilig_forecast(\n",
" base_url=self.base_url,\n",
" sheet_name=sheet_name,\n",
" address=address,\n",
" value=value,\n",
" timeout=self.timeout,\n",
" )\n",
" return json.dumps(compact_proof(proof), indent=2)\n",
"\n",
"\n",
"def call_bilig_forecast(*, base_url: str, sheet_name: str, address: str, value: float, timeout: int) -> dict[str, Any]:\n",
" if not base_url.startswith((\"http://\", \"https://\")):\n",
" raise ValueError(\"BILIG_BASE_URL must start with http:// or https://\")\n",
"\n",
" endpoint = urljoin(base_url.rstrip(\"/\") + \"/\", \"api/workpaper/n8n/forecast\")\n",
" body = json.dumps({\"sheetName\": sheet_name, \"address\": address.upper(), \"value\": value}).encode(\"utf-8\")\n",
" request = Request(\n",
" endpoint,\n",
" data=body,\n",
" headers={\n",
" \"accept\": \"application/json\",\n",
" \"content-type\": \"application/json\",\n",
" \"user-agent\": \"CrewAI-Bilig-WorkPaper-Quickstart/0.1\",\n",
" },\n",
" method=\"POST\",\n",
" )\n",
"\n",
" with urlopen(request, timeout=timeout) as response:\n",
" parsed = json.loads(response.read().decode(\"utf-8\"))\n",
"\n",
" if not isinstance(parsed, dict):\n",
" raise ValueError(f\"Expected JSON object response, received {type(parsed).__name__}\")\n",
" if parsed.get(\"verified\") is not True:\n",
" raise ValueError(f\"Bilig returned an unverified WorkPaper response: {parsed}\")\n",
"\n",
" return parsed\n",
"\n",
"\n",
"def compact_proof(proof: dict[str, Any]) -> dict[str, Any]:\n",
" before = proof.get(\"before\") if isinstance(proof.get(\"before\"), dict) else {}\n",
" after = proof.get(\"after\") if isinstance(proof.get(\"after\"), dict) else {}\n",
" checks = proof.get(\"checks\") if isinstance(proof.get(\"checks\"), dict) else {}\n",
"\n",
" return {\n",
" \"verified\": proof.get(\"verified\") is True,\n",
" \"editedCell\": proof.get(\"editedCell\"),\n",
" \"before\": {\"expectedArr\": before.get(\"expectedArr\"), \"targetGap\": before.get(\"targetGap\")},\n",
" \"after\": {\"expectedArr\": after.get(\"expectedArr\"), \"targetGap\": after.get(\"targetGap\")},\n",
" \"checks\": {\n",
" \"formulasPersisted\": checks.get(\"formulasPersisted\") is True,\n",
" \"restoredMatchesAfter\": checks.get(\"restoredMatchesAfter\") is True,\n",
" \"computedOutputChanged\": checks.get(\"computedOutputChanged\") is True,\n",
" },\n",
" \"source\": \"Bilig WorkPaper\",\n",
" \"github\": \"https://github.com/proompteng/bilig\",\n",
" }"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Run A Deterministic Tool Smoke Test\n",
"\n",
"This cell validates the workbook API and proof object without using an LLM."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"base_url = os.getenv(\"BILIG_BASE_URL\", DEFAULT_BASE_URL)\n",
"tool = BiligWorkPaperForecastTool(base_url=base_url)\n",
"\n",
"print(tool._run(sheet_name=\"Inputs\", address=\"B3\", value=0.4))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Let A CrewAI Agent Use The Tool\n",
"\n",
"Run this section after setting your model provider key, for example `OPENAI_API_KEY`. The agent is instructed to answer only from computed readback returned by the tool."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"verifier = Agent(\n",
" role=\"Workbook formula verifier\",\n",
" goal=\"Use verified workbook readback to explain whether the edited forecast input changed the computed ARR output.\",\n",
" backstory=(\n",
" \"You check spreadsheet-backed business logic through API tools. \"\n",
" \"You do not infer formula results; you rely on computed readback.\"\n",
" ),\n",
" tools=[tool],\n",
" verbose=True,\n",
")\n",
"\n",
"task = Task(\n",
" description=(\n",
" \"Set the forecast input cell to 0.4 with the Bilig WorkPaper tool. \"\n",
" \"Report the edited cell, before and after expected ARR, target gap, \"\n",
" \"and whether formulas persisted after restore.\"\n",
" ),\n",
" expected_output=\"A concise verification summary grounded only in the Bilig WorkPaper proof object returned by the tool.\",\n",
" agent=verifier,\n",
")\n",
"\n",
"crew = Crew(agents=[verifier], tasks=[task], process=Process.sequential, verbose=True)\n",
"result = crew.kickoff()\n",
"print(result)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ Each quickstart is self-contained and can be run independently:
3. Follow the instructions in the example
4. Experiment and modify the code to learn!

Featured guardrails quickstart:

- [Bilig WorkPaper Formula Readback](Guardrails/bilig_workpaper_formula_readback.ipynb) - verify spreadsheet formula outputs through a CrewAI tool without Excel or browser automation.

---

## Support
Expand All @@ -73,4 +77,4 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file

**Happy Learning! 🎉**

*Start your CrewAI journey today and build amazing multi-agent systems!*
*Start your CrewAI journey today and build amazing multi-agent systems!*