From a9461034a197f7956a7e016ddb4ad09b9ee0d176 Mon Sep 17 00:00:00 2001 From: Joseph Ivan Obala Date: Thu, 26 Mar 2026 16:51:03 +0300 Subject: [PATCH] fix(groq): accept string values for boolean/number tool parameters Llama models on Groq (e.g. llama-3.3-70b-versatile) serialize all tool call arguments as JSON strings regardless of the declared schema type. Groq validates the model output against the schema we send and rejects any mismatch before returning it to the caller: Groq Error [400]: tool call validation failed: parameters for tool get_transactions did not match schema: errors: [ `/is_cash_in`: expected boolean, but got string, `/limit`: expected number, but got string ] Fix: wrap boolean, number, and integer property types in anyOf so that Groq accepts either the declared type or a plain string from the model. The caller is responsible for coercing string values to the expected PHP type before invoking the tool handler. --- src/Providers/Groq/Maps/ToolMap.php | 31 ++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/Providers/Groq/Maps/ToolMap.php b/src/Providers/Groq/Maps/ToolMap.php index 7f665f4cc..7a4a61ee0 100644 --- a/src/Providers/Groq/Maps/ToolMap.php +++ b/src/Providers/Groq/Maps/ToolMap.php @@ -21,10 +21,39 @@ public static function Map(array $tools): array 'description' => $tool->description(), 'parameters' => [ 'type' => 'object', - 'properties' => $tool->parametersAsArray(), + 'properties' => static::relaxTypes($tool->parametersAsArray()), 'required' => $tool->requiredParameters(), ], ], ], $tools); } + + /** + * Llama models running on Groq consistently return all tool argument + * values as JSON strings, even when the schema declares boolean or number. + * Groq validates the model's output against our schema and rejects it: + * + * Groq Error [400]: tool call validation failed: parameters for tool + * get_transactions did not match schema: `/limit`: expected number, + * but got string + * + * Wrapping non-string scalar types in anyOf accepts both the declared type + * and a plain string, so Groq passes the response through to the caller + * regardless of how the model serialised the value. + * + * @param array> $properties + * @return array> + */ + protected static function relaxTypes(array $properties): array + { + return array_map(function (array $prop): array { + $type = $prop['type'] ?? null; + if (is_string($type) && in_array($type, ['boolean', 'number', 'integer'], true)) { + unset($prop['type']); + $prop['anyOf'] = [['type' => $type], ['type' => 'string']]; + } + + return $prop; + }, $properties); + } }