MindsDB 25.4.5.0__py3-none-any.whl → 25.5.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of MindsDB might be problematic. Click here for more details.
- mindsdb/__about__.py +1 -1
- mindsdb/__main__.py +215 -185
- mindsdb/api/a2a/__init__.py +0 -0
- mindsdb/api/a2a/__main__.py +114 -0
- mindsdb/api/a2a/a2a_client.py +439 -0
- mindsdb/api/a2a/agent.py +308 -0
- mindsdb/api/a2a/common/__init__.py +0 -0
- mindsdb/api/a2a/common/client/__init__.py +4 -0
- mindsdb/api/a2a/common/client/card_resolver.py +21 -0
- mindsdb/api/a2a/common/client/client.py +86 -0
- mindsdb/api/a2a/common/server/__init__.py +4 -0
- mindsdb/api/a2a/common/server/server.py +164 -0
- mindsdb/api/a2a/common/server/task_manager.py +287 -0
- mindsdb/api/a2a/common/server/utils.py +28 -0
- mindsdb/api/a2a/common/types.py +365 -0
- mindsdb/api/a2a/constants.py +9 -0
- mindsdb/api/a2a/run_a2a.py +129 -0
- mindsdb/api/a2a/task_manager.py +594 -0
- mindsdb/api/executor/command_executor.py +49 -28
- mindsdb/api/executor/datahub/classes/response.py +5 -2
- mindsdb/api/executor/datahub/datanodes/information_schema_datanode.py +8 -0
- mindsdb/api/executor/datahub/datanodes/integration_datanode.py +39 -72
- mindsdb/api/executor/datahub/datanodes/system_tables.py +10 -13
- mindsdb/api/executor/planner/query_planner.py +14 -2
- mindsdb/api/executor/sql_query/result_set.py +185 -52
- mindsdb/api/executor/sql_query/sql_query.py +1 -1
- mindsdb/api/executor/sql_query/steps/apply_predictor_step.py +11 -13
- mindsdb/api/executor/sql_query/steps/fetch_dataframe.py +8 -10
- mindsdb/api/executor/sql_query/steps/fetch_dataframe_partition.py +5 -44
- mindsdb/api/executor/sql_query/steps/insert_step.py +24 -15
- mindsdb/api/executor/sql_query/steps/join_step.py +1 -1
- mindsdb/api/executor/sql_query/steps/project_step.py +1 -1
- mindsdb/api/executor/sql_query/steps/sql_steps.py +1 -1
- mindsdb/api/executor/sql_query/steps/subselect_step.py +4 -8
- mindsdb/api/executor/sql_query/steps/union_step.py +1 -3
- mindsdb/api/http/initialize.py +118 -85
- mindsdb/api/http/namespaces/analysis.py +17 -4
- mindsdb/api/http/namespaces/file.py +8 -2
- mindsdb/api/http/namespaces/sql.py +13 -27
- mindsdb/api/http/namespaces/tree.py +1 -1
- mindsdb/api/http/start.py +7 -2
- mindsdb/api/mcp/start.py +42 -5
- mindsdb/api/mysql/mysql_proxy/data_types/mysql_packet.py +0 -1
- mindsdb/api/mysql/mysql_proxy/data_types/mysql_packets/binary_resultset_row_package.py +52 -19
- mindsdb/api/mysql/mysql_proxy/executor/mysql_executor.py +8 -10
- mindsdb/api/mysql/mysql_proxy/libs/constants/mysql.py +54 -38
- mindsdb/api/mysql/mysql_proxy/mysql_proxy.py +86 -123
- mindsdb/api/mysql/mysql_proxy/utilities/dump.py +351 -0
- mindsdb/api/mysql/mysql_proxy/utilities/exceptions.py +0 -4
- mindsdb/api/postgres/postgres_proxy/executor/executor.py +1 -1
- mindsdb/api/postgres/postgres_proxy/postgres_packets/postgres_message_formats.py +2 -2
- mindsdb/api/postgres/postgres_proxy/postgres_proxy.py +5 -6
- mindsdb/integrations/handlers/altibase_handler/altibase_handler.py +26 -27
- mindsdb/integrations/handlers/altibase_handler/connection_args.py +13 -13
- mindsdb/integrations/handlers/altibase_handler/tests/test_altibase_handler.py +8 -8
- mindsdb/integrations/handlers/altibase_handler/tests/test_altibase_handler_dsn.py +13 -13
- mindsdb/integrations/handlers/anthropic_handler/__init__.py +2 -2
- mindsdb/integrations/handlers/anthropic_handler/anthropic_handler.py +1 -3
- mindsdb/integrations/handlers/aurora_handler/aurora_handler.py +1 -0
- mindsdb/integrations/handlers/autosklearn_handler/autosklearn_handler.py +1 -1
- mindsdb/integrations/handlers/autosklearn_handler/config.py +0 -1
- mindsdb/integrations/handlers/bigquery_handler/bigquery_handler.py +1 -1
- mindsdb/integrations/handlers/bigquery_handler/requirements.txt +1 -0
- mindsdb/integrations/handlers/bigquery_handler/tests/test_bigquery_handler.py +1 -1
- mindsdb/integrations/handlers/binance_handler/binance_handler.py +1 -0
- mindsdb/integrations/handlers/binance_handler/binance_tables.py +3 -4
- mindsdb/integrations/handlers/byom_handler/__init__.py +0 -1
- mindsdb/integrations/handlers/chromadb_handler/requirements.txt +1 -0
- mindsdb/integrations/handlers/ckan_handler/ckan_handler.py +3 -0
- mindsdb/integrations/handlers/clickhouse_handler/__init__.py +1 -1
- mindsdb/integrations/handlers/cloud_spanner_handler/tests/test_cloud_spanner_handler.py +0 -2
- mindsdb/integrations/handlers/cloud_sql_handler/cloud_sql_handler.py +0 -1
- mindsdb/integrations/handlers/cohere_handler/__init__.py +1 -1
- mindsdb/integrations/handlers/cohere_handler/cohere_handler.py +11 -13
- mindsdb/integrations/handlers/confluence_handler/confluence_tables.py +6 -0
- mindsdb/integrations/handlers/databend_handler/connection_args.py +1 -1
- mindsdb/integrations/handlers/databend_handler/databend_handler.py +4 -4
- mindsdb/integrations/handlers/databend_handler/tests/__init__.py +0 -1
- mindsdb/integrations/handlers/databend_handler/tests/test_databend_handler.py +1 -1
- mindsdb/integrations/handlers/derby_handler/connection_args.py +1 -1
- mindsdb/integrations/handlers/derby_handler/derby_handler.py +14 -22
- mindsdb/integrations/handlers/derby_handler/tests/test_derby_handler.py +6 -6
- mindsdb/integrations/handlers/discord_handler/discord_handler.py +5 -5
- mindsdb/integrations/handlers/discord_handler/discord_tables.py +3 -3
- mindsdb/integrations/handlers/discord_handler/tests/test_discord.py +5 -3
- mindsdb/integrations/handlers/dockerhub_handler/dockerhub.py +3 -3
- mindsdb/integrations/handlers/dockerhub_handler/dockerhub_handler.py +2 -2
- mindsdb/integrations/handlers/dockerhub_handler/dockerhub_tables.py +57 -54
- mindsdb/integrations/handlers/dremio_handler/__init__.py +2 -2
- mindsdb/integrations/handlers/druid_handler/__init__.py +1 -1
- mindsdb/integrations/handlers/druid_handler/druid_handler.py +2 -2
- mindsdb/integrations/handlers/edgelessdb_handler/tests/test_edgelessdb_handler.py +9 -9
- mindsdb/integrations/handlers/email_handler/email_client.py +1 -1
- mindsdb/integrations/handlers/email_handler/email_ingestor.py +1 -1
- mindsdb/integrations/handlers/email_handler/email_tables.py +0 -1
- mindsdb/integrations/handlers/email_handler/settings.py +0 -1
- mindsdb/integrations/handlers/eventstoredb_handler/eventstoredb_handler.py +2 -1
- mindsdb/integrations/handlers/firebird_handler/firebird_handler.py +1 -1
- mindsdb/integrations/handlers/flaml_handler/flaml_handler.py +9 -9
- mindsdb/integrations/handlers/frappe_handler/frappe_client.py +5 -5
- mindsdb/integrations/handlers/frappe_handler/frappe_handler.py +6 -5
- mindsdb/integrations/handlers/frappe_handler/frappe_tables.py +2 -2
- mindsdb/integrations/handlers/github_handler/connection_args.py +2 -2
- mindsdb/integrations/handlers/github_handler/github_handler.py +1 -8
- mindsdb/integrations/handlers/github_handler/github_tables.py +13 -24
- mindsdb/integrations/handlers/gitlab_handler/gitlab_handler.py +2 -1
- mindsdb/integrations/handlers/gitlab_handler/gitlab_tables.py +1 -4
- mindsdb/integrations/handlers/gmail_handler/gmail_handler.py +6 -13
- mindsdb/integrations/handlers/gmail_handler/requirements.txt +1 -0
- mindsdb/integrations/handlers/google_analytics_handler/requirements.txt +2 -1
- mindsdb/integrations/handlers/google_books_handler/google_books_handler.py +2 -1
- mindsdb/integrations/handlers/google_books_handler/google_books_tables.py +0 -3
- mindsdb/integrations/handlers/google_books_handler/requirements.txt +1 -1
- mindsdb/integrations/handlers/google_calendar_handler/google_calendar_handler.py +4 -4
- mindsdb/integrations/handlers/google_calendar_handler/google_calendar_tables.py +2 -6
- mindsdb/integrations/handlers/google_calendar_handler/requirements.txt +1 -0
- mindsdb/integrations/handlers/google_content_shopping_handler/google_content_shopping_handler.py +3 -2
- mindsdb/integrations/handlers/google_content_shopping_handler/google_content_shopping_tables.py +0 -3
- mindsdb/integrations/handlers/google_content_shopping_handler/requirements.txt +1 -1
- mindsdb/integrations/handlers/google_fit_handler/google_fit_handler.py +10 -12
- mindsdb/integrations/handlers/google_fit_handler/google_fit_tables.py +11 -13
- mindsdb/integrations/handlers/google_fit_handler/requirements.txt +2 -0
- mindsdb/integrations/handlers/google_search_handler/google_search_handler.py +2 -1
- mindsdb/integrations/handlers/google_search_handler/google_search_tables.py +0 -3
- mindsdb/integrations/handlers/google_search_handler/requirements.txt +1 -1
- mindsdb/integrations/handlers/groq_handler/__init__.py +3 -3
- mindsdb/integrations/handlers/hackernews_handler/hn_handler.py +5 -7
- mindsdb/integrations/handlers/hackernews_handler/hn_table.py +6 -7
- mindsdb/integrations/handlers/hive_handler/tests/test_hive_handler.py +1 -1
- mindsdb/integrations/handlers/hsqldb_handler/connection_args.py +6 -6
- mindsdb/integrations/handlers/hsqldb_handler/hsqldb_handler.py +4 -3
- mindsdb/integrations/handlers/huggingface_api_handler/exceptions.py +1 -1
- mindsdb/integrations/handlers/huggingface_api_handler/huggingface_api_handler.py +1 -8
- mindsdb/integrations/handlers/huggingface_handler/huggingface_handler.py +6 -6
- mindsdb/integrations/handlers/huggingface_handler/requirements.txt +1 -1
- mindsdb/integrations/handlers/huggingface_handler/requirements_cpu.txt +1 -1
- mindsdb/integrations/handlers/ignite_handler/ignite_handler.py +2 -1
- mindsdb/integrations/handlers/impala_handler/impala_handler.py +9 -12
- mindsdb/integrations/handlers/impala_handler/tests/test_impala_handler.py +11 -11
- mindsdb/integrations/handlers/influxdb_handler/influxdb_handler.py +10 -13
- mindsdb/integrations/handlers/influxdb_handler/influxdb_tables.py +20 -20
- mindsdb/integrations/handlers/informix_handler/__about__.py +8 -8
- mindsdb/integrations/handlers/informix_handler/__init__.py +12 -5
- mindsdb/integrations/handlers/informix_handler/informix_handler.py +99 -133
- mindsdb/integrations/handlers/informix_handler/tests/test_informix_handler.py +13 -11
- mindsdb/integrations/handlers/ingres_handler/__about__.py +0 -1
- mindsdb/integrations/handlers/ingres_handler/ingres_handler.py +1 -0
- mindsdb/integrations/handlers/jira_handler/jira_handler.archived.py +75 -0
- mindsdb/integrations/handlers/jira_handler/jira_handler.py +113 -38
- mindsdb/integrations/handlers/jira_handler/jira_tables.py +229 -0
- mindsdb/integrations/handlers/jira_handler/requirements.txt +1 -0
- mindsdb/integrations/handlers/kinetica_handler/__init__.py +0 -1
- mindsdb/integrations/handlers/langchain_handler/langchain_handler.py +4 -4
- mindsdb/integrations/handlers/langchain_handler/tools.py +9 -10
- mindsdb/integrations/handlers/leonardoai_handler/__init__.py +1 -1
- mindsdb/integrations/handlers/lightfm_handler/requirements.txt +1 -0
- mindsdb/integrations/handlers/lightwood_handler/functions.py +2 -2
- mindsdb/integrations/handlers/lightwood_handler/lightwood_handler.py +0 -3
- mindsdb/integrations/handlers/lightwood_handler/requirements.txt +4 -4
- mindsdb/integrations/handlers/lightwood_handler/tests/test_lightwood_handler.py +11 -11
- mindsdb/integrations/handlers/lindorm_handler/requirements.txt +1 -0
- mindsdb/integrations/handlers/llama_index_handler/llama_index_handler.py +4 -4
- mindsdb/integrations/handlers/llama_index_handler/settings.py +10 -9
- mindsdb/integrations/handlers/materialize_handler/tests/test_materialize_handler.py +8 -10
- mindsdb/integrations/handlers/matrixone_handler/matrixone_handler.py +4 -4
- mindsdb/integrations/handlers/matrixone_handler/tests/test_matrixone_handler.py +8 -9
- mindsdb/integrations/handlers/maxdb_handler/connection_args.py +25 -25
- mindsdb/integrations/handlers/maxdb_handler/maxdb_handler.py +1 -0
- mindsdb/integrations/handlers/mediawiki_handler/mediawiki_handler.py +3 -2
- mindsdb/integrations/handlers/mediawiki_handler/mediawiki_tables.py +1 -1
- mindsdb/integrations/handlers/mendeley_handler/__about__.py +1 -1
- mindsdb/integrations/handlers/mendeley_handler/__init__.py +2 -2
- mindsdb/integrations/handlers/mendeley_handler/mendeley_handler.py +48 -56
- mindsdb/integrations/handlers/mendeley_handler/mendeley_tables.py +24 -29
- mindsdb/integrations/handlers/mendeley_handler/tests/test_mendeley_handler.py +19 -17
- mindsdb/integrations/handlers/merlion_handler/merlion_handler.py +5 -4
- mindsdb/integrations/handlers/minds_endpoint_handler/__init__.py +3 -3
- mindsdb/integrations/handlers/mlflow_handler/mlflow_handler.py +58 -36
- mindsdb/integrations/handlers/monetdb_handler/__about__.py +8 -8
- mindsdb/integrations/handlers/monetdb_handler/__init__.py +15 -5
- mindsdb/integrations/handlers/monetdb_handler/connection_args.py +17 -18
- mindsdb/integrations/handlers/monetdb_handler/monetdb_handler.py +40 -57
- mindsdb/integrations/handlers/monetdb_handler/tests/test_monetdb_handler.py +7 -8
- mindsdb/integrations/handlers/monetdb_handler/utils/monet_get_id.py +13 -14
- mindsdb/integrations/handlers/monkeylearn_handler/__about__.py +1 -1
- mindsdb/integrations/handlers/monkeylearn_handler/__init__.py +1 -1
- mindsdb/integrations/handlers/monkeylearn_handler/monkeylearn_handler.py +2 -5
- mindsdb/integrations/handlers/ms_one_drive_handler/ms_graph_api_one_drive_client.py +1 -0
- mindsdb/integrations/handlers/ms_one_drive_handler/ms_one_drive_handler.py +1 -1
- mindsdb/integrations/handlers/ms_one_drive_handler/requirements.txt +2 -0
- mindsdb/integrations/handlers/ms_teams_handler/ms_graph_api_teams_client.py +23 -23
- mindsdb/integrations/handlers/ms_teams_handler/ms_teams_handler.py +3 -3
- mindsdb/integrations/handlers/ms_teams_handler/ms_teams_tables.py +10 -5
- mindsdb/integrations/handlers/ms_teams_handler/requirements.txt +3 -1
- mindsdb/integrations/handlers/mssql_handler/mssql_handler.py +73 -8
- mindsdb/integrations/handlers/mysql_handler/__about__.py +8 -8
- mindsdb/integrations/handlers/mysql_handler/__init__.py +15 -5
- mindsdb/integrations/handlers/mysql_handler/connection_args.py +43 -47
- mindsdb/integrations/handlers/mysql_handler/mysql_handler.py +101 -34
- mindsdb/integrations/handlers/mysql_handler/settings.py +15 -13
- mindsdb/integrations/handlers/neuralforecast_handler/neuralforecast_handler.py +1 -1
- mindsdb/integrations/handlers/newsapi_handler/newsapi_handler.py +1 -1
- mindsdb/integrations/handlers/newsapi_handler/tests/test_newsapi_handler.py +4 -4
- mindsdb/integrations/handlers/nuo_jdbc_handler/connection_args.py +2 -2
- mindsdb/integrations/handlers/nuo_jdbc_handler/nuo_jdbc_handler.py +28 -36
- mindsdb/integrations/handlers/nuo_jdbc_handler/tests/test_nuo_handler.py +5 -5
- mindsdb/integrations/handlers/oceanbase_handler/oceanbase_handler.py +0 -1
- mindsdb/integrations/handlers/oceanbase_handler/tests/test_oceanbase_handler.py +8 -10
- mindsdb/integrations/handlers/ollama_handler/ollama_handler.py +3 -3
- mindsdb/integrations/handlers/openai_handler/openai_handler.py +5 -4
- mindsdb/integrations/handlers/opengauss_handler/tests/test_opengauss_handler.py +1 -2
- mindsdb/integrations/handlers/openstreetmap_handler/__init__.py +7 -7
- mindsdb/integrations/handlers/oracle_handler/connection_args.py +6 -0
- mindsdb/integrations/handlers/oracle_handler/oracle_handler.py +77 -11
- mindsdb/integrations/handlers/orioledb_handler/tests/test_orioledb_handler.py +8 -10
- mindsdb/integrations/handlers/palm_handler/__about__.py +1 -1
- mindsdb/integrations/handlers/palm_handler/__init__.py +1 -1
- mindsdb/integrations/handlers/palm_handler/palm_handler.py +1 -3
- mindsdb/integrations/handlers/paypal_handler/paypal_handler.py +2 -2
- mindsdb/integrations/handlers/paypal_handler/paypal_tables.py +15 -14
- mindsdb/integrations/handlers/pgvector_handler/pgvector_handler.py +53 -10
- mindsdb/integrations/handlers/phoenix_handler/__init__.py +1 -1
- mindsdb/integrations/handlers/phoenix_handler/phoenix_handler.py +1 -0
- mindsdb/integrations/handlers/pinot_handler/__init__.py +1 -1
- mindsdb/integrations/handlers/pinot_handler/pinot_handler.py +3 -2
- mindsdb/integrations/handlers/plaid_handler/plaid_handler.py +13 -13
- mindsdb/integrations/handlers/plaid_handler/plaid_tables.py +10 -12
- mindsdb/integrations/handlers/plaid_handler/utils.py +4 -6
- mindsdb/integrations/handlers/planetscale_handler/planetscale_handler.py +1 -4
- mindsdb/integrations/handlers/portkey_handler/__init__.py +2 -2
- mindsdb/integrations/handlers/postgres_handler/postgres_handler.py +105 -24
- mindsdb/integrations/handlers/postgres_handler/tests/test_postgres_handler.py +11 -6
- mindsdb/integrations/handlers/questdb_handler/questdb_handler.py +1 -2
- mindsdb/integrations/handlers/questdb_handler/tests/test_questdb_handler.py +2 -3
- mindsdb/integrations/handlers/quickbooks_handler/quickbooks_handler.py +6 -8
- mindsdb/integrations/handlers/quickbooks_handler/quickbooks_table.py +10 -10
- mindsdb/integrations/handlers/rag_handler/ingest.py +2 -2
- mindsdb/integrations/handlers/rag_handler/rag_handler.py +1 -1
- mindsdb/integrations/handlers/rag_handler/settings.py +1 -1
- mindsdb/integrations/handlers/reddit_handler/reddit_handler.py +2 -7
- mindsdb/integrations/handlers/reddit_handler/reddit_tables.py +2 -3
- mindsdb/integrations/handlers/replicate_handler/replicate_handler.py +6 -6
- mindsdb/integrations/handlers/rocket_chat_handler/rocket_chat_handler.py +1 -2
- mindsdb/integrations/handlers/rocket_chat_handler/rocket_chat_tables.py +0 -3
- mindsdb/integrations/handlers/rockset_handler/connection_args.py +14 -14
- mindsdb/integrations/handlers/rockset_handler/tests/test_rockset_handler.py +1 -0
- mindsdb/integrations/handlers/scylla_handler/scylla_handler.py +6 -5
- mindsdb/integrations/handlers/sendinblue_handler/sendinblue_handler.py +2 -1
- mindsdb/integrations/handlers/sendinblue_handler/sendinblue_tables.py +16 -16
- mindsdb/integrations/handlers/sentence_transformers_handler/__init__.py +1 -1
- mindsdb/integrations/handlers/sheets_handler/connection_args.py +1 -1
- mindsdb/integrations/handlers/shopify_handler/shopify_handler.py +7 -6
- mindsdb/integrations/handlers/shopify_handler/shopify_tables.py +38 -41
- mindsdb/integrations/handlers/singlestore_handler/__about__.py +1 -1
- mindsdb/integrations/handlers/singlestore_handler/__init__.py +0 -1
- mindsdb/integrations/handlers/singlestore_handler/singlestore_handler.py +1 -0
- mindsdb/integrations/handlers/singlestore_handler/tests/test_singlestore_handler.py +3 -3
- mindsdb/integrations/handlers/slack_handler/__init__.py +3 -3
- mindsdb/integrations/handlers/snowflake_handler/requirements.txt +1 -1
- mindsdb/integrations/handlers/snowflake_handler/snowflake_handler.py +100 -6
- mindsdb/integrations/handlers/solr_handler/connection_args.py +7 -7
- mindsdb/integrations/handlers/solr_handler/solr_handler.py +2 -1
- mindsdb/integrations/handlers/solr_handler/tests/test_solr_handler.py +2 -1
- mindsdb/integrations/handlers/sqlany_handler/sqlany_handler.py +3 -2
- mindsdb/integrations/handlers/sqlite_handler/sqlite_handler.py +1 -0
- mindsdb/integrations/handlers/sqreamdb_handler/connection_args.py +1 -1
- mindsdb/integrations/handlers/sqreamdb_handler/sqreamdb_handler.py +15 -20
- mindsdb/integrations/handlers/sqreamdb_handler/tests/test_sqreamdb_handler.py +4 -4
- mindsdb/integrations/handlers/stabilityai_handler/__init__.py +1 -1
- mindsdb/integrations/handlers/starrocks_handler/starrocks_handler.py +0 -1
- mindsdb/integrations/handlers/starrocks_handler/tests/test_starrocks_handler.py +8 -10
- mindsdb/integrations/handlers/statsforecast_handler/statsforecast_handler.py +2 -2
- mindsdb/integrations/handlers/strava_handler/strava_handler.py +4 -8
- mindsdb/integrations/handlers/strava_handler/strava_tables.py +22 -30
- mindsdb/integrations/handlers/stripe_handler/stripe_handler.py +3 -2
- mindsdb/integrations/handlers/stripe_handler/stripe_tables.py +11 -27
- mindsdb/integrations/handlers/supabase_handler/tests/test_supabase_handler.py +1 -1
- mindsdb/integrations/handlers/surrealdb_handler/surrealdb_handler.py +4 -4
- mindsdb/integrations/handlers/tdengine_handler/tdengine_handler.py +25 -27
- mindsdb/integrations/handlers/tdengine_handler/tests/test_tdengine_handler.py +8 -8
- mindsdb/integrations/handlers/tidb_handler/tests/test_tidb_handler.py +1 -2
- mindsdb/integrations/handlers/timegpt_handler/timegpt_handler.py +5 -5
- mindsdb/integrations/handlers/tpot_handler/tpot_handler.py +21 -26
- mindsdb/integrations/handlers/trino_handler/trino_handler.py +14 -14
- mindsdb/integrations/handlers/twitter_handler/twitter_handler.py +2 -4
- mindsdb/integrations/handlers/unify_handler/tests/test_unify_handler.py +7 -8
- mindsdb/integrations/handlers/unify_handler/unify_handler.py +9 -9
- mindsdb/integrations/handlers/vertex_handler/requirements.txt +1 -0
- mindsdb/integrations/handlers/vertex_handler/vertex_client.py +1 -1
- mindsdb/integrations/handlers/vertica_handler/tests/test_vertica_handler.py +11 -11
- mindsdb/integrations/handlers/vertica_handler/vertica_handler.py +11 -14
- mindsdb/integrations/handlers/vitess_handler/tests/test_vitess_handler.py +9 -11
- mindsdb/integrations/handlers/vitess_handler/vitess_handler.py +0 -1
- mindsdb/integrations/handlers/web_handler/web_handler.py +1 -0
- mindsdb/integrations/handlers/whatsapp_handler/__init__.py +3 -3
- mindsdb/integrations/handlers/writer_handler/evaluate.py +1 -1
- mindsdb/integrations/handlers/writer_handler/settings.py +0 -1
- mindsdb/integrations/handlers/writer_handler/writer_handler.py +1 -0
- mindsdb/integrations/handlers/youtube_handler/requirements.txt +1 -0
- mindsdb/integrations/handlers/youtube_handler/youtube_handler.py +5 -5
- mindsdb/integrations/handlers/youtube_handler/youtube_tables.py +26 -27
- mindsdb/integrations/handlers/yugabyte_handler/tests/test_yugabyte_handler.py +3 -3
- mindsdb/integrations/handlers/yugabyte_handler/yugabyte_handler.py +0 -6
- mindsdb/integrations/libs/response.py +67 -52
- mindsdb/integrations/libs/vectordatabase_handler.py +6 -0
- mindsdb/integrations/utilities/files/file_reader.py +5 -2
- mindsdb/integrations/utilities/handler_utils.py +15 -3
- mindsdb/integrations/utilities/handlers/api_utilities/__init__.py +0 -1
- mindsdb/integrations/utilities/handlers/auth_utilities/__init__.py +0 -2
- mindsdb/integrations/utilities/utils.py +3 -3
- mindsdb/interfaces/agents/agents_controller.py +164 -1
- mindsdb/interfaces/agents/constants.py +29 -2
- mindsdb/interfaces/agents/langchain_agent.py +18 -8
- mindsdb/interfaces/agents/mindsdb_database_agent.py +101 -2
- mindsdb/interfaces/database/projects.py +1 -7
- mindsdb/interfaces/functions/controller.py +11 -14
- mindsdb/interfaces/functions/to_markdown.py +9 -124
- mindsdb/interfaces/knowledge_base/controller.py +47 -19
- mindsdb/interfaces/knowledge_base/preprocessing/document_preprocessor.py +41 -15
- mindsdb/interfaces/knowledge_base/preprocessing/json_chunker.py +434 -0
- mindsdb/interfaces/knowledge_base/preprocessing/models.py +54 -0
- mindsdb/interfaces/knowledge_base/utils.py +10 -15
- mindsdb/interfaces/model/model_controller.py +0 -2
- mindsdb/interfaces/query_context/context_controller.py +66 -10
- mindsdb/interfaces/skills/custom/text2sql/mindsdb_kb_tools.py +190 -0
- mindsdb/interfaces/skills/custom/text2sql/mindsdb_sql_toolkit.py +92 -0
- mindsdb/interfaces/skills/skill_tool.py +202 -57
- mindsdb/interfaces/skills/sql_agent.py +238 -28
- mindsdb/interfaces/storage/fs.py +1 -0
- mindsdb/interfaces/variables/__init__.py +0 -0
- mindsdb/interfaces/variables/variables_controller.py +97 -0
- mindsdb/migrations/env.py +5 -7
- mindsdb/migrations/migrate.py +47 -9
- mindsdb/migrations/versions/2025-05-21_9f150e4f9a05_checkpoint_1.py +360 -0
- mindsdb/utilities/config.py +333 -220
- mindsdb/utilities/context.py +1 -1
- mindsdb/utilities/functions.py +0 -36
- mindsdb/utilities/langfuse.py +19 -10
- mindsdb/utilities/otel/__init__.py +9 -193
- mindsdb/utilities/otel/metric_handlers/__init__.py +5 -1
- mindsdb/utilities/otel/prepare.py +198 -0
- mindsdb/utilities/sql.py +83 -0
- mindsdb/utilities/starters.py +13 -0
- {mindsdb-25.4.5.0.dist-info → mindsdb-25.5.4.0.dist-info}/METADATA +351 -338
- {mindsdb-25.4.5.0.dist-info → mindsdb-25.5.4.0.dist-info}/RECORD +348 -322
- {mindsdb-25.4.5.0.dist-info → mindsdb-25.5.4.0.dist-info}/WHEEL +1 -1
- mindsdb/api/mysql/mysql_proxy/classes/sql_statement_parser.py +0 -151
- mindsdb/integrations/handlers/monkeylearn_handler/requirements.txt +0 -1
- {mindsdb-25.4.5.0.dist-info → mindsdb-25.5.4.0.dist-info}/licenses/LICENSE +0 -0
- {mindsdb-25.4.5.0.dist-info → mindsdb-25.5.4.0.dist-info}/top_level.txt +0 -0
mindsdb/api/a2a/agent.py
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import Any, AsyncIterable, Dict, List, Iterator
|
|
3
|
+
import requests
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
from mindsdb.api.a2a.constants import DEFAULT_STREAM_TIMEOUT
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class MindsDBAgent:
|
|
12
|
+
"""An agent that communicates with MindsDB over HTTP following the A2A protocol."""
|
|
13
|
+
|
|
14
|
+
# Supported content-types according to A2A spec. We include both the
|
|
15
|
+
# mime-type form and the simple "text" token so that clients using either
|
|
16
|
+
# convention succeed.
|
|
17
|
+
SUPPORTED_CONTENT_TYPES = ["text", "text/plain", "application/json"]
|
|
18
|
+
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
agent_name="my_agent",
|
|
22
|
+
project_name="mindsdb",
|
|
23
|
+
host="localhost",
|
|
24
|
+
port=47334,
|
|
25
|
+
):
|
|
26
|
+
self.agent_name = agent_name
|
|
27
|
+
self.project_name = project_name
|
|
28
|
+
self.host = host
|
|
29
|
+
self.port = port
|
|
30
|
+
self.base_url = f"http://{host}:{port}"
|
|
31
|
+
self.agent_url = (
|
|
32
|
+
f"{self.base_url}/api/projects/{project_name}/agents/{agent_name}"
|
|
33
|
+
)
|
|
34
|
+
self.sql_url = f"{self.base_url}/api/sql/query"
|
|
35
|
+
logger.info(f"Initialized MindsDB agent connector to {self.base_url}")
|
|
36
|
+
|
|
37
|
+
def invoke(self, query, session_id) -> Dict[str, Any]:
|
|
38
|
+
"""Send a query to the MindsDB agent using SQL API."""
|
|
39
|
+
try:
|
|
40
|
+
# Escape single quotes in the query for SQL
|
|
41
|
+
escaped_query = query.replace("'", "''")
|
|
42
|
+
|
|
43
|
+
# Build the SQL query to the agent
|
|
44
|
+
sql_query = f"SELECT * FROM {self.project_name}.{self.agent_name} WHERE question = '{escaped_query}'"
|
|
45
|
+
|
|
46
|
+
# Log request for debugging
|
|
47
|
+
logger.info(f"Sending SQL query to MindsDB: {sql_query[:100]}...")
|
|
48
|
+
|
|
49
|
+
# Send the request to MindsDB SQL API
|
|
50
|
+
response = requests.post(self.sql_url, json={"query": sql_query})
|
|
51
|
+
response.raise_for_status()
|
|
52
|
+
|
|
53
|
+
# Process the response
|
|
54
|
+
data = response.json()
|
|
55
|
+
|
|
56
|
+
# Log the response for debugging
|
|
57
|
+
logger.debug(f"Received response from MindsDB: {json.dumps(data)[:200]}...")
|
|
58
|
+
|
|
59
|
+
if "data" in data and len(data["data"]) > 0:
|
|
60
|
+
# The result should be in the first row
|
|
61
|
+
result_row = data["data"][0]
|
|
62
|
+
|
|
63
|
+
# Find the response column (might be 'response', 'answer', 'result', etc.)
|
|
64
|
+
# Try common column names or just return all content
|
|
65
|
+
for column in ["response", "result", "answer", "completion", "output"]:
|
|
66
|
+
if column in result_row:
|
|
67
|
+
content = result_row[column]
|
|
68
|
+
logger.info(
|
|
69
|
+
f"Found result in column '{column}': {content[:100]}..."
|
|
70
|
+
)
|
|
71
|
+
return {
|
|
72
|
+
"content": content,
|
|
73
|
+
"parts": [{"type": "text", "text": content}],
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
# If no specific column found, return the whole row as JSON
|
|
77
|
+
logger.info("No specific result column found, returning full row")
|
|
78
|
+
content = json.dumps(result_row, indent=2)
|
|
79
|
+
|
|
80
|
+
# Return structured data only if it is a dictionary (A2A `data` part
|
|
81
|
+
# must itself be a JSON object). In some cases MindsDB may return a
|
|
82
|
+
# list (for instance a list of rows or records). If that happens we
|
|
83
|
+
# downgrade it to plain-text to avoid schema-validation errors on the
|
|
84
|
+
# A2A side.
|
|
85
|
+
|
|
86
|
+
parts: List[dict] = [{"type": "text", "text": content}]
|
|
87
|
+
|
|
88
|
+
if isinstance(result_row, dict):
|
|
89
|
+
parts.append(
|
|
90
|
+
{
|
|
91
|
+
"type": "data",
|
|
92
|
+
"data": result_row,
|
|
93
|
+
"metadata": {"subtype": "json"},
|
|
94
|
+
}
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
"content": content,
|
|
99
|
+
"parts": parts,
|
|
100
|
+
}
|
|
101
|
+
else:
|
|
102
|
+
error_msg = "Error: No data returned from MindsDB"
|
|
103
|
+
logger.error(error_msg)
|
|
104
|
+
return {
|
|
105
|
+
"content": error_msg,
|
|
106
|
+
"parts": [{"type": "text", "text": error_msg}],
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
except requests.exceptions.RequestException as e:
|
|
110
|
+
error_msg = f"Error connecting to MindsDB: {str(e)}"
|
|
111
|
+
logger.error(error_msg)
|
|
112
|
+
return {
|
|
113
|
+
"content": error_msg,
|
|
114
|
+
"parts": [{"type": "text", "text": error_msg}],
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
except Exception as e:
|
|
118
|
+
error_msg = f"Error: {str(e)}"
|
|
119
|
+
logger.error(error_msg)
|
|
120
|
+
return {
|
|
121
|
+
"content": error_msg,
|
|
122
|
+
"parts": [{"type": "text", "text": error_msg}],
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
def streaming_invoke(
|
|
126
|
+
self, messages: List[dict], timeout: int = DEFAULT_STREAM_TIMEOUT
|
|
127
|
+
) -> Iterator[Dict[str, Any]]:
|
|
128
|
+
"""Stream responses from the MindsDB agent using the direct API endpoint.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
messages: List of message dictionaries, each containing 'question' and optionally 'answer'.
|
|
132
|
+
Example: [{'question': 'what is the average rental price for a three bedroom?', 'answer': None}]
|
|
133
|
+
timeout: Request timeout in seconds (default: 300)
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
Iterator yielding chunks of the streaming response.
|
|
137
|
+
"""
|
|
138
|
+
try:
|
|
139
|
+
# Construct the URL for the streaming completions endpoint
|
|
140
|
+
url = f"{self.base_url}/api/projects/{self.project_name}/agents/{self.agent_name}/completions/stream"
|
|
141
|
+
|
|
142
|
+
# Log request for debugging
|
|
143
|
+
logger.info(
|
|
144
|
+
f"Sending streaming request to MindsDB agent: {self.agent_name}"
|
|
145
|
+
)
|
|
146
|
+
logger.debug(f"Request messages: {json.dumps(messages)[:200]}...")
|
|
147
|
+
|
|
148
|
+
# Send the request to MindsDB streaming API with timeout
|
|
149
|
+
stream = requests.post(
|
|
150
|
+
url, json={"messages": messages}, stream=True, timeout=timeout
|
|
151
|
+
)
|
|
152
|
+
stream.raise_for_status()
|
|
153
|
+
|
|
154
|
+
# Process the streaming response directly
|
|
155
|
+
for line in stream.iter_lines():
|
|
156
|
+
if line:
|
|
157
|
+
# Parse each non-empty line
|
|
158
|
+
try:
|
|
159
|
+
line = line.decode("utf-8")
|
|
160
|
+
if line.startswith("data: "):
|
|
161
|
+
# Extract the JSON data from the line that starts with 'data: '
|
|
162
|
+
data = line[6:] # Remove 'data: ' prefix
|
|
163
|
+
try:
|
|
164
|
+
chunk = json.loads(data)
|
|
165
|
+
# Pass through the chunk with minimal modifications
|
|
166
|
+
yield chunk
|
|
167
|
+
except json.JSONDecodeError as e:
|
|
168
|
+
logger.warning(
|
|
169
|
+
f"Failed to parse JSON from line: {data}. Error: {str(e)}"
|
|
170
|
+
)
|
|
171
|
+
# Yield error information but continue processing
|
|
172
|
+
yield {
|
|
173
|
+
"error": f"JSON parse error: {str(e)}",
|
|
174
|
+
"data": data,
|
|
175
|
+
"is_task_complete": False,
|
|
176
|
+
"parts": [
|
|
177
|
+
{
|
|
178
|
+
"type": "text",
|
|
179
|
+
"text": f"Error parsing response: {str(e)}",
|
|
180
|
+
}
|
|
181
|
+
],
|
|
182
|
+
"metadata": {},
|
|
183
|
+
}
|
|
184
|
+
else:
|
|
185
|
+
# Log other lines for debugging
|
|
186
|
+
logger.debug(f"Received non-data line: {line}")
|
|
187
|
+
|
|
188
|
+
# If it looks like a raw text response (not SSE format), wrap it
|
|
189
|
+
if not line.startswith("event:") and not line.startswith(
|
|
190
|
+
":"
|
|
191
|
+
):
|
|
192
|
+
yield {"content": line, "is_task_complete": False}
|
|
193
|
+
except UnicodeDecodeError as e:
|
|
194
|
+
logger.warning(f"Failed to decode line: {str(e)}")
|
|
195
|
+
# Continue processing despite decode errors
|
|
196
|
+
|
|
197
|
+
except requests.exceptions.Timeout as e:
|
|
198
|
+
error_msg = f"Request timed out after {timeout} seconds: {str(e)}"
|
|
199
|
+
logger.error(error_msg)
|
|
200
|
+
yield {
|
|
201
|
+
"content": error_msg,
|
|
202
|
+
"parts": [{"type": "text", "text": error_msg}],
|
|
203
|
+
"is_task_complete": True,
|
|
204
|
+
"error": "timeout",
|
|
205
|
+
"metadata": {"error": True},
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
except requests.exceptions.ChunkedEncodingError as e:
|
|
209
|
+
error_msg = f"Stream was interrupted: {str(e)}"
|
|
210
|
+
logger.error(error_msg)
|
|
211
|
+
yield {
|
|
212
|
+
"content": error_msg,
|
|
213
|
+
"parts": [{"type": "text", "text": error_msg}],
|
|
214
|
+
"is_task_complete": True,
|
|
215
|
+
"error": "stream_interrupted",
|
|
216
|
+
"metadata": {"error": True},
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
except requests.exceptions.ConnectionError as e:
|
|
220
|
+
error_msg = f"Connection error: {str(e)}"
|
|
221
|
+
logger.error(error_msg)
|
|
222
|
+
yield {
|
|
223
|
+
"content": error_msg,
|
|
224
|
+
"parts": [{"type": "text", "text": error_msg}],
|
|
225
|
+
"is_task_complete": True,
|
|
226
|
+
"error": "connection_error",
|
|
227
|
+
"metadata": {"error": True},
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
except requests.exceptions.RequestException as e:
|
|
231
|
+
error_msg = f"Error connecting to MindsDB streaming API: {str(e)}"
|
|
232
|
+
logger.error(error_msg)
|
|
233
|
+
yield {
|
|
234
|
+
"content": error_msg,
|
|
235
|
+
"parts": [{"type": "text", "text": error_msg}],
|
|
236
|
+
"is_task_complete": True,
|
|
237
|
+
"error": "request_error",
|
|
238
|
+
"metadata": {"error": True},
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
except Exception as e:
|
|
242
|
+
error_msg = f"Error in streaming: {str(e)}"
|
|
243
|
+
logger.error(error_msg)
|
|
244
|
+
yield {
|
|
245
|
+
"content": error_msg,
|
|
246
|
+
"parts": [{"type": "text", "text": error_msg}],
|
|
247
|
+
"is_task_complete": True,
|
|
248
|
+
"error": "unknown_error",
|
|
249
|
+
"metadata": {"error": True},
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
# Send a final completion message
|
|
253
|
+
yield {"is_task_complete": True, "metadata": {"complete": True}}
|
|
254
|
+
|
|
255
|
+
async def stream(self, query, session_id) -> AsyncIterable[Dict[str, Any]]:
|
|
256
|
+
"""Stream responses from the MindsDB agent (uses streaming API endpoint)."""
|
|
257
|
+
try:
|
|
258
|
+
logger.info(f"Using streaming API for query: {query[:100]}...")
|
|
259
|
+
|
|
260
|
+
# Format the query into the message structure expected by streaming_invoke
|
|
261
|
+
messages = [{"question": query, "answer": None}]
|
|
262
|
+
|
|
263
|
+
# Use the streaming_invoke method to get real streaming responses
|
|
264
|
+
streaming_response = self.streaming_invoke(messages)
|
|
265
|
+
|
|
266
|
+
# Yield all chunks directly from the streaming response
|
|
267
|
+
for chunk in streaming_response:
|
|
268
|
+
# Only add required fields if they don't exist
|
|
269
|
+
# This preserves the original structure as much as possible
|
|
270
|
+
if "is_task_complete" not in chunk:
|
|
271
|
+
chunk["is_task_complete"] = False
|
|
272
|
+
|
|
273
|
+
if "metadata" not in chunk:
|
|
274
|
+
chunk["metadata"] = {}
|
|
275
|
+
|
|
276
|
+
# Ensure parts exist, but try to preserve original content
|
|
277
|
+
if "parts" not in chunk:
|
|
278
|
+
# If content exists, create a part from it
|
|
279
|
+
if "content" in chunk:
|
|
280
|
+
chunk["parts"] = [{"type": "text", "text": chunk["content"]}]
|
|
281
|
+
# If output exists, create a part from it
|
|
282
|
+
elif "output" in chunk:
|
|
283
|
+
chunk["parts"] = [{"type": "text", "text": chunk["output"]}]
|
|
284
|
+
# If actions exist, create empty parts
|
|
285
|
+
elif "actions" in chunk or "steps" in chunk or "messages" in chunk:
|
|
286
|
+
# These chunks have their own format, just add empty parts
|
|
287
|
+
chunk["parts"] = []
|
|
288
|
+
else:
|
|
289
|
+
# Skip chunks with no content
|
|
290
|
+
continue
|
|
291
|
+
|
|
292
|
+
yield chunk
|
|
293
|
+
|
|
294
|
+
except Exception as e:
|
|
295
|
+
logger.error(f"Error in streaming: {str(e)}")
|
|
296
|
+
yield {
|
|
297
|
+
"is_task_complete": True,
|
|
298
|
+
"parts": [
|
|
299
|
+
{
|
|
300
|
+
"type": "text",
|
|
301
|
+
"text": f"Error: {str(e)}",
|
|
302
|
+
}
|
|
303
|
+
],
|
|
304
|
+
"metadata": {
|
|
305
|
+
"type": "reasoning",
|
|
306
|
+
"subtype": "error",
|
|
307
|
+
},
|
|
308
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import httpx
|
|
2
|
+
from common.types import (
|
|
3
|
+
AgentCard,
|
|
4
|
+
A2AClientJSONError,
|
|
5
|
+
)
|
|
6
|
+
import json
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class A2ACardResolver:
|
|
10
|
+
def __init__(self, base_url, agent_card_path="/.well-known/agent.json"):
|
|
11
|
+
self.base_url = base_url.rstrip("/")
|
|
12
|
+
self.agent_card_path = agent_card_path.lstrip("/")
|
|
13
|
+
|
|
14
|
+
def get_agent_card(self) -> AgentCard:
|
|
15
|
+
with httpx.Client() as client:
|
|
16
|
+
response = client.get(self.base_url + "/" + self.agent_card_path)
|
|
17
|
+
response.raise_for_status()
|
|
18
|
+
try:
|
|
19
|
+
return AgentCard(**response.json())
|
|
20
|
+
except json.JSONDecodeError as e:
|
|
21
|
+
raise A2AClientJSONError(str(e)) from e
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import httpx
|
|
2
|
+
from httpx_sse import connect_sse
|
|
3
|
+
from typing import Any, AsyncIterable
|
|
4
|
+
from common.types import (
|
|
5
|
+
AgentCard,
|
|
6
|
+
GetTaskRequest,
|
|
7
|
+
SendTaskRequest,
|
|
8
|
+
SendTaskResponse,
|
|
9
|
+
JSONRPCRequest,
|
|
10
|
+
GetTaskResponse,
|
|
11
|
+
CancelTaskResponse,
|
|
12
|
+
CancelTaskRequest,
|
|
13
|
+
SetTaskPushNotificationRequest,
|
|
14
|
+
SetTaskPushNotificationResponse,
|
|
15
|
+
GetTaskPushNotificationRequest,
|
|
16
|
+
GetTaskPushNotificationResponse,
|
|
17
|
+
A2AClientHTTPError,
|
|
18
|
+
A2AClientJSONError,
|
|
19
|
+
SendTaskStreamingRequest,
|
|
20
|
+
SendTaskStreamingResponse,
|
|
21
|
+
)
|
|
22
|
+
import json
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class A2AClient:
|
|
26
|
+
def __init__(self, agent_card: AgentCard = None, url: str = None):
|
|
27
|
+
if agent_card:
|
|
28
|
+
self.url = agent_card.url
|
|
29
|
+
elif url:
|
|
30
|
+
self.url = url
|
|
31
|
+
else:
|
|
32
|
+
raise ValueError("Must provide either agent_card or url")
|
|
33
|
+
|
|
34
|
+
async def send_task(self, payload: dict[str, Any]) -> SendTaskResponse:
|
|
35
|
+
request = SendTaskRequest(params=payload)
|
|
36
|
+
return SendTaskResponse(**await self._send_request(request))
|
|
37
|
+
|
|
38
|
+
async def send_task_streaming(
|
|
39
|
+
self, payload: dict[str, Any]
|
|
40
|
+
) -> AsyncIterable[SendTaskStreamingResponse]:
|
|
41
|
+
request = SendTaskStreamingRequest(params=payload)
|
|
42
|
+
with httpx.Client(timeout=None) as client:
|
|
43
|
+
with connect_sse(
|
|
44
|
+
client, "POST", self.url, json=request.model_dump()
|
|
45
|
+
) as event_source:
|
|
46
|
+
try:
|
|
47
|
+
for sse in event_source.iter_sse():
|
|
48
|
+
yield SendTaskStreamingResponse(**json.loads(sse.data))
|
|
49
|
+
except json.JSONDecodeError as e:
|
|
50
|
+
raise A2AClientJSONError(str(e)) from e
|
|
51
|
+
except httpx.RequestError as e:
|
|
52
|
+
raise A2AClientHTTPError(400, str(e)) from e
|
|
53
|
+
|
|
54
|
+
async def _send_request(self, request: JSONRPCRequest) -> dict[str, Any]:
|
|
55
|
+
async with httpx.AsyncClient() as client:
|
|
56
|
+
try:
|
|
57
|
+
# Image generation could take time, adding timeout
|
|
58
|
+
response = await client.post(
|
|
59
|
+
self.url, json=request.model_dump(), timeout=30
|
|
60
|
+
)
|
|
61
|
+
response.raise_for_status()
|
|
62
|
+
return response.json()
|
|
63
|
+
except httpx.HTTPStatusError as e:
|
|
64
|
+
raise A2AClientHTTPError(e.response.status_code, str(e)) from e
|
|
65
|
+
except json.JSONDecodeError as e:
|
|
66
|
+
raise A2AClientJSONError(str(e)) from e
|
|
67
|
+
|
|
68
|
+
async def get_task(self, payload: dict[str, Any]) -> GetTaskResponse:
|
|
69
|
+
request = GetTaskRequest(params=payload)
|
|
70
|
+
return GetTaskResponse(**await self._send_request(request))
|
|
71
|
+
|
|
72
|
+
async def cancel_task(self, payload: dict[str, Any]) -> CancelTaskResponse:
|
|
73
|
+
request = CancelTaskRequest(params=payload)
|
|
74
|
+
return CancelTaskResponse(**await self._send_request(request))
|
|
75
|
+
|
|
76
|
+
async def set_task_callback(
|
|
77
|
+
self, payload: dict[str, Any]
|
|
78
|
+
) -> SetTaskPushNotificationResponse:
|
|
79
|
+
request = SetTaskPushNotificationRequest(params=payload)
|
|
80
|
+
return SetTaskPushNotificationResponse(**await self._send_request(request))
|
|
81
|
+
|
|
82
|
+
async def get_task_callback(
|
|
83
|
+
self, payload: dict[str, Any]
|
|
84
|
+
) -> GetTaskPushNotificationResponse:
|
|
85
|
+
request = GetTaskPushNotificationRequest(params=payload)
|
|
86
|
+
return GetTaskPushNotificationResponse(**await self._send_request(request))
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
from starlette.applications import Starlette
|
|
2
|
+
from starlette.middleware.cors import CORSMiddleware
|
|
3
|
+
from starlette.responses import JSONResponse
|
|
4
|
+
from sse_starlette.sse import EventSourceResponse
|
|
5
|
+
from starlette.requests import Request
|
|
6
|
+
from ...common.types import (
|
|
7
|
+
A2ARequest,
|
|
8
|
+
JSONRPCResponse,
|
|
9
|
+
InvalidRequestError,
|
|
10
|
+
JSONParseError,
|
|
11
|
+
GetTaskRequest,
|
|
12
|
+
CancelTaskRequest,
|
|
13
|
+
SendTaskRequest,
|
|
14
|
+
SetTaskPushNotificationRequest,
|
|
15
|
+
GetTaskPushNotificationRequest,
|
|
16
|
+
InternalError,
|
|
17
|
+
AgentCard,
|
|
18
|
+
TaskResubscriptionRequest,
|
|
19
|
+
SendTaskStreamingRequest,
|
|
20
|
+
)
|
|
21
|
+
from pydantic import ValidationError
|
|
22
|
+
import json
|
|
23
|
+
from typing import AsyncIterable, Any
|
|
24
|
+
from ...common.server.task_manager import TaskManager
|
|
25
|
+
|
|
26
|
+
import logging
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class A2AServer:
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
host="0.0.0.0",
|
|
35
|
+
port=5000,
|
|
36
|
+
endpoint="/",
|
|
37
|
+
agent_card: AgentCard = None,
|
|
38
|
+
task_manager: TaskManager = None,
|
|
39
|
+
):
|
|
40
|
+
self.host = host
|
|
41
|
+
self.port = port
|
|
42
|
+
self.endpoint = endpoint
|
|
43
|
+
self.task_manager = task_manager
|
|
44
|
+
self.agent_card = agent_card
|
|
45
|
+
self.app = Starlette()
|
|
46
|
+
self.app.add_route(self.endpoint, self._process_request, methods=["POST"])
|
|
47
|
+
self.app.add_route(
|
|
48
|
+
"/.well-known/agent.json", self._get_agent_card, methods=["GET"]
|
|
49
|
+
)
|
|
50
|
+
# TODO: Remove this when we have a proper CORS policy
|
|
51
|
+
self.app.add_middleware(
|
|
52
|
+
CORSMiddleware,
|
|
53
|
+
allow_origins=["*"],
|
|
54
|
+
allow_credentials=True,
|
|
55
|
+
allow_methods=["*"],
|
|
56
|
+
allow_headers=["*"],
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
def start(self):
|
|
60
|
+
if self.agent_card is None:
|
|
61
|
+
raise ValueError("agent_card is not defined")
|
|
62
|
+
|
|
63
|
+
if self.task_manager is None:
|
|
64
|
+
raise ValueError("request_handler is not defined")
|
|
65
|
+
|
|
66
|
+
import uvicorn
|
|
67
|
+
|
|
68
|
+
# Configure uvicorn with optimized settings for streaming
|
|
69
|
+
uvicorn.run(
|
|
70
|
+
self.app,
|
|
71
|
+
host=self.host,
|
|
72
|
+
port=self.port,
|
|
73
|
+
http="h11",
|
|
74
|
+
timeout_keep_alive=65,
|
|
75
|
+
log_level="info"
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
def _get_agent_card(self, request: Request) -> JSONResponse:
|
|
79
|
+
return JSONResponse(self.agent_card.model_dump(exclude_none=True))
|
|
80
|
+
|
|
81
|
+
async def _process_request(self, request: Request):
|
|
82
|
+
try:
|
|
83
|
+
body = await request.json()
|
|
84
|
+
json_rpc_request = A2ARequest.validate_python(body)
|
|
85
|
+
|
|
86
|
+
if isinstance(json_rpc_request, GetTaskRequest):
|
|
87
|
+
result = await self.task_manager.on_get_task(json_rpc_request)
|
|
88
|
+
elif isinstance(json_rpc_request, SendTaskRequest):
|
|
89
|
+
result = await self.task_manager.on_send_task(json_rpc_request)
|
|
90
|
+
elif isinstance(json_rpc_request, SendTaskStreamingRequest):
|
|
91
|
+
# Don't await the async generator, just pass it to _create_response
|
|
92
|
+
result = self.task_manager.on_send_task_subscribe(
|
|
93
|
+
json_rpc_request
|
|
94
|
+
)
|
|
95
|
+
elif isinstance(json_rpc_request, CancelTaskRequest):
|
|
96
|
+
result = await self.task_manager.on_cancel_task(json_rpc_request)
|
|
97
|
+
elif isinstance(json_rpc_request, SetTaskPushNotificationRequest):
|
|
98
|
+
result = await self.task_manager.on_set_task_push_notification(
|
|
99
|
+
json_rpc_request
|
|
100
|
+
)
|
|
101
|
+
elif isinstance(json_rpc_request, GetTaskPushNotificationRequest):
|
|
102
|
+
result = await self.task_manager.on_get_task_push_notification(
|
|
103
|
+
json_rpc_request
|
|
104
|
+
)
|
|
105
|
+
elif isinstance(json_rpc_request, TaskResubscriptionRequest):
|
|
106
|
+
result = await self.task_manager.on_resubscribe_to_task(
|
|
107
|
+
json_rpc_request
|
|
108
|
+
)
|
|
109
|
+
else:
|
|
110
|
+
logger.warning(f"Unexpected request type: {type(json_rpc_request)}")
|
|
111
|
+
raise ValueError(f"Unexpected request type: {type(request)}")
|
|
112
|
+
|
|
113
|
+
return self._create_response(result)
|
|
114
|
+
|
|
115
|
+
except Exception as e:
|
|
116
|
+
return self._handle_exception(e)
|
|
117
|
+
|
|
118
|
+
def _handle_exception(self, e: Exception) -> JSONResponse:
|
|
119
|
+
if isinstance(e, json.decoder.JSONDecodeError):
|
|
120
|
+
json_rpc_error = JSONParseError()
|
|
121
|
+
elif isinstance(e, ValidationError):
|
|
122
|
+
json_rpc_error = InvalidRequestError(data=json.loads(e.json()))
|
|
123
|
+
else:
|
|
124
|
+
logger.error(f"Unhandled exception: {e}")
|
|
125
|
+
json_rpc_error = InternalError()
|
|
126
|
+
|
|
127
|
+
response = JSONRPCResponse(id=None, error=json_rpc_error)
|
|
128
|
+
return JSONResponse(response.model_dump(exclude_none=True), status_code=400)
|
|
129
|
+
|
|
130
|
+
def _create_response(self, result: Any) -> JSONResponse | EventSourceResponse:
|
|
131
|
+
if isinstance(result, AsyncIterable):
|
|
132
|
+
|
|
133
|
+
async def event_generator(result) -> AsyncIterable[dict[str, str]]:
|
|
134
|
+
async for item in result:
|
|
135
|
+
# Send the data event with immediate flush directive
|
|
136
|
+
yield {
|
|
137
|
+
"data": item.model_dump_json(exclude_none=True),
|
|
138
|
+
"event": "message",
|
|
139
|
+
"id": str(id(item)), # Add a unique ID for each event
|
|
140
|
+
}
|
|
141
|
+
# Add an empty comment event to force flush
|
|
142
|
+
yield {
|
|
143
|
+
"comment": " ", # Empty comment event to force flush
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
# Create EventSourceResponse with complete headers for browser compatibility
|
|
147
|
+
return EventSourceResponse(
|
|
148
|
+
event_generator(result),
|
|
149
|
+
# Complete set of headers needed for browser streaming
|
|
150
|
+
headers={
|
|
151
|
+
"Cache-Control": "no-cache, no-transform",
|
|
152
|
+
"X-Accel-Buffering": "no",
|
|
153
|
+
"Connection": "keep-alive",
|
|
154
|
+
"Content-Type": "text/event-stream",
|
|
155
|
+
"Transfer-Encoding": "chunked"
|
|
156
|
+
},
|
|
157
|
+
# Explicitly set media_type
|
|
158
|
+
media_type="text/event-stream"
|
|
159
|
+
)
|
|
160
|
+
elif isinstance(result, JSONRPCResponse):
|
|
161
|
+
return JSONResponse(result.model_dump(exclude_none=True))
|
|
162
|
+
else:
|
|
163
|
+
logger.error(f"Unexpected result type: {type(result)}")
|
|
164
|
+
raise ValueError(f"Unexpected result type: {type(result)}")
|