tul-sdk
v0.1.0
Published
Claude-level tool calling for Gemini. Fewer tokens. Zero config.
Downloads
110
Maintainers
Readme
Tul
Claude-level tool calling for Gemini. Fewer tokens. Zero config.
Tul is a lightweight middleware library that brings Claude's superior tool-calling patterns to Google's Gemini models. It reduces token consumption, prevents common failure modes, and makes your Gemini agents significantly more reliable.
Why Tul Exists
While Gemini is powerful, its tool-calling behavior differs from Claude's in ways that impact real-world agents:
| Problem | Claude | Gemini | Tul | |---------|--------|--------|-----| | Token bloat | Efficient schemas | Verbose function definitions | Smart compression, filtering | | Malformed JSON | Rare | Occasional | Auto-repair with jsonrepair | | Tool loops | Built-in detection | None | Configurable loop detection | | Schema validation | Strict mode available | No enforcement | Strict validation with retry | | Examples | Supports tool examples | Limited | Example injection system | | Thought signatures | N/A | Gemini 3+ requirement | Automatic handling |
Tul bridges these gaps with a zero-config middleware pipeline that "just works."
Installation
npm install tulyarn add tulpnpm add tulQuick Start
import { createTul } from 'tul';
// Create client with defaults (all optimizations enabled)
const tul = createTul({
apiKey: process.env.GOOGLE_AI_API_KEY,
model: 'gemini-2.5-flash',
});
// Define tools
tul.defineTools([
{
name: 'search_web',
description: 'Search the web for information',
parameters: {
type: 'object',
properties: {
query: { type: 'string', description: 'Search query' },
maxResults: { type: 'integer', minimum: 1, maximum: 10 },
},
required: ['query'],
},
// Claude-inspired: provide examples
examples: [
{ query: 'latest TypeScript features', maxResults: 5 },
],
// Claude-inspired: enforce strict schema validation
strict: true,
},
]);
// Register tool handlers
tul.onToolCall(async (name, args) => {
if (name === 'search_web') {
return await searchWeb(args.query, args.maxResults);
}
});
// Run a conversation turn
const response = await tul.run('Find the latest news about AI');
console.log(response.text);
console.log(`Tokens saved: ${response.stats.tokensSaved}`);Features
Smart Tool Filtering
Reduces token usage by only sending relevant tools per request.
const tul = createTul({
apiKey: 'your-key',
model: 'gemini-2.5-flash',
toolFiltering: true, // default: true
maxToolsPerRequest: 5, // default: 5
filterThreshold: 0.3, // default: 0.3 (relevance score)
alwaysIncludeTools: ['error_handler'], // never filter these
});Schema Compression
Compresses JSON schemas to reduce token consumption.
const tul = createTul({
apiKey: 'your-key',
model: 'gemini-2.5-flash',
schemaCompression: true, // default: true
compressionLevel: 'moderate', // 'light' | 'moderate' | 'aggressive'
});Compression levels:
light: Remove only verbose descriptionsmoderate: Remove descriptions, abbreviate keys (default)aggressive: Maximum compression, drop optional field metadata
Example Injection (Claude-Inspired)
Injects tool usage examples into the system prompt.
tul.defineTools([
{
name: 'create_file',
description: 'Create a new file',
parameters: { /* ... */ },
examples: [
{ path: '/src/index.ts', content: 'export default {};' },
{ path: '/config.json', content: '{}' },
],
},
]);Example injection is automatic when exampleInjection: true (default).
Strict Schema Validation (Claude-Inspired)
Validates Gemini's tool call arguments against your schemas.
const tul = createTul({
apiKey: 'your-key',
model: 'gemini-2.5-flash',
strictValidation: true, // default: true
onValidationError: 'retry', // 'retry' | 'warn' | 'throw'
});
tul.defineTools([
{
name: 'send_email',
description: 'Send an email',
parameters: {
type: 'object',
properties: {
to: { type: 'string', pattern: '^[^@]+@[^@]+$' },
subject: { type: 'string', minLength: 1 },
body: { type: 'string' },
},
required: ['to', 'subject', 'body'],
},
strict: true, // Enable for this tool
},
]);JSON Repair
Automatically fixes malformed JSON in Gemini's responses.
const tul = createTul({
apiKey: 'your-key',
model: 'gemini-2.5-flash',
jsonRepair: true, // default: true
});Uses the jsonrepair library to handle common issues:
- Trailing commas
- Missing quotes
- Unescaped characters
- Truncated responses
Loop Detection
Prevents runaway tool call loops.
const tul = createTul({
apiKey: 'your-key',
model: 'gemini-2.5-flash',
loopDetection: true, // default: true
maxToolCallsPerTurn: 10, // default: 10
maxIdenticalCalls: 2, // default: 2
onLoop: 'break', // 'break' | 'warn'
});Detected loop types:
identical: Same tool called with same argumentsoscillation: Tool A calls tool B calls tool Arunaway: Too many tool calls in one turn
Retry Handler
Automatically retries on transient failures.
const tul = createTul({
apiKey: 'your-key',
model: 'gemini-2.5-flash',
retryOnFailure: true, // default: true
maxRetries: 3, // default: 3
retryDelay: 'exponential', // 'none' | 'linear' | 'exponential'
});Result Caching
Caches tool results to avoid redundant executions.
const tul = createTul({
apiKey: 'your-key',
model: 'gemini-2.5-flash',
resultCaching: true, // default: true
cacheTTL: 300000, // default: 5 minutes
cacheMaxSize: 100, // default: 100 entries
});
// Per-tool TTL override
tul.defineTools([
{
name: 'get_time',
description: 'Get current time',
cacheTTL: 0, // Never cache
},
{
name: 'get_weather',
description: 'Get weather for location',
cacheTTL: 60000, // Cache for 1 minute
},
]);Context Management (Claude-Inspired)
Automatically manages conversation context to stay within token limits.
const tul = createTul({
apiKey: 'your-key',
model: 'gemini-2.5-flash',
contextManagement: true, // default: true
maxContextTokens: 80000, // default: 80000
turnsToKeepFull: 3, // default: 3
compactionStrategy: 'summarize', // 'summarize' | 'truncate' | 'drop'
});Thought Signatures (Gemini 3+)
Handles thought signatures required by Gemini 3+ models.
const tul = createTul({
apiKey: 'your-key',
model: 'gemini-3-pro',
thoughtSignatures: true, // default: true
});Configuration Reference
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| apiKey | string | required | Google AI API key |
| model | string | required | Gemini model name |
| toolFiltering | boolean | true | Enable smart tool filtering |
| maxToolsPerRequest | number | 5 | Max tools to send per request |
| filterThreshold | number | 0.3 | Minimum relevance score (0-1) |
| alwaysIncludeTools | string[] | [] | Tools to never filter out |
| schemaCompression | boolean | true | Enable schema compression |
| compressionLevel | 'light' \| 'moderate' \| 'aggressive' | 'moderate' | Compression level |
| exampleInjection | boolean | true | Enable example injection |
| strictValidation | boolean | true | Enable schema validation |
| onValidationError | 'retry' \| 'warn' \| 'throw' | 'retry' | Validation error action |
| jsonRepair | boolean | true | Enable JSON repair |
| loopDetection | boolean | true | Enable loop detection |
| maxToolCallsPerTurn | number | 10 | Max tool calls per turn |
| maxIdenticalCalls | number | 2 | Max identical calls before loop |
| onLoop | 'break' \| 'warn' | 'break' | Loop detection action |
| retryOnFailure | boolean | true | Enable automatic retry |
| maxRetries | number | 3 | Maximum retry attempts |
| retryDelay | 'none' \| 'linear' \| 'exponential' | 'linear' | Retry delay strategy |
| resultCaching | boolean | true | Enable result caching |
| cacheTTL | number | 300000 | Cache TTL in ms (5 min) |
| cacheMaxSize | number | 100 | Max cache entries |
| contextManagement | boolean | true | Enable context management |
| maxContextTokens | number | 80000 | Max context tokens |
| turnsToKeepFull | number | 3 | Recent turns to keep full |
| compactionStrategy | 'summarize' \| 'truncate' \| 'drop' | 'summarize' | Context compaction strategy |
| thoughtSignatures | boolean | true | Enable thought signatures |
| systemPrompt | string | undefined | Custom system prompt |
| verbose | boolean | false | Enable verbose logging |
| logLevel | 'debug' \| 'info' \| 'warn' \| 'error' \| 'silent' | 'warn' | Log level |
API Reference
createTul(config: TulConfig): TulClient
Creates a new Tul client with the specified configuration.
TulClient
defineTools(tools: ToolDefinition[]): void
Registers tool definitions. Can be called multiple times to add more tools.
tul.defineTools([
{ name: 'tool_a', description: '...', parameters: { ... } },
{ name: 'tool_b', description: '...', parameters: { ... } },
]);onToolCall(handler: ToolCallHandler): void
Registers a handler for tool call execution.
tul.onToolCall(async (name, args) => {
switch (name) {
case 'search': return await search(args.query);
case 'read_file': return await fs.readFile(args.path, 'utf8');
default: throw new Error(`Unknown tool: ${name}`);
}
});run(message: string): Promise<TulResponse>
Sends a message and handles the full tool-calling loop.
const response = await tul.run('What is the weather in Tokyo?');addMessage(content: Content): void
Manually adds a message to the conversation history.
clearHistory(): void
Clears the conversation history.
getStats(): CumulativeStats
Returns cumulative statistics across all requests.
const stats = tul.getStats();
console.log(`Total tokens saved: ${stats.tokensSaved} (${stats.percentSaved}%)`);TulResponse
interface TulResponse {
text: string; // Final text response
toolCalls: ToolCallResult[]; // Tools called during request
stats: RequestStats; // Per-request statistics
raw: unknown; // Raw Gemini API response
truncatedByLoop?: boolean; // Whether loop detection triggered
warnings?: string[]; // Any warnings generated
}RequestStats
interface RequestStats {
inputTokens: number;
outputTokens: number;
tokensSaved: number;
toolsFiltered: number;
toolsSent: number;
examplesInjected: number;
exampleTokens: number;
compressionSaved: number;
cacheHit: boolean;
cacheHits: number;
retries: number;
jsonRepaired: boolean;
loopDetected: boolean;
validationFailed: boolean;
validationRecovered: boolean;
contextCompactionSaved: number;
toolCallsMade: number;
}Events
Listen for tool execution events:
tul.on('tool:call', ({ name, args }) => {
console.log(`Calling ${name} with`, args);
});
tul.on('tool:result', ({ name, result, cached }) => {
console.log(`${name} returned:`, result, cached ? '(cached)' : '');
});
tul.on('tool:loop', ({ loopType }) => {
console.warn(`Loop detected: ${loopType}`);
});
tul.on('tool:validation:fail', ({ name, errors }) => {
console.warn(`Validation failed for ${name}:`, errors);
});Token Savings Benchmarks
Note: Benchmarks coming soon. We're actively measuring token savings across various agent workloads.
Expected savings based on feature analysis:
| Feature | Estimated Savings | |---------|------------------| | Tool Filtering (10 -> 5 tools) | ~40-60% on tool schema tokens | | Schema Compression (moderate) | ~20-30% on schema tokens | | Result Caching | Varies by hit rate | | Context Compaction | ~30-50% on context tokens |
Overall, Tul typically reduces total token consumption by 20-40% compared to naive Gemini tool calling.
Comparison with Alternatives
| Feature | Tul | Raw Gemini | LangChain | |---------|-----|------------|-----------| | Zero config | Yes | N/A | No | | Token optimization | Yes | No | Limited | | Loop detection | Yes | No | No | | JSON repair | Yes | No | No | | Strict validation | Yes | No | Limited | | Bundle size | ~50KB | N/A | ~500KB+ |
Contributing
Contributions are welcome! Please read our contributing guidelines before submitting a pull request.
Development Setup
# Clone the repository
git clone https://github.com/your-org/tul.git
cd tul
# Install dependencies
npm install
# Run tests
npm test
# Run tests in watch mode
npm run test:watch
# Lint
npm run lint
# Type check
npm run typecheck
# Build
npm run buildProject Structure
src/
types/ # TypeScript type definitions
utils/ # Shared utility functions
logger.ts # Configurable logging
helpers.ts # General helpers
schema-utils.ts # JSON Schema utilities
middleware/ # Middleware implementations
pipeline.ts # Middleware orchestration
tool-filter.ts # Smart tool filtering
schema-compressor.ts
example-injector.ts
strict-validator.ts
json-repairer.ts
loop-detector.ts
retry-handler.ts
result-cache.ts
context-manager.ts
thought-signatures.ts
analytics/ # Token tracking and reporting
token-tracker.ts
reporter.ts
tool-runner.ts # Tool execution engine
client.ts # Main TulClient
index.ts # Public exportsCode Style
- TypeScript strict mode
- ESLint + Prettier
- No external dependencies except
jsonrepairandzod
License
MIT License - see LICENSE for details.
Built with care to make Gemini tool calling reliable.
