symbolicai 0.21.0__py3-none-any.whl → 1.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- symai/__init__.py +269 -173
- symai/backend/base.py +123 -110
- symai/backend/engines/drawing/engine_bfl.py +45 -44
- symai/backend/engines/drawing/engine_gpt_image.py +112 -97
- symai/backend/engines/embedding/engine_llama_cpp.py +63 -52
- symai/backend/engines/embedding/engine_openai.py +25 -21
- symai/backend/engines/execute/engine_python.py +19 -18
- symai/backend/engines/files/engine_io.py +104 -95
- symai/backend/engines/imagecaptioning/engine_blip2.py +28 -24
- symai/backend/engines/imagecaptioning/engine_llavacpp_client.py +102 -79
- symai/backend/engines/index/engine_pinecone.py +124 -97
- symai/backend/engines/index/engine_qdrant.py +1011 -0
- symai/backend/engines/index/engine_vectordb.py +84 -56
- symai/backend/engines/lean/engine_lean4.py +96 -52
- symai/backend/engines/neurosymbolic/__init__.py +41 -13
- symai/backend/engines/neurosymbolic/engine_anthropic_claudeX_chat.py +330 -248
- symai/backend/engines/neurosymbolic/engine_anthropic_claudeX_reasoning.py +329 -264
- symai/backend/engines/neurosymbolic/engine_cerebras.py +328 -0
- symai/backend/engines/neurosymbolic/engine_deepseekX_reasoning.py +118 -88
- symai/backend/engines/neurosymbolic/engine_google_geminiX_reasoning.py +344 -299
- symai/backend/engines/neurosymbolic/engine_groq.py +173 -115
- symai/backend/engines/neurosymbolic/engine_huggingface.py +114 -84
- symai/backend/engines/neurosymbolic/engine_llama_cpp.py +144 -118
- symai/backend/engines/neurosymbolic/engine_openai_gptX_chat.py +415 -307
- symai/backend/engines/neurosymbolic/engine_openai_gptX_reasoning.py +394 -231
- symai/backend/engines/ocr/engine_apilayer.py +23 -27
- symai/backend/engines/output/engine_stdout.py +10 -13
- symai/backend/engines/{webscraping → scrape}/engine_requests.py +101 -54
- symai/backend/engines/search/engine_openai.py +100 -88
- symai/backend/engines/search/engine_parallel.py +665 -0
- symai/backend/engines/search/engine_perplexity.py +44 -45
- symai/backend/engines/search/engine_serpapi.py +37 -34
- symai/backend/engines/speech_to_text/engine_local_whisper.py +54 -51
- symai/backend/engines/symbolic/engine_wolframalpha.py +15 -9
- symai/backend/engines/text_to_speech/engine_openai.py +20 -26
- symai/backend/engines/text_vision/engine_clip.py +39 -37
- symai/backend/engines/userinput/engine_console.py +5 -6
- symai/backend/mixin/__init__.py +13 -0
- symai/backend/mixin/anthropic.py +48 -38
- symai/backend/mixin/deepseek.py +6 -5
- symai/backend/mixin/google.py +7 -4
- symai/backend/mixin/groq.py +2 -4
- symai/backend/mixin/openai.py +140 -110
- symai/backend/settings.py +87 -20
- symai/chat.py +216 -123
- symai/collect/__init__.py +7 -1
- symai/collect/dynamic.py +80 -70
- symai/collect/pipeline.py +67 -51
- symai/collect/stats.py +161 -109
- symai/components.py +707 -360
- symai/constraints.py +24 -12
- symai/core.py +1857 -1233
- symai/core_ext.py +83 -80
- symai/endpoints/api.py +166 -104
- symai/extended/.DS_Store +0 -0
- symai/extended/__init__.py +46 -12
- symai/extended/api_builder.py +29 -21
- symai/extended/arxiv_pdf_parser.py +23 -14
- symai/extended/bibtex_parser.py +9 -6
- symai/extended/conversation.py +156 -126
- symai/extended/document.py +50 -30
- symai/extended/file_merger.py +57 -14
- symai/extended/graph.py +51 -32
- symai/extended/html_style_template.py +18 -14
- symai/extended/interfaces/blip_2.py +2 -3
- symai/extended/interfaces/clip.py +4 -3
- symai/extended/interfaces/console.py +9 -1
- symai/extended/interfaces/dall_e.py +4 -2
- symai/extended/interfaces/file.py +2 -0
- symai/extended/interfaces/flux.py +4 -2
- symai/extended/interfaces/gpt_image.py +16 -7
- symai/extended/interfaces/input.py +2 -1
- symai/extended/interfaces/llava.py +1 -2
- symai/extended/interfaces/{naive_webscraping.py → naive_scrape.py} +4 -3
- symai/extended/interfaces/naive_vectordb.py +9 -10
- symai/extended/interfaces/ocr.py +5 -3
- symai/extended/interfaces/openai_search.py +2 -0
- symai/extended/interfaces/parallel.py +30 -0
- symai/extended/interfaces/perplexity.py +2 -0
- symai/extended/interfaces/pinecone.py +12 -9
- symai/extended/interfaces/python.py +2 -0
- symai/extended/interfaces/serpapi.py +3 -1
- symai/extended/interfaces/terminal.py +2 -4
- symai/extended/interfaces/tts.py +3 -2
- symai/extended/interfaces/whisper.py +3 -2
- symai/extended/interfaces/wolframalpha.py +2 -1
- symai/extended/metrics/__init__.py +11 -1
- symai/extended/metrics/similarity.py +14 -13
- symai/extended/os_command.py +39 -29
- symai/extended/packages/__init__.py +29 -3
- symai/extended/packages/symdev.py +51 -43
- symai/extended/packages/sympkg.py +41 -35
- symai/extended/packages/symrun.py +63 -50
- symai/extended/repo_cloner.py +14 -12
- symai/extended/seo_query_optimizer.py +15 -13
- symai/extended/solver.py +116 -91
- symai/extended/summarizer.py +12 -10
- symai/extended/taypan_interpreter.py +17 -18
- symai/extended/vectordb.py +122 -92
- symai/formatter/__init__.py +9 -1
- symai/formatter/formatter.py +51 -47
- symai/formatter/regex.py +70 -69
- symai/functional.py +325 -176
- symai/imports.py +190 -147
- symai/interfaces.py +57 -28
- symai/memory.py +45 -35
- symai/menu/screen.py +28 -19
- symai/misc/console.py +66 -56
- symai/misc/loader.py +8 -5
- symai/models/__init__.py +17 -1
- symai/models/base.py +395 -236
- symai/models/errors.py +1 -2
- symai/ops/__init__.py +32 -22
- symai/ops/measures.py +24 -25
- symai/ops/primitives.py +1149 -731
- symai/post_processors.py +58 -50
- symai/pre_processors.py +86 -82
- symai/processor.py +21 -13
- symai/prompts.py +764 -685
- symai/server/huggingface_server.py +135 -49
- symai/server/llama_cpp_server.py +21 -11
- symai/server/qdrant_server.py +206 -0
- symai/shell.py +100 -42
- symai/shellsv.py +700 -492
- symai/strategy.py +630 -346
- symai/symbol.py +368 -322
- symai/utils.py +100 -78
- {symbolicai-0.21.0.dist-info → symbolicai-1.1.0.dist-info}/METADATA +22 -10
- symbolicai-1.1.0.dist-info/RECORD +168 -0
- symbolicai-0.21.0.dist-info/RECORD +0 -162
- {symbolicai-0.21.0.dist-info → symbolicai-1.1.0.dist-info}/WHEEL +0 -0
- {symbolicai-0.21.0.dist-info → symbolicai-1.1.0.dist-info}/entry_points.txt +0 -0
- {symbolicai-0.21.0.dist-info → symbolicai-1.1.0.dist-info}/licenses/LICENSE +0 -0
- {symbolicai-0.21.0.dist-info → symbolicai-1.1.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
import re
|
|
4
|
+
from copy import deepcopy
|
|
5
|
+
|
|
6
|
+
from cerebras.cloud.sdk import Cerebras
|
|
7
|
+
|
|
8
|
+
from ....components import SelfPrompt
|
|
9
|
+
from ....core_ext import retry
|
|
10
|
+
from ....utils import UserMessage
|
|
11
|
+
from ...base import Engine
|
|
12
|
+
from ...settings import SYMAI_CONFIG
|
|
13
|
+
|
|
14
|
+
logging.getLogger("cerebras").setLevel(logging.ERROR)
|
|
15
|
+
logging.getLogger("requests").setLevel(logging.ERROR)
|
|
16
|
+
logging.getLogger("urllib").setLevel(logging.ERROR)
|
|
17
|
+
logging.getLogger("httpx").setLevel(logging.ERROR)
|
|
18
|
+
logging.getLogger("httpcore").setLevel(logging.ERROR)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
_NON_VERBOSE_OUTPUT = (
|
|
22
|
+
"<META_INSTRUCTION/>\n"
|
|
23
|
+
"You do not output anything else, like verbose preambles or post explanation, such as "
|
|
24
|
+
'"Sure, let me...", "Hope that was helpful...", "Yes, I can help you with that...", etc. '
|
|
25
|
+
"Consider well formatted output, e.g. for sentences use punctuation, spaces etc. or for code use "
|
|
26
|
+
"indentation, etc. Never add meta instructions information to your output!\n\n"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class CerebrasEngine(Engine):
|
|
31
|
+
def __init__(self, api_key: str | None = None, model: str | None = None):
|
|
32
|
+
super().__init__()
|
|
33
|
+
self.config = deepcopy(SYMAI_CONFIG)
|
|
34
|
+
# In case we use EngineRepository.register to inject the api_key and model => dynamically change the engine at runtime
|
|
35
|
+
if api_key is not None and model is not None:
|
|
36
|
+
self.config["NEUROSYMBOLIC_ENGINE_API_KEY"] = api_key
|
|
37
|
+
self.config["NEUROSYMBOLIC_ENGINE_MODEL"] = model
|
|
38
|
+
if self.id() != "neurosymbolic":
|
|
39
|
+
# Do not initialize if not neurosymbolic; avoids conflict with llama.cpp check in
|
|
40
|
+
# EngineRepository.register_from_package.
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
self.api_key = self.config["NEUROSYMBOLIC_ENGINE_API_KEY"]
|
|
44
|
+
self.model = self.config["NEUROSYMBOLIC_ENGINE_MODEL"]
|
|
45
|
+
self.seed = None
|
|
46
|
+
self.name = self.__class__.__name__
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
self.client = Cerebras(api_key=self.api_key)
|
|
50
|
+
except Exception as exc:
|
|
51
|
+
UserMessage(
|
|
52
|
+
f"Failed to initialize Cerebras client. Please check your Cerebras SDK installation. Caused by: {exc}",
|
|
53
|
+
raise_with=ValueError,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
def id(self) -> str:
|
|
57
|
+
model_name = self.config.get("NEUROSYMBOLIC_ENGINE_MODEL")
|
|
58
|
+
if model_name and model_name.startswith("cerebras"):
|
|
59
|
+
return "neurosymbolic"
|
|
60
|
+
return super().id()
|
|
61
|
+
|
|
62
|
+
def command(self, *args, **kwargs):
|
|
63
|
+
super().command(*args, **kwargs)
|
|
64
|
+
if "NEUROSYMBOLIC_ENGINE_API_KEY" in kwargs:
|
|
65
|
+
self.api_key = kwargs["NEUROSYMBOLIC_ENGINE_API_KEY"]
|
|
66
|
+
try:
|
|
67
|
+
self.client = Cerebras(api_key=self.api_key)
|
|
68
|
+
except Exception as exc:
|
|
69
|
+
UserMessage(
|
|
70
|
+
f"Failed to reinitialize Cerebras client. Caused by: {exc}",
|
|
71
|
+
raise_with=ValueError,
|
|
72
|
+
)
|
|
73
|
+
if "NEUROSYMBOLIC_ENGINE_MODEL" in kwargs:
|
|
74
|
+
self.model = kwargs["NEUROSYMBOLIC_ENGINE_MODEL"]
|
|
75
|
+
if "seed" in kwargs:
|
|
76
|
+
self.seed = kwargs["seed"]
|
|
77
|
+
|
|
78
|
+
def compute_required_tokens(self, _messages):
|
|
79
|
+
UserMessage(
|
|
80
|
+
"Token counting not implemented for this engine.", raise_with=NotImplementedError
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
def compute_remaining_tokens(self, _prompts: list) -> int:
|
|
84
|
+
UserMessage(
|
|
85
|
+
"Token counting not implemented for this engine.", raise_with=NotImplementedError
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
def _handle_prefix(self, model_name: str) -> str:
|
|
89
|
+
"""Handle prefix for model name."""
|
|
90
|
+
return model_name.replace("cerebras:", "")
|
|
91
|
+
|
|
92
|
+
def _extract_thinking_content(self, outputs: list[str]) -> tuple[str | None, list[str]]:
|
|
93
|
+
"""Extract thinking content from textual output using <think>...</think> tags if present."""
|
|
94
|
+
if not outputs:
|
|
95
|
+
return None, outputs
|
|
96
|
+
|
|
97
|
+
content = outputs[0]
|
|
98
|
+
if not content:
|
|
99
|
+
return None, outputs
|
|
100
|
+
|
|
101
|
+
# This regular expression matches a <think>...</think> block and captures any content between the tags,
|
|
102
|
+
# including newlines, so that we can separate internal reasoning text from the user-facing answer.
|
|
103
|
+
think_pattern = r"<think>(.*?)</think>"
|
|
104
|
+
match = re.search(think_pattern, content, re.DOTALL)
|
|
105
|
+
|
|
106
|
+
thinking_content = None
|
|
107
|
+
if match:
|
|
108
|
+
thinking_content = match.group(1).strip() or None
|
|
109
|
+
|
|
110
|
+
cleaned_content = re.sub(think_pattern, "", content, flags=re.DOTALL).strip()
|
|
111
|
+
cleaned_outputs = [cleaned_content, *outputs[1:]]
|
|
112
|
+
|
|
113
|
+
return thinking_content, cleaned_outputs
|
|
114
|
+
|
|
115
|
+
# cumulative wait time is < 30s
|
|
116
|
+
@retry(tries=8, delay=0.5, backoff=1.5, max_delay=5, jitter=(0, 0.5))
|
|
117
|
+
def forward(self, argument):
|
|
118
|
+
kwargs = argument.kwargs
|
|
119
|
+
messages = argument.prop.prepared_input
|
|
120
|
+
payload = self._prepare_request_payload(messages, argument)
|
|
121
|
+
except_remedy = kwargs.get("except_remedy")
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
res = self.client.chat.completions.create(**payload)
|
|
125
|
+
except Exception as exc: # pragma: no cover - defensive path
|
|
126
|
+
res = self._handle_forward_exception(exc, argument, kwargs, except_remedy)
|
|
127
|
+
|
|
128
|
+
return self._build_outputs_and_metadata(res, payload)
|
|
129
|
+
|
|
130
|
+
def _handle_forward_exception(self, exc, argument, kwargs, except_remedy):
|
|
131
|
+
if self.api_key is None or self.api_key == "":
|
|
132
|
+
msg = (
|
|
133
|
+
"Cerebras API key is not set. Please set it in the config file or "
|
|
134
|
+
"pass it as an argument to the command method."
|
|
135
|
+
)
|
|
136
|
+
UserMessage(msg)
|
|
137
|
+
config_key = self.config.get("NEUROSYMBOLIC_ENGINE_API_KEY")
|
|
138
|
+
if config_key is None or config_key == "":
|
|
139
|
+
UserMessage(msg, raise_with=ValueError)
|
|
140
|
+
self.api_key = config_key
|
|
141
|
+
try:
|
|
142
|
+
self.client = Cerebras(api_key=self.api_key)
|
|
143
|
+
except Exception as inner_exc:
|
|
144
|
+
UserMessage(
|
|
145
|
+
f"Failed to initialize Cerebras client after missing API key. Caused by: {inner_exc}",
|
|
146
|
+
raise_with=ValueError,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
callback = self.client.chat.completions.create
|
|
150
|
+
kwargs["model"] = (
|
|
151
|
+
self._handle_prefix(kwargs["model"])
|
|
152
|
+
if "model" in kwargs
|
|
153
|
+
else self._handle_prefix(self.model)
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
if except_remedy is not None:
|
|
157
|
+
return except_remedy(self, exc, callback, argument)
|
|
158
|
+
|
|
159
|
+
UserMessage(f"Error during generation. Caused by: {exc}", raise_with=ValueError)
|
|
160
|
+
return None
|
|
161
|
+
|
|
162
|
+
def _build_outputs_and_metadata(self, res, payload):
|
|
163
|
+
metadata: dict = {"raw_output": res}
|
|
164
|
+
if payload.get("tools"):
|
|
165
|
+
metadata = self._process_function_calls(res, metadata)
|
|
166
|
+
|
|
167
|
+
outputs: list[str] = []
|
|
168
|
+
thinking_content: str | None = None
|
|
169
|
+
|
|
170
|
+
for choice in res.choices:
|
|
171
|
+
message = choice.message
|
|
172
|
+
outputs.append(getattr(message, "content", "") or "")
|
|
173
|
+
if thinking_content is None:
|
|
174
|
+
reasoning = getattr(message, "reasoning", None)
|
|
175
|
+
if reasoning:
|
|
176
|
+
thinking_content = reasoning
|
|
177
|
+
|
|
178
|
+
if thinking_content is None:
|
|
179
|
+
thinking_content, outputs = self._extract_thinking_content(outputs)
|
|
180
|
+
else:
|
|
181
|
+
_, outputs = self._extract_thinking_content(outputs)
|
|
182
|
+
|
|
183
|
+
if thinking_content:
|
|
184
|
+
metadata["thinking"] = thinking_content
|
|
185
|
+
|
|
186
|
+
return outputs, metadata
|
|
187
|
+
|
|
188
|
+
def _prepare_raw_input(self, argument):
|
|
189
|
+
if not argument.prop.processed_input:
|
|
190
|
+
UserMessage(
|
|
191
|
+
"Need to provide a prompt instruction to the engine if raw_input is enabled.",
|
|
192
|
+
raise_with=ValueError,
|
|
193
|
+
)
|
|
194
|
+
value = argument.prop.processed_input
|
|
195
|
+
if not isinstance(value, list):
|
|
196
|
+
if not isinstance(value, dict):
|
|
197
|
+
value = {"role": "user", "content": str(value)}
|
|
198
|
+
value = [value]
|
|
199
|
+
return value
|
|
200
|
+
|
|
201
|
+
def prepare(self, argument):
|
|
202
|
+
if argument.prop.raw_input:
|
|
203
|
+
argument.prop.prepared_input = self._prepare_raw_input(argument)
|
|
204
|
+
return
|
|
205
|
+
self._validate_response_format(argument)
|
|
206
|
+
|
|
207
|
+
system_message = self._build_system_message(argument)
|
|
208
|
+
user_content = self._build_user_content(argument)
|
|
209
|
+
user_prompt = {"role": "user", "content": user_content}
|
|
210
|
+
system_message, user_prompt = self._apply_self_prompt_if_needed(
|
|
211
|
+
argument, system_message, user_prompt
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
argument.prop.prepared_input = [
|
|
215
|
+
{"role": "system", "content": system_message},
|
|
216
|
+
user_prompt,
|
|
217
|
+
]
|
|
218
|
+
|
|
219
|
+
def _validate_response_format(self, argument) -> None:
|
|
220
|
+
if argument.prop.response_format:
|
|
221
|
+
response_format = argument.prop.response_format
|
|
222
|
+
assert response_format.get("type") is not None, (
|
|
223
|
+
'Expected format `{ "type": "json_object" }` for JSON mode. '
|
|
224
|
+
"See Cerebras structured outputs documentation for details."
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
def _build_system_message(self, argument) -> str:
|
|
228
|
+
system_message: str = ""
|
|
229
|
+
if argument.prop.suppress_verbose_output:
|
|
230
|
+
system_message += _NON_VERBOSE_OUTPUT
|
|
231
|
+
if system_message:
|
|
232
|
+
system_message = f"{system_message}\n"
|
|
233
|
+
|
|
234
|
+
ref = argument.prop.instance
|
|
235
|
+
static_context, dynamic_context = ref.global_context
|
|
236
|
+
if len(static_context) > 0:
|
|
237
|
+
system_message += f"<STATIC CONTEXT/>\n{static_context}\n\n"
|
|
238
|
+
|
|
239
|
+
if len(dynamic_context) > 0:
|
|
240
|
+
system_message += f"<DYNAMIC CONTEXT/>\n{dynamic_context}\n\n"
|
|
241
|
+
|
|
242
|
+
if argument.prop.payload:
|
|
243
|
+
system_message += f"<ADDITIONAL CONTEXT/>\n{argument.prop.payload!s}\n\n"
|
|
244
|
+
|
|
245
|
+
examples = argument.prop.examples
|
|
246
|
+
if examples and len(examples) > 0:
|
|
247
|
+
system_message += f"<EXAMPLES/>\n{examples!s}\n\n"
|
|
248
|
+
|
|
249
|
+
if argument.prop.prompt is not None and len(argument.prop.prompt) > 0:
|
|
250
|
+
prompt_value = str(argument.prop.prompt)
|
|
251
|
+
system_message += f"<INSTRUCTION/>\n{prompt_value}\n\n"
|
|
252
|
+
|
|
253
|
+
if argument.prop.template_suffix:
|
|
254
|
+
system_message += (
|
|
255
|
+
" You will only generate content for the placeholder "
|
|
256
|
+
f"`{argument.prop.template_suffix!s}` following the instructions and the provided context information.\n\n"
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
return system_message
|
|
260
|
+
|
|
261
|
+
def _build_user_content(self, argument) -> str:
|
|
262
|
+
return str(argument.prop.processed_input)
|
|
263
|
+
|
|
264
|
+
def _apply_self_prompt_if_needed(self, argument, system_message, user_prompt):
|
|
265
|
+
if argument.prop.instance._kwargs.get("self_prompt", False) or argument.prop.self_prompt:
|
|
266
|
+
self_prompter = SelfPrompt()
|
|
267
|
+
result = self_prompter({"user": user_prompt["content"], "system": system_message})
|
|
268
|
+
if result is None:
|
|
269
|
+
UserMessage("Self-prompting failed!", raise_with=ValueError)
|
|
270
|
+
return result["system"], {"role": "user", "content": result["user"]}
|
|
271
|
+
return system_message, user_prompt
|
|
272
|
+
|
|
273
|
+
def _process_function_calls(self, res, metadata):
|
|
274
|
+
hit = False
|
|
275
|
+
if (
|
|
276
|
+
hasattr(res, "choices")
|
|
277
|
+
and res.choices
|
|
278
|
+
and hasattr(res.choices[0], "message")
|
|
279
|
+
and res.choices[0].message
|
|
280
|
+
and hasattr(res.choices[0].message, "tool_calls")
|
|
281
|
+
and res.choices[0].message.tool_calls
|
|
282
|
+
):
|
|
283
|
+
for tool_call in res.choices[0].message.tool_calls:
|
|
284
|
+
if hasattr(tool_call, "function") and tool_call.function:
|
|
285
|
+
if hit:
|
|
286
|
+
UserMessage(
|
|
287
|
+
"Multiple function calls detected in the response but only the first one will be processed."
|
|
288
|
+
)
|
|
289
|
+
break
|
|
290
|
+
try:
|
|
291
|
+
args_dict = json.loads(tool_call.function.arguments)
|
|
292
|
+
except json.JSONDecodeError:
|
|
293
|
+
args_dict = {}
|
|
294
|
+
metadata["function_call"] = {
|
|
295
|
+
"name": tool_call.function.name,
|
|
296
|
+
"arguments": args_dict,
|
|
297
|
+
}
|
|
298
|
+
hit = True
|
|
299
|
+
return metadata
|
|
300
|
+
|
|
301
|
+
def _prepare_request_payload(self, messages, argument):
|
|
302
|
+
"""Prepares the request payload from the argument."""
|
|
303
|
+
kwargs = argument.kwargs
|
|
304
|
+
|
|
305
|
+
n = kwargs.get("n", 1)
|
|
306
|
+
if n > 1:
|
|
307
|
+
UserMessage(
|
|
308
|
+
"If N is supplied, it must be equal to 1. We default to 1 to avoid unexpected batch behavior."
|
|
309
|
+
)
|
|
310
|
+
n = 1
|
|
311
|
+
|
|
312
|
+
response_format = kwargs.get("response_format")
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
"messages": messages,
|
|
316
|
+
"model": self._handle_prefix(kwargs.get("model", self.model)),
|
|
317
|
+
"max_completion_tokens": kwargs.get("max_completion_tokens"),
|
|
318
|
+
"stop": kwargs.get("stop"),
|
|
319
|
+
"temperature": kwargs.get("temperature", 1),
|
|
320
|
+
"top_p": kwargs.get("top_p", 1),
|
|
321
|
+
"n": n,
|
|
322
|
+
"tools": kwargs.get("tools"),
|
|
323
|
+
"parallel_tool_calls": kwargs.get("parallel_tool_calls"),
|
|
324
|
+
"response_format": response_format,
|
|
325
|
+
"reasoning_effort": kwargs.get("reasoning_effort"),
|
|
326
|
+
"disable_reasoning": kwargs.get("disable_reasoning"),
|
|
327
|
+
"stream": kwargs.get("stream", False),
|
|
328
|
+
}
|
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
import re
|
|
3
2
|
from copy import deepcopy
|
|
4
|
-
from typing import List, Optional
|
|
5
3
|
|
|
6
|
-
from annotated_types import Not
|
|
7
4
|
from openai import OpenAI
|
|
8
5
|
|
|
9
6
|
from ....components import SelfPrompt
|
|
10
|
-
from ....
|
|
11
|
-
from ....symbol import Symbol
|
|
12
|
-
from ....utils import CustomUserWarning, encode_media_frames
|
|
7
|
+
from ....utils import UserMessage
|
|
13
8
|
from ...base import Engine
|
|
14
9
|
from ...mixin.deepseek import DeepSeekMixin
|
|
15
10
|
from ...settings import SYMAI_CONFIG
|
|
@@ -22,17 +17,17 @@ logging.getLogger("httpcore").setLevel(logging.ERROR)
|
|
|
22
17
|
|
|
23
18
|
|
|
24
19
|
class DeepSeekXReasoningEngine(Engine, DeepSeekMixin):
|
|
25
|
-
def __init__(self, api_key:
|
|
20
|
+
def __init__(self, api_key: str | None = None, model: str | None = None):
|
|
26
21
|
super().__init__()
|
|
27
22
|
self.config = deepcopy(SYMAI_CONFIG)
|
|
28
23
|
# In case we use EngineRepository.register to inject the api_key and model => dynamically change the engine at runtime
|
|
29
24
|
if api_key is not None and model is not None:
|
|
30
|
-
self.config[
|
|
31
|
-
self.config[
|
|
32
|
-
if self.id() !=
|
|
33
|
-
return
|
|
34
|
-
self.api_key = self.config[
|
|
35
|
-
self.model = self.config[
|
|
25
|
+
self.config["NEUROSYMBOLIC_ENGINE_API_KEY"] = api_key
|
|
26
|
+
self.config["NEUROSYMBOLIC_ENGINE_MODEL"] = model
|
|
27
|
+
if self.id() != "neurosymbolic":
|
|
28
|
+
return # do not initialize if not neurosymbolic; avoids conflict with llama.cpp check in EngineRepository.register_from_package
|
|
29
|
+
self.api_key = self.config["NEUROSYMBOLIC_ENGINE_API_KEY"]
|
|
30
|
+
self.model = self.config["NEUROSYMBOLIC_ENGINE_MODEL"]
|
|
36
31
|
self.name = self.__class__.__name__
|
|
37
32
|
self.tokenizer = None
|
|
38
33
|
self.max_context_tokens = self.api_max_context_tokens()
|
|
@@ -42,95 +37,115 @@ class DeepSeekXReasoningEngine(Engine, DeepSeekMixin):
|
|
|
42
37
|
try:
|
|
43
38
|
self.client = OpenAI(api_key=self.api_key, base_url="https://api.deepseek.com")
|
|
44
39
|
except Exception as e:
|
|
45
|
-
|
|
40
|
+
UserMessage(
|
|
41
|
+
f"Failed to initialize the DeepSeek client. Please check your library version. Caused by: {e}",
|
|
42
|
+
raise_with=RuntimeError,
|
|
43
|
+
)
|
|
46
44
|
|
|
47
45
|
def id(self) -> str:
|
|
48
|
-
if self.config.get(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
46
|
+
if self.config.get("NEUROSYMBOLIC_ENGINE_MODEL") and self.config.get(
|
|
47
|
+
"NEUROSYMBOLIC_ENGINE_MODEL"
|
|
48
|
+
).startswith("deepseek"):
|
|
49
|
+
return "neurosymbolic"
|
|
50
|
+
return super().id() # default to unregistered
|
|
52
51
|
|
|
53
52
|
def command(self, *args, **kwargs):
|
|
54
53
|
super().command(*args, **kwargs)
|
|
55
|
-
if
|
|
56
|
-
self.api_key = kwargs[
|
|
57
|
-
if
|
|
58
|
-
self.model = kwargs[
|
|
59
|
-
if
|
|
60
|
-
self.seed = kwargs[
|
|
61
|
-
|
|
62
|
-
def compute_required_tokens(self,
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
def
|
|
69
|
-
|
|
54
|
+
if "NEUROSYMBOLIC_ENGINE_API_KEY" in kwargs:
|
|
55
|
+
self.api_key = kwargs["NEUROSYMBOLIC_ENGINE_API_KEY"]
|
|
56
|
+
if "NEUROSYMBOLIC_ENGINE_MODEL" in kwargs:
|
|
57
|
+
self.model = kwargs["NEUROSYMBOLIC_ENGINE_MODEL"]
|
|
58
|
+
if "seed" in kwargs:
|
|
59
|
+
self.seed = kwargs["seed"]
|
|
60
|
+
|
|
61
|
+
def compute_required_tokens(self, _messages):
|
|
62
|
+
UserMessage(
|
|
63
|
+
'Method "compute_required_tokens" not implemented for DeepSeekXReasoningEngine.',
|
|
64
|
+
raise_with=NotImplementedError,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
def compute_remaining_tokens(self, _prompts: list) -> int:
|
|
68
|
+
UserMessage(
|
|
69
|
+
'Method "compute_remaining_tokens" not implemented for DeepSeekXReasoningEngine.',
|
|
70
|
+
raise_with=NotImplementedError,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
def truncate(
|
|
74
|
+
self, _prompts: list[dict], _truncation_percentage: float | None, _truncation_type: str
|
|
75
|
+
) -> list[dict]:
|
|
76
|
+
UserMessage(
|
|
77
|
+
'Method "truncate" not implemented for DeepSeekXReasoningEngine.',
|
|
78
|
+
raise_with=NotImplementedError,
|
|
79
|
+
)
|
|
70
80
|
|
|
71
81
|
def forward(self, argument):
|
|
72
82
|
kwargs = argument.kwargs
|
|
73
83
|
messages = argument.prop.prepared_input
|
|
74
84
|
payload = self._prepare_request_payload(argument)
|
|
75
|
-
except_remedy = kwargs.get(
|
|
85
|
+
except_remedy = kwargs.get("except_remedy")
|
|
76
86
|
|
|
77
87
|
try:
|
|
78
88
|
res = self.client.chat.completions.create(messages=messages, **payload)
|
|
79
89
|
|
|
80
90
|
except Exception as e:
|
|
81
|
-
if self.api_key is None or self.api_key ==
|
|
82
|
-
msg =
|
|
83
|
-
|
|
84
|
-
if
|
|
85
|
-
|
|
86
|
-
|
|
91
|
+
if self.api_key is None or self.api_key == "":
|
|
92
|
+
msg = "DeepSeek API key is not set. Please set it in the config file or pass it as an argument to the command method."
|
|
93
|
+
UserMessage(msg)
|
|
94
|
+
if (
|
|
95
|
+
self.config["NEUROSYMBOLIC_ENGINE_API_KEY"] is None
|
|
96
|
+
or self.config["NEUROSYMBOLIC_ENGINE_API_KEY"] == ""
|
|
97
|
+
):
|
|
98
|
+
UserMessage(msg, raise_with=ValueError)
|
|
99
|
+
self.api_key = self.config["NEUROSYMBOLIC_ENGINE_API_KEY"]
|
|
87
100
|
|
|
88
101
|
callback = self.client.chat.completions.create
|
|
89
|
-
kwargs[
|
|
102
|
+
kwargs["model"] = kwargs.get("model", self.model)
|
|
90
103
|
|
|
91
104
|
if except_remedy is not None:
|
|
92
105
|
res = except_remedy(self, e, callback, argument)
|
|
93
106
|
else:
|
|
94
|
-
|
|
107
|
+
UserMessage(f"Error during generation. Caused by: {e}", raise_with=ValueError)
|
|
95
108
|
|
|
96
109
|
reasoning_content = res.choices[0].message.reasoning_content
|
|
97
110
|
content = res.choices[0].message.content
|
|
98
|
-
metadata = {
|
|
111
|
+
metadata = {"raw_output": res, "thinking": reasoning_content}
|
|
99
112
|
|
|
100
113
|
return [content], metadata
|
|
101
114
|
|
|
102
115
|
def _prepare_raw_input(self, argument):
|
|
103
116
|
if not argument.prop.processed_input:
|
|
104
|
-
|
|
117
|
+
UserMessage(
|
|
118
|
+
"A prompt instruction is required for DeepSeekXReasoningEngine when raw_input is enabled.",
|
|
119
|
+
raise_with=ValueError,
|
|
120
|
+
)
|
|
105
121
|
value = argument.prop.processed_input
|
|
106
122
|
# convert to dict if not already
|
|
107
|
-
if
|
|
108
|
-
if
|
|
109
|
-
value = {
|
|
123
|
+
if not isinstance(value, list):
|
|
124
|
+
if not isinstance(value, dict):
|
|
125
|
+
value = {"role": "user", "content": str(value)}
|
|
110
126
|
value = [value]
|
|
111
127
|
return value
|
|
112
128
|
|
|
113
|
-
def
|
|
114
|
-
if argument.prop.raw_input:
|
|
115
|
-
argument.prop.prepared_input = self._prepare_raw_input(argument)
|
|
116
|
-
return
|
|
117
|
-
|
|
129
|
+
def _build_system_prompt(self, argument):
|
|
118
130
|
_non_verbose_output = """<META_INSTRUCTION/>\nYou do not output anything else, like verbose preambles or post explanation, such as "Sure, let me...", "Hope that was helpful...", "Yes, I can help you with that...", etc. Consider well formatted output, e.g. for sentences use punctuation, spaces etc. or for code use indentation, etc. Never add meta instructions information to your output!\n\n"""
|
|
119
|
-
user: str = ""
|
|
120
131
|
system: str = ""
|
|
132
|
+
prop = argument.prop
|
|
121
133
|
|
|
122
|
-
if
|
|
134
|
+
if prop.suppress_verbose_output:
|
|
123
135
|
system += _non_verbose_output
|
|
124
|
-
system = f
|
|
125
|
-
|
|
126
|
-
if
|
|
127
|
-
_rsp_fmt =
|
|
128
|
-
if not (_rsp_fmt.get(
|
|
129
|
-
|
|
136
|
+
system = f"{system}\n" if system and len(system) > 0 else ""
|
|
137
|
+
|
|
138
|
+
if prop.response_format:
|
|
139
|
+
_rsp_fmt = prop.response_format
|
|
140
|
+
if not (_rsp_fmt.get("type") is not None):
|
|
141
|
+
UserMessage(
|
|
142
|
+
'Response format type is required! Expected format `{"type": "json_object"}` or other supported types.',
|
|
143
|
+
raise_with=AssertionError,
|
|
144
|
+
)
|
|
130
145
|
system += _non_verbose_output
|
|
131
|
-
system += f
|
|
146
|
+
system += f"<RESPONSE_FORMAT/>\n{_rsp_fmt['type']}\n\n"
|
|
132
147
|
|
|
133
|
-
ref =
|
|
148
|
+
ref = prop.instance
|
|
134
149
|
static_ctxt, dyn_ctxt = ref.global_context
|
|
135
150
|
if len(static_ctxt) > 0:
|
|
136
151
|
system += f"<STATIC CONTEXT/>\n{static_ctxt}\n\n"
|
|
@@ -138,38 +153,53 @@ class DeepSeekXReasoningEngine(Engine, DeepSeekMixin):
|
|
|
138
153
|
if len(dyn_ctxt) > 0:
|
|
139
154
|
system += f"<DYNAMIC CONTEXT/>\n{dyn_ctxt}\n\n"
|
|
140
155
|
|
|
141
|
-
payload =
|
|
142
|
-
if
|
|
143
|
-
system += f"<ADDITIONAL CONTEXT/>\n{
|
|
156
|
+
payload = prop.payload
|
|
157
|
+
if prop.payload:
|
|
158
|
+
system += f"<ADDITIONAL CONTEXT/>\n{payload!s}\n\n"
|
|
144
159
|
|
|
145
|
-
examples:
|
|
160
|
+
examples: list[str] = prop.examples
|
|
146
161
|
if examples and len(examples) > 0:
|
|
147
|
-
system += f"<EXAMPLES/>\n{
|
|
162
|
+
system += f"<EXAMPLES/>\n{examples!s}\n\n"
|
|
148
163
|
|
|
149
|
-
if
|
|
150
|
-
val = str(
|
|
164
|
+
if prop.prompt is not None and len(prop.prompt) > 0:
|
|
165
|
+
val = str(prop.prompt)
|
|
151
166
|
system += f"<INSTRUCTION/>\n{val}\n\n"
|
|
152
167
|
|
|
153
|
-
|
|
168
|
+
if prop.template_suffix:
|
|
169
|
+
system += f" You will only generate content for the placeholder `{prop.template_suffix!s}` following the instructions and the provided context information.\n\n"
|
|
154
170
|
|
|
155
|
-
|
|
156
|
-
system += f' You will only generate content for the placeholder `{str(argument.prop.template_suffix)}` following the instructions and the provided context information.\n\n'
|
|
171
|
+
return system
|
|
157
172
|
|
|
158
|
-
|
|
173
|
+
def _build_user_prompt(self, argument):
|
|
174
|
+
return {"role": "user", "content": f"{argument.prop.processed_input!s}"}
|
|
159
175
|
|
|
160
|
-
|
|
161
|
-
|
|
176
|
+
def _apply_self_prompt(self, argument, system, user_prompt):
|
|
177
|
+
prop = argument.prop
|
|
178
|
+
if prop.instance._kwargs.get("self_prompt", False) or prop.self_prompt:
|
|
162
179
|
self_prompter = SelfPrompt()
|
|
163
180
|
|
|
164
|
-
res = self_prompter({
|
|
181
|
+
res = self_prompter({"user": user_prompt["content"], "system": system})
|
|
165
182
|
if res is None:
|
|
166
|
-
|
|
183
|
+
UserMessage(
|
|
184
|
+
"Self-prompting failed for DeepSeekXReasoningEngine.", raise_with=ValueError
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
user_prompt = {"role": "user", "content": res["user"]}
|
|
188
|
+
system = res["system"]
|
|
189
|
+
|
|
190
|
+
return system, user_prompt
|
|
191
|
+
|
|
192
|
+
def prepare(self, argument):
|
|
193
|
+
if argument.prop.raw_input:
|
|
194
|
+
argument.prop.prepared_input = self._prepare_raw_input(argument)
|
|
195
|
+
return
|
|
167
196
|
|
|
168
|
-
|
|
169
|
-
|
|
197
|
+
system = self._build_system_prompt(argument)
|
|
198
|
+
user_prompt = self._build_user_prompt(argument)
|
|
199
|
+
system, user_prompt = self._apply_self_prompt(argument, system, user_prompt)
|
|
170
200
|
|
|
171
201
|
argument.prop.prepared_input = [
|
|
172
|
-
{
|
|
202
|
+
{"role": "system", "content": system},
|
|
173
203
|
user_prompt,
|
|
174
204
|
]
|
|
175
205
|
|
|
@@ -177,13 +207,13 @@ class DeepSeekXReasoningEngine(Engine, DeepSeekMixin):
|
|
|
177
207
|
"""Prepares the request payload from the argument."""
|
|
178
208
|
kwargs = argument.kwargs
|
|
179
209
|
# 16/03/2025
|
|
180
|
-
# Not Supported Features
|
|
181
|
-
# Not Supported Parameters
|
|
210
|
+
# Not Supported Features: Function Call、Json Output、FIM (Beta)
|
|
211
|
+
# Not Supported Parameters: temperature、top_p、presence_penalty、frequency_penalty、logprobs、top_logprobs
|
|
182
212
|
return {
|
|
183
|
-
"model": kwargs.get(
|
|
184
|
-
"seed": kwargs.get(
|
|
185
|
-
"max_tokens": kwargs.get(
|
|
186
|
-
"stop": kwargs.get(
|
|
187
|
-
"n": kwargs.get(
|
|
188
|
-
"logit_bias": kwargs.get(
|
|
213
|
+
"model": kwargs.get("model", self.model),
|
|
214
|
+
"seed": kwargs.get("seed", self.seed),
|
|
215
|
+
"max_tokens": kwargs.get("max_tokens", self.max_response_tokens),
|
|
216
|
+
"stop": kwargs.get("stop", "<|endoftext|>"),
|
|
217
|
+
"n": kwargs.get("n", 1),
|
|
218
|
+
"logit_bias": kwargs.get("logit_bias"),
|
|
189
219
|
}
|