From e6f7f33a0927dc763a79ab77fa0d560279289d5c Mon Sep 17 00:00:00 2001 From: vahidrezvani Date: Wed, 25 Feb 2026 12:34:16 +0330 Subject: [PATCH] add kia ai chat tools7 --- src/services/kiaai/chat.py | 52 +++++++++++++++++++++++++++++++------- 1 file changed, 43 insertions(+), 9 deletions(-) diff --git a/src/services/kiaai/chat.py b/src/services/kiaai/chat.py index fb7e2dd..399ac03 100644 --- a/src/services/kiaai/chat.py +++ b/src/services/kiaai/chat.py @@ -25,21 +25,18 @@ def _to_openai_messages(messages: list[BaseMessage]) -> list[dict]: Supports multimodal HumanMessages where content is already a list (e.g. [{"type": "text", "text": "..."}, {"type": "image_url", ...}]). + For plain text-only content lists, normalizes to a plain string. """ result = [] for msg in messages: if isinstance(msg, SystemMessage): - result.append({"role": "system", "content": msg.content}) + result.append({"role": "system", "content": _normalize_content(msg.content)}) elif isinstance(msg, HumanMessage): - # content may be a plain string or a multimodal list - if isinstance(msg.content, list): - result.append({"role": "user", "content": msg.content}) - else: - result.append({"role": "user", "content": msg.content}) + result.append({"role": "user", "content": _normalize_content(msg.content)}) elif isinstance(msg, AIMessage): - result.append({"role": "assistant", "content": msg.content}) + result.append({"role": "assistant", "content": _normalize_content(msg.content)}) elif isinstance(msg, ToolMessage): result.append({ @@ -54,6 +51,28 @@ def _to_openai_messages(messages: list[BaseMessage]) -> list[dict]: return result +def _normalize_content(content) -> str | list: + """ + If content is a list containing only text-type items, return plain string. + If it contains images or other types, return the list as-is (multimodal). + """ + if isinstance(content, str): + return content + + if isinstance(content, list): + # Check if all items are plain text type + is_text_only = all( + isinstance(item, dict) and item.get("type") == "text" + for item in content + ) + if is_text_only: + return " ".join(item.get("text", "") for item in content) + # Multimodal — keep as list + return content + + return str(content) + + class KiaAIService: """ Service for calling KIA AI models directly via httpx SSE streaming. @@ -92,7 +111,17 @@ class KiaAIService: print(f"[KiaAI] POST {url}") print(f"[KiaAI] messages_count={len(messages)} | reasoning_effort={reasoning_effort}") - print(f"[KiaAI] payload (no content): { {k: v for k, v in payload.items() if k != 'messages'} }") + + openai_messages = _to_openai_messages(messages) + for i, m in enumerate(openai_messages): + content_preview = m["content"][:120] if isinstance(m["content"], str) else str(m["content"])[:120] + print(f"[KiaAI] msg[{i}] role={m['role']} | content={content_preview!r}") + + payload: dict = {"messages": openai_messages} + + # Add reasoning_effort only for models that support it + if reasoning_effort and model_name in REASONING_MODELS: + payload["reasoning_effort"] = reasoning_effort full_content = "" usage: dict = {} @@ -116,7 +145,6 @@ class KiaAIService: if not line.strip(): continue - # SSE lines: "data: {...}" or "data: [DONE]" or raw JSON raw_data = line.removeprefix("data:").strip() if raw_data == "[DONE]": @@ -129,6 +157,12 @@ class KiaAIService: print(f"[KiaAI] Skipping non-JSON line: {raw_data!r}") continue + # KIA server error inside stream + if chunk.get("code") and chunk["code"] != 200: + error_msg = chunk.get("msg", "Unknown KIA error") + print(f"[KiaAI] Server error in stream: code={chunk['code']} msg={error_msg!r}") + raise RuntimeError(f"KIA API error {chunk['code']}: {error_msg}") + print(f"[KiaAI] CHUNK keys={list(chunk.keys())} choices={chunk.get('choices')}") choices = chunk.get("choices") or []