From 719dfa2015cf49a5f81d9b184f845f0184cce2f4 Mon Sep 17 00:00:00 2001 From: mirivlad Date: Thu, 5 Mar 2026 01:52:56 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D1=8B=20=D0=BA=D1=80=D0=B8=D1=82=D0=B8=D1=87?= =?UTF-8?q?=D0=B5=D1=81=D0=BA=D0=B8=D0=B5=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA?= =?UTF-8?q?=D0=B8=20=D0=B2=20=D0=BA=D0=BE=D0=B4=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - GigaChatProvider: добавлено наследование от BaseAIProvider и методы - Компактификация: исправлен парсинг JSON-ответа Qwen - Compactor: добавлена проверка на None - Векторный поиск: исправлена структура результатов ChromaDB - extract_facts_with_ai: добавлена проверка авторизации Qwen - SSH сессия: исправлена логика буфера вывода - Cron job: добавлено обновление next_run после выполнения --- bot.py | 6 ++- bot/compaction.py | 29 +++++++++-- bot/tools/cron_tool.py | 3 ++ qwen_integration.py | 111 ++++++++++++++++++++++++++++++++++++++++- vector_memory.py | 35 +++++++++---- 5 files changed, 166 insertions(+), 18 deletions(-) diff --git a/bot.py b/bot.py index b16e566..9548548 100644 --- a/bot.py +++ b/bot.py @@ -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) diff --git a/bot/compaction.py b/bot/compaction.py index 81c0129..9d01304 100644 --- a/bot/compaction.py +++ b/bot/compaction.py @@ -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: diff --git a/bot/tools/cron_tool.py b/bot/tools/cron_tool.py index 215b949..514bad5 100644 --- a/bot/tools/cron_tool.py +++ b/bot/tools/cron_tool.py @@ -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, diff --git a/qwen_integration.py b/qwen_integration.py index f5d08cb..02acb58 100644 --- a/qwen_integration.py +++ b/qwen_integration.py @@ -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() diff --git a/vector_memory.py b/vector_memory.py index 9e89728..50b88d5 100644 --- a/vector_memory.py +++ b/vector_memory.py @@ -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):