前言
每一个码农都有一个全栈梦,尤其是我们新手小白初入手项目,总会感觉一个人完成全栈编程是一件“超级酷炫拽”的成就,本篇文章将会手把手带你搭建一个属于自己的初级AI全栈项目,让你初体验前后端交互,AI大模型在前后端调用的流程,带你全栈初体验!
一、项目目录结构
一个简单的AI全栈项目的目录结构很简单,只需最基础的前后端以及AI模型的调用即可
- frontend
- backend
- llm
- readme.md
不管什么项目,都建议编写一个readme文档,即帮助自己梳理项目思路的同时,也方便记录项目的进度和方便项目协作甚至是以后的开源分享
二、项目总览
这是一个集成了AI能力的全栈应用开发项目,主要功能包括:
🌟 核心功能
- 用户数据管理 - 后端使用json-server提供RESTful API服务
- 前端数据展示 - 使用Bootstrap构建响应式界面
- AI智能问答 - 集成OpenAI GPT-3.5模型进行智能分析
🏗️ 技术架构
前端界面 (Bootstrap)
↓ HTTP请求
后端API (JSON Server)
↓ 数据传递
AI服务 (OpenAI API)
↓ 智能回答
前端展示
🔄 工作流程
- 前端从后端获取用户数据并渲染展示
- 用户输入相关问题
- 前端将问题+数据发送到LLM服务
- AI模型分析后返回智能回答
- 前端展示分析结果
三、frontend
1. 基础样式
我们先从前端页面入手,在frontend文件夹下创建一个index.html文件,搭建主页面,此项目十分初级,主要是为了学习全栈开发和AI集成,我们前端框架采用Bootstrap框架
Bootstrap是一个流行的前端框架,通过响应式网格系统和预置组件快速构建移动设备优先的现代化网站。
我们先在HTML页面中引入框架
<link
href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css"
rel="stylesheet"
/>
以下是HTML基础页面的代码
<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></tbody>
</table>
</div>
// 表单显示区域
<div class="row">
<form name="aiForm">
<div class="form-group">
<input
type="text"
class="form-control"
id="questionInput"
name="question"
required
placeholder="请输入问题"
/>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">提交</button>
</div>
</form>
</div>
// 消息显示区域
<div class="row" id="message"></div>
</div>
打开html代码我们就可以看到以下页面样式,其中表单结构中的内容使我们渲染的数据,我们稍后再提及,我们就是通过在输入框中输入问题,调用AI大模型对表单数据进行提问处理
2. JavaScript设计
我们需要将输入的问题传递给AI模型,就需要获取到input输入框中的数据,我们采用JS中的DOM编程
// 获取输入框对象
const oForm=document.forms["aiForm"]
// 获取按钮对象
const oBtn = document.querySelector(".btn");
获取到了DOM对象,我们继续对其提交事件监听
oForm.addEventListener("submit",event=>{\
// 阻止表单的默认提交行为
event.preventDefault();
// 获取到表单问题
const question = oForm["question"].value;
// 判断一下输入框是否已经填写了问题
if (!question) {
alert("请输入问题");
return;
}
// 防止一次提交中反复点击按钮,造成不必要的调用
oBtn.disabled=true
// 向端口号为1314的服务器发送请求,传递question和users数据
fetch(
`http://localhost:1314/?question=${question}&data=${JSON.stringify(users)}`
)
.then((res)=>res.json()) // 等待响应后将获取到的结果拼接成字符串
.then((data)=>data{
// 响应结束,回复按钮的点击功能
oBtn.disabled = false;
// 将LLM处理完的回答输出到HTML界面中
document.getElementById("message").innerHTML = data.result;
})
})
完整的/frontend/index.html代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Users Chatbot</title>
<link
href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css"
rel="stylesheet"
/>
</head>
<!-- PC端页面框架
bootstrap Layout 行列
列的布局, 总共12个单位,
col-md-6 一半
-->
<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></tbody>
</table>
</div>
<div class="row">
<form name="aiForm">
<div class="form-group">
<!-- <label for="questionInput">请输入问题:</label> -->
<input
type="text"
class="form-control"
id="questionInput"
name="question"
required
placeholder="请输入问题"
/>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">提交</button>
</div>
</form>
</div>
<div class="row" id="message"></div>
</div>
<script>
const oForm = document.forms["aiForm"];
const oBody = document.querySelector("#user_table");
const oBtn = document.querySelector(".btn");
let users;
oForm.addEventListener("submit", (event) => {
event.preventDefault();
const question = oForm["question"].value;
// console.log(question);
// llm api 请求
if (!question) {
alert("请输入问题");
return;
}
oBtn.disabled = true;
// console.log(
// `http://localhost:1314/?question=${question}&data=${JSON.stringify(
// users
// )}`
// );
fetch(
`http://localhost:1314/?question=${question}&data=${JSON.stringify(
users
)}`
)
.then((res) => res.json())
.then((data) => {
console.log(data);
oBtn.disabled = false;
document.getElementById("message").innerHTML = data.result;
});
});
// 前(用户哪里,全球各地)后(机房)端通信
// js fetch 主动的向后端发起请求
fetch("http://localhost:3001/users")
// 数据到达后, then 接着做
// res 二进制数据, 转换为 json 格式
.then((res) => res.json())
.then((data) => {
// console.log(data);
users = data;
// console.log(users, '////');
oBody.innerHTML = data
.map(
(user) => `
<tr>
<td>${user.id}</td>
<td>${user.username}</td>
<td>${user.hometown}</td>
</tr>
`
)
.join("");
});
</script>
</body>
</html>
四、 backend
接下来让我们在设置一下后端端口和数据
1. 初始化后端项目
在/backend下运行终端,输入pnpm init -y,初始化一个后端项目文件夹
然后编辑package.json,添加json-server依赖和启动脚本,如下:
{
"name": "backend",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"dev": "json-server --watch users.json --port 3001"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"json-server": "1.0.0-beta.3"
}
}
其中"scripts": { "dev": "json-server --watch users.json --port 3001" },
下面详细解释每个部分:
-
scripts配置 :
- scripts是package.json中的一个标准字段,用于定义可执行的命令脚本
- 这些脚本可以通过 npm run 脚本名 或 pnpm run 脚本名 来执行
-
dev脚本 :
- "dev"是脚本的名称,通常用于开发环境的命令
- 执行 pnpm run dev 时,系统会运行后面定义的命令
-
json-server命令 :
- json-server是一个轻量级的工具,可以快速基于JSON文件创建RESTful API
- 它会自动为JSON文件中的数据集合创建完整的CRUD(创建、读取、更新、删除)API
-
--watch参数 :
- 这个参数告诉json-server监视指定的JSON文件
- 当users.json文件内容发生变化时,json-server会自动重新加载数据
- 这样开发者可以实时修改数据而不需要重启服务器
-
users.json文件 :
- 这是数据源文件,包含了用户数据
- json-server会基于这个文件中的数据结构自动生成API
- 在这个项目中,该文件包含了users数组,每个元素是一个用户对象
-
--port 3001参数 :
- 这个参数指定json-server运行的端口号为3001
- 服务启动后,可以通过 http://localhost:3001 访问API
2. 安装依赖生成pnpm-lock.yaml
运行安装命令,会自动创建pnpm-lock.yaml文件:
pnpm install
这个文件是pnpm的依赖锁定文件,用于确保项目在不同环境中安装完全相同版本的依赖。
3.用户数据
后端很重要的一个职责就是为前端页面提供数据,我们在/backend目录下新建一个users.json文件存储数据
{
"users": [
{
"id": 1,
"username": "林晓晓",
"hometown": "桂林"
},
{
"id": 2,
"username": "陈宇航",
"hometown": "厦门"
},
...
]
}
这个users数据就是我们前端页面通过fetch() 传递给服务器的数据,我们需要事先提供
4.启动服务
完成文件创建后,可以通过以下命令启动后端服务:
pnpm run dev
这将在3001端口启动json-server,提供基于users.json的RESTful API服务。
五、 llm
1. 初始化项目
pnpm init -y
2. 安装依赖
我们通过调用OpenAi来完成我们的AI处理数据,需要先安装它相关的依赖
pnpm i openai dotven
3. 创建.env文件,用于存储AIP_KEY
/.env
OPENAI_API_KEY=// 填写你自己的API_KEY
4. 创建index.mjs文件,编写主程序入口
我们还需要一个主程序入口,提供AI智能问答服务,将OpenAI API能力封装为HTTP接口供前端调用
先导入需要的模块
// index.mjs
// http模块,Node.js内置模块,无需安装 用于提供HTTP服务器和客户端的功能
import http from 'http'
// ulr模块,Node.js内置模块,无需安装在代码中用于解析 HTTP 请求的 URL, 帮助处理客户端发送的数据
import url from 'url'
import {config} from 'dotven'
import OpenAi from 'openai'
调用openAi模型
//调用从dotenv模块导入的 config() 函数,用于将指定文件中的环境变量加载到Node.js的 process.env 对象中。
config({
path:'.env'
})
// 创建一个OpenAI客户端对象,用于与OpenAI API进行交互
const client=new OpenAi({
apiKey:'process.env.OPENAI_API_KEY,
baseURL: 'https://api.agicto.cn/v1'
const getCompletion= async (prompt,model='gpt-3.5-turbo')=>{
// 构建消息格式
const message=[
{
role:'user',
content:'prompt'
}
]
// 调用OpenAi
const result= await client.chat.ompletions.create({
model,
message,
// 设置输出的随机性,数值越低越精确
temperature: 0.1
})
// 提取并返回回答
return result.choices[0].message.content
}
})
// 创建一个 HTTP服务器,实现Ai问答
http.createServer(async(req,res)=>{
// 设置响应头允许来自任何域的跨域请求,这对于前端应用能够调用此API非常重要
res.setHeader('Access-Control-Allow-Origin', '*')
// 使用url模块解析请求URL,提取其中的查询参数 第二个参数true表示将查询字符串解析为对象。
const parsedUrl = url.parse(req.url, true)
// 使用模板字符串编辑prompt
const prompt=`
${parsedUrl.query.data}
请根据上面的JSON数据, 回答${parsedUrl.query.question} 这个问题。
`
// 调用前面定义的异步函数,将编辑的prompt传递给AI模型,获取回答
const result = await getCompletion(prompt)
// 将回答封装为对象形式
let info = {
result
}
// 设置HTTP状态码为200(成功)
res.status = 200
// 指定响应内容类型为JSON格式
res.setHeader('Content-Type', 'application/json')
// 将响应对象序列化为JSON字符串并发送给客户端。
res.end(JSON.stringify(info))
})
.listen(1314) // 监听前端界面发送请求的端口
完整的/llm/index.mjs代码
// pnpm i openai dotenv
// 要提供http服务和llm服务结合起来
// http 服务 和llm 服务结合起来
import http from 'http'
import OpenAI from 'openai'
import url from 'url'
import { config } from 'dotenv'
config({
path: '.env'
})
// console.log(process.env.OPENAI_API_KEY, '////')
const client = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
baseURL: 'https://api.agicto.cn/v1'
})
// aigc
const getCompletion = async (prompt, model = 'gpt-3.5-turbo') => {
const messages = [
{
role: 'user',
content: prompt
}
]
const result = await client.chat.completions.create({
model,
messages,
temperature: 0.1
})
return result.choices[0].message.content
}
// const result = await getCompletion('你好');
// console.log(result);
http
.createServer(async (req, res) => {
res.setHeader('Access-Control-Allow-Origin', '*')
// req, question data
const parsedUrl = url.parse(req.url, true)
// console.log(parsedUrl);
const prompt = `
${parsedUrl.query.data}
请根据上面的JSON数据, 回答${parsedUrl.query.question} 这个问题。
`
const result = await getCompletion(prompt)
let info = {
result
}
res.status = 200
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify(info))
})
.listen(1314)
最终效果如下
六、 项目运行指南
1. 启动后端服务
cd backend
pnpm run dev
2. 启动AI服务
cd llm
pnpm index.mjs
3. 启动前端页面
在浏览器中打开/frontend/index.html
七、项目总结
本项目前端采用Bootstrap构建响应式页面布局,通过获取表单输入框数据,调用fetch() 函数向1314端口发送GET请求,传递表单的输入内容,后端通过json-server快速构建RESTful API服务,通过配置文件指定服务端口(3001)和数据源(users.json),再通过http请求调用AI模型,处理收到的问题,返回AI回答的内容,完成了一套全面的AI全栈流程。
🌟 技术收获
通过这个完整的AI全栈项目,我们掌握了:
| 技术层面 | 具体技能 |
|---|---|
| 前端开发 | Bootstrap响应式布局、DOM操作、Fetch API使用 |
| 后端服务 | RESTful API概念、JSON Server配置、数据接口设计 |
| AI集成 | OpenAI API调用、提示词工程、异步编程 |
| 全栈思维 | 前后端分离架构、API设计、错误处理 |
🎉 结语
恭喜你完成了第一个AI全栈项目!这只是一个开始,全栈开发的世界还有很多精彩等待探索。记住,最好的学习方式就是动手实践,不断尝试新功能,遇到问题就查阅文档、搜索解决方案。保持好奇,持续编码,你就是下一个全栈大神! 💻✨