symbolicai 0.21.0__py3-none-any.whl → 1.1.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 +269 -173
- symai/backend/base.py +123 -110
- symai/backend/engines/drawing/engine_bfl.py +45 -44
- symai/backend/engines/drawing/engine_gpt_image.py +112 -97
- symai/backend/engines/embedding/engine_llama_cpp.py +63 -52
- symai/backend/engines/embedding/engine_openai.py +25 -21
- symai/backend/engines/execute/engine_python.py +19 -18
- symai/backend/engines/files/engine_io.py +104 -95
- symai/backend/engines/imagecaptioning/engine_blip2.py +28 -24
- symai/backend/engines/imagecaptioning/engine_llavacpp_client.py +102 -79
- symai/backend/engines/index/engine_pinecone.py +124 -97
- symai/backend/engines/index/engine_qdrant.py +1011 -0
- symai/backend/engines/index/engine_vectordb.py +84 -56
- symai/backend/engines/lean/engine_lean4.py +96 -52
- symai/backend/engines/neurosymbolic/__init__.py +41 -13
- symai/backend/engines/neurosymbolic/engine_anthropic_claudeX_chat.py +330 -248
- symai/backend/engines/neurosymbolic/engine_anthropic_claudeX_reasoning.py +329 -264
- symai/backend/engines/neurosymbolic/engine_cerebras.py +328 -0
- symai/backend/engines/neurosymbolic/engine_deepseekX_reasoning.py +118 -88
- symai/backend/engines/neurosymbolic/engine_google_geminiX_reasoning.py +344 -299
- symai/backend/engines/neurosymbolic/engine_groq.py +173 -115
- symai/backend/engines/neurosymbolic/engine_huggingface.py +114 -84
- symai/backend/engines/neurosymbolic/engine_llama_cpp.py +144 -118
- symai/backend/engines/neurosymbolic/engine_openai_gptX_chat.py +415 -307
- symai/backend/engines/neurosymbolic/engine_openai_gptX_reasoning.py +394 -231
- symai/backend/engines/ocr/engine_apilayer.py +23 -27
- symai/backend/engines/output/engine_stdout.py +10 -13
- symai/backend/engines/{webscraping → scrape}/engine_requests.py +101 -54
- symai/backend/engines/search/engine_openai.py +100 -88
- symai/backend/engines/search/engine_parallel.py +665 -0
- symai/backend/engines/search/engine_perplexity.py +44 -45
- symai/backend/engines/search/engine_serpapi.py +37 -34
- symai/backend/engines/speech_to_text/engine_local_whisper.py +54 -51
- symai/backend/engines/symbolic/engine_wolframalpha.py +15 -9
- symai/backend/engines/text_to_speech/engine_openai.py +20 -26
- symai/backend/engines/text_vision/engine_clip.py +39 -37
- symai/backend/engines/userinput/engine_console.py +5 -6
- symai/backend/mixin/__init__.py +13 -0
- symai/backend/mixin/anthropic.py +48 -38
- symai/backend/mixin/deepseek.py +6 -5
- symai/backend/mixin/google.py +7 -4
- symai/backend/mixin/groq.py +2 -4
- symai/backend/mixin/openai.py +140 -110
- symai/backend/settings.py +87 -20
- symai/chat.py +216 -123
- symai/collect/__init__.py +7 -1
- symai/collect/dynamic.py +80 -70
- symai/collect/pipeline.py +67 -51
- symai/collect/stats.py +161 -109
- symai/components.py +707 -360
- symai/constraints.py +24 -12
- symai/core.py +1857 -1233
- symai/core_ext.py +83 -80
- symai/endpoints/api.py +166 -104
- symai/extended/.DS_Store +0 -0
- symai/extended/__init__.py +46 -12
- symai/extended/api_builder.py +29 -21
- symai/extended/arxiv_pdf_parser.py +23 -14
- symai/extended/bibtex_parser.py +9 -6
- symai/extended/conversation.py +156 -126
- symai/extended/document.py +50 -30
- symai/extended/file_merger.py +57 -14
- symai/extended/graph.py +51 -32
- symai/extended/html_style_template.py +18 -14
- symai/extended/interfaces/blip_2.py +2 -3
- symai/extended/interfaces/clip.py +4 -3
- symai/extended/interfaces/console.py +9 -1
- symai/extended/interfaces/dall_e.py +4 -2
- symai/extended/interfaces/file.py +2 -0
- symai/extended/interfaces/flux.py +4 -2
- symai/extended/interfaces/gpt_image.py +16 -7
- symai/extended/interfaces/input.py +2 -1
- symai/extended/interfaces/llava.py +1 -2
- symai/extended/interfaces/{naive_webscraping.py → naive_scrape.py} +4 -3
- symai/extended/interfaces/naive_vectordb.py +9 -10
- symai/extended/interfaces/ocr.py +5 -3
- 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 +12 -9
- symai/extended/interfaces/python.py +2 -0
- symai/extended/interfaces/serpapi.py +3 -1
- symai/extended/interfaces/terminal.py +2 -4
- symai/extended/interfaces/tts.py +3 -2
- symai/extended/interfaces/whisper.py +3 -2
- symai/extended/interfaces/wolframalpha.py +2 -1
- symai/extended/metrics/__init__.py +11 -1
- symai/extended/metrics/similarity.py +14 -13
- symai/extended/os_command.py +39 -29
- symai/extended/packages/__init__.py +29 -3
- symai/extended/packages/symdev.py +51 -43
- symai/extended/packages/sympkg.py +41 -35
- symai/extended/packages/symrun.py +63 -50
- symai/extended/repo_cloner.py +14 -12
- symai/extended/seo_query_optimizer.py +15 -13
- symai/extended/solver.py +116 -91
- symai/extended/summarizer.py +12 -10
- symai/extended/taypan_interpreter.py +17 -18
- symai/extended/vectordb.py +122 -92
- symai/formatter/__init__.py +9 -1
- symai/formatter/formatter.py +51 -47
- symai/formatter/regex.py +70 -69
- symai/functional.py +325 -176
- symai/imports.py +190 -147
- symai/interfaces.py +57 -28
- symai/memory.py +45 -35
- symai/menu/screen.py +28 -19
- symai/misc/console.py +66 -56
- symai/misc/loader.py +8 -5
- symai/models/__init__.py +17 -1
- symai/models/base.py +395 -236
- symai/models/errors.py +1 -2
- symai/ops/__init__.py +32 -22
- symai/ops/measures.py +24 -25
- symai/ops/primitives.py +1149 -731
- symai/post_processors.py +58 -50
- symai/pre_processors.py +86 -82
- symai/processor.py +21 -13
- symai/prompts.py +764 -685
- symai/server/huggingface_server.py +135 -49
- symai/server/llama_cpp_server.py +21 -11
- symai/server/qdrant_server.py +206 -0
- symai/shell.py +100 -42
- symai/shellsv.py +700 -492
- symai/strategy.py +630 -346
- symai/symbol.py +368 -322
- symai/utils.py +100 -78
- {symbolicai-0.21.0.dist-info → symbolicai-1.1.0.dist-info}/METADATA +22 -10
- symbolicai-1.1.0.dist-info/RECORD +168 -0
- symbolicai-0.21.0.dist-info/RECORD +0 -162
- {symbolicai-0.21.0.dist-info → symbolicai-1.1.0.dist-info}/WHEEL +0 -0
- {symbolicai-0.21.0.dist-info → symbolicai-1.1.0.dist-info}/entry_points.txt +0 -0
- {symbolicai-0.21.0.dist-info → symbolicai-1.1.0.dist-info}/licenses/LICENSE +0 -0
- {symbolicai-0.21.0.dist-info → symbolicai-1.1.0.dist-info}/top_level.txt +0 -0
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import base64
|
|
2
|
+
import contextlib
|
|
2
3
|
import logging
|
|
3
4
|
import tempfile
|
|
4
5
|
from pathlib import Path
|
|
5
|
-
from typing import Optional
|
|
6
6
|
|
|
7
7
|
import openai
|
|
8
8
|
import requests
|
|
9
9
|
|
|
10
10
|
from ....symbol import Result
|
|
11
|
+
from ....utils import UserMessage
|
|
11
12
|
from ...base import Engine
|
|
12
13
|
from ...settings import SYMAI_CONFIG
|
|
13
14
|
|
|
@@ -25,21 +26,23 @@ class GPTImageResult(Result):
|
|
|
25
26
|
Exposes .value as the raw response and ._value as the
|
|
26
27
|
first URL or decoded b64 image string.
|
|
27
28
|
"""
|
|
29
|
+
|
|
28
30
|
def __init__(self, value, **kwargs):
|
|
29
31
|
super().__init__(value, **kwargs)
|
|
30
32
|
imgs = []
|
|
31
33
|
for item in value.data:
|
|
32
34
|
has_url = hasattr(item, "url")
|
|
33
35
|
has_b64 = hasattr(item, "b64_json")
|
|
34
|
-
|
|
36
|
+
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp_file:
|
|
37
|
+
path = tmp_file.name
|
|
35
38
|
if has_url and item.url is not None:
|
|
36
39
|
request = requests.get(item.url, allow_redirects=True)
|
|
37
40
|
request.raise_for_status()
|
|
38
|
-
with open(
|
|
41
|
+
with Path(path).open("wb") as f:
|
|
39
42
|
f.write(request.content)
|
|
40
43
|
elif has_b64 and item.b64_json is not None:
|
|
41
44
|
raw = base64.b64decode(item.b64_json)
|
|
42
|
-
with open(
|
|
45
|
+
with Path(path).open("wb") as f:
|
|
43
46
|
f.write(raw)
|
|
44
47
|
imgs.append(path)
|
|
45
48
|
self._value = imgs
|
|
@@ -47,28 +50,21 @@ class GPTImageResult(Result):
|
|
|
47
50
|
|
|
48
51
|
class GPTImageEngine(Engine):
|
|
49
52
|
"""
|
|
50
|
-
A drop
|
|
53
|
+
A drop-in engine for OpenAI's unified Images API,
|
|
51
54
|
supporting gpt-image-1, dall-e-2, dall-e-3,
|
|
52
55
|
with all the extra parameters (background, moderation, etc).
|
|
53
56
|
"""
|
|
57
|
+
|
|
54
58
|
def __init__(
|
|
55
59
|
self,
|
|
56
|
-
api_key:
|
|
57
|
-
model:
|
|
60
|
+
api_key: str | None = None,
|
|
61
|
+
model: str | None = None,
|
|
58
62
|
):
|
|
59
63
|
super().__init__()
|
|
60
64
|
self.config = SYMAI_CONFIG
|
|
61
65
|
# pick up a separate config slot if you like, or fall back
|
|
62
|
-
openai.api_key = (
|
|
63
|
-
|
|
64
|
-
if api_key is None
|
|
65
|
-
else api_key
|
|
66
|
-
)
|
|
67
|
-
self.model = (
|
|
68
|
-
self.config.get("DRAWING_ENGINE_MODEL")
|
|
69
|
-
if model is None
|
|
70
|
-
else model
|
|
71
|
-
)
|
|
66
|
+
openai.api_key = self.config.get("DRAWING_ENGINE_API_KEY") if api_key is None else api_key
|
|
67
|
+
self.model = self.config.get("DRAWING_ENGINE_MODEL") if model is None else model
|
|
72
68
|
self.name = self.__class__.__name__
|
|
73
69
|
# quiet OpenAI's internal logger
|
|
74
70
|
log = logging.getLogger("openai")
|
|
@@ -83,7 +79,7 @@ class GPTImageEngine(Engine):
|
|
|
83
79
|
|
|
84
80
|
def command(self, *args, **kwargs):
|
|
85
81
|
"""
|
|
86
|
-
Allow hot
|
|
82
|
+
Allow hot-swapping API key or model at runtime.
|
|
87
83
|
"""
|
|
88
84
|
super().command(*args, **kwargs)
|
|
89
85
|
if "DRAWING_ENGINE_API_KEY" in kwargs:
|
|
@@ -105,98 +101,117 @@ class GPTImageEngine(Engine):
|
|
|
105
101
|
operation = kwargs.get("operation")
|
|
106
102
|
|
|
107
103
|
if operation is None:
|
|
108
|
-
|
|
104
|
+
UserMessage("Operation not specified!", raise_with=ValueError)
|
|
109
105
|
|
|
110
106
|
n = kwargs.get("n", 1)
|
|
111
107
|
|
|
112
|
-
|
|
113
|
-
if isinstance(kwargs["size"], int):
|
|
114
|
-
s = kwargs["size"]
|
|
115
|
-
kwargs["size"] = f"{s}x{s}"
|
|
108
|
+
self._normalize_size(kwargs)
|
|
116
109
|
|
|
117
110
|
except_remedy = kwargs.get("except_remedy", None)
|
|
118
111
|
|
|
119
112
|
callback = None
|
|
120
113
|
try:
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
if model == "dall-e-3":
|
|
130
|
-
create_kwargs["response_format"] = kwargs.get("response_format", "url")
|
|
131
|
-
create_kwargs["quality"] = kwargs.get("quality", "standard")
|
|
132
|
-
create_kwargs["style"] = kwargs.get("style", "vivid")
|
|
133
|
-
|
|
134
|
-
if model.startswith("gpt-image-"):
|
|
135
|
-
create_kwargs["quality"] = kwargs.get("quality", "medium")
|
|
136
|
-
create_kwargs["moderation"] = kwargs.get("moderation", "auto")
|
|
137
|
-
create_kwargs["background"] = kwargs.get("background", "auto")
|
|
138
|
-
create_kwargs["output_format"] = kwargs.get("output_compression", "png")
|
|
139
|
-
if create_kwargs["output_format"] == "jpeg" or create_kwargs["output_format"] == "webp":
|
|
140
|
-
create_kwargs["output_compression"] = kwargs.get("output_compression", "100")
|
|
141
|
-
|
|
142
|
-
callback = openai.images.generate
|
|
143
|
-
res = openai.images.generate(**create_kwargs)
|
|
144
|
-
|
|
145
|
-
elif operation == "variation":
|
|
146
|
-
assert "image_path" in kwargs, "image_path required for variation"
|
|
147
|
-
callback = openai.images.create_variation
|
|
148
|
-
with open(kwargs["image_path"], "rb") as img:
|
|
149
|
-
res = openai.images.create_variation(
|
|
150
|
-
model=model,
|
|
151
|
-
image=img,
|
|
152
|
-
n=n,
|
|
153
|
-
size=kwargs.get("size"),
|
|
154
|
-
response_format=kwargs.get("response_format", "url"),
|
|
155
|
-
)
|
|
156
|
-
|
|
157
|
-
elif operation == "edit":
|
|
158
|
-
assert "image_path" in kwargs, "image_path required for edit"
|
|
159
|
-
# allow either a single path or a list of paths
|
|
160
|
-
img_paths = kwargs["image_path"]
|
|
161
|
-
if not isinstance(img_paths, (list, tuple)):
|
|
162
|
-
img_paths = [img_paths]
|
|
163
|
-
# open all images
|
|
164
|
-
image_files = [open(p, "rb") for p in img_paths]
|
|
165
|
-
# optional mask (only for the first image)
|
|
166
|
-
mask_file = None
|
|
167
|
-
if "mask_path" in kwargs and kwargs["mask_path"] is not None:
|
|
168
|
-
mask_file = open(kwargs["mask_path"], "rb")
|
|
169
|
-
# construct API args
|
|
170
|
-
edit_kwargs = {
|
|
171
|
-
"model": model,
|
|
172
|
-
"image": image_files if len(image_files) > 1 else image_files[0],
|
|
173
|
-
"prompt": prompt,
|
|
174
|
-
"n": n,
|
|
175
|
-
"size": kwargs.get("size"),
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
if model.startswith("gpt-image-"):
|
|
179
|
-
edit_kwargs["quality"] = kwargs.get("quality", "auto")
|
|
180
|
-
|
|
181
|
-
if mask_file:
|
|
182
|
-
edit_kwargs["mask"] = mask_file
|
|
183
|
-
callback = openai.images.edit
|
|
184
|
-
|
|
185
|
-
res = openai.images.edit(**edit_kwargs)
|
|
186
|
-
# clean up file handles
|
|
187
|
-
for f in image_files:
|
|
188
|
-
f.close()
|
|
189
|
-
if mask_file:
|
|
190
|
-
mask_file.close()
|
|
191
|
-
else:
|
|
192
|
-
raise ValueError(f"Unknown image operation: {operation}")
|
|
193
|
-
|
|
114
|
+
callback = self._resolve_callback(operation)
|
|
115
|
+
callback, res = self._dispatch_operation(
|
|
116
|
+
operation=operation,
|
|
117
|
+
prompt=prompt,
|
|
118
|
+
model=model,
|
|
119
|
+
n=n,
|
|
120
|
+
kwargs=kwargs,
|
|
121
|
+
)
|
|
194
122
|
except Exception as e:
|
|
195
123
|
if except_remedy is None:
|
|
196
124
|
raise
|
|
197
125
|
res = except_remedy(self, e, callback, argument)
|
|
198
126
|
|
|
199
|
-
# wrap it up
|
|
200
127
|
metadata = {}
|
|
201
128
|
result = GPTImageResult(res)
|
|
202
129
|
return [result], metadata
|
|
130
|
+
|
|
131
|
+
def _normalize_size(self, kwargs):
|
|
132
|
+
if "size" in kwargs and isinstance(kwargs["size"], int):
|
|
133
|
+
s = kwargs["size"]
|
|
134
|
+
kwargs["size"] = f"{s}x{s}"
|
|
135
|
+
|
|
136
|
+
def _resolve_callback(self, operation):
|
|
137
|
+
if operation == "create":
|
|
138
|
+
return openai.images.generate
|
|
139
|
+
if operation == "variation":
|
|
140
|
+
return openai.images.create_variation
|
|
141
|
+
if operation == "edit":
|
|
142
|
+
return openai.images.edit
|
|
143
|
+
UserMessage(f"Unknown image operation: {operation}", raise_with=ValueError)
|
|
144
|
+
return openai.images.generate
|
|
145
|
+
|
|
146
|
+
def _dispatch_operation(self, operation, prompt, model, n, kwargs):
|
|
147
|
+
if operation == "create":
|
|
148
|
+
return self._execute_create(prompt, model, n, kwargs)
|
|
149
|
+
if operation == "variation":
|
|
150
|
+
return self._execute_variation(model, n, kwargs)
|
|
151
|
+
if operation == "edit":
|
|
152
|
+
return self._execute_edit(prompt, model, n, kwargs)
|
|
153
|
+
return UserMessage(f"Unknown image operation: {operation}", raise_with=ValueError)
|
|
154
|
+
|
|
155
|
+
def _execute_create(self, prompt, model, n, kwargs):
|
|
156
|
+
create_kwargs = {
|
|
157
|
+
"model": model,
|
|
158
|
+
"prompt": prompt,
|
|
159
|
+
"n": n,
|
|
160
|
+
"size": kwargs.get("size"),
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if model == "dall-e-3":
|
|
164
|
+
create_kwargs["response_format"] = kwargs.get("response_format", "url")
|
|
165
|
+
create_kwargs["quality"] = kwargs.get("quality", "standard")
|
|
166
|
+
create_kwargs["style"] = kwargs.get("style", "vivid")
|
|
167
|
+
|
|
168
|
+
if model.startswith("gpt-image-"):
|
|
169
|
+
create_kwargs["quality"] = kwargs.get("quality", "medium")
|
|
170
|
+
create_kwargs["moderation"] = kwargs.get("moderation", "auto")
|
|
171
|
+
create_kwargs["background"] = kwargs.get("background", "auto")
|
|
172
|
+
create_kwargs["output_format"] = kwargs.get("output_compression", "png")
|
|
173
|
+
if create_kwargs["output_format"] == "jpeg" or create_kwargs["output_format"] == "webp":
|
|
174
|
+
create_kwargs["output_compression"] = kwargs.get("output_compression", "100")
|
|
175
|
+
|
|
176
|
+
callback = openai.images.generate
|
|
177
|
+
return callback, callback(**create_kwargs)
|
|
178
|
+
|
|
179
|
+
def _execute_variation(self, model, n, kwargs):
|
|
180
|
+
assert "image_path" in kwargs, "image_path required for variation"
|
|
181
|
+
callback = openai.images.create_variation
|
|
182
|
+
with Path(kwargs["image_path"]).open("rb") as img:
|
|
183
|
+
result = callback(
|
|
184
|
+
model=model,
|
|
185
|
+
image=img,
|
|
186
|
+
n=n,
|
|
187
|
+
size=kwargs.get("size"),
|
|
188
|
+
response_format=kwargs.get("response_format", "url"),
|
|
189
|
+
)
|
|
190
|
+
return callback, result
|
|
191
|
+
|
|
192
|
+
def _execute_edit(self, prompt, model, n, kwargs):
|
|
193
|
+
assert "image_path" in kwargs, "image_path required for edit"
|
|
194
|
+
img_paths = kwargs["image_path"]
|
|
195
|
+
if not isinstance(img_paths, (list, tuple)):
|
|
196
|
+
img_paths = [img_paths]
|
|
197
|
+
with contextlib.ExitStack() as stack:
|
|
198
|
+
image_files = [stack.enter_context(Path(p).open("rb")) for p in img_paths]
|
|
199
|
+
mask_file = None
|
|
200
|
+
if "mask_path" in kwargs and kwargs["mask_path"] is not None:
|
|
201
|
+
mask_file = stack.enter_context(Path(kwargs["mask_path"]).open("rb"))
|
|
202
|
+
edit_kwargs = {
|
|
203
|
+
"model": model,
|
|
204
|
+
"image": image_files if len(image_files) > 1 else image_files[0],
|
|
205
|
+
"prompt": prompt,
|
|
206
|
+
"n": n,
|
|
207
|
+
"size": kwargs.get("size"),
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if model.startswith("gpt-image-"):
|
|
211
|
+
edit_kwargs["quality"] = kwargs.get("quality", "auto")
|
|
212
|
+
|
|
213
|
+
if mask_file:
|
|
214
|
+
edit_kwargs["mask"] = mask_file
|
|
215
|
+
callback = openai.images.edit
|
|
216
|
+
result = callback(**edit_kwargs)
|
|
217
|
+
return callback, result
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import logging
|
|
3
|
-
from
|
|
4
|
-
from typing import Optional
|
|
3
|
+
from typing import Any, ClassVar
|
|
5
4
|
|
|
6
5
|
import aiohttp
|
|
7
6
|
import nest_asyncio
|
|
8
|
-
import numpy as np
|
|
9
7
|
|
|
10
8
|
from ....core_ext import retry
|
|
11
|
-
from ....utils import
|
|
9
|
+
from ....utils import UserMessage
|
|
12
10
|
from ...base import Engine
|
|
13
11
|
from ...settings import SYMAI_CONFIG, SYMSERVER_CONFIG
|
|
14
12
|
|
|
@@ -17,58 +15,66 @@ logging.getLogger("urllib").setLevel(logging.ERROR)
|
|
|
17
15
|
logging.getLogger("httpx").setLevel(logging.ERROR)
|
|
18
16
|
logging.getLogger("httpcore").setLevel(logging.ERROR)
|
|
19
17
|
|
|
18
|
+
|
|
20
19
|
class LlamaCppEmbeddingEngine(Engine):
|
|
21
|
-
_retry_params = {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
20
|
+
_retry_params: ClassVar[dict[str, Any]] = {
|
|
21
|
+
"tries": 5,
|
|
22
|
+
"delay": 2,
|
|
23
|
+
"max_delay": 60,
|
|
24
|
+
"backoff": 2,
|
|
25
|
+
"jitter": (1, 5),
|
|
26
|
+
"graceful": True,
|
|
28
27
|
}
|
|
29
|
-
_timeout_params = {
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
_timeout_params: ClassVar[dict[str, Any]] = {
|
|
29
|
+
"read": None,
|
|
30
|
+
"connect": None,
|
|
32
31
|
}
|
|
33
32
|
|
|
34
|
-
def __init__(
|
|
35
|
-
self,
|
|
36
|
-
retry_params: dict = _retry_params,
|
|
37
|
-
timeout_params: dict = _timeout_params
|
|
38
|
-
):
|
|
33
|
+
def __init__(self, retry_params: dict = _retry_params, timeout_params: dict = _timeout_params):
|
|
39
34
|
super().__init__()
|
|
40
35
|
self.config = SYMAI_CONFIG
|
|
41
|
-
if self.id() !=
|
|
36
|
+
if self.id() != "embedding":
|
|
42
37
|
return
|
|
43
|
-
if not SYMSERVER_CONFIG.get(
|
|
44
|
-
|
|
38
|
+
if not SYMSERVER_CONFIG.get("online"):
|
|
39
|
+
UserMessage(
|
|
40
|
+
"You are using the llama.cpp embedding engine, but the server endpoint is not started. Please start the server with `symserver [--args]`.",
|
|
41
|
+
raise_with=ValueError,
|
|
42
|
+
)
|
|
45
43
|
|
|
46
|
-
self.server_endpoint =
|
|
44
|
+
self.server_endpoint = (
|
|
45
|
+
f"http://{SYMSERVER_CONFIG.get('--host')}:{SYMSERVER_CONFIG.get('--port')}"
|
|
46
|
+
)
|
|
47
47
|
self.timeout_params = self._validate_timeout_params(timeout_params)
|
|
48
48
|
self.retry_params = self._validate_retry_params(retry_params)
|
|
49
49
|
self.name = self.__class__.__name__
|
|
50
50
|
|
|
51
51
|
def id(self) -> str:
|
|
52
|
-
if self.config.get(
|
|
53
|
-
|
|
52
|
+
if self.config.get("EMBEDDING_ENGINE_MODEL") and self.config.get(
|
|
53
|
+
"EMBEDDING_ENGINE_MODEL"
|
|
54
|
+
).startswith("llama"):
|
|
55
|
+
return "embedding"
|
|
54
56
|
return super().id() # default to unregistered
|
|
55
57
|
|
|
56
58
|
def command(self, *args, **kwargs):
|
|
57
59
|
super().command(*args, **kwargs)
|
|
58
|
-
if
|
|
59
|
-
self.model = kwargs[
|
|
60
|
+
if "EMBEDDING_ENGINE_MODEL" in kwargs:
|
|
61
|
+
self.model = kwargs["EMBEDDING_ENGINE_MODEL"]
|
|
60
62
|
|
|
61
63
|
def _validate_timeout_params(self, timeout_params):
|
|
62
64
|
if not isinstance(timeout_params, dict):
|
|
63
|
-
|
|
64
|
-
assert all(key in timeout_params for key in [
|
|
65
|
+
UserMessage("timeout_params must be a dictionary", raise_with=ValueError)
|
|
66
|
+
assert all(key in timeout_params for key in ["read", "connect"]), (
|
|
67
|
+
"Available keys: ['read', 'connect']"
|
|
68
|
+
)
|
|
65
69
|
return timeout_params
|
|
66
70
|
|
|
67
71
|
def _validate_retry_params(self, retry_params):
|
|
68
72
|
if not isinstance(retry_params, dict):
|
|
69
|
-
|
|
70
|
-
assert all(
|
|
71
|
-
|
|
73
|
+
UserMessage("retry_params must be a dictionary", raise_with=ValueError)
|
|
74
|
+
assert all(
|
|
75
|
+
key in retry_params
|
|
76
|
+
for key in ["tries", "delay", "max_delay", "backoff", "jitter", "graceful"]
|
|
77
|
+
), "Available keys: ['tries', 'delay', 'max_delay', 'backoff', 'jitter', 'graceful']"
|
|
72
78
|
return retry_params
|
|
73
79
|
|
|
74
80
|
@staticmethod
|
|
@@ -77,7 +83,9 @@ class LlamaCppEmbeddingEngine(Engine):
|
|
|
77
83
|
try:
|
|
78
84
|
current_loop = asyncio.get_event_loop()
|
|
79
85
|
if current_loop.is_closed():
|
|
80
|
-
|
|
86
|
+
msg = "Event loop is closed."
|
|
87
|
+
UserMessage(msg)
|
|
88
|
+
raise RuntimeError(msg)
|
|
81
89
|
return current_loop
|
|
82
90
|
except RuntimeError:
|
|
83
91
|
new_loop = asyncio.new_event_loop()
|
|
@@ -86,20 +94,24 @@ class LlamaCppEmbeddingEngine(Engine):
|
|
|
86
94
|
|
|
87
95
|
async def _arequest(self, text: str, embd_normalize: str) -> dict:
|
|
88
96
|
"""Makes an async HTTP request to the llama.cpp server."""
|
|
97
|
+
|
|
89
98
|
@retry(**self.retry_params)
|
|
90
99
|
async def _make_request():
|
|
91
100
|
timeout = aiohttp.ClientTimeout(
|
|
92
|
-
sock_connect=self.timeout_params[
|
|
93
|
-
sock_read=self.timeout_params['read']
|
|
101
|
+
sock_connect=self.timeout_params["connect"], sock_read=self.timeout_params["read"]
|
|
94
102
|
)
|
|
95
|
-
async with
|
|
96
|
-
|
|
103
|
+
async with (
|
|
104
|
+
aiohttp.ClientSession(timeout=timeout) as session,
|
|
105
|
+
session.post(
|
|
97
106
|
f"{self.server_endpoint}/v1/embeddings",
|
|
98
|
-
json={"content": text, "embd_normalize": embd_normalize}
|
|
99
|
-
) as res
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
107
|
+
json={"content": text, "embd_normalize": embd_normalize},
|
|
108
|
+
) as res,
|
|
109
|
+
):
|
|
110
|
+
if res.status != 200:
|
|
111
|
+
UserMessage(
|
|
112
|
+
f"Request failed with status code: {res.status}", raise_with=ValueError
|
|
113
|
+
)
|
|
114
|
+
return await res.json()
|
|
103
115
|
|
|
104
116
|
return await _make_request()
|
|
105
117
|
|
|
@@ -108,11 +120,11 @@ class LlamaCppEmbeddingEngine(Engine):
|
|
|
108
120
|
kwargs = argument.kwargs
|
|
109
121
|
|
|
110
122
|
inp = prepared_input if isinstance(prepared_input, list) else [prepared_input]
|
|
111
|
-
embd_normalize = kwargs.get(
|
|
123
|
+
embd_normalize = kwargs.get("embd_normalize", -1) # -1 = no normalization
|
|
112
124
|
|
|
113
|
-
new_dim = kwargs.get(
|
|
125
|
+
new_dim = kwargs.get("new_dim")
|
|
114
126
|
if new_dim:
|
|
115
|
-
|
|
127
|
+
UserMessage("new_dim is not yet supported", raise_with=NotImplementedError)
|
|
116
128
|
|
|
117
129
|
nest_asyncio.apply()
|
|
118
130
|
loop = self._get_event_loop()
|
|
@@ -120,16 +132,15 @@ class LlamaCppEmbeddingEngine(Engine):
|
|
|
120
132
|
try:
|
|
121
133
|
res = loop.run_until_complete(self._arequest(inp, embd_normalize))
|
|
122
134
|
except Exception as e:
|
|
123
|
-
|
|
135
|
+
UserMessage(f"Request failed with error: {e!s}", raise_with=ValueError)
|
|
124
136
|
|
|
125
|
-
if res is not None
|
|
126
|
-
|
|
127
|
-
else:
|
|
128
|
-
output = None
|
|
129
|
-
metadata = {'raw_output': res}
|
|
137
|
+
output = [r["embedding"] for r in res] if res is not None else None # B x 1 x D
|
|
138
|
+
metadata = {"raw_output": res}
|
|
130
139
|
|
|
131
140
|
return [output], metadata
|
|
132
141
|
|
|
133
142
|
def prepare(self, argument):
|
|
134
|
-
assert not argument.prop.processed_input,
|
|
143
|
+
assert not argument.prop.processed_input, (
|
|
144
|
+
"LlamaCppEmbeddingEngine does not support processed_input."
|
|
145
|
+
)
|
|
135
146
|
argument.prop.prepared_input = argument.prop.entries
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from typing import Optional
|
|
3
2
|
|
|
4
3
|
import numpy as np
|
|
5
4
|
import openai
|
|
@@ -16,30 +15,32 @@ logging.getLogger("httpcore").setLevel(logging.ERROR)
|
|
|
16
15
|
|
|
17
16
|
|
|
18
17
|
class EmbeddingEngine(Engine, OpenAIMixin):
|
|
19
|
-
def __init__(self, api_key:
|
|
18
|
+
def __init__(self, api_key: str | None = None, model: str | None = None):
|
|
20
19
|
super().__init__()
|
|
21
|
-
logger = logging.getLogger(
|
|
20
|
+
logger = logging.getLogger("openai")
|
|
22
21
|
logger.setLevel(logging.WARNING)
|
|
23
22
|
self.config = SYMAI_CONFIG
|
|
24
|
-
if self.id() !=
|
|
25
|
-
return
|
|
26
|
-
openai.api_key = self.config[
|
|
27
|
-
self.model = self.config[
|
|
23
|
+
if self.id() != "embedding":
|
|
24
|
+
return # do not initialize if not embedding; avoids conflict with llama.cpp check in EngineRepository.register_from_package
|
|
25
|
+
openai.api_key = self.config["EMBEDDING_ENGINE_API_KEY"] if api_key is None else api_key
|
|
26
|
+
self.model = self.config["EMBEDDING_ENGINE_MODEL"] if model is None else model
|
|
28
27
|
self.max_tokens = self.api_max_context_tokens()
|
|
29
28
|
self.embedding_dim = self.api_embedding_dims()
|
|
30
29
|
self.name = self.__class__.__name__
|
|
31
30
|
|
|
32
31
|
def id(self) -> str:
|
|
33
|
-
if self.config.get(
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
if self.config.get("EMBEDDING_ENGINE_API_KEY") and self.config[
|
|
33
|
+
"EMBEDDING_ENGINE_MODEL"
|
|
34
|
+
].startswith("text-embedding"):
|
|
35
|
+
return "embedding"
|
|
36
|
+
return super().id() # default to unregistered
|
|
36
37
|
|
|
37
38
|
def command(self, *args, **kwargs):
|
|
38
39
|
super().command(*args, **kwargs)
|
|
39
|
-
if
|
|
40
|
-
openai.api_key = kwargs[
|
|
41
|
-
if
|
|
42
|
-
self.model = kwargs[
|
|
40
|
+
if "EMBEDDING_ENGINE_API_KEY" in kwargs:
|
|
41
|
+
openai.api_key = kwargs["EMBEDDING_ENGINE_API_KEY"]
|
|
42
|
+
if "EMBEDDING_ENGINE_MODEL" in kwargs:
|
|
43
|
+
self.model = kwargs["EMBEDDING_ENGINE_MODEL"]
|
|
43
44
|
|
|
44
45
|
def forward(self, argument):
|
|
45
46
|
prepared_input = argument.prop.prepared_input
|
|
@@ -47,8 +48,8 @@ class EmbeddingEngine(Engine, OpenAIMixin):
|
|
|
47
48
|
kwargs = argument.kwargs
|
|
48
49
|
|
|
49
50
|
inp = prepared_input if isinstance(prepared_input, list) else [prepared_input]
|
|
50
|
-
except_remedy = kwargs.get(
|
|
51
|
-
new_dim = kwargs.get(
|
|
51
|
+
except_remedy = kwargs.get("except_remedy")
|
|
52
|
+
new_dim = kwargs.get("new_dim")
|
|
52
53
|
|
|
53
54
|
try:
|
|
54
55
|
res = openai.embeddings.create(model=self.model, input=inp)
|
|
@@ -59,7 +60,9 @@ class EmbeddingEngine(Engine, OpenAIMixin):
|
|
|
59
60
|
res = except_remedy(e, inp, callback, self, *args, **kwargs)
|
|
60
61
|
|
|
61
62
|
if new_dim:
|
|
62
|
-
mn = min(
|
|
63
|
+
mn = min(
|
|
64
|
+
new_dim, self.embedding_dim
|
|
65
|
+
) # @NOTE: new_dim should be less than or equal to the original embedding dim
|
|
63
66
|
output = [self._normalize_l2(r.embedding[:mn]) for r in res.data]
|
|
64
67
|
else:
|
|
65
68
|
output = [r.embedding for r in res.data]
|
|
@@ -69,7 +72,9 @@ class EmbeddingEngine(Engine, OpenAIMixin):
|
|
|
69
72
|
return [output], metadata
|
|
70
73
|
|
|
71
74
|
def prepare(self, argument):
|
|
72
|
-
assert not argument.prop.processed_input,
|
|
75
|
+
assert not argument.prop.processed_input, (
|
|
76
|
+
"EmbeddingEngine does not support processed_input."
|
|
77
|
+
)
|
|
73
78
|
argument.prop.prepared_input = argument.prop.entries
|
|
74
79
|
|
|
75
80
|
def _normalize_l2(self, x):
|
|
@@ -79,6 +84,5 @@ class EmbeddingEngine(Engine, OpenAIMixin):
|
|
|
79
84
|
if norm == 0:
|
|
80
85
|
return x.tolist()
|
|
81
86
|
return (x / norm).tolist()
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
return np.where(norm == 0, x, x / norm).tolist()
|
|
87
|
+
norm = np.linalg.norm(x, 2, axis=1, keepdims=True)
|
|
88
|
+
return np.where(norm == 0, x, x / norm).tolist()
|
|
@@ -1,19 +1,20 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import traceback
|
|
3
|
+
|
|
1
4
|
from ....symbol import Result
|
|
2
5
|
from ...base import Engine
|
|
3
6
|
|
|
4
7
|
|
|
5
8
|
def full_stack():
|
|
6
|
-
import sys
|
|
7
|
-
import traceback
|
|
8
9
|
exc = sys.exc_info()[0]
|
|
9
10
|
stack = traceback.extract_stack()[-10:-1] # last one would be full_stack()
|
|
10
|
-
if exc is not None:
|
|
11
|
-
del stack[-1]
|
|
12
|
-
|
|
13
|
-
trc =
|
|
14
|
-
stackstr = trc +
|
|
11
|
+
if exc is not None: # i.e. an exception is present
|
|
12
|
+
del stack[-1] # remove call of full_stack, the printed exception
|
|
13
|
+
# will contain the caught exception caller instead
|
|
14
|
+
trc = "Traceback (most recent call last):\n"
|
|
15
|
+
stackstr = trc + "".join(traceback.format_list(stack))
|
|
15
16
|
if exc is not None:
|
|
16
|
-
|
|
17
|
+
stackstr += " " + traceback.format_exc().lstrip(trc)
|
|
17
18
|
return stackstr
|
|
18
19
|
|
|
19
20
|
|
|
@@ -63,14 +64,14 @@ class PythonEngine(Engine):
|
|
|
63
64
|
self.name = self.__class__.__name__
|
|
64
65
|
|
|
65
66
|
def id(self) -> str:
|
|
66
|
-
return
|
|
67
|
+
return "execute"
|
|
67
68
|
|
|
68
69
|
def forward(self, argument):
|
|
69
70
|
code = argument.prop.prepared_input
|
|
70
71
|
kwargs = argument.kwargs
|
|
71
|
-
globals_ = kwargs
|
|
72
|
-
locals_ = kwargs
|
|
73
|
-
input_handler = kwargs
|
|
72
|
+
globals_ = kwargs.get("globals", {})
|
|
73
|
+
locals_ = kwargs.get("locals", {})
|
|
74
|
+
input_handler = kwargs.get("input_handler")
|
|
74
75
|
if input_handler:
|
|
75
76
|
input_handler((code,))
|
|
76
77
|
|
|
@@ -78,18 +79,18 @@ class PythonEngine(Engine):
|
|
|
78
79
|
err = None
|
|
79
80
|
try:
|
|
80
81
|
exec(str(code), globals_, locals_)
|
|
81
|
-
rsp = {
|
|
82
|
-
if
|
|
83
|
-
rsp[
|
|
84
|
-
if
|
|
85
|
-
rsp[
|
|
82
|
+
rsp = {"globals": globals_, "locals": locals_}
|
|
83
|
+
if "res" in locals_:
|
|
84
|
+
rsp["locals_res"] = locals_["res"]
|
|
85
|
+
if "res" in globals_:
|
|
86
|
+
rsp["globals_res"] = globals_["res"]
|
|
86
87
|
rsp = Result(rsp)
|
|
87
88
|
except Exception as e:
|
|
88
89
|
err = e
|
|
89
90
|
raise e
|
|
90
91
|
|
|
91
92
|
metadata = {}
|
|
92
|
-
metadata[
|
|
93
|
+
metadata["error"] = None if not err else full_stack()
|
|
93
94
|
|
|
94
95
|
return [rsp], metadata
|
|
95
96
|
|