AB
AiBoss
チュートリアル

Ollama

チュートリアル

用 PowerShell 调用 Ollama HTTP API:原生接口与 OpenAI 兼容接口实操

不安装任何额外库,只用 PowerShell 5.1 的 Invoke-RestMethod 就能调用 Ollama 的 HTTP API。本文覆盖版本探测、模型列表、文本生成、OpenAI 兼容的 /v1 接口,以及 JSON 编码、嵌套深度、首次加载耗时等容易踩的坑。

Ollama 除了提供命令行和图形界面,还在本机开了一个 HTTP 服务,任何能发 HTTP 请求的工具都能直接调用它。这意味着在 Windows 上,你不必安装 Python SDK、Node 客户端或任何第三方库,用系统自带的 PowerShell 就能完成模型查询、文本生成、对话补全这些操作。本文面向已经在本机跑起 Ollama、想用脚本把模型能力接进自动化流程的读者,讲清楚两套接口——Ollama 原生的 /api 系列和 OpenAI 兼容的 /v1 系列——各自怎么调、返回什么、哪里容易出错。如果你更希望先了解这个工具本身的定位与安装方式,可以先看 Ollama 的工具页,再回到本文动手。

需要说明的是,下面所有示例都基于 PowerShell 的 Invoke-RestMethod 这一个 cmdlet,不涉及任何扩展模块。文中出现的版本号、模型体积、耗时数据都来自一次具体的验证环境,仅作参考;Ollama 迭代较快,接口字段和默认行为请以官网当前信息为准。

准备工作

运行环境

验证所用的环境如下,你可以对照检查自己的机器是否满足:

  • 操作系统:Windows 11(内部版本 10.0.26200)
  • Shell:Windows PowerShell 5.1(系统自带,无需安装 PowerShell 7)
  • Ollama:通过官方应用安装,服务端监听默认地址
  • 测试模型:qwen2.5:0.5b,一个体积很小的模型,适合做连通性验证

确认服务在监听

Ollama 安装完成后会以本机服务的形式运行,默认监听 127.0.0.1:11434。如果服务没有启动,后面所有请求都会直接连接失败。可以先在 PowerShell 里发一个最简单的版本查询请求,确认链路通畅:

(Invoke-RestMethod http://127.0.0.1:11434/api/version).version

这条命令返回一个版本号字符串,例如 0.40.4。能拿到版本号,说明服务已就绪、端口可达、PowerShell 的 HTTP 调用也正常。如果报连接被拒绝,先检查 Ollama 是否在运行,而不是去改脚本。

准备一个模型

后续示例都使用 qwen2.5:0.5b。你需要先在本地拉取该模型,或者把示例里的模型名换成自己机器上已有的任意模型。模型名写错时,接口会返回错误而不是静默失败,所以第一次跑之前最好先用下一节的模型列表接口核对一遍名字。

操作步骤

第一步:列出磁盘上的模型

原生接口 /api/tags 返回的是本地磁盘上已经存在的模型清单。请求它并只挑出关心的字段:

(Invoke-RestMethod http://127.0.0.1:11434/api/tags).models | Select-Object name, size

返回结果形如:

name              size
----              ----
qwen2.5:0.5b      397821319

这里的 size 单位是字节,不是 GB 也不是 MB,需要自己换算。返回的 models 是一个数组,除了 name 和 size,每个元素还带有修改时间、摘要等字段,用 Select-Object 可以按需裁剪。

容易混淆的一点:查询「磁盘上有哪些模型」要用 /api/tags,而不是 /api/ps。后者对应的是当前已加载进内存的模型,两者含义不同。刚启动服务时 /api/ps 很可能是空的,但这不代表你没有模型。

第二步:用原生接口做文本生成

/api/generate 是最直接的生成接口,输入一段提示词,返回模型输出。关键在于把 stream 显式设为 false,这样服务端会等生成全部结束后一次性返回完整 JSON,而不是逐块推送。

构造请求体时用哈希表,再交给 ConvertTo-Json 序列化:

$body = @{
    model   = "qwen2.5:0.5b"
    prompt  = "Reply with exactly: connection-ok"
    stream  = $false
    options = @{
        num_predict = 24
    }
} | ConvertTo-Json -Depth 5

$r = Invoke-RestMethod -Uri http://127.0.0.1:11434/api/generate `
    -Method Post `
    -Body $body `
    -ContentType "application/json; charset=utf-8" `
    -TimeoutSec 120

$r.response

返回的 response 字段就是模型生成的文本。上面这个提示词要求模型原样输出 connection-ok,因此返回结果应当就是这一串字符,很适合用来判断整条链路是否打通。

除了生成文本本身,响应里还带有若干统计字段,其中比较有用的是:

  • eval_count:本次生成的 token 数量
  • total_duration:整个请求的总耗时,单位是纳秒

这些字段可以用来粗略评估模型在当前机器上的响应速度。options 里的 num_predict 限制生成的最大 token 数,把它设小一些可以让验证请求更快返回,避免模型长篇输出。

第三步:调用 OpenAI 兼容接口

Ollama 同时提供了一套与 OpenAI API 字段名一致的接口,挂在 /v1 路径下。如果你手上已经有基于 OpenAI 格式写的客户端或脚本,想直接指向本地 Ollama,用这一套最省事。

先看模型列表,字段名是 data,每个元素里有 id:

(Invoke-RestMethod http://127.0.0.1:11434/v1/models).data.id

再做一次对话补全。注意这里用的是 messages 数组而不是单个 prompt,长度限制参数叫 max_tokens 而不是 num_predict:

$body = @{
    model    = "qwen2.5:0.5b"
    messages = @(
        @{
            role    = "user"
            content = "What is 1+1? Answer with just the number."
        }
    )
    max_tokens = 16
} | ConvertTo-Json -Depth 5

$r = Invoke-RestMethod -Uri http://127.0.0.1:11434/v1/chat/completions `
    -Method Post `
    -Body $body `
    -ContentType "application/json; charset=utf-8" `
    -TimeoutSec 120

$r.choices[0].message.content
$r.usage.completion_tokens

生成内容从 choices[0].message.content 取,token 用量从 usage.completion_tokens 取,这两条路径和 OpenAI 的返回结构一致。示例中模型返回了 1+1=2,用量字段返回 6。

两套接口的取舍可以这样理解:/api 系列是 Ollama 自己的原生格式,字段更贴近本地模型的运行细节(比如 options 里的采样参数、纳秒级的耗时统计);/v1 系列则是为了兼容既有生态,字段名与 OpenAI 对齐。写新脚本、想深入控制生成参数时用原生接口;复用现成的 OpenAI 客户端代码时用兼容接口。

一个完整示例

下面把前面的步骤串成一个可以直接粘贴进 PowerShell 5.1 执行的脚本。它依次完成:探测版本、列出模型、用原生接口生成一段文本、用兼容接口做一次对话,并在每一步打印关键结果。

$base = "http://127.0.0.1:11434"

# 1. 版本探测
$ver = (Invoke-RestMethod "$base/api/version").version
Write-Host "Ollama version: $ver"

# 2. 磁盘上的模型
$models = (Invoke-RestMethod "$base/api/tags").models
$models | Select-Object name, size | Format-Table

# 3. 原生生成接口
$genBody = @{
    model   = "qwen2.5:0.5b"
    prompt  = "Reply with exactly: connection-ok"
    stream  = $false
    options = @{ num_predict = 24 }
} | ConvertTo-Json -Depth 5

$gen = Invoke-RestMethod -Uri "$base/api/generate" `
    -Method Post `
    -Body $genBody `
    -ContentType "application/json; charset=utf-8" `
    -TimeoutSec 120

Write-Host "generate -> $($gen.response)"
Write-Host "eval_count = $($gen.eval_count), total_duration = $($gen.total_duration)"

# 4. OpenAI 兼容接口
$chatBody = @{
    model    = "qwen2.5:0.5b"
    messages = @(
        @{ role = "user"; content = "What is 1+1? Answer with just the number." }
    )
    max_tokens = 16
} | ConvertTo-Json -Depth 5

$chat = Invoke-RestMethod -Uri "$base/v1/chat/completions" `
    -Method Post `
    -Body $chatBody `
    -ContentType "application/json; charset=utf-8" `
    -TimeoutSec 120

Write-Host "chat -> $($chat.choices[0].message.content)"
Write-Host "completion_tokens = $($chat.usage.completion_tokens)"

把基础地址抽成变量 $base 的好处是,将来如果改了监听端口或换到另一台机器上的 Ollama,只需要改这一行。脚本里两处 -TimeoutSec 120 是刻意留得比较宽松的,原因见下一节。

注意事项

JSON 请求体要显式声明 charset=utf-8

当提示词里包含非 ASCII 字符(中文、日文、各种符号)时,请求体的编码方式会直接影响服务端能否正确解析。稳妥的做法是在 -ContentType 里明确写上 application/json; charset=utf-8,而不是只写 application/json。这样可以避开因环境默认编码不同而导致的乱码或解析失败,这类问题排查起来往往很费时间,因为报错信息未必指向编码。

ConvertTo-Json 的 -Depth 不能省

ConvertTo-Json 有一个默认的序列化深度,嵌套层级超过这个深度时,内层结构会被压平成字符串,而不是继续展开成 JSON 对象。像 messages 这种「数组里套哈希表」的结构,用默认深度转换出来的结果是不正确的,服务端会因此报参数错误。

解决办法是显式指定 -Depth 5。对于本文涉及的所有请求体,5 层已经足够;如果你自己构造的结构更深,就相应调大。

首次请求会明显变慢

模型第一次被调用时需要从磁盘加载进内存,这段时间会计入请求的总耗时。在验证环境里,即便是 qwen2.5:0.5b 这样的小模型,首次请求的 total_duration 也达到了约 30 秒;第二次及以后的请求会快得多,因为模型已经在内存里了。

这带来两个实践上的影响。一是不要把 -TimeoutSec 设得太短,否则首次调用会直接超时失败,而失败原因看起来像是接口不通。二是做性能对比时,要区分冷启动和热启动,否则数据没有可比性。

stream 的默认值是 true

/api/generate 默认以流式方式返回结果,也就是边生成边推送。如果你希望拿到一个完整的 JSON 对象再统一处理,必须在请求体里写 stream = $false。忘记写这一项时,Invoke-RestMethod 拿到的内容形态和预期不同,后续取 .response 就会出问题。这是初次接触该接口时最常见的疏漏之一。

区分 /api/tags 与 /api/ps

再强调一次:/api/tags 是磁盘上的模型清单,/api/ps 是当前已加载的模型。排查「模型明明下载了却调不通」这类问题时,先看 /api/tags 确认名字拼写,再看 /api/ps 确认加载状态,顺序不要颠倒。

版本与参数以官网为准

本文出现的版本号、模型体积、耗时数字都来自一次具体验证,会随 Ollama 版本和硬件条件变化。接口字段、默认值、可用参数也可能调整,实际使用前请以官网当前信息为准。

可以继续深入的方向

把 stream 设为 $true 并处理流式返回,可以实现逐字输出的效果;/api/chat 接口则适合维护多轮对话历史,把之前的消息按角色追加进 messages 数组即可。这两条路径都建立在本文已经跑通的基础之上,建议先把非流式调用调稳,再往上叠加。