【Bug已解决】openclaw: "context window exceeded" / Token limit reached — OpenClaw 上下文窗口超限解决方案

1. 问题描述

在使用 OpenClaw 处理大型代码库或长对话时,系统报出上下文窗口超限或 Token 数量超出限制错误:

# 上下文窗口超限
$ openclaw "分析整个项目代码"
Error: context window exceeded
Total tokens: 128547
Maximum: 128000
Exceeded by: 547 tokens

# Token 限制达到
$ openclaw "继续之前的对话"
Error: token limit reached
Input tokens: 95000
Output tokens: 33000
Total: 128000 (at limit)
Cannot process further input

# 上下文被截断
$ openclaw "分析大型文件"
Warning: Context truncated
File: src/large_module.ts (45000 tokens)
Only first 30000 tokens loaded
33000 tokens omitted

# 对话历史过长
$ openclaw "继续讨论"
Error: Conversation too long
Previous messages: 127 turns
Total tokens: 150000
Exceeds model limit: 128000

这个问题在以下场景中特别常见:

  • 分析大型代码文件(>5000行)
  • 长时间对话累积大量上下文
  • 同时加载多个文件到上下文
  • 代码库搜索结果过多
  • API 响应包含大量数据
  • 递归调用导致上下文叠加

2. 原因分析

用户输入 + 历史上下文 + 系统提示
    ↓
计算总Token数 ←──── Tokenizer分词
    ↓
总Token > 限制 ←──── 128K/200K限制
    ↓
拒绝请求/截断内容 → 上下文超限
原因分类 具体表现 占比
大文件加载 单文件>30K Token 约 30%
对话过长 历史消息累积 约 25%
多文件加载 多文件叠加 约 20%
搜索结果多 大量匹配 约 10%
API响应大 返回数据多 约 8%
系统提示长 模板过大 约 7%

深层原理

大语言模型的上下文窗口是其能处理的最大 Token 数量。Token 是文本的最小语义单元,英文中约 1 个单词 ≈ 1.3 个 Token,中文中约 1 个字符 ≈ 1.5-2 个 Token,代码中因符号和缩进而可能更多。当所有输入(系统提示 + 对话历史 + 用户输入 + 加载的文件)的总 Token 数超过模型的上下文窗口限制时,模型无法处理。不同模型有不同限制:GPT-4 为 128K,Claude 为 200K。超出限制时,系统要么拒绝请求,要么自动截断内容(可能丢失关键信息),导致分析不完整或回答不准确。

3. 解决方案

方案一:智能上下文管理(最推荐)

# 配置 OpenClaw 的上下文管理
python3 -c "
import json
with open('.openclaw/config.json', 'r') as f:
    config = json.load(f)

config['context'] = {
    'maxTokens': 120000,             # 保守限制(低于128K)
    'reserveOutput': 8000,           # 预留输出Token
    'maxInputTokens': 112000,        # 最大输入=120K-8K
    'strategy': 'sliding',           # sliding | summarization | selective
    'autoManage': True,              # 自动管理
    'truncateThreshold': 0.9,        # 90%时开始截断
    'warnThreshold': 0.8,            # 80%时警告
    'fileChunkSize': 10000,          # 文件分块大小
    'maxFileTokens': 30000,          # 单文件最大Token
    'maxFilesInContext': 5,          # 上下文最大文件数
    'historyRetention': 20,          # 保留最近20轮对话
    'summarizeThreshold': 50,        # 50轮后摘要
    'compressionLevel': 'medium'     # low | medium | high
}

with open('.openclaw/config.json', 'w') as f:
    json.dump(config, f, indent=2)
print('上下文管理: 滑动窗口+自动截断+文件分块+对话摘要')
"

# 查看当前上下文使用情况
openclaw --context-stats
# 输出:
# System prompt: 2000 tokens
# Conversation history: 45000 tokens (35 turns)
# Loaded files: 30000 tokens (3 files)
# Available: 51000 tokens
# Total: 77000 / 120000 (64%)

方案二:对话历史压缩和摘要

# 创建对话历史管理器
import json
import os
from datetime import datetime

class ConversationManager:
    """对话历史管理器"""
    
    def __init__(self, max_history_tokens=50000, max_turns=30):
        self.max_history_tokens = max_history_tokens
        self.max_turns = max_turns
        self.history = []
        self.summaries = []
    
    def add_message(self, role, content, tokens=None):
        """添加消息"""
        if tokens is None:
            # 简化的 Token 估算
            tokens = self.estimate_tokens(content)
        
        message = {
            'role': role,
            'content': content,
            'tokens': tokens,
            'timestamp': datetime.now().isoformat()
        }
        
        self.history.append(message)
        
        # 检查是否需要压缩
        total_tokens = sum(m['tokens'] for m in self.history)
        if total_tokens > self.max_history_tokens or len(self.history) > self.max_turns:
            self.compress_history()
    
    def compress_history(self):
        """压缩历史对话"""
        # 保留最近的 N 条消息
        keep_recent = min(10, len(self.history) // 3)
        recent = self.history[-keep_recent:]
        to_summarize = self.history[:-keep_recent]
        
        # 生成摘要
        if to_summarize:
            summary = self.generate_summary(to_summarize)
            self.summaries.append({
                'summary': summary,
                'original_count': len(to_summarize),
                'original_tokens': sum(m['tokens'] for m in to_summarize),
                'summary_tokens': self.estimate_tokens(summary),
                'timestamp': datetime.now().isoformat()
            })
        
        self.history = recent
        print(f"  📦 压缩: {len(to_summarize)} 条消息 -> 摘要")
    
    def generate_summary(self, messages):
        """生成对话摘要"""
        # 构建摘要提示
        summary_parts = []
        for msg in messages:
            role = msg['role']
            content = msg['content'][:500]  # 截取前500字符
            summary_parts.append(f"[{role}]: {content}")
        
        # 简化的摘要(实际应调用LLM)
        full_text = '\n'.join(summary_parts)
        
        # 关键信息提取
        key_points = []
        if 'error' in full_text.lower():
            key_points.append('讨论了错误处理')
        if 'config' in full_text.lower():
            key_points.append('涉及配置修改')
        if 'test' in full_text.lower():
            key_points.append('包含测试相关内容')
        
        summary = f"之前{len(messages)}轮对话摘要: {'; '.join(key_points) if key_points else '讨论了项目相关问题'}"
        
        return summary
    
    def estimate_tokens(self, text):
        """估算Token数"""
        # 简化估算: 英文~4字符/Token, 中文~2字符/Token
        chinese_chars = sum(1 for c in text if '\u4e00' <= c <= '\u9fff')
        other_chars = len(text) - chinese_chars
        return int(chinese_chars * 1.5 + other_chars / 4)
    
    def get_context(self):
        """获取当前上下文"""
        # 合并摘要 + 最近历史
        context_parts = []
        
        # 添加摘要
        for s in self.summaries:
            context_parts.append(s['summary'])
        
        # 添加最近历史
        for msg in self.history:
            context_parts.append(f"[{msg['role']}]: {msg['content']}")
        
        total_tokens = sum(s['summary_tokens'] for s in self.summaries)
        total_tokens += sum(m['tokens'] for m in self.history)
        
        return {
            'context': '\n'.join(context_parts),
            'total_tokens': total_tokens,
            'summaries_count': len(self.summaries),
            'history_count': len(self.history)
        }
    
    def save(self, filepath):
        """保存对话状态"""
        data = {
            'history': self.history,
            'summaries': self.summaries
        }
        with open(filepath, 'w') as f:
            json.dump(data, f, indent=2, ensure_ascii=False)
    
    def load(self, filepath):
        """加载对话状态"""
        if os.path.exists(filepath):
            with open(filepath, 'r') as f:
                data = json.load(f)
            self.history = data.get('history', [])
            self.summaries = data.get('summaries', [])

# 使用示例
if __name__ == "__main__":
    manager = ConversationManager(max_history_tokens=50000, max_turns=20)
    
    # 模拟长对话
    for i in range(25):
        manager.add_message('user', f'这是第{i+1}轮对话,包含一些问题和代码示例...')
        manager.add_message('assistant', f'这是第{i+1}轮回复,包含解决方案和说明...')
    
    context = manager.get_context()
    print(f"总Token: {context['total_tokens']}")
    print(f"摘要数: {context['summaries_count']}")
    print(f"历史数: {context['history_count']}")

方案三:文件智能分块加载

# 创建文件分块加载器
import os

class FileChunkLoader:
    """智能文件分块加载器"""
    
    def __init__(self, max_tokens=30000, chunk_size=5000):
        self.max_tokens = max_tokens
        self.chunk_size = chunk_size
    
    def estimate_tokens(self, text):
        """估算Token数"""
        chinese = sum(1 for c in text if '\u4e00' <= c <= '\u9fff')
        other = len(text) - chinese
        return int(chinese * 1.5 + other / 4)
    
    def load_file_chunked(self, filepath, chunk_index=0):
        """分块加载文件"""
        with open(filepath, 'r', encoding='utf-8', errors='replace') as f:
            content = f.read()
        
        total_tokens = self.estimate_tokens(content)
        
        if total_tokens <= self.max_tokens:
            return {
                'content': content,
                'tokens': total_tokens,
                'chunk_index': 0,
                'total_chunks': 1,
                'complete': True
            }
        
        # 分块
        lines = content.split('\n')
        chunks = []
        current_chunk = []
        current_tokens = 0
        
        for line in lines:
            line_tokens = self.estimate_tokens(line)
            
            if current_tokens + line_tokens > self.chunk_size:
                if current_chunk:
                    chunks.append('\n'.join(current_chunk))
                current_chunk = [line]
                current_tokens = line_tokens
            else:
                current_chunk.append(line)
                current_tokens += line_tokens
        
        if current_chunk:
            chunks.append('\n'.join(current_chunk))
        
        # 返回指定块
        if chunk_index < len(chunks):
            chunk_content = chunks[chunk_index]
            return {
                'content': chunk_content,
                'tokens': self.estimate_tokens(chunk_content),
                'chunk_index': chunk_index,
                'total_chunks': len(chunks),
                'complete': chunk_index == len(chunks) - 1,
                'file_path': filepath,
                'start_line': sum(len(c.split('\n')) for c in chunks[:chunk_index]) + 1
            }
        
        return None
    
    def load_file_selective(self, filepath, keywords=None, context_lines=10):
        """选择性加载文件(基于关键词)"""
        with open(filepath, 'r', encoding='utf-8', errors='replace') as f:
            lines = f.readlines()
        
        if not keywords:
            # 如果没有关键词,加载前 max_tokens 部分
            content = ''
            for line in lines:
                if self.estimate_tokens(content + line) > self.max_tokens:
                    break
                content += line
            return {
                'content': content,
                'tokens': self.estimate_tokens(content),
                'total_lines': len(lines),
                'loaded_lines': content.count('\n') + 1,
                'complete': content.count('\n') + 1 >= len(lines)
            }
        
        # 基于关键词选择相关部分
        relevant_sections = []
        for i, line in enumerate(lines):
            if any(kw.lower() in line.lower() for kw in keywords):
                start = max(0, i - context_lines)
                end = min(len(lines), i + context_lines + 1)
                section = ''.join(lines[start:end])
                relevant_sections.append({
                    'content': section,
                    'start_line': start + 1,
                    'end_line': end,
                    'keyword_lines': [i + 1]
                })
        
        # 合并重叠的部分
        merged = self._merge_sections(relevant_sections)
        
        total_tokens = sum(self.estimate_tokens(s['content']) for s in merged)
        
        # 如果仍然太大,截断
        if total_tokens > self.max_tokens:
            result_content = ''
            for section in merged:
                if self.estimate_tokens(result_content + section['content']) > self.max_tokens:
                    break
                result_content += section['content']
            total_tokens = self.estimate_tokens(result_content)
        else:
            result_content = '\n---\n'.join(s['content'] for s in merged)
        
        return {
            'content': result_content,
            'tokens': total_tokens,
            'sections': len(merged),
            'total_lines': len(lines),
            'keywords': keywords
        }
    
    def _merge_sections(self, sections):
        """合并重叠的段落"""
        if not sections:
            return []
        
        merged = [sections[0]]
        for section in sections[1:]:
            if section['start_line'] <= merged[-1]['end_line'] + 1:
                # 重叠或相邻,合并
                merged[-1]['end_line'] = section['end_line']
                # 需要重新获取合并后的内容
            else:
                merged.append(section)
        
        return merged

# 使用示例
if __name__ == "__main__":
    loader = FileChunkLoader(max_tokens=30000, chunk_size=5000)
    
    # 测试分块加载
    import sys
    filepath = sys.argv[1] if len(sys.argv) > 1 else __file__
    
    result = loader.load_file_chunked(filepath)
    print(f"文件: {filepath}")
    print(f"总块数: {result['total_chunks']}")
    print(f"当前块: {result['chunk_index']}")
    print(f"Token: {result['tokens']}")
    print(f"完整: {result['complete']}")
    
    # 选择性加载
    result = loader.load_file_selective(filepath, keywords=['class', 'def'])
    print(f"\n选择性加载:")
    print(f"Token: {result['tokens']}")
    print(f"段落数: {result['sections']}")

方案四:配置上下文优先级

# 配置上下文优先级策略
python3 -c "
import json
with open('.openclaw/config.json', 'r') as f:
    config = json.load(f)

config['context']['priority'] = {
    'systemPrompt': 1,           # 最高优先级
    'currentUserInput': 2,       # 当前用户输入
    'recentHistory': 3,          # 最近对话(5轮内)
    'loadedFiles': 4,            # 加载的文件
    'olderHistory': 5,           # 较早的对话
    'searchResults': 6,          # 搜索结果
    'summaries': 7               # 摘要(最低)
}

config['context']['eviction'] = {
    'strategy': 'priority',      # 按优先级淘汰
    'evictWhen': 0.85,           # 85%时开始淘汰
    'evictWhat': 'olderHistory', # 先淘汰旧历史
    'compressBefore': True,      # 淘汰前先压缩
    'notifyUser': True           # 通知用户内容被移除
}

with open('.openclaw/config.json', 'w') as f:
    json.dump(config, f, indent=2)
print('上下文优先级: 系统提示>用户输入>最近历史>文件>旧历史>搜索>摘要')
"

# 手动管理上下文
openclaw --context-clear          # 清除所有上下文
openclaw --context-clear-history  # 仅清除对话历史
openclaw --context-clear-files    # 仅清除加载的文件
openclaw --context-keep 5         # 保留最近5轮
openclaw --context-summarize      # 手动触发摘要

方案五:使用检索增强生成(RAG)

# 创建简易 RAG 系统减少上下文使用
import os
import json
import hashlib

class SimpleRAG:
    """简易检索增强生成系统"""
    
    def __init__(self, index_dir='.openclaw/rag_index'):
        self.index_dir = index_dir
        os.makedirs(index_dir, exist_ok=True)
        self.index = []
        self.load_index()
    
    def index_file(self, filepath):
        """索引文件内容"""
        with open(filepath, 'r', encoding='utf-8', errors='replace') as f:
            content = f.read()
        
        # 按段落分割
        paragraphs = content.split('\n\n')
        
        for para in paragraphs:
            if len(para.strip()) < 50:
                continue
            
            # 创建文档块
            doc = {
                'file': filepath,
                'content': para,
                'hash': hashlib.md5(para.encode()).hexdigest()[:8],
                'keywords': self.extract_keywords(para)
            }
            self.index.append(doc)
        
        self.save_index()
        print(f"已索引 {filepath}: {len(paragraphs)} 段落")
    
    def search(self, query, top_k=3):
        """搜索相关文档"""
        query_keywords = self.extract_keywords(query)
        
        scores = []
        for doc in self.index:
            score = self.calculate_relevance(query_keywords, doc['keywords'])
            if score > 0:
                scores.append((score, doc))
        
        # 排序并返回 top_k
        scores.sort(reverse=True, key=lambda x: x[0])
        
        results = []
        for score, doc in scores[:top_k]:
            results.append({
                'file': doc['file'],
                'content': doc['content'],
                'score': score,
                'keywords': doc['keywords']
            })
        
        return results
    
    def extract_keywords(self, text):
        """提取关键词(简化版)"""
        # 移除常见停用词
        stopwords = {'the', 'a', 'an', 'is', 'are', 'was', 'were',
                    'and', 'or', 'but', 'in', 'on', 'at', 'to',
                    '的', '了', '是', '在', '有', '和', '与'}
        
        words = text.lower().split()
        keywords = [w for w in words if w not in stopwords and len(w) > 2]
        return keywords
    
    def calculate_relevance(self, query_kw, doc_kw):
        """计算相关性"""
        if not query_kw or not doc_kw:
            return 0
        
        common = set(query_kw) & set(doc_kw)
        return len(common) / max(len(query_kw), 1)
    
    def save_index(self):
        """保存索引"""
        with open(os.path.join(self.index_dir, 'index.json'), 'w') as f:
            json.dump(self.index, f, ensure_ascii=False)
    
    def load_index(self):
        """加载索引"""
        index_file = os.path.join(self.index_dir, 'index.json')
        if os.path.exists(index_file):
            with open(index_file, 'r') as f:
                self.index = json.load(f)

# 使用示例
if __name__ == "__main__":
    rag = SimpleRAG()
    
    # 索引项目文件
    import glob
    for f in glob.glob('src/**/*.py', recursive=True)[:10]:
        rag.index_file(f)
    
    # 搜索
    results = rag.search("如何处理错误")
    for r in results:
        print(f"  [{r['score']:.2f}] {r['file']}")
        print(f"    {r['content'][:100]}...")

方案六:Token 计数和监控

# 配置 Token 监控
python3 -c "
import json
with open('.openclaw/config.json', 'r') as f:
    config = json.load(f)

config['tokenMonitoring'] = {
    'enabled': True,
    'countOnEveryRequest': True,
    'logToFile': True,
    'logFile': '.openclaw/logs/token_usage.json',
    'warnAt': 0.8,               # 80%警告
    'criticalAt': 0.95,          # 95%严重
    'trackByType': True,         # 按类型跟踪
    'trackByFile': True,         # 按文件跟踪
    'dailyReport': True,         # 每日报告
    'reportFile': '.openclaw/logs/token_daily.json'
}

with open('.openclaw/config.json', 'w') as f:
    json.dump(config, f, indent=2)
print('Token 监控已启用: 80%警告, 95%严重, 每日报告')
"

# 查看 Token 使用统计
openclaw --token-stats
# 输出:
# 今日使用: 450000 tokens
#   输入: 380000 (84%)
#   输出: 70000 (16%)
# 平均每请求: 8500 tokens
# 最大单次: 120000 tokens
# 峰值时间: 14:00-15:00

# 查看详细报告
cat .openclaw/logs/token_usage.json | python3 -m json.tool | head -30

# 创建 Token 预算
python3 -c "
import json
with open('.openclaw/config.json', 'r') as f:
    config = json.load(f)
config['tokenBudget'] = {
    'daily': 2000000,             # 每日200万Token
    'perRequest': 128000,         # 每请求128K
    'perFile': 30000,             # 每文件30K
    'perConversation': 500000,    # 每对话50万
    'actionOnExceed': 'warn'      # warn | block | summarize
}
with open('.openclaw/config.json', 'w') as f:
    json.dump(config, f, indent=2)
print('Token 预算: 每日200万, 每请求128K, 每文件30K')
"

4. 各方案对比总结

方案 适用场景 推荐指数
方案一:上下文管理 通用配置 ⭐⭐⭐⭐⭐
方案二:对话压缩 长对话 ⭐⭐⭐⭐⭐
方案三:文件分块 大文件 ⭐⭐⭐⭐⭐
方案四:优先级策略 复杂场景 ⭐⭐⭐⭐
方案五:RAG 大代码库 ⭐⭐⭐⭐
方案六:Token监控 运维 ⭐⭐⭐⭐

5. 常见问题 FAQ

5.1 Windows 上 Token 计数差异

Windows 换行符可能导致 Token 计数不同:

# Windows 使用 \r\n,可能增加 Token
# 配置统一换行符
python3 -c "
import json
with open('.openclaw/config.json', 'r') as f:
    config = json.load(f)
config['context']['normalizeLineEndings'] = True  # 统一为 \n
config['context']['stripBOM'] = True
with open('.openclaw/config.json', 'w') as f:
    json.dump(config, f, indent=2)
print('换行符标准化已启用')
"

5.2 Docker 中上下文丢失

容器重启后对话历史丢失:

# 持久化对话历史
docker run -v openclaw_data:/app/.openclaw openclaw "任务"

# 配置对话持久化
python3 -c "
import json
with open('.openclaw/config.json', 'r') as f:
    config = json.load(f)
config['context']['persistence'] = {
    'enabled': True,
    'saveDir': '.openclaw/conversations/',
    'autoSave': True,
    'saveInterval': 300000,        # 5分钟自动保存
    'maxSavedConversations': 50,   # 保留50个对话
    'compressSaved': True          # 压缩保存
}
with open('.openclaw/config.json', 'w') as f:
    json.dump(config, f, indent=2)
print('对话持久化: 自动保存+压缩+保留50个')
"

5.3 CI/CD 中上下文管理

CI 中每次都是新对话:

# CI 中使用一次性上下文
env:
  OPENCLAW_CONTEXT_MODE: single  # 单次模式
steps:
  - name: Run with minimal context
    run: |
      openclaw --no-history --max-tokens 50000 "一次性任务"
  
  - name: Save context for reuse
    run: |
      openclaw --context-export .openclaw/context_snapshot.json
  
  - name: Restore context
    run: |
      openclaw --context-import .openclaw/context_snapshot.json

5.4 多文件加载超出限制

同时加载多个文件导致超限:

# 配置多文件加载策略
python3 -c "
import json
with open('.openclaw/config.json', 'r') as f:
    config = json.load(f)
config['context']['multiFile'] = {
    'strategy': 'priority',        # priority | round-robin | selective
    'maxFiles': 5,                 # 最大文件数
    'maxTotalTokens': 50000,       # 文件总Token上限
    'perFileLimit': 15000,         # 每文件Token上限
    'priorityBy': 'relevance',     # relevance | size | recency
    'lazyLoad': True,              # 懒加载(需要时才加载)
    'autoUnload': True             # 自动卸载不相关的文件
}
with open('.openclaw/config.json', 'w') as f:
    json.dump(config, f, indent=2)
print('多文件加载: 最多5文件, 总50K Token, 懒加载')
"

# 使用选择性加载
openclaw --load src/index.ts --load src/utils.ts "分析这两个文件"
# 如果超出限制,自动截断

5.5 代码 Token 消耗过大

代码文件 Token 密度高:

# 代码精简工具
class CodeCompactor:
    """代码精简工具,减少Token"""
    
    @staticmethod
    def compact_code(code):
        """精简代码"""
        lines = code.split('\n')
        compacted = []
        
        for line in lines:
            # 移除空行
            if not line.strip():
                continue
            
            # 移除注释(保留重要注释)
            stripped = line.strip()
            if stripped.startswith('//') and not stripped.startswith('//!'):
                continue
            if stripped.startswith('#') and not any(stripped.startswith(f'#{m}') for m in ['!', 'TODO', 'FIXME']):
                continue
            
            # 移除多余空格
            line = ' '.join(line.split())
            
            # 简化常见模式
            line = line.replace('function ', 'fn ')
            line = line.replace('return ', 'ret ')
            line = line.replace('const ', 'c ')
            line = line.replace('let ', 'l ')
            
            compacted.append(line)
        
        return '\n'.join(compacted)
    
    @staticmethod
    def extract_signatures(code):
        """只提取函数签名和类定义"""
        import re
        lines = code.split('\n')
        signatures = []
        
        patterns = [
            r'^\s*(export\s+)?(async\s+)?function\s+\w+',  # 函数
            r'^\s*(export\s+)?class\s+\w+',                 # 类
            r'^\s*(export\s+)?(const|let|var)\s+\w+\s*=',  # 变量
            r'^\s*(export\s+)?interface\s+\w+',              # 接口
            r'^\s*(export\s+)?type\s+\w+',                   # 类型
        ]
        
        for line in lines:
            if any(re.match(p, line) for p in patterns):
                signatures.append(line)
        
        return '\n'.join(signatures)

# 使用
code = open('src/index.ts').read()
signatures = CodeCompactor.extract_signatures(code)
print(f"完整代码: {len(code)} 字符")
print(f"签名摘要: {len(signatures)} 字符 (节省 {100 - len(signatures)*100//len(code)}%)")

5.6 不同模型 Token 限制不同

切换模型可能导致限制变化:

# 配置模型特定的 Token 限制
python3 -c "
import json
with open('.openclaw/config.json', 'r') as f:
    config = json.load(f)
config['models'] = {
    'gpt-4': {'contextWindow': 128000, 'maxOutput': 4096},
    'gpt-4-turbo': {'contextWindow': 128000, 'maxOutput': 4096},
    'claude-3': {'contextWindow': 200000, 'maxOutput': 4096},
    'claude-3.5': {'contextWindow': 200000, 'maxOutput': 8192}
}
config['context']['autoDetectModel'] = True
config['context']['adjustForModel'] = True
with open('.openclaw/config.json', 'w') as f:
    json.dump(config, f, indent=2)
print('模型Token限制已配置: 自动检测+调整')
"

# 切换模型时自动调整
openclaw --model claude-3 "任务"  # 200K 限制
openclaw --model gpt-4 "任务"     # 128K 限制

5.7 上下文丢失关键信息

压缩后丢失重要上下文:

# 配置摘要保留策略
python3 -c "
import json
with open('.openclaw/config.json', 'r') as f:
    config = json.load(f)
config['context']['summarization'] = {
    'preserveCodeBlocks': True,       # 保留代码块
    'preserveErrorMessages': True,    # 保留错误信息
    'preserveFilePaths': True,        # 保留文件路径
    'preserveDecisions': True,        # 保留决策记录
    'preserveUserPreferences': True,  # 保留用户偏好
    'summarizeStyle': 'detailed',     # detailed | brief | bullet
    'maxSummaryTokens': 2000          # 摘要最大Token
}
with open('.openclaw/config.json', 'w') as f:
    json.dump(config, f, indent=2)
print('摘要保留: 代码块+错误+路径+决策+偏好')
"

# 手动标记重要信息
openclaw --pin "这段代码很重要"  # 标记为重要,不会被压缩
openclaw --pinned-list           # 查看已标记的内容

5.8 API 请求 Token 费用过高

Token 消耗直接关联费用:

# 配置费用控制
python3 -c "
import json
with open('.openclaw/config.json', 'r') as f:
    config = json.load(f)
config['costControl'] = {
    'dailyBudget': 10.0,             # 每日10美元
    'perRequestBudget': 1.0,         # 每请求1美元
    'alertThreshold': 0.8,           # 80%告警
    'blockThreshold': 0.95,          # 95%阻止
    'costPerMToken': {
        'input': 0.01,               # 输入$0.01/1K Token
        'output': 0.03               # 输出$0.03/1K Token
    },
    'optimizeMode': 'balanced'       # cheap | balanced | quality
}
with open('.openclaw/config.json', 'w') as f:
    json.dump(config, f, indent=2)
print('费用控制: 每日$10, 每请求$1, 80%告警')
"

# 查看费用统计
openclaw --cost-stats
# 输出:
# 今日费用: $3.50
#   输入: $2.80 (280K tokens)
#   输出: $0.70 (23K tokens)
# 预算使用: 35%
# 预计月费: $105

排查清单速查表

□ 1. 检查上下文使用: openclaw --context-stats
□ 2. 配置 maxTokens=120000(保守值)
□ 3. 设置对话压缩: summarizeThreshold=50
□ 4. 文件分块加载: chunkSize=5000
□ 5. 配置优先级淘汰策略
□ 6. 使用 RAG 检索替代全量加载
□ 7. 启用 Token 监控和预算
□ 8. 代码精简: 只加载签名
□ 9. 对话持久化防止丢失
□ 10. 费用控制: 设置每日预算

6. 总结

  1. 最常见原因:大文件加载(30%)和对话历史累积(25%)导致总 Token 超限
  2. 智能管理:配置滑动窗口策略,80% 警告,90% 开始淘汰低优先级内容
  3. 对话压缩:达到阈值后自动将旧对话摘要化,保留代码块、错误信息和决策记录
  4. 文件分块:大文件按 5000 Token 分块加载,支持基于关键词的选择性加载
  5. 最佳实践建议:使用 RAG 系统索引代码库按需检索,启用 Token 监控和每日预算控制费用,代码分析时只加载函数签名而非完整实现,持久化对话历史防止重启丢失

故障排查流程图

flowchart TD
    A[上下文超限] --> B[检查Token使用]
    B --> C[openclaw --context-stats]
    C --> D{哪部分超限?}
    D -->|对话历史| E[压缩历史]
    D -->|文件加载| F[分块加载]
    D -->|总Token| G[全局管理]
    E --> H[summarizeThreshold=50]
    H --> I[保留代码/错误/决策]
    I --> J[openclaw测试]
    F --> K[chunkSize=5000]
    K --> L[选择性关键词加载]
    L --> J
    G --> M[配置滑动窗口]
    M --> N[优先级淘汰策略]
    N --> J
    J --> O{成功?}
    O -->|是| P[✅ 问题解决]
    O -->|否| Q[使用RAG检索]
    Q --> R[索引代码库]
    R --> S[按需检索相关段落]
    S --> T[只加载签名]
    T --> J
    P --> U[部署Token监控]
    U --> V[配置每日预算]
    V --> W[✅ 长期可控]
Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐