"你說我們能不能開發一個類似 ChatGPT 的應用?"上個月,一位創業朋友找到我,想做一個垂直領域的 AI 助手。作為一個經常和 AI API 打交道的全棧開發者,這個想法立刻勾起了我的興趣。不過說實話,從零開始構建一個 AI 應用,還是讓我有點小緊張。
經過一個月的開發迭代,我們成功上線了第一個版本,使用者反饋出奇的好。今天就來分享這個過程中的技術選型、架構設計和實戰經驗。
技術選型
首先面臨的是技術棧的選擇。考慮到實時性、效能和開發效率,我們最終選定了這套技術棧:
// 專案技術棧
const techStack = {
frontend: {
framework: 'Next.js 14', // App Router + React Server Components
ui: 'Tailwind CSS + Shadcn UI',
state: 'Zustand',
realtime: 'Server-Sent Events'
},
backend: {
runtime: 'Node.js',
framework: 'Next.js API Routes',
database: 'PostgreSQL + Prisma',
cache: 'Redis'
},
ai: {
provider: 'OpenAI API',
framework: 'Langchain',
vectorStore: 'PineconeDB'
}
}
核心功能實現
1. 流式響應的實現
最關鍵的是實現打字機效果的流式響應:
// app/api/chat/route.ts
import { OpenAIStream } from '@/lib/openai'
import { StreamingTextResponse } from 'ai'
export async function POST(req: Request) {
const { messages } = await req.json()
// 呼叫 OpenAI API 獲取流式響應
const stream = await OpenAIStream({
model: 'gpt-4',
messages,
temperature: 0.7,
stream: true
})
// 返回流式響應
return new StreamingTextResponse(stream)
}
// components/Chat.tsx
function Chat() {
const [messages, setMessages] = useState<Message[]>([])
const [isLoading, setIsLoading] = useState(false)
const handleSubmit = async (content: string) => {
setIsLoading(true)
try {
const response = await fetch('/api/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
messages: [...messages, { role: 'user', content }]
})
})
if (!response.ok) throw new Error('請求失敗')
// 處理流式響應
const reader = response.body!.getReader()
const decoder = new TextDecoder()
let aiResponse = ''
while (true) {
const { done, value } = await reader.read()
if (done) break
// 解碼並追加新內容
aiResponse += decoder.decode(value)
// 更新UI
setMessages(prev => [...prev.slice(0, -1), { role: 'assistant', content: aiResponse }])
}
} catch (error) {
console.error('聊天出錯:', error)
} finally {
setIsLoading(false)
}
}
return (
<div className='flex flex-col h-screen'>
<div className='flex-1 overflow-auto p-4'>
{messages.map((message, index) => (
<Message key={index} {...message} />
))}
{isLoading && <TypingIndicator />}
</div>
<ChatInput onSubmit={handleSubmit} disabled={isLoading} />
</div>
)
}
2. 上下文記憶系統
為了讓對話更連貫,我們實現了基於向量資料庫的上下文記憶系統:
// lib/vectorStore.ts
import { PineconeClient } from '@pinecone-database/pinecone'
import { OpenAIEmbeddings } from 'langchain/embeddings/openai'
export class VectorStore {
private pinecone: PineconeClient
private embeddings: OpenAIEmbeddings
constructor() {
this.pinecone = new PineconeClient()
this.embeddings = new OpenAIEmbeddings()
}
async initialize() {
await this.pinecone.init({
environment: process.env.PINECONE_ENV!,
apiKey: process.env.PINECONE_API_KEY!
})
}
async storeConversation(messages: Message[]) {
const index = this.pinecone.Index('conversations')
// 將對話轉換為向量
const vectors = await Promise.all(
messages.map(async message => {
const vector = await this.embeddings.embedQuery(message.content)
return {
id: message.id,
values: vector,
metadata: {
role: message.role,
timestamp: Date.now()
}
}
})
)
// 儲存向量
await index.upsert({
upsertRequest: {
vectors
}
})
}
async retrieveContext(query: string, limit = 5) {
const index = this.pinecone.Index('conversations')
const queryVector = await this.embeddings.embedQuery(query)
// 查詢相似向量
const results = await index.query({
queryRequest: {
vector: queryVector,
topK: limit,
includeMetadata: true
}
})
return results.matches.map(match => ({
content: match.metadata.content,
score: match.score
}))
}
}
3. 提示詞最佳化
好的提示詞對 AI 輸出質量至關重要:
// lib/prompts.ts
export const createChatPrompt = (context: string, query: string) => ({
messages: [
{
role: 'system',
content: `你是一個專業的AI助手。請基於以下上下文資訊,
用簡潔專業的語言回答使用者問題。如果問題超出上下文範圍,
請誠實告知。
上下文資訊:
${context}
`
},
{
role: 'user',
content: query
}
],
temperature: 0.7, // 控制創造性
max_tokens: 1000, // 控制回答長度
presence_penalty: 0.6, // 鼓勵話題擴充套件
frequency_penalty: 0.5 // 避免重複
})
效能最佳化
AI 應用的效能最佳化主要從這幾個方面入手:
- 請求最佳化
// hooks/useChat.ts
export function useChat() {
const [messages, setMessages] = useState<Message[]>([])
// 使用防抖避免頻繁請求
const debouncedChat = useMemo(
() =>
debounce(async (content: string) => {
// ... 傳送請求
}, 500),
[]
)
// 使用快取避免重複請求
const cache = useMemo(() => new Map<string, string>(), [])
const sendMessage = async (content: string) => {
// 檢查快取
if (cache.has(content)) {
setMessages(prev => [...prev, { role: 'assistant', content: cache.get(content)! }])
return
}
// 傳送請求
await debouncedChat(content)
}
return { messages, sendMessage }
}
- 流式傳輸最佳化:
// lib/streaming.ts
export class StreamProcessor {
private buffer: string = ''
private decoder = new TextDecoder()
process(chunk: Uint8Array, callback: (text: string) => void) {
this.buffer += this.decoder.decode(chunk, { stream: true })
// 按完整的句子進行處理
const sentences = this.buffer.split(/([.!?。!?]\s)/)
if (sentences.length > 1) {
// 輸出完整的句子
const completeText = sentences.slice(0, -1).join('')
callback(completeText)
// 保留未完成的部分
this.buffer = sentences[sentences.length - 1]
}
}
}
部署與監控
我們使用了 Vercel 進行部署,並建立了完整的監控體系:
// lib/monitoring.ts
export class AIMonitoring {
// 記錄請求延遲
async trackLatency(startTime: number) {
const duration = Date.now() - startTime
await this.metrics.gauge('ai_request_latency', duration)
}
// 監控令牌使用
async trackTokenUsage(prompt: string, response: string) {
const tokenCount = await this.countTokens(prompt + response)
await this.metrics.increment('token_usage', tokenCount)
}
// 監控錯誤率
async trackError(error: Error) {
await this.metrics.increment('ai_errors', 1, {
type: error.name,
message: error.message
})
}
}
實踐心得
開發 AI 應用的過程中,我學到了很多:
- 流式響應是提升使用者體驗的關鍵
- 上下文管理要平衡準確性和效能
- 錯誤處理和降級策略很重要
- 持續最佳化提示詞能帶來明顯提升
最讓我驚喜的是使用者的反饋。有使用者說:"這是我用過的響應最快的 AI 應用!"這讓我們備受鼓舞。
寫在最後
AI 應用開發是一個充滿挑戰但也充滿機遇的領域。關鍵是要專注使用者體驗,不斷最佳化和迭代。正如那句話說的:"AI �� 是魔法,而是工程。"
有什麼問題歡迎在評論區討論,我們一起探索 AI 應用開發的更多可能!
如果覺得有幫助,別忘了點贊關注,我會繼續分享更多 AI 開發實戰經驗~