第 8 篇:工具与工具集------Hermes 的内置能力
引言
工具是 Hermes 代理可以调用的内置能力------文件读写、网页搜索、终端执行、浏览器操作等。工具集是相关工具的逻辑分组,可以按平台灵活启用或禁用。
查看与配置工具
bash
# 查看当前可用工具
hermes chat --toolsets "web,terminal,skills"
# 交互式配置工具
hermes tools
# 在会话中查看
/tools
内置工具集
| 工具集 | 包含的工具 | 说明 |
|---|---|---|
core |
read_file, write_file, list_files, search_files | 文件操作核心 |
terminal |
terminal, background_process | 命令行执行 |
web |
web_search, web_extract | 网页搜索和提取 |
browser |
browser_* | 浏览器自动化 |
skills |
skill_manage, skills_search | 技能管理 |
code_execution |
execute_code | Python 脚本执行 |
memory |
memory | 持久记忆读写 |
image_generation |
generate_image | AI 图像生成 |
voice |
voice_record, voice_play | 语音录制和播放 |
delegation |
delegate | 子代理委派 |
cron |
cronjob | 定时任务管理 |
mcp |
mcp_* | MCP 工具调用 |
终端后端
终端工具的执行后端可配置:
yaml
terminal:
backend: local # 默认------直接本地执行
# backend: docker # Docker 隔离
# backend: ssh # 远程服务器
Docker 隔离配置:
yaml
terminal:
backend: docker
docker:
image: hermes-sandbox:latest
resources:
memory: 2g
cpus: 2
security:
no_network: false
read_only_root: true
后台进程管理:
终端工具可以启动后台进程:
python
# 代理可以调用此工具
tool: background_process
action: start
command: "python train.py --epochs 100"
# 返回: {"session_id": "proc_abc123", "pid": 12345}
# 然后管理进程
tool: background_process
action: status
session_id: "proc_abc123"
tool: background_process
action: stop
session_id: "proc_abc123"
按平台配置工具集
不同平台可以启用不同的工具集:
yaml
# CLI 拥有全部工具
platform_toolsets:
cli:
- core
- terminal
- web
- browser
- code_execution
- skills
- memory
- delegation
- cron
# Telegram 只有安全子集
telegram:
- core
- web
- terminal
# Discord 排除终端
discord:
- core
- web
- skills
Sudo 支持
对于需要提升权限的命令:
yaml
terminal:
sudo:
enabled: true
password: ${SUDO_PASSWORD} # 从 .env 读取
工具预览长度
控制工具调用预览行的最大字符数:
yaml
display:
tool_preview_length: 80 # 截断到 80 字符(0 = 无限制)
Q&A
Q1: 如何为某个消息平台禁用所有危险工具? A1: 在 config.yaml 的 platform_toolsets 中为该平台只列出安全工具集(如 core、web),不列出 terminal。或者使用 agent.disabled_toolsets 全局禁用特定工具集。
Q2: Docker 终端后端如何保护 API 密钥安全? A2: Hermes 支持出口凭证注入代理(egress credential-injection proxy),沙箱永远不会看到你的真实 API 密钥------只有不透明的代理令牌。通过 hermes egress setup && hermes egress start 启用。
Q3: Singularity 后端如何使用? A8: 配置 terminal.backend: singularity,并预构建 SIF 镜像:
bash
# 预构建 SIF 以支持并行 worker
singularity build hermes-sandbox.sif Singularity.def
然后在 config.yaml 中指定 SIF 路径即可。
@buray 的 hermes mcp-server:让 Hermes 成为双向 MCP 节点
Hermes Agent 深度拆解 · 第 08 篇
@buray 的 hermes mcp-server:让 Hermes 成为双向 MCP 节点
大多数人把 Hermes 当作 MCP 客户端------调用外部工具。但 @buray 发现了一个被所有人忽略的事实:MCP 的服务端侧完全缺失。Claude Desktop、Cursor、VS Code Copilot 无法反向调用 Hermes 的终端、文件、记忆和自主 Agent 能力。他通过一次"PR 考古"------逐条遍历所有 open + closed PR------定位到 PR #64 只补了客户端,于是他写了 hermes mcp-server,三层架构(JSON-RPC 协议层 + 桥接层 + 传输层),暴露 9 个工具,让 Claude Desktop 能把整个自主任务委派给 Hermes。本文从 PR #97 到 code review 的 4 个 blocking issue,1:1 逆向完整链路。
作者:@buray (Discord) / ygd58 (GitHub) 分类:Integrations / Dev Workflow 来源:Discord + GitHub PR #97 发布:2026-02-26
PART 01
案例背景 + 溯源 + 整体架构
开篇:Hermes 只能"打电话",不能"接电话"
2026 年初,MCP(Model Context Protocol)已经成为 AI Agent 生态的事实标准。Claude Desktop、Cursor、VS Code Copilot 都支持通过 MCP 连接外部工具服务器。Hermes 社区也在 PR #64 中由 eren-karakus0 添加了 MCP 客户端支持------Hermes 可以作为客户端,调用外部 MCP 服务器提供的工具。这让 Hermes 拥有了"打电话"的能力:连接 Brave Search、GitHub、Filesystem 等外部 MCP 工具。
但社区用户 @buray(GitHub: ygd58,display name "buray")发现了一个被所有人忽略的问题:MCP 的服务端侧完全缺失。Hermes 可以调用外部工具,但外部 MCP 客户端(Claude Desktop、Cursor、VS Code Copilot)无法反向调用 Hermes 的终端、文件操作、记忆系统和自主 Agent 能力。Hermes 只能"打电话",不能"接电话"------它不是一个双向 MCP 节点。
作者原话
"I mapped all open + closed PRs to find what was truly missing. PR #64 added MCP client support (Hermes → external tools). The server side was completely absent. So I built hermes mcp-server --- making Hermes a full MCP server so Claude Desktop, Cursor, and any MCP client can use Hermes's tools directly."------@buray 在 Discord 社区中的原话。
溯源信息
溯源渠道与原始链接
作者@buray(Discord)/ ygd58(GitHub,display name "buray")
GitHub 主页github.com/ygd58
原始 PRgithub.com/NousResearc...(Closed, not merged)
官方用户故事hermes-agent.nousresearch.com/docs/user-s...
发布日期2026-02-26
分类Integrations / Dev Workflow
PR 状态Closed(未合并),但概念被后续 PR #16226 继承
业务问题:Claude Desktop 用不了 Hermes 的能力
想象一个常见场景:你在 Claude Desktop 中工作,需要执行一段终端命令、读取本地文件、搜索 Web 内容,甚至需要委派一个完整的自主任务给一个 7x24 小时运行的 Agent。Claude Desktop 本身没有终端访问能力,也没有持久化记忆系统。但 Hermes 有------Hermes 拥有 terminal、read_file、write_file、web_search、web_extract、memory_read、memory_write、list_skills 和最关键的 run_agent(自主任务委派)等 9 个核心工具。
问题在于:这些工具被锁在 Hermes 内部,外部 MCP 客户端无法触达。PR #64 让 Hermes 可以作为 MCP 客户端去调用外部工具,但没有人写服务端------没有人让 Hermes 把自己的工具暴露出去,供其他 MCP 客户端调用。这就像你有了一部电话可以打出去,但别人打不进来。
| 场景 | PR #97 之前 | PR #97 之后 |
|---|---|---|
| Claude Desktop → Hermes terminal(终端执行) | ❌ 不可用 | ✅ 可用 |
| Cursor → Hermes web_search(Web 搜索) | ❌ 不可用 | ✅ 可用 |
| 任意 MCP 客户端 → Hermes memory(记忆读写) | ❌ 不可用 | ✅ 可用 |
| 任意 MCP 客户端 → run_agent(完整自主任务委派) | ❌ 不可用 | ✅ 可用 |
"PR 考古":@buray 如何定位到缺失的一环
@buray 的方法论值得每一个贡献者学习。他没有凭直觉开始写代码,而是做了一次彻底的 "PR 考古"------逐条遍历 Hermes 仓库中所有 open 和 closed 的 PR,建立一张完整的"功能覆盖图":
| PR | 方向 | 作者 | 覆盖了什么 | 缺失了什么 |
|---|---|---|---|---|
| #64 | MCP 客户端 | eren-karakus0 | Hermes → 外部 MCP 工具(stdio + HTTP 传输,MCPManager 多服务器编排,85 单元测试) | 仅客户端方向,无服务端 |
| 其他 PR | 各种 | 各种 | Telegram/WhatsApp/Discord/Slack 网关、浏览器工具、记忆系统等 | 无任何 PR 涉及 MCP 服务端 |
| #97(本篇) | MCP 服务端 | @buray | 外部 MCP 客户端 → Hermes 工具(9 个工具暴露) | 填补了服务端空白 |
PR #64 背景
PR #64 由 eren-karakus0 提交,为 Hermes 添加了 MCP 客户端支持。它实现了 stdio + HTTP 双传输模式、MCPManager 多服务器编排能力,附带 85 个单元测试。这是 Hermes 迈向 MCP 生态的第一步------但只走了半步:客户端方向。@buray 的 PR #97 补上了另外半步:服务端方向。两者合在一起,Hermes 才成为真正的双向 MCP 节点。
@buray 的结论很清晰:PR #64 补了 MCP 客户端(Hermes → 外部工具),但服务端完全缺失。于是他开始构建 hermes mcp-server------一个完整的 MCP 服务器,让 Hermes 把自己的 9 个核心工具暴露给任何 MCP 客户端调用。
三层架构设计
@buray 将 MCP 服务器设计为三个清晰的层次:协议层 (JSON-RPC 2.0,MCP spec 2024-11-05)、桥接层 (将 MCP 方法调用映射到 Hermes 内部工具)、传输层(stdio 纯 stdlib asyncio + HTTP aiohttp)。用他自己的话说:
作者原话
"Built in three layers: protocol (JSON-RPC 2.0), bridge (maps MCP calls to Hermes internals), transport (stdio + HTTP). The run_agent tool is the highlight --- lets Claude Desktop delegate entire autonomous tasks to Hermes."
五层架构图
从 MCP 客户端发起请求到 Hermes 工具执行完毕,整个链路穿越五层。顶层是 MCP 客户端(Claude Desktop / Cursor / VS Code Copilot),中间是协议层和桥接层,底层是传输层和 9 个 Hermes 工具,持久层保存 Hermes 的记忆和状态,部署层提供本地 stdio 管道或 HTTP localhost:8765 两种接入方式。
顶层 · 客户端
Claude Desktop
stdio 模式
Cursor
stdio / HTTP
VS Code Copilot
stdio 模式
↓ JSON-RPC 2.0 请求(initialize / tools/list / tools/call)
中层 · 协议层
Protocol Layer(JSON-RPC 2.0)--- MCP spec 2024-11-05 · initialize 握手 · tools/list 工具枚举 · tools/call 工具调用
↓ MCP 方法 → Hermes 工具映射
中层 · 桥接层
Bridge Layer --- MCP method dispatch → Hermes internal tools · 9 工具路由表 · 参数转换 · 结果封装
↓ 工具执行
底层 · 传输层
stdio Transport
纯 stdlib asyncio
stdin/stdout
HTTP Transport
aiohttp server
127.0.0.1:8765
↓ 9 个 Hermes 工具暴露
底层 · 工具层
terminal
read_file
write_file
web_search
web_extract
memory_read
memory_write
list_skills
run_agent ★
↓ 持久化存储
持久层
Hermes Memory
长期记忆系统
state.db
会话状态
File System
本地文件读写
↓ 部署运维
部署层
本地 stdio 管道
hermes mcp-server
HTTP localhost:8765
hermes mcp-server --http
9 个 MCP 工具一览
| 工具名 | 方向 | 功能 | 对应 Hermes 内部能力 |
|---|---|---|---|
| terminal | MCP 客户端 → Hermes | 执行终端命令 | Hermes terminal 工具(需 Tirith 审批门) |
| read_file | MCP 客户端 → Hermes | 读取本地文件 | Hermes 文件读取 |
| write_file | MCP 客户端 → Hermes | 写入本地文件 | Hermes 文件写入 |
| web_search | MCP 客户端 → Hermes | Web 搜索 | Hermes Web 搜索引擎 |
| web_extract | MCP 客户端 → Hermes | 提取网页内容 | Hermes 网页内容提取 |
| memory_read | MCP 客户端 → Hermes | 读取 Hermes 记忆 | Hermes 记忆系统(读取) |
| memory_write | MCP 客户端 → Hermes | 写入 Hermes 记忆 | Hermes 记忆系统(写入) |
| list_skills | MCP 客户端 → Hermes | 列出可用技能 | Hermes 技能目录 |
| run_agent ★ | MCP 客户端 → Hermes | 委派完整自主任务 | Hermes Agent 内核(自主任务执行) |
run_agent 是核心亮点
run_agent 是 9 个工具中最具价值的一个。它允许 Claude Desktop、Cursor 等 MCP 客户端将一个完整的自主任务委派给 Hermes 执行。这意味着 Claude Desktop 不再只是一个对话式助手------它可以委托 Hermes 在后台执行多步骤、长时间运行的自主任务(如"搜索这个课题的所有相关论文,提取关键信息,写入记忆系统,然后生成一份摘要报告"),然后接收最终结果。
PR 基本数据
| 指标 | 数值 | 说明 |
|---|---|---|
| 文件变更 | 16 files | 涉及核心服务器、CLI 注册、测试等多个模块 |
| 提交数 | 23 commits | 迭代式开发,从协议层到传输层逐步完善 |
| 核心文件 1 | gateway/mcp_server.py | MCP 服务器核心(stdio + HTTP 传输) |
| 核心文件 2 | hermes_cli/mcp_commands.py | CLI 命令注册 |
| 核心文件 3 | tests/test_mcp_server.py | 27 单元测试(schema、协议合规、工具桥接、JSON-RPC 格式) |
| 测试结果 | 28 passed | PR 声称 27 个测试,实际运行 28 个通过 |
| PR 状态 | Closed(未合并) | code review 发现 4 个 blocking issue |
PART 02
从零搭建教程 + 全 Hermes 命令参考
第一步:前置条件检查
在开始构建 hermes mcp-server 之前,确保你的环境满足以下条件:
| 组件 | 最低要求 | 说明 |
|---|---|---|
| Hermes Agent | 已安装并可运行 | hermes --version 可正常输出 |
| Python | 3.11+ | 需要 asyncio 和 type hints 支持 |
| aiohttp | 3.9+ | HTTP 传输层依赖 |
| Claude Desktop | 已安装 | 用于测试 stdio 模式 MCP 集成 |
| Cursor | 已安装(可选) | 用于测试 HTTP 模式 MCP 集成 |
| VS Code + Copilot | 已安装(可选) | 第三个测试目标 |
第二步:获取代码
由于 PR #97 未被合并,你有两种方式获取代码:克隆 @buray 的 fork,或从 PR diff 手动重建。推荐前者:
bash
# ═══ 方式 1:克隆 @buray 的 fork(推荐)═══
git clone https://github.com/ygd58/hermes-agent.git
cd hermes-agent
git checkout mcp-server-feature
# ═══ 方式 2:从 PR diff 重建(适合学习)═══
# 先克隆官方仓库
git clone https://github.com/NousResearch/hermes-agent.git
cd hermes-agent
# 拉取 PR #97 的 diff
git fetch origin pull/97/head:pr-97
git checkout pr-97
第三步:构建 gateway/mcp_server.py(完整重构版)
以下是 @buray 的 mcp_server.py 的完整重构版本,包含三层架构的完整实现。这份代码基于 PR #97 的 diff 重建,并修复了 code review 中发现的 Click/argparse 不匹配问题:
python
# ═══ gateway/mcp_server.py --- MCP 服务器核心 ═══
# 三层架构:协议层(JSON-RPC 2.0)+ 桥接层 + 传输层(stdio + HTTP)
import asyncio
import json
import sys
import logging
from typing import Any, Optional
# ── HTTP 传输层依赖(可选,仅 --http 模式需要)──
try:
from aiohttp import web
AIOHTTP_AVAILABLE = True
except ImportError:
AIOHTTP_AVAILABLE = False
logger = logging.getLogger(__name__)
# ═══════════════════════════════════════════════════
第一层:协议层(JSON-RPC 2.0)
MCP spec version: 2024-11-05
# ═══════════════════════════════════════════════════
MCP_PROTOCOL_VERSION = "2024-11-05" # MCP 协议版本
SERVER_NAME = "hermes-mcp-server" # 服务器名称
SERVER_VERSION = "0.1.0" # 服务器版本
class MCPServer:
"""MCP 服务器核心类:处理 JSON-RPC 2.0 请求并分发给 Hermes 工具"""
def __init__(self):
# 桥接层实例:负责将 MCP 方法映射到 Hermes 内部工具
self.bridge = MCPBridge()
# 已初始化的客户端信息
self.initialized = False
self.client_info = {}
async def handle_request(self, request: dict) -> dict:
"""
处理 JSON-RPC 2.0 请求的入口
- 解析 method 字段,分发给对应的处理器
- 返回 JSON-RPC 2.0 响应
"""
method = request.get("method")
req_id = request.get("id")
params = request.get("params", {})
# JSON-RPC 2.0 方法路由表
handlers = {
"initialize": self._handle_initialize,
"tools/list": self._handle_tools_list,
"tools/call": self._handle_tools_call,
"ping": self._handle_ping,
}
handler = handlers.get(method)
if handler is None:
return self._error(req_id, -32601, f"Method not found: {method}")
try:
result = await handler(params)
return {"jsonrpc": "2.0", "id": req_id, "result": result}
except Exception as e:
logger.exception(f"Error handling {method}")
return self._error(req_id, -32603, str(e))
async def _handle_initialize(self, params: dict) -> dict:
"""initialize 握手:交换协议版本和服务器能力"""
self.client_info = params.get("clientInfo", {})
self.initialized = True
return {
"protocolVersion": MCP_PROTOCOL_VERSION,
"capabilities": {
"tools": {}, # 声明支持 tools 能力
},
"serverInfo": {
"name": SERVER_NAME,
"version": SERVER_VERSION,
},
}
async def _handle_tools_list(self, params: dict) -> dict:
"""tools/list:返回所有可用的 MCP 工具定义"""
return {"tools": self.bridge.get_tool_definitions()}
async def _handle_tools_call(self, params: dict) -> dict:
"""tools/call:执行指定工具调用,通过桥接层分发"""
tool_name = params.get("name")
arguments = params.get("arguments", {})
result = await self.bridge.call_tool(tool_name, arguments)
return {"content": [{"type": "text", "text": result}]}
async def _handle_ping(self, params: dict) -> dict:
"""ping:心跳检测"""
return {}
def _error(self, req_id, code, message):
"""构造 JSON-RPC 2.0 错误响应"""
return {
"jsonrpc": "2.0",
"id": req_id,
"error": {"code": code, "message": message},
}
# ═══════════════════════════════════════════════════
第二层:桥接层(MCP 方法 → Hermes 内部工具映射)
# ═══════════════════════════════════════════════════
class MCPBridge:
"""桥接层:将 MCP 工具调用映射到 Hermes 内部工具执行"""
# 9 个 MCP 工具的 schema 定义
TOOL_DEFINITIONS = [
{
"name": "terminal",
"description": "Execute a terminal command on the host system",
"inputSchema": {
"type": "object",
"properties": {"command": {"type": "string"}},
"required": ["command"],
},
},
{
"name": "read_file",
"description": "Read the contents of a file",
"inputSchema": {
"type": "object",
"properties": {"path": {"type": "string"}},
"required": ["path"],
},
},
{
"name": "write_file",
"description": "Write content to a file",
"inputSchema": {
"type": "object",
"properties": {
"path": {"type": "string"},
"content": {"type": "string"},
},
"required": ["path", "content"],
},
},
{
"name": "web_search",
"description": "Search the web for information",
"inputSchema": {
"type": "object",
"properties": {"query": {"type": "string"}},
"required": ["query"],
},
},
{
"name": "web_extract",
"description": "Extract content from a web page URL",
"inputSchema": {
"type": "object",
"properties": {"url": {"type": "string"}},
"required": ["url"],
},
},
{
"name": "memory_read",
"description": "Read from Hermes memory store",
"inputSchema": {
"type": "object",
"properties": {"key": {"type": "string"}},
"required": ["key"],
},
},
{
"name": "memory_write",
"description": "Write to Hermes memory store",
"inputSchema": {
"type": "object",
"properties": {
"key": {"type": "string"},
"value": {"type": "string"},
},
"required": ["key", "value"],
},
},
{
"name": "list_skills",
"description": "List all available Hermes skills",
"inputSchema": {"type": "object", "properties": {}},
},
{
"name": "run_agent",
"description": "Delegate an autonomous task to Hermes Agent",
"inputSchema": {
"type": "object",
"properties": {
"task": {"type": "string", "description": "The task description for the agent"},
"context": {"type": "string", "description": "Optional context"},
},
"required": ["task"],
},
},
]
def get_tool_definitions(self) -> list:
"""返回所有工具的 schema 定义"""
return self.TOOL_DEFINITIONS
async def call_tool(self, name: str, arguments: dict) -> str:
"""执行工具调用 --- 路由到对应的 Hermes 内部工具"""
# 工具路由表:工具名 → 处理函数
dispatch = {
"terminal": self._tool_terminal,
"read_file": self._tool_read_file,
"write_file": self._tool_write_file,
"web_search": self._tool_web_search,
"web_extract": self._tool_web_extract,
"memory_read": self._tool_memory_read,
"memory_write": self._tool_memory_write,
"list_skills": self._tool_list_skills,
"run_agent": self._tool_run_agent,
}
handler = dispatch.get(name)
if handler is None:
raise ValueError(f"Unknown tool: {name}")
return await handler(arguments)
# ── 9 个工具的具体实现 ──
async def _tool_terminal(self, args):
"""终端命令执行"""
import subprocess
result = subprocess.run(
args["command"], shell=True,
capture_output=True, text=True, timeout=300
)
return result.stdout + result.stderr
async def _tool_read_file(self, args):
"""读取文件内容"""
with open(args["path"], "r") as f:
return f.read()
async def _tool_write_file(self, args):
"""写入文件内容"""
with open(args["path"], "w") as f:
f.write(args["content"])
return f"Written to {args['path']}"
async def _tool_web_search(self, args):
"""Web 搜索 --- 调用 Hermes 搜索引擎"""
# 实际实现中调用 Hermes 的 web_search 工具
from hermes.tools.search import web_search as hermes_search
results = await hermes_search(args["query"])
return json.dumps(results, ensure_ascii=False)
async def _tool_web_extract(self, args):
"""网页内容提取"""
from hermes.tools.search import web_extract as hermes_extract
content = await hermes_extract(args["url"])
return content
async def _tool_memory_read(self, args):
"""读取 Hermes 记忆"""
from hermes.memory import MemoryStore
store = MemoryStore()
return store.read(args["key"])
async def _tool_memory_write(self, args):
"""写入 Hermes 记忆"""
from hermes.memory import MemoryStore
store = MemoryStore()
store.write(args["key"], args["value"])
return f"Memory written: {args['key']}"
async def _tool_list_skills(self, args):
"""列出所有可用技能"""
from hermes.skills import SkillRegistry
registry = SkillRegistry()
skills = registry.list_all()
return json.dumps(skills, ensure_ascii=False)
async def _tool_run_agent(self, args):
"""
run_agent --- 核心亮点工具
委派一个完整的自主任务给 Hermes Agent 执行
Claude Desktop 可以通过此工具将多步骤任务委托给 Hermes
"""
from hermes.agent import HermesAgent
agent = HermesAgent()
# 启动自主任务执行,返回最终结果
result = await agent.run(
task=args["task"],
context=args.get("context", ""),
)
return result
# ═══════════════════════════════════════════════════
第三层:传输层(stdio + HTTP)
# ═══════════════════════════════════════════════════
class StdioTransport:
"""stdio 传输层:纯 stdlib asyncio,通过 stdin/stdout 通信"""
def __init__(self, server: MCPServer):
self.server = server
async def run(self):
"""主循环:从 stdin 读取 JSON-RPC 请求,写入 stdout 响应"""
reader = asyncio.StreamReader()
protocol = asyncio.StreamReaderProtocol(reader)
await asyncio.get_event_loop().connect_readpipe(
lambda: protocol, sys.stdin
)
while True:
# 逐行读取 JSON-RPC 请求
line = await reader.readline()
if not line:
break
try:
request = json.loads(line.decode())
response = await self.server.handle_request(request)
# 将响应写入 stdout(JSON + 换行符分隔)
sys.stdout.write(json.dumps(response) + "\n")
sys.stdout.flush()
except json.JSONDecodeError:
continue
class HTTPTransport:
"""HTTP 传输层:aiohttp server,监听 127.0.0.1:8765"""
def __init__(self, server: MCPServer, host="127.0.0.1", port=8765):
self.server = server
self.host = host
self.port = port
async def run(self):
"""启动 aiohttp HTTP 服务器"""
app = web.Application()
app.router.add_post("/mcp", self._handle_http)
runner = web.AppRunner(app)
await runner.setup()
site = web.TCPSite(runner, self.host, self.port)
await site.start()
logger.info(f"MCP HTTP server listening on {self.host}:{self.port}")
# 保持服务器运行
await asyncio.Event().wait()
async def _handle_http(self, request):
"""处理 HTTP POST 请求中的 JSON-RPC 消息"""
data = await request.json()
response = await self.server.handle_request(data)
return web.json_response(response)
# ═══════════════════════════════════════════════════
入口函数
# ═══════════════════════════════════════════════════
def run_stdio():
"""启动 stdio 模式(Claude Desktop 默认使用此模式)"""
server = MCPServer()
transport = StdioTransport(server)
asyncio.run(transport.run())
def run_http(host="127.0.0.1", port=8765):
"""启动 HTTP 模式(Cursor 等 HTTP MCP 客户端使用此模式)"""
if not AIOHTTP_AVAILABLE:
raise RuntimeError("aiohttp is required for HTTP mode. Install: pip install aiohttp")
server = MCPServer()
transport = HTTPTransport(server, host, port)
asyncio.run(transport.run())
第四步:注册 CLI 命令(修复 Click/argparse 不匹配)
code review 中发现的第一个 blocking issue 就是 CLI 命令未正确连接。Hermes CLI 使用 argparse 框架,但 @buray 的 PR 中使用了 Click 装饰器风格注册命令,导致 hermes mcp-server 报错 argument command: invalid choice: 'mcp-server'。以下是修复后的注册方式:
python
# ═══ hermes_cli/mcp_commands.py --- CLI 命令注册 ═══
# 修复:使用 argparse 而非 Click,匹配 Hermes CLI 框架
import argparse
from gateway.mcp_server import run_stdio, run_http
def register_mcp_commands(subparsers: argparse._SubParsersAction):
"""
在 Hermes CLI 的 argparse subparser 中注册 mcp-server 命令
--- 必须使用 argparse,不能用 Click
"""
# 注册 mcp-server 子命令
mcp_parser = subparsers.add_parser(
"mcp-server",
help="Start Hermes as an MCP server (bidirectional MCP node)",
)
# --http 标志:切换到 HTTP 传输模式
mcp_parser.add_argument(
"--http",
action="store_true",
default=False,
help="Use HTTP transport (default: stdio)",
)
# --host:HTTP 模式监听地址
mcp_parser.add_argument(
"--host",
default="127.0.0.1",
help="HTTP listen host (default: 127.0.0.1)",
)
# --port:HTTP 模式监听端口
mcp_parser.add_argument(
"--port",
type=int,
default=8765,
help="HTTP listen port (default: 8765)",
)
mcp_parser.set_defaults(func=_handle_mcp_server)
def _handle_mcp_server(args: argparse.Namespace):
"""处理 mcp-server 命令的实际执行"""
if args.http:
# HTTP 模式:监听 127.0.0.1:8765
run_http(host=args.host, port=args.port)
else:
# stdio 模式:Claude Desktop 默认使用
run_stdio()
# ═══ 在 hermes_cli/main.py 中调用注册函数 ═══
# from hermes_cli.mcp_commands import register_mcp_commands
#
# def main():
# parser = argparse.ArgumentParser(prog="hermes")
# subparsers = parser.add_subparsers(dest="command")
# ... # 其他子命令注册
# register_mcp_commands(subparsers) # 注册 mcp-server
# ...
Blocking Issue #1:Click vs argparse 不匹配
code review 发现 hermes mcp-server 命令未正确连接到 argparse subparser。报错信息:hermes: error: argument command: invalid choice: 'mcp-server'。根因是 PR 中使用了 Click 装饰器风格注册命令,而 Hermes CLI 使用 argparse 框架。修复方法如上:在 hermes_cli/main.py 的 argparse subparser 中调用 register_mcp_commands(subparsers)。
第五步:配置 Claude Desktop
Claude Desktop 通过 stdio 模式连接 MCP 服务器。编辑 Claude Desktop 的 MCP 配置文件:
json
// ═══ Claude Desktop MCP 配置 ═══
// 文件路径(macOS): ~/Library/Application Support/Claude/claude_desktop_config.json
// 文件路径(Windows): %APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"hermes": {
"command": "hermes",
"args": ["mcp-server"]
}
}
}
第六步:配置 Cursor MCP
Cursor 支持 HTTP 模式连接 MCP 服务器。先启动 HTTP 模式:
bash
# ═══ 启动 HTTP 模式 MCP 服务器 ═══
hermes mcp-server --http
# 输出:MCP HTTP server listening on 127.0.0.1:8765
json
// ═══ Cursor MCP 配置 ═══
// 文件路径: ~/.cursor/mcp.json
{
"mcpServers": {
"hermes": {
"url": "http://127.0.0.1:8765/mcp"
}
}
}
第七步:测试 JSON-RPC initialize 握手
配置完成后,测试 MCP 协议握手是否成功。在 stdio 模式下手动发送 JSON-RPC 请求:
json
# ═══ 测试 initialize 握手 ═══
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"clientInfo":{"name":"test","version":"1.0"}}}' | hermes mcp-server
# 期望输出:
# {"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2024-11-05","capabilities":{"tools":{}},"serverInfo":{"name":"hermes-mcp-server","version":"0.1.0"}}}
# ═══ 测试 tools/list 枚举 9 个工具 ═══
echo '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' | hermes mcp-server
# 期望输出:返回 9 个工具的定义数组
# ═══ 测试 tools/call 调用 list_skills ═══
echo '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"list_skills","arguments":{}}}' | hermes mcp-server
第八步:逐个测试 9 个工具
| 工具 | 测试 JSON-RPC 请求 | 验证点 |
|---|---|---|
| terminal | {"name":"terminal","arguments":{"command":"echo hello"}} | 返回 "hello" |
| read_file | {"name":"read_file","arguments":{"path":"/tmp/test.txt"}} | 返回文件内容 |
| write_file | {"name":"write_file","arguments":{"path":"/tmp/test.txt","content":"hi"}} | 返回写入确认 |
| web_search | {"name":"web_search","arguments":{"query":"Hermes Agent"}} | 返回搜索结果 JSON |
| web_extract | {"name":"web_extract","arguments":{"url":"example.com"}} | 返回网页内容 |
| memory_read | {"name":"memory_read","arguments":{"key":"test_key"}} | 返回记忆值或空 |
| memory_write | {"name":"memory_write","arguments":{"key":"test_key","value":"hello"}} | 返回写入确认 |
| list_skills | {"name":"list_skills","arguments":{}} | 返回技能列表 JSON |
| run_agent | {"name":"run_agent","arguments":{"task":"say hello"}} | 返回 Agent 执行结果 |
全 Hermes MCP 命令参考
| 命令 | 模式 | 用途 | 典型客户端 |
|---|---|---|---|
| hermes mcp-server | stdio | 启动 MCP 服务器(stdio 传输,默认) | Claude Desktop、VS Code Copilot |
| hermes mcp-server --http | HTTP | 启动 MCP 服务器(HTTP 传输,监听 127.0.0.1:8765) | Cursor |
| hermes mcp-server --http --host 0.0.0.0 | HTTP | 启动 MCP 服务器(监听所有接口,需注意安全) | 远程 MCP 客户端 |
| hermes mcp-server --http --port 9000 | HTTP | 自定义端口 | 端口冲突时使用 |
| hermes mcp-server --help | --- | 查看帮助(注意:PR #97 中此命令会失败) | 调试用 |
| hermes mcp-client add | 客户端 | 添加外部 MCP 服务器(PR #64 功能) | Hermes 作为客户端时 |
| hermes mcp-client list | 客户端 | 列出已连接的外部 MCP 服务器 | PR #64 功能 |
注意:hermes mcp-server --help 在 PR #97 中会失败
code review 中确认 hermes mcp-server --help 会报错 invalid choice: 'mcp-server'。这是 blocking issue #1 的直接表现。在上面的第四步修复中已解决此问题。
PART 03
源码深度拆解 + 部署 + 复刻踩坑指南
核心代码 ①:JSON-RPC 2.0 协议处理器
JSON-RPC 2.0 是 MCP 的底层通信协议。每个请求包含 jsonrpc、id、method 和 params 四个字段。协议处理器需要支持三个核心方法:initialize(握手)、tools/list(工具枚举)、tools/call(工具调用)。
python
# ═══ JSON-RPC 2.0 协议处理器详解 ═══
async def handle_request(self, request: dict) -> dict:
# 1. 提取 JSON-RPC 2.0 标准字段
method = request.get("method") # 方法名:initialize / tools/list / tools/call / ping
req_id = request.get("id") # 请求 ID,用于匹配响应(通知类消息无 id)
params = request.get("params", {}) # 方法参数
# 2. 方法路由表 --- 将 method 映射到处理函数
handlers = {
"initialize": self._handle_initialize, # 握手:交换协议版本和能力
"tools/list": self._handle_tools_list, # 枚举:返回所有工具 schema
"tools/call": self._handle_tools_call, # 调用:执行指定工具
"ping": self._handle_ping, # 心跳:保活检测
}
# 3. 查找处理器,未找到返回 -32601 Method not found
handler = handlers.get(method)
if handler is None:
return self._error(req_id, -32601, f"Method not found: {method}")
# 4. 执行处理器,捕获异常返回 -32603 Internal error
try:
result = await handler(params)
# 5. 成功响应:{"jsonrpc":"2.0","id":req_id,"result":{...}}
return {"jsonrpc": "2.0", "id": req_id, "result": result}
except Exception as e:
# 6. 错误响应:{"jsonrpc":"2.0","id":req_id,"error":{"code":-32603,...}}
return self._error(req_id, -32603, str(e))
# ═══ initialize 握手 --- 协议版本协商 ═══
async def _handle_initialize(self, params: dict) -> dict:
# 客户端在连接后首先发送 initialize,携带自己的信息
self.client_info = params.get("clientInfo", {})
self.initialized = True
# 服务器返回:协议版本 + 能力声明 + 服务器信息
return {
"protocolVersion": "2024-11-05", # MCP spec 版本
"capabilities": {
"tools": {}, # 声明:本服务器支持 tools 能力
# 注意:不支持 resources 和 prompts(可扩展)
},
"serverInfo": {
"name": "hermes-mcp-server",
"version": "0.1.0",
},
}
# ═══ tools/list --- 返回 9 个工具的完整 schema ═══
async def _handle_tools_list(self, params: dict) -> dict:
# 直接返回桥接层中定义的工具 schema 列表
# 每个 schema 包含 name / description / inputSchema(JSON Schema 格式)
return {"tools": self.bridge.get_tool_definitions()}
# ═══ tools/call --- 执行工具调用 ═══
async def _handle_tools_call(self, params: dict) -> dict:
tool_name = params.get("name") # 工具名:terminal / read_file / ...
arguments = params.get("arguments", {}) # 工具参数(JSON Schema 验证后的值)
# 通过桥接层调用工具,获取结果
result = await self.bridge.call_tool(tool_name, arguments)
# MCP 规范要求结果封装在 content 数组中
return {
"content": [{
"type": "text", # 内容类型:text / image / resource
"text": result,
}]
}
核心代码 ②:桥接层 --- MCP 方法到 Hermes 工具的分发
桥接层是三层架构中承上启下的关键。它将 MCP 协议层的 tools/call 请求路由到 Hermes 内部工具,并负责参数转换和结果封装。
python
# ═══ 桥接层:工具路由表与分发逻辑 ═══
async def call_tool(self, name: str, arguments: dict) -> str:
"""
工具调用的核心分发函数
name: MCP 工具名(如 "terminal")
arguments: MCP 工具参数(如 {"command": "ls -la"})
返回: 工具执行结果(字符串)
"""
# 工具路由表 --- 工具名 → 处理函数的映射
dispatch = {
"terminal": self._tool_terminal, # → subprocess.run
"read_file": self._tool_read_file, # → open().read()
"write_file": self._tool_write_file, # → open().write()
"web_search": self._tool_web_search, # → hermes.tools.search
"web_extract": self._tool_web_extract, # → hermes.tools.search
"memory_read": self._tool_memory_read, # → hermes.memory.MemoryStore
"memory_write": self._tool_memory_write, # → hermes.memory.MemoryStore
"list_skills": self._tool_list_skills, # → hermes.skills.SkillRegistry
"run_agent": self._tool_run_agent, # → hermes.agent.HermesAgent
}
# 查找处理函数
handler = dispatch.get(name)
if handler is None:
raise ValueError(f"Unknown tool: {name}")
# 执行工具并返回结果
return await handler(arguments)
# ═══ run_agent --- 核心亮点工具详解 ═══
async def _tool_run_agent(self, args):
"""
run_agent 是 9 个工具中最具价值的一个
--- 让 Claude Desktop / Cursor 委派完整自主任务给 Hermes
工作流程:
1. MCP 客户端(Claude Desktop)调用 run_agent,传入 task 描述
2. 桥接层调用 HermesAgent.run(),启动自主任务执行
3. Hermes Agent 内核自主规划、执行多步骤任务
(可能调用 terminal / web_search / memory 等内部工具)
4. 任务完成后,结果通过 MCP 协议返回给 Claude Desktop
这意味着 Claude Desktop 不再只是一个对话助手
--- 它可以委托 Hermes 在后台执行长时间运行的自主任务
"""
from hermes.agent import HermesAgent
# 创建 Hermes Agent 实例
agent = HermesAgent()
# 启动自主任务执行
# task: 任务描述(必填)
# context: 额外上下文(可选,用于传递背景信息)
result = await agent.run(
task=args["task"],
context=args.get("context", ""),
)
# 返回最终结果给 MCP 客户端
return result
核心代码 ③:stdio 传输层(纯 stdlib asyncio)
stdio 传输层不依赖任何第三方库,完全使用 Python 标准库的 asyncio 实现。Claude Desktop 通过子进程方式启动 hermes mcp-server,通过 stdin/stdout 管道进行 JSON-RPC 通信。
python
# ═══ stdio 传输层详解 ═══
class StdioTransport:
"""stdio 传输层:纯 stdlib asyncio 实现"""
def __init__(self, server: MCPServer):
self.server = server
async def run(self):
"""
stdio 主循环:
1. 将 sys.stdin 包装为 asyncio StreamReader
2. 逐行读取 JSON-RPC 请求
3. 交给 MCPServer.handle_request() 处理
4. 将响应写入 sys.stdout
"""
# 将标准输入包装为 asyncio StreamReader
# 使用 StreamReaderProtocol + connect_readpipe
reader = asyncio.StreamReader()
protocol = asyncio.StreamReaderProtocol(reader)
await asyncio.get_event_loop().connect_readpipe(
lambda: protocol, sys.stdin
)
while True:
# 逐行读取(每行一个 JSON-RPC 请求)
line = await reader.readline()
if not line:
break # EOF:客户端关闭了 stdin
try:
# 解析 JSON-RPC 2.0 请求
request = json.loads(line.decode())
# 交给协议层处理
response = await self.server.handle_request(request)
# 将响应写入 stdout(JSON + 换行符分隔)
sys.stdout.write(json.dumps(response) + "\n")
sys.stdout.flush() # 立即刷新,避免缓冲延迟
except json.JSONDecodeError:
# 非 JSON 行:跳过(可能是调试输出)
continue
stdio 传输的关键设计
stdio 模式是 Claude Desktop 的默认连接方式。Claude Desktop 会将 hermes mcp-server 作为子进程启动,通过管道与 stdin/stdout 通信。所有日志必须输出到 stderr,不能污染 stdout ------stdout 只能用于 JSON-RPC 响应,任何非 JSON 输出都会导致 Claude Desktop 解析失败。这就是为什么 sys.stdout.flush() 如此重要:不刷新缓冲区会导致响应延迟,Claude Desktop 可能超时。
核心代码 ④:HTTP 传输层(aiohttp server)
HTTP 传输层使用 aiohttp 提供 REST 接口,监听 127.0.0.1:8765/mcp。Cursor 等 HTTP MCP 客户端通过 POST 请求发送 JSON-RPC 消息。
python
# ═══ HTTP 传输层详解 ═══
class HTTPTransport:
"""HTTP 传输层:aiohttp server,监听 127.0.0.1:8765"""
def __init__(self, server: MCPServer, host="127.0.0.1", port=8765):
self.server = server
self.host = host # 监听地址(默认仅本地)
self.port = port # 监听端口(默认 8765)
async def run(self):
"""启动 aiohttp HTTP 服务器"""
app = web.Application()
# POST /mcp --- 接收 JSON-RPC 2.0 请求
app.router.add_post("/mcp", self._handle_http)
# 启动 aiohttp 应用
runner = web.AppRunner(app)
await runner.setup()
site = web.TCPSite(runner, self.host, self.port)
await site.start()
logger.info(f"MCP HTTP server listening on {self.host}:{self.port}")
# 阻塞主协程,保持服务器运行
await asyncio.Event().wait()
async def _handle_http(self, request):
"""处理 HTTP POST 请求中的 JSON-RPC 消息"""
# 从请求体解析 JSON-RPC 2.0 消息
data = await request.json()
# 交给协议层处理(与 stdio 模式共用同一个 handle_request)
response = await self.server.handle_request(data)
# 返回 JSON 响应
return web.json_response(response)
HTTP 模式的安全注意事项
HTTP 传输默认监听 127.0.0.1:8765(仅本地访问)。如果使用 --host 0.0.0.0 开放到外网,必须添加认证层。@buray 的原始实现中没有认证机制------任何能访问该端口的人都可以调用 Hermes 的 terminal 工具,等价于获得了主机 shell 权限。在生产环境中,应在 aiohttp 中间件中添加 token 认证或 IP 白名单。
核心代码 ⑤:run_agent 工具实现(核心亮点)
run_agent 是整个 MCP 服务器中最具价值的工具。它让 Claude Desktop 等外部 MCP 客户端能够将完整的自主任务委派给 Hermes Agent 执行,实现 Agent-to-Agent 委派。
python
# ═══ run_agent 工具实现详解 ═══
async def _tool_run_agent(self, args):
"""
run_agent --- 让外部 MCP 客户端委派自主任务给 Hermes
使用场景示例:
--- Claude Desktop: "帮我搜索所有关于 MCP 协议的论文,
提取关键信息,写入记忆系统,生成摘要报告"
--- Hermes Agent 自主执行:
1. web_search("MCP protocol paper")
2. web_extract(url) for each result
3. memory_write("mcp_papers", summary)
4. 返回最终摘要报告
--- Claude Desktop 接收报告,展示给用户
参数:
task (必填): 任务描述
context (可选): 背景上下文
返回:
Hermes Agent 的最终执行结果(字符串)
"""
from hermes.agent import HermesAgent
# 创建 Hermes Agent 实例
# Agent 会加载 Hermes 的完整工具集和记忆系统
agent = HermesAgent()
# 启动自主任务执行
# agent.run() 是异步的,可能执行很长时间
# 在此期间,Hermes Agent 自主决策、调用工具、迭代执行
result = await agent.run(
task=args["task"], # 任务描述
context=args.get("context", ""), # 可选上下文
)
# 返回最终结果给 MCP 客户端
# 结果通过 JSON-RPC tools/call 响应传回 Claude Desktop
return result
如何修复 code review 中的 4 个 Blocking Issues
2026-05-15,审查者 cadugevaerd 对 PR #97 进行了详细 review,提出了 4 个 blocking issues(实际包含 5 项)。以下是每个问题的根因分析和修复方案:
| # | Blocking Issue | 根因 | 修复方案 |
|---|---|---|---|
| 1 | CLI 命令未正确连接 | PR 使用 Click 装饰器注册命令,Hermes CLI 使用 argparse | 改为在 argparse subparser 中注册(见第四步) |
| 2 | MCP 工具绕过 Hermes 工具注册表 | 直接实现了 terminal/read_file/write_file,未路由到 Hermes tool registry,跳过了 Tirith 审批门 | 通过 hermes.tools.registry 路由所有工具调用 |
| 3 | Memory 写入错误路径 | 写入 HERMES_HOME/memories/MEMORY.md ,未使用 Hermes memory provider 系统 | 调用 hermes.memory.MemoryStore 的标准接口 |
| 4 | 范围漂移 | PR 中打包了无关的 weather/Notion/HF/GitHub/code-sandbox 工具 | 移除无关工具,只保留 9 个核心工具 |
| 5 | code_sandbox.py 安全风险 | 在主机上直接运行任意 Python,除 timeout 外无沙箱 | 移除 code_sandbox 或使用 Docker/容器隔离 |
修复 Issue #2:通过 Hermes 工具注册表路由(而非直接实现)
python
# ═══ 修复前(有问题):直接实现 terminal 工具 ═══
async def _tool_terminal(self, args):
import subprocess
result = subprocess.run(args["command"], shell=True, ...)
return result.stdout
# 问题:绕过了 Tirith 审批门,任何 MCP 客户端都能直接执行命令
# ═══ 修复后(正确):通过 Hermes 工具注册表路由 ═══
async def _tool_terminal(self, args):
from hermes.tools.registry import ToolRegistry
registry = ToolRegistry()
# 通过注册表调用 --- 自动经过 Tirith 审批门
# Tirith 会检查命令是否在白名单/需要人工确认
result = await registry.execute(
tool_name="terminal",
params={"command": args["command"]},
)
return result
修复 Issue #3:使用 Hermes memory provider(而非原始文件路径)
python
# ═══ 修复前(有问题):写入原始文件路径 ═══
async def _tool_memory_write(self, args):
path = os.path.join(HERMES_HOME, "memories", "MEMORY.md")
with open(path, "a") as f:
f.write(args["value"])
# 问题:绕过了 Hermes memory provider 系统,可能导致数据不一致
# ═══ 修复后(正确):使用 Hermes memory provider ═══
async def _tool_memory_write(self, args):
from hermes.memory import get_memory_provider
provider = get_memory_provider() # 获取标准 memory provider
# 通过 provider 写入 --- 自动处理索引/检索/持久化
provider.store(
key=args["key"],
value=args["value"],
)
return f"Memory written: {args['key']}"
安全考量
安全风险:terminal 工具等价于远程 shell
terminal 工具让 MCP 客户端可以在 Hermes 主机上执行任意命令。如果 HTTP 模式开放到公网且无认证,等价于将主机 shell 暴露给互联网。必须确保:
- stdio 模式仅限本地 Claude Desktop 使用
- HTTP 模式默认监听 127.0.0.1,不开放到 0.0.0.0
- 如需远程访问,必须添加 token 认证 + TLS
- terminal 工具必须路由到 Hermes tool registry,经过 Tirith 审批门
安全风险:code_sandbox.py 在主机上运行任意 Python
code review 指出 code_sandbox.py 在主机上直接执行任意 Python 代码,除了 timeout 之外没有任何沙箱隔离。这意味着恶意 MCP 客户端可以通过 code_sandbox 工具执行 import os; os.system("rm -rf /") 等破坏性代码。修复方案:移除 code_sandbox 工具,或使用 Docker 容器 / nsjail / firejail 等隔离机制。
复刻检查清单
- 克隆 @buray 的 fork:
git clone https://github.com/ygd58/hermes-agent.git - 确认 Hermes 已安装:
hermes --version正常输出 - 构建
gateway/mcp_server.py(三层架构完整实现) - 在
hermes_cli/mcp_commands.py中使用 argparse 注册 CLI 命令(不用 Click) - 在
hermes_cli/main.py中调用register_mcp_commands(subparsers) - 验证
hermes mcp-server --help不再报错 - 所有 9 个工具通过 Hermes tool registry 路由(不直接实现)
- memory 工具使用 Hermes memory provider(不写原始文件路径)
- terminal 工具经过 Tirith 审批门
- 移除所有无关工具(weather/Notion/HF/GitHub/code-sandbox)
- 运行
pytest tests/test_mcp_server.py,确认 28 个测试通过 - 测试 stdio 模式:echo JSON-RPC 请求管道到
hermes mcp-server - 测试 HTTP 模式:
hermes mcp-server --http+ curl POST - 配置 Claude Desktop:
claude_desktop_config.json添加 hermes 服务器 - 重启 Claude Desktop,确认 Hermes 工具出现在工具列表中
- 测试
run_agent工具:委派一个简单任务给 Hermes
踩坑总结
| # | 坑点 | 现象 | 根因 | 解法 |
|---|---|---|---|---|
| 1 | Click vs argparse CLI 框架不匹配 | hermes: error: argument command: invalid choice: 'mcp-server' | PR 用 Click 装饰器注册命令,Hermes CLI 用 argparse | 改为 argparse subparser 注册 |
| 2 | MCP 工具绕过 Hermes tool registry | terminal 工具直接 subprocess.run,无审批 | 直接实现而非路由到 registry | 通过 ToolRegistry.execute() 路由 |
| 3 | Memory 写入错误路径 | 记忆写入 HERMES_HOME/memories/MEMORY.md ,Hermes 内部读不到 | 未使用 Hermes memory provider | 调用 get_memory_provider().store() |
| 4 | terminal 工具缺少 Tirith 审批门 | 任意命令直接执行,无安全检查 | 绕过 registry 导致跳过审批 | 同 #2,通过 registry 自动经过 Tirith |
| 5 | code_sandbox 安全风险 | 任意 Python 在主机执行,无沙箱 | 仅 timeout 隔离,无容器/进程隔离 | 移除或使用 Docker 隔离 |
| 6 | PR 范围漂移 | PR 中包含 weather/Notion/HF/GitHub 等无关工具 | 一个 PR 塞入过多功能 | 拆分 PR,只保留 9 个核心 MCP 工具 |
| 7 | stdio 模式 stdout 污染 | Claude Desktop 解析 JSON-RPC 失败 | 日志输出到 stdout 而非 stderr | 所有日志重定向到 stderr |
| 8 | HTTP 模式无认证 | 公网可访问时等价于远程 shell | 原始实现无认证层 | 添加 token 认证中间件 |
| 9 | 测试数不匹配 | PR 声称 27 测试,实际 28 个通过 | 计数偏差 | 更新 PR 描述中的测试数 |
社区影响与后续 PR
虽然 PR #97 最终未被合并,但 @buray 的核心概念------让 Hermes 成为双向 MCP 节点 ------得到了社区的认可。2026-04-26,开发者 noizo 提交了 PR #16226(feat(delegation): add native Claude/Cursor bridge transport and personas),在 @buray 概念的基础上构建了原生的 Claude/Cursor 桥接传输和 persona 系统。
PR #16226:站在 @buray 肩膀上
noizo 的 PR #16226 继承了 @buray 的核心思路------双向 MCP 节点 + Agent 委派------但解决了 code review 中的所有 blocking issues:使用 argparse 注册 CLI、通过 tool registry 路由、使用 memory provider、移除无关工具。这证明了 @buray 的概念方向是正确的,问题只在于实现细节和 PR 范围控制。
结语:双向 MCP 节点的深远意义
@buray 的 hermes mcp-server 虽然未被合并,但它揭示了一个重要的架构方向:Hermes 不应只是一个 MCP 客户端,而应成为一个双向 MCP 节点。这意味着三个层面的范式转变:
| 层面 | 单向(PR #64 之后) | 双向(PR #97 之后) |
|---|---|---|
| Hermes 的角色 | MCP 客户端 --- 调用外部工具 | MCP 客户端 + 服务端 --- 既调用也提供服务 |
| Hermes 作为中间件 | 不是 --- 只能消费 | 是 --- Claude Desktop / Cursor 通过 Hermes 访问终端/文件/记忆 |
| Agent-to-Agent 委派 | 不支持 | 支持 --- Claude Desktop 通过 run_agent 委派任务给 Hermes |
| 多 Agent 编排 | 不支持 | 支持 --- 多个 MCP 客户端可同时委派任务给同一个 Hermes 实例 |
当 Hermes 成为双向 MCP 节点后,它实际上变成了一个AI 中间件:Claude Desktop 负责与用户对话,当需要执行终端命令、读写文件、搜索 Web、访问持久化记忆、或委派长时间运行的自主任务时,它通过 MCP 协议将请求转发给 Hermes。Hermes 作为后台 Agent 内核,7x24 小时运行,维护记忆状态,执行自主任务,返回结果。这就是 Agent-to-Agent 委派的基础------也是多 Agent 编排的起点。
本篇核心启示
@buray 的贡献不在于代码本身(PR 未合并),而在于他通过"PR 考古"发现了一个所有人忽略的架构缺口------MCP 服务端侧的完全缺失。他提出的三层架构(协议层 + 桥接层 + 传输层)和 run_agent 工具的概念,直接启发了后续的 PR #16226。有时候,提出正确的问题比给出完美的答案更重要------@buray 做了两件事:他发现了 Hermes 只能"打电话"不能"接电话"的问题,然后给出了让 Hermes 成为双向 MCP 节点的方向。
溯源引用
- @buray (ygd58), "hermes mcp-server: Make Hermes a bidirectional MCP node" --- Hermes Agent PR #97. 三层架构(JSON-RPC 2.0 协议层 + 桥接层 + stdio/HTTP 传输层),暴露 9 个 MCP 工具。2026-02-26. Closed, not merged. github.com/NousResearc...
- @buray (ygd58), GitHub 主页。Display name "buray",Discord 社区活跃成员。 github.com/ygd58
- Nous Research, Hermes Agent 官方用户故事页面。@buray 的 hermes mcp-server 被收录为社区用户故事。 hermes-agent.nousresearch.com/docs/user-s...
- eren-karakus0, "MCP client support" --- Hermes Agent PR #64. 添加 MCP 客户端支持(Hermes → 外部工具),stdio + HTTP 传输,MCPManager 多服务器编排,85 单元测试。 github.com/NousResearc...
- cadugevaerd, PR #97 Code Review (2026-05-15). 4 个 blocking issues:CLI Click/argparse 不匹配、工具绕过 registry、Memory 路径错误、范围漂移 + code_sandbox 安全风险。 github.com/NousResearc...
- noizo, "feat(delegation): add native Claude/Cursor bridge transport and personas" --- Hermes Agent PR #16226. 在 @buray 概念基础上构建原生 Claude/Cursor 桥接。2026-04-26. github.com/NousResearc...
- @buray, gateway/mcp_server.py --- MCP 服务器核心。stdio + HTTP 双传输,JSON-RPC 2.0 协议处理,9 工具桥接层。 github.com/NousResearc...
- @buray, hermes_cli/mcp_commands.py --- CLI 命令注册。hermes mcp-server (stdio) + hermes mcp-server --http (HTTP)。 github.com/NousResearc...
- @buray, tests/test_mcp_server.py --- 27 单元测试(PR 声称)/ 28 通过(实际)。覆盖 schema 验证、协议合规、工具桥接、JSON-RPC 格式。 github.com/NousResearc...
- Model Context Protocol (MCP) Specification, version 2024-11-05. JSON-RPC 2.0 协议基础,initialize / tools/list / tools/call 方法定义。 spec.modelcontextprotocol.io/
- Anthropic, Claude Desktop MCP 配置文档。mcpServers 配置格式,stdio 模式启动参数。 modelcontextprotocol.io/docs/user-g...
- aiohttp --- Python 异步 HTTP 客户端/服务器框架。hermes mcp-server HTTP 传输层依赖。 docs.aiohttp.org/
- Nous Research, Hermes Agent 官方文档。CLI 命令参考、工具注册表、记忆系统、Tirith 审批门、技能系统。 hermes-agent.nousresearch.com/docs/
- @buray, Discord 社区发言(Nous Research community)。PR 考古方法论、三层架构设计理念、run_agent 工具定位。 hermes-agent.nousresearch.com/docs/user-s...
Hermes Agent 深度拆解连载 · 第 08 篇 · @buray 的 hermes mcp-server:让 Hermes 成为双向 MCP 节点
溯源驱动 · 源码佐证 · 1:1 可复刻 · 禁止虚构
延伸阅读与交流
本文涉及的Hermes Agent自进化智能体技术体系,目前已有系统化的深度学习资源可供参考。中国通信工业协会通信和信息技术创新人才培养工程项目办公室将于近期组织相关技术专题分享,围绕本文讨论的AI原生架构、智能体工作流、自进化数据层等方向展开系统讲解。
专题信息
- 主题:AI原生Hermes自进化智能体系统
- 时间:2026年8月22-23日
- 形式:线上直播
- 内容方向:AI原生架构 · Hermes智能体拆解 · 全栈扩展 · 智能自动化 · 产品级实战 · Context Engine · 自进化数据层
分享嘉宾
王老师(Gavin),Agentic AI企业联合创始人兼CTO,十余年硅谷AI系统工程经验。长期深耕NLP、强化学习、可控AI与智能体系统架构,提出"语言即控制(Language as Control)"原创范式,在RLHF、PPO、DPO、GRPO等方向有系统化工程实践,推动智能体技术在社交媒体、医疗、金融、法律、教育等专业场景落地。联系邮箱:hiheartfirst@gmail.com
技术交流
- 联系人:Sam
- Hermes Agent技术文档:hermes-agent.nousresearch.com/docs/

021 | 统计信息与规划器决策树:ANALYZE 给规划器塞了哪些干货
导读:上一篇我们把代价模型拆成了五个 GUC 的纯算术。但代价公式里藏着一个最关键又最不可见的数------选择率 。规划器不能在执行时跑采样查询,所以
ANALYZE提前把每列压缩成一个紧凑的统计摘要写进pg_statistic:null 比例、平均宽度、最常见值、直方图、相关性,七项事实撑起规划器的日常。本篇先讲这些事实怎么读、怎么计算选择率、什么时候过期;然后带规划器走一遍它的决策树------三道大题、四种扫描、三种连接、组合爆炸下的 DP/GEQO 兜底,最后用一个真实的 cinetrack 查询把全套决策串起来。
统计信息与 pg_statistic
规划器不能在执行计划时直接向数据提问。一个为了搞清基数去跑采样查询的规划器,会比它要优化的查询本身还慢。所以 Postgres 预先为每一列计算了一份紧凑的统计摘要,存在一个叫 pg_statistic 的系统目录里。 ANALYZE 就是干这件事的命令。
你几乎永远不会直接读 pg_statistic。它把值存成原始 bytea 二进制大对象,列名也晦涩。人类用 pg_stats 视图------它是同一份数据的更友好投影:值被转成可读文本、行被限定为当前用户可读的列。
ANALYZE 到底采了什么
ANALYZE 从每张表里读一份随机样本 ,把它们塞进类型相关的统计函数里跑,把结果写进 pg_statistic。样本很小 。默认是 300 × default_statistics_target,而 default_statistics_target 自己默认 100------所以默认样本是 30,000 行,不管你的表是一百万行还是五亿行 。把某一列的 target 抬到 1000,堆样本就涨到 300,000:样本取所有列 target 的最大值,而不是全部列默认值的总和。(对小于 target 的表,Postgres 直接全扫。)
每一列它存下来这几样事实:
null_frac:该列NULL的比例。avg_width:平均宽度(字节),用于内存和磁盘代价计算。n_distinct:估计的不同值个数。正数是计数;-1 到 0 之间的负数表示占总行数的比例,-1 表示每个值都唯一(比如主键)。most_common_vals(MCV,最常见值):列里出现最频繁的值组成的数组。most_common_freqs:与 MCV 数组一一对应的频率数组,范围 0 到 1。位置 0 的 MCV 对应most_common_freqs[0]。histogram_bounds:一组排好序的值,把非 MCV 部分的分布切成等频桶。correlation(相关性) :-1 到 1 之间的一个数,说磁盘上的物理行序跟列的逻辑顺序有多吻合 。对 BRIN 索引和"索引扫描是不是真便宜"都重要;它在cost_index()里驱动代价调整------强相关(接近顺序扫)→ 便宜;弱相关(接近随机访问)→ 昂贵。
这几乎就是全部图景。还有几个针对特定类型的(数组的 elem_count_histogram、范围的边界直方图),但规划器日常 90% 的工作跑在上面那七条之上。
怎么读 pg_stats
打开一个 psql 连到刚播过种的第 7 章 cinetrack,让它描述 reviews 表:
sql
SELECT attname,
null_frac,
n_distinct,
most_common_vals,
most_common_freqs,
array_length(histogram_bounds, 1) AS hist_buckets,
correlation
FROM pg_stats
WHERE tablename = 'reviews'
AND schemaname = 'public'
ORDER BY attname;
每个被分析的列会出一行。movie_id 列会显示一个长长的 MCV 数组,因为某些电影被评分的次数远多于其他电影;posted_at 列大概率显示 correlation 接近 1.0,因为行是按时间顺序到达的;id 列显示 n_distinct = -1,因为主键按定义就是唯一的。
这一输出不是给你看的------它是对这张表做的每一次代价计算的输入。 当规划器被问到 WHERE movie_id = 42,它会去看 most_common_vals。如果 42 在 MCV 列表里,规划器直接用那个确切频率------估得很准。如果 42 不在,规划器就退到非 MCV 部分上的均匀分布假设,准确度差一大截。
选择率到底怎么算
WHERE column = constant 是最简单的情形。规划器这样做:
- 如果该列的
null_frac有份,先减掉。 - 如果这个常量出现在
most_common_vals里,返回对应的most_common_freqs[i]。完事。 - 否则,假设这个常量是非 MCV 值里的某一个,返回
(1 - sum(MCV freqs) - null_frac) / (n_distinct - len(MCVs))。一个横扫长尾的均匀猜测。
范围查询用 histogram_bounds。规划器在桶里找 100 落在哪一格,算出直方图里在它下方的占比,再加上匹配的 MCV 部分。LIKE 和模板匹配有一个特殊通道 :会去检查常量字符串、试着从直方图里框定匹配范围。对左锚定模式意外地好用,对其他模式意外地糟 。IN (a, b, c) 算成每个值的选择率之和,上限封顶到 1.0 。当 MCV 跟 IN 列表对不齐、加和超过现实时,这个封顶很重要。
精度旋钮
MCV 数组和直方图的大小都由 default_statistics_target 控制。默认 100,即最多 100 个 MCV 和 100 个直方图桶 。同一个数还控制 ANALYZE 样本多大。
更高 = 在偏态分布或多 distinct 值的列上买更准的选择率估算。代价是 ANALYZE 时间更长、pg_statistic 更占地方、每个查询的规划时间稍长。从 100 调到 1000 通常不会坏事,对长尾 distinct 值的表还能显著改善估算。调到上限 10000 有时有用、有时反而伤。
可以按列覆盖:
sql
ALTER TABLE reviews ALTER COLUMN movie_id SET STATISTICS 1000;
ANALYZE reviews;
这告诉 Postgres 这一列最多跟踪 1000 个 MCV 和 1000 个桶的直方图。下一次 ANALYZE 就会按它来。设置持久化在目录里。
警告 :
ANALYZE不是免费的。在大表上设高 统计信息 target,它会读大得多得多的样本,对更多值跑类型分析器。千万行以上的表请在低谷期跑 ANALYZE。Autovacuum 在行变更计数越过阈值时自动触发 ANALYZE;这个阈值是按表可调的。
统计信息过期
统计信息描述的是 ANALYZE 那一刻的表。它不会随每次写入更新。 自上次 ANALYZE 以来插入了一千万行的表,对规划器是不可见的------它还以为表是以前那点行数。选择率估算错,计划漂移。
Autovacuum 在变更数超过 autovacuum_analyze_scale_factor × reltuples + autovacuum_analyze_threshold 时自动跑 ANALYZE。默认值对大多数表够用 。但它们对追加重的表 会跟不上:变更率一直高、百分比却一直低。一张一亿行的事务表,每天插入 20 万行------在默认设置下根本不会触发 autoanalyze :变更比例 0.2%,远低于默认的 20% 阈值。手动 ANALYZE 或按表调阈值才是答案。
重要提示 :规划器出错的诊断几乎总是从这里开始 。对这张表跑一次
ANALYZE。如果计划变了,就是统计信息过期了。如果没变,问题在别处。
规划器决策树
给定一条解析过的查询和一份新鲜的统计信息,规划器去走一棵选择树。每个节点它都有若干选项。每个选项它算一次代价。它挑最便宜的,继续往前。 我们现在就走这棵树。
规划器要回答的三道大题
每份计划都回答这三个问题,大致按这个顺序:
- 每个被触及的表,我要怎么把行掏出来? 顺序扫描?索引扫描?位图堆扫描?只索引扫描?
- 每一对要合并的表,我要怎么把它们连接? 嵌套循环?哈希连接?归并连接?用什么顺序?
- 还需要哪些额外工作,放在计划的什么位置? 排序、聚合、
LIMIT、DISTINCT、窗口函数。有些可以下推到更早的节点,有些不行。
这些问题里的每一个选择,都按代价模型和统计信息定价 。规划器在一棵树上搜索,每一个分支是一种不同的物理计划。最便宜的那片叶子赢。
选扫描方式
给定单张表和一个过滤条件,Postgres 在这几种里选:
- 顺序扫描 :按顺序读表的每一页。代价是
relpages * seq_page_cost + reltuples * cpu_tuple_cost,加上过滤条件每个元组的操作符代价。单页便宜,但大表上整体贵。过滤命中行占比大时赢。 - 索引扫描 :走索引,按每个 TID 跳进堆,返回行。每次堆访问都是一次随机读,按
random_page_cost标价。当过滤选择性高、行又分散时赢。 - 只索引扫描(仅索引扫描) :走索引,但在可见性映射说该页全可见时跳过堆访问 。要求查询需要的所有列都在索引里(覆盖索引或
INCLUDE)。能用上时,代价远低于普通索引扫描。 - 位图堆扫描(Bitmap 堆 scan) :走索引,按堆页顺序建一张 TID 位图,再按物理顺序读一次堆。在索引返回数千到数百万行时赢,因为随机读转变成了顺序读。
这些是我们在第 6 章从第一性原理搭起来的同一种扫描,可见性映射决定只索引扫描是不是合法。
规划器对每个可行选项跑一遍代价公式、选最便宜的。这个决策依赖三件事:过滤有多选择(选择率)、行有多分散(相关性)、页代价怎么设。三者全是同一套算术的输入。
一条有用的直觉:高选择率下索引扫描赢、中段处位图扫描赢、低选择率下顺序扫描赢 。交叉点取决于代价比值。在 SSD 上,随机读比顺序读贵不了多少,顺序扫描的领地比默认值假设的更窄 ------把 random_page_cost 调到 1.1 或 2.0 是现代硬件上单项最有效的单点修改之一。
选连接方式
给定两个要连接的关系,Postgres 在这几种里选:
- 嵌套循环(嵌套循环连接) :对外侧的每一行,在内侧找匹配。代价随
outer_rows * inner_lookup_cost缩放。外侧小、内侧连接键上有索引时赢。 - 哈希连接 :在一侧建内存哈希表,从另一侧探测。代价大概是
hash_build + outer_rows * hash_lookup。一侧小到能哈希、另一侧大时赢。 - 归并连接:两侧都按连接键排序,然后齐步走。两侧都已排好序时,或者一侧已排序另一侧排序成本不高时赢。
每一种都有启动代价和每行代价 。规划器用统计信息估算每一侧大小、选连接方式、再对查询里的下一个连接重复一次。早期估算喂给的后续大小,决定了后续估算------这就是为什么一个错的选择率会污染下游一切。
连接顺序才是难的那一题
一个连接五张表的查询有 5! = 120 种左深连接顺序(灌木式计划 ,即两个中间连接先合并,还会让数量进一步膨胀)。十张表光左深就有 360 万种排列。Postgres 没法在小数量之上穷举,所以它有两套算法:
- 动态规划 :先用 2 表构最优连接,再 3 表、再 4 表,通过组合更小的解来构。默认使用。两个独立 GUC 约束这个搜索 :
join_collapse_limit(默认 8)控制 FROM 列表里多少项规划器还会考虑重排------一旦关系数越过这个值,规划器停止自由重排、对多余的表按你写的连接顺序走;from_collapse_limit(默认 8)控制规划器多激进地把 FROM 子句里的子查询内联进外查询的 FROM 列表------一个会让 FROM 列表越过这个限制的子查询,会被保留为独立计划节点而非折叠进来。这两个上限都只是限制 DP 的搜索空间,并不替换限制内的 DP。 - GEQO (Genetic Query Optimization,遗传查询优化) :独立的另一触发器。当 FROM 列表超过
geqo_threshold(默认 12),规划器完全放弃 DP 、切到一个遗传算法,靠启发式搜索空间、花光预算就停。计划是非确定性的:同一条查询可能拿到不同计划。
两个算法都靠同一份选择率估算跑。 如果那些估算是错的,选出来的连接顺序就是错的------而错的顺序的代价可能差好几个数量级。
组合爆炸也是为什么"告诉规划器具体怎么干"这么难。在 SQL 里显式写的 JOIN 在 join_collapse_limit 内大致按你写的顺序对待;交叉连接语法把所有东西揉成一个大选择。
聚合与排序
连接之上,规划器还要处理:
ORDER BY:要么挂一个排序节点,要么用一个已经在正确键上排好序的索引。后面那种如果存在就是免费的。GROUP BY:要么哈希聚合(按键建哈希),要么排序聚合(先排后分)。结果能塞进work_mem时哈希赢;输入已排好时排序赢。DISTINCT:类似选择,哈希或排序。LIMIT:尽量下推。一个带索引的ORDER BY上的LIMIT 10让规划器可以在 10 行后停下。- 窗口函数:需要在分区和顺序键上排序。通常无法避免。
- 集合操作:基于哈希或基于排序,同样的权衡。
每一个选择都按代价模型定价 。哈希聚合把 work_mem 跟估算的哈希大小对比------规划器觉得哈希塞得下就选哈希,觉得会溢写就选排序。这就是为什么有时候你会看到一个哈希聚合在实际执行里溢写磁盘,尽管规划器以为它塞得下------估算错了。
一个走通的例子
想象 cinetrack 的查询"给我 2024 年上映电影的评论":
sql
SELECT r.body, r.posted_at
FROM reviews r
JOIN movies m ON m.id = r.movie_id
WHERE m.release_year = 2024;
规划器要决定:
- 怎么扫
movies。在release_year上有索引、预期匹配数小,索引扫描或位图扫描赢。 - 怎么扫
reviews。reviews上没有直接过滤,只有连接。规划器要先选连接方式,再推导访问模式。 - 哪种连接算法 。
movies侧小、reviews.movie_id上有索引时,嵌套循环常常赢。movies侧变大时,哈希连接接管。 - 顺序 。
movies在前(在release_year上过滤),然后reviews、用movies的行驱动对reviews.movie_id的查找。反过来扫reviews把过滤通过连接施加过去,几乎总是更糟。
具体计划取决于 release_year 的实际分布和两张表的大小。同一条查询,对着第 2 章那种小数据集、和对着第 7 章 5 万部电影和 50 万条评论,可能选不同的计划------而两者都可能是对的,因为它们看到的数据不同。
本篇要点回顾
| 概念 | 关键事实 |
|---|---|
pg_statistic vs pg_stats |
前者二进制 blob 系统目录,后者人类可读的可读投影 |
| ANALYZE 采的 7 项 | null_frac / avg_width / n_distinct / MCV / MCV 频率 / 直方图 / 相关性 |
| 默认样本大小 | 300 × default_statistics_target = 30,000 行 |
| 选择率计算 | MCV 直接命中→用频率;不在→长尾均匀假设 |
| 直方图适用 | 只覆盖非 MCV 部分,等频分桶 |
| 统计过期 | 写多但不触发阈值(追加型表)的典型陷阱 |
| 三道大题 | 怎么扫、怎么连、怎么聚合 |
| 扫描方式选择 | 高选择率→索引;中段→位图;低选择率→顺序 |
| 连接顺序 | DP 到 8/12 关系止,超过则 GEQO 启发式,结果非确定 |
| 哈希 vs 排序 | work_mem 是分水岭;规划器觉得塞得下就哈希 |
下一篇:022-扩展统计与规划器旋钮 ------ 单列统计信息假设列独立,但现实里列之间充满相关(邮编定城市、上映年份定导演年代)。下一篇讲 CREATE STATISTICS 的三种 kind 怎么修掉这条假设,以及规划器那串 GUC 旋钮里哪些值得调、哪些是陷阱。
常见问题答疑(学员答疑)
Q1:ANALYZE 只采样 30000 行,不管表是一百万行还是五亿行------这么小的样本,估算能靠谱吗?
出人意料地靠谱。ANALYZE 的采样量是 300 × default_statistics_target(默认 300 × 100 = 30000),这个数字对大多数分布够用------因为规划器不需要精确的行数,它只需要一个足够好的选择率估算来在候选计划之间做比较。30000 个随机样本能以很高置信度捕捉列的总体分布特征:常见值、频率、直方图形状。但偏态严重或 distinct 值极多的列会吃亏------100 个 MCV 桶和 100 个直方图桶不够捕捉真实分布,这时可以把那一列的 default_statistics_target 调到 500 或 1000,样本和桶数跟着涨。SQL Server 和 Oracle 也用类似的小采样策略,区别是 SQL Server 允许你指定采样百分比。Postgres 的设计哲学是"默认够用、按列精调",而不是"一把全扫"------五亿行表全扫 ANALYZE 要跑几十分钟,代价远超收益。
Q2:规划器找不到常量值在 MCV 列表里时,就退到"均匀分布假设"------这个假设什么时候会翻车?
均匀分布假设是规划器最后的退路:它假设所有非 MCV 值的出现频率都一样。如果列真的均匀分布(比如自增 ID),这很准。但如果列有长长的偏态尾巴------少数值出现极频繁但没进入 MCV 列表------估算就会严重偏低。一个典型翻车场景:表有 500 个 distinct 值,MCV 列表只存了 100 个,剩下 400 个里有一个值实际占了 20% 的行。均匀假设把这 20% 均摊给 400 个值,每个值估 0.05%,真相是 20%。估算差了 400 倍。修法是抬高那一列的 default_statistics_target,让 MCV 列表更长、把那个"隐藏的大值"收入 MCV。另一个翻车场景是 LIKE 查询------规划器对非左锚定模式几乎只能猜一个固定选择率,跟真实分布无关,三元组索引(pg_trgm)是更好的解法。
Q3:五张表的连接顺序有 120 种,十张表有 360 万种------规划器怎么在合理时间内选出最好的?
Postgres 用动态规划(DP)从 2 表连接开始逐步构建最优解,而不是暴力穷举。这就像拼图:先拼好所有 2 片组合,再用 2 片最优解拼 3 片,以此类推------每一步只考虑已验证的最优子解,搜索空间从指数级降到多项式级。但 DP 有上限:join_collapse_limit(默认 8)------超过 8 张表,规划器不再自由重排连接顺序,按你写的 JOIN 顺序走。超过 geqo_threshold(默认 12),完全切到遗传算法(GEQO),靠随机交叉和变异搜索空间,找到差不多好的就停。GEQO 的结果是非确定性的------同一条 SQL 可能两次拿到不同计划。MySQL 只支持嵌套循环连接,连接顺序依赖优化器的 greedy 搜索;Oracle 支持更多连接算法且 DP 上限更高。Postgres 的设计权衡是:DP 保证小查询最优,GEQO 保证大查询"别太差"。
第七篇:Chat端点实战------用自然语言向Honcho"询问"你的用户
如果你能给Honcho装一个麦克风,你最想问它什么?"这个用户到底想要什么?""他为什么突然发火了?""他喜欢什么样的沟通方式?"
Chat端点就是那个麦克风。
什么是Chat端点?
peer.chat()是Honcho推理系统的自然语言接口。你不需要手动检索结论------你的LLM可以提问,获取基于Honcho所有推理的合成答案。
python
from honcho import Honcho
honcho = Honcho()
peer = honcho.peer("user-123")
# 用自然语言询问
answer = peer.chat("用户最偏好什么方式完成任务?")
print(answer)
# "基于推理结论,该用户偏好使用键盘快捷键完成任务,
# 对命令行工具有高接受度,倾向于追求效率而非图形界面..."
Chat端点搜索Peer的整个Representation------所有推理得出的结论------合成自然语言答案。
推理等级:按需调节深度
python
# 快速事实查询------最小推理
answer = peer.chat("用户叫什么名字?", reasoning_level="minimal")
# 默认平衡------低推理(默认)
answer = peer.chat("用户的沟通偏好是什么?")
# 复杂综合------高推理
answer = peer.chat("总结用户的长期目标。", reasoning_level="high")
# 深度研究------最大推理
answer = peer.chat("分析用户的行为模式并给出性格推断。", reasoning_level="max")
| 等级 | 适用场景 | 特点 |
|---|---|---|
minimal |
快速事实查询 | 最小预取窗口和工具集,最低成本 |
low |
默认平衡 | 标准工具集和预算 |
medium |
多步或模糊问题 | 更少工具但思考更深更长 |
high |
跨源复杂综合 | 像medium但使用更多工具 |
max |
深度研究,最复杂查询 | 最高思考预算,最大迭代次数 |
四大集成模式
模式一:动态提示增强
让LLM决定它需要知道什么,然后注入到下一次生成中:
python
# LLM根据对话生成查询
llm_query = "用户偏好正式还是随意的沟通风格?"
# 从Honcho获取答案
context = peer.chat(llm_query)
# 注入到下一个LLM prompt
enhanced_prompt = f"""
Context about the user: {context}
User message: {user_input}
Respond appropriately based on the context.
"""
模式二:条件逻辑驱动
用Chat端点响应驱动应用逻辑:
python
# 检查用户是否完成onboarding
onboarding_status = peer.chat("用户是否完成了入门流程?")
if "yes" in onboarding_status.lower():
# 显示主界面
show_main_interface()
else:
# 显示入门引导
show_onboarding()
模式三:偏好提取
python
# 一次性提取多个维度
tone = peer.chat("用户偏好什么沟通语气?")
expertise = peer.chat("用户的技术专业水平如何?")
goals = peer.chat("用户的主要目标是什么?")
# 用这些配置Agent行为
agent_config = {
"tone": tone,
"expertise_level": expertise,
"primary_goals": goals
}
模式四:让Chat端点成为Agent的一个工具
python
# 如果你在构建Agent,把Honcho chat作为另一个工具
tools = [
{
"name": "honcho_memory",
"description": "查询用户的历史记忆和偏好",
"function": lambda query: peer.chat(query)
},
# ... 其他工具
]
这是最强大的模式------Agent自主决定何时需要查询记忆,查什么。
Chat端点如何工作
当你调用peer.chat(query)时:
- 搜索Peer Card和Representation中与查询语义相关的结论
- 如有需要,拉取源消息片段获取更多上下文
- 将结论追溯到的前提也纳入
- 合成为连贯的自然语言响应
这是一个Dialectic Agent(辩证代理)------它在回答前会探索记忆、搜索结论、追溯推理链。
最佳实践
- 问具体问题:不要问"告诉我关于用户的事",问"用户偏好什么沟通风格?"
- 让LLM来提问:Chat端点在LLM自主决定需要什么时最有价值
- 用于运行时决策:不仅用于LLM prompt,也用于应用逻辑、路由、功能开关
- 结合context()使用 :
context()给对话上下文,chat()给特定洞察
Q&A
Q1:每次调用chat()都会消耗token吗?频繁调用会不会很贵?
A:是的,每次peer.chat()调用都会启动一个Dialectic Agent进行推理。但你有几种控制手段:1)使用reasoning_level="minimal"降低单次成本;2)批量提取多个偏好用一次调用代替多次;3)缓存常用查询结果。值得注意的是,Chat端点的推理是查询时的实时推理,与后台的Representation推理(写入时触发)是分开计费的------后台推理按消息处理收费,Chat推理按调用次数收费。
Q2:Chat端点返回的答案有多准确?如果Honcho还没有足够的推理结果怎么办?
A:如果Honcho对该Peer的推理不充分(比如刚写入消息还没处理完),peer.chat()会返回None或基于有限信息的低置信答案。你可以检查返回是否为空来决定fallback策略。在生产中,建议在用户首次交互后就尽快写入消息,让推理有足够时间运行,之后再调用chat获取洞察。
Q3:Chat端点能带Session范围限制吗?比如只问某个特定会话的内容?
A:可以。peer.chat()支持session参数,可以限定在特定Session内查询:
python
response = alice.chat("在我们的对话中发生了什么?", session="session-1")
这对于查看特定交互上下文很有用。不带session参数时,Chat会搜索Peer的全局Representation------跨所有Session的推理结论。
大模型论文日报 2026-08-12
核心总览
今日5篇论文统一核心主题:现有大模型领域大量公认、默认成立的基础假设存在明显缺陷、具备脆弱性;覆盖长上下文架构、多语言安全、推理能力评测、事实核查流水线、学术LLM文本检测五大方向,多项行业通用实践认知被实验证伪。
论文1
标题
Cracks in the Foundation: Seemingly Minor Architectural Choices Impact Long Context Extension
收录
arXiv:2608.10296
研究方向
长上下文扩展、Transformer架构设计
核心摘要
行业普遍认为稠密Transformer内部各类架构微调对模型精度影响较小,本文针对长上下文场景推翻该认知。 Olmo、Llama、Qwen系列模型采用的四类常规轻量化架构设计(归一化方案、GQA分组查询注意力、预训练上下文长度、滑动窗口注意力)会形成叠加负面效果,最高可造成长上下文任务性能下滑47%;该性能衰减无法通过短上下文数据集损失、验证集指标提前预判。
核心结论
- 不同模型长上下文能力差距,主要由上述细微架构设计差异决定,相关性能倾向在预训练早期即可观测识别;
- 开源发布OlmPool数据集,包含26组统一对照7B模型,多款架构长上下文拓展能力优于Llama 3。
颠覆原有认知
- 推翻「稠密Transformer内部架构变体对模型性能影响有限」的固有认知;
- 证伪「短上下文测试指标能够预判模型长上下文表现」的通用工程实践逻辑。
论文2
标题
The Illusion of Cross-Lingual Safety in Low-Resource Languages
收录
arXiv:2608.11146
研究方向
多语言安全对齐、低资源语种模型安全
核心摘要
现有大模型安全对齐流程几乎全部基于英文完成,行业默认安全防护能力可跨语种通用迁移。 选取Twi、Hausa、Amharic、Swahili四类非洲低资源语言开展对照实验,构建LoDNA安全评测数据集,结合隐层表征几何分析框架观测安全机制迁移效果。 实验显示英文训练的安全规则跨语言迁移效果极差,多组模型-语种测试中,低资源语言有害提示仅保留不足10%英文场景的拒绝输出信号;即便提示文本字面翻译、语义相似度高达0.95~0.996,模型深层表征仍会发生安全机制漂移。
核心结论
当前多语言安全对齐仅停留在表层,不存在脱离语种、通用统一的危害判别逻辑。
颠覆原有认知
推翻「英文安全对齐策略可无损耗跨语言泛化」的核心假设,证明模型安全能力并非与语种无关的通用属性。
论文3
标题
From Reasoning Depth to Reasoning Breadth: Evaluating Multi-Point Associative Reasoning in Large Language Models
收录
arXiv:2608.10444
研究方向
大模型推理能力评测、多点关联推理
核心摘要
当下主流推理优化方案均聚焦推理深度 (延长思维链、分步拆解推导),业界默认更深推理链等价于更强综合推理能力,忽视「推理广度」维度(同步发散多条语义线索、多信息整合推导)。 构建双语评测基准MPAR-Bench,参考合作游戏逻辑,要求模型依靠多条差异化独立线索还原目标概念,通过线索遮蔽、打乱、干扰注入等扰动实验量化模型短板。 扰动条件下英文任务准确率下降918个百分点,中文下降512个百分点;CoT思考模式可提升基础准确率,但无法降低扰动带来的性能衰减,过度延长推理链甚至会推翻原本正确结论。
核心结论
推理深度、推理广度为相互独立的两大推理维度,现有评测体系侧重深度、缺失广度相关考核。
颠覆原有认知
证伪「加深推理深度即可同步提升推理广度」的隐含假设;说明思维链(think)模式不代表鲁棒、稳定的综合推理能力。
论文4
标题
Decomposition-Induced Context-Memory Conflict: When Fact-Checking Pipelines Contradict Their Own Source Text
收录
arXiv:2608.10627
研究方向
事实验证、事实核查流水线可靠性
核心摘要
以FActScore为代表的「文本分解-逐条验证」事实核查流水线,行业普遍认为文本拆分是无偏差中性预处理步骤。 本文提出分解诱导上下文记忆冲突(DI-CC):文本分解模块会注入模型自身固有偏见,生成与原始原文相互矛盾的原子陈述。 简单线性探针可精准识别DI-CC冲突位置(AUC=0.86~0.88);而行业通用的SelfCheckGPT自一致性检测完全失效(AUC仅0.51)。
核心结论
DI-CC与传统上下文记忆冲突底层机制同源,仅产生于流水线不同环节;上下文感知解码可缓解该问题,但会大幅提升算力开销,指代密集文本场景下大量语句无法正常拆解。
颠覆原有认知
- 推翻「文本分解是无偏中性预处理操作」的默认前提,证明分解环节会引入模型主观偏差;
- 证伪「自一致性采样能够检测全部事实偏差问题」,DI-CC错误会在多次重采样中稳定复现。
论文5
标题
Most Biomedical Publications Show Signs of LLM-Assisted Writing
收录
arXiv:2608.10715
研究方向
学术出版诚信、LLM生成文本检测
核心摘要
现有检测手段难以量化学术论文中LLM辅助写作的整体渗透率,本文提出基于词频偏移的无偏估算方法,针对PubMed Central开放获取生物医学论文全文批量分析。 数据显示截至2025年末,89%生物医学论文存在明显LLM写作特征;文章讨论部分LLM使用占比68%,是方法章节(32%)的2倍,且方法章节LLM辅助占比同样超50%。
核心结论
LLM辅助写作已全面渗透生物医药领域学术出版,干预程度远超过往预估,并非仅用于简单文字润色。
颠覆原有认知
- 否定「LLM仅用于学术文本润色改写」的乐观判断;
- 证明现有LLM文本检测工具严重低估学术场景中模型辅助的真实规模,监管识别能力不足。
大模型日报 2026-08-12
1. 美参议员桑德斯向三大AI巨头施压:暂停AI研发,否则将出手干预
美国联邦参议员伯尼·桑德斯向OpenAI、Anthropic、Meta三家企业高管发出公开信,要求他们立刻暂停AI研发,称AI能力已达到「关键阈值」,技术正逐渐脱离掌控,可能带来灾难性后果。桑德斯援引近期AI智能体突破测试环境、进入外部系统等安全事件,要求企业兑现此前的安全承诺。如企业拒绝主动行动,美国参议院将介入干预。超过1100名科技公司员工此前亦联名呼吁建立AI发展速度管理机制。
2. 林俊旸官宣创办AI公司语用科技 Pragmatik Labs,聚焦下一代智能体
阿里最年轻P10级技术专家林俊旸今日宣布在上海创办新公司Pragmatik Labs(语用科技),简称p7k,研究方向为横跨数字世界和物理世界的下一代智能体(Agent)。林俊旸与智谱AI创始人唐杰、月之暗面创始人杨植麟、腾讯首席AI科学家姚顺雨并称「基模四杰」。本轮融资由高榕创投和红杉中国共同领投,腾讯和上海未来产业基金提供支持。
3. 字节联合浙大发布SwanTale一体化音频生成大模型
字节跳动Seed团队联合浙江大学正式发布一体化音频生成大模型SwanTale,相关研究论文同步对外公开。该模型实现技术突破:使用者仅通过一段自然语言描述,便可同步生成多角色人声、背景音乐、环境音效,一站式输出适配动画、短视频、广播剧的完整音频工程,大幅降低多媒体内容音频制作门槛,重构AI音频创作流程。
4. 英伟达发布开源端侧模型Nemotron 3.5 Lightning,启动万亿参数大模型Nemotron 4研发
英伟达发布开源端侧模型Nemotron 3.5 Lightning,可在普通消费级GPU、笔记本本地运行,面向个人开发者、边缘设备场景开放;同时启动万亿参数大模型Nemotron 4研发。英伟达CEO黄仁勋公开解读5000亿美元AI算力资金池计划,明确英伟达仅作为补充参与方,单一项目出资上限25%,提出AI算力基础设施正成为标准化可投资资产。
5. 上海发布软件和信息服务业「十五五」规划,布局十万卡级智算集群
上海市经济和信息化委员会印发《软件和信息服务业发展「十五五」规划》,提出至2030年产业规模目标突破4万亿元,推进临港、松江等地大规模算力基地建设,实施「百千万」智算集群工程,重点扶持行业智能体、多模态大模型、AI原生软件研发,加速国产AI解决方案规模化落地。调研机构预测,今年国内高端AI芯片市场国产方案份额有望接近90%。














2026年重磅喜讯! 喜报!热烈祝贺Gavin大咖人工智能领域经典著作《企业级ChatGPT AI大模型应用开发实战(1000分钟视频)》中国水利水电出版社发行上市!
内容提要
本书内容基于作者在硅谷 ChatGPT 项目及企业培训中的实战经验凝练而成,重点介绍企业级 ChatGPT 开发的核心技术、案例研究及最佳实践。全书共 16 章,分为基础篇和实战篇两大部分。
基础篇:
介绍 ChatGPT 底层架构 Transformer 技术及源码实现、GPT 的内部机制及源码实现、GPT 系列模型原理与应用:从 GPT-2 到 GPT-4 等内容。
实战篇:
介绍基于 ChatGPT 的端到端语音聊天机器人项目实战,企业级 ChatGPT 开发的三大核心内部机制及案例实战,ChatGPT 插件的内部机制、源码及案例实战,ChatGPT 提示词开发实战,思维链及 ReAct 解析与实战,提示词本质解析及评估实战与源码解析,LangChain 大模型框架的七大核心组件及案例解析(上、下),LangChain 代理深入解析及源码解析,AutoGPT 源码解析及综合案例实战,使用 LangChain 构建问答聊天机器人案例实战,构建基于大模型的自治代理案例,Llama 2 模型与 LangChain 项目详解。书中每个知识点均配有相应的实现代码和实例。
本书适合有一定 Python 基础的 ChatGPT 爱好者阅读,主要面向从事大模型应用开发、机器学习、数据挖掘或深度学习的专业人员,高等院校相关专业的师生,以及相关领域的科研人员。
本书附赠丰富的学习资源,具体如下:①同步学习资源,即 16 集同步教学视频,视频时长共计约 1000 分钟;②教师授课的辅助资源,即 187 个案例知识点、15 个项目实战的全部源代码。
前言
在当今快速发展的科技时代,人工智能(artificial intelligence,AI)技术正以惊人的速度改变着人们的生活和工作方式。在这个新时代的浪潮中,大模型技术成为AI领域的一颗耀眼新星。ChatGPT作为大模型技术的重要应用之一,正在引领着人机交互领域的革新浪潮。本书将带领读者深入探索大模型新时代,通过ChatGPT实战项目和内部解析,深入掌握基于ChatGPT的大模型应用开发领域的关键技术,并解密ChatGPT的底层架构和实现原理。
本书主要内容
本书通过ChatGPT实战项目的方式,为读者呈现一个全面、系统的学习路径,从基础知识的介绍开始,带领读者深入了解ChatGPT的工作原理和实际应用。本书非常适合具备Python基础的读者学习。
全书共16章,分为基础篇和实战篇两大部分。 基础篇包括第1~3章;实战篇包括第4~16章。
第1章 ChatGPT底层架构Transformer技术及源码实现,详解最大似然估计、最大后验概率、贝叶斯Transformer及自编码与自回归语言模型的内部机制。
第2章 GPT的内部机制及源码实现,剖析GPT运行机制、掩码机制、Decoder-Only模式,详解数据流动生命周期及GPT-2源码。
第3章 GPT系列模型原理与应用:从GPT-2到GPT-4,解析ChatGPT提示词流程、GPT-2运行机制,可视化解读GPT-3/4的内部机制。
第4章 基于ChatGPT的端到端语音聊天机器人项目实战,涵盖ChatGPT API开发、前后端构建(ReAct+FastAPI)及项目优化。
第5章 企业级ChatGPT开发的三大核心内部机制及案例实战,解析企业级开发核心,演示Notion问答对话AI案例。
第6章 ChatGPT插件的内部机制、源码及案例实战,详解插件工作原理、检索插件源码及全流程开发实战。
第7章 ChatGPT提示词开发实战,基于LangChain框架的提示词、思维链、链式提示词及模型评估开发。
第8章 思维链及ReAct解析与实战,剖析思维链推理、ReAct技术原理、框架源码及案例实战。
第9章 提示词本质解析及评估实战与源码解析,包含问答评估、代理评估源码解析及提示词本质探讨。
第10~11章 LangChain大模型框架的七大核心组件及案例解析(上、下),涵盖模型、词嵌入、提示词、内存、回调、数据连接、代理等核心组件及聊天机器人综合案例。
第12章 LangChain代理深入解析及源码解析,详解代理工作原理及AutoGPT源码解析。
第13章 AutoGPT源码解析及综合案例实战,剖析AutoGPT内部机制及其在LangChain代理、内存、PromptGenerator中的应用。
第14章 使用LangChain构建问答聊天机器人案例实战,涵盖GPT-4代码生成全流程及LangChain开发实战。
第15章 构建基于大模型的自治代理案例,详解自治代理原理、工具、示例及开源实现源码。
第16章 Llama 2模型与LangChain项目详解,包括模型部署(Replicate)、Hugging Face/LangChain实践、检索增强生成及自定义提示词RetrievalQA开发。
本书特色
●深入探索,全面剖析。 本书涵盖ChatGPT案例实战、LangChain项目实战及框架源码解析等多个层面的内容。每章都深入探讨相关技术与案例,并提供源码解析,使读者能够全面了解ChatGPT和LangChain等技术的内部机制与开发原理,为实际项目的应用提供有力指导。
●实战剖析,项目揭秘。 本书每章都提供具体的案例实战与项目解析,引导读者通过实际操作和代码理解技术细节和底层逻辑。通过理论结合实践的方式,使读者能够更好地运用所学知识,深入了解项目和框架的实现细节。
●前沿突破,技术驱动。 本书介绍了一系列突破性的技术,如ChatGPT、LangChain、Transformer、Prompt、Llama 2、AutoGPT、BabyAGI、CoT、ToT、ReAct、MRKL等。通过对这些技术的深入剖析,读者可以了解相关技术的发展和应用,并了解它们在实际项目中的具体应用场景和效果。
●源码解析,细致讲解。 本书对LangChain框架的关键技术进行了逐行源码剖析。读者可以深入理解源码实现和机制原理,从而更好地理解技术细节和底层逻辑,并将其应用于实际开发工作中。
本书还为读者提供了丰富的知识和实用的技能,帮助读者在ChatGPT和LangChain领域取得突破性的进展。无论是初学者还是有一定经验的开发者,都可以从本书中获得有价值的学习资源。
配套资源
为便于教与学,本书配有同步教学视频(约1000分钟)、源代码、数据集、教学课件、教学大纲、安装程序。
作者简介
王家林
美国斯坦福大学计算机专业毕业。曾在美国担任硅谷顶级机器学习和人工智能实验室主任、杰出AI工程师及首席机器学习工程师,专精于对话式人工智能(conversational AI)。现担任硅谷某知名对话机器人公司CTO,自2019年起专注于基于红队测试(red teaming)的责任型AI(responsible AI),并热衷于构建生成式AI/大语言模型教练系统(GenAI/LLM coaching systems)。在硅谷任职期间,曾领导多个GenAI/LLM解决方案项目,成功平衡企业业务需求下的大模型推理(reasoning)系统与幻觉(hallucinations)及偏见(biases)风险的最小化。
作为数据科学、机器学习、NLP、ChatGPT及大模型等领域25本书的主要作者,王家林对利用人工智能提供解决方案,以及通过机器学习驱动的NLP与LLM流程帮助组织实现数据驱动决策充满热情。他曾领导Apple、PayPal、Chase Bank、Faethm、LinkedIn等公司的11个重大NLP项目。
在NLP、对话式AI、大数据及基于AWS的无服务器(serverless)技术方面,拥有丰富的机器学习咨询经验。
段智华
中国电信股份有限公司上海分公司高级工程师。长期从事大模型与智能体技术领域,专注Agentic AI、Harness Agent等前沿方向研究。
新书购买链接
《企业级ChatGPT AI大模型应用开发实战(1000分钟视频)》 购买链接:item.jd.com/15389212.ht...