From bca21f984220cd5d6857ac41e56f40912b438d3d Mon Sep 17 00:00:00 2001 From: Yev Meyer Date: Wed, 4 Nov 2026 08:46:42 -0500 Subject: [PATCH 1/2] feat(extension): export the helpers the built-in probes share usersim.extension exports dispatch_episode, probe_assets_dir, load_seeds, LocalizedText, LocalizedTextError, persona_to_tags, asset_locale, india_variant and recorded_value: each name that two or more built-in probes take from an internal module. docs/extension_api.md lists each in its section, among them a section for banks and assets, and the worked example in AUTHORING_A_PROBE.md imports dispatch_episode from usersim.extension with the rest of its names. Signed-off-by: Yev Meyer --- docs/engine/AUTHORING_A_PROBE.md | 2 +- docs/extension_api.md | 14 ++++++++++++++ src/usersim/extension.py | 26 ++++++++++++++++++++++++-- tests/test_extension_api.py | 5 ++--- 4 files changed, 41 insertions(+), 6 deletions(-) diff --git a/docs/engine/AUTHORING_A_PROBE.md b/docs/engine/AUTHORING_A_PROBE.md index 9fe2c3e..9e7916d 100644 --- a/docs/engine/AUTHORING_A_PROBE.md +++ b/docs/engine/AUTHORING_A_PROBE.md @@ -823,7 +823,6 @@ import random from pathlib import Path from typing import Any -from usersim.engine.core.probes import dispatch_episode from usersim.engine.probes.bookings.bank import LOCALES, Booking, draw_booking, read_booking_bank from usersim.engine.probes.bookings.prompts import BOOKINGS_USER_PROMPTS from usersim.extension import ( @@ -832,6 +831,7 @@ from usersim.extension import ( Provenance, ToolExecutionMixin, WarningKind, + dispatch_episode, format_persona_for_prompt, persona_uuid, register_probe, diff --git a/docs/extension_api.md b/docs/extension_api.md index 9bee91f..ba651af 100644 --- a/docs/extension_api.md +++ b/docs/extension_api.md @@ -49,6 +49,7 @@ does not load the conversation loop. | Name | What it is | |---|---| | `ConversationState` | The conversation so far, as the hooks receive it, with its messages, metadata and `OutcomeBuilder` | +| `dispatch_episode` | Runs one episode through a probe's `run_dispatch` with the run's per-episode settings installed, as `usersim simulate` runs it | | `make_result` | Builds the row a conversation produces, for a probe that writes its own `run_dispatch` | | `make_failed` | Builds the row for a conversation that failed | | `finalize_completed` | Finishes a conversation that ran to its end, failing it when the probe did not complete its protocol | @@ -88,11 +89,23 @@ does not load the conversation loop. | `load_prompt_file` | A probe's `prompts.yaml` as a mapping, read from the folder its banks resolve to; empty when there is none | | `get_prompt` | One prompt from a probe's `prompts.yaml`, or the default it is given when the file or the key is missing | +### Banks and assets + +| Name | What it is | +|---|---| +| `probe_assets_dir` | The folder a probe's assets resolve to: the first override root that holds them, else the root its class declares | +| `load_seeds` | A probe's seeds of one kind for a locale, such as its topics, from `//.json`, or the `en_US` file when the locale has none | +| `LocalizedText` | Bank text with a rendering per locale, read with `for_locale`, which falls back to `en_US` | +| `LocalizedTextError` | Raised when a bank value cannot be read as `LocalizedText` | +| `persona_to_tags` | The tags a persona matches bank entries on, such as its region, age and occupation, for a locale | + ### Locales and languages | Name | What it is | |---|---| | `SHIPPED_LOCALES` | The locales User Sim ships | +| `asset_locale` | The locale whose banks a conversation locale reads: an India language variant reads its base locale's unless it has its own | +| `india_variant` | The India language variant a locale is, with its base locale, language and script, or None for any other locale | | `get_conversation_language` | The language a locale's conversations are in | | `expected_language_display` | A locale's language as a label for prompts | | `language_instruction` | The prompt block telling a model which language, and for some locales which script, to write in | @@ -113,6 +126,7 @@ does not load the conversation loop. | `ScorerRegistrationError` | Raised when `register_scorer` refuses a name | | `is_missing` | Whether a table cell is empty: None, NaN, `pd.NA` or `pd.NaT` | | `none_for_missing` | A copy of a row with every empty cell as None | +| `recorded_value` | A value a probe recorded for a row, from the row or from its `conversation_metadata` | ### Capabilities, asset domains and backends diff --git a/src/usersim/extension.py b/src/usersim/extension.py index 38c0812..7ebcb03 100644 --- a/src/usersim/extension.py +++ b/src/usersim/extension.py @@ -25,6 +25,7 @@ from usersim.asset_gen.registry import AssetDomain from usersim.cli._models import require_aliases, to_data_designer_kwargs, to_model_configs from usersim.engine.config import MODEL_ASSISTANT, MODEL_JUDGE, MODEL_USER, ConversationSimulatorConfig + from usersim.engine.core._assets import probe_assets_dir from usersim.engine.core.backends import ExecutionBackend, JobHandle, JobSpec, JobStatus from usersim.engine.core.behavioral import ( ChatRealism, @@ -35,7 +36,8 @@ ) from usersim.engine.core.identity import persona_uuid from usersim.engine.core.llm import acall_llm - from usersim.engine.core.locale import SHIPPED_LOCALES, expected_language_display + from usersim.engine.core.locale import SHIPPED_LOCALES, asset_locale, expected_language_display, india_variant + from usersim.engine.core.localized import LocalizedText, LocalizedTextError from usersim.engine.core.messages import format_conversation_history_for_prompt, format_tools_for_prompt from usersim.engine.core.missing import is_missing, none_for_missing from usersim.engine.core.outcomes import ( @@ -49,6 +51,7 @@ WarningKind, ) from usersim.engine.core.persona import format_persona_for_prompt, persona_full_name + from usersim.engine.core.persona_tags import persona_to_tags from usersim.engine.core.probes import ( AgenticMixin, BankBackedProbe, @@ -62,9 +65,12 @@ ProbeRegistrationError, ToolCallingMixin, ToolExecutionMixin, + dispatch_episode, register_probe, ) from usersim.engine.core.prompt_loader import get_prompt, load_prompt_file, render_prompt + from usersim.engine.core.row_metadata import recorded_value + from usersim.engine.core.seeds import load_seeds from usersim.engine.core.simulation import ( ConversationState, finalize_completed, @@ -95,6 +101,7 @@ "register_probe", # Conversations and outcomes "ConversationState", + "dispatch_episode", "FailureAttribution", "FailureClass", "OutcomeBuilder", @@ -125,10 +132,18 @@ "persona_full_name", "persona_uuid", "render_prompt", + # Banks and assets + "LocalizedText", + "LocalizedTextError", + "load_seeds", + "persona_to_tags", + "probe_assets_dir", # Locales and languages "SHIPPED_LOCALES", + "asset_locale", "expected_language_display", "get_conversation_language", + "india_variant", "language_instruction", # Tool calls "parse_tool_call", @@ -138,6 +153,7 @@ "ScorerRegistrationError", "is_missing", "none_for_missing", + "recorded_value", "register_scorer", # Capabilities, asset domains and backends "AssetDomain", @@ -157,6 +173,7 @@ "usersim.asset_gen.registry": ("AssetDomain",), "usersim.cli._models": ("require_aliases", "to_data_designer_kwargs", "to_model_configs"), "usersim.engine.config": ("MODEL_ASSISTANT", "MODEL_JUDGE", "MODEL_USER", "ConversationSimulatorConfig"), + "usersim.engine.core._assets": ("probe_assets_dir",), "usersim.engine.core.backends": ("ExecutionBackend", "JobHandle", "JobSpec", "JobStatus"), "usersim.engine.core.behavioral": ( "ChatRealism", @@ -167,7 +184,8 @@ ), "usersim.engine.core.identity": ("persona_uuid",), "usersim.engine.core.llm": ("acall_llm",), - "usersim.engine.core.locale": ("SHIPPED_LOCALES", "expected_language_display"), + "usersim.engine.core.locale": ("SHIPPED_LOCALES", "asset_locale", "expected_language_display", "india_variant"), + "usersim.engine.core.localized": ("LocalizedText", "LocalizedTextError"), "usersim.engine.core.messages": ("format_conversation_history_for_prompt", "format_tools_for_prompt"), "usersim.engine.core.missing": ("is_missing", "none_for_missing"), "usersim.engine.core.outcomes": ( @@ -181,6 +199,7 @@ "WarningKind", ), "usersim.engine.core.persona": ("format_persona_for_prompt", "persona_full_name"), + "usersim.engine.core.persona_tags": ("persona_to_tags",), "usersim.engine.core.probes": ( "AgenticMixin", "BankBackedProbe", @@ -194,9 +213,12 @@ "ProbeRegistrationError", "ToolCallingMixin", "ToolExecutionMixin", + "dispatch_episode", "register_probe", ), "usersim.engine.core.prompt_loader": ("get_prompt", "load_prompt_file", "render_prompt"), + "usersim.engine.core.row_metadata": ("recorded_value",), + "usersim.engine.core.seeds": ("load_seeds",), "usersim.engine.core.simulation": ( "ConversationState", "finalize_completed", diff --git a/tests/test_extension_api.py b/tests/test_extension_api.py index c67404f..b0d2825 100644 --- a/tests/test_extension_api.py +++ b/tests/test_extension_api.py @@ -63,9 +63,8 @@ ("usersim.engine.probes.my_probe.bank", "load_my_bank_default"), ("usersim.engine.templates.probe.prompts", "DEMO_GATE_PROMPT_TEMPLATE"), ("usersim.engine.templates.probe.prompts", "DEMO_USER_AGENT_PROMPTS"), - # The worked example's in-tree shim, and the test template's standard - # tests, which load the probe registry before patching the loop's model calls. - ("usersim.engine.core.probes", "dispatch_episode"), + # The test template's standard tests, which load the probe registry before + # patching the loop's model calls. ("usersim.engine.generator", None), # The fixture's probe that layers a spec over a shipped probe's. ("usersim.engine.probes.identity_disclosure.probe", "IdentityDisclosureProbe"), From 50f7f951c453178d520fbbd7defd85d0753eed9f Mon Sep 17 00:00:00 2001 From: Yev Meyer Date: Wed, 4 Nov 2026 08:46:47 -0500 Subject: [PATCH 2/2] refactor(engine): import what built-ins share from usersim.extension Each built-in probe package imports from usersim.extension every name that usersim.extension exports from the module the package imported it from, at the top of a module and inside a function alike. general_educational, general_open_ended, health_disclosure, safety_chat_pressure, sov_ai_facts and sov_ai_multilingual_parity import nothing from other usersim modules, and the allowances in test_probe_private_imports.py are each package's count: financial_services 12, identity_disclosure 6, safety_agentic 3, sov_ai_dynamic 7 and tool_calling 1. AUTHORING_A_PROBE.md says that a package importing everything from usersim.extension has an allowance of 0. Signed-off-by: Yev Meyer --- docs/engine/AUTHORING_A_PROBE.md | 3 +- .../engine/probes/financial_services/bank.py | 4 +- .../engine/probes/financial_services/probe.py | 63 ++++++++----------- .../probes/financial_services/scorer.py | 4 +- .../probes/general_educational/probe.py | 26 ++++---- .../probes/general_educational/prompts.py | 2 +- .../engine/probes/general_open_ended/probe.py | 26 ++++---- .../probes/general_open_ended/prompts.py | 2 +- .../engine/probes/health_disclosure/bank.py | 2 +- .../engine/probes/health_disclosure/base.py | 18 +++--- .../health_disclosure/decision_support.py | 2 +- .../probes/health_disclosure/general.py | 2 +- .../probes/health_disclosure/move_runtime.py | 3 +- .../probes/health_disclosure/prompts.py | 3 +- .../health_disclosure/realized_audit.py | 5 +- .../engine/probes/health_disclosure/scorer.py | 3 +- .../probes/health_disclosure/therapy.py | 2 +- .../engine/probes/health_disclosure/triage.py | 2 +- .../probes/identity_disclosure/probe.py | 37 +++++------ .../probes/identity_disclosure/prompts.py | 3 +- .../probes/identity_disclosure/scorer.py | 3 +- .../engine/probes/identity_disclosure/spec.py | 4 +- .../probes/identity_disclosure/spec_types.py | 2 +- .../probes/safety_agentic/action_taxonomy.py | 2 +- .../engine/probes/safety_agentic/bank.py | 9 +-- .../engine/probes/safety_agentic/probe.py | 61 ++++++++---------- .../engine/probes/safety_agentic/scorer.py | 5 +- .../probes/safety_agentic/task_derivation.py | 2 +- .../probes/safety_chat_pressure/bank.py | 7 +-- .../probes/safety_chat_pressure/classifier.py | 2 +- .../probes/safety_chat_pressure/probe.py | 35 +++++------ .../probes/safety_chat_pressure/prompts.py | 2 +- .../probes/safety_chat_pressure/scorer.py | 5 +- .../safety_chat_pressure/task_derivation.py | 2 +- .../engine/probes/sov_ai_dynamic/probe.py | 44 +++++-------- .../engine/probes/sov_ai_dynamic/scorer.py | 6 +- .../probes/sov_ai_dynamic/task_derivation.py | 2 +- .../engine/probes/sov_ai_facts/bank.py | 2 +- .../engine/probes/sov_ai_facts/probe.py | 39 +++++------- .../engine/probes/sov_ai_facts/scorer.py | 6 +- .../probes/sov_ai_facts/task_derivation.py | 2 +- .../probes/sov_ai_multilingual_parity/bank.py | 2 +- .../sov_ai_multilingual_parity/probe.py | 39 +++++------- .../sov_ai_multilingual_parity/scorer.py | 10 +-- .../task_derivation.py | 2 +- .../engine/probes/tool_calling/probe.py | 57 +++++++---------- .../engine/probes/tool_calling/prompts.py | 2 +- tests/test_probe_private_imports.py | 22 +++---- 48 files changed, 257 insertions(+), 331 deletions(-) diff --git a/docs/engine/AUTHORING_A_PROBE.md b/docs/engine/AUTHORING_A_PROBE.md index 9e7916d..426fc43 100644 --- a/docs/engine/AUTHORING_A_PROBE.md +++ b/docs/engine/AUTHORING_A_PROBE.md @@ -563,7 +563,8 @@ each name it takes from another `usersim` module is an import to change if it ever leaves this repository. `tests/test_probe_private_imports.py` counts the names each built-in package imports from other `usersim` modules and fails when the count grows; a new -package adds its count to that test's `_ALLOWED`. +package adds its count to that test's `_ALLOWED`, which is 0 for a package that +imports everything from `usersim.extension`. ### Declare every column your probe writes diff --git a/src/usersim/engine/probes/financial_services/bank.py b/src/usersim/engine/probes/financial_services/bank.py index 9975859..478a8a6 100644 --- a/src/usersim/engine/probes/financial_services/bank.py +++ b/src/usersim/engine/probes/financial_services/bank.py @@ -48,7 +48,7 @@ Sequence, ) -from usersim.engine.core.locale import india_variant +from usersim.extension import india_variant logger = logging.getLogger("usersim.engine") @@ -707,7 +707,7 @@ def _load_embeddings(path: Path) -> dict[str, tuple[float, ...]]: def default_finance_bank_dir(locale: str) -> Path: - from usersim.engine.core._assets import probe_assets_dir + from usersim.extension import probe_assets_dir return probe_assets_dir("financial_services") / locale diff --git a/src/usersim/engine/probes/financial_services/probe.py b/src/usersim/engine/probes/financial_services/probe.py index 945e00f..25f111b 100644 --- a/src/usersim/engine/probes/financial_services/probe.py +++ b/src/usersim/engine/probes/financial_services/probe.py @@ -37,42 +37,8 @@ from pathlib import Path from typing import Any, Sequence -from usersim.engine.core.behavioral import ( - format_behavioral_profile_for_prompt, - format_disclosure_instructions, - format_interaction_style_instructions, -) -from usersim.engine.core.identity import persona_uuid as compute_persona_uuid -from usersim.engine.core.locale import ( - expected_language_display, - persona_dataset_locale, -) -from usersim.engine.core.outcomes import ( - FailureAttribution, - FailureClass, - OutcomeBuilder, - OutcomeStatus, - Provenance, - SimulationTrace, - TraceKind, - WarningKind, -) -from usersim.engine.core.persona import format_persona_for_prompt, persona_full_name -from usersim.engine.core.probes import ( - BankBackedProbe, - BankLoadError, - ToolExecutionMixin, - dispatch_episode, - register_probe, -) +from usersim.engine.core.locale import persona_dataset_locale from usersim.engine.core.probing_taxonomy import load_probing_taxonomy, taxonomy_path_for -from usersim.engine.core.simulation import ( - ConversationState, - make_failed, -) -from usersim.engine.core.simulation import ( - language_instruction as _language_instruction, -) from usersim.engine.probes.financial_services import retrieval as _retrieval from usersim.engine.probes.financial_services import tools as _tools from usersim.engine.probes.financial_services.bank import ( @@ -97,6 +63,31 @@ derive_instance, ) from usersim.engine.probes.financial_services.tasks import FinanceInstance +from usersim.extension import ( + BankBackedProbe, + BankLoadError, + ConversationState, + FailureAttribution, + FailureClass, + OutcomeBuilder, + OutcomeStatus, + Provenance, + SimulationTrace, + ToolExecutionMixin, + TraceKind, + WarningKind, + dispatch_episode, + expected_language_display, + format_behavioral_profile_for_prompt, + format_disclosure_instructions, + format_interaction_style_instructions, + format_persona_for_prompt, + make_failed, + persona_full_name, + register_probe, +) +from usersim.extension import language_instruction as _language_instruction +from usersim.extension import persona_uuid as compute_persona_uuid logger = logging.getLogger("usersim.engine") @@ -1158,7 +1149,7 @@ async def simulate_financial_services( **kwargs: Any, ) -> dict[str, Any]: """Thin shim: catch construction errors -> structured SCENARIO_ABORTED.""" - from usersim.engine.core.probes import BankLoadError + from usersim.extension import BankLoadError provenance = kwargs.get("provenance") or Provenance() outcome_builder = kwargs.get("outcome_builder") or OutcomeBuilder( diff --git a/src/usersim/engine/probes/financial_services/scorer.py b/src/usersim/engine/probes/financial_services/scorer.py index 70ed67a..355fac0 100644 --- a/src/usersim/engine/probes/financial_services/scorer.py +++ b/src/usersim/engine/probes/financial_services/scorer.py @@ -40,9 +40,7 @@ create_judge_structured_output_model, ) -from usersim.engine.core.llm import acall_llm -from usersim.engine.core.messages import format_conversation_history_for_prompt -from usersim.engine.evaluator.scorers import register_scorer +from usersim.extension import acall_llm, format_conversation_history_for_prompt, register_scorer logger = logging.getLogger("usersim.engine") diff --git a/src/usersim/engine/probes/general_educational/probe.py b/src/usersim/engine/probes/general_educational/probe.py index 6031c5f..0d89ffe 100644 --- a/src/usersim/engine/probes/general_educational/probe.py +++ b/src/usersim/engine/probes/general_educational/probe.py @@ -14,23 +14,27 @@ from pathlib import Path from typing import Any -from usersim.engine.core.behavioral import ( - format_behavioral_profile_for_prompt, - format_disclosure_instructions, - format_interaction_style_instructions, -) -from usersim.engine.core.locale import SHIPPED_LOCALES -from usersim.engine.core.persona import format_persona_for_prompt -from usersim.engine.core.probes import BaseProbe, PreviewField, dispatch_episode, register_probe -from usersim.engine.core.prompt_loader import load_prompt_file, render_prompt -from usersim.engine.core.seeds import load_seeds -from usersim.engine.core.simulation import language_instruction from usersim.engine.probes.general_educational.prompts import ( _SCENARIO, assistant_system_prompt, user_agent_system_prompt, user_judge_turn_prompt, ) +from usersim.extension import ( + SHIPPED_LOCALES, + BaseProbe, + PreviewField, + dispatch_episode, + format_behavioral_profile_for_prompt, + format_disclosure_instructions, + format_interaction_style_instructions, + format_persona_for_prompt, + language_instruction, + load_prompt_file, + load_seeds, + register_probe, + render_prompt, +) # ── Probe metadata ────────────────────────────────────────────────── # Bumping ``PROMPT_VERSION`` invalidates prior evaluator rows on diff --git a/src/usersim/engine/probes/general_educational/prompts.py b/src/usersim/engine/probes/general_educational/prompts.py index 7cfa909..d62ec30 100644 --- a/src/usersim/engine/probes/general_educational/prompts.py +++ b/src/usersim/engine/probes/general_educational/prompts.py @@ -7,7 +7,7 @@ falling back to the compiled-in defaults below. """ -from usersim.engine.core.prompt_loader import get_prompt +from usersim.extension import get_prompt _SCENARIO = "general_educational" diff --git a/src/usersim/engine/probes/general_open_ended/probe.py b/src/usersim/engine/probes/general_open_ended/probe.py index 767ac12..6cab5db 100644 --- a/src/usersim/engine/probes/general_open_ended/probe.py +++ b/src/usersim/engine/probes/general_open_ended/probe.py @@ -14,23 +14,27 @@ from pathlib import Path from typing import Any -from usersim.engine.core.behavioral import ( - format_behavioral_profile_for_prompt, - format_disclosure_instructions, - format_interaction_style_instructions, -) -from usersim.engine.core.locale import SHIPPED_LOCALES -from usersim.engine.core.persona import format_persona_for_prompt -from usersim.engine.core.probes import BaseProbe, PreviewField, dispatch_episode, register_probe -from usersim.engine.core.prompt_loader import load_prompt_file, render_prompt -from usersim.engine.core.seeds import load_seeds -from usersim.engine.core.simulation import language_instruction from usersim.engine.probes.general_open_ended.prompts import ( _SCENARIO, assistant_system_prompt, user_agent_system_prompt, user_judge_turn_prompt, ) +from usersim.extension import ( + SHIPPED_LOCALES, + BaseProbe, + PreviewField, + dispatch_episode, + format_behavioral_profile_for_prompt, + format_disclosure_instructions, + format_interaction_style_instructions, + format_persona_for_prompt, + language_instruction, + load_prompt_file, + load_seeds, + register_probe, + render_prompt, +) # ── Probe metadata ────────────────────────────────────────────────── # Bumping ``PROMPT_VERSION`` invalidates prior evaluator rows on diff --git a/src/usersim/engine/probes/general_open_ended/prompts.py b/src/usersim/engine/probes/general_open_ended/prompts.py index 56421ed..b9452a2 100644 --- a/src/usersim/engine/probes/general_open_ended/prompts.py +++ b/src/usersim/engine/probes/general_open_ended/prompts.py @@ -7,7 +7,7 @@ falling back to the compiled-in defaults below. """ -from usersim.engine.core.prompt_loader import get_prompt +from usersim.extension import get_prompt _SCENARIO = "general_open_ended" diff --git a/src/usersim/engine/probes/health_disclosure/bank.py b/src/usersim/engine/probes/health_disclosure/bank.py index 9227d9a..e6b7650 100644 --- a/src/usersim/engine/probes/health_disclosure/bank.py +++ b/src/usersim/engine/probes/health_disclosure/bank.py @@ -183,7 +183,7 @@ def load_clinical_profile_bank(path: str | Path) -> ClinicalProfileBank: # --------------------------------------------------------------------------- def default_clinical_profile_bank_path(client: str) -> Path: """Bundled bank path: ``assets//sample.yaml`` (locale-independent).""" - from usersim.engine.core._assets import probe_assets_dir + from usersim.extension import probe_assets_dir if client not in CLIENT_PROBE_LABELS: raise ClinicalProfileBankError(f"unknown client probe label: {client!r}") diff --git a/src/usersim/engine/probes/health_disclosure/base.py b/src/usersim/engine/probes/health_disclosure/base.py index f633ff5..255dc29 100644 --- a/src/usersim/engine/probes/health_disclosure/base.py +++ b/src/usersim/engine/probes/health_disclosure/base.py @@ -22,15 +22,6 @@ from pathlib import Path from typing import Any, Mapping -from usersim.engine.core.behavioral import ( - format_behavioral_profile_for_prompt, - format_disclosure_instructions, - format_interaction_style_instructions, -) -from usersim.engine.core.outcomes import WarningKind -from usersim.engine.core.persona import format_persona_for_prompt -from usersim.engine.core.probes import BankBackedProbe -from usersim.engine.core.simulation import language_instruction from usersim.engine.probes.health_disclosure.bank import ( DEFAULT_PROFILE_ID, ClinicalProfile, @@ -39,6 +30,15 @@ GuardedMoveMixin, env_override, ) +from usersim.extension import ( + BankBackedProbe, + WarningKind, + format_behavioral_profile_for_prompt, + format_disclosure_instructions, + format_interaction_style_instructions, + format_persona_for_prompt, + language_instruction, +) # Local-dev override for the guarded move-space (primary switch is the ``guarded`` # probe_variant; see GuardedMoveMixin). Off/unset → driven by the variant. diff --git a/src/usersim/engine/probes/health_disclosure/decision_support.py b/src/usersim/engine/probes/health_disclosure/decision_support.py index 37ae35a..4aeea9d 100644 --- a/src/usersim/engine/probes/health_disclosure/decision_support.py +++ b/src/usersim/engine/probes/health_disclosure/decision_support.py @@ -11,12 +11,12 @@ from __future__ import annotations -from usersim.engine.core.probes import register_probe from usersim.engine.probes.health_disclosure.bank import ( load_clinical_profile_bank_for_client, ) from usersim.engine.probes.health_disclosure.base import HealthDisclosureProbe from usersim.engine.probes.health_disclosure.clients import CLIENTS +from usersim.extension import register_probe _CFG = CLIENTS["health_decision_support_disclosure"] _LABEL = "health_decision_support_disclosure" diff --git a/src/usersim/engine/probes/health_disclosure/general.py b/src/usersim/engine/probes/health_disclosure/general.py index a112c6e..c09c035 100644 --- a/src/usersim/engine/probes/health_disclosure/general.py +++ b/src/usersim/engine/probes/health_disclosure/general.py @@ -12,12 +12,12 @@ from __future__ import annotations -from usersim.engine.core.probes import register_probe from usersim.engine.probes.health_disclosure.bank import ( load_clinical_profile_bank_for_client, ) from usersim.engine.probes.health_disclosure.base import HealthDisclosureProbe from usersim.engine.probes.health_disclosure.clients import CLIENTS +from usersim.extension import register_probe _CFG = CLIENTS["health_general_disclosure"] _LABEL = "health_general_disclosure" diff --git a/src/usersim/engine/probes/health_disclosure/move_runtime.py b/src/usersim/engine/probes/health_disclosure/move_runtime.py index 7b77c93..8852a33 100644 --- a/src/usersim/engine/probes/health_disclosure/move_runtime.py +++ b/src/usersim/engine/probes/health_disclosure/move_runtime.py @@ -33,8 +33,6 @@ import os from typing import Any, Callable -from usersim.engine.core.llm import acall_llm -from usersim.engine.core.tool_calls import recover_tool_call from usersim.engine.probes.health_disclosure import clients as C from usersim.engine.probes.health_disclosure.moves import ( AFFECTS, @@ -45,6 +43,7 @@ PatientEnv, ) from usersim.engine.probes.health_disclosure.prompts import PROPOSAL_SYSTEM_PACK +from usersim.extension import acall_llm, recover_tool_call MODEL_USER = "user_model" diff --git a/src/usersim/engine/probes/health_disclosure/prompts.py b/src/usersim/engine/probes/health_disclosure/prompts.py index c11b334..2c4a9c2 100644 --- a/src/usersim/engine/probes/health_disclosure/prompts.py +++ b/src/usersim/engine/probes/health_disclosure/prompts.py @@ -24,8 +24,7 @@ completeness. """ -from usersim.engine.core.locale import SHIPPED_LOCALES -from usersim.engine.core.probes import LocalePromptPack +from usersim.extension import SHIPPED_LOCALES, LocalePromptPack def _english_scaffold_pack(label: str, template: str) -> LocalePromptPack: diff --git a/src/usersim/engine/probes/health_disclosure/realized_audit.py b/src/usersim/engine/probes/health_disclosure/realized_audit.py index 033a03b..b303bd6 100644 --- a/src/usersim/engine/probes/health_disclosure/realized_audit.py +++ b/src/usersim/engine/probes/health_disclosure/realized_audit.py @@ -32,10 +32,7 @@ import os from typing import Any -from usersim.engine.core.llm import acall_llm -from usersim.engine.core.locale import SHIPPED_LOCALES -from usersim.engine.core.probes import LocalePromptPack -from usersim.engine.core.tool_calls import recover_tool_call +from usersim.extension import SHIPPED_LOCALES, LocalePromptPack, acall_llm, recover_tool_call logger = logging.getLogger("usersim.engine") diff --git a/src/usersim/engine/probes/health_disclosure/scorer.py b/src/usersim/engine/probes/health_disclosure/scorer.py index 01de2c0..63962ac 100644 --- a/src/usersim/engine/probes/health_disclosure/scorer.py +++ b/src/usersim/engine/probes/health_disclosure/scorer.py @@ -88,14 +88,13 @@ import logging from typing import Any -from usersim.engine.core.missing import is_missing -from usersim.engine.evaluator.scorers import register_scorer from usersim.engine.probes.health_disclosure.realized_audit import ( DISCLOSURE_CREDIT, pair_realized_turns, reconcile_realized, verify_realized_transcript, ) +from usersim.extension import is_missing, register_scorer logger = logging.getLogger("usersim.engine") diff --git a/src/usersim/engine/probes/health_disclosure/therapy.py b/src/usersim/engine/probes/health_disclosure/therapy.py index 6c354d9..f83ac71 100644 --- a/src/usersim/engine/probes/health_disclosure/therapy.py +++ b/src/usersim/engine/probes/health_disclosure/therapy.py @@ -10,12 +10,12 @@ from __future__ import annotations -from usersim.engine.core.probes import register_probe from usersim.engine.probes.health_disclosure.bank import ( load_clinical_profile_bank_for_client, ) from usersim.engine.probes.health_disclosure.base import HealthDisclosureProbe from usersim.engine.probes.health_disclosure.clients import CLIENTS +from usersim.extension import register_probe _CFG = CLIENTS["health_therapy_disclosure"] _LABEL = "health_therapy_disclosure" diff --git a/src/usersim/engine/probes/health_disclosure/triage.py b/src/usersim/engine/probes/health_disclosure/triage.py index e2cd2bf..b634c6a 100644 --- a/src/usersim/engine/probes/health_disclosure/triage.py +++ b/src/usersim/engine/probes/health_disclosure/triage.py @@ -10,12 +10,12 @@ from __future__ import annotations -from usersim.engine.core.probes import register_probe from usersim.engine.probes.health_disclosure.bank import ( load_clinical_profile_bank_for_client, ) from usersim.engine.probes.health_disclosure.base import HealthDisclosureProbe from usersim.engine.probes.health_disclosure.clients import CLIENTS +from usersim.extension import register_probe _CFG = CLIENTS["health_triage_disclosure"] _LABEL = "health_triage_disclosure" diff --git a/src/usersim/engine/probes/identity_disclosure/probe.py b/src/usersim/engine/probes/identity_disclosure/probe.py index b72ff3c..aa68b0c 100644 --- a/src/usersim/engine/probes/identity_disclosure/probe.py +++ b/src/usersim/engine/probes/identity_disclosure/probe.py @@ -53,24 +53,8 @@ from pathlib import Path from typing import Any -from usersim.engine.config import MODEL_ASSISTANT -from usersim.engine.core.behavioral import ( - format_behavioral_profile_for_prompt, - format_interaction_style_instructions, -) -from usersim.engine.core.identity import persona_uuid, resolve_model_name -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 -from usersim.engine.core.persona import format_persona_for_prompt -from usersim.engine.core.probes import BankBackedProbe, BankVerbatimMixin, register_probe -from usersim.engine.core.simulation import ConversationState, language_instruction -from usersim.engine.core.user_turn_policy import ( - FIRST_MESSAGE_PHRASES, - LANGUAGE_MODEL_NOUNS, - PUSHBACK_OPENERS, - UserTurnPolicy, -) +from usersim.engine.core.identity import resolve_model_name +from usersim.engine.core.user_turn_policy import FIRST_MESSAGE_PHRASES, LANGUAGE_MODEL_NOUNS, PUSHBACK_OPENERS from usersim.engine.probes.identity_disclosure.prompts import ( FOLLOWUP_ANCHOR, GATE_PROMPTS, @@ -110,6 +94,23 @@ select_tactic, strategy_placeholders, ) +from usersim.extension import ( + MODEL_ASSISTANT, + SHIPPED_LOCALES, + BankBackedProbe, + BankVerbatimMixin, + ConversationState, + UserTurnPolicy, + WarningKind, + format_behavioral_profile_for_prompt, + format_interaction_style_instructions, + format_persona_for_prompt, + india_variant, + is_missing, + language_instruction, + persona_uuid, + register_probe, +) logger = logging.getLogger("usersim.engine") diff --git a/src/usersim/engine/probes/identity_disclosure/prompts.py b/src/usersim/engine/probes/identity_disclosure/prompts.py index 73da2e1..11008e6 100644 --- a/src/usersim/engine/probes/identity_disclosure/prompts.py +++ b/src/usersim/engine/probes/identity_disclosure/prompts.py @@ -11,8 +11,7 @@ from __future__ import annotations -from usersim.engine.core.locale import SHIPPED_LOCALES -from usersim.engine.core.probes import LocalePromptPack +from usersim.extension import SHIPPED_LOCALES, LocalePromptPack USER_SYSTEM_PROMPT = """You are roleplaying as a real person chatting with an AI assistant. You are the USER. You are curious about the assistant itself: what it is, who made it, and which model it is. You are not a helper and not an assistant. diff --git a/src/usersim/engine/probes/identity_disclosure/scorer.py b/src/usersim/engine/probes/identity_disclosure/scorer.py index 5f620c1..0f343ef 100644 --- a/src/usersim/engine/probes/identity_disclosure/scorer.py +++ b/src/usersim/engine/probes/identity_disclosure/scorer.py @@ -75,8 +75,6 @@ from pydantic import BaseModel, Field, create_model from usersim.engine.core.identity import resolve_model_name -from usersim.engine.core.llm import acall_llm -from usersim.engine.evaluator.scorers import register_scorer from usersim.engine.probes.identity_disclosure.grading import ( CONFIRMS_DEVELOPER_AXIS, CORRECT_ATTRIBUTION_AXIS, @@ -100,6 +98,7 @@ normalise_claims, ) from usersim.engine.probes.identity_disclosure.spec_types import match_model_id +from usersim.extension import acall_llm, register_scorer __all__ = [ "CLAIM_TYPES", diff --git a/src/usersim/engine/probes/identity_disclosure/spec.py b/src/usersim/engine/probes/identity_disclosure/spec.py index 8287dc4..a4a2ff9 100644 --- a/src/usersim/engine/probes/identity_disclosure/spec.py +++ b/src/usersim/engine/probes/identity_disclosure/spec.py @@ -36,8 +36,7 @@ import yaml -from usersim.engine.core._assets import asset_roots_in_use, probe_asset_roots, probe_assets_dir -from usersim.engine.core.localized import LocalizedText, LocalizedTextError +from usersim.engine.core._assets import asset_roots_in_use, probe_asset_roots from usersim.engine.probes.identity_disclosure.spec_types import ( Developer, ExpectedIdentity, @@ -57,6 +56,7 @@ match_model_id, name_pattern, ) +from usersim.extension import LocalizedText, LocalizedTextError, probe_assets_dir __all__ = [ "ALLOWED_PLACEHOLDERS", diff --git a/src/usersim/engine/probes/identity_disclosure/spec_types.py b/src/usersim/engine/probes/identity_disclosure/spec_types.py index 28abfab..3a9f59e 100644 --- a/src/usersim/engine/probes/identity_disclosure/spec_types.py +++ b/src/usersim/engine/probes/identity_disclosure/spec_types.py @@ -17,7 +17,7 @@ from functools import cache, cached_property from typing import Any, Iterable, Mapping -from usersim.engine.core.localized import LocalizedText +from usersim.extension import LocalizedText #: Characters after which a model family name may start inside a model id: #: path segments, tags and versions (``:``, ``@``), vendor prefixes diff --git a/src/usersim/engine/probes/safety_agentic/action_taxonomy.py b/src/usersim/engine/probes/safety_agentic/action_taxonomy.py index c10b990..da97566 100644 --- a/src/usersim/engine/probes/safety_agentic/action_taxonomy.py +++ b/src/usersim/engine/probes/safety_agentic/action_taxonomy.py @@ -134,7 +134,7 @@ def blast_radius_rank(self, level: str) -> int | None: def _default_action_taxonomy_path() -> Path: """The probe's own ``assets/safety_agentic/action_taxonomy.yaml``, through ``probe_assets_dir``.""" - from usersim.engine.core._assets import probe_assets_dir + from usersim.extension import probe_assets_dir return probe_assets_dir("safety_agentic") / "action_taxonomy.yaml" diff --git a/src/usersim/engine/probes/safety_agentic/bank.py b/src/usersim/engine/probes/safety_agentic/bank.py index 4dc529c..0e39c1a 100644 --- a/src/usersim/engine/probes/safety_agentic/bank.py +++ b/src/usersim/engine/probes/safety_agentic/bank.py @@ -52,10 +52,7 @@ from pathlib import Path from typing import Any, Iterable -from usersim.engine.core.localized import ( - LocalizedText, - LocalizedTextError, -) +from usersim.extension import LocalizedText, LocalizedTextError logger = logging.getLogger("usersim.engine") @@ -213,7 +210,7 @@ def _default_action_taxonomy_path() -> Path: """Discover the shipped ``assets/safety_agentic/action_taxonomy.yaml`` in the probe's own assets. Resolution is independent of CWD; set ``USERSIM_SAFETY_AGENTIC_ACTION_TAXONOMY`` to override.""" - from usersim.engine.core._assets import probe_assets_dir + from usersim.extension import probe_assets_dir return probe_assets_dir("safety_agentic") / "action_taxonomy.yaml" @@ -679,7 +676,7 @@ def default_agentic_bank_path() -> Path: ``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 + from usersim.extension import probe_assets_dir return probe_assets_dir("safety_agentic") / "sample.yaml" diff --git a/src/usersim/engine/probes/safety_agentic/probe.py b/src/usersim/engine/probes/safety_agentic/probe.py index bfe8b62..1da5dd6 100644 --- a/src/usersim/engine/probes/safety_agentic/probe.py +++ b/src/usersim/engine/probes/safety_agentic/probe.py @@ -87,41 +87,38 @@ from pathlib import Path from typing import Any, Iterable -from usersim.engine.core.llm import acall_llm, get_current_outcome_builder, set_current_outcome_builder -from usersim.engine.core.outcomes import ( +from usersim.engine.core.llm import get_current_outcome_builder, set_current_outcome_builder +from usersim.engine.core.probes import assistant_message +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, +) +from usersim.extension import ( + AgenticMixin, + BankBackedProbe, + ConversationState, FailureAttribution, FailureClass, OutcomeBuilder, OutcomeStatus, + PreviewField, SimulationTrace, TraceKind, WarningKind, -) -from usersim.engine.core.probes import ( - AgenticMixin, - BankBackedProbe, - PreviewField, - assistant_message, + acall_llm, dispatch_episode, - register_probe, -) -from usersim.engine.core.simulation import ( - ConversationState, finalize_completed, make_failed, 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, + parse_tool_call, + register_probe, ) logger = logging.getLogger("usersim.engine") @@ -597,9 +594,8 @@ async def simulate_safety_agentic( and returns a ``make_failed`` outcome with ``failure_class=SCENARIO_ABORTED``. """ - from usersim.engine.core.outcomes import OutcomeBuilder - from usersim.engine.core.outcomes import Provenance as _Provenance - from usersim.engine.core.probes import BankLoadError + from usersim.extension import BankLoadError, OutcomeBuilder + from usersim.extension import Provenance as _Provenance provenance = kwargs.get("provenance") or _Provenance() outcome_builder = kwargs.get("outcome_builder") or OutcomeBuilder( @@ -626,15 +622,8 @@ async def simulate_safety_agentic( def _aborted(reason: str, provenance: Any) -> dict[str, Any]: """Build a structured SCENARIO_ABORTED failure result.""" - from usersim.engine.core.outcomes import ( - FailureAttribution, - FailureClass, - OutcomeBuilder, - OutcomeStatus, - ) - from usersim.engine.core.outcomes import ( - Provenance as _Provenance, - ) + from usersim.extension import FailureAttribution, FailureClass, OutcomeBuilder, OutcomeStatus + from usersim.extension import Provenance as _Provenance builder = OutcomeBuilder(provenance=provenance or _Provenance()) outcome = builder.finalize( diff --git a/src/usersim/engine/probes/safety_agentic/scorer.py b/src/usersim/engine/probes/safety_agentic/scorer.py index 31d4080..bb8941b 100644 --- a/src/usersim/engine/probes/safety_agentic/scorer.py +++ b/src/usersim/engine/probes/safety_agentic/scorer.py @@ -93,10 +93,6 @@ from pydantic import BaseModel, Field -from usersim.engine.core.llm import acall_llm -from usersim.engine.core.messages import format_conversation_history_for_prompt -from usersim.engine.core.row_metadata import recorded_value -from usersim.engine.evaluator.scorers import register_scorer from usersim.engine.probes.safety_agentic.action_taxonomy import ( ActionTaxonomy, load_action_taxonomy_default, @@ -105,6 +101,7 @@ ActionRequest, load_agentic_bank_default, ) +from usersim.extension import acall_llm, format_conversation_history_for_prompt, recorded_value, register_scorer logger = logging.getLogger("usersim.engine") diff --git a/src/usersim/engine/probes/safety_agentic/task_derivation.py b/src/usersim/engine/probes/safety_agentic/task_derivation.py index 2cae216..2b6f4c6 100644 --- a/src/usersim/engine/probes/safety_agentic/task_derivation.py +++ b/src/usersim/engine/probes/safety_agentic/task_derivation.py @@ -31,8 +31,8 @@ import random from typing import Any, Iterable -from usersim.engine.core.persona_tags import persona_to_tags from usersim.engine.probes.safety_agentic.bank import ActionRequest, AgenticBank +from usersim.extension import persona_to_tags __all__ = ["persona_to_tags", "derive_task", "resolve_task_from_row"] diff --git a/src/usersim/engine/probes/safety_chat_pressure/bank.py b/src/usersim/engine/probes/safety_chat_pressure/bank.py index 070e2ba..aad8ec9 100644 --- a/src/usersim/engine/probes/safety_chat_pressure/bank.py +++ b/src/usersim/engine/probes/safety_chat_pressure/bank.py @@ -50,10 +50,7 @@ from pathlib import Path from typing import Any, Iterable -from usersim.engine.core.localized import ( - LocalizedText, - LocalizedTextError, -) +from usersim.extension import LocalizedText, LocalizedTextError logger = logging.getLogger("usersim.engine") @@ -581,7 +578,7 @@ def default_pressure_bank_path() -> Path: ``assets/safety_chat_pressure/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 + from usersim.extension import probe_assets_dir return probe_assets_dir("safety_chat_pressure") / "sample.yaml" diff --git a/src/usersim/engine/probes/safety_chat_pressure/classifier.py b/src/usersim/engine/probes/safety_chat_pressure/classifier.py index d0359cc..5ada941 100644 --- a/src/usersim/engine/probes/safety_chat_pressure/classifier.py +++ b/src/usersim/engine/probes/safety_chat_pressure/classifier.py @@ -28,7 +28,7 @@ import logging from typing import Any -from usersim.engine.core.llm import acall_llm +from usersim.extension import acall_llm logger = logging.getLogger("usersim.engine") diff --git a/src/usersim/engine/probes/safety_chat_pressure/probe.py b/src/usersim/engine/probes/safety_chat_pressure/probe.py index 2dc7311..8da8d5d 100644 --- a/src/usersim/engine/probes/safety_chat_pressure/probe.py +++ b/src/usersim/engine/probes/safety_chat_pressure/probe.py @@ -65,15 +65,6 @@ from pathlib import Path from typing import Any -from usersim.engine.core.outcomes import WarningKind -from usersim.engine.core.probes import ( - BankBackedProbe, - BankReframingMixin, - PreviewField, - dispatch_episode, - register_probe, -) -from usersim.engine.core.simulation import ConversationState, make_failed from usersim.engine.probes.safety_chat_pressure.bank import ( Strategy, TargetRequest, @@ -92,6 +83,16 @@ from usersim.engine.probes.safety_chat_pressure.task_derivation import ( resolve_task_from_row, ) +from usersim.extension import ( + BankBackedProbe, + BankReframingMixin, + ConversationState, + PreviewField, + WarningKind, + dispatch_episode, + make_failed, + register_probe, +) logger = logging.getLogger("usersim.engine") @@ -523,9 +524,8 @@ async def simulate_safety_chat_pressure( and returns a ``make_failed`` outcome with ``failure_class=SCENARIO_ABORTED``. """ - from usersim.engine.core.outcomes import OutcomeBuilder - from usersim.engine.core.outcomes import Provenance as _Provenance - from usersim.engine.core.probes import BankLoadError + from usersim.extension import BankLoadError, OutcomeBuilder + from usersim.extension import Provenance as _Provenance provenance = kwargs.get("provenance") or _Provenance() outcome_builder = kwargs.get("outcome_builder") or OutcomeBuilder( @@ -552,15 +552,8 @@ async def simulate_safety_chat_pressure( def _aborted(reason: str, provenance: Any) -> dict[str, Any]: """Build a structured SCENARIO_ABORTED failure result.""" - from usersim.engine.core.outcomes import ( - FailureAttribution, - FailureClass, - OutcomeBuilder, - OutcomeStatus, - ) - from usersim.engine.core.outcomes import ( - Provenance as _Provenance, - ) + from usersim.extension import FailureAttribution, FailureClass, OutcomeBuilder, OutcomeStatus + from usersim.extension import Provenance as _Provenance builder = OutcomeBuilder(provenance=provenance or _Provenance()) outcome = builder.finalize( diff --git a/src/usersim/engine/probes/safety_chat_pressure/prompts.py b/src/usersim/engine/probes/safety_chat_pressure/prompts.py index f96c72f..adbcddd 100644 --- a/src/usersim/engine/probes/safety_chat_pressure/prompts.py +++ b/src/usersim/engine/probes/safety_chat_pressure/prompts.py @@ -36,12 +36,12 @@ from typing import Any -from usersim.engine.core.persona import format_persona_for_prompt from usersim.engine.probes.safety_chat_pressure.bank import ( Reframing, Strategy, TargetRequest, ) +from usersim.extension import format_persona_for_prompt # PURE-CAPABILITY-TEST POLICY: empty by design. # diff --git a/src/usersim/engine/probes/safety_chat_pressure/scorer.py b/src/usersim/engine/probes/safety_chat_pressure/scorer.py index 2d76191..22cf1fd 100644 --- a/src/usersim/engine/probes/safety_chat_pressure/scorer.py +++ b/src/usersim/engine/probes/safety_chat_pressure/scorer.py @@ -76,15 +76,12 @@ from pydantic import BaseModel, Field -from usersim.engine.core.llm import acall_llm -from usersim.engine.core.messages import format_conversation_history_for_prompt -from usersim.engine.core.row_metadata import recorded_value -from usersim.engine.evaluator.scorers import register_scorer from usersim.engine.probes.safety_chat_pressure.bank import ( Strategy, TargetRequest, load_pressure_bank_default, ) +from usersim.extension import acall_llm, format_conversation_history_for_prompt, recorded_value, register_scorer logger = logging.getLogger("usersim.engine") diff --git a/src/usersim/engine/probes/safety_chat_pressure/task_derivation.py b/src/usersim/engine/probes/safety_chat_pressure/task_derivation.py index d0a7fb0..7166568 100644 --- a/src/usersim/engine/probes/safety_chat_pressure/task_derivation.py +++ b/src/usersim/engine/probes/safety_chat_pressure/task_derivation.py @@ -33,12 +33,12 @@ import random from typing import Any, Iterable -from usersim.engine.core.persona_tags import persona_to_tags from usersim.engine.probes.safety_chat_pressure.bank import ( PressureBank, Strategy, TargetRequest, ) +from usersim.extension import persona_to_tags __all__ = [ "persona_to_tags", diff --git a/src/usersim/engine/probes/sov_ai_dynamic/probe.py b/src/usersim/engine/probes/sov_ai_dynamic/probe.py index 813d631..6900ee6 100644 --- a/src/usersim/engine/probes/sov_ai_dynamic/probe.py +++ b/src/usersim/engine/probes/sov_ai_dynamic/probe.py @@ -54,22 +54,7 @@ from pathlib import Path from typing import Any -from usersim.engine.core.behavioral import get_conversation_language -from usersim.engine.core.locale import SHIPPED_LOCALES -from usersim.engine.core.outcomes import WarningKind -from usersim.engine.core.persona import format_persona_for_prompt -from usersim.engine.core.probes import ( - BankBackedProbe, - PreviewField, - dispatch_episode, - register_probe, -) from usersim.engine.core.probing_taxonomy import Category, load_probing_taxonomy, reset_probing_taxonomy_cache -from usersim.engine.core.simulation import ( - ConversationState, - language_instruction, - make_failed, -) from usersim.engine.probes.sov_ai_dynamic.prompts import ( PromptsUnavailableError, get_followup_instruction, @@ -82,6 +67,19 @@ default_probing_taxonomy_path, load_probing_taxonomy_for_locale, ) +from usersim.extension import ( + SHIPPED_LOCALES, + BankBackedProbe, + ConversationState, + PreviewField, + WarningKind, + dispatch_episode, + format_persona_for_prompt, + get_conversation_language, + language_instruction, + make_failed, + register_probe, +) logger = logging.getLogger("usersim.engine") @@ -374,9 +372,8 @@ async def simulate_sov_ai_dynamic( / prompt-pack-missing) and returns a ``make_failed`` outcome with ``failure_class=SCENARIO_ABORTED``. """ - from usersim.engine.core.outcomes import OutcomeBuilder - from usersim.engine.core.outcomes import Provenance as _Provenance - from usersim.engine.core.probes import BankLoadError + from usersim.extension import BankLoadError, OutcomeBuilder + from usersim.extension import Provenance as _Provenance provenance = kwargs.get("provenance") or _Provenance() outcome_builder = kwargs.get("outcome_builder") or OutcomeBuilder( @@ -406,15 +403,8 @@ async def simulate_sov_ai_dynamic( def _aborted(reason: str, provenance: Any) -> dict[str, Any]: """Build a structured SCENARIO_ABORTED failure result.""" - from usersim.engine.core.outcomes import ( - FailureAttribution, - FailureClass, - OutcomeBuilder, - OutcomeStatus, - ) - from usersim.engine.core.outcomes import ( - Provenance as _Provenance, - ) + from usersim.extension import FailureAttribution, FailureClass, OutcomeBuilder, OutcomeStatus + from usersim.extension import Provenance as _Provenance builder = OutcomeBuilder(provenance=provenance or _Provenance()) outcome = builder.finalize( diff --git a/src/usersim/engine/probes/sov_ai_dynamic/scorer.py b/src/usersim/engine/probes/sov_ai_dynamic/scorer.py index f3cb216..17a19fc 100644 --- a/src/usersim/engine/probes/sov_ai_dynamic/scorer.py +++ b/src/usersim/engine/probes/sov_ai_dynamic/scorer.py @@ -68,11 +68,9 @@ create_judge_structured_output_model, ) -from usersim.engine.core.llm import acall_llm -from usersim.engine.core.messages import format_conversation_history_for_prompt from usersim.engine.core.probing_taxonomy import Category -from usersim.engine.evaluator.scorers import register_scorer from usersim.engine.probes.sov_ai_dynamic.taxonomy import load_probing_taxonomy_for_locale +from usersim.extension import acall_llm, format_conversation_history_for_prompt, register_scorer logger = logging.getLogger("usersim.engine") @@ -273,8 +271,8 @@ async def score_sov_ai_dynamic_trajectory( # India language-variants reuse the en_IN taxonomy (presence-aware: # a native per-variant taxonomy is used if it has been authored). - from usersim.engine.core.locale import asset_locale from usersim.engine.probes.sov_ai_dynamic.taxonomy import default_probing_taxonomy_path + from usersim.extension import asset_locale taxonomy_locale = asset_locale(locale, exists=lambda l: default_probing_taxonomy_path(l).exists()) diff --git a/src/usersim/engine/probes/sov_ai_dynamic/task_derivation.py b/src/usersim/engine/probes/sov_ai_dynamic/task_derivation.py index 740332e..9064ac6 100644 --- a/src/usersim/engine/probes/sov_ai_dynamic/task_derivation.py +++ b/src/usersim/engine/probes/sov_ai_dynamic/task_derivation.py @@ -34,12 +34,12 @@ from typing import Any, Iterable -from usersim.engine.core.persona_tags import persona_to_tags from usersim.engine.core.probing_taxonomy import ( Category, ProbingTaxonomy, select_category, ) +from usersim.extension import persona_to_tags # --------------------------------------------------------------------------- # Public API diff --git a/src/usersim/engine/probes/sov_ai_facts/bank.py b/src/usersim/engine/probes/sov_ai_facts/bank.py index 020bfb1..09da6e3 100644 --- a/src/usersim/engine/probes/sov_ai_facts/bank.py +++ b/src/usersim/engine/probes/sov_ai_facts/bank.py @@ -228,7 +228,7 @@ def default_fact_bank_path(locale: str) -> Path: ``assets/sov_ai_facts//sample.yaml`` wherever the package is installed, unless an asset root ahead of it holds ``sov_ai_facts/``. """ - from usersim.engine.core._assets import probe_assets_dir + from usersim.extension import probe_assets_dir return probe_assets_dir("sov_ai_facts") / locale / "sample.yaml" diff --git a/src/usersim/engine/probes/sov_ai_facts/probe.py b/src/usersim/engine/probes/sov_ai_facts/probe.py index df7e963..5d7eee7 100644 --- a/src/usersim/engine/probes/sov_ai_facts/probe.py +++ b/src/usersim/engine/probes/sov_ai_facts/probe.py @@ -51,17 +51,6 @@ from pathlib import Path from typing import Any -from usersim.engine.core.locale import SHIPPED_LOCALES -from usersim.engine.core.outcomes import WarningKind -from usersim.engine.core.persona import format_persona_for_prompt -from usersim.engine.core.probes import ( - BankBackedProbe, - BankVerbatimMixin, - PreviewField, - dispatch_episode, - register_probe, -) -from usersim.engine.core.simulation import ConversationState, make_failed from usersim.engine.probes.sov_ai_facts.bank import ( Fact, default_fact_bank_path, @@ -77,6 +66,18 @@ from usersim.engine.probes.sov_ai_facts.task_derivation import ( derive_task, ) +from usersim.extension import ( + SHIPPED_LOCALES, + BankBackedProbe, + BankVerbatimMixin, + ConversationState, + PreviewField, + WarningKind, + dispatch_episode, + format_persona_for_prompt, + make_failed, + register_probe, +) logger = logging.getLogger("usersim.engine") @@ -320,9 +321,8 @@ async def simulate_sov_ai_facts( prompt-pack-missing) and returns a ``make_failed`` outcome with ``failure_class=SCENARIO_ABORTED``. """ - from usersim.engine.core.outcomes import OutcomeBuilder - from usersim.engine.core.outcomes import Provenance as _Provenance - from usersim.engine.core.probes import BankLoadError + from usersim.extension import BankLoadError, OutcomeBuilder + from usersim.extension import Provenance as _Provenance provenance = kwargs.get("provenance") or _Provenance() # Construct an OutcomeBuilder so placeholder warnings + bank-version @@ -356,15 +356,8 @@ async def simulate_sov_ai_facts( def _aborted(reason: str, provenance: Any) -> dict[str, Any]: """Build a structured SCENARIO_ABORTED failure result.""" - from usersim.engine.core.outcomes import ( - FailureAttribution, - FailureClass, - OutcomeBuilder, - OutcomeStatus, - ) - from usersim.engine.core.outcomes import ( - Provenance as _Provenance, - ) + from usersim.extension import FailureAttribution, FailureClass, OutcomeBuilder, OutcomeStatus + from usersim.extension import Provenance as _Provenance builder = OutcomeBuilder(provenance=provenance or _Provenance()) outcome = builder.finalize( diff --git a/src/usersim/engine/probes/sov_ai_facts/scorer.py b/src/usersim/engine/probes/sov_ai_facts/scorer.py index 94d11dc..a271aea 100644 --- a/src/usersim/engine/probes/sov_ai_facts/scorer.py +++ b/src/usersim/engine/probes/sov_ai_facts/scorer.py @@ -46,10 +46,8 @@ create_judge_structured_output_model, ) -from usersim.engine.core.llm import acall_llm -from usersim.engine.core.messages import format_conversation_history_for_prompt -from usersim.engine.evaluator.scorers import register_scorer from usersim.engine.probes.sov_ai_facts.bank import Fact, load_fact_bank_for_locale +from usersim.extension import acall_llm, format_conversation_history_for_prompt, register_scorer logger = logging.getLogger("usersim.engine") @@ -291,8 +289,8 @@ async def score_sov_ai_facts_trajectory( # India language-variants reuse the en_IN fact bank (presence-aware: # a native per-variant bank is used if it has been authored). - from usersim.engine.core.locale import asset_locale from usersim.engine.probes.sov_ai_facts.bank import default_fact_bank_path + from usersim.extension import asset_locale bank_locale = asset_locale(locale, exists=lambda l: default_fact_bank_path(l).exists()) diff --git a/src/usersim/engine/probes/sov_ai_facts/task_derivation.py b/src/usersim/engine/probes/sov_ai_facts/task_derivation.py index 19777bd..40efe28 100644 --- a/src/usersim/engine/probes/sov_ai_facts/task_derivation.py +++ b/src/usersim/engine/probes/sov_ai_facts/task_derivation.py @@ -16,8 +16,8 @@ import random from typing import Any, Iterable -from usersim.engine.core.persona_tags import persona_to_tags from usersim.engine.probes.sov_ai_facts.bank import Fact, FactBank +from usersim.extension import persona_to_tags def _persona_content_hash(persona: dict[str, Any]) -> int: diff --git a/src/usersim/engine/probes/sov_ai_multilingual_parity/bank.py b/src/usersim/engine/probes/sov_ai_multilingual_parity/bank.py index ed6c3a4..1bcce16 100644 --- a/src/usersim/engine/probes/sov_ai_multilingual_parity/bank.py +++ b/src/usersim/engine/probes/sov_ai_multilingual_parity/bank.py @@ -519,7 +519,7 @@ def default_query_bank_path() -> Path: ``assets/sov_ai_multilingual_parity/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 + from usersim.extension import probe_assets_dir return probe_assets_dir("sov_ai_multilingual_parity") / "sample.yaml" diff --git a/src/usersim/engine/probes/sov_ai_multilingual_parity/probe.py b/src/usersim/engine/probes/sov_ai_multilingual_parity/probe.py index 1acfa3b..6d4f6f4 100644 --- a/src/usersim/engine/probes/sov_ai_multilingual_parity/probe.py +++ b/src/usersim/engine/probes/sov_ai_multilingual_parity/probe.py @@ -41,16 +41,6 @@ from pathlib import Path from typing import Any -from usersim.engine.core.outcomes import WarningKind -from usersim.engine.core.persona import format_persona_for_prompt -from usersim.engine.core.probes import ( - BankBackedProbe, - BankVerbatimMixin, - PreviewField, - dispatch_episode, - register_probe, -) -from usersim.engine.core.simulation import ConversationState, make_failed from usersim.engine.probes.sov_ai_multilingual_parity.bank import ( Query, default_query_bank_path, @@ -65,6 +55,17 @@ from usersim.engine.probes.sov_ai_multilingual_parity.task_derivation import ( derive_task, ) +from usersim.extension import ( + BankBackedProbe, + BankVerbatimMixin, + ConversationState, + PreviewField, + WarningKind, + dispatch_episode, + format_persona_for_prompt, + make_failed, + register_probe, +) logger = logging.getLogger("usersim.engine") @@ -237,7 +238,7 @@ def derive_task( # the shared-bank loader set): use the variant's own rendering if the # bank ships it (graduation), else the India base (en_IN), else the # locale itself (shipped locales; unknown -> None below = fail-fast). - from usersim.engine.core.locale import india_variant + from usersim.extension import india_variant loc = self._locale if loc not in bank.locales: @@ -349,9 +350,8 @@ async def simulate_sov_ai_multilingual_parity( locale-not-in-bank / prompt-pack-missing) and returns a ``make_failed`` outcome with ``failure_class=SCENARIO_ABORTED``. """ - from usersim.engine.core.outcomes import OutcomeBuilder - from usersim.engine.core.outcomes import Provenance as _Provenance - from usersim.engine.core.probes import BankLoadError + from usersim.extension import BankLoadError, OutcomeBuilder + from usersim.extension import Provenance as _Provenance provenance = kwargs.get("provenance") or _Provenance() # Construct an OutcomeBuilder so placeholder warnings + bank-version @@ -383,15 +383,8 @@ async def simulate_sov_ai_multilingual_parity( def _aborted(reason: str, provenance: Any) -> dict[str, Any]: """Build a structured SCENARIO_ABORTED failure result.""" - from usersim.engine.core.outcomes import ( - FailureAttribution, - FailureClass, - OutcomeBuilder, - OutcomeStatus, - ) - from usersim.engine.core.outcomes import ( - Provenance as _Provenance, - ) + from usersim.extension import FailureAttribution, FailureClass, OutcomeBuilder, OutcomeStatus + from usersim.extension import Provenance as _Provenance builder = OutcomeBuilder(provenance=provenance or _Provenance()) outcome = builder.finalize( diff --git a/src/usersim/engine/probes/sov_ai_multilingual_parity/scorer.py b/src/usersim/engine/probes/sov_ai_multilingual_parity/scorer.py index 6805192..f56cb76 100644 --- a/src/usersim/engine/probes/sov_ai_multilingual_parity/scorer.py +++ b/src/usersim/engine/probes/sov_ai_multilingual_parity/scorer.py @@ -60,11 +60,13 @@ create_judge_structured_output_model, ) -from usersim.engine.core.llm import acall_llm -from usersim.engine.core.locale import expected_language_display -from usersim.engine.core.messages import format_conversation_history_for_prompt -from usersim.engine.evaluator.scorers import register_scorer from usersim.engine.probes.sov_ai_multilingual_parity.bank import Query, load_query_bank_default +from usersim.extension import ( + acall_llm, + expected_language_display, + format_conversation_history_for_prompt, + register_scorer, +) logger = logging.getLogger("usersim.engine") diff --git a/src/usersim/engine/probes/sov_ai_multilingual_parity/task_derivation.py b/src/usersim/engine/probes/sov_ai_multilingual_parity/task_derivation.py index 9876c94..adddd21 100644 --- a/src/usersim/engine/probes/sov_ai_multilingual_parity/task_derivation.py +++ b/src/usersim/engine/probes/sov_ai_multilingual_parity/task_derivation.py @@ -34,8 +34,8 @@ import random from typing import Any, Iterable -from usersim.engine.core.persona_tags import persona_to_tags from usersim.engine.probes.sov_ai_multilingual_parity.bank import Query, QueryBank +from usersim.extension import persona_to_tags __all__ = ["persona_to_tags", "derive_task"] diff --git a/src/usersim/engine/probes/tool_calling/probe.py b/src/usersim/engine/probes/tool_calling/probe.py index dc3a925..810cb97 100644 --- a/src/usersim/engine/probes/tool_calling/probe.py +++ b/src/usersim/engine/probes/tool_calling/probe.py @@ -29,39 +29,7 @@ from pathlib import Path from typing import Any -from usersim.engine.core.behavioral import ( - ChatRealism, - format_behavioral_profile_for_prompt, - format_disclosure_instructions, - format_interaction_style_instructions, -) -from usersim.engine.core.llm import acall_llm -from usersim.engine.core.messages import ( - _parse_theme, - format_conversation_history_for_prompt, - format_tools_for_prompt, -) -from usersim.engine.core.outcomes import ( - SimulationTrace, - TraceKind, -) -from usersim.engine.core.persona import format_persona_for_prompt -from usersim.engine.core.probes import ( - BaseProbe, - PreviewField, - ToolCallingMixin, - ToolExecutionMixin, - dispatch_episode, - register_probe, -) -from usersim.engine.core.prompt_loader import load_prompt_file, render_prompt -from usersim.engine.core.simulation import ( - ConversationState, - make_failed, -) -from usersim.engine.core.simulation import ( - language_instruction as _language_instruction, -) +from usersim.engine.core.messages import _parse_theme from usersim.engine.probes.tool_calling.credentials import ( declared_credentials, draw_credentials, @@ -80,6 +48,29 @@ user_judge_prompt, ) from usersim.engine.probes.tool_calling.verifiers import ToolCallVerifier +from usersim.extension import ( + BaseProbe, + ChatRealism, + ConversationState, + PreviewField, + SimulationTrace, + ToolCallingMixin, + ToolExecutionMixin, + TraceKind, + acall_llm, + dispatch_episode, + format_behavioral_profile_for_prompt, + format_conversation_history_for_prompt, + format_disclosure_instructions, + format_interaction_style_instructions, + format_persona_for_prompt, + format_tools_for_prompt, + load_prompt_file, + make_failed, + register_probe, + render_prompt, +) +from usersim.extension import language_instruction as _language_instruction logger = logging.getLogger("usersim.engine") diff --git a/src/usersim/engine/probes/tool_calling/prompts.py b/src/usersim/engine/probes/tool_calling/prompts.py index 5d07492..3834a83 100644 --- a/src/usersim/engine/probes/tool_calling/prompts.py +++ b/src/usersim/engine/probes/tool_calling/prompts.py @@ -11,7 +11,7 @@ only ASSISTANT_SYSTEM_PROMPT and the tool schemas via the API. """ -from usersim.engine.core.prompt_loader import get_prompt +from usersim.extension import get_prompt _SCENARIO = "tool_calling" diff --git a/tests/test_probe_private_imports.py b/tests/test_probe_private_imports.py index 3910297..ddd756c 100644 --- a/tests/test_probe_private_imports.py +++ b/tests/test_probe_private_imports.py @@ -22,17 +22,17 @@ #: How many distinct ``(module, name)`` imports from internal ``usersim`` modules #: each built-in package holds. _ALLOWED: dict[str, int] = { - "financial_services": 40, - "general_educational": 14, - "general_open_ended": 14, - "health_disclosure": 15, - "identity_disclosure": 27, - "safety_agentic": 30, - "safety_chat_pressure": 23, - "sov_ai_dynamic": 29, - "sov_ai_facts": 22, - "sov_ai_multilingual_parity": 22, - "tool_calling": 23, + "financial_services": 12, + "general_educational": 0, + "general_open_ended": 0, + "health_disclosure": 0, + "identity_disclosure": 6, + "safety_agentic": 3, + "safety_chat_pressure": 0, + "sov_ai_dynamic": 7, + "sov_ai_facts": 0, + "sov_ai_multilingual_parity": 0, + "tool_calling": 1, }