Files
plates-server/docs/stream-response-solution-options.md
richarjiang ede5730647 feat: 实现饮食记录确认流程
- 新增饮食记录确认流程,将自动记录模式升级为用户确认模式,提升用户交互体验。
- 实现两阶段饮食记录流程,支持AI识别食物并生成确认选项,用户选择后记录到数据库并提供营养分析。
- 扩展DTO层,新增相关数据结构以支持确认流程。
- 更新服务层,新增处理确认逻辑的方法,优化饮食记录的创建流程。
- 增强API文档,详细说明新流程及使用建议,确保开发者理解和使用新功能。
2025-08-18 18:59:36 +08:00

3.6 KiB
Raw Blame History

流式响应与结构化数据冲突解决方案

问题描述

当前实现中,#记饮食 指令在第一阶段需要返回结构化数据(确认选项),但客户端可能设置了 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
  }
}

方案4SSE (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. 立即采用方案1,解决当前问题
  2. 文档中明确说明响应类型检测的必要性
  3. 后续版本考虑方案2提供更清晰的API设计