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,277 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Comprehensive RAG Score Metric.
|
|
3
|
+
|
|
4
|
+
Combines multiple RAG metrics into a single comprehensive score.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any, Dict, Optional
|
|
8
|
+
|
|
9
|
+
from ..base_metric import BaseMetric
|
|
10
|
+
from .types import RAGInput, RAGRetrievalInput
|
|
11
|
+
from .retrieval import ContextRecall, ContextPrecision
|
|
12
|
+
from .generation import AnswerRelevancy, ContextUtilization, RAGFaithfulness
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class RAGScore(BaseMetric[RAGInput]):
|
|
16
|
+
"""
|
|
17
|
+
Comprehensive RAG evaluation combining all metrics.
|
|
18
|
+
|
|
19
|
+
Evaluates both retrieval and generation components in a single call.
|
|
20
|
+
|
|
21
|
+
Default weights (configurable):
|
|
22
|
+
- Retrieval (40%): Context Recall, Context Precision
|
|
23
|
+
- Generation (40%): Faithfulness, Answer Relevancy
|
|
24
|
+
- Advanced (20%): Context Utilization
|
|
25
|
+
|
|
26
|
+
Score: 0.0 (poor RAG performance) to 1.0 (excellent RAG performance)
|
|
27
|
+
|
|
28
|
+
Example:
|
|
29
|
+
>>> rag_score = RAGScore()
|
|
30
|
+
>>> result = rag_score.evaluate([{
|
|
31
|
+
... "query": "What is the capital of France?",
|
|
32
|
+
... "response": "The capital of France is Paris.",
|
|
33
|
+
... "contexts": ["Paris is the capital and largest city of France."],
|
|
34
|
+
... "reference": "Paris is the capital of France."
|
|
35
|
+
... }])
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def metric_name(self) -> str:
|
|
40
|
+
return "rag_score"
|
|
41
|
+
|
|
42
|
+
def __init__(self, config: Optional[Dict[str, Any]] = None):
|
|
43
|
+
super().__init__(config)
|
|
44
|
+
self.weights = self.config.get("weights", {
|
|
45
|
+
"retrieval": 0.4,
|
|
46
|
+
"generation": 0.4,
|
|
47
|
+
"advanced": 0.2,
|
|
48
|
+
})
|
|
49
|
+
self.include_details = self.config.get("include_details", True)
|
|
50
|
+
|
|
51
|
+
def compute_one(self, inputs: RAGInput) -> Dict[str, Any]:
|
|
52
|
+
scores = {}
|
|
53
|
+
details = {}
|
|
54
|
+
|
|
55
|
+
# Prepare retrieval input — only if reference is available
|
|
56
|
+
reference = inputs.reference
|
|
57
|
+
has_reference = bool(reference and reference.strip())
|
|
58
|
+
|
|
59
|
+
if has_reference:
|
|
60
|
+
retrieval_input = RAGRetrievalInput(
|
|
61
|
+
query=inputs.query,
|
|
62
|
+
contexts=inputs.contexts,
|
|
63
|
+
reference=reference,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
recall_metric = ContextRecall()
|
|
67
|
+
recall_result = recall_metric.compute_one(retrieval_input)
|
|
68
|
+
scores["context_recall"] = recall_result["output"]
|
|
69
|
+
if self.include_details:
|
|
70
|
+
details["context_recall"] = recall_result.get("reason", "")
|
|
71
|
+
|
|
72
|
+
precision_metric = ContextPrecision()
|
|
73
|
+
precision_result = precision_metric.compute_one(retrieval_input)
|
|
74
|
+
scores["context_precision"] = precision_result["output"]
|
|
75
|
+
if self.include_details:
|
|
76
|
+
details["context_precision"] = precision_result.get("reason", "")
|
|
77
|
+
else:
|
|
78
|
+
scores["context_recall"] = 0.0
|
|
79
|
+
scores["context_precision"] = 0.0
|
|
80
|
+
if self.include_details:
|
|
81
|
+
details["context_recall"] = "No reference provided — skipped"
|
|
82
|
+
details["context_precision"] = "No reference provided — skipped"
|
|
83
|
+
|
|
84
|
+
retrieval_score = (scores["context_recall"] + scores["context_precision"]) / 2
|
|
85
|
+
|
|
86
|
+
# Generation metrics
|
|
87
|
+
from .types import AnswerRelevancyInput, ContextUtilizationInput
|
|
88
|
+
|
|
89
|
+
relevancy_input = AnswerRelevancyInput(
|
|
90
|
+
query=inputs.query,
|
|
91
|
+
response=inputs.response,
|
|
92
|
+
contexts=inputs.contexts,
|
|
93
|
+
)
|
|
94
|
+
relevancy_metric = AnswerRelevancy()
|
|
95
|
+
relevancy_result = relevancy_metric.compute_one(relevancy_input)
|
|
96
|
+
scores["answer_relevancy"] = relevancy_result["output"]
|
|
97
|
+
if self.include_details:
|
|
98
|
+
details["answer_relevancy"] = relevancy_result.get("reason", "")
|
|
99
|
+
|
|
100
|
+
faithfulness_metric = RAGFaithfulness()
|
|
101
|
+
faithfulness_result = faithfulness_metric.compute_one(inputs)
|
|
102
|
+
scores["faithfulness"] = faithfulness_result["output"]
|
|
103
|
+
if self.include_details:
|
|
104
|
+
details["faithfulness"] = faithfulness_result.get("reason", "")
|
|
105
|
+
|
|
106
|
+
generation_score = (scores["answer_relevancy"] + scores["faithfulness"]) / 2
|
|
107
|
+
|
|
108
|
+
# Advanced metrics
|
|
109
|
+
utilization_input = ContextUtilizationInput(
|
|
110
|
+
query=inputs.query,
|
|
111
|
+
response=inputs.response,
|
|
112
|
+
contexts=inputs.contexts,
|
|
113
|
+
)
|
|
114
|
+
utilization_metric = ContextUtilization()
|
|
115
|
+
utilization_result = utilization_metric.compute_one(utilization_input)
|
|
116
|
+
scores["context_utilization"] = utilization_result["output"]
|
|
117
|
+
if self.include_details:
|
|
118
|
+
details["context_utilization"] = utilization_result.get("reason", "")
|
|
119
|
+
|
|
120
|
+
advanced_score = scores["context_utilization"]
|
|
121
|
+
|
|
122
|
+
# Weighted combination
|
|
123
|
+
final_score = (
|
|
124
|
+
self.weights["retrieval"] * retrieval_score +
|
|
125
|
+
self.weights["generation"] * generation_score +
|
|
126
|
+
self.weights["advanced"] * advanced_score
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
result = {
|
|
130
|
+
"output": round(final_score, 4),
|
|
131
|
+
"reason": f"Retrieval={retrieval_score:.2f}, Generation={generation_score:.2f}, Advanced={advanced_score:.2f}",
|
|
132
|
+
"retrieval_score": round(retrieval_score, 4),
|
|
133
|
+
"generation_score": round(generation_score, 4),
|
|
134
|
+
"advanced_score": round(advanced_score, 4),
|
|
135
|
+
"component_scores": {k: round(v, 4) for k, v in scores.items()},
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if self.include_details:
|
|
139
|
+
result["component_details"] = details
|
|
140
|
+
|
|
141
|
+
return result
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class RAGScoreDetailed(BaseMetric[RAGInput]):
|
|
145
|
+
"""
|
|
146
|
+
Detailed RAG evaluation with all available metrics.
|
|
147
|
+
|
|
148
|
+
Runs all RAG metrics and provides comprehensive analysis.
|
|
149
|
+
More expensive but gives complete picture.
|
|
150
|
+
|
|
151
|
+
Components:
|
|
152
|
+
- Retrieval: Recall, Precision, Entity Recall
|
|
153
|
+
- Generation: Faithfulness, Relevancy, Groundedness
|
|
154
|
+
- Advanced: Context Utilization, Multi-hop (if applicable)
|
|
155
|
+
|
|
156
|
+
Score: 0.0 to 1.0
|
|
157
|
+
"""
|
|
158
|
+
|
|
159
|
+
@property
|
|
160
|
+
def metric_name(self) -> str:
|
|
161
|
+
return "rag_score_detailed"
|
|
162
|
+
|
|
163
|
+
def __init__(self, config: Optional[Dict[str, Any]] = None):
|
|
164
|
+
super().__init__(config)
|
|
165
|
+
self.retrieval_weight = self.config.get("retrieval_weight", 0.35)
|
|
166
|
+
self.generation_weight = self.config.get("generation_weight", 0.35)
|
|
167
|
+
self.advanced_weight = self.config.get("advanced_weight", 0.30)
|
|
168
|
+
|
|
169
|
+
def compute_one(self, inputs: RAGInput) -> Dict[str, Any]:
|
|
170
|
+
from .retrieval import ContextRecall, ContextPrecision, ContextEntityRecall
|
|
171
|
+
from .generation import AnswerRelevancy, ContextUtilization, Groundedness, RAGFaithfulness
|
|
172
|
+
from .types import AnswerRelevancyInput, ContextUtilizationInput
|
|
173
|
+
|
|
174
|
+
all_scores = {}
|
|
175
|
+
all_details = {}
|
|
176
|
+
|
|
177
|
+
# Prepare retrieval input — only if reference is available
|
|
178
|
+
reference = inputs.reference
|
|
179
|
+
has_reference = bool(reference and reference.strip())
|
|
180
|
+
|
|
181
|
+
if has_reference:
|
|
182
|
+
retrieval_input = RAGRetrievalInput(
|
|
183
|
+
query=inputs.query,
|
|
184
|
+
contexts=inputs.contexts,
|
|
185
|
+
reference=reference,
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
# === RETRIEVAL METRICS ===
|
|
189
|
+
recall = ContextRecall().compute_one(retrieval_input)
|
|
190
|
+
all_scores["context_recall"] = recall["output"]
|
|
191
|
+
all_details["context_recall"] = recall
|
|
192
|
+
|
|
193
|
+
precision = ContextPrecision().compute_one(retrieval_input)
|
|
194
|
+
all_scores["context_precision"] = precision["output"]
|
|
195
|
+
all_details["context_precision"] = precision
|
|
196
|
+
|
|
197
|
+
entity_recall = ContextEntityRecall().compute_one(retrieval_input)
|
|
198
|
+
all_scores["context_entity_recall"] = entity_recall["output"]
|
|
199
|
+
all_details["context_entity_recall"] = entity_recall
|
|
200
|
+
else:
|
|
201
|
+
for key in ("context_recall", "context_precision", "context_entity_recall"):
|
|
202
|
+
all_scores[key] = 0.0
|
|
203
|
+
all_details[key] = {"output": 0.0, "reason": "No reference provided — skipped"}
|
|
204
|
+
|
|
205
|
+
retrieval_avg = (
|
|
206
|
+
all_scores["context_recall"] +
|
|
207
|
+
all_scores["context_precision"] +
|
|
208
|
+
all_scores["context_entity_recall"]
|
|
209
|
+
) / 3
|
|
210
|
+
|
|
211
|
+
# === GENERATION METRICS ===
|
|
212
|
+
# Faithfulness
|
|
213
|
+
faithfulness = RAGFaithfulness().compute_one(inputs)
|
|
214
|
+
all_scores["faithfulness"] = faithfulness["output"]
|
|
215
|
+
all_details["faithfulness"] = faithfulness
|
|
216
|
+
|
|
217
|
+
# Answer Relevancy
|
|
218
|
+
relevancy_input = AnswerRelevancyInput(
|
|
219
|
+
query=inputs.query,
|
|
220
|
+
response=inputs.response,
|
|
221
|
+
contexts=inputs.contexts,
|
|
222
|
+
)
|
|
223
|
+
relevancy = AnswerRelevancy().compute_one(relevancy_input)
|
|
224
|
+
all_scores["answer_relevancy"] = relevancy["output"]
|
|
225
|
+
all_details["answer_relevancy"] = relevancy
|
|
226
|
+
|
|
227
|
+
# Groundedness
|
|
228
|
+
groundedness = Groundedness().compute_one(inputs)
|
|
229
|
+
all_scores["groundedness"] = groundedness["output"]
|
|
230
|
+
all_details["groundedness"] = groundedness
|
|
231
|
+
|
|
232
|
+
generation_avg = (
|
|
233
|
+
all_scores["faithfulness"] +
|
|
234
|
+
all_scores["answer_relevancy"] +
|
|
235
|
+
all_scores["groundedness"]
|
|
236
|
+
) / 3
|
|
237
|
+
|
|
238
|
+
# === ADVANCED METRICS ===
|
|
239
|
+
# Context Utilization
|
|
240
|
+
util_input = ContextUtilizationInput(
|
|
241
|
+
query=inputs.query,
|
|
242
|
+
response=inputs.response,
|
|
243
|
+
contexts=inputs.contexts,
|
|
244
|
+
)
|
|
245
|
+
utilization = ContextUtilization().compute_one(util_input)
|
|
246
|
+
all_scores["context_utilization"] = utilization["output"]
|
|
247
|
+
all_details["context_utilization"] = utilization
|
|
248
|
+
|
|
249
|
+
advanced_avg = all_scores["context_utilization"]
|
|
250
|
+
|
|
251
|
+
# === FINAL SCORE ===
|
|
252
|
+
final_score = (
|
|
253
|
+
self.retrieval_weight * retrieval_avg +
|
|
254
|
+
self.generation_weight * generation_avg +
|
|
255
|
+
self.advanced_weight * advanced_avg
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
# Determine quality level
|
|
259
|
+
if final_score >= 0.8:
|
|
260
|
+
quality = "excellent"
|
|
261
|
+
elif final_score >= 0.6:
|
|
262
|
+
quality = "good"
|
|
263
|
+
elif final_score >= 0.4:
|
|
264
|
+
quality = "fair"
|
|
265
|
+
else:
|
|
266
|
+
quality = "poor"
|
|
267
|
+
|
|
268
|
+
return {
|
|
269
|
+
"output": round(final_score, 4),
|
|
270
|
+
"reason": f"Quality: {quality} - Retrieval={retrieval_avg:.2f}, Generation={generation_avg:.2f}, Advanced={advanced_avg:.2f}",
|
|
271
|
+
"quality_level": quality,
|
|
272
|
+
"retrieval_average": round(retrieval_avg, 4),
|
|
273
|
+
"generation_average": round(generation_avg, 4),
|
|
274
|
+
"advanced_average": round(advanced_avg, 4),
|
|
275
|
+
"all_scores": {k: round(v, 4) for k, v in all_scores.items()},
|
|
276
|
+
"details": all_details,
|
|
277
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""
|
|
2
|
+
RAG Retrieval Metrics.
|
|
3
|
+
|
|
4
|
+
Metrics for evaluating the retrieval component of RAG systems.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .context_recall import ContextRecall
|
|
8
|
+
from .context_precision import ContextPrecision
|
|
9
|
+
from .context_entity_recall import ContextEntityRecall
|
|
10
|
+
from .noise_sensitivity import NoiseSensitivity
|
|
11
|
+
from .ranking import NDCG, MRR
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"ContextRecall",
|
|
15
|
+
"ContextPrecision",
|
|
16
|
+
"ContextEntityRecall",
|
|
17
|
+
"NoiseSensitivity",
|
|
18
|
+
"NDCG",
|
|
19
|
+
"MRR",
|
|
20
|
+
]
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Context Entity Recall Metric.
|
|
3
|
+
|
|
4
|
+
Measures entity-level retrieval coverage - what fraction of
|
|
5
|
+
entities from the reference appear in retrieved contexts.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Any, Dict, Optional, Set
|
|
9
|
+
|
|
10
|
+
from ...base_metric import BaseMetric
|
|
11
|
+
from ..types import RAGRetrievalInput
|
|
12
|
+
from ..utils import extract_entities, entities_match
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ContextEntityRecall(BaseMetric[RAGRetrievalInput]):
|
|
16
|
+
"""
|
|
17
|
+
Measures entity-level retrieval completeness.
|
|
18
|
+
|
|
19
|
+
Uses NER to extract entities from reference and contexts,
|
|
20
|
+
then calculates what fraction of reference entities appear
|
|
21
|
+
in the retrieved contexts.
|
|
22
|
+
|
|
23
|
+
Critical for fact-based use cases where specific entities
|
|
24
|
+
(names, dates, locations, amounts) must not be missed.
|
|
25
|
+
|
|
26
|
+
Formula:
|
|
27
|
+
Context Entity Recall = |Entities in Context ∩ Entities in Reference| / |Entities in Reference|
|
|
28
|
+
|
|
29
|
+
Score: 0.0 (no entities recalled) to 1.0 (all entities recalled)
|
|
30
|
+
|
|
31
|
+
Example:
|
|
32
|
+
>>> entity_recall = ContextEntityRecall()
|
|
33
|
+
>>> result = entity_recall.evaluate([{
|
|
34
|
+
... "query": "When did Einstein win the Nobel Prize?",
|
|
35
|
+
... "contexts": ["Albert Einstein received the Nobel Prize in Physics in 1921."],
|
|
36
|
+
... "reference": "Albert Einstein won the Nobel Prize in 1921."
|
|
37
|
+
... }])
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def metric_name(self) -> str:
|
|
42
|
+
return "context_entity_recall"
|
|
43
|
+
|
|
44
|
+
def __init__(self, config: Optional[Dict[str, Any]] = None):
|
|
45
|
+
super().__init__(config)
|
|
46
|
+
self.entity_types = self.config.get("entity_types", None) # None = all types
|
|
47
|
+
self.fuzzy_match = self.config.get("fuzzy_match", True)
|
|
48
|
+
self.case_sensitive = self.config.get("case_sensitive", False)
|
|
49
|
+
|
|
50
|
+
def compute_one(self, inputs: RAGRetrievalInput) -> Dict[str, Any]:
|
|
51
|
+
# Handle empty inputs
|
|
52
|
+
if not inputs.reference or not inputs.reference.strip():
|
|
53
|
+
return {
|
|
54
|
+
"output": 0.0,
|
|
55
|
+
"reason": "No reference provided — cannot measure entity recall",
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
# Extract entities from reference
|
|
59
|
+
reference_entities = extract_entities(
|
|
60
|
+
inputs.reference,
|
|
61
|
+
entity_types=self.entity_types,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
if not reference_entities:
|
|
65
|
+
return {
|
|
66
|
+
"output": 0.0,
|
|
67
|
+
"reason": "No entities found in reference",
|
|
68
|
+
"reference_entities": [],
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if not inputs.contexts:
|
|
72
|
+
return {
|
|
73
|
+
"output": 0.0,
|
|
74
|
+
"reason": "No contexts provided - cannot recall any entities",
|
|
75
|
+
"reference_entities": list(reference_entities),
|
|
76
|
+
"recalled_entities": [],
|
|
77
|
+
"missing_entities": list(reference_entities),
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# Extract entities from all contexts
|
|
81
|
+
context_entities: Set[str] = set()
|
|
82
|
+
for ctx in inputs.contexts:
|
|
83
|
+
ctx_ents = extract_entities(ctx, entity_types=self.entity_types)
|
|
84
|
+
context_entities.update(ctx_ents)
|
|
85
|
+
|
|
86
|
+
# Calculate entity overlap
|
|
87
|
+
recalled = set()
|
|
88
|
+
missing = set()
|
|
89
|
+
|
|
90
|
+
for ref_entity in reference_entities:
|
|
91
|
+
found = False
|
|
92
|
+
|
|
93
|
+
if self.fuzzy_match:
|
|
94
|
+
# Check for fuzzy matches
|
|
95
|
+
for ctx_entity in context_entities:
|
|
96
|
+
if entities_match(ref_entity, ctx_entity):
|
|
97
|
+
found = True
|
|
98
|
+
break
|
|
99
|
+
else:
|
|
100
|
+
# Exact match (case-insensitive by default)
|
|
101
|
+
if self.case_sensitive:
|
|
102
|
+
found = ref_entity in context_entities
|
|
103
|
+
else:
|
|
104
|
+
found = ref_entity.lower() in {e.lower() for e in context_entities}
|
|
105
|
+
|
|
106
|
+
if found:
|
|
107
|
+
recalled.add(ref_entity)
|
|
108
|
+
else:
|
|
109
|
+
missing.add(ref_entity)
|
|
110
|
+
|
|
111
|
+
# Calculate recall
|
|
112
|
+
recall = len(recalled) / len(reference_entities)
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
"output": round(recall, 4),
|
|
116
|
+
"reason": f"{len(recalled)}/{len(reference_entities)} entities recalled",
|
|
117
|
+
"total_reference_entities": len(reference_entities),
|
|
118
|
+
"recalled_count": len(recalled),
|
|
119
|
+
"missing_count": len(missing),
|
|
120
|
+
"reference_entities": list(reference_entities),
|
|
121
|
+
"recalled_entities": list(recalled),
|
|
122
|
+
"missing_entities": list(missing),
|
|
123
|
+
"context_entities": list(context_entities)[:20], # Limit for readability
|
|
124
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Context Precision Metric.
|
|
3
|
+
|
|
4
|
+
Measures retrieval ranking quality - whether relevant contexts
|
|
5
|
+
appear before irrelevant ones.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Any, Dict, Optional
|
|
9
|
+
|
|
10
|
+
from ...base_metric import BaseMetric
|
|
11
|
+
from ..types import RAGRetrievalInput
|
|
12
|
+
from ..utils import (
|
|
13
|
+
compute_semantic_similarity,
|
|
14
|
+
compute_word_overlap,
|
|
15
|
+
check_entailment,
|
|
16
|
+
NLILabel,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ContextPrecision(BaseMetric[RAGRetrievalInput]):
|
|
21
|
+
"""
|
|
22
|
+
Measures retrieval ranking quality.
|
|
23
|
+
|
|
24
|
+
Evaluates whether relevant contexts are ranked higher than
|
|
25
|
+
irrelevant ones. A single irrelevant chunk at position 1
|
|
26
|
+
significantly reduces the score vs. the same chunk at position 5.
|
|
27
|
+
|
|
28
|
+
Formula (Average Precision style):
|
|
29
|
+
Context Precision@K = Σ(Precision@k × v_k) / |Relevant items in top K|
|
|
30
|
+
|
|
31
|
+
Where:
|
|
32
|
+
- Precision@k = TP@k / (TP@k + FP@k)
|
|
33
|
+
- v_k ∈ {0,1} indicates relevance at rank k
|
|
34
|
+
|
|
35
|
+
Score: 0.0 (poor ranking) to 1.0 (perfect ranking)
|
|
36
|
+
|
|
37
|
+
Example:
|
|
38
|
+
>>> precision = ContextPrecision()
|
|
39
|
+
>>> result = precision.evaluate([{
|
|
40
|
+
... "query": "What is machine learning?",
|
|
41
|
+
... "contexts": [
|
|
42
|
+
... "Machine learning is a branch of AI.", # Relevant
|
|
43
|
+
... "The weather is nice today.", # Irrelevant
|
|
44
|
+
... ],
|
|
45
|
+
... "reference": "Machine learning is an AI technique."
|
|
46
|
+
... }])
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def metric_name(self) -> str:
|
|
51
|
+
return "context_precision"
|
|
52
|
+
|
|
53
|
+
def __init__(self, config: Optional[Dict[str, Any]] = None):
|
|
54
|
+
super().__init__(config)
|
|
55
|
+
self.relevance_threshold = self.config.get("relevance_threshold", 0.3)
|
|
56
|
+
self.use_reference = self.config.get("use_reference", True)
|
|
57
|
+
|
|
58
|
+
def compute_one(self, inputs: RAGRetrievalInput) -> Dict[str, Any]:
|
|
59
|
+
if not inputs.contexts:
|
|
60
|
+
return {
|
|
61
|
+
"output": 0.0,
|
|
62
|
+
"reason": "No contexts to evaluate",
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
# Determine relevance of each context
|
|
66
|
+
relevance = []
|
|
67
|
+
relevance_details = []
|
|
68
|
+
|
|
69
|
+
for i, ctx in enumerate(inputs.contexts):
|
|
70
|
+
if inputs.relevance_labels is not None and i < len(inputs.relevance_labels):
|
|
71
|
+
# Use provided labels
|
|
72
|
+
is_relevant = inputs.relevance_labels[i] == 1
|
|
73
|
+
relevance_score = 1.0 if is_relevant else 0.0
|
|
74
|
+
else:
|
|
75
|
+
# Compute relevance
|
|
76
|
+
relevance_score = self._compute_relevance(
|
|
77
|
+
ctx, inputs.reference, inputs.query
|
|
78
|
+
)
|
|
79
|
+
is_relevant = relevance_score >= self.relevance_threshold
|
|
80
|
+
|
|
81
|
+
relevance.append(1 if is_relevant else 0)
|
|
82
|
+
relevance_details.append({
|
|
83
|
+
"position": i + 1,
|
|
84
|
+
"relevant": is_relevant,
|
|
85
|
+
"score": round(relevance_score, 3),
|
|
86
|
+
"context_preview": ctx[:80] + "..." if len(ctx) > 80 else ctx,
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
# Calculate Average Precision
|
|
90
|
+
precision_sum = 0.0
|
|
91
|
+
relevant_count = 0
|
|
92
|
+
|
|
93
|
+
for k, is_rel in enumerate(relevance, 1):
|
|
94
|
+
if is_rel:
|
|
95
|
+
relevant_count += 1
|
|
96
|
+
precision_at_k = relevant_count / k
|
|
97
|
+
precision_sum += precision_at_k
|
|
98
|
+
|
|
99
|
+
total_relevant = sum(relevance)
|
|
100
|
+
if total_relevant == 0:
|
|
101
|
+
return {
|
|
102
|
+
"output": 0.0,
|
|
103
|
+
"reason": "No relevant contexts found",
|
|
104
|
+
"relevance_by_position": relevance,
|
|
105
|
+
"details": relevance_details,
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
avg_precision = precision_sum / total_relevant
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
"output": round(avg_precision, 4),
|
|
112
|
+
"reason": f"AP={avg_precision:.3f}, {total_relevant}/{len(relevance)} contexts relevant",
|
|
113
|
+
"total_contexts": len(relevance),
|
|
114
|
+
"relevant_contexts": total_relevant,
|
|
115
|
+
"relevance_by_position": relevance,
|
|
116
|
+
"details": relevance_details,
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
def _compute_relevance(
|
|
120
|
+
self, context: str, reference: str, query: str
|
|
121
|
+
) -> float:
|
|
122
|
+
"""
|
|
123
|
+
Compute relevance score for a context.
|
|
124
|
+
|
|
125
|
+
Combines multiple signals:
|
|
126
|
+
- Semantic similarity to reference
|
|
127
|
+
- Word overlap with reference
|
|
128
|
+
- Query relevance (if reference unavailable)
|
|
129
|
+
"""
|
|
130
|
+
scores = []
|
|
131
|
+
|
|
132
|
+
if reference:
|
|
133
|
+
# Semantic similarity to reference
|
|
134
|
+
sem_sim = compute_semantic_similarity(context, reference)
|
|
135
|
+
scores.append(sem_sim)
|
|
136
|
+
|
|
137
|
+
# Word overlap with reference
|
|
138
|
+
word_overlap = compute_word_overlap(context, reference)
|
|
139
|
+
scores.append(word_overlap)
|
|
140
|
+
|
|
141
|
+
# NLI-based check
|
|
142
|
+
label, nli_score = check_entailment(context, reference)
|
|
143
|
+
if label == NLILabel.ENTAILMENT:
|
|
144
|
+
scores.append(nli_score)
|
|
145
|
+
elif label == NLILabel.CONTRADICTION:
|
|
146
|
+
scores.append(0.0)
|
|
147
|
+
else:
|
|
148
|
+
scores.append(nli_score * 0.5)
|
|
149
|
+
|
|
150
|
+
# Query relevance as fallback or additional signal
|
|
151
|
+
if query:
|
|
152
|
+
query_sim = compute_semantic_similarity(context, query)
|
|
153
|
+
scores.append(query_sim * 0.8) # Slightly lower weight
|
|
154
|
+
|
|
155
|
+
if not scores:
|
|
156
|
+
return 0.0
|
|
157
|
+
|
|
158
|
+
return sum(scores) / len(scores)
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Context Recall Metric.
|
|
3
|
+
|
|
4
|
+
Measures retrieval completeness - how much of the ground truth
|
|
5
|
+
information is covered by retrieved contexts.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Any, Dict, Optional
|
|
9
|
+
|
|
10
|
+
from ...base_metric import BaseMetric
|
|
11
|
+
from ..types import RAGRetrievalInput
|
|
12
|
+
from ..utils import split_into_sentences, check_attribution
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ContextRecall(BaseMetric[RAGRetrievalInput]):
|
|
16
|
+
"""
|
|
17
|
+
Measures retrieval completeness.
|
|
18
|
+
|
|
19
|
+
Evaluates whether retrieved contexts contain all information
|
|
20
|
+
required to produce the ground truth answer.
|
|
21
|
+
|
|
22
|
+
Uses NLI-based attribution to check if reference sentences
|
|
23
|
+
can be inferred from the retrieved contexts.
|
|
24
|
+
|
|
25
|
+
Formula (Ragas-style):
|
|
26
|
+
Context Recall = |GT sentences attributable to context| / |GT sentences|
|
|
27
|
+
|
|
28
|
+
Score: 0.0 (no coverage) to 1.0 (complete coverage)
|
|
29
|
+
|
|
30
|
+
Example:
|
|
31
|
+
>>> recall = ContextRecall()
|
|
32
|
+
>>> result = recall.evaluate([{
|
|
33
|
+
... "query": "What is the capital of France?",
|
|
34
|
+
... "contexts": ["Paris is the capital of France."],
|
|
35
|
+
... "reference": "The capital of France is Paris."
|
|
36
|
+
... }])
|
|
37
|
+
>>> print(result.eval_results[0].output) # Close to 1.0
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def metric_name(self) -> str:
|
|
42
|
+
return "context_recall"
|
|
43
|
+
|
|
44
|
+
def __init__(self, config: Optional[Dict[str, Any]] = None):
|
|
45
|
+
super().__init__(config)
|
|
46
|
+
self.attribution_threshold = self.config.get("attribution_threshold", 0.5)
|
|
47
|
+
self.min_sentence_words = self.config.get("min_sentence_words", 3)
|
|
48
|
+
|
|
49
|
+
def compute_one(self, inputs: RAGRetrievalInput) -> Dict[str, Any]:
|
|
50
|
+
# Handle empty inputs
|
|
51
|
+
if not inputs.reference or not inputs.reference.strip():
|
|
52
|
+
return {
|
|
53
|
+
"output": 0.0,
|
|
54
|
+
"reason": "No reference provided — cannot measure recall",
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if not inputs.contexts:
|
|
58
|
+
return {
|
|
59
|
+
"output": 0.0,
|
|
60
|
+
"reason": "No contexts provided - cannot recall any information",
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
# Split reference into sentences
|
|
64
|
+
reference_sentences = split_into_sentences(inputs.reference)
|
|
65
|
+
|
|
66
|
+
# Filter very short sentences
|
|
67
|
+
reference_sentences = [
|
|
68
|
+
s for s in reference_sentences
|
|
69
|
+
if len(s.split()) >= self.min_sentence_words
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
if not reference_sentences:
|
|
73
|
+
return {
|
|
74
|
+
"output": 0.0,
|
|
75
|
+
"reason": "No verifiable sentences in reference",
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
# For each sentence, check attribution to any context
|
|
79
|
+
attributed = 0
|
|
80
|
+
attribution_details = []
|
|
81
|
+
|
|
82
|
+
for sentence in reference_sentences:
|
|
83
|
+
is_attributed, best_context, score = check_attribution(
|
|
84
|
+
sentence, inputs.contexts, self.attribution_threshold
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
if is_attributed:
|
|
88
|
+
attributed += 1
|
|
89
|
+
|
|
90
|
+
attribution_details.append({
|
|
91
|
+
"sentence": sentence[:100] + "..." if len(sentence) > 100 else sentence,
|
|
92
|
+
"attributed": is_attributed,
|
|
93
|
+
"score": round(score, 3),
|
|
94
|
+
"matched_context": best_context[:80] + "..." if best_context and len(best_context) > 80 else best_context,
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
# Calculate recall
|
|
98
|
+
recall = attributed / len(reference_sentences)
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
"output": round(recall, 4),
|
|
102
|
+
"reason": f"{attributed}/{len(reference_sentences)} reference sentences found in context",
|
|
103
|
+
"total_sentences": len(reference_sentences),
|
|
104
|
+
"attributed_sentences": attributed,
|
|
105
|
+
"details": attribution_details,
|
|
106
|
+
}
|