- 新增饮食记录确认流程,将自动记录模式升级为用户确认模式,提升用户交互体验。 - 实现两阶段饮食记录流程,支持AI识别食物并生成确认选项,用户选择后记录到数据库并提供营养分析。 - 扩展DTO层,新增相关数据结构以支持确认流程。 - 更新服务层,新增处理确认逻辑的方法,优化饮食记录的创建流程。 - 增强API文档,详细说明新流程及使用建议,确保开发者理解和使用新功能。
152 lines
3.6 KiB
Markdown
152 lines
3.6 KiB
Markdown
# 流式响应与结构化数据冲突解决方案
|
||
|
||
## 问题描述
|
||
|
||
当前实现中,`#记饮食` 指令在第一阶段需要返回结构化数据(确认选项),但客户端可能设置了 `stream: true`,导致响应类型冲突。
|
||
|
||
## 解决方案对比
|
||
|
||
### 方案1:强制非流式模式 ⭐ (当前实现)
|
||
|
||
**优点:**
|
||
- 实现简单,改动最小
|
||
- 完全向后兼容
|
||
- 客户端只需检查 Content-Type
|
||
|
||
**缺点:**
|
||
- 行为不够明确(忽略stream参数)
|
||
- 客户端需要额外处理响应类型检测
|
||
|
||
**实现:**
|
||
```typescript
|
||
// 当需要返回确认选项时,自动使用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设计:**
|
||
```typescript
|
||
// 专门的食物识别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中指示是否需要流式处理
|
||
|
||
**缺点:**
|
||
- 破坏向后兼容性
|
||
- 所有客户端都需要修改
|
||
|
||
**实现示例:**
|
||
```typescript
|
||
// 统一响应格式
|
||
{
|
||
conversationId: string;
|
||
responseType: 'text' | 'choices' | 'stream';
|
||
data: {
|
||
content?: string;
|
||
choices?: any[];
|
||
streamUrl?: string; // 流式数据的WebSocket URL
|
||
}
|
||
}
|
||
```
|
||
|
||
### 方案4:SSE (Server-Sent Events) 统一
|
||
|
||
**优点:**
|
||
- 可以发送不同类型的事件
|
||
- 保持连接状态
|
||
- 支持实时交互
|
||
|
||
**缺点:**
|
||
- 实现复杂度高
|
||
- 需要客户端支持SSE
|
||
|
||
**实现示例:**
|
||
```typescript
|
||
// SSE事件类型
|
||
event: text
|
||
data: {"chunk": "AI回复的文本片段"}
|
||
|
||
event: choices
|
||
data: {"choices": [...], "content": "请选择食物"}
|
||
|
||
event: complete
|
||
data: {"conversationId": "..."}
|
||
```
|
||
|
||
## 推荐方案
|
||
|
||
### 短期:方案1 (当前实现) ✅
|
||
- 快速解决问题
|
||
- 最小化影响
|
||
- 保持兼容性
|
||
|
||
### 长期:方案2 (分离API端点)
|
||
- 更清晰的API设计
|
||
- 更好的可维护性
|
||
- 更明确的职责分离
|
||
|
||
## 当前方案的客户端适配
|
||
|
||
```javascript
|
||
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. **立即采用方案1**,解决当前问题
|
||
2. **文档中明确说明**响应类型检测的必要性
|
||
3. **后续版本考虑方案2**,提供更清晰的API设计
|