symbolicai 1.0.0__py3-none-any.whl → 1.1.1__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 +198 -134
- symai/backend/base.py +51 -51
- symai/backend/engines/drawing/engine_bfl.py +33 -33
- symai/backend/engines/drawing/engine_gpt_image.py +4 -10
- symai/backend/engines/embedding/engine_llama_cpp.py +50 -35
- symai/backend/engines/embedding/engine_openai.py +22 -16
- symai/backend/engines/execute/engine_python.py +16 -16
- symai/backend/engines/files/engine_io.py +51 -49
- symai/backend/engines/imagecaptioning/engine_blip2.py +27 -23
- symai/backend/engines/imagecaptioning/engine_llavacpp_client.py +53 -46
- symai/backend/engines/index/engine_pinecone.py +116 -88
- symai/backend/engines/index/engine_qdrant.py +1011 -0
- symai/backend/engines/index/engine_vectordb.py +78 -52
- symai/backend/engines/lean/engine_lean4.py +65 -25
- symai/backend/engines/neurosymbolic/__init__.py +35 -28
- symai/backend/engines/neurosymbolic/engine_anthropic_claudeX_chat.py +137 -135
- symai/backend/engines/neurosymbolic/engine_anthropic_claudeX_reasoning.py +145 -152
- symai/backend/engines/neurosymbolic/engine_cerebras.py +328 -0
- symai/backend/engines/neurosymbolic/engine_deepseekX_reasoning.py +75 -49
- symai/backend/engines/neurosymbolic/engine_google_geminiX_reasoning.py +199 -155
- symai/backend/engines/neurosymbolic/engine_groq.py +106 -72
- symai/backend/engines/neurosymbolic/engine_huggingface.py +100 -67
- symai/backend/engines/neurosymbolic/engine_llama_cpp.py +121 -93
- symai/backend/engines/neurosymbolic/engine_openai_gptX_chat.py +213 -132
- symai/backend/engines/neurosymbolic/engine_openai_gptX_reasoning.py +180 -137
- symai/backend/engines/ocr/engine_apilayer.py +18 -20
- symai/backend/engines/output/engine_stdout.py +9 -9
- symai/backend/engines/{webscraping → scrape}/engine_requests.py +25 -11
- symai/backend/engines/search/engine_openai.py +95 -83
- symai/backend/engines/search/engine_parallel.py +665 -0
- symai/backend/engines/search/engine_perplexity.py +40 -41
- symai/backend/engines/search/engine_serpapi.py +33 -28
- symai/backend/engines/speech_to_text/engine_local_whisper.py +37 -27
- symai/backend/engines/symbolic/engine_wolframalpha.py +14 -8
- symai/backend/engines/text_to_speech/engine_openai.py +15 -19
- symai/backend/engines/text_vision/engine_clip.py +34 -28
- symai/backend/engines/userinput/engine_console.py +3 -4
- symai/backend/mixin/__init__.py +4 -0
- symai/backend/mixin/anthropic.py +48 -40
- symai/backend/mixin/cerebras.py +9 -0
- symai/backend/mixin/deepseek.py +4 -5
- symai/backend/mixin/google.py +5 -4
- symai/backend/mixin/groq.py +2 -4
- symai/backend/mixin/openai.py +132 -110
- symai/backend/settings.py +14 -14
- symai/chat.py +164 -94
- symai/collect/dynamic.py +13 -11
- symai/collect/pipeline.py +39 -31
- symai/collect/stats.py +109 -69
- symai/components.py +578 -238
- symai/constraints.py +14 -5
- symai/core.py +1495 -1210
- symai/core_ext.py +55 -50
- symai/endpoints/api.py +113 -58
- symai/extended/api_builder.py +22 -17
- symai/extended/arxiv_pdf_parser.py +13 -5
- symai/extended/bibtex_parser.py +8 -4
- symai/extended/conversation.py +88 -69
- symai/extended/document.py +40 -27
- symai/extended/file_merger.py +45 -7
- symai/extended/graph.py +38 -24
- symai/extended/html_style_template.py +17 -11
- symai/extended/interfaces/blip_2.py +1 -1
- symai/extended/interfaces/clip.py +4 -2
- symai/extended/interfaces/console.py +5 -3
- symai/extended/interfaces/dall_e.py +3 -1
- symai/extended/interfaces/file.py +2 -0
- symai/extended/interfaces/flux.py +3 -1
- symai/extended/interfaces/gpt_image.py +15 -6
- symai/extended/interfaces/input.py +2 -1
- symai/extended/interfaces/llava.py +1 -1
- symai/extended/interfaces/{naive_webscraping.py → naive_scrape.py} +3 -2
- symai/extended/interfaces/naive_vectordb.py +2 -2
- symai/extended/interfaces/ocr.py +4 -2
- symai/extended/interfaces/openai_search.py +2 -0
- symai/extended/interfaces/parallel.py +30 -0
- symai/extended/interfaces/perplexity.py +2 -0
- symai/extended/interfaces/pinecone.py +6 -4
- symai/extended/interfaces/python.py +2 -0
- symai/extended/interfaces/serpapi.py +2 -0
- symai/extended/interfaces/terminal.py +0 -1
- symai/extended/interfaces/tts.py +2 -1
- symai/extended/interfaces/whisper.py +2 -1
- symai/extended/interfaces/wolframalpha.py +1 -0
- symai/extended/metrics/__init__.py +1 -1
- symai/extended/metrics/similarity.py +5 -2
- symai/extended/os_command.py +31 -22
- symai/extended/packages/symdev.py +39 -34
- symai/extended/packages/sympkg.py +30 -27
- symai/extended/packages/symrun.py +46 -35
- symai/extended/repo_cloner.py +10 -9
- symai/extended/seo_query_optimizer.py +15 -12
- symai/extended/solver.py +104 -76
- symai/extended/summarizer.py +8 -7
- symai/extended/taypan_interpreter.py +10 -9
- symai/extended/vectordb.py +28 -15
- symai/formatter/formatter.py +39 -31
- symai/formatter/regex.py +46 -44
- symai/functional.py +184 -86
- symai/imports.py +85 -51
- symai/interfaces.py +1 -1
- symai/memory.py +33 -24
- symai/menu/screen.py +28 -19
- symai/misc/console.py +27 -27
- symai/misc/loader.py +4 -3
- symai/models/base.py +147 -76
- symai/models/errors.py +1 -1
- symai/ops/__init__.py +1 -1
- symai/ops/measures.py +17 -14
- symai/ops/primitives.py +933 -635
- symai/post_processors.py +28 -24
- symai/pre_processors.py +58 -52
- symai/processor.py +15 -9
- symai/prompts.py +714 -649
- symai/server/huggingface_server.py +115 -32
- symai/server/llama_cpp_server.py +14 -6
- symai/server/qdrant_server.py +206 -0
- symai/shell.py +98 -39
- symai/shellsv.py +307 -223
- symai/strategy.py +135 -81
- symai/symbol.py +276 -225
- symai/utils.py +62 -46
- {symbolicai-1.0.0.dist-info → symbolicai-1.1.1.dist-info}/METADATA +19 -9
- symbolicai-1.1.1.dist-info/RECORD +169 -0
- symbolicai-1.0.0.dist-info/RECORD +0 -163
- {symbolicai-1.0.0.dist-info → symbolicai-1.1.1.dist-info}/WHEEL +0 -0
- {symbolicai-1.0.0.dist-info → symbolicai-1.1.1.dist-info}/entry_points.txt +0 -0
- {symbolicai-1.0.0.dist-info → symbolicai-1.1.1.dist-info}/licenses/LICENSE +0 -0
- {symbolicai-1.0.0.dist-info → symbolicai-1.1.1.dist-info}/top_level.txt +0 -0
symai/core_ext.py
CHANGED
|
@@ -26,6 +26,7 @@ logging.getLogger("multiprocessing").setLevel(logging.ERROR)
|
|
|
26
26
|
_pool_registry: dict[int, mp.pool.Pool] = {}
|
|
27
27
|
_pool_lock = threading.Lock()
|
|
28
28
|
|
|
29
|
+
|
|
29
30
|
def _get_pool(workers: int) -> mp.pool.Pool:
|
|
30
31
|
pool = _pool_registry.get(workers)
|
|
31
32
|
if pool is not None:
|
|
@@ -37,28 +38,33 @@ def _get_pool(workers: int) -> mp.pool.Pool:
|
|
|
37
38
|
_pool_registry[workers] = pool
|
|
38
39
|
return pool
|
|
39
40
|
|
|
41
|
+
|
|
40
42
|
@atexit.register
|
|
41
43
|
def _shutdown_pools() -> None:
|
|
42
44
|
for pool in _pool_registry.values():
|
|
43
45
|
pool.close()
|
|
44
46
|
pool.join()
|
|
45
47
|
|
|
48
|
+
|
|
46
49
|
def _run_in_process(expr, func, args, kwargs):
|
|
47
50
|
expr = dill.loads(expr)
|
|
48
51
|
func = dill.loads(func)
|
|
49
52
|
return func(expr, *args, **kwargs)
|
|
50
53
|
|
|
54
|
+
|
|
51
55
|
def _parallel(func: Callable, expressions: list[Callable], worker: int = mp.cpu_count() // 2):
|
|
52
56
|
pickled_exprs = [dill.dumps(expr) for expr in expressions]
|
|
53
|
-
pickled_func
|
|
57
|
+
pickled_func = dill.dumps(func)
|
|
54
58
|
pool = _get_pool(worker)
|
|
59
|
+
|
|
55
60
|
def proxy_function(*args, **kwargs):
|
|
56
61
|
return pool.starmap(
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
62
|
+
_run_in_process, [(expr, pickled_func, args, kwargs) for expr in pickled_exprs]
|
|
63
|
+
)
|
|
64
|
+
|
|
60
65
|
return proxy_function
|
|
61
66
|
|
|
67
|
+
|
|
62
68
|
# Decorator
|
|
63
69
|
def parallel(expressions: list[Callable], worker: int = mp.cpu_count() // 2):
|
|
64
70
|
def decorator_parallel(func):
|
|
@@ -68,34 +74,31 @@ def parallel(expressions: list[Callable], worker: int = mp.cpu_count() // 2):
|
|
|
68
74
|
parallel_func = _parallel(func, expressions, worker=worker)
|
|
69
75
|
# Call the proxy function to execute in parallel and capture results
|
|
70
76
|
return parallel_func(*args, **kwargs)
|
|
77
|
+
|
|
71
78
|
return wrapper
|
|
79
|
+
|
|
72
80
|
return decorator_parallel
|
|
73
81
|
|
|
82
|
+
|
|
74
83
|
def bind(engine: str, property: str):
|
|
75
|
-
|
|
84
|
+
"""
|
|
76
85
|
Bind to an engine and retrieve one of its properties.
|
|
77
|
-
|
|
86
|
+
"""
|
|
87
|
+
|
|
78
88
|
def decorator(func):
|
|
79
89
|
@functools.wraps(func)
|
|
80
90
|
def wrapper(*_args, **_kwargs):
|
|
81
|
-
return EngineRepository.bind_property(
|
|
82
|
-
|
|
83
|
-
property=property
|
|
84
|
-
)
|
|
91
|
+
return EngineRepository.bind_property(engine=engine, property=property)
|
|
92
|
+
|
|
85
93
|
return wrapper
|
|
94
|
+
|
|
86
95
|
return decorator
|
|
87
96
|
|
|
88
97
|
|
|
89
98
|
def retry(
|
|
90
|
-
exceptions=Exception,
|
|
91
|
-
tries=-1,
|
|
92
|
-
delay=0,
|
|
93
|
-
max_delay=-1,
|
|
94
|
-
backoff=1,
|
|
95
|
-
jitter=0,
|
|
96
|
-
graceful=False
|
|
99
|
+
exceptions=Exception, tries=-1, delay=0, max_delay=-1, backoff=1, jitter=0, graceful=False
|
|
97
100
|
):
|
|
98
|
-
|
|
101
|
+
"""
|
|
99
102
|
Returns a retry decorator for both async and sync functions.
|
|
100
103
|
|
|
101
104
|
:param exceptions: an exception or a tuple of exceptions to catch. default: Exception.
|
|
@@ -108,9 +111,11 @@ def retry(
|
|
|
108
111
|
:param graceful: whether to raise an exception if all attempts fail or return with None. default: False.
|
|
109
112
|
|
|
110
113
|
Credits to invlpg (https://pypi.org/project/retry)
|
|
111
|
-
|
|
114
|
+
"""
|
|
115
|
+
|
|
112
116
|
def decorator(func):
|
|
113
117
|
if asyncio.iscoroutinefunction(func):
|
|
118
|
+
|
|
114
119
|
@functools.wraps(func)
|
|
115
120
|
async def async_wrapper(*args, **kwargs):
|
|
116
121
|
return await _aretry_func(
|
|
@@ -121,9 +126,11 @@ def retry(
|
|
|
121
126
|
max_delay=max_delay,
|
|
122
127
|
backoff=backoff,
|
|
123
128
|
jitter=jitter,
|
|
124
|
-
graceful=graceful
|
|
129
|
+
graceful=graceful,
|
|
125
130
|
)
|
|
131
|
+
|
|
126
132
|
return async_wrapper
|
|
133
|
+
|
|
127
134
|
@functools.wraps(func)
|
|
128
135
|
def sync_wrapper(*args, **kwargs):
|
|
129
136
|
return _retry_func(
|
|
@@ -134,9 +141,11 @@ def retry(
|
|
|
134
141
|
max_delay=max_delay,
|
|
135
142
|
backoff=backoff,
|
|
136
143
|
jitter=jitter,
|
|
137
|
-
graceful=graceful
|
|
144
|
+
graceful=graceful,
|
|
138
145
|
)
|
|
146
|
+
|
|
139
147
|
return sync_wrapper
|
|
148
|
+
|
|
140
149
|
return decorator
|
|
141
150
|
|
|
142
151
|
|
|
@@ -148,7 +157,7 @@ def _retry_func(
|
|
|
148
157
|
max_delay: int,
|
|
149
158
|
backoff: int,
|
|
150
159
|
jitter: int,
|
|
151
|
-
graceful: bool
|
|
160
|
+
graceful: bool,
|
|
152
161
|
):
|
|
153
162
|
_tries, _delay = tries, delay
|
|
154
163
|
while _tries:
|
|
@@ -182,7 +191,7 @@ async def _aretry_func(
|
|
|
182
191
|
max_delay: int,
|
|
183
192
|
backoff: int,
|
|
184
193
|
jitter: int,
|
|
185
|
-
graceful: bool
|
|
194
|
+
graceful: bool,
|
|
186
195
|
):
|
|
187
196
|
_tries, _delay = tries, delay
|
|
188
197
|
while _tries:
|
|
@@ -208,52 +217,42 @@ async def _aretry_func(
|
|
|
208
217
|
return None
|
|
209
218
|
|
|
210
219
|
|
|
211
|
-
def cache(
|
|
212
|
-
|
|
213
|
-
cache_path: str = __root_dir__ / 'cache'
|
|
214
|
-
):
|
|
215
|
-
'''
|
|
220
|
+
def cache(in_memory: bool, cache_path: str = __root_dir__ / "cache"):
|
|
221
|
+
"""
|
|
216
222
|
Cache the result of a *any* function call. This is very useful in cost optimization (e.g. computing embeddings).
|
|
217
|
-
|
|
223
|
+
"""
|
|
224
|
+
|
|
218
225
|
def decorator(func):
|
|
219
226
|
@functools.wraps(func)
|
|
220
227
|
def wrapper(instance):
|
|
221
|
-
return _cache_registry_func(
|
|
222
|
-
|
|
223
|
-
cache_path,
|
|
224
|
-
func,
|
|
225
|
-
instance
|
|
226
|
-
)
|
|
228
|
+
return _cache_registry_func(in_memory, cache_path, func, instance)
|
|
229
|
+
|
|
227
230
|
return wrapper
|
|
228
|
-
return decorator
|
|
229
231
|
|
|
232
|
+
return decorator
|
|
230
233
|
|
|
231
|
-
def _cache_registry_func(
|
|
232
|
-
in_memory: bool,
|
|
233
|
-
cache_path: str,
|
|
234
|
-
func: Callable,
|
|
235
|
-
*args, **kwargs
|
|
236
|
-
):
|
|
237
234
|
|
|
235
|
+
def _cache_registry_func(in_memory: bool, cache_path: str, func: Callable, *args, **kwargs):
|
|
238
236
|
cache_dir = Path(cache_path)
|
|
239
237
|
cache_dir.mkdir(parents=True, exist_ok=True)
|
|
240
238
|
cache_file = cache_dir / func.__qualname__
|
|
241
239
|
|
|
242
240
|
if in_memory and cache_file.exists():
|
|
243
|
-
with cache_file.open(
|
|
241
|
+
with cache_file.open("rb") as f:
|
|
244
242
|
return pickle.load(f)
|
|
245
243
|
|
|
246
244
|
call = func(*args, **kwargs)
|
|
247
|
-
with cache_file.open(
|
|
248
|
-
pickle.dump(call
|
|
245
|
+
with cache_file.open("wb") as f:
|
|
246
|
+
pickle.dump(call, f)
|
|
249
247
|
|
|
250
248
|
return call
|
|
251
249
|
|
|
252
250
|
|
|
253
251
|
def error_logging(debug: bool = False):
|
|
254
|
-
|
|
252
|
+
"""
|
|
255
253
|
Log the error of a function call.
|
|
256
|
-
|
|
254
|
+
"""
|
|
255
|
+
|
|
257
256
|
def dec(func):
|
|
258
257
|
@functools.wraps(func)
|
|
259
258
|
def _dec(*args, **kwargs):
|
|
@@ -263,11 +262,13 @@ def error_logging(debug: bool = False):
|
|
|
263
262
|
logger.error(e)
|
|
264
263
|
if debug:
|
|
265
264
|
# Simple message:
|
|
266
|
-
UserMessage(f
|
|
265
|
+
UserMessage(f"Function: {func.__name__} call failed. Error: {e}")
|
|
267
266
|
# print traceback
|
|
268
267
|
traceback.print_exc()
|
|
269
268
|
raise e
|
|
269
|
+
|
|
270
270
|
return _dec
|
|
271
|
+
|
|
271
272
|
return dec
|
|
272
273
|
|
|
273
274
|
|
|
@@ -289,18 +290,21 @@ def deprecated(reason: str = ""):
|
|
|
289
290
|
@deprecated("Use new_method() instead")
|
|
290
291
|
def old_method(self): pass
|
|
291
292
|
"""
|
|
293
|
+
|
|
292
294
|
def decorator(obj):
|
|
293
295
|
if isinstance(obj, type):
|
|
294
296
|
# If obj is a class
|
|
295
297
|
original_init = obj.__init__
|
|
298
|
+
|
|
296
299
|
@functools.wraps(original_init)
|
|
297
300
|
def new_init(self, *args, **kwargs):
|
|
298
301
|
logger.warning(
|
|
299
302
|
f"{obj.__name__} is deprecated and will be removed in future versions. {reason}",
|
|
300
303
|
category=DeprecationWarning,
|
|
301
|
-
stacklevel=2
|
|
304
|
+
stacklevel=2,
|
|
302
305
|
)
|
|
303
306
|
original_init(self, *args, **kwargs)
|
|
307
|
+
|
|
304
308
|
obj.__init__ = new_init
|
|
305
309
|
return obj
|
|
306
310
|
|
|
@@ -310,9 +314,10 @@ def deprecated(reason: str = ""):
|
|
|
310
314
|
logger.warning(
|
|
311
315
|
f"{obj.__name__} is deprecated and will be removed in future versions. {reason}",
|
|
312
316
|
category=DeprecationWarning,
|
|
313
|
-
stacklevel=2
|
|
317
|
+
stacklevel=2,
|
|
314
318
|
)
|
|
315
319
|
return obj(*args, **kwargs)
|
|
320
|
+
|
|
316
321
|
return wrapper
|
|
317
322
|
|
|
318
323
|
return decorator
|
symai/endpoints/api.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import importlib
|
|
2
2
|
import inspect
|
|
3
3
|
import pickle
|
|
4
|
-
from typing import Any
|
|
4
|
+
from typing import Any, Generic, TypeVar
|
|
5
5
|
|
|
6
6
|
import redis
|
|
7
7
|
from fastapi import APIRouter, FastAPI, HTTPException, Security, status
|
|
@@ -17,10 +17,11 @@ from ..symbol import Expression, Symbol
|
|
|
17
17
|
from ..utils import UserMessage
|
|
18
18
|
|
|
19
19
|
# Configure Redis server connection parameters and executable path
|
|
20
|
-
HOST
|
|
21
|
-
PORT
|
|
20
|
+
HOST = "localhost"
|
|
21
|
+
PORT = 6379
|
|
22
22
|
DEBUG = True
|
|
23
|
-
API_KEY = settings.SYMAI_CONFIG.get(
|
|
23
|
+
API_KEY = settings.SYMAI_CONFIG.get("FASTAPI_API_KEY", None)
|
|
24
|
+
|
|
24
25
|
|
|
25
26
|
def is_redis_running(host: str, port: int) -> bool:
|
|
26
27
|
"""Check if a Redis server is running at the given host and port."""
|
|
@@ -30,26 +31,31 @@ def is_redis_running(host: str, port: int) -> bool:
|
|
|
30
31
|
UserMessage(f"Redis server is running at {host}:{port}")
|
|
31
32
|
return True
|
|
32
33
|
except RedisConnectionError:
|
|
33
|
-
UserMessage(
|
|
34
|
+
UserMessage(
|
|
35
|
+
f"Redis server is not running at {host}:{port} or is not reachable - falling back to in-memory storage"
|
|
36
|
+
)
|
|
34
37
|
return False
|
|
35
38
|
|
|
36
39
|
|
|
37
|
-
|
|
40
|
+
T = TypeVar("T")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class GenericRepository(Generic[T]):
|
|
38
44
|
def __init__(self, redis_client: redis.Redis, id_key: str, use_redis: bool = True):
|
|
39
45
|
self.storage: dict[str, T] = {} # In-memory dictionary to mock Redis
|
|
40
|
-
self.use_redis
|
|
41
|
-
self.id_key
|
|
42
|
-
self.redis_client
|
|
46
|
+
self.use_redis = use_redis
|
|
47
|
+
self.id_key = id_key # Key used for storing the incremental ID counter
|
|
48
|
+
self.redis_client = redis_client
|
|
43
49
|
if self.use_redis:
|
|
44
|
-
self.set
|
|
45
|
-
self.get
|
|
46
|
-
self.delete
|
|
47
|
-
self.uid
|
|
50
|
+
self.set = self._store_redis
|
|
51
|
+
self.get = self._retrieve_redis
|
|
52
|
+
self.delete = self._delete_redis
|
|
53
|
+
self.uid = self._generate_id_redis
|
|
48
54
|
else:
|
|
49
|
-
self.set
|
|
50
|
-
self.get
|
|
51
|
-
self.delete
|
|
52
|
-
self.uid
|
|
55
|
+
self.set = self._store_memory
|
|
56
|
+
self.get = self._retrieve_memory
|
|
57
|
+
self.delete = self._delete_memory
|
|
58
|
+
self.uid = self._generate_id_memory
|
|
53
59
|
|
|
54
60
|
def _deserialize_object(self, serialized_item: bytes) -> T:
|
|
55
61
|
"""Deserialize the byte object back into a Python object of type T."""
|
|
@@ -72,7 +78,7 @@ class GenericRepository[T]:
|
|
|
72
78
|
|
|
73
79
|
def _generate_id_memory(self) -> str:
|
|
74
80
|
id_ = len(self.storage) + 1
|
|
75
|
-
return f
|
|
81
|
+
return f"{self.id_key}:{id_}"
|
|
76
82
|
|
|
77
83
|
def _store_redis(self, item_id: str, item: T) -> None:
|
|
78
84
|
item = self._serialize_object(item_id, item)
|
|
@@ -89,38 +95,44 @@ class GenericRepository[T]:
|
|
|
89
95
|
|
|
90
96
|
def _generate_id_redis(self) -> str:
|
|
91
97
|
id_ = self.redis_client.incr(self.id_key)
|
|
92
|
-
return f
|
|
98
|
+
return f"{self.id_key}:{id_}"
|
|
93
99
|
|
|
94
100
|
|
|
95
101
|
# Initialize the Redis client
|
|
96
102
|
redis_client = redis.Redis(
|
|
97
|
-
host=HOST,
|
|
98
|
-
port=PORT,
|
|
99
|
-
db=0
|
|
103
|
+
host=HOST, # Or use the host where your Redis server is running
|
|
104
|
+
port=PORT, # The default Redis port number
|
|
105
|
+
db=0, # The default database index
|
|
100
106
|
)
|
|
101
107
|
|
|
102
108
|
# Initialize the FastAPI app and API router
|
|
103
|
-
app
|
|
104
|
-
api_key_header
|
|
105
|
-
router
|
|
106
|
-
use_redis
|
|
109
|
+
app = FastAPI(title="SymbolicAI API", version="1.0")
|
|
110
|
+
api_key_header = APIKeyHeader(name="X-API-Key")
|
|
111
|
+
router = APIRouter()
|
|
112
|
+
use_redis = is_redis_running(HOST, PORT)
|
|
107
113
|
|
|
108
114
|
# Instantiate the generic repositories with the counter keys
|
|
109
|
-
symbol_repository
|
|
110
|
-
expression_repository
|
|
115
|
+
symbol_repository = GenericRepository[Symbol](redis_client, "sym_id", use_redis=use_redis)
|
|
116
|
+
expression_repository = GenericRepository[Expression](redis_client, "expr_id", use_redis=use_redis)
|
|
111
117
|
# Register all types which subclass Expression and offer a get() method with default=None
|
|
112
118
|
component_class_types = {
|
|
113
|
-
name: cls
|
|
119
|
+
name: cls
|
|
120
|
+
for name, cls in inspect.getmembers(
|
|
121
|
+
importlib.import_module("symai.components"), inspect.isclass
|
|
122
|
+
)
|
|
123
|
+
if issubclass(cls, Expression)
|
|
114
124
|
}
|
|
115
|
-
component_instance_repository = GenericRepository[Symbol | Expression](
|
|
125
|
+
component_instance_repository = GenericRepository[Symbol | Expression](
|
|
126
|
+
redis_client, "comp_id", use_redis=use_redis
|
|
127
|
+
)
|
|
116
128
|
|
|
117
129
|
|
|
118
130
|
def _load_extended_types():
|
|
119
131
|
"""Load extended classes lazily to avoid premature engine initialization."""
|
|
120
|
-
extended_module = importlib.import_module(
|
|
121
|
-
personas_module = importlib.import_module(
|
|
122
|
-
sales_module = importlib.import_module(
|
|
123
|
-
student_module = importlib.import_module(
|
|
132
|
+
extended_module = importlib.import_module("symai.extended")
|
|
133
|
+
personas_module = importlib.import_module("symai.extended.personas")
|
|
134
|
+
sales_module = importlib.import_module("symai.extended.personas.sales")
|
|
135
|
+
student_module = importlib.import_module("symai.extended.personas.student")
|
|
124
136
|
return [
|
|
125
137
|
extended_module.Conversation,
|
|
126
138
|
personas_module.Persona,
|
|
@@ -129,14 +141,21 @@ def _load_extended_types():
|
|
|
129
141
|
sales_module.ErikJames,
|
|
130
142
|
]
|
|
131
143
|
|
|
132
|
-
|
|
144
|
+
|
|
145
|
+
app.add_middleware(
|
|
146
|
+
CORSMiddleware,
|
|
147
|
+
allow_origins=["*"],
|
|
148
|
+
allow_credentials=True,
|
|
149
|
+
allow_methods=["*"],
|
|
150
|
+
allow_headers=["*"],
|
|
151
|
+
)
|
|
133
152
|
|
|
134
153
|
### Symbols Endpoints ###
|
|
135
154
|
|
|
136
155
|
|
|
137
156
|
class CreateSymbolRequest(BaseModel):
|
|
138
|
-
value: Any | None
|
|
139
|
-
static_context: str | None =
|
|
157
|
+
value: Any | None = None
|
|
158
|
+
static_context: str | None = ""
|
|
140
159
|
|
|
141
160
|
|
|
142
161
|
class UpdateSymbolRequest(BaseModel):
|
|
@@ -148,6 +167,7 @@ class SymbolMethodRequest(BaseModel):
|
|
|
148
167
|
args: list[Any] = []
|
|
149
168
|
kwargs: dict[str, Any] = {}
|
|
150
169
|
|
|
170
|
+
|
|
151
171
|
def get_api_key(api_key_header: str = Security(api_key_header) if API_KEY else None) -> str:
|
|
152
172
|
if API_KEY is None:
|
|
153
173
|
return True
|
|
@@ -158,6 +178,7 @@ def get_api_key(api_key_header: str = Security(api_key_header) if API_KEY else N
|
|
|
158
178
|
detail="Invalid or missing API Key",
|
|
159
179
|
)
|
|
160
180
|
|
|
181
|
+
|
|
161
182
|
@app.post("/symbol/")
|
|
162
183
|
def create_symbol(symbol_request: CreateSymbolRequest, _api_key: str = Security(get_api_key)):
|
|
163
184
|
symbol = Symbol(symbol_request.value, static_context=symbol_request.static_context)
|
|
@@ -175,7 +196,9 @@ def get_symbol(symbol_id: str, _api_key: str = Security(get_api_key)):
|
|
|
175
196
|
|
|
176
197
|
|
|
177
198
|
@app.patch("/symbol/{symbol_id}/")
|
|
178
|
-
def update_symbol(
|
|
199
|
+
def update_symbol(
|
|
200
|
+
symbol_id: str, update_request: UpdateSymbolRequest, _api_key: str = Security(get_api_key)
|
|
201
|
+
):
|
|
179
202
|
symbol = symbol_repository.get(symbol_id)
|
|
180
203
|
if symbol is None:
|
|
181
204
|
raise HTTPException(status_code=404, detail="Symbol not found")
|
|
@@ -195,13 +218,20 @@ def delete_symbol(symbol_id: str, _api_key: str = Security(get_api_key)):
|
|
|
195
218
|
|
|
196
219
|
@app.post("/symbol/{symbol_id}/{method_name}/")
|
|
197
220
|
@core_ext.error_logging(debug=DEBUG)
|
|
198
|
-
def operate_on_symbol(
|
|
221
|
+
def operate_on_symbol(
|
|
222
|
+
symbol_id: str,
|
|
223
|
+
method_name: str,
|
|
224
|
+
method_request: SymbolMethodRequest,
|
|
225
|
+
_api_key: str = Security(get_api_key),
|
|
226
|
+
):
|
|
199
227
|
symbol = symbol_repository.get(symbol_id)
|
|
200
228
|
if symbol is None:
|
|
201
229
|
raise HTTPException(status_code=404, detail="Symbol not found")
|
|
202
230
|
method = getattr(symbol, method_name, None)
|
|
203
231
|
if method is None or not callable(method):
|
|
204
|
-
raise HTTPException(
|
|
232
|
+
raise HTTPException(
|
|
233
|
+
status_code=404, detail=f"Method {method_name} not found or is not callable"
|
|
234
|
+
)
|
|
205
235
|
result = method(*method_request.args, **method_request.kwargs)
|
|
206
236
|
return {"result": result.json() if isinstance(result, Symbol) else result}
|
|
207
237
|
|
|
@@ -218,7 +248,9 @@ class UpdateExpressionRequest(BaseModel):
|
|
|
218
248
|
|
|
219
249
|
|
|
220
250
|
@app.post("/expression/")
|
|
221
|
-
def create_expression(
|
|
251
|
+
def create_expression(
|
|
252
|
+
expression_request: CreateExpressionRequest, _api_key: str = Security(get_api_key)
|
|
253
|
+
):
|
|
222
254
|
expression = Expression(expression_request.value)
|
|
223
255
|
expression_id = expression_repository.uid()
|
|
224
256
|
expression_repository.set(expression_id, expression)
|
|
@@ -235,7 +267,9 @@ def get_expression(expression_id: str, _api_key: str = Security(get_api_key)):
|
|
|
235
267
|
|
|
236
268
|
@app.post("/expression/{expression_id}/call/")
|
|
237
269
|
@core_ext.error_logging(debug=DEBUG)
|
|
238
|
-
def call_expression(
|
|
270
|
+
def call_expression(
|
|
271
|
+
expression_id: str, method_request: SymbolMethodRequest, _api_key: str = Security(get_api_key)
|
|
272
|
+
):
|
|
239
273
|
# Retrieve the expression instance by ID
|
|
240
274
|
expression = expression_repository.get(expression_id)
|
|
241
275
|
if expression is None:
|
|
@@ -246,19 +280,30 @@ def call_expression(expression_id: str, method_request: SymbolMethodRequest, _ap
|
|
|
246
280
|
|
|
247
281
|
@app.post("/expression/{expression_id}/{method_name}/")
|
|
248
282
|
@core_ext.error_logging(debug=DEBUG)
|
|
249
|
-
def operate_on_expression(
|
|
283
|
+
def operate_on_expression(
|
|
284
|
+
expression_id: str,
|
|
285
|
+
method_name: str,
|
|
286
|
+
method_request: SymbolMethodRequest,
|
|
287
|
+
_api_key: str = Security(get_api_key),
|
|
288
|
+
):
|
|
250
289
|
expression = expression_repository.get(expression_id)
|
|
251
290
|
if expression is None:
|
|
252
291
|
raise HTTPException(status_code=404, detail="Expression not found")
|
|
253
292
|
method = getattr(expression, method_name, None)
|
|
254
293
|
if method is None or not callable(method):
|
|
255
|
-
raise HTTPException(
|
|
294
|
+
raise HTTPException(
|
|
295
|
+
status_code=404, detail=f"Method {method_name} not found or is not callable"
|
|
296
|
+
)
|
|
256
297
|
result = method(*method_request.args, **method_request.kwargs)
|
|
257
298
|
return {"result": result.json() if isinstance(result, Symbol) else result}
|
|
258
299
|
|
|
259
300
|
|
|
260
301
|
@app.patch("/expression/{expression_id}/")
|
|
261
|
-
def update_expression(
|
|
302
|
+
def update_expression(
|
|
303
|
+
expression_id: str,
|
|
304
|
+
update_request: UpdateExpressionRequest,
|
|
305
|
+
_api_key: str = Security(get_api_key),
|
|
306
|
+
):
|
|
262
307
|
expression = expression_repository.get(expression_id)
|
|
263
308
|
if expression is None:
|
|
264
309
|
raise HTTPException(status_code=404, detail="Expression not found")
|
|
@@ -288,17 +333,17 @@ class AddComponentRequest(BaseModel):
|
|
|
288
333
|
# Endpoint to instantiate a generic component class and get the ID
|
|
289
334
|
class CreateComponentGenericRequest(BaseModel):
|
|
290
335
|
class_name: str
|
|
291
|
-
init_args: list[Any]
|
|
336
|
+
init_args: list[Any] = []
|
|
292
337
|
init_kwargs: dict[str, Any] = {}
|
|
293
338
|
|
|
294
339
|
|
|
295
340
|
# Modify the existing GenericRequest
|
|
296
341
|
class GenericRequest(BaseModel):
|
|
297
342
|
class_name: str
|
|
298
|
-
init_args: list[Any]
|
|
299
|
-
init_kwargs: dict[str, Any]
|
|
300
|
-
forward_args: list[
|
|
301
|
-
forward_kwargs: dict[str, Any]
|
|
343
|
+
init_args: list[Any] = {}
|
|
344
|
+
init_kwargs: dict[str, Any] = {}
|
|
345
|
+
forward_args: list[Any] = {}
|
|
346
|
+
forward_kwargs: dict[str, Any] = {}
|
|
302
347
|
|
|
303
348
|
|
|
304
349
|
class UpdateComponentRequest(BaseModel):
|
|
@@ -315,7 +360,9 @@ def create_component(request: CreateComponentGenericRequest, _api_key: str = Sec
|
|
|
315
360
|
instance = cls(*request.init_args, **request.init_kwargs)
|
|
316
361
|
# Store the instance with a generated ID and return the ID
|
|
317
362
|
instance_id = component_instance_repository.uid()
|
|
318
|
-
component_instance_repository.set(
|
|
363
|
+
component_instance_repository.set(
|
|
364
|
+
instance_id, instance
|
|
365
|
+
) # Assuming component_instance_repository exists
|
|
319
366
|
return {"id": instance_id}
|
|
320
367
|
|
|
321
368
|
|
|
@@ -333,7 +380,7 @@ def get_component(instance_id: str, _api_key: str = Security(get_api_key)):
|
|
|
333
380
|
@core_ext.error_logging(debug=DEBUG)
|
|
334
381
|
def generic_forward(request: GenericRequest, _api_key: str = Security(get_api_key)):
|
|
335
382
|
# Dynamically import the class from components module based on request.class_name
|
|
336
|
-
components_module = importlib.import_module(
|
|
383
|
+
components_module = importlib.import_module(".components", package="symai")
|
|
337
384
|
cls = getattr(components_module, request.class_name)
|
|
338
385
|
# Check if cls is subclass of Expression and instantiate
|
|
339
386
|
if not issubclass(cls, components_module.Expression):
|
|
@@ -351,7 +398,9 @@ def generic_forward(request: GenericRequest, _api_key: str = Security(get_api_ke
|
|
|
351
398
|
|
|
352
399
|
|
|
353
400
|
@app.patch("/components/{instance_id}/")
|
|
354
|
-
def update_component(
|
|
401
|
+
def update_component(
|
|
402
|
+
instance_id: str, update_request: UpdateComponentRequest, _api_key: str = Security(get_api_key)
|
|
403
|
+
):
|
|
355
404
|
instance = component_instance_repository.get(instance_id)
|
|
356
405
|
if instance is None:
|
|
357
406
|
raise HTTPException(status_code=404, detail="Component instance not found")
|
|
@@ -374,14 +423,16 @@ def delete_component(instance_id: str, _api_key: str = Security(get_api_key)):
|
|
|
374
423
|
# extended types loaded lazily to avoid early engine configuration
|
|
375
424
|
extended_types = _load_extended_types()
|
|
376
425
|
# Register only the extended types from the extended_types Union
|
|
377
|
-
extended_class_types
|
|
378
|
-
extended_instance_repository = GenericRepository[extended_types](
|
|
426
|
+
extended_class_types = {c.__name__: c for c in extended_types}
|
|
427
|
+
extended_instance_repository = GenericRepository[extended_types](
|
|
428
|
+
redis_client, "ext_id", use_redis=use_redis
|
|
429
|
+
)
|
|
379
430
|
|
|
380
431
|
|
|
381
432
|
# Model definitions for extended classes
|
|
382
433
|
class CreateExtendedRequest(BaseModel):
|
|
383
434
|
class_name: str
|
|
384
|
-
init_args: list[Any]
|
|
435
|
+
init_args: list[Any] = []
|
|
385
436
|
init_kwargs: dict[str, Any] = {}
|
|
386
437
|
|
|
387
438
|
|
|
@@ -447,7 +498,9 @@ def get_extended(instance_id: str, _api_key: str = Security(get_api_key)):
|
|
|
447
498
|
|
|
448
499
|
|
|
449
500
|
@app.patch("/extended/{instance_id}/")
|
|
450
|
-
def update_extended(
|
|
501
|
+
def update_extended(
|
|
502
|
+
instance_id: str, update_request: UpdateExtendedRequest, _api_key: str = Security(get_api_key)
|
|
503
|
+
):
|
|
451
504
|
# Retrieve the instance by its ID
|
|
452
505
|
extended_instance = extended_instance_repository.get(instance_id)
|
|
453
506
|
if extended_instance is None:
|
|
@@ -464,7 +517,9 @@ def delete_extended(instance_id: str, _api_key: str = Security(get_api_key)):
|
|
|
464
517
|
# Attempt to delete the instance by its ID
|
|
465
518
|
success = extended_instance_repository.delete(instance_id)
|
|
466
519
|
if not success:
|
|
467
|
-
raise HTTPException(
|
|
520
|
+
raise HTTPException(
|
|
521
|
+
status_code=404, detail="Extended instance not found or already deleted"
|
|
522
|
+
)
|
|
468
523
|
return {"message": "Extended instance deleted successfully"}
|
|
469
524
|
|
|
470
525
|
|