大家好,我是掘金上的AI应用开发爱好者FogLetter。今天要跟大家分享一个超级有趣的项目——phoneGPT!这是一个专门回答手机相关问题的AI聊天机器人,让我带你从零开始,看看如何用最新的AI技术栈打造一个专业领域的智能助手。
为什么需要phoneGPT?
想象一下这样的场景:你想了解最新的iPhone 16 Pro有什么新功能,或者想对比三星Galaxy S25和小米14 Ultra的配置,但需要去各个网站搜索,信息分散且耗时。phoneGPT就是为了解决这个问题而生——它整合了最新的手机知识,让你用自然语言就能获取准确、专业的手机信息。
技术选型:现代化全栈AI应用架构
前端技术栈
- Next.js 15:React全栈框架,服务端渲染,SEO友好
- TypeScript:类型安全,开发体验更佳
- Tailwind CSS:原子化CSS,快速构建漂亮界面
- @ai-sdk/react:Vercel官方AI SDK,简化流式输出处理
AI与数据处理
- OpenAI GPT-4o-mini:轻量但强大的语言模型
- RAG技术:检索增强生成,让AI回答更准确
- Supabase:后端即服务,内置向量数据库
- LangChain:AI应用开发框架,简化数据处理流程
核心实现:RAG技术深度解析
什么是RAG?
RAG(Retrieval-Augmented Generation)检索增强生成是当前最火的AI应用技术之一。简单来说,它让AI在回答问题前,先从一个知识库中检索相关信息,然后基于这些信息生成回答,这样既保证了准确性,又避免了AI的"胡言乱语"。
知识库构建过程
// 爬取网页内容并向量化存储
const loadData = async (webpages: string[]) => {
for (const url of webpages) {
const content = await scrapePage(url);
const chunks = await splitter.splitText(content);
for (const chunk of chunks) {
const { embedding } = await embed({
model: openai.embedding('text-embedding-3-small'),
value: chunk
});
// 存储到Supabase向量数据库
await supabase.from('chunks').insert({
content: chunk,
vector: embedding,
url: url,
});
}
}
}
这个过程很有意思:
- 网页爬取:使用Puppeteer无头浏览器爬取手机相关的Wikipedia页面
- 文本分块:用LangChain的RecursiveCharacterTextSplitter把长文本切成512字符的小块
- 向量化:通过OpenAI的text-embedding-3-small模型将文本转换为1536维的向量
- 向量存储:将向量和原文存储到Supabase的PostgreSQL数据库中
相似度搜索的魔法
在Supabase中,我们创建了一个特殊的函数来处理向量相似度搜索:
CREATE OR REPLACE FUNCTION get_relevant_chunks(
query_vector vector(1536),
match_threshold float,
match_count int
)
RETURNS TABLE (
id uuid,
content text,
url text,
date_updated timestamp,
similarity float
)
LANGUAGE sql stable
AS $$
SELECT
id,
content,
url,
date_updated,
1 - (chunks.vector <=> query_vector) as similarity
FROM chunks
WHERE 1 - (chunks.vector <=> query_vector) > match_threshold
ORDER BY similarity DESC
LIMIT match_count;
$$;
这里用到了PostgreSQL的pgvector扩展,<=>操作符计算向量间的余弦距离,1减去这个距离就是相似度。我们只返回相似度超过0.7的最相关的3个文本块。
前端实现:流畅的聊天体验
使用AI SDK简化流式输出
// 一行代码搞定复杂的流式输出逻辑!
const {
input,
messages,
status,
handleInputChange,
handleSubmit,
} = useChat();
@ai-sdk/react的useChat hook真是开发者的福音!它封装了所有与AI对话相关的状态管理,让我们可以专注于UI实现。
响应式设计技巧
<main className="max-w-3xl mx-auto p-4">
<div className="space-y-4 mb-4 max-h-[80vh] overflow-y-auto">
{/* 聊天内容 */}
</div>
{/* 输入框 */}
</main>
这里用到了Tailwind CSS的移动优先设计:
max-w-3xl:在PC端限制最大宽度为768px,居中显示- 移动端自动撑满整个宽度
max-h-[80vh]:聊天区域最大高度为视口的80%,确保输入框始终可见
Markdown渲染优化
const AssistantChat = ({ content }: { content: string }) => {
return (
<div className='pr-8 w-full mb-6'>
<ReactMarkdown
components={{
a: ({ href, children }) => (
<a target='_blank' href={href}>{children}</a>
),
}}
>{content}</ReactMarkdown>
</div>
)
}
AI的回答通常是Markdown格式,我们用react-markdown来渲染,特别处理了链接,让它们在新标签页打开,提升用户体验。
后端API:智能检索与生成
精心设计的Prompt模板
const createPrompt = (context: string, userQuestion: string) => {
return {
role: 'system',
content: `
You are a helpful assistant that provides information about the latest smartphones.
Use the following context to answer questions:
----------------
START CONTEXT
${context}
END CONTEXT
----------------
Return the answer in markdown format including relevant links and the date when the information was last updated.
Where the above context does not provide enough information relating to the question provide an answer based on your own knowledge but caveat it so the user
knows that it may not be up to date.
If the user asks a question that is not related to a smartphone, politely inform them that you can only answer questions about smartphones.
----------------
QUESTION: ${userQuestion}
----------------
`,
}
}
这个Prompt模板设计得很巧妙:
- 明确身份:指定AI是手机信息助手
- 上下文分离:清晰划分context和question
- 格式要求:要求返回Markdown格式,包含链接和更新日期
- 边界处理:当知识库信息不足时,基于自身知识回答但要提醒用户可能不是最新信息
- 范围限制:只回答手机相关问题
流式响应处理
export async function POST(req: Request) {
try {
const { messages } = await req.json();
const latestMessage = messages.at(-1).content;
// 1. 将用户问题转换为向量
const { embedding } = await generateEmbedding(latestMessage);
// 2. 从知识库检索相关上下文
const context = await fetchRelevantContext(embedding);
// 3. 构建Prompt并调用AI
const prompt = createPrompt(context, latestMessage);
const result = streamText({
model: openai.chat('gpt-4o-mini'),
messages: [prompt, ...messages],
})
// 4. 返回流式响应
return result.toDataStreamResponse();
} catch (error) {
throw error;
}
}
开发中的挑战与解决方案
1. TypeScript配置问题
在运行seed脚本时,遇到了ts-node不支持ESM模块的问题。解决方案是在tsconfig.json中配置:
{
"compilerOptions": {
"module": "CommonJS",
"target": "ES2020",
"esModuleInterop": true
}
}
2. 向量相似度计算
最初想用MySQL,但发现对向量计算支持不够好,最终选择PostgreSQL+pgvector扩展,性能完美满足需求。
3. 爬虫内容清洗
Wikipedia页面包含大量HTML标签,用正则表达式/<[^>]*>?/gm进行清洗,提取纯文本内容。
项目亮点总结
技术亮点
- 现代化技术栈:Next.js 15 + TypeScript + Tailwind CSS
- AI最佳实践:RAG技术确保回答准确性
- 流式输出:使用AI SDK实现流畅的打字机效果
- 向量搜索:基于余弦相似度的智能检索
- 响应式设计:移动端优先的UI体验
架构亮点
- 清晰的关注点分离:前端、API、数据处理各司其职
- 可扩展的知识库:轻松添加新的手机信息源
- 模块化组件设计:ChatInput、ChatOutput独立可复用
- 类型安全:完整的TypeScript类型定义
部署与性能优化
项目部署在Vercel上,利用其全球CDN确保快速访问。Supabase选择离用户最近的数据中心,减少网络延迟。
性能优化方面:
- 使用GPT-4o-mini平衡性能与成本
- 限制检索结果数量,避免上下文过长
- 实现流式输出,减少用户等待时间
未来规划
- 扩展知识库:加入更多手机品牌和型号
- 多语言支持:覆盖中文等更多语言
- 实时信息:接入手机发布会直播等信息源
- 对比功能:支持多款手机的参数对比
- 价格追踪:整合各平台的价格信息
结语
通过phoneGPT这个项目,我们看到了现代AI应用开发的完整流程:从数据采集、向量化存储,到智能检索、流式生成,再到优雅的前端展示。RAG技术让AI回答更加准确可靠,而整个技术栈的选择体现了当前前端和AI开发的最佳实践。
这个项目不仅是一个实用的工具,更是学习AI应用开发的绝佳案例。无论你是想了解AI技术,还是准备构建自己的AI应用,希望这篇分享都能给你带来启发。
技术的魅力在于将复杂的能力封装成简单的体验。phoneGPT背后是众多先进技术的融合,但用户看到的只是一个友好的聊天界面——这大概就是工程师最大的成就感吧!