llm-agent-backend/app/llm_client.py
2025-09-17 02:23:19 +03:00

209 lines
9.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Этот модуль предоставляет инструменты для взаимодействия с различными LLM,
включая OpenAI (Gemini) и MistralAI, обеспечивая унифицированный интерфейс.
"""
import requests
from typing import Dict, Any, List
from langchain_core.messages import HumanMessage, SystemMessage, AIMessage
# --- Конфигурация LLM ---
# Пустой словарь для локальных моделей (Ollama)
LOCAL_MODELS: Dict[str, Any] = {}
# Конфигурация внешних моделей
MODELS: Dict[str, Dict[str, Any]] = {
"gemini-2.0-flash": {
"name": "gemini-2.0-flash",
"provider": "openai", # Изменено на "openai"
"model_name": "gemini-2.0-flash", # Добавлено имя модели для LangChain
"apiBase": "https://render-service-gsu7.onrender.com/g/v1beta",
"apiKey": "AIzaSyDpueKFWVqknVKlQn6TdasLmJ2lvAUiBik",
"stream": True,
"capabilities": ["vision"],
},
"gemini-2.5-flash": {
"name": "gemini-2.5-flash",
"provider": "openai", # Изменено на "openai"
"model_name": "gemini-2.5-flash", # Добавлено имя модели для LangChain
"apiBase": "https://render-service-gsu7.onrender.com/g/v1beta",
"apiKey": "AIzaSyDpueKFWVqknVKlQn6TdasLmJ2lvAUiBik",
"stream": True,
"capabilities": ["vision"],
},
"gemini-2.5-flash-bh": {
"name": "gemini-2.5-flash",
"provider": "openai", # Изменено на "openai"
"model_name": "gemini-2.5-flash", # Добавлено имя модели для LangChain
"apiBase": "https://bothub.chat/api/v2/openai/v1",
"apiKey": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImQ3MTMwYTk0LTJiNmMtNDgzZi04Y2NiLWM5MjgwNzlmNDFlZSIsImlzRGV2ZWxvcGVyIjp0cnVlLCJpYXQiOjE3NTQ0NzYwMDcsImV4cCI6MjA3MDA1MjAwN30._64_DXNZatdd4-4VbXp8dOXdDhIlNWMQt9A_Nn6mu3g",
"stream": True,
"capabilities": ["vision"],
},
"mistral-small-latest": {
"name": "mistral-small-latest",
"provider": "mistralai",
"baseUrl": "https://render-service-gsu7.onrender.com/m",
"apiKey": "Q0m29fvxBY0Cfdj4sjHaKqccy1NjonLW",
"stream": True,
"capabilities": ["vision"],
},
}
class CustomLLM:
"""
Класс-обертка для взаимодействия с различными LLM.
"""
def __init__(self, config: Dict[str, Any]):
self.name = config["name"]
self.provider = config["provider"]
self.config = config
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"]
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}")
def invoke(self, messages: List[Any]) -> str:
"""
Отправляет запрос к LLM и возвращает ответ.
Messages могут быть строкой или списком объектов Langchain Message.
"""
if self.provider == "openai":
# OpenAI ожидает список сообщений в определенном формате
openai_messages = []
if isinstance(messages, str):
openai_messages.append({"role": "user", "content": messages})
elif isinstance(messages, list):
for msg in messages:
if isinstance(msg, HumanMessage):
openai_messages.append({
"role": "user",
"content": msg.content
})
elif isinstance(msg, SystemMessage):
openai_messages.append({
"role": "system",
"content": msg.content
})
elif isinstance(msg, AIMessage): # Добавляем обработку AIMessage
openai_messages.append({
"role": "assistant",
"content": msg.content
})
else: # Предполагаем, что это другие типы сообщений Langchain или словари
openai_messages.append({
"role":
msg.type if hasattr(msg, 'type') else 'user',
"content":
msg.content
})
try:
response = self.client.chat.completions.create(
model=self.model_name,
messages=openai_messages,
stream=self.stream,
)
if self.stream:
# Handle streaming responses
collected_chunks = []
collected_messages = []
for chunk in response:
collected_chunks.append(chunk)
chunk_message = chunk.choices[0].delta.content
if chunk_message is not None:
collected_messages.append(chunk_message)
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
elif self.provider == "mistralai":
# Отступы исправлены
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json",
}
# Формируем сообщения для Mistral API
mistral_messages = []
if isinstance(messages, str):
mistral_messages.append({"role": "user", "content": messages})
elif isinstance(messages, list):
for msg in messages:
if isinstance(msg, HumanMessage):
mistral_messages.append({
"role": "user",
"content": msg.content
})
elif isinstance(msg, SystemMessage):
mistral_messages.append({
"role": "system",
"content": msg.content
})
elif isinstance(msg, AIMessage): # Добавляем обработку AIMessage
mistral_messages.append({
"role": "assistant",
"content": msg.content
})
else: # Предполагаем, что это другие типы сообщений Langchain или словари
mistral_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}")
raise
except KeyError:
print(f"Неверный формат ответа от Mistral API: {data}")
raise ValueError("Неверный формат ответа от Mistral API")
return ""
def get_llm(name: str) -> CustomLLM:
"""
Возвращает экземпляр CustomLLM для заданной модели.
"""
config = MODELS.get(name)
if not config:
raise ValueError(f"Модель '{name}' не сконфигурирована.")
return CustomLLM(config)
def get_available_models():
"""Возвращает список доступных моделей."""
return list(MODELS.keys())