Hermes Agent 深度解析
Hermes Agent 深度解析
Hermes 是新一代面向生产的 Agent 编排框架,以"强工具调用能力 + 结构化多智能体通信协议"为核心卖点,在 2025-2026 年的企业级 AI 系统建设中快速崛起。深入理解 Hermes 的架构理念,是高级 AI 工程师岗位面试的差异化竞争力。
写在前面
字节、阿里、百度的 AI 架构岗面试,已开始出现 Hermes 相关考点。面试官不会只问"什么是 Hermes",而会直接问:Hermes 的工具调用机制和 OpenClaw 框架有什么本质区别?你如何在 Hermes 中设计一个带记忆的多轮对话 Agent?Hermes 的多智能体通信协议是怎么保证消息可靠性的? 本文系统拆解 Hermes 核心原理、架构设计与 Java 实战,帮你在面试中脱颖而出。
Hermes 是什么
Hermes 是一个以强类型工具调用和结构化 Agent 间通信为核心的 Agent 编排框架。名字来源于希腊神话中的信使之神,寓意其在多智能体系统中承担"消息路由与任务传递"的核心角色。
Hermes 的核心设计目标
- 工具调用可靠性:通过 Schema 强约束和运行时验证,将工具调用错误率降到最低
- Agent 间通信标准化:定义了一套类似 gRPC 的 Agent 通信协议,消息有类型、有版本、有 ACK 机制
- 可观测性优先:每一步推理、每一次工具调用都产生结构化追踪日志,天然支持 OpenTelemetry
- 生产级容错:内置超时、重试、熔断机制,不需要业务代码自己实现
Hermes vs 主流框架定位
| 框架 | 定位 | 核心特点 | 典型场景 |
|---|---|---|---|
| Hermes | 生产级 Agent 编排 | 强工具协议 + 多智能体通信 | 企业级多 Agent 协作系统 |
| OpenClaw / OpenClaws | 开源 Agent 流程引擎 | 灵活的流程图驱动 + 插件生态 | 快速原型和中小规模部署 |
| LangGraph4j | 有向状态机 Agent | 状态持久化 + Human-in-Loop | 复杂状态管理的长期任务 |
| Spring AI | Java AI 统一抽象 | LLM/向量库统一接口 | 已有 Spring 项目的 AI 集成 |
Hermes 核心架构
整体架构图
六大核心组件
1. Agent Gateway(入口网关)
所有外部请求的统一入口。负责认证(JWT/API Key)、限流、请求路由。将用户请求转化为 Hermes 内部的 AgentTask 对象,携带 traceId、sessionId、priority 等元数据。
2. Orchestrator(编排器)
Hermes 的大脑。接收 AgentTask 后,调用规划 LLM 将任务分解为子任务列表,通过 Message Broker 分发给合适的专业 Agent。同时负责监控子任务完成状态、处理超时重试、汇总最终结果。
3. Memory Manager(记忆管理器)
管理两层记忆:
- 短期记忆(In-Context):当前会话的对话历史,存储在 Redis,有 TTL 自动过期
- 长期记忆(Semantic):用 Embedding 向量化后存入 VectorStore,检索时按语义相似度召回
4. Tool Registry(工具注册中心)
所有工具的统一注册与发现中心。工具以 ToolDescriptor(包含名称、描述、入参 Schema、权限标签)形式注册,Agent 运行时动态查询可用工具列表,实现工具与 Agent 的解耦。
5. Message Broker(消息代理)
Agent 间通信的核心。基于 Hermes 消息协议(HMP),每条消息包含:消息类型、发送方 Agent ID、接收方 Agent ID、payload、ACK 要求标志。底层可以是 Redis Pub/Sub 或 Kafka,保证消息的可靠投递。
6. Tool Executor(工具执行器)
实际执行工具调用的沙箱组件。支持 HTTP 调用、JDBC 查询、代码执行(Docker 沙箱)、文件操作。每次执行自动记录入参、出参、耗时到 Observability 系统。
Hermes vs OpenClaw 深度对比
架构理念差异
工具调用机制对比
OpenClaw 的工具调用方式:工具以"节点插件"形式挂载到流程图,调用时由流程引擎直接触发,工具入参/出参通过流程变量传递,缺乏运行时 Schema 验证。
// OpenClaw 风格:工具作为流程节点
@FlowNode(name = "queryDB")
public class QueryDatabaseNode implements FlowNodeHandler {
@Override
public FlowResult execute(FlowContext context) {
String sql = (String) context.getVariable("sql"); // 无类型保证
// 直接执行,无 Schema 验证
List<Map<String, Object>> result = jdbcTemplate.queryForList(sql);
context.setVariable("queryResult", result);
return FlowResult.success();
}
}Hermes 的工具调用方式:工具以 ToolDescriptor 注册,调用前 Tool Registry 做入参 Schema 验证,调用后做出参类型检查,全程有 traceId 追踪。
// Hermes 风格:工具以描述符注册,强类型约束
@HermesTool(
name = "query_database",
description = "在业务数据库执行 SELECT 查询,返回 JSON 结果列表",
permissions = {"db:read"},
timeout = 5000
)
public class QueryDatabaseTool implements HermesTool {
// Hermes 自动根据此 Schema 做入参验证
@Override
public ToolSchema getSchema() {
return ToolSchema.builder()
.addParam("sql", ParamType.STRING, "SELECT 查询语句", true)
.addParam("limit", ParamType.INTEGER, "最大返回行数,默认50", false)
.build();
}
@Override
public ToolResult execute(ToolCallContext ctx) {
String sql = ctx.getRequiredParam("sql", String.class);
int limit = ctx.getOptionalParam("limit", Integer.class, 50);
// 执行并返回强类型结果
List<Map<String, Object>> rows = jdbcTemplate.queryForList(
sql + " LIMIT " + Math.min(limit, 500)
);
return ToolResult.success(rows);
}
}核心差异对比表
| 对比维度 | OpenClaw / OpenClaws | Hermes |
|---|---|---|
| 编排方式 | 可视化流程图 / DAG | 代码驱动的 Agent 编排 |
| 工具调用 | 流程节点插件,弱类型 | 强类型 ToolDescriptor + Schema 验证 |
| Agent 通信 | 通过流程变量间接传递 | HMP 协议直接消息通信 |
| 记忆管理 | 需手动集成,无内置方案 | 内置双层记忆(Redis + VectorStore) |
| 容错机制 | 需业务代码实现 | 内置超时/重试/熔断 |
| 可观测性 | 基本日志,需自行集成 | 原生 OpenTelemetry 集成 |
| 上手难度 | 低(可视化设计器) | 中(需理解 HMP 协议) |
| 生产就绪 | 需较多定制 | 开箱即用 |
| 多 Agent 规模 | 适合 10 个以内 Agent | 可支持 100+ Agent 的大规模集群 |
选型建议
选 OpenClaw:团队以业务人员为主,需要可视化拖拽设计流程;或项目处于快速原型阶段,需要快速验证 Agent 流程可行性;或场景简单,Agent 数量少于 5 个。
选 Hermes:需要生产级稳定性和可观测性;多 Agent 协作场景复杂,Agent 数量多;有严格的安全审计要求(工具调用需要权限管控和全量日志);团队有较强的 Java 后端研发能力。
Hermes 工具调用机制深度解析
工具调用完整时序
ToolDescriptor 注册机制
Hermes 在应用启动时扫描所有 @HermesTool 注解的 Bean,自动构建 Tool Registry:
// 工具注册中心核心实现
@Component
public class HermesToolRegistry {
// toolName -> ToolDescriptor 的映射
private final Map<String, ToolDescriptor> registry = new ConcurrentHashMap<>();
@PostConstruct
public void init() {
// Spring 启动时自动扫描并注册所有 HermesTool
applicationContext.getBeansWithAnnotation(HermesTool.class)
.forEach((beanName, bean) -> {
HermesTool annotation = bean.getClass().getAnnotation(HermesTool.class);
HermesTool tool = (HermesTool) bean;
ToolDescriptor descriptor = ToolDescriptor.builder()
.name(annotation.name())
.description(annotation.description())
.schema(tool.getSchema())
.permissions(Set.of(annotation.permissions()))
.timeout(annotation.timeout())
.implementation(tool)
.build();
registry.put(annotation.name(), descriptor);
});
}
// 根据 Agent 权限过滤可见工具
public List<ToolDescriptor> getAvailableTools(Set<String> agentPermissions) {
return registry.values().stream()
.filter(tool -> agentPermissions.containsAll(tool.getPermissions()))
.collect(Collectors.toList());
}
// 执行工具调用(含 Schema 验证)
public ToolResult call(String toolName, Map<String, Object> params,
String traceId, Set<String> callerPermissions) {
ToolDescriptor descriptor = registry.get(toolName);
if (descriptor == null) {
return ToolResult.error("工具不存在:" + toolName);
}
// 权限检查
if (!callerPermissions.containsAll(descriptor.getPermissions())) {
return ToolResult.error("权限不足,需要:" + descriptor.getPermissions());
}
// Schema 验证
ValidationResult validation = descriptor.getSchema().validate(params);
if (!validation.isValid()) {
return ToolResult.validationError(validation.getErrors());
}
// 执行(含超时控制)
return executeWithTimeout(descriptor, params, traceId);
}
private ToolResult executeWithTimeout(ToolDescriptor descriptor,
Map<String, Object> params, String traceId) {
ToolCallContext ctx = new ToolCallContext(params, traceId);
CompletableFuture<ToolResult> future = CompletableFuture.supplyAsync(
() -> descriptor.getImplementation().execute(ctx)
);
try {
return future.get(descriptor.getTimeout(), TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
future.cancel(true);
return ToolResult.error(
String.format("工具 %s 执行超时(%dms)", descriptor.getName(), descriptor.getTimeout())
);
} catch (Exception e) {
return ToolResult.error("工具执行异常:" + e.getMessage());
}
}
}工具调用的错误自愈机制
Hermes 内置了工具调用失败后的 LLM 自愈循环,当工具返回 ValidationError 时,Agent 会将错误信息反馈给 LLM,由 LLM 修正入参后重试:
@Service
public class HermesAgentExecutor {
private static final int MAX_TOOL_RETRY = 3;
public String executeWithToolCall(AgentContext ctx, String userMessage) {
List<Message> messages = new ArrayList<>(ctx.getHistory());
messages.add(new UserMessage(userMessage));
for (int step = 0; step < ctx.getMaxSteps(); step++) {
// 获取当前 Agent 可用工具(按权限过滤)
List<ToolDescriptor> tools = toolRegistry.getAvailableTools(ctx.getPermissions());
// LLM 决策
LlmResponse response = llmClient.call(messages, tools);
if (response.isToolCall()) {
// 执行工具调用(含自愈重试)
ToolResult result = executeToolWithRetry(
response.getToolName(),
response.getToolParams(),
ctx.getTraceId(),
ctx.getPermissions(),
MAX_TOOL_RETRY
);
messages.add(response.asAssistantMessage());
messages.add(new ToolMessage(response.getToolCallId(), result.toJson()));
} else {
// 非工具调用,返回最终答案
return response.getContent();
}
}
return "达到最大步骤限制(" + ctx.getMaxSteps() + "步),当前任务未完成";
}
private ToolResult executeToolWithRetry(String toolName, Map<String, Object> params,
String traceId, Set<String> permissions, int maxRetry) {
for (int attempt = 1; attempt <= maxRetry; attempt++) {
ToolResult result = toolRegistry.call(toolName, params, traceId, permissions);
if (result.isSuccess()) {
return result;
}
if (result.isValidationError() && attempt < maxRetry) {
// 将验证错误反馈给 LLM,让其修正
params = llmClient.fixToolParams(toolName, params, result.getErrors());
log.warn("工具调用验证失败,第{}次修正重试: {}", attempt, result.getErrors());
} else {
return result; // 非验证错误或已达最大重试次数,直接返回
}
}
return ToolResult.error("超过最大重试次数(" + maxRetry + ")");
}
}Agent 编排:从简单到复杂
第一层:简单单 Agent
最基本的用法——一个 Agent 配置工具列表,处理用户请求。
// Maven 依赖
// <dependency>
// <groupId>io.hermes</groupId>
// <artifactId>hermes-agent-spring-boot-starter</artifactId>
// <version>2.1.0</version>
// </dependency>
@Service
public class SimpleHermesAgent {
private final HermesAgentBuilder agentBuilder;
private final QueryDatabaseTool queryDatabaseTool;
private final WebSearchTool webSearchTool;
// 构建单 Agent 实例
private final HermesAgent agent;
@PostConstruct
public void init() {
this.agent = agentBuilder
.name("business-assistant")
.description("企业业务助手,可查询数据库和搜索网络信息")
.llmProvider("openai", "gpt-4o-mini") // 使用 GPT-4o Mini 降成本
.systemPrompt("""
你是一个企业业务助手。
回答规范:
1. 先分析用户问题,判断是否需要查询数据
2. 数据查询优先用数据库工具;数据库没有的信息再考虑网络搜索
3. 返回结果要简洁,数字类结果加上单位
4. 涉及金额统一显示到元,保留两位小数
""")
.tools(queryDatabaseTool, webSearchTool)
.maxSteps(8)
.permissions("db:read", "web:search")
.build();
}
public String ask(String userId, String question) {
AgentSession session = AgentSession.ofUser(userId);
return agent.execute(session, question);
}
}第二层:复杂单 Agent(带记忆)
加入短期记忆(对话历史)和长期记忆(语义检索),实现真正的多轮上下文对话:
@Service
@Slf4j
public class MemoryAwareHermesAgent {
private final HermesAgent agent;
private final HermesMemoryManager memoryManager;
@PostConstruct
public void init() {
this.agent = HermesAgent.builder()
.name("customer-service-agent")
.description("客服 Agent,记住用户历史问题和偏好")
.llmProvider("qwen", "qwen-plus")
.systemPrompt("""
你是一名经验丰富的客服专员。
对话规范:
- 称呼用户时使用其姓名(如记忆中有)
- 关联用户历史问题,避免重复让用户解释
- 敏感操作(退款、账户变更)需要先验证用户身份
""")
.memory(MemoryConfig.builder()
.shortTermTtl(Duration.ofHours(2)) // 短期记忆 2 小时
.shortTermMaxMessages(20) // 最多保留 20 条对话
.longTermEnabled(true) // 开启语义长期记忆
.longTermTopK(5) // 每次召回最相关的 5 条
.longTermStore("pgvector") // 用 PGVector 存储
.build())
.tools(customerQueryTool, orderTool, refundTool)
.maxSteps(10)
.build();
}
public String chat(String userId, String message) {
// 1. 从长期记忆中召回相关历史(语义检索)
List<MemoryEntry> relatedMemories = memoryManager.recallLongTerm(userId, message, 5);
// 2. 将长期记忆注入到系统提示词的上下文中
String enrichedSystem = buildSystemWithMemory(relatedMemories);
// 3. 执行 Agent(短期记忆由框架自动管理)
AgentSession session = AgentSession.ofUser(userId)
.withSystemEnrichment(enrichedSystem);
String response = agent.execute(session, message);
// 4. 将本轮对话存入长期记忆(异步,不阻塞响应)
CompletableFuture.runAsync(() ->
memoryManager.storeLongTerm(userId, message, response)
);
return response;
}
private String buildSystemWithMemory(List<MemoryEntry> memories) {
if (memories.isEmpty()) return "";
StringBuilder sb = new StringBuilder("用户历史记录(供参考):\n");
memories.forEach(m -> sb.append("- ").append(m.getSummary()).append("\n"));
return sb.toString();
}
}第三层:多 Agent 系统
多个专业 Agent 通过 Orchestrator 协作,处理复杂的跨领域任务:
// ===== 子 Agent 定义 =====
// 数据分析专家 Agent
@HermesAgent(
name = "data-analyst",
description = "负责数据查询、统计分析和数据可视化建议",
permissions = {"db:read", "analytics:compute"}
)
@Service
public class DataAnalystAgent extends AbstractHermesAgent {
@Override
protected void configureAgent(HermesAgentBuilder builder) {
builder
.llmProvider("openai", "gpt-4o") // 分析任务用强模型
.systemPrompt("""
你是一名资深数据分析师。
职责:数据查询、统计计算、趋势分析。
输出格式:分析结论 + 关键数字 + 数据来源说明。
""")
.tools(queryDatabaseTool, statisticsTool, chartConfigTool)
.maxSteps(6);
}
}
// 报告撰写专家 Agent
@HermesAgent(
name = "report-writer",
description = "负责将数据分析结果整理为结构化的业务报告",
permissions = {"report:write"}
)
@Service
public class ReportWriterAgent extends AbstractHermesAgent {
@Override
protected void configureAgent(HermesAgentBuilder builder) {
builder
.llmProvider("openai", "gpt-4o-mini") // 写作用小模型降成本
.systemPrompt("""
你是一名专业商业报告撰写人。
职责:将分析数据整理为清晰的管理层报告。
格式要求:标题 + 摘要 + 数据详情 + 结论建议,使用 Markdown。
""")
.tools(templateTool, formatTool)
.maxSteps(4);
}
}
// 通知发送专家 Agent
@HermesAgent(
name = "notification-sender",
description = "负责通过邮件/企业微信发送报告通知",
permissions = {"email:send", "wework:push"}
)
@Service
public class NotificationSenderAgent extends AbstractHermesAgent {
@Override
protected void configureAgent(HermesAgentBuilder builder) {
builder
.llmProvider("qwen", "qwen-turbo") // 简单任务用最轻量模型
.systemPrompt("你负责发送通知。收到报告内容后,确定收件人列表,完成发送。")
.tools(emailTool, weworkTool)
.maxSteps(3);
}
}
// ===== Orchestrator 定义 =====
@Service
@Slf4j
public class ReportOrchestrator {
private final HermesOrchestrator orchestrator;
private final DataAnalystAgent dataAnalystAgent;
private final ReportWriterAgent reportWriterAgent;
private final NotificationSenderAgent notificationSenderAgent;
@PostConstruct
public void init() {
this.orchestrator = HermesOrchestrator.builder()
.name("weekly-report-orchestrator")
.plannerLlm("openai", "gpt-4o") // 规划用强模型
.agents(dataAnalystAgent, reportWriterAgent, notificationSenderAgent)
.maxParallelism(2) // 最多 2 个 Agent 并行
.taskTimeoutMinutes(10)
.build();
}
public OrchestratorResult generateWeeklyReport(String department, List<String> recipients) {
String task = String.format(
"为 %s 部门生成本周(2026W16)销售数据周报,并发送给:%s",
department, String.join("、", recipients)
);
return orchestrator.execute(OrchestrationTask.builder()
.task(task)
.context(Map.of(
"department", department,
"recipients", recipients,
"weekRange", "2026-04-13 至 2026-04-18"
))
.build());
}
}多智能体通信协议(HMP)
HMP 协议消息结构
Hermes Message Protocol(HMP)定义了 Agent 间通信的标准消息格式:
{
"hmpVersion": "2.1",
"messageId": "msg-a1b2c3d4",
"correlationId": "task-xyz-001",
"timestamp": "2026-04-18T10:30:00.000Z",
"from": {
"agentId": "data-analyst-01",
"agentType": "DataAnalystAgent",
"instanceId": "node-03"
},
"to": {
"agentId": "report-writer-01",
"agentType": "ReportWriterAgent"
},
"messageType": "TASK_RESULT",
"priority": "NORMAL",
"ackRequired": true,
"ttlMs": 30000,
"payload": {
"taskId": "analyze-sales-q1",
"status": "SUCCESS",
"data": {
"summary": "Q1 销售总额 328 万,环比增长 12%",
"details": "..."
}
},
"traceContext": {
"traceId": "trace-0f1a2b3c",
"spanId": "span-7d8e9f"
}
}HMP 消息类型
| 消息类型 | 方向 | 说明 |
|---|---|---|
TASK_ASSIGN | Orchestrator → Agent | 分配子任务 |
TASK_RESULT | Agent → Orchestrator | 返回执行结果 |
TASK_PROGRESS | Agent → Orchestrator | 报告执行进度(长任务用) |
TASK_ERROR | Agent → Orchestrator | 报告执行失败及原因 |
PEER_REQUEST | Agent → Agent | Agent 间直接请求(经 Broker 路由) |
PEER_RESPONSE | Agent → Agent | Agent 间请求的响应 |
BROADCAST | Orchestrator → All Agents | 广播通知(如全局上下文更新) |
多 Agent 协作时序
ACK 机制与消息可靠性
Hermes 的消息可靠性通过三层机制保证:
@Component
public class HermesMessageBroker {
private final RedisTemplate<String, HmpMessage> redisTemplate;
private final Map<String, CompletableFuture<HmpMessage>> pendingAcks = new ConcurrentHashMap<>();
// 发送消息(可选 ACK)
public CompletableFuture<HmpMessage> sendMessage(HmpMessage message) {
// 1. 持久化消息到 Redis(防止 Broker 宕机丢失)
String msgKey = "hmp:pending:" + message.getMessageId();
redisTemplate.opsForValue().set(msgKey, message, Duration.ofMillis(message.getTtlMs()));
// 2. 发布到订阅频道
String channel = "hmp:agent:" + message.getTo().getAgentId();
redisTemplate.convertAndSend(channel, message);
if (!message.isAckRequired()) {
return CompletableFuture.completedFuture(null);
}
// 3. 注册 ACK 等待(超时自动失败)
CompletableFuture<HmpMessage> ackFuture = new CompletableFuture<HmpMessage>()
.orTimeout(message.getTtlMs(), TimeUnit.MILLISECONDS);
pendingAcks.put(message.getMessageId(), ackFuture);
return ackFuture;
}
// Agent 收到消息后自动回 ACK
public void acknowledge(String originalMessageId, HmpMessage ackPayload) {
CompletableFuture<HmpMessage> future = pendingAcks.remove(originalMessageId);
if (future != null) {
future.complete(ackPayload);
}
// 清理 Redis 持久化记录
redisTemplate.delete("hmp:pending:" + originalMessageId);
}
// 启动时恢复未 ACK 的消息(容灾重启)
@EventListener(ApplicationReadyEvent.class)
public void recoverUnackedMessages() {
Set<String> pendingKeys = redisTemplate.keys("hmp:pending:*");
if (pendingKeys != null && !pendingKeys.isEmpty()) {
log.warn("发现 {} 条未 ACK 消息,开始恢复投递", pendingKeys.size());
pendingKeys.forEach(key -> {
HmpMessage msg = redisTemplate.opsForValue().get(key);
if (msg != null) sendMessage(msg);
});
}
}
}Memory 管理深度解析
记忆层次架构
记忆管理器实现
@Service
@Slf4j
public class HermesMemoryManager {
private final RedisTemplate<String, Object> redisTemplate;
private final VectorStore vectorStore;
private final EmbeddingModel embeddingModel;
private final UserEntityRepository entityRepository;
// === 短期记忆管理 ===
public void addMessage(String sessionId, Message message) {
String key = "hermes:memory:short:" + sessionId;
redisTemplate.opsForList().rightPush(key, message);
redisTemplate.expire(key, Duration.ofHours(2));
// 超出上限时裁剪(保留最新的 N 条)
Long size = redisTemplate.opsForList().size(key);
if (size != null && size > 20) {
redisTemplate.opsForList().trim(key, size - 20, -1);
}
}
public List<Message> getShortTermMemory(String sessionId) {
String key = "hermes:memory:short:" + sessionId;
List<Object> rawList = redisTemplate.opsForList().range(key, 0, -1);
if (rawList == null) return Collections.emptyList();
return rawList.stream()
.map(obj -> (Message) obj)
.collect(Collectors.toList());
}
// === 长期语义记忆 ===
public void storeLongTerm(String userId, String userInput, String agentResponse) {
// 生成摘要(避免存储原始长文本)
String summary = generateSummary(userInput, agentResponse);
String content = String.format("用户问:%s\nAgent答:%s", userInput, summary);
Document doc = new Document(content,
Map.of("userId", userId, "timestamp", Instant.now().toString())
);
vectorStore.add(List.of(doc));
log.debug("已存储长期记忆,userId={}", userId);
}
public List<MemoryEntry> recallLongTerm(String userId, String query, int topK) {
// 语义相似度检索,过滤当前用户的记忆
SearchRequest request = SearchRequest.query(query)
.withTopK(topK)
.withFilterExpression("userId == '" + userId + "'");
return vectorStore.similaritySearch(request).stream()
.map(doc -> MemoryEntry.fromDocument(doc))
.collect(Collectors.toList());
}
// === 实体记忆(用户偏好)===
public void updateEntityMemory(String userId, String entityKey, Object value) {
String redisKey = "hermes:entity:" + userId;
redisTemplate.opsForHash().put(redisKey, entityKey, value);
redisTemplate.expire(redisKey, Duration.ofDays(30));
// 异步同步到 MySQL 做持久化
CompletableFuture.runAsync(() ->
entityRepository.upsert(userId, entityKey, value.toString())
);
}
public Map<String, Object> getEntityMemory(String userId) {
String redisKey = "hermes:entity:" + userId;
Map<Object, Object> rawMap = redisTemplate.opsForHash().entries(redisKey);
if (rawMap.isEmpty()) {
// Redis 未命中,从 MySQL 加载
rawMap = entityRepository.findByUserId(userId);
if (!rawMap.isEmpty()) {
redisTemplate.opsForHash().putAll(redisKey, rawMap);
redisTemplate.expire(redisKey, Duration.ofDays(30));
}
}
return rawMap.entrySet().stream()
.collect(Collectors.toMap(e -> e.getKey().toString(), Map.Entry::getValue));
}
private String generateSummary(String input, String response) {
// 调用轻量模型生成摘要,压缩长文本
return summaryLlm.call(
"用一句话概括以下对话的核心信息:\n用户:" + input + "\nAgent:" + response
);
}
}生产级 Hermes 部署
application.yml 完整配置
hermes:
# 全局配置
agent:
default-max-steps: 10
default-timeout-ms: 30000
# 记忆配置
memory:
short-term:
store: redis
ttl: 2h
max-messages: 20
long-term:
store: pgvector
embedding-model: text-embedding-3-small
top-k: 5
# 消息代理
message-broker:
type: redis # 单机用 redis,集群用 kafka
ack-timeout-ms: 5000
max-retry: 3
retry-backoff-ms: 1000
# 工具执行
tool-executor:
thread-pool-size: 20
default-timeout-ms: 5000
sandbox:
enabled: true # 代码执行工具开启沙箱
docker-image: hermes-sandbox:1.0
# 可观测性
observability:
tracing:
enabled: true
exporter: jaeger
endpoint: http://jaeger:14268/api/traces
sample-rate: 0.1 # 生产环境采样 10%
metrics:
enabled: true
export: prometheus
spring:
data:
redis:
host: redis-cluster
port: 6379
password: ${REDIS_PASSWORD}
datasource:
url: jdbc:postgresql://pg:5432/hermes
username: ${DB_USER}
password: ${DB_PASSWORD}可观测性追踪
Hermes 原生集成 OpenTelemetry,每一次工具调用、每一条 Agent 间消息都自动产生 Span:
@Component
public class HermesTraceInterceptor implements HermesExecutionInterceptor {
private final Tracer tracer;
@Override
public void beforeToolCall(ToolCallEvent event) {
Span span = tracer.nextSpan()
.name("hermes.tool." + event.getToolName())
.tag("agent.id", event.getAgentId())
.tag("tool.name", event.getToolName())
.tag("trace.id", event.getTraceId())
.start();
event.setSpan(span);
}
@Override
public void afterToolCall(ToolCallEvent event, ToolResult result) {
Span span = event.getSpan();
span.tag("tool.status", result.isSuccess() ? "success" : "error");
span.tag("tool.duration.ms", String.valueOf(event.getDurationMs()));
if (!result.isSuccess()) {
span.tag("tool.error", result.getErrorMessage());
span.error(new RuntimeException(result.getErrorMessage()));
}
span.end();
}
@Override
public void beforeAgentMessage(HmpMessage message) {
// HMP 消息自动注入 traceContext,实现跨 Agent 链路追踪
Span span = tracer.nextSpan()
.name("hermes.message." + message.getMessageType())
.tag("msg.from", message.getFrom().getAgentId())
.tag("msg.to", message.getTo().getAgentId())
.tag("msg.type", message.getMessageType().name())
.start();
message.getTraceContext().setSpanId(span.context().spanIdString());
}
}高频面试题
Q1:Hermes 的工具调用机制和 OpenClaw 有什么核心区别?
核心区别在于工具的"协议化程度"。OpenClaw 将工具实现为流程图节点插件,工具入参通过流程变量(Map)传递,无运行时类型检查,工具失败只能触发全局重试或流程中断,缺乏细粒度的错误处理能力。Hermes 将工具定义为带
ToolDescriptor的独立服务,入参有 JSON Schema 约束,每次调用前做 Schema 验证,验证失败时将错误结构化反馈给 LLM,由 LLM 修正入参后自动重试(LLM 自愈循环)。此外 Hermes 工具调用天然产生 OpenTelemetry Span,不需要额外的监控代码。面试时可以用"弱类型流程变量 vs 强类型 ToolDescriptor"来概括这个区别。
Q2:Hermes 多智能体通信协议(HMP)如何保证消息可靠性?
HMP 的可靠性通过三层机制保证:持久化、ACK 确认、故障恢复。消息发送时先写入 Redis(携带 TTL),再发布到订阅频道;对于
ackRequired=true的消息,发送方会等待接收方回 ACK,超时未收到则触发重试(默认重试 3 次,指数退避);服务重启后,Broker 会自动扫描 Redis 中未 ACK 的消息重新投递,防止宕机丢消息。对于更高可靠性要求的场景(如金融级)可以将底层换成 Kafka,利用 Kafka 的 offset commit 机制进一步增强持久性。
Q3:Hermes 的四层记忆架构各解决什么问题?
四层记忆针对不同时效和不同用途设计:L1 短期记忆(Redis + TTL)存储当前会话对话历史,保证多轮对话的上下文连贯性,TTL 自动清理避免无限膨胀;L2 工作记忆(JVM 堆)存储当前任务的中间工具调用结果,任务结束即释放,不污染持久化存储;L3 语义记忆(VectorStore)将历史对话 Embedding 后存入向量库,通过语义相似度召回跨会话的历史记录,解决"用户上周问过类似问题,本次 Agent 需要知道历史答案"的场景;L4 实体记忆(Redis + MySQL)用结构化 KV 存储用户偏好和实体信息(如"这个用户习惯要详细解释"),Redis 做热缓存,MySQL 做持久化防止丢失。四层记忆各司其职,L1/L2 管当下,L3/L4 管历史。
Q4:在 Hermes 中如何防止多 Agent 并发执行时的状态竞争?
Hermes 在 Orchestrator 层通过任务 DAG + 依赖声明来防止竞争:每个子任务可以声明前置依赖(
dependsOn),有依赖的任务等待前置完成后才启动,Orchestrator 维护任务完成状态图,并发安全地更新(ConcurrentHashMap + CAS)。对于共享资源(如同一用户的 Entity Memory),Hermes 提供了@AgentLock注解,底层用 Redis 分布式锁防止多 Agent 同时修改同一用户的记忆状态。工具层面,读工具(db:read)允许并发,写工具(db:write、email:send)在工具 Descriptor 上标记exclusive=true,框架保证同一 sessionId 下写工具串行执行。
Q5:如何设计一个在 Hermes 中支持 Human-in-the-Loop 的 Agent 工作流?
Human-in-the-Loop 在 Hermes 中通过挂起/恢复机制实现:当 Agent 决策需要人工确认时,调用
hermesContext.suspend(approvalRequest)将当前执行状态(消息历史、工具调用进度、session 上下文)序列化到 Redis,返回一个resumeToken;同时通过通知 Agent 向审批人发送钉钉/企业微信消息,附带审批链接;审批人点击确认/拒绝后,后端调用hermesRuntime.resume(resumeToken, approvalResult)反序列化状态,Agent 从挂起点继续执行。挂起状态在 Redis 中默认保留 24 小时,超时未审批自动标记为失败。高风险操作(退款金额 > 10000 元、批量删除数据)必须配置 Human-in-the-Loop。
Q6:Hermes Agent 如何做 Token 成本优化?
成本优化从四个维度入手:模型分层——规划/决策用强模型(GPT-4o),执行简单步骤用小模型(GPT-4o-mini / Qwen-plus),写作/格式化用最轻量模型(Qwen-turbo),根据任务复杂度动态选择,整体成本可降低 60% 以上;工具结果压缩——数据库工具只返回关键字段和 topK 行,大文本工具返回摘要而非原文,避免无用信息占用上下文;记忆压缩——短期记忆超出窗口时用小模型生成摘要替代原始消息,长期记忆存 Embedding 而非全文;缓存工具调用结果——相同参数的工具调用结果缓存 5 分钟,Agent 在多步推理中重复查询同一数据时命中缓存,不重复消耗 Token 和外部 API 配额。
推荐阅读
相关框架文档
- Spring AI 官方文档 — Java 生态的 AI 统一抽象层,与 Hermes 框架互补
- LangSmith — Agent 追踪与评估工具,可用于 Hermes Agent 的可观测性集成参考
- AgentBench: Evaluating LLMs as Agents — Agent 评估 Benchmark,了解多智能体系统的能力评估方法论
知识星球深度内容
完整大厂面经(含详细答案、最新更新)、AI 项目源码、1v1 简历修改,扫码加入「AI 工程师加速社区」知识星球获取 👉 立即加入
