fix: исправлены критические ошибки в коде

- GigaChatProvider: добавлено наследование от BaseAIProvider и методы
- Компактификация: исправлен парсинг JSON-ответа Qwen
- Compactor: добавлена проверка на None
- Векторный поиск: исправлена структура результатов ChromaDB
- extract_facts_with_ai: добавлена проверка авторизации Qwen
- SSH сессия: исправлена логика буфера вывода
- Cron job: добавлено обновление next_run после выполнения
This commit is contained in:
mirivlad 2026-03-05 01:52:56 +08:00
parent 107685771c
commit 719dfa2015
5 changed files with 166 additions and 18 deletions

6
bot.py
View File

@ -376,8 +376,10 @@ async def handle_ai_task(update: Update, text: str):
# Формируем контекст с историей + памятью + summary # Формируем контекст с историей + памятью + summary
# Получаем summary и последние сообщения из ChromaDB # Получаем summary и последние сообщения из ChromaDB
summary = None summary = None
recent_messages = []
try: try:
summary, recent_messages = compactor.get_context_with_summary(limit=20) if compactor is not None:
summary, recent_messages = compactor.get_context_with_summary(limit=20)
# Формируем историю из последних сообщений # Формируем историю из последних сообщений
history_context = "\n".join([f"{msg['role']}: {msg['content']}" for msg in recent_messages]) history_context = "\n".join([f"{msg['role']}: {msg['content']}" for msg in recent_messages])
except Exception as e: except Exception as e:
@ -859,7 +861,7 @@ async def handle_ssh_session_input(update: Update, text: str, session: SSHSessio
while not is_done: while not is_done:
more_output, is_done = await read_ssh_output(session.process, timeout=5.0) more_output, is_done = await read_ssh_output(session.process, timeout=5.0)
output += more_output output += more_output
session.output_buffer += output session.output_buffer += more_output
session.last_activity = datetime.now() session.last_activity = datetime.now()
new_input_type = detect_input_type(output) new_input_type = detect_input_type(output)

View File

@ -341,7 +341,7 @@ class DialogueCompactor:
logger.info("Запуск Qwen Code для сжатия...") logger.info("Запуск Qwen Code для сжатия...")
# Запускаем задачу # Запускаем задачу
await self.qwen_manager.run_task( result = await self.qwen_manager.run_task(
user_id=999, # Системный user_id для компактификации user_id=999, # Системный user_id для компактификации
task=prompt, task=prompt,
on_output=on_output, on_output=on_output,
@ -349,9 +349,30 @@ class DialogueCompactor:
use_system_prompt=False # Не добавляем системный промпт бота use_system_prompt=False # Не добавляем системный промпт бота
) )
summary = "".join(output_parts) # Парсим результат - извлекаем текст из JSON как в основном коде
import re
summary = "".join(output_parts).strip()
# Извлекаем текст из JSON ответа (как в bot.py)
text_matches = re.findall(r'"text":"([^"]+)"', summary)
if text_matches:
summary = " ".join(text_matches).replace("\\n", "\n")
else:
# Fallback: пробуем найти result поле
try:
import json
for line in summary.split('\n'):
line = line.strip()
if line.startswith('{'):
data = json.loads(line)
if data.get('type') == 'result':
summary = data.get('result', summary)
break
if data.get('result'):
summary = data.get('result', summary)
except Exception:
pass
# Очищаем summary от служебных символов
summary = summary.strip() summary = summary.strip()
if not summary: if not summary:

View File

@ -501,6 +501,9 @@ class CronTool(BaseTool):
conn.commit() conn.commit()
conn.close() conn.close()
# Обновляем next_run после успешного выполнения
await self.update_next_run(job_id)
return ToolResult( return ToolResult(
success=True, success=True,
data=result_data, data=result_data,

View File

@ -18,6 +18,14 @@ from datetime import datetime, timedelta
from typing import Optional, Dict, Callable, Any, List, Union from typing import Optional, Dict, Callable, Any, List, Union
from enum import Enum from enum import Enum
from bot.base_ai_provider import (
BaseAIProvider,
ProviderResponse,
AIMessage,
ToolCall,
ToolCallStatus,
)
# Импортируем OAuth модуль и константы # Импортируем OAuth модуль и константы
from bot.utils.qwen_oauth import ( from bot.utils.qwen_oauth import (
get_authorization_url, get_authorization_url,
@ -596,7 +604,7 @@ class QwenCodeManager:
return '\n'.join(cleaned) if cleaned else output return '\n'.join(cleaned) if cleaned else output
class GigaChatProvider: class GigaChatProvider(BaseAIProvider):
""" """
AI-провайдер для работы с GigaChat API. AI-провайдер для работы с GigaChat API.
@ -605,10 +613,23 @@ class GigaChatProvider:
""" """
def __init__(self): def __init__(self):
super().__init__()
self._tool = None self._tool = None
self._initialized = False self._initialized = False
self._config_error: Optional[str] = None self._config_error: Optional[str] = None
@property
def provider_name(self) -> str:
return "GigaChat"
@property
def supports_tools(self) -> bool:
return False
@property
def supports_streaming(self) -> bool:
return False
def _ensure_initialized(self): def _ensure_initialized(self):
"""Ленивая инициализация инструмента""" """Ленивая инициализация инструмента"""
if self._initialized: if self._initialized:
@ -729,6 +750,94 @@ class GigaChatProvider:
self._ensure_initialized() self._ensure_initialized()
return self._config_error return self._config_error
async def chat(
self,
prompt: str,
system_prompt: Optional[str] = None,
context: Optional[List[Dict[str, str]]] = None,
tools: Optional[List[Dict[str, Any]]] = None,
on_chunk: Optional[Callable[[str], Any]] = None,
user_id: Optional[int] = None,
**kwargs
) -> ProviderResponse:
"""Реализация метода chat для интерфейса BaseAIProvider."""
result = await self.chat(
prompt=prompt,
system_prompt=system_prompt,
on_chunk=on_chunk,
)
if result.get("success"):
return ProviderResponse(
success=True,
message=AIMessage(
content=result.get("content", ""),
metadata={"model": result.get("model")}
),
provider_name=self.provider_name,
usage=result.get("usage")
)
else:
return ProviderResponse(
success=False,
error=result.get("error", "Unknown error"),
provider_name=self.provider_name
)
async def execute_tool(
self,
tool_name: str,
tool_args: Dict[str, Any],
tool_call_id: Optional[str] = None,
**kwargs
) -> ToolCall:
"""GigaChat не поддерживает инструменты нативно."""
return ToolCall(
tool_name=tool_name,
tool_args=tool_args,
tool_call_id=tool_call_id,
status=ToolCallStatus.PENDING
)
async def process_with_tools(
self,
prompt: str,
system_prompt: Optional[str] = None,
context: Optional[List[Dict[str, str]]] = None,
tools_registry: Optional[Dict[str, Any]] = None,
on_chunk: Optional[Callable[[str], Any]] = None,
max_iterations: int = 5,
**kwargs
) -> ProviderResponse:
"""Обработка запроса с инструментами для GigaChat.
GigaChat не поддерживает инструменты нативно, поэтому просто
выполняем запрос без инструментов.
"""
# GigaChat не поддерживает инструменты - выполняем обычный запрос
result = await self.chat(
prompt=prompt,
system_prompt=system_prompt,
on_chunk=on_chunk,
)
if result.get("success"):
return ProviderResponse(
success=True,
message=AIMessage(
content=result.get("content", ""),
metadata={"model": result.get("model")}
),
provider_name=self.provider_name,
usage=result.get("usage")
)
else:
return ProviderResponse(
success=False,
error=result.get("error", "Unknown error"),
provider_name=self.provider_name
)
# Глобальный менеджер # Глобальный менеджер
qwen_manager = QwenCodeManager() qwen_manager = QwenCodeManager()

View File

@ -180,18 +180,22 @@ class VectorMemoryStorage:
# Преобразуем результаты # Преобразуем результаты
found_messages = [] found_messages = []
if results and results['ids'] and results['ids'][0]: if results and results.get('ids') and results['ids']:
docs = results.get('documents', [[]])[0]
metas = results.get('metadatas', [[]])[0]
dists = results.get('distances', [[]])[0] if results.get('distances') else []
for i, doc_id in enumerate(results['ids'][0]): for i, doc_id in enumerate(results['ids'][0]):
doc_text = results['documents'][0][i] doc_text = docs[i] if i < len(docs) else ""
metadata = results['metadatas'][0][i] metadata = metas[i] if i < len(metas) else {}
distance = results['distances'][0][i] if results['distances'] else 0.0 distance = dists[i] if i < len(dists) else 0.0
message = Message( message = Message(
id=None, id=None,
user_id=int(metadata['user_id']), user_id=int(metadata.get('user_id', 0)),
role=metadata['role'], role=metadata.get('role', 'user'),
content=doc_text, content=doc_text,
timestamp=datetime.fromisoformat(metadata['timestamp']), timestamp=datetime.fromisoformat(metadata.get('timestamp', datetime.now().isoformat())),
session_id=metadata.get('session_id') session_id=metadata.get('session_id')
) )
@ -226,10 +230,13 @@ class VectorMemoryStorage:
) )
messages = [] messages = []
if results and results.get('ids') and results['ids'][0]: if results and results.get('ids') and results['ids']:
docs = results.get('documents', [[]])[0] if results.get('documents') else []
metas = results.get('metadatas', [[]])[0] if results.get('metadatas') else []
for i, doc_id in enumerate(results['ids'][0]): for i, doc_id in enumerate(results['ids'][0]):
doc_text = results['documents'][0][i] if 'documents' in results else "" doc_text = docs[i] if i < len(docs) else ""
metadata = results['metadatas'][0][i] if 'metadatas' in results else {} metadata = metas[i] if i < len(metas) else {}
message = Message( message = Message(
id=None, id=None,
@ -480,8 +487,14 @@ class HybridMemoryManager:
""" """
try: try:
# Импортируем qwen_manager # Импортируем qwen_manager и проверяем авторизацию
from qwen_integration import qwen_manager from qwen_integration import qwen_manager
from bot.utils.qwen_oauth import is_authorized
# Проверяем авторизацию перед выполнением
if not await is_authorized():
logger.warning(f"Qwen не авторизован, пропускаем извлечение фактов для пользователя {user_id}")
return []
output_buffer = [] output_buffer = []
def on_output(text): def on_output(text):