-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathmodels.py
More file actions
189 lines (152 loc) · 6.48 KB
/
Copy pathmodels.py
File metadata and controls
189 lines (152 loc) · 6.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
from __future__ import annotations
from datetime import datetime
from enum import StrEnum
from typing import Annotated
from typing import Any
from pydantic import BaseModel
from pydantic import Field
from pydantic import field_validator
from pydantic import model_validator
from app.types import MetadataDict
class SourceType(StrEnum):
"""Allowed source types for context entries"""
USER = 'user'
AGENT = 'agent'
class ContentType(StrEnum):
"""Content type classification"""
TEXT = 'text'
MULTIMODAL = 'multimodal'
class ImageAttachment(BaseModel):
"""Image attachment model for transport"""
data: str = Field(..., description='Base64 encoded image data')
mime_type: str = Field(default='image/png', pattern='^image/(png|jpeg|jpg|gif|webp)$')
metadata: MetadataDict | None = Field(default=None)
position: Annotated[int, Field(default=0, ge=0)]
@field_validator('data')
@classmethod
def validate_base64(cls, v: str) -> str:
"""Validate base64 format"""
import base64
try:
base64.b64decode(v)
return v
except Exception as e:
raise ValueError('Invalid base64 encoded data') from e
class ContextEntry(BaseModel):
"""Core context entry model"""
model_config = {
'json_schema_extra': {
'examples': [
{
'thread_id': 'task_123',
'source': 'user',
'text_content': 'Analyze this data',
'tags': ['analysis', 'priority-high'],
},
],
},
}
id: int | None = Field(default=None, description='Auto-generated ID')
thread_id: str = Field(..., description='Thread identifier for context scoping')
source: SourceType = Field(..., description='Origin of the context')
content_type: ContentType = Field(default=ContentType.TEXT)
text_content: str | None = Field(default=None)
images: list[Any] = Field(default_factory=list, max_length=10) # Pyright false positive - ImageAttachment is defined
metadata: MetadataDict | None = Field(default=None)
tags: list[str] = Field(default_factory=list)
created_at: datetime | None = Field(default=None)
updated_at: datetime | None = Field(default=None)
@field_validator('text_content')
@classmethod
def validate_text_content(cls, v: str | None) -> str | None:
"""Validate text content is not empty if provided."""
if v is not None and not v.strip():
raise ValueError('Text content cannot be empty or contain only whitespace')
return v
@field_validator('tags')
@classmethod
def validate_tags(cls, v: list[str]) -> list[str]:
"""Ensure tags are properly formatted with robust character support.
Handles:
- Unicode characters including forward slashes (/)
- Edge cases like double-encoding
- Non-string types that can be converted
- Whitespace normalization
Returns:
List of validated and normalized tag strings.
"""
validated_tags: list[str] = []
for tag in v:
# The tag should always be a string at this point due to type hints
# Normalize whitespace: replace multiple spaces/tabs/newlines with single space
normalized_tag = ' '.join(tag.split()).strip().lower()
if normalized_tag:
validated_tags.append(normalized_tag)
return validated_tags
@model_validator(mode='after')
def set_content_type(self) -> ContextEntry:
"""Auto-set content type to MULTIMODAL when images are present"""
if self.images:
self.content_type = ContentType.MULTIMODAL
return self
class SearchFilters(BaseModel):
"""Search filter parameters with efficient indexing support"""
model_config = {
'json_schema_extra': {
'examples': [
{
'thread_id': 'task_123',
'source': 'agent',
'limit': 10,
},
],
},
}
thread_id: str | None = Field(default=None, description='Filter by thread')
source: SourceType | None = Field(default=None, description='Filter by source')
tags: list[str] | None = Field(default=None, description='Filter by tags (OR logic)')
start_date: datetime | None = Field(default=None)
end_date: datetime | None = Field(default=None)
content_type: ContentType | None = Field(default=None)
limit: Annotated[int, Field(default=50, le=500, ge=1)]
offset: Annotated[int, Field(default=0, ge=0)]
include_images: bool = Field(default=False, description='Include image data in response')
class StoreContextRequest(BaseModel):
"""Request model for storing context"""
thread_id: str = Field(..., description='Thread ID for context scoping')
source: SourceType = Field(..., description="Must be 'user' or 'agent'")
text: str | None = Field(default=None)
images: list[ImageAttachment] | None = Field(default=None, max_length=10)
metadata: MetadataDict | None = Field(default=None)
tags: list[str] | None = Field(default=None)
@field_validator('thread_id')
@classmethod
def validate_thread_id(cls, v: str) -> str:
"""Validate thread_id is not empty or whitespace."""
if not v.strip():
raise ValueError('thread_id is required and cannot be empty')
return v
@field_validator('text')
@classmethod
def validate_text(cls, v: str | None) -> str | None:
"""Validate text is not empty if provided."""
if v is not None and not v.strip():
raise ValueError('text is required and cannot be empty')
return v
class DeleteContextRequest(BaseModel):
"""Request model for deleting context"""
context_ids: list[int] | None = Field(default=None)
thread_id: str | None = Field(default=None)
@field_validator('context_ids')
@classmethod
def validate_context_ids(cls, v: list[int] | None) -> list[int] | None:
"""Validate context_ids is not empty if provided."""
if v is not None and len(v) == 0:
raise ValueError('context_ids cannot be an empty list')
return v
@model_validator(mode='after')
def validate_has_fields(self) -> DeleteContextRequest:
"""Ensure at least one field is provided for deletion"""
if not self.context_ids and not self.thread_id:
raise ValueError('Must provide either context_ids or thread_id')
return self