Tandoor Version
2.6.9 (Home Assistant add-on by alexbelgium)
Setup
Others (please state below)
Reverse Proxy
Others (please state below)
Other
Cloudflare Tunnel in front of Home Assistant Add-on (alexbelgium/hassio-addons tandoor_recipes 2.6.9). Add-on serves nginx → uwsgi internally.
Bug description
The POST /api/recipe/{id}/aiproperties/?provider={n} endpoint crashes with JSONDecodeError: Expecting value: line 1 column 1 (char 0) whenever the configured AI provider is an Anthropic Claude model (tested with anthropic/claude-sonnet-4-5 via LiteLLM).
Root cause: in cookbook/views/api.py (around line 2084 on the 2.6.9 tag) the code does:
ai_request = {
'api_key': ai_provider.api_key,
'model': ai_provider.model_name,
'response_format': {"type": "json_object"},
'messages': messages,
}
ai_response = completion(**ai_request)
response_text = ai_response.choices[0].message.content
return Response(json.loads(response_text), status=status.HTTP_200_OK)
Although response_format={"type": "json_object"} is passed to LiteLLM, the Anthropic backend in LiteLLM does not enforce JSON mode the way OpenAI does — Claude is free to return JSON wrapped in a Markdown code fence:
Calling `json.loads()` on that raw string fails with `Expecting value: line 1 column 1 (char 0)` because the first character is a backtick, not `{`. The same code path works for OpenAI / Gemini providers because those backends actually enforce JSON-only responses on the wire.
The same pattern exists in the AI import path; the import endpoint happens to handle Claude correctly only because the upstream parser on that branch is more lenient.
### Steps to reproduce
1. Add an Anthropic AI Provider in Django admin: model name `anthropic/claude-sonnet-4-5` (or any Claude 4.x model), valid API key.
2. Set the new provider as the space's default AI provider.
3. Open any recipe and trigger the AI property generation.
Result: 500, traceback below.
### Suggested fix
Strip Markdown code fences (and optional `json` language tag) from `response_text` before `json.loads()`. A minimal robust version:
```python
import re
def _strip_json_fence(text: str) -> str:
text = text.strip()
m = re.match(r"^```(?:json)?\s*\n(.*)\n```\s*$", text, re.DOTALL)
return m.group(1) if m else text
response_text = _strip_json_fence(ai_response.choices[0].message.content)
return Response(json.loads(response_text), status=status.HTTP_200_OK)
Even better: switch to Anthropic's structured outputs / tool-use schema enforcement (LiteLLM supports it for Claude 4.x), so the server-side guarantee is preserved. The fence-stripping fix is enough for the immediate crash though, and protects against any future provider that occasionally fences its output.
Relevant logs
ThreadPoolExecutor-0_0 ERROR django.request Internal Server Error: /api/recipe/22/aiproperties/
Traceback (most recent call last):
File "/opt/recipes/cookbook/views/api.py", line 2084, in aiproperties
return Response(json.loads(response_text), status=status.HTTP_200_OK)
~~~~~~~~~~^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.13/json/__init__.py", line 352, in loads
return _default_decoder.decode(s)
~~~~~~~~~~~~~~~~~~~~~~~^^
File "/usr/local/lib/python3.13/json/decoder.py", line 345, in decode
obj, end = self.raw_decode(s, idx=_w(s, 0).end())
~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.13/json/decoder.py", line 363, in raw_decode
raise JSONDecodeError("Expecting value", s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
POST /api/recipe/22/aiproperties/?provider=7 HTTP/1.1 500 131
Tandoor Version
2.6.9 (Home Assistant add-on by alexbelgium)
Setup
Others (please state below)
Reverse Proxy
Others (please state below)
Other
Cloudflare Tunnel in front of Home Assistant Add-on (alexbelgium/hassio-addons tandoor_recipes 2.6.9). Add-on serves nginx → uwsgi internally.
Bug description
The
POST /api/recipe/{id}/aiproperties/?provider={n}endpoint crashes withJSONDecodeError: Expecting value: line 1 column 1 (char 0)whenever the configured AI provider is an Anthropic Claude model (tested withanthropic/claude-sonnet-4-5via LiteLLM).Root cause: in
cookbook/views/api.py(around line 2084 on the 2.6.9 tag) the code does:Although
response_format={"type": "json_object"}is passed to LiteLLM, the Anthropic backend in LiteLLM does not enforce JSON mode the way OpenAI does — Claude is free to return JSON wrapped in a Markdown code fence:Even better: switch to Anthropic's structured outputs / tool-use schema enforcement (LiteLLM supports it for Claude 4.x), so the server-side guarantee is preserved. The fence-stripping fix is enough for the immediate crash though, and protects against any future provider that occasionally fences its output.
Relevant logs