mcpField
A
ai-integration

ai-integration

github.com/mgd34msu开发656 次安装10 周前更新
代码仓库

关于 ai-integration

ai-integration 是面向应用开发者的 AI 集成技能,涵盖从聊天与流式响应到 RAG、向量检索和工具调用的实现,并提供安全、测试与验证指导。

主要功能

  1. 01指导选择 AI 提供方与 SDK
  2. 02构建聊天界面和流式响应
  3. 03实现 RAG、嵌入与向量检索
  4. 04添加工具调用、限流与安全措施
  5. 05提供测试和集成验证流程

适用场景

  1. 01为应用加入 LLM 聊天功能
  2. 02构建文档检索问答流程
  3. 03集成外部函数调用能力
  4. 04检查 AI 集成的安全与成本控制

安装

复制下面的命令,在终端中运行。

$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 集成实施

本技能将指导你将 AI 和大模型能力集成到应用中,从基础聊天界面到复杂的 RAG 工作流和函数调用。它利用 GoodVibes 精准工具和项目分析,助你实现可用于生产环境的 AI 功能。

何时使用此技能

在需要以下操作时使用此技能:

  • 使用大模型提供商实现聊天界面
  • 添加流式响应功能
  • 构建 RAG(检索增强生成)工作流
  • 集成嵌入和向量搜索
  • 实现工具/函数调用模式
  • 连接 OpenAI、Anthropic 或本地模型
  • 处理 AI API 速率限制和错误恢复

工作流

按以下顺序进行 AI 集成:

1. 发现现有 AI 基础设施

在实现 AI 功能之前,先了解当前情况:

detect_stack:
  project_root: "."
  categories: ["ai", "llm"]

这可以识别:

  • 使用中的 AI SDK(Vercel AI SDK、LangChain、LlamaIndex)
  • 大模型提供商(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 始终使用流式传输”)
  • 已知问题(“Anthropic 在 Claude Opus 上的速率限制”)

搜索现有 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
本地/隐私使用 Ollama 搭配 Llama 3 或 Mistral
函数调用OpenAI(支持最佳)或 Anthropic
长上下文Anthropic Claude(200K tokens)
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. 实现流式响应

通过逐个 token 的流式传输优化聊天体验。

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

服务器发送事件(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. 实现工具/函数调用

启用大模型调用外部函数和工具。

使用 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 集成

实施确定性测试策略。

模拟大模型响应:

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

验证脚本会检查:

  1. 是否已安装 AI SDK 或大模型库
  2. 是否在 .env.example 中记录了 API 密钥
  3. 是否存在流式传输端点
  4. 源代码中是否存在硬编码的 API 密钥
  5. 是否已配置速率限制
  6. AI 调用周围是否进行了错误处理
  7. 是否采用了 Token/成本跟踪模式

部署前请修复所有违规项。

最佳实践

错误处理
  • 始终使用 try/catch 块封装 AI 调用
  • 使用指数退避处理速率限制
  • 为外部 API 实施熔断器
  • 记录错误时不要暴露 API 密钥
性能
  • 缓存频繁访问内容的嵌入向量
  • 对较长的补全请求使用流式传输
  • 尽可能批量生成嵌入向量
  • 实施请求去重
成本管理
  • 为每个请求设置最大 Token 限制
  • 跟踪每日/月度支出
  • 简单任务使用成本较低的模型
  • 适时缓存补全结果
安全
  • 切勿将 API 密钥提交到源代码管理中
  • 按用户/IP 实施速率限制
  • 发送给大模型前清理用户输入
  • 对用户生成的提示词使用内容审核
  • 验证工具执行结果
监控
  • 跟踪 Token 用量和成本
  • 监控响应延迟
  • 记录成功和失败的请求
  • 对异常支出模式发出警报

常见陷阱

  1. 忘记使用流式传输:聊天界面始终使用流式传输,以改善用户体验
  2. 忽略速率限制:实施退避和重试逻辑
  3. 硬编码提示词:使用模板系统以便于维护
  4. 不对文档分块:RAG 需要对大型文档进行分块
  5. 跳过成本跟踪:AI 成本可能迅速攀升
  6. 信任用户输入:始终清理并验证
  7. 没有错误边界:发生故障时应优雅降级
  8. 上下文过大:限制上下文以控制在 Token 预算内

其他资源

详细的提供商比较和架构模式,请参阅 references/ai-patterns.md。

评论

支持 Markdown