recallrai 0.1.1__py3-none-any.whl → 0.3.0__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 recallrai might be problematic. Click here for more details.

@@ -2,7 +2,6 @@
2
2
  Validation-related exceptions for the RecallrAI SDK.
3
3
  """
4
4
 
5
- from typing import Any, Dict, Optional, Union
6
5
  from .base import RecallrAIError
7
6
 
8
7
 
@@ -14,11 +13,5 @@ class ValidationError(RecallrAIError):
14
13
  due to invalid or missing parameters.
15
14
  """
16
15
 
17
- def __init__(
18
- self,
19
- message: str = "Validation error",
20
- code: str = "validation_error",
21
- http_status: int = 422,
22
- details: Optional[Dict[str, Any]] = None
23
- ):
24
- super().__init__(message, code, http_status, details)
16
+ def __init__(self, message: str, http_status: int):
17
+ super().__init__(message, http_status)
@@ -0,0 +1,189 @@
1
+ """
2
+ Merge conflict management functionality for the RecallrAI SDK.
3
+ """
4
+
5
+ from typing import List
6
+ from .utils import HTTPClient
7
+ from .models import (
8
+ MergeConflictModel,
9
+ MergeConflictStatus,
10
+ MergeConflictAnswer,
11
+ )
12
+ from .exceptions import (
13
+ UserNotFoundError,
14
+ MergeConflictNotFoundError,
15
+ MergeConflictAlreadyResolvedError,
16
+ MergeConflictInvalidQuestionsError,
17
+ MergeConflictMissingAnswersError,
18
+ MergeConflictInvalidAnswerError,
19
+ RecallrAIError
20
+ )
21
+ from logging import getLogger
22
+
23
+ logger = getLogger(__name__)
24
+
25
+
26
+ class MergeConflict:
27
+ """
28
+ Represents a merge conflict in the RecallrAI system.
29
+
30
+ This class provides methods for inspecting and resolving merge conflicts
31
+ that occur when new memories conflict with existing ones.
32
+ """
33
+
34
+ def __init__(
35
+ self,
36
+ http_client: HTTPClient,
37
+ user_id: str,
38
+ conflict_data: MergeConflictModel,
39
+ ):
40
+ """
41
+ Initialize a merge conflict.
42
+
43
+ Args:
44
+ http_client: HTTP client for API communication.
45
+ user_id: User ID who owns this conflict.
46
+ conflict_data: Merge conflict data model.
47
+ """
48
+ self._http = http_client
49
+ self.user_id = user_id
50
+ self._conflict_data = conflict_data
51
+
52
+ # Expose key properties for easy access
53
+ self.conflict_id = conflict_data.id
54
+ self.status = conflict_data.status
55
+ self.new_memory_content = conflict_data.new_memory_content
56
+ self.conflicting_memories = conflict_data.conflicting_memories
57
+ self.clarifying_questions = conflict_data.clarifying_questions
58
+ self.created_at = conflict_data.created_at
59
+ self.resolved_at = conflict_data.resolved_at
60
+ self.resolution_data = conflict_data.resolution_data
61
+
62
+ def resolve(self, answers: List[MergeConflictAnswer]) -> None:
63
+ """
64
+ Resolve this merge conflict by providing answers to clarifying questions.
65
+
66
+ Args:
67
+ answers: List of answers to the clarifying questions.
68
+
69
+ Raises:
70
+ UserNotFoundError: If the user is not found.
71
+ MergeConflictNotFoundError: If the merge conflict is not found.
72
+ MergeConflictAlreadyResolvedError: If the conflict is already resolved.
73
+ MergeConflictInvalidQuestionsError: If the provided questions don't match the original questions.
74
+ MergeConflictMissingAnswersError: If not all required questions have been answered.
75
+ MergeConflictInvalidAnswerError: If an answer is not a valid option for its question.
76
+ ValidationError: If the answers are invalid.
77
+ AuthenticationError: If the API key or project ID is invalid.
78
+ InternalServerError: If the server encounters an error.
79
+ NetworkError: If there are network issues.
80
+ TimeoutError: If the request times out.
81
+ RecallrAIError: For other API-related errors.
82
+ """
83
+ if self.status in [MergeConflictStatus.RESOLVED, MergeConflictStatus.FAILED]:
84
+ raise MergeConflictAlreadyResolvedError(
85
+ message=f"Merge conflict {self.conflict_id} is already resolved",
86
+ http_status=400
87
+ )
88
+
89
+ # Convert answers to the format expected by the API
90
+ answer_data = {
91
+ "question_answers": [
92
+ {
93
+ "question": answer.question,
94
+ "answer": answer.answer,
95
+ "message": answer.message,
96
+ }
97
+ for answer in answers
98
+ ]
99
+ }
100
+
101
+ response = self._http.post(
102
+ f"/api/v1/users/{self.user_id}/merge-conflicts/{self.conflict_id}/resolve",
103
+ data={"answers": answer_data},
104
+ )
105
+
106
+ if response.status_code == 404:
107
+ # Check if it's a user not found or conflict not found error
108
+ detail = response.json().get('detail', '')
109
+ if f"User {self.user_id} not found" in detail:
110
+ raise UserNotFoundError(message=detail, http_status=response.status_code)
111
+ else:
112
+ raise MergeConflictNotFoundError(message=detail, http_status=response.status_code)
113
+ elif response.status_code == 400:
114
+ detail = response.json().get('detail', '')
115
+ if "already resolved" in detail:
116
+ raise MergeConflictAlreadyResolvedError(message=detail, http_status=response.status_code)
117
+ elif "Invalid questions provided" in detail:
118
+ raise MergeConflictInvalidQuestionsError(
119
+ message=detail,
120
+ http_status=response.status_code
121
+ )
122
+ elif "Missing answers for the following questions" in detail:
123
+ raise MergeConflictMissingAnswersError(
124
+ message=detail,
125
+ http_status=response.status_code
126
+ )
127
+ elif "Invalid answer" in detail and "for question" in detail:
128
+ raise MergeConflictInvalidAnswerError(
129
+ message=detail,
130
+ http_status=response.status_code
131
+ )
132
+ else:
133
+ raise RecallrAIError(
134
+ message=detail,
135
+ http_status=response.status_code
136
+ )
137
+ elif response.status_code != 200:
138
+ raise RecallrAIError(
139
+ message=response.json().get('detail', 'Unknown error'),
140
+ http_status=response.status_code
141
+ )
142
+
143
+ # Update the conflict data with the response
144
+ updated_data = MergeConflictModel.from_api_response(response.json())
145
+ self._conflict_data = updated_data
146
+ self.status = updated_data.status
147
+ self.resolved_at = updated_data.resolved_at
148
+ self.resolution_data = updated_data.resolution_data
149
+
150
+ def refresh(self) -> None:
151
+ """
152
+ Refresh this merge conflict's data from the API.
153
+
154
+ Raises:
155
+ UserNotFoundError: If the user is not found.
156
+ MergeConflictNotFoundError: If the merge conflict is not found.
157
+ AuthenticationError: If the API key or project ID is invalid.
158
+ InternalServerError: If the server encounters an error.
159
+ NetworkError: If there are network issues.
160
+ TimeoutError: If the request times out.
161
+ RecallrAIError: For other API-related errors.
162
+ """
163
+ response = self._http.get(
164
+ f"/api/v1/users/{self.user_id}/merge-conflicts/{self.conflict_id}"
165
+ )
166
+
167
+ if response.status_code == 404:
168
+ # Check if it's a user not found or conflict not found error
169
+ detail = response.json().get('detail', '')
170
+ if f"User {self.user_id} not found" in detail:
171
+ raise UserNotFoundError(message=detail, http_status=response.status_code)
172
+ else:
173
+ raise MergeConflictNotFoundError(message=detail, http_status=response.status_code)
174
+ elif response.status_code != 200:
175
+ raise RecallrAIError(
176
+ message=response.json().get('detail', 'Unknown error'),
177
+ http_status=response.status_code
178
+ )
179
+
180
+ # Update with fresh data
181
+ updated_data = MergeConflictModel.from_api_response(response.json())
182
+ self._conflict_data = updated_data
183
+ self.status = updated_data.status
184
+ self.resolved_at = updated_data.resolved_at
185
+ self.resolution_data = updated_data.resolution_data
186
+
187
+ def __repr__(self) -> str:
188
+ """Return a string representation of the merge conflict."""
189
+ return f"MergeConflict(id='{self.conflict_id}', status='{self.status}', user_id='{self.user_id}')"
@@ -1,16 +1,49 @@
1
- # Path: recallrai/models/__init__.py
2
- # Description: Package initialization for data models
1
+ """
2
+ Models used in the SDK.
3
+ """
3
4
 
4
- from .session import Context, Message, MessageRole, Session, SessionList, SessionStatus
5
- from .user import User, UserList
5
+ from .user import (
6
+ UserModel,
7
+ UserList,
8
+ UserMemoriesList,
9
+ UserMessage,
10
+ UserMessagesList,
11
+ UserMemoryItem,
12
+ MemoryVersionInfo,
13
+ MemoryRelationship,
14
+ )
15
+ from .session import SessionModel, SessionList, SessionMessagesList, MessageRole, SessionStatus, Context, RecallStrategy
16
+ from .merge_conflict import (
17
+ MergeConflictModel,
18
+ MergeConflictList,
19
+ MergeConflictStatus,
20
+ MergeConflictMemory,
21
+ MergeConflictQuestion,
22
+ MergeConflictAnswer,
23
+ )
6
24
 
7
25
  __all__ = [
8
- "User",
26
+ "UserModel",
9
27
  "UserList",
10
- "Session",
28
+ "UserMemoriesList",
29
+ "UserMessage",
30
+ "UserMessagesList",
31
+ "UserMemoryItem",
32
+ "MemoryVersionInfo",
33
+ "MemoryRelationship",
34
+
35
+ "SessionModel",
11
36
  "SessionList",
12
- "SessionStatus",
13
- "Message",
37
+ "SessionMessagesList",
14
38
  "MessageRole",
39
+ "SessionStatus",
15
40
  "Context",
41
+ "RecallStrategy",
42
+
43
+ "MergeConflictModel",
44
+ "MergeConflictList",
45
+ "MergeConflictStatus",
46
+ "MergeConflictMemory",
47
+ "MergeConflictQuestion",
48
+ "MergeConflictAnswer",
16
49
  ]
@@ -0,0 +1,151 @@
1
+ """
2
+ Merge conflict-related data models for the RecallrAI SDK.
3
+ """
4
+
5
+ import enum
6
+ from datetime import datetime
7
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional
8
+ from pydantic import BaseModel, Field
9
+ from ..utils import HTTPClient
10
+
11
+ if TYPE_CHECKING:
12
+ from ..merge_conflict import MergeConflict
13
+
14
+
15
+ class MergeConflictStatus(str, enum.Enum):
16
+ """
17
+ Status of a merge conflict.
18
+ """
19
+ PENDING = "PENDING"
20
+ IN_QUEUE = "IN_QUEUE"
21
+ RESOLVING = "RESOLVING"
22
+ RESOLVED = "RESOLVED"
23
+ FAILED = "FAILED"
24
+
25
+
26
+ class MergeConflictMemory(BaseModel):
27
+ """
28
+ Represents a memory involved in a merge conflict.
29
+ """
30
+ content: str = Field(..., description="Content of the conflicting memory.")
31
+ reason: str = Field(..., description="Reason why this memory conflicts.")
32
+
33
+ class Config:
34
+ """Pydantic configuration."""
35
+ frozen = True
36
+
37
+
38
+ class MergeConflictQuestion(BaseModel):
39
+ """
40
+ Represents a clarifying question for merge conflict resolution.
41
+ """
42
+ question: str = Field(..., description="The clarifying question.")
43
+ options: List[str] = Field(..., description="Available answer options.")
44
+
45
+ class Config:
46
+ """Pydantic configuration."""
47
+ frozen = True
48
+
49
+
50
+ class MergeConflictAnswer(BaseModel):
51
+ """
52
+ Represents an answer to a clarifying question.
53
+ """
54
+ question: str = Field(..., description="The question being answered.")
55
+ answer: str = Field(..., description="The selected answer.")
56
+ message: Optional[str] = Field(None, description="Optional additional message.")
57
+
58
+ class Config:
59
+ """Pydantic configuration."""
60
+ frozen = True
61
+
62
+
63
+ class MergeConflictModel(BaseModel):
64
+ """
65
+ Represents a merge conflict in the RecallrAI system.
66
+ """
67
+ id: str = Field(..., description="Unique identifier for the merge conflict.")
68
+ custom_user_id: str = Field(..., description="User ID who owns this conflict.")
69
+ project_user_session_id: str = Field(..., description="Session ID where the conflict occurred.")
70
+ new_memory_content: str = Field(..., description="New memory content that caused the conflict.")
71
+ conflicting_memories: List[MergeConflictMemory] = Field(..., description="Existing memories that conflict.")
72
+ clarifying_questions: List[MergeConflictQuestion] = Field(..., description="Questions to resolve the conflict.")
73
+ status: MergeConflictStatus = Field(..., description="Current status of the conflict.")
74
+ resolution_data: Optional[Dict[str, Any]] = Field(None, description="Resolution data if resolved.")
75
+ created_at: datetime = Field(..., description="When the conflict was created.")
76
+ resolved_at: Optional[datetime] = Field(None, description="When the conflict was resolved.")
77
+
78
+ class Config:
79
+ """Pydantic configuration."""
80
+ frozen = True
81
+ json_encoders = {
82
+ datetime: lambda dt: dt.isoformat()
83
+ }
84
+
85
+ @classmethod
86
+ def from_api_response(cls, data: Dict[str, Any]) -> "MergeConflictModel":
87
+ """
88
+ Create a MergeConflictModel instance from an API response.
89
+
90
+ Args:
91
+ data: API response data.
92
+
93
+ Returns:
94
+ A MergeConflictModel instance.
95
+ """
96
+ conflict_data = data.get("conflict", data)
97
+
98
+ return cls(
99
+ id=conflict_data["id"],
100
+ custom_user_id=conflict_data["custom_user_id"],
101
+ project_user_session_id=conflict_data["project_user_session_id"],
102
+ new_memory_content=conflict_data["new_memory_content"],
103
+ conflicting_memories=[
104
+ MergeConflictMemory(**memory) for memory in conflict_data["conflicting_memories"]
105
+ ],
106
+ clarifying_questions=[
107
+ MergeConflictQuestion(**question) for question in conflict_data["clarifying_questions"]
108
+ ],
109
+ status=MergeConflictStatus(conflict_data["status"]),
110
+ resolution_data=conflict_data.get("resolution_data"),
111
+ created_at=conflict_data["created_at"],
112
+ resolved_at=conflict_data.get("resolved_at"),
113
+ )
114
+
115
+
116
+ class MergeConflictList(BaseModel):
117
+ """
118
+ Represents a paginated list of merge conflicts.
119
+ """
120
+ conflicts: List["MergeConflict"] = Field(..., description="List of merge conflicts.")
121
+ total: int = Field(..., description="Total number of conflicts.")
122
+ has_more: bool = Field(..., description="Whether there are more conflicts to fetch.")
123
+
124
+ class Config:
125
+ """Pydantic configuration."""
126
+ frozen = True
127
+ arbitrary_types_allowed = True
128
+
129
+ @classmethod
130
+ def from_api_response(cls, data: Dict[str, Any], http_client: HTTPClient, user_id: str) -> "MergeConflictList":
131
+ """
132
+ Create a MergeConflictList instance from an API response.
133
+
134
+ Args:
135
+ data: API response data.
136
+ http_client: HTTP client for making API requests.
137
+ user_id: User ID who owns these conflicts.
138
+
139
+ Returns:
140
+ A MergeConflictList instance.
141
+ """
142
+ from ..merge_conflict import MergeConflict
143
+
144
+ return cls(
145
+ conflicts=[
146
+ MergeConflict(http_client, user_id, MergeConflictModel.from_api_response(conflict))
147
+ for conflict in data["conflicts"]
148
+ ],
149
+ total=data["total"],
150
+ has_more=data["has_more"],
151
+ )
@@ -1,16 +1,14 @@
1
- # Path: recallrai/models/session.py
2
- # Description: Session data models for the RecallrAI SDK
3
-
4
1
  """
5
2
  Session-related data models for the RecallrAI SDK.
6
3
  """
7
4
 
8
- import enum, uuid
5
+ import enum
9
6
  from datetime import datetime
10
- from typing import Any, Dict, List, Optional
11
-
7
+ from typing import TYPE_CHECKING, Any, Dict, List
12
8
  from pydantic import BaseModel, Field
13
-
9
+ from ..utils import HTTPClient
10
+ if TYPE_CHECKING:
11
+ from ..session import Session
14
12
 
15
13
  class MessageRole(str, enum.Enum):
16
14
  """
@@ -24,9 +22,9 @@ class Message(BaseModel):
24
22
  """
25
23
  Represents a message in a conversation session.
26
24
  """
27
- role: MessageRole = Field(..., description="Role of the message sender (user or assistant)")
28
- content: str = Field(..., description="Content of the message")
29
- timestamp: datetime = Field(..., description="When the message was sent")
25
+ role: MessageRole = Field(..., description="Role of the message sender (user or assistant).")
26
+ content: str = Field(..., description="Content of the message.")
27
+ timestamp: datetime = Field(..., description="When the message was sent.")
30
28
 
31
29
  class Config:
32
30
  """Pydantic configuration."""
@@ -36,6 +34,27 @@ class Message(BaseModel):
36
34
  }
37
35
 
38
36
 
37
+ class SessionMessagesList(BaseModel):
38
+ """
39
+ Represents a paginated list of messages in a session.
40
+ """
41
+
42
+ messages: List[Message] = Field(..., description="List of messages in the page.")
43
+ total: int = Field(..., description="Total number of messages in the session.")
44
+ has_more: bool = Field(..., description="Whether there are more messages to fetch.")
45
+
46
+ class Config:
47
+ frozen = True
48
+
49
+ @classmethod
50
+ def from_api_response(cls, data: Dict[str, Any]) -> "SessionMessagesList":
51
+ return cls(
52
+ messages=[Message(**msg) for msg in data["messages"]],
53
+ total=data["total"],
54
+ has_more=data["has_more"],
55
+ )
56
+
57
+
39
58
  class SessionStatus(str, enum.Enum):
40
59
  """
41
60
  Status of a session.
@@ -45,38 +64,42 @@ class SessionStatus(str, enum.Enum):
45
64
  PROCESSED = "processed"
46
65
 
47
66
 
48
- class Session(BaseModel):
67
+ class SessionModel(BaseModel):
49
68
  """
50
69
  Represents a conversation session.
51
70
  """
52
- session_id: uuid.UUID = Field(..., description="Unique identifier for the session")
53
- status: SessionStatus = Field(..., description="Current status of the session")
54
- created_at: datetime = Field(..., description="When the session was created")
71
+ session_id: str = Field(..., description="Unique identifier for the session.")
72
+ status: SessionStatus = Field(..., description="Current status of the session.")
73
+ created_at: datetime = Field(..., description="When the session was created.")
74
+ metadata: Dict[str, Any] = Field(default_factory=dict, description="Optional metadata for the session.")
55
75
 
56
76
  class Config:
57
77
  """Pydantic configuration."""
58
78
  frozen = True
59
79
  json_encoders = {
60
80
  datetime: lambda dt: dt.isoformat(),
61
- uuid.UUID: lambda id: str(id)
62
81
  }
63
82
 
64
83
  @classmethod
65
- def from_api_response(cls, data: Dict[str, Any]) -> "Session":
84
+ def from_api_response(cls, data: Dict[str, Any]) -> "SessionModel":
66
85
  """
67
- Create a Session instance from an API response.
86
+ Create a SessionModel instance from an API response.
68
87
 
69
88
  Args:
70
- data: API response data
89
+ data: API response data.
71
90
 
72
91
  Returns:
73
- A Session instance
92
+ A SessionModel instance.
74
93
  """
94
+ if "session" in data:
95
+ session_data = data["session"]
96
+ else:
97
+ session_data = data
75
98
  return cls(
76
- session_id=data["session_id"],
77
- status=data.get("status", SessionStatus.PENDING),
78
- created_at=data.get("created_at", datetime.now()),
79
- messages=None
99
+ session_id=session_data["session_id"],
100
+ status=session_data["status"],
101
+ created_at=session_data["created_at"],
102
+ metadata=session_data["metadata"],
80
103
  )
81
104
 
82
105
 
@@ -84,38 +107,48 @@ class SessionList(BaseModel):
84
107
  """
85
108
  Represents a paginated list of sessions.
86
109
  """
87
- sessions: List[Session] = Field(..., description="List of sessions")
88
- total: int = Field(..., description="Total number of sessions")
89
- has_more: bool = Field(..., description="Whether there are more sessions to fetch")
110
+ sessions: List["Session"] = Field(..., description="List of sessions.")
111
+ total: int = Field(..., description="Total number of sessions.")
112
+ has_more: bool = Field(..., description="Whether there are more sessions to fetch.")
90
113
 
91
114
  class Config:
92
115
  """Pydantic configuration."""
93
116
  frozen = True
117
+ arbitrary_types_allowed = True
94
118
 
95
119
  @classmethod
96
- def from_api_response(cls, data: Dict[str, Any]) -> "SessionList":
120
+ def from_api_response(cls, data: Dict[str, Any], user_id: str, http_client: HTTPClient) -> "SessionList":
97
121
  """
98
122
  Create a SessionList instance from an API response.
99
123
 
100
124
  Args:
101
- data: API response data
125
+ data: API response data.
102
126
 
103
127
  Returns:
104
- A SessionList instance
128
+ A SessionList instance.
105
129
  """
130
+ from ..session import Session
106
131
  return cls(
107
- sessions=[Session.from_api_response(session) for session in data["sessions"]],
132
+ sessions=[
133
+ Session(http_client, user_id, SessionModel.from_api_response(session)) for session in data["sessions"]
134
+ ],
108
135
  total=data["total"],
109
136
  has_more=data["has_more"],
110
137
  )
111
138
 
139
+ class RecallStrategy(str, enum.Enum):
140
+ """
141
+ Type of recall strategy.
142
+ """
143
+ LOW_LATENCY = "low_latency"
144
+ BALANCED = "balanced"
145
+ DEEP = "deep"
112
146
 
113
147
  class Context(BaseModel):
114
148
  """
115
149
  Represents the context for a session.
116
150
  """
117
- memory_used: bool = Field(..., description="Whether memory was used to generate the context")
118
- context: str = Field(..., description="The context for the session")
151
+ context: str = Field(..., description="The context for the session.")
119
152
 
120
153
  class Config:
121
154
  """Pydantic configuration."""
@@ -127,12 +160,11 @@ class Context(BaseModel):
127
160
  Create a Context instance from an API response.
128
161
 
129
162
  Args:
130
- data: API response data
163
+ data: API response data.
131
164
 
132
165
  Returns:
133
- A Context instance
166
+ A Context instance.
134
167
  """
135
168
  return cls(
136
- memory_used=data["memory_used"],
137
169
  context=data["context"],
138
170
  )