构建上下文引擎插件
上下文引擎插件用于替代内置的 ContextCompressor,采用另一种策略来管理对话上下文。例如,一种无损上下文管理(LCM)引擎,它通过构建知识有向无环图(DAG)而非有损摘要来实现。
工作原理
代理的上下文管理基于 ContextEngine ABC(agent/context_engine.py)。内置的 ContextCompressor 是默认实现。插件引擎必须实现相同的接口。
同一时间只能激活一个上下文引擎。选择方式由配置驱动:
# config.yaml
context:
engine: "compressor" # default built-in
engine: "lcm" # activates a plugin engine named "lcm"
插件引擎不会自动启用——用户必须显式将 context.engine 设置为插件名称。
目录结构
每个上下文引擎都位于 plugins/context_engine/<name>/ 中:
plugins/context_engine/lcm/
├── __init__.py # exports the ContextEngine subclass
├── plugin.yaml # metadata (name, description, version)
└── ... # any other modules your engine needs
ContextEngine ABC 接口
您的引擎必须实现以下必需方法:
from agent.context_engine import ContextEngine
class LCMEngine(ContextEngine):
@property
def name(self) -> str:
"""Short identifier, e.g. 'lcm'. Must match config.yaml value."""
return "lcm"
def update_from_response(self, usage: dict) -> None:
"""Called after every LLM call with the usage dict.
Update self.last_prompt_tokens, self.last_completion_tokens,
self.last_total_tokens from the response.
"""
def should_compress(self, prompt_tokens: int = None) -> bool:
"""Return True if compaction should fire this turn."""
def compress(self, messages: list, current_tokens: int = None) -> list:
"""Compact the message list and return a new (possibly shorter) list.
The returned list must be a valid OpenAI-format message sequence.
"""
引擎必须维护的类属性
代理会直接读取这些属性以用于显示和日志记录:
last_prompt_tokens: int = 0
last_completion_tokens: int = 0
last_total_tokens: int = 0
threshold_tokens: int = 0 # when compression triggers
context_length: int = 0 # model's full context window
compression_count: int = 0 # how many times compress() has run
可选方法
这些方法在 ABC 中已有合理默认实现。根据需要进行覆盖:
| 方法 | 默认行为 | 覆盖场景 |
|---|---|---|
on_session_start(session_id, **kwargs) | 空操作 | 需要加载持久化状态(如 DAG、数据库) |
on_session_end(session_id, messages) | 空操作 | 需要刷新状态或关闭连接 |
on_session_reset() | 重置 token 计数器 | 您有每会话状态需要清除 |
update_model(model, context_length, ...) | 更新 context_length + threshold | 切换模型时需重新计算预算 |
get_tool_schemas() | 返回 [] | 引擎提供可被代理调用的工具(如 lcm_grep) |
handle_tool_call(name, args, **kwargs) | 返回错误 JSON | 实现工具处理器 |
should_compress_preflight(messages) | 返回 False | 可进行低成本的 API 调用前估算 |
get_status() | 标准 token/threshold 字典 | 有自定义指标需要暴露 |
引擎工具
上下文引擎可以暴露供代理直接调用的工具。从 get_tool_schemas() 返回工具 schema,并在 handle_tool_call() 中处理调用:
def get_tool_schemas(self):
return [{
"name": "lcm_grep",
"description": "Search the context knowledge graph",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "Search query"}
},
"required": ["query"],
},
}]
def handle_tool_call(self, name, args, **kwargs):
if name == "lcm_grep":
results = self._search_dag(args["query"])
return json.dumps({"results": results})
return json.dumps({"error": f"Unknown tool: {name}"})
引擎工具会在代理启动时自动注入到工具列表中并被调度执行——无需注册。
注册方式
通过目录(推荐)
将您的引擎放置于 plugins/context_engine/<name>/。__init__.py 必须导出一个 ContextEngine 的子类。发现系统将自动查找并实例化该类。
通过通用插件系统
通用插件也可以注册一个上下文引擎:
def register(ctx):
engine = LCMEngine(context_length=200000)
ctx.register_context_engine(engine)
仅允许注册一个引擎。第二个尝试注册的插件将被拒绝,并发出警告。
生命周期
1. Engine instantiated (plugin load or directory discovery)
2. on_session_start() — conversation begins
3. update_from_response() — after each API call
4. should_compress() — checked each turn
5. compress() — called when should_compress() returns True
6. on_session_end() — session boundary (CLI exit, /reset, gateway expiry)
on_session_reset() 在 /new 或 /reset 时被调用,用于清除会话级状态而无需完全关闭。
配置
用户可通过以下方式选择您的引擎:
hermes plugins → Provider Plugins → Context Engine,或编辑 config.yaml:
context:
engine: "lcm" # must match your engine's name property
compression 配置块(compression.threshold、compression.protect_last_n 等)是针对内置 ContextCompressor 的。如果您的引擎需要特定配置格式,请在初始化期间从 config.yaml 读取自定义配置。
测试
from agent.context_engine import ContextEngine
def test_engine_satisfies_abc():
engine = YourEngine(context_length=200000)
assert isinstance(engine, ContextEngine)
assert engine.name == "your-name"
def test_compress_returns_valid_messages():
engine = YourEngine(context_length=200000)
msgs = [{"role": "user", "content": "hello"}]
result = engine.compress(msgs)
assert isinstance(result, list)
assert all("role" in m for m in result)
参见 tests/agent/test_context_engine.py 获取完整的 ABC 合约测试套件。