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,147 @@
1
+ """Stage zero: working out which agent you mean.
2
+
3
+ Everything the harness does is about one agent, so something has to establish which one. That
4
+ used to be two flags on a command line, which is the wrong place for it: the whole point is that
5
+ you say what you want and it happens, and "here is my agent, set up a test environment for it"
6
+ is a sentence, not an invocation.
7
+
8
+ So this is a stage like any other. It can look around the filesystem to find what you are
9
+ pointing at, it asks if what you said is ambiguous, and it finishes by naming the agent and where
10
+ it lives. Everything after it, including where artifacts are written, follows from that.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from collections.abc import Callable
16
+ from pathlib import Path
17
+ from typing import Any
18
+
19
+ from .backends import SessionSpec, tool, tool_server
20
+
21
+ from .config import (
22
+ artifact_dir,
23
+ chosen_model,
24
+ )
25
+ from .session import Stage
26
+ from .sources import AgentSource, clone_github_repository, resolve, supported
27
+ from .tools import schema
28
+
29
+ RECEPTION_SERVER = "agent"
30
+ TOOL_NAMES = ("point_at_agent",)
31
+
32
+ _INSTRUCTIONS = """
33
+ You are the front desk of a harness that builds test environments for agents.
34
+
35
+ Somebody has arrived with an agent they want tested. Your only job is to work out which agent,
36
+ and where it lives, and then call point_at_agent. Nothing else happens until you do.
37
+
38
+ Usually they will just tell you: a path, a repository, a folder, or a public GitHub URL. Take it.
39
+ For an explicit local path, call point_at_agent immediately: that tool validates the path. Do not
40
+ scan it first. Use Read, Glob or Grep only when the person gave an ambiguous parent directory or
41
+ no path and you genuinely have to locate the agent. For a GitHub URL, call point_at_agent with kind "github" and the URL as
42
+ path. The harness clones it into this session; do not ask them to clone it themselves. A name is a
43
+ label for their artifacts, so lower case and no spaces.
44
+
45
+ The agent is usually somewhere else on disk, not inside the harness. A path they give you is
46
+ relative to where you are looking from, which is a workspace holding many repositories, so try
47
+ it as given before deciding it does not exist.
48
+
49
+ If the path really is not there, say so and say what you did find near it. If they gestured
50
+ vaguely at a directory holding several agents, look, and ask which one with AskUserQuestion.
51
+
52
+ Do not read the agent properly and do not start working anything out about it. That is the next
53
+ stage's job and it has its own instructions. Point at the agent, say in one line what you are
54
+ about to do, and stop.
55
+ """
56
+
57
+
58
+ def point_at(
59
+ name: str,
60
+ path: str,
61
+ kind: str,
62
+ found: dict[str, AgentSource],
63
+ source_dir: Path | None = None,
64
+ ) -> dict[str, Any]:
65
+ """Establish which agent this is, or say why it cannot be.
66
+
67
+ A plain function rather than only a tool body, so what counts as a reachable agent can be
68
+ exercised without standing up a session.
69
+ """
70
+ name, path, kind = name.strip(), path.strip(), (kind.strip() or "repo")
71
+ if not name:
72
+ return _err("no name: the artifacts have to be filed under something")
73
+ if kind not in supported():
74
+ return _err(f"no such kind {kind!r}; there is {', '.join(supported())}")
75
+ if kind == "repo" and not Path(path).expanduser().exists():
76
+ return _err(
77
+ f"there is nothing at {path!r}. Look again with Glob, and if you cannot find it, "
78
+ "ask where the agent actually lives."
79
+ )
80
+ try:
81
+ if kind == "github":
82
+ root = clone_github_repository(
83
+ path, source_dir or artifact_dir(name) / "source"
84
+ )
85
+ found["source"] = resolve(kind, name=name, root=root, url=path)
86
+ else:
87
+ found["source"] = resolve(kind, name=name, root=Path(path).expanduser())
88
+ except Exception as failed:
89
+ return _err(f"could not reach that agent: {failed}")
90
+ return {
91
+ "content": [{"type": "text", "text": f"Pointed at {name} ({kind}) at {path}."}]
92
+ }
93
+
94
+
95
+ def open_stage(
96
+ *,
97
+ cwd: str | Path | None = None,
98
+ source_dir: str | Path | None = None,
99
+ ask: Callable[..., Any] | None = None,
100
+ max_turns: int = 20,
101
+ ) -> tuple[Stage, dict[str, AgentSource]]:
102
+ """A stage that establishes which agent this conversation is about."""
103
+ found: dict[str, AgentSource] = {}
104
+
105
+ @tool(
106
+ "point_at_agent",
107
+ "Name the agent this conversation is about and say where it is. `kind` is how it is "
108
+ f"supplied, one of: {', '.join(supported())}. For a repository, `path` is its directory; "
109
+ "for github, it is the public HTTPS repository URL and the harness clones it. Call this "
110
+ "once you know what you are pointing at.",
111
+ schema({"name": str, "path": str, "kind": str}, ["name", "path"]),
112
+ )
113
+ async def point_at_agent(args: dict[str, Any]) -> dict[str, Any]:
114
+ return point_at(
115
+ str(args.get("name") or ""),
116
+ str(args.get("path") or ""),
117
+ str(args.get("kind") or "repo"),
118
+ found,
119
+ Path(source_dir) if source_dir else None,
120
+ )
121
+
122
+ server = tool_server(
123
+ name=RECEPTION_SERVER, version="0.1.0", tools=[point_at_agent]
124
+ )
125
+ spec = SessionSpec(
126
+ system_prompt=_INSTRUCTIONS.strip(),
127
+ servers={RECEPTION_SERVER: server},
128
+ builtins=("Read", "Glob", "Grep", "AskUserQuestion"),
129
+ cwd=str(cwd or Path.cwd()),
130
+ max_turns=max_turns,
131
+ model=chosen_model(),
132
+ ask=ask,
133
+ thinking=True,
134
+ )
135
+ return Stage(spec, name="reception"), found
136
+
137
+
138
+ def opening() -> str:
139
+ return (
140
+ "Somebody has just opened the harness and has not said anything yet. Greet them in one "
141
+ "short line and ask which agent they want tested and where it lives. Do not list your "
142
+ "capabilities."
143
+ )
144
+
145
+
146
+ def _err(text: str) -> dict[str, Any]:
147
+ return {"content": [{"type": "text", "text": text}], "is_error": True}
@@ -0,0 +1,373 @@
1
+ """Native Retell API-chat execution for hosted RL Environment runs."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import json
7
+ import urllib.error
8
+ import urllib.request
9
+ from datetime import datetime, timezone
10
+ from typing import Any, Mapping
11
+
12
+ from fi.simulate.agent.wrapper import (
13
+ AgentInput,
14
+ AgentResponse,
15
+ AgentWrapper,
16
+ SimulationEvent,
17
+ )
18
+
19
+ from .call_runner import ArtifactUploader, CallRunnerContext, RETELL_API_KEY_ALIAS
20
+ from .chat_call_runner import (
21
+ _HostedChatTarget,
22
+ _conversation_scenario,
23
+ _drive_conversation,
24
+ _duration_ms,
25
+ _scenario_document,
26
+ _tool_world,
27
+ )
28
+ from .contract import AgentContract
29
+ from .hosted_scheduler import CallAborted, CallOutcome, Scenario, World
30
+ from .outbound import ArtifactKind, format_rfc3339_millis
31
+ from .process_runtime import EnvironmentRuntime
32
+
33
+
34
+ class RetellChatError(RuntimeError):
35
+ """A bounded, credential-safe Retell Chat API error."""
36
+
37
+
38
+ class RetellChatEnded(Exception):
39
+ """Retell reports that the remote chat reached a normal terminal state."""
40
+
41
+
42
+ def _arguments(value: Any) -> dict[str, Any]:
43
+ if isinstance(value, Mapping):
44
+ return dict(value)
45
+ if isinstance(value, str):
46
+ try:
47
+ parsed = json.loads(value)
48
+ except ValueError:
49
+ return {"_raw": value}
50
+ return dict(parsed) if isinstance(parsed, Mapping) else {"_raw": value}
51
+ return {}
52
+
53
+
54
+ def _completion_messages(
55
+ messages: Any,
56
+ ) -> tuple[str, list[dict[str, Any]], list[dict[str, Any]]]:
57
+ if not isinstance(messages, list):
58
+ raise RetellChatError("retell_chat_completion_messages_invalid")
59
+ answer = ""
60
+ calls: list[dict[str, Any]] = []
61
+ responses: list[dict[str, Any]] = []
62
+ for item in messages:
63
+ if not isinstance(item, Mapping):
64
+ continue
65
+ role = str(item.get("role") or "")
66
+ if role == "agent" and str(item.get("content") or "").strip():
67
+ answer = str(item["content"]).strip()
68
+ elif role == "tool_call_invocation":
69
+ calls.append(
70
+ {
71
+ "id": str(item.get("tool_call_id") or item.get("message_id") or ""),
72
+ "name": str(item.get("name") or ""),
73
+ "arguments": _arguments(item.get("arguments")),
74
+ }
75
+ )
76
+ elif role == "tool_call_result":
77
+ responses.append(
78
+ {
79
+ "tool_call_id": str(item.get("tool_call_id") or ""),
80
+ "content": item.get("content"),
81
+ "success": not bool(item.get("error")),
82
+ "error": item.get("error"),
83
+ }
84
+ )
85
+ return answer, calls, responses
86
+
87
+
88
+ class RetellChatWrapper(AgentWrapper):
89
+ def __init__(
90
+ self,
91
+ *,
92
+ api_key: str,
93
+ agent_id: str,
94
+ agent_version: int | None = None,
95
+ dynamic_variables: Mapping[str, Any] | None = None,
96
+ api_base_url: str = "https://api.retellai.com",
97
+ ) -> None:
98
+ if not api_key:
99
+ raise RetellChatError("retell_chat_api_key_missing")
100
+ if not agent_id:
101
+ raise RetellChatError("retell_chat_agent_id_missing")
102
+ self._api_key = api_key
103
+ self._agent_id = agent_id
104
+ self._agent_version = agent_version
105
+ self._dynamic_variables = {
106
+ str(key): str(value) for key, value in dict(dynamic_variables or {}).items()
107
+ }
108
+ self._base = api_base_url.rstrip("/")
109
+ self._chat_id: str | None = None
110
+
111
+ def _request(
112
+ self, method: str, path: str, payload: Mapping[str, Any] | None = None
113
+ ) -> dict[str, Any]:
114
+ body = None if payload is None else json.dumps(payload).encode("utf-8")
115
+ request = urllib.request.Request(
116
+ self._base + path,
117
+ data=body,
118
+ method=method,
119
+ headers={
120
+ "Authorization": f"Bearer {self._api_key}",
121
+ "Content-Type": "application/json",
122
+ "Accept": "application/json",
123
+ "User-Agent": "FutureAGI-ALK/1.0",
124
+ },
125
+ )
126
+ try:
127
+ with urllib.request.urlopen(request, timeout=120) as response: # noqa: S310
128
+ raw = response.read()
129
+ except urllib.error.HTTPError as exc:
130
+ detail = " ".join(exc.read(512).decode("utf-8", errors="replace").split())
131
+ if exc.code == 400 and "chat already ended" in detail.lower():
132
+ raise RetellChatEnded from exc
133
+ raise RetellChatError(
134
+ f"retell_chat_api_error: {method} returned HTTP {exc.code}"
135
+ + (f": {detail}" if detail else "")
136
+ ) from exc
137
+ except (urllib.error.URLError, TimeoutError) as exc:
138
+ raise RetellChatError(
139
+ f"retell_chat_api_unavailable: {type(exc).__name__}"
140
+ ) from exc
141
+ if not raw:
142
+ return {}
143
+ try:
144
+ value = json.loads(raw)
145
+ except ValueError as exc:
146
+ raise RetellChatError("retell_chat_api_response_invalid_json") from exc
147
+ if not isinstance(value, dict):
148
+ raise RetellChatError("retell_chat_api_response_must_be_object")
149
+ return value
150
+
151
+ def _is_ended(self) -> bool:
152
+ if self._chat_id is None:
153
+ return False
154
+ result = self._request("GET", f"/get-chat/{self._chat_id}")
155
+ return str(result.get("chat_status") or "").strip().lower() == "ended"
156
+
157
+ def _ended_response(
158
+ self,
159
+ *,
160
+ answer: str = "",
161
+ calls: list[dict[str, Any]] | None = None,
162
+ responses: list[dict[str, Any]] | None = None,
163
+ ) -> AgentResponse:
164
+ return AgentResponse(
165
+ content=answer,
166
+ tool_calls=calls or [],
167
+ tool_responses=responses or [],
168
+ events=[
169
+ SimulationEvent(
170
+ type="external_agent",
171
+ name="retell_chat_ended",
172
+ payload={"provider": "retell", "chat_id": self._chat_id},
173
+ )
174
+ ],
175
+ metadata={
176
+ "external_agent": {"success": True},
177
+ "retell_chat_id": self._chat_id,
178
+ "conversation_ended": True,
179
+ },
180
+ )
181
+
182
+ def _complete(self, content: str) -> AgentResponse:
183
+ if self._chat_id is None:
184
+ create: dict[str, Any] = {
185
+ "agent_id": self._agent_id,
186
+ "metadata": {"source": "futureagi-rl-environment"},
187
+ }
188
+ if self._agent_version is not None:
189
+ create["agent_version"] = self._agent_version
190
+ if self._dynamic_variables:
191
+ create["retell_llm_dynamic_variables"] = self._dynamic_variables
192
+ result = self._request("POST", "/create-chat", create)
193
+ self._chat_id = str(result.get("chat_id") or "").strip()
194
+ if not self._chat_id:
195
+ raise RetellChatError("retell_chat_create_missing_chat_id")
196
+ try:
197
+ result = self._request(
198
+ "POST",
199
+ "/create-chat-completion",
200
+ {"chat_id": self._chat_id, "content": content},
201
+ )
202
+ except RetellChatEnded:
203
+ return self._ended_response()
204
+ answer, calls, responses = _completion_messages(result.get("messages"))
205
+ explicitly_ended = (
206
+ bool(result.get("call_ended"))
207
+ or str(result.get("chat_status") or "").strip().lower() == "ended"
208
+ )
209
+ if not answer:
210
+ if explicitly_ended or self._is_ended():
211
+ return self._ended_response(calls=calls, responses=responses)
212
+ raise RetellChatError("retell_chat_completion_missing_agent_message")
213
+ if explicitly_ended:
214
+ return self._ended_response(answer=answer, calls=calls, responses=responses)
215
+ event = SimulationEvent(
216
+ type="external_agent",
217
+ name="retell_chat_completion",
218
+ payload={
219
+ "provider": "retell",
220
+ "chat_id": self._chat_id,
221
+ "tool_call_count": len(calls),
222
+ },
223
+ )
224
+ return AgentResponse(
225
+ content=answer,
226
+ tool_calls=calls,
227
+ tool_responses=responses,
228
+ events=[event],
229
+ metadata={
230
+ "external_agent": {"success": True},
231
+ "retell_chat_id": self._chat_id,
232
+ },
233
+ )
234
+
235
+ async def call(self, input: AgentInput) -> AgentResponse:
236
+ content = str((input.new_message or {}).get("content") or "").strip()
237
+ if not content:
238
+ raise RetellChatError("retell_chat_user_message_missing")
239
+ return await asyncio.to_thread(self._complete, content)
240
+
241
+ async def aclose(self) -> None:
242
+ if self._chat_id is None:
243
+ return
244
+ chat_id, self._chat_id = self._chat_id, None
245
+ try:
246
+ await asyncio.to_thread(self._request, "PATCH", f"/end-chat/{chat_id}")
247
+ except (RetellChatError, RetellChatEnded):
248
+ # Closing is idempotent. A chat may have ended as part of its final
249
+ # completion, in which case Retell rejects the redundant PATCH with
250
+ # "Chat already ended". Cleanup must never replace a valid call
251
+ # outcome with that harmless terminal response.
252
+ return
253
+
254
+
255
+ class RetellChatCallRunner:
256
+ """Drive multi-turn scenarios directly through Retell's native Chat API."""
257
+
258
+ def __init__(self, adapter: ArtifactUploader, context: CallRunnerContext) -> None:
259
+ self._adapter = adapter
260
+ self._context = context
261
+ path = context.bundle_dir / "contract.json"
262
+ self._contract = (
263
+ AgentContract.model_validate_json(path.read_text())
264
+ if path.is_file()
265
+ else None
266
+ )
267
+
268
+ async def run(
269
+ self,
270
+ scenario: Scenario,
271
+ runtime: EnvironmentRuntime,
272
+ *,
273
+ world: World | None = None,
274
+ ) -> CallOutcome:
275
+ del world
276
+ if self._contract is None:
277
+ raise CallAborted(
278
+ "chat_contract_unavailable: bundle/contract.json is absent"
279
+ )
280
+ agent_id = str(
281
+ runtime.metadata.get("provider_target_id")
282
+ or self._context.job.agent.config.get("agent_id")
283
+ or ""
284
+ ).strip()
285
+ api_key = self._context.target_provider_secret_values.get(
286
+ RETELL_API_KEY_ALIAS, ""
287
+ )
288
+ config = self._context.job.agent.config
289
+ raw_dynamic = config.get("dynamic_variables")
290
+ dynamic_variables = raw_dynamic if isinstance(raw_dynamic, Mapping) else {}
291
+ version_value = config.get("agent_version")
292
+ agent_version = int(version_value) if version_value not in (None, "") else None
293
+ wrapper = RetellChatWrapper(
294
+ api_key=api_key,
295
+ agent_id=agent_id,
296
+ agent_version=agent_version,
297
+ dynamic_variables=dynamic_variables,
298
+ api_base_url=str(
299
+ config.get("provider_api_base_url") or "https://api.retellai.com"
300
+ ),
301
+ )
302
+ document = _scenario_document(self._context.bundle_dir, scenario.scenario_key)
303
+ conversation_scenario = _conversation_scenario(document)
304
+ target_world = _tool_world(
305
+ self._context.bundle_dir,
306
+ self._contract,
307
+ runtime,
308
+ self._context.source_directory,
309
+ )
310
+ target = _HostedChatTarget(
311
+ wrapper=wrapper,
312
+ contract=self._contract,
313
+ world=target_world,
314
+ scenario_key=scenario.scenario_key,
315
+ scenario_id=scenario.scenario_id,
316
+ )
317
+ started = datetime.now(timezone.utc)
318
+ try:
319
+ transcript = await _drive_conversation(
320
+ target, conversation_scenario, self._contract, self._context.bundle_dir
321
+ )
322
+ except CallAborted:
323
+ raise
324
+ except Exception as exc:
325
+ raise CallAborted(
326
+ f"retell_chat_target_failed: {type(exc).__name__}: {exc}"
327
+ ) from exc
328
+ finally:
329
+ await wrapper.aclose()
330
+ ended = datetime.now(timezone.utc)
331
+ transcript_id = await self._adapter.upload_artifact(
332
+ transcript.artifact(),
333
+ kind=ArtifactKind.TRANSCRIPT,
334
+ scenario_key=scenario.scenario_key,
335
+ )
336
+ calls = tuple(transcript.calls)
337
+ if calls:
338
+ trace = "\n".join(
339
+ json.dumps(
340
+ {
341
+ "name": call.name,
342
+ "arguments": call.arguments,
343
+ "result": call.result,
344
+ "ok": call.ok,
345
+ "error": call.error,
346
+ "refused": call.refused,
347
+ "at": call.at,
348
+ },
349
+ sort_keys=True,
350
+ default=str,
351
+ )
352
+ for call in calls
353
+ ).encode()
354
+ await self._adapter.upload_artifact(
355
+ trace, kind=ArtifactKind.TOOL_TRACE, scenario_key=scenario.scenario_key
356
+ )
357
+ return CallOutcome(
358
+ calls=calls,
359
+ turns=len(transcript.exchanges),
360
+ started_at=format_rfc3339_millis(started),
361
+ ended_at=format_rfc3339_millis(ended),
362
+ duration_ms=_duration_ms(started, ended),
363
+ transcript_artifact=transcript_id,
364
+ messages=tuple(transcript.canonical_messages()),
365
+ )
366
+
367
+
368
+ __all__ = [
369
+ "RetellChatCallRunner",
370
+ "RetellChatEnded",
371
+ "RetellChatError",
372
+ "RetellChatWrapper",
373
+ ]