usersim 0.2.0rc1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- usersim/__init__.py +23 -0
- usersim/asset_gen/__init__.py +22 -0
- usersim/asset_gen/financial_services/__init__.py +22 -0
- usersim/asset_gen/financial_services/evaluate.py +330 -0
- usersim/asset_gen/financial_services/pipeline.py +1546 -0
- usersim/asset_gen/financial_services/prompts.py +295 -0
- usersim/asset_gen/financial_services/spec.py +721 -0
- usersim/asset_gen/financial_services/validate.py +647 -0
- usersim/asset_gen/registry.py +191 -0
- usersim/cli/__init__.py +185 -0
- usersim/cli/__main__.py +13 -0
- usersim/cli/_async.py +35 -0
- usersim/cli/_env.py +74 -0
- usersim/cli/_errors.py +44 -0
- usersim/cli/_inputs.py +691 -0
- usersim/cli/_models.py +1222 -0
- usersim/cli/_ngc.py +441 -0
- usersim/cli/_persona_language.py +257 -0
- usersim/cli/_pipeline.py +943 -0
- usersim/cli/evaluate.py +407 -0
- usersim/cli/evaluate_assets.py +107 -0
- usersim/cli/gen_assets.py +110 -0
- usersim/cli/model_catalog.py +449 -0
- usersim/cli/models_default.toml +45 -0
- usersim/cli/models_openai.toml +26 -0
- usersim/cli/models_openrouter.toml +33 -0
- usersim/cli/panel.py +182 -0
- usersim/cli/report.py +248 -0
- usersim/cli/rescore.py +223 -0
- usersim/cli/select.py +302 -0
- usersim/cli/setup_ngc.py +129 -0
- usersim/cli/simulate.py +825 -0
- usersim/cli/smoke.py +1015 -0
- usersim/cli/validate_assets.py +64 -0
- usersim/engine/__init__.py +4 -0
- usersim/engine/assets/financial_services/SCHEMA.md +314 -0
- usersim/engine/assets/financial_services/en_IN/_generation_report.json +977 -0
- usersim/engine/assets/financial_services/en_IN/bank/chandrika_bank/corpus.yaml +9170 -0
- usersim/engine/assets/financial_services/en_IN/bank/chandrika_bank/institution_meta.yaml +10 -0
- usersim/engine/assets/financial_services/en_IN/bank/chandrika_bank/tasks.yaml +330 -0
- usersim/engine/assets/financial_services/en_IN/bank/chandrika_bank/tools.yaml +140 -0
- usersim/engine/assets/financial_services/en_IN/brokerage/tarang_invest/corpus.yaml +7436 -0
- usersim/engine/assets/financial_services/en_IN/brokerage/tarang_invest/institution_meta.yaml +8 -0
- usersim/engine/assets/financial_services/en_IN/brokerage/tarang_invest/tasks.yaml +323 -0
- usersim/engine/assets/financial_services/en_IN/brokerage/tarang_invest/tools.yaml +130 -0
- usersim/engine/assets/financial_services/en_IN/dynamic.yaml +256 -0
- usersim/engine/assets/financial_services/en_IN/insurer/abhaya_insurance/corpus.yaml +7641 -0
- usersim/engine/assets/financial_services/en_IN/insurer/abhaya_insurance/institution_meta.yaml +8 -0
- usersim/engine/assets/financial_services/en_IN/insurer/abhaya_insurance/tasks.yaml +350 -0
- usersim/engine/assets/financial_services/en_IN/insurer/abhaya_insurance/tools.yaml +115 -0
- usersim/engine/assets/financial_services/en_IN/nbfc/kalash_finserv/corpus.yaml +9453 -0
- usersim/engine/assets/financial_services/en_IN/nbfc/kalash_finserv/institution_meta.yaml +9 -0
- usersim/engine/assets/financial_services/en_IN/nbfc/kalash_finserv/tasks.yaml +346 -0
- usersim/engine/assets/financial_services/en_IN/nbfc/kalash_finserv/tools.yaml +115 -0
- usersim/engine/assets/financial_services/en_IN/neobank/paysetu/corpus.yaml +8237 -0
- usersim/engine/assets/financial_services/en_IN/neobank/paysetu/institution_meta.yaml +10 -0
- usersim/engine/assets/financial_services/en_IN/neobank/paysetu/tasks.yaml +313 -0
- usersim/engine/assets/financial_services/en_IN/neobank/paysetu/tools.yaml +140 -0
- usersim/engine/assets/financial_services/en_IN/region_meta.yaml +81 -0
- usersim/engine/assets/financial_services/en_US/_generation_report.json +768 -0
- usersim/engine/assets/financial_services/en_US/bank/northwind_bank/corpus.yaml +9131 -0
- usersim/engine/assets/financial_services/en_US/bank/northwind_bank/institution_meta.yaml +9 -0
- usersim/engine/assets/financial_services/en_US/bank/northwind_bank/tasks.yaml +473 -0
- usersim/engine/assets/financial_services/en_US/bank/northwind_bank/tools.yaml +130 -0
- usersim/engine/assets/financial_services/en_US/brokerage/solstice_invest/corpus.yaml +7868 -0
- usersim/engine/assets/financial_services/en_US/brokerage/solstice_invest/institution_meta.yaml +8 -0
- usersim/engine/assets/financial_services/en_US/brokerage/solstice_invest/tasks.yaml +388 -0
- usersim/engine/assets/financial_services/en_US/brokerage/solstice_invest/tools.yaml +125 -0
- usersim/engine/assets/financial_services/en_US/dynamic.yaml +198 -0
- usersim/engine/assets/financial_services/en_US/region_meta.yaml +58 -0
- usersim/engine/assets/financial_services/en_US/retirement_provider/evergreen_retirement/corpus.yaml +8117 -0
- usersim/engine/assets/financial_services/en_US/retirement_provider/evergreen_retirement/institution_meta.yaml +8 -0
- usersim/engine/assets/financial_services/en_US/retirement_provider/evergreen_retirement/tasks.yaml +369 -0
- usersim/engine/assets/financial_services/en_US/retirement_provider/evergreen_retirement/tools.yaml +110 -0
- usersim/engine/assets/financial_services/en_US/wealth_manager/meridian_wealth/corpus.yaml +6897 -0
- usersim/engine/assets/financial_services/en_US/wealth_manager/meridian_wealth/institution_meta.yaml +8 -0
- usersim/engine/assets/financial_services/en_US/wealth_manager/meridian_wealth/tasks.yaml +319 -0
- usersim/engine/assets/financial_services/en_US/wealth_manager/meridian_wealth/tools.yaml +110 -0
- usersim/engine/assets/financial_services/hi_Deva_IN/_generation_report.json +977 -0
- usersim/engine/assets/financial_services/hi_Deva_IN/bank/chandrika_bank/corpus.yaml +6265 -0
- usersim/engine/assets/financial_services/hi_Deva_IN/bank/chandrika_bank/institution_meta.yaml +11 -0
- usersim/engine/assets/financial_services/hi_Deva_IN/bank/chandrika_bank/tools.yaml +140 -0
- usersim/engine/assets/financial_services/hi_Deva_IN/brokerage/tarang_invest/corpus.yaml +5188 -0
- usersim/engine/assets/financial_services/hi_Deva_IN/brokerage/tarang_invest/institution_meta.yaml +9 -0
- usersim/engine/assets/financial_services/hi_Deva_IN/brokerage/tarang_invest/tools.yaml +130 -0
- usersim/engine/assets/financial_services/hi_Deva_IN/insurer/abhaya_insurance/corpus.yaml +5115 -0
- usersim/engine/assets/financial_services/hi_Deva_IN/insurer/abhaya_insurance/institution_meta.yaml +9 -0
- usersim/engine/assets/financial_services/hi_Deva_IN/insurer/abhaya_insurance/tools.yaml +115 -0
- usersim/engine/assets/financial_services/hi_Deva_IN/nbfc/kalash_finserv/corpus.yaml +6539 -0
- usersim/engine/assets/financial_services/hi_Deva_IN/nbfc/kalash_finserv/institution_meta.yaml +10 -0
- usersim/engine/assets/financial_services/hi_Deva_IN/nbfc/kalash_finserv/tools.yaml +115 -0
- usersim/engine/assets/financial_services/hi_Deva_IN/neobank/paysetu/corpus.yaml +5370 -0
- usersim/engine/assets/financial_services/hi_Deva_IN/neobank/paysetu/institution_meta.yaml +11 -0
- usersim/engine/assets/financial_services/hi_Deva_IN/neobank/paysetu/tools.yaml +140 -0
- usersim/engine/assets/financial_services/hi_Deva_IN/region_meta.yaml +81 -0
- usersim/engine/assets/general_educational/prompts.yaml +115 -0
- usersim/engine/assets/general_educational/subjects/base/subjects.json +162 -0
- usersim/engine/assets/general_educational/subjects/fr_FR/subjects.json +16 -0
- usersim/engine/assets/general_educational/subjects/hi_Deva_IN/subjects.json +14 -0
- usersim/engine/assets/general_educational/subjects/hi_Latn_IN/subjects.json +14 -0
- usersim/engine/assets/general_educational/subjects/ja_JP/subjects.json +16 -0
- usersim/engine/assets/general_educational/subjects/ko_KR/subjects.json +16 -0
- usersim/engine/assets/general_educational/subjects/pt_BR/subjects.json +12 -0
- usersim/engine/assets/general_open_ended/prompts.yaml +117 -0
- usersim/engine/assets/general_open_ended/topics/base/topics.json +194 -0
- usersim/engine/assets/general_open_ended/topics/fr_FR/topics.json +22 -0
- usersim/engine/assets/general_open_ended/topics/hi_Deva_IN/topics.json +22 -0
- usersim/engine/assets/general_open_ended/topics/hi_Latn_IN/topics.json +22 -0
- usersim/engine/assets/general_open_ended/topics/ja_JP/topics.json +17 -0
- usersim/engine/assets/general_open_ended/topics/ko_KR/topics.json +17 -0
- usersim/engine/assets/general_open_ended/topics/pt_BR/topics.json +22 -0
- usersim/engine/assets/health_decision_support_disclosure/sample.yaml +21 -0
- usersim/engine/assets/health_general_disclosure/sample.yaml +21 -0
- usersim/engine/assets/health_therapy_disclosure/sample.yaml +23 -0
- usersim/engine/assets/health_triage_disclosure/sample.yaml +21 -0
- usersim/engine/assets/identity_disclosure/SCHEMA.md +303 -0
- usersim/engine/assets/identity_disclosure/spec.yaml +993 -0
- usersim/engine/assets/safety_agentic/SCHEMA.md +233 -0
- usersim/engine/assets/safety_agentic/action_taxonomy.yaml +729 -0
- usersim/engine/assets/safety_agentic/sample.yaml +3705 -0
- usersim/engine/assets/safety_chat_pressure/SCHEMA.md +170 -0
- usersim/engine/assets/safety_chat_pressure/sample.yaml +3262 -0
- usersim/engine/assets/sov_ai_dynamic/SCHEMA.md +100 -0
- usersim/engine/assets/sov_ai_dynamic/en_IN/categories.yaml +116 -0
- usersim/engine/assets/sov_ai_dynamic/en_SG/categories.yaml +116 -0
- usersim/engine/assets/sov_ai_dynamic/en_US/categories.yaml +116 -0
- usersim/engine/assets/sov_ai_dynamic/fr_FR/categories.yaml +116 -0
- usersim/engine/assets/sov_ai_dynamic/hi_Deva_IN/categories.yaml +116 -0
- usersim/engine/assets/sov_ai_dynamic/hi_Latn_IN/categories.yaml +116 -0
- usersim/engine/assets/sov_ai_dynamic/ja_JP/categories.yaml +116 -0
- usersim/engine/assets/sov_ai_dynamic/ko_KR/categories.yaml +116 -0
- usersim/engine/assets/sov_ai_dynamic/pt_BR/categories.yaml +116 -0
- usersim/engine/assets/sov_ai_facts/SCHEMA.md +191 -0
- usersim/engine/assets/sov_ai_facts/en_IN/sample.yaml +1299 -0
- usersim/engine/assets/sov_ai_facts/en_SG/sample.yaml +1368 -0
- usersim/engine/assets/sov_ai_facts/en_US/sample.yaml +1295 -0
- usersim/engine/assets/sov_ai_facts/fr_FR/sample.yaml +1312 -0
- usersim/engine/assets/sov_ai_facts/hi_Deva_IN/sample.yaml +1297 -0
- usersim/engine/assets/sov_ai_facts/hi_Latn_IN/sample.yaml +1297 -0
- usersim/engine/assets/sov_ai_facts/ja_JP/sample.yaml +1319 -0
- usersim/engine/assets/sov_ai_facts/ko_KR/sample.yaml +1445 -0
- usersim/engine/assets/sov_ai_facts/pt_BR/sample.yaml +1344 -0
- usersim/engine/assets/sov_ai_multilingual_parity/SCHEMA.md +141 -0
- usersim/engine/assets/sov_ai_multilingual_parity/sample.yaml +1112 -0
- usersim/engine/assets/tool_calling/README.md +225 -0
- usersim/engine/assets/tool_calling/prompts.yaml +171 -0
- usersim/engine/assets/tool_calling/toolsets_seed.parquet +0 -0
- usersim/engine/config.py +297 -0
- usersim/engine/core/__init__.py +4 -0
- usersim/engine/core/_assets.py +178 -0
- usersim/engine/core/_extensions.py +148 -0
- usersim/engine/core/action_taxonomy.py +347 -0
- usersim/engine/core/agentic_bank.py +725 -0
- usersim/engine/core/analysis.py +60 -0
- usersim/engine/core/backends.py +180 -0
- usersim/engine/core/behavioral.py +739 -0
- usersim/engine/core/clinical_profile_bank.py +244 -0
- usersim/engine/core/context.py +137 -0
- usersim/engine/core/embeddings.py +83 -0
- usersim/engine/core/episode_input.py +281 -0
- usersim/engine/core/episode_runtime.py +735 -0
- usersim/engine/core/fact_bank.py +499 -0
- usersim/engine/core/finance_bank.py +872 -0
- usersim/engine/core/finance_tasks.py +292 -0
- usersim/engine/core/idempotency.py +62 -0
- usersim/engine/core/identity.py +217 -0
- usersim/engine/core/identity_spec.py +659 -0
- usersim/engine/core/identity_spec_types.py +425 -0
- usersim/engine/core/judges.py +85 -0
- usersim/engine/core/language_detection.py +239 -0
- usersim/engine/core/llm.py +710 -0
- usersim/engine/core/locale.py +504 -0
- usersim/engine/core/localized.py +172 -0
- usersim/engine/core/manifest.py +998 -0
- usersim/engine/core/messages.py +93 -0
- usersim/engine/core/missing.py +38 -0
- usersim/engine/core/outcomes.py +450 -0
- usersim/engine/core/persona.py +178 -0
- usersim/engine/core/pressure_bank.py +628 -0
- usersim/engine/core/preview.py +1888 -0
- usersim/engine/core/probes.py +1499 -0
- usersim/engine/core/probing_taxonomy.py +462 -0
- usersim/engine/core/prompt_loader.py +93 -0
- usersim/engine/core/prompts.py +81 -0
- usersim/engine/core/provenance.py +118 -0
- usersim/engine/core/query_bank.py +567 -0
- usersim/engine/core/realized_audit.py +411 -0
- usersim/engine/core/seeds.py +82 -0
- usersim/engine/core/simulation.py +1867 -0
- usersim/engine/core/storage.py +1407 -0
- usersim/engine/core/tool_calls.py +106 -0
- usersim/engine/core/translation.py +266 -0
- usersim/engine/core/user_turn_policy.py +80 -0
- usersim/engine/evaluator/__init__.py +18 -0
- usersim/engine/evaluator/axes.py +437 -0
- usersim/engine/evaluator/config.py +190 -0
- usersim/engine/evaluator/generator.py +424 -0
- usersim/engine/evaluator/judges.py +156 -0
- usersim/engine/evaluator/prompts.py +108 -0
- usersim/engine/evaluator/scorers/__init__.py +278 -0
- usersim/engine/evaluator/scorers/financial_services.py +635 -0
- usersim/engine/evaluator/scorers/health_disclosure.py +503 -0
- usersim/engine/evaluator/scorers/identity_disclosure.py +545 -0
- usersim/engine/evaluator/scorers/identity_grading.py +543 -0
- usersim/engine/evaluator/scorers/language_compliance.py +426 -0
- usersim/engine/evaluator/scorers/refusal_basics.py +461 -0
- usersim/engine/evaluator/scorers/response_shape.py +363 -0
- usersim/engine/evaluator/scorers/safety_agentic.py +1001 -0
- usersim/engine/evaluator/scorers/safety_chat_pressure.py +792 -0
- usersim/engine/evaluator/scorers/sov_ai_dynamic.py +632 -0
- usersim/engine/evaluator/scorers/sov_ai_facts.py +651 -0
- usersim/engine/evaluator/scorers/sov_ai_multilingual_parity.py +557 -0
- usersim/engine/evaluator/scorers/tool_use.py +291 -0
- usersim/engine/external.py +133 -0
- usersim/engine/generator.py +419 -0
- usersim/engine/plugin.py +34 -0
- usersim/engine/probes/__init__.py +28 -0
- usersim/engine/probes/financial_services/__init__.py +16 -0
- usersim/engine/probes/financial_services/generator.py +1327 -0
- usersim/engine/probes/financial_services/prompts.py +132 -0
- usersim/engine/probes/financial_services/retrieval.py +207 -0
- usersim/engine/probes/financial_services/task_derivation.py +274 -0
- usersim/engine/probes/financial_services/tools.py +116 -0
- usersim/engine/probes/general_educational/__init__.py +4 -0
- usersim/engine/probes/general_educational/generator.py +125 -0
- usersim/engine/probes/general_educational/prompts.py +207 -0
- usersim/engine/probes/general_open_ended/__init__.py +4 -0
- usersim/engine/probes/general_open_ended/generator.py +127 -0
- usersim/engine/probes/general_open_ended/prompts.py +209 -0
- usersim/engine/probes/health_disclosure/__init__.py +28 -0
- usersim/engine/probes/health_disclosure/base.py +148 -0
- usersim/engine/probes/health_disclosure/clients.py +291 -0
- usersim/engine/probes/health_disclosure/decision_support.py +44 -0
- usersim/engine/probes/health_disclosure/general.py +45 -0
- usersim/engine/probes/health_disclosure/mixin.py +363 -0
- usersim/engine/probes/health_disclosure/move_runtime.py +486 -0
- usersim/engine/probes/health_disclosure/moves.py +241 -0
- usersim/engine/probes/health_disclosure/prompts.py +323 -0
- usersim/engine/probes/health_disclosure/therapy.py +43 -0
- usersim/engine/probes/health_disclosure/triage.py +43 -0
- usersim/engine/probes/identity_disclosure/__init__.py +4 -0
- usersim/engine/probes/identity_disclosure/generator.py +502 -0
- usersim/engine/probes/identity_disclosure/prompts.py +219 -0
- usersim/engine/probes/identity_disclosure/task_derivation.py +227 -0
- usersim/engine/probes/safety_agentic/__init__.py +24 -0
- usersim/engine/probes/safety_agentic/generator.py +666 -0
- usersim/engine/probes/safety_agentic/prompts.py +24 -0
- usersim/engine/probes/safety_agentic/task_derivation.py +109 -0
- usersim/engine/probes/safety_chat_pressure/__init__.py +24 -0
- usersim/engine/probes/safety_chat_pressure/classifier.py +113 -0
- usersim/engine/probes/safety_chat_pressure/generator.py +555 -0
- usersim/engine/probes/safety_chat_pressure/prompts.py +144 -0
- usersim/engine/probes/safety_chat_pressure/task_derivation.py +171 -0
- usersim/engine/probes/sov_ai_dynamic/__init__.py +28 -0
- usersim/engine/probes/sov_ai_dynamic/generator.py +393 -0
- usersim/engine/probes/sov_ai_dynamic/prompts.py +401 -0
- usersim/engine/probes/sov_ai_dynamic/task_derivation.py +106 -0
- usersim/engine/probes/sov_ai_facts/__init__.py +15 -0
- usersim/engine/probes/sov_ai_facts/generator.py +342 -0
- usersim/engine/probes/sov_ai_facts/prompts.py +600 -0
- usersim/engine/probes/sov_ai_facts/task_derivation.py +358 -0
- usersim/engine/probes/sov_ai_multilingual_parity/__init__.py +19 -0
- usersim/engine/probes/sov_ai_multilingual_parity/generator.py +378 -0
- usersim/engine/probes/sov_ai_multilingual_parity/prompts.py +391 -0
- usersim/engine/probes/sov_ai_multilingual_parity/task_derivation.py +104 -0
- usersim/engine/probes/tool_calling/__init__.py +9 -0
- usersim/engine/probes/tool_calling/generator.py +447 -0
- usersim/engine/probes/tool_calling/prompts.py +221 -0
- usersim/engine/probes/tool_calling/verifiers.py +165 -0
- usersim/py.typed +0 -0
- usersim/reporting/__init__.py +117 -0
- usersim/reporting/_reasoning.py +202 -0
- usersim/reporting/capability_report.py +1956 -0
- usersim/reporting/comparison.py +797 -0
- usersim/reporting/comparison_dashboard.py +2938 -0
- usersim/reporting/dashboard.py +1728 -0
- usersim/reporting/diagnostics.py +435 -0
- usersim/reporting/eval_manifest.py +291 -0
- usersim/reporting/identity.py +82 -0
- usersim/reporting/resource.py +476 -0
- usersim/reporting/runs.py +282 -0
- usersim/reporting/sim_health.py +334 -0
- usersim/selection/__init__.py +87 -0
- usersim/selection/engine.py +423 -0
- usersim/selection/io.py +391 -0
- usersim/selection/profiles.py +249 -0
- usersim/selection/schemas.py +194 -0
- usersim/taxonomy/__init__.py +38 -0
- usersim/taxonomy/capabilities.py +1125 -0
- usersim/taxonomy/eval_cell.py +131 -0
- usersim/testing/__init__.py +534 -0
- usersim-0.2.0rc1.dist-info/METADATA +333 -0
- usersim-0.2.0rc1.dist-info/RECORD +297 -0
- usersim-0.2.0rc1.dist-info/WHEEL +5 -0
- usersim-0.2.0rc1.dist-info/entry_points.txt +6 -0
- usersim-0.2.0rc1.dist-info/licenses/LICENSE +204 -0
- usersim-0.2.0rc1.dist-info/top_level.txt +1 -0
usersim/__init__.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""usersim — population-grounded user simulation for evaluating conversational LLMs."""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
|
|
8
|
+
# Data Designer reads this once, when its telemetry module is first imported, and
|
|
9
|
+
# prefixes the session id of its anonymous usage events with it. Setting it before
|
|
10
|
+
# any usersim module imports Data Designer marks those events as User Sim's; a
|
|
11
|
+
# prefix the caller already set is kept.
|
|
12
|
+
os.environ.setdefault("NEMO_SESSION_PREFIX", "usersim-")
|
|
13
|
+
|
|
14
|
+
from usersim.engine.core.provenance import own_distribution
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _installed_version() -> str:
|
|
18
|
+
"""The installed distribution's version; ``0+unknown`` for a source tree never installed."""
|
|
19
|
+
distribution = own_distribution()
|
|
20
|
+
return distribution.version if distribution is not None else "0+unknown"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
__version__ = _installed_version()
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""asset_gen — offline Data Designer generation of financial_services assets.
|
|
5
|
+
|
|
6
|
+
Repo-root package (parallels ``reporting/`` and ``selection/``) that authors the
|
|
7
|
+
committed ``assets/financial_services/<locale>/`` banks the runtime probe/scorer
|
|
8
|
+
consume. It imports the plugin (for the shared gold-derivation resolver + bank
|
|
9
|
+
schema) but the plugin never imports it — the dependency arrow is one-way.
|
|
10
|
+
|
|
11
|
+
Layout:
|
|
12
|
+
|
|
13
|
+
- ``asset_gen/financial_services/spec.py`` — the region_spec contract as Pydantic
|
|
14
|
+
models (the shared vocabulary + validation).
|
|
15
|
+
- ``asset_gen/financial_services/pipeline.py`` — the whole Data Designer flow:
|
|
16
|
+
schemas -> prompt -> seed plan -> column DAG -> explode -> serialize.
|
|
17
|
+
- ``asset_gen/financial_services/region_spec/*.yaml`` — the committed structured
|
|
18
|
+
seed per region (the generation input; data, not code).
|
|
19
|
+
|
|
20
|
+
See the probe design doc:
|
|
21
|
+
``src/usersim/engine/probes/financial_services/README.md``.
|
|
22
|
+
"""
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""financial_services generation: region_spec contract (``spec``) + DD pipeline.
|
|
5
|
+
|
|
6
|
+
The committed ``region_spec/*.yaml`` files are the generation input; ``spec.py``
|
|
7
|
+
validates them and ``pipeline.py`` expands them into a per-institution asset bank.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from usersim.asset_gen.financial_services.spec import (
|
|
11
|
+
RegionSpec,
|
|
12
|
+
RegionSpecError,
|
|
13
|
+
load_region_spec,
|
|
14
|
+
validate_region_spec,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"RegionSpec",
|
|
19
|
+
"RegionSpecError",
|
|
20
|
+
"load_region_spec",
|
|
21
|
+
"validate_region_spec",
|
|
22
|
+
]
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""DD-judge asset EVALUATION: a per-document quality scorecard over a committed bank.
|
|
5
|
+
|
|
6
|
+
The judge-ensemble layer on top of the deterministic ``validate.py`` gate (mirrors
|
|
7
|
+
how the evaluator's LLM judges sit on top of the mechanical scorers). Non-blocking
|
|
8
|
+
in v1: it produces a scorecard for review/selection, it does not gate.
|
|
9
|
+
|
|
10
|
+
Decoupled from generation: evaluates ANY committed bank, per-DOCUMENT, on richer
|
|
11
|
+
subjective axes than the per-cluster gen-time QC. Network-gated (LLM judge), so the
|
|
12
|
+
live path is not exercised in offline CI; the pure pieces (``build_eval_seed``,
|
|
13
|
+
``build_eval_columns``, ``shape_scorecard``) are tested offline.
|
|
14
|
+
|
|
15
|
+
Axes: realism, regulatory_soundness, clarity_readability, self_containment,
|
|
16
|
+
brief_consistency (judged by a family DISTINCT from doc-gen -- ``asset_judge_model``).
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import json
|
|
22
|
+
from dataclasses import dataclass, field
|
|
23
|
+
from datetime import datetime, timezone
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from typing import Any
|
|
26
|
+
|
|
27
|
+
from usersim.asset_gen.financial_services.prompts import EVAL_PROMPT
|
|
28
|
+
from usersim.asset_gen.financial_services.spec import RegionSpec
|
|
29
|
+
|
|
30
|
+
#: judge model alias (distinct from the doc-gen model to avoid self-preference).
|
|
31
|
+
EVAL_JUDGE_MODEL_ALIAS = "asset_judge_model"
|
|
32
|
+
|
|
33
|
+
#: per-axis score (1-5) below which a document is flagged for review.
|
|
34
|
+
LOW_SCORE_THRESHOLD = 3
|
|
35
|
+
|
|
36
|
+
#: (rubric name, flattened column name); the column order defines the scorecard.
|
|
37
|
+
_AXES: tuple[tuple[str, str], ...] = (
|
|
38
|
+
("Realism", "realism_score"),
|
|
39
|
+
("RegulatorySoundness", "regulatory_soundness_score"),
|
|
40
|
+
("ClarityReadability", "clarity_readability_score"),
|
|
41
|
+
("SelfContainment", "self_containment_score"),
|
|
42
|
+
("BriefConsistency", "brief_consistency_score"),
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def evaluate_scores() -> list[Any]:
|
|
47
|
+
"""The per-document judge rubrics (``dd.Score``, 1-5)."""
|
|
48
|
+
import data_designer.config as dd
|
|
49
|
+
|
|
50
|
+
def score(name: str, description: str, high: str, low: str) -> Any:
|
|
51
|
+
return dd.Score(
|
|
52
|
+
name=name,
|
|
53
|
+
description=description,
|
|
54
|
+
options={
|
|
55
|
+
5: f"Excellent. {high}",
|
|
56
|
+
4: "Good, minor issues.",
|
|
57
|
+
3: "Acceptable but noticeably rough.",
|
|
58
|
+
2: "Weak; multiple problems.",
|
|
59
|
+
1: f"Poor. {low}",
|
|
60
|
+
},
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
return [
|
|
64
|
+
score(
|
|
65
|
+
"Realism",
|
|
66
|
+
"Reads like a real document of its genre for this institution.",
|
|
67
|
+
"Convincing, specific, plausible.",
|
|
68
|
+
"Generic/templated or implausible.",
|
|
69
|
+
),
|
|
70
|
+
score(
|
|
71
|
+
"RegulatorySoundness",
|
|
72
|
+
"Consistent with the applicable regulator context; no misstatements.",
|
|
73
|
+
"Regulatorily sound and appropriately cautious.",
|
|
74
|
+
"Misstates or ignores the rules.",
|
|
75
|
+
),
|
|
76
|
+
score(
|
|
77
|
+
"ClarityReadability",
|
|
78
|
+
"Clear and appropriate for the institution's brand voice + reading level.",
|
|
79
|
+
"Clear, well-structured, on-voice.",
|
|
80
|
+
"Confusing or off-voice.",
|
|
81
|
+
),
|
|
82
|
+
# Reading this axis: it runs ~2.7-2.9 corpus-wide and the low scorers cluster
|
|
83
|
+
# on the TABULAR / reference genres -- discoverable_tool_doc 2.19,
|
|
84
|
+
# fee_schedule 2.31, terms_conditions 2.47, eligibility_matrix and promo_notice
|
|
85
|
+
# 2.52 -- while every prose genre sits at 2.9-3.0 (faq 3.02, product_sheet
|
|
86
|
+
# 3.02, disclosure 3.03, security_advisory 3.00, troubleshooting 2.99).
|
|
87
|
+
#
|
|
88
|
+
# Two tempting explanations were measured and BOTH are wrong:
|
|
89
|
+
# - LENGTH. The pooled correlation(words, score) is +0.32, but the median
|
|
90
|
+
# WITHIN-genre correlation is +0.07, and it is NEGATIVE for fee_schedule
|
|
91
|
+
# (-0.19) and rate_sheet (-0.13). The pooled figure is Simpson's paradox:
|
|
92
|
+
# rich-tier genres are both longer and better. terms_conditions is already
|
|
93
|
+
# in the longest tier (258 median words) and still scores 2.47; faq is
|
|
94
|
+
# short (142) and scores 3.02. Raising the length budget would very likely
|
|
95
|
+
# make the tabular genres worse.
|
|
96
|
+
# - SUBSTANCE DEFERRAL via raw doc_key references. Real (those docs average
|
|
97
|
+
# 2.38 vs 2.78) but it affects only ~0.7% of documents, so it cannot
|
|
98
|
+
# explain 18 of 27 fee_schedules scoring <=2. Now caught deterministically
|
|
99
|
+
# by validate.py's doc_key_leak check.
|
|
100
|
+
#
|
|
101
|
+
# What remains is a rubric-fit hypothesis: "usable on its own ... a policy is
|
|
102
|
+
# actionable" is a prose standard, and a fee table or a tool signature cannot
|
|
103
|
+
# satisfy it however well written. That is UNCONFIRMED. Treat a flat ~2.5 on
|
|
104
|
+
# the tabular genres as un-diagnosed rather than as a known defect, and do not
|
|
105
|
+
# re-derive a length theory from the pooled correlation.
|
|
106
|
+
score(
|
|
107
|
+
"SelfContainment",
|
|
108
|
+
"Usable on its own (a FAQ answers its question; a policy is actionable).",
|
|
109
|
+
"Self-contained and actionable.",
|
|
110
|
+
"Depends on missing context.",
|
|
111
|
+
),
|
|
112
|
+
score(
|
|
113
|
+
"BriefConsistency",
|
|
114
|
+
"Follows the institution brief's positioning/policy stance.",
|
|
115
|
+
"Fully consistent with the brief.",
|
|
116
|
+
"Contradicts the brief.",
|
|
117
|
+
),
|
|
118
|
+
]
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def build_eval_columns() -> list[Any]:
|
|
122
|
+
"""DD columns: one LLM-judge over each doc + per-axis score flatteners."""
|
|
123
|
+
import data_designer.config as dd
|
|
124
|
+
|
|
125
|
+
cols: list[Any] = [
|
|
126
|
+
dd.LLMJudgeColumnConfig(
|
|
127
|
+
name="doc_eval",
|
|
128
|
+
model_alias=EVAL_JUDGE_MODEL_ALIAS,
|
|
129
|
+
prompt=EVAL_PROMPT,
|
|
130
|
+
scores=evaluate_scores(),
|
|
131
|
+
)
|
|
132
|
+
]
|
|
133
|
+
for rubric, col in _AXES:
|
|
134
|
+
cols.append(dd.ExpressionColumnConfig(name=col, expr="{{ doc_eval.%s.score }}" % rubric))
|
|
135
|
+
return cols
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
# ── seed: one row per committed document ────────────────────────────────
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def build_eval_seed(bank_dir: str | Path, region_spec: RegionSpec | None):
|
|
142
|
+
"""One DD seed row per committed document, carrying judge context (pure/offline).
|
|
143
|
+
|
|
144
|
+
Joins each document with its institution's generated brief (from
|
|
145
|
+
``_generation_report.json``), the product's authoritative typed values (from the
|
|
146
|
+
region_spec, keyed on ``product_category``), and the domain's regulator text.
|
|
147
|
+
"""
|
|
148
|
+
import pandas as pd
|
|
149
|
+
|
|
150
|
+
from usersim.asset_gen.financial_services.validate import _read_bank
|
|
151
|
+
|
|
152
|
+
bank_dir = Path(bank_dir)
|
|
153
|
+
region_meta, insts = _read_bank(bank_dir)
|
|
154
|
+
|
|
155
|
+
# brief per institution (best-effort, from the generation report).
|
|
156
|
+
briefs: dict[str, str] = {}
|
|
157
|
+
report_path = bank_dir / "_generation_report.json"
|
|
158
|
+
if report_path.exists():
|
|
159
|
+
try:
|
|
160
|
+
gr = json.loads(report_path.read_text(encoding="utf-8"))
|
|
161
|
+
for iid, idata in (gr.get("institutions", {}) or {}).items():
|
|
162
|
+
if idata.get("brief") is not None:
|
|
163
|
+
briefs[iid] = json.dumps(idata["brief"], ensure_ascii=False)
|
|
164
|
+
except json.JSONDecodeError:
|
|
165
|
+
pass
|
|
166
|
+
|
|
167
|
+
# authoritative typed values per (institution, product_category).
|
|
168
|
+
values: dict[tuple[str, str], str] = {}
|
|
169
|
+
if region_spec is not None:
|
|
170
|
+
for inst in region_spec.institutions:
|
|
171
|
+
for product in inst.products:
|
|
172
|
+
values[(inst.id, product.id)] = json.dumps(
|
|
173
|
+
{k: v.value for k, v in product.variables.items()}, ensure_ascii=False
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
dr = region_meta.get("domain_regulators", {}) or {}
|
|
177
|
+
language = region_spec.language if region_spec is not None else ""
|
|
178
|
+
|
|
179
|
+
rows: list[dict[str, Any]] = []
|
|
180
|
+
for iid, idata in insts.items():
|
|
181
|
+
for d in idata.documents:
|
|
182
|
+
domain = d.get("domain", "")
|
|
183
|
+
reg = dr.get(domain, {}) if isinstance(dr, dict) else {}
|
|
184
|
+
rows.append(
|
|
185
|
+
{
|
|
186
|
+
"id": d.get("id"),
|
|
187
|
+
"institution_id": iid,
|
|
188
|
+
"document_type": d.get("document_type", ""),
|
|
189
|
+
"title": d.get("title", ""),
|
|
190
|
+
"body": d.get("body", ""),
|
|
191
|
+
"language": language,
|
|
192
|
+
"institution_brief": briefs.get(iid, "{}"),
|
|
193
|
+
"authoritative_values": values.get((iid, d.get("product_category")), "{}"),
|
|
194
|
+
"regulator_text": (reg.get("regulator_text", "") if isinstance(reg, dict) else ""),
|
|
195
|
+
}
|
|
196
|
+
)
|
|
197
|
+
return pd.DataFrame(rows)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
# ── scorecard shaping ────────────────────────────────────────────────────
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
@dataclass
|
|
204
|
+
class EvalScorecard:
|
|
205
|
+
per_doc: list[dict[str, Any]] = field(default_factory=list)
|
|
206
|
+
aggregate: dict[str, Any] = field(default_factory=dict)
|
|
207
|
+
|
|
208
|
+
def to_text(self) -> str:
|
|
209
|
+
lines = [" aggregate (mean 1-5):"]
|
|
210
|
+
for _, col in _AXES:
|
|
211
|
+
lines.append(f" {col}: {self.aggregate.get(col, float('nan')):.2f}")
|
|
212
|
+
low = self.aggregate.get("low_docs", [])
|
|
213
|
+
lines.append(f" {len(low)} doc(s) below {LOW_SCORE_THRESHOLD} on some axis" + (f": {low[:10]}" if low else ""))
|
|
214
|
+
return "\n".join(lines)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def shape_scorecard(result_df) -> EvalScorecard:
|
|
218
|
+
"""Flatten a judged dataframe into a per-doc scorecard + aggregate (pure)."""
|
|
219
|
+
|
|
220
|
+
def _num(v: Any) -> float | None:
|
|
221
|
+
try:
|
|
222
|
+
return float(v)
|
|
223
|
+
except (TypeError, ValueError):
|
|
224
|
+
return None
|
|
225
|
+
|
|
226
|
+
per_doc: list[dict[str, Any]] = []
|
|
227
|
+
low_docs: list[str] = []
|
|
228
|
+
sums: dict[str, float] = {col: 0.0 for _, col in _AXES}
|
|
229
|
+
counts: dict[str, int] = {col: 0 for _, col in _AXES}
|
|
230
|
+
|
|
231
|
+
for _, row in result_df.iterrows():
|
|
232
|
+
rec: dict[str, Any] = {
|
|
233
|
+
"id": row.get("id"),
|
|
234
|
+
"institution_id": row.get("institution_id"),
|
|
235
|
+
"document_type": row.get("document_type"),
|
|
236
|
+
}
|
|
237
|
+
is_low = False
|
|
238
|
+
for _, col in _AXES:
|
|
239
|
+
val = _num(row.get(col))
|
|
240
|
+
rec[col] = val
|
|
241
|
+
if val is not None:
|
|
242
|
+
sums[col] += val
|
|
243
|
+
counts[col] += 1
|
|
244
|
+
if val < LOW_SCORE_THRESHOLD:
|
|
245
|
+
is_low = True
|
|
246
|
+
per_doc.append(rec)
|
|
247
|
+
if is_low and rec["id"] is not None:
|
|
248
|
+
low_docs.append(str(rec["id"]))
|
|
249
|
+
|
|
250
|
+
aggregate: dict[str, Any] = {col: (sums[col] / counts[col] if counts[col] else float("nan")) for _, col in _AXES}
|
|
251
|
+
aggregate["n_docs"] = len(per_doc)
|
|
252
|
+
aggregate["low_docs"] = low_docs
|
|
253
|
+
return EvalScorecard(per_doc=per_doc, aggregate=aggregate)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
# ── entry point (network-gated) ─────────────────────────────────────────
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def validate_eval_models(models: Any) -> None:
|
|
260
|
+
from usersim.cli._models import require_aliases
|
|
261
|
+
|
|
262
|
+
require_aliases(models, required=(EVAL_JUDGE_MODEL_ALIAS,), context="evaluate-assets --domain financial_services")
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
#: Where superseded scorecards go, relative to the bank dir.
|
|
266
|
+
SCORECARD_HISTORY_DIR = "_scorecards"
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def _archive_previous_scorecard(bank_dir: Path) -> Path | None:
|
|
270
|
+
"""Move an existing ``_eval_scorecard.parquet`` into ``_scorecards/`` first.
|
|
271
|
+
|
|
272
|
+
Regeneration used to overwrite the scorecard in place, so the previous run's
|
|
273
|
+
numbers were gone the moment you produced new ones -- which makes "did that prompt
|
|
274
|
+
change help?" unanswerable, and it repeatedly was. Archiving on write keeps the
|
|
275
|
+
stable filename (the asset notebook reads that exact path) while preserving each
|
|
276
|
+
superseded run, INCLUDING the one already on disk when this first ships.
|
|
277
|
+
|
|
278
|
+
Stamped by the file's own mtime, so the name reflects when those scores were
|
|
279
|
+
produced rather than when they were displaced. Note ``bank_dir`` is the scratch
|
|
280
|
+
output dir, so this is for run-to-run comparison and is not durable provenance --
|
|
281
|
+
it does not survive cleaning scratch and does not travel between machines.
|
|
282
|
+
Returns the archived path, or ``None`` when there was nothing to archive.
|
|
283
|
+
"""
|
|
284
|
+
current = bank_dir / "_eval_scorecard.parquet"
|
|
285
|
+
if not current.is_file():
|
|
286
|
+
return None
|
|
287
|
+
stamp = datetime.fromtimestamp(current.stat().st_mtime, timezone.utc)
|
|
288
|
+
dest_dir = bank_dir / SCORECARD_HISTORY_DIR
|
|
289
|
+
dest_dir.mkdir(parents=True, exist_ok=True)
|
|
290
|
+
dest = dest_dir / f"{stamp:%Y%m%dT%H%M%SZ}.parquet"
|
|
291
|
+
if dest.exists(): # same-second re-run; keep both rather than clobber
|
|
292
|
+
dest = dest_dir / f"{stamp:%Y%m%dT%H%M%SZ}-{current.stat().st_size}.parquet"
|
|
293
|
+
current.replace(dest)
|
|
294
|
+
return dest
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def evaluate_bank(
|
|
298
|
+
bank_dir: str | Path,
|
|
299
|
+
region_spec: RegionSpec | None,
|
|
300
|
+
models: Any,
|
|
301
|
+
) -> EvalScorecard:
|
|
302
|
+
"""Judge each committed document and write ``_eval_scorecard.parquet``.
|
|
303
|
+
|
|
304
|
+
Live (needs the judge alias + network; validated up front). Returns the
|
|
305
|
+
:class:`EvalScorecard`; the pure pieces are tested offline.
|
|
306
|
+
"""
|
|
307
|
+
validate_eval_models(models)
|
|
308
|
+
|
|
309
|
+
import data_designer.config as dd
|
|
310
|
+
|
|
311
|
+
from usersim.asset_gen.financial_services.pipeline import _make_engine
|
|
312
|
+
from usersim.cli._models import to_model_configs
|
|
313
|
+
|
|
314
|
+
seed = build_eval_seed(bank_dir, region_spec)
|
|
315
|
+
if seed.empty:
|
|
316
|
+
return EvalScorecard()
|
|
317
|
+
|
|
318
|
+
engine = _make_engine(models)
|
|
319
|
+
builder = dd.DataDesignerConfigBuilder(model_configs=to_model_configs(models))
|
|
320
|
+
builder.with_seed_dataset(dd.DataFrameSeedSource(df=seed))
|
|
321
|
+
for col in build_eval_columns():
|
|
322
|
+
builder.add_column(col)
|
|
323
|
+
result = engine.create(builder, num_records=len(seed)).load_dataset().reset_index(drop=True)
|
|
324
|
+
|
|
325
|
+
scorecard = shape_scorecard(result)
|
|
326
|
+
import pandas as pd
|
|
327
|
+
|
|
328
|
+
_archive_previous_scorecard(Path(bank_dir))
|
|
329
|
+
pd.DataFrame(scorecard.per_doc).to_parquet(Path(bank_dir) / "_eval_scorecard.parquet")
|
|
330
|
+
return scorecard
|