MindsDB 25.8.2.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 +5 -45
- mindsdb/api/a2a/__init__.py +52 -0
- mindsdb/api/a2a/agent.py +17 -28
- mindsdb/api/a2a/common/server/server.py +17 -36
- mindsdb/api/a2a/common/server/task_manager.py +14 -28
- mindsdb/api/a2a/common/types.py +3 -4
- mindsdb/api/a2a/task_manager.py +43 -55
- mindsdb/api/a2a/utils.py +63 -0
- mindsdb/api/common/middleware.py +106 -0
- mindsdb/api/http/initialize.py +13 -15
- mindsdb/api/http/namespaces/agents.py +6 -7
- 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 +17 -2
- mindsdb/interfaces/agents/langchain_agent.py +83 -18
- mindsdb/interfaces/knowledge_base/controller.py +3 -1
- mindsdb/interfaces/skills/custom/text2sql/mindsdb_sql_toolkit.py +7 -1
- mindsdb/interfaces/skills/skill_tool.py +7 -1
- mindsdb/interfaces/skills/sql_agent.py +6 -2
- mindsdb/utilities/config.py +3 -155
- mindsdb/utilities/fs.py +10 -4
- mindsdb/utilities/log.py +0 -25
- mindsdb/utilities/starters.py +0 -39
- {mindsdb-25.8.2.0.dist-info → mindsdb-25.9.1.0.dist-info}/METADATA +265 -263
- {mindsdb-25.8.2.0.dist-info → mindsdb-25.9.1.0.dist-info}/RECORD +54 -98
- 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.2.0.dist-info → mindsdb-25.9.1.0.dist-info}/WHEEL +0 -0
- {mindsdb-25.8.2.0.dist-info → mindsdb-25.9.1.0.dist-info}/licenses/LICENSE +0 -0
- {mindsdb-25.8.2.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
|
|
|
@@ -134,6 +127,9 @@ class TrunkProcessData:
|
|
|
134
127
|
|
|
135
128
|
def close_api_gracefully(trunc_processes_struct):
|
|
136
129
|
_stop_event.set()
|
|
130
|
+
|
|
131
|
+
delete_pid_file()
|
|
132
|
+
|
|
137
133
|
try:
|
|
138
134
|
for trunc_processes_data in trunc_processes_struct.values():
|
|
139
135
|
process = trunc_processes_data.process
|
|
@@ -345,9 +341,6 @@ if __name__ == "__main__":
|
|
|
345
341
|
config.raise_warnings(logger=logger)
|
|
346
342
|
os.environ["MINDSDB_RUNTIME"] = "1"
|
|
347
343
|
|
|
348
|
-
if os.environ.get("FLASK_SECRET_KEY") is None:
|
|
349
|
-
os.environ["FLASK_SECRET_KEY"] = secrets.token_hex(32)
|
|
350
|
-
|
|
351
344
|
if os.environ.get("ARROW_DEFAULT_MEMORY_POOL") is None:
|
|
352
345
|
try:
|
|
353
346
|
"""It seems like snowflake handler have memory issue that related to pyarrow. Memory usage keep growing with
|
|
@@ -382,7 +375,7 @@ if __name__ == "__main__":
|
|
|
382
375
|
apis = os.getenv("MINDSDB_APIS") or config.cmd_args.api
|
|
383
376
|
|
|
384
377
|
if apis is None: # If "--api" option is not specified, start the default APIs
|
|
385
|
-
api_arr = [TrunkProcessEnum.HTTP, TrunkProcessEnum.MYSQL
|
|
378
|
+
api_arr = [TrunkProcessEnum.HTTP, TrunkProcessEnum.MYSQL]
|
|
386
379
|
elif apis == "": # If "--api=" (blank) is specified, don't start any APIs
|
|
387
380
|
api_arr = []
|
|
388
381
|
else: # The user has provided a list of APIs to start
|
|
@@ -418,9 +411,7 @@ if __name__ == "__main__":
|
|
|
418
411
|
# Get config values for APIs
|
|
419
412
|
http_api_config = config.get("api", {}).get("http", {})
|
|
420
413
|
mysql_api_config = config.get("api", {}).get("mysql", {})
|
|
421
|
-
mcp_api_config = config.get("api", {}).get("mcp", {})
|
|
422
414
|
litellm_api_config = config.get("api", {}).get("litellm", {})
|
|
423
|
-
a2a_api_config = config.get("api", {}).get("a2a", {})
|
|
424
415
|
trunc_processes_struct = {
|
|
425
416
|
TrunkProcessEnum.HTTP: TrunkProcessData(
|
|
426
417
|
name=TrunkProcessEnum.HTTP.value,
|
|
@@ -444,12 +435,6 @@ if __name__ == "__main__":
|
|
|
444
435
|
"max_restart_interval_seconds", TrunkProcessData.max_restart_interval_seconds
|
|
445
436
|
),
|
|
446
437
|
),
|
|
447
|
-
TrunkProcessEnum.MONGODB: TrunkProcessData(
|
|
448
|
-
name=TrunkProcessEnum.MONGODB.value,
|
|
449
|
-
entrypoint=start_mongo,
|
|
450
|
-
port=config["api"]["mongodb"]["port"],
|
|
451
|
-
args=(config.cmd_args.verbose,),
|
|
452
|
-
),
|
|
453
438
|
TrunkProcessEnum.POSTGRES: TrunkProcessData(
|
|
454
439
|
name=TrunkProcessEnum.POSTGRES.value,
|
|
455
440
|
entrypoint=start_postgres,
|
|
@@ -465,18 +450,6 @@ if __name__ == "__main__":
|
|
|
465
450
|
TrunkProcessEnum.ML_TASK_QUEUE: TrunkProcessData(
|
|
466
451
|
name=TrunkProcessEnum.ML_TASK_QUEUE.value, entrypoint=start_ml_task_queue, args=(config.cmd_args.verbose,)
|
|
467
452
|
),
|
|
468
|
-
TrunkProcessEnum.MCP: TrunkProcessData(
|
|
469
|
-
name=TrunkProcessEnum.MCP.value,
|
|
470
|
-
entrypoint=start_mcp,
|
|
471
|
-
port=mcp_api_config.get("port", 47337),
|
|
472
|
-
args=(config.cmd_args.verbose,),
|
|
473
|
-
need_to_run=mcp_api_config.get("need_to_run", False),
|
|
474
|
-
restart_on_failure=mcp_api_config.get("restart_on_failure", False),
|
|
475
|
-
max_restart_count=mcp_api_config.get("max_restart_count", TrunkProcessData.max_restart_count),
|
|
476
|
-
max_restart_interval_seconds=mcp_api_config.get(
|
|
477
|
-
"max_restart_interval_seconds", TrunkProcessData.max_restart_interval_seconds
|
|
478
|
-
),
|
|
479
|
-
),
|
|
480
453
|
TrunkProcessEnum.LITELLM: TrunkProcessData(
|
|
481
454
|
name=TrunkProcessEnum.LITELLM.value,
|
|
482
455
|
entrypoint=start_litellm,
|
|
@@ -488,18 +461,6 @@ if __name__ == "__main__":
|
|
|
488
461
|
"max_restart_interval_seconds", TrunkProcessData.max_restart_interval_seconds
|
|
489
462
|
),
|
|
490
463
|
),
|
|
491
|
-
TrunkProcessEnum.A2A: TrunkProcessData(
|
|
492
|
-
name=TrunkProcessEnum.A2A.value,
|
|
493
|
-
entrypoint=start_a2a,
|
|
494
|
-
port=a2a_api_config.get("port", 8001),
|
|
495
|
-
args=(config.cmd_args.verbose,),
|
|
496
|
-
need_to_run=a2a_api_config.get("enabled", False),
|
|
497
|
-
restart_on_failure=a2a_api_config.get("restart_on_failure", True),
|
|
498
|
-
max_restart_count=a2a_api_config.get("max_restart_count", TrunkProcessData.max_restart_count),
|
|
499
|
-
max_restart_interval_seconds=a2a_api_config.get(
|
|
500
|
-
"max_restart_interval_seconds", TrunkProcessData.max_restart_interval_seconds
|
|
501
|
-
),
|
|
502
|
-
),
|
|
503
464
|
}
|
|
504
465
|
|
|
505
466
|
for api_enum in api_arr:
|
|
@@ -600,7 +561,6 @@ if __name__ == "__main__":
|
|
|
600
561
|
],
|
|
601
562
|
return_exceptions=False,
|
|
602
563
|
)
|
|
603
|
-
delete_pid_file()
|
|
604
564
|
|
|
605
565
|
ioloop = asyncio.new_event_loop()
|
|
606
566
|
ioloop.run_until_complete(wait_apis_start())
|
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
|
-
from mindsdb.api.a2a.utils import to_serializable
|
|
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,23 +116,12 @@ class MindsDBAgent:
|
|
|
116
116
|
) -> AsyncIterable[Dict[str, Any]]:
|
|
117
117
|
"""Stream responses from the MindsDB agent (uses streaming API endpoint)."""
|
|
118
118
|
try:
|
|
119
|
-
|
|
120
|
-
|
|
119
|
+
# Create A2A message structure with history and current query
|
|
120
|
+
a2a_message = {"role": "user", "parts": [{"text": query}]}
|
|
121
121
|
if history:
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
text = ""
|
|
126
|
-
for part in msg_dict.get("parts", []):
|
|
127
|
-
if part.get("type") == "text":
|
|
128
|
-
text = part.get("text", "")
|
|
129
|
-
break
|
|
130
|
-
if text:
|
|
131
|
-
if role == "user":
|
|
132
|
-
formatted_messages.append({"question": text, "answer": None})
|
|
133
|
-
elif role == "assistant" and formatted_messages:
|
|
134
|
-
formatted_messages[-1]["answer"] = text
|
|
135
|
-
formatted_messages.append({"question": query, "answer": None})
|
|
122
|
+
a2a_message["history"] = history
|
|
123
|
+
# Convert to Q&A format using centralized utility
|
|
124
|
+
formatted_messages = convert_a2a_message_to_qa_format(a2a_message)
|
|
136
125
|
logger.debug(f"Formatted messages for agent: {formatted_messages}")
|
|
137
126
|
streaming_response = self.streaming_invoke(formatted_messages, timeout=timeout)
|
|
138
127
|
async for chunk in streaming_response:
|
|
@@ -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/common/types.py
CHANGED
|
@@ -35,9 +35,7 @@ class FileContent(BaseModel):
|
|
|
35
35
|
if not (self.bytes or self.uri):
|
|
36
36
|
raise ValueError("Either 'bytes' or 'uri' must be present in the file data")
|
|
37
37
|
if self.bytes and self.uri:
|
|
38
|
-
raise ValueError(
|
|
39
|
-
"Only one of 'bytes' or 'uri' can be present in the file data"
|
|
40
|
-
)
|
|
38
|
+
raise ValueError("Only one of 'bytes' or 'uri' can be present in the file data")
|
|
41
39
|
return self
|
|
42
40
|
|
|
43
41
|
|
|
@@ -57,9 +55,10 @@ Part = Annotated[Union[TextPart, FilePart, DataPart], Field(discriminator="type"
|
|
|
57
55
|
|
|
58
56
|
|
|
59
57
|
class Message(BaseModel):
|
|
60
|
-
role: Literal["user", "agent"]
|
|
58
|
+
role: Literal["user", "agent", "assistant"]
|
|
61
59
|
parts: List[Part]
|
|
62
60
|
metadata: dict[str, Any] | None = None
|
|
61
|
+
history: Optional[List["Message"]] = None
|
|
63
62
|
|
|
64
63
|
|
|
65
64
|
class TaskStatus(BaseModel):
|