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); + } }