Add support for mistral

This commit is contained in:
Grigorii Dimitriev 2025-09-30 13:50:38 +03:00
parent e95f1a3810
commit b84872f507
5 changed files with 81 additions and 53 deletions

2
.gitignore vendored
View File

@ -4,3 +4,5 @@ env/
node_modules/ node_modules/
__pycache__ __pycache__
*.db

View File

@ -7,6 +7,10 @@ import requests
from typing import Dict, Any, List from typing import Dict, Any, List
from langchain_core.messages import HumanMessage, SystemMessage, AIMessage from langchain_core.messages import HumanMessage, SystemMessage, AIMessage
from openai import OpenAI
from mistralai import Mistral
# --- Конфигурация LLM --- # --- Конфигурация LLM ---
# Пустой словарь для локальных моделей (Ollama) # Пустой словарь для локальных моделей (Ollama)
LOCAL_MODELS: Dict[str, Any] = {} LOCAL_MODELS: Dict[str, Any] = {}
@ -64,8 +68,19 @@ MODELS: Dict[str, Dict[str, Any]] = {
"mistral-small-latest": { "mistral-small-latest": {
"name": "mistral-small-latest", "name": "mistral-small-latest",
"provider": "mistralai", "provider": "mistralai",
"baseUrl": "model_name": "mistral-small-latest", # Добавлено имя модели для LangChain
"https://render-service-gsu7.onrender.com/m", # "api.mistral.ai", # "https://render-service-gsu7.onrender.com/m", "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", "apiKey": "Q0m29fvxBY0Cfdj4sjHaKqccy1NjonLW",
"stream": True, "stream": True,
"capabilities": ["vision"], "capabilities": ["vision"],
@ -79,13 +94,14 @@ class CustomLLM:
""" """
def __init__(self, config: Dict[str, Any]): def __init__(self, config: Dict[str, Any]):
self.config = config
self.name = config["name"] self.name = config["name"]
self.provider = config["provider"] self.provider = config["provider"]
self.config = config
try:
if self.provider == "openai": if self.provider == "openai":
# Инициализация Gemini через OpenAI API # Инициализация Gemini через OpenAI API
from openai import OpenAI
self.client = OpenAI(api_key=config["apiKey"], self.client = OpenAI(api_key=config["apiKey"],
base_url=config["apiBase"]) base_url=config["apiBase"])
self.model_name = config["model_name"] self.model_name = config["model_name"]
@ -93,13 +109,18 @@ class CustomLLM:
elif self.provider == "mistralai": elif self.provider == "mistralai":
# Для Mistral используем прямые HTTP-запросы # Для Mistral используем прямые HTTP-запросы
self.base_url = config["baseUrl"] self.client = Mistral(api_key=config["apiKey"],
self.api_key = config["apiKey"] server_url=config["apiBase"])
self.stream = config.get("stream", False) self.model_name = config["model_name"]
self.capabilities = config.get("capabilities", []) self.stream = config["stream"]
# self.capabilities = config.get("capabilities", [])
else: else:
raise ValueError(f"Неизвестный провайдер LLM: {self.provider}") raise ValueError(f"Неизвестный провайдер LLM: {self.provider}")
except Exception as e:
print(f"Ошибка при вызове OpenAI API: {e}")
raise
def invoke(self, messages: List[Any]) -> str: def invoke(self, messages: List[Any]) -> str:
""" """
Отправляет запрос к LLM и возвращает ответ. Отправляет запрос к LLM и возвращает ответ.
@ -161,61 +182,61 @@ class CustomLLM:
print(f"Ошибка при вызове OpenAI API: {e}") print(f"Ошибка при вызове OpenAI API: {e}")
raise raise
elif self.provider == "mistralai": elif self.provider == "mistralai":
# Отступы исправлены mistralai_messages = []
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json",
}
# Формируем сообщения для Mistral API
mistral_messages = []
if isinstance(messages, str): if isinstance(messages, str):
mistral_messages.append({"role": "user", "content": messages}) mistralai_messages.append({"role": "user", "content": messages})
elif isinstance(messages, list): elif isinstance(messages, list):
for msg in messages: for msg in messages:
if isinstance(msg, HumanMessage): if isinstance(msg, HumanMessage):
mistral_messages.append({ mistralai_messages.append({
"role": "user", "role": "user",
"content": msg.content "content": msg.content
}) })
elif isinstance(msg, SystemMessage): elif isinstance(msg, SystemMessage):
mistral_messages.append({ mistralai_messages.append({
"role": "system", "role": "system",
"content": msg.content "content": msg.content
}) })
elif isinstance( elif isinstance(
msg, AIMessage): # Добавляем обработку AIMessage msg, AIMessage): # Добавляем обработку AIMessage
mistral_messages.append({ mistralai_messages.append({
"role": "assistant", "role": "assistant",
"content": msg.content "content": msg.content
}) })
else: # Предполагаем, что это другие типы сообщений Langchain или словари else: # Предполагаем, что это другие типы сообщений Langchain или словари
mistral_messages.append({ mistralai_messages.append({
"role": "role":
msg.type if hasattr(msg, 'type') else 'user', msg.type if hasattr(msg, 'type') else 'user',
"content": "content":
msg.content msg.content
}) })
payload = {
"model": self.name,
"messages": mistral_messages,
"stream": self.stream,
}
try: try:
response = requests.post(self.base_url, response = self.client.chat.stream(
json=payload, model=self.model_name,
headers=headers) messages=mistralai_messages
response.raise_for_status( )
) # Вызывает исключение для HTTP ошибок
data = response.json() if self.stream:
# Извлекаем контент ответа collected_messages = []
return data["choices"][0]["message"]["content"] for chunk in response:
except requests.exceptions.RequestException as e: delta = chunk.data.choices[0].delta
print(f"Ошибка при вызове Mistral API: {e}") 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 raise
except KeyError:
print(f"Неверный формат ответа от Mistral API: {data}")
raise ValueError("Неверный формат ответа от Mistral API")
return "" return ""

View File

@ -143,7 +143,7 @@ def execute_command_node(state: AgentState) -> AgentState:
DEFAULT_LLM_NAME = "gemini-2.0-flash" #"gemini-2.5-flash" DEFAULT_LLM_NAME = "gemini-2.0-flash" #"gemini-2.5-flash"
main_llm = get_llm(DEFAULT_LLM_NAME) 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: def call_llm_node(state: AgentState) -> AgentState:

View File

@ -24,10 +24,13 @@ class TitleGenerator:
# Системные промпты для генерации заголовков # Системные промпты для генерации заголовков
self.graph_title_prompt = """Создай краткий заголовок (максимум 80 символов) для диалога на основе первого сообщения пользователя. self.graph_title_prompt = """Создай краткий заголовок (максимум 80 символов) для диалога на основе первого сообщения пользователя.
Заголовок должен отражать основную тему или вопрос. Отвечай только заголовком, без дополнительных объяснений. Ты должен сделать саммери, а не ответить на вопросы, если они есть в сообщении.""" Заголовок должен отражать основную тему или вопрос. Заголовок должен быть простым текстом, без какого-либо форматирования или использования
специальных символов разметки (например, #, *, _, `). Отвечай только заголовком, без дополнительных объяснений. Ты должен сделать саммери, а не ответить на вопросы, если они есть в сообщении."""
self.node_title_prompt = """Создай краткий заголовок (максимум 40 символов) для этого сообщения/действия. self.node_title_prompt = """Создай краткий заголовок (максимум 40 символов) для этого сообщения/действия.
Заголовок должен кратко описывать суть сообщения или действия. Отвечай только заголовком, без дополнительных объяснений. Ты должен сделать саммери, а не ответить на вопросы, если они есть в сообщении.""" Заголовок должен кратко описывать суть сообщения или действия. Заголовок должен быть простым текстом, без какого-либо форматирования или использования
специальных символов разметки (например, #, *, _, `). Отвечай только заголовком, без дополнительных объяснений. Ты должен сделать саммери, а не ответить на вопросы, если они есть в сообщении."""
def _get_connection(self): def _get_connection(self):
""" """

View File

@ -6,3 +6,5 @@ langchain_google_genai
flask flask
flask_cors flask_cors
mistralai