Prompt engineering для RAG: шаблоны, контекст, инструкции и борьба с галлюцинациями
Промпт — это интерфейс между retrieved контекстом и ответом LLM. Плохой промпт = плохой ответ.
Самый простой RAG-промпт содержит три элемента: контекст, вопрос и инструкцию.
prompt = f"""Контекст из документов:
{context}
Вопрос: {question}
Ответь на вопрос, используя контекст."""Это работает для простых случаев. Но в продакшене нужен более структурированный подход.
Системный промпт задаёт поведение модели на всём протяжении разговора. Он особенно важен в RAG, потому что контролирует, как модель использует контекст.
system_prompt = """Ты — эксперт по внутренним документам компании.
Твоя задача — отвечатьать на вопросы сотрудников, используя ТОЛЬКО
предоставленный контекст из документов.
Правила:
- Если в контексте есть информация для ответа — используй её
- Если информации недостаточно — скажи «Информация не найдена в документах»
- Не добавляй информацию, которой нет в контексте
- Не упоминай, что ты используешь контекст — просто отвечай"""
user_prompt = f"""Контекст:
{context}
Вопрос: {question}"""Без чётких правил модель может начать использовать свои внутренние знания вместо контекста — это подрывает суть RAG.
Few-shot prompting — это добавление примеров «вопрос → правильный ответ» в промпт. Модель учится на примерах форматирования и стиля.
prompt = f"""Контекст:
{context}
Ответь на вопрос, используя контекст. Следуй формату примеров:
Пример 1:
Вопрос: Как подать заявление на отпуск?
Ответ: Заявление на отпуск подаётся через HR-портал не менее чем за 2 недели до желаемой даты начала отпуска.
Пример 2:
Вопрос: Где получить медицинскую страховку?
Ответ: Информация не найдена в документах.
Теперь ответь на вопрос:
Вопрос: {question}
Ответ:"""Few-shot помогает модели понять:
Минус: примеры занимают токены и увеличивают стоимость. Обычно 2–3 примеров достаточно.
Галлюцинация — это когда LLM генерирует информацию, которой нет в контексте. В RAG это особенно опасно: пользователь доверяет ответам, основанным на документах.
prompt = f"""Контекст:
{context}
Вопрос: {question}
Инструкция: отвечай ТОЛЬКО на основе контекста выше. Если контекст не содержит информации для ответа на вопрос, ответь ровно: «Информация не найдена в предоставленных документах». Не используй свои собственные знания и не придумывай информацию."""Попросите модель указывать, из какого документа взята информация:
prompt = f"""Контекст (каждый фрагмент пронумерован):
[1] {context_chunks[0]}
[2] {context_chunks[1]}
[3] {context_chunks[2]}
Вопрос: {question}
Ответь на вопрос и укажи в скобках номер фрагмента, из которого взята информация.
Например: «Заявление подаётся за 2 недели [1]»."""Это заставляет модель привязывать каждый факт к конкретному фрагменту. Если модель не может найти источник — скорее всего, она галлюцинирует.
Двухэтапный подход: сначала генерация, потом проверка:
# Этап 1: генерация ответа
answer = generate_answer(question, context)
# Этап 2: проверка на галлюцинации
verification_prompt = f"""Контекст:
{context}
Ответ модели:
{answer}
Проверь: все ли утверждения в ответе подтверждаются контекстом?
Ответь «Да» или «Нет» и укажи, какие утверждения не подтверждаются."""
verification = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": verification_prompt}],
temperature=0.0
)Если проверка вернула «Нет» — можно отклонить ответ и вернуть «Информация не найдена».
Иногда ответ нужно распарсить программно. Попросите модель вернуть JSON:
prompt = f"""Контекст:
{context}
Вопрос: {question}
Ответь в формате JSON:
{{
"answer": "Текст ответа или null, если информации нет",
"has_info": true/false,
"sources": ["номера фрагментов, использованных для ответа"]
}}
Ответь ТОЛЬКО JSON, без дополнительного текста."""Для гарантированного JSON используйте параметр response_format:
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": prompt}],
response_format={"type": "json_object"},
temperature=0.0
)
import json
result = json.loads(response.choices[0].message.content)
print(result["answer"])prompt = f"""Контекст:
{context}
Вопрос: {question}
Ответь, используя Markdown:
- Заголовок с кратким ответом
- Основные пункты списком
- Ссылки на источники в конце"""| Параметр | Значение для RAG | Эффект |
|---|---|---|
temperature=0.0 | Детерминированный | Всегда одинаковый ответ для одного входа |
temperature=0.1–0.3 | Рекомендуется | Минимальная вариативность, фокус на контексте |
temperature=0.7–1.0 | Не рекомендуется | Модель добавляет информацию от себя |
top_p=0.9 | Альтернатива temperature | Ограничивает «хвост» распределения токенов |
# Консервативный RAG-запрос
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": prompt}],
temperature=0.1, # Минимальная креативность
top_p=0.9 # Ограничиваем выбор токенов
)temperature и top_p — альтернативные параметры. Обычно используют что-то одно. Для RAG temperature=0.1 — стандартный выбор.
def create_prompt(question, context):
return f"""Контекст:
{context}
Вопрос: {question}
Ответь на вопрос, используя контекст."""from string import Template
RAG_TEMPLATE = Template("""Контекст:
$context
Вопрос: $question
Ответь на вопрос, используя контекст. Если информации нет, скажи «Информация не найдена».""")
def create_prompt(question, context):
return RAG_TEMPLATE.substitute(context=context, question=question)string.Template безопаснее: если question содержит { или }, f-string может вызвать ошибку, а Template — нет.
from string import Template
ADVANCED_RAG_TEMPLATE = Template("""Ты — $role.
Контекст из документов:
$context
Предыдущие вопросы в разговоре:
$conversation_history
Текущий вопрос: $question
Инструкции:
$instructions
Ответ:""")
def create_advanced_prompt(question, context, conversation="",
role="эксперт по документам",
instructions="Отвечай только по контексту."):
return ADVANCED_RAG_TEMPLATE.substitute(
role=role,
context=context,
conversation_history=conversation or "Нет предыдущих вопросов",
question=question,
instructions=instructions
)# Плохо — модель не понимает, что именно делать
prompt = f"Контекст: {context}\nВопрос: {question}\nОтветь."# Хорошо — чёткая инструкция с правилами
prompt = f"""Контекст: {context}
Вопрос: {question}
Ответь на вопрос, используя ТОЛЬКО контекст. Если информации нет — скажи «Информация не найдена»."""# Плохо — модель не понимает, где кончается контекст и начинается вопрос
prompt = f"{context} {question} ответь"# Хорошо — явные разделители
prompt = f"""Контекст:
{context}
---
Вопрос: {question}"""# Плохо — «будь краток» и «расскажи подробно» противоречат друг другу
prompt = f"""Контекст: {context}
Вопрос: {question}
Будь краток, но расскажи максимально подробно. Используй свои знания и только контекст."""# Хорошо — последовательные инструкции
prompt = f"""Контекст: {context}
Вопрос: {question}
Ответь кратко (2–3 предложения), используя ТОЛЬКО контекст."""from openai import OpenAI
from string import Template
RAG_TEMPLATE = Template("""Ты — эксперт по документам компании. Отвечай только на основе контекста.
Контекст:
$context
Вопрос: $question
Если информации достаточно в контексте — ответь подробно. Если нет — скажи «Информация не найдена в документах».""")
class RAGSystem:
def __init__(self, model="gpt-4o-mini", temperature=0.1):
self.client = OpenAI()
self.model = model
self.temperature = temperature
self.template = RAG_TEMPLATE
def query(self, question, context_chunks):
context = "\n\n---\n\n".join(context_chunks)
prompt = self.template.substitute(context=context, question=question)
response = self.client.chat.completions.create(
model=self.model,
messages=[{"role": "user", "content": prompt}],
temperature=self.temperature,
max_tokens=500
)
return response.choices[0].message.content
# Использование
rag = RAGSystem(model="gpt-4o-mini")
answer = rag.query(
"Как оформить отпуск?",
["Отпуск оформляется через HR-портал за 2 недели."]
)
print(answer)Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.