solace-agent-mesh 1.6.2__py3-none-any.whl → 1.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of solace-agent-mesh might be problematic. Click here for more details.
- solace_agent_mesh/agent/adk/adk_llm.txt +12 -18
- solace_agent_mesh/agent/adk/artifacts/artifacts_llm.txt +1 -1
- solace_agent_mesh/agent/adk/callbacks.py +138 -20
- solace_agent_mesh/agent/adk/embed_resolving_mcp_toolset.py +2 -0
- solace_agent_mesh/agent/adk/models/lite_llm.py +38 -5
- solace_agent_mesh/agent/adk/models/models_llm.txt +82 -35
- solace_agent_mesh/agent/adk/runner.py +9 -0
- solace_agent_mesh/agent/adk/services.py +3 -3
- solace_agent_mesh/agent/adk/stream_parser.py +6 -1
- solace_agent_mesh/agent/adk/tool_wrapper.py +3 -0
- solace_agent_mesh/agent/agent_llm.txt +61 -70
- solace_agent_mesh/agent/protocol/event_handlers.py +29 -1
- solace_agent_mesh/agent/protocol/protocol_llm.txt +1 -1
- solace_agent_mesh/agent/proxies/a2a/a2a_llm.txt +190 -0
- solace_agent_mesh/agent/proxies/base/base_llm.txt +148 -0
- solace_agent_mesh/agent/proxies/proxies_llm.txt +283 -0
- solace_agent_mesh/agent/sac/app.py +22 -0
- solace_agent_mesh/agent/sac/component.py +76 -40
- solace_agent_mesh/agent/sac/sac_llm.txt +1 -1
- solace_agent_mesh/agent/sac/task_execution_context.py +21 -0
- solace_agent_mesh/agent/testing/testing_llm.txt +2 -1
- solace_agent_mesh/agent/tools/builtin_artifact_tools.py +13 -148
- solace_agent_mesh/agent/tools/dynamic_tool.py +2 -0
- solace_agent_mesh/agent/tools/tools_llm.txt +93 -80
- solace_agent_mesh/agent/tools/tools_llm_detail.txt +3 -2
- solace_agent_mesh/agent/utils/artifact_helpers.py +4 -0
- solace_agent_mesh/agent/utils/utils_llm.txt +16 -2
- solace_agent_mesh/assets/docs/404.html +3 -3
- solace_agent_mesh/assets/docs/assets/js/05749d90.c70b2be9.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/15ba94aa.92fea363.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/15e40e79.36003774.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/2987107d.a80604f9.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/3ac1795d.e4870a49.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/3ff0015d.b63ee53a.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/547e15cc.2f7790c1.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/5c2bd65f.45b32c2b.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/631738c7.fa471607.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/64195356.c498c4d0.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/6a520c9d.b6e3f2ce.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/6ad8f0bd.a5b36a60.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/71da7b71.374b9d54.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/8024126c.fa0e7186.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/8b032486.91a91afc.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/94e8668d.09ed9234.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/{ab9708a8.3e6dd091.js → ab9708a8.245ae0ef.js} +1 -1
- solace_agent_mesh/assets/docs/assets/js/ad87452a.9d73dad6.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/cbe2e9ea.f902fad8.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/da0b5bad.b62f7b08.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/db5d6442.3daf1696.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/dd817ffc.c37a755e.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/dd81e2b8.b682e9c2.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/de915948.44a432bc.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/e04b235d.c9c50c7b.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/e3d9abda.d11c67a7.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/{e6f9706b.e74a984d.js → e6f9706b.045d0fa1.js} +1 -1
- solace_agent_mesh/assets/docs/assets/js/e92d0134.3bda61dd.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/f284c35a.5099c51e.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/ff4d71f2.74710fc1.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/main.e6488e8b.js +2 -0
- solace_agent_mesh/assets/docs/assets/js/runtime~main.d9606d6a.js +1 -0
- solace_agent_mesh/assets/docs/docs/documentation/components/agents/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/artifact-management/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/audio-tools/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/data-analysis-tools/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/embeds/index.html +18 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/cli/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/gateways/index.html +5 -5
- solace_agent_mesh/assets/docs/docs/documentation/components/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/orchestrator/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/plugins/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/projects/index.html +196 -0
- solace_agent_mesh/assets/docs/docs/documentation/components/proxies/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/deploying/debugging/index.html +5 -5
- solace_agent_mesh/assets/docs/docs/documentation/deploying/deployment-options/index.html +6 -7
- solace_agent_mesh/assets/docs/docs/documentation/deploying/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/deploying/kubernetes-deployment/index.html +47 -0
- solace_agent_mesh/assets/docs/docs/documentation/deploying/logging/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/deploying/observability/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/create-agents/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/create-gateways/index.html +160 -169
- solace_agent_mesh/assets/docs/docs/documentation/developing/creating-python-tools/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/creating-service-providers/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/evaluations/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/structure/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/bedrock-agents/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/custom-agent/index.html +5 -5
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/event-mesh-gateway/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/mcp-integration/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/mongodb-integration/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/rag-integration/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/rest-gateway/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/slack-integration/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/sql-database/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/agent-builder/index.html +59 -0
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/connectors/index.html +62 -0
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/index.html +10 -6
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/installation/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/rbac-setup-guide/index.html +24 -29
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/secure-user-delegated-access/index.html +440 -0
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/single-sign-on/index.html +27 -4
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/wheel-installation/index.html +62 -0
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/architecture/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/index.html +5 -4
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/try-agent-mesh/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/artifact-storage/index.html +290 -0
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/configurations/index.html +9 -9
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/installation/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/large_language_models/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/run-project/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/session-storage/index.html +251 -0
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/user-feedback/index.html +88 -0
- solace_agent_mesh/assets/docs/docs/documentation/migrations/a2a-upgrade/a2a-gateway-upgrade-to-0.3.0/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/migrations/a2a-upgrade/a2a-technical-migration-map/index.html +3 -3
- solace_agent_mesh/assets/docs/lunr-index-1762189824009.json +1 -0
- solace_agent_mesh/assets/docs/lunr-index.json +1 -1
- solace_agent_mesh/assets/docs/search-doc-1762189824009.json +1 -0
- solace_agent_mesh/assets/docs/search-doc.json +1 -1
- solace_agent_mesh/assets/docs/sitemap.xml +1 -1
- solace_agent_mesh/cli/__init__.py +1 -1
- solace_agent_mesh/cli/commands/docs_cmd.py +4 -1
- solace_agent_mesh/cli/commands/init_cmd/orchestrator_step.py +1 -1
- solace_agent_mesh/client/webui/frontend/static/assets/{authCallback-D4_RMYRh.js → authCallback-tcIFZLis.js} +1 -1
- solace_agent_mesh/client/webui/frontend/static/assets/{client-UZ3qU6Bq.js → client-CRYdKo2Q.js} +3 -3
- solace_agent_mesh/client/webui/frontend/static/assets/main-CojeY_1w.css +1 -0
- solace_agent_mesh/client/webui/frontend/static/assets/main-ILja9MCG.js +353 -0
- solace_agent_mesh/client/webui/frontend/static/assets/vendor-CINwxvwV.js +470 -0
- solace_agent_mesh/client/webui/frontend/static/auth-callback.html +3 -3
- solace_agent_mesh/client/webui/frontend/static/index.html +4 -4
- solace_agent_mesh/common/a2a/a2a_llm.txt +13 -20
- solace_agent_mesh/common/a2a/protocol.py +5 -0
- solace_agent_mesh/common/a2a/types.py +1 -0
- solace_agent_mesh/common/a2a_spec/a2a_spec_llm.txt +49 -11
- solace_agent_mesh/common/a2a_spec/schemas/artifact_creation_progress.json +23 -6
- solace_agent_mesh/common/a2a_spec/schemas/feedback_event.json +51 -0
- solace_agent_mesh/common/a2a_spec/schemas/schemas_llm.txt +26 -9
- solace_agent_mesh/common/common_llm.txt +13 -34
- solace_agent_mesh/common/data_parts.py +20 -4
- solace_agent_mesh/common/middleware/middleware_llm.txt +1 -1
- solace_agent_mesh/common/sac/sac_llm.txt +1 -1
- solace_agent_mesh/common/sam_events/sam_events_llm.txt +1 -1
- solace_agent_mesh/common/services/employee_service.py +1 -1
- solace_agent_mesh/common/services/providers/providers_llm.txt +3 -2
- solace_agent_mesh/common/services/services_llm.txt +9 -4
- solace_agent_mesh/common/utils/embeds/constants.py +1 -0
- solace_agent_mesh/common/utils/embeds/embeds_llm.txt +1 -1
- solace_agent_mesh/common/utils/embeds/modifiers.py +2 -1
- solace_agent_mesh/common/utils/embeds/resolver.py +58 -6
- solace_agent_mesh/common/utils/embeds/types.py +8 -0
- solace_agent_mesh/common/utils/utils_llm.txt +5 -6
- solace_agent_mesh/core_a2a/core_a2a_llm.txt +1 -1
- solace_agent_mesh/gateway/adapter/__init__.py +1 -0
- solace_agent_mesh/gateway/adapter/base.py +143 -0
- solace_agent_mesh/gateway/adapter/types.py +221 -0
- solace_agent_mesh/gateway/base/app.py +29 -2
- solace_agent_mesh/gateway/base/base_llm.txt +10 -8
- solace_agent_mesh/gateway/base/component.py +573 -142
- solace_agent_mesh/gateway/gateway_llm.txt +55 -59
- solace_agent_mesh/gateway/generic/__init__.py +1 -0
- solace_agent_mesh/gateway/generic/app.py +50 -0
- solace_agent_mesh/gateway/generic/component.py +650 -0
- solace_agent_mesh/gateway/http_sse/alembic/alembic_llm.txt +99 -49
- solace_agent_mesh/gateway/http_sse/alembic/versions/20251023_add_fulltext_search_indexes.py +92 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/20251023_add_project_users_table.py +72 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/20251023_add_soft_delete_and_search.py +150 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/20251024_add_default_agent_to_projects.py +26 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/20251024_add_projects_table.py +135 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/versions_llm.txt +26 -20
- solace_agent_mesh/gateway/http_sse/app.py +19 -14
- solace_agent_mesh/gateway/http_sse/component.py +150 -118
- solace_agent_mesh/gateway/http_sse/components/components_llm.txt +1 -1
- solace_agent_mesh/gateway/http_sse/dependencies.py +21 -3
- solace_agent_mesh/gateway/http_sse/http_sse_llm.txt +8 -8
- solace_agent_mesh/gateway/http_sse/main.py +55 -14
- solace_agent_mesh/gateway/http_sse/repository/__init__.py +19 -1
- solace_agent_mesh/gateway/http_sse/repository/entities/entities_llm.txt +56 -98
- solace_agent_mesh/gateway/http_sse/repository/entities/project.py +81 -0
- solace_agent_mesh/gateway/http_sse/repository/entities/project_user.py +47 -0
- solace_agent_mesh/gateway/http_sse/repository/entities/session.py +23 -1
- solace_agent_mesh/gateway/http_sse/repository/feedback_repository.py +47 -0
- solace_agent_mesh/gateway/http_sse/repository/interfaces.py +112 -4
- solace_agent_mesh/gateway/http_sse/repository/models/__init__.py +9 -1
- solace_agent_mesh/gateway/http_sse/repository/models/models_llm.txt +51 -60
- solace_agent_mesh/gateway/http_sse/repository/models/project_model.py +51 -0
- solace_agent_mesh/gateway/http_sse/repository/models/project_user_model.py +75 -0
- solace_agent_mesh/gateway/http_sse/repository/models/session_model.py +7 -1
- solace_agent_mesh/gateway/http_sse/repository/project_repository.py +172 -0
- solace_agent_mesh/gateway/http_sse/repository/project_user_repository.py +186 -0
- solace_agent_mesh/gateway/http_sse/repository/repository_llm.txt +125 -157
- solace_agent_mesh/gateway/http_sse/repository/session_repository.py +269 -8
- solace_agent_mesh/gateway/http_sse/routers/artifacts.py +143 -51
- solace_agent_mesh/gateway/http_sse/routers/config.py +69 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/dto_llm.txt +198 -94
- solace_agent_mesh/gateway/http_sse/routers/dto/requests/project_requests.py +48 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/requests/requests_llm.txt +68 -18
- solace_agent_mesh/gateway/http_sse/routers/dto/requests/session_requests.py +13 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/responses/project_responses.py +30 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/responses/responses_llm.txt +51 -35
- solace_agent_mesh/gateway/http_sse/routers/dto/responses/session_responses.py +2 -0
- solace_agent_mesh/gateway/http_sse/routers/feedback.py +133 -2
- solace_agent_mesh/gateway/http_sse/routers/projects.py +542 -0
- solace_agent_mesh/gateway/http_sse/routers/routers_llm.txt +9 -11
- solace_agent_mesh/gateway/http_sse/routers/sessions.py +154 -3
- solace_agent_mesh/gateway/http_sse/routers/tasks.py +296 -4
- solace_agent_mesh/gateway/http_sse/services/project_service.py +403 -0
- solace_agent_mesh/gateway/http_sse/services/services_llm.txt +16 -10
- solace_agent_mesh/gateway/http_sse/services/session_service.py +178 -6
- solace_agent_mesh/gateway/http_sse/shared/exception_handlers.py +2 -3
- solace_agent_mesh/gateway/http_sse/shared/shared_llm.txt +48 -14
- solace_agent_mesh/solace_agent_mesh_llm.txt +1 -1
- {solace_agent_mesh-1.6.2.dist-info → solace_agent_mesh-1.7.0.dist-info}/METADATA +3 -5
- {solace_agent_mesh-1.6.2.dist-info → solace_agent_mesh-1.7.0.dist-info}/RECORD +219 -176
- solace_agent_mesh/assets/docs/assets/js/15ba94aa.932dd2db.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/3ac1795d.76654dd9.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/3ff0015d.2be20244.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/547e15cc.2cbb060a.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/5c2bd65f.eda4bcb2.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/631738c7.7c4594c9.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/6a520c9d.ba015d81.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/6ad8f0bd.f4b15f3b.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/71da7b71.ddbdfbe2.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/8024126c.56e59919.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/94e8668d.b5ddb7a1.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/da0b5bad.d08a9466.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/dd817ffc.0aa9630a.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/dd81e2b8.d590bc9e.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/de915948.27d6b065.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/e3d9abda.6b9493d0.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/e92d0134.4f395c6b.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/f284c35a.720d2ef2.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/ff4d71f2.15b02f97.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/main.d1643f0b.js +0 -2
- solace_agent_mesh/assets/docs/assets/js/runtime~main.97f920d4.js +0 -1
- solace_agent_mesh/assets/docs/lunr-index-1761663789856.json +0 -1
- solace_agent_mesh/assets/docs/search-doc-1761663789856.json +0 -1
- solace_agent_mesh/client/webui/frontend/static/assets/main--3yJYl7S.css +0 -1
- solace_agent_mesh/client/webui/frontend/static/assets/main-DojKHS49.js +0 -342
- solace_agent_mesh/client/webui/frontend/static/assets/vendor-DSqhjwq_.js +0 -405
- /solace_agent_mesh/assets/docs/assets/js/{main.d1643f0b.js.LICENSE.txt → main.e6488e8b.js.LICENSE.txt} +0 -0
- {solace_agent_mesh-1.6.2.dist-info → solace_agent_mesh-1.7.0.dist-info}/WHEEL +0 -0
- {solace_agent_mesh-1.6.2.dist-info → solace_agent_mesh-1.7.0.dist-info}/entry_points.txt +0 -0
- {solace_agent_mesh-1.6.2.dist-info → solace_agent_mesh-1.7.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use strict";(self.webpackChunksolace_agenitc_mesh_docs=self.webpackChunksolace_agenitc_mesh_docs||[]).push([[6084],{4795:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>s,default:()=>h,frontMatter:()=>i,metadata:()=>a,toc:()=>l});const a=JSON.parse('{"id":"documentation/developing/tutorials/custom-agent","title":"Build Your Own Agent","description":"This tutorial shows you how to build a sophisticated weather agent using the Agent Mesh framework. Learn how to integrate with external APIs, manage resources properly, and create production-ready agents.","source":"@site/docs/documentation/developing/tutorials/custom-agent.md","sourceDirName":"documentation/developing/tutorials","slug":"/documentation/developing/tutorials/custom-agent","permalink":"/solace-agent-mesh/docs/documentation/developing/tutorials/custom-agent","draft":false,"unlisted":false,"editUrl":"https://github.com/SolaceLabs/solace-agent-mesh/edit/main/docs/docs/documentation/developing/tutorials/custom-agent.md","tags":[],"version":"current","sidebarPosition":5,"frontMatter":{"title":"Build Your Own Agent","sidebar_position":5},"sidebar":"docSidebar","previous":{"title":"Evaluating Agents","permalink":"/solace-agent-mesh/docs/documentation/developing/evaluations"},"next":{"title":"MCP Integration","permalink":"/solace-agent-mesh/docs/documentation/developing/tutorials/mcp-integration"}}');var r=t(4848),o=t(8453);const i={title:"Build Your Own Agent",sidebar_position:5},s="Build Your Own Agent",c={},l=[{value:"Overview",id:"overview",level:2},{value:"Prerequisites",id:"prerequisites",level:2},{value:"Planning the Weather Agent",id:"planning-the-weather-agent",level:2},{value:"Step 1: Project Structure",id:"step-1-project-structure",level:2},{value:"Step 2: Weather Service Implementation",id:"step-2-weather-service-implementation",level:2},{value:"Step 3: Weather Tools Implementation",id:"step-3-weather-tools-implementation",level:2},{value:"Step 4: Lifecycle Functions",id:"step-4-lifecycle-functions",level:2},{value:"Step 5: Agent Configuration",id:"step-5-agent-configuration",level:2},{value:"Step 6: Environment Setup",id:"step-6-environment-setup",level:2},{value:"Step 7: Running the Agent",id:"step-7-running-the-agent",level:2},{value:"Step 8: Testing the Weather Agent",id:"step-8-testing-the-weather-agent",level:2},{value:"Key Features Demonstrated",id:"key-features-demonstrated",level:2},{value:"1. External API Integration",id:"1-external-api-integration",level:3},{value:"2. Resource Management",id:"2-resource-management",level:3},{value:"3. Configuration Management",id:"3-configuration-management",level:3},{value:"4. Error Handling",id:"4-error-handling",level:3},{value:"5. Artifact Management",id:"5-artifact-management",level:3}];function d(e){const n={a:"a",admonition:"admonition",blockquote:"blockquote",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,o.R)(),...e.components},{Details:t}=n;return t||function(e,n){throw new Error("Expected "+(n?"component":"object")+" `"+e+"` to be defined: you likely forgot to import, pass, or provide it.")}("Details",!0),(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(n.header,{children:(0,r.jsx)(n.h1,{id:"build-your-own-agent",children:"Build Your Own Agent"})}),"\n",(0,r.jsx)(n.p,{children:"This tutorial shows you how to build a sophisticated weather agent using the Agent Mesh framework. Learn how to integrate with external APIs, manage resources properly, and create production-ready agents."}),"\n",(0,r.jsx)(n.h2,{id:"overview",children:"Overview"}),"\n",(0,r.jsx)(n.p,{children:"Our weather agent will demonstrate:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"External API integration (OpenWeatherMap)"}),"\n",(0,r.jsx)(n.li,{children:"Professional service layer architecture"}),"\n",(0,r.jsx)(n.li,{children:"Multiple sophisticated tools"}),"\n",(0,r.jsx)(n.li,{children:"Proper lifecycle management"}),"\n",(0,r.jsx)(n.li,{children:"Error handling and validation"}),"\n",(0,r.jsx)(n.li,{children:"Artifact creation and management"}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"prerequisites",children:"Prerequisites"}),"\n",(0,r.jsx)(n.p,{children:"Before starting this tutorial, make sure you have:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["Read the ",(0,r.jsx)(n.a,{href:"/solace-agent-mesh/docs/documentation/developing/create-agents",children:"Create Agent"})," tutorial"]}),"\n",(0,r.jsxs)(n.li,{children:["An OpenWeatherMap API key (free at ",(0,r.jsx)(n.a,{href:"https://openweathermap.org/api",children:"openweathermap.org"}),")"]}),"\n",(0,r.jsx)(n.li,{children:"Basic understanding of Python async/await"}),"\n",(0,r.jsx)(n.li,{children:"Familiarity with HTTP APIs"}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"planning-the-weather-agent",children:"Planning the Weather Agent"}),"\n",(0,r.jsx)(n.p,{children:"Our weather agent will have the following capabilities:"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Get Current Weather"}),": Fetch current weather conditions for a specified location"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Get Weather Forecast"}),": Retrieve a multi-day weather forecast"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Save Weather Reports"}),": Store weather data as artifacts"]}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"The agent will demonstrate:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"External API integration"}),"\n",(0,r.jsx)(n.li,{children:"Error handling and validation"}),"\n",(0,r.jsx)(n.li,{children:"Configuration management"}),"\n",(0,r.jsx)(n.li,{children:"Artifact creation"}),"\n",(0,r.jsx)(n.li,{children:"Resource lifecycle management"}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"step-1-project-structure",children:"Step 1: Project Structure"}),"\n",(0,r.jsx)(n.p,{children:"Run the following command to create a new custom agent:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"sam add agent --gui\n"})}),"\n",(0,r.jsxs)(n.admonition,{type:"tip",children:[(0,r.jsxs)(n.p,{children:["You can create an agent either by using the ",(0,r.jsx)(n.code,{children:"sam add agent"})," command or by creating a new plugin of type agent, ",(0,r.jsx)(n.code,{children:"sam plugin create my-hello-agent --type agent"}),"."]}),(0,r.jsxs)(n.p,{children:["For information and recommendations about these options, see ",(0,r.jsx)(n.a,{href:"/solace-agent-mesh/docs/documentation/components/plugins#agent-or-plugin-which-to-use",children:(0,r.jsx)(n.code,{children:"Agent or Plugin: Which To use?"})}),"."]}),(0,r.jsxs)(n.p,{children:["For an example of plugin agents, see the ",(0,r.jsx)(n.a,{href:"/solace-agent-mesh/docs/documentation/developing/create-agents#step-1-initialize-your-agent",children:"Create Agents"})," guide."]})]}),"\n",(0,r.jsx)(n.p,{children:'Follow the steps on the GUI to create a new agent named "Weather Agent". We can update the tools/skills section directly in the configuration file later.'}),"\n",(0,r.jsxs)(n.admonition,{title:"Important Notice",type:"warning",children:[(0,r.jsx)(n.p,{children:"This tutorial is intentionally comprehensive to demonstrate the full flexibility and advanced features available in Agent Mesh agents. For most straightforward use cases, you only need a simple Python function and a corresponding reference in your YAML configuration file."}),(0,r.jsxs)(t,{children:[(0,r.jsx)("summary",{children:"Simple Weather Agent Example"}),(0,r.jsxs)(n.p,{children:["After going through the add agent wizard from ",(0,r.jsx)(n.code,{children:"sam add agent --gui"}),", create the following file under ",(0,r.jsx)(n.code,{children:"src/weather_agent/tools.py"})," directory:"]}),(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-py",children:'# src/weather_agent/tools.py\nimport httpx\nfrom typing import Any, Dict, Optional\nfrom google.adk.tools import ToolContext\nfrom solace_ai_connector.common.log import log\n\n\nasync def get_current_weather(\n location: str,\n units: str = "metric",\n tool_context: Optional[ToolContext] = None,\n tool_config: Optional[Dict[str, Any]] = None\n) -> Dict[str, Any]:\n """\n Get current weather conditions for a specified location.\n \n Args:\n location: City name, state, and country (for example, "New York, NY, US")\n units: Temperature units - "metric" (Celsius), "imperial" (Fahrenheit), or "kelvin"\n """\n log.info("[GetCurrentWeather] Getting current weather for: %s", location)\n base_url = "https://api.openweathermap.org/data/2.5"\n api_key = tool_config.get("api_key") if tool_config else None\n\n url = f"{base_url}/weather"\n params = {\n "q": location,\n "appid": api_key,\n "units": units\n }\n\n try:\n # Fetch weather data\n async with httpx.AsyncClient() as client:\n response = await client.get(url, params=params)\n response.raise_for_status()\n weather_data = response.json()\n \n result = {\n "status": "success",\n "location": location,\n "units": units,\n "data": weather_data\n }\n return result\n \n except Exception as e:\n log.error(f"[GetCurrentWeather] Error getting weather: {e}")\n return {\n "status": "error",\n "message": f"Weather service error: {str(e)}"\n }\n\n\nasync def get_weather_forecast(\n location: str,\n days: int = 5,\n units: str = "metric",\n tool_context: Optional[ToolContext] = None,\n tool_config: Optional[Dict[str, Any]] = None\n) -> Dict[str, Any]:\n """\n Get weather forecast for a specified location.\n \n Args:\n location: City name, state, and country\n days: Number of days for forecast (1-5)\n units: Temperature units\n """\n log.info("[GetWeatherForecast] Getting %d-day forecast for: %s", days, location)\n base_url = "https://api.openweathermap.org/data/2.5"\n api_key = tool_config.get("api_key") if tool_config else None\n\n url = f"{base_url}/forecast"\n params = {\n "q": location,\n "appid": api_key,\n "units": units,\n "cnt": min(days * 8, 40) \n }\n\n try:\n # Fetch forecast data\n async with httpx.AsyncClient() as client:\n response = await client.get(url, params=params)\n response.raise_for_status()\n forecast_data = response.json()\n\n result = {\n "status": "success",\n "location": forecast_data["location"],\n "days": days,\n "units": units,\n "data": forecast_data\n }\n return result\n except Exception as e:\n log.error(f"[GetWeatherForecast] Error getting forecast: {e}")\n return {\n "status": "error",\n "message": f"Weather service error: {str(e)}"\n }\n\n'})}),(0,r.jsxs)(n.p,{children:["And update the weather agent config file's tool section under ",(0,r.jsx)(n.code,{children:"configs/agent/weather-agent.yaml"})," as follows:"]}),(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:' # Tools configuration\n tools:\n # Current weather tool\n - tool_type: python\n component_module: "src.weather_agent.tools"\n component_base_path: .\n function_name: "get_current_weather"\n tool_description: "Get current weather conditions for a specified location"\n tool_config:\n api_key: ${OPENWEATHER_API_KEY}\n\n # Weather forecast tool\n - tool_type: python\n component_module: "src.weather_agent.tools"\n function_name: "get_weather_forecast"\n component_base_path: .\n tool_description: "Get weather forecast for up to 5 days for a specified location"\n tool_config:\n api_key: ${OPENWEATHER_API_KEY}\n\n'})}),(0,r.jsxs)(n.p,{children:["For better discoverability, update the ",(0,r.jsx)(n.a,{href:"/solace-agent-mesh/docs/documentation/components/agents#agent-card",children:"agent card"})," section in the same YAML file as follows:"]}),(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:' # Agent card\n agent_card:\n description: "Professional weather agent providing current conditions, forecasts, and weather comparisons"\n defaultInputModes: ["text"]\n defaultOutputModes: ["text"]\n skills:\n - id: "get_current_weather"\n name: "Get Current Weather"\n description: "Retrieve current weather conditions for any location worldwide"\n - id: "get_weather_forecast"\n name: "Get Weather Forecast"\n description: "Provide detailed weather forecasts up to 5 days ahead"\n'})}),(0,r.jsxs)(n.p,{children:["To run the agent, you can continue following documentation from ",(0,r.jsx)(n.a,{href:"#step-6-environment-setup",children:"Step 6"})," of this tutorial."]})]}),(0,r.jsx)(n.p,{children:"Other concepts mentioned in this page such as lifecycle, services, artifacts are just to showcase more advanced patterns."})]}),"\n",(0,r.jsx)(n.p,{children:"Create the directory structure for the weather agent:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{children:"sam-project/\n\u251c\u2500\u2500 src/\n\u2502 \u2514\u2500\u2500 weather_agent/\n\u2502 \u251c\u2500\u2500 __init__.py\n\u2502 \u251c\u2500\u2500 tools.py\n\u2502 \u251c\u2500\u2500 lifecycle.py\n\u2502 \u2514\u2500\u2500 services/\n\u2502 \u251c\u2500\u2500 __init__.py\n\u2502 \u2514\u2500\u2500 weather_service.py\n\u251c\u2500\u2500 configs\n\u2502 \u2514\u2500\u2500 shared_config.yaml\n\u2502 \u2514\u2500\u2500 agents/\n\u2502 \u2514\u2500\u2500 weather_agent.yaml\n...\n"})}),"\n",(0,r.jsxs)(n.admonition,{type:"tip",children:[(0,r.jsxs)(n.p,{children:["In Agent Mesh, you can create an agent either by using the ",(0,r.jsx)(n.code,{children:"sam add agent"})," command or by creating a new plugin of type agent, ",(0,r.jsx)(n.code,{children:"sam plugin create your-agent --type agent"}),"."]}),(0,r.jsxs)(n.p,{children:["This tutorial uses the ",(0,r.jsx)(n.code,{children:"sam add agent"}),' command to create a new agent named "Weather Agent", for an example of creating a custom agent plugin, see the ',(0,r.jsx)(n.a,{href:"/solace-agent-mesh/docs/documentation/developing/create-agents",children:"Create Agents"})," tutorial."]})]}),"\n",(0,r.jsx)(n.h2,{id:"step-2-weather-service-implementation",children:"Step 2: Weather Service Implementation"}),"\n",(0,r.jsx)(n.p,{children:"First, create a service class to handle weather API interactions:"}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsxs)(n.strong,{children:[(0,r.jsx)(n.code,{children:"src/weather_agent/services/weather_service.py"}),":"]})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:'"""\nWeather service for interacting with external weather APIs.\n"""\n\nimport aiohttp\nfrom typing import Dict, Any, Optional, List\nfrom datetime import datetime, timezone\nfrom solace_ai_connector.common.log import log\n\n\nclass WeatherService:\n """Service for fetching weather data from external APIs."""\n \n def __init__(self, api_key: str, base_url: str = "https://api.openweathermap.org/data/2.5"):\n self.api_key = api_key\n self.base_url = base_url\n self.session: Optional[aiohttp.ClientSession] = None\n self.log_identifier = "[WeatherService]"\n \n async def _get_session(self) -> aiohttp.ClientSession:\n """Get or create an HTTP session."""\n if self.session is None or self.session.closed:\n self.session = aiohttp.ClientSession()\n return self.session\n \n async def close(self):\n """Close the HTTP session."""\n if self.session and not self.session.closed:\n await self.session.close()\n log.info(f"{self.log_identifier} HTTP session closed")\n \n async def get_current_weather(self, location: str, units: str = "metric") -> Dict[str, Any]:\n """\n Get current weather for a location.\n \n Args:\n location: City name, state code, and country code (for example, "London,UK")\n units: Temperature units (metric, imperial, kelvin)\n \n Returns:\n Dictionary containing current weather data\n """\n log.info(f"{self.log_identifier} Fetching current weather for: {location}")\n \n session = await self._get_session()\n url = f"{self.base_url}/weather"\n params = {\n "q": location,\n "appid": self.api_key,\n "units": units\n }\n \n try:\n async with session.get(url, params=params) as response:\n if response.status == 200:\n data = await response.json()\n log.info(f"{self.log_identifier} Successfully fetched weather for {location}")\n return self._format_current_weather(data)\n elif response.status == 404:\n raise ValueError(f"Location \'{location}\' not found")\n else:\n error_data = await response.json()\n raise Exception(f"Weather API error: {error_data.get(\'message\', \'Unknown error\')}")\n \n except aiohttp.ClientError as e:\n log.error(f"{self.log_identifier} Network error fetching weather: {e}")\n raise Exception(f"Network error: {str(e)}")\n \n async def get_weather_forecast(self, location: str, days: int = 5, units: str = "metric") -> Dict[str, Any]:\n """\n Get weather forecast for a location.\n \n Args:\n location: City name, state code, and country code\n days: Number of days for forecast (1-5)\n units: Temperature units\n \n Returns:\n Dictionary containing forecast data\n """\n log.info(f"{self.log_identifier} Fetching {days}-day forecast for: {location}")\n \n session = await self._get_session()\n url = f"{self.base_url}/forecast"\n params = {\n "q": location,\n "appid": self.api_key,\n "units": units,\n "cnt": min(days * 8, 40) # API returns 3-hour intervals, max 40 entries\n }\n \n try:\n async with session.get(url, params=params) as response:\n if response.status == 200:\n data = await response.json()\n log.info(f"{self.log_identifier} Successfully fetched forecast for {location}")\n return self._format_forecast_data(data, days)\n elif response.status == 404:\n raise ValueError(f"Location \'{location}\' not found")\n else:\n error_data = await response.json()\n raise Exception(f"Weather API error: {error_data.get(\'message\', \'Unknown error\')}")\n \n except aiohttp.ClientError as e:\n log.error(f"{self.log_identifier} Network error fetching forecast: {e}")\n raise Exception(f"Network error: {str(e)}")\n \n def _format_current_weather(self, data: Dict) -> Dict[str, Any]:\n """Format current weather data for consistent output."""\n return {\n "location": f"{data[\'name\']}, {data[\'sys\'][\'country\']}",\n "temperature": data[\'main\'][\'temp\'],\n "feels_like": data[\'main\'][\'feels_like\'],\n "humidity": data[\'main\'][\'humidity\'],\n "pressure": data[\'main\'][\'pressure\'],\n "description": data[\'weather\'][0][\'description\'].title(),\n "wind_speed": data.get(\'wind\', {}).get(\'speed\', 0),\n "wind_direction": data.get(\'wind\', {}).get(\'deg\', 0),\n "visibility": data.get(\'visibility\', 0) / 1000, # Convert to km\n "timestamp": datetime.fromtimestamp(data[\'dt\']).isoformat(),\n "sunrise": datetime.fromtimestamp(data[\'sys\'][\'sunrise\']).isoformat(),\n "sunset": datetime.fromtimestamp(data[\'sys\'][\'sunset\']).isoformat()\n }\n \n def _format_forecast_data(self, data: Dict, days: int) -> Dict[str, Any]:\n """Format forecast data for consistent output."""\n forecasts = []\n current_date = None\n daily_data = []\n \n for item in data[\'list\'][:days * 8]:\n forecast_date = datetime.fromtimestamp(item[\'dt\']).date()\n \n if current_date != forecast_date:\n if daily_data:\n forecasts.append(self._aggregate_daily_forecast(daily_data))\n current_date = forecast_date\n daily_data = []\n \n daily_data.append(item)\n \n # Add the last day\'s data\n if daily_data:\n forecasts.append(self._aggregate_daily_forecast(daily_data))\n \n return {\n "location": f"{data[\'city\'][\'name\']}, {data[\'city\'][\'country\']}",\n "forecasts": forecasts[:days]\n }\n \n def _aggregate_daily_forecast(self, daily_data: List[Dict]) -> Dict[str, Any]:\n """Aggregate 3-hour forecasts into daily summary."""\n if not daily_data:\n return {}\n \n # Get temperatures for min/max calculation\n temps = [item[\'main\'][\'temp\'] for item in daily_data]\n \n # Use the forecast closest to noon for general conditions\n noon_forecast = min(daily_data, key=lambda x: abs(\n datetime.fromtimestamp(x[\'dt\']).hour - 12\n ))\n \n return {\n "date": datetime.fromtimestamp(daily_data[0][\'dt\']).date().isoformat(),\n "temperature_min": min(temps),\n "temperature_max": max(temps),\n "description": noon_forecast[\'weather\'][0][\'description\'].title(),\n "humidity": noon_forecast[\'main\'][\'humidity\'],\n "wind_speed": noon_forecast.get(\'wind\', {}).get(\'speed\', 0),\n "precipitation_probability": noon_forecast.get(\'pop\', 0) * 100\n }\n'})}),"\n",(0,r.jsx)(n.h2,{id:"step-3-weather-tools-implementation",children:"Step 3: Weather Tools Implementation"}),"\n",(0,r.jsx)(n.p,{children:"Now create the tool functions:"}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsxs)(n.strong,{children:[(0,r.jsx)(n.code,{children:"src/weather_agent/tools.py"}),":"]})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:'"""\nWeather agent tools for fetching and processing weather data.\n"""\n\nimport json\nfrom typing import Any, Dict, Optional\nfrom datetime import datetime, timezone\nfrom google.adk.tools import ToolContext\nfrom solace_ai_connector.common.log import log\nfrom solace_agent_mesh.agent.utils.artifact_helpers import save_artifact_with_metadata\n\nasync def get_current_weather(\n location: str,\n units: str = "metric",\n save_to_file: bool = False,\n tool_context: Optional[ToolContext] = None,\n tool_config: Optional[Dict[str, Any]] = None\n) -> Dict[str, Any]:\n """\n Get current weather conditions for a specified location.\n \n Args:\n location: City name, state, and country (for example, "New York, NY, US")\n units: Temperature units - "metric" (Celsius), "imperial" (Fahrenheit), or "kelvin"\n save_to_file: Whether to save the weather report as an artifact\n \n Returns:\n Dictionary containing current weather information\n """\n log_identifier = "[GetCurrentWeather]"\n log.info(f"{log_identifier} Getting current weather for: {location}")\n \n if not tool_context:\n return {\n "status": "error",\n "message": "Tool context is required for weather operations"\n }\n \n try:\n # Get weather service from agent state\n host_component = getattr(tool_context._invocation_context, "agent", None)\n if host_component:\n host_component = getattr(host_component, "host_component", None)\n \n if not host_component:\n return {\n "status": "error",\n "message": "Could not access agent host component"\n }\n \n weather_service = host_component.get_agent_specific_state("weather_service")\n if not weather_service:\n return {\n "status": "error",\n "message": "Weather service not initialized"\n }\n \n # Fetch weather data\n weather_data = await weather_service.get_current_weather(location, units)\n \n # Create human-readable summary\n summary = _create_weather_summary(weather_data)\n \n result = {\n "status": "success",\n "location": weather_data["location"],\n "summary": summary,\n "data": weather_data\n }\n \n # Save to artifact if requested\n if save_to_file:\n artifact_result = await _save_weather_artifact(\n weather_data, f"current_weather_{location}", tool_context\n )\n result["artifact"] = artifact_result\n \n log.info(f"{log_identifier} Successfully retrieved weather for {location}")\n return result\n \n except ValueError as e:\n log.warning(f"{log_identifier} Invalid location: {e}")\n return {\n "status": "error",\n "message": f"Location error: {str(e)}"\n }\n except Exception as e:\n log.error(f"{log_identifier} Error getting weather: {e}")\n return {\n "status": "error",\n "message": f"Weather service error: {str(e)}"\n }\n\n\nasync def get_weather_forecast(\n location: str,\n days: int = 5,\n units: str = "metric",\n save_to_file: bool = False,\n tool_context: Optional[ToolContext] = None,\n tool_config: Optional[Dict[str, Any]] = None\n) -> Dict[str, Any]:\n """\n Get weather forecast for a specified location.\n \n Args:\n location: City name, state, and country\n days: Number of days for forecast (1-5)\n units: Temperature units\n save_to_file: Whether to save the forecast as an artifact\n \n Returns:\n Dictionary containing weather forecast information\n """\n log_identifier = "[GetWeatherForecast]"\n log.info(f"{log_identifier} Getting {days}-day forecast for: {location}")\n \n if not tool_context:\n return {\n "status": "error",\n "message": "Tool context is required for weather operations"\n }\n \n # Validate days parameter\n if not 1 <= days <= 5:\n return {\n "status": "error",\n "message": "Days must be between 1 and 5"\n }\n \n try:\n # Get weather service from agent state\n host_component = getattr(tool_context._invocation_context, "agent", None)\n if host_component:\n host_component = getattr(host_component, "host_component", None)\n \n if not host_component:\n return {\n "status": "error",\n "message": "Could not access agent host component"\n }\n \n weather_service = host_component.get_agent_specific_state("weather_service")\n if not weather_service:\n return {\n "status": "error",\n "message": "Weather service not initialized"\n }\n \n # Fetch forecast data\n forecast_data = await weather_service.get_weather_forecast(location, days, units)\n \n # Create human-readable summary\n summary = _create_forecast_summary(forecast_data)\n \n result = {\n "status": "success",\n "location": forecast_data["location"],\n "summary": summary,\n "data": forecast_data\n }\n \n # Save to artifact if requested\n if save_to_file:\n artifact_result = await _save_weather_artifact(\n forecast_data, f"forecast_{location}_{days}day", tool_context\n )\n result["artifact"] = artifact_result\n \n log.info(f"{log_identifier} Successfully retrieved forecast for {location}")\n return result\n \n except ValueError as e:\n log.warning(f"{log_identifier} Invalid location: {e}")\n return {\n "status": "error",\n "message": f"Location error: {str(e)}"\n }\n except Exception as e:\n log.error(f"{log_identifier} Error getting forecast: {e}")\n return {\n "status": "error",\n "message": f"Weather service error: {str(e)}"\n }\n\n\ndef _create_weather_summary(weather_data: Dict[str, Any]) -> str:\n """Create a human-readable weather summary."""\n temp_unit = "\xb0C" # Assuming metric units for summary\n \n summary = f"Current weather in {weather_data[\'location\']}:\\n"\n summary += f"\u2022 Temperature: {weather_data[\'temperature\']}{temp_unit} (feels like {weather_data[\'feels_like\']}{temp_unit})\\n"\n summary += f"\u2022 Conditions: {weather_data[\'description\']}\\n"\n summary += f"\u2022 Humidity: {weather_data[\'humidity\']}%\\n"\n summary += f"\u2022 Wind: {weather_data[\'wind_speed\']} m/s\\n"\n summary += f"\u2022 Visibility: {weather_data[\'visibility\']} km"\n \n return summary\n\n\ndef _create_forecast_summary(forecast_data: Dict[str, Any]) -> str:\n """Create a human-readable forecast summary."""\n summary = f"Weather forecast for {forecast_data[\'location\']}:\\n\\n"\n \n for forecast in forecast_data[\'forecasts\']:\n date = datetime.fromisoformat(forecast[\'date\']).strftime(\'%A, %B %d\')\n summary += f"\u2022 {date}: {forecast[\'description\']}\\n"\n summary += f" High: {forecast[\'temperature_max\']:.1f}\xb0C, Low: {forecast[\'temperature_min\']:.1f}\xb0C\\n"\n if forecast[\'precipitation_probability\'] > 0:\n summary += f" Precipitation: {forecast[\'precipitation_probability\']:.0f}% chance\\n"\n summary += "\\n"\n \n return summary.strip()\n\n\nasync def _save_weather_artifact(\n weather_data: Dict[str, Any],\n filename_base: str,\n tool_context: ToolContext\n) -> Dict[str, Any]:\n """Save weather data as an artifact."""\n try:\n # Prepare content\n content = json.dumps(weather_data, indent=2, default=str)\n timestamp = datetime.now(timezone.utc)\n filename = f"{filename_base}_{timestamp.strftime(\'%Y%m%d_%H%M%S\')}.json"\n \n # Save artifact\n artifact_service = tool_context._invocation_context.artifact_service\n result = await save_artifact_with_metadata(\n artifact_service=artifact_service,\n app_name=tool_context._invocation_context.app_name,\n user_id=tool_context._invocation_context.user_id,\n session_id=tool_context._invocation_context.session.id,\n filename=filename,\n content_bytes=content.encode(\'utf-8\'),\n mime_type="application/json",\n metadata_dict={\n "description": "Weather data report",\n "source": "Weather Agent"\n },\n timestamp=timestamp\n )\n \n return {\n "filename": filename,\n "status": result.get("status", "success")\n }\n \n except Exception as e:\n log.error(f"[WeatherArtifact] Error saving artifact: {e}")\n return {\n "status": "error",\n "message": f"Failed to save artifact: {str(e)}"\n }\n'})}),"\n",(0,r.jsx)(n.h2,{id:"step-4-lifecycle-functions",children:"Step 4: Lifecycle Functions"}),"\n",(0,r.jsx)(n.p,{children:"Create the lifecycle management:"}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsxs)(n.strong,{children:[(0,r.jsx)(n.code,{children:"src/weather_agent/lifecycle.py"}),":"]})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-python",children:'"""\nLifecycle functions for the Weather Agent.\n"""\n\nfrom typing import Any\nimport asyncio\nfrom pydantic import BaseModel, Field, SecretStr\nfrom solace_ai_connector.common.log import log\nfrom .services.weather_service import WeatherService\n\n\nclass WeatherAgentInitConfig(BaseModel):\n """\n Configuration model for Weather Agent initialization.\n """\n api_key: SecretStr = Field(description="OpenWeatherMap API key")\n base_url: str = Field(\n default="https://api.openweathermap.org/data/2.5",\n description="Weather API base URL"\n )\n startup_message: str = Field(\n default="Weather Agent is ready to provide weather information!",\n description="Message to log on startup"\n )\n\n\ndef initialize_weather_agent(host_component: Any, init_config: WeatherAgentInitConfig):\n """\n Initialize the Weather Agent with weather service.\n \n Args:\n host_component: The agent host component\n init_config: Validated initialization configuration\n """\n log_identifier = f"[{host_component.agent_name}:init]"\n log.info(f"{log_identifier} Starting Weather Agent initialization...")\n \n try:\n # Initialize weather service\n weather_service = WeatherService(\n api_key=init_config.api_key.get_secret_value(),\n base_url=init_config.base_url\n )\n \n # Store service in agent state\n host_component.set_agent_specific_state("weather_service", weather_service)\n \n # Log startup message\n log.info(f"{log_identifier} {init_config.startup_message}")\n \n # Store initialization metadata\n host_component.set_agent_specific_state("initialized_at", "2024-01-01T00:00:00Z")\n host_component.set_agent_specific_state("weather_requests_count", 0)\n \n log.info(f"{log_identifier} Weather Agent initialization completed successfully")\n \n except Exception as e:\n log.error(f"{log_identifier} Failed to initialize Weather Agent: {e}")\n raise\n\n\ndef cleanup_weather_agent(host_component: Any):\n """\n Clean up Weather Agent resources.\n \n Args:\n host_component: The agent host component\n """\n log_identifier = f"[{host_component.agent_name}:cleanup]"\n log.info(f"{log_identifier} Starting Weather Agent cleanup...")\n\n async def cleanup_async(host_component: Any):\n try:\n # Get and close weather service\n weather_service = host_component.get_agent_specific_state("weather_service")\n if weather_service:\n await weather_service.close()\n log.info(f"{log_identifier} Weather service closed successfully")\n \n # Log final statistics\n request_count = host_component.get_agent_specific_state("weather_requests_count", 0)\n log.info(f"{log_identifier} Agent processed {request_count} weather requests during its lifetime")\n \n log.info(f"{log_identifier} Weather Agent cleanup completed")\n \n except Exception as e:\n log.error(f"{log_identifier} Error during cleanup: {e}")\n \n # run cleanup in the event loop\n loop = asyncio.get_event_loop()\n loop.run_until_complete(cleanup_async(host_component))\n log.info(f"{log_identifier} Weather Agent cleanup completed successfully")\n'})}),"\n",(0,r.jsx)(n.h2,{id:"step-5-agent-configuration",children:"Step 5: Agent Configuration"}),"\n",(0,r.jsx)(n.p,{children:"Create the comprehensive YAML configuration:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:'# Weather Agent Configuration\nlog:\n stdout_log_level: INFO\n log_file_level: DEBUG\n log_file: weather-agent.log\n\n!include ../shared_config.yaml\n\napps:\n - name: weather-agent\n # Broker configuration\n app_module: solace_agent_mesh.agent.sac.app\n broker:\n <<: *broker_connection\n\n app_config:\n namespace: "${NAMESPACE}"\n agent_name: "WeatherAgent"\n display_name: "Weather Information Agent"\n supports_streaming: true\n \n # LLM model configuration\n model: *general_model\n \n # Agent instructions\n instruction: |\n You are a professional weather agent that provides accurate, up-to-date weather information.\n \n Your capabilities include:\n 1. Getting current weather conditions for any location worldwide\n 2. Providing detailed weather forecasts up to 5 days\n 3. Saving weather reports as files for future reference\n \n Guidelines:\n - Always specify the location clearly when providing weather information\n - Include relevant details like temperature, conditions, humidity, and wind\n - Offer to save weather reports when providing detailed information\n - Be helpful in suggesting appropriate clothing or activities based on weather\n - If a location is ambiguous, ask for clarification (city, state/province, country)\n \n When users ask about weather, use the appropriate tools to fetch real-time data.\n Present information in a clear, organized manner that\'s easy to understand.\n \n # Lifecycle functions\n agent_init_function:\n module: "src.weather_agent.lifecycle"\n name: "initialize_weather_agent"\n base_path: .\n config:\n api_key: ${OPENWEATHER_API_KEY}\n base_url: "https://api.openweathermap.org/data/2.5"\n startup_message: "Weather Agent is ready to provide weather information!"\n \n agent_cleanup_function:\n module: "src.weather_agent.lifecycle"\n base_path: .\n name: "cleanup_weather_agent"\n \n # Tools configuration\n tools:\n # Current weather tool\n - tool_type: python\n component_module: "src.weather_agent.tools"\n component_base_path: .\n function_name: "get_current_weather"\n tool_description: "Get current weather conditions for a specified location"\n \n # Weather forecast tool\n - tool_type: python\n component_module: "src.weather_agent.tools"\n component_base_path: .\n function_name: "get_weather_forecast"\n tool_description: "Get weather forecast for up to 5 days for a specified location"\n \n # Built-in artifact tools for file operations\n - tool_type: builtin-group\n group_name: "artifact_management"\n \n session_service: *default_session_service\n artifact_service: *default_artifact_service\n\n artifact_handling_mode: "reference"\n enable_embed_resolution: true\n enable_artifact_content_instruction: true\n # Agent card\n agent_card:\n description: "Professional weather agent providing current conditions, forecasts, and weather comparisons"\n defaultInputModes: ["text"]\n defaultOutputModes: ["text", "file"]\n skills:\n - id: "get_current_weather"\n name: "Get Current Weather"\n description: "Retrieve current weather conditions for any location worldwide"\n - id: "get_weather_forecast"\n name: "Get Weather Forecast"\n description: "Provide detailed weather forecasts up to 5 days ahead"\n \n agent_card_publishing:\n interval_seconds: 30\n\n agent_discovery:\n enabled: false\n\n inter_agent_communication:\n allow_list: []\n request_timeout_seconds: 30\n'})}),"\n",(0,r.jsxs)(n.p,{children:["For more details on agent cards, see the ",(0,r.jsx)(n.a,{href:"/solace-agent-mesh/docs/documentation/components/agents#agent-card",children:"Agent Card Concepts"})," documentation."]}),"\n",(0,r.jsx)(n.h2,{id:"step-6-environment-setup",children:"Step 6: Environment Setup"}),"\n",(0,r.jsx)(n.p,{children:"Before running your weather agent, you'll need to:"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Get an OpenWeatherMap API key"}),":"]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["Sign up at ",(0,r.jsx)(n.a,{href:"https://openweathermap.org/api",children:"OpenWeatherMap"})]}),"\n",(0,r.jsx)(n.li,{children:"Get your free API key"}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Set environment variables"}),":"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:'export OPENWEATHER_API_KEY="your_api_key_here"\n# Other environment variables as needed\n'})}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"step-7-running-the-agent",children:"Step 7: Running the Agent"}),"\n",(0,r.jsxs)(n.p,{children:["To start the agent, it is preferred to build the plugin and then install it with your agent name. But for debugging or isolated development testing, you can run your agent from the ",(0,r.jsx)(n.code,{children:"src"})," directory directly using the Agent Mesh CLI."]}),"\n",(0,r.jsx)(n.p,{children:"Start your weather agent for development purposes run:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"sam run\n"})}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["To solely run the agent, use ",(0,r.jsx)(n.code,{children:"sam run configs/agents/weather_agent.yaml"})]}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"step-8-testing-the-weather-agent",children:"Step 8: Testing the Weather Agent"}),"\n",(0,r.jsx)(n.p,{children:"You can test your weather agent with these example requests:"}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Current Weather:"})}),"\n",(0,r.jsxs)(n.blockquote,{children:["\n",(0,r.jsx)(n.p,{children:'"What\'s the current weather in New York City?"'}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Weather Forecast:"})}),"\n",(0,r.jsxs)(n.blockquote,{children:["\n",(0,r.jsx)(n.p,{children:'"Can you give me a 5-day forecast for London, UK and save it to a file?"'}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Weather with File Save:"})}),"\n",(0,r.jsxs)(n.blockquote,{children:["\n",(0,r.jsx)(n.p,{children:'"Get the current weather for Tokyo, Japan and save the report"'}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"key-features-demonstrated",children:"Key Features Demonstrated"}),"\n",(0,r.jsx)(n.h3,{id:"1-external-api-integration",children:"1. External API Integration"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Proper HTTP session management"}),"\n",(0,r.jsx)(n.li,{children:"Error handling for network issues"}),"\n",(0,r.jsx)(n.li,{children:"API response transformation"}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"2-resource-management",children:"2. Resource Management"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Lifecycle functions for initialization and cleanup"}),"\n",(0,r.jsx)(n.li,{children:"Shared service instances across tool calls"}),"\n",(0,r.jsx)(n.li,{children:"Proper resource disposal"}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"3-configuration-management",children:"3. Configuration Management"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Pydantic models for type-safe configuration"}),"\n",(0,r.jsx)(n.li,{children:"Environment variable integration"}),"\n",(0,r.jsx)(n.li,{children:"Flexible tool configuration"}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"4-error-handling",children:"4. Error Handling"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Comprehensive exception handling"}),"\n",(0,r.jsx)(n.li,{children:"User-friendly error messages"}),"\n",(0,r.jsx)(n.li,{children:"Logging for debugging"}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"5-artifact-management",children:"5. Artifact Management"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Saving structured data as files"}),"\n",(0,r.jsx)(n.li,{children:"Metadata enrichment"}),"\n",(0,r.jsx)(n.li,{children:"File format handling"}),"\n"]})]})}function h(e={}){const{wrapper:n}={...(0,o.R)(),...e.components};return n?(0,r.jsx)(n,{...e,children:(0,r.jsx)(d,{...e})}):d(e)}},8453:(e,n,t)=>{t.d(n,{R:()=>i,x:()=>s});var a=t(6540);const r={},o=a.createContext(r);function i(e){const n=a.useContext(o);return a.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function s(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:i(e.components),a.createElement(o.Provider,{value:n},e.children)}}}]);
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use strict";(self.webpackChunksolace_agenitc_mesh_docs=self.webpackChunksolace_agenitc_mesh_docs||[]).push([[5525],{5822:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>d,contentTitle:()=>o,default:()=>h,frontMatter:()=>r,metadata:()=>i,toc:()=>c});const i=JSON.parse('{"id":"documentation/installing-and-configuring/configurations","title":"Configuring Agent Mesh","description":"The shared_config.yaml file is used to define configurations that can be shared across multiple agents or components in Agent Mesh. This centralized approach simplifies management of common configurations such as Solace event broker connections, language model settings, and service definitions.","source":"@site/docs/documentation/installing-and-configuring/configurations.md","sourceDirName":"documentation/installing-and-configuring","slug":"/documentation/installing-and-configuring/configurations","permalink":"/solace-agent-mesh/docs/documentation/installing-and-configuring/configurations","draft":false,"unlisted":false,"editUrl":"https://github.com/SolaceLabs/solace-agent-mesh/edit/main/docs/docs/documentation/installing-and-configuring/configurations.md","tags":[],"version":"current","sidebarPosition":330,"frontMatter":{"title":"Configuring Agent Mesh","sidebar_position":330,"toc_max_heading_level":4},"sidebar":"docSidebar","previous":{"title":"Creating and Running an Agent Mesh Project","permalink":"/solace-agent-mesh/docs/documentation/installing-and-configuring/run-project"},"next":{"title":"Configuring LLMs","permalink":"/solace-agent-mesh/docs/documentation/installing-and-configuring/large_language_models"}}');var s=n(4848),l=n(8453);const r={title:"Configuring Agent Mesh",sidebar_position:330,toc_max_heading_level:4},o=void 0,d={},c=[{value:"Understanding Shared Configuration",id:"understanding-shared-configuration",level:2},{value:"Managing Multiple Shared Configuration Files",id:"managing-multiple-shared-configuration-files",level:3},{value:"Configuration Structure",id:"configuration-structure",level:2},{value:"Event Broker Connection",id:"event-broker-connection",level:2},{value:"LLM Configuration",id:"llm-configuration",level:2},{value:"Model Configuration Parameters",id:"model-configuration-parameters",level:3},{value:"Predefined Model Types",id:"predefined-model-types",level:3},{value:"Service Configuration",id:"service-configuration",level:2},{value:"Session Service",id:"session-service",level:3},{value:"Artifact Service",id:"artifact-service",level:3},{value:"Data Tools Configuration",id:"data-tools-configuration",level:3}];function a(e){const t={a:"a",admonition:"admonition",code:"code",h2:"h2",h3:"h3",li:"li",p:"p",pre:"pre",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,l.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsxs)(t.p,{children:["The ",(0,s.jsx)(t.code,{children:"shared_config.yaml"})," file is used to define configurations that can be shared across multiple agents or components in Agent Mesh. This centralized approach simplifies management of common configurations such as Solace event broker connections, language model settings, and service definitions."]}),"\n",(0,s.jsx)(t.h2,{id:"understanding-shared-configuration",children:"Understanding Shared Configuration"}),"\n",(0,s.jsxs)(t.p,{children:["All agents and gateways require access to a ",(0,s.jsx)(t.code,{children:"shared_config"})," object. You can provide configuration in the following ways:"]}),"\n",(0,s.jsxs)(t.ul,{children:["\n",(0,s.jsx)(t.li,{children:"hard-coding configuration values directly within your agent or gateway YAML files. This method works for simple setups or quick prototyping, but it becomes unwieldy as your deployment grows."}),"\n",(0,s.jsxs)(t.li,{children:["using the ",(0,s.jsx)(t.code,{children:"!include"})," directive to reference a centralized configuration file. This approach promotes consistency and simplifies maintenance across your entire project."]}),"\n"]}),"\n",(0,s.jsxs)(t.p,{children:["When a plugin is installed, it may come with hard-coded default values. It is a best practice to remove this section and use ",(0,s.jsx)(t.code,{children:"!include"})," to point to the centralized ",(0,s.jsx)(t.code,{children:"shared_config"})," file. This ensures that all components are using the same base configuration."]}),"\n",(0,s.jsx)(t.h3,{id:"managing-multiple-shared-configuration-files",children:"Managing Multiple Shared Configuration Files"}),"\n",(0,s.jsx)(t.p,{children:"You can use multiple shared configuration files to manage different environments or setups (e.g., for different cloud providers), as follows:"}),"\n",(0,s.jsxs)(t.ul,{children:["\n",(0,s.jsxs)(t.li,{children:["\n",(0,s.jsxs)(t.p,{children:["The filename must always begin with ",(0,s.jsx)(t.code,{children:"shared_config"}),", followed by any descriptive suffix that helps identify the configuration's purpose. Examples include ",(0,s.jsx)(t.code,{children:"shared_config_aws.yaml"})," for Amazon Web Services deployments or ",(0,s.jsx)(t.code,{children:"shared_config_production.yaml"})," for production environments. This naming convention ensures the system can locate and process these files correctly."]}),"\n"]}),"\n",(0,s.jsxs)(t.li,{children:["\n",(0,s.jsxs)(t.p,{children:["You can organize configuration files into subdirectories to further improve project structure. For instance, you might place files in ",(0,s.jsx)(t.code,{children:"configs/agents/shared_config.yaml"})," or ",(0,s.jsx)(t.code,{children:"environments/dev/shared_config_dev.yaml"}),". When you use subdirectories, you must update the ",(0,s.jsx)(t.code,{children:"!include"})," path in your agent or gateway configurations to reflect the correct file location."]}),"\n"]}),"\n"]}),"\n",(0,s.jsxs)(t.p,{children:["The configuration file uses YAML anchors (",(0,s.jsx)(t.code,{children:"&anchor_name"}),") to create reusable configuration blocks, which can then be referenced in agent configuration files."]}),"\n",(0,s.jsx)(t.h2,{id:"configuration-structure",children:"Configuration Structure"}),"\n",(0,s.jsxs)(t.p,{children:["The following example shows the structure of the ",(0,s.jsx)(t.code,{children:"shared_config.yaml"})," configuration file:"]}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-yaml",children:"shared_config:\n - broker_connection: &broker_connection\n dev_mode: ${SOLACE_DEV_MODE, false}\n broker_url: ${SOLACE_BROKER_URL, ws://localhost:8008}\n broker_username: ${SOLACE_BROKER_USERNAME, default}\n broker_password: ${SOLACE_BROKER_PASSWORD, default}\n broker_vpn: ${SOLACE_BROKER_VPN, default}\n temporary_queue: ${USE_TEMPORARY_QUEUES, true}\n # Ensure high enough limits if many agents are running\n # max_connection_retries: -1 # Retry forever\n\n - models:\n planning: &planning_model\n # This dictionary structure tells ADK to use the LiteLlm wrapper.\n # 'model' uses the specific model identifier your endpoint expects.\n model: ${LLM_SERVICE_PLANNING_MODEL_NAME} # Use env var for model name\n # 'api_base' tells LiteLLM where to send the request.\n api_base: ${LLM_SERVICE_ENDPOINT} # Use env var for endpoint URL\n # 'api_key' provides authentication.\n api_key: ${LLM_SERVICE_API_KEY} # Use env var for API key\n # Enable parallel tool calls for planning model\n parallel_tool_calls: true\n # Prompt Caching Strategy\n cache_strategy: \"5m\" # none, 5m, 1h\n # max_tokens: ${MAX_TOKENS, 16000} # Set a reasonable max token limit for planning\n # temperature: 0.1 # Lower temperature for more deterministic planning\n \n general: &general_model\n # This dictionary structure tells ADK to use the LiteLlm wrapper.\n # 'model' uses the specific model identifier your endpoint expects.\n model: ${LLM_SERVICE_GENERAL_MODEL_NAME} # Use env var for model name\n # 'api_base' tells LiteLLM where to send the request.\n api_base: ${LLM_SERVICE_ENDPOINT} # Use env var for endpoint URL\n # 'api_key' provides authentication.\n api_key: ${LLM_SERVICE_API_KEY} # Use env var for API key\n\n # ... (similar structure)\n\n - services:\n # Default session service configuration\n session_service: &default_session_service\n type: \"memory\"\n default_behavior: \"PERSISTENT\"\n \n # Default artifact service configuration\n artifact_service: &default_artifact_service\n type: \"filesystem\"\n base_path: \"/tmp/samv2\"\n artifact_scope: namespace\n \n # Default data tools configuration\n data_tools_config: &default_data_tools_config\n sqlite_memory_threshold_mb: 100\n max_result_preview_rows: 50\n max_result_preview_bytes: 4096\n"})}),"\n",(0,s.jsx)(t.h2,{id:"event-broker-connection",children:"Event Broker Connection"}),"\n",(0,s.jsxs)(t.p,{children:["The ",(0,s.jsx)(t.code,{children:"broker_connection"})," section configures the connection to the Solace event broker. The connection parameters are described in the following table:"]}),"\n",(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{style:{textAlign:"left"},children:"Parameter"}),(0,s.jsx)(t.th,{style:{textAlign:"left"},children:"Environment Variable"}),(0,s.jsx)(t.th,{style:{textAlign:"left"},children:"Description"}),(0,s.jsx)(t.th,{style:{textAlign:"left"},children:"Default"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"dev_mode"})}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"SOLACE_DEV_MODE"})}),(0,s.jsxs)(t.td,{style:{textAlign:"left"},children:["When set to ",(0,s.jsx)(t.code,{children:"true"}),", uses an in-memory broker for testing."]}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"false"})})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"broker_url"})}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"SOLACE_BROKER_URL"})}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:"The URL of the Solace event broker."}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"ws://localhost:8008"})})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"broker_username"})}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"SOLACE_BROKER_USERNAME"})}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:"The username for authenticating with the event broker."}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"default"})})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"broker_password"})}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"SOLACE_BROKER_PASSWORD"})}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:"The password for authenticating with the event broker."}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"default"})})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"broker_vpn"})}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"SOLACE_BROKER_VPN"})}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:"The Message VPN to connect to on the event broker."}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"default"})})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"temporary_queue"})}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"USE_TEMPORARY_QUEUES"})}),(0,s.jsxs)(t.td,{style:{textAlign:"left"},children:["Whether to use temporary queues for communication. If ",(0,s.jsx)(t.code,{children:"false"}),", a durable queue will be created."]}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"true"})})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"max_connection_retries"})}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"MAX_CONNECTION_RETRIES"})}),(0,s.jsxs)(t.td,{style:{textAlign:"left"},children:["The maximum number of times to retry connecting to the event broker if the connection fails. A value of ",(0,s.jsx)(t.code,{children:"-1"})," means retry forever."]}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"-1"})})]})]})]}),"\n",(0,s.jsx)(t.admonition,{type:"tip",children:(0,s.jsxs)(t.p,{children:["If you need to configure multiple brokers, you can do so by adding additional entries under ",(0,s.jsx)(t.code,{children:"shared_config"})," with a unique name (For example, ",(0,s.jsx)(t.code,{children:"broker_connection_eu: &broker_connection_eu"})," or ",(0,s.jsx)(t.code,{children:"broker_connection_us: &broker_connection_us"}),"). Reference these configurations in your agent files using the appropriate anchor, such as ",(0,s.jsx)(t.code,{children:"<<: *broker_connection_eu"}),"."]})}),"\n",(0,s.jsxs)(t.admonition,{type:"info",children:[(0,s.jsxs)(t.p,{children:["Setting the ",(0,s.jsx)(t.code,{children:"temporary_queue"})," parameter to ",(0,s.jsx)(t.code,{children:"true"})," (default) will use ",(0,s.jsx)(t.a,{href:"https://docs.solace.com/Messaging/Guaranteed-Msg/Endpoints.htm#temporary-endpoints",children:"temporary endpoints"})," for A2A communication. Temporary queues are automatically created and deleted by the broker, which simplifies management and reduces the need for manual cleanup. However, it does not allow for multiple client connections to the same queue, which may be a limitation in some scenarios where you're running multiple instances of the same agent or a new instance needs to be started while an old instance is still running."]}),(0,s.jsxs)(t.p,{children:["If you set ",(0,s.jsx)(t.code,{children:"temporary_queue"})," to ",(0,s.jsx)(t.code,{children:"false"}),", the system will create a durable queue for the client. Durable queues persist beyond the lifetime of the client connection, allowing multiple clients to connect to the same queue and ensuring that messages are not lost if the client disconnects. However, this requires manual management of the queues, including cleanup of unused queues."]}),(0,s.jsxs)(t.p,{children:["Check the ",(0,s.jsx)(t.a,{href:"/solace-agent-mesh/docs/documentation/deploying/deployment-options#setting-up-queue-templates",children:"Setting up Queue Templates"})," section for guidance on configuring queue templates to manage message TTL."]})]}),"\n",(0,s.jsx)(t.h2,{id:"llm-configuration",children:"LLM Configuration"}),"\n",(0,s.jsxs)(t.p,{children:["The models section configures the various Large Language Models and other generative models that power your agents' intelligence. This configuration leverages the ",(0,s.jsx)(t.a,{href:"https://litellm.ai/",children:"LiteLLM"})," library, which provides a standardized interface for interacting with ",(0,s.jsx)(t.a,{href:"https://docs.litellm.ai/docs/providers",children:"different model providers"}),", simplifying the process of switching between or combining multiple AI services."]}),"\n",(0,s.jsx)(t.h3,{id:"model-configuration-parameters",children:"Model Configuration Parameters"}),"\n",(0,s.jsxs)(t.p,{children:["Each model configuration requires specific parameters that tell the system how to communicate with the model provider. The model parameter specifies the exact model identifier in the format expected by your provider, such as ",(0,s.jsx)(t.code,{children:"openai/gpt-4"})," or ",(0,s.jsx)(t.code,{children:"anthropic/claude-3-opus-20240229"}),". The API base URL points to your provider's endpoint, but some providers use default endpoints that don't require explicit specification."]}),"\n",(0,s.jsx)(t.p,{children:"Authentication typically requires an API key, but some providers use alternative authentication mechanisms. Additional parameters control model behavior, such as enabling parallel tool calls for models that support this feature, setting maximum token limits to control response length and costs, and adjusting temperature values to influence response creativity versus determinism."}),"\n",(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{style:{textAlign:"left"},children:"Parameter"}),(0,s.jsx)(t.th,{style:{textAlign:"left"},children:"Environment Variable"}),(0,s.jsx)(t.th,{style:{textAlign:"left"},children:"Description"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"model"})}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"LLM_SERVICE_<MODEL_NAME>_MODEL_NAME"})}),(0,s.jsxs)(t.td,{style:{textAlign:"left"},children:["The specific model identifier that the endpoint expects in the format of ",(0,s.jsx)(t.code,{children:"provider/model"})," (e.g., ",(0,s.jsx)(t.code,{children:"openai/gpt-4"}),", ",(0,s.jsx)(t.code,{children:"anthropic/claude-3-opus-20240229"}),")."]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"api_base"})}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"LLM_SERVICE_ENDPOINT"})}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:"The base URL of the LLM provider's API endpoint."})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"api_key"})}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"LLM_SERVICE_API_KEY"})}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:"The API key for authenticating with the service."})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"parallel_tool_calls"})}),(0,s.jsx)(t.td,{style:{textAlign:"left"}}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:"Enable parallel tool calls for the model."})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"cache_strategy"})}),(0,s.jsx)(t.td,{style:{textAlign:"left"}}),(0,s.jsxs)(t.td,{style:{textAlign:"left"},children:["Set the prompt caching strategy (one of: ",(0,s.jsx)(t.code,{children:"none"}),", ",(0,s.jsx)(t.code,{children:"5m"}),", ",(0,s.jsx)(t.code,{children:"1h"}),"). For more details check ",(0,s.jsx)(t.a,{href:"/solace-agent-mesh/docs/documentation/installing-and-configuring/large_language_models#prompt-caching",children:"LLM Configuration"})," page."]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"max_tokens"})}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"MAX_TOKENS"})}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:"Set a reasonable max token limit for the model."})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"temperature"})}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"TEMPERATURE"})}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:"Lower temperature for more deterministic planning."})]})]})]}),"\n",(0,s.jsx)(t.p,{children:"For Google's Gemini models, you can use a simplified configuration approach that references the model directly:"}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-yaml",children:"model: gemini-2.5-pro\n"})}),"\n",(0,s.jsxs)(t.p,{children:["For detailed information about configuring Gemini models and setting up the required environment variables, see the ",(0,s.jsx)(t.a,{href:"https://google.github.io/adk-docs/agents/models/#using-google-gemini-models",children:"Gemini model documentation"}),"."]}),"\n",(0,s.jsx)(t.h3,{id:"predefined-model-types",children:"Predefined Model Types"}),"\n",(0,s.jsxs)(t.p,{children:["The ",(0,s.jsx)(t.code,{children:"shared_config.yaml"})," configuration file defines predefined model types that serve as aliases for specific use cases. These aliases allow you to reference models by their intended purpose rather than their technical specifications, making your agent configurations more readable and maintainable. The model types are as follows:"]}),"\n",(0,s.jsxs)(t.ul,{children:["\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.code,{children:"planning"}),": Used by agents for planning and decision-making. It's configured for deterministic outputs (",(0,s.jsx)(t.code,{children:"temperature: 0.1"}),") and can use tools in parallel."]}),"\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.code,{children:"general"}),": A general-purpose model for various tasks."]}),"\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.code,{children:"image_gen"}),": A model for generating images."]}),"\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.code,{children:"image_describe"}),": A model for describing the content of images."]}),"\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.code,{children:"audio_transcription"}),": A model for transcribing audio files."]}),"\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.code,{children:"report_gen"}),": A model specialized for generating reports."]}),"\n",(0,s.jsxs)(t.li,{children:[(0,s.jsx)(t.code,{children:"multimodal"}),": A simple string reference to a multimodal model (e.g., ",(0,s.jsx)(t.code,{children:'"gemini-1.5-flash-latest"'}),")."]}),"\n"]}),"\n",(0,s.jsxs)(t.p,{children:["You can define any number of models in this section and reference them in your agent configurations. The system uses only the ",(0,s.jsx)(t.code,{children:"planning"})," and ",(0,s.jsx)(t.code,{children:"general"})," models by default; you don't need to configure the specialized models unless your agents specifically require those capabilities."]}),"\n",(0,s.jsxs)(t.p,{children:["For information about configuring different LLM providers and SSL/TLS security settings, see ",(0,s.jsx)(t.a,{href:"/solace-agent-mesh/docs/documentation/installing-and-configuring/large_language_models",children:"Configuring LLMs"}),"."]}),"\n",(0,s.jsx)(t.h2,{id:"service-configuration",children:"Service Configuration"}),"\n",(0,s.jsxs)(t.p,{children:["The ",(0,s.jsx)(t.code,{children:"services"})," section in ",(0,s.jsx)(t.code,{children:"shared_config.yaml"})," is used to configure various services that are available to agents. These services handle concerns such as session persistence, artifact storage, and data processing optimization."]}),"\n",(0,s.jsx)(t.h3,{id:"session-service",children:"Session Service"}),"\n",(0,s.jsx)(t.p,{children:"The session service manages conversation history and context persistence across agent interactions. This service determines whether agents remember previous conversations and how long that memory persists."}),"\n",(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{style:{textAlign:"left"},children:"Parameter"}),(0,s.jsx)(t.th,{style:{textAlign:"left"},children:"Options"}),(0,s.jsx)(t.th,{style:{textAlign:"left"},children:"Description"}),(0,s.jsx)(t.th,{style:{textAlign:"left"},children:"Default"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"type"})}),(0,s.jsxs)(t.td,{style:{textAlign:"left"},children:[(0,s.jsx)(t.code,{children:"memory"}),", ",(0,s.jsx)(t.code,{children:"sql"}),", ",(0,s.jsx)(t.code,{children:"vertex_rag"})]}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:"Configuration for ADK Session Service"}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"memory"})})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"default_behavior"})}),(0,s.jsxs)(t.td,{style:{textAlign:"left"},children:[(0,s.jsx)(t.code,{children:"PERSISTENT"}),", ",(0,s.jsx)(t.code,{children:"RUN_BASED"})]}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:"The default behavior of keeping the session history"}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"PERSISTENT"})})]})]})]}),"\n",(0,s.jsx)(t.admonition,{type:"tip",children:(0,s.jsxs)(t.p,{children:["Although the default session service type is ",(0,s.jsx)(t.code,{children:"memory"}),", both Orchestrator Agent and Web UI gateway use ",(0,s.jsx)(t.code,{children:"sql"})," as their session service to allow for persistent sessions."]})}),"\n",(0,s.jsx)(t.h3,{id:"artifact-service",children:"Artifact Service"}),"\n",(0,s.jsxs)(t.p,{children:["The ",(0,s.jsx)(t.code,{children:"artifact_service"})," is responsible for managing artifacts, which are files or data generated by agents, such as generated documents, processed data files, and intermediate results."]}),"\n",(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{style:{textAlign:"left"},children:"Parameter"}),(0,s.jsx)(t.th,{style:{textAlign:"left"},children:"Options"}),(0,s.jsx)(t.th,{style:{textAlign:"left"},children:"Description"}),(0,s.jsx)(t.th,{style:{textAlign:"left"},children:"Default"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"type"})}),(0,s.jsxs)(t.td,{style:{textAlign:"left"},children:[(0,s.jsx)(t.code,{children:"memory"}),", ",(0,s.jsx)(t.code,{children:"gcs"}),", ",(0,s.jsx)(t.code,{children:"filesystem"})]}),(0,s.jsxs)(t.td,{style:{textAlign:"left"},children:["Service type for artifact storage. Use ",(0,s.jsx)(t.code,{children:"memory"})," for in-memory, ",(0,s.jsx)(t.code,{children:"gcs"})," for Google Cloud Storage, or ",(0,s.jsx)(t.code,{children:"filesystem"})," for local file storage."]}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"memory"})})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"base_path"})}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:"local path"}),(0,s.jsxs)(t.td,{style:{textAlign:"left"},children:["Base directory path for storing artifacts. Required only if ",(0,s.jsx)(t.code,{children:"type"})," is ",(0,s.jsx)(t.code,{children:"filesystem"}),"."]}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:"(none)"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"bucket_name"})}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:"bucket name"}),(0,s.jsxs)(t.td,{style:{textAlign:"left"},children:["Google Cloud Storage bucket name. Required only if ",(0,s.jsx)(t.code,{children:"type"})," is ",(0,s.jsx)(t.code,{children:"gcs"}),"."]}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:"(none)"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"artifact_scope"})}),(0,s.jsxs)(t.td,{style:{textAlign:"left"},children:[(0,s.jsx)(t.code,{children:"namespace"}),", ",(0,s.jsx)(t.code,{children:"app"})]}),(0,s.jsxs)(t.td,{style:{textAlign:"left"},children:["Scope for artifact sharing. ",(0,s.jsx)(t.code,{children:"namespace"}),": shared by all components in the namespace. ",(0,s.jsx)(t.code,{children:"app"}),": isolated by agent/gateway name. Must be consistent for all components in the same process."]}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"namespace"})})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"artifact_scope_value"})}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:"custom scope id"}),(0,s.jsxs)(t.td,{style:{textAlign:"left"},children:["Custom identifier for artifact scope. Required if ",(0,s.jsx)(t.code,{children:"artifact_scope"})," is set to a custom value."]}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:"(none)"})]})]})]}),"\n",(0,s.jsx)(t.h3,{id:"data-tools-configuration",children:"Data Tools Configuration"}),"\n",(0,s.jsx)(t.p,{children:"The data tools configuration optimizes how agents handle data analysis and processing tasks. These settings balance performance, memory usage, and user experience when agents work with databases and large datasets."}),"\n",(0,s.jsx)(t.p,{children:"The SQLite memory threshold determines when the system switches from disk-based to memory-based database operations. Lower thresholds favor memory usage for better performance although consume more system RAM. Higher thresholds reduce memory pressure although may slow database operations."}),"\n",(0,s.jsx)(t.p,{children:"Result preview settings control how much data agents display when showing query results or data samples. These limits prevent overwhelming users with massive datasets while ensuring they see enough information to understand the results."}),"\n",(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{style:{textAlign:"left"},children:"Parameter"}),(0,s.jsx)(t.th,{style:{textAlign:"left"},children:"Type"}),(0,s.jsx)(t.th,{style:{textAlign:"left"},children:"Description"}),(0,s.jsx)(t.th,{style:{textAlign:"left"},children:"Default"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"sqlite_memory_threshold_mb"})}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"integer"})}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:"The memory threshold in megabytes for using an in-memory SQLite database."}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"100"})})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"max_result_preview_rows"})}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"integer"})}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:"The maximum number of rows to show in a result preview."}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"50"})})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"max_result_preview_bytes"})}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"integer"})}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:"The maximum number of bytes to show in a result preview."}),(0,s.jsx)(t.td,{style:{textAlign:"left"},children:(0,s.jsx)(t.code,{children:"4096"})})]})]})]})]})}function h(e={}){const{wrapper:t}={...(0,l.R)(),...e.components};return t?(0,s.jsx)(t,{...e,children:(0,s.jsx)(a,{...e})}):a(e)}},8453:(e,t,n)=>{n.d(t,{R:()=>r,x:()=>o});var i=n(6540);const s={},l=i.createContext(s);function r(e){const t=i.useContext(l);return i.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function o(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:r(e.components),i.createElement(l.Provider,{value:t},e.children)}}}]);
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use strict";(self.webpackChunksolace_agenitc_mesh_docs=self.webpackChunksolace_agenitc_mesh_docs||[]).push([[3349],{3085:(e,n,o)=>{o.r(n),o.d(n,{assets:()=>r,contentTitle:()=>l,default:()=>h,frontMatter:()=>a,metadata:()=>t,toc:()=>c});const t=JSON.parse('{"id":"documentation/developing/creating-python-tools","title":"Creating Python Tools","description":"Agent Mesh provides a powerful and unified system for creating custom agent tools using Python. This is the primary way to extend an agent\'s capabilities with your own business logic, integrate with proprietary APIs, or perform specialized data processing.","source":"@site/docs/documentation/developing/creating-python-tools.md","sourceDirName":"documentation/developing","slug":"/documentation/developing/creating-python-tools","permalink":"/solace-agent-mesh/docs/documentation/developing/creating-python-tools","draft":false,"unlisted":false,"editUrl":"https://github.com/SolaceLabs/solace-agent-mesh/edit/main/docs/docs/documentation/developing/creating-python-tools.md","tags":[],"version":"current","sidebarPosition":440,"frontMatter":{"title":"Creating Python Tools","sidebar_position":440},"sidebar":"docSidebar","previous":{"title":"Create Gateways","permalink":"/solace-agent-mesh/docs/documentation/developing/create-gateways"},"next":{"title":"Creating Service Providers","permalink":"/solace-agent-mesh/docs/documentation/developing/creating-service-providers"}}');var i=o(4848),s=o(8453);const a={title:"Creating Python Tools",sidebar_position:440},l="Creating Python Tools",r={},c=[{value:"Tool Creation Patterns",id:"tool-creation-patterns",level:2},{value:"Pattern 1: Simple Function-Based Tools",id:"pattern-1-simple-function-based-tools",level:2},{value:"Step 1: Write the Tool Function",id:"step-1-write-the-tool-function",level:3},{value:"Step 2: Configure the Tool",id:"step-2-configure-the-tool",level:3},{value:"Pattern 2: Advanced Single-Class Tools",id:"pattern-2-advanced-single-class-tools",level:2},{value:"Step 1: Create the <code>DynamicTool</code> Class",id:"step-1-create-the-dynamictool-class",level:3},{value:"Step 2: Configure the Tool",id:"step-2-configure-the-tool-1",level:3},{value:"Pattern 3: The Tool Provider Factory",id:"pattern-3-the-tool-provider-factory",level:2},{value:"Step 1: Create the Provider and Tools",id:"step-1-create-the-provider-and-tools",level:3},{value:"Step 2: Configure the Provider",id:"step-2-configure-the-provider",level:3},{value:"Managing Tool Lifecycles with <code>init</code> and <code>cleanup</code>",id:"managing-tool-lifecycles-with-init-and-cleanup",level:2},{value:"YAML-Based Lifecycle Hooks",id:"yaml-based-lifecycle-hooks",level:3},{value:"Step 1: Define the Tool and Hook Functions",id:"step-1-define-the-tool-and-hook-functions",level:4},{value:"Step 2: Configure the Hooks in YAML",id:"step-2-configure-the-hooks-in-yaml",level:4},{value:"Class-Based Lifecycle Methods (for <code>DynamicTool</code>)",id:"class-based-lifecycle-methods-for-dynamictool",level:3},{value:"Execution Order and Guarantees",id:"execution-order-and-guarantees",level:3},{value:"Adding Validated Configuration to Dynamic Tools",id:"adding-validated-configuration-to-dynamic-tools",level:2},{value:"Example 1: Using a Pydantic Model with a Single <code>DynamicTool</code>",id:"example-1-using-a-pydantic-model-with-a-single-dynamictool",level:3},{value:"Step 1: Define the Model and Tool Class",id:"step-1-define-the-model-and-tool-class",level:4},{value:"Step 2: Configure the Tool in YAML",id:"step-2-configure-the-tool-in-yaml",level:4},{value:"Example 2: Using a Pydantic Model with a <code>DynamicToolProvider</code>",id:"example-2-using-a-pydantic-model-with-a-dynamictoolprovider",level:3},{value:"Step 1: Define the Model and Provider Class",id:"step-1-define-the-model-and-provider-class",level:4},{value:"Step 2: Configure the Provider in YAML",id:"step-2-configure-the-provider-in-yaml",level:4}];function d(e){const n={code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",hr:"hr",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,s.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(n.header,{children:(0,i.jsx)(n.h1,{id:"creating-python-tools",children:"Creating Python Tools"})}),"\n",(0,i.jsx)(n.p,{children:"Agent Mesh provides a powerful and unified system for creating custom agent tools using Python. This is the primary way to extend an agent's capabilities with your own business logic, integrate with proprietary APIs, or perform specialized data processing."}),"\n",(0,i.jsxs)(n.p,{children:["This guide covers the different patterns for creating custom tools, all of which are configured using the versatile ",(0,i.jsx)(n.code,{children:"tool_type: python"}),"."]}),"\n",(0,i.jsx)(n.h2,{id:"tool-creation-patterns",children:"Tool Creation Patterns"}),"\n",(0,i.jsx)(n.p,{children:"There are three primary patterns for creating Python tools, ranging from simple to advanced. You can choose the best pattern for your needs, and even mix and match them within the same project."}),"\n",(0,i.jsxs)(n.table,{children:[(0,i.jsx)(n.thead,{children:(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.th,{children:"Pattern"}),(0,i.jsx)(n.th,{children:"Best For"}),(0,i.jsx)(n.th,{children:"Key Feature"})]})}),(0,i.jsxs)(n.tbody,{children:[(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.strong,{children:"Function-Based"})}),(0,i.jsx)(n.td,{children:"Simple, self-contained tools with static inputs."}),(0,i.jsx)(n.td,{children:"Quick and easy; uses function signature."})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsxs)(n.strong,{children:["Single ",(0,i.jsx)(n.code,{children:"DynamicTool"})," Class"]})}),(0,i.jsx)(n.td,{children:"Tools that require complex logic or a programmatically defined interface."}),(0,i.jsx)(n.td,{children:"Full control over the tool's definition."})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsxs)(n.strong,{children:[(0,i.jsx)(n.code,{children:"DynamicToolProvider"})," Class"]})}),(0,i.jsx)(n.td,{children:"Generating multiple related tools from a single, configurable source."}),(0,i.jsx)(n.td,{children:"Maximum scalability and code reuse."})]})]})]}),"\n",(0,i.jsxs)(n.p,{children:["All three patterns are configured in your agent's YAML file under the ",(0,i.jsx)(n.code,{children:"tools"})," list with ",(0,i.jsx)(n.code,{children:"tool_type: python"}),"."]}),"\n",(0,i.jsx)(n.hr,{}),"\n",(0,i.jsx)(n.h2,{id:"pattern-1-simple-function-based-tools",children:"Pattern 1: Simple Function-Based Tools"}),"\n",(0,i.jsxs)(n.p,{children:["This is the most straightforward way to create a custom tool. You define a standard Python ",(0,i.jsx)(n.code,{children:"async"})," function, and Agent Mesh automatically introspects its signature and docstring to create the tool definition for the LLM."]}),"\n",(0,i.jsx)(n.h3,{id:"step-1-write-the-tool-function",children:"Step 1: Write the Tool Function"}),"\n",(0,i.jsxs)(n.p,{children:["Create a Python file (e.g., ",(0,i.jsx)(n.code,{children:"src/my_agent/tools.py"}),") and define your tool."]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-python",children:'# src/my_agent/tools.py\nfrom typing import Any, Dict, Optional\nfrom google.adk.tools import ToolContext\n\nasync def greet_user(\n name: str,\n tool_context: Optional[ToolContext] = None,\n tool_config: Optional[Dict[str, Any]] = None\n) -> Dict[str, Any]:\n """\n Greets a user with a personalized message.\n\n Args:\n name: The name of the person to greet.\n\n Returns:\n A dictionary with the greeting message.\n """\n greeting_prefix = "Hello"\n if tool_config:\n greeting_prefix = tool_config.get("greeting_prefix", "Hello")\n\n greeting_message = f"{greeting_prefix}, {name}! Welcome to Agent Mesh!"\n\n return {\n "status": "success",\n "message": greeting_message\n }\n'})}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Key Requirements:"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["The function must be ",(0,i.jsx)(n.code,{children:"async def"}),"."]}),"\n",(0,i.jsxs)(n.li,{children:["The function's docstring is used as the tool's ",(0,i.jsx)(n.code,{children:"description"})," for the LLM."]}),"\n",(0,i.jsxs)(n.li,{children:["Type hints (",(0,i.jsx)(n.code,{children:"str"}),", ",(0,i.jsx)(n.code,{children:"int"}),", ",(0,i.jsx)(n.code,{children:"bool"}),") are used to generate the parameter schema."]}),"\n",(0,i.jsxs)(n.li,{children:["The function should accept ",(0,i.jsx)(n.code,{children:"tool_context"})," and ",(0,i.jsx)(n.code,{children:"tool_config"})," as optional keyword arguments to receive framework context and YAML configuration."]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"step-2-configure-the-tool",children:"Step 2: Configure the Tool"}),"\n",(0,i.jsxs)(n.p,{children:["In your agent's YAML configuration, add a ",(0,i.jsx)(n.code,{children:"tool_type: python"})," block and point it to your function."]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-yaml",children:'# In your agent\'s app_config:\ntools:\n - tool_type: python\n component_module: "my_agent.tools"\n function_name: "greet_user"\n tool_config:\n greeting_prefix: "Greetings"\n'})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"component_module"}),": The Python module path to your tools file."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"function_name"}),": The exact name of the function to load."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"tool_config"}),": An optional dictionary passed to your tool at runtime."]}),"\n"]}),"\n",(0,i.jsx)(n.hr,{}),"\n",(0,i.jsx)(n.h2,{id:"pattern-2-advanced-single-class-tools",children:"Pattern 2: Advanced Single-Class Tools"}),"\n",(0,i.jsxs)(n.p,{children:["For tools that require more complex logic\u2014such as defining their interface programmatically based on configuration\u2014you can use a class that inherits from ",(0,i.jsx)(n.code,{children:"DynamicTool"}),"."]}),"\n",(0,i.jsxs)(n.h3,{id:"step-1-create-the-dynamictool-class",children:["Step 1: Create the ",(0,i.jsx)(n.code,{children:"DynamicTool"})," Class"]}),"\n",(0,i.jsxs)(n.p,{children:["Instead of a function, define a class that implements the ",(0,i.jsx)(n.code,{children:"DynamicTool"})," abstract base class."]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-python",children:'# src/my_agent/tools.py\nfrom typing import Optional, Dict, Any\nfrom google.genai import types as adk_types\nfrom solace_agent_mesh.agent.tools.dynamic_tool import DynamicTool\n\nclass WeatherTool(DynamicTool):\n """A dynamic tool that fetches current weather information."""\n\n @property\n def tool_name(self) -> str:\n return "get_current_weather"\n\n @property\n def tool_description(self) -> str:\n return "Get the current weather for a specified location."\n\n @property\n def parameters_schema(self) -> adk_types.Schema:\n # Programmatically define the tool\'s parameters\n return adk_types.Schema(\n type=adk_types.Type.OBJECT,\n properties={\n "location": adk_types.Schema(type=adk_types.Type.STRING, description="The city and state/country."),\n "units": adk_types.Schema(type=adk_types.Type.STRING, enum=["celsius", "fahrenheit"], nullable=True),\n },\n required=["location"],\n )\n\n async def _run_async_impl(self, args: Dict[str, Any], **kwargs) -> Dict[str, Any]:\n location = args["location"]\n # Access config via self.tool_config\n api_key = self.tool_config.get("api_key")\n if not api_key:\n return {"status": "error", "message": "API key not configured"}\n # ... implementation to call weather API ...\n return {"status": "success", "weather": "Sunny"}\n'})}),"\n",(0,i.jsx)(n.h3,{id:"step-2-configure-the-tool-1",children:"Step 2: Configure the Tool"}),"\n",(0,i.jsxs)(n.p,{children:["The YAML configuration is very similar. You can either specify the ",(0,i.jsx)(n.code,{children:"class_name"})," or let Agent Mesh auto-discover it if it's the only ",(0,i.jsx)(n.code,{children:"DynamicTool"})," in the module."]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-yaml",children:"# In your agent's app_config:\ntools:\n - tool_type: python\n component_module: \"my_agent.tools\"\n # class_name: WeatherTool # Optional if it's the only one\n tool_config:\n api_key: ${WEATHER_API_KEY}\n"})}),"\n",(0,i.jsx)(n.hr,{}),"\n",(0,i.jsx)(n.h2,{id:"pattern-3-the-tool-provider-factory",children:"Pattern 3: The Tool Provider Factory"}),"\n",(0,i.jsx)(n.p,{children:"This is the most powerful pattern, designed for generating multiple, related tools from a single module and configuration block. It's perfect for creating toolsets based on external schemas, database tables, or other dynamic sources."}),"\n",(0,i.jsx)(n.h3,{id:"step-1-create-the-provider-and-tools",children:"Step 1: Create the Provider and Tools"}),"\n",(0,i.jsxs)(n.p,{children:["In your tools module, you define your ",(0,i.jsx)(n.code,{children:"DynamicTool"})," classes as before, but you also create a ",(0,i.jsx)(n.strong,{children:"provider"})," class that inherits from ",(0,i.jsx)(n.code,{children:"DynamicToolProvider"}),". This provider acts as a factory."]}),"\n",(0,i.jsxs)(n.p,{children:["You can also use the ",(0,i.jsx)(n.code,{children:"@register_tool"})," decorator on simple functions to have them automatically included by the provider."]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-python",children:'# src/my_agent/database_tools.py\nfrom typing import Optional, Dict, Any, List\nfrom google.genai import types as adk_types\nfrom solace_agent_mesh.agent.tools.dynamic_tool import DynamicTool, DynamicToolProvider\n\n# --- Tool Implementations ---\nclass DatabaseQueryTool(DynamicTool):\n # ... (implementation as in previous examples) ...\n pass\n\nclass DatabaseSchemaTool(DynamicTool):\n # ... (implementation as in previous examples) ...\n pass\n\n# --- Tool Provider Implementation ---\nclass DatabaseToolProvider(DynamicToolProvider):\n """A factory that creates all database-related tools."""\n\n # Use a decorator for a simple, function-based tool\n\n def create_tools(self, tool_config: Optional[dict] = None) -> List[DynamicTool]:\n """\n Generates a list of all database tools, passing the shared\n configuration to each one.\n """\n # 1. Create tools from any decorated functions in this module\n tools = self._create_tools_from_decorators(tool_config)\n\n # 2. Programmatically create and add more complex tools\n if tool_config and tool_config.get("connection_string"):\n tools.append(DatabaseQueryTool(tool_config=tool_config))\n tools.append(DatabaseSchemaTool(tool_config=tool_config))\n\n return tools\n\n# NOTE that you must use the decorator outside of any class with the provider\'s class name.\n@DatabaseToolProvider.register_tool\nasync def get_database_server_version(tool_config: dict, **kwargs) -> dict:\n """Returns the version of the connected PostgreSQL server."""\n # ... implementation ...\n return {"version": "PostgreSQL 15.3"}\n\n'})}),"\n",(0,i.jsx)(n.h3,{id:"step-2-configure-the-provider",children:"Step 2: Configure the Provider"}),"\n",(0,i.jsxs)(n.p,{children:["You only need a single YAML block. Agent Mesh will automatically detect the ",(0,i.jsx)(n.code,{children:"DynamicToolProvider"})," and use it to load all the tools it generates."]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-yaml",children:'# In your agent\'s app_config:\ntools:\n # This single block loads get_database_server_version,\n # execute_database_query, and get_database_schema.\n - tool_type: python\n component_module: "my_agent.database_tools"\n tool_config:\n connection_string: ${DB_CONNECTION_STRING}\n max_rows: 1000\n'})}),"\n",(0,i.jsx)(n.p,{children:"This approach is incredibly scalable, as one configuration entry can bootstrap an entire suite of dynamically generated tools."}),"\n",(0,i.jsx)(n.hr,{}),"\n",(0,i.jsxs)(n.h2,{id:"managing-tool-lifecycles-with-init-and-cleanup",children:["Managing Tool Lifecycles with ",(0,i.jsx)(n.code,{children:"init"})," and ",(0,i.jsx)(n.code,{children:"cleanup"})]}),"\n",(0,i.jsxs)(n.p,{children:["For tools that need to manage resources\u2014such as database connections, API clients, or temporary files\u2014Agent Mesh provides optional ",(0,i.jsx)(n.code,{children:"init"})," and ",(0,i.jsx)(n.code,{children:"cleanup"})," lifecycle hooks. These allow you to run code when the agent starts up and shuts down, ensuring that resources are acquired and released gracefully."]}),"\n",(0,i.jsx)(n.p,{children:"There are two ways to define these hooks:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsxs)(n.strong,{children:["YAML-based (",(0,i.jsx)(n.code,{children:"init_function"}),", ",(0,i.jsx)(n.code,{children:"cleanup_function"}),"):"]})," A flexible method that works for ",(0,i.jsx)(n.em,{children:"any"})," Python tool, including simple function-based ones."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsxs)(n.strong,{children:["Class-based (",(0,i.jsx)(n.code,{children:"init"}),", ",(0,i.jsx)(n.code,{children:"cleanup"})," methods):"]})," The idiomatic and recommended way for ",(0,i.jsx)(n.code,{children:"DynamicTool"})," and ",(0,i.jsx)(n.code,{children:"DynamicToolProvider"})," classes."]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"yaml-based-lifecycle-hooks",children:"YAML-Based Lifecycle Hooks"}),"\n",(0,i.jsxs)(n.p,{children:["You can add ",(0,i.jsx)(n.code,{children:"init_function"})," and ",(0,i.jsx)(n.code,{children:"cleanup_function"})," to any Python tool's configuration in your agent's YAML. The lifecycle functions must be defined in the same module as the tool itself."]}),"\n",(0,i.jsx)(n.h4,{id:"step-1-define-the-tool-and-hook-functions",children:"Step 1: Define the Tool and Hook Functions"}),"\n",(0,i.jsxs)(n.p,{children:["In your tool's Python file (e.g., ",(0,i.jsx)(n.code,{children:"src/my_agent/db_tools.py"}),"), define the tool function and its corresponding ",(0,i.jsx)(n.code,{children:"init"})," and ",(0,i.jsx)(n.code,{children:"cleanup"})," functions. These functions must be ",(0,i.jsx)(n.code,{children:"async"})," and will receive the agent component instance and the tool's configuration model object as arguments."]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-python",children:'# src/my_agent/db_tools.py\nfrom solace_agent_mesh.agent.sac.component import SamAgentComponent\nfrom solace_agent_mesh.agent.tools.tool_config_types import AnyToolConfig\nfrom google.adk.tools import ToolContext\nfrom typing import Dict, Any\n\n# --- Lifecycle Hooks ---\n\nasync def initialize_db_connection(component: SamAgentComponent, tool_config_model: AnyToolConfig):\n """Initializes a database connection and stores it for the agent to use."""\n print("INFO: Initializing database connection...")\n # In a real scenario, you would create a client instance\n db_client = {"connection_string": tool_config_model.tool_config.get("connection_string")}\n # Store the client in a shared state accessible by the component\n component.set_agent_specific_state("db_client", db_client)\n print("INFO: Database client initialized.")\n\nasync def close_db_connection(component: SamAgentComponent, tool_config_model: AnyToolConfig):\n """Retrieves and closes the database connection."""\n print("INFO: Closing database connection...")\n db_client = component.get_agent_specific_state("db_client")\n if db_client:\n # In a real scenario, you would call db_client.close()\n print("INFO: Database connection closed.")\n\n# --- Tool Function ---\n\nasync def query_database(query: str, tool_context: ToolContext, **kwargs) -> Dict[str, Any]:\n """Queries the database using the initialized connection."""\n host_component = tool_context._invocation_context.agent.host_component\n db_client = host_component.get_agent_specific_state("db_client")\n if not db_client:\n return {"error": "Database connection not initialized."}\n # ... use db_client to run query ...\n return {"result": "some data"}\n'})}),"\n",(0,i.jsx)(n.h4,{id:"step-2-configure-the-hooks-in-yaml",children:"Step 2: Configure the Hooks in YAML"}),"\n",(0,i.jsxs)(n.p,{children:["In your YAML configuration, reference the lifecycle functions by name. The framework will automatically look for them in the ",(0,i.jsx)(n.code,{children:"component_module"}),"."]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-yaml",children:'# In your agent\'s app_config:\ntools:\n - tool_type: python\n component_module: "my_agent.db_tools"\n function_name: "query_database"\n tool_config:\n connection_string: "postgresql://user:pass@host/db"\n\n # Initialize the tool on startup\n init_function: "initialize_db_connection"\n\n # Clean up the tool on shutdown\n cleanup_function: "close_db_connection"\n'})}),"\n",(0,i.jsxs)(n.h3,{id:"class-based-lifecycle-methods-for-dynamictool",children:["Class-Based Lifecycle Methods (for ",(0,i.jsx)(n.code,{children:"DynamicTool"}),")"]}),"\n",(0,i.jsxs)(n.p,{children:["For tools built with ",(0,i.jsx)(n.code,{children:"DynamicTool"})," or ",(0,i.jsx)(n.code,{children:"DynamicToolProvider"}),", the recommended approach is to override the ",(0,i.jsx)(n.code,{children:"init"})," and ",(0,i.jsx)(n.code,{children:"cleanup"})," methods directly within the class. This co-locates the entire tool's logic and improves encapsulation."]}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsxs)(n.strong,{children:["Example: Adding Lifecycle Methods to a ",(0,i.jsx)(n.code,{children:"DynamicTool"})]})}),"\n",(0,i.jsxs)(n.p,{children:["Here, we extend a ",(0,i.jsx)(n.code,{children:"DynamicTool"})," to manage its own API client."]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-python",children:'# src/my_agent/api_tool.py\nfrom solace_agent_mesh.agent.sac.component import SamAgentComponent\nfrom solace_agent_mesh.agent.tools.dynamic_tool import DynamicTool\nfrom solace_agent_mesh.agent.tools.tool_config_types import AnyToolConfig\n# Assume WeatherApiClient is a custom class for an external service\nfrom my_agent.api_client import WeatherApiClient\n\nclass WeatherTool(DynamicTool):\n """A dynamic tool that fetches weather and manages its own API client."""\n\n async def init(self, component: "SamAgentComponent", tool_config: "AnyToolConfig") -> None:\n """Initializes the API client when the agent starts."""\n print("INFO: Initializing Weather API client...")\n # self.tool_config is the validated Pydantic model or dict from YAML\n api_key = self.tool_config.get("api_key")\n self.api_client = WeatherApiClient(api_key=api_key)\n print("INFO: Weather API client initialized.")\n\n async def cleanup(self, component: "SamAgentComponent", tool_config: "AnyToolConfig") -> None:\n """Closes the API client connection when the agent shuts down."""\n print("INFO: Closing Weather API client...")\n if hasattr(self, "api_client"):\n await self.api_client.close()\n print("INFO: Weather API client closed.")\n\n # ... other required properties like tool_name, tool_description, etc. ...\n\n async def _run_async_impl(self, args: dict, **kwargs) -> dict:\n """Uses the initialized client to perform its task."""\n location = args.get("location")\n if not hasattr(self, "api_client"):\n return {"error": "API client not initialized. Check lifecycle hooks."}\n weather_data = await self.api_client.get_weather(location)\n return {"weather": weather_data}\n'})}),"\n",(0,i.jsx)(n.p,{children:"The YAML configuration remains simple, as the lifecycle logic is now part of the tool's code."}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-yaml",children:'# In your agent\'s app_config:\ntools:\n - tool_type: python\n component_module: "my_agent.api_tool"\n class_name: "WeatherTool"\n tool_config:\n api_key: ${WEATHER_API_KEY}\n'})}),"\n",(0,i.jsx)(n.h3,{id:"execution-order-and-guarantees",children:"Execution Order and Guarantees"}),"\n",(0,i.jsx)(n.p,{children:"It's important to understand the order in which lifecycle hooks are executed, especially if you mix both YAML-based and class-based methods for a single tool."}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:[(0,i.jsxs)(n.strong,{children:["Initialization (",(0,i.jsx)(n.code,{children:"init"}),"):"]})," All ",(0,i.jsx)(n.code,{children:"init"})," hooks are awaited during agent startup. A failure in any ",(0,i.jsx)(n.code,{children:"init"})," hook will prevent the agent from starting."]}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:["The YAML-based ",(0,i.jsx)(n.code,{children:"init_function"})," is executed first."]}),"\n",(0,i.jsxs)(n.li,{children:["The class-based ",(0,i.jsx)(n.code,{children:"init()"})," method is executed second."]}),"\n"]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:[(0,i.jsxs)(n.strong,{children:["Cleanup (",(0,i.jsx)(n.code,{children:"cleanup"}),"):"]})," All registered ",(0,i.jsx)(n.code,{children:"cleanup"})," hooks are executed during agent shutdown. They run in ",(0,i.jsx)(n.strong,{children:"LIFO (Last-In, First-Out)"})," order relative to initialization."]}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:["The class-based ",(0,i.jsx)(n.code,{children:"cleanup()"})," method is executed first."]}),"\n",(0,i.jsxs)(n.li,{children:["The YAML-based ",(0,i.jsx)(n.code,{children:"cleanup_function"})," is executed second."]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:"This LIFO order for cleanup is intuitive: the resource that was initialized last is the first one to be torn down."}),"\n",(0,i.jsx)(n.hr,{}),"\n",(0,i.jsx)(n.h2,{id:"adding-validated-configuration-to-dynamic-tools",children:"Adding Validated Configuration to Dynamic Tools"}),"\n",(0,i.jsxs)(n.p,{children:["For any class-based tool (",(0,i.jsx)(n.code,{children:"DynamicTool"})," or ",(0,i.jsx)(n.code,{children:"DynamicToolProvider"}),") that requires configuration, this is the recommended pattern. By linking a Pydantic model to your tool class, you can add automatic validation and type safety to your ",(0,i.jsx)(n.code,{children:"tool_config"}),". This provides several key benefits:"]}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Automatic Validation:"})," The agent will fail to start if the YAML configuration doesn't match your model, providing clear error messages."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Type Safety:"})," Inside your tool, ",(0,i.jsx)(n.code,{children:"self.tool_config"})," is a fully typed Pydantic object, not a dictionary, enabling autocompletion and preventing common errors."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Self-Documentation:"})," The Pydantic model itself serves as clear, machine-readable documentation for your tool's required configuration."]}),"\n"]}),"\n",(0,i.jsxs)(n.h3,{id:"example-1-using-a-pydantic-model-with-a-single-dynamictool",children:["Example 1: Using a Pydantic Model with a Single ",(0,i.jsx)(n.code,{children:"DynamicTool"})]}),"\n",(0,i.jsxs)(n.p,{children:["This example shows how to add a validated configuration to a standalone ",(0,i.jsx)(n.code,{children:"DynamicTool"})," class."]}),"\n",(0,i.jsx)(n.h4,{id:"step-1-define-the-model-and-tool-class",children:"Step 1: Define the Model and Tool Class"}),"\n",(0,i.jsxs)(n.p,{children:["In your tools file, define a ",(0,i.jsx)(n.code,{children:"pydantic.BaseModel"})," for your configuration. Then, in your ",(0,i.jsx)(n.code,{children:"DynamicTool"})," class, link to it using the ",(0,i.jsx)(n.code,{children:"config_model"})," class attribute."]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-python",children:'# src/my_agent/weather_tools.py\nfrom typing import Dict, Any\nfrom pydantic import BaseModel, Field\nfrom google.genai import types as adk_types\nfrom solace_agent_mesh.agent.tools.dynamic_tool import DynamicTool\n\n# 1. Define the configuration model\nclass WeatherConfig(BaseModel):\n api_key: str = Field(..., description="The API key for the weather service.")\n default_unit: str = Field(default="celsius", description="The default temperature unit.")\n\n# 2. Create a tool and link the config model\nclass GetCurrentWeatherTool(DynamicTool):\n config_model = WeatherConfig\n\n def __init__(self, tool_config: WeatherConfig):\n super().__init__(tool_config)\n # self.tool_config is now a validated WeatherConfig instance\n # You can safely access attributes with type safety\n self.api_key = self.tool_config.api_key\n self.unit = self.tool_config.default_unit\n\n @property\n def tool_name(self) -> str:\n return "get_current_weather"\n\n @property\n def tool_description(self) -> str:\n return f"Get the current weather. The default unit is {self.unit}."\n\n @property\n def parameters_schema(self) -> adk_types.Schema:\n return adk_types.Schema(\n type=adk_types.Type.OBJECT,\n properties={\n "location": adk_types.Schema(type=adk_types.Type.STRING, description="The city and state/country."),\n },\n required=["location"],\n )\n\n async def _run_async_impl(self, args: Dict[str, Any], **kwargs) -> Dict[str, Any]:\n # ... implementation using self.api_key ...\n return {"weather": f"Sunny in {args[\'location\']}"}\n'})}),"\n",(0,i.jsx)(n.h4,{id:"step-2-configure-the-tool-in-yaml",children:"Step 2: Configure the Tool in YAML"}),"\n",(0,i.jsx)(n.p,{children:"The YAML configuration remains simple. The framework handles the validation against your Pydantic model automatically."}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-yaml",children:'# In your agent\'s app_config:\ntools:\n - tool_type: python\n component_module: "my_agent.weather_tools"\n # The framework will auto-discover the GetCurrentWeatherTool class\n tool_config:\n api_key: ${WEATHER_API_KEY}\n default_unit: "fahrenheit" # Optional, overrides the model\'s default\n'})}),"\n",(0,i.jsxs)(n.p,{children:["If you were to forget ",(0,i.jsx)(n.code,{children:"api_key"})," in the YAML, the agent would fail to start and print a clear error message indicating that the ",(0,i.jsx)(n.code,{children:"api_key"})," field is required, making debugging configuration issues much easier."]}),"\n",(0,i.jsxs)(n.h3,{id:"example-2-using-a-pydantic-model-with-a-dynamictoolprovider",children:["Example 2: Using a Pydantic Model with a ",(0,i.jsx)(n.code,{children:"DynamicToolProvider"})]}),"\n",(0,i.jsx)(n.p,{children:"The same pattern applies to tool providers, allowing you to pass a validated, type-safe configuration object to your tool factory."}),"\n",(0,i.jsx)(n.h4,{id:"step-1-define-the-model-and-provider-class",children:"Step 1: Define the Model and Provider Class"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-python",children:'# src/my_agent/weather_tools_provider.py\nfrom typing import List\nfrom pydantic import BaseModel, Field\nfrom solace_agent_mesh.agent.tools.dynamic_tool import DynamicTool, DynamicToolProvider\n# ... assume GetCurrentWeatherTool is defined in this file or imported ...\n\n# 1. Define the configuration model\nclass WeatherProviderConfig(BaseModel):\n api_key: str = Field(..., description="The API key for the weather service.")\n default_unit: str = Field(default="celsius", description="The default temperature unit.")\n\n# 2. Create a provider and link the config model\nclass WeatherToolProvider(DynamicToolProvider):\n config_model = WeatherProviderConfig\n\n def create_tools(self, tool_config: WeatherProviderConfig) -> List[DynamicTool]:\n # The framework passes a validated WeatherProviderConfig instance here\n return [\n GetCurrentWeatherTool(tool_config=tool_config)\n # You could create other tools here that also use the config\n ]\n'})}),"\n",(0,i.jsx)(n.h4,{id:"step-2-configure-the-provider-in-yaml",children:"Step 2: Configure the Provider in YAML"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-yaml",children:'# In your agent\'s app_config:\ntools:\n - tool_type: python\n component_module: "my_agent.weather_tools_provider"\n # The framework will auto-discover the WeatherToolProvider\n tool_config:\n api_key: ${WEATHER_API_KEY}\n default_unit: "fahrenheit" # Optional, overrides the model\'s default\n'})})]})}function h(e={}){const{wrapper:n}={...(0,s.R)(),...e.components};return n?(0,i.jsx)(n,{...e,children:(0,i.jsx)(d,{...e})}):d(e)}},8453:(e,n,o)=>{o.d(n,{R:()=>a,x:()=>l});var t=o(6540);const i={},s=t.createContext(i);function a(e){const n=t.useContext(s);return t.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function l(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:a(e.components),t.createElement(s.Provider,{value:n},e.children)}}}]);
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use strict";(self.webpackChunksolace_agenitc_mesh_docs=self.webpackChunksolace_agenitc_mesh_docs||[]).push([[8627],{5483:(e,n,s)=>{s.r(n),s.d(n,{assets:()=>c,contentTitle:()=>a,default:()=>h,frontMatter:()=>o,metadata:()=>i,toc:()=>l});const i=JSON.parse('{"id":"documentation/enterprise/rbac-setup-guide","title":"Setting Up RBAC","description":"This guide walks you through configuring Role-Based Access Control (RBAC) in a Docker installation for Agent Mesh. You will learn how to control access to Agent Mesh Enterprise features and resources based on user roles and permissions.","source":"@site/docs/documentation/enterprise/rbac-setup-guide.md","sourceDirName":"documentation/enterprise","slug":"/documentation/enterprise/rbac-setup-guide","permalink":"/solace-agent-mesh/docs/documentation/enterprise/rbac-setup-guide","draft":false,"unlisted":false,"editUrl":"https://github.com/SolaceLabs/solace-agent-mesh/edit/main/docs/docs/documentation/enterprise/rbac-setup-guide.md","tags":[],"version":"current","sidebarPosition":10,"frontMatter":{"title":"Setting Up RBAC","sidebar_position":10},"sidebar":"docSidebar","previous":{"title":"Installing Agent Mesh Enterprise","permalink":"/solace-agent-mesh/docs/documentation/enterprise/installation"},"next":{"title":"Enabling SSO","permalink":"/solace-agent-mesh/docs/documentation/enterprise/single-sign-on"}}');var r=s(4848),t=s(8453);const o={title:"Setting Up RBAC",sidebar_position:10},a=void 0,c={},l=[{value:"Table of Contents",id:"table-of-contents",level:2},{value:"Understanding RBAC in Agent Mesh Enterprise",id:"understanding-rbac-in-agent-mesh-enterprise",level:2},{value:"The Three Components",id:"the-three-components",level:3},{value:"How Authorization Works",id:"how-authorization-works",level:3},{value:"Planning Your RBAC Configuration",id:"planning-your-rbac-configuration",level:2},{value:"Identifying User Types",id:"identifying-user-types",level:3},{value:"Designing Roles",id:"designing-roles",level:3},{value:"Mapping Scopes to Features",id:"mapping-scopes-to-features",level:3},{value:"Setting Up RBAC in Docker",id:"setting-up-rbac-in-docker",level:2},{value:"Prerequisites",id:"prerequisites",level:3},{value:"Creating the Configuration Directory Structure",id:"creating-the-configuration-directory-structure",level:3},{value:"Defining Roles and Permissions",id:"defining-roles-and-permissions",level:3},{value:"Assigning Users to Roles",id:"assigning-users-to-roles",level:3},{value:"Creating the Enterprise Configuration",id:"creating-the-enterprise-configuration",level:3},{value:"Running the Docker Container",id:"running-the-docker-container",level:3},{value:"Verifying Your Configuration",id:"verifying-your-configuration",level:3},{value:"Understanding Configuration Files",id:"understanding-configuration-files",level:2},{value:"Role-to-Scope Definitions Structure",id:"role-to-scope-definitions-structure",level:3},{value:"User-to-Role Assignments Structure",id:"user-to-role-assignments-structure",level:3},{value:"Enterprise Configuration Structure",id:"enterprise-configuration-structure",level:3},{value:"Advanced Configuration Options",id:"advanced-configuration-options",level:2},{value:"Production-Ready Role Configuration",id:"production-ready-role-configuration",level:3},{value:"Integrating with Microsoft Graph",id:"integrating-with-microsoft-graph",level:3},{value:"Best Practices",id:"best-practices",level:2},{value:"Security Recommendations",id:"security-recommendations",level:3},{value:"Role Design Principles",id:"role-design-principles",level:3},{value:"Docker-Specific Recommendations",id:"docker-specific-recommendations",level:3},{value:"Troubleshooting",id:"troubleshooting",level:2},{value:"Authorization Denied for Valid User",id:"authorization-denied-for-valid-user",level:3},{value:"Configuration Files Not Found",id:"configuration-files-not-found",level:3},{value:"Microsoft Graph Integration Not Working",id:"microsoft-graph-integration-not-working",level:3},{value:"Debugging Authorization Issues",id:"debugging-authorization-issues",level:3},{value:"Getting Help",id:"getting-help",level:3},{value:"Conclusion",id:"conclusion",level:2}];function d(e){const n={a:"a",code:"code",h2:"h2",h3:"h3",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,t.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(n.p,{children:"This guide walks you through configuring Role-Based Access Control (RBAC) in a Docker installation for Agent Mesh. You will learn how to control access to Agent Mesh Enterprise features and resources based on user roles and permissions."}),"\n",(0,r.jsx)(n.h2,{id:"table-of-contents",children:"Table of Contents"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#understanding-rbac-in-agent-mesh-enterprise",children:"Understanding RBAC in Agent Mesh Enterprise"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#planning-your-rbac-configuration",children:"Planning Your RBAC Configuration"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#setting-up-rbac-in-docker",children:"Setting Up RBAC in Docker"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#understanding-configuration-files",children:"Understanding Configuration Files"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#advanced-configuration-options",children:"Advanced Configuration Options"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#best-practices",children:"Best Practices"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"#troubleshooting",children:"Troubleshooting"})}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"understanding-rbac-in-agent-mesh-enterprise",children:"Understanding RBAC in Agent Mesh Enterprise"}),"\n",(0,r.jsx)(n.p,{children:"Before you configure RBAC, you need to understand how the system works. Agent Mesh Enterprise uses a three-tier authorization model that separates identity, roles, and permissions."}),"\n",(0,r.jsx)(n.h3,{id:"the-three-components",children:"The Three Components"}),"\n",(0,r.jsx)(n.p,{children:"RBAC in Agent Mesh Enterprise consists of three interconnected components:"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Users"})," represent identities in your system. Each user has a unique identifier, typically an email address. When a user attempts to access a feature or resource, Agent Mesh Enterprise checks their assigned roles to determine what they can do."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Roles"}),' are collections of permissions that you assign to users. Instead of granting permissions directly to individual users, you create roles that represent job functions or responsibilities. For example, you might create a "data_analyst" role for users who need to work with data tools and artifacts. This approach simplifies administration because you can modify a role\'s permissions once and affect all users assigned to that role.']}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Scopes"})," are the actual permissions that grant access to specific features or resources. Each scope follows a pattern that identifies what it controls. For example, the scope ",(0,r.jsx)(n.code,{children:"tool:data:read"})," grants permission to read data tools, while ",(0,r.jsx)(n.code,{children:"artifact:create"})," allows creating artifacts. Scopes use wildcards to grant broader permissions. For example, the scope ",(0,r.jsx)(n.code,{children:"tool:data:*"})," grants all permissions for data tools."]}),"\n",(0,r.jsx)(n.h3,{id:"how-authorization-works",children:"How Authorization Works"}),"\n",(0,r.jsx)(n.p,{children:"When a user attempts an action in Agent Mesh Enterprise, the system follows this authorization flow:"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsx)(n.li,{children:"The system identifies the user based on their authentication credentials"}),"\n",(0,r.jsx)(n.li,{children:"It retrieves all roles assigned to that user"}),"\n",(0,r.jsx)(n.li,{children:"For each role, it collects all associated scopes (permissions)"}),"\n",(0,r.jsx)(n.li,{children:"It checks if any of the user's scopes match the permission required for the requested action"}),"\n",(0,r.jsx)(n.li,{children:"If a matching scope exists, the system allows the action; otherwise, it denies access"}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"This model implements the principle of least privilege: users receive only the permissions they need to perform their job functions."}),"\n",(0,r.jsx)(n.h2,{id:"planning-your-rbac-configuration",children:"Planning Your RBAC Configuration"}),"\n",(0,r.jsx)(n.p,{children:"Before you create configuration files, you should plan your RBAC structure. This planning phase helps you design a system that meets your organization's needs while remaining maintainable."}),"\n",(0,r.jsx)(n.h3,{id:"identifying-user-types",children:"Identifying User Types"}),"\n",(0,r.jsx)(n.p,{children:"Start by identifying the different types of users in your organization. Consider their job functions and what they need to accomplish with Agent Mesh Enterprise. Common user types include:"}),"\n",(0,r.jsx)(n.p,{children:"Administrators need full access to all features and resources. They manage the system, configure settings, and troubleshoot issues. You typically assign these users a role with wildcard permissions."}),"\n",(0,r.jsx)(n.p,{children:"Operators perform day-to-day tasks such as running tools, creating artifacts, and monitoring system activity. They need broad access to operational features but not administrative capabilities."}),"\n",(0,r.jsx)(n.p,{children:"Analysts work with data and reports. They need access to data tools, artifact creation, and monitoring capabilities, but they do not need access to system configuration or advanced tools."}),"\n",(0,r.jsx)(n.p,{children:"Viewers need read-only access to monitor system activity and view artifacts. They cannot create, modify, or delete resources."}),"\n",(0,r.jsx)(n.h3,{id:"designing-roles",children:"Designing Roles"}),"\n",(0,r.jsx)(n.p,{children:"Once you identify user types, design roles that match their needs. Each role should represent a specific job function and include only the scopes necessary for that function."}),"\n",(0,r.jsx)(n.p,{children:'Consider creating a role hierarchy where some roles inherit permissions from others. For example, an "operator" role might inherit all permissions from a "viewer" role and add additional capabilities. This approach reduces duplication and makes your configuration easier to maintain.'}),"\n",(0,r.jsx)(n.h3,{id:"mapping-scopes-to-features",children:"Mapping Scopes to Features"}),"\n",(0,r.jsx)(n.p,{children:"Understanding available scopes helps you design effective roles. Agent Mesh Enterprise uses a hierarchical scope naming convention:"}),"\n",(0,r.jsxs)(n.p,{children:["Tool scopes control access to tools and follow the pattern ",(0,r.jsx)(n.code,{children:"tool:<category>:<action>"}),". For example, ",(0,r.jsx)(n.code,{children:"tool:basic:read"})," grants permission to read basic tools, while ",(0,r.jsx)(n.code,{children:"tool:data:*"})," grants all permissions for data tools."]}),"\n",(0,r.jsxs)(n.p,{children:["Artifact scopes control access to artifacts (files and data created by the system) and use the pattern ",(0,r.jsx)(n.code,{children:"artifact:<action>"}),". Common artifact scopes include ",(0,r.jsx)(n.code,{children:"artifact:read"}),", ",(0,r.jsx)(n.code,{children:"artifact:create"}),", and ",(0,r.jsx)(n.code,{children:"artifact:delete"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["Monitoring scopes control access to system monitoring features and follow the pattern ",(0,r.jsx)(n.code,{children:"monitor/namespace/<namespace>:a2a_messages:subscribe"}),". These scopes allow users to observe message traffic in specific namespaces."]}),"\n",(0,r.jsxs)(n.p,{children:["The wildcard scope ",(0,r.jsx)(n.code,{children:"*"})," grants all permissions and should only be used for administrator roles."]}),"\n",(0,r.jsx)(n.h2,{id:"setting-up-rbac-in-docker",children:"Setting Up RBAC in Docker"}),"\n",(0,r.jsx)(n.p,{children:"Now that you understand RBAC concepts and have planned your configuration, you can set up RBAC in your Docker environment. This process involves creating configuration files, setting up the Docker container, and verifying that everything works correctly."}),"\n",(0,r.jsx)(n.h3,{id:"prerequisites",children:"Prerequisites"}),"\n",(0,r.jsx)(n.p,{children:"Before you begin, ensure you have:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Docker installed and running on your system"}),"\n",(0,r.jsxs)(n.li,{children:["The Agent Mesh Enterprise Docker image (",(0,r.jsx)(n.code,{children:"solace-agent-mesh-enterprise"}),")"]}),"\n",(0,r.jsx)(n.li,{children:"A text editor for creating configuration files"}),"\n",(0,r.jsx)(n.li,{children:"Basic familiarity with YAML file format"}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"creating-the-configuration-directory-structure",children:"Creating the Configuration Directory Structure"}),"\n",(0,r.jsx)(n.p,{children:"You need to create a directory structure on your host system to store RBAC configuration files. The Docker container will mount this directory to access your configurations."}),"\n",(0,r.jsx)(n.p,{children:"Create the directory structure:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"mkdir -p sam-enterprise/configs/auth\n"})}),"\n",(0,r.jsxs)(n.p,{children:["This command creates a ",(0,r.jsx)(n.code,{children:"sam-enterprise"})," directory with a nested ",(0,r.jsx)(n.code,{children:"configs/auth"})," subdirectory. The ",(0,r.jsx)(n.code,{children:"auth"})," subdirectory will contain your RBAC configuration files, while the ",(0,r.jsx)(n.code,{children:"configs"})," directory can hold other configuration files you might need."]}),"\n",(0,r.jsx)(n.h3,{id:"defining-roles-and-permissions",children:"Defining Roles and Permissions"}),"\n",(0,r.jsxs)(n.p,{children:["Create a file named ",(0,r.jsx)(n.code,{children:"role-to-scope-definitions.yaml"})," in the ",(0,r.jsx)(n.code,{children:"sam-enterprise/configs/auth"})," directory. This file defines all roles in your system and the scopes (permissions) associated with each role."]}),"\n",(0,r.jsx)(n.p,{children:"Here is an example configuration that defines three roles:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:'# role-to-scope-definitions.yaml\nroles:\n enterprise_admin:\n description: "Full access for enterprise administrators"\n scopes:\n - "*" # Wildcard grants all permissions\n \n data_analyst:\n description: "Data analysis and visualization specialist"\n scopes:\n - "tool:data:*" # All data tools\n - "artifact:read"\n - "artifact:create"\n - "monitor/namespace/*:a2a_messages:subscribe" # Can monitor any namespace\n \n standard_user:\n description: "Standard user with basic access"\n scopes:\n - "artifact:read"\n - "tool:basic:read"\n - "tool:basic:search"\n'})}),"\n",(0,r.jsx)(n.p,{children:"This configuration creates three distinct roles:"}),"\n",(0,r.jsxs)(n.p,{children:["The ",(0,r.jsx)(n.code,{children:"enterprise_admin"})," role receives the wildcard scope ",(0,r.jsx)(n.code,{children:"*"}),", which grants all permissions in the system. You should assign this role only to trusted administrators who need complete control over Agent Mesh Enterprise."]}),"\n",(0,r.jsxs)(n.p,{children:["The ",(0,r.jsx)(n.code,{children:"data_analyst"})," role receives permissions tailored for data analysis work. The scope ",(0,r.jsx)(n.code,{children:"tool:data:*"})," grants all permissions for data-related tools (read, write, execute). The ",(0,r.jsx)(n.code,{children:"artifact:read"})," and ",(0,r.jsx)(n.code,{children:"artifact:create"})," scopes allow analysts to view existing artifacts and create new ones. The monitoring scope ",(0,r.jsx)(n.code,{children:"monitor/namespace/*:a2a_messages:subscribe"})," enables analysts to observe message traffic across all namespaces, which helps them understand data flows."]}),"\n",(0,r.jsxs)(n.p,{children:["The ",(0,r.jsx)(n.code,{children:"standard_user"})," role provides minimal permissions for basic operations. Users with this role can read artifacts and perform basic tool operations but cannot create new artifacts or access advanced features."]}),"\n",(0,r.jsx)(n.h3,{id:"assigning-users-to-roles",children:"Assigning Users to Roles"}),"\n",(0,r.jsxs)(n.p,{children:["Create a file named ",(0,r.jsx)(n.code,{children:"user-to-role-assignments.yaml"})," in the ",(0,r.jsx)(n.code,{children:"sam-enterprise/configs/auth"})," directory. This file maps user identities to roles."]}),"\n",(0,r.jsx)(n.p,{children:"Here is an example configuration:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:'# user-to-role-assignments.yaml\nusers:\n admin@example.com:\n roles: ["enterprise_admin"]\n description: "Enterprise Administrator Account"\n \n data.analyst@example.com:\n roles: ["data_analyst"]\n description: "Senior Data Analyst"\n \n user1@example.com:\n roles: ["standard_user"]\n description: "Standard Enterprise User"\n'})}),"\n",(0,r.jsx)(n.p,{children:"Each entry in this file maps a user identity (typically an email address) to one or more roles. The user identity must match exactly what your authentication system provides because Agent Mesh Enterprise performs case-sensitive matching."}),"\n",(0,r.jsxs)(n.p,{children:["You can assign multiple roles to a single user by listing them in the ",(0,r.jsx)(n.code,{children:"roles"})," array. When a user has multiple roles, they receive the combined permissions from all assigned roles. For example, if you assign both ",(0,r.jsx)(n.code,{children:"data_analyst"})," and ",(0,r.jsx)(n.code,{children:"standard_user"})," roles to a user, they receive all scopes from both roles."]}),"\n",(0,r.jsxs)(n.p,{children:["The ",(0,r.jsx)(n.code,{children:"description"})," field is optional but recommended. It helps you document the purpose of each user account, which is valuable when reviewing or auditing your RBAC configuration."]}),"\n",(0,r.jsx)(n.h3,{id:"creating-the-enterprise-configuration",children:"Creating the Enterprise Configuration"}),"\n",(0,r.jsxs)(n.p,{children:["Create a file named ",(0,r.jsx)(n.code,{children:"enterprise_config.yaml"})," in the ",(0,r.jsx)(n.code,{children:"sam-enterprise/configs"})," directory (not in the ",(0,r.jsx)(n.code,{children:"auth"})," subdirectory). This file tells Agent Mesh Enterprise where to find your RBAC configuration files and how to use them."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:'# enterprise_config.yaml\nauthorization_service:\n type: "default_rbac"\n role_to_scope_definitions_path: "configs/auth/role-to-scope-definitions.yaml"\n user_to_role_assignments_path: "configs/auth/user-to-role-assignments.yaml"\n\nnamespace: "enterprise_prod"\ngateway_id: "enterprise_gateway"\n'})}),"\n",(0,r.jsxs)(n.p,{children:["The ",(0,r.jsx)(n.code,{children:"authorization_service"})," section configures the RBAC system. The ",(0,r.jsx)(n.code,{children:"type"})," field specifies ",(0,r.jsx)(n.code,{children:"default_rbac"}),", which tells Agent Mesh Enterprise to use the file-based RBAC system. The two path fields point to your RBAC configuration files\u2014these paths are relative to the container's working directory, not your host system."]}),"\n",(0,r.jsxs)(n.p,{children:["The ",(0,r.jsx)(n.code,{children:"namespace"})," and ",(0,r.jsx)(n.code,{children:"gateway_id"})," fields configure the Agent Mesh Enterprise instance. The namespace isolates this instance from others, while the gateway ID identifies the web interface gateway."]}),"\n",(0,r.jsx)(n.h3,{id:"running-the-docker-container",children:"Running the Docker Container"}),"\n",(0,r.jsxs)(n.p,{children:["Now you can start the Docker container with your RBAC configuration. Navigate to your ",(0,r.jsx)(n.code,{children:"sam-enterprise"})," directory and run:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:'cd sam-enterprise\n\ndocker run -d \\\n --name sam-enterprise \\\n -p 8000:8000 \\\n -p 5002:5002 \\\n -v "$(pwd)/configs:/app/configs" \\\n -e SAM_AUTHORIZATION_CONFIG="/app/configs/enterprise_config.yaml" \\\n -e NAMESPACE=enterprise_prod \\\n -e WEBUI_GATEWAY_ID=enterprise_gateway \\\n -e ... list here all other necessary env vars ...\n solace-agent-mesh-enterprise:<tagname> run configs\n'})}),"\n",(0,r.jsx)(n.p,{children:"This command performs several important operations:"}),"\n",(0,r.jsxs)(n.p,{children:["The ",(0,r.jsx)(n.code,{children:"-d"})," flag runs the container in detached mode, which means it runs in the background and does not block your terminal."]}),"\n",(0,r.jsxs)(n.p,{children:["The ",(0,r.jsx)(n.code,{children:"-p"})," flags map container ports to host ports. Port 8000 is the API port, and port 5002 is the web interface port. After the container starts, you can access the web interface at ",(0,r.jsx)(n.code,{children:"http://localhost:5002"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["The ",(0,r.jsx)(n.code,{children:"-v"})," flag mounts your local ",(0,r.jsx)(n.code,{children:"configs"})," directory to ",(0,r.jsx)(n.code,{children:"/app/configs"})," inside the container. This mount allows the container to read your RBAC configuration files. The ",(0,r.jsx)(n.code,{children:"$(pwd)"})," command expands to your current directory path, ensuring the mount works regardless of where you run the command."]}),"\n",(0,r.jsxs)(n.p,{children:["The ",(0,r.jsx)(n.code,{children:"-e"})," flags set environment variables inside the container. The ",(0,r.jsx)(n.code,{children:"SAM_AUTHORIZATION_CONFIG"})," variable tells Agent Mesh Enterprise where to find the main configuration file. The ",(0,r.jsx)(n.code,{children:"NAMESPACE"})," and ",(0,r.jsx)(n.code,{children:"WEBUI_GATEWAY_ID"})," variables must match the values in your ",(0,r.jsx)(n.code,{children:"enterprise_config.yaml"})," file."]}),"\n",(0,r.jsx)(n.h3,{id:"verifying-your-configuration",children:"Verifying Your Configuration"}),"\n",(0,r.jsx)(n.p,{children:"After starting the container, you should verify that RBAC is working correctly. Follow these steps:"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:["Open your web browser and navigate to ",(0,r.jsx)(n.code,{children:"http://localhost:5002"})]}),"\n",(0,r.jsxs)(n.li,{children:["Log in using one of the user identities defined in your ",(0,r.jsx)(n.code,{children:"user-to-role-assignments.yaml"})," file"]}),"\n",(0,r.jsx)(n.li,{children:"Attempt to access features that the user should have permission to use"}),"\n",(0,r.jsx)(n.li,{children:"Attempt to access features that the user should not have permission to use"}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"If RBAC is configured correctly, the user can access permitted features and receives authorization errors when attempting to access restricted features."}),"\n",(0,r.jsx)(n.p,{children:"You can also check the container logs to verify that Agent Mesh Enterprise loaded your configuration files:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"docker logs sam-enterprise\n"})}),"\n",(0,r.jsx)(n.p,{children:"Look for log messages that indicate successful configuration loading. You should see messages similar to:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{children:"INFO:solace_ai_connector:[ConfigurableRbacAuthSvc] Successfully loaded role-to-scope definitions from: /app/configs/auth/role-to-scope-definitions.yaml\nDEBUG:solace_ai_connector:[ConfigurableRbacAuthSvc] Role 'enterprise_admin' loaded with 1 direct scopes, 1 resolved scopes.\nDEBUG:solace_ai_connector:[ConfigurableRbacAuthSvc] Role 'data_analyst' loaded with 4 direct scopes, 4 resolved scopes.\nDEBUG:solace_ai_connector:[ConfigurableRbacAuthSvc] Role 'standard_user' loaded with 3 direct scopes, 3 resolved scopes.\n"})}),"\n",(0,r.jsx)(n.p,{children:"These messages confirm that Agent Mesh Enterprise found and parsed your configuration files correctly."}),"\n",(0,r.jsx)(n.h2,{id:"understanding-configuration-files",children:"Understanding Configuration Files"}),"\n",(0,r.jsx)(n.p,{children:"Now that you have a working RBAC configuration, you should understand the full structure and capabilities of each configuration file. This knowledge helps you customize the configuration to meet your specific needs."}),"\n",(0,r.jsx)(n.h3,{id:"role-to-scope-definitions-structure",children:"Role-to-Scope Definitions Structure"}),"\n",(0,r.jsxs)(n.p,{children:["The ",(0,r.jsx)(n.code,{children:"role-to-scope-definitions.yaml"})," file supports several features beyond the basic examples shown earlier. Here is the complete structure:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:'roles:\n role_name:\n description: "Role description"\n scopes:\n - "scope1"\n - "scope2"\n inherits: # Optional - inherit scopes from other roles\n - "parent_role1"\n - "parent_role2"\n'})}),"\n",(0,r.jsxs)(n.p,{children:["The ",(0,r.jsx)(n.code,{children:"inherits"})," field allows you to create role hierarchies. When a role inherits from another role, it receives all scopes from the parent role in addition to its own scopes. This feature reduces duplication and makes your configuration easier to maintain."]}),"\n",(0,r.jsx)(n.p,{children:'For example, you might create a base "viewer" role with read-only permissions, then create an "operator" role that inherits from "viewer" and adds write permissions:'}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:'roles:\n viewer:\n description: "Read-only access"\n scopes:\n - "tool:basic:read"\n - "artifact:read"\n \n operator:\n description: "Operational access"\n inherits:\n - "viewer"\n scopes:\n - "tool:basic:*"\n - "artifact:create"\n'})}),"\n",(0,r.jsxs)(n.p,{children:['In this example, the "operator" role receives all scopes from "viewer" (',(0,r.jsx)(n.code,{children:"tool:basic:read"})," and ",(0,r.jsx)(n.code,{children:"artifact:read"}),") plus its own scopes (",(0,r.jsx)(n.code,{children:"tool:basic:*"})," and ",(0,r.jsx)(n.code,{children:"artifact:create"}),"). Note that ",(0,r.jsx)(n.code,{children:"tool:basic:*"})," includes ",(0,r.jsx)(n.code,{children:"tool:basic:read"}),", so there is some overlap. Agent Mesh Enterprise handles this correctly by deduplicating scopes."]}),"\n",(0,r.jsx)(n.h3,{id:"user-to-role-assignments-structure",children:"User-to-Role Assignments Structure"}),"\n",(0,r.jsxs)(n.p,{children:["The ",(0,r.jsx)(n.code,{children:"user-to-role-assignments.yaml"})," file supports both global user identities and gateway-specific identities. Here is the complete structure:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:'users:\n user_identity:\n roles: ["role1", "role2"]\n description: "User description"\n\n# Optional: Gateway-specific user identities\ngateway_specific_identities:\n gateway_id:user_identity:\n roles: ["role1", "role2"]\n description: "User with specific roles on this gateway"\n'})}),"\n",(0,r.jsxs)(n.p,{children:["The ",(0,r.jsx)(n.code,{children:"users"})," section defines global user identities that apply across all gateways. Most configurations only need this section."]}),"\n",(0,r.jsxs)(n.p,{children:["The ",(0,r.jsx)(n.code,{children:"gateway_specific_identities"})," section allows you to assign different roles to the same user identity on different gateways. This feature is useful in multi-gateway deployments where you want to grant different permissions based on which gateway a user accesses. The key format is ",(0,r.jsx)(n.code,{children:"gateway_id:user_identity"}),", where ",(0,r.jsx)(n.code,{children:"gateway_id"})," matches the gateway ID in your enterprise configuration."]}),"\n",(0,r.jsx)(n.h3,{id:"enterprise-configuration-structure",children:"Enterprise Configuration Structure"}),"\n",(0,r.jsx)(n.p,{children:"The enterprise configuration file supports multiple authorization service types. Here is the complete structure for the file-based RBAC system:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:'authorization_service:\n type: "default_rbac"\n role_to_scope_definitions_path: "path/to/role-to-scope-definitions.yaml"\n user_to_role_assignments_path: "path/to/user-to-role-assignments.yaml"\n'})}),"\n",(0,r.jsxs)(n.p,{children:["The paths you specify are relative to the container's working directory. When you mount your ",(0,r.jsx)(n.code,{children:"configs"})," directory to ",(0,r.jsx)(n.code,{children:"/app/configs"}),", you should use paths like ",(0,r.jsx)(n.code,{children:"configs/auth/role-to-scope-definitions.yaml"}),"."]}),"\n",(0,r.jsx)(n.h2,{id:"advanced-configuration-options",children:"Advanced Configuration Options"}),"\n",(0,r.jsx)(n.p,{children:"After you have a basic RBAC configuration working, you might want to explore advanced options that provide additional flexibility and integration capabilities."}),"\n",(0,r.jsx)(n.h3,{id:"production-ready-role-configuration",children:"Production-Ready Role Configuration"}),"\n",(0,r.jsx)(n.p,{children:"A production environment typically needs more sophisticated role definitions than the basic examples\n. Here is a comprehensive configuration that demonstrates best practices:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:'# role-to-scope-definitions.yaml\nroles:\n admin:\n description: "Administrator with full access"\n scopes:\n - "*"\n \n operator:\n description: "System operator"\n scopes:\n - "tool:basic:*"\n - "tool:advanced:read"\n - "artifact:read"\n - "artifact:create"\n - "monitor/namespace/*:a2a_messages:subscribe"\n \n viewer:\n description: "Read-only access"\n scopes:\n - "tool:basic:read"\n - "artifact:read"\n - "monitor/namespace/*:a2a_messages:subscribe"\n'})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:'# user-to-role-assignments.yaml\nusers:\n admin@company.com:\n roles: ["admin"]\n description: "System Administrator"\n \n operator@company.com:\n roles: ["operator"]\n description: "System Operator"\n \n viewer@company.com:\n roles: ["viewer"]\n description: "Read-only User"\n'})}),"\n",(0,r.jsx)(n.p,{children:"This configuration creates a clear hierarchy of access levels. The admin role has unrestricted access, the operator role can perform most operational tasks, and the viewer role provides read-only access for monitoring and auditing purposes."}),"\n",(0,r.jsx)(n.h3,{id:"integrating-with-microsoft-graph",children:"Integrating with Microsoft Graph"}),"\n",(0,r.jsx)(n.p,{children:"For enterprise environments that use Microsoft Entra ID (formerly Azure AD) for user management, you can integrate Agent Mesh Enterprise with Microsoft Graph. This integration allows you to manage user role assignments through Microsoft Graph instead of maintaining a separate YAML file."}),"\n",(0,r.jsxs)(n.p,{children:["To configure Microsoft Graph integration, modify your ",(0,r.jsx)(n.code,{children:"enterprise_config.yaml"}),":"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:'# enterprise_config.yaml\nauthorization_service:\n type: "default_rbac"\n role_to_scope_definitions_path: "configs/auth/role-to-scope-definitions.yaml"\n user_to_role_provider: "ms_graph"\n \n ms_graph_config:\n ms_graph_tenant_id: ${MS_GRAPH_TENANT_ID}\n ms_graph_client_id: ${MS_GRAPH_CLIENT_ID}\n ms_graph_client_secret: ${MS_GRAPH_CLIENT_SECRET}\n'})}),"\n",(0,r.jsxs)(n.p,{children:["This configuration tells Agent Mesh Enterprise to retrieve user role assignments from Microsoft Graph instead of reading them from a YAML file. The ",(0,r.jsx)(n.code,{children:"${...}"})," syntax indicates that these values come from environment variables, which keeps sensitive credentials out of your configuration files."]}),"\n",(0,r.jsxs)(n.p,{children:["When you use Microsoft Graph integration, you still define roles in the ",(0,r.jsx)(n.code,{children:"role-to-scope-definitions.yaml"})," file, but you manage user-to-role assignments through Microsoft Graph groups or attributes."]}),"\n",(0,r.jsx)(n.p,{children:"Run the Docker container with the Microsoft Graph credentials:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:'docker run -d \\\n --name sam-enterprise \\\n -p 8000:8000 \\\n -p 5002:5002 \\\n -v "$(pwd)/config:/app/configs" \\\n -e MS_GRAPH_TENANT_ID=your-tenant-id \\\n -e MS_GRAPH_CLIENT_ID=your-client-id \\\n -e MS_GRAPH_CLIENT_SECRET=your-client-secret \\\n -e NAMESPACE=enterprise_prod \\\n -e WEBUI_GATEWAY_ID=enterprise_gateway \\\n solace-agent-mesh-enterprise:<tag>\n'})}),"\n",(0,r.jsx)(n.p,{children:"The Microsoft Graph integration requires that you configure an application registration in Microsoft Entra ID with appropriate permissions to read user and group information. The tenant ID identifies your Microsoft Entra ID tenant, the client ID identifies your application registration, and the client secret authenticates your application."}),"\n",(0,r.jsx)(n.h2,{id:"best-practices",children:"Best Practices"}),"\n",(0,r.jsx)(n.p,{children:"Following best practices helps you create a secure, maintainable RBAC configuration that scales with your organization's needs."}),"\n",(0,r.jsx)(n.h3,{id:"security-recommendations",children:"Security Recommendations"}),"\n",(0,r.jsx)(n.p,{children:"You should implement these security practices to protect your Agent Mesh Enterprise deployment:"}),"\n",(0,r.jsx)(n.p,{children:"Apply the principle of least privilege by assigning users only the minimum permissions necessary for their tasks. Start with restrictive permissions and add more as needed, rather than starting with broad permissions and removing them later. This approach reduces the risk of unauthorized access."}),"\n",(0,r.jsx)(n.p,{children:"Conduct regular audits of your role assignments and permissions. Review who has access to what features and verify that access levels remain appropriate as job responsibilities change. Remove access for users who no longer need it."}),"\n",(0,r.jsx)(n.p,{children:"Protect your RBAC configuration files with appropriate file permissions on your host system. These files control access to your entire Agent Mesh Enterprise deployment, so you should restrict read and write access to authorized administrators only."}),"\n",(0,r.jsx)(n.p,{children:"Store sensitive information like Microsoft Graph credentials as environment variables rather than hardcoding them in configuration files. Environment variables provide better security because they do not appear in version control systems or configuration backups."}),"\n",(0,r.jsx)(n.p,{children:"Never use development configurations in production environments. Development configurations often include test accounts with elevated permissions or relaxed security settings that are inappropriate for production use."}),"\n",(0,r.jsx)(n.h3,{id:"role-design-principles",children:"Role Design Principles"}),"\n",(0,r.jsx)(n.p,{children:"Well-designed roles make your RBAC configuration easier to understand and maintain:"}),"\n",(0,r.jsx)(n.p,{children:"Create roles that align with job functions in your organization. Each role should represent a specific type of work that users perform. This alignment makes it easier to determine which role to assign to new users."}),"\n",(0,r.jsx)(n.p,{children:"Use role inheritance to build a logical hierarchy. If one role needs all the permissions of another role plus additional permissions, use inheritance rather than duplicating scopes. This approach reduces configuration size and makes updates easier."}),"\n",(0,r.jsx)(n.p,{children:'Use clear, descriptive names for roles that indicate their purpose. Names like "data_analyst" or "system_operator" are more meaningful than generic names like "role1" or "user_type_a".'}),"\n",(0,r.jsx)(n.p,{children:"Document the purpose and scope of each role in the description field. This documentation helps other administrators understand your RBAC configuration and makes it easier to maintain over time."}),"\n",(0,r.jsxs)(n.p,{children:["Minimize wildcard usage in scope definitions. While wildcards like ",(0,r.jsx)(n.code,{children:"*"})," or ",(0,r.jsx)(n.code,{children:"tool:*:*"})," are convenient, they grant broad permissions that might include features you did not intend to allow. Use specific scopes whenever possible, and reserve wildcards for administrator roles."]}),"\n",(0,r.jsx)(n.h3,{id:"docker-specific-recommendations",children:"Docker-Specific Recommendations"}),"\n",(0,r.jsx)(n.p,{children:"When you run Agent Mesh Enterprise in Docker, follow these recommendations:"}),"\n",(0,r.jsx)(n.p,{children:"Use Docker volumes for persistent configuration storage. The volume mount approach shown in this guide ensures that your configuration persists even if you remove and recreate the container."}),"\n",(0,r.jsx)(n.p,{children:"Create separate configuration files for different environments (development, staging, production). This separation prevents accidental use of inappropriate configurations and makes it easier to maintain environment-specific settings."}),"\n",(0,r.jsx)(n.p,{children:"Implement health checks to verify that RBAC is functioning correctly. You can add a health check to your Docker run command that periodically tests whether the container is responding correctly."}),"\n",(0,r.jsx)(n.p,{children:"Regularly backup your RBAC configuration files. Store backups in a secure location separate from your Docker host. If you lose your configuration files, you lose control over who can access your Agent Mesh Enterprise deployment."}),"\n",(0,r.jsx)(n.p,{children:"Follow Docker security best practices such as running containers as non-root users and using read-only filesystems where possible. These practices reduce the impact of potential security vulnerabilities."}),"\n",(0,r.jsx)(n.h2,{id:"troubleshooting",children:"Troubleshooting"}),"\n",(0,r.jsx)(n.p,{children:"When you encounter issues with your RBAC configuration, systematic troubleshooting helps you identify and resolve problems quickly."}),"\n",(0,r.jsx)(n.h3,{id:"authorization-denied-for-valid-user",children:"Authorization Denied for Valid User"}),"\n",(0,r.jsx)(n.p,{children:"If a user cannot access features they should have permission to use, you might see authorization denied messages in the logs or user interface."}),"\n",(0,r.jsxs)(n.p,{children:["To resolve this issue, first verify that the user identity matches exactly what appears in your ",(0,r.jsx)(n.code,{children:"user-to-role-assignments.yaml"})," file. Agent Mesh Enterprise performs case-sensitive matching, so ",(0,r.jsx)(n.code,{children:"user@example.com"})," and ",(0,r.jsx)(n.code,{children:"User@example.com"})," are different identities."]}),"\n",(0,r.jsxs)(n.p,{children:["Next, check that the role assigned to the user has the necessary scopes. Review the ",(0,r.jsx)(n.code,{children:"role-to-scope-definitions.yaml"})," file and verify that the role includes scopes for the features the user is trying to access."]}),"\n",(0,r.jsx)(n.p,{children:"Ensure that your configuration files are correctly mounted in the Docker container. You can verify the mount by running:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"docker exec -it sam-enterprise ls -la /app/configs/auth\n"})}),"\n",(0,r.jsxs)(n.p,{children:["This command lists the files in the mounted directory. You should see your ",(0,r.jsx)(n.code,{children:"role-to-scope-definitions.yaml"})," and ",(0,r.jsx)(n.code,{children:"user-to-role-assignments.yaml"})," files."]}),"\n",(0,r.jsx)(n.p,{children:"Check the container logs for authorization service errors:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"docker logs sam-enterprise\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Look for messages with the ",(0,r.jsx)(n.code,{children:"[ConfigurableRbacAuthSvc]"})," prefix. These messages indicate whether Agent Mesh Enterprise successfully loaded your configuration files and how it resolved roles and scopes. You should see messages like:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{children:"INFO:solace_ai_connector:[ConfigurableRbacAuthSvc] Successfully loaded role-to-scope definitions from: /app/configs/auth/role-to-scope-definitions.yaml\nDEBUG:solace_ai_connector:[ConfigurableRbacAuthSvc] Role 'enterprise_admin' loaded with 1 direct scopes, 1 resolved scopes.\nDEBUG:solace_ai_connector:[ConfigurableRbacAuthSvc] Role 'data_analyst' loaded with 4 direct scopes, 4 resolved scopes.\nDEBUG:solace_ai_connector:[ConfigurableRbacAuthSvc] Role 'standard_user' loaded with 1 direct scopes, 1 resolved scopes.\n"})}),"\n",(0,r.jsx)(n.h3,{id:"configuration-files-not-found",children:"Configuration Files Not Found"}),"\n",(0,r.jsx)(n.p,{children:"If you see error messages about missing configuration files or the system uses default authorization behavior, the container cannot find your configuration files."}),"\n",(0,r.jsxs)(n.p,{children:["Verify that the file paths in your ",(0,r.jsx)(n.code,{children:"enterprise_config.yaml"})," are correct. The paths should be relative to the container's working directory, typically ",(0,r.jsx)(n.code,{children:"/app"}),". If you mounted your configs directory to ",(0,r.jsx)(n.code,{children:"/app/configs"}),", your paths should start with ",(0,r.jsx)(n.code,{children:"configs/"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["Check that the volume mount in your Docker run command is correct. The mount should map your host directory to ",(0,r.jsx)(n.code,{children:"/app/configs"})," in the container. Verify that you are using the correct path on your host system."]}),"\n",(0,r.jsx)(n.p,{children:"Ensure that file permissions allow the container user to read the files. On Linux systems, you might need to adjust file permissions:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"chmod 644 sam-enterprise/configs/auth/*.yaml\n"})}),"\n",(0,r.jsx)(n.p,{children:"Check for typos in file names or paths. The file names are case-sensitive, and even small typos prevent Agent Mesh Enterprise from finding your configuration files."}),"\n",(0,r.jsx)(n.h3,{id:"microsoft-graph-integration-not-working",children:"Microsoft Graph Integration Not Working"}),"\n",(0,r.jsx)(n.p,{children:"If users cannot authenticate when you use Microsoft Graph integration, or you see error messages related to Microsoft Graph in the logs, several issues might be causing the problem."}),"\n",(0,r.jsx)(n.p,{children:"Verify that your Microsoft Graph credentials are correct. Double-check the tenant ID, client ID, and client secret against your Microsoft Entra ID application registration."}),"\n",(0,r.jsx)(n.p,{children:"Check that environment variables are properly set in your Docker run command. You can verify environment variables inside the container:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"docker exec -it sam-enterprise env | grep MS_GRAPH\n"})}),"\n",(0,r.jsx)(n.p,{children:"Ensure that your Microsoft Graph application has the necessary permissions. The application needs permissions to read user and group information from Microsoft Entra ID."}),"\n",(0,r.jsxs)(n.p,{children:["Check network connectivity from the container to Microsoft Graph endpoints. The container must be able to reach ",(0,r.jsx)(n.code,{children:"graph.microsoft.com"})," over HTTPS. Firewall rules or network policies might block this connectivity."]}),"\n",(0,r.jsx)(n.h3,{id:"debugging-authorization-issues",children:"Debugging Authorization Issues"}),"\n",(0,r.jsx)(n.p,{children:"When you need to investigate authorization problems in detail, follow these debugging steps:"}),"\n",(0,r.jsxs)(n.p,{children:["Enable debug logging by adding a log level setting to your ",(0,r.jsx)(n.code,{children:"enterprise_config.yaml"}),":"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:'# Add to your enterprise_config.yaml\nlog_level: "DEBUG"\n'})}),"\n",(0,r.jsx)(n.p,{children:"Debug logging provides detailed information about authorization decisions, including which scopes the system checked and why it allowed or denied access."}),"\n",(0,r.jsx)(n.p,{children:"Check the container logs for detailed information:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"docker logs sam-enterprise\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Look for log messages with the ",(0,r.jsx)(n.code,{children:"[EnterpriseConfigResolverImpl]"})," or ",(0,r.jsx)(n.code,{children:"[ConfigurableRbacAuthSvc]"})," prefixes. These messages show how Agent Mesh Enterprise loaded and processed your configuration."]}),"\n",(0,r.jsx)(n.p,{children:"Temporarily assign the user to an administrator role to verify whether the issue is permission-related. If the user can access features when assigned to an admin role, the problem is with the scopes assigned to their original role."}),"\n",(0,r.jsx)(n.p,{children:"Inspect the mounted configuration files inside the container to verify that they contain the expected content:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"docker exec -it sam-enterprise cat /app/configs/auth/role-to-scope-definitions.yaml\ndocker exec -it sam-enterprise cat /app/configs/auth/user-to-role-assignments.yaml\n"})}),"\n",(0,r.jsx)(n.p,{children:"This verification ensures that the files inside the container match your host files and that the volume mount is working correctly."}),"\n",(0,r.jsx)(n.h3,{id:"getting-help",children:"Getting Help"}),"\n",(0,r.jsx)(n.p,{children:"If you continue to experience issues after following these troubleshooting steps, you can get additional help:"}),"\n",(0,r.jsx)(n.p,{children:"Check the Agent Mesh Enterprise documentation for updates or additional information about RBAC configuration."}),"\n",(0,r.jsx)(n.p,{children:"Review the container logs for specific error messages. Error messages often include details about what went wrong and how to fix it."}),"\n",(0,r.jsx)(n.p,{children:"Contact Solace support with details of your configuration and the issues you are experiencing. Include relevant log excerpts and describe the steps you have already taken to troubleshoot the problem."}),"\n",(0,r.jsx)(n.h2,{id:"conclusion",children:"Conclusion"}),"\n",(0,r.jsx)(n.p,{children:"Setting up Role-Based Access Control in your Agent Mesh Enterprise Docker installation provides enhanced security and granular access control. This guide has walked you through understanding RBAC concepts, planning your configuration, creating configuration files, and troubleshooting common issues."}),"\n",(0,r.jsx)(n.p,{children:"You now have the knowledge to configure RBAC to meet your organization's specific requirements while maintaining a secure and manageable environment. Remember to regularly review and update your RBAC configuration as your organization's needs evolve, and always follow security best practices when managing access control."})]})}function h(e={}){const{wrapper:n}={...(0,t.R)(),...e.components};return n?(0,r.jsx)(n,{...e,children:(0,r.jsx)(d,{...e})}):d(e)}},8453:(e,n,s)=>{s.d(n,{R:()=>o,x:()=>a});var i=s(6540);const r={},t=i.createContext(r);function o(e){const n=i.useContext(t);return i.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function a(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:o(e.components),i.createElement(t.Provider,{value:n},e.children)}}}]);
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use strict";(self.webpackChunksolace_agenitc_mesh_docs=self.webpackChunksolace_agenitc_mesh_docs||[]).push([[582],{4415:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>l,contentTitle:()=>o,default:()=>_,frontMatter:()=>r,metadata:()=>a,toc:()=>c});const a=JSON.parse('{"id":"documentation/developing/create-gateways","title":"Create Gateways","description":"Gateways in Agent Mesh serve as bridges between external systems and the A2A (Agent-to-Agent) ecosystem. They enable your agents to receive information from and send responses to diverse external platforms like chat systems, web applications, IoT devices, APIs, and file systems.","source":"@site/docs/documentation/developing/create-gateways.md","sourceDirName":"documentation/developing","slug":"/documentation/developing/create-gateways","permalink":"/solace-agent-mesh/docs/documentation/developing/create-gateways","draft":false,"unlisted":false,"editUrl":"https://github.com/SolaceLabs/solace-agent-mesh/edit/main/docs/docs/documentation/developing/create-gateways.md","tags":[],"version":"current","sidebarPosition":430,"frontMatter":{"title":"Create Gateways","sidebar_position":430},"sidebar":"docSidebar","previous":{"title":"Creating Agents","permalink":"/solace-agent-mesh/docs/documentation/developing/create-agents"},"next":{"title":"Creating Python Tools","permalink":"/solace-agent-mesh/docs/documentation/developing/creating-python-tools"}}');var i=t(4848),s=t(8453);const r={title:"Create Gateways",sidebar_position:430},o="Create Gateways",l={},c=[{value:"What Are Gateways?",id:"what-are-gateways",level:2},{value:"Quick Start: Creating Your First Gateway",id:"quick-start-creating-your-first-gateway",level:2},{value:"CLI Options",id:"cli-options",level:3},{value:"Gateway Architecture",id:"gateway-architecture",level:2},{value:"Gateway App",id:"gateway-app",level:3},{value:"Gateway Component",id:"gateway-component",level:3},{value:"Step-by-Step Tutorial",id:"step-by-step-tutorial",level:2},{value:"Step 1: Generate the Gateway Structure",id:"step-1-generate-the-gateway-structure",level:3},{value:"Step 2: Define Configuration Schema",id:"step-2-define-configuration-schema",level:3},{value:"Step 3: Implement Core Logic",id:"step-3-implement-core-logic",level:3},{value:"Step 4: Configure the Gateway",id:"step-4-configure-the-gateway",level:3},{value:"Step 5: Install Dependencies",id:"step-5-install-dependencies",level:3},{value:"Step 6: Run Your Gateway",id:"step-6-run-your-gateway",level:3},{value:"Advanced Gateway Patterns",id:"advanced-gateway-patterns",level:2},{value:"Authentication and Authorization",id:"authentication-and-authorization",level:3},{value:"File Handling with Artifacts",id:"file-handling-with-artifacts",level:3},{value:"Streaming Responses",id:"streaming-responses",level:3},{value:"Error Handling and Retry Logic",id:"error-handling-and-retry-logic",level:3},{value:"Best Practices",id:"best-practices",level:2},{value:"1. Configuration Management",id:"1-configuration-management",level:3},{value:"2. Error Handling",id:"2-error-handling",level:3},{value:"3. Security",id:"3-security",level:3},{value:"4. Performance",id:"4-performance",level:3},{value:"5. Monitoring and Logging",id:"5-monitoring-and-logging",level:3},{value:"Common Gateway Patterns",id:"common-gateway-patterns",level:2},{value:"HTTP/REST API Gateway",id:"httprest-api-gateway",level:3},{value:"WebSocket Gateway",id:"websocket-gateway",level:3},{value:"Message Queue Gateway",id:"message-queue-gateway",level:3},{value:"Packaging as a Plugin",id:"packaging-as-a-plugin",level:2},{value:"1. Create Plugin Structure",id:"1-create-plugin-structure",level:3},{value:"2. Configure <code>pyproject.toml</code>",id:"2-configure-pyprojecttoml",level:3},{value:"3. Build and Install",id:"3-build-and-install",level:3},{value:"Troubleshooting",id:"troubleshooting",level:2},{value:"Common Issues",id:"common-issues",level:3},{value:"Gateway Fails to Start",id:"gateway-fails-to-start",level:4},{value:"Tasks Not Reaching Agents",id:"tasks-not-reaching-agents",level:4},{value:"Authentication Failures",id:"authentication-failures",level:4},{value:"File/Artifact Issues",id:"fileartifact-issues",level:4},{value:"Debugging Tips",id:"debugging-tips",level:3}];function d(e){const n={admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,s.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(n.header,{children:(0,i.jsx)(n.h1,{id:"create-gateways",children:"Create Gateways"})}),"\n",(0,i.jsx)(n.p,{children:"Gateways in Agent Mesh serve as bridges between external systems and the A2A (Agent-to-Agent) ecosystem. They enable your agents to receive information from and send responses to diverse external platforms like chat systems, web applications, IoT devices, APIs, and file systems."}),"\n",(0,i.jsx)(n.p,{children:"This guide walks you through the steps of creating custom gateways, from basic concepts to advanced implementations."}),"\n",(0,i.jsx)(n.h2,{id:"what-are-gateways",children:"What Are Gateways?"}),"\n",(0,i.jsx)(n.p,{children:"A gateway acts as a translator and coordinator that:"}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Receives"})," events, messages, or data from external systems"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Authenticates"})," and authorizes external interactions"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Translates"})," external data into standardized A2A ",(0,i.jsx)(n.code,{children:"Task"})," format"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Submits"})," tasks to target A2A agents for processing"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Receives"})," responses and status updates from agents"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Translates"})," A2A responses back to external system format"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Sends"})," results back to the originating external system"]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"quick-start-creating-your-first-gateway",children:"Quick Start: Creating Your First Gateway"}),"\n",(0,i.jsxs)(n.p,{children:["You can create a gateway directly using the Agent Mesh CLI ",(0,i.jsx)(n.code,{children:"sam add gateway"}),":"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"sam add gateway my-custom-gateway\n"})}),"\n",(0,i.jsx)(n.p,{children:"This command:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["Launches an interactive setup (or use ",(0,i.jsx)(n.code,{children:"--gui"})," for browser-based configuration)"]}),"\n",(0,i.jsx)(n.li,{children:"Generates the necessary files and configuration"}),"\n",(0,i.jsx)(n.li,{children:"Sets up the basic gateway structure"}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"cli-options",children:"CLI Options"}),"\n",(0,i.jsx)(n.p,{children:"You can customize the gateway creation with these options:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:'sam add gateway my-gateway \\\n --namespace "myorg/dev" \\\n --gateway-id "my-custom-gw-id" \\\n --artifact-service-type "filesystem" \\\n --artifact-service-base-path "var/data/my-gateway-artifacts" \\\n --system-purpose "This gateway processes external data feeds" \\\n --response-format "Agents should respond with structured JSON"\n'})}),"\n",(0,i.jsx)(n.p,{children:"For a complete list of options, run:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"sam add gateway --help\n"})}),"\n",(0,i.jsx)(n.h2,{id:"gateway-architecture",children:"Gateway Architecture"}),"\n",(0,i.jsx)(n.p,{children:"Every Agent Mesh gateway consists of two main components:"}),"\n",(0,i.jsx)(n.h3,{id:"gateway-app",children:"Gateway App"}),"\n",(0,i.jsxs)(n.p,{children:["Gateway App (",(0,i.jsx)(n.code,{children:"app.py"}),"):"]}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"Defines configuration schema"}),"\n",(0,i.jsx)(n.li,{children:"Manages gateway-level settings"}),"\n",(0,i.jsx)(n.li,{children:"Links to the gateway component"}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"gateway-component",children:"Gateway Component"}),"\n",(0,i.jsxs)(n.p,{children:["Gateway Component (",(0,i.jsx)(n.code,{children:"component.py"}),"):"]}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"Contains the core business logic"}),"\n",(0,i.jsx)(n.li,{children:"Handles external system integration"}),"\n",(0,i.jsx)(n.li,{children:"Implements required abstract methods"}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"step-by-step-tutorial",children:"Step-by-Step Tutorial"}),"\n",(0,i.jsxs)(n.p,{children:["Let's create a practical example, ",(0,i.jsx)(n.strong,{children:"Directory Monitor Gateway"}),", a gateway that monitors a directory for new files and sends them to agents for processing."]}),"\n",(0,i.jsxs)(n.p,{children:["You can create a gateway using either ",(0,i.jsx)(n.code,{children:"sam add gateway <your_gateway_name>"})," command directly or ",(0,i.jsx)(n.code,{children:"sam plugin create <your_gateway_plugin_name> --type gateway"})," command as gateway plugin."]}),"\n",(0,i.jsxs)(n.admonition,{title:"Gateway as plugin",type:"tip",children:[(0,i.jsx)(n.p,{children:"Gateways can also be implemented as plugins. This allows you to easily package your gateway logic and reuse it across different projects."}),(0,i.jsxs)(n.p,{children:["To create a plugin of type gateway, use the ",(0,i.jsx)(n.code,{children:"sam plugin create <your_gateway_plugin_name> --type gateway"})," command."]}),(0,i.jsx)(n.p,{children:"For a complete list of options, run:"}),(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"sam plugin create --help\n"})}),(0,i.jsxs)(n.p,{children:["To create a gateway instance based on a plugin, use the ",(0,i.jsx)(n.code,{children:"sam plugin add <your_gateway_name> --plugin <your_gateway_plugin>"})," command."]}),(0,i.jsx)(n.p,{children:"For a complete list of options, run:"}),(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"sam plugin add --help\n"})}),(0,i.jsx)(n.p,{children:"Although the specific directory structure may differ from standalone gateways, the core concepts remain the same. The core files remain the same: app.py, component.py, and the YAML configuration file."})]}),"\n",(0,i.jsx)(n.h3,{id:"step-1-generate-the-gateway-structure",children:"Step 1: Generate the Gateway Structure"}),"\n",(0,i.jsxs)(n.p,{children:["This tutorial shows you how to create a new gateway with the ",(0,i.jsx)(n.code,{children:"sam add gateway"})," command."]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"sam add gateway dir-monitor\n"})}),"\n",(0,i.jsx)(n.p,{children:"This creates:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"configs/gateways/dir_monitor_config.yaml"})," - Configuration file"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"src/dir_monitor/app.py"})," - Gateway app class"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"src/dir_monitor/component.py"})," - Gateway component class"]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"step-2-define-configuration-schema",children:"Step 2: Define Configuration Schema"}),"\n",(0,i.jsxs)(n.p,{children:["Define Configuration Schema (",(0,i.jsx)(n.code,{children:"app.py"}),")"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-python",children:'# src/dir_monitor/app.py\nfrom typing import Any, Dict, List, Type\nfrom solace_ai_connector.common.log import log\nfrom solace_agent_mesh.gateway.base.app import BaseGatewayApp\nfrom solace_agent_mesh.gateway.base.component import BaseGatewayComponent\nfrom .component import DirMonitorGatewayComponent\n\n# Module info required by SAC\ninfo = {\n "class_name": "DirMonitorGatewayApp",\n "description": "Custom App class for the A2A DirMonitor Gateway.",\n}\n\nclass DirMonitorGatewayApp(BaseGatewayApp):\n """\n Directory Monitor Gateway App\n Extends BaseGatewayApp with directory monitoring specific configuration.\n """\n\n # Define gateway-specific configuration parameters\n SPECIFIC_APP_SCHEMA_PARAMS: List[Dict[str, Any]] = [\n {\n "name": "directory_path",\n "required": True,\n "type": "string",\n "description": "The directory path to monitor for changes.",\n },\n {\n "name": "target_agent_name",\n "required": False,\n "type": "string",\n "default": "OrchestratorAgent",\n "description": "The A2A agent to send tasks to.",\n },\n {\n "name": "default_user_identity",\n "required": False,\n "type": "string",\n "default": "dir_monitor_user",\n "description": "Default user identity for A2A tasks.",\n },\n {\n "name": "error_directory_path",\n "required": True,\n "type": "string",\n "description": "Directory to move files if processing fails.",\n },\n ]\n\n def __init__(self, app_info: Dict[str, Any], **kwargs):\n log_prefix = app_info.get("name", "DirMonitorGatewayApp")\n log.info("[%s] Initializing Directory Monitor Gateway App...", log_prefix)\n super().__init__(app_info=app_info, **kwargs)\n log.info("[%s] Directory Monitor Gateway App initialized.", self.name)\n\n def _get_gateway_component_class(self) -> Type[BaseGatewayComponent]:\n """Returns the gateway component class for this app."""\n return DirMonitorGatewayComponent\n'})}),"\n",(0,i.jsx)(n.h3,{id:"step-3-implement-core-logic",children:"Step 3: Implement Core Logic"}),"\n",(0,i.jsxs)(n.p,{children:["Implement Core Logic (",(0,i.jsx)(n.code,{children:"component.py"}),")"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-python",children:'# src/dir_monitor/component.py\nimport asyncio\nimport os\nimport shutil\nimport mimetypes\nimport threading\nfrom typing import Any, Dict, List, Optional, Tuple, Union\nfrom datetime import datetime, timezone\n\nfrom solace_ai_connector.common.log import log\n\n# Import watchdog for file system monitoring\ntry:\n from watchdog.observers import Observer\n from watchdog.events import FileSystemEventHandler\n WATCHDOG_AVAILABLE = True\nexcept ImportError:\n WATCHDOG_AVAILABLE = False\n Observer = None\n FileSystemEventHandler = None\n\nfrom solace_agent_mesh.gateway.base.component import BaseGatewayComponent\nfrom solace_agent_mesh.common.types import (\n Part as A2APart,\n TextPart,\n FilePart,\n Task,\n TaskStatusUpdateEvent,\n TaskArtifactUpdateEvent,\n JSONRPCError,\n FileContent,\n)\nfrom solace_agent_mesh.agent.utils.artifact_helpers import save_artifact_with_metadata\n\n# Component info\ninfo = {\n "class_name": "DirMonitorGatewayComponent",\n "description": "Monitors directories for new files and processes them via A2A agents.",\n}\n\nclass DirMonitorGatewayComponent(BaseGatewayComponent):\n """\n Directory Monitor Gateway Component\n Watches a directory and creates A2A tasks for new files.\n """\n\n def __init__(self, **kwargs: Any):\n super().__init__(**kwargs)\n log.info("%s Initializing Directory Monitor Gateway Component...", self.log_identifier)\n\n # Check if watchdog is available\n if not WATCHDOG_AVAILABLE:\n log.error("%s Watchdog library not found. Install with: pip install watchdog", \n self.log_identifier)\n raise ImportError("Watchdog library required for directory monitoring")\n\n # Load configuration\n try:\n self.directory_path = self.get_config("directory_path")\n self.target_agent_name = self.get_config("target_agent_name", "OrchestratorAgent")\n self.default_user_identity_id = self.get_config("default_user_identity", "dir_monitor_user")\n self.error_directory_path = self.get_config("error_directory_path")\n\n # Validate directories\n if not os.path.isdir(self.directory_path):\n raise ValueError(f"Monitor directory not found: {self.directory_path}")\n \n os.makedirs(self.error_directory_path, exist_ok=True)\n log.info("%s Monitoring: %s, Error dir: %s", \n self.log_identifier, self.directory_path, self.error_directory_path)\n\n except Exception as e:\n log.error("%s Configuration error: %s", self.log_identifier, e)\n raise\n\n # Initialize monitoring components\n self.observer: Optional[Observer] = None\n self.watchdog_thread: Optional[threading.Thread] = None\n\n log.info("%s Directory Monitor Gateway Component initialized.", self.log_identifier)\n\n class DirWatchEventHandler(FileSystemEventHandler):\n """Handles file system events from Watchdog."""\n \n def __init__(self, component_ref: \'DirMonitorGatewayComponent\'):\n super().__init__()\n self.component_ref = component_ref\n self.log_identifier = f"{component_ref.log_identifier}[FileHandler]"\n\n def on_created(self, event):\n if event.is_directory:\n return\n\n file_path = event.src_path\n log.info("%s New file detected: %s", self.log_identifier, file_path)\n\n # Bridge to async loop\n if self.component_ref.async_loop and self.component_ref.async_loop.is_running():\n asyncio.run_coroutine_threadsafe(\n self.component_ref._process_new_file(file_path),\n self.component_ref.async_loop\n )\n else:\n log.error("%s Async loop not available for file: %s", \n self.log_identifier, file_path)\n\n def generate_uuid(self) -> str:\n """Generate a unique identifier."""\n import uuid\n return str(uuid.uuid4())\n\n def _start_listener(self) -> None:\n """Start the directory monitoring listener."""\n log_id_prefix = f"{self.log_identifier}[StartListener]"\n log.info("%s Starting directory monitor for: %s", log_id_prefix, self.directory_path)\n\n if not WATCHDOG_AVAILABLE:\n log.error("%s Watchdog not available", log_id_prefix)\n self.stop_signal.set()\n return\n\n # Set up file system observer\n self.observer = Observer()\n event_handler = self.DirWatchEventHandler(self)\n self.observer.schedule(event_handler, self.directory_path, recursive=False)\n\n # Start observer in separate thread\n self.watchdog_thread = threading.Thread(\n target=self._run_observer,\n name=f"{self.name}_WatchdogThread",\n daemon=True\n )\n self.watchdog_thread.start()\n log.info("%s Directory monitor started", log_id_prefix)\n\n def _run_observer(self):\n """Run the watchdog observer."""\n if not self.observer:\n return\n \n log_id_prefix = f"{self.log_identifier}[Observer]"\n try:\n log.info("%s Starting file system observer...", log_id_prefix)\n self.observer.start()\n \n # Wait for stop signal\n while not self.stop_signal.is_set() and self.observer.is_alive():\n self.stop_signal.wait(timeout=1)\n \n log.info("%s Observer loop exiting", log_id_prefix)\n except Exception as e:\n log.exception("%s Observer error: %s", log_id_prefix, e)\n self.stop_signal.set()\n finally:\n if self.observer.is_alive():\n self.observer.stop()\n self.observer.join()\n log.info("%s Observer stopped", log_id_prefix)\n\n def _stop_listener(self) -> None:\n """Stop the directory monitoring listener."""\n log_id_prefix = f"{self.log_identifier}[StopListener]"\n log.info("%s Stopping directory monitor...", log_id_prefix)\n \n if self.observer and self.observer.is_alive():\n log.info("%s Stopping observer...", log_id_prefix)\n self.observer.stop()\n \n if self.watchdog_thread and self.watchdog_thread.is_alive():\n log.info("%s Joining observer thread...", log_id_prefix)\n self.watchdog_thread.join(timeout=5)\n if self.watchdog_thread.is_alive():\n log.warning("%s Observer thread did not join cleanly", log_id_prefix)\n \n log.info("%s Directory monitor stopped", log_id_prefix)\n\n async def _process_new_file(self, file_path: str):\n """Process a newly detected file."""\n log_id_prefix = f"{self.log_identifier}[ProcessFile:{os.path.basename(file_path)}]"\n log.info("%s Processing new file: %s", log_id_prefix, file_path)\n \n error_context = {\n "file_path": file_path,\n "a2a_session_id": f"dir_monitor-error-{self.generate_uuid()}"\n }\n\n try:\n # Step 1: Authenticate and enrich user\n user_identity_profile = await self.authenticate_and_enrich_user(file_path)\n if not user_identity_profile:\n log.error("%s Authentication failed for file: %s", log_id_prefix, file_path)\n error_obj = JSONRPCError(code=-32001, message="Authentication failed")\n await self._send_error_to_external(error_context, error_obj)\n return\n\n # Step 2: Translate external input to A2A format\n target_agent_name, a2a_parts, external_request_context = await self._translate_external_input(\n file_path, user_identity_profile\n )\n\n if not target_agent_name or not a2a_parts:\n log.error("%s Failed to translate file to A2A task: %s", log_id_prefix, file_path)\n error_obj = JSONRPCError(code=-32002, message="Failed to translate file to A2A task")\n final_error_context = {**error_context, **external_request_context}\n await self._send_error_to_external(final_error_context, error_obj)\n return\n\n # Step 3: Submit A2A task\n log.info("%s Submitting A2A task for file: %s to agent: %s", \n log_id_prefix, file_path, target_agent_name)\n await self.submit_a2a_task(\n target_agent_name=target_agent_name,\n a2a_parts=a2a_parts,\n external_request_context=external_request_context,\n user_identity=user_identity_profile\n )\n log.info("%s A2A task submitted for file: %s", log_id_prefix, file_path)\n\n except FileNotFoundError:\n log.error("%s File not found during processing: %s", log_id_prefix, file_path)\n except Exception as e:\n log.exception("%s Unexpected error processing file %s: %s", log_id_prefix, file_path, e)\n error_obj = JSONRPCError(code=-32000, message=f"Unexpected error: {e}")\n await self._send_error_to_external(error_context, error_obj)\n\n async def _extract_initial_claims(self, external_event_data: Any) -> Optional[Dict[str, Any]]:\n """Extract user identity claims from file event."""\n file_path = str(external_event_data)\n log_id_prefix = f"{self.log_identifier}[ExtractClaims:{os.path.basename(file_path)}]"\n \n claims = {\n "id": self.default_user_identity_id,\n "source": "dir_monitor",\n "file_path": file_path\n }\n log.debug("%s Extracted claims for file %s: %s", log_id_prefix, file_path, claims)\n return claims\n\n async def _translate_external_input(\n self, external_event_data: Any, authenticated_user_identity: Dict[str, Any]\n ) -> Tuple[Optional[str], List[A2APart], Dict[str, Any]]:\n """Translate file event to A2A task format."""\n file_path = str(external_event_data)\n log_id_prefix = f"{self.log_identifier}[TranslateInput:{os.path.basename(file_path)}]"\n\n user_id_for_a2a = authenticated_user_identity.get("id", self.default_user_identity_id)\n a2a_session_id = f"dir_monitor-session-{self.generate_uuid()}"\n \n # Prepare external request context\n external_request_context: Dict[str, Any] = {\n "file_path": file_path,\n "user_id_for_a2a": user_id_for_a2a,\n "app_name_for_artifacts": self.gateway_id,\n "user_id_for_artifacts": user_id_for_a2a,\n "a2a_session_id": a2a_session_id,\n }\n a2a_parts: List[A2APart] = []\n\n try:\n # Check if file exists\n if not os.path.exists(file_path):\n log.error("%s File does not exist: %s", log_id_prefix, file_path)\n raise FileNotFoundError(f"File not found: {file_path}")\n\n # Read file content\n with open(file_path, "rb") as f:\n content_bytes = f.read()\n \n # Determine MIME type\n mime_type, _ = mimetypes.guess_type(file_path)\n if mime_type is None:\n mime_type = "application/octet-stream"\n\n # Save file as artifact\n if not self.shared_artifact_service:\n log.error("%s Artifact service not available for file: %s", \n log_id_prefix, os.path.basename(file_path))\n return None, [], external_request_context\n\n artifact_metadata = {\n "source": "dir_monitor_gateway",\n "original_filename": os.path.basename(file_path),\n "detected_mime_type": mime_type,\n "processing_timestamp_utc": datetime.now(timezone.utc).isoformat(),\n }\n\n log.debug("%s Saving artifact for file: %s", log_id_prefix, file_path)\n save_result = await save_artifact_with_metadata(\n artifact_service=self.shared_artifact_service,\n app_name=self.gateway_id,\n user_id=str(user_id_for_a2a),\n session_id=a2a_session_id,\n filename=os.path.basename(file_path),\n content_bytes=content_bytes,\n mime_type=mime_type,\n metadata_dict=artifact_metadata,\n timestamp=datetime.now(timezone.utc),\n )\n\n if save_result["status"] not in ["success", "partial_success"]:\n log.error("%s Failed to save file as artifact: %s", \n log_id_prefix, save_result.get("message"))\n return None, [], external_request_context\n\n # Create artifact URI\n data_version = save_result.get("data_version", 0)\n artifact_uri = f"artifact://{self.gateway_id}/{str(user_id_for_a2a)}/{a2a_session_id}/{os.path.basename(file_path)}?version={data_version}"\n \n log.info("%s Saved file as artifact: %s", log_id_prefix, artifact_uri)\n\n # Create A2A parts\n file_content_obj = FileContent(\n name=os.path.basename(file_path),\n uri=artifact_uri,\n mimeType=mime_type\n )\n a2a_parts.append(FilePart(file=file_content_obj))\n a2a_parts.append(TextPart(\n text=f"Please analyze and summarize the content of: {os.path.basename(file_path)}"\n ))\n\n log.info("%s Successfully translated file %s into A2A parts", log_id_prefix, file_path)\n return self.target_agent_name, a2a_parts, external_request_context\n\n except Exception as e:\n log.exception("%s Error translating file %s: %s", log_id_prefix, file_path, e)\n return None, [], external_request_context\n\n async def _send_final_response_to_external(\n self, external_request_context: Dict[str, Any], task_data: Task\n ) -> None:\n """Handle final response from A2A agent."""\n log_id_prefix = f"{self.log_identifier}[SendFinalResponse]"\n file_path = external_request_context.get("file_path", "Unknown file")\n task_id = task_data.id\n\n # Extract summary from response\n summary_text = "Summary not available."\n if task_data.status and task_data.status.message and task_data.status.message.parts:\n for part in task_data.status.message.parts:\n if isinstance(part, TextPart):\n summary_text = part.text\n break\n \n log.info("%s Task %s completed for file \'%s\'. Status: %s", \n log_id_prefix, task_id, os.path.basename(file_path), \n task_data.status.state if task_data.status else "Unknown")\n log.info("%s Summary: %s", log_id_prefix, summary_text[:200] + "..." if len(summary_text) > 200 else summary_text)\n\n async def _send_error_to_external(\n self, external_request_context: Dict[str, Any], error_data: JSONRPCError\n ) -> None:\n """Handle errors by moving files to error directory."""\n log_id_prefix = f"{self.log_identifier}[SendError]"\n file_path = external_request_context.get("file_path")\n \n log.error("%s A2A Error for file \'%s\'. Code: %s, Message: %s",\n log_id_prefix, \n os.path.basename(file_path) if file_path else "Unknown file",\n error_data.code, error_data.message)\n\n # Move problematic file to error directory\n if file_path and os.path.exists(file_path):\n try:\n os.makedirs(self.error_directory_path, exist_ok=True)\n base_name = os.path.basename(file_path)\n error_file_path = os.path.join(self.error_directory_path, base_name)\n \n # Handle filename conflicts\n counter = 0\n while os.path.exists(error_file_path):\n counter += 1\n name, ext = os.path.splitext(base_name)\n error_file_path = os.path.join(self.error_directory_path, f"{name}_error_{counter}{ext}")\n\n shutil.move(file_path, error_file_path)\n log.info("%s Moved problematic file %s to %s", log_id_prefix, file_path, error_file_path)\n except Exception as e:\n log.exception("%s Failed to move file %s to error directory: %s",\n log_id_prefix, file_path, e)\n\n async def _send_update_to_external(\n self,\n external_request_context: Dict[str, Any],\n event_data: Union[TaskStatusUpdateEvent, TaskArtifactUpdateEvent],\n is_final_chunk_of_update: bool,\n ) -> None:\n """Handle intermediate updates (optional for this gateway)."""\n log_id_prefix = f"{self.log_identifier}[SendUpdate]"\n task_id = event_data.id\n file_path = external_request_context.get("file_path", "Unknown file")\n \n log.debug("%s Received update for task %s (file %s). Updates not processed by this gateway.",\n log_id_prefix, task_id, os.path.basename(file_path))\n\n def cleanup(self):\n """Clean up resources."""\n log.info("%s Cleaning up Directory Monitor Gateway Component...", self.log_identifier)\n super().cleanup()\n log.info("%s Directory Monitor Gateway Component cleanup finished.", self.log_identifier)\n'})}),"\n",(0,i.jsx)(n.h3,{id:"step-4-configure-the-gateway",children:"Step 4: Configure the Gateway"}),"\n",(0,i.jsxs)(n.p,{children:["Configure the Gateway (",(0,i.jsx)(n.code,{children:"dir_monitor_config.yaml"}),")"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-yaml",children:'# configs/gateways/dir_monitor_config.yaml\nlog:\n stdout_log_level: INFO\n log_file_level: DEBUG\n log_file: "dir_monitor_gateway.log"\n\n!include ../shared_config.yaml\n\napps:\n - name: dir_monitor_gateway_app\n app_base_path: .\n app_module: src.dir_monitor.app\n\n broker:\n <<: *broker_connection\n\n app_config:\n namespace: ${NAMESPACE}\n gateway_id: dir-monitor-gateway\n \n # Artifact service configuration\n artifact_service: *default_artifact_service\n\n # System purpose for A2A context\n system_purpose: >\n This system monitors directories for new files and processes them automatically.\n Analyze and summarize file contents. Always provide useful insights about the files.\n Your external name is Directory Monitor Agent.\n\n response_format: >\n Responses should be clear, concise, and professionally formatted.\n Provide structured analysis of file contents in Markdown format.\n\n # Gateway-specific configuration\n directory_path: /path/to/monitor/directory\n error_directory_path: /path/to/error/directory\n target_agent_name: "OrchestratorAgent"\n default_user_identity: "dir_monitor_system"\n'})}),"\n",(0,i.jsx)(n.h3,{id:"step-5-install-dependencies",children:"Step 5: Install Dependencies"}),"\n",(0,i.jsx)(n.p,{children:"Add required dependencies to your project:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"pip install watchdog\n"})}),"\n",(0,i.jsx)(n.h3,{id:"step-6-run-your-gateway",children:"Step 6: Run Your Gateway"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"sam run configs/gateways/dir_monitor_config.yaml\n"})}),"\n",(0,i.jsx)(n.h2,{id:"advanced-gateway-patterns",children:"Advanced Gateway Patterns"}),"\n",(0,i.jsx)(n.h3,{id:"authentication-and-authorization",children:"Authentication and Authorization"}),"\n",(0,i.jsx)(n.p,{children:"Gateways can implement sophisticated authentication:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-python",children:'async def _extract_initial_claims(self, external_event_data: Any) -> Optional[Dict[str, Any]]:\n """Extract user claims with API key validation."""\n request = external_event_data.get("request")\n \n # Validate API key\n api_key = request.headers.get("X-API-Key")\n if not api_key or not self._validate_api_key(api_key):\n return None\n \n # Extract user information\n user_id = request.headers.get("X-User-ID", "anonymous")\n \n return {\n "id": user_id,\n "source": "api_gateway",\n "api_key_hash": hashlib.sha256(api_key.encode()).hexdigest()[:8],\n "roles": self._get_user_roles(user_id)\n }\n'})}),"\n",(0,i.jsx)(n.h3,{id:"file-handling-with-artifacts",children:"File Handling with Artifacts"}),"\n",(0,i.jsx)(n.p,{children:"For gateways that handle files:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-python",children:'async def _save_file_as_artifact(self, file_content: bytes, filename: str, \n mime_type: str, session_id: str) -> Optional[str]:\n """Save file content as artifact and return URI."""\n if not self.shared_artifact_service:\n return None\n \n try:\n save_result = await save_artifact_with_metadata(\n artifact_service=self.shared_artifact_service,\n app_name=self.gateway_id,\n user_id="system",\n session_id=session_id,\n filename=filename,\n content_bytes=file_content,\n mime_type=mime_type,\n metadata_dict={\n "source": "my_gateway",\n "upload_timestamp": datetime.now(timezone.utc).isoformat()\n },\n timestamp=datetime.now(timezone.utc)\n )\n \n if save_result["status"] in ["success", "partial_success"]:\n version = save_result.get("data_version", 0)\n return f"artifact://{self.gateway_id}/system/{session_id}/{filename}?version={version}"\n \n except Exception as e:\n log.error("Failed to save artifact: %s", e)\n \n return None\n'})}),"\n",(0,i.jsx)(n.h3,{id:"streaming-responses",children:"Streaming Responses"}),"\n",(0,i.jsx)(n.p,{children:"Handle streaming responses from agents:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-python",children:'async def _send_update_to_external(\n self, external_request_context: Dict[str, Any],\n event_data: Union[TaskStatusUpdateEvent, TaskArtifactUpdateEvent],\n is_final_chunk_of_update: bool\n) -> None:\n """Send streaming updates to external system."""\n if isinstance(event_data, TaskStatusUpdateEvent):\n if event_data.status and event_data.status.message:\n for part in event_data.status.message.parts:\n if isinstance(part, TextPart):\n # Send partial text to external system\n await self._send_partial_response(\n external_request_context,\n part.text,\n is_final=is_final_chunk_of_update\n )\n'})}),"\n",(0,i.jsx)(n.h3,{id:"error-handling-and-retry-logic",children:"Error Handling and Retry Logic"}),"\n",(0,i.jsx)(n.p,{children:"Implement robust error handling:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-python",children:'async def _process_with_retry(self, data: Any, max_retries: int = 3):\n """Process data with retry logic."""\n for attempt in range(max_retries):\n try:\n return await self._process_data(data)\n except TemporaryError as e:\n if attempt < max_retries - 1:\n wait_time = 2 ** attempt # Exponential backoff\n log.warning("Attempt %d failed, retrying in %ds: %s", \n attempt + 1, wait_time, e)\n await asyncio.sleep(wait_time)\n else:\n raise\n except PermanentError:\n # Don\'t retry permanent errors\n raise\n'})}),"\n",(0,i.jsx)(n.h2,{id:"best-practices",children:"Best Practices"}),"\n",(0,i.jsx)(n.h3,{id:"1-configuration-management",children:"1. Configuration Management"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"Use environment variables for sensitive data"}),"\n",(0,i.jsx)(n.li,{children:"Provide sensible defaults"}),"\n",(0,i.jsx)(n.li,{children:"Validate configuration at startup"}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"2-error-handling",children:"2. Error Handling"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"Implement comprehensive error handling"}),"\n",(0,i.jsx)(n.li,{children:"Use appropriate HTTP status codes"}),"\n",(0,i.jsx)(n.li,{children:"Log errors with sufficient context"}),"\n",(0,i.jsx)(n.li,{children:"Provide meaningful error messages"}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"3-security",children:"3. Security"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"Validate all external inputs"}),"\n",(0,i.jsx)(n.li,{children:"Use secure authentication methods"}),"\n",(0,i.jsx)(n.li,{children:"Implement rate limiting where appropriate"}),"\n",(0,i.jsx)(n.li,{children:"Store secrets securely (use environment variables)"}),"\n",(0,i.jsx)(n.li,{children:"Follow principle of least privilege"}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"4-performance",children:"4. Performance"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"Use async/await for I/O operations"}),"\n",(0,i.jsx)(n.li,{children:"Implement connection pooling for external APIs"}),"\n",(0,i.jsx)(n.li,{children:"Monitor resource usage"}),"\n",(0,i.jsx)(n.li,{children:"Handle backpressure appropriately"}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"5-monitoring-and-logging",children:"5. Monitoring and Logging"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"Use structured logging"}),"\n",(0,i.jsx)(n.li,{children:"Include correlation IDs"}),"\n",(0,i.jsx)(n.li,{children:"Monitor key metrics (latency, error rates, throughput)"}),"\n",(0,i.jsx)(n.li,{children:"Set up health checks"}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"common-gateway-patterns",children:"Common Gateway Patterns"}),"\n",(0,i.jsx)(n.h3,{id:"httprest-api-gateway",children:"HTTP/REST API Gateway"}),"\n",(0,i.jsx)(n.p,{children:"For HTTP-based integrations:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-python",children:'from fastapi import FastAPI, HTTPException, Depends\nfrom fastapi.security import HTTPBearer\n\nclass HTTPAPIGatewayComponent(BaseGatewayComponent):\n def __init__(self, **kwargs):\n super().__init__(**kwargs)\n self.app = FastAPI()\n self.security = HTTPBearer()\n self._setup_routes()\n \n def _setup_routes(self):\n @self.app.post("/webhook/{endpoint_id}")\n async def webhook_handler(endpoint_id: str, request: Request,\n token: str = Depends(self.security)):\n # Authenticate request\n user_identity = await self.authenticate_and_enrich_user({\n "token": token,\n "endpoint_id": endpoint_id,\n "request": request\n })\n \n if not user_identity:\n raise HTTPException(status_code=401, detail="Unauthorized")\n \n # Process webhook\n body = await request.json()\n target_agent, parts, context = await self._translate_external_input(\n body, user_identity\n )\n \n task_id = await self.submit_a2a_task(\n target_agent_name=target_agent,\n a2a_parts=parts,\n external_request_context=context,\n user_identity=user_identity\n )\n \n return {"task_id": task_id, "status": "accepted"}\n'})}),"\n",(0,i.jsx)(n.h3,{id:"websocket-gateway",children:"WebSocket Gateway"}),"\n",(0,i.jsx)(n.p,{children:"For real-time bidirectional communication:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-python",children:'import websockets\nimport json\n\nclass WebSocketGatewayComponent(BaseGatewayComponent):\n def __init__(self, **kwargs):\n super().__init__(**kwargs)\n self.connections = {}\n \n async def _start_listener(self):\n """Start WebSocket server."""\n self.server = await websockets.serve(\n self.handle_websocket,\n self.get_config("websocket_host", "localhost"),\n self.get_config("websocket_port", 8765)\n )\n log.info("%s WebSocket server started", self.log_identifier)\n \n async def handle_websocket(self, websocket, path):\n """Handle WebSocket connections."""\n connection_id = self.generate_uuid()\n self.connections[connection_id] = websocket\n \n try:\n async for message in websocket:\n data = json.loads(message)\n await self.process_websocket_message(connection_id, data)\n except websockets.exceptions.ConnectionClosed:\n log.info("%s WebSocket connection closed: %s", self.log_identifier, connection_id)\n finally:\n self.connections.pop(connection_id, None)\n \n async def process_websocket_message(self, connection_id: str, data: dict):\n """Process incoming WebSocket message."""\n user_identity = await self.authenticate_and_enrich_user({\n "connection_id": connection_id,\n "data": data\n })\n \n if user_identity:\n target_agent, parts, context = await self._translate_external_input(\n data, user_identity\n )\n context["connection_id"] = connection_id\n \n await self.submit_a2a_task(\n target_agent_name=target_agent,\n a2a_parts=parts,\n external_request_context=context,\n user_identity=user_identity\n )\n \n async def _send_final_response_to_external(self, context: Dict[str, Any], task_data: Task):\n """Send response back via WebSocket."""\n connection_id = context.get("connection_id")\n websocket = self.connections.get(connection_id)\n \n if websocket:\n response = {\n "task_id": task_data.id,\n "status": task_data.status.state.value if task_data.status else "unknown",\n "result": self._extract_text_from_task(task_data)\n }\n await websocket.send(json.dumps(response))\n'})}),"\n",(0,i.jsx)(n.h3,{id:"message-queue-gateway",children:"Message Queue Gateway"}),"\n",(0,i.jsx)(n.p,{children:"For integration with message queues:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-python",children:'import asyncio\nimport aio_pika\n\nclass MessageQueueGatewayComponent(BaseGatewayComponent):\n def __init__(self, **kwargs):\n super().__init__(**kwargs)\n self.connection = None\n self.channel = None\n \n async def _start_listener(self):\n """Connect to message queue and start consuming."""\n connection_url = self.get_config("rabbitmq_url")\n queue_name = self.get_config("input_queue_name")\n \n self.connection = await aio_pika.connect_robust(connection_url)\n self.channel = await self.connection.channel()\n \n queue = await self.channel.declare_queue(queue_name, durable=True)\n await queue.consume(self.process_message)\n \n log.info("%s Started consuming from queue: %s", self.log_identifier, queue_name)\n \n async def process_message(self, message: aio_pika.IncomingMessage):\n """Process incoming queue message."""\n async with message.process():\n try:\n data = json.loads(message.body.decode())\n \n user_identity = await self.authenticate_and_enrich_user(data)\n if not user_identity:\n log.warning("%s Authentication failed for message", self.log_identifier)\n return\n \n target_agent, parts, context = await self._translate_external_input(\n data, user_identity\n )\n context["message_id"] = message.message_id\n context["reply_to"] = message.reply_to\n \n await self.submit_a2a_task(\n target_agent_name=target_agent,\n a2a_parts=parts,\n external_request_context=context,\n user_identity=user_identity\n )\n \n except Exception as e:\n log.exception("%s Error processing message: %s", self.log_identifier, e)\n \n async def _send_final_response_to_external(self, context: Dict[str, Any], task_data: Task):\n """Send response back to reply queue."""\n reply_to = context.get("reply_to")\n if reply_to and self.channel:\n response = {\n "task_id": task_data.id,\n "status": task_data.status.state.value if task_data.status else "unknown",\n "result": self._extract_text_from_task(task_data)\n }\n \n await self.channel.default_exchange.publish(\n aio_pika.Message(json.dumps(response).encode()),\n routing_key=reply_to\n )\n'})}),"\n",(0,i.jsx)(n.h2,{id:"packaging-as-a-plugin",children:"Packaging as a Plugin"}),"\n",(0,i.jsx)(n.p,{children:"For distribution and reusability, package your gateway as a plugin:"}),"\n",(0,i.jsx)(n.h3,{id:"1-create-plugin-structure",children:"1. Create Plugin Structure"}),"\n",(0,i.jsxs)(n.p,{children:["The following structure is created when running the ",(0,i.jsx)(n.code,{children:"sam plugin create my-gateway-plugin --type gateway"})," command:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{children:"my-gateway-plugin/\n\u251c\u2500\u2500 pyproject.toml\n\u251c\u2500\u2500 README.md\n\u251c\u2500\u2500 src/\n\u2502 \u2514\u2500\u2500 sam_my_gateway/\n\u2502 \u251c\u2500\u2500 __init__.py\n\u2502 \u251c\u2500\u2500 app.py\n\u2502 \u251c\u2500\u2500 component.py\n\u251c\u2500\u2500 config.yaml\n\u2514\u2500\u2500 examples/\n \u2514\u2500\u2500 my_gateway_example.yaml\n"})}),"\n",(0,i.jsxs)(n.h3,{id:"2-configure-pyprojecttoml",children:["2. Configure ",(0,i.jsx)(n.code,{children:"pyproject.toml"})]}),"\n",(0,i.jsxs)(n.p,{children:["Update the ",(0,i.jsx)(n.code,{children:"pyproject.toml"})," file to include your gateway dependencies:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-toml",children:'...\ndependencies = [\n "watchdog>=3.0.0", # Add your specific dependencies\n]\n...\n'})}),"\n",(0,i.jsx)(n.h3,{id:"3-build-and-install",children:"3. Build and Install"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"# Build the plugin\nsam plugin build\n\n# Install plugin from local wheel file\nsam plugin add my-gateway --plugin dist/sam_my_gateway-0.1.0-py3-none-any.whl\n"})}),"\n",(0,i.jsx)(n.h2,{id:"troubleshooting",children:"Troubleshooting"}),"\n",(0,i.jsx)(n.h3,{id:"common-issues",children:"Common Issues"}),"\n",(0,i.jsx)(n.h4,{id:"gateway-fails-to-start",children:"Gateway Fails to Start"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"Check configuration schema validation"}),"\n",(0,i.jsx)(n.li,{children:"Verify all required parameters are provided"}),"\n",(0,i.jsx)(n.li,{children:"Ensure external dependencies are installed"}),"\n"]}),"\n",(0,i.jsx)(n.h4,{id:"tasks-not-reaching-agents",children:"Tasks Not Reaching Agents"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"Verify namespace configuration matches agents"}),"\n",(0,i.jsx)(n.li,{children:"Check Solace broker connectivity"}),"\n",(0,i.jsx)(n.li,{children:"Confirm agent names are correct"}),"\n"]}),"\n",(0,i.jsx)(n.h4,{id:"authentication-failures",children:"Authentication Failures"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"Validate user identity extraction logic"}),"\n",(0,i.jsx)(n.li,{children:"Check authorization service configuration"}),"\n",(0,i.jsx)(n.li,{children:"Verify claims format matches expectations"}),"\n"]}),"\n",(0,i.jsx)(n.h4,{id:"fileartifact-issues",children:"File/Artifact Issues"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"Ensure artifact service is properly configured"}),"\n",(0,i.jsx)(n.li,{children:"Check file permissions and paths"}),"\n",(0,i.jsx)(n.li,{children:"Verify artifact URI construction"}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"debugging-tips",children:"Debugging Tips"}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"Enable Debug Logging"}),":"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-yaml",children:"log:\n stdout_log_level: DEBUG\n log_file_level: DEBUG\n"})}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"Use Test Agents"}),":\nCreate simple echo agents for testing gateway integration"]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"Monitor Solace Topics"}),":\nUse Solace monitoring tools to trace message flow"]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"Add Correlation IDs"}),":\nInclude unique identifiers in logs for request tracing"]}),"\n"]}),"\n"]})]})}function _(e={}){const{wrapper:n}={...(0,s.R)(),...e.components};return n?(0,i.jsx)(n,{...e,children:(0,i.jsx)(d,{...e})}):d(e)}},8453:(e,n,t)=>{t.d(n,{R:()=>r,x:()=>o});var a=t(6540);const i={},s=a.createContext(i);function r(e){const n=a.useContext(s);return a.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function o(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:r(e.components),a.createElement(s.Provider,{value:n},e.children)}}}]);
|