legate 0.1.0

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 (317) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +345 -0
  4. data/bin/legate +13 -0
  5. data/examples/00_quickstart.rb +51 -0
  6. data/examples/01_simple_agent.rb +105 -0
  7. data/examples/02_multi_tool_agent.rb +140 -0
  8. data/examples/03_custom_tool.rb +93 -0
  9. data/examples/04_agent_instructions.rb +84 -0
  10. data/examples/05_state_and_sessions.rb +91 -0
  11. data/examples/06_callbacks.rb +186 -0
  12. data/examples/07_async_jobs.rb +112 -0
  13. data/examples/08_loop_agent.rb +197 -0
  14. data/examples/09_sequential_workflow.rb +40 -0
  15. data/examples/10_parallel_workflow.rb +34 -0
  16. data/examples/11_agent_delegation.rb +24 -0
  17. data/examples/12_http_client_tool.rb +156 -0
  18. data/examples/13_authentication.rb +220 -0
  19. data/examples/14_mcp_client.rb +154 -0
  20. data/examples/15_mcp_server.rb +79 -0
  21. data/examples/16_webhooks.rb +91 -0
  22. data/examples/README_sequential_agents.md +164 -0
  23. data/examples/advanced/auth/cookie_auth_tool.rb +146 -0
  24. data/examples/advanced/auth/custom_auth_flows_example.rb +626 -0
  25. data/examples/advanced/auth/excon_middleware.rb +317 -0
  26. data/examples/advanced/auth/excon_middleware_auth.rb +399 -0
  27. data/examples/advanced/auth/fiber_auth_example.rb +281 -0
  28. data/examples/advanced/auth/fiber_oidc_example.rb +403 -0
  29. data/examples/advanced/auth/httpbin_bearer_tool.rb +159 -0
  30. data/examples/advanced/auth/oauth2_auth.rb +419 -0
  31. data/examples/advanced/auth/oidc_auth.rb +514 -0
  32. data/examples/advanced/auth/openweather_api.rb +251 -0
  33. data/examples/advanced/auth/openweather_tool.rb +153 -0
  34. data/examples/advanced/auth/query_param_middleware_test.rb +138 -0
  35. data/examples/advanced/auth/service_account.rb +135 -0
  36. data/examples/advanced/auth/test_with_httpbin.rb +202 -0
  37. data/examples/advanced/auth/token_lifecycle_example.rb +428 -0
  38. data/examples/advanced/callback_monitoring.rb +679 -0
  39. data/examples/advanced/mas/fixed_delegation_example.rb +191 -0
  40. data/examples/advanced/mas/loop_workflow.rb +28 -0
  41. data/examples/advanced/mas/mock_planner.rb +77 -0
  42. data/examples/advanced/mas/proper_delegation_example.rb +276 -0
  43. data/examples/advanced/mcp/legate_mcp_server_resource_example.rb +182 -0
  44. data/examples/advanced/mcp/mcp_resource_server_example.rb +309 -0
  45. data/examples/advanced/mcp/mcp_server_async.rb +76 -0
  46. data/examples/advanced/mcp/mcp_server_async_tools.rb +122 -0
  47. data/examples/advanced/mcp/mcp_server_legate_agent.rb +95 -0
  48. data/examples/advanced/mcp/mcp_server_rack.rb +89 -0
  49. data/examples/advanced/random_calculator.rb +104 -0
  50. data/examples/advanced/sleep_agent.rb +153 -0
  51. data/examples/advanced/webhooks/webhook_e2e_runner.rb +110 -0
  52. data/examples/advanced/webhooks/webhook_receiver_agent.rb +58 -0
  53. data/examples/advanced/workflows/task_refinement_loop_agent.rb +278 -0
  54. data/examples/advanced/workflows/travel_planner_auto_sequential.rb +444 -0
  55. data/examples/advanced/workflows/travel_planner_parallel.rb +656 -0
  56. data/examples/advanced/workflows/travel_planner_sequential.rb +512 -0
  57. data/examples/tools/oauth2_example.rb +136 -0
  58. data/examples/tools/sleepy_tool.rb +42 -0
  59. data/lib/legate/activity_log.rb +71 -0
  60. data/lib/legate/agent.rb +959 -0
  61. data/lib/legate/agent_code_generator.rb +185 -0
  62. data/lib/legate/agent_definition.rb +812 -0
  63. data/lib/legate/agentic/decision.rb +49 -0
  64. data/lib/legate/agentic/loop.rb +134 -0
  65. data/lib/legate/agentic.rb +5 -0
  66. data/lib/legate/agents/loop_agent.rb +248 -0
  67. data/lib/legate/agents/parallel_agent.rb +163 -0
  68. data/lib/legate/agents/sequential_agent.rb +190 -0
  69. data/lib/legate/agents.rb +14 -0
  70. data/lib/legate/auth/config.rb +148 -0
  71. data/lib/legate/auth/coordinator.rb +218 -0
  72. data/lib/legate/auth/coordinators/oauth2_coordinator.rb +99 -0
  73. data/lib/legate/auth/coordinators/oidc_coordinator.rb +68 -0
  74. data/lib/legate/auth/coordinators/service_account_coordinator.rb +122 -0
  75. data/lib/legate/auth/credential.rb +157 -0
  76. data/lib/legate/auth/encryption.rb +108 -0
  77. data/lib/legate/auth/error.rb +94 -0
  78. data/lib/legate/auth/exchanged_credential.rb +180 -0
  79. data/lib/legate/auth/excon_middleware.rb +285 -0
  80. data/lib/legate/auth/http_client_utils.rb +364 -0
  81. data/lib/legate/auth/manager.rb +531 -0
  82. data/lib/legate/auth/manager_store.rb +394 -0
  83. data/lib/legate/auth/middleware_factory.rb +290 -0
  84. data/lib/legate/auth/runner.rb +279 -0
  85. data/lib/legate/auth/scheme.rb +125 -0
  86. data/lib/legate/auth/schemes/api_key.rb +212 -0
  87. data/lib/legate/auth/schemes/google_service_account.rb +108 -0
  88. data/lib/legate/auth/schemes/http_bearer.rb +98 -0
  89. data/lib/legate/auth/schemes/oauth2.rb +396 -0
  90. data/lib/legate/auth/schemes/openid_connect.rb +346 -0
  91. data/lib/legate/auth/schemes/service_account.rb +388 -0
  92. data/lib/legate/auth/schemes.rb +40 -0
  93. data/lib/legate/auth/token_manager.rb +362 -0
  94. data/lib/legate/auth/token_store.rb +86 -0
  95. data/lib/legate/auth/tool_context_extension.rb +97 -0
  96. data/lib/legate/auth/tool_integration.rb +188 -0
  97. data/lib/legate/auth/url_guard.rb +81 -0
  98. data/lib/legate/auth.rb +453 -0
  99. data/lib/legate/callbacks/callback_context.rb +71 -0
  100. data/lib/legate/cli/agent_commands.rb +950 -0
  101. data/lib/legate/cli/auth_commands.rb +520 -0
  102. data/lib/legate/cli/base_command.rb +24 -0
  103. data/lib/legate/cli/deployment_commands.rb +934 -0
  104. data/lib/legate/cli/output_helper.rb +108 -0
  105. data/lib/legate/cli/session_commands.rb +138 -0
  106. data/lib/legate/cli/skaffold_commands.rb +223 -0
  107. data/lib/legate/cli/tool_commands.rb +261 -0
  108. data/lib/legate/cli/web_commands.rb +182 -0
  109. data/lib/legate/cli.rb +40 -0
  110. data/lib/legate/configuration/webhooks.rb +113 -0
  111. data/lib/legate/configuration.rb +39 -0
  112. data/lib/legate/definition_store.rb +23 -0
  113. data/lib/legate/errors.rb +118 -0
  114. data/lib/legate/event.rb +161 -0
  115. data/lib/legate/gemini_ai_beta_patch.rb +39 -0
  116. data/lib/legate/generators/agent_generator.rb +412 -0
  117. data/lib/legate/generators/code_validator.rb +48 -0
  118. data/lib/legate/generators/legate/install_generator.rb +35 -0
  119. data/lib/legate/generators/legate/templates/create_legate_tables.rb.tt +36 -0
  120. data/lib/legate/generators/legate/templates/initializer.rb +18 -0
  121. data/lib/legate/generators/runtime_tool_loader.rb +76 -0
  122. data/lib/legate/generators/tool_generator.rb +408 -0
  123. data/lib/legate/generators.rb +11 -0
  124. data/lib/legate/global_definition_registry.rb +506 -0
  125. data/lib/legate/global_tool_manager.rb +135 -0
  126. data/lib/legate/llm/adapter.rb +69 -0
  127. data/lib/legate/llm/gemini.rb +172 -0
  128. data/lib/legate/llm/ollama.rb +80 -0
  129. data/lib/legate/llm.rb +34 -0
  130. data/lib/legate/mcp/client.rb +320 -0
  131. data/lib/legate/mcp/connection/sse.rb +292 -0
  132. data/lib/legate/mcp/connection/stdio.rb +273 -0
  133. data/lib/legate/mcp/connection_manager.rb +103 -0
  134. data/lib/legate/mcp/server/legate_agent_adapter.rb +170 -0
  135. data/lib/legate/mcp/server/legate_direct_agent_adapter.rb +140 -0
  136. data/lib/legate/mcp/server/legate_tool_adapter.rb +119 -0
  137. data/lib/legate/mcp/tool_wrapper.rb +138 -0
  138. data/lib/legate/mcp/util/schema_converter.rb +134 -0
  139. data/lib/legate/mcp.rb +23 -0
  140. data/lib/legate/plan_executor.rb +375 -0
  141. data/lib/legate/planner.rb +839 -0
  142. data/lib/legate/rails/railtie.rb +43 -0
  143. data/lib/legate/rails.rb +9 -0
  144. data/lib/legate/redaction.rb +32 -0
  145. data/lib/legate/session.rb +299 -0
  146. data/lib/legate/session_service/active_record.rb +300 -0
  147. data/lib/legate/session_service/base.rb +68 -0
  148. data/lib/legate/session_service/event_broadcast.rb +74 -0
  149. data/lib/legate/session_service/in_memory.rb +188 -0
  150. data/lib/legate/tool/metadata_dsl.rb +122 -0
  151. data/lib/legate/tool.rb +276 -0
  152. data/lib/legate/tool_code_generator.rb +103 -0
  153. data/lib/legate/tool_context.rb +350 -0
  154. data/lib/legate/tool_loader.rb +39 -0
  155. data/lib/legate/tool_registry.rb +73 -0
  156. data/lib/legate/tool_result.rb +61 -0
  157. data/lib/legate/tools/agent_tool.rb +187 -0
  158. data/lib/legate/tools/base/http_client.rb +319 -0
  159. data/lib/legate/tools/base/safe_url.rb +56 -0
  160. data/lib/legate/tools/base_async_job_tool.rb +91 -0
  161. data/lib/legate/tools/calculator.rb +89 -0
  162. data/lib/legate/tools/cat_facts.rb +81 -0
  163. data/lib/legate/tools/check_job_status_tool.rb +48 -0
  164. data/lib/legate/tools/current_time_tool.rb +64 -0
  165. data/lib/legate/tools/echo.rb +43 -0
  166. data/lib/legate/tools/http_request_tool.rb +105 -0
  167. data/lib/legate/tools/random_number_tool.rb +64 -0
  168. data/lib/legate/tools/read_webpage_tool.rb +92 -0
  169. data/lib/legate/tools/sleepy_tool.rb +74 -0
  170. data/lib/legate/tools/webhook_tool.rb +146 -0
  171. data/lib/legate/version.rb +5 -0
  172. data/lib/legate/web/app.rb +984 -0
  173. data/lib/legate/web/public/css/main.css +4980 -0
  174. data/lib/legate/web/public/images/favicon-256.png +0 -0
  175. data/lib/legate/web/public/images/favicon-32.png +0 -0
  176. data/lib/legate/web/public/images/legate-logo-dark.png +0 -0
  177. data/lib/legate/web/public/images/legate-logo-light.png +0 -0
  178. data/lib/legate/web/public/js/legate.js +616 -0
  179. data/lib/legate/web/public/styles/main.scss +4402 -0
  180. data/lib/legate/web/routes/agent_authentication_routes.rb +530 -0
  181. data/lib/legate/web/routes/agent_definition_routes.rb +803 -0
  182. data/lib/legate/web/routes/agent_generator_routes.rb +80 -0
  183. data/lib/legate/web/routes/agent_interaction_routes.rb +734 -0
  184. data/lib/legate/web/routes/agent_runtime_routes.rb +323 -0
  185. data/lib/legate/web/routes/api_routes.rb +56 -0
  186. data/lib/legate/web/routes/authentication_routes.rb +1541 -0
  187. data/lib/legate/web/routes/core_routes.rb +111 -0
  188. data/lib/legate/web/routes/documentation_routes.rb +220 -0
  189. data/lib/legate/web/routes/tool_generator_routes.rb +81 -0
  190. data/lib/legate/web/routes/tools_ui_routes.rb +207 -0
  191. data/lib/legate/web/sass_compiler.rb +73 -0
  192. data/lib/legate/web/views/_active_session_info.slim +25 -0
  193. data/lib/legate/web/views/_activity_list.slim +55 -0
  194. data/lib/legate/web/views/_agent_card.slim +56 -0
  195. data/lib/legate/web/views/_agent_generator_modal.slim +382 -0
  196. data/lib/legate/web/views/_agent_status_controls.slim +71 -0
  197. data/lib/legate/web/views/_agent_tool_table.slim +74 -0
  198. data/lib/legate/web/views/_chat_message.slim +95 -0
  199. data/lib/legate/web/views/_display_agent_configuration.slim +26 -0
  200. data/lib/legate/web/views/_display_agent_description.slim +11 -0
  201. data/lib/legate/web/views/_display_agent_fallback.slim +15 -0
  202. data/lib/legate/web/views/_display_agent_hierarchy.slim +93 -0
  203. data/lib/legate/web/views/_display_agent_instruction.slim +17 -0
  204. data/lib/legate/web/views/_display_agent_mcp.slim +13 -0
  205. data/lib/legate/web/views/_display_agent_model.slim +17 -0
  206. data/lib/legate/web/views/_display_agent_name.slim +42 -0
  207. data/lib/legate/web/views/_display_agent_output_key.slim +26 -0
  208. data/lib/legate/web/views/_display_agent_type.slim +65 -0
  209. data/lib/legate/web/views/_edit_agent_configuration.slim +74 -0
  210. data/lib/legate/web/views/_edit_agent_description.slim +16 -0
  211. data/lib/legate/web/views/_edit_agent_fallback.slim +25 -0
  212. data/lib/legate/web/views/_edit_agent_hierarchy.slim +98 -0
  213. data/lib/legate/web/views/_edit_agent_instruction.slim +49 -0
  214. data/lib/legate/web/views/_edit_agent_mcp.slim +33 -0
  215. data/lib/legate/web/views/_edit_agent_model.slim +23 -0
  216. data/lib/legate/web/views/_edit_agent_output_key.slim +36 -0
  217. data/lib/legate/web/views/_edit_agent_tools.slim +40 -0
  218. data/lib/legate/web/views/_edit_agent_type.slim +67 -0
  219. data/lib/legate/web/views/_session_error.slim +4 -0
  220. data/lib/legate/web/views/_skeleton.slim +69 -0
  221. data/lib/legate/web/views/_tool_card.slim +9 -0
  222. data/lib/legate/web/views/_tool_generator_modal.slim +311 -0
  223. data/lib/legate/web/views/agent.slim +436 -0
  224. data/lib/legate/web/views/agent_auth.slim +562 -0
  225. data/lib/legate/web/views/agents.slim +369 -0
  226. data/lib/legate/web/views/auth.slim +112 -0
  227. data/lib/legate/web/views/auth_credential_detail.slim +327 -0
  228. data/lib/legate/web/views/auth_credentials.slim +261 -0
  229. data/lib/legate/web/views/auth_debug.slim +94 -0
  230. data/lib/legate/web/views/auth_mapping_detail.slim +151 -0
  231. data/lib/legate/web/views/auth_mapping_new.slim +123 -0
  232. data/lib/legate/web/views/auth_mappings.slim +120 -0
  233. data/lib/legate/web/views/auth_scheme_detail.slim +274 -0
  234. data/lib/legate/web/views/auth_schemes.slim +259 -0
  235. data/lib/legate/web/views/auth_test.slim +418 -0
  236. data/lib/legate/web/views/chat.slim +192 -0
  237. data/lib/legate/web/views/docs_index.slim +105 -0
  238. data/lib/legate/web/views/docs_show.slim +105 -0
  239. data/lib/legate/web/views/error_404.slim +5 -0
  240. data/lib/legate/web/views/index.slim +148 -0
  241. data/lib/legate/web/views/layout.slim +144 -0
  242. data/lib/legate/web/views/tool_detail.slim +87 -0
  243. data/lib/legate/web/views/tools.slim +50 -0
  244. data/lib/legate/web/webhook_listener.rb +367 -0
  245. data/lib/legate/web.rb +9 -0
  246. data/lib/legate.rb +220 -0
  247. data/public/docs/advanced/callbacks.md +828 -0
  248. data/public/docs/advanced/mcp_schema_conversion.md +59 -0
  249. data/public/docs/authentication/api_reference/config.md +210 -0
  250. data/public/docs/authentication/api_reference/credential.md +246 -0
  251. data/public/docs/authentication/api_reference/encryption.md +218 -0
  252. data/public/docs/authentication/api_reference/exchanged_credential.md +271 -0
  253. data/public/docs/authentication/api_reference/excon_middleware.md +175 -0
  254. data/public/docs/authentication/api_reference/index.md +30 -0
  255. data/public/docs/authentication/api_reference/scheme.md +250 -0
  256. data/public/docs/authentication/api_reference/schemes/api_key.md +175 -0
  257. data/public/docs/authentication/api_reference/schemes/google_service_account.md +221 -0
  258. data/public/docs/authentication/api_reference/schemes/http_bearer.md +169 -0
  259. data/public/docs/authentication/api_reference/schemes/oauth2.md +343 -0
  260. data/public/docs/authentication/api_reference/schemes/oidc.md +73 -0
  261. data/public/docs/authentication/api_reference/schemes/openid_connect.md +311 -0
  262. data/public/docs/authentication/api_reference/schemes/service_account.md +287 -0
  263. data/public/docs/authentication/api_reference/token_manager.md +221 -0
  264. data/public/docs/authentication/api_reference/token_store.md +146 -0
  265. data/public/docs/authentication/api_reference/tool_context_extension.md +166 -0
  266. data/public/docs/authentication/guides/api_key.md +190 -0
  267. data/public/docs/authentication/guides/bearer.md +172 -0
  268. data/public/docs/authentication/guides/configuration.md +255 -0
  269. data/public/docs/authentication/guides/custom_flow.md +523 -0
  270. data/public/docs/authentication/guides/index.md +24 -0
  271. data/public/docs/authentication/guides/migration.md +435 -0
  272. data/public/docs/authentication/guides/oauth2.md +252 -0
  273. data/public/docs/authentication/guides/oidc.md +241 -0
  274. data/public/docs/authentication/guides/overview.md +155 -0
  275. data/public/docs/authentication/guides/secure_storage.md +301 -0
  276. data/public/docs/authentication/guides/service_account.md +228 -0
  277. data/public/docs/authentication/guides/token_lifecycle.md +295 -0
  278. data/public/docs/authentication/guides/web_ui_integration.md +504 -0
  279. data/public/docs/authentication/index.md +58 -0
  280. data/public/docs/authentication/troubleshooting/credential_storage.md +550 -0
  281. data/public/docs/authentication/troubleshooting/environment_variables.md +540 -0
  282. data/public/docs/authentication/troubleshooting/index.md +11 -0
  283. data/public/docs/authentication/troubleshooting/oauth2_issues.md +220 -0
  284. data/public/docs/authentication/troubleshooting/oidc_issues.md +412 -0
  285. data/public/docs/authentication/troubleshooting/token_refresh.md +338 -0
  286. data/public/docs/cli/legate_cli_usage.md +363 -0
  287. data/public/docs/core_concepts/legate_agent_lifecycle.md +124 -0
  288. data/public/docs/core_concepts/legate_architecture_overview.md +110 -0
  289. data/public/docs/core_concepts/legate_configuration.md +116 -0
  290. data/public/docs/core_concepts/legate_definition_store.md +102 -0
  291. data/public/docs/core_concepts/legate_planner.md +94 -0
  292. data/public/docs/core_concepts/legate_session_service.md +104 -0
  293. data/public/docs/error_handling/legate_error_handling.md +122 -0
  294. data/public/docs/examples.md +199 -0
  295. data/public/docs/getting_started.md +111 -0
  296. data/public/docs/guides/agentic_agents.md +137 -0
  297. data/public/docs/guides/ai_code_generators.md +437 -0
  298. data/public/docs/guides/auto_loading.md +326 -0
  299. data/public/docs/guides/configuring_agent_webhooks.md +219 -0
  300. data/public/docs/guides/http_client_usage.md +264 -0
  301. data/public/docs/guides/llm_providers.md +137 -0
  302. data/public/docs/guides/mcp_client_integration.md +232 -0
  303. data/public/docs/guides/mcp_server_exposure.md +206 -0
  304. data/public/docs/guides/rails_integration.md +128 -0
  305. data/public/docs/guides/sending_outbound_webhooks.md +227 -0
  306. data/public/docs/guides/streaming.md +112 -0
  307. data/public/docs/guides/webhooks.md +288 -0
  308. data/public/docs/introduction.md +51 -0
  309. data/public/docs/multi_agent_systems/advanced_features.md +57 -0
  310. data/public/docs/multi_agent_systems/agent_delegation.md +190 -0
  311. data/public/docs/multi_agent_systems/agent_hierarchy.md +49 -0
  312. data/public/docs/multi_agent_systems/state_management.md +47 -0
  313. data/public/docs/multi_agent_systems/workflow_agents.md +72 -0
  314. data/public/docs/tools/legate_built_in_tools.md +332 -0
  315. data/public/docs/tools/legate_tools_and_registry.md +263 -0
  316. data/public/docs/web_ui/legate_web_ui.md +137 -0
  317. metadata +823 -0
@@ -0,0 +1,828 @@
1
+ # Legate Callbacks: Observe, Customize, and Control Agent Behavior
2
+
3
+ ## Introduction: What are Callbacks and Why Use Them?
4
+
5
+ Callbacks are a cornerstone feature of the Ruby Legate, providing a powerful mechanism to hook into an agent's execution process. They allow you to observe, customize, and even control the agent's behavior at specific, predefined points without modifying the core Legate framework code.
6
+
7
+ **What are they?** In essence, callbacks are standard Ruby Procs or lambdas that you define. You then associate these functions with an agent when you create its `Legate::AgentDefinition`. The Legate framework automatically calls your functions at key stages, letting you observe or intervene. Think of it like checkpoints during the agent's process:
8
+
9
+ * **Agent Lifecycle:**
10
+ * `before_agent_callback`: Executes right before the agent's main work begins for a specific `run_task` request.
11
+ * `after_agent_callback`: Executes right after the agent has finished all its steps for that request and has prepared the final result, but just before the result is returned from `run_task`.
12
+ * **Model Interaction (LLM):**
13
+ * `before_model_callback`: Runs just before a request is made to the Large Language Model (LLM) by the `Legate::Planner`.
14
+ * `after_model_callback`: Runs immediately after a response is received from the LLM by the `Legate::Planner`.
15
+ * **Tool Execution:**
16
+ * `before_tool_callback`: Called just before a specific tool's `execute` method is invoked by the agent.
17
+ * `after_tool_callback`: Called immediately after a tool's `execute` method successfully completes (before its result is further processed by the agent).
18
+
19
+ **Why use them?** Callbacks unlock significant flexibility and enable advanced agent capabilities:
20
+
21
+ * **Observe & Debug:** Log detailed information at critical steps for monitoring and troubleshooting.
22
+ * **Customize & Control:** Modify data flowing through the agent (like LLM requests or tool results) or even bypass certain steps entirely based on your logic.
23
+ * **Implement Guardrails:** Enforce safety rules, validate inputs/outputs, or prevent disallowed operations.
24
+ * **Manage State:** Read or dynamically update the agent's session state during execution via the provided context object.
25
+ * **Integrate & Enhance:** Trigger external actions (API calls, notifications) or add features like caching.
26
+
27
+ **How are they added?** You register callbacks by assigning Procs to the relevant attributes in the `Legate::AgentDefinition` block:
28
+
29
+ ```ruby
30
+ # --- Define your callback function ---
31
+ my_before_model_callback_proc = lambda do |callback_context, llm_request_params|
32
+ # callback_context is an instance of Legate::Callbacks::CallbackContext
33
+ # llm_request_params is a Hash representing what would be sent to the LLM
34
+ puts "[Callback] Before model call for agent: #{callback_context.agent_name}"
35
+ # ... your custom logic here ...
36
+
37
+ # Example: Modify the request
38
+ llm_request_params[:prompt] = "Prefix: " + llm_request_params[:prompt]
39
+
40
+ return nil # Allow the model call to proceed with (potentially modified) request
41
+ end
42
+
43
+ # --- Register it during AgentDefinition ---
44
+ Legate::Agent.define do |a|
45
+ a.name :my_callback_agent
46
+ a.model_name "gemini-2.0-flash"
47
+ a.instruction "Be helpful."
48
+ # ... other agent parameters ...
49
+
50
+ a.before_model_callback(&my_before_model_callback_proc)
51
+ end
52
+ ```
53
+
54
+ ## The Callback Mechanism: Interception and Control
55
+
56
+ When the Legate framework encounters a point where a callback can run (e.g., just before calling the LLM), it checks if you provided a corresponding callback Proc for that agent. If you did, the framework executes your Proc.
57
+
58
+ **Context is Key:** Your callback Proc isn't called in isolation. The framework provides special context objects (`Legate::Callbacks::CallbackContext` or `Legate::ToolContext`) as arguments. These objects contain vital information about the current state of the agent's execution, including the invocation details, session state, and access to services like the session service and logger.
59
+
60
+ **Controlling the Flow (The Core Mechanism):** The most powerful aspect of callbacks lies in how their return value influences the agent's subsequent actions.
61
+
62
+ * **`return nil` (or last expression evaluates to `nil`): Allow Default Behavior**
63
+ * This is the standard way to signal that your callback has finished its work (e.g., logging, inspection, minor modifications to mutable input arguments) and that the Legate agent should proceed with its normal operation.
64
+ * For `before_*` callbacks, returning `nil` means the next step in the sequence (running the agent logic, calling the LLM, executing the tool) will occur.
65
+ * For `after_*` callbacks, returning `nil` means the result just produced by the preceding step (the agent's output, the LLM's response, the tool's result) will be used as is.
66
+
67
+ * **`return <Specific Object>` (Override Default Behavior / Replace Result):**
68
+ * Returning a specific type of object (instead of `nil`) is how you override the Legate agent's default behavior or replace a result.
69
+ * **`before_agent_callback` &rarr; `Hash` (Agent Content):** Skips the agent's main `run_task` logic. The returned Hash is treated as the agent's final output content for this turn.
70
+ * **`before_model_callback` &rarr; `Hash` (Planner Plan):** Skips the call to the external Large Language Model by the `Legate::Planner`. The returned Hash (expected plan structure) is used as if it were the response from the LLM.
71
+ * **`before_tool_callback` &rarr; `Hash` (Tool Result):** Skips the execution of the actual tool's `execute` method. The returned Hash (standard tool result format, e.g., `{status: :success, result: ...}`) is used as the result of the tool call.
72
+ * **`after_agent_callback` &rarr; `Hash` (Agent Content):** Replaces the content Hash that the agent's `run_task` logic just produced.
73
+ * **`after_model_callback` &rarr; `Hash` (Planner Plan):** Replaces the plan Hash received from the LLM.
74
+ * **`after_tool_callback` &rarr; `Hash` (Tool Result):** Replaces the result Hash returned by the tool's `execute` method.
75
+
76
+ **State Management in Callbacks:**
77
+ Callbacks can read from and write to the session state using methods on the `callback_context` (an instance of `Legate::Callbacks::CallbackContext`) or `tool_context` (an instance of `Legate::ToolContext`). Key methods include:
78
+ * `state_get(key)`: Retrieves a value from the session state.
79
+ * `state_set(key, value)`: Sets a value in the pending state delta.
80
+ * `state_update(hash_to_merge)`: Merges a hash of key-value pairs into the pending state delta.
81
+
82
+ Changes made via `state_set` or `state_update` are collected in a `pending_state_delta` within the context object.
83
+ The Legate framework automatically merges this `pending_state_delta` into the `state_delta` of the *next relevant Legate::Event* that is logged to the session history. This ensures state changes are tied to specific points in the execution flow.
84
+
85
+ ## Types of Callbacks
86
+
87
+ ### 1. Agent Lifecycle Callbacks (`Legate::Agent`)
88
+
89
+ These callbacks hook into the overall execution of an agent's `run_task` method.
90
+
91
+ #### `before_agent_callback`
92
+
93
+ * **When:** Called immediately *before* the agent's main `run_task` logic begins (after session retrieval but before planning or tool execution for that task).
94
+ * **Signature:** `lambda { |callback_context| ... }`
95
+ * `callback_context` (`Legate::Callbacks::CallbackContext`): Provides agent name, invocation ID, session details, session service, logger, and state access methods.
96
+ * **Purpose:**
97
+ * Initial setup or validation for a specific task run.
98
+ * Logging entry into the agent's task processing.
99
+ * Access control: Decide if the agent should even process the current request.
100
+ * Modifying initial session state for the current invocation via `callback_context.state_set`.
101
+ * **Return Value Effect:**
102
+ * `nil`: Agent proceeds with its normal `run_task` logic (planning, execution).
103
+ * `Hash` (Agent Content, e.g., `{status: :success, result: "Handled by callback"}`): Agent's main logic for `run_task` is skipped. The returned Hash becomes the content of the final agent event for this turn.
104
+
105
+ ```ruby
106
+ # Example: before_agent_callback
107
+ Legate::Agent.define do |a|
108
+ a.name :my_guarded_agent
109
+ # ...
110
+ a.before_agent_callback do |context|
111
+ if context.state_get(:user_flagged)
112
+ puts "[Callback] User #{context.user_id} is flagged. Skipping agent run."
113
+ context.state_set(:agent_skipped_reason, "User flagged")
114
+ { status: :error, error_message: "Access denied for this request." } # Override
115
+ else
116
+ puts "[Callback] User #{context.user_id} is not flagged. Proceeding."
117
+ nil # Proceed
118
+ end
119
+ end
120
+ end
121
+ ```
122
+
123
+ #### `after_agent_callback`
124
+
125
+ * **When:** Called *after* the agent's `run_task` logic has fully completed and generated its final response content, but *before* that final `Legate::Event` is returned from `run_task`.
126
+ * **Signature:** `lambda { |callback_context, agent_response_content| ... }`
127
+ * `callback_context` (`Legate::Callbacks::CallbackContext`)
128
+ * `agent_response_content` (`Hash`): A mutable copy of the content hash that the agent is about to return (e.g., `{status: :success, result: ..., plan_details: ...}`).
129
+ * **Purpose:**
130
+ * Post-processing the agent's final response.
131
+ * Logging the outcome of the agent's task.
132
+ * Final state modifications based on the agent's overall result.
133
+ * Adding standard disclaimers or formatting to all agent outputs.
134
+ * **Return Value Effect:**
135
+ * `nil`: The `agent_response_content` (potentially modified in place by the callback) is used as the final agent event content.
136
+ * `Hash` (Agent Content): The returned Hash *replaces* the agent's original `agent_response_content`.
137
+
138
+ ```ruby
139
+ # Example: after_agent_callback
140
+ Legate::Agent.define do |a|
141
+ a.name :my_response_modifier_agent
142
+ # ...
143
+ a.after_agent_callback do |context, response_content|
144
+ puts "[Callback] Agent finished. Original response content: #{response_content.inspect}"
145
+ if response_content[:status] == :success
146
+ response_content[:result] = "[MODIFIED] #{response_content[:result]}"
147
+ context.state_set(:last_response_modified, true)
148
+ end
149
+ # If you return nil, the (modified) response_content is used.
150
+ # Or, you could return a completely new Hash:
151
+ # { status: :success, result: "New result from callback", original_was: response_content }
152
+ nil
153
+ end
154
+ end
155
+ ```
156
+
157
+ ### 2. Model Interaction Callbacks (`Legate::Planner` via `Legate::Agent`)
158
+
159
+ These callbacks hook into the `Legate::Planner`'s interaction with the LLM. The `Legate::Agent` orchestrates these callbacks around its call to the planner.
160
+
161
+ #### `before_model_callback`
162
+
163
+ * **When:** Called just *before* the `Legate::Planner` makes a request to the LLM (e.g., to generate a plan).
164
+ * **Signature:** `lambda { |callback_context, llm_request_params| ... }`
165
+ * `callback_context` (`Legate::Callbacks::CallbackContext`): Provides agent/session context.
166
+ * `llm_request_params` (`Hash`): A mutable hash representing the parameters that will be sent to the LLM (e.g., `{ prompt: "...", model_config: {...} }`). Modifications to this hash will affect the actual LLM call.
167
+ * **Purpose:**
168
+ * Inspect or modify the prompt/request being sent to the LLM.
169
+ * Implement input guardrails (e.g., block certain prompts).
170
+ * Inject dynamic information into the prompt from session state.
171
+ * Implement request-level caching (return a cached plan).
172
+ * **Return Value Effect:**
173
+ * `nil`: The planner proceeds to call the LLM with the (potentially modified) `llm_request_params`.
174
+ * `Hash` (Planner Plan, e.g., `{ steps: [...] }` or `{ error: "..." }`): The planner skips the actual LLM call and uses this returned Hash as if it were the LLM's response.
175
+
176
+ ```ruby
177
+ # Example: before_model_callback
178
+ Legate::Agent.define do |a|
179
+ a.name :my_prompt_injector_agent
180
+ # ...
181
+ a.before_model_callback do |context, request_params|
182
+ user_preference = context.state_get(:user_style_preference)
183
+ if user_preference
184
+ request_params[:prompt] += "\nStyle hint: #{user_preference}"
185
+ end
186
+
187
+ if request_params[:prompt].include?("forbidden topic")
188
+ puts "[Callback] Forbidden topic detected in prompt. Blocking LLM call."
189
+ { error: "Request blocked due to content policy." } # Override
190
+ else
191
+ nil # Proceed
192
+ end
193
+ end
194
+ end
195
+ ```
196
+
197
+ #### `after_model_callback`
198
+
199
+ * **When:** Called *after* the `Legate::Planner` receives a response from the LLM, but *before* the planner fully processes this response into its internal plan structure.
200
+ * **Signature:** `lambda { |callback_context, llm_response_data| ... }`
201
+ * `callback_context` (`Legate::Callbacks::CallbackContext`)
202
+ * `llm_response_data` (`Hash`): A mutable copy of the raw-ish response data from the LLM (e.g., the parsed JSON string that the planner will then interpret into steps).
203
+ * **Purpose:**
204
+ * Inspect or modify the LLM's raw response.
205
+ * Sanitize LLM output.
206
+ * Log LLM responses for analysis or fine-tuning.
207
+ * Parse structured data from the LLM output and store it in session state.
208
+ * **Return Value Effect:**
209
+ * `nil`: The (potentially modified in place) `llm_response_data` is used by the planner.
210
+ * `Hash` (Planner Plan): The returned Hash *replaces* the LLM's original response.
211
+
212
+ ```ruby
213
+ # Example: after_model_callback
214
+ Legate::Agent.define do |a|
215
+ a.name :my_llm_response_logger_agent
216
+ # ...
217
+ a.after_model_callback do |context, llm_response|
218
+ puts "[Callback] LLM Response received: #{llm_response.inspect}"
219
+ context.state_set(:last_llm_raw_output, llm_response)
220
+
221
+ if llm_response.is_a?(Hash) && llm_response.dig(:plan, 0, :tool_name) == :risky_tool
222
+ puts "[Callback] LLM planned risky_tool. Modifying plan to use safe_tool instead."
223
+ llm_response[:plan][:tool_name] = :safe_tool # Modify in place
224
+ end
225
+ nil # Use the (potentially modified) llm_response
226
+ end
227
+ end
228
+ ```
229
+
230
+ ### 3. Tool Execution Callbacks (`Legate::Agent`)
231
+
232
+ These callbacks hook into the agent's execution of individual tools.
233
+
234
+ #### `before_tool_callback`
235
+
236
+ * **When:** Called *before* a specific tool's `execute` method is invoked by the agent.
237
+ * **Signature:** `lambda { |tool_instance, tool_args, tool_context| ... }`
238
+ * `tool_instance` (`Legate::Tool`): The instance of the tool about to be executed.
239
+ * `tool_args` (`Hash`): A mutable hash of the arguments that will be passed to the tool.
240
+ * `tool_context` (`Legate::ToolContext`): Context specific to this tool execution, providing session details, access to the agent's tool registry, session service, logger, invocation ID, and state methods.
241
+ * **Purpose:**
242
+ * Inspect or modify tool arguments before execution.
243
+ * Implement tool-specific input validation or authorization.
244
+ * Log tool usage attempts.
245
+ * Implement tool-level caching (return a cached result).
246
+ * **Return Value Effect:**
247
+ * `nil`: The tool's `execute` method is called with the (potentially modified) `tool_args`.
248
+ * `Hash` (Tool Result, e.g. `{status: :success, result: ...}`): The tool's `execute` method is skipped. The returned Hash is used as the result of the tool call.
249
+
250
+ ```ruby
251
+ # Example: before_tool_callback
252
+ Legate::Agent.define do |a|
253
+ a.name :my_tool_caching_agent
254
+ # ...
255
+ a.before_tool_callback do |tool, args, context|
256
+ if tool.name == :expensive_api_call
257
+ cache_key = "cache:#{tool.name}:#{args.to_json}"
258
+ cached_result = context.state_get(cache_key)
259
+ if cached_result
260
+ puts "[Callback] Cache hit for #{tool.name}! Returning cached result."
261
+ return cached_result # Override: return cached result
262
+ end
263
+ end
264
+ args[:timestamp] = Time.now.iso8601 # Modify args
265
+ nil # Proceed with actual tool call
266
+ end
267
+ end
268
+ ```
269
+
270
+ #### `after_tool_callback`
271
+
272
+ * **When:** Called *after* a tool's `execute` method successfully completes and returns its result hash, but *before* this result is further processed or logged by the agent. It does *not* run if the tool's `execute` method itself raised an unhandled exception.
273
+ * **Signature:** `lambda { |tool_instance, tool_args, tool_context, tool_result| ... }`
274
+ * `tool_instance` (`Legate::Tool`)
275
+ * `tool_args` (`Hash`): The (potentially modified by `before_tool_callback`) arguments that were passed to the tool.
276
+ * `tool_context` (`Legate::ToolContext`)
277
+ * `tool_result` (`Hash`): A mutable copy of the result hash returned by the tool's `execute` method.
278
+ * **Purpose:**
279
+ * Inspect or modify the tool's result.
280
+ * Log tool execution outcomes.
281
+ * Post-process or format tool results.
282
+ * Save tool results to a cache or session state.
283
+ * **Return Value Effect:**
284
+ * `nil`: The (potentially modified in place) `tool_result` is used by the agent.
285
+ * `Hash` (Tool Result): The returned Hash *replaces* the tool's original `tool_result`.
286
+
287
+ ```ruby
288
+ # Example: after_tool_callback
289
+ Legate::Agent.define do |a|
290
+ a.name :my_tool_result_processor_agent
291
+ # ...
292
+ a.after_tool_callback do |tool, args, context, result|
293
+ puts "[Callback] Tool #{tool.name} executed with #{args.inspect}. Result: #{result.inspect}"
294
+ if tool.name == :expensive_api_call && result[:status] == :success
295
+ cache_key = "cache:#{tool.name}:#{args.to_json}"
296
+ context.state_set(cache_key, result.dup) # Save a copy to state for caching
297
+ puts "[Callback] Saved result for #{tool.name} to cache."
298
+ end
299
+
300
+ if result[:status] == :success && result[:result].is_a?(String)
301
+ result[:result] = result[:result].upcase # Modify result in place
302
+ end
303
+ nil # Use the modified result
304
+ end
305
+ end
306
+ ```
307
+
308
+ ## Design Patterns and Best Practices for Callbacks
309
+
310
+ By understanding this callback mechanism, you can precisely control the agent's execution path, making callbacks an essential tool for building sophisticated and reliable agents with the Ruby Legate.
311
+
312
+ Callbacks are versatile. Here are some common patterns and best practices to make the most of them in your Legate agents:
313
+
314
+ ### 1. Comprehensive Logging and Auditing
315
+
316
+ Use callbacks to gain deep insights into your agent's behavior by logging detailed information at each step.
317
+
318
+ * **`before_agent_callback`**: Log the initial request and any relevant session state.
319
+ * **`before_model_callback`**: Log the exact prompt being sent to the LLM.
320
+ * **`after_model_callback`**: Log the raw response from the LLM.
321
+ * **`before_tool_callback`**: Log the tool name and arguments before execution.
322
+ * **`after_tool_callback`**: Log the result (or error) from a tool.
323
+ * **`after_agent_callback`**: Log the final response being sent to the user and any significant state changes.
324
+
325
+ ```mermaid
326
+ sequenceDiagram
327
+ participant AgentLogic as Agent's Main Logic
328
+ participant BeforeModelCB as before_model_callback
329
+ participant LoggerService as Legate Logger
330
+ participant LLMPlanner as Planner
331
+
332
+ AgentLogic->>BeforeModelCB: Invoke(context, llm_request_params)
333
+ BeforeModelCB->>LoggerService: context.logger.info("Sending to LLM...")
334
+ BeforeModelCB->>LoggerService: context.logger.debug("Full LLM Request...")
335
+ BeforeModelCB-->>AgentLogic: Return nil (proceed)
336
+ AgentLogic->>LLMPlanner: plan(user_input)
337
+ LLMPlanner-->>AgentLogic: llm_response_data
338
+ Note over AgentLogic: Similar logging can be done in other callbacks (after_model, before_tool, etc.)
339
+ ```
340
+
341
+ **Code Example:**
342
+ ```ruby
343
+ # Example: Detailed Logging
344
+ Legate::Agent.define do |a|
345
+ a.name :audit_trail_agent
346
+ a.instruction "I am an agent that logs everything."
347
+ a.model_name "gemini-pro" # Or your preferred model
348
+
349
+ a.before_agent_callback do |context|
350
+ context.logger.info "[Audit] Agent '#{context.agent_name}' starting task. Invocation: #{context.invocation_id}, Session: #{context.session_id}"
351
+ # Log initial important state if any
352
+ important_state = context.state_get(:user_preference)
353
+ context.logger.debug "[Audit] Initial user_preference: #{important_state}" if important_state
354
+ nil
355
+ end
356
+
357
+ a.before_model_callback do |context, llm_request_params|
358
+ context.logger.info "[Audit] Sending to LLM. Prompt snippet: #{llm_request_params[:prompt][0..100]}..."
359
+ context.logger.debug "[Audit] Full LLM Request: #{llm_request_params.inspect}"
360
+ nil
361
+ end
362
+
363
+ # ... (other logging callbacks as shown previously) ...
364
+ end
365
+ ```
366
+
367
+ ### 2. Input Validation and Sanitization (Guardrails)
368
+
369
+ Ensure data integrity and enforce policies by validating inputs and sanitizing outputs.
370
+
371
+ * **`before_model_callback`**: Check the prompt for policy violations. Return an error plan to prevent the LLM call, or sanitize the prompt.
372
+
373
+ ```mermaid
374
+ sequenceDiagram
375
+ participant AgentLogic as Agent's Main Logic
376
+ participant BeforeModelCB as before_model_callback
377
+ participant LLMPlanner as Planner
378
+ participant ExternalLLM as External LLM Service
379
+
380
+ AgentLogic->>BeforeModelCB: Invoke(context, llm_request_params)
381
+ alt Prompt is VALID and/or SANITIZED
382
+ BeforeModelCB->>BeforeModelCB: Sanitize(llm_request_params)
383
+ BeforeModelCB-->>AgentLogic: Return nil
384
+ AgentLogic->>LLMPlanner: plan(sanitized_request_params)
385
+ LLMPlanner->>ExternalLLM: Call LLM
386
+ ExternalLLM-->>LLMPlanner: LLM Response
387
+ LLMPlanner-->>AgentLogic: llm_response_data
388
+ else Prompt is INVALID (e.g., forbidden keyword)
389
+ BeforeModelCB-->>AgentLogic: Return error_plan (Hash)
390
+ Note over AgentLogic: LLM Call is SKIPPED
391
+ AgentLogic-->>User: Return error based on error_plan
392
+ end
393
+ ```
394
+
395
+ **Code Example:**
396
+ ```ruby
397
+ # Example: Input Guardrails
398
+ Legate::Agent.define do |a|
399
+ a.name :safe_query_agent
400
+ a.instruction "I will only process safe queries."
401
+ a.model_name "gemini-pro"
402
+
403
+ FORBIDDEN_KEYWORDS = [/unsafe_action/i, /dangerous_command/i].freeze
404
+
405
+ a.before_model_callback do |context, llm_request_params|
406
+ prompt_text = llm_request_params[:prompt]
407
+ if FORBIDDEN_KEYWORDS.any? { |keyword| prompt_text.match?(keyword) }
408
+ context.logger.warn "[Guardrail] Forbidden keyword detected. Blocking LLM call."
409
+ { error: "Request blocked due to content policy." } # Override
410
+ else
411
+ llm_request_params[:prompt] = prompt_text.gsub(/email:\s*\S+@\S+\.\S+/, "email: [REDACTED]")
412
+ nil # Proceed
413
+ end
414
+ end
415
+ # ... (other guardrail callbacks as shown previously) ...
416
+ end
417
+ ```
418
+
419
+ ### 3. Caching Strategies
420
+
421
+ Improve performance and reduce costs by caching results from LLM calls or expensive tool executions.
422
+
423
+ * **`before_model_callback`**: Check cache. If hit, return cached plan.
424
+ * **`after_model_callback`**: If cache miss, store LLM response in cache.
425
+
426
+ ```mermaid
427
+ sequenceDiagram
428
+ participant AgentLogic as Agent's Main Logic
429
+ participant BeforeModelCB as before_model_callback
430
+ participant CacheService as Cache (e.g., Session State)
431
+ participant LLMPlanner as Planner
432
+ participant ExternalLLM as External LLM Service
433
+ participant AfterModelCB as after_model_callback
434
+
435
+ AgentLogic->>BeforeModelCB: Invoke(context, llm_request_params)
436
+ BeforeModelCB->>CacheService: state_get(cache_key)
437
+ alt Cache Hit
438
+ CacheService-->>BeforeModelCB: Return cached_plan
439
+ BeforeModelCB-->>AgentLogic: Return cached_plan (Hash)
440
+ Note over AgentLogic: LLM Call is SKIPPED
441
+ else Cache Miss
442
+ CacheService-->>BeforeModelCB: Return nil (not found)
443
+ BeforeModelCB-->>AgentLogic: Return nil (proceed to LLM)
444
+ AgentLogic->>LLMPlanner: plan(llm_request_params)
445
+ LLMPlanner->>ExternalLLM: Call LLM
446
+ ExternalLLM-->>LLMPlanner: llm_response
447
+ LLMPlanner-->>AgentLogic: llm_response_data
448
+ AgentLogic->>AfterModelCB: Invoke(context, llm_response_data)
449
+ AfterModelCB->>CacheService: state_set(cache_key, llm_response_data)
450
+ AfterModelCB-->>AgentLogic: Return nil
451
+ end
452
+ AgentLogic-->>User: Process result (from cache or LLM)
453
+ ```
454
+
455
+ **Code Example:**
456
+ ```ruby
457
+ # Example: Caching LLM Responses (Simplified)
458
+ require 'digest'
459
+ Legate::Agent.define do |a|
460
+ a.name :caching_agent
461
+ a.instruction "I try to be efficient by caching."
462
+ a.model_name "gemini-pro"
463
+
464
+ a.before_model_callback do |context, llm_request_params|
465
+ cache_key = "llm_cache:#{Digest::SHA256.hexdigest(llm_request_params[:prompt])}"
466
+ cached_plan = context.state_get(cache_key)
467
+ if cached_plan
468
+ context.logger.info "[Cache] LLM cache hit."
469
+ return cached_plan # Override
470
+ else
471
+ context.logger.info "[Cache] LLM cache miss."
472
+ context.instance_variable_set(:@current_cache_key, cache_key) # Store for after_model_callback
473
+ nil # Proceed
474
+ end
475
+ end
476
+
477
+ a.after_model_callback do |context, llm_response_data|
478
+ cache_key = context.instance_variable_get(:@current_cache_key)
479
+ if cache_key && llm_response_data.is_a?(Hash) && llm_response_data.key?(:steps)
480
+ context.state_set(cache_key, llm_response_data.dup)
481
+ context.logger.info "[Cache] Stored LLM response in cache."
482
+ end
483
+ nil
484
+ end
485
+ end
486
+ ```
487
+ *Note: Robust caching requires careful key generation and consideration of cache eviction policies.*
488
+
489
+ ### 4. Dynamic Prompt Engineering
490
+
491
+ Modify prompts on the fly based on session state, user history, or other dynamic factors.
492
+
493
+ * **`before_model_callback`**: Inject dynamic information into the prompt.
494
+
495
+ ```mermaid
496
+ sequenceDiagram
497
+ participant AgentLogic as Agent's Main Logic
498
+ participant BeforeModelCB as before_model_callback
499
+ participant SessionState as Session State Access
500
+ participant LLMPlanner as Planner
501
+
502
+ AgentLogic->>BeforeModelCB: Invoke(context, llm_request_params)
503
+ BeforeModelCB->>SessionState: context.state_get(:user_preference)
504
+ SessionState-->>BeforeModelCB: preference_value
505
+ Note over BeforeModelCB: Augment llm_request_params[:prompt] += preference_value
506
+ BeforeModelCB-->>AgentLogic: Return nil (with modified params)
507
+ AgentLogic->>LLMPlanner: plan(modified_request_params)
508
+ LLMPlanner-->>AgentLogic: llm_response_data
509
+ ```
510
+
511
+ **Code Example:**
512
+ ```ruby
513
+ # Example: Dynamic Prompt Injection
514
+ Legate::Agent.define do |a|
515
+ a.name :personalized_agent
516
+ a.instruction "I tailor my responses." # Base instruction
517
+ a.model_name "gemini-pro"
518
+
519
+ a.before_model_callback do |context, llm_request_params|
520
+ user_tone = context.state_get(:user_tone)
521
+ if user_tone && llm_request_params[:prompt].is_a?(String)
522
+ llm_request_params[:prompt] += "\n\nPlease respond in a #{user_tone} tone."
523
+ context.logger.info "[Personalize] Augmented prompt with tone."
524
+ end
525
+ nil
526
+ end
527
+ end
528
+ ```
529
+
530
+ ### 5. Conditional Execution & Overrides
531
+
532
+ Control the agent's workflow by conditionally skipping steps or providing alternative results.
533
+
534
+ * **`before_model_callback`**: If query matches FAQ, return a pre-canned plan.
535
+
536
+ ```mermaid
537
+ sequenceDiagram
538
+ participant AgentLogic as Agent's Main Logic
539
+ participant BeforeModelCB as before_model_callback
540
+ participant FAQData as Internal FAQ Data
541
+ participant LLMPlanner as Planner
542
+
543
+ AgentLogic->>BeforeModelCB: Invoke(context, llm_request_params)
544
+ BeforeModelCB->>FAQData: Check if llm_request_params[:prompt] matches FAQ
545
+ alt FAQ Match
546
+ FAQData-->>BeforeModelCB: Return FAQ answer/plan
547
+ BeforeModelCB-->>AgentLogic: Return faq_plan_hash (Override)
548
+ Note over AgentLogic: LLM Call is SKIPPED
549
+ else No FAQ Match
550
+ FAQData-->>BeforeModelCB: Not found
551
+ BeforeModelCB-->>AgentLogic: Return nil (proceed to LLM)
552
+ AgentLogic->>LLMPlanner: plan(llm_request_params)
553
+ LLMPlanner-->>AgentLogic: llm_response_data
554
+ end
555
+ AgentLogic-->>User: Process result
556
+ ```
557
+
558
+ **Code Example:**
559
+ ```ruby
560
+ # Example: Conditional Skip of LLM for FAQ
561
+ Legate::Agent.define do |a|
562
+ a.name :faq_bot
563
+ a.instruction "I answer FAQs."
564
+ a.model_name "gemini-pro"
565
+
566
+ FAQ = { "what is your name?" => "My name is FAQ Bot." }.freeze
567
+
568
+ a.before_model_callback do |context, llm_request_params|
569
+ query = llm_request_params[:prompt].to_s.downcase.strip
570
+ if FAQ.key?(query)
571
+ context.logger.info "[FAQ] Matched FAQ."
572
+ { thought_process: "FAQ match.", steps: [{ type: :agent_response, tool_name: :echo, tool_input: { message: FAQ[query] } }] } # Override
573
+ else
574
+ nil # Proceed to LLM
575
+ end
576
+ end
577
+ end
578
+ ```
579
+
580
+ ### 6. Advanced State Management Techniques
581
+
582
+ Use callbacks to manage complex state transitions or share information.
583
+
584
+ * **`after_tool_callback`**: Parse complex tool result and store structured data into session state.
585
+
586
+ ```mermaid
587
+ sequenceDiagram
588
+ participant AgentLogic as Agent's Main Logic
589
+ participant ToolInstance as Specific Tool
590
+ participant AfterToolCB as after_tool_callback
591
+ participant SessionState as Session State Access
592
+
593
+ AgentLogic->>ToolInstance: execute(args, tool_context)
594
+ ToolInstance-->>AgentLogic: tool_result_hash
595
+ AgentLogic->>AfterToolCB: Invoke(tool, args, context, tool_result_hash)
596
+ Note over AfterToolCB: Parse tool_result_hash
597
+ AfterToolCB->>SessionState: context.state_set(:parsed_data_A, valueA)
598
+ AfterToolCB->>SessionState: context.state_set(:parsed_data_B, valueB)
599
+ AfterToolCB-->>AgentLogic: Return nil (use original/modified tool_result)
600
+ AgentLogic-->>User: Continues processing
601
+ ```
602
+
603
+ **Code Example:**
604
+ ```ruby
605
+ # Example: Storing Parsed Tool Output
606
+ require 'json'
607
+ Legate::Agent.define do |a|
608
+ a.name :data_parser_agent
609
+ a.use_tool :fetch_user_profile # Assumed to return JSON string
610
+
611
+ a.after_tool_callback do |tool, _args, context, tool_result|
612
+ if tool.name == :fetch_user_profile && tool_result[:status] == :success
613
+ begin
614
+ data = JSON.parse(tool_result[:result])
615
+ context.state_set(:user_name_from_profile, data['name'])
616
+ context.state_set(:user_email_from_profile, data['email'])
617
+ context.logger.info "[State] Parsed and stored profile data."
618
+ rescue JSON::ParserError => e
619
+ context.logger.error "[State] Failed to parse profile JSON: #{e.message}"
620
+ end
621
+ end
622
+ nil
623
+ end
624
+ end
625
+ ```
626
+
627
+ ### 7. Integrating External Services
628
+
629
+ Trigger external API calls or notifications at appropriate points.
630
+
631
+ * **`after_agent_callback`**: Send a notification with the task outcome.
632
+
633
+ ```mermaid
634
+ sequenceDiagram
635
+ participant AgentLogic as Agent's Main Logic
636
+ participant AfterAgentCB as after_agent_callback
637
+ participant ExternalNotificationService as External Service (e.g., Email/Slack API)
638
+
639
+ AgentLogic-->>User: Agent completes task, prepares final_response_content
640
+ AgentLogic->>AfterAgentCB: Invoke(context, final_response_content)
641
+ Note over AfterAgentCB: Logic to determine notification content
642
+ AfterAgentCB->>ExternalNotificationService: SendNotification(details from final_response_content)
643
+ ExternalNotificationService-->>AfterAgentCB: Ack/Response (optional)
644
+ AfterAgentCB-->>AgentLogic: Return nil (use original final_response_content)
645
+ AgentLogic-->>User: Returns final response to user
646
+ ```
647
+
648
+ **Code Example:**
649
+ ```ruby
650
+ # Example: Notification on Task Completion (Conceptual)
651
+ # Assume NotificationService.send(...) exists
652
+ Legate::Agent.define do |a|
653
+ a.name :notifier_agent
654
+ # ...
655
+ a.after_agent_callback do |context, agent_response_content|
656
+ subject = if agent_response_content[:status] == :error
657
+ "Agent Task Failed: #{context.agent_name}"
658
+ else
659
+ "Agent Task Completed: #{context.agent_name}"
660
+ end
661
+ # Conceptual call
662
+ # NotificationService.send(to: 'ops@example.com', subject: subject, body: agent_response_content.inspect)
663
+ context.logger.info "[Notification] Would send notification: #{subject}"
664
+ nil
665
+ end
666
+ end
667
+ ```
668
+
669
+ By combining these patterns, you can build highly customized, robust, and observable agents using the Legate callback system. Remember to keep callbacks focused on their specific stage and to handle errors gracefully within your callback logic.
670
+
671
+ ## Callbacks: Visualizing the Flow
672
+
673
+ This section provides Mermaid sequence diagrams to visualize how different callbacks intercept and potentially alter the typical execution flow of an Legate Agent interacting with an LLM (via its Planner).
674
+
675
+ ### Scenario 1: `before_agent_callback` Skips Agent Execution
676
+
677
+ This diagram shows a `before_agent_callback` determining that the agent's main logic should be skipped, and the callback itself provides the final response.
678
+
679
+ ```mermaid
680
+ sequenceDiagram
681
+ participant User
682
+ participant AgentRunner as Legate Runner/Framework
683
+ participant BeforeAgentCB as before_agent_callback
684
+ participant AgentLogic as Agent's Main Run Logic
685
+ participant LLMPlanner as Planner (LLM Interaction)
686
+ participant FinalEvent as Final Agent Event
687
+
688
+ User->>AgentRunner: run_task(input)
689
+ AgentRunner->>BeforeAgentCB: Invoke(callback_context)
690
+ Note over BeforeAgentCB: Logic decides to skip agent
691
+ BeforeAgentCB-->>AgentRunner: Return override_content (Hash)
692
+ Note over AgentRunner: Agent's main logic (AgentLogic, LLMPlanner) is SKIPPED
693
+ AgentRunner->>FinalEvent: CreateEvent(override_content)
694
+ FinalEvent-->>User: Return Final Response (from callback)
695
+ ```
696
+
697
+ **Explanation:**
698
+
699
+ 1. The `User` initiates a task.
700
+ 2. The `Legate Runner/Framework` calls the `before_agent_callback`.
701
+ 3. The callback's internal logic decides the main agent execution isn't needed (e.g., access denied, simple known answer).
702
+ 4. It returns an `override_content` (a Hash representing the agent's response).
703
+ 5. The framework bypasses the `Agent's Main Run Logic` and any calls to the `Planner (LLM Interaction)`.
704
+ 6. A `Final Agent Event` is created directly from the callback's `override_content` and returned to the `User`.
705
+
706
+ ---
707
+
708
+ ### Scenario 2: `before_model_callback` Skips LLM Call (e.g., Cache Hit)
709
+
710
+ Here, the `before_model_callback` intercepts the request to the LLM, finds a cached response, and returns it, preventing the actual LLM call.
711
+
712
+ ```mermaid
713
+ sequenceDiagram
714
+ participant User
715
+ participant AgentRunner as Legate Runner/Framework
716
+ participant AgentLogic as Agent's Main Run Logic
717
+ participant BeforeModelCB as before_model_callback
718
+ participant LLMPlanner as Planner (LLM Interaction)
719
+ participant ActualLLM as External LLM Service
720
+ participant AfterModelCB as after_model_callback
721
+ participant FinalEvent as Final Agent Event
722
+
723
+ User->>AgentRunner: run_task(input)
724
+ AgentRunner->>AgentLogic: Start run_task_impl
725
+ AgentLogic->>BeforeModelCB: Invoke(callback_context, llm_request_params)
726
+ Note over BeforeModelCB: Cache hit found!
727
+ BeforeModelCB-->>AgentLogic: Return cached_plan_hash
728
+ Note over AgentLogic: Actual LLM call is SKIPPED
729
+ AgentLogic->>AfterModelCB: Invoke(callback_context, cached_plan_hash)
730
+ Note over AfterModelCB: Logic might log cache usage
731
+ AfterModelCB-->>AgentLogic: Return nil (use cached_plan_hash)
732
+ AgentLogic-->>AgentRunner: Process plan (from cache)
733
+ AgentRunner->>FinalEvent: CreateEvent(agent_result)
734
+ FinalEvent-->>User: Return Final Response
735
+ ```
736
+
737
+ **Explanation:**
738
+
739
+ 1. The agent's main logic decides it needs to consult the LLM.
740
+ 2. Before calling the `LLMPlanner` (which would call the `External LLM Service`), the `before_model_callback` is invoked.
741
+ 3. The callback finds a suitable pre-existing plan/response (e.g., from a cache).
742
+ 4. It returns this `cached_plan_hash`.
743
+ 5. The `AgentLogic` uses this cached plan, skipping the call to the `LLMPlanner` and the `External LLM Service`.
744
+ 6. The `after_model_callback` is still called with the `cached_plan_hash`. It might log that a cache was used or perform other actions, then returns `nil` to indicate the cached plan should be used.
745
+ 7. The agent proceeds with the cached plan.
746
+
747
+ ---
748
+
749
+ ### Scenario 3: `after_model_callback` Modifies LLM Response
750
+
751
+ This diagram shows the `after_model_callback` inspecting and altering the response received from the LLM before the agent uses it.
752
+
753
+ ```mermaid
754
+ sequenceDiagram
755
+ participant User
756
+ participant AgentRunner as Legate Runner/Framework
757
+ participant AgentLogic as Agent's Main Run Logic
758
+ participant BeforeModelCB as before_model_callback
759
+ participant LLMPlanner as Planner (LLM Interaction)
760
+ participant ActualLLM as External LLM Service
761
+ participant AfterModelCB as after_model_callback
762
+ participant FinalEvent as Final Agent Event
763
+
764
+ User->>AgentRunner: run_task(input)
765
+ AgentRunner->>AgentLogic: Start run_task_impl
766
+ AgentLogic->>BeforeModelCB: Invoke(callback_context, llm_request_params)
767
+ BeforeModelCB-->>AgentLogic: Return nil (proceed with LLM call)
768
+ AgentLogic->>LLMPlanner: plan(user_input)
769
+ LLMPlanner->>ActualLLM: Generate Content Request
770
+ ActualLLM-->>LLMPlanner: Raw LLM Response (plan_hash)
771
+ LLMPlanner-->>AgentLogic: Return original_plan_hash
772
+ AgentLogic->>AfterModelCB: Invoke(callback_context, original_plan_hash)
773
+ Note over AfterModelCB: Modifies the plan/response
774
+ AfterModelCB-->>AgentLogic: Return modified_plan_hash
775
+ AgentLogic-->>AgentRunner: Process modified_plan_hash
776
+ AgentRunner->>FinalEvent: CreateEvent(agent_result)
777
+ FinalEvent-->>User: Return Final Response
778
+ ```
779
+
780
+ **Explanation:**
781
+
782
+ 1. The `before_model_callback` allows the LLM call to proceed (returns `nil`).
783
+ 2. The `LLMPlanner` calls the `External LLM Service` and gets a response (e.g., a plan).
784
+ 3. The `AgentLogic` receives this `original_plan_hash`.
785
+ 4. The `after_model_callback` is invoked with the `original_plan_hash`.
786
+ 5. The callback modifies the plan (e.g., adds a disclaimer, filters content, changes a tool call).
787
+ 6. It returns the `modified_plan_hash`.
788
+ 7. The `AgentLogic` uses this `modified_plan_hash` for subsequent steps.
789
+
790
+ ---
791
+
792
+ ### Scenario 4: Tool Callbacks with `before_tool_callback` Skipping Execution
793
+
794
+ This shows a `before_tool_callback` preventing a tool from actually running, perhaps due to invalid arguments or a cache hit.
795
+
796
+ ```mermaid
797
+ sequenceDiagram
798
+ participant AgentLogic as Agent's Main Run Logic
799
+ participant BeforeToolCB as before_tool_callback
800
+ participant ActualTool as Specific Tool (e.g., Calculator)
801
+ participant AfterToolCB as after_tool_callback
802
+ participant ToolResultEvent as Tool Result Event
803
+
804
+ Note over AgentLogic: Plan indicates calling 'Calculator'
805
+ AgentLogic->>BeforeToolCB: Invoke(tool_instance, tool_args, tool_context)
806
+ Note over BeforeToolCB: Validation fails or cache hit
807
+ BeforeToolCB-->>AgentLogic: Return override_tool_result (Hash)
808
+ Note over AgentLogic: ActualTool.execute() is SKIPPED
809
+ AgentLogic->>AfterToolCB: Invoke(tool_instance, tool_args, tool_context, override_tool_result)
810
+ Note over AfterToolCB: Logic might log, but uses override
811
+ AfterToolCB-->>AgentLogic: Return nil (use override_tool_result)
812
+ AgentLogic->>ToolResultEvent: CreateEvent(override_tool_result)
813
+ ToolResultEvent-->>AgentLogic: Logged/Processed
814
+ ```
815
+
816
+ **Explanation:**
817
+
818
+ 1. The `AgentLogic` determines it needs to call a specific tool (e.g., "Calculator").
819
+ 2. The `before_tool_callback` is invoked with the tool instance, arguments, and context.
820
+ 3. The callback decides the tool shouldn't run (e.g., arguments are invalid, or it has a cached result).
821
+ 4. It returns an `override_tool_result` (a Hash in the standard tool result format).
822
+ 5. The `ActualTool.execute()` method is **not** called.
823
+ 6. The `after_tool_callback` is still invoked, but it receives the `override_tool_result`. It might log this or perform other actions, then likely returns `nil` to indicate the override should be used.
824
+ 7. The `AgentLogic` processes the `override_tool_result` as if it came from the actual tool.
825
+
826
+ ---
827
+
828
+ This documentation should provide a solid starting point for users to understand and leverage the callback system you're planning. Remember to update the conceptual `CallbackContext` and `ToolContext` definitions in the examples once their final Ruby implementation is in place.