diff --git a/bot.py b/bot.py index b276911..a9a2efc 100644 --- a/bot.py +++ b/bot.py @@ -678,6 +678,24 @@ async def send_image_to_channel(url: str, channel: discord.abc.Messageable, capt return f"Failed to fetch or send image: {exc}" +def _assistant_msg_dict(msg) -> dict: + """Serialize an assistant ChatCompletionMessage to a plain dict. + + Qwen's jinja template rejects content=null, so we normalize it to "". + """ + d: dict = {"role": "assistant", "content": msg.content or ""} + if msg.tool_calls: + d["tool_calls"] = [ + { + "id": tc.id, + "type": "function", + "function": {"name": tc.function.name, "arguments": tc.function.arguments}, + } + for tc in msg.tool_calls + ] + return d + + def _ensure_user_tail(messages: list) -> None: """Qwen's chat template requires a non-empty user message as the last message.""" if not messages: @@ -750,7 +768,7 @@ async def _run_agent_inner(messages: list, channel: discord.abc.Messageable | No if not msg.tool_calls: return msg.content - kwargs["messages"].append(msg) + kwargs["messages"].append(_assistant_msg_dict(msg)) for tc in msg.tool_calls: args = json.loads(tc.function.arguments) @@ -822,7 +840,7 @@ async def run_reflection_agent(messages: list, max_turns: int) -> tuple[str, str if not msg.tool_calls: break - kwargs["messages"].append(msg) + kwargs["messages"].append(_assistant_msg_dict(msg)) for tc in msg.tool_calls: args = json.loads(tc.function.arguments) @@ -875,7 +893,7 @@ async def run_life_event_reflection(messages: list): if not msg.tool_calls: break - kwargs["messages"].append(msg) + kwargs["messages"].append(_assistant_msg_dict(msg)) for tc in msg.tool_calls: args = json.loads(tc.function.arguments) @@ -1244,11 +1262,17 @@ async def on_message(message: discord.Message): messages = [{"role": "system", "content": system_content}] + list(history)[:-1] + [current_msg] async with message.channel.typing(): - try: - reply = await run_agent(messages, channel=message.channel) - except Exception as exc: - reply = f"Error: {exc}" - history.pop() + reply = None + for attempt in range(3): + try: + reply = await run_agent(messages, channel=message.channel) + break + except Exception as exc: + log.error(f"[agent] Error (attempt {attempt + 1}/3): {exc}") + if attempt == 2: + history.pop() + return + await asyncio.sleep(2) if reply: history.append({"role": "assistant", "content": reply})