MindsDB 25.7.3.0__py3-none-any.whl → 25.8.2.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.

Potentially problematic release.


This version of MindsDB might be problematic. Click here for more details.

Files changed (102) hide show
  1. mindsdb/__about__.py +1 -1
  2. mindsdb/__main__.py +11 -1
  3. mindsdb/api/a2a/common/server/server.py +16 -6
  4. mindsdb/api/executor/command_executor.py +215 -150
  5. mindsdb/api/executor/datahub/datanodes/project_datanode.py +14 -3
  6. mindsdb/api/executor/planner/plan_join.py +3 -0
  7. mindsdb/api/executor/planner/plan_join_ts.py +117 -100
  8. mindsdb/api/executor/planner/query_planner.py +1 -0
  9. mindsdb/api/executor/sql_query/steps/apply_predictor_step.py +54 -85
  10. mindsdb/api/executor/sql_query/steps/fetch_dataframe.py +21 -24
  11. mindsdb/api/executor/sql_query/steps/fetch_dataframe_partition.py +9 -3
  12. mindsdb/api/executor/sql_query/steps/subselect_step.py +11 -8
  13. mindsdb/api/executor/utilities/mysql_to_duckdb_functions.py +264 -0
  14. mindsdb/api/executor/utilities/sql.py +30 -0
  15. mindsdb/api/http/initialize.py +18 -44
  16. mindsdb/api/http/namespaces/agents.py +23 -20
  17. mindsdb/api/http/namespaces/chatbots.py +83 -120
  18. mindsdb/api/http/namespaces/file.py +1 -1
  19. mindsdb/api/http/namespaces/jobs.py +38 -60
  20. mindsdb/api/http/namespaces/tree.py +69 -61
  21. mindsdb/api/http/namespaces/views.py +56 -72
  22. mindsdb/api/mcp/start.py +2 -0
  23. mindsdb/api/mysql/mysql_proxy/utilities/dump.py +3 -2
  24. mindsdb/integrations/handlers/autogluon_handler/requirements.txt +1 -1
  25. mindsdb/integrations/handlers/autosklearn_handler/requirements.txt +1 -1
  26. mindsdb/integrations/handlers/bigquery_handler/bigquery_handler.py +25 -5
  27. mindsdb/integrations/handlers/chromadb_handler/chromadb_handler.py +3 -3
  28. mindsdb/integrations/handlers/db2_handler/db2_handler.py +19 -23
  29. mindsdb/integrations/handlers/flaml_handler/requirements.txt +1 -1
  30. mindsdb/integrations/handlers/gong_handler/__about__.py +2 -0
  31. mindsdb/integrations/handlers/gong_handler/__init__.py +30 -0
  32. mindsdb/integrations/handlers/gong_handler/connection_args.py +37 -0
  33. mindsdb/integrations/handlers/gong_handler/gong_handler.py +164 -0
  34. mindsdb/integrations/handlers/gong_handler/gong_tables.py +508 -0
  35. mindsdb/integrations/handlers/gong_handler/icon.svg +25 -0
  36. mindsdb/integrations/handlers/gong_handler/test_gong_handler.py +125 -0
  37. mindsdb/integrations/handlers/google_calendar_handler/google_calendar_tables.py +82 -73
  38. mindsdb/integrations/handlers/hubspot_handler/requirements.txt +1 -1
  39. mindsdb/integrations/handlers/huggingface_handler/__init__.py +8 -12
  40. mindsdb/integrations/handlers/huggingface_handler/finetune.py +203 -223
  41. mindsdb/integrations/handlers/huggingface_handler/huggingface_handler.py +360 -383
  42. mindsdb/integrations/handlers/huggingface_handler/requirements.txt +7 -7
  43. mindsdb/integrations/handlers/huggingface_handler/requirements_cpu.txt +7 -7
  44. mindsdb/integrations/handlers/huggingface_handler/settings.py +25 -25
  45. mindsdb/integrations/handlers/langchain_handler/langchain_handler.py +83 -77
  46. mindsdb/integrations/handlers/lightwood_handler/requirements.txt +4 -4
  47. mindsdb/integrations/handlers/litellm_handler/litellm_handler.py +5 -2
  48. mindsdb/integrations/handlers/litellm_handler/settings.py +2 -1
  49. mindsdb/integrations/handlers/openai_handler/constants.py +11 -30
  50. mindsdb/integrations/handlers/openai_handler/helpers.py +27 -34
  51. mindsdb/integrations/handlers/openai_handler/openai_handler.py +14 -12
  52. mindsdb/integrations/handlers/pgvector_handler/pgvector_handler.py +106 -90
  53. mindsdb/integrations/handlers/postgres_handler/postgres_handler.py +41 -39
  54. mindsdb/integrations/handlers/salesforce_handler/constants.py +215 -0
  55. mindsdb/integrations/handlers/salesforce_handler/salesforce_handler.py +141 -80
  56. mindsdb/integrations/handlers/salesforce_handler/salesforce_tables.py +0 -1
  57. mindsdb/integrations/handlers/tpot_handler/requirements.txt +1 -1
  58. mindsdb/integrations/handlers/web_handler/urlcrawl_helpers.py +32 -17
  59. mindsdb/integrations/handlers/web_handler/web_handler.py +19 -22
  60. mindsdb/integrations/libs/llm/config.py +0 -14
  61. mindsdb/integrations/libs/llm/utils.py +0 -15
  62. mindsdb/integrations/libs/vectordatabase_handler.py +10 -1
  63. mindsdb/integrations/utilities/files/file_reader.py +5 -19
  64. mindsdb/integrations/utilities/handler_utils.py +32 -12
  65. mindsdb/integrations/utilities/rag/rerankers/base_reranker.py +1 -1
  66. mindsdb/interfaces/agents/agents_controller.py +246 -149
  67. mindsdb/interfaces/agents/constants.py +0 -1
  68. mindsdb/interfaces/agents/langchain_agent.py +11 -6
  69. mindsdb/interfaces/data_catalog/data_catalog_loader.py +4 -4
  70. mindsdb/interfaces/database/database.py +38 -13
  71. mindsdb/interfaces/database/integrations.py +20 -5
  72. mindsdb/interfaces/database/projects.py +174 -23
  73. mindsdb/interfaces/database/views.py +86 -60
  74. mindsdb/interfaces/jobs/jobs_controller.py +103 -110
  75. mindsdb/interfaces/knowledge_base/controller.py +33 -6
  76. mindsdb/interfaces/knowledge_base/evaluate.py +2 -1
  77. mindsdb/interfaces/knowledge_base/executor.py +24 -0
  78. mindsdb/interfaces/knowledge_base/preprocessing/document_preprocessor.py +6 -10
  79. mindsdb/interfaces/knowledge_base/preprocessing/text_splitter.py +73 -0
  80. mindsdb/interfaces/query_context/context_controller.py +111 -145
  81. mindsdb/interfaces/skills/skills_controller.py +18 -6
  82. mindsdb/interfaces/storage/db.py +40 -6
  83. mindsdb/interfaces/variables/variables_controller.py +8 -15
  84. mindsdb/utilities/config.py +5 -3
  85. mindsdb/utilities/fs.py +54 -17
  86. mindsdb/utilities/functions.py +72 -60
  87. mindsdb/utilities/log.py +38 -6
  88. mindsdb/utilities/ps.py +7 -7
  89. {mindsdb-25.7.3.0.dist-info → mindsdb-25.8.2.0.dist-info}/METADATA +282 -268
  90. {mindsdb-25.7.3.0.dist-info → mindsdb-25.8.2.0.dist-info}/RECORD +94 -92
  91. mindsdb/integrations/handlers/anyscale_endpoints_handler/__about__.py +0 -9
  92. mindsdb/integrations/handlers/anyscale_endpoints_handler/__init__.py +0 -20
  93. mindsdb/integrations/handlers/anyscale_endpoints_handler/anyscale_endpoints_handler.py +0 -290
  94. mindsdb/integrations/handlers/anyscale_endpoints_handler/creation_args.py +0 -14
  95. mindsdb/integrations/handlers/anyscale_endpoints_handler/icon.svg +0 -4
  96. mindsdb/integrations/handlers/anyscale_endpoints_handler/requirements.txt +0 -2
  97. mindsdb/integrations/handlers/anyscale_endpoints_handler/settings.py +0 -51
  98. mindsdb/integrations/handlers/anyscale_endpoints_handler/tests/test_anyscale_endpoints_handler.py +0 -212
  99. /mindsdb/integrations/handlers/{anyscale_endpoints_handler/tests/__init__.py → gong_handler/requirements.txt} +0 -0
  100. {mindsdb-25.7.3.0.dist-info → mindsdb-25.8.2.0.dist-info}/WHEEL +0 -0
  101. {mindsdb-25.7.3.0.dist-info → mindsdb-25.8.2.0.dist-info}/licenses/LICENSE +0 -0
  102. {mindsdb-25.7.3.0.dist-info → mindsdb-25.8.2.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,264 @@
1
+ from mindsdb_sql_parser.ast import Identifier, Function, Constant, BinaryOperation
2
+
3
+
4
+ def adapt_char_fn(node: Function) -> Function | None:
5
+ """Replace MySQL's multy-arg CHAR call to chain of DuckDB's CHR calls
6
+
7
+ Example:
8
+ CHAR(77, 78, 79) => CHR(77) || CHR(78) || CHR(79)
9
+
10
+ Args:
11
+ node (Function): Function node to adapt
12
+
13
+ Returns:
14
+ Function | None: Adapted function node
15
+ """
16
+ if len(node.args) == 1:
17
+ node.op = "chr"
18
+ return node
19
+
20
+ acc = None
21
+ for arg in node.args:
22
+ fn = Function(op="chr", args=[arg])
23
+ if acc is None:
24
+ acc = fn
25
+ continue
26
+ acc = BinaryOperation("||", args=[acc, fn])
27
+
28
+ acc.parentheses = True
29
+ acc.alias = node.alias
30
+ return acc
31
+
32
+
33
+ def adapt_locate_fn(node: Function) -> Function | None:
34
+ """Replace MySQL's LOCATE (or INSTR) call to DuckDB's STRPOS call
35
+
36
+ Example:
37
+ LOCATE('bar', 'foobarbar') => STRPOS('foobarbar', 'bar')
38
+ INSTR('foobarbar', 'bar') => STRPOS('foobarbar', 'bar')
39
+ LOCATE('bar', 'foobarbar', 3) => ValueError (there is no analogue in DuckDB)
40
+
41
+ Args:
42
+ node (Function): Function node to adapt
43
+
44
+ Returns:
45
+ Function | None: Adapted function node
46
+
47
+ Raises:
48
+ ValueError: If the function has 3 arguments
49
+ """
50
+ if len(node.args) == 3:
51
+ raise ValueError("MySQL LOCATE function with 3 arguments is not supported")
52
+ if node.op == "locate":
53
+ node.args = [node.args[1], node.args[0]]
54
+ elif node.op == "insrt":
55
+ node.args = [node.args[0], node.args[1]]
56
+ node.op = "strpos"
57
+
58
+
59
+ def adapt_unhex_fn(node: Function) -> None:
60
+ """Check MySQL's UNHEX function call arguments to ensure they are strings,
61
+ because DuckDB's UNHEX accepts only string arguments, while MySQL's UNHEX can accept integer arguments.
62
+ NOTE: if return dataframe from duckdb then unhex values are array - this may be an issue
63
+
64
+ Args:
65
+ node (Function): Function node to adapt
66
+
67
+ Returns:
68
+ None
69
+
70
+ Raises:
71
+ ValueError: If the function argument is not a string
72
+ """
73
+ for arg in node.args:
74
+ if not isinstance(arg, (str, bytes)):
75
+ raise ValueError("MySQL UNHEX function argument must be a string")
76
+
77
+
78
+ def adapt_format_fn(node: Function) -> None:
79
+ """Adapt MySQL's FORMAT function to DuckDB's FORMAT function
80
+
81
+ Example:
82
+ FORMAT(1234567.89, 0) => FORMAT('{:,.0f}', 1234567.89)
83
+ FORMAT(1234567.89, 2) => FORMAT('{:,.2f}', 1234567.89)
84
+ FORMAT(name, 2) => FORMAT('{:,.2f}', name)
85
+ FORMAT('{:.2f}', 1234567.89) => FORMAT('{:,.2f}', 1234567.89) # no changes for original style
86
+
87
+ Args:
88
+ node (Function): Function node to adapt
89
+
90
+ Returns:
91
+ None
92
+
93
+ Raises:
94
+ ValueError: If MySQL's function has 3rd 'locale' argument, like FORMAT(12332.2, 2, 'de_DE')
95
+ """
96
+ match node.args[0], node.args[1]:
97
+ case Constant(value=(int() | float())), Constant(value=int()):
98
+ ...
99
+ case Identifier(), Constant(value=int()):
100
+ ...
101
+ case _:
102
+ return node
103
+
104
+ if len(node.args) > 2:
105
+ raise ValueError("'locale' argument of 'format' function is not supported")
106
+ decimal_places = node.args[1].value
107
+
108
+ if isinstance(node.args[0], Constant):
109
+ node.args[1].value = node.args[0].value
110
+ node.args[0].value = f"{{:,.{decimal_places}f}}"
111
+ else:
112
+ node.args[1] = node.args[0]
113
+ node.args[0] = Constant(f"{{:,.{decimal_places}f}}")
114
+
115
+
116
+ def adapt_sha2_fn(node: Function) -> None:
117
+ """Adapt MySQL's SHA2 function to DuckDB's SHA256 function
118
+
119
+ Example:
120
+ SHA2('test', 256) => SHA256('test')
121
+
122
+ Args:
123
+ node (Function): Function node to adapt
124
+
125
+ Returns:
126
+ None
127
+
128
+ Raises:
129
+ ValueError: If the function has more than 1 argument or the argument is not 256
130
+ """
131
+ if len(node.args) > 1 and node.args[1].value != 256:
132
+ raise ValueError("Only sha256 is supported")
133
+ node.op = "sha256"
134
+ node.args = [node.args[0]]
135
+
136
+
137
+ def adapt_length_fn(node: Function) -> None:
138
+ """Adapt MySQL's LENGTH function to DuckDB's STRLEN function
139
+ NOTE: duckdb also have LENGTH, therefore it can not be used
140
+
141
+ Example:
142
+ LENGTH('test') => STRLEN('test')
143
+
144
+ Args:
145
+ node (Function): Function node to adapt
146
+
147
+ Returns:
148
+ None
149
+ """
150
+ node.op = "strlen"
151
+
152
+
153
+ def adapt_regexp_substr_fn(node: Function) -> None:
154
+ """Adapt MySQL's REGEXP_SUBSTR function to DuckDB's REGEXP_EXTRACT function
155
+
156
+ Example:
157
+ REGEXP_SUBSTR('foobarbar', 'bar', 1, 1) => REGEXP_EXTRACT('foobarbar', 'bar')
158
+
159
+ Args:
160
+ node (Function): Function node to adapt
161
+
162
+ Returns:
163
+ None
164
+
165
+ Raises:
166
+ ValueError: If the function has more than 2 arguments or 3rd or 4th argument is not 1
167
+ """
168
+ if (
169
+ len(node.args) == 3
170
+ and node.args[2].value != 1
171
+ or len(node.args) == 4
172
+ and (node.args[3].value != 1 or node.args[2].value != 1)
173
+ or len(node.args) > 4
174
+ ):
175
+ raise ValueError("Only 2 arguments are supported for REGEXP_SUBSTR function")
176
+ node.args = node.args[:2]
177
+ node.op = "regexp_extract"
178
+
179
+
180
+ def adapt_substring_index_fn(node: Function) -> BinaryOperation | Function:
181
+ """Adapt MySQL's SUBSTRING_INDEX function to DuckDB's SPLIT_PART function
182
+
183
+ Example:
184
+ SUBSTRING_INDEX('a.b.c.d', '.', 1) => SPLIT_PART('a.b.c.d', '.', 1)
185
+ SUBSTRING_INDEX('a.b.c.d', '.', 2) => CONCAT_WS('.', SPLIT_PART('a.b.c.d', '.', 1), SPLIT_PART('a.b.c.d', '.', 2))
186
+
187
+ Args:
188
+ node (Function): Function node to adapt
189
+
190
+ Returns:
191
+ BinaryOperation | Function: Binary operation node or function node
192
+
193
+ Raises:
194
+ ValueError: If the function has more than 3 arguments or the 3rd argument is not 1
195
+ """
196
+ if len(node.args[1].value) > 1:
197
+ raise ValueError("Only one car in separator")
198
+
199
+ if node.args[2].value == 1:
200
+ node.op = "split_part"
201
+ return node
202
+
203
+ acc = [node.args[1]]
204
+ for i in range(node.args[2].value):
205
+ fn = Function(op="split_part", args=[node.args[0], node.args[1], Constant(i + 1)])
206
+ acc.append(fn)
207
+
208
+ acc = Function(op="concat_ws", args=acc)
209
+ acc.alias = node.alias
210
+ return acc
211
+
212
+
213
+ def adapt_curtime_fn(node: Function) -> BinaryOperation:
214
+ """Adapt MySQL's CURTIME function to DuckDB's GET_CURRENT_TIME function.
215
+ To get the same type as MySQL's CURTIME function, we need to cast the result to time type.
216
+
217
+ Example:
218
+ CURTIME() => GET_CURRENT_TIME()::time
219
+
220
+ Args:
221
+ node (Function): Function node to adapt
222
+
223
+ Returns:
224
+ BinaryOperation: Binary operation node
225
+ """
226
+ return BinaryOperation("::", args=[Function(op="get_current_time", args=[]), Identifier("time")], alias=node.alias)
227
+
228
+
229
+ def adapt_timestampdiff_fn(node: Function) -> None:
230
+ """Adapt MySQL's TIMESTAMPDIFF function to DuckDB's DATE_DIFF function
231
+ NOTE: Looks like cast string args to timestamp works in most cases, but there may be some exceptions.
232
+
233
+ Example:
234
+ TIMESTAMPDIFF(YEAR, '2000-02-01', '2003-05-01') => DATE_DIFF('year', timestamp '2000-02-01', timestamp '2003-05-01')
235
+
236
+ Args:
237
+ node (Function): Function node to adapt
238
+
239
+ Returns:
240
+ None
241
+ """
242
+ node.op = "date_diff"
243
+ node.args[0] = Constant(node.args[0].parts[0])
244
+ node.args[1] = BinaryOperation(" ", args=[Identifier("timestamp"), node.args[1]])
245
+ node.args[2] = BinaryOperation(" ", args=[Identifier("timestamp"), node.args[2]])
246
+
247
+
248
+ def adapt_extract_fn(node: Function) -> None:
249
+ """Adapt MySQL's EXTRACT function to DuckDB's EXTRACT function
250
+ TODO: multi-part args, like YEAR_MONTH, is not supported yet
251
+ NOTE: Looks like adding 'timestamp' works in most cases, but there may be some exceptions.
252
+
253
+ Example:
254
+ EXTRACT(YEAR FROM '2000-02-01') => EXTRACT('year' from timestamp '2000-02-01')
255
+
256
+ Args:
257
+ node (Function): Function node to adapt
258
+
259
+ Returns:
260
+ None
261
+ """
262
+ node.args[0] = Constant(node.args[0].parts[0])
263
+ if not isinstance(node.from_arg, Identifier):
264
+ node.from_arg = BinaryOperation(" ", args=[Identifier("timestamp"), node.from_arg])
@@ -14,6 +14,19 @@ from mindsdb.utilities.exception import format_db_error_message
14
14
  from mindsdb.utilities.functions import resolve_table_identifier, resolve_model_identifier
15
15
  from mindsdb.utilities.json_encoder import CustomJSONEncoder
16
16
  from mindsdb.utilities.render.sqlalchemy_render import SqlalchemyRender
17
+ from mindsdb.api.executor.utilities.mysql_to_duckdb_functions import (
18
+ adapt_char_fn,
19
+ adapt_locate_fn,
20
+ adapt_unhex_fn,
21
+ adapt_format_fn,
22
+ adapt_sha2_fn,
23
+ adapt_length_fn,
24
+ adapt_regexp_substr_fn,
25
+ adapt_substring_index_fn,
26
+ adapt_curtime_fn,
27
+ adapt_timestampdiff_fn,
28
+ adapt_extract_fn,
29
+ )
17
30
 
18
31
  logger = log.getLogger(__name__)
19
32
 
@@ -185,6 +198,23 @@ def query_df(df, query, session=None):
185
198
  if isinstance(node, Function):
186
199
  fnc_name = node.op.lower()
187
200
 
201
+ mysql_to_duck_fn_map = {
202
+ "char": adapt_char_fn,
203
+ "locate": adapt_locate_fn,
204
+ "insrt": adapt_locate_fn,
205
+ "unhex": adapt_unhex_fn,
206
+ "format": adapt_format_fn,
207
+ "sha2": adapt_sha2_fn,
208
+ "length": adapt_length_fn,
209
+ "regexp_substr": adapt_regexp_substr_fn,
210
+ "substring_index": adapt_substring_index_fn,
211
+ "curtime": adapt_curtime_fn,
212
+ "timestampdiff": adapt_timestampdiff_fn,
213
+ "extract": adapt_extract_fn,
214
+ }
215
+ if fnc_name in mysql_to_duck_fn_map:
216
+ return mysql_to_duck_fn_map[fnc_name](node)
217
+
188
218
  if fnc_name == "database" and len(node.args) == 0:
189
219
  if session is not None:
190
220
  cur_db = session.database
@@ -43,6 +43,7 @@ from mindsdb.api.http.namespaces.webhooks import ns_conf as webhooks_ns
43
43
  from mindsdb.interfaces.database.integrations import integration_controller
44
44
  from mindsdb.interfaces.database.database import DatabaseController
45
45
  from mindsdb.interfaces.file.file_controller import FileController
46
+ from mindsdb.interfaces.jobs.jobs_controller import JobsController
46
47
  from mindsdb.interfaces.storage import db
47
48
  from mindsdb.metrics.server import init_metrics
48
49
  from mindsdb.utilities import log
@@ -109,9 +110,7 @@ def get_last_compatible_gui_version() -> Version:
109
110
  return False
110
111
 
111
112
  if res.status_code != 200:
112
- logger.error(
113
- f"Cant get compatible-config.json: returned status code = {res.status_code}"
114
- )
113
+ logger.error(f"Cant get compatible-config.json: returned status code = {res.status_code}")
115
114
  return False
116
115
 
117
116
  try:
@@ -132,10 +131,7 @@ def get_last_compatible_gui_version() -> Version:
132
131
  else:
133
132
  mindsdb_lv = parse_version(el["mindsdb_version"])
134
133
  gui_lv = parse_version(el["gui_version"])
135
- if (
136
- mindsdb_lv.base_version not in gui_versions
137
- or gui_lv > gui_versions[mindsdb_lv.base_version]
138
- ):
134
+ if mindsdb_lv.base_version not in gui_versions or gui_lv > gui_versions[mindsdb_lv.base_version]:
139
135
  gui_versions[mindsdb_lv.base_version] = gui_lv
140
136
  if max_mindsdb_lv is None or max_mindsdb_lv < mindsdb_lv:
141
137
  max_mindsdb_lv = mindsdb_lv
@@ -151,9 +147,7 @@ def get_last_compatible_gui_version() -> Version:
151
147
  gui_version_lv = max_gui_lv
152
148
  else:
153
149
  lower_versions = {
154
- key: value
155
- for key, value in gui_versions.items()
156
- if parse_version(key) < current_mindsdb_lv
150
+ key: value for key, value in gui_versions.items() if parse_version(key) < current_mindsdb_lv
157
151
  }
158
152
  if len(lower_versions) == 0:
159
153
  gui_version_lv = gui_versions[all_mindsdb_lv[0].base_version]
@@ -169,7 +163,7 @@ def get_last_compatible_gui_version() -> Version:
169
163
 
170
164
 
171
165
  def get_current_gui_version() -> Version:
172
- logger.debug("Getting current frontend version..")
166
+ logger.debug("Getting current frontend version...")
173
167
  config = Config()
174
168
  static_path = Path(config["paths"]["static"])
175
169
  version_txt_path = static_path.joinpath("version.txt")
@@ -179,9 +173,7 @@ def get_current_gui_version() -> Version:
179
173
  with open(version_txt_path, "rt") as f:
180
174
  current_gui_version = f.readline()
181
175
 
182
- current_gui_lv = (
183
- None if current_gui_version is None else parse_version(current_gui_version)
184
- )
176
+ current_gui_lv = None if current_gui_version is None else parse_version(current_gui_version)
185
177
  logger.debug(f"Current frontend version: {current_gui_lv}.")
186
178
 
187
179
  return current_gui_lv
@@ -197,10 +189,7 @@ def initialize_static():
197
189
  if required_gui_version is not None:
198
190
  required_gui_version_lv = parse_version(required_gui_version)
199
191
  success = True
200
- if (
201
- current_gui_version_lv is None
202
- or required_gui_version_lv != current_gui_version_lv
203
- ):
192
+ if current_gui_version_lv is None or required_gui_version_lv != current_gui_version_lv:
204
193
  logger.debug("Updating gui..")
205
194
  success = update_static(required_gui_version_lv)
206
195
  else:
@@ -208,16 +197,14 @@ def initialize_static():
208
197
  return False
209
198
 
210
199
  # ignore versions like '23.9.2.2'
211
- if (
212
- current_gui_version_lv is not None
213
- and len(current_gui_version_lv.release) < 3
214
- ):
200
+ if current_gui_version_lv is not None and len(current_gui_version_lv.release) < 3:
215
201
  if current_gui_version_lv == last_gui_version_lv:
216
202
  return True
217
203
  logger.debug("Updating gui..")
218
204
  success = update_static(last_gui_version_lv)
219
205
 
220
- db.session.close()
206
+ if db.session:
207
+ db.session.close()
221
208
  return success
222
209
 
223
210
 
@@ -227,12 +214,8 @@ def initialize_app(config, no_studio):
227
214
  gui_exists = Path(static_root).joinpath("index.html").is_file()
228
215
  logger.debug(f"Does GUI already exist.. {'YES' if gui_exists else 'NO'}")
229
216
  init_static_thread = None
230
- if no_studio is False and (
231
- config["gui"]["autoupdate"] is True or gui_exists is False
232
- ):
233
- init_static_thread = threading.Thread(
234
- target=initialize_static, name="initialize_static"
235
- )
217
+ if no_studio is False and (config["gui"]["autoupdate"] is True or gui_exists is False):
218
+ init_static_thread = threading.Thread(target=initialize_static, name="initialize_static")
236
219
  init_static_thread.start()
237
220
 
238
221
  # Wait for static initialization.
@@ -334,9 +317,7 @@ def initialize_app(config, no_studio):
334
317
  # region routes where auth is required
335
318
  if (
336
319
  config["auth"]["http_auth_enabled"] is True
337
- and any(
338
- request.path.startswith(f"/api{ns.path}") for ns in protected_namespaces
339
- )
320
+ and any(request.path.startswith(f"/api{ns.path}") for ns in protected_namespaces)
340
321
  and check_auth() is False
341
322
  ):
342
323
  return http_error(
@@ -368,18 +349,14 @@ def initialize_app(config, no_studio):
368
349
  try:
369
350
  company_id = int(company_id)
370
351
  except Exception as e:
371
- logger.error(
372
- f"Cloud not parse company id: {company_id} | exception: {e}"
373
- )
352
+ logger.error(f"Cloud not parse company id: {company_id} | exception: {e}")
374
353
  company_id = None
375
354
 
376
355
  if user_class is not None:
377
356
  try:
378
357
  user_class = int(user_class)
379
358
  except Exception as e:
380
- logger.error(
381
- f"Cloud not parse user_class: {user_class} | exception: {e}"
382
- )
359
+ logger.error(f"Cloud not parse user_class: {user_class} | exception: {e}")
383
360
  user_class = 0
384
361
  else:
385
362
  user_class = 0
@@ -419,9 +396,7 @@ def initialize_flask(config, init_static_thread, no_studio):
419
396
 
420
397
  app.config["SECRET_KEY"] = os.environ.get("FLASK_SECRET_KEY", secrets.token_hex(32))
421
398
  app.config["SESSION_COOKIE_NAME"] = "session"
422
- app.config["PERMANENT_SESSION_LIFETIME"] = config["auth"][
423
- "http_permanent_session_lifetime"
424
- ]
399
+ app.config["PERMANENT_SESSION_LIFETIME"] = config["auth"]["http_permanent_session_lifetime"]
425
400
  app.config["SEND_FILE_MAX_AGE_DEFAULT"] = 60
426
401
  app.config["SWAGGER_HOST"] = "http://localhost:8000/mindsdb"
427
402
  app.json = CustomJSONProvider()
@@ -467,6 +442,7 @@ def initialize_interfaces(app):
467
442
  app.integration_controller = integration_controller
468
443
  app.database_controller = DatabaseController()
469
444
  app.file_controller = FileController()
445
+ app.jobs_controller = JobsController()
470
446
  config = Config()
471
447
  app.config_obj = config
472
448
 
@@ -479,9 +455,7 @@ def _open_webbrowser(url: str, pid: int, port: int, init_static_thread, static_f
479
455
  if init_static_thread is not None:
480
456
  init_static_thread.join()
481
457
  try:
482
- is_http_active = wait_func_is_true(
483
- func=is_pid_listen_port, timeout=15, pid=pid, port=port
484
- )
458
+ is_http_active = wait_func_is_true(func=is_pid_listen_port, timeout=15, pid=pid, port=port)
485
459
  if is_http_active:
486
460
  webbrowser.open(url)
487
461
  except Exception as e:
@@ -27,14 +27,21 @@ def create_agent(project_name, name, agent):
27
27
  if name is None:
28
28
  return http_error(HTTPStatus.BAD_REQUEST, "Missing field", 'Missing "name" field for agent')
29
29
 
30
- if "model_name" not in agent:
31
- return http_error(HTTPStatus.BAD_REQUEST, "Missing field", 'Missing "model_name" field for agent')
30
+ if not name.islower():
31
+ return http_error(HTTPStatus.BAD_REQUEST, "Wrong name", f"The name must be in lower case: {name}")
32
32
 
33
- model_name = agent["model_name"]
33
+ model_name = agent.get("model_name")
34
34
  provider = agent.get("provider")
35
- params = agent.get("params", {})
36
35
  skills = agent.get("skills", [])
37
36
 
37
+ params = agent.get("params", {})
38
+ if agent.get("data"):
39
+ params["data"] = agent["data"]
40
+ if agent.get("model"):
41
+ params["model"] = agent["model"]
42
+ if agent.get("prompt_template"):
43
+ params["prompt_template"] = agent["prompt_template"]
44
+
38
45
  agents_controller = AgentsController()
39
46
 
40
47
  try:
@@ -153,12 +160,6 @@ class AgentResource(Resource):
153
160
 
154
161
  agent = request.json["agent"]
155
162
  name = agent.get("name", None)
156
- model_name = agent.get("model_name", None)
157
- skills_to_add = agent.get("skills_to_add", [])
158
- skills_to_remove = agent.get("skills_to_remove", [])
159
- skills_to_rewrite = agent.get("skills", [])
160
- provider = agent.get("provider")
161
- params = agent.get("params", None)
162
163
 
163
164
  # Agent must not exist with new name.
164
165
  if name is not None and name != agent_name:
@@ -176,9 +177,18 @@ class AgentResource(Resource):
176
177
 
177
178
  # Update
178
179
  try:
179
- # Prepare the params dictionary
180
- if params is None:
181
- params = {}
180
+ model_name = agent.get("model_name", None)
181
+ skills_to_add = agent.get("skills_to_add", [])
182
+ skills_to_remove = agent.get("skills_to_remove", [])
183
+ skills_to_rewrite = agent.get("skills", [])
184
+ provider = agent.get("provider")
185
+ params = agent.get("params", {})
186
+ if agent.get("data"):
187
+ params["data"] = agent["data"]
188
+ if agent.get("model"):
189
+ params["model"] = agent["model"]
190
+ if agent.get("prompt_template"):
191
+ params["prompt_template"] = agent["prompt_template"]
182
192
 
183
193
  # Check if any of the skills to be added is of type 'retrieval'
184
194
  session = SessionController()
@@ -377,13 +387,6 @@ class AgentCompletions(Resource):
377
387
  HTTPStatus.NOT_FOUND, "Project not found", f"Project with name {project_name} does not exist"
378
388
  )
379
389
 
380
- # Add OpenAI API key to agent params if not already present.
381
- if not existing_agent.params:
382
- existing_agent.params = {}
383
- existing_agent.params["openai_api_key"] = existing_agent.params.get(
384
- "openai_api_key", os.getenv("OPENAI_API_KEY")
385
- )
386
-
387
390
  # set mode to `retrieval` if agent has a skill of type `retrieval` and mode is not set
388
391
  if "mode" not in existing_agent.params and any(
389
392
  rel.skill.type == "retrieval" for rel in existing_agent.skills_relationships