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,54 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Hallucination Detection Metrics.
|
|
3
|
+
|
|
4
|
+
NLI-based evaluation for detecting hallucinations in LLM outputs.
|
|
5
|
+
Provides fast, deterministic evaluation without LLM-as-judge dependency.
|
|
6
|
+
|
|
7
|
+
Architecture:
|
|
8
|
+
- nli.py: NLI entailment checking (transformer or heuristic fallback)
|
|
9
|
+
- sentinel.py: Fast rule-based pre-screening
|
|
10
|
+
- detector.py: Main orchestrator combining sentinel + NLI
|
|
11
|
+
- metrics.py: 5 BaseMetric classes for the evaluate() API
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from .types import (
|
|
15
|
+
HallucinationInput,
|
|
16
|
+
ClaimExtractionInput,
|
|
17
|
+
FactualConsistencyInput,
|
|
18
|
+
Claim,
|
|
19
|
+
NLIResult,
|
|
20
|
+
HallucinationResult,
|
|
21
|
+
)
|
|
22
|
+
from .metrics import (
|
|
23
|
+
Faithfulness,
|
|
24
|
+
ClaimSupport,
|
|
25
|
+
FactualConsistency,
|
|
26
|
+
ContradictionDetection,
|
|
27
|
+
HallucinationScore,
|
|
28
|
+
)
|
|
29
|
+
from .nli import NLILabel, check_entailment, check_contradiction
|
|
30
|
+
from .sentinel import HallucinationSentinel
|
|
31
|
+
from .detector import HallucinationDetector
|
|
32
|
+
|
|
33
|
+
__all__ = [
|
|
34
|
+
# Types
|
|
35
|
+
"HallucinationInput",
|
|
36
|
+
"ClaimExtractionInput",
|
|
37
|
+
"FactualConsistencyInput",
|
|
38
|
+
"Claim",
|
|
39
|
+
"NLIResult",
|
|
40
|
+
"HallucinationResult",
|
|
41
|
+
# Metrics
|
|
42
|
+
"Faithfulness",
|
|
43
|
+
"ClaimSupport",
|
|
44
|
+
"FactualConsistency",
|
|
45
|
+
"ContradictionDetection",
|
|
46
|
+
"HallucinationScore",
|
|
47
|
+
# NLI utilities
|
|
48
|
+
"NLILabel",
|
|
49
|
+
"check_entailment",
|
|
50
|
+
"check_contradiction",
|
|
51
|
+
# Components
|
|
52
|
+
"HallucinationSentinel",
|
|
53
|
+
"HallucinationDetector",
|
|
54
|
+
]
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""
|
|
2
|
+
HallucinationDetector — main orchestrator for hallucination detection.
|
|
3
|
+
|
|
4
|
+
Combines sentinel screening, claim extraction, and NLI classification
|
|
5
|
+
to provide comprehensive hallucination analysis.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import re
|
|
9
|
+
from typing import List, Optional
|
|
10
|
+
|
|
11
|
+
from .types import Claim, HallucinationResult
|
|
12
|
+
from .nli import NLILabel, nli_score_for_claim
|
|
13
|
+
from .sentinel import HallucinationSentinel
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _split_into_sentences(text: str) -> List[str]:
|
|
17
|
+
"""Split text into sentences."""
|
|
18
|
+
sentences = re.split(r"(?<=[.!?])\s+", text.strip())
|
|
19
|
+
return [s.strip() for s in sentences if s.strip()]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
_META_PATTERNS = [
|
|
23
|
+
r"^(I think|I believe|In my opinion|It seems|Perhaps)",
|
|
24
|
+
r"^(Here is|Here are|The following|Below is)",
|
|
25
|
+
r"^(Note:|Important:|Warning:|Disclaimer:)",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def extract_claims(text: str) -> List[Claim]:
|
|
30
|
+
"""
|
|
31
|
+
Extract verifiable claims from text.
|
|
32
|
+
|
|
33
|
+
Uses sentence-level extraction with heuristic filtering
|
|
34
|
+
to remove questions, meta-statements, and very short fragments.
|
|
35
|
+
"""
|
|
36
|
+
sentences = _split_into_sentences(text)
|
|
37
|
+
|
|
38
|
+
claims = []
|
|
39
|
+
for sentence in sentences:
|
|
40
|
+
if len(sentence.split()) < 3:
|
|
41
|
+
continue
|
|
42
|
+
if sentence.strip().endswith("?"):
|
|
43
|
+
continue
|
|
44
|
+
if any(re.match(p, sentence, re.IGNORECASE) for p in _META_PATTERNS):
|
|
45
|
+
continue
|
|
46
|
+
|
|
47
|
+
claims.append(
|
|
48
|
+
Claim(text=sentence, source_span=sentence, confidence=1.0)
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
return claims
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class HallucinationDetector:
|
|
55
|
+
"""
|
|
56
|
+
Main orchestrator for hallucination detection.
|
|
57
|
+
|
|
58
|
+
Pipeline:
|
|
59
|
+
1. Sentinel screens for obvious risk level
|
|
60
|
+
2. Claims are extracted from the response
|
|
61
|
+
3. Each claim is classified via NLI against the context
|
|
62
|
+
4. Results are aggregated into a HallucinationResult
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
def __init__(self, sentinel: Optional[HallucinationSentinel] = None):
|
|
66
|
+
self.sentinel = sentinel or HallucinationSentinel()
|
|
67
|
+
|
|
68
|
+
def detect(
|
|
69
|
+
self,
|
|
70
|
+
response: str,
|
|
71
|
+
context: str,
|
|
72
|
+
claims: Optional[List[Claim]] = None,
|
|
73
|
+
) -> HallucinationResult:
|
|
74
|
+
"""
|
|
75
|
+
Run full hallucination detection pipeline.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
response: The LLM response to check
|
|
79
|
+
context: The source context
|
|
80
|
+
claims: Optional pre-extracted claims
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
HallucinationResult with detailed analysis
|
|
84
|
+
"""
|
|
85
|
+
contexts = [context] if isinstance(context, str) else context
|
|
86
|
+
|
|
87
|
+
# Step 1: sentinel screening
|
|
88
|
+
risk_level, sentinel_details = self.sentinel.screen(response, contexts[0])
|
|
89
|
+
|
|
90
|
+
# Step 2: extract claims
|
|
91
|
+
if claims is None:
|
|
92
|
+
claims = extract_claims(response)
|
|
93
|
+
|
|
94
|
+
if not claims:
|
|
95
|
+
return HallucinationResult(
|
|
96
|
+
score=1.0,
|
|
97
|
+
claims_analyzed=0,
|
|
98
|
+
supported_claims=0,
|
|
99
|
+
unsupported_claims=0,
|
|
100
|
+
contradicted_claims=0,
|
|
101
|
+
claim_details=[],
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
# Step 3: classify each claim via NLI
|
|
105
|
+
supported = 0
|
|
106
|
+
unsupported = 0
|
|
107
|
+
contradicted = 0
|
|
108
|
+
claim_details = []
|
|
109
|
+
|
|
110
|
+
for claim in claims:
|
|
111
|
+
label, score, best_ctx = nli_score_for_claim(claim.text, contexts)
|
|
112
|
+
|
|
113
|
+
if label == NLILabel.ENTAILMENT:
|
|
114
|
+
status = "supported"
|
|
115
|
+
supported += 1
|
|
116
|
+
elif label == NLILabel.CONTRADICTION:
|
|
117
|
+
status = "contradicted"
|
|
118
|
+
contradicted += 1
|
|
119
|
+
else:
|
|
120
|
+
status = "unsupported"
|
|
121
|
+
unsupported += 1
|
|
122
|
+
|
|
123
|
+
claim_details.append({
|
|
124
|
+
"claim": claim.text[:100],
|
|
125
|
+
"status": status,
|
|
126
|
+
"nli_score": round(score, 3),
|
|
127
|
+
"best_context": best_ctx,
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
# Step 4: aggregate
|
|
131
|
+
total = len(claims)
|
|
132
|
+
support_ratio = supported / total
|
|
133
|
+
contradiction_penalty = contradicted / total
|
|
134
|
+
|
|
135
|
+
# Weighted score: support is good, contradictions are heavily penalized
|
|
136
|
+
overall_score = 0.6 * support_ratio + 0.4 * (1.0 - contradiction_penalty)
|
|
137
|
+
|
|
138
|
+
# Sentinel risk can further adjust: high risk with no support is worse
|
|
139
|
+
if risk_level == "high" and support_ratio < 0.3:
|
|
140
|
+
overall_score *= 0.8
|
|
141
|
+
|
|
142
|
+
return HallucinationResult(
|
|
143
|
+
score=round(overall_score, 4),
|
|
144
|
+
claims_analyzed=total,
|
|
145
|
+
supported_claims=supported,
|
|
146
|
+
unsupported_claims=unsupported,
|
|
147
|
+
contradicted_claims=contradicted,
|
|
148
|
+
claim_details=claim_details,
|
|
149
|
+
)
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Hallucination Detection Metrics.
|
|
3
|
+
|
|
4
|
+
NLI-based evaluation for detecting hallucinations in LLM outputs.
|
|
5
|
+
Uses the NLI layer (nli.py) for semantic entailment checking,
|
|
6
|
+
with heuristic fallback when transformers is not installed.
|
|
7
|
+
|
|
8
|
+
Metrics:
|
|
9
|
+
- Faithfulness — proportion of claims supported by context
|
|
10
|
+
- ClaimSupport — per-claim entailment detail
|
|
11
|
+
- FactualConsistency — NLI score against a reference
|
|
12
|
+
- ContradictionDetection — NLI contradiction detection
|
|
13
|
+
- HallucinationScore — composite (sentinel + NLI)
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from typing import Any, Dict, Optional
|
|
17
|
+
|
|
18
|
+
from ..base_metric import BaseMetric
|
|
19
|
+
from .types import HallucinationInput, FactualConsistencyInput
|
|
20
|
+
from .nli import NLILabel, check_entailment, check_contradiction, nli_score_for_claim
|
|
21
|
+
from .detector import extract_claims
|
|
22
|
+
from .sentinel import HallucinationSentinel
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# Neutral claims get partial credit in scoring
|
|
26
|
+
_NEUTRAL_SCORE = 0.4
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Faithfulness(BaseMetric[HallucinationInput]):
|
|
30
|
+
"""
|
|
31
|
+
Evaluates response faithfulness to provided context.
|
|
32
|
+
|
|
33
|
+
Measures the proportion of claims in the response that are
|
|
34
|
+
supported by the context, using NLI entailment checking.
|
|
35
|
+
|
|
36
|
+
Returns score from 0.0 (all hallucinated) to 1.0 (fully faithful).
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
supports_llm_judge = True
|
|
40
|
+
judge_description = "Whether every claim in the output is supported by the provided context."
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def metric_name(self) -> str:
|
|
44
|
+
return "faithfulness"
|
|
45
|
+
|
|
46
|
+
def __init__(self, config: Optional[Dict[str, Any]] = None):
|
|
47
|
+
super().__init__(config)
|
|
48
|
+
self.support_threshold = self.config.get("support_threshold", 0.5)
|
|
49
|
+
|
|
50
|
+
def compute_one(self, inputs: HallucinationInput) -> Dict[str, Any]:
|
|
51
|
+
claims = inputs.claims or extract_claims(inputs.response)
|
|
52
|
+
|
|
53
|
+
if not claims:
|
|
54
|
+
return {
|
|
55
|
+
"output": 1.0,
|
|
56
|
+
"reason": "No verifiable claims found in response.",
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
contexts = inputs.context if isinstance(inputs.context, list) else [inputs.context]
|
|
60
|
+
|
|
61
|
+
supported = 0
|
|
62
|
+
unsupported = 0
|
|
63
|
+
neutral_count = 0
|
|
64
|
+
claim_details = []
|
|
65
|
+
|
|
66
|
+
for claim in claims:
|
|
67
|
+
label, score, best_ctx = nli_score_for_claim(claim.text, contexts)
|
|
68
|
+
|
|
69
|
+
if label == NLILabel.ENTAILMENT and score >= self.support_threshold:
|
|
70
|
+
supported += 1
|
|
71
|
+
status = "supported"
|
|
72
|
+
elif label == NLILabel.CONTRADICTION:
|
|
73
|
+
unsupported += 1
|
|
74
|
+
status = "contradicted"
|
|
75
|
+
elif label == NLILabel.NEUTRAL:
|
|
76
|
+
neutral_count += 1
|
|
77
|
+
status = "neutral"
|
|
78
|
+
else:
|
|
79
|
+
unsupported += 1
|
|
80
|
+
status = "unsupported"
|
|
81
|
+
|
|
82
|
+
claim_details.append({
|
|
83
|
+
"claim": claim.text[:100],
|
|
84
|
+
"label": status,
|
|
85
|
+
"score": round(score, 3),
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
total = len(claims)
|
|
89
|
+
# Supported = 1.0, neutral = partial, unsupported/contradicted = 0.0
|
|
90
|
+
faithfulness_score = (supported + neutral_count * _NEUTRAL_SCORE) / total
|
|
91
|
+
|
|
92
|
+
reason_parts = [f"{supported}/{total} claims supported"]
|
|
93
|
+
if neutral_count > 0:
|
|
94
|
+
reason_parts.append(f"{neutral_count} neutral")
|
|
95
|
+
if unsupported > 0:
|
|
96
|
+
reason_parts.append(f"{unsupported} unsupported")
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
"output": round(faithfulness_score, 4),
|
|
100
|
+
"reason": ". ".join(reason_parts),
|
|
101
|
+
"details": claim_details,
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class ClaimSupport(BaseMetric[HallucinationInput]):
|
|
106
|
+
"""
|
|
107
|
+
Evaluates support level for individual claims via NLI.
|
|
108
|
+
|
|
109
|
+
Returns average NLI-based support score from 0.0 to 1.0.
|
|
110
|
+
Neutral claims receive partial credit.
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
supports_llm_judge = True
|
|
114
|
+
judge_description = "Per-claim entailment analysis — how well each claim is supported by context."
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def metric_name(self) -> str:
|
|
118
|
+
return "claim_support"
|
|
119
|
+
|
|
120
|
+
def compute_one(self, inputs: HallucinationInput) -> Dict[str, Any]:
|
|
121
|
+
claims = inputs.claims or extract_claims(inputs.response)
|
|
122
|
+
|
|
123
|
+
if not claims:
|
|
124
|
+
return {
|
|
125
|
+
"output": 1.0,
|
|
126
|
+
"reason": "No claims to evaluate.",
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
contexts = inputs.context if isinstance(inputs.context, list) else [inputs.context]
|
|
130
|
+
|
|
131
|
+
total_score = 0.0
|
|
132
|
+
claim_results = []
|
|
133
|
+
|
|
134
|
+
for claim in claims:
|
|
135
|
+
label, score, _ = nli_score_for_claim(claim.text, contexts)
|
|
136
|
+
|
|
137
|
+
if label == NLILabel.ENTAILMENT:
|
|
138
|
+
claim_score = score
|
|
139
|
+
elif label == NLILabel.NEUTRAL:
|
|
140
|
+
claim_score = score * _NEUTRAL_SCORE
|
|
141
|
+
else:
|
|
142
|
+
claim_score = 0.0
|
|
143
|
+
|
|
144
|
+
total_score += claim_score
|
|
145
|
+
claim_results.append({
|
|
146
|
+
"claim": claim.text[:80],
|
|
147
|
+
"support_score": round(claim_score, 3),
|
|
148
|
+
"nli_label": label.value,
|
|
149
|
+
"raw_nli_score": round(score, 3),
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
avg_score = total_score / len(claims)
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
"output": round(avg_score, 4),
|
|
156
|
+
"reason": f"Average claim support: {avg_score:.1%} across {len(claims)} claims",
|
|
157
|
+
"claims": claim_results,
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class FactualConsistency(BaseMetric[FactualConsistencyInput]):
|
|
162
|
+
"""
|
|
163
|
+
Evaluates factual consistency between response and reference using NLI.
|
|
164
|
+
|
|
165
|
+
Checks each claim against the reference text for entailment or contradiction.
|
|
166
|
+
|
|
167
|
+
Returns score from 0.0 to 1.0.
|
|
168
|
+
"""
|
|
169
|
+
|
|
170
|
+
supports_llm_judge = True
|
|
171
|
+
judge_description = "Whether the output is factually consistent with a reference text."
|
|
172
|
+
|
|
173
|
+
@property
|
|
174
|
+
def metric_name(self) -> str:
|
|
175
|
+
return "factual_consistency"
|
|
176
|
+
|
|
177
|
+
def compute_one(self, inputs: FactualConsistencyInput) -> Dict[str, Any]:
|
|
178
|
+
if not inputs.reference:
|
|
179
|
+
return {
|
|
180
|
+
"output": 0.0,
|
|
181
|
+
"reason": "No reference provided for factual consistency check.",
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
claims = inputs.claims or extract_claims(inputs.response)
|
|
185
|
+
|
|
186
|
+
if not claims:
|
|
187
|
+
return {
|
|
188
|
+
"output": 1.0,
|
|
189
|
+
"reason": "No factual claims found in response.",
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
consistent = 0
|
|
193
|
+
inconsistent = 0
|
|
194
|
+
neutral_count = 0
|
|
195
|
+
details = []
|
|
196
|
+
|
|
197
|
+
for claim in claims:
|
|
198
|
+
label, score = check_entailment(inputs.reference, claim.text)
|
|
199
|
+
is_contradicted, contradiction_conf = check_contradiction(
|
|
200
|
+
claim.text, inputs.reference
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
if is_contradicted and contradiction_conf > 0.5:
|
|
204
|
+
inconsistent += 1
|
|
205
|
+
details.append({"claim": claim.text[:80], "status": "contradicted"})
|
|
206
|
+
elif label == NLILabel.ENTAILMENT:
|
|
207
|
+
consistent += 1
|
|
208
|
+
details.append({"claim": claim.text[:80], "status": "consistent"})
|
|
209
|
+
elif label == NLILabel.NEUTRAL:
|
|
210
|
+
neutral_count += 1
|
|
211
|
+
details.append({"claim": claim.text[:80], "status": "neutral"})
|
|
212
|
+
else:
|
|
213
|
+
details.append({"claim": claim.text[:80], "status": "unverified"})
|
|
214
|
+
|
|
215
|
+
total = len(claims)
|
|
216
|
+
# Contradictions heavily penalized
|
|
217
|
+
if inconsistent > 0:
|
|
218
|
+
score = max(0.0, (consistent + neutral_count * _NEUTRAL_SCORE - inconsistent * 2) / total)
|
|
219
|
+
else:
|
|
220
|
+
score = (consistent + neutral_count * _NEUTRAL_SCORE) / total
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
"output": round(score, 4),
|
|
224
|
+
"reason": f"Consistent: {consistent}, Contradicted: {inconsistent}, Neutral: {neutral_count}, Total: {total}",
|
|
225
|
+
"details": details,
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
class ContradictionDetection(BaseMetric[HallucinationInput]):
|
|
230
|
+
"""
|
|
231
|
+
Detects contradictions between response and context using NLI.
|
|
232
|
+
|
|
233
|
+
Uses the NLI CONTRADICTION label for detection instead of
|
|
234
|
+
crude negation removal heuristics.
|
|
235
|
+
|
|
236
|
+
Returns 1.0 if no contradictions, 0.0 if contradictions found.
|
|
237
|
+
"""
|
|
238
|
+
|
|
239
|
+
supports_llm_judge = True
|
|
240
|
+
judge_description = "Whether the output contradicts the provided context."
|
|
241
|
+
|
|
242
|
+
@property
|
|
243
|
+
def metric_name(self) -> str:
|
|
244
|
+
return "contradiction_detection"
|
|
245
|
+
|
|
246
|
+
def compute_one(self, inputs: HallucinationInput) -> Dict[str, Any]:
|
|
247
|
+
claims = inputs.claims or extract_claims(inputs.response)
|
|
248
|
+
|
|
249
|
+
if not claims:
|
|
250
|
+
return {
|
|
251
|
+
"output": 1.0,
|
|
252
|
+
"reason": "No claims to check for contradictions.",
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
contexts = inputs.context if isinstance(inputs.context, list) else [inputs.context]
|
|
256
|
+
full_context = " ".join(contexts)
|
|
257
|
+
|
|
258
|
+
contradictions = []
|
|
259
|
+
|
|
260
|
+
for claim in claims:
|
|
261
|
+
is_contradicted, confidence = check_contradiction(claim.text, full_context)
|
|
262
|
+
if is_contradicted:
|
|
263
|
+
contradictions.append({
|
|
264
|
+
"claim": claim.text[:100],
|
|
265
|
+
"confidence": round(confidence, 3),
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
if contradictions:
|
|
269
|
+
return {
|
|
270
|
+
"output": 0.0,
|
|
271
|
+
"reason": f"Found {len(contradictions)} contradiction(s) in response.",
|
|
272
|
+
"contradictions": contradictions,
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
"output": 1.0,
|
|
277
|
+
"reason": "No contradictions detected.",
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
class HallucinationScore(BaseMetric[HallucinationInput]):
|
|
282
|
+
"""
|
|
283
|
+
Comprehensive hallucination detection score.
|
|
284
|
+
|
|
285
|
+
Combines:
|
|
286
|
+
- Sentinel pre-screening (fast risk assessment)
|
|
287
|
+
- NLI-based claim support analysis
|
|
288
|
+
- NLI contradiction detection
|
|
289
|
+
- Neutral handling with partial credit
|
|
290
|
+
|
|
291
|
+
Returns score from 0.0 (severe hallucination) to 1.0 (no hallucination).
|
|
292
|
+
"""
|
|
293
|
+
|
|
294
|
+
supports_llm_judge = True
|
|
295
|
+
judge_description = (
|
|
296
|
+
"Composite hallucination detection — how much of the output is fabricated "
|
|
297
|
+
"vs grounded in context."
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
@property
|
|
301
|
+
def metric_name(self) -> str:
|
|
302
|
+
return "hallucination_score"
|
|
303
|
+
|
|
304
|
+
def __init__(self, config: Optional[Dict[str, Any]] = None):
|
|
305
|
+
super().__init__(config)
|
|
306
|
+
self.support_weight = self.config.get("support_weight", 0.6)
|
|
307
|
+
self.contradiction_weight = self.config.get("contradiction_weight", 0.4)
|
|
308
|
+
self._sentinel = HallucinationSentinel()
|
|
309
|
+
|
|
310
|
+
def compute_one(self, inputs: HallucinationInput) -> Dict[str, Any]:
|
|
311
|
+
claims = inputs.claims or extract_claims(inputs.response)
|
|
312
|
+
|
|
313
|
+
if not claims:
|
|
314
|
+
return {
|
|
315
|
+
"output": 1.0,
|
|
316
|
+
"reason": "No verifiable claims in response.",
|
|
317
|
+
"claims_analyzed": 0,
|
|
318
|
+
"supported": 0,
|
|
319
|
+
"unsupported": 0,
|
|
320
|
+
"contradicted": 0,
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
contexts = inputs.context if isinstance(inputs.context, list) else [inputs.context]
|
|
324
|
+
|
|
325
|
+
# Sentinel screening
|
|
326
|
+
risk_level, _ = self._sentinel.screen(inputs.response, contexts[0])
|
|
327
|
+
|
|
328
|
+
supported = 0
|
|
329
|
+
unsupported = 0
|
|
330
|
+
contradicted = 0
|
|
331
|
+
neutral_count = 0
|
|
332
|
+
total_support_score = 0.0
|
|
333
|
+
details = []
|
|
334
|
+
|
|
335
|
+
for claim in claims:
|
|
336
|
+
label, score, _ = nli_score_for_claim(claim.text, contexts)
|
|
337
|
+
|
|
338
|
+
if label == NLILabel.ENTAILMENT:
|
|
339
|
+
total_support_score += score
|
|
340
|
+
status = "supported"
|
|
341
|
+
supported += 1
|
|
342
|
+
elif label == NLILabel.CONTRADICTION:
|
|
343
|
+
total_support_score += 0.0
|
|
344
|
+
status = "contradicted"
|
|
345
|
+
contradicted += 1
|
|
346
|
+
else:
|
|
347
|
+
# Neutral: partial credit
|
|
348
|
+
total_support_score += score * _NEUTRAL_SCORE
|
|
349
|
+
status = "neutral"
|
|
350
|
+
neutral_count += 1
|
|
351
|
+
|
|
352
|
+
details.append({
|
|
353
|
+
"claim": claim.text[:80],
|
|
354
|
+
"status": status,
|
|
355
|
+
"support_score": round(score, 3),
|
|
356
|
+
})
|
|
357
|
+
|
|
358
|
+
total = len(claims)
|
|
359
|
+
avg_support = total_support_score / total
|
|
360
|
+
contradiction_penalty = contradicted / total
|
|
361
|
+
|
|
362
|
+
final_score = (
|
|
363
|
+
self.support_weight * avg_support
|
|
364
|
+
+ self.contradiction_weight * (1.0 - contradiction_penalty)
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
# Sentinel adjustment: high risk with low support is worse
|
|
368
|
+
if risk_level == "high" and avg_support < 0.3:
|
|
369
|
+
final_score *= 0.8
|
|
370
|
+
|
|
371
|
+
severity = "none"
|
|
372
|
+
if final_score < 0.3:
|
|
373
|
+
severity = "severe"
|
|
374
|
+
elif final_score < 0.6:
|
|
375
|
+
severity = "moderate"
|
|
376
|
+
elif final_score < 0.8:
|
|
377
|
+
severity = "minor"
|
|
378
|
+
|
|
379
|
+
return {
|
|
380
|
+
"output": round(final_score, 4),
|
|
381
|
+
"reason": f"Hallucination severity: {severity}. "
|
|
382
|
+
f"Supported: {supported}, Neutral: {neutral_count}, "
|
|
383
|
+
f"Unsupported: {unsupported}, Contradicted: {contradicted}",
|
|
384
|
+
"claims_analyzed": total,
|
|
385
|
+
"supported": supported,
|
|
386
|
+
"unsupported": unsupported,
|
|
387
|
+
"contradicted": contradicted,
|
|
388
|
+
"sentinel_risk": risk_level,
|
|
389
|
+
"details": details,
|
|
390
|
+
}
|