from __future__ import annotations import json from typing import Any, Dict, List def to_data_url(image_str: str) -> str: if not isinstance(image_str, str) or not image_str: return image_str s = image_str.strip() if s.startswith("data:image/"): return s if s.startswith("http://") or s.startswith("https://"): return s b64 = s.replace("\n", "").replace("\r", "") kind = "image/png" if b64.startswith("/9j/"): kind = "image/jpeg" elif b64.startswith("iVBORw0KGgo"): kind = "image/png" elif b64.startswith("R0lGOD"): kind = "image/gif" return f"data:{kind};base64,{b64}" def convert_ollama_messages( messages: List[Dict[str, Any]] | None, top_images: List[str] | None ) -> List[Dict[str, Any]]: out: List[Dict[str, Any]] = [] msgs = messages if isinstance(messages, list) else [] pending_call_ids: List[str] = [] call_counter = 0 for m in msgs: if not isinstance(m, dict): continue role = m.get("role") or "user" nm: Dict[str, Any] = {"role": role} content = m.get("content") images = m.get("images") if isinstance(m.get("images"), list) else [] parts: List[Dict[str, Any]] = [] if isinstance(content, list): for p in content: if isinstance(p, dict) and p.get("type") == "text" and isinstance(p.get("text"), str): parts.append({"type": "text", "text": p.get("text")}) elif isinstance(content, str): parts.append({"type": "text", "text": content}) for img in images: url = to_data_url(img) if isinstance(url, str) and url: parts.append({"type": "image_url", "image_url": {"url": url}}) if parts: nm["content"] = parts if role == "assistant" and isinstance(m.get("tool_calls"), list): tcs = [] for tc in m.get("tool_calls"): if not isinstance(tc, dict): continue fn = tc.get("function") if isinstance(tc.get("function"), dict) else {} name = fn.get("name") if isinstance(fn.get("name"), str) else None args = fn.get("arguments") if name is None: continue call_id = tc.get("id") or tc.get("call_id") if not isinstance(call_id, str) or not call_id: call_counter += 1 call_id = f"ollama_call_{call_counter}" pending_call_ids.append(call_id) tcs.append( { "id": call_id, "type": "function", "function": { "name": name, "arguments": args if isinstance(args, str) else (json.dumps(args) if isinstance(args, dict) else "{}"), }, } ) if tcs: nm["tool_calls"] = tcs if role == "tool": tci = m.get("tool_call_id") or m.get("id") if not isinstance(tci, str) or not tci: if pending_call_ids: tci = pending_call_ids.pop(0) if isinstance(tci, str) and tci: nm["tool_call_id"] = tci if not parts and isinstance(content, str): nm["content"] = content out.append(nm) if isinstance(top_images, list) and top_images: attach_to = None for i in range(len(out) - 1, -1, -1): if out[i].get("role") == "user": attach_to = out[i] break if attach_to is None: attach_to = {"role": "user", "content": []} out.append(attach_to) attach_to.setdefault("content", []) for img in top_images: url = to_data_url(img) if isinstance(url, str) and url: attach_to["content"].append({"type": "image_url", "image_url": {"url": url}}) return out def normalize_ollama_tools(tools: List[Dict[str, Any]] | None) -> List[Dict[str, Any]]: out: List[Dict[str, Any]] = [] if not isinstance(tools, list): return out for t in tools: if not isinstance(t, dict): continue if isinstance(t.get("function"), dict): fn = t.get("function") name = fn.get("name") if isinstance(fn.get("name"), str) else None if not name: continue out.append( { "type": "function", "function": { "name": name, "description": fn.get("description") or "", "parameters": fn.get("parameters") if isinstance(fn.get("parameters"), dict) else {"type": "object", "properties": {}}, }, } ) continue name = t.get("name") if isinstance(t.get("name"), str) else None if name: out.append( { "type": "function", "function": { "name": name, "description": t.get("description") or "", "parameters": {"type": "object", "properties": {}}, }, } ) return out