Подключение LLM: OpenAI, Anthropic, локальные модели через OpenRouter и Ollama
Embedding нашёл контекст. Теперь LLM превращает его в ответ.
В RAG-пайплайне LLM — это генератор ответов. После того как retrieval нашёл релевантные чанки, LLM получает промпт с контекстом и вопросом, и создаёт текстовый ответ.
Качество LLM критично: даже с отличным контекстом слабая модель может дать плохой ответ. И наоборот — сильная модель с плохим промптом тоже ошибётся.
В этой теме мы разберём, как подключать разные LLM-провайдеры к RAG-системе.
OpenAI — самый популярный провайдер LLM. Модели GPT-4o, GPT-4o-mini, o1 и другие.
from openai import OpenAI
# Клиент читает ключ из переменной окружения OPENAI_API_KEY
client = OpenAI()
# Или явно указываем ключ
client = OpenAI(api_key="sk-your-key-here")Ключ API лучше хранить в переменных окружения, а не в коде:
export OPENAI_API_KEY="sk-your-key-here"import os
from openai import OpenAI
client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": "Ты помощник, который отвечает на вопросы по документам компании."},
{"role": "user", "content": "Как оформить отпуск?"}
],
temperature=0.3,
max_tokens=500
)
answer = response.choices[0].message.content
print(answer)Параметры:
model — название модели. gpt-4o-mini дешевле и быстрее, gpt-4o качественнееmessages — список сообщений. role: system (инструкция), user (вопрос), assistant (предыдущий ответ)temperature — креативность: 0 = детерминированный ответ, 1 = разнообразный. Для RAG обычно 0.1–0.3max_tokens — максимальная длина ответа. 500 токенов ≈ 350–400 словdef rag_query(question, context_chunks, client):
"""RAG-запрос с контекстом."""
context = "\n\n".join(context_chunks)
prompt = f"""Ответь на вопрос, используя следующий контекст из документов:
{context}
Вопрос: {question}
Если в контексте нет информации для ответа, скажи «Информация не найдена в документах»."""
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": "Ты помощник по документам компании. Отвечай только на основе предоставленного контекста."},
{"role": "user", "content": prompt}
],
temperature=0.1,
max_tokens=500
)
return response.choices[0].message.content
# Использование
context = [
"Отпуск оформляется через HR-портал. Заявление подаётся за 2 недели.",
"Для международных отпусков процедура занимает до 4 недель."
]
answer = rag_query("Как оформить международный отпуск?", context, client)
print(answer)Anthropic — альтернатива OpenAI с моделями Claude 3.5 Sonnet, Claude 3 Opus, Claude 3 Haiku.
pip install anthropicimport anthropic
client = anthropic.Anthropic(api_key="sk-ant-your-key")
message = client.messages.create(
model="claude-3-haiku-20240307",
max_tokens=500,
system="Ты помощник по документам компании. Отвечай только на основе контекста.",
messages=[
{"role": "user", "content": "Как оформить отпуск?"}
]
)
answer = message.content[0].text
print(answer)Ключевое отличие от OpenAI: системный промпт передаётся отдельным параметром system, а не в списке сообщений.
def rag_claude(question, context_chunks, client):
context = "\n\n".join(context_chunks)
prompt = f"""Контекст из документов:
{context}
Вопрос: {question}
Ответь на вопрос, используя контекст. Если информации нет в контексте, скажи «Информация не найдена»."""
message = client.messages.create(
model="claude-3-haiku-20240307",
max_tokens=500,
system="Ты — эксперт по внутренним документам компании.",
messages=[{"role": "user", "content": prompt}]
)
return message.content[0].textClaude часто лучше понимает длинные контексты и реже «галлюцинирует» — для RAG это важно.
Ollama — инструмент для запуска LLM локально. Не нужен API-ключ, данные не уходят с компьютера.
# macOS
brew install ollama
# Запускаем сервер
ollama serve &
# Скачиваем модель
ollama pull llama3.2pip install ollamaimport ollama
response = ollama.chat(
model="llama3.2",
messages=[
{"role": "user", "content": "Как оформить отпуск?"}
]
)
answer = response["message"]["content"]
print(answer)def rag_ollama(question, context_chunks):
context = "\n\n".join(context_chunks)
prompt = f"""Контекст:
{context}
Вопрос: {question}
Ответь на основе контекста. Если информации нет, скажи «Информация не найдена»."""
response = ollama.chat(
model="llama3.2",
messages=[{"role": "user", "content": prompt}],
options={"temperature": 0.1}
)
return response["message"]["content"]Плюсы Ollama: бесплатно, приватно, оффлайн. Минусы: локальные модели уступают GPT-4/Claude по качеству, особенно для русского языка.
OpenRouter — агрегатор LLM-провайдеров. Один API-ключ даёт доступ к десяткам моделей от разных компаний.
pip install openai # OpenRouter совместим с OpenAI SDKfrom openai import OpenAI
client = OpenAI(
base_url="https://openrouter.ai/api/v1",
api_key="sk-or-your-key"
)
response = client.chat.completions.create(
model="anthropic/claude-3.5-sonnet", # или openai/gpt-4o, или meta-llama/llama-3.1-70b
messages=[
{"role": "user", "content": "Как оформить отпуск?"}
]
)
print(response.choices[0].message.content)Формат model — провайдер/модель. Доступные модели можно посмотреть на openrouter.ai/models.
def rag_openrouter(question, context_chunks, model="anthropic/claude-3-haiku"):
context = "\n\n".join(context_chunks)
prompt = f"""Контекст:
{context}
Вопрос: {question}
Ответь на основе контекста."""
client = OpenAI(
base_url="https://openrouter.ai/api/v1",
api_key="sk-or-your-key"
)
response = client.chat.completions.create(
model=model,
messages=[
{"role": "system", "content": "Помощник по документам. Отвечай только по контексту."},
{"role": "user", "content": prompt}
],
temperature=0.1
)
return response.choices[0].message.contentПо умолчанию LLM ждёт полный ответ и возвращает его целиком. Для длинных ответов это создаёт задержку. Streaming позволяет получать ответ по частям.
stream = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": "Расскажи подробно про процедуру отпуска"}],
stream=True # Включаем стриминг
)
full_response = ""
for chunk in stream:
if chunk.choices[0].delta.content:
token = chunk.choices[0].delta.content
full_response += token
print(token, end="", flush=True) # Печатаем каждый токен сразу
print(f"\n\nПолная длина: {len(full_response)} символов")stream = ollama.chat(
model="llama3.2",
messages=[{"role": "user", "content": "Расскажи про отпуск"}],
stream=True
)
full_response = ""
for chunk in stream:
token = chunk["message"]["content"]
full_response += token
print(token, end="", flush=True)API-вызовы могут падать: сеть, лимиты, неверный ключ. Обработка ошибок и retries обязательны для надёжной RAG-системы.
import time
from openai import OpenAI, APIError
def llm_with_retry(prompt, max_retries=3, delay=2):
"""Вызов LLM с повторными попытками при ошибке."""
client = OpenAI()
for attempt in range(max_retries):
try:
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": prompt}],
temperature=0.1,
max_tokens=500
)
return response.choices[0].message.content
except APIError as e:
print(f"Попытка {attempt + 1} не удалась: {e}")
if attempt < max_retries - 1:
time.sleep(delay * (2 ** attempt)) # Экспоненциальная задержка
else:
raise # Пробрасываем ошибку после всех попыток
result = llm_with_retry("Как оформить отпуск?")
print(result)Экспоненциальная задержка: 2с → 4с → 8с. Это стандартный паттерн для работы с API.
LLM API оплачивается по токенам. 1 токен ≈ 4 символа английского текста (для русского — чуть меньше).
def estimate_cost(input_tokens, output_tokens, model="gpt-4o-mini"):
"""Оценивает стоимость запроса."""
prices = {
"gpt-4o-mini": {"input": 0.15, "output": 0.60}, # $/1M токенов
"gpt-4o": {"input": 2.50, "output": 10.00},
"claude-3-haiku": {"input": 0.25, "output": 1.25},
}
price = prices.get(model, {"input": 1.0, "output": 2.0})
input_cost = (input_tokens / 1_000_000) * price["input"]
output_cost = (output_tokens / 1_000_000) * price["output"]
return input_cost + output_cost
# Типичный RAG-запрос: 2000 токенов контекста + 500 токенов ответа
cost = estimate_cost(2000, 500, "gpt-4o-mini")
print(f"Стоимость одного запроса: ${cost:.6f}")
print(f"Стоимость 1000 запросов: ${cost * 1000:.4f}")Для gpt-4o-mini один RAG-запрос стоит доли цента. Для gpt-4o — заметно дороже. Выбор модели зависит от бюджета и требований к качеству.
Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.