Tool Output Filter Plugin
Automatically filter and reduce large tool outputs using intelligent JQ filtering to maintain optimal token usage
The Tool Output Filter Plugin automatically detects and reduces oversized tool outputs using intelligent JQ filtering powered by LLM. This plugin helps maintain optimal token usage by filtering verbose API responses, large datasets, and nested structures while preserving essential information.
Overview
This plugin intercepts tool outputs that exceed configurable size thresholds and uses an LLM to generate JQ filters that extract the most relevant data. The filtering process is iterative and intelligent, ensuring that critical information is preserved while reducing overall size.
Key Features
- Automatic Detection: Monitors tool output size and key count to trigger filtering
- Intelligent Filtering: Uses LLM to generate context-aware JQ filters
- Iterative Refinement: Multiple filtering iterations for optimal size reduction
- Configurable Thresholds: Customizable size limits and target sizes
- Tool-Specific Control: Enable/disable filtering for specific tools
- Security First: Rejects dangerous JQ patterns and validates filter safety
- Performance Optimized: Efficient processing with configurable timeouts
Installation
pnpm add @iqai/adkExternal Dependency
This plugin requires the jq command-line tool to be installed and available
in your system's PATH. You can install it from
stedolan.github.io/jq or using your
system's package manager.
Installing jq:
# macOS (using Homebrew)
brew install jq
# Ubuntu/Debian
sudo apt-get install jq
# CentOS/RHEL/Fedora
sudo yum install jq # or dnf install jq
# Windows (using Chocolatey)
choco install jq
# Verify installation
jq --versionBasic Usage
import { InMemoryRunner, LlmAgent } from "@iqai/adk";
import { ToolOutputFilterPlugin } from "@iqai/adk";
// Basic setup with default configuration
const filterPlugin = new ToolOutputFilterPlugin({
filterModel: "gemini-1.5-flash", // Fast/cheap model for filter generation
});
const agent = new LlmAgent({
name: "data_assistant",
description: "Analyzes large datasets",
model: "gemini-1.5-pro",
tools: [new LargeDataApiTool()],
});
const runner = new InMemoryRunner(agent, {
appName: "filter-demo",
plugins: [filterPlugin],
});import { InMemoryRunner, LlmAgent } from "@iqai/adk";
import { ToolOutputFilterPlugin } from "@iqai/adk";
// Advanced configuration with custom thresholds
const filterPlugin = new ToolOutputFilterPlugin({
filterModel: "gemini-1.5-flash",
config: {
sizeThreshold: 12000, // Trigger at 12KB instead of default 8KB
keyThreshold: 75, // Trigger at 75 keys instead of default 50
targetSize: 6000, // Target 6KB output instead of default 4KB
maxIterations: 5, // Allow up to 5 iterations (default: 3)
maxSchemaDepth: 6, // Extract schema to depth 6 (default: 4)
debug: true, // Enable debug logging
},
});
const runner = new InMemoryRunner(agent, {
appName: "advanced-filter-demo",
plugins: [filterPlugin],
});import { InMemoryRunner, LlmAgent } from "@iqai/adk";
import { ToolOutputFilterPlugin } from "@iqai/adk";
// Filter only specific tools
const filterPlugin = new ToolOutputFilterPlugin({
filterModel: "gemini-1.5-flash",
enabledTools: ["api_fetcher", "database_query"], // Only these tools
config: {
sizeThreshold: 10000,
targetSize: 5000,
},
});
// Alternative: Exclude specific tools from filtering
const excludePlugin = new ToolOutputFilterPlugin({
filterModel: "gemini-1.5-flash",
disabledTools: ["small_api", "config_tool"], // Skip these tools
config: {
sizeThreshold: 8000,
targetSize: 4000,
},
});Configuration Options
FilterConfig Interface
interface FilterConfig {
/** Size threshold in characters to trigger filtering (default: 8000) */
sizeThreshold?: number;
/** Key/field count threshold to trigger filtering (default: 50) */
keyThreshold?: number;
/** Target size after filtering (default: 4000) */
targetSize?: number;
/** Maximum filtering iterations (default: 3) */
maxIterations?: number;
/** Maximum depth for schema extraction (default: 4) */
maxSchemaDepth?: number;
/** Enable debug logging (default: false) */
debug?: boolean;
}ToolOutputFilterPluginOptions Interface
interface ToolOutputFilterPluginOptions {
/** Plugin name (default: "tool_output_filter_plugin") */
name?: string;
/** LLM model used to generate JQ filters (required) */
filterModel: BaseLlm;
/** Only filter outputs from these tools (undefined = filter all) */
enabledTools?: string[];
/** Skip filtering for these tools */
disabledTools?: string[];
/** Configuration for filtering behavior */
config?: FilterConfig;
}How It Works
Filtering Process
- Detection: Monitors tool output size and key count
- Analysis: Extracts schema and generates preview of the data
- Filter Generation: Uses LLM to create context-aware JQ filter
- Application: Applies JQ filter to reduce output size
- Iteration: Repeats process until target size is reached or max iterations exceeded
- Metadata: Adds filtering metadata to the result
Filtered Output Format
When filtering is applied, the output includes metadata:
{
"_mcp_filtered": true,
"_original_size": 15000,
"_filtered_size": 4500,
"_reduction_percent": 70.0,
"_jq_filters": ["{id, name, summary}"],
"_iterations": 2,
"id": "123",
"name": "Example Data",
"summary": "Filtered content..."
}API Reference
Constructor
constructor(options: ToolOutputFilterPluginOptions)Creates a new ToolOutputFilterPlugin instance with the specified configuration.
Parameters:
options: Configuration object defining filter behavior
Example:
const plugin = new ToolOutputFilterPlugin({
filterModel: myLlmModel,
config: { sizeThreshold: 10000 },
enabledTools: ["api_tool"],
});afterToolCallback
async afterToolCallback(params: {
tool: BaseTool;
toolArgs: Record<string, any>;
toolContext: ToolContext;
result: Record<string, any>;
}): Promise<Record<string, any> | undefined>Main callback method that processes tool outputs and applies filtering when needed.
Parameters:
tool: The tool that generated the outputtoolArgs: Arguments passed to the tooltoolContext: Context information for the tool executionresult: The tool's output result
Returns:
- Filtered result with metadata if filtering was applied
undefinedif no filtering was needed
shouldFilterTool
private shouldFilterTool(toolName: string): booleanDetermines whether a tool's output should be filtered based on enabled/disabled tool lists.
Parameters:
toolName: Name of the tool to check
Returns:
trueif the tool should be filteredfalseif the tool should be skipped
analyzeResponse
private analyzeResponse(result: any): SchemaExtractionResultAnalyzes tool output to extract schema, calculate size, and count keys.
Parameters:
result: Tool output to analyze
Returns:
- Object containing schema, preview, size, and key count
needsFiltering
private needsFiltering(analysis: SchemaExtractionResult): booleanDetermines if filtering is needed based on size and key thresholds.
Parameters:
analysis: Analysis result fromanalyzeResponse
Returns:
trueif filtering should be appliedfalseif output is within thresholds
Advanced Usage
Custom Filter Model Configuration
import { GoogleGenAI } from "@google/genai";
// Use a specific model configuration for filter generation
const filterModel = new GoogleGenAI({
apiKey: process.env.GOOGLE_API_KEY,
}).getGenerativeModel({ model: "gemini-1.5-flash" });
const plugin = new ToolOutputFilterPlugin({
filterModel,
config: {
sizeThreshold: 10000,
targetSize: 5000,
maxIterations: 4,
},
});Performance Optimization
Performance Tips
Optimize filtering performance by adjusting thresholds and iteration limits based on your specific use case.
// For high-throughput applications
const performancePlugin = new ToolOutputFilterPlugin({
filterModel: "gemini-1.5-flash",
config: {
sizeThreshold: 15000, // Higher threshold to reduce filtering frequency
targetSize: 8000, // Higher target for faster processing
maxIterations: 2, // Fewer iterations for speed
maxSchemaDepth: 3, // Shallow schema extraction
},
});
// For memory-constrained environments
const memoryPlugin = new ToolOutputFilterPlugin({
filterModel: "gemini-1.5-flash",
config: {
sizeThreshold: 5000, // Lower threshold for early filtering
targetSize: 2000, // Aggressive size reduction
maxIterations: 5, // More iterations for better compression
},
});Error Handling
The plugin includes comprehensive error handling and will fall back to the original output if filtering fails:
// Failed filtering scenarios:
// 1. JQ filter generation fails
// 2. JQ execution fails or times out
// 3. Filtered output is larger than original
// 4. Dangerous patterns detected in generated filter
// In all cases, the plugin returns undefined and the original output is usedSecurity Features
Dangerous Pattern Detection
The plugin automatically rejects JQ filters containing dangerous patterns:
const dangerousPatterns = [
"system(", // System command execution
"$ENV", // Environment variable access
"env.", // Environment object access
"input_filename", // File system access
"$__", // Internal variable access
];Safe Filter Generation
- Spawn-based Execution: Uses Node.js spawn to avoid command injection
- Timeout Protection: 10-second timeout for JQ execution
- Input Validation: Validates all filter inputs and outputs
- Error Isolation: Plugin failures don't affect agent execution
Integration Examples
With AgentBuilder
import { AgentBuilder } from "@iqai/adk";
import { ToolOutputFilterPlugin } from "@iqai/adk";
const { runner } = await AgentBuilder.withModel("gemini-1.5-pro")
.withDescription("Data analysis assistant")
.withTools([new ApiTool(), new DatabaseTool()])
.withSessionService(sessionService)
.withPlugins(
new ToolOutputFilterPlugin({
filterModel: "gemini-1.5-flash",
enabledTools: ["api_tool", "database_tool"],
config: {
sizeThreshold: 10000,
targetSize: 5000,
},
}),
)
.build();With Multiple Plugins
import { InMemoryRunner, LlmAgent } from "@iqai/adk";
import { ToolOutputFilterPlugin, ReflectAndRetryToolPlugin } from "@iqai/adk";
const plugins = [
// Filter large outputs first
new ToolOutputFilterPlugin({
filterModel: "gemini-1.5-flash",
config: { sizeThreshold: 8000, targetSize: 4000 },
}),
// Then handle any errors that occur
new ReflectAndRetryToolPlugin({
maxRetries: 3,
}),
];
const runner = new InMemoryRunner(agent, {
appName: "production-app",
plugins,
});Best Practices
When to Use
- Large API Responses: REST APIs returning verbose data
- Database Queries: Complex queries with nested relationships
- File Processing: Reading large JSON/XML files
- External Integrations: Third-party services with verbose responses
- Data Aggregation: Combining multiple data sources
When Not to Use
- Small Outputs: Outputs below size thresholds (unnecessary overhead)
- Binary Data: Non-JSON outputs (plugin only processes JSON)
- Critical Data: When every field is essential for downstream processing
- Real-time Requirements: When filtering latency is unacceptable
Configuration Guidelines
Important
Always test filtering behavior with your specific data to ensure critical information is preserved.
- Start Conservative: Begin with default thresholds and adjust based on observations
- Monitor Performance: Track filtering frequency and execution time
- Test Data Integrity: Verify that filtered outputs contain necessary information
- Adjust Thresholds: Fine-tune based on your token budget and performance requirements
- Use Tool-Specific Controls: Enable filtering only for tools that produce large outputs
Debugging
Enable debug logging to monitor filtering behavior:
const debugPlugin = new ToolOutputFilterPlugin({
filterModel: "gemini-1.5-flash",
config: {
debug: true, // Enable detailed logging
},
});
// Debug output includes:
// - Response size and key count analysis
// - Generated JQ filters
// - Iteration progress
// - Success/failure reasonsTroubleshooting
Common Issues
Plugin not filtering outputs:
- Check if output exceeds size/key thresholds
- Verify tool is not in disabledTools list
- Ensure tool is in enabledTools list (if specified)
Filtering taking too long:
- Reduce maxIterations
- Increase targetSize for faster convergence
- Use faster filter model
Critical data being filtered out:
- Increase targetSize to preserve more data
- Reduce maxSchemaDepth for more aggressive filtering
- Adjust sizeThreshold to trigger filtering less frequently
JQ errors:
- Check for malformed JSON in tool outputs
- Verify JQ is installed and accessible
- Review debug logs for specific error messages
Performance Considerations
- Filter Model Speed: Use fast/cheap models like Gemini 1.5 Flash for filter generation
- Threshold Tuning: Higher thresholds reduce filtering frequency but may miss optimization opportunities
- Iteration Limits: More iterations provide better filtering but increase latency
- Tool Selection: Only filter tools that consistently produce large outputs
- Async Processing: Plugin operations are asynchronous and non-blocking
Support
For issues with the Tool Output Filter Plugin:
- Check Debug Logs: Enable debug mode for detailed operation logging
- Review Configuration: Verify thresholds and tool lists are appropriate
- Test JQ Installation: Ensure JQ is properly installed (
jq --version) - Monitor Performance: Track filtering frequency and execution times
- Report Issues: Include debug logs and configuration when reporting problems