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.
- mindsdb/__about__.py +1 -1
- mindsdb/__main__.py +11 -1
- mindsdb/api/a2a/common/server/server.py +16 -6
- mindsdb/api/executor/command_executor.py +215 -150
- mindsdb/api/executor/datahub/datanodes/project_datanode.py +14 -3
- mindsdb/api/executor/planner/plan_join.py +3 -0
- mindsdb/api/executor/planner/plan_join_ts.py +117 -100
- mindsdb/api/executor/planner/query_planner.py +1 -0
- mindsdb/api/executor/sql_query/steps/apply_predictor_step.py +54 -85
- mindsdb/api/executor/sql_query/steps/fetch_dataframe.py +21 -24
- mindsdb/api/executor/sql_query/steps/fetch_dataframe_partition.py +9 -3
- mindsdb/api/executor/sql_query/steps/subselect_step.py +11 -8
- mindsdb/api/executor/utilities/mysql_to_duckdb_functions.py +264 -0
- mindsdb/api/executor/utilities/sql.py +30 -0
- mindsdb/api/http/initialize.py +18 -44
- mindsdb/api/http/namespaces/agents.py +23 -20
- mindsdb/api/http/namespaces/chatbots.py +83 -120
- mindsdb/api/http/namespaces/file.py +1 -1
- mindsdb/api/http/namespaces/jobs.py +38 -60
- mindsdb/api/http/namespaces/tree.py +69 -61
- mindsdb/api/http/namespaces/views.py +56 -72
- mindsdb/api/mcp/start.py +2 -0
- mindsdb/api/mysql/mysql_proxy/utilities/dump.py +3 -2
- mindsdb/integrations/handlers/autogluon_handler/requirements.txt +1 -1
- mindsdb/integrations/handlers/autosklearn_handler/requirements.txt +1 -1
- mindsdb/integrations/handlers/bigquery_handler/bigquery_handler.py +25 -5
- mindsdb/integrations/handlers/chromadb_handler/chromadb_handler.py +3 -3
- mindsdb/integrations/handlers/db2_handler/db2_handler.py +19 -23
- mindsdb/integrations/handlers/flaml_handler/requirements.txt +1 -1
- mindsdb/integrations/handlers/gong_handler/__about__.py +2 -0
- mindsdb/integrations/handlers/gong_handler/__init__.py +30 -0
- mindsdb/integrations/handlers/gong_handler/connection_args.py +37 -0
- mindsdb/integrations/handlers/gong_handler/gong_handler.py +164 -0
- mindsdb/integrations/handlers/gong_handler/gong_tables.py +508 -0
- mindsdb/integrations/handlers/gong_handler/icon.svg +25 -0
- mindsdb/integrations/handlers/gong_handler/test_gong_handler.py +125 -0
- mindsdb/integrations/handlers/google_calendar_handler/google_calendar_tables.py +82 -73
- mindsdb/integrations/handlers/hubspot_handler/requirements.txt +1 -1
- mindsdb/integrations/handlers/huggingface_handler/__init__.py +8 -12
- mindsdb/integrations/handlers/huggingface_handler/finetune.py +203 -223
- mindsdb/integrations/handlers/huggingface_handler/huggingface_handler.py +360 -383
- mindsdb/integrations/handlers/huggingface_handler/requirements.txt +7 -7
- mindsdb/integrations/handlers/huggingface_handler/requirements_cpu.txt +7 -7
- mindsdb/integrations/handlers/huggingface_handler/settings.py +25 -25
- mindsdb/integrations/handlers/langchain_handler/langchain_handler.py +83 -77
- mindsdb/integrations/handlers/lightwood_handler/requirements.txt +4 -4
- mindsdb/integrations/handlers/litellm_handler/litellm_handler.py +5 -2
- mindsdb/integrations/handlers/litellm_handler/settings.py +2 -1
- mindsdb/integrations/handlers/openai_handler/constants.py +11 -30
- mindsdb/integrations/handlers/openai_handler/helpers.py +27 -34
- mindsdb/integrations/handlers/openai_handler/openai_handler.py +14 -12
- mindsdb/integrations/handlers/pgvector_handler/pgvector_handler.py +106 -90
- mindsdb/integrations/handlers/postgres_handler/postgres_handler.py +41 -39
- mindsdb/integrations/handlers/salesforce_handler/constants.py +215 -0
- mindsdb/integrations/handlers/salesforce_handler/salesforce_handler.py +141 -80
- mindsdb/integrations/handlers/salesforce_handler/salesforce_tables.py +0 -1
- mindsdb/integrations/handlers/tpot_handler/requirements.txt +1 -1
- mindsdb/integrations/handlers/web_handler/urlcrawl_helpers.py +32 -17
- mindsdb/integrations/handlers/web_handler/web_handler.py +19 -22
- mindsdb/integrations/libs/llm/config.py +0 -14
- mindsdb/integrations/libs/llm/utils.py +0 -15
- mindsdb/integrations/libs/vectordatabase_handler.py +10 -1
- mindsdb/integrations/utilities/files/file_reader.py +5 -19
- mindsdb/integrations/utilities/handler_utils.py +32 -12
- mindsdb/integrations/utilities/rag/rerankers/base_reranker.py +1 -1
- mindsdb/interfaces/agents/agents_controller.py +246 -149
- mindsdb/interfaces/agents/constants.py +0 -1
- mindsdb/interfaces/agents/langchain_agent.py +11 -6
- mindsdb/interfaces/data_catalog/data_catalog_loader.py +4 -4
- mindsdb/interfaces/database/database.py +38 -13
- mindsdb/interfaces/database/integrations.py +20 -5
- mindsdb/interfaces/database/projects.py +174 -23
- mindsdb/interfaces/database/views.py +86 -60
- mindsdb/interfaces/jobs/jobs_controller.py +103 -110
- mindsdb/interfaces/knowledge_base/controller.py +33 -6
- mindsdb/interfaces/knowledge_base/evaluate.py +2 -1
- mindsdb/interfaces/knowledge_base/executor.py +24 -0
- mindsdb/interfaces/knowledge_base/preprocessing/document_preprocessor.py +6 -10
- mindsdb/interfaces/knowledge_base/preprocessing/text_splitter.py +73 -0
- mindsdb/interfaces/query_context/context_controller.py +111 -145
- mindsdb/interfaces/skills/skills_controller.py +18 -6
- mindsdb/interfaces/storage/db.py +40 -6
- mindsdb/interfaces/variables/variables_controller.py +8 -15
- mindsdb/utilities/config.py +5 -3
- mindsdb/utilities/fs.py +54 -17
- mindsdb/utilities/functions.py +72 -60
- mindsdb/utilities/log.py +38 -6
- mindsdb/utilities/ps.py +7 -7
- {mindsdb-25.7.3.0.dist-info → mindsdb-25.8.2.0.dist-info}/METADATA +282 -268
- {mindsdb-25.7.3.0.dist-info → mindsdb-25.8.2.0.dist-info}/RECORD +94 -92
- mindsdb/integrations/handlers/anyscale_endpoints_handler/__about__.py +0 -9
- mindsdb/integrations/handlers/anyscale_endpoints_handler/__init__.py +0 -20
- mindsdb/integrations/handlers/anyscale_endpoints_handler/anyscale_endpoints_handler.py +0 -290
- mindsdb/integrations/handlers/anyscale_endpoints_handler/creation_args.py +0 -14
- mindsdb/integrations/handlers/anyscale_endpoints_handler/icon.svg +0 -4
- mindsdb/integrations/handlers/anyscale_endpoints_handler/requirements.txt +0 -2
- mindsdb/integrations/handlers/anyscale_endpoints_handler/settings.py +0 -51
- mindsdb/integrations/handlers/anyscale_endpoints_handler/tests/test_anyscale_endpoints_handler.py +0 -212
- /mindsdb/integrations/handlers/{anyscale_endpoints_handler/tests/__init__.py → gong_handler/requirements.txt} +0 -0
- {mindsdb-25.7.3.0.dist-info → mindsdb-25.8.2.0.dist-info}/WHEEL +0 -0
- {mindsdb-25.7.3.0.dist-info → mindsdb-25.8.2.0.dist-info}/licenses/LICENSE +0 -0
- {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
|
mindsdb/api/http/initialize.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
|
31
|
-
return http_error(HTTPStatus.BAD_REQUEST, "
|
|
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
|
|
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
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|