第 2 课

llama serve:OpenAI 兼容的本地 API

用 llama serve 起服务并用 /health 判断就绪,用 curl 发请求时看懂思考模型回答为空、finish_reason 为 length 的原因,读 usage、timings 与提示词缓存,用 OpenAI Python SDK 调用和流式输出,设 --api-key 并对照 401,再用 Anthropic /v1/messages 和 /tokenize、/props 原生接口调试。

图文24 分钟

课程目录第 2 / 3 课

学习位置仅保存在当前浏览器,有效期 180 天。

01 / 图文教材

图文教材

本课依据:Llama 官方文档「Running a server」https://llama.app/docs/serve 、「API server」https://llama.app/docs/api 、「Web UI」https://llama.app/docs/webui (2026-10-27 核对,llama build b11852)。

这一课做完你会得到什么

用 llama serve 在本机起一个 OpenAI 兼容的 API 服务,用 curl 和 OpenAI 官方 Python SDK 调用它,学会流式输出、看用量和速度、设访问密钥,再用 Anthropic 格式调一次同一个模型。以后任何支持「自定义 Base URL」的应用都能接上你的本地模型。

本课继续用第 1 课下载的 ggml-org/Qwen3.5-0.8B-GGUF:Q8_0,内存够的话换成官方示例的 ggml-org/gemma-4-e4b-it-GGUF:Q4_0 效果更好。

第 1 步:启动服务

llama serve -hf ggml-org/Qwen3.5-0.8B-GGUF:Q8_0 \
  -c 8192 -a qwen3.5-0.8b

三个参数的意思:

参数作用
-c 8192上下文 8192 token;-c 0 用模型支持的最大值
-a qwen3.5-0.8bAPI 里报告的模型名,调用时好写
--port换端口,默认 9931

看到 listening on http://127.0.0.1:9931 就是起来了。服务默认只监听本机(127.0.0.1),想让局域网其他设备访问才加 --host 0.0.0.0,这时务必同时设第 5 步的 API Key。

另开一个终端,检查服务是否就绪:

curl http://localhost:9931/health

对照结果(2026-10-27 实测):

{"status":"ok"}

模型还在加载时这个接口返回 HTTP 503,加载完返回 200。写启动脚本时可以循环等到 200 再发请求:

until curl -sf http://localhost:9931/health; do sleep 1; done

第 2 步:用 curl 发第一个请求

把请求体存成 ask.json:

{
  "model": "qwen3.5-0.8b",
  "messages": [
    {"role": "system", "content": "你是简洁的助手。"},
    {"role": "user", "content": "列出本地跑大模型的 3 个好处。"}
  ],
  "max_tokens": 200
}

发送:

curl -s http://localhost:9931/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d @ask.json

对照结果(2026-10-27 实测,只摘关键字段):

finish_reason: length
content: ''
reasoning_content: Thinking Process:  1.  **Analyze the Request:** ...

回答是空的。原因:Qwen 3.5 默认先思考,思考内容放在 message.reasoning_content 里,和最终回答 message.content 分开返回;200 个 token 全花在思考上,还没开始回答就到上限了(finish_reason: length)。

两种解决办法:调大 max_tokens,或者对不需要思考的任务直接关掉思考。按 Ctrl+C 停掉服务,加上 -rea off 重启:

llama serve -hf ggml-org/Qwen3.5-0.8B-GGUF:Q8_0 \
  -c 8192 -a qwen3.5-0.8b -rea off

再发一次同样的请求,对照结果(2026-10-27 实测,节选):

finish_reason: stop
本地跑大模型能带来三大核心价值:

1. **数据隐私与安全性** ……
2. **算力成本与延迟更低** ……
3. **可控性与定制化** ……
usage: {'prompt_tokens': 33, 'completion_tokens': 152,
        'total_tokens': 185}
timings: {'cache_n': 0, 'prompt_per_second': 229.1,
          'predicted_per_second': 44.4}

usage 是标准的 OpenAI 用量字段;timings 是 llama.cpp 额外给的速度统计。把同一个请求再发一次,cache_n 变成 29:服务端默认开启提示词缓存,前缀相同的部分(比如固定的系统提示词)不用重新计算。

cache_n: 29

第 3 步:用 OpenAI Python SDK 调用

官方 SDK 只需要改 base_url。没设密钥时 api_key 随便填一个非空字符串。新建 hello_local.py:

from openai import OpenAI

client = OpenAI(
    base_url="http://localhost:9931/v1",
    api_key="no-key-required",
)

resp = client.chat.completions.create(
    model="qwen3.5-0.8b",
    messages=[{"role": "user", "content": "用一句话介绍北京。"}],
    max_tokens=80,
)
print(resp.choices[0].message.content)
print(resp.usage)

用 uv 临时装上 openai 包运行(也可以先 pip install openai 再 python hello_local.py):

uv run --with openai python hello_local.py

对照结果(2026-10-27 实测,usage 一行有删节):

北京是中国的首都、政治、经济、文化、历史和科技创新中心,……
CompletionUsage(completion_tokens=40, prompt_tokens=17,
total_tokens=57, ...)

查看服务里有哪些模型,返回的 id 就是 -a 设的名字:

curl -s http://localhost:9931/v1/models

对照结果(2026-10-27 实测,只取 id):

['qwen3.5-0.8b']

第 4 步:流式输出

加 stream=True,一边生成一边打印。新建 stream.py:

from openai import OpenAI

client = OpenAI(base_url="http://localhost:9931/v1", api_key="x")

stream = client.chat.completions.create(
    model="qwen3.5-0.8b",
    messages=[{"role": "user", "content": "从 1 数到 5,用逗号分隔。"}],
    stream=True,
)
for chunk in stream:
    if chunk.choices and chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="", flush=True)
print()
uv run --with openai python stream.py

对照结果(2026-10-27 实测):

1, 2, 3, 4, 5

第 5 步:设访问密钥

服务对外开放前一定要加密钥。停掉服务,加上 --api-key 重启(多个密钥用逗号分隔):

llama serve -hf ggml-org/Qwen3.5-0.8B-GGUF:Q8_0 \
  -a qwen3.5-0.8b -rea off --api-key sk-local-123

不带密钥请求会被拒绝:

curl -s http://localhost:9931/v1/models

对照结果(2026-10-27 实测,HTTP 401,为排版折成两行):

{"error":{"message":"Invalid API Key",
"type":"authentication_error","code":401}}

带上 Bearer 头就正常返回 200:

curl -s http://localhost:9931/v1/models \
  -H "Authorization: Bearer sk-local-123"

Python 里把 api_key="sk-local-123" 传给 OpenAI(...) 即可。

注意:这次没写 -c,启动日志里每个槽位的上下文变成了 n_ctx_slot = 262144,即模型支持的最大长度。llama.cpp 会按可用内存自动调整,但长上下文占的内存更多,内存紧张的机器建议明确写 -c。

第 6 步:Anthropic 格式和原生接口

同一个服务还提供 Anthropic Messages 格式的 /v1/messages,用 Anthropic SDK 写的应用也能直接接:

curl -s http://localhost:9931/v1/messages \
  -H "Content-Type: application/json" \
  -H "x-api-key: any" \
  -d '{"model": "qwen3.5-0.8b", "max_tokens": 200,
       "messages": [{"role": "user",
                     "content": "用一句话介绍 llama.cpp。"}]}'

对照结果(2026-10-27 实测,未设 --api-key 的服务,节选):

type: message  stop_reason: end_turn
usage: {'input_tokens': 18, 'output_tokens': 45}
Llama.cpp 是用于部署 Llama 3 模型的开源 C++ 版本,……

返回格式是 Anthropic 的(type: message、stop_reason: end_turn)。回答内容不准确:llama.cpp 并不只服务 Llama 3。这又是小模型的事实性问题,不是接口问题。

llama.cpp 还有一组原生接口,调试时很有用:

# 看一段文字会被切成几个 token
curl -s http://localhost:9931/tokenize \
  -H "Content-Type: application/json" \
  -d '{"content": "你好,llama.cpp"}'

# 查看服务和模型属性
curl -s http://localhost:9931/props

对照结果(2026-10-27 实测,/props 只摘 4 个字段):

{"tokens":[109266,3709,636,2933,6982]}
total_slots: 4  model_alias: qwen3.5-0.8b  n_ctx: 8192
modalities: {'vision': False, 'video': False, 'audio': False}

total_slots: 4 表示能同时处理 4 个请求;要调整用 -np,例如 -np 2。

第 7 步:浏览器里的聊天界面

本节按官方 Web UI 文档描述,未在云主机的浏览器里实测。服务起来后,用浏览器打开 http://localhost:9931 就是内置的聊天界面:支持 Markdown、公式、代码高亮,思考型模型的思考过程会折叠显示;可以拖入图片、PDF 或音频(要模型本身支持);可以编辑历史消息分叉对话,导入导出 JSON。只想要 API 不要网页,启动时加 --no-webui。

小结

  • llama serve -hf 仓库:量化 -a 别名 起服务,/health 返回 200 再发请求;
  • 回答为空先看 finish_reason 和 reasoning_content,用 -rea off 或调大 max_tokens;
  • OpenAI SDK 只改 base_url;对外开放必须加 --api-key;
  • 同一服务还能用 Anthropic /v1/messages 格式调用。

下一课做结构化输出、工具调用、向量检索,并把本地模型接到编程智能体上。