simile 0.5.3__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 simile might be problematic. Click here for more details.

simile/exceptions.py ADDED
@@ -0,0 +1,47 @@
1
+ class SimileAPIError(Exception):
2
+ """Base exception for Simile API client errors."""
3
+
4
+ def __init__(self, message: str, status_code: int = None, detail: str = None):
5
+ super().__init__(message)
6
+ self.status_code = status_code
7
+ self.detail = detail
8
+
9
+ def __str__(self):
10
+ if self.status_code and self.detail:
11
+ return f"{super().__str__()} (Status Code: {self.status_code}, Detail: {self.detail})"
12
+ elif self.status_code:
13
+ return f"{super().__str__()} (Status Code: {self.status_code})"
14
+ return super().__str__()
15
+
16
+
17
+ class SimileAuthenticationError(SimileAPIError):
18
+ """Exception for authentication errors (e.g., invalid API key)."""
19
+
20
+ def __init__(
21
+ self,
22
+ message: str = "Authentication failed. Ensure API key is valid.",
23
+ status_code: int = 401,
24
+ detail: str = None,
25
+ ):
26
+ super().__init__(message, status_code, detail)
27
+
28
+
29
+ class SimileNotFoundError(SimileAPIError):
30
+ """Exception for resource not found errors (404)."""
31
+
32
+ def __init__(
33
+ self,
34
+ message: str = "Resource not found.",
35
+ status_code: int = 404,
36
+ detail: str = None,
37
+ ):
38
+ super().__init__(message, status_code, detail)
39
+
40
+
41
+ class SimileBadRequestError(SimileAPIError):
42
+ """Exception for bad request errors (400)."""
43
+
44
+ def __init__(
45
+ self, message: str = "Bad request.", status_code: int = 400, detail: str = None
46
+ ):
47
+ super().__init__(message, status_code, detail)
simile/models.py ADDED
@@ -0,0 +1,435 @@
1
+ from typing import List, Dict, Any, Optional, Union, Literal
2
+ from pydantic import BaseModel, Field, validator
3
+ from datetime import datetime
4
+ from enum import Enum
5
+ import uuid
6
+
7
+
8
+ class Population(BaseModel):
9
+ population_id: uuid.UUID
10
+ name: str
11
+ description: Optional[str] = None
12
+ created_at: datetime
13
+ updated_at: datetime
14
+ metadata: Optional[Dict[str, Any]] = None
15
+
16
+
17
+ class PopulationInfo(BaseModel):
18
+ population_id: uuid.UUID
19
+ name: str
20
+ description: Optional[str] = None
21
+ agent_count: int
22
+ metadata: Optional[Dict[str, Any]] = None
23
+
24
+
25
+ class DataItem(BaseModel):
26
+ id: uuid.UUID
27
+ agent_id: uuid.UUID
28
+ data_type: str
29
+ content: Any
30
+ created_at: datetime
31
+ updated_at: datetime
32
+ metadata: Optional[Dict[str, Any]] = None
33
+
34
+
35
+ class Agent(BaseModel):
36
+ agent_id: uuid.UUID
37
+ name: str
38
+ population_id: Optional[uuid.UUID] = None
39
+ created_at: datetime
40
+ updated_at: datetime
41
+ data_items: List[DataItem] = Field(default_factory=list)
42
+ source: Optional[str] = None
43
+ source_id: Optional[str] = None
44
+
45
+
46
+ class CreatePopulationPayload(BaseModel):
47
+ name: str
48
+ description: Optional[str] = None
49
+
50
+
51
+ class UpdatePopulationMetadataPayload(BaseModel):
52
+ metadata: Dict[str, Any]
53
+ mode: Optional[Literal["merge", "replace"]] = "merge"
54
+
55
+
56
+ class UpdatePopulationInfoPayload(BaseModel):
57
+ name: Optional[str] = None
58
+ description: Optional[str] = None
59
+ metadata: Optional[Dict[str, Any]] = None
60
+
61
+
62
+ class UpdateAgentInfoPayload(BaseModel):
63
+ name: str
64
+
65
+
66
+ class InitialDataItemPayload(BaseModel):
67
+ data_type: str
68
+ content: Any
69
+ metadata: Optional[Dict[str, Any]] = None
70
+
71
+
72
+ class CreateAgentPayload(BaseModel):
73
+ name: str
74
+ source: Optional[str] = None
75
+ source_id: Optional[str] = None
76
+ population_id: Optional[uuid.UUID] = None
77
+ agent_data: Optional[List[InitialDataItemPayload]] = None
78
+
79
+
80
+ class CreateDataItemPayload(BaseModel):
81
+ data_type: str
82
+ content: Any
83
+ metadata: Optional[Dict[str, Any]] = None
84
+
85
+
86
+ class UpdateDataItemPayload(BaseModel):
87
+ content: Any
88
+ metadata: Optional[Dict[str, Any]] = None
89
+
90
+
91
+ class DeletionResponse(BaseModel):
92
+ message: str
93
+
94
+
95
+ # --- Generation Operation Models ---
96
+ class OpenGenerationRequest(BaseModel):
97
+ question: str
98
+ data_types: Optional[List[str]] = None
99
+ exclude_data_types: Optional[List[str]] = None
100
+ images: Optional[Dict[str, str]] = (
101
+ None # Dict of {description: url} for multiple images
102
+ )
103
+ reasoning: bool = False
104
+ evidence: bool = False
105
+ confidence: bool = False
106
+ memory_stream: Optional["MemoryStream"] = None
107
+ include_data_room: Optional[bool] = False
108
+ organization_id: Optional[Union[str, uuid.UUID]] = None
109
+
110
+
111
+ class OpenGenerationResponse(BaseModel):
112
+ question: str
113
+ answer: str
114
+ reasoning: Optional[str] = ""
115
+ evidence: Optional[Dict[str, str]] = None # Phrase -> citation number
116
+ evidence_content: Optional[Dict[str, Dict[str, str]]] = None # Citation number -> data item
117
+ confidence: Optional[float] = None
118
+
119
+
120
+ class ClosedGenerationRequest(BaseModel):
121
+ question: str
122
+ options: List[str]
123
+ data_types: Optional[List[str]] = None
124
+ exclude_data_types: Optional[List[str]] = None
125
+ images: Optional[Dict[str, str]] = None
126
+ reasoning: bool = False
127
+ evidence: bool = False
128
+ confidence: bool = False
129
+ memory_stream: Optional["MemoryStream"] = None
130
+ include_data_room: Optional[bool] = False
131
+ organization_id: Optional[Union[str, uuid.UUID]] = None
132
+
133
+
134
+ class ClosedGenerationResponse(BaseModel):
135
+ question: str
136
+ options: List[str]
137
+ response: str
138
+ reasoning: Optional[str] = ""
139
+ evidence: Optional[Dict[str, str]] = None # Phrase -> citation number
140
+ evidence_content: Optional[Dict[str, Dict[str, str]]] = None # Citation number -> data item
141
+ confidence: Optional[float] = None
142
+
143
+
144
+ class AddContextRequest(BaseModel):
145
+ context: str
146
+
147
+
148
+ class AddContextResponse(BaseModel):
149
+ message: str
150
+ session_id: uuid.UUID
151
+
152
+
153
+ # --- Survey Session Models ---
154
+ class TurnType(str, Enum):
155
+ """Enum for different types of conversation turns."""
156
+
157
+ CONTEXT = "context"
158
+ IMAGE = "image"
159
+ OPEN_QUESTION = "open_question"
160
+ CLOSED_QUESTION = "closed_question"
161
+
162
+
163
+ class BaseTurn(BaseModel):
164
+ """Base model for all conversation turns."""
165
+
166
+ timestamp: datetime = Field(default_factory=lambda: datetime.now())
167
+ type: TurnType
168
+
169
+ class Config:
170
+ use_enum_values = True
171
+
172
+
173
+ class ContextTurn(BaseTurn):
174
+ """A context turn that provides background information."""
175
+
176
+ type: Literal[TurnType.CONTEXT] = TurnType.CONTEXT
177
+ user_context: str
178
+
179
+
180
+ class ImageTurn(BaseTurn):
181
+ """A standalone image turn (e.g., for context or reference)."""
182
+
183
+ type: Literal[TurnType.IMAGE] = TurnType.IMAGE
184
+ images: Dict[str, str]
185
+ caption: Optional[str] = None
186
+
187
+
188
+ class OpenQuestionTurn(BaseTurn):
189
+ """An open question-answer turn."""
190
+
191
+ type: Literal[TurnType.OPEN_QUESTION] = TurnType.OPEN_QUESTION
192
+ user_question: str
193
+ user_images: Optional[Dict[str, str]] = None
194
+ llm_response: Optional[str] = None
195
+
196
+
197
+ class ClosedQuestionTurn(BaseTurn):
198
+ """A closed question-answer turn."""
199
+
200
+ type: Literal[TurnType.CLOSED_QUESTION] = TurnType.CLOSED_QUESTION
201
+ user_question: str
202
+ user_options: List[str]
203
+ user_images: Optional[Dict[str, str]] = None
204
+ llm_response: Optional[str] = None
205
+
206
+ @validator("user_options")
207
+ def validate_options(cls, v):
208
+ if not v:
209
+ raise ValueError("Closed questions must have at least one option")
210
+ if len(v) < 2:
211
+ raise ValueError("Closed questions should have at least two options")
212
+ return v
213
+
214
+ @validator("llm_response")
215
+ def validate_response(cls, v, values):
216
+ if (
217
+ v is not None
218
+ and "user_options" in values
219
+ and v not in values["user_options"]
220
+ ):
221
+ raise ValueError(f"Response '{v}' must be one of the provided options")
222
+ return v
223
+
224
+
225
+ # Union type for all possible turn types
226
+ SurveySessionTurn = Union[ContextTurn, ImageTurn, OpenQuestionTurn, ClosedQuestionTurn]
227
+
228
+
229
+ class SurveySessionCreateResponse(BaseModel):
230
+ id: uuid.UUID # Session ID
231
+ agent_id: uuid.UUID
232
+ created_at: datetime
233
+ status: str
234
+
235
+
236
+ class SurveySessionDetailResponse(BaseModel):
237
+ """Detailed survey session response with typed conversation turns."""
238
+
239
+ id: uuid.UUID
240
+ agent_id: uuid.UUID
241
+ created_at: datetime
242
+ updated_at: datetime
243
+ status: str
244
+ conversation_history: List[SurveySessionTurn] = Field(default_factory=list)
245
+
246
+ class Config:
247
+ json_encoders = {datetime: lambda v: v.isoformat()}
248
+
249
+
250
+ class SurveySessionListItemResponse(BaseModel):
251
+ """Summary response for listing survey sessions."""
252
+
253
+ id: uuid.UUID
254
+ agent_id: uuid.UUID
255
+ created_at: datetime
256
+ updated_at: datetime
257
+ status: str
258
+ turn_count: int = Field(description="Number of turns in conversation history")
259
+
260
+ class Config:
261
+ json_encoders = {datetime: lambda v: v.isoformat()}
262
+
263
+
264
+ class SurveySessionCloseResponse(BaseModel):
265
+ id: uuid.UUID # Session ID
266
+ status: str
267
+ updated_at: datetime
268
+ message: Optional[str] = None
269
+
270
+
271
+ # --- Memory Stream Models (to replace Survey Sessions) ---
272
+ class MemoryTurnType(str, Enum):
273
+ """Enum for different types of memory turns."""
274
+
275
+ CONTEXT = "context"
276
+ IMAGE = "image"
277
+ OPEN_QUESTION = "open_question"
278
+ CLOSED_QUESTION = "closed_question"
279
+
280
+
281
+ class BaseMemoryTurn(BaseModel):
282
+ """Base model for all memory turns."""
283
+
284
+ timestamp: datetime = Field(default_factory=lambda: datetime.now())
285
+ type: MemoryTurnType
286
+
287
+ class Config:
288
+ use_enum_values = True
289
+
290
+ def to_dict(self) -> Dict[str, Any]:
291
+ """Convert to dictionary for serialization."""
292
+ data = self.model_dump()
293
+ # Remove timestamp - let API handle it
294
+ data.pop("timestamp", None)
295
+ # Ensure enum is serialized as string
296
+ if "type" in data:
297
+ if hasattr(data["type"], "value"):
298
+ data["type"] = data["type"].value
299
+ return data
300
+
301
+
302
+ class ContextMemoryTurn(BaseMemoryTurn):
303
+ """A context turn that provides background information."""
304
+
305
+ type: MemoryTurnType = Field(default=MemoryTurnType.CONTEXT)
306
+ user_context: str
307
+
308
+
309
+ class ImageMemoryTurn(BaseMemoryTurn):
310
+ """A standalone image turn (e.g., for context or reference)."""
311
+
312
+ type: MemoryTurnType = Field(default=MemoryTurnType.IMAGE)
313
+ images: Dict[str, str]
314
+ caption: Optional[str] = None
315
+
316
+
317
+ class OpenQuestionMemoryTurn(BaseMemoryTurn):
318
+ """An open question-answer turn."""
319
+
320
+ type: MemoryTurnType = Field(default=MemoryTurnType.OPEN_QUESTION)
321
+ user_question: str
322
+ user_images: Optional[Dict[str, str]] = None
323
+ llm_response: Optional[str] = None
324
+ llm_reasoning: Optional[str] = None
325
+
326
+
327
+ class ClosedQuestionMemoryTurn(BaseMemoryTurn):
328
+ """A closed question-answer turn."""
329
+
330
+ type: MemoryTurnType = Field(default=MemoryTurnType.CLOSED_QUESTION)
331
+ user_question: str
332
+ user_options: List[str]
333
+ user_images: Optional[Dict[str, str]] = None
334
+ llm_response: Optional[str] = None
335
+ llm_reasoning: Optional[str] = None
336
+
337
+
338
+ # Discriminated union of all memory turn types
339
+ MemoryTurn = Union[
340
+ ContextMemoryTurn, ImageMemoryTurn, OpenQuestionMemoryTurn, ClosedQuestionMemoryTurn
341
+ ]
342
+
343
+
344
+ class MemoryStream(BaseModel):
345
+ """
346
+ A flexible memory stream that can be passed to generation functions.
347
+ This replaces the session-based approach with a more flexible paradigm.
348
+ """
349
+
350
+ turns: List[MemoryTurn] = Field(default_factory=list)
351
+
352
+ def add_turn(self, turn: MemoryTurn) -> None:
353
+ """Add a turn to the memory stream."""
354
+ self.turns.append(turn)
355
+
356
+ def remove_turn(self, index: int) -> Optional[MemoryTurn]:
357
+ """Remove a turn at the specified index."""
358
+ if 0 <= index < len(self.turns):
359
+ return self.turns.pop(index)
360
+ return None
361
+
362
+ def get_turns_by_type(self, turn_type: MemoryTurnType) -> List[MemoryTurn]:
363
+ """Get all turns of a specific type."""
364
+ return [turn for turn in self.turns if turn.type == turn_type]
365
+
366
+ def get_last_turn(self) -> Optional[MemoryTurn]:
367
+ """Get the most recent turn."""
368
+ return self.turns[-1] if self.turns else None
369
+
370
+ def clear(self) -> None:
371
+ """Clear all turns from the memory stream."""
372
+ self.turns = []
373
+
374
+ def __len__(self) -> int:
375
+ """Return the number of turns in the memory stream."""
376
+ return len(self.turns)
377
+
378
+ def __bool__(self) -> bool:
379
+ """Return True if the memory stream has any turns."""
380
+ return bool(self.turns)
381
+
382
+ def to_dict(self) -> Dict[str, Any]:
383
+ """Convert memory stream to a dictionary for serialization."""
384
+ return {"turns": [turn.to_dict() for turn in self.turns]}
385
+
386
+ @classmethod
387
+ def from_dict(cls, data: Dict[str, Any]) -> "MemoryStream":
388
+ """Create a MemoryStream from a dictionary."""
389
+ memory = cls()
390
+ for turn_data in data.get("turns", []):
391
+ turn_type = turn_data.get("type")
392
+ if turn_type == MemoryTurnType.CONTEXT:
393
+ memory.add_turn(ContextMemoryTurn(**turn_data))
394
+ elif turn_type == MemoryTurnType.IMAGE:
395
+ memory.add_turn(ImageMemoryTurn(**turn_data))
396
+ elif turn_type == MemoryTurnType.OPEN_QUESTION:
397
+ memory.add_turn(OpenQuestionMemoryTurn(**turn_data))
398
+ elif turn_type == MemoryTurnType.CLOSED_QUESTION:
399
+ memory.add_turn(ClosedQuestionMemoryTurn(**turn_data))
400
+ return memory
401
+
402
+ def fork(self, up_to_index: Optional[int] = None) -> "MemoryStream":
403
+ """Create a copy of this memory stream, optionally up to a specific index."""
404
+ new_memory = MemoryStream()
405
+ turns_to_copy = (
406
+ self.turns[:up_to_index] if up_to_index is not None else self.turns
407
+ )
408
+ for turn in turns_to_copy:
409
+ new_memory.add_turn(turn.model_copy())
410
+ return new_memory
411
+
412
+ def filter_by_type(self, turn_type: MemoryTurnType) -> "MemoryStream":
413
+ """Create a new memory stream with only turns of a specific type."""
414
+ new_memory = MemoryStream()
415
+ for turn in self.get_turns_by_type(turn_type):
416
+ new_memory.add_turn(turn.model_copy())
417
+ return new_memory
418
+
419
+ def get_question_answer_pairs(self) -> List[tuple]:
420
+ """Extract question-answer pairs from the memory."""
421
+ pairs = []
422
+ for turn in self.turns:
423
+ if isinstance(turn, (OpenQuestionMemoryTurn, ClosedQuestionMemoryTurn)):
424
+ if turn.llm_response:
425
+ pairs.append((turn.user_question, turn.llm_response))
426
+ return pairs
427
+
428
+ def truncate(self, max_turns: int) -> None:
429
+ """Keep only the most recent N turns."""
430
+ if len(self.turns) > max_turns:
431
+ self.turns = self.turns[-max_turns:]
432
+
433
+ def insert_turn(self, index: int, turn: MemoryTurn) -> None:
434
+ """Insert a turn at a specific position."""
435
+ self.turns.insert(index, turn)