FastAPI開發AI應用:模型新增圖片理解
本文將深入講解如何在 FastAPI AI 聊天應用中實現圖片理解功能,讓 AI 能夠理解和分析用戶上傳的圖片內容。通過本教程,你將學會如何構建完整的多模態交互系統,包括圖片上傳、預處理、多模態消息格式化以及流式響應處理等核心技術。
本文概述
想象一下,當你向 AI 發送一張圖片時,AI 不僅能看懂圖片內容,還能基于圖片進行深度分析和對話——就像一個擁有視覺能力的智能助手。這就是我們要實現的視覺理解功能!用戶可以上傳圖片,AI 能夠識別圖片中的物體、場景、文字,并與用戶進行基于圖片內容的智能對話。
要實現的效果就是下圖所示:
圖片
本文中,我們新增功能具備圖片上傳預覽與管理、文本和圖片混合消息處理、AI 識別分析圖片元素、流式顯示分析結果及圖片格式與大小驗證等核心能力,可實現多模態內容的高效交互與安全處理。
技術上采用 FastAPI 作為后端框架,結合 Pillow 進行圖片處理,依托 doubao-seed-1-6、GPT-4o 等多模態 AI 模型實現圖片理解,通過 Base64 編碼傳輸圖片數據,并利用 HTML5 File API 與 JavaScript 完成前端的圖片上傳和預覽交互。
支持的圖片模型
模型系列 | 代表模型 | 圖片能力 | 特色 |
OpenAI GPT-4o | gpt-4o, gpt-4o-mini | 多模態融合 | 圖文混合理解、實時交互 |
豆包視覺 | doubao-seed-1.6 | 中文場景優化 | 中文OCR、本土化識別 |
圖片理解能力詳解
這里用豆包文檔的圖片理解說明,幫助大家理解大模型對圖片理解的一些限制。
圖片
圖片傳入方式
圖片理解模型支持兩種圖片傳入方式:
- 圖片 URL 方式:直接傳入可訪問的圖片鏈接
- Base64 編碼方式:將圖片轉換為 Base64 編碼字符串傳輸
本項目采用 Base64 編碼方式,確保圖片數據的安全傳輸和處理。
圖片格式與尺寸要求
支持的圖片格式:
- JPEG (.jpg, .jpeg)
- PNG (.png)
- GIF (.gif)
- WebP (.webp)
- BMP (.bmp)
圖片尺寸限制:
根據不同模型版本,圖片尺寸要求有所不同:
新版豆包模型(doubao-1.5-vision-pro-32k-250115 及以后版本):
- 最小尺寸:寬 > 14px 且 高 > 14px
- 像素范圍:寬×高 在 [196, 3600萬] 像素之間
- 推薦尺寸:
低精度模式:104萬像素(1024×1024)
高精度模式:401萬像素(2048×1960)
圖片數量限制
單次請求中可傳入的圖片數量受模型上下文長度限制:
計算公式:
最大圖片數量 = 模型上下文長度 ÷ 單張圖片Token消耗
實際示例:
- 高分辨率圖片(1312 tokens/張):32k上下文可傳入約 24 張
- 低分辨率圖片(256 tokens/張):32k上下文可傳入約 125 張
注意事項:
- 圖片數量過多會影響模型理解質量
- 建議單次請求控制在 5-10 張圖片以內
- 對話API是無狀態的,多次理解同一張圖片需重復傳入
理解深度控制
大部分圖片模型支持兩種理解深度:
低精度模式(detail: low)
- 處理速度快,Token消耗少
- 適合簡單的圖片識別和分類
- 圖片會被壓縮到較小尺寸
高精度模式(detail: high)
- 處理精度高,能識別更多細節
- Token消耗較多,處理時間較長
- 保持圖片原始分辨率進行分析
在豆包模型中可以通過控制 detail 傳參來實現。
# 在消息格式化時指定理解深度
content = [
{
"type": "image_url",
"image_url": {
"url": f"data:{image_type};base64,{image_data}",
"detail": "high" # 或 "low"
}
},
{
"type": "text",
"text": "請詳細分析這張圖片的內容"
}
]
核心理念
圖片理解功能的實現基于三個核心設計原則:
1. 統一消息格式原則文本消息和圖片消息使用統一的數據結構,確保系統能夠無縫處理多模態內容。這樣可以讓現有的對話邏輯無需大幅修改就能支持圖片。
2. 流式處理原則圖片分析結果應該支持流式返回,讓用戶能夠實時看到 AI 的分析過程。這不僅提升了用戶體驗,還保持了與純文本對話的一致性。
3. 安全優先原則所有上傳的圖片都需要經過嚴格的格式驗證和大小限制,確保系統安全穩定運行。
架構層次
圖片理解功能的架構分為三個清晰的層次:
1. 前端交互層(HTML5 + JavaScript)
這一層負責用戶的圖片上傳交互和預覽展示:
/**
* 處理圖片上傳的核心函數
* 包含文件驗證、大小檢查、格式轉換等功能
*/
asyncfunction handleImageUpload(event) {
const file = event.target.files[0];
if (!file) return;
// 檢查文件類型
if (!file.type.startsWith('image/')) {
alert('請選擇圖片文件');
return;
}
// 檢查文件大?。ㄏ拗茷?MB)
if (file.size > 5 * 1024 * 1024) {
alert('圖片文件大小不能超過5MB');
return;
}
try {
// 創建FormData對象
const formData = new FormData();
formData.append('file', file);
// 上傳圖片
const response = await fetch('/upload/image', {
method: 'POST',
body: formData
});
if (response.ok) {
const result = await response.json();
// 保存圖片數據
currentImageData = result.data.base64_data;
currentImageType = result.data.content_type;
// 顯示圖片預覽
showImagePreview(file, result.data.filename);
console.log('圖片上傳成功:', result.message);
} else {
const error = await response.json();
alert('圖片上傳失敗: ' + (error.detail || '未知錯誤'));
}
} catch (error) {
console.error('圖片上傳失敗:', error);
alert('圖片上傳失敗: ' + error.message);
}
// 清空文件輸入框
event.target.value = '';
}
新增 image_data、image_type 接受前端上傳圖片 base64 內容以及圖片格式。
from dataclasses import dataclass
from typing import Optional
@dataclass
class AIMessage:
"""
AI消息數據模型
支持文本和圖片的統一消息格式
"""
role: str # 消息角色:user, assistant, system
content: str # 文本內容
timestamp: float # 時間戳
image_data: Optional[str] = None # Base64編碼的圖片數據
image_type: Optional[str] = None # 圖片MIME類型
...
核心特點:
- 文件驗證:嚴格檢查文件類型和大小
- 異步上傳:使用 FormData 進行異步文件傳輸
- 實時預覽:上傳成功后立即顯示圖片預覽
- 錯誤處理:完善的錯誤提示和異常處理
2. 后端處理層(FastAPI + Pillow)
這一層負責接收圖片文件,進行驗證和格式轉換:
@app.post("/upload/image")
asyncdef upload_image(file: UploadFile = File(...)):
"""
圖片上傳API端點
處理圖片文件的接收、驗證、轉換和存儲
Args:
file: 上傳的圖片文件
Returns:
dict: 包含上傳結果和圖片數據的響應
"""
logger.info(f"接收圖片上傳請求 - 文件名: {file.filename}, 類型: {file.content_type}")
try:
# 檢查文件類型
ifnot file.content_type ornot file.content_type.startswith('image/'):
raise HTTPException(status_code=400, detail="只支持圖片文件")
# 讀取文件內容
file_content = await file.read()
# 檢查文件大?。?0MB = 10 * 1024 * 1024 bytes)
max_size = 10 * 1024 * 1024# 10MB
if len(file_content) > max_size:
logger.warning(f"文件大小超出限制 - 文件名: {file.filename}, 大小: {len(file_content)} bytes, 限制: {max_size} bytes")
raise HTTPException(status_code=413, detail=f"文件大小不能超過10MB,當前文件大小: {len(file_content) / (1024 * 1024):.2f}MB")
# 驗證圖片格式和完整性
try:
image = Image.open(BytesIO(file_content))
image.verify() # 驗證圖片完整性
except Exception as e:
logger.error(f"圖片驗證失敗: {e}")
raise HTTPException(status_code=400, detail="無效的圖片文件")
# 轉換為base64編碼
base64_data = base64.b64encode(file_content).decode('utf-8')
logger.info(f"圖片上傳成功 - 文件名: {file.filename}, 大小: {len(file_content)} bytes")
return {
"success": True,
"message": "圖片上傳成功",
"data": {
"filename": file.filename,
"content_type": file.content_type,
"size": len(file_content),
"base64_data": base64_data
}
}
except HTTPException:
raise
except Exception as e:
logger.error(f"圖片上傳失敗: {e}")
raise HTTPException(status_code=500, detail=f"圖片上傳失敗: {str(e)}")
關鍵功能:
- 格式驗證:使用 Pillow 驗證圖片格式和完整性
- Base64 編碼:將圖片轉換為 Base64 格式便于傳輸
- 異常處理:完善的錯誤處理和日志記錄
- 安全檢查:多層次的文件安全驗證
3. 多模態消息層(OpenAI Compatible)
這一層負責將圖片和文本組合成多模態消息格式:
def format_messages(self, messages: List[AIMessage], system_prompt: str = None) -> List[Dict[str, Any]]:
"""
格式化消息為提供商特定格式,支持多模態內容
將文本和圖片統一格式化為 OpenAI 兼容的消息格式
Args:
messages: 消息列表,包含文本和圖片消息
system_prompt: 系統提示詞
Returns:
List[Dict[str, Any]]: 格式化后的消息列表
"""
formatted_messages = []
# 添加系統提示
if system_prompt:
formatted_messages.append({
"role": "system",
"content": system_prompt
})
# 處理歷史消息
for msg in messages:
if msg.role in ["user", "assistant"]:
# 檢查是否包含圖片數據
if msg.image_data:
# 多模態消息格式
content = [
{
"type": "image_url",
"image_url": {
"url": f"data:{msg.image_type};base64,{msg.image_data}"
}
},
{
"type": "text",
"text": msg.content
},
]
formatted_messages.append({
"role": msg.role,
"content": content
})
else:
# 純文本消息格式
formatted_messages.append({
"role": msg.role,
"content": msg.content
})
return formatted_messages
采用統一數據結構處理文本和圖片消息,不僅完全兼容 OpenAI 的多模態消息格式,還支持純文本、純圖片、圖文混合等多種消息類型,且易于擴展以支持更多模態類型。
到這里,我們的圖片上傳處理的核心邏輯就已經完成了。
踩坑點
大家知道標準的 EventSource API 設計時就只支持 GET,不支持 POST 請求,但是由于我們的聊天應用上傳圖片時采用base64 格式,導致上傳內容很大,后端接收時,會出現參數截斷現象,因此我們要修改 SSE 實現,改用 fetch post 請求來實現 SSE POST 請求。
// 構建請求體
const requestBody = {
user_id: userId,
session_id: currentSessionId,
message: message,
provider: provider,
model: model,
image_data: currentImageDataTmp,
image_type: currentImageTypeTmp
};
// 發送POST請求獲取流式響應
const response = await fetch('/chat/stream', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(requestBody)
});
if (!response.ok) {
thrownewError(`HTTP error! status: ${response.status}`);
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let reasoningMessage = '';
let contentMessage = '';
let messageContainer = null;
let reasoningElement = null;
let contentElement = null;
let hasShownTypingIndicator = false;
// 處理流式響應
asyncfunction processStream() {
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value, { stream: true });
const lines = chunk.split('\n');
for (const line of lines) {
if (line.startsWith('data: ')) {
const jsonStr = line.slice(6);
if (jsonStr.trim() === '') continue;
try {
const data = JSON.parse(jsonStr);
if (data.type === 'chunk' || data.type === 'content' || data.type === 'reasoning') {
...
}
} catch (parseError) {
console.error('解析JSON失敗:', parseError, 'JSON字符串:', jsonStr);
}
}
}
}
} catch (error) {
console.error('處理流式響應失敗:', error);
hideTypingIndicator();
addMessage('assistant', '? 抱歉,連接出現問題,請重試。');
messageInput.disabled = false;
document.getElementById('sendButton').disabled = false;
}
}
// 開始處理流式響應
processStream();
再給大家演示下,看下我們上傳圖片的最終效果,
圖片
總結
本文詳細講解了在 FastAPI AI 聊天應用中實現圖片理解功能的方法,包括構建多模態交互系統的核心技術、支持的圖片模型及其實力、核心理念、各層次實現代碼、設計亮點和踩坑點等內容。