使用向量嵌入上下文
chatgpt是一种精美的成长和学习资源,何时接受有关您使用的技术的教育。
最初,我希望Chatgpt帮助我创建Hilla应用程序。但是,由于它在2021年接受了数据培训,因此它给出了与现实不符的制造答复。
我可以改善的另一个领域是,希拉支持前端的反应并点亮。我需要确保答复将相关框架考虑到上下文。
这是我建立助手的方法,该助手使用最新文档为Chatgpt提供相关答复。
关键原则:嵌入
chatgpt与其他大语言模型一样,具有有限的上下文大小,必须适合您的问题,相关的背景事实和回答。例如,get-3.5-turbo
的令牌最大为4096,大约等于3000个单词。将最有用的文档件结合在提示中对于引起有意义的答复至关重要。
嵌入是确定这些关键文档部分的实用方法。嵌入是一种将文本含义编码到代表多维空间中位置的向量中的技术。具有相似含义的文本靠近在一起,而具有不同含义的文本则更分开。
这个想法与彩色拾取器的想法相似。包含红色,绿色和蓝色值的三元素向量可用于表示每种颜色。具有可比值的颜色具有相似的值,而具有不同值的颜色具有不同的值。
足以知道本文的OpenAI API converts text into embeddings。如果您想了解嵌入方式的功能,this article是一个美丽的起点。
一旦生成了内容的嵌入,您就可以通过找到与查询最密切相关的零件来快速发现最相关的位。
。概述:提供文档作为chatgpt的上下文
以下是让chatgpt使用您的文档作为上下文所需的高级程序:
:为您的文档创建嵌入
- 将您的文档分为较小的块,例如通过标题,然后为每个文档生成一个嵌入(向量)。
- 在矢量数据库中保存嵌入,源文本和其他元数据。
以文档为上下文提供响应
-
对用户查询进行嵌入。
-
使用嵌入式,搜索矢量数据库中的文档的n部分与查询最相关。
-
创建一个提示,告诉chatgpt仅使用可用文档回答给定查询。
-
要为提示产生一个完成,请使用OpenAI API。
在随后的部分中,我将进一步详细介绍我如何执行这些过程。
用过的工具
源代码
我将简单地提及以下代码中最重要的部分。您可以在GitHube
上找到源代码文档处理
Hilla documentation写在Asciidoc。以下是将它们转换为嵌入所需的过程:
-
应使用asciidoctor处理ASCIIDOC文件以包括代码段和其他包含物。
-
基于HTML文档结构,将生成的文档分为各节。
-
要保存令牌,请将材料转换为纯文本。
-
如有必要,将零件分成较小的碎片。
-
为文本的每个块制作嵌入向量。
-
Pinecone应用于保存嵌入向量和源文本。
Asciidoc的处理
async function processAdoc(file, path) {
console.log(`Processing ${path}`);
const frontMatterRegex = /^---[\s\S]+?---\n*/;
const namespace = path.includes('articles/react') ? 'react' : path.includes('articles/lit') ? 'lit' : '';
if (!namespace) return;
// Remove front matter. The JS version of asciidoctor doesn't support removing it.
const noFrontMatter = file.replace(frontMatterRegex, '');
// Run through asciidoctor to get includes
const html = asciidoctor.convert(noFrontMatter, {
attributes: {
root: process.env.DOCS_ROOT,
articles: process.env.DOCS_ARTICLES,
react: namespace === 'react',
lit: namespace === 'lit'
},
safe: 'unsafe',
base_dir: process.env.DOCS_ARTICLES
});
// Extract sections
const dom = new JSDOM(html);
const sections = dom.window.document.querySelectorAll('.sect1');
// Convert section html to plain text to save on tokens
const plainText = Array.from(sections).map(section => convert(section.innerHTML));
// Split section content further if needed, filter out short blocks
const docs = await splitter.createDocuments(plainText);
const blocks = docs.map(doc => doc.pageContent)
.filter(block => block.length > 200);
await createAndSaveEmbeddings(blocks, path, namespace);
}
创建嵌入并保存它们
async function createAndSaveEmbeddings(blocks, path, namespace) {
// OpenAI suggests removing newlines for better performance when creating embeddings.
// Don't remove them from the source.
const withoutNewlines = blocks.map(block => block.replace(/\n/g, ' '));
const embeddings = await getEmbeddings(withoutNewlines);
const vectors = embeddings.map((embedding, i) => ({
id: nanoid(),
values: embedding,
metadata: {
path: path,
text: blocks[i]
}
}));
await pinecone.upsert({
upsertRequest: {
vectors,
namespace
}
});
}
从Openai获取嵌入
export async function getEmbeddings(texts) {
const response = await openai.createEmbedding({
model: 'text-embedding-ada-002',
input: texts
});
return response.data.data.map((item) => item.embedding);
}
使用上下文搜索
到目前为止,我们已经将文档分为可管理的块,并将其放在向量数据库中。当用户提出问题时,我们必须执行以下操作:
-
根据问的查询而创建一个嵌入。
-
搜索矢量数据库中的十个最相关的文档部分。
-
创建一个问题,其中有许多文档部分包装到1536个令牌中,而2560的响应则留下了。
async function getMessagesWithContext(messages: ChatCompletionRequestMessage[], frontend: string) {
// Ensure that there are only messages from the user and assistant, trim input
const historyMessages = sanitizeMessages(messages);
// Send all messages to OpenAI for moderation.
// Throws exception if flagged -> should be handled properly in a real app.
await moderate(historyMessages);
// Extract the last user message to get the question
const [userMessage] = historyMessages.filter(({role}) => role === ChatCompletionRequestMessageRoleEnum.User).slice(-1)
// Create an embedding for the user's question
const embedding = await createEmbedding(userMessage.content);
// Find the most similar documents to the user's question
const docSections = await findSimilarDocuments(embedding, 10, frontend);
// Get at most 1536 tokens of documentation as context
const contextString = await getContextString(docSections, 1536);
// The messages that set up the context for the question
const initMessages: ChatCompletionRequestMessage[] = [
{
role: ChatCompletionRequestMessageRoleEnum.System,
content: codeBlock`
${oneLine`
You are Hilla AI. You love to help developers!
Answer the user's question given the following
information from the Hilla documentation.
`}
`
},
{
role: ChatCompletionRequestMessageRoleEnum.User,
content: codeBlock`
Here is the Hilla documentation:
"""
${contextString}
"""
`
},
{
role: ChatCompletionRequestMessageRoleEnum.User,
content: codeBlock`
${oneLine`
Answer all future questions using only the above
documentation and your knowledge of the
${frontend === 'react' ? 'React' : 'Lit'} library
`}
${oneLine`
You must also follow the below rules when answering:
`}
${oneLine`
- Do not make up answers that are not provided
in the documentation
`}
${oneLine`
- If you are unsure and the answer is not explicitly
written in the documentation context, say
"Sorry, I don't know how to help with that"
`}
${oneLine`
- Prefer splitting your response into
multiple paragraphs
`}
${oneLine`
- Output as markdown
`}
${oneLine`
- Always include code snippets if available
`}
`
}
];
// Cap the messages to fit the max token count, removing earlier messages if necessary
return capMessages(
initMessages,
historyMessages
);
}
当用户提出问题时,我们使用getMessagesWithContext()
检索必须发送到chatgpt的消息。然后,使用OpenAI API获取完整并将响应提供给客户。
export default async function handler(req: NextRequest) {
// All the non-system messages up until now along with
// the framework we should use for the context.
const {messages, frontend} = (await req.json()) as {
messages: ChatCompletionRequestMessage[],
frontend: string
};
const completionMessages = await getMessagesWithContext(messages, frontend);
const stream = await streamChatCompletion(completionMessages, MAX_RESPONSE_TOKENS);
return new Response(stream);
}
源代码
感谢您与我保持联系,直到最后。您是一个很棒的读者!
ahsan mangal
希望您能发现它有益和吸引人。如果您喜欢这些内容,请考虑以后关注我以获取更多这样的文章。保持好奇并继续学习!