Standalone evaluation framework for testing Betterworks AI features and guardrails against golden datasets. Pulls prompts dynamically from GitHub and evaluates against versioned test datasets.
Prompts sourced from Betterworks/llm-engine. Outputs raw LLM responses for manual or downstream review.
| Feature | Dataset | Version | Test Cases |
|---|---|---|---|
| Writing Assistant | writing_assistant |
v1.2 | 150 |
| Goal Assist | goal_assist |
v1.0 | 1 |
| Feedback Summary | feedback_summary |
v1.0 | 2 |
| Performance Summary | performance_summary |
v1.1 | 100 |
| Meetings Summary | meetings_summary |
v1.0 | 100 |
| Skills Discovery | skills_discovery |
v1.1 | 100 |
Prompts sourced from Betterworks/llm-proxy. Evaluates safety, PII, and bias guardrails and computes classification metrics against ground-truth labels.
| Guardrail | Type | Dataset | Version | Test Cases |
|---|---|---|---|---|
| Safety | Blocking (error) | guardrails |
v1.1 | 150 |
| PII | Blocking (error) / Silent | guardrails |
v1.1 | 150 |
| Bias | Non-blocking (warning) | guardrails |
v1.1 | 150 |
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txtcp .env.example .envEdit .env with your credentials (see Configuration below).
python cli.py test-connection
python cli.py config-check# Feature eval (5 writing assistant cases)
python cli.py run --feature writing_assistant --max-cases 5
# Guardrail eval (5 cases)
python cli.py run-guardrails --max-cases 5Collect LLM responses for an AI feature.
python cli.py run [OPTIONS]
Options:
--feature TEXT writing_assistant | goal_assist | feedback_summary |
performance_summary | meetings_summary | skills_discovery
--dataset-version TEXT Override dataset version (default: per-feature from config)
--max-cases INTEGER Limit test cases (useful for smoke tests)
--max-concurrent INT Parallel requests (default: 3)
--output-dir PATH Custom output directory
-v, --verbose Debug loggingExamples:
# Quick test
python cli.py run --feature writing_assistant --max-cases 5
# Full run with higher concurrency
python cli.py run --feature performance_summary --max-concurrent 5
# Specific dataset version
python cli.py run --feature skills_discovery --dataset-version 1.0Evaluate safety, PII, and bias guardrails against the golden dataset. Each test case runs all requested guardrail types in parallel, then computes classification metrics.
python cli.py run-guardrails [OPTIONS]
Options:
--dataset-version TEXT Override dataset version (default: 1.1)
--max-cases INTEGER Limit test cases
--max-concurrent INT Concurrent test cases (each spawns up to 3 parallel
guardrail calls internally)
--output-dir PATH Custom output directory
--force-refresh-prompts Re-fetch guardrail prompts from llm-proxy (bypass cache)
-v, --verbose Debug loggingExamples:
# Smoke test
python cli.py run-guardrails --max-cases 5
# Full evaluation
python cli.py run-guardrails
# Force fresh prompts from llm-proxy
python cli.py run-guardrails --force-refresh-promptspython cli.py dataset-info --feature writing_assistant
python cli.py dataset-info --feature performance_summary --dataset-version 1.1python cli.py test-connectionpython cli.py config-checkEach run creates a timestamped directory under artifacts/<feature>/run_<timestamp>/:
artifacts/writing_assistant/run_20260507_120000/
├── responses.jsonl # Full response data (one JSON object per line)
├── responses.csv # Tabular format (14 columns, no text truncation)
└── responses.md # Markdown summary with response table
CSV columns: test_number, test_id, feature, category, input_text, input_text_length, llm_output, output_length, model, execution_time_seconds, ai_response_time_seconds, timestamp, success, error_message
Each run creates a timestamped directory under artifacts/guardrails/run_<timestamp>/:
artifacts/guardrails/run_20260507_120000/
├── responses.jsonl # Full per-case data including per-guardrail raw responses
├── responses.csv # Tabular format with per-type triggered/category columns
├── responses.md # Markdown summary (predicted vs expected outcome per case)
├── metrics.json # Full classification metrics (machine-readable)
└── metrics.md # Classification metrics report (human-readable)
Guardrail CSV columns: test_number, test_id, direction, context_type, guardrails_requested, input_text, input_text_length, predicted_outcome, expected_outcome, outcome_correct, category_correct, expected_triggered_categories, safety_raw, safety_triggered, safety_category, pii_raw, pii_triggered, pii_category, bias_raw, bias_triggered, bias_category, difficulty, locale, edge_case_type, model, execution_time_seconds, timestamp, error_message
Metrics reported:
- Overall: outcome accuracy, category accuracy, precision, recall, F1, FPR (over-block rate), FNR (miss rate), TP/FP/TN/FN
- Per guardrail type: same metrics for safety / pii / bias independently
- Breakdowns by: difficulty, locale, context type
- Edge cases: separate accuracy for the 20 boundary-condition cases per split
All settings via .env file or environment variables.
GITHUB_TOKEN=<token> # Personal access token (recommended to avoid rate limits)
GITHUB_ORG=Betterworks # Default: Betterworks
LLMENGINE_REPO=llm-engine # Prompt source for AI features
LLM_PROXY_REPO=llm-proxy # Prompt source for guardrails
GOLDEN_DATASETS_REPO=llm-golden-datasets # Test dataset sourceENDPOINT_URL=http://10.50.21.45:8000/v1/chat/completions
ENDPOINT_MODEL=google/gemma-4-31B-itMAX_CONCURRENT=3 # Parallel test cases (default: 3)
CACHE_DIR=.cache # Local cache for datasets and prompts (default: .cache)
CACHE_ENABLED=true # Toggle caching (default: true)
OUTPUT_DIR=artifacts # Results output directory (default: artifacts)DATASET_VERSION_WRITING_ASSISTANT=1.2
DATASET_VERSION_GOAL_ASSIST=1.0
DATASET_VERSION_FEEDBACK_SUMMARY=1.0
DATASET_VERSION_PERFORMANCE_SUMMARY=1.1
DATASET_VERSION_MEETINGS_SUMMARY=1.0
DATASET_VERSION_SKILLS_DISCOVERY=1.1
DATASET_VERSION_GUARDRAILS=1.1The framework caches datasets and prompts locally in .cache/ to speed up repeated runs and reduce GitHub API calls.
.cache/
├── datasets/ # Cached golden datasets
│ ├── writing_assistant/
│ ├── goal_assist/
│ ├── feedback_summary/
│ ├── performance_summary/
│ ├── meetings_summary/
│ ├── skills_discovery/
│ └── guardrails/
└── prompts/ # Cached prompts from llm-engine/llm-proxy
├── writing_assistant/
├── goals_assistant/
├── feedback_summary/
├── performance_summary/
├── meetings_summary/
├── skill/
└── guardrails/
# Clear all caches (datasets + prompts)
rm -rf .cache
# Clear only prompts cache (force fresh prompt download)
rm -rf .cache/prompts
# Clear only datasets cache (force fresh dataset download)
rm -rf .cache/datasets
# Clear specific feature prompt cache
rm -rf .cache/prompts/writing_assistant
# Clear guardrail prompts cache
rm -rf .cache/prompts/guardrails- Prompts updated in llm-engine/llm-proxy — Clear prompts cache to get latest versions
- Dataset version changed — Cache is version-specific, so switching versions auto-fetches new data
- Debugging unexpected responses — Clear prompts cache to ensure you have latest instructions
- Multi-language support issues — Clear prompts cache to refresh locale-aware prompt templates
- Disk space concerns — Typical cache is ~5MB datasets + ~50KB prompts
# Force refresh guardrail prompts without clearing cache file
python cli.py run-guardrails --force-refresh-prompts
# Disable caching for a single run (set in .env)
CACHE_ENABLED=false python cli.py run --feature writing_assistanttest-llm-eval/
├── config.py # Pydantic configuration (env vars)
├── cli.py # Click CLI (run, run-guardrails, dataset-info, ...)
├── src/
│ ├── dataset_loader.py # Load datasets from llm-golden-datasets (+ GuardrailTestCase)
│ ├── prompt_loader.py # Fetch feature prompts from llm-engine
│ ├── guardrail_prompt_loader.py # Fetch guardrail prompts from llm-proxy
│ ├── prompt_builder.py # Feature-specific prompt builders
│ ├── factory.py # PromptBuilderFactory
│ ├── endpoint_client.py # OpenAI-compatible HTTP client with retry
│ ├── runner_simple.py # Feature eval runner (response collection)
│ ├── runner_guardrails.py # Guardrail eval runner (classification + metrics)
│ └── utils.py # Shared utilities
├── .cache/ # Cached datasets and prompts (git-ignored)
│ ├── datasets/ # Per-feature/version JSONL caches
│ └── prompts/ # Per-feature prompt file caches
│ └── guardrails/ # Guardrail prompt caches (safety/pii/bias)
└── artifacts/ # Evaluation outputs (git-ignored)
AI features — fetched from Betterworks/llm-engine:
app/prompt/writing_assistant/meta.pyapp/prompt/goals_assistant/meta.pyapp/prompt/feedback_summary/meta.pyapp/prompt/performance_summary/meta.pyapp/prompt/meetings_summary/meta.pyapp/prompt/skill/{conversation,feedback,job_description,job_title}/meta.py
Guardrails — fetched from Betterworks/llm-proxy:
src/api/prompts/selfcheck.py→INPUT_CHECK_PROMPT,OUTPUT_CHECK_PROMPTsrc/api/prompts/pii.py→PII_DETECTION_PROMPTsrc/api/prompts/bias.py→BIAS_DETECTION_PROMPT
All prompts are cached in .cache/prompts/ after the first fetch.
test_case.prompt (JSON)
→ parse: {text, guardrails_requested, direction, context_type}
→ parallel LLM calls (one per requested guardrail, via asyncio.to_thread)
safety call → "unsafe: <Category> - <Reason>" | "safe: ..."
pii call → "PII: <Category> - <Explanation>" | "Safe: ..."
bias call → "bias: <Category> - <Explanation>" | "no-bias: ..."
→ combine: error > warning > clean (silent PII → clean)
→ score vs test_case.response (ground truth)
→ aggregate classification metrics
GitHub rate limit errors — Set GITHUB_TOKEN in .env.
Prompt not found — If a guardrail prompt constant name changed in llm-proxy, clear the guardrail prompts cache:
rm -rf .cache/prompts/guardrails
python cli.py run-guardrails --force-refresh-prompts --max-cases 1 --verboseCache stale or outdated prompts — See the Cache Management section for detailed cache clearing commands.
Multi-language responses defaulting to English — Ensure prompts cache is cleared to get latest locale-aware templates:
rm -rf .cache/promptsEndpoint unreachable — Check VPN/network access to the endpoint:
python cli.py test-connectionVerbose logging — Add -v to any command for debug output:
python cli.py run-guardrails --max-cases 5 -v