Python Flask项目方式接入阿里云通义AI大模型API 实现一个简单的AI聊天Web项目(流式传输+多轮对话+会话记录+代码高亮)----- noob学生

07-21 1278阅读

效果图

Python Flask项目方式接入阿里云通义AI大模型API 实现一个简单的AI聊天Web项目(流式传输+多轮对话+会话记录+代码高亮)----- noob学生

Python Flask项目方式接入阿里云通义AI大模型API 实现一个简单的AI聊天Web项目(流式传输+多轮对话+会话记录+代码高亮)----- noob学生数据库记录:

Python Flask项目方式接入阿里云通义AI大模型API 实现一个简单的AI聊天Web项目(流式传输+多轮对话+会话记录+代码高亮)----- noob学生

流式输出就不展示了

准备工作

 前往阿里云大模型服务平台使用自己账号开通大模型服务平台百炼 

地址:大模型服务平台_通义大模型_自然语言处理_达摩院-阿里云 (aliyun.com)

1.进入自己的控制台--模型广场--通义千问--找到自己要使用的模型  我这里使用通义千问Max

Python Flask项目方式接入阿里云通义AI大模型API 实现一个简单的AI聊天Web项目(流式传输+多轮对话+会话记录+代码高亮)----- noob学生

一般是有免费额度可以使用的   后期可以自己买额度 很便宜 

然后到我的应用   创建好自己的应用  选择相应的模型 这时我们就哟APIkey和 自己的appid了  

2.在自己电脑安装好Redis (用于存储聊天缓存) 如果使用服务器就在服务器安装好并运行就行

3.安装好mysql数据库  推荐使用5.7版本

实施

        创建一个Python Flask项目并创建好虚拟环境解释器使用python3.8  项目结构如下图所示
Python Flask项目方式接入阿里云通义AI大模型API 实现一个简单的AI聊天Web项目(流式传输+多轮对话+会话记录+代码高亮)----- noob学生

key.py及其下方文件为我个人测试部署时使用  可以忽略  

app.py

运行项目的文件 也就是后端  

# -*- coding: utf-8 -*-
from flask import Flask, request, jsonify, Response, render_template, session
from flask_session import Session
from dashscope import Generation
from http import HTTPStatus
from flask_cors import CORS
import redis
import json
from database import save_conversation
app = Flask(__name__)
CORS(app)
#可以自己生成一串
app.secret_key = '3ddd8f0da764cb34850e1da48d03da24'  
# 配置 Redis
app.config['SESSION_TYPE'] = 'redis'
app.config['SESSION_REDIS'] = redis.Redis(host='localhost', port=6379)
app.config['SESSION_PERMANENT'] = False
app.config['SESSION_USE_SIGNER'] = True
Session(app)
@app.route('/ChatView')
def index():
    return render_template('index.html')
@app.route('/chat', methods=['POST'])
def chat():
    user_message = request.json.get('message', '')
    if not user_message:
        return jsonify({'error': '未提供消息'}), HTTPStatus.BAD_REQUEST
    # 从会话中加载消息或初始化一个新列表  可以自己自定义角色 修改'你是一个ai助手'内容就行
    messages = session.get('messages', [{'role': 'system', 'content': '你是一个ai助手'}])
    # 将用户的消息添加到列表中
    messages.append({'role': 'user', 'content': user_message})
    session['messages'] = messages  # 在添加用户消息后立即保存
    def generate():
        responses = Generation.call(
            "qwen-max",
            app_id='自己的应用id',
            api_key='自己的api key',
            messages=messages,
            result_format='message',
            stream=True,
            incremental_output=True
        )
        buffer = ''
        for response in responses:
            if response.status_code == HTTPStatus.OK:
                content = response.output.choices[0]['message']['content'].strip()
                print(content)
                buffer += content
                yield f"{content}"
            else:
                yield f"Error: {response.message}\n\n"
                break
    return Response(generate(), mimetype='text/event-stream')
@app.route('/update', methods=['POST'])
def update():
    try:
        data = request.json
        bot_message = data.get('bot_message', '')
        user_message = data.get('user_message', '')
        conversation_id = data.get('conversation_id', '')
        if not bot_message or not user_message or not conversation_id:
            app.logger.error('Missing bot_message, user_message, or conversation_id')
            return jsonify({'error': '未提供消息或对话ID'}), HTTPStatus.BAD_REQUEST
        messages = session.get('messages', [])
        messages.append({'role': 'assistant', 'content': bot_message})
        session['messages'] = messages
        save_conversation(conversation_id, user_message, bot_message)  # 保存对话
        return jsonify({'status': 'updated'}), HTTPStatus.OK
    except Exception as e:
        app.logger.error(f"Error in /update: {str(e)}")
        return jsonify({'error': str(e)}), HTTPStatus.INTERNAL_SERVER_ERROR
@app.route('/clear', methods=['POST'])
def clear():
    session.pop('messages', None)
    return jsonify({'status': 'cleared'}), HTTPStatus.OK
if __name__ == '__main__':
    app.run(debug=True)
    # 这里是自定义本地运行的端口号和ip地址
    # app.run(host='0.0.0.0', port=8080, debug=True)
​

database.py

指向数据库操作 保存记录的后端文件  需要修改为自己的数据库账户和密码  

# -*- coding: utf-8 -*-
import mysql.connector
def get_db_connection():
    return mysql.connector.connect(
        host="localhost",
        user="用户名",
        password="密码",
        database="数据库名"
    )
def save_conversation(conversation_id, user_message, bot_message):
    try:
        connection = get_db_connection()
        cursor = connection.cursor()
        # 检查对话是否存在
        conversation_query = "SELECT 1 FROM conversations WHERE conversation_id = %s"
        cursor.execute(conversation_query, (conversation_id,))
        conversation_exists = cursor.fetchone()
        
        if not conversation_exists:
            # 插入对话记录
            conversation_query = """
            INSERT INTO conversations (conversation_id)
            VALUES (%s)
            """
            cursor.execute(conversation_query, (conversation_id,))
        # 插入聊天记录
        chat_record_query = """
        INSERT INTO chat_records (conversation_id, user_message, bot_message)
        VALUES (%s, %s, %s)
        """
        cursor.execute(chat_record_query, (conversation_id, user_message, bot_message))
        connection.commit()
    except mysql.connector.Error as err:
        print(f"Error: {err}")
        raise
    finally:
        if connection.is_connected():
            cursor.close()
            connection.close()

数据库结构   sql语句   

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for chat_records
-- ----------------------------
DROP TABLE IF EXISTS `chat_records`;
CREATE TABLE `chat_records`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `conversation_id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
  `user_message` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL,
  `bot_message` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL,
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `conversation_id`(`conversation_id`) USING BTREE,
  CONSTRAINT `chat_records_ibfk_1` FOREIGN KEY (`conversation_id`) REFERENCES `conversations` (`conversation_id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for conversations
-- ----------------------------
DROP TABLE IF EXISTS `conversations`;
CREATE TABLE `conversations`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `conversation_id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
  `conversation_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `conversation_id`(`conversation_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;

前端页面和样式

/templates/index.html   

头像图片可以自定义  修改就行 



  
  
  顶顶顶顶顶
  
  
  
  
  
  


  
    Loading...
    

正在加载...请稍候

👻Tongyi`Ai By Lwh

 新对话 Python Flask项目方式接入阿里云通义AI大模型API 实现一个简单的AI聊天Web项目(流式传输+多轮对话+会话记录+代码高亮)----- noob学生 你好!有什么可以帮你的吗

/static/css/styles.css

.message {
  white-space: pre-wrap;
  word-wrap: break-word;
  max-width: 100%;
}
.bot-message-container {
  display: flex;
  align-items: flex-start;
}
.bot-message-container img {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  margin-right: 10px;
  border: rgb(125, 125, 242) 3px solid;
}
pre {
  border-bottom-left-radius: 0.5rem;
  border-bottom-right-radius: 0.5rem;
}
code {
  font-family: 'Consolas', 'Courier New', monospace;
  border-bottom-left-radius: 0.5rem;
  border-bottom-right-radius: 0.5rem;
  /* 隐藏默认的滚动条样式 */
  scrollbar-width: none;
  /* Firefox */
  -ms-overflow-style: none;
  /* IE and Edge */
}
code::-webkit-scrollbar {
  display: none;
  /* Chrome, Safari, and Opera */
}
/* for block of numbers */
.hljs-ln-numbers {
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  -khtml-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
  text-align: center;
  color: #ccc;
  border-right: 2px dashed #8f0feb;
  vertical-align: top;
}
.hljs-ln td {
  padding-right: 10px;
  padding-left: 10px;
}
@keyframes fadeIn {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}
.fade-in {
  animation: fadeIn 1s ease-out;
}
.copy {
  /* button */
  --button-bg: #353434;
  --button-hover-bg: #464646;
  --button-text-color: #CCCCCC;
  --button-hover-text-color: #8bb9fe;
  --button-border-radius: 10px;
  --button-diameter: 36px;
  --button-outline-width: 1px;
  --button-outline-color: rgb(141, 141, 141);
  /* tooltip */
  --tooltip-bg: #171212;
  --toolptip-border-radius: 4px;
  --tooltip-font-size:8px;
  --tooltip-transition-duration: 0.3s;
  --tootip-text-color: rgb(255, 255, 255);
  --tooltip-padding-x: 5px;
  --tooltip-padding-y: 5px;
  --tooltip-offset: 8px;
  font-weight: bold;
  font-family: 'YouYuan', sans-serif;
}
.copy {
  box-sizing: border-box;
  width: var(--button-diameter);
  height: var(--button-diameter);
  border-radius: var(--button-border-radius);
  background-color: var(--button-bg);
  color: var(--button-text-color);
  border: none;
  cursor: pointer;
  position: relative;
  outline: none;
}
.tooltip {
  position: absolute;
  opacity: 0;
  visibility: 0;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  white-space: nowrap;
  font: var(--tooltip-font-size) var(--tooltip-font-family);
  color: var(--tootip-text-color);
  background: var(--tooltip-bg);
  padding: var(--tooltip-padding-y) var(--tooltip-padding-x);
  border-radius: var(--toolptip-border-radius);
  pointer-events: none;
  transition: all var(--tooltip-transition-duration) cubic-bezier(0.68, -0.55, 0.265, 1.55);
}
.tooltip::before {
  content: attr(data-text-initial);
}
.tooltip::after {
  content: "";
  position: absolute;
  bottom: calc(var(--tooltip-padding-y) / 2 * -1);
  width: var(--tooltip-padding-y);
  height: var(--tooltip-padding-y);
  background: inherit;
  left: 50%;
  transform: translateX(-50%) rotate(45deg);
  z-index: -999;
  pointer-events: none;
}
.copy svg {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}
.checkmark {
  display: none;
}
/* actions */
.copy:hover .tooltip,
.copy:focus:not(:focus-visible) .tooltip {
  opacity: 1;
  visibility: visible;
  top: calc((100% + var(--tooltip-offset)) * -1);
}
.copy:focus:not(:focus-visible) .tooltip::before {
  content: attr(data-text-end);
}
.copy:focus:not(:focus-visible) .clipboard {
  display: none;
}
.copy:focus:not(:focus-visible) .checkmark {
  display: block;
}
.copy:hover,
.copy:focus {
  background-color: var(--button-hover-bg);
}
.copy:active {
  outline: var(--button-outline-width) solid var(--button-outline-color);
}
.copy:hover svg {
  color: var(--button-hover-text-color);
}
@media screen and (max-width: 600px) {
  pre {
    white-space: pre-wrap;
    /* 换行 */
    word-wrap: break-word;
    /* 防止超出屏幕宽度 */
  }
  .message.bot {
    max-width: 100%;
    /* 确保在小屏幕设备上不超出屏幕宽度 */
  }
}
/* Loader styles */
.loader {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: white;
  z-index: 9999;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}
.loader-text {
  font-size: 24px;
  color: rgb(0, 0, 0);
  margin-bottom: 20px;
  align-self: center;
}
.loader-bar {
  width: 10%;
  height: 10px;
  border-radius: 5px;
  background-color: rgb(0, 0, 0);
  animation: loader-bar-animation 2s ease-in-out infinite;
}
@keyframes loader-bar-animation {
  0% {
    /* transform: translateX(-100%) rotate(270deg); */
    transform: translateX(-100%);
  }
  50% {
    /* transform: translateX(100%) rotate(-90deg); */
    transform: translateX(100%);
  }
  100% {
    /* transform: translateX(-100%) rotate(270deg); */
    transform: translateX(-100%);
  }
}

/static/js/lwhapp.js

这部分很重要!!!

可以修改每次对话可以对话几条  建议20以内

document.addEventListener("DOMContentLoaded", function() {
  
    hljs.highlightAll();
    hljs.initLineNumbersOnLoad();
  
    const chatBox = document.getElementById("chat-box");
    const userInput = document.getElementById("user-input");
    const maxMessages = 10; // 定义最大消息数
    let messageCount = 0;
  
    userInput.addEventListener("keydown", function (event) {
      if (event.keyCode === 13 && !event.shiftKey) {
        event.preventDefault();
        sendMessage();
      }
    });
  
    document.getElementById("sendButton").addEventListener("click", sendMessage);
    document.querySelector("button[onclick='clearMessages()']").addEventListener("click", clearMessages);
  
    function sendMessage() {
      if (messageCount >= maxMessages) {
        disableInput();
        return;
      }
  
      const message = userInput.value;
      if (message.trim() === "") return;
  
      addMessage("user", message, false);
      const conversationId = localStorage.getItem("conversation_id") || generateConversationId();
      saveMessage("user", message);
      userInput.value = "";
      messageCount++;
  
      if (messageCount >= maxMessages) {
        disableInput();
      }
  
      fetch("/chat", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({ message }),
      })
        .then((response) => {
          if (!response.ok) {
            throw new Error("网络回复未完成");
          }
          const reader = response.body.getReader();
          const decoder = new TextDecoder();
          let buffer = "";
          let botMessageDiv = addMessage("bot", "", true); // 用于显示机器人的回复
  
          function readStream() {
            reader.read().then(({ done, value }) => {
              if (done) {
                const botResponse = buffer.trim();
                saveMessage("bot", botResponse);
                // 向后端发送完整的回复以更新会话历史
                fetch("/update", {
                  method: "POST",
                  headers: {
                    "Content-Type": "application/json",
                  },
                  body: JSON.stringify({ conversation_id: conversationId, user_message: message, bot_message: botResponse }),
                }).then(response => {
                  if (!response.ok) {
                    console.error('Error updating conversation:', response.statusText);
                  }
                }).catch(error => {
                  console.error('Fetch error:', error);
                });
                return;
              }
  
              const text = decoder.decode(value, { stream: true }).trim();
              buffer += text;
              addTypingEffect(botMessageDiv, buffer);
              applyStyles(botMessageDiv);
              readStream();
            });
          }
  
          readStream();
        })
        .catch((error) => {
          console.error("Error:", error);
        });
    }
  
    function generateConversationId() {
      const conversationId = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
        var r = Math.random() * 16 | 0,
        v = c == 'x' ? r : (r & 0x3 | 0x8);
        return v.toString(16);
      });
      localStorage.setItem("conversation_id", conversationId);
      return conversationId;
    }
  
    function addMessage(role, content, isTypingEffect = false) {
      let messageDiv;
      if (role === "bot") {
        const containerDiv = document.createElement("div");
        containerDiv.classList.add("bot-message-container", "fade-in");
  
        const avatar = document.createElement("img");
        avatar.src = "static/images/Lwh.jpeg";
        avatar.alt = "Lwh";
        containerDiv.appendChild(avatar);
  
        messageDiv = document.createElement("div");
        messageDiv.classList.add("message", role, "font-medium", "p-3", "rounded-lg", "max-w-lg", "text-sm", "self-start", "bg-green-500", "dark:bg-zinc-500", "text-white");
        containerDiv.appendChild(messageDiv);
  
        chatBox.appendChild(containerDiv);
      } else {
        messageDiv = document.createElement("div");
        messageDiv.classList.add("message", role, "p-3", "rounded-lg", "max-w-lg", "text-sm", "self-end", "bg-blue-500", "text-white");
        chatBox.appendChild(messageDiv);
      }
  
      if (isTypingEffect) {
        return messageDiv;
      }
      const codeRegex = /```([\s\S]*?)```/g;
      if (codeRegex.test(content)) {
        const parts = content.split(codeRegex);
        parts.forEach((part, index) => {
          if (index % 2 === 0) {
            const textNode = document.createTextNode(part);
            messageDiv.appendChild(textNode);
          } else {
            const pre = document.createElement("pre");
            const code = document.createElement("code");
            const lang = part.split("\n")[0].trim();
            const codeContent = part.split("\n").slice(1).join("\n");
            const codeHeader = document.createElement("div");
            codeHeader.classList.add(
              "flex",
              "justify-between",
              "items-center",
              "bg-gray-700",
              "text-white",
              "font-mono",
              "px-2",
              "py-1",
              "text-base",
              "rounded-t-lg"
            );
            codeHeader.innerHTML = `${lang}
              
VPS购买请点击我

文章版权声明:除非注明,否则均为主机测评原创文章,转载或复制请以超链接形式并注明出处。

目录[+]