第四章

从入口开始:程序是如何启动的

🎧 语音播放

追踪从命令行到 Agent 初始化的完整流程

🎯 本章目标

  • 理解 Hermes-Agent 的启动流程
  • 掌握命令行参数如何被解析
  • 理解各模块的初始化顺序

📚 前置知识

  • 第三章的架构认知
  • Python 基础(import、类初始化)

💡 学完收获

  • 能追踪一条用户消息的完整处理路径
  • 理解各模块的初始化顺序和依赖关系
  • 为调试和扩展打下基础

4.1 启动入口:hermes 命令

当你运行 hermes 时,发生了什么?

启动流程 Startup Flow

flowchart TD A["⌨️ hermes 命令"] --> B["🔧 hermes_cli/main.py"] B --> C["📂 加载环境变量 ~/.hermes/.env"] C --> D["🎛️ HermesCLI.__init__"] D --> E["🧠 AIAgent.__init__"] E --> F["🔍 工具自动发现"] E --> G["📝 构建系统 Prompt"] E --> H["💾 初始化状态存储"] F --> I["✅ 就绪,等待用户输入"] style A fill:#dbeafe,stroke:#2563eb style I fill:#d1fae5,stroke:#059669

1. 命令解析

首先,hermes 命令被 hermes_cli/main.py 处理:

hermes_cli/main.py
def main():
    parser = argparse.ArgumentParser()
    subparsers = parser.add_subparsers()
    subparsers.add_command("model", ...)
    subparsers.add_command("gateway", ...)
    # ...

# 无子命令时 → 启动 CLI
if __name__ == "__main__":
    hermes_cli()

2. 环境加载

在初始化任何功能之前,Hermes 先加载环境变量:

run_agent.py (行 48-59)
# Load .env from ~/.hermes/.env first, then project root as dev fallback.
from hermes_cli.env_loader import load_hermes_dotenv

_hermes_home = get_hermes_home()
_project_env = Path(__file__).parent / '.env'
_loaded_env_paths = load_hermes_dotenv(
    hermes_home=_hermes_home, 
    project_env=_project_env
)
💡 为什么先加载 .env?

Hermes 需要 API 密钥才能工作,所以必须最早加载环境变量。后面的模块初始化可能已经需要调用 LLM 了。

4.2 核心类初始化

AIAgent.__init__ 流程

当你创建一个 AIAgent 实例时,初始化顺序是:

1
IterationBudget
设置最大迭代次数(默认 90)
2
OpenAI 客户端
配置 API 密钥和 base_url
3
加载工具
调用 get_tool_definitions()
4
构建 Prompt
调用 prompt_builder
5
初始化组件
ContextCompressor 等

关键代码片段

run_agent.py (简化)
class AIAgent:
    def __init__(self, model="anthropic/claude-opus-4.6",
                 max_iterations=90, ...):
        
        # 1. 初始化迭代预算
        self.iteration_budget = IterationBudget(max_iterations)
        
        # 2. 创建模型客户端
        self.client = OpenAI(api_key=api_key, base_url=base_url)
        
        # 3. 获取工具定义
        tool_schemas = get_tool_definitions(
            enabled_toolsets=enabled_toolsets,
            disabled_toolsets=disabled_toolsets
        )
        
        # 4. 构建系统 Prompt
        system_prompt = self._build_system_prompt()
        
        # 5. 初始化压缩器
        self.context_compressor = ContextCompressor(model=model)

4.3 工具的自动发现

Hermes-Agent 使用自动发现机制找到所有工具:

工具发现机制 Tool Discovery

flowchart LR A["📦 model_tools.py 加载"] --> B["🔄 _discover_tools()"] B --> C["📥 import tools.web_tools"] B --> D["📥 import tools.terminal_tool"] B --> E["📥 import tools.file_tools"] B --> F["📥 ..."] C --> G["📝 registry.register()"] D --> G E --> G F --> G G --> H["🗂️ 工具注册表"] style B fill:#dbeafe,stroke:#2563eb style H fill:#ede9fe,stroke:#7c3aed

4.4 命令行处理流程

当你输入一条消息时,CLI 的处理流程是:

👤
用户输入消息
🎛️
HermesCLI.process_message()
验证命令(如 /help、/model)
🧠
AIAgent.run_conversation()
开始 Agent 循环
📡
LLM 调用 + 工具执行
返回最终响应
🖥️
HermesCLI.display_response()
显示给用户

4.5 配置加载

Hermes 使用多层配置,优先级从高到低:

1 命令行参数
最高优先级
2 环境变量
~/.hermes/.env
3 配置文件
~/.hermes/config.yaml

4 默认配置
最低优先级

4.6 工具集(Toolsets)机制

Hermes 使用"工具集"来组织工具:

🌐 web
web_search, web_extract
🖥️ terminal
terminal, process
📁 file
read/write/patch/search
🌐 browser
浏览器自动化
🎯 skills
技能管理
cronjob
定时任务
💡 为什么要用工具集?

想象你做一个纯研究的 Agent,只需要网络搜索功能;做一个编程的 Agent,需要文件和终端功能。工具集让按需启用成为可能。

4.7 易错点:新手常见问题

⚠️ 问题 1:API 密钥没生效

如果提示 "No API key found",检查:

  • ~/.hermes/.env 文件是否存在
  • 文件格式是否正确(KEY=VALUE,每行一个)
  • 是否有多余的空格或引号

⚠️ 问题 2:工具未找到

如果某个工具不可用,检查:

  • 对应的 toolset 是否在 enabled_toolsets
  • 是否有缺少的环境变量(如 SEARCH_API_KEY

📚 本章总结

本章重点:

  • 启动流程:hermesHermesCLIAIAgent
  • 环境加载:最早加载 .env 文件
  • 工具发现:模块 import 时自动注册
  • 配置优先级:命令行 > 环境变量 > 配置文件 > 默认值

一句话记忆点: Hermes 的启动就像做一杯咖啡:先准备好豆子(env),再启动机器(Agent),最后出品(响应)

延伸思考

  1. 为什么 Hermes 选择在模块 import 时注册工具,而不是用配置文件?
  2. 如果需要支持"热加载"新工具(即不重启添加工具),你会如何设计?
  3. 配置优先级这样设计的原因是什么?有什么优缺点?