MindsDB 25.9.2.0a1__py3-none-any.whl → 25.10.0rc1__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 (163) hide show
  1. mindsdb/__about__.py +1 -1
  2. mindsdb/__main__.py +40 -29
  3. mindsdb/api/a2a/__init__.py +1 -1
  4. mindsdb/api/a2a/agent.py +16 -10
  5. mindsdb/api/a2a/common/server/server.py +7 -3
  6. mindsdb/api/a2a/common/server/task_manager.py +12 -5
  7. mindsdb/api/a2a/common/types.py +66 -0
  8. mindsdb/api/a2a/task_manager.py +65 -17
  9. mindsdb/api/common/middleware.py +10 -12
  10. mindsdb/api/executor/command_executor.py +51 -40
  11. mindsdb/api/executor/datahub/datanodes/datanode.py +2 -2
  12. mindsdb/api/executor/datahub/datanodes/information_schema_datanode.py +7 -13
  13. mindsdb/api/executor/datahub/datanodes/integration_datanode.py +101 -49
  14. mindsdb/api/executor/datahub/datanodes/project_datanode.py +8 -4
  15. mindsdb/api/executor/datahub/datanodes/system_tables.py +3 -2
  16. mindsdb/api/executor/exceptions.py +29 -10
  17. mindsdb/api/executor/planner/plan_join.py +17 -3
  18. mindsdb/api/executor/planner/query_prepare.py +2 -20
  19. mindsdb/api/executor/sql_query/sql_query.py +74 -74
  20. mindsdb/api/executor/sql_query/steps/fetch_dataframe.py +1 -2
  21. mindsdb/api/executor/sql_query/steps/subselect_step.py +0 -1
  22. mindsdb/api/executor/utilities/functions.py +6 -6
  23. mindsdb/api/executor/utilities/sql.py +37 -20
  24. mindsdb/api/http/gui.py +5 -11
  25. mindsdb/api/http/initialize.py +75 -61
  26. mindsdb/api/http/namespaces/agents.py +10 -15
  27. mindsdb/api/http/namespaces/analysis.py +13 -20
  28. mindsdb/api/http/namespaces/auth.py +1 -1
  29. mindsdb/api/http/namespaces/chatbots.py +0 -5
  30. mindsdb/api/http/namespaces/config.py +15 -11
  31. mindsdb/api/http/namespaces/databases.py +140 -201
  32. mindsdb/api/http/namespaces/file.py +17 -4
  33. mindsdb/api/http/namespaces/handlers.py +17 -7
  34. mindsdb/api/http/namespaces/knowledge_bases.py +28 -7
  35. mindsdb/api/http/namespaces/models.py +94 -126
  36. mindsdb/api/http/namespaces/projects.py +13 -22
  37. mindsdb/api/http/namespaces/sql.py +33 -25
  38. mindsdb/api/http/namespaces/tab.py +27 -37
  39. mindsdb/api/http/namespaces/views.py +1 -1
  40. mindsdb/api/http/start.py +16 -10
  41. mindsdb/api/mcp/__init__.py +2 -1
  42. mindsdb/api/mysql/mysql_proxy/executor/mysql_executor.py +15 -20
  43. mindsdb/api/mysql/mysql_proxy/mysql_proxy.py +26 -50
  44. mindsdb/api/mysql/mysql_proxy/utilities/__init__.py +0 -1
  45. mindsdb/api/mysql/mysql_proxy/utilities/dump.py +8 -2
  46. mindsdb/integrations/handlers/byom_handler/byom_handler.py +165 -190
  47. mindsdb/integrations/handlers/databricks_handler/databricks_handler.py +98 -46
  48. mindsdb/integrations/handlers/druid_handler/druid_handler.py +32 -40
  49. mindsdb/integrations/handlers/file_handler/file_handler.py +7 -0
  50. mindsdb/integrations/handlers/gitlab_handler/gitlab_handler.py +5 -2
  51. mindsdb/integrations/handlers/lightwood_handler/functions.py +45 -79
  52. mindsdb/integrations/handlers/mssql_handler/mssql_handler.py +438 -100
  53. mindsdb/integrations/handlers/mssql_handler/requirements_odbc.txt +3 -0
  54. mindsdb/integrations/handlers/mysql_handler/mysql_handler.py +235 -3
  55. mindsdb/integrations/handlers/oracle_handler/__init__.py +2 -0
  56. mindsdb/integrations/handlers/oracle_handler/connection_args.py +7 -1
  57. mindsdb/integrations/handlers/oracle_handler/oracle_handler.py +321 -16
  58. mindsdb/integrations/handlers/oracle_handler/requirements.txt +1 -1
  59. mindsdb/integrations/handlers/postgres_handler/postgres_handler.py +14 -2
  60. mindsdb/integrations/handlers/shopify_handler/shopify_handler.py +25 -12
  61. mindsdb/integrations/handlers/snowflake_handler/snowflake_handler.py +2 -1
  62. mindsdb/integrations/handlers/statsforecast_handler/requirements.txt +1 -0
  63. mindsdb/integrations/handlers/statsforecast_handler/requirements_extra.txt +1 -0
  64. mindsdb/integrations/handlers/web_handler/urlcrawl_helpers.py +4 -4
  65. mindsdb/integrations/handlers/zendesk_handler/zendesk_tables.py +144 -111
  66. mindsdb/integrations/libs/api_handler.py +10 -10
  67. mindsdb/integrations/libs/base.py +4 -4
  68. mindsdb/integrations/libs/llm/utils.py +2 -2
  69. mindsdb/integrations/libs/ml_handler_process/create_engine_process.py +4 -7
  70. mindsdb/integrations/libs/ml_handler_process/func_call_process.py +2 -7
  71. mindsdb/integrations/libs/ml_handler_process/learn_process.py +37 -47
  72. mindsdb/integrations/libs/ml_handler_process/update_engine_process.py +4 -7
  73. mindsdb/integrations/libs/ml_handler_process/update_process.py +2 -7
  74. mindsdb/integrations/libs/process_cache.py +132 -140
  75. mindsdb/integrations/libs/response.py +18 -12
  76. mindsdb/integrations/libs/vectordatabase_handler.py +26 -0
  77. mindsdb/integrations/utilities/files/file_reader.py +6 -7
  78. mindsdb/integrations/utilities/handlers/auth_utilities/snowflake/__init__.py +1 -0
  79. mindsdb/integrations/utilities/handlers/auth_utilities/snowflake/snowflake_jwt_gen.py +151 -0
  80. mindsdb/integrations/utilities/rag/config_loader.py +37 -26
  81. mindsdb/integrations/utilities/rag/rerankers/base_reranker.py +83 -30
  82. mindsdb/integrations/utilities/rag/rerankers/reranker_compressor.py +4 -4
  83. mindsdb/integrations/utilities/rag/retrievers/sql_retriever.py +55 -133
  84. mindsdb/integrations/utilities/rag/settings.py +58 -133
  85. mindsdb/integrations/utilities/rag/splitters/file_splitter.py +5 -15
  86. mindsdb/interfaces/agents/agents_controller.py +2 -3
  87. mindsdb/interfaces/agents/constants.py +0 -2
  88. mindsdb/interfaces/agents/litellm_server.py +34 -58
  89. mindsdb/interfaces/agents/mcp_client_agent.py +10 -10
  90. mindsdb/interfaces/agents/mindsdb_database_agent.py +5 -5
  91. mindsdb/interfaces/agents/run_mcp_agent.py +12 -21
  92. mindsdb/interfaces/chatbot/chatbot_task.py +20 -23
  93. mindsdb/interfaces/chatbot/polling.py +30 -18
  94. mindsdb/interfaces/data_catalog/data_catalog_loader.py +16 -17
  95. mindsdb/interfaces/data_catalog/data_catalog_reader.py +15 -4
  96. mindsdb/interfaces/database/data_handlers_cache.py +190 -0
  97. mindsdb/interfaces/database/database.py +3 -3
  98. mindsdb/interfaces/database/integrations.py +7 -110
  99. mindsdb/interfaces/database/projects.py +2 -6
  100. mindsdb/interfaces/database/views.py +1 -4
  101. mindsdb/interfaces/file/file_controller.py +6 -6
  102. mindsdb/interfaces/functions/controller.py +1 -1
  103. mindsdb/interfaces/functions/to_markdown.py +2 -2
  104. mindsdb/interfaces/jobs/jobs_controller.py +5 -9
  105. mindsdb/interfaces/jobs/scheduler.py +3 -9
  106. mindsdb/interfaces/knowledge_base/controller.py +244 -128
  107. mindsdb/interfaces/knowledge_base/evaluate.py +36 -41
  108. mindsdb/interfaces/knowledge_base/executor.py +11 -0
  109. mindsdb/interfaces/knowledge_base/llm_client.py +51 -17
  110. mindsdb/interfaces/knowledge_base/preprocessing/json_chunker.py +40 -61
  111. mindsdb/interfaces/model/model_controller.py +172 -168
  112. mindsdb/interfaces/query_context/context_controller.py +14 -2
  113. mindsdb/interfaces/skills/custom/text2sql/mindsdb_sql_toolkit.py +10 -14
  114. mindsdb/interfaces/skills/retrieval_tool.py +43 -50
  115. mindsdb/interfaces/skills/skill_tool.py +2 -2
  116. mindsdb/interfaces/skills/skills_controller.py +1 -4
  117. mindsdb/interfaces/skills/sql_agent.py +25 -19
  118. mindsdb/interfaces/storage/db.py +16 -6
  119. mindsdb/interfaces/storage/fs.py +114 -169
  120. mindsdb/interfaces/storage/json.py +19 -18
  121. mindsdb/interfaces/tabs/tabs_controller.py +49 -72
  122. mindsdb/interfaces/tasks/task_monitor.py +3 -9
  123. mindsdb/interfaces/tasks/task_thread.py +7 -9
  124. mindsdb/interfaces/triggers/trigger_task.py +7 -13
  125. mindsdb/interfaces/triggers/triggers_controller.py +47 -52
  126. mindsdb/migrations/migrate.py +16 -16
  127. mindsdb/utilities/api_status.py +58 -0
  128. mindsdb/utilities/config.py +68 -2
  129. mindsdb/utilities/exception.py +40 -1
  130. mindsdb/utilities/fs.py +0 -1
  131. mindsdb/utilities/hooks/profiling.py +17 -14
  132. mindsdb/utilities/json_encoder.py +24 -10
  133. mindsdb/utilities/langfuse.py +40 -45
  134. mindsdb/utilities/log.py +272 -0
  135. mindsdb/utilities/ml_task_queue/consumer.py +52 -58
  136. mindsdb/utilities/ml_task_queue/producer.py +26 -30
  137. mindsdb/utilities/render/sqlalchemy_render.py +22 -20
  138. mindsdb/utilities/starters.py +0 -10
  139. mindsdb/utilities/utils.py +2 -2
  140. {mindsdb-25.9.2.0a1.dist-info → mindsdb-25.10.0rc1.dist-info}/METADATA +293 -276
  141. {mindsdb-25.9.2.0a1.dist-info → mindsdb-25.10.0rc1.dist-info}/RECORD +144 -158
  142. mindsdb/api/mysql/mysql_proxy/utilities/exceptions.py +0 -14
  143. mindsdb/api/postgres/__init__.py +0 -0
  144. mindsdb/api/postgres/postgres_proxy/__init__.py +0 -0
  145. mindsdb/api/postgres/postgres_proxy/executor/__init__.py +0 -1
  146. mindsdb/api/postgres/postgres_proxy/executor/executor.py +0 -189
  147. mindsdb/api/postgres/postgres_proxy/postgres_packets/__init__.py +0 -0
  148. mindsdb/api/postgres/postgres_proxy/postgres_packets/errors.py +0 -322
  149. mindsdb/api/postgres/postgres_proxy/postgres_packets/postgres_fields.py +0 -34
  150. mindsdb/api/postgres/postgres_proxy/postgres_packets/postgres_message.py +0 -31
  151. mindsdb/api/postgres/postgres_proxy/postgres_packets/postgres_message_formats.py +0 -1265
  152. mindsdb/api/postgres/postgres_proxy/postgres_packets/postgres_message_identifiers.py +0 -31
  153. mindsdb/api/postgres/postgres_proxy/postgres_packets/postgres_packets.py +0 -253
  154. mindsdb/api/postgres/postgres_proxy/postgres_proxy.py +0 -477
  155. mindsdb/api/postgres/postgres_proxy/utilities/__init__.py +0 -10
  156. mindsdb/api/postgres/start.py +0 -11
  157. mindsdb/integrations/handlers/mssql_handler/tests/__init__.py +0 -0
  158. mindsdb/integrations/handlers/mssql_handler/tests/test_mssql_handler.py +0 -169
  159. mindsdb/integrations/handlers/oracle_handler/tests/__init__.py +0 -0
  160. mindsdb/integrations/handlers/oracle_handler/tests/test_oracle_handler.py +0 -32
  161. {mindsdb-25.9.2.0a1.dist-info → mindsdb-25.10.0rc1.dist-info}/WHEEL +0 -0
  162. {mindsdb-25.9.2.0a1.dist-info → mindsdb-25.10.0rc1.dist-info}/licenses/LICENSE +0 -0
  163. {mindsdb-25.9.2.0a1.dist-info → mindsdb-25.10.0rc1.dist-info}/top_level.txt +0 -0
mindsdb/__about__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  __title__ = "MindsDB"
2
2
  __package_name__ = "mindsdb"
3
- __version__ = "25.9.2.0a1"
3
+ __version__ = "25.10.0rc1"
4
4
  __description__ = "MindsDB's AI SQL Server enables developers to build AI tools that need access to real-time data to perform their tasks"
5
5
  __email__ = "jorge@mindsdb.com"
6
6
  __author__ = "MindsDB Inc"
mindsdb/__main__.py CHANGED
@@ -8,8 +8,8 @@ import atexit
8
8
  import signal
9
9
  import psutil
10
10
  import asyncio
11
- import traceback
12
11
  import threading
12
+ import shutil
13
13
  from enum import Enum
14
14
  from dataclasses import dataclass, field
15
15
  from typing import Callable, Optional, Tuple, List
@@ -27,7 +27,6 @@ from mindsdb.utilities.config import config
27
27
  from mindsdb.utilities.starters import (
28
28
  start_http,
29
29
  start_mysql,
30
- start_postgres,
31
30
  start_ml_task_queue,
32
31
  start_scheduler,
33
32
  start_tasks,
@@ -39,6 +38,7 @@ from mindsdb.utilities.fs import clean_process_marks, clean_unlinked_process_mar
39
38
  from mindsdb.utilities.context import context as ctx
40
39
  from mindsdb.utilities.auth import register_oauth_client, get_aws_meta_data
41
40
  from mindsdb.utilities.sentry import sentry_sdk # noqa: F401
41
+ from mindsdb.utilities.api_status import set_api_status
42
42
 
43
43
  try:
44
44
  import torch.multiprocessing as mp
@@ -57,7 +57,6 @@ _stop_event = threading.Event()
57
57
  class TrunkProcessEnum(Enum):
58
58
  HTTP = "http"
59
59
  MYSQL = "mysql"
60
- POSTGRES = "postgres"
61
60
  JOBS = "jobs"
62
61
  TASKS = "tasks"
63
62
  ML_TASK_QUEUE = "ml_task_queue"
@@ -152,6 +151,16 @@ def close_api_gracefully(trunc_processes_struct):
152
151
  sys.exit(0)
153
152
 
154
153
 
154
+ def clean_mindsdb_tmp_dir():
155
+ """Clean the MindsDB tmp dir at exit."""
156
+ temp_dir = config["paths"]["tmp"]
157
+ for file in temp_dir.iterdir():
158
+ if file.is_dir():
159
+ shutil.rmtree(file)
160
+ else:
161
+ file.unlink()
162
+
163
+
155
164
  def set_error_model_status_by_pids(unexisting_pids: List[int]):
156
165
  """Models have id of its traiing process in the 'training_metadata' field.
157
166
  If the pid does not exist, we should set the model status to "error".
@@ -217,19 +226,20 @@ def create_permanent_integrations():
217
226
  """
218
227
  integration_name = "files"
219
228
  existing = db.session.query(db.Integration).filter_by(name=integration_name, company_id=None).first()
220
- if existing is None:
221
- integration_record = db.Integration(
222
- name=integration_name,
223
- data={},
224
- engine=integration_name,
225
- company_id=None,
226
- )
227
- db.session.add(integration_record)
228
- try:
229
- db.session.commit()
230
- except Exception as e:
231
- logger.error(f"Failed to commit permanent integration {integration_name}: {e}")
232
- db.session.rollback()
229
+ if existing is not None:
230
+ return
231
+ integration_record = db.Integration(
232
+ name=integration_name,
233
+ data={},
234
+ engine=integration_name,
235
+ company_id=None,
236
+ )
237
+ db.session.add(integration_record)
238
+ try:
239
+ db.session.commit()
240
+ except Exception:
241
+ logger.exception(f"Failed to create permanent integration '{integration_name}' in the internal database.")
242
+ db.session.rollback()
233
243
 
234
244
 
235
245
  def validate_default_project() -> None:
@@ -289,7 +299,7 @@ def start_process(trunc_process_data: TrunkProcessData) -> None:
289
299
  )
290
300
  trunc_process_data.process.start()
291
301
  except Exception as e:
292
- logger.error(f"Failed to start {trunc_process_data.name} API with exception {e}\n{traceback.format_exc()}")
302
+ logger.exception(f"Failed to start '{trunc_process_data.name}' API process due to unexpected error:")
293
303
  close_api_gracefully(trunc_processes_struct)
294
304
  raise e
295
305
 
@@ -363,8 +373,8 @@ if __name__ == "__main__":
363
373
  if environment == "aws_marketplace":
364
374
  try:
365
375
  register_oauth_client()
366
- except Exception as e:
367
- logger.error(f"Something went wrong during client register: {e}")
376
+ except Exception:
377
+ logger.exception("Something went wrong during client register:")
368
378
  elif environment != "local":
369
379
  try:
370
380
  aws_meta_data = get_aws_meta_data()
@@ -384,6 +394,7 @@ if __name__ == "__main__":
384
394
  logger.info(f"Version: {mindsdb_version}")
385
395
  logger.info(f"Configuration file: {config.config_path or 'absent'}")
386
396
  logger.info(f"Storage path: {config.paths['root']}")
397
+ log.log_system_info(logger)
387
398
  logger.debug(f"User config: {config.user_config}")
388
399
  logger.debug(f"System config: {config.auto_config}")
389
400
  logger.debug(f"Env config: {config.env_config}")
@@ -391,13 +402,12 @@ if __name__ == "__main__":
391
402
  is_cloud = config.is_cloud
392
403
  unexisting_pids = clean_unlinked_process_marks()
393
404
  if not is_cloud:
394
- logger.debug("Applying database migrations")
395
405
  try:
396
406
  from mindsdb.migrations import migrate
397
407
 
398
408
  migrate.migrate_to_head()
399
- except Exception as e:
400
- logger.error(f"Error! Something went wrong during DB migrations: {e}")
409
+ except Exception:
410
+ logger.exception("Failed to apply database migrations. This may prevent MindsDB from operating correctly:")
401
411
 
402
412
  validate_default_project()
403
413
 
@@ -435,12 +445,6 @@ if __name__ == "__main__":
435
445
  "max_restart_interval_seconds", TrunkProcessData.max_restart_interval_seconds
436
446
  ),
437
447
  ),
438
- TrunkProcessEnum.POSTGRES: TrunkProcessData(
439
- name=TrunkProcessEnum.POSTGRES.value,
440
- entrypoint=start_postgres,
441
- port=config["api"]["postgres"]["port"],
442
- args=(config.cmd_args.verbose,),
443
- ),
444
448
  TrunkProcessEnum.JOBS: TrunkProcessData(
445
449
  name=TrunkProcessEnum.JOBS.value, entrypoint=start_scheduler, args=(config.cmd_args.verbose,)
446
450
  ),
@@ -484,8 +488,12 @@ if __name__ == "__main__":
484
488
  if trunc_process_data.started is True or trunc_process_data.need_to_run is False:
485
489
  continue
486
490
  start_process(trunc_process_data)
491
+ # Set status for APIs without ports (they don't go through wait_api_start)
492
+ if trunc_process_data.port is None:
493
+ set_api_status(trunc_process_data.name, True)
487
494
 
488
495
  atexit.register(close_api_gracefully, trunc_processes_struct=trunc_processes_struct)
496
+ atexit.register(clean_mindsdb_tmp_dir)
489
497
 
490
498
  async def wait_api_start(api_name, pid, port):
491
499
  timeout = 60
@@ -494,6 +502,9 @@ if __name__ == "__main__":
494
502
  while (time.time() - start_time) < timeout and started is False:
495
503
  await asyncio.sleep(0.5)
496
504
  started = is_pid_listen_port(pid, port)
505
+
506
+ set_api_status(api_name, started)
507
+
497
508
  return api_name, port, started
498
509
 
499
510
  async def wait_apis_start():
@@ -531,7 +542,7 @@ if __name__ == "__main__":
531
542
  trunc_process_data.process = None
532
543
  if trunc_process_data.name == TrunkProcessEnum.HTTP.value:
533
544
  # do not open GUI on HTTP API restart
534
- trunc_process_data.args = (config.cmd_args.verbose, True)
545
+ trunc_process_data.args = (config.cmd_args.verbose, None, True)
535
546
  start_process(trunc_process_data)
536
547
  api_name, port, started = await wait_api_start(
537
548
  trunc_process_data.name,
@@ -33,7 +33,7 @@ def get_a2a_app(
33
33
  agent_card = AgentCard(
34
34
  name="MindsDB Agent Connector",
35
35
  description=(f"A2A connector that proxies requests to MindsDB agents in project '{project_name}'."),
36
- url=f"http://127.0.0.1:{mindsdb_port}",
36
+ url=f"http://127.0.0.1:{mindsdb_port}/a2a/",
37
37
  version="1.0.0",
38
38
  defaultInputModes=MindsDBAgent.SUPPORTED_CONTENT_TYPES,
39
39
  defaultOutputModes=MindsDBAgent.SUPPORTED_CONTENT_TYPES,
mindsdb/api/a2a/agent.py CHANGED
@@ -24,7 +24,15 @@ class MindsDBAgent:
24
24
  self.agent_name = agent_name
25
25
  self.project_name = project_name
26
26
  port = config.get("api", {}).get("http", {}).get("port", 47334)
27
- self.base_url = f"http://localhost:{port}"
27
+ host = config.get("api", {}).get("http", {}).get("host", "127.0.0.1")
28
+
29
+ # Use 127.0.0.1 instead of localhost for better compatibility
30
+ if host in ("0.0.0.0", ""):
31
+ url = f"http://127.0.0.1:{port}/"
32
+ else:
33
+ url = f"http://{host}:{port}/"
34
+
35
+ self.base_url = url
28
36
  self.agent_url = f"{self.base_url}/api/projects/{project_name}/agents/{agent_name}"
29
37
  self.sql_url = f"{self.base_url}/api/sql/query"
30
38
  self.headers = {k: v for k, v in user_info.items() if v is not None} or {}
@@ -73,17 +81,15 @@ class MindsDBAgent:
73
81
  "parts": [{"type": "text", "text": error_msg}],
74
82
  }
75
83
  except requests.exceptions.RequestException as e:
76
- error_msg = f"Error connecting to MindsDB: {str(e)}"
77
- logger.error(error_msg)
84
+ logger.exception("Error connecting to MindsDB:")
78
85
  return {
79
- "content": error_msg,
86
+ "content": f"Error connecting to MindsDB: {e}",
80
87
  "parts": [{"type": "text", "text": error_msg}],
81
88
  }
82
89
  except Exception as e:
83
- error_msg = f"Error: {str(e)}"
84
- logger.error(error_msg)
90
+ logger.exception("Error: ")
85
91
  return {
86
- "content": error_msg,
92
+ "content": f"Error: {e}",
87
93
  "parts": [{"type": "text", "text": error_msg}],
88
94
  }
89
95
 
@@ -102,7 +108,7 @@ class MindsDBAgent:
102
108
  try:
103
109
  yield json.loads(payload)
104
110
  except Exception as e:
105
- logger.error(f"Failed to parse SSE JSON payload: {e}; line: {payload}")
111
+ logger.exception(f"Failed to parse SSE JSON payload: {e}; line: {payload}")
106
112
  # Ignore comments or control lines
107
113
  # Signal the end of the stream
108
114
  yield {"is_task_complete": True}
@@ -129,13 +135,13 @@ class MindsDBAgent:
129
135
  wrapped_chunk = {"is_task_complete": False, "content": content_value, "metadata": {}}
130
136
  yield wrapped_chunk
131
137
  except Exception as e:
132
- logger.error(f"Error in streaming: {str(e)}")
138
+ logger.exception(f"Error in streaming: {e}")
133
139
  yield {
134
140
  "is_task_complete": True,
135
141
  "parts": [
136
142
  {
137
143
  "type": "text",
138
- "text": f"Error: {str(e)}",
144
+ "text": f"Error: {e}",
139
145
  }
140
146
  ],
141
147
  "metadata": {
@@ -22,6 +22,7 @@ from ...common.types import (
22
22
  AgentCard,
23
23
  TaskResubscriptionRequest,
24
24
  SendTaskStreamingRequest,
25
+ MessageStreamRequest,
25
26
  )
26
27
  from pydantic import ValidationError
27
28
  from ...common.server.task_manager import TaskManager
@@ -43,6 +44,7 @@ class A2AServer:
43
44
  routes=[
44
45
  Route("/", self._process_request, methods=["POST"]),
45
46
  Route("/.well-known/agent.json", self._get_agent_card, methods=["GET"]),
47
+ Route("/.well-known/agent-card.json", self._get_agent_card, methods=["GET"]),
46
48
  Route("/status", self._get_status, methods=["GET"]),
47
49
  ]
48
50
  )
@@ -103,6 +105,8 @@ class A2AServer:
103
105
  result = await self.task_manager.on_get_task_push_notification(json_rpc_request)
104
106
  elif isinstance(json_rpc_request, TaskResubscriptionRequest):
105
107
  result = await self.task_manager.on_resubscribe_to_task(json_rpc_request)
108
+ elif isinstance(json_rpc_request, MessageStreamRequest):
109
+ result = await self.task_manager.on_message_stream(json_rpc_request, user_info)
106
110
  else:
107
111
  logger.warning(f"Unexpected request type: {type(json_rpc_request)}")
108
112
  raise ValueError(f"Unexpected request type: {type(request)}")
@@ -118,7 +122,7 @@ class A2AServer:
118
122
  elif isinstance(e, ValidationError):
119
123
  json_rpc_error = InvalidRequestError(data=json.loads(e.json()))
120
124
  else:
121
- logger.error(f"Unhandled exception: {e}")
125
+ logger.exception("Unhandled exception:")
122
126
  json_rpc_error = InternalError()
123
127
 
124
128
  response = JSONRPCResponse(id=None, error=json_rpc_error)
@@ -137,8 +141,8 @@ class A2AServer:
137
141
  else:
138
142
  data = json.dumps(item)
139
143
  except Exception as e:
140
- logger.error(f"Serialization error in SSE stream: {e}")
141
- data = json.dumps({"error": f"Serialization error: {str(e)}"})
144
+ logger.exception("Serialization error in SSE stream:")
145
+ data = json.dumps({"error": f"Serialization error: {e}"})
142
146
  yield {"data": data}
143
147
 
144
148
  # Add robust SSE headers for compatibility
@@ -1,7 +1,7 @@
1
1
  from abc import ABC, abstractmethod
2
2
  from typing import Union, AsyncIterable, List, Dict
3
- from ...common.types import Task
4
3
  from ...common.types import (
4
+ Task,
5
5
  JSONRPCResponse,
6
6
  TaskIdParams,
7
7
  TaskQueryParams,
@@ -29,6 +29,7 @@ from ...common.types import (
29
29
  JSONRPCError,
30
30
  TaskPushNotificationConfig,
31
31
  InternalError,
32
+ MessageStreamRequest,
32
33
  )
33
34
  from ...common.server.utils import new_not_implemented_error
34
35
  from mindsdb.utilities import log
@@ -74,6 +75,12 @@ class TaskManager(ABC):
74
75
  ) -> Union[AsyncIterable[SendTaskResponse], JSONRPCResponse]:
75
76
  pass
76
77
 
78
+ @abstractmethod
79
+ async def on_message_stream(
80
+ self, request: MessageStreamRequest, user_info: Dict
81
+ ) -> Union[AsyncIterable[SendTaskStreamingResponse], JSONRPCResponse]:
82
+ pass
83
+
77
84
 
78
85
  class InMemoryTaskManager(TaskManager):
79
86
  def __init__(self):
@@ -152,8 +159,8 @@ class InMemoryTaskManager(TaskManager):
152
159
  task_notification_params.id,
153
160
  task_notification_params.pushNotificationConfig,
154
161
  )
155
- except Exception as e:
156
- logger.error(f"Error while setting push notification info: {e}")
162
+ except Exception:
163
+ logger.exception("Error while setting push notification info:")
157
164
  return JSONRPCResponse(
158
165
  id=request.id,
159
166
  error=InternalError(message="An error occurred while setting push notification info"),
@@ -169,8 +176,8 @@ class InMemoryTaskManager(TaskManager):
169
176
 
170
177
  try:
171
178
  notification_info = await self.get_push_notification_info(task_params.id)
172
- except Exception as e:
173
- logger.error(f"Error while getting push notification info: {e}")
179
+ except Exception:
180
+ logger.exception("Error while getting push notification info:")
174
181
  return GetTaskPushNotificationResponse(
175
182
  id=request.id,
176
183
  error=InternalError(message="An error occurred while getting push notification info"),
@@ -59,6 +59,47 @@ class Message(BaseModel):
59
59
  parts: List[Part]
60
60
  metadata: dict[str, Any] | None = None
61
61
  history: Optional[List["Message"]] = None
62
+ messageId: str | None = None
63
+
64
+
65
+ class FlexibleMessage(BaseModel):
66
+ """Message that can handle both 'type' and 'kind' in parts."""
67
+
68
+ role: Literal["user", "agent", "assistant"]
69
+ parts: List[dict[str, Any]] # Raw parts that we'll process manually
70
+ metadata: dict[str, Any] | None = None
71
+ history: Optional[List["FlexibleMessage"]] = None
72
+
73
+ @model_validator(mode="after")
74
+ def normalize_parts(self):
75
+ """Convert parts with 'kind' to parts with 'type'."""
76
+ normalized_parts = []
77
+ for part in self.parts:
78
+ if isinstance(part, dict):
79
+ # Convert 'kind' to 'type' if needed
80
+ if "kind" in part and "type" not in part:
81
+ normalized_part = part.copy()
82
+ normalized_part["type"] = normalized_part.pop("kind")
83
+ else:
84
+ normalized_part = part
85
+
86
+ # Validate the normalized part
87
+ try:
88
+ if normalized_part.get("type") == "text":
89
+ normalized_parts.append(TextPart.model_validate(normalized_part))
90
+ elif normalized_part.get("type") == "file":
91
+ normalized_parts.append(FilePart.model_validate(normalized_part))
92
+ elif normalized_part.get("type") == "data":
93
+ normalized_parts.append(DataPart.model_validate(normalized_part))
94
+ else:
95
+ raise ValueError(f"Unknown part type: {normalized_part.get('type')}")
96
+ except Exception as e:
97
+ raise ValueError(f"Invalid part: {normalized_part}, error: {e}")
98
+ else:
99
+ normalized_parts.append(part)
100
+
101
+ self.parts = normalized_parts
102
+ return self
62
103
 
63
104
 
64
105
  class TaskStatus(BaseModel):
@@ -88,6 +129,7 @@ class Task(BaseModel):
88
129
  artifacts: List[Artifact] | None = None
89
130
  history: List[Message] | None = None
90
131
  metadata: dict[str, Any] | None = None
132
+ contextId: str | None = None
91
133
 
92
134
 
93
135
  class TaskStatusUpdateEvent(BaseModel):
@@ -95,12 +137,16 @@ class TaskStatusUpdateEvent(BaseModel):
95
137
  status: TaskStatus
96
138
  final: bool = False
97
139
  metadata: dict[str, Any] | None = None
140
+ contextId: str | None = None
141
+ taskId: str | None = None
98
142
 
99
143
 
100
144
  class TaskArtifactUpdateEvent(BaseModel):
101
145
  id: str
102
146
  artifact: Artifact
103
147
  metadata: dict[str, Any] | None = None
148
+ contextId: str | None = None
149
+ taskId: str | None = None
104
150
 
105
151
 
106
152
  class AuthenticationInfo(BaseModel):
@@ -182,6 +228,25 @@ class SendTaskStreamingResponse(JSONRPCResponse):
182
228
  result: TaskStatusUpdateEvent | TaskArtifactUpdateEvent | None = None
183
229
 
184
230
 
231
+ class MessageStreamParams(BaseModel):
232
+ sessionId: str = Field(default_factory=lambda: uuid4().hex)
233
+ message: FlexibleMessage
234
+ metadata: dict[str, Any] | None = None
235
+
236
+
237
+ class MessageStreamRequest(JSONRPCRequest):
238
+ method: Literal["message/stream"] = "message/stream"
239
+ params: MessageStreamParams
240
+
241
+
242
+ class MessageStreamResponse(JSONRPCResponse):
243
+ result: Message | None = None
244
+
245
+
246
+ class SendStreamingMessageSuccessResponse(JSONRPCResponse):
247
+ result: Union[Task, TaskStatusUpdateEvent, TaskArtifactUpdateEvent] | None = None
248
+
249
+
185
250
  class GetTaskRequest(JSONRPCRequest):
186
251
  method: Literal["tasks/get"] = "tasks/get"
187
252
  params: TaskQueryParams
@@ -233,6 +298,7 @@ A2ARequest = TypeAdapter(
233
298
  GetTaskPushNotificationRequest,
234
299
  TaskResubscriptionRequest,
235
300
  SendTaskStreamingRequest,
301
+ MessageStreamRequest,
236
302
  ],
237
303
  Field(discriminator="method"),
238
304
  ]
@@ -1,4 +1,8 @@
1
- from typing import AsyncIterable, Dict
1
+ import time
2
+ import logging
3
+ import asyncio
4
+ from typing import AsyncIterable, Dict, Union
5
+
2
6
  from mindsdb.api.a2a.common.types import (
3
7
  SendTaskRequest,
4
8
  TaskSendParams,
@@ -15,16 +19,14 @@ from mindsdb.api.a2a.common.types import (
15
19
  SendTaskStreamingRequest,
16
20
  SendTaskStreamingResponse,
17
21
  InvalidRequestError,
22
+ MessageStreamRequest,
23
+ SendStreamingMessageSuccessResponse,
18
24
  )
19
25
  from mindsdb.api.a2a.common.server.task_manager import InMemoryTaskManager
20
26
  from mindsdb.api.a2a.agent import MindsDBAgent
21
27
  from mindsdb.api.a2a.utils import to_serializable, convert_a2a_message_to_qa_format
28
+ from mindsdb.interfaces.agents.agents_controller import AgentsController
22
29
 
23
- from typing import Union
24
- import logging
25
- import asyncio
26
- import time
27
- import traceback
28
30
 
29
31
  logger = logging.getLogger(__name__)
30
32
 
@@ -80,11 +82,11 @@ class AgentTaskManager(InMemoryTaskManager):
80
82
  task = await self.upsert_task(task_send_params)
81
83
  logger.info(f"Task created/updated with history length: {len(task.history) if task.history else 0}")
82
84
  except Exception as e:
83
- logger.error(f"Error creating task: {str(e)}")
85
+ logger.exception("Error creating task:")
84
86
  error_result = to_serializable(
85
87
  {
86
88
  "id": request.id,
87
- "error": to_serializable(InternalError(message=f"Error creating task: {str(e)}")),
89
+ "error": to_serializable(InternalError(message=f"Error creating task: {e}")),
88
90
  }
89
91
  )
90
92
  yield error_result
@@ -149,14 +151,14 @@ class AgentTaskManager(InMemoryTaskManager):
149
151
  return
150
152
 
151
153
  except Exception as e:
152
- logger.error(f"Error invoking agent: {e}")
154
+ logger.exception("Error invoking agent:")
153
155
  error_result = to_serializable(
154
156
  {
155
157
  "id": request.id,
156
158
  "error": to_serializable(
157
159
  JSONRPCResponse(
158
160
  id=request.id,
159
- error=to_serializable(InternalError(message=f"Error invoking agent: {str(e)}")),
161
+ error=to_serializable(InternalError(message=f"Error invoking agent: {e}")),
160
162
  )
161
163
  ),
162
164
  }
@@ -182,11 +184,10 @@ class AgentTaskManager(InMemoryTaskManager):
182
184
  item["artifact"]["parts"] = [to_serializable(p) for p in item["artifact"]["parts"]]
183
185
  yield to_serializable(item)
184
186
  except Exception as e:
185
- logger.error(f"An error occurred while streaming the response: {e}")
186
- logger.error(traceback.format_exc())
187
- error_text = f"An error occurred while streaming the response: {str(e)}"
187
+ error_text = "An error occurred while streaming the response:"
188
+ logger.exception(error_text)
188
189
  # Ensure all parts are plain dicts
189
- parts = [{"type": "text", "text": error_text}]
190
+ parts = [{"type": "text", "text": f"{error_text} {e}"}]
190
191
  parts = [to_serializable(part) for part in parts]
191
192
  artifact = {
192
193
  "parts": parts,
@@ -333,11 +334,11 @@ class AgentTaskManager(InMemoryTaskManager):
333
334
  yield response
334
335
  except Exception as e:
335
336
  # If an error occurs, yield an error response
336
- logger.error(f"Error in on_send_task_subscribe: {str(e)}")
337
+ logger.exception(f"Error in on_send_task_subscribe: {e}")
337
338
  error_result = to_serializable(
338
339
  {
339
340
  "id": request.id,
340
- "error": to_serializable(InternalError(message=f"Error processing streaming request: {str(e)}")),
341
+ "error": to_serializable(InternalError(message=f"Error processing streaming request: {e}")),
341
342
  }
342
343
  )
343
344
  yield error_result
@@ -463,7 +464,7 @@ class AgentTaskManager(InMemoryTaskManager):
463
464
  )
464
465
  return to_serializable(SendTaskResponse(id=request.id, result=task))
465
466
  except Exception as e:
466
- logger.error(f"Error invoking agent: {e}")
467
+ logger.exception("Error invoking agent:")
467
468
  result_text = f"Error invoking agent: {e}"
468
469
  parts = [{"type": "text", "text": result_text}]
469
470
 
@@ -474,3 +475,50 @@ class AgentTaskManager(InMemoryTaskManager):
474
475
  [Artifact(parts=parts)],
475
476
  )
476
477
  return to_serializable(SendTaskResponse(id=request.id, result=task))
478
+
479
+ async def on_message_stream(
480
+ self, request: MessageStreamRequest, user_info: Dict
481
+ ) -> Union[AsyncIterable[SendStreamingMessageSuccessResponse], JSONRPCResponse]:
482
+ """
483
+ Handle message streaming requests.
484
+ """
485
+ logger.info(f"Processing message stream request for session {request.params.sessionId}")
486
+
487
+ query = self._get_user_query(request.params)
488
+ params = self._get_task_params(request.params)
489
+
490
+ try:
491
+ task_id = f"msg_stream_{request.params.sessionId}_{request.id}"
492
+ context_id = f"ctx_{request.params.sessionId}"
493
+ message_id = f"msg_{request.id}"
494
+
495
+ agents_controller = AgentsController()
496
+ existing_agent = agents_controller.get_agent(params["agent_name"])
497
+ resp = agents_controller.get_completion(existing_agent, [{"question": query}])
498
+ response_message = resp["answer"][0]
499
+
500
+ response_message = Message(
501
+ role="agent", parts=[{"type": "text", "text": response_message}], metadata={}, messageId=message_id
502
+ )
503
+
504
+ task_status = TaskStatus(state=TaskState.COMPLETED, message=response_message)
505
+
506
+ task_status_update = TaskStatusUpdateEvent(
507
+ id=task_id,
508
+ status=task_status,
509
+ final=True,
510
+ metadata={"message_stream": True},
511
+ contextId=context_id,
512
+ taskId=task_id,
513
+ )
514
+
515
+ async def message_stream_generator():
516
+ yield to_serializable(SendStreamingMessageSuccessResponse(id=request.id, result=task_status_update))
517
+
518
+ return message_stream_generator()
519
+
520
+ except Exception as e:
521
+ logger.error(f"Error processing message stream: {e}")
522
+ return SendStreamingMessageSuccessResponse(
523
+ id=request.id, error=InternalError(message=f"Error processing message stream: {str(e)}")
524
+ )
@@ -1,13 +1,13 @@
1
+ import os
2
+ import hmac
3
+ import secrets
4
+ import hashlib
5
+ from http import HTTPStatus
6
+ from typing import Optional
7
+
1
8
  from starlette.middleware.base import BaseHTTPMiddleware
2
9
  from starlette.responses import JSONResponse
3
10
  from starlette.requests import Request
4
- from http import HTTPStatus
5
- from typing import Optional
6
- import secrets
7
- import hmac
8
- import hashlib
9
- import os
10
- import traceback
11
11
 
12
12
  from mindsdb.utilities import log
13
13
  from mindsdb.utilities.config import config
@@ -75,7 +75,7 @@ class PATAuthMiddleware(BaseHTTPMiddleware):
75
75
  return await call_next(request)
76
76
 
77
77
 
78
- # Used by mysql and postgres protocols
78
+ # Used by mysql protocol
79
79
  def check_auth(username, password, scramble_func, salt, company_id, config):
80
80
  try:
81
81
  hardcoded_user = config["auth"].get("username")
@@ -100,7 +100,5 @@ def check_auth(username, password, scramble_func, salt, company_id, config):
100
100
 
101
101
  logger.info(f"Check auth, user={username}: Ok")
102
102
  return {"success": True, "username": username}
103
- except Exception as e:
104
- logger.error(f"Check auth, user={username}: ERROR")
105
- logger.error(e)
106
- logger.error(traceback.format_exc())
103
+ except Exception:
104
+ logger.exception(f"Check auth, user={username}: ERROR")