eeshanyaj commited on
Commit
f19ee9b
·
1 Parent(s): bcbf1db

fixed pydantic error

Browse files
app/models/backup_conversation.py ADDED
@@ -0,0 +1,313 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Conversation Models for MongoDB
3
+
4
+ Handles conversation persistence with:
5
+ - Auto-generated titles from first message
6
+ - Message metadata (policy actions, retrieval stats)
7
+ - Archive/unarchive support
8
+ - Search indexing ready
9
+ """
10
+
11
+ from datetime import datetime
12
+ from typing import List, Optional, Dict, Any
13
+ from pydantic import BaseModel, Field
14
+ from bson import ObjectId
15
+
16
+
17
+ # ============================================================================
18
+ # CUSTOM TYPES
19
+ # ============================================================================
20
+
21
+ class PyObjectId(ObjectId):
22
+ """Custom ObjectId type compatible with Pydantic v2"""
23
+
24
+ @classmethod
25
+ def __get_validators__(cls):
26
+ yield cls.validate
27
+
28
+ @classmethod
29
+ def validate(cls, v):
30
+ if not ObjectId.is_valid(v):
31
+ raise ValueError("Invalid ObjectId")
32
+ return ObjectId(v)
33
+
34
+ @classmethod
35
+ def __get_pydantic_json_schema__(cls, core_schema, handler):
36
+ schema = handler(core_schema)
37
+ schema.update(type="string")
38
+ return schema
39
+
40
+
41
+
42
+ # ============================================================================
43
+ # MESSAGE MODEL
44
+ # ============================================================================
45
+
46
+ class Message(BaseModel):
47
+ """
48
+ Single message in a conversation.
49
+
50
+ Contains:
51
+ - User/assistant content
52
+ - Metadata from RAG pipeline (policy action, retrieval stats)
53
+ - Timestamp
54
+ """
55
+
56
+ role: str = Field(..., description="Role: 'user' or 'assistant'")
57
+ content: str = Field(..., description="Message content")
58
+ timestamp: datetime = Field(default_factory=datetime.utcnow)
59
+
60
+ # Metadata from RAG pipeline (only for assistant messages)
61
+ metadata: Optional[Dict[str, Any]] = Field(
62
+ default=None,
63
+ description="RAG metadata: policy_action, confidence, docs_retrieved, etc."
64
+ )
65
+
66
+ class Config:
67
+ json_encoders = {
68
+ datetime: lambda v: v.isoformat()
69
+ }
70
+ schema_extra = {
71
+ "example": {
72
+ "role": "user",
73
+ "content": "What is my account balance?",
74
+ "timestamp": "2024-01-15T10:30:00",
75
+ "metadata": None
76
+ }
77
+ }
78
+
79
+
80
+ # ============================================================================
81
+ # CONVERSATION MODEL (MongoDB Document)
82
+ # ============================================================================
83
+
84
+ class Conversation(BaseModel):
85
+ """
86
+ Full conversation document stored in MongoDB.
87
+
88
+ Features:
89
+ - Auto-generated title from first user message
90
+ - Message history with metadata
91
+ - Archive/active status
92
+ - User association
93
+ - Search-ready structure
94
+ """
95
+
96
+ id: Optional[PyObjectId] = Field(alias="_id", default=None)
97
+ user_id: str = Field(..., description="User ID who owns this conversation")
98
+ title: str = Field(..., description="Conversation title (auto-generated or custom)")
99
+
100
+ messages: List[Message] = Field(
101
+ default_factory=list,
102
+ description="List of messages in chronological order"
103
+ )
104
+
105
+ # Status flags
106
+ is_archived: bool = Field(default=False, description="Is conversation archived?")
107
+ is_deleted: bool = Field(default=False, description="Soft delete flag")
108
+
109
+ # Timestamps
110
+ created_at: datetime = Field(default_factory=datetime.utcnow)
111
+ updated_at: datetime = Field(default_factory=datetime.utcnow)
112
+ last_message_at: Optional[datetime] = Field(default=None)
113
+
114
+ # Metadata
115
+ message_count: int = Field(default=0, description="Total messages (excluding deleted)")
116
+
117
+ class Config:
118
+ model_config = {
119
+ "populate_by_name": True,
120
+ "arbitrary_types_allowed": True,
121
+ "json_encoders": {
122
+ ObjectId: str,
123
+ datetime: lambda v: v.isoformat(),
124
+ },
125
+ }
126
+ schema_extra = {
127
+ "example": {
128
+ "user_id": "user_123",
129
+ "title": "Account Balance Inquiry",
130
+ "messages": [
131
+ {
132
+ "role": "user",
133
+ "content": "What is my account balance?",
134
+ "timestamp": "2024-01-15T10:30:00"
135
+ },
136
+ {
137
+ "role": "assistant",
138
+ "content": "Your current account balance is...",
139
+ "timestamp": "2024-01-15T10:30:05",
140
+ "metadata": {
141
+ "policy_action": "FETCH",
142
+ "confidence": 0.95,
143
+ "documents_retrieved": 3
144
+ }
145
+ }
146
+ ],
147
+ "is_archived": False,
148
+ "created_at": "2024-01-15T10:30:00",
149
+ "updated_at": "2024-01-15T10:30:05",
150
+ "message_count": 2
151
+ }
152
+ }
153
+
154
+
155
+ # ============================================================================
156
+ # REQUEST/RESPONSE MODELS (for API)
157
+ # ============================================================================
158
+
159
+ class CreateConversationRequest(BaseModel):
160
+ """Request body for creating a new conversation"""
161
+
162
+ title: Optional[str] = Field(
163
+ default=None,
164
+ description="Optional custom title. If not provided, will be auto-generated from first message",
165
+ max_length=100
166
+ )
167
+ first_message: Optional[str] = Field(
168
+ default=None,
169
+ description="Optional first user message to start the conversation",
170
+ max_length=1000
171
+ )
172
+
173
+ class Config:
174
+ schema_extra = {
175
+ "example": {
176
+ "title": "Savings Account Help",
177
+ "first_message": "How do I open a savings account?"
178
+ }
179
+ }
180
+
181
+
182
+ class AddMessageRequest(BaseModel):
183
+ """Request body for adding a message to conversation"""
184
+
185
+ message: str = Field(..., description="User message to add")
186
+
187
+ class Config:
188
+ schema_extra = {
189
+ "example": {
190
+ "message": "What are the interest rates?"
191
+ }
192
+ }
193
+
194
+
195
+ class UpdateConversationRequest(BaseModel):
196
+ """Request body for updating conversation properties"""
197
+
198
+ title: Optional[str] = Field(default=None, description="New title")
199
+ is_archived: Optional[bool] = Field(default=None, description="Archive status")
200
+
201
+ class Config:
202
+ schema_extra = {
203
+ "example": {
204
+ "title": "Fixed Deposit Rates Discussion"
205
+ }
206
+ }
207
+
208
+
209
+ class ConversationResponse(BaseModel):
210
+ """Response model for single conversation"""
211
+
212
+ id: str = Field(..., description="Conversation ID")
213
+ user_id: str
214
+ title: str
215
+ messages: List[Message]
216
+ is_archived: bool
217
+ created_at: datetime
218
+ updated_at: datetime
219
+ last_message_at: Optional[datetime]
220
+ message_count: int
221
+
222
+ class Config:
223
+ json_encoders = {
224
+ datetime: lambda v: v.isoformat()
225
+ }
226
+
227
+
228
+ class ConversationListResponse(BaseModel):
229
+ """Response model for list of conversations (without full messages)"""
230
+
231
+ id: str
232
+ user_id: str
233
+ title: str
234
+ preview: str = Field(..., description="Last message preview (first 100 chars)")
235
+ is_archived: bool
236
+ created_at: datetime
237
+ updated_at: datetime
238
+ last_message_at: Optional[datetime]
239
+ message_count: int
240
+
241
+ class Config:
242
+ json_encoders = {
243
+ datetime: lambda v: v.isoformat()
244
+ }
245
+ schema_extra = {
246
+ "example": {
247
+ "id": "507f1f77bcf86cd799439011",
248
+ "user_id": "user_123",
249
+ "title": "Account Balance Inquiry",
250
+ "preview": "What is my current account balance?",
251
+ "is_archived": False,
252
+ "created_at": "2024-01-15T10:30:00",
253
+ "updated_at": "2024-01-15T10:35:00",
254
+ "last_message_at": "2024-01-15T10:35:00",
255
+ "message_count": 6
256
+ }
257
+ }
258
+
259
+
260
+ class ConversationListResult(BaseModel):
261
+ """Paginated list of conversations"""
262
+
263
+ conversations: List[ConversationListResponse]
264
+ total: int = Field(..., description="Total conversations matching filter")
265
+ page: int = Field(default=1, description="Current page number")
266
+ page_size: int = Field(default=20, description="Items per page")
267
+ has_more: bool = Field(..., description="Are there more pages?")
268
+
269
+ class Config:
270
+ schema_extra = {
271
+ "example": {
272
+ "conversations": [],
273
+ "total": 42,
274
+ "page": 1,
275
+ "page_size": 20,
276
+ "has_more": True
277
+ }
278
+ }
279
+
280
+
281
+
282
+
283
+ # class PyObjectId(ObjectId):
284
+ # """Custom ObjectId type for Pydantic validation"""
285
+
286
+ # @classmethod
287
+ # def __get_validators__(cls):
288
+ # yield cls.validate
289
+
290
+ # @classmethod
291
+ # def validate(cls, v):
292
+ # if not ObjectId.is_valid(v):
293
+ # raise ValueError("Invalid ObjectId")
294
+ # return ObjectId(v)
295
+
296
+ # @classmethod
297
+ # def __modify_schema__(cls, field_schema):
298
+ # field_schema.update(type="string")
299
+
300
+
301
+
302
+
303
+ # allow_population_by_field_name = True
304
+ # arbitrary_types_allowed = True
305
+ # model_config = {
306
+ # "populate_by_name": True,
307
+ # "arbitrary_types_allowed": True,
308
+ # }
309
+
310
+ # json_encoders = {
311
+ # ObjectId: str,
312
+ # datetime: lambda v: v.isoformat()
313
+ # }
app/models/conversation.py CHANGED
@@ -9,8 +9,8 @@ Handles conversation persistence with:
9
  """
10
 
11
  from datetime import datetime
12
- from typing import List, Optional, Dict, Any
13
- from pydantic import BaseModel, Field
14
  from bson import ObjectId
15
 
16
 
@@ -18,22 +18,18 @@ from bson import ObjectId
18
  # CUSTOM TYPES
19
  # ============================================================================
20
 
21
- class PyObjectId(ObjectId):
22
- """Custom ObjectId type for Pydantic validation"""
23
-
24
- @classmethod
25
- def __get_validators__(cls):
26
- yield cls.validate
27
-
28
- @classmethod
29
- def validate(cls, v):
30
- if not ObjectId.is_valid(v):
31
- raise ValueError("Invalid ObjectId")
32
- return ObjectId(v)
33
-
34
- @classmethod
35
- def __modify_schema__(cls, field_schema):
36
- field_schema.update(type="string")
37
 
38
 
39
  # ============================================================================
@@ -60,11 +56,11 @@ class Message(BaseModel):
60
  description="RAG metadata: policy_action, confidence, docs_retrieved, etc."
61
  )
62
 
63
- class Config:
64
- json_encoders = {
65
  datetime: lambda v: v.isoformat()
66
- }
67
- schema_extra = {
68
  "example": {
69
  "role": "user",
70
  "content": "What is my account balance?",
@@ -72,6 +68,7 @@ class Message(BaseModel):
72
  "metadata": None
73
  }
74
  }
 
75
 
76
 
77
  # ============================================================================
@@ -111,14 +108,14 @@ class Conversation(BaseModel):
111
  # Metadata
112
  message_count: int = Field(default=0, description="Total messages (excluding deleted)")
113
 
114
- class Config:
115
- allow_population_by_field_name = True
116
- arbitrary_types_allowed = True
117
- json_encoders = {
118
  ObjectId: str,
119
- datetime: lambda v: v.isoformat()
120
- }
121
- schema_extra = {
122
  "example": {
123
  "user_id": "user_123",
124
  "title": "Account Balance Inquiry",
@@ -145,6 +142,7 @@ class Conversation(BaseModel):
145
  "message_count": 2
146
  }
147
  }
 
148
 
149
 
150
  # ============================================================================
@@ -165,13 +163,14 @@ class CreateConversationRequest(BaseModel):
165
  max_length=1000
166
  )
167
 
168
- class Config:
169
- schema_extra = {
170
  "example": {
171
  "title": "Savings Account Help",
172
  "first_message": "How do I open a savings account?"
173
  }
174
  }
 
175
 
176
 
177
  class AddMessageRequest(BaseModel):
@@ -179,12 +178,13 @@ class AddMessageRequest(BaseModel):
179
 
180
  message: str = Field(..., description="User message to add")
181
 
182
- class Config:
183
- schema_extra = {
184
  "example": {
185
  "message": "What are the interest rates?"
186
  }
187
  }
 
188
 
189
 
190
  class UpdateConversationRequest(BaseModel):
@@ -193,12 +193,13 @@ class UpdateConversationRequest(BaseModel):
193
  title: Optional[str] = Field(default=None, description="New title")
194
  is_archived: Optional[bool] = Field(default=None, description="Archive status")
195
 
196
- class Config:
197
- schema_extra = {
198
  "example": {
199
  "title": "Fixed Deposit Rates Discussion"
200
  }
201
  }
 
202
 
203
 
204
  class ConversationResponse(BaseModel):
@@ -214,10 +215,11 @@ class ConversationResponse(BaseModel):
214
  last_message_at: Optional[datetime]
215
  message_count: int
216
 
217
- class Config:
218
- json_encoders = {
219
  datetime: lambda v: v.isoformat()
220
  }
 
221
 
222
 
223
  class ConversationListResponse(BaseModel):
@@ -233,11 +235,11 @@ class ConversationListResponse(BaseModel):
233
  last_message_at: Optional[datetime]
234
  message_count: int
235
 
236
- class Config:
237
- json_encoders = {
238
  datetime: lambda v: v.isoformat()
239
- }
240
- schema_extra = {
241
  "example": {
242
  "id": "507f1f77bcf86cd799439011",
243
  "user_id": "user_123",
@@ -250,6 +252,7 @@ class ConversationListResponse(BaseModel):
250
  "message_count": 6
251
  }
252
  }
 
253
 
254
 
255
  class ConversationListResult(BaseModel):
@@ -261,8 +264,8 @@ class ConversationListResult(BaseModel):
261
  page_size: int = Field(default=20, description="Items per page")
262
  has_more: bool = Field(..., description="Are there more pages?")
263
 
264
- class Config:
265
- schema_extra = {
266
  "example": {
267
  "conversations": [],
268
  "total": 42,
@@ -270,4 +273,5 @@ class ConversationListResult(BaseModel):
270
  "page_size": 20,
271
  "has_more": True
272
  }
273
- }
 
 
9
  """
10
 
11
  from datetime import datetime
12
+ from typing import List, Optional, Dict, Any, Annotated
13
+ from pydantic import BaseModel, Field, ConfigDict, field_validator, BeforeValidator
14
  from bson import ObjectId
15
 
16
 
 
18
  # CUSTOM TYPES
19
  # ============================================================================
20
 
21
+ def validate_object_id(v: Any) -> ObjectId:
22
+ """Validator function for ObjectId"""
23
+ if isinstance(v, ObjectId):
24
+ return v
25
+ if isinstance(v, str):
26
+ if ObjectId.is_valid(v):
27
+ return ObjectId(v)
28
+ raise ValueError("Invalid ObjectId")
29
+
30
+
31
+ # Annotated type for PyObjectId compatible with Pydantic v2
32
+ PyObjectId = Annotated[ObjectId, BeforeValidator(validate_object_id)]
 
 
 
 
33
 
34
 
35
  # ============================================================================
 
56
  description="RAG metadata: policy_action, confidence, docs_retrieved, etc."
57
  )
58
 
59
+ model_config = ConfigDict(
60
+ json_encoders={
61
  datetime: lambda v: v.isoformat()
62
+ },
63
+ json_schema_extra={
64
  "example": {
65
  "role": "user",
66
  "content": "What is my account balance?",
 
68
  "metadata": None
69
  }
70
  }
71
+ )
72
 
73
 
74
  # ============================================================================
 
108
  # Metadata
109
  message_count: int = Field(default=0, description="Total messages (excluding deleted)")
110
 
111
+ model_config = ConfigDict(
112
+ populate_by_name=True,
113
+ arbitrary_types_allowed=True,
114
+ json_encoders={
115
  ObjectId: str,
116
+ datetime: lambda v: v.isoformat(),
117
+ },
118
+ json_schema_extra={
119
  "example": {
120
  "user_id": "user_123",
121
  "title": "Account Balance Inquiry",
 
142
  "message_count": 2
143
  }
144
  }
145
+ )
146
 
147
 
148
  # ============================================================================
 
163
  max_length=1000
164
  )
165
 
166
+ model_config = ConfigDict(
167
+ json_schema_extra={
168
  "example": {
169
  "title": "Savings Account Help",
170
  "first_message": "How do I open a savings account?"
171
  }
172
  }
173
+ )
174
 
175
 
176
  class AddMessageRequest(BaseModel):
 
178
 
179
  message: str = Field(..., description="User message to add")
180
 
181
+ model_config = ConfigDict(
182
+ json_schema_extra={
183
  "example": {
184
  "message": "What are the interest rates?"
185
  }
186
  }
187
+ )
188
 
189
 
190
  class UpdateConversationRequest(BaseModel):
 
193
  title: Optional[str] = Field(default=None, description="New title")
194
  is_archived: Optional[bool] = Field(default=None, description="Archive status")
195
 
196
+ model_config = ConfigDict(
197
+ json_schema_extra={
198
  "example": {
199
  "title": "Fixed Deposit Rates Discussion"
200
  }
201
  }
202
+ )
203
 
204
 
205
  class ConversationResponse(BaseModel):
 
215
  last_message_at: Optional[datetime]
216
  message_count: int
217
 
218
+ model_config = ConfigDict(
219
+ json_encoders={
220
  datetime: lambda v: v.isoformat()
221
  }
222
+ )
223
 
224
 
225
  class ConversationListResponse(BaseModel):
 
235
  last_message_at: Optional[datetime]
236
  message_count: int
237
 
238
+ model_config = ConfigDict(
239
+ json_encoders={
240
  datetime: lambda v: v.isoformat()
241
+ },
242
+ json_schema_extra={
243
  "example": {
244
  "id": "507f1f77bcf86cd799439011",
245
  "user_id": "user_123",
 
252
  "message_count": 6
253
  }
254
  }
255
+ )
256
 
257
 
258
  class ConversationListResult(BaseModel):
 
264
  page_size: int = Field(default=20, description="Items per page")
265
  has_more: bool = Field(..., description="Are there more pages?")
266
 
267
+ model_config = ConfigDict(
268
+ json_schema_extra={
269
  "example": {
270
  "conversations": [],
271
  "total": 42,
 
273
  "page_size": 20,
274
  "has_more": True
275
  }
276
+ }
277
+ )