Standards advisor¶
Retrieve → rank → explain over FAIRsharing. See the contract for the shape every agent returns; this file covers what is specific to this one.
advisor.standards accepts a DatasetProfile, declares grounding mode retrieval and
returns Recommendations. Serve it with uv run dd-advisor-standards serve --port 8105.
Configuration¶
build() in factory.py reads these environment variables:
| Variable | Values | Default |
|---|---|---|
DD_STANDARDS_RETRIEVAL |
snapshot, or live (falls back to the snapshot, marked stale) |
snapshot |
DD_STANDARDS_EXPLAINER |
template or anthropic |
template |
DD_MODEL_ID |
model id for the Anthropic explainer | explain.DEFAULT_MODEL (claude-sonnet-5-5) |
DD_SNAPSHOT_PATH |
path to a snapshot JSONL file | data/fairsharing/snapshot.jsonl |
ANTHROPIC_API_KEY |
the Anthropic explainer's key, in development | none |
ANTHROPIC_API_KEY_FILE |
a file holding that key, in deployment; a relative path is under $CREDENTIALS_DIRECTORY |
none |
An unknown value is a configuration error and stops the service at start-up. So is the Anthropic
explainer without exactly one of the two key variables. The key is passed to the Anthropic client
explicitly: the SDK's own credential chain, such as an ant auth login, is never used
(Policy and identity). In development, put the key in config/local/credentials/ and name it in
config/local/agents/advisor-standards.env, which only this agent reads. In deployment, the key is loaded as a systemd credential (deploy/README.md).
With the Anthropic explainer the advisor sends Anthropic the dataset profile's title,
description, keywords, themes and field names and types, and the candidate records' names,
descriptions and subjects (AnthropicExplainer._prompt). It never sends data values. Its
manifest then declares the model (models), and the app's About tab names it. The app runs the
advisor in its own process only with the template explainer. For the model, run it as a
service.
Grounding¶
Each Recommendation carries one GroundingRef for the FAIRsharing record it recommends, and
names the record nowhere else. The record's name, status and DOI are in the matching evidence
item's content, the json-sorted-utf8-v1 projection its hash covers. The linter re-hashes
that content (E1, Evidence and grounding), so what a reader is shown is what was retrieved. The root
Recommendations.grounded_on lists every cited record once.
Honest limits¶
- Ranking is lexical and naive. BM25 over name, abbreviation, description and subject labels, weighted with subject overlap and curation status. Over the 169-record snapshot the soil-chemistry sample gets sensible formats (CSV, Tabular Data Package, ISO 8601 for its date fields) and plausible but arguable terminologies (a chemistry vocabulary ranks above AGROVOC). Every recommendation carries its score and signals so a reviewer can disagree. The seeded-defect evaluation measures this: ENVO is never recommended for the soil sample, AGROVOC only once the title is removed, and the Food Ontology is.
- Vocabulary versus ontology (the standards advisor's one explicit distinction) is decided lexically from the
record's name and description, recorded as
classification_derivation: lexical, withterminology_unclassifiedwhen neither pattern fires. FAIRsharing's curated subtype is not exposed on the public record route. TODO(#4): read it from the authenticated API. - Live search needs a FAIRsharing account (
FAIRSHARING_LOGIN/FAIRSHARING_PASSWORD); record fetch does not. No live-search cassette is committed, so that path is unsubstantiated inCONFORMANCE.mduntil someone with an account records one. - The Anthropic explainer (
DD_STANDARDS_EXPLAINER=anthropic) is tested only against a hand-written cassette (tests/cassettes/test_key_not_stored/), which shows that its key stays out of every stored record. No real reply has been recorded, so the quality of its rationales is unsubstantiated.