NousResearch/hermes-agent · error · RuntimeError
Auxiliary {task or 'call'}: LLM returned invalid response (t
Error message
Auxiliary {task or 'call'}: LLM returned invalid response (type={response_type}): {response_preview!r}. Expected object with .choices[0].message — check provider adapter or custom endpoint compatibility. What it means
The auxiliary response validator requires an object with .choices[0].message (OpenAI shape). It first tried _recover_aux_response_message() to salvage alternate shapes; recovery failed, so the response type name and a 120-char preview are embedded in the error. The message explicitly points at provider adapters or custom endpoints that do not produce OpenAI-compatible payloads.
Source
Thrown at agent/auxiliary_client.py:8513
f"Auxiliary {task or 'call'}: LLM returned None response"
)
from agent.aux_accounting import record_aux_usage
record_aux_usage(response, task, provider=provider, base_url=base_url)
# Allow SimpleNamespace responses from adapters (CodexAuxiliaryClient,
# AnthropicAuxiliaryClient) — they have .choices[0].message.
try:
choices = response.choices
if not choices or not hasattr(choices[0], "message"):
raise AttributeError("missing choices[0].message")
except (AttributeError, TypeError, IndexError) as exc:
recovered = _recover_aux_response_message(response)
if recovered is not None:
_record_relay_auxiliary_response_model(response)
_complete_relay_auxiliary_call()
return recovered
response_type = type(response).__name__
response_preview = str(response)[:120]
raise RuntimeError(
f"Auxiliary {task or 'call'}: LLM returned invalid response "
f"(type={response_type}): {response_preview!r}. "
f"Expected object with .choices[0].message — check provider "
f"adapter or custom endpoint compatibility."
) from exc
_record_relay_auxiliary_response_model(response)
_complete_relay_auxiliary_call()
return response
def _complete_relay_auxiliary_call(*, outcome: str = "success") -> None:
"""Close one auxiliary logical call after acceptance or terminal failure."""
context = _RELAY_AUX_CALL_CONTEXT.get()
if context is None:
return
from agent import relay_llm
relay_llm.complete_logical_call(View on GitHub (pinned to c896c09c42)
Solutions
- Inspect the response preview in the error message — it usually reveals an HTML error page or an Anthropic-shaped payload.
- Make the custom endpoint OpenAI chat.completions-compatible (`choices[0].message.content`).
- If the provider is Anthropic/Codex-style, use it via its proper adapter rather than the generic aux path.
- Pin the auxiliary task to a mainstream provider in config.yaml (`auxiliary.<task>.provider`) while the custom endpoint is fixed.
Defensive patterns
Strategy: validation
Validate before calling
import json, urllib.request
def endpoint_returns_openai_shape(base_url, model, api_key):
req = urllib.request.Request(
base_url.rstrip("/") + "/chat/completions",
data=json.dumps({"model": model, "messages": [{"role": "user", "content": "hi"}]}).encode(),
headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"})
body = json.load(urllib.request.urlopen(req, timeout=30))
return bool(body.get("choices") and body["choices"][0].get("message") is not None) Type guard
def looks_like_chat_completion(obj) -> bool:
choices = getattr(obj, "choices", None) or obj.get("choices") if isinstance(obj, dict) else getattr(obj, "choices", None)
try:
return bool(choices) and getattr(choices[0], "message", None) is not None
except (TypeError, IndexError, AttributeError):
return False Try / catch
try:
resp = await aux_call_async(...)
except RuntimeError as e:
if "Expected object with .choices[0].message" in str(e):
log.error("aux endpoint not OpenAI-compatible: %s", e)
raise # configuration problem — retrying won't help Prevention
- Validate custom endpoints return OpenAI chat.completion JSON before assigning them auxiliary tasks.
- Route Anthropic/Codex-style providers through their dedicated adapters, not the generic aux path.
When it happens
Trigger: Calling an auxiliary task through a custom endpoint or adapter whose response object lacks .choices (e.g. a raw dict, an Anthropic-style content block response, or an error HTML payload stringified by the SDK).
Common situations: Pointing auxiliary tasks at a LiteLLM/proxy that returns a nonstandard schema on error; a provider plugin whose adapter returns a partially-built object; an endpoint that returns 200 with an error body.
Related errors
- Auxiliary {task or 'call'}: LLM returned None response
- Codex auxiliary Responses stream exceeded {float(total_timeo
- Codex auxiliary Responses stream did not return a final resp
- Auxiliary {task or 'call'}: provider {resolved_provider} cou
- Auxiliary streamed call timed out after {self._total_ceiling
AI-assisted analysis of NousResearch/hermes-agent@c896c09c42 (2026-08-14).
Data as JSON: /api/errors/b53c01e255c99bd5.
Report an issue: GitHub.