Chat APIによるRAG(推奨)
Chat APIのmemoryオプションを使えば、コンテキスト取得・プロンプト構築・LLM呼び出しをSDKが一括で処理します。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;
}
カスタマーサポートBot
会話履歴を保持し、文脈を考慮した回答を生成するパターンです。参照情報には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}件`);
}
// JSONからのインポート
async function importFaqFromJson(filePath: string) {
const data = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
const items = data.map((item: any) => ({
title: item.title,
content: item.content,
tags: item.tags || ['imported'],
}));
const created = await neuradex.knowledge.bulkCreate(items);
console.log(`インポート完了: ${created.length}件`);
}
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;
}
知識の変更履歴追跡
ナレッジの変更履歴を追跡し、誰がいつ何を変更したかを把握するパターンです。async function getKnowledgeAuditLog(knowledgeId: string) {
const history = await neuradex.knowledge.getHistory(knowledgeId);
for (const episode of history) {
console.log(`[${episode.occurredAt}] ${episode.episodeType}`);
console.log(` 実行者: ${episode.actorName || 'System'}`);
console.log(` 理由: ${episode.changeReason || '-'}`);
if (episode.previousValue) {
console.log(` 変更前: ${JSON.stringify(episode.previousValue)}`);
}
}
}
ベストプラクティス
トークン予算の最適化
トークン予算の最適化
- 小さいモデル(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
コンテキスト組み立ての詳細

