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.
Files changed (134) hide show
  1. symai/__init__.py +269 -173
  2. symai/backend/base.py +123 -110
  3. symai/backend/engines/drawing/engine_bfl.py +45 -44
  4. symai/backend/engines/drawing/engine_gpt_image.py +112 -97
  5. symai/backend/engines/embedding/engine_llama_cpp.py +63 -52
  6. symai/backend/engines/embedding/engine_openai.py +25 -21
  7. symai/backend/engines/execute/engine_python.py +19 -18
  8. symai/backend/engines/files/engine_io.py +104 -95
  9. symai/backend/engines/imagecaptioning/engine_blip2.py +28 -24
  10. symai/backend/engines/imagecaptioning/engine_llavacpp_client.py +102 -79
  11. symai/backend/engines/index/engine_pinecone.py +124 -97
  12. symai/backend/engines/index/engine_qdrant.py +1011 -0
  13. symai/backend/engines/index/engine_vectordb.py +84 -56
  14. symai/backend/engines/lean/engine_lean4.py +96 -52
  15. symai/backend/engines/neurosymbolic/__init__.py +41 -13
  16. symai/backend/engines/neurosymbolic/engine_anthropic_claudeX_chat.py +330 -248
  17. symai/backend/engines/neurosymbolic/engine_anthropic_claudeX_reasoning.py +329 -264
  18. symai/backend/engines/neurosymbolic/engine_cerebras.py +328 -0
  19. symai/backend/engines/neurosymbolic/engine_deepseekX_reasoning.py +118 -88
  20. symai/backend/engines/neurosymbolic/engine_google_geminiX_reasoning.py +344 -299
  21. symai/backend/engines/neurosymbolic/engine_groq.py +173 -115
  22. symai/backend/engines/neurosymbolic/engine_huggingface.py +114 -84
  23. symai/backend/engines/neurosymbolic/engine_llama_cpp.py +144 -118
  24. symai/backend/engines/neurosymbolic/engine_openai_gptX_chat.py +415 -307
  25. symai/backend/engines/neurosymbolic/engine_openai_gptX_reasoning.py +394 -231
  26. symai/backend/engines/ocr/engine_apilayer.py +23 -27
  27. symai/backend/engines/output/engine_stdout.py +10 -13
  28. symai/backend/engines/{webscraping → scrape}/engine_requests.py +101 -54
  29. symai/backend/engines/search/engine_openai.py +100 -88
  30. symai/backend/engines/search/engine_parallel.py +665 -0
  31. symai/backend/engines/search/engine_perplexity.py +44 -45
  32. symai/backend/engines/search/engine_serpapi.py +37 -34
  33. symai/backend/engines/speech_to_text/engine_local_whisper.py +54 -51
  34. symai/backend/engines/symbolic/engine_wolframalpha.py +15 -9
  35. symai/backend/engines/text_to_speech/engine_openai.py +20 -26
  36. symai/backend/engines/text_vision/engine_clip.py +39 -37
  37. symai/backend/engines/userinput/engine_console.py +5 -6
  38. symai/backend/mixin/__init__.py +13 -0
  39. symai/backend/mixin/anthropic.py +48 -38
  40. symai/backend/mixin/deepseek.py +6 -5
  41. symai/backend/mixin/google.py +7 -4
  42. symai/backend/mixin/groq.py +2 -4
  43. symai/backend/mixin/openai.py +140 -110
  44. symai/backend/settings.py +87 -20
  45. symai/chat.py +216 -123
  46. symai/collect/__init__.py +7 -1
  47. symai/collect/dynamic.py +80 -70
  48. symai/collect/pipeline.py +67 -51
  49. symai/collect/stats.py +161 -109
  50. symai/components.py +707 -360
  51. symai/constraints.py +24 -12
  52. symai/core.py +1857 -1233
  53. symai/core_ext.py +83 -80
  54. symai/endpoints/api.py +166 -104
  55. symai/extended/.DS_Store +0 -0
  56. symai/extended/__init__.py +46 -12
  57. symai/extended/api_builder.py +29 -21
  58. symai/extended/arxiv_pdf_parser.py +23 -14
  59. symai/extended/bibtex_parser.py +9 -6
  60. symai/extended/conversation.py +156 -126
  61. symai/extended/document.py +50 -30
  62. symai/extended/file_merger.py +57 -14
  63. symai/extended/graph.py +51 -32
  64. symai/extended/html_style_template.py +18 -14
  65. symai/extended/interfaces/blip_2.py +2 -3
  66. symai/extended/interfaces/clip.py +4 -3
  67. symai/extended/interfaces/console.py +9 -1
  68. symai/extended/interfaces/dall_e.py +4 -2
  69. symai/extended/interfaces/file.py +2 -0
  70. symai/extended/interfaces/flux.py +4 -2
  71. symai/extended/interfaces/gpt_image.py +16 -7
  72. symai/extended/interfaces/input.py +2 -1
  73. symai/extended/interfaces/llava.py +1 -2
  74. symai/extended/interfaces/{naive_webscraping.py → naive_scrape.py} +4 -3
  75. symai/extended/interfaces/naive_vectordb.py +9 -10
  76. symai/extended/interfaces/ocr.py +5 -3
  77. symai/extended/interfaces/openai_search.py +2 -0
  78. symai/extended/interfaces/parallel.py +30 -0
  79. symai/extended/interfaces/perplexity.py +2 -0
  80. symai/extended/interfaces/pinecone.py +12 -9
  81. symai/extended/interfaces/python.py +2 -0
  82. symai/extended/interfaces/serpapi.py +3 -1
  83. symai/extended/interfaces/terminal.py +2 -4
  84. symai/extended/interfaces/tts.py +3 -2
  85. symai/extended/interfaces/whisper.py +3 -2
  86. symai/extended/interfaces/wolframalpha.py +2 -1
  87. symai/extended/metrics/__init__.py +11 -1
  88. symai/extended/metrics/similarity.py +14 -13
  89. symai/extended/os_command.py +39 -29
  90. symai/extended/packages/__init__.py +29 -3
  91. symai/extended/packages/symdev.py +51 -43
  92. symai/extended/packages/sympkg.py +41 -35
  93. symai/extended/packages/symrun.py +63 -50
  94. symai/extended/repo_cloner.py +14 -12
  95. symai/extended/seo_query_optimizer.py +15 -13
  96. symai/extended/solver.py +116 -91
  97. symai/extended/summarizer.py +12 -10
  98. symai/extended/taypan_interpreter.py +17 -18
  99. symai/extended/vectordb.py +122 -92
  100. symai/formatter/__init__.py +9 -1
  101. symai/formatter/formatter.py +51 -47
  102. symai/formatter/regex.py +70 -69
  103. symai/functional.py +325 -176
  104. symai/imports.py +190 -147
  105. symai/interfaces.py +57 -28
  106. symai/memory.py +45 -35
  107. symai/menu/screen.py +28 -19
  108. symai/misc/console.py +66 -56
  109. symai/misc/loader.py +8 -5
  110. symai/models/__init__.py +17 -1
  111. symai/models/base.py +395 -236
  112. symai/models/errors.py +1 -2
  113. symai/ops/__init__.py +32 -22
  114. symai/ops/measures.py +24 -25
  115. symai/ops/primitives.py +1149 -731
  116. symai/post_processors.py +58 -50
  117. symai/pre_processors.py +86 -82
  118. symai/processor.py +21 -13
  119. symai/prompts.py +764 -685
  120. symai/server/huggingface_server.py +135 -49
  121. symai/server/llama_cpp_server.py +21 -11
  122. symai/server/qdrant_server.py +206 -0
  123. symai/shell.py +100 -42
  124. symai/shellsv.py +700 -492
  125. symai/strategy.py +630 -346
  126. symai/symbol.py +368 -322
  127. symai/utils.py +100 -78
  128. {symbolicai-0.21.0.dist-info → symbolicai-1.1.0.dist-info}/METADATA +22 -10
  129. symbolicai-1.1.0.dist-info/RECORD +168 -0
  130. symbolicai-0.21.0.dist-info/RECORD +0 -162
  131. {symbolicai-0.21.0.dist-info → symbolicai-1.1.0.dist-info}/WHEEL +0 -0
  132. {symbolicai-0.21.0.dist-info → symbolicai-1.1.0.dist-info}/entry_points.txt +0 -0
  133. {symbolicai-0.21.0.dist-info → symbolicai-1.1.0.dist-info}/licenses/LICENSE +0 -0
  134. {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 CustomUserWarning
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 = ['Sorry, failed to interact with index. Please check index name and try again later:', str(e)]
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 = {'matches': [{'metadata': {'text': '\n'.join(message)}}]}
37
- return [v['metadata']['text'] for v in res]
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
- match = match.strip()
44
- if match.startswith('# ----[FILE_START]') and '# ----[FILE_END]' in match:
45
- m = match.split('[FILE_CONTENT]:')[-1].strip()
46
- splits = m.split('# ----[FILE_END]')
47
- assert len(splits) >= 2, 'Invalid file format: {}'.format(splits)
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 = ','.join(splits[1:]) # TODO: check why there are multiple file names
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, match
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
- content = '\n'.join([' ' + line for line in content.split('\n')])
59
- str_view += f'* {filename}\n{content}\n\n'
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
- {'-=-' * 13}
66
+ {"-=-" * 13}
63
67
 
64
68
  Query: {self._query}
65
69
 
66
- {'-=-' * 13}
70
+ {"-=-" * 13}
67
71
 
68
72
  Matches:
69
73
 
70
74
  {str_view}
71
- {'-=-' * 13}
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'<ul>{doc_str}</ul>'
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 = 'dataindex'
88
+ _default_index_name = "dataindex"
85
89
  _default_index_dims = 768
86
90
  _default_index_top_k = 5
87
- _default_index_metric = 'cosine'
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
- self,
92
- index_name=_default_index_name,
93
- index_dims=_default_index_dims,
94
- index_top_k=_default_index_top_k,
95
- index_metric=_default_index_metric,
96
- index_dict=_index_dict,
97
- index_storage_file=_index_storage_file,
98
- **kwargs
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 not self.config['INDEXING_ENGINE_API_KEY'] or self.config['INDEXING_ENGINE_API_KEY'] == '':
115
- return 'index'
116
- return super().id() # default to unregistered
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 == 'search':
140
+ if operation == "search":
133
141
  if isinstance(query, list) and len(query) > 1:
134
- CustomUserWarning('VectorDB indexing engine does not support multiple queries. Pass a single string query instead.', raise_with=ValueError)
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](vector=query_vector, top_k=top_k, return_similarities=similarities)
137
- rsp = [{'metadata': {'text': result}} for result in results]
138
- elif operation == 'add':
139
- assert isinstance(query, list), 'VectorDB indexing requires a list of queries at insertion, even if there is only one query.'
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 == 'config':
147
- assert kwargs, 'Please provide a configuration by passing the appropriate kwargs. Currently, only `load`, `save`, `purge`.'
148
- maybe_as_prompt = kwargs.get('prompt')
149
- if kwargs.get('load', maybe_as_prompt == 'load'):
150
- assert storage_file, 'Please provide a `storage_file` path to load the pre-computed index.'
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('save', maybe_as_prompt == 'save'):
171
+ elif kwargs.get("save", maybe_as_prompt == "save"):
153
172
  self.save(index_name, storage_file)
154
- elif kwargs.get('purge', maybe_as_prompt == 'purge'):
173
+ elif kwargs.get("purge", maybe_as_prompt == "purge"):
155
174
  self.purge(index_name)
156
175
  else:
157
- CustomUserWarning('Invalid configuration; please use either "load", "save", or "purge".', raise_with=ValueError)
176
+ UserMessage(
177
+ 'Invalid configuration; please use either "load", "save", or "purge".',
178
+ raise_with=ValueError,
179
+ )
158
180
  else:
159
- CustomUserWarning('Invalid operation; please use either "search", "add", or "config".', raise_with=ValueError)
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 #@NOTE: the VectorDBIndexEngine class uses precomputed embeddings so the model is not needed in the VectorDB class
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, 'VectorDB indexing engine does not support 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 = None, storage_file = None):
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 = None):
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
- import tempfile
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
- def __init__(self, value: Dict[str, str]) -> None:
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 = 'localhost',
48
+ ssh_host: str = "localhost",
43
49
  ssh_port: int = 2222,
44
- ssh_user: str = 'root',
45
- ssh_key_path: str = '~/.ssh/id_rsa'
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: str = os.path.expanduser(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 'lean4'
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 = self.docker_client.containers.get(container_name)
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
- print(f"No existing container named '{container_name}' found. Proceeding to create a new one.")
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: str = temp_dockerfile.name
123
+ dockerfile_path = Path(temp_dockerfile.name)
114
124
 
115
125
  image: docker.models.images.Image
116
- image, _ = self.docker_client.images.build(path=os.path.dirname(dockerfile_path), dockerfile=dockerfile_path, tag="lean4-container-image")
117
- os.remove(dockerfile_path)
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 os.path.exists(self.ssh_key_path):
133
- subprocess.run(['ssh-keygen', '-t', 'rsa', '-b', '2048', '-f', self.ssh_key_path, '-N', ''], check=True)
134
-
135
- subprocess.run(['docker', 'exec', self.container.id, 'mkdir', '-p', '/root/.ssh'], check=True)
136
- subprocess.run(['docker', 'cp', f'{self.ssh_key_path}.pub', f'{self.container.id}:/root/.ssh/authorized_keys'], check=True)
137
- subprocess.run(['docker', 'exec', self.container.id, 'chmod', '600', '/root/.ssh/authorized_keys'], check=True)
138
- subprocess.run(['docker', 'exec', self.container.id, 'chown', 'root:root', '/root/.ssh/authorized_keys'], check=True)
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) -> Tuple[List[LeanResult], dict]:
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: Optional[LeanResult] = None
153
- err: Optional[str] = None
154
- tmpfile_path: Optional[str] = None
155
- metadata: Dict[str, Any] = {}
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({'output': output})
203
+ rsp = LeanResult({"output": output})
166
204
  else:
167
- metadata['status'] = 'no_output'
205
+ metadata["status"] = "no_output"
168
206
  except Exception as e:
169
207
  err = str(e)
170
- metadata.update({'status': 'error', 'message': err})
171
- print(f"Error during Lean execution: {err}")
208
+ metadata.update({"status": "error", "message": err})
209
+ UserMessage(f"Error during Lean execution: {err}")
172
210
  finally:
173
- if tmpfile_path and os.path.exists(tmpfile_path):
174
- os.remove(tmpfile_path)
211
+ if tmpfile_path and tmpfile_path.exists():
212
+ tmpfile_path.unlink()
175
213
  if self.container:
176
- print(f"Killing Docker container '{self.container.id}'...")
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) -> Tuple[str, dict]:
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(self.ssh_host, port=self.ssh_port, username=self.ssh_user, key_filename=self.ssh_key_path)
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
- stdin, stdout, stderr = ssh.exec_command(f"{elan_path} default stable && {lean_path} --version")
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
- print("SSH Command Output:", output)
203
- print("SSH Command Error:", error)
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/{os.path.basename(filepath)}"
251
+ remote_path: str = f"/root/{Path(filepath).name}"
207
252
  sftp.put(filepath, remote_path)
208
253
  sftp.close()
209
254
 
210
- stdin, stdout, stderr = ssh.exec_command(f"{lean_path} {remote_path}")
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, {'status': 'failure'}
219
- elif not output and not error:
220
- return "Lean program halted successfully with no output.", {'status': 'success'}
221
- else:
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
- raise RuntimeError(f"SSH command execution failed: {str(e)}")
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 (ANTHROPIC_CHAT_MODELS, ANTHROPIC_REASONING_MODELS,
2
- DEEPSEEK_CHAT_MODELS, DEEPSEEK_REASONING_MODELS,
3
- GOOGLE_CHAT_MODELS, GOOGLE_REASONING_MODELS,
4
- GROQ_CHAT_MODELS, GROQ_REASONING_MODELS,
5
- OPENAI_CHAT_MODELS, OPENAI_REASONING_MODELS)
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
- **{model_name: ClaudeXChatEngine for model_name in ANTHROPIC_CHAT_MODELS},
17
- **{model_name: ClaudeXReasoningEngine for model_name in ANTHROPIC_REASONING_MODELS},
18
- **{model_name: DeepSeekXReasoningEngine for model_name in DEEPSEEK_REASONING_MODELS},
19
- **{model_name: GeminiXReasoningEngine for model_name in GOOGLE_REASONING_MODELS},
20
- **{model_name: GPTXChatEngine for model_name in OPENAI_CHAT_MODELS},
21
- **{model_name: GPTXReasoningEngine for model_name in OPENAI_REASONING_MODELS},
22
- **{model_name: GroqEngine for model_name in GROQ_CHAT_MODELS},
23
- **{model_name: GroqEngine for model_name in GROQ_REASONING_MODELS},
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
+ ]