1. 项目概述:代码库与LLM上下文窗口适配检测工具
在当今AI辅助编程日益普及的背景下,大型语言模型(LLM)已成为开发者不可或缺的伙伴。然而,当我们尝试用LLM分析大型代码库时,常常会遇到一个根本性限制——上下文窗口(Context Window)。这个工具正是为解决这一痛点而生,它能智能评估你的代码库是否适合放入LLM的上下文窗口,并提供具体的优化建议。
核心价值:帮助开发者在5分钟内了解代码库与目标LLM模型的适配程度,避免因上下文溢出导致的低效分析
我曾在分析一个中型Python项目(约2万行代码)时,发现GPT-4只能处理其中不到30%的内容。这种"盲人摸象"式的分析导致了许多错误的架构建议。正是这种亲身经历促使我开发了这个检测工具。
2. 核心原理与技术选型
2.1 上下文窗口的本质
LLM的上下文窗口就像人类的工作记忆(Working Memory),它决定了模型能同时处理多少信息。以GPT-4为例,其标准上下文窗口为8192个token(约6万个字符),这相当于:
- 30-50个Python文件(平均大小)
- 约150页纯文本
- 一本中篇小说的体量
2.2 Token计算机制
不同于简单的字符或行数统计,token是LLM处理文本的基本单位。例如:
python复制def hello(): # 6个token
print("World") # 4个token
我们使用OpenAI的tiktoken库进行精确计算,这是因为它:
- 与实际API使用的tokenizer完全一致
- 支持所有主流模型(GPT-3/4, Codex等)
- 处理速度极快(百万token/秒)
2.3 技术栈选择理由
| 组件 | 选型 | 优势 |
|---|---|---|
| 语言 | Python 3.8+ | 丰富的AI生态,适合快速原型开发 |
| Token计算 | tiktoken | 官方推荐,计算精确 |
| 目录扫描 | treeignore | 自动忽略.git/node_modules等目录 |
| 并行处理 | concurrent.futures | 原生库,无额外依赖 |
3. 实现详解与关键代码
3.1 环境配置与初始化
建议使用虚拟环境隔离依赖:
bash复制python -m venv llm_analyzer
source llm_analyzer/bin/activate # Linux/Mac
llm_analyzer\Scripts\activate # Windows
pip install openai tiktoken treeignore chardet
初始化分析器时需注意:
python复制class ContextAnalyzer:
def __init__(self, api_key: str, model: str = 'gpt-4'):
self.ignored_files = {'.git', '__pycache__', 'node_modules'} # 默认忽略目录
self.encoding = tiktoken.encoding_for_model(model) # 加载指定模型的tokenizer
self.model_context_limit = self._get_model_limit(model) # 获取模型上下文限制
def _get_model_limit(self, model: str) -> int:
"""不同模型的上下文窗口差异很大"""
limits = {
'gpt-4': 8192,
'gpt-3.5-turbo': 4096,
'claude-2': 100000
}
return limits.get(model, 8192) # 默认返回GPT-4的限制
3.2 核心扫描逻辑实现
递归扫描目录时需注意:
- 控制扫描深度(避免无限递归)
- 处理特殊文件编码
- 跳过二进制文件
优化后的文件分析代码:
python复制def analyze_file(self, file_path: str) -> Optional[Dict]:
"""增强版文件分析,处理各种边缘情况"""
if not os.path.isfile(file_path):
return None
# 跳过二进制文件(通过扩展名初步判断)
binary_exts = {'.png', '.jpg', '.pdf', '.zip'}
if any(file_path.lower().endswith(ext) for ext in binary_exts):
return None
try:
# 自动检测文件编码
with open(file_path, 'rb') as f:
raw_data = f.read(1024) # 只读取前1KB用于编码检测
encoding = chardet.detect(raw_data)['encoding'] or 'utf-8'
# 完整读取文件内容
with open(file_path, 'r', encoding=encoding, errors='replace') as f:
content = f.read()
return {
'file': os.path.relpath(file_path),
'tokens': len(self.encoding.encode(content)),
'lines': content.count('\n') + 1,
'size_kb': os.path.getsize(file_path) / 1024
}
except Exception as e:
print(f"[Warning] 分析文件失败: {file_path} - {str(e)}")
return None
3.3 上下文适配评估算法
关键改进点:
- 添加10%的安全缓冲区(避免完全填满上下文窗口)
- 按文件重要性排序(而不仅是大小)
- 支持自定义权重计算
python复制def check_context_fit(self, directory: str, safety_buffer: float = 0.9) -> Dict:
"""增强版上下文适配检查"""
files = [f for f in self.scan_directory(directory) if f is not None]
if not files:
return {'error': '未发现可分析文件'}
# 计算总token和有效限制
total_tokens = sum(f['tokens'] for f in files)
effective_limit = int(self.model_context_limit * safety_buffer)
# 按重要性排序(考虑文件路径和类型)
def file_score(file_info):
path = file_info['file'].lower()
weight = 1.0
if '/test/' in path: weight = 0.3 # 测试文件权重较低
if path.endswith('.py'): weight = 1.2 # Python文件更重要
return file_info['tokens'] * weight
sorted_files = sorted(files, key=file_score, reverse=True)
# 计算累积token
cumulative_files = []
cumulative_tokens = 0
for file in sorted_files:
cumulative_tokens += file['tokens']
cumulative_files.append({
'file': file['file'],
'tokens': file['tokens'],
'cumulative': cumulative_tokens,
'included': cumulative_tokens <= effective_limit
})
if cumulative_tokens > effective_limit:
break
return {
'model': self.model,
'total_files': len(files),
'total_tokens': total_tokens,
'context_limit': self.model_context_limit,
'effective_limit': effective_limit,
'fit_ratio': min(100, (effective_limit / total_tokens) * 100),
'top_files': cumulative_files,
'unfit_files': [f for f in sorted_files if f['tokens'] > effective_limit]
}
4. 高级功能与优化建议
4.1 智能优化建议生成
与简单拆分文件不同,我们通过LLM生成符合软件工程原则的建议:
python复制def get_optimization_suggestions(self, analysis: Dict) -> str:
"""生成结构化优化建议"""
prompt_template = """
作为资深架构师,请针对以下代码库分析提供专业优化建议:
项目概况:
- 总文件数: {file_count}
- 总Token数: {total_tokens} (上下文限制: {context_limit})
- 适配率: {fit_ratio:.1f}%
关键问题文件:
{problem_files}
请从以下维度提供建议:
1. 模块拆分策略(按功能/层级)
2. 接口设计优化
3. 依赖关系重构
4. 测试策略调整
要求:
- 使用Markdown格式
- 每个建议附带具体实施步骤
- 优先处理前3大文件
"""
problem_files = '\n'.join(
f"- {f['file']} ({f['tokens']} tokens)"
for f in analysis['top_files'][:3]
)
prompt = prompt_template.format(
file_count=analysis['total_files'],
total_tokens=analysis['total_tokens'],
context_limit=analysis['context_limit'],
fit_ratio=analysis['fit_ratio'],
problem_files=problem_files
)
# 实际调用LLM API(示例为模拟响应)
return """
### 优化建议报告
**1. 模块拆分策略**
- `core/utils.py` (2456 tokens) 可拆分为:
- `core/string_utils.py`
- `core/file_utils.py`
- `core/date_utils.py`
- 实施步骤:
1. 创建新文件并按功能转移代码
2. 更新引用点的导入语句
3. 添加`__init__.py`暴露公共接口
**2. 接口设计优化**
- 将`service/api.py`中的混合逻辑改为分层设计:
- 路由层:只处理HTTP相关
- 服务层:业务逻辑
- 仓库层:数据访问
"""
4.2 可视化分析报告
使用Matplotlib生成专业图表:
python复制def generate_visual_report(analysis: Dict, output_dir: str):
"""生成可视化报告"""
os.makedirs(output_dir, exist_ok=True)
# 1. 文件大小分布图
plt.figure(figsize=(12, 6))
files = [f['file'].split('/')[-1][:15] for f in analysis['top_files'][:10]]
sizes = [f['tokens'] for f in analysis['top_files'][:10]]
plt.barh(files, sizes, color=['green' if f['included'] else 'red' for f in analysis['top_files'][:10]])
plt.title('Top 10 Files by Token Count')
plt.xlabel('Tokens')
plt.ylabel('Files')
plt.tight_layout()
plt.savefig(os.path.join(output_dir, 'file_sizes.png'))
plt.close()
# 2. 累积Token占比图
plt.figure(figsize=(10, 6))
cumulatives = [f['cumulative'] for f in analysis['top_files']]
percentages = [min(100, (c/analysis['total_tokens'])*100) for c in cumulatives]
plt.plot(percentages, marker='o')
plt.axhline(y=100, color='r', linestyle='--')
plt.axvline(x=len([f for f in analysis['top_files'] if f['included']]), color='g', linestyle=':')
plt.title('Cumulative Token Percentage')
plt.xlabel('File Index')
plt.ylabel('Percentage (%)')
plt.grid()
plt.savefig(os.path.join(output_dir, 'cumulative.png'))
plt.close()
5. 实战技巧与避坑指南
5.1 性能优化技巧
- 并行扫描加速:
python复制from concurrent.futures import ThreadPoolExecutor
def parallel_scan(directory: str, workers: int = 4) -> List[Dict]:
"""多线程目录扫描"""
file_paths = []
for root, _, files in os.walk(directory):
for file in files:
file_paths.append(os.path.join(root, file))
with ThreadPoolExecutor(max_workers=workers) as executor:
results = list(executor.map(self.analyze_file, file_paths))
return [r for r in results if r is not None]
- 缓存机制:
python复制import hashlib
import pickle
from pathlib import Path
def get_file_hash(file_path: str) -> str:
"""计算文件内容哈希"""
with open(file_path, 'rb') as f:
return hashlib.md5(f.read()).hexdigest()
def load_cached_analysis(directory: str) -> Optional[Dict]:
cache_file = Path(directory) / '.llm_analyzer_cache'
if cache_file.exists():
with open(cache_file, 'rb') as f:
return pickle.load(f)
return None
def save_analysis_cache(directory: str, data: Dict):
cache_file = Path(directory) / '.llm_analyzer_cache'
with open(cache_file, 'wb') as f:
pickle.dump(data, f)
5.2 常见问题解决方案
问题1:Token计算与API实际消耗不一致
原因:系统消息、格式标记等隐藏token
解决方案:添加15%的余量
python复制effective_limit = int(context_limit * 0.85) # 保留15%给系统消息
问题2:超大文件(>10k行)分析失败
解决方案:分块处理
python复制def analyze_large_file(file_path: str, chunk_size: int = 1000) -> Dict:
"""分块分析大文件"""
tokens = 0
lines = 0
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
while True:
chunk = f.read(chunk_size * 10) # 预估读取量
if not chunk:
break
tokens += len(self.encoding.encode(chunk))
lines += chunk.count('\n')
return {'file': file_path, 'tokens': tokens, 'lines': lines}
问题3:误分析二进制文件导致内存溢出
解决方案:添加二进制检测
python复制def is_binary(file_path: str) -> bool:
"""启发式判断二进制文件"""
with open(file_path, 'rb') as f:
return b'\x00' in f.read(1024) # 二进制文件通常包含null字节
6. 扩展方向与进阶应用
6.1 多模型支持扩展
python复制class MultiModelAnalyzer:
SUPPORTED_MODELS = {
'openai': {
'gpt-4': {'tokenizer': 'cl100k_base', 'limit': 8192},
'gpt-3.5': {'tokenizer': 'cl100k_base', 'limit': 4096}
},
'anthropic': {
'claude-2': {'tokenizer': 'claude', 'limit': 100000}
}
}
def __init__(self, provider: str, model: str):
self.provider = provider.lower()
self.model = model.lower()
self._validate_model()
self._init_tokenizer()
def _validate_model(self):
if self.provider not in self.SUPPORTED_MODELS:
raise ValueError(f"不支持的提供商: {self.provider}")
if self.model not in self.SUPPORTED_MODELS[self.provider]:
raise ValueError(f"提供商 {self.provider} 不支持模型 {self.model}")
def _init_tokenizer(self):
config = self.SUPPORTED_MODELS[self.provider][self.model]
if config['tokenizer'] == 'cl100k_base':
self.encoding = tiktoken.get_encoding('cl100k_base')
elif config['tokenizer'] == 'claude':
self.encoding = AnthropicTokenizer()
self.limit = config['limit']
6.2 集成到CI/CD流程
创建GitHub Action示例:
yaml复制name: LLM Context Check
on: [push, pull_request]
jobs:
analyze:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Install dependencies
run: |
python -m pip install llm-context-analyzer
- name: Run analysis
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
run: |
python -m llm_context_analyzer ./ --threshold=80
- name: Upload report
uses: actions/upload-artifact@v3
if: ${{ failure() }}
with:
name: context-analysis-report
path: ./context_report/
6.3 增量分析与监控
使用watchdog实现文件监控:
python复制from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
class CodeChangeHandler(FileSystemEventHandler):
def __init__(self, analyzer):
self.analyzer = analyzer
self.last_analysis = None
def on_modified(self, event):
if not event.is_directory and event.src_path.endswith('.py'):
print(f"检测到文件变更: {event.src_path}")
self.last_analysis = self.analyzer.incremental_analysis(
os.path.dirname(event.src_path),
changed_files=[event.src_path]
)
def start_monitoring(directory: str):
analyzer = ContextAnalyzer(os.getenv('OPENAI_API_KEY'))
event_handler = CodeChangeHandler(analyzer)
observer = Observer()
observer.schedule(event_handler, directory, recursive=True)
observer.start()
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
observer.stop()
observer.join()
在实际项目中,我发现这个工具特别适合以下场景:
- 准备用LLM分析遗留代码库前
- 重构大型单体应用时
- 设计新项目的模块划分时
- 评估第三方库的集成成本时
一个实用的技巧是:将分析结果与git历史结合,优先重构那些频繁修改的大文件,这样能获得最大的投入产出比。