xiaohongshu
xiaohongshu
ai-structured-output
Generate type-safe AI content using Gemini structured output with Zod validation and Code Execution Tool. Use when building AI generation functions that need guaranteed output format.
Full skill instructions
This skill documents patterns for generating type-safe, validated AI content using Google Gemini with Vercel AI SDK.
The recommended architecture uses a 2-step generation flow:
Step 1: Analysis (Accuracy) Step 2: Structured Output (Presentation)
┌─────────────────────────────┐ ┌─────────────────────────────┐
│ generateText() │ │ generateObject() │
│ + Code Execution Tool │ ──▶ │ + Zod Schema │
│ + Extended Thinking │ │ + Field Descriptions │
│ = Accurate calculations │ │ = Type-safe output │
└─────────────────────────────┘ └─────────────────────────────┘
Why 2 steps?
import { google } from "@ai-sdk/google";
import { generateText, generateObject } from "ai";
import { z } from "zod";
// Define output schema with field descriptions
const outputSchema = z.object({
title: z.string().max(100).describe("SEO-optimised title, max 60 chars preferred"),
excerpt: z.string().max(500).describe("2-3 sentence summary for meta description"),
content: z.string().describe("Full markdown content without H1 title"),
tags: z.array(z.string()).min(1).max(10).describe("3-5 category tags in Title Case"),
highlights: z.array(z.object({
value: z.string().describe('Metric value, e.g. "52.60%", "$125,000"'),
label: z.string().describe('Short label, e.g. "Electric Vehicles Lead"'),
detail: z.string().describe('Context, e.g. "2,081 units registered"'),
})).min(3).max(10).describe("3-6 key statistics for visual display"),
});
type GeneratedOutput = z.infer<typeof outputSchema>;
export async function generate2Step(data: string): Promise<GeneratedOutput> {
// STEP 1: Analysis with Code Execution
const analysisResult = await generateText({
model: google("gemini-2.5-flash"),
system: ANALYSIS_INSTRUCTIONS,
tools: { code_execution: google.tools.codeExecution({}) },
prompt: `Analyse this data:\n${data}\n\nProvide detailed analysis with accurate calculations.`,
providerOptions: {
google: {
thinkingConfig: {
thinkingBudget: -1, // Unlimited thinking for complex analysis
},
},
},
});
// STEP 2: Structured Output Generation
const { object } = await generateObject({
model: google("gemini-2.5-flash"),
schema: outputSchema,
system: GENERATION_INSTRUCTIONS,
prompt: `Based on this analysis:\n\n${analysisResult.text}\n\nGenerate the structured output.`,
});
return object; // Fully typed!
}
The Code Execution Tool is critical for preventing hallucinations in data analysis.
tools: { code_execution: google.tools.codeExecution({}) }
| Without Code Execution | With Code Execution |
|---|---|
| LLM guesses calculations | Python executes actual math |
| Plausible but wrong numbers | Verified accurate results |
| Cannot validate data | Can parse and validate input |
| Unreliable for financial data | Safe for market analysis |
const analysisResult = await generateText({
model: google("gemini-2.5-flash"),
tools: { code_execution: google.tools.codeExecution({}) },
system: `You are a data analyst. Use Python code execution for ALL calculations.
Never estimate or guess numbers. Execute code to:
- Parse the input data
- Calculate totals and percentages
- Compare values and trends
- Validate data consistency`,
prompt: `Analyse this sales data:\n${pipeDelimitedData}`,
});
Extended thinking improves analysis quality but increases latency. Use selectively.
providerOptions: {
google: {
thinkingConfig: {
thinkingBudget: -1, // -1 = unlimited, or set specific token budget
},
},
}
| Step | Extended Thinking | Reason |
|---|---|---|
| Analysis (Step 1) | YES | Complex reasoning, data patterns |
| Generation (Step 2) | NO | Speed matters, schema guides output |
// Step 1: WITH extended thinking (complex analysis)
const analysis = await generateText({
model: google("gemini-2.5-flash"),
tools: { code_execution: google.tools.codeExecution({}) },
providerOptions: {
google: {
thinkingConfig: { thinkingBudget: -1 },
},
},
prompt: analysisPrompt,
});
// Step 2: WITHOUT extended thinking (faster generation)
const { object } = await generateObject({
model: google("gemini-2.5-flash"),
schema: outputSchema,
prompt: generationPrompt,
// No thinkingConfig = faster response
});
Use .describe() to guide LLM output:
const schema = z.object({
// Constraints + description = better output
title: z.string()
.max(100)
.describe("SEO title, max 60 chars preferred, include main keyword"),
// Array bounds prevent over/under generation
tags: z.array(z.string())
.min(3)
.max(5)
.describe("Category tags in Title Case, first tag is primary category"),
// Nested objects with descriptions
author: z.object({
name: z.string().describe("Full name"),
role: z.string().describe("Job title or role"),
}).describe("Content author information"),
});
// Infer TypeScript type from schema
type Output = z.infer<typeof schema>;
// Use in function signatures
async function generate(): Promise<Output> {
const { object } = await generateObject({
model: google("gemini-2.5-flash"),
schema,
prompt: "...",
});
return object; // Typed as Output
}
// Optional fields with defaults
z.string().optional().default("Unknown")
// Enum-like constraints
z.enum(["draft", "published", "archived"])
// Numeric constraints
z.number().min(0).max(100).describe("Percentage value 0-100")
// Date strings
z.string().describe("ISO 8601 date string, e.g. 2024-01-15")
// Markdown content
z.string().describe("Markdown formatted content, use ## for sections")
Use controlled vocabulary for consistent categorisation:
// Define constants with as const
export const CATEGORY_TAGS = [
"Technology",
"Business",
"Finance",
"Market Analysis",
"Monthly Update",
] as const;
// Extract type from constants
export type CategoryTag = (typeof CATEGORY_TAGS)[number];
// Use in schema
const schema = z.object({
tags: z.array(z.enum(CATEGORY_TAGS))
.min(1)
.max(5)
.describe("Select from predefined categories"),
});
export const CARS_TAGS = [
"Cars", "Registrations", "Fuel Types", "Vehicle Types",
"Monthly Update", "New Registration", "Market Trends",
] as const;
export const COE_TAGS = [
"COE", "Quota Premium", "1st Bidding Round", "2nd Bidding Round",
"Monthly Update", "PQP",
] as const;
// Type union
export type DataTag = (typeof CARS_TAGS)[number] | (typeof COE_TAGS)[number];
Separate instructions for analysis vs generation:
// Analysis instructions focus on accuracy
const ANALYSIS_INSTRUCTIONS = `You are a data analyst.
Use Python code execution for ALL calculations.
Never estimate or guess numbers.
Required analysis:
1. Parse the pipe-delimited input data
2. Calculate totals, percentages, and changes
3. Identify top performers and trends
4. Compare with previous periods if available
Output: Detailed analysis with verified numbers.`;
// Generation instructions focus on format
const GENERATION_INSTRUCTIONS = `You are a content writer.
Transform the analysis into structured output.
Requirements:
- Title: SEO-optimised, max 60 characters
- Excerpt: 2-3 sentences, under 300 characters
- Content: Markdown without H1, 500-700 words
- Tags: 3-5 from the allowed vocabulary
- Highlights: 3-6 key statistics with value/label/detail
Tone: Professional, accessible, data-driven.`;
Track generation performance with Langfuse:
import { generateText, generateObject } from "ai";
// Step 1: Analysis telemetry
const analysisResult = await generateText({
model: google("gemini-2.5-flash"),
tools: { code_execution: google.tools.codeExecution({}) },
prompt: analysisPrompt,
experimental_telemetry: {
isEnabled: true,
functionId: "content-analysis/cars",
metadata: {
step: "analysis",
dataType: "cars",
month: "2024-01",
tags: ["cars", "2024-01", "analysis"],
},
},
});
// Step 2: Generation telemetry
const { object } = await generateObject({
model: google("gemini-2.5-flash"),
schema: outputSchema,
prompt: generationPrompt,
experimental_telemetry: {
isEnabled: true,
functionId: "content-generation/cars",
metadata: {
step: "generation",
dataType: "cars",
month: "2024-01",
tags: ["cars", "2024-01", "generation"],
},
},
});
// instrumentation.ts
import { registerOTel } from "@vercel/otel";
import { LangfuseExporter } from "@langfuse/otel";
export function startTracing() {
registerOTel({
serviceName: "ai-generation",
traceExporter: new LangfuseExporter({
publicKey: process.env.LANGFUSE_PUBLIC_KEY,
secretKey: process.env.LANGFUSE_SECRET_KEY,
baseUrl: process.env.LANGFUSE_HOST,
}),
});
}
export async function shutdownTracing() {
// Flush pending traces before exit
await new Promise((resolve) => setTimeout(resolve, 1000));
}
export interface GenerateParams {
data: string;
month: string;
dataType: "cars" | "coe";
}
export interface GenerateResult {
object: GeneratedOutput;
usage: { inputTokens: number; outputTokens: number; totalTokens: number };
response: { id: string; modelId: string; timestamp: Date };
}
export async function generateContent(
params: GenerateParams
): Promise<GenerateResult> {
startTracing();
try {
// Step 1: Analysis
const analysis = await generateText({ /* ... */ });
// Step 2: Generation
const { object, usage, response } = await generateObject({ /* ... */ });
return { object, usage, response };
} finally {
await shutdownTracing();
}
}
import type { WorkflowContext } from "@upstash/workflow";
export async function generateInWorkflow(
context: WorkflowContext,
params: GenerateParams
): Promise<GenerateResult> {
// Use workflow context for step orchestration
const result = await context.run("generate-content", async () => {
return generateContent(params);
});
// Additional workflow steps
await context.run("save-to-database", async () => {
await saveToDatabase(result);
});
await context.run("invalidate-cache", async () => {
await revalidateTag("content:list");
});
return result;
}
import { APIError } from "@ai-sdk/google";
export async function generateWithRetry(
params: GenerateParams,
maxRetries = 3
): Promise<GenerateResult> {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await generateContent(params);
} catch (error) {
if (error instanceof APIError) {
// Handle rate limits
if (error.status === 429 && attempt < maxRetries) {
await new Promise(r => setTimeout(r, 2000 * attempt));
continue;
}
// Handle quota exceeded
if (error.status === 403) {
throw new Error("API quota exceeded. Check billing.");
}
}
throw error;
}
}
throw new Error("Max retries exceeded");
}
Required:
GOOGLE_GENERATIVE_AI_API_KEY=... # Google AI API key
Optional (for telemetry):
LANGFUSE_PUBLIC_KEY=pk-lf-...
LANGFUSE_SECRET_KEY=sk-lf-...
LANGFUSE_HOST=https://cloud.langfuse.com
gemini-blog - Blog-specific generation patternsschema-design - Database schema for persisting generated contentworkflow-management - QStash workflow integrationredis-cache - Caching generated contentpackages/ai/src/generate-post.ts - 2-step flow implementationpackages/ai/src/schemas.ts - Zod schema patternspackages/ai/src/tags.ts - Tag constantspackages/ai/src/config.ts - System instructionspackages/ai/src/instrumentation.ts - Langfuse setupxiaohongshu
technical spec
product ux expert
database patterns
Conduct multi-agent task orchestration and workflow coordination.
Initialize project with Conductor artifacts (product definition,
Expert in web animations, transitions, and motion design using Framer Motion and CSS
Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...
PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.
Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...
Display project status, active tracks, and next actions
Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience