程序员文章、书籍推荐和程序员创业信息与资源分享平台

网站首页 > 技术文章 正文

python入门-day30:项目优化与总结

hfteth 2025-04-26 18:28:40 技术文章 3 ℃

我们将优化之前的 Flask 聊天机器人,添加记忆功能,并总结 30 天学习成果,最后提出下一步建议。


优化聊天机器人 - 添加记忆功能

为了让聊天机器人更智能,我们将改进上下文管理,添加长期记忆功能,使其能够记住用户之前的对话内容,并基于完整的对话历史生成更连贯的回答。

优化后的代码 (app.py):

python

from flask import Flask, render_template, request, jsonify
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch

app = Flask(__name__)

# 全局变量存储模型和对话历史
tokenizer = None
model = None
device = None
chat_history = []  # 存储完整的对话历史

def load_qwen_model():
    global tokenizer, model, device
    try:
        model_name = "Qwen/Qwen-1.8B-Chat"
        print(f"正在加载模型 {model_name}...")
        
        tokenizer = AutoTokenizer.from_pretrained(model_name)
        model = AutoModelForCausalLM.from_pretrained(model_name)
        
        device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
        model = model.to(device)
        
        print("模型加载成功!")
        return True
    except Exception as e:
        print(f"加载模型时出错: {str(e)}")
        return False

def generate_response(user_input, max_length=100):
    try:
        # 构建完整的对话历史作为上下文
        history_text = "".join([f"用户: {entry['user']}\n机器人: {entry['bot']}\n" 
                              for entry in chat_history])  # 使用全部历史
        full_input = history_text + f"用户: {user_input}\n机器人: "
        
        # 编码输入
        inputs = tokenizer(full_input, return_tensors="pt").to(device)
        
        # 检查输入长度,避免超过模型限制
        if len(inputs["input_ids"][0]) > 1024:  # 假设模型最大上下文为1024 tokens
            # 截取最近的部分
            truncated_history = "".join([f"用户: {entry['user']}\n机器人: {entry['bot']}\n" 
                                      for entry in chat_history[-5:]])  # 保留最后5轮
            full_input = truncated_history + f"用户: {user_input}\n机器人: "
            inputs = tokenizer(full_input, return_tensors="pt").to(device)
        
        # 生成回答
        outputs = model.generate(
            inputs["input_ids"],
            max_length=max_length + len(inputs["input_ids"][0]),
            num_return_sequences=1,
            temperature=0.7,
            top_p=0.9,
            do_sample=True
        )
        
        # 解码并提取回答部分
        response = tokenizer.decode(outputs[0], skip_special_tokens=True)
        bot_response = response[len(full_input):].strip()
        return bot_response
    
    except Exception as e:
        return f"生成回答时出错: {str(e)}"

@app.route('/')
def index():
    return render_template('index.html', chat_history=chat_history)

@app.route('/chat', methods=['POST'])
def chat():
    user_input = request.form.get('user_input', '').strip()
    
    if not user_input:
        return jsonify({'error': '请输入内容!'})
    
    # 生成回答
    bot_response = generate_response(user_input)
    
    # 更新对话历史
    chat_history.append({'user': user_input, 'bot': bot_response})
    
    return jsonify({'bot_response': bot_response, 'chat_history': chat_history})

# 添加清理历史的功能
@app.route('/clear', methods=['POST'])
def clear_history():
    global chat_history
    chat_history = []
    return jsonify({'message': '对话历史已清除', 'chat_history': chat_history})

if __name__ == "__main__":
    if load_qwen_model():
        app.run(debug=True, host='0.0.0.0', port=5000)
    else:
        print("模型加载失败,无法启动服务")

更新后的 HTML 模板 (templates/index.html):

在之前的 HTML 基础上,添加清理历史按钮:

html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>千问聊天机器人</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        .chat-container { max-width: 800px; margin: auto; }
        .chat-history { border: 1px solid #ccc; padding: 10px; height: 400px; overflow-y: auto; }
        .message { margin: 5px 0; }
        .user { color: blue; }
        .bot { color: green; }
        .input-area { margin-top: 10px; }
        .button-area { margin-top: 10px; }
    </style>
</head>
<body>
    <div class="chat-container">
        <h2>千问聊天机器人</h2>
        <div class="chat-history" id="chatHistory">
            {% for entry in chat_history %}
                <div class="message user">你: {{ entry.user }}</div>
                <div class="message bot">机器人: {{ entry.bot }}</div>
            {% endfor %}
        </div>
        <div class="input-area">
            <textarea id="userInput" rows="3" cols="50" placeholder="输入你的问题..."></textarea><br>
            <button onclick="sendMessage()">发送</button>
        </div>
        <div class="button-area">
            <button onclick="clearHistory()">清除历史</button>
        </div>
    </div>

    <script>
        function sendMessage() {
            const userInput = document.getElementById('userInput').value;
            if (!userInput.trim()) {
                alert('请输入内容!');
                return;
            }

            fetch('/chat', {
                method: 'POST',
                headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                body: 'user_input=' + encodeURIComponent(userInput)
            })
            .then(response => response.json())
            .then(data => {
                if (data.error) {
                    alert(data.error);
                    return;
                }
                updateChatHistory(data.chat_history);
                document.getElementById('userInput').value = '';
            })
            .catch(error => console.error('Error:', error));
        }

        function clearHistory() {
            fetch('/clear', {
                method: 'POST',
                headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
            })
            .then(response => response.json())
            .then(data => updateChatHistory(data.chat_history))
            .catch(error => console.error('Error:', error));
        }

        function updateChatHistory(history) {
            const chatHistory = document.getElementById('chatHistory');
            chatHistory.innerHTML = '';
            history.forEach(entry => {
                chatHistory.innerHTML += `<div class="message user">你: ${entry.user}</div>`;
                chatHistory.innerHTML += `<div class="message bot">机器人: ${entry.bot}</div>`;
            });
            chatHistory.scrollTop = chatHistory.scrollHeight;
        }

        document.getElementById('userInput').addEventListener('keypress', function(e) {
            if (e.key === 'Enter' && !e.shiftKey) {
                e.preventDefault();
                sendMessage();
            }
        });
    </script>
</body>
</html>

优化亮点:

  1. 记忆功能:
  2. 默认使用完整的对话历史作为上下文。
  3. 当输入超过模型最大上下文长度(假设1024 tokens)时,自动截取最近5轮对话。
  4. 历史清理:
  5. 新增 /clear 路由和“清除历史”按钮,允许用户重置对话。
  6. 用户体验:
  7. 保留了之前的实时更新和自动滚动功能。



总结 30 天学习成果

经过 30 天的学习,你从零开始逐步掌握了基于大模型的聊天机器人开发,以下是主要成果:

  1. 基础知识:
  2. 熟悉了 Python 编程基础和 transformers 库的使用。
  3. 理解了大模型(如千问)的加载、分词和生成逻辑。
  4. 项目开发:
  5. Day 28-29: 完成了命令行版聊天机器人,支持基本的输入输出和错误处理。
  6. Day 29: 升级为 Flask Web 应用,实现了网页访问和连续问答显示。
  7. Day 30: 添加了记忆功能和历史清理功能,提升了机器人实用性。
  8. 技能提升:
  9. 掌握了前后端交互(Flask + HTML/JavaScript)。
  10. 学会了处理大模型的上下文管理和优化。

下一步建议

你的学习旅程告一段落,但这只是起点!以下是下一步建议:

  1. 深入学习 Transformers:
  2. 研究注意力机制(Attention)和模型微调(Fine-tuning),尝试在特定领域(如客服、知识问答)优化千问模型。
  3. 阅读 Hugging Face 的官方文档或《Transformers: State-of-the-Art Natural Language Processing》一书。
  4. 模型部署:
  5. 使用 Docker 将聊天机器人打包,部署到云服务器(如 AWS、阿里云),实现公网访问。
  6. 探索模型量化(如使用 ONNX 或 TensorRT)以提升推理速度。
  7. 扩展功能:
  8. 添加多模态支持(如图片输入)。
  9. 实现用户身份管理,支持多用户独立对话历史。

运行与测试

  1. 确保依赖已安装:

bash

pip install flask transformers torch
  1. 运行 app.py,访问 http://localhost:5000。
  2. 测试记忆功能,例如:
  3. 输入:“我叫小明。”
  4. 再输入:“我叫什么名字?”(机器人应记住“小明”)

至此,第 30 天的学习任务完成,你的聊天机器人已具备基本智能和 Web 交互能力,30 天学习圆满收官!下一步,勇敢迈向更深入的 NLP 领域吧!

看看问答效果吧,简单模型就只能当玩具了!

Tags:

最近发表
标签列表