from typing import Any def _tool_call_ids(tool_calls: list[dict[str, Any]]) -> list[str]: return [tc.get("id", "") for tc in tool_calls if tc.get("id")] def sanitize_openai_messages(messages: list[dict[str, Any]]) -> list[dict[str, Any]]: """Убирает битые tool-цепочки и подряд идущих assistant без user между ними.""" if not messages: return messages system = messages[0] if messages[0].get("role") == "system" else None rest = messages[1:] if system else list(messages) cleaned: list[dict[str, Any]] = [] i = 0 while i < len(rest): msg = rest[i] role = msg.get("role") if role == "assistant" and msg.get("tool_calls"): tool_calls = msg["tool_calls"] needed_ids = set(_tool_call_ids(tool_calls)) if not needed_ids: i += 1 continue block = [msg] i += 1 found_ids: set[str] = set() while i < len(rest) and rest[i].get("role") == "tool": tool_id = rest[i].get("tool_call_id", "") if tool_id in needed_ids: block.append(rest[i]) found_ids.add(tool_id) i += 1 if found_ids == needed_ids: cleaned.extend(block) continue if role == "tool": # осиротевший tool без assistant tool_calls i += 1 continue if role == "assistant" and cleaned and cleaned[-1].get("role") == "assistant": # два assistant подряд ломают API (старый баг pomodoro) i += 1 continue cleaned.append(msg) i += 1 if system: return [system, *cleaned] return cleaned def strip_historical_reasoning(messages: list[dict[str, Any]]) -> list[dict[str, Any]]: """Reasoning из БД часто неполный — для старых сообщений убираем.""" result: list[dict[str, Any]] = [] for msg in messages: entry = dict(msg) if entry.get("role") == "assistant": entry.pop("reasoning", None) entry.pop("reasoning_content", None) entry.pop("reasoning_details", None) result.append(entry) return result