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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +345 -0
- data/bin/legate +13 -0
- data/examples/00_quickstart.rb +51 -0
- data/examples/01_simple_agent.rb +105 -0
- data/examples/02_multi_tool_agent.rb +140 -0
- data/examples/03_custom_tool.rb +93 -0
- data/examples/04_agent_instructions.rb +84 -0
- data/examples/05_state_and_sessions.rb +91 -0
- data/examples/06_callbacks.rb +186 -0
- data/examples/07_async_jobs.rb +112 -0
- data/examples/08_loop_agent.rb +197 -0
- data/examples/09_sequential_workflow.rb +40 -0
- data/examples/10_parallel_workflow.rb +34 -0
- data/examples/11_agent_delegation.rb +24 -0
- data/examples/12_http_client_tool.rb +156 -0
- data/examples/13_authentication.rb +220 -0
- data/examples/14_mcp_client.rb +154 -0
- data/examples/15_mcp_server.rb +79 -0
- data/examples/16_webhooks.rb +91 -0
- data/examples/README_sequential_agents.md +164 -0
- data/examples/advanced/auth/cookie_auth_tool.rb +146 -0
- data/examples/advanced/auth/custom_auth_flows_example.rb +626 -0
- data/examples/advanced/auth/excon_middleware.rb +317 -0
- data/examples/advanced/auth/excon_middleware_auth.rb +399 -0
- data/examples/advanced/auth/fiber_auth_example.rb +281 -0
- data/examples/advanced/auth/fiber_oidc_example.rb +403 -0
- data/examples/advanced/auth/httpbin_bearer_tool.rb +159 -0
- data/examples/advanced/auth/oauth2_auth.rb +419 -0
- data/examples/advanced/auth/oidc_auth.rb +514 -0
- data/examples/advanced/auth/openweather_api.rb +251 -0
- data/examples/advanced/auth/openweather_tool.rb +153 -0
- data/examples/advanced/auth/query_param_middleware_test.rb +138 -0
- data/examples/advanced/auth/service_account.rb +135 -0
- data/examples/advanced/auth/test_with_httpbin.rb +202 -0
- data/examples/advanced/auth/token_lifecycle_example.rb +428 -0
- data/examples/advanced/callback_monitoring.rb +679 -0
- data/examples/advanced/mas/fixed_delegation_example.rb +191 -0
- data/examples/advanced/mas/loop_workflow.rb +28 -0
- data/examples/advanced/mas/mock_planner.rb +77 -0
- data/examples/advanced/mas/proper_delegation_example.rb +276 -0
- data/examples/advanced/mcp/legate_mcp_server_resource_example.rb +182 -0
- data/examples/advanced/mcp/mcp_resource_server_example.rb +309 -0
- data/examples/advanced/mcp/mcp_server_async.rb +76 -0
- data/examples/advanced/mcp/mcp_server_async_tools.rb +122 -0
- data/examples/advanced/mcp/mcp_server_legate_agent.rb +95 -0
- data/examples/advanced/mcp/mcp_server_rack.rb +89 -0
- data/examples/advanced/random_calculator.rb +104 -0
- data/examples/advanced/sleep_agent.rb +153 -0
- data/examples/advanced/webhooks/webhook_e2e_runner.rb +110 -0
- data/examples/advanced/webhooks/webhook_receiver_agent.rb +58 -0
- data/examples/advanced/workflows/task_refinement_loop_agent.rb +278 -0
- data/examples/advanced/workflows/travel_planner_auto_sequential.rb +444 -0
- data/examples/advanced/workflows/travel_planner_parallel.rb +656 -0
- data/examples/advanced/workflows/travel_planner_sequential.rb +512 -0
- data/examples/tools/oauth2_example.rb +136 -0
- data/examples/tools/sleepy_tool.rb +42 -0
- data/lib/legate/activity_log.rb +71 -0
- data/lib/legate/agent.rb +959 -0
- data/lib/legate/agent_code_generator.rb +185 -0
- data/lib/legate/agent_definition.rb +812 -0
- data/lib/legate/agentic/decision.rb +49 -0
- data/lib/legate/agentic/loop.rb +134 -0
- data/lib/legate/agentic.rb +5 -0
- data/lib/legate/agents/loop_agent.rb +248 -0
- data/lib/legate/agents/parallel_agent.rb +163 -0
- data/lib/legate/agents/sequential_agent.rb +190 -0
- data/lib/legate/agents.rb +14 -0
- data/lib/legate/auth/config.rb +148 -0
- data/lib/legate/auth/coordinator.rb +218 -0
- data/lib/legate/auth/coordinators/oauth2_coordinator.rb +99 -0
- data/lib/legate/auth/coordinators/oidc_coordinator.rb +68 -0
- data/lib/legate/auth/coordinators/service_account_coordinator.rb +122 -0
- data/lib/legate/auth/credential.rb +157 -0
- data/lib/legate/auth/encryption.rb +108 -0
- data/lib/legate/auth/error.rb +94 -0
- data/lib/legate/auth/exchanged_credential.rb +180 -0
- data/lib/legate/auth/excon_middleware.rb +285 -0
- data/lib/legate/auth/http_client_utils.rb +364 -0
- data/lib/legate/auth/manager.rb +531 -0
- data/lib/legate/auth/manager_store.rb +394 -0
- data/lib/legate/auth/middleware_factory.rb +290 -0
- data/lib/legate/auth/runner.rb +279 -0
- data/lib/legate/auth/scheme.rb +125 -0
- data/lib/legate/auth/schemes/api_key.rb +212 -0
- data/lib/legate/auth/schemes/google_service_account.rb +108 -0
- data/lib/legate/auth/schemes/http_bearer.rb +98 -0
- data/lib/legate/auth/schemes/oauth2.rb +396 -0
- data/lib/legate/auth/schemes/openid_connect.rb +346 -0
- data/lib/legate/auth/schemes/service_account.rb +388 -0
- data/lib/legate/auth/schemes.rb +40 -0
- data/lib/legate/auth/token_manager.rb +362 -0
- data/lib/legate/auth/token_store.rb +86 -0
- data/lib/legate/auth/tool_context_extension.rb +97 -0
- data/lib/legate/auth/tool_integration.rb +188 -0
- data/lib/legate/auth/url_guard.rb +81 -0
- data/lib/legate/auth.rb +453 -0
- data/lib/legate/callbacks/callback_context.rb +71 -0
- data/lib/legate/cli/agent_commands.rb +950 -0
- data/lib/legate/cli/auth_commands.rb +520 -0
- data/lib/legate/cli/base_command.rb +24 -0
- data/lib/legate/cli/deployment_commands.rb +934 -0
- data/lib/legate/cli/output_helper.rb +108 -0
- data/lib/legate/cli/session_commands.rb +138 -0
- data/lib/legate/cli/skaffold_commands.rb +223 -0
- data/lib/legate/cli/tool_commands.rb +261 -0
- data/lib/legate/cli/web_commands.rb +182 -0
- data/lib/legate/cli.rb +40 -0
- data/lib/legate/configuration/webhooks.rb +113 -0
- data/lib/legate/configuration.rb +39 -0
- data/lib/legate/definition_store.rb +23 -0
- data/lib/legate/errors.rb +118 -0
- data/lib/legate/event.rb +161 -0
- data/lib/legate/gemini_ai_beta_patch.rb +39 -0
- data/lib/legate/generators/agent_generator.rb +412 -0
- data/lib/legate/generators/code_validator.rb +48 -0
- data/lib/legate/generators/legate/install_generator.rb +35 -0
- data/lib/legate/generators/legate/templates/create_legate_tables.rb.tt +36 -0
- data/lib/legate/generators/legate/templates/initializer.rb +18 -0
- data/lib/legate/generators/runtime_tool_loader.rb +76 -0
- data/lib/legate/generators/tool_generator.rb +408 -0
- data/lib/legate/generators.rb +11 -0
- data/lib/legate/global_definition_registry.rb +506 -0
- data/lib/legate/global_tool_manager.rb +135 -0
- data/lib/legate/llm/adapter.rb +69 -0
- data/lib/legate/llm/gemini.rb +172 -0
- data/lib/legate/llm/ollama.rb +80 -0
- data/lib/legate/llm.rb +34 -0
- data/lib/legate/mcp/client.rb +320 -0
- data/lib/legate/mcp/connection/sse.rb +292 -0
- data/lib/legate/mcp/connection/stdio.rb +273 -0
- data/lib/legate/mcp/connection_manager.rb +103 -0
- data/lib/legate/mcp/server/legate_agent_adapter.rb +170 -0
- data/lib/legate/mcp/server/legate_direct_agent_adapter.rb +140 -0
- data/lib/legate/mcp/server/legate_tool_adapter.rb +119 -0
- data/lib/legate/mcp/tool_wrapper.rb +138 -0
- data/lib/legate/mcp/util/schema_converter.rb +134 -0
- data/lib/legate/mcp.rb +23 -0
- data/lib/legate/plan_executor.rb +375 -0
- data/lib/legate/planner.rb +839 -0
- data/lib/legate/rails/railtie.rb +43 -0
- data/lib/legate/rails.rb +9 -0
- data/lib/legate/redaction.rb +32 -0
- data/lib/legate/session.rb +299 -0
- data/lib/legate/session_service/active_record.rb +300 -0
- data/lib/legate/session_service/base.rb +68 -0
- data/lib/legate/session_service/event_broadcast.rb +74 -0
- data/lib/legate/session_service/in_memory.rb +188 -0
- data/lib/legate/tool/metadata_dsl.rb +122 -0
- data/lib/legate/tool.rb +276 -0
- data/lib/legate/tool_code_generator.rb +103 -0
- data/lib/legate/tool_context.rb +350 -0
- data/lib/legate/tool_loader.rb +39 -0
- data/lib/legate/tool_registry.rb +73 -0
- data/lib/legate/tool_result.rb +61 -0
- data/lib/legate/tools/agent_tool.rb +187 -0
- data/lib/legate/tools/base/http_client.rb +319 -0
- data/lib/legate/tools/base/safe_url.rb +56 -0
- data/lib/legate/tools/base_async_job_tool.rb +91 -0
- data/lib/legate/tools/calculator.rb +89 -0
- data/lib/legate/tools/cat_facts.rb +81 -0
- data/lib/legate/tools/check_job_status_tool.rb +48 -0
- data/lib/legate/tools/current_time_tool.rb +64 -0
- data/lib/legate/tools/echo.rb +43 -0
- data/lib/legate/tools/http_request_tool.rb +105 -0
- data/lib/legate/tools/random_number_tool.rb +64 -0
- data/lib/legate/tools/read_webpage_tool.rb +92 -0
- data/lib/legate/tools/sleepy_tool.rb +74 -0
- data/lib/legate/tools/webhook_tool.rb +146 -0
- data/lib/legate/version.rb +5 -0
- data/lib/legate/web/app.rb +984 -0
- data/lib/legate/web/public/css/main.css +4980 -0
- data/lib/legate/web/public/images/favicon-256.png +0 -0
- data/lib/legate/web/public/images/favicon-32.png +0 -0
- data/lib/legate/web/public/images/legate-logo-dark.png +0 -0
- data/lib/legate/web/public/images/legate-logo-light.png +0 -0
- data/lib/legate/web/public/js/legate.js +616 -0
- data/lib/legate/web/public/styles/main.scss +4402 -0
- data/lib/legate/web/routes/agent_authentication_routes.rb +530 -0
- data/lib/legate/web/routes/agent_definition_routes.rb +803 -0
- data/lib/legate/web/routes/agent_generator_routes.rb +80 -0
- data/lib/legate/web/routes/agent_interaction_routes.rb +734 -0
- data/lib/legate/web/routes/agent_runtime_routes.rb +323 -0
- data/lib/legate/web/routes/api_routes.rb +56 -0
- data/lib/legate/web/routes/authentication_routes.rb +1541 -0
- data/lib/legate/web/routes/core_routes.rb +111 -0
- data/lib/legate/web/routes/documentation_routes.rb +220 -0
- data/lib/legate/web/routes/tool_generator_routes.rb +81 -0
- data/lib/legate/web/routes/tools_ui_routes.rb +207 -0
- data/lib/legate/web/sass_compiler.rb +73 -0
- data/lib/legate/web/views/_active_session_info.slim +25 -0
- data/lib/legate/web/views/_activity_list.slim +55 -0
- data/lib/legate/web/views/_agent_card.slim +56 -0
- data/lib/legate/web/views/_agent_generator_modal.slim +382 -0
- data/lib/legate/web/views/_agent_status_controls.slim +71 -0
- data/lib/legate/web/views/_agent_tool_table.slim +74 -0
- data/lib/legate/web/views/_chat_message.slim +95 -0
- data/lib/legate/web/views/_display_agent_configuration.slim +26 -0
- data/lib/legate/web/views/_display_agent_description.slim +11 -0
- data/lib/legate/web/views/_display_agent_fallback.slim +15 -0
- data/lib/legate/web/views/_display_agent_hierarchy.slim +93 -0
- data/lib/legate/web/views/_display_agent_instruction.slim +17 -0
- data/lib/legate/web/views/_display_agent_mcp.slim +13 -0
- data/lib/legate/web/views/_display_agent_model.slim +17 -0
- data/lib/legate/web/views/_display_agent_name.slim +42 -0
- data/lib/legate/web/views/_display_agent_output_key.slim +26 -0
- data/lib/legate/web/views/_display_agent_type.slim +65 -0
- data/lib/legate/web/views/_edit_agent_configuration.slim +74 -0
- data/lib/legate/web/views/_edit_agent_description.slim +16 -0
- data/lib/legate/web/views/_edit_agent_fallback.slim +25 -0
- data/lib/legate/web/views/_edit_agent_hierarchy.slim +98 -0
- data/lib/legate/web/views/_edit_agent_instruction.slim +49 -0
- data/lib/legate/web/views/_edit_agent_mcp.slim +33 -0
- data/lib/legate/web/views/_edit_agent_model.slim +23 -0
- data/lib/legate/web/views/_edit_agent_output_key.slim +36 -0
- data/lib/legate/web/views/_edit_agent_tools.slim +40 -0
- data/lib/legate/web/views/_edit_agent_type.slim +67 -0
- data/lib/legate/web/views/_session_error.slim +4 -0
- data/lib/legate/web/views/_skeleton.slim +69 -0
- data/lib/legate/web/views/_tool_card.slim +9 -0
- data/lib/legate/web/views/_tool_generator_modal.slim +311 -0
- data/lib/legate/web/views/agent.slim +436 -0
- data/lib/legate/web/views/agent_auth.slim +562 -0
- data/lib/legate/web/views/agents.slim +369 -0
- data/lib/legate/web/views/auth.slim +112 -0
- data/lib/legate/web/views/auth_credential_detail.slim +327 -0
- data/lib/legate/web/views/auth_credentials.slim +261 -0
- data/lib/legate/web/views/auth_debug.slim +94 -0
- data/lib/legate/web/views/auth_mapping_detail.slim +151 -0
- data/lib/legate/web/views/auth_mapping_new.slim +123 -0
- data/lib/legate/web/views/auth_mappings.slim +120 -0
- data/lib/legate/web/views/auth_scheme_detail.slim +274 -0
- data/lib/legate/web/views/auth_schemes.slim +259 -0
- data/lib/legate/web/views/auth_test.slim +418 -0
- data/lib/legate/web/views/chat.slim +192 -0
- data/lib/legate/web/views/docs_index.slim +105 -0
- data/lib/legate/web/views/docs_show.slim +105 -0
- data/lib/legate/web/views/error_404.slim +5 -0
- data/lib/legate/web/views/index.slim +148 -0
- data/lib/legate/web/views/layout.slim +144 -0
- data/lib/legate/web/views/tool_detail.slim +87 -0
- data/lib/legate/web/views/tools.slim +50 -0
- data/lib/legate/web/webhook_listener.rb +367 -0
- data/lib/legate/web.rb +9 -0
- data/lib/legate.rb +220 -0
- data/public/docs/advanced/callbacks.md +828 -0
- data/public/docs/advanced/mcp_schema_conversion.md +59 -0
- data/public/docs/authentication/api_reference/config.md +210 -0
- data/public/docs/authentication/api_reference/credential.md +246 -0
- data/public/docs/authentication/api_reference/encryption.md +218 -0
- data/public/docs/authentication/api_reference/exchanged_credential.md +271 -0
- data/public/docs/authentication/api_reference/excon_middleware.md +175 -0
- data/public/docs/authentication/api_reference/index.md +30 -0
- data/public/docs/authentication/api_reference/scheme.md +250 -0
- data/public/docs/authentication/api_reference/schemes/api_key.md +175 -0
- data/public/docs/authentication/api_reference/schemes/google_service_account.md +221 -0
- data/public/docs/authentication/api_reference/schemes/http_bearer.md +169 -0
- data/public/docs/authentication/api_reference/schemes/oauth2.md +343 -0
- data/public/docs/authentication/api_reference/schemes/oidc.md +73 -0
- data/public/docs/authentication/api_reference/schemes/openid_connect.md +311 -0
- data/public/docs/authentication/api_reference/schemes/service_account.md +287 -0
- data/public/docs/authentication/api_reference/token_manager.md +221 -0
- data/public/docs/authentication/api_reference/token_store.md +146 -0
- data/public/docs/authentication/api_reference/tool_context_extension.md +166 -0
- data/public/docs/authentication/guides/api_key.md +190 -0
- data/public/docs/authentication/guides/bearer.md +172 -0
- data/public/docs/authentication/guides/configuration.md +255 -0
- data/public/docs/authentication/guides/custom_flow.md +523 -0
- data/public/docs/authentication/guides/index.md +24 -0
- data/public/docs/authentication/guides/migration.md +435 -0
- data/public/docs/authentication/guides/oauth2.md +252 -0
- data/public/docs/authentication/guides/oidc.md +241 -0
- data/public/docs/authentication/guides/overview.md +155 -0
- data/public/docs/authentication/guides/secure_storage.md +301 -0
- data/public/docs/authentication/guides/service_account.md +228 -0
- data/public/docs/authentication/guides/token_lifecycle.md +295 -0
- data/public/docs/authentication/guides/web_ui_integration.md +504 -0
- data/public/docs/authentication/index.md +58 -0
- data/public/docs/authentication/troubleshooting/credential_storage.md +550 -0
- data/public/docs/authentication/troubleshooting/environment_variables.md +540 -0
- data/public/docs/authentication/troubleshooting/index.md +11 -0
- data/public/docs/authentication/troubleshooting/oauth2_issues.md +220 -0
- data/public/docs/authentication/troubleshooting/oidc_issues.md +412 -0
- data/public/docs/authentication/troubleshooting/token_refresh.md +338 -0
- data/public/docs/cli/legate_cli_usage.md +363 -0
- data/public/docs/core_concepts/legate_agent_lifecycle.md +124 -0
- data/public/docs/core_concepts/legate_architecture_overview.md +110 -0
- data/public/docs/core_concepts/legate_configuration.md +116 -0
- data/public/docs/core_concepts/legate_definition_store.md +102 -0
- data/public/docs/core_concepts/legate_planner.md +94 -0
- data/public/docs/core_concepts/legate_session_service.md +104 -0
- data/public/docs/error_handling/legate_error_handling.md +122 -0
- data/public/docs/examples.md +199 -0
- data/public/docs/getting_started.md +111 -0
- data/public/docs/guides/agentic_agents.md +137 -0
- data/public/docs/guides/ai_code_generators.md +437 -0
- data/public/docs/guides/auto_loading.md +326 -0
- data/public/docs/guides/configuring_agent_webhooks.md +219 -0
- data/public/docs/guides/http_client_usage.md +264 -0
- data/public/docs/guides/llm_providers.md +137 -0
- data/public/docs/guides/mcp_client_integration.md +232 -0
- data/public/docs/guides/mcp_server_exposure.md +206 -0
- data/public/docs/guides/rails_integration.md +128 -0
- data/public/docs/guides/sending_outbound_webhooks.md +227 -0
- data/public/docs/guides/streaming.md +112 -0
- data/public/docs/guides/webhooks.md +288 -0
- data/public/docs/introduction.md +51 -0
- data/public/docs/multi_agent_systems/advanced_features.md +57 -0
- data/public/docs/multi_agent_systems/agent_delegation.md +190 -0
- data/public/docs/multi_agent_systems/agent_hierarchy.md +49 -0
- data/public/docs/multi_agent_systems/state_management.md +47 -0
- data/public/docs/multi_agent_systems/workflow_agents.md +72 -0
- data/public/docs/tools/legate_built_in_tools.md +332 -0
- data/public/docs/tools/legate_tools_and_registry.md +263 -0
- data/public/docs/web_ui/legate_web_ui.md +137 -0
- 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` → `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` → `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` → `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` → `Hash` (Agent Content):** Replaces the content Hash that the agent's `run_task` logic just produced.
|
|
73
|
+
* **`after_model_callback` → `Hash` (Planner Plan):** Replaces the plan Hash received from the LLM.
|
|
74
|
+
* **`after_tool_callback` → `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.
|