You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 01:42:01