From b84872f507122ade6ed0d5f3e099414c20431f1f Mon Sep 17 00:00:00 2001 From: Grigorii Dimitriev Date: Tue, 30 Sep 2025 13:50:38 +0300 Subject: [PATCH] Add support for mistral --- .gitignore | 4 +- app/llm_client.py | 117 ++++++++++++++++++++++++----------------- app/nodes.py | 2 +- app/title_generator.py | 7 ++- requirements.txt | 4 +- 5 files changed, 81 insertions(+), 53 deletions(-) diff --git a/.gitignore b/.gitignore index e2275c6..bdf6d1e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,6 @@ env/ node_modules/ -__pycache__ \ No newline at end of file +__pycache__ + +*.db \ No newline at end of file diff --git a/app/llm_client.py b/app/llm_client.py index 3009a3a..268df65 100644 --- a/app/llm_client.py +++ b/app/llm_client.py @@ -7,6 +7,10 @@ import requests from typing import Dict, Any, List from langchain_core.messages import HumanMessage, SystemMessage, AIMessage +from openai import OpenAI + +from mistralai import Mistral + # --- Конфигурация LLM --- # Пустой словарь для локальных моделей (Ollama) LOCAL_MODELS: Dict[str, Any] = {} @@ -64,8 +68,19 @@ MODELS: Dict[str, Dict[str, Any]] = { "mistral-small-latest": { "name": "mistral-small-latest", "provider": "mistralai", - "baseUrl": - "https://render-service-gsu7.onrender.com/m", # "api.mistral.ai", # "https://render-service-gsu7.onrender.com/m", + "model_name": "mistral-small-latest", # Добавлено имя модели для LangChain + "apiBase": + "https://api.mistral.ai", + "apiKey": "Q0m29fvxBY0Cfdj4sjHaKqccy1NjonLW", + "stream": True, + "capabilities": ["vision"], + }, + "mistral-small-latest-r": { + "name": "mistral-small-latest", + "provider": "mistralai", + "model_name": "mistral-small-latest", # Добавлено имя модели для LangChain + "apiBase": + "https://render-service-gsu7.onrender.com/m", "apiKey": "Q0m29fvxBY0Cfdj4sjHaKqccy1NjonLW", "stream": True, "capabilities": ["vision"], @@ -79,26 +94,32 @@ class CustomLLM: """ def __init__(self, config: Dict[str, Any]): + self.config = config self.name = config["name"] self.provider = config["provider"] - self.config = config + + try: - if self.provider == "openai": - # Инициализация Gemini через OpenAI API - from openai import OpenAI - self.client = OpenAI(api_key=config["apiKey"], - base_url=config["apiBase"]) - self.model_name = config["model_name"] - self.stream = config["stream"] + if self.provider == "openai": + # Инициализация Gemini через OpenAI API + self.client = OpenAI(api_key=config["apiKey"], + base_url=config["apiBase"]) + self.model_name = config["model_name"] + self.stream = config["stream"] - elif self.provider == "mistralai": - # Для Mistral используем прямые HTTP-запросы - self.base_url = config["baseUrl"] - self.api_key = config["apiKey"] - self.stream = config.get("stream", False) - self.capabilities = config.get("capabilities", []) - else: - raise ValueError(f"Неизвестный провайдер LLM: {self.provider}") + elif self.provider == "mistralai": + # Для Mistral используем прямые HTTP-запросы + self.client = Mistral(api_key=config["apiKey"], + server_url=config["apiBase"]) + self.model_name = config["model_name"] + self.stream = config["stream"] + # self.capabilities = config.get("capabilities", []) + else: + raise ValueError(f"Неизвестный провайдер LLM: {self.provider}") + + except Exception as e: + print(f"Ошибка при вызове OpenAI API: {e}") + raise def invoke(self, messages: List[Any]) -> str: """ @@ -161,61 +182,61 @@ class CustomLLM: print(f"Ошибка при вызове OpenAI API: {e}") raise elif self.provider == "mistralai": - # Отступы исправлены - headers = { - "Authorization": f"Bearer {self.api_key}", - "Content-Type": "application/json", - } - # Формируем сообщения для Mistral API - mistral_messages = [] + mistralai_messages = [] if isinstance(messages, str): - mistral_messages.append({"role": "user", "content": messages}) + mistralai_messages.append({"role": "user", "content": messages}) elif isinstance(messages, list): for msg in messages: if isinstance(msg, HumanMessage): - mistral_messages.append({ + mistralai_messages.append({ "role": "user", "content": msg.content }) elif isinstance(msg, SystemMessage): - mistral_messages.append({ + mistralai_messages.append({ "role": "system", "content": msg.content }) elif isinstance( msg, AIMessage): # Добавляем обработку AIMessage - mistral_messages.append({ + mistralai_messages.append({ "role": "assistant", "content": msg.content }) else: # Предполагаем, что это другие типы сообщений Langchain или словари - mistral_messages.append({ + mistralai_messages.append({ "role": msg.type if hasattr(msg, 'type') else 'user', "content": msg.content }) - payload = { - "model": self.name, - "messages": mistral_messages, - "stream": self.stream, - } try: - response = requests.post(self.base_url, - json=payload, - headers=headers) - response.raise_for_status( - ) # Вызывает исключение для HTTP ошибок - data = response.json() - # Извлекаем контент ответа - return data["choices"][0]["message"]["content"] - except requests.exceptions.RequestException as e: - print(f"Ошибка при вызове Mistral API: {e}") + response = self.client.chat.stream( + model=self.model_name, + messages=mistralai_messages + ) + + if self.stream: + collected_messages = [] + for chunk in response: + delta = chunk.data.choices[0].delta + chunk_message = getattr(delta, "content", None) # или delta.content, если всегда есть + + if chunk_message is not None: + collected_messages.append(chunk_message) + + if getattr(delta, "finish_reason", None) is not None: + break + + full_reply_content = "".join(collected_messages) + return full_reply_content + else: + return response.choices[0].message.content + + except Exception as e: + print(f"Ошибка при вызове OpenAI API: {e}") raise - except KeyError: - print(f"Неверный формат ответа от Mistral API: {data}") - raise ValueError("Неверный формат ответа от Mistral API") return "" diff --git a/app/nodes.py b/app/nodes.py index b6f6666..3dcdd78 100644 --- a/app/nodes.py +++ b/app/nodes.py @@ -143,7 +143,7 @@ def execute_command_node(state: AgentState) -> AgentState: DEFAULT_LLM_NAME = "gemini-2.0-flash" #"gemini-2.5-flash" main_llm = get_llm(DEFAULT_LLM_NAME) -DEFAULT_SUMMARIZATION_LLM_NAME = "gemini-2.0-flash" +DEFAULT_SUMMARIZATION_LLM_NAME = "gemini-2.0-flash-r" # "mistral-small-latest" #"gemini-2.0-flash-r" def call_llm_node(state: AgentState) -> AgentState: diff --git a/app/title_generator.py b/app/title_generator.py index 4abaa3c..a8d2dd7 100644 --- a/app/title_generator.py +++ b/app/title_generator.py @@ -24,10 +24,13 @@ class TitleGenerator: # Системные промпты для генерации заголовков self.graph_title_prompt = """Создай краткий заголовок (максимум 80 символов) для диалога на основе первого сообщения пользователя. -Заголовок должен отражать основную тему или вопрос. Отвечай только заголовком, без дополнительных объяснений. Ты должен сделать саммери, а не ответить на вопросы, если они есть в сообщении.""" +Заголовок должен отражать основную тему или вопрос. Заголовок должен быть простым текстом, без какого-либо форматирования или использования +специальных символов разметки (например, #, *, _, `). Отвечай только заголовком, без дополнительных объяснений. Ты должен сделать саммери, а не ответить на вопросы, если они есть в сообщении.""" self.node_title_prompt = """Создай краткий заголовок (максимум 40 символов) для этого сообщения/действия. -Заголовок должен кратко описывать суть сообщения или действия. Отвечай только заголовком, без дополнительных объяснений. Ты должен сделать саммери, а не ответить на вопросы, если они есть в сообщении.""" +Заголовок должен кратко описывать суть сообщения или действия. Заголовок должен быть простым текстом, без какого-либо форматирования или использования +специальных символов разметки (например, #, *, _, `). Отвечай только заголовком, без дополнительных объяснений. Ты должен сделать саммери, а не ответить на вопросы, если они есть в сообщении.""" + def _get_connection(self): """ diff --git a/requirements.txt b/requirements.txt index b9434fe..2163e8c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,4 +5,6 @@ langchain_openai langchain_google_genai flask -flask_cors \ No newline at end of file +flask_cors + +mistralai \ No newline at end of file