LLMClient 视觉模型支持
在 Step 9 中,我们扩展了 LLMClient 以支持视觉模型。核心思路是:当检测到消息中包含图像时,自动切换到配置的视觉模型。
配置扩展
首先,在 LLMConfig 类型中添加视觉模型配置:
1 2 3 4 5 6 7 8 9 10 11
|
export type LLMConfig = { apiKey: string; baseURL?: string; model?: string; visionModel?: string; temperature?: number; maxTokens?: number; streaming?: boolean; };
|
配置示例:
1 2 3 4 5 6 7 8
| { "llm": { "apiKey": "sk-...", "baseUrl": "https://api.openai.com/v1", "model": "gpt-4", "visionModel": "gpt-4-vision-preview" } }
|
模型选择逻辑
LLMClient 添加了 getModel() 方法,根据消息内容自动选择合适的模型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
private hasImage(messages: LLMMessage[]): boolean { return messages.some((msg) => { const content = msg.content; if (typeof content === "string") { return false; } if (Array.isArray(content)) { return content.some((item) => item.type === "image"); } if (content && typeof content === "object") { return content.type === "image"; } return false; }); }
private getModel(messages: LLMMessage[]): string { const hasImages = this.hasImage(messages); if (hasImages && this.config.visionModel) { return this.config.visionModel; } return this.config.model || "deepseek-chat"; }
|
工作流程:
1 2 3 4 5 6 7
| 用户消息 ↓ hasImage() 检测 ↓ 包含图像?→ 是 → 使用 visionModel ↓ 否 使用默认 model
|
消息格式转换
视觉模型通常要求使用特定的消息格式。OpenAI 的视觉 API 使用 image_url 类型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
|
private convertMessage(msg: LLMMessage): any { const baseMsg: any = { role: msg.role, };
if (msg.role === "tool") { baseMsg.tool_call_id = msg.tool_call_id; baseMsg.name = msg.name; baseMsg.content = msg.content; } else { const content = msg.content; if (typeof content === "string") { baseMsg.content = content; } else if (Array.isArray(content)) { baseMsg.content = content.map((item: any) => { if (item.type === "text") { return { type: "text", text: item.text }; } else if (item.type === "image") { return { type: "image_url", image_url: { url: `data:${item.mediaType};base64,${item.data}`, }, }; } }); } else if (content.type === "image") { baseMsg.content = [{ type: "image_url", image_url: { url: `data:${content.mediaType};base64,${content.data}`, }, }]; } }
return baseMsg; }
|
格式转换示例
输入消息(内部格式):
1 2 3 4 5 6 7 8 9 10 11
| { role: "user", content: [ { type: "text", text: "这是什么?" }, { type: "image", mediaType: "image/png", data: "iVBORw0KGgo..." } ] }
|
输出消息(OpenAI 格式):
1 2 3 4 5 6 7 8 9 10 11 12
| { role: "user", content: [ { type: "text", text: "这是什么?" }, { type: "image_url", image_url: { url: "data:image/png;base64,iVBORw0KGgo..." } } ] }
|
完整调用流程
chat() 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| async chat( systemPrompt: string, messages: LLMMessage[] ): Promise<LLMResponse> { const openAIMessages = [ { role: "system", content: systemPrompt }, ...messages.map((msg) => this.convertMessage(msg)) ];
const model = this.getModel(messages);
const completion = await this.client.chat.completions.create({ model: model, messages: openAIMessages, temperature: this.config.temperature ?? 0.7, max_tokens: this.config.maxTokens, tools: this.getTools(), });
return { content: completion.choices[0].message.content, toolCalls: extractToolCalls(completion) }; }
|
chatStream() 方法
流式输出同样支持视觉模型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| async chatStream( systemPrompt: string, messages: LLMMessage[], onChunk: (chunk: StreamChunk) => void ): Promise<LLMResponse> { const openAIMessages = [ { role: "system", content: systemPrompt }, ...messages.map((msg) => this.convertMessage(msg)) ];
const model = this.getModel(messages);
const stream = await this.client.chat.completions.create({ model: model, messages: openAIMessages, temperature: this.config.temperature ?? 0.7, max_tokens: this.config.maxTokens, tools: this.getTools(), stream: true, });
}
|
配置最佳实践
模型选择建议
OpenAI 系列:
1 2 3 4
| { "model": "gpt-4", "visionModel": "gpt-4o" }
|
- 使用 GPT-4o 处理视觉(速度快、效果好)
- 使用 GPT-4 处理纯文本(成本更低)
Anthropic 系列:
1 2 3 4
| { "model": "claude-3-opus-20240229", "visionModel": "claude-3-opus-20240229" }
|
- Claude 3 Opus 同时支持文本和视觉
- 使用同一模型简化配置
混合方案:
1 2 3 4
| { "model": "deepseek-chat", "visionModel": "gpt-4o" }
|
- 文本使用 DeepSeek(成本低)
- 视觉使用 GPT-4o(效果好)
降级处理
如果未配置视觉模型,可以优雅降级:
1 2 3 4 5 6 7 8 9 10 11 12 13
| private getModel(messages: LLMMessage[]): string { const hasImages = this.hasImage(messages);
if (hasImages) { if (this.config.visionModel) { return this.config.visionModel; } console.warn("消息包含图像但未配置视觉模型,可能无法正确处理"); }
return this.config.model || "deepseek-chat"; }
|
API 差异处理
不同的视觉模型可能有不同的 API 格式:
OpenAI 格式
1 2 3 4
| { type: "image_url", image_url: { url: "data:image/png;base64,..." } }
|
Anthropic 格式
1 2 3 4 5 6 7 8
| { type: "image", source: { type: "base64", media_type: "image/png", data: "..." } }
|
统一处理方案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| private convertMessage(msg: LLMMessage): any { const isAnthropic = this.config.baseURL?.includes("anthropic");
if (isAnthropic && item.type === "image") { return { type: "image", source: { type: "base64", media_type: item.mediaType, data: item.data } }; }
return { type: "image_url", image_url: { url: `data:${item.mediaType};base64,${item.data}` } }; }
|
小结
本节介绍了如何在 LLMClient 中集成视觉模型:
- 添加 visionModel 配置
- 实现自动模型选择
- 处理多模态消息格式转换
- 支持流式和同步两种调用方式
导航
上一篇: 11.1 多模态 AI 概述
下一篇: 11.3 图像处理与传输