进阶 📋 7 个步骤 第 426 / 470 篇

用 Ollama 跑本地工具调用 Agent:Qwen3 8B + OpenAI 兼容接口,数据不出本机

本地工具调用 Agent 实操:Ollama 安装与模型选择、OpenAI 兼容端点验证、手写 function calling 循环、三条护栏、ollama launch 接入编码 Agent 与性能调优。

2026.09.14· 26 分钟阅读· 约 2102 字· 🦙 Ollama / 🧠 本地模型

云端模型有一个绕不开的性质:你的代码和数据要离开本机。对很多团队来说这不是洁癖,是硬要求。好在 2026 年的本地推理已经足够可用——Ollama 现在既能提供 OpenAI 兼容接口,也能用一条命令把本地模型直接接进主流编码 Agent。这篇教程带你搭一个真正会调用工具的本地 Agent:模型在你机器上跑,工具在你机器上执行,数据不出本机。

🎯 适合人群:有隐私/合规要求、或者想省掉按 token 计费的开发者。需要一台内存 16GB 起的电脑(Mac / Windows / Linux 均可)。不需要 GPU,但 Apple Silicon 或独立显卡会让体验好很多。

先理解:本地 Agent 的三个组成部分

别把它想复杂,本地 Agent 就是三件事拼起来:

组件在本教程里是什么作用
推理引擎Ollama(默认监听 11434 端口)把模型跑起来,并提供 API
模型Qwen3 8B 这类支持工具调用的开源模型理解任务、决定调用哪个工具
工具循环你自己写的那几十行代码执行工具、把结果回填、判断是否继续

很多人以为「本地模型不行」,其实是第三块没搭好。工具循环写对了,小模型也能干成事。

Step 1:装 Ollama 并拉一个会调工具的模型

1 装完就一条命令拉模型
# macOS / Linux
curl -fsSL https://ollama.com/install.sh | sh

# Windows(PowerShell)
irm https://ollama.com/install.ps1 | iex

# 也可以直接用官方 Docker 镜像 ollama/ollama

# 拉一个支持工具调用的模型(8B 在 16GB 机器上是甜点区)
ollama pull qwen3:8b

# 确认拉好了
ollama list

不是所有本地模型都支持工具调用协议。选模型前先确认它声明了 tool calling 能力。另外 Qwen3 属于混合推理模型,每次工具调用前都会先「想一遍」,这会明显拖慢循环速度——后面 Step 7 会讲怎么关掉它。

Step 2:确认 OpenAI 兼容端点可用

2 先用 curl 打通,再写代码

Ollama 默认在 11434 端口提供两种接口:原生 /api/chat,以及 OpenAI 兼容的 /v1/chat/completions。用后者意味着你以后想切回云端,代码几乎不用改。

curl http://localhost:11434/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "qwen3:8b",
    "messages": [{"role": "user", "content": "用一句话介绍你自己"}]
  }'
💡 看到返回 JSON 就说明服务通了。如果连不上:dmg 安装的版本会自动在后台起服务;用 Homebrew 装的要自己执行 ollama serve。端口被占用的话,检查是不是已经有一个实例在跑。

Step 3:跑通最小对话,并验证工具调用真的可用

3 别跳过这一步
pip install ollama

# minimal.py
from ollama import chat

r = chat(
    model='qwen3:8b',
    messages=[{'role': 'user', 'content': '用 Python 写一个快速排序'}],
)
print(r.message.content)

对话能跑通不代表工具调用能跑通——这是两件事。用一个带工具定义的最小请求确认一次:

tools = [{
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "查询某个城市的当前天气",
        "parameters": {
            "type": "object",
            "properties": {"city": {"type": "string"}},
            "required": ["city"],
        },
    },
}]

r = chat(model='qwen3:8b',
         messages=[{'role': 'user', 'content': '北京现在天气怎么样?'}],
         tools=tools)

print(r.message.tool_calls)   # 期望:返回一个 get_weather 调用,而不是普通文本

如果 tool_calls 是空的,说明这个模型或这个量化版本没走通工具协议。换个模型标签重试,别急着往下写循环——循环写得再漂亮,模型不发起调用也是白搭。

Step 4:手写一个工具调用循环

4 整个 Agent 的核心就这一个 while

循环的逻辑只有五步:让模型决策 → 执行工具 → 把结果追加回消息 → 再问模型 → 直到模型给出最终回答。关键是要有步数上限,否则模型可能反复调同一个工具。

import json, ollama

TOOLS_SCHEMA = [/* 同 Step 3 的工具定义 */]
MAX_STEPS = 12

def execute(name, args):
    if name == "get_weather":
        return {"city": args["city"], "temp_c": 24, "condition": "晴"}
    raise ValueError("未授权的工具: " + name)   # 白名单兜底

def run(goal):
    messages = [{"role": "user", "content": goal}]
    for step in range(MAX_STEPS):
        r = ollama.chat(model='qwen3:8b', messages=messages, tools=TOOLS_SCHEMA)
        msg = r.message

        if not msg.tool_calls:            # 没有工具调用 = 这是最终回答
            return msg.content

        messages.append(msg)
        for call in msg.tool_calls:
            name = call.function.name
            args = call.function.arguments or {}
            print("[step %d] 调用 %s(%s)" % (step, name, args))
            result = execute(name, args)
            messages.append({
                "role": "tool",
                "content": json.dumps(result, ensure_ascii=False),
            })
    return "达到最大步数上限,已中止(请检查是否陷入循环)"

print(run("查一下北京的天气,再用一句话总结"))
💡 注意 messages.append(msg) 这一步——它把模型的工具调用意图也写进了历史。少了这行,下一轮模型就看不到自己刚才发起了什么调用,会一直重复同一个动作。这是手写循环最常见的 Bug。

Step 5:加三条护栏

5 让 Agent 能干活,但不能乱干
护栏做法为什么必要
工具白名单execute() 里对未声明工具直接抛错防止模型编造出一个你没实现的工具名
只读优先先只给读类工具;写类工具要求显式确认小模型对「该不该动手」的判断不如大模型稳
全程留痕每步记录工具名、参数、结果、步数出错时你能复盘的东西就只有这份日志

别让本地模型直接操作生产系统。「本地」只解决了数据不出机器的问题,不解决判断力问题。删库、转账、发邮件这类不可逆动作,无论模型跑在哪里,都应该走人工确认。

Step 6:一条命令把本地模型接进编码 Agent

6 ollama launch 是最省事的路径

这是 2026 年本地部署最实用的新玩法:Ollama 内置了 launcher,一条命令就能把本地模型接进主流编码 Agent,不用手工配环境变量。

ollama launch claude    --model qwen3:8b   # Claude Code
ollama launch opencode  --model qwen3:8b   # OpenCode
ollama launch openclaw  --model qwen3:8b   # OpenClaw

# 支持的集成还包括 Codex、Copilot CLI、Droid 等

结果就是:编码 Agent 的交互体验不变,但推理跑在你自己的机器上——不按 token 计费,代码也不出本机。

Step 7:手工配置与性能调优

7 四个参数决定体验好坏

如果你想手动接 Claude Code,核心是让请求指向本地端点。有三个细节新手最容易踩:

export ANTHROPIC_BASE_URL=http://127.0.0.1:11434
export ANTHROPIC_API_KEY=""
export ANTHROPIC_AUTH_TOKEN=ollama    # 必须写 ollama,留空会连不上
export ANTHROPIC_MODEL=qwen3:8b
export ANTHROPIC_DEFAULT_HAIKU_MODEL=qwen3:8b   # 摘要也用同一个模型,
                                                # 避免同时加载两个模型占满显存
export MAX_THINKING_TOKENS=0          # 关掉 Qwen3 的思考过程,
                                      # 否则每次工具调用前都要等它"想"
export DISABLE_TELEMETRY=1
export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1

想让它长期生效,就把这些写进 ~/.claude/settings.json 的 env 段。跑起来之后用这条命令看性能:

ollama ps
# NAME          SIZE     PROCESSOR   UNTIL
# qwen3:8b      5.2 GB   100% GPU    4 minutes from now

# PROCESSOR 显示 100% GPU = 加速生效
# 若 CPU 占比很高,说明没吃上显卡加速,速度会差好几倍

设定正确预期,比调参更重要。① 本地模型的多步推理能力仍不如云端前沿模型,跨文件的复杂架构调整是它的弱项;② 小模型更容易给出「看起来对但其实错」的答案,用在大仓库上尤其要谨慎;③ Ollama 的强项是简单,不是吞吐——多人并发场景请换 vLLM 之类的方案;④ 改端点意味着所有代码都流经本机,如果这台机器是共享的,隐私优势就不成立了。

常见问题速查

你遇到的现象大概率原因 & 解决
模型返回 404 / not found模型标签写错。先 ollama list 看准确名称再填
连不上 11434服务没起。dmg 版会自动起;Homebrew 版要手动 ollama serve
工具调用一直不触发模型不支持工具协议,或量化版本丢了该能力。换模型标签重试
Agent 反复调同一个工具忘记把模型的 tool_calls 消息追加进历史;或步数上限设得太高
回答慢到无法忍受关掉 thinking(MAX_THINKING_TOKENS=0),或换更小一档模型
速度远低于预期ollama ps 看 PROCESSOR 列,确认 GPU 加速是否生效
← 返回教程中心