Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
99 commits
Select commit Hold shift + click to select a range
8285dac
llm-benchmark: add normalized table evals
bradleyshep Jul 17, 2026
e51e287
llm-benchmark: add behavioral table evals
bradleyshep Jul 17, 2026
ba223b3
llm-benchmark: add reducer behavior evals
bradleyshep Jul 17, 2026
0d3f74c
llm-benchmark: add scheduled batch deletion eval
bradleyshep Jul 17, 2026
fb2f938
llm-benchmark: add query builder view evals
bradleyshep Jul 17, 2026
644340e
llm-benchmark: add procedural view evals
bradleyshep Jul 17, 2026
2f647d6
llm-benchmark: complete view eval coverage
bradleyshep Jul 17, 2026
ba06e21
llm-benchmark: add auth and lifecycle evals
bradleyshep Jul 17, 2026
8c510ff
llm-benchmark: add procedure and http evals
bradleyshep Jul 17, 2026
ce87575
llm-benchmark: add migration-aware default eval
bradleyshep Jul 17, 2026
633f6d7
llm-benchmark: add migration and RLS evals
bradleyshep Jul 17, 2026
8106177
Support stable local LLM benchmark builds
bradleyshep Jul 17, 2026
6c3139f
Retry silent LLM benchmark publish failures
bradleyshep Jul 17, 2026
104fafd
Make local LLM benchmark logs collision proof
bradleyshep Jul 17, 2026
25c8b31
Include existing modules in migration eval prompts
bradleyshep Jul 17, 2026
401b206
Make advanced LLM evals self-contained
bradleyshep Jul 17, 2026
f49cc64
Document advanced TypeScript server APIs
bradleyshep Jul 17, 2026
43c0330
Validate LLM benchmark goldens behaviorally
bradleyshep Jul 17, 2026
6d06baa
Generalize advanced TypeScript server guidance
bradleyshep Jul 17, 2026
9a775e5
Clarify advanced view eval requirements
bradleyshep Jul 17, 2026
2f6bb29
Document TypeScript server API edge cases
bradleyshep Jul 17, 2026
b3f4bbc
Grade only specified HTTP response headers
bradleyshep Jul 17, 2026
4a084de
Bound LLM benchmark scorer calls
bradleyshep Jul 17, 2026
239ebbe
Document advanced Rust and C# server APIs
bradleyshep Jul 17, 2026
fd97271
Refine TypeScript eval guidance and prompts
bradleyshep Jul 17, 2026
87ee8c8
Align new eval prompts with benchmark format
bradleyshep Jul 17, 2026
5567d91
Retry transient HTTP eval calls
bradleyshep Jul 17, 2026
3c250b9
Clarify ambiguous eval task contracts
bradleyshep Jul 18, 2026
4a1252e
Fix reducer schema parity extraction
bradleyshep Jul 18, 2026
081432f
Strengthen view eval result grading
bradleyshep Jul 18, 2026
d39578f
Ignore reducer parameter names in schema parity
bradleyshep Jul 18, 2026
97ba8af
Clarify private table eval contracts
bradleyshep Jul 18, 2026
4af3acd
Clarify aggregate table visibility
bradleyshep Jul 18, 2026
7958c49
Allow complete benchmark module responses
bradleyshep Jul 18, 2026
2d00980
Avoid implicit response caps for optional providers
bradleyshep Jul 18, 2026
e040c8d
Clarify private schema and scheduled exports
bradleyshep Jul 18, 2026
ccf14de
Retry silent Rust compiler exits
bradleyshep Jul 18, 2026
d188c8f
Align Rust lifecycle prompt with golden schema
bradleyshep Jul 18, 2026
ef4b8b1
Retry diagnostic-free Cargo exits
bradleyshep Jul 18, 2026
9e7607f
Fix Rust external upload golden capture
bradleyshep Jul 18, 2026
c699171
Clarify Rust lifecycle and HTTP APIs
bradleyshep Jul 18, 2026
414e2b1
Generalize LLM eval guidance and strengthen checks
bradleyshep Jul 19, 2026
905249e
Clarify TypeScript eval guidance
bradleyshep Jul 19, 2026
3d21ee7
Document TypeScript module type contracts
bradleyshep Jul 19, 2026
f672791
Clarify Rust query and HTTP context contracts
bradleyshep Jul 20, 2026
5fb7784
Correct Rust procedural view guidance
bradleyshep Jul 20, 2026
d38f6d9
Correct Rust string accessor guidance
bradleyshep Jul 20, 2026
3dfba78
Clarify C# view and HTTP contracts
bradleyshep Jul 20, 2026
4b41dae
Clarify C# HTTP types and presence IDs
bradleyshep Jul 20, 2026
005a093
Retry transient dotnet linker failures
bradleyshep Jul 20, 2026
25e0b50
updates
bradleyshep Jul 20, 2026
62aa9e9
updates
bradleyshep Jul 20, 2026
5d63ebe
Update SKILL.md
bradleyshep Jul 20, 2026
5fe4011
Update StdbModule.csproj
bradleyshep Jul 20, 2026
35c5d42
Update templates.rs
bradleyshep Jul 21, 2026
6cb8317
Harden Rust LLM benchmark evaluation
bradleyshep Jul 21, 2026
6719f9d
fixes
bradleyshep Jul 21, 2026
b59d00d
Improve C# benchmark reliability
bradleyshep Jul 21, 2026
219d866
Clarify C# connection ID encoding eval
bradleyshep Jul 21, 2026
b58c1fa
Merge remote-tracking branch 'origin/bradley/fix-csharp-llm-bench' in…
bradleyshep Jul 21, 2026
e96a9fc
Harden LLM benchmark validation and cleanup
bradleyshep Jul 21, 2026
9bcdc72
Clarify HTTP APIs in server benchmark skills
bradleyshep Jul 22, 2026
5c5eb8f
Merge branch 'master' into bradley/new-llm-benchmark-evals-0717
bradleyshep Jul 22, 2026
438f71c
Clarify TypeScript benchmark guidance
bradleyshep Jul 22, 2026
5aac866
Harden benchmark regression tests
bradleyshep Jul 22, 2026
bdbeb96
Remove bias
bradleyshep Jul 22, 2026
21125cb
remove bias
bradleyshep Jul 22, 2026
a4a4a3a
cleanup
bradleyshep Jul 22, 2026
07ad955
Use workspace CLI for LLM benchmark runs
bradleyshep Jul 22, 2026
173fa66
Parallelize periodic benchmarks by language
bradleyshep Jul 23, 2026
02e1781
Merge branch 'master' into bradley/new-llm-benchmark-evals-0717
bradleyshep Jul 23, 2026
3483bd6
Keep parallel Rust benchmark builds isolated
bradleyshep Jul 23, 2026
30dbf5a
Refine TypeScript eval guidance and randomness scoring
bradleyshep Jul 23, 2026
3571338
updates
bradleyshep Jul 23, 2026
808baa4
Merge branch 'master' into bradley/new-llm-benchmark-evals-0717
bradleyshep Jul 23, 2026
5b46b13
lints
bradleyshep Jul 23, 2026
0db8781
analysis + post to discord
bradleyshep Jul 24, 2026
c3e0fb6
Update llm-benchmark-periodic.yml
bradleyshep Jul 24, 2026
c018a44
discord summary script
bradleyshep Jul 24, 2026
cf34623
Merge branch 'master' into bradley/new-llm-benchmark-evals-0717
bradleyshep Jul 27, 2026
7c265e5
reasoning arg
bradleyshep Jul 27, 2026
48f890c
Merge branch 'master' into bradley/llm-analysis-expansion
bradleyshep Jul 27, 2026
ef59cc2
polish
bradleyshep Jul 28, 2026
719d982
Merge branch 'master' into bradley/llm-analysis-expansion
bradleyshep Jul 28, 2026
8680f50
Merge branch 'master' into bradley/llm-analysis-expansion
bradleyshep Jul 29, 2026
bd29cbd
Merge branch 'master' into bradley/new-llm-benchmark-evals-0717
bradleyshep Jul 29, 2026
3e5666f
Allow benchmark runs to skip task catalog upload
bradleyshep Jul 29, 2026
177cba4
Update llm-benchmark-periodic.yml
bradleyshep Jul 31, 2026
e883388
Merge branch 'master' into bradley/llm-analysis-expansion
bradleyshep Aug 3, 2026
7241a05
Merge branch 'master' into bradley/new-llm-benchmark-evals-0717
bradleyshep Aug 6, 2026
872e06a
Remove rls test
bradleyshep Aug 6, 2026
d54a204
Merge branch 'bradley/new-llm-benchmark-evals-0717' into bradley/llm-…
bradleyshep Aug 6, 2026
c8844e2
Merge branch 'master' into bradley/llm-analysis-expansion
bradleyshep Aug 6, 2026
4aa99bd
Merge branch 'master' into bradley/llm-analysis-expansion
bradleyshep Aug 17, 2026
79231f3
update codex skills
bradleyshep Aug 14, 2026
79de192
Fix benchmark process timeouts
bradleyshep Aug 14, 2026
e5bbc64
Merge branch 'master' into bradley/llm-analysis-expansion
bradleyshep Aug 24, 2026
6eb6eac
Merge remote-tracking branch 'origin/master' into bradley/llm-analysi…
bradleyshep Aug 24, 2026
2ef6900
Merge branch 'master' into bradley/llm-analysis-expansion
bradleyshep Aug 28, 2026
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
96 changes: 95 additions & 1 deletion .github/workflows/llm-benchmark-periodic.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ on:
description: 'Run benchmarks without uploading results'
required: false
default: 'false'
skip_task_catalog_upload:
description: 'Skip uploading the benchmark task catalog'
required: false
type: boolean
default: false
post_discord:
description: 'Post the analysis summary to Discord'
required: false
type: boolean
default: false

permissions:
contents: read
Expand Down Expand Up @@ -166,14 +176,16 @@ jobs:
LLM_BENCH_CONCURRENCY: "8"
LLM_BENCH_RUST_CONCURRENCY: "4"
LLM_BENCH_CSHARP_CONCURRENCY: "2"
LLM_BENCH_ROUTE_CONCURRENCY: ${{ matrix.lang == 'typescript' && '4' || '2' }}
LLM_BENCH_ROUTE_CONCURRENCY: ${{ matrix.lang == 'typescript' && '4' || matrix.lang == 'csharp' && '1' || '2' }}
LLM_BENCHMARK_REPORT_DIR: ${{ runner.temp }}/llm-benchmark-reports
INPUT_LANGUAGE: ${{ matrix.lang }}
INPUT_MODEL_SET: ${{ inputs.model_set || 'website_active' }}
INPUT_MODELS: ${{ inputs.models || '' }}
INPUT_MODES: ${{ inputs.modes || 'guidelines,no_context' }}
INPUT_CATEGORIES: ${{ inputs.categories || '' }}
INPUT_TASKS: ${{ inputs.tasks || '' }}
INPUT_DRY_RUN: ${{ inputs.dry_run || 'false' }}
INPUT_SKIP_TASK_CATALOG_UPLOAD: ${{ inputs.skip_task_catalog_upload || 'false' }}
run: |
LANG="$INPUT_LANGUAGE"
MODEL_SET="$INPUT_MODEL_SET"
Expand All @@ -182,6 +194,7 @@ jobs:
CATEGORIES="$INPUT_CATEGORIES"
TASKS="$INPUT_TASKS"
DRY_RUN="$INPUT_DRY_RUN"
SKIP_TASK_CATALOG_UPLOAD="$INPUT_SKIP_TASK_CATALOG_UPLOAD"

case "$MODEL_SET" in
website_active)
Expand Down Expand Up @@ -219,6 +232,9 @@ jobs:
if [ "$DRY_RUN" = "true" ]; then
EXTRA_ARGS+=(--dry-run)
fi
if [ "$SKIP_TASK_CATALOG_UPLOAD" = "true" ]; then
EXTRA_ARGS+=(--skip-task-catalog-upload)
fi

if [ "$MODEL_SET" = "website_active" ]; then
llm_benchmark run --lang "$LANG" --modes "$MODES" --model-source remote "${EXTRA_ARGS[@]}"
Expand All @@ -227,3 +243,81 @@ jobs:
else
llm_benchmark run --lang "$LANG" --modes "$MODES" --models "${MODEL_ARGS[@]}" "${EXTRA_ARGS[@]}"
fi

- name: Upload analysis reports
if: always()
uses: actions/upload-artifact@v4
with:
name: llm-benchmark-analysis-${{ matrix.lang }}
path: ${{ runner.temp }}/llm-benchmark-reports
if-no-files-found: ignore
retention-days: 14

summarize:
name: Summarize benchmark analysis
if: always()
needs: run-benchmarks
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Test Discord formatter
run: python3 tools/xtask-llm-benchmark/scripts/test_discord_summary.py

- name: Download analysis reports
continue-on-error: true
uses: actions/download-artifact@v4
with:
pattern: llm-benchmark-analysis-*
path: reports
merge-multiple: true

- name: Publish workflow summary
run: |
echo "# LLM benchmark analysis" >> "$GITHUB_STEP_SUMMARY"
echo >> "$GITHUB_STEP_SUMMARY"

if [ ! -d reports ] || ! find reports -type f -name '*.md' -print -quit | grep -q .; then
echo "No analysis reports were produced." >> "$GITHUB_STEP_SUMMARY"
exit 0
fi

echo "Full per-model reports are available in the workflow artifacts." >> "$GITHUB_STEP_SUMMARY"
echo >> "$GITHUB_STEP_SUMMARY"

find reports -type f -name '*.md' -print | sort | while IFS= read -r report; do
awk '/^## Failure patterns/{exit} NR <= 80 {print}' "$report" >> "$GITHUB_STEP_SUMMARY"
echo >> "$GITHUB_STEP_SUMMARY"
echo "---" >> "$GITHUB_STEP_SUMMARY"
echo >> "$GITHUB_STEP_SUMMARY"
done

- name: Prepare Discord summary
if: github.event_name == 'schedule' || inputs.post_discord
env:
RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
RUN_LABEL: ${{ github.event_name == 'schedule' && 'Weekly scheduled run' || 'Manual run' }}
run: >
python3 tools/xtask-llm-benchmark/scripts/discord_summary.py
--reports-dir reports
--run-url "$RUN_URL"
--run-label "$RUN_LABEL"
--output discord-payload.json

- name: Post Discord summary
if: github.event_name == 'schedule' || inputs.post_discord
continue-on-error: true
env:
DISCORD_WEBHOOK_URL: ${{ secrets.LLM_BENCHMARK_DISCORD_WEBHOOK_URL }}
run: |
if [ -z "$DISCORD_WEBHOOK_URL" ]; then
echo "::warning::LLM_BENCHMARK_DISCORD_WEBHOOK_URL is not configured"
exit 0
fi

curl --fail --silent --show-error \
--header 'Content-Type: application/json' \
--data-binary @discord-payload.json \
"$DISCORD_WEBHOOK_URL"
173 changes: 173 additions & 0 deletions tools/xtask-llm-benchmark/scripts/discord_summary.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
#!/usr/bin/env python3

import argparse
import json
import re
from collections import defaultdict
from dataclasses import dataclass
from pathlib import Path

GREEN = 0x57F287
YELLOW = 0xFEE75C
RED = 0xED4245
MAX_ITEMS = 5
MAX_FIELD_LENGTH = 1000


@dataclass
class Report:
language: str
mode: str
model: str
passed_tasks: int
total_tasks: int
actions: list[str]
other_findings: list[str]


def field(text: str, name: str) -> str:
match = re.search(rf"^- {re.escape(name)}: (.+)$", text, re.MULTILINE)
return match.group(1) if match else "unknown"


def language_name(language: str) -> str:
return {
"csharp": "C#",
"rust": "Rust",
"typescript": "TypeScript",
}.get(language, language)


def parse_report(text: str) -> Report:
language = field(text, "Language")
mode = field(text, "Mode")
model = field(text, "Model")
task_match = re.match(r"(\d+)/(\d+)", field(text, "Tasks"))
passed_tasks, total_tasks = map(int, task_match.groups()) if task_match else (0, 0)

actions = []
recommended = text.partition("## Recommended actions")[2].partition("## Failure patterns")[0]
for line in recommended.splitlines():
if line.startswith("- **["):
actions.append(f"- {language_name(language)} / {mode}: {line[2:]}")

other_findings = []
failures = text.partition("## Failure patterns")[2]
for section in re.split(r"^### ", failures, flags=re.MULTILINE)[1:]:
heading = section.splitlines()[0]
title_match = re.match(r"(.+) \(\d+ tasks?\)$", heading)
title = title_match.group(1) if title_match else heading
classification = re.search(r"^- \*\*Classification:\*\* (.+)$", section, re.MULTILINE)
if classification and classification.group(1) in {
"Model limitation",
"Infrastructure/provider problem",
"No action",
}:
other_findings.append(
f"- {language_name(language)} / {mode} / {model}: {title} - {classification.group(1)}"
)

return Report(language, mode, model, passed_tasks, total_tasks, actions, other_findings)


def load_reports(reports_dir: Path) -> list[Report]:
return [
parse_report(path.read_text(encoding="utf-8"))
for path in sorted(reports_dir.rglob("*.md"))
]


def rate(passed: int, total: int) -> str:
percent = passed * 100 / total if total else 0
return f"{passed}/{total} ({percent:.1f}%)"


def field_value(lines: list[str], empty: str, overflow_label: str) -> str:
unique = list(dict.fromkeys(lines))
visible = unique[:MAX_ITEMS] or [empty]
if len(unique) > MAX_ITEMS:
visible.append(f"- ...and {len(unique) - MAX_ITEMS} more {overflow_label}(s)")
value = "\n".join(visible)
if len(value) > MAX_FIELD_LENGTH:
return value[: MAX_FIELD_LENGTH - 30].rstrip() + "\n... View the full analysis."
return value


def build_payload(reports: list[Report], run_url: str, run_label: str) -> dict:
totals = [0, 0]
by_language: dict[str, list[int]] = defaultdict(lambda: [0, 0])
actions = []
other_findings = []

for report in reports:
totals[0] += report.passed_tasks
totals[1] += report.total_tasks
by_language[report.language][0] += report.passed_tasks
by_language[report.language][1] += report.total_tasks
actions.extend(report.actions)
other_findings.extend(report.other_findings)

passed, total = totals
pass_percent = passed * 100 / total if total else 0
has_infrastructure_failure = any("Infrastructure/provider problem" in item for item in other_findings)
if not total or pass_percent < 90 or has_infrastructure_failure:
color = RED
elif actions or pass_percent < 95:
color = YELLOW
else:
color = GREEN

language_rates = [
f"- **{language_name(language)}:** {rate(*counts)}"
for language, counts in sorted(by_language.items())
]
return {
"username": "SpacetimeDB LLM Benchmarks",
"allowed_mentions": {"parse": []},
"embeds": [
{
"title": "LLM Benchmark Analysis",
"url": run_url,
"description": f"**{rate(*totals)}** task runs passed",
"color": color,
"fields": [
{
"name": "By language",
"value": field_value(
language_rates,
"No analysis reports were produced.",
"language",
),
"inline": False,
},
{
"name": "Action items",
"value": field_value(actions, "None", "action"),
"inline": False,
},
{
"name": "Other failures",
"value": field_value(other_findings, "None", "finding"),
"inline": False,
},
],
"footer": {"text": run_label},
}
],
}


def main() -> None:
parser = argparse.ArgumentParser(description="Build a Discord embed from LLM benchmark analysis reports.")
parser.add_argument("--reports-dir", type=Path, required=True)
parser.add_argument("--run-url", required=True)
parser.add_argument("--run-label", required=True)
parser.add_argument("--output", type=Path, required=True)
args = parser.parse_args()

payload = build_payload(load_reports(args.reports_dir), args.run_url, args.run_label)
args.output.write_text(json.dumps(payload, ensure_ascii=False), encoding="utf-8")


if __name__ == "__main__":
main()
Loading
Loading