ToolContext
Enhanced context for tool execution with memory search and artifact management
ToolContext extends CallbackContext with additional capabilities specifically designed for tool implementations. It provides memory search, artifact listing, and enhanced event action management.
Overview
ToolContext is the most capable context available for tool implementations, providing access to memory services, enhanced artifact operations, and function call tracking.
import { ToolContext } from "@iqai/adk";
Key Features
- All CallbackContext Features: Mutable state, artifact load/save operations
- Memory Search: Query long-term memory stores and knowledge bases
- Artifact Listing: Enumerate available artifacts in the session
- Function Call Tracking: Link tool executions to originating LLM function calls
- Enhanced Event Actions: Direct access to action management via
actions
property
Properties
functionCallId
Unique identifier linking this tool execution to the LLM function call:
functionCallId?: string
This ID helps correlate tool responses with their originating function calls for debugging and event tracking.
actions
Direct access to event actions for flow control:
get actions(): EventActions
Provides convenient access to eventActions
with a shorter property name.
Enhanced Methods
listArtifacts()
Lists all artifact filenames attached to the current session:
async listArtifacts(): Promise<string[]>
async function examineSessionArtifacts(toolContext: ToolContext) {
const artifacts = await toolContext.listArtifacts();
console.log(`Found ${artifacts.length} artifacts:`);
artifacts.forEach(filename => {
console.log(`- ${filename}`);
});
// Save artifact inventory to state
toolContext.state.artifactInventory = {
count: artifacts.length,
files: artifacts,
lastChecked: new Date().toISOString()
};
return artifacts;
}
searchMemory()
Search the memory service for relevant information:
async searchMemory(query: string): Promise<SearchMemoryResponse>
async function findRelevantContext(toolContext: ToolContext, userQuery: string) {
try {
const memoryResults = await toolContext.searchMemory(userQuery);
// Process search results
const relevantMemories = memoryResults.memories || [];
// Update state with findings
toolContext.state.lastMemorySearch = {
query: userQuery,
resultCount: relevantMemories.length,
timestamp: new Date().toISOString()
};
return relevantMemories;
} catch (error) {
console.error("Memory search failed:", error);
toolContext.state.memorySearchError = {
query: userQuery,
error: error instanceof Error ? error.message : String(error),
timestamp: new Date().toISOString()
};
return [];
}
}
Memory search requires a memory service to be configured in the runner or agent setup.
Tool Implementation Patterns
Basic Tool Structure
Most tools using ToolContext follow this pattern:
import { BaseTool, ToolContext } from "@iqai/adk";
export class MyTool extends BaseTool {
constructor() {
super({
name: "my_tool",
description: "Example tool using ToolContext"
});
}
async runAsync(args: any, context: ToolContext): Promise<any> {
// Your tool implementation here
return await this.processWithContext(args, context);
}
private async processWithContext(args: any, context: ToolContext) {
// Use context features
const artifacts = await context.listArtifacts();
const memories = await context.searchMemory(args.query);
// Update state
context.state.toolExecution = {
toolName: this.name,
functionCallId: context.functionCallId,
timestamp: new Date().toISOString()
};
return { success: true };
}
}
Memory-Enhanced Tool
Create tools that leverage memory for enhanced responses:
import { FunctionTool, ToolContext } from "@iqai/adk";
async function researchTopic(
params: { topic: string; depth: "basic" | "detailed" },
toolContext: ToolContext
) {
// Search memory for existing knowledge
const existingKnowledge = await toolContext.searchMemory(params.topic);
// Check for cached research
const cacheKey = `research_${params.topic}_${params.depth}`;
const cachedResearch = await toolContext.loadArtifact(`${cacheKey}.json`);
if (cachedResearch) {
toolContext.state.cacheHit = true;
return JSON.parse(cachedResearch.text || '{}');
}
// Perform new research combining memory and external sources
const research = {
topic: params.topic,
depth: params.depth,
existingKnowledge: existingKnowledge.memories || [],
newFindings: [], // Your research logic here
timestamp: new Date().toISOString()
};
// Cache the results
await toolContext.saveArtifact(`${cacheKey}.json`, {
text: JSON.stringify(research, null, 2)
});
// Update search context in state
toolContext.state.lastResearch = {
topic: params.topic,
knowledgeFound: existingKnowledge.memories?.length || 0,
cached: false
};
return research;
}
const researchTool = new FunctionTool(researchTopic, {
name: "research_topic",
description: "Research a topic using memory and external sources"
});
Artifact Processing Tool
Tools that work with multiple artifacts:
async function processDocuments(
params: { operation: "summarize" | "analyze" | "compare" },
toolContext: ToolContext
) {
// Get all available documents
const allArtifacts = await toolContext.listArtifacts();
const documents = allArtifacts.filter(name =>
name.endsWith('.txt') || name.endsWith('.md') || name.endsWith('.json')
);
if (documents.length === 0) {
return { error: "No documents found to process" };
}
const results = [];
for (const docName of documents) {
const document = await toolContext.loadArtifact(docName);
if (!document?.text) continue;
let result;
switch (params.operation) {
case "summarize":
result = { summary: document.text.substring(0, 200) + "..." };
break;
case "analyze":
result = {
wordCount: document.text.split(' ').length,
paragraphs: document.text.split('\n\n').length
};
break;
case "compare":
// Compare with memory knowledge
const related = await toolContext.searchMemory(document.text.substring(0, 100));
result = { relatedMemories: related.memories?.length || 0 };
break;
}
results.push({
document: docName,
result: result
});
}
// Save processing results
const reportName = `processing_report_${Date.now()}.json`;
await toolContext.saveArtifact(reportName, {
text: JSON.stringify({
operation: params.operation,
processedDocuments: results,
timestamp: new Date().toISOString()
}, null, 2)
});
// Update state
toolContext.state.lastProcessing = {
operation: params.operation,
documentsProcessed: results.length,
reportFile: reportName
};
return {
operation: params.operation,
processedCount: results.length,
results: results,
reportFile: reportName
};
}
Event Action Management
Flow Control Actions
ToolContext provides direct access to flow control through the actions
property:
async function smartExitTool(
params: { condition: string },
toolContext: ToolContext
) {
// Check condition against memory and state
const memoryCheck = await toolContext.searchMemory(params.condition);
const stateCheck = toolContext.state.exitConditions?.[params.condition];
if (memoryCheck.memories?.length > 0 || stateCheck) {
// Exit the current loop/flow
toolContext.actions.escalate = true;
return {
action: "exit",
reason: "Condition met",
condition: params.condition
};
}
return {
action: "continue",
condition: params.condition,
status: "not_met"
};
}
Agent Transfer
Tools can transfer control to other agents:
async function intelligentTransfer(
params: { userQuery: string },
toolContext: ToolContext
) {
// Search memory for best agent for this query
const agentSuggestions = await toolContext.searchMemory(`agent for ${params.userQuery}`);
// Analyze current state
const currentAgent = toolContext.agentName;
const userHistory = toolContext.state.userHistory || [];
// Determine best agent based on memory and context
let targetAgent = "general_assistant"; // default
if (params.userQuery.includes("code")) {
targetAgent = "code_assistant";
} else if (params.userQuery.includes("research")) {
targetAgent = "research_assistant";
}
// Transfer to the determined agent
toolContext.actions.transferToAgent = targetAgent;
// Update state with transfer info
toolContext.state.lastTransfer = {
from: currentAgent,
to: targetAgent,
reason: params.userQuery,
timestamp: new Date().toISOString()
};
return {
action: "transfer",
fromAgent: currentAgent,
toAgent: targetAgent,
reason: "Specialized agent better suited for this query"
};
}
Advanced Use Cases
Multi-Source Information Synthesis
Combine memory, artifacts, and state for comprehensive responses:
async function synthesizeInformation(
params: { topic: string },
toolContext: ToolContext
) {
// Gather information from all sources
const [memories, artifacts, state] = await Promise.all([
toolContext.searchMemory(params.topic),
toolContext.listArtifacts(),
Promise.resolve(toolContext.state)
]);
// Load relevant artifacts
const relevantArtifacts = artifacts.filter(name =>
name.toLowerCase().includes(params.topic.toLowerCase())
);
const artifactContents = await Promise.all(
relevantArtifacts.map(async name => ({
name,
content: await toolContext.loadArtifact(name)
}))
);
// Synthesize information
const synthesis = {
topic: params.topic,
sources: {
memories: memories.memories?.length || 0,
artifacts: artifactContents.length,
stateReferences: Object.keys(state).filter(key =>
key.toLowerCase().includes(params.topic.toLowerCase())
).length
},
summary: `Found information about ${params.topic} from multiple sources`,
details: {
memoryInsights: memories.memories?.slice(0, 3) || [],
relevantFiles: relevantArtifacts,
stateData: state[params.topic] || null
}
};
// Save comprehensive report
await toolContext.saveArtifact(`synthesis_${params.topic}_${Date.now()}.json`, {
text: JSON.stringify(synthesis, null, 2)
});
return synthesis;
}
Context-Aware Decision Making
Use all available context for intelligent decisions:
async function makeContextualDecision(
params: { decision: string; options: string[] },
toolContext: ToolContext
) {
// Gather contextual information
const userHistory = toolContext.state.userHistory || [];
const recentChoices = toolContext.state.recentChoices || [];
const memoryGuidance = await toolContext.searchMemory(
`decision ${params.decision} options ${params.options.join(' ')}`
);
// Analyze patterns
const patterns = {
userPreference: analyzeUserPreferences(userHistory, params.options),
pastChoices: recentChoices.slice(-5),
memoryInsights: memoryGuidance.memories || []
};
// Make intelligent choice
const recommendedOption = patterns.userPreference || params.options[0];
// Update decision history
toolContext.state.recentChoices = [
...recentChoices.slice(-9), // Keep last 9
{
decision: params.decision,
recommended: recommendedOption,
timestamp: new Date().toISOString(),
functionCallId: toolContext.functionCallId
}
];
return {
decision: params.decision,
recommended: recommendedOption,
reasoning: patterns,
confidence: calculateConfidence(patterns)
};
}
function analyzeUserPreferences(history: any[], options: string[]): string | null {
// Analyze user history to determine preferences
// Implementation depends on your specific needs
return null;
}
function calculateConfidence(patterns: any): number {
// Calculate confidence score based on available patterns
return 0.7; // Placeholder
}
Best Practices
Error Handling
async function robustToolExecution(
params: any,
toolContext: ToolContext
) {
try {
// Attempt operations with fallbacks
let result;
try {
const memories = await toolContext.searchMemory(params.query);
result = processWithMemory(memories);
} catch (memoryError) {
console.warn("Memory search failed, using local processing:", memoryError);
result = processLocally(params);
}
// Save successful result
await toolContext.saveArtifact("operation_result.json", {
text: JSON.stringify(result, null, 2)
});
return result;
} catch (error) {
// Log error and update state
toolContext.state.lastError = {
tool: "robustToolExecution",
error: error instanceof Error ? error.message : String(error),
timestamp: new Date().toISOString(),
functionCallId: toolContext.functionCallId
};
return { error: "Operation failed", details: String(error) };
}
}
function processWithMemory(memories: any) {
// Implementation with memory results
return { source: "memory", data: memories };
}
function processLocally(params: any) {
// Fallback local processing
return { source: "local", data: params };
}
Resource Optimization
async function optimizedToolExecution(
params: any,
toolContext: ToolContext
) {
// Check cache first
const cacheKey = `tool_cache_${JSON.stringify(params)}`;
const cached = await toolContext.loadArtifact(cacheKey);
if (cached) {
toolContext.state.cacheHits = (toolContext.state.cacheHits || 0) + 1;
return JSON.parse(cached.text || '{}');
}
// Batch operations when possible
const [artifacts, memories] = await Promise.all([
toolContext.listArtifacts(),
toolContext.searchMemory(params.query || "")
]);
// Process efficiently
const result = {
artifacts: artifacts.length,
memories: memories.memories?.length || 0,
processed: true
};
// Cache result
await toolContext.saveArtifact(cacheKey, {
text: JSON.stringify(result, null, 2)
});
return result;
}
Related Contexts
- CallbackContext: Base class providing state management and artifact operations
- ReadonlyContext: For read-only access without modification capabilities
- InvocationContext: For complete framework access in agent implementations