MindsDB 25.8.3.0__py3-none-any.whl → 25.9.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of MindsDB might be problematic. Click here for more details.
- mindsdb/__about__.py +1 -1
- mindsdb/__main__.py +2 -44
- mindsdb/api/a2a/__init__.py +52 -0
- mindsdb/api/a2a/agent.py +11 -12
- mindsdb/api/a2a/common/server/server.py +17 -36
- mindsdb/api/a2a/common/server/task_manager.py +14 -28
- mindsdb/api/a2a/task_manager.py +20 -21
- mindsdb/api/a2a/utils.py +1 -1
- mindsdb/api/common/middleware.py +106 -0
- mindsdb/api/http/initialize.py +13 -15
- mindsdb/api/http/namespaces/auth.py +6 -14
- mindsdb/api/http/namespaces/config.py +0 -2
- mindsdb/api/http/namespaces/default.py +74 -106
- mindsdb/api/http/start.py +25 -44
- mindsdb/api/litellm/start.py +11 -10
- mindsdb/api/mcp/__init__.py +165 -0
- mindsdb/api/mysql/mysql_proxy/mysql_proxy.py +33 -64
- mindsdb/api/postgres/postgres_proxy/postgres_proxy.py +86 -85
- mindsdb/integrations/handlers/crate_handler/crate_handler.py +3 -7
- mindsdb/integrations/handlers/derby_handler/derby_handler.py +32 -34
- mindsdb/integrations/handlers/documentdb_handler/requirements.txt +1 -0
- mindsdb/integrations/handlers/dummy_data_handler/dummy_data_handler.py +12 -13
- mindsdb/integrations/handlers/google_books_handler/google_books_handler.py +45 -44
- mindsdb/integrations/handlers/google_calendar_handler/google_calendar_handler.py +101 -95
- mindsdb/integrations/handlers/google_content_shopping_handler/google_content_shopping_handler.py +129 -129
- mindsdb/integrations/handlers/google_fit_handler/google_fit_handler.py +59 -43
- mindsdb/integrations/handlers/google_search_handler/google_search_handler.py +38 -39
- mindsdb/integrations/handlers/informix_handler/informix_handler.py +5 -18
- mindsdb/integrations/handlers/maxdb_handler/maxdb_handler.py +22 -28
- mindsdb/integrations/handlers/monetdb_handler/monetdb_handler.py +3 -7
- mindsdb/integrations/handlers/mongodb_handler/mongodb_handler.py +53 -67
- mindsdb/integrations/handlers/mongodb_handler/requirements.txt +1 -0
- mindsdb/{api/mongo/utilities → integrations/handlers/mongodb_handler/utils}/mongodb_ast.py +43 -68
- mindsdb/{api/mongo/utilities → integrations/handlers/mongodb_handler/utils}/mongodb_parser.py +17 -25
- mindsdb/{api/mongo/utilities → integrations/handlers/mongodb_handler/utils}/mongodb_query.py +10 -16
- mindsdb/integrations/handlers/mongodb_handler/utils/mongodb_render.py +43 -69
- mindsdb/integrations/libs/base.py +1 -1
- mindsdb/interfaces/agents/constants.py +1 -0
- mindsdb/interfaces/knowledge_base/controller.py +3 -1
- mindsdb/utilities/config.py +3 -155
- mindsdb/utilities/log.py +0 -25
- mindsdb/utilities/starters.py +0 -39
- {mindsdb-25.8.3.0.dist-info → mindsdb-25.9.1.0.dist-info}/METADATA +263 -261
- {mindsdb-25.8.3.0.dist-info → mindsdb-25.9.1.0.dist-info}/RECORD +47 -91
- mindsdb/api/a2a/__main__.py +0 -144
- mindsdb/api/a2a/run_a2a.py +0 -86
- mindsdb/api/common/check_auth.py +0 -42
- mindsdb/api/http/gunicorn_wrapper.py +0 -17
- mindsdb/api/mcp/start.py +0 -205
- mindsdb/api/mongo/__init__.py +0 -0
- mindsdb/api/mongo/classes/__init__.py +0 -5
- mindsdb/api/mongo/classes/query_sql.py +0 -19
- mindsdb/api/mongo/classes/responder.py +0 -45
- mindsdb/api/mongo/classes/responder_collection.py +0 -34
- mindsdb/api/mongo/classes/scram.py +0 -86
- mindsdb/api/mongo/classes/session.py +0 -23
- mindsdb/api/mongo/functions/__init__.py +0 -19
- mindsdb/api/mongo/responders/__init__.py +0 -73
- mindsdb/api/mongo/responders/add_shard.py +0 -13
- mindsdb/api/mongo/responders/aggregate.py +0 -90
- mindsdb/api/mongo/responders/buildinfo.py +0 -17
- mindsdb/api/mongo/responders/coll_stats.py +0 -63
- mindsdb/api/mongo/responders/company_id.py +0 -25
- mindsdb/api/mongo/responders/connection_status.py +0 -22
- mindsdb/api/mongo/responders/count.py +0 -21
- mindsdb/api/mongo/responders/db_stats.py +0 -32
- mindsdb/api/mongo/responders/delete.py +0 -105
- mindsdb/api/mongo/responders/describe.py +0 -23
- mindsdb/api/mongo/responders/end_sessions.py +0 -13
- mindsdb/api/mongo/responders/find.py +0 -175
- mindsdb/api/mongo/responders/get_cmd_line_opts.py +0 -18
- mindsdb/api/mongo/responders/get_free_monitoring_status.py +0 -14
- mindsdb/api/mongo/responders/get_parameter.py +0 -23
- mindsdb/api/mongo/responders/getlog.py +0 -14
- mindsdb/api/mongo/responders/host_info.py +0 -28
- mindsdb/api/mongo/responders/insert.py +0 -270
- mindsdb/api/mongo/responders/is_master.py +0 -20
- mindsdb/api/mongo/responders/is_master_lower.py +0 -13
- mindsdb/api/mongo/responders/list_collections.py +0 -55
- mindsdb/api/mongo/responders/list_databases.py +0 -37
- mindsdb/api/mongo/responders/list_indexes.py +0 -22
- mindsdb/api/mongo/responders/ping.py +0 -13
- mindsdb/api/mongo/responders/recv_chunk_start.py +0 -13
- mindsdb/api/mongo/responders/replsetgetstatus.py +0 -13
- mindsdb/api/mongo/responders/sasl_continue.py +0 -34
- mindsdb/api/mongo/responders/sasl_start.py +0 -33
- mindsdb/api/mongo/responders/update_range_deletions.py +0 -12
- mindsdb/api/mongo/responders/whatsmyuri.py +0 -18
- mindsdb/api/mongo/server.py +0 -388
- mindsdb/api/mongo/start.py +0 -15
- mindsdb/api/mongo/utilities/__init__.py +0 -0
- {mindsdb-25.8.3.0.dist-info → mindsdb-25.9.1.0.dist-info}/WHEEL +0 -0
- {mindsdb-25.8.3.0.dist-info → mindsdb-25.9.1.0.dist-info}/licenses/LICENSE +0 -0
- {mindsdb-25.8.3.0.dist-info → mindsdb-25.9.1.0.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.9.1.0"
|
|
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,7 +8,6 @@ import atexit
|
|
|
8
8
|
import signal
|
|
9
9
|
import psutil
|
|
10
10
|
import asyncio
|
|
11
|
-
import secrets
|
|
12
11
|
import traceback
|
|
13
12
|
import threading
|
|
14
13
|
from enum import Enum
|
|
@@ -28,14 +27,11 @@ from mindsdb.utilities.config import config
|
|
|
28
27
|
from mindsdb.utilities.starters import (
|
|
29
28
|
start_http,
|
|
30
29
|
start_mysql,
|
|
31
|
-
start_mongo,
|
|
32
30
|
start_postgres,
|
|
33
31
|
start_ml_task_queue,
|
|
34
32
|
start_scheduler,
|
|
35
33
|
start_tasks,
|
|
36
|
-
start_mcp,
|
|
37
34
|
start_litellm,
|
|
38
|
-
start_a2a,
|
|
39
35
|
)
|
|
40
36
|
from mindsdb.utilities.ps import is_pid_listen_port, get_child_pids
|
|
41
37
|
import mindsdb.interfaces.storage.db as db
|
|
@@ -61,18 +57,15 @@ _stop_event = threading.Event()
|
|
|
61
57
|
class TrunkProcessEnum(Enum):
|
|
62
58
|
HTTP = "http"
|
|
63
59
|
MYSQL = "mysql"
|
|
64
|
-
MONGODB = "mongodb"
|
|
65
60
|
POSTGRES = "postgres"
|
|
66
61
|
JOBS = "jobs"
|
|
67
62
|
TASKS = "tasks"
|
|
68
63
|
ML_TASK_QUEUE = "ml_task_queue"
|
|
69
|
-
MCP = "mcp"
|
|
70
64
|
LITELLM = "litellm"
|
|
71
|
-
A2A = "a2a"
|
|
72
65
|
|
|
73
66
|
@classmethod
|
|
74
67
|
def _missing_(cls, value):
|
|
75
|
-
|
|
68
|
+
logger.error(f'"{value}" is not a valid name of subprocess')
|
|
76
69
|
sys.exit(1)
|
|
77
70
|
|
|
78
71
|
|
|
@@ -348,9 +341,6 @@ if __name__ == "__main__":
|
|
|
348
341
|
config.raise_warnings(logger=logger)
|
|
349
342
|
os.environ["MINDSDB_RUNTIME"] = "1"
|
|
350
343
|
|
|
351
|
-
if os.environ.get("FLASK_SECRET_KEY") is None:
|
|
352
|
-
os.environ["FLASK_SECRET_KEY"] = secrets.token_hex(32)
|
|
353
|
-
|
|
354
344
|
if os.environ.get("ARROW_DEFAULT_MEMORY_POOL") is None:
|
|
355
345
|
try:
|
|
356
346
|
"""It seems like snowflake handler have memory issue that related to pyarrow. Memory usage keep growing with
|
|
@@ -385,7 +375,7 @@ if __name__ == "__main__":
|
|
|
385
375
|
apis = os.getenv("MINDSDB_APIS") or config.cmd_args.api
|
|
386
376
|
|
|
387
377
|
if apis is None: # If "--api" option is not specified, start the default APIs
|
|
388
|
-
api_arr = [TrunkProcessEnum.HTTP, TrunkProcessEnum.MYSQL
|
|
378
|
+
api_arr = [TrunkProcessEnum.HTTP, TrunkProcessEnum.MYSQL]
|
|
389
379
|
elif apis == "": # If "--api=" (blank) is specified, don't start any APIs
|
|
390
380
|
api_arr = []
|
|
391
381
|
else: # The user has provided a list of APIs to start
|
|
@@ -421,9 +411,7 @@ if __name__ == "__main__":
|
|
|
421
411
|
# Get config values for APIs
|
|
422
412
|
http_api_config = config.get("api", {}).get("http", {})
|
|
423
413
|
mysql_api_config = config.get("api", {}).get("mysql", {})
|
|
424
|
-
mcp_api_config = config.get("api", {}).get("mcp", {})
|
|
425
414
|
litellm_api_config = config.get("api", {}).get("litellm", {})
|
|
426
|
-
a2a_api_config = config.get("api", {}).get("a2a", {})
|
|
427
415
|
trunc_processes_struct = {
|
|
428
416
|
TrunkProcessEnum.HTTP: TrunkProcessData(
|
|
429
417
|
name=TrunkProcessEnum.HTTP.value,
|
|
@@ -447,12 +435,6 @@ if __name__ == "__main__":
|
|
|
447
435
|
"max_restart_interval_seconds", TrunkProcessData.max_restart_interval_seconds
|
|
448
436
|
),
|
|
449
437
|
),
|
|
450
|
-
TrunkProcessEnum.MONGODB: TrunkProcessData(
|
|
451
|
-
name=TrunkProcessEnum.MONGODB.value,
|
|
452
|
-
entrypoint=start_mongo,
|
|
453
|
-
port=config["api"]["mongodb"]["port"],
|
|
454
|
-
args=(config.cmd_args.verbose,),
|
|
455
|
-
),
|
|
456
438
|
TrunkProcessEnum.POSTGRES: TrunkProcessData(
|
|
457
439
|
name=TrunkProcessEnum.POSTGRES.value,
|
|
458
440
|
entrypoint=start_postgres,
|
|
@@ -468,18 +450,6 @@ if __name__ == "__main__":
|
|
|
468
450
|
TrunkProcessEnum.ML_TASK_QUEUE: TrunkProcessData(
|
|
469
451
|
name=TrunkProcessEnum.ML_TASK_QUEUE.value, entrypoint=start_ml_task_queue, args=(config.cmd_args.verbose,)
|
|
470
452
|
),
|
|
471
|
-
TrunkProcessEnum.MCP: TrunkProcessData(
|
|
472
|
-
name=TrunkProcessEnum.MCP.value,
|
|
473
|
-
entrypoint=start_mcp,
|
|
474
|
-
port=mcp_api_config.get("port", 47337),
|
|
475
|
-
args=(config.cmd_args.verbose,),
|
|
476
|
-
need_to_run=mcp_api_config.get("need_to_run", False),
|
|
477
|
-
restart_on_failure=mcp_api_config.get("restart_on_failure", False),
|
|
478
|
-
max_restart_count=mcp_api_config.get("max_restart_count", TrunkProcessData.max_restart_count),
|
|
479
|
-
max_restart_interval_seconds=mcp_api_config.get(
|
|
480
|
-
"max_restart_interval_seconds", TrunkProcessData.max_restart_interval_seconds
|
|
481
|
-
),
|
|
482
|
-
),
|
|
483
453
|
TrunkProcessEnum.LITELLM: TrunkProcessData(
|
|
484
454
|
name=TrunkProcessEnum.LITELLM.value,
|
|
485
455
|
entrypoint=start_litellm,
|
|
@@ -491,18 +461,6 @@ if __name__ == "__main__":
|
|
|
491
461
|
"max_restart_interval_seconds", TrunkProcessData.max_restart_interval_seconds
|
|
492
462
|
),
|
|
493
463
|
),
|
|
494
|
-
TrunkProcessEnum.A2A: TrunkProcessData(
|
|
495
|
-
name=TrunkProcessEnum.A2A.value,
|
|
496
|
-
entrypoint=start_a2a,
|
|
497
|
-
port=a2a_api_config.get("port", 8001),
|
|
498
|
-
args=(config.cmd_args.verbose,),
|
|
499
|
-
need_to_run=a2a_api_config.get("enabled", False),
|
|
500
|
-
restart_on_failure=a2a_api_config.get("restart_on_failure", True),
|
|
501
|
-
max_restart_count=a2a_api_config.get("max_restart_count", TrunkProcessData.max_restart_count),
|
|
502
|
-
max_restart_interval_seconds=a2a_api_config.get(
|
|
503
|
-
"max_restart_interval_seconds", TrunkProcessData.max_restart_interval_seconds
|
|
504
|
-
),
|
|
505
|
-
),
|
|
506
464
|
}
|
|
507
465
|
|
|
508
466
|
for api_enum in api_arr:
|
mindsdb/api/a2a/__init__.py
CHANGED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# A2A specific imports
|
|
2
|
+
from mindsdb.api.a2a.common.types import (
|
|
3
|
+
AgentCard,
|
|
4
|
+
AgentCapabilities,
|
|
5
|
+
AgentSkill,
|
|
6
|
+
)
|
|
7
|
+
from mindsdb.api.a2a.common.server.server import A2AServer
|
|
8
|
+
from mindsdb.api.a2a.task_manager import AgentTaskManager
|
|
9
|
+
from mindsdb.api.a2a.agent import MindsDBAgent
|
|
10
|
+
from mindsdb.utilities.config import config
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_a2a_app(
|
|
14
|
+
project_name: str = "mindsdb",
|
|
15
|
+
):
|
|
16
|
+
mindsdb_port = config.get("api", {}).get("http", {}).get("port", 47334)
|
|
17
|
+
|
|
18
|
+
# Prepare A2A artefacts (agent card & task-manager)
|
|
19
|
+
capabilities = AgentCapabilities(streaming=True)
|
|
20
|
+
skill = AgentSkill(
|
|
21
|
+
id="mindsdb_query",
|
|
22
|
+
name="MindsDB Query",
|
|
23
|
+
description="Executes natural-language queries via MindsDB agents.",
|
|
24
|
+
tags=["database", "mindsdb", "query", "analytics"],
|
|
25
|
+
examples=[
|
|
26
|
+
"What trends exist in my sales data?",
|
|
27
|
+
"Generate insights from the support tickets dataset.",
|
|
28
|
+
],
|
|
29
|
+
inputModes=MindsDBAgent.SUPPORTED_CONTENT_TYPES,
|
|
30
|
+
outputModes=MindsDBAgent.SUPPORTED_CONTENT_TYPES,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
agent_card = AgentCard(
|
|
34
|
+
name="MindsDB Agent Connector",
|
|
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}",
|
|
37
|
+
version="1.0.0",
|
|
38
|
+
defaultInputModes=MindsDBAgent.SUPPORTED_CONTENT_TYPES,
|
|
39
|
+
defaultOutputModes=MindsDBAgent.SUPPORTED_CONTENT_TYPES,
|
|
40
|
+
capabilities=capabilities,
|
|
41
|
+
skills=[skill],
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
task_manager = AgentTaskManager(
|
|
45
|
+
project_name=project_name,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
server = A2AServer(
|
|
49
|
+
agent_card=agent_card,
|
|
50
|
+
task_manager=task_manager,
|
|
51
|
+
)
|
|
52
|
+
return server.app
|
mindsdb/api/a2a/agent.py
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import json
|
|
2
2
|
from typing import Any, AsyncIterable, Dict, List
|
|
3
3
|
import requests
|
|
4
|
-
import logging
|
|
5
4
|
import httpx
|
|
6
5
|
from mindsdb.api.a2a.utils import to_serializable, convert_a2a_message_to_qa_format
|
|
7
6
|
from mindsdb.api.a2a.constants import DEFAULT_STREAM_TIMEOUT
|
|
7
|
+
from mindsdb.utilities import log
|
|
8
|
+
from mindsdb.utilities.config import config
|
|
8
9
|
|
|
9
|
-
logger =
|
|
10
|
+
logger = log.getLogger(__name__)
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
class MindsDBAgent:
|
|
@@ -18,16 +19,15 @@ class MindsDBAgent:
|
|
|
18
19
|
self,
|
|
19
20
|
agent_name="my_agent",
|
|
20
21
|
project_name="mindsdb",
|
|
21
|
-
|
|
22
|
-
port=47334,
|
|
22
|
+
user_info: Dict[str, Any] = None,
|
|
23
23
|
):
|
|
24
24
|
self.agent_name = agent_name
|
|
25
25
|
self.project_name = project_name
|
|
26
|
-
|
|
27
|
-
self.
|
|
28
|
-
self.base_url = f"http://{host}:{port}"
|
|
26
|
+
port = config.get("api", {}).get("http", {}).get("port", 47334)
|
|
27
|
+
self.base_url = f"http://localhost:{port}"
|
|
29
28
|
self.agent_url = f"{self.base_url}/api/projects/{project_name}/agents/{agent_name}"
|
|
30
29
|
self.sql_url = f"{self.base_url}/api/sql/query"
|
|
30
|
+
self.headers = {k: v for k, v in user_info.items() if v is not None} or {}
|
|
31
31
|
logger.info(f"Initialized MindsDB agent connector to {self.base_url}")
|
|
32
32
|
|
|
33
33
|
def invoke(self, query, session_id) -> Dict[str, Any]:
|
|
@@ -35,8 +35,8 @@ class MindsDBAgent:
|
|
|
35
35
|
try:
|
|
36
36
|
escaped_query = query.replace("'", "''")
|
|
37
37
|
sql_query = f"SELECT * FROM {self.project_name}.{self.agent_name} WHERE question = '{escaped_query}'"
|
|
38
|
-
logger.
|
|
39
|
-
response = requests.post(self.sql_url, json={"query": sql_query})
|
|
38
|
+
logger.debug(f"Sending SQL query to MindsDB: {sql_query[:100]}...")
|
|
39
|
+
response = requests.post(self.sql_url, json={"query": sql_query}, headers=self.headers)
|
|
40
40
|
response.raise_for_status()
|
|
41
41
|
data = response.json()
|
|
42
42
|
logger.debug(f"Received response from MindsDB: {json.dumps(data)[:200]}...")
|
|
@@ -89,8 +89,8 @@ class MindsDBAgent:
|
|
|
89
89
|
|
|
90
90
|
async def streaming_invoke(self, messages, timeout=DEFAULT_STREAM_TIMEOUT):
|
|
91
91
|
url = f"{self.base_url}/api/projects/{self.project_name}/agents/{self.agent_name}/completions/stream"
|
|
92
|
-
logger.
|
|
93
|
-
async with httpx.AsyncClient(timeout=timeout) as client:
|
|
92
|
+
logger.debug(f"Sending streaming request to MindsDB agent: {self.agent_name}")
|
|
93
|
+
async with httpx.AsyncClient(timeout=timeout, headers=self.headers) as client:
|
|
94
94
|
async with client.stream("POST", url, json={"messages": to_serializable(messages)}) as response:
|
|
95
95
|
response.raise_for_status()
|
|
96
96
|
async for line in response.aiter_lines():
|
|
@@ -116,7 +116,6 @@ class MindsDBAgent:
|
|
|
116
116
|
) -> AsyncIterable[Dict[str, Any]]:
|
|
117
117
|
"""Stream responses from the MindsDB agent (uses streaming API endpoint)."""
|
|
118
118
|
try:
|
|
119
|
-
logger.info(f"Using streaming API for query: {query[:100]}...")
|
|
120
119
|
# Create A2A message structure with history and current query
|
|
121
120
|
a2a_message = {"role": "user", "parts": [{"text": query}]}
|
|
122
121
|
if history:
|
|
@@ -7,6 +7,7 @@ from starlette.middleware.cors import CORSMiddleware
|
|
|
7
7
|
from starlette.responses import JSONResponse
|
|
8
8
|
from sse_starlette.sse import EventSourceResponse
|
|
9
9
|
from starlette.requests import Request
|
|
10
|
+
from starlette.routing import Route
|
|
10
11
|
from ...common.types import (
|
|
11
12
|
A2ARequest,
|
|
12
13
|
JSONRPCResponse,
|
|
@@ -26,7 +27,6 @@ from pydantic import ValidationError
|
|
|
26
27
|
from ...common.server.task_manager import TaskManager
|
|
27
28
|
|
|
28
29
|
from mindsdb.utilities import log
|
|
29
|
-
from mindsdb.utilities.log import get_uvicorn_logging_config, get_mindsdb_log_level
|
|
30
30
|
|
|
31
31
|
logger = log.getLogger(__name__)
|
|
32
32
|
|
|
@@ -34,22 +34,18 @@ logger = log.getLogger(__name__)
|
|
|
34
34
|
class A2AServer:
|
|
35
35
|
def __init__(
|
|
36
36
|
self,
|
|
37
|
-
host="0.0.0.0",
|
|
38
|
-
port=5000,
|
|
39
|
-
endpoint="/",
|
|
40
37
|
agent_card: AgentCard = None,
|
|
41
38
|
task_manager: TaskManager = None,
|
|
42
39
|
):
|
|
43
|
-
self.host = host
|
|
44
|
-
self.port = port
|
|
45
|
-
self.endpoint = endpoint
|
|
46
40
|
self.task_manager = task_manager
|
|
47
41
|
self.agent_card = agent_card
|
|
48
|
-
self.app = Starlette(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
42
|
+
self.app = Starlette(
|
|
43
|
+
routes=[
|
|
44
|
+
Route("/", self._process_request, methods=["POST"]),
|
|
45
|
+
Route("/.well-known/agent.json", self._get_agent_card, methods=["GET"]),
|
|
46
|
+
Route("/status", self._get_status, methods=["GET"]),
|
|
47
|
+
]
|
|
48
|
+
)
|
|
53
49
|
# TODO: Remove this when we have a proper CORS policy
|
|
54
50
|
self.app.add_middleware(
|
|
55
51
|
CORSMiddleware,
|
|
@@ -60,26 +56,6 @@ class A2AServer:
|
|
|
60
56
|
)
|
|
61
57
|
self.start_time = time.time()
|
|
62
58
|
|
|
63
|
-
def start(self):
|
|
64
|
-
if self.agent_card is None:
|
|
65
|
-
raise ValueError("agent_card is not defined")
|
|
66
|
-
|
|
67
|
-
if self.task_manager is None:
|
|
68
|
-
raise ValueError("request_handler is not defined")
|
|
69
|
-
|
|
70
|
-
import uvicorn
|
|
71
|
-
|
|
72
|
-
# Configure uvicorn with optimized settings for streaming
|
|
73
|
-
uvicorn.run(
|
|
74
|
-
self.app,
|
|
75
|
-
host=self.host,
|
|
76
|
-
port=self.port,
|
|
77
|
-
http="h11",
|
|
78
|
-
timeout_keep_alive=65,
|
|
79
|
-
log_level=get_mindsdb_log_level(),
|
|
80
|
-
log_config=get_uvicorn_logging_config("uvicorn_a2a"),
|
|
81
|
-
)
|
|
82
|
-
|
|
83
59
|
def _get_agent_card(self, request: Request) -> JSONResponse:
|
|
84
60
|
return JSONResponse(self.agent_card.model_dump(exclude_none=True))
|
|
85
61
|
|
|
@@ -94,8 +70,6 @@ class A2AServer:
|
|
|
94
70
|
"status": "ok",
|
|
95
71
|
"service": "mindsdb-a2a",
|
|
96
72
|
"uptime_seconds": round(uptime_seconds, 2),
|
|
97
|
-
"host": self.host,
|
|
98
|
-
"port": self.port,
|
|
99
73
|
"agent_name": self.agent_card.name if self.agent_card else None,
|
|
100
74
|
"version": self.agent_card.version if self.agent_card else "unknown",
|
|
101
75
|
}
|
|
@@ -107,13 +81,20 @@ class A2AServer:
|
|
|
107
81
|
body = await request.json()
|
|
108
82
|
json_rpc_request = A2ARequest.validate_python(body)
|
|
109
83
|
|
|
84
|
+
user_info = {
|
|
85
|
+
"user-id": request.headers.get("user-id", None),
|
|
86
|
+
"company-id": request.headers.get("company-id", None),
|
|
87
|
+
"user-class": request.headers.get("user-class", None),
|
|
88
|
+
"authorization": request.headers.get("Authorization", None),
|
|
89
|
+
}
|
|
90
|
+
|
|
110
91
|
if isinstance(json_rpc_request, GetTaskRequest):
|
|
111
92
|
result = await self.task_manager.on_get_task(json_rpc_request)
|
|
112
93
|
elif isinstance(json_rpc_request, SendTaskRequest):
|
|
113
|
-
result = await self.task_manager.on_send_task(json_rpc_request)
|
|
94
|
+
result = await self.task_manager.on_send_task(json_rpc_request, user_info)
|
|
114
95
|
elif isinstance(json_rpc_request, SendTaskStreamingRequest):
|
|
115
96
|
# Don't await the async generator, just pass it to _create_response
|
|
116
|
-
result = self.task_manager.on_send_task_subscribe(json_rpc_request)
|
|
97
|
+
result = self.task_manager.on_send_task_subscribe(json_rpc_request, user_info)
|
|
117
98
|
elif isinstance(json_rpc_request, CancelTaskRequest):
|
|
118
99
|
result = await self.task_manager.on_cancel_task(json_rpc_request)
|
|
119
100
|
elif isinstance(json_rpc_request, SetTaskPushNotificationRequest):
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
|
-
from typing import Union, AsyncIterable, List
|
|
2
|
+
from typing import Union, AsyncIterable, List, Dict
|
|
3
3
|
from ...common.types import Task
|
|
4
4
|
from ...common.types import (
|
|
5
5
|
JSONRPCResponse,
|
|
@@ -31,10 +31,10 @@ from ...common.types import (
|
|
|
31
31
|
InternalError,
|
|
32
32
|
)
|
|
33
33
|
from ...common.server.utils import new_not_implemented_error
|
|
34
|
+
from mindsdb.utilities import log
|
|
34
35
|
import asyncio
|
|
35
|
-
import logging
|
|
36
36
|
|
|
37
|
-
logger =
|
|
37
|
+
logger = log.getLogger(__name__)
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
class TaskManager(ABC):
|
|
@@ -47,12 +47,12 @@ class TaskManager(ABC):
|
|
|
47
47
|
pass
|
|
48
48
|
|
|
49
49
|
@abstractmethod
|
|
50
|
-
async def on_send_task(self, request: SendTaskRequest) -> SendTaskResponse:
|
|
50
|
+
async def on_send_task(self, request: SendTaskRequest, user_info: Dict) -> SendTaskResponse:
|
|
51
51
|
pass
|
|
52
52
|
|
|
53
53
|
@abstractmethod
|
|
54
54
|
async def on_send_task_subscribe(
|
|
55
|
-
self, request: SendTaskStreamingRequest
|
|
55
|
+
self, request: SendTaskStreamingRequest, user_info: Dict
|
|
56
56
|
) -> Union[AsyncIterable[SendTaskStreamingResponse], JSONRPCResponse]:
|
|
57
57
|
pass
|
|
58
58
|
|
|
@@ -92,9 +92,7 @@ class InMemoryTaskManager(TaskManager):
|
|
|
92
92
|
if task is None:
|
|
93
93
|
return GetTaskResponse(id=request.id, error=TaskNotFoundError())
|
|
94
94
|
|
|
95
|
-
task_result = self.append_task_history(
|
|
96
|
-
task, task_query_params.historyLength
|
|
97
|
-
)
|
|
95
|
+
task_result = self.append_task_history(task, task_query_params.historyLength)
|
|
98
96
|
|
|
99
97
|
return GetTaskResponse(id=request.id, result=task_result)
|
|
100
98
|
|
|
@@ -110,18 +108,16 @@ class InMemoryTaskManager(TaskManager):
|
|
|
110
108
|
return CancelTaskResponse(id=request.id, error=TaskNotCancelableError())
|
|
111
109
|
|
|
112
110
|
@abstractmethod
|
|
113
|
-
async def on_send_task(self, request: SendTaskRequest) -> SendTaskResponse:
|
|
111
|
+
async def on_send_task(self, request: SendTaskRequest, user_info: Dict) -> SendTaskResponse:
|
|
114
112
|
pass
|
|
115
113
|
|
|
116
114
|
@abstractmethod
|
|
117
115
|
async def on_send_task_subscribe(
|
|
118
|
-
self, request: SendTaskStreamingRequest
|
|
116
|
+
self, request: SendTaskStreamingRequest, user_info: Dict
|
|
119
117
|
) -> Union[AsyncIterable[SendTaskStreamingResponse], JSONRPCResponse]:
|
|
120
118
|
pass
|
|
121
119
|
|
|
122
|
-
async def set_push_notification_info(
|
|
123
|
-
self, task_id: str, notification_config: PushNotificationConfig
|
|
124
|
-
):
|
|
120
|
+
async def set_push_notification_info(self, task_id: str, notification_config: PushNotificationConfig):
|
|
125
121
|
async with self.lock:
|
|
126
122
|
task = self.tasks.get(task_id)
|
|
127
123
|
if task is None:
|
|
@@ -160,14 +156,10 @@ class InMemoryTaskManager(TaskManager):
|
|
|
160
156
|
logger.error(f"Error while setting push notification info: {e}")
|
|
161
157
|
return JSONRPCResponse(
|
|
162
158
|
id=request.id,
|
|
163
|
-
error=InternalError(
|
|
164
|
-
message="An error occurred while setting push notification info"
|
|
165
|
-
),
|
|
159
|
+
error=InternalError(message="An error occurred while setting push notification info"),
|
|
166
160
|
)
|
|
167
161
|
|
|
168
|
-
return SetTaskPushNotificationResponse(
|
|
169
|
-
id=request.id, result=task_notification_params
|
|
170
|
-
)
|
|
162
|
+
return SetTaskPushNotificationResponse(id=request.id, result=task_notification_params)
|
|
171
163
|
|
|
172
164
|
async def on_get_task_push_notification(
|
|
173
165
|
self, request: GetTaskPushNotificationRequest
|
|
@@ -181,16 +173,12 @@ class InMemoryTaskManager(TaskManager):
|
|
|
181
173
|
logger.error(f"Error while getting push notification info: {e}")
|
|
182
174
|
return GetTaskPushNotificationResponse(
|
|
183
175
|
id=request.id,
|
|
184
|
-
error=InternalError(
|
|
185
|
-
message="An error occurred while getting push notification info"
|
|
186
|
-
),
|
|
176
|
+
error=InternalError(message="An error occurred while getting push notification info"),
|
|
187
177
|
)
|
|
188
178
|
|
|
189
179
|
return GetTaskPushNotificationResponse(
|
|
190
180
|
id=request.id,
|
|
191
|
-
result=TaskPushNotificationConfig(
|
|
192
|
-
id=task_params.id, pushNotificationConfig=notification_info
|
|
193
|
-
),
|
|
181
|
+
result=TaskPushNotificationConfig(id=task_params.id, pushNotificationConfig=notification_info),
|
|
194
182
|
)
|
|
195
183
|
|
|
196
184
|
async def upsert_task(self, task_send_params: TaskSendParams) -> Task:
|
|
@@ -216,9 +204,7 @@ class InMemoryTaskManager(TaskManager):
|
|
|
216
204
|
) -> Union[AsyncIterable[SendTaskStreamingResponse], JSONRPCResponse]:
|
|
217
205
|
return new_not_implemented_error(request.id)
|
|
218
206
|
|
|
219
|
-
async def update_store(
|
|
220
|
-
self, task_id: str, status: TaskStatus, artifacts: list[Artifact]
|
|
221
|
-
) -> Task:
|
|
207
|
+
async def update_store(self, task_id: str, status: TaskStatus, artifacts: list[Artifact]) -> Task:
|
|
222
208
|
async with self.lock:
|
|
223
209
|
try:
|
|
224
210
|
task = self.tasks[task_id]
|
mindsdb/api/a2a/task_manager.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import AsyncIterable
|
|
1
|
+
from typing import AsyncIterable, Dict
|
|
2
2
|
from mindsdb.api.a2a.common.types import (
|
|
3
3
|
SendTaskRequest,
|
|
4
4
|
TaskSendParams,
|
|
@@ -24,6 +24,7 @@ from typing import Union
|
|
|
24
24
|
import logging
|
|
25
25
|
import asyncio
|
|
26
26
|
import time
|
|
27
|
+
import traceback
|
|
27
28
|
|
|
28
29
|
logger = logging.getLogger(__name__)
|
|
29
30
|
|
|
@@ -46,19 +47,15 @@ class AgentTaskManager(InMemoryTaskManager):
|
|
|
46
47
|
def __init__(
|
|
47
48
|
self,
|
|
48
49
|
project_name: str,
|
|
49
|
-
mindsdb_host: str,
|
|
50
|
-
mindsdb_port: int,
|
|
51
50
|
agent_name: str = None,
|
|
52
51
|
):
|
|
53
52
|
super().__init__()
|
|
54
53
|
self.project_name = project_name
|
|
55
|
-
self.mindsdb_host = mindsdb_host
|
|
56
|
-
self.mindsdb_port = mindsdb_port
|
|
57
54
|
self.agent_name = agent_name
|
|
58
55
|
self.tasks = {} # Task storage
|
|
59
56
|
self.lock = asyncio.Lock() # Lock for task operations
|
|
60
57
|
|
|
61
|
-
def _create_agent(self, agent_name: str = None) -> MindsDBAgent:
|
|
58
|
+
def _create_agent(self, user_info: Dict, agent_name: str = None) -> MindsDBAgent:
|
|
62
59
|
"""Create a new MindsDBAgent instance for the given agent name."""
|
|
63
60
|
if not agent_name:
|
|
64
61
|
raise ValueError("Agent name is required but was not provided in the request")
|
|
@@ -66,11 +63,12 @@ class AgentTaskManager(InMemoryTaskManager):
|
|
|
66
63
|
return MindsDBAgent(
|
|
67
64
|
agent_name=agent_name,
|
|
68
65
|
project_name=self.project_name,
|
|
69
|
-
|
|
70
|
-
port=self.mindsdb_port,
|
|
66
|
+
user_info=user_info,
|
|
71
67
|
)
|
|
72
68
|
|
|
73
|
-
async def _stream_generator(
|
|
69
|
+
async def _stream_generator(
|
|
70
|
+
self, request: SendTaskStreamingRequest, user_info: Dict
|
|
71
|
+
) -> AsyncIterable[SendTaskStreamingResponse]:
|
|
74
72
|
task_send_params: TaskSendParams = request.params
|
|
75
73
|
query = self._get_user_query(task_send_params)
|
|
76
74
|
params = self._get_task_params(task_send_params)
|
|
@@ -92,7 +90,7 @@ class AgentTaskManager(InMemoryTaskManager):
|
|
|
92
90
|
yield error_result
|
|
93
91
|
return # Early return from generator
|
|
94
92
|
|
|
95
|
-
agent = self._create_agent(agent_name)
|
|
93
|
+
agent = self._create_agent(user_info, agent_name)
|
|
96
94
|
|
|
97
95
|
# Get the history from the task object (where it was properly extracted and stored)
|
|
98
96
|
history = task.history if task and task.history else []
|
|
@@ -168,16 +166,16 @@ class AgentTaskManager(InMemoryTaskManager):
|
|
|
168
166
|
|
|
169
167
|
# If streaming is enabled (default), use the streaming implementation
|
|
170
168
|
try:
|
|
171
|
-
logger.debug(f"
|
|
169
|
+
logger.debug(f"Entering agent.stream() at {time.time()}")
|
|
172
170
|
# Create A2A message structure and convert using centralized utility
|
|
173
171
|
a2a_message = task_send_params.message.model_dump()
|
|
172
|
+
logger.debug(f"History: {history}")
|
|
174
173
|
if history:
|
|
175
174
|
a2a_message["history"] = [msg.model_dump() if hasattr(msg, "model_dump") else msg for msg in history]
|
|
176
175
|
|
|
177
176
|
# Convert to Q&A format using centralized utility function
|
|
178
177
|
all_messages = convert_a2a_message_to_qa_format(a2a_message)
|
|
179
178
|
|
|
180
|
-
logger.debug(f"Sending {len(all_messages)} total messages to streaming agent")
|
|
181
179
|
async for item in agent.streaming_invoke(all_messages, timeout=60):
|
|
182
180
|
# Clean up: Remove verbose debug logs, keep only errors and essential info
|
|
183
181
|
if isinstance(item, dict) and "artifact" in item and "parts" in item["artifact"]:
|
|
@@ -185,6 +183,7 @@ class AgentTaskManager(InMemoryTaskManager):
|
|
|
185
183
|
yield to_serializable(item)
|
|
186
184
|
except Exception as e:
|
|
187
185
|
logger.error(f"An error occurred while streaming the response: {e}")
|
|
186
|
+
logger.error(traceback.format_exc())
|
|
188
187
|
error_text = f"An error occurred while streaming the response: {str(e)}"
|
|
189
188
|
# Ensure all parts are plain dicts
|
|
190
189
|
parts = [{"type": "text", "text": error_text}]
|
|
@@ -310,27 +309,27 @@ class AgentTaskManager(InMemoryTaskManager):
|
|
|
310
309
|
|
|
311
310
|
return None
|
|
312
311
|
|
|
313
|
-
async def on_send_task(self, request: SendTaskRequest) -> SendTaskResponse:
|
|
312
|
+
async def on_send_task(self, request: SendTaskRequest, user_info: Dict) -> SendTaskResponse:
|
|
314
313
|
error = self._validate_request(request)
|
|
315
314
|
if error:
|
|
316
315
|
return error
|
|
317
316
|
|
|
318
|
-
return await self._invoke(request)
|
|
317
|
+
return await self._invoke(request, user_info=user_info)
|
|
319
318
|
|
|
320
319
|
async def on_send_task_subscribe(
|
|
321
|
-
self, request: SendTaskStreamingRequest
|
|
320
|
+
self, request: SendTaskStreamingRequest, user_info: Dict
|
|
322
321
|
) -> AsyncIterable[SendTaskStreamingResponse]:
|
|
323
322
|
error = self._validate_request(request)
|
|
324
323
|
if error:
|
|
325
|
-
logger.info(f"
|
|
324
|
+
logger.info(f"Yielding error at {time.time()} for invalid request: {error}")
|
|
326
325
|
yield to_serializable(SendTaskStreamingResponse(id=request.id, error=to_serializable(error.error)))
|
|
327
326
|
return
|
|
328
327
|
|
|
329
328
|
# We can't await an async generator directly, so we need to use it as is
|
|
330
329
|
try:
|
|
331
|
-
logger.debug(f"
|
|
332
|
-
async for response in self._stream_generator(request):
|
|
333
|
-
logger.debug(f"
|
|
330
|
+
logger.debug(f"Entering streaming path at {time.time()}")
|
|
331
|
+
async for response in self._stream_generator(request, user_info):
|
|
332
|
+
logger.debug(f"Yielding streaming response at {time.time()} with: {str(response)[:120]}")
|
|
334
333
|
yield response
|
|
335
334
|
except Exception as e:
|
|
336
335
|
# If an error occurs, yield an error response
|
|
@@ -409,13 +408,13 @@ class AgentTaskManager(InMemoryTaskManager):
|
|
|
409
408
|
"session_id": task_send_params.sessionId,
|
|
410
409
|
}
|
|
411
410
|
|
|
412
|
-
async def _invoke(self, request: SendTaskRequest) -> SendTaskResponse:
|
|
411
|
+
async def _invoke(self, request: SendTaskRequest, user_info: Dict) -> SendTaskResponse:
|
|
413
412
|
task_send_params: TaskSendParams = request.params
|
|
414
413
|
query = self._get_user_query(task_send_params)
|
|
415
414
|
params = self._get_task_params(task_send_params)
|
|
416
415
|
agent_name = params["agent_name"]
|
|
417
416
|
streaming = params["streaming"]
|
|
418
|
-
agent = self._create_agent(agent_name)
|
|
417
|
+
agent = self._create_agent(user_info, agent_name)
|
|
419
418
|
|
|
420
419
|
try:
|
|
421
420
|
# Get the history from the task
|
mindsdb/api/a2a/utils.py
CHANGED
|
@@ -42,7 +42,7 @@ def convert_a2a_message_to_qa_format(a2a_message: Dict) -> List[Dict[str, str]]:
|
|
|
42
42
|
converted_messages = []
|
|
43
43
|
|
|
44
44
|
# Process conversation history first
|
|
45
|
-
if "history" in a2a_message:
|
|
45
|
+
if "history" in a2a_message and a2a_message["history"] is not None:
|
|
46
46
|
for hist_msg in a2a_message["history"]:
|
|
47
47
|
if hist_msg.get("role") == "user":
|
|
48
48
|
# Extract text from parts
|