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 и последние сообщения из ChromaDB
summary = None
recent_messages = []
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])
except Exception as e:
@ -859,7 +861,7 @@ async def handle_ssh_session_input(update: Update, text: str, session: SSHSessio
while not is_done:
more_output, is_done = await read_ssh_output(session.process, timeout=5.0)
output += more_output
session.output_buffer += output
session.output_buffer += more_output
session.last_activity = datetime.now()
new_input_type = detect_input_type(output)

View File

@ -341,7 +341,7 @@ class DialogueCompactor:
logger.info("Запуск Qwen Code для сжатия...")
# Запускаем задачу
await self.qwen_manager.run_task(
result = await self.qwen_manager.run_task(
user_id=999, # Системный user_id для компактификации
task=prompt,
on_output=on_output,
@ -349,9 +349,30 @@ class DialogueCompactor:
use_system_prompt=False # Не добавляем системный промпт бота
)
summary = "".join(output_parts)
# Очищаем summary от служебных символов
# Парсим результат - извлекаем текст из 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.strip()
if not summary:

View File

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

View File

@ -18,6 +18,14 @@ from datetime import datetime, timedelta
from typing import Optional, Dict, Callable, Any, List, Union
from enum import Enum
from bot.base_ai_provider import (
BaseAIProvider,
ProviderResponse,
AIMessage,
ToolCall,
ToolCallStatus,
)
# Импортируем OAuth модуль и константы
from bot.utils.qwen_oauth import (
get_authorization_url,
@ -596,7 +604,7 @@ class QwenCodeManager:
return '\n'.join(cleaned) if cleaned else output
class GigaChatProvider:
class GigaChatProvider(BaseAIProvider):
"""
AI-провайдер для работы с GigaChat API.
@ -605,9 +613,22 @@ class GigaChatProvider:
"""
def __init__(self):
super().__init__()
self._tool = None
self._initialized = False
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):
"""Ленивая инициализация инструмента"""
@ -729,6 +750,94 @@ class GigaChatProvider:
self._ensure_initialized()
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()

View File

@ -180,18 +180,22 @@ class VectorMemoryStorage:
# Преобразуем результаты
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]):
doc_text = results['documents'][0][i]
metadata = results['metadatas'][0][i]
distance = results['distances'][0][i] if results['distances'] else 0.0
doc_text = docs[i] if i < len(docs) else ""
metadata = metas[i] if i < len(metas) else {}
distance = dists[i] if i < len(dists) else 0.0
message = Message(
id=None,
user_id=int(metadata['user_id']),
role=metadata['role'],
user_id=int(metadata.get('user_id', 0)),
role=metadata.get('role', 'user'),
content=doc_text,
timestamp=datetime.fromisoformat(metadata['timestamp']),
timestamp=datetime.fromisoformat(metadata.get('timestamp', datetime.now().isoformat())),
session_id=metadata.get('session_id')
)
@ -226,10 +230,13 @@ class VectorMemoryStorage:
)
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]):
doc_text = results['documents'][0][i] if 'documents' in results else ""
metadata = results['metadatas'][0][i] if 'metadatas' in results else {}
doc_text = docs[i] if i < len(docs) else ""
metadata = metas[i] if i < len(metas) else {}
message = Message(
id=None,
@ -480,8 +487,14 @@ class HybridMemoryManager:
"""
try:
# Импортируем qwen_manager
# Импортируем 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 = []
def on_output(text):