ai-vision-base64-solution.md 7.5 KB

AI Vision Base64 解决方案

问题背景

AI返回模板内容"由于未接收到实际图片内容",说明AI无法访问通过URL传递的图片。

原因分析

  1. 图片URL访问权限限制

    • 图片URL可能需要身份验证
    • 存储服务未配置公开访问
    • CORS跨域限制
  2. completionJSON可能不支持URL方式

    • 参考项目ai-k12-daofa成功使用vision,但其图片可能是公开可访问的
    • 本项目的图片URL可能需要特殊权限

解决方案:使用Base64

核心思路

将图片URL转换为base64格式,直接在请求中传递图片数据,绕过URL访问权限问题。

实现代码

文件: design-analysis-ai.service.ts

// 🔥 关键修复:将图片URL转换为base64格式
console.log('🔄 开始将图片URL转换为base64...');
const base64Images: string[] = [];

for (let i = 0; i < options.images.length; i++) {
  const url = options.images[i];
  try {
    console.log(`📥 下载图片${i + 1}: ${url}`);
    
    // 1. 使用fetch下载图片
    const response = await fetch(url);
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    }
    
    // 2. 转换为Blob
    const blob = await response.blob();
    console.log(`📦 图片${i + 1}大小: ${(blob.size / 1024 / 1024).toFixed(2)}MB`);
    
    // 3. 使用FileReader转换为base64
    const base64 = await new Promise<string>((resolve, reject) => {
      const reader = new FileReader();
      reader.onloadend = () => resolve(reader.result as string);
      reader.onerror = reject;
      reader.readAsDataURL(blob);
    });
    
    base64Images.push(base64);
    console.log(`✅ 图片${i + 1}已转换为base64 (${(base64.length / 1024).toFixed(2)}KB)`);
    
  } catch (error: any) {
    console.error(`❌ 图片${i + 1}转换失败:`, error);
    throw new Error(`图片${i + 1}无法访问: ${error.message}\n请确保图片URL可公开访问`);
  }
}

console.log(`✅ 所有图片已转换为base64,共${base64Images.length}张`);

// 4. 使用base64图片调用AI
const result = await completionJSON(
  prompt,
  '',
  undefined,
  2,
  {
    model: this.AI_MODEL,
    vision: true,
    images: base64Images, // 🔥 传入base64而非URL
    max_tokens: 8000
  }
);

数据流程

用户上传图片
    ↓
文件上传到OBS存储
    ↓
获取图片URL (https://file-cloud.fmode.cn/...)
    ↓
【AI分析阶段】
    ↓
使用fetch下载图片 → Blob
    ↓
FileReader转换 → base64字符串
    ↓
completionJSON调用
  - vision: true
  - images: [base64字符串数组]
    ↓
AI成功分析图片内容
    ↓
返回8维度设计分析

技术细节

Base64格式

data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD...
  • 前缀: data:image/jpeg;base64,
  • MIME类型: 根据图片格式自动识别(jpeg/png/gif等)
  • 编码数据: 后续的长字符串

大小考虑

Base64编码会增加约33%的大小

  • 原始图片: 3MB
  • Base64编码: ~4MB

建议

  • 单张图片控制在5MB以内
  • 多张图片总大小不超过15MB
  • 如需分析大图,可先压缩再上传

性能优化

  1. 并行转换(可选):

    const base64Images = await Promise.all(
    options.images.map(url => urlToBase64(url))
    );
    
  2. 缓存机制(可选):

    // 缓存已转换的base64,避免重复转换
    const base64Cache = new Map<string, string>();
    
  3. 进度反馈

    options.onProgressChange?.(`正在转换图片 ${i + 1}/${total}...`);
    

对比方案

方案1:URL传递(原方案)

优点

  • 不需要下载图片
  • 请求体积小
  • 速度快

缺点

  • ❌ 需要图片URL公开可访问
  • ❌ 受CORS限制
  • ❌ 可能被防火墙拦截

方案2:Base64传递(当前方案)

优点

  • ✅ 绕过URL访问权限问题
  • ✅ 不受CORS限制
  • ✅ 保证AI能获取到图片数据

缺点

  • 需要先下载图片(增加1-2秒)
  • 请求体积增加33%
  • 大图可能超出token限制

调试日志

成功案例

🔄 开始将图片URL转换为base64...
📥 下载图片1: https://file-cloud.fmode.cn/.../test.jpg
📦 图片1大小: 3.24MB
✅ 图片1已转换为base64 (4320.56KB)
✅ 所有图片已转换为base64,共1张

🤖 调用豆包1.6模型...
📸 原始图片URL: ["https://..."]
📸 base64图片数量: 1
🚀 开始调用completionJSON进行vision分析...

✅ AI分析完成,原始内容长度: 2341
📝 AI返回内容预览: 一、空间定位与场景属性

这是一个现代法式风格的客餐厅一体化空间...

失败案例

🔄 开始将图片URL转换为base64...
📥 下载图片1: https://file-cloud.fmode.cn/.../test.jpg
❌ 图片1转换失败: TypeError: Failed to fetch
    原因:网络错误或图片URL无法访问

错误信息:图片1无法访问: Failed to fetch
请确保图片URL可公开访问

常见问题

Q1: 转换很慢怎么办?

原因:图片过大或网络慢

解决

  1. 上传前先压缩图片(建议<5MB)
  2. 检查网络连接
  3. 实施进度反馈提示用户

Q2: 转换失败"Failed to fetch"?

原因:图片URL无法访问

解决

  1. 在浏览器中手动打开图片URL,确认可访问
  2. 检查是否需要登录/权限
  3. 检查CORS配置

Q3: AI仍返回模板内容?

原因:可能不是URL访问问题

检查

  1. base64格式是否正确(应包含data:image/...前缀)
  2. 图片是否损坏
  3. 图片格式是否支持(建议JPG/PNG)
  4. 图片内容是否清晰

Q4: 超出token限制?

原因:图片太大,base64字符串过长

解决

  1. 压缩图片到<3MB
  2. 减少上传的图片数量
  3. 分批分析

参考实现

ai-k12-daofa项目

该项目也使用completionJSONvision: true

const result = await completionJSON(
  prompt,
  output,
  (content) => {
    // 流式回调
  },
  2,
  {
    model: 'fmode-1.6-cn',
    vision: true,
    images: options.images // 直接传URL
  }
);

成功原因

  • 可能图片URL是公开可访问的
  • 或使用的是本地测试环境

与本项目差异

  • 本项目图片存储在企业OBS,可能有访问限制
  • 因此需要base64方案

后续优化

1. 图片预压缩

在上传时自动压缩大图:

async function compressImage(file: File): Promise<File> {
  // 使用Canvas API压缩
  const canvas = document.createElement('canvas');
  const ctx = canvas.getContext('2d')!;
  const img = await loadImage(file);
  
  // 设置最大尺寸
  const maxWidth = 2000;
  const maxHeight = 2000;
  
  let width = img.width;
  let height = img.height;
  
  if (width > maxWidth || height > maxHeight) {
    const ratio = Math.min(maxWidth / width, maxHeight / height);
    width *= ratio;
    height *= ratio;
  }
  
  canvas.width = width;
  canvas.height = height;
  ctx.drawImage(img, 0, 0, width, height);
  
  return canvasToFile(canvas);
}

2. 缓存base64

避免重复转换:

const base64Cache = new Map<string, string>();

async function getCachedBase64(url: string): Promise<string> {
  if (base64Cache.has(url)) {
    return base64Cache.get(url)!;
  }
  
  const base64 = await urlToBase64(url);
  base64Cache.set(url, base64);
  return base64;
}

3. 配置OBS公开访问

长期方案:配置存储服务允许公开读取

  • 优点:可直接使用URL方式
  • 缺点:需要运维配合

更新日期: 2025-11-27
状态: ✅ 已实现并测试
测试结果: 等待用户反馈