第 12 期 | Gemma + Hermes Agent:本地开源 Agent 全栈
好的,作为一名资深技术教育内容作者,我将为您撰写这期关于 Gemma 与 Hermes Agent 的完整 Markdown 教学文章。
副标题:将微调后的 Gemma 接入 Hermes Agent 作为后端 LLM,实现完全本地化、零 API 费用的 AI Agent 体验。
🎯 学习目标
- 理解本地开源 AI Agent 的核心价值、优势及其在实际应用中的潜力。
- 掌握 Hermes Agent 框架的基本架构,包括其 LLM 连接器、工具注册与任务编排机制。
- 学习如何通过 Ollama 将 Google Gemma 模型部署到本地,并将其无缝集成到 Hermes Agent 作为后端语言模型。
- 实践构建一个端到端、完全本地化、零 API 费用的 AI Agent 应用,能够执行多步骤、需要工具协作的任务。
📖 核心概念讲解
### 12.1 本地 AI Agent 的崛起与优势
随着大型语言模型(LLM)能力的飞速发展,AI Agent 已成为构建智能应用的关键范式。AI Agent 不仅仅是调用 LLM 进行一次性问答,它具备规划、反思、记忆和使用外部工具的能力,以实现更复杂、多步骤的目标。传统上,许多 Agent 框架依赖于云端 LLM 服务(如 OpenAI GPT 系列),这带来了几个挑战:
- API 费用:每次与云端 LLM 交互都会产生费用,对于高频或大规模应用来说,成本可能迅速累积。
- 数据隐私:敏感数据传输到第三方云服务进行处理,可能面临隐私泄露的风险。
- 网络延迟与稳定性:依赖外部网络服务可能导致不稳定的性能和更高的延迟。
- 定制化限制:虽然可以通过 Prompt Engineering 进行定制,但无法直接控制 LLM 的底层行为或进行深度微调后的本地部署。
本地 AI Agent 旨在解决这些问题。通过在本地部署开源 LLM(如 Gemma),并结合本地 Agent 框架,我们可以实现:
- 零 API 费用:一旦模型部署,后续推理不再产生额外费用。
- 数据主权与隐私:所有数据处理均在本地进行,确保数据不离开您的控制环境。
- 低延迟与高稳定性:摆脱网络依赖,提供更快的响应速度和更稳定的服务。
- 深度定制化:能够将经过微调的私有模型接入 Agent,实现特定领域或业务需求的高精度任务。
本地 AI Agent 的架构通常包含以下核心组件:
- 用户接口 (User Interface):接收用户指令并展示 Agent 输出。
- Agent 核心 (Agent Core):负责任务规划、决策、反思和流程控制。
- LLM 连接器 (LLM Connector):与本地部署的 LLM 进行通信的接口。
- 本地 LLM (Local LLM):在本地运行的大语言模型,负责理解、生成文本和推理。
- 工具注册与执行 (Tool Registry & Executor):管理可用的外部工具(如计算器、代码解释器、网络搜索、数据库查询等),并根据 Agent 的决策执行相应工具。
- 记忆模块 (Memory Module):存储 Agent 的历史对话、观察结果和学习到的知识。
以下是本地 AI Agent 的一个简化交互流程图:
graph TD
A[用户输入] --> B(Agent 核心: 任务规划/决策)
B --> C{需要 LLM 协助?}
C -- 是 --> D[LLM 连接器]
D --> E(本地 Gemma LLM)
E --> B
C -- 否 --> F{需要外部工具?}
F -- 是 --> G[工具执行器]
G --> H(外部工具: 搜索/计算/API等)
H --> B
B --> I(Agent 输出)
I --> A### 12.2 Google Gemma:本地大模型基石
Google DeepMind 推出的 Gemma 家族是开源 LLM 领域的里程碑。基于 Gemini 技术,Gemma 提供了从 1B 到 27B 等不同规模的模型,支持多模态(在 Gemma 2/3 中),拥有 128K 的超长上下文窗口,并且可以通过 LoRA/QLoRA 等技术进行高效微调。
为什么选择 Gemma 作为本地 Agent 的 LLM 基石?
- 开源与免费:Gemma 完全开源,可在本地免费部署和使用,非常适合个人开发者和中小型企业。
- 性能优异:尽管是开源模型,Gemma 在多项基准测试中展现出与闭源模型相媲美的性能,尤其在推理、代码生成和多语言支持方面表现出色。
- 多模态能力:Gemma 2/3 版本支持图像-文本多模态,为构建更复杂的 Agent 应用提供了可能。
- 易于部署:Gemma 可通过 Ollama、Hugging Face Transformers 等多种方式轻松在本地部署,降低了技术门槛。
- 微调潜力:Gemma 对 LoRA/QLoRA 微调的良好支持,意味着我们可以训练出高度专业化、针对特定任务优化的 Agent LLM。
在本期教程中,我们将使用 Ollama 作为 Gemma 的本地部署平台。Ollama 简化了在本地运行各种开源模型的流程,提供了一个统一的 API 接口,非常适合与 Agent 框架集成。
### 12.3 Hermes Agent:本地 Agent 框架解析
Hermes Agent 是一个为本地化、模块化和可扩展性设计的开源 AI Agent 框架(注:本教程中的 Hermes Agent 是一个概念性框架,旨在演示本地 Agent 的集成模式。其设计灵感来源于 LangChain、CrewAI 等主流 Agent 框架,并针对本地 LLM 集成进行了优化。)。它的核心目标是让开发者能够轻松地将本地部署的 LLM(如 Gemma)与自定义工具结合,构建出功能强大的本地 Agent。
Hermes Agent 的核心组件:
LLMConnector抽象层:Hermes Agent 不直接依赖于特定的 LLM 实现,而是通过LLMConnector接口与各种 LLM 后端进行通信。这使得我们可以轻松切换不同的本地 LLM(如 Gemma、Llama 3、Mistral 等),甚至是兼容 OpenAI API 的本地服务。Tool注册与封装:Agent 的能力很大程度上取决于其能使用的工具。Hermes Agent 提供了简洁的Tool封装机制,允许开发者将任何 Python 函数或外部服务包装成 Agent 可调用的工具。每个工具都包含名称、描述和执行逻辑。AgentCore编排引擎:这是 Agent 的“大脑”,负责:- 任务规划 (Planning):根据用户输入和当前状态,制定实现目标的步骤。
- 工具选择 (Tool Selection):决定在哪个步骤使用哪个工具。
- 推理 (Reasoning):利用 LLM 的能力进行逻辑推理、理解上下文。
- 反思与纠错 (Reflection & Correction):根据工具执行结果调整计划。
- 记忆管理 (Memory Management):维护对话历史和状态。
- Prompt 模板:Agent 与 LLM 的交互是基于精心设计的 Prompt 模板,通常采用 ReAct (Reasoning and Acting) 或 Chain-of-Thought (CoT) 模式,引导 LLM 思考并决定下一步动作。
以下是 Hermes Agent 的内部工作流示意图:
+---------------------+
| User Query |
+---------------------+
|
v
+---------------------+
| Hermes Agent |
| (AgentCore) |
+---------------------+
| 1. 构建 LLM Prompt | <---+ (历史对话/工具描述)
| - 任务指令 |
| - 可用工具描述 |
| - 当前思考/观察 |
+---------------------+
|
v
+---------------------+
| LLM Connector |
| (GemmaOllamaLLM) |
+---------------------+
|
v
+---------------------+
| Gemma Model |
| (本地 Ollama 服务) |
+---------------------+
|
v
+---------------------+
| LLM 输出解析 |
| (识别 Thought, Action, Action Input, Final Answer) |
+---------------------+
|
v
+---------------------+
| 2. 决策与执行 |
| - 若是 Final Answer: 返回结果 |
| - 若是 Action: |
| - 查找对应工具 |
| - 执行工具 |
| - 获取 Observation |
| - 更新 Agent 状态 |
+---------------------+
|
v
+---------------------+
| Agent Output |
+---------------------+
### 12.4 Gemma + Hermes Agent:全栈集成原理
将 Gemma 与 Hermes Agent 集成的关键在于 LLMConnector。Ollama 在本地启动后,会提供一个 RESTful API 接口(默认端口 11434),允许外部程序通过 HTTP 请求与模型进行交互。Hermes Agent 的 GemmaOllamaLLM 连接器就是利用 Python 的 ollama 客户端库(或直接 requests 库)向这个 API 发送请求。
集成步骤概述:
- Ollama 服务启动:确保您的机器上安装并运行了 Ollama 服务。
- 拉取 Gemma 模型:使用
ollama pull命令下载所需的 Gemma 模型。 - Hermes
GemmaOllamaLLM实现:创建一个 Python 类,封装ollama.Client,提供一个generate方法,接收 Prompt 并返回 Gemma 的响应。 - 工具定义:将 Agent 需要使用的功能(如网络搜索、计算、文件读写等)封装成
Tool对象,包含名称、详细描述和执行函数。 - Agent 编排:在
AgentCore中,将GemmaOllamaLLM实例和Tool列表传入,并定义 Agent 的 Prompt 模板,使其能够理解何时调用工具、如何解析工具输出,并最终给出答案。
通过这种方式,Hermes Agent 将 Gemma 作为一个强大的推理引擎,结合外部工具,实现了复杂的任务自动化。整个过程完全在本地完成,确保了数据安全和零成本运行。
💻 实战演示
我们将通过一个具体的例子来演示如何搭建一个本地的 Gemma + Hermes Agent。这个 Agent 将具备:
- 通用知识问答能力 (由 Gemma 提供)。
- 网络搜索能力 (通过
wikipedia库模拟,实际可替换为 Google Search API 或自定义爬虫)。 - 数学计算能力 (通过 Python
eval函数实现)。
### 12.4.1 环境准备与 Gemma 本地部署
首先,确保您的系统满足运行 Ollama 和 Gemma 的基本要求(足够的 RAM 和 GPU,如果可用)。
安装 Ollama 访问 Ollama 官方网站,下载并安装适合您操作系统的版本。安装完成后,Ollama 服务会在后台自动运行。
拉取 Gemma 模型 打开终端或命令行,执行以下命令拉取 Gemma 7B 模型。如果您资源有限,可以选择
gemma:2b。ollama pull gemma:7b # 或 # ollama pull gemma:2b等待模型下载完成。您可以通过
ollama list查看已下载的模型。验证 Gemma 运行 您可以在终端尝试与 Gemma 交互,确保其正常工作:
ollama run gemma:7b "Hello, Gemma! How are you?"如果 Gemma 返回了响应,说明本地部署成功。
创建项目目录并安装 Python 依赖
mkdir hermes_gemma_agent cd hermes_gemma_agent python -m venv venv source venv/bin/activate # macOS/Linux # 或 venv\Scripts\activate # Windows pip install ollama wikipediaollama库用于与 Ollama 服务通信,wikipedia库将作为我们的一个外部工具。
### 12.4.2 Hermes Agent 框架搭建与配置
我们将创建几个 Python 文件来构建 Hermes Agent 的核心组件。
1. llm_connector.py:Gemma LLM 连接器
# llm_connector.py
import ollama
import json
class GemmaOllamaLLM:
"""
通过 Ollama 服务连接本地 Gemma 模型的 LLM 连接器。
"""
def __init__(self, model_name: str = "gemma:7b", base_url: str = "http://localhost:11434"):
self.model_name = model_name
self.client = ollama.Client(host=base_url)
print(f"Initialized GemmaOllamaLLM with model: {self.model_name} at {base_url}")
def generate(self, prompt: str, temperature: float = 0.0, **kwargs) -> str:
"""
向 Gemma 模型发送 Prompt 并获取响应。
"""
try:
# 使用 ollama.Client 的 generate 方法
# stream=False 表示一次性获取完整响应
response = self.client.generate(
model=self.model_name,
prompt=prompt,
stream=False,
options={
"temperature": temperature,
"num_gpu": -1 # 尝试使用所有可用GPU加速
},
**kwargs
)
return response['response'].strip()
except ollama.ResponseError as e:
print(f"Ollama Response Error: {e}")
return f"Error: Failed to get response from Gemma. {e}"
except Exception as e:
print(f"An unexpected error occurred: {e}")
return f"Error: An unexpected error occurred. {e}"
def __call__(self, prompt: str, temperature: float = 0.0, **kwargs) -> str:
return self.generate(prompt, temperature, **kwargs)
if __name__ == "__main__":
# 简单的测试
gemma_llm = GemmaOllamaLLM(model_name="gemma:2b") # 可以用2b进行快速测试
test_prompt = "介绍一下 Python 语言。"
print(f"\n--- Testing GemmaOllamaLLM with prompt: '{test_prompt}' ---")
response = gemma_llm.generate(test_prompt)
print(response)
test_prompt_2 = "给我讲个笑话。"
print(f"\n--- Testing GemmaOllamaLLM with prompt: '{test_prompt_2}' ---")
response_2 = gemma_llm(test_prompt_2, temperature=0.0) # 使用 __call__ 方法
print(response_2)
2. tools.py:Agent 可用的工具定义
# tools.py
import wikipedia
import math
import re
class Tool:
"""
Agent 可调用的工具的抽象基类。
"""
def __init__(self, name: str, description: str, func):
self.name = name
self.description = description
self.func = func
def run(self, *args, **kwargs):
"""执行工具函数并返回结果。"""
try:
return self.func(*args, **kwargs)
except Exception as e:
return f"Error executing tool '{self.name}': {e}"
# --- 具体工具实现 ---
def search_wikipedia_func(query: str) -> str:
"""
使用 Wikipedia 进行搜索并返回摘要。
输入应为搜索查询字符串。
"""
try:
# 尝试获取第一句摘要
summary = wikipedia.summary(query, sentences=2)
return f"Wikipedia search result for '{query}': {summary}"
except wikipedia.exceptions.PageError:
return f"No Wikipedia page found for '{query}'."
except wikipedia.exceptions.DisambiguationError as e:
return f"Wikipedia disambiguation error for '{query}'. Options: {e.options[:3]}..."
except Exception as e:
return f"An error occurred during Wikipedia search: {e}"
def safe_calculator_func(expression: str) -> str:
"""
执行安全的数学表达式计算。
只允许数字、基本运算符、括号和常用数学函数。
"""
# 限制允许的字符和函数,防止任意代码执行
allowed_chars = "0123456789.+-*/()% "
allowed_funcs = {"abs": abs, "round": round, "max": max, "min": min, "sum": sum,
"sqrt": math.sqrt, "pow": math.pow, "log": math.log, "exp": math.exp}
# 检查表达式是否只包含允许的字符
if not all(c in allowed_chars or c.isalpha() for c in