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,609 @@
1
+ """Custom evaluation builder for creating evaluations without full classes.
2
+
3
+ This module provides a builder pattern and decorator-based approach
4
+ for defining custom evaluations with minimal boilerplate.
5
+
6
+ Example using decorator:
7
+ from fi.evals.framework.evals.builder import custom_eval
8
+
9
+ @custom_eval("sentiment_score")
10
+ def evaluate_sentiment(inputs):
11
+ text = inputs["text"]
12
+ # Your evaluation logic here
13
+ positive_words = ["good", "great", "excellent"]
14
+ score = sum(1 for w in text.lower().split() if w in positive_words) / 10
15
+ return {"score": min(1.0, score), "passed": score > 0.5}
16
+
17
+ Example using builder:
18
+ from fi.evals.framework.evals.builder import EvalBuilder
19
+
20
+ sentiment_eval = (
21
+ EvalBuilder("sentiment_score")
22
+ .version("1.0.0")
23
+ .required_fields(["text"])
24
+ .threshold(0.5)
25
+ .evaluator(lambda inputs: {"score": 0.8, "passed": True})
26
+ .build()
27
+ )
28
+ """
29
+
30
+ from dataclasses import dataclass, field
31
+ from typing import Any, Callable, Dict, List, Optional, Union
32
+
33
+ from ..protocols import BaseEvaluation, register_evaluation
34
+
35
+
36
+ @dataclass
37
+ class CustomEvalResult:
38
+ """Result from a custom evaluation.
39
+
40
+ This flexible result type supports any evaluation output format.
41
+
42
+ Attributes:
43
+ score: Evaluation score between 0 and 1
44
+ passed: Whether the evaluation passed
45
+ confidence: Confidence in the result (0-1)
46
+ details: Additional evaluation details
47
+ """
48
+
49
+ score: float
50
+ passed: bool
51
+ confidence: float = 1.0
52
+ details: Dict[str, Any] = field(default_factory=dict)
53
+
54
+ @classmethod
55
+ def from_dict(cls, data: Dict[str, Any]) -> "CustomEvalResult":
56
+ """Create result from dictionary."""
57
+ return cls(
58
+ score=data.get("score", 0.0),
59
+ passed=data.get("passed", False),
60
+ confidence=data.get("confidence", 1.0),
61
+ details={k: v for k, v in data.items()
62
+ if k not in ("score", "passed", "confidence")},
63
+ )
64
+
65
+
66
+ class CustomEvaluation(BaseEvaluation):
67
+ """Dynamically created evaluation from builder or decorator."""
68
+
69
+ def __init__(
70
+ self,
71
+ name: str,
72
+ evaluator_fn: Callable[[Dict[str, Any]], Union[Dict[str, Any], CustomEvalResult]],
73
+ version: str = "1.0.0",
74
+ required_fields: Optional[List[str]] = None,
75
+ threshold: float = 0.7,
76
+ description: str = "",
77
+ span_attributes_fn: Optional[Callable[[Any], Dict[str, Any]]] = None,
78
+ ):
79
+ """Initialize custom evaluation.
80
+
81
+ Args:
82
+ name: Unique evaluation name
83
+ evaluator_fn: Function that performs the evaluation
84
+ version: Evaluation version
85
+ required_fields: List of required input fields
86
+ threshold: Score threshold for passing
87
+ description: Human-readable description
88
+ span_attributes_fn: Optional function to extract span attributes
89
+ """
90
+ self._name = name
91
+ self._version = version
92
+ self._evaluator_fn = evaluator_fn
93
+ self._required_fields = required_fields or []
94
+ self._threshold = threshold
95
+ self._description = description
96
+ self._span_attributes_fn = span_attributes_fn
97
+
98
+ @property
99
+ def name(self) -> str:
100
+ return self._name
101
+
102
+ @property
103
+ def version(self) -> str:
104
+ return self._version
105
+
106
+ @property
107
+ def threshold(self) -> float:
108
+ return self._threshold
109
+
110
+ @property
111
+ def description(self) -> str:
112
+ return self._description
113
+
114
+ def validate_inputs(self, inputs: Dict[str, Any]) -> List[str]:
115
+ """Validate required inputs."""
116
+ errors = []
117
+ for field_name in self._required_fields:
118
+ if field_name not in inputs:
119
+ errors.append(f"Missing required field: {field_name}")
120
+ return errors
121
+
122
+ def evaluate(self, inputs: Dict[str, Any]) -> CustomEvalResult:
123
+ """Run the evaluation.
124
+
125
+ Args:
126
+ inputs: Dictionary of evaluation inputs
127
+
128
+ Returns:
129
+ CustomEvalResult with evaluation outcome
130
+ """
131
+ result = self._evaluator_fn(inputs)
132
+
133
+ if isinstance(result, CustomEvalResult):
134
+ return result
135
+ elif isinstance(result, dict):
136
+ return CustomEvalResult.from_dict(result)
137
+ else:
138
+ raise ValueError(
139
+ f"Evaluator function must return dict or CustomEvalResult, "
140
+ f"got {type(result).__name__}"
141
+ )
142
+
143
+ def get_span_attributes(self, result: CustomEvalResult) -> Dict[str, Any]:
144
+ """Get span attributes for tracing.
145
+
146
+ Args:
147
+ result: Evaluation result
148
+
149
+ Returns:
150
+ Dictionary of span attributes
151
+ """
152
+ if self._span_attributes_fn:
153
+ return self._span_attributes_fn(result)
154
+
155
+ return {
156
+ "score": result.score,
157
+ "passed": result.passed,
158
+ "confidence": result.confidence,
159
+ "threshold": self._threshold,
160
+ }
161
+
162
+
163
+ class EvalBuilder:
164
+ """Builder for creating custom evaluations.
165
+
166
+ Provides a fluent interface for configuring evaluations.
167
+
168
+ Example:
169
+ eval = (
170
+ EvalBuilder("my_eval")
171
+ .version("2.0.0")
172
+ .required_fields(["text", "reference"])
173
+ .threshold(0.8)
174
+ .description("Checks text quality")
175
+ .evaluator(my_evaluation_function)
176
+ .build()
177
+ )
178
+ """
179
+
180
+ def __init__(self, name: str):
181
+ """Initialize builder with evaluation name.
182
+
183
+ Args:
184
+ name: Unique evaluation name
185
+ """
186
+ self._name = name
187
+ self._version = "1.0.0"
188
+ self._required_fields: List[str] = []
189
+ self._threshold = 0.7
190
+ self._description = ""
191
+ self._evaluator_fn: Optional[Callable] = None
192
+ self._span_attributes_fn: Optional[Callable] = None
193
+ self._register = False
194
+
195
+ def version(self, version: str) -> "EvalBuilder":
196
+ """Set evaluation version.
197
+
198
+ Args:
199
+ version: Semantic version string
200
+
201
+ Returns:
202
+ Self for chaining
203
+ """
204
+ self._version = version
205
+ return self
206
+
207
+ def required_fields(self, fields: List[str]) -> "EvalBuilder":
208
+ """Set required input fields.
209
+
210
+ Args:
211
+ fields: List of field names
212
+
213
+ Returns:
214
+ Self for chaining
215
+ """
216
+ self._required_fields = fields
217
+ return self
218
+
219
+ def require(self, *fields: str) -> "EvalBuilder":
220
+ """Add required input fields.
221
+
222
+ Args:
223
+ *fields: Field names to require
224
+
225
+ Returns:
226
+ Self for chaining
227
+ """
228
+ self._required_fields.extend(fields)
229
+ return self
230
+
231
+ def threshold(self, threshold: float) -> "EvalBuilder":
232
+ """Set score threshold for passing.
233
+
234
+ Args:
235
+ threshold: Threshold value (0-1)
236
+
237
+ Returns:
238
+ Self for chaining
239
+ """
240
+ self._threshold = threshold
241
+ return self
242
+
243
+ def description(self, description: str) -> "EvalBuilder":
244
+ """Set human-readable description.
245
+
246
+ Args:
247
+ description: Description text
248
+
249
+ Returns:
250
+ Self for chaining
251
+ """
252
+ self._description = description
253
+ return self
254
+
255
+ def evaluator(
256
+ self,
257
+ fn: Callable[[Dict[str, Any]], Union[Dict[str, Any], CustomEvalResult]]
258
+ ) -> "EvalBuilder":
259
+ """Set the evaluator function.
260
+
261
+ The function should take an inputs dict and return either:
262
+ - A dict with at least "score" and "passed" keys
263
+ - A CustomEvalResult object
264
+
265
+ Args:
266
+ fn: Evaluator function
267
+
268
+ Returns:
269
+ Self for chaining
270
+ """
271
+ self._evaluator_fn = fn
272
+ return self
273
+
274
+ def span_attributes(
275
+ self,
276
+ fn: Callable[[CustomEvalResult], Dict[str, Any]]
277
+ ) -> "EvalBuilder":
278
+ """Set custom span attributes extractor.
279
+
280
+ Args:
281
+ fn: Function to extract span attributes from result
282
+
283
+ Returns:
284
+ Self for chaining
285
+ """
286
+ self._span_attributes_fn = fn
287
+ return self
288
+
289
+ def auto_register(self, register: bool = True) -> "EvalBuilder":
290
+ """Enable automatic registration with EvalRegistry.
291
+
292
+ Args:
293
+ register: Whether to auto-register
294
+
295
+ Returns:
296
+ Self for chaining
297
+ """
298
+ self._register = register
299
+ return self
300
+
301
+ def build(self) -> CustomEvaluation:
302
+ """Build the custom evaluation.
303
+
304
+ Returns:
305
+ Configured CustomEvaluation instance
306
+
307
+ Raises:
308
+ ValueError: If evaluator function not set
309
+ """
310
+ if self._evaluator_fn is None:
311
+ raise ValueError("Evaluator function must be set via .evaluator()")
312
+
313
+ evaluation = CustomEvaluation(
314
+ name=self._name,
315
+ evaluator_fn=self._evaluator_fn,
316
+ version=self._version,
317
+ required_fields=self._required_fields,
318
+ threshold=self._threshold,
319
+ description=self._description,
320
+ span_attributes_fn=self._span_attributes_fn,
321
+ )
322
+
323
+ if self._register:
324
+ register_evaluation(evaluation.__class__)
325
+
326
+ return evaluation
327
+
328
+
329
+ def custom_eval(
330
+ name: str,
331
+ version: str = "1.0.0",
332
+ required_fields: Optional[List[str]] = None,
333
+ threshold: float = 0.7,
334
+ description: str = "",
335
+ auto_register: bool = False,
336
+ ) -> Callable:
337
+ """Decorator for creating custom evaluations from functions.
338
+
339
+ The decorated function should take an inputs dict and return either:
340
+ - A dict with at least "score" and "passed" keys
341
+ - A CustomEvalResult object
342
+
343
+ Args:
344
+ name: Unique evaluation name
345
+ version: Evaluation version
346
+ required_fields: List of required input fields
347
+ threshold: Score threshold for passing
348
+ description: Human-readable description
349
+ auto_register: Whether to register with EvalRegistry
350
+
351
+ Returns:
352
+ Decorator function
353
+
354
+ Example:
355
+ @custom_eval("sentiment", required_fields=["text"])
356
+ def evaluate_sentiment(inputs):
357
+ text = inputs["text"]
358
+ score = analyze_sentiment(text)
359
+ return {"score": score, "passed": score > 0.5}
360
+
361
+ # Use it
362
+ result = evaluate_sentiment.evaluate({"text": "Great product!"})
363
+ """
364
+ def decorator(fn: Callable) -> CustomEvaluation:
365
+ evaluation = CustomEvaluation(
366
+ name=name,
367
+ evaluator_fn=fn,
368
+ version=version,
369
+ required_fields=required_fields or [],
370
+ threshold=threshold,
371
+ description=description or fn.__doc__ or "",
372
+ )
373
+
374
+ if auto_register:
375
+ # Create a class to register
376
+ eval_class = type(
377
+ f"Custom_{name}",
378
+ (CustomEvaluation,),
379
+ {"name": name, "version": version}
380
+ )
381
+ register_evaluation(eval_class)
382
+
383
+ return evaluation
384
+
385
+ return decorator
386
+
387
+
388
+ def simple_eval(
389
+ name: str,
390
+ scorer: Callable[[Dict[str, Any]], float],
391
+ threshold: float = 0.7,
392
+ required_fields: Optional[List[str]] = None,
393
+ ) -> CustomEvaluation:
394
+ """Create a simple evaluation from a scoring function.
395
+
396
+ This is the simplest way to create a custom evaluation when you
397
+ only need to compute a score.
398
+
399
+ Args:
400
+ name: Unique evaluation name
401
+ scorer: Function that takes inputs and returns a score (0-1)
402
+ threshold: Score threshold for passing
403
+ required_fields: List of required input fields
404
+
405
+ Returns:
406
+ CustomEvaluation instance
407
+
408
+ Example:
409
+ word_count_eval = simple_eval(
410
+ "word_count",
411
+ scorer=lambda inputs: min(1.0, len(inputs["text"].split()) / 100),
412
+ threshold=0.5,
413
+ required_fields=["text"],
414
+ )
415
+ """
416
+ def evaluator(inputs: Dict[str, Any]) -> Dict[str, Any]:
417
+ score = scorer(inputs)
418
+ return {
419
+ "score": score,
420
+ "passed": score >= threshold,
421
+ }
422
+
423
+ return CustomEvaluation(
424
+ name=name,
425
+ evaluator_fn=evaluator,
426
+ required_fields=required_fields or [],
427
+ threshold=threshold,
428
+ )
429
+
430
+
431
+ def comparison_eval(
432
+ name: str,
433
+ comparator: Callable[[Any, Any], float],
434
+ source_field: str = "response",
435
+ target_field: str = "reference",
436
+ threshold: float = 0.7,
437
+ ) -> CustomEvaluation:
438
+ """Create an evaluation that compares two fields.
439
+
440
+ This is useful for evaluations that compare a response to a reference.
441
+
442
+ Args:
443
+ name: Unique evaluation name
444
+ comparator: Function that compares source and target, returns score (0-1)
445
+ source_field: Name of the source field
446
+ target_field: Name of the target field
447
+ threshold: Score threshold for passing
448
+
449
+ Returns:
450
+ CustomEvaluation instance
451
+
452
+ Example:
453
+ length_match_eval = comparison_eval(
454
+ "length_match",
455
+ comparator=lambda src, tgt: 1 - abs(len(src) - len(tgt)) / max(len(src), len(tgt), 1),
456
+ threshold=0.8,
457
+ )
458
+ """
459
+ def evaluator(inputs: Dict[str, Any]) -> Dict[str, Any]:
460
+ source = inputs.get(source_field, "")
461
+ target = inputs.get(target_field, "")
462
+ score = comparator(source, target)
463
+ return {
464
+ "score": score,
465
+ "passed": score >= threshold,
466
+ "source_field": source_field,
467
+ "target_field": target_field,
468
+ }
469
+
470
+ return CustomEvaluation(
471
+ name=name,
472
+ evaluator_fn=evaluator,
473
+ required_fields=[source_field, target_field],
474
+ threshold=threshold,
475
+ )
476
+
477
+
478
+ def threshold_eval(
479
+ name: str,
480
+ metric_fn: Callable[[Dict[str, Any]], float],
481
+ min_threshold: Optional[float] = None,
482
+ max_threshold: Optional[float] = None,
483
+ required_fields: Optional[List[str]] = None,
484
+ ) -> CustomEvaluation:
485
+ """Create an evaluation based on min/max thresholds.
486
+
487
+ Pass if the metric is within the specified range.
488
+
489
+ Args:
490
+ name: Unique evaluation name
491
+ metric_fn: Function that computes the metric value
492
+ min_threshold: Minimum acceptable value (inclusive)
493
+ max_threshold: Maximum acceptable value (inclusive)
494
+ required_fields: List of required input fields
495
+
496
+ Returns:
497
+ CustomEvaluation instance
498
+
499
+ Example:
500
+ length_eval = threshold_eval(
501
+ "response_length",
502
+ metric_fn=lambda inputs: len(inputs["response"]),
503
+ min_threshold=10,
504
+ max_threshold=500,
505
+ required_fields=["response"],
506
+ )
507
+ """
508
+ def evaluator(inputs: Dict[str, Any]) -> Dict[str, Any]:
509
+ value = metric_fn(inputs)
510
+
511
+ passed = True
512
+ if min_threshold is not None and value < min_threshold:
513
+ passed = False
514
+ if max_threshold is not None and value > max_threshold:
515
+ passed = False
516
+
517
+ # Normalize score to 0-1
518
+ if min_threshold is not None and max_threshold is not None:
519
+ range_val = max_threshold - min_threshold
520
+ if range_val > 0:
521
+ score = max(0, min(1, (value - min_threshold) / range_val))
522
+ else:
523
+ score = 1.0 if value == min_threshold else 0.0
524
+ elif min_threshold is not None:
525
+ score = 1.0 if value >= min_threshold else value / min_threshold
526
+ elif max_threshold is not None:
527
+ score = 1.0 if value <= max_threshold else max_threshold / value
528
+ else:
529
+ score = 1.0
530
+
531
+ return {
532
+ "score": score,
533
+ "passed": passed,
534
+ "value": value,
535
+ "min_threshold": min_threshold,
536
+ "max_threshold": max_threshold,
537
+ }
538
+
539
+ return CustomEvaluation(
540
+ name=name,
541
+ evaluator_fn=evaluator,
542
+ required_fields=required_fields or [],
543
+ threshold=0.5, # Not used, but required
544
+ )
545
+
546
+
547
+ def pattern_match_eval(
548
+ name: str,
549
+ patterns: List[str],
550
+ field: str = "response",
551
+ mode: str = "any", # "any", "all", "none"
552
+ case_sensitive: bool = False,
553
+ ) -> CustomEvaluation:
554
+ """Create an evaluation based on pattern matching.
555
+
556
+ Args:
557
+ name: Unique evaluation name
558
+ patterns: List of regex patterns
559
+ field: Field to check patterns against
560
+ mode: "any" (pass if any match), "all" (pass if all match),
561
+ "none" (pass if none match)
562
+ case_sensitive: Whether patterns are case-sensitive
563
+
564
+ Returns:
565
+ CustomEvaluation instance
566
+
567
+ Example:
568
+ has_greeting = pattern_match_eval(
569
+ "has_greeting",
570
+ patterns=[r"\\b(hello|hi|hey)\\b"],
571
+ field="response",
572
+ mode="any",
573
+ )
574
+ """
575
+ import re
576
+ flags = 0 if case_sensitive else re.IGNORECASE
577
+
578
+ compiled_patterns = [re.compile(p, flags) for p in patterns]
579
+
580
+ def evaluator(inputs: Dict[str, Any]) -> Dict[str, Any]:
581
+ text = str(inputs.get(field, ""))
582
+ matches = [bool(p.search(text)) for p in compiled_patterns]
583
+
584
+ if mode == "any":
585
+ passed = any(matches)
586
+ score = sum(matches) / len(matches) if matches else 0
587
+ elif mode == "all":
588
+ passed = all(matches)
589
+ score = sum(matches) / len(matches) if matches else 0
590
+ elif mode == "none":
591
+ passed = not any(matches)
592
+ score = 1 - (sum(matches) / len(matches)) if matches else 1
593
+ else:
594
+ raise ValueError(f"Unknown mode: {mode}")
595
+
596
+ return {
597
+ "score": score,
598
+ "passed": passed,
599
+ "mode": mode,
600
+ "matches": matches,
601
+ "pattern_count": len(patterns),
602
+ }
603
+
604
+ return CustomEvaluation(
605
+ name=name,
606
+ evaluator_fn=evaluator,
607
+ required_fields=[field],
608
+ threshold=0.5,
609
+ )