symbolicai 1.0.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 +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 +28 -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/anthropic.py +48 -40
- 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 +556 -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.0.dist-info}/METADATA +19 -9
- symbolicai-1.1.0.dist-info/RECORD +168 -0
- symbolicai-1.0.0.dist-info/RECORD +0 -163
- {symbolicai-1.0.0.dist-info → symbolicai-1.1.0.dist-info}/WHEEL +0 -0
- {symbolicai-1.0.0.dist-info → symbolicai-1.1.0.dist-info}/entry_points.txt +0 -0
- {symbolicai-1.0.0.dist-info → symbolicai-1.1.0.dist-info}/licenses/LICENSE +0 -0
- {symbolicai-1.0.0.dist-info → symbolicai-1.1.0.dist-info}/top_level.txt +0 -0
|
@@ -13,22 +13,23 @@ from ...base import Engine
|
|
|
13
13
|
# Initialize Tika lazily to avoid spawning JVMs prematurely for all workers
|
|
14
14
|
_TIKA_STATE = {"initialized": False}
|
|
15
15
|
|
|
16
|
+
|
|
16
17
|
def _ensure_tika_vm():
|
|
17
18
|
if not _TIKA_STATE["initialized"]:
|
|
18
19
|
with contextlib.suppress(Exception):
|
|
19
20
|
tika.initVM()
|
|
20
|
-
logging.getLogger(
|
|
21
|
+
logging.getLogger("tika").setLevel(logging.CRITICAL)
|
|
21
22
|
_TIKA_STATE["initialized"] = True
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
def _int_or_none(value):
|
|
25
|
-
return int(value) if value !=
|
|
26
|
+
return int(value) if value != "" else None
|
|
26
27
|
|
|
27
28
|
|
|
28
29
|
def _parse_slice_token(token):
|
|
29
|
-
if
|
|
30
|
+
if ":" not in token:
|
|
30
31
|
return int(token)
|
|
31
|
-
parts = token.split(
|
|
32
|
+
parts = token.split(":")
|
|
32
33
|
if len(parts) == 2:
|
|
33
34
|
start, end = parts
|
|
34
35
|
return slice(_int_or_none(start), _int_or_none(end), None)
|
|
@@ -39,13 +40,13 @@ def _parse_slice_token(token):
|
|
|
39
40
|
|
|
40
41
|
|
|
41
42
|
def _parse_slice_spec(file_path):
|
|
42
|
-
if
|
|
43
|
+
if "[" not in file_path or "]" not in file_path:
|
|
43
44
|
return file_path, None
|
|
44
|
-
path_part, remainder = file_path.split(
|
|
45
|
-
slice_section = remainder.split(
|
|
45
|
+
path_part, remainder = file_path.split("[", 1)
|
|
46
|
+
slice_section = remainder.split("]", 1)[0]
|
|
46
47
|
slices = []
|
|
47
|
-
for token in slice_section.split(
|
|
48
|
-
if token ==
|
|
48
|
+
for token in slice_section.split(","):
|
|
49
|
+
if token == "":
|
|
49
50
|
continue
|
|
50
51
|
parsed = _parse_slice_token(token)
|
|
51
52
|
if parsed is not None:
|
|
@@ -75,13 +76,13 @@ class FileEngine(Engine):
|
|
|
75
76
|
self.name = self.__class__.__name__
|
|
76
77
|
|
|
77
78
|
def id(self) -> str:
|
|
78
|
-
return
|
|
79
|
+
return "files"
|
|
79
80
|
|
|
80
81
|
def _read_slice_file(self, file_path, argument):
|
|
81
82
|
# check if file is empty
|
|
82
|
-
with_metadata = argument.kwargs.get(
|
|
83
|
-
file_id
|
|
84
|
-
if file_path is None or file_path.strip() ==
|
|
83
|
+
with_metadata = argument.kwargs.get("with_metadata", False)
|
|
84
|
+
file_id = Path(argument.prop.prepared_input).stem.replace(" ", "_")
|
|
85
|
+
if file_path is None or file_path.strip() == "":
|
|
85
86
|
return None
|
|
86
87
|
|
|
87
88
|
# check if file slice is used
|
|
@@ -90,25 +91,25 @@ class FileEngine(Engine):
|
|
|
90
91
|
path_obj = Path(file_path)
|
|
91
92
|
|
|
92
93
|
# check if file exists
|
|
93
|
-
assert path_obj.exists(), f
|
|
94
|
+
assert path_obj.exists(), f"File does not exist: {file_path}"
|
|
94
95
|
|
|
95
96
|
# verify if file is empty
|
|
96
97
|
if path_obj.stat().st_size <= 0:
|
|
97
|
-
return
|
|
98
|
+
return ""
|
|
98
99
|
|
|
99
100
|
# For common plain-text extensions, avoid Tika overhead
|
|
100
101
|
ext = path_obj.suffix.lower()
|
|
101
|
-
if ext in {
|
|
102
|
+
if ext in {".txt", ".md", ".py", ".json", ".yaml", ".yml", ".csv", ".tsv", ".log"}:
|
|
102
103
|
try:
|
|
103
|
-
with path_obj.open(encoding=
|
|
104
|
+
with path_obj.open(encoding="utf-8", errors="ignore") as f:
|
|
104
105
|
content = f.read()
|
|
105
106
|
if content is None:
|
|
106
107
|
return None
|
|
107
108
|
# Apply slicing by lines, mirroring the Tika branch
|
|
108
|
-
lines = content.split(
|
|
109
|
+
lines = content.split("\n")
|
|
109
110
|
lines = _apply_slices(lines, slices_)
|
|
110
|
-
content =
|
|
111
|
-
content = content.encode(
|
|
111
|
+
content = "\n".join(lines)
|
|
112
|
+
content = content.encode("utf8", "ignore").decode("utf8", "ignore")
|
|
112
113
|
return content if not with_metadata else [TextContainer(file_id, None, content)]
|
|
113
114
|
except Exception:
|
|
114
115
|
# Fallback to Tika if plain read fails
|
|
@@ -116,25 +117,26 @@ class FileEngine(Engine):
|
|
|
116
117
|
|
|
117
118
|
_ensure_tika_vm()
|
|
118
119
|
file_ = unpack.from_file(str(path_obj))
|
|
119
|
-
content = file_[
|
|
120
|
+
content = file_["content"] if "content" in file_ else str(file_)
|
|
120
121
|
|
|
121
122
|
if content is None:
|
|
122
123
|
return None
|
|
123
|
-
content = content.split(
|
|
124
|
+
content = content.split("\n")
|
|
124
125
|
|
|
125
126
|
content = _apply_slices(content, slices_)
|
|
126
|
-
content =
|
|
127
|
-
content = content.encode(
|
|
127
|
+
content = "\n".join(content)
|
|
128
|
+
content = content.encode("utf8", "ignore").decode("utf8", "ignore")
|
|
128
129
|
return content if not with_metadata else [TextContainer(file_id, None, content)]
|
|
129
130
|
|
|
130
|
-
|
|
131
131
|
def reset_eof_of_pdf_return_stream(self, pdf_stream_in: list):
|
|
132
132
|
actual_line = len(pdf_stream_in) # Predefined value in case EOF not found
|
|
133
133
|
# find the line position of the EOF
|
|
134
134
|
for i, x in enumerate(pdf_stream_in[::-1]):
|
|
135
|
-
if b
|
|
136
|
-
actual_line = len(pdf_stream_in)-i
|
|
137
|
-
UserMessage(
|
|
135
|
+
if b"%%EOF" in x:
|
|
136
|
+
actual_line = len(pdf_stream_in) - i
|
|
137
|
+
UserMessage(
|
|
138
|
+
f"EOF found at line position {-i} = actual {actual_line}, with value {x}"
|
|
139
|
+
)
|
|
138
140
|
break
|
|
139
141
|
|
|
140
142
|
# return the list up to that point
|
|
@@ -143,55 +145,55 @@ class FileEngine(Engine):
|
|
|
143
145
|
def fix_pdf(self, file_path: str):
|
|
144
146
|
# opens the file for reading
|
|
145
147
|
path_obj = Path(file_path)
|
|
146
|
-
with path_obj.open(
|
|
147
|
-
txt =
|
|
148
|
+
with path_obj.open("rb") as p:
|
|
149
|
+
txt = p.readlines()
|
|
148
150
|
|
|
149
151
|
# get the new list terminating correctly
|
|
150
152
|
txtx = self.reset_eof_of_pdf_return_stream(txt)
|
|
151
153
|
|
|
152
154
|
# write to new pdf
|
|
153
|
-
new_file_path = Path(f
|
|
154
|
-
with new_file_path.open(
|
|
155
|
+
new_file_path = Path(f"{file_path}_fixed.pdf")
|
|
156
|
+
with new_file_path.open("wb") as f:
|
|
155
157
|
f.writelines(txtx)
|
|
156
158
|
|
|
157
159
|
return pypdf.PdfReader(str(new_file_path))
|
|
158
160
|
|
|
159
161
|
def read_text(self, pdf_reader, page_range, argument):
|
|
160
162
|
txt = []
|
|
161
|
-
n_pages
|
|
162
|
-
with_metadata = argument.kwargs.get(
|
|
163
|
-
file_id
|
|
163
|
+
n_pages = len(pdf_reader.pages)
|
|
164
|
+
with_metadata = argument.kwargs.get("with_metadata", False)
|
|
165
|
+
file_id = Path(argument.prop.prepared_input).stem.replace(" ", "_")
|
|
164
166
|
for i in range(n_pages)[slice(0, n_pages) if page_range is None else page_range]:
|
|
165
167
|
page = pdf_reader.pages[i]
|
|
166
168
|
extracted = page.extract_text()
|
|
167
|
-
extracted = extracted.encode(
|
|
169
|
+
extracted = extracted.encode("utf8", "ignore").decode("utf8", "ignore")
|
|
168
170
|
if with_metadata:
|
|
169
171
|
txt.append(TextContainer(file_id, str(i), extracted))
|
|
170
172
|
else:
|
|
171
173
|
txt.append(extracted)
|
|
172
174
|
|
|
173
|
-
return
|
|
175
|
+
return "\n".join(txt) if not with_metadata else txt
|
|
174
176
|
|
|
175
177
|
def forward(self, argument):
|
|
176
|
-
kwargs
|
|
177
|
-
path
|
|
178
|
+
kwargs = argument.kwargs
|
|
179
|
+
path = argument.prop.prepared_input
|
|
178
180
|
|
|
179
|
-
if
|
|
181
|
+
if ".pdf" in path:
|
|
180
182
|
page_range = None
|
|
181
|
-
if
|
|
182
|
-
page_range = kwargs[
|
|
183
|
+
if "slice" in kwargs:
|
|
184
|
+
page_range = kwargs["slice"]
|
|
183
185
|
if isinstance(page_range, (tuple, list)):
|
|
184
186
|
page_range = slice(*page_range)
|
|
185
187
|
|
|
186
|
-
rsp =
|
|
188
|
+
rsp = ""
|
|
187
189
|
try:
|
|
188
|
-
with Path(path).open(
|
|
190
|
+
with Path(path).open("rb") as f:
|
|
189
191
|
# creating a pdf reader object
|
|
190
192
|
pdf_reader = pypdf.PdfReader(f)
|
|
191
193
|
rsp = self.read_text(pdf_reader, page_range, argument)
|
|
192
194
|
except Exception as e:
|
|
193
|
-
UserMessage(f
|
|
194
|
-
if
|
|
195
|
+
UserMessage(f"Error reading PDF: {e} | {path}")
|
|
196
|
+
if "fix_pdf" not in kwargs or not kwargs["fix_pdf"]:
|
|
195
197
|
raise e
|
|
196
198
|
fixed_pdf = self.fix_pdf(str(path))
|
|
197
199
|
pdf_reader_fixed = pypdf.PdfReader(fixed_pdf)
|
|
@@ -200,11 +202,11 @@ class FileEngine(Engine):
|
|
|
200
202
|
try:
|
|
201
203
|
rsp = self._read_slice_file(path, argument)
|
|
202
204
|
except Exception as e:
|
|
203
|
-
UserMessage(f
|
|
205
|
+
UserMessage(f"Error reading empty file: {e} | {path}")
|
|
204
206
|
raise e
|
|
205
207
|
|
|
206
208
|
if rsp is None:
|
|
207
|
-
UserMessage(f
|
|
209
|
+
UserMessage(f"Error reading file - empty result: {path}", raise_with=Exception)
|
|
208
210
|
|
|
209
211
|
metadata = {}
|
|
210
212
|
|
|
@@ -213,5 +215,5 @@ class FileEngine(Engine):
|
|
|
213
215
|
def prepare(self, argument):
|
|
214
216
|
assert not argument.prop.processed_input, "FileEngine does not support processed_input."
|
|
215
217
|
path = argument.prop.path
|
|
216
|
-
path = path.replace(
|
|
218
|
+
path = path.replace("\\", "")
|
|
217
219
|
argument.prop.prepared_input = path
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
import requests
|
|
3
2
|
import torch
|
|
4
3
|
|
|
@@ -18,7 +17,7 @@ class Blip2Engine(Engine):
|
|
|
18
17
|
def __init__(self):
|
|
19
18
|
super().__init__()
|
|
20
19
|
self.config = SYMAI_CONFIG
|
|
21
|
-
ids = self.config[
|
|
20
|
+
ids = self.config["CAPTION_ENGINE_MODEL"].split("/")
|
|
22
21
|
if len(ids) != 2:
|
|
23
22
|
# return unregistered engine
|
|
24
23
|
return
|
|
@@ -27,42 +26,47 @@ class Blip2Engine(Engine):
|
|
|
27
26
|
self.model = None # lazy loading
|
|
28
27
|
self.vis_processors = None # lazy loading
|
|
29
28
|
self.txt_processors = None # lazy loading
|
|
30
|
-
self.device = torch.device(
|
|
29
|
+
self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
|
31
30
|
self.name = self.__class__.__name__
|
|
32
31
|
|
|
33
32
|
def id(self) -> str:
|
|
34
|
-
if
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
return super().id() # default to unregistered
|
|
33
|
+
if self.config["CAPTION_ENGINE_MODEL"] and "blip2" in self.config["CAPTION_ENGINE_MODEL"]:
|
|
34
|
+
return "imagecaptioning"
|
|
35
|
+
return super().id() # default to unregistered
|
|
38
36
|
|
|
39
37
|
def command(self, *args, **kwargs):
|
|
40
38
|
super().command(*args, **kwargs)
|
|
41
|
-
if
|
|
42
|
-
self.model_id = kwargs[
|
|
39
|
+
if "CAPTION_ENGINE_MODEL" in kwargs:
|
|
40
|
+
self.model_id = kwargs["CAPTION_ENGINE_MODEL"]
|
|
43
41
|
|
|
44
42
|
def forward(self, argument):
|
|
45
43
|
if load_model_and_preprocess is None:
|
|
46
|
-
UserMessage(
|
|
44
|
+
UserMessage(
|
|
45
|
+
"Blip2 is not installed. Please install it with `pip install symbolicai[blip2]`",
|
|
46
|
+
raise_with=ImportError,
|
|
47
|
+
)
|
|
47
48
|
if self.model is None:
|
|
48
|
-
self.model, self.vis_processors, self.txt_processors
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
device = self.device)
|
|
49
|
+
self.model, self.vis_processors, self.txt_processors = load_model_and_preprocess(
|
|
50
|
+
name=self.name_id, model_type=self.model_id, is_eval=True, device=self.device
|
|
51
|
+
)
|
|
52
52
|
|
|
53
53
|
image, prompt = argument.prop.prepared_input
|
|
54
|
-
kwargs
|
|
55
|
-
except_remedy = kwargs.get(
|
|
54
|
+
kwargs = argument.kwargs
|
|
55
|
+
except_remedy = kwargs.get("except_remedy")
|
|
56
56
|
|
|
57
|
-
if
|
|
58
|
-
image = Image.open(requests.get(image, stream=True).raw).convert(
|
|
59
|
-
elif
|
|
60
|
-
image = Image.open(image).convert(
|
|
57
|
+
if "http" in image:
|
|
58
|
+
image = Image.open(requests.get(image, stream=True).raw).convert("RGB")
|
|
59
|
+
elif "/" in image or "\\" in image:
|
|
60
|
+
image = Image.open(image).convert("RGB")
|
|
61
61
|
|
|
62
62
|
try:
|
|
63
|
-
image
|
|
64
|
-
prompt
|
|
65
|
-
res
|
|
63
|
+
image = self.vis_processors["eval"](image).unsqueeze(0).to(self.device)
|
|
64
|
+
prompt = self.txt_processors["eval"](prompt)
|
|
65
|
+
res = self.model.generate(
|
|
66
|
+
samples={"image": image, "prompt": prompt},
|
|
67
|
+
use_nucleus_sampling=True,
|
|
68
|
+
num_captions=3,
|
|
69
|
+
)
|
|
66
70
|
except Exception as e:
|
|
67
71
|
if except_remedy is None:
|
|
68
72
|
raise e
|
|
@@ -13,36 +13,36 @@ from ...base import Engine
|
|
|
13
13
|
from ...settings import SYMAI_CONFIG
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
def image_to_byte_array(image: Image, format=
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
def image_to_byte_array(image: Image, format="PNG") -> bytes:
|
|
17
|
+
# BytesIO is a file-like buffer stored in memory
|
|
18
|
+
imgByteArr = io.BytesIO()
|
|
19
|
+
# image.save expects a file-like as a argument
|
|
20
|
+
image.save(imgByteArr, format=format)
|
|
21
|
+
# Turn the BytesIO object back into a bytes object
|
|
22
|
+
return imgByteArr.getvalue()
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
class LLaMAResult(Result):
|
|
26
26
|
def __init__(self, value=None, *args, **kwargs):
|
|
27
27
|
super().__init__(value, *args, **kwargs)
|
|
28
28
|
self._value = value
|
|
29
|
-
self.error
|
|
30
|
-
self.raw
|
|
29
|
+
self.error = None
|
|
30
|
+
self.raw = value
|
|
31
31
|
self._perse_result()
|
|
32
32
|
|
|
33
33
|
def _perse_result(self):
|
|
34
|
-
val
|
|
34
|
+
val = json.loads(self.value)
|
|
35
35
|
self.value = val
|
|
36
|
-
if
|
|
37
|
-
self.error = val[
|
|
38
|
-
if
|
|
39
|
-
self.value = val[
|
|
36
|
+
if "error" in val:
|
|
37
|
+
self.error = val["error"]
|
|
38
|
+
if "content" in val:
|
|
39
|
+
self.value = val["content"]
|
|
40
40
|
|
|
41
41
|
|
|
42
42
|
class LLaMACppClientEngine(Engine):
|
|
43
|
-
def __init__(self, host: str =
|
|
43
|
+
def __init__(self, host: str = "localhost", port: int = 8080, timeout: int = 240):
|
|
44
44
|
super().__init__()
|
|
45
|
-
logger = logging.getLogger(
|
|
45
|
+
logger = logging.getLogger("nesy_client")
|
|
46
46
|
logger.setLevel(logging.WARNING)
|
|
47
47
|
self.config = SYMAI_CONFIG
|
|
48
48
|
self.host = host
|
|
@@ -51,43 +51,45 @@ class LLaMACppClientEngine(Engine):
|
|
|
51
51
|
self.name = self.__class__.__name__
|
|
52
52
|
|
|
53
53
|
def id(self) -> str:
|
|
54
|
-
if
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
if (
|
|
55
|
+
self.config["CAPTION_ENGINE_MODEL"]
|
|
56
|
+
and "llavacpp" in self.config["CAPTION_ENGINE_MODEL"]
|
|
57
|
+
):
|
|
58
|
+
return "imagecaptioning"
|
|
59
|
+
return super().id() # default to unregistered
|
|
58
60
|
|
|
59
61
|
@property
|
|
60
62
|
def max_tokens(self):
|
|
61
63
|
return 4096
|
|
62
64
|
|
|
63
65
|
def forward(self, argument):
|
|
64
|
-
prompts
|
|
65
|
-
kwargs
|
|
66
|
+
prompts = argument.prop.prepared_input
|
|
67
|
+
kwargs = argument.kwargs
|
|
66
68
|
system, user, image = prompts
|
|
67
69
|
# escape special characters
|
|
68
|
-
system
|
|
69
|
-
user
|
|
70
|
+
system = system["content"]
|
|
71
|
+
user = user["content"]
|
|
70
72
|
|
|
71
|
-
if isinstance(image[
|
|
73
|
+
if isinstance(image["content"], Image):
|
|
72
74
|
# format image to bytes
|
|
73
|
-
format_ = argument.prop.image_format if argument.prop.image_format else
|
|
74
|
-
im_bytes = image_to_byte_array(image[
|
|
75
|
+
format_ = argument.prop.image_format if argument.prop.image_format else "PNG"
|
|
76
|
+
im_bytes = image_to_byte_array(image["content"], format=format_)
|
|
75
77
|
else:
|
|
76
78
|
# Convert image to bytes, open as binary
|
|
77
|
-
with Path(image[
|
|
79
|
+
with Path(image["content"]).open("rb") as f:
|
|
78
80
|
im_bytes = f.read()
|
|
79
81
|
# Create multipart/form-data payload
|
|
80
|
-
payload
|
|
82
|
+
payload = MultipartEncoder(
|
|
81
83
|
fields={
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
84
|
+
"user_prompt": ("user_prompt", user, "text/plain"),
|
|
85
|
+
"image_file": ("image_file", im_bytes, "application/octet-stream"),
|
|
86
|
+
"system_prompt": ("system_prompt", system, "text/plain"),
|
|
85
87
|
}
|
|
86
88
|
)
|
|
87
89
|
# Update the headers for multipart/form-data
|
|
88
|
-
headers
|
|
89
|
-
api
|
|
90
|
-
except_remedy = kwargs.get(
|
|
90
|
+
headers = {"Content-Type": payload.content_type}
|
|
91
|
+
api = f"http://{self.host}:{self.port}/llava"
|
|
92
|
+
except_remedy = kwargs.get("except_remedy")
|
|
91
93
|
try:
|
|
92
94
|
# use http localhost 8000 to send a request to the server
|
|
93
95
|
rsp = requests.post(api, data=payload, headers=headers, timeout=self.timeout)
|
|
@@ -95,14 +97,16 @@ class LLaMACppClientEngine(Engine):
|
|
|
95
97
|
except Exception as e:
|
|
96
98
|
if except_remedy is None:
|
|
97
99
|
raise e
|
|
100
|
+
|
|
98
101
|
def callback():
|
|
99
102
|
return requests.post(api, data=payload, headers=headers, timeout=self.timeout)
|
|
103
|
+
|
|
100
104
|
res = except_remedy(self, e, callback, argument)
|
|
101
105
|
|
|
102
106
|
metadata = {}
|
|
103
107
|
|
|
104
|
-
res
|
|
105
|
-
rsp
|
|
108
|
+
res = LLaMAResult(res)
|
|
109
|
+
rsp = [res]
|
|
106
110
|
output = rsp if isinstance(prompts, list) else rsp[0]
|
|
107
111
|
return output, metadata
|
|
108
112
|
|
|
@@ -110,7 +114,10 @@ class LLaMACppClientEngine(Engine):
|
|
|
110
114
|
if not argument.prop.raw_input:
|
|
111
115
|
return False
|
|
112
116
|
if not argument.prop.processed_input:
|
|
113
|
-
UserMessage(
|
|
117
|
+
UserMessage(
|
|
118
|
+
"Need to provide a prompt instruction to the engine if raw_input is enabled.",
|
|
119
|
+
raise_with=ValueError,
|
|
120
|
+
)
|
|
114
121
|
argument.prop.prepared_input = argument.prop.processed_input
|
|
115
122
|
return True
|
|
116
123
|
|
|
@@ -141,16 +148,16 @@ class LLaMACppClientEngine(Engine):
|
|
|
141
148
|
return user
|
|
142
149
|
|
|
143
150
|
def _extract_system_instructions(self, argument, system: str, suffix: str) -> tuple[str, str]:
|
|
144
|
-
if
|
|
145
|
-
parts = suffix.split(
|
|
151
|
+
if "[SYSTEM_INSTRUCTION::]: <<<" in suffix and argument.prop.parse_system_instructions:
|
|
152
|
+
parts = suffix.split("\n>>>\n")
|
|
146
153
|
consumed = 0
|
|
147
154
|
for part in parts:
|
|
148
|
-
if
|
|
155
|
+
if "SYSTEM_INSTRUCTION" in part:
|
|
149
156
|
system += f"{part}\n"
|
|
150
157
|
consumed += 1
|
|
151
158
|
else:
|
|
152
159
|
break
|
|
153
|
-
suffix =
|
|
160
|
+
suffix = "\n>>>\n".join(parts[consumed:])
|
|
154
161
|
return system, suffix
|
|
155
162
|
|
|
156
163
|
def _append_template_suffix(self, user: str, argument) -> str:
|
|
@@ -164,7 +171,7 @@ class LLaMACppClientEngine(Engine):
|
|
|
164
171
|
return
|
|
165
172
|
|
|
166
173
|
system: str = ""
|
|
167
|
-
system = f
|
|
174
|
+
system = f"{system}\n" if system and len(system) > 0 else ""
|
|
168
175
|
system = self._append_context_sections(system, argument)
|
|
169
176
|
|
|
170
177
|
user = self._build_user_instruction(argument)
|
|
@@ -173,9 +180,9 @@ class LLaMACppClientEngine(Engine):
|
|
|
173
180
|
user += f"{suffix}"
|
|
174
181
|
user = self._append_template_suffix(user, argument)
|
|
175
182
|
|
|
176
|
-
user_prompt = {
|
|
183
|
+
user_prompt = {"role": "user", "content": user}
|
|
177
184
|
argument.prop.prepared_input = [
|
|
178
|
-
{
|
|
185
|
+
{"role": "system", "content": system},
|
|
179
186
|
user_prompt,
|
|
180
|
-
{
|
|
187
|
+
{"role": "image", "content": argument.prop.image},
|
|
181
188
|
]
|