摘要:本文系统梳理 Ollama 的两个核心 HTTP 接口:用于文本补全的/api/generate和用于多轮对话的/api/chat,并逐一说明model、prompt、messages、stream、format、keep_alive、tools等常用参数的含义与取值。文中还对比了流式与非流式响应的差异及解析方式,介绍了多模态图片输入写法,汇总了temperature、top_p、num_ctx等推理参数,并给出num_ctx设置、JSON 输出容错、keep_alive配置等常见易错点提示,帮助读者快速完成接口对接与调试。
Ollama 主要涉及两个接口:/api/generate(补全)和/api/chat(对话),参数几乎一样。下面按接口给你完整说明。
一、/api/generate(文本补全)
POST http://localhost:11434/api/generate { "model": "qwen2.5:7b", "prompt": "写一首诗", "system": "你是一个诗人", "stream": false, "format": "", "options": {}, "keep_alive": "5m", "context": [] }
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
model | string | ✅ | 模型名,如qwen2.5:7b、llama3.2 |
prompt | string | ✅ | 输入提示词 |
system | string | ❌ | 系统提示,覆盖模型内置的 system prompt |
stream | bool | ❌ | false一次返回全部;true流式逐行返回(默认 true) |
format | string | ❌ | 强制输出格式:"json"或 JSON Schema(如{"type":"object","properties":{...}}) |
options | object | ❌ | 推理参数(见下方第五节) |
keep_alive | string/int | ❌ | 模型在内存保留时长:"5m"、"1h"或毫秒数;0立即卸载,-1永久驻留 |
context | int[] | ❌ | 上一轮返回的context,实现多轮记忆(老接口,新项目建议用 chat) |
raw | bool | ❌ | true时不做模板化处理,直接送原提示词(适配不兼容模板的模型) |
images | string[] | ❌ | 多模态模型的图片(base64 编码列表) |
二、/api/chat(对话,推荐)
POST http://localhost:11434/api/chat { "model": "qwen2.5:7b", "messages": [ { "role": "system", "content": "你是助手" }, { "role": "user", "content": "你好" }, { "role": "assistant", "content": "你好!" }, { "role": "user", "content": "再来一首诗" } ], "stream": false, "format": "json", "options": { "temperature": 0.7 }, "keep_alive": "5m", "tools": [] }
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
model | string | ✅ | 同上 |
messages | array | ✅ | 对话数组,元素{role, content};role为system/user/assistant/tool |
stream | bool | ❌ | 同上 |
format | string | ❌ | 同上("json"或 Schema) |
options | object | ❌ | 同上 |
keep_alive | 同上 | ❌ | 同上 |
tools | array | ❌ | 工具调用(function calling)定义列表 |
tool_choice | string | ❌ | 工具选择策略:"none"/"auto"/"required"或指定工具名 |
三、流式与非流式差异
非流式(stream: false)响应:一次返回完整 JSON
{ "model": "qwen2.5:7b", "created_at": "2026-10-28T...", "message": { "role": "assistant", "content": "整段回复" }, "done": true, "done_reason": "stop", // stop / length / tool_calls "total_duration": 123456, "load_duration": 5000, "prompt_eval_count": 20, "eval_count": 80, "eval_duration": 100000 }
流式(stream: true)响应:每行一个 JSON,content是增量
{"model":"qwen2.5:7b","created_at":"...","message":{"role":"assistant","content":"你"},"done":false} {"model":"qwen2.5:7b","created_at":"...","message":{"role":"assistant","content":"好"},"done":false} ... {"model":"qwen2.5:7b","created_at":"...","message":{"role":"assistant","content":""},"done":true,"done_reason":"stop"}
解析时按行读,判断 done 是否为 true。
四、多模态(图片,generate和chat通用)
{ "model": "llava", "messages": [ { "role": "user", "content": "这张图里有什么?", "images": ["iVBORw0KGgoAAAANSUhEUgAA..."] // base64 图片 } ] }
五、options推理参数(最常用)
"options": { "temperature": 0.8, // 温度,0~2,越低越确定 "top_p": 0.9, // 核采样,0~1 "top_k": 40, // 候选词数量 "num_predict": 2048, // 最大生成 token 数(-1 无限,-2 填满上下文) "stop": ["\n", "###"], // 停止生成的自定义字符串 "repeat_penalty": 1.1, // 重复惩罚 "num_ctx": 4096, // 上下文窗口长度 "seed": 42, // 随机种子(复现结果) "num_thread": 4, // CPU 线程数 "num_gpu": 1, // GPU 层数(-1 自动) "presence_penalty": 0.0, // 话题新鲜度惩罚 "frequency_penalty": 0.0 // 频率惩罚 }
六、几个易错点
num_ctx决定记忆长度:默认 4096,想聊长对话可改成 8192 或 16384,但更吃显存。format: "json"并不保证模型永远输出合法 JSON,建议在代码里再做一次 try/parse 容错。keep_alive别设-1:模型常驻内存,长时间不用会白白占用显存;设"5m"或"10m"更合理。- 流式响应按行读取,用
ReadLineAsync而不是整段ReadToEnd。 context是generate的老写法,新项目统一用messages数组管理多轮对话,不要混用。
七、C# Demo
WinForms 里最核心的坑是线程:流式和非流式都要避免阻塞 UI 线程。
拖这几个控件:
Button btnStream(流式对话)Button btnNonStream(非流式对话)RichTextBox rtbOutput(显示回复)ComboBox cmbModel(模型下拉)TextBox txtInput(输入框,Multiline)
二、Ollama 封装类(流式 + 非流式)
csharp
运行
using System.Net.Http; using System.Text; using System.Text.Json; public class OllamaClient { private readonly HttpClient _http; private readonly string _baseUrl; public OllamaClient(string baseUrl = "http://localhost:11434") { _baseUrl = baseUrl.TrimEnd('/'); _http = new HttpClient { Timeout = TimeSpan.FromMinutes(10) }; } // ========== 非流式:一次拿完整回复 ========== public async Task<string> ChatAsync(string model, string prompt, string system = null) { var body = new { model, messages = BuildMessages(prompt, system), stream = false }; var json = JsonSerializer.Serialize(body); var resp = await _http.PostAsync($"{_baseUrl}/api/chat", new StringContent(json, Encoding.UTF8, "application/json")); resp.EnsureSuccessStatusCode(); var content = await resp.Content.ReadAsStringAsync(); using var doc = JsonDocument.Parse(content); return doc.RootElement.GetProperty("message").GetProperty("content").GetString(); } // ========== 流式:逐字回调(回调在后台线程) ========== public async Task ChatStreamAsync(string model, string prompt, string system, Action<string> onToken, CancellationToken ct) { var body = new { model, messages = BuildMessages(prompt, system), stream = true }; using var req = new HttpRequestMessage(HttpMethod.Post, $"{_baseUrl}/api/chat") { Content = new StringContent(JsonSerializer.Serialize(body), Encoding.UTF8, "application/json") }; using var resp = await _http.SendAsync(req, HttpCompletionOption.ResponseHeadersRead, ct); resp.EnsureSuccessStatusCode(); await using var stream = await resp.Content.ReadAsStreamAsync(ct); using var reader = new StreamReader(stream); while (!reader.EndOfStream) { var line = await reader.ReadLineAsync(ct); if (string.IsNullOrWhiteSpace(line)) continue; using var doc = JsonDocument.Parse(line); var token = doc.RootElement.GetProperty("message").GetProperty("content").GetString(); if (!string.IsNullOrEmpty(token)) onToken(token); // 回调切回 UI 线程由调用方处理 if (doc.RootElement.GetProperty("done").GetBoolean()) break; } } private static object[] BuildMessages(string prompt, string system) { var list = new List<object>(); if (!string.IsNullOrEmpty(system)) list.Add(new { role = "system", content = system }); list.Add(new { role = "user", content = prompt }); return list.ToArray(); } }三、WinForms 事件调用(重点:线程处理)
csharp
运行
public partial class Form1 : Form { private readonly OllamaClient _ollama = new OllamaClient("http://localhost:11434"); private CancellationTokenSource _cts; public Form1() { InitializeComponent(); LoadModels(); // 启动时加载模型列表 } // 启动时填充模型下拉 private async void LoadModels() { try { var json = await _ollama.GetModelsJsonAsync(); // 见下方补充 using var doc = JsonDocument.Parse(json); foreach (var m in doc.RootElement.GetProperty("models").EnumerateArray()) cmbModel.Items.Add(m.GetProperty("name").GetString()); if (cmbModel.Items.Count > 0) cmbModel.SelectedIndex = 0; } catch { cmbModel.Items.Add("qwen2.5:7b"); } } // ========== 非流式:异步等待即可,无需额外处理 ========== private async void btnNonStream_Click(object sender, EventArgs e) { SetBusy(true); try { rtbOutput.AppendText("\n>>> 非流式请求...\n"); var answer = await _ollama.ChatAsync(cmbModel.Text, txtInput.Text); rtbOutput.AppendText(answer + "\n"); } catch (Exception ex) { rtbOutput.AppendText("\n[错误] " + ex.Message + "\n"); } finally { SetBusy(false); } } // ========== 流式:回调里 Invoke 切回 UI 线程 ========== private async void btnStream_Click(object sender, EventArgs e) { _cts?.Cancel(); _cts = new CancellationTokenSource(); SetBusy(true); try { rtbOutput.AppendText("\n>>> 流式输出:\n"); await _ollama.ChatStreamAsync( cmbModel.Text, txtInput.Text, null, token => { // 回调在后台线程,必须 Invoke 回 UI 线程才能改控件 rtbOutput.Invoke((MethodInvoker)(() => rtbOutput.AppendText(token))); }, _cts.Token); rtbOutput.Invoke((MethodInvoker)(() => rtbOutput.AppendText("\n[完成]\n"))); } catch (OperationCanceledException) { rtbOutput.Invoke((MethodInvoker)(() => rtbOutput.AppendText("\n[已取消]\n"))); } catch (Exception ex) { rtbOutput.Invoke((MethodInvoker)(() => rtbOutput.AppendText("\n[错误] " + ex.Message + "\n"))); } finally { SetBusy(false); } } private void SetBusy(bool busy) { btnStream.Enabled = btnNonStream.Enabled = !busy; } }