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
|
@@ -0,0 +1,1136 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MirixClient implementation for Mirix.
|
|
3
|
+
This client communicates with a remote Mirix server via REST API.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
from typing import Any, Callable, Dict, List, Optional, Union
|
|
8
|
+
|
|
9
|
+
import requests
|
|
10
|
+
from requests.adapters import HTTPAdapter
|
|
11
|
+
from urllib3.util.retry import Retry
|
|
12
|
+
|
|
13
|
+
from mirix.client.client import AbstractClient
|
|
14
|
+
from mirix.constants import FUNCTION_RETURN_CHAR_LIMIT
|
|
15
|
+
from mirix.schemas.agent import AgentState, AgentType, CreateAgent, CreateMetaAgent
|
|
16
|
+
from mirix.schemas.block import Block, BlockUpdate, CreateBlock, Human, Persona
|
|
17
|
+
from mirix.schemas.embedding_config import EmbeddingConfig
|
|
18
|
+
from mirix.schemas.environment_variables import (
|
|
19
|
+
SandboxEnvironmentVariable,
|
|
20
|
+
SandboxEnvironmentVariableCreate,
|
|
21
|
+
SandboxEnvironmentVariableUpdate,
|
|
22
|
+
)
|
|
23
|
+
from mirix.schemas.file import FileMetadata
|
|
24
|
+
from mirix.schemas.llm_config import LLMConfig
|
|
25
|
+
from mirix.schemas.memory import ArchivalMemorySummary, Memory, RecallMemorySummary
|
|
26
|
+
from mirix.schemas.message import Message, MessageCreate
|
|
27
|
+
from mirix.schemas.mirix_response import MirixResponse
|
|
28
|
+
from mirix.schemas.organization import Organization
|
|
29
|
+
from mirix.schemas.sandbox_config import (
|
|
30
|
+
E2BSandboxConfig,
|
|
31
|
+
LocalSandboxConfig,
|
|
32
|
+
SandboxConfig,
|
|
33
|
+
SandboxConfigCreate,
|
|
34
|
+
SandboxConfigUpdate,
|
|
35
|
+
)
|
|
36
|
+
from mirix.schemas.tool import Tool, ToolCreate, ToolUpdate
|
|
37
|
+
from mirix.schemas.tool_rule import BaseToolRule
|
|
38
|
+
from mirix.log import get_logger
|
|
39
|
+
|
|
40
|
+
logger = get_logger(__name__)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class MirixClient(AbstractClient):
|
|
44
|
+
"""
|
|
45
|
+
Client that communicates with a remote Mirix server via REST API.
|
|
46
|
+
|
|
47
|
+
This client runs on the user's local machine and makes HTTP requests
|
|
48
|
+
to a Mirix server hosted in the cloud.
|
|
49
|
+
|
|
50
|
+
Example:
|
|
51
|
+
>>> client = MirixClient(
|
|
52
|
+
... base_url="https://api.mirix.ai",
|
|
53
|
+
... user_id="my-user",
|
|
54
|
+
... org_id="my-org",
|
|
55
|
+
... )
|
|
56
|
+
>>> agent = client.create_agent(name="my_agent")
|
|
57
|
+
>>> response = client.send_message(
|
|
58
|
+
... agent_id=agent.id,
|
|
59
|
+
... message="Hello!",
|
|
60
|
+
... role="user"
|
|
61
|
+
... )
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
def __init__(
|
|
65
|
+
self,
|
|
66
|
+
api_key: Optional[str] = None,
|
|
67
|
+
base_url: Optional[str] = None,
|
|
68
|
+
user_id: Optional[str] = None,
|
|
69
|
+
user_name: Optional[str] = None,
|
|
70
|
+
org_id: Optional[str] = None,
|
|
71
|
+
org_name: Optional[str] = None,
|
|
72
|
+
debug: bool = False,
|
|
73
|
+
timeout: int = 60,
|
|
74
|
+
max_retries: int = 3,
|
|
75
|
+
):
|
|
76
|
+
"""
|
|
77
|
+
Initialize MirixClient.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
base_url: Base URL of the Mirix API server (optional, can also be set via MIRIX_API_URL env var, default: "http://localhost:8000")
|
|
81
|
+
user_id: User ID (optional, will be auto-generated if not provided)
|
|
82
|
+
user_name: User name (optional, defaults to user_id if not provided)
|
|
83
|
+
org_id: Organization ID (optional, will be auto-generated if not provided)
|
|
84
|
+
org_name: Organization name (optional, defaults to org_id if not provided)
|
|
85
|
+
debug: Whether to enable debug logging
|
|
86
|
+
timeout: Request timeout in seconds
|
|
87
|
+
max_retries: Number of retries for failed requests
|
|
88
|
+
"""
|
|
89
|
+
super().__init__(debug=debug)
|
|
90
|
+
|
|
91
|
+
# Get base URL from parameter or environment variable
|
|
92
|
+
self.base_url = (base_url or os.environ.get("MIRIX_API_URL", "http://localhost:8000")).rstrip("/")
|
|
93
|
+
|
|
94
|
+
# Generate IDs if not provided
|
|
95
|
+
if not user_id:
|
|
96
|
+
import uuid
|
|
97
|
+
user_id = f"user-{uuid.uuid4().hex[:8]}"
|
|
98
|
+
|
|
99
|
+
if not org_id:
|
|
100
|
+
import uuid
|
|
101
|
+
org_id = f"org-{uuid.uuid4().hex[:8]}"
|
|
102
|
+
|
|
103
|
+
self.user_id = user_id
|
|
104
|
+
self.user_name = user_name or user_id
|
|
105
|
+
self.org_id = org_id
|
|
106
|
+
self.org_name = org_name or org_id
|
|
107
|
+
self.timeout = timeout
|
|
108
|
+
|
|
109
|
+
# Track initialized meta agent for this project
|
|
110
|
+
self._meta_agent: Optional[AgentState] = None
|
|
111
|
+
|
|
112
|
+
# Create session with retry logic
|
|
113
|
+
self.session = requests.Session()
|
|
114
|
+
|
|
115
|
+
# Configure retries
|
|
116
|
+
retry_strategy = Retry(
|
|
117
|
+
total=max_retries,
|
|
118
|
+
backoff_factor=1,
|
|
119
|
+
status_forcelist=[429, 500, 502, 503, 504],
|
|
120
|
+
allowed_methods=["HEAD", "GET", "PUT", "DELETE", "OPTIONS", "TRACE", "POST"],
|
|
121
|
+
)
|
|
122
|
+
adapter = HTTPAdapter(max_retries=retry_strategy)
|
|
123
|
+
self.session.mount("http://", adapter)
|
|
124
|
+
self.session.mount("https://", adapter)
|
|
125
|
+
|
|
126
|
+
# Set headers
|
|
127
|
+
if self.user_id:
|
|
128
|
+
self.session.headers.update({"X-User-ID": self.user_id})
|
|
129
|
+
|
|
130
|
+
if self.org_id:
|
|
131
|
+
self.session.headers.update({"X-Org-ID": self.org_id})
|
|
132
|
+
|
|
133
|
+
self.session.headers.update({"Content-Type": "application/json"})
|
|
134
|
+
|
|
135
|
+
# Create organization and user if they don't exist
|
|
136
|
+
self._ensure_org_and_user_exist()
|
|
137
|
+
|
|
138
|
+
def _ensure_org_and_user_exist(self):
|
|
139
|
+
"""
|
|
140
|
+
Ensure that the organization and user exist on the server.
|
|
141
|
+
Creates them if they don't exist.
|
|
142
|
+
"""
|
|
143
|
+
try:
|
|
144
|
+
|
|
145
|
+
# Create or get organization first
|
|
146
|
+
org_response = self._request(
|
|
147
|
+
"POST",
|
|
148
|
+
"/organizations/create_or_get",
|
|
149
|
+
json={"org_id": self.org_id, "name": self.org_name}
|
|
150
|
+
)
|
|
151
|
+
if self.debug:
|
|
152
|
+
logger.debug("[MirixClient] Organization initialized: %s (name: %s)", self.org_id, self.org_name)
|
|
153
|
+
|
|
154
|
+
# Create or get user
|
|
155
|
+
user_response = self._request(
|
|
156
|
+
"POST",
|
|
157
|
+
"/users/create_or_get",
|
|
158
|
+
json={
|
|
159
|
+
"user_id": self.user_id,
|
|
160
|
+
"name": self.user_name,
|
|
161
|
+
"org_id": self.org_id
|
|
162
|
+
}
|
|
163
|
+
)
|
|
164
|
+
if self.debug:
|
|
165
|
+
logger.debug("[MirixClient] User initialized: %s (name: %s)", self.user_id, self.user_name)
|
|
166
|
+
except Exception as e:
|
|
167
|
+
# Don't fail initialization if this fails - the server might handle it
|
|
168
|
+
if self.debug:
|
|
169
|
+
logger.debug("[MirixClient] Note: Could not pre-create user/org: %s", e)
|
|
170
|
+
logger.debug("[MirixClient] Server will create them on first request if needed")
|
|
171
|
+
|
|
172
|
+
def _request(
|
|
173
|
+
self,
|
|
174
|
+
method: str,
|
|
175
|
+
endpoint: str,
|
|
176
|
+
json: Optional[Dict] = None,
|
|
177
|
+
params: Optional[Dict] = None,
|
|
178
|
+
) -> Any:
|
|
179
|
+
"""
|
|
180
|
+
Make an HTTP request to the API.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
method: HTTP method (GET, POST, etc.)
|
|
184
|
+
endpoint: API endpoint (e.g., "/agents")
|
|
185
|
+
json: JSON body for the request
|
|
186
|
+
params: Query parameters
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
Response data (parsed JSON)
|
|
190
|
+
|
|
191
|
+
Raises:
|
|
192
|
+
requests.HTTPError: If the request fails
|
|
193
|
+
"""
|
|
194
|
+
url = f"{self.base_url}{endpoint}"
|
|
195
|
+
|
|
196
|
+
if self.debug:
|
|
197
|
+
logger.debug("[MirixClient] %s %s", method, url)
|
|
198
|
+
if json:
|
|
199
|
+
logger.debug("[MirixClient] Request body: %s", json)
|
|
200
|
+
|
|
201
|
+
response = self.session.request(
|
|
202
|
+
method=method,
|
|
203
|
+
url=url,
|
|
204
|
+
json=json,
|
|
205
|
+
params=params,
|
|
206
|
+
timeout=self.timeout,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
try:
|
|
210
|
+
response.raise_for_status()
|
|
211
|
+
except requests.HTTPError as e:
|
|
212
|
+
# Try to extract error message from response
|
|
213
|
+
try:
|
|
214
|
+
error_detail = response.json().get("detail", str(e))
|
|
215
|
+
except:
|
|
216
|
+
error_detail = str(e)
|
|
217
|
+
raise requests.HTTPError(f"API request failed: {error_detail}") from e
|
|
218
|
+
|
|
219
|
+
# Return parsed JSON if there's content
|
|
220
|
+
if response.content:
|
|
221
|
+
return response.json()
|
|
222
|
+
return None
|
|
223
|
+
|
|
224
|
+
# ========================================================================
|
|
225
|
+
# Agent Methods
|
|
226
|
+
# ========================================================================
|
|
227
|
+
|
|
228
|
+
def list_agents(
|
|
229
|
+
self,
|
|
230
|
+
query_text: Optional[str] = None,
|
|
231
|
+
tags: Optional[List[str]] = None,
|
|
232
|
+
limit: int = 100,
|
|
233
|
+
cursor: Optional[str] = None,
|
|
234
|
+
parent_id: Optional[str] = None,
|
|
235
|
+
) -> List[AgentState]:
|
|
236
|
+
"""List all agents."""
|
|
237
|
+
params = {"limit": limit}
|
|
238
|
+
if query_text:
|
|
239
|
+
params["query_text"] = query_text
|
|
240
|
+
if tags:
|
|
241
|
+
params["tags"] = ",".join(tags)
|
|
242
|
+
if cursor:
|
|
243
|
+
params["cursor"] = cursor
|
|
244
|
+
if parent_id:
|
|
245
|
+
params["parent_id"] = parent_id
|
|
246
|
+
|
|
247
|
+
data = self._request("GET", "/agents", params=params)
|
|
248
|
+
return [AgentState(**agent) for agent in data]
|
|
249
|
+
|
|
250
|
+
def agent_exists(
|
|
251
|
+
self, agent_id: Optional[str] = None, agent_name: Optional[str] = None
|
|
252
|
+
) -> bool:
|
|
253
|
+
"""Check if an agent exists."""
|
|
254
|
+
if not (agent_id or agent_name):
|
|
255
|
+
raise ValueError("Either agent_id or agent_name must be provided")
|
|
256
|
+
if agent_id and agent_name:
|
|
257
|
+
raise ValueError("Only one of agent_id or agent_name can be provided")
|
|
258
|
+
|
|
259
|
+
existing = self.list_agents()
|
|
260
|
+
if agent_id:
|
|
261
|
+
return str(agent_id) in [str(agent.id) for agent in existing]
|
|
262
|
+
else:
|
|
263
|
+
return agent_name in [str(agent.name) for agent in existing]
|
|
264
|
+
|
|
265
|
+
def create_agent(
|
|
266
|
+
self,
|
|
267
|
+
name: Optional[str] = None,
|
|
268
|
+
agent_type: Optional[AgentType] = AgentType.chat_agent,
|
|
269
|
+
embedding_config: Optional[EmbeddingConfig] = None,
|
|
270
|
+
llm_config: Optional[LLMConfig] = None,
|
|
271
|
+
memory: Optional[Memory] = None,
|
|
272
|
+
block_ids: Optional[List[str]] = None,
|
|
273
|
+
system: Optional[str] = None,
|
|
274
|
+
tool_ids: Optional[List[str]] = None,
|
|
275
|
+
tool_rules: Optional[List[BaseToolRule]] = None,
|
|
276
|
+
include_base_tools: Optional[bool] = True,
|
|
277
|
+
include_meta_memory_tools: Optional[bool] = False,
|
|
278
|
+
metadata: Optional[Dict] = None,
|
|
279
|
+
description: Optional[str] = None,
|
|
280
|
+
initial_message_sequence: Optional[List[Message]] = None,
|
|
281
|
+
tags: Optional[List[str]] = None,
|
|
282
|
+
) -> AgentState:
|
|
283
|
+
"""Create an agent."""
|
|
284
|
+
request_data = {
|
|
285
|
+
"name": name,
|
|
286
|
+
"agent_type": agent_type,
|
|
287
|
+
"embedding_config": embedding_config.model_dump() if embedding_config else None,
|
|
288
|
+
"llm_config": llm_config.model_dump() if llm_config else None,
|
|
289
|
+
"memory": memory.model_dump() if memory else None,
|
|
290
|
+
"block_ids": block_ids,
|
|
291
|
+
"system": system,
|
|
292
|
+
"tool_ids": tool_ids,
|
|
293
|
+
"tool_rules": [rule.model_dump() if hasattr(rule, 'model_dump') else rule for rule in (tool_rules or [])],
|
|
294
|
+
"include_base_tools": include_base_tools,
|
|
295
|
+
"include_meta_memory_tools": include_meta_memory_tools,
|
|
296
|
+
"metadata": metadata,
|
|
297
|
+
"description": description,
|
|
298
|
+
"initial_message_sequence": [msg.model_dump() if hasattr(msg, 'model_dump') else msg for msg in (initial_message_sequence or [])],
|
|
299
|
+
"tags": tags,
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
data = self._request("POST", "/agents", json=request_data)
|
|
303
|
+
return AgentState(**data)
|
|
304
|
+
|
|
305
|
+
def update_agent(
|
|
306
|
+
self,
|
|
307
|
+
agent_id: str,
|
|
308
|
+
name: Optional[str] = None,
|
|
309
|
+
description: Optional[str] = None,
|
|
310
|
+
system: Optional[str] = None,
|
|
311
|
+
tool_ids: Optional[List[str]] = None,
|
|
312
|
+
metadata: Optional[Dict] = None,
|
|
313
|
+
llm_config: Optional[LLMConfig] = None,
|
|
314
|
+
embedding_config: Optional[EmbeddingConfig] = None,
|
|
315
|
+
message_ids: Optional[List[str]] = None,
|
|
316
|
+
memory: Optional[Memory] = None,
|
|
317
|
+
tags: Optional[List[str]] = None,
|
|
318
|
+
):
|
|
319
|
+
"""Update an agent."""
|
|
320
|
+
request_data = {
|
|
321
|
+
"name": name,
|
|
322
|
+
"description": description,
|
|
323
|
+
"system": system,
|
|
324
|
+
"tool_ids": tool_ids,
|
|
325
|
+
"metadata": metadata,
|
|
326
|
+
"llm_config": llm_config.model_dump() if llm_config else None,
|
|
327
|
+
"embedding_config": embedding_config.model_dump() if embedding_config else None,
|
|
328
|
+
"message_ids": message_ids,
|
|
329
|
+
"memory": memory.model_dump() if memory else None,
|
|
330
|
+
"tags": tags,
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
data = self._request("PATCH", f"/agents/{agent_id}", json=request_data)
|
|
334
|
+
return AgentState(**data)
|
|
335
|
+
|
|
336
|
+
def get_agent(self, agent_id: str) -> AgentState:
|
|
337
|
+
"""Get an agent by ID."""
|
|
338
|
+
data = self._request("GET", f"/agents/{agent_id}")
|
|
339
|
+
return AgentState(**data)
|
|
340
|
+
|
|
341
|
+
def get_agent_id(self, agent_name: str) -> Optional[str]:
|
|
342
|
+
"""Get agent ID by name."""
|
|
343
|
+
agents = self.list_agents()
|
|
344
|
+
for agent in agents:
|
|
345
|
+
if agent.name == agent_name:
|
|
346
|
+
return agent.id
|
|
347
|
+
return None
|
|
348
|
+
|
|
349
|
+
def delete_agent(self, agent_id: str):
|
|
350
|
+
"""Delete an agent."""
|
|
351
|
+
self._request("DELETE", f"/agents/{agent_id}")
|
|
352
|
+
|
|
353
|
+
def rename_agent(self, agent_id: str, new_name: str):
|
|
354
|
+
"""Rename an agent."""
|
|
355
|
+
self.update_agent(agent_id, name=new_name)
|
|
356
|
+
|
|
357
|
+
def get_tools_from_agent(self, agent_id: str) -> List[Tool]:
|
|
358
|
+
"""Get tools from an agent."""
|
|
359
|
+
agent = self.get_agent(agent_id)
|
|
360
|
+
return agent.tools
|
|
361
|
+
|
|
362
|
+
def add_tool_to_agent(self, agent_id: str, tool_id: str):
|
|
363
|
+
"""Add a tool to an agent."""
|
|
364
|
+
raise NotImplementedError("add_tool_to_agent not yet implemented in REST API")
|
|
365
|
+
|
|
366
|
+
def remove_tool_from_agent(self, agent_id: str, tool_id: str):
|
|
367
|
+
"""Remove a tool from an agent."""
|
|
368
|
+
raise NotImplementedError("remove_tool_from_agent not yet implemented in REST API")
|
|
369
|
+
|
|
370
|
+
# ========================================================================
|
|
371
|
+
# Memory Methods
|
|
372
|
+
# ========================================================================
|
|
373
|
+
|
|
374
|
+
def get_in_context_memory(self, agent_id: str) -> Memory:
|
|
375
|
+
"""Get in-context memory of an agent."""
|
|
376
|
+
data = self._request("GET", f"/agents/{agent_id}/memory")
|
|
377
|
+
return Memory(**data)
|
|
378
|
+
|
|
379
|
+
def update_in_context_memory(
|
|
380
|
+
self, agent_id: str, section: str, value: Union[List[str], str]
|
|
381
|
+
) -> Memory:
|
|
382
|
+
"""Update in-context memory."""
|
|
383
|
+
raise NotImplementedError("update_in_context_memory not yet implemented in REST API")
|
|
384
|
+
|
|
385
|
+
def get_archival_memory_summary(self, agent_id: str) -> ArchivalMemorySummary:
|
|
386
|
+
"""Get archival memory summary."""
|
|
387
|
+
data = self._request("GET", f"/agents/{agent_id}/memory/archival")
|
|
388
|
+
return ArchivalMemorySummary(**data)
|
|
389
|
+
|
|
390
|
+
def get_recall_memory_summary(self, agent_id: str) -> RecallMemorySummary:
|
|
391
|
+
"""Get recall memory summary."""
|
|
392
|
+
data = self._request("GET", f"/agents/{agent_id}/memory/recall")
|
|
393
|
+
return RecallMemorySummary(**data)
|
|
394
|
+
|
|
395
|
+
def get_in_context_messages(self, agent_id: str) -> List[Message]:
|
|
396
|
+
"""Get in-context messages."""
|
|
397
|
+
raise NotImplementedError("get_in_context_messages not yet implemented in REST API")
|
|
398
|
+
|
|
399
|
+
# ========================================================================
|
|
400
|
+
# Message Methods
|
|
401
|
+
# ========================================================================
|
|
402
|
+
|
|
403
|
+
def send_message(
|
|
404
|
+
self,
|
|
405
|
+
message: str,
|
|
406
|
+
role: str,
|
|
407
|
+
agent_id: Optional[str] = None,
|
|
408
|
+
name: Optional[str] = None,
|
|
409
|
+
stream: Optional[bool] = False,
|
|
410
|
+
stream_steps: bool = False,
|
|
411
|
+
stream_tokens: bool = False,
|
|
412
|
+
filter_tags: Optional[Dict[str, Any]] = None,
|
|
413
|
+
use_cache: bool = True,
|
|
414
|
+
) -> MirixResponse:
|
|
415
|
+
"""Send a message to an agent.
|
|
416
|
+
|
|
417
|
+
Args:
|
|
418
|
+
message: The message text to send
|
|
419
|
+
role: The role of the message sender (user/system)
|
|
420
|
+
agent_id: The ID of the agent to send the message to
|
|
421
|
+
name: Optional name of the message sender
|
|
422
|
+
stream: Enable streaming (not yet implemented)
|
|
423
|
+
stream_steps: Stream intermediate steps
|
|
424
|
+
stream_tokens: Stream tokens as they are generated
|
|
425
|
+
filter_tags: Optional filter tags for categorization and filtering.
|
|
426
|
+
Example: {"project_id": "proj-alpha", "session_id": "sess-123"}
|
|
427
|
+
use_cache: Control Redis cache behavior (default: True)
|
|
428
|
+
|
|
429
|
+
Returns:
|
|
430
|
+
MirixResponse: The response from the agent
|
|
431
|
+
|
|
432
|
+
Example:
|
|
433
|
+
>>> response = client.send_message(
|
|
434
|
+
... message="What's the status?",
|
|
435
|
+
... role="user",
|
|
436
|
+
... agent_id="agent123",
|
|
437
|
+
... filter_tags={"project": "alpha", "priority": "high"}
|
|
438
|
+
... )
|
|
439
|
+
"""
|
|
440
|
+
if stream or stream_steps or stream_tokens:
|
|
441
|
+
raise NotImplementedError("Streaming not yet implemented in REST API")
|
|
442
|
+
|
|
443
|
+
request_data = {
|
|
444
|
+
"message": message,
|
|
445
|
+
"role": role,
|
|
446
|
+
"name": name,
|
|
447
|
+
"stream_steps": stream_steps,
|
|
448
|
+
"stream_tokens": stream_tokens,
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
# Include filter_tags if provided
|
|
452
|
+
if filter_tags is not None:
|
|
453
|
+
request_data["filter_tags"] = filter_tags
|
|
454
|
+
|
|
455
|
+
# Include use_cache if not default
|
|
456
|
+
if not use_cache:
|
|
457
|
+
request_data["use_cache"] = use_cache
|
|
458
|
+
|
|
459
|
+
data = self._request("POST", f"/agents/{agent_id}/messages", json=request_data)
|
|
460
|
+
return MirixResponse(**data)
|
|
461
|
+
|
|
462
|
+
def user_message(self, agent_id: str, message: str) -> MirixResponse:
|
|
463
|
+
"""Send a user message to an agent."""
|
|
464
|
+
return self.send_message(message=message, role="user", agent_id=agent_id)
|
|
465
|
+
|
|
466
|
+
def get_messages(
|
|
467
|
+
self,
|
|
468
|
+
agent_id: str,
|
|
469
|
+
before: Optional[str] = None,
|
|
470
|
+
after: Optional[str] = None,
|
|
471
|
+
limit: Optional[int] = 1000,
|
|
472
|
+
use_cache: bool = True,
|
|
473
|
+
) -> List[Message]:
|
|
474
|
+
"""Get messages from an agent.
|
|
475
|
+
|
|
476
|
+
Args:
|
|
477
|
+
agent_id: The ID of the agent
|
|
478
|
+
before: Get messages before this cursor
|
|
479
|
+
after: Get messages after this cursor
|
|
480
|
+
limit: Maximum number of messages to retrieve
|
|
481
|
+
use_cache: Control Redis cache behavior (default: True)
|
|
482
|
+
|
|
483
|
+
Returns:
|
|
484
|
+
List of messages
|
|
485
|
+
"""
|
|
486
|
+
params = {"limit": limit}
|
|
487
|
+
if before:
|
|
488
|
+
params["cursor"] = before
|
|
489
|
+
if not use_cache:
|
|
490
|
+
params["use_cache"] = "false"
|
|
491
|
+
|
|
492
|
+
data = self._request("GET", f"/agents/{agent_id}/messages", params=params)
|
|
493
|
+
return [Message(**msg) for msg in data]
|
|
494
|
+
|
|
495
|
+
# ========================================================================
|
|
496
|
+
# Tool Methods
|
|
497
|
+
# ========================================================================
|
|
498
|
+
|
|
499
|
+
def list_tools(
|
|
500
|
+
self, cursor: Optional[str] = None, limit: Optional[int] = 50
|
|
501
|
+
) -> List[Tool]:
|
|
502
|
+
"""List all tools."""
|
|
503
|
+
params = {"limit": limit}
|
|
504
|
+
if cursor:
|
|
505
|
+
params["cursor"] = cursor
|
|
506
|
+
|
|
507
|
+
data = self._request("GET", "/tools", params=params)
|
|
508
|
+
return [Tool(**tool) for tool in data]
|
|
509
|
+
|
|
510
|
+
def get_tool(self, id: str) -> Tool:
|
|
511
|
+
"""Get a tool by ID."""
|
|
512
|
+
data = self._request("GET", f"/tools/{id}")
|
|
513
|
+
return Tool(**data)
|
|
514
|
+
|
|
515
|
+
def create_tool(
|
|
516
|
+
self,
|
|
517
|
+
func,
|
|
518
|
+
name: Optional[str] = None,
|
|
519
|
+
tags: Optional[List[str]] = None,
|
|
520
|
+
return_char_limit: int = FUNCTION_RETURN_CHAR_LIMIT,
|
|
521
|
+
) -> Tool:
|
|
522
|
+
"""Create a tool."""
|
|
523
|
+
raise NotImplementedError(
|
|
524
|
+
"create_tool with function not supported in MirixClient. "
|
|
525
|
+
"Tools must be created on the server side."
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
def create_or_update_tool(
|
|
529
|
+
self,
|
|
530
|
+
func,
|
|
531
|
+
name: Optional[str] = None,
|
|
532
|
+
tags: Optional[List[str]] = None,
|
|
533
|
+
return_char_limit: int = FUNCTION_RETURN_CHAR_LIMIT,
|
|
534
|
+
) -> Tool:
|
|
535
|
+
"""Create or update a tool."""
|
|
536
|
+
raise NotImplementedError(
|
|
537
|
+
"create_or_update_tool with function not supported in MirixClient. "
|
|
538
|
+
"Tools must be created on the server side."
|
|
539
|
+
)
|
|
540
|
+
|
|
541
|
+
def update_tool(
|
|
542
|
+
self,
|
|
543
|
+
id: str,
|
|
544
|
+
name: Optional[str] = None,
|
|
545
|
+
description: Optional[str] = None,
|
|
546
|
+
func: Optional[Callable] = None,
|
|
547
|
+
tags: Optional[List[str]] = None,
|
|
548
|
+
return_char_limit: int = FUNCTION_RETURN_CHAR_LIMIT,
|
|
549
|
+
) -> Tool:
|
|
550
|
+
"""Update a tool."""
|
|
551
|
+
raise NotImplementedError("update_tool not yet implemented in REST API")
|
|
552
|
+
|
|
553
|
+
def delete_tool(self, id: str):
|
|
554
|
+
"""Delete a tool."""
|
|
555
|
+
self._request("DELETE", f"/tools/{id}")
|
|
556
|
+
|
|
557
|
+
def get_tool_id(self, name: str) -> Optional[str]:
|
|
558
|
+
"""Get tool ID by name."""
|
|
559
|
+
tools = self.list_tools()
|
|
560
|
+
for tool in tools:
|
|
561
|
+
if tool.name == name:
|
|
562
|
+
return tool.id
|
|
563
|
+
return None
|
|
564
|
+
|
|
565
|
+
def upsert_base_tools(self) -> List[Tool]:
|
|
566
|
+
"""Upsert base tools."""
|
|
567
|
+
raise NotImplementedError("upsert_base_tools must be done on server side")
|
|
568
|
+
|
|
569
|
+
# ========================================================================
|
|
570
|
+
# Block Methods
|
|
571
|
+
# ========================================================================
|
|
572
|
+
|
|
573
|
+
def list_blocks(
|
|
574
|
+
self, label: Optional[str] = None, templates_only: Optional[bool] = True
|
|
575
|
+
) -> List[Block]:
|
|
576
|
+
"""List blocks."""
|
|
577
|
+
params = {}
|
|
578
|
+
if label:
|
|
579
|
+
params["label"] = label
|
|
580
|
+
|
|
581
|
+
data = self._request("GET", "/blocks", params=params)
|
|
582
|
+
return [Block(**block) for block in data]
|
|
583
|
+
|
|
584
|
+
def get_block(self, block_id: str) -> Block:
|
|
585
|
+
"""Get a block by ID."""
|
|
586
|
+
data = self._request("GET", f"/blocks/{block_id}")
|
|
587
|
+
return Block(**data)
|
|
588
|
+
|
|
589
|
+
def create_block(
|
|
590
|
+
self,
|
|
591
|
+
label: str,
|
|
592
|
+
value: str,
|
|
593
|
+
limit: Optional[int] = None,
|
|
594
|
+
) -> Block:
|
|
595
|
+
"""Create a block."""
|
|
596
|
+
block_data = {
|
|
597
|
+
"label": label,
|
|
598
|
+
"value": value,
|
|
599
|
+
"limit": limit,
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
block = Block(**block_data)
|
|
603
|
+
data = self._request("POST", "/blocks", json=block.model_dump())
|
|
604
|
+
return Block(**data)
|
|
605
|
+
|
|
606
|
+
def delete_block(self, id: str) -> Block:
|
|
607
|
+
"""Delete a block."""
|
|
608
|
+
self._request("DELETE", f"/blocks/{id}")
|
|
609
|
+
|
|
610
|
+
# ========================================================================
|
|
611
|
+
# Human/Persona Methods
|
|
612
|
+
# ========================================================================
|
|
613
|
+
|
|
614
|
+
def create_human(self, name: str, text: str) -> Human:
|
|
615
|
+
"""Create a human block."""
|
|
616
|
+
human = Human(value=text)
|
|
617
|
+
data = self._request("POST", "/blocks", json=human.model_dump())
|
|
618
|
+
return Human(**data)
|
|
619
|
+
|
|
620
|
+
def create_persona(self, name: str, text: str) -> Persona:
|
|
621
|
+
"""Create a persona block."""
|
|
622
|
+
persona = Persona(value=text)
|
|
623
|
+
data = self._request("POST", "/blocks", json=persona.model_dump())
|
|
624
|
+
return Persona(**data)
|
|
625
|
+
|
|
626
|
+
def list_humans(self) -> List[Human]:
|
|
627
|
+
"""List human blocks."""
|
|
628
|
+
blocks = self.list_blocks(label="human")
|
|
629
|
+
return [Human(**block.model_dump()) for block in blocks]
|
|
630
|
+
|
|
631
|
+
def list_personas(self) -> List[Persona]:
|
|
632
|
+
"""List persona blocks."""
|
|
633
|
+
blocks = self.list_blocks(label="persona")
|
|
634
|
+
return [Persona(**block.model_dump()) for block in blocks]
|
|
635
|
+
|
|
636
|
+
def update_human(self, human_id: str, text: str) -> Human:
|
|
637
|
+
"""Update a human block."""
|
|
638
|
+
raise NotImplementedError("update_human not yet implemented in REST API")
|
|
639
|
+
|
|
640
|
+
def update_persona(self, persona_id: str, text: str) -> Persona:
|
|
641
|
+
"""Update a persona block."""
|
|
642
|
+
raise NotImplementedError("update_persona not yet implemented in REST API")
|
|
643
|
+
|
|
644
|
+
def get_persona(self, id: str) -> Persona:
|
|
645
|
+
"""Get a persona block."""
|
|
646
|
+
data = self._request("GET", f"/blocks/{id}")
|
|
647
|
+
return Persona(**data)
|
|
648
|
+
|
|
649
|
+
def get_human(self, id: str) -> Human:
|
|
650
|
+
"""Get a human block."""
|
|
651
|
+
data = self._request("GET", f"/blocks/{id}")
|
|
652
|
+
return Human(**data)
|
|
653
|
+
|
|
654
|
+
def get_persona_id(self, name: str) -> str:
|
|
655
|
+
"""Get persona ID by name."""
|
|
656
|
+
personas = self.list_personas()
|
|
657
|
+
if personas:
|
|
658
|
+
return personas[0].id
|
|
659
|
+
return None
|
|
660
|
+
|
|
661
|
+
def get_human_id(self, name: str) -> str:
|
|
662
|
+
"""Get human ID by name."""
|
|
663
|
+
humans = self.list_humans()
|
|
664
|
+
if humans:
|
|
665
|
+
return humans[0].id
|
|
666
|
+
return None
|
|
667
|
+
|
|
668
|
+
def delete_persona(self, id: str):
|
|
669
|
+
"""Delete a persona."""
|
|
670
|
+
self.delete_block(id)
|
|
671
|
+
|
|
672
|
+
def delete_human(self, id: str):
|
|
673
|
+
"""Delete a human."""
|
|
674
|
+
self.delete_block(id)
|
|
675
|
+
|
|
676
|
+
# ========================================================================
|
|
677
|
+
# Configuration Methods
|
|
678
|
+
# ========================================================================
|
|
679
|
+
|
|
680
|
+
def list_model_configs(self) -> List[LLMConfig]:
|
|
681
|
+
"""List available LLM configurations."""
|
|
682
|
+
data = self._request("GET", "/config/llm")
|
|
683
|
+
return [LLMConfig(**config) for config in data]
|
|
684
|
+
|
|
685
|
+
def list_embedding_configs(self) -> List[EmbeddingConfig]:
|
|
686
|
+
"""List available embedding configurations."""
|
|
687
|
+
data = self._request("GET", "/config/embedding")
|
|
688
|
+
return [EmbeddingConfig(**config) for config in data]
|
|
689
|
+
|
|
690
|
+
# ========================================================================
|
|
691
|
+
# Organization Methods
|
|
692
|
+
# ========================================================================
|
|
693
|
+
|
|
694
|
+
def create_org(self, name: Optional[str] = None) -> Organization:
|
|
695
|
+
"""Create an organization."""
|
|
696
|
+
data = self._request("POST", "/organizations", json={"name": name})
|
|
697
|
+
return Organization(**data)
|
|
698
|
+
|
|
699
|
+
def list_orgs(
|
|
700
|
+
self, cursor: Optional[str] = None, limit: Optional[int] = 50
|
|
701
|
+
) -> List[Organization]:
|
|
702
|
+
"""List organizations."""
|
|
703
|
+
params = {"limit": limit}
|
|
704
|
+
if cursor:
|
|
705
|
+
params["cursor"] = cursor
|
|
706
|
+
|
|
707
|
+
data = self._request("GET", "/organizations", params=params)
|
|
708
|
+
return [Organization(**org) for org in data]
|
|
709
|
+
|
|
710
|
+
def delete_org(self, org_id: str) -> Organization:
|
|
711
|
+
"""Delete an organization."""
|
|
712
|
+
raise NotImplementedError("delete_org not yet implemented in REST API")
|
|
713
|
+
|
|
714
|
+
# ========================================================================
|
|
715
|
+
# Sandbox Methods (Not Implemented)
|
|
716
|
+
# ========================================================================
|
|
717
|
+
|
|
718
|
+
def create_sandbox_config(
|
|
719
|
+
self, config: Union[LocalSandboxConfig, E2BSandboxConfig]
|
|
720
|
+
) -> SandboxConfig:
|
|
721
|
+
"""Create sandbox config."""
|
|
722
|
+
raise NotImplementedError("Sandbox config not yet implemented in REST API")
|
|
723
|
+
|
|
724
|
+
def update_sandbox_config(
|
|
725
|
+
self,
|
|
726
|
+
sandbox_config_id: str,
|
|
727
|
+
config: Union[LocalSandboxConfig, E2BSandboxConfig],
|
|
728
|
+
) -> SandboxConfig:
|
|
729
|
+
"""Update sandbox config."""
|
|
730
|
+
raise NotImplementedError("Sandbox config not yet implemented in REST API")
|
|
731
|
+
|
|
732
|
+
def delete_sandbox_config(self, sandbox_config_id: str) -> None:
|
|
733
|
+
"""Delete sandbox config."""
|
|
734
|
+
raise NotImplementedError("Sandbox config not yet implemented in REST API")
|
|
735
|
+
|
|
736
|
+
def list_sandbox_configs(
|
|
737
|
+
self, limit: int = 50, cursor: Optional[str] = None
|
|
738
|
+
) -> List[SandboxConfig]:
|
|
739
|
+
"""List sandbox configs."""
|
|
740
|
+
raise NotImplementedError("Sandbox config not yet implemented in REST API")
|
|
741
|
+
|
|
742
|
+
def create_sandbox_env_var(
|
|
743
|
+
self,
|
|
744
|
+
sandbox_config_id: str,
|
|
745
|
+
key: str,
|
|
746
|
+
value: str,
|
|
747
|
+
description: Optional[str] = None,
|
|
748
|
+
) -> SandboxEnvironmentVariable:
|
|
749
|
+
"""Create sandbox environment variable."""
|
|
750
|
+
raise NotImplementedError("Sandbox env vars not yet implemented in REST API")
|
|
751
|
+
|
|
752
|
+
def update_sandbox_env_var(
|
|
753
|
+
self,
|
|
754
|
+
env_var_id: str,
|
|
755
|
+
key: Optional[str] = None,
|
|
756
|
+
value: Optional[str] = None,
|
|
757
|
+
description: Optional[str] = None,
|
|
758
|
+
) -> SandboxEnvironmentVariable:
|
|
759
|
+
"""Update sandbox environment variable."""
|
|
760
|
+
raise NotImplementedError("Sandbox env vars not yet implemented in REST API")
|
|
761
|
+
|
|
762
|
+
def delete_sandbox_env_var(self, env_var_id: str) -> None:
|
|
763
|
+
"""Delete sandbox environment variable."""
|
|
764
|
+
raise NotImplementedError("Sandbox env vars not yet implemented in REST API")
|
|
765
|
+
|
|
766
|
+
def list_sandbox_env_vars(
|
|
767
|
+
self, sandbox_config_id: str, limit: int = 50, cursor: Optional[str] = None
|
|
768
|
+
) -> List[SandboxEnvironmentVariable]:
|
|
769
|
+
"""List sandbox environment variables."""
|
|
770
|
+
raise NotImplementedError("Sandbox env vars not yet implemented in REST API")
|
|
771
|
+
|
|
772
|
+
# ========================================================================
|
|
773
|
+
# New Memory API Methods
|
|
774
|
+
# ========================================================================
|
|
775
|
+
|
|
776
|
+
def _load_system_prompts(self, config: Dict[str, Any]) -> Dict[str, str]:
|
|
777
|
+
"""Load all system prompts from the system_prompts_folder.
|
|
778
|
+
|
|
779
|
+
Args:
|
|
780
|
+
config: Configuration dictionary that may contain 'system_prompts_folder'
|
|
781
|
+
|
|
782
|
+
Returns:
|
|
783
|
+
Dict mapping agent names to their prompt text
|
|
784
|
+
"""
|
|
785
|
+
import os
|
|
786
|
+
import logging
|
|
787
|
+
|
|
788
|
+
logger = logging.getLogger(__name__)
|
|
789
|
+
prompts = {}
|
|
790
|
+
|
|
791
|
+
system_prompts_folder = config.get("system_prompts_folder")
|
|
792
|
+
if not system_prompts_folder:
|
|
793
|
+
return prompts
|
|
794
|
+
|
|
795
|
+
if not os.path.exists(system_prompts_folder):
|
|
796
|
+
return prompts
|
|
797
|
+
|
|
798
|
+
# Load all .txt files from the system prompts folder
|
|
799
|
+
for filename in os.listdir(system_prompts_folder):
|
|
800
|
+
if filename.endswith(".txt"):
|
|
801
|
+
agent_name = filename[:-4] # Strip .txt suffix
|
|
802
|
+
prompt_file = os.path.join(system_prompts_folder, filename)
|
|
803
|
+
|
|
804
|
+
try:
|
|
805
|
+
with open(prompt_file, "r", encoding="utf-8") as f:
|
|
806
|
+
prompts[agent_name] = f.read()
|
|
807
|
+
except Exception as e:
|
|
808
|
+
# Log warning but continue
|
|
809
|
+
logger.warning(
|
|
810
|
+
f"Failed to load system prompt for {agent_name} from {prompt_file}: {e}"
|
|
811
|
+
)
|
|
812
|
+
|
|
813
|
+
return prompts
|
|
814
|
+
|
|
815
|
+
def initialize_meta_agent(
|
|
816
|
+
self,
|
|
817
|
+
config: Optional[Dict[str, Any]] = None,
|
|
818
|
+
config_path: Optional[str] = None,
|
|
819
|
+
update_agents: Optional[bool] = False,
|
|
820
|
+
) -> AgentState:
|
|
821
|
+
"""
|
|
822
|
+
Initialize a meta agent with the given configuration.
|
|
823
|
+
|
|
824
|
+
This creates a meta memory agent that manages multiple specialized memory agents
|
|
825
|
+
(episodic, semantic, procedural, etc.) for the current project.
|
|
826
|
+
|
|
827
|
+
Args:
|
|
828
|
+
config: Configuration dictionary with llm_config, embedding_config, etc.
|
|
829
|
+
config_path: Path to YAML config file (alternative to config dict)
|
|
830
|
+
|
|
831
|
+
Returns:
|
|
832
|
+
AgentState: The initialized meta agent
|
|
833
|
+
|
|
834
|
+
Example:
|
|
835
|
+
>>> client = MirixClient(project="test")
|
|
836
|
+
>>> config = {
|
|
837
|
+
... "llm_config": {"model": "gemini-2.0-flash"},
|
|
838
|
+
... "embedding_config": {"model": "text-embedding-004"}
|
|
839
|
+
... }
|
|
840
|
+
>>> meta_agent = client.initialize_meta_agent(config=config)
|
|
841
|
+
"""
|
|
842
|
+
# Load config from file if provided
|
|
843
|
+
if config_path:
|
|
844
|
+
import yaml
|
|
845
|
+
from pathlib import Path
|
|
846
|
+
|
|
847
|
+
config_file = Path(config_path)
|
|
848
|
+
if config_file.exists():
|
|
849
|
+
with open(config_file, "r") as f:
|
|
850
|
+
config = yaml.safe_load(f)
|
|
851
|
+
|
|
852
|
+
if not config:
|
|
853
|
+
raise ValueError("Either config or config_path must be provided")
|
|
854
|
+
|
|
855
|
+
# Load system prompts from folder if specified and not already provided
|
|
856
|
+
if config.get("meta_agent_config") and config['meta_agent_config'].get("system_prompts_folder") and not config.get("system_prompts"):
|
|
857
|
+
config['meta_agent_config']["system_prompts"] = self._load_system_prompts(config['meta_agent_config'])
|
|
858
|
+
del config['meta_agent_config']['system_prompts_folder']
|
|
859
|
+
|
|
860
|
+
# Prepare request data
|
|
861
|
+
request_data = {
|
|
862
|
+
"config": config,
|
|
863
|
+
"update_agents": update_agents,
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
# Make API request to initialize meta agent
|
|
867
|
+
data = self._request("POST", "/agents/meta/initialize", json=request_data)
|
|
868
|
+
self._meta_agent = AgentState(**data)
|
|
869
|
+
return self._meta_agent
|
|
870
|
+
|
|
871
|
+
def add(
|
|
872
|
+
self,
|
|
873
|
+
user_id: str,
|
|
874
|
+
messages: List[Dict[str, Any]],
|
|
875
|
+
chaining: bool = True,
|
|
876
|
+
verbose: bool = False,
|
|
877
|
+
filter_tags: Optional[Dict[str, Any]] = None,
|
|
878
|
+
use_cache: bool = True,
|
|
879
|
+
) -> Dict[str, Any]:
|
|
880
|
+
"""
|
|
881
|
+
Add conversation turns to memory (asynchronous processing).
|
|
882
|
+
|
|
883
|
+
This method queues conversation turns for background processing by queue workers.
|
|
884
|
+
The messages are stored in the appropriate memory systems asynchronously.
|
|
885
|
+
|
|
886
|
+
Args:
|
|
887
|
+
user_id: User ID for the conversation
|
|
888
|
+
messages: List of message dicts with role and content.
|
|
889
|
+
Messages should end with an assistant turn.
|
|
890
|
+
Format: [
|
|
891
|
+
{"role": "user", "content": [{"type": "text", "text": "..."}]},
|
|
892
|
+
{"role": "assistant", "content": [{"type": "text", "text": "..."}]}
|
|
893
|
+
]
|
|
894
|
+
verbose: If True, enable verbose output during memory processing
|
|
895
|
+
filter_tags: Optional dict of tags for filtering and categorization.
|
|
896
|
+
Example: {"project_id": "proj-123", "session_id": "sess-456"}
|
|
897
|
+
use_cache: Control Redis cache behavior (default: True)
|
|
898
|
+
|
|
899
|
+
Returns:
|
|
900
|
+
Dict containing:
|
|
901
|
+
- success (bool): True if message was queued successfully
|
|
902
|
+
- message (str): Status message
|
|
903
|
+
- status (str): "queued" - indicates async processing
|
|
904
|
+
- agent_id (str): Meta agent ID processing the messages
|
|
905
|
+
- message_count (int): Number of messages queued
|
|
906
|
+
|
|
907
|
+
Note:
|
|
908
|
+
Processing happens asynchronously. The response indicates the message
|
|
909
|
+
was successfully queued, not that processing is complete.
|
|
910
|
+
|
|
911
|
+
Example:
|
|
912
|
+
>>> response = client.add(
|
|
913
|
+
... user_id='user_123',
|
|
914
|
+
... messages=[
|
|
915
|
+
... {"role": "user", "content": [{"type": "text", "text": "I went to dinner"}]},
|
|
916
|
+
... {"role": "assistant", "content": [{"type": "text", "text": "That's great!"}]}
|
|
917
|
+
... ],
|
|
918
|
+
... verbose=True,
|
|
919
|
+
... filter_tags={"session_id": "sess-789", "tags": ["personal"]}
|
|
920
|
+
... )
|
|
921
|
+
>>> logger.debug(response)
|
|
922
|
+
{
|
|
923
|
+
"success": True,
|
|
924
|
+
"message": "Memory queued for processing",
|
|
925
|
+
"status": "queued",
|
|
926
|
+
"agent_id": "agent-456",
|
|
927
|
+
"message_count": 2
|
|
928
|
+
}
|
|
929
|
+
"""
|
|
930
|
+
if not self._meta_agent:
|
|
931
|
+
raise ValueError("Meta agent not initialized. Call initialize_meta_agent() first.")
|
|
932
|
+
|
|
933
|
+
request_data = {
|
|
934
|
+
"user_id": user_id,
|
|
935
|
+
"meta_agent_id": self._meta_agent.id,
|
|
936
|
+
"messages": messages,
|
|
937
|
+
"chaining": chaining,
|
|
938
|
+
"verbose": verbose,
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
if filter_tags is not None:
|
|
942
|
+
request_data["filter_tags"] = filter_tags
|
|
943
|
+
|
|
944
|
+
if not use_cache:
|
|
945
|
+
request_data["use_cache"] = use_cache
|
|
946
|
+
|
|
947
|
+
return self._request("POST", "/memory/add", json=request_data)
|
|
948
|
+
|
|
949
|
+
def retrieve_with_conversation(
|
|
950
|
+
self,
|
|
951
|
+
user_id: str,
|
|
952
|
+
messages: List[Dict[str, Any]],
|
|
953
|
+
limit: int = 10,
|
|
954
|
+
filter_tags: Optional[Dict[str, Any]] = None,
|
|
955
|
+
use_cache: bool = True,
|
|
956
|
+
) -> Dict[str, Any]:
|
|
957
|
+
"""
|
|
958
|
+
Retrieve relevant memories based on conversation context.
|
|
959
|
+
|
|
960
|
+
This method analyzes the conversation and retrieves relevant memories
|
|
961
|
+
from all memory systems.
|
|
962
|
+
|
|
963
|
+
Args:
|
|
964
|
+
user_id: User ID for the conversation
|
|
965
|
+
messages: List of message dicts with role and content.
|
|
966
|
+
Messages should end with a user turn.
|
|
967
|
+
Format: [
|
|
968
|
+
{"role": "user", "content": [{"type": "text", "text": "..."}]}
|
|
969
|
+
]
|
|
970
|
+
limit: Maximum number of items to retrieve per memory type (default: 10)
|
|
971
|
+
filter_tags: Optional dict of tags for filtering results.
|
|
972
|
+
Only memories matching these tags will be returned.
|
|
973
|
+
use_cache: Control Redis cache behavior (default: True)
|
|
974
|
+
|
|
975
|
+
Returns:
|
|
976
|
+
Dict containing retrieved memories organized by type
|
|
977
|
+
|
|
978
|
+
Example:
|
|
979
|
+
>>> memories = client.retrieve_with_conversation(
|
|
980
|
+
... user_id='user_123',
|
|
981
|
+
... messages=[
|
|
982
|
+
... {"role": "user", "content": [{"type": "text", "text": "Where did I go yesterday?"}]}
|
|
983
|
+
... ],
|
|
984
|
+
... limit=5,
|
|
985
|
+
... filter_tags={"session_id": "sess-789"}
|
|
986
|
+
... )
|
|
987
|
+
"""
|
|
988
|
+
if not self._meta_agent:
|
|
989
|
+
raise ValueError("Meta agent not initialized. Call initialize_meta_agent() first.")
|
|
990
|
+
|
|
991
|
+
request_data = {
|
|
992
|
+
"user_id": user_id,
|
|
993
|
+
"messages": messages,
|
|
994
|
+
"limit": limit,
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
if filter_tags is not None:
|
|
998
|
+
request_data["filter_tags"] = filter_tags
|
|
999
|
+
|
|
1000
|
+
if not use_cache:
|
|
1001
|
+
request_data["use_cache"] = use_cache
|
|
1002
|
+
|
|
1003
|
+
return self._request("POST", "/memory/retrieve/conversation", json=request_data)
|
|
1004
|
+
|
|
1005
|
+
def retrieve_with_topic(
|
|
1006
|
+
self,
|
|
1007
|
+
user_id: str,
|
|
1008
|
+
topic: str,
|
|
1009
|
+
limit: int = 10,
|
|
1010
|
+
) -> Dict[str, Any]:
|
|
1011
|
+
"""
|
|
1012
|
+
Retrieve relevant memories based on a topic.
|
|
1013
|
+
|
|
1014
|
+
This method searches for memories related to a specific topic or keyword.
|
|
1015
|
+
|
|
1016
|
+
Args:
|
|
1017
|
+
user_id: User ID for the conversation
|
|
1018
|
+
topic: Topic or keyword to search for
|
|
1019
|
+
limit: Maximum number of items to retrieve per memory type (default: 10)
|
|
1020
|
+
|
|
1021
|
+
Returns:
|
|
1022
|
+
Dict containing retrieved memories organized by type
|
|
1023
|
+
|
|
1024
|
+
Example:
|
|
1025
|
+
>>> memories = client.retrieve_with_topic(
|
|
1026
|
+
... user_id='user_123',
|
|
1027
|
+
... topic="dinner",
|
|
1028
|
+
... limit=5
|
|
1029
|
+
... )
|
|
1030
|
+
"""
|
|
1031
|
+
if not self._meta_agent:
|
|
1032
|
+
raise ValueError("Meta agent not initialized. Call initialize_meta_agent() first.")
|
|
1033
|
+
|
|
1034
|
+
params = {
|
|
1035
|
+
"user_id": user_id,
|
|
1036
|
+
"topic": topic,
|
|
1037
|
+
"limit": limit,
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
return self._request("GET", "/memory/retrieve/topic", params=params)
|
|
1041
|
+
|
|
1042
|
+
def search(
|
|
1043
|
+
self,
|
|
1044
|
+
user_id: str,
|
|
1045
|
+
query: str,
|
|
1046
|
+
memory_type: str = "all",
|
|
1047
|
+
search_field: str = "null",
|
|
1048
|
+
search_method: str = "bm25",
|
|
1049
|
+
limit: int = 10,
|
|
1050
|
+
) -> Dict[str, Any]:
|
|
1051
|
+
"""
|
|
1052
|
+
Search for memories using various search methods.
|
|
1053
|
+
Similar to the search_in_memory tool function.
|
|
1054
|
+
|
|
1055
|
+
This method performs a search across specified memory types and returns
|
|
1056
|
+
a flat list of results.
|
|
1057
|
+
|
|
1058
|
+
Args:
|
|
1059
|
+
user_id: User ID for the conversation
|
|
1060
|
+
query: Search query
|
|
1061
|
+
memory_type: Type of memory to search. Options: "episodic", "resource",
|
|
1062
|
+
"procedural", "knowledge_vault", "semantic", "all" (default: "all")
|
|
1063
|
+
search_field: Field to search in. Options vary by memory type:
|
|
1064
|
+
- episodic: "summary", "details"
|
|
1065
|
+
- resource: "summary", "content"
|
|
1066
|
+
- procedural: "summary", "steps"
|
|
1067
|
+
- knowledge_vault: "caption", "secret_value"
|
|
1068
|
+
- semantic: "name", "summary", "details"
|
|
1069
|
+
- For "all": use "null" (default)
|
|
1070
|
+
search_method: Search method. Options: "bm25" (default), "embedding"
|
|
1071
|
+
limit: Maximum number of results per memory type (default: 10)
|
|
1072
|
+
|
|
1073
|
+
Returns:
|
|
1074
|
+
Dict containing:
|
|
1075
|
+
- success: bool
|
|
1076
|
+
- query: str (the search query)
|
|
1077
|
+
- memory_type: str (the memory type searched)
|
|
1078
|
+
- search_field: str (the field searched)
|
|
1079
|
+
- search_method: str (the search method used)
|
|
1080
|
+
- results: List[Dict] (flat list of results from all memory types)
|
|
1081
|
+
- count: int (total number of results)
|
|
1082
|
+
|
|
1083
|
+
Example:
|
|
1084
|
+
>>> # Search all memory types
|
|
1085
|
+
>>> results = client.search(
|
|
1086
|
+
... user_id='user_123',
|
|
1087
|
+
... query="restaurants",
|
|
1088
|
+
... limit=5
|
|
1089
|
+
... )
|
|
1090
|
+
logger.debug("Found %s results", results['count'])
|
|
1091
|
+
>>>
|
|
1092
|
+
>>> # Search only episodic memories in details field
|
|
1093
|
+
>>> episodic_results = client.search(
|
|
1094
|
+
... user_id='user_123',
|
|
1095
|
+
... query="meeting",
|
|
1096
|
+
... memory_type="episodic",
|
|
1097
|
+
... search_field="details",
|
|
1098
|
+
... limit=10
|
|
1099
|
+
... )
|
|
1100
|
+
"""
|
|
1101
|
+
if not self._meta_agent:
|
|
1102
|
+
raise ValueError("Meta agent not initialized. Call initialize_meta_agent() first.")
|
|
1103
|
+
|
|
1104
|
+
params = {
|
|
1105
|
+
"user_id": user_id,
|
|
1106
|
+
"query": query,
|
|
1107
|
+
"memory_type": memory_type,
|
|
1108
|
+
"search_field": search_field,
|
|
1109
|
+
"search_method": search_method,
|
|
1110
|
+
"limit": limit,
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
return self._request("GET", "/memory/search", params=params)
|
|
1114
|
+
|
|
1115
|
+
# ========================================================================
|
|
1116
|
+
# LangChain/Composio/CrewAI Integration (Not Supported)
|
|
1117
|
+
# ========================================================================
|
|
1118
|
+
|
|
1119
|
+
def load_langchain_tool(
|
|
1120
|
+
self,
|
|
1121
|
+
langchain_tool: "LangChainBaseTool",
|
|
1122
|
+
additional_imports_module_attr_map: dict[str, str] = None,
|
|
1123
|
+
) -> Tool:
|
|
1124
|
+
"""Load LangChain tool."""
|
|
1125
|
+
raise NotImplementedError(
|
|
1126
|
+
"load_langchain_tool not supported in MirixClient. "
|
|
1127
|
+
"Tools must be created on the server side."
|
|
1128
|
+
)
|
|
1129
|
+
|
|
1130
|
+
def load_composio_tool(self, action: "ActionType") -> Tool:
|
|
1131
|
+
"""Load Composio tool."""
|
|
1132
|
+
raise NotImplementedError(
|
|
1133
|
+
"load_composio_tool not supported in MirixClient. "
|
|
1134
|
+
"Tools must be created on the server side."
|
|
1135
|
+
)
|
|
1136
|
+
|