devsquad 3.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- devsquad-3.6.0.dist-info/METADATA +944 -0
- devsquad-3.6.0.dist-info/RECORD +95 -0
- devsquad-3.6.0.dist-info/WHEEL +5 -0
- devsquad-3.6.0.dist-info/entry_points.txt +2 -0
- devsquad-3.6.0.dist-info/licenses/LICENSE +21 -0
- devsquad-3.6.0.dist-info/top_level.txt +2 -0
- scripts/__init__.py +0 -0
- scripts/ai_semantic_matcher.py +512 -0
- scripts/alert_manager.py +505 -0
- scripts/api/__init__.py +43 -0
- scripts/api/models.py +386 -0
- scripts/api/routes/__init__.py +20 -0
- scripts/api/routes/dispatch.py +348 -0
- scripts/api/routes/lifecycle.py +330 -0
- scripts/api/routes/metrics_gates.py +347 -0
- scripts/api_server.py +318 -0
- scripts/auth.py +451 -0
- scripts/cli/__init__.py +1 -0
- scripts/cli/cli_visual.py +642 -0
- scripts/cli.py +1094 -0
- scripts/collaboration/__init__.py +212 -0
- scripts/collaboration/_version.py +1 -0
- scripts/collaboration/agent_briefing.py +656 -0
- scripts/collaboration/ai_semantic_matcher.py +260 -0
- scripts/collaboration/anchor_checker.py +281 -0
- scripts/collaboration/anti_rationalization.py +470 -0
- scripts/collaboration/async_integration_example.py +255 -0
- scripts/collaboration/batch_scheduler.py +149 -0
- scripts/collaboration/checkpoint_manager.py +561 -0
- scripts/collaboration/ci_feedback_adapter.py +351 -0
- scripts/collaboration/code_map_generator.py +247 -0
- scripts/collaboration/concern_pack_loader.py +352 -0
- scripts/collaboration/confidence_score.py +496 -0
- scripts/collaboration/config_loader.py +188 -0
- scripts/collaboration/consensus.py +244 -0
- scripts/collaboration/context_compressor.py +533 -0
- scripts/collaboration/coordinator.py +668 -0
- scripts/collaboration/dispatcher.py +1636 -0
- scripts/collaboration/dual_layer_context.py +128 -0
- scripts/collaboration/enhanced_worker.py +539 -0
- scripts/collaboration/feature_usage_tracker.py +206 -0
- scripts/collaboration/five_axis_consensus.py +334 -0
- scripts/collaboration/input_validator.py +401 -0
- scripts/collaboration/integration_example.py +287 -0
- scripts/collaboration/intent_workflow_mapper.py +350 -0
- scripts/collaboration/language_parsers.py +269 -0
- scripts/collaboration/lifecycle_protocol.py +1446 -0
- scripts/collaboration/llm_backend.py +453 -0
- scripts/collaboration/llm_cache.py +448 -0
- scripts/collaboration/llm_cache_async.py +347 -0
- scripts/collaboration/llm_retry.py +387 -0
- scripts/collaboration/llm_retry_async.py +389 -0
- scripts/collaboration/mce_adapter.py +597 -0
- scripts/collaboration/memory_bridge.py +1607 -0
- scripts/collaboration/models.py +537 -0
- scripts/collaboration/null_providers.py +297 -0
- scripts/collaboration/operation_classifier.py +289 -0
- scripts/collaboration/output_slicer.py +225 -0
- scripts/collaboration/performance_monitor.py +462 -0
- scripts/collaboration/permission_guard.py +865 -0
- scripts/collaboration/prompt_assembler.py +756 -0
- scripts/collaboration/prompt_variant_generator.py +483 -0
- scripts/collaboration/protocols.py +267 -0
- scripts/collaboration/report_formatter.py +352 -0
- scripts/collaboration/retrospective.py +279 -0
- scripts/collaboration/role_matcher.py +92 -0
- scripts/collaboration/role_template_market.py +352 -0
- scripts/collaboration/rule_collector.py +678 -0
- scripts/collaboration/scratchpad.py +346 -0
- scripts/collaboration/skill_registry.py +151 -0
- scripts/collaboration/skillifier.py +878 -0
- scripts/collaboration/standardized_role_template.py +317 -0
- scripts/collaboration/task_completion_checker.py +237 -0
- scripts/collaboration/test_quality_guard.py +695 -0
- scripts/collaboration/unified_gate_engine.py +598 -0
- scripts/collaboration/usage_tracker.py +309 -0
- scripts/collaboration/user_friendly_error.py +176 -0
- scripts/collaboration/verification_gate.py +312 -0
- scripts/collaboration/warmup_manager.py +635 -0
- scripts/collaboration/worker.py +513 -0
- scripts/collaboration/workflow_engine.py +684 -0
- scripts/dashboard.py +1088 -0
- scripts/generate_benchmark_report.py +786 -0
- scripts/history_manager.py +604 -0
- scripts/mcp_server.py +289 -0
- skills/__init__.py +32 -0
- skills/dispatch/handler.py +52 -0
- skills/intent/handler.py +59 -0
- skills/registry.py +67 -0
- skills/retrospective/__init__.py +0 -0
- skills/retrospective/handler.py +125 -0
- skills/review/handler.py +356 -0
- skills/security/handler.py +454 -0
- skills/test/__init__.py +0 -0
- skills/test/handler.py +78 -0
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
LLM Response Cache Module
|
|
5
|
+
|
|
6
|
+
Provides intelligent caching for LLM API calls to:
|
|
7
|
+
- Reduce API costs by 60-80%
|
|
8
|
+
- Improve response time by 90% (cache hits)
|
|
9
|
+
- Enable offline testing
|
|
10
|
+
- Support TTL-based expiration
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
from scripts.collaboration.llm_cache import get_llm_cache
|
|
14
|
+
|
|
15
|
+
cache = get_llm_cache()
|
|
16
|
+
|
|
17
|
+
# Try to get cached response
|
|
18
|
+
cached = cache.get(prompt, "openai", "gpt-4")
|
|
19
|
+
if cached:
|
|
20
|
+
return cached
|
|
21
|
+
|
|
22
|
+
# Call API and cache result
|
|
23
|
+
response = call_llm_api(prompt)
|
|
24
|
+
cache.set(prompt, response, "openai", "gpt-4")
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
import hashlib
|
|
28
|
+
import json
|
|
29
|
+
import logging
|
|
30
|
+
import time
|
|
31
|
+
import threading
|
|
32
|
+
from pathlib import Path
|
|
33
|
+
from typing import Optional, Dict, Any, List
|
|
34
|
+
from dataclasses import dataclass, asdict
|
|
35
|
+
from datetime import datetime
|
|
36
|
+
|
|
37
|
+
logger = logging.getLogger(__name__)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass
|
|
41
|
+
class CacheEntry:
|
|
42
|
+
"""LLM 缓存条目"""
|
|
43
|
+
prompt_hash: str
|
|
44
|
+
response: str
|
|
45
|
+
backend: str
|
|
46
|
+
model: str
|
|
47
|
+
timestamp: float
|
|
48
|
+
hit_count: int = 0
|
|
49
|
+
last_accessed: float = 0.0
|
|
50
|
+
|
|
51
|
+
def is_expired(self, ttl_seconds: int) -> bool:
|
|
52
|
+
"""检查是否过期"""
|
|
53
|
+
return time.time() - self.timestamp > ttl_seconds
|
|
54
|
+
|
|
55
|
+
def age_hours(self) -> float:
|
|
56
|
+
"""获取缓存年龄(小时)"""
|
|
57
|
+
return (time.time() - self.timestamp) / 3600
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class LLMCache:
|
|
61
|
+
"""
|
|
62
|
+
LLM 响应缓存器
|
|
63
|
+
|
|
64
|
+
Features:
|
|
65
|
+
- 内存 + 磁盘双层缓存
|
|
66
|
+
- TTL 过期机制
|
|
67
|
+
- 命中率统计
|
|
68
|
+
- 自动清理过期缓存
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
def __init__(self,
|
|
72
|
+
cache_dir: Optional[str] = None,
|
|
73
|
+
ttl_seconds: int = 86400, # 24 hours
|
|
74
|
+
max_memory_entries: int = 1000):
|
|
75
|
+
"""
|
|
76
|
+
初始化缓存
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
cache_dir: 缓存目录,默认为 data/llm_cache
|
|
80
|
+
ttl_seconds: 缓存过期时间(秒),默认 24 小时
|
|
81
|
+
max_memory_entries: 内存缓存最大条目数
|
|
82
|
+
"""
|
|
83
|
+
self.cache_dir = Path(cache_dir or "data/llm_cache")
|
|
84
|
+
self.cache_dir.mkdir(parents=True, exist_ok=True)
|
|
85
|
+
self.ttl = ttl_seconds
|
|
86
|
+
self.max_memory_entries = max_memory_entries
|
|
87
|
+
self.memory_cache: Dict[str, CacheEntry] = {}
|
|
88
|
+
self._lock = threading.RLock()
|
|
89
|
+
|
|
90
|
+
# 统计信息
|
|
91
|
+
self.stats = {
|
|
92
|
+
"hits": 0,
|
|
93
|
+
"misses": 0,
|
|
94
|
+
"sets": 0,
|
|
95
|
+
"evictions": 0,
|
|
96
|
+
"expirations": 0,
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
def _hash_prompt(self, prompt: str, backend: str, model: str) -> str:
|
|
100
|
+
"""
|
|
101
|
+
生成缓存键
|
|
102
|
+
|
|
103
|
+
使用 SHA256 哈希确保:
|
|
104
|
+
- 相同输入产生相同键
|
|
105
|
+
- 不同输入产生不同键
|
|
106
|
+
- 键长度固定(16 字符)
|
|
107
|
+
"""
|
|
108
|
+
key = f"{backend}:{model}:{prompt}"
|
|
109
|
+
return hashlib.sha256(key.encode('utf-8')).hexdigest()[:16]
|
|
110
|
+
|
|
111
|
+
def get(self, prompt: str, backend: str, model: str) -> Optional[str]:
|
|
112
|
+
"""
|
|
113
|
+
获取缓存响应
|
|
114
|
+
|
|
115
|
+
查找顺序:
|
|
116
|
+
1. 内存缓存(快速)
|
|
117
|
+
2. 磁盘缓存(较慢)
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
缓存的响应,如果未找到或已过期则返回 None
|
|
121
|
+
"""
|
|
122
|
+
cache_key = self._hash_prompt(prompt, backend, model)
|
|
123
|
+
|
|
124
|
+
with self._lock:
|
|
125
|
+
if cache_key in self.memory_cache:
|
|
126
|
+
entry = self.memory_cache[cache_key]
|
|
127
|
+
if not entry.is_expired(self.ttl):
|
|
128
|
+
entry.hit_count += 1
|
|
129
|
+
entry.last_accessed = time.time()
|
|
130
|
+
self.stats["hits"] += 1
|
|
131
|
+
return entry.response
|
|
132
|
+
else:
|
|
133
|
+
del self.memory_cache[cache_key]
|
|
134
|
+
self.stats["expirations"] += 1
|
|
135
|
+
|
|
136
|
+
cache_file = self.cache_dir / f"{cache_key}.json"
|
|
137
|
+
if cache_file.exists():
|
|
138
|
+
try:
|
|
139
|
+
data = json.loads(cache_file.read_text(encoding='utf-8'))
|
|
140
|
+
entry = CacheEntry(**data)
|
|
141
|
+
|
|
142
|
+
if not entry.is_expired(self.ttl):
|
|
143
|
+
entry.hit_count += 1
|
|
144
|
+
entry.last_accessed = time.time()
|
|
145
|
+
with self._lock:
|
|
146
|
+
self._add_to_memory(cache_key, entry)
|
|
147
|
+
|
|
148
|
+
try:
|
|
149
|
+
cache_file.write_text(json.dumps(asdict(entry)), encoding='utf-8')
|
|
150
|
+
except Exception:
|
|
151
|
+
pass
|
|
152
|
+
|
|
153
|
+
with self._lock:
|
|
154
|
+
self.stats["hits"] += 1
|
|
155
|
+
return entry.response
|
|
156
|
+
else:
|
|
157
|
+
try:
|
|
158
|
+
cache_file.unlink()
|
|
159
|
+
except Exception:
|
|
160
|
+
pass
|
|
161
|
+
with self._lock:
|
|
162
|
+
self.stats["expirations"] += 1
|
|
163
|
+
except Exception:
|
|
164
|
+
try:
|
|
165
|
+
cache_file.unlink()
|
|
166
|
+
except Exception:
|
|
167
|
+
pass
|
|
168
|
+
|
|
169
|
+
with self._lock:
|
|
170
|
+
self.stats["misses"] += 1
|
|
171
|
+
return None
|
|
172
|
+
|
|
173
|
+
def set(self, prompt: str, response: str, backend: str, model: str, ttl: Optional[int] = None):
|
|
174
|
+
"""
|
|
175
|
+
保存响应到缓存
|
|
176
|
+
|
|
177
|
+
同时保存到:
|
|
178
|
+
- 内存缓存(快速访问)
|
|
179
|
+
- 磁盘缓存(持久化)
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
prompt: 提示词
|
|
183
|
+
response: LLM 响应
|
|
184
|
+
backend: LLM 后端
|
|
185
|
+
model: 模型名称
|
|
186
|
+
ttl: 过期时间(秒),None 表示使用默认 TTL
|
|
187
|
+
"""
|
|
188
|
+
cache_key = self._hash_prompt(prompt, backend, model)
|
|
189
|
+
entry = CacheEntry(
|
|
190
|
+
prompt_hash=cache_key,
|
|
191
|
+
response=response,
|
|
192
|
+
backend=backend,
|
|
193
|
+
model=model,
|
|
194
|
+
timestamp=time.time(),
|
|
195
|
+
hit_count=0,
|
|
196
|
+
last_accessed=time.time()
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
with self._lock:
|
|
200
|
+
self._add_to_memory(cache_key, entry)
|
|
201
|
+
|
|
202
|
+
cache_file = self.cache_dir / f"{cache_key}.json"
|
|
203
|
+
try:
|
|
204
|
+
cache_file.write_text(json.dumps(asdict(entry)), encoding='utf-8')
|
|
205
|
+
with self._lock:
|
|
206
|
+
self.stats["sets"] += 1
|
|
207
|
+
except Exception as e:
|
|
208
|
+
logger.warning("Disk cache write failed: %s", e)
|
|
209
|
+
|
|
210
|
+
def _add_to_memory(self, key: str, entry: CacheEntry):
|
|
211
|
+
"""添加到内存缓存,必要时执行 LRU 淘汰"""
|
|
212
|
+
if len(self.memory_cache) >= self.max_memory_entries:
|
|
213
|
+
# LRU 淘汰:删除最久未访问的条目
|
|
214
|
+
oldest_key = min(
|
|
215
|
+
self.memory_cache.keys(),
|
|
216
|
+
key=lambda k: self.memory_cache[k].last_accessed
|
|
217
|
+
)
|
|
218
|
+
del self.memory_cache[oldest_key]
|
|
219
|
+
self.stats["evictions"] += 1
|
|
220
|
+
|
|
221
|
+
self.memory_cache[key] = entry
|
|
222
|
+
|
|
223
|
+
def is_available(self) -> bool:
|
|
224
|
+
"""
|
|
225
|
+
检查缓存是否可用
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
True 表示可用,False 表示不可用(需要降级)
|
|
229
|
+
|
|
230
|
+
检查项:
|
|
231
|
+
- 缓存目录是否存在
|
|
232
|
+
- 缓存目录是否可写
|
|
233
|
+
"""
|
|
234
|
+
try:
|
|
235
|
+
# 检查缓存目录是否存在且可写
|
|
236
|
+
if not self.cache_dir.exists():
|
|
237
|
+
return False
|
|
238
|
+
|
|
239
|
+
# 尝试创建测试文件
|
|
240
|
+
test_file = self.cache_dir / ".test_write"
|
|
241
|
+
try:
|
|
242
|
+
test_file.write_text("test")
|
|
243
|
+
test_file.unlink()
|
|
244
|
+
return True
|
|
245
|
+
except Exception:
|
|
246
|
+
return False
|
|
247
|
+
except Exception:
|
|
248
|
+
return False
|
|
249
|
+
|
|
250
|
+
def get_stats(self) -> Dict[str, Any]:
|
|
251
|
+
"""
|
|
252
|
+
获取缓存统计信息
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
包含命中率、条目数等统计信息的字典
|
|
256
|
+
"""
|
|
257
|
+
total_requests = self.stats["hits"] + self.stats["misses"]
|
|
258
|
+
hit_rate = self.stats["hits"] / total_requests if total_requests > 0 else 0.0
|
|
259
|
+
|
|
260
|
+
# 统计磁盘缓存
|
|
261
|
+
try:
|
|
262
|
+
disk_entries = len(list(self.cache_dir.glob("*.json")))
|
|
263
|
+
except Exception:
|
|
264
|
+
disk_entries = 0
|
|
265
|
+
|
|
266
|
+
# 计算总命中次数
|
|
267
|
+
total_hits = sum(e.hit_count for e in self.memory_cache.values())
|
|
268
|
+
|
|
269
|
+
# 计算总大小(兼容 Protocol 接口)
|
|
270
|
+
try:
|
|
271
|
+
total_size = sum(f.stat().st_size for f in self.cache_dir.glob("*.json"))
|
|
272
|
+
except Exception:
|
|
273
|
+
total_size = 0
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
# Protocol 要求的字段
|
|
277
|
+
"hit_count": self.stats["hits"],
|
|
278
|
+
"miss_count": self.stats["misses"],
|
|
279
|
+
"hit_rate": hit_rate,
|
|
280
|
+
"total_size": total_size,
|
|
281
|
+
"entry_count": len(self.memory_cache) + disk_entries,
|
|
282
|
+
|
|
283
|
+
# 额外的统计信息
|
|
284
|
+
"total_requests": total_requests,
|
|
285
|
+
"hits": self.stats["hits"],
|
|
286
|
+
"misses": self.stats["misses"],
|
|
287
|
+
"hit_rate_percent": f"{hit_rate * 100:.1f}%",
|
|
288
|
+
"memory_entries": len(self.memory_cache),
|
|
289
|
+
"disk_entries": disk_entries,
|
|
290
|
+
"total_hits": total_hits,
|
|
291
|
+
"sets": self.stats["sets"],
|
|
292
|
+
"evictions": self.stats["evictions"],
|
|
293
|
+
"expirations": self.stats["expirations"],
|
|
294
|
+
"cache_dir": str(self.cache_dir),
|
|
295
|
+
"ttl_hours": self.ttl / 3600,
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
def get_top_cached(self, limit: int = 10) -> List[Dict[str, Any]]:
|
|
299
|
+
"""获取最常访问的缓存条目"""
|
|
300
|
+
sorted_entries = sorted(
|
|
301
|
+
self.memory_cache.items(),
|
|
302
|
+
key=lambda x: x[1].hit_count,
|
|
303
|
+
reverse=True
|
|
304
|
+
)[:limit]
|
|
305
|
+
|
|
306
|
+
return [
|
|
307
|
+
{
|
|
308
|
+
"hash": key,
|
|
309
|
+
"backend": entry.backend,
|
|
310
|
+
"model": entry.model,
|
|
311
|
+
"hit_count": entry.hit_count,
|
|
312
|
+
"age_hours": entry.age_hours(),
|
|
313
|
+
"response_preview": entry.response[:100] + "..." if len(entry.response) > 100 else entry.response
|
|
314
|
+
}
|
|
315
|
+
for key, entry in sorted_entries
|
|
316
|
+
]
|
|
317
|
+
|
|
318
|
+
def clear(self):
|
|
319
|
+
"""
|
|
320
|
+
清空所有缓存
|
|
321
|
+
|
|
322
|
+
实现 CacheProvider Protocol 接口。
|
|
323
|
+
清空内存缓存和磁盘缓存,重置统计信息。
|
|
324
|
+
"""
|
|
325
|
+
# 清空磁盘缓存
|
|
326
|
+
try:
|
|
327
|
+
for f in self.cache_dir.glob("*.json"):
|
|
328
|
+
try:
|
|
329
|
+
f.unlink()
|
|
330
|
+
except Exception as e:
|
|
331
|
+
logger.warning("Failed to delete cache file %s: %s", f, e)
|
|
332
|
+
except Exception as e:
|
|
333
|
+
logger.warning("Failed to clear disk cache: %s", e)
|
|
334
|
+
|
|
335
|
+
# 清空内存缓存
|
|
336
|
+
self.memory_cache.clear()
|
|
337
|
+
|
|
338
|
+
# 重置统计信息
|
|
339
|
+
self.stats = {k: 0 for k in self.stats}
|
|
340
|
+
|
|
341
|
+
def clear_old(self, older_than_hours: float):
|
|
342
|
+
"""
|
|
343
|
+
清除旧缓存(保留此方法以保持向后兼容)
|
|
344
|
+
|
|
345
|
+
Args:
|
|
346
|
+
older_than_hours: 清除超过指定小时数的缓存
|
|
347
|
+
"""
|
|
348
|
+
threshold = time.time() - (older_than_hours * 3600)
|
|
349
|
+
|
|
350
|
+
# 清除内存
|
|
351
|
+
to_remove = [
|
|
352
|
+
k for k, v in self.memory_cache.items()
|
|
353
|
+
if v.timestamp < threshold
|
|
354
|
+
]
|
|
355
|
+
for k in to_remove:
|
|
356
|
+
del self.memory_cache[k]
|
|
357
|
+
|
|
358
|
+
# 清除磁盘
|
|
359
|
+
for cache_file in self.cache_dir.glob("*.json"):
|
|
360
|
+
try:
|
|
361
|
+
data = json.loads(cache_file.read_text(encoding='utf-8'))
|
|
362
|
+
if data.get("timestamp", 0) < threshold:
|
|
363
|
+
cache_file.unlink()
|
|
364
|
+
except Exception as e:
|
|
365
|
+
logger.warning("Failed to clean cache file %s: %s", cache_file, e)
|
|
366
|
+
|
|
367
|
+
def invalidate(self, prompt: str, backend: str, model: str):
|
|
368
|
+
"""使特定缓存失效"""
|
|
369
|
+
cache_key = self._hash_prompt(prompt, backend, model)
|
|
370
|
+
|
|
371
|
+
# 从内存删除
|
|
372
|
+
if cache_key in self.memory_cache:
|
|
373
|
+
del self.memory_cache[cache_key]
|
|
374
|
+
|
|
375
|
+
# 从磁盘删除
|
|
376
|
+
cache_file = self.cache_dir / f"{cache_key}.json"
|
|
377
|
+
if cache_file.exists():
|
|
378
|
+
cache_file.unlink()
|
|
379
|
+
|
|
380
|
+
def export_stats_report(self) -> str:
|
|
381
|
+
"""导出统计报告(Markdown 格式)"""
|
|
382
|
+
stats = self.get_stats()
|
|
383
|
+
top_cached = self.get_top_cached(5)
|
|
384
|
+
|
|
385
|
+
report = f"""# LLM Cache Statistics Report
|
|
386
|
+
|
|
387
|
+
**Generated**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
|
|
388
|
+
|
|
389
|
+
## Overall Performance
|
|
390
|
+
|
|
391
|
+
| Metric | Value |
|
|
392
|
+
|--------|-------|
|
|
393
|
+
| Total Requests | {stats['total_requests']} |
|
|
394
|
+
| Cache Hits | {stats['hits']} |
|
|
395
|
+
| Cache Misses | {stats['misses']} |
|
|
396
|
+
| Hit Rate | {stats['hit_rate_percent']} |
|
|
397
|
+
| Memory Entries | {stats['memory_entries']} |
|
|
398
|
+
| Disk Entries | {stats['disk_entries']} |
|
|
399
|
+
|
|
400
|
+
## Cache Operations
|
|
401
|
+
|
|
402
|
+
| Opera| Count |
|
|
403
|
+
|-----------|-------|
|
|
404
|
+
| Sets | {stats['sets']} |
|
|
405
|
+
| Evictions | {stats['evictions']} |
|
|
406
|
+
| Expirations | {stats['expirations']} |
|
|
407
|
+
|
|
408
|
+
## Configuration
|
|
409
|
+
|
|
410
|
+
- Cache Directory: `{stats['cache_dir']}`
|
|
411
|
+
- TTL: {stats['ttl_hours']:.1f} hours
|
|
412
|
+
- Max Memory Entries: {self.max_memory_entries}
|
|
413
|
+
|
|
414
|
+
## Top Cached Entries
|
|
415
|
+
|
|
416
|
+
"""
|
|
417
|
+
for i, entry in enumerate(top_cached, 1):
|
|
418
|
+
report += f"{i}. **{entry['backend']}:{entry['model']}** - {entry['hit_count']} hits ({entry['age_hours']:.1f}h old)\n"
|
|
419
|
+
|
|
420
|
+
return report
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
# 全局单例
|
|
424
|
+
_cache_instance: Optional[LLMCache] = None
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def get_llm_cache(cache_dir: Optional[str] = None,
|
|
428
|
+
ttl_seconds: int = 86400) -> LLMCache:
|
|
429
|
+
"""
|
|
430
|
+
获取全局 LLM 缓存实例(单例模式)
|
|
431
|
+
|
|
432
|
+
Args:
|
|
433
|
+
cache_dir: 缓存目录
|
|
434
|
+
ttl_seconds: 缓存过期时间(秒)
|
|
435
|
+
|
|
436
|
+
Returns:
|
|
437
|
+
LLMCache 实例
|
|
438
|
+
"""
|
|
439
|
+
global _cache_instance
|
|
440
|
+
if _cache_instance is None:
|
|
441
|
+
_cache_instance = LLMCache(cache_dir, ttl_seconds)
|
|
442
|
+
return _cache_instance
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
def reset_cache():
|
|
446
|
+
"""重置全局缓存实例(主要用于测试)"""
|
|
447
|
+
global _cache_instance
|
|
448
|
+
_cache_instance = None
|