AI 新手村大冒险:我的第一次全栈项目“智”旅

294 阅读7分钟

前言:

在经历了一段时间对于前后端的初步学习、LLM 的了解与实战后,今天给大家分享一下本人第一次AI全栈项目。而通过这篇文章,我希望能够为那些同样希望学习 AI 的充满热情的小白提供一些有用的建议和经验分享。本项目为实现 通过询问 AI 内容来查询表格中的内容,那么话不多说,直接开整!!

准备工作

1.创建好所需文件夹:

首先我们需要准备三个文件夹来分别存放我们项目的backend(后端)、frontend(前端)、LLM(大模型),这样可以提高我们项目的结构清晰度、维护性,同时简化开发和部署流程。 image.png

2. user.json的创建:

backend文件夹中创建一个user.json文件,在这里存放我们的后端数据,例如:

{
    "users": [
        {
            "id": 1,
            "name": "张三",
            "hometown": "江西"
        },
        {
            "id": 2,
            "name": "李四",
            "hometown": "北京"
        },
        {
            "id": 3,
            "name": "王五",
            "hometown": "南京"
        }
    ]
}

在准备工作弄好后,那么直接开整项目来咯!!

正片开始

后端部分(backend):

1.初始化项目 + 创建服务器:

在后端文件夹中,我们需要在命令行中输入以下指令

// 初始化项目:快速生成一个带有默认配置的 package.json 文件
npm init -y

// 创建服务器,快速创建一个 RESTful API 服务器,模拟后端 API,使得前端开发可以在没有实际后端服务的情况下进行。
npm i json-server

2.定义脚本:

package.json中,我们将设置以下内容:

"scripts": {
  "dev": "json-server --watch user.json --port 3001"
},

这里我们进行拆分理解:

  • "dev" :这是脚本的名称,我们可以在终端中通过 npm run dev 来运行这个脚本。
  • json-server:调用json-server工具的命令。
  • --watch user.json:这个选项告诉json-server去监听(或者说监视)user.json文件的变化。当user.json文件发生变化时,json-server会自动重新加载数据。
  • --port 3001:这个选项指定了服务器监听的端口号为3001。

这意味着我们可以通过 http://localhost:3001/users 访问我们的 API,并且告诉 json-server 监听 user.json 文件的变化,实时反映这些变化。

让我们在命令行中输入npm run dev来看看这个脚本,得到了以下结果:

image.png

在这里可以看到已经成功了,那么我们接下来就可以在前端通过 fetch(Endpoints) 来访问后端的数据了。

LLM 部分:

1. 初始化项目 + OpenAI:

在 LLM 文件夹中,我们需要在命令行中输入以下指令

// 初始化项目:快速生成一个带有默认配置的 package.json 文件
npm init -y

// 这将会根据你的 `package.json` 文件中的配置下载并安装最新版本的 `openai` 包以及它的依赖
npm i openai

2. 利用 OpenAI 来处理来自客户端的问题请求:

首先我们进行导入模块

const http = require('http'); // 使用 Node.js 内置的 http 模块来创建 HTTP 服务器
const OpenAi = require('openai'); // 然后引入 OpenAI
const url = require('url'); // 用 Node.js 内置的 url 模块来解析 URL 字符串。

创建了一个 OpenAI API 客户端实例,这里的 apiKeybaseURL 请各位自身实际情况填写。apiKey 是你访问 OpenAI API 所需的密钥,而 baseURL 则是指向 OpenAI API 的基础 URL。当然我们这里没有使用 dotenv来保护我们的 key ,请读者自行增添。

const client = new OpenAi({
    apiKey: '',
    baseURL:''
});

接下来就是创建getCompletion函数来实现与 OpenAI 的对话式 API (Chat API) 进行交互的功能,我们输入提示(prompt)后,AI 模型就会生成文本来响应。

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,
        temperature: 0.1 // 控制输出的随机性
    }) 
    return response.choices[0].message.content
}

接下来就是创建了一个 HTTP 服务器,接收带有查询参数的请求,将这些参数构造成提示信息发送给 OpenAI 的 API 获取 AI 回答,然后将回答以 JSON 格式返回给客户端。

const server = http.createServer(async (req, res) => {
    // 设置 CORS 相关的响应头
    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;
    
    console.log(parsedUrl.query);

    // 构造用户问题的提示信息
    const prompt = `
    ${queryObj.data}
    请根据上面的JSON数据,回答${queryObj.question} 这个问题
    `;
    
    // 获取 AI 回答
    const result = await getCompletion(prompt);
    let info = {
        result
    };
    
    // 发送响应给客户端
    res.statusCode = 200;
    res.setHeader('Content-Type', 'text/json');
    res.end(JSON.stringify(info));
})

最后,HTTP 服务器开始监听 1314 端口上的连接请求。当有客户端发起 HTTP 请求时,Node.js 会执行上面定义的回调函数来处理请求。

server.listen(1314)

前端部分(frontend):

在前端部分我们要实现创建一个简单的网页应用,包含一个表格用于显示用户信息,以及一个表单用于向后端发送问题并接收 AI 的回答。

首先我们创建一个页面,该页面包含一个用户信息表格,用于展示用户的 ID、姓名和家乡信息。包含一个简单的表单,允许用户输入一个问题并提交给服务器处理。最后,有一个用于显示结果或消息的区域。

<!-- 用户信息表格 -->
<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 class="row">
           <form name="aiForm">
               <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>

接下来就是加载用户数据了,首先通过fetch发送 HTTP 请求到 http://localhost:3001/users 获取用户数据,然后通过.then(res => res.json())将响应内容解析为 JSON 格式,使用 for...of 循环遍历每个用户对象,并为每个用户创建一行(<tr>)添加到表格中。(别忘了进行错误处理哦~)

// 请求数据
fetch('http://localhost:3001/users')
    // 解析内容
    .then(res => res.json())
    .then(users => {
        usersData = users;
        for(let user of users){
            const tr = document.createElement('tr');
            // 在这里我们不对idTd,nameTd,hometownTd分别操作,可以提高代码的美观加可读性
            for(let key in user){
                const td = document.createElement('td');
                td.innerText = user[key];
                tr.appendChild(td);
            }
            tableBody.appendChild(tr);
        }
    })
    .catch(error => console.error('Error:', error));

然后就是处理表单提交事件,当用户提交表单时,阻止默认的页面刷新行为,并使用 fetch API 向 AI 服务器发送问题和用户数据,然后将服务器返回的回答显示在页面上。

oForm.addEventListener('submit', e => {
    e.preventDefault(); // 阻止表单默认提交
    // 使用 fetch 在页面不刷新的时候 向ai server 发出请求
    // 发送数据到后端
    
    // web 2.0 动态页面开发  (js 用fetch实现 可以主动拉取数据)
    const question = oForm.question.value.trim();
    if(!question){
        alert('请输入问题');
        return;
    }

    fetch(`http://localhost:1314/api?question=${question}&data=${JSON.stringify(usersData)}`)
        .then(data => data.json())
        .then(data => {
            document.getElementById('message').innerText = data.result;
        })
});

献上代码:

<!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>
                </tbody>
            </table>
            <div class="row">
                <form name="aiForm">
                    <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>
        // js 主动向后端发送数据接口请求
        // fetch --> 前端向后端(json-server)拉取数据
        const tableBody = document.querySelector('table tbody');
        const oForm = document.forms['aiForm'];

        let usersData ;

        fetch('http://localhost:3001/users')
            // 数据到达前端(二进制数据 -> json格式)
            .then(res => res.json())
            .then(users => {
                usersData = users;
                // 处理后端返回的数据
                // console.log(data);
                for(let user of users){
                    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);
                    }

                    // 屎山代码
                    // const idTd = document.createElement('td');
                    // idTd.innerText = user.id;
                    // tr.appendChild(idTd);

                    // const nameTd = document.createElement('td');
                    // nameTd.innerText = user.name;
                    // tr.appendChild(nameTd);

                    // const hometownTd = document.createElement('td');
                    // hometownTd.innerText = user.hometown;
                    // tr.appendChild(hometownTd);

                    tableBody.appendChild(tr);
                    
                }
            })
            .catch(error => {
                // 处理请求错误
                console.error('Error:', error);
            });

            oForm.addEventListener('submit', e => {
                // 事件对象
                // console.log(e);
                e.preventDefault(); // 阻止表单默认提交
                // 使用 fetch 在页面不刷新的时候 向ai server 发出请求
                // 发送数据到后端

                // web 2.0 动态页面开发  (js 用fetch实现 可以主动拉取数据)
                const question = oForm.question.value.trim();
                if(!question){
                    alert('请输入问题');
                    return;
                }

                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>
</body>
</html>

到这里我们就实现了这个项目的所有功能了,是不是感觉有种坐过山车的感觉,赶紧来检验一下成果吧!!

在 LLM 的终端中输入node main.js后,在提问框中输入我们的需要查询的内容,例如:"有哪些人是江西的",我们就能得到返还内容了。

image.png

image.png

小结:

本文通过构建包含后端 API 服务器、OpenAI 集成和动态前端界面的全栈应用,展示了如何实现用户与 AI 的交互,希望对各位有所帮助。

---欢迎各位点赞、收藏、关注,如果觉得有收获或者需要改进的地方,希望评论在下方,不定期更新

0bae-hcffhsw0416753.gif