MindsDB 25.9.3rc1__py3-none-any.whl → 25.10.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 +1 -9
- mindsdb/api/a2a/__init__.py +1 -1
- mindsdb/api/a2a/agent.py +9 -1
- mindsdb/api/a2a/common/server/server.py +4 -0
- mindsdb/api/a2a/common/server/task_manager.py +8 -1
- mindsdb/api/a2a/common/types.py +66 -0
- mindsdb/api/a2a/task_manager.py +50 -0
- mindsdb/api/common/middleware.py +1 -1
- mindsdb/api/executor/command_executor.py +49 -36
- mindsdb/api/executor/datahub/datanodes/information_schema_datanode.py +7 -13
- mindsdb/api/executor/datahub/datanodes/integration_datanode.py +2 -2
- mindsdb/api/executor/datahub/datanodes/system_tables.py +2 -1
- mindsdb/api/executor/planner/query_prepare.py +2 -20
- mindsdb/api/executor/utilities/sql.py +5 -4
- mindsdb/api/http/initialize.py +76 -60
- mindsdb/api/http/namespaces/agents.py +0 -3
- mindsdb/api/http/namespaces/chatbots.py +0 -5
- mindsdb/api/http/namespaces/file.py +2 -0
- mindsdb/api/http/namespaces/handlers.py +10 -5
- mindsdb/api/http/namespaces/knowledge_bases.py +20 -0
- mindsdb/api/http/namespaces/sql.py +2 -2
- mindsdb/api/http/start.py +2 -2
- mindsdb/api/mysql/mysql_proxy/utilities/dump.py +8 -2
- mindsdb/integrations/handlers/byom_handler/byom_handler.py +2 -10
- mindsdb/integrations/handlers/databricks_handler/databricks_handler.py +98 -46
- mindsdb/integrations/handlers/druid_handler/druid_handler.py +32 -40
- mindsdb/integrations/handlers/gitlab_handler/gitlab_handler.py +5 -2
- mindsdb/integrations/handlers/mssql_handler/mssql_handler.py +438 -100
- mindsdb/integrations/handlers/mssql_handler/requirements_odbc.txt +3 -0
- mindsdb/integrations/handlers/mysql_handler/mysql_handler.py +235 -3
- mindsdb/integrations/handlers/oracle_handler/__init__.py +2 -0
- mindsdb/integrations/handlers/oracle_handler/connection_args.py +7 -1
- mindsdb/integrations/handlers/oracle_handler/oracle_handler.py +321 -16
- mindsdb/integrations/handlers/oracle_handler/requirements.txt +1 -1
- mindsdb/integrations/handlers/postgres_handler/postgres_handler.py +2 -2
- mindsdb/integrations/handlers/shopify_handler/requirements.txt +1 -0
- mindsdb/integrations/handlers/shopify_handler/shopify_handler.py +57 -3
- mindsdb/integrations/handlers/zendesk_handler/zendesk_tables.py +144 -111
- mindsdb/integrations/libs/response.py +2 -2
- mindsdb/integrations/utilities/handlers/auth_utilities/snowflake/__init__.py +1 -0
- mindsdb/integrations/utilities/handlers/auth_utilities/snowflake/snowflake_jwt_gen.py +151 -0
- mindsdb/integrations/utilities/rag/rerankers/base_reranker.py +24 -21
- mindsdb/interfaces/agents/agents_controller.py +0 -2
- mindsdb/interfaces/data_catalog/data_catalog_loader.py +6 -7
- mindsdb/interfaces/data_catalog/data_catalog_reader.py +15 -4
- mindsdb/interfaces/database/data_handlers_cache.py +190 -0
- mindsdb/interfaces/database/database.py +3 -3
- mindsdb/interfaces/database/integrations.py +1 -121
- mindsdb/interfaces/database/projects.py +2 -6
- mindsdb/interfaces/database/views.py +1 -4
- mindsdb/interfaces/jobs/jobs_controller.py +0 -4
- mindsdb/interfaces/jobs/scheduler.py +0 -1
- mindsdb/interfaces/knowledge_base/controller.py +197 -108
- mindsdb/interfaces/knowledge_base/evaluate.py +36 -41
- mindsdb/interfaces/knowledge_base/executor.py +11 -0
- mindsdb/interfaces/knowledge_base/llm_client.py +51 -17
- mindsdb/interfaces/model/model_controller.py +4 -4
- mindsdb/interfaces/skills/custom/text2sql/mindsdb_sql_toolkit.py +4 -10
- mindsdb/interfaces/skills/skills_controller.py +1 -4
- mindsdb/interfaces/storage/db.py +16 -6
- mindsdb/interfaces/triggers/triggers_controller.py +1 -3
- mindsdb/utilities/config.py +19 -2
- mindsdb/utilities/exception.py +2 -2
- mindsdb/utilities/json_encoder.py +24 -10
- mindsdb/utilities/render/sqlalchemy_render.py +15 -14
- mindsdb/utilities/starters.py +0 -10
- {mindsdb-25.9.3rc1.dist-info → mindsdb-25.10.0.dist-info}/METADATA +278 -264
- {mindsdb-25.9.3rc1.dist-info → mindsdb-25.10.0.dist-info}/RECORD +72 -86
- mindsdb/api/postgres/__init__.py +0 -0
- mindsdb/api/postgres/postgres_proxy/__init__.py +0 -0
- mindsdb/api/postgres/postgres_proxy/executor/__init__.py +0 -1
- mindsdb/api/postgres/postgres_proxy/executor/executor.py +0 -182
- mindsdb/api/postgres/postgres_proxy/postgres_packets/__init__.py +0 -0
- mindsdb/api/postgres/postgres_proxy/postgres_packets/errors.py +0 -322
- mindsdb/api/postgres/postgres_proxy/postgres_packets/postgres_fields.py +0 -34
- mindsdb/api/postgres/postgres_proxy/postgres_packets/postgres_message.py +0 -31
- mindsdb/api/postgres/postgres_proxy/postgres_packets/postgres_message_formats.py +0 -1265
- mindsdb/api/postgres/postgres_proxy/postgres_packets/postgres_message_identifiers.py +0 -31
- mindsdb/api/postgres/postgres_proxy/postgres_packets/postgres_packets.py +0 -265
- mindsdb/api/postgres/postgres_proxy/postgres_proxy.py +0 -477
- mindsdb/api/postgres/postgres_proxy/utilities/__init__.py +0 -10
- mindsdb/api/postgres/start.py +0 -11
- mindsdb/integrations/handlers/mssql_handler/tests/__init__.py +0 -0
- mindsdb/integrations/handlers/mssql_handler/tests/test_mssql_handler.py +0 -169
- mindsdb/integrations/handlers/oracle_handler/tests/__init__.py +0 -0
- mindsdb/integrations/handlers/oracle_handler/tests/test_oracle_handler.py +0 -32
- {mindsdb-25.9.3rc1.dist-info → mindsdb-25.10.0.dist-info}/WHEEL +0 -0
- {mindsdb-25.9.3rc1.dist-info → mindsdb-25.10.0.dist-info}/licenses/LICENSE +0 -0
- {mindsdb-25.9.3rc1.dist-info → mindsdb-25.10.0.dist-info}/top_level.txt +0 -0
|
@@ -34,41 +34,31 @@ class ZendeskUsersTable(APITable):
|
|
|
34
34
|
If the query contains an unsupported condition
|
|
35
35
|
"""
|
|
36
36
|
|
|
37
|
-
select_statement_parser = SELECTQueryParser(
|
|
38
|
-
query,
|
|
39
|
-
'users',
|
|
40
|
-
self.get_columns()
|
|
41
|
-
)
|
|
37
|
+
select_statement_parser = SELECTQueryParser(query, "users", self.get_columns())
|
|
42
38
|
|
|
43
|
-
selected_columns, where_conditions, order_by_conditions, result_limit = (
|
|
44
|
-
select_statement_parser.parse_query()
|
|
45
|
-
)
|
|
39
|
+
selected_columns, where_conditions, order_by_conditions, result_limit = select_statement_parser.parse_query()
|
|
46
40
|
|
|
47
|
-
subset_where_conditions =
|
|
41
|
+
subset_where_conditions = []
|
|
42
|
+
api_filters = {}
|
|
48
43
|
for op, arg1, arg2 in where_conditions:
|
|
49
44
|
if arg1 in self.get_columns():
|
|
50
|
-
if op !=
|
|
45
|
+
if op != "=":
|
|
51
46
|
raise NotImplementedError(f"Unknown op: {op}. Only '=' is supported.")
|
|
52
|
-
|
|
47
|
+
api_filters[arg1] = arg2
|
|
48
|
+
subset_where_conditions.append([op, arg1, arg2])
|
|
53
49
|
|
|
54
|
-
|
|
55
|
-
result = self.handler.zen_client.users(**subset_where_conditions)
|
|
50
|
+
result = self.handler.zen_client.users(**api_filters)
|
|
56
51
|
response = []
|
|
57
52
|
if isinstance(result, zenpy.lib.generator.BaseResultGenerator):
|
|
58
|
-
|
|
59
|
-
response.append(
|
|
60
|
-
count += 1
|
|
53
|
+
for user in result:
|
|
54
|
+
response.append(user.to_dict())
|
|
61
55
|
else:
|
|
62
56
|
response.append(result.to_dict())
|
|
63
57
|
|
|
64
58
|
df = pd.DataFrame(response, columns=self.get_columns())
|
|
65
59
|
|
|
66
60
|
select_statement_executor = SELECTQueryExecutor(
|
|
67
|
-
df,
|
|
68
|
-
selected_columns,
|
|
69
|
-
subset_where_conditions,
|
|
70
|
-
order_by_conditions,
|
|
71
|
-
result_limit
|
|
61
|
+
df, selected_columns, subset_where_conditions, order_by_conditions, result_limit
|
|
72
62
|
)
|
|
73
63
|
|
|
74
64
|
df = select_statement_executor.execute_query()
|
|
@@ -85,15 +75,43 @@ class ZendeskUsersTable(APITable):
|
|
|
85
75
|
"""
|
|
86
76
|
|
|
87
77
|
return [
|
|
88
|
-
"active",
|
|
89
|
-
"
|
|
90
|
-
"
|
|
91
|
-
"
|
|
92
|
-
"
|
|
93
|
-
"
|
|
94
|
-
"
|
|
95
|
-
"
|
|
96
|
-
"
|
|
78
|
+
"active",
|
|
79
|
+
"alias",
|
|
80
|
+
"chat_only",
|
|
81
|
+
"created_at",
|
|
82
|
+
"custom_role_id",
|
|
83
|
+
"details",
|
|
84
|
+
"email",
|
|
85
|
+
"external_id",
|
|
86
|
+
"id",
|
|
87
|
+
"last_login_at",
|
|
88
|
+
"locale",
|
|
89
|
+
"locale_id",
|
|
90
|
+
"moderator",
|
|
91
|
+
"name",
|
|
92
|
+
"notes",
|
|
93
|
+
"only_private_comments",
|
|
94
|
+
"organization_id",
|
|
95
|
+
"phone",
|
|
96
|
+
"photo",
|
|
97
|
+
"restricted_agent",
|
|
98
|
+
"role",
|
|
99
|
+
"shared",
|
|
100
|
+
"shared_agent",
|
|
101
|
+
"signature",
|
|
102
|
+
"suspended",
|
|
103
|
+
"tags",
|
|
104
|
+
"ticket_restriction",
|
|
105
|
+
"time_zone",
|
|
106
|
+
"two_factor_auth_enabled",
|
|
107
|
+
"updated_at",
|
|
108
|
+
"url",
|
|
109
|
+
"verified",
|
|
110
|
+
"iana_time_zone",
|
|
111
|
+
"shared_phone_number",
|
|
112
|
+
"role_type",
|
|
113
|
+
"default_group_id",
|
|
114
|
+
"report_csv",
|
|
97
115
|
]
|
|
98
116
|
|
|
99
117
|
|
|
@@ -119,41 +137,31 @@ class ZendeskTicketsTable(APITable):
|
|
|
119
137
|
If the query contains an unsupported condition
|
|
120
138
|
"""
|
|
121
139
|
|
|
122
|
-
select_statement_parser = SELECTQueryParser(
|
|
123
|
-
query,
|
|
124
|
-
'tickets',
|
|
125
|
-
self.get_columns()
|
|
126
|
-
)
|
|
140
|
+
select_statement_parser = SELECTQueryParser(query, "tickets", self.get_columns())
|
|
127
141
|
|
|
128
|
-
selected_columns, where_conditions, order_by_conditions, result_limit = (
|
|
129
|
-
select_statement_parser.parse_query()
|
|
130
|
-
)
|
|
142
|
+
selected_columns, where_conditions, order_by_conditions, result_limit = select_statement_parser.parse_query()
|
|
131
143
|
|
|
132
|
-
subset_where_conditions =
|
|
144
|
+
subset_where_conditions = []
|
|
145
|
+
api_filters = {}
|
|
133
146
|
for op, arg1, arg2 in where_conditions:
|
|
134
147
|
if arg1 in self.get_columns():
|
|
135
|
-
if op !=
|
|
148
|
+
if op != "=":
|
|
136
149
|
raise NotImplementedError(f"Unknown op: {op}. Only '=' is supported.")
|
|
137
|
-
|
|
150
|
+
api_filters[arg1] = arg2
|
|
151
|
+
subset_where_conditions.append([op, arg1, arg2])
|
|
138
152
|
|
|
139
|
-
|
|
140
|
-
result = self.handler.zen_client.tickets(**subset_where_conditions)
|
|
153
|
+
result = self.handler.zen_client.tickets(**api_filters)
|
|
141
154
|
response = []
|
|
142
155
|
if isinstance(result, zenpy.lib.generator.BaseResultGenerator):
|
|
143
|
-
|
|
144
|
-
response.append(
|
|
145
|
-
count += 1
|
|
156
|
+
for ticket in result:
|
|
157
|
+
response.append(ticket.to_dict())
|
|
146
158
|
else:
|
|
147
159
|
response.append(result.to_dict())
|
|
148
160
|
|
|
149
161
|
df = pd.DataFrame(response, columns=self.get_columns())
|
|
150
162
|
|
|
151
163
|
select_statement_executor = SELECTQueryExecutor(
|
|
152
|
-
df,
|
|
153
|
-
selected_columns,
|
|
154
|
-
subset_where_conditions,
|
|
155
|
-
order_by_conditions,
|
|
156
|
-
result_limit
|
|
164
|
+
df, selected_columns, subset_where_conditions, order_by_conditions, result_limit
|
|
157
165
|
)
|
|
158
166
|
|
|
159
167
|
df = select_statement_executor.execute_query()
|
|
@@ -170,20 +178,54 @@ class ZendeskTicketsTable(APITable):
|
|
|
170
178
|
"""
|
|
171
179
|
|
|
172
180
|
return [
|
|
173
|
-
"assignee_id",
|
|
174
|
-
"
|
|
175
|
-
"
|
|
176
|
-
"
|
|
177
|
-
"
|
|
178
|
-
"
|
|
179
|
-
"
|
|
180
|
-
"
|
|
181
|
-
"
|
|
182
|
-
"
|
|
183
|
-
"
|
|
184
|
-
"
|
|
185
|
-
"
|
|
186
|
-
"
|
|
181
|
+
"assignee_id",
|
|
182
|
+
"brand_id",
|
|
183
|
+
"collaborator_ids",
|
|
184
|
+
"created_at",
|
|
185
|
+
"custom_fields",
|
|
186
|
+
"description",
|
|
187
|
+
"due_at",
|
|
188
|
+
"external_id",
|
|
189
|
+
"fields",
|
|
190
|
+
"forum_topic_id",
|
|
191
|
+
"group_id",
|
|
192
|
+
"has_incidents",
|
|
193
|
+
"id",
|
|
194
|
+
"organization_id",
|
|
195
|
+
"priority",
|
|
196
|
+
"problem_id",
|
|
197
|
+
"raw_subject",
|
|
198
|
+
"recipient",
|
|
199
|
+
"requester_id",
|
|
200
|
+
"sharing_agreement_ids",
|
|
201
|
+
"status",
|
|
202
|
+
"subject",
|
|
203
|
+
"submitter_id",
|
|
204
|
+
"tags",
|
|
205
|
+
"type",
|
|
206
|
+
"updated_at",
|
|
207
|
+
"url",
|
|
208
|
+
"generated_timestamp",
|
|
209
|
+
"follower_ids",
|
|
210
|
+
"email_cc_ids",
|
|
211
|
+
"is_public",
|
|
212
|
+
"custom_status_id",
|
|
213
|
+
"followup_ids",
|
|
214
|
+
"ticket_form_id",
|
|
215
|
+
"allow_channelback",
|
|
216
|
+
"allow_attachments",
|
|
217
|
+
"from_messaging_channel",
|
|
218
|
+
"satisfaction_rating.assignee_id",
|
|
219
|
+
"satisfaction_rating.created_at",
|
|
220
|
+
"satisfaction_rating.group_id",
|
|
221
|
+
"satisfaction_rating.id",
|
|
222
|
+
"satisfaction_rating.requester_id",
|
|
223
|
+
"satisfaction_rating.score",
|
|
224
|
+
"satisfaction_rating.ticket_id",
|
|
225
|
+
"satisfaction_rating.updated_at",
|
|
226
|
+
"satisfaction_rating.url",
|
|
227
|
+
"via.channel",
|
|
228
|
+
"via.source.rel",
|
|
187
229
|
]
|
|
188
230
|
|
|
189
231
|
|
|
@@ -209,41 +251,31 @@ class ZendeskTriggersTable(APITable):
|
|
|
209
251
|
If the query contains an unsupported condition
|
|
210
252
|
"""
|
|
211
253
|
|
|
212
|
-
select_statement_parser = SELECTQueryParser(
|
|
213
|
-
query,
|
|
214
|
-
'triggers',
|
|
215
|
-
self.get_columns()
|
|
216
|
-
)
|
|
254
|
+
select_statement_parser = SELECTQueryParser(query, "triggers", self.get_columns())
|
|
217
255
|
|
|
218
|
-
selected_columns, where_conditions, order_by_conditions, result_limit = (
|
|
219
|
-
select_statement_parser.parse_query()
|
|
220
|
-
)
|
|
256
|
+
selected_columns, where_conditions, order_by_conditions, result_limit = select_statement_parser.parse_query()
|
|
221
257
|
|
|
222
|
-
subset_where_conditions =
|
|
258
|
+
subset_where_conditions = []
|
|
259
|
+
api_filters = {}
|
|
223
260
|
for op, arg1, arg2 in where_conditions:
|
|
224
261
|
if arg1 in self.get_columns():
|
|
225
|
-
if op !=
|
|
262
|
+
if op != "=":
|
|
226
263
|
raise NotImplementedError(f"Unknown op: {op}. Only '=' is supported.")
|
|
227
|
-
|
|
264
|
+
api_filters[arg1] = arg2
|
|
265
|
+
subset_where_conditions.append([op, arg1, arg2])
|
|
228
266
|
|
|
229
|
-
|
|
230
|
-
result = self.handler.zen_client.triggers(**subset_where_conditions)
|
|
267
|
+
result = self.handler.zen_client.triggers(**api_filters)
|
|
231
268
|
response = []
|
|
232
269
|
if isinstance(result, zenpy.lib.generator.BaseResultGenerator):
|
|
233
|
-
|
|
234
|
-
response.append(
|
|
235
|
-
count += 1
|
|
270
|
+
for trigger in result:
|
|
271
|
+
response.append(trigger.to_dict())
|
|
236
272
|
else:
|
|
237
273
|
response.append(result.to_dict())
|
|
238
274
|
|
|
239
275
|
df = pd.DataFrame(response, columns=self.get_columns())
|
|
240
276
|
|
|
241
277
|
select_statement_executor = SELECTQueryExecutor(
|
|
242
|
-
df,
|
|
243
|
-
selected_columns,
|
|
244
|
-
subset_where_conditions,
|
|
245
|
-
order_by_conditions,
|
|
246
|
-
result_limit
|
|
278
|
+
df, selected_columns, subset_where_conditions, order_by_conditions, result_limit
|
|
247
279
|
)
|
|
248
280
|
|
|
249
281
|
df = select_statement_executor.execute_query()
|
|
@@ -260,9 +292,20 @@ class ZendeskTriggersTable(APITable):
|
|
|
260
292
|
"""
|
|
261
293
|
|
|
262
294
|
return [
|
|
263
|
-
"actions",
|
|
264
|
-
"
|
|
265
|
-
"
|
|
295
|
+
"actions",
|
|
296
|
+
"active",
|
|
297
|
+
"description",
|
|
298
|
+
"id",
|
|
299
|
+
"position",
|
|
300
|
+
"title",
|
|
301
|
+
"url",
|
|
302
|
+
"updated_at",
|
|
303
|
+
"created_at",
|
|
304
|
+
"default",
|
|
305
|
+
"raw_title",
|
|
306
|
+
"category_id",
|
|
307
|
+
"conditions.all",
|
|
308
|
+
"conditions.any",
|
|
266
309
|
]
|
|
267
310
|
|
|
268
311
|
|
|
@@ -288,41 +331,31 @@ class ZendeskActivitiesTable(APITable):
|
|
|
288
331
|
If the query contains an unsupported condition
|
|
289
332
|
"""
|
|
290
333
|
|
|
291
|
-
select_statement_parser = SELECTQueryParser(
|
|
292
|
-
query,
|
|
293
|
-
'activities',
|
|
294
|
-
self.get_columns()
|
|
295
|
-
)
|
|
334
|
+
select_statement_parser = SELECTQueryParser(query, "activities", self.get_columns())
|
|
296
335
|
|
|
297
|
-
selected_columns, where_conditions, order_by_conditions, result_limit = (
|
|
298
|
-
select_statement_parser.parse_query()
|
|
299
|
-
)
|
|
336
|
+
selected_columns, where_conditions, order_by_conditions, result_limit = select_statement_parser.parse_query()
|
|
300
337
|
|
|
301
|
-
subset_where_conditions =
|
|
338
|
+
subset_where_conditions = []
|
|
339
|
+
api_filters = {}
|
|
302
340
|
for op, arg1, arg2 in where_conditions:
|
|
303
341
|
if arg1 in self.get_columns():
|
|
304
|
-
if op !=
|
|
342
|
+
if op != "=":
|
|
305
343
|
raise NotImplementedError(f"Unknown op: {op}. Only '=' is supported.")
|
|
306
|
-
|
|
344
|
+
api_filters[arg1] = arg2
|
|
345
|
+
subset_where_conditions.append([op, arg1, arg2])
|
|
307
346
|
|
|
308
|
-
|
|
309
|
-
result = self.handler.zen_client.activities(**subset_where_conditions)
|
|
347
|
+
result = self.handler.zen_client.activities(**api_filters)
|
|
310
348
|
response = []
|
|
311
349
|
if isinstance(result, zenpy.lib.generator.BaseResultGenerator):
|
|
312
|
-
|
|
313
|
-
response.append(
|
|
314
|
-
count += 1
|
|
350
|
+
for activity in result:
|
|
351
|
+
response.append(activity.to_dict())
|
|
315
352
|
else:
|
|
316
353
|
response.append(result.to_dict())
|
|
317
354
|
|
|
318
355
|
df = pd.DataFrame(response, columns=self.get_columns())
|
|
319
356
|
|
|
320
357
|
select_statement_executor = SELECTQueryExecutor(
|
|
321
|
-
df,
|
|
322
|
-
selected_columns,
|
|
323
|
-
subset_where_conditions,
|
|
324
|
-
order_by_conditions,
|
|
325
|
-
result_limit
|
|
358
|
+
df, selected_columns, subset_where_conditions, order_by_conditions, result_limit
|
|
326
359
|
)
|
|
327
360
|
|
|
328
361
|
df = select_statement_executor.execute_query()
|
|
@@ -459,5 +492,5 @@ class ZendeskActivitiesTable(APITable):
|
|
|
459
492
|
"target.ticket.type",
|
|
460
493
|
"target.ticket.updated_at",
|
|
461
494
|
"target.ticket.url",
|
|
462
|
-
"target.ticket.via"
|
|
495
|
+
"target.ticket.via",
|
|
463
496
|
]
|
|
@@ -50,7 +50,7 @@ class HandlerResponse:
|
|
|
50
50
|
error_message: str | None = None,
|
|
51
51
|
affected_rows: int | None = None,
|
|
52
52
|
mysql_types: list[MYSQL_DATA_TYPE] | None = None,
|
|
53
|
-
|
|
53
|
+
is_expected_error: bool = False,
|
|
54
54
|
) -> None:
|
|
55
55
|
self.resp_type = resp_type
|
|
56
56
|
self.query = query
|
|
@@ -61,7 +61,7 @@ class HandlerResponse:
|
|
|
61
61
|
if isinstance(self.affected_rows, int) is False or self.affected_rows < 0:
|
|
62
62
|
self.affected_rows = 0
|
|
63
63
|
self.mysql_types = mysql_types
|
|
64
|
-
self.
|
|
64
|
+
self.is_expected_error = is_expected_error
|
|
65
65
|
self.exception = None
|
|
66
66
|
current_exception = sys.exc_info()
|
|
67
67
|
if current_exception[0] is not None:
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .snowflake_jwt_gen import get_validated_jwt as get_validated_jwt
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# Based on https://docs.snowflake.com/en/developer-guide/sql-api/authenticating
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
import base64
|
|
5
|
+
import hashlib
|
|
6
|
+
import logging
|
|
7
|
+
from datetime import timedelta, timezone, datetime
|
|
8
|
+
|
|
9
|
+
from cryptography.hazmat.primitives.serialization import load_pem_private_key
|
|
10
|
+
from cryptography.hazmat.primitives.serialization import Encoding
|
|
11
|
+
from cryptography.hazmat.primitives.serialization import PublicFormat
|
|
12
|
+
from cryptography.hazmat.backends import default_backend
|
|
13
|
+
import jwt
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
ISSUER = "iss"
|
|
18
|
+
EXPIRE_TIME = "exp"
|
|
19
|
+
ISSUE_TIME = "iat"
|
|
20
|
+
SUBJECT = "sub"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class JWTGenerator(object):
|
|
24
|
+
"""
|
|
25
|
+
Creates and signs a JWT with the specified private key file, username, and account identifier. The JWTGenerator keeps the
|
|
26
|
+
generated token and only regenerates the token if a specified period of time has passed.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
LIFETIME = timedelta(minutes=60) # The tokens will have a 59 minute lifetime
|
|
30
|
+
ALGORITHM = "RS256" # Tokens will be generated using RSA with SHA256
|
|
31
|
+
|
|
32
|
+
def __init__(self, account: str, user: str, private_key: str, lifetime: timedelta = LIFETIME):
|
|
33
|
+
"""
|
|
34
|
+
__init__ creates an object that generates JWTs for the specified user, account identifier, and private key.
|
|
35
|
+
:param account: Your Snowflake account identifier. See https://docs.snowflake.com/en/user-guide/admin-account-identifier.html. Note that if you are using the account locator, exclude any region information from the account locator.
|
|
36
|
+
:param user: The Snowflake username.
|
|
37
|
+
:param private_key: The private key file used for signing the JWTs.
|
|
38
|
+
:param lifetime: The number of minutes (as a timedelta) during which the key will be valid.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
logger.info(
|
|
42
|
+
"""Creating JWTGenerator with arguments
|
|
43
|
+
account : %s, user : %s, lifetime : %s""",
|
|
44
|
+
account,
|
|
45
|
+
user,
|
|
46
|
+
lifetime,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# Construct the fully qualified name of the user in uppercase.
|
|
50
|
+
self.account = self.prepare_account_name_for_jwt(account)
|
|
51
|
+
self.user = user.upper()
|
|
52
|
+
self.qualified_username = self.account + "." + self.user
|
|
53
|
+
|
|
54
|
+
self.lifetime = lifetime
|
|
55
|
+
self.renew_time = datetime.now(timezone.utc)
|
|
56
|
+
self.token = None
|
|
57
|
+
|
|
58
|
+
self.private_key = load_pem_private_key(private_key.encode(), None, default_backend())
|
|
59
|
+
|
|
60
|
+
def prepare_account_name_for_jwt(self, raw_account: str) -> str:
|
|
61
|
+
"""
|
|
62
|
+
Prepare the account identifier for use in the JWT.
|
|
63
|
+
For the JWT, the account identifier must not include the subdomain or any region or cloud provider information.
|
|
64
|
+
:param raw_account: The specified account identifier.
|
|
65
|
+
:return: The account identifier in a form that can be used to generate JWT.
|
|
66
|
+
"""
|
|
67
|
+
account = raw_account
|
|
68
|
+
if ".global" not in account:
|
|
69
|
+
# Handle the general case.
|
|
70
|
+
idx = account.find(".")
|
|
71
|
+
if idx > 0:
|
|
72
|
+
account = account[0:idx]
|
|
73
|
+
else:
|
|
74
|
+
# Handle the replication case.
|
|
75
|
+
idx = account.find("-")
|
|
76
|
+
if idx > 0:
|
|
77
|
+
account = account[0:idx]
|
|
78
|
+
# Use uppercase for the account identifier.
|
|
79
|
+
return account.upper()
|
|
80
|
+
|
|
81
|
+
def get_token(self) -> str:
|
|
82
|
+
"""
|
|
83
|
+
Generates a new JWT.
|
|
84
|
+
:return: the new token
|
|
85
|
+
"""
|
|
86
|
+
now = datetime.now(timezone.utc) # Fetch the current time
|
|
87
|
+
|
|
88
|
+
# Prepare the fields for the payload.
|
|
89
|
+
# Generate the public key fingerprint for the issuer in the payload.
|
|
90
|
+
public_key_fp = self.calculate_public_key_fingerprint(self.private_key)
|
|
91
|
+
|
|
92
|
+
# Create our payload
|
|
93
|
+
payload = {
|
|
94
|
+
# Set the issuer to the fully qualified username concatenated with the public key fingerprint.
|
|
95
|
+
ISSUER: self.qualified_username + "." + public_key_fp,
|
|
96
|
+
# Set the subject to the fully qualified username.
|
|
97
|
+
SUBJECT: self.qualified_username,
|
|
98
|
+
# Set the issue time to now.
|
|
99
|
+
ISSUE_TIME: now,
|
|
100
|
+
# Set the expiration time, based on the lifetime specified for this object.
|
|
101
|
+
EXPIRE_TIME: now + self.lifetime,
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
# Regenerate the actual token
|
|
105
|
+
token = jwt.encode(payload, key=self.private_key, algorithm=JWTGenerator.ALGORITHM)
|
|
106
|
+
# If you are using a version of PyJWT prior to 2.0, jwt.encode returns a byte string, rather than a string.
|
|
107
|
+
# If the token is a byte string, convert it to a string.
|
|
108
|
+
if isinstance(token, bytes):
|
|
109
|
+
token = token.decode("utf-8")
|
|
110
|
+
self.token = token
|
|
111
|
+
|
|
112
|
+
return self.token
|
|
113
|
+
|
|
114
|
+
def calculate_public_key_fingerprint(self, private_key: str) -> str:
|
|
115
|
+
"""
|
|
116
|
+
Given a private key in PEM format, return the public key fingerprint.
|
|
117
|
+
:param private_key: private key string
|
|
118
|
+
:return: public key fingerprint
|
|
119
|
+
"""
|
|
120
|
+
# Get the raw bytes of public key.
|
|
121
|
+
public_key_raw = private_key.public_key().public_bytes(Encoding.DER, PublicFormat.SubjectPublicKeyInfo)
|
|
122
|
+
|
|
123
|
+
# Get the sha256 hash of the raw bytes.
|
|
124
|
+
sha256hash = hashlib.sha256()
|
|
125
|
+
sha256hash.update(public_key_raw)
|
|
126
|
+
|
|
127
|
+
# Base64-encode the value and prepend the prefix 'SHA256:'.
|
|
128
|
+
public_key_fp = "SHA256:" + base64.b64encode(sha256hash.digest()).decode("utf-8")
|
|
129
|
+
logger.info("Public key fingerprint is %s", public_key_fp)
|
|
130
|
+
|
|
131
|
+
return public_key_fp
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def get_validated_jwt(token: str, account: str, user: str, private_key: str) -> str:
|
|
135
|
+
try:
|
|
136
|
+
content = jwt.decode(token, algorithms=[JWTGenerator.ALGORITHM], options={"verify_signature": False})
|
|
137
|
+
|
|
138
|
+
expired = content.get("exp", 0)
|
|
139
|
+
# add 5 seconds before limit
|
|
140
|
+
if expired - 5 > time.time():
|
|
141
|
+
# keep the same
|
|
142
|
+
return token
|
|
143
|
+
|
|
144
|
+
except jwt.DecodeError:
|
|
145
|
+
# wrong key
|
|
146
|
+
...
|
|
147
|
+
|
|
148
|
+
# generate new token
|
|
149
|
+
if private_key is None:
|
|
150
|
+
raise ValueError("Private key is missing")
|
|
151
|
+
return JWTGenerator(account, user, private_key).get_token()
|
|
@@ -7,7 +7,6 @@ import math
|
|
|
7
7
|
import os
|
|
8
8
|
import random
|
|
9
9
|
from abc import ABC
|
|
10
|
-
from textwrap import dedent
|
|
11
10
|
from typing import Any, List, Optional, Tuple
|
|
12
11
|
|
|
13
12
|
from openai import AsyncOpenAI, AsyncAzureOpenAI
|
|
@@ -27,6 +26,16 @@ from mindsdb.integrations.libs.base import BaseMLEngine
|
|
|
27
26
|
log = logging.getLogger(__name__)
|
|
28
27
|
|
|
29
28
|
|
|
29
|
+
def get_event_loop():
|
|
30
|
+
try:
|
|
31
|
+
loop = asyncio.get_running_loop()
|
|
32
|
+
except RuntimeError:
|
|
33
|
+
# If no running loop exists, create a new one
|
|
34
|
+
loop = asyncio.new_event_loop()
|
|
35
|
+
asyncio.set_event_loop(loop)
|
|
36
|
+
return loop
|
|
37
|
+
|
|
38
|
+
|
|
30
39
|
class BaseLLMReranker(BaseModel, ABC):
|
|
31
40
|
filtering_threshold: float = 0.0 # Default threshold for filtering
|
|
32
41
|
provider: str = "openai"
|
|
@@ -74,7 +83,12 @@ class BaseLLMReranker(BaseModel, ABC):
|
|
|
74
83
|
timeout=self.request_timeout,
|
|
75
84
|
max_retries=2,
|
|
76
85
|
)
|
|
77
|
-
elif self.provider
|
|
86
|
+
elif self.provider in ("openai", "ollama"):
|
|
87
|
+
if self.provider == "ollama":
|
|
88
|
+
self.method = "no-logprobs"
|
|
89
|
+
if self.api_key is None:
|
|
90
|
+
self.api_key = "n/a"
|
|
91
|
+
|
|
78
92
|
api_key_var: str = "OPENAI_API_KEY"
|
|
79
93
|
openai_api_key = self.api_key or os.getenv(api_key_var)
|
|
80
94
|
if not openai_api_key:
|
|
@@ -84,7 +98,6 @@ class BaseLLMReranker(BaseModel, ABC):
|
|
|
84
98
|
self.client = AsyncOpenAI(
|
|
85
99
|
api_key=openai_api_key, base_url=base_url, timeout=self.request_timeout, max_retries=2
|
|
86
100
|
)
|
|
87
|
-
|
|
88
101
|
else:
|
|
89
102
|
# try to use litellm
|
|
90
103
|
from mindsdb.api.executor.controllers.session_controller import SessionController
|
|
@@ -99,7 +112,7 @@ class BaseLLMReranker(BaseModel, ABC):
|
|
|
99
112
|
self.method = "no-logprobs"
|
|
100
113
|
|
|
101
114
|
async def _call_llm(self, messages):
|
|
102
|
-
if self.provider in ("azure_openai", "openai"):
|
|
115
|
+
if self.provider in ("azure_openai", "openai", "ollama"):
|
|
103
116
|
return await self.client.chat.completions.create(
|
|
104
117
|
model=self.model,
|
|
105
118
|
messages=messages,
|
|
@@ -134,7 +147,7 @@ class BaseLLMReranker(BaseModel, ABC):
|
|
|
134
147
|
for idx, result in enumerate(results):
|
|
135
148
|
if isinstance(result, Exception):
|
|
136
149
|
log.error(f"Error processing document {i + idx}: {str(result)}")
|
|
137
|
-
raise RuntimeError(f"Error during reranking: {result}")
|
|
150
|
+
raise RuntimeError(f"Error during reranking: {result}") from result
|
|
138
151
|
|
|
139
152
|
score = result["relevance_score"]
|
|
140
153
|
|
|
@@ -217,13 +230,11 @@ class BaseLLMReranker(BaseModel, ABC):
|
|
|
217
230
|
return rerank_data
|
|
218
231
|
|
|
219
232
|
async def search_relevancy_no_logprob(self, query: str, document: str) -> Any:
|
|
220
|
-
prompt =
|
|
221
|
-
f""
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
Search query: {query}
|
|
226
|
-
"""
|
|
233
|
+
prompt = (
|
|
234
|
+
f"Score the relevance between search query and user message on scale between 0 and 100 per cents. "
|
|
235
|
+
f"Consider semantic meaning, key concepts, and contextual relevance. "
|
|
236
|
+
f"Return ONLY a numerical score between 0 and 100 per cents. No other text. Stop after sending a number. "
|
|
237
|
+
f"Search query: {query}"
|
|
227
238
|
)
|
|
228
239
|
|
|
229
240
|
response = await self._call_llm(
|
|
@@ -392,16 +403,8 @@ class BaseLLMReranker(BaseModel, ABC):
|
|
|
392
403
|
def get_scores(self, query: str, documents: list[str]):
|
|
393
404
|
query_document_pairs = [(query, doc) for doc in documents]
|
|
394
405
|
# Create event loop and run async code
|
|
395
|
-
import asyncio
|
|
396
|
-
|
|
397
|
-
try:
|
|
398
|
-
loop = asyncio.get_running_loop()
|
|
399
|
-
except RuntimeError:
|
|
400
|
-
# If no running loop exists, create a new one
|
|
401
|
-
loop = asyncio.new_event_loop()
|
|
402
|
-
asyncio.set_event_loop(loop)
|
|
403
406
|
|
|
404
|
-
documents_and_scores =
|
|
407
|
+
documents_and_scores = get_event_loop().run_until_complete(self._rank(query_document_pairs))
|
|
405
408
|
|
|
406
409
|
scores = [score for _, score in documents_and_scores]
|
|
407
410
|
return scores
|
|
@@ -423,8 +423,6 @@ class AgentsController:
|
|
|
423
423
|
raise ValueError("It is forbidden to change properties of the demo object")
|
|
424
424
|
|
|
425
425
|
if name is not None and name != agent_name:
|
|
426
|
-
if not name.islower():
|
|
427
|
-
raise ValueError(f"The name must be in lower case: {name}")
|
|
428
426
|
# Check to see if updated name already exists
|
|
429
427
|
agent_with_new_name = self.get_agent(name, project_name=project_name)
|
|
430
428
|
if agent_with_new_name is not None:
|