引言
这次我们来搞一个完整的全栈项目:在网页上显示后端的数据,能够结合AI回答用户提出的问题。下面,我们将从零开始规划这样一个项目,确保每个步骤都清晰明了,适合不同技术水平的朋友跟随学习,相信每个人在看完后都能有所收获。
完成的结果:
不多废话,下面就开始。
全栈项目
介绍一下本次任务的架构设计:
- 在前端中,浏览器向本地服务器(127.0.0.1)发送请求。后端使用json-sever搭建服务器接受请求,读取数据并通过http发送到浏览器,浏览器解析json并显示出后端数据。
- 前端接收用户提出的问题,传给后端,后端通过API接口请求AI回答问题,并将回答传回后端,最后返回给前端并在页面上显示回答。
我们需要分别创建三个文件夹:
- backend(后端):应用程序中负责处理数据逻辑、业务规则以及与数据库交互的部分。它不直接与用户交互,而是通过前端界面间接地进行。
- frontend(前端):指用户可以直接看到并与之互动的应用程序部分。这部分关注的是用户体验,包括视觉设计、页面布局、用户交互等方面。
- LLM(大模型):大型语言模型是一种人工智能技术,能够理解和生成自然语言文本。这类模型基于深度学习技术训练而成,可以用于各种任务,比如自动问答、内容创作、翻译等。
Backend 后端
后端的使命是要提供数据接口。
- 首先,在后端文件夹命令行中
npm init -y // 初始化后端项目
// 安装 json-server 来创建服务器
// 快速地搭建一个 RESTful API 服务器,它基于你的 JSON 数据文件来提供数据。它允许开发者在没有实际后端的情况下测试和开发前端应用。
npm i json-server
- 并在package.json文件中的 "scripts" 脚本项中添加以下内容。在运行
npm run dev时将会dev中的命令。使用json-server,监听user.json文件变化并自动重启服务器(可以简单理解为把user.json作为服务器),使用3001端口。
"scripts": {
"dev": "json-server --watch user.json --port 3001"
},
- 在后端文件夹中创建一个user.json文件用来存放后端数据,我们将把它作为一个简单的服务器。这里是我的user.json中的数据:
{
"users": [
{
"id": 1,
"name": "张三",
"hometown": "北京"
},
{
"id": 2,
"name": "李四",
"hometown": "上海"
},
{
"id": 3,
"name": "王五",
"hometown": "北京"
}
]
}
至此,我们backend的配置就完成。我们运行一下
npm run dev //运行开发环境下的dev脚本
可以看到已经成功了(不要关闭服务器),我们接下来就可以在前端通过fetch(Endpoints)拿取后端的数据了。
Fronted 前端
先来构建HTML的基本结构,这里使用了Bootstrap前端框架快速构建页面。如果想要了解Bootstrap,可以看看我之前的文章通过Bootstrap设计的PC端网格布局,给你的代码加加速
- 我们使用表格来展示后端数据,先在
<tbody>中随便添加一些内容来看一看最后的页面显示结果。一会儿将使用DOM根据数据动态创建每一行。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI Users Rag chatbot</title>
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
<!-- 占页面一半并居中 -->
<div class="row col-md-6 col-md-offset-3">
<!-- 表格使用斑马纹样式 -->
<table class="table table-striped" id="user_table">
<thead>
<tr>
<th>ID</th>
<th>姓名</th>
<th>家乡</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>1</td>
<td>1</td>
</tr>
<tr>
<td>2</td>
<td>2</td>
<td>2</td>
</tr>
<tr>
<td>3</td>
<td>3</td>
<td>3</td>
</tr>
</tbody>
</table>
<!-- 用户提问部分 -->
<div class="row">
<form name="aiForm" action="http://www.baidu.com">
<div class="from-group">
<label for="questionInput">提问</label>
<input type="text" id="questionInput" class="form-control" name="question" placeholder="请输入问题"
required>
</div>
<button type="submit" class="btn btn-default" name="btn">查询</button>
</form>
</div>
<div class="row" id="message"></div>
</div>
</div>
<script>
</script>
</body>
</html>
下面是页面展示
我们现在来进行js部分
- js 主动向后端发送数据接口请求,前端向后端拉取数据,并打印到页面上。我们要通过
fetch向后本地服务器的3001端口发出请求,请求users内容,.then用来等待。但fetch比较底层,请求的数据为二进制,还需要使用.json将二进制转化为 json 。使用let of遍历json数组,并将其打印在createElement创建的tr和td中。
// js 主动向后端发送数据接口请求
// 前端向后端拉去数据
// DOM获取tbody元素,准备在tbody中间动态添加 tr 和 td
const tableBody = document.querySelector('table tbody');
let usersData;
// 请求本地服务器的 3001 端口中的 users 数组
fetch('http://localhost:3001/users')
// 数据到达前端之后 二进制 -> json
.then(res => res.json())
.then(users => {
usersData = users;
// console.log(users);
for (let user of users) { //遍历json并打印数据
// 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)
}
})
可以看到,我们后端的数据已经在前端渲染出来了。
- 给表单添加提交事件,能够将用户提出的问题传到后端,后端交给AI处理。
JSON.stringify()用于将JS对象转换为json字符串
const oForm = document.forms['aiForm']; // 特定的获取表单对象 [name]
oForm.addEventListener('submit', e => {
// 事件对象
// console.log(e);
e.preventDefault(); // 阻止表单默认行为,表单提交后将不再会转到 action 属性的页面
// 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)
// 再次请求本地服务器的1314端口(本例中用来使用AI),并向服务器传入数据 question 和 data
fetch(`http://localhost:1314/api?question=${question}&data=${JSON.stringify(usersData)}`)
// 二进制 -> json
// 后端返回回答
.then(data => data.json())
.then(data => {
console.log(data)
// 将回答打印在页面上
document.getElementById('message').innerText =
data.result;
})
})
这里我们完成了后端得到回答后将回答传给前端,并打印到页面上。 下面,我们将处理LLM的部分,让后端向LLM提问,并获得回答。
LLM 大模型
现在在LLM文件下
npm init -y // 初始化
npm i openai // 下载openai
npm i dotenv // 下载dotenv,用于个人安全防护
(有关AI使用和dotenv的部分可以查看我的文章零基础LLM+Prompt:原来Prompt要这么用)
创建main.js文件
// node 内置的 http 模块 用于创建 HTTP 服务器和客户端
// 相当于es6的import from (因为在这里使用的js文件,不能使用import 模块化)
const http = require('http');
const OpenAi = require('openai');
const url = require('url');// node 内置 url 用于解析、格式化和编码 URL
const dotenv=require('dotenv');
dotenv.config();
const client = new OpenAi({
apiKey: process.env.OPENAI_API_KEY, // 你自己的密钥
baseURL: process.env.OPENAI_API_BASE_URL // 代理转发地址
});
const getCompletion = async (prompt, model = "gpt-3.5-turbo") => {
// 用户提的问题
const messages = [{
role: 'user',
content: prompt
}];
// AIGC chat 接口
const response = await client.chat.completions.create({
model: model,
messages: messages,
// LLM 生成内容的随机性
temperature: 0.1
})
return response.choices[0].message.content
}
接下来,创建一个http服务。
// 创建http服务器
// req 是请求对象,包含了请求的信息 res 是响应对象,用于发送响应给客户端
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'); // 允许的请求头
// 解析请求的 URL,并将查询字符串解析为对象
const parsedUrl = url.parse(req.url, true);
// 获取解析后的查询参数对象
const queryObj = parsedUrl.query; // 打印queryObj,可以看到包含了 question 和 data,正是在前端时传入的数据
console.log(parsedUrl.query);
// 提出 prompt 使用解析后的对象
const prompt = `
${queryObj.data}
请根据上面的JSON数据,回答${queryObj.question} 这个问题
`
const result = await getCompletion(prompt)
// 将返回结果存储在对象中
let info = {
result
}
res.statusCode = 200 // 设置响应状态码为 200,表示请求成功
res.setHeader('Content-Type', 'text/json') // 设置响应的内容类型为 `text/json`
res.end(JSON.stringify(info)) // 将info对象转换为 JSON 字符串,并发送给客户端,结束响应
})
// 开启监听 1314 端口
server.listen(1314)
打印解析后的查询参数对象,获取到了用户提出的问题和后端数据。
测试
我们现在来检测一下:
- 在后端打开服务器:
npm run dev - 在LLM运行main.js接口文件
- 在前端打开页面
- 提问得到结果
我们已经成功完成了一个AI全栈项目!!!
总结
从后端到前端再到LLM,从项目的构思到最终的实现,每一步都充满了挑战与收获。LLM模型的集成是整个项目的核心,我们确保了AI能够提供准确的响应。此外,我们还特别注重数据的安全性和隐私保护,确保用户信息得到妥善处理。