AI聊天机器人Flask后端与React前端对接失败问题求助
Flask后端API与React前端对接失败问题
我开发了一款可正常运行的AI问答模型,采用Flask框架搭建后端API并通过ngrok提供公网访问地址,该API在Postman中测试完全正常,但无法与React前端成功对接。
后端代码(Flask)
from flask import Flask, request, jsonify from flask_cors import CORS import json import nltk import numpy as np import random import pickle from time import sleep from nltk.stem.lancaster import LancasterStemmer from sklearn.model_selection import train_test_split from sklearn.metrics import f1_score from keras.models import Sequential from keras.layers import Dense from keras.optimizers import Adam from pathlib import Path from pyngrok import ngrok stemmer = LancasterStemmer() # Load intents file with open("intents2.json") as file: data = json.load(file) app = Flask(__name__) CORS(app) # Enable CORS for all routes # Load preprocessed data or process it if not available try: with open("data.pickle", "rb") as f: words, labels, training, output = pickle.load(f) except: words = [] labels = [] docs_x = [] docs_y = [] for intent in data["intents"]: for pattern in intent["patterns"]: wrds = nltk.word_tokenize(pattern) words.extend(wrds) docs_x.append(wrds) docs_y.append(intent["tag"]) if intent["tag"] not in labels: labels.append(intent["tag"]) words = [stemmer.stem(w.lower()) for w in words if w != "?"] words = sorted(list(set(words))) labels = sorted(labels) training = [] output = [] out_empty = [0 for _ in range(len(labels))] for x, doc in enumerate(docs_x): bag = [] wrds = [stemmer.stem(w) for w in doc] for w in words: if w in wrds: bag.append(1) else: bag.append(0) output_row = out_empty[:] output_row[labels.index(docs_y[x])] = 1 training.append(bag) output.append(output_row) training = np.array(training) output = np.array(output) with open("data.pickle", "wb") as f: pickle.dump((words, labels, training, output), f) # Split the data into training and testing sets X_train, X_test, y_train, y_test = train_test_split(training, output, test_size=0.2) # Define the Keras model model = Sequential() model.add(Dense(20, input_shape=(len(X_train[0]),), activation='relu')) model.add(Dense(20, activation='relu')) model.add(Dense(20, activation='relu')) model.add(Dense(len(y_train[0]), activation='softmax')) # Compile the model model.compile(optimizer=Adam(learning_rate=0.01), loss='categorical_crossentropy', metrics=['accuracy']) # Train the model model.fit(X_train, y_train, epochs=200, batch_size=8, verbose=1) # Save the model model.save("model.h5") # Evaluate the model y_train_pred = model.predict(X_train) f1_train = f1_score(y_train.argmax(axis=1), y_train_pred.argmax(axis=1), average='weighted') print(f"F1 Score (Training): {f1_train:.4f}") test_loss, test_acc = model.evaluate(X_test, y_test) print(f"Test Loss: {test_loss:.4f}") print(f"Test Accuracy: {test_acc:.4f}") def bag_of_words(s, words): bag = [0 for _ in range(len(words))] s_words = nltk.word_tokenize(s) s_words = [stemmer.stem(word.lower()) for word in s_words] for se in s_words: for i, w in enumerate(words): if w == se: bag[i] = 1 return np.array(bag) @app.route('/chat', methods=['POST']) def chat(): user_input = request.form.get('message') if not user_input: return jsonify({"error": "No message provided"}), 400 results = model.predict(np.array([bag_of_words(user_input, words)]))[0] results_index = np.argmax(results) tag = labels[results_index] if results[results_index] > 0.8: for tg in data["intents"]: if tg['tag'] == tag: responses = tg['responses'] sleep(1) bot_response = random.choice(responses) else: bot_response = "I don't understand!" return jsonify({"response": bot_response}) if __name__ == '__main__': # Set up ngrok ngrok.set_auth_token("2i16Qv3WbGVeggfwzrzUGEZbEK5_3d5kArxvbSNbwB6kQsJZA") public_url = ngrok.connect(5000).public_url print(" * ngrok tunnel \"{}\" -> \"http://127.0.0.1:5000/\"".format(public_url)) # Run Flask app app.run(debug=True, use_reloader=False)
前端代码(React)
import { useState} from "react"; import bgimg from "./image.png"; import "./mainChat.css"; export default function Mainchat() { const [enteredQuestion, setEnteredQuestion] = useState([]); const [data, setData] = useState([]); const [Loading, setLoading] = useState(true); const fetchData = async () => { if (!enteredQuestion.length) return; const response = await fetch('https://36ea-197-32-165-58.ngrok-free.app/chat',{ method: 'POST', body: JSON.stringify({ message: enteredQuestion[0]}), headers: { "Content-Type": 'application/json', } }); console.log(JSON.stringify({ message: enteredQuestion[0]})) const result = await response.json(); console.log(result); setData(response.key); }; fetchData(); function updatingTheChat(event) { if (event.keyCode === 13) { const question = event.target.value; setEnteredQuestion((existingQuestions) => [ question, ...existingQuestions, ]); event.target.value = ""; } fetchData() } function reset() { setEnteredQuestion([]); setData([]); } return ( <div className="thechat"> <span className="online">ONLINE</span> <button type="button" className="btn btn-light Start" onClick={reset}> Start New chat + </button> <img src={bgimg} className="bgimg" alt="Background" /> <div className="answers d-flex flex-column-reverse justify-content-center"> {enteredQuestion.map((question, index) => ( <div key={index} className="d-flex flex-column mb-5"> <div className="question mb-3 text-end pe-5">{question}</div> <div className="bot ps-5">hello</div> </div> ))} </div> <input type="text" placeholder="Ask anything ..." className="typing" onKeyDown={updatingTheChat} /> </div> ); }
错误情况
前端请求返回400错误,提示内容为"No message provided"。
问题分析与解决方案
1. 后端参数获取方式不匹配
后端用request.form.get('message')获取表单格式参数,但前端发送的是JSON格式数据,导致无法正确获取message字段,返回400错误。
修改后端chat函数:
@app.route('/chat', methods=['POST']) def chat(): # 先判断请求是否为JSON格式 if not request.is_json: return jsonify({"error": "Request must be JSON"}), 400 # 从JSON中获取message user_input = request.get_json().get('message') if not user_input: return jsonify({"error": "No message provided"}), 400 results = model.predict(np.array([bag_of_words(user_input, words)]))[0] results_index = np.argmax(results) tag = labels[results_index] if results[results_index] > 0.8: for tg in data["intents"]: if tg['tag'] == tag: responses = tg['responses'] sleep(1) bot_response = random.choice(responses) else: bot_response = "I don't understand!" return jsonify({"response": bot_response})
2. 前端请求逻辑问题
- 组件加载时立即调用
fetchData,此时enteredQuestion为空,无意义请求 - 每次按键都调用
fetchData,只有回车提交问题时才需要请求 setData(response.key)错误,应该存储后端返回的response字段
修改前端代码:
import { useState, useEffect} from "react"; import bgimg from "./image.png"; import "./mainChat.css"; export default function Mainchat() { const [enteredQuestion, setEnteredQuestion] = useState([]); const [responses, setResponses] = useState([]); const [isLoading, setIsLoading] = useState(false); const fetchData = async (question) => { setIsLoading(true); try { const response = await fetch('https://36ea-197-32-165-58.ngrok-free.app/chat',{ method: 'POST', body: JSON.stringify({ message: question }), headers: { "Content-Type": 'application/json', } }); const result = await response.json(); if (response.ok) { setResponses(prev => [result.response, ...prev]); } else { console.error('请求错误:', result.error); } } catch (error) { console.error('网络错误:', error); } finally { setIsLoading(false); } }; // 当有新问题时自动触发请求 useEffect(() => { if (enteredQuestion.length > 0 && responses.length < enteredQuestion.length) { fetchData(enteredQuestion[0]); } }, [enteredQuestion, responses]); function updatingTheChat(event) { if (event.keyCode === 13) { const question = event.target.value.trim(); if (!question) return; setEnteredQuestion(prev => [question, ...prev]); event.target.value = ""; } } function reset() { setEnteredQuestion([]); setResponses([]); } return ( <div className="thechat"> <span className="online">ONLINE</span> <button type="button" className="btn btn-light Start" onClick={reset}> Start New chat + </button> <img src={bgimg} className="bgimg" alt="Background" /> <div className="answers d-flex flex-column-reverse justify-content-center"> {enteredQuestion.map((question, index) => ( <div key={index} className="d-flex flex-column mb-5"> <div className="question mb-3 text-end pe-5">{question}</div> <div className="bot ps-5"> {isLoading && index === 0 ? "正在回复..." : responses[index] || "hello"} </div> </div> ))} </div> <input type="text" placeholder="Ask anything ..." className="typing" onKeyDown={updatingTheChat} disabled={isLoading} /> </div> ); }
内容的提问来源于stack exchange,提问作者Mohammed Wael
相关产品推荐
相关产品推荐

