不需要 API Key,不依赖网络,本地能跑的完整 Agent

上一篇讲了 Hermes 协议的由来和价值。这一篇把它落到地上——用 Ollama 跑 Hermes 3,本地搭一个真的能用工具的 Agent。全程不需要 API Key、不依赖外网,跑完这一篇你会有一个完整可复用的本地 Agent 骨架。

这对很多场景特别关键:企业敏感数据不能送云端、边缘设备需要本地推理、预算紧张不想烧 API 费用。开源 Agent 生态在 2025~2026 年真正进入"能用"阶段,很大程度上就是因为 Hermes 这类工具调用增强的开源模型补齐了最后一块拼图。

Nous Research 和 Hermes 家族

Nous Research 是一家开源模型微调方面非常活跃的小团队。他们不从头训练基础模型,而是在 Meta 的 Llama、阿里的 Qwen 等开源 base model 上做针对性后训练(SFT + DPO)。Hermes 是他们最知名的一条线。

Hermes 系列的特色是指令遵循 + 工具调用 + 无过度审查。Meta 发布的官方 Llama Instruct 版本在这三件事上都有明显短板:格式经常不守、调工具不稳、过度拒答。Hermes 就是对这些短板的针对性修正。

几个关键版本:

Hermes 2 Pro (2024 Q1)——基于 Llama 3 8B / 70B,引入了 <tool_call> 格式。第一个真正能稳定做工具调用的开源模型家族。

Hermes 3 (2024 下半年)——基于 Llama 3.1,工具调用和多轮对话显著提升,8B 小模型就能做实用 Agent。这是本篇的主角。

Hermes 4 (2025 年底)——基于 Llama 4,多模态、长上下文、推理能力加强。工具调用格式向后兼容 Hermes 3。

Nous 的发布节奏通常是 base model 出来 1~3 个月后跟进。到 2026 年,想跑本地 Agent,Hermes 几乎是默认选项。

为什么 Hermes 3 在开源 Agent 里重要

几个原因让它超越了其他"支持 function calling 的开源模型":

格式稳定性。大多数开源模型声称"支持 tool calling",真跑下来 JSON 经常打错(漏引号、漏 key、名字拼错)。Hermes 3 在这方面做了大量针对性训练,8B 模型的 JSON 有效率就已经接近 98%,70B 基本完美。

多步循环。不只是"能调一次工具",能在 10 轮、20 轮循环里持续用好工具。这对 Agent 至关重要——调一次靠运气,调二十次靠模型真的理解了协议。

生态兼容。Ollama、vLLM、LM Studio 都把 Hermes 的 <tool_call> 格式转译为 OpenAI tools API,你用 OpenAI SDK 直接调,模型是 Hermes 还是 GPT-4 对应用层完全透明。

环境搭建

# macOS / Linux
curl -fsSL https://ollama.com/install.sh | sh

# 拉一个 Hermes 3 的量化版本
# 8B 版本适合 16GB 内存的笔记本
ollama pull hermes3:8b

# 或者 70B,需要 40GB+ 显存或统一内存
ollama pull hermes3:70b

装好后,测试一下模型能不能起来:

ollama run hermes3:8b "你会用工具吗?"

模型应该回答"能,我可以调用工具"之类。如果回答听起来像普通闲聊、完全没提工具,那说明可能拉到了不带工具调用训练的版本,需要确认拉的是对的 tag。

用 OpenAI SDK 调 Hermes 3

关键一步:Ollama 在 11434 端口暴露一个 OpenAI 兼容的 API,你用熟悉的 OpenAI Python SDK 就能调。

from openai import OpenAI

client = OpenAI(
    base_url="http://localhost:11434/v1",
    api_key="ollama",  # 必须有个值,内容随便
)

resp = client.chat.completions.create(
    model="hermes3:8b",
    messages=[{"role": "user", "content": "写一首关于秋天的三句诗"}],
)
print(resp.choices[0].message.content)

跑通就说明链路通了。现在来真正的——工具调用

import json
from openai import OpenAI

client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")

def get_weather(city: str) -> str:
    # 真实场景接 API,这里给 mock
    data = {"北京": "15 度晴", "上海": "22 度多云", "广州": "28 度雨"}
    return data.get(city, f"{city}: 数据缺失")

def calculate(expression: str) -> str:
    return str(eval(expression))

TOOLS = {"get_weather": get_weather, "calculate": calculate}

TOOL_SCHEMAS = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "获取指定城市的天气",
            "parameters": {
                "type": "object",
                "properties": {"city": {"type": "string", "description": "城市名"}},
                "required": ["city"],
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "calculate",
            "description": "计算数学表达式",
            "parameters": {
                "type": "object",
                "properties": {"expression": {"type": "string"}},
                "required": ["expression"],
            },
        },
    },
]

def run_agent(question: str, max_steps: int = 8):
    messages = [
        {"role": "system", "content": "你是一个助手,可以调用工具。"},
        {"role": "user", "content": question},
    ]
    for step in range(max_steps):
        resp = client.chat.completions.create(
            model="hermes3:8b",
            messages=messages,
            tools=TOOL_SCHEMAS,
            tool_choice="auto",
        )
        msg = resp.choices[0].message
        messages.append(msg.model_dump(exclude_none=True))

        if not msg.tool_calls:
            return msg.content

        for tc in msg.tool_calls:
            args = json.loads(tc.function.arguments)
            result = TOOLS[tc.function.name](**args)
            messages.append({
                "role": "tool",
                "tool_call_id": tc.id,
                "content": result,
            })

    return "达到最大步数"

print(run_agent("北京和上海的温差是多少度?"))

运行后期望输出:

北京是 15 度,上海是 22 度,两地温差是 7 度。

模型在背后做了两件事:调 get_weather("北京")、调 get_weather("上海")(可能并行),然后调 calculate("22-15"),最后总结。

这段代码和用 GPT-4o 做 Agent 的代码几乎一模一样——只是 base_urlmodel 换了。整个开源生态能做到这个程度,就是因为 Hermes 协议和 Ollama 兼容层的配合。

本地模型 vs 云端模型的真实差距

说实话,Hermes 3 8B 的 Agent 能力比 GPT-4o 弱一档。具体体现在:

长链路任务——7 步以内 Hermes 3 很稳,到 10+ 步会开始漂移(忘记最初目标、重复调用已经成功的工具) 复杂参数——嵌套 JSON、数组参数的成功率下降明显,简单扁平参数基本没问题 错误恢复——工具返回错误时,闭源大模型更擅长"换一种方式再试",小模型容易放弃

但 70B 版本的差距就小得多,在大部分 Agent 场景已经能和 GPT-4o-mini 打平手,某些结构化任务甚至更稳定。如果你有足够显存(或者用 Groq、Together AI 这种开源模型托管),70B 是很好的选择。

何况它跑在你自己的机器上——零调用费用、零 API 延迟、完全可控。这些在生产决策里经常比"质量高 10%"重要得多。

调参:让 Hermes 3 更稳定

几个经过验证的经验值:

temperature 设 0.0 或 0.1。Agent 的工具调用需要确定性,creativity 是有害的。 top_p 0.9。配合低 temperature。 限制 max_tokens。每轮 2048 足够,避免模型在 Thought 里写长篇大论。 system prompt 开头强调格式。"所有工具调用必须严格遵循 JSON Schema,不要漏 required 字段,不要加 schema 外的字段。"

这些设置在 Ollama API 里都支持,通过 OpenAI SDK 的 temperaturetop_p 等参数传。

Hermes 3 vs 其他开源工具调用模型

2026 年开源世界能做 Agent 的选项不止 Hermes。简单对比:

Hermes 3 / 4 (Nous Research)——最通用的选择,生态最成熟,Ollama 一键跑 Qwen 3 Instruct (阿里)——中文任务强,工具调用用 <function> 格式,中文 Agent 首选 Llama 3 Groq Tool Use (Groq)——Groq 平台上推理超快,工具调用专项优化 Mistral Large 2 + Nemo——欧洲选项,合规导向场景 ToolACE 系列——专门为工具调用做的小模型(7B 级别),在 Berkeley Function Calling Leaderboard 上性能突出

选型经验:英文场景 + 通用 Agent,Hermes 3 8B 起步;中文场景,Qwen 3 14B 起步;工具超多(50+)的场景,ToolACE 可能更稳;需要最强质量,上 Hermes 3 70B 或 Qwen 3 72B

一个更实用的本地 Agent 模板

把前面所有内容整合成一个可复用的模板,加上简单的错误处理和日志:

import json, logging
from openai import OpenAI

logging.basicConfig(level=logging.INFO)
log = logging.getLogger("agent")

class LocalAgent:
    def __init__(self, model="hermes3:8b", tools=None, schemas=None):
        self.client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")
        self.model = model
        self.tools = tools or {}
        self.schemas = schemas or []

    def run(self, task: str, max_steps=10, system=None):
        messages = [
            {"role": "system", "content": system or "你是一个助手,可以调用工具。"},
            {"role": "user", "content": task},
        ]

        for step in range(max_steps):
            log.info(f"[step {step+1}] calling model...")
            resp = self.client.chat.completions.create(
                model=self.model,
                messages=messages,
                tools=self.schemas,
                tool_choice="auto",
                temperature=0.0,
            )
            msg = resp.choices[0].message
            messages.append(msg.model_dump(exclude_none=True))

            if not msg.tool_calls:
                log.info(f"[step {step+1}] finished")
                return msg.content

            for tc in msg.tool_calls:
                log.info(f"  -> {tc.function.name}({tc.function.arguments})")
                try:
                    args = json.loads(tc.function.arguments)
                    result = self.tools[tc.function.name](**args)
                except Exception as e:
                    result = f"工具调用失败: {e}"
                    log.warning(f"     {result}")
                messages.append({
                    "role": "tool",
                    "tool_call_id": tc.id,
                    "content": str(result)[:2000],  # 防 token 爆炸
                })

        return "达到最大步数限制"

agent = LocalAgent(tools=TOOLS, schemas=TOOL_SCHEMAS)
print(agent.run("北京天气加上海天气除以二等于多少?只看温度"))

这个模板有几个生产级的细节:temperature=0 保证稳定;工具结果裁剪到 2000 字符避免 token 爆炸;每步打日志方便调试;工具异常不会让 Agent 崩溃,只会把错误信息回传给模型——模型能基于错误重试。

小结

Hermes 3 + Ollama 是 2026 年开源 Agent 栈的黄金组合。理解清楚几件事:协议层有 OpenAI API 兼容,所以你几乎不用改代码;模型层Hermes 3 8B 足够大部分任务,70B 接近闭源中档水平;运维层Ollama 把模型加载、内存管理、API 服务全包了。

下一篇把这个开源 Agent 栈再往前推一步——加上 RAG,用本地向量库 + Hermes 3 + Qwen-Agent 的组件搭一个完全离线的、能查询你自己文档的 Agent。这基本是"企业内部 AI 助手"的最小通路。

相关阅读

版权声明: 如无特别声明,本文版权归 sshipanoo 所有,转载请注明本文链接。

(采用 CC BY-NC-SA 4.0 许可协议进行授权)

本文标题:10. Hermes 3 实战:在 Ollama 上跑一个本地工具调用 Agent

本文链接:https://www.sshipanoo.com/blog/ai/ai-agent/10-Hermes3实战/

本文最后一次更新为 天前,文章中的某些内容可能已过时!