ai-generation-persistence

作者: openai

AI生成持久化模式——每次LLM生成的唯一ID、可寻址URL、数据库存储及成本追踪

npx skills add https://github.com/openai/plugins --skill ai-generation-persistence

AI Generation Persistence

AI generations are expensive, non-reproducible assets. Never discard them.

Every call to an LLM costs real money and produces unique output that cannot be exactly reproduced. Treat generations like database records — assign an ID, persist immediately, and make them retrievable.

Core Rules

  1. Generate an ID before the LLM call — use nanoid() or createId() from @paralleldrive/cuid2
  2. Persist every generation — text and metadata to database, images and files to Vercel Blob
  3. Make every generation addressable — URL pattern: /chat/[id], /generate/[id], /image/[id]
  4. Track metadata — model name, token usage, estimated cost, timestamp, user ID
  5. Never stream without saving — if the user refreshes, the generation must survive

Generate-Then-Redirect Pattern

The standard UX flow for AI features: create the resource first, then redirect to its page.

// app/api/chat/route.ts
import { nanoid } from "nanoid";
import { db } from "@/lib/db";
import { redirect } from "next/navigation";

export async function POST(req: Request) {
  const { prompt, model } = await req.json();
  const id = nanoid();

  // Create the record BEFORE generation starts
  await db.insert(generations).values({
    id,
    prompt,
    model,
    status: "pending",
    createdAt: new Date(),
  });

  // Redirect to the generation page — it handles streaming
  redirect(`/chat/${id}`);
}
// app/chat/[id]/page.tsx
import { db } from "@/lib/db";
import { notFound } from "next/navigation";

export default async function ChatPage({ params }: { params: Promise<{ id: string }> }) {
  const { id } = await params;
  const generation = await db.query.generations.findFirst({
    where: eq(generations.id, id),
  });
  if (!generation) notFound();

  // Render with streaming if still pending, or show saved result
  return <ChatView generation={generation} />;
}

This gives you: shareable URLs, back-button support, multi-tab sessions, and generation history for free.

Persistence Schema

// lib/db/schema.ts
import { pgTable, text, integer, timestamp, jsonb } from "drizzle-orm/pg-core";

export const generations = pgTable("generations", {
  id: text("id").primaryKey(),            // nanoid
  userId: text("user_id"),                // auth user
  model: text("model").notNull(),         // "openai/gpt-5.4"
  prompt: text("prompt"),                 // input text
  result: text("result"),                 // generated output
  imageUrls: jsonb("image_urls"),         // Blob URLs for generated images
  tokenUsage: jsonb("token_usage"),       // { promptTokens, completionTokens }
  estimatedCostCents: integer("estimated_cost_cents"),
  status: text("status").default("pending"), // pending | streaming | complete | error
  createdAt: timestamp("created_at").defaultNow(),
});

Storage Strategy

Data TypeStorageWhy
Text, metadata, historyNeon Postgres via DrizzleQueryable, relational, supports search
Generated images & filesVercel Blob (@vercel/blob)Permanent URLs, CDN-backed, no expiry
Prompt dedup cacheUpstash RedisFast lookup, TTL-based expiry

Image Persistence

Never serve generated images as ephemeral base64 or temporary URLs. Save to Blob immediately:

import { put } from "@vercel/blob";
import { generateText } from "ai";

const result = await generateText({ model, prompt });

// Save every generated image to permanent storage
const imageUrls: string[] = [];
for (const file of result.files ?? []) {
  if (file.mediaType?.startsWith("image/")) {
    const ext = file.mediaType.split("/")[1] || "png";
    const blob = await put(`generations/${generationId}.${ext}`, file.uint8Array, {
      access: "public",
      contentType: file.mediaType,
    });
    imageUrls.push(blob.url);
  }
}

// Update the generation record with permanent URLs
await db.update(generations)
  .set({ imageUrls, status: "complete" })
  .where(eq(generations.id, generationId));

Cost Tracking

Extract usage from every generation and store it. This enables billing, budgeting, and abuse detection:

const result = await generateText({ model, prompt });

const usage = result.usage; // { promptTokens, completionTokens, totalTokens }
const estimatedCostCents = estimateCost(model, usage);

await db.update(generations).set({
  result: result.text,
  tokenUsage: usage,
  estimatedCostCents,
  status: "complete",
}).where(eq(generations.id, generationId));

Prompt Dedup / Caching

Avoid paying for identical generations. Cache by content hash:

import { Redis } from "@upstash/redis";
import { createHash } from "crypto";

const redis = Redis.fromEnv();

function hashPrompt(model: string, prompt: string): string {
  return createHash("sha256").update(`${model}:${prompt}`).digest("hex");
}

// Check cache before generating
const cacheKey = `gen:${hashPrompt(model, prompt)}`;
const cached = await redis.get<string>(cacheKey);
if (cached) return cached; // Return cached generation ID

// After generation, cache the result
await redis.set(cacheKey, generationId, { ex: 3600 }); // 1hr TTL

Anti-Patterns

  • Streaming to client without saving — generation lost on page refresh. Always write to DB as tokens arrive or on completion.
  • Routes without [id] segments/api/chat with no ID means generations aren't addressable. Use /chat/[id].
  • Re-generating identical prompts — check cache first. Same prompt + same model = same cost for no new value.
  • Ephemeral base64 images — generated images served inline are lost when the component unmounts. Save to Vercel Blob.
  • Missing metadata — always store model name, token counts, and timestamp. You need this for cost tracking and debugging.
  • Client-only state — storing generations only in React state or localStorage. Use a database — generations must survive across devices and sessions.

来自 openai 的更多技能

user-context
openai
加载或管理数据分析插件的持久化源路由偏好、引导逻辑、设置进度及语义层注册表。
official
notion-research-documentation
openai
研究Notion内容,并将其综合成带有引用的结构化简报、报告或对比。通过定向查询搜索并获取Notion页面,然后按主题组织发现,附带内联来源引用和参考文献部分。根据范围和用户目标,从四种输出格式(快速简报、研究摘要、对比、综合报告)中选择。使用内置模板创建和更新Notion页面;直接链接来源,并在新信息到达时跟踪变更...
official
rcsb-pdb-skill
openai
提交紧凑的RCSB PDB请求以获取核心元数据、Search API查询和FASTA下载。当用户需要简洁的RCSB摘要时使用;保存原始JSON或…
official
pdf
openai
PDF的读取、创建与验证,支持可视化渲染与程序化生成。使用Poppler(pdftoppm)将PDF页面渲染为PNG,以便在交付前直观检查布局、间距与排版;通过reportlab程序化生成PDF,确保格式可靠;利用pdfplumber或pypdf提取文本与元数据。执行质量标准:无文本裁剪、元素重叠、表格损坏或渲染伪影;仅使用ASCII连字符,引用内容需可读。使用...
official
test-coverage-improver
openai
改进OpenAI Agents JS mon
official
playwright
openai
基于终端驱动的浏览器自动化,支持元素快照与交互式UI工作流。通过playwright-cli包装脚本运行(需npx),支持无头模式与有头模式进行可视化调试。核心工作流:打开页面、获取快照以稳定元素引用、使用引用进行交互、在导航或DOM变更后重新快照。包含表单填写、点击、输入、多标签页管理、截图/PDF捕获及用于流程调试的追踪记录。元素引用(如e3、e15)...
official
ukb-topmed-phewas-skill
openai
通过接受rsID、GRCh37或GRCh38输入并解析为所需的GRCh38查询,获取单个变体的紧凑型UKB-TOPMed PheWAS摘要。当需要…时使用。
official
code-review-context
openai
模型可见上下文
official