feat: enhance function calling with richer schemas, validation, parallel execution, and Ollama native support (#51)
* feat: add richer JSON Schema types and AJV validation utility - Extended ToolParameter with enum, items, properties, additionalProperties (max depth 3) - Added ToolError class with ToolErrorCategory enum - Added ToolValidationError interface - Created AJV-based validateToolArgs() with lazy loading and schema depth enforcement - 57 new tests covering types and validation * feat: add validation gate, parallel execution, and structured error feedback - Arsenal.execute() validates args against JSON Schema before handler execution - Tool execution errors categorized (scope_denied, tool_not_found, timeout, execution_error, validation_error) - ToolError import fix in agent (was missing, causing silent ReferenceError) - Semaphore-based parallel tool execution with configurable concurrency (default 5) - Results maintain original call order regardless of completion order - Structured error feedback to LLM with category only (no raw error messages) - Scope/approval gates checked per-call before execution - 35 new tests covering validation, parallel execution, and error feedback * feat: add Ollama native function calling with probe + cache + fallback - Added nativeTools config option to LLMConfig - LocalAdapter probes model support for native tools on first call - Probe result cached per model name to avoid re-probing - Native tools formatted as Ollama /api/chat tools parameter - Tool calls parsed from Ollama's tool_calls response format - Falls back to text-based renderToolContract when native not supported - OpenAI-compatible wire format also supported (for vLLM, LM Studio) - 10 new tests covering probe, cache, fallback, and config overrides * fix(tools): harden validation and native fallback --------- Co-authored-by: sronaal <sronaal@users.noreply.github.com> Co-authored-by: Joseph Magly <1159087+jmagly@users.noreply.github.com>
This commit is contained in:
14 files changed
+3019
-63
No files matched your search
@@ -16,6 +16,12 @@ export interface LLMConfig {
|
||||
maxTokens?: number;
|
||||
temperature?: number;
|
||||
timeout?: number;
|
||||
/**
|
||||
* When true, force native Ollama / OpenAI-compatible function calling. When
|
||||
* false, always use text-based tool contract injection. When unset, probe
|
||||
* on the first call and cache per-model.
|
||||
*/
|
||||
nativeTools?: boolean;
|
||||
/**
|
||||
* Ordered model/provider ladder to fall back to when the PRIMARY model fails for
|
||||
* ANY reason it can't self-recover from — hard errors after same-model retries
|
||||
@@ -79,6 +85,13 @@ export interface LLMToolDefinition {
|
||||
enum?: string[];
|
||||
items?: { type: string };
|
||||
default?: unknown;
|
||||
properties?: Record<string, {
|
||||
type: string;
|
||||
description?: string;
|
||||
enum?: string[];
|
||||
}>;
|
||||
required?: string[];
|
||||
additionalProperties?: boolean;
|
||||
}>;
|
||||
required?: string[];
|
||||
};
|
||||
@@ -391,6 +404,27 @@ export interface Message {
|
||||
* additionally fire a loud warning. Absent/safe/active tools are ungated. */
|
||||
export type RiskTier = 'local_read' | 'passive' | 'active' | 'intrusive' | 'credential' | 'dangerous';
|
||||
|
||||
/** Structured error categories for tool failures — enables LLM feedback without leaking internals. */
|
||||
export enum ToolErrorCategory {
|
||||
ScopeDenied = 'scope_denied',
|
||||
ToolNotFound = 'tool_not_found',
|
||||
Timeout = 'timeout',
|
||||
ExecutionError = 'execution_error',
|
||||
ValidationError = 'validation_error',
|
||||
}
|
||||
|
||||
/** Structured error thrown by tool execution — carries category for LLM feedback and optional tool name. */
|
||||
export class ToolError extends Error {
|
||||
constructor(
|
||||
public readonly category: ToolErrorCategory,
|
||||
message: string,
|
||||
public readonly toolName?: string,
|
||||
) {
|
||||
super(message);
|
||||
this.name = 'ToolError';
|
||||
}
|
||||
}
|
||||
|
||||
export interface CustomTool {
|
||||
name: string;
|
||||
description: string;
|
||||
@@ -408,6 +442,26 @@ export interface ToolParameter {
|
||||
description: string;
|
||||
required: boolean;
|
||||
default?: unknown;
|
||||
/** Allowed values — constrains the parameter to a fixed set. */
|
||||
enum?: (string | number)[];
|
||||
/** Schema for array items (when type === 'array'). */
|
||||
items?: {
|
||||
type: string;
|
||||
description?: string;
|
||||
properties?: Record<string, ToolParameter>;
|
||||
};
|
||||
/** Nested object properties (when type === 'object'). */
|
||||
properties?: Record<string, ToolParameter>;
|
||||
/** Whether additional properties are allowed on object parameters. */
|
||||
additionalProperties?: boolean;
|
||||
}
|
||||
|
||||
/** A single validation error produced by validateToolArgs. */
|
||||
export interface ToolValidationError {
|
||||
field: string;
|
||||
message: string;
|
||||
received: unknown;
|
||||
expected: string;
|
||||
}
|
||||
|
||||
export interface ToolContext {
|
||||
|
||||
Reference in new issue
Block a user