Skip to content

Connection profiles

LLM-judge evaluator connections are service-owned. Callers send metric configuration in a ScoreRequest, never provider credentials — the service resolves those from a named, registered scoring connection profile.

The profile model

scoring_service/scoring/profiles.py supports these profile kinds:

| Kind | Fields | Notes | |---|---| | azure_openai | name, endpoint, api_key, deployment, api_version | Azure OpenAI resource endpoint and API version | | foundry_decision | name, endpoint, api_key, deployment | Microsoft Decision-1's Foundry resource endpoint, ending in /providers/microsoft, or its /v1/systemone endpoint; it has no Azure OpenAI API version | | foundry_openai_v1 | name, endpoint, api_key, deployment | Foundry's OpenAI-compatible v1 base endpoint, ending in /openai/v1; /responses and /chat/completions are also accepted and normalized to the base endpoint |

api_key accepts a secret URI of the form https://{vault}.vault.azure.net/secrets/{name}. The service resolves that URI to a real key at evaluation time using azure-identity's DefaultAzureCredential (managed identity in Azure, developer credentials locally). No read route returns api_key, whether it was registered as a literal or a Key Vault URI.

Decision profiles use the same API-key or Key Vault URI behavior as Azure OpenAI profiles. They do not support Entra ID authentication in this release.

Registering a profile

Profiles are registered via POST /v1/connection-profiles and persisted to a JSON state file (connection_profiles.json inside OFFLINE_SCORING_STATE_DIR, scoring_service/state/ by default) so they survive service restarts. On the deployed service only the platform-admin caller may register a profile (see Authentication); the commands below run against a local service, where every route is open:

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"
  }'

Registering a profile alone does not connect it to any metric — a backend assignment does that.

Registering a Decision profile

curl -s -X POST http://127.0.0.1:8001/v1/connection-profiles \
  -H "Content-Type: application/json" \
  -d '{
    "name": "decision-default",
    "kind": "foundry_decision",
    "endpoint": "https://your-resource.services.ai.azure.com/providers/microsoft",
    "api_key": "replace-me",
    "deployment": "Decision-1-DZ"
  }'

The evaluator accepts either endpoint form in the table. It appends /v1/systemone to the resource base and leaves a full System One endpoint unchanged.

Registering a Foundry OpenAI v1 profile

Use this profile for custom_rubric when the judge deployment is exposed by Foundry's OpenAI-compatible v1 API rather than the Azure OpenAI versioned API:

curl -s -X POST http://127.0.0.1:8001/v1/connection-profiles \
  -H "Content-Type: application/json" \
  -d '{
    "name": "foundry-judge-default",
    "kind": "foundry_openai_v1",
    "endpoint": "https://your-resource.services.ai.azure.com/openai/v1",
    "api_key": "replace-me",
    "deployment": "gpt-5.1-DZ"
  }'

The OpenAI SDK appends its operation path to the configured base endpoint. The service also accepts the /responses or /chat/completions URL copied from a provider sample and normalizes it back to the base endpoint before calling the SDK.

Backend assignment

Definition: backend assignment A backend assignment maps one evaluator backend key to one registered connection profile name. An evaluator backend can have at most one assignment; assigning a new profile replaces the previous one. See backend assignment.

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"}'

Needs the platform-admin caller's key.

curl -s -X PUT https://scoring-service.bravecliff-5469885d.swedencentral.azurecontainerapps.io/v1/evaluator-backends/deepeval_llm_judge/connection-profile \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $SCORING_SERVICE_API_KEY" \
  -d '{"connection_profile_name": "azure-default"}'

The path names the evaluator backend and the body names the profile, in the same direction the service resolves them. connection_profile_name must already be registered, or the request fails with HTTP 400. Assigning again replaces the previous assignment, so the call is safe to repeat.

Read the current assignments back with GET /v1/evaluator-backends, which returns a backend to profile-name map. A backend with no profile is simply absent from it.

Backend keys

Backend key Used by
deepeval_llm_judge Every deepeval catalogue entry
azure_ai_evaluation_llm_judge Every azure_ai_evaluation catalogue entry, including the statistical F1ScoreEvaluator
custom_rubric_llm_judge custom_rubric/inline and every accepted custom_rubric/{name} evaluator
decision_llm_judge decision_judge/inline

builtin and builtin_deterministic evaluators never resolve a backend — they do not appear in this table because they never need a connection profile.

Resolution behavior

graph TD
  A["POST /v1/score-jobs received"]
  B["build_evaluators checks all metrics before scoring"]
  C{"metric framework?"}
  D["builtin or builtin_deterministic"]
  E["deepeval, azure_ai_evaluation, custom_rubric, or decision_judge"]
  F{"backend key has an assignment?"}
  G["HTTP 400 - named backend has no profile assigned"]
  H["fetch registered profile from registry"]
  I{"api_key is a Key Vault URI?"}
  J["use api_key as a literal string"]
  K["resolve secret via DefaultAzureCredential at evaluation time"]
  L["build evaluator instance"]
  M["no connection profile needed"]

  A --> B
  B --> C
  C --> D
  C --> E
  D --> M
  M --> L
  E --> F
  F -- "no" --> G
  F -- "yes" --> H
  H --> I
  I -- "no" --> J
  I -- "yes" --> K
  J --> L
  K --> L

  style A fill:#e5f4fd,stroke:#0098eb,stroke-width:2px
  style G fill:#fce8e8,stroke:#cc4444,stroke-width:2px
  style L fill:#e7eeec,stroke:#125a40,stroke-width:2px
Scenario Behavior
Routing-only request (builtin) No profile required
builtin_deterministic metric No profile required
deepeval, azure_ai_evaluation, custom_rubric, or decision_judge metric with a missing backend assignment Reject the whole POST /v1/score-jobs submission with HTTP 400 before any evaluator runs and before any job is created, naming the missing backend
azure_ai_evaluation/F1ScoreEvaluator (statistical, no LLM call) Still requires azure_ai_evaluation_llm_judge to be assigned — the catalogue factory resolves the backend for every azure_ai_evaluation entry unconditionally, even though F1ScoreEvaluator's own constructor ignores the resulting credentials
Invalid or malformed state file at startup Service fails to start
Unknown backend name inside the state file at startup Service fails to start

The rejection happens for the whole request, not just the offending metric: build_evaluators() constructs every evaluator for every requested scope before any evaluation runs, so a missing backend assignment for one metric fails the batch before scoring begins.

Managing profiles

Route Purpose
GET /v1/connection-profiles List every registered profile, without their api_key
GET /v1/connection-profiles/{name} Fetch one profile by name, without its api_key (404 if unknown)
GET /v1/evaluator-backends Read back which profile each evaluator backend uses
DELETE /v1/connection-profiles/{name} Remove a profile; also clears any backend assignment that referenced it
BASE=http://127.0.0.1:8001          # or the deployed FQDN
AUTH="X-API-Key: $SCORING_SERVICE_API_KEY"   # your own caller key; unused locally

curl -s -H "$AUTH" "$BASE/v1/connection-profiles"
curl -s -H "$AUTH" "$BASE/v1/connection-profiles/azure-default"
curl -s -H "$AUTH" "$BASE/v1/evaluator-backends"
curl -s -X DELETE -H "$AUTH" "$BASE/v1/connection-profiles/azure-default"

No route ever returns an api_key. The read routes answer with the profile's name, kind, endpoint, deployment, and API version only. There is no way to retrieve a stored credential, which is deliberate: it is what makes storing a Key Vault URI worth doing.

Deleting a profile does not fail if the deleted profile was assigned to a backend — the assignment is silently removed, and the next request that needs that backend fails with the standard "no scoring connection profile is configured" error until a new profile is assigned.

Authentication

Every caller has its own caller key, sent as an X-API-Key: <value> header. The deployed service splits the profile and evaluator routes by what they do:

Route Who
POST /v1/connection-profiles, DELETE /v1/connection-profiles/{name}, PUT /v1/evaluator-backends/{backend}/connection-profile, GET /v1/evaluators/requests The platform-admin caller only (HTTP 403 for a pod key)
GET /v1/connection-profiles, GET /v1/connection-profiles/{name}, GET /v1/evaluator-backends, POST /v1/evaluators/requests Any known caller

Registering or deleting a profile and assigning a backend on the deployed service is therefore the platform team's job; a pod reads them. What a profile points at is internal configuration, so a caller without a key reads nothing.

GET /health, GET /v1/evaluators never require the header. The scoring endpoints (POST /v1/score-jobs, GET /v1/score-jobs/{job_id}) are in a warning phase: a known caller is identified in the logs, and a call without a known key is still accepted. They require a key from an enforcement date announced in the changelog.

When CALLER_KEY_VAULT_URL is unset every endpoint is open and no caller is identified, which is the local-development and test default. The service logs a warning at startup when that happens, so an unprotected deployment is visible in its own logs rather than indistinguishable from a healthy one. With it set, the service logs Loaded N callers: <ids> at startup. Issuing and revoking keys is described in Deployment.