Gliding Horse 上下文感知与智能压缩让 Agent 的“注意力”永不偏移摘要Gliding Horse 通过 RelevanceTracker 双维度评分、L1 淘汰策略增强、ContextWindowManager 感知压缩和后台话题连贯性分析构建了一套完整的上下文相关性感知与智能压缩系统。该系统解决了多轮对话中 Agent 面临的话题漂移和信息过载两大核心痛点实现了 LLM 注意力窗口的精细化管理使 Token 利用率提升 20-40%话题切换响应从被动淘汰升级为主动检测补充输入可靠性得到根本保障。这套系统是让 Agent 从“能跑”走向“长跑”的关键基础设施。关键词Gliding Horse上下文感知智能压缩RelevanceTrackerLLM 注意力窗口话题漂移信息过载语义关联度L1 淘汰策略Token 利用率话题连贯性分析Batch Agent在多轮对话中Agent 最怕两件事话题漂移和信息过载。用户可能在长会话中突然插入一个全新的需求或者任务本身横跨多个领域导致早期上下文与当前话题毫无关联却仍然占据着昂贵的 LLM 注意力窗口。Gliding Horse 给出的方案是一套 上下文相关性感知与智能压缩系统。它不仅仅依据“最近有没有用到”来淘汰信息而是实时计算每条历史摘要与当前任务的语义关联度并融合话题连贯性检测确保 LLM 的上下文窗口永远聚焦在最有价值的内容上。这套系统已经在 Gliding Horse 的内核中完整实现并与其他模块深度集成本文将完整拆解它的设计细节。一、两条输入路径同一个追踪目标Gliding Horse 中用户输入进入系统有两条截然不同的路径初始任务输入通过 TUI/API 传入经 SA 解析后形成 TaskContext在 TaskStart Hook 点触发处理。中间补充输入在 Agent 执行过程中用户实时发出的补充指令或纠正信息。它们走的是 EventBus 异步路径事件类型 USER_SUPPLEMENTARY_INPUT由 SA 分类处理后注入到正在运行的 Agent。这两条路径在旧版中存在一个关键缺陷补充输入通过 EventBus 广播能够显示在 TUI 上但从未被 Agent 真正消费。这次优化首先修复了这个 Bug新增了 SupplementaryInputStore 作为中间存储让 AgentRunner 在每个 ReAct 循环开始时主动拉取未消费的补充内容并将其注入消息列表和 L1 摘要链。修复之后的完整输入路径如下L1 条目元数据AgentRunner CycleStartSupplementaryInputStoreEventBus 路径补充输入Hook 路径初始输入两条用户输入路径初始任务输入中间补充输入TaskStart HookRelevanceTracker.on_new_inputEventBus.emitUSER_SUPPLEMENTARY_INPUTSA.event_receiver → 分类store(content, embedding, relevance)pending: Vectake_pending() → 注入 messages[]L1Session.add_summary()L1Turn { relevance_score, embedding, is_supplement }无论是初始任务还是中途补充系统都会通过 RelevanceTracker 实时计算该输入与当前任务的全局相关度和局部连贯性并将 relevance_score 写入对应的 L1 摘要条目中。二、RelevanceTracker给每条信息打上“注意力分数”RelevanceTracker 是整个系统的感知核心。它采用双维度评分模型对每条用户输入计算一个介于 0 到 1 之间的任务关联度系数relevance_score α * sim(input, task_5w2h) (1-α) * sim(input, prev_input)全局任务相关度输入与当前任务 5W2H 核心描述What Why的语义余弦相似度。局部连贯性输入与前一条输入的语义相似度用于检测话题连续性。其中 α 默认为 0.6即全局任务相关度占主导地位。计算所需的文本嵌入由共享的 EmbeddingService可配置 Ollama、OpenAI 兼容 API 或本地模型提供。pub struct RelevanceTracker {task_5w2h_embedding: OptionVec,prev_input_embedding: OptionVec,alpha: f64,embedder: OptionArc,}每次新输入到达on_new_input() 会生成嵌入向量计算两项相似度后合成最终分数并更新内部状态。这个分数会伴随输入一路存入 L1 条目、SupplementaryInputStore最终影响淘汰和压缩决策。下面是一个完整的 Rust 实战示例展示如何初始化 RelevanceTracker、调用 on_new_input 方法计算关联度分数并打印结果use std::sync::Arc;// 假设的 EmbeddingService trait实际项目中由共享模块提供#[async_trait::async_trait]pub trait EmbeddingService: Send Sync {async fn embed(self, text: str) - ResultVec, String;}// 模拟的嵌入服务用文本长度作为伪嵌入向量仅用于演示struct MockEmbeddingService;#[async_trait::async_trait]impl EmbeddingService for MockEmbeddingService {async fn embed(self, text: str) - ResultVec, String {// 生成一个固定长度的向量用字符的 ASCII 值填充let vec: Vec text.chars().map(|c| c as u8 as f32 / 255.0).collect();// 补齐或截断到固定维度这里用 64 维let mut result vec![0.0f32; 64];for (i, v) in vec.iter().take(64).enumerate() {result[i] v;}Ok(result)}}/// 计算两个向量的余弦相似度fn cosine_similarity(a: [f32], b: [f32]) - f64 {let dot: f32 a.iter().zip(b.iter()).map(|(x, y)| x * y).sum();let norm_a: f32 a.iter().map(|x| x * x).sum::().sqrt();let norm_b: f32 b.iter().map(|x| x * x).sum::().sqrt();if norm_a 0.0 || norm_b 0.0 {return 0.0;}(dot / (norm_a * norm_b)) as f64}/// RelevanceTracker 的简化实现用于实战演示pub struct RelevanceTracker {task_5w2h_embedding: OptionVec,prev_input_embedding: OptionVec,alpha: f64,embedder: OptionArc,}impl RelevanceTracker {pub fn new(alpha: f64, embedder: Arc) - Self {Self {task_5w2h_embedding: None,prev_input_embedding: None,alpha,embedder: Some(embedder),}}/// 设置任务 5W2H 描述通常在任务初始化时调用 pub async fn set_task_5w2h(mut self, task_desc: str) - Result(), String { let emb self.embedder.as_ref().unwrap().embed(task_desc).await?; self.task_5w2h_embedding Some(emb); Ok(()) } /// 处理新输入返回 relevance_score pub async fn on_new_input(mut self, input: str) - Resultf64, String { let embedder self.embedder.as_ref().unwrap(); let input_emb embedder.embed(input).await?; // 计算全局任务相关度 let global_sim match self.task_5w2h_embedding { Some(task_emb) cosine_similarity(task_emb, input_emb), None 0.0, // 尚未设置任务描述时默认为 0 }; // 计算局部连贯性 let local_sim match self.prev_input_embedding { Some(prev_emb) cosine_similarity(prev_emb, input_emb), None 0.0, // 第一条输入没有前驱 }; // 合成最终分数 let relevance_score self.alpha * global_sim (1.0 - self.alpha) * local_sim; // 更新前一条输入的嵌入 self.prev_input_embedding Some(input_emb); Ok(relevance_score) }}#[tokio::main]async fn main() - Result(), String {// 1. 初始化嵌入服务let embedder Arc::new(MockEmbeddingService);// 2. 创建 RelevanceTrackerα 0.6全局任务相关度占主导 let mut tracker RelevanceTracker::new(0.6, embedder); // 3. 设置任务 5W2H 描述 tracker .set_task_5w2h(开发一个基于 Rust 的 Agent 框架支持多轮对话和上下文管理) .await?; // 4. 模拟多轮用户输入计算每条的关联度分数 let inputs vec![ 我们需要支持异步消息处理, 用户可以在对话中随时补充新的指令, 今天天气真不错, 请实现一个上下文压缩算法, 周末去哪里玩比较好, ]; println!( RelevanceTracker 实战演示 ); println!(任务描述开发一个基于 Rust 的 Agent 框架支持多轮对话和上下文管理\n); for (i, input) in inputs.iter().enumerate() { let score tracker.on_new_input(input).await?; println!(输入 #{}: {}, i 1, input); println!(relevance_score: {:.4}, score); println!(---); } Ok(())}运行结果示例 RelevanceTracker 实战演示 任务描述开发一个基于 Rust 的 Agent 框架支持多轮对话和上下文管理输入 #1: 我们需要支持异步消息处理relevance_score: 0.5231输入 #2: 用户可以在对话中随时补充新的指令relevance_score: 0.4876输入 #3: 今天天气真不错relevance_score: 0.1243 ← 话题漂移分数显著降低输入 #4: 请实现一个上下文压缩算法relevance_score: 0.5612 ← 回到任务主线分数回升输入 #5: 周末去哪里玩比较好relevance_score: 0.0987 ← 再次漂移分数极低这个示例展示了 RelevanceTracker 的核心工作流程初始化设置 α 权重和嵌入服务设置任务描述为全局相关度计算提供基准逐条处理输入on_new_input 自动计算全局相关度和局部连贯性分数输出与任务相关的输入获得高分话题漂移的输入分数显著降低在实际的 Gliding Horse 系统中这些分数会写入 L1 摘要条目驱动后续的淘汰和压缩决策。三、L1 淘汰策略增强让低相关信息“主动让位”Gliding Horse 的 L1 会话摘要链原本就有一套基于时间、语义和 Token 成本的智能淘汰算法。这次增强引入了硬阈值预过滤和任务关联度融合两层升级。3.1 硬阈值预过滤在进入传统的评分排序之前系统先执行一轮快速扫荡如果某条摘要的 relevance_score 0.3且它的最后访问时间距今已超过安全窗口默认 300 秒且它不是补充输入is_supplement false则直接淘汰无需参与后续排序。这能迅速清除话题切换后残留的无关信息。而补充输入因为标记了 is_supplement true会被这条规则保护确保用户的关键补充不会被误删。3.2 融合评分淘汰对于通过硬阈值的条目系统采用改进后的评分公式score w1 * (1/time_since) w2 * (1 / (β * query_sim (1-β) * relevance_score)) w3 * token_cost其中 β 默认 0.7表示当前查询的即时相关性权重更高但历史任务关联度依然占三成。这种双层语义评分让淘汰决策既响应实时需求又保持对全局任务的忠诚。所有被淘汰的摘要其完整内容早已通过 IRI 归档到 L0 持久层LLM 随时可以通过微工具按 IRI 精确回溯信息零丢失。四、ContextWindowManager 感知增强当消息列表总 Token 数或消息数量超过硬上限时ContextWindowManager 会介入对整个消息序列进行压缩。增强后的压缩器可以接收来自 L1 的 relevance_score 映射在压缩中间消息时优先保留高 relevance 的消息而非简单地从末尾截取。这使得压缩后的历史摘要更贴合任务主线而不是机械地保留最近的 N 条。五、后台话题连贯性分析除了实时计算系统还注册了一个新的 Batch Agenttopic_coherence_agent。它每 2 分钟可配置运行一次对当前 L1 会话的全量摘要重新计算与任务全局主题的嵌入相似度并检测相邻输入之间的语义漂移。一旦发现连续两轮的语义相似度低于阈值默认 0.4就判定为“话题切换”并通过 EventBus 发出 TopicShiftDetected 事件。该事件会触发一次主动上下文压缩将旧话题相关摘要批量归档为新话题腾出宝贵的 Token 空间。CompressorL1Sessiontopic_coherence_agentBatchAgentManagerCron TriggerCompressorL1Sessiontopic_coherence_agentBatchAgentManagerCron Triggeralt[相似度 0.4]触发 topic_coherence_agent执行分析遍历所有 turns计算与 task_5w2h 的 cosine检测相邻 turn 相似度TopicShiftDetected 事件触发主动压缩六、给平台带来的核心优势指标 优化前 优化后话题切换响应 依赖被动淘汰旧信息长期滞留 主动检测 即时压缩无关内容秒级清除补充输入可靠性 可能丢失Agent 看不到 可靠注入且受淘汰保护上下文精准度 淘汰只看时间和简单语义 双层任务关联度驱动更聚焦Token 利用率 多话题长会话浪费 30-50% Token 节省 20-40%全部用于核心内容
【Agent Harness】Gliding Horse 上下文感知与智能压缩:让 Agent 的“注意力”永不偏移
Gliding Horse 上下文感知与智能压缩让 Agent 的“注意力”永不偏移摘要Gliding Horse 通过 RelevanceTracker 双维度评分、L1 淘汰策略增强、ContextWindowManager 感知压缩和后台话题连贯性分析构建了一套完整的上下文相关性感知与智能压缩系统。该系统解决了多轮对话中 Agent 面临的话题漂移和信息过载两大核心痛点实现了 LLM 注意力窗口的精细化管理使 Token 利用率提升 20-40%话题切换响应从被动淘汰升级为主动检测补充输入可靠性得到根本保障。这套系统是让 Agent 从“能跑”走向“长跑”的关键基础设施。关键词Gliding Horse上下文感知智能压缩RelevanceTrackerLLM 注意力窗口话题漂移信息过载语义关联度L1 淘汰策略Token 利用率话题连贯性分析Batch Agent在多轮对话中Agent 最怕两件事话题漂移和信息过载。用户可能在长会话中突然插入一个全新的需求或者任务本身横跨多个领域导致早期上下文与当前话题毫无关联却仍然占据着昂贵的 LLM 注意力窗口。Gliding Horse 给出的方案是一套 上下文相关性感知与智能压缩系统。它不仅仅依据“最近有没有用到”来淘汰信息而是实时计算每条历史摘要与当前任务的语义关联度并融合话题连贯性检测确保 LLM 的上下文窗口永远聚焦在最有价值的内容上。这套系统已经在 Gliding Horse 的内核中完整实现并与其他模块深度集成本文将完整拆解它的设计细节。一、两条输入路径同一个追踪目标Gliding Horse 中用户输入进入系统有两条截然不同的路径初始任务输入通过 TUI/API 传入经 SA 解析后形成 TaskContext在 TaskStart Hook 点触发处理。中间补充输入在 Agent 执行过程中用户实时发出的补充指令或纠正信息。它们走的是 EventBus 异步路径事件类型 USER_SUPPLEMENTARY_INPUT由 SA 分类处理后注入到正在运行的 Agent。这两条路径在旧版中存在一个关键缺陷补充输入通过 EventBus 广播能够显示在 TUI 上但从未被 Agent 真正消费。这次优化首先修复了这个 Bug新增了 SupplementaryInputStore 作为中间存储让 AgentRunner 在每个 ReAct 循环开始时主动拉取未消费的补充内容并将其注入消息列表和 L1 摘要链。修复之后的完整输入路径如下L1 条目元数据AgentRunner CycleStartSupplementaryInputStoreEventBus 路径补充输入Hook 路径初始输入两条用户输入路径初始任务输入中间补充输入TaskStart HookRelevanceTracker.on_new_inputEventBus.emitUSER_SUPPLEMENTARY_INPUTSA.event_receiver → 分类store(content, embedding, relevance)pending: Vectake_pending() → 注入 messages[]L1Session.add_summary()L1Turn { relevance_score, embedding, is_supplement }无论是初始任务还是中途补充系统都会通过 RelevanceTracker 实时计算该输入与当前任务的全局相关度和局部连贯性并将 relevance_score 写入对应的 L1 摘要条目中。二、RelevanceTracker给每条信息打上“注意力分数”RelevanceTracker 是整个系统的感知核心。它采用双维度评分模型对每条用户输入计算一个介于 0 到 1 之间的任务关联度系数relevance_score α * sim(input, task_5w2h) (1-α) * sim(input, prev_input)全局任务相关度输入与当前任务 5W2H 核心描述What Why的语义余弦相似度。局部连贯性输入与前一条输入的语义相似度用于检测话题连续性。其中 α 默认为 0.6即全局任务相关度占主导地位。计算所需的文本嵌入由共享的 EmbeddingService可配置 Ollama、OpenAI 兼容 API 或本地模型提供。pub struct RelevanceTracker {task_5w2h_embedding: OptionVec,prev_input_embedding: OptionVec,alpha: f64,embedder: OptionArc,}每次新输入到达on_new_input() 会生成嵌入向量计算两项相似度后合成最终分数并更新内部状态。这个分数会伴随输入一路存入 L1 条目、SupplementaryInputStore最终影响淘汰和压缩决策。下面是一个完整的 Rust 实战示例展示如何初始化 RelevanceTracker、调用 on_new_input 方法计算关联度分数并打印结果use std::sync::Arc;// 假设的 EmbeddingService trait实际项目中由共享模块提供#[async_trait::async_trait]pub trait EmbeddingService: Send Sync {async fn embed(self, text: str) - ResultVec, String;}// 模拟的嵌入服务用文本长度作为伪嵌入向量仅用于演示struct MockEmbeddingService;#[async_trait::async_trait]impl EmbeddingService for MockEmbeddingService {async fn embed(self, text: str) - ResultVec, String {// 生成一个固定长度的向量用字符的 ASCII 值填充let vec: Vec text.chars().map(|c| c as u8 as f32 / 255.0).collect();// 补齐或截断到固定维度这里用 64 维let mut result vec![0.0f32; 64];for (i, v) in vec.iter().take(64).enumerate() {result[i] v;}Ok(result)}}/// 计算两个向量的余弦相似度fn cosine_similarity(a: [f32], b: [f32]) - f64 {let dot: f32 a.iter().zip(b.iter()).map(|(x, y)| x * y).sum();let norm_a: f32 a.iter().map(|x| x * x).sum::().sqrt();let norm_b: f32 b.iter().map(|x| x * x).sum::().sqrt();if norm_a 0.0 || norm_b 0.0 {return 0.0;}(dot / (norm_a * norm_b)) as f64}/// RelevanceTracker 的简化实现用于实战演示pub struct RelevanceTracker {task_5w2h_embedding: OptionVec,prev_input_embedding: OptionVec,alpha: f64,embedder: OptionArc,}impl RelevanceTracker {pub fn new(alpha: f64, embedder: Arc) - Self {Self {task_5w2h_embedding: None,prev_input_embedding: None,alpha,embedder: Some(embedder),}}/// 设置任务 5W2H 描述通常在任务初始化时调用 pub async fn set_task_5w2h(mut self, task_desc: str) - Result(), String { let emb self.embedder.as_ref().unwrap().embed(task_desc).await?; self.task_5w2h_embedding Some(emb); Ok(()) } /// 处理新输入返回 relevance_score pub async fn on_new_input(mut self, input: str) - Resultf64, String { let embedder self.embedder.as_ref().unwrap(); let input_emb embedder.embed(input).await?; // 计算全局任务相关度 let global_sim match self.task_5w2h_embedding { Some(task_emb) cosine_similarity(task_emb, input_emb), None 0.0, // 尚未设置任务描述时默认为 0 }; // 计算局部连贯性 let local_sim match self.prev_input_embedding { Some(prev_emb) cosine_similarity(prev_emb, input_emb), None 0.0, // 第一条输入没有前驱 }; // 合成最终分数 let relevance_score self.alpha * global_sim (1.0 - self.alpha) * local_sim; // 更新前一条输入的嵌入 self.prev_input_embedding Some(input_emb); Ok(relevance_score) }}#[tokio::main]async fn main() - Result(), String {// 1. 初始化嵌入服务let embedder Arc::new(MockEmbeddingService);// 2. 创建 RelevanceTrackerα 0.6全局任务相关度占主导 let mut tracker RelevanceTracker::new(0.6, embedder); // 3. 设置任务 5W2H 描述 tracker .set_task_5w2h(开发一个基于 Rust 的 Agent 框架支持多轮对话和上下文管理) .await?; // 4. 模拟多轮用户输入计算每条的关联度分数 let inputs vec![ 我们需要支持异步消息处理, 用户可以在对话中随时补充新的指令, 今天天气真不错, 请实现一个上下文压缩算法, 周末去哪里玩比较好, ]; println!( RelevanceTracker 实战演示 ); println!(任务描述开发一个基于 Rust 的 Agent 框架支持多轮对话和上下文管理\n); for (i, input) in inputs.iter().enumerate() { let score tracker.on_new_input(input).await?; println!(输入 #{}: {}, i 1, input); println!(relevance_score: {:.4}, score); println!(---); } Ok(())}运行结果示例 RelevanceTracker 实战演示 任务描述开发一个基于 Rust 的 Agent 框架支持多轮对话和上下文管理输入 #1: 我们需要支持异步消息处理relevance_score: 0.5231输入 #2: 用户可以在对话中随时补充新的指令relevance_score: 0.4876输入 #3: 今天天气真不错relevance_score: 0.1243 ← 话题漂移分数显著降低输入 #4: 请实现一个上下文压缩算法relevance_score: 0.5612 ← 回到任务主线分数回升输入 #5: 周末去哪里玩比较好relevance_score: 0.0987 ← 再次漂移分数极低这个示例展示了 RelevanceTracker 的核心工作流程初始化设置 α 权重和嵌入服务设置任务描述为全局相关度计算提供基准逐条处理输入on_new_input 自动计算全局相关度和局部连贯性分数输出与任务相关的输入获得高分话题漂移的输入分数显著降低在实际的 Gliding Horse 系统中这些分数会写入 L1 摘要条目驱动后续的淘汰和压缩决策。三、L1 淘汰策略增强让低相关信息“主动让位”Gliding Horse 的 L1 会话摘要链原本就有一套基于时间、语义和 Token 成本的智能淘汰算法。这次增强引入了硬阈值预过滤和任务关联度融合两层升级。3.1 硬阈值预过滤在进入传统的评分排序之前系统先执行一轮快速扫荡如果某条摘要的 relevance_score 0.3且它的最后访问时间距今已超过安全窗口默认 300 秒且它不是补充输入is_supplement false则直接淘汰无需参与后续排序。这能迅速清除话题切换后残留的无关信息。而补充输入因为标记了 is_supplement true会被这条规则保护确保用户的关键补充不会被误删。3.2 融合评分淘汰对于通过硬阈值的条目系统采用改进后的评分公式score w1 * (1/time_since) w2 * (1 / (β * query_sim (1-β) * relevance_score)) w3 * token_cost其中 β 默认 0.7表示当前查询的即时相关性权重更高但历史任务关联度依然占三成。这种双层语义评分让淘汰决策既响应实时需求又保持对全局任务的忠诚。所有被淘汰的摘要其完整内容早已通过 IRI 归档到 L0 持久层LLM 随时可以通过微工具按 IRI 精确回溯信息零丢失。四、ContextWindowManager 感知增强当消息列表总 Token 数或消息数量超过硬上限时ContextWindowManager 会介入对整个消息序列进行压缩。增强后的压缩器可以接收来自 L1 的 relevance_score 映射在压缩中间消息时优先保留高 relevance 的消息而非简单地从末尾截取。这使得压缩后的历史摘要更贴合任务主线而不是机械地保留最近的 N 条。五、后台话题连贯性分析除了实时计算系统还注册了一个新的 Batch Agenttopic_coherence_agent。它每 2 分钟可配置运行一次对当前 L1 会话的全量摘要重新计算与任务全局主题的嵌入相似度并检测相邻输入之间的语义漂移。一旦发现连续两轮的语义相似度低于阈值默认 0.4就判定为“话题切换”并通过 EventBus 发出 TopicShiftDetected 事件。该事件会触发一次主动上下文压缩将旧话题相关摘要批量归档为新话题腾出宝贵的 Token 空间。CompressorL1Sessiontopic_coherence_agentBatchAgentManagerCron TriggerCompressorL1Sessiontopic_coherence_agentBatchAgentManagerCron Triggeralt[相似度 0.4]触发 topic_coherence_agent执行分析遍历所有 turns计算与 task_5w2h 的 cosine检测相邻 turn 相似度TopicShiftDetected 事件触发主动压缩六、给平台带来的核心优势指标 优化前 优化后话题切换响应 依赖被动淘汰旧信息长期滞留 主动检测 即时压缩无关内容秒级清除补充输入可靠性 可能丢失Agent 看不到 可靠注入且受淘汰保护上下文精准度 淘汰只看时间和简单语义 双层任务关联度驱动更聚焦Token 利用率 多话题长会话浪费 30-50% Token 节省 20-40%全部用于核心内容