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
|
@@ -21,7 +21,7 @@ logging.getLogger("httpcore").setLevel(logging.ERROR)
|
|
|
21
21
|
_NON_VERBOSE_OUTPUT = (
|
|
22
22
|
"<META_INSTRUCTION/>\n"
|
|
23
23
|
"You do not output anything else, like verbose preambles or post explanation, such as "
|
|
24
|
-
"
|
|
24
|
+
'"Sure, let me...", "Hope that was helpful...", "Yes, I can help you with that...", etc. '
|
|
25
25
|
"Consider well formatted output, e.g. for sentences use punctuation, spaces etc. or for code use "
|
|
26
26
|
"indentation, etc. Never add meta instructions information to your output!\n\n"
|
|
27
27
|
)
|
|
@@ -33,44 +33,56 @@ class GroqEngine(Engine):
|
|
|
33
33
|
self.config = deepcopy(SYMAI_CONFIG)
|
|
34
34
|
# In case we use EngineRepository.register to inject the api_key and model => dynamically change the engine at runtime
|
|
35
35
|
if api_key is not None and model is not None:
|
|
36
|
-
self.config[
|
|
37
|
-
self.config[
|
|
38
|
-
if self.id() !=
|
|
39
|
-
return
|
|
40
|
-
openai.api_key = self.config[
|
|
41
|
-
self.model = self.config[
|
|
36
|
+
self.config["NEUROSYMBOLIC_ENGINE_API_KEY"] = api_key
|
|
37
|
+
self.config["NEUROSYMBOLIC_ENGINE_MODEL"] = model
|
|
38
|
+
if self.id() != "neurosymbolic":
|
|
39
|
+
return # do not initialize if not neurosymbolic; avoids conflict with llama.cpp check in EngineRepository.register_from_package
|
|
40
|
+
openai.api_key = self.config["NEUROSYMBOLIC_ENGINE_API_KEY"]
|
|
41
|
+
self.model = self.config[
|
|
42
|
+
"NEUROSYMBOLIC_ENGINE_MODEL"
|
|
43
|
+
] # Keep the original config name to avoid confusion in downstream tasks
|
|
42
44
|
self.seed = None
|
|
43
45
|
self.name = self.__class__.__name__
|
|
44
46
|
|
|
45
47
|
try:
|
|
46
|
-
self.client = openai.OpenAI(
|
|
48
|
+
self.client = openai.OpenAI(
|
|
49
|
+
api_key=openai.api_key, base_url="https://api.groq.com/openai/v1"
|
|
50
|
+
)
|
|
47
51
|
except Exception as e:
|
|
48
|
-
UserMessage(
|
|
52
|
+
UserMessage(
|
|
53
|
+
f"Failed to initialize OpenAI client. Please check your OpenAI library version. Caused by: {e}",
|
|
54
|
+
raise_with=ValueError,
|
|
55
|
+
)
|
|
49
56
|
|
|
50
57
|
def id(self) -> str:
|
|
51
|
-
if self.config.get(
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
58
|
+
if self.config.get("NEUROSYMBOLIC_ENGINE_MODEL") and self.config.get(
|
|
59
|
+
"NEUROSYMBOLIC_ENGINE_MODEL"
|
|
60
|
+
).startswith("groq"):
|
|
61
|
+
return "neurosymbolic"
|
|
62
|
+
return super().id() # default to unregistered
|
|
55
63
|
|
|
56
64
|
def command(self, *args, **kwargs):
|
|
57
65
|
super().command(*args, **kwargs)
|
|
58
|
-
if
|
|
59
|
-
openai.api_key = kwargs[
|
|
60
|
-
if
|
|
61
|
-
self.model = kwargs[
|
|
62
|
-
if
|
|
63
|
-
self.seed = kwargs[
|
|
66
|
+
if "NEUROSYMBOLIC_ENGINE_API_KEY" in kwargs:
|
|
67
|
+
openai.api_key = kwargs["NEUROSYMBOLIC_ENGINE_API_KEY"]
|
|
68
|
+
if "NEUROSYMBOLIC_ENGINE_MODEL" in kwargs:
|
|
69
|
+
self.model = kwargs["NEUROSYMBOLIC_ENGINE_MODEL"]
|
|
70
|
+
if "seed" in kwargs:
|
|
71
|
+
self.seed = kwargs["seed"]
|
|
64
72
|
|
|
65
73
|
def compute_required_tokens(self, _messages):
|
|
66
|
-
UserMessage(
|
|
74
|
+
UserMessage(
|
|
75
|
+
"Token counting not implemented for this engine.", raise_with=NotImplementedError
|
|
76
|
+
)
|
|
67
77
|
|
|
68
78
|
def compute_remaining_tokens(self, _prompts: list) -> int:
|
|
69
|
-
UserMessage(
|
|
79
|
+
UserMessage(
|
|
80
|
+
"Token counting not implemented for this engine.", raise_with=NotImplementedError
|
|
81
|
+
)
|
|
70
82
|
|
|
71
83
|
def _handle_prefix(self, model_name: str) -> str:
|
|
72
84
|
"""Handle prefix for model name."""
|
|
73
|
-
return model_name.replace(
|
|
85
|
+
return model_name.replace("groq:", "")
|
|
74
86
|
|
|
75
87
|
def _extract_thinking_content(self, output: list[str]) -> tuple[str | None, list[str]]:
|
|
76
88
|
"""Extract thinking content from model output if present and return cleaned output."""
|
|
@@ -81,7 +93,7 @@ class GroqEngine(Engine):
|
|
|
81
93
|
if not content:
|
|
82
94
|
return None, output
|
|
83
95
|
|
|
84
|
-
think_pattern = r
|
|
96
|
+
think_pattern = r"<think>(.*?)</think>"
|
|
85
97
|
match = re.search(think_pattern, content, re.DOTALL)
|
|
86
98
|
|
|
87
99
|
thinking_content = None
|
|
@@ -90,57 +102,67 @@ class GroqEngine(Engine):
|
|
|
90
102
|
if not thinking_content:
|
|
91
103
|
thinking_content = None
|
|
92
104
|
|
|
93
|
-
cleaned_content = re.sub(think_pattern,
|
|
105
|
+
cleaned_content = re.sub(think_pattern, "", content, flags=re.DOTALL).strip()
|
|
94
106
|
cleaned_output = [cleaned_content, *output[1:]]
|
|
95
107
|
|
|
96
108
|
return thinking_content, cleaned_output
|
|
97
109
|
|
|
98
|
-
# cumulative wait time is
|
|
99
|
-
@retry(tries=8, delay=0.5, backoff=
|
|
110
|
+
# cumulative wait time is < 30s
|
|
111
|
+
@retry(tries=8, delay=0.5, backoff=1.5, max_delay=5, jitter=(0, 0.5))
|
|
100
112
|
def forward(self, argument):
|
|
101
113
|
kwargs = argument.kwargs
|
|
102
114
|
messages = argument.prop.prepared_input
|
|
103
115
|
payload = self._prepare_request_payload(messages, argument)
|
|
104
|
-
except_remedy = kwargs.get(
|
|
116
|
+
except_remedy = kwargs.get("except_remedy")
|
|
105
117
|
|
|
106
118
|
try:
|
|
107
119
|
res = self.client.chat.completions.create(**payload)
|
|
108
120
|
|
|
109
121
|
except Exception as e:
|
|
110
|
-
if openai.api_key is None or openai.api_key ==
|
|
111
|
-
msg =
|
|
122
|
+
if openai.api_key is None or openai.api_key == "":
|
|
123
|
+
msg = "Groq API key is not set. Please set it in the config file or pass it as an argument to the command method."
|
|
112
124
|
UserMessage(msg)
|
|
113
|
-
if
|
|
125
|
+
if (
|
|
126
|
+
self.config["NEUROSYMBOLIC_ENGINE_API_KEY"] is None
|
|
127
|
+
or self.config["NEUROSYMBOLIC_ENGINE_API_KEY"] == ""
|
|
128
|
+
):
|
|
114
129
|
UserMessage(msg, raise_with=ValueError)
|
|
115
|
-
openai.api_key = self.config[
|
|
130
|
+
openai.api_key = self.config["NEUROSYMBOLIC_ENGINE_API_KEY"]
|
|
116
131
|
|
|
117
132
|
callback = self.client.chat.completions.create
|
|
118
|
-
kwargs[
|
|
133
|
+
kwargs["model"] = (
|
|
134
|
+
self._handle_prefix(kwargs["model"])
|
|
135
|
+
if "model" in kwargs
|
|
136
|
+
else self._handle_prefix(self.model)
|
|
137
|
+
)
|
|
119
138
|
|
|
120
139
|
if except_remedy is not None:
|
|
121
140
|
res = except_remedy(self, e, callback, argument)
|
|
122
141
|
else:
|
|
123
|
-
UserMessage(f
|
|
142
|
+
UserMessage(f"Error during generation. Caused by: {e}", raise_with=ValueError)
|
|
124
143
|
|
|
125
|
-
metadata = {
|
|
126
|
-
if payload.get(
|
|
144
|
+
metadata = {"raw_output": res}
|
|
145
|
+
if payload.get("tools"):
|
|
127
146
|
metadata = self._process_function_calls(res, metadata)
|
|
128
147
|
|
|
129
148
|
output = [r.message.content for r in res.choices]
|
|
130
149
|
thinking, output = self._extract_thinking_content(output)
|
|
131
150
|
if thinking:
|
|
132
|
-
metadata[
|
|
151
|
+
metadata["thinking"] = thinking
|
|
133
152
|
|
|
134
153
|
return output, metadata
|
|
135
154
|
|
|
136
155
|
def _prepare_raw_input(self, argument):
|
|
137
156
|
if not argument.prop.processed_input:
|
|
138
|
-
UserMessage(
|
|
157
|
+
UserMessage(
|
|
158
|
+
"Need to provide a prompt instruction to the engine if raw_input is enabled.",
|
|
159
|
+
raise_with=ValueError,
|
|
160
|
+
)
|
|
139
161
|
value = argument.prop.processed_input
|
|
140
162
|
# convert to dict if not already
|
|
141
163
|
if not isinstance(value, list):
|
|
142
164
|
if not isinstance(value, dict):
|
|
143
|
-
value = {
|
|
165
|
+
value = {"role": "user", "content": str(value)}
|
|
144
166
|
value = [value]
|
|
145
167
|
return value
|
|
146
168
|
|
|
@@ -156,14 +178,14 @@ class GroqEngine(Engine):
|
|
|
156
178
|
system, user_prompt = self._apply_self_prompt_if_needed(argument, system, user_prompt)
|
|
157
179
|
|
|
158
180
|
argument.prop.prepared_input = [
|
|
159
|
-
{
|
|
181
|
+
{"role": "system", "content": system},
|
|
160
182
|
user_prompt,
|
|
161
183
|
]
|
|
162
184
|
|
|
163
185
|
def _validate_response_format(self, argument) -> None:
|
|
164
186
|
if argument.prop.response_format:
|
|
165
187
|
response_format = argument.prop.response_format
|
|
166
|
-
assert response_format.get(
|
|
188
|
+
assert response_format.get("type") is not None, (
|
|
167
189
|
'Expected format `{ "type": "json_object" }`! We are using the OpenAI compatible API for Groq. '
|
|
168
190
|
"See more here: https://console.groq.com/docs/tool-use"
|
|
169
191
|
)
|
|
@@ -206,36 +228,38 @@ class GroqEngine(Engine):
|
|
|
206
228
|
return str(argument.prop.processed_input)
|
|
207
229
|
|
|
208
230
|
def _apply_self_prompt_if_needed(self, argument, system, user_prompt):
|
|
209
|
-
if argument.prop.instance._kwargs.get(
|
|
231
|
+
if argument.prop.instance._kwargs.get("self_prompt", False) or argument.prop.self_prompt:
|
|
210
232
|
self_prompter = SelfPrompt()
|
|
211
|
-
res = self_prompter({
|
|
233
|
+
res = self_prompter({"user": user_prompt["content"], "system": system})
|
|
212
234
|
if res is None:
|
|
213
235
|
UserMessage("Self-prompting failed!", raise_with=ValueError)
|
|
214
|
-
return res[
|
|
236
|
+
return res["system"], {"role": "user", "content": res["user"]}
|
|
215
237
|
return system, user_prompt
|
|
216
238
|
|
|
217
239
|
def _process_function_calls(self, res, metadata):
|
|
218
240
|
hit = False
|
|
219
241
|
if (
|
|
220
|
-
hasattr(res,
|
|
242
|
+
hasattr(res, "choices")
|
|
221
243
|
and res.choices
|
|
222
|
-
and hasattr(res.choices[0],
|
|
244
|
+
and hasattr(res.choices[0], "message")
|
|
223
245
|
and res.choices[0].message
|
|
224
|
-
and hasattr(res.choices[0].message,
|
|
246
|
+
and hasattr(res.choices[0].message, "tool_calls")
|
|
225
247
|
and res.choices[0].message.tool_calls
|
|
226
248
|
):
|
|
227
249
|
for tool_call in res.choices[0].message.tool_calls:
|
|
228
|
-
if hasattr(tool_call,
|
|
250
|
+
if hasattr(tool_call, "function") and tool_call.function:
|
|
229
251
|
if hit:
|
|
230
|
-
UserMessage(
|
|
252
|
+
UserMessage(
|
|
253
|
+
"Multiple function calls detected in the response but only the first one will be processed."
|
|
254
|
+
)
|
|
231
255
|
break
|
|
232
256
|
try:
|
|
233
257
|
args_dict = json.loads(tool_call.function.arguments)
|
|
234
258
|
except json.JSONDecodeError:
|
|
235
259
|
args_dict = {}
|
|
236
|
-
metadata[
|
|
237
|
-
|
|
238
|
-
|
|
260
|
+
metadata["function_call"] = {
|
|
261
|
+
"name": tool_call.function.name,
|
|
262
|
+
"arguments": args_dict,
|
|
239
263
|
}
|
|
240
264
|
hit = True
|
|
241
265
|
return metadata
|
|
@@ -253,43 +277,53 @@ class GroqEngine(Engine):
|
|
|
253
277
|
"stream_options",
|
|
254
278
|
]:
|
|
255
279
|
if param in kwargs:
|
|
256
|
-
UserMessage(
|
|
280
|
+
UserMessage(
|
|
281
|
+
f"The parameter {param} is not supported by the Groq API. It will be ignored."
|
|
282
|
+
)
|
|
257
283
|
del kwargs[param]
|
|
258
284
|
|
|
259
|
-
n = kwargs.get(
|
|
285
|
+
n = kwargs.get("n", 1)
|
|
260
286
|
if n > 1:
|
|
261
|
-
UserMessage(
|
|
287
|
+
UserMessage(
|
|
288
|
+
"If N is supplied, it must be equal to 1. We default to 1 to not crash your program."
|
|
289
|
+
)
|
|
262
290
|
n = 1
|
|
263
291
|
|
|
264
292
|
# Handle Groq JSON-mode quirk: JSON Object Mode internally uses a constrainer tool.
|
|
265
|
-
response_format = kwargs.get(
|
|
266
|
-
tool_choice = kwargs.get(
|
|
267
|
-
tools = kwargs.get(
|
|
268
|
-
if
|
|
269
|
-
|
|
270
|
-
|
|
293
|
+
response_format = kwargs.get("response_format")
|
|
294
|
+
tool_choice = kwargs.get("tool_choice", "auto" if kwargs.get("tools") else "none")
|
|
295
|
+
tools = kwargs.get("tools")
|
|
296
|
+
if (
|
|
297
|
+
response_format
|
|
298
|
+
and isinstance(response_format, dict)
|
|
299
|
+
and response_format.get("type") == "json_object"
|
|
300
|
+
):
|
|
301
|
+
if tool_choice in (None, "none"):
|
|
302
|
+
tool_choice = "auto"
|
|
271
303
|
if tools:
|
|
272
304
|
tools = None
|
|
273
305
|
|
|
274
306
|
payload = {
|
|
275
307
|
"messages": messages,
|
|
276
|
-
"model": self._handle_prefix(kwargs.get(
|
|
277
|
-
"seed": kwargs.get(
|
|
278
|
-
"max_completion_tokens": kwargs.get(
|
|
279
|
-
"stop": kwargs.get(
|
|
280
|
-
"temperature": kwargs.get(
|
|
281
|
-
"frequency_penalty": kwargs.get(
|
|
282
|
-
"presence_penalty": kwargs.get(
|
|
283
|
-
"reasoning_effort": kwargs.get(
|
|
284
|
-
"service_tier": kwargs.get(
|
|
285
|
-
"top_p": kwargs.get(
|
|
308
|
+
"model": self._handle_prefix(kwargs.get("model", self.model)),
|
|
309
|
+
"seed": kwargs.get("seed", self.seed),
|
|
310
|
+
"max_completion_tokens": kwargs.get("max_completion_tokens"),
|
|
311
|
+
"stop": kwargs.get("stop"),
|
|
312
|
+
"temperature": kwargs.get("temperature", 1), # Default temperature for gpt-oss-120b
|
|
313
|
+
"frequency_penalty": kwargs.get("frequency_penalty", 0),
|
|
314
|
+
"presence_penalty": kwargs.get("presence_penalty", 0),
|
|
315
|
+
"reasoning_effort": kwargs.get("reasoning_effort"),
|
|
316
|
+
"service_tier": kwargs.get("service_tier", "on_demand"),
|
|
317
|
+
"top_p": kwargs.get("top_p", 1),
|
|
286
318
|
"n": n,
|
|
287
319
|
"tools": tools,
|
|
288
320
|
"tool_choice": tool_choice,
|
|
289
321
|
"response_format": response_format,
|
|
290
322
|
}
|
|
291
323
|
|
|
292
|
-
if not self._handle_prefix(self.model).startswith(
|
|
293
|
-
|
|
324
|
+
if not self._handle_prefix(self.model).startswith("qwen") or not self._handle_prefix(
|
|
325
|
+
self.model
|
|
326
|
+
).startswith("openai"):
|
|
327
|
+
del payload["reasoning_effort"]
|
|
294
328
|
|
|
295
329
|
return payload
|
|
@@ -18,31 +18,41 @@ class HFTokenizer:
|
|
|
18
18
|
|
|
19
19
|
@staticmethod
|
|
20
20
|
def encode(text: str, add_special_tokens: bool = False) -> list[int]:
|
|
21
|
-
res = requests.post(
|
|
22
|
-
"
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
res = requests.post(
|
|
22
|
+
f"{HFTokenizer._server_endpoint}/tokenize",
|
|
23
|
+
json={
|
|
24
|
+
"input": text,
|
|
25
|
+
"add_special_tokens": add_special_tokens,
|
|
26
|
+
},
|
|
27
|
+
)
|
|
25
28
|
|
|
26
29
|
if res.status_code != 200:
|
|
27
|
-
UserMessage(
|
|
30
|
+
UserMessage(
|
|
31
|
+
f"Request failed with status code: {res.status_code}", raise_with=ValueError
|
|
32
|
+
)
|
|
28
33
|
|
|
29
34
|
res = res.json()
|
|
30
35
|
|
|
31
|
-
return res[
|
|
36
|
+
return res["tokens"]
|
|
32
37
|
|
|
33
38
|
@staticmethod
|
|
34
39
|
def decode(tokens: list[int], skip_special_tokens: bool = True) -> str:
|
|
35
|
-
res = requests.post(
|
|
36
|
-
"
|
|
37
|
-
|
|
38
|
-
|
|
40
|
+
res = requests.post(
|
|
41
|
+
f"{HFTokenizer._server_endpoint}/detokenize",
|
|
42
|
+
json={
|
|
43
|
+
"tokens": tokens,
|
|
44
|
+
"skip_special_tokens": skip_special_tokens,
|
|
45
|
+
},
|
|
46
|
+
)
|
|
39
47
|
|
|
40
48
|
if res.status_code != 200:
|
|
41
|
-
UserMessage(
|
|
49
|
+
UserMessage(
|
|
50
|
+
f"Request failed with status code: {res.status_code}", raise_with=ValueError
|
|
51
|
+
)
|
|
42
52
|
|
|
43
53
|
res = res.json()
|
|
44
54
|
|
|
45
|
-
return res[
|
|
55
|
+
return res["text"]
|
|
46
56
|
|
|
47
57
|
|
|
48
58
|
class HFEngine(Engine):
|
|
@@ -51,70 +61,90 @@ class HFEngine(Engine):
|
|
|
51
61
|
self.config = deepcopy(SYMAI_CONFIG)
|
|
52
62
|
# In case we use EngineRepository.register to inject the api_key and model => dynamically change the engine at runtime
|
|
53
63
|
if model is not None:
|
|
54
|
-
self.config[
|
|
55
|
-
if self.id() !=
|
|
64
|
+
self.config["NEUROSYMBOLIC_ENGINE_MODEL"] = model
|
|
65
|
+
if self.id() != "neurosymbolic":
|
|
56
66
|
return
|
|
57
|
-
if not SYMSERVER_CONFIG.get(
|
|
58
|
-
UserMessage(
|
|
59
|
-
|
|
60
|
-
|
|
67
|
+
if not SYMSERVER_CONFIG.get("online"):
|
|
68
|
+
UserMessage(
|
|
69
|
+
"You are using the huggingface engine, but the server endpoint is not started. Please start the server with `symserver [--args]` or run `symserver --help` to see the available options for this engine.",
|
|
70
|
+
raise_with=ValueError,
|
|
71
|
+
)
|
|
72
|
+
self.server_endpoint = (
|
|
73
|
+
f"http://{SYMSERVER_CONFIG.get('host')}:{SYMSERVER_CONFIG.get('port')}"
|
|
74
|
+
)
|
|
75
|
+
self.tokenizer = HFTokenizer # backwards compatibility with how we handle tokenization, i.e. self.tokenizer().encode(...)
|
|
61
76
|
self.name = self.__class__.__name__
|
|
62
77
|
|
|
63
78
|
def id(self) -> str:
|
|
64
|
-
if self.config.get(
|
|
65
|
-
|
|
66
|
-
|
|
79
|
+
if self.config.get("NEUROSYMBOLIC_ENGINE_MODEL") and self.config.get(
|
|
80
|
+
"NEUROSYMBOLIC_ENGINE_MODEL"
|
|
81
|
+
).startswith("huggingface"):
|
|
82
|
+
return "neurosymbolic"
|
|
83
|
+
return super().id() # default to unregistered
|
|
67
84
|
|
|
68
85
|
def command(self, *args, **kwargs):
|
|
69
86
|
super().command(*args, **kwargs)
|
|
70
|
-
if
|
|
71
|
-
self.model = kwargs[
|
|
72
|
-
if
|
|
73
|
-
self.seed = kwargs[
|
|
74
|
-
if
|
|
75
|
-
self.except_remedy = kwargs[
|
|
87
|
+
if "NEUROSYMBOLIC_ENGINE_MODEL" in kwargs:
|
|
88
|
+
self.model = kwargs["NEUROSYMBOLIC_ENGINE_MODEL"]
|
|
89
|
+
if "seed" in kwargs:
|
|
90
|
+
self.seed = kwargs["seed"]
|
|
91
|
+
if "except_remedy" in kwargs:
|
|
92
|
+
self.except_remedy = kwargs["except_remedy"]
|
|
76
93
|
|
|
77
94
|
def compute_required_tokens(self, _messages) -> int:
|
|
78
|
-
UserMessage(
|
|
95
|
+
UserMessage(
|
|
96
|
+
"Not implemented for HFEngine. Please use the tokenizer directly to compute tokens.",
|
|
97
|
+
raise_with=NotImplementedError,
|
|
98
|
+
)
|
|
99
|
+
|
|
79
100
|
def compute_remaining_tokens(self, _prompts: list) -> int:
|
|
80
|
-
UserMessage(
|
|
101
|
+
UserMessage(
|
|
102
|
+
"Not implemented for HFEngine. Please use the tokenizer directly to compute tokens.",
|
|
103
|
+
raise_with=NotImplementedError,
|
|
104
|
+
)
|
|
105
|
+
|
|
81
106
|
def forward(self, argument):
|
|
82
|
-
kwargs
|
|
107
|
+
kwargs = argument.kwargs
|
|
83
108
|
prompts = argument.prop.prepared_input
|
|
84
109
|
|
|
85
|
-
stop
|
|
86
|
-
seed
|
|
87
|
-
temperature
|
|
88
|
-
top_p
|
|
89
|
-
top_k
|
|
90
|
-
max_tokens
|
|
91
|
-
max_tokens_forcing = kwargs.get(
|
|
92
|
-
logprobs
|
|
93
|
-
do_sample
|
|
94
|
-
num_beams
|
|
95
|
-
num_beam_groups
|
|
96
|
-
eos_token_id
|
|
97
|
-
except_remedy
|
|
110
|
+
stop = kwargs.get("stop")
|
|
111
|
+
seed = kwargs.get("seed")
|
|
112
|
+
temperature = kwargs.get("temperature", 1.0)
|
|
113
|
+
top_p = kwargs.get("top_p", 1.0)
|
|
114
|
+
top_k = kwargs.get("top_k", 50)
|
|
115
|
+
max_tokens = kwargs.get("max_tokens", 2048)
|
|
116
|
+
max_tokens_forcing = kwargs.get("max_tokens_forcing")
|
|
117
|
+
logprobs = kwargs.get("logprobs", True)
|
|
118
|
+
do_sample = kwargs.get("do_sample", True)
|
|
119
|
+
num_beams = kwargs.get("num_beams", 1)
|
|
120
|
+
num_beam_groups = kwargs.get("num_beam_groups", 1)
|
|
121
|
+
eos_token_id = kwargs.get("eos_token_id")
|
|
122
|
+
except_remedy = kwargs.get("except_remedy")
|
|
98
123
|
|
|
99
124
|
try:
|
|
100
|
-
res = requests.post(
|
|
101
|
-
"
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
125
|
+
res = requests.post(
|
|
126
|
+
f"{self.server_endpoint}/chat",
|
|
127
|
+
json={
|
|
128
|
+
"messages": prompts,
|
|
129
|
+
"temperature": temperature,
|
|
130
|
+
"top_p": top_p,
|
|
131
|
+
"stop": stop,
|
|
132
|
+
"seed": seed,
|
|
133
|
+
"max_tokens": max_tokens,
|
|
134
|
+
"max_tokens_forcing": max_tokens_forcing,
|
|
135
|
+
"top_k": top_k,
|
|
136
|
+
"logprobs": logprobs,
|
|
137
|
+
"do_sample": do_sample,
|
|
138
|
+
"num_beams": num_beams,
|
|
139
|
+
"num_beam_groups": num_beam_groups,
|
|
140
|
+
"eos_token_id": eos_token_id,
|
|
141
|
+
},
|
|
142
|
+
)
|
|
115
143
|
|
|
116
144
|
if res.status_code != 200:
|
|
117
|
-
UserMessage(
|
|
145
|
+
UserMessage(
|
|
146
|
+
f"Request failed with status code: {res.status_code}", raise_with=ValueError
|
|
147
|
+
)
|
|
118
148
|
|
|
119
149
|
res = res.json()
|
|
120
150
|
|
|
@@ -122,21 +152,24 @@ class HFEngine(Engine):
|
|
|
122
152
|
if except_remedy is not None:
|
|
123
153
|
res = except_remedy(self, e, argument)
|
|
124
154
|
else:
|
|
125
|
-
UserMessage(f
|
|
155
|
+
UserMessage(f"Error during generation. Caused by: {e}", raise_with=ValueError)
|
|
126
156
|
|
|
127
|
-
metadata = {
|
|
157
|
+
metadata = {"raw_output": res}
|
|
128
158
|
|
|
129
|
-
rsp
|
|
159
|
+
rsp = [r["message"]["content"] for r in res["choices"]]
|
|
130
160
|
output = rsp if isinstance(prompts, list) else rsp[0]
|
|
131
161
|
return output, metadata
|
|
132
162
|
|
|
133
163
|
def _prepare_raw_input(self, argument):
|
|
134
164
|
if not argument.prop.processed_input:
|
|
135
|
-
UserMessage(
|
|
165
|
+
UserMessage(
|
|
166
|
+
"Need to provide a prompt instruction to the engine if raw_input is enabled.",
|
|
167
|
+
raise_with=ValueError,
|
|
168
|
+
)
|
|
136
169
|
value = argument.prop.processed_input
|
|
137
170
|
if not isinstance(value, list):
|
|
138
171
|
if not isinstance(value, dict):
|
|
139
|
-
value = {
|
|
172
|
+
value = {"role": "user", "content": str(value)}
|
|
140
173
|
value = [value]
|
|
141
174
|
return value
|
|
142
175
|
|
|
@@ -147,12 +180,12 @@ class HFEngine(Engine):
|
|
|
147
180
|
|
|
148
181
|
_non_verbose_output = """<META_INSTRUCTION/>\n You will NOT output verbose preambles or post explanation, such as "Sure, let me...", "Hope that was helpful...", "Yes, I can help you with that...", etc. You will consider well formatted output, e.g. for sentences you will use punctuation, spaces, etc. or for code indentation, etc.\n"""
|
|
149
182
|
|
|
150
|
-
|
|
183
|
+
# @TODO: Non-trivial how to handle user/system/assistant roles;
|
|
151
184
|
user = ""
|
|
152
185
|
|
|
153
186
|
if argument.prop.suppress_verbose_output:
|
|
154
187
|
user += _non_verbose_output
|
|
155
|
-
user = f
|
|
188
|
+
user = f"{user}\n" if user and len(user) > 0 else ""
|
|
156
189
|
|
|
157
190
|
ref = argument.prop.instance
|
|
158
191
|
static_ctxt, dyn_ctxt = ref.global_context
|
|
@@ -179,5 +212,5 @@ class HFEngine(Engine):
|
|
|
179
212
|
user += str(argument.prop.processed_input)
|
|
180
213
|
|
|
181
214
|
argument.prop.prepared_input = [
|
|
182
|
-
{
|
|
215
|
+
{"role": "user", "content": user},
|
|
183
216
|
]
|