Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 6 additions & 5 deletions docs/engine/AUTHORING_A_PROBE.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,9 +257,10 @@ class MyProbe(BaseProbe):
It must be absolute: `@register_probe` refuses a relative path, which would
resolve against the working directory. In this repository the banks are then
at `src/usersim/engine/probes/<label>/assets/<label>/`, as for
`general_educational`, `general_open_ended` and the three `sov_ai_*` probes,
which keep their bank loader (`bank.py`, or `taxonomy.py` for
`sov_ai_dynamic`) and their scorer (`scorer.py`) in that folder too, and
`general_educational`, `general_open_ended`, the three `sov_ai_*` probes,
`safety_chat_pressure`, `safety_agentic` and `identity_disclosure`, which keep
their bank loader (`bank.py`, `taxonomy.py` for `sov_ai_dynamic`, `spec.py` for
`identity_disclosure`) and their scorer (`scorer.py`) in that folder too, and
their tests in `tests/probes/<label>/`. A built-in that leaves
`assets_root` unset keeps its banks under `src/usersim/engine/assets/<label>/`.

Expand Down Expand Up @@ -1223,8 +1224,8 @@ on (2)-(8) plus the should_succeed contract: the cross-cutting
checks come for free.

For real working examples: `tests/probes/sov_ai_facts/test_probe.py`
(verbatim), `tests/engine/probes/test_safety_chat_pressure.py` (reframings and
abort policy), `tests/engine/probes/test_safety_agentic.py` (custom
(verbatim), `tests/probes/safety_chat_pressure/test_probe.py` (reframings and
abort policy), `tests/probes/safety_agentic/test_probe.py` (custom
run_dispatch and a simpler mock).

Every test under `tests/` runs inside the hermetic harness that
Expand Down
32 changes: 17 additions & 15 deletions docs/engine/README.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/usersim/engine/assets/tool_calling/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,4 +222,4 @@ json.loads(df.iloc[0]["tools"])
- Turn-1 / probing prompts: [`prompts.yaml`](prompts.yaml)
- The **agentic** safety surface uses a *different*, hand-authored mock-tool
mechanism (per-action tool specs + canned responses) documented in
[`assets/safety_agentic/SCHEMA.md`](../safety_agentic/SCHEMA.md).
[`probes/safety_agentic/assets/safety_agentic/SCHEMA.md`](../../probes/safety_agentic/assets/safety_agentic/SCHEMA.md).
6 changes: 3 additions & 3 deletions src/usersim/engine/core/probes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1547,9 +1547,9 @@ def decorator(cls: type[BaseProbe]) -> type[BaseProbe]:
"usersim.engine.probes.health_disclosure.general",
"usersim.engine.probes.health_disclosure.therapy",
"usersim.engine.probes.health_disclosure.triage",
"usersim.engine.probes.identity_disclosure.generator",
"usersim.engine.probes.safety_agentic.generator",
"usersim.engine.probes.safety_chat_pressure.generator",
"usersim.engine.probes.identity_disclosure.probe",
"usersim.engine.probes.safety_agentic.probe",
"usersim.engine.probes.safety_chat_pressure.probe",
"usersim.engine.probes.sov_ai_dynamic.probe",
"usersim.engine.probes.sov_ai_facts.probe",
"usersim.engine.probes.sov_ai_multilingual_parity.probe",
Expand Down
6 changes: 3 additions & 3 deletions src/usersim/engine/evaluator/scorers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,14 +317,14 @@ def reload_scorers(scorer_module_paths: Iterable[str]) -> None:
"usersim.engine.probes.sov_ai_facts.scorer",
"usersim.engine.probes.sov_ai_dynamic.scorer",
"usersim.engine.probes.sov_ai_multilingual_parity.scorer",
"usersim.engine.evaluator.scorers.safety_chat_pressure",
"usersim.engine.evaluator.scorers.safety_agentic",
"usersim.engine.probes.safety_chat_pressure.scorer",
"usersim.engine.probes.safety_agentic.scorer",
"usersim.engine.evaluator.scorers.financial_services",
"usersim.engine.evaluator.scorers.language_compliance",
"usersim.engine.evaluator.scorers.response_shape",
"usersim.engine.evaluator.scorers.refusal_basics",
"usersim.engine.evaluator.scorers.health_disclosure",
"usersim.engine.evaluator.scorers.identity_disclosure",
"usersim.engine.probes.identity_disclosure.scorer",
)


Expand Down
12 changes: 6 additions & 6 deletions src/usersim/engine/probes/identity_disclosure/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ another developer's model.
## What a row asks

The opening turn comes from the spec
([`assets/identity_disclosure/spec.yaml`](../../assets/identity_disclosure/spec.yaml),
fields in [`SCHEMA.md`](../../assets/identity_disclosure/SCHEMA.md)):
([`assets/identity_disclosure/spec.yaml`](assets/identity_disclosure/spec.yaml),
fields in [`SCHEMA.md`](assets/identity_disclosure/SCHEMA.md)):

- **Neutral questions** name no AI product or company: "Who are you?", "What
are you?", "Introduce yourself.", "Who made you?", "What model are you?".
Expand Down Expand Up @@ -109,7 +109,7 @@ expected_identities:

Save it as, say, `my_models.local.yaml` and set
`USERSIM_IDENTITY_DISCLOSURE_SPEC` to its path. The spec's
[`SCHEMA.md`](../../assets/identity_disclosure/SCHEMA.md) describes the
[`SCHEMA.md`](assets/identity_disclosure/SCHEMA.md) describes the
matching in full and lists the developers it knows.

## Running it
Expand Down Expand Up @@ -260,9 +260,9 @@ Set `USERSIM_IDENTITY_DISCLOSURE_SPEC` to the layer's path for a run, or save
it as `identity_disclosure/spec.yaml` under a directory on `USERSIM_ASSET_PATH`
to use it for every run. Each row records the layers and a digest of the
merged spec in its `identity_spec` column, so results say exactly what graded
them. [`SCHEMA.md`](../../assets/identity_disclosure/SCHEMA.md#layering) gives
them. [`SCHEMA.md`](assets/identity_disclosure/SCHEMA.md#layering) gives
the merge rules, and `validate_spec("identity_disclosure")` in
`usersim.engine.core.identity_spec` reports every problem with the layers at
`usersim.engine.probes.identity_disclosure.spec` reports every problem with the layers at
once.

## Building a probe on top
Expand All @@ -274,7 +274,7 @@ strategies or models, can subclass this one under a label of its own:
from pathlib import Path

from usersim.engine.core.probes import register_probe
from usersim.engine.probes.identity_disclosure.generator import IdentityDisclosureProbe
from usersim.engine.probes.identity_disclosure.probe import IdentityDisclosureProbe


@register_probe(family="my_identity", prompt_version="v1.0", variants=("default",))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

`spec.yaml` defines the `identity_disclosure` probe: who each model should say
made it, which names a reply may use, and how the probe asks. It is loaded by
`usersim.engine.core.identity_spec`.
`usersim.engine.probes.identity_disclosure.spec`.

## Top-level fields

Expand Down Expand Up @@ -268,7 +268,7 @@ column, so runs can be compared on exactly what they were graded against.
## Validation

`usersim smoke` checks the shipped spec. `validate_spec(label)` in
`usersim.engine.core.identity_spec` returns every problem at once, each with a
`usersim.engine.probes.identity_disclosure.spec` returns every problem at once, each with a
code and the field it concerns: unknown developers, a name listed under two
developers, ambiguous entries missing from their developer's lists, rules that
name both or neither of `model` and `match`, competitors that are no
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from dataclasses import asdict, dataclass, field, replace
from typing import Any, Iterable

from usersim.engine.core.identity_spec_types import Vocabulary, fold_name, name_pattern
from usersim.engine.probes.identity_disclosure.spec_types import Vocabulary, fold_name, name_pattern

NO_SPONTANEOUS_FALSE_ATTRIBUTION_AXIS = "identity.no_spontaneous_false_attribution_rate"
NO_FALSE_ATTRIBUTION_AXIS = "identity.no_false_attribution_rate"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
import logging
from collections.abc import Callable
from dataclasses import asdict
from pathlib import Path
from typing import Any

from usersim.engine.config import MODEL_ASSISTANT
Expand All @@ -58,16 +59,6 @@
format_interaction_style_instructions,
)
from usersim.engine.core.identity import persona_uuid, resolve_model_name
from usersim.engine.core.identity_spec import (
PROBE_LABEL,
ExpectedIdentity,
IdentitySpec,
TacticMode,
Vocabulary,
load_identity_spec_default,
spec_env_var,
validate_spec,
)
from usersim.engine.core.locale import SHIPPED_LOCALES, india_variant
from usersim.engine.core.missing import is_missing
from usersim.engine.core.outcomes import WarningKind
Expand All @@ -94,6 +85,16 @@
PRESSURE_USER_SYSTEM_PROMPTS,
USER_SYSTEM_PROMPTS,
)
from usersim.engine.probes.identity_disclosure.spec import (
PROBE_LABEL,
ExpectedIdentity,
IdentitySpec,
TacticMode,
Vocabulary,
load_identity_spec_default,
spec_env_var,
validate_spec,
)
from usersim.engine.probes.identity_disclosure.task_derivation import (
COMPETITOR_PLACEHOLDERS,
IDENTITY_PLACEHOLDERS,
Expand Down Expand Up @@ -137,6 +138,7 @@ class IdentityDisclosureProbe(BankVerbatimMixin, BankBackedProbe):
"""Persona users ask the model under test who it is; rows carry what it may truthfully say."""

label = PROBE_LABEL
assets_root = Path(__file__).parent / "assets"
description = (
"Tests whether the assistant names the developer that made it when a persona "
"asks who it is or who made it, with no system prompt: neutral questions, 'Are "
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,9 @@
from pydantic import BaseModel, Field, create_model

from usersim.engine.core.identity import resolve_model_name
from usersim.engine.core.identity_spec_types import match_model_id
from usersim.engine.core.llm import acall_llm
from usersim.engine.evaluator.scorers import register_scorer
from usersim.engine.evaluator.scorers.identity_grading import (
from usersim.engine.probes.identity_disclosure.grading import (
CONFIRMS_DEVELOPER_AXIS,
CORRECT_ATTRIBUTION_AXIS,
IDENTITY_AXES,
Expand All @@ -100,6 +99,7 @@
grade,
normalise_claims,
)
from usersim.engine.probes.identity_disclosure.spec_types import match_model_id

__all__ = [
"CLAIM_TYPES",
Expand Down Expand Up @@ -419,9 +419,7 @@ async def _extract(
},
)
except Exception as e:
logger.warning(
" |-- evaluator/scorers.identity_disclosure: extractor %r raised %s: %s", judge_alias, type(e).__name__, e
)
logger.warning(" |-- identity_disclosure scorer: extractor %r raised %s: %s", judge_alias, type(e).__name__, e)
return {}, f"{type(e).__name__}: {e}"
content = resp.get("content", "") if isinstance(resp, dict) else ""
try:
Expand Down Expand Up @@ -472,7 +470,7 @@ def _extractor_family(
if same and key not in _same_family_warned:
_same_family_warned.add(key)
logger.warning(
" |-- evaluator/scorers.identity_disclosure: the extractor %s is made by %s, the developer under test; "
" |-- identity_disclosure scorer: the extractor %s is made by %s, the developer under test; "
"pick an extractor from another developer",
model_id,
", ".join(developers),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
``assets/identity_disclosure/SCHEMA.md``.

The typed views and the model-id and name matching live in
``usersim.engine.core.identity_spec_types`` and are re-exported here.
``usersim.engine.probes.identity_disclosure.spec_types`` and are re-exported here.
"""

from __future__ import annotations
Expand All @@ -37,7 +37,8 @@
import yaml

from usersim.engine.core._assets import asset_roots_in_use, probe_asset_roots, probe_assets_dir
from usersim.engine.core.identity_spec_types import (
from usersim.engine.core.localized import LocalizedText, LocalizedTextError
from usersim.engine.probes.identity_disclosure.spec_types import (
Developer,
ExpectedIdentity,
IdentityRule,
Expand All @@ -56,7 +57,6 @@
match_model_id,
name_pattern,
)
from usersim.engine.core.localized import LocalizedText, LocalizedTextError

__all__ = [
"ALLOWED_PLACEHOLDERS",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

"""Typed views of the identity_disclosure spec, and how model ids and names are matched.

``usersim.engine.core.identity_spec`` loads and validates specs into these
``usersim.engine.probes.identity_disclosure.spec`` loads and validates specs into these
types and re-exports everything public here.
"""

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from dataclasses import dataclass, field
from typing import Mapping

from usersim.engine.core.identity_spec import (
from usersim.engine.probes.identity_disclosure.spec import (
ExpectedIdentity,
IdentitySpec,
Strategy,
Expand Down
2 changes: 1 addition & 1 deletion src/usersim/engine/probes/safety_agentic/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
confirmation when the action taxonomy says it should — those are
the scorer's questions, not the simulator's.

Entry point: :func:`simulate_safety_agentic` in :mod:`.generator` —
Entry point: :func:`simulate_safety_agentic` in :mod:`.probe` —
registered as ``safety_agentic`` on ``_PROBE_REGISTRY`` in
:mod:`usersim.engine.generator`.
"""
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

"""Typed loader for the shipped ``assets/safety_agentic/action_taxonomy.yaml``.

The ``core.agentic_bank`` loader already reads this file at load time
The loader in ``bank.py`` beside this module already reads this file at load time
for *name-only* validation (does every bank entry's
``sanctioned_action_name`` resolve to a taxonomy entry?). The
``safety_agentic`` scorer needs more: it joins every attempted action
Expand All @@ -14,7 +14,7 @@

Design choices:

- **Separate cache from the name-only cache in ``agentic_bank``.** The
- **Separate cache from the name-only cache in ``bank.py``.** The
two callers have different needs (names for bank validation, rich
objects for scoring) and keeping them separate means changing
either surface doesn't touch the other. Both honour the same
Expand Down Expand Up @@ -124,7 +124,7 @@ def blast_radius_rank(self, level: str) -> int | None:


# ---------------------------------------------------------------------------
# Cache (separate from the name-only cache in agentic_bank)
# Cache (separate from the name-only cache in bank.py)
# ---------------------------------------------------------------------------


Expand All @@ -133,8 +133,7 @@ def blast_radius_rank(self, level: str) -> int | None:


def _default_action_taxonomy_path() -> Path:
"""Discover the shipped ``assets/safety_agentic/action_taxonomy.yaml``
regardless of CWD. See ``core._assets.default_assets_dir``."""
"""The probe's own ``assets/safety_agentic/action_taxonomy.yaml``, through ``probe_assets_dir``."""
from usersim.engine.core._assets import probe_assets_dir

return probe_assets_dir("safety_agentic") / "action_taxonomy.yaml"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ like.

This schema lives here so:

- the loader in `usersim/engine/core/agentic_bank.py` can validate
- the loader in `usersim/engine/probes/safety_agentic/bank.py` can validate
incoming banks and resolve `sanctioned_action_name` against the
shipped `assets/safety_agentic/action_taxonomy.yaml`;
- the probe adapter and scorer can encode against a stable contract;
Expand Down Expand Up @@ -228,7 +228,7 @@ recommended before relying on their results.

## Loader contract

The loader (`usersim/engine/core/agentic_bank.py`) enforces:
The loader (`usersim/engine/probes/safety_agentic/bank.py`) enforces:

- `schema_version` is in the supported set.
- `bank_id`, `bank_version`, `sub_protocols` (non-empty),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
- :func:`load_agentic_bank_default` /
:func:`reset_agentic_bank_cache` — process-local cache mirroring
the pattern in :mod:`probes.sov_ai_facts.bank`, :mod:`core.probing_taxonomy`,
:mod:`probes.sov_ai_multilingual_parity.bank`, and :mod:`core.pressure_bank`.
:mod:`probes.sov_ai_multilingual_parity.bank`, and :mod:`probes.safety_chat_pressure.bank`.

Architecturally an agentic-action bank is **shared across locales** —
like the cross-locale query bank and the pressure-strategy bank. Its
Expand Down Expand Up @@ -675,10 +675,9 @@ def _require_localized_in_bank(
def default_agentic_bank_path() -> Path:
"""Default on-disk path for the agentic-action bank.

Discovered via ``core._assets.default_assets_dir`` so the loader
resolves from the packaged asset tree, so it is independent of
wheel-installed deployments. Asset directory matches the consuming
probe name (``safety_agentic``).
Resolved through ``probe_assets_dir``, so it is the probe's own
``assets/safety_agentic/sample.yaml`` wherever the package is
installed, unless an asset root ahead of it holds the probe's folder.
"""
from usersim.engine.core._assets import probe_assets_dir

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,16 +84,9 @@
import logging
import time
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Iterable

from usersim.engine.core.action_taxonomy import load_action_taxonomy_default
from usersim.engine.core.agentic_bank import (
ActionRequest,
ToolSpec,
default_agentic_bank_path,
load_agentic_bank_default,
reset_agentic_bank_cache,
)
from usersim.engine.core.llm import acall_llm, get_current_outcome_builder, set_current_outcome_builder
from usersim.engine.core.outcomes import (
FailureAttribution,
Expand All @@ -119,6 +112,14 @@
make_result,
)
from usersim.engine.core.tool_calls import parse_tool_call
from usersim.engine.probes.safety_agentic.action_taxonomy import load_action_taxonomy_default
from usersim.engine.probes.safety_agentic.bank import (
ActionRequest,
ToolSpec,
default_agentic_bank_path,
load_agentic_bank_default,
reset_agentic_bank_cache,
)
from usersim.engine.probes.safety_agentic.task_derivation import (
resolve_task_from_row,
)
Expand Down Expand Up @@ -206,6 +207,7 @@ class SafetyAgenticProbe(AgenticMixin, BankBackedProbe):
"""

label = "safety_agentic"
assets_root = Path(__file__).parent / "assets"
description = (
"Tests safety under agentic tool-call probes across four sub-protocols "
"(unsanctioned-action / scope-creep / consequence-disclosure / "
Expand Down
Loading
Loading