js開(kāi)發(fā)一個(gè)類(lèi)似ChatGPT的AI應(yīng)用助手
"你說(shuō)我們能不能開(kāi)發(fā)一個(gè)類(lèi)似 ChatGPT 的應(yīng)用?"上個(gè)月,一位創(chuàng)業(yè)朋友找到我,想做一個(gè)垂直領(lǐng)域的 AI 助手。作為一個(gè)經(jīng)常和 AI API 打交道的全棧開(kāi)發(fā)者,這個(gè)想法立刻勾起了我的興趣。不過(guò)說(shuō)實(shí)話(huà),從零開(kāi)始構(gòu)建一個(gè) AI 應(yīng)用,還是讓我有點(diǎn)小緊張。
經(jīng)過(guò)一個(gè)月的開(kāi)發(fā)迭代,我們成功上線(xiàn)了第一個(gè)版本,用戶(hù)反饋出奇的好。今天就來(lái)分享這個(gè)過(guò)程中的技術(shù)選型、架構(gòu)設(shè)計(jì)和實(shí)戰(zhàn)經(jīng)驗(yàn)。
技術(shù)選型
首先面臨的是技術(shù)棧的選擇。考慮到實(shí)時(shí)性、性能和開(kāi)發(fā)效率,我們最終選定了這套技術(shù)棧:
// 項(xiàng)目技術(shù)棧
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'
}
}
核心功能實(shí)現(xiàn)
1. 流式響應(yīng)的實(shí)現(xiàn)
最關(guān)鍵的是實(shí)現(xiàn)打字機(jī)效果的流式響應(yīng):
// 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()
// 調(diào)用 OpenAI API 獲取流式響應(yīng)
const stream = await OpenAIStream({
model: 'gpt-4',
messages,
temperature: 0.7,
stream: true
})
// 返回流式響應(yīng)
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('請(qǐng)求失敗')
// 處理流式響應(yīng)
const reader = response.body!.getReader()
const decoder = new TextDecoder()
let aiResponse = ''
while (true) {
const { done, value } = await reader.read()
if (done) break
// 解碼并追加新內(nèi)容
aiResponse += decoder.decode(value)
// 更新UI
setMessages(prev => [...prev.slice(0, -1), { role: 'assistant', content: aiResponse }])
}
} catch (error) {
console.error('聊天出錯(cuò):', 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. 上下文記憶系統(tǒng)
為了讓對(duì)話(huà)更連貫,我們實(shí)現(xiàn)了基于向量數(shù)據(jù)庫(kù)的上下文記憶系統(tǒng):
// 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')
// 將對(duì)話(huà)轉(zhuǎn)換為向量
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()
}
}
})
)
// 存儲(chǔ)向量
await index.upsert({
upsertRequest: {
vectors
}
})
}
async retrieveContext(query: string, limit = 5) {
const index = this.pinecone.Index('conversations')
const queryVector = await this.embeddings.embedQuery(query)
// 查詢(xún)相似向量
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. 提示詞優(yōu)化
好的提示詞對(duì) AI 輸出質(zhì)量至關(guān)重要:
// lib/prompts.ts
export const createChatPrompt = (context: string, query: string) => ({
messages: [
{
role: 'system',
content: `你是一個(gè)專(zhuān)業(yè)的AI助手。請(qǐng)基于以下上下文信息,
用簡(jiǎn)潔專(zhuān)業(yè)的語(yǔ)言回答用戶(hù)問(wèn)題。如果問(wèn)題超出上下文范圍,
請(qǐng)誠(chéng)實(shí)告知。
上下文信息:
${context}
`
},
{
role: 'user',
content: query
}
],
temperature: 0.7, // 控制創(chuàng)造性
max_tokens: 1000, // 控制回答長(zhǎng)度
presence_penalty: 0.6, // 鼓勵(lì)話(huà)題擴(kuò)展
frequency_penalty: 0.5 // 避免重復(fù)
})
性能優(yōu)化
AI 應(yīng)用的性能優(yōu)化主要從這幾個(gè)方面入手:
- 請(qǐng)求優(yōu)化
// hooks/useChat.ts
export function useChat() {
const [messages, setMessages] = useState<Message[]>([])
// 使用防抖避免頻繁請(qǐng)求
const debouncedChat = useMemo(
() =>
debounce(async (content: string) => {
// ... 發(fā)送請(qǐng)求
}, 500),
[]
)
// 使用緩存避免重復(fù)請(qǐng)求
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
}
// 發(fā)送請(qǐng)求
await debouncedChat(content)
}
return { messages, sendMessage }
}
- 流式傳輸優(yōu)化:
// 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 })
// 按完整的句子進(jìn)行處理
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]
}
}
}
部署與監(jiān)控
我們使用了 Vercel 進(jìn)行部署,并建立了完整的監(jiān)控體系:
// lib/monitoring.ts
export class AIMonitoring {
// 記錄請(qǐng)求延遲
async trackLatency(startTime: number) {
const duration = Date.now() - startTime
await this.metrics.gauge('ai_request_latency', duration)
}
// 監(jiān)控令牌使用
async trackTokenUsage(prompt: string, response: string) {
const tokenCount = await this.countTokens(prompt + response)
await this.metrics.increment('token_usage', tokenCount)
}
// 監(jiān)控錯(cuò)誤率
async trackError(error: Error) {
await this.metrics.increment('ai_errors', 1, {
type: error.name,
message: error.message
})
}
}
實(shí)踐心得
開(kāi)發(fā) AI 應(yīng)用的過(guò)程中,我學(xué)到了很多:
- 流式響應(yīng)是提升用戶(hù)體驗(yàn)的關(guān)鍵
- 上下文管理要平衡準(zhǔn)確性和性能
- 錯(cuò)誤處理和降級(jí)策略很重要
- 持續(xù)優(yōu)化提示詞能帶來(lái)明顯提升
最讓我驚喜的是用戶(hù)的反饋。有用戶(hù)說(shuō):"這是我用過(guò)的響應(yīng)最快的 AI 應(yīng)用!"這讓我們備受鼓舞。
寫(xiě)在最后
AI 應(yīng)用開(kāi)發(fā)是一個(gè)充滿(mǎn)挑戰(zhàn)但也充滿(mǎn)機(jī)遇的領(lǐng)域。關(guān)鍵是要專(zhuān)注用戶(hù)體驗(yàn),不斷優(yōu)化和迭代。正如那句話(huà)說(shuō)的:"AI不是魔法,而是工程。"
到此這篇關(guān)于js開(kāi)發(fā)一個(gè)類(lèi)似ChatGPT的AI應(yīng)用助手的文章就介紹到這了,更多相關(guān)js開(kāi)發(fā)一個(gè)AI應(yīng)用助手內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解Web使用webpack構(gòu)建前端項(xiàng)目
本篇文章主要介紹了詳解Web使用webpack構(gòu)建前端項(xiàng)目,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-09-09
js根據(jù)當(dāng)前日期獲取前一周或者后一周等日期
有的時(shí)候要獲取當(dāng)前日期,或者前一天、后一天的日期,下面這篇文章主要給大家介紹了關(guān)于js根據(jù)當(dāng)前日期獲取前一周或者后一周等日期的相關(guān)資料,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-04-04
JavaScript 網(wǎng)頁(yè)中實(shí)現(xiàn)一個(gè)計(jì)算當(dāng)年還剩多少時(shí)間的倒數(shù)計(jì)時(shí)程序
這篇文章主要介紹了JavaScript 網(wǎng)頁(yè)中實(shí)現(xiàn)一個(gè)計(jì)算當(dāng)年還剩多少時(shí)間的倒數(shù)計(jì)時(shí)程序,需要的朋友可以參考下2017-01-01
js實(shí)現(xiàn)一個(gè)簡(jiǎn)單的數(shù)字時(shí)鐘效果
本文主要介紹了js實(shí)現(xiàn)一個(gè)簡(jiǎn)單的數(shù)字時(shí)鐘效果的示例代碼。具有很好的參考價(jià)值,下面跟著小編一起來(lái)看下吧2017-03-03
TS?中?never,void,unknown類(lèi)型詳解
這篇文章主要介紹了TS?中?never,void,unknown類(lèi)型詳解,never?類(lèi)型是 TypeScript 中的底層類(lèi)型,unknown是TypeScript中比較特殊的一種類(lèi)型,它用于描述類(lèi)型不確定的變量,本文給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-05-05
如何讓div span等元素能響應(yīng)鍵盤(pán)事件操作指南
在我這幾天的工作中遇到了一個(gè)問(wèn)題,我有一個(gè)可編輯的div,并且在DIV里面還有一個(gè)可編輯的span,我想要讓span能響應(yīng)鍵盤(pán)事,想實(shí)現(xiàn)這種效果,應(yīng)該如何實(shí)踐呢2012-11-11

