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,365 @@
|
|
|
1
|
+
"""
|
|
2
|
+
OpenAI Moderation Backend for Guardrails.
|
|
3
|
+
|
|
4
|
+
Uses OpenAI's Moderation API (FREE) for content screening.
|
|
5
|
+
Supports the omni-moderation-latest model with 13 categories.
|
|
6
|
+
|
|
7
|
+
Docs: https://platform.openai.com/docs/guides/moderation
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
import time
|
|
12
|
+
from typing import Any, Dict, List, Optional
|
|
13
|
+
|
|
14
|
+
from fi.evals.guardrails.backends.base import BaseBackend
|
|
15
|
+
from fi.evals.guardrails.config import GuardrailModel, RailType
|
|
16
|
+
from fi.evals.guardrails.types import GuardrailResult
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# Category mapping from OpenAI to our standard categories
|
|
20
|
+
OPENAI_CATEGORY_MAP = {
|
|
21
|
+
"hate": "hate_speech",
|
|
22
|
+
"hate/threatening": "hate_speech",
|
|
23
|
+
"harassment": "harassment",
|
|
24
|
+
"harassment/threatening": "harassment",
|
|
25
|
+
"self-harm": "self_harm",
|
|
26
|
+
"self-harm/intent": "self_harm",
|
|
27
|
+
"self-harm/instructions": "self_harm",
|
|
28
|
+
"sexual": "sexual_content",
|
|
29
|
+
"sexual/minors": "sexual_content",
|
|
30
|
+
"violence": "violence",
|
|
31
|
+
"violence/graphic": "violence",
|
|
32
|
+
"illicit": "illegal_activity",
|
|
33
|
+
"illicit/violent": "illegal_activity",
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class OpenAIBackend(BaseBackend):
|
|
38
|
+
"""
|
|
39
|
+
OpenAI Moderation API backend.
|
|
40
|
+
|
|
41
|
+
This is a FREE API provided by OpenAI for content moderation.
|
|
42
|
+
It supports 13 categories with confidence scores from 0 to 1.
|
|
43
|
+
|
|
44
|
+
Usage:
|
|
45
|
+
backend = OpenAIBackend(
|
|
46
|
+
model=GuardrailModel.OPENAI_MODERATION,
|
|
47
|
+
api_key="sk-..." # or set OPENAI_API_KEY env var
|
|
48
|
+
)
|
|
49
|
+
results = backend.classify("some content", RailType.INPUT)
|
|
50
|
+
|
|
51
|
+
Rate Limits:
|
|
52
|
+
- 1,000 requests per minute
|
|
53
|
+
- 150,000 tokens per minute
|
|
54
|
+
- Does not count toward API usage limits
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
def __init__(
|
|
58
|
+
self,
|
|
59
|
+
model: GuardrailModel,
|
|
60
|
+
api_key: Optional[str] = None,
|
|
61
|
+
moderation_model: str = "omni-moderation-latest",
|
|
62
|
+
):
|
|
63
|
+
"""
|
|
64
|
+
Initialize OpenAI Moderation backend.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
model: The guardrail model enum value
|
|
68
|
+
api_key: OpenAI API key (falls back to OPENAI_API_KEY env var)
|
|
69
|
+
moderation_model: OpenAI moderation model to use
|
|
70
|
+
"""
|
|
71
|
+
super().__init__(model)
|
|
72
|
+
|
|
73
|
+
self._api_key = api_key or os.environ.get("OPENAI_API_KEY")
|
|
74
|
+
if not self._api_key:
|
|
75
|
+
raise ValueError(
|
|
76
|
+
"OpenAI API key required. Set OPENAI_API_KEY environment variable "
|
|
77
|
+
"or pass api_key parameter."
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
self._moderation_model = moderation_model
|
|
81
|
+
self._client = None
|
|
82
|
+
|
|
83
|
+
def _get_client(self):
|
|
84
|
+
"""Lazy-load the OpenAI client."""
|
|
85
|
+
if self._client is None:
|
|
86
|
+
try:
|
|
87
|
+
from openai import OpenAI
|
|
88
|
+
except ImportError:
|
|
89
|
+
raise ImportError(
|
|
90
|
+
"openai package required for OpenAI backend. "
|
|
91
|
+
"Install with: pip install openai"
|
|
92
|
+
)
|
|
93
|
+
self._client = OpenAI(api_key=self._api_key)
|
|
94
|
+
return self._client
|
|
95
|
+
|
|
96
|
+
def classify(
|
|
97
|
+
self,
|
|
98
|
+
content: str,
|
|
99
|
+
rail_type: RailType,
|
|
100
|
+
context: Optional[str] = None,
|
|
101
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
102
|
+
) -> List[GuardrailResult]:
|
|
103
|
+
"""
|
|
104
|
+
Classify content using OpenAI Moderation API.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
content: Content to classify
|
|
108
|
+
rail_type: Type of rail (input, output, retrieval)
|
|
109
|
+
context: Optional context (not used by OpenAI)
|
|
110
|
+
metadata: Optional metadata (not used by OpenAI)
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
List of GuardrailResult objects for flagged categories
|
|
114
|
+
"""
|
|
115
|
+
start_time = time.time()
|
|
116
|
+
results = []
|
|
117
|
+
|
|
118
|
+
# Handle empty content
|
|
119
|
+
if not content or not content.strip():
|
|
120
|
+
return [
|
|
121
|
+
GuardrailResult(
|
|
122
|
+
passed=True,
|
|
123
|
+
category="empty",
|
|
124
|
+
score=0.0,
|
|
125
|
+
model=self.model_name,
|
|
126
|
+
reason="Empty or whitespace-only content",
|
|
127
|
+
action="pass",
|
|
128
|
+
latency_ms=(time.time() - start_time) * 1000,
|
|
129
|
+
)
|
|
130
|
+
]
|
|
131
|
+
|
|
132
|
+
try:
|
|
133
|
+
client = self._get_client()
|
|
134
|
+
|
|
135
|
+
# Call OpenAI Moderation API
|
|
136
|
+
response = client.moderations.create(
|
|
137
|
+
input=content,
|
|
138
|
+
model=self._moderation_model,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
elapsed_ms = (time.time() - start_time) * 1000
|
|
142
|
+
|
|
143
|
+
if not response.results:
|
|
144
|
+
return [
|
|
145
|
+
GuardrailResult(
|
|
146
|
+
passed=True,
|
|
147
|
+
category="safe",
|
|
148
|
+
score=0.0,
|
|
149
|
+
model=self.model_name,
|
|
150
|
+
reason="No moderation results returned",
|
|
151
|
+
action="pass",
|
|
152
|
+
latency_ms=elapsed_ms,
|
|
153
|
+
)
|
|
154
|
+
]
|
|
155
|
+
|
|
156
|
+
# Process the first result (single input)
|
|
157
|
+
mod_result = response.results[0]
|
|
158
|
+
|
|
159
|
+
# Check if any category was flagged
|
|
160
|
+
if not mod_result.flagged:
|
|
161
|
+
return [
|
|
162
|
+
GuardrailResult(
|
|
163
|
+
passed=True,
|
|
164
|
+
category="safe",
|
|
165
|
+
score=0.0,
|
|
166
|
+
model=self.model_name,
|
|
167
|
+
reason="Content passed all moderation checks",
|
|
168
|
+
action="pass",
|
|
169
|
+
latency_ms=elapsed_ms,
|
|
170
|
+
)
|
|
171
|
+
]
|
|
172
|
+
|
|
173
|
+
# Extract flagged categories and scores
|
|
174
|
+
categories = mod_result.categories
|
|
175
|
+
scores = mod_result.category_scores
|
|
176
|
+
|
|
177
|
+
# Convert to dict for easier access
|
|
178
|
+
categories_dict = categories.model_dump() if hasattr(categories, 'model_dump') else dict(categories)
|
|
179
|
+
scores_dict = scores.model_dump() if hasattr(scores, 'model_dump') else dict(scores)
|
|
180
|
+
|
|
181
|
+
# Create results for each flagged category
|
|
182
|
+
for openai_category, is_flagged in categories_dict.items():
|
|
183
|
+
if is_flagged:
|
|
184
|
+
# Map to our category name
|
|
185
|
+
our_category = OPENAI_CATEGORY_MAP.get(
|
|
186
|
+
openai_category, openai_category.replace("/", "_").replace("-", "_")
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
# Get the score (handle nested category names)
|
|
190
|
+
score_key = openai_category.replace("/", "_").replace("-", "_")
|
|
191
|
+
score = scores_dict.get(score_key, scores_dict.get(openai_category, 0.5))
|
|
192
|
+
|
|
193
|
+
results.append(
|
|
194
|
+
GuardrailResult(
|
|
195
|
+
passed=False,
|
|
196
|
+
category=our_category,
|
|
197
|
+
score=float(score),
|
|
198
|
+
model=self.model_name,
|
|
199
|
+
reason=f"Content flagged for {openai_category}",
|
|
200
|
+
action="block",
|
|
201
|
+
latency_ms=elapsed_ms,
|
|
202
|
+
)
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
# If flagged but no specific categories (shouldn't happen), add generic
|
|
206
|
+
if not results:
|
|
207
|
+
results.append(
|
|
208
|
+
GuardrailResult(
|
|
209
|
+
passed=False,
|
|
210
|
+
category="harmful_content",
|
|
211
|
+
score=1.0,
|
|
212
|
+
model=self.model_name,
|
|
213
|
+
reason="Content flagged by OpenAI moderation",
|
|
214
|
+
action="block",
|
|
215
|
+
latency_ms=elapsed_ms,
|
|
216
|
+
)
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
except Exception as e:
|
|
220
|
+
elapsed_ms = (time.time() - start_time) * 1000
|
|
221
|
+
results.append(
|
|
222
|
+
GuardrailResult(
|
|
223
|
+
passed=False,
|
|
224
|
+
category="error",
|
|
225
|
+
score=0.0,
|
|
226
|
+
model=self.model_name,
|
|
227
|
+
reason=f"OpenAI API error: {str(e)}",
|
|
228
|
+
action="block",
|
|
229
|
+
latency_ms=elapsed_ms,
|
|
230
|
+
)
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
return results
|
|
234
|
+
|
|
235
|
+
async def classify_async(
|
|
236
|
+
self,
|
|
237
|
+
content: str,
|
|
238
|
+
rail_type: RailType,
|
|
239
|
+
context: Optional[str] = None,
|
|
240
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
241
|
+
) -> List[GuardrailResult]:
|
|
242
|
+
"""
|
|
243
|
+
Async version using OpenAI's async client.
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
content: Content to classify
|
|
247
|
+
rail_type: Type of rail
|
|
248
|
+
context: Optional context
|
|
249
|
+
metadata: Optional metadata
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
List of GuardrailResult objects
|
|
253
|
+
"""
|
|
254
|
+
start_time = time.time()
|
|
255
|
+
|
|
256
|
+
# Handle empty content
|
|
257
|
+
if not content or not content.strip():
|
|
258
|
+
return [
|
|
259
|
+
GuardrailResult(
|
|
260
|
+
passed=True,
|
|
261
|
+
category="empty",
|
|
262
|
+
score=0.0,
|
|
263
|
+
model=self.model_name,
|
|
264
|
+
reason="Empty or whitespace-only content",
|
|
265
|
+
action="pass",
|
|
266
|
+
latency_ms=(time.time() - start_time) * 1000,
|
|
267
|
+
)
|
|
268
|
+
]
|
|
269
|
+
|
|
270
|
+
try:
|
|
271
|
+
from openai import AsyncOpenAI
|
|
272
|
+
except ImportError:
|
|
273
|
+
# Fall back to sync implementation
|
|
274
|
+
return await super().classify_async(content, rail_type, context, metadata)
|
|
275
|
+
|
|
276
|
+
try:
|
|
277
|
+
async_client = AsyncOpenAI(api_key=self._api_key)
|
|
278
|
+
|
|
279
|
+
response = await async_client.moderations.create(
|
|
280
|
+
input=content,
|
|
281
|
+
model=self._moderation_model,
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
elapsed_ms = (time.time() - start_time) * 1000
|
|
285
|
+
|
|
286
|
+
if not response.results:
|
|
287
|
+
return [
|
|
288
|
+
GuardrailResult(
|
|
289
|
+
passed=True,
|
|
290
|
+
category="safe",
|
|
291
|
+
score=0.0,
|
|
292
|
+
model=self.model_name,
|
|
293
|
+
reason="No moderation results returned",
|
|
294
|
+
action="pass",
|
|
295
|
+
latency_ms=elapsed_ms,
|
|
296
|
+
)
|
|
297
|
+
]
|
|
298
|
+
|
|
299
|
+
mod_result = response.results[0]
|
|
300
|
+
|
|
301
|
+
if not mod_result.flagged:
|
|
302
|
+
return [
|
|
303
|
+
GuardrailResult(
|
|
304
|
+
passed=True,
|
|
305
|
+
category="safe",
|
|
306
|
+
score=0.0,
|
|
307
|
+
model=self.model_name,
|
|
308
|
+
reason="Content passed all moderation checks",
|
|
309
|
+
action="pass",
|
|
310
|
+
latency_ms=elapsed_ms,
|
|
311
|
+
)
|
|
312
|
+
]
|
|
313
|
+
|
|
314
|
+
results = []
|
|
315
|
+
categories_dict = mod_result.categories.model_dump() if hasattr(mod_result.categories, 'model_dump') else dict(mod_result.categories)
|
|
316
|
+
scores_dict = mod_result.category_scores.model_dump() if hasattr(mod_result.category_scores, 'model_dump') else dict(mod_result.category_scores)
|
|
317
|
+
|
|
318
|
+
for openai_category, is_flagged in categories_dict.items():
|
|
319
|
+
if is_flagged:
|
|
320
|
+
our_category = OPENAI_CATEGORY_MAP.get(
|
|
321
|
+
openai_category, openai_category.replace("/", "_").replace("-", "_")
|
|
322
|
+
)
|
|
323
|
+
score_key = openai_category.replace("/", "_").replace("-", "_")
|
|
324
|
+
score = scores_dict.get(score_key, scores_dict.get(openai_category, 0.5))
|
|
325
|
+
|
|
326
|
+
results.append(
|
|
327
|
+
GuardrailResult(
|
|
328
|
+
passed=False,
|
|
329
|
+
category=our_category,
|
|
330
|
+
score=float(score),
|
|
331
|
+
model=self.model_name,
|
|
332
|
+
reason=f"Content flagged for {openai_category}",
|
|
333
|
+
action="block",
|
|
334
|
+
latency_ms=elapsed_ms,
|
|
335
|
+
)
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
if not results:
|
|
339
|
+
results.append(
|
|
340
|
+
GuardrailResult(
|
|
341
|
+
passed=False,
|
|
342
|
+
category="harmful_content",
|
|
343
|
+
score=1.0,
|
|
344
|
+
model=self.model_name,
|
|
345
|
+
reason="Content flagged by OpenAI moderation",
|
|
346
|
+
action="block",
|
|
347
|
+
latency_ms=elapsed_ms,
|
|
348
|
+
)
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
return results
|
|
352
|
+
|
|
353
|
+
except Exception as e:
|
|
354
|
+
elapsed_ms = (time.time() - start_time) * 1000
|
|
355
|
+
return [
|
|
356
|
+
GuardrailResult(
|
|
357
|
+
passed=False,
|
|
358
|
+
category="error",
|
|
359
|
+
score=0.0,
|
|
360
|
+
model=self.model_name,
|
|
361
|
+
reason=f"OpenAI API error: {str(e)}",
|
|
362
|
+
action="block",
|
|
363
|
+
latency_ms=elapsed_ms,
|
|
364
|
+
)
|
|
365
|
+
]
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Qwen3Guard Backend for Guardrails.
|
|
3
|
+
|
|
4
|
+
Uses Alibaba's Qwen3Guard model for content safety classification.
|
|
5
|
+
Models: Qwen/Qwen3Guard-8B, Qwen/Qwen3Guard-4B
|
|
6
|
+
|
|
7
|
+
Features:
|
|
8
|
+
- 119 language support
|
|
9
|
+
- Safe/Controversial/Unsafe classification
|
|
10
|
+
- Streaming support (Qwen3Guard-Stream)
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from typing import List, Optional
|
|
14
|
+
|
|
15
|
+
from fi.evals.guardrails.backends.local_base import LocalModelBackend
|
|
16
|
+
from fi.evals.guardrails.config import RailType
|
|
17
|
+
from fi.evals.guardrails.types import GuardrailResult
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Qwen3GuardBackend(LocalModelBackend):
|
|
21
|
+
"""
|
|
22
|
+
Qwen3Guard safety classification backend.
|
|
23
|
+
|
|
24
|
+
Alibaba's Qwen3Guard is a multilingual safety classifier
|
|
25
|
+
supporting 119 languages with three-level classification.
|
|
26
|
+
|
|
27
|
+
Features:
|
|
28
|
+
- 119 language support
|
|
29
|
+
- Safe/Controversial/Unsafe classification
|
|
30
|
+
- Severity levels
|
|
31
|
+
- Available in 8B, 4B, and streaming variants
|
|
32
|
+
|
|
33
|
+
Usage:
|
|
34
|
+
backend = Qwen3GuardBackend(
|
|
35
|
+
model=GuardrailModel.QWEN3GUARD_8B,
|
|
36
|
+
vllm_url="http://localhost:28000",
|
|
37
|
+
)
|
|
38
|
+
results = backend.classify("How to make a bomb?", RailType.INPUT)
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
MODEL_VARIANTS = {
|
|
42
|
+
"qwen3guard-8b": "Qwen/Qwen3Guard-8B",
|
|
43
|
+
"qwen3guard-4b": "Qwen/Qwen3Guard-4B",
|
|
44
|
+
"qwen3guard-0.6b": "Qwen/Qwen3Guard-0.6B",
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
MAX_NEW_TOKENS = 64
|
|
48
|
+
TEMPERATURE = 0.1
|
|
49
|
+
|
|
50
|
+
# Qwen3Guard classification to our categories
|
|
51
|
+
SEVERITY_THRESHOLDS = {
|
|
52
|
+
"safe": 0.0,
|
|
53
|
+
"controversial": 0.5,
|
|
54
|
+
"unsafe": 1.0,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
def _get_hf_model_name(self) -> str:
|
|
58
|
+
"""Get the HuggingFace model name based on variant."""
|
|
59
|
+
model_value = self.model.value
|
|
60
|
+
return self.MODEL_VARIANTS.get(model_value, self.MODEL_VARIANTS["qwen3guard-8b"])
|
|
61
|
+
|
|
62
|
+
def _format_prompt(
|
|
63
|
+
self,
|
|
64
|
+
content: str,
|
|
65
|
+
rail_type: RailType,
|
|
66
|
+
context: Optional[str] = None,
|
|
67
|
+
) -> str:
|
|
68
|
+
"""
|
|
69
|
+
Format content into Qwen3Guard prompt.
|
|
70
|
+
|
|
71
|
+
Qwen3Guard uses a simple classification prompt.
|
|
72
|
+
"""
|
|
73
|
+
if rail_type == RailType.OUTPUT and context:
|
|
74
|
+
text_to_classify = f"User: {context}\nAssistant: {content}"
|
|
75
|
+
elif rail_type == RailType.OUTPUT:
|
|
76
|
+
text_to_classify = f"Assistant: {content}"
|
|
77
|
+
else:
|
|
78
|
+
text_to_classify = content
|
|
79
|
+
|
|
80
|
+
prompt = f"""<|im_start|>system
|
|
81
|
+
You are a content safety classifier. Classify the following content as:
|
|
82
|
+
- Safe: Content is appropriate and harmless
|
|
83
|
+
- Controversial: Content may be sensitive but not clearly harmful
|
|
84
|
+
- Unsafe: Content is harmful, dangerous, or violates policies
|
|
85
|
+
|
|
86
|
+
Respond with only the classification and a brief reason.<|im_end|>
|
|
87
|
+
<|im_start|>user
|
|
88
|
+
Classify this content:
|
|
89
|
+
|
|
90
|
+
{text_to_classify}<|im_end|>
|
|
91
|
+
<|im_start|>assistant
|
|
92
|
+
"""
|
|
93
|
+
return prompt
|
|
94
|
+
|
|
95
|
+
def _parse_response(
|
|
96
|
+
self,
|
|
97
|
+
response: str,
|
|
98
|
+
content: str,
|
|
99
|
+
rail_type: RailType,
|
|
100
|
+
) -> List[GuardrailResult]:
|
|
101
|
+
"""
|
|
102
|
+
Parse Qwen3Guard response.
|
|
103
|
+
|
|
104
|
+
Expected format:
|
|
105
|
+
Unsafe: This content contains instructions for creating weapons.
|
|
106
|
+
or:
|
|
107
|
+
Safe: This is a normal conversation.
|
|
108
|
+
"""
|
|
109
|
+
response_lower = response.lower().strip()
|
|
110
|
+
|
|
111
|
+
# Determine classification
|
|
112
|
+
if response_lower.startswith("unsafe"):
|
|
113
|
+
classification = "unsafe"
|
|
114
|
+
score = 1.0
|
|
115
|
+
passed = False
|
|
116
|
+
elif response_lower.startswith("controversial"):
|
|
117
|
+
classification = "controversial"
|
|
118
|
+
score = 0.5
|
|
119
|
+
passed = False # Treat controversial as failed for safety
|
|
120
|
+
else:
|
|
121
|
+
classification = "safe"
|
|
122
|
+
score = 0.0
|
|
123
|
+
passed = True
|
|
124
|
+
|
|
125
|
+
if passed:
|
|
126
|
+
return [
|
|
127
|
+
GuardrailResult(
|
|
128
|
+
passed=True,
|
|
129
|
+
category="safe",
|
|
130
|
+
score=score,
|
|
131
|
+
model=self.model_name,
|
|
132
|
+
reason="Content passed Qwen3Guard safety check",
|
|
133
|
+
action="pass",
|
|
134
|
+
latency_ms=0.0,
|
|
135
|
+
)
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
# Infer categories from content and response
|
|
139
|
+
categories = self._infer_categories(content, response)
|
|
140
|
+
|
|
141
|
+
results = []
|
|
142
|
+
for category in categories:
|
|
143
|
+
results.append(
|
|
144
|
+
GuardrailResult(
|
|
145
|
+
passed=False,
|
|
146
|
+
category=category,
|
|
147
|
+
score=score,
|
|
148
|
+
model=self.model_name,
|
|
149
|
+
reason=f"Qwen3Guard: {classification}",
|
|
150
|
+
action="block" if classification == "unsafe" else "flag",
|
|
151
|
+
latency_ms=0.0,
|
|
152
|
+
)
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
if not results:
|
|
156
|
+
results.append(
|
|
157
|
+
GuardrailResult(
|
|
158
|
+
passed=False,
|
|
159
|
+
category="harmful_content",
|
|
160
|
+
score=score,
|
|
161
|
+
model=self.model_name,
|
|
162
|
+
reason=f"Qwen3Guard: {classification}",
|
|
163
|
+
action="block" if classification == "unsafe" else "flag",
|
|
164
|
+
latency_ms=0.0,
|
|
165
|
+
)
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
return results
|
|
169
|
+
|
|
170
|
+
# _infer_categories inherited from LocalModelBackend
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ShieldGemma Backend for Guardrails.
|
|
3
|
+
|
|
4
|
+
Uses Google's ShieldGemma model for content safety classification.
|
|
5
|
+
Model: google/shieldgemma-2b
|
|
6
|
+
|
|
7
|
+
Features:
|
|
8
|
+
- Lightweight (2B parameters)
|
|
9
|
+
- Instruction-tuned for safety
|
|
10
|
+
- Yes/No classification
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from typing import List, Optional
|
|
14
|
+
|
|
15
|
+
from fi.evals.guardrails.backends.local_base import LocalModelBackend
|
|
16
|
+
from fi.evals.guardrails.config import RailType
|
|
17
|
+
from fi.evals.guardrails.types import GuardrailResult
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ShieldGemmaBackend(LocalModelBackend):
|
|
21
|
+
"""
|
|
22
|
+
ShieldGemma safety classification backend.
|
|
23
|
+
|
|
24
|
+
Google's ShieldGemma is a lightweight instruction-tuned safety
|
|
25
|
+
classifier based on the Gemma architecture.
|
|
26
|
+
|
|
27
|
+
Features:
|
|
28
|
+
- Lightweight (2B parameters)
|
|
29
|
+
- Fast inference
|
|
30
|
+
- Yes/No classification with optional explanation
|
|
31
|
+
- Good for edge deployment
|
|
32
|
+
|
|
33
|
+
Usage:
|
|
34
|
+
backend = ShieldGemmaBackend(
|
|
35
|
+
model=GuardrailModel.SHIELDGEMMA_2B,
|
|
36
|
+
vllm_url="http://localhost:28000",
|
|
37
|
+
)
|
|
38
|
+
results = backend.classify("How to make a bomb?", RailType.INPUT)
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
HF_MODEL_NAME = "google/shieldgemma-2b"
|
|
42
|
+
MAX_NEW_TOKENS = 64
|
|
43
|
+
TEMPERATURE = 0.1
|
|
44
|
+
|
|
45
|
+
# Safety categories ShieldGemma can detect
|
|
46
|
+
SAFETY_CATEGORIES = [
|
|
47
|
+
"dangerous",
|
|
48
|
+
"harassment",
|
|
49
|
+
"hate",
|
|
50
|
+
"sexual",
|
|
51
|
+
"self_harm",
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
def _format_prompt(
|
|
55
|
+
self,
|
|
56
|
+
content: str,
|
|
57
|
+
rail_type: RailType,
|
|
58
|
+
context: Optional[str] = None,
|
|
59
|
+
) -> str:
|
|
60
|
+
"""
|
|
61
|
+
Format content into ShieldGemma prompt.
|
|
62
|
+
|
|
63
|
+
ShieldGemma uses a structured safety check format.
|
|
64
|
+
"""
|
|
65
|
+
if rail_type == RailType.OUTPUT and context:
|
|
66
|
+
text_to_check = f"User message: {context}\nAI response: {content}"
|
|
67
|
+
task = "Check if the AI response is safe and appropriate."
|
|
68
|
+
elif rail_type == RailType.OUTPUT:
|
|
69
|
+
text_to_check = f"AI response: {content}"
|
|
70
|
+
task = "Check if this AI response is safe and appropriate."
|
|
71
|
+
else:
|
|
72
|
+
text_to_check = f"User message: {content}"
|
|
73
|
+
task = "Check if this user message requests harmful content."
|
|
74
|
+
|
|
75
|
+
prompt = f"""<start_of_turn>user
|
|
76
|
+
You are a safety classifier. {task}
|
|
77
|
+
|
|
78
|
+
Content to evaluate:
|
|
79
|
+
{text_to_check}
|
|
80
|
+
|
|
81
|
+
Does this content violate safety policies? Answer "Yes" if it contains dangerous, harmful, harassing, hateful, sexual, or self-harm content. Answer "No" if it is safe.
|
|
82
|
+
|
|
83
|
+
Answer with only "Yes" or "No" followed by a brief explanation.<end_of_turn>
|
|
84
|
+
<start_of_turn>model
|
|
85
|
+
"""
|
|
86
|
+
return prompt
|
|
87
|
+
|
|
88
|
+
def _parse_response(
|
|
89
|
+
self,
|
|
90
|
+
response: str,
|
|
91
|
+
content: str,
|
|
92
|
+
rail_type: RailType,
|
|
93
|
+
) -> List[GuardrailResult]:
|
|
94
|
+
"""
|
|
95
|
+
Parse ShieldGemma response.
|
|
96
|
+
|
|
97
|
+
Expected format:
|
|
98
|
+
Yes - This content contains instructions for violence.
|
|
99
|
+
or:
|
|
100
|
+
No - This is a safe request.
|
|
101
|
+
"""
|
|
102
|
+
response_clean = response.strip()
|
|
103
|
+
response_lower = response_clean.lower()
|
|
104
|
+
|
|
105
|
+
# Check for Yes/No at the start
|
|
106
|
+
is_violation = response_lower.startswith("yes")
|
|
107
|
+
is_safe = response_lower.startswith("no")
|
|
108
|
+
|
|
109
|
+
if is_safe and not is_violation:
|
|
110
|
+
return [
|
|
111
|
+
GuardrailResult(
|
|
112
|
+
passed=True,
|
|
113
|
+
category="safe",
|
|
114
|
+
score=0.0,
|
|
115
|
+
model=self.model_name,
|
|
116
|
+
reason="Content passed ShieldGemma safety check",
|
|
117
|
+
action="pass",
|
|
118
|
+
latency_ms=0.0,
|
|
119
|
+
)
|
|
120
|
+
]
|
|
121
|
+
|
|
122
|
+
# Content is unsafe
|
|
123
|
+
categories = self._infer_categories(content, response_clean)
|
|
124
|
+
|
|
125
|
+
results = []
|
|
126
|
+
for category in categories:
|
|
127
|
+
results.append(
|
|
128
|
+
GuardrailResult(
|
|
129
|
+
passed=False,
|
|
130
|
+
category=category,
|
|
131
|
+
score=1.0,
|
|
132
|
+
model=self.model_name,
|
|
133
|
+
reason=f"ShieldGemma: {response_clean[:100]}",
|
|
134
|
+
action="block",
|
|
135
|
+
latency_ms=0.0,
|
|
136
|
+
)
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
if not results:
|
|
140
|
+
results.append(
|
|
141
|
+
GuardrailResult(
|
|
142
|
+
passed=False,
|
|
143
|
+
category="harmful_content",
|
|
144
|
+
score=1.0,
|
|
145
|
+
model=self.model_name,
|
|
146
|
+
reason="ShieldGemma classified as unsafe",
|
|
147
|
+
action="block",
|
|
148
|
+
latency_ms=0.0,
|
|
149
|
+
)
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
return results
|
|
153
|
+
|
|
154
|
+
# _infer_categories inherited from LocalModelBackend
|