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
@@ -28,6 +28,7 @@ logging.getLogger("urllib").setLevel(logging.ERROR)
28
28
  logging.getLogger("httpx").setLevel(logging.ERROR)
29
29
  logging.getLogger("httpcore").setLevel(logging.ERROR)
30
30
 
31
+
31
32
  class TokenizerWrapper:
32
33
  def __init__(self, compute_tokens_func):
33
34
  self.compute_tokens_func = compute_tokens_func
@@ -35,18 +36,19 @@ class TokenizerWrapper:
35
36
  def encode(self, text: str) -> int:
36
37
  return self.compute_tokens_func([{"role": "user", "content": text}])
37
38
 
39
+
38
40
  class ClaudeXChatEngine(Engine, AnthropicMixin):
39
41
  def __init__(self, api_key: str | None = None, model: str | None = None):
40
42
  super().__init__()
41
43
  self.config = deepcopy(SYMAI_CONFIG)
42
44
  # In case we use EngineRepository.register to inject the api_key and model => dynamically change the engine at runtime
43
45
  if api_key is not None and model is not None:
44
- self.config['NEUROSYMBOLIC_ENGINE_API_KEY'] = api_key
45
- self.config['NEUROSYMBOLIC_ENGINE_MODEL'] = model
46
- if self.id() != 'neurosymbolic':
47
- return # do not initialize if not neurosymbolic; avoids conflict with llama.cpp check in EngineRepository.register_from_package
48
- anthropic.api_key = self.config['NEUROSYMBOLIC_ENGINE_API_KEY']
49
- self.model = self.config['NEUROSYMBOLIC_ENGINE_MODEL']
46
+ self.config["NEUROSYMBOLIC_ENGINE_API_KEY"] = api_key
47
+ self.config["NEUROSYMBOLIC_ENGINE_MODEL"] = model
48
+ if self.id() != "neurosymbolic":
49
+ return # do not initialize if not neurosymbolic; avoids conflict with llama.cpp check in EngineRepository.register_from_package
50
+ anthropic.api_key = self.config["NEUROSYMBOLIC_ENGINE_API_KEY"]
51
+ self.model = self.config["NEUROSYMBOLIC_ENGINE_MODEL"]
50
52
  self.name = self.__class__.__name__
51
53
  self.tokenizer = TokenizerWrapper(self.compute_required_tokens)
52
54
  self.max_context_tokens = self.api_max_context_tokens()
@@ -54,21 +56,25 @@ class ClaudeXChatEngine(Engine, AnthropicMixin):
54
56
  self.client = anthropic.Anthropic(api_key=anthropic.api_key)
55
57
 
56
58
  def id(self) -> str:
57
- if self.config.get('NEUROSYMBOLIC_ENGINE_MODEL') and \
58
- self.config.get('NEUROSYMBOLIC_ENGINE_MODEL').startswith('claude') and \
59
- ('3-7' not in self.config.get('NEUROSYMBOLIC_ENGINE_MODEL') and \
60
- '4-0' not in self.config.get('NEUROSYMBOLIC_ENGINE_MODEL') and \
61
- '4-1' not in self.config.get('NEUROSYMBOLIC_ENGINE_MODEL') and \
62
- '4-5' not in self.config.get('NEUROSYMBOLIC_ENGINE_MODEL')):
63
- return 'neurosymbolic'
64
- return super().id() # default to unregistered
59
+ if (
60
+ self.config.get("NEUROSYMBOLIC_ENGINE_MODEL")
61
+ and self.config.get("NEUROSYMBOLIC_ENGINE_MODEL").startswith("claude")
62
+ and (
63
+ "3-7" not in self.config.get("NEUROSYMBOLIC_ENGINE_MODEL")
64
+ and "4-0" not in self.config.get("NEUROSYMBOLIC_ENGINE_MODEL")
65
+ and "4-1" not in self.config.get("NEUROSYMBOLIC_ENGINE_MODEL")
66
+ and "4-5" not in self.config.get("NEUROSYMBOLIC_ENGINE_MODEL")
67
+ )
68
+ ):
69
+ return "neurosymbolic"
70
+ return super().id() # default to unregistered
65
71
 
66
72
  def command(self, *args, **kwargs):
67
73
  super().command(*args, **kwargs)
68
- if 'NEUROSYMBOLIC_ENGINE_API_KEY' in kwargs:
69
- anthropic.api_key = kwargs['NEUROSYMBOLIC_ENGINE_API_KEY']
70
- if 'NEUROSYMBOLIC_ENGINE_MODEL' in kwargs:
71
- self.model = kwargs['NEUROSYMBOLIC_ENGINE_MODEL']
74
+ if "NEUROSYMBOLIC_ENGINE_API_KEY" in kwargs:
75
+ anthropic.api_key = kwargs["NEUROSYMBOLIC_ENGINE_API_KEY"]
76
+ if "NEUROSYMBOLIC_ENGINE_MODEL" in kwargs:
77
+ self.model = kwargs["NEUROSYMBOLIC_ENGINE_MODEL"]
72
78
 
73
79
  def compute_required_tokens(self, messages) -> int:
74
80
  claude_messages, system_content = self._build_claude_messages(messages)
@@ -83,11 +89,11 @@ class ClaudeXChatEngine(Engine, AnthropicMixin):
83
89
  system_content = None
84
90
 
85
91
  for role, content_str in self._message_parts(messages):
86
- if role == 'system':
92
+ if role == "system":
87
93
  system_content = content_str
88
94
  continue
89
95
 
90
- if role in ['user', 'assistant']:
96
+ if role in ["user", "assistant"]:
91
97
  message_content = self._build_message_content(content_str)
92
98
  if message_content:
93
99
  claude_messages.append(self._create_claude_message(role, message_content))
@@ -102,11 +108,11 @@ class ClaudeXChatEngine(Engine, AnthropicMixin):
102
108
 
103
109
  def _extract_message_details(self, part):
104
110
  if isinstance(part, str):
105
- return 'user', part
111
+ return "user", part
106
112
 
107
113
  if isinstance(part, dict):
108
- role = part.get('role')
109
- content_str = str(part.get('content', ''))
114
+ role = part.get("role")
115
+ content_str = str(part.get("content", ""))
110
116
  return role, content_str
111
117
 
112
118
  msg = f"Unsupported message part type: {type(part)}"
@@ -121,33 +127,21 @@ class ClaudeXChatEngine(Engine, AnthropicMixin):
121
127
 
122
128
  text_content = self._remove_vision_pattern(content_str)
123
129
  if text_content:
124
- message_content.append({
125
- "type": "text",
126
- "text": text_content
127
- })
130
+ message_content.append({"type": "text", "text": text_content})
128
131
 
129
132
  return message_content
130
133
 
131
134
  def _create_claude_message(self, role: str, message_content: list) -> dict:
132
- if len(message_content) == 1 and message_content[0].get('type') == 'text':
133
- return {
134
- 'role': role,
135
- 'content': message_content[0]['text']
136
- }
135
+ if len(message_content) == 1 and message_content[0].get("type") == "text":
136
+ return {"role": role, "content": message_content[0]["text"]}
137
137
 
138
- return {
139
- 'role': role,
140
- 'content': message_content
141
- }
138
+ return {"role": role, "content": message_content}
142
139
 
143
140
  def _count_claude_tokens(self, claude_messages: list, system_content: str | None) -> int:
144
141
  try:
145
- count_params = {
146
- 'model': self.model,
147
- 'messages': claude_messages
148
- }
142
+ count_params = {"model": self.model, "messages": claude_messages}
149
143
  if system_content:
150
- count_params['system'] = system_content
144
+ count_params["system"] = system_content
151
145
  count_response = self.client.messages.count_tokens(**count_params)
152
146
  return count_response.input_tokens
153
147
  except Exception as e:
@@ -155,16 +149,17 @@ class ClaudeXChatEngine(Engine, AnthropicMixin):
155
149
  UserMessage(f"Error counting tokens for Claude: {e!s}", raise_with=RuntimeError)
156
150
 
157
151
  def compute_remaining_tokens(self, _prompts: list) -> int:
158
- UserMessage('Method not implemented.', raise_with=NotImplementedError)
152
+ UserMessage("Method not implemented.", raise_with=NotImplementedError)
159
153
 
160
154
  def _handle_image_content(self, content: str) -> list:
161
155
  """Handle image content by processing vision patterns and returning image file data."""
156
+
162
157
  def extract_pattern(text):
163
- pattern = r'<<vision:(.*?):>>'
158
+ pattern = r"<<vision:(.*?):>>"
164
159
  return re.findall(pattern, text)
165
160
 
166
161
  image_files = []
167
- if '<<vision:' in content:
162
+ if "<<vision:" in content:
168
163
  parts = extract_pattern(content)
169
164
  for p in parts:
170
165
  img_ = p.strip()
@@ -172,89 +167,94 @@ class ClaudeXChatEngine(Engine, AnthropicMixin):
172
167
  max_used_frames = 10
173
168
  buffer, ext = encode_media_frames(img_)
174
169
  if len(buffer) > 1:
175
- step = len(buffer) // max_frames_spacing # max frames spacing
170
+ step = len(buffer) // max_frames_spacing # max frames spacing
176
171
  frames = []
177
172
  indices = list(range(0, len(buffer), step))[:max_used_frames]
178
173
  for i in indices:
179
- frames.append({'data': buffer[i], 'media_type': f'image/{ext}', 'type': 'base64'})
174
+ frames.append(
175
+ {"data": buffer[i], "media_type": f"image/{ext}", "type": "base64"}
176
+ )
180
177
  image_files.extend(frames)
181
178
  elif len(buffer) == 1:
182
- image_files.append({'data': buffer[0], 'media_type': f'image/{ext}', 'type': 'base64'})
179
+ image_files.append(
180
+ {"data": buffer[0], "media_type": f"image/{ext}", "type": "base64"}
181
+ )
183
182
  else:
184
- UserMessage('No frames found for image!')
183
+ UserMessage("No frames found for image!")
185
184
  return image_files
186
185
 
187
186
  def _remove_vision_pattern(self, text: str) -> str:
188
187
  """Remove vision patterns from text."""
189
- pattern = r'<<vision:(.*?):>>'
190
- return re.sub(pattern, '', text)
188
+ pattern = r"<<vision:(.*?):>>"
189
+ return re.sub(pattern, "", text)
191
190
 
192
191
  def forward(self, argument):
193
192
  kwargs = argument.kwargs
194
193
  system, messages = argument.prop.prepared_input
195
194
  payload = self._prepare_request_payload(argument)
196
- except_remedy = kwargs.get('except_remedy')
195
+ except_remedy = kwargs.get("except_remedy")
197
196
 
198
197
  try:
199
- res = self.client.messages.create(
200
- system=system,
201
- messages=messages,
202
- **payload
203
- )
198
+ res = self.client.messages.create(system=system, messages=messages, **payload)
204
199
  except Exception as e:
205
- if anthropic.api_key is None or anthropic.api_key == '':
206
- msg = 'Anthropic API key is not set. Please set it in the config file or pass it as an argument to the command method.'
200
+ if anthropic.api_key is None or anthropic.api_key == "":
201
+ msg = "Anthropic API key is not set. Please set it in the config file or pass it as an argument to the command method."
207
202
  UserMessage(msg)
208
- if self.config['NEUROSYMBOLIC_ENGINE_API_KEY'] is None or self.config['NEUROSYMBOLIC_ENGINE_API_KEY'] == '':
203
+ if (
204
+ self.config["NEUROSYMBOLIC_ENGINE_API_KEY"] is None
205
+ or self.config["NEUROSYMBOLIC_ENGINE_API_KEY"] == ""
206
+ ):
209
207
  UserMessage(msg, raise_with=ValueError)
210
- anthropic.api_key = self.config['NEUROSYMBOLIC_ENGINE_API_KEY']
208
+ anthropic.api_key = self.config["NEUROSYMBOLIC_ENGINE_API_KEY"]
211
209
 
212
210
  callback = self.client.messages.create
213
- kwargs['model'] = kwargs.get('model', self.model)
211
+ kwargs["model"] = kwargs.get("model", self.model)
214
212
 
215
213
  if except_remedy is not None:
216
214
  res = except_remedy(self, e, callback, argument)
217
215
  else:
218
- UserMessage(f'Error during generation. Caused by: {e}', raise_with=ValueError)
216
+ UserMessage(f"Error during generation. Caused by: {e}", raise_with=ValueError)
219
217
 
220
- if payload['stream']:
221
- res = list(res) # Unpack the iterator to a list
222
- metadata = {'raw_output': res}
218
+ if payload["stream"]:
219
+ res = list(res) # Unpack the iterator to a list
220
+ metadata = {"raw_output": res}
223
221
  response_data = self._collect_response(res)
224
222
 
225
- if response_data.get('function_call'):
226
- metadata['function_call'] = response_data['function_call']
223
+ if response_data.get("function_call"):
224
+ metadata["function_call"] = response_data["function_call"]
227
225
 
228
- text_output = response_data.get('text', '')
226
+ text_output = response_data.get("text", "")
229
227
  if argument.prop.response_format:
230
228
  # Anthropic returns JSON in markdown format
231
- text_output = text_output.replace('```json', '').replace('```', '')
229
+ text_output = text_output.replace("```json", "").replace("```", "")
232
230
 
233
231
  return [text_output], metadata
234
232
 
235
233
  def _prepare_raw_input(self, argument):
236
234
  if not argument.prop.processed_input:
237
- msg = 'Need to provide a prompt instruction to the engine if `raw_input` is enabled!'
235
+ msg = "Need to provide a prompt instruction to the engine if `raw_input` is enabled!"
238
236
  UserMessage(msg)
239
237
  raise ValueError(msg)
240
238
  system = NOT_GIVEN
241
239
  prompt = copy(argument.prop.processed_input)
242
240
  if not isinstance(prompt, list):
243
241
  if not isinstance(prompt, dict):
244
- prompt = {'role': 'user', 'content': str(prompt)}
242
+ prompt = {"role": "user", "content": str(prompt)}
245
243
  prompt = [prompt]
246
244
  if len(prompt) > 1:
247
245
  # assert there are not more than 1 system instruction
248
- assert len([p for p in prompt if p['role'] == 'system']) <= 1, 'Only one system instruction is allowed!'
246
+ assert len([p for p in prompt if p["role"] == "system"]) <= 1, (
247
+ "Only one system instruction is allowed!"
248
+ )
249
249
  for p in prompt:
250
- if p['role'] == 'system':
251
- system = p['content']
250
+ if p["role"] == "system":
251
+ system = p["content"]
252
252
  prompt.remove(p)
253
253
  break
254
254
  return system, prompt
255
255
 
256
256
  def prepare(self, argument):
257
- #@NOTE: OpenAI compatibility at high level
257
+ # @NOTE: OpenAI compatibility at high level
258
258
  if argument.prop.raw_input:
259
259
  argument.prop.prepared_input = self._prepare_raw_input(argument)
260
260
  return
@@ -265,7 +265,9 @@ class ClaudeXChatEngine(Engine, AnthropicMixin):
265
265
 
266
266
  system = self._build_system_prompt(argument, has_image, non_verbose_output)
267
267
  user_text, user_prompt, image_blocks = self._build_user_prompt(argument, image_files)
268
- system, user_prompt = self._apply_self_prompt_if_needed(argument, system, user_text, image_blocks, user_prompt)
268
+ system, user_prompt = self._apply_self_prompt_if_needed(
269
+ argument, system, user_text, image_blocks, user_prompt
270
+ )
269
271
 
270
272
  argument.prop.prepared_input = (system, [user_prompt])
271
273
 
@@ -280,13 +282,15 @@ class ClaudeXChatEngine(Engine, AnthropicMixin):
280
282
  if argument.prop.suppress_verbose_output:
281
283
  system += non_verbose_output
282
284
 
283
- system = f'{system}\n' if system and len(system) > 0 else ''
285
+ system = f"{system}\n" if system and len(system) > 0 else ""
284
286
 
285
287
  if argument.prop.response_format:
286
288
  response_format = argument.prop.response_format
287
- assert response_format.get('type') is not None, 'Response format type is required! Expected format `{"type": str}`! The str value will be passed to the engine. Refer to the Anthropic documentation for more information: https://docs.anthropic.com/en/docs/test-and-evaluate/strengthen-guardrails/increase-consistency#example-standardizing-customer-feedback'
289
+ assert response_format.get("type") is not None, (
290
+ 'Response format type is required! Expected format `{"type": str}`! The str value will be passed to the engine. Refer to the Anthropic documentation for more information: https://docs.anthropic.com/en/docs/test-and-evaluate/strengthen-guardrails/increase-consistency#example-standardizing-customer-feedback'
291
+ )
288
292
  system += non_verbose_output
289
- system += f'<RESPONSE_FORMAT/>\n{response_format["type"]}\n\n'
293
+ system += f"<RESPONSE_FORMAT/>\n{response_format['type']}\n\n"
290
294
 
291
295
  return system
292
296
 
@@ -321,7 +325,7 @@ class ClaudeXChatEngine(Engine, AnthropicMixin):
321
325
 
322
326
  def _append_template_suffix(self, system: str, argument) -> str:
323
327
  if argument.prop.template_suffix:
324
- system += f' You will only generate content for the placeholder `{argument.prop.template_suffix!s}` following the instructions and the provided context information.\n\n'
328
+ system += f" You will only generate content for the placeholder `{argument.prop.template_suffix!s}` following the instructions and the provided context information.\n\n"
325
329
 
326
330
  return system
327
331
 
@@ -334,59 +338,58 @@ class ClaudeXChatEngine(Engine, AnthropicMixin):
334
338
  if not user_text:
335
339
  user_text = "N/A"
336
340
 
337
- image_blocks = [{'type': 'image', 'source': image_file} for image_file in image_files]
341
+ image_blocks = [{"type": "image", "source": image_file} for image_file in image_files]
338
342
  user_prompt = self._wrap_user_prompt_content(user_text, image_blocks)
339
343
  return user_text, user_prompt, image_blocks
340
344
 
341
345
  def _wrap_user_prompt_content(self, user_text: str, image_blocks: list[dict]) -> dict:
342
346
  if len(image_blocks) > 0:
343
- return {
344
- "role": "user",
345
- "content": [
346
- *image_blocks,
347
- {'type': 'text', 'text': user_text}
348
- ]
349
- }
347
+ return {"role": "user", "content": [*image_blocks, {"type": "text", "text": user_text}]}
350
348
 
351
- return {
352
- "role": "user",
353
- "content": user_text
354
- }
349
+ return {"role": "user", "content": user_text}
355
350
 
356
- def _apply_self_prompt_if_needed(self, argument, system: str, user_text: str, image_blocks: list[dict], user_prompt: dict):
357
- if not (argument.prop.instance._kwargs.get('self_prompt', False) or argument.prop.self_prompt):
351
+ def _apply_self_prompt_if_needed(
352
+ self, argument, system: str, user_text: str, image_blocks: list[dict], user_prompt: dict
353
+ ):
354
+ if not (
355
+ argument.prop.instance._kwargs.get("self_prompt", False) or argument.prop.self_prompt
356
+ ):
358
357
  return system, user_prompt
359
358
 
360
359
  self_prompter = SelfPrompt()
361
- res = self_prompter({'user': user_text, 'system': system})
360
+ res = self_prompter({"user": user_text, "system": system})
362
361
  if res is None:
363
362
  msg = "Self-prompting failed!"
364
363
  UserMessage(msg)
365
364
  raise ValueError(msg)
366
365
 
367
- updated_user_prompt = self._wrap_user_prompt_content(res['user'], image_blocks)
368
- return res['system'], updated_user_prompt
366
+ updated_user_prompt = self._wrap_user_prompt_content(res["user"], image_blocks)
367
+ return res["system"], updated_user_prompt
369
368
 
370
369
  def _prepare_request_payload(self, argument):
371
370
  kwargs = argument.kwargs
372
- model = kwargs.get('model', self.model)
373
- max_tokens = kwargs.get('max_tokens', self.max_response_tokens)
374
- stop = kwargs.get('stop', NOT_GIVEN)
375
- temperature = kwargs.get('temperature', 1)
376
- top_p = kwargs.get('top_p', NOT_GIVEN if temperature is not None else 1) #@NOTE:'You should either alter temperature or top_p, but not both.'
377
- top_k = kwargs.get('top_k', NOT_GIVEN)
378
- stream = kwargs.get('stream', True) # Do NOT remove this default value! Getting tons of API errors because they can't process requests >10m
379
- tools = kwargs.get('tools', NOT_GIVEN)
380
- tool_choice = kwargs.get('tool_choice', NOT_GIVEN)
381
- metadata_anthropic = kwargs.get('metadata', NOT_GIVEN)
371
+ model = kwargs.get("model", self.model)
372
+ max_tokens = kwargs.get("max_tokens", self.max_response_tokens)
373
+ stop = kwargs.get("stop", NOT_GIVEN)
374
+ temperature = kwargs.get("temperature", 1)
375
+ top_p = kwargs.get(
376
+ "top_p", NOT_GIVEN if temperature is not None else 1
377
+ ) # @NOTE:'You should either alter temperature or top_p, but not both.'
378
+ top_k = kwargs.get("top_k", NOT_GIVEN)
379
+ stream = kwargs.get(
380
+ "stream", True
381
+ ) # Do NOT remove this default value! Getting tons of API errors because they can't process requests >10m
382
+ tools = kwargs.get("tools", NOT_GIVEN)
383
+ tool_choice = kwargs.get("tool_choice", NOT_GIVEN)
384
+ metadata_anthropic = kwargs.get("metadata", NOT_GIVEN)
382
385
 
383
386
  if stop != NOT_GIVEN and not isinstance(stop, list):
384
387
  stop = [stop]
385
388
 
386
- #@NOTE: Anthropic fails if stop is not raw string, so cast it to r'…'
389
+ # @NOTE: Anthropic fails if stop is not raw string, so cast it to r'…'
387
390
  # E.g. when we use defaults in core.py, i.e. stop=['\n']
388
391
  if stop != NOT_GIVEN:
389
- stop = [r'{s}' for s in stop]
392
+ stop = [r"{s}" for s in stop]
390
393
 
391
394
  return {
392
395
  "model": model,
@@ -398,7 +401,7 @@ class ClaudeXChatEngine(Engine, AnthropicMixin):
398
401
  "stream": stream,
399
402
  "metadata": metadata_anthropic,
400
403
  "tools": tools,
401
- "tool_choice": tool_choice
404
+ "tool_choice": tool_choice,
402
405
  }
403
406
 
404
407
  def _collect_response(self, res):
@@ -408,7 +411,9 @@ class ClaudeXChatEngine(Engine, AnthropicMixin):
408
411
  if isinstance(res, Message):
409
412
  return self._collect_message_response(res)
410
413
 
411
- UserMessage(f"Unexpected response type from Anthropic API: {type(res)}", raise_with=ValueError)
414
+ UserMessage(
415
+ f"Unexpected response type from Anthropic API: {type(res)}", raise_with=ValueError
416
+ )
412
417
  return {}
413
418
 
414
419
  def _collect_streaming_response(self, res):
@@ -426,27 +431,24 @@ class ClaudeXChatEngine(Engine, AnthropicMixin):
426
431
  if tool_call is not None:
427
432
  tool_calls_raw.append(tool_call)
428
433
 
429
- text_content = ''.join(text_parts)
434
+ text_content = "".join(text_parts)
430
435
  function_call_data = self._build_function_call_data(tool_calls_raw)
431
436
 
432
- return {
433
- "text": text_content,
434
- "function_call": function_call_data
435
- }
437
+ return {"text": text_content, "function_call": function_call_data}
436
438
 
437
439
  def _start_tool_call(self, chunk, active_tool_calls: dict):
438
440
  if isinstance(chunk.content_block, ToolUseBlock):
439
441
  active_tool_calls[chunk.index] = {
440
- 'id': chunk.content_block.id,
441
- 'name': chunk.content_block.name,
442
- 'input_json_str': ""
442
+ "id": chunk.content_block.id,
443
+ "name": chunk.content_block.name,
444
+ "input_json_str": "",
443
445
  }
444
446
 
445
447
  def _update_stream_chunk(self, chunk, text_parts: list, active_tool_calls: dict):
446
448
  if isinstance(chunk.delta, TextDelta):
447
449
  text_parts.append(chunk.delta.text)
448
450
  elif isinstance(chunk.delta, InputJSONDelta) and chunk.index in active_tool_calls:
449
- active_tool_calls[chunk.index]['input_json_str'] += chunk.delta.partial_json
451
+ active_tool_calls[chunk.index]["input_json_str"] += chunk.delta.partial_json
450
452
 
451
453
  def _finish_tool_call(self, chunk, active_tool_calls: dict):
452
454
  if chunk.index not in active_tool_calls:
@@ -454,10 +456,12 @@ class ClaudeXChatEngine(Engine, AnthropicMixin):
454
456
 
455
457
  tool_call_info = active_tool_calls.pop(chunk.index)
456
458
  try:
457
- tool_call_info['input'] = json.loads(tool_call_info['input_json_str'])
459
+ tool_call_info["input"] = json.loads(tool_call_info["input_json_str"])
458
460
  except json.JSONDecodeError as e:
459
- UserMessage(f"Failed to parse JSON for tool call {tool_call_info['name']}: {e}. Raw JSON: '{tool_call_info['input_json_str']}'")
460
- tool_call_info['input'] = {}
461
+ UserMessage(
462
+ f"Failed to parse JSON for tool call {tool_call_info['name']}: {e}. Raw JSON: '{tool_call_info['input_json_str']}'"
463
+ )
464
+ tool_call_info["input"] = {}
461
465
  return tool_call_info
462
466
 
463
467
  def _build_function_call_data(self, tool_calls_raw: list | None) -> dict | None:
@@ -465,13 +469,12 @@ class ClaudeXChatEngine(Engine, AnthropicMixin):
465
469
  return None
466
470
 
467
471
  if len(tool_calls_raw) > 1:
468
- UserMessage("Multiple tool calls detected in the stream but only the first one will be processed.")
472
+ UserMessage(
473
+ "Multiple tool calls detected in the stream but only the first one will be processed."
474
+ )
469
475
 
470
476
  tool_call = tool_calls_raw[0]
471
- return {
472
- 'name': tool_call['name'],
473
- 'arguments': tool_call['input']
474
- }
477
+ return {"name": tool_call["name"], "arguments": tool_call["input"]}
475
478
 
476
479
  def _collect_message_response(self, res: Message):
477
480
  text_parts = []
@@ -483,15 +486,14 @@ class ClaudeXChatEngine(Engine, AnthropicMixin):
483
486
  text_parts.append(content_block.text)
484
487
  elif isinstance(content_block, ToolUseBlock):
485
488
  if hit_tool_use:
486
- UserMessage("Multiple tool use blocks detected in the response but only the first one will be processed.")
489
+ UserMessage(
490
+ "Multiple tool use blocks detected in the response but only the first one will be processed."
491
+ )
487
492
  else:
488
493
  function_call_data = {
489
- 'name': content_block.name,
490
- 'arguments': content_block.input
494
+ "name": content_block.name,
495
+ "arguments": content_block.input,
491
496
  }
492
497
  hit_tool_use = True
493
498
 
494
- return {
495
- "text": ''.join(text_parts),
496
- "function_call": function_call_data
497
- }
499
+ return {"text": "".join(text_parts), "function_call": function_call_data}