基于多个大模型自己建造一个AI智能助手
一、概述
前面在博文 "如何自己建造一个AI智能助手" https://blog.csdn.net/cnds123/article/details/155134935 是基于通义千问(Qwen)API(免费额度可用)构建的AI智能助手。
你只需要一个通义千问的DashScope API Key https://dashscope.console.aliyun.com/apiKey (获取 API Key。阿里云账号免费注册,新用户送额度)。需要用支付宝对账号账号进行实名认证。
下面介绍,基于多个大模型,建造一个AI智能助手。
基于多个大模型自己建造一个AI **智能助手,**需要获取多个大模型的API Key。这里基于DashScope API Key 和DeepSeek 的API为例演示。
获取 DeepSeek API Key: https://platform.deepseek.com/sign_in
注意:DeepSeek调用 API 服务需要充值。实名认证后再充值,支付金额最少10元。【充值金额仅用于调用 API 服务,网页版及 App 对话免费使用,无需充值。】
需要有一个邮箱验证登录。创建成功后,系统会生成一个以 sk- 开头的 API Key
立即复制并妥善保存这个 Key,因为关闭对话框后通常无法再次查看完整 Key。
编程能力,这里,具体采用:
-
前端:纯 HTML + JavaScript
-
后端:Python FastAPI
-
AI 模型 :调用DashScope API Key 和DeepSeek 的API
-
输入框改进行:用多行文本域(<textarea>),不仅能输入多行内容,还能保留换行、缩进等格式,特别适合粘贴 Python 代码、JSON、Markdown 等结构化文本。支持 Enter 发送(避免换行冲突,需按 Ctrl+Enter发送)
注意:在运行这个应用时,需要安装这些第三方库。
安装命令:
pip install fastapi uvicorn httpx pydantic dashscope
解释一下这些第三方库的作用:
FastAPI: 构建API的主要框架。其中,FastAPI创建 Web API 应用的主要类;HTTPException用于抛出 HTTP 错误异常;CORSMiddleware:处理跨域资源共享(CORS)的中间件。
pydantic: 通过Python类型注解来进行数据验证和设置管理。其中BaseModel 用于定义数据模型。
httpx: 支持异步的HTTP客户端。用于向 DeepSeek API 发送请求。
uvicorn: 用于运行FastAPI应用的ASGI服务器。运行 FastAPI 应用。
Dashscope:是阿里云推出的 模型即服务(MaaS)平台,提供包括通义千问(Qwen)在内的多种大模型 API,支持文本生成、图像生成、语音识别等。
先看运行效果截图:

二、源码
项目结构
D:/ai-assistant- multiple/
├── backend/
│ └── main.py
└── frontend/
└── index.html
后端:backend/main.py 文件源码:
python
# backend/main.py
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
import httpx
import json
from enum import Enum
from typing import Optional
import os #
# 模型配置
class ModelType(str, Enum):
DEEPSEEK = "deepseek"
TONGYI = "tongyi"
OPENAI = "openai"
# API 配置
API_CONFIG = {
ModelType.DEEPSEEK: {
"url": "https://api.deepseek.com/v1/chat/completions",
#"api_key": "你的 DeepSeek API Key", # 你的 DeepSeek API Key,从环境变量读取更安全
"api_key": os.getenv("DEEPSEEK_API_KEY"), # ← 这行
"model_name": "deepseek-chat"
},
ModelType.TONGYI: {
"url": "https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation",
#"api_key": "你的 Tongyi API Key", # 你的 Tongyi API Key,从环境变量读取更安全
"api_key": os.getenv("DASHSCOPE_API_KEY"), # ← 这行
"model_name": "qwen-plus"
}
}
app = FastAPI()
# 允许前端跨域访问
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
class ChatRequest(BaseModel):
message: str
model: ModelType = ModelType.DEEPSEEK # 默认模型
class ModelInfo(BaseModel):
name: str
display_name: str
provider: str
@app.get("/models")
async def get_available_models():
"""获取可用的模型列表"""
models = [
ModelInfo(
name=ModelType.DEEPSEEK,
display_name="DeepSeek Chat",
provider="DeepSeek"
),
ModelInfo(
name=ModelType.TONGYI,
display_name="通义千问",
provider="阿里云"
)
]
return {"models": models}
@app.post("/chat")
async def chat(request: ChatRequest):
try:
config = API_CONFIG.get(request.model)
if not config:
raise HTTPException(status_code=400, detail="不支持的模型")
if request.model == ModelType.TONGYI:
return await call_tongyi_api(request.message, config)
else:
return await call_openai_compatible_api(request.message, config, request.model)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
async def call_openai_compatible_api(message: str, config: dict, model_type: ModelType):
"""调用 OpenAI 兼容的 API(DeepSeek、OpenAI)"""
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {config['api_key']}"
}
payload = {
"model": config["model_name"],
"messages": [
{
"role": "user",
"content": message
}
],
"stream": False
}
async with httpx.AsyncClient() as client:
response = await client.post(
config["url"],
headers=headers,
json=payload,
timeout=30.0
)
if response.status_code == 200:
data = response.json()
return {"reply": data["choices"][0]["message"]["content"]}
else:
error_detail = f"{model_type.value} API 错误: {response.status_code} - {response.text}"
raise HTTPException(status_code=500, detail=error_detail)
async def call_tongyi_api(message: str, config: dict):
"""调用通义千问 API"""
import dashscope
from dashscope import Generation
dashscope.api_key = config["api_key"]
response = Generation.call(
model=config["model_name"],
prompt=message
)
if response.status_code == 200:
return {"reply": response.output.text}
else:
raise HTTPException(status_code=500, detail=f"通义千问 API 错误: {response.status_code}")
@app.get("/")
async def root():
return {"message": "多模型 AI 助手后端服务运行中"}
前端:frontend/index.html 文件源码:
html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<title>多模型 AI 助手</title>
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css"
crossorigin="anonymous">
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
max-width: 800px;
margin: 40px auto;
padding: 20px;
background: #f9f9f9;
line-height: 1.6;
}
#chat-box {
height: 400px;
border: 1px solid #ddd;
padding: 15px;
overflow-y: auto;
background: white;
margin-bottom: 10px;
border-radius: 6px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.controls {
display: flex;
gap: 10px;
margin-bottom: 10px;
align-items: flex-start; /* 适配 textarea 高度 */
}
#model-select {
padding: 8px 12px;
border: 1px solid #ccc;
border-radius: 4px;
background: white;
flex-shrink: 0;
}
/* 多行输入框 */
#user-input {
flex: 1;
padding: 12px;
border: 1px solid #ccc;
border-radius: 6px;
resize: vertical; /* 允许手动拉高 */
min-height: 60px;
max-height: 200px;
font-family: Consolas, Monaco, 'Courier New', monospace; /* 等宽字体,适合代码 */
font-size: 14px;
line-height: 1.4;
}
#send-btn {
padding: 12px 20px;
background: #007bff;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
flex-shrink: 0;
}
#send-btn:hover { background: #0069d9; }
#send-btn:disabled { background: #6c757d; cursor: not-allowed; }
.message { margin-bottom: 16px; word-wrap: break-word; }
.user { color: #007bff; }
.ai { color: #28a745; }
.ai-content { margin-top: 4px; white-space: pre-wrap; }
#chat-box pre {
background: #2d2d2d !important;
border-radius: 6px;
padding: 12px !important;
margin: 8px 0;
overflow-x: auto;
font-size: 14px;
}
#chat-box code {
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
font-size: 13px;
}
/* 提示:按 Ctrl+Enter 发送 */
.hint {
font-size: 12px;
color: #666;
margin-top: 4px;
margin-left: 8px;
}
</style>
</head>
<body>
<h1>🤖 多模型 AI 助手</h1>
<div id="chat-box"></div>
<div class="controls">
<select id="model-select">
<option value="tongyi">通义千问</option>
<option value="deepseek">DeepSeek Chat</option>
</select>
<textarea id="user-input" placeholder="请输入你的问题(支持多行,如 Python 代码)。按 Ctrl+Enter直接发送..."></textarea>
<button id="send-btn">发送</button>
</div>
<div class="hint">💡 提示:按 <kbd>Ctrl+Enter</kbd>(Windows/Linux)或 <kbd>Cmd+Enter</kbd>(Mac)快速发送</div>
<!-- 第三方库 -->
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.0.5/purify.min.js"></script>
<script>
marked.setOptions({
highlight: function(code, lang) {
const language = hljs.getLanguage(lang) ? lang : 'plaintext';
return hljs.highlight(code, { language }).value;
},
langPrefix: 'hljs language-'
});
const chatBox = document.getElementById('chat-box');
const modelSelect = document.getElementById('model-select');
const input = document.getElementById('user-input');
const sendBtn = document.getElementById('send-btn');
// 自动调整 textarea 高度
input.addEventListener('input', function() {
this.style.height = 'auto';
this.style.height = Math.min(this.scrollHeight, 200) + 'px';
});
async function sendMessage() {
const msg = input.value.trim();
if (!msg) return;
const model = modelSelect.value;
// 显示用户消息(保留原始格式)
const userDiv = document.createElement('div');
userDiv.className = 'message user';
// 使用 <pre> 保留换行和空格,但用 DOMPurify 防 XSS
userDiv.innerHTML = `<strong>你:</strong><pre style="margin:8px 0;background:#f0f8ff;padding:10px;border-radius:4px;overflow:auto;">${DOMPurify.sanitize(msg)}</pre>`;
chatBox.appendChild(userDiv);
chatBox.scrollTop = chatBox.scrollHeight;
input.value = '';
input.style.height = 'auto'; // 重置高度
sendBtn.disabled = true;
sendBtn.textContent = '发送中...';
// 显示"思考中"
const thinkingDiv = document.createElement('div');
thinkingDiv.className = 'message ai';
thinkingDiv.innerHTML = '<strong>AI:</strong> <i>正在思考...</i>';
chatBox.appendChild(thinkingDiv);
chatBox.scrollTop = chatBox.scrollHeight;
try {
const res = await fetch('http://localhost:8000/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message: msg, model: model })
});
const data = await res.json();
if (res.ok) {
const cleanHtml = DOMPurify.sanitize(marked.parse(data.reply));
thinkingDiv.innerHTML = `<strong>AI:</strong> ${cleanHtml}`;
} else {
thinkingDiv.innerHTML = `<strong>AI:</strong> ❌ 出错了:${data.detail || '未知错误'}`;
}
} catch (err) {
thinkingDiv.innerHTML = `<strong>AI:</strong> ⚠️ 无法连接到后端,请确保它正在运行。`;
} finally {
sendBtn.disabled = false;
sendBtn.textContent = '发送';
}
}
sendBtn.addEventListener('click', sendMessage);
// 支持快捷键发送:Ctrl+Enter / Cmd+Enter
input.addEventListener('keydown', (e) => {
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
e.preventDefault();
sendMessage();
}
});
</script>
</body>
</html>
提示,可以把 marked.min.js、highlight.min.js、purify.min.js 下载到本地 static/ 目录,改用相对路径。
其中,CDN (Content Delivery Network,内容分发网络)加载 marked 和 highlight.js 用于前端的交互输出格式。让 AI 返回的 代码块、Markdown 格式 能正确显示,并支持 Python 等语言的语法高亮!若缺乏格式输出python代码时就不好用了。
引入purify.min.js,增强安全:purify.min.js 是 DOMPurify 库的压缩版,它的核心作用是: 安全地清理(Sanitize)HTML 字符串,防止 XSS(跨站脚本攻击)。
三、运行步骤
1.启动后端服务
在cmd中(下行若在Windows PowerShell中运行,去掉 /d):
cd /d D:/ai-assistant-multiple/backend
$env:DASHSCOPE_API_KEY="真实的DASHSCOPE_API_KEY "
$env:DEEPSEEK_API_KEY="真实的DEEPSEEK_API_KEY"
uvicorn main:app --port 8000
如果你嫌输入比较麻烦,可以使用批处理文件(.bat)
在D:\ai-assistant-multiple\backend目录中用记事本建立名称为start_backend.bat批处理文件,以后只需双击这个 .bat 文件就能运行后端,无需手动输入命令。内容如下:
bash
@echo off
cd /d "D:\ai-assistant-multiple\backend"
:: Set API Key
set DASHSCOPE_API_KEY=真实的DASHSCOPE_API_KEY
set DEEPSEEK_API_KEY=真实的DEEPSEEK_API_KEY
echo Starting AI assistant backend...
echo Visit http://localhost:8000 to check status
echo Open frontend/index.html in your browser
echo.
uvicorn main:app --port 8000
pause
注意,在使用时不要关闭后端服务。
2.打开前端
直接双击 frontend/index.html 用浏览器打开
3.聊天
现在可以在前端网页中开始聊天!
可以在同一个界面中体验不同 AI 模型的回答风格和能力了!
输入框改用多行文本域(<textarea>),不仅能输入多行内容,还能保留换行、缩进等格式,特别适合粘贴 Python 代码、JSON、Markdown 等结构化文本。支持 Enter 发送(避免换行冲突,需按 Ctrl+Enter发送)
用户点击"发送" → 立刻看到"正在思考...",让用户在等待期间知道系统在工作,不会误以为卡死。
未来可以继续扩展高级功能,如:
· 保存聊天历史
· 部署到公网,7x24 小时可用,分享链接给任何人