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
|
@@ -17,10 +17,10 @@ logging.getLogger("httpcore").setLevel(logging.ERROR)
|
|
|
17
17
|
class SearchResult(Result):
|
|
18
18
|
def __init__(self, value, **kwargs) -> None:
|
|
19
19
|
super().__init__(value, **kwargs)
|
|
20
|
-
if value.get(
|
|
21
|
-
UserMessage(value[
|
|
20
|
+
if value.get("error"):
|
|
21
|
+
UserMessage(value["error"], raise_with=ValueError)
|
|
22
22
|
try:
|
|
23
|
-
self._value = value[
|
|
23
|
+
self._value = value["choices"][0]["message"]["content"]
|
|
24
24
|
except Exception as e:
|
|
25
25
|
self._value = None
|
|
26
26
|
UserMessage(f"Failed to parse response: {e}", raise_with=ValueError)
|
|
@@ -42,73 +42,72 @@ class PerplexityEngine(Engine):
|
|
|
42
42
|
def __init__(self):
|
|
43
43
|
super().__init__()
|
|
44
44
|
self.config = SYMAI_CONFIG
|
|
45
|
-
self.api_key = self.config[
|
|
46
|
-
self.model = self.config[
|
|
45
|
+
self.api_key = self.config["SEARCH_ENGINE_API_KEY"]
|
|
46
|
+
self.model = self.config["SEARCH_ENGINE_MODEL"]
|
|
47
47
|
self.name = self.__class__.__name__
|
|
48
48
|
|
|
49
49
|
def id(self) -> str:
|
|
50
|
-
if self.config.get(
|
|
51
|
-
|
|
50
|
+
if self.config.get("SEARCH_ENGINE_API_KEY") and self.config.get(
|
|
51
|
+
"SEARCH_ENGINE_MODEL"
|
|
52
|
+
).startswith("sonar"):
|
|
53
|
+
return "search"
|
|
52
54
|
return super().id() # default to unregistered
|
|
53
55
|
|
|
54
56
|
def command(self, *args, **kwargs):
|
|
55
57
|
super().command(*args, **kwargs)
|
|
56
|
-
if
|
|
57
|
-
self.api_key = kwargs[
|
|
58
|
-
if
|
|
59
|
-
self.model = kwargs[
|
|
58
|
+
if "SEARCH_ENGINE_API_KEY" in kwargs:
|
|
59
|
+
self.api_key = kwargs["SEARCH_ENGINE_API_KEY"]
|
|
60
|
+
if "SEARCH_ENGINE_MODEL" in kwargs:
|
|
61
|
+
self.model = kwargs["SEARCH_ENGINE_MODEL"]
|
|
60
62
|
|
|
61
63
|
def forward(self, argument):
|
|
62
|
-
messages
|
|
63
|
-
kwargs
|
|
64
|
+
messages = argument.prop.prepared_input
|
|
65
|
+
kwargs = argument.kwargs
|
|
64
66
|
|
|
65
67
|
payload = {
|
|
66
68
|
"model": self.model,
|
|
67
69
|
"messages": messages,
|
|
68
|
-
"max_tokens": kwargs.get(
|
|
69
|
-
"temperature": kwargs.get(
|
|
70
|
-
"top_p": kwargs.get(
|
|
71
|
-
"search_domain_filter": kwargs.get(
|
|
72
|
-
"return_images": kwargs.get(
|
|
73
|
-
"return_related_questions": kwargs.get(
|
|
74
|
-
"search_recency_filter": kwargs.get(
|
|
75
|
-
"top_k": kwargs.get(
|
|
76
|
-
"stream": kwargs.get(
|
|
77
|
-
"presence_penalty": kwargs.get(
|
|
78
|
-
"frequency_penalty": kwargs.get(
|
|
79
|
-
"response_format": kwargs.get(
|
|
70
|
+
"max_tokens": kwargs.get("max_tokens", None),
|
|
71
|
+
"temperature": kwargs.get("temperature", 0.2),
|
|
72
|
+
"top_p": kwargs.get("top_p", 0.9),
|
|
73
|
+
"search_domain_filter": kwargs.get("search_domain_filter", []),
|
|
74
|
+
"return_images": kwargs.get("return_images", False),
|
|
75
|
+
"return_related_questions": kwargs.get("return_related_questions", False),
|
|
76
|
+
"search_recency_filter": kwargs.get("search_recency_filter", "month"),
|
|
77
|
+
"top_k": kwargs.get("top_k", 0),
|
|
78
|
+
"stream": kwargs.get("stream", False),
|
|
79
|
+
"presence_penalty": kwargs.get("presence_penalty", 0),
|
|
80
|
+
"frequency_penalty": kwargs.get("frequency_penalty", 1),
|
|
81
|
+
"response_format": kwargs.get("response_format", None),
|
|
80
82
|
}
|
|
81
|
-
web_search_options = kwargs.get(
|
|
83
|
+
web_search_options = kwargs.get("web_search_options", None)
|
|
82
84
|
if web_search_options is not None:
|
|
83
85
|
payload["web_search_options"] = web_search_options
|
|
84
86
|
|
|
85
|
-
headers = {
|
|
86
|
-
"Authorization": f"Bearer {self.api_key}",
|
|
87
|
-
"Content-Type": "application/json"
|
|
88
|
-
}
|
|
87
|
+
headers = {"Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json"}
|
|
89
88
|
|
|
90
89
|
try:
|
|
91
|
-
res = requests.post(
|
|
90
|
+
res = requests.post(
|
|
91
|
+
"https://api.perplexity.ai/chat/completions", json=payload, headers=headers
|
|
92
|
+
)
|
|
92
93
|
res = SearchResult(res.json())
|
|
93
94
|
except Exception as e:
|
|
94
95
|
UserMessage(f"Failed to make request: {e}", raise_with=ValueError)
|
|
95
96
|
|
|
96
97
|
metadata = {"raw_output": res.raw}
|
|
97
|
-
output
|
|
98
|
+
output = [res]
|
|
98
99
|
|
|
99
100
|
return output, metadata
|
|
100
101
|
|
|
101
102
|
def prepare(self, argument):
|
|
102
|
-
system_message =
|
|
103
|
+
system_message = (
|
|
104
|
+
"You are a helpful AI assistant. Be precise and informative."
|
|
105
|
+
if argument.kwargs.get("system_message") is None
|
|
106
|
+
else argument.kwargs.get("system_message")
|
|
107
|
+
)
|
|
103
108
|
|
|
104
109
|
res = [
|
|
105
|
-
{
|
|
106
|
-
|
|
107
|
-
"content": system_message
|
|
108
|
-
},
|
|
109
|
-
{
|
|
110
|
-
"role": "user",
|
|
111
|
-
"content": f"{argument.prop.query}"
|
|
112
|
-
}
|
|
110
|
+
{"role": "system", "content": system_message},
|
|
111
|
+
{"role": "user", "content": f"{argument.prop.query}"},
|
|
113
112
|
]
|
|
114
113
|
argument.prop.prepared_input = res
|
|
@@ -16,21 +16,21 @@ except ImportError:
|
|
|
16
16
|
class SearchResult(Result):
|
|
17
17
|
def __init__(self, value, **kwargs) -> None:
|
|
18
18
|
super().__init__(value, **kwargs)
|
|
19
|
-
if
|
|
20
|
-
self._value = value[
|
|
21
|
-
elif
|
|
22
|
-
self._value = value[
|
|
23
|
-
elif
|
|
24
|
-
self._value = value[
|
|
25
|
-
elif
|
|
26
|
-
self._value = value["organic_results"][0][
|
|
19
|
+
if "answer_box" in value and "answer" in value["answer_box"]:
|
|
20
|
+
self._value = value["answer_box"]["answer"]
|
|
21
|
+
elif "answer_box" in value and "snippet" in value["answer_box"]:
|
|
22
|
+
self._value = value["answer_box"]["snippet"]
|
|
23
|
+
elif "answer_box" in value and "snippet_highlighted_words" in value["answer_box"]:
|
|
24
|
+
self._value = value["answer_box"]["snippet_highlighted_words"][0]
|
|
25
|
+
elif "organic_results" in value and "snippet" in value["organic_results"][0]:
|
|
26
|
+
self._value = value["organic_results"][0]["snippet"]
|
|
27
27
|
else:
|
|
28
28
|
self._value = value
|
|
29
29
|
|
|
30
|
-
if
|
|
31
|
-
self.results = value[
|
|
30
|
+
if "organic_results" in value:
|
|
31
|
+
self.results = value["organic_results"]
|
|
32
32
|
if len(self.results) > 0:
|
|
33
|
-
self.links = [r[
|
|
33
|
+
self.links = [r["link"] for r in self.results]
|
|
34
34
|
else:
|
|
35
35
|
self.links = []
|
|
36
36
|
else:
|
|
@@ -48,30 +48,35 @@ class SerpApiEngine(Engine):
|
|
|
48
48
|
def __init__(self):
|
|
49
49
|
super().__init__()
|
|
50
50
|
self.config = SYMAI_CONFIG
|
|
51
|
-
self.api_key = self.config[
|
|
52
|
-
self.engine = self.config[
|
|
51
|
+
self.api_key = self.config["SEARCH_ENGINE_API_KEY"]
|
|
52
|
+
self.engine = self.config["SEARCH_ENGINE_MODEL"]
|
|
53
53
|
self.name = self.__class__.__name__
|
|
54
54
|
|
|
55
55
|
def id(self) -> str:
|
|
56
|
-
if
|
|
56
|
+
if (
|
|
57
|
+
self.config.get("SEARCH_ENGINE_API_KEY")
|
|
58
|
+
and self.config.get("SEARCH_ENGINE_MODEL") == "google"
|
|
59
|
+
): # only support Google for now
|
|
57
60
|
if GoogleSearch is None:
|
|
58
|
-
UserMessage(
|
|
59
|
-
|
|
60
|
-
|
|
61
|
+
UserMessage(
|
|
62
|
+
"SerpApi is not installed. Please install it with `pip install symbolicai[serpapi]`"
|
|
63
|
+
)
|
|
64
|
+
return "search"
|
|
65
|
+
return super().id() # default to unregistered
|
|
61
66
|
|
|
62
67
|
def command(self, *args, **kwargs):
|
|
63
68
|
super().command(*args, **kwargs)
|
|
64
|
-
if
|
|
65
|
-
self.api_key = kwargs[
|
|
66
|
-
if
|
|
67
|
-
self.engine
|
|
69
|
+
if "SEARCH_ENGINE_API_KEY" in kwargs:
|
|
70
|
+
self.api_key = kwargs["SEARCH_ENGINE_API_KEY"]
|
|
71
|
+
if "SEARCH_ENGINE_MODEL" in kwargs:
|
|
72
|
+
self.engine = kwargs["SEARCH_ENGINE_MODEL"]
|
|
68
73
|
|
|
69
74
|
def forward(self, argument):
|
|
70
|
-
queries
|
|
71
|
-
kwargs
|
|
75
|
+
queries = argument.prop.prepared_input
|
|
76
|
+
kwargs = argument.kwargs
|
|
72
77
|
queries_ = queries if isinstance(queries, list) else [queries]
|
|
73
|
-
rsp
|
|
74
|
-
engine
|
|
78
|
+
rsp = []
|
|
79
|
+
engine = kwargs.get("engine", self.engine)
|
|
75
80
|
|
|
76
81
|
for q in queries_:
|
|
77
82
|
query = {
|
|
@@ -80,11 +85,11 @@ class SerpApiEngine(Engine):
|
|
|
80
85
|
"q": q,
|
|
81
86
|
"google_domain": "google.com",
|
|
82
87
|
"gl": "us",
|
|
83
|
-
"hl": "en"
|
|
88
|
+
"hl": "en",
|
|
84
89
|
}
|
|
85
90
|
|
|
86
91
|
# send to Google
|
|
87
|
-
with io.capture_output():
|
|
92
|
+
with io.capture_output(): # disables prints from GoogleSearch
|
|
88
93
|
search = GoogleSearch(query)
|
|
89
94
|
res = search.get_dict()
|
|
90
95
|
|
|
@@ -98,7 +103,7 @@ class SerpApiEngine(Engine):
|
|
|
98
103
|
return output, metadata
|
|
99
104
|
|
|
100
105
|
def prepare(self, argument):
|
|
101
|
-
res
|
|
106
|
+
res = ""
|
|
102
107
|
res += str(argument.prop.query)
|
|
103
108
|
res += str(argument.prop.processed_input)
|
|
104
109
|
argument.prop.prepared_input = res
|
|
@@ -15,7 +15,7 @@ try:
|
|
|
15
15
|
from whisper.audio import N_SAMPLES # @NOTE: sample_rate (16_000) * chunk_length (30) = 480_000
|
|
16
16
|
from whisper.tokenizer import get_tokenizer
|
|
17
17
|
except ImportError:
|
|
18
|
-
whisper
|
|
18
|
+
whisper = None
|
|
19
19
|
N_SAMPLES = 16_000 * 30
|
|
20
20
|
|
|
21
21
|
|
|
@@ -36,7 +36,9 @@ class WhisperTimestampsFormatter(Expression):
|
|
|
36
36
|
start = prev_end
|
|
37
37
|
prev_end = end
|
|
38
38
|
prev_start = start
|
|
39
|
-
result.append(
|
|
39
|
+
result.append(
|
|
40
|
+
f"{self._format_to_hours(start + (i * 30))} {self._get_sentence(head)}"
|
|
41
|
+
)
|
|
40
42
|
continue
|
|
41
43
|
if start < prev_start:
|
|
42
44
|
continue
|
|
@@ -47,7 +49,9 @@ class WhisperTimestampsFormatter(Expression):
|
|
|
47
49
|
start += prev_end
|
|
48
50
|
end = 30 if start + delta > 30 else start + delta
|
|
49
51
|
prev_end = end
|
|
50
|
-
result.append(
|
|
52
|
+
result.append(
|
|
53
|
+
f"{self._format_to_hours(start + (i * 30))} {self._get_sentence(head)}"
|
|
54
|
+
)
|
|
51
55
|
return "\n".join(result)
|
|
52
56
|
|
|
53
57
|
def _filter_empty_string(self, s: str) -> list[str]:
|
|
@@ -88,7 +92,7 @@ class WhisperResult(Result):
|
|
|
88
92
|
return result
|
|
89
93
|
|
|
90
94
|
def _seconds(self, tmp: str) -> int:
|
|
91
|
-
h, m
|
|
95
|
+
h, m, s = tmp.split(":")
|
|
92
96
|
return int(h) * 3600 + int(m) * 60 + int(s)
|
|
93
97
|
|
|
94
98
|
|
|
@@ -96,37 +100,44 @@ class WhisperEngine(Engine):
|
|
|
96
100
|
def __init__(self, model: str | None = None, to_device: str | None = None):
|
|
97
101
|
super().__init__()
|
|
98
102
|
self.config = SYMAI_CONFIG
|
|
99
|
-
self.model = None
|
|
100
|
-
self.model_id = self.config[
|
|
101
|
-
self.old_model_id = self.config[
|
|
103
|
+
self.model = None # lazy loading
|
|
104
|
+
self.model_id = self.config["SPEECH_TO_TEXT_ENGINE_MODEL"] if model is None else model
|
|
105
|
+
self.old_model_id = self.config["SPEECH_TO_TEXT_ENGINE_MODEL"] if model is None else model
|
|
102
106
|
self.tokens = []
|
|
103
107
|
self.text = []
|
|
104
108
|
self.formatter = WhisperTimestampsFormatter()
|
|
105
109
|
self.name = self.__class__.__name__
|
|
106
110
|
if self.model is None or self.model_id != self.old_model_id:
|
|
107
|
-
device_fallback =
|
|
111
|
+
device_fallback = "cpu"
|
|
108
112
|
device = "cuda" if torch.cuda.is_available() else device_fallback
|
|
109
|
-
device =
|
|
113
|
+
device = (
|
|
114
|
+
to_device if to_device is not None else device_fallback
|
|
115
|
+
) # user preference over auto detection
|
|
110
116
|
try:
|
|
111
117
|
self.model = whisper.load_model(self.model_id, device=device)
|
|
112
118
|
except RuntimeError:
|
|
113
|
-
UserMessage(
|
|
119
|
+
UserMessage(
|
|
120
|
+
f"Whisper failed to load model on device {device}. Fallback to {device_fallback}."
|
|
121
|
+
)
|
|
114
122
|
self.model = whisper.load_model(self.model_id, device=device_fallback)
|
|
115
123
|
self.old_model_id = self.model_id
|
|
116
124
|
|
|
117
125
|
self._try_compile()
|
|
118
126
|
|
|
119
127
|
def id(self) -> str:
|
|
120
|
-
if self.config[
|
|
128
|
+
if self.config["SPEECH_TO_TEXT_ENGINE_MODEL"]:
|
|
121
129
|
if whisper is None:
|
|
122
|
-
UserMessage(
|
|
123
|
-
|
|
124
|
-
|
|
130
|
+
UserMessage(
|
|
131
|
+
"Whisper is not installed. Please install it with `pip install symbolicai[whisper]`",
|
|
132
|
+
raise_with=ImportError,
|
|
133
|
+
)
|
|
134
|
+
return "speech-to-text"
|
|
135
|
+
return super().id() # default to unregistered
|
|
125
136
|
|
|
126
137
|
def command(self, *args, **kwargs):
|
|
127
138
|
super().command(*args, **kwargs)
|
|
128
|
-
if
|
|
129
|
-
self.model_id = kwargs[
|
|
139
|
+
if "SPEECH_TO_TEXT_ENGINE_MODEL" in kwargs:
|
|
140
|
+
self.model_id = kwargs["SPEECH_TO_TEXT_ENGINE_MODEL"]
|
|
130
141
|
|
|
131
142
|
def forward(self, argument):
|
|
132
143
|
assert whisper is not None, "Whisper is not installed. Please install it first."
|
|
@@ -140,16 +151,17 @@ class WhisperEngine(Engine):
|
|
|
140
151
|
without_timestamps = kwargs.get("without_timestamps", False)
|
|
141
152
|
|
|
142
153
|
raw_result = []
|
|
143
|
-
if prompt ==
|
|
144
|
-
|
|
154
|
+
if prompt == "detect_language":
|
|
155
|
+
# @NOTE: the accuracy of mel spectrogram is not good enough; don't use it to transcribe
|
|
145
156
|
audio = whisper.pad_or_trim(audio)
|
|
146
157
|
mel = whisper.log_mel_spectrogram(audio).to(self.model.device)
|
|
147
158
|
_, probs = self.model.detect_language(mel)
|
|
148
159
|
rsp = max(probs, key=probs.get)
|
|
149
|
-
elif prompt ==
|
|
160
|
+
elif prompt == "decode":
|
|
150
161
|
if show_pbar:
|
|
151
162
|
# Suppress tqdm warning; keep optional dependency lazy.
|
|
152
|
-
from tqdm import tqdm
|
|
163
|
+
from tqdm import tqdm # noqa
|
|
164
|
+
|
|
153
165
|
pbar = tqdm(self._get_chunks(audio))
|
|
154
166
|
else:
|
|
155
167
|
pbar = self._get_chunks(audio)
|
|
@@ -163,11 +175,9 @@ class WhisperEngine(Engine):
|
|
|
163
175
|
)
|
|
164
176
|
raw_result.append(result)
|
|
165
177
|
self.text.append(result["text"])
|
|
166
|
-
self.tokens.append(
|
|
167
|
-
token
|
|
168
|
-
|
|
169
|
-
for token in segment["tokens"]
|
|
170
|
-
])
|
|
178
|
+
self.tokens.append(
|
|
179
|
+
[token for segment in result["segments"] for token in segment["tokens"]]
|
|
180
|
+
)
|
|
171
181
|
if without_timestamps is not None:
|
|
172
182
|
tokenizer = get_tokenizer(self.model.is_multilingual)
|
|
173
183
|
tokens = [tokenizer.decode_with_timestamps(t) for t in self.tokens]
|
|
@@ -186,7 +196,7 @@ class WhisperEngine(Engine):
|
|
|
186
196
|
def prepare(self, argument):
|
|
187
197
|
assert not argument.prop.processed_input, "Whisper does not support processed_input."
|
|
188
198
|
assert argument.prop.audio, "Whisper requires audio input."
|
|
189
|
-
audio_file
|
|
199
|
+
audio_file = str(argument.prop.audio)
|
|
190
200
|
audio = whisper.load_audio(audio_file)
|
|
191
201
|
argument.prop.prepared_input = (audio_file, audio)
|
|
192
202
|
|
|
@@ -196,7 +206,7 @@ class WhisperEngine(Engine):
|
|
|
196
206
|
"""
|
|
197
207
|
size = len(it)
|
|
198
208
|
for i in range(0, size, batch):
|
|
199
|
-
yield torch.tensor(it[i:min(i + batch, size)]).to(self.model.device)
|
|
209
|
+
yield torch.tensor(it[i : min(i + batch, size)]).to(self.model.device)
|
|
200
210
|
|
|
201
211
|
def _try_compile(self):
|
|
202
212
|
with contextlib.suppress(Exception):
|
|
@@ -8,7 +8,10 @@ from ...settings import SYMAI_CONFIG
|
|
|
8
8
|
try:
|
|
9
9
|
import wolframalpha as wa
|
|
10
10
|
except ImportError:
|
|
11
|
-
UserMessage(
|
|
11
|
+
UserMessage(
|
|
12
|
+
"WolframAlpha is not installed. Please install it with `pip install symbolicai[wolframalpha]`",
|
|
13
|
+
raise_with=ImportError,
|
|
14
|
+
)
|
|
12
15
|
|
|
13
16
|
|
|
14
17
|
class WolframResult(Result):
|
|
@@ -22,19 +25,19 @@ class WolframAlphaEngine(Engine):
|
|
|
22
25
|
def __init__(self, api_key: str | None = None):
|
|
23
26
|
super().__init__()
|
|
24
27
|
self.config = deepcopy(SYMAI_CONFIG)
|
|
25
|
-
self.api_key = self.config[
|
|
28
|
+
self.api_key = self.config["SYMBOLIC_ENGINE_API_KEY"] if api_key is None else api_key
|
|
26
29
|
self.client = wa.Client(self.api_key)
|
|
27
30
|
self.name = self.__class__.__name__
|
|
28
31
|
|
|
29
32
|
def id(self) -> str:
|
|
30
|
-
if self.config[
|
|
31
|
-
return
|
|
32
|
-
return super().id()
|
|
33
|
+
if self.config["SYMBOLIC_ENGINE_API_KEY"]:
|
|
34
|
+
return "symbolic"
|
|
35
|
+
return super().id() # default to unregistered
|
|
33
36
|
|
|
34
37
|
def command(self, *args, **kwargs):
|
|
35
38
|
super().command(*args, **kwargs)
|
|
36
|
-
if
|
|
37
|
-
self.api_key = kwargs[
|
|
39
|
+
if "SYMBOLIC_ENGINE_API_KEY" in kwargs:
|
|
40
|
+
self.api_key = kwargs["SYMBOLIC_ENGINE_API_KEY"]
|
|
38
41
|
self.client = wa.Client(self.api_key)
|
|
39
42
|
|
|
40
43
|
def forward(self, argument):
|
|
@@ -45,7 +48,10 @@ class WolframAlphaEngine(Engine):
|
|
|
45
48
|
rsp = self.client.query(queries)
|
|
46
49
|
rsp = WolframResult(rsp)
|
|
47
50
|
except Exception as e:
|
|
48
|
-
UserMessage(
|
|
51
|
+
UserMessage(
|
|
52
|
+
f'Failed to interact with WolframAlpha: {e}.\n\n If you are getting an error related to "assert", that is a well-known issue with WolframAlpha. There is a manual fix for this issue: https://github.com/jaraco/wolframalpha/pull/34/commits/6eb3828ee812f65592e00629710fc027d40e7bd1',
|
|
53
|
+
raise_with=ValueError,
|
|
54
|
+
)
|
|
49
55
|
|
|
50
56
|
metadata = {}
|
|
51
57
|
|
|
@@ -14,33 +14,29 @@ class TTSEngine(Engine):
|
|
|
14
14
|
def __init__(self, api_key: str | None = None, model: str | None = None):
|
|
15
15
|
super().__init__()
|
|
16
16
|
self.config = SYMAI_CONFIG
|
|
17
|
-
self.api_key = self.config[
|
|
18
|
-
self.model_id = self.config[
|
|
17
|
+
self.api_key = self.config["TEXT_TO_SPEECH_ENGINE_API_KEY"] if api_key is None else api_key
|
|
18
|
+
self.model_id = self.config["TEXT_TO_SPEECH_ENGINE_MODEL"] if model is None else model
|
|
19
19
|
self.tokens = []
|
|
20
20
|
self.text = []
|
|
21
21
|
self.client = OpenAI(api_key=self.api_key)
|
|
22
22
|
self.name = self.__class__.__name__
|
|
23
23
|
|
|
24
24
|
def id(self) -> str:
|
|
25
|
-
if self.config[
|
|
26
|
-
return
|
|
27
|
-
return super().id()
|
|
25
|
+
if self.config["TEXT_TO_SPEECH_ENGINE_API_KEY"]:
|
|
26
|
+
return "text-to-speech"
|
|
27
|
+
return super().id() # default to unregistered
|
|
28
28
|
|
|
29
29
|
def command(self, *args, **kwargs):
|
|
30
30
|
super().command(*args, **kwargs)
|
|
31
|
-
if
|
|
32
|
-
self.api_key = kwargs[
|
|
33
|
-
if
|
|
34
|
-
self.model_id = kwargs[
|
|
31
|
+
if "TEXT_TO_SPEECH_ENGINE_API_KEY" in kwargs:
|
|
32
|
+
self.api_key = kwargs["TEXT_TO_SPEECH_ENGINE_API_KEY"]
|
|
33
|
+
if "TEXT_TO_SPEECH_ENGINE_MODEL" in kwargs:
|
|
34
|
+
self.model_id = kwargs["TEXT_TO_SPEECH_ENGINE_MODEL"]
|
|
35
35
|
|
|
36
36
|
def forward(self, argument):
|
|
37
37
|
voice, path, prompt = argument.prop.prepared_input
|
|
38
38
|
|
|
39
|
-
rsp = self.client.audio.speech.create(
|
|
40
|
-
model=self.model_id,
|
|
41
|
-
voice=voice,
|
|
42
|
-
input=prompt
|
|
43
|
-
)
|
|
39
|
+
rsp = self.client.audio.speech.create(model=self.model_id, voice=voice, input=prompt)
|
|
44
40
|
|
|
45
41
|
metadata = {}
|
|
46
42
|
|
|
@@ -51,9 +47,9 @@ class TTSEngine(Engine):
|
|
|
51
47
|
|
|
52
48
|
def prepare(self, argument):
|
|
53
49
|
assert not argument.prop.processed_input, "TTSEngine does not support processed_input."
|
|
54
|
-
assert
|
|
55
|
-
assert
|
|
56
|
-
voice
|
|
57
|
-
audio_file
|
|
58
|
-
prompt
|
|
50
|
+
assert "voice" in argument.kwargs, "TTS requires voice selection."
|
|
51
|
+
assert "path" in argument.kwargs, "TTS requires path selection."
|
|
52
|
+
voice = str(argument.kwargs["voice"]).lower()
|
|
53
|
+
audio_file = str(argument.kwargs["path"])
|
|
54
|
+
prompt = str(argument.prop.prompt)
|
|
59
55
|
argument.prop.prepared_input = (voice, audio_file, prompt)
|
|
@@ -17,22 +17,22 @@ logging.getLogger("PIL").setLevel(logging.WARNING)
|
|
|
17
17
|
class CLIPEngine(Engine):
|
|
18
18
|
def __init__(self, model: str | None = None):
|
|
19
19
|
super().__init__()
|
|
20
|
-
self.model =
|
|
21
|
-
self.preprocessor = None
|
|
20
|
+
self.model = None # lazy loading
|
|
21
|
+
self.preprocessor = None # lazy loading
|
|
22
22
|
self.config = SYMAI_CONFIG
|
|
23
|
-
self.model_id = self.config[
|
|
24
|
-
self.old_model_id = self.config[
|
|
23
|
+
self.model_id = self.config["VISION_ENGINE_MODEL"] if model is None else model
|
|
24
|
+
self.old_model_id = self.config["VISION_ENGINE_MODEL"] if model is None else model
|
|
25
25
|
self.name = self.__class__.__name__
|
|
26
26
|
|
|
27
27
|
def id(self) -> str:
|
|
28
|
-
if self.config[
|
|
29
|
-
return
|
|
30
|
-
return super().id()
|
|
28
|
+
if self.config["VISION_ENGINE_MODEL"]:
|
|
29
|
+
return "text_vision"
|
|
30
|
+
return super().id() # default to unregistered
|
|
31
31
|
|
|
32
32
|
def command(self, *args, **kwargs):
|
|
33
33
|
super().command(*args, **kwargs)
|
|
34
|
-
if
|
|
35
|
-
self.model_id
|
|
34
|
+
if "VISION_ENGINE_MODEL" in kwargs:
|
|
35
|
+
self.model_id = kwargs["VISION_ENGINE_MODEL"]
|
|
36
36
|
|
|
37
37
|
def load_images(self, image):
|
|
38
38
|
images = []
|
|
@@ -43,35 +43,41 @@ class CLIPEngine(Engine):
|
|
|
43
43
|
if isinstance(img, bytes):
|
|
44
44
|
images.append(Image.open(BytesIO(img)))
|
|
45
45
|
elif isinstance(img, str):
|
|
46
|
-
image_source = requests.get(img, stream=True).raw if img.startswith(
|
|
46
|
+
image_source = requests.get(img, stream=True).raw if img.startswith("http") else img
|
|
47
47
|
image = Image.open(image_source)
|
|
48
48
|
images.append(image)
|
|
49
49
|
return images
|
|
50
50
|
|
|
51
51
|
def forward(self, argument):
|
|
52
|
-
image_url, text
|
|
52
|
+
image_url, text = argument.prop.prepared_input
|
|
53
53
|
|
|
54
54
|
if self.model is None or self.model_id != self.old_model_id:
|
|
55
|
-
self.device
|
|
56
|
-
self.model
|
|
57
|
-
self.processor
|
|
55
|
+
self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
|
56
|
+
self.model = CLIPModel.from_pretrained(self.model_id).to(self.device)
|
|
57
|
+
self.processor = CLIPProcessor.from_pretrained(self.model_id)
|
|
58
58
|
self.old_model_id = self.model_id
|
|
59
59
|
|
|
60
60
|
if text is None and image_url is not None:
|
|
61
|
-
image
|
|
62
|
-
inputs
|
|
63
|
-
rsp
|
|
61
|
+
image = self.load_images(image_url)
|
|
62
|
+
inputs = self.processor(images=image, return_tensors="pt").to(self.device)
|
|
63
|
+
rsp = self.model.get_image_features(**inputs)
|
|
64
64
|
elif image_url is None and text is not None:
|
|
65
|
-
inputs
|
|
66
|
-
rsp
|
|
65
|
+
inputs = self.processor(text=text, return_tensors="pt").to(self.device)
|
|
66
|
+
rsp = self.model.get_text_features(**inputs)
|
|
67
67
|
elif image_url is not None and text is not None:
|
|
68
|
-
image
|
|
69
|
-
inputs
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
68
|
+
image = self.load_images(image_url)
|
|
69
|
+
inputs = self.processor(text=text, images=image, return_tensors="pt", padding=True).to(
|
|
70
|
+
self.device
|
|
71
|
+
)
|
|
72
|
+
outputs = self.model(**inputs)
|
|
73
|
+
logits_per_image = outputs.logits_per_image # this is the image-text similarity score
|
|
74
|
+
rsp = logits_per_image.softmax(
|
|
75
|
+
dim=1
|
|
76
|
+
) # we can take the softmax to get the label probabilities
|
|
73
77
|
else:
|
|
74
|
-
UserMessage(
|
|
78
|
+
UserMessage(
|
|
79
|
+
"CLIPEngine requires either image or text input.", raise_with=NotImplementedError
|
|
80
|
+
)
|
|
75
81
|
|
|
76
82
|
rsp = rsp.squeeze().detach().cpu().numpy()
|
|
77
83
|
|
|
@@ -81,7 +87,7 @@ class CLIPEngine(Engine):
|
|
|
81
87
|
|
|
82
88
|
def prepare(self, argument):
|
|
83
89
|
assert not argument.prop.processed_input, "CLIPEngine does not support processed_input."
|
|
84
|
-
kwargs
|
|
85
|
-
image_url
|
|
86
|
-
text
|
|
90
|
+
kwargs = argument.kwargs
|
|
91
|
+
image_url = argument.kwargs["image"] if "image" in kwargs else None
|
|
92
|
+
text = argument.kwargs["text"] if "text" in kwargs else None
|
|
87
93
|
argument.prop.prepared_input = (image_url, text)
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
from ....utils import UserMessage
|
|
3
2
|
from ...base import Engine
|
|
4
3
|
|
|
@@ -9,14 +8,14 @@ class UserInputEngine(Engine):
|
|
|
9
8
|
self.name = self.__class__.__name__
|
|
10
9
|
|
|
11
10
|
def id(self) -> str:
|
|
12
|
-
return
|
|
11
|
+
return "userinput"
|
|
13
12
|
|
|
14
13
|
def forward(self, argument):
|
|
15
14
|
msg = argument.prop.prepared_input
|
|
16
15
|
kwargs = argument.kwargs
|
|
17
16
|
|
|
18
|
-
mock = kwargs.get(
|
|
19
|
-
if mock:
|
|
17
|
+
mock = kwargs.get("mock", False)
|
|
18
|
+
if mock: # mock user input
|
|
20
19
|
UserMessage(msg)
|
|
21
20
|
rsp = mock
|
|
22
21
|
else:
|