一、项目背景
在传统 AI 问答系统中,用户输入问题后需等待服务器返回完整结果才能显示,这种同步方式在遇到复杂问题或网络延迟时,会极大影响用户体验。而“流式调用”技术能将 AI 的思考过程与回答内容逐步展现,实现类似“边生成边展示”的效果,使交互更加自然、高效。
本项目基于 Flutter 实现一个支持多学科切换的 AI 问答助手,后端通过 /ai/deepseek-stream
接口提供流式响应能力。本文将聚焦该项目在流式调用上的技术设计与实现细节。
二、流式调用的设计目标
-
边接收边展示:用户无需等待完整响应,系统实时展示 AI 的“思考过程”与“最终回答”。
-
学科定制化问题上下文:支持“语文”“数学”“英语”等多个学科的上下文构造。
-
界面动态更新与用户可控性:支持展开/折叠 AI 的思考过程,提高可读性与可控性。
三、技术实现
1. 发起流式请求
final request = http.Request('POST', Uri.parse('http://localhost:8000/ai/deepseek-stream?content=${Uri.encodeComponent(content)}'));
request.headers['token'] = (await SharedPreferences.getInstance()).getString('token') ?? '';
final streamedResponse = await request.send();
final response = await http.Response.fromStream(streamedResponse);
通过 http.Request
和 send()
方法创建一个流式 POST 请求,传入拼接后的用户问题上下文。
2. 响应内容解析与动态更新
服务器返回数据格式如下:
data:<think> data:这是AI的思考内容... data:</think> data:这是AI的最终回答...
客户端通过解析每一行 data:
标签内容,判断是否处于思考/回答阶段并分段更新 UI。
if (line.contains('data:<think>'))
isThinking = true;
else if (line.contains('data:</think>')) {
isThinking = false;
isAnswer = true;
}
每解析到一段内容,通过 setState()
实时刷新消息内容:
setState(() { _messages.last["text"] = "思考过程:\n$thinking\n\n回答:\n$answer"; });
3. 界面体验优化
-
学科选择器:支持用户选择问题所属学科,在构造上下文时自动添加前缀(如“数学题:...”)。
-
思考过程折叠功能:通过点击“思考过程”文字控制展开/收起详细信息,避免内容冗长影响阅读。
-
发送状态提示:当 AI 正在响应时,输入框禁用并显示
CircularProgressIndicator
。
四、开发中遇到的问题与优化
1. 中文响应乱码
-
问题:服务器返回的中文文本显示为乱码。
-
原因:未正确解码 response 内容。
-
解决方案:使用
utf8.decode(response.bodyBytes)
对返回内容进行正确解码,确保中文字符完整显示。
2. 流式响应阻塞 UI
-
问题:流式响应过程中,长文本处理导致 UI 卡顿,影响用户体验。
-
原因:快速连续
setState()
调用导致主线程占用过高。 -
解决方案:通过在每次 UI 更新后增加
await Future.delayed(Duration(milliseconds: 50));
控制节奏,保持界面响应顺畅。
3. 学科未绑定上下文
-
问题:AI 回答偏泛或与题目学科不符。
-
原因:问题未加入明确学科标识,影响后端大模型理解。
-
解决方案:在问题前添加如
"数学题:"
前缀,用于构造带有学科背景的上下文内容,提升回答准确性。
4. 思考过程与最终输出的区分显示逻辑
-
问题:AI 响应中“思考过程”和“回答”混合在一起,用户难以区分两者,阅读体验差。
-
实现机制:
-
后端以
data:<think>
和data:</think>
标记思考阶段; -
客户端通过标记位
isThinking
和isAnswer
控制内容流入对应的变量。
-
-
展示策略优化:
-
在 UI 中将“思考过程”部分折叠显示,点击可展开;
-
“回答”内容单独展示,避免与推理过程混淆;
-
若无思考过程,仅展示最终回答内容。
-
setState(() {
if (thinking.isEmpty) {
_messages.last["text"] = "思考过程:\n$answer\n\n回答:\n$answer";
} else {
_messages.last["text"] = "思考过程:\n$thinking\n\n回答:\n$answer"; }
});
-
用户体验提升:
-
实现 AI 的“可解释性”,帮助用户理解答案来源;
-
支持折叠功能,避免内容过长影响信息阅读。
-