MindsDB 25.7.2.0__py3-none-any.whl → 25.7.4.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 (69) hide show
  1. mindsdb/__about__.py +1 -1
  2. mindsdb/__main__.py +1 -1
  3. mindsdb/api/a2a/common/server/server.py +16 -6
  4. mindsdb/api/executor/command_executor.py +213 -137
  5. mindsdb/api/executor/datahub/datanodes/integration_datanode.py +5 -1
  6. mindsdb/api/executor/datahub/datanodes/project_datanode.py +14 -3
  7. mindsdb/api/executor/planner/plan_join.py +3 -0
  8. mindsdb/api/executor/planner/plan_join_ts.py +117 -100
  9. mindsdb/api/executor/planner/query_planner.py +1 -0
  10. mindsdb/api/executor/sql_query/steps/apply_predictor_step.py +54 -85
  11. mindsdb/api/http/initialize.py +16 -43
  12. mindsdb/api/http/namespaces/agents.py +24 -21
  13. mindsdb/api/http/namespaces/chatbots.py +83 -120
  14. mindsdb/api/http/namespaces/file.py +1 -1
  15. mindsdb/api/http/namespaces/jobs.py +38 -60
  16. mindsdb/api/http/namespaces/tree.py +69 -61
  17. mindsdb/api/mcp/start.py +2 -0
  18. mindsdb/api/mysql/mysql_proxy/utilities/dump.py +3 -2
  19. mindsdb/integrations/handlers/autogluon_handler/requirements.txt +1 -1
  20. mindsdb/integrations/handlers/autosklearn_handler/requirements.txt +1 -1
  21. mindsdb/integrations/handlers/bigquery_handler/bigquery_handler.py +25 -5
  22. mindsdb/integrations/handlers/chromadb_handler/chromadb_handler.py +3 -3
  23. mindsdb/integrations/handlers/flaml_handler/requirements.txt +1 -1
  24. mindsdb/integrations/handlers/google_calendar_handler/google_calendar_tables.py +82 -73
  25. mindsdb/integrations/handlers/hubspot_handler/requirements.txt +1 -1
  26. mindsdb/integrations/handlers/langchain_handler/langchain_handler.py +83 -76
  27. mindsdb/integrations/handlers/lightwood_handler/requirements.txt +4 -4
  28. mindsdb/integrations/handlers/litellm_handler/litellm_handler.py +16 -3
  29. mindsdb/integrations/handlers/litellm_handler/settings.py +2 -1
  30. mindsdb/integrations/handlers/llama_index_handler/requirements.txt +1 -1
  31. mindsdb/integrations/handlers/pgvector_handler/pgvector_handler.py +106 -90
  32. mindsdb/integrations/handlers/postgres_handler/postgres_handler.py +41 -39
  33. mindsdb/integrations/handlers/s3_handler/s3_handler.py +72 -70
  34. mindsdb/integrations/handlers/salesforce_handler/constants.py +208 -0
  35. mindsdb/integrations/handlers/salesforce_handler/salesforce_handler.py +142 -81
  36. mindsdb/integrations/handlers/salesforce_handler/salesforce_tables.py +12 -4
  37. mindsdb/integrations/handlers/slack_handler/slack_tables.py +141 -161
  38. mindsdb/integrations/handlers/tpot_handler/requirements.txt +1 -1
  39. mindsdb/integrations/handlers/web_handler/urlcrawl_helpers.py +32 -17
  40. mindsdb/integrations/handlers/web_handler/web_handler.py +19 -22
  41. mindsdb/integrations/handlers/youtube_handler/youtube_tables.py +183 -55
  42. mindsdb/integrations/libs/vectordatabase_handler.py +10 -1
  43. mindsdb/integrations/utilities/handler_utils.py +32 -12
  44. mindsdb/interfaces/agents/agents_controller.py +169 -110
  45. mindsdb/interfaces/agents/langchain_agent.py +10 -3
  46. mindsdb/interfaces/data_catalog/data_catalog_loader.py +22 -8
  47. mindsdb/interfaces/database/database.py +38 -13
  48. mindsdb/interfaces/database/integrations.py +20 -5
  49. mindsdb/interfaces/database/projects.py +63 -16
  50. mindsdb/interfaces/database/views.py +86 -60
  51. mindsdb/interfaces/jobs/jobs_controller.py +103 -110
  52. mindsdb/interfaces/knowledge_base/controller.py +33 -5
  53. mindsdb/interfaces/knowledge_base/evaluate.py +53 -9
  54. mindsdb/interfaces/knowledge_base/executor.py +24 -0
  55. mindsdb/interfaces/knowledge_base/llm_client.py +3 -3
  56. mindsdb/interfaces/knowledge_base/preprocessing/document_preprocessor.py +21 -13
  57. mindsdb/interfaces/query_context/context_controller.py +100 -133
  58. mindsdb/interfaces/skills/skills_controller.py +18 -6
  59. mindsdb/interfaces/storage/db.py +40 -6
  60. mindsdb/interfaces/variables/variables_controller.py +8 -15
  61. mindsdb/utilities/config.py +3 -3
  62. mindsdb/utilities/functions.py +72 -60
  63. mindsdb/utilities/log.py +38 -6
  64. mindsdb/utilities/ps.py +7 -7
  65. {mindsdb-25.7.2.0.dist-info → mindsdb-25.7.4.0.dist-info}/METADATA +262 -263
  66. {mindsdb-25.7.2.0.dist-info → mindsdb-25.7.4.0.dist-info}/RECORD +69 -68
  67. {mindsdb-25.7.2.0.dist-info → mindsdb-25.7.4.0.dist-info}/WHEEL +0 -0
  68. {mindsdb-25.7.2.0.dist-info → mindsdb-25.7.4.0.dist-info}/licenses/LICENSE +0 -0
  69. {mindsdb-25.7.2.0.dist-info → mindsdb-25.7.4.0.dist-info}/top_level.txt +0 -0
@@ -37,54 +37,74 @@ def get_api_key(
37
37
 
38
38
  # 1
39
39
  if "using" in create_args and f"{api_name.lower()}_api_key" in create_args["using"]:
40
- return create_args["using"][f"{api_name.lower()}_api_key"]
40
+ api_key = create_args["using"][f"{api_name.lower()}_api_key"]
41
+ if api_key:
42
+ return api_key
41
43
 
42
44
  # 1.5 - Check for generic api_key in using
43
45
  if "using" in create_args and "api_key" in create_args["using"]:
44
- return create_args["using"]["api_key"]
46
+ api_key = create_args["using"]["api_key"]
47
+ if api_key:
48
+ return api_key
45
49
 
46
50
  # 2
47
51
  if f"{api_name.lower()}_api_key" in create_args:
48
- return create_args[f"{api_name.lower()}_api_key"]
52
+ api_key = create_args[f"{api_name.lower()}_api_key"]
53
+ if api_key:
54
+ return api_key
49
55
 
50
56
  # 2.5 - Check for generic api_key
51
57
  if "api_key" in create_args:
52
- return create_args["api_key"]
58
+ api_key = create_args["api_key"]
59
+ if api_key:
60
+ return api_key
53
61
 
54
62
  # 3 - Check in params dictionary if it exists (for agents)
55
63
  if "params" in create_args and create_args["params"] is not None:
56
64
  if f"{api_name.lower()}_api_key" in create_args["params"]:
57
- return create_args["params"][f"{api_name.lower()}_api_key"]
65
+ api_key = create_args["params"][f"{api_name.lower()}_api_key"]
66
+ if api_key:
67
+ return api_key
58
68
  # 3.5 - Check for generic api_key in params
59
69
  if "api_key" in create_args["params"]:
60
- return create_args["params"]["api_key"]
70
+ api_key = create_args["params"]["api_key"]
71
+ if api_key:
72
+ return api_key
61
73
 
62
74
  # 4
63
75
  if engine_storage is not None:
64
76
  connection_args = engine_storage.get_connection_args()
65
77
  if f"{api_name.lower()}_api_key" in connection_args:
66
- return connection_args[f"{api_name.lower()}_api_key"]
78
+ api_key = connection_args[f"{api_name.lower()}_api_key"]
79
+ if api_key:
80
+ return api_key
67
81
  # 4.5 - Check for generic api_key in connection_args
68
82
  if "api_key" in connection_args:
69
- return connection_args["api_key"]
83
+ api_key = connection_args["api_key"]
84
+ if api_key:
85
+ return api_key
70
86
 
71
87
  # 5
72
88
  api_key = os.getenv(f"{api_name.lower()}_api_key")
73
- if api_key is not None:
89
+ if api_key:
74
90
  return api_key
75
91
  api_key = os.getenv(f"{api_name.upper()}_API_KEY")
76
- if api_key is not None:
92
+ if api_key:
77
93
  return api_key
78
94
 
79
95
  # 6
80
96
  config = Config()
81
97
  api_cfg = config.get(api_name, {})
82
98
  if f"{api_name.lower()}_api_key" in api_cfg:
83
- return api_cfg[f"{api_name.lower()}_api_key"]
99
+ api_key = api_cfg[f"{api_name.lower()}_api_key"]
100
+ if api_key:
101
+ return api_key
84
102
 
85
103
  # 7
86
104
  if "api_keys" in create_args and api_name in create_args["api_keys"]:
87
- return create_args["api_keys"][api_name]
105
+ api_key = create_args["api_keys"][api_name]
106
+ if api_key:
107
+ return api_key
88
108
 
89
109
  if strict:
90
110
  provider_upper = api_name.upper()
@@ -20,7 +20,7 @@ from mindsdb.utilities import log
20
20
 
21
21
  from mindsdb.utilities.exception import EntityExistsError, EntityNotExistsError
22
22
 
23
- from .constants import ASSISTANT_COLUMN, SUPPORTED_PROVIDERS, PROVIDER_TO_MODELS, DEFAULT_TEXT2SQL_DATABASE
23
+ from .constants import ASSISTANT_COLUMN, SUPPORTED_PROVIDERS, PROVIDER_TO_MODELS
24
24
  from .langchain_agent import get_llm_provider
25
25
 
26
26
  logger = log.getLogger(__name__)
@@ -165,22 +165,27 @@ class AgentsController:
165
165
  with one of keys is "name", and other is additional parameters for relationship agent<>skill
166
166
  provider (str): The provider of the model
167
167
  params (Dict[str, str]): Parameters to use when running the agent
168
+ data: Dict, data sources for an agent, keys:
169
+ - knowledge_bases: List of KBs to use
170
+ - tables: list of tables to use
171
+ model: Dict, parameters for the model to use
172
+ - provider: The provider of the model (e.g., 'openai', 'google')
173
+ - Other model-specific parameters like 'api_key', 'model_name', etc.
174
+ <provider>_api_key: API key for the provider (e.g., openai_api_key)
175
+
176
+ # Deprecated parameters:
168
177
  database: The database to use for text2sql skills (default is 'mindsdb')
169
178
  knowledge_base_database: The database to use for knowledge base queries (default is 'mindsdb')
170
179
  include_tables: List of tables to include for text2sql skills
171
180
  ignore_tables: List of tables to ignore for text2sql skills
172
181
  include_knowledge_bases: List of knowledge bases to include for text2sql skills
173
182
  ignore_knowledge_bases: List of knowledge bases to ignore for text2sql skills
174
- <provider>_api_key: API key for the provider (e.g., openai_api_key)
175
- data: Dict, data sources for an agent, keys:
176
- - knowledge_bases: List of KBs to use (alternative to `include_knowledge_bases`)
177
- - tables: list of tables to use (alternative to `include_tables`)
178
183
 
179
184
  Returns:
180
185
  agent (db.Agents): The created agent
181
186
 
182
187
  Raises:
183
- ValueError: Agent with given name already exists, or skill/model with given name does not exist.
188
+ EntityExistsError: Agent with given name already exists, or skill/model with given name does not exist.
184
189
  """
185
190
  if project_name is None:
186
191
  project_name = default_project
@@ -189,7 +194,7 @@ class AgentsController:
189
194
  agent = self.get_agent(name, project_name)
190
195
 
191
196
  if agent is not None:
192
- raise ValueError(f"Agent with name already exists: {name}")
197
+ raise EntityExistsError("Agent already exists", name)
193
198
 
194
199
  # No need to copy params since we're not preserving the original reference
195
200
  params = params or {}
@@ -223,48 +228,31 @@ class AgentsController:
223
228
  # It will be picked up by get_api_key() in handler_utils.py
224
229
  pass
225
230
 
226
- # Extract table and knowledge base parameters from params
227
- database = params.pop("database", None)
228
- knowledge_base_database = params.pop("knowledge_base_database", DEFAULT_TEXT2SQL_DATABASE)
229
- include_tables = params.pop("include_tables", None)
230
- ignore_tables = params.pop("ignore_tables", None)
231
- include_knowledge_bases = params.pop("include_knowledge_bases", None)
232
- ignore_knowledge_bases = params.pop("ignore_knowledge_bases", None)
233
-
234
- # Save the extracted parameters back to params for API responses only if they were explicitly provided
235
- # or if they're needed for skills
236
- need_params = include_tables or ignore_tables or include_knowledge_bases or ignore_knowledge_bases
237
-
238
- if "database" in params or need_params:
239
- params["database"] = database
231
+ depreciated_params = [
232
+ "database",
233
+ "knowledge_base_database",
234
+ "include_tables",
235
+ "ignore_tables",
236
+ "include_knowledge_bases",
237
+ "ignore_knowledge_bases",
238
+ ]
239
+ if any(param in params for param in depreciated_params):
240
+ raise ValueError(
241
+ f"Parameters {', '.join(depreciated_params)} are deprecated. "
242
+ "Use 'data' parameter with 'tables' and 'knowledge_bases' keys instead."
243
+ )
240
244
 
245
+ include_tables = None
246
+ include_knowledge_bases = None
241
247
  if "data" in params:
242
- if include_knowledge_bases is None:
243
- include_knowledge_bases = params["data"].get("knowledge_bases")
244
- if include_tables is None:
245
- include_tables = params["data"].get("tables")
246
-
247
- if "knowledge_base_database" in params or include_knowledge_bases or ignore_knowledge_bases:
248
- params["knowledge_base_database"] = knowledge_base_database
249
-
250
- if include_tables is not None:
251
- params["include_tables"] = include_tables
252
- if ignore_tables is not None:
253
- params["ignore_tables"] = ignore_tables
254
- if include_knowledge_bases is not None:
255
- params["include_knowledge_bases"] = include_knowledge_bases
256
- if ignore_knowledge_bases is not None:
257
- params["ignore_knowledge_bases"] = ignore_knowledge_bases
248
+ include_knowledge_bases = params["data"].get("knowledge_bases")
249
+ include_tables = params["data"].get("tables")
258
250
 
259
251
  # Convert string parameters to lists if needed
260
252
  if isinstance(include_tables, str):
261
253
  include_tables = [t.strip() for t in include_tables.split(",")]
262
- if isinstance(ignore_tables, str):
263
- ignore_tables = [t.strip() for t in ignore_tables.split(",")]
264
254
  if isinstance(include_knowledge_bases, str):
265
255
  include_knowledge_bases = [kb.strip() for kb in include_knowledge_bases.split(",")]
266
- if isinstance(ignore_knowledge_bases, str):
267
- ignore_knowledge_bases = [kb.strip() for kb in ignore_knowledge_bases.split(",")]
268
256
 
269
257
  # Auto-create SQL skill if no skills are provided but include_tables or include_knowledge_bases params are provided
270
258
  if not skills and (include_tables or include_knowledge_bases):
@@ -272,23 +260,15 @@ class AgentsController:
272
260
  skill_name = f"{name}_sql_skill"
273
261
  skill_params = {
274
262
  "type": "sql",
275
- "database": database,
276
263
  "description": f"Auto-generated SQL skill for agent {name}",
277
264
  }
278
265
 
279
- # Add table restrictions if provided
266
+ # Add restrictions provided
280
267
  if include_tables:
281
268
  skill_params["include_tables"] = include_tables
282
- if ignore_tables:
283
- skill_params["ignore_tables"] = ignore_tables
284
-
285
- # Add knowledge base parameters if provided
286
- if knowledge_base_database:
287
- skill_params["knowledge_base_database"] = knowledge_base_database
288
269
  if include_knowledge_bases:
289
270
  skill_params["include_knowledge_bases"] = include_knowledge_bases
290
- if ignore_knowledge_bases:
291
- skill_params["ignore_knowledge_bases"] = ignore_knowledge_bases
271
+
292
272
  try:
293
273
  # Check if skill already exists
294
274
  existing_skill = self.skills_controller.get_skill(skill_name, project_name)
@@ -340,44 +320,16 @@ class AgentsController:
340
320
  db.session.rollback()
341
321
  raise ValueError(f"Skill with name does not exist: {skill_name}")
342
322
 
343
- if existing_skill.type == "sql":
344
- # Run Data Catalog loader if enabled
345
- if config.get("data_catalog", {}).get("enabled", False):
346
- if include_tables:
347
- database_table_map = {}
348
- for table in include_tables:
349
- parts = table.split(".", 1)
350
- database_table_map[parts[0]] = database_table_map.get(parts[0], []) + [parts[1]]
351
-
352
- for database_name, table_names in database_table_map.items():
353
- data_catalog_loader = DataCatalogLoader(
354
- database_name=database_name, table_names=table_names
355
- )
356
- data_catalog_loader.load_metadata()
357
-
358
- elif "database" in existing_skill.params:
359
- data_catalog_loader = DataCatalogLoader(
360
- database_name=existing_skill.params["database"],
361
- table_names=parameters["tables"] if "tables" in parameters else None,
362
- )
363
- data_catalog_loader.load_metadata()
364
-
365
- else:
366
- raise ValueError(
367
- "Data Catalog loading is enabled, but the provided parameters are insufficient to load metadata. "
368
- )
323
+ # Run Data Catalog loader if enabled.
324
+ if include_tables:
325
+ self._run_data_catalog_loader_for_table_entries(include_tables, project_name, skill=existing_skill)
326
+ else:
327
+ self._run_data_catalog_loader_for_skill(existing_skill, project_name, tables=parameters.get("tables"))
369
328
 
329
+ if existing_skill.type == "sql":
370
330
  # Add table restrictions if this is a text2sql skill
371
- if include_tables or ignore_tables:
372
- parameters["tables"] = include_tables or ignore_tables
373
-
374
- # Pass database parameter if provided
375
- if database and "database" not in parameters:
376
- parameters["database"] = database
377
-
378
- # Pass knowledge base database parameter if provided
379
- if knowledge_base_database and "knowledge_base_database" not in parameters:
380
- parameters["knowledge_base_database"] = knowledge_base_database
331
+ if include_tables:
332
+ parameters["tables"] = include_tables
381
333
 
382
334
  # Add knowledge base parameters to both the skill and the association parameters
383
335
  if include_knowledge_bases:
@@ -385,16 +337,6 @@ class AgentsController:
385
337
  if "include_knowledge_bases" not in existing_skill.params:
386
338
  existing_skill.params["include_knowledge_bases"] = include_knowledge_bases
387
339
  flag_modified(existing_skill, "params")
388
- elif ignore_knowledge_bases:
389
- parameters["ignore_knowledge_bases"] = ignore_knowledge_bases
390
- if "ignore_knowledge_bases" not in existing_skill.params:
391
- existing_skill.params["ignore_knowledge_bases"] = ignore_knowledge_bases
392
- flag_modified(existing_skill, "params")
393
-
394
- # Ensure knowledge_base_database is set in the skill's params
395
- if knowledge_base_database and "knowledge_base_database" not in existing_skill.params:
396
- existing_skill.params["knowledge_base_database"] = knowledge_base_database
397
- flag_modified(existing_skill, "params")
398
340
 
399
341
  association = db.AgentSkillsAssociation(parameters=parameters, agent=agent, skill=existing_skill)
400
342
  db.session.add(association)
@@ -462,6 +404,8 @@ class AgentsController:
462
404
  raise ValueError("It is forbidden to change properties of the demo object")
463
405
 
464
406
  if name is not None and name != agent_name:
407
+ if not name.islower():
408
+ raise ValueError(f"The name must be in lower case: {name}")
465
409
  # Check to see if updated name already exists
466
410
  agent_with_new_name = self.get_agent(name, project_name=project_name)
467
411
  if agent_with_new_name is not None:
@@ -503,12 +447,20 @@ class AgentsController:
503
447
 
504
448
  # add skills
505
449
  for skill_name in set(skills_to_add_names) - set(existing_agent_skills_names):
450
+ # Run Data Catalog loader if enabled for the new skill
451
+ self._run_data_catalog_loader_for_skill(
452
+ skill_name,
453
+ project_name,
454
+ tables=next((x for x in skills_to_add if x["name"] == skill_name), {}).get("tables"),
455
+ )
456
+
506
457
  skill_parameters = next(x for x in skills_to_add if x["name"] == skill_name).copy()
507
458
  del skill_parameters["name"]
508
459
  association = db.AgentSkillsAssociation(
509
460
  parameters=skill_parameters, agent=existing_agent, skill=skill_name_to_record_map[skill_name]
510
461
  )
511
462
  db.session.add(association)
463
+
512
464
  elif len(skills_to_rewrite) > 0:
513
465
  skill_name_to_parameters = {
514
466
  x["name"]: {k: v for k, v in x.items() if k != "name"} for x in skills_to_rewrite
@@ -519,9 +471,23 @@ class AgentsController:
519
471
  db.session.delete(rel)
520
472
  else:
521
473
  existing_skill_names.add(rel.skill.name)
522
- rel.parameters = skill_name_to_parameters[rel.skill.name]
474
+ skill_parameters = skill_name_to_parameters[rel.skill.name]
475
+
476
+ # Run Data Catalog loader if enabled for the updated skill
477
+ self._run_data_catalog_loader_for_skill(
478
+ rel.skill.name, project_name, tables=skill_parameters.get("tables")
479
+ )
480
+
481
+ rel.parameters = skill_parameters
523
482
  flag_modified(rel, "parameters")
524
483
  for new_skill_name in set(skill_name_to_parameters) - existing_skill_names:
484
+ # Run Data Catalog loader if enabled for the new skill
485
+ self._run_data_catalog_loader_for_skill(
486
+ new_skill_name,
487
+ project_name,
488
+ tables=skill_name_to_parameters[new_skill_name].get("tables"),
489
+ )
490
+
525
491
  association = db.AgentSkillsAssociation(
526
492
  parameters=skill_name_to_parameters[new_skill_name],
527
493
  agent=existing_agent,
@@ -530,8 +496,17 @@ class AgentsController:
530
496
  db.session.add(association)
531
497
 
532
498
  if params is not None:
533
- # Merge params on update
534
499
  existing_params = existing_agent.params or {}
500
+
501
+ if params.get("data", {}).get("tables"):
502
+ new_table_entries = set(params["data"]["tables"]) - set(
503
+ existing_params.get("data", {}).get("tables", [])
504
+ )
505
+ if new_table_entries:
506
+ # Run Data Catalog loader for new table entries if enabled.
507
+ self._run_data_catalog_loader_for_table_entries(new_table_entries, project_name)
508
+
509
+ # Merge params on update
535
510
  existing_params.update(params)
536
511
  # Remove None values entirely.
537
512
  params = {k: v for k, v in existing_params.items() if v is not None}
@@ -543,6 +518,86 @@ class AgentsController:
543
518
 
544
519
  return existing_agent
545
520
 
521
+ def _run_data_catalog_loader_for_skill(
522
+ self,
523
+ skill: Union[str, db.Skills],
524
+ project_name: str,
525
+ tables: List[str] = None,
526
+ ):
527
+ """
528
+ Runs Data Catalog loader for a skill if enabled in the config.
529
+ This is used to load metadata for SQL skills when they are added or updated.
530
+ """
531
+ if not config.get("data_catalog", {}).get("enabled", False):
532
+ return
533
+
534
+ skill = skill if isinstance(skill, db.Skills) else self.skills_controller.get_skill(skill, project_name)
535
+ if skill.type == "sql":
536
+ if "database" in skill.params:
537
+ valid_table_names = skill.params.get("tables") if skill.params.get("tables") else tables
538
+ data_catalog_loader = DataCatalogLoader(
539
+ database_name=skill.params["database"], table_names=valid_table_names
540
+ )
541
+ data_catalog_loader.load_metadata()
542
+ else:
543
+ raise ValueError(
544
+ "Data Catalog loading is enabled, but the provided parameters for the new skills are insufficient to load metadata. "
545
+ )
546
+
547
+ def _run_data_catalog_loader_for_table_entries(
548
+ self,
549
+ table_entries: List[str],
550
+ project_name: str,
551
+ skill: Union[str, db.Skills] = None,
552
+ ):
553
+ """
554
+ Runs Data Catalog loader for a list of table entries if enabled in the config.
555
+ This is used to load metadata for SQL skills when they are added or updated.
556
+ """
557
+ if not config.get("data_catalog", {}).get("enabled", False):
558
+ return
559
+
560
+ skill = skill if isinstance(skill, db.Skills) else self.skills_controller.get_skill(skill, project_name)
561
+ if not skill or skill.type == "sql":
562
+ database_table_map = {}
563
+ for table_entry in table_entries:
564
+ parts = table_entry.split(".", 1)
565
+
566
+ # Ensure the table name is in 'database.table' format.
567
+ if len(parts) != 2:
568
+ logger.warning(
569
+ f"Invalid table name format: {table_entry}. Expected 'database.table' format."
570
+ "Metadata will not be loaded for this entry."
571
+ )
572
+ continue
573
+
574
+ database, table = parts[0], parts[1]
575
+
576
+ # Wildcards in database names are not supported at the moment by data catalog loader.
577
+ if "*" in database:
578
+ logger.warning(
579
+ f"Invalid database name format: {database}. Wildcards are not supported."
580
+ "Metadata will not be loaded for this entry."
581
+ )
582
+ continue
583
+
584
+ # Wildcards in table names are supported either.
585
+ # However, the table name itself can be a wildcard representing all tables.
586
+ if table == "*":
587
+ table = None
588
+ elif "*" in table:
589
+ logger.warning(
590
+ f"Invalid table name format: {table}. Wildcards are not supported."
591
+ "Metadata will not be loaded for this entry."
592
+ )
593
+ continue
594
+
595
+ database_table_map[database] = database_table_map.get(database, []) + [table]
596
+
597
+ for database_name, table_names in database_table_map.items():
598
+ data_catalog_loader = DataCatalogLoader(database_name=database_name, table_names=table_names)
599
+ data_catalog_loader.load_metadata()
600
+
546
601
  def delete_agent(self, agent_name: str, project_name: str = default_project):
547
602
  """
548
603
  Deletes an agent by name.
@@ -584,20 +639,22 @@ class AgentsController:
584
639
  def get_completion(
585
640
  self,
586
641
  agent: db.Agents,
587
- messages: List[Dict[str, str]],
642
+ messages: list[Dict[str, str]],
588
643
  project_name: str = default_project,
589
- tools: List[BaseTool] = None,
644
+ tools: list[BaseTool] = None,
590
645
  stream: bool = False,
646
+ params: dict | None = None,
591
647
  ) -> Union[Iterator[object], pd.DataFrame]:
592
648
  """
593
649
  Queries an agent to get a completion.
594
650
 
595
651
  Parameters:
596
652
  agent (db.Agents): Existing agent to get completion from
597
- messages (List[Dict[str, str]]): Chat history to send to the agent
653
+ messages (list[Dict[str, str]]): Chat history to send to the agent
598
654
  project_name (str): Project the agent belongs to (default mindsdb)
599
- tools (List[BaseTool]): Tools to use while getting the completion
655
+ tools (list[BaseTool]): Tools to use while getting the completion
600
656
  stream (bool): Whether to stream the response
657
+ params (dict | None): params to redefine agent params
601
658
 
602
659
  Returns:
603
660
  response (Union[Iterator[object], pd.DataFrame]): Completion as a DataFrame or iterator of completion chunks
@@ -606,7 +663,7 @@ class AgentsController:
606
663
  ValueError: Agent's model does not exist.
607
664
  """
608
665
  if stream:
609
- return self._get_completion_stream(agent, messages, project_name=project_name, tools=tools)
666
+ return self._get_completion_stream(agent, messages, project_name=project_name, tools=tools, params=params)
610
667
  from .langchain_agent import LangchainAgent
611
668
 
612
669
  model, provider = self.check_model_provider(agent.model_name, agent.provider)
@@ -619,25 +676,27 @@ class AgentsController:
619
676
  llm_params = self.get_agent_llm_params(agent.params)
620
677
 
621
678
  lang_agent = LangchainAgent(agent, model, llm_params=llm_params)
622
- return lang_agent.get_completion(messages)
679
+ return lang_agent.get_completion(messages, params=params)
623
680
 
624
681
  def _get_completion_stream(
625
682
  self,
626
683
  agent: db.Agents,
627
- messages: List[Dict[str, str]],
684
+ messages: list[Dict[str, str]],
628
685
  project_name: str = default_project,
629
- tools: List[BaseTool] = None,
686
+ tools: list[BaseTool] = None,
687
+ params: dict | None = None,
630
688
  ) -> Iterator[object]:
631
689
  """
632
690
  Queries an agent to get a stream of completion chunks.
633
691
 
634
692
  Parameters:
635
693
  agent (db.Agents): Existing agent to get completion from
636
- messages (List[Dict[str, str]]): Chat history to send to the agent
694
+ messages (list[Dict[str, str]]): Chat history to send to the agent
637
695
  trace_id (str): ID of Langfuse trace to use
638
696
  observation_id (str): ID of parent Langfuse observation to use
639
697
  project_name (str): Project the agent belongs to (default mindsdb)
640
- tools (List[BaseTool]): Tools to use while getting the completion
698
+ tools (list[BaseTool]): Tools to use while getting the completion
699
+ params (dict | None): params to redefine agent params
641
700
 
642
701
  Returns:
643
702
  chunks (Iterator[object]): Completion chunks as an iterator
@@ -659,4 +718,4 @@ class AgentsController:
659
718
  llm_params = self.get_agent_llm_params(agent.params)
660
719
 
661
720
  lang_agent = LangchainAgent(agent, model=model, llm_params=llm_params)
662
- return lang_agent.get_completion(messages, stream=True)
721
+ return lang_agent.get_completion(messages, stream=True, params=params)
@@ -321,7 +321,7 @@ class LangchainAgent:
321
321
  self.provider,
322
322
  ]
323
323
 
324
- def get_completion(self, messages, stream: bool = False):
324
+ def get_completion(self, messages, stream: bool = False, params: dict | None = None):
325
325
  # Get metadata and tags to be used in the trace
326
326
  metadata = self.get_metadata()
327
327
  tags = self.get_tags()
@@ -342,7 +342,9 @@ class LangchainAgent:
342
342
  if stream:
343
343
  return self._get_completion_stream(messages)
344
344
 
345
- args = self.args
345
+ args = {}
346
+ args.update(self.args)
347
+ args.update(params or {})
346
348
 
347
349
  df = pd.DataFrame(messages)
348
350
 
@@ -598,7 +600,12 @@ AI: {response}"""
598
600
  completions.append(result[ASSISTANT_COLUMN])
599
601
  contexts.append(result[CONTEXT_COLUMN])
600
602
  except TimeoutError:
601
- timeout_message = "I'm sorry! I couldn't come up with a response in time. Please try again."
603
+ timeout_message = (
604
+ f"I'm sorry! I couldn't generate a response within the allotted time ({agent_timeout_seconds} seconds). "
605
+ "If you need more time for processing, you can adjust the timeout settings. "
606
+ "Please refer to the documentation for instructions on how to change the timeout value. "
607
+ "Feel free to try your request again."
608
+ )
602
609
  logger.warning(f"Agent execution timed out after {agent_timeout_seconds} seconds")
603
610
  for _ in range(len(futures) - len(completions)):
604
611
  completions.append(timeout_message)
@@ -1,7 +1,7 @@
1
1
  from typing import List, Union
2
-
3
2
  import pandas as pd
4
-
3
+ import json
4
+ import datetime
5
5
  from mindsdb.integrations.libs.response import RESPONSE_TYPE
6
6
  from mindsdb.interfaces.data_catalog.base_data_catalog import BaseDataCatalog
7
7
  from mindsdb.interfaces.storage import db
@@ -120,7 +120,7 @@ class DataCatalogLoader(BaseDataCatalog):
120
120
  Load the column metadata from the handler.
121
121
  """
122
122
  self.logger.info(f"Loading columns for {self.database_name}")
123
- response = self.data_handler.meta_get_columns(self.table_names)
123
+ response = self.data_handler.meta_get_columns([table.name for table in tables])
124
124
  if response.resp_type == RESPONSE_TYPE.ERROR:
125
125
  self.logger.error(f"Failed to load columns for {self.database_name}: {response.error_message}")
126
126
  return []
@@ -168,7 +168,7 @@ class DataCatalogLoader(BaseDataCatalog):
168
168
  Load the column statistics metadata from the handler.
169
169
  """
170
170
  self.logger.info(f"Loading column statistics for {self.database_name}")
171
- response = self.data_handler.meta_get_column_statistics(self.table_names)
171
+ response = self.data_handler.meta_get_column_statistics([table.name for table in tables])
172
172
  if response.resp_type == RESPONSE_TYPE.ERROR:
173
173
  self.logger.error(f"Failed to load column statistics for {self.database_name}: {response.error_message}")
174
174
  return
@@ -204,6 +204,8 @@ class DataCatalogLoader(BaseDataCatalog):
204
204
  # Convert the distinct_values_count to an integer if it is not NaN, otherwise set it to None.
205
205
  val = row.get("distinct_values_count")
206
206
  distinct_values_count = int(val) if pd.notna(val) else None
207
+ min_val = row.get("minimum_value")
208
+ max_val = row.get("maximum_value")
207
209
 
208
210
  # Convert the most_common_frequencies to a list of strings.
209
211
  most_common_frequencies = [str(val) for val in row.get("most_common_frequencies") or []]
@@ -214,8 +216,8 @@ class DataCatalogLoader(BaseDataCatalog):
214
216
  most_common_frequencies=most_common_frequencies,
215
217
  null_percentage=row.get("null_percentage"),
216
218
  distinct_values_count=distinct_values_count,
217
- minimum_value=row.get("minimum_value"),
218
- maximum_value=row.get("maximum_value"),
219
+ minimum_value=self.to_str(min_val),
220
+ maximum_value=self.to_str(max_val),
219
221
  )
220
222
  column_statistics.append(record)
221
223
 
@@ -231,7 +233,7 @@ class DataCatalogLoader(BaseDataCatalog):
231
233
  Load the primary keys metadata from the handler.
232
234
  """
233
235
  self.logger.info(f"Loading primary keys for {self.database_name}")
234
- response = self.data_handler.meta_get_primary_keys(self.table_names)
236
+ response = self.data_handler.meta_get_primary_keys([table.name for table in tables])
235
237
  if response.resp_type == RESPONSE_TYPE.ERROR:
236
238
  self.logger.error(f"Failed to load primary keys for {self.database_name}: {response.error_message}")
237
239
  return
@@ -283,7 +285,7 @@ class DataCatalogLoader(BaseDataCatalog):
283
285
  Load the foreign keys metadata from the handler.
284
286
  """
285
287
  self.logger.info(f"Loading foreign keys for {self.database_name}")
286
- response = self.data_handler.meta_get_foreign_keys(self.table_names)
288
+ response = self.data_handler.meta_get_foreign_keys([table.name for table in tables])
287
289
  if response.resp_type == RESPONSE_TYPE.ERROR:
288
290
  self.logger.error(f"Failed to foreign keys for {self.database_name}: {response.error_message}")
289
291
  return
@@ -373,3 +375,15 @@ class DataCatalogLoader(BaseDataCatalog):
373
375
  db.session.delete(table)
374
376
  db.session.commit()
375
377
  self.logger.info(f"Metadata for {self.database_name} removed successfully.")
378
+
379
+ def to_str(self, val) -> str:
380
+ """
381
+ Convert a value to a string.
382
+ """
383
+ if val is None:
384
+ return None
385
+ if isinstance(val, (datetime.datetime, datetime.date)):
386
+ return val.isoformat()
387
+ if isinstance(val, (list, dict, set, tuple)):
388
+ return json.dumps(val, default=str)
389
+ return str(val)