メインコンテンツへスキップ

Chat APIによるRAG(推奨)

Chat APImemoryオプションを使えば、コンテキスト取得・プロンプト構築・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 APIgetContext()を呼び出し、結果を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

コンテキスト組み立ての詳細