Getting started — scoring_app
Choose the flow that matches your entry point. All three flows share the same HTTP API; only the host and auth requirements differ.
Install and run
From scoring_app/:
uv sync
uv run python scoring_service/main.py
This starts the FastAPI service on 127.0.0.1:8001 by default. Confirm it is
up:
curl -s http://127.0.0.1:8001/health
# {"status": "ok"}
No connection profile is required to start the service or to run routing-only scoring.
Environment variables
The service reads scoring_service/.env.scoring_service and then
scoring_service/.env.scoring_service.local (both optional; the first value
found for a given variable wins, so the .local file is for overrides that
should not be committed).
| Variable | Default | Purpose |
|---|---|---|
OFFLINE_SCORING_HOST |
127.0.0.1 |
Bind host |
OFFLINE_SCORING_PORT |
8001 |
Bind port |
OFFLINE_SCORING_RELOAD |
false |
Enable uvicorn auto-reload for local development |
OFFLINE_SCORING_DEFAULT_MODEL |
OFFLINE_EVAL_DEEPEVAL_MODEL, or gpt-4.1 |
Default evaluator model label used when a metric and its connection profile do not name one |
CALLER_KEY_VAULT_URL |
(unset) | Key Vault whose scoring-caller-<caller-id> secrets are the caller keys, for example https://semantic-eval-dev-kv.vault.azure.net/. Unset leaves every route open and identifies no caller, with a startup warning |
OFFLINE_SCORING_STATE_DIR |
scoring_service/state/ |
Directory holding connection_profiles.json, pending custom-rubric submissions, and scoring-job records |
OFFLINE_SCORING_MAX_CONCURRENT_JUDGE_CALLS |
4 |
How many judge calls one scoring job runs concurrently |
Evaluator provider secrets (API keys) are never read from environment variables — they are registered through the HTTP API. See 02-connection-profiles.md.
First scoring request
Routing-only scoring needs no connection profile. This example submits one
item as a scoring job and polls it to completion, using the
builtin/ExactMatch evaluator, which compares
expected_output["handoff_to"] against prediction["handoff_to"]:
curl -s -X POST http://127.0.0.1:8001/v1/score-jobs \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"item_id": "item-1",
"scope": "routing",
"dataset_name": "getting-started-demo",
"input": {"query": "Reset my password"},
"expected_output": {"handoff_to": "account_support"},
"metadata": {}
}
],
"predictions": [
{
"item_id": "item-1",
"scope": "routing",
"dataset_name": "getting-started-demo",
"predictor_name": "demo-predictor",
"input": {"query": "Reset my password"},
"expected_output": {"handoff_to": "account_support"},
"prediction": {"handoff_to": "account_support"}
}
],
"evaluators": {
"routing": {
"metrics": {
"handoff_correct": {
"framework": "builtin",
"evaluator": "ExactMatch",
"threshold": 3.0
}
}
}
}
}'
# {"job_id": "…", "status": "pending"}
Poll the job with the returned job_id:
curl -s http://127.0.0.1:8001/v1/score-jobs/<job_id>
A deterministic, routing-only job like this one finishes almost
immediately, so the very next poll typically already shows
"status": "completed" with a result carrying metric_results (one per
item per metric), aggregate_results (one per scope/metric pair), and
gate_result (the pass/fail decision for the whole run). See
04-api-reference.md for the full
request and response shape.
In real usage, pods do not build this payload or poll by hand — the SDK's
ScoringServiceClient.score() and the offline-evaluation runner in
sdk/semantic_evaluation/ construct the request from a run manifest and
predictor output, then submit and poll for you.
Install and run
From scoring_app/:
uv sync
uv run python scoring_service/main.py
Registering a connection profile
End-to-end LLM-judge metrics (deepeval, azure_ai_evaluation, or
custom_rubric) need a registered connection profile and a backend
assignment before the first request that uses them:
curl -s -X POST http://127.0.0.1:8001/v1/connection-profiles \
-H "Content-Type: application/json" \
-d '{
"name": "azure-default",
"kind": "azure_openai",
"endpoint": "https://your-resource.openai.azure.com/",
"api_key": "replace-me",
"deployment": "gpt-4.1",
"api_version": "2024-12-01-preview"
}'
curl -s -X PUT http://127.0.0.1:8001/v1/evaluator-backends/deepeval_llm_judge/connection-profile \
-H "Content-Type: application/json" \
-d '{"connection_profile_name": "azure-default"}'
api_key may be a literal string (as above, for local development) or an
Azure Key Vault secret URI; the service resolves the URI at evaluation time
via managed identity. A local service has no CALLER_KEY_VAULT_URL, so
neither call needs a key.
See 02-connection-profiles.md for the full
model, including what happens when a backend has no assignment, and the
backend key names for azure_ai_evaluation and custom_rubric.
Access requirements
scoring-service runs in semantic-eval-nonprod-env (Sweden Central). A
VPN connection is required to reach it from a developer machine — confirmed
reachable at the hostname below once connected (the .internal.-prefixed
variant of this hostname does not resolve correctly from outside the VNet
and should not be used).
FQDN:
scoring-service.bravecliff-5469885d.swedencentral.azurecontainerapps.io
The first request after the service has been idle can take 10-15 seconds (cold start) before it responds — this is normal, not a failure.
Authentication
Every caller has its own caller key, issued by the
platform team, and sends it as the X-API-Key header; pods set it as
SCORING_SERVICE_API_KEY. The profile, evaluator-backend and
evaluator-request routes need a known key, and the write routes
(registering or deleting a profile, assigning a backend, listing pending
evaluator submissions) need the platform-admin caller's. GET /health and
GET /v1/evaluators need none. The scoring endpoints
(POST /v1/score-jobs, GET /v1/score-jobs/{job_id}) identify a known
caller and still accept a call without a key during a warning phase, so send
your key from the start. The full route table is in the
API reference.
Health check
curl -s "https://scoring-service.bravecliff-5469885d.swedencentral.azurecontainerapps.io/health"
# {"status": "ok"}
Connection profiles on the deployed service
The deployed service persists connection profiles to the shared Azure Files
volume (/mnt/state/connection_profiles.json), so profiles survive restarts
and redeploys. The platform team registers them with the platform-admin
caller's key, the same way as locally, using the deployed FQDN and including
the API key header. A pod only reads them (GET /v1/connection-profiles,
GET /v1/evaluator-backends):
curl -s -X POST \
"https://scoring-service.bravecliff-5469885d.swedencentral.azurecontainerapps.io/v1/connection-profiles" \
-H "Content-Type: application/json" \
-H "X-API-Key: <platform-admin caller key>" \
-d '{
"name": "azure-default",
"kind": "azure_openai",
"endpoint": "https://your-resource.openai.azure.com/",
"api_key": "https://your-kv.vault.azure.net/secrets/your-secret",
"deployment": "gpt-4.1",
"api_version": "2024-12-01-preview"
}'
In production, api_key should be a Key Vault secret URI, not a literal
string — the service resolves it at evaluation time via managed identity.
Scoring via the SDK
The SDK's ScoringServiceClient (from sdk/semantic_evaluation/) accepts a
base_url argument pointing to either the local or the deployed service.
Pods running in the peered VNet can call the deployed service directly in
their offline-evaluation runs without any code change — only base_url and
the caller key differ between environments.
Where to go next
| File | What it covers |
|---|---|
| changelog.md | What a caller notices change, per deploy and API major |
| 00-scoring-overview.md | Architecture, boundary, module map |
| 01-evaluator-catalogue.md | Every supported (framework, evaluator) pair and score normalization |
| 02-connection-profiles.md | The full connection-profile and backend-assignment model |
| 03-custom-rubric-and-registration.md | Pod-defined rubrics, inline vs. named, the registration lifecycle |
| 03-decision-evaluator.md | Fixed-option Microsoft Decision-1 questions, score mappings, and result semantics |
| 04-api-reference.md | Every HTTP route |
| 05-testing.md | Test-suite layers and the dependency-upgrade workflow |
| 06-deployment.md | Container Apps infrastructure, CI/CD pipeline, secret management |