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.
- mindsdb/__about__.py +1 -1
- mindsdb/__main__.py +40 -29
- mindsdb/api/a2a/__init__.py +1 -1
- mindsdb/api/a2a/agent.py +16 -10
- mindsdb/api/a2a/common/server/server.py +7 -3
- mindsdb/api/a2a/common/server/task_manager.py +12 -5
- mindsdb/api/a2a/common/types.py +66 -0
- mindsdb/api/a2a/task_manager.py +65 -17
- mindsdb/api/common/middleware.py +10 -12
- mindsdb/api/executor/command_executor.py +51 -40
- mindsdb/api/executor/datahub/datanodes/datanode.py +2 -2
- mindsdb/api/executor/datahub/datanodes/information_schema_datanode.py +7 -13
- mindsdb/api/executor/datahub/datanodes/integration_datanode.py +101 -49
- mindsdb/api/executor/datahub/datanodes/project_datanode.py +8 -4
- mindsdb/api/executor/datahub/datanodes/system_tables.py +3 -2
- mindsdb/api/executor/exceptions.py +29 -10
- mindsdb/api/executor/planner/plan_join.py +17 -3
- mindsdb/api/executor/planner/query_prepare.py +2 -20
- mindsdb/api/executor/sql_query/sql_query.py +74 -74
- mindsdb/api/executor/sql_query/steps/fetch_dataframe.py +1 -2
- mindsdb/api/executor/sql_query/steps/subselect_step.py +0 -1
- mindsdb/api/executor/utilities/functions.py +6 -6
- mindsdb/api/executor/utilities/sql.py +37 -20
- mindsdb/api/http/gui.py +5 -11
- mindsdb/api/http/initialize.py +75 -61
- mindsdb/api/http/namespaces/agents.py +10 -15
- mindsdb/api/http/namespaces/analysis.py +13 -20
- mindsdb/api/http/namespaces/auth.py +1 -1
- mindsdb/api/http/namespaces/chatbots.py +0 -5
- mindsdb/api/http/namespaces/config.py +15 -11
- mindsdb/api/http/namespaces/databases.py +140 -201
- mindsdb/api/http/namespaces/file.py +17 -4
- mindsdb/api/http/namespaces/handlers.py +17 -7
- mindsdb/api/http/namespaces/knowledge_bases.py +28 -7
- mindsdb/api/http/namespaces/models.py +94 -126
- mindsdb/api/http/namespaces/projects.py +13 -22
- mindsdb/api/http/namespaces/sql.py +33 -25
- mindsdb/api/http/namespaces/tab.py +27 -37
- mindsdb/api/http/namespaces/views.py +1 -1
- mindsdb/api/http/start.py +16 -10
- mindsdb/api/mcp/__init__.py +2 -1
- mindsdb/api/mysql/mysql_proxy/executor/mysql_executor.py +15 -20
- mindsdb/api/mysql/mysql_proxy/mysql_proxy.py +26 -50
- mindsdb/api/mysql/mysql_proxy/utilities/__init__.py +0 -1
- mindsdb/api/mysql/mysql_proxy/utilities/dump.py +8 -2
- mindsdb/integrations/handlers/byom_handler/byom_handler.py +165 -190
- mindsdb/integrations/handlers/databricks_handler/databricks_handler.py +98 -46
- mindsdb/integrations/handlers/druid_handler/druid_handler.py +32 -40
- mindsdb/integrations/handlers/file_handler/file_handler.py +7 -0
- mindsdb/integrations/handlers/gitlab_handler/gitlab_handler.py +5 -2
- mindsdb/integrations/handlers/lightwood_handler/functions.py +45 -79
- 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 +14 -2
- mindsdb/integrations/handlers/shopify_handler/shopify_handler.py +25 -12
- mindsdb/integrations/handlers/snowflake_handler/snowflake_handler.py +2 -1
- mindsdb/integrations/handlers/statsforecast_handler/requirements.txt +1 -0
- mindsdb/integrations/handlers/statsforecast_handler/requirements_extra.txt +1 -0
- mindsdb/integrations/handlers/web_handler/urlcrawl_helpers.py +4 -4
- mindsdb/integrations/handlers/zendesk_handler/zendesk_tables.py +144 -111
- mindsdb/integrations/libs/api_handler.py +10 -10
- mindsdb/integrations/libs/base.py +4 -4
- mindsdb/integrations/libs/llm/utils.py +2 -2
- mindsdb/integrations/libs/ml_handler_process/create_engine_process.py +4 -7
- mindsdb/integrations/libs/ml_handler_process/func_call_process.py +2 -7
- mindsdb/integrations/libs/ml_handler_process/learn_process.py +37 -47
- mindsdb/integrations/libs/ml_handler_process/update_engine_process.py +4 -7
- mindsdb/integrations/libs/ml_handler_process/update_process.py +2 -7
- mindsdb/integrations/libs/process_cache.py +132 -140
- mindsdb/integrations/libs/response.py +18 -12
- mindsdb/integrations/libs/vectordatabase_handler.py +26 -0
- mindsdb/integrations/utilities/files/file_reader.py +6 -7
- 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/config_loader.py +37 -26
- mindsdb/integrations/utilities/rag/rerankers/base_reranker.py +83 -30
- mindsdb/integrations/utilities/rag/rerankers/reranker_compressor.py +4 -4
- mindsdb/integrations/utilities/rag/retrievers/sql_retriever.py +55 -133
- mindsdb/integrations/utilities/rag/settings.py +58 -133
- mindsdb/integrations/utilities/rag/splitters/file_splitter.py +5 -15
- mindsdb/interfaces/agents/agents_controller.py +2 -3
- mindsdb/interfaces/agents/constants.py +0 -2
- mindsdb/interfaces/agents/litellm_server.py +34 -58
- mindsdb/interfaces/agents/mcp_client_agent.py +10 -10
- mindsdb/interfaces/agents/mindsdb_database_agent.py +5 -5
- mindsdb/interfaces/agents/run_mcp_agent.py +12 -21
- mindsdb/interfaces/chatbot/chatbot_task.py +20 -23
- mindsdb/interfaces/chatbot/polling.py +30 -18
- mindsdb/interfaces/data_catalog/data_catalog_loader.py +16 -17
- 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 +7 -110
- mindsdb/interfaces/database/projects.py +2 -6
- mindsdb/interfaces/database/views.py +1 -4
- mindsdb/interfaces/file/file_controller.py +6 -6
- mindsdb/interfaces/functions/controller.py +1 -1
- mindsdb/interfaces/functions/to_markdown.py +2 -2
- mindsdb/interfaces/jobs/jobs_controller.py +5 -9
- mindsdb/interfaces/jobs/scheduler.py +3 -9
- mindsdb/interfaces/knowledge_base/controller.py +244 -128
- 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/knowledge_base/preprocessing/json_chunker.py +40 -61
- mindsdb/interfaces/model/model_controller.py +172 -168
- mindsdb/interfaces/query_context/context_controller.py +14 -2
- mindsdb/interfaces/skills/custom/text2sql/mindsdb_sql_toolkit.py +10 -14
- mindsdb/interfaces/skills/retrieval_tool.py +43 -50
- mindsdb/interfaces/skills/skill_tool.py +2 -2
- mindsdb/interfaces/skills/skills_controller.py +1 -4
- mindsdb/interfaces/skills/sql_agent.py +25 -19
- mindsdb/interfaces/storage/db.py +16 -6
- mindsdb/interfaces/storage/fs.py +114 -169
- mindsdb/interfaces/storage/json.py +19 -18
- mindsdb/interfaces/tabs/tabs_controller.py +49 -72
- mindsdb/interfaces/tasks/task_monitor.py +3 -9
- mindsdb/interfaces/tasks/task_thread.py +7 -9
- mindsdb/interfaces/triggers/trigger_task.py +7 -13
- mindsdb/interfaces/triggers/triggers_controller.py +47 -52
- mindsdb/migrations/migrate.py +16 -16
- mindsdb/utilities/api_status.py +58 -0
- mindsdb/utilities/config.py +68 -2
- mindsdb/utilities/exception.py +40 -1
- mindsdb/utilities/fs.py +0 -1
- mindsdb/utilities/hooks/profiling.py +17 -14
- mindsdb/utilities/json_encoder.py +24 -10
- mindsdb/utilities/langfuse.py +40 -45
- mindsdb/utilities/log.py +272 -0
- mindsdb/utilities/ml_task_queue/consumer.py +52 -58
- mindsdb/utilities/ml_task_queue/producer.py +26 -30
- mindsdb/utilities/render/sqlalchemy_render.py +22 -20
- mindsdb/utilities/starters.py +0 -10
- mindsdb/utilities/utils.py +2 -2
- {mindsdb-25.9.2.0a1.dist-info → mindsdb-25.10.0rc1.dist-info}/METADATA +293 -276
- {mindsdb-25.9.2.0a1.dist-info → mindsdb-25.10.0rc1.dist-info}/RECORD +144 -158
- mindsdb/api/mysql/mysql_proxy/utilities/exceptions.py +0 -14
- 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 -189
- 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 -253
- 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.2.0a1.dist-info → mindsdb-25.10.0rc1.dist-info}/WHEEL +0 -0
- {mindsdb-25.9.2.0a1.dist-info → mindsdb-25.10.0rc1.dist-info}/licenses/LICENSE +0 -0
- {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.
|
|
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
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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.
|
|
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
|
|
367
|
-
logger.
|
|
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
|
|
400
|
-
logger.
|
|
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,
|
mindsdb/api/a2a/__init__.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
77
|
-
logger.error(error_msg)
|
|
84
|
+
logger.exception("Error connecting to MindsDB:")
|
|
78
85
|
return {
|
|
79
|
-
"content":
|
|
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
|
-
|
|
84
|
-
logger.error(error_msg)
|
|
90
|
+
logger.exception("Error: ")
|
|
85
91
|
return {
|
|
86
|
-
"content":
|
|
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.
|
|
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.
|
|
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: {
|
|
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.
|
|
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.
|
|
141
|
-
data = json.dumps({"error": f"Serialization error: {
|
|
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
|
|
156
|
-
logger.
|
|
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
|
|
173
|
-
logger.
|
|
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"),
|
mindsdb/api/a2a/common/types.py
CHANGED
|
@@ -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
|
]
|
mindsdb/api/a2a/task_manager.py
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
|
|
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.
|
|
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: {
|
|
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.
|
|
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: {
|
|
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
|
-
|
|
186
|
-
logger.
|
|
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.
|
|
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: {
|
|
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.
|
|
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
|
+
)
|
mindsdb/api/common/middleware.py
CHANGED
|
@@ -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
|
|
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
|
|
104
|
-
logger.
|
|
105
|
-
logger.error(e)
|
|
106
|
-
logger.error(traceback.format_exc())
|
|
103
|
+
except Exception:
|
|
104
|
+
logger.exception(f"Check auth, user={username}: ERROR")
|