Skip to main content
The information on this page is for Solace Agent Mesh 1.x

Visit the Solace Documentation site for information about Solace Agent Mesh 2.x and later.

Explore the Agent Mesh 2.x docs →

Migration Guide: Upgrading to the New Tool Framework

This guide is for developers who have built custom tools for Solace Agent Mesh. A recent architectural update introduces a new tool framework that significantly reduces boilerplate code and provides better patterns for artifact handling, context access, and return values.

This document provides a comprehensive guide to migrating your existing tools to the new framework patterns.

Why the Change?

The migration to the new tool framework provides several key benefits:

  • Reduced Boilerplate: Automatic artifact loading and saving eliminates 15+ lines of repetitive code per tool.
  • Type Safety: New type hints (ArtifactContent, ToolResult) provide better IDE support and catch errors earlier.
  • Cleaner Context Access: The ToolContextFacade provides a simple, read-only interface instead of accessing internal implementation details.
  • Explicit Data Handling: DataDisposition makes it clear how tool outputs should be handled (inline, artifact, or preview).
  • Future-Proofing: Insulates your tools from internal implementation changes in the framework.

New Framework Components

The new tool framework introduces four key components:

  1. ToolResult - Structured return type with automatic artifact handling
  2. ArtifactContent - Type hint for automatic artifact pre-loading
  3. ToolContextFacade - Simplified read-only access to context and artifacts
  4. DataObject - Explicit data disposition (inline, artifact, or preview)

Quick Reference

Old PatternNew Pattern
Return {"status": "success", ...}Return ToolResult(message=..., data=[...])
Manual load_artifact_content_or_metadata()Type hint ArtifactContent or ctx.load_artifact()
Manual save_artifact_with_metadata()DataObject(disposition=ARTIFACT) in ToolResult
Access tool_context._invocation_contextUse ToolContextFacade (ctx)
Access config via host_component.get_config()Use ctx.get_config()

Migration Steps

Step 1: Update Imports

# Old imports
from google.adk.tools import ToolContext
from solace_agent_mesh.agent.utils.artifact_helpers import (
load_artifact_content_or_metadata,
save_artifact_with_metadata,
)
from solace_agent_mesh.agent.utils.context_helpers import get_original_session_id

# New imports
from solace_agent_mesh.agent.tools import (
register_tool,
ToolResult,
DataObject,
DataDisposition,
ArtifactContent, # Only if using type hint for pre-loading
)
from solace_agent_mesh.agent.utils import ToolContextFacade

Step 2: Update Function Signature

# Old signature
async def my_tool(
input_filename: str,
param1: str,
tool_context: ToolContext = None,
) -> Dict[str, Any]:

# New signature (with artifact pre-loading)
@register_tool(name="my_tool", description="...")
async def my_tool(
input_content: ArtifactContent, # Auto-loaded
input_filename: str, # Keep for format detection
param1: str,
ctx: ToolContextFacade = None, # Auto-injected
) -> ToolResult:

# New signature (without artifact pre-loading)
@register_tool(name="my_tool", description="...")
async def my_tool(
input_filename: str,
param1: str,
ctx: ToolContextFacade = None, # Auto-injected
) -> ToolResult:
# Use ctx.load_artifact() inside function

Step 3: Update Context Access

# Old pattern - accessing context internals
inv_context = tool_context._invocation_context
app_name = inv_context.app_name
user_id = inv_context.user_id
session_id = get_original_session_id(inv_context)
host_component = getattr(inv_context.agent, "host_component", None)
config = host_component.get_config("key", default) if host_component else default

# New pattern - using facade
config = ctx.get_config()
# Access session_id, user_id, app_name via ctx properties if needed
session_id = ctx.session_id
user_id = ctx.user_id
app_name = ctx.app_name

Step 4: Update Artifact Loading

Option A: Use ArtifactContent Type Hint (Recommended for Single Files)

# Old pattern
async def my_tool(input_filename: str, tool_context: ToolContext = None):
inv_context = tool_context._invocation_context
# Parse filename:version format (rsplit to handle colons in filenames)
parts = input_filename.rsplit(":", 1)
if len(parts) == 2 and parts[1].isdigit():
filename_base = parts[0]
version = int(parts[1])
else:
filename_base = input_filename
version = "latest"

load_result = await load_artifact_content_or_metadata(
artifact_service=inv_context.artifact_service,
app_name=inv_context.app_name,
user_id=inv_context.user_id,
session_id=get_original_session_id(inv_context),
filename=filename_base,
version=version,
return_raw_bytes=True,
component=host_component,
)
if load_result["status"] != "success":
return {"status": "error", "message": load_result["message"]}
content = load_result["raw_bytes"]

# New pattern - content is pre-loaded automatically!
@register_tool(name="my_tool", description="...")
async def my_tool(
input_content: ArtifactContent, # Framework loads this before calling
input_filename: str,
ctx: ToolContextFacade = None,
) -> ToolResult:
# input_content is already loaded - use it directly
content = input_content # bytes or str

Option B: Use ctx.load_artifact() (For Dynamic/Multiple Files)

# New pattern for dynamic loading
@register_tool(name="my_tool", description="...")
async def my_tool(
input_files: Dict[str, str], # Multiple files
ctx: ToolContextFacade = None,
) -> ToolResult:
for table_name, filename in input_files.items():
# Parse filename:version format (rsplit to handle colons in filenames)
parts = filename.rsplit(":", 1)
if len(parts) == 2 and parts[1].isdigit():
filename_base = parts[0]
version = int(parts[1])
else:
filename_base = filename
version = "latest"

# Simple one-liner instead of 15+ lines
content = await ctx.load_artifact(filename_base, version=version)
metadata = await ctx.load_artifact_metadata(filename_base, version=version)

Step 5: Update Return Values

# Old pattern - dict with manual status
return {
"status": "success",
"message": "Operation completed",
"output_filename": "result.json",
"output_version": version,
"preview": preview_data,
}

# Old error pattern
return {"status": "error", "message": "Something went wrong"}

# New pattern - structured ToolResult
return ToolResult(
message="Operation completed",
data=[
DataObject(
data=result_bytes,
disposition=DataDisposition.ARTIFACT_WITH_PREVIEW,
filename="result.json",
mime_type="application/json",
description="Result of operation",
metadata={"key": "value"},
preview=preview_data,
)
],
metadata={
"output_filename": "result.json",
},
)

# New error pattern
return ToolResult.error("Something went wrong")

Step 6: Update Artifact Saving

# Old pattern - manual save
artifact_service = inv_context.artifact_service
save_result = await save_artifact_with_metadata(
artifact_service=artifact_service,
app_name=inv_context.app_name,
user_id=inv_context.user_id,
session_id=get_original_session_id(inv_context),
filename=output_filename,
content_bytes=result_bytes,
mime_type="application/json",
metadata_dict={"description": "..."},
timestamp=datetime.now(timezone.utc),
schema_max_keys=schema_max_keys,
)
if save_result["status"] == "error":
raise IOError(save_result["message"])

# New pattern - automatic via ToolResult
# Just include DataObject in your ToolResult - framework handles saving!
return ToolResult(
message="Success",
data=[
DataObject(
data=result_bytes,
disposition=DataDisposition.ARTIFACT, # Will be saved automatically
filename=output_filename,
mime_type="application/json",
description="...",
metadata={...},
)
],
)

DataDisposition Options

DispositionBehavior
AUTOFramework decides based on size/type
INLINEReturn directly to LLM, no artifact
ARTIFACTSave as artifact, return filename to LLM
ARTIFACT_WITH_PREVIEWSave as artifact + include preview in response

Working with Multiple Artifacts

Use List[ArtifactContent] for Multiple Pre-loaded Artifacts

When your tool needs multiple artifacts of the same type, use List[ArtifactContent]:

from typing import List
from solace_agent_mesh.agent.tools import (
ArtifactContent, ToolResult, DataObject, DataDisposition, register_tool
)
from solace_agent_mesh.agent.utils import ToolContextFacade

@register_tool(name="merge_files", description="Merge multiple files together")
async def merge_files(
input_files: List[ArtifactContent], # LLM provides list of filenames
output_name: str,
ctx: ToolContextFacade = None,
) -> ToolResult:
# input_files is already a list of loaded content (bytes or str)
merged = b"".join(f if isinstance(f, bytes) else f.encode() for f in input_files)

return ToolResult(
message=f"Merged {len(input_files)} files",
data=[DataObject(
data=merged,
disposition=DataDisposition.ARTIFACT,
filename=output_name,
)]
)

Schema Translation:

  • List[ArtifactContent] -> LLM sees: array of strings (artifact filenames)
  • Tool receives: list of artifact contents

Supported Type Patterns:

Type HintLLM SchemaTool Receives
ArtifactContentstringbytes or str
List[ArtifactContent]array of stringslist[bytes] or list[str]
Optional[ArtifactContent]string (nullable)bytes, str, or None
Optional[List[ArtifactContent]]array (nullable)list or None

When to Use Which Pattern

Use ArtifactContent Type Hint When:

  • Tool has a single, known input artifact
  • The artifact filename is a direct parameter
  • You want zero-boilerplate artifact loading

Use List[ArtifactContent] Type Hint When:

  • Tool needs multiple artifacts of the same type
  • The number of artifacts is variable but known at call time
  • All artifacts should be loaded before tool execution

Use ctx.load_artifact() When:

  • Tool has multiple input files with different roles (e.g., dict mapping)
  • Input files are determined dynamically during execution
  • You need to load artifacts conditionally

Use ToolResult + DataObject When:

  • Tool produces output that should be saved as artifact
  • You want explicit control over data disposition
  • You need to include metadata with the artifact

Complete Examples

For complete migration examples, see the example tool implementations in the source code:

  • src/solace_agent_mesh/agent/tools/examples/migrated_jmespath_tool.py - Uses ArtifactContent type hint
  • src/solace_agent_mesh/agent/tools/examples/migrated_sql_tool.py - Uses ctx.load_artifact() for multiple files

Both demonstrate the full migration from old to new patterns.