building-custom-agent-images.md
1 --- 2 title: Building Custom Agent Images 3 sidebar_position: 25 4 --- 5 6 # Building Custom Agent Images 7 8 This tutorial walks you through packaging a custom agent plugin into a Docker/OCI image suitable for Kubernetes deployment. By the end you will have a container image that extends the SAM Enterprise base image with your own Python tool, ready to deploy with Helm. For the deployment step, see [Agent and Workflow Deployment](https://solaceproducts.github.io/solace-agent-mesh-helm-quickstart/docs/standalone-agent-deployment). 9 10 ## Prerequisites 11 12 You need Docker or Podman installed on your build machine, access to a container registry that your Kubernetes cluster can pull from, and a SAM Enterprise base image. Familiarity with Python packaging basics is helpful but not required---this tutorial covers everything you need. For background on tool function patterns, see [Creating Python Tools](../creating-python-tools.md). For plugin packaging basics, see [Plugins](../../components/plugins.md). 13 14 ## Step 1: Create the Plugin 15 16 Start by setting up a standard Python package for your custom tool. The directory structure follows the `src` layout convention: 17 18 ``` 19 custom-echo-agent/ 20 ├── pyproject.toml 21 └── src/ 22 └── custom_echo_agent/ 23 ├── __init__.py 24 └── tools.py 25 ``` 26 27 The `pyproject.toml` declares the package metadata and uses `hatchling` as the build backend. The `src-path` setting tells Hatch where to find the package source, and the `packages` list ensures only your plugin code is included in the wheel: 28 29 ```toml 30 [build-system] 31 requires = ["hatchling"] 32 build-backend = "hatchling.build" 33 34 [project] 35 name = "custom_echo_agent" 36 version = "0.1.0" 37 description = "A custom echo tool for SAM" 38 requires-python = ">=3.10" 39 dependencies = [] 40 41 [tool.hatch.build.targets.wheel] 42 packages = ["src/custom_echo_agent"] 43 src-path = "src" 44 ``` 45 46 Create an empty `__init__.py` file in the `src/custom_echo_agent/` directory so Python recognizes it as a package. 47 48 ## Step 2: Write the Tool 49 50 Create `src/custom_echo_agent/tools.py` with your tool function: 51 52 ```python 53 import logging 54 from typing import Any, Dict, Optional 55 56 from google.adk.tools import ToolContext 57 58 log = logging.getLogger(__name__) 59 60 async def echo_tool( 61 message: str, 62 tool_context: Optional[ToolContext] = None, 63 tool_config: Optional[Dict[str, Any]] = None, 64 ) -> Dict[str, Any]: 65 """Echoes the input message back.""" 66 current_config = tool_config if tool_config is not None else {} 67 prefix = current_config.get("prefix", "Echo: ") 68 69 result = f"{prefix}{message}" 70 log.info(f"[EchoTool] Echoing: {result}") 71 72 return { 73 "status": "success", 74 "message": result, 75 } 76 ``` 77 78 Every tool function must be `async`. The `tool_context` and `tool_config` parameters are always the last two---SAM injects them automatically at runtime. All other parameters become the tool's input schema that the LLM sees and populates. In this example the LLM will see a single `message` string parameter. The `google.adk.tools` package is already included in the SAM Enterprise base image, so you do not need to install it separately. 79 80 ## Step 3: Write the Agent YAML 81 82 Create `custom-echo-agent.yaml` to define the agent. This file is passed to Helm at deploy time via `--set-file config.yaml=custom-echo-agent.yaml`. All `${...}` variables are resolved at runtime from environment variables that the Helm chart injects---you do not need to hardcode any values: 83 84 ```yaml 85 log: 86 stdout_log_level: INFO 87 log_file_level: DEBUG 88 log_file: custom-echo-agent.log 89 90 shared_config: 91 - broker_connection: &broker_connection 92 dev_mode: ${SOLACE_DEV_MODE, false} 93 broker_url: ${SOLACE_BROKER_URL, ws://localhost:8080} 94 broker_username: ${SOLACE_BROKER_USERNAME, default} 95 broker_password: ${SOLACE_BROKER_PASSWORD, default} 96 broker_vpn: ${SOLACE_BROKER_VPN, default} 97 temporary_queue: ${USE_TEMPORARY_QUEUES, true} 98 99 - models: 100 general: &general_model 101 model: ${LLM_SERVICE_GENERAL_MODEL_NAME} 102 api_base: ${LLM_SERVICE_ENDPOINT} 103 api_key: ${LLM_SERVICE_API_KEY} 104 105 - services: 106 session_service: &default_session_service 107 type: "memory" 108 default_behavior: "PERSISTENT" 109 110 artifact_service: &default_artifact_service 111 type: "memory" 112 113 apps: 114 - name: custom-echo-agent-app 115 app_base_path: . 116 app_module: solace_agent_mesh.agent.sac.app 117 broker: 118 <<: *broker_connection 119 120 app_config: 121 namespace: ${NAMESPACE} 122 supports_streaming: true 123 agent_name: "CustomEchoAgent" 124 display_name: "Custom Echo Agent" 125 model: *general_model 126 model_provider: 127 - "general" 128 129 instruction: | 130 You are a custom echo agent. When a user sends you a message, 131 use the echo_tool to echo it back to them. 132 133 tools: 134 - tool_type: python 135 component_module: custom_echo_agent.tools 136 function_name: echo_tool 137 tool_config: 138 prefix: "Echo: " 139 140 session_service: *default_session_service 141 artifact_service: *default_artifact_service 142 143 agent_card: 144 description: "A custom agent that echoes messages back" 145 defaultInputModes: ["text"] 146 defaultOutputModes: ["text"] 147 skills: 148 - id: "echo_tool" 149 name: "Echo Tool" 150 description: "Echoes the input message back to the user" 151 152 agent_card_publishing: { interval_seconds: 10 } 153 agent_discovery: { enabled: false } 154 inter_agent_communication: 155 allow_list: [] 156 request_timeout_seconds: 30 157 ``` 158 159 The `component_module` field points to the Python module path of your tool (`custom_echo_agent.tools`), and `function_name` identifies which function to load. The `tool_config` section provides configuration values that SAM passes to your function's `tool_config` parameter at runtime. 160 161 ## Step 4: Build the Docker Image 162 163 Your project directory should look like this before building: 164 165 ``` 166 your-project/ 167 ├── Dockerfile 168 ├── custom-echo-agent.yaml 169 └── custom-echo-agent/ 170 ├── pyproject.toml 171 └── src/custom_echo_agent/ 172 ├── __init__.py 173 └── tools.py 174 ``` 175 176 Create the following `Dockerfile`. It extends the SAM Enterprise base image, builds your plugin as a wheel, installs it, and fixes filesystem ownership so the runtime user can write to the SAM data directory: 177 178 ```dockerfile 179 FROM <your-registry>/solace-agent-mesh-enterprise:<your-version> 180 181 USER 0 182 WORKDIR /app 183 184 # Install the build package (not included in the runtime image) 185 RUN pip install build 186 187 # Copy and build the custom plugin 188 COPY custom-echo-agent/ /tmp/custom-echo-agent/ 189 RUN sam plugin build /tmp/custom-echo-agent && \ 190 pip install /tmp/custom-echo-agent/dist/*.whl && \ 191 rm -rf /tmp/custom-echo-agent 192 193 # Fix ownership: sam plugin build creates /app/.sam as root, 194 # but the runtime user (solaceai) needs write access 195 RUN chown -R solaceai:solaceai /app/.sam 196 197 USER solaceai 198 199 ENV SAM_CLI_HOME=/app/.sam 200 ENTRYPOINT ["solace-agent-mesh"] 201 CMD ["run", "/preset/agents"] 202 ``` 203 204 The image temporarily switches to `USER 0` (root) to install build tools and compile the plugin. The `sam plugin build` command produces a wheel under the plugin's `dist/` directory, which `pip install` then installs into the image's Python environment. After cleanup, `chown` ensures the SAM data directory is writable by the non-root `solaceai` user that runs at runtime. 205 206 :::info 207 The `pip install build` step requires internet access during image build. This is the same build pattern used in the SAM Enterprise Dockerfile itself. In air-gapped environments, pre-download the `build` package and COPY it into the image. 208 ::: 209 210 ## Step 5: Push to Registry 211 212 Build and push the image using Docker or Podman: 213 214 ```bash 215 docker build -t <your-registry>/custom-echo-agent:1.0.0 . 216 docker push <your-registry>/custom-echo-agent:1.0.0 217 ``` 218 219 ```bash 220 podman build . -t <your-registry>/custom-echo-agent:1.0.0 221 podman push <your-registry>/custom-echo-agent:1.0.0 222 ``` 223 224 Tag images with a version number rather than relying on `latest`. This makes rollbacks straightforward and ensures Kubernetes pulls the exact image you intend. 225 226 ## Adapting for Your Own Agent 227 228 | What to change | Echo example | Your agent | 229 |----------------|-------------|------------| 230 | Plugin directory | `custom-echo-agent/` | `my-agent/` | 231 | Python package | `custom_echo_agent` | `my_agent` (underscores) | 232 | `component_module` in YAML | `custom_echo_agent.tools` | `my_agent.tools` | 233 | `agent_name` in YAML | `CustomEchoAgent` | `MyAgent` (PascalCase) | 234 | Helm release name | `custom-echo-agent` | `my-agent` (kebab-case) | 235 236 To expose multiple tools from a single agent, add additional entries under `tools` and match them in `agent_card.skills`: 237 238 ```yaml 239 tools: 240 - tool_type: python 241 component_module: my_agent.tools 242 function_name: first_tool 243 244 - tool_type: python 245 component_module: my_agent.tools 246 function_name: second_tool 247 ``` 248 249 If your tool has third-party dependencies, add them to the `dependencies` list in `pyproject.toml`. In air-gapped environments the dependencies must already be present in the base image or copied into the build as local `.whl` files. 250 251 ## Troubleshooting 252 253 :::warning 254 The `sam plugin build` command creates `/app/.sam` as root. If you forget the `RUN chown -R solaceai:solaceai /app/.sam` line in your Dockerfile, the container will fail at runtime with a `PermissionError` because the `solaceai` user cannot write to that directory. 255 ::: 256 257 If you see `ModuleNotFoundError: No module named 'custom_echo_agent'` (or your package name) at runtime, the plugin was not installed correctly during the image build. Verify the install by running `docker run --rm <your-image> pip list | grep custom` against your built image. Check that the `packages` and `src-path` settings in `pyproject.toml` match your directory layout. 258 259 ## Next Steps 260 261 - Deploy your image to Kubernetes using the [Agent and Workflow Deployment](https://solaceproducts.github.io/solace-agent-mesh-helm-quickstart/docs/standalone-agent-deployment) guide 262 - Learn more about tool development patterns in [Creating Python Tools](../creating-python-tools.md) 263 - Explore plugin packaging in detail at [Plugins](../../components/plugins.md)