You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

19 KiB

Changelog: 发消息支持可选 AI 生成

日期: 2026-02-13
版本: v1.0
类型: 架构优化 (API 简化)
影响范围: POST /api/v1/ai-conversations/{conversation_id}/messages


📋 概述

发消息触发 AI 生成合并为一步操作,简化前端调用流程。

Before (两步操作)

// 步骤 1: 发送消息
const message = await POST('/conversations/{id}/messages', {
  content: '生成一个日落场景'
});

// 步骤 2: 触发生成
const generation = await POST('/conversations/{id}/generate', {
  messageId: message.messageId,
  generationType: 'image',
  modelId: 'dall-e-3',
  aiParams: { ... }
});

After (一步操作)

// 一步完成
const response = await POST('/conversations/{id}/messages', {
  content: '生成一个日落场景',
  generate: {                  // ✅ 可选:触发 AI 生成
    enabled: true,
    generationType: 'image',
    modelId: 'dall-e-3',
    aiParams: { ... }
  }
});

// response.data.message - 消息信息
// response.data.generation - AI 生成信息(如果触发)

🎯 改动目标

问题背景

原有流程需要两次 API 调用:

  1. 调用复杂:前端需要管理两次请求
  2. 状态同步:需要手动关联 messageId
  3. 错误处理:需要分别处理两个接口的错误

改进方案

将 AI 生成作为可选参数嵌入发消息接口:

  • 一步完成:发消息 + 触发生成
  • 向后兼容generate 参数为可选
  • 统一响应:返回消息 + 生成信息

🛠️ 技术实现

1. Schema 层改动

文件: server/app/schemas/ai_conversation_message.py

1.1 新增 GenerateConfig Schema

class GenerateConfig(BaseModel):
    """AI 生成配置(嵌入消息创建中)"""
    model_config = ConfigDict(populate_by_name=True)
    
    enabled: bool = Field(..., description="是否启用 AI 生成")
    generation_type: str = Field(..., alias="generationType", description="生成类型(image/video)")
    model_id: str = Field(..., alias="modelId", description="模型 ID")
    ai_params: Dict[str, Any] = Field(..., alias="aiParams", description="AI 模型参数")
    business_params: Optional[Dict[str, Any]] = Field(None, alias="businessParams", description="业务参数(可选)")
    
    @field_validator('generation_type')
    @classmethod
    def validate_generation_type(cls, v: str) -> str:
        """验证生成类型"""
        if v not in ['image', 'video']:
            raise ValueError(f"不支持的生成类型: {v}")
        return v

说明

  • enabled: 是否启用 AI 生成(布尔值)
  • generationType: 生成类型(imagevideo
  • modelId: 模型 ID
  • aiParams: AI 模型参数(与 RFC 144 统一)
  • businessParams: 业务参数(可选,通用字典)

1.2 更新 AIConversationMessageCreate Schema

class AIConversationMessageCreate(BaseModel):
    """创建消息请求(支持可选的 AI 生成)"""
    content: str = Field(..., description="消息内容", min_length=1, max_length=10000)
    generate: Optional[GenerateConfig] = Field(None, description="AI 生成配置(可选,会保存到 meta_data)")  # ✅ 新增
    
    model_config = ConfigDict(populate_by_name=True)

变化

  • 新增 generate 字段(可选)
  • 类型为 GenerateConfig Pydantic 对象
  • 移除 meta_data 字段(generate 配置会自动保存到数据库 meta_data

1.3 新增 GenerationInfo Schema

class GenerationInfo(BaseModel):
    """AI 生成信息(嵌入消息响应中)"""
    job_id: str = Field(..., alias="jobId", description="任务 ID")
    task_id: str = Field(..., alias="taskId", description="Celery 任务 ID")
    status: str = Field(..., description="任务状态")
    estimated_credits: int = Field(..., alias="estimatedCredits", description="预估积分消耗")
    reference_images_count: int = Field(0, alias="referenceImagesCount", description="参考图数量")
    
    model_config = ConfigDict(populate_by_name=True)

1.4 更新 AIConversationMessageResponse Schema

class AIConversationMessageResponse(BaseModel):
    """消息响应(可能包含 AI 生成信息)"""
    message_id: UUID = Field(..., description="消息 ID")
    conversation_id: UUID = Field(..., description="对话会话 ID")
    user_id: UUID = Field(..., description="用户 ID")
    ai_job_id: Optional[UUID] = Field(None, description="关联的 AI 任务 ID")
    role: int = Field(..., description="消息角色(1=用户 2=AI 3=系统)")
    content: str = Field(..., description="消息内容")
    meta_data: Dict[str, Any] = Field(default_factory=dict, description="额外元数据")
    order_index: int = Field(..., description="消息顺序")
    created_at: datetime = Field(..., description="创建时间")
    updated_at: datetime = Field(..., description="更新时间")
    
    # 新增:AI 生成信息(如果触发了生成)
    generation: Optional[GenerationInfo] = Field(None, description="AI 生成信息(如果触发)")  # ✅ 新增
    
    model_config = ConfigDict(
        from_attributes=True,
        populate_by_name=True,
        alias_generator=to_camel
    )

变化

  • 新增 generation 字段(可选)
  • 类型为 GenerationInfo Pydantic 对象

2. Service 层改动

文件: server/app/services/ai_conversation_service.py

2.1 更新 send_message 方法签名

async def send_message(
    self,
    conversation_id: UUID,
    user_id: UUID,
    content: str,
    generate_config: Optional[Dict[str, Any]] = None  # ✅ 新增参数
) -> Dict[str, Any]:
    """发送用户消息(支持自动解析提及 + 可选触发 AI 生成)
    
    Args:
        conversation_id: 对话会话 ID
        user_id: 用户 ID
        content: 消息内容(可能包含提及标记)
        generate_config: AI 生成配置(可选,会保存到 meta_data)
            - enabled: 是否启用 AI 生成
            - generation_type: 生成类型(image/video)
            - model_id: 模型 ID
            - ai_params: AI 模型参数
            - business_params: 业务参数(可选)
    
    Returns:
        Dict 包含消息信息 + AI 生成信息(如果触发)
    """

2.2 构建 meta_data 逻辑(优化版)

# 解析提及标记(如果有)
mentions, reference_images = await self._parse_mentions(
    content, user_id, conversation_id
)

# 构建 meta_data
message_meta_data = {}

# 如果提供了 generate_config
if generate_config:
    # 合并提及信息到 business_params
    business_params = generate_config.get('business_params') or {}
    if mentions:
        business_params['mentions'] = mentions
        business_params['reference_images'] = reference_images
        logger.info("解析到 %d 个提及,已添加到 business_params", len(mentions))
    
    # 更新 generate_config
    generate_config['business_params'] = business_params
    
    # 保存 generate 配置到 meta_data
    message_meta_data['generate'] = generate_config
    logger.info("保存 generate 配置到 meta_data")
elif mentions:
    # 如果没有 generate_config,但有提及,仍然保存提及信息
    message_meta_data['mentions'] = mentions
    message_meta_data['reference_images'] = reference_images
    logger.info("解析到 %d 个提及", len(mentions))

关键设计

  • 提及信息归类到 business_params:当有 generate 时,mentionsreference_images 保存在 business_params
  • 向后兼容:如果仅有提及无 generate,提及信息仍然保存在 meta_data 顶层
  • meta_data 结构(有 generate):
    {
      "generate": {
        "enabled": true,
        "generation_type": "image",
        "model_id": "dall-e-3",
        "ai_params": {...},
        "business_params": {
          "mentions": [...],           //  提及信息
          "reference_images": [...],   //  参考图列表
          "customField": "value"       // 其他业务参数
        }
      }
    }
    
  • meta_data 结构(仅提及):
    {
      "mentions": [...],               //  向后兼容
      "reference_images": [...]
    }
    

2.3 在方法结尾添加生成逻辑

logger.info("用户消息已创建: message_id=%s", message.message_id)

# 准备返回结果
result = {
    'message': message,
    'generation': None  # 默认无生成信息
}

# 如果提供了 generate_config 且启用了生成
if generate_config and generate_config.get('enabled'):
    logger.info("触发 AI 生成: message_id=%s", message.message_id)
    try:
        # 调用 trigger_ai_generation
        generation_result = await self.trigger_ai_generation(
            conversation_id=conversation_id,
            user_id=user_id,
            message_id=message.message_id,
            generation_type=generate_config['generation_type'],
            model_id=generate_config['model_id'],
            ai_params=generate_config['ai_params'],
            business_params=generate_config.get('business_params')
        )
        result['generation'] = generation_result
        logger.info("AI 生成已触发: job_id=%s", generation_result.get('jobId'))
    except Exception as gen_error:
        logger.error(
            "触发 AI 生成失败: message_id=%s, 错误=%s",
            message.message_id, str(gen_error),
            exc_info=True
        )
        # 不抛出异常,允许消息创建成功

return result

关键设计

  • 容错处理:生成失败不影响消息创建
  • 条件触发:只有 enabled=true 时才触发
  • 复用逻辑:调用已有的 trigger_ai_generation 方法
  • generate_config 已保存:此时 generate_config 已在前面保存到 message.meta_data['generate']

3. API Router 改动

文件: server/app/api/v1/ai_conversations.py

@router.post("/{conversation_id}/messages", response_model=SuccessResponse[AIConversationMessageResponse], summary="发送消息")
async def send_message(
    conversation_id: UUID,
    request: MessageCreateRequest,
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db)
):
    """发送用户消息(支持可选的 AI 生成)
    
    支持 @ 提及功能:
    - 自动解析消息中的提及标记
    - 验证所有提及的资源
    - 构建 meta_data(mentions + reference_images)
    
    支持可选 AI 生成(一步完成):
    - 提供 `generate` 参数可在发消息后自动触发 AI 生成
    - `generate` 配置会保存到消息的 `meta_data['generate']` 中
    - 无需单独调用 `/generate` 接口
    
    示例请求 1(仅发消息):
    ```json
    {
      "content": "帮我生成一个日落场景"
    }
    ```
    
    示例请求 2(发消息 + 触发生成):
    ```json
    {
      "content": "生成一个日落场景",
      "generate": {
        "enabled": true,
        "generationType": "image",
        "modelId": "dall-e-3",
        "aiParams": {
          "resolution": "1024",
          "aspectRatio": "1:1"
        }
      }
    }
    ```
    """
    service = AIConversationService(db)
    
    # 提取 generate_config
    generate_config = None
    if request.generate:
        generate_config = {
            'enabled': request.generate.enabled,
            'generation_type': request.generate.generation_type,
            'model_id': request.generate.model_id,
            'ai_params': request.generate.ai_params,
            'business_params': request.generate.business_params
        }
    
    result = await service.send_message(
        conversation_id=conversation_id,
        user_id=current_user.user_id,
        content=request.content,
        generate_config=generate_config  # ✅ 传递 generate_config(会保存到 meta_data)
    )
    
    # 构建响应
    message = result['message']
    generation = result.get('generation')
    
    # 使用 Pydantic schema 验证并序列化消息
    message_response = AIConversationMessageResponse.model_validate(message)
    
    # 如果有生成信息,添加到响应中
    if generation:
        from app.schemas.ai_conversation_message import GenerationInfo
        message_response.generation = GenerationInfo(
            job_id=generation['jobId'],
            task_id=generation['taskId'],
            status=generation['status'],
            estimated_credits=generation.get('estimatedCredits', 0),
            reference_images_count=generation.get('referenceImagesCount', 0)
        )
    
    return SuccessResponse(data=message_response)

关键变化

  1. 提取 request.generate 转换为 generate_config 字典
  2. 传递给 Service 层(会自动保存到 meta_data['generate']
  3. 构建响应时,如果有生成信息,附加到 message_response.generation

📊 对比总结

API 调用对比

Before (两步操作)

// 步骤 1: POST /conversations/{id}/messages
{
  "content": "生成一个日落场景"
}

// 响应 1
{
  "data": {
    "messageId": "xxx",
    "content": "生成一个日落场景",
    ...
  }
}

// 步骤 2: POST /conversations/{id}/generate
{
  "messageId": "xxx",
  "generationType": "image",
  "modelId": "dall-e-3",
  "aiParams": { ... }
}

// 响应 2
{
  "data": {
    "jobId": "yyy",
    "taskId": "zzz",
    "status": "pending"
  }
}

After (一步操作)

// 一步: POST /conversations/{id}/messages
{
  "content": "生成一个日落场景",
  "generate": {                      //  可选
    "enabled": true,
    "generationType": "image",
    "modelId": "dall-e-3",
    "aiParams": {
      "resolution": "1024",
      "aspectRatio": "1:1"
    }
  }
}

// 响应(合并)
{
  "data": {
    "messageId": "xxx",
    "content": "生成一个日落场景",
    "generation": {                  //  生成信息(如果触发)
      "jobId": "yyy",
      "taskId": "zzz",
      "status": "pending",
      "estimatedCredits": 10
    },
    ...
  }
}

🎯 改进效果

1. 简化前端调用

// Before (两步)
const message = await sendMessage({ content });
const generation = await triggerGeneration({ messageId, ... });

// After (一步)
const response = await sendMessage({
  content,
  generate: { enabled: true, ... }
});

2. 统一错误处理

try {
  const response = await sendMessage({ content, generate });
  // 一次处理消息 + 生成结果
} catch (error) {
  // 统一错误处理
}

3. 自动关联

  • Service 层自动关联 messageId
  • 无需前端手动管理 ID

4. 向后兼容

// 仅发消息(原有功能)
{
  "content": "这是一条消息"
}

// 发消息 + 生成(新功能)
{
  "content": "生成一个日落场景",
  "generate": { ... }
}

🧪 验证测试

测试场景 1: 仅发消息(向后兼容)

curl -X POST http://localhost:8000/api/v1/ai-conversations/{id}/messages \
  -H "Authorization: Bearer xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "这是一条普通消息"
  }'

预期结果:

{
  "data": {
    "messageId": "xxx",
    "content": "这是一条普通消息",
    "generation": null  //  无生成信息
  }
}

测试场景 2: 发消息 + 触发生成

curl -X POST http://localhost:8000/api/v1/ai-conversations/{id}/messages \
  -H "Authorization: Bearer xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "生成一个日落场景",
    "generate": {
      "enabled": true,
      "generationType": "image",
      "modelId": "dall-e-3",
      "aiParams": {
        "resolution": "1024",
        "aspectRatio": "1:1"
      }
    }
  }'

预期结果:

{
  "data": {
    "messageId": "xxx",
    "content": "生成一个日落场景",
    "generation": {  //  包含生成信息
      "jobId": "yyy",
      "taskId": "zzz",
      "status": "pending",
      "estimatedCredits": 10,
      "referenceImagesCount": 0
    }
  }
}

测试场景 3: 生成失败但消息成功

模拟生成失败(如模型不可用):

预期行为:

  • 消息创建成功
  • generation 字段为 null
  • 后台日志记录生成失败

📝 后续工作

优先级 1: 前端适配(高)

更新前端调用方式:

// API 类型定义
interface SendMessageRequest {
  content: string;
  metaData?: Record<string, any>;
  generate?: {
    enabled: boolean;
    generationType: 'image' | 'video';
    modelId: string;
    aiParams: Record<string, any>;
    businessParams?: Record<string, any>;
  };
}

// 使用示例
const sendMessageWithGeneration = async () => {
  const response = await api.post(`/conversations/${id}/messages`, {
    content: '生成一个日落场景',
    generate: {
      enabled: true,
      generationType: 'image',
      modelId: 'dall-e-3',
      aiParams: {
        resolution: '1024',
        aspectRatio: '1:1'
      }
    }
  });
  
  // response.data.message - 消息信息
  // response.data.generation - AI 生成信息(可能为 null)
};

优先级 2: 废弃旧接口(低)

考虑废弃单独的 POST /{conversation_id}/generate 接口:

  • ⚠️ 标记为 @deprecated
  • ⚠️ 在文档中说明使用新方式
  • ⚠️ 保留向后兼容(可选)

优先级 3: 监控和日志(中)

增加指标监控:

  • 📊 发消息并触发生成的比例
  • 📊 生成失败但消息成功的次数
  • 📊 平均响应时间

🔗 相关文档


📌 影响范围

破坏性变更 (Breaking Changes)

  • 无破坏性变更generate 参数为可选,完全向后兼容

建议迁移策略

渐进式迁移(推荐)

  1. Phase 1:前端继续使用两步操作(无需修改)
  2. Phase 2:新功能使用一步操作
  3. Phase 3:逐步迁移旧代码到新方式

直接迁移

前端直接适配新接口,简化代码逻辑。


验证清单

  • Schema 定义(GenerateConfig, GenerationInfo, 更新请求/响应 Schema)
  • Service 层方法签名更新
  • Service 层生成触发逻辑
  • API Router 更新
  • Python 语法检查通过
  • Docker 服务重启成功
  • 前端适配新格式
  • 端到端测试
  • 监控指标添加

最新更新: 2026-02-13
版本: v1.0
状态: 已完成(待前端适配)