ai-generation-persistence — скилл для Codex: сохранение генераций
Помогает сохранять результаты запросов к LLM, присваивать им ID и открывать по адресу. Подойдёт для проектов с историей чатов или генераций.
npx skills add openai/plugins --skill ai-generation-persistence -a codexЧто делает ai-generation-persistence
Скилл предлагает создавать запись с ID до вызова модели, затем сохранять результат и метаданные: модель, расход токенов, оценочную стоимость, время и ID пользователя. Для изображений и файлов он описывает сохранение в Vercel Blob с записью постоянных URL в базу. Стриминг тоже должен завершаться сохранением результата.
В примерах используются nanoid, Drizzle и Postgres для записей, @vercel/blob для файлов, а Upstash Redis — для кэширования одинаковых запросов по хешу. Стандартный поток создаёт ресурс и перенаправляет пользователя на страницу с его ID. Конкретные сервисы хранения из примеров понадобятся, если вы будете повторять именно эту схему.
Ставьте скилл, если генерации должны переживать обновление страницы и открываться по ссылке; для приложения, которому не нужна история результатов, эти правила избыточны.
Какие задачи решает
- сохранять результат генерации до завершения стриминга
- открывать чат или генерацию по ID в URL
- записывать расход токенов и оценочную стоимость
- сохранять сгенерированные изображения по постоянным URL
- кэшировать повторные запросы по модели и промпту
Примеры запросов агенту
- Добавь сохранение истории чатов с отдельным URL для каждого чата.
- Сохраняй результат и расход токенов после каждой генерации.
- Сделай кэш одинаковых запросов по модели и промпту.
Как установить ai-generation-persistence
1Через Skills CLI (Vercel)
npx skills add openai/plugins --skill ai-generation-persistence -a codex2Вручную: положить файлы
.agents/skills/ai-generation-persistence/SKILL.mdДля всех проектов — ~/.agents/skills/
1Через Skills CLI (Vercel)
npx skills add openai/plugins --skill ai-generation-persistence -a claude-code2Вручную: положить файлы
~/.claude/skills/ai-generation-persistence/SKILL.mdПапка скилла целиком; для одного проекта — .claude/skills/ai-generation-persistence/
Вручную: положить файлы
.cursor/skills/ai-generation-persistence/SKILL.mdCursor читает и .agents/skills/, и .claude/skills/
1GitHub CLI
gh skill install openai/plugins ai-generation-persistence2Вручную: положить файлы
.github/skills/ai-generation-persistence/SKILL.mdКомандой платформы
gemini skills install https://github.com/openai/plugins --path plugins/vercel/skills/ai-generation-persistenceВручную: положить файлы
.devin/skills/ai-generation-persistence/SKILL.mdСтарый путь .windsurf/skills/ тоже читается
Claude.ai
Упакуйте папку в zip и загрузите в Settings → Capabilities → Skills
Из того же набора201
Весь набор →Похожие
Вопросы о ai-generation-persistence
Как установить ai-generation-persistence в Codex?
Выполните npx skills add openai/plugins --skill ai-generation-persistence. Для установки с указанием агента используйте npx skills add openai/plugins --skill ai-generation-persistence -a codex.
Что делает ai-generation-persistence?
Подсказывает, как сохранять генерации с ID, результатами и метаданными, а затем открывать их по URL. Также описывает хранение изображений и кэширование повторных запросов.
Какие хранилища используются в примерах скилла?
Для текстовых данных и истории приведён Neon Postgres через Drizzle, для изображений и файлов — Vercel Blob, для кэша — Upstash Redis.
Исходный текст
SKILL.md · Apache-2.0
---
name: ai-generation-persistence
description: "AI generation persistence patterns — unique IDs, addressable URLs, database storage, and cost tracking for every LLM generation"
metadata:
priority: 6
docs:
- "https://sdk.vercel.ai/docs/ai-sdk-ui/storing-messages"
sitemap: "https://sdk.vercel.ai/sitemap.xml"
pathPatterns:
- "app/api/generate/**"
- "app/api/generations/**"
- "src/app/api/generate/**"
- "src/app/api/generations/**"
- "app/chat/[id]/**"
- "app/generate/[id]/**"
- "src/app/chat/[id]/**"
- "src/app/generate/[id]/**"
- "lib/generations/**"
- "src/lib/generations/**"
bashPatterns: []
importPatterns:
- "ai"
- "@ai-sdk/*"
- "@vercel/blob"
- "nanoid"
- "@paralleldrive/cuid2"
promptSignals:
phrases:
- "save generations"
- "persist generations"
- "generation history"
- "chat history"
- "save chat"
- "generation id"
- "ai chat"
- "chat app"
- "chatbot"
- "image generation"
- "text generation"
- "ai app"
allOf:
- [generate, save]
- [generate, persist]
- [generate, store]
- [ai, persist]
- [ai, history]
- [ai, database]
- [chat, persist]
- [chat, database]
- [chat, url]
- [generation, url]
- [generation, id]
- [image, generate]
- [stream, save]
- [stream, persist]
anyOf:
- "shareable"
- "retrievable"
- "permalink"
- "cost tracking"
- "token usage"
- "nanoid"
- "cuid"
- "openai"
- "anthropic"
- "llm"
- "gpt"
- "claude"
noneOf:
- "github actions"
- "ci workflow"
minScore: 6
---
# 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.
```ts
// 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}`);
}
```
```tsx
// 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
```ts
// 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 Type | Storage | Why |
|-----------|---------|-----|
| Text, metadata, history | Neon Postgres via Drizzle | Queryable, relational, supports search |
| Generated images & files | Vercel Blob (`@vercel/blob`) | Permanent URLs, CDN-backed, no expiry |
| Prompt dedup cache | Upstash Redis | Fast lookup, TTL-based expiry |
## Image Persistence
Never serve generated images as ephemeral base64 or temporary URLs. Save to Blob immediately:
```ts
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:
```ts
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:
```ts
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.
© Vercel Labs · Apache-2.0 · openai/plugins