ในตัวอย่างนี้เราจะสร้างแอปพลิเคชั่นแบบเต็มซ้อนที่ใช้การดึง (RAG) ที่ขับเคลื่อนโดย Pinecone เพื่อส่งคำตอบที่ถูกต้องและเกี่ยวข้องกับบริบทใน chatbot
RAG เป็นเครื่องมือที่ทรงพลังที่รวมประโยชน์ของโมเดลที่ใช้การดึงข้อมูลและแบบจำลองการกำเนิด ซึ่งแตกต่างจากแชทบอทแบบดั้งเดิมที่สามารถต่อสู้กับการรักษาข้อมูลที่ทันสมัยหรือเข้าถึงความรู้เฉพาะโดเมนแชทบ็อตที่ใช้ RAG ใช้ฐานความรู้ที่สร้างขึ้นจาก URL ที่รวบรวมไว้เพื่อให้การตอบสนองที่เกี่ยวข้องกับบริบท
การรวม AI SDK ของ Vercel ไว้ในแอปพลิเคชันของเราจะช่วยให้เราตั้งค่าเวิร์กโฟลว์ Chatbot ได้อย่างง่ายดายและใช้การสตรีมอย่างมีประสิทธิภาพมากขึ้นโดยเฉพาะในสภาพแวดล้อมที่ขอบเพิ่มการตอบสนองและประสิทธิภาพของ Chatbot ของเรา
ในตอนท้ายของบทช่วยสอนนี้คุณจะมีแชทบ็อตที่รับรู้บริบทที่ให้คำตอบที่แม่นยำโดยไม่มีภาพหลอนทำให้มั่นใจได้ว่าประสบการณ์ผู้ใช้ที่มีประสิทธิภาพและน่าดึงดูดยิ่งขึ้น มาเริ่มกันในการสร้างเครื่องมือที่ทรงพลังนี้ (รายการรหัสเต็ม)
Next.js เป็นเฟรมเวิร์ก JavaScript ที่ทรงพลังซึ่งช่วยให้เราสามารถสร้างเว็บแอปพลิเคชันที่แสดงผลด้านเซิร์ฟเวอร์และเว็บแอปพลิเคชันแบบคงที่โดยใช้ React เป็นตัวเลือกที่ยอดเยี่ยมสำหรับโครงการของเราเนื่องจากความสะดวกในการติดตั้งประสิทธิภาพที่ยอดเยี่ยมและคุณสมบัติในตัวเช่นเส้นทางการกำหนดเส้นทางและเส้นทาง API
หากต้องการสร้างแอป Next.js ใหม่ให้เรียกใช้คำสั่งต่อไปนี้:
npx create-next-app chatbot
ต่อไปเราจะเพิ่มแพ็คเกจ ai
:
npm install ai
คุณสามารถใช้รายการการพึ่งพาทั้งหมดหากคุณต้องการสร้างพร้อมกับการสอน
ในขั้นตอนนี้เราจะใช้ Vercel SDK เพื่อสร้างแบ็กเอนด์และส่วนหน้าของ chatbot ของเราภายในแอปพลิเคชันถัดไป JS ในตอนท้ายของขั้นตอนนี้แชทบ็อตพื้นฐานของเราจะพร้อมใช้งานพร้อมให้เราเพิ่มความสามารถในการรับรู้บริบทในขั้นตอนต่อไปนี้ เริ่มต้นกันเถอะ
ตอนนี้เรามามุ่งเน้นไปที่องค์ประกอบส่วนหน้าของ Chatbot ของเรา เรากำลังจะสร้างองค์ประกอบที่ผู้ใช้หันหน้าเข้าหาบอทของเราสร้างอินเทอร์เฟซที่ผู้ใช้จะโต้ตอบกับแอปพลิเคชันของเรา สิ่งนี้จะเกี่ยวข้องกับการสร้างการออกแบบและฟังก์ชั่นของอินเทอร์เฟซแชทภายในแอปพลิเคชัน Next.js ของเรา
ก่อนอื่นเราจะสร้างองค์ประกอบ Chat
ที่จะทำให้อินเทอร์เฟซแชท
import React , { FormEvent , ChangeEvent } from "react" ;
import Messages from "./Messages" ;
import { Message } from "ai/react" ;
interface Chat {
input : string ;
handleInputChange : ( e : ChangeEvent < HTMLInputElement > ) => void ;
handleMessageSubmit : ( e : FormEvent < HTMLFormElement > ) => Promise < void > ;
messages : Message [ ] ;
}
const Chat : React . FC < Chat > = ( {
input ,
handleInputChange ,
handleMessageSubmit ,
messages ,
} ) => {
return (
< div id = "chat" className = "..." >
< Messages messages = { messages } / >
< >
< form onSubmit = { handleMessageSubmit } className = "..." >
< input
type = "text"
className = "..."
value = { input }
onChange = { handleInputChange }
/ >
< span className = "..." > Press ⮐ to send < / span >
< / form >
< / >
< / div >
) ;
} ;
export default Chat ;
ส่วนประกอบนี้จะแสดงรายการข้อความและแบบฟอร์มอินพุตเพื่อให้ผู้ใช้ส่งข้อความ ส่วนประกอบ Messages
เพื่อแสดงข้อความแชท:
import { Message } from "ai" ;
import { useRef } from "react" ;
export default function Messages ( { messages } : { messages : Message [ ] } ) {
const messagesEndRef = useRef < HTMLDivElement | null > ( null ) ;
return (
< div className = "..." >
{ messages . map ( ( msg , index ) => (
< div
key = { index }
className = { ` ${
msg . role === "assistant" ? "text-green-300" : "text-blue-300"
} ... ` }
>
< div className = "..." > { msg . role === "assistant" ? "?" : "?" } < / div >
< div className = "..." > { msg . content } < / div >
< / div >
) ) }
< div ref = { messagesEndRef } / >
< / div >
) ;
}
ส่วนประกอบ Page
หลักของเราจะจัดการสถานะสำหรับข้อความที่แสดงในองค์ประกอบ Chat
:
"use client" ;
import Header from "@/components/Header" ;
import Chat from "@/components/Chat" ;
import { useChat } from "ai/react" ;
const Page : React . FC = ( ) => {
const [ context , setContext ] = useState < string [ ] | null > ( null ) ;
const { messages , input , handleInputChange , handleSubmit } = useChat ( ) ;
return (
< div className = "..." >
< Header className = "..." / >
< div className = "..." >
< Chat
input = { input }
handleInputChange = { handleInputChange }
handleMessageSubmit = { handleSubmit }
messages = { messages }
/ >
< / div >
< / div >
) ;
} ;
export default Page ;
useChat
HOOK ที่มีประโยชน์จะจัดการสถานะสำหรับข้อความที่แสดงในองค์ประกอบ Chat
มันจะ:
ต่อไปเราจะตั้งค่าจุดสิ้นสุดของ Chatbot API นี่คือส่วนประกอบฝั่งเซิร์ฟเวอร์ที่จะจัดการกับคำขอและการตอบกลับสำหรับ chatbot ของเรา เราจะสร้างไฟล์ใหม่ที่เรียกว่า api/chat/route.ts
และเพิ่มการอ้างอิงต่อไปนี้:
import { Configuration , OpenAIApi } from "openai-edge" ;
import { Message , OpenAIStream , StreamingTextResponse } from "ai" ;
การพึ่งพาครั้งแรกคือแพ็คเกจ openai-edge
ซึ่งทำให้ง่ายต่อการโต้ตอบกับ API ของ OpenAI ในสภาพแวดล้อมที่ขอบ การพึ่งพาครั้งที่สองคือแพ็คเกจ ai
ที่เราจะใช้เพื่อกำหนด Message
และประเภท OpenAIStream
ซึ่งเราจะใช้เพื่อสตรีมการตอบกลับจาก OpenAI กลับไปยังไคลเอนต์
ถัดไปเริ่มต้นไคลเอนต์ OpenAI:
// Create an OpenAI API client (that's edge friendly!)
const config = new Configuration ( {
apiKey : process . env . OPENAI_API_KEY ,
} ) ;
const openai = new OpenAIApi ( config ) ;
ในการกำหนดจุดสิ้นสุดนี้เป็นฟังก์ชันขอบเราจะกำหนดและส่งออกตัวแปร runtime
export const runtime = "edge" ;
ต่อไปเราจะกำหนดตัวจัดการปลายทาง:
export async function POST ( req : Request ) {
try {
const { messages } = await req . json ( ) ;
const prompt = [
{
role : "system" ,
content : `AI assistant is a brand new, powerful, human-like artificial intelligence.
The traits of AI include expert knowledge, helpfulness, cleverness, and articulateness.
AI is a well-behaved and well-mannered individual.
AI is always friendly, kind, and inspiring, and he is eager to provide vivid and thoughtful responses to the user.
AI has the sum of all knowledge in their brain, and is able to accurately answer nearly any question about any topic in conversation.
AI assistant is a big fan of Pinecone and Vercel.
` ,
} ,
] ;
// Ask OpenAI for a streaming chat completion given the prompt
const response = await openai . createChatCompletion ( {
model : "gpt-3.5-turbo" ,
stream : true ,
messages : [
... prompt ,
... messages . filter ( ( message : Message ) => message . role === "user" ) ,
] ,
} ) ;
// Convert the response into a friendly text-stream
const stream = OpenAIStream ( response ) ;
// Respond with the stream
return new StreamingTextResponse ( stream ) ;
} catch ( e ) {
throw e ;
}
}
ที่นี่เราแยกแยะข้อความจากโพสต์และสร้างพรอมต์เริ่มต้นของเรา เราใช้พรอมต์และข้อความเป็นอินพุตไปยังวิธี createChatCompletion
จากนั้นเราจะแปลงการตอบกลับเป็นสตรีมและส่งคืนไปยังลูกค้า โปรดทราบว่าในตัวอย่างนี้เราจะส่งข้อความของผู้ใช้ไปยัง OpenAI เท่านั้น (ตรงข้ามกับการรวมข้อความของบอทด้วย)
ในขณะที่เราดำน้ำในการสร้าง chatbot ของเราสิ่งสำคัญคือต้องเข้าใจบทบาทของบริบท การเพิ่มบริบทให้กับคำตอบของ Chatbot ของเราเป็นกุญแจสำคัญในการสร้างประสบการณ์การใช้งานที่เป็นธรรมชาติและเป็นธรรมชาติมากขึ้น หากไม่มีบริบทการตอบสนองของแชทบอทอาจรู้สึกไม่ถูกแยกหรือไม่เกี่ยวข้อง โดยการทำความเข้าใจบริบทของการสืบค้นของผู้ใช้ chatbot ของเราจะสามารถให้คำตอบที่แม่นยำมีความเกี่ยวข้องและมีส่วนร่วมมากขึ้น ตอนนี้เรามาเริ่มสร้างเป้าหมายนี้กันเถอะ
ก่อนอื่นเราจะมุ่งเน้นไปที่การเพาะฐานความรู้ เราจะสร้างตัวรวบรวมข้อมูลและสคริปต์เมล็ดและตั้งค่าจุดสิ้นสุดการรวบรวมข้อมูล สิ่งนี้จะช่วยให้เราสามารถรวบรวมและจัดระเบียบข้อมูล chatbot ของเราจะใช้เพื่อให้คำตอบที่เกี่ยวข้องกับบริบท
หลังจากที่เราได้เติมฐานความรู้ของเราเราจะดึงการแข่งขันจากการฝังตัวของเรา สิ่งนี้จะช่วยให้แชทบ็อตของเราค้นหาข้อมูลที่เกี่ยวข้องตามแบบสอบถามของผู้ใช้
ต่อไปเราจะห่อตรรกะของเราลงในฟังก์ชั่น getContext และอัปเดตพรอมต์ของ Chatbot ของเรา สิ่งนี้จะปรับปรุงรหัสของเราและปรับปรุงประสบการณ์การใช้งานของผู้ใช้โดยมั่นใจว่าการแจ้งเตือนของ Chatbot นั้นมีความเกี่ยวข้องและมีส่วนร่วม
สุดท้ายเราจะเพิ่มแผงบริบทและจุดสิ้นสุดบริบทที่เกี่ยวข้อง สิ่งเหล่านี้จะให้อินเทอร์เฟซผู้ใช้สำหรับ chatbot และวิธีในการดึงบริบทที่จำเป็นสำหรับการสืบค้นผู้ใช้แต่ละคน
ขั้นตอนนี้เป็นเรื่องเกี่ยวกับการให้ข้อมูลกับ chatbot ของเราข้อมูลที่ต้องการและตั้งค่าโครงสร้างพื้นฐานที่จำเป็นเพื่อให้สามารถดึงและใช้ข้อมูลนั้นได้อย่างมีประสิทธิภาพ เริ่มต้นกันเถอะ
ตอนนี้เราจะย้ายไปที่ฐานความรู้แหล่งข้อมูลพื้นฐานที่จะแจ้งคำตอบของ Chatbot ของเรา ขั้นตอนนี้เกี่ยวข้องกับการรวบรวมและจัดระเบียบข้อมูล chatbot ของเราจำเป็นต้องทำงานอย่างมีประสิทธิภาพ ในคู่มือนี้เราจะใช้ข้อมูลที่ดึงมาจากเว็บไซต์ต่าง ๆ ซึ่งเราจะถามคำถามเกี่ยวกับในภายหลัง ในการทำเช่นนี้เราจะสร้างตัวรวบรวมข้อมูลที่จะขูดข้อมูลจากเว็บไซต์ฝังและเก็บไว้ใน Pinecone
เพื่อความกะทัดรัดคุณจะสามารถค้นหารหัสเต็มสำหรับตัวรวบรวมข้อมูลได้ที่นี่ นี่คือส่วนที่เกี่ยวข้อง:
class Crawler {
private seen = new Set < string > ( ) ;
private pages : Page [ ] = [ ] ;
private queue : { url : string ; depth : number } [ ] = [ ] ;
constructor ( private maxDepth = 2 , private maxPages = 1 ) { }
async crawl ( startUrl : string ) : Promise < Page [ ] > {
// Add the start URL to the queue
this . addToQueue ( startUrl ) ;
// While there are URLs in the queue and we haven't reached the maximum number of pages...
while ( this . shouldContinueCrawling ( ) ) {
// Dequeue the next URL and depth
const { url , depth } = this . queue . shift ( ) ! ;
// If the depth is too great or we've already seen this URL, skip it
if ( this . isTooDeep ( depth ) || this . isAlreadySeen ( url ) ) continue ;
// Add the URL to the set of seen URLs
this . seen . add ( url ) ;
// Fetch the page HTML
const html = await this . fetchPage ( url ) ;
// Parse the HTML and add the page to the list of crawled pages
this . pages . push ( { url , content : this . parseHtml ( html ) } ) ;
// Extract new URLs from the page HTML and add them to the queue
this . addNewUrlsToQueue ( this . extractUrls ( html , url ) , depth ) ;
}
// Return the list of crawled pages
return this . pages ;
}
// ... Some private methods removed for brevity
private async fetchPage ( url : string ) : Promise < string > {
try {
const response = await fetch ( url ) ;
return await response . text ( ) ;
} catch ( error ) {
console . error ( `Failed to fetch ${ url } : ${ error } ` ) ;
return "" ;
}
}
private parseHtml ( html : string ) : string {
const $ = cheerio . load ( html ) ;
$ ( "a" ) . removeAttr ( "href" ) ;
return NodeHtmlMarkdown . translate ( $ . html ( ) ) ;
}
private extractUrls ( html : string , baseUrl : string ) : string [ ] {
const $ = cheerio . load ( html ) ;
const relativeUrls = $ ( "a" )
. map ( ( _ , link ) => $ ( link ) . attr ( "href" ) )
. get ( ) as string [ ] ;
return relativeUrls . map (
( relativeUrl ) => new URL ( relativeUrl , baseUrl ) . href
) ;
}
}
คลาส Crawler
เป็นนักรวบรวมข้อมูลเว็บที่เข้าชม URL เริ่มต้นจากจุดที่กำหนดและรวบรวมข้อมูลจากพวกเขา มันทำงานภายในความลึกที่แน่นอนและจำนวนหน้าสูงสุดตามที่กำหนดไว้ในตัวสร้าง วิธีการรวบรวมข้อมูลเป็นฟังก์ชั่นหลักที่เริ่มกระบวนการรวบรวมข้อมูล
วิธีการของผู้ช่วย FetchPage, Parsehtml และ extracturls ตามลำดับจัดการกับการดึงเนื้อหา HTML ของหน้าแยกวิเคราะห์ HTML เพื่อแยกข้อความและแยก URL ทั้งหมดออกจากหน้าเพื่อเข้าคิวสำหรับการรวบรวมข้อมูลถัดไป ชั้นเรียนยังเก็บบันทึก URL ที่เข้าชมเพื่อหลีกเลี่ยงการทำซ้ำ
seed
เพื่อผูกสิ่งต่าง ๆ เข้าด้วยกันเราจะสร้างฟังก์ชั่นเมล็ดพันธุ์ที่จะใช้ตัวรวบรวมข้อมูลเพื่อเพาะเมล็ดฐานความรู้ ในส่วนนี้ของรหัสนี้เราจะเริ่มต้นการรวบรวมข้อมูลและดึง URL ที่กำหนดจากนั้นแบ่งเนื้อหาออกเป็นชิ้น ๆ และในที่สุดก็ฝังและจัดทำดัชนีชิ้นใน pinecone
async function seed ( url : string , limit : number , indexName : string , options : SeedOptions ) {
try {
// Initialize the Pinecone client
const pinecone = new Pinecone ( ) ;
// Destructure the options object
const { splittingMethod , chunkSize , chunkOverlap } = options ;
// Create a new Crawler with depth 1 and maximum pages as limit
const crawler = new Crawler ( 1 , limit || 100 ) ;
// Crawl the given URL and get the pages
const pages = await crawler . crawl ( url ) as Page [ ] ;
// Choose the appropriate document splitter based on the splitting method
const splitter : DocumentSplitter = splittingMethod === 'recursive' ?
new RecursiveCharacterTextSplitter ( { chunkSize , chunkOverlap } ) : new MarkdownTextSplitter ( { } ) ;
// Prepare documents by splitting the pages
const documents = await Promise . all ( pages . map ( page => prepareDocument ( page , splitter ) ) ) ;
// Create Pinecone index if it does not exist
const indexList = await pinecone . listIndexes ( ) ;
const indexExists = indexList . some ( index => index . name === indexName )
if ( ! indexExists ) {
await pinecone . createIndex ( {
name : indexName ,
dimension : 1536 ,
waitUntilReady : true ,
} ) ;
}
const index = pinecone . Index ( indexName )
// Get the vector embeddings for the documents
const vectors = await Promise . all ( documents . flat ( ) . map ( embedDocument ) ) ;
// Upsert vectors into the Pinecone index
await chunkedUpsert ( index ! , vectors , '' , 10 ) ;
// Return the first document
return documents [ 0 ] ;
} catch ( error ) {
console . error ( "Error seeding:" , error ) ;
throw error ;
}
}
ในการแก้ไขเนื้อหาเราจะใช้หนึ่งในวิธีการต่อไปนี้:
RecursiveCharacterTextSplitter
- ตัวแยกนี้แยกข้อความออกเป็นชิ้นขนาดที่กำหนดจากนั้นแยกชิ้นส่วนใหม่ออกเป็นชิ้นเล็ก ๆ จนกระทั่งถึงขนาดก้อน วิธีนี้มีประโยชน์สำหรับเอกสารยาวMarkdownTextSplitter
- ตัวแยกนี้แยกข้อความออกเป็นชิ้น ๆ ตามส่วนหัวของ Markdown วิธีนี้มีประโยชน์สำหรับเอกสารที่มีโครงสร้างแล้วโดยใช้ markdown ประโยชน์ของวิธีการนี้คือมันจะแบ่งเอกสารออกเป็นชิ้น ๆ ตามส่วนหัวซึ่งจะเป็นประโยชน์สำหรับ chatbot ของเราเพื่อทำความเข้าใจโครงสร้างของเอกสาร เราสามารถสันนิษฐานได้ว่าแต่ละหน่วยของข้อความภายใต้ส่วนหัวเป็นหน่วยข้อมูลที่เชื่อมโยงกันภายในและเมื่อผู้ใช้ถามคำถามบริบทที่ดึงมาจะสอดคล้องกันภายในเช่นกันcrawl
จุดสิ้นสุดสำหรับจุดสิ้นสุด crawl
นั้นค่อนข้างตรงไปตรงมา มันเรียกฟังก์ชัน seed
และส่งคืนผลลัพธ์
import seed from "./seed" ;
import { NextResponse } from "next/server" ;
export const runtime = "edge" ;
export async function POST ( req : Request ) {
const { url , options } = await req . json ( ) ;
try {
const documents = await seed ( url , 1 , process . env . PINECONE_INDEX ! , options ) ;
return NextResponse . json ( { success : true , documents } ) ;
} catch ( error ) {
return NextResponse . json ( { success : false , error : "Failed crawling" } ) ;
}
}
ตอนนี้แบ็กเอนด์ของเราสามารถรวบรวมข้อมูล URL ที่กำหนดฝังเนื้อหาและจัดทำดัชนี embeddings ใน pinecone จุดสิ้นสุดจะส่งคืนเซ็กเมนต์ทั้งหมดในหน้าเว็บที่เรารวบรวมไว้ดังนั้นเราจะสามารถแสดงได้ ต่อไปเราจะเขียนชุดของฟังก์ชั่นที่จะสร้างบริบทจากการฝังตัวเหล่านี้
ในการดึงเอกสารที่เกี่ยวข้องมากที่สุดจากดัชนีเราจะใช้ฟังก์ชั่น query
ใน Pinecone SDK ฟังก์ชั่นนี้ใช้เวกเตอร์และส่งคืนเวกเตอร์ที่คล้ายกันมากที่สุดจากดัชนี เราจะใช้ฟังก์ชั่นนี้เพื่อดึงเอกสารที่เกี่ยวข้องมากที่สุดจากดัชนีโดยได้รับการฝังตัว
const getMatchesFromEmbeddings = async ( embeddings : number [ ] , topK : number , namespace : string ) : Promise < ScoredPineconeRecord < Metadata > [ ] > => {
// Obtain a client for Pinecone
const pinecone = new Pinecone ( ) ;
const indexName : string = process . env . PINECONE_INDEX || '' ;
if ( indexName === '' ) {
throw new Error ( 'PINECONE_INDEX environment variable not set' )
}
// Retrieve the list of indexes to check if expected index exists
const indexes = await pinecone . listIndexes ( )
if ( indexes . filter ( i => i . name === indexName ) . length !== 1 ) {
throw new Error ( `Index ${ indexName } does not exist` )
}
// Get the Pinecone index
const index = pinecone ! . Index < Metadata > ( indexName ) ;
// Get the namespace
const pineconeNamespace = index . namespace ( namespace ?? '' )
try {
// Query the index with the defined request
const queryResult = await pineconeNamespace . query ( {
vector : embeddings ,
topK ,
includeMetadata : true ,
} )
return queryResult . matches || [ ]
} catch ( e ) {
// Log the error and throw it
console . log ( "Error querying embeddings: " , e )
throw new Error ( `Error querying embeddings: ${ e } ` )
}
}
ฟังก์ชั่นใช้เวลาในการฝังตัวพารามิเตอร์ TOPK และเนมสเปซและส่งคืนการจับคู่ TOPK จากดัชนี pinecone ก่อนอื่นจะได้รับไคลเอนต์ Pinecone ตรวจสอบว่าดัชนีที่ต้องการมีอยู่ในรายการดัชนีและส่งข้อผิดพลาดหากไม่ จากนั้นจะได้รับดัชนี pinecone เฉพาะ ฟังก์ชั่นจากนั้นสอบถามดัชนี pinecone ด้วยคำขอที่กำหนดและส่งคืนการจับคู่
getContext
เราจะรวมสิ่งต่าง ๆ เข้าด้วยกันในฟังก์ชั่น getContext
ฟังก์ชั่นนี้จะใช้ใน message
และส่งคืนบริบท - ไม่ว่าจะเป็นในรูปแบบสตริงหรือเป็นชุดของ ScoredVector
export const getContext = async (
message : string ,
namespace : string ,
maxTokens = 3000 ,
minScore = 0.7 ,
getOnlyText = true
) : Promise < string | ScoredVector [ ] > => {
// Get the embeddings of the input message
const embedding = await getEmbeddings ( message ) ;
// Retrieve the matches for the embeddings from the specified namespace
const matches = await getMatchesFromEmbeddings ( embedding , 3 , namespace ) ;
// Filter out the matches that have a score lower than the minimum score
const qualifyingDocs = matches . filter ( ( m ) => m . score && m . score > minScore ) ;
// If the `getOnlyText` flag is false, we'll return the matches
if ( ! getOnlyText ) {
return qualifyingDocs ;
}
let docs = matches
? qualifyingDocs . map ( ( match ) => ( match . metadata as Metadata ) . chunk )
: [ ] ;
// Join all the chunks of text together, truncate to the maximum number of tokens, and return the result
return docs . join ( "n" ) . substring ( 0 , maxTokens ) ;
} ;
ย้อนกลับไปใน chat/route.ts
เราจะเพิ่มการโทรไปยัง getContext
:
const { messages } = await req . json ( ) ;
// Get the last message
const lastMessage = messages [ messages . length - 1 ] ;
// Get the context from the last message
const context = await getContext ( lastMessage . content , "" ) ;
ในที่สุดเราจะอัปเดตพรอมต์เพื่อรวมบริบทที่เราดึงมาจากฟังก์ชั่น getContext
const prompt = [
{
role : "system" ,
content : `AI assistant is a brand new, powerful, human-like artificial intelligence.
The traits of AI include expert knowledge, helpfulness, cleverness, and articulateness.
AI is a well-behaved and well-mannered individual.
AI is always friendly, kind, and inspiring, and he is eager to provide vivid and thoughtful responses to the user.
AI has the sum of all knowledge in their brain, and is able to accurately answer nearly any question about any topic in conversation.
AI assistant is a big fan of Pinecone and Vercel.
START CONTEXT BLOCK
${ context }
END OF CONTEXT BLOCK
AI assistant will take into account any CONTEXT BLOCK that is provided in a conversation.
If the context does not provide the answer to question, the AI assistant will say, "I'm sorry, but I don't know the answer to that question".
AI assistant will not apologize for previous responses, but instead will indicated new information was gained.
AI assistant will not invent anything that is not drawn directly from the context.
` ,
} ,
] ;
ในพรอมต์นี้เราได้เพิ่ม START CONTEXT BLOCK
และ END OF CONTEXT BLOCK
เพื่อระบุว่าควรแทรกบริบทที่ไหน นอกจากนี้เรายังเพิ่มบรรทัดเพื่อระบุว่าผู้ช่วย AI จะคำนึงถึงบล็อกบริบทใด ๆ ที่มีให้ในการสนทนา
ต่อไปเราต้องเพิ่มแผงบริบทลงในแชท UI เราจะเพิ่มส่วนประกอบใหม่ที่เรียกว่า Context
(รหัสเต็ม)
เราต้องการอนุญาตให้อินเทอร์เฟซระบุว่าส่วนใดของเนื้อหาที่ดึงมาใช้เพื่อสร้างการตอบสนอง ในการทำเช่นนี้เราจะเพิ่มจุดสิ้นสุดอื่นที่จะเรียก getContext
เดียวกัน
export async function POST ( req : Request ) {
try {
const { messages } = await req . json ( ) ;
const lastMessage =
messages . length > 1 ? messages [ messages . length - 1 ] : messages [ 0 ] ;
const context = ( await getContext (
lastMessage . content ,
"" ,
10000 ,
0.7 ,
false
) ) as ScoredPineconeRecord [ ] ;
return NextResponse . json ( { context } ) ;
} catch ( e ) {
console . log ( e ) ;
return NextResponse . error ( ) ;
}
}
เมื่อใดก็ตามที่ผู้ใช้คลาน URL แผงบริบทจะแสดงเซ็กเมนต์ทั้งหมดของหน้าเว็บที่ดึงมา เมื่อใดก็ตามที่แบ็กเอนด์ส่งข้อความกลับเสร็จสิ้นส่วนหน้าจะทำให้เกิดเอฟเฟกต์ที่จะดึงบริบทนี้:
useEffect ( ( ) => {
const getContext = async ( ) => {
const response = await fetch ( "/api/context" , {
method : "POST" ,
body : JSON . stringify ( {
messages ,
} ) ,
} ) ;
const { context } = await response . json ( ) ;
setContext ( context . map ( ( c : any ) => c . id ) ) ;
} ;
if ( gotMessages && messages . length >= prevMessagesLengthRef . current ) {
getContext ( ) ;
}
prevMessagesLengthRef . current = messages . length ;
} , [ messages , gotMessages ] ) ;
Pinecone-Vercel-Starter ใช้นักเขียนบทละครสำหรับการทดสอบจากจุดสิ้นสุด
เพื่อเรียกใช้การทดสอบทั้งหมด:
npm run test:e2e
โดยค่าเริ่มต้นเมื่อทำงานในเครื่องหากพบข้อผิดพลาดนักเขียนบทละครจะเปิดรายงาน HTML ที่แสดงว่าการทดสอบใดล้มเหลวและไดรเวอร์เบราว์เซอร์ใด
ในการแสดงรายงานการทดสอบล่าสุดในพื้นที่ให้เรียกใช้:
npm run test:show