LocalAI GPT Vision 视觉理解实战:OpenAI 兼容 API、视觉模型安装与图片标记注入原理
【免费下载链接】LocalAILocalAI is the open-source AI engine. Run any model - LLMs, vision, voice, image, video - on any hardware. No GPU required.项目地址: https://gitcode.com/GitHub_Trending/lo/LocalAI
LocalAI 通过集成 LLaVA 系列视觉模型(llama-cpp 后端)实现了 OpenAI GPT Vision API 的本地兼容版本,让你无需 GPU、无需外部服务即可让大模型"看懂"图片。本文基于仓库中的功能文档 gpt-vision.md 展开,完整覆盖从安装视觉模型、调用/v1/chat/completions传图提问,到结合 Grammar 约束输出的全部用法,并深入到源码层面解释图片是如何被转换为模型可理解的标记的。
功能概述:LocalAI 的 GPT Vision 实现
LocalAI 使用 LLaVA 一类视觉模型来理解图片,并在接口层面实现了 OpenAI 的 GPT Vision API。这意味着:
- 请求走标准的
/v1/chat/completions端点,消息体结构与 OpenAI 完全一致(image_url内容块 +text内容块混排); - 视觉能力由 llama-cpp 后端承载,模型为 GGUF 格式的文本模型 + 视觉投影器(mmproj)组合;
- 视觉请求与文本生成共享同一套能力:约束语法(grammar)、function tools、流式输出等均可叠加使用。
安装视觉模型
方式一:CLI 一键安装运行
从模型画廊(gallery)安装一个具备视觉能力的模型,功能文档中的示例使用较小的视觉模型moondream2-20250414:
local-ai run moondream2-20250414方式二:Web UI 的 Models 页面
也可以直接打开 Web UI 的Models页面浏览并安装视觉模型。画廊中可用的其他视觉模型包括smolvlm-instruct和smolvlm2-2.2b-instruct等,详见 模型画廊文档。
视觉模型在画廊中的实际构成
从 gallery/index.yaml 中可以看到moondream2-20250414的注册信息:它由两个模型文件组成——
moondream2-text-model-f16_ct-vicuna.gguf:文本主干模型(F16 精度);moondream2-mmproj-f16-20250414.gguf:视觉投影器(multimodal projector),负责把图片编码结果映射到语言模型空间。
对应的行为配置文件为 gallery/moondream.yaml,其关键配置如下:
config_file: | backend: llama-cpp context_size: 2046 f16: true known_usecases: - chat roles: assistant: "\nAnswer: " system: "\nSystem: " user: "\nQuestion: " stopwords: - 'Question:' - template: chat: | {{.Input}} Answer: completion: | Complete the following sentence: {{.Input}}几个要点:context_size: 2046限制了该小模型的上下文规模;roles/stopwords/template定义了 Vicuna 风格的对话模板;f16: true与 gguf 文件的 F16 量化保持一致。
smolvlm系列(gallery/smolvlm.yaml)的配置则额外声明了known_usecases: [chat, vision],并通过一组 stopword(<dummy32000>、</s>、<end_of_utterance>等)防止模型输出模板符号,聊天模板形如:
{{ .Input -}} Assistant:而经典的 LLaVA 模型配置见 gallery/llava.yaml,采用USER:/ASSISTANT:角色前缀与context_size: 4096。这三份配置展示了 LocalAI 视觉模型接入的通用模式:llama-cpp 后端 + GGUF 文本模型 + mmproj 投影器 + 对话模板。
用 /v1/chat/completions 让模型理解图片
安装模型后,向/v1/chat/completions发起请求即可让 LocalAI 理解并回复图片内容。messages数组中的content是一个数组,混排text与image_url两种内容块:
curl http://localhost:8080/v1/chat/completions -H "Content-Type: application/json" -d '{ "model": "moondream2-20250414", "messages": [{"role": "user", "content": [{"type":"text", "text": "What is in the image?"}, {"type": "image_url", "image_url": {"url": "https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg" }}], "temperature": 0.9}]}'请求结构说明:
| 字段 | 说明 |
|---|---|
model | 已安装的视觉模型名,如moondream2-20250414 |
messages[].role | 消息角色,视觉内容一般放在user消息中 |
messages[].content | 内容块数组,支持text与image_url两种type混排 |
image_url.url | 图片的 URL 或 base64 data URI(见下文源码解析) |
temperature | 采样温度,视觉问答场景常用 0.9 左右 |
除 OpenAI 风格的image_url外,Realtime / Responses 接口还定义了input_image内容块,其中image_url字段接受 base64 编码的 data URI(如data:image/png;base64,...),支持 PNG 和 JPEG 格式,并可附带detail精度字段——类型定义见 message_item.go。
结合 Grammar 约束视觉输出
视觉 API 可以像纯文本 API 一样叠加约束语法(grammar)与 function tools。例如用root ::= ("yes" | "no")强制模型只回答 yes 或 no,从而把视觉理解变成一个二分类判断:
curl http://localhost:8080/v1/chat/completions -H "Content-Type: application/json" -d '{ "model": "moondream2-20250414", "grammar": "root ::= (\"yes\" | \"no\")", "messages": [{"role": "user", "content": [{"type":"text", "text": "Is there some grass in the image?"}, {"type": "image_url", "image_url": {"url": "https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg" }}], "temperature": 0.9}]}'这一组合非常适合把视觉理解接入自动化流程:用 grammar 锁定输出词表后,响应可以直接作为程序判断条件使用,无需再解析自由文本。
源码解析:图片如何变成模型能看懂的标记
从源码结构看,一张图片进入模型要走三段处理:内容块解析、模板渲染标记、后端标记对齐。
1. 中间件解析内容块(middleware)
所有请求先经过 request.go 中的内容解析循环。对于image_url(及别名image)类型的内容块:
case "image_url", "image": // Decode content as base64 either if it's a URL or base64 text base64, err := utils.GetContentURIAsBase64(pp.ImageURL.URL) if err != nil { xlog.Error("Failed encoding image", "error", err) continue CONTENT } input.Messages[i].StringImages = append(input.Messages[i].StringImages, base64) imgIndex++ nrOfImgsInMessage++这里GetContentURIAsBase64会同时处理两种输入:远端 URL(下载后编码)与已经是 base64 的内容,统一存入StringImages并累计本条消息的图片数量。视频(video_url)、音频(audio_url/input_audio)在同一个循环中按相同模式处理,因此该管道天然支持多模态混排。
2. 多模态模板渲染(templates)
解析得到的纯文本部分,随后交由 multimodal.go 的TemplateMultiModal渲染为带占位标记的提示词。LocalAI 使用一个哨兵标记:
// DefaultMultiMediaMarker is the sentinel marker LocalAI emits in the rendered // prompt for each image/audio item. It matches llama.cpp's historical // mtmd_default_marker() ("<__media__>"). ... const DefaultMultiMediaMarker = "<__media__>" const DefaultMultiModalTemplate = "{{ range .Audio }}<__media__>{{end}}{{ range .Images }}<__media__>{{end}}{{ range .Video }}[vid-{{.ID}}]{{end}}{{.Text}}"也就是说:一条消息里有 2 张图,渲染结果就是"<__media__><__media__>What is in the image?"。模板本身是可通过配置覆盖的(模型配置的multimodal模板字段),默认模板会为每张图片/音频输出一个标记、为每个视频输出[vid-N]。单元测试 multimodal_test.go 覆盖了 0 张图、多图以及自定义模板的各种渲染场景。
中间件处的调用见 request.go:TemplateMultiModal收到TotalImages(全局图片序号)与ImagesInMessage(本消息内图片数),据此生成正确数量的标记。
3. 后端标记对齐与图片传递(backend)
哨兵标记是"先渲染、后对齐"的:中间件执行时后端尚未加载,不知道具体后端期望的真实标记。待模型加载完成后,llm.go 完成两件事:
// The prompt was rendered with the sentinel "<__media__>" marker because // middleware templating runs before the backend is loaded and probed. // Once we know the backend's actual media marker, substitute so marker // count matches the bitmap count passed through opts.Images/Videos/Audios. prompt := s if c.MediaMarker != "" && c.MediaMarker != templates.DefaultMultiMediaMarker { prompt = strings.ReplaceAll(prompt, templates.DefaultMultiMediaMarker, c.MediaMarker) } opts.Prompt = prompt opts.Images = imagesMediaMarker来自对后端元数据的探测:llama.cpp 服务端会为每个服务进程随机生成一个多模态标记并通过ModelMetadataResponse.media_marker上报(见 gguf.go 的探测逻辑,以及 llm.go 中"仅当MediaMarker为空才探测"的按需探测条件);探测结果会持久化回模型配置(persistProbedReasoning),后续请求无需重复探测。MediaMarker字段定义在 model_config.go。- 替换确保提示词中"标记的个数"与通过
opts.Images传递的图片张数严格一致——这是多模态模型正确对齐图文位置的关键。
另外有一个重要分支:若模型配置了UseTokenizerTemplate(即由后端自己完成 tokenize 与模板渲染,llama.cpp 服务端会自行注入媒体标记),中间件就只传递纯文本、不再重复输出标记,避免双重标记——源码注释见 request.go。
适用前提与限制
- 视觉模型必须是画廊中的视觉模型(llama-cpp 后端 + mmproj 投影器组合),纯文本模型无法处理
image_url内容块; - 文档示例所用
moondream2-20250414是小型视觉模型,context_size仅 2046,适合快速验证与低资源部署;更高质量的视觉理解可选smolvlm-instruct、smolvlm2-2.2b-instruct等更大规格; image_url支持远端 URL 与 base64 两种形式,远端 URL 会在服务端下载并编码,需保证网络可达;- 视觉请求与 grammar、function tools 的组合受所用模型能力限制,约束效果以模型实际表现为准。
小结
LocalAI 的 GPT Vision 能力把"传图给模型"标准化为三件事:从画廊安装视觉模型(local-ai run <model>)、用 OpenAI 兼容的image_url内容块发起/v1/chat/completions请求、按需叠加 grammar 约束输出。底层由中间件内容解析、<__media__>哨兵标记模板渲染与后端标记探测对齐三个环节协作完成,整条链路对调用方透明——这也是现有 OpenAI 客户端可以几乎不改代码地切换到 LocalAI 本地视觉服务的原因。
【免费下载链接】LocalAILocalAI is the open-source AI engine. Run any model - LLMs, vision, voice, image, video - on any hardware. No GPU required.项目地址: https://gitcode.com/GitHub_Trending/lo/LocalAI
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考