LLM Patterns 技能
加载与: base.md + [语言].md
针对以AI为先导的应用,LLMs处理逻辑操作。
核心原则
LLM用于逻辑,代码用于管道。
使用LLMs进行:
- 分类、提取、总结
- 使用自然语言推理进行决策制定
- 内容生成和转换
- 在代码中会显得脆弱的复杂条件逻辑
使用传统代码进行:
- 数据验证(Zod/Pydantic)
- API路由和HTTP处理
- 数据库操作
- 认证/授权
- 编排和错误处理
项目结构
project/
├── src/
│ ├── core/
│ │ ├── prompts/ # 提示模板
│ │ │ ├── classify.ts
│ │ │ └── extract.ts
│ │ ├── llm/ # LLM客户端和工具
│ │ │ ├── client.ts # LLM客户端包装器
│ │ │ ├── schemas.ts # 响应模式(Zod)
│ │ │ └── index.ts
│ │ └── services/ # 使用LLM的业务逻辑
│ ├── infra/
│ └── ...
├── tests/
│ ├── unit/
│ ├── integration/
│ └── llm/ # LLM特定测试
│ ├── fixtures/ # 用于确定性测试的保存响应
│ ├── evals/ # 评估测试套件
│ └── mocks/ # 模拟LLM响应
└── _project_specs/
└── prompts/ # 提示规范
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-20250514',
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
: '';
// 解析并验证响应
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 `将此支持票分类为以下类别之一:
- support: 技术问题或帮助请求
- sales: 价格、计划或购买咨询
- feedback: 建议或投诉
- other: 其他任何事情
以JSON格式响应:
{
"category": "...",
"confidence": 0.0-1.0,
"reasoning": "简短解释"
}
票:
${ticket}`;
}
提示版本控制
// core/prompts/index.ts
export const PROMPTS = {
classify: {
v1: classifyTicketPromptV1,
v2: classifyTicketPromptV2, // 提高准确性
current: classifyTicketPromptV2,
},
} as const;
测试LLM调用
1. 使用模拟的单元测试(快速,确定性)
// tests/llm/mocks/classify.mock.ts
export const mockClassifyResponse = {
category: 'support',
confidence: 0.95,
reasoning: '用户正在寻求登录帮助',
};
// tests/unit/services/ticket.test.ts
import { classifyTicket } from '../../../src/core/services/ticket';
import { mockClassifyResponse } from '../../llm/mocks/classify.mock';
// 模拟LLM客户端
vi.mock('../../../src/core/llm/client', () => ({
llmCall: vi.fn().mockResolvedValue(mockClassifyResponse),
}));
describe('classifyTicket', () => {
it('返回票的分类', async () => {
const result = await classifyTicket('我无法登录');
expect(result.category).toBe('support');
expect(result.confidence).toBeGreaterThan(0.9);
});
});
2. 固定装置测试(确定性,测试解析)
// tests/llm/fixtures/classify.fixtures.json
{
"support_ticket": {
"input": "我无法重置我的密码",
"expected_category": "support",
"raw_response": "{\"category\":\"support\",\"confidence\":0.98,\"reasoning\":\"密码重置是支持问题\"}"
}
}
// tests/llm/classify.fixture.test.ts
import fixtures from './fixtures/classify.fixtures.json';
import { ClassificationSchema } from '../../src/core/llm/schemas';
describe('分类响应解析', () => {
Object.entries(fixtures).forEach(([name, fixture]) => {
it(`正确解析 ${name}`), () => {
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: '专业计划的费用是多少?', expected: 'sales' },
{ input: '点击保存时应用程序崩溃', expected: 'support' },
{ input: '你们应该添加暗色模式', expected: 'feedback' },
{ input: '东京现在几点?', expected: 'other' },
];
describe('分类准确性(评估)', () => {
// 在常规CI中跳过,夜间运行
const runEvals = process.env.RUN_LLM_EVALS === 'true';
it.skipIf(!runEvals)('在测试集上实现>90%的准确性', 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超时LLM调用
});
GitHub Actions用于LLM测试
# .github/workflows/quality.yml(添加到现有)
jobs:
quality:
# ...现有步骤...
- name: 运行测试(带有LLM模拟)
run: npm run test:coverage
llm-evals:
runs-on: ubuntu-latest
# 夜间运行或按需运行
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
steps:
- uses: actions/checkout@v4
- name: 设置Node
uses: actions/setup-node@v4
with:
node-version: '20'
- name: 安装依赖项
run: npm ci
- name: 运行LLM评估
run: npm run test:evals
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
RUN_LLM_EVALS: 'true'
成本和性能跟踪
// core/llm/client.ts - 添加跟踪
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),
};
// 记录或发送到监控
console.log('[LLM]', metrics);
return { result: parsed, metrics };
}
LLM反模式
- ❌ 硬编码提示在业务逻辑中 - 使用提示模板
- ❌ LLM响应上没有模式验证 - 始终使用Zod
- ❌ CI中使用实时LLM调用进行测试 - 单元测试使用模拟
- ❌ 没有成本跟踪 - 监控令牌使用情况
- ❌ 忽略延迟 - LLM调用很慢,设计为异步
- ❌ LLM故障没有备用方案 - 处理超时和错误
- ❌ 提示没有版本控制 - 跟踪提示更改
- ❌ 没有评估套件 - 随时间测量准确性
- ❌ 使用LLM进行确定性逻辑 - 使用代码进行验证、认证、数学
- ❌ 巨大的单一提示 - 组合更小的专注提示