Chat API로 RAG 구현 (권장)
Chat API의memory 옵션을 사용하면, SDK가 컨텍스트 가져오기, 프롬프트 구성, LLM 호출을 일괄 처리합니다. RAG 파이프라인을 직접 구축할 필요가 없습니다.
import { NdxClient } from '@neuradex/sdk';
const client = new NdxClient({
apiKey: process.env.NEURADEX_API_KEY,
projectId: process.env.NEURADEX_PROJECT_ID,
});
async function ragAnswer(question: string): Promise<string> {
const stream = client.chat.create({
model: 'gpt-4o',
messages: [
{
role: 'system',
content: '다음 정보를 참고하여 질문에 답변해 주세요. 정보가 부족한 경우 솔직히 말씀해 주세요.',
},
{ role: 'user', content: question },
],
memory: {
enabled: true,
maxTokens: 3000,
includeEpisodes: true,
},
});
return await stream.text;
}
Chat API는 내부적으로 Memory API의
getContext()를 호출하고, 결과를 system 메시지에 자동 삽입합니다. 위의 코드만으로 아래 수동 RAG 파이프라인과 동일한 처리가 실현됩니다.도구 자동 실행 에이전트
Chat API의 도구 자동 실행을 사용하면, LLM이 도구를 호출하고 결과를 받아 재추론하는 루프를 SDK가 자동 처리합니다.const stream = client.chat.create({
model: 'gpt-4o',
messages: [
{ role: 'system', content: '당신은 사내 헬프데스크입니다.' },
{ role: 'user', content: '회의실 A의 오늘 예약 현황을 확인해 줘' },
],
tools: {
checkRoomAvailability: {
description: '회의실 예약 현황을 확인합니다',
parameters: {
type: 'object',
properties: {
room: { type: 'string', description: '회의실명' },
date: { type: 'string', format: 'date' },
},
required: ['room'],
},
execute: async ({ room, date }) => {
const bookings = await roomApi.getBookings(room, date);
return JSON.stringify(bookings);
},
},
},
memory: { enabled: true },
maxToolRoundtrips: 3,
});
// 도구 실행 상황도 실시간 추적 가능
for await (const event of stream.fullStream) {
if (event.type === 'tool-call') console.log(`실행 중: ${event.name}`);
if (event.type === 'text-delta') process.stdout.write(event.textDelta);
}
RAG 파이프라인 (수동 구성)
외부 LLM과 조합하여 RAG(Retrieval Augmented Generation)를 구현하는 전형적인 패턴입니다. Memory API를 통한 컨텍스트 가져오기와 Episodes API를 통한 Q&A 이력 기록을 사용합니다.import { NdxClient } from '@neuradex/sdk';
import OpenAI from 'openai';
const neuradex = new NdxClient({
apiKey: process.env.NEURADEX_API_KEY,
projectId: process.env.NEURADEX_PROJECT_ID,
});
const openai = new OpenAI();
async function ragAnswer(question: string): Promise<string> {
// 1. 컨텍스트 가져오기
const context = await neuradex.memory.getContext(question, {
tokenBudget: 3000,
includeEpisodes: true,
maxDepth: 2,
});
// 2. LLM으로 답변 생성
const response = await openai.chat.completions.create({
model: 'gpt-4o',
messages: [
{
role: 'system',
content: `다음 정보를 참고하여 질문에 답변해 주세요.
정보가 부족한 경우 솔직히 말씀해 주세요.
${context.formatted}`,
},
{ role: 'user', content: question },
],
});
const answer = response.choices[0].message.content ?? '';
// 3. Q&A를 에피소드로 기록 (학습용)
const questionEpisode = await neuradex.episodes.create({
actorType: 'user',
episodeType: 'question',
content: question,
scopeType: 'project',
scopeId: process.env.NEURADEX_PROJECT_ID,
channel: 'api',
});
await neuradex.episodes.create({
actorType: 'agent',
episodeType: 'answer',
content: answer,
scopeType: 'project',
scopeId: process.env.NEURADEX_PROJECT_ID,
channel: 'api',
parentEpisodeId: questionEpisode.id,
});
return answer;
}
고객 지원 봇
대화 이력을 유지하고 문맥을 고려한 답변을 생성하는 패턴입니다. 참조 정보에 Knowledge를 활용합니다.interface ChatSession {
sessionId: string;
userId: string;
}
async function handleCustomerQuery(
query: string,
session: ChatSession
): Promise<{ answer: string; sources: string[] }> {
// 1. 세션의 과거 대화 가져오기
const history = await neuradex.episodes.getBySession(session.sessionId);
// 2. 컨텍스트 가져오기
const context = await neuradex.memory.getContext(query, {
tokenBudget: 4000,
includeEpisodes: true,
});
// 3. 대화 이력과 컨텍스트를 조합하여 프롬프트 구성
const conversationHistory = history.data
.map(e => `${e.episodeType === 'question' ? 'Customer' : 'Agent'}: ${e.content}`)
.join('\n');
const response = await openai.chat.completions.create({
model: 'gpt-4o',
messages: [
{
role: 'system',
content: `당신은 친절한 고객 지원 담당자입니다.
## 참고 정보
${context.formatted}
## 지금까지의 대화
${conversationHistory}`,
},
{ role: 'user', content: query },
],
});
const answer = response.choices[0].message.content ?? '';
// 4. 대화 기록
const questionEpisode = await neuradex.episodes.create({
actorType: 'user',
actorId: session.userId,
episodeType: 'question',
content: query,
scopeType: 'project',
scopeId: process.env.NEURADEX_PROJECT_ID,
channel: 'widget',
sessionId: session.sessionId,
});
await neuradex.episodes.create({
actorType: 'agent',
actorName: 'Support Bot',
episodeType: 'answer',
content: answer,
scopeType: 'project',
scopeId: process.env.NEURADEX_PROJECT_ID,
channel: 'widget',
sessionId: session.sessionId,
parentEpisodeId: questionEpisode.id,
});
// 5. 참조한 지식 제목 반환
const sources = context.items
.filter(item => item.type === 'knowledge')
.map(item => item.title ?? item.id);
return { answer, sources };
}
FAQ 일괄 가져오기
CSV나 JSON에서 FAQ를 일괄로 가져오는 패턴입니다.import { parse } from 'csv-parse/sync';
import fs from 'fs';
interface FaqRecord {
question: string;
answer: string;
category: string;
}
async function importFaqFromCsv(filePath: string) {
const csv = fs.readFileSync(filePath, 'utf-8');
const records: FaqRecord[] = parse(csv, { columns: true });
// 청크로 분할하여 처리 (대량 데이터 대응)
const chunkSize = 50;
const chunks = [];
for (let i = 0; i < records.length; i += chunkSize) {
chunks.push(records.slice(i, i + chunkSize));
}
let totalCreated = 0;
for (const chunk of chunks) {
const items = chunk.map(record => ({
title: record.question,
content: record.answer,
tags: ['faq', record.category],
}));
const created = await neuradex.knowledge.bulkCreate(items);
totalCreated += created.length;
console.log(`진행: ${totalCreated}/${records.length}`);
}
console.log(`가져오기 완료: ${totalCreated}건`);
}
2단계 검색 패턴
가벼운 검색 결과를 가져온 후 필요에 따라 상세를 가져오는 패턴입니다.search()는 제목, 태그, 점수만 반환하므로 빠릅니다.
전체 콘텐츠가 필요한 경우에만 get()을 호출하여 성능을 최적화할 수 있습니다.async function searchWithDetails(query: string, topK: number = 5) {
// 1. 가벼운 검색 (빠름)
const results = await neuradex.knowledge.search(query, { limit: topK });
console.log(`${results.length}건 발견`);
// 2. 점수가 높은 것만 상세 가져오기
const threshold = 0.7;
const relevantResults = results.filter(r => r.score >= threshold);
const detailedResults = await Promise.all(
relevantResults.map(async result => {
const detail = await neuradex.knowledge.get(result.id);
return {
...result,
content: detail.content,
connectedKnowledge: detail.connectedKnowledge,
};
})
);
return detailedResults;
}
에러 핸들링
프로덕션 환경에서 권장되는 에러 핸들링 패턴입니다.interface ActionResult<T> {
data?: T;
error?: string;
}
async function safeApiCall<T>(
fn: () => Promise<T>
): Promise<ActionResult<T>> {
try {
const data = await fn();
return { data };
} catch (error) {
const message = error instanceof Error ? error.message : 'Unknown error';
console.error('API Error:', message);
return { error: message };
}
}
// 사용 예
async function searchKnowledge(query: string) {
const result = await safeApiCall(() =>
neuradex.knowledge.search(query, { limit: 10 })
);
if (result.error) {
// 에러 처리 (UI에 표시, 재시도 등)
return [];
}
return result.data;
}
베스트 프랙티스
토큰 예산 최적화
토큰 예산 최적화
- 작은 모델 (GPT-3.5 등):
tokenBudget: 2000 - 큰 모델 (GPT-4 등):
tokenBudget: 4000-8000 - 에피소드가 필요 없으면
includeEpisodes: false로 비활성화
검색 결과 활용
검색 결과 활용
- 먼저
search()로 후보를 좁히기 - 필요한 경우에만
get()으로 상세 가져오기 memory.getContext()는 복잡한 질문에 최적
에피소드 활용
에피소드 활용
- Q&A를 기록하여 학습 데이터로 활용
sessionId로 대화 그룹화parentEpisodeId로 질문과 답변 연결
배치 처리
배치 처리
- 대량 데이터는
bulkCreate()로 청크 분할 - 업데이트 후
tasks.registerReindex()로 인덱스 재구축
다음 단계
Chat API
메모리 기반 Chat Completions
Knowledge API
지식 조작 상세
Memory API
컨텍스트 조립 상세

