jl-ecms-client 0.2.8__py3-none-any.whl → 0.2.23__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.

Files changed (40) hide show
  1. {jl_ecms_client-0.2.8.dist-info → jl_ecms_client-0.2.23.dist-info}/METADATA +6 -1
  2. jl_ecms_client-0.2.23.dist-info/RECORD +67 -0
  3. mirix/__init__.py +41 -0
  4. mirix/client/client.py +1 -1
  5. mirix/constants.py +251 -0
  6. mirix/errors.py +238 -0
  7. mirix/functions/__init__.py +0 -0
  8. mirix/functions/ast_parsers.py +113 -0
  9. mirix/functions/function_sets/__init__.py +1 -0
  10. mirix/functions/function_sets/base.py +330 -0
  11. mirix/functions/function_sets/extras.py +271 -0
  12. mirix/functions/function_sets/memory_tools.py +933 -0
  13. mirix/functions/functions.py +199 -0
  14. mirix/functions/helpers.py +311 -0
  15. mirix/functions/schema_generator.py +511 -0
  16. mirix/helpers/json_helpers.py +3 -3
  17. mirix/log.py +163 -0
  18. mirix/schemas/agent.py +1 -1
  19. mirix/schemas/block.py +1 -1
  20. mirix/schemas/embedding_config.py +0 -3
  21. mirix/schemas/enums.py +12 -0
  22. mirix/schemas/episodic_memory.py +1 -1
  23. mirix/schemas/knowledge_vault.py +1 -1
  24. mirix/schemas/memory.py +1 -1
  25. mirix/schemas/message.py +1 -1
  26. mirix/schemas/mirix_request.py +1 -1
  27. mirix/schemas/procedural_memory.py +1 -1
  28. mirix/schemas/providers.py +1 -1
  29. mirix/schemas/resource_memory.py +1 -1
  30. mirix/schemas/sandbox_config.py +1 -3
  31. mirix/schemas/semantic_memory.py +1 -1
  32. mirix/schemas/tool.py +241 -241
  33. mirix/schemas/user.py +3 -3
  34. mirix/settings.py +280 -0
  35. mirix/system.py +261 -0
  36. jl_ecms_client-0.2.8.dist-info/RECORD +0 -53
  37. mirix/client/constants.py +0 -60
  38. {jl_ecms_client-0.2.8.dist-info → jl_ecms_client-0.2.23.dist-info}/WHEEL +0 -0
  39. {jl_ecms_client-0.2.8.dist-info → jl_ecms_client-0.2.23.dist-info}/licenses/LICENSE +0 -0
  40. {jl_ecms_client-0.2.8.dist-info → jl_ecms_client-0.2.23.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,271 @@
1
+ from typing import Optional
2
+
3
+ from mirix.log import get_logger
4
+
5
+ logger = get_logger(__name__)
6
+
7
+ # def message_chatgpt(self, message: str):
8
+ # """
9
+ # Send a message to a more basic AI, ChatGPT. A useful resource for asking questions. ChatGPT does not retain memory of previous interactions.
10
+
11
+ # Args:
12
+ # message (str): Message to send ChatGPT. Phrase your message as a full English sentence.
13
+
14
+ # Returns:
15
+ # str: Reply message from ChatGPT
16
+ # """
17
+ # dummy_user_id = uuid.uuid4()
18
+ # dummy_agent_id = uuid.uuid4()
19
+ # message_sequence = [
20
+ # Message(user_id=dummy_user_id, agent_id=dummy_agent_id, role="system", text=MESSAGE_CHATGPT_FUNCTION_SYSTEM_MESSAGE),
21
+ # Message(user_id=dummy_user_id, agent_id=dummy_agent_id, role="user", text=str(message)),
22
+ # ]
23
+ # # TODO: this will error without an LLMConfig
24
+ # response = create(
25
+ # model=MESSAGE_CHATGPT_FUNCTION_MODEL,
26
+ # messages=message_sequence,
27
+ # )
28
+
29
+ # reply = response.choices[0].message.content
30
+ # return reply
31
+
32
+ # def read_from_text_file(self, filename: str, line_start: int, num_lines: Optional[int] = 1):
33
+ # """
34
+ # Read lines from a text file.
35
+
36
+ # Args:
37
+ # filename (str): The name of the file to read.
38
+ # line_start (int): Line to start reading from.
39
+ # num_lines (Optional[int]): How many lines to read (defaults to 1).
40
+
41
+ # Returns:
42
+ # str: Text read from the file
43
+ # """
44
+ # max_chars = 500
45
+ # trunc_message = True
46
+ # if not os.path.exists(filename):
47
+ # raise FileNotFoundError(f"The file '{filename}' does not exist.")
48
+
49
+ # if line_start < 1 or num_lines < 1:
50
+ # raise ValueError("Both line_start and num_lines must be positive integers.")
51
+
52
+ # lines = []
53
+ # chars_read = 0
54
+ # with open(filename, "r", encoding="utf-8") as file:
55
+ # for current_line_number, line in enumerate(file, start=1):
56
+ # if line_start <= current_line_number < line_start + num_lines:
57
+ # chars_to_add = len(line)
58
+ # if max_chars is not None and chars_read + chars_to_add > max_chars:
59
+ # # If adding this line exceeds MAX_CHARS, truncate the line if needed and stop reading further.
60
+ # excess_chars = (chars_read + chars_to_add) - max_chars
61
+ # lines.append(line[:-excess_chars].rstrip("\n"))
62
+ # if trunc_message:
63
+ # lines.append(f"[SYSTEM ALERT - max chars ({max_chars}) reached during file read]")
64
+ # break
65
+ # else:
66
+ # lines.append(line.rstrip("\n"))
67
+ # chars_read += chars_to_add
68
+ # if current_line_number >= line_start + num_lines - 1:
69
+ # break
70
+
71
+ # return "\n".join(lines)
72
+
73
+ # def append_to_text_file(self, filename: str, content: str):
74
+ # """
75
+ # Append to a text file.
76
+
77
+ # Args:
78
+ # filename (str): The name of the file to append to.
79
+ # content (str): Content to append to the file.
80
+
81
+ # Returns:
82
+ # Optional[str]: None is always returned as this function does not produce a response.
83
+ # """
84
+ # if not os.path.exists(filename):
85
+ # raise FileNotFoundError(f"The file '{filename}' does not exist.")
86
+
87
+ # with open(filename, "a", encoding="utf-8") as file:
88
+ # file.write(content + "\n")
89
+
90
+ # def http_request(self, method: str, url: str, payload_json: Optional[str] = None):
91
+ # """
92
+ # Generates an HTTP request and returns the response.
93
+
94
+ # Args:
95
+ # method (str): The HTTP method (e.g., 'GET', 'POST').
96
+ # url (str): The URL for the request.
97
+ # payload_json (Optional[str]): A JSON string representing the request payload.
98
+
99
+ # Returns:
100
+ # dict: The response from the HTTP request.
101
+ # """
102
+ # try:
103
+ # headers = {"Content-Type": "application/json"}
104
+
105
+ # # For GET requests, ignore the payload
106
+ # if method.upper() == "GET":
107
+ # logger.debug("[HTTP] launching GET request to %s", url)
108
+ # response = requests.get(url, headers=headers)
109
+ # else:
110
+ # # Validate and convert the payload for other types of requests
111
+ # if payload_json:
112
+ # payload = json_loads(payload_json)
113
+ # else:
114
+ # payload = {}
115
+ # logger.debug("[HTTP] launching %s request to %s, payload=\n%s", method, url, json_dumps(payload, indent=2))
116
+ # response = requests.request(method, url, json=payload, headers=headers)
117
+
118
+ # return {"status_code": response.status_code, "headers": dict(response.headers), "body": response.text}
119
+ # except Exception as e:
120
+ # return {"error": str(e)}
121
+
122
+ def fetch_and_read_pdf(self, url: str, max_pages: Optional[int] = 10):
123
+ """
124
+ Fetch and read a PDF file from a URL, extracting its text content.
125
+
126
+ Args:
127
+ url (str): The URL of the PDF file to fetch and read.
128
+ max_pages (Optional[int]): Maximum number of pages to read (defaults to 10).
129
+
130
+ Returns:
131
+ str: Extracted text content from the PDF file.
132
+ """
133
+ try:
134
+ logger.debug("[PDF_READER] Fetching PDF from: %s", url)
135
+
136
+ # Import libraries for PDF processing
137
+ try:
138
+ import io
139
+
140
+ import requests
141
+ from PyPDF2 import PdfReader
142
+ except ImportError as e:
143
+ missing_lib = str(e).split("'")[1] if "'" in str(e) else "required library"
144
+ return f"PDF processing library not available. Please install '{missing_lib}' to enable PDF reading functionality."
145
+
146
+ # Fetch the PDF file
147
+ headers = {
148
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
149
+ }
150
+
151
+ response = requests.get(url, headers=headers, timeout=30)
152
+
153
+ if response.status_code != 200:
154
+ return f"Failed to fetch PDF: HTTP status {response.status_code}"
155
+
156
+ # Check if the response is actually a PDF
157
+ content_type = response.headers.get("content-type", "").lower()
158
+ if "pdf" not in content_type and not url.lower().endswith(".pdf"):
159
+ return f"The URL does not appear to point to a PDF file. Content-Type: {content_type}"
160
+
161
+ logger.debug(
162
+ f"[PDF_READER] PDF fetched successfully, size: {len(response.content)} bytes"
163
+ )
164
+
165
+ # Read the PDF content
166
+ pdf_file = io.BytesIO(response.content)
167
+ pdf_reader = PdfReader(pdf_file)
168
+
169
+ num_pages = len(pdf_reader.pages)
170
+ max_pages = min(max_pages or 10, num_pages)
171
+
172
+ logger.debug(
173
+ f"[PDF_READER] PDF has {num_pages} pages, reading first {max_pages} pages"
174
+ )
175
+
176
+ extracted_text = []
177
+ for page_num in range(max_pages):
178
+ try:
179
+ page = pdf_reader.pages[page_num]
180
+ text = page.extract_text()
181
+ if text.strip(): # Only add non-empty pages
182
+ extracted_text.append(f"--- Page {page_num + 1} ---")
183
+ extracted_text.append(text.strip())
184
+ extracted_text.append("") # Empty line for separation
185
+ except Exception as page_error:
186
+ extracted_text.append(
187
+ f"--- Page {page_num + 1} (Error reading page) ---"
188
+ )
189
+ extracted_text.append(f"Error: {str(page_error)}")
190
+ extracted_text.append("")
191
+
192
+ if not extracted_text:
193
+ return "PDF was fetched successfully but no readable text content was found. The PDF may contain only images or be encrypted."
194
+
195
+ full_text = "\n".join(extracted_text)
196
+
197
+ # Limit output length for practical use
198
+ if len(full_text) > 10000:
199
+ full_text = (
200
+ full_text[:10000]
201
+ + f"\n\n[Text truncated - showing first 10,000 characters of {len(full_text)} total]"
202
+ )
203
+
204
+ logger.debug("[PDF_READER] Successfully extracted text from %s pages", max_pages)
205
+ return full_text
206
+
207
+ except Exception as e:
208
+ logger.debug("[PDF_READER] Error: %s", e)
209
+ return f"Error reading PDF: {str(e)}"
210
+
211
+ def web_search(self, query: str, num_results: Optional[int] = 5):
212
+ """
213
+ Search the web for information using DuckDuckGo comprehensive search.
214
+
215
+ Args:
216
+ query (str): The search query to look for on the web.
217
+ num_results (Optional[int]): Maximum number of results to return (defaults to 5, max 10).
218
+
219
+ Returns:
220
+ str: Search results formatted as text with titles, URLs, and descriptions.
221
+ """
222
+ try:
223
+ # Limit num_results to reasonable bounds
224
+ num_results = min(max(1, num_results or 5), 10)
225
+
226
+ logger.debug("[WEB_SEARCH] Searching for: %s", query)
227
+
228
+ # Import ddgs here to avoid import errors if not available
229
+ try:
230
+ from ddgs import DDGS
231
+
232
+ except ImportError:
233
+ return "Web search library not available. Please install 'ddgs' library to enable web search functionality."
234
+
235
+ # Use DDGS for comprehensive web search results
236
+ ddgs = DDGS()
237
+ search_results = ddgs.text(query, max_results=num_results)
238
+
239
+ if not search_results:
240
+ return f"No results found for '{query}'. Try rephrasing your search terms or using more specific keywords."
241
+
242
+ # Format results for display
243
+ formatted_results = []
244
+ for i, result in enumerate(search_results, 1):
245
+ title = result.get("title", "No title")
246
+ body = result.get("body", "No description")
247
+ href = result.get("href", "No URL")
248
+
249
+ # Limit description length for readability
250
+ if len(body) > 200:
251
+ body = body[:197] + "..."
252
+
253
+ formatted_results.append(f"{i}. {title}")
254
+ if body:
255
+ formatted_results.append(f" {body}")
256
+ if href:
257
+ formatted_results.append(f" URL: {href}")
258
+ formatted_results.append("") # Empty line for separation
259
+
260
+ # Remove the last empty line
261
+ if formatted_results and formatted_results[-1] == "":
262
+ formatted_results.pop()
263
+
264
+ result_text = "\n".join(formatted_results)
265
+ logger.debug("[WEB_SEARCH] Found %s results", len(search_results))
266
+
267
+ return result_text
268
+
269
+ except Exception as e:
270
+ logger.debug("[WEB_SEARCH] Error: %s", e)
271
+ return f"Search error: {str(e)}. Try rephrasing your search query."