From 1f0944de210b14f6173a70dbe2cc7bfea9e52d6f Mon Sep 17 00:00:00 2001 From: teknium1 Date: Mon, 9 Mar 2026 03:32:32 -0700 Subject: [PATCH] fix: handle non-string content from OpenAI-compatible servers (#759) Some local LLM servers (llama-server, etc.) return message.content as a dict or list instead of a plain string. This caused AttributeError 'dict object has no attribute strip' on every API call. Normalizes content to string immediately after receiving the response: - dict: extracts 'text' or 'content' field, falls back to json.dumps - list: extracts text parts (OpenAI multimodal content format) - other: str() conversion Applied at the single point where response.choices[0].message is read in the main agent loop, so all downstream .strip()/.startswith()/[:100] operations work regardless of server implementation. Closes #759 --- run_agent.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/run_agent.py b/run_agent.py index 04522912..c1f2623c 100644 --- a/run_agent.py +++ b/run_agent.py @@ -3834,6 +3834,27 @@ class AIAgent: else: assistant_message = response.choices[0].message + # Normalize content to string — some OpenAI-compatible servers + # (llama-server, etc.) return content as a dict or list instead + # of a plain string, which crashes downstream .strip() calls. + if assistant_message.content is not None and not isinstance(assistant_message.content, str): + raw = assistant_message.content + if isinstance(raw, dict): + assistant_message.content = raw.get("text", "") or raw.get("content", "") or json.dumps(raw) + elif isinstance(raw, list): + # Multimodal content list — extract text parts + parts = [] + for part in raw: + if isinstance(part, str): + parts.append(part) + elif isinstance(part, dict) and part.get("type") == "text": + parts.append(part.get("text", "")) + elif isinstance(part, dict) and "text" in part: + parts.append(str(part["text"])) + assistant_message.content = "\n".join(parts) + else: + assistant_message.content = str(raw) + # Handle assistant response if assistant_message.content and not self.quiet_mode: print(f"{self.log_prefix}🤖 Assistant: {assistant_message.content[:100]}{'...' if len(assistant_message.content) > 100 else ''}")