ai-integration について
ai-integrationは、アプリ開発者向けのAI連携スキルです。チャットやストリーミングからRAG、ベクトル検索、ツール呼び出しまで扱い、安全性・テスト・検証も案内します。
主な機能
- 01AIプロバイダーとSDKの選定ガイド
- 02チャット画面とストリーミング応答
- 03RAG、埋め込み、ベクトル検索
- 04ツール呼び出し、レート制限、安全対策
- 05テストと連携検証の手順
活用シーン
- 01アプリへのLLMチャット機能の追加
- 02ドキュメント検索と質問応答の構築
- 03外部関数呼び出しのLLM連携
- 04AI機能の安全性とコスト管理の確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ mgd34msu/ goodvibes-plugin --skill ai-integrationインストール後、Claude Code で/ai-integrationを入力して読み込まれたか確認します。
同梱ファイルreferences/ai-patterns.mdscripts/validate-ai-integration.sh
詳細
リソース
scripts/
validate-ai-integration.sh
references/
ai-patterns.md
AI 統合の実装
このスキルでは、基本的なチャットインターフェースから複雑な RAG パイプラインや関数呼び出しまで、アプリケーションに AI と LLM の機能を統合する方法を説明します。GoodVibes の高精度ツールとプロジェクト分析を活用し、本番環境に対応した AI 実装を実現します。
このスキルを使用する場面
次のような場合に、このスキルを使用してください。
- LLM プロバイダーを使用したチャットインターフェースを実装する
- ストリーミング応答機能を追加する
- RAG(検索拡張生成)パイプラインを構築する
- 埋め込みとベクトル検索を統合する
- ツール/関数呼び出しのパターンを実装する
- OpenAI、Anthropic、またはローカルモデルに接続する
- AI API のレート制限とエラーからの復旧に対応する
ワークフロー
AI 統合では、次の手順に従ってください。
1. 既存の AI インフラストラクチャを確認する
AI 機能を実装する前に、現在の状態を把握してください。
detect_stack:
project_root: "."
categories: ["ai", "llm"]
次の項目を特定できます。
- 使用中の AI SDK(Vercel AI SDK、LangChain、LlamaIndex)
- LLM プロバイダー(OpenAI、Anthropic、Cohere)
- ベクトルデータベース(Pinecone、pgvector、Weaviate)
- 既存のチャットまたは補完エンドポイント
- 埋め込み生成のパターン
AI に関する決定事項をプロジェクトのメモリで確認します。
precision_read:
files:
- path: ".goodvibes/memory/decisions.json"
- path: ".goodvibes/memory/patterns.json"
verbosity: minimal
次の項目を探します。
- プロバイダーの選択(「品質を優先し、OpenAI GPT-4 を使用する」)
- コスト最適化のパターン(「埋め込みを 24 時間キャッシュする」)
- ストリーミングの設定(「チャット UI では常にストリーミングする」)
- 既知の問題(「Claude Opus で Anthropic のレート制限が発生する」)
既存の AI パターンを検索します。
precision_grep:
queries:
- id: openai-usage
pattern: "import.*openai|from 'openai'"
glob: "**/*.{ts,tsx,js,jsx}"
- id: streaming-endpoints
pattern: "stream|StreamingTextResponse|toDataStreamResponse"
glob: "**/*.{ts,tsx}"
- id: embeddings
pattern: "embed|embedding|vector"
glob: "**/*.{ts,tsx}"
output:
format: files_only
2. プロバイダーを選択する
要件に基づいて、適切な AI プロバイダーと SDK を選択します。
参照:プロバイダーの比較については references/ai-patterns.md をご覧ください。
主な選択要因:
| 必要な項目 | 推奨 |
|---|---|
| 最高品質のモデル | OpenAI GPT-4 または Anthropic Claude Opus |
| 最速の応答 | OpenAI GPT-4o または Anthropic Claude Haiku |
| コスト効率 | Anthropic Claude Haiku または GPT-4o-mini |
| ローカル/プライバシー | Llama 3 または Mistral を搭載した Ollama |
| 関数呼び出し | OpenAI(最も充実したサポート)または Anthropic |
| 長いコンテキスト | Anthropic Claude(200K トークン) |
| React との統合 | Vercel AI SDK |
| 複雑なワークフロー | LangChain または LlamaIndex |
依存関係をインストールします。
precision_exec:
commands:
- cmd: "npm install ai @ai-sdk/openai @ai-sdk/anthropic zod"
expect:
exit_code: 0
verbosity: minimal
3. API キーを設定する
環境変数を使用して API キーを安全に管理します。
ドキュメントについては .env.example を確認します。
precision_read:
files:
- path: ".env.example"
verbosity: minimal
必要な環境変数を追加します。
precision_edit:
operations:
- file: ".env.example"
edits:
- action: append
content: |
# AI Provider API Keys
OPENAI_API_KEY=your_openai_api_key_here
ANTHROPIC_API_KEY=sk-ant-...
# Optional: Model configuration
AI_MODEL=gpt-4o # or gpt-4o-mini, claude-3-5-sonnet-20241022, etc.
AI_TEMPERATURE=0.7
AI_MAX_TOKENS=4096
verbosity: minimal
ハードコードされたキーがないことを検証します。
precision_grep:
queries:
- id: hardcoded-keys
pattern: "sk-[a-zA-Z0-9]{20,}"
glob: "**/*.{ts,tsx,js,jsx}"
output:
format: count_only
想定される件数は 0 です。見つかった場合は、すぐにキーを環境変数に移してください。
4. チャットインターフェースを実装する
Vercel AI SDK のパターンを使用して、チャットエンドポイントと UI を作成します。
API ルートを作成します。
precision_write:
files:
- path: "src/app/api/chat/route.ts"
mode: fail_if_exists
content: |
import { openai } from '@ai-sdk/openai';
import { streamText } from 'ai';
export const runtime = 'edge'; // Optional: Edge runtime for low-latency responses. Remove if using Node-only modules (ws, fs, etc.)
// Note: Import zod at the top of the file
// import { z } from 'zod';
export async function POST(req: Request) {
// Validate input with Zod (shown here, omitted in later examples for brevity)
const RequestSchema = z.object({
messages: z.array(z.object({
role: z.enum(['user', 'assistant', 'system']),
content: z.string().max(10000),
})),
});
const body = RequestSchema.safeParse(await req.json());
if (!body.success) {
return new Response('Invalid request', { status: 400 });
}
const { messages } = body.data;
const result = streamText({
model: openai('gpt-4o'),
messages,
temperature: 0.7,
maxTokens: 4096,
});
return result.toDataStreamResponse();
}
verbosity: minimal
チャット UI コンポーネントを作成します。
precision_write:
files:
- path: "src/components/chat.tsx"
mode: fail_if_exists
content: |
'use client';
import { useChat } from 'ai/react';
export function Chat() {
const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat();
return (
<div className="flex flex-col h-screen">
<div className="flex-1 overflow-y-auto p-4 space-y-4">
{messages.map((m) => (
<div key={m.id} className={`flex ${m.role === 'user' ? 'justify-end' : 'justify-start'}`}>
<div className={`max-w-md px-4 py-2 rounded-lg ${
m.role === 'user' ? 'bg-blue-500 text-white' : 'bg-gray-200'
}`}>
{m.content}
</div>
</div>
))}
{isLoading && <div className="text-gray-500">Thinking...</div>}
</div>
<form onSubmit={handleSubmit} className="border-t p-4">
<input
value={input}
onChange={handleInputChange}
placeholder="Type a message..."
className="w-full px-4 py-2 border rounded-lg"
disabled={isLoading}
/>
</form>
</div>
);
}
verbosity: minimal
実装を検証します。
precision_exec:
commands:
- cmd: "npm run typecheck"
expect:
exit_code: 0
verbosity: minimal
5. ストリーミング応答を実装する
トークン単位のストリーミングでチャットの UX を最適化します。
Vercel AI SDK には streamText() と useChat() によるストリーミング機能が組み込まれています。上記のパターンでは、すでにストリーミングが行われます。
独自のストリーミングを実装する場合:
precision_write:
files:
- path: "src/lib/streaming.ts"
mode: fail_if_exists
content: |
import OpenAI from 'openai';
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
export async function streamCompletion(prompt: string) {
const stream = await openai.chat.completions.create({
model: 'gpt-4o',
messages: [{ role: 'user', content: prompt }],
stream: true,
});
const encoder = new TextEncoder();
return new ReadableStream({
async start(controller) {
for await (const chunk of stream) {
const content = chunk.choices[0]?.delta?.content || '';
if (content) {
controller.enqueue(encoder.encode(content));
}
}
controller.close();
},
});
}
verbosity: minimal
Server-Sent Events(SSE)を使用する方法:
precision_write:
files:
- path: "src/app/api/stream/route.ts"
mode: fail_if_exists
content: |
import OpenAI from 'openai';
export const runtime = 'edge'; // Optional: Edge runtime for low-latency responses. Remove if using Node-only modules (ws, fs, etc.)
export async function POST(req: Request) {
// Add Zod validation as shown in first API route example
const { prompt } = await req.json();
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
const stream = await openai.chat.completions.create({
model: 'gpt-4o',
messages: [{ role: 'user', content: prompt }],
stream: true,
});
const encoder = new TextEncoder();
return new Response(
new ReadableStream({
async start(controller) {
for await (const chunk of stream) {
const content = chunk.choices[0]?.delta?.content || '';
if (content) {
controller.enqueue(encoder.encode(`data: ${JSON.stringify({ content })}\n\n`));
}
}
controller.enqueue(encoder.encode('data: [DONE]\n\n'));
controller.close();
},
}),
{
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
},
}
);
}
verbosity: minimal
6. RAG パイプラインを構築する
ドキュメントの取り込み、チャンク分割、埋め込み、検索を実装します。
ステップ 6.1:ドキュメントの取り込み
precision_write:
files:
- path: "src/lib/rag/ingestion.ts"
mode: fail_if_exists
content: |
import { RecursiveCharacterTextSplitter } from 'langchain/text_splitter';
export async function chunkDocument(text: string, metadata: Record<string, unknown> = {}) {
const splitter = new RecursiveCharacterTextSplitter({
chunkSize: 1000,
chunkOverlap: 200,
separators: ['\n\n', '\n', ' ', ''],
});
const chunks = await splitter.createDocuments([text], [metadata]);
return chunks.map((chunk, index) => ({
content: chunk.pageContent,
metadata: { ...chunk.metadata, chunk_index: index },
}));
}
verbosity: minimal
ステップ 6.2:埋め込みの生成
precision_write:
files:
- path: "src/lib/rag/embeddings.ts"
mode: fail_if_exists
content: |
import { embed } from 'ai';
import { openai } from '@ai-sdk/openai';
export async function generateEmbedding(text: string): Promise<number[]> {
const { embedding } = await embed({
model: openai.embedding('text-embedding-3-small'),
value: text,
});
return embedding;
}
export async function generateEmbeddings(texts: string[]): Promise<number[][]> {
return Promise.all(texts.map(generateEmbedding));
}
verbosity: minimal
ステップ 6.3:ベクトルストレージ(Pinecone の例)
precision_write:
files:
- path: "src/lib/rag/vector-store.ts"
mode: fail_if_exists
content: |
import { Pinecone } from '@pinecone-database/pinecone';
import { generateEmbedding } from './embeddings';
// Validate env vars at startup (example shown here, subsequent ! usage for brevity)
const apiKey = process.env.PINECONE_API_KEY;
if (!apiKey) throw new Error('PINECONE_API_KEY is required');
const pinecone = new Pinecone({ apiKey });
const index = pinecone.index('documents');
export async function storeDocument(id: string, text: string, metadata: Record<string, unknown>) {
const embedding = await generateEmbedding(text);
await index.upsert([{
id,
values: embedding,
metadata: { text, ...metadata },
}]);
}
export async function searchSimilar(query: string, topK: number = 5) {
const queryEmbedding = await generateEmbedding(query);
const results = await index.query({
vector: queryEmbedding,
topK,
includeMetadata: true,
});
return results.matches || [];
}
verbosity: minimal
ステップ 6.4:RAG 補完エンドポイント
precision_write:
files:
- path: "src/app/api/rag/route.ts"
mode: fail_if_exists
content: |
import { openai } from '@ai-sdk/openai';
import { streamText } from 'ai';
import { searchSimilar } from '@/lib/rag/vector-store';
export const runtime = 'edge'; // Optional: Edge runtime for low-latency responses. Remove if using Node-only modules (ws, fs, etc.)
export async function POST(req: Request) {
// Add Zod validation as shown in first API route example
const { messages } = await req.json();
const lastMessage = messages[messages.length - 1];
// Retrieve relevant context
const context = await searchSimilar(lastMessage.content, 3);
const contextText = context
.map((match) => match.metadata?.text || '')
.join('\n\n');
// Augment prompt with context
const augmentedMessages = [
{
role: 'system',
content: `You are a helpful assistant. Use the following context to answer the user's question:\n\n${contextText}`,
},
...messages,
];
const result = streamText({
model: openai('gpt-4o'),
messages: augmentedMessages,
});
return result.toDataStreamResponse();
}
verbosity: minimal
7. ベクトル検索を実装する
セマンティック検索用にベクトルデータベースを設定します。
プロバイダーの比較:
ベクトルデータベースの比較については references/ai-patterns.md をご覧ください。
Pinecone(マネージド):
precision_exec:
commands:
- cmd: "npm install @pinecone-database/pinecone"
expect:
exit_code: 0
verbosity: minimal
pgvector(PostgreSQL 拡張機能):
precision_write:
files:
- path: "src/lib/vector/pgvector.ts"
mode: fail_if_exists
content: |
import { sql } from 'drizzle-orm';
import { db } from '@/lib/db';
export async function enablePgvector() {
await db.execute(sql`CREATE EXTENSION IF NOT EXISTS vector`);
}
export async function searchSimilar(embedding: number[], limit: number = 5) {
return db.execute(sql`
SELECT id, content, metadata, 1 - (embedding <=> ${embedding}::vector) AS similarity
FROM documents
ORDER BY embedding <=> ${embedding}::vector
LIMIT ${limit}
`);
}
verbosity: minimal
Weaviate(ハイブリッド検索):
precision_write:
files:
- path: "src/lib/vector/weaviate.ts"
mode: fail_if_exists
content: |
import weaviate, { WeaviateClient } from 'weaviate-ts-client';
const client: WeaviateClient = weaviate.client({
scheme: 'https',
host: process.env.WEAVIATE_HOST!,
apiKey: { apiKey: process.env.WEAVIATE_API_KEY! },
});
export async function hybridSearch(query: string, limit: number = 5) {
return client.graphql
.get()
.withClassName('Document')
.withHybrid({ query, alpha: 0.5 })
.withFields('content metadata _additional { score }')
.withLimit(limit)
.do();
}
verbosity: minimal
8. ツール/関数呼び出しを実装する
LLM が外部関数やツールを呼び出せるようにします。
Zod スキーマを使用してツールを定義します。
precision_write:
files:
- path: "src/lib/ai/tools.ts"
mode: fail_if_exists
content: |
import { z } from 'zod';
import { tool } from 'ai';
export const weatherTool = tool({
description: 'Get the current weather for a location',
parameters: z.object({
location: z.string().describe('City name or zip code'),
unit: z.enum(['celsius', 'fahrenheit']).default('celsius'),
}),
execute: async ({ location, unit }) => {
// Call weather API using URL constructor for safe param encoding
const url = new URL('https://api.weather.com/v1/current');
url.searchParams.set('location', location);
url.searchParams.set('unit', unit);
const response = await fetch(url);
return response.json();
},
});
export const calculatorTool = tool({
description: 'Perform mathematical calculations',
parameters: z.object({
expression: z.string().describe('Mathematical expression to evaluate'),
}),
execute: async ({ expression }) => {
// Safe eval using mathjs
try {
const { evaluate } = await import('mathjs');
const result = evaluate(expression);
return { result };
} catch (error) {
return { error: 'Invalid expression' };
}
},
});
verbosity: minimal
補完処理でツールを使用します。
precision_write:
files:
- path: "src/app/api/chat-tools/route.ts"
mode: fail_if_exists
content: |
import { openai } from '@ai-sdk/openai';
import { streamText } from 'ai';
import { weatherTool, calculatorTool } from '@/lib/ai/tools';
export const runtime = 'edge'; // Optional: Edge runtime for low-latency responses. Remove if using Node-only modules (ws, fs, etc.)
export async function POST(req: Request) {
// Add Zod validation as shown in first API route example
const { messages } = await req.json();
const result = streamText({
model: openai('gpt-4o'),
messages,
tools: {
weather: weatherTool,
calculator: calculatorTool,
},
maxSteps: 5, // Allow multi-step tool use
});
return result.toDataStreamResponse();
}
verbosity: minimal
UI でツールの結果を処理します。
precision_edit:
operations:
- file: "src/components/chat.tsx"
edits:
- action: replace
old_text: "export function Chat() {"
new_text: |
export function Chat() {
const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({
onToolCall: ({ toolCall }) => {
// Use proper logger in production: logger.info('Tool called:', toolCall.toolName, toolCall.args);
console.log('Tool called:', toolCall.toolName, toolCall.args);
},
});
verbosity: minimal
9. セキュリティとレート制限の追加
不正利用を防ぎ、コストを管理します。
レート制限ミドルウェア:
precision_write:
files:
- path: "src/middleware/rate-limit.ts"
mode: fail_if_exists
content: |
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';
const redis = new Redis({
url: process.env.UPSTASH_REDIS_REST_URL!,
token: process.env.UPSTASH_REDIS_REST_TOKEN!,
});
export const ratelimit = new Ratelimit({
redis,
limiter: Ratelimit.slidingWindow(10, '60 s'), // 10 requests per minute
analytics: true,
});
export async function checkRateLimit(identifier: string) {
const { success, limit, remaining, reset } = await ratelimit.limit(identifier);
if (!success) {
throw new Error(`Rate limit exceeded. Try again in ${Math.ceil((reset - Date.now()) / 1000)}s`);
}
return { limit, remaining, reset };
}
verbosity: minimal
ルートへのレート制限の適用:
precision_edit:
operations:
- file: "src/app/api/chat/route.ts"
edits:
- action: replace
old_text: "export async function POST(req: Request) {"
new_text: |
import { checkRateLimit } from '@/middleware/rate-limit';
export async function POST(req: Request) {
const ip = req.headers.get('x-forwarded-for') || 'anonymous';
try {
await checkRateLimit(ip);
} catch (error) {
const message = error instanceof Error ? error.message : 'Rate limit exceeded';
return new Response(message, { status: 429 });
}
verbosity: minimal
プロンプトインジェクションの防止:
precision_write:
files:
- path: "src/lib/ai/safety.ts"
mode: fail_if_exists
content: |
// Detect prompt injection attempts
export function detectInjection(input: string): boolean {
const injectionPatterns = [
/ignore (previous|above|all) (instructions|prompts)/i,
/you are now/i,
/new instructions:/i,
/system: /i,
/<\w+>/, // HTML-like tags
];
return injectionPatterns.some((pattern) => pattern.test(input));
}
// Sanitize user input
export function sanitizeInput(input: string): string {
// Remove potential injection markers
return input
.replace(/[<>]/g, '')
.replace(/system:/gi, '')
.trim()
.slice(0, 10000); // Limit length
}
verbosity: minimal
コンテンツフィルタリング:
precision_write:
files:
- path: "src/lib/ai/moderation.ts"
mode: fail_if_exists
content: |
import OpenAI from 'openai';
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
export async function moderateContent(text: string) {
const moderation = await openai.moderations.create({ input: text });
const result = moderation.results[0];
if (result.flagged) {
const categories = Object.entries(result.categories)
.filter(([_, flagged]) => flagged)
.map(([category]) => category);
throw new Error(`Content flagged for: ${categories.join(', ')}`);
}
return result;
}
verbosity: minimal
10. AI 統合のテスト
決定論的なテスト戦略を実装します。
LLM の応答をモックする:
precision_write:
files:
- path: "src/lib/ai/__tests__/chat.test.ts"
mode: fail_if_exists
content: |
import { describe, it, expect, vi } from 'vitest';
import { streamText } from 'ai';
vi.mock('ai', () => ({
streamText: vi.fn(),
}));
describe('Chat API', () => {
it('should return streamed response', async () => {
const mockStream = {
toDataStreamResponse: () => new Response('mocked response'),
};
vi.mocked(streamText).mockResolvedValue(mockStream);
import { POST } from '@/app/api/chat/route';
const req = new Request('http://localhost/api/chat', {
method: 'POST',
body: JSON.stringify({ messages: [{ role: 'user', content: 'Hello' }] }),
});
const response = await POST(req);
expect(response.ok).toBe(true);
});
});
verbosity: minimal
決定論的なシードを使用したテスト:
precision_write:
files:
- path: "src/lib/ai/__tests__/embeddings.test.ts"
mode: fail_if_exists
content: |
import { describe, it, expect } from 'vitest';
import { generateEmbedding } from '../embeddings';
describe('Embeddings', () => {
// Mock AI SDK to prevent real API calls in tests
vi.mock('ai', () => ({
embed: vi.fn().mockResolvedValue({ embedding: new Array(1536).fill(0.1) })
}));
it('should generate consistent embeddings', async () => {
const text = 'Hello world';
const embedding1 = await generateEmbedding(text);
const embedding2 = await generateEmbedding(text);
// Embeddings should be very similar (cosine similarity)
const similarity = cosineSimilarity(embedding1, embedding2);
expect(similarity).toBeGreaterThan(0.99);
});
});
function cosineSimilarity(a: number[], b: number[]): number {
const dotProduct = a.reduce((sum, val, i) => sum + val * b[i], 0);
const magnitudeA = Math.sqrt(a.reduce((sum, val) => sum + val * val, 0));
const magnitudeB = Math.sqrt(b.reduce((sum, val) => sum + val * val, 0));
return dotProduct / (magnitudeA * magnitudeB);
}
verbosity: minimal
コストの追跡:
precision_write:
files:
- path: "src/lib/ai/cost-tracker.ts"
mode: fail_if_exists
content: |
import { Redis } from '@upstash/redis';
const redis = new Redis({
url: process.env.UPSTASH_REDIS_REST_URL!,
token: process.env.UPSTASH_REDIS_REST_TOKEN!,
});
const MODEL_COSTS = {
// Prices as of early 2026 -- verify current rates at provider sites
'gpt-4o': { input: 0.0025, output: 0.01 }, // per 1K tokens
'gpt-4o-mini': { input: 0.00015, output: 0.0006 },
'claude-opus-4': { input: 0.015, output: 0.075 },
'claude-sonnet-4-5': { input: 0.003, output: 0.015 },
'claude-haiku-4-5': { input: 0.0008, output: 0.004 },
};
export async function trackUsage(
model: keyof typeof MODEL_COSTS,
inputTokens: number,
outputTokens: number
) {
const costs = MODEL_COSTS[model];
const cost = (inputTokens / 1000) * costs.input + (outputTokens / 1000) * costs.output;
const today = new Date().toISOString().split('T')[0];
const key = `ai:cost:${today}`;
await redis.incrbyfloat(key, cost);
await redis.expire(key, 86400 * 90); // 90 days retention
return cost;
}
export async function getTodayCost(): Promise<number> {
const today = new Date().toISOString().split('T')[0];
const cost = await redis.get<number>(`ai:cost:${today}`);
return cost || 0;
}
verbosity: minimal
11. 検証
検証スクリプトを実行して、AI 統合に関するすべての要件が満たされていることを確認します。
precision_exec:
commands:
- cmd: "bash plugins/goodvibes/skills/outcome/ai-integration/scripts/validate-ai-integration.sh ."
expect:
exit_code: 0
verbosity: standard
検証スクリプトでは、次の項目を確認します。
- AI SDK または LLM ライブラリがインストールされている
- API キーが .env.example に記載されている
- ストリーミングエンドポイントが存在する
- ソースコードに API キーがハードコードされていない
- レート制限が設定されている
- AI の呼び出しにエラーハンドリングが実装されている
- トークン/コストの追跡パターンが実装されている
デプロイする前に、違反項目を修正してください。
ベストプラクティス
エラーハンドリング
- AI の呼び出しは必ず try/catch ブロックで囲みます
- 指数バックオフを使用してレート制限に対処します
- 外部 API にサーキットブレーカーを実装します
- API キーを漏えいさせずにエラーをログに記録します
パフォーマンス
- 頻繁にアクセスされるコンテンツの埋め込みをキャッシュします
- 長い応答を生成する場合はストリーミングを使用します
- 可能であれば、埋め込みの生成をバッチ処理します
- リクエストの重複排除を実装します
コスト管理
- リクエストごとに最大トークン数を設定します
- 日次/月次の支出を追跡します
- 単純なタスクには、より安価なモデルを使用します
- 適切な場合は、生成結果をキャッシュします
セキュリティ
- API キーをソース管理に絶対にコミットしないでください
- ユーザー/IP ごとにレート制限を実装します
- LLM に送信する前にユーザー入力をサニタイズします
- ユーザーが作成したプロンプトにはコンテンツモデレーションを使用します
- ツールの実行結果を検証します
モニタリング
- トークンの使用量とコストを追跡します
- 応答のレイテンシを監視します
- 成功したリクエストと失敗したリクエストをログに記録します
- 異常な支出パターンを検知したらアラートを出します
よくある落とし穴
- ストリーミングを忘れる:チャットインターフェースの UX を向上させるため、常にストリーミングを使用します
- レート制限を無視する:バックオフと再試行のロジックを実装します
- プロンプトをハードコードする:保守性を高めるため、テンプレートシステムを使用します
- ドキュメントをチャンク分割しない:RAG で大きなドキュメントを扱うにはチャンク分割が必要です
- コスト追跡を省略する:AI のコストはすぐに膨らむ可能性があります
- ユーザー入力を信頼する:必ずサニタイズして検証します
- エラーバウンダリがない:障害が発生しても、機能を適切に縮退させる必要があります
- コンテキストが大きすぎる:トークンの上限内に収まるよう、コンテキストを制限します
追加リソース
プロバイダーの詳細な比較とアーキテクチャパターンについては、references/ai-patterns.md を参照してください。