agent-Specialization/utils/conversation_manager/token_mixin.py
JOJO f661dc6456 refactor(server,modules,utils,frontend): split oversized modules into packages and fix regressions
- Split server/chat.py, server/status.py, server/tasks.py into sub-packages
- Split utils/context_manager.py, utils/conversation_manager.py into mixin packages
- Split modules/file_manager.py, persistent_terminal.py, terminal_ops.py, mcp_client_manager.py into packages
- Split core/main_terminal_parts/context.py into base + mixins
- Split static/src/app/methods/{ui,message,upload,taskPolling,conversation} into sub-modules
- Fix flattened method exports, dynamic import paths, missing cross-module imports
- Add missing permission/network/execution mode constants and _PERMISSION_MODE_LABEL
- Update AGENTS.md, CLAUDE.md and project memories to reflect new structure
2026-06-20 15:54:48 +08:00

135 lines
4.9 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

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.

# utils/conversation_manager.py - 对话持久化管理器集成Token统计
import json
import os
import time
import tempfile
import threading
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Optional, Any
from dataclasses import dataclass
try:
from config import DATA_DIR, HOST_WORKSPACES_FILE
except ImportError:
import sys
from pathlib import Path
project_root = Path(__file__).resolve().parents[1]
if str(project_root) not in sys.path:
sys.path.insert(0, str(project_root))
from config import DATA_DIR, HOST_WORKSPACES_FILE
@dataclass
class ConversationMetadata:
"""对话元数据"""
id: str
title: str
created_at: str
updated_at: str
project_path: Optional[str]
project_relative_path: Optional[str]
thinking_mode: bool
total_messages: int
total_tools: int
run_mode: str = "fast"
model_key: Optional[str] = None
has_images: bool = False
has_videos: bool = False
status: str = "active" # active, archived, error
class TokenMixin:
"""ConversationManager token mixin 能力 mixin。"""
def update_token_statistics(
self,
conversation_id: str,
input_tokens: int,
output_tokens: int,
total_tokens: int,
current_context_tokens: Optional[int] = None,
) -> bool:
"""
更新对话的Token统计
Args:
conversation_id: 对话ID
input_tokens: 输入Token数量
output_tokens: 输出Token数量
total_tokens: 本次请求的总Token数量prompt+completion
current_context_tokens: 当前上下文长度(用于压缩阈值判断)
Returns:
bool: 更新是否成功
"""
try:
conversation_data = self.load_conversation(conversation_id)
if not conversation_data:
print(f"⚠️ 无法找到对话 {conversation_id}跳过Token统计")
return False
# 确保Token统计结构存在
if "token_statistics" not in conversation_data:
conversation_data["token_statistics"] = self._initialize_token_statistics()
# 更新统计数据
token_stats = conversation_data["token_statistics"]
token_stats["total_input_tokens"] = token_stats.get("total_input_tokens", 0) + input_tokens
token_stats["total_output_tokens"] = token_stats.get("total_output_tokens", 0) + output_tokens
token_stats["total_tokens"] = token_stats.get("total_tokens", 0) + total_tokens
if current_context_tokens is None:
# 兼容旧调用:未显式传入时,默认以输入 token 作为当前上下文长度
current_context_tokens = input_tokens
token_stats["current_context_tokens"] = max(0, int(current_context_tokens or 0))
token_stats["updated_at"] = datetime.now().isoformat()
# 保存更新
self._save_conversation_file(conversation_id, conversation_data)
print(f"📊 Token统计已更新: +{input_tokens}输入, +{output_tokens}输出 "
f"(总计: {token_stats['total_input_tokens']}输入, {token_stats['total_output_tokens']}输出)")
return True
except Exception as e:
print(f"⌘ 更新Token统计失败 {conversation_id}: {e}")
return False
def get_token_statistics(self, conversation_id: str) -> Optional[Dict]:
"""
获取对话的Token统计
Args:
conversation_id: 对话ID
Returns:
Dict: Token统计数据
"""
try:
conversation_data = self.load_conversation(conversation_id)
if not conversation_data:
return None
validated = self._validate_token_statistics(conversation_data)
token_stats = validated.get("token_statistics", {})
result = {
"total_input_tokens": token_stats.get("total_input_tokens", 0),
"total_output_tokens": token_stats.get("total_output_tokens", 0),
"total_tokens": token_stats.get("total_tokens", 0),
"current_context_tokens": token_stats.get("current_context_tokens", 0),
"updated_at": token_stats.get("updated_at"),
"conversation_id": conversation_id
}
return result
except Exception as e:
print(f"⌘ 获取Token统计失败 {conversation_id}: {e}")
return None
def get_current_context_tokens(self, conversation_id: str) -> int:
"""获取最近一次请求的上下文token"""
stats = self.get_token_statistics(conversation_id)
if not stats:
return 0
return stats.get("current_context_tokens", 0)