Tool Output Filter Plugin
Shrink large tool outputs with intelligent JQ filtering to save tokens
When a tool returns a huge JSON response (like a full API payload or database result), it eats up your token budget and can confuse the model. The Tool Output Filter Plugin automatically detects oversized outputs and uses an LLM to generate JQ filters that extract only the relevant data — shrinking the output while preserving what matters.
Quick start
import { AgentBuilder, LLMRegistry, ToolOutputFilterPlugin } from "@iqai/adk";
const { runner } = await AgentBuilder.withModel("gemini-1.5-pro")
.withDescription("Data analysis assistant")
.withPlugins(
new ToolOutputFilterPlugin({
filterModel: LLMRegistry.newLLM("gemini-1.5-flash"),
}),
)
.build();That's it. Any tool output over 8KB or 50 JSON keys will be automatically filtered down to ~4KB.
Requires JQ
This plugin shells out to the jq command-line tool. Install it before using:
brew install jq (macOS), apt-get install jq (Ubuntu), or choco install jq (Windows).
How it works
- A tool returns a large JSON response
- The plugin checks if it exceeds the size/key thresholds
- If so, it asks a fast LLM (like Gemini Flash) to generate a JQ filter expression (e.g.,
{id, name, summary}) - It runs
jqon the data to extract just the relevant fields - If the result is still too large, it repeats (up to
maxIterations) - The filtered output replaces the original, with metadata showing what changed
If anything goes wrong (JQ error, timeout, dangerous pattern), the plugin falls back to the original output — it never breaks your agent.
Usage
import { AgentBuilder, LLMRegistry, ToolOutputFilterPlugin } from "@iqai/adk";
const { runner } = await AgentBuilder.withModel("gemini-1.5-pro")
.withDescription("Data analysis assistant")
.withPlugins(
new ToolOutputFilterPlugin({
filterModel: LLMRegistry.newLLM("gemini-1.5-flash"),
enabledTools: ["api_tool", "database_tool"], // only filter these
}),
)
.build();import { LlmAgent, LLMRegistry, ToolOutputFilterPlugin } from "@iqai/adk";
const agent = new LlmAgent({
name: "data_assistant",
description: "Analyzes large datasets",
model: "gemini-1.5-pro",
plugins: [
new ToolOutputFilterPlugin({
filterModel: LLMRegistry.newLLM("gemini-1.5-flash"),
}),
],
});import { AgentBuilder, LLMRegistry, ToolOutputFilterPlugin } from "@iqai/adk";
const { runner } = await AgentBuilder.withModel("gemini-1.5-pro")
.withDescription("Data analysis assistant")
.withPlugins(
new ToolOutputFilterPlugin({
filterModel: LLMRegistry.newLLM("gemini-1.5-flash"),
config: {
sizeThreshold: 12000, // trigger at 12KB (default: 8000)
keyThreshold: 75, // trigger at 75 keys (default: 50)
targetSize: 6000, // aim for 6KB output (default: 4000)
maxIterations: 5, // up to 5 filter passes (default: 3)
debug: true, // log filtering details
},
enabledTools: ["api_fetcher", "database_query"],
}),
)
.build();Configuration
| Option | Type | Default | Description |
|---|---|---|---|
filterModel | BaseLlm | Required | LLM to generate JQ filters. Use LLMRegistry.newLLM("model-name") |
sizeThreshold | number | 8000 | Character count to trigger filtering |
keyThreshold | number | 50 | JSON key count to trigger filtering |
targetSize | number | 4000 | Target output size after filtering |
maxIterations | number | 3 | Max filter-and-check cycles |
maxSchemaDepth | number | 4 | Depth for schema extraction |
debug | boolean | false | Log filtering details |
enabledTools | string[] | undefined | Only filter these tools (undefined = all) |
disabledTools | string[] | undefined | Skip filtering for these tools |
Use a fast, cheap model for filterModel (like Gemini Flash) — filter
generation is lightweight and doesn't need a powerful model.
What the filtered output looks like
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..."
}When to use it
- Large API responses (REST APIs returning verbose data)
- Database queries with nested relationships
- File processing (large JSON/XML files)
- Third-party integrations with verbose outputs
When not to use it
- Small outputs (unnecessary overhead)
- Binary/non-JSON data (plugin only processes JSON)
- Critical data where every field matters
- Real-time requirements where filtering latency is unacceptable
Debugging
new ToolOutputFilterPlugin({
filterModel: LLMRegistry.newLLM("gemini-1.5-flash"),
config: { debug: true },
});
// Logs: response size/key count, generated JQ filters, iteration progressTroubleshooting
| Issue | Fix |
|---|---|
| Not filtering outputs | Check output exceeds thresholds. Verify tool isn't in disabledTools. |
| Filtering too slow | Reduce maxIterations. Increase targetSize. Use faster filter model. |
| Critical data filtered out | Increase targetSize. Adjust sizeThreshold higher. |
| JQ errors | Verify JQ is installed (jq --version). Check for malformed JSON. Enable debug: true. |