AnatoliiG commited on
Commit
6eebe14
·
1 Parent(s): 3b802b2

fix api bugs

Browse files
Files changed (4) hide show
  1. src/api/routes.py +49 -30
  2. src/core/config.py +1 -1
  3. src/core/engine.py +33 -23
  4. src/ui/callbacks.py +10 -4
src/api/routes.py CHANGED
@@ -4,7 +4,7 @@ import logging
4
  import threading
5
 
6
  from fastapi import APIRouter, HTTPException, Request
7
- from fastapi.responses import StreamingResponse
8
 
9
  from src.core.config import settings
10
  from src.core.engine import engine
@@ -26,14 +26,21 @@ async def chat_completions(request: Request):
26
  except Exception:
27
  raise HTTPException(status_code=400, detail="Invalid JSON")
28
 
29
- messages = [
30
- {"role": m.get("role", "user"), "content": get_clean_text(m.get("content"))}
31
- for m in data.get("messages", [])
32
- ]
33
-
34
- max_tokens = data.get("max_tokens", settings.DEFAULT_MAX_TOKENS)
35
- temperature = data.get("temperature", settings.DEFAULT_TEMP)
36
- top_p = data.get("top_p", 0.95)
 
 
 
 
 
 
 
37
  stop = data.get("stop", [])
38
  if isinstance(stop, str):
39
  stop = [stop]
@@ -43,6 +50,30 @@ async def chat_completions(request: Request):
43
  if s not in stop:
44
  stop.append(s)
45
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  abort_event = threading.Event()
47
 
48
  async def stream_generator():
@@ -51,18 +82,9 @@ async def chat_completions(request: Request):
51
 
52
  def worker():
53
  try:
54
- gen_kwargs = {
55
- "max_tokens": int(max_tokens),
56
- "temperature": float(temperature),
57
- "top_p": float(top_p),
58
- "stop": stop,
59
- "abort_event": abort_event,
60
- }
61
-
62
- # Запускаем генерацию
63
- for chunk in engine.generate_stream(messages, **gen_kwargs):
64
  loop.call_soon_threadsafe(queue.put_nowait, chunk)
65
-
66
  loop.call_soon_threadsafe(queue.put_nowait, None)
67
  except Exception as e:
68
  if not abort_event.is_set():
@@ -74,7 +96,6 @@ async def chat_completions(request: Request):
74
  try:
75
  while True:
76
  if await request.is_disconnected():
77
- logger.info("Client disconnected! Aborting generation...")
78
  abort_event.set()
79
  break
80
 
@@ -99,17 +120,15 @@ async def chat_completions(request: Request):
99
  yield f"data: {json.dumps(chunk)}\n\n"
100
 
101
  except asyncio.CancelledError:
102
- logger.info("Task cancelled. Stopping worker.")
103
  abort_event.set()
104
  raise
105
 
106
- # Возвращаем стрим
107
- headers = {
108
- "X-Accel-Buffering": "no",
109
- "Cache-Control": "no-cache",
110
- "Connection": "keep-alive",
111
- "Content-Type": "text/event-stream",
112
- }
113
  return StreamingResponse(
114
- stream_generator(), media_type="text/event-stream", headers=headers
 
 
 
 
 
 
115
  )
 
4
  import threading
5
 
6
  from fastapi import APIRouter, HTTPException, Request
7
+ from fastapi.responses import JSONResponse, StreamingResponse
8
 
9
  from src.core.config import settings
10
  from src.core.engine import engine
 
26
  except Exception:
27
  raise HTTPException(status_code=400, detail="Invalid JSON")
28
 
29
+ # Бережно собираем сообщения, сохраняя служебные поля для Tool Calling
30
+ messages = []
31
+ for m in data.get("messages", []):
32
+ msg = {
33
+ "role": m.get("role", "user"),
34
+ "content": get_clean_text(m.get("content")),
35
+ }
36
+ if "tool_calls" in m:
37
+ msg["tool_calls"] = m["tool_calls"]
38
+ if "tool_call_id" in m:
39
+ msg["tool_call_id"] = m["tool_call_id"]
40
+ messages.append(msg)
41
+
42
+ # Параметры от агента
43
+ is_stream = data.get("stream", False)
44
  stop = data.get("stop", [])
45
  if isinstance(stop, str):
46
  stop = [stop]
 
50
  if s not in stop:
51
  stop.append(s)
52
 
53
+ gen_kwargs = {
54
+ "max_tokens": data.get("max_tokens", settings.DEFAULT_MAX_TOKENS),
55
+ "temperature": data.get("temperature", settings.DEFAULT_TEMP),
56
+ "top_p": data.get("top_p", 0.95),
57
+ "stop": stop,
58
+ "stream": is_stream,
59
+ "tools": data.get("tools", None),
60
+ "tool_choice": data.get("tool_choice", None),
61
+ }
62
+
63
+ # Если Агент просит ответ целиком (stream=False)
64
+ if not is_stream:
65
+ loop = asyncio.get_running_loop()
66
+ try:
67
+ # Выполняем синхронный код в пуле потоков, чтобы не заблокировать FastAPI
68
+ response = await loop.run_in_executor(
69
+ None, lambda: engine.generate(messages, **gen_kwargs)
70
+ )
71
+ return JSONResponse(content=response)
72
+ except Exception as e:
73
+ logger.error(f"Sync generation error: {e}")
74
+ raise HTTPException(status_code=500, detail=str(e))
75
+
76
+ # Если это Чат (stream=True)
77
  abort_event = threading.Event()
78
 
79
  async def stream_generator():
 
82
 
83
  def worker():
84
  try:
85
+ gen_kwargs["abort_event"] = abort_event
86
+ for chunk in engine.generate(messages, **gen_kwargs):
 
 
 
 
 
 
 
 
87
  loop.call_soon_threadsafe(queue.put_nowait, chunk)
 
88
  loop.call_soon_threadsafe(queue.put_nowait, None)
89
  except Exception as e:
90
  if not abort_event.is_set():
 
96
  try:
97
  while True:
98
  if await request.is_disconnected():
 
99
  abort_event.set()
100
  break
101
 
 
120
  yield f"data: {json.dumps(chunk)}\n\n"
121
 
122
  except asyncio.CancelledError:
 
123
  abort_event.set()
124
  raise
125
 
 
 
 
 
 
 
 
126
  return StreamingResponse(
127
+ stream_generator(),
128
+ media_type="text/event-stream",
129
+ headers={
130
+ "X-Accel-Buffering": "no",
131
+ "Cache-Control": "no-cache",
132
+ "Connection": "keep-alive",
133
+ },
134
  )
src/core/config.py CHANGED
@@ -6,7 +6,7 @@ from pydantic_settings import BaseSettings
6
  class Settings(BaseSettings):
7
  # Меняем на нужную нам супер-модель
8
  REPO_ID: str = "empero-ai/Qwythos-9B-Claude-Mythos-5-1M-GGUF"
9
- FILENAME: str = "Qwythos-9B-Claude-Mythos-5-1M-Q4_K_M.gguf"
10
 
11
  CONTEXT_SIZE: int = 32768
12
  DEFAULT_MAX_TOKENS: int = 8192
 
6
  class Settings(BaseSettings):
7
  # Меняем на нужную нам супер-модель
8
  REPO_ID: str = "empero-ai/Qwythos-9B-Claude-Mythos-5-1M-GGUF"
9
+ FILENAME: str = "Qwythos-9B-Claude-Mythos-5-1M-MTP-Q5_K_M.gguf"
10
 
11
  CONTEXT_SIZE: int = 32768
12
  DEFAULT_MAX_TOKENS: int = 8192
src/core/engine.py CHANGED
@@ -32,46 +32,56 @@ class ModelEngine:
32
  except Exception as e:
33
  print(f"CRITICAL ERROR loading model: {e}")
34
 
35
- def generate_stream(
36
  self,
37
- messages: List[Dict[str, str]],
38
- abort_event: Optional[threading.Event] = None, # Новый аргумент
39
  **kwargs,
40
- ) -> Generator:
41
  if not self.llm:
42
  raise RuntimeError("Model not loaded")
43
 
44
- max_tokens = kwargs.get("max_tokens", settings.DEFAULT_MAX_TOKENS)
45
- temperature = kwargs.get("temperature", settings.DEFAULT_TEMP)
46
- stop = kwargs.get("stop", [])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
 
48
  acquired = False
49
  while not acquired:
50
  if abort_event and abort_event.is_set():
51
  print("Request aborted while waiting in queue.")
52
  return
53
-
54
  acquired = self.lock.acquire(timeout=0.5)
55
 
56
  try:
57
  if abort_event and abort_event.is_set():
58
  return
59
 
60
- stream = self.llm.create_chat_completion(
61
- messages=messages,
62
- max_tokens=int(max_tokens),
63
- temperature=float(temperature),
64
- stop=stop,
65
- stream=True,
66
- top_p=kwargs.get("top_p", 0.95),
67
- )
68
-
69
- for chunk in stream:
70
- if abort_event and abort_event.is_set():
71
- print("Request aborted during generation.")
72
- break
73
-
74
- yield chunk
75
 
76
  finally:
77
  self.lock.release()
 
32
  except Exception as e:
33
  print(f"CRITICAL ERROR loading model: {e}")
34
 
35
+ def generate(
36
  self,
37
+ messages: List[Dict[str, Any]],
38
+ abort_event: Optional[threading.Event] = None,
39
  **kwargs,
40
+ ):
41
  if not self.llm:
42
  raise RuntimeError("Model not loaded")
43
 
44
+ stream_mode = kwargs.get("stream", True)
45
+
46
+ # Подготавливаем аргументы для llama_cpp_python
47
+ llama_kwargs = {
48
+ "messages": messages,
49
+ "max_tokens": int(kwargs.get("max_tokens", settings.DEFAULT_MAX_TOKENS)),
50
+ "temperature": float(kwargs.get("temperature", settings.DEFAULT_TEMP)),
51
+ "top_p": float(kwargs.get("top_p", 0.95)),
52
+ "stop": kwargs.get("stop", []),
53
+ "stream": stream_mode,
54
+ }
55
+
56
+ # Прокидываем инструменты (Tool Calling)
57
+ if kwargs.get("tools"):
58
+ llama_kwargs["tools"] = kwargs["tools"]
59
+ if kwargs.get("tool_choice"):
60
+ llama_kwargs["tool_choice"] = kwargs["tool_choice"]
61
 
62
  acquired = False
63
  while not acquired:
64
  if abort_event and abort_event.is_set():
65
  print("Request aborted while waiting in queue.")
66
  return
 
67
  acquired = self.lock.acquire(timeout=0.5)
68
 
69
  try:
70
  if abort_event and abort_event.is_set():
71
  return
72
 
73
+ response = self.llm.create_chat_completion(**llama_kwargs)
74
+
75
+ # Если просят стрим - отдаем генератор
76
+ if stream_mode:
77
+ for chunk in response:
78
+ if abort_event and abort_event.is_set():
79
+ print("Request aborted during generation.")
80
+ break
81
+ yield chunk
82
+ # Если просят целый ответ (Агенты) - отдаем сразу словарь
83
+ else:
84
+ return response
 
 
 
85
 
86
  finally:
87
  self.lock.release()
src/ui/callbacks.py CHANGED
@@ -30,6 +30,7 @@ def set_interactive(is_interactive):
30
  )
31
 
32
 
 
33
  def bot_response(history, system_prompt, temperature, max_tokens):
34
  messages = [{"role": "system", "content": system_prompt}]
35
  for msg in history[-7:]:
@@ -40,14 +41,19 @@ def bot_response(history, system_prompt, temperature, max_tokens):
40
  history.append({"role": "assistant", "content": ""})
41
 
42
  try:
43
- stream = engine.generate_stream(
44
- messages=messages, max_tokens=max_tokens, temperature=temperature
 
 
 
 
45
  )
46
 
47
  partial_text = ""
48
  for chunk in stream:
49
- delta = chunk["choices"][0]["delta"]
50
- if "content" in delta:
 
51
  partial_text += delta["content"]
52
  history[-1]["content"] = partial_text
53
  yield history
 
30
  )
31
 
32
 
33
+ # Замени bot_response на этот:
34
  def bot_response(history, system_prompt, temperature, max_tokens):
35
  messages = [{"role": "system", "content": system_prompt}]
36
  for msg in history[-7:]:
 
41
  history.append({"role": "assistant", "content": ""})
42
 
43
  try:
44
+ # Указываем stream=True для UI
45
+ stream = engine.generate(
46
+ messages=messages,
47
+ max_tokens=max_tokens,
48
+ temperature=temperature,
49
+ stream=True,
50
  )
51
 
52
  partial_text = ""
53
  for chunk in stream:
54
+ delta = chunk["choices"][0].get("delta", {})
55
+ # Безопасная проверка контента
56
+ if delta.get("content"):
57
  partial_text += delta["content"]
58
  history[-1]["content"] = partial_text
59
  yield history