syntaxmatrix 1.4.6__py3-none-any.whl → 2.5.5.4__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.
Files changed (45) hide show
  1. syntaxmatrix/__init__.py +13 -8
  2. syntaxmatrix/agentic/__init__.py +0 -0
  3. syntaxmatrix/agentic/agent_tools.py +24 -0
  4. syntaxmatrix/agentic/agents.py +810 -0
  5. syntaxmatrix/agentic/code_tools_registry.py +37 -0
  6. syntaxmatrix/agentic/model_templates.py +1790 -0
  7. syntaxmatrix/auth.py +308 -14
  8. syntaxmatrix/commentary.py +328 -0
  9. syntaxmatrix/core.py +993 -375
  10. syntaxmatrix/dataset_preprocessing.py +218 -0
  11. syntaxmatrix/db.py +92 -95
  12. syntaxmatrix/display.py +95 -121
  13. syntaxmatrix/generate_page.py +634 -0
  14. syntaxmatrix/gpt_models_latest.py +46 -0
  15. syntaxmatrix/history_store.py +26 -29
  16. syntaxmatrix/kernel_manager.py +96 -17
  17. syntaxmatrix/llm_store.py +1 -1
  18. syntaxmatrix/plottings.py +6 -0
  19. syntaxmatrix/profiles.py +64 -8
  20. syntaxmatrix/project_root.py +55 -43
  21. syntaxmatrix/routes.py +5072 -1398
  22. syntaxmatrix/session.py +19 -0
  23. syntaxmatrix/settings/logging.py +40 -0
  24. syntaxmatrix/settings/model_map.py +300 -33
  25. syntaxmatrix/settings/prompts.py +273 -62
  26. syntaxmatrix/settings/string_navbar.py +3 -3
  27. syntaxmatrix/static/docs.md +272 -0
  28. syntaxmatrix/static/icons/favicon.png +0 -0
  29. syntaxmatrix/static/icons/hero_bg.jpg +0 -0
  30. syntaxmatrix/templates/dashboard.html +608 -147
  31. syntaxmatrix/templates/docs.html +71 -0
  32. syntaxmatrix/templates/error.html +2 -3
  33. syntaxmatrix/templates/login.html +1 -0
  34. syntaxmatrix/templates/register.html +1 -0
  35. syntaxmatrix/ui_modes.py +14 -0
  36. syntaxmatrix/utils.py +2482 -159
  37. syntaxmatrix/vectorizer.py +16 -12
  38. {syntaxmatrix-1.4.6.dist-info → syntaxmatrix-2.5.5.4.dist-info}/METADATA +20 -17
  39. syntaxmatrix-2.5.5.4.dist-info/RECORD +68 -0
  40. syntaxmatrix/model_templates.py +0 -30
  41. syntaxmatrix/static/icons/favicon.ico +0 -0
  42. syntaxmatrix-1.4.6.dist-info/RECORD +0 -54
  43. {syntaxmatrix-1.4.6.dist-info → syntaxmatrix-2.5.5.4.dist-info}/WHEEL +0 -0
  44. {syntaxmatrix-1.4.6.dist-info → syntaxmatrix-2.5.5.4.dist-info}/licenses/LICENSE.txt +0 -0
  45. {syntaxmatrix-1.4.6.dist-info → syntaxmatrix-2.5.5.4.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,46 @@
1
+ # --- Helper: robustly extract text from Responses API objects ---
2
+ def extract_output_text(resp) -> str:
3
+ # Fast path
4
+ if hasattr(resp, "output_text") and resp.output_text:
5
+ return resp.output_text.strip()
6
+ # Fallback: parse .output for message->content blocks
7
+ text_parts = []
8
+ output = getattr(resp, "output", None) or []
9
+ for item in output:
10
+ if getattr(item, "type", "") != "message":
11
+ continue
12
+ for block in getattr(item, "content", []) or []:
13
+ btype = getattr(block, "type", "")
14
+ if btype in ("output_text", "text"):
15
+ t = (getattr(block, "text", "") or "").strip()
16
+ if t:
17
+ text_parts.append(t)
18
+ return "\n".join(text_parts).strip()
19
+
20
+
21
+ def set_args(
22
+ model,
23
+ instructions,
24
+ input,
25
+ previous_id=None,
26
+ store=False,
27
+ reasoning_effort="medium", # "minimal", "low", "medium", "high"
28
+ verbosity="medium", # "low", "medium", "high"
29
+ truncation="auto",
30
+ ):
31
+ base_params = {
32
+ "model": model,
33
+ "instructions": instructions,
34
+ "input": input,
35
+ "previous_response_id": previous_id,
36
+ "store": store,
37
+ "truncation": truncation,
38
+ }
39
+ if model == "gpt-5.1-chat-latest":
40
+ args = base_params
41
+ else:
42
+ args = {**base_params,
43
+ "reasoning": {"effort": reasoning_effort},
44
+ "text": {"verbosity": verbosity}
45
+ }
46
+ return args
@@ -11,7 +11,7 @@ from syntaxmatrix.project_root import detect_project_root
11
11
 
12
12
  _CLIENT_DIR = detect_project_root()
13
13
  # ——— Anonymous-user JSON fallback store ——————————————
14
- _fallback_dir = os.path.join(_CLIENT_DIR, "data", "smx_history")
14
+ _fallback_dir = os.path.join(_CLIENT_DIR, "smx_history")
15
15
  os.makedirs(_fallback_dir, exist_ok=True)
16
16
 
17
17
  # Persist chats.db under dev app’s data/ directory
@@ -79,17 +79,14 @@ class SQLHistoryStore:
79
79
  payload = json.dumps(history, ensure_ascii=False)
80
80
  now = datetime.utcnow().isoformat()
81
81
  # if caller didn’t supply a title, preserve existing or default
82
- if title is not None:
83
- conn.execute(
84
- "INSERT OR REPLACE INTO chats (user_id, chat_id, title, history, updated_at) "
85
- "VALUES (?, ?, ?, ?, ?)",
86
- (user_id, chat_id, title, payload, now)
87
- )
88
- else:
89
- conn.execute(
90
- "UPDATE chats SET history = ?, updated_at = ? WHERE user_id = ? AND chat_id = ?",
91
- (payload, now, user_id, chat_id)
92
- )
82
+ if title is None:
83
+ cur = conn.execute("SELECT title FROM chats WHERE user_id=? AND chat_id=?", (user_id, chat_id))
84
+ row = cur.fetchone()
85
+ title = row[0] if row else "Current"
86
+ conn.execute(
87
+ "INSERT OR REPLACE INTO chats (user_id, chat_id, title, history, updated_at) VALUES (?, ?, ?, ?, ?)",
88
+ (user_id, chat_id, title, payload, now)
89
+ )
93
90
  conn.commit()
94
91
  conn.close()
95
92
 
@@ -173,20 +170,20 @@ class PersistentHistoryStore:
173
170
  os.remove(file_path)
174
171
 
175
172
 
176
- @atexit.register
177
- def _clear_anonymous_history_on_exit() -> None:
178
- """
179
- On clean shutdown, delete all JSON chat files for anonymous users
180
- under data/smx_history/, leaving chats.db untouched.
181
- """
182
- history_dir = Path(_fallback_dir)
183
- if not history_dir.is_dir():
184
- return
185
-
186
- for file_path in history_dir.glob("*.json"):
187
- try:
188
- file_path.unlink()
189
- except Exception as e:
190
- logging.warning(
191
- f"syntaxmatrix: failed to delete anonymous history file {file_path}: {e}"
192
- )
173
+ @atexit.register
174
+ def _clear_anonymous_history_on_exit() -> None:
175
+ """
176
+ On clean shutdown, delete all JSON chat files for anonymous users
177
+ under data/smx_history/, leaving chats.db untouched.
178
+ """
179
+ history_dir = Path(_fallback_dir)
180
+ if not history_dir.is_dir():
181
+ return
182
+
183
+ for file_path in history_dir.glob("*.json"):
184
+ try:
185
+ file_path.unlink()
186
+ except Exception as e:
187
+ logging.warning(
188
+ f"syntaxmatrix: failed to delete anonymous history file {file_path}: {e}"
189
+ )
@@ -2,12 +2,15 @@
2
2
 
3
3
  import jupyter_client
4
4
  import nest_asyncio
5
- import uuid
6
- import time
7
- import re
5
+ import io, contextlib
6
+ import time
8
7
  from functools import wraps
9
8
  import inspect, pandas as _pd
10
-
9
+ from functools import wraps
10
+ import inspect, pandas as _pd
11
+ import io, contextlib
12
+ import html as _html
13
+ import re as _re
11
14
 
12
15
  nest_asyncio.apply()
13
16
 
@@ -42,11 +45,13 @@ class SyntaxMatrixKernelManager:
42
45
  for sid in list(cls._kernels):
43
46
  cls.shutdown_kernel(sid)
44
47
 
45
- nest_asyncio.apply()
46
48
 
47
49
  _df_cache = None
48
50
 
49
- def execute_code_in_kernel(kc, code, timeout=8):
51
+ def execute_code_in_kernel(kc, code, timeout=120):
52
+
53
+ _local_stdout = ""
54
+ _local_stderr = ""
50
55
 
51
56
  global _df_cache
52
57
  exec_namespace = {}
@@ -134,7 +139,13 @@ def execute_code_in_kernel(kc, code, timeout=8):
134
139
  exec_namespace["df"] = _df_cache
135
140
 
136
141
  try:
137
- exec(code, exec_namespace, exec_namespace)
142
+ # Prevent any print()/stdout/stderr from hitting your server console
143
+ _buf_out, _buf_err = io.StringIO(), io.StringIO()
144
+ with contextlib.redirect_stdout(_buf_out), contextlib.redirect_stderr(_buf_err):
145
+ exec(code, exec_namespace, exec_namespace)
146
+
147
+ _local_stdout = _buf_out.getvalue()
148
+ _local_stderr = _buf_err.getvalue()
138
149
 
139
150
  # ── show a friendly “missing package” hint ────
140
151
  except (ModuleNotFoundError, ImportError) as e:
@@ -152,7 +163,7 @@ def execute_code_in_kernel(kc, code, timeout=8):
152
163
  except Exception:
153
164
  pass
154
165
 
155
- # cache df for next call
166
+ # cache df for next call
156
167
  if "df" in exec_namespace:
157
168
  _df_cache = exec_namespace["df"]
158
169
 
@@ -171,34 +182,102 @@ def execute_code_in_kernel(kc, code, timeout=8):
171
182
  start_time = time.time()
172
183
 
173
184
  while True:
185
+ # Block until a message is available; if `timeout` is None this will block.
174
186
  try:
175
187
  msg = kc.get_iopub_msg(timeout=timeout)
176
188
  except Exception:
177
- break # timeout reached
189
+ break # only trips if a numeric timeout was provided # timeout reached
178
190
 
179
191
  if msg["parent_header"].get("msg_id") != msg_id:
180
192
  continue
181
193
 
182
194
  mtype, content = msg["msg_type"], msg["content"]
195
+ # Stop cleanly when the kernel reports it is idle (execution finished)
196
+ if mtype == 'status' and content.get('execution_state') == 'idle':
197
+ break
198
+
199
+ # if mtype == "stream":
200
+ # output_blocks.append(f"<pre>{content['text']}</pre>")
183
201
 
184
202
  if mtype == "stream":
185
- output_blocks.append(f"<pre>{content['text']}</pre>")
203
+ raw = content.get("text", "")
204
+ # Remove noisy reprs from printed HTML/Markdown display objects
205
+ lines = [
206
+ ln for ln in raw.splitlines()
207
+ if ("IPython.core.display.HTML object" not in ln
208
+ and "IPython.core.display.Markdown object" not in ln)
209
+ ]
210
+ txt = "\n".join(lines).strip()
211
+ if txt:
212
+ output_blocks.append(f"<pre>{_html.escape(txt)}</pre>")
213
+
214
+ # elif mtype in ("execute_result", "display_data"):
215
+ # data = content["data"]
216
+ # if "text/html" in data:
217
+ # output_blocks.append(data["text/html"])
218
+ # elif "image/png" in data:
219
+ # output_blocks.append(
220
+ # f"<img src='data:image/png;base64,{data['image/png']}' "
221
+ # f"style='max-width:100%;'/>"
222
+ # )
223
+ # else:
224
+ # output_blocks.append(f"<pre>{data.get('text/plain','')}</pre>")
186
225
 
187
226
  elif mtype in ("execute_result", "display_data"):
188
- data = content["data"]
227
+ data = content.get("data", {})
189
228
  if "text/html" in data:
190
- output_blocks.append(data["text/html"])
229
+ output_blocks.append(data["text/html"])
191
230
  elif "image/png" in data:
192
- output_blocks.append(
193
- f"<img src='data:image/png;base64,{data['image/png']}' "
194
- f"style='max-width:100%;'/>"
195
- )
231
+ output_blocks.append(
232
+ f"<img src='data:image/png;base64,{data['image/png']}' "
233
+ f"style='max-width:100%;'/>"
234
+ )
235
+
196
236
  else:
197
- output_blocks.append(f"<pre>{data.get('text/plain','')}</pre>")
237
+ # Clean up plain-text reprs like "<IPython.core.display.HTML object>"
238
+ txt = data.get("text/plain", "") or ""
239
+ if ("IPython.core.display.HTML object" in txt
240
+ or "IPython.core.display.Markdown object" in txt):
241
+ # skip useless reprs entirely
242
+ continue
243
+ output_blocks.append(f"<pre>{_html.escape(txt)}</pre>")
198
244
 
199
245
  elif mtype == "error":
200
246
  # keep the traceback html-friendly
201
247
  traceback_html = "<br>".join(content["traceback"])
202
248
  errors.append(f"<pre style='color:red;'>{traceback_html}</pre>")
249
+ # --- surface the locally captured commentary (stdout/stderr) back to the UI ---
250
+ if _local_stdout.strip():
251
+ # Put commentary first so the user sees it above plots/tables
252
+ output_blocks.insert(0, f"<pre>{_html.escape(_local_stdout)}</pre>")
253
+ if _local_stderr.strip():
254
+ errors.insert(0, f"<pre style='color:#b00;'>{_html.escape(_local_stderr)}</pre>")
255
+
256
+ def _smx_strip_display_reprs(text: str) -> str:
257
+ if not text:
258
+ return text
259
+ # remove tokens like "<IPython.core.display.HTML object>"
260
+ text = _re.sub(r"<IPython\.core\.display\.[A-Za-z]+\s+object>", "", text)
261
+ # if these were printed as lists, remove leftover brackets/commas
262
+ text = _re.sub(r"[\[\],]", " ", text)
263
+ # collapse whitespace
264
+ text = _re.sub(r"\s+", " ", text).strip()
265
+ return text
266
+
267
+ _cleaned_blocks = []
268
+ for blk in output_blocks:
269
+ # pre-wrapped plaintext
270
+ if blk.startswith("<pre>") and blk.endswith("</pre>"):
271
+ inner = blk[5:-6]
272
+ inner = _smx_strip_display_reprs(_html.unescape(inner))
273
+ if inner:
274
+ _cleaned_blocks.append(f"<pre>{_html.escape(inner)}</pre>")
275
+ # if empty after cleaning, drop it
276
+ continue
277
+
278
+ # html/img payloads: just remove stray repr tokens if they slipped in
279
+ cleaned = _re.sub(r"<IPython\.core\.display\.[A-Za-z]+\s+object>", "", blk)
280
+ _cleaned_blocks.append(cleaned)
281
+ output_blocks = _cleaned_blocks
203
282
 
204
283
  return output_blocks, errors
syntaxmatrix/llm_store.py CHANGED
@@ -12,7 +12,7 @@ from syntaxmatrix.project_root import detect_project_root
12
12
  # Ensures a stable encryption key, no env var needed.
13
13
  # ------------------------------------------------------------------
14
14
  _CLIENT_DIR = detect_project_root()
15
- KEY_PATH = os.path.join(_CLIENT_DIR, "data", "fernet.key")
15
+ KEY_PATH = os.path.join(_CLIENT_DIR, "fernet.key")
16
16
  if os.path.exists(KEY_PATH):
17
17
  __FERNET = Fernet(open(KEY_PATH, "rb").read())
18
18
  else:
syntaxmatrix/plottings.py CHANGED
@@ -14,6 +14,12 @@ warnings.filterwarnings(
14
14
  message="FigureCanvasAgg is non-interactive"
15
15
  )
16
16
 
17
+ def describe_matplotlib():
18
+ pass
19
+
20
+ def describe_plotly():
21
+ pass
22
+
17
23
  # ── Matplotlib Integration (static PNGs) ────────────────────────────────
18
24
 
19
25
  def figure(*args, **kwargs):
syntaxmatrix/profiles.py CHANGED
@@ -1,18 +1,74 @@
1
1
  # syntaxmatrix/profiles.py
2
+ from openai import OpenAI
3
+ from google import genai
4
+ import anthropic
2
5
 
3
6
  from syntaxmatrix.llm_store import list_profiles, load_profile
4
7
 
5
8
  # Preload once at import-time
6
9
  _profiles: dict[str, dict] = {}
7
- for entry in list_profiles():
8
- prof = load_profile(entry["name"])
9
- if prof:
10
- _profiles[entry["purpose"]] = prof
11
10
 
11
+ def _refresh_profiles() -> None:
12
+ _profiles.clear()
13
+ for p in list_profiles():
14
+ prof = load_profile(p["name"])
15
+ if prof:
16
+ _profiles[prof["purpose"]] = prof
17
+
18
+ def refresh_profiles_cache() -> None:
19
+ _refresh_profiles()
20
+
12
21
  def get_profile(purpose: str) -> dict:
22
+ prof = _profiles.get(purpose)
23
+ if prof:
24
+ return prof
25
+ _refresh_profiles()
26
+ return _profiles.get(purpose)
27
+
28
+ def get_profiles():
29
+ return list_profiles()
30
+
31
+ def get_client(profile):
32
+
33
+ provider = profile["provider"].lower()
34
+ api_key = profile["api_key"]
35
+
36
+ #1 - Google - gemini series
37
+ if provider == "google":
38
+ return genai.Client(api_key=api_key)
39
+
40
+ #2 OpenAI gpt-5 series
41
+ if provider == "openai":
42
+ return OpenAI(api_key=api_key)
43
+
44
+ #3 - xAI - grok series
45
+ if provider == "xai":
46
+ return OpenAI(api_key=api_key, base_url="https://api.x.ai/v1")
47
+
48
+ #4 - DeepSeek chat model
49
+ if provider == "deepseek":
50
+ return OpenAI(api_key=api_key, base_url="https://api.deepseek.com")
51
+
52
+ #5 - Moonshot chat model
53
+ if provider == "moonshot": #5
54
+ return OpenAI(api_key=api_key, base_url="https://api.moonshot.ai/v1")
55
+
56
+ #6 - Alibaba qwen series
57
+ if provider == "alibaba": #6
58
+ return OpenAI(api_key=api_key, base_url="https://dashscope-intl.aliyuncs.com/compatible-mode/v1",)
59
+
60
+ #7 - Anthropic claude series
61
+ if provider == "anthropic": #7
62
+ return anthropic.Anthropic(api_key=api_key)
63
+
64
+ def drop_cached_profile_by_name(profile_name: str) -> bool:
13
65
  """
14
- Return the full profile dict for that purpose (e.g. "chat", "embedding").
15
- Returns None if no such profile exists.
66
+ Remove the cached profile with this name (if present) from the in-memory map.
67
+ Returns True if something was removed.
16
68
  """
17
- prof = _profiles.get(purpose, None)
18
- return prof
69
+ removed = False
70
+ for purpose, prof in list(_profiles.items()):
71
+ if isinstance(prof, dict) and prof.get("name") == profile_name:
72
+ _profiles.pop(purpose, None)
73
+ removed = True
74
+ return removed
@@ -1,61 +1,73 @@
1
- # syntaxmatrix/project_root.py
2
-
1
+ # syntaxmatrix/project_root.py
3
2
  import os
4
3
  import inspect
5
4
  from pathlib import Path
6
5
  import syntaxmatrix
7
6
 
8
-
9
7
  def scandir() -> Path:
10
- """
11
- Find the first stack frame outside of the syntaxmatrix package
12
- whose filename is a real .py file on disk, and return its parent dir.
13
- """
14
8
  framework_dir = Path(syntaxmatrix.__file__).resolve().parent
15
-
16
9
  for frame in inspect.stack():
17
10
  fname = frame.filename
18
-
19
- # 1) skip internal frames (<frozen ...>) or empty names
20
- if not fname or fname.startswith("<"):
11
+ if not fname or not isinstance(fname, str):
21
12
  continue
22
-
23
- candidate = Path(fname)
24
-
25
- # 2) skip non-.py or non-existent paths
26
- if candidate.suffix != ".py" or not candidate.exists():
27
- continue
28
-
13
+ p = Path(fname)
29
14
  try:
30
- candidate = candidate.resolve()
31
- except (OSError, RuntimeError):
32
- # if for some reason resolve() fails, skip it
15
+ if p.is_file() and framework_dir not in p.parents:
16
+ return p.parent
17
+ except Exception:
33
18
  continue
19
+ return framework_dir
34
20
 
35
- # 3) skip anything inside the framework itself
36
- if framework_dir in candidate.parents:
37
- continue
38
-
39
- # FOUND: a user file (e.g. app.py, manage.py, etc.)
40
- return candidate.parent
41
-
42
- # fallback: whatever cwd() is
43
- return Path(os.getcwd()).resolve()
44
-
21
+ def _writable(p: Path) -> bool:
22
+ try:
23
+ p.mkdir(parents=True, exist_ok=True)
24
+ test = p / ".smx_write_test"
25
+ with open(test, "w", encoding="utf-8") as f:
26
+ f.write("ok")
27
+ test.unlink(missing_ok=True)
28
+ return True
29
+ except Exception:
30
+ return False
45
31
 
46
32
  def detect_project_root() -> Path:
47
33
  """
48
- Returns the consumer project's syntaxmatrixdir folder, creating it if necessary.
49
- All framework data & uploads live here.
34
+ Return the consumer project's 'syntaxmatrixdir' folder, creating it if necessary.
35
+ Resolution order:
36
+ 1) SMX_CLIENT_DIR (if set and writable)
37
+ 2) ./syntaxmatrixdir under current working dir (if writable)
38
+ 3) GCS Fuse standard mounts (if present & writable)
39
+ 4) /tmp/syntaxmatrixdir (always writable on Cloud Run)
40
+ 5) Fallback near the first non-framework caller (if writable)
50
41
  """
51
- # 1) First check the CWD (where your app:app is running)
42
+
43
+ # 1) Explicit override (keeps local stable; handy in Cloud Run)
44
+ env = os.environ.get("SMX_CLIENT_DIR")
45
+ if env:
46
+ p = Path(env)
47
+ if _writable(p):
48
+ return p
49
+
50
+ # 2) CWD-based
52
51
  cwd = Path.cwd()
53
- candidate = cwd / "syntaxmatrixdir"
54
- if candidate.exists():
55
- return candidate
56
-
57
- # 2) Otherwise fall back to the old logic (e.g. inside site-packages)
58
- proj_root = scandir()
59
- fw_root = proj_root / "syntaxmatrixdir"
60
- fw_root.mkdir(exist_ok=True)
61
- return fw_root
52
+ p = cwd / "syntaxmatrixdir"
53
+ if _writable(p):
54
+ return p
55
+
56
+ # 3) Common GCS Fuse mount points (Gen2)
57
+ for candidate in [Path("/mnt/gcs/syntaxmatrixdir"),
58
+ Path("/mnt/disks/gcs/syntaxmatrixdir")]:
59
+ if _writable(candidate):
60
+ return candidate
61
+
62
+ # 4) Cloud Run safe default
63
+ tmp = Path("/tmp/syntaxmatrixdir")
64
+ if _writable(tmp):
65
+ return tmp
66
+
67
+ # 5) Fallback alongside caller
68
+ fallback = scandir() / "syntaxmatrixdir"
69
+ if _writable(fallback):
70
+ return fallback
71
+
72
+ # Last resort: return /tmp anyway (avoids import-time crashes)
73
+ return tmp