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
fi/evals/manager.py ADDED
@@ -0,0 +1,1018 @@
1
+ """
2
+ Management client for the revamped eval template API.
3
+
4
+ Covers the `/model-hub/eval-templates/*` surface introduced in the backend
5
+ Evals Revamp (Phases 1–7): list, create, detail, update, delete, versions,
6
+ and composite evals. For execution of single evals use
7
+ ``fi.evals.Evaluator.evaluate``; this client is for managing the templates
8
+ themselves.
9
+ """
10
+
11
+ import logging
12
+ import threading
13
+ import uuid
14
+ from typing import Any, Dict, List, Literal, Optional
15
+
16
+ from requests import Response
17
+
18
+ from fi.api.auth import APIKeyAuth, ResponseHandler
19
+ from fi.api.types import HttpMethod, RequestConfig
20
+ from fi.evals.execution import Execution
21
+ from fi.evals.manager_types import (
22
+ BulkDeleteResponse,
23
+ CompositeCreateResponse,
24
+ CompositeDetailResponse,
25
+ CompositeExecutionResponse,
26
+ GroundTruthConfigResponse,
27
+ GroundTruthDataResponse,
28
+ GroundTruthDeleteResponse,
29
+ GroundTruthListResponse,
30
+ GroundTruthRoleMappingResponse,
31
+ GroundTruthSearchResponse,
32
+ GroundTruthStatusResponse,
33
+ GroundTruthUploadResponse,
34
+ GroundTruthVariableMappingResponse,
35
+ PlaygroundRunResponse,
36
+ TemplateChartsResponse,
37
+ TemplateCreateResponse,
38
+ TemplateDetailResponse,
39
+ TemplateDuplicateResponse,
40
+ TemplateFeedbackListResponse,
41
+ TemplateListResponse,
42
+ TemplateUpdateResponse,
43
+ TemplateUsageResponse,
44
+ VersionCreateResponse,
45
+ VersionListResponse,
46
+ VersionRestoreResponse,
47
+ VersionSetDefaultResponse,
48
+ )
49
+ from fi.utils.errors import InvalidAuthError
50
+ from fi.utils.routes import Routes
51
+
52
+
53
+ logger = logging.getLogger(__name__)
54
+
55
+
56
+ class _JsonResponseHandler(ResponseHandler[Dict[str, Any], None]):
57
+ """Return the ``result`` payload from a standard backend response."""
58
+
59
+ @classmethod
60
+ def _parse_success(cls, response: Response) -> Dict[str, Any]:
61
+ try:
62
+ data = response.json()
63
+ except ValueError:
64
+ raise Exception(f"Non-JSON response: {response.text}")
65
+ if isinstance(data, dict) and "result" in data:
66
+ return data["result"]
67
+ return data
68
+
69
+ @classmethod
70
+ def _handle_error(cls, response: Response) -> None:
71
+ if response.status_code == 403:
72
+ raise InvalidAuthError()
73
+ if response.status_code == 404:
74
+ raise Exception(f"Not found: {response.text}")
75
+ raise Exception(
76
+ f"Eval template API error {response.status_code}: {response.text}"
77
+ )
78
+
79
+
80
+ class EvalTemplateManager(APIKeyAuth):
81
+ """
82
+ Client for managing eval templates on Future AGI.
83
+
84
+ Example::
85
+
86
+ from fi.evals import EvalTemplateManager
87
+
88
+ mgr = EvalTemplateManager()
89
+ t = mgr.create_template(
90
+ name="is-polite",
91
+ eval_type="llm",
92
+ instructions="Is the {{output}} polite? Answer yes or no.",
93
+ output_type="pass_fail",
94
+ pass_threshold=0.7,
95
+ )
96
+ print(t.id) # attribute access (autocomplete ✓)
97
+ print(t["id"]) # dict access (backwards compat ✓)
98
+ mgr.delete_template(t.id)
99
+ """
100
+
101
+ def _typed_request(self, config: RequestConfig, response_type: Any = None) -> Any:
102
+ """Make a request and parse the response into a typed model."""
103
+ raw = self.request(config=config, response_handler=_JsonResponseHandler)
104
+ if response_type is None or isinstance(raw, str):
105
+ return raw
106
+ return response_type.model_validate(raw)
107
+
108
+ # ------------------------------------------------------------------
109
+ # Templates
110
+ # ------------------------------------------------------------------
111
+
112
+ def list_templates(
113
+ self,
114
+ *,
115
+ page: int = 0,
116
+ page_size: int = 25,
117
+ search: Optional[str] = None,
118
+ owner_filter: Literal["all", "user", "system"] = "all",
119
+ filters: Optional[Dict[str, Any]] = None,
120
+ sort_by: Literal["name", "updated_at", "created_at"] = "updated_at",
121
+ sort_order: Literal["asc", "desc"] = "desc",
122
+ ) -> TemplateListResponse:
123
+ """List eval templates with pagination, search, and filters.
124
+
125
+ Returns a dict with ``items``, ``total``, ``page``, ``page_size``.
126
+ """
127
+ payload: Dict[str, Any] = {
128
+ "page": page,
129
+ "page_size": page_size,
130
+ "owner_filter": owner_filter,
131
+ "sort_by": sort_by,
132
+ "sort_order": sort_order,
133
+ }
134
+ if search is not None:
135
+ payload["search"] = search
136
+ if filters is not None:
137
+ payload["filters"] = filters
138
+
139
+ raw = self.request(
140
+ config=RequestConfig(
141
+ method=HttpMethod.POST,
142
+ url=f"{self._base_url}/{Routes.eval_template_list.value}",
143
+ json=payload,
144
+ timeout=self._default_timeout,
145
+ ),
146
+ response_handler=_JsonResponseHandler,
147
+ )
148
+ return TemplateListResponse.model_validate(raw)
149
+
150
+ def get_template(self, template_id: str) -> TemplateDetailResponse:
151
+ """Fetch a single eval template by UUID."""
152
+ url = (
153
+ f"{self._base_url}/"
154
+ + Routes.eval_template_detail.value.format(template_id=template_id)
155
+ )
156
+ raw = self.request(
157
+ config=RequestConfig(
158
+ method=HttpMethod.GET, url=url, timeout=self._default_timeout
159
+ ),
160
+ response_handler=_JsonResponseHandler,
161
+ )
162
+ return TemplateDetailResponse.model_validate(raw)
163
+
164
+ def create_template(
165
+ self,
166
+ *,
167
+ name: str,
168
+ instructions: str = "",
169
+ eval_type: Literal["llm", "code", "agent"] = "llm",
170
+ model: str = "turing_large",
171
+ output_type: Literal["pass_fail", "percentage", "deterministic"] = "pass_fail",
172
+ pass_threshold: float = 0.5,
173
+ choice_scores: Optional[Dict[str, float]] = None,
174
+ description: Optional[str] = None,
175
+ tags: Optional[List[str]] = None,
176
+ check_internet: bool = False,
177
+ code: Optional[str] = None,
178
+ code_language: Optional[Literal["python", "javascript"]] = None,
179
+ messages: Optional[List[Dict[str, Any]]] = None,
180
+ few_shot_examples: Optional[List[Dict[str, Any]]] = None,
181
+ mode: Optional[Literal["auto", "agent", "quick"]] = None,
182
+ tools: Optional[Dict[str, Any]] = None,
183
+ knowledge_bases: Optional[List[str]] = None,
184
+ data_injection: Optional[Dict[str, Any]] = None,
185
+ summary: Optional[Dict[str, Any]] = None,
186
+ is_draft: bool = False,
187
+ ) -> TemplateCreateResponse:
188
+ """
189
+ Create a single eval template.
190
+
191
+ The ``name`` must match ``[a-z0-9_-]+`` and cannot start/end with a
192
+ separator (backend constraint). For ``eval_type="code"`` supply
193
+ ``code``; otherwise supply ``instructions`` that reference at least
194
+ one ``{{variable}}`` unless ``data_injection`` is enabled.
195
+ """
196
+ payload: Dict[str, Any] = {
197
+ "name": name,
198
+ "is_draft": is_draft,
199
+ "eval_type": eval_type,
200
+ "instructions": instructions,
201
+ "model": model,
202
+ "output_type": output_type,
203
+ "pass_threshold": pass_threshold,
204
+ "tags": list(tags or []),
205
+ "check_internet": check_internet,
206
+ }
207
+ if choice_scores is not None:
208
+ payload["choice_scores"] = choice_scores
209
+ if description is not None:
210
+ payload["description"] = description
211
+ if code is not None:
212
+ payload["code"] = code
213
+ if code_language is not None:
214
+ payload["code_language"] = code_language
215
+ if messages is not None:
216
+ payload["messages"] = messages
217
+ if few_shot_examples is not None:
218
+ payload["few_shot_examples"] = few_shot_examples
219
+ if mode is not None:
220
+ payload["mode"] = mode
221
+ if tools is not None:
222
+ payload["tools"] = tools
223
+ if knowledge_bases is not None:
224
+ payload["knowledge_bases"] = knowledge_bases
225
+ if data_injection is not None:
226
+ payload["data_injection"] = data_injection
227
+ if summary is not None:
228
+ payload["summary"] = summary
229
+
230
+ raw = self.request(
231
+ config=RequestConfig(
232
+ method=HttpMethod.POST,
233
+ url=f"{self._base_url}/{Routes.eval_template_create_v2.value}",
234
+ json=payload,
235
+ timeout=self._default_timeout,
236
+ ),
237
+ response_handler=_JsonResponseHandler,
238
+ )
239
+ return TemplateCreateResponse.model_validate(raw)
240
+
241
+ def update_template(
242
+ self, template_id: str, **fields: Any
243
+ ) -> TemplateUpdateResponse:
244
+ """
245
+ Update mutable fields on an eval template (PUT).
246
+
247
+ Accepts any subset of: ``name``, ``eval_type``, ``instructions``,
248
+ ``model``, ``output_type``, ``pass_threshold``, ``choice_scores``,
249
+ ``multi_choice``, ``description``, ``tags``, ``check_internet``,
250
+ ``code``, ``code_language``, ``messages``, ``few_shot_examples``,
251
+ ``mode``, ``tools``, ``knowledge_bases``, ``data_injection``,
252
+ ``summary``, ``error_localizer_enabled``, ``publish``.
253
+ """
254
+ url = (
255
+ f"{self._base_url}/"
256
+ + Routes.eval_template_update_v2.value.format(template_id=template_id)
257
+ )
258
+ raw = self.request(
259
+ config=RequestConfig(
260
+ method=HttpMethod.PUT,
261
+ url=url,
262
+ json={k: v for k, v in fields.items() if v is not None},
263
+ timeout=self._default_timeout,
264
+ ),
265
+ response_handler=_JsonResponseHandler,
266
+ )
267
+ return TemplateUpdateResponse.model_validate(raw)
268
+
269
+ def delete_template(self, template_id: str) -> Dict[str, Any]:
270
+ """Soft-delete an eval template."""
271
+ return self.request(
272
+ config=RequestConfig(
273
+ method=HttpMethod.POST,
274
+ url=f"{self._base_url}/{Routes.eval_template_delete.value}",
275
+ json={"eval_template_id": template_id},
276
+ timeout=self._default_timeout,
277
+ ),
278
+ response_handler=_JsonResponseHandler,
279
+ )
280
+
281
+ def bulk_delete_templates(self, template_ids: List[str]) -> BulkDeleteResponse:
282
+ """Soft-delete multiple eval templates in one call."""
283
+ raw = self.request(
284
+ config=RequestConfig(
285
+ method=HttpMethod.POST,
286
+ url=f"{self._base_url}/{Routes.eval_template_bulk_delete.value}",
287
+ json={"template_ids": list(template_ids)},
288
+ timeout=self._default_timeout,
289
+ ),
290
+ response_handler=_JsonResponseHandler,
291
+ )
292
+ return BulkDeleteResponse.model_validate(raw)
293
+
294
+ # ------------------------------------------------------------------
295
+ # Versions
296
+ # ------------------------------------------------------------------
297
+
298
+ def list_versions(self, template_id: str) -> VersionListResponse:
299
+ url = (
300
+ f"{self._base_url}/"
301
+ + Routes.eval_template_version_list.value.format(template_id=template_id)
302
+ )
303
+ raw = self.request(
304
+ config=RequestConfig(
305
+ method=HttpMethod.GET, url=url, timeout=self._default_timeout
306
+ ),
307
+ response_handler=_JsonResponseHandler,
308
+ )
309
+ return VersionListResponse.model_validate(raw)
310
+
311
+ def create_version(
312
+ self,
313
+ template_id: str,
314
+ *,
315
+ criteria: Optional[str] = None,
316
+ model: Optional[str] = None,
317
+ config_snapshot: Optional[Dict[str, Any]] = None,
318
+ ) -> VersionCreateResponse:
319
+ """
320
+ Snapshot the current template state as a new version. Each param is
321
+ optional — any value left ``None`` is copied from the live template.
322
+ """
323
+ url = (
324
+ f"{self._base_url}/"
325
+ + Routes.eval_template_version_create.value.format(template_id=template_id)
326
+ )
327
+ payload: Dict[str, Any] = {}
328
+ if criteria is not None:
329
+ payload["criteria"] = criteria
330
+ if model is not None:
331
+ payload["model"] = model
332
+ if config_snapshot is not None:
333
+ payload["config_snapshot"] = config_snapshot
334
+
335
+ raw = self.request(
336
+ config=RequestConfig(
337
+ method=HttpMethod.POST,
338
+ url=url,
339
+ json=payload,
340
+ timeout=self._default_timeout,
341
+ ),
342
+ response_handler=_JsonResponseHandler,
343
+ )
344
+ return VersionCreateResponse.model_validate(raw)
345
+
346
+ def set_default_version(
347
+ self, template_id: str, version_id: str
348
+ ) -> VersionSetDefaultResponse:
349
+ url = (
350
+ f"{self._base_url}/"
351
+ + Routes.eval_template_version_set_default.value.format(
352
+ template_id=template_id, version_id=version_id
353
+ )
354
+ )
355
+ raw = self.request(
356
+ config=RequestConfig(
357
+ method=HttpMethod.PUT, url=url, timeout=self._default_timeout
358
+ ),
359
+ response_handler=_JsonResponseHandler,
360
+ )
361
+ return VersionSetDefaultResponse.model_validate(raw)
362
+
363
+ def restore_version(
364
+ self, template_id: str, version_id: str
365
+ ) -> VersionRestoreResponse:
366
+ """
367
+ Create a new version on top of the template whose contents match
368
+ the target version. The old version is left untouched.
369
+ """
370
+ url = (
371
+ f"{self._base_url}/"
372
+ + Routes.eval_template_version_restore.value.format(
373
+ template_id=template_id, version_id=version_id
374
+ )
375
+ )
376
+ raw = self.request(
377
+ config=RequestConfig(
378
+ method=HttpMethod.POST, url=url, timeout=self._default_timeout
379
+ ),
380
+ response_handler=_JsonResponseHandler,
381
+ )
382
+ return VersionRestoreResponse.model_validate(raw)
383
+
384
+ # ------------------------------------------------------------------
385
+ # Composite evals
386
+ # ------------------------------------------------------------------
387
+
388
+ def create_composite(
389
+ self,
390
+ *,
391
+ name: str,
392
+ child_template_ids: List[str],
393
+ description: Optional[str] = None,
394
+ tags: Optional[List[str]] = None,
395
+ aggregation_enabled: bool = True,
396
+ aggregation_function: Literal[
397
+ "weighted_avg", "avg", "min", "max", "pass_rate"
398
+ ] = "weighted_avg",
399
+ child_weights: Optional[Dict[str, float]] = None,
400
+ composite_child_axis: Literal[
401
+ "", "pass_fail", "percentage", "choices", "code"
402
+ ] = "",
403
+ ) -> CompositeCreateResponse:
404
+ """
405
+ Create a composite eval from existing eval templates. ``child_weights``
406
+ maps template_id → weight (default 1.0). Setting
407
+ ``composite_child_axis`` enforces a homogeneity check — every child
408
+ must match that output axis (e.g. all ``pass_fail``).
409
+ """
410
+ payload: Dict[str, Any] = {
411
+ "name": name,
412
+ "child_template_ids": list(child_template_ids),
413
+ "aggregation_enabled": aggregation_enabled,
414
+ "aggregation_function": aggregation_function,
415
+ "composite_child_axis": composite_child_axis,
416
+ "tags": list(tags or []),
417
+ }
418
+ if description is not None:
419
+ payload["description"] = description
420
+ if child_weights is not None:
421
+ payload["child_weights"] = child_weights
422
+
423
+ raw = self.request(
424
+ config=RequestConfig(
425
+ method=HttpMethod.POST,
426
+ url=f"{self._base_url}/{Routes.composite_eval_create.value}",
427
+ json=payload,
428
+ timeout=self._default_timeout,
429
+ ),
430
+ response_handler=_JsonResponseHandler,
431
+ )
432
+ return CompositeCreateResponse.model_validate(raw)
433
+
434
+ def get_composite(self, template_id: str) -> CompositeDetailResponse:
435
+ url = (
436
+ f"{self._base_url}/"
437
+ + Routes.composite_eval_detail.value.format(template_id=template_id)
438
+ )
439
+ raw = self.request(
440
+ config=RequestConfig(
441
+ method=HttpMethod.GET, url=url, timeout=self._default_timeout
442
+ ),
443
+ response_handler=_JsonResponseHandler,
444
+ )
445
+ return CompositeDetailResponse.model_validate(raw)
446
+
447
+ def update_composite(
448
+ self,
449
+ template_id: str,
450
+ **fields: Any,
451
+ ) -> CompositeDetailResponse:
452
+ """PATCH the composite. Same field names as ``create_composite``."""
453
+ url = (
454
+ f"{self._base_url}/"
455
+ + Routes.composite_eval_detail.value.format(template_id=template_id)
456
+ )
457
+ raw = self.request(
458
+ config=RequestConfig(
459
+ method=HttpMethod.PATCH,
460
+ url=url,
461
+ json={k: v for k, v in fields.items() if v is not None},
462
+ timeout=self._default_timeout,
463
+ ),
464
+ response_handler=_JsonResponseHandler,
465
+ )
466
+ return CompositeDetailResponse.model_validate(raw)
467
+
468
+ def submit_composite(
469
+ self,
470
+ template_id: str,
471
+ *,
472
+ mapping: Dict[str, Any],
473
+ model: Optional[str] = None,
474
+ config: Optional[Dict[str, Any]] = None,
475
+ error_localizer: bool = False,
476
+ input_data_types: Optional[Dict[str, str]] = None,
477
+ span_context: Optional[Dict[str, Any]] = None,
478
+ trace_context: Optional[Dict[str, Any]] = None,
479
+ session_context: Optional[Dict[str, Any]] = None,
480
+ ) -> Execution:
481
+ """
482
+ Submit a composite eval for non-blocking execution.
483
+
484
+ The backend's composite execute endpoint is still synchronous,
485
+ so this method offloads the HTTP call to a background thread
486
+ inside the calling process and returns an :class:`Execution`
487
+ handle immediately. Use ``handle.wait()`` to block until the
488
+ run finishes, or poll ``handle.is_done()`` yourself.
489
+
490
+ IMPORTANT: The execution ID is client-local — if this process
491
+ dies or you drop the handle, the in-flight work is lost. For
492
+ cross-process resumable runs, use
493
+ :py:meth:`fi.evals.Evaluator.submit` against a single eval.
494
+ """
495
+ handle = Execution(
496
+ id=str(uuid.uuid4()),
497
+ kind="composite",
498
+ status="pending",
499
+ )
500
+ # Keep a reference so the thread isn't GC'd while running.
501
+ handle._refresher = lambda h=handle: h # type: ignore[attr-defined]
502
+
503
+ def _runner() -> None:
504
+ handle.status = "processing"
505
+ try:
506
+ result = self.execute_composite(
507
+ template_id,
508
+ mapping=mapping,
509
+ model=model,
510
+ config=config,
511
+ error_localizer=error_localizer,
512
+ input_data_types=input_data_types,
513
+ span_context=span_context,
514
+ trace_context=trace_context,
515
+ session_context=session_context,
516
+ )
517
+ handle.result = result
518
+ if isinstance(result, dict):
519
+ handle.error_localizer = result.get("error_localizer_results")
520
+ handle.status = "completed"
521
+ except Exception as exc: # noqa: BLE001
522
+ handle.error_message = f"{exc.__class__.__name__}: {exc}"
523
+ handle.status = "failed"
524
+ logger.exception(
525
+ "Composite execution %s failed: %s", handle.id, exc
526
+ )
527
+
528
+ thread = threading.Thread(
529
+ target=_runner, daemon=True, name=f"fi-composite-{handle.id[:8]}"
530
+ )
531
+ thread.start()
532
+ return handle
533
+
534
+ def execute_composite(
535
+ self,
536
+ template_id: str,
537
+ *,
538
+ mapping: Dict[str, Any],
539
+ model: Optional[str] = None,
540
+ config: Optional[Dict[str, Any]] = None,
541
+ error_localizer: bool = False,
542
+ input_data_types: Optional[Dict[str, str]] = None,
543
+ span_context: Optional[Dict[str, Any]] = None,
544
+ trace_context: Optional[Dict[str, Any]] = None,
545
+ session_context: Optional[Dict[str, Any]] = None,
546
+ ) -> CompositeExecutionResponse:
547
+ """
548
+ Run every child eval in the composite with a single input mapping
549
+ and return per-child results plus the aggregated score.
550
+ """
551
+ payload: Dict[str, Any] = {
552
+ "mapping": mapping,
553
+ "config": config or {},
554
+ "error_localizer": error_localizer,
555
+ "input_data_types": input_data_types or {},
556
+ }
557
+ if model is not None:
558
+ payload["model"] = model
559
+ if span_context is not None:
560
+ payload["span_context"] = span_context
561
+ if trace_context is not None:
562
+ payload["trace_context"] = trace_context
563
+ if session_context is not None:
564
+ payload["session_context"] = session_context
565
+
566
+ url = (
567
+ f"{self._base_url}/"
568
+ + Routes.composite_eval_execute.value.format(template_id=template_id)
569
+ )
570
+ raw = self.request(
571
+ config=RequestConfig(
572
+ method=HttpMethod.POST,
573
+ url=url,
574
+ json=payload,
575
+ timeout=self._default_timeout,
576
+ ),
577
+ response_handler=_JsonResponseHandler,
578
+ )
579
+ return CompositeExecutionResponse.model_validate(raw)
580
+
581
+ # ------------------------------------------------------------------
582
+ # Ground Truth (Phase 9)
583
+ # ------------------------------------------------------------------
584
+
585
+ def list_ground_truth(self, template_id: str) -> GroundTruthListResponse:
586
+ """List ground-truth datasets attached to an eval template."""
587
+ url = (
588
+ f"{self._base_url}/"
589
+ + Routes.ground_truth_list.value.format(template_id=template_id)
590
+ )
591
+ raw = self.request(
592
+ config=RequestConfig(
593
+ method=HttpMethod.GET, url=url, timeout=self._default_timeout
594
+ ),
595
+ response_handler=_JsonResponseHandler,
596
+ )
597
+ return GroundTruthListResponse.model_validate(raw)
598
+
599
+ def upload_ground_truth(
600
+ self,
601
+ template_id: str,
602
+ *,
603
+ name: str,
604
+ columns: List[str],
605
+ data: List[Dict[str, Any]],
606
+ description: str = "",
607
+ file_name: str = "",
608
+ variable_mapping: Optional[Dict[str, str]] = None,
609
+ role_mapping: Optional[Dict[str, str]] = None,
610
+ ) -> GroundTruthUploadResponse:
611
+ """
612
+ Upload a ground-truth dataset as a JSON body (``columns`` +
613
+ ``data`` rows). For file-upload mode (CSV/XLSX) use the HTTP API
614
+ directly — the SDK does not wrap multipart uploads yet.
615
+ """
616
+ payload: Dict[str, Any] = {
617
+ "name": name,
618
+ "description": description,
619
+ "file_name": file_name,
620
+ "columns": list(columns),
621
+ "data": list(data),
622
+ }
623
+ if variable_mapping is not None:
624
+ payload["variable_mapping"] = variable_mapping
625
+ if role_mapping is not None:
626
+ payload["role_mapping"] = role_mapping
627
+
628
+ url = (
629
+ f"{self._base_url}/"
630
+ + Routes.ground_truth_upload.value.format(template_id=template_id)
631
+ )
632
+ raw = self.request(
633
+ config=RequestConfig(
634
+ method=HttpMethod.POST,
635
+ url=url,
636
+ json=payload,
637
+ timeout=self._default_timeout,
638
+ ),
639
+ response_handler=_JsonResponseHandler,
640
+ )
641
+ return GroundTruthUploadResponse.model_validate(raw)
642
+
643
+ def get_ground_truth_config(self, template_id: str) -> GroundTruthConfigResponse:
644
+ """Fetch the ground-truth config block from an eval template."""
645
+ url = (
646
+ f"{self._base_url}/"
647
+ + Routes.ground_truth_config.value.format(template_id=template_id)
648
+ )
649
+ raw = self.request(
650
+ config=RequestConfig(
651
+ method=HttpMethod.GET, url=url, timeout=self._default_timeout
652
+ ),
653
+ response_handler=_JsonResponseHandler,
654
+ )
655
+ return GroundTruthConfigResponse.model_validate(raw)
656
+
657
+ def set_ground_truth_config(
658
+ self,
659
+ template_id: str,
660
+ *,
661
+ enabled: bool = True,
662
+ ground_truth_id: Optional[str] = None,
663
+ mode: Literal["auto", "manual", "disabled"] = "auto",
664
+ max_examples: int = 3,
665
+ similarity_threshold: float = 0.7,
666
+ injection_format: Literal[
667
+ "structured", "conversational", "xml"
668
+ ] = "structured",
669
+ ) -> GroundTruthConfigResponse:
670
+ """
671
+ Update the ground-truth configuration on an eval template
672
+ (few-shot retrieval settings used at eval time).
673
+ """
674
+ payload = {
675
+ "enabled": enabled,
676
+ "ground_truth_id": ground_truth_id,
677
+ "mode": mode,
678
+ "max_examples": max_examples,
679
+ "similarity_threshold": similarity_threshold,
680
+ "injection_format": injection_format,
681
+ }
682
+ url = (
683
+ f"{self._base_url}/"
684
+ + Routes.ground_truth_config.value.format(template_id=template_id)
685
+ )
686
+ raw = self.request(
687
+ config=RequestConfig(
688
+ method=HttpMethod.PUT,
689
+ url=url,
690
+ json=payload,
691
+ timeout=self._default_timeout,
692
+ ),
693
+ response_handler=_JsonResponseHandler,
694
+ )
695
+ return GroundTruthConfigResponse.model_validate(raw)
696
+
697
+ def set_ground_truth_variable_mapping(
698
+ self, ground_truth_id: str, variable_mapping: Dict[str, str]
699
+ ) -> GroundTruthVariableMappingResponse:
700
+ """Map eval variables → ground-truth column names."""
701
+ url = (
702
+ f"{self._base_url}/"
703
+ + Routes.ground_truth_mapping.value.format(
704
+ ground_truth_id=ground_truth_id
705
+ )
706
+ )
707
+ raw = self.request(
708
+ config=RequestConfig(
709
+ method=HttpMethod.PUT,
710
+ url=url,
711
+ json={"variable_mapping": variable_mapping},
712
+ timeout=self._default_timeout,
713
+ ),
714
+ response_handler=_JsonResponseHandler,
715
+ )
716
+ return GroundTruthVariableMappingResponse.model_validate(raw)
717
+
718
+ def set_ground_truth_role_mapping(
719
+ self, ground_truth_id: str, role_mapping: Dict[str, str]
720
+ ) -> GroundTruthRoleMappingResponse:
721
+ """
722
+ Map semantic roles (``input``, ``expected_output``, ``score``,
723
+ ``reasoning``) → ground-truth column names.
724
+ """
725
+ url = (
726
+ f"{self._base_url}/"
727
+ + Routes.ground_truth_role_mapping.value.format(
728
+ ground_truth_id=ground_truth_id
729
+ )
730
+ )
731
+ raw = self.request(
732
+ config=RequestConfig(
733
+ method=HttpMethod.PUT,
734
+ url=url,
735
+ json={"role_mapping": role_mapping},
736
+ timeout=self._default_timeout,
737
+ ),
738
+ response_handler=_JsonResponseHandler,
739
+ )
740
+ return GroundTruthRoleMappingResponse.model_validate(raw)
741
+
742
+ def get_ground_truth_data(
743
+ self,
744
+ ground_truth_id: str,
745
+ *,
746
+ page: int = 1,
747
+ page_size: int = 50,
748
+ ) -> GroundTruthDataResponse:
749
+ """Paginated ground-truth rows (1-based page)."""
750
+ url = (
751
+ f"{self._base_url}/"
752
+ + Routes.ground_truth_data.value.format(
753
+ ground_truth_id=ground_truth_id
754
+ )
755
+ )
756
+ raw = self.request(
757
+ config=RequestConfig(
758
+ method=HttpMethod.GET,
759
+ url=url,
760
+ params={"page": page, "page_size": page_size},
761
+ timeout=self._default_timeout,
762
+ ),
763
+ response_handler=_JsonResponseHandler,
764
+ )
765
+ return GroundTruthDataResponse.model_validate(raw)
766
+
767
+ def get_ground_truth_status(self, ground_truth_id: str) -> GroundTruthStatusResponse:
768
+ """Embedding generation status / progress for a ground-truth dataset."""
769
+ url = (
770
+ f"{self._base_url}/"
771
+ + Routes.ground_truth_status.value.format(
772
+ ground_truth_id=ground_truth_id
773
+ )
774
+ )
775
+ raw = self.request(
776
+ config=RequestConfig(
777
+ method=HttpMethod.GET, url=url, timeout=self._default_timeout
778
+ ),
779
+ response_handler=_JsonResponseHandler,
780
+ )
781
+ return GroundTruthStatusResponse.model_validate(raw)
782
+
783
+ def trigger_ground_truth_embeddings(
784
+ self, ground_truth_id: str
785
+ ) -> Dict[str, Any]:
786
+ """Kick off async embedding generation for a ground-truth dataset."""
787
+ url = (
788
+ f"{self._base_url}/"
789
+ + Routes.ground_truth_embed.value.format(
790
+ ground_truth_id=ground_truth_id
791
+ )
792
+ )
793
+ return self.request(
794
+ config=RequestConfig(
795
+ method=HttpMethod.POST, url=url, timeout=self._default_timeout
796
+ ),
797
+ response_handler=_JsonResponseHandler,
798
+ )
799
+
800
+ def search_ground_truth(
801
+ self,
802
+ ground_truth_id: str,
803
+ *,
804
+ query: str,
805
+ max_results: int = 3,
806
+ ) -> GroundTruthSearchResponse:
807
+ """
808
+ Retrieve the most similar ground-truth rows to ``query``. Requires
809
+ embeddings to be ``completed`` — call
810
+ :py:meth:`trigger_ground_truth_embeddings` first and poll
811
+ :py:meth:`get_ground_truth_status`.
812
+ """
813
+ url = (
814
+ f"{self._base_url}/"
815
+ + Routes.ground_truth_search.value.format(
816
+ ground_truth_id=ground_truth_id
817
+ )
818
+ )
819
+ raw = self.request(
820
+ config=RequestConfig(
821
+ method=HttpMethod.POST,
822
+ url=url,
823
+ json={"query": query, "max_results": max_results},
824
+ timeout=self._default_timeout,
825
+ ),
826
+ response_handler=_JsonResponseHandler,
827
+ )
828
+ return GroundTruthSearchResponse.model_validate(raw)
829
+
830
+ def delete_ground_truth(self, ground_truth_id: str) -> GroundTruthDeleteResponse:
831
+ """Soft-delete a ground-truth dataset."""
832
+ url = (
833
+ f"{self._base_url}/"
834
+ + Routes.ground_truth_delete.value.format(
835
+ ground_truth_id=ground_truth_id
836
+ )
837
+ )
838
+ raw = self.request(
839
+ config=RequestConfig(
840
+ method=HttpMethod.DELETE,
841
+ url=url,
842
+ timeout=self._default_timeout,
843
+ ),
844
+ response_handler=_JsonResponseHandler,
845
+ )
846
+ return GroundTruthDeleteResponse.model_validate(raw)
847
+
848
+ # ------------------------------------------------------------------
849
+ # Usage stats, feedback, and 30-day charts (Phase 10)
850
+ # ------------------------------------------------------------------
851
+
852
+ def get_template_usage(
853
+ self,
854
+ template_id: str,
855
+ *,
856
+ page: int = 0,
857
+ page_size: int = 25,
858
+ period: Literal["30m", "6h", "1d", "7d", "30d", "90d"] = "30d",
859
+ version: Optional[str] = None,
860
+ ) -> TemplateUsageResponse:
861
+ """Paginated usage stats + run logs for an eval template."""
862
+ params: Dict[str, Any] = {
863
+ "page": page,
864
+ "page_size": page_size,
865
+ "period": period,
866
+ }
867
+ if version is not None:
868
+ params["version"] = version
869
+ url = (
870
+ f"{self._base_url}/"
871
+ + Routes.eval_template_usage.value.format(template_id=template_id)
872
+ )
873
+ raw = self.request(
874
+ config=RequestConfig(
875
+ method=HttpMethod.GET,
876
+ url=url,
877
+ params=params,
878
+ timeout=self._default_timeout,
879
+ ),
880
+ response_handler=_JsonResponseHandler,
881
+ )
882
+ return TemplateUsageResponse.model_validate(raw)
883
+
884
+ def list_template_feedback(
885
+ self,
886
+ template_id: str,
887
+ *,
888
+ page: int = 0,
889
+ page_size: int = 25,
890
+ ) -> TemplateFeedbackListResponse:
891
+ """Paginated user feedback on an eval template's runs."""
892
+ url = (
893
+ f"{self._base_url}/"
894
+ + Routes.eval_template_feedback_list.value.format(
895
+ template_id=template_id
896
+ )
897
+ )
898
+ raw = self.request(
899
+ config=RequestConfig(
900
+ method=HttpMethod.GET,
901
+ url=url,
902
+ params={"page": page, "page_size": page_size},
903
+ timeout=self._default_timeout,
904
+ ),
905
+ response_handler=_JsonResponseHandler,
906
+ )
907
+ return TemplateFeedbackListResponse.model_validate(raw)
908
+
909
+ def get_template_charts(
910
+ self, template_ids: List[str]
911
+ ) -> TemplateChartsResponse:
912
+ """
913
+ 30-day sparkline data (run counts + error rates) for the given
914
+ templates — the same payload that powers the eval-list table.
915
+ """
916
+ url = f"{self._base_url}/{Routes.eval_template_list_charts.value}"
917
+ raw = self.request(
918
+ config=RequestConfig(
919
+ method=HttpMethod.POST,
920
+ url=url,
921
+ json={"template_ids": list(template_ids)},
922
+ timeout=self._default_timeout,
923
+ ),
924
+ response_handler=_JsonResponseHandler,
925
+ )
926
+ return TemplateChartsResponse.model_validate(raw)
927
+
928
+ # ------------------------------------------------------------------
929
+ # Duplicate + playground
930
+ # ------------------------------------------------------------------
931
+
932
+ def duplicate_template(
933
+ self, template_id: str, name: str
934
+ ) -> TemplateDuplicateResponse:
935
+ """
936
+ Clone an existing user-owned eval template under a new ``name``.
937
+ Returns ``{message, eval_template_id}``.
938
+ """
939
+ raw = self.request(
940
+ config=RequestConfig(
941
+ method=HttpMethod.POST,
942
+ url=f"{self._base_url}/{Routes.eval_template_duplicate.value}",
943
+ json={"eval_template_id": template_id, "name": name},
944
+ timeout=self._default_timeout,
945
+ ),
946
+ response_handler=_JsonResponseHandler,
947
+ )
948
+ return TemplateDuplicateResponse.model_validate(raw)
949
+
950
+ def run_playground(
951
+ self,
952
+ template_id: str,
953
+ *,
954
+ mapping: Dict[str, Any],
955
+ model: Optional[str] = None,
956
+ config: Optional[Dict[str, Any]] = None,
957
+ input_data_types: Optional[Dict[str, str]] = None,
958
+ error_localizer: bool = False,
959
+ kb_id: Optional[str] = None,
960
+ # Auto-context: pass either a resolved dict, or let the server
961
+ # resolve via the matching id.
962
+ row_context: Optional[Dict[str, Any]] = None,
963
+ span_context: Optional[Dict[str, Any]] = None,
964
+ trace_context: Optional[Dict[str, Any]] = None,
965
+ session_context: Optional[Dict[str, Any]] = None,
966
+ call_context: Optional[Dict[str, Any]] = None,
967
+ span_id: Optional[str] = None,
968
+ trace_id: Optional[str] = None,
969
+ session_id: Optional[str] = None,
970
+ call_id: Optional[str] = None,
971
+ ) -> PlaygroundRunResponse:
972
+ """
973
+ Run a saved eval template via the playground endpoint.
974
+
975
+ Unlike :py:meth:`fi.evals.Evaluator.evaluate` — which identifies
976
+ the eval by name — this identifies by ``template_id`` and
977
+ exercises the full runtime config path, including auto-context
978
+ injection (row / span / trace / session / call). Useful for
979
+ test-drive workflows that want to run a specific template
980
+ version against a specific trace or dataset row.
981
+ """
982
+ payload: Dict[str, Any] = {
983
+ "template_id": template_id,
984
+ "mapping": mapping,
985
+ "error_localizer": error_localizer,
986
+ }
987
+ if model is not None:
988
+ payload["model"] = model
989
+ if config is not None:
990
+ payload["config"] = config
991
+ if input_data_types is not None:
992
+ payload["input_data_types"] = input_data_types
993
+ if kb_id is not None:
994
+ payload["kb_id"] = kb_id
995
+ for key, value in (
996
+ ("row_context", row_context),
997
+ ("span_context", span_context),
998
+ ("trace_context", trace_context),
999
+ ("session_context", session_context),
1000
+ ("call_context", call_context),
1001
+ ("span_id", span_id),
1002
+ ("trace_id", trace_id),
1003
+ ("session_id", session_id),
1004
+ ("call_id", call_id),
1005
+ ):
1006
+ if value is not None:
1007
+ payload[key] = value
1008
+
1009
+ raw = self.request(
1010
+ config=RequestConfig(
1011
+ method=HttpMethod.POST,
1012
+ url=f"{self._base_url}/{Routes.eval_playground.value}",
1013
+ json=payload,
1014
+ timeout=self._default_timeout,
1015
+ ),
1016
+ response_handler=_JsonResponseHandler,
1017
+ )
1018
+ return PlaygroundRunResponse.model_validate(raw)