agent-learning-kit 0.1.0__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.
- agent_learning_kit-0.1.0.dist-info/METADATA +381 -0
- agent_learning_kit-0.1.0.dist-info/RECORD +642 -0
- agent_learning_kit-0.1.0.dist-info/WHEEL +4 -0
- agent_learning_kit-0.1.0.dist-info/entry_points.txt +5 -0
- agent_learning_kit-0.1.0.dist-info/licenses/LICENSE +173 -0
- agent_learning_kit-0.1.0.dist-info/licenses/NOTICE +7 -0
- fi/__init__.py +5 -0
- fi/alk/__init__.py +57 -0
- fi/alk/_facade.py +31 -0
- fi/alk/_module_alias.py +68 -0
- fi/alk/_paths.py +14 -0
- fi/alk/_schema.py +522 -0
- fi/alk/actions.py +727 -0
- fi/alk/bench/__init__.py +517 -0
- fi/alk/bench/_codeexec.py +213 -0
- fi/alk/bench/_coding.py +215 -0
- fi/alk/bench/_docker.py +237 -0
- fi/alk/bench/_grader.py +286 -0
- fi/alk/bench/_pull.py +212 -0
- fi/alk/bench/_voice.py +147 -0
- fi/alk/capabilities.py +627 -0
- fi/alk/cli.py +6396 -0
- fi/alk/config.py +130 -0
- fi/alk/cua_loop.py +562 -0
- fi/alk/evals.py +2351 -0
- fi/alk/extensions.py +163 -0
- fi/alk/harness/ARCHITECTURE.md +231 -0
- fi/alk/harness/DESIGN.md +246 -0
- fi/alk/harness/ENVIRONMENT_CONFORMANCE.md +127 -0
- fi/alk/harness/HOW-IT-WORKS.md +297 -0
- fi/alk/harness/IMPLEMENTATION_AND_VALIDATION_STATUS.md +229 -0
- fi/alk/harness/README.md +417 -0
- fi/alk/harness/__init__.py +77 -0
- fi/alk/harness/__main__.py +3 -0
- fi/alk/harness/amend.py +312 -0
- fi/alk/harness/artifacts.py +319 -0
- fi/alk/harness/authoring_entrypoint.py +189 -0
- fi/alk/harness/authoring_runtime_validation.py +267 -0
- fi/alk/harness/backends/README.md +43 -0
- fi/alk/harness/backends/__init__.py +122 -0
- fi/alk/harness/backends/base.py +241 -0
- fi/alk/harness/backends/claude.py +211 -0
- fi/alk/harness/backends/files.py +182 -0
- fi/alk/harness/backends/vertex_gemini.py +457 -0
- fi/alk/harness/background_noise.py +95 -0
- fi/alk/harness/build.py +385 -0
- fi/alk/harness/bundle.py +593 -0
- fi/alk/harness/bundle_author_v2.py +1831 -0
- fi/alk/harness/bundle_v2.py +719 -0
- fi/alk/harness/call_runner.py +1440 -0
- fi/alk/harness/callback_http_adapter.py +111 -0
- fi/alk/harness/catalogue.py +287 -0
- fi/alk/harness/chat.py +428 -0
- fi/alk/harness/chat_call_runner.py +506 -0
- fi/alk/harness/checks.py +136 -0
- fi/alk/harness/cli.py +1354 -0
- fi/alk/harness/config.py +338 -0
- fi/alk/harness/contract.py +718 -0
- fi/alk/harness/credentials.py +674 -0
- fi/alk/harness/data/persona_vocabulary.json +111 -0
- fi/alk/harness/environment.py +99 -0
- fi/alk/harness/environment_plan.py +168 -0
- fi/alk/harness/events.py +125 -0
- fi/alk/harness/executor.py +304 -0
- fi/alk/harness/folder.py +234 -0
- fi/alk/harness/generated_runtime.py +815 -0
- fi/alk/harness/github.py +72 -0
- fi/alk/harness/hosted_authoring_entrypoint.py +183 -0
- fi/alk/harness/hosted_entrypoint.py +2402 -0
- fi/alk/harness/hosted_scheduler.py +2218 -0
- fi/alk/harness/job.py +426 -0
- fi/alk/harness/judge.py +184 -0
- fi/alk/harness/livekit_source.py +50 -0
- fi/alk/harness/livekit_tool_trace_bootstrap.py +71 -0
- fi/alk/harness/observability.py +208 -0
- fi/alk/harness/outbound.py +3252 -0
- fi/alk/harness/packaging.py +515 -0
- fi/alk/harness/persona_guides.py +157 -0
- fi/alk/harness/platform.py +692 -0
- fi/alk/harness/process_preflight.py +764 -0
- fi/alk/harness/process_runtime.py +5670 -0
- fi/alk/harness/prove.py +425 -0
- fi/alk/harness/provider_import.py +703 -0
- fi/alk/harness/provider_lifecycle.py +392 -0
- fi/alk/harness/provision.py +2896 -0
- fi/alk/harness/reception.py +147 -0
- fi/alk/harness/retell_chat_call_runner.py +373 -0
- fi/alk/harness/run/__init__.py +296 -0
- fi/alk/harness/run/alk.py +184 -0
- fi/alk/harness/run/call.py +162 -0
- fi/alk/harness/run/conversation.py +264 -0
- fi/alk/harness/run/data/voices_by_language_and_gender.json +693 -0
- fi/alk/harness/run/evidence.py +195 -0
- fi/alk/harness/run/grade.py +598 -0
- fi/alk/harness/run/live.py +297 -0
- fi/alk/harness/run/models.py +56 -0
- fi/alk/harness/run/platform_evals.py +227 -0
- fi/alk/harness/run/sdk_voice.py +130 -0
- fi/alk/harness/run/simulation.py +1209 -0
- fi/alk/harness/run/stage.py +91 -0
- fi/alk/harness/run/targets.py +508 -0
- fi/alk/harness/run/tools.py +601 -0
- fi/alk/harness/run/voice.py +340 -0
- fi/alk/harness/runtime.py +172 -0
- fi/alk/harness/sandbox_server.py +2011 -0
- fi/alk/harness/sandbox_worker.py +44 -0
- fi/alk/harness/scenario.py +1048 -0
- fi/alk/harness/scenario_source.py +879 -0
- fi/alk/harness/scenario_tools.py +1143 -0
- fi/alk/harness/scenarios.py +915 -0
- fi/alk/harness/secrets.py +168 -0
- fi/alk/harness/service_catalog.py +97 -0
- fi/alk/harness/session.py +391 -0
- fi/alk/harness/sessions.py +372 -0
- fi/alk/harness/simulator.py +76 -0
- fi/alk/harness/simulator_voice.py +928 -0
- fi/alk/harness/skills/build-environment/SKILL.md +538 -0
- fi/alk/harness/skills/harness.md +131 -0
- fi/alk/harness/skills/kinds/chat.md +48 -0
- fi/alk/harness/skills/kinds/voice-voicemail.md +63 -0
- fi/alk/harness/skills/kinds/voice.md +59 -0
- fi/alk/harness/skills/plan-suite/SKILL.md +103 -0
- fi/alk/harness/skills/provision-environment/SKILL.md +136 -0
- fi/alk/harness/skills/run-scenarios/SKILL.md +112 -0
- fi/alk/harness/skills/understand-agent/SKILL.md +251 -0
- fi/alk/harness/skills/write-scenarios/SKILL.md +606 -0
- fi/alk/harness/skills/write-scenarios/references/refusals.md +28 -0
- fi/alk/harness/skills/write-scenarios/references/world-api.md +92 -0
- fi/alk/harness/source_data_invariants.py +444 -0
- fi/alk/harness/source_tool_evidence.py +79 -0
- fi/alk/harness/sources.py +253 -0
- fi/alk/harness/spend.py +140 -0
- fi/alk/harness/tool_trace_proxy.py +104 -0
- fi/alk/harness/tools.py +1018 -0
- fi/alk/harness/understand.py +169 -0
- fi/alk/harness/voicemail_audio.py +74 -0
- fi/alk/harness/world/__init__.py +33 -0
- fi/alk/harness/world/errors.py +68 -0
- fi/alk/harness/world/expectations.py +91 -0
- fi/alk/harness/world/handle.py +538 -0
- fi/alk/harness/world/kinds.py +196 -0
- fi/alk/harness/world/mutate.py +186 -0
- fi/alk/harness/world/probe.py +413 -0
- fi/alk/harness/world/provision.py +511 -0
- fi/alk/harness/world/provisioned.py +191 -0
- fi/alk/harness/world/runtime.py +616 -0
- fi/alk/harness/world/snapshot.py +288 -0
- fi/alk/harness/world/stores/__init__.py +305 -0
- fi/alk/harness/world/stores/container.py +215 -0
- fi/alk/harness/world/stores/inprocess.py +346 -0
- fi/alk/harness/world/stores/postgres.py +481 -0
- fi/alk/harness/world/stores/prove.py +202 -0
- fi/alk/harness/world/stores/sqlite.py +245 -0
- fi/alk/harness/world/stores/written.py +182 -0
- fi/alk/harness/world/tools.py +1516 -0
- fi/alk/harness/world/workspace.py +144 -0
- fi/alk/image_loop.py +453 -0
- fi/alk/image_perturb.py +241 -0
- fi/alk/improve.py +274 -0
- fi/alk/live/__init__.py +154 -0
- fi/alk/live/_attribution.py +184 -0
- fi/alk/live/_capture.py +264 -0
- fi/alk/live/_codec.py +391 -0
- fi/alk/live/_contract.py +134 -0
- fi/alk/live/_loopback.py +316 -0
- fi/alk/live/_perturb.py +449 -0
- fi/alk/live/_runner.py +386 -0
- fi/alk/live/_stats.py +561 -0
- fi/alk/live/_transcript.py +240 -0
- fi/alk/live/_workers/__init__.py +9 -0
- fi/alk/live/_workers/a2a_worker.py +316 -0
- fi/alk/live/_workers/langgraph_worker.py +217 -0
- fi/alk/live/_workers/livekit_worker.py +207 -0
- fi/alk/live/_workers/mcp_loopback_server.py +46 -0
- fi/alk/live/_workers/mcp_worker.py +158 -0
- fi/alk/live/_workers/pipecat_worker.py +189 -0
- fi/alk/live/a2a_lane.py +138 -0
- fi/alk/live/langgraph_lane.py +339 -0
- fi/alk/live/livekit_lane.py +376 -0
- fi/alk/live/mcp_lane.py +172 -0
- fi/alk/live/pipecat_lane.py +341 -0
- fi/alk/live/voice_redteam.py +494 -0
- fi/alk/loss.py +306 -0
- fi/alk/optimize.py +36260 -0
- fi/alk/practice/__init__.py +51 -0
- fi/alk/practice/_assess.py +103 -0
- fi/alk/practice/_budget.py +81 -0
- fi/alk/practice/_calibrate.py +69 -0
- fi/alk/practice/_capstone.py +86 -0
- fi/alk/practice/_contract.py +91 -0
- fi/alk/practice/_diagnose.py +79 -0
- fi/alk/practice/_drill.py +196 -0
- fi/alk/practice/_experiment.py +720 -0
- fi/alk/practice/_schedule.py +102 -0
- fi/alk/practice/_store.py +194 -0
- fi/alk/practice/_trainer.py +245 -0
- fi/alk/practice/_update.py +125 -0
- fi/alk/redteam.py +2621 -0
- fi/alk/rewardhack.py +237 -0
- fi/alk/simulate.py +10351 -0
- fi/alk/studio/__init__.py +82 -0
- fi/alk/studio/_bias.py +314 -0
- fi/alk/studio/_calibration.py +522 -0
- fi/alk/studio/_coverage.py +262 -0
- fi/alk/studio/_download.py +665 -0
- fi/alk/studio/_fidelity_attack.py +114 -0
- fi/alk/studio/_generate.py +652 -0
- fi/alk/studio/_library.py +370 -0
- fi/alk/studio/_scan.py +134 -0
- fi/alk/studio/_upgrade.py +42 -0
- fi/alk/studio/_vendor.py +172 -0
- fi/alk/suite.py +4200 -0
- fi/alk/tasks.py +828 -0
- fi/alk/telemetry/__init__.py +149 -0
- fi/alk/telemetry/_contract.py +141 -0
- fi/alk/telemetry/_emit.py +182 -0
- fi/alk/telemetry/_ledger.py +296 -0
- fi/alk/telemetry/_queue.py +127 -0
- fi/alk/telemetry/_row.py +294 -0
- fi/alk/telemetry/_run.py +233 -0
- fi/alk/telemetry/_sync.py +193 -0
- fi/alk/telemetry/_url.py +119 -0
- fi/alk/trinity.py +49397 -0
- fi/alk/voice_loop.py +174 -0
- fi/api/__init__.py +1 -0
- fi/api/auth.py +137 -0
- fi/api/types.py +29 -0
- fi/cli/__init__.py +9 -0
- fi/cli/assertions/__init__.py +25 -0
- fi/cli/assertions/conditions.py +76 -0
- fi/cli/assertions/evaluator.py +286 -0
- fi/cli/assertions/exit_codes.py +20 -0
- fi/cli/assertions/parser.py +131 -0
- fi/cli/assertions/reporter.py +194 -0
- fi/cli/commands/__init__.py +9 -0
- fi/cli/commands/config.py +165 -0
- fi/cli/commands/export.py +208 -0
- fi/cli/commands/init.py +112 -0
- fi/cli/commands/list_cmd.py +213 -0
- fi/cli/commands/run.py +486 -0
- fi/cli/commands/validate.py +173 -0
- fi/cli/commands/view.py +424 -0
- fi/cli/config/__init__.py +6 -0
- fi/cli/config/defaults.py +206 -0
- fi/cli/config/loader.py +155 -0
- fi/cli/config/schema.py +174 -0
- fi/cli/main.py +78 -0
- fi/cli/output/__init__.py +6 -0
- fi/cli/output/formatters.py +106 -0
- fi/cli/output/reporters.py +46 -0
- fi/cli/storage/__init__.py +5 -0
- fi/cli/storage/run_history.py +249 -0
- fi/cli/utils/__init__.py +5 -0
- fi/cli/utils/console.py +44 -0
- fi/evals/__init__.py +131 -0
- fi/evals/autoeval/__init__.py +137 -0
- fi/evals/autoeval/analyzer.py +211 -0
- fi/evals/autoeval/config.py +244 -0
- fi/evals/autoeval/export.py +213 -0
- fi/evals/autoeval/interactive.py +283 -0
- fi/evals/autoeval/pipeline.py +625 -0
- fi/evals/autoeval/prompts.py +139 -0
- fi/evals/autoeval/recommender.py +242 -0
- fi/evals/autoeval/rules.py +589 -0
- fi/evals/autoeval/templates.py +299 -0
- fi/evals/autoeval/types.py +232 -0
- fi/evals/core/__init__.py +16 -0
- fi/evals/core/cloud_registry.py +184 -0
- fi/evals/core/engines.py +368 -0
- fi/evals/core/evaluate.py +319 -0
- fi/evals/core/judge_prompt.py +90 -0
- fi/evals/core/prompt_generator.py +83 -0
- fi/evals/core/registry.py +57 -0
- fi/evals/core/result.py +55 -0
- fi/evals/evaluator.py +721 -0
- fi/evals/execution.py +168 -0
- fi/evals/feedback/__init__.py +32 -0
- fi/evals/feedback/calibrator.py +160 -0
- fi/evals/feedback/collector.py +214 -0
- fi/evals/feedback/hooks.py +81 -0
- fi/evals/feedback/retriever.py +128 -0
- fi/evals/feedback/store.py +272 -0
- fi/evals/feedback/types.py +99 -0
- fi/evals/framework/README.md +79 -0
- fi/evals/framework/__init__.py +267 -0
- fi/evals/framework/backends/Dockerfile.eval-runner +33 -0
- fi/evals/framework/backends/__init__.py +99 -0
- fi/evals/framework/backends/_container.py +141 -0
- fi/evals/framework/backends/_utils.py +145 -0
- fi/evals/framework/backends/base.py +223 -0
- fi/evals/framework/backends/celery_backend.py +417 -0
- fi/evals/framework/backends/celery_worker.py +78 -0
- fi/evals/framework/backends/kubernetes_backend.py +665 -0
- fi/evals/framework/backends/ray_backend.py +521 -0
- fi/evals/framework/backends/temporal.py +350 -0
- fi/evals/framework/backends/temporal_worker.py +126 -0
- fi/evals/framework/backends/thread_pool.py +286 -0
- fi/evals/framework/context.py +258 -0
- fi/evals/framework/enrichment.py +306 -0
- fi/evals/framework/evals/__init__.py +68 -0
- fi/evals/framework/evals/agentic.py +399 -0
- fi/evals/framework/evals/builder.py +609 -0
- fi/evals/framework/evals/semantic.py +142 -0
- fi/evals/framework/evaluator.py +647 -0
- fi/evals/framework/evaluators/__init__.py +22 -0
- fi/evals/framework/evaluators/blocking.py +347 -0
- fi/evals/framework/evaluators/non_blocking.py +577 -0
- fi/evals/framework/propagation.py +421 -0
- fi/evals/framework/protocols.py +385 -0
- fi/evals/framework/registry.py +370 -0
- fi/evals/framework/resilience/__init__.py +150 -0
- fi/evals/framework/resilience/circuit_breaker.py +309 -0
- fi/evals/framework/resilience/degradation.py +355 -0
- fi/evals/framework/resilience/health.py +505 -0
- fi/evals/framework/resilience/rate_limiter.py +228 -0
- fi/evals/framework/resilience/retry.py +274 -0
- fi/evals/framework/resilience/types.py +288 -0
- fi/evals/framework/resilience/wrapper.py +433 -0
- fi/evals/framework/types.py +218 -0
- fi/evals/guardrails/README.md +915 -0
- fi/evals/guardrails/__init__.py +96 -0
- fi/evals/guardrails/backends/__init__.py +43 -0
- fi/evals/guardrails/backends/azure.py +361 -0
- fi/evals/guardrails/backends/base.py +88 -0
- fi/evals/guardrails/backends/generic_llm.py +163 -0
- fi/evals/guardrails/backends/granite.py +216 -0
- fi/evals/guardrails/backends/llamaguard.py +221 -0
- fi/evals/guardrails/backends/local_base.py +479 -0
- fi/evals/guardrails/backends/openai.py +365 -0
- fi/evals/guardrails/backends/qwen.py +170 -0
- fi/evals/guardrails/backends/shieldgemma.py +154 -0
- fi/evals/guardrails/backends/turing.py +235 -0
- fi/evals/guardrails/backends/vllm_client.py +321 -0
- fi/evals/guardrails/backends/wildguard.py +188 -0
- fi/evals/guardrails/base.py +888 -0
- fi/evals/guardrails/config.py +221 -0
- fi/evals/guardrails/discovery.py +243 -0
- fi/evals/guardrails/gateway.py +437 -0
- fi/evals/guardrails/registry.py +231 -0
- fi/evals/guardrails/scanners/__init__.py +127 -0
- fi/evals/guardrails/scanners/base.py +191 -0
- fi/evals/guardrails/scanners/code_injection.py +243 -0
- fi/evals/guardrails/scanners/eval_delegate.py +574 -0
- fi/evals/guardrails/scanners/invisible_chars.py +351 -0
- fi/evals/guardrails/scanners/jailbreak.py +412 -0
- fi/evals/guardrails/scanners/language.py +288 -0
- fi/evals/guardrails/scanners/pipeline.py +260 -0
- fi/evals/guardrails/scanners/regex.py +311 -0
- fi/evals/guardrails/scanners/secrets.py +274 -0
- fi/evals/guardrails/scanners/topics.py +649 -0
- fi/evals/guardrails/scanners/urls.py +341 -0
- fi/evals/guardrails/types.py +96 -0
- fi/evals/llm/__init__.py +3 -0
- fi/evals/llm/base_llm_provider.py +35 -0
- fi/evals/llm/providers/litellm.py +70 -0
- fi/evals/local/__init__.py +90 -0
- fi/evals/local/evaluator.py +690 -0
- fi/evals/local/execution_mode.py +121 -0
- fi/evals/local/llm.py +489 -0
- fi/evals/local/metrics/__init__.py +19 -0
- fi/evals/local/registry.py +360 -0
- fi/evals/manager.py +1018 -0
- fi/evals/manager_types.py +362 -0
- fi/evals/metrics/__init__.py +185 -0
- fi/evals/metrics/agents/__init__.py +74 -0
- fi/evals/metrics/agents/metrics.py +693 -0
- fi/evals/metrics/agents/report.py +36463 -0
- fi/evals/metrics/agents/types.py +160 -0
- fi/evals/metrics/base_llm_metric.py +111 -0
- fi/evals/metrics/base_metric.py +138 -0
- fi/evals/metrics/code_security/__init__.py +305 -0
- fi/evals/metrics/code_security/analyzer.py +985 -0
- fi/evals/metrics/code_security/benchmarks/__init__.py +73 -0
- fi/evals/metrics/code_security/benchmarks/builtin.py +750 -0
- fi/evals/metrics/code_security/benchmarks/loader.py +580 -0
- fi/evals/metrics/code_security/benchmarks/types.py +308 -0
- fi/evals/metrics/code_security/detectors/__init__.py +186 -0
- fi/evals/metrics/code_security/detectors/base.py +394 -0
- fi/evals/metrics/code_security/detectors/cryptography.py +345 -0
- fi/evals/metrics/code_security/detectors/injection.py +744 -0
- fi/evals/metrics/code_security/detectors/secrets.py +287 -0
- fi/evals/metrics/code_security/detectors/serialization.py +192 -0
- fi/evals/metrics/code_security/joint_metrics.py +588 -0
- fi/evals/metrics/code_security/judges/__init__.py +83 -0
- fi/evals/metrics/code_security/judges/base.py +238 -0
- fi/evals/metrics/code_security/judges/dual_judge.py +534 -0
- fi/evals/metrics/code_security/judges/llm_judge.py +301 -0
- fi/evals/metrics/code_security/judges/pattern_judge.py +515 -0
- fi/evals/metrics/code_security/metrics.py +388 -0
- fi/evals/metrics/code_security/modes/__init__.py +63 -0
- fi/evals/metrics/code_security/modes/adversarial.py +284 -0
- fi/evals/metrics/code_security/modes/autocomplete.py +198 -0
- fi/evals/metrics/code_security/modes/base.py +283 -0
- fi/evals/metrics/code_security/modes/instruct.py +253 -0
- fi/evals/metrics/code_security/modes/repair.py +230 -0
- fi/evals/metrics/code_security/reports/__init__.py +57 -0
- fi/evals/metrics/code_security/reports/generator.py +404 -0
- fi/evals/metrics/code_security/reports/leaderboard.py +509 -0
- fi/evals/metrics/code_security/types.py +534 -0
- fi/evals/metrics/function_calling/__init__.py +34 -0
- fi/evals/metrics/function_calling/metrics.py +573 -0
- fi/evals/metrics/function_calling/types.py +87 -0
- fi/evals/metrics/hallucination/__init__.py +54 -0
- fi/evals/metrics/hallucination/detector.py +149 -0
- fi/evals/metrics/hallucination/metrics.py +390 -0
- fi/evals/metrics/hallucination/nli.py +253 -0
- fi/evals/metrics/hallucination/sentinel.py +106 -0
- fi/evals/metrics/hallucination/types.py +132 -0
- fi/evals/metrics/heuristics/aggregation_metrics.py +85 -0
- fi/evals/metrics/heuristics/json_metrics.py +87 -0
- fi/evals/metrics/heuristics/similarity_metrics.py +375 -0
- fi/evals/metrics/heuristics/string_metrics.py +391 -0
- fi/evals/metrics/llm_as_judges/__init__.py +17 -0
- fi/evals/metrics/llm_as_judges/custom_judge/metric.py +112 -0
- fi/evals/metrics/llm_as_judges/custom_judge/prompts.py +26 -0
- fi/evals/metrics/llm_as_judges/types.py +48 -0
- fi/evals/metrics/rag/__init__.py +111 -0
- fi/evals/metrics/rag/advanced/__init__.py +14 -0
- fi/evals/metrics/rag/advanced/multi_hop.py +283 -0
- fi/evals/metrics/rag/advanced/source_attribution.py +344 -0
- fi/evals/metrics/rag/generation/__init__.py +17 -0
- fi/evals/metrics/rag/generation/answer_relevancy.py +176 -0
- fi/evals/metrics/rag/generation/context_utilization.py +245 -0
- fi/evals/metrics/rag/generation/faithfulness.py +241 -0
- fi/evals/metrics/rag/generation/groundedness.py +131 -0
- fi/evals/metrics/rag/rag_score.py +277 -0
- fi/evals/metrics/rag/retrieval/__init__.py +20 -0
- fi/evals/metrics/rag/retrieval/context_entity_recall.py +124 -0
- fi/evals/metrics/rag/retrieval/context_precision.py +158 -0
- fi/evals/metrics/rag/retrieval/context_recall.py +106 -0
- fi/evals/metrics/rag/retrieval/noise_sensitivity.py +163 -0
- fi/evals/metrics/rag/retrieval/ranking.py +261 -0
- fi/evals/metrics/rag/types.py +100 -0
- fi/evals/metrics/rag/utils/__init__.py +62 -0
- fi/evals/metrics/rag/utils/claims.py +189 -0
- fi/evals/metrics/rag/utils/entities.py +244 -0
- fi/evals/metrics/rag/utils/nli.py +92 -0
- fi/evals/metrics/rag/utils/similarity.py +345 -0
- fi/evals/metrics/structured/__init__.py +114 -0
- fi/evals/metrics/structured/field_completeness.py +313 -0
- fi/evals/metrics/structured/hierarchy_score.py +366 -0
- fi/evals/metrics/structured/json_validation.py +190 -0
- fi/evals/metrics/structured/schema_compliance.py +280 -0
- fi/evals/metrics/structured/structured_output_score.py +298 -0
- fi/evals/metrics/structured/types.py +108 -0
- fi/evals/metrics/structured/validators/__init__.py +30 -0
- fi/evals/metrics/structured/validators/base.py +189 -0
- fi/evals/metrics/structured/validators/json_validator.py +196 -0
- fi/evals/metrics/structured/validators/pydantic_validator.py +178 -0
- fi/evals/metrics/structured/validators/yaml_validator.py +248 -0
- fi/evals/otel/__init__.py +266 -0
- fi/evals/otel/config.py +400 -0
- fi/evals/otel/conventions.py +463 -0
- fi/evals/otel/enrichment.py +371 -0
- fi/evals/otel/instrumentors/__init__.py +140 -0
- fi/evals/otel/instrumentors/anthropic.py +517 -0
- fi/evals/otel/instrumentors/base.py +382 -0
- fi/evals/otel/instrumentors/openai.py +673 -0
- fi/evals/otel/processors/__init__.py +36 -0
- fi/evals/otel/processors/base.py +473 -0
- fi/evals/otel/processors/cost.py +445 -0
- fi/evals/otel/processors/evaluation.py +559 -0
- fi/evals/otel/processors/llm.py +462 -0
- fi/evals/otel/tracer.py +506 -0
- fi/evals/otel/types.py +232 -0
- fi/evals/otel_utils.py +23 -0
- fi/evals/protect.py +671 -0
- fi/evals/protect_input_adapter.py +154 -0
- fi/evals/streaming/__init__.py +88 -0
- fi/evals/streaming/buffer.py +213 -0
- fi/evals/streaming/evaluator.py +551 -0
- fi/evals/streaming/policy.py +307 -0
- fi/evals/streaming/scorers.py +368 -0
- fi/evals/streaming/types.py +238 -0
- fi/evals/templates.py +472 -0
- fi/evals/types.py +156 -0
- fi/opt/__init__.py +221 -0
- fi/opt/_objective_scoring.py +85 -0
- fi/opt/base/__init__.py +11 -0
- fi/opt/base/base_generator.py +33 -0
- fi/opt/base/base_mapper.py +26 -0
- fi/opt/base/base_optimizer.py +45 -0
- fi/opt/base/evaluator.py +211 -0
- fi/opt/components.py +3095 -0
- fi/opt/datamappers/__init__.py +3 -0
- fi/opt/datamappers/basic_mapper.py +40 -0
- fi/opt/deployment.py +1021 -0
- fi/opt/evidence.py +4332 -0
- fi/opt/generators/__init__.py +3 -0
- fi/opt/generators/litellm.py +66 -0
- fi/opt/integrations/__init__.py +23 -0
- fi/opt/integrations/generative_suite.py +410 -0
- fi/opt/integrations/simulate.py +1313 -0
- fi/opt/mutations.py +771 -0
- fi/opt/observability.py +4639 -0
- fi/opt/optimizer_trace.py +889 -0
- fi/opt/optimizers/__init__.py +80 -0
- fi/opt/optimizers/agent.py +331 -0
- fi/opt/optimizers/agent_bandit.py +392 -0
- fi/opt/optimizers/agent_curriculum.py +635 -0
- fi/opt/optimizers/agent_evolution.py +894 -0
- fi/opt/optimizers/agent_feedback.py +1863 -0
- fi/opt/optimizers/agent_pareto.py +547 -0
- fi/opt/optimizers/agent_social_memory.py +1113 -0
- fi/opt/optimizers/agent_tpe.py +321 -0
- fi/opt/optimizers/bayesian_search.py +449 -0
- fi/opt/optimizers/council.py +2075 -0
- fi/opt/optimizers/futureagi_replay.py +799 -0
- fi/opt/optimizers/gepa.py +322 -0
- fi/opt/optimizers/metaprompt.py +243 -0
- fi/opt/optimizers/promptwizard.py +417 -0
- fi/opt/optimizers/protegi.py +329 -0
- fi/opt/optimizers/random_search.py +224 -0
- fi/opt/research.py +518 -0
- fi/opt/simulation.py +260 -0
- fi/opt/targets.py +232 -0
- fi/opt/types.py +66 -0
- fi/opt/utils/__init__.py +4 -0
- fi/opt/utils/early_stopping.py +266 -0
- fi/opt/utils/setup_logging.py +82 -0
- fi/simulate/__init__.py +540 -0
- fi/simulate/_hashing.py +35 -0
- fi/simulate/_logging.py +10 -0
- fi/simulate/adapters.py +87 -0
- fi/simulate/agent/__init__.py +120 -0
- fi/simulate/agent/browser.py +658 -0
- fi/simulate/agent/definition.py +587 -0
- fi/simulate/agent/frameworks.py +3528 -0
- fi/simulate/agent/generic.py +8286 -0
- fi/simulate/agent/import_probe.py +227 -0
- fi/simulate/agent/memory.py +905 -0
- fi/simulate/agent/mocks.py +101 -0
- fi/simulate/agent/multi_agent.py +361 -0
- fi/simulate/agent/orchestration.py +903 -0
- fi/simulate/agent/realtime.py +665 -0
- fi/simulate/agent/wrapper.py +99 -0
- fi/simulate/agent/wrappers/__init__.py +18 -0
- fi/simulate/agent/wrappers/anthropic.py +62 -0
- fi/simulate/agent/wrappers/gemini.py +65 -0
- fi/simulate/agent/wrappers/http.py +404 -0
- fi/simulate/agent/wrappers/langchain.py +80 -0
- fi/simulate/agent/wrappers/openai.py +75 -0
- fi/simulate/agent/wrappers/websocket.py +326 -0
- fi/simulate/artifacts/__init__.py +11 -0
- fi/simulate/artifacts/manifest.py +62 -0
- fi/simulate/cli.py +20560 -0
- fi/simulate/endpoints/__init__.py +45 -0
- fi/simulate/endpoints/_http_actor.py +73 -0
- fi/simulate/endpoints/actor_sources.py +243 -0
- fi/simulate/endpoints/base.py +107 -0
- fi/simulate/endpoints/builtins.py +10 -0
- fi/simulate/endpoints/callable.py +95 -0
- fi/simulate/endpoints/http.py +76 -0
- fi/simulate/endpoints/livekit.py +138 -0
- fi/simulate/endpoints/originators.py +132 -0
- fi/simulate/endpoints/profiles.py +348 -0
- fi/simulate/endpoints/retell.py +633 -0
- fi/simulate/endpoints/vapi.py +205 -0
- fi/simulate/endpoints/websocket.py +76 -0
- fi/simulate/environment.py +33026 -0
- fi/simulate/environments/__init__.py +11 -0
- fi/simulate/environments/base.py +73 -0
- fi/simulate/environments/chat.py +697 -0
- fi/simulate/environments/voice.py +212 -0
- fi/simulate/evaluation/__init__.py +4 -0
- fi/simulate/evaluation/ai_eval.py +227 -0
- fi/simulate/evidence/__init__.py +35 -0
- fi/simulate/evidence/base.py +59 -0
- fi/simulate/evidence/caller_observed.py +50 -0
- fi/simulate/evidence/livekit_instrumentation.py +51 -0
- fi/simulate/evidence/livekit_room.py +50 -0
- fi/simulate/evidence/otel.py +49 -0
- fi/simulate/evidence/providers/__init__.py +24 -0
- fi/simulate/evidence/providers/base.py +61 -0
- fi/simulate/evidence/providers/retell.py +376 -0
- fi/simulate/evidence/providers/vapi.py +426 -0
- fi/simulate/hosted/__init__.py +32 -0
- fi/simulate/hosted/child_entrypoint.py +306 -0
- fi/simulate/hosted/job.py +150 -0
- fi/simulate/hosted/targets.py +53 -0
- fi/simulate/instrumentation/__init__.py +5 -0
- fi/simulate/instrumentation/livekit/__init__.py +122 -0
- fi/simulate/manifest.py +1033 -0
- fi/simulate/matrix_cli.py +165 -0
- fi/simulate/realtime/__init__.py +40 -0
- fi/simulate/realtime/events.py +107 -0
- fi/simulate/realtime/media.py +61 -0
- fi/simulate/realtime/session.py +91 -0
- fi/simulate/recording/__init__.py +5 -0
- fi/simulate/recording/room_recorder.py +326 -0
- fi/simulate/registry.py +185 -0
- fi/simulate/results/__init__.py +9 -0
- fi/simulate/results/base.py +18 -0
- fi/simulate/results/filesystem.py +71 -0
- fi/simulate/results/futureagi.py +1340 -0
- fi/simulate/runtime/__init__.py +85 -0
- fi/simulate/runtime/capabilities.py +40 -0
- fi/simulate/runtime/events.py +63 -0
- fi/simulate/runtime/failures.py +25 -0
- fi/simulate/runtime/ids.py +34 -0
- fi/simulate/runtime/plan.py +70 -0
- fi/simulate/runtime/planner.py +102 -0
- fi/simulate/runtime/report.py +174 -0
- fi/simulate/runtime/run.py +75 -0
- fi/simulate/runtime/runner.py +333 -0
- fi/simulate/runtime/spec.py +186 -0
- fi/simulate/simulation/__init__.py +30 -0
- fi/simulate/simulation/behavior_policy.py +425 -0
- fi/simulate/simulation/bridge/__init__.py +9 -0
- fi/simulate/simulation/bridge/audio.py +29 -0
- fi/simulate/simulation/bridge/connector.py +46 -0
- fi/simulate/simulation/bridge/livekit.py +252 -0
- fi/simulate/simulation/bridge/retell.py +188 -0
- fi/simulate/simulation/bridge/vapi.py +177 -0
- fi/simulate/simulation/contract.py +419 -0
- fi/simulate/simulation/engines/__init__.py +12 -0
- fi/simulate/simulation/engines/base.py +21 -0
- fi/simulate/simulation/engines/cloud.py +517 -0
- fi/simulate/simulation/engines/livekit.py +4167 -0
- fi/simulate/simulation/engines/local_text.py +89 -0
- fi/simulate/simulation/fidelity.py +374 -0
- fi/simulate/simulation/gemini_tts_stream.py +110 -0
- fi/simulate/simulation/generator.py +91 -0
- fi/simulate/simulation/goal_machine.py +185 -0
- fi/simulate/simulation/livekit_models.py +467 -0
- fi/simulate/simulation/matrix.py +170 -0
- fi/simulate/simulation/models.py +279 -0
- fi/simulate/simulation/runner.py +153 -0
- fi/simulate/simulation/synthetic.py +880 -0
- fi/simulate/simulation/voice_prompt.py +502 -0
- fi/simulate/simulator/__init__.py +55 -0
- fi/simulate/simulator/builtins.py +53 -0
- fi/simulate/suite.py +1288 -0
- fi/simulate/utils/routes.py +164 -0
- fi/simulate/voice.py +225 -0
- fi/simulate/voice_cli.py +182 -0
- fi/utils/__init__.py +1 -0
- fi/utils/constants.py +14 -0
- fi/utils/errors.py +200 -0
- fi/utils/executor.py +26 -0
- fi/utils/routes.py +119 -0
- fi/utils/utils.py +17 -0
|
@@ -0,0 +1,649 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Topic Restriction Scanner for Guardrails.
|
|
3
|
+
|
|
4
|
+
Restricts conversations to allowed topics and detects off-topic content.
|
|
5
|
+
Supports both keyword-based and semantic embedding-based detection.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import re
|
|
9
|
+
import time
|
|
10
|
+
from typing import Any, Dict, List, Optional, Set
|
|
11
|
+
|
|
12
|
+
from fi.evals.guardrails.scanners.base import (
|
|
13
|
+
BaseScanner,
|
|
14
|
+
ScanResult,
|
|
15
|
+
ScanMatch,
|
|
16
|
+
ScannerAction,
|
|
17
|
+
register_scanner,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# Topic descriptions for semantic embedding matching
|
|
22
|
+
TOPIC_DESCRIPTIONS: Dict[str, str] = {
|
|
23
|
+
"politics": "Political discussions about elections, voting, government, political parties, politicians, legislation, and policy debates",
|
|
24
|
+
"religion": "Religious discussions about faith, worship, God, spiritual beliefs, churches, mosques, temples, and religious texts",
|
|
25
|
+
"violence": "Violent content including weapons, attacks, murder, assault, warfare, fighting, and physical harm",
|
|
26
|
+
"drugs": "Drug-related content about narcotics, illegal substances, drug use, addiction, and trafficking",
|
|
27
|
+
"adult_content": "Adult or sexual content including pornography, explicit material, and NSFW topics",
|
|
28
|
+
"gambling": "Gambling discussions about casinos, betting, poker, lottery, and wagering",
|
|
29
|
+
"medical_advice": "Medical advice about diagnoses, treatments, medications, symptoms, and health conditions",
|
|
30
|
+
"financial_advice": "Financial advice about investments, stocks, trading, cryptocurrency, and portfolio management",
|
|
31
|
+
"legal_advice": "Legal advice about lawsuits, attorneys, court cases, litigation, and legal proceedings",
|
|
32
|
+
"customer_support": "Customer support topics like orders, shipping, refunds, account issues, and billing questions",
|
|
33
|
+
"product_info": "Product information about features, specifications, pricing, availability, and warranties",
|
|
34
|
+
"technical_support": "Technical support for software errors, bugs, installation, configuration, and troubleshooting",
|
|
35
|
+
"general_knowledge": "General knowledge questions about facts, history, science, geography, and explanations",
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
# Predefined topic keywords for common restrictions
|
|
39
|
+
TOPIC_KEYWORDS: Dict[str, Set[str]] = {
|
|
40
|
+
# Sensitive topics often restricted
|
|
41
|
+
"politics": {
|
|
42
|
+
"election", "vote", "democrat", "republican", "liberal", "conservative",
|
|
43
|
+
"president", "congress", "senate", "parliament", "politician", "government",
|
|
44
|
+
"left-wing", "right-wing", "campaign", "ballot", "trump", "biden", "party",
|
|
45
|
+
},
|
|
46
|
+
"religion": {
|
|
47
|
+
"god", "jesus", "allah", "buddha", "church", "mosque", "temple", "prayer",
|
|
48
|
+
"bible", "quran", "torah", "christian", "muslim", "jewish", "hindu",
|
|
49
|
+
"atheist", "agnostic", "faith", "worship", "salvation", "sin", "heaven", "hell",
|
|
50
|
+
},
|
|
51
|
+
"violence": {
|
|
52
|
+
"kill", "murder", "attack", "assault", "weapon", "gun", "bomb", "terrorist",
|
|
53
|
+
"violence", "violent", "hurt", "harm", "blood", "death", "dead", "shoot",
|
|
54
|
+
"stab", "fight", "war", "battle", "combat",
|
|
55
|
+
},
|
|
56
|
+
"drugs": {
|
|
57
|
+
"cocaine", "heroin", "marijuana", "cannabis", "weed", "meth", "lsd", "mdma",
|
|
58
|
+
"drug", "narcotic", "overdose", "addiction", "dealer", "cartel", "trafficking",
|
|
59
|
+
},
|
|
60
|
+
"adult_content": {
|
|
61
|
+
"sex", "porn", "nude", "naked", "erotic", "explicit", "xxx", "nsfw",
|
|
62
|
+
"fetish", "intimate", "sexual", "genitals",
|
|
63
|
+
},
|
|
64
|
+
"gambling": {
|
|
65
|
+
"casino", "bet", "betting", "gamble", "gambling", "poker", "blackjack",
|
|
66
|
+
"slot", "lottery", "wager", "odds", "bookie", "sportsbook",
|
|
67
|
+
},
|
|
68
|
+
"medical_advice": {
|
|
69
|
+
"diagnosis", "treatment", "medication", "prescription", "dosage", "symptom",
|
|
70
|
+
"disease", "illness", "cure", "therapy", "doctor", "patient",
|
|
71
|
+
},
|
|
72
|
+
"financial_advice": {
|
|
73
|
+
"invest", "investment", "stock", "bond", "portfolio", "trading", "forex",
|
|
74
|
+
"crypto", "bitcoin", "dividend", "retirement", "pension",
|
|
75
|
+
},
|
|
76
|
+
"legal_advice": {
|
|
77
|
+
"lawsuit", "attorney", "lawyer", "court", "judge", "verdict", "settlement",
|
|
78
|
+
"litigation", "defendant", "plaintiff", "legal", "illegal",
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
# Common allowed topics
|
|
82
|
+
"customer_support": {
|
|
83
|
+
"order", "shipping", "delivery", "refund", "return", "exchange", "tracking",
|
|
84
|
+
"account", "password", "login", "subscription", "billing", "payment",
|
|
85
|
+
"help", "support", "issue", "problem", "question",
|
|
86
|
+
},
|
|
87
|
+
"product_info": {
|
|
88
|
+
"product", "feature", "specification", "price", "availability", "warranty",
|
|
89
|
+
"size", "color", "model", "version", "compatible",
|
|
90
|
+
},
|
|
91
|
+
"technical_support": {
|
|
92
|
+
"error", "bug", "crash", "install", "update", "download", "configure",
|
|
93
|
+
"setup", "troubleshoot", "debug", "fix", "issue", "problem",
|
|
94
|
+
},
|
|
95
|
+
"general_knowledge": {
|
|
96
|
+
"what", "how", "why", "when", "where", "who", "explain", "describe",
|
|
97
|
+
"define", "meaning", "history", "science", "math", "geography",
|
|
98
|
+
},
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@register_scanner("topics")
|
|
103
|
+
class TopicRestrictionScanner(BaseScanner):
|
|
104
|
+
"""
|
|
105
|
+
Scanner for topic restriction and off-topic detection.
|
|
106
|
+
|
|
107
|
+
Supports two detection modes:
|
|
108
|
+
- Keyword-based: Fast pattern matching (default)
|
|
109
|
+
- Semantic: Embedding-based similarity matching
|
|
110
|
+
|
|
111
|
+
Restricts conversations to allowed topics or blocks denied topics.
|
|
112
|
+
|
|
113
|
+
Usage:
|
|
114
|
+
# Keyword-based (fast, no dependencies)
|
|
115
|
+
scanner = TopicRestrictionScanner(
|
|
116
|
+
allowed_topics=["customer_support", "product_info"],
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# Semantic embedding-based (requires sentence-transformers)
|
|
120
|
+
scanner = TopicRestrictionScanner(
|
|
121
|
+
denied_topics=["politics", "religion"],
|
|
122
|
+
use_embeddings=True,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
# Hybrid mode - combines both approaches
|
|
126
|
+
scanner = TopicRestrictionScanner(
|
|
127
|
+
allowed_topics=["customer_support"],
|
|
128
|
+
use_embeddings=True,
|
|
129
|
+
combine_scores=True,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# Custom topic descriptions for semantic matching
|
|
133
|
+
scanner = TopicRestrictionScanner(
|
|
134
|
+
custom_topic_descriptions={
|
|
135
|
+
"insurance_claims": "Insurance claim processing, policy coverage, claim status",
|
|
136
|
+
},
|
|
137
|
+
allowed_topics=["insurance_claims"],
|
|
138
|
+
use_embeddings=True,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
result = scanner.scan("Who should I vote for in the election?")
|
|
142
|
+
if not result.passed:
|
|
143
|
+
print(f"Off-topic: {result.metadata.get('detected_topics')}")
|
|
144
|
+
"""
|
|
145
|
+
|
|
146
|
+
name = "topics"
|
|
147
|
+
category = "topic_restriction"
|
|
148
|
+
description = "Restricts conversations to allowed topics"
|
|
149
|
+
default_action = ScannerAction.FLAG
|
|
150
|
+
|
|
151
|
+
# Default embedding model
|
|
152
|
+
DEFAULT_EMBEDDING_MODEL = "all-MiniLM-L6-v2"
|
|
153
|
+
|
|
154
|
+
def __init__(
|
|
155
|
+
self,
|
|
156
|
+
action: Optional[ScannerAction] = None,
|
|
157
|
+
enabled: bool = True,
|
|
158
|
+
threshold: float = 0.5,
|
|
159
|
+
allowed_topics: Optional[List[str]] = None,
|
|
160
|
+
denied_topics: Optional[List[str]] = None,
|
|
161
|
+
custom_topics: Optional[Dict[str, Set[str]]] = None,
|
|
162
|
+
custom_topic_descriptions: Optional[Dict[str, str]] = None,
|
|
163
|
+
min_keyword_matches: int = 2,
|
|
164
|
+
case_sensitive: bool = False,
|
|
165
|
+
use_embeddings: bool = False,
|
|
166
|
+
embedding_model: Optional[str] = None,
|
|
167
|
+
combine_scores: bool = True,
|
|
168
|
+
embedding_weight: float = 0.6,
|
|
169
|
+
keyword_weight: float = 0.4,
|
|
170
|
+
semantic_threshold: float = 0.5,
|
|
171
|
+
device: Optional[str] = None,
|
|
172
|
+
):
|
|
173
|
+
"""
|
|
174
|
+
Initialize topic restriction scanner.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
action: Action on detection
|
|
178
|
+
enabled: Whether scanner is enabled
|
|
179
|
+
threshold: Confidence threshold (based on keyword match ratio)
|
|
180
|
+
allowed_topics: List of allowed topic names (whitelist mode)
|
|
181
|
+
denied_topics: List of denied topic names (blacklist mode)
|
|
182
|
+
custom_topics: Custom topic definitions {topic_name: {keywords}}
|
|
183
|
+
custom_topic_descriptions: Custom descriptions for semantic matching
|
|
184
|
+
min_keyword_matches: Minimum keyword matches to detect a topic
|
|
185
|
+
case_sensitive: Whether matching is case-sensitive
|
|
186
|
+
use_embeddings: Enable semantic embedding-based detection
|
|
187
|
+
embedding_model: Model name for embeddings (default: all-MiniLM-L6-v2)
|
|
188
|
+
combine_scores: Combine keyword and embedding scores (hybrid mode)
|
|
189
|
+
embedding_weight: Weight for embedding score in combined mode
|
|
190
|
+
keyword_weight: Weight for keyword score in combined mode
|
|
191
|
+
semantic_threshold: Similarity threshold for semantic matching
|
|
192
|
+
device: Device for embedding model ('cpu', 'cuda', 'mps', or None)
|
|
193
|
+
"""
|
|
194
|
+
super().__init__(action, enabled)
|
|
195
|
+
self.threshold = threshold
|
|
196
|
+
self.allowed_topics = set(allowed_topics) if allowed_topics else None
|
|
197
|
+
self.denied_topics = set(denied_topics) if denied_topics else None
|
|
198
|
+
self.min_keyword_matches = min_keyword_matches
|
|
199
|
+
self.case_sensitive = case_sensitive
|
|
200
|
+
|
|
201
|
+
# Embedding settings
|
|
202
|
+
self.use_embeddings = use_embeddings
|
|
203
|
+
self.embedding_model_name = embedding_model or self.DEFAULT_EMBEDDING_MODEL
|
|
204
|
+
self.combine_scores = combine_scores
|
|
205
|
+
self.embedding_weight = embedding_weight
|
|
206
|
+
self.keyword_weight = keyword_weight
|
|
207
|
+
self.semantic_threshold = semantic_threshold
|
|
208
|
+
self.device = device
|
|
209
|
+
|
|
210
|
+
# Build topic dictionaries
|
|
211
|
+
self.topics = TOPIC_KEYWORDS.copy()
|
|
212
|
+
if custom_topics:
|
|
213
|
+
self.topics.update(custom_topics)
|
|
214
|
+
|
|
215
|
+
self.topic_descriptions = TOPIC_DESCRIPTIONS.copy()
|
|
216
|
+
if custom_topic_descriptions:
|
|
217
|
+
self.topic_descriptions.update(custom_topic_descriptions)
|
|
218
|
+
|
|
219
|
+
# Compile patterns for keyword matching
|
|
220
|
+
self._topic_patterns: Dict[str, List[re.Pattern]] = {}
|
|
221
|
+
for topic, keywords in self.topics.items():
|
|
222
|
+
flags = 0 if case_sensitive else re.IGNORECASE
|
|
223
|
+
patterns = [
|
|
224
|
+
re.compile(r'\b' + re.escape(kw) + r'\b', flags)
|
|
225
|
+
for kw in keywords
|
|
226
|
+
]
|
|
227
|
+
self._topic_patterns[topic] = patterns
|
|
228
|
+
|
|
229
|
+
# Lazy-loaded embedding components
|
|
230
|
+
self._embedding_model: Optional[Any] = None
|
|
231
|
+
self._topic_embeddings: Optional[Dict[str, Any]] = None
|
|
232
|
+
self._embeddings_available = False
|
|
233
|
+
self._embeddings_load_error: Optional[str] = None
|
|
234
|
+
|
|
235
|
+
# Pre-load embeddings if requested
|
|
236
|
+
if use_embeddings:
|
|
237
|
+
self._load_embedding_model()
|
|
238
|
+
|
|
239
|
+
def _load_embedding_model(self) -> bool:
|
|
240
|
+
"""
|
|
241
|
+
Lazy load the embedding model and compute topic embeddings.
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
True if model loaded successfully, False otherwise
|
|
245
|
+
"""
|
|
246
|
+
if self._embedding_model is not None:
|
|
247
|
+
return self._embeddings_available
|
|
248
|
+
|
|
249
|
+
try:
|
|
250
|
+
from sentence_transformers import SentenceTransformer
|
|
251
|
+
|
|
252
|
+
# Determine device
|
|
253
|
+
device = self.device
|
|
254
|
+
if device is None:
|
|
255
|
+
try:
|
|
256
|
+
import torch
|
|
257
|
+
if torch.cuda.is_available():
|
|
258
|
+
device = "cuda"
|
|
259
|
+
elif hasattr(torch.backends, "mps") and torch.backends.mps.is_available():
|
|
260
|
+
device = "mps"
|
|
261
|
+
else:
|
|
262
|
+
device = "cpu"
|
|
263
|
+
except ImportError:
|
|
264
|
+
device = "cpu"
|
|
265
|
+
|
|
266
|
+
# Load embedding model
|
|
267
|
+
self._embedding_model = SentenceTransformer(
|
|
268
|
+
self.embedding_model_name,
|
|
269
|
+
device=device,
|
|
270
|
+
)
|
|
271
|
+
self._device = device
|
|
272
|
+
|
|
273
|
+
# Pre-compute topic embeddings
|
|
274
|
+
self._compute_topic_embeddings()
|
|
275
|
+
|
|
276
|
+
self._embeddings_available = True
|
|
277
|
+
return True
|
|
278
|
+
|
|
279
|
+
except ImportError as e:
|
|
280
|
+
self._embeddings_load_error = f"sentence-transformers not installed: {e}"
|
|
281
|
+
self._embeddings_available = False
|
|
282
|
+
return False
|
|
283
|
+
except Exception as e:
|
|
284
|
+
self._embeddings_load_error = f"Failed to load embedding model: {e}"
|
|
285
|
+
self._embeddings_available = False
|
|
286
|
+
return False
|
|
287
|
+
|
|
288
|
+
def _compute_topic_embeddings(self) -> None:
|
|
289
|
+
"""Pre-compute embeddings for all topic descriptions."""
|
|
290
|
+
if self._embedding_model is None:
|
|
291
|
+
return
|
|
292
|
+
|
|
293
|
+
self._topic_embeddings = {}
|
|
294
|
+
|
|
295
|
+
# Determine which topics to compute embeddings for
|
|
296
|
+
relevant_topics = set()
|
|
297
|
+
if self.allowed_topics:
|
|
298
|
+
relevant_topics.update(self.allowed_topics)
|
|
299
|
+
if self.denied_topics:
|
|
300
|
+
relevant_topics.update(self.denied_topics)
|
|
301
|
+
if not relevant_topics:
|
|
302
|
+
# Compute for all topics if no specific restriction
|
|
303
|
+
relevant_topics = set(self.topic_descriptions.keys())
|
|
304
|
+
|
|
305
|
+
for topic in relevant_topics:
|
|
306
|
+
if topic in self.topic_descriptions:
|
|
307
|
+
description = self.topic_descriptions[topic]
|
|
308
|
+
else:
|
|
309
|
+
# Fallback to keywords as description
|
|
310
|
+
keywords = self.topics.get(topic, set())
|
|
311
|
+
description = " ".join(keywords) if keywords else topic
|
|
312
|
+
|
|
313
|
+
embedding = self._embedding_model.encode(
|
|
314
|
+
description,
|
|
315
|
+
convert_to_tensor=True,
|
|
316
|
+
normalize_embeddings=True,
|
|
317
|
+
)
|
|
318
|
+
self._topic_embeddings[topic] = embedding
|
|
319
|
+
|
|
320
|
+
def _semantic_similarity(
|
|
321
|
+
self, content: str
|
|
322
|
+
) -> Dict[str, Dict[str, Any]]:
|
|
323
|
+
"""
|
|
324
|
+
Compute semantic similarity between content and topic descriptions.
|
|
325
|
+
|
|
326
|
+
Args:
|
|
327
|
+
content: Text to analyze
|
|
328
|
+
|
|
329
|
+
Returns:
|
|
330
|
+
Dict of {topic: {similarity, confidence}}
|
|
331
|
+
"""
|
|
332
|
+
if not self._embeddings_available or self._embedding_model is None:
|
|
333
|
+
return {}
|
|
334
|
+
|
|
335
|
+
try:
|
|
336
|
+
from sentence_transformers import util
|
|
337
|
+
|
|
338
|
+
# Encode content
|
|
339
|
+
content_embedding = self._embedding_model.encode(
|
|
340
|
+
content,
|
|
341
|
+
convert_to_tensor=True,
|
|
342
|
+
normalize_embeddings=True,
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
results = {}
|
|
346
|
+
for topic, topic_embedding in self._topic_embeddings.items():
|
|
347
|
+
# Compute cosine similarity
|
|
348
|
+
similarity = util.cos_sim(content_embedding, topic_embedding).item()
|
|
349
|
+
|
|
350
|
+
# Normalize to 0-1 range (cosine similarity can be negative)
|
|
351
|
+
confidence = max(0.0, (similarity + 1.0) / 2.0)
|
|
352
|
+
|
|
353
|
+
results[topic] = {
|
|
354
|
+
"similarity": similarity,
|
|
355
|
+
"confidence": confidence,
|
|
356
|
+
"method": "semantic",
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return results
|
|
360
|
+
|
|
361
|
+
except Exception:
|
|
362
|
+
# Return empty on error
|
|
363
|
+
return {}
|
|
364
|
+
|
|
365
|
+
def _detect_topics_keywords(self, text: str) -> Dict[str, Dict]:
|
|
366
|
+
"""
|
|
367
|
+
Detect topics in text based on keyword matching.
|
|
368
|
+
|
|
369
|
+
Returns:
|
|
370
|
+
Dict of {topic: {count, keywords, confidence, method}}
|
|
371
|
+
"""
|
|
372
|
+
detected = {}
|
|
373
|
+
|
|
374
|
+
for topic, patterns in self._topic_patterns.items():
|
|
375
|
+
matched_keywords = []
|
|
376
|
+
for pattern in patterns:
|
|
377
|
+
matches = pattern.findall(text)
|
|
378
|
+
if matches:
|
|
379
|
+
matched_keywords.extend(matches)
|
|
380
|
+
|
|
381
|
+
if len(matched_keywords) >= self.min_keyword_matches:
|
|
382
|
+
# Calculate confidence based on keyword density
|
|
383
|
+
total_keywords = len(self.topics[topic])
|
|
384
|
+
unique_matches = len(set(kw.lower() for kw in matched_keywords))
|
|
385
|
+
confidence = min(unique_matches / total_keywords * 2, 1.0)
|
|
386
|
+
|
|
387
|
+
detected[topic] = {
|
|
388
|
+
"count": len(matched_keywords),
|
|
389
|
+
"unique_count": unique_matches,
|
|
390
|
+
"keywords": list(set(matched_keywords)),
|
|
391
|
+
"confidence": confidence,
|
|
392
|
+
"method": "keyword",
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return detected
|
|
396
|
+
|
|
397
|
+
def _detect_topics(self, text: str) -> Dict[str, Dict]:
|
|
398
|
+
"""
|
|
399
|
+
Detect topics using configured method(s).
|
|
400
|
+
|
|
401
|
+
Returns:
|
|
402
|
+
Dict of {topic: {confidence, method, ...}}
|
|
403
|
+
"""
|
|
404
|
+
# Get keyword-based detection
|
|
405
|
+
keyword_results = self._detect_topics_keywords(text)
|
|
406
|
+
|
|
407
|
+
# Get semantic detection if enabled
|
|
408
|
+
semantic_results = {}
|
|
409
|
+
if self.use_embeddings and self._embeddings_available:
|
|
410
|
+
semantic_results = self._semantic_similarity(text)
|
|
411
|
+
|
|
412
|
+
# Combine results
|
|
413
|
+
if not self.use_embeddings or not self._embeddings_available:
|
|
414
|
+
return keyword_results
|
|
415
|
+
|
|
416
|
+
if not self.combine_scores:
|
|
417
|
+
# Semantic-only mode (filter by threshold)
|
|
418
|
+
return {
|
|
419
|
+
topic: info
|
|
420
|
+
for topic, info in semantic_results.items()
|
|
421
|
+
if info["confidence"] >= self.semantic_threshold
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
# Hybrid mode: combine scores
|
|
425
|
+
all_topics = set(keyword_results.keys()) | set(semantic_results.keys())
|
|
426
|
+
combined = {}
|
|
427
|
+
|
|
428
|
+
for topic in all_topics:
|
|
429
|
+
kw_info = keyword_results.get(topic, {})
|
|
430
|
+
sem_info = semantic_results.get(topic, {})
|
|
431
|
+
|
|
432
|
+
kw_conf = kw_info.get("confidence", 0.0)
|
|
433
|
+
sem_conf = sem_info.get("confidence", 0.0)
|
|
434
|
+
|
|
435
|
+
# Weighted combination
|
|
436
|
+
combined_confidence = (
|
|
437
|
+
self.keyword_weight * kw_conf +
|
|
438
|
+
self.embedding_weight * sem_conf
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
combined[topic] = {
|
|
442
|
+
"confidence": combined_confidence,
|
|
443
|
+
"keyword_confidence": kw_conf,
|
|
444
|
+
"semantic_confidence": sem_conf,
|
|
445
|
+
"method": "hybrid",
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
# Include keyword details if available
|
|
449
|
+
if kw_info:
|
|
450
|
+
combined[topic]["keywords"] = kw_info.get("keywords", [])
|
|
451
|
+
combined[topic]["count"] = kw_info.get("count", 0)
|
|
452
|
+
|
|
453
|
+
# Include semantic similarity if available
|
|
454
|
+
if sem_info:
|
|
455
|
+
combined[topic]["similarity"] = sem_info.get("similarity", 0.0)
|
|
456
|
+
|
|
457
|
+
return combined
|
|
458
|
+
|
|
459
|
+
def scan(self, content: str, context: Optional[str] = None) -> ScanResult:
|
|
460
|
+
"""
|
|
461
|
+
Scan content for topic violations.
|
|
462
|
+
|
|
463
|
+
Uses keyword matching, semantic similarity, or both depending on config.
|
|
464
|
+
|
|
465
|
+
Args:
|
|
466
|
+
content: Content to scan
|
|
467
|
+
context: Optional context
|
|
468
|
+
|
|
469
|
+
Returns:
|
|
470
|
+
ScanResult with topic detection details
|
|
471
|
+
"""
|
|
472
|
+
start = time.perf_counter()
|
|
473
|
+
matches = []
|
|
474
|
+
issues = []
|
|
475
|
+
metadata: Dict[str, Any] = {}
|
|
476
|
+
|
|
477
|
+
# Record detection mode
|
|
478
|
+
if self.use_embeddings and self._embeddings_available:
|
|
479
|
+
metadata["detection_mode"] = "hybrid" if self.combine_scores else "semantic"
|
|
480
|
+
metadata["embedding_model"] = self.embedding_model_name
|
|
481
|
+
else:
|
|
482
|
+
metadata["detection_mode"] = "keyword"
|
|
483
|
+
if self.use_embeddings and not self._embeddings_available:
|
|
484
|
+
metadata["embedding_error"] = self._embeddings_load_error
|
|
485
|
+
|
|
486
|
+
# Detect topics
|
|
487
|
+
detected_topics = self._detect_topics(content)
|
|
488
|
+
|
|
489
|
+
# Also check context if provided
|
|
490
|
+
if context:
|
|
491
|
+
context_topics = self._detect_topics(context)
|
|
492
|
+
for topic, info in context_topics.items():
|
|
493
|
+
if topic in detected_topics:
|
|
494
|
+
# Merge: take max confidence
|
|
495
|
+
if info["confidence"] > detected_topics[topic]["confidence"]:
|
|
496
|
+
detected_topics[topic] = info
|
|
497
|
+
else:
|
|
498
|
+
detected_topics[topic] = info
|
|
499
|
+
|
|
500
|
+
# Check topic restrictions
|
|
501
|
+
violation = False
|
|
502
|
+
|
|
503
|
+
# Whitelist mode: only allowed topics are permitted
|
|
504
|
+
if self.allowed_topics:
|
|
505
|
+
for topic, info in detected_topics.items():
|
|
506
|
+
if info["confidence"] >= self.threshold:
|
|
507
|
+
if topic not in self.allowed_topics:
|
|
508
|
+
matches.append(ScanMatch(
|
|
509
|
+
pattern_name="off_topic",
|
|
510
|
+
matched_text=f"Topic: {topic}",
|
|
511
|
+
start=0,
|
|
512
|
+
end=len(content),
|
|
513
|
+
confidence=info["confidence"],
|
|
514
|
+
metadata={
|
|
515
|
+
"topic": topic,
|
|
516
|
+
"keywords": info.get("keywords", []),
|
|
517
|
+
"method": info.get("method", "unknown"),
|
|
518
|
+
},
|
|
519
|
+
))
|
|
520
|
+
issues.append(f"Off-topic: {topic}")
|
|
521
|
+
violation = True
|
|
522
|
+
|
|
523
|
+
# Also flag if no allowed topic was detected
|
|
524
|
+
allowed_detected = any(
|
|
525
|
+
topic in self.allowed_topics and info["confidence"] >= self.threshold
|
|
526
|
+
for topic, info in detected_topics.items()
|
|
527
|
+
)
|
|
528
|
+
if not allowed_detected and not violation and detected_topics:
|
|
529
|
+
# Content doesn't match any allowed topic
|
|
530
|
+
matches.append(ScanMatch(
|
|
531
|
+
pattern_name="no_allowed_topic",
|
|
532
|
+
matched_text="No allowed topic detected",
|
|
533
|
+
start=0,
|
|
534
|
+
end=len(content),
|
|
535
|
+
confidence=0.6,
|
|
536
|
+
))
|
|
537
|
+
issues.append("No allowed topic detected")
|
|
538
|
+
violation = True
|
|
539
|
+
|
|
540
|
+
# Blacklist mode: denied topics are blocked
|
|
541
|
+
if self.denied_topics:
|
|
542
|
+
for topic, info in detected_topics.items():
|
|
543
|
+
if topic in self.denied_topics and info["confidence"] >= self.threshold:
|
|
544
|
+
matches.append(ScanMatch(
|
|
545
|
+
pattern_name="denied_topic",
|
|
546
|
+
matched_text=f"Topic: {topic}",
|
|
547
|
+
start=0,
|
|
548
|
+
end=len(content),
|
|
549
|
+
confidence=info["confidence"],
|
|
550
|
+
metadata={
|
|
551
|
+
"topic": topic,
|
|
552
|
+
"keywords": info.get("keywords", []),
|
|
553
|
+
"method": info.get("method", "unknown"),
|
|
554
|
+
},
|
|
555
|
+
))
|
|
556
|
+
issues.append(f"Denied topic: {topic}")
|
|
557
|
+
violation = True
|
|
558
|
+
|
|
559
|
+
latency = (time.perf_counter() - start) * 1000
|
|
560
|
+
|
|
561
|
+
# Determine result
|
|
562
|
+
max_confidence = max([m.confidence for m in matches], default=0.0)
|
|
563
|
+
|
|
564
|
+
metadata["detected_topics"] = {
|
|
565
|
+
k: v for k, v in detected_topics.items()
|
|
566
|
+
if v["confidence"] >= self.threshold
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
if violation:
|
|
570
|
+
return self._create_result(
|
|
571
|
+
passed=False,
|
|
572
|
+
matches=matches,
|
|
573
|
+
score=max_confidence,
|
|
574
|
+
reason="; ".join(issues),
|
|
575
|
+
latency_ms=latency,
|
|
576
|
+
metadata=metadata,
|
|
577
|
+
)
|
|
578
|
+
|
|
579
|
+
return self._create_result(
|
|
580
|
+
passed=True,
|
|
581
|
+
matches=[],
|
|
582
|
+
score=0.0,
|
|
583
|
+
reason="Content is on-topic",
|
|
584
|
+
latency_ms=latency,
|
|
585
|
+
metadata=metadata,
|
|
586
|
+
)
|
|
587
|
+
|
|
588
|
+
@classmethod
|
|
589
|
+
def with_embeddings(
|
|
590
|
+
cls,
|
|
591
|
+
allowed_topics: Optional[List[str]] = None,
|
|
592
|
+
denied_topics: Optional[List[str]] = None,
|
|
593
|
+
embedding_model: Optional[str] = None,
|
|
594
|
+
threshold: float = 0.5,
|
|
595
|
+
**kwargs,
|
|
596
|
+
) -> "TopicRestrictionScanner":
|
|
597
|
+
"""
|
|
598
|
+
Factory method to create an embedding-enabled topic scanner.
|
|
599
|
+
|
|
600
|
+
Args:
|
|
601
|
+
allowed_topics: Allowed topic list
|
|
602
|
+
denied_topics: Denied topic list
|
|
603
|
+
embedding_model: Model to use (defaults to all-MiniLM-L6-v2)
|
|
604
|
+
threshold: Detection threshold
|
|
605
|
+
**kwargs: Additional arguments passed to __init__
|
|
606
|
+
|
|
607
|
+
Returns:
|
|
608
|
+
Configured TopicRestrictionScanner with embeddings enabled
|
|
609
|
+
"""
|
|
610
|
+
return cls(
|
|
611
|
+
allowed_topics=allowed_topics,
|
|
612
|
+
denied_topics=denied_topics,
|
|
613
|
+
use_embeddings=True,
|
|
614
|
+
embedding_model=embedding_model,
|
|
615
|
+
threshold=threshold,
|
|
616
|
+
**kwargs,
|
|
617
|
+
)
|
|
618
|
+
|
|
619
|
+
@classmethod
|
|
620
|
+
def semantic_only(
|
|
621
|
+
cls,
|
|
622
|
+
allowed_topics: Optional[List[str]] = None,
|
|
623
|
+
denied_topics: Optional[List[str]] = None,
|
|
624
|
+
embedding_model: Optional[str] = None,
|
|
625
|
+
threshold: float = 0.5,
|
|
626
|
+
**kwargs,
|
|
627
|
+
) -> "TopicRestrictionScanner":
|
|
628
|
+
"""
|
|
629
|
+
Factory method to create a semantic-only topic scanner (no keywords).
|
|
630
|
+
|
|
631
|
+
Args:
|
|
632
|
+
allowed_topics: Allowed topic list
|
|
633
|
+
denied_topics: Denied topic list
|
|
634
|
+
embedding_model: Model to use
|
|
635
|
+
threshold: Detection threshold
|
|
636
|
+
**kwargs: Additional arguments
|
|
637
|
+
|
|
638
|
+
Returns:
|
|
639
|
+
Configured TopicRestrictionScanner using semantic-only detection
|
|
640
|
+
"""
|
|
641
|
+
return cls(
|
|
642
|
+
allowed_topics=allowed_topics,
|
|
643
|
+
denied_topics=denied_topics,
|
|
644
|
+
use_embeddings=True,
|
|
645
|
+
combine_scores=False,
|
|
646
|
+
embedding_model=embedding_model,
|
|
647
|
+
threshold=threshold,
|
|
648
|
+
**kwargs,
|
|
649
|
+
)
|