基于Python + LangChain + 通义千问的聊天机器人实战
本文详细介绍如何从零搭建一个带可视化界面的AI聊天机器人。采用Python+Flask+LangChain作为后端,接入阿里云通义千问大模型;前端使用纯HTML/CSS/JS实现无框架依赖的交互界面。文章包含完整开发流程:从环境配置、API密钥管理,到核心聊天逻辑实现(支持多轮对话),再到前后端分离架构设计(REST API+Axios通信)。项目亮点包括会话管理、Markdown渲染、加载动画等
> 零基础搭建一个带可视化界面的AI聊天机器人,从环境搭建到前后端联调,完整走一遍。
前言
大语言模型(LLM)的火热让"AI聊天机器人"成了每个开发者都想动手试试的项目。但很多人卡在了第一步——怎么把模型能力接到自己的代码里?
这篇文章带你从零搭建一个完整的聊天机器人应用:
- **后端**:Python + Flask + LangChain + 通义千问
- **前端**:纯 HTML/CSS/JS,无框架依赖
- **通信**:REST API + Axios
最终效果:浏览器打开页面,打字发送,AI实时回复,支持多轮对话。
一、环境准备
1.1 Python 版本
本项目使用 **Python 3.13**,建议 3.10 以上版本均可。
1.2 安装依赖
pip install langchain # LangChain 核心框架
pip install langchain-community # 第三方模型集成
pip install langchain-openai # OpenAI 兼容接口(通义千问用这个)
pip install python-dotenv # 环境变量管理
pip install flask # Web 后端框架
pip install flask-cors # 跨域支持
1.3 获取通义千问 API Key
通义千问是阿里云推出的大语言模型,提供了兼容 OpenAI 的 API 接口,对国内开发者非常友好。
1. 访问 [阿里云 DashScope 控制台](https://dashscope.console.aliyun.com/)
2. 注册/登录阿里云账号
3. 开通 DashScope 服务(新用户有免费额度)
4. 在「API-KEY 管理」中创建一个新的 Key

拿到 Key 后,在项目根目录创建 `.env` 文件:
DASHSCOPE_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx
为什么用 .env 文件?** 把密钥写在代码里是危险的,一旦上传到 Git 就泄露了。`.env` 文件可以加入 `.gitignore`,保证密钥安全。
二、核心原理:LangChain + 通义千问
2.1 为什么选 LangChain?
LangChain 是目前最流行的 LLM 应用开发框架。它的核心价值在于:
- **统一接口**:同一套代码,换模型只需改两行配置
- **消息管理**:自动管理对话历史(System / Human / AI 消息)
- **生态丰富**:链式调用、Agent、RAG 等高级能力随时可扩展
2.2 通义千问的接入方式
通义千问提供了 **OpenAI 兼容模式**,这意味着我们不需要学新的 SDK,直接用 LangChain 的 `ChatOpenAI` 类,把接口地址换成阿里云的就行:
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
model="qwen-plus", # 模型名称
openai_api_key="你的API Key",
openai_api_base="https://dashscope.aliyuncs.com/compatible-mode/v1",
)
就这么简单,三行代码接入大模型。
2.3 可选模型
| 模型 | 特点 | 适用场景 |
| `qwen-turbo` | 速度快,成本低 | 简单问答、快速响应 |
| `qwen-plus` | 均衡型 | 通用对话(推荐) |
| `qwen-max` | 能力最强 | 复杂推理、长文本 |
三、命令行版:最小可运行Demo
先把最核心的聊天逻辑跑通,文件名 `chat_qwen.py`:
import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
# 加载 .env 中的环境变量
load_dotenv()
def create_chat_model():
"""创建通义千问聊天模型"""
return ChatOpenAI(
model="qwen-plus",
openai_api_key=os.getenv("DASHSCOPE_API_KEY"),
openai_api_base="https://dashscope.aliyuncs.com/compatible-mode/v1",
)
def main():
chat = create_chat_model()
# 初始化对话历史,设定 AI 的角色
messages = [
SystemMessage(content="你是一个有用的AI助手,请用中文回答问题。")
]
print("通义千问聊天机器人 (输入 quit 退出)")
print("=" * 40)
while True:
user_input = input("\n你: ").strip()
if not user_input:
continue
if user_input.lower() == "quit":
print("再见!")
break
# 把用户消息加入历史
messages.append(HumanMessage(content=user_input))
# 调用模型(传入完整历史,实现多轮对话)
response = chat.invoke(messages)
# 把 AI 回复也加入历史,下一轮它会"记住"
messages.append(AIMessage(content=response.content))
print(f"\nAI: {response.content}")
if __name__ == "__main__":
main()
运行:
python chat_qwen.py
到这一步,已经完成了核心功能——一个支持多轮对话的命令行聊天机器人。
关键点解析:
- `SystemMessage`:设定 AI 的角色和行为准则,每次对话都会带上
- `HumanMessage`:用户说的话
- `AIMessage`:AI 的回复
- 每次调用 `chat.invoke(messages)` 时传入“完整的消息列表”,模型据此理解上下文
四、Web化:从命令行到可视化界面
4.1 整体架构
浏览器 (index.html)
│ axios POST /api/chat
Flask 后端 (server.py)
│ LangChain 调用通义千问 API
阿里云 DashScope API
│
通义千问大模型 → 返回结果
4.2 后端 API:server.py
import os
import uuid
from dotenv import load_dotenv
from flask import Flask, request, jsonify, send_from_directory
from flask_cors import CORS
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
load_dotenv()
app = Flask(__name__, static_folder="static")
CORS(app)
# 用字典在内存中存储各会话的聊天历史
sessions = {}
SYSTEM_PROMPT = "你是一个有用的AI助手,请用中文回答问题。"
def get_chat_model():
return ChatOpenAI(
model="qwen-plus",
openai_api_key=os.getenv("DASHSCOPE_API_KEY"),
openai_api_base="https://dashscope.aliyuncs.com/compatible-mode/v1",
)
# 前端页面
@app.route("/")
def index():
return send_from_directory("static", "index.html")
# 聊天接口
@app.route("/api/chat", methods=["POST"])
def chat():
data = request.json
user_message = data.get("message", "").strip()
session_id = data.get("session_id")
if not user_message:
return jsonify({"error": "消息不能为空"}), 400
# 首次访问,创建新会话
if not session_id or session_id not in sessions:
session_id = str(uuid.uuid4())
sessions[session_id] = [SystemMessage(content=SYSTEM_PROMPT)]
history = sessions[session_id]
history.append(HumanMessage(content=user_message))
try:
llm = get_chat_model()
response = llm.invoke(history)
history.append(AIMessage(content=response.content))
return jsonify({
"reply": response.content,
"session_id": session_id,
})
except Exception as e:
return jsonify({"error": str(e)}), 500
# 新建会话接口
@app.route("/api/new-session", methods=["POST"])
def new_session():
session_id = str(uuid.uuid4())
sessions[session_id] = [SystemMessage(content=SYSTEM_PROMPT)]
return jsonify({"session_id": session_id})
if __name__ == "__main__":
print("聊天机器人服务启动: http://127.0.0.1:5000")
app.run(debug=True, port=5000)
API 说明:
| 接口 | 方法 | 功能 | 参数 |
| `/api/chat` | POST | 发送消息并获取AI回复 | `{ message, session_id }` |
| `/api/new-session` | POST | 创建新的对话会话 | 无 |
| `/` | GET | 返回前端页面 | 无 |
会话管理思路:用 `session_id` 区分不同对话,每个 session 维护独立的消息历史。实际生产环境可替换为 Redis 或数据库存储。
4.3 前端页面:static/index.html
前端是纯 HTML + CSS + JS 的单文件方案,引入了两个 CDN 库:
- Axios:HTTP 请求
- Marked.js:将 AI 回复中的 Markdown 渲染为 HTML(支持代码块高亮)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>通义千问聊天机器人</title>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background: #f0f2f5;
height: 100vh;
display: flex;
flex-direction: column;
}
/* 顶部栏 */
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #fff;
padding: 16px 24px;
display: flex;
align-items: center;
justify-content: space-between;
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
}
.header h1 { font-size: 20px; font-weight: 600; }
.header button {
background: rgba(255,255,255,0.2);
border: 1px solid rgba(255,255,255,0.4);
color: #fff;
padding: 6px 16px;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
transition: background 0.2s;
}
.header button:hover { background: rgba(255,255,255,0.35); }
/* 聊天区域 */
.chat-container {
flex: 1;
overflow-y: auto;
padding: 20px;
max-width: 800px;
width: 100%;
margin: 0 auto;
}
.message {
display: flex;
margin-bottom: 16px;
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
.message.user { justify-content: flex-end; }
.message.ai { justify-content: flex-start; }
.avatar {
width: 36px;
height: 36px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
flex-shrink: 0;
}
.message.user .avatar { background: #667eea; color: #fff; margin-left: 10px; order: 2; }
.message.ai .avatar { background: #e8e8e8; color: #555; margin-right: 10px; }
.bubble {
max-width: 70%;
padding: 12px 16px;
border-radius: 12px;
line-height: 1.6;
font-size: 15px;
word-break: break-word;
}
.message.user .bubble {
background: linear-gradient(135deg, #667eea, #764ba2);
color: #fff;
border-bottom-right-radius: 4px;
}
.message.ai .bubble {
background: #fff;
color: #333;
border-bottom-left-radius: 4px;
box-shadow: 0 1px 4px rgba(0,0,0,0.08);
}
.message.ai .bubble p { margin: 0.4em 0; }
.message.ai .bubble pre {
background: #1e1e1e;
color: #d4d4d4;
padding: 12px;
border-radius: 6px;
overflow-x: auto;
margin: 8px 0;
font-size: 13px;
}
.message.ai .bubble code {
background: #f0f0f0;
padding: 2px 6px;
border-radius: 3px;
font-size: 13px;
}
.message.ai .bubble pre code { background: none; padding: 0; }
/* 加载动画 */
.typing-indicator {
display: flex;
gap: 4px;
padding: 4px 0;
}
.typing-indicator span {
width: 8px; height: 8px;
background: #999;
border-radius: 50%;
animation: bounce 1.4s infinite;
}
.typing-indicator span:nth-child(2) { animation-delay: 0.2s; }
.typing-indicator span:nth-child(3) { animation-delay: 0.4s; }
@keyframes bounce {
0%, 60%, 100% { transform: translateY(0); }
30% { transform: translateY(-6px); }
}
/* 输入区域 */
.input-area {
background: #fff;
padding: 16px 20px;
border-top: 1px solid #e0e0e0;
box-shadow: 0 -2px 8px rgba(0,0,0,0.05);
}
.input-wrapper {
max-width: 800px;
margin: 0 auto;
display: flex;
gap: 12px;
}
.input-wrapper textarea {
flex: 1;
padding: 12px 16px;
border: 1px solid #ddd;
border-radius: 10px;
font-size: 15px;
resize: none;
outline: none;
font-family: inherit;
transition: border-color 0.2s;
min-height: 46px;
max-height: 120px;
}
.input-wrapper textarea:focus { border-color: #667eea; }
.input-wrapper button {
background: linear-gradient(135deg, #667eea, #764ba2);
color: #fff;
border: none;
padding: 12px 24px;
border-radius: 10px;
font-size: 15px;
cursor: pointer;
transition: opacity 0.2s;
white-space: nowrap;
}
.input-wrapper button:hover { opacity: 0.9; }
.input-wrapper button:disabled { opacity: 0.5; cursor: not-allowed; }
.error-msg {
color: #e74c3c;
text-align: center;
padding: 8px;
font-size: 14px;
}
</style>
</head>
<body>
<div class="header">
<h1>通义千问聊天机器人</h1>
<button onclick="newSession()">新对话</button>
</div>
<div class="chat-container" id="chatContainer">
<div class="message ai">
<div class="avatar">AI</div>
<div class="bubble">你好!我是通义千问助手,有什么可以帮你的吗?</div>
</div>
</div>
<div class="input-area">
<div class="input-wrapper">
<textarea id="userInput" placeholder="输入消息,按 Enter 发送..." rows="1"
oninput="autoResize(this)"></textarea>
<button id="sendBtn" onclick="sendMessage()">发送</button>
</div>
</div>
<script>
let sessionId = null;
const container = document.getElementById('chatContainer');
const userInput = document.getElementById('userInput');
const sendBtn = document.getElementById('sendBtn');
// Enter 发送, Shift+Enter 换行
userInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
});
function autoResize(el) {
el.style.height = 'auto';
el.style.height = Math.min(el.scrollHeight, 120) + 'px';
}
function scrollToBottom() {
container.scrollTop = container.scrollHeight;
}
function addMessage(role, content) {
const div = document.createElement('div');
div.className = `message ${role}`;
const avatar = document.createElement('div');
avatar.className = 'avatar';
avatar.textContent = role === 'user' ? '我' : 'AI';
const bubble = document.createElement('div');
bubble.className = 'bubble';
if (role === 'ai') {
bubble.innerHTML = marked.parse(content);
} else {
bubble.textContent = content;
}
div.appendChild(avatar);
div.appendChild(bubble);
container.appendChild(div);
scrollToBottom();
return bubble;
}
function addTypingIndicator() {
const div = document.createElement('div');
div.className = 'message ai';
div.id = 'typing';
const avatar = document.createElement('div');
avatar.className = 'avatar';
avatar.textContent = 'AI';
const bubble = document.createElement('div');
bubble.className = 'bubble';
bubble.innerHTML = '<div class="typing-indicator"><span></span><span></span><span></span></div>';
div.appendChild(avatar);
div.appendChild(bubble);
container.appendChild(div);
scrollToBottom();
}
function removeTypingIndicator() {
const el = document.getElementById('typing');
if (el) el.remove();
}
async function sendMessage() {
const message = userInput.value.trim();
if (!message) return;
userInput.value = '';
userInput.style.height = 'auto';
sendBtn.disabled = true;
addMessage('user', message);
addTypingIndicator();
try {
const res = await axios.post('/api/chat', { message, session_id: sessionId });
removeTypingIndicator();
sessionId = res.data.session_id;
addMessage('ai', res.data.reply);
} catch (err) {
removeTypingIndicator();
const errMsg = err.response?.data?.error || '请求失败,请重试';
addMessage('ai', `出错了: ${errMsg}`);
} finally {
sendBtn.disabled = false;
userInput.focus();
}
}
async function newSession() {
try {
const res = await axios.post('/api/new-session');
sessionId = res.data.session_id;
container.innerHTML = '';
addMessage('ai', '新对话已开始,有什么可以帮你的吗?');
} catch (err) {
alert('创建新对话失败');
}
}
// 页面加载时创建会话
newSession();
</script>
</body>
</html>
五、运行效果
启动服务:
python server.py
浏览器打开 http://127.0.0.1:5000,即可看到聊天界面:
- 紫色渐变气泡为用户消息,白色气泡为 AI 回复
- AI 回复支持 Markdown 格式(代码块、列表等)
- 等待回复时有跳动的加载动画
- 点击「新对话」清空历史、重新开始
- 支持 Enter 发送、Shift+Enter 换行
六、项目结构
test-project/
├── .env # API Key 配置(不入库)
├── chat_qwen.py # 命令行版聊天机器人
├── server.py # Flask Web 后端
└── static/
└── index.html # 前端聊天界面
七、后续扩展方向
这个项目是一个最小可用的基础版本,在此基础上可以扩展很多能力:
| 方向 | 说明 |
| 流式输出 | 用 SSE(Server-Sent Events)实现逐字输出,体验更流畅 |
| RAG 知识库 | 接入向量数据库,让 AI 基于你的文档回答问题 |
| Agent 工具调用 | 给 AI 加上搜索、查天气、执行代码等工具能力 |
| 用户系统 | 加登录注册,对话历史存数据库(SQLite/MySQL) |
| 部署上线 | 用 Gunicorn + Nginx 部署到云服务器 |
注意:仅供参考!!!
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐




所有评论(0)