llm-integration について
API ベースのアプリを開発する人向けの LLM 統合スキルガイドです。ストリーミング、ツール利用、RAG、文書分割、モデル選択、コスト管理を扱います。
主な機能
- 01LLM API クライアントのパターン
- 02ストリーミング応答
- 03関数呼び出しとツールスキーマ
- 04RAG パイプラインと文書分割
- 05タスクに応じたモデル選択
- 06リトライ、検証、コスト管理
活用シーン
- 01LLM アプリへのストリーミング追加
- 02文書検索パイプラインの構築
- 03モデル選択と重複リクエストの削減
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ rohitg00/ awesome-claude-code-toolkit --skill llm-integrationインストール後、Claude Code で/llm-integrationを入力して読み込まれたか確認します。
詳細
LLM 統合
API クライアントパターン
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
async function generateResponse(
systemPrompt: string,
userMessage: string,
options?: { maxTokens?: number; temperature?: number }
): Promise<string> {
const response = await client.messages.create({
model: "claude-sonnet-4-20250514",
max_tokens: options?.maxTokens ?? 1024,
temperature: options?.temperature ?? 0,
system: systemPrompt,
messages: [{ role: "user", content: userMessage }],
});
const textBlock = response.content.find(block => block.type === "text");
return textBlock?.text ?? "";
}
ストリーミング応答
async function streamResponse(
messages: Array<{ role: "user" | "assistant"; content: string }>,
onChunk: (text: string) => void
): Promise<string> {
const stream = client.messages.stream({
model: "claude-sonnet-4-20250514",
max_tokens: 4096,
messages,
});
let fullText = "";
for await (const event of stream) {
if (event.type === "content_block_delta" && event.delta.type === "text_delta") {
onChunk(event.delta.text);
fullText += event.delta.text;
}
}
return fullText;
}
const response = await streamResponse(
[{ role: "user", content: "Explain async/await in TypeScript" }],
(chunk) => process.stdout.write(chunk)
);
関数呼び出し(ツールの使用)
const tools: Anthropic.Tool[] = [
{
name: "search_database",
description: "Search the product database by name, category, or price range",
input_schema: {
type: "object" as const,
properties: {
query: { type: "string", description: "Search query" },
category: { type: "string", description: "Product category filter" },
max_price: { type: "number", description: "Maximum price" },
},
required: ["query"],
},
},
];
async function agentLoop(userMessage: string): Promise<string> {
const messages: Anthropic.MessageParam[] = [
{ role: "user", content: userMessage },
];
while (true) {
const response = await client.messages.create({
model: "claude-sonnet-4-20250514",
max_tokens: 4096,
tools,
messages,
});
if (response.stop_reason === "end_turn") {
const text = response.content.find(b => b.type === "text");
return text?.text ?? "";
}
const toolUse = response.content.find(b => b.type === "tool_use");
if (!toolUse || toolUse.type !== "tool_use") break;
const result = await executeToolCall(toolUse.name, toolUse.input);
messages.push({ role: "assistant", content: response.content });
messages.push({
role: "user",
content: [{ type: "tool_result", tool_use_id: toolUse.id, content: result }],
});
}
return "";
}
RAG パイプライン
import { embed } from "./embeddings";
interface Chunk {
id: string;
text: string;
metadata: Record<string, string>;
embedding: number[];
}
async function retrieveAndGenerate(query: string): Promise<string> {
const queryEmbedding = await embed(query);
const relevantChunks = await vectorDb.search({
vector: queryEmbedding,
topK: 5,
filter: { source: "documentation" },
});
const context = relevantChunks
.map((chunk, i) => `[${i + 1}] ${chunk.text}`)
.join("\n\n");
const response = await client.messages.create({
model: "claude-sonnet-4-20250514",
max_tokens: 2048,
system: `Answer questions using the provided context. Cite sources with [n] notation. If the context doesn't contain the answer, say so.`,
messages: [
{
role: "user",
content: `Context:\n${context}\n\nQuestion: ${query}`,
},
],
});
return response.content[0].type === "text" ? response.content[0].text : "";
}
ドキュメントのチャンク分割
function chunkDocument(
text: string,
options: { chunkSize: number; overlap: number }
): string[] {
const { chunkSize, overlap } = options;
const chunks: string[] = [];
const sentences = text.split(/(?<=[.!?])\s+/);
let current = "";
for (const sentence of sentences) {
if (current.length + sentence.length > chunkSize && current.length > 0) {
chunks.push(current.trim());
const words = current.split(" ");
const overlapWords = words.slice(-Math.floor(overlap / 5));
current = overlapWords.join(" ") + " " + sentence;
} else {
current += (current ? " " : "") + sentence;
}
}
if (current.trim()) chunks.push(current.trim());
return chunks;
}
コスト最適化
function selectModel(task: TaskType): string {
switch (task) {
case "classification":
case "extraction":
return "claude-haiku-4-20250514";
case "analysis":
case "coding":
return "claude-sonnet-4-20250514";
case "complex-reasoning":
return "claude-opus-4-5-20251101";
default:
return "claude-sonnet-4-20250514";
}
}
許容できる品質を達成できる最小のモデルを使用します。可能な場合は、埋め込みと応答をキャッシュします。レイテンシーが重要でない場合は、リクエストをバッチ処理します。
アンチパターン
- 関連するチャンクだけが必要なのに、ドキュメント全体を送信する
- API 呼び出しで指数バックオフによる再試行ロジックを実装しない
- トークン使用量の追跡を無視する(予期しないコストにつながります)
- 単純な分類タスクに最も高価なモデルを使用する
- コードで使用する前に、LLM の出力を検証またはサニタイズしない
- まず検索品質を評価せずに RAG を構築する
チェックリスト
- API 呼び出しに再試行ロジックとエラー処理を実装する
- ユーザー向けの応答にはストリーミングを使用する
- 関数呼び出しのスキーマに明確な説明を含める
- RAG のチャンクを適切なサイズ(500~1000 トークン)にし、オーバーラップを設ける
- タスクの複雑さに基づいてモデルを選択する
- コスト管理のためにトークン使用量を追跡・監視する
- 後続処理で使用する前に LLM の出力を検証する
- 重複する API 呼び出しを避けるために埋め込みをキャッシュする
コメント
Markdown 対応