gns3-server/docs/gns3-copilot/context-window-management.md
YueGuobin d12b2df306 feat(docs): update context window management documentation
- Refactor system message structure to combine system prompt and topology info using template variables
- Update token calculation process with merged system message approach
- Clarify priority order for message retention during context window management
- Add detailed token counting implementation using tiktoken library
- Include boundary case handling for system message exceeding budget
- Add SPDX license headers to tool files for proper licensing documentation
2026-03-05 11:23:11 +08:00

855 lines
28 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# LLM 上下文窗口管理实现文档
## 概述
本文档说明了 GNS3 Copilot 如何处理不同 LLM 模型的上下文窗口限制,以及如何实现自动消息裁剪功能。
## ⚠️ 重要context_limit 必须手动配置
**由于模型供应商频繁更新上下文窗口大小,系统不再提供内置默认值。**
用户在创建 LLM 模型配置时**必须提供 `context_limit`**。请从模型供应商的官方文档获取最新的上下文窗口大小。
### ⚡ 依赖要求
**tiktoken 是必需依赖**,系统无法在没有 tiktoken 的情况下运行。
```bash
pip install tiktoken>=0.8.0
```
如果未安装 tiktoken系统将在首次尝试计数 tokens 时抛出 ImportError。
### ⚡ 单位说明
**`context_limit` 的单位是 K tokens千 tokens**
- `1` = 1K = 1,000 tokens
- `128` = 128K = 128,000 tokens
- `200` = 200K = 200,000 tokens
- `2800` = 2800K = 2,800,000 tokens
### 为什么使用 K tokens
1. **更简洁** - `128``128000` 更易读易写
2. **减少错误** - 避免少写或多写 0
3. **符合习惯** - 与业界常用的 "128K", "200K" 表示法一致
### 为什么必须手动配置?
1. **模型更新频繁** - OpenAI、Anthropic、Google 等供应商经常发布模型更新
2. **上下文大小变化** - 新版本往往增加上下文窗口,旧版本可能被废弃
3. **维护成本高** - 内置默认值很快过时,可能导致配置错误
4. **责任明确** - 用户根据实际使用的模型配置,避免混淆
---
## 数据库配置方式
### 配置要求
`context_limit` 是**必填字段**,单位为 **K tokens**。创建或更新 LLM 模型配置时必须提供。
| 字段 | 类型 | 必填 | 单位 | 说明 |
|------|------|------|------|------|
| `context_limit` | `int` | ✅ **是** | K tokens | 模型上下文窗口限制128 = 128K tokens |
| `context_strategy` | `"conservative" \| "balanced" \| "aggressive"` | 否 | - | 裁剪策略,默认 "balanced" |
### 获取上下文窗口大小
#### 官方文档链接
| 供应商 | 文档链接 | 示例值tokens | 配置值K |
|--------|---------|------------------|------------|
| OpenAI | https://platform.openai.com/docs/models | 128,000 | `128` |
| Anthropic | https://docs.anthropic.com/claude/docs/models-overview | 200,000 | `200` |
| Google | https://ai.google.dev/gemini-api/docs/models | 2,800,000 | `2800` |
| DeepSeek | https://platform.deepseek.com/api-docs/ | 128,000 | `128` |
| xAI | https://docs.x.ai/ | 128,000 | `128` |
**转换示例**
- 官方文档显示 `128,000 tokens` → 配置为 `128`
- 官方文档显示 `200,000 tokens` → 配置为 `200`
- 官方文档显示 `2,800,000 tokens` → 配置为 `2800`
#### 参考工具
运行参考脚本查看常见模型的上下文限制(显示为 K tokens
```bash
python scripts/show_model_context_limits.py
```
**注意**:此脚本仅提供参考值,请以官方文档为准。
---
## API 使用示例
### 创建配置(必须提供 context_limit
```bash
POST /v3/users/{user_id}/llm-model-configs
Content-Type: application/json
{
"name": "GPT-4o Configuration",
"model_type": "text",
"is_default": true,
"provider": "openai",
"base_url": "https://api.openai.com/v1",
"model": "gpt-4o",
"temperature": 0.7,
"api_key": "sk-...",
"context_limit": 128, # ← 必填:单位是 K tokens (128 = 128K = 128,000 tokens)
"context_strategy": "balanced" # 可选,默认 "balanced"
}
```
### 配置示例对比
#### 官方文档 → API 配置
| 模型 | 官方文档 | API 配置 |
|------|---------|---------|
| GPT-4o | 128,000 tokens | `"context_limit": 128` |
| GPT-3.5 Turbo | 16,385 tokens | `"context_limit": 17` (向上取整) |
| Claude 3.5 Sonnet | 200,000 tokens | `"context_limit": 200` |
| Gemini 1.5 Pro | 2,800,000 tokens | `"context_limit": 2800` |
### 错误示例(缺少 context_limit
```bash
POST /v3/users/{user_id}/llm-model-configs
Content-Type: application/json
{
"name": "Invalid Config",
"provider": "openai",
"model": "gpt-4o",
# ❌ 缺少 context_limit
}
```
**响应**
```json
{
"detail": "context_limit is required (unit: K tokens, e.g., 128 = 128K = 128,000 tokens). Please check your model provider's documentation for the current context window size and specify it in the configuration."
}
```
---
## 常见模型配置参考
以下是一些常见模型的 `context_limit` 配置值(**请以官方文档为准**
### OpenAI
| 模型 | 官方文档 | 配置值 |
|------|---------|-------|
| GPT-4o | 128,000 tokens | `"context_limit": 128` |
| GPT-4o-mini | 128,000 tokens | `"context_limit": 128` |
| GPT-4 Turbo | 128,000 tokens | `"context_limit": 128` |
| GPT-3.5 Turbo | 16,385 tokens | `"context_limit": 17` |
### Anthropic
| 模型 | 官方文档 | 配置值 |
|------|---------|-------|
| Claude 3.5 Sonnet | 200,000 tokens | `"context_limit": 200` |
| Claude 3 Opus | 200,000 tokens | `"context_limit": 200` |
| Claude 3 Haiku | 200,000 tokens | `"context_limit": 200` |
### Google
| 模型 | 官方文档 | 配置值 |
|------|---------|-------|
| Gemini 2.0 Flash | 1,000,000 tokens | `"context_limit": 1000` |
| Gemini 1.5 Pro | 2,800,000 tokens | `"context_limit": 2800` |
| Gemini 1.5 Flash | 2,800,000 tokens | `"context_limit": 2800` |
### DeepSeek
| 模型 | 官方文档 | 配置值 |
|------|---------|-------|
| DeepSeek Chat | 128,000 tokens | `"context_limit": 128` |
| DeepSeek Coder | 128,000 tokens | `"context_limit": 128` |
### xAI
| 模型 | 官方文档 | 配置值 |
|------|---------|-------|
| Grok Beta | 128,000 tokens | `"context_limit": 128` |
---
## 问题背景
### 原始问题
- 消息历史无限制累积,使用 `operator.add` 追加所有消息
- 不同模型的上下文窗口限制不同
- 当消息历史超过模型限制时LLM 调用会失败
- 没有优雅的降级或裁剪机制
### 解决方案
使用 LangChain/LangGraph 的内置功能实现智能上下文管理:
- **`trim_messages`**: LangChain 的消息裁剪工具
- **`count_tokens_approximately`**: Token 计数功能
- **用户配置**: 必须提供 context_limit单位K tokens无默认值
## 实现架构
### 1. 核心模块
**文件位置**: `gns3server/agent/gns3_copilot/agent/context_manager.py`
#### Token 计数策略
系统使用 **tiktoken** 进行准确的 Token 计数:
1. **tiktokenOpenAI 的 tokenizer**
- 使用 `cl100k_base` 编码GPT-4
- 对大多数现代 LLM 准确OpenAI、Anthropic、DeepSeek
- 准确率约 95%+
- **必需依赖**:系统强制要求安装 tiktoken
2. **工具定义 Token 估算**
- 工具的 schemaname、description、parameters会被转换为 JSON 发送给 LLM
- 系统会自动计算这些定义的 token 消耗
- 每个工具约 500-1500 tokens取决于 schema 复杂度)
#### 安装 tiktoken必需
```bash
pip install tiktoken>=0.8.0
```
**为什么使用 tiktoken**
- 比字符估算准确 2-3 倍(特别是中文内容)
- 支持 LangChain 使用的所有主流模型
- 性能优秀(缓存编码器)
- **这是必需依赖**,系统将无法运行如果未安装
#### 关键函数
**`get_model_context_limit(model_name: str, llm_config: dict) -> int`**
- 从数据库配置获取模型的上下文窗口大小
- **`llm_config` 必须包含 `context_limit` 字段单位K tokens**
- 如果未提供或无效,抛出 `ValueError`
- 返回值单位为实际 tokensK tokens × 1000
**`calculate_max_tokens(model_limit, strategy) -> int`**
- 计算可用 token 数量(预留输出空间)
- 三种策略:
- `conservative`: 使用 60% 限制(更安全)
- `balanced`: 使用 75% 限制(默认)
- `aggressive`: 使用 85% 限制(最大化输入)
**`trim_messages_for_context(messages, model_name, strategy, tool_tokens) -> list`**
- 使用 tiktoken 裁剪消息
- 保留最近的消息
- 始终保留系统消息
- **关键**:考虑工具定义占用的 tokens
### 2. 裁剪逻辑详解
#### 2.1 Token 预算分配
当调用 LLM 时,发送的内容包含两部分:
```
发送给 LLM 的完整请求:
┌─────────────────────────────────────────────────────────────┐
│ 1. Messages (我们管理的) │
│ ├─ SystemMessage: system prompt + topology (模板注入) │
│ └─ HumanMessage/AIMessage: 用户消息 / 历史消息 │
├─────────────────────────────────────────────────────────────┤
│ 2. Tool Definitions (LangChain 自动添加,不在消息中) │
│ ├─ Tool 1 schema (name, description, parameters) │
│ ├─ Tool 2 schema │
│ └─ ... (约 500-1500 tokens per tool) │
└─────────────────────────────────────────────────────────────┘
```
**System Message 结构**
- 使用模板变量 `{{topology_info}}` 动态注入 topology
- System prompt 包含占位符:`"### CURRENT TOPOLOGY\n{{topology_info}}"`
- 如果有 topology替换为实际内容
- 如果没有 topology替换为 `"(No topology information available)"`
#### 2.2 裁剪流程
```
第一步:计算输入预算
┌─────────────────────────────────────────────────────────────┐
│ context_limit: 16,000 tokens │
│ strategy: conservative (60%) │
│ │
│ 输入预算 = 16,000 × 0.6 = 9,600 tokens │
└─────────────────────────────────────────────────────────────┘
第二步:减去工具定义
┌─────────────────────────────────────────────────────────────┐
│ 输入预算: 9,600 tokens │
│ 工具定义: 1,862 tokens │
│ │
│ 可用于消息 = 9,600 - 1,862 = 7,738 tokens │
└─────────────────────────────────────────────────────────────┘
第三步:计算 System Message token已合并
┌─────────────────────────────────────────────────────────────┐
│ 可用于消息: 7,738 tokens │
│ │
│ System Message (已包含 system + topology): │
│ - System prompt (base): ~1,000 tokens │
│ - Topology info: ~3,000 tokens │
│ = 4,000 tokens (合并后) │
│ │
│ 可用于历史 = 7,738 - 4,000 = 3,738 tokens │
└─────────────────────────────────────────────────────────────┘
第四步:裁剪对话历史
┌─────────────────────────────────────────────────────────────┐
│ 历史消息: 5,000 tokens → 需要裁剪到 3,738 tokens │
│ │
│ 丢弃旧消息,直到满足限制 │
└─────────────────────────────────────────────────────────────┘
```
#### 2.3 裁剪优先级
系统按以下优先级保留内容:
| 优先级 | 内容 | 说明 |
|--------|------|------|
| 1⃣ | System Message (system prompt + topology) | 永远保留 |
| 2⃣ | 最新用户消息 | 至少保留最后1条 |
| 3⃣ | 旧对话历史 | 按时间顺序丢弃 |
**注意**System prompt 和 topology info 通过模板变量合并为一个 SystemMessage无法单独分离。
#### 2.4 边界情况处理
| 情况 | 处理方式 |
|------|----------|
| System (包含 topology) > 预算 | 保留完整的 System Message无法分离 system 和 topology |
| Tools > 预算 | 警告日志,建议增加 context_limit 或减少工具数量 |
| 历史全被裁剪 | 保留最后1条用户消息 |
**重要提示**
- 当 system + topology 超出可用预算时,**两者都会被保留**
- 无法只丢弃 topology 而保留 system prompt因为已合并
- 建议在 system prompt 中精简 topology 信息或使用更短的 system prompt
### 2. 集成到 GNS3 Copilot
**修改文件**: `gns3server/agent/gns3_copilot/agent/gns3_copilot.py`
#### 改动内容
**导入新模块**:
```python
from gns3server.agent.gns3_copilot.agent.context_manager import (
prepare_context_messages,
)
```
**替换消息构建逻辑**:
```python
# 旧代码(手动构建)
full_messages = (
[SystemMessage(content=current_prompt)]
+ context_messages
+ state["messages"]
)
# 新代码(自动裁剪)
full_messages = prepare_context_messages(
state_messages=state["messages"],
system_prompt=current_prompt,
topology_context=topology_context,
model_name=llm_config.get("model", "default"),
llm_config=llm_config, # ← 包含 context_limit 和 context_strategy
)
```
### 3. Token 计数实现细节
系统使用 **tiktoken** 进行准确的 Token 计数:
#### 3.1 Token 计数器
**使用的编码**`cl100k_base` (GPT-4)
**支持的模型**
- ✅ OpenAI (GPT-4, GPT-3.5)
- ✅ Anthropic (Claude 系列)
- ✅ DeepSeek (deepseek-chat, deepseek-coder)
- ✅ 大多数基于 GPT-4 架构的模型
**准确率**
- 英文95%+
- 中文95%+
- 代码90-95%
#### 3.2 工具定义 Token 估算
```python
def estimate_tool_tokens(tools: list[Any]) -> int:
"""动态序列化工具 schema 并计算 token"""
for tool in tools:
tool_schema = {
"type": "function",
"function": {
"name": tool.name,
"description": tool.description,
"parameters": tool.args_schema.schema()
}
}
# 使用 tiktoken 计数
schema_str = json.dumps(tool_schema, ensure_ascii=False)
tool_tokens = len(encoding.encode(schema_str))
```
**典型消耗**
- 简单工具1-2 个参数500-800 tokens
- 中等工具3-5 个参数800-1200 tokens
- 复杂工具嵌套参数1200-1500 tokens
- 8 个工具:约 6000-10000 tokens
---
## 策略详细说明
### Conservative保守策略
**参数配置**
```json
{
"context_limit": 128,
"context_strategy": "conservative"
}
```
**实际效果**
- **使用比例**60% (context_limit × 0.6)
- **计算公式**`max_tokens = context_limit × 1000 × 0.6`
- **示例**:对于 128K 上下文限制
- 保留空间:`128 × 1000 × 0.6 = 76,800` tokens 用于输入
- 预留空间:`128,000 - 76,800 = 51,200` tokens 用于输出
**适用场景**
1. **长输出任务**
- 代码生成(可能生成数百行代码)
- 文章写作(需要完整文章内容)
- 详细报告生成(包含多个章节)
2. **复杂任务**
- 多步骤推理任务
- 需要深度分析的请求
- 综合性问题的解决方案
3. **不确定输出大小时**
- 不确定 LLM 会返回多长的内容
- 首次尝试某种类型的任务
- 需要额外安全边界的场景
**优缺点**
-**优点**:输出更不容易被截断,安全性高
-**缺点**:输入上下文较少,可能遗漏早期信息
**日志示例**
```
INFO: Context prepared: 20 msgs, ~72800 tokens / 128K limit (56.9%), strategy=conservative
INFO: Available for output: ~51200 tokens
```
---
### Balanced平衡策略推荐
**参数配置**
```json
{
"context_limit": 128,
"context_strategy": "balanced"
}
```
**实际效果**
- **使用比例**75% (context_limit × 0.75)
- **计算公式**`max_tokens = context_limit × 1000 × 0.75`
- **示例**:对于 128K 上下文限制
- 保留空间:`128 × 1000 × 0.75 = 96,000` tokens 用于输入
- 预留空间:`128,000 - 96,000 = 32,000` tokens 用于输出
**适用场景**
1. **一般对话**
- 日常聊天交互
- 问答式对话
- 技术支持和咨询
2. **大多数场景**
- 网络配置命令生成
- 故障排查建议
- 知识问答
3. **平衡输入和输出**
- 需要较多上下文,但输出也较长的场景
- 中等复杂度的任务
**优缺点**
-**优点**:在输入上下文和输出空间之间取得良好平衡
-**优点**:适用于大多数使用场景
- ⚠️ **注意**:对于特别长的输出可能被截断
**日志示例**
```
INFO: Context prepared: 30 msgs, ~85600 tokens / 128K limit (66.9%), strategy=balanced
INFO: Available for output: ~32000 tokens
```
---
### Aggressive激进策略
**参数配置**
```json
{
"context_limit": 128,
"context_strategy": "aggressive"
}
```
**实际效果**
- **使用比例**85% (context_limit × 0.85)
- **计算公式**`max_tokens = context_limit × 1000 × 0.85`
- **示例**:对于 128K 上下文限制
- 保留空间:`128 × 1000 × 0.85 = 108,800` tokens 用于输入
- 预留空间:`128,000 - 108,800 = 19,200` tokens 用于输出
**适用场景**
1. **简短输出任务**
- 是/否判断
- 简短回答确认
- 状态查询类请求
2. **分析类任务**
- 日志分析(输出分析结果,但不需要很长)
- 数据解读(输出简洁的结论)
- 配置检查(返回 OK 或简短说明)
3. **输出比较确定的场景**
- 明确知道输出会很短
- 只需要简单确认或状态
- 不需要长篇解释的任务
**优缺点**
-**优点**:最大化输入上下文,保留更多历史信息
-**缺点**:输出容易被截断,不适用于长输出任务
**日志示例**
```
INFO: Context prepared: 50 msgs, ~105200 tokens / 128K limit (82.2%), strategy=aggressive
INFO: Available for output: ~19200 tokens
```
---
## 策略对比总结
| 特性 | Conservative | Balanced | Aggressive |
|------|-------------|----------|------------|
| **输入比例** | 60% | 75% | 85% |
| **输出预留** | 40% | 25% | 15% |
| **上下文数量** | 最少 | 中等 | 最多 |
| **输出空间** | 最大 | 中等 | 最小 |
| **适用性** | 长输出 | 一般使用 | 短输出 |
| **风险** | 上下文不足 | 平衡 | 输出截断 |
**选择建议流程**
```
1. 任务类型是什么?
├─ 代码生成/长文档 → Conservative
├─ 日常对话/一般任务 → Balanced (推荐)
└─ 简短确认/分析 → Aggressive
2. 输出长度预估?
├─ 不确定/可能很长 → Conservative
├─ 中等长度 → Balanced
└─ 很短/简洁 → Aggressive
3. 上下文重要性?
├─ 早期历史不太重要 → Aggressive (更多上下文)
├─ 需要平衡 → Balanced
└─ 重点是输出完整性 → Conservative (更多输出空间)
```
## 工作流程
```
用户发送消息
获取消息历史 state["messages"]
构建系统提示 + 拓扑信息
调用 prepare_context_messages()
├─ 估算 token 数量
├─ 获取模型上下文限制
├─ 判断是否需要裁剪
└─ 如果需要 → 调用 trim_messages()
调用 LLM带裁剪后的消息
返回响应
```
## 日志输出示例
### 正常情况(使用 tiktoken包含工具定义
```
INFO: Using tiktoken (cl100k_base) for accurate token counting
INFO: Tool definitions estimated at ~8500 total tokens (8 tools)
INFO: Using database config context limit: 128000 tokens for model 'gpt-4o'
INFO: Context prepared: 15 msgs, ~28432 tokens (messages) + 8500 tokens (tools) = 36932 total / 128K limit (28.9%), strategy=balanced
INFO: LLM call completed: tool_calls=2
```
### 发生裁剪时
```
INFO: Using tiktoken (cl100k_base) for accurate token counting
INFO: Tool definitions estimated at ~8500 total tokens (8 tools)
INFO: Using database config context limit: 128000 tokens for model 'gpt-4o'
INFO: Trimming messages: 95000 → 82000 tokens (model: gpt-4o)
INFO: Trimmed 50 → 25 messages
INFO: Context prepared: 27 msgs, ~82000 tokens (messages) + 8500 tokens (tools) = 90500 total / 128K limit (70.7%), strategy=balanced
```
### 配置错误时
```
ERROR: context_limit is required but not provided for model 'gpt-4o'.
Please configure context_limit in your LLM model configuration.
Refer to the model provider's documentation for the current context window size.
```
### 日志格式说明
新的日志格式(包含工具定义和 topology 分解):
```
Context prepared: system={总tokens} (base={base_tokens} + topology={topology_tokens}) + history={history_tokens} + tools={tool_tokens} = {总计} total / {限制}K limit ({使用百分比}%), strategy={策略}
```
字段说明:
- **system**System Message 的总 token 数(包含 system prompt + topology
- **base**:基础 system prompt 的 token 数(不含 topology
- **topology**Topology info 的 token 数
- **history**对话历史HumanMessage + AIMessage + ToolMessage的 token 数
- **tools**工具定义schema的 token 数
- **总计**:所有部分的 token 总和
- **限制**模型的上下文窗口大小K tokens
- **使用百分比**:总计 tokens / 限制 × 100%
- **策略**使用的裁剪策略conservative/balanced/aggressive
**示例输出**
```
Context prepared: system=6124 (base=2804 + topology=3320) + history=11800 + tools=1862 = 19786 total / 128K limit (15.5%), strategy=balanced
```
这表示:
- System Message 总共 6124 tokens
- 其中基础 system prompt 2804 tokens
- 其中 topology info 3320 tokens
- 对话历史 11800 tokens
- 工具定义 1862 tokens
- 总计 19786 tokens使用 128K 限制的 15.5%
---
## Token 计数准确性
### 为什么估算值和实际值可能不同?
#### 1. **工具定义的影响**
每次 LLM 调用时,工具定义会被转换为 JSON 并发送给 LLM
```json
{
"type": "function",
"function": {
"name": "ExecuteMultipleDeviceCommands",
"description": "在多个网络设备上执行命令...",
"parameters": {
"type": "object",
"properties": {
"commands": {...}
}
}
}
}
```
**典型消耗**
- 简单工具500-800 tokens
- 复杂工具1000-1500 tokens
- 8 个工具:约 6000-12000 tokens
**系统处理**
- ✅ 现在会自动估算工具定义的 tokens
- ✅ 日志中会显示工具 tokens
- ✅ 裁剪决策会考虑工具定义
#### 2. **中文内容的 Tokenization**
不同语言对 token 的使用效率不同:
| 语言 | 平均字符/token | 示例 |
|------|---------------|------|
| 英文 | 3-4 字符 | "Hello world" ≈ 2-3 tokens |
| 中文 | 1-1.5 字符 | "你好世界" ≈ 3-4 tokens |
| 代码 | 2-3 字符 | `print("Hello")` ≈ 4-5 tokens |
**使用 tiktoken 后**
- 对中文的准确率从 30-40% 提升到 95%+
- 对英文的准确率保持在 95%+
- 对代码的准确率约 90-95%
#### 3. **System Prompt 和 Topology Context**
- **System Prompt**: 固定内容,约 1000-2000 tokens
- **Topology Context**: 动态内容,取决于拓扑大小
- 小型拓扑(< 10 节点 500-1000 tokens
- 中型拓扑10-50 节点 2000-5000 tokens
- 大型拓扑> 50 节点):约 5000-10000+ tokens
这些都会被准确计数并计入总限制。
---
## Token 使用对比
### 改进前LangChain 估算)
```
INFO: Context prepared: 29 msgs, ~11523 tokens / 128K limit (9.0%), strategy=conservative
实际发送: 39700 tokens
差距: 28177 tokens (2.4x 低估)
```
**问题**
1. ❌ 工具定义没有被计入(约 8000-12000 tokens
2. ❌ 中文内容被低估(约 2.4x 误差)
3. ❌ 总体低估约 2-3 倍
### 改进后tiktoken + 工具定义)
```
INFO: Using tiktoken (cl100k_base) for accurate token counting
INFO: Tool definitions estimated at ~8500 total tokens (8 tools)
INFO: Context prepared: 29 msgs, ~27500 tokens (messages) + 8500 tokens (tools) = 36000 total / 128K limit (28.1%), strategy=conservative
实际发送: 39700 tokens
差距: 3700 tokens (1.1x 误差)
```
**改进**
1. ✅ 工具定义被准确估算
2. ✅ 中文内容准确率提升到 95%+
3. ✅ 总体误差降低到 10% 以内
---
## 错误处理
### tiktoken 未安装
如果 tiktoken 未安装,系统将在首次尝试计数 tokens 时抛出错误:
```python
ImportError: tiktoken is required for accurate token counting.
Please install it with: pip install tiktoken>=0.8.0
```
**解决方法**
```bash
pip install tiktoken>=0.8.0
```
### Token 计数失败
```python
try:
tokens = count_messages_tokens(messages)
except ImportError as e:
logger.error("tiktoken not available: %s", e)
raise # Re-raise to fail fast
except Exception as e:
logger.error("Failed to count tokens: %s", e)
raise
```
### 裁剪失败
```python
try:
trimmed = trim_messages(...)
except Exception as e:
logger.error("Failed to trim: %s", e)
# 回退到简单的切片操作
return system_msgs + other_msgs[-N:]
```
## 参考资源
- [tiktoken (OpenAI Tokenizer)](https://github.com/openai/tiktoken)
- [LangChain Messages Utils](https://python.langchain.com/docs/messages/)
- [LangGraph Memory Management](https://langchain-ai.github.io/langgraph/concepts/agentic_concepts/#memory)
- [OpenAI Models Context Limits](https://platform.openai.com/docs/models)
- [Anthropic Models Context Limits](https://docs.anthropic.com/claude/docs/models-overview)
## 相关源文件
- `gns3server/agent/gns3_copilot/agent/context_manager.py` - 上下文管理核心逻辑
- `gns3server/agent/gns3_copilot/agent/gns3_copilot.py` - LLM 调用节点
- `gns3server/agent/gns3_copilot/agent_service.py` - Agent 服务接口
## 总结
通过使用 LangChain/LangGraph 的内置功能和自定义优化,我们实现了:
1. ✅ 智能裁剪消息历史,避免超限
2.**必须手动配置 context_limit**,确保使用正确的上下文窗口大小
3. ✅ 可配置的裁剪策略(保守/平衡/激进)
4. ✅ 始终保留系统消息(包含 system prompt 和 topology
5. ✅ 详细的日志输出,便于调试
6. ✅ 优雅的错误处理和明确的错误提示
7. ✅ 提供参考工具,帮助查找常见模型的上下文限制
8.**使用 tiktoken 进行准确的 token 计数**(准确率 95%+
9.**自动估算工具定义的 token 消耗**
10.**支持中文、英文、代码等多种内容类型**
11.**使用模板变量动态注入 topology info** (新增)
12.**日志中详细显示 system/topology/history 分解** (新增)
### 关键优势
- **准确性**
- 用户从官方文档获取最新的上下文限制,避免使用过时数据
- tiktoken 提供准确的 token 计数,误差 < 10%
- 工具定义 token 被正确计入估算
- **灵活性**
- 每个配置独立设置支持不同用户使用不同限制
- 自动适配不同语言和内容类型
- 模板变量注入方式易于维护和调整
- **明确性**
- 缺少配置时立即报错避免静默失败
- 详细日志显示所有 token 消耗system base + topology + history + tools
- 清楚显示各部分的 token 分解
- **可维护性**
- 无需维护内置默认值减少代码维护负担
- 模板变量注入方式system prompt topology 管理更清晰
- 模块化设计易于扩展和调试
- **可观测性**
- 详细日志显示上下文使用情况和裁剪决策
- 区分 system base tokenstopology tokenshistory tokens 和工具 tokens
- 显示使用百分比和策略选择
这个实现确保了即使在进行长对话时系统也不会因为上下文溢出而失败