jl-ecms-client 0.2.5__py3-none-any.whl → 0.2.19__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.
mirix/client/utils.py ADDED
@@ -0,0 +1,34 @@
1
+ """
2
+ Lightweight utilities for Mirix client package.
3
+
4
+ Contains only essential utility functions needed by the client,
5
+ without heavy dependencies.
6
+ """
7
+
8
+ import json
9
+ from datetime import datetime, timezone
10
+
11
+
12
+ def get_utc_time() -> datetime:
13
+ """Get the current UTC time"""
14
+ return datetime.now(timezone.utc)
15
+
16
+
17
+ def json_dumps(data, indent=2):
18
+ """
19
+ JSON serializer that handles datetime objects.
20
+
21
+ Args:
22
+ data: Data to serialize
23
+ indent: JSON indentation level
24
+
25
+ Returns:
26
+ str: JSON string
27
+ """
28
+
29
+ def safe_serializer(obj):
30
+ if isinstance(obj, datetime):
31
+ return obj.isoformat()
32
+ raise TypeError(f"Type {type(obj)} not serializable")
33
+
34
+ return json.dumps(data, indent=indent, default=safe_serializer, ensure_ascii=False)
mirix/constants.py ADDED
@@ -0,0 +1,251 @@
1
+ import os
2
+ from logging import CRITICAL, DEBUG, ERROR, INFO, NOTSET, WARN, WARNING
3
+
4
+ # ============================================================================
5
+ # Client Constants - Used by both client and server
6
+ # ============================================================================
7
+
8
+ # Default organization and user IDs (needed by schemas)
9
+ DEFAULT_ORG_ID = "org-00000000-0000-4000-8000-000000000000"
10
+ DEFAULT_USER_ID = "user-00000000-0000-4000-8000-000000000000"
11
+
12
+ # Embedding constants
13
+ MAX_EMBEDDING_DIM = 4096 # maximum supported embedding size - do NOT change or else DBs will need to be reset
14
+ DEFAULT_EMBEDDING_CHUNK_SIZE = 300
15
+ MIN_CONTEXT_WINDOW = 4096
16
+
17
+ # Memory limits
18
+ CORE_MEMORY_BLOCK_CHAR_LIMIT: int = 5000
19
+
20
+ # Function/Tool constants
21
+ FUNCTION_RETURN_CHAR_LIMIT = 60000 # ~300 words
22
+ TOOL_CALL_ID_MAX_LEN = 29
23
+
24
+ # Tool module names
25
+ COMPOSIO_TOOL_TAG_NAME = "composio"
26
+ MIRIX_CORE_TOOL_MODULE_NAME = "mirix.functions.function_sets.base"
27
+ MIRIX_MEMORY_TOOL_MODULE_NAME = "mirix.functions.function_sets.memory_tools"
28
+ MIRIX_EXTRA_TOOL_MODULE_NAME = "mirix.functions.function_sets.extras"
29
+
30
+ # Message defaults
31
+ DEFAULT_MESSAGE_TOOL = "send_message"
32
+ DEFAULT_MESSAGE_TOOL_KWARG = "message"
33
+
34
+ # LLM model token limits
35
+ LLM_MAX_TOKENS = {
36
+ "DEFAULT": 8192,
37
+ ## OpenAI models: https://platform.openai.com/docs/models/overview
38
+ "chatgpt-4o-latest": 128000,
39
+ "gpt-4o-2024-08-06": 128000,
40
+ "gpt-4-turbo-preview": 128000,
41
+ "gpt-4o": 128000,
42
+ "gpt-3.5-turbo-instruct": 16385,
43
+ "gpt-4-0125-preview": 128000,
44
+ "gpt-3.5-turbo-0125": 16385,
45
+ "gpt-4-turbo-2024-04-09": 128000,
46
+ "gpt-4-turbo": 8192,
47
+ "gpt-4o-2024-05-13": 128000,
48
+ "gpt-4o-mini": 128000,
49
+ "gpt-4o-mini-2024-07-18": 128000,
50
+ "gpt-4-1106-preview": 128000,
51
+ "gpt-4": 8192,
52
+ "gpt-4-32k": 32768,
53
+ "gpt-4-0613": 8192,
54
+ "gpt-4-32k-0613": 32768,
55
+ "gpt-4-0314": 8192, # legacy
56
+ "gpt-4-32k-0314": 32768, # legacy
57
+ "gpt-3.5-turbo-1106": 16385,
58
+ "gpt-3.5-turbo": 4096,
59
+ "gpt-3.5-turbo-16k": 16385,
60
+ "gpt-3.5-turbo-0613": 4096, # legacy
61
+ "gpt-3.5-turbo-16k-0613": 16385, # legacy
62
+ "gpt-3.5-turbo-0301": 4096, # legacy
63
+ }
64
+
65
+ # ============================================================================
66
+ # Server-Only Constants
67
+ # ============================================================================
68
+
69
+ MIRIX_DIR = os.path.join(os.path.expanduser("~"), ".mirix")
70
+ MIRIX_DIR_TOOL_SANDBOX = os.path.join(MIRIX_DIR, "tool_sandbox_dir")
71
+
72
+ ADMIN_PREFIX = "/v1/admin"
73
+ API_PREFIX = "/v1"
74
+ OPENAI_API_PREFIX = "/openai"
75
+
76
+ COMPOSIO_ENTITY_ENV_VAR_KEY = "COMPOSIO_ENTITY"
77
+
78
+ # String in the error message for when the context window is too large
79
+ # Example full message:
80
+ # This model's maximum context length is 8192 tokens. However, your messages resulted in 8198 tokens (7450 in the messages, 748 in the functions). Please reduce the length of the messages or functions.
81
+ OPENAI_CONTEXT_WINDOW_ERROR_SUBSTRING = "maximum context length"
82
+
83
+ # System prompt templating
84
+ IN_CONTEXT_MEMORY_KEYWORD = "CORE_MEMORY"
85
+
86
+ MAX_CHAINING_STEPS = 10
87
+ MAX_RETRIEVAL_LIMIT_IN_SYSTEM = 10
88
+
89
+ # tokenizers
90
+ EMBEDDING_TO_TOKENIZER_MAP = {
91
+ "text-embedding-3-small": "cl100k_base",
92
+ }
93
+ EMBEDDING_TO_TOKENIZER_DEFAULT = "cl100k_base"
94
+
95
+
96
+ DEFAULT_MIRIX_MODEL = "gpt-4" # TODO: fixme
97
+ DEFAULT_PERSONA = "sam_pov"
98
+ DEFAULT_HUMAN = "basic"
99
+ DEFAULT_PRESET = "memgpt_chat"
100
+
101
+ # Base tools that cannot be edited, as they access agent state directly
102
+ # Note that we don't include "conversation_search_date" for now
103
+ BASE_TOOLS = [
104
+ "send_intermediate_message",
105
+ "conversation_search",
106
+ "search_in_memory",
107
+ "list_memory_within_timerange",
108
+ ]
109
+ # Base memory tools CAN be edited, and are added by default by the server
110
+ CORE_MEMORY_TOOLS = ["core_memory_append", "core_memory_rewrite"]
111
+ EPISODIC_MEMORY_TOOLS = [
112
+ "episodic_memory_insert",
113
+ "episodic_memory_merge",
114
+ "episodic_memory_replace",
115
+ "check_episodic_memory",
116
+ ]
117
+ PROCEDURAL_MEMORY_TOOLS = ["procedural_memory_insert", "procedural_memory_update"]
118
+ RESOURCE_MEMORY_TOOLS = ["resource_memory_insert", "resource_memory_update"]
119
+ KNOWLEDGE_VAULT_TOOLS = ["knowledge_vault_insert", "knowledge_vault_update"]
120
+ SEMANTIC_MEMORY_TOOLS = [
121
+ "semantic_memory_insert",
122
+ "semantic_memory_update",
123
+ "check_semantic_memory",
124
+ ]
125
+ CHAT_AGENT_TOOLS = []
126
+ EXTRAS_TOOLS = ["web_search", "fetch_and_read_pdf"]
127
+ MCP_TOOLS = []
128
+ META_MEMORY_TOOLS = ["trigger_memory_update"]
129
+ SEARCH_MEMORY_TOOLS = ["search_in_memory", "list_memory_within_timerange"]
130
+ UNIVERSAL_MEMORY_TOOLS = [
131
+ "search_in_memory",
132
+ "finish_memory_update",
133
+ "list_memory_within_timerange",
134
+ ]
135
+ ALL_TOOLS = list(
136
+ set(
137
+ BASE_TOOLS
138
+ + CORE_MEMORY_TOOLS
139
+ + EPISODIC_MEMORY_TOOLS
140
+ + PROCEDURAL_MEMORY_TOOLS
141
+ + RESOURCE_MEMORY_TOOLS
142
+ + KNOWLEDGE_VAULT_TOOLS
143
+ + SEMANTIC_MEMORY_TOOLS
144
+ + META_MEMORY_TOOLS
145
+ + UNIVERSAL_MEMORY_TOOLS
146
+ + CHAT_AGENT_TOOLS
147
+ + EXTRAS_TOOLS
148
+ + MCP_TOOLS
149
+ )
150
+ )
151
+
152
+ # The name of the tool used to send message to the user
153
+ # Structured output models
154
+ STRUCTURED_OUTPUT_MODELS = {"gpt-4o", "gpt-4o-mini"}
155
+
156
+ # LOGGER_LOG_LEVEL is use to convert Text to Logging level value for logging mostly for Cli input to setting level
157
+ LOGGER_LOG_LEVELS = {
158
+ "CRITICAL": CRITICAL,
159
+ "ERROR": ERROR,
160
+ "WARN": WARN,
161
+ "WARNING": WARNING,
162
+ "INFO": INFO,
163
+ "DEBUG": DEBUG,
164
+ "NOTSET": NOTSET,
165
+ }
166
+
167
+ FIRST_MESSAGE_ATTEMPTS = 10
168
+
169
+ INITIAL_BOOT_MESSAGE = "Boot sequence complete. Persona activated."
170
+ INITIAL_BOOT_MESSAGE_SEND_MESSAGE_THOUGHT = (
171
+ "Bootup sequence complete. Persona activated. Testing messaging functionality."
172
+ )
173
+ STARTUP_QUOTES = [
174
+ "I think, therefore I am.",
175
+ "All those moments will be lost in time, like tears in rain.",
176
+ "More human than human is our motto.",
177
+ ]
178
+ INITIAL_BOOT_MESSAGE_SEND_MESSAGE_FIRST_MSG = STARTUP_QUOTES[2]
179
+
180
+ CLI_WARNING_PREFIX = "Warning: "
181
+
182
+ ERROR_MESSAGE_PREFIX = "Error"
183
+
184
+ NON_USER_MSG_PREFIX = "[This is an automated system message hidden from the user] "
185
+
186
+ # The error message that Mirix will receive
187
+ # MESSAGE_SUMMARY_WARNING_STR = f"Warning: the conversation history will soon reach its maximum length and be trimmed. Make sure to save any important information from the conversation to your memory before it is removed."
188
+ # Much longer and more specific variant of the prompt
189
+ # TODO: Emit the warning to Meta Memory Manager instead of the Chat Agent.
190
+ MESSAGE_SUMMARY_WARNING_STR = " ".join(
191
+ [
192
+ f"{NON_USER_MSG_PREFIX}The conversation history will soon reach its maximum length and be trimmed.",
193
+ "Do NOT tell the user about this system alert, they should not know that the history is reaching max length.",
194
+ ]
195
+ )
196
+
197
+ # The ackknowledgement message used in the summarize sequence
198
+ MESSAGE_SUMMARY_REQUEST_ACK = "Understood, I will respond with a summary of the message (and only the summary, nothing else) once I receive the conversation history. I'm ready."
199
+
200
+ # Maximum length of an error message
201
+ MAX_ERROR_MESSAGE_CHAR_LIMIT = 500
202
+
203
+ # Default memory limits
204
+ CORE_MEMORY_PERSONA_CHAR_LIMIT: int = 5000
205
+ CORE_MEMORY_HUMAN_CHAR_LIMIT: int = 5000
206
+
207
+ MAX_PAUSE_HEARTBEATS = 360 # in min
208
+
209
+ MESSAGE_CHATGPT_FUNCTION_MODEL = "gpt-3.5-turbo"
210
+ MESSAGE_CHATGPT_FUNCTION_SYSTEM_MESSAGE = (
211
+ "You are a helpful assistant. Keep your responses short and concise."
212
+ )
213
+
214
+ #### Functions related
215
+
216
+ # REQ_HEARTBEAT_MESSAGE = f"{NON_USER_MSG_PREFIX}continue_chaining == true"
217
+ REQ_HEARTBEAT_MESSAGE = f"{NON_USER_MSG_PREFIX}Function called using continue_chaining=true, returning control"
218
+ # FUNC_FAILED_HEARTBEAT_MESSAGE = f"{NON_USER_MSG_PREFIX}Function call failed"
219
+ FUNC_FAILED_HEARTBEAT_MESSAGE = (
220
+ f"{NON_USER_MSG_PREFIX}Function call failed, returning control"
221
+ )
222
+
223
+
224
+ RETRIEVAL_QUERY_DEFAULT_PAGE_SIZE = 5
225
+
226
+ MAX_FILENAME_LENGTH = 255
227
+ RESERVED_FILENAMES = {"CON", "PRN", "AUX", "NUL", "COM1", "COM2", "LPT1", "LPT2"}
228
+
229
+ MAX_IMAGES_TO_PROCESS = 100
230
+
231
+ DEFAULT_WRAPPER_NAME = "chatml"
232
+ INNER_THOUGHTS_KWARG_DESCRIPTION = "Deep inner monologue private to you only."
233
+ INNER_THOUGHTS_CLI_SYMBOL = "💭"
234
+ ASSISTANT_MESSAGE_CLI_SYMBOL = "🤖"
235
+
236
+ CLEAR_HISTORY_AFTER_MEMORY_UPDATE = os.getenv(
237
+ "CLEAR_HISTORY_AFTER_MEMORY_UPDATE", "true"
238
+ ).lower() in ("true", "1", "yes")
239
+ CALL_MEMORY_AGENT_IN_PARALLEL = os.getenv(
240
+ "CALL_MEMORY_AGENT_IN_PARALLEL", "false"
241
+ ).lower() in ("true", "1", "yes")
242
+ CHAINING_FOR_MEMORY_UPDATE = os.getenv(
243
+ "CHAINING_FOR_MEMORY_UPDATE", "false"
244
+ ).lower() in ("true", "1", "yes")
245
+
246
+ LOAD_IMAGE_CONTENT_FOR_LAST_MESSAGE_ONLY = os.getenv(
247
+ "LOAD_IMAGE_CONTENT_FOR_LAST_MESSAGE_ONLY", "false"
248
+ ).lower() in ("true", "1", "yes")
249
+ BUILD_EMBEDDINGS_FOR_MEMORY = os.getenv(
250
+ "BUILD_EMBEDDINGS_FOR_MEMORY", "true"
251
+ ).lower() in ("true", "1", "yes")
mirix/errors.py ADDED
@@ -0,0 +1,238 @@
1
+ import json
2
+ from enum import Enum
3
+ from typing import TYPE_CHECKING, List, Optional, Union
4
+
5
+ # Avoid circular imports
6
+ if TYPE_CHECKING:
7
+ from mirix.schemas.message import Message
8
+ from mirix.schemas.mirix_message import MirixMessage
9
+
10
+
11
+ class ErrorCode(Enum):
12
+ """Enum for error codes used by client."""
13
+
14
+ INTERNAL_SERVER_ERROR = "INTERNAL_SERVER_ERROR"
15
+ CONTEXT_WINDOW_EXCEEDED = "CONTEXT_WINDOW_EXCEEDED"
16
+ RATE_LIMIT_EXCEEDED = "RATE_LIMIT_EXCEEDED"
17
+
18
+
19
+ class MirixError(Exception):
20
+ """Base class for all Mirix related errors."""
21
+
22
+ def __init__(
23
+ self, message: str, code: Optional[ErrorCode] = None, details: dict = {}
24
+ ):
25
+ self.message = message
26
+ self.code = code
27
+ self.details = details
28
+ super().__init__(message)
29
+
30
+ def __str__(self) -> str:
31
+ if self.code:
32
+ return f"{self.code.value}: {self.message}"
33
+ return self.message
34
+
35
+ def __repr__(self) -> str:
36
+ return f"{self.__class__.__name__}(message='{self.message}', code='{self.code}', details={self.details})"
37
+
38
+
39
+ class MirixToolCreateError(MirixError):
40
+ """Error raised when a tool cannot be created."""
41
+
42
+ default_error_message = "Error creating tool."
43
+
44
+ def __init__(self, message=None):
45
+ super().__init__(message=message or self.default_error_message)
46
+
47
+
48
+ class MirixConfigurationError(MirixError):
49
+ """Error raised when there are configuration-related issues."""
50
+
51
+ def __init__(self, message: str, missing_fields: Optional[List[str]] = None):
52
+ self.missing_fields = missing_fields or []
53
+ super().__init__(
54
+ message=message, details={"missing_fields": self.missing_fields}
55
+ )
56
+
57
+
58
+ class MirixAgentNotFoundError(MirixError):
59
+ """Error raised when an agent is not found."""
60
+
61
+
62
+ class MirixUserNotFoundError(MirixError):
63
+ """Error raised when a user is not found."""
64
+
65
+
66
+ class LLMError(MirixError):
67
+ pass
68
+
69
+
70
+ class LLMAuthenticationError(LLMError):
71
+ """Error raised when LLM authentication fails."""
72
+
73
+ pass
74
+
75
+
76
+ class LLMBadRequestError(LLMError):
77
+ """Error raised when LLM request is malformed."""
78
+
79
+ pass
80
+
81
+
82
+ class LLMConnectionError(LLMError):
83
+ """Error raised when LLM connection fails."""
84
+
85
+ pass
86
+
87
+
88
+ class LLMNotFoundError(LLMError):
89
+ """Error raised when LLM resource is not found."""
90
+
91
+ pass
92
+
93
+
94
+ class LLMPermissionDeniedError(LLMError):
95
+ """Error raised when LLM permission is denied."""
96
+
97
+ pass
98
+
99
+
100
+ class LLMRateLimitError(LLMError):
101
+ """Error raised when LLM rate limit is exceeded."""
102
+
103
+ pass
104
+
105
+
106
+ class LLMServerError(LLMError):
107
+ """Error raised when LLM server encounters an error."""
108
+
109
+ pass
110
+
111
+
112
+ class LLMUnprocessableEntityError(LLMError):
113
+ """Error raised when LLM cannot process the entity."""
114
+
115
+ pass
116
+
117
+
118
+ class BedrockPermissionError(MirixError):
119
+ """Exception raised for errors in the Bedrock permission process."""
120
+
121
+ def __init__(
122
+ self,
123
+ message="User does not have access to the Bedrock model with the specified ID.",
124
+ ):
125
+ super().__init__(message=message)
126
+
127
+
128
+ class BedrockError(MirixError):
129
+ """Exception raised for errors in the Bedrock process."""
130
+
131
+ def __init__(self, message="Error with Bedrock model."):
132
+ super().__init__(message=message)
133
+
134
+
135
+ class LLMJSONParsingError(MirixError):
136
+ """Exception raised for errors in the JSON parsing process."""
137
+
138
+ def __init__(self, message="Error parsing JSON generated by LLM"):
139
+ super().__init__(message=message)
140
+
141
+
142
+ class LocalLLMError(MirixError):
143
+ """Generic catch-all error for local LLM problems"""
144
+
145
+ def __init__(self, message="Encountered an error while running local LLM"):
146
+ super().__init__(message=message)
147
+
148
+
149
+ class LocalLLMConnectionError(MirixError):
150
+ """Error for when local LLM cannot be reached with provided IP/port"""
151
+
152
+ def __init__(self, message="Could not connect to local LLM"):
153
+ super().__init__(message=message)
154
+
155
+
156
+ class ContextWindowExceededError(MirixError):
157
+ """Error raised when the context window is exceeded but further summarization fails."""
158
+
159
+ def __init__(self, message: str, details: dict = {}):
160
+ error_message = f"{message} ({details})"
161
+ super().__init__(
162
+ message=error_message,
163
+ code=ErrorCode.CONTEXT_WINDOW_EXCEEDED,
164
+ details=details,
165
+ )
166
+
167
+
168
+ class RateLimitExceededError(MirixError):
169
+ """Error raised when the llm rate limiter throttles api requests."""
170
+
171
+ def __init__(self, message: str, max_retries: int):
172
+ error_message = f"{message} ({max_retries})"
173
+ super().__init__(
174
+ message=error_message,
175
+ code=ErrorCode.RATE_LIMIT_EXCEEDED,
176
+ details={"max_retries": max_retries},
177
+ )
178
+
179
+
180
+ class MirixMessageError(MirixError):
181
+ """Base error class for handling message-related errors."""
182
+
183
+ messages: List[Union["Message", "MirixMessage"]]
184
+ default_error_message: str = "An error occurred with the message."
185
+
186
+ def __init__(
187
+ self,
188
+ *,
189
+ messages: List[Union["Message", "MirixMessage"]],
190
+ explanation: Optional[str] = None,
191
+ ) -> None:
192
+ error_msg = self.construct_error_message(
193
+ messages, self.default_error_message, explanation
194
+ )
195
+ super().__init__(error_msg)
196
+ self.messages = messages
197
+
198
+ @staticmethod
199
+ def construct_error_message(
200
+ messages: List[Union["Message", "MirixMessage"]],
201
+ error_msg: str,
202
+ explanation: Optional[str] = None,
203
+ ) -> str:
204
+ """Helper method to construct a clean and formatted error message."""
205
+ if explanation:
206
+ error_msg += f" (Explanation: {explanation})"
207
+
208
+ # Pretty print out message JSON
209
+ message_json = json.dumps(
210
+ [message.model_dump() for message in messages], indent=4
211
+ )
212
+ return f"{error_msg}\n\n{message_json}"
213
+
214
+
215
+ class MissingToolCallError(MirixMessageError):
216
+ """Error raised when a message is missing a tool call."""
217
+
218
+ default_error_message = "The message is missing a tool call."
219
+
220
+
221
+ class InvalidToolCallError(MirixMessageError):
222
+ """Error raised when a message uses an invalid tool call."""
223
+
224
+ default_error_message = (
225
+ "The message uses an invalid tool call or has improper usage of a tool call."
226
+ )
227
+
228
+
229
+ class MissingInnerMonologueError(MirixMessageError):
230
+ """Error raised when a message is missing an inner monologue."""
231
+
232
+ default_error_message = "The message is missing an inner monologue."
233
+
234
+
235
+ class InvalidInnerMonologueError(MirixMessageError):
236
+ """Error raised when a message has a malformed inner monologue."""
237
+
238
+ default_error_message = "The message has a malformed inner monologue."
@@ -28,13 +28,13 @@ def parse_json(string) -> dict:
28
28
  result = json_loads(string)
29
29
  return result
30
30
  except Exception as e:
31
- logger.debug("Error parsing json with json package: %s", e)
31
+ logger.debug(f"Error parsing json with json package: {e}")
32
32
 
33
33
  try:
34
34
  result = demjson.decode(string)
35
35
  return result
36
36
  except demjson.JSONDecodeError as e:
37
- logger.debug("Error parsing json with demjson package: %s", e)
37
+ logger.debug(f"Error parsing json with demjson package: {e}")
38
38
 
39
39
  try:
40
40
  from json_repair import repair_json
@@ -43,5 +43,5 @@ def parse_json(string) -> dict:
43
43
  return result
44
44
 
45
45
  except Exception as e:
46
- logger.debug("Error repairing json with json_repair package: %s", e)
46
+ logger.debug(f"Error repairing json with json_repair package: {e}")
47
47
  raise e