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.
Files changed (127) hide show
  1. symai/__init__.py +198 -134
  2. symai/backend/base.py +51 -51
  3. symai/backend/engines/drawing/engine_bfl.py +33 -33
  4. symai/backend/engines/drawing/engine_gpt_image.py +4 -10
  5. symai/backend/engines/embedding/engine_llama_cpp.py +50 -35
  6. symai/backend/engines/embedding/engine_openai.py +22 -16
  7. symai/backend/engines/execute/engine_python.py +16 -16
  8. symai/backend/engines/files/engine_io.py +51 -49
  9. symai/backend/engines/imagecaptioning/engine_blip2.py +27 -23
  10. symai/backend/engines/imagecaptioning/engine_llavacpp_client.py +53 -46
  11. symai/backend/engines/index/engine_pinecone.py +116 -88
  12. symai/backend/engines/index/engine_qdrant.py +1011 -0
  13. symai/backend/engines/index/engine_vectordb.py +78 -52
  14. symai/backend/engines/lean/engine_lean4.py +65 -25
  15. symai/backend/engines/neurosymbolic/__init__.py +28 -28
  16. symai/backend/engines/neurosymbolic/engine_anthropic_claudeX_chat.py +137 -135
  17. symai/backend/engines/neurosymbolic/engine_anthropic_claudeX_reasoning.py +145 -152
  18. symai/backend/engines/neurosymbolic/engine_cerebras.py +328 -0
  19. symai/backend/engines/neurosymbolic/engine_deepseekX_reasoning.py +75 -49
  20. symai/backend/engines/neurosymbolic/engine_google_geminiX_reasoning.py +199 -155
  21. symai/backend/engines/neurosymbolic/engine_groq.py +106 -72
  22. symai/backend/engines/neurosymbolic/engine_huggingface.py +100 -67
  23. symai/backend/engines/neurosymbolic/engine_llama_cpp.py +121 -93
  24. symai/backend/engines/neurosymbolic/engine_openai_gptX_chat.py +213 -132
  25. symai/backend/engines/neurosymbolic/engine_openai_gptX_reasoning.py +180 -137
  26. symai/backend/engines/ocr/engine_apilayer.py +18 -20
  27. symai/backend/engines/output/engine_stdout.py +9 -9
  28. symai/backend/engines/{webscraping → scrape}/engine_requests.py +25 -11
  29. symai/backend/engines/search/engine_openai.py +95 -83
  30. symai/backend/engines/search/engine_parallel.py +665 -0
  31. symai/backend/engines/search/engine_perplexity.py +40 -41
  32. symai/backend/engines/search/engine_serpapi.py +33 -28
  33. symai/backend/engines/speech_to_text/engine_local_whisper.py +37 -27
  34. symai/backend/engines/symbolic/engine_wolframalpha.py +14 -8
  35. symai/backend/engines/text_to_speech/engine_openai.py +15 -19
  36. symai/backend/engines/text_vision/engine_clip.py +34 -28
  37. symai/backend/engines/userinput/engine_console.py +3 -4
  38. symai/backend/mixin/anthropic.py +48 -40
  39. symai/backend/mixin/deepseek.py +4 -5
  40. symai/backend/mixin/google.py +5 -4
  41. symai/backend/mixin/groq.py +2 -4
  42. symai/backend/mixin/openai.py +132 -110
  43. symai/backend/settings.py +14 -14
  44. symai/chat.py +164 -94
  45. symai/collect/dynamic.py +13 -11
  46. symai/collect/pipeline.py +39 -31
  47. symai/collect/stats.py +109 -69
  48. symai/components.py +556 -238
  49. symai/constraints.py +14 -5
  50. symai/core.py +1495 -1210
  51. symai/core_ext.py +55 -50
  52. symai/endpoints/api.py +113 -58
  53. symai/extended/api_builder.py +22 -17
  54. symai/extended/arxiv_pdf_parser.py +13 -5
  55. symai/extended/bibtex_parser.py +8 -4
  56. symai/extended/conversation.py +88 -69
  57. symai/extended/document.py +40 -27
  58. symai/extended/file_merger.py +45 -7
  59. symai/extended/graph.py +38 -24
  60. symai/extended/html_style_template.py +17 -11
  61. symai/extended/interfaces/blip_2.py +1 -1
  62. symai/extended/interfaces/clip.py +4 -2
  63. symai/extended/interfaces/console.py +5 -3
  64. symai/extended/interfaces/dall_e.py +3 -1
  65. symai/extended/interfaces/file.py +2 -0
  66. symai/extended/interfaces/flux.py +3 -1
  67. symai/extended/interfaces/gpt_image.py +15 -6
  68. symai/extended/interfaces/input.py +2 -1
  69. symai/extended/interfaces/llava.py +1 -1
  70. symai/extended/interfaces/{naive_webscraping.py → naive_scrape.py} +3 -2
  71. symai/extended/interfaces/naive_vectordb.py +2 -2
  72. symai/extended/interfaces/ocr.py +4 -2
  73. symai/extended/interfaces/openai_search.py +2 -0
  74. symai/extended/interfaces/parallel.py +30 -0
  75. symai/extended/interfaces/perplexity.py +2 -0
  76. symai/extended/interfaces/pinecone.py +6 -4
  77. symai/extended/interfaces/python.py +2 -0
  78. symai/extended/interfaces/serpapi.py +2 -0
  79. symai/extended/interfaces/terminal.py +0 -1
  80. symai/extended/interfaces/tts.py +2 -1
  81. symai/extended/interfaces/whisper.py +2 -1
  82. symai/extended/interfaces/wolframalpha.py +1 -0
  83. symai/extended/metrics/__init__.py +1 -1
  84. symai/extended/metrics/similarity.py +5 -2
  85. symai/extended/os_command.py +31 -22
  86. symai/extended/packages/symdev.py +39 -34
  87. symai/extended/packages/sympkg.py +30 -27
  88. symai/extended/packages/symrun.py +46 -35
  89. symai/extended/repo_cloner.py +10 -9
  90. symai/extended/seo_query_optimizer.py +15 -12
  91. symai/extended/solver.py +104 -76
  92. symai/extended/summarizer.py +8 -7
  93. symai/extended/taypan_interpreter.py +10 -9
  94. symai/extended/vectordb.py +28 -15
  95. symai/formatter/formatter.py +39 -31
  96. symai/formatter/regex.py +46 -44
  97. symai/functional.py +184 -86
  98. symai/imports.py +85 -51
  99. symai/interfaces.py +1 -1
  100. symai/memory.py +33 -24
  101. symai/menu/screen.py +28 -19
  102. symai/misc/console.py +27 -27
  103. symai/misc/loader.py +4 -3
  104. symai/models/base.py +147 -76
  105. symai/models/errors.py +1 -1
  106. symai/ops/__init__.py +1 -1
  107. symai/ops/measures.py +17 -14
  108. symai/ops/primitives.py +933 -635
  109. symai/post_processors.py +28 -24
  110. symai/pre_processors.py +58 -52
  111. symai/processor.py +15 -9
  112. symai/prompts.py +714 -649
  113. symai/server/huggingface_server.py +115 -32
  114. symai/server/llama_cpp_server.py +14 -6
  115. symai/server/qdrant_server.py +206 -0
  116. symai/shell.py +98 -39
  117. symai/shellsv.py +307 -223
  118. symai/strategy.py +135 -81
  119. symai/symbol.py +276 -225
  120. symai/utils.py +62 -46
  121. {symbolicai-1.0.0.dist-info → symbolicai-1.1.0.dist-info}/METADATA +19 -9
  122. symbolicai-1.1.0.dist-info/RECORD +168 -0
  123. symbolicai-1.0.0.dist-info/RECORD +0 -163
  124. {symbolicai-1.0.0.dist-info → symbolicai-1.1.0.dist-info}/WHEEL +0 -0
  125. {symbolicai-1.0.0.dist-info → symbolicai-1.1.0.dist-info}/entry_points.txt +0 -0
  126. {symbolicai-1.0.0.dist-info → symbolicai-1.1.0.dist-info}/licenses/LICENSE +0 -0
  127. {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('tika').setLevel(logging.CRITICAL)
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 != '' else None
26
+ return int(value) if value != "" else None
26
27
 
27
28
 
28
29
  def _parse_slice_token(token):
29
- if ':' not in token:
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 '[' not in file_path or ']' not in file_path:
43
+ if "[" not in file_path or "]" not in file_path:
43
44
  return file_path, None
44
- path_part, remainder = file_path.split('[', 1)
45
- slice_section = remainder.split(']', 1)[0]
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 'files'
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('with_metadata', False)
83
- file_id = Path(argument.prop.prepared_input).stem.replace(' ', '_')
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'File does not exist: {file_path}'
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 {'.txt', '.md', '.py', '.json', '.yaml', '.yml', '.csv', '.tsv', '.log'}:
102
+ if ext in {".txt", ".md", ".py", ".json", ".yaml", ".yml", ".csv", ".tsv", ".log"}:
102
103
  try:
103
- with path_obj.open(encoding='utf-8', errors='ignore') as f:
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('\n')
109
+ lines = content.split("\n")
109
110
  lines = _apply_slices(lines, slices_)
110
- content = '\n'.join(lines)
111
- content = content.encode('utf8', 'ignore').decode('utf8', 'ignore')
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_['content'] if 'content' in file_ else str(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('\n')
124
+ content = content.split("\n")
124
125
 
125
126
  content = _apply_slices(content, slices_)
126
- content = '\n'.join(content)
127
- content = content.encode('utf8', 'ignore').decode('utf8', 'ignore')
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'%%EOF' in x:
136
- actual_line = len(pdf_stream_in)-i
137
- UserMessage(f'EOF found at line position {-i} = actual {actual_line}, with value {x}')
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('rb') as p:
147
- txt = (p.readlines())
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'{file_path}_fixed.pdf')
154
- with new_file_path.open('wb') as f:
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 = len(pdf_reader.pages)
162
- with_metadata = argument.kwargs.get('with_metadata', False)
163
- file_id = Path(argument.prop.prepared_input).stem.replace(' ', '_')
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('utf8', 'ignore').decode('utf8', 'ignore')
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 '\n'.join(txt) if not with_metadata else txt
175
+ return "\n".join(txt) if not with_metadata else txt
174
176
 
175
177
  def forward(self, argument):
176
- kwargs = argument.kwargs
177
- path = argument.prop.prepared_input
178
+ kwargs = argument.kwargs
179
+ path = argument.prop.prepared_input
178
180
 
179
- if '.pdf' in path:
181
+ if ".pdf" in path:
180
182
  page_range = None
181
- if 'slice' in kwargs:
182
- page_range = kwargs['slice']
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('rb') as f:
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'Error reading PDF: {e} | {path}')
194
- if 'fix_pdf' not in kwargs or not kwargs['fix_pdf']:
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'Error reading empty file: {e} | {path}')
205
+ UserMessage(f"Error reading empty file: {e} | {path}")
204
206
  raise e
205
207
 
206
208
  if rsp is None:
207
- UserMessage(f'Error reading file - empty result: {path}', raise_with=Exception)
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['CAPTION_ENGINE_MODEL'].split('/')
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('cuda' if torch.cuda.is_available() else 'cpu')
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 self.config['CAPTION_ENGINE_MODEL'] and \
35
- 'blip2' in self.config['CAPTION_ENGINE_MODEL']:
36
- return 'imagecaptioning'
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 'CAPTION_ENGINE_MODEL' in kwargs:
42
- self.model_id = kwargs['CAPTION_ENGINE_MODEL']
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('Blip2 is not installed. Please install it with `pip install symbolicai[blip2]`', raise_with=ImportError)
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 = load_model_and_preprocess(name = self.name_id,
49
- model_type = self.model_id,
50
- is_eval = True,
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 = argument.kwargs
55
- except_remedy = kwargs.get('except_remedy')
54
+ kwargs = argument.kwargs
55
+ except_remedy = kwargs.get("except_remedy")
56
56
 
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')
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 = self.vis_processors['eval'](image).unsqueeze(0).to(self.device)
64
- prompt = self.txt_processors['eval'](prompt)
65
- res = self.model.generate(samples={"image": image, "prompt": prompt}, use_nucleus_sampling=True, num_captions=3)
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='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()
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 = None
30
- self.raw = value
29
+ self.error = None
30
+ self.raw = value
31
31
  self._perse_result()
32
32
 
33
33
  def _perse_result(self):
34
- val = json.loads(self.value)
34
+ val = json.loads(self.value)
35
35
  self.value = val
36
- if 'error' in val:
37
- self.error = val['error']
38
- if 'content' in val:
39
- self.value = val['content']
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 = 'localhost', port: int = 8080, timeout: int = 240):
43
+ def __init__(self, host: str = "localhost", port: int = 8080, timeout: int = 240):
44
44
  super().__init__()
45
- logger = logging.getLogger('nesy_client')
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 self.config['CAPTION_ENGINE_MODEL'] and \
55
- 'llavacpp' in self.config['CAPTION_ENGINE_MODEL']:
56
- return 'imagecaptioning'
57
- return super().id() # default to unregistered
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 = argument.prop.prepared_input
65
- kwargs = argument.kwargs
66
+ prompts = argument.prop.prepared_input
67
+ kwargs = argument.kwargs
66
68
  system, user, image = prompts
67
69
  # escape special characters
68
- system = system['content']
69
- user = user['content']
70
+ system = system["content"]
71
+ user = user["content"]
70
72
 
71
- if isinstance(image['content'], 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 'PNG'
74
- im_bytes = image_to_byte_array(image['content'], format=format_)
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['content']).open('rb') as f:
79
+ with Path(image["content"]).open("rb") as f:
78
80
  im_bytes = f.read()
79
81
  # Create multipart/form-data payload
80
- payload = MultipartEncoder(
82
+ payload = MultipartEncoder(
81
83
  fields={
82
- 'user_prompt': ('user_prompt', user, 'text/plain'),
83
- 'image_file': ('image_file', im_bytes, 'application/octet-stream'),
84
- 'system_prompt': ('system_prompt', system, 'text/plain')
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 = {'Content-Type': payload.content_type}
89
- api = f'http://{self.host}:{self.port}/llava'
90
- except_remedy = kwargs.get('except_remedy')
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 = LLaMAResult(res)
105
- rsp = [res]
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('Need to provide a prompt instruction to the engine if raw_input is enabled.', raise_with=ValueError)
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 '[SYSTEM_INSTRUCTION::]: <<<' in suffix and argument.prop.parse_system_instructions:
145
- parts = suffix.split('\n>>>\n')
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 'SYSTEM_INSTRUCTION' in part:
155
+ if "SYSTEM_INSTRUCTION" in part:
149
156
  system += f"{part}\n"
150
157
  consumed += 1
151
158
  else:
152
159
  break
153
- suffix = '\n>>>\n'.join(parts[consumed:])
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'{system}\n' if system and len(system) > 0 else ''
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 = { "role": "user", "content": user }
183
+ user_prompt = {"role": "user", "content": user}
177
184
  argument.prop.prepared_input = [
178
- { "role": "system", "content": system },
185
+ {"role": "system", "content": system},
179
186
  user_prompt,
180
- { "role": "image", "content": argument.prop.image }
187
+ {"role": "image", "content": argument.prop.image},
181
188
  ]