Summary
When a tool parameter is declared with withEnumParameter() and the handler method types that parameter as a BackedEnum subclass, Prism forwards the raw string value from the model straight into call_user_func. PHP's strict typing rejects the call, the TypeError is caught, and the model receives a validation error instead of a successful tool invocation. I'd like Prism to cast the string to the enum before invoking the handler, analogous to Laravel's route-model binding of enums.
Reproduction
use Prism\Prism\Tool;
enum Classification: string
{
case A = 'A';
case B = 'B';
case C = 'C';
}
class ClassifyTool extends Tool
{
public function __construct()
{
$this->as('classify')
->for('Classify something')
->withEnumParameter(
name: 'target',
description: 'Target classification',
options: array_map(fn (Classification $c) => $c->value, Classification::cases()),
)
->using($this);
}
public function __invoke(Classification $target): string
{
return $target->value;
}
}
The model sends {"target": "B"}. "B" is a string; $target expects Classification. The call fails.
Current behavior
The TypeError is caught by Tool::handle() (around the call_user_func($this->fn, ...$args) line) and turned into a ToolError sent back to the model:
Parameter validation error: Type mismatch. Expected: [target (EnumSchema, required)]. Received: {"target":"B"}. Please provide correct parameter types and names.
The schema-declared type (EnumSchema with string options) and the PHP handler signature (Classification) disagree, and the gap is silently pushed onto the caller. Current workaround is to type the handler parameter as string and call Classification::from(\$value) manually inside the handler.
Proposed behavior
Before calling the handler, inspect each handler parameter via reflection. If the type is a BackedEnum subclass and the corresponding argument is a string or int, convert it with \$enumClass::from(\$value). If the schema for that parameter was declared with withEnumParameter, the developer has already told Prism the options map to enum cases — the cast is unambiguous.
\$reflection = new ReflectionFunction(Closure::fromCallable(\$this->fn));
foreach (\$reflection->getParameters() as \$index => \$param) {
\$type = \$param->getType();
if (! \$type instanceof ReflectionNamedType) {
continue;
}
\$typeName = \$type->getName();
if (! is_subclass_of(\$typeName, BackedEnum::class)) {
continue;
}
\$key = array_key_exists(\$param->getName(), \$args) ? \$param->getName() : \$index;
\$value = \$args[\$key] ?? null;
if (is_string(\$value) || is_int(\$value)) {
\$args[\$key] = \$typeName::from(\$value);
}
}
Version and related work
Happy to open a PR with tests if the maintainers are open to the change.
Summary
When a tool parameter is declared with
withEnumParameter()and the handler method types that parameter as aBackedEnumsubclass, Prism forwards the raw string value from the model straight intocall_user_func. PHP's strict typing rejects the call, theTypeErroris caught, and the model receives a validation error instead of a successful tool invocation. I'd like Prism to cast the string to the enum before invoking the handler, analogous to Laravel's route-model binding of enums.Reproduction
The model sends
{"target": "B"}."B"is a string;$targetexpectsClassification. The call fails.Current behavior
The
TypeErroris caught byTool::handle()(around thecall_user_func($this->fn, ...$args)line) and turned into aToolErrorsent back to the model:The schema-declared type (
EnumSchemawith string options) and the PHP handler signature (Classification) disagree, and the gap is silently pushed onto the caller. Current workaround is to type the handler parameter asstringand callClassification::from(\$value)manually inside the handler.Proposed behavior
Before calling the handler, inspect each handler parameter via reflection. If the type is a
BackedEnumsubclass and the corresponding argument is a string or int, convert it with\$enumClass::from(\$value). If the schema for that parameter was declared withwithEnumParameter, the developer has already told Prism the options map to enum cases — the cast is unambiguous.Version and related work
mainstill callscall_user_funcwith raw args and does no handler-side casting.enum,BackedEnum,withEnumParameter,tool parameter type. Nothing overlapping. Closest tangentially-related open issue is [Groq] Llama models return string values for boolean/number parameters, Groq rejects with 400 #984 (Groq returns wrong wire types for boolean/number params), but that's provider-side normalization, distinct from handler-side enum casting.BackedEnumsupport, so this fits the direction of travel.Happy to open a PR with tests if the maintainers are open to the change.