在这个AI快速发展的时代,你是否也想亲手搭建一个属于自己的AI聊天应用?
你可能已经尝试过使用各种AI服务,但总觉得缺少一些个性化的特色;或者你正在学习编程,想要一个既能实践技术又能创造价值的项目。
很多人最开始入门AI应用开发时应该会遇到很多疑虑:环境配置会不会很繁琐、API调用会不会很复杂、前端交互会不会很陌生...
在这篇教程中,我将用最简洁的技术栈(Python + Flask + HTML),带你一步步实现一个完整的AI聊天应用。
为什么选择这个技术组合?
因为它不仅学习曲线平缓,更重要的是能让我们专注于核心功能的实现,而不会陷入复杂的框架细节中。(说白了就是简单)
通过这个项目,你将学会:
1、如何搭建一个基础的Web应用框架
2、如何优雅地集成AI模型API
3、如何设计简洁且实用的用户界面
4、如何处理实时的对话交互
无论你是刚接触编程的新手,还是想要扩展技术栈的开发者,这篇教程都能帮你迈出构建AI应用的第一步。
客套话讲完了,现在让我们开始真正的代码实践:
首先我们复习一下,大语言模型的API需要具备的要素
1.请求地址:
base_url="https://api.openai.com/v1"
2.访问令牌:
api_key="sk-xxxxxx"
3.预设提示词与用户消息:
{
"role": "developer",
"content": "You are a helpful assistant."
},
{
"role": "user",
"content": "Hello!"
}
当然还有有个环境变量可以附加:4.模型名称
那现在我们就先确定我们需要三个环境变量:
OPENAI_API_KEY=
OPENAI_BASE_URL=
OPENAI_MODEL=
第一阶段:基础的一次性对话
首先让我们从最简单的一次性对话开始。这个版本只能进行单次非流式问答:
from openai import OpenAI
client = OpenAI(api_key="sk-0xxxxxxxxxx", base_url="https://api.xxxx.com/v1")
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": "You are a helpful assistant"},
{"role": "user", "content": "Hello"},
],
stream=False
)
print(response.choices[0].message.content)
输出效果:
这个基础版本虽然能够完成简单对话,但用户体验并不理想 - 模型的回答会一次性全部显示出来。在实际应用中,我们往往希望看到更自然的回复过程,就像真人打字一样。让我们在第二阶段添加流式输出功能。
关键改进计划: 将 stream 参数设置为 True 使用 for 循环逐步处理回复内容 通过 flush=True 确保实时显示每个字符
第二阶段:添加流式输出
接下来,我们为输出添加流式效果,让回答像打字机一样逐字显示:
from openai import OpenAI
client = OpenAI(api_key="sk-0xxxxxxxxxx", base_url="https://api.xxxx.com/v1")
completion = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "developer", "content": "You are a helpful assistant."},
{"role": "user", "content": "hello!"}
],
stream=True
)
for chunk in completion:
content = chunk.choices[0].delta.content
if content is not None:
print(content, end='', flush=True) # 立即输出,避免换行
输出效果:
流式输出让对话变得更自然,但目前的程序仍然只能进行一次对话就结束了。在实际使用中,我们希望能够持续进行对话。第三阶段将添加基本的交互循环功能。
关键改进计划: 添加 while True 循环支持持续对话 实现简单的退出机制 将对话逻辑封装进函数便于管理
第三阶段:支持连续对话
这个版本允许用户多次输入,但每次对话都是独立的,没有上下文关联:
from openai import OpenAI
client = OpenAI(api_key="sk-0xxxxxxxxxx", base_url="https://api.xxxx.com/v1")
def chat_once(prompt):
completion = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": prompt}],
stream=True
)
for chunk in completion:
content = chunk.choices[0].delta.content
if content is not None:
print(content, end='', flush=True)
print("\n") # 在回答结束后换行
while True:
user_input = input("\n请输入问题(输入 'quit' 退出): ")
if user_input.lower() == 'quit':
break
chat_once(user_input)
输出效果:
现在我们可以多次对话,不用一次执行就退出了,但是这个版本仍有局限:每次对话都是独立的,AI 无法记住之前的对话内容。这就像每次对话都在和一个"失忆"的助手交谈。
现在我们如何改进?
要实现真正的多轮对话,我们需要理解对话历史是如何工作的。
假设用户第一次提问:
messages = [
{"role": "user", "content": "中国最高的山峰是什么?"}
]
AI回答后,对话历史变成:
messages = [
{"role": "user", "content": "中国最高的山峰是什么?"},
{"role": "assistant", "content": "中国最高的山峰是珠穆朗玛峰,高度为8848.86米。"}
]
当用户继续提问"它有多高?"时,完整的对话历史变为:
messages = [
{"role": "user", "content": "中国最高的山峰是什么?"},
{"role": "assistant", "content": "中国最高的山峰是珠穆朗玛峰,高度为8848.86米。"},
{"role": "user", "content": "它有多高?"}
]
通过维护这个对话历史列表,AI就能理解上下文,进行连贯的对话。这正是第四阶段要实现的核心功能。
SO!
第四阶段:完整的多轮对话系统
最后,让我们实现一个完整的多轮对话系统,它能够记住对话历史并据此作出回应:
from openai import OpenAI
client = OpenAI(api_key="sk-0xxxxxxxxxx", base_url="https://api.xxxx.com/v1")
class ChatBot:
def __init__(self):
self.messages = []
def chat(self, user_input):
# 将用户输入添加到对话历史
self.messages.append({"role": "user", "content": user_input})
# 创建流式响应
completion = client.chat.completions.create(
model="gpt-4o-mini",
messages=self.messages,
stream=True
)
# 收集完整的响应文本
full_response = ""
for chunk in completion:
content = chunk.choices[0].delta.content
if content is not None:
full_response += content
print(content, end='', flush=True)
# 将助手的回复添加到对话历史
self.messages.append({"role": "assistant", "content": full_response})
print("\n") # 在回答结束后换行
def main():
chatbot = ChatBot()
print("欢迎使用AI聊天助手!输入 'quit' 退出对话。")
while True:
user_input = input("\n您: ")
if user_input.lower() == 'quit':
print("谢谢使用,再见!")
break
chatbot.chat(user_input)
if __name__ == "__main__":
main()
输出效果:
现在我们就有了一个可以在控制台连续对话并且可以简单记住历史记录的AI聊天机器人
代码说明
第一阶段展示了最基本的API调用,适合理解API的基本用法。
第二阶段添加了流式输出,提供了更好的用户体验。
第三阶段实现了基本的交互循环,但每次对话都是独立的。
第四阶段是最完整的实现:
使用类来管理对话状态
维护完整的对话历史
支持流式输出
提供了更好的用户交互逻辑
关键改进点:
从简单的脚本到结构化的类
从单次对话到完整的对话历史管理
添加了用户友好的提示和退出机制
保持了流式输出的即时反馈特性
当然这只是一个控制台聊天应用,甚至都没有一个UI界面
从控制台到Web:认识Flask与HTML
刚才我们已经实现了一个功能完整的控制台聊天应用。然而,在实际使用中,我们往往需要一个更友好的用户界面。这就需要将我们的应用改造成Web版本。在开始之前,让我们先了解一下要用到的核心技术。
什么是Flask?
Flask是一个轻量级的Python Web框架。如果把我们的应用比作一座房子,Flask就像是房子的框架和地基。它负责处理:
1. 路由:决定用户访问不同网址时显示什么内容
@app.route('/')
def home():
return "这是首页"
2. 处理请求:接收用户发送的数据
@app.route('/chat', methods=['POST'])
def chat():
user_message = request.json['message']
return chatbot.get_response(user_message)
Flask的特点是简单直观,不需要复杂的配置就能快速搭建Web应用。正如我们在控制台应用中追求简洁一样,Flask也能让我们专注于实现核心功能。
理解HTML
HTML则是我们应用的"界面",就像房子的墙壁和装饰。它定义了用户看到的内容布局和结构。一个简单的聊天界面可能是这样的:
<div class="chat-window">
<!-- 聊天记录区域 -->
<div class="chat-messages">
<div class="message user">
您好!
</div>
<div class="message bot">
你好!我是AI助手,很高兴为您服务。
</div>
</div>
<!-- 输入区域 -->
<div class="input-area">
<input type="text" placeholder="请输入您的问题...">
<button>发送</button>
</div>
</div>
渲染在浏览器里面就是:
Flask与HTML如何协同工作?
想象一下收发信件的过程:
1. 用户在浏览器(HTML界面)中输入消息
2. Flask收到这个"信件"(请求)
3. Flask将请求转交给我们的ChatBot处理
4. ChatBot生成回复
5. Flask将回复"寄回"给用户的浏览器
6. 浏览器(HTML)展示这个回复
这种工作模式让我们能够构建一个既美观又实用的聊天应用。在接下来的章节中,我们将一步步实现这个过程,把我们的控制台应用改造成一个完整的Web聊天系统。
1、项目结构与环境配置
首先,让我们创建一个简单但结构清晰的项目目录:
chatbot-web/
├── .env # 环境变量配置文件
├── app.py # Flask应用主文件
└── templates/ # HTML模板文件夹
└── index.html # 聊天界面
我们需要将之前用到的环境变量保存在.env
文件中:
OPENAI_API_KEY=sk-xxxxxxxxxx
OPENAI_BASE_URL=https://api.xxxx.com/v1
OPENAI_MODEL=gpt-4o-mini
其实将控制台应用转换为Web应用只需要完成三个主要步骤:
第一步:搭建Flask框架
我们需要将之前的ChatBot类改造成Web服务。这个过程中要确保保留流式输出的特性,同时适配Web环境的需求。Flask将作为连接前端界面和AI模型的桥梁,处理来自用户的请求并返回AI的响应。
第二步:设计聊天界面
聊天界面需要两个核心组件:
1.消息显示区域:用于展示对话历史
2.输入区域:供用户输入和发送消息
界面设计需要特别考虑如何展示流式响应,确保AI的回复能够像在控制台中一样逐字显示,创造出自然的对话感。
第三步:实现实时通信
这是最关键的部分,需要实现前端和后端之间的无缝通信。 主要包括:
1. 服务器端处理用户消息请求
2. 实现响应流的传输
3. 前端逐字符显示响应内容
每次对话的流程如下:
1. 用户在输入框中输入消息
2. 前端将消息发送到服务器
3. 服务器通过ChatBot处理消息
4. AI的响应以流的形式返回给前端
5. 前端实时显示响应内容,创造打字机效果
这种实现方式保持了控制台版本的自然交互感,同时提供了更直观的用户界面。通过流式传输,用户可以实时看到AI响应的形成过程,就像在控制台中一样。
2、基础Web聊天应用的实际代码
让我们从一个简单但功能完整的版本开始。
首先是主应用文件app.py:
from flask import Flask, render_template, request, Response
from openai import OpenAI
from dotenv import load_dotenv
import os
import json
load_dotenv()
app = Flask(__name__)
client = OpenAI(
api_key=os.getenv('OPENAI_API_KEY'),
base_url=os.getenv('OPENAI_BASE_URL')
)
class ChatBot:
def __init__(self):
self.messages = []
def get_stream_response(self, user_input):
self.messages.append({"role": "user", "content": user_input})
try:
response = client.chat.completions.create(
model=os.getenv('OPENAI_MODEL'),
messages=self.messages,
stream=True
)
for chunk in response:
if chunk.choices[0].delta.content:
yield f"data: {json.dumps({'content': chunk.choices[0].delta.content})}\n\n"
# 保存完整对话到历史记录
full_response = ''.join(chunk.choices[0].delta.content or ''
for chunk in response)
self.messages.append({"role": "assistant", "content": full_response})
except Exception as e:
yield f"data: {json.dumps({'error': str(e)})}\n\n"
chatbot = ChatBot()
@app.route('/')
def home():
return render_template('index.html')
@app.route('/chat', methods=['POST'])
def chat():
user_message = request.json.get('message', '')
return Response(
chatbot.get_stream_response(user_message),
mimetype='text/event-stream'
)
if __name__ == '__main__':
app.run(debug=True)
然后是聊天界面模板index.html:
<!DOCTYPE html>
<html>
<head>
<title>AI聊天助手</title>
<style>
.chat-container {
max-width: 800px;
margin: 20px auto;
padding: 20px;
border: 1px solid #ddd;
border-radius: 8px;
}
.chat-box {
height: 400px;
overflow-y: auto;
border: 1px solid #eee;
padding: 10px;
margin-bottom: 20px;
}
.input-area {
display: flex;
gap: 10px;
}
#user-input {
flex: 1;
padding: 8px;
}
.message {
margin: 10px 0;
padding: 8px;
border-radius: 4px;
}
.user-message {
background: #007bff;
color: white;
margin-left: 20%;
}
.bot-message {
background: #f0f0f0;
margin-right: 20%;
}
</style>
</head>
<body>
<div class="chat-container">
<div class="chat-box" id="chat-box"></div>
<div class="input-area">
<input type="text" id="user-input" placeholder="请输入您的问题..." onkeypress="handleKeyPress(event)">
<button onclick="sendMessage()">发送</button>
</div>
</div>
<script>
function sendMessage() {
const input = document.getElementById('user-input');
const message = input.value.trim();
if (message === '') return;
// 显示用户消息
addMessage(message, 'user');
input.value = '';
// 创建新的bot消息div用于流式显示
const botMessageDiv = document.createElement('div');
botMessageDiv.className = 'message bot-message';
document.getElementById('chat-box').appendChild(botMessageDiv);
// 发送POST请求到服务器
fetch('/chat', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({message: message})
}).then(response => {
const reader = response.body.getReader();
let decoder = new TextDecoder();
function readStream() {
reader.read().then(({done, value}) => {
if (done) {
return;
}
// 处理接收到的数据
const text = decoder.decode(value);
const lines = text.split('\n');
lines.forEach(line => {
if (line.startsWith('data: ')) {
try {
const data = JSON.parse(line.slice(6));
if (data.content) {
botMessageDiv.textContent += data.content;
botMessageDiv.scrollIntoView({ behavior: 'smooth' });
}
if (data.error) {
botMessageDiv.textContent = '错误: ' + data.error;
}
} catch (e) {
console.error('解析响应数据时出错:', e);
}
}
});
readStream();
}).catch(error => {
console.error('读取流时出错:', error);
botMessageDiv.textContent = '连接出错,请重试';
});
}
readStream();
}).catch(error => {
console.error('发送请求时出错:', error);
botMessageDiv.textContent = '发送失败,请重试';
});
}
function addMessage(message, sender) {
const chatBox = document.getElementById('chat-box');
const messageDiv = document.createElement('div');
messageDiv.className = `message ${sender}-message`;
messageDiv.textContent = message;
chatBox.appendChild(messageDiv);
chatBox.scrollTop = chatBox.scrollHeight;
}
function handleKeyPress(event) {
if (event.keyCode === 13) { // Enter键
sendMessage();
}
}
</script>
</body>
</html>
需要启动应用,只需在终端中运行:
python app.py
然后在浏览器中访问 http://localhost:5000 即可开始聊天。
具体的渲染效果则是:
好了现在我们就有了一个最基础的AI Web聊天应用
最后让我们加亿点点细节:
比如加个边框,加个markdown渲染,加个latx公式渲染,加个历史记录,搞个头像......
自己试一下吧