- 新增饮食记录确认流程,将自动记录模式升级为用户确认模式,提升用户交互体验。 - 实现两阶段饮食记录流程,支持AI识别食物并生成确认选项,用户选择后记录到数据库并提供营养分析。 - 扩展DTO层,新增相关数据结构以支持确认流程。 - 更新服务层,新增处理确认逻辑的方法,优化饮食记录的创建流程。 - 增强API文档,详细说明新流程及使用建议,确保开发者理解和使用新功能。
3.6 KiB
3.6 KiB
流式响应与结构化数据冲突解决方案
问题描述
当前实现中,#记饮食 指令在第一阶段需要返回结构化数据(确认选项),但客户端可能设置了 stream: true,导致响应类型冲突。
解决方案对比
方案1:强制非流式模式 ⭐ (当前实现)
优点:
- 实现简单,改动最小
- 完全向后兼容
- 客户端只需检查 Content-Type
缺点:
- 行为不够明确(忽略stream参数)
- 客户端需要额外处理响应类型检测
实现:
// 当需要返回确认选项时,自动使用JSON响应
if (typeof result === 'object' && 'type' in result) {
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.send({ conversationId, data: result.data });
return;
}
方案2:分离API端点
优点:
- API语义清晰
- 响应类型明确
- 易于测试和维护
缺点:
- 需要新增API端点
- 客户端需要适配新API
建议API设计:
// 专门的食物识别API
@Post('analyze-food')
async analyzeFood(body: { imageUrls: string[] }): Promise<FoodRecognitionResponseDto>
// 确认并记录API
@Post('confirm-food-record')
async confirmFoodRecord(body: { selectedOption: any, imageUrl: string }): Promise<DietRecordResponseDto>
// 原有聊天API保持纯文本
@Post('chat')
async chat(): Promise<StreamableFile | { text: string }>
方案3:统一JSON响应格式
优点:
- 响应格式统一
- 可以在JSON中指示是否需要流式处理
缺点:
- 破坏向后兼容性
- 所有客户端都需要修改
实现示例:
// 统一响应格式
{
conversationId: string;
responseType: 'text' | 'choices' | 'stream';
data: {
content?: string;
choices?: any[];
streamUrl?: string; // 流式数据的WebSocket URL
}
}
方案4:SSE (Server-Sent Events) 统一
优点:
- 可以发送不同类型的事件
- 保持连接状态
- 支持实时交互
缺点:
- 实现复杂度高
- 需要客户端支持SSE
实现示例:
// SSE事件类型
event: text
data: {"chunk": "AI回复的文本片段"}
event: choices
data: {"choices": [...], "content": "请选择食物"}
event: complete
data: {"conversationId": "..."}
推荐方案
短期:方案1 (当前实现) ✅
- 快速解决问题
- 最小化影响
- 保持兼容性
长期:方案2 (分离API端点)
- 更清晰的API设计
- 更好的可维护性
- 更明确的职责分离
当前方案的客户端适配
async function sendDietRequest(imageUrls, conversationId, stream = true) {
const response = await fetch('/ai-coach/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
conversationId,
messages: [{ role: 'user', content: '#记饮食' }],
imageUrls,
stream
})
});
// 检查响应类型
const contentType = response.headers.get('content-type');
if (contentType?.includes('application/json')) {
// 结构化数据(确认选项)
const data = await response.json();
return { type: 'choices', data };
} else {
// 流式文本
return { type: 'stream', stream: response.body };
}
}
总结
当前的方案1实现简单有效,能够解决流式响应冲突问题。虽然在语义上不够完美,但在实际使用中是可行的。建议:
- 立即采用方案1,解决当前问题
- 文档中明确说明响应类型检测的必要性
- 后续版本考虑方案2,提供更清晰的API设计