perf: 降低 lru_cache 上限并限制流式响应块内存占用

- 缩减 model_permissions / tiktoken / formula_engine 的 lru_cache maxsize
- StreamUsageTracker 响应块增加 4MB 大小限制,超限后只计数不存储
- raw_chunks 改用 deque(maxlen=50) 避免无限增长
- HardwareTooltip 导入路径修正、tooltip 延迟归零、文案中文化
This commit is contained in:
fawney19
2026-02-12 11:41:42 +08:00
parent 483d536e2c
commit be430ebdde
5 changed files with 41 additions and 21 deletions

View File

@@ -1,11 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import type { ProxyNode } from '@/api/proxy-nodes' import type { ProxyNode } from '@/api/proxy-nodes'
import { import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from '@/components/ui'
import { Cpu } from 'lucide-vue-next' import { Cpu } from 'lucide-vue-next'
import { computed } from 'vue' import { computed } from 'vue'
@@ -77,7 +72,10 @@ function formatNumber(n: number) {
</script> </script>
<template> <template>
<TooltipProvider v-if="showHardwareInfo"> <TooltipProvider
v-if="showHardwareInfo"
:delay-duration="0"
>
<Tooltip> <Tooltip>
<TooltipTrigger as-child> <TooltipTrigger as-child>
<button <button
@@ -97,7 +95,7 @@ function formatNumber(n: number) {
v-if="hardwareRows.length === 0" v-if="hardwareRows.length === 0"
class="text-muted-foreground" class="text-muted-foreground"
> >
No hardware info reported. 暂无硬件信息上报
</div> </div>
<template v-else> <template v-else>
<div <div

View File

@@ -236,7 +236,7 @@ def validate_and_extract_model_mappings(
return True, None, mappings return True, None, mappings
@lru_cache(maxsize=2000) @lru_cache(maxsize=512)
def _compile_pattern_cached(pattern: str) -> regex.Pattern | None: def _compile_pattern_cached(pattern: str) -> regex.Pattern | None:
""" """
编译正则模式(带 LRU 缓存) 编译正则模式(带 LRU 缓存)

View File

@@ -22,7 +22,7 @@ except ImportError: # pragma: no cover
tiktoken = None tiktoken = None
@lru_cache(maxsize=256) @lru_cache(maxsize=32)
def _get_encoder_cached(model: str) -> Any: def _get_encoder_cached(model: str) -> Any:
"""全局编码器缓存。 """全局编码器缓存。

View File

@@ -71,7 +71,7 @@ def _iter_ast_nodes(node: ast.AST) -> Iterable[ast.AST]:
yield from _iter_ast_nodes(child) yield from _iter_ast_nodes(child)
@lru_cache(maxsize=2048) @lru_cache(maxsize=256)
def _validate_expression_cached(expression: str) -> ast.Expression: def _validate_expression_cached(expression: str) -> ast.Expression:
""" """
Parse + validate an expression and cache the resulting AST. Parse + validate an expression and cache the resulting AST.

View File

@@ -7,6 +7,7 @@ from __future__ import annotations
import json import json
import re import re
from collections import deque
from collections.abc import AsyncIterator from collections.abc import AsyncIterator
from typing import Any from typing import Any
@@ -108,8 +109,13 @@ class StreamUsageTracker:
"stop_sequence": None, "stop_sequence": None,
"usage": {}, "usage": {},
} }
self.response_chunks = [] # 保存所有原始响应块 self.response_chunks = [] # 保存解析后的响应块
self.raw_chunks = [] # 保存所有原始字节流(用于错误诊断 self.response_chunks_count = 0 # 响应块总计数(含被丢弃的
self.response_chunks_size = 0 # 响应块累计序列化大小(字节)
self._response_chunks_max_size = 4 * 1024 * 1024 # 4MB留余量给 truncate_body 的 5MB 上限
self.raw_chunks: deque[str | bytes] = deque(
maxlen=50
) # 仅保留最后50个原始chunk用于错误诊断
# 时间跟踪 # 时间跟踪
self.start_time = None self.start_time = None
@@ -133,6 +139,15 @@ class StreamUsageTracker:
self.error_message = None # 错误消息(如果有) self.error_message = None # 错误消息(如果有)
self.attempt_id = attempt_id self.attempt_id = attempt_id
def _append_response_chunk(self, data: dict[str, Any]) -> None:
"""追加响应块,超过大小限制后只计数不存储"""
self.response_chunks_count += 1
if self.response_chunks_size < self._response_chunks_max_size:
chunk_size = len(json.dumps(data, ensure_ascii=False))
self.response_chunks_size += chunk_size
if self.response_chunks_size <= self._response_chunks_max_size:
self.response_chunks.append(data)
def set_error_status(self, status_code: int, error_message: str) -> None: def set_error_status(self, status_code: int, error_message: str) -> None:
""" """
设置错误状态 设置错误状态
@@ -275,7 +290,7 @@ class StreamUsageTracker:
data = json.loads(data_str) data = json.loads(data_str)
if isinstance(data, dict): if isinstance(data, dict):
self.response_chunks.append(data) self._append_response_chunk(data)
try: try:
self._update_complete_response(data) self._update_complete_response(data)
except Exception as update_error: except Exception as update_error:
@@ -357,7 +372,7 @@ class StreamUsageTracker:
# 更新完整响应(如果有数据) # 更新完整响应(如果有数据)
if chunk.data: if chunk.data:
self.response_chunks.append(chunk.data) self._append_response_chunk(chunk.data)
try: try:
self._update_complete_response(chunk.data) self._update_complete_response(chunk.data)
except Exception as update_error: except Exception as update_error:
@@ -635,14 +650,21 @@ class StreamUsageTracker:
# 否则使用原始字节流用于错误诊断如403 HTML响应 # 否则使用原始字节流用于错误诊断如403 HTML响应
if self.response_chunks: if self.response_chunks:
# 正常情况成功解析的SSE JSON响应 # 正常情况成功解析的SSE JSON响应
stored_chunks = len(self.response_chunks)
total_chunks = self.response_chunks_count
metadata = {
"stream": True,
"total_chunks": total_chunks,
"stored_chunks": stored_chunks,
"content_length": len(self.accumulated_content),
"response_time_ms": response_time_ms,
}
if stored_chunks < total_chunks:
metadata["truncated"] = True
metadata["dropped_chunks"] = total_chunks - stored_chunks
response_body = { response_body = {
"chunks": self.response_chunks, "chunks": self.response_chunks,
"metadata": { "metadata": metadata,
"stream": True,
"total_chunks": len(self.response_chunks),
"content_length": len(self.accumulated_content),
"response_time_ms": response_time_ms,
},
} }
else: else:
# 错误情况无法解析为JSON如HTML错误页面 # 错误情况无法解析为JSON如HTML错误页面