AI 지식 베이스를 20분 만에? Workers AI + Vectorize로 RAG 구축하기 (전체 코드 포함)

회사용 지능형 고객 지원 서비스를 만들려고 RAG 튜토리얼을 찾아보니, 이론만 모호하게 설명하거나 GPU를 빌리고 환경부터 구축하라는 내용이 대부분이었습니다. LangChain과 벡터 데이터베이스를 설정하는 데만 이틀이 걸리고, 그렇게 해도 제대로 실행된다는 보장이 없었습니다.
그러다 Cloudflare가 Workers AI + Vectorize + D1으로 구성된 완전관리형 AI 도구 세트를 제공한다는 사실을 알게 됐습니다. 무료 할당량도 꽤 넉넉했습니다. 이 도구들로 노트 Q&A 애플리케이션을 만들어 보니 처음부터 실제 사용까지 20분도 걸리지 않았고, 코드도 100여 줄에 불과했습니다.
이 글에서는 전체 과정을 단계별로 진행합니다.
- 개념 이해: 어려운 용어 없이 RAG가 무엇인지 설명합니다.
- 실전 구축: 실제로 실행되는 지식 베이스 Q&A 애플리케이션을 전체 코드와 함께 만듭니다.
- 최적화 팁: 검색 정확도를 높이고 비용을 낮춥니다.
- 배포: 실제로 사용할 수 있도록 온라인에 배포합니다.
JavaScript를 조금 알고 무료 Cloudflare 계정만 있다면 그대로 따라 만들 수 있습니다.
RAG란 무엇인가요? 5분 만에 이해하는 작동 원리
시험에 빗대어 이해하는 RAG
직관적인 비유부터 들어 보겠습니다. 시험을 볼 때 폐쇄형 시험은 머릿속에 기억한 내용만으로 답해야 하므로, 기억이 나지 않으면 엉뚱한 답을 지어낼 수 있습니다. 오픈북 시험은 확실하지 않을 때 책과 자료를 찾아볼 수 있어 훨씬 정확하게 답할 수 있습니다.
RAG(검색 증강 생성)는 AI에 오픈북 시험을 볼 권한을 주는 기술입니다.
기존 LLM은 폐쇄형 시험을 치르는 것처럼 학습할 때 본 데이터만으로 답합니다. 여기에는 몇 가지 문제가 있습니다.
- 학습 데이터에는 시점의 한계가 있어 최신 소식을 알지 못합니다.
- 회사 내부 문서를 본 적이 없습니다.
- 모든 세부 사항을 기억하지 못해 내용을 지어내기 쉽습니다. 이를 전문 용어로 ‘환각’이라고 합니다.
RAG는 미리 준비한 지식 베이스에서 관련 자료를 먼저 찾은 다음, AI가 그 내용을 바탕으로 답하게 합니다. 따라서 답변의 신뢰성을 높이면서 최신 정보도 반영할 수 있습니다.
RAG의 세 가지 핵심 단계
전체 과정은 세 단계로 요약할 수 있습니다.
1단계: 지식을 벡터로 변환해 저장합니다.
여러 문서가 있다고 해보겠습니다. RAG는 각 텍스트 조각을 일련의 숫자로 변환합니다. 이를 전문 용어로 ‘벡터’ 또는 ‘Embedding’이라고 하며, 이 숫자들은 텍스트의 의미를 나타냅니다.
예를 들어 ‘고양이는 귀엽다’와 ‘아기 고양이는 사랑스럽다’는 표현이 다르지만 뜻은 비슷합니다. 따라서 벡터로 바꾸면 두 숫자 배열도 서로 가까워집니다. 이런 벡터를 Vectorize 같은 벡터 데이터베이스에 저장합니다.
2단계: 사용자가 질문하면 가장 관련 있는 지식 조각을 찾습니다.
사용자가 ‘고양이는 어떻게 훈련하나요?‘라고 물으면 시스템은 이 질문도 벡터로 변환한 다음, 데이터베이스에서 ‘거리가 가장 가까운’ 몇 개의 내용을 찾습니다. 즉, 의미가 가장 유사한 지식을 검색합니다.
이 과정을 ‘유사도 검색’이라고 합니다. 속도가 빨라 수만 건의 데이터에서도 몇 밀리초 안에 가장 잘 맞는 3~5개를 찾을 수 있습니다.
3단계: 검색한 내용을 LLM에 전달해 답을 생성합니다.
관련 내용을 찾으면 Prompt로 조합해 AI에 보냅니다.
다음은 관련 자료입니다.
[검색된 내용 1]
[검색된 내용 2]
...
사용자 질문: 고양이는 어떻게 훈련하나요?
위 자료를 바탕으로 답하세요.
AI는 이 ‘참고 자료’를 보고 정확하고 근거 있는 답을 생성할 수 있습니다.
왜 Cloudflare 통합 구성을 선택할까요?
LangChain, LlamaIndex 등 RAG를 구현하는 방법은 많습니다. 하지만 직접 환경을 설정하고, 벡터 데이터베이스를 선택하며, GPU 리소스까지 관리하려면 꽤 번거롭습니다.
Cloudflare 구성의 장점은 다음과 같습니다.
Workers AI - Llama 3, Claude 등 10여 개의 오픈 소스 모델이 내장되어 있어 API 호출만으로 사용할 수 있습니다. GPU를 빌릴 필요가 없습니다. 무료 티어는 매일 정해진 Neurons 할당량을 제공하며 개인 프로젝트에는 충분합니다.
Vectorize - 관리형 벡터 데이터베이스이므로 Milvus나 Pinecone 등을 직접 구축할 필요가 없습니다. 인덱스 생성, 벡터 삽입, 유사도 검색을 몇 줄의 코드로 처리할 수 있습니다.
D1 - 원문 텍스트를 저장하는 Cloudflare의 SQLite 데이터베이스입니다. 벡터 데이터베이스에는 벡터만 저장되므로 실제 텍스트는 이곳에서 가져옵니다.
완전관리형 - 가장 편리한 부분입니다. 서버, 확장, 백업을 걱정하지 않고 코드 작성에 집중할 수 있습니다. Cloudflare의 엣지 네트워크 덕분에 세계 어디서나 빠르게 접근할 수 있습니다.
"Cloudflare는 2025년에 AutoRAG를 출시해 R2에 문서를 업로드한 뒤 문서 분할, 벡터화, 검색, 생성을 모두 자동으로 처리할 수 있게 했습니다."
Cloudflare는 2025년에 AutoRAG도 출시해 과정을 더욱 단순화했습니다. R2에 문서를 업로드하면 이후의 분할, 벡터화, 검색, 생성이 모두 자동으로 진행됩니다. 하지만 이 글에서는 내부 원리까지 배울 수 있도록 전체 과정을 직접 구축해 보겠습니다.
이론은 여기까지 하고 이제 직접 만들어 보겠습니다.
실전: 첫 번째 RAG 애플리케이션 구축하기
사용자가 노트를 추가하고 질문하면 시스템이 모든 노트에서 관련 내용을 찾아 답하는 노트 Q&A 애플리케이션을 만들어 보겠습니다.
프로젝트 초기화와 환경 준비
먼저 Wrangler(Cloudflare CLI 도구)를 설치합니다.
npm install -g wrangler
wrangler login # Cloudflare 계정에 로그인
프로젝트를 생성합니다.
npm create cloudflare@latest rag-notes-app
# "Hello World" worker 선택
# TypeScript 선택
cd rag-notes-app
네이티브 Workers API보다 사용하기 편한 라우팅 라이브러리 Hono를 설치합니다.
npm install hono
D1 데이터베이스와 Vectorize 인덱스를 생성합니다.
# 원본 노트를 저장할 D1 데이터베이스 생성
wrangler d1 create notes-db
# Vectorize 인덱스 생성(768차원, bge-base-en-v1.5 모델에 맞춤)
wrangler vectorize create notes-index --dimensions=768 --metric=cosine
그다음 wrangler.jsonc(또는 wrangler.toml)를 설정합니다.
{
"name": "rag-notes-app",
"main": "src/index.ts",
"compatibility_date": "2024-01-01",
"node_compat": true,
// AI 바인딩
"ai": {
"binding": "AI"
},
// D1 데이터베이스 바인딩
"d1_databases": [
{
"binding": "DB",
"database_name": "notes-db",
"database_id": "你的数据库ID" // 위 생성 명령의 출력에서 복사
}
],
// Vectorize 인덱스 바인딩
"vectorize": [
{
"binding": "VECTORIZE",
"index_name": "notes-index"
}
],
// Workflow 바인딩(비동기 벡터화 작업 처리)
"workflows": [
{
"binding": "RAG_WORKFLOW",
"name": "rag-workflow",
"class_name": "RAGWorkflow"
}
]
}
데이터베이스 테이블을 초기화합니다.
-- schema.sql
CREATE TABLE IF NOT EXISTS notes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
text TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
다음을 실행합니다.
wrangler d1 execute notes-db --file=./schema.sql
지식 베이스 입력 기능 구현
사용자의 노트를 벡터로 변환해 저장하는 RAG의 핵심 부분입니다.
비동기 작업을 처리할 src/workflow.ts를 생성합니다.
import { WorkflowEntrypoint, WorkflowStep } from 'cloudflare:workers';
type Env = {
AI: Ai;
DB: D1Database;
VECTORIZE: VectorizeIndex;
};
type Params = {
noteId: number;
text: string;
};
export class RAGWorkflow extends WorkflowEntrypoint<Env, Params> {
async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
const { noteId, text } = event.payload;
// 1단계: D1 레코드가 생성됐는지 확인(기본 라우트에서 처리)
// 2단계: 벡터 생성
const embeddings = await step.do('generate embeddings', async () => {
const response = await this.env.AI.run(
'@cf/baai/bge-base-en-v1.5', // 768차원 Embedding 모델
{ text: [text] }
);
return response.data[0]; // 벡터 배열 반환
});
// 3단계: Vectorize에 삽입
await step.do('insert vector', async () => {
await this.env.VECTORIZE.insert([
{
id: noteId.toString(),
values: embeddings,
metadata: { text } // 디버깅에 쓸 텍스트 사본 저장
}
]);
});
}
}
노트 추가 요청을 처리하는 기본 라우트 src/index.ts입니다.
import { Hono } from 'hono';
import { RAGWorkflow } from './workflow';
type Bindings = {
AI: Ai;
DB: D1Database;
VECTORIZE: VectorizeIndex;
RAG_WORKFLOW: Workflow;
};
const app = new Hono<{ Bindings: Bindings }>();
// 노트 추가
app.post('/notes', async (c) => {
const { text } = await c.req.json<{ text: string }>();
if (!text?.trim()) {
return c.json({ error: 'Text is required' }, 400);
}
// D1에 삽입
const result = await c.env.DB.prepare(
'INSERT INTO notes (text) VALUES (?) RETURNING id'
).bind(text).first<{ id: number }>();
if (!result) {
return c.json({ error: 'Failed to create note' }, 500);
}
// Workflow를 실행해 비동기로 벡터 생성
await c.env.RAG_WORKFLOW.create({
params: { noteId: result.id, text }
});
return c.json({
id: result.id,
message: 'Note created, vectorization in progress'
});
});
export default app;
export { RAGWorkflow };
이제 사용자가 POST 요청으로 노트를 추가하면 다음 과정이 진행됩니다.
- 텍스트를 즉시 D1에 저장합니다.
- 백그라운드 Workflow가 벡터를 생성해 Vectorize에 삽입합니다.
- 벡터화에 몇 초가 걸려도 사용자 요청을 차단하지 않습니다.
지능형 Q&A 기능 구현
이제 노트를 저장할 수 있으니 검색 기능을 만들어 보겠습니다.
src/index.ts에 다음 코드를 추가합니다.
// 질의응답
app.get('/', async (c) => {
const query = c.req.query('q');
if (!query) {
return c.json({ error: 'Query parameter "q" is required' }, 400);
}
// 1단계: 질문을 벡터로 변환
const queryEmbedding = await c.env.AI.run(
'@cf/baai/bge-base-en-v1.5',
{ text: [query] }
);
// 2단계: Vectorize에서 가장 유사한 노트 3개 검색
const matches = await c.env.VECTORIZE.query(
queryEmbedding.data[0],
{ topK: 3, returnMetadata: true }
);
if (matches.count === 0) {
return c.json({ answer: '没有找到相关笔记' });
}
// 3단계: D1에서 전체 텍스트 가져오기(필요한 경우)
const noteIds = matches.matches.map(m => m.id);
const notes = await c.env.DB.prepare(
`SELECT text FROM notes WHERE id IN (${noteIds.map(() => '?').join(',')})`
).bind(...noteIds).all();
// 4단계: Prompt를 만들고 LLM을 호출해 답변 생성
const context = notes.results.map((n: any) => n.text).join('\n\n---\n\n');
const prompt = `以下是相关的笔记内容:
${context}
用户问题:${query}
请基于上述笔记内容回答用户问题。如果笔记中没有相关信息,请说明。`;
const aiResponse = await c.env.AI.run(
'@cf/meta/llama-3-8b-instruct', // 또는 claude-3-5-sonnet-latest 사용
{
messages: [
{ role: 'system', content: '你是一个智能笔记助手' },
{ role: 'user', content: prompt }
]
}
);
return c.json({
answer: aiResponse.response,
sources: matches.matches.map(m => ({
id: m.id,
score: m.score,
text: m.metadata?.text
}))
});
});
테스트해 보겠습니다.
# 로컬 실행
wrangler dev
# 노트 추가
curl -X POST http://localhost:8787/notes \
-H "Content-Type: application/json" \
-d '{"text": "Cloudflare Workers AI 支持 Llama 3 和 Claude 模型"}'
curl -X POST http://localhost:8787/notes \
-H "Content-Type: application/json" \
-d '{"text": "Vectorize 使用余弦相似度进行向量检索"}'
# Workflow가 벡터화를 마칠 때까지 몇 초 대기
# 질문
curl "http://localhost:8787/?q=Workers%20AI%20有哪些模型"
모든 것이 정상이라면 노트 내용을 바탕으로 한 답변을 받게 됩니다.
삭제 및 업데이트 기능
노트를 삭제할 때는 D1과 Vectorize의 데이터를 함께 삭제해야 합니다.
app.delete('/notes/:id', async (c) => {
const id = c.req.param('id');
// D1에서 삭제
await c.env.DB.prepare('DELETE FROM notes WHERE id = ?').bind(id).run();
// Vectorize에서 삭제
await c.env.VECTORIZE.deleteByIds([id]);
return c.json({ message: 'Note deleted' });
});
업데이트는 기존 데이터를 삭제한 뒤 다시 추가해 벡터를 새로 생성하는 방식이 가장 간단합니다.
전체 코드는 Cloudflare 공식 예제를 참고하세요.
고급 최적화: RAG를 더 똑똑하게 만들기
기본 기능은 실행되지만 실제 프로젝트에 사용하려면 몇 가지 세부 사항을 더 다듬는 것이 좋습니다.
텍스트 청킹 전략
현재는 노트 하나를 통째로 하나의 단위로 저장합니다. 하지만 노트가 기술 문서처럼 길다면 문제가 생깁니다.
- 문서 전체의 유사도는 낮을 수 있습니다. 관련 있는 부분이 일부 문단뿐일 수 있기 때문입니다.
- Prompt가 너무 길어 LLM의 컨텍스트 창 한도를 초과할 수 있습니다.
더 좋은 방법은 긴 텍스트를 작은 청크(chunk)로 나누고 각 청크의 벡터를 별도로 생성하는 것입니다.
간단한 청킹 방법은 다음과 같습니다.
function splitText(text: string, chunkSize: number = 500, overlap: number = 50): string[] {
const chunks: string[] = [];
let start = 0;
while (start < text.length) {
const end = Math.min(start + chunkSize, text.length);
chunks.push(text.slice(start, end));
start = end - overlap; // 문장이 잘리는 것을 막기 위해 일부 중첩
}
return chunks;
}
더 지능적인 방법으로는 문단이나 의미 단위로 나눌 수 있습니다. LangChain의 RecursiveCharacterTextSplitter를 사용할 수도 있습니다. 하지만 대부분의 사례에는 고정 길이와 중첩만으로도 충분합니다.
Workflow를 수정해 각 청크에 고유 ID를 할당합니다.
const chunks = splitText(text);
for (let i = 0; i < chunks.length; i++) {
const chunkId = `${noteId}-${i}`;
const embeddings = await this.env.AI.run('@cf/baai/bge-base-en-v1.5', {
text: [chunks[i]]
});
await this.env.VECTORIZE.insert([{
id: chunkId,
values: embeddings.data[0],
metadata: { noteId, chunkIndex: i, text: chunks[i] }
}]);
}
검색 정확도 높이기
topK와 유사도 임계값 조정
기본값인 상위 3개는 부족할 수도, 너무 많을 수도 있습니다. 5개로 늘린 다음 유사도가 너무 낮은 결과를 걸러 보세요.
const matches = await c.env.VECTORIZE.query(queryEmbedding.data[0], {
topK: 5,
returnMetadata: true
});
// 유사도가 0.7보다 높은 결과만 유지
const relevantMatches = matches.matches.filter(m => m.score > 0.7);
코사인 유사도의 점수 범위는 0~1이며, 일반적으로 0.7 이상이면 관련성이 높은 편입니다.
Prompt 최적화
검색한 내용을 AI에 그대로 던지는 데 그치지 말고, 이 정보를 어떻게 사용해야 하는지 알려주세요.
const prompt = `你是一个智能笔记助手。以下是从笔记库中检索到的相关内容(按相关性排序):
${context}
请严格基于上述内容回答用户问题。如果内容不足以回答问题,明确说明"笔记中没有找到相关信息",不要编造答案。
用户问题:${query}`;
핵심은 다음과 같습니다.
- AI에 검색된 자료임을 명확히 알려줍니다.
- 해당 내용만을 바탕으로 답하도록 요구합니다.
- ‘모른다’고 답할 수 있게 합니다.
이렇게 하면 AI가 내용을 지어내는 상황을 줄일 수 있습니다.
비용 관리와 요청 제한
Workers AI 무료 티어에는 일일 Neurons 할당량이 있습니다. 구체적인 수치는 바뀔 수 있으므로 Pricing 페이지에서 최신 정보를 확인하세요.
사용량 모니터링:
Cloudflare Dashboard → Workers AI에서 일일 사용량을 볼 수 있습니다. 모델마다 사용량이 다르며 Embedding 모델은 저렴하고 LLM 생성은 상대적으로 더 비쌉니다.
대체 처리 전략:
한도 초과가 걱정된다면 다음 방법을 사용할 수 있습니다.
- KV 또는 Durable Objects 카운터로 사용자별 요청 빈도를 제한합니다.
- 할당량을 초과하면 더 작은 모델이나 캐시된 결과를 사용합니다.
- 중요하지 않은 요청에는 LLM을 호출하지 않고 검색된 원문만 반환합니다.
// 간단한 요청 제한 예제
const userKey = c.req.header('X-User-ID') || 'anonymous';
const requestCount = await c.env.KV.get(`rate:${userKey}`) || 0;
if (requestCount > 100) {
return c.json({ error: 'Rate limit exceeded' }, 429);
}
await c.env.KV.put(`rate:${userKey}`, requestCount + 1, { expirationTtl: 86400 });
더 강력한 모델로 전환하기
Llama 3 8B도 상당히 좋지만 더 높은 이해 능력이 필요하다면 Claude를 사용해 볼 수 있습니다.
// 먼저 Dashboard에서 Anthropic API key를 바인딩해야 함
const aiResponse = await c.env.AI.run('claude-3-5-sonnet-latest', {
messages: [
{ role: 'system', content: '你是一个智能笔记助手' },
{ role: 'user', content: prompt }
]
});
Claude는 이해 능력과 출력 품질이 더 좋지만 Neurons도 더 많이 사용합니다. 실제 요구 사항에 맞게 선택하세요.
제 경험을 기준으로는 다음과 같습니다.
- 간단한 Q&A: Llama 3면 충분합니다.
- 추론과 요약이 필요한 작업: Claude가 확실히 더 낫습니다.
- 예산이 제한된 경우: Llama로 먼저 테스트하고 요구 사항을 확인한 뒤 업그레이드합니다.
온라인 배포와 실제 활용 사례
배포 과정
로컬 테스트가 끝나면 배포는 매우 간단합니다.
wrangler deploy
이 명령 한 줄이면 Cloudflare가 자동으로 다음 작업을 처리합니다.
- 코드를 패키징합니다.
- 전 세계 엣지 노드에 배포합니다.
.workers.dev도메인을 생성합니다.
다음과 비슷한 출력이 표시됩니다.
Published rag-notes-app
https://rag-notes-app.your-account.workers.dev
이 주소가 애플리케이션의 API 주소입니다.
사용자 정의 도메인 연결(선택 사항):
Cloudflare에서 관리하는 도메인이 있다면 다음과 같이 연결할 수 있습니다.
wrangler domains add api.yourdomain.com
또는 Dashboard → Workers & Pages → 해당 Worker → Settings → Domains에서 추가할 수 있습니다.
환경 변수와 Secrets:
Anthropic API key나 기타 민감한 정보를 사용한다면 다음 명령을 실행합니다.
wrangler secret put ANTHROPIC_API_KEY
# key 입력
코드에서는 다음과 같이 사용합니다.
const apiKey = c.env.ANTHROPIC_API_KEY;
실제 활용 사례
이 RAG 아키텍처는 다양한 곳에 활용할 수 있습니다. 몇 가지 실제 사례를 소개합니다.
1. 기업 지식 베이스 Q&A
상황: 회사에 수백 페이지의 직원 안내서, 기술 문서, FAQ가 있어 신입 직원이 자료를 찾기 어렵습니다.
구현 방법:
- 모든 문서를 업로드하고 장별로 청킹해 Vectorize에 저장합니다.
- 간단한 Web 인터페이스를 만들거나 기업용 WeChat 봇에 연결합니다.
- 직원이 ‘경비 정산 절차는 무엇인가요?‘라고 물으면 시스템이 관련 부분을 검색해 답합니다.
장점: 하루 24시간 이용할 수 있으며 문서를 직접 뒤지는 것보다 훨씬 빠릅니다.
2. 지능형 고객 지원
상황: 전자상거래 사이트에 상품 정보와 사후 지원 정책이 많아 상담원이 같은 질문에 반복해서 답합니다.
구현 방법:
- 자주 묻는 질문, 상품 설명, 반품 및 교환 정책을 저장합니다.
- 사용자가 문의하면 먼저 RAG 시스템이 답하게 합니다.
- 답하지 못한 질문만 상담원에게 전달합니다.
효과: 한 개발자는 이 구성으로 고객 지원 부담을 60% 이상 줄였습니다.
3. 개인 노트 도우미
상황: Notion이나 Obsidian에 수년 동안 기록한 노트에서 특정 지식을 빠르게 찾고 싶습니다.
구현 방법:
- 노트를 정기적으로 내보내 API를 통해 RAG 시스템에 추가합니다.
- 필요할 때 ‘지난번에 본 TypeScript 팁이 뭐였지?‘라고 바로 물어봅니다.
- 시스템이 관련 노트 조각을 검색합니다.
저도 비슷한 도구를 사용하고 있는데, 자료를 찾는 효율이 실제로 크게 높아졌습니다.
4. ‘Chat with PDF’ 도구
상황: 사용자가 논문, 계약서, 보고서 같은 PDF를 업로드한 뒤 정보를 빠르게 추출하고 싶습니다.
구현 방법(Rohit Patil의 사례 참고):
- 사용자가 PDF를 R2에 업로드합니다.
- Worker가 PDF를 읽고 텍스트를 추출한 뒤 청킹하고 벡터화합니다.
- 사용자는 ‘이 계약서의 결제 조건은 무엇인가요?‘라고 질문할 수 있습니다.
특히 법률과 컨설팅 업계에서 유용한 사례입니다.
자주 발생하는 문제 해결
문제 1: 벡터 차원 불일치
오류: dimension mismatch: expected 768, got 512
원인: Vectorize 인덱스를 생성할 때 설정한 차원(768)과 모델 출력 차원이 일치하지 않습니다.
해결: 인덱스 차원과 모델이 일치하는지 확인합니다. bge-base-en-v1.5는 768차원이므로 모델을 잘못 선택하지 마세요.
문제 2: D1과 Vectorize 데이터 불일치
현상: 검색 결과의 note ID가 D1에 존재하지 않습니다.
원인: D1 레코드를 삭제할 때 Vectorize 데이터를 삭제하지 않았거나 Workflow가 실패했을 수 있습니다.
해결: 삭제 작업을 트랜잭션으로 묶거나 Workflow로 양쪽 데이터를 모두 확실히 삭제합니다.
문제 3: Workflow 시간 초과
오류: workflow execution timeout
원인: 많은 텍스트를 벡터화하는 동안 Workflow의 제한 시간을 초과했습니다.
해결: 큰 문서를 여러 Workflow 작업으로 나누거나 배치로 처리합니다.
// 배치 처리
const batchSize = 10;
for (let i = 0; i < chunks.length; i += batchSize) {
const batch = chunks.slice(i, i + batchSize);
await c.env.RAG_WORKFLOW.create({
params: { noteId, chunks: batch, offset: i }
});
}
결론
지금까지 진행한 내용을 정리해 보겠습니다.
- RAG 원리를 이해했습니다: 검색 증강 생성은 AI에 오픈북 시험을 볼 권한을 주는 방식으로, 먼저 자료를 찾고 나서 답합니다.
- 실행 가능한 애플리케이션을 만들었습니다: 환경 준비부터 코드 구현까지 노트 Q&A 시스템의 전체 과정을 진행했습니다.
- 최적화 방법을 배웠습니다: 텍스트 청킹, 검색 조정, 비용 관리를 통해 애플리케이션을 실제로 쓸 수 있게 다듬었습니다.
- 실제 사례를 살펴봤습니다: 기업 지식 베이스, 지능형 고객 지원, 개인 도우미, PDF 채팅 모두 현실에 적용할 수 있습니다.
Cloudflare 구성의 가장 큰 장점은 진입 장벽이 낮다는 것입니다. GPU를 빌리거나 데이터베이스를 직접 구축할 필요가 없고 운영도 걱정하지 않아도 됩니다. 무료 할당량은 개인 프로젝트에 충분하며, 프로덕션 환경에서도 유료 플랜이 직접 구축하는 것보다 훨씬 저렴할 수 있습니다.
이제 다음 단계로 넘어가 보세요.
- 바로 시작하기: 공식 예제 코드를 복제하고
wrangler dev를 실행하면 5분 안에 결과를 볼 수 있습니다. - 실제 데이터 연결하기: 노트, 문서, FAQ를 가져와 검색 품질을 확인합니다.
- 프론트엔드 인터페이스 만들기: React/Vue로 간단한 채팅 인터페이스를 만들거나 Cloudflare Pages에 바로 배포합니다.
- 더 많은 가능성 탐색하기: 이미지와 표를 결합하는 멀티모달 RAG, 지식 그래프로 강화하는 GraphRAG 같은 고급 기능도 시도해 보세요.
RAG는 현재 AI 애플리케이션에서 가장 실용적인 아키텍처 중 하나입니다. 이를 익히면 다양한 유용한 서비스를 만들 수 있습니다. 직접 사용해 보니 Cloudflare 통합 구성이 여러 현실적인 문제를 해결해 주는 것이 분명했습니다.
문제가 생기면 Cloudflare Discord나 Community 포럼에 질문해 보세요. 커뮤니티가 활발합니다.
FAQ
RAG와 기존 검색 엔진은 무엇이 다른가요?
• 키워드 일치를 기반으로 문서 링크를 반환합니다.
RAG:
• 의미를 이해해 관련 내용을 검색하고 자연어 답변을 생성합니다.
• '고양이는 귀엽다'와 '아기 고양이는 사랑스럽다'가 비슷한 의미임을 이해할 수 있습니다.
• 기존 검색은 같은 키워드만 일치시킬 수 있습니다.
• RAG는 사용자가 여러 문서를 다시 찾아볼 필요 없이 바로 답을 제공합니다.
Cloudflare 무료 플랜은 어느 정도 규모의 지식 베이스를 지원하나요?
• D1 무료 플랜은 10GB 스토리지를 지원합니다.
• Vectorize 무료 플랜은 500만 개의 벡터(약 5GB의 텍스트 콘텐츠)를 지원합니다.
개인 프로젝트와 중소기업의 지식 베이스에는 충분한 수준입니다.
더 큰 용량이 필요하다면 유료 플랜으로 업그레이드하거나 여러 인덱스 샤드에 나누어 저장할 수 있습니다.
RAG의 검색 정확도는 어떻게 높일 수 있나요?
1) 적절한 청킹:
• chunk size 500~1,000자
• overlap 50~100자
2) 매개변수 조정:
• topK 매개변수(보통 3~5개)
• 유사도 임계값(>0.7)
3) AI가 검색된 내용에 근거해 답하도록 Prompt 최적화
4) 더 나은 Embedding 모델 사용(예: OpenAI의 text-embedding-3)
5) 자주 묻는 질문을 위한 FAQ 캐시 구축
RAG 애플리케이션의 비용은 어떻게 관리하나요?
1) 무료 티어 Workers AI 사용(매일 정해진 Neurons 할당량 제공)
2) 사용자 요청 빈도 제한(KV 스토리지 카운터)
3) 자주 묻는 질문의 답변 캐싱
4) 간단한 Q&A에는 Llama 3를 쓰고 복잡한 작업에만 Claude 사용
5) 일일 사용량을 모니터링하고 한도에 가까워지면 LLM 호출 없이 검색 결과만 반환하도록 대체 처리
RAG는 어떤 실제 활용 사례에 적합한가요?
1) 기업 지식 베이스 Q&A(직원 안내서, 기술 문서, 정책 조회)
2) 지능형 고객 지원(상품 문의, 사후 지원 정책, 자주 묻는 질문)
3) 개인 노트 도우미(오랫동안 쌓인 노트를 빠르게 검색)
4) 문서 Q&A 도구(PDF/Word 업로드 후 지능형 정보 추출)
기존 지식을 바탕으로 질문에 답해야 하는 모든 상황에 적용할 수 있습니다.
5분 읽기 · 게시일: 2025년 12월 1일 · 수정일: 2026년 9월 4일



댓글
GitHub로 로그인하여 댓글을 남기세요