Tools
Tools expose Python functions to agents. Legacy decorators and provider imports remain compatible, but runtime-owned orchestration is the preferred direction.
from praval import Agent
agent = Agent("calculator", provider="openai", model="gpt-5.4-mini")
@agent.tool
def add(x: int, y: int) -> int:
return x + y
try:
print(agent.chat("Use the tool to add 2 and 3."))
finally:
agent.close()
Tool declarations are normalized into ToolSpec objects with JSON Schema
parameters. HITL metadata such as requires_approval, risk_level, and
approval_reason is preserved when legacy tool dictionaries are converted.
Register an existing JSON Schema, including an MCP tool’s input schema, with
Agent.add_tool_spec(ToolSpec(...), handler). Object schemas retain their
properties, required fields, nested arrays/objects, integer and boolean types,
enums and constraints. Anthropic sends them as input_schema; Gemini sends
them through its native parametersJsonSchema field, separate from the older
parameters representation. OpenAI and Cohere v2 send JSON Schema. Cohere v1
translates it into native parameter definitions and includes the complete schema
in the tool description; runtime argument validation still uses the original
schema.
Provider APIs may reject schema features outside their supported subset.
See Gemini function declarations
and Anthropic tool schemas.
The registration contract tests exercise add_tool_spec through real adapters
with fake SDK/HTTP clients and inspect declarations on every dependent request.
The live certificate in examples/certification/live_provider_tools.py also
uses this registration path, requires integer and boolean arguments, and checks
actual handler execution and per-request usage. For a Gemini release-branch
check with a configured key and model:
source venv/bin/activate
export PRAVAL_GEMINI_MODEL="gemini-3.1-flash-lite"
export PRAVAL_DEMO_REPORT_DIR="/tmp/praval-gemini-tools"
python examples/certification/live_provider_tools.py --provider gemini
For Anthropic, set ANTHROPIC_API_KEY and PRAVAL_ANTHROPIC_MODEL, select a
separate report directory, and use --provider anthropic. These checks call
paid APIs and require available quota.
Agent.tool and Agent.add_tool_spec also add the tool to the global tool
registry for discovery, unless that name is already registered. close()
removes the entries the agent added; entries registered elsewhere, including
shared tools attached to the agent, stay in the registry.
Argument validation
Model-supplied arguments are validated before the handler runs, on the sync, async and HITL paths alike (for HITL, after approval, so edited arguments are checked too):
A Python function is validated against its signature with pydantic in lax mode, so
"3"becomes3for anintparameter and the handler receives the coerced values. A number is accepted for astrparameter and passed as a string (42becomes"42";trueis still rejected), when the installed pydantic supportscoerce_numbers_to_str. A parameter whose default isNoneacceptsNone, as if annotatedOptional[...]. Unknown arguments are rejected unless the function takes**kwargs. A parameter whose annotation cannot be resolved, or has none, is passed through unchanged.A tool whose handler only accepts
**kwargsand that declares a JSON Schema object (tools added withAgent.add_tool_spec, including MCP tools) is validated withjsonschema, Draft 2020-12 unless the schema declares another$schema. This path does not coerce values. An invalid schema is skipped with a warning. A$refresolves only inside the schema itself: remote andfile:references are never fetched, and a schema whose$refcannot be resolved, or that is nested too deeply to check, is skipped with a warning. Arguments nested too deeply to validate are rejected. A string longer than 10,000 characters (praval.tool_execution.MAX_PATTERN_STRING_CHARS) fails apatternkeyword without the pattern being evaluated. Shorter inputs use theregexengine in compatible VERSION0 mode with a 50 ms matching timeout. A timeout returns a typed validation failure and the handler does not run. Patterns themselves are limited to 10,000 characters. ExternalpatternPropertiesschemas are rejected before validation becausejsonschemaalso matches those patterns insideadditionalPropertiesandunevaluatedPropertieshelpers without a timeout. Nested$schemadialect changes are also rejected so they cannot restore an untimed validator. Ordinaryproperties,propertyNames, and nestedpatternchecks remain supported. These limits apply to external schemas; trusted response-schema matching retains its existing behavior. The timeout applies to each match rather than the entire validation operation. See regex timeout documentation.
An argument string from the model that is not a JSON object (malformed JSON,
or a JSON array or scalar) is passed to validation as {"raw": "<string>"},
so it fails as an unexpected raw argument instead of the tool running with
its defaults. An empty string means no arguments.
When validation fails the handler is not called. The model receives an error result naming each failing field and the expected type, for example:
Error: Invalid arguments for tool 'add': x: Input should be a valid integer,
unable to parse string as an integer, expected int; z: unexpected argument
Tool results
Every tool call produces one ToolResult, in sync and async runs alike:
A handler that returns a
ToolResultkeeps itscontent,is_errorandmetadata; the runtime setstool_call_idandnamefrom the model’s call.Any other return value becomes
content=str(value). A string that starts withError:,Unknown function:orRejected by human reviewer:is an error, as in earlier releases.An exception becomes an error result with content
Error: <ExceptionType>: <message>.Unknown tools and rejected HITL calls are error results.
execute_legacy_tool_call and HITLRuntime.execute_or_interrupt /
execute_with_decision still return the result content as a string for
provider adapters; HITLRuntime.execute_or_interrupt_result and
execute_with_decision_result return the ToolResult, as do the async
variants.
Execution
Providers translate declarations and provider-specific tool-call wire shapes. Runtime code owns execution, approval and resume state, tracing, and final follow-up calls. Retry behavior is provider and error specific; Praval does not promise universal retries or a circuit breaker.