auto-coder 0.1.397__py3-none-any.whl → 0.1.399__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.399.dist-info/METADATA +396 -0
- {auto_coder-0.1.397.dist-info → auto_coder-0.1.399.dist-info}/RECORD +81 -28
- {auto_coder-0.1.397.dist-info → auto_coder-0.1.399.dist-info}/WHEEL +1 -1
- {auto_coder-0.1.397.dist-info → auto_coder-0.1.399.dist-info}/entry_points.txt +2 -0
- autocoder/agent/base_agentic/base_agent.py +2 -2
- autocoder/agent/base_agentic/tools/replace_in_file_tool_resolver.py +1 -1
- autocoder/agent/entry_command_agent/__init__.py +29 -0
- autocoder/agent/entry_command_agent/auto_tool.py +61 -0
- autocoder/agent/entry_command_agent/chat.py +475 -0
- autocoder/agent/entry_command_agent/designer.py +53 -0
- autocoder/agent/entry_command_agent/generate_command.py +50 -0
- autocoder/agent/entry_command_agent/project_reader.py +58 -0
- autocoder/agent/entry_command_agent/voice2text.py +71 -0
- autocoder/auto_coder.py +23 -548
- autocoder/auto_coder_rag.py +1 -0
- autocoder/auto_coder_runner.py +510 -8
- autocoder/chat/rules_command.py +1 -1
- autocoder/chat_auto_coder.py +8 -0
- autocoder/common/ac_style_command_parser/__init__.py +15 -0
- autocoder/common/ac_style_command_parser/example.py +7 -0
- autocoder/{command_parser.py → common/ac_style_command_parser/parser.py} +1 -33
- autocoder/common/ac_style_command_parser/test_parser.py +516 -0
- autocoder/common/command_completer_v2.py +1 -1
- autocoder/common/command_file_manager/examples.py +22 -8
- autocoder/common/command_file_manager/manager.py +37 -6
- 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/get_conversation_manager.py +143 -0
- autocoder/common/conversations/manager.py +1028 -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 +406 -0
- autocoder/common/v2/agent/agentic_edit.py +131 -18
- autocoder/common/v2/agent/agentic_edit_types.py +10 -0
- autocoder/common/v2/code_auto_generate_editblock.py +10 -2
- autocoder/dispacher/__init__.py +10 -0
- autocoder/rags.py +73 -50
- autocoder/run_context.py +1 -0
- autocoder/sdk/__init__.py +188 -0
- autocoder/sdk/cli/__init__.py +15 -0
- autocoder/sdk/cli/__main__.py +26 -0
- autocoder/sdk/cli/completion_wrapper.py +38 -0
- autocoder/sdk/cli/formatters.py +211 -0
- autocoder/sdk/cli/handlers.py +174 -0
- autocoder/sdk/cli/install_completion.py +301 -0
- autocoder/sdk/cli/main.py +284 -0
- autocoder/sdk/cli/options.py +72 -0
- autocoder/sdk/constants.py +102 -0
- autocoder/sdk/core/__init__.py +20 -0
- autocoder/sdk/core/auto_coder_core.py +867 -0
- autocoder/sdk/core/bridge.py +497 -0
- autocoder/sdk/example.py +0 -0
- autocoder/sdk/exceptions.py +72 -0
- autocoder/sdk/models/__init__.py +19 -0
- autocoder/sdk/models/messages.py +209 -0
- autocoder/sdk/models/options.py +194 -0
- autocoder/sdk/models/responses.py +311 -0
- autocoder/sdk/session/__init__.py +32 -0
- autocoder/sdk/session/session.py +106 -0
- autocoder/sdk/session/session_manager.py +56 -0
- autocoder/sdk/utils/__init__.py +24 -0
- autocoder/sdk/utils/formatters.py +216 -0
- autocoder/sdk/utils/io_utils.py +302 -0
- autocoder/sdk/utils/validators.py +287 -0
- autocoder/version.py +2 -1
- auto_coder-0.1.397.dist-info/METADATA +0 -111
- autocoder/common/conversations/compatibility.py +0 -303
- autocoder/common/conversations/conversation_manager.py +0 -502
- autocoder/common/conversations/example.py +0 -152
- {auto_coder-0.1.397.dist-info → auto_coder-0.1.399.dist-info/licenses}/LICENSE +0 -0
- {auto_coder-0.1.397.dist-info → auto_coder-0.1.399.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PersistConversationManager 数据模型定义
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
import uuid
|
|
7
|
+
from typing import Union, Dict, List, Optional, Any
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class ConversationMessage:
|
|
13
|
+
"""对话消息数据模型"""
|
|
14
|
+
|
|
15
|
+
role: str
|
|
16
|
+
content: Union[str, dict, list]
|
|
17
|
+
timestamp: float = field(default_factory=time.time)
|
|
18
|
+
message_id: str = field(default_factory=lambda: str(uuid.uuid4()))
|
|
19
|
+
metadata: Optional[dict] = None
|
|
20
|
+
|
|
21
|
+
def __post_init__(self):
|
|
22
|
+
"""数据验证"""
|
|
23
|
+
self._validate()
|
|
24
|
+
|
|
25
|
+
def _validate(self):
|
|
26
|
+
"""验证消息数据"""
|
|
27
|
+
# 验证角色
|
|
28
|
+
valid_roles = ["system", "user", "assistant"]
|
|
29
|
+
if not self.role or self.role not in valid_roles:
|
|
30
|
+
raise ValueError(f"无效的消息角色: {self.role},有效角色: {valid_roles}")
|
|
31
|
+
|
|
32
|
+
# 验证内容
|
|
33
|
+
if self.content is None or (isinstance(self.content, str) and len(self.content) == 0):
|
|
34
|
+
raise ValueError("消息内容不能为空")
|
|
35
|
+
|
|
36
|
+
# 验证时间戳
|
|
37
|
+
if not isinstance(self.timestamp, (int, float)) or self.timestamp <= 0:
|
|
38
|
+
raise ValueError("无效的时间戳")
|
|
39
|
+
|
|
40
|
+
# 验证消息ID
|
|
41
|
+
if not isinstance(self.message_id, str) or len(self.message_id) == 0:
|
|
42
|
+
raise ValueError("消息ID不能为空")
|
|
43
|
+
|
|
44
|
+
def to_dict(self) -> dict:
|
|
45
|
+
"""序列化为字典"""
|
|
46
|
+
return {
|
|
47
|
+
"role": self.role,
|
|
48
|
+
"content": self.content,
|
|
49
|
+
"timestamp": self.timestamp,
|
|
50
|
+
"message_id": self.message_id,
|
|
51
|
+
"metadata": self.metadata
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
@classmethod
|
|
55
|
+
def from_dict(cls, data: dict) -> "ConversationMessage":
|
|
56
|
+
"""从字典反序列化"""
|
|
57
|
+
return cls(
|
|
58
|
+
role=data["role"],
|
|
59
|
+
content=data["content"],
|
|
60
|
+
timestamp=data["timestamp"],
|
|
61
|
+
message_id=data["message_id"],
|
|
62
|
+
metadata=data.get("metadata")
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass
|
|
67
|
+
class Conversation:
|
|
68
|
+
"""对话数据模型"""
|
|
69
|
+
|
|
70
|
+
name: str
|
|
71
|
+
conversation_id: str = field(default_factory=lambda: str(uuid.uuid4()))
|
|
72
|
+
description: Optional[str] = None
|
|
73
|
+
created_at: float = field(default_factory=time.time)
|
|
74
|
+
updated_at: float = field(default_factory=time.time)
|
|
75
|
+
messages: List[dict] = field(default_factory=list)
|
|
76
|
+
metadata: Optional[dict] = None
|
|
77
|
+
version: int = 1
|
|
78
|
+
|
|
79
|
+
def __post_init__(self):
|
|
80
|
+
"""数据验证"""
|
|
81
|
+
self._validate()
|
|
82
|
+
|
|
83
|
+
def _validate(self):
|
|
84
|
+
"""验证对话数据"""
|
|
85
|
+
# 验证名称
|
|
86
|
+
if not self.name or (isinstance(self.name, str) and len(self.name) == 0):
|
|
87
|
+
raise ValueError("对话名称不能为空")
|
|
88
|
+
|
|
89
|
+
# 验证版本号
|
|
90
|
+
if not isinstance(self.version, int) or self.version <= 0:
|
|
91
|
+
raise ValueError("版本号必须是正整数")
|
|
92
|
+
|
|
93
|
+
# 验证时间戳
|
|
94
|
+
if not isinstance(self.created_at, (int, float)) or self.created_at <= 0:
|
|
95
|
+
raise ValueError("无效的创建时间戳")
|
|
96
|
+
|
|
97
|
+
if not isinstance(self.updated_at, (int, float)) or self.updated_at < self.created_at:
|
|
98
|
+
raise ValueError("无效的更新时间戳")
|
|
99
|
+
|
|
100
|
+
# 验证对话ID
|
|
101
|
+
if not isinstance(self.conversation_id, str) or len(self.conversation_id) == 0:
|
|
102
|
+
raise ValueError("对话ID不能为空")
|
|
103
|
+
|
|
104
|
+
# 验证消息列表
|
|
105
|
+
if not isinstance(self.messages, list):
|
|
106
|
+
raise ValueError("消息列表必须是列表类型")
|
|
107
|
+
|
|
108
|
+
def add_message(self, message: ConversationMessage):
|
|
109
|
+
"""添加消息到对话"""
|
|
110
|
+
self.messages.append(message.to_dict())
|
|
111
|
+
self.updated_at = time.time()
|
|
112
|
+
|
|
113
|
+
def remove_message(self, message_id: str) -> bool:
|
|
114
|
+
"""从对话中删除消息"""
|
|
115
|
+
for i, msg in enumerate(self.messages):
|
|
116
|
+
if msg.get("message_id") == message_id:
|
|
117
|
+
del self.messages[i]
|
|
118
|
+
self.updated_at = time.time()
|
|
119
|
+
return True
|
|
120
|
+
return False
|
|
121
|
+
|
|
122
|
+
def get_message(self, message_id: str) -> Optional[dict]:
|
|
123
|
+
"""从对话中获取消息"""
|
|
124
|
+
for msg in self.messages:
|
|
125
|
+
if msg.get("message_id") == message_id:
|
|
126
|
+
return msg
|
|
127
|
+
return None
|
|
128
|
+
|
|
129
|
+
def to_dict(self) -> dict:
|
|
130
|
+
"""序列化为字典"""
|
|
131
|
+
return {
|
|
132
|
+
"conversation_id": self.conversation_id,
|
|
133
|
+
"name": self.name,
|
|
134
|
+
"description": self.description,
|
|
135
|
+
"created_at": self.created_at,
|
|
136
|
+
"updated_at": self.updated_at,
|
|
137
|
+
"messages": self.messages,
|
|
138
|
+
"metadata": self.metadata,
|
|
139
|
+
"version": self.version
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
@classmethod
|
|
143
|
+
def from_dict(cls, data: dict) -> "Conversation":
|
|
144
|
+
"""从字典反序列化"""
|
|
145
|
+
return cls(
|
|
146
|
+
conversation_id=data["conversation_id"],
|
|
147
|
+
name=data["name"],
|
|
148
|
+
description=data.get("description"),
|
|
149
|
+
created_at=data["created_at"],
|
|
150
|
+
updated_at=data["updated_at"],
|
|
151
|
+
messages=data.get("messages", []),
|
|
152
|
+
metadata=data.get("metadata"),
|
|
153
|
+
version=data.get("version", 1)
|
|
154
|
+
)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Search and filtering module for conversations.
|
|
3
|
+
|
|
4
|
+
This module provides text search and filtering capabilities for conversations
|
|
5
|
+
and messages, supporting full-text search, keyword matching, and complex
|
|
6
|
+
filtering operations.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from .text_searcher import TextSearcher
|
|
10
|
+
from .filter_manager import FilterManager
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
'TextSearcher',
|
|
14
|
+
'FilterManager'
|
|
15
|
+
]
|
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Filter management for conversations and messages.
|
|
3
|
+
|
|
4
|
+
This module provides comprehensive filtering capabilities including
|
|
5
|
+
time-based filters, content filters, and complex query combinations.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import re
|
|
9
|
+
from datetime import datetime, timedelta
|
|
10
|
+
from typing import List, Dict, Any, Optional, Union, Callable
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from enum import Enum
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class FilterOperator(Enum):
|
|
16
|
+
"""Enumeration of filter operators."""
|
|
17
|
+
EQUALS = "eq"
|
|
18
|
+
NOT_EQUALS = "ne"
|
|
19
|
+
GREATER_THAN = "gt"
|
|
20
|
+
GREATER_THAN_OR_EQUAL = "gte"
|
|
21
|
+
LESS_THAN = "lt"
|
|
22
|
+
LESS_THAN_OR_EQUAL = "lte"
|
|
23
|
+
CONTAINS = "contains"
|
|
24
|
+
NOT_CONTAINS = "not_contains"
|
|
25
|
+
STARTS_WITH = "starts_with"
|
|
26
|
+
ENDS_WITH = "ends_with"
|
|
27
|
+
REGEX = "regex"
|
|
28
|
+
IN = "in"
|
|
29
|
+
NOT_IN = "not_in"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class LogicalOperator(Enum):
|
|
33
|
+
"""Enumeration of logical operators for combining filters."""
|
|
34
|
+
AND = "and"
|
|
35
|
+
OR = "or"
|
|
36
|
+
NOT = "not"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class Filter:
|
|
41
|
+
"""Represents a single filter condition."""
|
|
42
|
+
field: str
|
|
43
|
+
operator: FilterOperator
|
|
44
|
+
value: Any
|
|
45
|
+
case_sensitive: bool = False
|
|
46
|
+
|
|
47
|
+
def apply(self, item: Dict[str, Any]) -> bool:
|
|
48
|
+
"""Apply this filter to an item."""
|
|
49
|
+
field_value = self._get_field_value(item, self.field)
|
|
50
|
+
|
|
51
|
+
if field_value is None:
|
|
52
|
+
return False
|
|
53
|
+
|
|
54
|
+
return self._compare_values(field_value, self.value, self.operator)
|
|
55
|
+
|
|
56
|
+
def _get_field_value(self, item: Dict[str, Any], field_path: str) -> Any:
|
|
57
|
+
"""Get field value supporting nested field access (e.g., 'metadata.tags')."""
|
|
58
|
+
value = item
|
|
59
|
+
|
|
60
|
+
for field_part in field_path.split('.'):
|
|
61
|
+
if isinstance(value, dict) and field_part in value:
|
|
62
|
+
value = value[field_part]
|
|
63
|
+
else:
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
return value
|
|
67
|
+
|
|
68
|
+
def _compare_values(self, field_value: Any, filter_value: Any, operator: FilterOperator) -> bool:
|
|
69
|
+
"""Compare field value with filter value using the specified operator."""
|
|
70
|
+
try:
|
|
71
|
+
if operator == FilterOperator.EQUALS:
|
|
72
|
+
return self._normalize_for_comparison(field_value) == self._normalize_for_comparison(filter_value)
|
|
73
|
+
|
|
74
|
+
elif operator == FilterOperator.NOT_EQUALS:
|
|
75
|
+
return self._normalize_for_comparison(field_value) != self._normalize_for_comparison(filter_value)
|
|
76
|
+
|
|
77
|
+
elif operator == FilterOperator.GREATER_THAN:
|
|
78
|
+
return field_value > filter_value
|
|
79
|
+
|
|
80
|
+
elif operator == FilterOperator.GREATER_THAN_OR_EQUAL:
|
|
81
|
+
return field_value >= filter_value
|
|
82
|
+
|
|
83
|
+
elif operator == FilterOperator.LESS_THAN:
|
|
84
|
+
return field_value < filter_value
|
|
85
|
+
|
|
86
|
+
elif operator == FilterOperator.LESS_THAN_OR_EQUAL:
|
|
87
|
+
return field_value <= filter_value
|
|
88
|
+
|
|
89
|
+
elif operator == FilterOperator.CONTAINS:
|
|
90
|
+
field_str = self._normalize_for_comparison(str(field_value))
|
|
91
|
+
filter_str = self._normalize_for_comparison(str(filter_value))
|
|
92
|
+
return filter_str in field_str
|
|
93
|
+
|
|
94
|
+
elif operator == FilterOperator.NOT_CONTAINS:
|
|
95
|
+
field_str = self._normalize_for_comparison(str(field_value))
|
|
96
|
+
filter_str = self._normalize_for_comparison(str(filter_value))
|
|
97
|
+
return filter_str not in field_str
|
|
98
|
+
|
|
99
|
+
elif operator == FilterOperator.STARTS_WITH:
|
|
100
|
+
field_str = self._normalize_for_comparison(str(field_value))
|
|
101
|
+
filter_str = self._normalize_for_comparison(str(filter_value))
|
|
102
|
+
return field_str.startswith(filter_str)
|
|
103
|
+
|
|
104
|
+
elif operator == FilterOperator.ENDS_WITH:
|
|
105
|
+
field_str = self._normalize_for_comparison(str(field_value))
|
|
106
|
+
filter_str = self._normalize_for_comparison(str(filter_value))
|
|
107
|
+
return field_str.endswith(filter_str)
|
|
108
|
+
|
|
109
|
+
elif operator == FilterOperator.REGEX:
|
|
110
|
+
field_str = str(field_value)
|
|
111
|
+
flags = 0 if self.case_sensitive else re.IGNORECASE
|
|
112
|
+
return bool(re.search(filter_value, field_str, flags))
|
|
113
|
+
|
|
114
|
+
elif operator == FilterOperator.IN:
|
|
115
|
+
if not isinstance(filter_value, (list, tuple, set)):
|
|
116
|
+
return False
|
|
117
|
+
return field_value in filter_value
|
|
118
|
+
|
|
119
|
+
elif operator == FilterOperator.NOT_IN:
|
|
120
|
+
if not isinstance(filter_value, (list, tuple, set)):
|
|
121
|
+
return True
|
|
122
|
+
return field_value not in filter_value
|
|
123
|
+
|
|
124
|
+
except (TypeError, ValueError, AttributeError):
|
|
125
|
+
return False
|
|
126
|
+
|
|
127
|
+
return False
|
|
128
|
+
|
|
129
|
+
def _normalize_for_comparison(self, value: Any) -> Any:
|
|
130
|
+
"""Normalize value for comparison based on case sensitivity."""
|
|
131
|
+
if isinstance(value, str) and not self.case_sensitive:
|
|
132
|
+
return value.lower()
|
|
133
|
+
return value
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@dataclass
|
|
137
|
+
class FilterGroup:
|
|
138
|
+
"""Represents a group of filters combined with logical operators."""
|
|
139
|
+
filters: List[Union[Filter, 'FilterGroup']]
|
|
140
|
+
operator: LogicalOperator = LogicalOperator.AND
|
|
141
|
+
|
|
142
|
+
def apply(self, item: Dict[str, Any]) -> bool:
|
|
143
|
+
"""Apply this filter group to an item."""
|
|
144
|
+
if not self.filters:
|
|
145
|
+
return True
|
|
146
|
+
|
|
147
|
+
if self.operator == LogicalOperator.AND:
|
|
148
|
+
return all(f.apply(item) for f in self.filters)
|
|
149
|
+
|
|
150
|
+
elif self.operator == LogicalOperator.OR:
|
|
151
|
+
return any(f.apply(item) for f in self.filters)
|
|
152
|
+
|
|
153
|
+
elif self.operator == LogicalOperator.NOT:
|
|
154
|
+
# For NOT operator, apply AND logic and negate the result
|
|
155
|
+
return not all(f.apply(item) for f in self.filters)
|
|
156
|
+
|
|
157
|
+
return True
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class FilterManager:
|
|
161
|
+
"""Manager for building and applying complex filters."""
|
|
162
|
+
|
|
163
|
+
def __init__(self):
|
|
164
|
+
"""Initialize filter manager."""
|
|
165
|
+
self.predefined_filters = {}
|
|
166
|
+
self._register_predefined_filters()
|
|
167
|
+
|
|
168
|
+
def _register_predefined_filters(self):
|
|
169
|
+
"""Register commonly used predefined filters."""
|
|
170
|
+
# Time-based filters
|
|
171
|
+
now = datetime.now().timestamp()
|
|
172
|
+
|
|
173
|
+
self.predefined_filters.update({
|
|
174
|
+
'today': Filter('created_at', FilterOperator.GREATER_THAN_OR_EQUAL,
|
|
175
|
+
now - 24 * 3600),
|
|
176
|
+
'this_week': Filter('created_at', FilterOperator.GREATER_THAN_OR_EQUAL,
|
|
177
|
+
now - 7 * 24 * 3600),
|
|
178
|
+
'this_month': Filter('created_at', FilterOperator.GREATER_THAN_OR_EQUAL,
|
|
179
|
+
now - 30 * 24 * 3600),
|
|
180
|
+
'has_messages': Filter('message_count', FilterOperator.GREATER_THAN, 0),
|
|
181
|
+
'no_messages': Filter('message_count', FilterOperator.EQUALS, 0),
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
def create_filter(
|
|
185
|
+
self,
|
|
186
|
+
field: str,
|
|
187
|
+
operator: Union[FilterOperator, str],
|
|
188
|
+
value: Any,
|
|
189
|
+
case_sensitive: bool = False
|
|
190
|
+
) -> Filter:
|
|
191
|
+
"""
|
|
192
|
+
Create a single filter.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
field: Field name to filter on
|
|
196
|
+
operator: Filter operator
|
|
197
|
+
value: Value to compare against
|
|
198
|
+
case_sensitive: Whether comparison should be case sensitive
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
Filter instance
|
|
202
|
+
"""
|
|
203
|
+
if isinstance(operator, str):
|
|
204
|
+
operator = FilterOperator(operator)
|
|
205
|
+
|
|
206
|
+
return Filter(field, operator, value, case_sensitive)
|
|
207
|
+
|
|
208
|
+
def create_filter_group(
|
|
209
|
+
self,
|
|
210
|
+
filters: List[Union[Filter, FilterGroup]],
|
|
211
|
+
operator: Union[LogicalOperator, str] = LogicalOperator.AND
|
|
212
|
+
) -> FilterGroup:
|
|
213
|
+
"""
|
|
214
|
+
Create a filter group.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
filters: List of filters or filter groups
|
|
218
|
+
operator: Logical operator to combine filters
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
FilterGroup instance
|
|
222
|
+
"""
|
|
223
|
+
if isinstance(operator, str):
|
|
224
|
+
operator = LogicalOperator(operator)
|
|
225
|
+
|
|
226
|
+
return FilterGroup(filters, operator)
|
|
227
|
+
|
|
228
|
+
def create_time_range_filter(
|
|
229
|
+
self,
|
|
230
|
+
field: str,
|
|
231
|
+
start_time: Optional[Union[datetime, float]] = None,
|
|
232
|
+
end_time: Optional[Union[datetime, float]] = None
|
|
233
|
+
) -> FilterGroup:
|
|
234
|
+
"""
|
|
235
|
+
Create a time range filter.
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
field: Time field name (e.g., 'created_at', 'updated_at')
|
|
239
|
+
start_time: Start of time range
|
|
240
|
+
end_time: End of time range
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
FilterGroup with time range filters
|
|
244
|
+
"""
|
|
245
|
+
filters = []
|
|
246
|
+
|
|
247
|
+
if start_time is not None:
|
|
248
|
+
if isinstance(start_time, datetime):
|
|
249
|
+
start_time = start_time.timestamp()
|
|
250
|
+
filters.append(Filter(field, FilterOperator.GREATER_THAN_OR_EQUAL, start_time))
|
|
251
|
+
|
|
252
|
+
if end_time is not None:
|
|
253
|
+
if isinstance(end_time, datetime):
|
|
254
|
+
end_time = end_time.timestamp()
|
|
255
|
+
filters.append(Filter(field, FilterOperator.LESS_THAN_OR_EQUAL, end_time))
|
|
256
|
+
|
|
257
|
+
return FilterGroup(filters, LogicalOperator.AND)
|
|
258
|
+
|
|
259
|
+
def create_text_search_filter(
|
|
260
|
+
self,
|
|
261
|
+
fields: List[str],
|
|
262
|
+
query: str,
|
|
263
|
+
case_sensitive: bool = False
|
|
264
|
+
) -> FilterGroup:
|
|
265
|
+
"""
|
|
266
|
+
Create a text search filter across multiple fields.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
fields: List of field names to search in
|
|
270
|
+
query: Search query
|
|
271
|
+
case_sensitive: Whether search should be case sensitive
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
FilterGroup with text search filters
|
|
275
|
+
"""
|
|
276
|
+
filters = []
|
|
277
|
+
|
|
278
|
+
for field in fields:
|
|
279
|
+
filters.append(Filter(field, FilterOperator.CONTAINS, query, case_sensitive))
|
|
280
|
+
|
|
281
|
+
return FilterGroup(filters, LogicalOperator.OR)
|
|
282
|
+
|
|
283
|
+
def create_role_filter(self, roles: List[str]) -> Filter:
|
|
284
|
+
"""Create a filter for message roles."""
|
|
285
|
+
return Filter('role', FilterOperator.IN, roles)
|
|
286
|
+
|
|
287
|
+
def create_content_type_filter(self, content_types: List[str]) -> FilterGroup:
|
|
288
|
+
"""
|
|
289
|
+
Create a filter for content types (string, dict, list).
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
content_types: List of content types ('string', 'dict', 'list')
|
|
293
|
+
|
|
294
|
+
Returns:
|
|
295
|
+
FilterGroup for content type filtering
|
|
296
|
+
"""
|
|
297
|
+
filters = []
|
|
298
|
+
|
|
299
|
+
for content_type in content_types:
|
|
300
|
+
if content_type == 'string':
|
|
301
|
+
# Filter for string content
|
|
302
|
+
filters.append(Filter('content', FilterOperator.REGEX, r'^[^{\[].*', case_sensitive=True))
|
|
303
|
+
elif content_type == 'dict':
|
|
304
|
+
# Filter for dict content (starts with {)
|
|
305
|
+
filters.append(Filter('content', FilterOperator.REGEX, r'^\{.*', case_sensitive=True))
|
|
306
|
+
elif content_type == 'list':
|
|
307
|
+
# Filter for list content (starts with [)
|
|
308
|
+
filters.append(Filter('content', FilterOperator.REGEX, r'^\[.*', case_sensitive=True))
|
|
309
|
+
|
|
310
|
+
return FilterGroup(filters, LogicalOperator.OR)
|
|
311
|
+
|
|
312
|
+
def apply_filters(
|
|
313
|
+
self,
|
|
314
|
+
items: List[Dict[str, Any]],
|
|
315
|
+
filter_spec: Union[Filter, FilterGroup, str, Dict[str, Any]]
|
|
316
|
+
) -> List[Dict[str, Any]]:
|
|
317
|
+
"""
|
|
318
|
+
Apply filters to a list of items.
|
|
319
|
+
|
|
320
|
+
Args:
|
|
321
|
+
items: List of items to filter
|
|
322
|
+
filter_spec: Filter specification (Filter, FilterGroup, predefined name, or dict)
|
|
323
|
+
|
|
324
|
+
Returns:
|
|
325
|
+
Filtered list of items
|
|
326
|
+
"""
|
|
327
|
+
if not items:
|
|
328
|
+
return []
|
|
329
|
+
|
|
330
|
+
# Convert filter_spec to Filter or FilterGroup
|
|
331
|
+
filter_obj = self._parse_filter_spec(filter_spec)
|
|
332
|
+
|
|
333
|
+
if filter_obj is None:
|
|
334
|
+
return items
|
|
335
|
+
|
|
336
|
+
# Apply filter
|
|
337
|
+
return [item for item in items if filter_obj.apply(item)]
|
|
338
|
+
|
|
339
|
+
def _parse_filter_spec(self, filter_spec: Union[Filter, FilterGroup, str, Dict[str, Any]]) -> Optional[Union[Filter, FilterGroup]]:
|
|
340
|
+
"""Parse filter specification into Filter or FilterGroup."""
|
|
341
|
+
if isinstance(filter_spec, (Filter, FilterGroup)):
|
|
342
|
+
return filter_spec
|
|
343
|
+
|
|
344
|
+
elif isinstance(filter_spec, str):
|
|
345
|
+
# Predefined filter name
|
|
346
|
+
return self.predefined_filters.get(filter_spec)
|
|
347
|
+
|
|
348
|
+
elif isinstance(filter_spec, dict):
|
|
349
|
+
# Dictionary specification
|
|
350
|
+
return self._parse_dict_filter(filter_spec)
|
|
351
|
+
|
|
352
|
+
return None
|
|
353
|
+
|
|
354
|
+
def _parse_dict_filter(self, filter_dict: Dict[str, Any]) -> Optional[Union[Filter, FilterGroup]]:
|
|
355
|
+
"""Parse dictionary filter specification."""
|
|
356
|
+
if 'filters' in filter_dict:
|
|
357
|
+
# FilterGroup specification
|
|
358
|
+
filters = []
|
|
359
|
+
for f in filter_dict['filters']:
|
|
360
|
+
parsed_filter = self._parse_filter_spec(f)
|
|
361
|
+
if parsed_filter:
|
|
362
|
+
filters.append(parsed_filter)
|
|
363
|
+
|
|
364
|
+
operator = LogicalOperator(filter_dict.get('operator', 'and'))
|
|
365
|
+
return FilterGroup(filters, operator)
|
|
366
|
+
|
|
367
|
+
elif 'field' in filter_dict and 'operator' in filter_dict and 'value' in filter_dict:
|
|
368
|
+
# Single Filter specification
|
|
369
|
+
return Filter(
|
|
370
|
+
field=filter_dict['field'],
|
|
371
|
+
operator=FilterOperator(filter_dict['operator']),
|
|
372
|
+
value=filter_dict['value'],
|
|
373
|
+
case_sensitive=filter_dict.get('case_sensitive', False)
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
return None
|
|
377
|
+
|
|
378
|
+
def create_conversation_filters(self) -> Dict[str, Union[Filter, FilterGroup]]:
|
|
379
|
+
"""Create common conversation filters."""
|
|
380
|
+
return {
|
|
381
|
+
'active': Filter('message_count', FilterOperator.GREATER_THAN, 0),
|
|
382
|
+
'empty': Filter('message_count', FilterOperator.EQUALS, 0),
|
|
383
|
+
'recent': Filter('updated_at', FilterOperator.GREATER_THAN_OR_EQUAL,
|
|
384
|
+
datetime.now().timestamp() - 7 * 24 * 3600),
|
|
385
|
+
'old': Filter('updated_at', FilterOperator.LESS_THAN,
|
|
386
|
+
datetime.now().timestamp() - 30 * 24 * 3600),
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
def create_message_filters(self) -> Dict[str, Union[Filter, FilterGroup]]:
|
|
390
|
+
"""Create common message filters."""
|
|
391
|
+
return {
|
|
392
|
+
'user_messages': Filter('role', FilterOperator.EQUALS, 'user'),
|
|
393
|
+
'assistant_messages': Filter('role', FilterOperator.EQUALS, 'assistant'),
|
|
394
|
+
'system_messages': Filter('role', FilterOperator.EQUALS, 'system'),
|
|
395
|
+
'recent_messages': Filter('timestamp', FilterOperator.GREATER_THAN_OR_EQUAL,
|
|
396
|
+
datetime.now().timestamp() - 24 * 3600),
|
|
397
|
+
'has_metadata': Filter('metadata', FilterOperator.NOT_EQUALS, None),
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
def combine_filters(
|
|
401
|
+
self,
|
|
402
|
+
filters: List[Union[Filter, FilterGroup]],
|
|
403
|
+
operator: LogicalOperator = LogicalOperator.AND
|
|
404
|
+
) -> FilterGroup:
|
|
405
|
+
"""
|
|
406
|
+
Combine multiple filters with a logical operator.
|
|
407
|
+
|
|
408
|
+
Args:
|
|
409
|
+
filters: List of filters to combine
|
|
410
|
+
operator: Logical operator (AND, OR, NOT)
|
|
411
|
+
|
|
412
|
+
Returns:
|
|
413
|
+
Combined FilterGroup
|
|
414
|
+
"""
|
|
415
|
+
return FilterGroup(filters, operator)
|
|
416
|
+
|
|
417
|
+
def validate_filter_spec(self, filter_spec: Dict[str, Any]) -> bool:
|
|
418
|
+
"""
|
|
419
|
+
Validate a filter specification dictionary.
|
|
420
|
+
|
|
421
|
+
Args:
|
|
422
|
+
filter_spec: Filter specification to validate
|
|
423
|
+
|
|
424
|
+
Returns:
|
|
425
|
+
True if valid, False otherwise
|
|
426
|
+
"""
|
|
427
|
+
try:
|
|
428
|
+
parsed = self._parse_dict_filter(filter_spec)
|
|
429
|
+
return parsed is not None
|
|
430
|
+
except (ValueError, KeyError, TypeError):
|
|
431
|
+
return False
|