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
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import itertools
|
|
2
2
|
from copy import deepcopy
|
|
3
|
+
from typing import ClassVar
|
|
3
4
|
|
|
4
5
|
from ....extended.vectordb import VectorDB
|
|
5
6
|
from ....symbol import Result
|
|
6
|
-
from ....utils import
|
|
7
|
+
from ....utils import UserMessage
|
|
7
8
|
from ...base import Engine
|
|
8
9
|
from ...settings import SYMAI_CONFIG
|
|
9
10
|
|
|
@@ -31,72 +32,76 @@ class VectorDBResult(Result):
|
|
|
31
32
|
try:
|
|
32
33
|
res = self._to_symbol(res).ast()
|
|
33
34
|
except Exception as e:
|
|
34
|
-
message = [
|
|
35
|
+
message = [
|
|
36
|
+
"Sorry, failed to interact with index. Please check index name and try again later:",
|
|
37
|
+
str(e),
|
|
38
|
+
]
|
|
35
39
|
# package the message for the IndexResult class
|
|
36
|
-
res = {
|
|
37
|
-
return [v[
|
|
40
|
+
res = {"matches": [{"metadata": {"text": "\n".join(message)}}]}
|
|
41
|
+
return [v["metadata"]["text"] for v in res]
|
|
38
42
|
|
|
39
43
|
def _unpack_matches(self):
|
|
40
44
|
if not self.value:
|
|
41
45
|
return
|
|
42
46
|
for i, match in enumerate(self.value):
|
|
43
|
-
|
|
44
|
-
if
|
|
45
|
-
m =
|
|
46
|
-
splits = m.split(
|
|
47
|
-
assert len(splits) >= 2,
|
|
47
|
+
match_value = match.strip()
|
|
48
|
+
if match_value.startswith("# ----[FILE_START]") and "# ----[FILE_END]" in match_value:
|
|
49
|
+
m = match_value.split("[FILE_CONTENT]:")[-1].strip()
|
|
50
|
+
splits = m.split("# ----[FILE_END]")
|
|
51
|
+
assert len(splits) >= 2, f"Invalid file format: {splits}"
|
|
48
52
|
content = splits[0]
|
|
49
|
-
file_name =
|
|
53
|
+
file_name = ",".join(splits[1:]) # TODO: check why there are multiple file names
|
|
50
54
|
yield file_name.strip(), content.strip()
|
|
51
55
|
else:
|
|
52
|
-
yield i+1,
|
|
56
|
+
yield i + 1, match_value
|
|
53
57
|
|
|
54
58
|
def __str__(self):
|
|
55
|
-
str_view =
|
|
59
|
+
str_view = ""
|
|
56
60
|
for filename, content in self._unpack_matches():
|
|
57
61
|
# indent each line of the content
|
|
58
|
-
|
|
59
|
-
str_view += f
|
|
60
|
-
return f
|
|
62
|
+
content_view = "\n".join([" " + line for line in content.split("\n")])
|
|
63
|
+
str_view += f"* {filename}\n{content_view}\n\n"
|
|
64
|
+
return f"""
|
|
61
65
|
[RESULT]
|
|
62
|
-
{
|
|
66
|
+
{"-=-" * 13}
|
|
63
67
|
|
|
64
68
|
Query: {self._query}
|
|
65
69
|
|
|
66
|
-
{
|
|
70
|
+
{"-=-" * 13}
|
|
67
71
|
|
|
68
72
|
Matches:
|
|
69
73
|
|
|
70
74
|
{str_view}
|
|
71
|
-
{
|
|
72
|
-
|
|
75
|
+
{"-=-" * 13}
|
|
76
|
+
"""
|
|
73
77
|
|
|
74
78
|
def _repr_html_(self) -> str:
|
|
75
79
|
# return a nicely styled HTML list results based on retrieved documents
|
|
76
|
-
doc_str =
|
|
80
|
+
doc_str = ""
|
|
77
81
|
for filename, content in self._unpack_matches():
|
|
78
82
|
doc_str += f'<li><a href="{filename}"><b>{filename}</a></b><br>{content}</li>\n'
|
|
79
|
-
return f
|
|
83
|
+
return f"<ul>{doc_str}</ul>"
|
|
80
84
|
|
|
81
85
|
|
|
82
86
|
class VectorDBIndexEngine(Engine):
|
|
83
87
|
# Updated default values to be congruent with VectorDB's defaults
|
|
84
|
-
_default_index_name =
|
|
88
|
+
_default_index_name = "dataindex"
|
|
85
89
|
_default_index_dims = 768
|
|
86
90
|
_default_index_top_k = 5
|
|
87
|
-
_default_index_metric =
|
|
88
|
-
_index_dict = {}
|
|
89
|
-
_index_storage_file = None
|
|
91
|
+
_default_index_metric = "cosine"
|
|
92
|
+
_index_dict: ClassVar[dict[str, object]] = {}
|
|
93
|
+
_index_storage_file: ClassVar[str | None] = None
|
|
94
|
+
|
|
90
95
|
def __init__(
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
96
|
+
self,
|
|
97
|
+
index_name=_default_index_name,
|
|
98
|
+
index_dims=_default_index_dims,
|
|
99
|
+
index_top_k=_default_index_top_k,
|
|
100
|
+
index_metric=_default_index_metric,
|
|
101
|
+
index_dict=_index_dict,
|
|
102
|
+
index_storage_file=_index_storage_file,
|
|
103
|
+
**_kwargs,
|
|
104
|
+
):
|
|
100
105
|
super().__init__()
|
|
101
106
|
self.config = deepcopy(SYMAI_CONFIG)
|
|
102
107
|
self.index_name = index_name
|
|
@@ -111,9 +116,12 @@ class VectorDBIndexEngine(Engine):
|
|
|
111
116
|
self.name = self.__class__.__name__
|
|
112
117
|
|
|
113
118
|
def id(self) -> str:
|
|
114
|
-
if
|
|
115
|
-
|
|
116
|
-
|
|
119
|
+
if (
|
|
120
|
+
not self.config["INDEXING_ENGINE_API_KEY"]
|
|
121
|
+
or self.config["INDEXING_ENGINE_API_KEY"] == ""
|
|
122
|
+
):
|
|
123
|
+
return "index"
|
|
124
|
+
return super().id() # default to unregistered
|
|
117
125
|
|
|
118
126
|
def forward(self, argument):
|
|
119
127
|
query = argument.prop.prepared_input
|
|
@@ -129,34 +137,51 @@ class VectorDBIndexEngine(Engine):
|
|
|
129
137
|
|
|
130
138
|
self._init(index_name, top_k, index_dims, metric)
|
|
131
139
|
|
|
132
|
-
if operation ==
|
|
140
|
+
if operation == "search":
|
|
133
141
|
if isinstance(query, list) and len(query) > 1:
|
|
134
|
-
|
|
142
|
+
UserMessage(
|
|
143
|
+
"VectorDB indexing engine does not support multiple queries. Pass a single string query instead.",
|
|
144
|
+
raise_with=ValueError,
|
|
145
|
+
)
|
|
135
146
|
query_vector = self.index[index_name].embedding_function([query])[0]
|
|
136
|
-
results = self.index[index_name](
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
147
|
+
results = self.index[index_name](
|
|
148
|
+
vector=query_vector, top_k=top_k, return_similarities=similarities
|
|
149
|
+
)
|
|
150
|
+
rsp = [{"metadata": {"text": result}} for result in results]
|
|
151
|
+
elif operation == "add":
|
|
152
|
+
assert isinstance(query, list), (
|
|
153
|
+
"VectorDB indexing requires a list of queries at insertion, even if there is only one query."
|
|
154
|
+
)
|
|
140
155
|
documents = []
|
|
141
156
|
vectors = []
|
|
142
157
|
for q in query:
|
|
143
158
|
vectors.append(self.index[index_name].embedding_function([q])[0])
|
|
144
159
|
documents.append(q)
|
|
145
160
|
self.index[index_name].add(documents=documents, vectors=vectors)
|
|
146
|
-
elif operation ==
|
|
147
|
-
assert kwargs,
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
161
|
+
elif operation == "config":
|
|
162
|
+
assert kwargs, (
|
|
163
|
+
"Please provide a configuration by passing the appropriate kwargs. Currently, only `load`, `save`, `purge`."
|
|
164
|
+
)
|
|
165
|
+
maybe_as_prompt = kwargs.get("prompt")
|
|
166
|
+
if kwargs.get("load", maybe_as_prompt == "load"):
|
|
167
|
+
assert storage_file, (
|
|
168
|
+
"Please provide a `storage_file` path to load the pre-computed index."
|
|
169
|
+
)
|
|
151
170
|
self.load(index_name, storage_file, index_dims, top_k, metric)
|
|
152
|
-
elif kwargs.get(
|
|
171
|
+
elif kwargs.get("save", maybe_as_prompt == "save"):
|
|
153
172
|
self.save(index_name, storage_file)
|
|
154
|
-
elif kwargs.get(
|
|
173
|
+
elif kwargs.get("purge", maybe_as_prompt == "purge"):
|
|
155
174
|
self.purge(index_name)
|
|
156
175
|
else:
|
|
157
|
-
|
|
176
|
+
UserMessage(
|
|
177
|
+
'Invalid configuration; please use either "load", "save", or "purge".',
|
|
178
|
+
raise_with=ValueError,
|
|
179
|
+
)
|
|
158
180
|
else:
|
|
159
|
-
|
|
181
|
+
UserMessage(
|
|
182
|
+
'Invalid operation; please use either "search", "add", or "config".',
|
|
183
|
+
raise_with=ValueError,
|
|
184
|
+
)
|
|
160
185
|
|
|
161
186
|
metadata = {}
|
|
162
187
|
rsp = VectorDBResult(rsp, query[0], None)
|
|
@@ -170,12 +195,15 @@ class VectorDBIndexEngine(Engine):
|
|
|
170
195
|
index_dims=index_dims,
|
|
171
196
|
top_k=top_k,
|
|
172
197
|
similarity_metric=metric,
|
|
173
|
-
embedding_model=embedding_model
|
|
198
|
+
embedding_model=embedding_model, # @NOTE: the VectorDBIndexEngine class uses precomputed embeddings so the model is not needed in the VectorDB class
|
|
174
199
|
)
|
|
175
200
|
|
|
176
201
|
def prepare(self, argument):
|
|
177
|
-
assert not argument.prop.processed_input,
|
|
202
|
+
assert not argument.prop.processed_input, (
|
|
203
|
+
"VectorDB indexing engine does not support processed_input."
|
|
204
|
+
)
|
|
178
205
|
argument.prop.prepared_input = argument.prop.prompt
|
|
206
|
+
argument.prop.limit = 1
|
|
179
207
|
|
|
180
208
|
def load(self, index_name, storage_file, index_dims, top_k, metric):
|
|
181
209
|
self.index[index_name] = VectorDB(
|
|
@@ -186,11 +214,11 @@ class VectorDBIndexEngine(Engine):
|
|
|
186
214
|
index_name=index_name,
|
|
187
215
|
)
|
|
188
216
|
|
|
189
|
-
def save(self, index_name
|
|
217
|
+
def save(self, index_name=None, storage_file=None):
|
|
190
218
|
index_name = index_name or self.index_name
|
|
191
219
|
storage_file = storage_file or self._index_storage_file
|
|
192
220
|
self.index[index_name].save(storage_file)
|
|
193
221
|
|
|
194
|
-
def purge(self, index_name
|
|
222
|
+
def purge(self, index_name=None):
|
|
195
223
|
index_name = index_name or self.index_name
|
|
196
224
|
self.index[index_name].purge(index_name)
|
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
import os
|
|
2
1
|
import subprocess
|
|
2
|
+
import tempfile
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
3
6
|
import docker
|
|
4
7
|
import paramiko
|
|
5
|
-
|
|
6
|
-
from typing import Any, List, Tuple, Optional, Dict
|
|
7
|
-
from ...base import Engine
|
|
8
|
+
|
|
8
9
|
from ....symbol import Result
|
|
10
|
+
from ....utils import UserMessage
|
|
11
|
+
from ...base import Engine
|
|
12
|
+
|
|
9
13
|
|
|
10
14
|
class LeanResult(Result):
|
|
11
15
|
"""
|
|
@@ -14,7 +18,8 @@ class LeanResult(Result):
|
|
|
14
18
|
Attributes:
|
|
15
19
|
_value (Dict[str, str]): A dictionary containing the output of the Lean execution.
|
|
16
20
|
"""
|
|
17
|
-
|
|
21
|
+
|
|
22
|
+
def __init__(self, value: dict[str, str]) -> None:
|
|
18
23
|
"""
|
|
19
24
|
Initializes a new LeanResult instance.
|
|
20
25
|
|
|
@@ -24,6 +29,7 @@ class LeanResult(Result):
|
|
|
24
29
|
super().__init__(value)
|
|
25
30
|
self._value = value
|
|
26
31
|
|
|
32
|
+
|
|
27
33
|
class LeanEngine(Engine):
|
|
28
34
|
"""
|
|
29
35
|
Engine for executing Lean code within a Docker container, providing SSH access for execution.
|
|
@@ -39,10 +45,10 @@ class LeanEngine(Engine):
|
|
|
39
45
|
|
|
40
46
|
def __init__(
|
|
41
47
|
self,
|
|
42
|
-
ssh_host: str =
|
|
48
|
+
ssh_host: str = "localhost",
|
|
43
49
|
ssh_port: int = 2222,
|
|
44
|
-
ssh_user: str =
|
|
45
|
-
ssh_key_path: str =
|
|
50
|
+
ssh_user: str = "root",
|
|
51
|
+
ssh_key_path: str = "~/.ssh/id_rsa",
|
|
46
52
|
) -> None:
|
|
47
53
|
"""
|
|
48
54
|
Initializes the LeanEngine with SSH and Docker configurations.
|
|
@@ -57,7 +63,7 @@ class LeanEngine(Engine):
|
|
|
57
63
|
self.ssh_host: str = ssh_host
|
|
58
64
|
self.ssh_port: int = ssh_port
|
|
59
65
|
self.ssh_user: str = ssh_user
|
|
60
|
-
self.ssh_key_path:
|
|
66
|
+
self.ssh_key_path: Path = Path(ssh_key_path).expanduser()
|
|
61
67
|
self.docker_client: docker.DockerClient = docker.from_env()
|
|
62
68
|
self.container: docker.models.containers.Container = self._ensure_container()
|
|
63
69
|
self.name = self.__class__.__name__
|
|
@@ -70,7 +76,7 @@ class LeanEngine(Engine):
|
|
|
70
76
|
Returns:
|
|
71
77
|
str: The identifier of the LeanEngine, 'lean4'.
|
|
72
78
|
"""
|
|
73
|
-
return
|
|
79
|
+
return "lean4"
|
|
74
80
|
|
|
75
81
|
def _ensure_container(self) -> docker.models.containers.Container:
|
|
76
82
|
"""
|
|
@@ -82,10 +88,14 @@ class LeanEngine(Engine):
|
|
|
82
88
|
container_name: str = "lean-container"
|
|
83
89
|
|
|
84
90
|
try:
|
|
85
|
-
existing_container: docker.models.containers.Container =
|
|
91
|
+
existing_container: docker.models.containers.Container = (
|
|
92
|
+
self.docker_client.containers.get(container_name)
|
|
93
|
+
)
|
|
86
94
|
existing_container.remove(force=True)
|
|
87
95
|
except docker.errors.NotFound:
|
|
88
|
-
|
|
96
|
+
UserMessage(
|
|
97
|
+
f"No existing container named '{container_name}' found. Proceeding to create a new one."
|
|
98
|
+
)
|
|
89
99
|
|
|
90
100
|
dockerfile: str = """
|
|
91
101
|
FROM buildpack-deps:buster
|
|
@@ -110,17 +120,18 @@ class LeanEngine(Engine):
|
|
|
110
120
|
"""
|
|
111
121
|
with tempfile.NamedTemporaryFile("w", delete=False) as temp_dockerfile:
|
|
112
122
|
temp_dockerfile.write(dockerfile)
|
|
113
|
-
dockerfile_path
|
|
123
|
+
dockerfile_path = Path(temp_dockerfile.name)
|
|
114
124
|
|
|
115
125
|
image: docker.models.images.Image
|
|
116
|
-
image, _ = self.docker_client.images.build(
|
|
117
|
-
|
|
126
|
+
image, _ = self.docker_client.images.build(
|
|
127
|
+
path=str(dockerfile_path.parent),
|
|
128
|
+
dockerfile=str(dockerfile_path),
|
|
129
|
+
tag="lean4-container-image",
|
|
130
|
+
)
|
|
131
|
+
dockerfile_path.unlink()
|
|
118
132
|
|
|
119
133
|
container: docker.models.containers.Container = self.docker_client.containers.run(
|
|
120
|
-
image.id,
|
|
121
|
-
detach=True,
|
|
122
|
-
name=container_name,
|
|
123
|
-
ports={'22/tcp': self.ssh_port}
|
|
134
|
+
image.id, detach=True, name=container_name, ports={"22/tcp": self.ssh_port}
|
|
124
135
|
)
|
|
125
136
|
return container
|
|
126
137
|
|
|
@@ -129,15 +140,42 @@ class LeanEngine(Engine):
|
|
|
129
140
|
Sets up SSH access to the Docker container, including generating an SSH key pair if necessary,
|
|
130
141
|
and configuring the container to accept SSH connections using the generated key.
|
|
131
142
|
"""
|
|
132
|
-
if not
|
|
133
|
-
subprocess.run(
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
subprocess.run(
|
|
143
|
+
if not self.ssh_key_path.exists():
|
|
144
|
+
subprocess.run(
|
|
145
|
+
["ssh-keygen", "-t", "rsa", "-b", "2048", "-f", str(self.ssh_key_path), "-N", ""],
|
|
146
|
+
check=True,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
subprocess.run(
|
|
150
|
+
["docker", "exec", self.container.id, "mkdir", "-p", "/root/.ssh"], check=True
|
|
151
|
+
)
|
|
152
|
+
public_key_path = self.ssh_key_path.parent / f"{self.ssh_key_path.name}.pub"
|
|
153
|
+
subprocess.run(
|
|
154
|
+
[
|
|
155
|
+
"docker",
|
|
156
|
+
"cp",
|
|
157
|
+
str(public_key_path),
|
|
158
|
+
f"{self.container.id}:/root/.ssh/authorized_keys",
|
|
159
|
+
],
|
|
160
|
+
check=True,
|
|
161
|
+
)
|
|
162
|
+
subprocess.run(
|
|
163
|
+
["docker", "exec", self.container.id, "chmod", "600", "/root/.ssh/authorized_keys"],
|
|
164
|
+
check=True,
|
|
165
|
+
)
|
|
166
|
+
subprocess.run(
|
|
167
|
+
[
|
|
168
|
+
"docker",
|
|
169
|
+
"exec",
|
|
170
|
+
self.container.id,
|
|
171
|
+
"chown",
|
|
172
|
+
"root:root",
|
|
173
|
+
"/root/.ssh/authorized_keys",
|
|
174
|
+
],
|
|
175
|
+
check=True,
|
|
176
|
+
)
|
|
139
177
|
|
|
140
|
-
def forward(self, argument: Any) ->
|
|
178
|
+
def forward(self, argument: Any) -> tuple[list[LeanResult], dict]:
|
|
141
179
|
"""
|
|
142
180
|
Executes Lean code provided as a string or as an object property.
|
|
143
181
|
|
|
@@ -149,36 +187,36 @@ class LeanEngine(Engine):
|
|
|
149
187
|
"""
|
|
150
188
|
code: str = argument if isinstance(argument, str) else argument.prop.prepared_input
|
|
151
189
|
|
|
152
|
-
rsp:
|
|
153
|
-
err:
|
|
154
|
-
tmpfile_path:
|
|
155
|
-
metadata:
|
|
190
|
+
rsp: LeanResult | None = None
|
|
191
|
+
err: str | None = None
|
|
192
|
+
tmpfile_path: Path | None = None
|
|
193
|
+
metadata: dict[str, Any] = {}
|
|
156
194
|
try:
|
|
157
195
|
with tempfile.NamedTemporaryFile(delete=False, suffix=".lean") as tmpfile:
|
|
158
196
|
tmpfile.write(code.encode())
|
|
159
|
-
tmpfile_path = tmpfile.name
|
|
197
|
+
tmpfile_path = Path(tmpfile.name)
|
|
160
198
|
|
|
161
199
|
output, exec_metadata = self._execute_lean(tmpfile_path)
|
|
162
200
|
metadata.update(exec_metadata)
|
|
163
201
|
|
|
164
202
|
if output:
|
|
165
|
-
rsp = LeanResult({
|
|
203
|
+
rsp = LeanResult({"output": output})
|
|
166
204
|
else:
|
|
167
|
-
metadata[
|
|
205
|
+
metadata["status"] = "no_output"
|
|
168
206
|
except Exception as e:
|
|
169
207
|
err = str(e)
|
|
170
|
-
metadata.update({
|
|
171
|
-
|
|
208
|
+
metadata.update({"status": "error", "message": err})
|
|
209
|
+
UserMessage(f"Error during Lean execution: {err}")
|
|
172
210
|
finally:
|
|
173
|
-
if tmpfile_path and
|
|
174
|
-
|
|
211
|
+
if tmpfile_path and tmpfile_path.exists():
|
|
212
|
+
tmpfile_path.unlink()
|
|
175
213
|
if self.container:
|
|
176
|
-
|
|
214
|
+
UserMessage(f"Killing Docker container '{self.container.id}'...")
|
|
177
215
|
self.container.remove(force=True)
|
|
178
216
|
|
|
179
217
|
return [rsp] if rsp else [], metadata
|
|
180
218
|
|
|
181
|
-
def _execute_lean(self, filepath: str) ->
|
|
219
|
+
def _execute_lean(self, filepath: str) -> tuple[str, dict]:
|
|
182
220
|
"""
|
|
183
221
|
Executes a Lean script within the Docker container via SSH.
|
|
184
222
|
|
|
@@ -191,23 +229,30 @@ class LeanEngine(Engine):
|
|
|
191
229
|
try:
|
|
192
230
|
ssh = paramiko.SSHClient()
|
|
193
231
|
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
194
|
-
ssh.connect(
|
|
232
|
+
ssh.connect(
|
|
233
|
+
self.ssh_host,
|
|
234
|
+
port=self.ssh_port,
|
|
235
|
+
username=self.ssh_user,
|
|
236
|
+
key_filename=str(self.ssh_key_path),
|
|
237
|
+
)
|
|
195
238
|
|
|
196
239
|
elan_path: str = "/usr/local/elan/bin/elan"
|
|
197
240
|
lean_path: str = "/usr/local/elan/bin/lean"
|
|
198
241
|
|
|
199
|
-
|
|
242
|
+
_stdin, stdout, stderr = ssh.exec_command(
|
|
243
|
+
f"{elan_path} default stable && {lean_path} --version"
|
|
244
|
+
)
|
|
200
245
|
output: str = stdout.read().decode()
|
|
201
246
|
error: str = stderr.read().decode()
|
|
202
|
-
|
|
203
|
-
|
|
247
|
+
UserMessage(f"SSH Command Output: {output}")
|
|
248
|
+
UserMessage(f"SSH Command Error: {error}")
|
|
204
249
|
|
|
205
250
|
sftp = ssh.open_sftp()
|
|
206
|
-
remote_path: str = f"/root/{
|
|
251
|
+
remote_path: str = f"/root/{Path(filepath).name}"
|
|
207
252
|
sftp.put(filepath, remote_path)
|
|
208
253
|
sftp.close()
|
|
209
254
|
|
|
210
|
-
|
|
255
|
+
_stdin, stdout, stderr = ssh.exec_command(f"{lean_path} {remote_path}")
|
|
211
256
|
output = stdout.read().decode()
|
|
212
257
|
error = stderr.read().decode()
|
|
213
258
|
|
|
@@ -215,14 +260,13 @@ class LeanEngine(Engine):
|
|
|
215
260
|
ssh.close()
|
|
216
261
|
|
|
217
262
|
if "error" in output.lower() or "error" in error.lower():
|
|
218
|
-
return output, {
|
|
219
|
-
|
|
220
|
-
return "Lean program halted successfully with no output.", {
|
|
221
|
-
|
|
222
|
-
return output, {'status': 'success'}
|
|
263
|
+
return output, {"status": "failure"}
|
|
264
|
+
if not output and not error:
|
|
265
|
+
return "Lean program halted successfully with no output.", {"status": "success"}
|
|
266
|
+
return output, {"status": "success"}
|
|
223
267
|
|
|
224
268
|
except Exception as e:
|
|
225
|
-
|
|
269
|
+
UserMessage(f"SSH command execution failed: {e!s}", raise_with=RuntimeError)
|
|
226
270
|
|
|
227
271
|
def prepare(self, argument: Any) -> None:
|
|
228
272
|
"""
|
|
@@ -1,8 +1,15 @@
|
|
|
1
|
-
from ...mixin import (
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
from ...mixin import (
|
|
2
|
+
ANTHROPIC_CHAT_MODELS,
|
|
3
|
+
ANTHROPIC_REASONING_MODELS,
|
|
4
|
+
DEEPSEEK_CHAT_MODELS,
|
|
5
|
+
DEEPSEEK_REASONING_MODELS,
|
|
6
|
+
GOOGLE_CHAT_MODELS,
|
|
7
|
+
GOOGLE_REASONING_MODELS,
|
|
8
|
+
GROQ_CHAT_MODELS,
|
|
9
|
+
GROQ_REASONING_MODELS,
|
|
10
|
+
OPENAI_CHAT_MODELS,
|
|
11
|
+
OPENAI_REASONING_MODELS,
|
|
12
|
+
)
|
|
6
13
|
from .engine_anthropic_claudeX_chat import ClaudeXChatEngine
|
|
7
14
|
from .engine_anthropic_claudeX_reasoning import ClaudeXReasoningEngine
|
|
8
15
|
from .engine_deepseekX_reasoning import DeepSeekXReasoningEngine
|
|
@@ -13,12 +20,33 @@ from .engine_openai_gptX_reasoning import GPTXReasoningEngine
|
|
|
13
20
|
|
|
14
21
|
# create the mapping
|
|
15
22
|
ENGINE_MAPPING = {
|
|
16
|
-
**
|
|
17
|
-
**
|
|
18
|
-
**
|
|
19
|
-
**
|
|
20
|
-
**
|
|
21
|
-
**
|
|
22
|
-
**
|
|
23
|
-
**
|
|
23
|
+
**dict.fromkeys(ANTHROPIC_CHAT_MODELS, ClaudeXChatEngine),
|
|
24
|
+
**dict.fromkeys(ANTHROPIC_REASONING_MODELS, ClaudeXReasoningEngine),
|
|
25
|
+
**dict.fromkeys(DEEPSEEK_REASONING_MODELS, DeepSeekXReasoningEngine),
|
|
26
|
+
**dict.fromkeys(GOOGLE_REASONING_MODELS, GeminiXReasoningEngine),
|
|
27
|
+
**dict.fromkeys(OPENAI_CHAT_MODELS, GPTXChatEngine),
|
|
28
|
+
**dict.fromkeys(OPENAI_REASONING_MODELS, GPTXReasoningEngine),
|
|
29
|
+
**dict.fromkeys(GROQ_CHAT_MODELS, GroqEngine),
|
|
30
|
+
**dict.fromkeys(GROQ_REASONING_MODELS, GroqEngine),
|
|
24
31
|
}
|
|
32
|
+
|
|
33
|
+
__all__ = [
|
|
34
|
+
"ANTHROPIC_CHAT_MODELS",
|
|
35
|
+
"ANTHROPIC_REASONING_MODELS",
|
|
36
|
+
"DEEPSEEK_CHAT_MODELS",
|
|
37
|
+
"DEEPSEEK_REASONING_MODELS",
|
|
38
|
+
"ENGINE_MAPPING",
|
|
39
|
+
"GOOGLE_CHAT_MODELS",
|
|
40
|
+
"GOOGLE_REASONING_MODELS",
|
|
41
|
+
"GROQ_CHAT_MODELS",
|
|
42
|
+
"GROQ_REASONING_MODELS",
|
|
43
|
+
"OPENAI_CHAT_MODELS",
|
|
44
|
+
"OPENAI_REASONING_MODELS",
|
|
45
|
+
"ClaudeXChatEngine",
|
|
46
|
+
"ClaudeXReasoningEngine",
|
|
47
|
+
"DeepSeekXReasoningEngine",
|
|
48
|
+
"GPTXChatEngine",
|
|
49
|
+
"GPTXReasoningEngine",
|
|
50
|
+
"GeminiXReasoningEngine",
|
|
51
|
+
"GroqEngine",
|
|
52
|
+
]
|