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.
Files changed (642) hide show
  1. agent_learning_kit-0.1.0.dist-info/METADATA +381 -0
  2. agent_learning_kit-0.1.0.dist-info/RECORD +642 -0
  3. agent_learning_kit-0.1.0.dist-info/WHEEL +4 -0
  4. agent_learning_kit-0.1.0.dist-info/entry_points.txt +5 -0
  5. agent_learning_kit-0.1.0.dist-info/licenses/LICENSE +173 -0
  6. agent_learning_kit-0.1.0.dist-info/licenses/NOTICE +7 -0
  7. fi/__init__.py +5 -0
  8. fi/alk/__init__.py +57 -0
  9. fi/alk/_facade.py +31 -0
  10. fi/alk/_module_alias.py +68 -0
  11. fi/alk/_paths.py +14 -0
  12. fi/alk/_schema.py +522 -0
  13. fi/alk/actions.py +727 -0
  14. fi/alk/bench/__init__.py +517 -0
  15. fi/alk/bench/_codeexec.py +213 -0
  16. fi/alk/bench/_coding.py +215 -0
  17. fi/alk/bench/_docker.py +237 -0
  18. fi/alk/bench/_grader.py +286 -0
  19. fi/alk/bench/_pull.py +212 -0
  20. fi/alk/bench/_voice.py +147 -0
  21. fi/alk/capabilities.py +627 -0
  22. fi/alk/cli.py +6396 -0
  23. fi/alk/config.py +130 -0
  24. fi/alk/cua_loop.py +562 -0
  25. fi/alk/evals.py +2351 -0
  26. fi/alk/extensions.py +163 -0
  27. fi/alk/harness/ARCHITECTURE.md +231 -0
  28. fi/alk/harness/DESIGN.md +246 -0
  29. fi/alk/harness/ENVIRONMENT_CONFORMANCE.md +127 -0
  30. fi/alk/harness/HOW-IT-WORKS.md +297 -0
  31. fi/alk/harness/IMPLEMENTATION_AND_VALIDATION_STATUS.md +229 -0
  32. fi/alk/harness/README.md +417 -0
  33. fi/alk/harness/__init__.py +77 -0
  34. fi/alk/harness/__main__.py +3 -0
  35. fi/alk/harness/amend.py +312 -0
  36. fi/alk/harness/artifacts.py +319 -0
  37. fi/alk/harness/authoring_entrypoint.py +189 -0
  38. fi/alk/harness/authoring_runtime_validation.py +267 -0
  39. fi/alk/harness/backends/README.md +43 -0
  40. fi/alk/harness/backends/__init__.py +122 -0
  41. fi/alk/harness/backends/base.py +241 -0
  42. fi/alk/harness/backends/claude.py +211 -0
  43. fi/alk/harness/backends/files.py +182 -0
  44. fi/alk/harness/backends/vertex_gemini.py +457 -0
  45. fi/alk/harness/background_noise.py +95 -0
  46. fi/alk/harness/build.py +385 -0
  47. fi/alk/harness/bundle.py +593 -0
  48. fi/alk/harness/bundle_author_v2.py +1831 -0
  49. fi/alk/harness/bundle_v2.py +719 -0
  50. fi/alk/harness/call_runner.py +1440 -0
  51. fi/alk/harness/callback_http_adapter.py +111 -0
  52. fi/alk/harness/catalogue.py +287 -0
  53. fi/alk/harness/chat.py +428 -0
  54. fi/alk/harness/chat_call_runner.py +506 -0
  55. fi/alk/harness/checks.py +136 -0
  56. fi/alk/harness/cli.py +1354 -0
  57. fi/alk/harness/config.py +338 -0
  58. fi/alk/harness/contract.py +718 -0
  59. fi/alk/harness/credentials.py +674 -0
  60. fi/alk/harness/data/persona_vocabulary.json +111 -0
  61. fi/alk/harness/environment.py +99 -0
  62. fi/alk/harness/environment_plan.py +168 -0
  63. fi/alk/harness/events.py +125 -0
  64. fi/alk/harness/executor.py +304 -0
  65. fi/alk/harness/folder.py +234 -0
  66. fi/alk/harness/generated_runtime.py +815 -0
  67. fi/alk/harness/github.py +72 -0
  68. fi/alk/harness/hosted_authoring_entrypoint.py +183 -0
  69. fi/alk/harness/hosted_entrypoint.py +2402 -0
  70. fi/alk/harness/hosted_scheduler.py +2218 -0
  71. fi/alk/harness/job.py +426 -0
  72. fi/alk/harness/judge.py +184 -0
  73. fi/alk/harness/livekit_source.py +50 -0
  74. fi/alk/harness/livekit_tool_trace_bootstrap.py +71 -0
  75. fi/alk/harness/observability.py +208 -0
  76. fi/alk/harness/outbound.py +3252 -0
  77. fi/alk/harness/packaging.py +515 -0
  78. fi/alk/harness/persona_guides.py +157 -0
  79. fi/alk/harness/platform.py +692 -0
  80. fi/alk/harness/process_preflight.py +764 -0
  81. fi/alk/harness/process_runtime.py +5670 -0
  82. fi/alk/harness/prove.py +425 -0
  83. fi/alk/harness/provider_import.py +703 -0
  84. fi/alk/harness/provider_lifecycle.py +392 -0
  85. fi/alk/harness/provision.py +2896 -0
  86. fi/alk/harness/reception.py +147 -0
  87. fi/alk/harness/retell_chat_call_runner.py +373 -0
  88. fi/alk/harness/run/__init__.py +296 -0
  89. fi/alk/harness/run/alk.py +184 -0
  90. fi/alk/harness/run/call.py +162 -0
  91. fi/alk/harness/run/conversation.py +264 -0
  92. fi/alk/harness/run/data/voices_by_language_and_gender.json +693 -0
  93. fi/alk/harness/run/evidence.py +195 -0
  94. fi/alk/harness/run/grade.py +598 -0
  95. fi/alk/harness/run/live.py +297 -0
  96. fi/alk/harness/run/models.py +56 -0
  97. fi/alk/harness/run/platform_evals.py +227 -0
  98. fi/alk/harness/run/sdk_voice.py +130 -0
  99. fi/alk/harness/run/simulation.py +1209 -0
  100. fi/alk/harness/run/stage.py +91 -0
  101. fi/alk/harness/run/targets.py +508 -0
  102. fi/alk/harness/run/tools.py +601 -0
  103. fi/alk/harness/run/voice.py +340 -0
  104. fi/alk/harness/runtime.py +172 -0
  105. fi/alk/harness/sandbox_server.py +2011 -0
  106. fi/alk/harness/sandbox_worker.py +44 -0
  107. fi/alk/harness/scenario.py +1048 -0
  108. fi/alk/harness/scenario_source.py +879 -0
  109. fi/alk/harness/scenario_tools.py +1143 -0
  110. fi/alk/harness/scenarios.py +915 -0
  111. fi/alk/harness/secrets.py +168 -0
  112. fi/alk/harness/service_catalog.py +97 -0
  113. fi/alk/harness/session.py +391 -0
  114. fi/alk/harness/sessions.py +372 -0
  115. fi/alk/harness/simulator.py +76 -0
  116. fi/alk/harness/simulator_voice.py +928 -0
  117. fi/alk/harness/skills/build-environment/SKILL.md +538 -0
  118. fi/alk/harness/skills/harness.md +131 -0
  119. fi/alk/harness/skills/kinds/chat.md +48 -0
  120. fi/alk/harness/skills/kinds/voice-voicemail.md +63 -0
  121. fi/alk/harness/skills/kinds/voice.md +59 -0
  122. fi/alk/harness/skills/plan-suite/SKILL.md +103 -0
  123. fi/alk/harness/skills/provision-environment/SKILL.md +136 -0
  124. fi/alk/harness/skills/run-scenarios/SKILL.md +112 -0
  125. fi/alk/harness/skills/understand-agent/SKILL.md +251 -0
  126. fi/alk/harness/skills/write-scenarios/SKILL.md +606 -0
  127. fi/alk/harness/skills/write-scenarios/references/refusals.md +28 -0
  128. fi/alk/harness/skills/write-scenarios/references/world-api.md +92 -0
  129. fi/alk/harness/source_data_invariants.py +444 -0
  130. fi/alk/harness/source_tool_evidence.py +79 -0
  131. fi/alk/harness/sources.py +253 -0
  132. fi/alk/harness/spend.py +140 -0
  133. fi/alk/harness/tool_trace_proxy.py +104 -0
  134. fi/alk/harness/tools.py +1018 -0
  135. fi/alk/harness/understand.py +169 -0
  136. fi/alk/harness/voicemail_audio.py +74 -0
  137. fi/alk/harness/world/__init__.py +33 -0
  138. fi/alk/harness/world/errors.py +68 -0
  139. fi/alk/harness/world/expectations.py +91 -0
  140. fi/alk/harness/world/handle.py +538 -0
  141. fi/alk/harness/world/kinds.py +196 -0
  142. fi/alk/harness/world/mutate.py +186 -0
  143. fi/alk/harness/world/probe.py +413 -0
  144. fi/alk/harness/world/provision.py +511 -0
  145. fi/alk/harness/world/provisioned.py +191 -0
  146. fi/alk/harness/world/runtime.py +616 -0
  147. fi/alk/harness/world/snapshot.py +288 -0
  148. fi/alk/harness/world/stores/__init__.py +305 -0
  149. fi/alk/harness/world/stores/container.py +215 -0
  150. fi/alk/harness/world/stores/inprocess.py +346 -0
  151. fi/alk/harness/world/stores/postgres.py +481 -0
  152. fi/alk/harness/world/stores/prove.py +202 -0
  153. fi/alk/harness/world/stores/sqlite.py +245 -0
  154. fi/alk/harness/world/stores/written.py +182 -0
  155. fi/alk/harness/world/tools.py +1516 -0
  156. fi/alk/harness/world/workspace.py +144 -0
  157. fi/alk/image_loop.py +453 -0
  158. fi/alk/image_perturb.py +241 -0
  159. fi/alk/improve.py +274 -0
  160. fi/alk/live/__init__.py +154 -0
  161. fi/alk/live/_attribution.py +184 -0
  162. fi/alk/live/_capture.py +264 -0
  163. fi/alk/live/_codec.py +391 -0
  164. fi/alk/live/_contract.py +134 -0
  165. fi/alk/live/_loopback.py +316 -0
  166. fi/alk/live/_perturb.py +449 -0
  167. fi/alk/live/_runner.py +386 -0
  168. fi/alk/live/_stats.py +561 -0
  169. fi/alk/live/_transcript.py +240 -0
  170. fi/alk/live/_workers/__init__.py +9 -0
  171. fi/alk/live/_workers/a2a_worker.py +316 -0
  172. fi/alk/live/_workers/langgraph_worker.py +217 -0
  173. fi/alk/live/_workers/livekit_worker.py +207 -0
  174. fi/alk/live/_workers/mcp_loopback_server.py +46 -0
  175. fi/alk/live/_workers/mcp_worker.py +158 -0
  176. fi/alk/live/_workers/pipecat_worker.py +189 -0
  177. fi/alk/live/a2a_lane.py +138 -0
  178. fi/alk/live/langgraph_lane.py +339 -0
  179. fi/alk/live/livekit_lane.py +376 -0
  180. fi/alk/live/mcp_lane.py +172 -0
  181. fi/alk/live/pipecat_lane.py +341 -0
  182. fi/alk/live/voice_redteam.py +494 -0
  183. fi/alk/loss.py +306 -0
  184. fi/alk/optimize.py +36260 -0
  185. fi/alk/practice/__init__.py +51 -0
  186. fi/alk/practice/_assess.py +103 -0
  187. fi/alk/practice/_budget.py +81 -0
  188. fi/alk/practice/_calibrate.py +69 -0
  189. fi/alk/practice/_capstone.py +86 -0
  190. fi/alk/practice/_contract.py +91 -0
  191. fi/alk/practice/_diagnose.py +79 -0
  192. fi/alk/practice/_drill.py +196 -0
  193. fi/alk/practice/_experiment.py +720 -0
  194. fi/alk/practice/_schedule.py +102 -0
  195. fi/alk/practice/_store.py +194 -0
  196. fi/alk/practice/_trainer.py +245 -0
  197. fi/alk/practice/_update.py +125 -0
  198. fi/alk/redteam.py +2621 -0
  199. fi/alk/rewardhack.py +237 -0
  200. fi/alk/simulate.py +10351 -0
  201. fi/alk/studio/__init__.py +82 -0
  202. fi/alk/studio/_bias.py +314 -0
  203. fi/alk/studio/_calibration.py +522 -0
  204. fi/alk/studio/_coverage.py +262 -0
  205. fi/alk/studio/_download.py +665 -0
  206. fi/alk/studio/_fidelity_attack.py +114 -0
  207. fi/alk/studio/_generate.py +652 -0
  208. fi/alk/studio/_library.py +370 -0
  209. fi/alk/studio/_scan.py +134 -0
  210. fi/alk/studio/_upgrade.py +42 -0
  211. fi/alk/studio/_vendor.py +172 -0
  212. fi/alk/suite.py +4200 -0
  213. fi/alk/tasks.py +828 -0
  214. fi/alk/telemetry/__init__.py +149 -0
  215. fi/alk/telemetry/_contract.py +141 -0
  216. fi/alk/telemetry/_emit.py +182 -0
  217. fi/alk/telemetry/_ledger.py +296 -0
  218. fi/alk/telemetry/_queue.py +127 -0
  219. fi/alk/telemetry/_row.py +294 -0
  220. fi/alk/telemetry/_run.py +233 -0
  221. fi/alk/telemetry/_sync.py +193 -0
  222. fi/alk/telemetry/_url.py +119 -0
  223. fi/alk/trinity.py +49397 -0
  224. fi/alk/voice_loop.py +174 -0
  225. fi/api/__init__.py +1 -0
  226. fi/api/auth.py +137 -0
  227. fi/api/types.py +29 -0
  228. fi/cli/__init__.py +9 -0
  229. fi/cli/assertions/__init__.py +25 -0
  230. fi/cli/assertions/conditions.py +76 -0
  231. fi/cli/assertions/evaluator.py +286 -0
  232. fi/cli/assertions/exit_codes.py +20 -0
  233. fi/cli/assertions/parser.py +131 -0
  234. fi/cli/assertions/reporter.py +194 -0
  235. fi/cli/commands/__init__.py +9 -0
  236. fi/cli/commands/config.py +165 -0
  237. fi/cli/commands/export.py +208 -0
  238. fi/cli/commands/init.py +112 -0
  239. fi/cli/commands/list_cmd.py +213 -0
  240. fi/cli/commands/run.py +486 -0
  241. fi/cli/commands/validate.py +173 -0
  242. fi/cli/commands/view.py +424 -0
  243. fi/cli/config/__init__.py +6 -0
  244. fi/cli/config/defaults.py +206 -0
  245. fi/cli/config/loader.py +155 -0
  246. fi/cli/config/schema.py +174 -0
  247. fi/cli/main.py +78 -0
  248. fi/cli/output/__init__.py +6 -0
  249. fi/cli/output/formatters.py +106 -0
  250. fi/cli/output/reporters.py +46 -0
  251. fi/cli/storage/__init__.py +5 -0
  252. fi/cli/storage/run_history.py +249 -0
  253. fi/cli/utils/__init__.py +5 -0
  254. fi/cli/utils/console.py +44 -0
  255. fi/evals/__init__.py +131 -0
  256. fi/evals/autoeval/__init__.py +137 -0
  257. fi/evals/autoeval/analyzer.py +211 -0
  258. fi/evals/autoeval/config.py +244 -0
  259. fi/evals/autoeval/export.py +213 -0
  260. fi/evals/autoeval/interactive.py +283 -0
  261. fi/evals/autoeval/pipeline.py +625 -0
  262. fi/evals/autoeval/prompts.py +139 -0
  263. fi/evals/autoeval/recommender.py +242 -0
  264. fi/evals/autoeval/rules.py +589 -0
  265. fi/evals/autoeval/templates.py +299 -0
  266. fi/evals/autoeval/types.py +232 -0
  267. fi/evals/core/__init__.py +16 -0
  268. fi/evals/core/cloud_registry.py +184 -0
  269. fi/evals/core/engines.py +368 -0
  270. fi/evals/core/evaluate.py +319 -0
  271. fi/evals/core/judge_prompt.py +90 -0
  272. fi/evals/core/prompt_generator.py +83 -0
  273. fi/evals/core/registry.py +57 -0
  274. fi/evals/core/result.py +55 -0
  275. fi/evals/evaluator.py +721 -0
  276. fi/evals/execution.py +168 -0
  277. fi/evals/feedback/__init__.py +32 -0
  278. fi/evals/feedback/calibrator.py +160 -0
  279. fi/evals/feedback/collector.py +214 -0
  280. fi/evals/feedback/hooks.py +81 -0
  281. fi/evals/feedback/retriever.py +128 -0
  282. fi/evals/feedback/store.py +272 -0
  283. fi/evals/feedback/types.py +99 -0
  284. fi/evals/framework/README.md +79 -0
  285. fi/evals/framework/__init__.py +267 -0
  286. fi/evals/framework/backends/Dockerfile.eval-runner +33 -0
  287. fi/evals/framework/backends/__init__.py +99 -0
  288. fi/evals/framework/backends/_container.py +141 -0
  289. fi/evals/framework/backends/_utils.py +145 -0
  290. fi/evals/framework/backends/base.py +223 -0
  291. fi/evals/framework/backends/celery_backend.py +417 -0
  292. fi/evals/framework/backends/celery_worker.py +78 -0
  293. fi/evals/framework/backends/kubernetes_backend.py +665 -0
  294. fi/evals/framework/backends/ray_backend.py +521 -0
  295. fi/evals/framework/backends/temporal.py +350 -0
  296. fi/evals/framework/backends/temporal_worker.py +126 -0
  297. fi/evals/framework/backends/thread_pool.py +286 -0
  298. fi/evals/framework/context.py +258 -0
  299. fi/evals/framework/enrichment.py +306 -0
  300. fi/evals/framework/evals/__init__.py +68 -0
  301. fi/evals/framework/evals/agentic.py +399 -0
  302. fi/evals/framework/evals/builder.py +609 -0
  303. fi/evals/framework/evals/semantic.py +142 -0
  304. fi/evals/framework/evaluator.py +647 -0
  305. fi/evals/framework/evaluators/__init__.py +22 -0
  306. fi/evals/framework/evaluators/blocking.py +347 -0
  307. fi/evals/framework/evaluators/non_blocking.py +577 -0
  308. fi/evals/framework/propagation.py +421 -0
  309. fi/evals/framework/protocols.py +385 -0
  310. fi/evals/framework/registry.py +370 -0
  311. fi/evals/framework/resilience/__init__.py +150 -0
  312. fi/evals/framework/resilience/circuit_breaker.py +309 -0
  313. fi/evals/framework/resilience/degradation.py +355 -0
  314. fi/evals/framework/resilience/health.py +505 -0
  315. fi/evals/framework/resilience/rate_limiter.py +228 -0
  316. fi/evals/framework/resilience/retry.py +274 -0
  317. fi/evals/framework/resilience/types.py +288 -0
  318. fi/evals/framework/resilience/wrapper.py +433 -0
  319. fi/evals/framework/types.py +218 -0
  320. fi/evals/guardrails/README.md +915 -0
  321. fi/evals/guardrails/__init__.py +96 -0
  322. fi/evals/guardrails/backends/__init__.py +43 -0
  323. fi/evals/guardrails/backends/azure.py +361 -0
  324. fi/evals/guardrails/backends/base.py +88 -0
  325. fi/evals/guardrails/backends/generic_llm.py +163 -0
  326. fi/evals/guardrails/backends/granite.py +216 -0
  327. fi/evals/guardrails/backends/llamaguard.py +221 -0
  328. fi/evals/guardrails/backends/local_base.py +479 -0
  329. fi/evals/guardrails/backends/openai.py +365 -0
  330. fi/evals/guardrails/backends/qwen.py +170 -0
  331. fi/evals/guardrails/backends/shieldgemma.py +154 -0
  332. fi/evals/guardrails/backends/turing.py +235 -0
  333. fi/evals/guardrails/backends/vllm_client.py +321 -0
  334. fi/evals/guardrails/backends/wildguard.py +188 -0
  335. fi/evals/guardrails/base.py +888 -0
  336. fi/evals/guardrails/config.py +221 -0
  337. fi/evals/guardrails/discovery.py +243 -0
  338. fi/evals/guardrails/gateway.py +437 -0
  339. fi/evals/guardrails/registry.py +231 -0
  340. fi/evals/guardrails/scanners/__init__.py +127 -0
  341. fi/evals/guardrails/scanners/base.py +191 -0
  342. fi/evals/guardrails/scanners/code_injection.py +243 -0
  343. fi/evals/guardrails/scanners/eval_delegate.py +574 -0
  344. fi/evals/guardrails/scanners/invisible_chars.py +351 -0
  345. fi/evals/guardrails/scanners/jailbreak.py +412 -0
  346. fi/evals/guardrails/scanners/language.py +288 -0
  347. fi/evals/guardrails/scanners/pipeline.py +260 -0
  348. fi/evals/guardrails/scanners/regex.py +311 -0
  349. fi/evals/guardrails/scanners/secrets.py +274 -0
  350. fi/evals/guardrails/scanners/topics.py +649 -0
  351. fi/evals/guardrails/scanners/urls.py +341 -0
  352. fi/evals/guardrails/types.py +96 -0
  353. fi/evals/llm/__init__.py +3 -0
  354. fi/evals/llm/base_llm_provider.py +35 -0
  355. fi/evals/llm/providers/litellm.py +70 -0
  356. fi/evals/local/__init__.py +90 -0
  357. fi/evals/local/evaluator.py +690 -0
  358. fi/evals/local/execution_mode.py +121 -0
  359. fi/evals/local/llm.py +489 -0
  360. fi/evals/local/metrics/__init__.py +19 -0
  361. fi/evals/local/registry.py +360 -0
  362. fi/evals/manager.py +1018 -0
  363. fi/evals/manager_types.py +362 -0
  364. fi/evals/metrics/__init__.py +185 -0
  365. fi/evals/metrics/agents/__init__.py +74 -0
  366. fi/evals/metrics/agents/metrics.py +693 -0
  367. fi/evals/metrics/agents/report.py +36463 -0
  368. fi/evals/metrics/agents/types.py +160 -0
  369. fi/evals/metrics/base_llm_metric.py +111 -0
  370. fi/evals/metrics/base_metric.py +138 -0
  371. fi/evals/metrics/code_security/__init__.py +305 -0
  372. fi/evals/metrics/code_security/analyzer.py +985 -0
  373. fi/evals/metrics/code_security/benchmarks/__init__.py +73 -0
  374. fi/evals/metrics/code_security/benchmarks/builtin.py +750 -0
  375. fi/evals/metrics/code_security/benchmarks/loader.py +580 -0
  376. fi/evals/metrics/code_security/benchmarks/types.py +308 -0
  377. fi/evals/metrics/code_security/detectors/__init__.py +186 -0
  378. fi/evals/metrics/code_security/detectors/base.py +394 -0
  379. fi/evals/metrics/code_security/detectors/cryptography.py +345 -0
  380. fi/evals/metrics/code_security/detectors/injection.py +744 -0
  381. fi/evals/metrics/code_security/detectors/secrets.py +287 -0
  382. fi/evals/metrics/code_security/detectors/serialization.py +192 -0
  383. fi/evals/metrics/code_security/joint_metrics.py +588 -0
  384. fi/evals/metrics/code_security/judges/__init__.py +83 -0
  385. fi/evals/metrics/code_security/judges/base.py +238 -0
  386. fi/evals/metrics/code_security/judges/dual_judge.py +534 -0
  387. fi/evals/metrics/code_security/judges/llm_judge.py +301 -0
  388. fi/evals/metrics/code_security/judges/pattern_judge.py +515 -0
  389. fi/evals/metrics/code_security/metrics.py +388 -0
  390. fi/evals/metrics/code_security/modes/__init__.py +63 -0
  391. fi/evals/metrics/code_security/modes/adversarial.py +284 -0
  392. fi/evals/metrics/code_security/modes/autocomplete.py +198 -0
  393. fi/evals/metrics/code_security/modes/base.py +283 -0
  394. fi/evals/metrics/code_security/modes/instruct.py +253 -0
  395. fi/evals/metrics/code_security/modes/repair.py +230 -0
  396. fi/evals/metrics/code_security/reports/__init__.py +57 -0
  397. fi/evals/metrics/code_security/reports/generator.py +404 -0
  398. fi/evals/metrics/code_security/reports/leaderboard.py +509 -0
  399. fi/evals/metrics/code_security/types.py +534 -0
  400. fi/evals/metrics/function_calling/__init__.py +34 -0
  401. fi/evals/metrics/function_calling/metrics.py +573 -0
  402. fi/evals/metrics/function_calling/types.py +87 -0
  403. fi/evals/metrics/hallucination/__init__.py +54 -0
  404. fi/evals/metrics/hallucination/detector.py +149 -0
  405. fi/evals/metrics/hallucination/metrics.py +390 -0
  406. fi/evals/metrics/hallucination/nli.py +253 -0
  407. fi/evals/metrics/hallucination/sentinel.py +106 -0
  408. fi/evals/metrics/hallucination/types.py +132 -0
  409. fi/evals/metrics/heuristics/aggregation_metrics.py +85 -0
  410. fi/evals/metrics/heuristics/json_metrics.py +87 -0
  411. fi/evals/metrics/heuristics/similarity_metrics.py +375 -0
  412. fi/evals/metrics/heuristics/string_metrics.py +391 -0
  413. fi/evals/metrics/llm_as_judges/__init__.py +17 -0
  414. fi/evals/metrics/llm_as_judges/custom_judge/metric.py +112 -0
  415. fi/evals/metrics/llm_as_judges/custom_judge/prompts.py +26 -0
  416. fi/evals/metrics/llm_as_judges/types.py +48 -0
  417. fi/evals/metrics/rag/__init__.py +111 -0
  418. fi/evals/metrics/rag/advanced/__init__.py +14 -0
  419. fi/evals/metrics/rag/advanced/multi_hop.py +283 -0
  420. fi/evals/metrics/rag/advanced/source_attribution.py +344 -0
  421. fi/evals/metrics/rag/generation/__init__.py +17 -0
  422. fi/evals/metrics/rag/generation/answer_relevancy.py +176 -0
  423. fi/evals/metrics/rag/generation/context_utilization.py +245 -0
  424. fi/evals/metrics/rag/generation/faithfulness.py +241 -0
  425. fi/evals/metrics/rag/generation/groundedness.py +131 -0
  426. fi/evals/metrics/rag/rag_score.py +277 -0
  427. fi/evals/metrics/rag/retrieval/__init__.py +20 -0
  428. fi/evals/metrics/rag/retrieval/context_entity_recall.py +124 -0
  429. fi/evals/metrics/rag/retrieval/context_precision.py +158 -0
  430. fi/evals/metrics/rag/retrieval/context_recall.py +106 -0
  431. fi/evals/metrics/rag/retrieval/noise_sensitivity.py +163 -0
  432. fi/evals/metrics/rag/retrieval/ranking.py +261 -0
  433. fi/evals/metrics/rag/types.py +100 -0
  434. fi/evals/metrics/rag/utils/__init__.py +62 -0
  435. fi/evals/metrics/rag/utils/claims.py +189 -0
  436. fi/evals/metrics/rag/utils/entities.py +244 -0
  437. fi/evals/metrics/rag/utils/nli.py +92 -0
  438. fi/evals/metrics/rag/utils/similarity.py +345 -0
  439. fi/evals/metrics/structured/__init__.py +114 -0
  440. fi/evals/metrics/structured/field_completeness.py +313 -0
  441. fi/evals/metrics/structured/hierarchy_score.py +366 -0
  442. fi/evals/metrics/structured/json_validation.py +190 -0
  443. fi/evals/metrics/structured/schema_compliance.py +280 -0
  444. fi/evals/metrics/structured/structured_output_score.py +298 -0
  445. fi/evals/metrics/structured/types.py +108 -0
  446. fi/evals/metrics/structured/validators/__init__.py +30 -0
  447. fi/evals/metrics/structured/validators/base.py +189 -0
  448. fi/evals/metrics/structured/validators/json_validator.py +196 -0
  449. fi/evals/metrics/structured/validators/pydantic_validator.py +178 -0
  450. fi/evals/metrics/structured/validators/yaml_validator.py +248 -0
  451. fi/evals/otel/__init__.py +266 -0
  452. fi/evals/otel/config.py +400 -0
  453. fi/evals/otel/conventions.py +463 -0
  454. fi/evals/otel/enrichment.py +371 -0
  455. fi/evals/otel/instrumentors/__init__.py +140 -0
  456. fi/evals/otel/instrumentors/anthropic.py +517 -0
  457. fi/evals/otel/instrumentors/base.py +382 -0
  458. fi/evals/otel/instrumentors/openai.py +673 -0
  459. fi/evals/otel/processors/__init__.py +36 -0
  460. fi/evals/otel/processors/base.py +473 -0
  461. fi/evals/otel/processors/cost.py +445 -0
  462. fi/evals/otel/processors/evaluation.py +559 -0
  463. fi/evals/otel/processors/llm.py +462 -0
  464. fi/evals/otel/tracer.py +506 -0
  465. fi/evals/otel/types.py +232 -0
  466. fi/evals/otel_utils.py +23 -0
  467. fi/evals/protect.py +671 -0
  468. fi/evals/protect_input_adapter.py +154 -0
  469. fi/evals/streaming/__init__.py +88 -0
  470. fi/evals/streaming/buffer.py +213 -0
  471. fi/evals/streaming/evaluator.py +551 -0
  472. fi/evals/streaming/policy.py +307 -0
  473. fi/evals/streaming/scorers.py +368 -0
  474. fi/evals/streaming/types.py +238 -0
  475. fi/evals/templates.py +472 -0
  476. fi/evals/types.py +156 -0
  477. fi/opt/__init__.py +221 -0
  478. fi/opt/_objective_scoring.py +85 -0
  479. fi/opt/base/__init__.py +11 -0
  480. fi/opt/base/base_generator.py +33 -0
  481. fi/opt/base/base_mapper.py +26 -0
  482. fi/opt/base/base_optimizer.py +45 -0
  483. fi/opt/base/evaluator.py +211 -0
  484. fi/opt/components.py +3095 -0
  485. fi/opt/datamappers/__init__.py +3 -0
  486. fi/opt/datamappers/basic_mapper.py +40 -0
  487. fi/opt/deployment.py +1021 -0
  488. fi/opt/evidence.py +4332 -0
  489. fi/opt/generators/__init__.py +3 -0
  490. fi/opt/generators/litellm.py +66 -0
  491. fi/opt/integrations/__init__.py +23 -0
  492. fi/opt/integrations/generative_suite.py +410 -0
  493. fi/opt/integrations/simulate.py +1313 -0
  494. fi/opt/mutations.py +771 -0
  495. fi/opt/observability.py +4639 -0
  496. fi/opt/optimizer_trace.py +889 -0
  497. fi/opt/optimizers/__init__.py +80 -0
  498. fi/opt/optimizers/agent.py +331 -0
  499. fi/opt/optimizers/agent_bandit.py +392 -0
  500. fi/opt/optimizers/agent_curriculum.py +635 -0
  501. fi/opt/optimizers/agent_evolution.py +894 -0
  502. fi/opt/optimizers/agent_feedback.py +1863 -0
  503. fi/opt/optimizers/agent_pareto.py +547 -0
  504. fi/opt/optimizers/agent_social_memory.py +1113 -0
  505. fi/opt/optimizers/agent_tpe.py +321 -0
  506. fi/opt/optimizers/bayesian_search.py +449 -0
  507. fi/opt/optimizers/council.py +2075 -0
  508. fi/opt/optimizers/futureagi_replay.py +799 -0
  509. fi/opt/optimizers/gepa.py +322 -0
  510. fi/opt/optimizers/metaprompt.py +243 -0
  511. fi/opt/optimizers/promptwizard.py +417 -0
  512. fi/opt/optimizers/protegi.py +329 -0
  513. fi/opt/optimizers/random_search.py +224 -0
  514. fi/opt/research.py +518 -0
  515. fi/opt/simulation.py +260 -0
  516. fi/opt/targets.py +232 -0
  517. fi/opt/types.py +66 -0
  518. fi/opt/utils/__init__.py +4 -0
  519. fi/opt/utils/early_stopping.py +266 -0
  520. fi/opt/utils/setup_logging.py +82 -0
  521. fi/simulate/__init__.py +540 -0
  522. fi/simulate/_hashing.py +35 -0
  523. fi/simulate/_logging.py +10 -0
  524. fi/simulate/adapters.py +87 -0
  525. fi/simulate/agent/__init__.py +120 -0
  526. fi/simulate/agent/browser.py +658 -0
  527. fi/simulate/agent/definition.py +587 -0
  528. fi/simulate/agent/frameworks.py +3528 -0
  529. fi/simulate/agent/generic.py +8286 -0
  530. fi/simulate/agent/import_probe.py +227 -0
  531. fi/simulate/agent/memory.py +905 -0
  532. fi/simulate/agent/mocks.py +101 -0
  533. fi/simulate/agent/multi_agent.py +361 -0
  534. fi/simulate/agent/orchestration.py +903 -0
  535. fi/simulate/agent/realtime.py +665 -0
  536. fi/simulate/agent/wrapper.py +99 -0
  537. fi/simulate/agent/wrappers/__init__.py +18 -0
  538. fi/simulate/agent/wrappers/anthropic.py +62 -0
  539. fi/simulate/agent/wrappers/gemini.py +65 -0
  540. fi/simulate/agent/wrappers/http.py +404 -0
  541. fi/simulate/agent/wrappers/langchain.py +80 -0
  542. fi/simulate/agent/wrappers/openai.py +75 -0
  543. fi/simulate/agent/wrappers/websocket.py +326 -0
  544. fi/simulate/artifacts/__init__.py +11 -0
  545. fi/simulate/artifacts/manifest.py +62 -0
  546. fi/simulate/cli.py +20560 -0
  547. fi/simulate/endpoints/__init__.py +45 -0
  548. fi/simulate/endpoints/_http_actor.py +73 -0
  549. fi/simulate/endpoints/actor_sources.py +243 -0
  550. fi/simulate/endpoints/base.py +107 -0
  551. fi/simulate/endpoints/builtins.py +10 -0
  552. fi/simulate/endpoints/callable.py +95 -0
  553. fi/simulate/endpoints/http.py +76 -0
  554. fi/simulate/endpoints/livekit.py +138 -0
  555. fi/simulate/endpoints/originators.py +132 -0
  556. fi/simulate/endpoints/profiles.py +348 -0
  557. fi/simulate/endpoints/retell.py +633 -0
  558. fi/simulate/endpoints/vapi.py +205 -0
  559. fi/simulate/endpoints/websocket.py +76 -0
  560. fi/simulate/environment.py +33026 -0
  561. fi/simulate/environments/__init__.py +11 -0
  562. fi/simulate/environments/base.py +73 -0
  563. fi/simulate/environments/chat.py +697 -0
  564. fi/simulate/environments/voice.py +212 -0
  565. fi/simulate/evaluation/__init__.py +4 -0
  566. fi/simulate/evaluation/ai_eval.py +227 -0
  567. fi/simulate/evidence/__init__.py +35 -0
  568. fi/simulate/evidence/base.py +59 -0
  569. fi/simulate/evidence/caller_observed.py +50 -0
  570. fi/simulate/evidence/livekit_instrumentation.py +51 -0
  571. fi/simulate/evidence/livekit_room.py +50 -0
  572. fi/simulate/evidence/otel.py +49 -0
  573. fi/simulate/evidence/providers/__init__.py +24 -0
  574. fi/simulate/evidence/providers/base.py +61 -0
  575. fi/simulate/evidence/providers/retell.py +376 -0
  576. fi/simulate/evidence/providers/vapi.py +426 -0
  577. fi/simulate/hosted/__init__.py +32 -0
  578. fi/simulate/hosted/child_entrypoint.py +306 -0
  579. fi/simulate/hosted/job.py +150 -0
  580. fi/simulate/hosted/targets.py +53 -0
  581. fi/simulate/instrumentation/__init__.py +5 -0
  582. fi/simulate/instrumentation/livekit/__init__.py +122 -0
  583. fi/simulate/manifest.py +1033 -0
  584. fi/simulate/matrix_cli.py +165 -0
  585. fi/simulate/realtime/__init__.py +40 -0
  586. fi/simulate/realtime/events.py +107 -0
  587. fi/simulate/realtime/media.py +61 -0
  588. fi/simulate/realtime/session.py +91 -0
  589. fi/simulate/recording/__init__.py +5 -0
  590. fi/simulate/recording/room_recorder.py +326 -0
  591. fi/simulate/registry.py +185 -0
  592. fi/simulate/results/__init__.py +9 -0
  593. fi/simulate/results/base.py +18 -0
  594. fi/simulate/results/filesystem.py +71 -0
  595. fi/simulate/results/futureagi.py +1340 -0
  596. fi/simulate/runtime/__init__.py +85 -0
  597. fi/simulate/runtime/capabilities.py +40 -0
  598. fi/simulate/runtime/events.py +63 -0
  599. fi/simulate/runtime/failures.py +25 -0
  600. fi/simulate/runtime/ids.py +34 -0
  601. fi/simulate/runtime/plan.py +70 -0
  602. fi/simulate/runtime/planner.py +102 -0
  603. fi/simulate/runtime/report.py +174 -0
  604. fi/simulate/runtime/run.py +75 -0
  605. fi/simulate/runtime/runner.py +333 -0
  606. fi/simulate/runtime/spec.py +186 -0
  607. fi/simulate/simulation/__init__.py +30 -0
  608. fi/simulate/simulation/behavior_policy.py +425 -0
  609. fi/simulate/simulation/bridge/__init__.py +9 -0
  610. fi/simulate/simulation/bridge/audio.py +29 -0
  611. fi/simulate/simulation/bridge/connector.py +46 -0
  612. fi/simulate/simulation/bridge/livekit.py +252 -0
  613. fi/simulate/simulation/bridge/retell.py +188 -0
  614. fi/simulate/simulation/bridge/vapi.py +177 -0
  615. fi/simulate/simulation/contract.py +419 -0
  616. fi/simulate/simulation/engines/__init__.py +12 -0
  617. fi/simulate/simulation/engines/base.py +21 -0
  618. fi/simulate/simulation/engines/cloud.py +517 -0
  619. fi/simulate/simulation/engines/livekit.py +4167 -0
  620. fi/simulate/simulation/engines/local_text.py +89 -0
  621. fi/simulate/simulation/fidelity.py +374 -0
  622. fi/simulate/simulation/gemini_tts_stream.py +110 -0
  623. fi/simulate/simulation/generator.py +91 -0
  624. fi/simulate/simulation/goal_machine.py +185 -0
  625. fi/simulate/simulation/livekit_models.py +467 -0
  626. fi/simulate/simulation/matrix.py +170 -0
  627. fi/simulate/simulation/models.py +279 -0
  628. fi/simulate/simulation/runner.py +153 -0
  629. fi/simulate/simulation/synthetic.py +880 -0
  630. fi/simulate/simulation/voice_prompt.py +502 -0
  631. fi/simulate/simulator/__init__.py +55 -0
  632. fi/simulate/simulator/builtins.py +53 -0
  633. fi/simulate/suite.py +1288 -0
  634. fi/simulate/utils/routes.py +164 -0
  635. fi/simulate/voice.py +225 -0
  636. fi/simulate/voice_cli.py +182 -0
  637. fi/utils/__init__.py +1 -0
  638. fi/utils/constants.py +14 -0
  639. fi/utils/errors.py +200 -0
  640. fi/utils/executor.py +26 -0
  641. fi/utils/routes.py +119 -0
  642. 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
+ }