-
Notifications
You must be signed in to change notification settings - Fork 26
Add data-query category: AL query-generation benchmark #740
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Onat Buyukakkus (onbuyuka)
wants to merge
77
commits into
main
Choose a base branch
from
onbuyuka/data-query-category
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
77 commits
Select commit
Hold shift + click to select a range
e3dc7fa
Add data-query category: AL query-generation benchmark
0d46002
Merge remote-tracking branch 'origin/main' into onbuyuka/data-query-c…
8d08fbe
Fix pre-commit: ruff-format + ty (Sequence[Mapping] for result_sets_m…
f62ace6
Register data-query in Get-BCBenchDatasetPath (container setup)
a06dec4
data-query: clear workspace contents instead of rmtree (dir is mounte…
ec5dfee
data-query: drop invalid 'Extensible' property from wrapped API query…
614dea7
data-query: don't crash the job on a gold-query compile failure
5761e0e
data-query: normalize query object name and use per-object API entity…
237c930
data-query: use proven Invoke-AppBuildAndPublish + in-container OData…
d7ad1dd
data-query: build Basic auth header by hand for the in-container ODat…
8268f07
data-query: calibrate prompts + fix the intersection gold query
2a41a6c
data-query: add 5 more deterministically-scorable tasks (6 -> 11)
129c5ef
data-query: address PR review feedback (scoring integrity + robustness)
afde215
data-query: fix invalid Count columns in gold queries and skill
56f2335
data-query: exclude unscorable results from the bceval export too
faf22ba
data-query: validate the gold query before evaluating the agent's
45715a3
Withhold gold queries from the agent's filesystem during generation
adfca7c
Merge remote-tracking branch 'origin/main' into onbuyuka/data-query-c…
1c9aa72
Harden gold withholding: also hide the .git object database from the …
d24eb18
Add --skills dispatch flag to toggle agent skills per run
cad29ec
Normalize only numeric result values, preserve Code strings verbatim
979a388
Merge branch 'main' into onbuyuka/data-query-category
haoranpb 9820ae7
Simplify data-query harness per owner review
3ccb00d
Merge origin/main through PR #761
haoranpb f0921f5
uptake PR#761 with better extensibility
haoranpb 96fd0b4
uptake the file sysmte operation
haoranpb ee06e26
the latest release is 28.3 I believe
haoranpb 231a27e
Merge branch 'main' of https://github.com/microsoft/BC-Bench into onb…
haoranpb c218e13
uptake `bootstrap_app_json` util function
haoranpb 7517c75
Merge branch 'main' of https://github.com/microsoft/BC-Bench into onb…
9732dcb
Add BC MCP + Microsoft Learn MCP as agent capabilities for data-query
eb47c22
TEMP: BC MCP runner diagnostics (revert before merge)
63a62f9
TEMP: pull BC 29 sandbox artifact from insider feed (revert before me…
c718146
Build the MCP config app inside the shared repo folder
5693f08
Pivot data-query to answer DATA, not just write a query (+ contaminat…
1774a75
Bake gold data, bump build timeout, and remove BC MCP diagnostics
27c570f
Capture Copilot tool usage from the JSON event stream
eb4abc8
Scrub BC connection vars from the agent env; temp-raise gold timeout
1f8515f
TEMP: re-add agent *.log upload to diagnose BC MCP tool use (revert b…
50dcda3
Add localhost MCP gateway to isolate agent from BC /api (Phase A)
62cc621
Use Copilot-licensed user PAT for MCP registry policy in Actions
f3bfd71
Note server-prefixed MCP tool names in the bc-al-query-mcp skill
7cefd9f
Warm up + probe BC MCP tools through the gateway at startup
c3f9d23
Fix warm-up probe: read SSE incrementally + log timing, raise timeout
bb9664a
Add NST log capture to diagnose BC MCP exposing zero tools
f3b2ee9
Fix MCP Configuration DB query flags + add app install-status capture
8b6ee1a
Remove accidentally-committed diagnostic download folder; ignore _*/
833d361
Log raw HTTP status/body of an empty tools/list in the warm-up probe
5251222
Probe tools/list both via gateway and directly against BC
ed3f231
Fix gateway SSE relay: read1() so held-open streams flush promptly
ba83857
Capture Claude tool_use + transcript via stream-json output
5014d27
Raise Claude MCP timeout for BC's slow cold tools/list; log gateway r…
9cdeac5
Serve tools/list from the gateway warm-up cache
7dfeec7
De-stream held-open POST SSE replies in the gateway
684a29f
Relay POST SSE faithfully and close on response; handle client discon…
37604de
Log BC initialize result + relayed SSE headers to diagnose Claude han…
d083272
Relay BC MCP faithfully (hold streams open); only short-circuit tools…
ecedbb2
Strip x-ms-headerless from BC initialize so Claude's MCP client connects
6ae9b35
Pin exact BC data tool names in skill + prompt to stop name guessing
6e1d586
Clean up diagnostic logging and temporary hooks
39ea69d
Merge remote-tracking branch 'origin/main' into onbuyuka/data-query-c…
59194d5
Document the data-query MCP isolation work (3 reports) and refresh docs
93bec88
Sanitize forwarded headers in the MCP gateway (CodeQL: response split…
8b85758
Remove local reports from PR; fix CI lint-and-test (ruff format + ty)
c3068c1
Address PR review comments: pin gold company, type suffix, drop no-op
2f9166c
Drop the last removed-report link from docs/data-query.md
f6c2974
Correct the insider-artifact hack markers: remove at BC 29 GA, not be…
536db7d
Add Stop hook forcing the agent to write answer.json (data-query)
b83c024
TEMP diagnostic: re-add Claude transcript + *.log upload to inspect S…
1699c08
Retry BC MCP warm-up until the tool catalog is cached
c78a4d9
Revert temporary Stop-hook diagnostics (transcript dump + *.log upload)
c684013
Remove the Stop hook - the warm-up retry made it unnecessary
24d45b4
Raise BC MCP warm-up budget 360s->600s for slow cold containers
51aa12b
Drop baked gold_rows machinery and --ms-learn-mcp flag (PR review A)
c81b3fc
Add phased logging to query-run template; clarify fail-loud policy (P…
d0912e2
Rework data-query docs into a results-showcase page (PR review C)
41ffbf0
Remove now-unused _config global from dataset.py
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| {"instance_id": "dataquery__outstanding-sales-value-by-customer-1", "environment_setup_version": "29.0", "created_at": "2026-07-10", "metadata": {"area": "sales"}, "nl_prompt": "For each customer that has at least one open sales order line, return the customer's number, the customer's name, and their total outstanding amount. Use open sales order lines (Sales Line records whose document type is Order) and sum the line 'Outstanding Amount' field (which is net of VAT). Customers with no open sales order lines must not appear.", "ordered": false, "gold_query": "query 50100 OutstandingSalesByCustomer\n{\n QueryType = Normal;\n\n elements\n {\n dataitem(Customer; Customer)\n {\n column(No; \"No.\") { }\n column(Name; Name) { }\n dataitem(SalesLine; \"Sales Line\")\n {\n DataItemLink = \"Sell-to Customer No.\" = Customer.\"No.\";\n DataItemTableFilter = \"Document Type\" = const(Order);\n column(TotalOutstanding; \"Outstanding Amount\") { Method = Sum; }\n }\n }\n }\n}"} | ||
| {"instance_id": "dataquery__total-sold-quantity-by-item-1", "environment_setup_version": "29.0", "created_at": "2026-07-10", "metadata": {"area": "inventory"}, "nl_prompt": "Across all posted sales invoice lines whose type is Item, return each item's number together with the total quantity sold (the sum of the line Quantity). Produce one row per item number.", "ordered": false, "gold_query": "query 50100 SoldQuantityByItem\n{\n QueryType = Normal;\n\n elements\n {\n dataitem(SalesInvoiceLine; \"Sales Invoice Line\")\n {\n DataItemTableFilter = Type = const(Item);\n column(ItemNo; \"No.\") { }\n column(TotalQuantity; Quantity) { Method = Sum; }\n }\n }\n}"} | ||
| {"instance_id": "dataquery__avg-invoice-amount-by-country-1", "environment_setup_version": "29.0", "created_at": "2026-07-10", "metadata": {"area": "finance"}, "nl_prompt": "For each country/region, return the country/region code and the average posted sales invoice line amount. Average the 'Amount' field (net of VAT) over all posted sales invoice lines, grouping the lines by their bill-to customer's Country/Region Code.", "ordered": false, "gold_query": "query 50100 AvgInvoiceAmountByCountry\n{\n QueryType = Normal;\n\n elements\n {\n dataitem(Customer; Customer)\n {\n column(CountryRegionCode; \"Country/Region Code\") { }\n dataitem(SalesInvoiceHeader; \"Sales Invoice Header\")\n {\n DataItemLink = \"Bill-to Customer No.\" = Customer.\"No.\";\n dataitem(SalesInvoiceLine; \"Sales Invoice Line\")\n {\n DataItemLink = \"Document No.\" = SalesInvoiceHeader.\"No.\";\n column(AvgAmount; Amount) { Method = Average; }\n }\n }\n }\n }\n}"} | ||
| {"instance_id": "dataquery__total-purchase-amount-by-vendor-1", "environment_setup_version": "29.0", "created_at": "2026-07-10", "metadata": {"area": "purchasing"}, "nl_prompt": "For each vendor that has at least one posted purchase invoice line, return the vendor's number, the vendor's name, and their total posted purchase amount. Sum the 'Amount' field (net of VAT) from posted purchase invoice lines. Vendors with no posted purchase invoice lines must not appear.", "ordered": false, "gold_query": "query 50100 PurchaseAmountByVendor\n{\n QueryType = Normal;\n\n elements\n {\n dataitem(Vendor; Vendor)\n {\n column(No; \"No.\") { }\n column(Name; Name) { }\n dataitem(PurchInvLine; \"Purch. Inv. Line\")\n {\n DataItemLink = \"Buy-from Vendor No.\" = Vendor.\"No.\";\n column(TotalAmount; Amount) { Method = Sum; }\n }\n }\n }\n}"} | ||
| {"instance_id": "dataquery__open-sales-order-count-by-customer-1", "environment_setup_version": "29.0", "created_at": "2026-07-13", "metadata": {"area": "sales"}, "nl_prompt": "For each customer that has at least one open sales order, return the customer's number, the customer's name, and the number of open sales orders they have. An open sales order is a sales document whose document type is Order; count the order documents (headers), not the order lines. Customers with no open sales orders must not appear.", "ordered": false, "gold_query": "query 50100 OpenSalesOrdersByCustomer\n{\n QueryType = Normal;\n\n elements\n {\n dataitem(Customer; Customer)\n {\n column(No; \"No.\") { }\n column(Name; Name) { }\n dataitem(SalesHeader; \"Sales Header\")\n {\n DataItemLink = \"Sell-to Customer No.\" = Customer.\"No.\";\n DataItemTableFilter = \"Document Type\" = const(Order);\n column(OrderCount) { Method = Count; }\n }\n }\n }\n}"} | ||
| {"instance_id": "dataquery__opportunity-count-by-status-1", "environment_setup_version": "29.0", "created_at": "2026-07-10", "metadata": {"area": "crm"}, "nl_prompt": "Return the number of CRM opportunities in each status. Group the opportunities by their Status field and, for each status value that occurs, output the status and the count of opportunities with that status.", "ordered": false, "gold_query": "query 50100 OpportunityCountByStatus\n{\n QueryType = Normal;\n\n elements\n {\n dataitem(Opportunity; Opportunity)\n {\n column(Status; Status) { }\n column(OpportunityCount) { Method = Count; }\n }\n }\n}"} | ||
| {"instance_id": "dataquery__customer-count-by-country-1", "environment_setup_version": "29.0", "created_at": "2026-07-28", "metadata": {"area": "sales"}, "nl_prompt": "Group all customers by their Country/Region Code and return, for each distinct code, the Country/Region Code and the number of customers that have it. Include customers whose Country/Region Code is blank as their own group. Count the Customer records (one row per distinct Country/Region Code).", "ordered": false, "gold_query": "query 50100 CustomerCountByCountry\n{\n QueryType = Normal;\n\n elements\n {\n dataitem(Customer; Customer)\n {\n column(CountryRegionCode; \"Country/Region Code\") { }\n column(CustomerCount) { Method = Count; }\n }\n }\n}"} | ||
| {"instance_id": "dataquery__outstanding-purchase-value-by-vendor-1", "environment_setup_version": "29.0", "created_at": "2026-07-28", "metadata": {"area": "purchase"}, "nl_prompt": "For each vendor that has at least one open purchase order line, return the vendor's number, the vendor's name, and their total outstanding amount. Use open purchase order lines (Purchase Line records whose Document Type is Order) and sum the line 'Outstanding Amount' field (net of VAT). Vendors with no open purchase order lines must not appear.", "ordered": false, "gold_query": "query 50100 OutstandingPurchaseByVendor\n{\n QueryType = Normal;\n\n elements\n {\n dataitem(Vendor; Vendor)\n {\n column(No; \"No.\") { }\n column(Name; Name) { }\n dataitem(PurchaseLine; \"Purchase Line\")\n {\n DataItemLink = \"Buy-from Vendor No.\" = Vendor.\"No.\";\n DataItemTableFilter = \"Document Type\" = const(Order);\n column(TotalOutstanding; \"Outstanding Amount\") { Method = Sum; }\n }\n }\n }\n}"} | ||
| {"instance_id": "dataquery__total-posted-sales-amount-by-customer-1", "environment_setup_version": "29.0", "created_at": "2026-07-28", "metadata": {"area": "sales"}, "nl_prompt": "For each customer that has at least one posted sales invoice line, return the customer's number, the customer's name, and their total posted sales amount. Join posted sales invoice headers to their lines via Document No., group by the header's Bill-to Customer No., and sum the line 'Amount' field (net of VAT). Customers with no posted sales invoice lines must not appear.", "ordered": false, "gold_query": "query 50100 PostedSalesAmountByCustomer\n{\n QueryType = Normal;\n\n elements\n {\n dataitem(Customer; Customer)\n {\n column(No; \"No.\") { }\n column(Name; Name) { }\n dataitem(SalesInvoiceHeader; \"Sales Invoice Header\")\n {\n DataItemLink = \"Bill-to Customer No.\" = Customer.\"No.\";\n dataitem(SalesInvoiceLine; \"Sales Invoice Line\")\n {\n DataItemLink = \"Document No.\" = SalesInvoiceHeader.\"No.\";\n column(TotalAmount; Amount) { Method = Sum; }\n }\n }\n }\n }\n}"} | ||
| {"instance_id": "dataquery__line-count-per-open-sales-order-1", "environment_setup_version": "29.0", "created_at": "2026-07-28", "metadata": {"area": "sales"}, "nl_prompt": "For each open sales order, return the order's document number and the number of lines it has. Use Sales Line records whose Document Type is Order, group them by Document No., and count the lines per order (one row per order document number).", "ordered": false, "gold_query": "query 50100 LineCountPerOpenSalesOrder\n{\n QueryType = Normal;\n\n elements\n {\n dataitem(SalesLine; \"Sales Line\")\n {\n DataItemTableFilter = \"Document Type\" = const(Order);\n column(DocumentNo; \"Document No.\") { }\n column(LineCount) { Method = Count; }\n }\n }\n}"} | ||
| {"instance_id": "dataquery__total-purchased-quantity-by-item-1", "environment_setup_version": "29.0", "created_at": "2026-07-28", "metadata": {"area": "purchase"}, "nl_prompt": "Across all posted purchase invoice lines whose Type is Item, return each item's number together with the total purchased quantity (the sum of the line Quantity). Produce one row per item number.", "ordered": false, "gold_query": "query 50100 PurchasedQuantityByItem\n{\n QueryType = Normal;\n\n elements\n {\n dataitem(PurchInvLine; \"Purch. Inv. Line\")\n {\n DataItemTableFilter = Type = const(Item);\n column(ItemNo; \"No.\") { }\n column(TotalQuantity; Quantity) { Method = Sum; }\n }\n }\n}"} |
|
onbuyuka marked this conversation as resolved.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,111 @@ | ||
| --- | ||
| layout: default | ||
| title: Data Query - BC-Bench | ||
| --- | ||
|
|
||
| # Data Query | ||
|
|
||
| This category evaluates an **agent harness and model (or MCP Host)** on its ability to **retrieve data | ||
| from a live Business Central environment** to answer a natural-language data question. It is | ||
| **execution-based** (no LLM judge): the agent reports the rows it retrieved (`answer.json`), and the | ||
| run is **resolved** when those rows match the result set of a hidden gold AL query run against the same | ||
| Cronus/Contoso demo data (values compared normalized; order ignored unless the entry is `ordered`). | ||
|
|
||
| The point of the category is to compare **how the data is retrieved**: | ||
|
|
||
| - **Baseline** — no data tooling. The agent has to reach the answer on its own (e.g. authoring an AL | ||
| query from knowledge of the schema), which is hard on a low-resource domain language. | ||
| - **BC MCP experiment** — the agent is given Business Central's **Data Query MCP tools** | ||
| (`bc_data_find_tables`, `bc_data_get_table_schema`, `bc_data_get_table_relations`, `bc_data_query`) | ||
| so it can discover tables, inspect schemas and relations, and compile/run read-only AL queries | ||
| against the live environment. The agent is isolated so the MCP endpoint is its only route to the | ||
| data, which keeps the comparison honest. | ||
|
|
||
| ## Baseline Leaderboard | ||
|
|
||
| {% if site.data.data-query.aggregate %} | ||
| <table> | ||
| <thead> | ||
| <tr> | ||
| <th>Agent</th> | ||
| <th>Model</th> | ||
| <th>mean (95% CI)</th> | ||
| <th>pass^5</th> | ||
| <th>Avg Time</th> | ||
| <th>Version</th> | ||
| </tr> | ||
| </thead> | ||
| <tbody> | ||
| {% assign sorted_results = site.data.data-query.aggregate | sort: "average" | reverse %} | ||
| {% for agg in sorted_results %} | ||
| {% if agg.experiment == null %} | ||
| <tr> | ||
| <td>{{ agg.agent_name }}</td> | ||
| <td>{{ agg.model }}</td> | ||
| <td>{{ agg.average | times: 100.0 | round: 1 }}%{% if agg.ci_low %} ({{ agg.ci_low | times: 100.0 | round: 1 }}-{{ agg.ci_high | times: 100.0 | round: 1 }}%){% endif %}</td> | ||
| <td>{% if agg.pass_hat_5 %}{{ agg.pass_hat_5 | times: 100.0 | round: 1 }}%{% endif %}</td> | ||
| <td>{{ agg.average_duration | round: 1 }}s</td> | ||
| <td><a href="https://github.com/microsoft/BC-Bench/releases/tag/v{{ agg.benchmark_version }}" target="_blank">{{ agg.benchmark_version }}</a></td> | ||
| </tr> | ||
| {% endif %} | ||
| {% endfor %} | ||
| </tbody> | ||
| </table> | ||
| {% else %} | ||
| <p><em>No results available yet. Check back soon!</em></p> | ||
| {% endif %} | ||
|
|
||
| ## BC MCP Experiment | ||
|
|
||
| Comparing runs that enable the **Business Central Data Query MCP tools** (`bc-mcp`) against the | ||
| matching no-tooling **Default** baseline for the same model. | ||
|
|
||
| {% if site.data.data-query.aggregate %} | ||
| {%- assign mcp_models = "" -%} | ||
| {%- for agg in site.data.data-query.aggregate -%} | ||
| {%- if agg.experiment and agg.experiment.mcp_servers.size > 0 -%} | ||
| {%- assign mcp_models = mcp_models | append: "|" | append: agg.model | append: "|" -%} | ||
| {%- endif -%} | ||
| {%- endfor -%} | ||
| <table> | ||
| <thead> | ||
| <tr> | ||
| <th>Model</th> | ||
| <th>MCP Servers</th> | ||
| <th>Skills</th> | ||
| <th>mean (95% CI)</th> | ||
| <th>pass^5</th> | ||
| <th>Avg Time</th> | ||
| <th>Ver</th> | ||
| </tr> | ||
| </thead> | ||
| <tbody> | ||
| {%- assign sorted_results = site.data.data-query.aggregate | sort: "average" | reverse -%} | ||
| {%- for agg in sorted_results -%} | ||
| {%- assign is_mcp = false -%} | ||
| {%- assign show_row = false -%} | ||
| {%- if agg.experiment -%} | ||
| {%- if agg.experiment.mcp_servers.size > 0 %}{% assign is_mcp = true %}{% assign show_row = true %}{% endif -%} | ||
| {%- else -%} | ||
| {%- assign model_key = agg.model | prepend: "|" | append: "|" -%} | ||
| {%- if mcp_models contains model_key %}{% assign show_row = true %}{% endif -%} | ||
| {%- endif -%} | ||
| {%- if show_row %} | ||
| <tr> | ||
| <td>{{ agg.model }}</td> | ||
| <td>{% if is_mcp %}{{ agg.experiment.mcp_servers | join: ", " }}{% else %}<em>Default</em>{% endif %}</td> | ||
| <td>{% if is_mcp and agg.experiment.skills_enabled %}✓{% else %}—{% endif %}</td> | ||
| <td>{{ agg.average | times: 100.0 | round: 1 }}%{% if agg.ci_low %} ({{ agg.ci_low | times: 100.0 | round: 1 }}-{{ agg.ci_high | times: 100.0 | round: 1 }}%){% endif %}</td> | ||
| <td>{% if agg.pass_hat_5 %}{{ agg.pass_hat_5 | times: 100.0 | round: 1 }}%{% endif %}</td> | ||
| <td>{{ agg.average_duration | round: 1 }}s</td> | ||
| <td><a href="https://github.com/microsoft/BC-Bench/releases/tag/v{{ agg.benchmark_version }}" target="_blank">{{ agg.benchmark_version }}</a></td> | ||
| </tr> | ||
| {%- endif -%} | ||
| {%- endfor %} | ||
| </tbody> | ||
| </table> | ||
| {% else %} | ||
| <p><em>No results available yet. Check back soon!</em></p> | ||
| {% endif %} | ||
|
|
||
| [← Back to Home](index.md) |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.