Files
smartagent/ts/smartagent.tools.json.ts
Juergen Kunz 60f8bbe1b6 feat(tools): add getToolExplanation() method with XML examples for LLM tool calling
Each tool now provides comprehensive documentation including parameter
schemas and concrete <tool_call> XML examples. This helps smaller LLMs
understand the exact format required for tool invocation.
2026-01-20 01:30:03 +00:00

225 lines
6.5 KiB
TypeScript

import * as interfaces from './smartagent.interfaces.js';
import { BaseToolWrapper } from './smartagent.tools.base.js';
/**
* JsonValidatorTool - Validates and formats JSON data
* Useful for agents to self-validate their JSON output before completing a task
*/
export class JsonValidatorTool extends BaseToolWrapper {
public name = 'json';
public description = 'Validate and format JSON data. Use this to verify your JSON output is valid before completing a task.';
public actions: interfaces.IToolAction[] = [
{
name: 'validate',
description: 'Validate that a string is valid JSON and optionally check required fields',
parameters: {
type: 'object',
properties: {
jsonString: {
type: 'string',
description: 'The JSON string to validate',
},
requiredFields: {
type: 'array',
items: { type: 'string' },
description: 'Optional list of field names that must be present at the root level',
},
},
required: ['jsonString'],
},
},
{
name: 'format',
description: 'Parse and pretty-print JSON string',
parameters: {
type: 'object',
properties: {
jsonString: {
type: 'string',
description: 'The JSON string to format',
},
},
required: ['jsonString'],
},
},
];
async initialize(): Promise<void> {
this.isInitialized = true;
}
async cleanup(): Promise<void> {
this.isInitialized = false;
}
async execute(
action: string,
params: Record<string, unknown>
): Promise<interfaces.IToolExecutionResult> {
this.validateAction(action);
switch (action) {
case 'validate':
return this.validateJson(params);
case 'format':
return this.formatJson(params);
default:
return { success: false, error: `Unknown action: ${action}` };
}
}
/**
* Validate JSON string and optionally check for required fields
*/
private validateJson(params: Record<string, unknown>): interfaces.IToolExecutionResult {
const jsonString = params.jsonString as string;
const requiredFields = params.requiredFields as string[] | undefined;
if (!jsonString || typeof jsonString !== 'string') {
return {
success: false,
error: 'jsonString parameter is required and must be a string',
};
}
try {
const parsed = JSON.parse(jsonString);
// Check required fields if specified
if (requiredFields && Array.isArray(requiredFields)) {
const missingFields = requiredFields.filter((field) => {
if (typeof parsed !== 'object' || parsed === null) {
return true;
}
return !(field in parsed);
});
if (missingFields.length > 0) {
return {
success: false,
error: `Missing required fields: ${missingFields.join(', ')}`,
result: {
valid: false,
missingFields,
presentFields: Object.keys(parsed || {}),
},
};
}
}
return {
success: true,
result: {
valid: true,
parsed,
type: Array.isArray(parsed) ? 'array' : typeof parsed,
fieldCount: typeof parsed === 'object' && parsed !== null ? Object.keys(parsed).length : undefined,
},
summary: `JSON is valid (${Array.isArray(parsed) ? 'array' : typeof parsed})`,
};
} catch (error) {
const errorMessage = (error as Error).message;
// Extract position from error message if available
const posMatch = errorMessage.match(/position\s*(\d+)/i);
const position = posMatch ? parseInt(posMatch[1]) : undefined;
// Provide context around the error position
let context: string | undefined;
if (position !== undefined) {
const start = Math.max(0, position - 20);
const end = Math.min(jsonString.length, position + 20);
context = jsonString.substring(start, end);
}
return {
success: false,
error: `Invalid JSON: ${errorMessage}`,
result: {
valid: false,
errorPosition: position,
errorContext: context,
},
};
}
}
/**
* Format/pretty-print JSON string
*/
private formatJson(params: Record<string, unknown>): interfaces.IToolExecutionResult {
const jsonString = params.jsonString as string;
if (!jsonString || typeof jsonString !== 'string') {
return {
success: false,
error: 'jsonString parameter is required and must be a string',
};
}
try {
const parsed = JSON.parse(jsonString);
const formatted = JSON.stringify(parsed, null, 2);
return {
success: true,
result: formatted,
summary: `Formatted JSON (${formatted.length} chars)`,
};
} catch (error) {
return {
success: false,
error: `Cannot format invalid JSON: ${(error as Error).message}`,
};
}
}
public getToolExplanation(): string {
return `## Tool: json
Validate and format JSON data. Use this to verify your JSON output is valid before completing a task.
### Actions:
**validate** - Validate that a string is valid JSON and optionally check required fields
Parameters:
- jsonString (required): The JSON string to validate
- requiredFields (optional): Array of field names that must be present
Example:
<tool_call>
<tool>json</tool>
<action>validate</action>
<params>{"jsonString": "{\\"invoice_number\\":\\"INV-001\\",\\"total\\":99.99}", "requiredFields": ["invoice_number", "total"]}</params>
</tool_call>
**format** - Parse and pretty-print JSON string
Parameters:
- jsonString (required): The JSON string to format
Example:
<tool_call>
<tool>json</tool>
<action>format</action>
<params>{"jsonString": "{\\"name\\":\\"test\\",\\"value\\":123}"}</params>
</tool_call>
`;
}
getCallSummary(action: string, params: Record<string, unknown>): string {
const jsonStr = (params.jsonString as string) || '';
const preview = jsonStr.length > 50 ? jsonStr.substring(0, 50) + '...' : jsonStr;
switch (action) {
case 'validate':
const fields = params.requiredFields as string[] | undefined;
const fieldInfo = fields ? ` (checking fields: ${fields.join(', ')})` : '';
return `Validate JSON: ${preview}${fieldInfo}`;
case 'format':
return `Format JSON: ${preview}`;
default:
return `JSON ${action}: ${preview}`;
}
}
}