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,80 @@
1
+ from typing import Any, Union
2
+ from fi.simulate.agent.wrapper import AgentWrapper, AgentInput, AgentResponse
3
+
4
+ try:
5
+ from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
6
+ except ImportError:
7
+ # LangChain is optional. Fallback message classes keep wrappers and tests
8
+ # usable for mocked/local simulations without installing langchain-core.
9
+ class _FallbackMessage:
10
+ def __init__(self, content: str):
11
+ self.content = content
12
+
13
+ class HumanMessage(_FallbackMessage):
14
+ pass
15
+
16
+ class AIMessage(_FallbackMessage):
17
+ pass
18
+
19
+ class SystemMessage(_FallbackMessage):
20
+ pass
21
+
22
+ LANGCHAIN_INSTALLED = False
23
+ else:
24
+ LANGCHAIN_INSTALLED = True
25
+
26
+ class LangChainAgentWrapper(AgentWrapper):
27
+ """
28
+ Wrapper for LangChain Runnable or Chain agents.
29
+ """
30
+ def __init__(self, agent: Any, system_prompt: str = None):
31
+ """
32
+ Args:
33
+ agent: A LangChain Runnable (chain, agent executor) that accepts input.
34
+ It is expected to accept a dictionary with "messages" or "input".
35
+ system_prompt: Optional system prompt to prepend to message history.
36
+ """
37
+ self.agent = agent
38
+ self.system_prompt = system_prompt
39
+
40
+ async def call(self, input: AgentInput) -> Union[str, AgentResponse]:
41
+
42
+ # Convert history to LangChain messages
43
+ lc_messages = []
44
+
45
+ if self.system_prompt:
46
+ lc_messages.append(SystemMessage(content=self.system_prompt))
47
+
48
+ for msg in input.messages:
49
+ role = msg.get("role")
50
+ content = msg.get("content")
51
+ if role == "user":
52
+ lc_messages.append(HumanMessage(content=content))
53
+ elif role == "assistant":
54
+ lc_messages.append(AIMessage(content=content))
55
+ elif role == "system":
56
+ lc_messages.append(SystemMessage(content=content))
57
+
58
+ # Invoke the agent
59
+ # We try standard keys used in LC agents
60
+ inputs = {
61
+ "messages": lc_messages,
62
+ "input": input.new_message.get("content") if input.new_message else "",
63
+ "chat_history": lc_messages[:-1] if lc_messages else []
64
+ }
65
+
66
+ # Support both ainvoke and invoke
67
+ if hasattr(self.agent, "ainvoke"):
68
+ response = await self.agent.ainvoke(inputs)
69
+ else:
70
+ response = self.agent.invoke(inputs)
71
+
72
+ # Parse response
73
+ if isinstance(response, str):
74
+ return response
75
+ elif hasattr(response, "content"):
76
+ return response.content
77
+ elif isinstance(response, dict) and "output" in response:
78
+ return response["output"]
79
+
80
+ return str(response)
@@ -0,0 +1,75 @@
1
+ from typing import Any, Union
2
+ from fi.simulate.agent.wrapper import AgentWrapper, AgentInput, AgentResponse
3
+
4
+ class OpenAIAgentWrapper(AgentWrapper):
5
+ """
6
+ Wrapper for OpenAI-based agents.
7
+ Automatically handles message conversion to OpenAI format.
8
+ """
9
+ def __init__(self, client: Any, model: str = "gpt-4-turbo", system_prompt: str = None):
10
+ """
11
+ Args:
12
+ client: The OpenAI client instance (AsyncOpenAI or OpenAI).
13
+ model: The model name to use (e.g., "gpt-4-turbo").
14
+ system_prompt: Optional system instructions for the agent.
15
+ """
16
+ self.client = client
17
+ self.model = model
18
+ self.system_prompt = system_prompt
19
+
20
+ async def call(self, input: AgentInput) -> Union[str, AgentResponse]:
21
+ # Convert internal message format to OpenAI format
22
+ # Input messages are already in [{"role": "...", "content": "..."}] format which OpenAI accepts
23
+ messages = []
24
+ if self.system_prompt:
25
+ messages.append({"role": "system", "content": self.system_prompt})
26
+
27
+ messages.extend(input.messages)
28
+
29
+ # Check if client is async or sync
30
+ if hasattr(self.client, "chat") and hasattr(self.client.chat, "completions"):
31
+ # Handle AsyncOpenAI vs OpenAI
32
+ if hasattr(self.client.chat.completions, "create"):
33
+ # Modern OpenAI SDK (v1+)
34
+ if is_async_client(self.client):
35
+ completion = await self.client.chat.completions.create(
36
+ model=self.model,
37
+ messages=messages
38
+ )
39
+ else:
40
+ completion = self.client.chat.completions.create(
41
+ model=self.model,
42
+ messages=messages
43
+ )
44
+
45
+ message = completion.choices[0].message
46
+ content = message.content or ""
47
+
48
+ # Extract tool_calls if present
49
+ tool_calls = None
50
+ if hasattr(message, 'tool_calls') and message.tool_calls:
51
+ # Convert tool_calls to dict format for serialization
52
+ tool_calls = []
53
+ for tc in message.tool_calls:
54
+ tool_call_dict = {
55
+ "id": tc.id,
56
+ "type": tc.type,
57
+ "function": {
58
+ "name": tc.function.name,
59
+ "arguments": tc.function.arguments
60
+ }
61
+ }
62
+ tool_calls.append(tool_call_dict)
63
+
64
+ # Return AgentResponse if tool_calls exist, otherwise just content string
65
+ if tool_calls:
66
+ return AgentResponse(content=content, tool_calls=tool_calls)
67
+ return content
68
+
69
+ raise ValueError("Unsupported OpenAI client. Please provide a valid OpenAI or AsyncOpenAI client.")
70
+
71
+ def is_async_client(client: Any) -> bool:
72
+ """Check if the client is an async client."""
73
+ # Heuristic check for async client
74
+ return type(client).__name__ == "AsyncOpenAI"
75
+
@@ -0,0 +1,326 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import base64
5
+ import hashlib
6
+ import json
7
+ import os
8
+ import socket
9
+ import time
10
+ from typing import Any, Mapping, Optional
11
+ from urllib.parse import urlparse
12
+
13
+ from fi.simulate.agent.wrapper import (
14
+ AgentInput,
15
+ AgentResponse,
16
+ SimulationArtifact,
17
+ SimulationEvent,
18
+ )
19
+ from fi.simulate.agent.wrapper import AgentWrapper
20
+ from fi.simulate.agent.wrappers.http import (
21
+ _artifact_list,
22
+ _content_text,
23
+ _event_list,
24
+ _optional_mapping,
25
+ _redacted_endpoint,
26
+ _tool_call_list,
27
+ _tool_response_list,
28
+ )
29
+
30
+
31
+ class WebSocketAgentWrapper(AgentWrapper):
32
+ """WebSocket target adapter for local framework transport simulation."""
33
+
34
+ def __init__(
35
+ self,
36
+ *,
37
+ endpoint: str,
38
+ protocol: str = "fi.alk",
39
+ model: Optional[str] = None,
40
+ api_key: Optional[str] = None,
41
+ api_key_env: Optional[str] = None,
42
+ headers: Optional[Mapping[str, str]] = None,
43
+ timeout: float = 30.0,
44
+ include_tools: bool = True,
45
+ system_prompt: Optional[str] = None,
46
+ metadata: Optional[Mapping[str, Any]] = None,
47
+ ) -> None:
48
+ if not endpoint:
49
+ raise ValueError("endpoint is required")
50
+ self.endpoint = endpoint
51
+ self.protocol = _normalize_protocol(protocol)
52
+ self.model = model
53
+ self.api_key = api_key
54
+ self.api_key_env = api_key_env
55
+ self.headers = {str(k): str(v) for k, v in dict(headers or {}).items()}
56
+ self.timeout = float(timeout)
57
+ self.include_tools = bool(include_tools)
58
+ self.system_prompt = system_prompt
59
+ self.metadata = dict(metadata or {})
60
+
61
+ async def call(self, input: AgentInput) -> AgentResponse:
62
+ started = time.time()
63
+ request_payload = self._request_payload(input)
64
+ headers = self._request_headers()
65
+ status_code = 0
66
+ response_payload: dict[str, Any] = {}
67
+ error: Optional[str] = None
68
+ try:
69
+ status_code, response_payload = await asyncio.to_thread(
70
+ self._send_json,
71
+ request_payload,
72
+ headers,
73
+ )
74
+ response = self._agent_response_from_payload(response_payload)
75
+ except Exception as exc:
76
+ error = str(exc)
77
+ response = AgentResponse(content=f"WebSocket target failed: {exc}")
78
+
79
+ latency_ms = round((time.time() - started) * 1000, 4)
80
+ parsed = urlparse(self.endpoint)
81
+ trace = {
82
+ "kind": "external_agent_websocket_trace",
83
+ "protocol": self.protocol,
84
+ "endpoint": _redacted_endpoint(self.endpoint),
85
+ "endpoint_host": parsed.netloc,
86
+ "model": self.model,
87
+ "status_code": status_code,
88
+ "latency_ms": latency_ms,
89
+ "request_message_count": len(input.messages),
90
+ "request_tool_count": len(input.tools) if self.include_tools else 0,
91
+ "response_tool_call_count": len(response.tool_calls or []),
92
+ "success": error is None and status_code == 101,
93
+ "request_header_names": sorted(headers),
94
+ "auth": {
95
+ "mode": "bearer" if self._resolved_api_key() else "none",
96
+ "api_key_env": self.api_key_env,
97
+ "redacted": bool(self._resolved_api_key()),
98
+ },
99
+ "error": error,
100
+ **self.metadata,
101
+ }
102
+ response.events.append(
103
+ SimulationEvent(
104
+ type="external_agent",
105
+ name="external_agent_websocket_call",
106
+ payload=trace,
107
+ )
108
+ )
109
+ response.artifacts.append(
110
+ SimulationArtifact(
111
+ type="trace",
112
+ role="agent",
113
+ data=trace,
114
+ metadata={"kind": "external_agent_websocket_trace"},
115
+ )
116
+ )
117
+ state = dict(response.state or {})
118
+ state["external_agent"] = trace
119
+ state["external_agent_trace"] = trace
120
+ response.state = state
121
+ metadata = dict(response.metadata or {})
122
+ metadata["external_agent"] = trace
123
+ metadata["external_agent_trace"] = trace
124
+ response.metadata = metadata
125
+ return response
126
+
127
+ def _request_payload(self, input: AgentInput) -> dict[str, Any]:
128
+ messages = list(input.messages)
129
+ if self.system_prompt:
130
+ messages = [{"role": "system", "content": self.system_prompt}, *messages]
131
+ return {
132
+ "thread_id": input.thread_id,
133
+ "execution_id": input.execution_id,
134
+ "turn_index": input.turn_index,
135
+ "scenario_name": input.scenario_name,
136
+ "persona": input.persona,
137
+ "situation": input.situation,
138
+ "expected_outcome": input.expected_outcome,
139
+ "messages": messages,
140
+ "new_message": input.new_message,
141
+ "tools": list(input.tools) if self.include_tools else [],
142
+ "metadata": input.metadata,
143
+ }
144
+
145
+ def _request_headers(self) -> dict[str, str]:
146
+ headers = dict(self.headers)
147
+ api_key = self._resolved_api_key()
148
+ if api_key and not any(key.lower() == "authorization" for key in headers):
149
+ headers["Authorization"] = f"Bearer {api_key}"
150
+ return headers
151
+
152
+ def _resolved_api_key(self) -> str:
153
+ if self.api_key not in (None, ""):
154
+ return str(self.api_key)
155
+ if self.api_key_env:
156
+ return os.environ.get(self.api_key_env, "")
157
+ return ""
158
+
159
+ def _send_json(
160
+ self,
161
+ payload: Mapping[str, Any],
162
+ headers: Mapping[str, str],
163
+ ) -> tuple[int, dict[str, Any]]:
164
+ parsed = urlparse(self.endpoint)
165
+ if parsed.scheme != "ws":
166
+ raise ValueError("WebSocketAgentWrapper currently supports ws:// endpoints")
167
+ host = parsed.hostname or ""
168
+ port = int(parsed.port or 80)
169
+ path = parsed.path or "/"
170
+ if parsed.query:
171
+ path = f"{path}?{parsed.query}"
172
+ key = base64.b64encode(os.urandom(16)).decode("ascii")
173
+ with socket.create_connection((host, port), timeout=self.timeout) as sock:
174
+ sock.settimeout(self.timeout)
175
+ status = self._handshake(
176
+ sock,
177
+ host=host,
178
+ port=port,
179
+ path=path,
180
+ key=key,
181
+ headers=headers,
182
+ )
183
+ _send_text_frame(sock, json.dumps(payload, default=str), mask=True)
184
+ opcode, text = _read_frame(sock)
185
+ if opcode == 8:
186
+ raise ValueError("WebSocket target closed before returning JSON")
187
+ try:
188
+ _send_close_frame(sock)
189
+ except OSError:
190
+ pass
191
+ try:
192
+ response = json.loads(text or "{}")
193
+ except json.JSONDecodeError as exc:
194
+ raise ValueError(f"WebSocket target returned non-JSON response: {exc}") from exc
195
+ if not isinstance(response, dict):
196
+ raise ValueError("WebSocket target response must be a JSON object")
197
+ return status, response
198
+
199
+ def _handshake(
200
+ self,
201
+ sock: socket.socket,
202
+ *,
203
+ host: str,
204
+ port: int,
205
+ path: str,
206
+ key: str,
207
+ headers: Mapping[str, str],
208
+ ) -> int:
209
+ request_headers = {
210
+ "Host": f"{host}:{port}",
211
+ "Upgrade": "websocket",
212
+ "Connection": "Upgrade",
213
+ "Sec-WebSocket-Key": key,
214
+ "Sec-WebSocket-Version": "13",
215
+ **dict(headers),
216
+ }
217
+ request = "GET " + path + " HTTP/1.1\r\n" + "\r\n".join(
218
+ f"{name}: {value}" for name, value in request_headers.items()
219
+ ) + "\r\n\r\n"
220
+ sock.sendall(request.encode("utf-8"))
221
+ response = _read_until(sock, b"\r\n\r\n").decode("utf-8", errors="replace")
222
+ lines = response.split("\r\n")
223
+ status_line = lines[0] if lines else ""
224
+ parts = status_line.split()
225
+ status = int(parts[1]) if len(parts) >= 2 and parts[1].isdigit() else 0
226
+ response_headers: dict[str, str] = {}
227
+ for line in lines[1:]:
228
+ if ":" in line:
229
+ name, value = line.split(":", 1)
230
+ response_headers[name.strip().lower()] = value.strip()
231
+ expected_accept = base64.b64encode(
232
+ hashlib.sha1(
233
+ (key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").encode("ascii")
234
+ ).digest()
235
+ ).decode("ascii")
236
+ if status != 101:
237
+ raise ValueError(f"WebSocket target returned status {status}")
238
+ if response_headers.get("sec-websocket-accept") != expected_accept:
239
+ raise ValueError("WebSocket target returned invalid accept key")
240
+ return status
241
+
242
+ def _agent_response_from_payload(self, payload: Mapping[str, Any]) -> AgentResponse:
243
+ return AgentResponse(
244
+ content=_content_text(payload.get("content") or payload.get("message")),
245
+ tool_calls=_tool_call_list(payload.get("tool_calls")),
246
+ tool_responses=_tool_response_list(payload.get("tool_responses")),
247
+ artifacts=_artifact_list(payload.get("artifacts")),
248
+ events=_event_list(payload.get("events")),
249
+ memory_updates=_optional_mapping(payload.get("memory_updates")),
250
+ state=_optional_mapping(payload.get("state")),
251
+ metadata=_optional_mapping(payload.get("metadata")),
252
+ )
253
+
254
+
255
+ def _normalize_protocol(value: str) -> str:
256
+ protocol = str(value or "fi.alk").lower().replace("-", "_")
257
+ aliases = {
258
+ "agent_learning_websocket": "fi.alk",
259
+ "websocket": "fi.alk",
260
+ "ws": "fi.alk",
261
+ }
262
+ protocol = aliases.get(protocol, protocol)
263
+ if protocol != "fi.alk":
264
+ raise ValueError("protocol must be fi.alk")
265
+ return protocol
266
+
267
+
268
+ def _read_until(sock: socket.socket, marker: bytes) -> bytes:
269
+ chunks: list[bytes] = []
270
+ data = b""
271
+ while marker not in data:
272
+ chunk = sock.recv(4096)
273
+ if not chunk:
274
+ break
275
+ chunks.append(chunk)
276
+ data = b"".join(chunks)
277
+ return data
278
+
279
+
280
+ def _send_text_frame(sock: socket.socket, text: str, *, mask: bool) -> None:
281
+ payload = text.encode("utf-8")
282
+ header = bytearray([0x81])
283
+ length = len(payload)
284
+ mask_bit = 0x80 if mask else 0
285
+ if length < 126:
286
+ header.append(mask_bit | length)
287
+ elif length <= 0xFFFF:
288
+ header.extend([mask_bit | 126, *length.to_bytes(2, "big")])
289
+ else:
290
+ header.extend([mask_bit | 127, *length.to_bytes(8, "big")])
291
+ if mask:
292
+ key = os.urandom(4)
293
+ masked = bytes(byte ^ key[index % 4] for index, byte in enumerate(payload))
294
+ sock.sendall(bytes(header) + key + masked)
295
+ else:
296
+ sock.sendall(bytes(header) + payload)
297
+
298
+
299
+ def _send_close_frame(sock: socket.socket) -> None:
300
+ sock.sendall(b"\x88\x80" + os.urandom(4))
301
+
302
+
303
+ def _read_frame(sock: socket.socket) -> tuple[int, str]:
304
+ first = _read_exact(sock, 2)
305
+ opcode = first[0] & 0x0F
306
+ masked = bool(first[1] & 0x80)
307
+ length = first[1] & 0x7F
308
+ if length == 126:
309
+ length = int.from_bytes(_read_exact(sock, 2), "big")
310
+ elif length == 127:
311
+ length = int.from_bytes(_read_exact(sock, 8), "big")
312
+ mask_key = _read_exact(sock, 4) if masked else b""
313
+ payload = _read_exact(sock, length) if length else b""
314
+ if masked:
315
+ payload = bytes(byte ^ mask_key[index % 4] for index, byte in enumerate(payload))
316
+ return opcode, payload.decode("utf-8")
317
+
318
+
319
+ def _read_exact(sock: socket.socket, size: int) -> bytes:
320
+ data = bytearray()
321
+ while len(data) < size:
322
+ chunk = sock.recv(size - len(data))
323
+ if not chunk:
324
+ raise ValueError("WebSocket connection closed unexpectedly")
325
+ data.extend(chunk)
326
+ return bytes(data)
@@ -0,0 +1,11 @@
1
+ from .manifest import (
2
+ ARTIFACT_MANIFEST_SCHEMA_VERSION,
3
+ ArtifactManifest,
4
+ ArtifactManifestEntry,
5
+ )
6
+
7
+ __all__ = [
8
+ "ARTIFACT_MANIFEST_SCHEMA_VERSION",
9
+ "ArtifactManifest",
10
+ "ArtifactManifestEntry",
11
+ ]
@@ -0,0 +1,62 @@
1
+ from __future__ import annotations
2
+
3
+ from pydantic import BaseModel, Field, JsonValue, model_validator
4
+
5
+ from fi.simulate.agent.wrapper import ArtifactType
6
+ from fi.simulate.evidence import EvidenceClass
7
+ from fi.simulate._hashing import content_hash
8
+
9
+ ARTIFACT_MANIFEST_SCHEMA_VERSION = "futureagi.artifact-manifest.v1"
10
+
11
+
12
+ class ArtifactManifestEntry(BaseModel):
13
+ artifact_id: str
14
+ test_case_id: str | None = None
15
+ type: ArtifactType
16
+ path: str | None = None
17
+ uri: str | None = None
18
+ checksum: str
19
+ size_bytes: int = Field(ge=0)
20
+ mime_type: str | None = None
21
+ codec: str | None = None
22
+ sample_rate: int | None = Field(default=None, gt=0)
23
+ channels: int | None = Field(default=None, gt=0)
24
+ participant_id: str | None = None
25
+ track_id: str | None = None
26
+ leg_id: str | None = None
27
+ start_time_ns: int | None = None
28
+ end_time_ns: int | None = None
29
+ evidence_class: EvidenceClass
30
+ evidence_source_id: str
31
+ redacted: bool = False
32
+ metadata: dict[str, JsonValue] = Field(default_factory=dict)
33
+
34
+ @model_validator(mode="after")
35
+ def _validate_location(self) -> "ArtifactManifestEntry":
36
+ if not self.path and not self.uri:
37
+ raise ValueError("artifact_location_missing: path or uri is required")
38
+ if self.path and self.uri:
39
+ raise ValueError("artifact_location_ambiguous: provide path or uri, not both")
40
+ if not self.checksum.startswith("sha256:"):
41
+ raise ValueError("artifact_checksum_invalid: checksum must use sha256:<digest>")
42
+ return self
43
+
44
+
45
+ class ArtifactManifest(BaseModel):
46
+ schema_version: str = ARTIFACT_MANIFEST_SCHEMA_VERSION
47
+ run_id: str
48
+ entries: list[ArtifactManifestEntry] = Field(default_factory=list)
49
+ manifest_hash: str | None = None
50
+
51
+ def content_hash(self) -> str:
52
+ payload = self.model_dump(exclude={"manifest_hash"}, exclude_none=True)
53
+ payload["entries"] = sorted(payload["entries"], key=lambda item: item["artifact_id"])
54
+ return content_hash(payload)
55
+
56
+ @model_validator(mode="after")
57
+ def _stamp_hash(self) -> "ArtifactManifest":
58
+ expected = self.content_hash()
59
+ if self.manifest_hash is not None and self.manifest_hash != expected:
60
+ raise ValueError("artifact_manifest_hash_mismatch")
61
+ object.__setattr__(self, "manifest_hash", expected)
62
+ return self