Tip: How to Build a Custom Agent in Solace Agent Mesh
Solace Agent Mesh is built around intelligent agents that communicate through the A2A protocol to accomplish tasks. Solace Agent Mesh ships with an orchestrator agent, but the real power comes when you create your own domain-specific agents—whether for database queries, API integrations, document processing, or any custom capability.
Solace Agent Mesh provides a framework that handles all the hard parts—LLM orchestration, tool execution, session management, and agent discovery. You focus on one thing: writing the tools that define your agent’s capabilities.
Quick Start: A Working Agent in 5 Minutes
Here’s the minimum code to create a custom agent:
1. Create the tool function (src/my_agent/tools.py):
from typing import Any, Dict, Optional
from google.adk.tools import ToolContext
from solace_agent_mesh.agent.tools import ToolResult
from solace_ai_connector.common.log import log
async def greet_user(
name: str,
tool_context: Optional[ToolContext] = None,
tool_config: Optional[Dict[str, Any]] = None
) -> ToolResult:
"""
Greets a user with a personalized message.
Args:
name: The name of the person to greet.
"""
log.info(f"[GreetUser] Greeting user: {name}")
greeting_prefix = tool_config.get("greeting_prefix", "Hello") if tool_config else "Hello"
return ToolResult.ok(f"{greeting_prefix}, {name}! Welcome to Agent Mesh!")
2. Configure the agent (configs/agents/my-agent.yaml):
apps:
- name: my-agent
app_module: solace_agent_mesh.agent.sac.app
broker:
<<: *broker_connection
app_config:
namespace: ${NAMESPACE}
agent_name: "GreetingAgent"
display_name: "Greeting Agent"
supports_streaming: true
model: *general_model
model_provider:
- "general"
instruction: |
You are a friendly greeting agent. Use the greet_user tool
to welcome users by name.
tools:
- tool_type: python
component_module: "my_agent.tools"
component_base_path: .
function_name: "greet_user"
tool_config:
greeting_prefix: "Hello there"
agent_card:
description: "A friendly agent that greets users"
defaultInputModes: ["text"]
defaultOutputModes: ["text"]
skills:
- id: "greet_user"
name: "Greet User"
description: "Greets users with personalized messages"
session_service: *default_session_service
artifact_service: *default_artifact_service
That’s it. Run with sam run configs/agents/my-agent.yaml and your agent is live.
Table of Contents
- Two Approaches
- Writing Tools
- Tool Patterns
- Working with Artifacts
- Lifecycle Functions
- Agent Card
- Building and Distributing
Two Approaches
Add Agent (Project-specific): Use sam add agent my-agent to create an agent directly in your project. Best for one-off agents specific to your application.
Plugin (Reusable): Use sam plugin create my-agent-plugin --type agent to create a distributable package. Best for agents you want to share or reuse across projects.
See Agent or Plugin: Which to Use? for guidance.
Writing Tools
Tools are Python async functions that define what your agent can do. The LLM reads your docstring to decide when to use each tool.
Tool Function Requirements
from typing import Any, Dict, Optional
from google.adk.tools import ToolContext
from solace_agent_mesh.agent.tools import ToolResult
from solace_ai_connector.common.log import log
async def my_tool(
param1: str, # Required parameter
param2: int = 10, # Optional with default
tool_context: Optional[ToolContext] = None, # Framework context
tool_config: Optional[Dict[str, Any]] = None # YAML configuration
) -> ToolResult:
"""
Short description of what this tool does.
Args:
param1: Description for the LLM.
param2: Another description.
"""
log.info(f"[MyTool] Processing with param1={param1}")
# Your logic here
return ToolResult.ok("Success message", data={"key": "value"})
Key points:
- Functions must be
async def - The docstring becomes the tool’s description for the LLM
- Type hints (
str,int,bool) generate the parameter schema - Use
ToolResultas the return type for consistent, structured responses - You can also return a
Dict[str, Any]with astatusfield, butToolResultis recommended
ToolResult
from solace_agent_mesh.agent.tools import ToolResult, DataObject, DataDisposition
# Simple success
return ToolResult.ok("Operation completed successfully")
# Success with data
return ToolResult.ok("Found 5 results", data={"count": 5, "items": [...]})
# Error
return ToolResult.error("Database connection failed")
# Success with file output
return ToolResult.ok(
"Report generated",
data_objects=[
DataObject(
name="report.csv",
content=csv_content,
mime_type="text/csv",
disposition=DataDisposition.artifact
)
]
)
Tool Patterns
Solace Agent Mesh supports three patterns for creating tools, from simple to advanced. See the Creating Python Tools guide for complete details.
Pattern 1: Function-Based (Simple)
Best for straightforward, self-contained tools:
async def calculate_sum(a: int, b: int) -> ToolResult:
"""Adds two numbers together."""
return ToolResult.ok(f"The sum is {a + b}", data={"result": a + b})
tools:
- tool_type: python
component_module: "my_agent.tools"
function_name: "calculate_sum"
Pattern 2: DynamicTool Class (Advanced)
Best for tools with complex logic or programmatic schemas:
from solace_agent_mesh.agent.tools.dynamic_tool import DynamicTool
from solace_agent_mesh.agent.tools import ToolResult
from google.genai import types as adk_types
class WeatherTool(DynamicTool):
@property
def tool_name(self) -> str:
return "get_weather"
@property
def tool_description(self) -> str:
return "Get current weather for a location."
@property
def parameters_schema(self) -> adk_types.Schema:
return adk_types.Schema(
type=adk_types.Type.OBJECT,
properties={
"location": adk_types.Schema(type=adk_types.Type.STRING, description="The city and state/country."),
"units": adk_types.Schema(type=adk_types.Type.STRING, enum=["celsius", "fahrenheit"], nullable=True),
},
required=["location"],
)
async def _run_async_impl(self, args: dict, **kwargs) -> ToolResult:
api_key = self.tool_config.get("api_key")
if not api_key:
return ToolResult.error("API key not configured")
# ... call weather API ...
return ToolResult.ok(f"The weather in {args['location']} is sunny.", data={"weather": "Sunny"})
Pattern 3: DynamicToolProvider (Factory)
Best for generating multiple related tools from a single config:
from typing import List
from solace_agent_mesh.agent.tools.dynamic_tool import DynamicTool, DynamicToolProvider
from solace_agent_mesh.agent.tools import ToolResult
class DatabaseToolProvider(DynamicToolProvider):
def create_tools(self, tool_config=None) -> List[DynamicTool]:
# Create tools from any decorated functions
tools = self._create_tools_from_decorators(tool_config)
# Add more complex tools programmatically
if tool_config and tool_config.get("connection_string"):
tools.append(DatabaseQueryTool(tool_config=tool_config))
return tools
# NOTE: Decorator must be outside the class
@DatabaseToolProvider.register_tool
async def get_database_server_version(tool_config: dict, **kwargs) -> ToolResult:
"""Returns the version of the connected database server."""
# ... implementation ...
return ToolResult.ok("Database version retrieved.", data={"version": "PostgreSQL 15.3"})
Working with Artifacts
Use the Artifact type hint to automatically load files that users have uploaded or other tools have created:
from typing import Optional
from google.adk.tools import ToolContext
from solace_agent_mesh.agent.tools import Artifact, ToolResult
async def analyze_document(
document: Artifact,
include_word_count: bool = True,
tool_context: Optional[ToolContext] = None,
) -> ToolResult:
"""
Analyzes a document and returns statistics.
Args:
document: The document to analyze (pre-loaded by framework).
include_word_count: Whether to include word count.
"""
# Get content as text - handles bytes/str conversion automatically
text = document.as_text()
data = {
"filename": document.filename,
"version": document.version,
"mime_type": document.mime_type,
"character_count": len(text),
}
if include_word_count:
data["word_count"] = len(text.split())
return ToolResult.ok(f"Analyzed {document.filename}: {len(text)} characters.", data=data)
The LLM passes {"document": "report.txt"}, and your tool receives a full Artifact object with content, filename, version, mime_type, and metadata.
For multiple files, use List[Artifact]. For optional files, use Optional[Artifact].
See Working with Artifacts for more details.
Lifecycle Functions
Manage resources that persist across tool calls using lifecycle functions:
# src/my_agent/lifecycle.py
from typing import Any
from pydantic import BaseModel, Field
from solace_ai_connector.common.log import log
class MyAgentConfig(BaseModel):
"""Configuration model for agent initialization."""
api_key: str = Field(..., description="API key for external service")
cache_size: int = Field(default=100, description="Cache size limit")
def initialize_agent(host_component: Any, init_config: MyAgentConfig):
"""Called when the agent starts."""
log_identifier = f"[{host_component.agent_name}:init]"
log.info(f"{log_identifier} Starting agent initialization...")
# Initialize shared resources
host_component.set_agent_specific_state("api_client", create_client(init_config.api_key))
host_component.set_agent_specific_state("request_count", 0)
log.info(f"{log_identifier} Agent initialization completed")
def cleanup_agent(host_component: Any):
"""Called when the agent shuts down."""
log_identifier = f"[{host_component.agent_name}:cleanup]"
log.info(f"{log_identifier} Starting agent cleanup...")
client = host_component.get_agent_specific_state("api_client")
if client:
client.close()
request_count = host_component.get_agent_specific_state("request_count", 0)
log.info(f"{log_identifier} Agent processed {request_count} requests during its lifetime")
log.info(f"{log_identifier} Agent cleanup completed")
Reference them in your config:
app_config:
agent_init_function:
module: "my_agent.lifecycle"
name: "initialize_agent"
base_path: .
config:
api_key: ${MY_API_KEY}
cache_size: 200
agent_cleanup_function:
module: "my_agent.lifecycle"
name: "cleanup_agent"
base_path: .
Agent Card
The agent card describes your agent’s capabilities to the mesh. Skills should match your configured tools:
agent_card:
description: "An agent that processes documents and generates reports"
defaultInputModes: ["text", "file"]
defaultOutputModes: ["text", "file"]
skills:
- id: "analyze_document"
name: "Analyze Document"
description: "Analyzes uploaded documents and returns statistics"
- id: "generate_report"
name: "Generate Report"
description: "Creates PDF reports from data"
The id should match the tool name (or tool_name if you’ve renamed it in config). The description helps the LLM (and other agents) decide when to use this skill.
Building and Distributing
Quick Debug Mode
During development, run directly from your source directory:
cd src
sam run ../config.yaml
Changes take effect immediately without rebuilding. This is useful for rapid iteration but should not be used for production.
Create a Plugin
For distributable agents:
sam plugin create my-agent-plugin --type agent
Build it (requires pip install build):
cd my-agent-plugin
sam plugin build
Add to a project:
sam plugin add my-custom-agent --plugin ./dist/my_agent_plugin-0.1.0.tar.gz
Or from PyPI:
sam plugin add my-custom-agent --plugin my-agent-plugin
Or from a Git repository:
sam plugin add my-custom-agent --plugin git+https://github.com/username/my-agent-plugin
See the Plugins documentation for more details.
Tips
-
Start with one tool. Get a simple function working end-to-end before adding complexity.
-
Write clear docstrings. The LLM uses them to decide when to call your tool. Be specific about inputs and outputs.
-
Use
ToolResultconsistently. It provides structured responses that the LLM can parse reliably. -
Validate config with Pydantic. For
DynamicToolclasses, define aconfig_modelto catch configuration errors at startup. -
Use lifecycle functions for shared resources. Database connections, API clients, and caches should be initialized once, not per-call.
-
Use logging for debugging. Import
from solace_ai_connector.common.log import logand add log statements to track tool execution. -
Check existing agents for patterns. The Weather Agent tutorial demonstrates a complete real-world implementation.
Learn More
- Full Documentation: Creating Agents
- Python Tools Guide: Creating Python Tools
- Built-in Tools: Built-in Tools Reference
- Weather Agent Tutorial: Build Your Own Agent
- Architecture Overview: Architecture
- Repository: github.com/SolaceLabs/solace-agent-mesh