全站高质量IT专业课程选集,一应俱全,
IT人的毁灭者同志会!流程猿的华特
————————————
稳步预览:www.hhdhwc.com
————————————

ChatGPT能协助他们同时实现许多原先极难同时实现机能,为现代控制系统重新加入AI全力支持,进而提高使用者新体验。责任编辑如是说了怎样给新浪网Markdown文件格式控制系统加进ChatGPT概要全力支持,将动态文件格式改建为智能化文件格式。书名: Build a ChatGPT Powered Markdown Documentation in No Time[1]

简述

他们在这里主要讨论:
需要将内容存储在数据库中。
需要让使用者输入查询。
在数据库中搜索与使用者查询最相似的结果(稍后详细如是说)。
基于匹配查询的前5个最相似结果创建”上下文”并询问ChatGPT:
根据下面的上下文回答问题,如果不能根据上下文回答问题,就说”我不知道上下文:
[上下文内容]
—
问题:
[问题内容]
回答:
同时实现细节
好,他们开始。
以下是同时实现本控制系统需要的前提条件。
Embedbase API key[3]: 一个能找到”最相似结果”的数据库。并不是所有数据库都适合这种工作,他们将使用Embedbase,它能做到这一点。Embedbase允许他们找到搜索查询和存储内容之间的”语义相似性”。
OpenAI API key[4]: 这是ChatGPT部分。
Nextra[5]: 并且安装好Node.js[6]
在.env中填好Embedbase和OpenAI API key。
OPENAI_API_KEY=”EMBEDBASE_API_KEY=”提醒一下,他们将基于了不起的文件格式框架Nextra创建由ChatGPT提供全力支持的QA文件格式,该框架允许他们使用NextJS、tailwindcss和MDX(Markdown + React)编写文件格式。他们还将使用Embedbase作为数据库,并调用OpenAI的ChatGPT。
创建Nextra文件格式
能在Github[7]上找到官方Nextra文件格式模板,用模板创建文件格式之后,能用任何你喜欢的编辑器打开。
# we wont use “pnpm” here, rather the traditional “npmrm pnpm-lock.yaml
npm i
npm run dev
尝试编辑.mdx文件格式,看看内容有何变化。
准备并存储文件
第一步需要将文件格式存储在Embedbase中。不过有一点需要注意,如果他们在DB中存储相关联的较小的块,效果会更好,因此他们将把文件格式按句子分组。让他们从在文件夹scripts中编写一个名为sync.js的脚本开始。
你需要glob库来列出文件,用命令npm i [email protected](他们将使用8.1.0版本)安装glob库。
const glob = require(“glob”);
const fs = require(“fs”);
const sync = async () => {
// 1. read all files under pages/* with .mdx extension
// for each file, read the content
const documents = glob.sync(“pages/**/*.mdx”).map((path) => ({
// we use as id /{pagename} which could be useful to
// provide links in the UI
id: path.replace(“pages/”, “/”).replace(“index.mdx”, “”).replace(“.mdx”, “”),
// content of the file
data: fs.readFileSync(path, “utf-8”)
}));
// 2. here we split the documents in chunks, you can do it in many different ways, pick the one you prefer
// split documents into chunks of 100 lines
const chunks = [];
documents.forEach((document) => {
const lines = document.data.split(“\n”);
const chunkSize = 100;
for (let i = 0; i
const chunk = lines.slice(i, i + chunkSize).join(“\n”);
chunks.push({
data: chunk
});
}
});
}
sync();
现在他们构建好了存储在DB中的块,接下来扩展脚本,以便将块加进到Embedbase。
要查询Embedbase,需要执行npm i [email protected]安装2.6.9版本的node-fetch。
const fetch = require(“node-fetch”);
// your Embedbase api key
const apiKey = process.env.EMBEDBASE_API_KEY;
const sync = async () => {
// …
// 3. we then insert the data in Embedbase
headers: {
“Authorization”: “Bearer ” + apiKey,
“Content-Type”: “application/json },
body: JSON.stringify({
documents: chunks
})
});
const data = await response.json();
console.log(data);
}
sync();
很好,现在能运行了:
EMBEDBASE_API_KEY=”” node scripts/sync.js
如果运行良好,应该看到:

接下来修改Nextra文件格式主题,将内置搜索栏替换为全力支持ChatGPT的搜索栏。
在theme.config.tsx中加进一个Modal组件,内容如下:
// update the imports
import { DocsThemeConfig, useTheme } from nextra-theme-docs
const Modal = ({ children, open, onClose }) => {
const theme = useTheme();
if (!open) return null;
return (
e.stopPropagation()}>
{children}
);
};
现在创建搜索栏:
// update the imports
import React, { useState } from react
// we create a Search component
const Search = () => {
const [open, setOpen] = useState(false);
const [question, setQuestion] = useState(“”);
// …
// All the logic that we will see later
const answerQuestion = () => { }
// …
return (
setOpen(false)}>
setQuestion(e.target.value)}
/>
Ask
);
}
最后,预览配置以设置新创建的搜索栏:
const config: DocsThemeConfig = {
logo: My Project,
project: {
},
footer: {
text: Nextra Docs Template,
},
// add this to use our Search component
search: {
component:
}
}
构建上下文
这里需要OpenAI token计数库tiktoken,执行npm i @dqbd/tiktoken安装。
接下来创建带上下文的ChatGPT提示词。创建文件pages/api/buildPrompt.ts,代码如下:
// pages/api/buildPrompt.ts
import { get_encoding } from “@dqbd/tiktoken”;
// Load the tokenizer which is designed to work with the embedding model
const enc = get_encoding(cl100k_base);
const apiKey = process.env.EMBEDBASE_API_KEY;
// this is how you search Embedbase with a string query
const search = async (query: string) => {
method: “POST”,
headers: {
Authorization: “Bearer ” + apiKey,
“Content-Type”: “application/json },
body: JSON.stringify({
query: query
})
}).then(response => response.json());
};
const createContext = async (question: string, maxLen = 1800) => {
// get the similar data to our query from the database
const searchResponse = await search(question);
let curLen = 0;
const returns = [];
// We want to add context to some limit of length (tokens)
// because usually LLM have limited input size
for (const similarity of searchResponse[“similarities”]) {
const sentence = similarity[“data”];
// count the tokens
const nTokens = enc.encode(sentence).length;
// a token is roughly 4 characters, to learn more
curLen += nTokens + 4;
if (curLen > maxLen) {
break;
}
returns.push(sentence);
}
// we join the entries we found with a separator to show its different
return returns.join(“\n\n###\n\n”);
}
// this is the endpoint that returns an answer to the client
export default async function buildPrompt(req, res) {
const prompt = req.body.prompt;
const context = await createContext(prompt);
const newPrompt = `Answer the question based on the context below, and if the question cant be answered based on the context, say “I dont know”\n\nContext: ${context}\n\n—\n\nQuestion: ${prompt}\nAnswer:`;
res.status(200).json({ prompt: newPrompt });
}
调用ChatGPT
首先,在文件utils/OpenAIStream.ts中加进一些用于对OpenAI进行流调用的函数,执行npm i eventsource-parser安装eventsource-parser。
import {
createParser,
ParsedEvent,
ReconnectInterval,
} from “eventsource-parser”;
export interface OpenAIStreamPayload {
model: string;
// this is a list of messages to give ChatGPT
messages: { role: “user”; content: string }[];
stream: boolean;
}
export async function OpenAIStream(payload: OpenAIStreamPayload) {
const encoder = new TextEncoder();
const decoder = new TextDecoder();
let counter = 0;
{
headers: {
“Content-Type”: “application/json”,
“Authorization”: `Bearer ${process.env.OPENAI_API_KEY ?? “”}`,
},
method: “POST”,
body: JSON.stringify(payload),
});
const stream = new ReadableStream({
async start(controller) {
// callback
function onParse(event: ParsedEvent | ReconnectInterval) {
if (event.type === “event”) {
const data = event.data;
if (data === “[DONE]”) {
controller.close();
return;
}
try {
const json = JSON.parse(data);
// get the text response from ChatGPT
const text = json.choices[0]?.delta?.content;
if (!text) return;
if (counter
// this is a prefix character (i.e., “\n\n”), do nothing
return;
}
const queue = encoder.encode(text);
controller.enqueue(queue);
counter++;
} catch (e) {
// maybe parse error
controller.error(e);
}
}
}
// stream response (SSE) from OpenAI may be fragmented into multiple chunks
// this ensures we properly read chunks and invoke an event for each SSE event stream
const parser = createParser(onParse);
for await (const chunk of res.body as any) {
parser.feed(decoder.decode(chunk));
}
},
});
return stream;
}
然后创建文件pages/api/qa.ts,作为对ChatGPT进行流调用的端点。
// pages/api/qa.ts
import { OpenAIStream, OpenAIStreamPayload } from “../../utils/OpenAIStream”;
export const config = {
// We are using Vercel edge function for this endpoint
runtime: “edge”,
};
interface RequestPayload {
prompt: string;
}
const handler = async (req: Request, res: Response): Promise => {
const { prompt } = (await req.json()) as RequestPayload;
if (!prompt) {
return new Response(“No prompt in the request”, { status: 400 });
}
const payload: OpenAIStreamPayload = {
model: “gpt-3.5-turbo”,
messages: [{ role: “user”, content: prompt }],
stream: true,
};
const stream = await OpenAIStream(payload);
return new Response(stream);
};
export default handler;
连接一切并提问
现在是时候通过API调用提问。编辑theme.config.tsx,将该函数加进到Search组件中:
// theme.config.tsx
const Search = () => {
const [open, setOpen] = useState(false);
const [question, setQuestion] = useState(“”);
const [answer, setAnswer] = useState(“”);
const answerQuestion = async (e: any) => {
e.preventDefault();
setAnswer(“”);
// build the contextualized prompt
const promptResponse = await fetch(“/api/buildPrompt”, {
method: “POST”,
headers: {
“Content-Type”: “application/json”,
},
body: JSON.stringify({
prompt: question,
}),
});
const promptData = await promptResponse.json();
// send it to ChatGPT
const response = await fetch(“/api/qa”, {
method: “POST”,
headers: {
“Content-Type”: “application/json”,
},
body: JSON.stringify({
prompt: promptData.prompt,
}),
});
if (!response.ok) {
throw new Error(response.statusText);
}
const data = response.body;
if (!data) {
return;
}
const reader = data.getReader();
const decoder = new TextDecoder();
let done = false;
// read the streaming ChatGPT answer
while (!done) {
const { value, done: doneReading } = await reader.read();
done = doneReading;
const chunkValue = decoder.decode(value);
// update our interface with the answer
setAnswer((prev) => prev + chunkValue);
}
};
return (
setOpen(false)}>
setQuestion(e.target.value)}
/>
Ask
{answer}
);
}
你现在应该能看到:

当然,能随意改进样式。
结论
总结一下,他们做了:
创建了Nextra文件格式
在Embedbase中准备和存储文件格式
在数据库中搜索需要查询ChatGPT的问题上下文
使用此上下文构建提示并调用ChatGPT
通过将所有内容起来,让使用者提问
感谢阅读责任编辑,Github[8]上有一个创建此类文件格式的开源模板。
延伸阅读
嵌入(Embedding)是一种机器学习概念,允许他们将数据的语义数字化,进而创建以下机能:
语义搜索(例如,”牛吃草”和”猴子吃香蕉”之间有什么相似之处,也适用于比较图像等)
推荐控制系统(如果你喜欢电影《阿凡达》,可能也会喜欢《星球大战》)
分类(“这部电影太棒了”是肯定句,”这部电影烂透了”是否定句)
生成式搜索(能回答有关PDF、网站、YouTube视频等问题的聊天机器人)
Embedding并不是一项新技术,但由于OpenAI Embedding端点的快速和廉价,最近变得更受欢迎、更通用、更容易使用。在网上有许多关于Embedding的信息,因此他们不会深入研究Embedding的技术主题。
AI embedding能被认为是哈利波特的分院帽。就像分院帽根据学生特质来分配学院一样,AI embedding也是根据特征来分类相似内容。当他们想找到类似内容时,能要求AI为他们提供内容的embedding,计算它们之间的距离。embedding之间的距离越近,内容就越相似。这个过程类似于分院帽怎样利用每个学生的特征来确定最适合的学院。通过使用AI embedding,他们能根据内容特征快速、轻松的进行比较,进而做出更明智的决定和更有效的搜索结果。

上面描述的方法只是简单的嵌入单词,但如今已经能嵌入句子、图像、句子+图像以及许多其他东西。
如果想在生产环境中使用embedding,有一些陷阱需要小心:
大规模存储embedding的基础设施
成本优化(例如避免计算两次数据)
使用者embedding的隔离(不希望搜索机能显示其他使用者的数据)
处理模型输入的大小限制
与流行的应用基础设施(supabase, firebase,谷歌云等)集成
在GitHub Action中稳步准备数据
embedding的意义在于能够索引任何类型的非结构化数据,他们希望每次修改文件格式时都能被索引,对吧?下面展示的是一个GitHub Action,当主分支完成git push时,将索引每个markdown文件:
# .github/workflows/index.yaml
name: Index documentation
on:
push:
branches:
– main
jobs:
index:
runs-on: ubuntu-latest
steps:
– uses: actions/checkout@v2
– uses: actions/setup-node@v2
with:
node-version: 14
– run: npm install
– run: node scripts/sync.js
env:
EMBEDBASE_API_KEY: ${{ secrets.EMBEDBASE_API_KEY }}
别忘了把EMBEDBASE_API_KEY加进到你的GitHub密钥里。
你好,我是俞凡,在Motorola做过研发,现在在Mavenir做技术工作,对通信、网络、后端架构、云原生、DevOps、CICD、区块链、AI等技术始终保持着浓厚的兴趣,平时喜欢阅读、思考,相信稳步学习、终身成长,欢迎一起交流学习。
参考资料
[1]
Build a ChatGPT Powered Markdown Documentation in No Time:
[6]
Node.js:
[7]
Nextra Docs Template:
[8]
Embedbase Nextra Docs Template: