mcpField
L
llm-patterns

llm-patterns

github.com/alinaqi開発711293 回インストール2 週間前に更新
リポジトリ

llm-patterns について

llm-patternsは、論理処理にLLMを使うAIアプリケーションの開発者向けガイドです。プロンプトの整理、構造化出力、テストと評価を扱い、検証やデータ処理、オーケストレーションは通常のコードに任せます。

主な機能

  1. 01LLMとコードの役割分担
  2. 02型付きクライアントと構造化出力
  3. 03プロンプトのテンプレート化とバージョン管理
  4. 04モック、フィクスチャ、評価テスト
  5. 05トークン使用量、コスト、遅延の追跡

活用シーン

  1. 01分類、抽出、要約機能の開発
  2. 02LLM応答のスキーマ検証
  3. 03プロンプトやモデル変更の影響測定

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/alinaqi/maggy --skill llm-patterns

インストール後、Claude Code で/llm-patternsを入力して読み込まれたか確認します。

詳細

LLM パターンのスキル

LLM が論理処理を担う、AI ファーストのアプリケーション向けです。


基本原則

ロジックには LLM、配管処理にはコードを使用します。

LLM を使用する処理:

  • 分類、抽出、要約
  • 自然言語による推論を伴う意思決定
  • コンテンツの生成と変換
  • コードでは脆弱になりやすい複雑な条件分岐

従来のコードを使用する処理:

  • データ検証(Zod/Pydantic)
  • API ルーティングと HTTP 処理
  • データベース操作
  • 認証/認可
  • オーケストレーションとエラー処理

プロジェクト構成

project/
├── src/
│   ├── core/
│   │   ├── prompts/           # Prompt templates
│   │   │   ├── classify.ts
│   │   │   └── extract.ts
│   │   ├── llm/               # LLM client and utilities
│   │   │   ├── client.ts      # LLM client wrapper
│   │   │   ├── schemas.ts     # Response schemas (Zod)
│   │   │   └── index.ts
│   │   └── services/          # Business logic using LLM
│   ├── infra/
│   └── ...
├── tests/
│   ├── unit/
│   ├── integration/
│   └── llm/                   # LLM-specific tests
│       ├── fixtures/          # Saved responses for deterministic tests
│       ├── evals/             # Evaluation test suites
│       └── mocks/             # Mock LLM responses
└── _project_specs/
    └── prompts/               # Prompt specifications

LLM クライアントのパターン

型付き LLM ラッパー
// core/llm/client.ts
import Anthropic from '@anthropic-ai/sdk';
import { z } from 'zod';

const client = new Anthropic();

interface LLMCallOptions<T> {
  prompt: string;
  schema: z.ZodSchema<T>;
  model?: string;
  maxTokens?: number;
}

export async function llmCall<T>({
  prompt,
  schema,
  model = 'claude-sonnet-4-6',
  maxTokens = 1024,
}: LLMCallOptions<T>): Promise<T> {
  const response = await client.messages.create({
    model,
    max_tokens: maxTokens,
    messages: [{ role: 'user', content: prompt }],
  });

  const text = response.content[0].type === 'text'
    ? response.content[0].text
    : '';

  // Parse and validate response
  const parsed = JSON.parse(text);
  return schema.parse(parsed);
}
構造化出力
// core/llm/schemas.ts
import { z } from 'zod';

export const ClassificationSchema = z.object({
  category: z.enum(['support', 'sales', 'feedback', 'other']),
  confidence: z.number().min(0).max(1),
  reasoning: z.string(),
});

export type Classification = z.infer<typeof ClassificationSchema>;

プロンプトのパターン

テンプレート関数
// core/prompts/classify.ts
export function classifyTicketPrompt(ticket: string): string {
  return `Classify this support ticket into one of these categories:
- support: Technical issues or help requests
- sales: Pricing, plans, or purchase inquiries
- feedback: Suggestions or complaints
- other: Anything else

Respond with JSON:
{
  "category": "...",
  "confidence": 0.0-1.0,
  "reasoning": "brief explanation"
}

Ticket:
${ticket}`;
}
プロンプトのバージョン管理
// core/prompts/index.ts
export const PROMPTS = {
  classify: {
    v1: classifyTicketPromptV1,
    v2: classifyTicketPromptV2,  // improved accuracy
    current: classifyTicketPromptV2,
  },
} as const;

LLM 呼び出しのテスト

1. モックを使った単体テスト(高速、決定論的)
// tests/llm/mocks/classify.mock.ts
export const mockClassifyResponse = {
  category: 'support',
  confidence: 0.95,
  reasoning: 'User is asking for help with login',
};

// tests/unit/services/ticket.test.ts
import { classifyTicket } from '../../../src/core/services/ticket';
import { mockClassifyResponse } from '../../llm/mocks/classify.mock';

// Mock the LLM client
vi.mock('../../../src/core/llm/client', () => ({
  llmCall: vi.fn().mockResolvedValue(mockClassifyResponse),
}));

describe('classifyTicket', () => {
  it('returns classification for ticket', async () => {
    const result = await classifyTicket('I cannot log in');

    expect(result.category).toBe('support');
    expect(result.confidence).toBeGreaterThan(0.9);
  });
});
2. フィクスチャテスト(決定論的、解析をテスト)
// tests/llm/fixtures/classify.fixtures.json
{
  "support_ticket": {
    "input": "I can't reset my password",
    "expected_category": "support",
    "raw_response": "{\"category\":\"support\",\"confidence\":0.98,\"reasoning\":\"Password reset is a support issue\"}"
  }
}

// tests/llm/classify.fixture.test.ts
import fixtures from './fixtures/classify.fixtures.json';
import { ClassificationSchema } from '../../src/core/llm/schemas';

describe('Classification Response Parsing', () => {
  Object.entries(fixtures).forEach(([name, fixture]) => {
    it(`parses ${name} correctly`, () => {
      const parsed = JSON.parse(fixture.raw_response);
      const result = ClassificationSchema.parse(parsed);

      expect(result.category).toBe(fixture.expected_category);
    });
  });
});
3. 評価テスト(低速、CI で毎晩実行)
// tests/llm/evals/classify.eval.test.ts
import { classifyTicket } from '../../../src/core/services/ticket';

const TEST_CASES = [
  { input: 'How much does the pro plan cost?', expected: 'sales' },
  { input: 'The app crashes when I click save', expected: 'support' },
  { input: 'You should add dark mode', expected: 'feedback' },
  { input: 'What time is it in Tokyo?', expected: 'other' },
];

describe('Classification Accuracy (Eval)', () => {
  // Skip in regular CI, run nightly
  const runEvals = process.env.RUN_LLM_EVALS === 'true';

  it.skipIf(!runEvals)('achieves >90% accuracy on test set', async () => {
    let correct = 0;

    for (const testCase of TEST_CASES) {
      const result = await classifyTicket(testCase.input);
      if (result.category === testCase.expected) correct++;
    }

    const accuracy = correct / TEST_CASES.length;
    expect(accuracy).toBeGreaterThan(0.9);
  }, 60000); // 60s timeout for LLM calls
});

LLM テスト用の GitHub Actions

# .github/workflows/quality.yml (add to existing)
jobs:
  quality:
    # ... existing steps ...

    - name: Run Tests (with LLM mocks)
      run: npm run test:coverage

  llm-evals:
    runs-on: ubuntu-latest
    # Run nightly or on-demand
    if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Install dependencies
        run: npm ci

      - name: Run LLM Evals
        run: npm run test:evals
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
          RUN_LLM_EVALS: 'true'

コストとパフォーマンスの追跡

// core/llm/client.ts - add tracking
interface LLMMetrics {
  model: string;
  inputTokens: number;
  outputTokens: number;
  latencyMs: number;
  cost: number;
}

export async function llmCallWithMetrics<T>(
  options: LLMCallOptions<T>
): Promise<{ result: T; metrics: LLMMetrics }> {
  const start = Date.now();

  const response = await client.messages.create({...});

  const metrics: LLMMetrics = {
    model: options.model,
    inputTokens: response.usage.input_tokens,
    outputTokens: response.usage.output_tokens,
    latencyMs: Date.now() - start,
    cost: calculateCost(response.usage, options.model),
  };

  // Log or send to monitoring
  console.log('[LLM]', metrics);

  return { result: parsed, metrics };
}

LLM のアンチパターン

  • ❌ ビジネスロジックにプロンプトをハードコードする — プロンプトテンプレートを使用します
  • ❌ LLM の応答をスキーマで検証しない — 常に Zod を使用します
  • ❌ CI で実際の LLM 呼び出しを使ってテストする — 単体テストにはモックを使用します
  • ❌ コストを追跡しない — トークンの使用量を監視します
  • ❌ レイテンシーを無視する — LLM の呼び出しは遅いため、非同期処理を前提に設計します
  • ❌ LLM の失敗時のフォールバックがない — タイムアウトとエラーに対処します
  • ❌ バージョン管理されていないプロンプト — プロンプトの変更を追跡します
  • ❌ 評価スイートがない — 時間の経過に伴う精度を測定します
  • ❌ 決定論的なロジックに LLM を使用する — 検証、認証、数学的処理にはコードを使用します
  • ❌ 巨大で単一のプロンプト — より小さく、目的を絞ったプロンプトを組み合わせます

コメント