引言
现如今已经没有完全的前端和后端了,不管你是哪一个都要会他们的交互,本文将介绍如何从零开始构建一个简单的全栈应用———AI用户信息查询系统,该系统能智能回答用户输入的相关问题。
大概架构
frontend(前端)
用html创建一个Web前端界面供用户提问。
backend(后端)
使用JSON Server来模拟数据库,监听http://localhost:3000/users,用于提供用户数据接口,返回数据。
LLM(大语言模型)
实现一个HTTP服务器来接收前端请求,并返回基于OpenAI GPT-3.5模型的回答。
项目准备
后端初始化
-
准备数据
创建一个名为
user.json的数据文件,用于存储用户信息。这个文件将作为我们的数据库。
{
"users": [
{
"id": 1,
"name": "张三",
"hometown": "杭州"
},
{
"id": 2,
"name": "李四",
"hometown": "上海"
},
{
"id": 3,
"name": "王五",
"hometown": "上海"
}
]
}
- 创建项目
首先我们先初始化一个新的Node.js的项目,然后
我们点击右键选择在集成终端打开,在里面输入npm init -y,这是完成后端命令的一个指令,当你运行这个命令时,它会自动创建一个 package.json 文件,然后用pnpm i json-server来引入json-server,json-server是一个轻量级的工具,它可以根据JSON文件自动生成RESTFULAPI接口
。
- 项目启动
- 为了让项目更容易启动,我们在
package.json中添加了一个名为dev的脚本。这个脚本命令是用于启动一个开发服务器,通过这个脚本命令,我们可以方便的启动一个本地开发服务器,用于测试和开发基于JSON数据的应用程序。当我们每次修改数据文件时,服务器都会自动更新。
-
运行
我们使用
npm run dev命令来启动json-server。一旦启动就将在http://localhost:3000/users地址上提供用户数据,这样运行的好处之一是npm 会自动将当前目录添加到PATH环境变量中,这意味着你可以在脚本中直接调用项目依赖的二进制,而不需要知道它们的具体路径。
前端初始化
创建完HTML页面后,我们在tr里的th标签写上ID,姓名,家乡,并在下面创建一个表单。
在这个input里面的required是最好要写上的,因为它能防止用户什么都没输入,运行结果如下图:
到这里我们的准备工作基本做完了,下面要做的是把user.json里的数据传给前端的页面,并结合AI大模型来实现智能信息问答。
LLM
先在LLM里创建一个main.js,再用上面的那些命令创建出package.json等文件
项目完善
前端代码
<script>
// js 主动向后端发送数据接口请求
// 前端向后端拉去数据
const tableBody = document.querySelector('table tbody');
const oForm = document.forms['aiForm'];
let usersData;
fetch('http://localhost:3000/users')
// 数据到达前端 二进制 -》 json
.then(res => res.json())
.then(users => {
usersData = users;
// console.log(data);
for (let user of users) {
// console.log(user);
const tr = document.createElement('tr');
// for in json 对象遍历
for (let key in user) {
const td = document.createElement('td');
td.innerText = user[key];
tr.appendChild(td);
}
tableBody.appendChild(tr)
}
})
oForm.addEventListener('submit', e => {
// 事件对象
// console.log(e);
e.preventDefault(); // 阻止表单默认行为
// fetch 在页面不刷新的时候向ai server 发出请求
// const question = oForm.question.value;
// 发送数据到后端
// web 2.0 动态页面开发 js fetch 可以主动拉去数据
const question = oForm.question.value.trim();
if (!question) {
alert('请输入问题');
return;
}
console.log(question)
fetch(`http://localhost:1314/api?question=${question}&data=${JSON.stringify(usersData)}`)
.then(data => data.json())
.then(data => {
console.log(data)
document.getElementById('message').innerText =
data.result;
})
})
</script>
代码解释
fetch('http://localhost:3000/users'):使用fetch函数发起一个 GET 请求到http://localhost:3000/users这个 URL,这个 URL 通常指向一个提供用户数据的 API 端点。.then(res => res.json()):当请求成功时,fetch会返回一个 Promise 对象。这里使用.then方法来处理这个 Promise,并将响应数据转换为 JSON 格式。res.json()是一个异步操作,它将响应体解析为 JSON 对象。.then(users => {...}):这是第二个.then方法,用于处理上一步中解析得到的 JSON 数据。在这个方法中,我们将接收到的 JSON 数据赋值给users变量。usersData = users;:将获取到的用户数据存储在usersData变量中,以便后续使用。for (let user of users) {...}:使用for...of循环遍历users数组中的每个用户对象。const tr = document.createElement('tr');:为每个用户对象创建一个<tr>元素,用于在表格中表示一行数据。for (let key in user) {...}:使用for...in循环遍历用户对象的每个属性。const td = document.createElement('td');:为每个属性创建一个<td>元素,用于在表格中表示一列数据。td.innerText = user[key];:将用户对象的属性值设置为<td>元素的文本内容。tr.appendChild(td);:将创建的<td>元素添加到<tr>元素中。tableBody.appendChild(tr);:将创建的<tr>元素添加到表格的<tbody>中,这样用户数据就会显示在表格中。oForm.addEventListener('submit', e => {...}):为表单添加一个提交事件监听器。当表单被提交时,这个事件监听器会被触发。e.preventDefault();:阻止表单的默认提交行为,这样页面就不会刷新。const question = oForm.question.value.trim();:从表单中获取用户输入的问题,并去除问题前后的空格。if (!question) {...}:如果问题为空,则弹出一个提示框,提示用户输入问题。fetch(http://localhost:1314/api?question=${question}&data=${JSON.stringify(usersData)}`)`:使用 `fetch` 函数发起一个 GET 请求到http://localhost:1314/api这个 URL,并将问题和用户数据作为查询参数添加到 URL 中。.then(data => data.json()):当请求成功时,将响应数据转换为 JSON 格式。.then(data => {...}):处理上一步中解析得到的 JSON 数据,并将处理结果显示在页面上。
LLM
导入模块
我们先集成一个OpenAI的GPT-3.5-turbo模型用来处理来自客户端的请求并生成相应的回答,并导入Node.js内置的http模块用于创建HTTP服务器和URL模块用于解析URL。
// node 内置的http 模块
const http = require('http');
const OpenAi = require('openai');
const url = require('url');// node 内置
初始化OpenAI客户端
创建一个OpenAI客户端实例,设置API密钥和URL(笔者这里的apiKey是错的,你们肯定用不了[doge])
const client = new OpenAi({
apiKey: 'sk-ReSSAr5RgliZPxoLlaB0yMG1nhNn1CQGi4iQHvkcqpZ2',
baseURL: 'https://api.302.ai/v1'
});
定义获取GPT-3.5-turbo回答的函数
- 函数定义:
在这里我们要定义一个名为getCompletion的异步函数,用于与OpenAI的GPT-3.5-turbo 模型进行交互,以获取给定提示的回答
```js
const getCompletion = async (prompt, model = "gpt-3.5-turbo") => {
```
2. 构造消息数组:
创建了一个包含单个消息对象的数组。这个消息对象表示用户的输入,其中 role 字段被设置为 'user',content 字段被设置为传入的 prompt 参数。
const messages = [{
role: 'user',
content: prompt
}];
- 调用 OpenAI API:
调用一个 OpenAI 的 API 来创建一个聊天完成(completion)。它使用了之前初始化的
client对象,并传递了一个包含模型名称、消息数组和温度(temperature)参数的对象。温度参数控制了生成文本的随机性,值越低,生成的文本越确定。最后调用OpenAI的API获取回答,并返回回答的内容。
const response = await client.chat.completions.create({
model: model,
messages: messages,
temperature: 0.1
})
return response.choices[0].message.content
- 创建HTTP服务器:
这里我们要创建一个HTTP服务器,让它在接收到请求时,会设置跨域访问头,然后解析请求的URL来获取查询参数中的
data和question,调用getCompletion来获取回答,并将回答以JSON格式返回给客户端。
const server = http.createServer(async (req, res) => {
res.setHeader('Access-Control-Allow-Origin', '*'); // 允许所有来源访问,也可以指定具体的域名,如'http://example.com'
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); // 允许的请求方法
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization'); // 允许的请求头
const parsedUrl = url.parse(req.url, true);
const queryObj = parsedUrl.query;
console.log(parsedUrl.query);
const prompt = `
${queryObj.data}
请根据上面的JSON数据,回答${queryObj.question} 这个问题
`
const result = await getCompletion(prompt)
let info = {
result
}
res.statusCode = 200
res.setHeader('Content-Type', 'text/json')
res.end(JSON.stringify(info))
})
- 启动HTTP服务器 将HTTP服务器绑定到1314端口,使其能开始监听来自客户端的请求。
server.listen(1314)
结果展示
小结
通过这个小demo,我们成功的使用前后端分离,完成了最终的效果,用户可以通过网页界面查看用户数据,并通过输入问题获得基于这些数据的智能回答。