auto-coder 0.1.397__py3-none-any.whl → 0.1.398__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.
Potentially problematic release.
This version of auto-coder might be problematic. Click here for more details.
- {auto_coder-0.1.397.dist-info → auto_coder-0.1.398.dist-info}/METADATA +2 -2
- {auto_coder-0.1.397.dist-info → auto_coder-0.1.398.dist-info}/RECORD +30 -11
- autocoder/auto_coder_rag.py +1 -0
- autocoder/chat_auto_coder.py +3 -0
- autocoder/common/conversations/__init__.py +84 -39
- autocoder/common/conversations/backup/__init__.py +14 -0
- autocoder/common/conversations/backup/backup_manager.py +564 -0
- autocoder/common/conversations/backup/restore_manager.py +546 -0
- autocoder/common/conversations/cache/__init__.py +16 -0
- autocoder/common/conversations/cache/base_cache.py +89 -0
- autocoder/common/conversations/cache/cache_manager.py +368 -0
- autocoder/common/conversations/cache/memory_cache.py +224 -0
- autocoder/common/conversations/config.py +195 -0
- autocoder/common/conversations/exceptions.py +72 -0
- autocoder/common/conversations/file_locker.py +145 -0
- autocoder/common/conversations/manager.py +917 -0
- autocoder/common/conversations/models.py +154 -0
- autocoder/common/conversations/search/__init__.py +15 -0
- autocoder/common/conversations/search/filter_manager.py +431 -0
- autocoder/common/conversations/search/text_searcher.py +366 -0
- autocoder/common/conversations/storage/__init__.py +16 -0
- autocoder/common/conversations/storage/base_storage.py +82 -0
- autocoder/common/conversations/storage/file_storage.py +267 -0
- autocoder/common/conversations/storage/index_manager.py +317 -0
- autocoder/rags.py +73 -23
- autocoder/version.py +1 -1
- {auto_coder-0.1.397.dist-info → auto_coder-0.1.398.dist-info}/LICENSE +0 -0
- {auto_coder-0.1.397.dist-info → auto_coder-0.1.398.dist-info}/WHEEL +0 -0
- {auto_coder-0.1.397.dist-info → auto_coder-0.1.398.dist-info}/entry_points.txt +0 -0
- {auto_coder-0.1.397.dist-info → auto_coder-0.1.398.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PersistConversationManager 配置类定义
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import json
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from typing import Optional, Dict, Any
|
|
9
|
+
from copy import deepcopy
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class ConversationManagerConfig:
|
|
14
|
+
"""对话管理器配置类"""
|
|
15
|
+
|
|
16
|
+
storage_path: str = "./.auto-coder/conversations"
|
|
17
|
+
max_cache_size: int = 100
|
|
18
|
+
cache_ttl: float = 300.0
|
|
19
|
+
lock_timeout: float = 10.0
|
|
20
|
+
backup_enabled: bool = True
|
|
21
|
+
backup_interval: float = 3600.0
|
|
22
|
+
max_backups: int = 10
|
|
23
|
+
enable_compression: bool = False
|
|
24
|
+
log_level: str = "INFO"
|
|
25
|
+
|
|
26
|
+
def __post_init__(self):
|
|
27
|
+
"""配置验证"""
|
|
28
|
+
self._validate()
|
|
29
|
+
|
|
30
|
+
def _validate(self):
|
|
31
|
+
"""验证配置数据"""
|
|
32
|
+
# 验证存储路径
|
|
33
|
+
if not self.storage_path or not isinstance(self.storage_path, str):
|
|
34
|
+
raise ValueError("存储路径不能为空")
|
|
35
|
+
|
|
36
|
+
# 验证缓存大小
|
|
37
|
+
if not isinstance(self.max_cache_size, int) or self.max_cache_size <= 0:
|
|
38
|
+
raise ValueError("缓存大小必须是正整数")
|
|
39
|
+
|
|
40
|
+
# 验证缓存TTL
|
|
41
|
+
if not isinstance(self.cache_ttl, (int, float)) or self.cache_ttl <= 0:
|
|
42
|
+
raise ValueError("缓存TTL必须是正数")
|
|
43
|
+
|
|
44
|
+
# 验证锁超时
|
|
45
|
+
if not isinstance(self.lock_timeout, (int, float)) or self.lock_timeout <= 0:
|
|
46
|
+
raise ValueError("锁超时时间必须是正数")
|
|
47
|
+
|
|
48
|
+
# 验证备份间隔
|
|
49
|
+
if not isinstance(self.backup_interval, (int, float)) or self.backup_interval <= 0:
|
|
50
|
+
raise ValueError("备份间隔必须是正数")
|
|
51
|
+
|
|
52
|
+
# 验证最大备份数
|
|
53
|
+
if not isinstance(self.max_backups, int) or self.max_backups <= 0:
|
|
54
|
+
raise ValueError("最大备份数必须是正整数")
|
|
55
|
+
|
|
56
|
+
# 验证日志级别
|
|
57
|
+
valid_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
|
|
58
|
+
if self.log_level not in valid_levels:
|
|
59
|
+
raise ValueError(f"无效的日志级别: {self.log_level},有效级别: {valid_levels}")
|
|
60
|
+
|
|
61
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
62
|
+
"""转换为字典"""
|
|
63
|
+
return {
|
|
64
|
+
"storage_path": self.storage_path,
|
|
65
|
+
"max_cache_size": self.max_cache_size,
|
|
66
|
+
"cache_ttl": self.cache_ttl,
|
|
67
|
+
"lock_timeout": self.lock_timeout,
|
|
68
|
+
"backup_enabled": self.backup_enabled,
|
|
69
|
+
"backup_interval": self.backup_interval,
|
|
70
|
+
"max_backups": self.max_backups,
|
|
71
|
+
"enable_compression": self.enable_compression,
|
|
72
|
+
"log_level": self.log_level
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
@classmethod
|
|
76
|
+
def from_dict(cls, data: Dict[str, Any]) -> "ConversationManagerConfig":
|
|
77
|
+
"""从字典创建配置"""
|
|
78
|
+
# 创建默认配置
|
|
79
|
+
config = cls()
|
|
80
|
+
|
|
81
|
+
# 更新配置字段
|
|
82
|
+
for key, value in data.items():
|
|
83
|
+
if hasattr(config, key):
|
|
84
|
+
setattr(config, key, value)
|
|
85
|
+
|
|
86
|
+
# 重新验证
|
|
87
|
+
config._validate()
|
|
88
|
+
|
|
89
|
+
return config
|
|
90
|
+
|
|
91
|
+
@classmethod
|
|
92
|
+
def from_env(cls, prefix: str = "CONVERSATION_") -> "ConversationManagerConfig":
|
|
93
|
+
"""从环境变量创建配置"""
|
|
94
|
+
config = cls()
|
|
95
|
+
|
|
96
|
+
# 环境变量映射
|
|
97
|
+
env_mapping = {
|
|
98
|
+
f"{prefix}STORAGE_PATH": "storage_path",
|
|
99
|
+
f"{prefix}MAX_CACHE_SIZE": "max_cache_size",
|
|
100
|
+
f"{prefix}CACHE_TTL": "cache_ttl",
|
|
101
|
+
f"{prefix}LOCK_TIMEOUT": "lock_timeout",
|
|
102
|
+
f"{prefix}BACKUP_ENABLED": "backup_enabled",
|
|
103
|
+
f"{prefix}BACKUP_INTERVAL": "backup_interval",
|
|
104
|
+
f"{prefix}MAX_BACKUPS": "max_backups",
|
|
105
|
+
f"{prefix}ENABLE_COMPRESSION": "enable_compression",
|
|
106
|
+
f"{prefix}LOG_LEVEL": "log_level"
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
for env_key, attr_name in env_mapping.items():
|
|
110
|
+
env_value = os.environ.get(env_key)
|
|
111
|
+
if env_value is not None:
|
|
112
|
+
# 类型转换
|
|
113
|
+
try:
|
|
114
|
+
if attr_name in ["max_cache_size", "max_backups"]:
|
|
115
|
+
value = int(env_value)
|
|
116
|
+
elif attr_name in ["cache_ttl", "lock_timeout", "backup_interval"]:
|
|
117
|
+
value = float(env_value)
|
|
118
|
+
elif attr_name in ["backup_enabled", "enable_compression"]:
|
|
119
|
+
value = env_value.lower() in ["true", "1", "yes", "on"]
|
|
120
|
+
else:
|
|
121
|
+
value = env_value
|
|
122
|
+
|
|
123
|
+
setattr(config, attr_name, value)
|
|
124
|
+
except (ValueError, TypeError) as e:
|
|
125
|
+
raise ValueError(f"环境变量 {env_key} 的值 '{env_value}' 无效: {e}")
|
|
126
|
+
|
|
127
|
+
# 重新验证
|
|
128
|
+
config._validate()
|
|
129
|
+
|
|
130
|
+
return config
|
|
131
|
+
|
|
132
|
+
def save_to_file(self, file_path: str):
|
|
133
|
+
"""保存配置到文件"""
|
|
134
|
+
# 确保目录存在
|
|
135
|
+
os.makedirs(os.path.dirname(file_path), exist_ok=True)
|
|
136
|
+
|
|
137
|
+
with open(file_path, 'w', encoding='utf-8') as f:
|
|
138
|
+
json.dump(self.to_dict(), f, indent=2, ensure_ascii=False)
|
|
139
|
+
|
|
140
|
+
@classmethod
|
|
141
|
+
def load_from_file(cls, file_path: str) -> "ConversationManagerConfig":
|
|
142
|
+
"""从文件加载配置"""
|
|
143
|
+
if not os.path.exists(file_path):
|
|
144
|
+
raise FileNotFoundError(f"配置文件不存在: {file_path}")
|
|
145
|
+
|
|
146
|
+
try:
|
|
147
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
148
|
+
data = json.load(f)
|
|
149
|
+
|
|
150
|
+
return cls.from_dict(data)
|
|
151
|
+
except json.JSONDecodeError as e:
|
|
152
|
+
raise ValueError(f"配置文件格式错误: {e}")
|
|
153
|
+
|
|
154
|
+
def copy(self) -> "ConversationManagerConfig":
|
|
155
|
+
"""创建配置的深拷贝"""
|
|
156
|
+
return ConversationManagerConfig.from_dict(self.to_dict())
|
|
157
|
+
|
|
158
|
+
def update(self, **kwargs):
|
|
159
|
+
"""更新配置字段"""
|
|
160
|
+
# 先备份当前配置
|
|
161
|
+
backup_values = {}
|
|
162
|
+
for key in kwargs.keys():
|
|
163
|
+
if hasattr(self, key):
|
|
164
|
+
backup_values[key] = getattr(self, key)
|
|
165
|
+
else:
|
|
166
|
+
raise AttributeError(f"配置类没有属性: {key}")
|
|
167
|
+
|
|
168
|
+
# 尝试更新
|
|
169
|
+
try:
|
|
170
|
+
for key, value in kwargs.items():
|
|
171
|
+
setattr(self, key, value)
|
|
172
|
+
# 重新验证
|
|
173
|
+
self._validate()
|
|
174
|
+
except Exception:
|
|
175
|
+
# 如果验证失败,恢复原值
|
|
176
|
+
for key, value in backup_values.items():
|
|
177
|
+
setattr(self, key, value)
|
|
178
|
+
raise
|
|
179
|
+
|
|
180
|
+
def __repr__(self) -> str:
|
|
181
|
+
"""字符串表示"""
|
|
182
|
+
return (f"ConversationManagerConfig("
|
|
183
|
+
f"storage_path='{self.storage_path}', "
|
|
184
|
+
f"max_cache_size={self.max_cache_size}, "
|
|
185
|
+
f"cache_ttl={self.cache_ttl}, "
|
|
186
|
+
f"lock_timeout={self.lock_timeout}, "
|
|
187
|
+
f"backup_enabled={self.backup_enabled}, "
|
|
188
|
+
f"log_level='{self.log_level}')")
|
|
189
|
+
|
|
190
|
+
def __eq__(self, other) -> bool:
|
|
191
|
+
"""相等性比较"""
|
|
192
|
+
if not isinstance(other, ConversationManagerConfig):
|
|
193
|
+
return False
|
|
194
|
+
|
|
195
|
+
return self.to_dict() == other.to_dict()
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PersistConversationManager 异常类定义
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ConversationManagerError(Exception):
|
|
7
|
+
"""对话管理器基础异常类"""
|
|
8
|
+
|
|
9
|
+
def __init__(self, message="对话管理器发生错误", error_code="GENERAL_ERROR"):
|
|
10
|
+
super().__init__(message)
|
|
11
|
+
self.error_code = error_code
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ConversationNotFoundError(ConversationManagerError):
|
|
15
|
+
"""对话不存在异常"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, conversation_id):
|
|
18
|
+
# 检查是否是类似ID的字符串(不包含中文等)
|
|
19
|
+
if (isinstance(conversation_id, str) and len(conversation_id) > 0 and
|
|
20
|
+
not any(ord(c) > 127 or c.isspace() for c in conversation_id)):
|
|
21
|
+
message = f"对话未找到: {conversation_id}"
|
|
22
|
+
else:
|
|
23
|
+
message = conversation_id # 自定义消息
|
|
24
|
+
super().__init__(message, error_code="CONVERSATION_NOT_FOUND")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class MessageNotFoundError(ConversationManagerError):
|
|
28
|
+
"""消息不存在异常"""
|
|
29
|
+
|
|
30
|
+
def __init__(self, message_id):
|
|
31
|
+
# 检查是否是类似ID的字符串(不包含中文等)
|
|
32
|
+
if (isinstance(message_id, str) and len(message_id) > 0 and
|
|
33
|
+
not any(ord(c) > 127 or c.isspace() for c in message_id)):
|
|
34
|
+
message = f"消息未找到: {message_id}"
|
|
35
|
+
else:
|
|
36
|
+
message = message_id # 自定义消息
|
|
37
|
+
super().__init__(message, error_code="MESSAGE_NOT_FOUND")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class ConcurrencyError(ConversationManagerError):
|
|
41
|
+
"""并发访问异常"""
|
|
42
|
+
|
|
43
|
+
def __init__(self, message="并发访问冲突"):
|
|
44
|
+
super().__init__(message, error_code="CONCURRENCY_ERROR")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class DataIntegrityError(ConversationManagerError):
|
|
48
|
+
"""数据完整性异常"""
|
|
49
|
+
|
|
50
|
+
def __init__(self, message="数据完整性检查失败"):
|
|
51
|
+
super().__init__(message, error_code="DATA_INTEGRITY_ERROR")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class LockTimeoutError(ConversationManagerError):
|
|
55
|
+
"""锁超时异常"""
|
|
56
|
+
|
|
57
|
+
def __init__(self, message="锁获取超时"):
|
|
58
|
+
super().__init__(message, error_code="LOCK_TIMEOUT_ERROR")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class BackupError(ConversationManagerError):
|
|
62
|
+
"""备份操作异常"""
|
|
63
|
+
|
|
64
|
+
def __init__(self, message="备份操作失败"):
|
|
65
|
+
super().__init__(message, error_code="BACKUP_ERROR")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class RestoreError(ConversationManagerError):
|
|
69
|
+
"""恢复操作异常"""
|
|
70
|
+
|
|
71
|
+
def __init__(self, message="恢复操作失败"):
|
|
72
|
+
super().__init__(message, error_code="RESTORE_ERROR")
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PersistConversationManager 跨平台文件锁实现
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
import time
|
|
8
|
+
import contextlib
|
|
9
|
+
from typing import Generator
|
|
10
|
+
from .exceptions import LockTimeoutError
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# 跨平台文件锁实现
|
|
14
|
+
if sys.platform == "win32":
|
|
15
|
+
import msvcrt
|
|
16
|
+
|
|
17
|
+
class FileLocker:
|
|
18
|
+
"""Windows 平台文件锁实现"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, lock_file: str, timeout: float = 10.0):
|
|
21
|
+
self.lock_file = lock_file
|
|
22
|
+
self.timeout = timeout
|
|
23
|
+
self.lock_fd = None
|
|
24
|
+
|
|
25
|
+
@contextlib.contextmanager
|
|
26
|
+
def acquire_read_lock(self) -> Generator[None, None, None]:
|
|
27
|
+
"""获取读锁(共享锁)- Windows 实现"""
|
|
28
|
+
self._acquire_lock(shared=True)
|
|
29
|
+
try:
|
|
30
|
+
yield
|
|
31
|
+
finally:
|
|
32
|
+
self._release_lock()
|
|
33
|
+
|
|
34
|
+
@contextlib.contextmanager
|
|
35
|
+
def acquire_write_lock(self) -> Generator[None, None, None]:
|
|
36
|
+
"""获取写锁(排他锁)- Windows 实现"""
|
|
37
|
+
self._acquire_lock(shared=False)
|
|
38
|
+
try:
|
|
39
|
+
yield
|
|
40
|
+
finally:
|
|
41
|
+
self._release_lock()
|
|
42
|
+
|
|
43
|
+
def _acquire_lock(self, shared: bool = False):
|
|
44
|
+
"""Windows 文件锁实现"""
|
|
45
|
+
start_time = time.time()
|
|
46
|
+
while True:
|
|
47
|
+
try:
|
|
48
|
+
# 确保锁文件目录存在
|
|
49
|
+
os.makedirs(os.path.dirname(self.lock_file), exist_ok=True)
|
|
50
|
+
|
|
51
|
+
# 打开文件用于锁定
|
|
52
|
+
self.lock_fd = open(self.lock_file, 'w+')
|
|
53
|
+
|
|
54
|
+
# Windows 下使用 msvcrt.locking
|
|
55
|
+
# 注意:Windows 不直接支持共享锁,这里简化处理
|
|
56
|
+
msvcrt.locking(self.lock_fd.fileno(), msvcrt.LK_NBLCK, 1)
|
|
57
|
+
return
|
|
58
|
+
|
|
59
|
+
except (IOError, OSError):
|
|
60
|
+
if self.lock_fd:
|
|
61
|
+
self.lock_fd.close()
|
|
62
|
+
self.lock_fd = None
|
|
63
|
+
|
|
64
|
+
if time.time() - start_time > self.timeout:
|
|
65
|
+
raise LockTimeoutError(f"Failed to acquire lock on {self.lock_file} within {self.timeout}s")
|
|
66
|
+
|
|
67
|
+
time.sleep(0.1)
|
|
68
|
+
|
|
69
|
+
def _release_lock(self):
|
|
70
|
+
"""释放锁"""
|
|
71
|
+
if self.lock_fd:
|
|
72
|
+
try:
|
|
73
|
+
msvcrt.locking(self.lock_fd.fileno(), msvcrt.LK_UNLCK, 1)
|
|
74
|
+
self.lock_fd.close()
|
|
75
|
+
except:
|
|
76
|
+
# 忽略释放时的错误
|
|
77
|
+
pass
|
|
78
|
+
finally:
|
|
79
|
+
self.lock_fd = None
|
|
80
|
+
|
|
81
|
+
else:
|
|
82
|
+
# Unix/Linux/Mac 系统使用 fcntl
|
|
83
|
+
import fcntl
|
|
84
|
+
|
|
85
|
+
class FileLocker:
|
|
86
|
+
"""Unix/Linux/Mac 平台文件锁实现"""
|
|
87
|
+
|
|
88
|
+
def __init__(self, lock_file: str, timeout: float = 10.0):
|
|
89
|
+
self.lock_file = lock_file
|
|
90
|
+
self.timeout = timeout
|
|
91
|
+
self.lock_fd = None
|
|
92
|
+
|
|
93
|
+
@contextlib.contextmanager
|
|
94
|
+
def acquire_read_lock(self) -> Generator[None, None, None]:
|
|
95
|
+
"""获取读锁(共享锁)- Unix/Linux/Mac 实现"""
|
|
96
|
+
self._acquire_lock(fcntl.LOCK_SH)
|
|
97
|
+
try:
|
|
98
|
+
yield
|
|
99
|
+
finally:
|
|
100
|
+
self._release_lock()
|
|
101
|
+
|
|
102
|
+
@contextlib.contextmanager
|
|
103
|
+
def acquire_write_lock(self) -> Generator[None, None, None]:
|
|
104
|
+
"""获取写锁(排他锁)- Unix/Linux/Mac 实现"""
|
|
105
|
+
self._acquire_lock(fcntl.LOCK_EX)
|
|
106
|
+
try:
|
|
107
|
+
yield
|
|
108
|
+
finally:
|
|
109
|
+
self._release_lock()
|
|
110
|
+
|
|
111
|
+
def _acquire_lock(self, lock_type: int):
|
|
112
|
+
"""Unix/Linux/Mac 文件锁实现"""
|
|
113
|
+
start_time = time.time()
|
|
114
|
+
|
|
115
|
+
# 确保锁文件目录存在
|
|
116
|
+
os.makedirs(os.path.dirname(self.lock_file), exist_ok=True)
|
|
117
|
+
|
|
118
|
+
# 打开锁文件
|
|
119
|
+
self.lock_fd = open(self.lock_file, 'w+')
|
|
120
|
+
|
|
121
|
+
while True:
|
|
122
|
+
try:
|
|
123
|
+
# 尝试获取非阻塞锁
|
|
124
|
+
fcntl.flock(self.lock_fd.fileno(), lock_type | fcntl.LOCK_NB)
|
|
125
|
+
return
|
|
126
|
+
|
|
127
|
+
except (IOError, OSError):
|
|
128
|
+
if time.time() - start_time > self.timeout:
|
|
129
|
+
self.lock_fd.close()
|
|
130
|
+
self.lock_fd = None
|
|
131
|
+
raise LockTimeoutError(f"Failed to acquire lock on {self.lock_file} within {self.timeout}s")
|
|
132
|
+
|
|
133
|
+
time.sleep(0.1)
|
|
134
|
+
|
|
135
|
+
def _release_lock(self):
|
|
136
|
+
"""释放锁"""
|
|
137
|
+
if self.lock_fd:
|
|
138
|
+
try:
|
|
139
|
+
fcntl.flock(self.lock_fd.fileno(), fcntl.LOCK_UN)
|
|
140
|
+
self.lock_fd.close()
|
|
141
|
+
except:
|
|
142
|
+
# 忽略释放时的错误
|
|
143
|
+
pass
|
|
144
|
+
finally:
|
|
145
|
+
self.lock_fd = None
|