symbolicai 0.20.2__py3-none-any.whl → 1.0.0__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.
- symai/__init__.py +96 -64
- symai/backend/base.py +93 -80
- symai/backend/engines/drawing/engine_bfl.py +12 -11
- symai/backend/engines/drawing/engine_gpt_image.py +108 -87
- symai/backend/engines/embedding/engine_llama_cpp.py +25 -28
- symai/backend/engines/embedding/engine_openai.py +3 -5
- symai/backend/engines/execute/engine_python.py +6 -5
- symai/backend/engines/files/engine_io.py +74 -67
- symai/backend/engines/imagecaptioning/engine_blip2.py +3 -3
- symai/backend/engines/imagecaptioning/engine_llavacpp_client.py +54 -38
- symai/backend/engines/index/engine_pinecone.py +23 -24
- symai/backend/engines/index/engine_vectordb.py +16 -14
- symai/backend/engines/lean/engine_lean4.py +38 -34
- symai/backend/engines/neurosymbolic/__init__.py +41 -13
- symai/backend/engines/neurosymbolic/engine_anthropic_claudeX_chat.py +262 -182
- symai/backend/engines/neurosymbolic/engine_anthropic_claudeX_reasoning.py +263 -191
- symai/backend/engines/neurosymbolic/engine_deepseekX_reasoning.py +53 -49
- symai/backend/engines/neurosymbolic/engine_google_geminiX_reasoning.py +212 -211
- symai/backend/engines/neurosymbolic/engine_groq.py +87 -63
- symai/backend/engines/neurosymbolic/engine_huggingface.py +21 -24
- symai/backend/engines/neurosymbolic/engine_llama_cpp.py +117 -48
- symai/backend/engines/neurosymbolic/engine_openai_gptX_chat.py +256 -229
- symai/backend/engines/neurosymbolic/engine_openai_gptX_reasoning.py +270 -150
- symai/backend/engines/ocr/engine_apilayer.py +6 -8
- symai/backend/engines/output/engine_stdout.py +1 -4
- symai/backend/engines/search/engine_openai.py +7 -7
- symai/backend/engines/search/engine_perplexity.py +5 -5
- symai/backend/engines/search/engine_serpapi.py +12 -14
- symai/backend/engines/speech_to_text/engine_local_whisper.py +20 -27
- symai/backend/engines/symbolic/engine_wolframalpha.py +3 -3
- symai/backend/engines/text_to_speech/engine_openai.py +5 -7
- symai/backend/engines/text_vision/engine_clip.py +7 -11
- symai/backend/engines/userinput/engine_console.py +3 -3
- symai/backend/engines/webscraping/engine_requests.py +81 -48
- symai/backend/mixin/__init__.py +13 -0
- symai/backend/mixin/anthropic.py +4 -2
- symai/backend/mixin/deepseek.py +2 -0
- symai/backend/mixin/google.py +2 -0
- symai/backend/mixin/openai.py +11 -3
- symai/backend/settings.py +83 -16
- symai/chat.py +101 -78
- symai/collect/__init__.py +7 -1
- symai/collect/dynamic.py +77 -69
- symai/collect/pipeline.py +35 -27
- symai/collect/stats.py +75 -63
- symai/components.py +198 -169
- symai/constraints.py +15 -12
- symai/core.py +698 -359
- symai/core_ext.py +32 -34
- symai/endpoints/api.py +80 -73
- symai/extended/.DS_Store +0 -0
- symai/extended/__init__.py +46 -12
- symai/extended/api_builder.py +11 -8
- symai/extended/arxiv_pdf_parser.py +13 -12
- symai/extended/bibtex_parser.py +2 -3
- symai/extended/conversation.py +101 -90
- symai/extended/document.py +17 -10
- symai/extended/file_merger.py +18 -13
- symai/extended/graph.py +18 -13
- symai/extended/html_style_template.py +2 -4
- symai/extended/interfaces/blip_2.py +1 -2
- symai/extended/interfaces/clip.py +1 -2
- symai/extended/interfaces/console.py +7 -1
- symai/extended/interfaces/dall_e.py +1 -1
- symai/extended/interfaces/flux.py +1 -1
- symai/extended/interfaces/gpt_image.py +1 -1
- symai/extended/interfaces/input.py +1 -1
- symai/extended/interfaces/llava.py +0 -1
- symai/extended/interfaces/naive_vectordb.py +7 -8
- symai/extended/interfaces/naive_webscraping.py +1 -1
- symai/extended/interfaces/ocr.py +1 -1
- symai/extended/interfaces/pinecone.py +6 -5
- symai/extended/interfaces/serpapi.py +1 -1
- symai/extended/interfaces/terminal.py +2 -3
- symai/extended/interfaces/tts.py +1 -1
- symai/extended/interfaces/whisper.py +1 -1
- symai/extended/interfaces/wolframalpha.py +1 -1
- symai/extended/metrics/__init__.py +11 -1
- symai/extended/metrics/similarity.py +11 -13
- symai/extended/os_command.py +17 -16
- symai/extended/packages/__init__.py +29 -3
- symai/extended/packages/symdev.py +19 -16
- symai/extended/packages/sympkg.py +12 -9
- symai/extended/packages/symrun.py +21 -19
- symai/extended/repo_cloner.py +11 -10
- symai/extended/seo_query_optimizer.py +1 -2
- symai/extended/solver.py +20 -23
- symai/extended/summarizer.py +4 -3
- symai/extended/taypan_interpreter.py +10 -12
- symai/extended/vectordb.py +99 -82
- symai/formatter/__init__.py +9 -1
- symai/formatter/formatter.py +12 -16
- symai/formatter/regex.py +62 -63
- symai/functional.py +176 -122
- symai/imports.py +136 -127
- symai/interfaces.py +56 -27
- symai/memory.py +14 -13
- symai/misc/console.py +49 -39
- symai/misc/loader.py +5 -3
- symai/models/__init__.py +17 -1
- symai/models/base.py +269 -181
- symai/models/errors.py +0 -1
- symai/ops/__init__.py +32 -22
- symai/ops/measures.py +11 -15
- symai/ops/primitives.py +348 -228
- symai/post_processors.py +32 -28
- symai/pre_processors.py +39 -41
- symai/processor.py +6 -4
- symai/prompts.py +59 -45
- symai/server/huggingface_server.py +23 -20
- symai/server/llama_cpp_server.py +7 -5
- symai/shell.py +3 -4
- symai/shellsv.py +499 -375
- symai/strategy.py +517 -287
- symai/symbol.py +111 -116
- symai/utils.py +42 -36
- {symbolicai-0.20.2.dist-info → symbolicai-1.0.0.dist-info}/METADATA +4 -2
- symbolicai-1.0.0.dist-info/RECORD +163 -0
- symbolicai-0.20.2.dist-info/RECORD +0 -162
- {symbolicai-0.20.2.dist-info → symbolicai-1.0.0.dist-info}/WHEEL +0 -0
- {symbolicai-0.20.2.dist-info → symbolicai-1.0.0.dist-info}/entry_points.txt +0 -0
- {symbolicai-0.20.2.dist-info → symbolicai-1.0.0.dist-info}/licenses/LICENSE +0 -0
- {symbolicai-0.20.2.dist-info → symbolicai-1.0.0.dist-info}/top_level.txt +0 -0
symai/imports.py
CHANGED
|
@@ -1,25 +1,24 @@
|
|
|
1
1
|
import importlib
|
|
2
2
|
import json
|
|
3
3
|
import logging
|
|
4
|
-
import os
|
|
5
4
|
import shutil
|
|
6
5
|
import stat
|
|
7
6
|
import subprocess
|
|
8
7
|
import sys
|
|
9
8
|
from pathlib import Path
|
|
10
|
-
from typing import List, Tuple, Union
|
|
11
9
|
|
|
12
10
|
from loguru import logger
|
|
13
11
|
|
|
14
12
|
from .backend.settings import HOME_PATH
|
|
15
13
|
from .symbol import Expression
|
|
14
|
+
from .utils import UserMessage
|
|
16
15
|
|
|
17
16
|
logging.getLogger("subprocess").setLevel(logging.ERROR)
|
|
18
17
|
|
|
19
18
|
|
|
20
19
|
__root_dir__ = HOME_PATH / 'packages'
|
|
21
20
|
BASE_PACKAGE_MODULE = '' # use relative path
|
|
22
|
-
BASE_PACKAGE_PATH =
|
|
21
|
+
BASE_PACKAGE_PATH = __root_dir__
|
|
23
22
|
sys.path.append(str(__root_dir__))
|
|
24
23
|
|
|
25
24
|
|
|
@@ -33,10 +32,10 @@ class Import(Expression):
|
|
|
33
32
|
@staticmethod
|
|
34
33
|
def exists(module):
|
|
35
34
|
# Check if module is a local path or a GitHub repo reference
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
module_path = Path(module)
|
|
36
|
+
if module_path.exists() and module_path.is_dir():
|
|
37
|
+
return (module_path / 'package.json').exists()
|
|
38
|
+
return (BASE_PACKAGE_PATH / module / 'package.json').exists()
|
|
40
39
|
|
|
41
40
|
@staticmethod
|
|
42
41
|
def get_from_local(module, local_path):
|
|
@@ -47,37 +46,39 @@ class Import(Expression):
|
|
|
47
46
|
local_path: Path to local package directory
|
|
48
47
|
"""
|
|
49
48
|
# If base package does not exist, create it
|
|
50
|
-
|
|
51
|
-
os.makedirs(BASE_PACKAGE_PATH)
|
|
49
|
+
BASE_PACKAGE_PATH.mkdir(parents=True, exist_ok=True)
|
|
52
50
|
|
|
53
51
|
# Create module directory
|
|
54
|
-
module_path =
|
|
55
|
-
|
|
52
|
+
module_path = BASE_PACKAGE_PATH / module
|
|
53
|
+
module_path.parent.mkdir(parents=True, exist_ok=True)
|
|
56
54
|
|
|
57
55
|
# Copy files from local path to package directory
|
|
58
56
|
try:
|
|
59
|
-
if
|
|
57
|
+
if module_path.exists():
|
|
60
58
|
shutil.rmtree(module_path)
|
|
61
59
|
shutil.copytree(local_path, module_path)
|
|
62
60
|
logger.info(f"Copied local package from {local_path} to {module_path}")
|
|
63
61
|
|
|
64
62
|
# Install dependencies
|
|
65
|
-
|
|
66
|
-
|
|
63
|
+
package_json = module_path / 'package.json'
|
|
64
|
+
if package_json.exists():
|
|
65
|
+
with package_json.open() as f:
|
|
67
66
|
pkg = json.load(f)
|
|
68
67
|
for dependency in pkg.get('dependencies', []):
|
|
69
68
|
# Update git_url for the dependency
|
|
70
69
|
git_url_dependency = f'git@github.com:{dependency}.git'
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
dependency_path = BASE_PACKAGE_PATH / dependency
|
|
71
|
+
if not dependency_path.exists():
|
|
72
|
+
subprocess.check_call(['git', 'clone', git_url_dependency, str(dependency_path)])
|
|
73
73
|
|
|
74
74
|
# Install requirements
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
requirements_file = module_path / 'requirements.txt'
|
|
76
|
+
if requirements_file.exists():
|
|
77
|
+
with requirements_file.open() as f:
|
|
77
78
|
for dependency in f.readlines():
|
|
78
|
-
|
|
79
|
-
if
|
|
80
|
-
subprocess.check_call([sys.executable, '-m', 'pip', 'install',
|
|
79
|
+
dependency_name = dependency.strip()
|
|
80
|
+
if dependency_name:
|
|
81
|
+
subprocess.check_call([sys.executable, '-m', 'pip', 'install', dependency_name])
|
|
81
82
|
except Exception as e:
|
|
82
83
|
logger.error(f"Error installing from local path: {e}")
|
|
83
84
|
raise
|
|
@@ -85,29 +86,31 @@ class Import(Expression):
|
|
|
85
86
|
@staticmethod
|
|
86
87
|
def get_from_github(module, submodules=False):
|
|
87
88
|
# if base package does not exist, create it
|
|
88
|
-
|
|
89
|
-
os.makedirs(BASE_PACKAGE_PATH)
|
|
89
|
+
BASE_PACKAGE_PATH.mkdir(parents=True, exist_ok=True)
|
|
90
90
|
|
|
91
91
|
# Clone repository
|
|
92
92
|
git_url = f'git@github.com:{module}.git'
|
|
93
93
|
clone_cmd = ['git', 'clone']
|
|
94
94
|
if submodules:
|
|
95
95
|
clone_cmd.extend(['--recurse-submodules'])
|
|
96
|
-
clone_cmd.extend([git_url,
|
|
96
|
+
clone_cmd.extend([git_url, str(BASE_PACKAGE_PATH / module)])
|
|
97
97
|
subprocess.check_call(clone_cmd)
|
|
98
98
|
|
|
99
99
|
# Install dependencies
|
|
100
|
-
|
|
100
|
+
package_json = BASE_PACKAGE_PATH / module / 'package.json'
|
|
101
|
+
with package_json.open() as f:
|
|
101
102
|
pkg = json.load(f)
|
|
102
103
|
for dependency in pkg['dependencies']:
|
|
103
104
|
# Update git_url for the dependency
|
|
104
105
|
git_url_dependency = f'git@github.com:{dependency}.git'
|
|
105
|
-
|
|
106
|
-
|
|
106
|
+
dependency_path = BASE_PACKAGE_PATH / dependency
|
|
107
|
+
if not dependency_path.exists():
|
|
108
|
+
subprocess.check_call(['git', 'clone', git_url_dependency, str(dependency_path)])
|
|
107
109
|
|
|
108
110
|
# Install requirements
|
|
109
|
-
|
|
110
|
-
|
|
111
|
+
requirements_file = BASE_PACKAGE_PATH / module / 'requirements.txt'
|
|
112
|
+
if requirements_file.exists():
|
|
113
|
+
with requirements_file.open() as f:
|
|
111
114
|
for dependency in f.readlines():
|
|
112
115
|
subprocess.check_call(['pip', 'install', dependency])
|
|
113
116
|
|
|
@@ -115,31 +118,25 @@ class Import(Expression):
|
|
|
115
118
|
def load_module_class(module):
|
|
116
119
|
module_classes = []
|
|
117
120
|
# Detect if module is a local path
|
|
118
|
-
|
|
121
|
+
module_path_obj = Path(module)
|
|
122
|
+
is_local_path = module_path_obj.exists() and module_path_obj.is_dir()
|
|
119
123
|
|
|
120
|
-
if is_local_path
|
|
121
|
-
package_path = module
|
|
122
|
-
else:
|
|
123
|
-
package_path = f'{BASE_PACKAGE_PATH}/{module}'
|
|
124
|
+
package_path = module_path_obj if is_local_path else BASE_PACKAGE_PATH / module
|
|
124
125
|
|
|
125
|
-
with
|
|
126
|
+
with (package_path / 'package.json').open() as f:
|
|
126
127
|
pkg = json.load(f)
|
|
127
128
|
for expr in pkg['expressions']:
|
|
128
129
|
if is_local_path:
|
|
129
|
-
module_path = f'{package_path}/{expr["module"].replace("/", ".")}'
|
|
130
130
|
# For local modules, we need to add the path to sys.path
|
|
131
|
-
parent_dir =
|
|
132
|
-
if parent_dir not in sys.path:
|
|
133
|
-
sys.path.append(parent_dir)
|
|
131
|
+
parent_dir = package_path.parent
|
|
132
|
+
if str(parent_dir) not in sys.path:
|
|
133
|
+
sys.path.append(str(parent_dir))
|
|
134
134
|
# Use local module's name from the directory structure
|
|
135
|
-
module_name =
|
|
135
|
+
module_name = package_path.name
|
|
136
136
|
relative_module_path = f"{module_name}.{expr['module'].replace('/', '.')}"
|
|
137
137
|
else:
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
relative_module_path = module_path.replace(BASE_PACKAGE_PATH, '').replace('/', '.').lstrip('.')
|
|
141
|
-
# Replace with actual username and package_name values
|
|
142
|
-
relative_module_path = module.split('/')[0] + '.' + module.split('/')[1] + '.' + relative_module_path
|
|
138
|
+
module_parts = module.split('/')
|
|
139
|
+
relative_module_path = '.'.join([*module_parts, expr['module'].replace('/', '.')])
|
|
143
140
|
|
|
144
141
|
try:
|
|
145
142
|
module_class = getattr(importlib.import_module(relative_module_path), expr['type'])
|
|
@@ -150,65 +147,66 @@ class Import(Expression):
|
|
|
150
147
|
return module_classes
|
|
151
148
|
|
|
152
149
|
@staticmethod
|
|
153
|
-
def
|
|
150
|
+
def _normalize_expressions(expressions: list[str] | tuple[str] | str) -> tuple[list[str], bool]:
|
|
151
|
+
if isinstance(expressions, str):
|
|
152
|
+
return [expressions], True
|
|
153
|
+
if isinstance(expressions, (list, tuple)):
|
|
154
|
+
expression_list = list(expressions)
|
|
155
|
+
return expression_list, len(expression_list) == 1
|
|
156
|
+
msg = "Invalid type for 'expressions'. Must be str, list or tuple."
|
|
157
|
+
UserMessage(msg)
|
|
158
|
+
raise Exception(msg)
|
|
159
|
+
|
|
160
|
+
@staticmethod
|
|
161
|
+
def load_expression(module, expressions: list[str] | tuple[str] | str) -> list[Expression] | Expression:
|
|
162
|
+
expression_list, return_single = Import._normalize_expressions(expressions)
|
|
163
|
+
expected_count = len(expression_list)
|
|
164
|
+
expression_targets = set(expression_list)
|
|
154
165
|
module_classes = []
|
|
155
166
|
# Detect if module is a local path
|
|
156
|
-
|
|
167
|
+
module_path_obj = Path(module)
|
|
168
|
+
is_local_path = module_path_obj.exists() and module_path_obj.is_dir()
|
|
169
|
+
|
|
170
|
+
package_path = module_path_obj if is_local_path else BASE_PACKAGE_PATH / module
|
|
157
171
|
|
|
158
172
|
if is_local_path:
|
|
159
|
-
|
|
173
|
+
parent_dir = package_path.parent
|
|
174
|
+
if str(parent_dir) not in sys.path:
|
|
175
|
+
sys.path.append(str(parent_dir))
|
|
176
|
+
module_name = package_path.name
|
|
160
177
|
else:
|
|
161
|
-
|
|
178
|
+
module_parts = module.split('/')
|
|
162
179
|
|
|
163
|
-
with
|
|
180
|
+
with (package_path / 'package.json').open() as f:
|
|
164
181
|
pkg = json.load(f)
|
|
165
182
|
for expr in pkg['expressions']:
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
183
|
+
relative_module_path = (
|
|
184
|
+
f"{module_name}.{expr['module'].replace('/', '.')}"
|
|
185
|
+
if is_local_path
|
|
186
|
+
else '.'.join([*module_parts, expr['module'].replace('/', '.')])
|
|
187
|
+
)
|
|
171
188
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
try:
|
|
186
|
-
module_obj = importlib.import_module(relative_module_path)
|
|
187
|
-
module_class = getattr(module_obj, expr['type'])
|
|
188
|
-
return module_class
|
|
189
|
-
except (ImportError, ModuleNotFoundError) as e:
|
|
190
|
-
logger.error(f"Error importing module {relative_module_path}: {e}")
|
|
191
|
-
raise
|
|
192
|
-
elif isinstance(expressions, list) or isinstance(expressions, tuple):
|
|
193
|
-
if expr['type'] in expressions:
|
|
194
|
-
try:
|
|
195
|
-
module_obj = importlib.import_module(relative_module_path)
|
|
196
|
-
module_class = getattr(module_obj, expr['type'])
|
|
197
|
-
if len(expressions) == 1:
|
|
198
|
-
return module_class
|
|
199
|
-
module_classes.append(module_class)
|
|
200
|
-
except (ImportError, ModuleNotFoundError) as e:
|
|
201
|
-
logger.error(f"Error importing module {relative_module_path}: {e}")
|
|
202
|
-
raise
|
|
203
|
-
else:
|
|
204
|
-
raise Exception("Invalid type for 'expressions'. Must be str, list or tuple.")
|
|
189
|
+
if expr['type'] not in expression_targets:
|
|
190
|
+
continue
|
|
191
|
+
|
|
192
|
+
try:
|
|
193
|
+
module_obj = importlib.import_module(relative_module_path)
|
|
194
|
+
module_class = getattr(module_obj, expr['type'])
|
|
195
|
+
except (ImportError, ModuleNotFoundError) as e:
|
|
196
|
+
logger.error(f"Error importing module {relative_module_path}: {e}")
|
|
197
|
+
raise
|
|
198
|
+
|
|
199
|
+
if return_single:
|
|
200
|
+
return module_class
|
|
201
|
+
module_classes.append(module_class)
|
|
205
202
|
|
|
206
203
|
assert len(module_classes) > 0, f"Expression '{expressions}' not found in module '{module}'"
|
|
207
204
|
module_classes_names = [str(class_.__name__) for class_ in module_classes]
|
|
208
|
-
|
|
205
|
+
missing_expressions = [expr for expr in expression_list if expr not in module_classes_names]
|
|
206
|
+
assert len(module_classes) == expected_count, f"Not all expressions found in module '{module}'. Could not load {missing_expressions}"
|
|
209
207
|
return module_classes
|
|
210
208
|
|
|
211
|
-
def __new__(
|
|
209
|
+
def __new__(cls, module, auto_clone: bool = True, verbose: bool = False, local_path: str | None = None,
|
|
212
210
|
submodules: bool = False, *args, **kwargs):
|
|
213
211
|
"""
|
|
214
212
|
Import a module from GitHub or local path.
|
|
@@ -222,15 +220,18 @@ class Import(Expression):
|
|
|
222
220
|
*args, **kwargs: Additional arguments to pass to the module constructor
|
|
223
221
|
"""
|
|
224
222
|
# Detect if module is a local path
|
|
225
|
-
|
|
223
|
+
module_path_obj = Path(module)
|
|
224
|
+
is_local_path = module_path_obj.exists() and module_path_obj.is_dir()
|
|
226
225
|
|
|
227
226
|
if is_local_path:
|
|
228
227
|
# If module is a local path
|
|
229
|
-
package_path =
|
|
230
|
-
if not
|
|
231
|
-
|
|
228
|
+
package_path = module_path_obj
|
|
229
|
+
if not (package_path / 'package.json').exists():
|
|
230
|
+
msg = f"No package.json found in {module}"
|
|
231
|
+
UserMessage(msg)
|
|
232
|
+
raise ValueError(msg)
|
|
232
233
|
|
|
233
|
-
with
|
|
234
|
+
with (package_path / 'package.json').open() as f:
|
|
234
235
|
pkg = json.load(f)
|
|
235
236
|
else:
|
|
236
237
|
# Module is a GitHub reference
|
|
@@ -240,27 +241,29 @@ class Import(Expression):
|
|
|
240
241
|
else:
|
|
241
242
|
Import.get_from_github(module, submodules)
|
|
242
243
|
|
|
243
|
-
with
|
|
244
|
+
with (BASE_PACKAGE_PATH / module / 'package.json').open() as f:
|
|
244
245
|
pkg = json.load(f)
|
|
245
246
|
if 'run' not in pkg:
|
|
246
|
-
|
|
247
|
+
msg = f"Module '{module}' has no 'run' expression defined."
|
|
248
|
+
UserMessage(msg)
|
|
249
|
+
raise Exception(msg)
|
|
247
250
|
expr = pkg['run']
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
relative_module_path =
|
|
251
|
-
# Replace with actual username and package_name values
|
|
252
|
-
relative_module_path = module.split('/')[0] + '.' + module.split('/')[1] + '.' + relative_module_path
|
|
251
|
+
module_rel = expr['module'].replace('/', '.')
|
|
252
|
+
module_parts = module.split('/')
|
|
253
|
+
relative_module_path = '.'.join([*module_parts, module_rel])
|
|
253
254
|
class_ = expr['type']
|
|
254
255
|
if verbose:
|
|
255
256
|
logger.info(f"Loading module '{relative_module_path}.{expr['type']}'")
|
|
256
257
|
module_class = getattr(importlib.import_module(relative_module_path), class_)
|
|
257
258
|
return module_class(*args, **kwargs)
|
|
258
259
|
|
|
259
|
-
def __call__(self, *
|
|
260
|
-
|
|
260
|
+
def __call__(self, *_args, **_kwargs):
|
|
261
|
+
msg = "Cannot call Import class directly. Use Import.load_module_class(module) instead."
|
|
262
|
+
UserMessage(msg)
|
|
263
|
+
raise Exception(msg)
|
|
261
264
|
|
|
262
265
|
@staticmethod
|
|
263
|
-
def install(module: str, local_path: str = None, submodules: bool = False):
|
|
266
|
+
def install(module: str, local_path: str | None = None, submodules: bool = False):
|
|
264
267
|
"""Install a package from GitHub or a local path.
|
|
265
268
|
|
|
266
269
|
Args:
|
|
@@ -269,11 +272,12 @@ class Import(Expression):
|
|
|
269
272
|
submodules: Whether to initialize submodules for GitHub repos
|
|
270
273
|
"""
|
|
271
274
|
# Determine if module is a local path
|
|
272
|
-
|
|
275
|
+
local_path_obj = Path(local_path) if local_path is not None else None
|
|
276
|
+
is_local_path = local_path_obj is not None and local_path_obj.exists() and local_path_obj.is_dir()
|
|
273
277
|
|
|
274
278
|
if not Import.exists(module):
|
|
275
279
|
if is_local_path:
|
|
276
|
-
Import.get_from_local(module,
|
|
280
|
+
Import.get_from_local(module, str(local_path_obj))
|
|
277
281
|
logger.success(f"Module '{module}' installed from local path.")
|
|
278
282
|
else:
|
|
279
283
|
Import.get_from_github(module, submodules)
|
|
@@ -284,44 +288,48 @@ class Import(Expression):
|
|
|
284
288
|
@staticmethod
|
|
285
289
|
def remove(module: str):
|
|
286
290
|
# Determine if module is a local path
|
|
287
|
-
|
|
291
|
+
module_path_obj = Path(module)
|
|
292
|
+
is_local_path = module_path_obj.exists() and module_path_obj.is_dir()
|
|
288
293
|
|
|
289
294
|
if is_local_path:
|
|
290
295
|
# For local path, remove directly
|
|
291
|
-
if
|
|
292
|
-
def del_rw(
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
+
if module_path_obj.exists():
|
|
297
|
+
def del_rw(_action, name, _exc):
|
|
298
|
+
path_obj = Path(name)
|
|
299
|
+
path_obj.chmod(stat.S_IWRITE)
|
|
300
|
+
path_obj.unlink()
|
|
301
|
+
shutil.rmtree(module_path_obj, onerror=del_rw)
|
|
296
302
|
logger.success(f"Removed local module at '{module}'")
|
|
297
303
|
else:
|
|
298
304
|
logger.error(f"Local module '{module}' not found.")
|
|
299
305
|
else:
|
|
300
306
|
# For GitHub modules, remove from packages directory
|
|
301
|
-
module_path =
|
|
302
|
-
if
|
|
303
|
-
def del_rw(
|
|
304
|
-
|
|
305
|
-
|
|
307
|
+
module_path = BASE_PACKAGE_PATH / module
|
|
308
|
+
if module_path.exists():
|
|
309
|
+
def del_rw(_action, name, _exc):
|
|
310
|
+
path_obj = Path(name)
|
|
311
|
+
path_obj.chmod(stat.S_IWRITE)
|
|
312
|
+
path_obj.unlink()
|
|
306
313
|
shutil.rmtree(module_path, onerror=del_rw)
|
|
307
314
|
logger.success(f"Removed module '{module}'")
|
|
308
315
|
|
|
309
316
|
# Check if folder is empty and remove it
|
|
310
|
-
parent_path =
|
|
311
|
-
if
|
|
312
|
-
|
|
317
|
+
parent_path = BASE_PACKAGE_PATH / module.split('/')[0]
|
|
318
|
+
if parent_path.exists() and not any(parent_path.iterdir()):
|
|
319
|
+
parent_path.rmdir()
|
|
313
320
|
logger.info(f"Removed empty parent folder '{parent_path}'")
|
|
314
321
|
else:
|
|
315
322
|
logger.error(f"Module '{module}' not found.")
|
|
316
323
|
|
|
317
324
|
@staticmethod
|
|
318
325
|
def list_installed():
|
|
319
|
-
|
|
326
|
+
if not BASE_PACKAGE_PATH.exists():
|
|
327
|
+
return []
|
|
328
|
+
base_dirs = [entry for entry in BASE_PACKAGE_PATH.iterdir() if entry.is_dir()]
|
|
320
329
|
|
|
321
330
|
sub_dirs = []
|
|
322
331
|
for base_dir in base_dirs:
|
|
323
|
-
|
|
324
|
-
sub_dirs.extend([f'{base_dir}/{dir}' for dir in os.listdir(full_path) if os.path.isdir(f'{full_path}/{dir}')])
|
|
332
|
+
sub_dirs.extend([f'{base_dir.name}/{entry.name}' for entry in base_dir.iterdir() if entry.is_dir()])
|
|
325
333
|
|
|
326
334
|
return sub_dirs
|
|
327
335
|
|
|
@@ -335,13 +343,14 @@ class Import(Expression):
|
|
|
335
343
|
"""
|
|
336
344
|
if Import.exists(module):
|
|
337
345
|
# Determine if module is a local path
|
|
338
|
-
|
|
346
|
+
module_path_obj = Path(module)
|
|
347
|
+
is_local_path = module_path_obj.exists() and module_path_obj.is_dir()
|
|
339
348
|
|
|
340
349
|
# Use the appropriate path based on whether it's local or not
|
|
341
|
-
module_path =
|
|
350
|
+
module_path = module_path_obj if is_local_path else BASE_PACKAGE_PATH / module.replace(".", "/")
|
|
342
351
|
|
|
343
352
|
# Construct the git pull command based on whether submodules should be included
|
|
344
|
-
pull_cmd = ['git', '-C', module_path]
|
|
353
|
+
pull_cmd = ['git', '-C', str(module_path)]
|
|
345
354
|
if submodules:
|
|
346
355
|
pull_cmd.extend(['pull', '--recurse-submodules'])
|
|
347
356
|
subprocess.check_call(pull_cmd)
|
symai/interfaces.py
CHANGED
|
@@ -5,7 +5,7 @@ from .backend.mixin import OPENAI_CHAT_MODELS, OPENAI_REASONING_MODELS
|
|
|
5
5
|
from .backend.settings import SYMAI_CONFIG
|
|
6
6
|
from .imports import Import
|
|
7
7
|
from .symbol import Expression
|
|
8
|
-
from .utils import
|
|
8
|
+
from .utils import UserMessage
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class Interface(Expression):
|
|
@@ -13,19 +13,19 @@ class Interface(Expression):
|
|
|
13
13
|
super().__init__(*args, **kwargs)
|
|
14
14
|
self.logger = logging.getLogger(__name__)
|
|
15
15
|
|
|
16
|
-
def __new__(
|
|
16
|
+
def __new__(cls, module: str, *args, **kwargs):
|
|
17
17
|
module = str(module)
|
|
18
18
|
# if `/` in module, assume github repo; else assume local module
|
|
19
19
|
if "/" in module:
|
|
20
20
|
return Import(module)
|
|
21
21
|
module = module.lower()
|
|
22
22
|
module = module.replace("-", "_")
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
return Interface.load_module_class(
|
|
23
|
+
cls._module = module
|
|
24
|
+
cls.module_path = f"symai.extended.interfaces.{module}"
|
|
25
|
+
return Interface.load_module_class(cls.module_path, cls._module)(*args, **kwargs)
|
|
26
26
|
|
|
27
|
-
def __call__(self, *
|
|
28
|
-
|
|
27
|
+
def __call__(self, *_args, **_kwargs):
|
|
28
|
+
UserMessage(f"Interface {self._module} is not callable.", raise_with=NotImplementedError)
|
|
29
29
|
|
|
30
30
|
@staticmethod
|
|
31
31
|
def load_module_class(module_path, class_name):
|
|
@@ -33,37 +33,66 @@ class Interface(Expression):
|
|
|
33
33
|
return getattr(module_, class_name)
|
|
34
34
|
|
|
35
35
|
|
|
36
|
-
def
|
|
37
|
-
"""Maps configuration to interface."""
|
|
38
|
-
mapping = {}
|
|
36
|
+
def _add_symbolic_interface(mapping):
|
|
39
37
|
symbolic_api_key = SYMAI_CONFIG.get("SYMBOLIC_ENGINE_API_KEY")
|
|
40
38
|
if symbolic_api_key is not None:
|
|
41
39
|
mapping["symbolic"] = Interface("wolframalpha")
|
|
42
40
|
|
|
41
|
+
|
|
42
|
+
def _resolve_drawing_interface_name(drawing_engine_model):
|
|
43
|
+
if drawing_engine_model.startswith("flux"):
|
|
44
|
+
return "flux"
|
|
45
|
+
if drawing_engine_model.startswith("dall-e-"):
|
|
46
|
+
return "dall_e"
|
|
47
|
+
if drawing_engine_model.startswith("gpt-image-"):
|
|
48
|
+
return "gpt_image"
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _add_drawing_interface(mapping):
|
|
43
53
|
drawing_engine_api_key = SYMAI_CONFIG.get("DRAWING_ENGINE_API_KEY")
|
|
44
|
-
if drawing_engine_api_key is
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
54
|
+
if drawing_engine_api_key is None:
|
|
55
|
+
return
|
|
56
|
+
drawing_engine_model = SYMAI_CONFIG.get("DRAWING_ENGINE_MODEL")
|
|
57
|
+
interface_name = _resolve_drawing_interface_name(drawing_engine_model)
|
|
58
|
+
if interface_name is not None:
|
|
59
|
+
mapping["drawing"] = Interface(interface_name)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _resolve_search_interface_name(search_engine_model):
|
|
63
|
+
if search_engine_model.startswith("google"):
|
|
64
|
+
return "serpapi"
|
|
65
|
+
if search_engine_model.startswith("sonar"):
|
|
66
|
+
return "perplexity"
|
|
67
|
+
if search_engine_model in OPENAI_REASONING_MODELS + OPENAI_CHAT_MODELS:
|
|
68
|
+
return "openai_search"
|
|
69
|
+
return None
|
|
52
70
|
|
|
71
|
+
|
|
72
|
+
def _add_search_interface(mapping):
|
|
53
73
|
search_engine_api_key = SYMAI_CONFIG.get("SEARCH_ENGINE_API_KEY")
|
|
54
|
-
if search_engine_api_key is
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
mapping["search"] = Interface("openai_search")
|
|
74
|
+
if search_engine_api_key is None:
|
|
75
|
+
return
|
|
76
|
+
search_engine_model = SYMAI_CONFIG.get("SEARCH_ENGINE_MODEL")
|
|
77
|
+
interface_name = _resolve_search_interface_name(search_engine_model)
|
|
78
|
+
if interface_name is not None:
|
|
79
|
+
mapping["search"] = Interface(interface_name)
|
|
80
|
+
|
|
62
81
|
|
|
82
|
+
def _add_tts_interface(mapping):
|
|
63
83
|
tts_engine_api_key = SYMAI_CONFIG.get("TEXT_TO_SPEECH_ENGINE_API_KEY")
|
|
64
|
-
if tts_engine_api_key is not None:
|
|
84
|
+
if tts_engine_api_key is not None: # TODO: add tests for this engine
|
|
65
85
|
mapping["tts"] = Interface("tts")
|
|
66
86
|
|
|
87
|
+
|
|
88
|
+
def cfg_to_interface():
|
|
89
|
+
"""Maps configuration to interface."""
|
|
90
|
+
mapping = {}
|
|
91
|
+
_add_symbolic_interface(mapping)
|
|
92
|
+
_add_drawing_interface(mapping)
|
|
93
|
+
_add_search_interface(mapping)
|
|
94
|
+
_add_tts_interface(mapping)
|
|
95
|
+
|
|
67
96
|
mapping["indexing"] = Interface("naive_vectordb")
|
|
68
97
|
mapping["scraper"] = Interface("naive_webscraping")
|
|
69
98
|
mapping["stt"] = Interface("whisper")
|
symai/memory.py
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
from typing import List
|
|
2
1
|
|
|
3
2
|
from . import core_ext
|
|
4
|
-
from .symbol import Expression, Symbol
|
|
5
3
|
from .components import Function
|
|
4
|
+
from .symbol import Expression, Symbol
|
|
6
5
|
|
|
7
6
|
|
|
8
7
|
class Memory(Expression):
|
|
@@ -25,24 +24,24 @@ class Memory(Expression):
|
|
|
25
24
|
class SlidingWindowListMemory(Memory):
|
|
26
25
|
def __init__(self, window_size: int = 10, max_size: int = 1000, **kwargs):
|
|
27
26
|
super().__init__(**kwargs)
|
|
28
|
-
self._memory:
|
|
27
|
+
self._memory: list[str] = []
|
|
29
28
|
self._window_size: int = window_size
|
|
30
29
|
self._max_size: int = max_size
|
|
31
30
|
|
|
32
|
-
def store(self, query: str, *
|
|
31
|
+
def store(self, query: str, *_args, **_kwargs):
|
|
33
32
|
self._memory.append(query)
|
|
34
33
|
if len(self._memory) > self._max_size:
|
|
35
34
|
self._memory = self._memory[-self._max_size:]
|
|
36
35
|
|
|
37
|
-
def forget(self, query: Symbol, *
|
|
36
|
+
def forget(self, query: Symbol, *_args, **_kwargs):
|
|
38
37
|
self._memory.remove(query)
|
|
39
38
|
|
|
40
|
-
def recall(self, *
|
|
39
|
+
def recall(self, *_args, **_kwargs):
|
|
41
40
|
return self._memory[-self._window_size:]
|
|
42
41
|
|
|
43
42
|
|
|
44
43
|
class SlidingWindowStringConcatMemory(Memory):
|
|
45
|
-
def __init__(self, *
|
|
44
|
+
def __init__(self, *_args, **kwargs):
|
|
46
45
|
super().__init__(**kwargs)
|
|
47
46
|
self._memory: str = ''
|
|
48
47
|
self.marker: str = '[--++=|=++--]'
|
|
@@ -71,9 +70,9 @@ class SlidingWindowStringConcatMemory(Memory):
|
|
|
71
70
|
|
|
72
71
|
def store(self, query: str):
|
|
73
72
|
# append to string to memory
|
|
74
|
-
self._memory += f'{
|
|
73
|
+
self._memory += f'{query!s}{self.marker}'
|
|
75
74
|
|
|
76
|
-
def forget(self, query: Symbol, *
|
|
75
|
+
def forget(self, query: Symbol, *_args, **_kwargs):
|
|
77
76
|
# remove substring from memory
|
|
78
77
|
sym = Symbol(self._memory)
|
|
79
78
|
self._memory = str(sym - query)
|
|
@@ -101,13 +100,15 @@ class VectorDatabaseMemory(Memory):
|
|
|
101
100
|
self.top_k: int = top_k
|
|
102
101
|
self.index_name = index_name
|
|
103
102
|
|
|
104
|
-
def store(self, query: str , *
|
|
105
|
-
if not self.enabled:
|
|
103
|
+
def store(self, query: str , *_args, **_kwargs):
|
|
104
|
+
if not self.enabled:
|
|
105
|
+
return
|
|
106
106
|
|
|
107
107
|
self.add(Symbol(query).zip(), index_name=self.index_name)
|
|
108
108
|
|
|
109
|
-
def recall(self, query: str, *
|
|
110
|
-
if not self.enabled:
|
|
109
|
+
def recall(self, query: str, *_args, **_kwargs):
|
|
110
|
+
if not self.enabled:
|
|
111
|
+
return None
|
|
111
112
|
|
|
112
113
|
res = self.get(Symbol(query).embed().value, index_top_k=self.top_k, index_name=self.index_name).ast()
|
|
113
114
|
|