> ## Documentation Index
> Fetch the complete documentation index at: https://runcrate.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# AI Product Description Generator

> Generate SEO-optimized product descriptions, titles, and bullet points from raw product data using structured output and the Vercel AI SDK.

export const RuncrateStyles = () => {
  if (typeof document !== 'undefined' && !document.getElementById('runcrate-overrides')) {
    const s = document.createElement('style');
    s.id = 'runcrate-overrides';
    s.textContent = `
      /* Match Runcrate's rounding scale (--radius: 0.75rem) */
      .rounded-sm { border-radius: 0.5rem !important; }   /* 8px */
      .rounded-md { border-radius: 0.625rem !important; } /* 10px */
      .rounded-lg { border-radius: 0.75rem !important; }  /* 12px */
      .rounded-l-sm { border-top-left-radius: 0.5rem !important; border-bottom-left-radius: 0.5rem !important; }
      .rounded-r-sm { border-top-right-radius: 0.5rem !important; border-bottom-right-radius: 0.5rem !important; }
      .rounded-l-md { border-top-left-radius: 0.625rem !important; border-bottom-left-radius: 0.625rem !important; }
      .rounded-r-md { border-top-right-radius: 0.625rem !important; border-bottom-right-radius: 0.625rem !important; }
      .rounded-l-lg { border-top-left-radius: 0.75rem !important; border-bottom-left-radius: 0.75rem !important; }
      .rounded-r-lg { border-top-right-radius: 0.75rem !important; border-bottom-right-radius: 0.75rem !important; }

      /* Cards: never pure white in light mode */
      .card { background-color: #fcfcfc !important; border-radius: 0.75rem !important; }
      html.dark .card { background-color: #141414 !important; }

      /* Docs hero box */
      .rc-hero { background-color: #fcfcfc; border: 1px solid #e0e0e0; }
      html.dark .rc-hero { background-color: #141414; border-color: #242424; }
      html.dark .rc-hero h1 { color: #f5f5f5; }

      /* Runcrate scrollbar — thin, transparent track, hide-until-hover thumb */
      ::-webkit-scrollbar { width: 6px; height: 6px; background-color: transparent; }
      ::-webkit-scrollbar-track { background-color: transparent; }
      ::-webkit-scrollbar-thumb { background-color: rgba(155, 155, 155, 0.5); border-radius: 10px; transition: opacity 0.3s ease; opacity: 0; }
      ::-webkit-scrollbar-thumb:hover { background-color: rgba(155, 155, 155, 0.7); }
      *:hover::-webkit-scrollbar-thumb,
      *:focus::-webkit-scrollbar-thumb,
      *:active::-webkit-scrollbar-thumb { opacity: 1; }
      * { scrollbar-width: thin; scrollbar-color: rgba(155, 155, 155, 0.5) transparent; }
    `;
    document.head.appendChild(s);
  }
  return null;
};

<RuncrateStyles />

Generate structured, SEO-ready product descriptions from raw product data using DeepSeek-V3 with the Vercel AI SDK's `Output.object`.

***

## Vercel AI SDK — structured output

```typescript theme={"theme":"github-dark"}
import { runcrate } from '@runcrate/ai';
import { generateText, Output } from 'ai';
import { z } from 'zod';

const ProductListing = z.object({
  title: z.string().describe('SEO product title, 50-70 chars'),
  shortDescription: z.string().describe('One-sentence hook, under 160 chars'),
  longDescription: z.string().describe('2-3 paragraph description'),
  bulletPoints: z.array(z.string()).describe('5-7 feature bullets'),
  metaDescription: z.string().describe('Meta description, under 155 chars'),
  tags: z.array(z.string()).describe('5-10 product tags'),
});

const { output } = await generateText({
  model: runcrate('deepseek-ai/DeepSeek-V3'),
  output: Output.object({ schema: ProductListing }),
  prompt: `Generate a product listing for:
Name: AeroFit Pro Wireless Earbuds
Category: Audio / Earbuds
Specs: Bluetooth 5.3, ANC, 32-hour battery, IPX5, 6mm drivers
Price: $79.99`,
});
console.log(output.title);
console.log(output.bulletPoints);
```

***

## Next.js API route

```typescript theme={"theme":"github-dark"}
// app/api/generate-listing/route.ts
import { runcrate } from '@runcrate/ai';
import { generateText, Output } from 'ai';
import { z } from 'zod';

const ProductListing = z.object({
  title: z.string(), shortDescription: z.string(), longDescription: z.string(),
  bulletPoints: z.array(z.string()), metaDescription: z.string(), tags: z.array(z.string()),
});

export async function POST(req: Request) {
  const { name, category, specs, price } = await req.json();
  const { output } = await generateText({
    model: runcrate('deepseek-ai/DeepSeek-V3'),
    output: Output.object({ schema: ProductListing }),
    prompt: `Generate an SEO product listing.\nName: ${name}\nCategory: ${category}\nSpecs: ${specs}\nPrice: ${price}\n\nBe specific. Avoid superlatives.`,
  });
  return Response.json(output);
}
```

***

## Python — JSON mode

```python theme={"theme":"github-dark"}
from runcrate import Runcrate
import json

client = Runcrate(api_key="rc_live_YOUR_API_KEY")
response = client.models.chat_completion(
    model="deepseek-ai/DeepSeek-V3",
    messages=[
        {"role": "system", "content": "Generate SEO product listings. Return valid JSON only."},
        {"role": "user", "content": "JSON fields: title, shortDescription, longDescription, bulletPoints, metaDescription, tags\n\nProduct: AeroFit Pro Wireless Earbuds\nSpecs: Bluetooth 5.3, ANC, 32hr battery, IPX5\nPrice: $79.99"}
    ],
    response_format={"type": "json_object"},
)
listing = json.loads(response.choices[0].message.content)
print(f"Title: {listing['title']}")
```

***

## Batch generation

```python theme={"theme":"github-dark"}
import json, csv
# Using the same client from above
with open("products.csv") as f:
    products = list(csv.DictReader(f))

results = []
for p in products:
    response = client.models.chat_completion(
        model="deepseek-ai/DeepSeek-V3",
        messages=[
            {"role": "system", "content": "Generate SEO product listings. Return valid JSON only."},
            {"role": "user", "content": f"Fields: title, shortDescription, longDescription, bulletPoints, metaDescription, tags\n\nName: {p['name']}\nSpecs: {p['specs']}\nPrice: {p['price']}"}
        ],
        response_format={"type": "json_object"},
    )
    results.append(json.loads(response.choices[0].message.content))

with open("listings.json", "w") as f:
    json.dump(results, f, indent=2)
```

***

## Tips

* **Structured output.** `Output.object` with Zod is the most reliable way to get typed JSON from the AI SDK.
* **JSON mode in Python.** Pass `response_format={"type": "json_object"}` and mention "JSON" in the system prompt.
* **SEO title.** Google displays 50-60 characters. Front-load the product name.
