AutoGen.Ollama 实战教程:用 OllamaAgent 与 LLaVA 多模态模型进行图文对话
本文基于 AutoGen .NET(autogen)仓库中的文档 Chat-with-llava.md 及其配套示例代码,讲解如何通过 AutoGen.Ollama 包中的 OllamaAgent 连接本地 Ollama 服务器上的 LLaVA 视觉模型,实现同时支持文本与图片输入的多模态对话。读完本文后,你将能够完整搭建 LLaVA 聊天环境,理解 ImageMessage、MultiModalMessage 两种多模态消息形态的用法,并掌握底层消息转换中间件 OllamaMessageConnector 将 AutoGen 消息体系映射到 Ollama /api/chat 协议的实现原理。
适用前提与环境要求
运行该示例需要满足以下条件(与原文档要求一致):
- 本机已启动一个 Ollama 服务器(默认监听
http://localhost:11434); - 已通过
ollama pull llava:latest安装llava:latest模型(LLaVA 是 Ollama 支持图像输入的视觉-语言模型)。
代码层面的环境要求来自仓库根部的 global.json:dotnet 解决方案基于 .NET SDK 9.0(rollForward: latestFeature)。示例项目 AutoGen.Ollama.Sample.csproj 通过 ProjectReference 直接引用了 AutoGen.Ollama 等源码工程,因此克隆仓库后也可以直接在示例工程中运行。
Step 1:安装 AutoGen.Ollama 包
若你是在自己的 .NET 项目中使用该能力,先安装 NuGet 包:
dotnet add package AutoGen.Ollama
如需安装 nightly build,请参考官方文档 Installation。
Step 2:添加 using 声明
OllamaAgent 位于 AutoGen.Ollama 命名空间,而注册消息转换中间件的扩展方法 RegisterMessageConnector() 位于 AutoGen.Ollama.Extension 命名空间。参照示例 Chat_With_LLaVA.cs 头部:
using AutoGen.Core;
using AutoGen.Ollama.Extension;
说明:原文档中引用的完整示例代码路径为
dotnet/samples/AutoGen.Ollama.Sample/Chat_With_LLaVA.cs,在当前仓库快照中该示例位于 dotnet/samples/AgentChat/AutoGen.Ollama.Sample/Chat_With_LLaVA.cs,内容一致。示例的入口 Program.cs 只做一件事:await Chat_With_LLaVA.RunAsync();
Step 3:创建 OllamaAgent
示例代码(摘自 Chat_With_LLaVA.cs):
using var httpClient = new HttpClient()
{
BaseAddress = new Uri("http://localhost:11434"),
};
var ollamaAgent = new OllamaAgent(
httpClient: httpClient,
name: "ollama",
modelName: "llava:latest",
systemMessage: "You are a helpful AI assistant")
.RegisterMessageConnector()
.RegisterPrintMessage();
各参数的作用可以结合 OllamaAgent.cs 的构造函数签名确认:
| 参数 | 说明 |
|---|---|
httpClient |
用于访问 Ollama HTTP API 的 HttpClient,BaseAddress 指向 Ollama 服务地址,示例中为默认的 http://localhost:11434 |
name |
Agent 名称,会作为回复消息 From 字段的一部分,参与对话角色判定 |
modelName |
Ollama 模型名,本例为 llava:latest |
systemMessage |
系统提示词,默认值即 "You are a helpful AI assistant"(见 OllamaAgent.cs 的默认参数) |
replyOptions |
可选的 OllamaReplyOptions,用于控制采样参数(temperature、top_k 等),见下文“采样参数”小节 |
两个链式扩展方法的作用:
RegisterMessageConnector():定义在 OllamaAgentExtension.cs,向 Agent 注册流式中间件OllamaMessageConnector,负责把 AutoGen 的TextMessage/ImageMessage/MultiModalMessage翻译成 Ollama 请求格式,并把模型返回的ChatResponse还原为普通的TextMessage。多模态能力正是由这一中间件提供的,若不调用,直接发送ImageMessage会在消息转换阶段失败;RegisterPrintMessage():来自AutoGen.Core,在控制台打印每轮消息,便于观察对话过程。
从源码结构看,OllamaAgent 实现了 IStreamingAgent 接口,同时提供 GenerateReplyAsync(非流式,request.Stream = false)与 GenerateStreamingReplyAsync(流式,逐行解析 NDJSON 更新)两种生成方式(见 OllamaAgent.cs)。请求统一 POST 到 Ollama 的 /api/chat 端点,常量定义在 OllamaConsts.cs。
另外,OllamaAgent 在构建请求历史时有一条兜底逻辑:如果传入的消息里没有任何 role == "system" 的消息,会在最前面自动补上构造时提供的 _systemMessage(见 OllamaAgent.cs)。这解释了为什么示例中 systemMessage 参数几乎总是必需的上下文。
Step 4:发送图文混合消息,开始多模态对话
LLaVA 同时接受文本与图片输入。示例(摘自 Chat_With_LLaVA.cs):
var image = Path.Combine("resource", "images", "background.png");
var binaryData = BinaryData.FromBytes(File.ReadAllBytes(image), "image/png");
var imageMessage = new ImageMessage(Role.User, binaryData);
var textMessage = new TextMessage(Role.User, "what's in this image?");
var reply = await ollamaAgent.SendAsync(chatHistory: [textMessage, imageMessage]);
要点解析:
- 图片以文件字节 + MIME 类型(
image/png)构造为BinaryData,封装进ImageMessage。示例按相对运行目录读取resource/images/background.png——该图片文件并未随仓库提供,你需要在自己的运行目录下自行准备一张图片放在对应路径(仓库测试项目则使用了自带的测试图片,见下文测试说明); SendAsync接收chatHistory数组,文本消息与图片消息作为独立的IMessage一起传入,中间件会把它们分别转换为 Ollama 消息后发送给模型。
备选写法:使用 MultiModalMessage 打包图文
示例还给出了第二种写法(Chat_With_LLaVA.cs):
// You can also use MultiModalMessage to put text and image together in one message
// In this case, all the messages in the multi-modal message will be put into single piece of message
// where the text is the concatenation of all the text messages seperated by \n
// and the images are all the images in the multi-modal message
var multiModalMessage = new MultiModalMessage(Role.User, [textMessage, imageMessage]);
reply = await ollamaAgent.SendAsync(chatHistory: [multiModalMessage]);
MultiModalMessage 会把其中多条文本用换行符拼接为一条文本,并汇集全部图片,最终合成单条 Ollama 消息。这一点与 OllamaMessageConnector.cs 中 ProcessMultiModalMessage 的实现一一对应:
// 聚合所有文本消息(用换行拼接)
var textContent = string.Join("\n", textMessages.Select(m => ((TextMessage)m).Content));
// 汇集所有图片
var images = imageMessages.SelectMany(...);
var message = new Message()
{
Role = "user",
Value = textContent,
Images = images.ToList(),
};
两种写法的区别可以概括为:分开发送 TextMessage + ImageMessage 时,它们在 Ollama 请求中是独立消息;用 MultiModalMessage 打包时则合并为一条带图片的 user 消息。对 LLaVA 单轮“看图 + 提问”的场景,两者效果等价。
底层原理:消息如何变成 Ollama /api/chat 请求
理解 OllamaMessageConnector 的转换规则,是排障的关键。核心逻辑位于 OllamaMessageConnector.cs:
- 图片处理(
ProcessImageMessage):优先读取ImageMessage.Data的字节流;若无Data但有Url,则下载该 URL 的图片;最后统一转成 Base64 字符串放入 OllamaMessage.Images字段(该字段在 Ollama 协议中即为 base64 图片列表)。同时校验:图片消息的From必须为null或等于当前 agent 名,且 role 必须能映射为user,否则抛出InvalidOperationException; - 文本处理(
ProcessTextMessage):Role.System映射为system消息;From等于 agent 名的历史消息映射为assistant;其余按Role.User/Role.Assistant映射,映射失败会抛异常; - 响应还原:非流式路径下,中间件把
IMessage<ChatResponse>中的文本内容还原为Role.Assistant的TextMessage(见 OllamaMessageConnector.cs);流式路径会把若干TextMessageUpdate逐块下发,并在最后聚合为一个完整的TextMessage(见 OllamaMessageConnector.cs)。
最终发往 Ollama 的每条消息对应 Message.cs 的 DTO:
public class Message
{
[JsonPropertyName("role")] public string Role { get; set; } = string.Empty; // system / user / assistant
[JsonPropertyName("content")] public string Value { get; set; } = string.Empty; // 文本内容
[JsonPropertyName("images")] public IList<string>? Images { get; set; } // 可选:base64 图片列表(多模态模型如 llava)
}
可以看到,images 字段正是注释中注明的“for multimodal models such as llava”,这就是 LLaVA 图片输入最终落到协议层的形态。
采样参数(OllamaReplyOptions)
虽然 LLaVA 示例未显式使用,但 OllamaAgent 构造器接受可选的 OllamaReplyOptions(继承自 GenerateReplyOptions,定义见 OllamaReplyOptions.cs),常用字段及源码注释中的默认值如下:
| 字段 | 作用 | Ollama 默认值 |
|---|---|---|
Temperature |
采样温度,越高越“有创意” | 0.8 |
MaxToken |
最大生成 token 数 | 128(-1 表示无限生成) |
TopK / TopP |
核采样 / Top-P 采样 | 40 / 0.9 |
NumCtx |
上下文窗口大小 | 2048 |
NumThread / NumGpu |
计算线程数 / 送入 GPU 的层数 | Ollama 自动检测 / macOS 默认 1 |
KeepAlive |
模型加载在内存中的保持时长 | 5m |
Seed |
随机种子,固定后同 prompt 生成相同文本 | 0 |
Format |
强制 JSON 输出(FormatType.Json) |
无 |
需要留意的是:MaxToken 默认只有 128,如果 LLaVA 对复杂图片的长描述被截断,可以在构造 OllamaAgent 时传入 OllamaReplyOptions { MaxToken = 512 } 之类放宽。这些选项会被 BuildChatRequestOptions 映射到 ChatRequest.Options(见 OllamaAgent.cs)。
用测试用例验证 LLaVA 链路
仓库自带了针对 LLaVA 的集成测试,可作为运行本示例的参照。OllamaAgentTests.cs 中的用例均标记 [ApiKeyFact("OLLAMA_HOST")],即需要设置环境变量 OLLAMA_HOST 指向本地 Ollama 服务后才会执行:
ItReturnValidMessageUsingLLavaAsync(OllamaAgentTests.cs):不经过中间件,直接构造带Images = [base64Image]的 Ollama 原生Message调用GenerateReplyAsync,验证llava:latest的原始协议路径;ItCanProcessMultiModalMessageUsingLLavaAsync(OllamaAgentTests.cs):与本文 Step 4 的MultiModalMessage写法完全一致——ImageMessage+TextMessage打包后SendAsync,断言回复是Role.Assistant的TextMessage且内容非空;ItCanProcessImageMessageUsingLLavaAsync(OllamaAgentTests.cs):只发送单条ImageMessage,验证“纯图片提问”也是合法路径;ItReturnValidStreamingMessageUsingLLavaAsync(OllamaAgentTests.cs):验证带图请求的流式响应,Done == true的最后一帧携带完整ChatResponse。
这组测试覆盖了本文介绍的所有消息形态,如果你本地示例跑不通,可以先按测试的写法缩小问题范围:原生 Message 路径失败通常是 Ollama 服务端/模型问题,中间件路径失败则多半是消息构造(role/from/图片来源)问题。
小结
围绕 Chat-with-llava.md 这条主线,本文完整覆盖了从安装包、创建 OllamaAgent、到发送图文混合消息的全部步骤,并结合 OllamaAgent.cs、OllamaMessageConnector.cs、Message.cs 等源码解释了图文消息如何被 Base64 化并映射进 Ollama /api/chat 协议,最后给出了仓库内置的 LLaVA 测试用例作为验证手段。核心要点:多模态能力依赖 RegisterMessageConnector() 注册的中间件;ImageMessage 与 MultiModalMessage 两种写法可按场景选用;OllamaReplyOptions 用于在需要时微调采样行为。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00