RAG на Command A: ответы с цитатами, которым можно верить
Корпоративному ассистенту мало «правильного» ответа — нужна ссылка на источник. Собираем RAG на Command A так, чтобы каждое утверждение было проверяемо.
Елена Рудакова
RAG на Command A: ответы с цитатами, которым можно верить
В корпоративном ассистенте главный вопрос не «ответила ли модель», а «можно ли этому ответу доверять и как проверить». Отсюда требование: каждое утверждение должно опираться на конкретный фрагмент документа, и этот фрагмент должно быть видно. Command A ориентирована ровно под это, и собрать такую систему на ней проще, чем кажется.
Из чего состоит RAG
- Индексация. Документы режем на куски (chunks) по 300–800 токенов, каждый превращаем в вектор эмбеддингом и складываем в векторное хранилище.
- Поиск. На вопрос пользователя считаем эмбеддинг запроса, достаём топ-N ближайших кусков.
- Генерация. Отдаём куски в Command A с жёсткой инструкцией отвечать только по ним и ссылаться на источник.
Первые два шага — про эмбеддинги (об их выборе у нас отдельный материал), третий — про модель.
Индексация фрагментов
Векторы для кусков считаем через /v1/embeddings:
from openai import OpenAIclient = OpenAI(base_url="https://api.yenisei.ru/v1", api_key="YENISEI_KEY")def embed(texts): r = client.embeddings.create(model="bge-m3", input=texts) return [d.embedding for d in r.data]vectors = embed([chunk.text for chunk in chunks])# vectors кладём в векторную БД вместе с id и метаданными кускаГенерация ответа с опорой на источники
Ключ — в системном промпте и в том, как оформлены фрагменты. Даём каждому куску явный идентификатор, чтобы модель могла на него сослаться:
context = "\n\n".join( f"[{c.doc}#{c.id}] {c.text}" for c in retrieved)resp = client.chat.completions.create( model="command-a", messages=[ {"role": "system", "content": ( "Отвечай строго по фрагментам ниже. " "После каждого утверждения ставь ссылку в формате [doc#id]. " "Если ответа в фрагментах нет — напиши: «В документах ответа нет»." )}, {"role": "user", "content": f"Вопрос: {question}\n\nФрагменты:\n{context}"}, ],)print(resp.choices[0].message.content)Почему именно Command A
Дисциплина. Модель, которую не удерживают в узде, на пограничном вопросе дополнит ответ «здравым смыслом» из своих общих знаний — и в корпоративном контексте это баг, а не фича. Command A по своей природе лучше держится приложенных источников и честнее говорит «не знаю», когда ответа в контексте нет. С окном 256K в неё влезает много фрагментов сразу, так что на широких вопросах не приходится жертвовать полнотой.
Что чаще всего ломает RAG
- Плохие чанки. Порезали документ по 2000 токенов — поиск тащит куски, где нужное разбавлено мусором. Порезали слишком мелко — теряется контекст. 300–800 токенов с небольшим перекрытием обычно золотая середина.
- Один эмбеддинг на всё. Русскоязычную базу нужно индексировать многоязычной моделью (BGE-M3, Qwen3 Embedding), иначе поиск проседает.
- Нет запасного «не знаю». Без явной инструкции модель постарается ответить всегда — и на вопросе не по теме выдаст правдоподобную выдумку.
- Смешение ролей. Инструкция «только по источникам» должна быть в системном сообщении, а фрагменты — в пользовательском; если свалить всё в кучу, дисциплина падает.
Как проверять качество
Соберите десятка три реальных вопросов с известными ответами и прогоняйте на них связку при каждом изменении: сменили эмбеддинг, поменяли размер чанка, поправили промпт. Метрика простая — доля ответов, где утверждения подтверждаются процитированными фрагментами. Если цитата не бьётся с текстом — это ложное срабатывание, и оно опаснее прямого «не знаю».