后端与前端怎么交互(LangChain入门详解(第七章):搭建带前端页面的简易 AI 聊天应用)

后端与前端怎么交互(LangChain入门详解(第七章):搭建带前端页面的简易 AI 聊天应用)
LangChain入门详解(第七章):搭建带前端页面的简易 AI 聊天应用

在前六章的内容中,我们依次学习了 LangChain 接入大模型 API、实现短期记忆、构建知识库、调用外部工具、开发 SQL Agent 以及提示词工程等核心能力。本章将把这些知识点落地,手把手教你搭建一个带前端页面 + Flask 后端的简易 AI 聊天应用,让你从 “纯代码调用” 进阶到 “可视化交互”,真正体验 LangChain 的工程化应用。

一、本章核心目标

我们将实现一个具备以下功能的聊天应用:

  1. 基于 Flask 搭建后端 API,封装 LangChain Agent 能力;
  2. 开发美观的前端聊天界面(Vue+TailwindCSS),支持消息发送 / 展示;
  3. 实现多模型切换、对话管理、深色模式等实用功能;
  4. 前后端分离通信,支持跨域请求,保证交互流畅性。

二、技术栈选型说明

后端技术栈

  • Flask:轻量级 Python Web 框架,快速搭建 API 服务,适合新手入门;
  • Flask-CORS:解决前后端跨域通信问题;
  • LangChain:核心框架,封装大模型调用和 Agent 能力;
  • LangGraph:提供对话记忆(InMemorySaver),支持多轮对话上下文;
  • LangChain-OpenAI:适配各类兼容 OpenAI 接口的大模型(通义千问、DeepSeek、Kimi 等)。

前端技术栈

  • 原生 HTML/CSS/JS:无复杂框架依赖,新手易理解;
  • Vue 3:轻量响应式框架,简化界面状态管理;
  • TailwindCSS:原子化 CSS 框架,快速实现美观的 UI;
  • Axios:处理前后端 HTTP 请求,简化异步通信。
chat-app/├── config.py          # 配置文件(API密钥、模型地址等)├── app.py             # Flask后端主文件├── page/              # 前端页面目录│   └── index.html     # 前端聊天界面└── requirements.txt   # 依赖清单

先来展示一下功能

后端与前端怎么交互(LangChain入门详解(第七章):搭建带前端页面的简易 AI 聊天应用)


页面展示

有了这个界面,我们就可以摆脱控制台的交互了。

下面粘出代码,其中config的代码可以使用之前的

requirements.txt,对比之前的 ,增加了flask的依赖

langchain-openai==1.0.3langchain-core==1.0.7langchain==1.0.8langchain-community==0.4.1langchain-anthropic==1.1.0matplotlib==3.8.4pypdf==4.1.0pymysql==1.1.1flask==3.0.3flask-cors==5.0.0

app.py

from flask import Flask, request, jsonify, render_template_string, send_from_directoryfrom flask_cors import CORSfrom langchain.agents import create_agentfrom langchain_openai import ChatOpenAIfrom config import API_KEY, BASE_URL, MODEL_NAMEfrom langgraph.checkpoint.memory import InMemorySaver  import osimport jsonapp = Flask(__name__)CORS(app)  # 启用跨域支持SYSTEM_PROMPT = """你是'小白的飞机'的智能助手,你的名字叫小飞机助手,你可以回答用户的问题"""# 全局模型名称变量CURRENT_MODEL_NAME = MODEL_NAMEmodel = ChatOpenAI(    model=CURRENT_MODEL_NAME,    openai_api_key=API_KEY,    openai_api_base=BASE_URL,    temperature=0.001    )agent = create_agent(    model=model,    checkpointer=InMemorySaver(),      system_prompt=SYSTEM_PROMPT,)@app.route('/')def index():    """提供主页"""    return send_from_directory('page', 'index.html')@app.route('/page/')def serve_page_files(filename):    """提供页面静态文件"""    return send_from_directory('page', filename)@app.route('/api/chat', methods=['POST'])def chat():    """聊天接口"""    try:        data = request.json        user_input = data.get('message', '').strip()        chat_id = data.get('chatId', '1')                if not user_input:            return jsonify({'error': '请输入有效内容'}), 400        inputs = {"messages": [{"role": "user", "content": user_input}]}        # 执行查询        response = agent.invoke(inputs, {"configurable": {"thread_id": chat_id}})                content = response["messages"][-1].content                return jsonify({            'success': True,            'response': content,            'chatId': chat_id        })    except Exception as e:        return jsonify({            'success': False,             'error': f'执行出错: {str(e)}'        }), 500@app.route('/api/models', methods=['GET'])def get_models():    """获取可用模型列表"""    try:        models = [            {'value': 'Qwen/Qwen3-32B', 'label': 'Qwen3-32B'},            {'value': 'Qwen/Qwen3-VL-30B-A3B-Instruct', 'label': 'Qwen3-A3B'},            {'value': 'moonshotai/Kimi-K2-Instruct-0905', 'label': 'Kimi-K2'}        ]        return jsonify({'models': models})    except Exception as e:        return jsonify({            'success': False,             'error': f'获取模型列表失败: {str(e)}'        }), 500@app.route('/api/model', methods=['GET'])def get_current_model():    """获取当前使用的模型"""    try:        return jsonify({            'success': True,            'model': CURRENT_MODEL_NAME        })    except Exception as e:        return jsonify({            'success': False,             'error': f'获取当前模型失败: {str(e)}'        }), 500@app.route('/api/model', methods=['POST'])def set_model():    """设置当前使用的模型"""    global CURRENT_MODEL_NAME, model, agent        try:        data = request.json        new_model = data.get('model', '').strip()                # 验证模型是否在可用列表中        available_models = [           {'value': 'Qwen/Qwen3-32B', 'label': 'Qwen3-32B'},            {'value': 'Qwen/Qwen3-VL-30B-A3B-Instruct', 'label': 'Qwen3-A3B'},            {'value': 'moonshotai/Kimi-K2-Instruct-0905', 'label': 'Kimi-K2'}        ]                model_values = [model['value'] for model in available_models]                if new_model in model_values:            CURRENT_MODEL_NAME = new_model                        # 更新模型实例            model = ChatOpenAI(                model=CURRENT_MODEL_NAME,                openai_api_key=API_KEY,                openai_api_base=BASE_URL,                temperature=0.001            )                        # 也需要更新agent,使用新的模型            agent = create_agent(                model=model,                checkpointer=InMemorySaver(),                  system_prompt=SYSTEM_PROMPT,            )                        return jsonify({                'success': True,                'model': CURRENT_MODEL_NAME,                'message': f'模型已成功更新为 {CURRENT_MODEL_NAME}'            })        else:            return jsonify({                'success': False,                'error': f'模型 {new_model} 不在可用模型列表中',                'currentModel': CURRENT_MODEL_NAME,                'availableModels': model_values            }), 400                except Exception as e:        return jsonify({            'success': False,             'error': f'设置模型失败: {str(e)}',            'currentModel': CURRENT_MODEL_NAME        }), 500if __name__ == '__main__':    # 获取端口,优先使用环境变量,否则使用默认值    port = int(os.environ.get('PORT', 5000))    app.run(host='0.0.0.0', port=port, debug=True)

页面,page/index.html

        AI Agent Demo 聊天界面    <!-- Tailwind CSS v3 -->    [xss_clean][xss_clean]    <!-- Vue 3 -->    [xss_clean][xss_clean]    <!-- Axios -->    [xss_clean][xss_clean]    [xss_clean]        tailwind.config = {            darkMode: 'class',            theme: {                extend: {                    colors: {                        primary: '#3B82F6',                        secondary: '#6B7280',                        background: '#F9FAFB',                        'background-dark': '#111827',                        card: '#FFFFFF',                        'card-dark': '#1F2937',                        border: '#E5E7EB',                        'border-dark': '#374151'                    },                    fontFamily: {                        sans: ['Inter', 'system-ui', 'sans-serif']                    }                }            }        }    [xss_clean]        
<!-- 顶部导航栏 -->

AI Agent

<!-- 主要内容区 -->
<!-- 左侧边栏 --> <!-- 右侧聊天区 -->
<!-- 聊天消息区 -->
{{ formatDate(msg.timestamp) }} {{ msg.sender === 'user' ? '你' : 'AI Assistant' }}

<!-- AI正在输入指示器 -->
AI Assistant 正在输入...
<!-- 输入区域 -->
[xss_clean] const { createApp, ref, reactive, computed, onMounted, nextTick, watch } = Vue; createApp({ setup() { // 响应式数据 const loading = ref(false); const showSettings = ref(false); const messageInput = ref(''); const isTyping = ref(false); const isSending = ref(false); const textareaRows = ref(1); // 设置选项 const selectedModel = ref('gpt-4'); const darkMode = ref(false); // 监听模型选择变化,自动调用后端API更新模型 watch(selectedModel, async (newModel) => { try { const response = await axios.post('http://localhost:5000/api/model', { model: newModel }); if (!response.data.success) { console.error('设置模型失败:', response.data.error); showToast('设置模型失败: ' + response.data.error); // 如果设置失败,恢复之前的模型选择 selectedModel.value = response.data.currentModel || selectedModel.value; } else { showToast('模型已更新为: ' + response.data.model); } } catch (error) { console.error('更新模型设置时出错:', error); showToast('更新模型设置失败'); // 出错时恢复之前的模型选择 selectedModel.value = localStorage.getItem('selectedModel') || 'gpt-4'; } }); // AI模型选项 const aiModels = ref([ ]); const selectedChat = ref({ id: 1, title: '当前对话', timestamp: new Date().toISOString(), lastMessage: '', messages: [] }); // 在组件挂载时加载模型列表和设置 onMounted(async () => { // 加载模型列表和当前模型设置 await loadModels(); // 从本地存储加载其他设置 const savedSettings = JSON.parse(localStorage.getItem('chatSettings') || '{}'); if (savedSettings.darkMode !== undefined) { darkMode.value = savedSettings.darkMode; if (darkMode.value) { document.documentElement.classList.add('dark'); } } }); // 方法 const createNewChat = () => { // 重置当前对话 selectedChat.value = { id: Date.now(), title: '当前对话', timestamp: new Date().toISOString(), lastMessage: '', messages: [] }; }; const formatDate = (dateString) => { const date = new Date(dateString); const now = new Date(); const diffTime = Math.abs(now - date); const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); if (diffDays === 1) return '今天'; if (diffDays === 2) return '昨天'; if (diffDays <= 7) return `${diffDays - 1}天前`; return date.toLocaleDateString('zh-CN', { month: 'numeric', day: 'numeric' }); }; const formatMessage = (content) => { return content.replace(/\n/g, '
'); }; const adjustTextareaHeight = () => { nextTick(() => { const textarea = document.querySelector('textarea'); if (textarea) { textarea.style.height = 'auto'; textarea.style.height = Math.min(textarea.scrollHeight, 150) + 'px'; if (textarea.scrollHeight > 150) { textarea.style.overflowY = 'auto'; } else { textarea.style.overflowY = 'hidden'; } textareaRows.value = Math.min(Math.max(textarea.scrollHeight / 20, 1), 8); } }); }; const scrollToBottom = () => { nextTick(() => { const chatContainer = document.getElementById('chat-messages'); if (chatContainer) { chatContainer.scrollTop = chatContainer.scrollHeight; } }); }; const handleEnterKey = (event) => { if (!event.shiftKey) { event.preventDefault(); sendMessage(); } }; const sendMessage = async () => { if (!messageInput.value.trim() || isSending.value) return; const userMessage = { id: Date.now(), sender: 'user', content: messageInput.value, timestamp: new Date().toISOString() }; // 添加用户消息到当前对话 selectedChat.value.messages.push(userMessage); selectedChat.value.lastMessage = messageInput.value.substring(0, 30) + (messageInput.value.length > 30 ? '...' : ''); // 清空输入框并重置高度 messageInput.value = ''; textareaRows.value = 1; adjustTextareaHeight(); // 滚动到底部 scrollToBottom(); // 显示打字指示器 isTyping.value = true; try { isSending.value = true; // 调用后端API获取AI响应 const response = await axios.post('http://localhost:5000/api/chat', { message: userMessage.content, chatId: selectedChat.value.id }); if (response.data.success) { const aiMessage = { id: Date.now(), sender: 'ai', content: response.data.response, timestamp: new Date().toISOString() }; // 添加AI消息 selectedChat.value.messages.push(aiMessage); selectedChat.value.lastMessage = response.data.response.substring(0, 30) + (response.data.response.length > 30 ? '...' : ''); // 滚动到底部 scrollToBottom(); } else { const errorMessage = { id: Date.now(), sender: 'ai', content: response.data.error || '抱歉,我在处理您的请求时遇到了一些问题。', timestamp: new Date().toISOString() }; selectedChat.value.messages.push(errorMessage); // 滚动到底部 scrollToBottom(); } } catch (error) { console.error('发送消息时出错:', error); const errorMessage = { id: Date.now(), sender: 'ai', content: '抱歉,网络连接出现问题。请检查网络后重试。', timestamp: new Date().toISOString() }; selectedChat.value.messages.push(errorMessage); // 滚动到底部 scrollToBottom(); } finally { isTyping.value = false; isSending.value = false; } }; const toggleDarkMode = () => { if (darkMode.value) { document.documentElement.classList.add('dark'); document.body.classList.add('bg-gray-900'); } else { document.documentElement.classList.remove('dark'); document.body.classList.remove('bg-gray-900'); } }; const showToast = (message) => { // 创建toast元素 let toast = document.querySelector('.toast-notification'); if (!toast) { toast = document.createElement('div'); toast.className = 'toast-notification fixed bottom-4 right-4 bg-gray-800 text-white px-4 py-2 rounded-md shadow-lg z-50'; document.body.appendChild(toast); } toast.textContent = message; toast.style.display = 'block'; // 3秒后隐藏toast setTimeout(() => { toast.style.display = 'none'; }, 3000); }; const loadModels = async () => { try { const response = await axios.get('http://localhost:5000/api/models'); if (response.data.success) { aiModels.value = response.data.models; // 获取当前模型设置 const currentModelResponse = await axios.get('http://localhost:5000/api/model'); if (currentModelResponse.data.success) { selectedModel.value = currentModelResponse.data.model; // 将当前模型设置保存到localStorage const settings = JSON.parse(localStorage.getItem('chatSettings') || '{}'); settings.selectedModel = selectedModel.value; localStorage.setItem('chatSettings', JSON.stringify(settings)); } } } catch (error) { console.error('加载模型列表失败:', error); // 使用默认模型列表 aiModels.value = [ {value: 'gpt-4', label: 'GPT-4'}, {value: 'gpt-3.5', label: 'GPT-3.5'}, {value: 'claude-3', label: 'Claude 3'}, {value: 'llama-3', label: 'Llama 3'}, {value: 'gemini-pro', label: 'Gemini Pro'} ]; } }; const saveSettings = async () => { // 保存设置到本地存储 localStorage.setItem('chatSettings', JSON.stringify({ selectedModel: selectedModel.value, darkMode: darkMode.value })); // 应用深色模式设置 toggleDarkMode(); // 显示保存成功提示 showSettings.value = false; showToast('设置已保存'); }; // 初始化设置 onMounted(async () => { // 从后端获取模型列表 try { const response = await axios.get('http://localhost:5000/api/models'); if (response.data.models) { aiModels.value = response.data.models; } } catch (error) { console.error('获取模型列表失败:', error); } const savedSettings = localStorage.getItem('chatSettings'); if (savedSettings) { const settings = JSON.parse(savedSettings); selectedModel.value = settings.selectedModel || 'gpt-4'; darkMode.value = settings.darkMode || false; } else { // 如果没有保存的设置,则从后端获取当前模型 try { const response = await axios.get('http://localhost:5000/api/model'); if (response.data.success) { selectedModel.value = response.data.model; } else { // 如果获取当前模型失败,则使用第一个可用模型作为默认值 if (aiModels.value && aiModels.value.length > 0) { selectedModel.value = aiModels.value[0].value; } else { selectedModel.value = 'gpt-4'; // 默认值 } } } catch (error) { console.error('获取当前模型失败:', error); // 如果获取当前模型失败,则使用第一个可用模型作为默认值 if (aiModels.value && aiModels.value.length > 0) { selectedModel.value = aiModels.value[0].value; } else { selectedModel.value = 'gpt-4'; // 默认值 } } } // 如果还没有设置模型(例如,模型列表为空或获取失败),则使用默认值 if (!selectedModel.value) { if (aiModels.value && aiModels.value.length > 0) { selectedModel.value = aiModels.value[0].value; } else { selectedModel.value = 'gpt-4'; // 默认值 } } // 应用深色模式设置 toggleDarkMode(); }); return { loading, showSettings, messageInput, isTyping, isSending, textareaRows, selectedModel, darkMode, aiModels, selectedChat, createNewChat, formatDate, formatMessage, adjustTextareaHeight, handleEnterKey, sendMessage, toggleDarkMode, saveSettings }; } }).mount('#app'); [xss_clean]

文章版权声明:除非注明,否则均为边学边练网络文章,版权归原作者所有

相关阅读