Groq tool use error: for 'role:assistant' ... property 'reasoning' is unsupported
Please update gptel first -- errors are often fixed by the time they're reported.
- [x] I have updated gptel to the latest commit and tested that the issue still exists
Bug Description
Groq is returning an error after using a tool due to the content of a request.
"HTTP/2 400 \r\ndate: Thu, 10 Apr 2025 12:50:42 GMT\r\ncontent-type: application/json\r\ncontent-length: 199..."
{
"gptel": "response body",
"timestamp": "2025-04-10 07:50:42"
}
{
"error": {
"message": "'messages.2' : for 'role:assistant' the following must be satisfied[('messages.2' : property 'reasoning' is unsupported, did you mean 'role'?)]",
"type": "invalid_request_error"
}
}
Request to Groq includes this after it uses any tool. The request made here uses the "brave_search" tool from the wiki which has worked in the past for me. Other tools like the emacs documentation tool also cause the same problem, which is that the messages "assistant" object has an unexpected field, "reasoning".
{"gptel": "request Curl command", "timestamp": "2025-04-10 07:50:42"}
curl \
--disable \
--location \
-d[[EXPANDED BELOW]]
...
https\://api.groq.com/openai/v1/chat/completions
data expanded
{
"model": "deepseek-r1-distill-llama-70b",
"messages": [
{
"role": "system",
"content": "You are a large language model living in Emacs and a helpful eager assistant, ready to use the tools available to you to accomplish your tasks. Give me well organized information that will help me accomplish tasks quickly. I live in REDACTED, REDACTED."
},
{
"role": "user",
"content": "What's the weather today?"
},
{
"role": "assistant",
"reasoning": "Alright, the user is asking about the weather today in REDACTED, REDACTED. I need to figure out how to get that information using the tools I have. Looking at the tools provided, I see that \"brave_search\" can be used to perform a web search. That seems like the right tool for this because I can't access live weather data directly but can look it up online.\n\nSo, I should use the brave_search function with a query that specifies today's weather in REDACTED, REDACTED. That should fetch the relevant information from the web. I need to make sure the query is clear so that the search results are accurate.\n\nI'll structure the tool call with the function name as \"brave_search\" and the query parameter as \"weather today in REDACTED, REDACTED\". That should give the user the up-to-date weather information they're asking for.\n",
"tool_calls": [
{
"id": "call_q1ef",
"type": "function",
"function": {
"name": "brave_search",
"arguments": "{\"query\": \"weather today in REDACTED, REDACTED\"}"
}
}
]
},
Backend
Other (please specify in Additional Context)
Steps to Reproduce
- Add tooling and enable it.
- Have a chat that invokes a tool.
Additional Context
Emacs version: GNU Emacs 30.1 (build 1, x86_64-redhat-linux-gnu, GTK+ Version 3.24.43, cairo version 1.18.2) of 2025-03-13 Operating System: Fedora Linux 41.20250402.0 (Silverblue) Backend info:
(gptel-make-openai "Groq"
:host "api.groq.com"
:endpoint "/openai/v1/chat/completions"
:stream nil
:key groq-api-key
Backtrace
Log Information
{
"model": "deepseek-r1-distill-llama-70b",
"messages": [
{
"role": "system",
"content": "You are a large language model living in Emacs and a helpful eager assistant, ready to use the tools available to you to accomplish your tasks. Give me well organized information that will help me accomplish tasks quickly. I live in REDACTED, REDACTED."
},
{
"role": "user",
"content": "What's the weather today?"
}
],
"stream": false,
"temperature": 1.0,
"tools": [
{
"type": "function",
"function": {
"name": "read_url",
"description": "Fetch and read the contents of a URL",
"parameters": {
"type": "object",
"properties": {
"url": {
"type": "string",
"description": "The URL to read"
}
},
"required": [
"url"
],
"additionalProperties": false
}
}
},
{
"type": "function",
"function": {
"name": "brave_search",
"description": "Perform a web search using the Brave Search API",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The search query string"
}
},
"required": [
"query"
],
"additionalProperties": false
}
}
},
{
"type": "function",
"function": {
"name": "list_directory",
"description": "List the contents of a given directory",
"parameters": {
"type": "object",
"properties": {
"directory": {
"type": "string",
"description": "The path to the directory to list"
}
},
"required": [
"directory"
],
"additionalProperties": false
}
}
},
{
"type": "function",
"function": {
"name": "read_documentation",
"description": "Read the documentation for a given function or variable",
"parameters": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The name of the function or variable whose documentation is to be retrieved"
}
},
"required": [
"name"
],
"additionalProperties": false
}
}
},
{
"type": "function",
"function": {
"name": "echo_message",
"description": "Send a message to the *Messages* buffer",
"parameters": {
"type": "object",
"properties": {
"text": {
"type": "string",
"description": "The text to send to the messages buffer"
}
},
"required": [
"text"
],
"additionalProperties": false
}
}
}
],
"parallel_tool_calls": true
}
Tool code from wiki I have:
(defvar brave-search-api-key nil
"API key for accessing the Brave Search API.")
(when brave-search-api-key
(defun brave-search-query (query)
"Perform a web search using the Brave Search API with the given QUERY."
(unless brave-search-api-key
(user-error "Missing Brave search key"))
(let ((url-request-method "GET")
(url-request-extra-headers `(("X-Subscription-Token" . ,brave-search-api-key)))
(url (format "https://api.search.brave.com/res/v1/web/search?q=%s" (url-encode-url query))))
(with-current-buffer (url-retrieve-synchronously url)
(goto-char (point-min))
(when (re-search-forward "^$" nil 'move)
(let ((json-object-type 'hash-table)) ; Use hash-table for JSON parsing
(json-parse-string (buffer-substring-no-properties (point) (point-max))))))))
(gptel-make-tool
:function #'brave-search-query
:name "brave_search"
:description "Perform a web search using the Brave Search API"
:args (list '(:name "query"
:type string
:description "The search query string"))
:category "web"))
Output is not used correctly, but appears unrelated as other tools also not working.
From messages:
{
"role": "tool",
"tool_call_id": "call_q1ef",
"content": "#s(hash-table test equal data (\"query\" #s(hash-table test equal data (\"original\" \"weather today in REDACTED, REDACTED\" \"show_strict_warning\" :false \"is_navigational\" :false \"is_news_breaking\" :false \"spellcheck_off\"
Okay, I found the source of this error.
If you'll permit me to rant for a minute (not directed at you!): this whole API space is cursed. There is absolutely no consensus on what "OpenAI-compatible API" means, with every provider (like Groq) making up their own rules on how to serve responses and how to handle edge cases. It's a total mess. Groq uses a completely different response format if you use reasoning only vs reasoning + tool use. The parsing for the two is not even close, and none of this is documented anywhere. If I fix the Groq error I'll be breaking Openrouter response parsing, and I then fix that I'll break OpenAI responses.
I wonder, is there any LLM client out there that can handle ALL of these cases correctly?
- Groq streaming responses with reasoning and tool calls
- Groq streaming responses with reasoning only
- Other "OpenAI-compatible" APIs with streaming, reasoning and tool calls
2/3 is easy, I would be very surprised if it's all 3.
Thanks for the bug report @herbertjones, I'll fix it when I can figure out how to not break something else. Or Groq and Openrouter can get their crap together, but I'm not holding my breath.