| name | sage-context-management |
| description | Sage 上下文管理开发指南,涵盖 Token 估算、消息裁剪、自动压缩、摘要生成 |
| when_to_use | 当涉及上下文窗口管理、Token 计数、消息裁剪、Auto-Compact 时使用 |
| allowed_tools | ["Read","Grep","Glob","Edit","Write","Bash"] |
| user_invocable | true |
| priority | 89 |
Sage 上下文管理开发指南
模块概览
上下文模块管理 LLM 对话的上下文窗口,代码量 4029 行,包含:
crates/sage-core/src/context/
├── mod.rs # 公开接口 (68行)
├── config.rs # 配置 (263行)
├── estimator.rs # Token 估算器 (223行)
├── pruner.rs # 消息裁剪器 (409行)
├── summarizer.rs # 摘要生成器 (359行)
├── compact.rs # 压缩操作 (459行)
├── manager/ # 上下文管理器
│ ├── mod.rs # 入口
│ ├── core.rs # 核心实现 (148行)
│ ├── operations.rs # 操作方法 (180行)
│ ├── types.rs # 类型定义 (67行)
│ └── tests.rs # 测试
├── auto_compact/ # 自动压缩(学习自 Claude Code)
│ ├── mod.rs # 入口
│ ├── config.rs # 配置
│ ├── manager.rs # 管理器
│ ├── operations.rs # 操作
│ ├── partition.rs # 分区
│ ├── result.rs # 结果
│ ├── stats.rs # 统计
│ ├── summary.rs # 摘要 (142行)
│ └── tests.rs # 测试
└── streaming/ # 流式计数
├── mod.rs # 入口
├── counter.rs # 计数器 (157行)
├── metrics.rs # 指标 (163行)
├── types.rs # 类型 (79行)
└── tests.rs # 测试
一、核心架构
1.1 ContextManager
#[derive(Clone)]
pub struct ContextManager {
pub(super) config: ContextConfig,
pub(super) estimator: TokenEstimator,
pub(super) pruner: MessagePruner,
pub(super) summarizer: ConversationSummarizer,
}
impl ContextManager {
pub fn new(config: ContextConfig) -> Self {
let estimator = TokenEstimator::new();
let pruner = MessagePruner::new(config.clone());
let summarizer = ConversationSummarizer::new();
Self { config, estimator, pruner, summarizer }
}
pub fn for_provider(provider: &str, model: &str) -> Self {
let config = ContextConfig::for_provider(provider, model);
= TokenEstimator::for_provider(provider);
}
(&, messages: &[LlmMessage]) {
.estimator.(messages)
}
(&, messages: &[LlmMessage]) {
= .estimator.(messages);
current_tokens >= .config.()
}
(&, messages: &[LlmMessage]) ContextUsageStats {
= .estimator.(messages);
= .config.max_context_tokens;
ContextUsageStats {
current_tokens,
max_tokens,
usage_percentage: (current_tokens / max_tokens ) * ,
messages_count: messages.(),
}
}
}
1.2 配置结构
#[derive(Debug, Clone)]
pub struct ContextConfig {
pub max_context_tokens: usize,
pub threshold_ratio: f32,
pub overflow_strategy: OverflowStrategy,
pub preserve_system_messages: bool,
pub preserve_recent_count: usize,
pub reserved_for_response: usize,
}
#[derive(Debug, Clone, Copy)]
pub enum OverflowStrategy {
Prune,
Summarize,
AutoCompact,
Error,
}
impl ContextConfig {
pub fn for_provider(provider: &str, model: &str) -> Self {
match provider {
"anthropic" => match model {
m if m.contains("opus") => Self::claude_opus(),
m m.() => ::(),
_ => ::(),
},
=> model {
m m.() => ::(),
m m.() => ::(),
_ => ::(),
},
_ => ::(),
}
}
(&) {
(.max_context_tokens * .threshold_ratio)
}
}
二、Token 估算
2.1 TokenEstimator
pub struct TokenEstimator {
chars_per_token: f32,
provider_overhead: usize,
}
impl TokenEstimator {
pub fn estimate_message(&self, message: &LlmMessage) -> usize {
let content_tokens = self.estimate_text(&message.content);
let role_tokens = 4;
content_tokens + role_tokens + self.provider_overhead
}
pub fn estimate_conversation(&self, messages: &[LlmMessage]) -> usize {
messages.iter().map(|m| self.estimate_message(m)).sum()
}
pub fn estimate_request(
&self,
messages: &[LlmMessage],
tools: Option<&[ToolSchema]>,
) -> usize {
let = .(messages);
= tools
.(|t| .(t))
.();
conversation_tokens + tool_tokens
}
(&, text: &) {
(text.() / .chars_per_token).()
}
}
2.2 Provider 特定估算
impl TokenEstimator {
pub fn for_provider(provider: &str) -> Self {
match provider {
"anthropic" => Self {
chars_per_token: 4.0,
provider_overhead: 8,
},
"openai" => Self {
chars_per_token: 4.0,
provider_overhead: 4,
},
"google" => Self {
chars_per_token: 4.5,
provider_overhead: 6,
},
_ => Self::default(),
}
}
}
三、消息裁剪
3.1 MessagePruner
pub struct MessagePruner {
config: ContextConfig,
}
impl MessagePruner {
pub fn prune(&self, messages: Vec<LlmMessage>, target_tokens: usize) -> PruneResult {
let mut result = messages.clone();
let mut removed_count = 0;
let system_messages: Vec<_> = result
.iter()
.filter(|m| m.role == MessageRole::System)
.cloned()
.collect();
let recent_count = self.config.preserve_recent_count;
let recent_messages: Vec<_> = result
.iter()
.rev()
.take(recent_count)
.cloned()
.collect();
while self.estimate_tokens(&result) > target_tokens {
result.() <= recent_count + system_messages.() {
;
}
= result.().(|m| m.role != MessageRole::System);
(i) = idx {
result.(i);
removed_count += ;
}
}
PruneResult {
messages: result,
removed_count,
tokens_saved: .(&messages) - .(&result),
}
}
}
{
messages: <LlmMessage>,
removed_count: ,
tokens_saved: ,
}
四、自动压缩(Auto-Compact)
4.1 设计理念(学习自 Claude Code)
当上下文接近限制时,自动压缩历史对话为摘要:
原始对话:
[System] You are a helpful assistant
[User] Hello
[Assistant] Hi there!
[User] What is Rust?
[Assistant] Rust is a systems programming language...
[User] Show me an example
[Assistant] Here's a simple example...
... (更多消息) ...
压缩后:
[System] You are a helpful assistant
[Compact Boundary] <summary of previous conversation>
[User] (最近的消息)
[Assistant] (最近的响应)
4.2 AutoCompact 配置
pub struct AutoCompactConfig {
pub enabled: bool,
pub max_context_tokens: usize,
pub compact_threshold_ratio: f32,
pub reserved_for_response: usize,
pub target_ratio: f32,
}
impl AutoCompactConfig {
pub fn with_env_override(mut self) -> Self {
if let Ok(val) = std::env::var(AUTOCOMPACT_PCT_OVERRIDE_ENV) {
if let Ok(pct) = val.parse::<f32>() {
self.compact_threshold_ratio = pct / 100.0;
}
}
self
}
}
4.3 AutoCompact 管理器
pub struct AutoCompact {
config: AutoCompactConfig,
llm_client: Option<Arc<LlmClient>>,
stats: AutoCompactStats,
}
impl AutoCompact {
pub fn needs_compaction(&self, messages: &[LlmMessage]) -> bool {
if !self.config.enabled {
return false;
}
let active_messages = slice_from_last_compact_boundary(messages);
let current_tokens = self.estimate_tokens(&active_messages);
current_tokens >= self.config.threshold_tokens()
}
pub async fn check_and_compact(
&mut self,
messages: &mut Vec<LlmMessage>,
) -> SageResult<CompactResult> {
if !self.needs_compaction(messages) {
self.stats.skipped_count += 1;
return Ok(CompactResult::Skipped);
}
= .(messages).?;
.stats.compact_count += ;
.stats.tokens_saved += result.tokens_saved;
(result)
}
(&, messages: & <LlmMessage>) SageResult<CompactResult> {
= (messages);
= boundary_idx.(|i| i + ).();
(to_compact, to_keep) = partition::(
&messages[active_start..],
.config.preserve_recent_count,
);
= (client) = &.llm_client {
summary::(client, &to_compact).?
} {
summary::(&to_compact)
};
= (&summary);
= &messages[..active_start];
= [
preserved,
&[compact_boundary],
&to_keep,
].();
= .(messages);
= .(&new_messages);
*messages = new_messages;
(CompactResult::Compacted {
messages_compacted: to_compact.(),
tokens_saved: tokens_before - tokens_after,
})
}
}
4.4 压缩边界
pub const COMPACT_BOUNDARY_KEY: &str = "compact_boundary";
pub const COMPACT_SUMMARY_KEY: &str = "compact_summary";
pub const COMPACT_TIMESTAMP_KEY: &str = "compact_timestamp";
pub const COMPACT_ID_KEY: &str = "compact_id";
pub fn create_compact_boundary(summary: &str) -> LlmMessage {
let mut metadata = HashMap::new();
metadata.insert(COMPACT_BOUNDARY_KEY.to_string(), json!(true));
metadata.insert(COMPACT_SUMMARY_KEY.to_string(), json!(summary));
metadata.insert(COMPACT_TIMESTAMP_KEY.to_string(), json!(Utc::now().to_rfc3339()));
metadata.insert(COMPACT_ID_KEY.to_string(), json!(Uuid::new_v4().to_string()));
LlmMessage::assistant(format!(
"[Previous conversation summary]\n\n{}\n\n[End of summary]",
summary
)).with_metadata(metadata)
}
(message: &LlmMessage) {
message.metadata
.(COMPACT_BOUNDARY_KEY)
.(|v| v.())
.()
}
(messages: &[LlmMessage]) <> {
messages.().(is_compact_boundary)
}
(messages: &[LlmMessage]) &[LlmMessage] {
(messages) {
(idx) => &messages[idx + ..],
=> messages,
}
}
五、摘要生成
5.1 ConversationSummarizer
pub struct ConversationSummarizer {
llm_client: Option<Arc<LlmClient>>,
}
impl ConversationSummarizer {
pub async fn summarize(&self, messages: &[LlmMessage]) -> SageResult<String> {
if let Some(client) = &self.llm_client {
self.summarize_with_llm(client, messages).await
} else {
Ok(self.simple_summarize(messages))
}
}
async fn summarize_with_llm(
&self,
client: &LlmClient,
messages: &[LlmMessage],
) -> SageResult<String> {
let prompt = build_summary_prompt(messages);
let response = client.chat(&[LlmMessage::user(prompt)], None).await?;
Ok(response.content)
}
fn simple_summarize(&, messages: &[LlmMessage]) {
= messages.().(|m| m.role == MessageRole::User).();
= messages.().(|m| m.role == MessageRole::Assistant).();
(
,
user_count, assistant_count
)
}
}
六、流式 Token 计数
6.1 StreamingTokenCounter
pub struct StreamingTokenCounter {
estimator: TokenEstimator,
input_tokens: AtomicUsize,
output_tokens: AtomicUsize,
}
impl StreamingTokenCounter {
pub fn record_input(&self, text: &str) {
let tokens = self.estimator.estimate_text(text);
self.input_tokens.fetch_add(tokens, Ordering::SeqCst);
}
pub fn record_output(&self, chunk: &str) {
let tokens = self.estimator.estimate_text(chunk);
self.output_tokens.fetch_add(tokens, Ordering::SeqCst);
}
pub fn get_stats(&self) -> StreamingStats {
StreamingStats {
input_tokens: self.input_tokens.load(Ordering::SeqCst),
output_tokens: self.output_tokens.load(Ordering::SeqCst),
}
}
}
七、使用示例
7.1 基本使用
use sage_core::context::{ContextManager, ContextConfig};
let config = ContextConfig::for_provider("anthropic", "claude-3.5-sonnet");
let manager = ContextManager::new(config);
let stats = manager.get_usage_stats(&messages);
println!("Usage: {:.1}%", stats.usage_percentage);
if manager.is_approaching_limit(&messages) {
let pruned = manager.prune(messages.clone(), 8000);
println!("Removed {} messages", pruned.removed_count);
}
7.2 Auto-Compact 使用
use sage_core::context::{AutoCompact, AutoCompactConfig};
let config = AutoCompactConfig::default()
.with_env_override();
let mut auto_compact = AutoCompact::with_llm_client(config, llm_client);
let result = auto_compact.check_and_compact(&mut messages).await?;
match result {
CompactResult::Compacted { messages_compacted, tokens_saved } => {
println!("Compacted {} messages, saved {} tokens", messages_compacted, tokens_saved);
}
CompactResult::Skipped => {
}
}
八、相关模块
sage-llm-integration - LLM 客户端(用于摘要生成)
sage-session-management - 会话管理(消息存储)
sage-agent-execution - Agent 执行(上下文集成)
最后更新: 2026-01-10