📦 项目教程仓库:https://github.com/ZIQI-a/AI_Agent_study 🚀 成品项目地址:https://github.com/ZIQI-a/huamiao_Agent
本章目标
要做的事:构建古诗词生成功能 ------ 输入名词生成诗词,带注释赏析
学到的知识:
- 领域特定的 Prompt 设计
- Few-shot 示例在诗词中的应用
- 多步骤生成(先写诗,再注释,再赏析)
- 古风 UI 设计
8.1 诗词 Prompt 设计
创建 src/lib/prompts/poem.ts:
typescript
export interface PoemOptions {
noun: string;
type: string;
dynasty: string;
}
export function getPoemSystemPrompt(options: PoemOptions) {
const { type, dynasty } = options;
return `你是一位精通古典文学的诗人,深谙${dynasty}代诗词的格律和意境。
## 创作要求
- 诗词类型:${type}
- 风格参考:${dynasty}代诗风
- 根据用户提供的名词或主题创作
## 格律要求
${getTypeRules(type)}
## 创作原则
1. 意境优先:诗要有画面感,让读者"看见"诗中的场景
2. 用典自然:可以用典故,但要自然不生硬
3. 情景交融:景中有情,情中有景
4. 语言凝练:每个字都要有用,不多余
## 输出格式
请严格按以下格式输出,不要添加额外内容:
[诗词标题]
[诗词正文,每句一行]
---
[注释]
- 词语1:解释
- 词语2:解释
[赏析]
(对诗词的意境、手法、情感进行赏析,80-120字)
## 约束
- 不要写现代白话诗
- 不要在诗词正文中加括号注释
- 不要使用过于生僻的字
- 诗词标题要雅致,不要直接用输入的名词`;
}
function getTypeRules(type: string): string {
const rules: Record<string, string> = {
五言绝句: "五言绝句,四句,每句五字。注意平仄和押韵。",
七言绝句: "七言绝句,四句,每句七字。注意平仄和押韵。",
五言律诗: "五言律诗,八句,每句五字。中间两联(颔联、颈联)要求对仗。",
七言律诗: "七言律诗,八句,每句七字。中间两联(颔联、颈联)要求对仗。",
宋词: "宋词,按词牌填写。请注明使用的词牌名,如《浣溪沙》《蝶恋花》等。",
};
return rules[type] || rules["七言绝句"];
}
export const POEM_TYPES = [
{ value: "五言绝句", label: "五言绝句" },
{ value: "七言绝句", label: "七言绝句" },
{ value: "五言律诗", label: "五言律诗" },
{ value: "七言律诗", label: "七言律诗" },
{ value: "宋词", label: "宋词" },
];
export const DYNASTIES = [
{ value: "唐", label: "唐诗" },
{ value: "宋", label: "宋词" },
{ value: "魏晋", label: "魏晋风骨" },
];
8.2 创建 API Route
创建 src/app/api/poems/generate/route.ts:
typescript
import { streamText } from "ai";
import { createOpenAI } from "@ai-sdk/openai";
import { getPoemSystemPrompt } from "@/lib/prompts/poem";
const deepseek = createOpenAI({
apiKey: process.env.DEEPSEEK_API_KEY,
baseURL: "https://api.deepseek.com",
});
export async function POST(req: Request) {
const { noun, type, dynasty } = await req.json();
if (!noun) {
return new Response("请输入名词", { status: 400 });
}
const system = getPoemSystemPrompt({
noun,
type: type || "七言绝句",
dynasty: dynasty || "唐",
});
const result = streamText({
model: deepseek("deepseek-chat"),
system,
prompt: `请以「${noun}」为主题,创作一首诗词。`,
maxTokens: 1500,
temperature: 0.9, // 诗词需要更多创意
});
return result.toDataStreamResponse();
}
8.3 构建古诗词页面
修改 src/app/poems/page.tsx:
typescript
"use client";
import { useState } from "react";
import { useCompletion } from "ai/react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { PageContainer } from "@/components/layout/page-container";
import { POEM_TYPES, DYNASTIES } from "@/lib/prompts/poem";
export default function Poems() {
const [noun, setNoun] = useState("");
const [type, setType] = useState("七言绝句");
const [dynasty, setDynasty] = useState("唐");
const { completion, isLoading, error, complete } = useCompletion({
api: "/api/poems/generate",
});
const handleGenerate = async () => {
if (!noun.trim()) return;
await complete("", {
body: { noun, type, dynasty },
});
};
return (
<PageContainer
title="古诗词生成"
description="输入名词,AI 创作古诗词"
>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* 左侧:参数 */}
<div className="lg:col-span-1">
<Card>
<CardHeader>
<CardTitle>创作参数</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label>主题名词</Label>
<Input
value={noun}
onChange={(e) => setNoun(e.target.value)}
placeholder="如:月亮、故乡、春天..."
onKeyDown={(e) => e.key === "Enter" && handleGenerate()}
/>
</div>
<div className="space-y-2">
<Label>诗词类型</Label>
<Select value={type} onValueChange={setType}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
{POEM_TYPES.map((t) => (
<SelectItem key={t.value} value={t.value}>
{t.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label>风格朝代</Label>
<Select value={dynasty} onValueChange={setDynasty}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
{DYNASTIES.map((d) => (
<SelectItem key={d.value} value={d.value}>
{d.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<Button
onClick={handleGenerate}
disabled={!noun.trim() || isLoading}
className="w-full"
size="lg"
>
{isLoading ? "诗意酝酿中..." : "创作诗词"}
</Button>
</CardContent>
</Card>
</div>
{/* 右侧:诗词展示 */}
<div className="lg:col-span-2">
<Card className="min-h-[500px]">
<CardHeader>
<CardTitle>诗词预览</CardTitle>
</CardHeader>
<CardContent>
{error && (
<div className="text-red-500 mb-4">
生成失败:{error.message}
</div>
)}
{!completion && !isLoading && (
<div className="text-center text-muted-foreground py-20">
<div className="text-6xl mb-4">📜</div>
<p>输入一个名词,开始诗意之旅</p>
</div>
)}
{isLoading && !completion && (
<div className="text-center text-muted-foreground py-20">
<div className="text-6xl mb-4 animate-pulse">🌙</div>
<p>诗兴大发中...</p>
</div>
)}
{completion && (
<div className="poem-content">
<PoemRenderer content={completion} />
{isLoading && (
<span className="inline-block w-0.5 h-5 bg-primary animate-pulse" />
)}
</div>
)}
</CardContent>
</Card>
</div>
</div>
</PageContainer>
);
}
// 诗词专用渲染组件
function PoemRenderer({ content }: { content: string }) {
// 按 --- 分割诗词和注释赏析
const parts = content.split("---");
const poemPart = parts[0]?.trim();
const analysisPart = parts[1]?.trim();
return (
<div className="space-y-8">
{/* 诗词正文 - 古风样式 */}
<div className="text-center py-8">
<div className="inline-block text-left">
{poemPart?.split("\n").map((line, i) => {
if (line.trim() === "") return <br key={i} />;
// 标题行(不以标点结尾的短行)
if (
i === 0 &&
!line.match(/[,。!?;]$/) &&
line.trim().length < 10
) {
return (
<h3
key={i}
className="text-2xl font-bold text-primary mb-6 text-center"
>
{line.trim()}
</h3>
);
}
return (
<p
key={i}
className="text-xl leading-loose tracking-wider text-foreground/90"
>
{line.trim()}
</p>
);
})}
</div>
</div>
{/* 注释赏析 - 普通排版 */}
{analysisPart && (
<div className="border-t pt-6">
{analysisPart.split("\n").map((line, i) => {
if (line.trim() === "") return <br key={i} />;
if (line.trim().startsWith("[") || line.trim().startsWith("【")) {
return (
<h4 key={i} className="font-semibold text-lg mt-4 mb-2">
{line.replace(/[\[\]【】]/g, "")}
</h4>
);
}
if (line.trim().startsWith("-")) {
return (
<p key={i} className="text-muted-foreground ml-4 mb-1">
{line}
</p>
);
}
return (
<p key={i} className="text-foreground/80 leading-7 mb-2">
{line}
</p>
);
})}
</div>
)}
</div>
);
}
8.4 测试古诗词生成
访问 http://localhost:3000/poems:
- 输入「月亮」,选七言绝句,唐诗风格
- 点击创作,观察诗词生成
- 尝试不同名词:故乡、梅花、编程、咖啡
- 切换不同诗词类型和朝代风格

本章小结
| 要点 | 说明 |
|---|---|
| 领域 Prompt | 诗词需要格律约束和意境要求 |
| Temperature | 诗词创作用较高温度(0.9)增加创意 |
| 古风 UI | 居中排版、大字号、行间距大 |
| 内容分割 | 用 --- 分隔诗词正文和注释赏析 |
下一章预告
目前所有数据都在内存中,刷新页面就丢失了。下一章我们引入 SQLite 数据库,让创作的文章和诗词持久化保存。
如果这个教程对你有帮助,欢迎 ⭐ Star 支持一下!