Revert "add kia ai chat tools18"

This reverts commit 3b14f898e3.
This commit is contained in:
vahidrezvani 2026-02-28 09:32:34 +03:30
parent 3b14f898e3
commit a844bc1b4e
1 changed files with 60 additions and 210 deletions

View File

@ -115,198 +115,20 @@ class KiaAIService:
print(f"[KiaAI] payload keys: {[k for k in payload if k != 'messages']}")
return url, payload
# async def _iter_sse(
# self, url: str, payload: dict
# ) -> AsyncGenerator[AIMessageChunk, None]:
# """
# Core SSE parser — yields one AIMessageChunk per content delta token.
# Final chunk: AIMessageChunk(content="", usage_metadata={...}).
# Raises RuntimeError on KIA server errors (triggers retry in stream()).
# Uses aiter_bytes(chunk_size=32) instead of aiter_lines() so every
# byte received from the network is processed immediately without
# waiting for httpx's internal 64 KB read buffer to fill up.
# """
# usage: dict = {}
# async with httpx.AsyncClient(timeout=180.0) as client:
# async with client.stream(
# "POST",
# url,
# content=json.dumps(payload),
# headers={
# "Content-Type": "application/json",
# "Authorization": f"Bearer {settings.kia_api_key}",
# },
# ) as resp:
# print(f"[KiaAI] HTTP {resp.status_code} | {url}")
# resp.raise_for_status()
# line_count = 0
# buf = ""
# done = False
# async for raw in resp.aiter_bytes(chunk_size=32):
# buf += raw.decode("utf-8", errors="replace")
# while "\n" in buf:
# line, buf = buf.split("\n", 1)
# line = line.rstrip("\r")
# line_count += 1
# if line_count <= 3:
# print(f"[KiaAI] raw[{line_count}]: {line!r}")
# if not line:
# continue
# # Non-data lines may carry KIA error JSON
# if not line.startswith("data:"):
# try:
# err = json.loads(line)
# code = err.get("code")
# if code and int(code) >= 400:
# msg = err.get("msg", "Unknown error")
# print(f"[KiaAI] Error line: code={code} msg={msg!r}")
# raise RuntimeError(f"KIA error {code}: {msg}")
# except (json.JSONDecodeError, ValueError):
# pass
# print(f"[KiaAI] non-data line: {line!r}")
# continue
# raw_data = line[6:] if line.startswith("data: ") else line[5:]
# if raw_data.strip() == "[DONE]":
# print("[KiaAI] [DONE]")
# done = True
# break
# try:
# data = json.loads(raw_data)
# except json.JSONDecodeError:
# print(f"[KiaAI] JSON decode error: {raw_data!r}")
# continue
# # KIA server error embedded in stream body
# if data.get("code") and int(data["code"]) >= 500:
# msg = data.get("msg", "Unknown error")
# print(f"[KiaAI] Error chunk: {data['code']} {msg!r}")
# raise RuntimeError(f"KIA error {data['code']}: {msg}")
# # Yield content tokens
# for choice in (data.get("choices") or []):
# delta = choice.get("delta") or {}
# token = delta.get("content") or ""
# if token:
# yield AIMessageChunk(content=token)
# # Capture usage from final summary chunk
# if data.get("usage"):
# usage = data["usage"]
# if done:
# break
# # Final chunk carries usage metadata
# print(f"[KiaAI] Stream done | total_lines={line_count} | usage={usage}")
# yield AIMessageChunk(
# content="",
# usage_metadata={
# "input_tokens": usage.get("prompt_tokens", 0),
# "output_tokens": usage.get("completion_tokens", 0),
# "total_tokens": usage.get("total_tokens", 0),
# },
# )
# # ------------------------------------------------------------------
# # Public API
# # ------------------------------------------------------------------
# async def stream(
# self,
# model: str,
# messages: list[BaseMessage],
# reasoning_effort: str | None = None,
# include_thoughts: bool = False,
# google_search: bool = False,
# tools: list[dict] | None = None,
# response_format: dict | None = None,
# max_retries: int = 3,
# retry_delay: float = 2.0,
# ) -> AsyncGenerator[AIMessageChunk, None]:
# """
# Token-by-token async generator.
# Yields AIMessageChunk(content=token) per SSE delta.
# Last chunk: AIMessageChunk(content="", usage_metadata={...}).
# Retries on server errors only if no tokens were yielded yet.
# """
# url, payload = self._build_payload(
# model, messages, reasoning_effort, include_thoughts,
# google_search, tools, response_format,
# )
# last_error: Exception | None = None
# for attempt in range(1, max_retries + 1):
# if attempt > 1:
# print(f"[KiaAI] Stream retry {attempt}/{max_retries} in {retry_delay}s ...")
# await asyncio.sleep(retry_delay)
# yielded_any = False
# try:
# async for chunk in self._iter_sse(url, payload):
# yielded_any = True
# yield chunk
# return # success
# except RuntimeError as e:
# if yielded_any:
# raise # can't retry after content was already sent
# last_error = e
# print(f"[KiaAI] Attempt {attempt} failed (no content yet): {e}")
# raise last_error
# async def invoke(
# self,
# model: str,
# messages: list[BaseMessage],
# reasoning_effort: str | None = None,
# include_thoughts: bool = False,
# google_search: bool = False,
# tools: list[dict] | None = None,
# response_format: dict | None = None,
# max_retries: int = 3,
# retry_delay: float = 2.0,
# ) -> AIMessage:
# """Collects all streaming chunks into a single AIMessage."""
# full_content = ""
# usage: dict = {}
# async for chunk in self.stream(
# model=model,
# messages=messages,
# reasoning_effort=reasoning_effort,
# include_thoughts=include_thoughts,
# google_search=google_search,
# tools=tools,
# response_format=response_format,
# max_retries=max_retries,
# retry_delay=retry_delay,
# ):
# full_content += chunk.content or ""
# if chunk.usage_metadata:
# usage = chunk.usage_metadata
# print(f"[KiaAI] invoke() done | content_len={len(full_content)} | usage={usage}")
# ai_msg = AIMessage(content=full_content)
# ai_msg.usage_metadata = usage
# return ai_msg
async def _iter_sse(
self, url: str, payload: dict
) -> AsyncGenerator[AIMessageChunk, None]:
"""
Real-time SSE parser yields tokens immediately as they arrive (no line buffering),
but still collects usage metadata from final chunk.
Core SSE parser yields one AIMessageChunk per content delta token.
Final chunk: AIMessageChunk(content="", usage_metadata={...}).
Raises RuntimeError on KIA server errors (triggers retry in stream()).
Uses aiter_bytes(chunk_size=32) instead of aiter_lines() so every
byte received from the network is processed immediately without
waiting for httpx's internal 64 KB read buffer to fill up.
"""
usage: dict = {}
async with httpx.AsyncClient(timeout=180.0) as client:
async with client.stream(
"POST",
@ -317,49 +139,75 @@ class KiaAIService:
"Authorization": f"Bearer {settings.kia_api_key}",
},
) as resp:
print(f"[KiaAI] HTTP {resp.status_code} | {url}")
resp.raise_for_status()
buffer = ""
line_count = 0
buf = ""
done = False
async for raw in resp.aiter_bytes(chunk_size=32):
text = raw.decode("utf-8", errors="replace")
buffer += text
# پردازش خطوط کامل یا partial JSON
while "\n" in buffer:
line, buffer = buffer.split("\n", 1)
async for raw in resp.aiter_bytes(chunk_size=32):
buf += raw.decode("utf-8", errors="replace")
while "\n" in buf:
line, buf = buf.split("\n", 1)
line = line.rstrip("\r")
if not line.startswith("data:"):
line_count += 1
if line_count <= 3:
print(f"[KiaAI] raw[{line_count}]: {line!r}")
if not line:
continue
# Non-data lines may carry KIA error JSON
if not line.startswith("data:"):
try:
err = json.loads(line)
code = err.get("code")
if code and int(code) >= 400:
msg = err.get("msg", "Unknown error")
print(f"[KiaAI] Error line: code={code} msg={msg!r}")
raise RuntimeError(f"KIA error {code}: {msg}")
except (json.JSONDecodeError, ValueError):
pass
print(f"[KiaAI] non-data line: {line!r}")
continue
raw_data = line[6:] if line.startswith("data: ") else line[5:]
if raw_data.strip() == "[DONE]":
print("[KiaAI] [DONE]")
done = True
break
try:
data = json.loads(raw_data)
except json.JSONDecodeError:
print(f"[KiaAI] JSON decode error: {raw_data!r}")
continue
# بررسی خطای سرور
# KIA server error embedded in stream body
if data.get("code") and int(data["code"]) >= 500:
msg = data.get("msg", "Unknown error")
print(f"[KiaAI] Error chunk: {data['code']} {msg!r}")
raise RuntimeError(f"KIA error {data['code']}: {msg}")
# yield توکن‌ها فوراً
for choice in data.get("choices", []):
# Yield content tokens
for choice in (data.get("choices") or []):
delta = choice.get("delta") or {}
token = delta.get("content") or ""
if token:
yield AIMessageChunk(content=token)
# ذخیره usage از chunk نهایی
# Capture usage from final summary chunk
if data.get("usage"):
usage = data["usage"]
if done:
break
# Yield final chunk with usage metadata
# Final chunk carries usage metadata
print(f"[KiaAI] Stream done | total_lines={line_count} | usage={usage}")
yield AIMessageChunk(
content="",
usage_metadata={
@ -370,7 +218,7 @@ class KiaAIService:
)
# ------------------------------------------------------------------
# Public streaming API
# Public API
# ------------------------------------------------------------------
async def stream(
@ -386,7 +234,10 @@ class KiaAIService:
retry_delay: float = 2.0,
) -> AsyncGenerator[AIMessageChunk, None]:
"""
Real-time token streaming to client, retries on server errors if no tokens yielded yet.
Token-by-token async generator.
Yields AIMessageChunk(content=token) per SSE delta.
Last chunk: AIMessageChunk(content="", usage_metadata={...}).
Retries on server errors only if no tokens were yielded yet.
"""
url, payload = self._build_payload(
model, messages, reasoning_effort, include_thoughts,
@ -396,22 +247,22 @@ class KiaAIService:
last_error: Exception | None = None
for attempt in range(1, max_retries + 1):
if attempt > 1:
print(f"[KiaAI] Stream retry {attempt}/{max_retries} in {retry_delay}s ...")
await asyncio.sleep(retry_delay)
yielded_any = False
try:
async for chunk in self._iter_sse(url, payload):
yielded_any = True
yield chunk
return
return # success
except RuntimeError as e:
if yielded_any:
raise
raise # can't retry after content was already sent
last_error = e
raise last_error
print(f"[KiaAI] Attempt {attempt} failed (no content yet): {e}")
# ------------------------------------------------------------------
# Full message collection
# ------------------------------------------------------------------
raise last_error
async def invoke(
self,
@ -425,9 +276,7 @@ class KiaAIService:
max_retries: int = 3,
retry_delay: float = 2.0,
) -> AIMessage:
"""
Collects all streamed chunks into a complete AIMessage.
"""
"""Collects all streaming chunks into a single AIMessage."""
full_content = ""
usage: dict = {}
async for chunk in self.stream(
@ -445,6 +294,7 @@ class KiaAIService:
if chunk.usage_metadata:
usage = chunk.usage_metadata
print(f"[KiaAI] invoke() done | content_len={len(full_content)} | usage={usage}")
ai_msg = AIMessage(content=full_content)
ai_msg.usage_metadata = usage
return ai_msg