Практические заметки: Что такое RAG: как приложения предоставляют ИИ доступ к собственным данным
Пошаговое руководство по практическим заметкам: что такое RAG: как приложения предоставляют ИИ доступ к собственным контрактам, проверкам и слотам для кода для команд, использующих эту модель.
Используйте это как упрощённую версию идей из статьи «RAG Explained: How Applications Give AI Access to Their Own Data», предназначенную для операторов: чёткие этапы, упорядоченные блоки кода и записи о восстановлении, сохраняющиеся при передаче задач. Этап обзора работает лучше всего, если рассматривать его как измеримую основу. Соберите один идеальный пример работы, один случай сбоя и запись о возврате к предыдущему состоянию перед расширением объёма работ. Предпочитайте небольшие, тестируемые единицы кода вместо обширных скриптов. Когда какой-то шаг терпит неудачу, причина сбоя должна указывать на конкретную ответственность, а не на запутанную цепочку операций.
Что такое Retrieval-Augmented Generation (RAG)?
На этапе «Что такое генерация с усилением через поиск» необходимо определить входные данные, ответственного за выполнение шага и критерии завершения перед изменением кода. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Рассматривайте этот этап как контракт между входными данными и проверенными результатами. Укажите названия результатов работы, определите критерии успеха и не допускайте молчаливого частичного завершения задачи. Цитируйте те фрагменты, которые фактически легли в основу ответа. Без цитат операторы не смогут отличить галлюцинации от пробелов в индексации.
Without RAG:
[User Question] ──> [LLM] ──> [Answer based on generic training data]
With RAG:
[User Question] ──> [Search Engine / Vector DB]
│
└──> [Relevant Documents]
│
[User Question] + [Relevant Documents] ──> [LLM] ──> [Factual Answer]
Почему важен RAG: проблемы, которые он решает
Чтобы понять, почему важен RAG, необходимо заранее определить исходные данные, ответственного за выполнение шага и критерии завершения перед изменением кода. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Рядом с функциональными результатами следует записывать время выполнения и стоимость токенов или запросов. Отображение затрат с самого начала помогает избежать неожиданных счетов при переходе от демо-среды к общедоступным средам. Необходимо указывать конкретные фрагменты текста, на которых основан ответ. Без цитат операторы не могут отличить галлюцинации от пробелов в индексации.
Как на самом деле работает RAG?
На этапе «Как работает RAG» необходимо определить входные данные, ответственного за выполнение шага и критерии завершения перед внесением изменений в код. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Конфигурацию следует хранить отдельно от кода приложения. Файлы среды, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь код. Указывайте те участки текста, которые легли в основу ответа. Без цитат операторы не смогут отличить галлюцинации от проблем с индексацией. На этапе «Как работает RAG» необходимо определить входные данные, ответственного за выполнение шага и критерии завершения перед внесением изменений в код. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Предпочитайте небольшие, тестируемые единицы кода большим, сложным скриптам. При сбое шага он должен указывать на конкретную проблему, а не на сложную цепочку операций.
1. Ingestion Pipeline (Offline / Background):
[Raw Documents] ──> [Chunking] ──> [Embedding Model] ──> [Vector Database]
2. Query Pipeline (Runtime):
[User Question] ──> [Embedding Model] ──> [Vector Search in DB]
│
▼
[User Question] + [Top Matching Chunks] ──> [LLM] ──> [Final Answer]
1. Разбиение на чанки
На этапе разбиения на чанки сначала запишите контракт: необходимые входные данные, сигнал о успехе и действия при частичной неудаче. Такой список помогает сохранять честность при последующих изменениях кода. Рассматривайте этот этап как контракт между входными данными и проверенными выходными результатами. Дайте названия элементам, определите критерии успеха и не допускайте молчаливого частичного выполнения задачи. Оцените уровень воспроизведения на фиксированном наборе вопросов перед настройкой подсказок. Частая смена подсказок редко помогает улучшить качество поиска.
2. Эмбеддинги
При работе над этапом 2 Embeddings сначала запишите описание интерфейса: необходимые входные данные, сигнал успешного выполнения и действия при частичной неудаче. Такой чек-лист поможет сохранять честность при последующих изменениях кода. Рядом с функциональными результатами записывайте время выполнения и стоимость токенов или запросов. Отслеживание затрат с самого начала предотвращает неожиданные расходы при переходе с демо-среды в общедоступные среды. Измеряйте уровень воспроизводимости ответов на фиксированный набор вопросов перед настройкой подсказок. Частая смена подсказок редко помогает улучшить качество поиска.
3. Векторное хранилище
При работе над тремя этапами векторного хранилища сначала запишите спецификацию: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой чек-лист помогает сохранять честность последующих изменений в коде. Храните конфигурацию отдельно от кода приложения. Файлы среды, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь код. Измеряйте показатель воспроизводимости на фиксированном наборе вопросов перед настройкой подсказок. Частая смена подсказок редко помогает улучшить качество поиска. При работе над тремя этапами векторного хранилища сначала запишите спецификацию: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой чек-лист помогает сохранять честность последующих изменений в коде. Предпочитайте небольшие, тестируемые единицы кода большим скриптам. Когда какой-то шаг терпит неудачу, причина должна быть связана с одной конкретной функцией, а не с запутанной цепочкой операций.
4. Поиск векторов (косинусное сходство)
Этап косинусного поиска векторов работает наилучшим образом, если рассматривать его как измеримую поверхность. Соберите один идеальный пример выполнения, один случай сбоя и запись о возврате к предыдущему состоянию перед расширением объема работ. Рассматривайте этот этап как контракт между входными данными и проверенными выходными результатами. Дайте названия соответствующим документам, определите критерии успешного выполнения и не соглашайтесь на молчаливое частичное завершение работы. Разделяйте политику разбиения данных на части и политику поиска. Изменение одной из них не должно приводить к переписыванию другой при изменении показателей качества.
5. Повышение эффективности промптов
Этап усиления запросов с использованием 5 подходов работает наилучшим образом, если рассматриваться как измеримая характеристика. Соберите один идеальный пример выполнения, один случай сбоя и записку о возврате к предыдущему состоянию перед расширением объема работ. Записывайте время выполнения, а также стоимость токенов или запросов рядом с функциональными результатами. Отслеживание затрат на раннем этапе предотвращает неожиданные счета при переходе от демо-среды к общедоступным средам. Установите лимит токенов на один ход и на всю сессию. Инструменты агентного типа активно расширяют контекст; жесткие ограничения не позволяют демо-версиям превращаться в неожиданные счета.
Практическая реализация в Node.js
Практическая реализация на этапе Node работает наилучшим образом, когда рассматривается как измеримая сфера. Соберите один идеальный пример работы, один случай сбоя и запись о возврате к предыдущему состоянию перед расширением объема работ. Храните конфигурацию вне кода приложения. Файлы среды, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь код. Разделяйте политику разбиения на части и политику извлечения данных. Изменение одной из них не должно приводить к переписыванию другой при изменении показателей качества. Практическая реализация на этапе Node работает наилучшим образом, когда рассматривается как измеримая сфера. Соберите один идеальный пример работы, один случай сбоя и запись о возврате к предыдущему состоянию перед расширением объема работ. Предпочитайте небольшие, тестируемые единицы кода вместо обширных скриптов. Когда какой-либо шаг срабатывает некорректно, причина должна указывать на конкретную ответственность, а не на запутанную цепочку операций.
Шаг 1: Прием и векторизация документов
На этапе 1 «Прием данных» необходимо определить входные данные, ответственного за выполнение шага и критерии завершения перед изменением кода. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Рассматривайте этот этап как контракт между входными данными и проверенными выходными результатами. Укажите названия элементов, определите критерии успеха и не допускайте безответственного частичного выполнения задачи. Цитируйте те фрагменты, которые легли в основу ответа. Без цитат операторы не смогут отличить галлюцинации от пробелов в индексации.
// ingest.js
import { OpenAI } from "openai";
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
// In a real application, you would load these from Markdown files or a CMS
const documents = [
{
id: "doc_1",
text: "Enterprise customers can request a full refund within 30 days of contract signing. Contact enterprise-support@example.com."
},
{
id: "doc_2",
text: "Monthly self-serve subscriptions are non-refundable once the billing cycle begins. Users can cancel anytime to avoid future charges."
},
{
id: "doc_3",
text: "Custom engineering work and onboarding packages are strictly non-refundable once the kickoff meeting has taken place."
}
];
async function createEmbedding(text) {
const response = await openai.embeddings.create({
model: "text-embedding-3-small",
input: text,
});
return response.data[0].embedding;
}
export async function buildKnowledgeBase() {
const embeddedDocs = [];
for (const doc of documents) {
const vector = await createEmbedding(doc.text);
embeddedDocs.push({ ...doc, vector });
}
return embeddedDocs;
}
Шаг 2: Поиск релевантного контекста
На этапе «Поиск релевантных данных» второго шага необходимо определить входные данные, ответственного за выполнение шага и критерии завершения перед изменением кода. Операторы должны иметь возможность перезапустить шаг, исходя из известной точки контроля, без необходимости угадывания скрытого состояния. Рядом с функциональными результатами следует записывать время выполнения и стоимость токенов или запросов. Отображение стоимости заранее помогает избежать неожиданных счетов при переходе с демо-среды в общедоступные среды. Указывайте те фрагменты текста, которые легли в основу ответа. Без цитат операторы не смогут отличить галлюцинации от пробелов в индексации.
// search.js
function cosineSimilarity(vecA, vecB) {
let dotProduct = 0;
let normA = 0;
let normB = 0;
for (let i = 0; i < vecA.length; i++) {
dotProduct += vecA[i] * vecB[i];
normA += vecA[i] * vecA[i];
normB += vecB[i] * vecB[i];
}
return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
}
export function findTopMatches(queryVector, knowledgeBase, topK = 2) {
return knowledgeBase
.map(doc => ({
...doc,
score: cosineSimilarity(queryVector, doc.vector)
}))
.sort((a, b) => b.score - a.score)
.slice(0, topK);
}
Шаг 3: Доработка промпта и генерация ответа
На этапе 3 «Усиление процесса» необходимо определить входные данные, ответственного за выполнение шага и критерии завершения перед внесением изменений в код. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Конфигурацию следует хранить отдельно от кода приложения. Файлы среды, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь код. При следующем шаге, представляющем собой код или вызов инструмента, лучше использовать структурированные выходные данные с проверкой схемы, чем свободный текст. На этапе 3 «Усиление процесса» необходимо определить входные данные, ответственного за выполнение шага и критерии завершения перед внесением изменений в код. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Лучше использовать небольшие, тестируемые единицы кода вместо обширных скриптов. При сбое шага причина должна быть связана с конкретной функцией, а не с запутанной цепочкой операций.
// answer.js
import { OpenAI } from "openai";
import { createEmbedding, buildKnowledgeBase } from "./ingest.js";
import { findTopMatches } from "./search.js";
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
async function askKnowledgeBase(userQuestion, knowledgeBase) {
// 1. Convert user question into an embedding
const questionVector = await createEmbedding(userQuestion);
// 2. Retrieve top matching chunks
const matches = findTopMatches(questionVector, knowledgeBase, 1);
const contextText = matches.map(m => m.text).join("\n---\n");
// 3. Augment prompt with retrieved context
const systemPrompt = `
You are a helpful customer service assistant.
Answer the user's question using ONLY the context provided below.
If the context does not contain the answer, say "I do not have enough information to answer that."
Context:
${contextText}
`;
// 4. Generate answer
const completion = await openai.chat.completions.create({
model: "gpt-4o-mini",
messages: [
{ role: "system", content: systemPrompt },
{ role: "user", content: userQuestion }
],
temperature: 0.2, // Low temperature minimizes creative liberties
});
return completion.choices[0].message.content;
}
// Example usage:
const knowledgeBase = await buildKnowledgeBase();
const answer = await askKnowledgeBase("I signed an enterprise deal 2 weeks ago, can I get my money back?", knowledgeBase);
console.log(answer);
// Output: Yes, enterprise customers can request a full refund within 30 days of contract signing. You can email enterprise-support@example.com to initiate the process.
Распространённые ошибки, которые допускают разработчики при использовании RAG
При работе над этапом «Распространённые ошибки, которые допускают разработчики» сначала запишите условия работы: необходимые входные данные, сигнал о успешном выполнении и последствия частичной неудачи. Такой список поможет избежать неточностей при последующих изменениях кода. Рассматривайте этот этап как договор между входными данными и проверенными результатами. Дайте названия элементам данных, определите критерии успешности и не допускайте молчаливого частичного выполнения задачи. Оцените уровень воспроизведения информации на фиксированном наборе вопросов перед настройкой подсказок. Частая замена подсказок редко помогает улучшить качество поиска информации.
1. Наивное разбиение на фрагменты
При работе на этапе 1 «Наивное разбиение» сначала запишите условия работы алгоритма: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой список поможет избежать ошибок при последующих изменениях кода. Рядом с функциональными результатами записывайте время выполнения и стоимость обработки токенов или запросов. Отслеживание затрат с самого начала предотвращает неожиданные расходы при переходе с демо-среды в общедоступные среды. Перед настройкой подсказок измерьте уровень воспроизведения ответов на фиксированный набор вопросов. Частая смена подсказок редко помогает улучшить качество поиска.
2. Использование исключительно векторного поиска
При работе на этапе «2 Relying Solely on» сначала запишите условия работы системы: необходимые входные данные, сигнал успешного выполнения и действия при частичной неудаче. Такой список поможет сохранять честность при последующих изменениях кода. Храните конфигурацию вне кода приложения. Файлы с настройками, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь код. Измеряйте точность воспроизведения ответов на фиксированном наборе вопросов перед настройкой подсказок. Изменение подсказок редко помогает улучшить качество поиска. При работе на этапе «2 Relying Solely on» сначала запишите условия работы системы: необходимые входные данные, сигнал успешного выполнения и действия при частичной неудаче. Такой список поможет сохранять честность при последующих изменениях кода. Предпочитайте небольшие, тестируемые единицы кода большим скриптам. Если какой-то шаг не сработает, проблема должна указывать на конкретную ответственность, а не на запутанную структуру обработки данных.
3. Чрезмерное включение контекста (проблема потери информации)
Этот этап чрезмерного включения контекста работает наилучшим образом, если рассматривать его как измеримую площадку. Соберите один идеальный пример транскрипции, один пример сбоя и записку о возврате к предыдущему состоянию перед расширением объёма работы. Рассматривайте этот этап как контракт между входными данными и проверенными выходными результатами. Дайте названия создаваемым документам, определите критерии успеха и не соглашайтесь на молчаливое частичное выполнение задачи. Разделяйте политику разбиения данных на части и политику их извлечения. Изменение одной из них не должно приводить к переписыванию другой при изменении показателей качества.
Когда следует использовать RAG вместо тонкой настройки?
Этап «Когда следует использовать» работает наилучшим образом, если рассматриваться как измеримая основа. Соберите один идеальный пример работы, один случай сбоя и записку о возврате к предыдущему состоянию перед расширением объёма работ. Записывайте временные показатели, а также стоимость токенов или запросов рядом с функциональными результатами. Отслеживание затрат на раннем этапе предотвращает неожиданные счёты при переходе от демо-среды к общедоступным средам. Разделяйте политику разбиения данных и политику их извлечения; изменение одной из них не должно приводить к необходимости переписывания другой при изменении показателей качества.
Заключение
Этап Заключения работает наилучшим образом, когда его рассматривают как измеримую поверхность. Соберите один идеальный пример работы, один случай сбоя и записку о возврате к предыдущему состоянию перед расширением объёма работ. Храните конфигурацию вне кода приложения. Файлы среды, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь код. Разделяйте политику разбиения на части и политику получения данных. Изменение одной из них не должно приводить к переписыванию другой при изменении показателей качества. Этап Заключения работает наилучшим образом, когда его рассматривают как измеримую поверхность. Соберите один идеальный пример работы, один случай сбоя и записку о возврате к предыдущему состоянию перед расширением объёма работ. Предпочитайте небольшие, тестируемые единицы кода вместо обширных скриптов. Когда какой-либо шаг срабатывает некорректно, причина сбоя должна указывать на конкретную ответственность, а не на запутанную цепочку операций.
Чек-лист для эксплуатации
При работе над этапом операционного чек-листа сначала запишите условия контракта: необходимые входные данные, сигнал успешного выполнения и действия при частичной неудаче. Такой чек-лист помогает сохранять честность при последующих изменениях кода.
Задокументируйте одновременно «идеальный» путь выполнения и путь восстановления. Повторные попытки, проверки человеком и обработка неработоспособных сообщений являются частью продукта, а не элементами последующей доработки.
Измеряйте показатель воспроизводимости на фиксированном наборе вопросов перед настройкой подсказок. Частая смена подсказок редко помогает улучшить слабую систему поиска информации.
Закрепите версии зависимостей и запишите хэш изображения, использованного для демонстрации. Воспроизводимость важнее коллективного опыта.
Предпочитайте небольшие, тестируемые модули большим скриптам. Когда какой-то шаг терпит неудачу, причина должна быть связана с конкретной функцией, а не с запутанной цепочкой операций.
Измеряйте показатель воспроизводимости на фиксированном наборе вопросов перед настройкой подсказок. Частая смена подсказок редко помогает улучшить слабую систему поиска информации.
Перед внедрением стека заморозьте версии, сделайте копию «золотого» отчета для критической цепочки операций и уточните шаги возврата к предыдущему состоянию. В совместных средах необходимы ограничения на частоту запросов, проверки принадлежности пользователя и четко определенный ответственный за обновление секретов. Лучше выбирать надежность, чем креативные одноразовые демонстрации.
Примечание для 2c8785c21af6: не храните ключи поставщика в репозитории, установите лимит токенов на сессию и сохраняйте отчеты рядом с фикстурами для оценки, чтобы последующие замены моделей оставались сопоставимыми.