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
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: b06e76343d31ff76c3638acd71650a387dc3953c906f8e04a8539d85a3783445
|
|
4
|
+
data.tar.gz: 80d3719fd189ec9143b90ebf068b03fed902a358bece5cf0ead12a30028609aa
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: b5b88dfcdf545ec9586bf134112344d80919af25398b815c35948a5256c140c1315a0a751149405cc7c93676d90c0f4083b7c47fe72baaf23369afd8a25c81ca
|
|
7
|
+
data.tar.gz: 4780b16ff7582091c688e7cb438762669d1aaa488ffee5bdd693d7d1b8951f374462712cd6d5ab9faf3c3ce0208afc33a581e64de754393c9340a42304b62589
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-2026 Taylor Weibley
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
# Legate
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/rb/legate)
|
|
4
|
+
[](https://github.com/tweibley/legate/actions/workflows/ci.yml)
|
|
5
|
+
|
|
6
|
+
Legate is a framework for building AI agents in Ruby with dynamic tool selection, multi-step planning, and session management.
|
|
7
|
+
|
|
8
|
+
It's **batteries-included** — one gem ships the agent runtime, an LLM planner, a web UI, a CLI, MCP support, and an authentication subsystem. That's a deliberate choice: Legate is a framework, not a micro-library, so it bundles the dependencies those pieces need (Sinatra/Puma for the web UI, Thor for the CLI, and so on). If you only want the library, `require 'legate'` loads **just the core** — the web stack is opt-in via `require 'legate/web'` and the CLI via the `legate` executable, so library-only users never load Sinatra, Puma, or Slim.
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- **Flexible Agent Architecture** — Create agents with custom tools, models, and capabilities
|
|
13
|
+
- **Dynamic Tool System** — Register and use tools with automatic parameter validation
|
|
14
|
+
- **LLM-Powered Planning** — Agents break down complex tasks into multi-step plans (Gemini by default; pluggable provider adapters)
|
|
15
|
+
- **Session Management** — Track agent interactions with in-memory session state
|
|
16
|
+
- **Multi-Agent Systems** — Sequential, parallel, and loop agent patterns with delegation
|
|
17
|
+
- **MCP Integration** — Model Context Protocol support for external tool servers (configs are trusted input — see [Security model](#security-model))
|
|
18
|
+
- **Web UI** — Visual interface for agent interaction and monitoring (Sinatra + HTMX; a developer tool, **unauthenticated by default** — see [Security model](#security-model))
|
|
19
|
+
- **CLI** — Command-line interface for running agents, managing auth, and AI-powered code generation
|
|
20
|
+
- **Callbacks** — 6 hooks (before/after agent, model, tool) for monitoring, caching, and authorization
|
|
21
|
+
- **HTTP Client Mixin** — Built-in `HttpClient` module for tools that call external APIs
|
|
22
|
+
- **Webhook Support** — Trigger agent tasks via inbound HTTP webhooks
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
Add to your Gemfile:
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
29
|
+
gem 'legate'
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Then run:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
bundle install
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Quick Start
|
|
39
|
+
|
|
40
|
+
Set your Gemini API key (without it, planning is disabled and you'll get a clear warning):
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
export GEMINI_API_KEY=your_gemini_api_key_here
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Then ask an agent a question in one line:
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
require 'legate'
|
|
50
|
+
|
|
51
|
+
agent = Legate::Agent.new(definition: Legate::AgentDefinition.new.define do |a|
|
|
52
|
+
a.name :calculator_agent
|
|
53
|
+
a.description 'Does arithmetic with the calculator tool.'
|
|
54
|
+
a.instruction 'Use the calculator to answer math questions.'
|
|
55
|
+
a.use_tool :calculator
|
|
56
|
+
end)
|
|
57
|
+
|
|
58
|
+
puts agent.ask('What is 21 * 2?').answer
|
|
59
|
+
# => 42.0
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`ask` starts the agent, runs the task, and returns the final event — call `.answer`
|
|
63
|
+
for the result (or `.success?` / `.error_message`).
|
|
64
|
+
|
|
65
|
+
Agents come with useful tools out of the box — including `http_request` (an
|
|
66
|
+
SSRF-safe, auth-aware HTTP client), `read_webpage` (fetch a page as readable
|
|
67
|
+
text), `current_time`, and `calculator` — so an agent can do real work without
|
|
68
|
+
you writing a tool first. See [built-in tools](public/docs/tools/legate_built_in_tools.md).
|
|
69
|
+
|
|
70
|
+
Prefer a runnable file or the visual interface?
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
bundle exec ruby examples/00_quickstart.rb # the example above, ready to run
|
|
74
|
+
bundle exec legate web start # then open http://localhost:4567
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Configuration
|
|
78
|
+
|
|
79
|
+
### Environment Variables
|
|
80
|
+
|
|
81
|
+
| Variable | Purpose | Required |
|
|
82
|
+
|----------|---------|----------|
|
|
83
|
+
| `GOOGLE_API_KEY` | Google Gemini API key for LLM planning (`GEMINI_API_KEY` is accepted as an alias) | Yes (for the default Gemini adapter) |
|
|
84
|
+
| `LEGATE_LOG_LEVEL` | `DEBUG`, `INFO`, `WARN`, `ERROR`, `FATAL`, `NONE` | No |
|
|
85
|
+
| `RACK_ENV` | `development` or `production` | No |
|
|
86
|
+
| `SESSION_SECRET` | Web UI session cookie secret | Production |
|
|
87
|
+
| `BASIC_AUTH_USER` / `BASIC_AUTH_PASSWORD` | Enable optional HTTP Basic Auth on the web UI | No |
|
|
88
|
+
| `LEGATE_AUTH_ENCRYPTION_KEY` | Encrypts stored credentials at rest (libsodium) | No (recommended in production) |
|
|
89
|
+
| `LEGATE_ALLOW_PRIVATE_TOOL_URLS` | Let the HTTP tools (`http_request` / `read_webpage`) reach private/loopback hosts — **development only** | No |
|
|
90
|
+
| `LEGATE_ALLOW_PRIVATE_AUTH_URLS` | Let auth/credential-test requests reach private hosts — **development only** | No |
|
|
91
|
+
|
|
92
|
+
> The library never reads `.env` on its own. An application opts in by calling `Legate.load_environment` (as the `legate` CLI and the numbered examples do), which loads `.env` and maps `GEMINI_API_KEY` → `GOOGLE_API_KEY`.
|
|
93
|
+
|
|
94
|
+
### LLM providers
|
|
95
|
+
|
|
96
|
+
Planning goes through a pluggable `Legate::LLM::Adapter`. Gemini is the default; a local **Ollama** adapter ships in the box (no API key, no cost). Select a provider for every agent with a factory:
|
|
97
|
+
|
|
98
|
+
```ruby
|
|
99
|
+
# Use a local Ollama model instead of Gemini
|
|
100
|
+
Legate::LLM.default_adapter_factory = lambda do |model:, **|
|
|
101
|
+
Legate::LLM::Ollama.new(model: model) # talks to http://localhost:11434
|
|
102
|
+
end
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Or inject an adapter per planner via `Legate::Planner.new(agent:, llm_adapter:)`. Implement `Legate::LLM::Adapter` (`available?`, `model_name`, `generate(prompt, json:)`) to add any provider.
|
|
106
|
+
|
|
107
|
+
## Examples
|
|
108
|
+
|
|
109
|
+
### Simple Echo Agent
|
|
110
|
+
|
|
111
|
+
```ruby
|
|
112
|
+
require 'legate'
|
|
113
|
+
|
|
114
|
+
echo_agent = Legate::Agent.new(definition: Legate::AgentDefinition.new.define do |a|
|
|
115
|
+
a.name :simple_echo_agent
|
|
116
|
+
a.description 'A simple agent that can echo messages'
|
|
117
|
+
a.instruction 'You are an echo agent. Repeat the user input exactly.'
|
|
118
|
+
a.use_tool :echo
|
|
119
|
+
end)
|
|
120
|
+
|
|
121
|
+
puts echo_agent.ask('Hello, world!').answer
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
`ask` is the convenience path. If you need explicit control over sessions and the
|
|
125
|
+
agent lifecycle (e.g. multi-turn conversations, long-lived hosts), use the
|
|
126
|
+
underlying API directly:
|
|
127
|
+
|
|
128
|
+
```ruby
|
|
129
|
+
agent = Legate::Agent.new(definition: echo_agent_definition)
|
|
130
|
+
service = Legate::SessionService::InMemory.new
|
|
131
|
+
session = service.create_session(app_name: agent.name, user_id: 'example_user')
|
|
132
|
+
|
|
133
|
+
agent.start
|
|
134
|
+
result = agent.run_task(session_id: session.id, user_input: 'Hello, world!', session_service: service)
|
|
135
|
+
puts result.answer
|
|
136
|
+
agent.stop # tears down MCP connections; skip it to keep the agent warm for more asks
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Multi-Step Planning
|
|
140
|
+
|
|
141
|
+
```ruby
|
|
142
|
+
require 'legate'
|
|
143
|
+
|
|
144
|
+
random_calc_definition = Legate::AgentDefinition.new.define do |a|
|
|
145
|
+
a.name :random_calculator_agent
|
|
146
|
+
a.description 'An agent that uses random number and calculator tools.'
|
|
147
|
+
a.instruction 'Generate a random number then perform a calculation with it.'
|
|
148
|
+
a.use_tool :random_number
|
|
149
|
+
a.use_tool :calculator
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
agent = Legate::Agent.new(definition: random_calc_definition)
|
|
153
|
+
result = agent.ask('Get a random number between 10 and 20, then multiply it by 3.')
|
|
154
|
+
puts result.answer
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
See the `examples/` directory for more: multi-tool agents, MCP integration, webhooks, auth, callbacks, and multi-agent workflows.
|
|
158
|
+
|
|
159
|
+
## Core Concepts
|
|
160
|
+
|
|
161
|
+
### Agents
|
|
162
|
+
|
|
163
|
+
Agents are defined via `AgentDefinition` and instantiated with `Agent.new`:
|
|
164
|
+
|
|
165
|
+
```ruby
|
|
166
|
+
my_definition = Legate::AgentDefinition.new.define do |a|
|
|
167
|
+
a.name :my_agent
|
|
168
|
+
a.description 'Description of what the agent does'
|
|
169
|
+
a.instruction 'You are a helpful assistant.' # Optional — defaults from name/description
|
|
170
|
+
a.use_tool :my_tool_name
|
|
171
|
+
a.model_name 'gemini-3.5-flash' # Optional
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
agent = Legate::Agent.new(definition: my_definition)
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Only `name` is required. `instruction` is optional — a tool-only agent gets a sensible default derived from its name and description.
|
|
178
|
+
|
|
179
|
+
**Agent types:** `:llm` (default, uses Gemini for planning), `:sequential`, `:parallel`, `:loop`
|
|
180
|
+
|
|
181
|
+
### Tools
|
|
182
|
+
|
|
183
|
+
Tools inherit from `Legate::Tool` and define metadata via DSL:
|
|
184
|
+
|
|
185
|
+
```ruby
|
|
186
|
+
class MyCustomTool < Legate::Tool
|
|
187
|
+
tool_description 'Performs a custom action with input.'
|
|
188
|
+
|
|
189
|
+
parameter :input_data, type: :string, required: true,
|
|
190
|
+
description: 'The data needed for the action'
|
|
191
|
+
parameter :optional_flag, type: :boolean, required: false
|
|
192
|
+
|
|
193
|
+
private
|
|
194
|
+
|
|
195
|
+
def perform_execution(params, context)
|
|
196
|
+
input = params[:input_data]
|
|
197
|
+
Legate::ToolResult.success("Action performed on #{input}")
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Return a `Legate::ToolResult` (`.success(value)` / `.error(message)` / `.pending(job_id:)`)
|
|
203
|
+
or the equivalent hash (`{ status: :success, result: ... }`) — both work. The typed
|
|
204
|
+
form mirrors the `Event#answer` / `#success?` accessors and avoids hand-built hashes.
|
|
205
|
+
|
|
206
|
+
**Built-in tools:** `:echo`, `:calculator`, `:cat_facts`, `:random_number`, `:delegate_task`, `:check_job_status`
|
|
207
|
+
|
|
208
|
+
**Selecting tools:** `use_tool` takes a registered name (`:echo` or `'echo'`) **or** a `Legate::Tool` subclass — passing the class registers *and* selects it in one step:
|
|
209
|
+
|
|
210
|
+
```ruby
|
|
211
|
+
a.use_tool MyCustomTool # registers globally + selects it; no separate register_tool call
|
|
212
|
+
a.use_tool :echo # a built-in by name
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
A typo'd or unknown tool name produces a warning with a "did you mean?" suggestion and the list of available tools (it stays non-fatal, since MCP tools register when the agent connects).
|
|
216
|
+
|
|
217
|
+
**Parameter types:** `:string`, `:integer`, `:float`/`:numeric`, `:boolean`, `:array`, `:hash`
|
|
218
|
+
|
|
219
|
+
**Return values:** a `Legate::ToolResult` (`.success` / `.error` / `.pending`) or the equivalent `{ status: :success, result: ... }` hash; or raise `Legate::ToolError` / `Legate::ToolArgumentError`
|
|
220
|
+
|
|
221
|
+
**Introspection:** `Legate.tools` lists registered tools (name, description, parameters); `legate tool list` / `legate tool info NAME` do the same from the CLI.
|
|
222
|
+
|
|
223
|
+
### Sessions
|
|
224
|
+
|
|
225
|
+
```ruby
|
|
226
|
+
session_service = Legate::SessionService::InMemory.new
|
|
227
|
+
session = session_service.create_session(app_name: agent.name, user_id: 'user123')
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Making HTTP Requests in Tools
|
|
231
|
+
|
|
232
|
+
Include the `HttpClient` mixin for standardized HTTP with error wrapping:
|
|
233
|
+
|
|
234
|
+
```ruby
|
|
235
|
+
class MyApiTool < Legate::Tool
|
|
236
|
+
include Legate::Tools::Base::HttpClient
|
|
237
|
+
|
|
238
|
+
def initialize(**options)
|
|
239
|
+
super(**options)
|
|
240
|
+
setup_http_client(base_url: 'https://api.example.com/v2/')
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
private
|
|
244
|
+
|
|
245
|
+
def perform_execution(params, context)
|
|
246
|
+
response = http_get("items/#{params[:id]}")
|
|
247
|
+
data = JSON.parse(response.body)
|
|
248
|
+
{ status: :success, result: data }
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Errors are automatically wrapped into `ToolTimeoutError`, `ToolNetworkError`, `ToolHttpError`, etc.
|
|
254
|
+
|
|
255
|
+
### Callbacks
|
|
256
|
+
|
|
257
|
+
```ruby
|
|
258
|
+
agent_definition = Legate::AgentDefinition.new.define do |a|
|
|
259
|
+
a.name :agent_with_callbacks
|
|
260
|
+
a.instruction 'You are a helpful assistant.'
|
|
261
|
+
a.use_tool :echo
|
|
262
|
+
|
|
263
|
+
a.before_agent_callback { |context| puts "Starting: #{context.session_id}" }
|
|
264
|
+
a.after_agent_callback { |context, response| nil }
|
|
265
|
+
a.before_tool_callback { |tool, args, context| nil }
|
|
266
|
+
a.after_tool_callback { |tool, args, context, result| nil }
|
|
267
|
+
end
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### Inbound Webhooks
|
|
271
|
+
|
|
272
|
+
Trigger agent tasks via HTTP webhooks from external systems:
|
|
273
|
+
|
|
274
|
+
- Dynamic agent routing: `POST /webhooks/agents/:agent_name/trigger`
|
|
275
|
+
- Agent-defined validation and payload transformation
|
|
276
|
+
- Asynchronous processing (returns `202 Accepted`)
|
|
277
|
+
- HMAC signature verification support
|
|
278
|
+
|
|
279
|
+
## CLI
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
# Start the web UI
|
|
283
|
+
bundle exec legate web start
|
|
284
|
+
|
|
285
|
+
# Agent commands
|
|
286
|
+
bundle exec legate agent list
|
|
287
|
+
bundle exec legate agent execute my_agent "task"
|
|
288
|
+
bundle exec legate agent chat my_agent
|
|
289
|
+
|
|
290
|
+
# AI-powered code generation
|
|
291
|
+
bundle exec legate agent ai-generate
|
|
292
|
+
bundle exec legate tool ai-generate
|
|
293
|
+
|
|
294
|
+
# Authentication management
|
|
295
|
+
bundle exec legate auth status
|
|
296
|
+
bundle exec legate auth scheme list
|
|
297
|
+
bundle exec legate auth credential list
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
## Security model
|
|
301
|
+
|
|
302
|
+
Legate is a framework you embed in your own application. A few trust boundaries are worth understanding before you deploy it.
|
|
303
|
+
|
|
304
|
+
**The web UI is a developer tool and is unauthenticated by default.** `legate web start` binds an admin-grade interface — it can create agents, run tasks, and edit configuration. It ships **no application login**; the only built-in gate is optional HTTP Basic Auth, enabled by setting `BASIC_AUTH_USER` and `BASIC_AUTH_PASSWORD`. CSRF protection is on, and production refuses to boot without `SESSION_SECRET`, but those are not a substitute for authentication. **Run the web UI on localhost or a trusted private network. Do not expose it to untrusted users** without putting your own auth in front of it.
|
|
305
|
+
|
|
306
|
+
**MCP server configurations are trusted input — treat them like a `Gemfile` entry.** Configuring an agent's `mcp_servers` means running code you trust:
|
|
307
|
+
|
|
308
|
+
- **`:stdio` servers launch a local subprocess** from the configured `command`/`args`. Anyone who can set `mcp_servers` can run arbitrary local commands — that is what stdio MCP *is*.
|
|
309
|
+
- **`:sse`/remote MCP URLs are not SSRF-restricted.** MCP servers legitimately live on `localhost`/your private network, so Legate intentionally does not block private/loopback/metadata addresses for them.
|
|
310
|
+
|
|
311
|
+
So the real boundary is **who can supply an agent definition**. In code you control, this is a non-issue. The risk only appears if you let untrusted users create/edit agent definitions — which, combined with the unauthenticated web UI above, is why the UI must not be public.
|
|
312
|
+
|
|
313
|
+
**What *is* guarded:** outbound webhook and auth/credential-test requests run through an SSRF guard (`Legate::Auth::UrlGuard`) that refuses loopback/link-local/private/metadata addresses (incl. IPv4-mapped IPv6) and fails closed on resolution errors — and the webhook tool additionally pins the connection to the validated IP to defeat DNS rebinding; inbound webhooks verify HMAC signatures with a constant-time compare; stored credentials are encrypted at rest (libsodium). MCP is deliberately exempt from the SSRF guard for the reason above.
|
|
314
|
+
|
|
315
|
+
To report a vulnerability, see [SECURITY.md](SECURITY.md).
|
|
316
|
+
|
|
317
|
+
## Known limitations
|
|
318
|
+
|
|
319
|
+
Worth knowing before you build on Legate:
|
|
320
|
+
|
|
321
|
+
- **The web UI is unauthenticated by default** — it's a developer tool. Run it on localhost or a trusted network, or put your own auth in front of it (see [Security model](#security-model)).
|
|
322
|
+
- **State is in-memory by default.** Sessions and the agent/tool registries live in the process — they're lost on restart and not shared across multiple Puma workers. Opt into `SessionService::ActiveRecord` for durable sessions; run a single web process (or add a shared store) for cross-worker consistency.
|
|
323
|
+
- **LLM planning needs an API key**, and plan quality depends on the model. Without `GOOGLE_API_KEY` (or a local Ollama adapter), planning is disabled and tools can only be invoked directly.
|
|
324
|
+
- **Pin a model you've verified.** Hosted model lineups change — older models get retired and start returning 404s. The default tracks a current Gemini model, but if you set `model_name` explicitly, confirm it's still available for your key.
|
|
325
|
+
- **`read_webpage` is best-effort text extraction, not a browser.** It strips HTML without running JavaScript, so JS-rendered/SPA pages yield little text and complex markup may extract imperfectly.
|
|
326
|
+
- **`current_time` has no named-timezone support** (e.g. `America/New_York`) — only `UTC`, `local`, or a fixed offset like `+09:00` — to avoid a timezone-database dependency.
|
|
327
|
+
- **No built-in rate limiting or cost controls** on LLM calls — budget and throttle at your application layer.
|
|
328
|
+
- **MCP server configs are trusted input** (stdio launches local subprocesses; remote MCP URLs are intentionally not SSRF-restricted) — see [Security model](#security-model).
|
|
329
|
+
|
|
330
|
+
## Development
|
|
331
|
+
|
|
332
|
+
```bash
|
|
333
|
+
bundle install
|
|
334
|
+
bundle exec rspec # Run the test suite
|
|
335
|
+
bundle exec rubocop # Lint
|
|
336
|
+
bundle exec legate web start # Start dev server
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
## Contributing
|
|
340
|
+
|
|
341
|
+
Bug reports and pull requests are welcome on GitHub at [https://github.com/tweibley/legate](https://github.com/tweibley/legate). See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and guidelines.
|
|
342
|
+
|
|
343
|
+
## License
|
|
344
|
+
|
|
345
|
+
Legate is released under the [MIT License](LICENSE).
|
data/bin/legate
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Suppress logs when stdin is piped (for clean stdout output in pipe workflows)
|
|
5
|
+
# or when --json flag is used (for clean machine-readable output)
|
|
6
|
+
# This must happen BEFORE requiring Legate to prevent log initialization noise
|
|
7
|
+
ENV['LEGATE_LOG_LEVEL'] ||= 'SILENT' if !$stdin.tty? || ARGV.include?('--json')
|
|
8
|
+
|
|
9
|
+
require_relative '../lib/legate' # Load Legate library first
|
|
10
|
+
Legate.load_environment # Handle Bundler, Dotenv, etc.
|
|
11
|
+
require_relative '../lib/legate/cli' # Use require_relative for CLI component
|
|
12
|
+
|
|
13
|
+
Legate::CLI::Main.start(ARGV)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# If running from project root: bundle exec ruby examples/00_quickstart.rb
|
|
5
|
+
require_relative '../lib/legate'
|
|
6
|
+
|
|
7
|
+
# Load .env and map GEMINI_API_KEY -> GOOGLE_API_KEY (as the `legate` CLI does).
|
|
8
|
+
# The library never reads .env on its own; an application must opt in.
|
|
9
|
+
Legate.load_environment
|
|
10
|
+
|
|
11
|
+
puts '--- Quickstart: agent.ask in one line ---'
|
|
12
|
+
|
|
13
|
+
# Define an agent. Only `name` is required — `instruction` is optional (it
|
|
14
|
+
# defaults from the name/description), and `use_tool` selects a tool (here a
|
|
15
|
+
# built-in; you can also pass a Legate::Tool subclass to register + select it).
|
|
16
|
+
agent = Legate::Agent.new(definition: Legate::AgentDefinition.new.define do |a|
|
|
17
|
+
a.name :quickstart_agent
|
|
18
|
+
a.description 'Repeats what the user says.'
|
|
19
|
+
a.use_tool :echo
|
|
20
|
+
end)
|
|
21
|
+
|
|
22
|
+
# `ask` is the convenience path: it starts the agent, creates a session, runs
|
|
23
|
+
# the task, and returns the final event. No start/create_session/stop dance.
|
|
24
|
+
event = agent.ask('Echo this back: Hello, Legate!')
|
|
25
|
+
|
|
26
|
+
# Read the result off the event — no reaching into event.content.
|
|
27
|
+
puts "Answer: #{event.answer}"
|
|
28
|
+
puts "Success: #{event.success?}"
|
|
29
|
+
puts "Error: #{event.error_message}" if event.error?
|
|
30
|
+
|
|
31
|
+
puts "\n--- Watch progress live (optional block) ---"
|
|
32
|
+
# Pass a block to stream each lifecycle event as it happens (user message, each
|
|
33
|
+
# tool request/result, final answer) — handy for a CLI spinner or a UI.
|
|
34
|
+
agent.ask('Repeat: streaming works') do |e|
|
|
35
|
+
puts " #{e.role}#{e.tool_name ? " (#{e.tool_name})" : ''}"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# `ask` makes a fresh session each call. To keep context across turns, create a
|
|
39
|
+
# session once and pass its id to each ask:
|
|
40
|
+
#
|
|
41
|
+
# session = agent.session_service.create_session(app_name: agent.name.to_s, user_id: 'demo')
|
|
42
|
+
# agent.ask('first question', session_id: session.id)
|
|
43
|
+
# agent.ask('a follow-up', session_id: session.id)
|
|
44
|
+
#
|
|
45
|
+
# See examples 02+ for multi-tool planning, custom tools, sessions, and more.
|
|
46
|
+
|
|
47
|
+
# `ask` does not auto-stop (it stays warm for more questions). Stop a long-lived
|
|
48
|
+
# agent when you're done to release any MCP connections.
|
|
49
|
+
agent.stop
|
|
50
|
+
|
|
51
|
+
puts "\n--- Quickstart Complete ---"
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# If running from project root: bundle exec ruby examples/01_simple_agent.rb
|
|
5
|
+
require_relative '../lib/legate'
|
|
6
|
+
|
|
7
|
+
# Load .env and map GEMINI_API_KEY -> GOOGLE_API_KEY (as the `legate` CLI does).
|
|
8
|
+
# The library never reads .env on its own; an application must opt in.
|
|
9
|
+
Legate.load_environment
|
|
10
|
+
|
|
11
|
+
puts '--- Simple Echo Agent Example (Session-Based) ---'
|
|
12
|
+
|
|
13
|
+
# 1. --- Agent Definition Setup ---
|
|
14
|
+
simple_echo_definition = Legate::AgentDefinition.new.define do |a|
|
|
15
|
+
a.name :simple_echo_agent # Agent name as a Symbol
|
|
16
|
+
a.description 'A simple agent that can echo messages'
|
|
17
|
+
a.instruction 'You are an echo agent. Your task is to repeat the user\'s input exactly.' # Instruction is required
|
|
18
|
+
a.use_tool :echo # Tool name as a Symbol, Legate::Tools::Echo should be globally discoverable
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Optional: Register with GlobalDefinitionRegistry if this definition needs to be found by name later
|
|
22
|
+
# Legate::GlobalDefinitionRegistry.register(simple_echo_definition)
|
|
23
|
+
|
|
24
|
+
# Ensure the tool is globally available (Legate::Tools::Echo should be by default)
|
|
25
|
+
# If Legate::Tools::Echo wasn't automatically registered, you might need:
|
|
26
|
+
# Legate::GlobalToolManager.register_tool(Legate::Tools::Echo)
|
|
27
|
+
|
|
28
|
+
# 2. --- Agent Instantiation ---
|
|
29
|
+
# Initialize the agent with the definition object
|
|
30
|
+
agent = Legate::Agent.new(definition: simple_echo_definition)
|
|
31
|
+
|
|
32
|
+
puts "\nAgent '#{agent.name}' created with tool: #{agent.tools.first&.name || 'none'}"
|
|
33
|
+
|
|
34
|
+
# 3. --- Start Agent ---
|
|
35
|
+
agent.start
|
|
36
|
+
puts "Agent started. Running: #{agent.running?}"
|
|
37
|
+
|
|
38
|
+
# 4. --- Session Setup ---
|
|
39
|
+
session_service = Legate::SessionService::InMemory.new
|
|
40
|
+
session = session_service.create_session(app_name: agent.name, user_id: 'example_user')
|
|
41
|
+
session_id = session.id
|
|
42
|
+
puts "\nCreated session: #{session_id}"
|
|
43
|
+
|
|
44
|
+
# 5. --- Task Execution ---
|
|
45
|
+
task = 'Hello, world!'
|
|
46
|
+
puts "\nExecuting task: '#{task}'"
|
|
47
|
+
|
|
48
|
+
begin
|
|
49
|
+
result_event = agent.run_task(
|
|
50
|
+
session_id: session_id,
|
|
51
|
+
user_input: task,
|
|
52
|
+
session_service: session_service
|
|
53
|
+
)
|
|
54
|
+
puts "Raw result event: #{result_event.inspect}"
|
|
55
|
+
|
|
56
|
+
# --- Updated Result Handling ---
|
|
57
|
+
puts "\nInterpreted Result:"
|
|
58
|
+
if result_event.is_a?(Legate::Event)
|
|
59
|
+
puts ' Status: Event Received'
|
|
60
|
+
puts " Role: #{result_event.role}"
|
|
61
|
+
|
|
62
|
+
content = result_event.content
|
|
63
|
+
if content.is_a?(Array)
|
|
64
|
+
puts ' Content Type: Multi-Step Plan Results'
|
|
65
|
+
any_errors = false
|
|
66
|
+
content.each_with_index do |step_hash, index|
|
|
67
|
+
print " Step #{index + 1}: "
|
|
68
|
+
if step_hash.is_a?(Hash) && step_hash[:status] == :success
|
|
69
|
+
puts "Success | Result: #{step_hash[:result]}"
|
|
70
|
+
elsif step_hash.is_a?(Hash) && step_hash[:status] == :error
|
|
71
|
+
puts "Error | Message: #{step_hash[:error_message]}"
|
|
72
|
+
any_errors = true
|
|
73
|
+
else
|
|
74
|
+
puts "Unknown Format | Data: #{step_hash.inspect}"
|
|
75
|
+
any_errors = true # Treat unexpected format as problematic
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
puts " Overall Plan Status: #{any_errors ? 'Completed with errors' : 'Completed successfully'}"
|
|
79
|
+
elsif content.is_a?(Hash) && content.key?(:status)
|
|
80
|
+
# Single step plan or a planning error
|
|
81
|
+
if content[:status] == :success
|
|
82
|
+
puts ' Content Type: Single Step Success'
|
|
83
|
+
puts " Result: #{content[:result]}"
|
|
84
|
+
else # status == :error or other
|
|
85
|
+
puts ' Content Type: Error (or Single Step Error)'
|
|
86
|
+
puts " Message: #{content[:error_message]}"
|
|
87
|
+
end
|
|
88
|
+
else
|
|
89
|
+
puts ' Content Type: String or Other Format'
|
|
90
|
+
puts " Content: #{content}"
|
|
91
|
+
end
|
|
92
|
+
else
|
|
93
|
+
puts ' Status: Unknown (Unexpected Format)'
|
|
94
|
+
puts " Raw Data: #{result_event.inspect}"
|
|
95
|
+
end
|
|
96
|
+
# --- End Updated Result Handling ---
|
|
97
|
+
rescue StandardError => e
|
|
98
|
+
puts "\nError executing task: #{e.class} - #{e.message}"
|
|
99
|
+
puts e.backtrace.first(5).join("\n")
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# 6. --- Stop Agent ---
|
|
103
|
+
agent.stop
|
|
104
|
+
puts "\nAgent stopped. Running: #{agent.running?}"
|
|
105
|
+
puts "\n--- Example Complete ---"
|