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

如何在Flutter应用中实现Python版GPT-3聊天交互功能

GPT-3聊天模块实现方案

临近项目截止日期优先选Dart直连接入Flutter的方案,无需额外部署后端,改完前端代码即可跑通全流程,耗时最短。如果后续需要做密钥隔离、多端复用,再考虑Python搭建API的方案。


方案1:Dart直连GPT-3(推荐,最快落地)

前置准备

  • 拿到OpenAI平台的API_KEY
  • 在Flutter项目的pubspec.yaml中添加网络请求依赖:
dependencies:
  flutter:
    sdk: flutter
  http: ^1.1.0
  • 给Android项目加网络权限:在android/app/src/main/AndroidManifest.xml的<manifest>标签下加一行:
<uses-permission android:name="android.permission.INTERNET" />

代码修改

直接替换原有homepage的代码即可,核心逻辑:

  • 维护generative_conversation字符串存储完整会话上下文,初始值预置AI开场语
  • 用户点击发送后,先把用户消息追加到会话串、添加到消息列表
  • 调用GPT-3接口拿到回复后,把AI消息追加到会话串、添加到消息列表,自动滚动到底部
  • 加了简单的加载状态,请求过程中禁用发送按钮避免重复提交
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'messageModel.dart';

class homepage extends StatefulWidget {
  const homepage({Key? key}) : super(key: key);

  @override
  State<homepage> createState() => _homepageState();
}

class _homepageState extends State<homepage> {
  final ScrollController _scrollController = ScrollController();
  final TextEditingController myController = TextEditingController();
  List<ChatMessage> messages = [];
  // 替换成你自己的OpenAI API Key
  final String openAiApiKey = "YOUR_API_KEY_HERE";
  // 会话上下文字符串,初始预置Carl Rogers的开场
  String generativeConversation = "T:Hello, nice to see you today.\nP:";
  bool isLoading = false;

  @override
  void initState() {
    super.initState();
    // 初始化时添加开场消息
    messages.add(const ChatMessage(
      messageContent: "Hello, nice to see you today.",
      messageType: "receiver"
    ));
  }

  // 调用GPT-3接口拿回复
  Future<String> getGptResponse() async {
    final response = await http.post(
      Uri.parse("https://api.openai.com/v1/completions"),
      headers: {
        "Content-Type": "application/json",
        "Authorization": "Bearer $openAiApiKey"
      },
      body: jsonEncode({
        "model": "text-davinci-002",
        "prompt": generativeConversation,
        "max_tokens": 100,
        "temperature": 0.8,
        "stop": "P:"
      })
    );

    if (response.statusCode == 200) {
      final resData = jsonDecode(response.body);
      return resData["choices"][0]["text"].toString().trim();
    } else {
      throw Exception("接口请求失败: ${response.body}");
    }
  }

  // 发送消息逻辑
  void handleSend() async {
    final inputText = myController.text.trim();
    if (inputText.isEmpty || isLoading) return;

    setState(() {
      // 添加用户消息
      messages.add(ChatMessage(
        messageContent: inputText,
        messageType: "sender"
      ));
      isLoading = true;
    });
    myController.clear();

    // 先滚动到底部
    Future.delayed(const Duration(milliseconds: 100), () {
      _scrollController.animateTo(
        _scrollController.position.maxScrollExtent,
        duration: const Duration(milliseconds: 300),
        curve: Curves.easeOut
      );
    });

    try {
      // 把用户消息追加到会话串
      generativeConversation += "$inputText\nT:";
      // 拿AI回复
      final aiReply = await getGptResponse();
      // 把AI回复追加到会话串,准备下一次请求
      generativeConversation += "$aiReply\nP:";

      setState(() {
        messages.add(ChatMessage(
          messageContent: aiReply,
          messageType: "receiver"
        ));
        isLoading = false;
      });

      // 再次滚动到底部
      Future.delayed(const Duration(milliseconds: 100), () {
        _scrollController.animateTo(
          _scrollController.position.maxScrollExtent,
          duration: const Duration(milliseconds: 300),
          curve: Curves.easeOut
        );
      });
    } catch (e) {
      setState(() {
        isLoading = false;
        messages.add(const ChatMessage(
          messageContent: "抱歉,当前响应出错,请稍后重试",
          messageType: "receiver"
        ));
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Scaffold(
        appBar: AppBar(
            toolbarHeight: 100,
            elevation: 0,
            automaticallyImplyLeading: false,
            backgroundColor: const Color.fromARGB(255, 62, 238, 176),
            flexibleSpace: SafeArea(
              child: Container(
                padding: const EdgeInsets.only(right: 16),
                child: Row(
                  children: <Widget>[
                    const SizedBox(width: 10),
                    const CircleAvatar(
                      backgroundImage: NetworkImage(
                          "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQtutzqepv7vSde5mQLzT00fWEbynYBq70VaQ&usqp=CAU"),
                      maxRadius: 40,
                    ),
                    const SizedBox(width: 15),
                    Expanded(
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: const <Widget>[
                          Text(
                            "Carl Rogers",
                            style: TextStyle(
                                fontSize: 32, fontWeight: FontWeight.w600),
                          ),
                          SizedBox(height: 6),
                        ],
                      ),
                    ),
                  ],
                ),
              ),
            )),
        body: Stack(
          children: <Widget>[
            SizedBox(
              height: MediaQuery.of(context).size.height - 160,
              child: SingleChildScrollView(
                child: ListView.builder(
                  controller: _scrollController,
                  itemCount: messages.length,
                  shrinkWrap: true,
                  padding: const EdgeInsets.only(top: 10, bottom: 10),
                  itemBuilder: (context, index) {
                    return Container(
                      padding: const EdgeInsets.only(
                          left: 14, right: 14, top: 10, bottom: 10),
                      child: Align(
                        alignment: (messages[index].messageType == "receiver"
                            ? Alignment.topLeft
                            : Alignment.topRight),
                        child: Container(
                          decoration: BoxDecoration(
                            borderRadius: BorderRadius.circular(20),
                            color: (messages[index].messageType == "receiver"
                                ? Colors.grey.shade200
                                : const Color.fromARGB(255, 93, 232, 183)),
                          ),
                          padding: const EdgeInsets.all(16),
                          child: Text(
                            messages[index].messageContent,
                            style: const TextStyle(fontSize: 15),
                          ),
                        ),
                      ),
                    );
                  },
                ),
              ),
            ),
            Align(
              alignment: Alignment.bottomLeft,
              child: Container(
                padding: const EdgeInsets.only(left: 10, bottom: 10, top: 10),
                height: 60,
                width: double.infinity,
                color: Colors.white,
                child: Row(
                  children: <Widget>[
                    const SizedBox(width: 15),
                    Expanded(
                      child: TextField(
                        controller: myController,
                        enabled: !isLoading,
                        decoration: const InputDecoration(
                            hintText: "Write message...",
                            hintStyle: TextStyle(color: Colors.black54),
                            border: InputBorder.none),
                      ),
                    ),
                    const SizedBox(width: 15),
                    FloatingActionButton(
                      onPressed: isLoading ? null : handleSend,
                      child: isLoading 
                        ? const SizedBox(
                            width: 18,
                            height: 18,
                            child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white)
                          )
                        : const Icon(
                            Icons.send,
                            color: Colors.white,
                            size: 18,
                          ),
                      backgroundColor: const Color.fromARGB(255, 62, 238, 176),
                      elevation: 0,
                    ),
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

注:原Python代码中对generative_conversation做固定长度切片的逻辑存在bug,会丢失历史对话上下文,上面的代码修正为直接追加会话内容,保证上下文完整。如果是本地演示交作业,硬编码API_KEY可以正常跑,如果要上线正式版本,建议把API_KEY放到后端,避免泄露。


方案2:Python搭建API供前端调用

如果需要隔离API密钥,用Flask搭接口最快,步骤如下:

  1. 安装依赖:
pip install flask openai flask-cors
  1. 编写后端服务代码app.py:
from flask import Flask, request, jsonify
from flask_cors import CORS
import openai

app = Flask(__name__)
CORS(app) # 解决跨域问题
openai.api_key = "YOUR_API_KEY_HERE"

@app.route("/chat", methods=["POST"])
def chat():
    # 前端传完整的会话字符串即可,后端无需存会话状态
    prompt = request.json.get("prompt", "")
    response = openai.Completion.create(
        model="text-davinci-002",
        prompt=prompt,
        max_tokens=100,
        temperature=0.8,
        stop="P:"
    )
    reply = response["choices"][0]["text"].strip()
    return jsonify({"reply": reply})

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000, debug=True)
  1. 启动服务后,把方案1里的getGptResponse方法的请求地址改成你部署的服务地址(本地测试就是http://你电脑的局域网IP:5000/chat),去掉Authorization请求头,请求体只传prompt字段即可。
    这个方案需要额外占用时间部署服务、调试网络连通性,时间紧张的话不推荐。

内容的提问来源于stack exchange,提问作者John Jacob

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 09:39:43