# Ollama API：从你的代码调用本地模型

> Ollama API 是本机服务器在 `http://localhost:11434/api` 上提供的一组 Web 接口，不需要密钥。`/api/generate` 回答单个提示词，`/api/chat` 接收一组消息，两者默认都是流式输出。同样的模型还能在 `/v1` 上响应 OpenAI SDK，而 Modelfile 可以把系统提示词和参数保存成一个新的模型名。

Mindket 编辑部 · 发布于 2026-11-05 · 更新于 2026-11-05 · https://mindket.com/cn/learn/local-ai/ollama-api

## 要点

- 用 curl 或几行 Python，把提示词发到你自己的电脑上。
- 每次调用都带上历史记录，让对话接得下去。
- 把云服务的地址换成你的笔记本，客户端代码不用重写。
- 把常用的助手做成一个模型名，指令直接写在里面。

## 开始之前

你需要按第 1 课的做法让 Ollama 运行起来，并且硬盘上至少有一个模型。

示例用的是 `gemma4`，Ollama 的 API 示例大多也用它，所以先把它拉下来，或者换成你已有的任何标签。写代码还需要 Python 3 和两个包：`ollama` 和 `openai`。

这里说的 API，是一组接收 JSON、返回 JSON 的 Web 地址。本机服务器在 `11434` 端口上应答。来自本机的请求不需要密钥，而且服务器只监听 `127.0.0.1`，所以在你修改 `OLLAMA_HOST` 之前，别的电脑连不上它。

Ollama 没有给这个 API 做严格的版本管理。它的[介绍页面](https://docs.ollama.com/api/introduction)说，API 预期会保持稳定、向后兼容，官方也提供了 Python 和 JavaScript 两个库。

入口有两个，选哪个决定了你能用到什么。`/api` 下的原生路由开放了 Ollama 的全部能力，包括单次请求的参数、`keep_alive` 和思考控制。`/v1` 路由照搬 OpenAI 的格式，为 OpenAI 写的代码和工具不用改就能跑，只是少几个字段。

同一套 API 也在 Ollama 的云端运行。调用云端模型时，基础地址换成 `https://ollama.com/api`，每个请求都要带 `Authorization: Bearer` 请求头，里面是你在[账号设置](https://ollama.com/settings/keys)里拿到的 `OLLAMA_API_KEY`。

这个密钥只能放在服务器上，不能写进浏览器代码，也不能提交到代码仓库。下面的内容都用本机服务器，那里根本没有密钥可以泄露。

这套 API 是一步步长出来的，每一步都在 Ollama 的博客上公布过。早于某一步写成的教程不会提到它，所以拿教程的日期对照一下这张时间线。

````bash
ollama pull gemma4
pip install ollama openai
````

**Ollama API 各部分上线的时间**

1. **2024年2月8日** OpenAI 兼容。`/v1` 路由上线，为 OpenAI API 写的代码可以调用本地模型。
2. **2024年7月25日** 工具支持。`/api/chat` 接收 `tools`，支持工具的模型会用 `tool_calls` 回应。
3. **2024年12月6日** 结构化输出。`format` 可以接收 JSON schema，回复会按你定义的结构返回。
4. **2025年5月28日** 带工具调用的流式输出。模型调用工具时，回复也能继续流式输出。
5. **2026年1月16日** Anthropic API 兼容。从 v0.14.0 起，Claude Code 可以通过 Anthropic 风格的 Messages API 在 Ollama 上运行。
6. **2026年8月25日** 支持 Claude Desktop。在 Ollama 应用里选中 Claude 并打开开关，网关会自动配置好，本地模型和云端模型都能用。

来源：[Ollama blog: OpenAI compatibility](https://ollama.com/blog/openai-compatibility) (2026-11-05 查阅)；[Ollama blog: Tool support](https://ollama.com/blog/tool-support) (2026-11-05 查阅)；[Ollama blog: Structured outputs](https://ollama.com/blog/structured-outputs) (2026-11-05 查阅)；[Ollama blog: Streaming responses with tool calling](https://ollama.com/blog/streaming-tool) (2026-11-05 查阅)；[Ollama blog: Claude Code with Anthropic API compatibility](https://ollama.com/blog/claude) (2026-11-05 查阅)；[Ollama blog: Claude Desktop support with Ollama](https://ollama.com/blog/claude-desktop) (2026-11-05 查阅)

## 用 curl 发出第一个请求

从最简单的接口开始。`/api/generate` 接收一个提示词，返回一个回答。加上 `"stream": false`，服务器会等生成完，只发回一个 JSON 对象，第一次看会清楚很多。`/api/chat` 接收的则是一组消息，每条消息都有 `role` 和对应的 `content`。

两个接口都不记得之前的调用。用 `/api/chat` 时，每次都要发送整段对话：之前的问题、助手之前的回答，最后是新问题。漏掉哪一轮，模型就看不到哪一轮。每多一轮，提示词就更长一些，每次回复里的 `prompt_eval_count` 会显示它在增长。

两个接口能接收的不只是文字。`options` 对象放的是单次请求的设置：`temperature`、`top_p`、让输出可复现的 `seed`、`stop` 停止序列、限制回复长度的 `num_predict`，以及上下文长度 `num_ctx`。能看图的模型，可以在 `images` 里传入一组 base64 编码的文件。

思考型模型还多一个字段。用 `think` 可以要求模型给出推理过程，也可以关掉它。有些模型，比如 `gpt-oss`，接受的是 `"low"` 这样的档位，而不是 true 或 false。推理内容放在单独的 `thinking` 字段里，和回答分开。某个模型接受哪些值，可以问 `/api/show`。

只带模型名、别的什么都不带的请求也有用。它会载入模型但不生成内容，这样第一个真正的问题就不用等载入。

````bash
# is the server up, and which models does it have?
curl http://localhost:11434/api/tags

# one prompt, one answer
curl http://localhost:11434/api/generate -d '{
  "model": "gemma4",
  "prompt": "Why is the sky blue?",
  "stream": false
}'

# a conversation: send the messages so far
curl http://localhost:11434/api/chat -d '{
  "model": "gemma4",
  "messages": [
    {
      "role": "user",
      "content": "why is the sky blue?"
    }
  ],
  "stream": false
}'
````

1. 运行 `/api/tags` 那条命令。它返回一个 `models` 列表，里面有每个模型的名字、以字节计的硬盘占用，以及 `parameter_size` 和 `quantization_level` 等详情。这一步报错，说明服务器没在运行。
2. 运行 `/api/generate` 请求。回答在 `response` 字段里，`done` 为 `true`。
3. 运行 `/api/chat` 请求。回答这次在 `message.content` 里，`role` 是 `assistant`。
4. 把这条助手消息和一个追问追加到 `messages` 里，再发一次请求。模型现在能引用它自己的第一个回答了。
5. 从任一请求里删掉 `"stream": false`，再运行一次。回复会分成很多行到达，每行一个 JSON 对象，各带一段文字。

|  | `/api/generate` | `/api/chat` |
| --- | --- | --- |
| 你发送什么 | `prompt`，一个字符串 | `messages`，由 `role` 和 `content` 组成的列表 |
| 系统提示词 | `system` 字段 | 一条 role 为 `system` 的消息 |
| 之前的回合 | 没有，每次调用各自独立 | 你在 `messages` 里放了多少历史，就有多少 |
| 回答在哪里 | `response` | `message.content` |
| 额外输入 | `suffix` 用于中间填充模型，`raw` 跳过提示词模板 | `tools` 用于函数调用，结果用 `tool` 消息发回 |

## 从 Python 调用：Ollama 的库，或者 OpenAI SDK

官方 Python 库包装的是同一批接口。`chat()` 接收模型名和一组消息。加上 `stream=True`，调用返回的是一段段数据块，而不是一个完整回答，每块的 `message.content` 里是回复的下一段。

下面的循环一收到就打印，所以模型还没写完，前面的字就已经出来了。

流式输出是服务器的默认行为，格式是按行分隔的 JSON：每行一个完整的 JSON 对象，一有新文字就发出来。普通的数据块带着一段回答和 `done: false`。最后一块是 `done: true`，带有 `stop` 这样的 `done_reason`，以及整次运行的统计数据，[流式输出页面](https://docs.ollama.com/api/streaming)有示例。

速度方面的问题，大多能从这些统计里找到答案。所有时长都以纳秒计。常说的每秒 token 数，等于 `eval_count` 除以 `eval_duration`，再乘以十亿。token 是一小段文字，常常是半个词。

generate 参考文档完整列出了一次回复作为示例。它来自 `gemma4`，结尾是 `done_reason: stop`、`prompt_eval_count: 11` 和 `eval_count: 18`，也就是那个提示词用了十一个输入 token，回答用了十八个。

chat 只是众多调用之一。这个库还有 `generate`、`list`、`show`、`create`、`copy`、`delete`、`pull`、`push`、`embed` 和 `ps`，覆盖了命令行能做的事，所以脚本可以先拉取模型再使用它。

要连其他地址上的服务器，就自己建一个客户端：`Client(host='http://localhost:11434')` 可以传入地址和额外的请求头。

很多工具本来就用 OpenAI 的 API，而 Ollama 在 `/v1` 下应答同样的请求。你只改 base URL，其余代码照旧。客户端没有 API 密钥就不肯启动，Ollama 又会忽略你传的值，所以随便填一个字符串就行。

第二段代码就是兼容性页面自己的示例，只把模型换成了 `gemma4`。页面列出的路由有 `/v1/chat/completions`、`/v1/completions`、`/v1/models`、`/v1/embeddings` 和 `/v1/responses`，另外还有语音合成、转写和翻译的音频路由。

有几样功能不支持。Chat completions 不处理 `logprobs`、`tool_choice`、`logit_bias`、`user` 和 `n`，图片只能是 base64 数据，不能是 URL。Responses 路由是无状态的，所以 `previous_response_id` 和 `conversation` 用不了，每次都要重新发送历史。

OpenAI 的 API 也没有设置上下文长度的字段。变通办法是用 Modelfile 建一个 `num_ctx` 更大的新模型，任何 OpenAI 客户端都能在 `model` 里写上它的名字。

JavaScript 也一样。那边的 OpenAI 客户端用 `baseURL: "http://localhost:11434/v1/"` 和 `apiKey: "ollama"`，调用的其余部分都不用改。

为 Claude 写的代码也有路可走。Ollama 支持 Anthropic Messages API 的一部分，所以把 Anthropic 客户端的 base URL 设成 `http://localhost:11434` 就能用，客户端会自己在地址后面加上 `/v1/messages`。

````python
from ollama import chat

stream = chat(
  model='gemma4',
  messages=[{'role': 'user', 'content': 'Why is the sky blue?'}],
  stream=True,
)

for chunk in stream:
  print(chunk['message']['content'], end='', flush=True)

# the same model through the OpenAI SDK
from openai import OpenAI

client = OpenAI(
    base_url='http://localhost:11434/v1/',
    api_key='ollama',  # required but ignored
)

chat_completion = client.chat.completions.create(
    messages=[
        {
            'role': 'user',
            'content': 'Say this is a test',
        }
    ],
    model='gemma4',
)
print(chat_completion.choices[0].message.content)
````

| 字段 | 含义 |
| --- | --- |
| `prompt_eval_count` | 提示词里的输入 token 数 |
| `prompt_eval_cached_count` | 直接从缓存读取、没有重新处理的提示词 token 数 |
| `eval_count` | 模型生成的 token 数 |
| `load_duration` | 载入模型花的时间，冷启动时会体现在这里 |
| `prompt_eval_duration` | 处理未缓存的提示词 token 花的时间 |
| `eval_duration` | 生成回答花的时间 |
| `total_duration` | 整个请求花的时间 |

## 把系统提示词存进 Modelfile

Modelfile 是一个简短的文本文件，用你已有的模型构建一个新模型。`FROM` 指定基础模型。`PARAMETER` 设置选项，比如以 token 计的上下文长度 `num_ctx`，以及控制措辞随机程度的 `temperature`。`SYSTEM` 写的是系统提示词，发给新模型的每个请求都会带上它。

`ollama create` 读取这个文件并注册新名字。之后，凡是能写模型名的地方，新模型都能用：`ollama run`、`/api/chat`，还有 OpenAI SDK。SYSTEM 那一行和别的提示词没什么两样，提示词工程课程里的系统提示词那一课讲了怎样写好它。

从 Hugging Face 拉取的模型，用另一种方式做同样的设置：仓库里名为 `system` 的文件设定系统提示词，名为 `params` 的 JSON 文件设定参数。

[Modelfile 参考文档](https://docs.ollama.com/modelfile)还列了其他指令：`TEMPLATE` 设置完整的提示词模板，`MESSAGE` 写入消息历史，`LICENSE` 写许可证，`REQUIRES` 指定所需的最低 Ollama 版本。

````text
FROM gemma4
PARAMETER num_ctx 8192
PARAMETER temperature 0.3
SYSTEM You answer questions about the user's notes in plain English. If the notes do not say, answer that you do not know.
````

1. 把上面四行保存成一个空目录里的 `Modelfile`。
2. 运行 `ollama create notes-helper -f ./Modelfile`。
3. 运行 `ollama run notes-helper`，问一个你的笔记里不会有答案的问题。
4. 用 `ollama show --modelfile notes-helper` 打印你刚建好的模型。对 `gemma4` 运行同一条命令，能看到你的文件继承了哪些模板和参数。

## Georgi Gerganov 谈本地模型为什么看起来不好用

Georgi Gerganov 在构建 ggml-org，也就是 llama.cpp 所在的 GitHub 组织，同时是 Hugging Face 的工程师。三月，他在 [X 上](https://x.com/ggerganov/status/2038674698809102599)的一条回复里回答了一个关于本地模型的问题。

他推荐 Qwen 3.5，因为它的尺寸覆盖面广。接着他提醒，人们遇到的大多数问题并不在模型本身，而在运行框架、对话模板和提示词的拼法上。

他的建议是：先用你的硬件装得下的全精度模型，并弄清楚你的运行框架到底做了什么。他写道，把 Claude Code 或 Codex 接到一个本地模型上就指望奇迹发生，是行不通的。等效果出来了，再为速度去做量化。他自己测试时，大多数模型都选了 Q8_0 版本，以保住大部分原始质量。

这套 API 把模板交到了你手里。`/api/chat` 会把模型自己的对话模板套在你的消息上，而 `/api/generate` 的 `raw` 会跳过模板，所以除非你自己重建模板，否则别开 `raw`。`ollama show --modelfile` 会打印模型用的模板。本地模型做不好某个任务时，先读一读这个模板和你的客户端发出的请求，再去怪模型。

> 我认为大家的共识是，Qwen3.5 是一次跨越，所以目前我会推荐去试试它，因为它的尺寸范围适合各种设备。
>
> Georgi Gerganov, 构建 ggml-org，Hugging Face 工程师，引文为译文, [X](https://x.com/ggerganov/status/2038674698809102599) (2026-11-05 查阅)

[![Georgi Gerganov（@ggerganov）2026 年 3 月 30 日在 X 上回复本地模型的问题：他认为 Qwen3.5 是一次跨越，尺寸适合各种设备，接着开始讲人们在本地模型上遇到的主要问题。](https://mindket.com/blog/shots/x-ggerganov-local-models.webp?v=9109fffd4a60)](https://x.com/ggerganov/status/2038674698809102599)

Georgi Gerganov 在 X 上，2026 年 3 月 30 日

> X 官方嵌入卡片的截图，拍摄于 2026 年 11 月 5 日，时间为 UTC。X 用“Show more”截断了这条帖子。点击截图可到 X 上打开原帖。

## 常见错误

- 把流式回复当成一个 JSON 文档来解析。没有 `"stream": false` 时，响应体是很多个对象，每行一个，要逐行读取。
- 只把最新一条消息发给 `/api/chat`。服务器不保存对话，你漏掉哪一轮，模型就忘了哪一轮。
- 在网页或浏览器扩展里调用 API，结果被拦下。Ollama 默认只接受来自 `127.0.0.1` 和 `0.0.0.0` 的浏览器请求，其他来源要加进 `OLLAMA_ORIGINS`，比如 `chrome-extension://*`。
- 不看 `done_reason`。被 `num_predict` 限住的回复可能停在半句话上，而 `done_reason` 正是说明回复为什么结束的字段。
- 批量任务跑到一半，模型被卸载了。每个请求都可以带 `keep_alive`：负数让模型一直驻留，`0` 让它在回答后马上卸载。
- 把云端密钥放进桌面应用或网页里。调用 `https://ollama.com/api` 需要 `OLLAMA_API_KEY`，所以要从你自己的服务器发这些请求。
- 只在提示词里要求输出 JSON，然后碰运气。把 `format` 设成 `"json"`，或者设成一个 JSON schema 对象，回复就会按这个结构返回。
- 在 `/v1` 代码里写了一个 `ollama ls` 列表里没有的模型名。本机服务器只能运行硬盘上的模型，所以解决办法是 `ollama pull`，而不是换一个密钥。

## 自己试一试

- 把同一个提示词发给 `/api/generate` 两次，一次带 `"stream": false`，一次不带。流式回复一共有多少行？
- 用某次响应里的 `eval_count` 和 `eval_duration` 算出每秒 token 数。
- 在 Python 里用库的 `list` 调用列出你的模型，并打印每个名字。
- 把 OpenAI 示例里的 `base_url` 在 Ollama 和某个云服务之间来回切换。哪几行变了？
- 在 Modelfile 里加上 `PARAMETER num_ctx`，重新创建模型，运行时看看 `ollama ps` 的 CONTEXT 列。
- 在请求里带上 `"keep_alive": 0`，看模型在回答之后马上从 `ollama ps` 里消失。
- 对一个思考型模型，把 `think` 分别打开和关掉，问同一个问题，对比 `thinking` 字段和回答。

> 接下来：LM Studio 在一个桌面应用里运行同样的本地服务器，自带模型浏览器，也有自己的 OpenAI 兼容接口。[第 3 课](https://mindket.com/cn/learn/local-ai/lm-studio-tutorial)会把它装好，并拿它的 `lms` 命令行和你现在熟悉的 Ollama 命令做对比。

## 自测一下

1. 非流式的 /api/chat 响应里，回答在哪里？

   A. response
   B. message.content
   C. choices[0].text
   D. done_reason

   答案：B。 chat 接口返回的是一条助手消息，所以文字在 message.content 里，/api/generate 用的才是 response。

2. 第二次调用 /api/chat 时，模型不记得第一个问题了，缺了什么？

   A. 更长的 keep_alive
   B. 一个 API 密钥
   C. messages 列表里之前的回合

   答案：C。 服务器不保存对话，模型要看到的历史，每次调用都得带上。

3. 用 OpenAI SDK 连 Ollama 时，API 密钥填什么？

   A. 随便一个字符串，Ollama 会忽略它
   B. 你的 OpenAI 密钥
   C. OLLAMA_HOST 的值
   D. 什么都不填，客户端不需要它也能启动

   答案：A。 客户端要求必须有一个值，而本机服务器会忽略它。

4. 怎样让 OpenAI 风格的客户端用上更长的上下文窗口？

   A. 在 OpenAI 请求里加 num_ctx
   B. 把 stream 设成 false
   C. 调高 max_tokens
   D. 用带 PARAMETER num_ctx 的 Modelfile 建一个模型

   答案：D。 OpenAI 的 API 没有上下文长度字段，所以这个设置要放进一个客户端能指定名字的新模型里。

## 常见问题

### 怎样在 PowerShell 里调用 Ollama API？

用 `Invoke-WebRequest`，Windows 页面的写法是：`(Invoke-WebRequest -method POST -Body '{"model":"llama3.2", "prompt":"Why is the sky blue?", "stream": false}' -uri http://localhost:11434/api/generate ).Content | ConvertFrom-json`。把 `llama3.2` 换成你拉取过的模型。请求体里要保留 `"stream": false`，因为 `ConvertFrom-json` 要的是一个 JSON 对象，而不是每行一个。

### 局域网里的其他电脑能用我的 Ollama API 吗？

能。用 `OLLAMA_HOST=0.0.0.0:11434` 让它绑定所有网卡，这个变量要设在应用或服务读取环境的地方，然后重启 Ollama。之后谁能连上这个端口，谁就能发请求，所以要加一条只放行你自己机器的防火墙规则。

### Ollama API 支持工具调用吗？

支持，通过 `/api/chat`。在 `tools` 里传入函数定义，回复里出现 `tool_calls` 时，运行对应的函数，再用 role 为 `tool` 的消息把结果发回去。chat 参考文档用 `qwen3` 和一个 `get_current_weather` 函数演示了这个过程。模型只是提出调用请求，要不要真的运行，由你的代码决定。

### Ollama API 能生成嵌入向量吗？

能。先拉取 `embeddinggemma` 这样的嵌入模型，再把文字发给 `/v1/embeddings`，或者调用 Python 库里的 `embed`。每段文字都会变成一串数字，这正是搜索索引存储的形式，RAG 课程就建立在它之上。

## 资料来源

- [Ollama Docs: API introduction](https://docs.ollama.com/api/introduction) (2026-11-05 查阅)
- [Ollama Docs: Generate a response](https://docs.ollama.com/api/generate) (2026-11-05 查阅)
- [Ollama Docs: Generate a chat message](https://docs.ollama.com/api/chat) (2026-11-05 查阅)
- [Ollama Docs: List models](https://docs.ollama.com/api/tags) (2026-11-05 查阅)
- [Ollama Docs: Streaming](https://docs.ollama.com/api/streaming) (2026-11-05 查阅)
- [Ollama Docs: OpenAI compatibility](https://docs.ollama.com/api/openai-compatibility) (2026-11-05 查阅)
- [Ollama Docs: Anthropic compatibility](https://docs.ollama.com/api/anthropic-compatibility) (2026-11-05 查阅)
- [Ollama Docs: Modelfile reference](https://docs.ollama.com/modelfile) (2026-11-05 查阅)
- [Ollama Docs: FAQ](https://docs.ollama.com/faq) (2026-11-05 查阅)
- [Ollama Docs: Windows](https://docs.ollama.com/windows) (2026-11-05 查阅)
- [GitHub: ollama/ollama-python](https://github.com/ollama/ollama-python) (2026-11-05 查阅)
- [Ollama blog: OpenAI compatibility](https://ollama.com/blog/openai-compatibility) (2026-11-05 查阅)
- [Ollama blog: Tool support](https://ollama.com/blog/tool-support) (2026-11-05 查阅)
- [Ollama blog: Structured outputs](https://ollama.com/blog/structured-outputs) (2026-11-05 查阅)
- [Ollama blog: Streaming responses with tool calling](https://ollama.com/blog/streaming-tool) (2026-11-05 查阅)
- [Ollama blog: Claude Code with Anthropic API compatibility](https://ollama.com/blog/claude) (2026-11-05 查阅)
- [Ollama blog: Claude Desktop support with Ollama](https://ollama.com/blog/claude-desktop) (2026-11-05 查阅)
- [Georgi Gerganov on X: the main issues with local models](https://x.com/ggerganov/status/2038674698809102599) (2026-11-05 查阅)

[浏览 MCP 服务器](https://mindket.com/cn/mcp-servers)
