jl-ecms-client 0.2.8__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 jl-ecms-client might be problematic. Click here for more details.
- jl_ecms_client-0.2.8.dist-info/METADATA +295 -0
- jl_ecms_client-0.2.8.dist-info/RECORD +53 -0
- jl_ecms_client-0.2.8.dist-info/WHEEL +5 -0
- jl_ecms_client-0.2.8.dist-info/licenses/LICENSE +190 -0
- jl_ecms_client-0.2.8.dist-info/top_level.txt +1 -0
- mirix/client/__init__.py +14 -0
- mirix/client/client.py +405 -0
- mirix/client/constants.py +60 -0
- mirix/client/remote_client.py +1136 -0
- mirix/client/utils.py +34 -0
- mirix/helpers/__init__.py +1 -0
- mirix/helpers/converters.py +429 -0
- mirix/helpers/datetime_helpers.py +90 -0
- mirix/helpers/json_helpers.py +47 -0
- mirix/helpers/message_helpers.py +74 -0
- mirix/helpers/tool_rule_solver.py +166 -0
- mirix/schemas/__init__.py +1 -0
- mirix/schemas/agent.py +401 -0
- mirix/schemas/block.py +188 -0
- mirix/schemas/cloud_file_mapping.py +29 -0
- mirix/schemas/embedding_config.py +114 -0
- mirix/schemas/enums.py +69 -0
- mirix/schemas/environment_variables.py +82 -0
- mirix/schemas/episodic_memory.py +170 -0
- mirix/schemas/file.py +57 -0
- mirix/schemas/health.py +10 -0
- mirix/schemas/knowledge_vault.py +181 -0
- mirix/schemas/llm_config.py +187 -0
- mirix/schemas/memory.py +318 -0
- mirix/schemas/message.py +1315 -0
- mirix/schemas/mirix_base.py +107 -0
- mirix/schemas/mirix_message.py +411 -0
- mirix/schemas/mirix_message_content.py +230 -0
- mirix/schemas/mirix_request.py +39 -0
- mirix/schemas/mirix_response.py +183 -0
- mirix/schemas/openai/__init__.py +1 -0
- mirix/schemas/openai/chat_completion_request.py +122 -0
- mirix/schemas/openai/chat_completion_response.py +144 -0
- mirix/schemas/openai/chat_completions.py +127 -0
- mirix/schemas/openai/embedding_response.py +11 -0
- mirix/schemas/openai/openai.py +229 -0
- mirix/schemas/organization.py +38 -0
- mirix/schemas/procedural_memory.py +151 -0
- mirix/schemas/providers.py +816 -0
- mirix/schemas/resource_memory.py +134 -0
- mirix/schemas/sandbox_config.py +132 -0
- mirix/schemas/semantic_memory.py +162 -0
- mirix/schemas/source.py +96 -0
- mirix/schemas/step.py +53 -0
- mirix/schemas/tool.py +241 -0
- mirix/schemas/tool_rule.py +209 -0
- mirix/schemas/usage.py +31 -0
- mirix/schemas/user.py +67 -0
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)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from mirix.helpers.tool_rule_solver import ToolRulesSolver as ToolRulesSolver
|
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
from typing import Any, Dict, List, Optional, Union
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
from anthropic.types.beta.messages import (
|
|
6
|
+
BetaMessageBatch,
|
|
7
|
+
BetaMessageBatchIndividualResponse,
|
|
8
|
+
)
|
|
9
|
+
from sqlalchemy import Dialect
|
|
10
|
+
|
|
11
|
+
from mirix.schemas.agent import AgentStepState
|
|
12
|
+
from mirix.schemas.embedding_config import EmbeddingConfig
|
|
13
|
+
from mirix.schemas.enums import ProviderType, ToolRuleType
|
|
14
|
+
from mirix.schemas.llm_config import LLMConfig
|
|
15
|
+
from mirix.schemas.message import ToolReturn
|
|
16
|
+
from mirix.schemas.mirix_message_content import (
|
|
17
|
+
CloudFileContent,
|
|
18
|
+
FileContent,
|
|
19
|
+
ImageContent,
|
|
20
|
+
MessageContent,
|
|
21
|
+
MessageContentType,
|
|
22
|
+
OmittedReasoningContent,
|
|
23
|
+
ReasoningContent,
|
|
24
|
+
RedactedReasoningContent,
|
|
25
|
+
TextContent,
|
|
26
|
+
ToolCallContent,
|
|
27
|
+
ToolReturnContent,
|
|
28
|
+
)
|
|
29
|
+
from mirix.schemas.openai.openai import Function as OpenAIFunction
|
|
30
|
+
from mirix.schemas.openai.openai import ToolCall as OpenAIToolCall
|
|
31
|
+
from mirix.schemas.tool_rule import (
|
|
32
|
+
ChildToolRule,
|
|
33
|
+
ConditionalToolRule,
|
|
34
|
+
ContinueToolRule,
|
|
35
|
+
InitToolRule,
|
|
36
|
+
MaxCountPerStepToolRule,
|
|
37
|
+
ParentToolRule,
|
|
38
|
+
TerminalToolRule,
|
|
39
|
+
ToolRule,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# --------------------------
|
|
43
|
+
# LLMConfig Serialization
|
|
44
|
+
# --------------------------
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def serialize_llm_config(config: Union[Optional[LLMConfig], Dict]) -> Optional[Dict]:
|
|
48
|
+
"""Convert an LLMConfig object into a JSON-serializable dictionary."""
|
|
49
|
+
if config and isinstance(config, LLMConfig):
|
|
50
|
+
return config.model_dump(mode="json")
|
|
51
|
+
return config
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def deserialize_llm_config(data: Optional[Dict]) -> Optional[LLMConfig]:
|
|
55
|
+
"""Convert a dictionary back into an LLMConfig object."""
|
|
56
|
+
return LLMConfig(**data) if data else None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# --------------------------
|
|
60
|
+
# EmbeddingConfig Serialization
|
|
61
|
+
# --------------------------
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def serialize_embedding_config(
|
|
65
|
+
config: Union[Optional[EmbeddingConfig], Dict],
|
|
66
|
+
) -> Optional[Dict]:
|
|
67
|
+
"""Convert an EmbeddingConfig object into a JSON-serializable dictionary."""
|
|
68
|
+
if config and isinstance(config, EmbeddingConfig):
|
|
69
|
+
return config.model_dump(mode="json")
|
|
70
|
+
return config
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def deserialize_embedding_config(data: Optional[Dict]) -> Optional[EmbeddingConfig]:
|
|
74
|
+
"""Convert a dictionary back into an EmbeddingConfig object."""
|
|
75
|
+
return EmbeddingConfig(**data) if data else None
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# --------------------------
|
|
79
|
+
# ToolRule Serialization
|
|
80
|
+
# --------------------------
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def serialize_tool_rules(tool_rules: Optional[List[ToolRule]]) -> List[Dict[str, Any]]:
|
|
84
|
+
"""Convert a list of ToolRules into a JSON-serializable format."""
|
|
85
|
+
|
|
86
|
+
if not tool_rules:
|
|
87
|
+
return []
|
|
88
|
+
|
|
89
|
+
data = [
|
|
90
|
+
{**rule.model_dump(mode="json"), "type": rule.type.value} for rule in tool_rules
|
|
91
|
+
] # Convert Enum to string for JSON compatibility
|
|
92
|
+
|
|
93
|
+
# Validate ToolRule structure
|
|
94
|
+
for rule_data in data:
|
|
95
|
+
if (
|
|
96
|
+
rule_data["type"] == ToolRuleType.constrain_child_tools.value
|
|
97
|
+
and "children" not in rule_data
|
|
98
|
+
):
|
|
99
|
+
raise ValueError(
|
|
100
|
+
f"Invalid ToolRule serialization: 'children' field missing for rule {rule_data}"
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
return data
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def deserialize_tool_rules(data: Optional[List[Dict]]) -> List[ToolRule]:
|
|
107
|
+
"""Convert a list of dictionaries back into ToolRule objects."""
|
|
108
|
+
if not data:
|
|
109
|
+
return []
|
|
110
|
+
|
|
111
|
+
return [deserialize_tool_rule(rule_data) for rule_data in data]
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def deserialize_tool_rule(
|
|
115
|
+
data: Dict,
|
|
116
|
+
) -> ToolRule:
|
|
117
|
+
"""Deserialize a dictionary to the appropriate ToolRule subclass based on 'type'."""
|
|
118
|
+
rule_type = ToolRuleType(data.get("type"))
|
|
119
|
+
|
|
120
|
+
if rule_type == ToolRuleType.run_first:
|
|
121
|
+
data["type"] = ToolRuleType.run_first
|
|
122
|
+
return InitToolRule(**data)
|
|
123
|
+
elif rule_type == ToolRuleType.exit_loop:
|
|
124
|
+
data["type"] = ToolRuleType.exit_loop
|
|
125
|
+
return TerminalToolRule(**data)
|
|
126
|
+
elif rule_type == ToolRuleType.constrain_child_tools:
|
|
127
|
+
data["type"] = ToolRuleType.constrain_child_tools
|
|
128
|
+
return ChildToolRule(**data)
|
|
129
|
+
elif rule_type == ToolRuleType.conditional:
|
|
130
|
+
return ConditionalToolRule(**data)
|
|
131
|
+
elif rule_type == ToolRuleType.continue_loop:
|
|
132
|
+
return ContinueToolRule(**data)
|
|
133
|
+
elif rule_type == ToolRuleType.max_count_per_step:
|
|
134
|
+
return MaxCountPerStepToolRule(**data)
|
|
135
|
+
elif rule_type == ToolRuleType.parent_last_tool:
|
|
136
|
+
return ParentToolRule(**data)
|
|
137
|
+
raise ValueError(f"Unknown ToolRule type: {rule_type}")
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
# --------------------------
|
|
141
|
+
# ToolCall Serialization
|
|
142
|
+
# --------------------------
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def serialize_tool_calls(
|
|
146
|
+
tool_calls: Optional[List[Union[OpenAIToolCall, dict]]],
|
|
147
|
+
) -> List[Dict]:
|
|
148
|
+
"""Convert a list of OpenAI ToolCall objects into JSON-serializable format."""
|
|
149
|
+
if not tool_calls:
|
|
150
|
+
return []
|
|
151
|
+
|
|
152
|
+
serialized_calls = []
|
|
153
|
+
for call in tool_calls:
|
|
154
|
+
if isinstance(call, OpenAIToolCall):
|
|
155
|
+
serialized_calls.append(call.model_dump(mode="json"))
|
|
156
|
+
elif isinstance(call, dict):
|
|
157
|
+
serialized_calls.append(call) # Already a dictionary, leave it as-is
|
|
158
|
+
else:
|
|
159
|
+
raise TypeError(f"Unexpected tool call type: {type(call)}")
|
|
160
|
+
|
|
161
|
+
return serialized_calls
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def deserialize_tool_calls(data: Optional[List[Dict]]) -> List[OpenAIToolCall]:
|
|
165
|
+
"""Convert a JSON list back into OpenAIToolCall objects."""
|
|
166
|
+
if not data:
|
|
167
|
+
return []
|
|
168
|
+
|
|
169
|
+
calls = []
|
|
170
|
+
for item in data:
|
|
171
|
+
func_data = item.pop("function", None)
|
|
172
|
+
tool_call_function = OpenAIFunction(**func_data)
|
|
173
|
+
calls.append(OpenAIToolCall(function=tool_call_function, **item))
|
|
174
|
+
|
|
175
|
+
return calls
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
# --------------------------
|
|
179
|
+
# ToolReturn Serialization
|
|
180
|
+
# --------------------------
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def serialize_tool_returns(
|
|
184
|
+
tool_returns: Optional[List[Union[ToolReturn, dict]]],
|
|
185
|
+
) -> List[Dict]:
|
|
186
|
+
"""Convert a list of ToolReturn objects into JSON-serializable format."""
|
|
187
|
+
if not tool_returns:
|
|
188
|
+
return []
|
|
189
|
+
|
|
190
|
+
serialized_tool_returns = []
|
|
191
|
+
for tool_return in tool_returns:
|
|
192
|
+
if isinstance(tool_return, ToolReturn):
|
|
193
|
+
serialized_tool_returns.append(tool_return.model_dump(mode="json"))
|
|
194
|
+
elif isinstance(tool_return, dict):
|
|
195
|
+
serialized_tool_returns.append(
|
|
196
|
+
tool_return
|
|
197
|
+
) # Already a dictionary, leave it as-is
|
|
198
|
+
else:
|
|
199
|
+
raise TypeError(f"Unexpected tool return type: {type(tool_return)}")
|
|
200
|
+
|
|
201
|
+
return serialized_tool_returns
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def deserialize_tool_returns(data: Optional[List[Dict]]) -> List[ToolReturn]:
|
|
205
|
+
"""Convert a JSON list back into ToolReturn objects."""
|
|
206
|
+
if not data:
|
|
207
|
+
return []
|
|
208
|
+
|
|
209
|
+
tool_returns = []
|
|
210
|
+
for item in data:
|
|
211
|
+
tool_return = ToolReturn(**item)
|
|
212
|
+
tool_returns.append(tool_return)
|
|
213
|
+
|
|
214
|
+
return tool_returns
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
# ----------------------------
|
|
218
|
+
# MessageContent Serialization
|
|
219
|
+
# ----------------------------
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def serialize_message_content(
|
|
223
|
+
message_content: Optional[List[Union[MessageContent, dict]]],
|
|
224
|
+
) -> List[Dict]:
|
|
225
|
+
"""Convert a list of MessageContent objects into JSON-serializable format."""
|
|
226
|
+
if not message_content:
|
|
227
|
+
return []
|
|
228
|
+
|
|
229
|
+
serialized_message_content = []
|
|
230
|
+
for content in message_content:
|
|
231
|
+
if isinstance(content, MessageContent):
|
|
232
|
+
serialized_message_content.append(content.model_dump(mode="json"))
|
|
233
|
+
elif isinstance(content, dict):
|
|
234
|
+
serialized_message_content.append(
|
|
235
|
+
content
|
|
236
|
+
) # Already a dictionary, leave it as-is
|
|
237
|
+
else:
|
|
238
|
+
raise TypeError(f"Unexpected message content type: {type(content)}")
|
|
239
|
+
|
|
240
|
+
return serialized_message_content
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def deserialize_message_content(data: Optional[List[Dict]]) -> List[MessageContent]:
|
|
244
|
+
"""Convert a JSON list back into MessageContent objects."""
|
|
245
|
+
if not data:
|
|
246
|
+
return []
|
|
247
|
+
|
|
248
|
+
message_content = []
|
|
249
|
+
for item in data:
|
|
250
|
+
if not item:
|
|
251
|
+
continue
|
|
252
|
+
|
|
253
|
+
content_type = item.get("type")
|
|
254
|
+
if content_type == MessageContentType.text:
|
|
255
|
+
content = TextContent(**item)
|
|
256
|
+
elif content_type == MessageContentType.image_url:
|
|
257
|
+
content = ImageContent(**item)
|
|
258
|
+
elif content_type == MessageContentType.file_uri:
|
|
259
|
+
content = FileContent(**item)
|
|
260
|
+
elif content_type == MessageContentType.google_cloud_file_uri:
|
|
261
|
+
content = CloudFileContent(**item)
|
|
262
|
+
elif content_type == MessageContentType.tool_call:
|
|
263
|
+
content = ToolCallContent(**item)
|
|
264
|
+
elif content_type == MessageContentType.tool_return:
|
|
265
|
+
content = ToolReturnContent(**item)
|
|
266
|
+
elif content_type == MessageContentType.reasoning:
|
|
267
|
+
content = ReasoningContent(**item)
|
|
268
|
+
elif content_type == MessageContentType.redacted_reasoning:
|
|
269
|
+
content = RedactedReasoningContent(**item)
|
|
270
|
+
elif content_type == MessageContentType.omitted_reasoning:
|
|
271
|
+
content = OmittedReasoningContent(**item)
|
|
272
|
+
else:
|
|
273
|
+
# Skip invalid content
|
|
274
|
+
continue
|
|
275
|
+
|
|
276
|
+
message_content.append(content)
|
|
277
|
+
|
|
278
|
+
return message_content
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
# --------------------------
|
|
282
|
+
# Vector Serialization
|
|
283
|
+
# --------------------------
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def serialize_vector(
|
|
287
|
+
vector: Optional[Union[List[float], np.ndarray]],
|
|
288
|
+
) -> Optional[bytes]:
|
|
289
|
+
"""Convert a NumPy array or list into a base64-encoded byte string."""
|
|
290
|
+
if vector is None:
|
|
291
|
+
return None
|
|
292
|
+
if isinstance(vector, list):
|
|
293
|
+
vector = np.array(vector, dtype=np.float32)
|
|
294
|
+
|
|
295
|
+
return base64.b64encode(vector.tobytes())
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def deserialize_vector(data: Optional[bytes], dialect: Dialect) -> Optional[np.ndarray]:
|
|
299
|
+
"""Convert a base64-encoded byte string back into a NumPy array."""
|
|
300
|
+
if not data:
|
|
301
|
+
return None
|
|
302
|
+
|
|
303
|
+
if dialect.name == "sqlite":
|
|
304
|
+
data = base64.b64decode(data)
|
|
305
|
+
|
|
306
|
+
return np.frombuffer(data, dtype=np.float32)
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
# --------------------------
|
|
310
|
+
# Batch Request Serialization
|
|
311
|
+
# --------------------------
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def serialize_create_batch_response(
|
|
315
|
+
create_batch_response: Union[BetaMessageBatch],
|
|
316
|
+
) -> Dict[str, Any]:
|
|
317
|
+
"""Convert a list of ToolRules into a JSON-serializable format."""
|
|
318
|
+
llm_provider_type = None
|
|
319
|
+
if isinstance(create_batch_response, BetaMessageBatch):
|
|
320
|
+
llm_provider_type = ProviderType.anthropic.value
|
|
321
|
+
|
|
322
|
+
if not llm_provider_type:
|
|
323
|
+
raise ValueError(
|
|
324
|
+
f"Could not determine llm provider from create batch response object type: {create_batch_response}"
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
return {
|
|
328
|
+
"data": create_batch_response.model_dump(mode="json"),
|
|
329
|
+
"type": llm_provider_type,
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def deserialize_create_batch_response(data: Dict) -> Union[BetaMessageBatch]:
|
|
334
|
+
provider_type = ProviderType(data.get("type"))
|
|
335
|
+
|
|
336
|
+
if provider_type == ProviderType.anthropic:
|
|
337
|
+
return BetaMessageBatch(**data.get("data"))
|
|
338
|
+
|
|
339
|
+
raise ValueError(f"Unknown ProviderType type: {provider_type}")
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
# TODO: Note that this is the same as above for Anthropic, but this is not the case for all providers
|
|
343
|
+
# TODO: Some have different types based on the create v.s. poll requests
|
|
344
|
+
def serialize_poll_batch_response(
|
|
345
|
+
poll_batch_response: Optional[Union[BetaMessageBatch]],
|
|
346
|
+
) -> Optional[Dict[str, Any]]:
|
|
347
|
+
"""Convert a list of ToolRules into a JSON-serializable format."""
|
|
348
|
+
if not poll_batch_response:
|
|
349
|
+
return None
|
|
350
|
+
|
|
351
|
+
llm_provider_type = None
|
|
352
|
+
if isinstance(poll_batch_response, BetaMessageBatch):
|
|
353
|
+
llm_provider_type = ProviderType.anthropic.value
|
|
354
|
+
|
|
355
|
+
if not llm_provider_type:
|
|
356
|
+
raise ValueError(
|
|
357
|
+
f"Could not determine llm provider from poll batch response object type: {poll_batch_response}"
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
return {
|
|
361
|
+
"data": poll_batch_response.model_dump(mode="json"),
|
|
362
|
+
"type": llm_provider_type,
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def deserialize_poll_batch_response(
|
|
367
|
+
data: Optional[Dict],
|
|
368
|
+
) -> Optional[Union[BetaMessageBatch]]:
|
|
369
|
+
if not data:
|
|
370
|
+
return None
|
|
371
|
+
|
|
372
|
+
provider_type = ProviderType(data.get("type"))
|
|
373
|
+
|
|
374
|
+
if provider_type == ProviderType.anthropic:
|
|
375
|
+
return BetaMessageBatch(**data.get("data"))
|
|
376
|
+
|
|
377
|
+
raise ValueError(f"Unknown ProviderType type: {provider_type}")
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def serialize_batch_request_result(
|
|
381
|
+
batch_individual_response: Optional[Union[BetaMessageBatchIndividualResponse]],
|
|
382
|
+
) -> Optional[Dict[str, Any]]:
|
|
383
|
+
"""Convert a list of ToolRules into a JSON-serializable format."""
|
|
384
|
+
if not batch_individual_response:
|
|
385
|
+
return None
|
|
386
|
+
|
|
387
|
+
llm_provider_type = None
|
|
388
|
+
if isinstance(batch_individual_response, BetaMessageBatchIndividualResponse):
|
|
389
|
+
llm_provider_type = ProviderType.anthropic.value
|
|
390
|
+
|
|
391
|
+
if not llm_provider_type:
|
|
392
|
+
raise ValueError(
|
|
393
|
+
f"Could not determine llm provider from batch result object type: {batch_individual_response}"
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
return {
|
|
397
|
+
"data": batch_individual_response.model_dump(mode="json"),
|
|
398
|
+
"type": llm_provider_type,
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def deserialize_batch_request_result(
|
|
403
|
+
data: Optional[Dict],
|
|
404
|
+
) -> Optional[Union[BetaMessageBatchIndividualResponse]]:
|
|
405
|
+
if not data:
|
|
406
|
+
return None
|
|
407
|
+
provider_type = ProviderType(data.get("type"))
|
|
408
|
+
|
|
409
|
+
if provider_type == ProviderType.anthropic:
|
|
410
|
+
return BetaMessageBatchIndividualResponse(**data.get("data"))
|
|
411
|
+
|
|
412
|
+
raise ValueError(f"Unknown ProviderType type: {provider_type}")
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def serialize_agent_step_state(
|
|
416
|
+
agent_step_state: Optional[AgentStepState],
|
|
417
|
+
) -> Optional[Dict[str, Any]]:
|
|
418
|
+
"""Convert a list of ToolRules into a JSON-serializable format."""
|
|
419
|
+
if not agent_step_state:
|
|
420
|
+
return None
|
|
421
|
+
|
|
422
|
+
return agent_step_state.model_dump(mode="json")
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def deserialize_agent_step_state(data: Optional[Dict]) -> Optional[AgentStepState]:
|
|
426
|
+
if not data:
|
|
427
|
+
return None
|
|
428
|
+
|
|
429
|
+
return AgentStepState(**data)
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from datetime import datetime, timedelta, timezone
|
|
3
|
+
|
|
4
|
+
import pytz
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def parse_formatted_time(formatted_time):
|
|
8
|
+
# parse times returned by letta.utils.get_formatted_time()
|
|
9
|
+
return datetime.strptime(formatted_time, "%Y-%m-%d %I:%M:%S %p %Z%z")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def datetime_to_timestamp(dt):
|
|
13
|
+
# convert datetime object to integer timestamp
|
|
14
|
+
return int(dt.timestamp())
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def timestamp_to_datetime(ts):
|
|
18
|
+
# convert integer timestamp to datetime object
|
|
19
|
+
return datetime.fromtimestamp(ts)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_local_time_military():
|
|
23
|
+
# Get the current time in UTC
|
|
24
|
+
current_time_utc = datetime.now(pytz.utc)
|
|
25
|
+
|
|
26
|
+
# Convert to San Francisco's time zone (PST/PDT)
|
|
27
|
+
sf_time_zone = pytz.timezone("America/Los_Angeles")
|
|
28
|
+
local_time = current_time_utc.astimezone(sf_time_zone)
|
|
29
|
+
|
|
30
|
+
# You may format it as you desire
|
|
31
|
+
formatted_time = local_time.strftime("%Y-%m-%d %H:%M:%S %Z%z")
|
|
32
|
+
|
|
33
|
+
return formatted_time
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_local_time_timezone(timezone="America/Los_Angeles"):
|
|
37
|
+
# Get the current time in UTC
|
|
38
|
+
current_time_utc = datetime.now(pytz.utc)
|
|
39
|
+
|
|
40
|
+
# Convert to San Francisco's time zone (PST/PDT)
|
|
41
|
+
sf_time_zone = pytz.timezone(timezone)
|
|
42
|
+
local_time = current_time_utc.astimezone(sf_time_zone)
|
|
43
|
+
|
|
44
|
+
# You may format it as you desire, including AM/PM
|
|
45
|
+
formatted_time = local_time.strftime("%Y-%m-%d %I:%M:%S %p %Z%z")
|
|
46
|
+
|
|
47
|
+
return formatted_time
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def get_local_time(timezone=None):
|
|
51
|
+
if timezone is not None:
|
|
52
|
+
time_str = get_local_time_timezone(timezone)
|
|
53
|
+
else:
|
|
54
|
+
# Get the current time, which will be in the local timezone of the computer
|
|
55
|
+
local_time = datetime.now().astimezone()
|
|
56
|
+
|
|
57
|
+
# You may format it as you desire, including AM/PM
|
|
58
|
+
time_str = local_time.strftime("%Y-%m-%d %I:%M:%S %p %Z%z")
|
|
59
|
+
|
|
60
|
+
return time_str.strip()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def get_utc_time() -> datetime:
|
|
64
|
+
"""Get the current UTC time"""
|
|
65
|
+
# return datetime.now(pytz.utc)
|
|
66
|
+
return datetime.now(timezone.utc)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def format_datetime(dt):
|
|
70
|
+
return dt.strftime("%Y-%m-%d %I:%M:%S %p %Z%z")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def validate_date_format(date_str):
|
|
74
|
+
"""Validate the given date string in the format 'YYYY-MM-DD'."""
|
|
75
|
+
try:
|
|
76
|
+
datetime.strptime(date_str, "%Y-%m-%d")
|
|
77
|
+
return True
|
|
78
|
+
except (ValueError, TypeError):
|
|
79
|
+
return False
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def extract_date_from_timestamp(timestamp):
|
|
83
|
+
"""Extracts and returns the date from the given timestamp."""
|
|
84
|
+
# Extracts the date (ignoring the time and timezone)
|
|
85
|
+
match = re.match(r"(\d{4}-\d{2}-\d{2})", timestamp)
|
|
86
|
+
return match.group(1) if match else None
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def is_utc_datetime(dt: datetime) -> bool:
|
|
90
|
+
return dt.tzinfo is not None and dt.tzinfo.utcoffset(dt) == timedelta(0)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
|
|
4
|
+
import demjson3 as demjson
|
|
5
|
+
|
|
6
|
+
from mirix.log import get_logger
|
|
7
|
+
|
|
8
|
+
logger = get_logger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def json_loads(data):
|
|
12
|
+
return json.loads(data, strict=False)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def json_dumps(data, indent=2):
|
|
16
|
+
def safe_serializer(obj):
|
|
17
|
+
if isinstance(obj, datetime):
|
|
18
|
+
return obj.isoformat()
|
|
19
|
+
raise TypeError(f"Type {type(obj)} not serializable")
|
|
20
|
+
|
|
21
|
+
return json.dumps(data, indent=indent, default=safe_serializer, ensure_ascii=False)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def parse_json(string) -> dict:
|
|
25
|
+
"""Parse JSON string into JSON with both json and demjson"""
|
|
26
|
+
result = None
|
|
27
|
+
try:
|
|
28
|
+
result = json_loads(string)
|
|
29
|
+
return result
|
|
30
|
+
except Exception as e:
|
|
31
|
+
logger.debug("Error parsing json with json package: %s", e)
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
result = demjson.decode(string)
|
|
35
|
+
return result
|
|
36
|
+
except demjson.JSONDecodeError as e:
|
|
37
|
+
logger.debug("Error parsing json with demjson package: %s", e)
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
from json_repair import repair_json
|
|
41
|
+
string = repair_json(string)
|
|
42
|
+
result = json_loads(string)
|
|
43
|
+
return result
|
|
44
|
+
|
|
45
|
+
except Exception as e:
|
|
46
|
+
logger.debug("Error repairing json with json_repair package: %s", e)
|
|
47
|
+
raise e
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
from mirix import system
|
|
2
|
+
from mirix.schemas.enums import MessageRole
|
|
3
|
+
from mirix.schemas.message import Message, MessageCreate
|
|
4
|
+
from mirix.schemas.mirix_message_content import (
|
|
5
|
+
CloudFileContent,
|
|
6
|
+
FileContent,
|
|
7
|
+
ImageContent,
|
|
8
|
+
TextContent,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def extract_and_wrap_message_content(
|
|
13
|
+
role: MessageRole,
|
|
14
|
+
content: str | TextContent | ImageContent | FileContent | CloudFileContent,
|
|
15
|
+
wrap_user_message: bool = False,
|
|
16
|
+
wrap_system_message: bool = True,
|
|
17
|
+
):
|
|
18
|
+
# Extract message content
|
|
19
|
+
if isinstance(content, str):
|
|
20
|
+
message_content = content
|
|
21
|
+
elif content and isinstance(content, TextContent):
|
|
22
|
+
message_content = content.text
|
|
23
|
+
elif content and isinstance(content, ImageContent):
|
|
24
|
+
# Skip wrapping if the content is an image
|
|
25
|
+
return content
|
|
26
|
+
elif content and isinstance(content, FileContent):
|
|
27
|
+
# Skip wrapping if the content is a file
|
|
28
|
+
return content
|
|
29
|
+
elif content and isinstance(content, CloudFileContent):
|
|
30
|
+
return content
|
|
31
|
+
else:
|
|
32
|
+
raise ValueError("Message content is empty or invalid")
|
|
33
|
+
|
|
34
|
+
# Apply wrapping if needed
|
|
35
|
+
if role == MessageRole.user and wrap_user_message:
|
|
36
|
+
message_content = system.package_user_message(user_message=message_content)
|
|
37
|
+
elif role == MessageRole.system and wrap_system_message:
|
|
38
|
+
message_content = system.package_system_message(system_message=message_content)
|
|
39
|
+
elif role not in {MessageRole.user, MessageRole.system}:
|
|
40
|
+
raise ValueError(f"Invalid message role: {role}")
|
|
41
|
+
return TextContent(text=message_content)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def prepare_input_message_create(
|
|
45
|
+
message: MessageCreate, agent_id: str, **kwargs
|
|
46
|
+
) -> Message:
|
|
47
|
+
"""Converts a MessageCreate object into a Message object, applying wrapping if needed."""
|
|
48
|
+
# TODO: This seems like extra boilerplate with little benefit
|
|
49
|
+
assert isinstance(message, MessageCreate)
|
|
50
|
+
|
|
51
|
+
if isinstance(message.content, list):
|
|
52
|
+
content = [
|
|
53
|
+
extract_and_wrap_message_content(role=message.role, content=c, **kwargs)
|
|
54
|
+
for c in message.content
|
|
55
|
+
]
|
|
56
|
+
else:
|
|
57
|
+
content = [
|
|
58
|
+
extract_and_wrap_message_content(
|
|
59
|
+
role=message.role, content=message.content, **kwargs
|
|
60
|
+
)
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
return Message(
|
|
64
|
+
agent_id=agent_id,
|
|
65
|
+
role=message.role,
|
|
66
|
+
content=content,
|
|
67
|
+
name=message.name,
|
|
68
|
+
model=None, # assigned later?
|
|
69
|
+
tool_calls=None, # irrelevant
|
|
70
|
+
tool_call_id=None,
|
|
71
|
+
otid=message.otid,
|
|
72
|
+
sender_id=message.sender_id,
|
|
73
|
+
group_id=message.group_id,
|
|
74
|
+
)
|