dg-dataagent 0.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.
- dataagent/__init__.py +37 -0
- dataagent/__main__.py +16 -0
- dataagent/a2a_server/__init__.py +25 -0
- dataagent/a2a_server/agent_card.py +85 -0
- dataagent/a2a_server/agent_executor.py +310 -0
- dataagent/a2a_server/server.py +169 -0
- dataagent/a2a_server/task_store.py +29 -0
- dataagent/actions/__init__.py +12 -0
- dataagent/actions/environment/__init__.py +21 -0
- dataagent/actions/environment/compound_env.py +89 -0
- dataagent/actions/environment/env.py +52 -0
- dataagent/actions/environment/env_config.py +101 -0
- dataagent/actions/gym/__init__.py +32 -0
- dataagent/actions/gym/arithmetic/arithmetic.py +124 -0
- dataagent/actions/gym/changping/changping_env.py +480 -0
- dataagent/actions/gym/icbc/icbc_env.py +800 -0
- dataagent/actions/gym/manufacturing/manufacturing_env.py +534 -0
- dataagent/actions/gym/nl2sql/base_env.py +444 -0
- dataagent/actions/gym/nl2sql/sqlite_env.py +79 -0
- dataagent/actions/gym/ontology_env.py +1310 -0
- dataagent/actions/perceptor/perceptor_atomic.py +671 -0
- dataagent/actions/perceptor/perceptor_utils.py +31 -0
- dataagent/actions/skills/__init__.py +12 -0
- dataagent/actions/skills/data_analysis_report/SKILL.md +95 -0
- dataagent/actions/skills/ontology_service/SKILL.md +65 -0
- dataagent/actions/skills/ontology_service/__init__.py +12 -0
- dataagent/actions/skills/ontology_service/references/commands.md +204 -0
- dataagent/actions/skills/ontology_service/references/public-api.md +66 -0
- dataagent/actions/skills/ontology_service/scripts/__init__.py +12 -0
- dataagent/actions/skills/ontology_service/scripts/cli_utils.py +75 -0
- dataagent/actions/skills/ontology_service/scripts/describe_ontology.py +39 -0
- dataagent/actions/skills/ontology_service/scripts/entity_lookup.py +77 -0
- dataagent/actions/skills/ontology_service/scripts/ontology_cli.py +1067 -0
- dataagent/actions/skills/ontology_service/scripts/ontology_client.py +533 -0
- dataagent/actions/tools/__init__.py +36 -0
- dataagent/actions/tools/a2a.py +472 -0
- dataagent/actions/tools/backfill.py +126 -0
- dataagent/actions/tools/concurrency.py +114 -0
- dataagent/actions/tools/context.py +49 -0
- dataagent/actions/tools/hooks/__init__.py +34 -0
- dataagent/actions/tools/hooks/base.py +123 -0
- dataagent/actions/tools/hooks/config.py +73 -0
- dataagent/actions/tools/hooks/examples/__init__.py +17 -0
- dataagent/actions/tools/hooks/examples/example_hooks.py +95 -0
- dataagent/actions/tools/local.py +172 -0
- dataagent/actions/tools/local_tool/agent_status_handler.py +221 -0
- dataagent/actions/tools/local_tool/document_recall.py +300 -0
- dataagent/actions/tools/local_tool/plan.py +166 -0
- dataagent/actions/tools/local_tool/sandbox.py +655 -0
- dataagent/actions/tools/local_tool/sql_reader.py +36 -0
- dataagent/actions/tools/local_tool/sub_agent_entry.py +255 -0
- dataagent/actions/tools/local_tool/tools.py +2756 -0
- dataagent/actions/tools/mcp.py +486 -0
- dataagent/actions/tools/mcp_examples.py +231 -0
- dataagent/actions/tools/mcp_tool/nl2sql.py +62 -0
- dataagent/actions/tools/schema_validator.py +438 -0
- dataagent/actions/tools/semantic_tool/get_join_relations.py +108 -0
- dataagent/actions/tools/semantic_tool/get_table_desc.py +35 -0
- dataagent/actions/tools/semantic_tool/search_metric_instance.py +991 -0
- dataagent/actions/tools/semantic_tool/search_tables_with_schema.py +588 -0
- dataagent/actions/tools/semantic_tool/search_udf_functions.py +641 -0
- dataagent/actions/tools/utils.py +65 -0
- dataagent/agents/document_recall/document_recall_agent.yaml +109 -0
- dataagent/agents/document_recall/tools.py +141 -0
- dataagent/agents/galatea/__init__.py +13 -0
- dataagent/agents/galatea/actions/__init__.py +38 -0
- dataagent/agents/galatea/actions/bash.py +59 -0
- dataagent/agents/galatea/actions/create_subagent.py +70 -0
- dataagent/agents/galatea/actions/edit.py +90 -0
- dataagent/agents/galatea/actions/inspect.py +73 -0
- dataagent/agents/galatea/actions/read.py +66 -0
- dataagent/agents/galatea/actions/search_online.py +80 -0
- dataagent/agents/galatea/actions/write.py +45 -0
- dataagent/agents/galatea/agent.py +194 -0
- dataagent/agents/galatea/core/__init__.py +19 -0
- dataagent/agents/galatea/graph/__init__.py +12 -0
- dataagent/agents/galatea/graph/executor.py +122 -0
- dataagent/agents/galatea/graph/planner.py +125 -0
- dataagent/agents/galatea/graph/router.py +139 -0
- dataagent/agents/galatea/graph/rule.py +35 -0
- dataagent/agents/galatea/hooks/__init__.py +12 -0
- dataagent/agents/galatea/hooks/metadata_tracker.py +210 -0
- dataagent/agents/galatea/hooks/portraiter.py +150 -0
- dataagent/agents/galatea/hooks/pruner.py +84 -0
- dataagent/agents/galatea/hooks/streamer.py +141 -0
- dataagent/agents/galatea/prompts/planner/system.md +42 -0
- dataagent/agents/galatea/prompts/planner/user.md +7 -0
- dataagent/agents/galatea/state/__init__.py +12 -0
- dataagent/agents/galatea/state/state.py +22 -0
- dataagent/agents/galatea/utils/__init__.py +12 -0
- dataagent/agents/galatea/utils/history_utils.py +173 -0
- dataagent/agents/galatea/utils/json_store.py +40 -0
- dataagent/agents/galatea/utils/metadata_utils.py +148 -0
- dataagent/agents/galatea/utils/portraiter_utils.py +114 -0
- dataagent/agents/galatea/utils/workspace_utils.py +67 -0
- dataagent/agents/nl2sql/__init__.py +3 -0
- dataagent/agents/nl2sql/agent.py +178 -0
- dataagent/agents/nl2sql/errors.py +98 -0
- dataagent/agents/nl2sql/nl2sql_agent.yaml +41 -0
- dataagent/agents/nl2sql/nodes/__init__.py +31 -0
- dataagent/agents/nl2sql/nodes/base_nl2sql_node.py +70 -0
- dataagent/agents/nl2sql/nodes/coordinator.py +34 -0
- dataagent/agents/nl2sql/nodes/executor.py +54 -0
- dataagent/agents/nl2sql/nodes/generator.py +94 -0
- dataagent/agents/nl2sql/nodes/perceptor.py +199 -0
- dataagent/agents/nl2sql/nodes/reflector.py +59 -0
- dataagent/agents/nl2sql/nodes/selector.py +74 -0
- dataagent/agents/nl2sql/nodes/validator.py +172 -0
- dataagent/agents/nl2sql/prompts/coordinator/system.md +30 -0
- dataagent/agents/nl2sql/prompts/coordinator/user.md +7 -0
- dataagent/agents/nl2sql/prompts/generator/dc_system.md +44 -0
- dataagent/agents/nl2sql/prompts/generator/dc_user.md +12 -0
- dataagent/agents/nl2sql/prompts/generator/icl_system.md +33 -0
- dataagent/agents/nl2sql/prompts/generator/icl_user.md +18 -0
- dataagent/agents/nl2sql/prompts/generator/prompt_system.md +17 -0
- dataagent/agents/nl2sql/prompts/generator/prompt_user.md +15 -0
- dataagent/agents/nl2sql/prompts/generator/skeleton_system.md +46 -0
- dataagent/agents/nl2sql/prompts/generator/skeleton_user.md +10 -0
- dataagent/agents/nl2sql/prompts/perceptor/filter_udn_table_system.md +32 -0
- dataagent/agents/nl2sql/prompts/perceptor/filter_udn_table_user.md +8 -0
- dataagent/agents/nl2sql/prompts/reflector/system.md +29 -0
- dataagent/agents/nl2sql/prompts/reflector/user.md +7 -0
- dataagent/agents/nl2sql/prompts/selector/system.md +30 -0
- dataagent/agents/nl2sql/prompts/selector/user.md +13 -0
- dataagent/agents/nl2sql/prompts/user/sql_rules_bird.md +6 -0
- dataagent/agents/nl2sql/prompts/user/sql_rules_udn.md +71 -0
- dataagent/agents/nl2sql/prompts/user/sql_rules_zdy.md +3 -0
- dataagent/agents/nl2sql/prompts/validator/extract_columns_system.md +30 -0
- dataagent/agents/nl2sql/prompts/validator/extract_columns_user.md +3 -0
- dataagent/agents/nl2sql/prompts/validator/validate_semantic_system.md +30 -0
- dataagent/agents/nl2sql/prompts/validator/validate_semantic_user.md +18 -0
- dataagent/agents/nl2sql/udn.yaml +33 -0
- dataagent/agents/nl2sql/utils/metavisor_client.py +123 -0
- dataagent/agents/nl2sql/utils/nl2sql_utils.py +232 -0
- dataagent/agents/nl2sql/utils/sql_service.py +310 -0
- dataagent/agents/nl2sql/workflow/router.py +63 -0
- dataagent/agents/nl2sql/workflow/state.py +104 -0
- dataagent/common_utils/knowledge_base/knowledge_base.py +405 -0
- dataagent/common_utils/knowledge_base/memory.py +1338 -0
- dataagent/common_utils/knowledge_base/metadata_management.py +1129 -0
- dataagent/common_utils/knowledge_base/tool_management.py +331 -0
- dataagent/common_utils/knowledge_base/utils_common.py +869 -0
- dataagent/common_utils/knowledge_base/utils_inference.py +80 -0
- dataagent/common_utils/knowledge_base/utils_knowledgebase.py +119 -0
- dataagent/common_utils/knowledge_base/utils_memory.py +322 -0
- dataagent/common_utils/knowledge_base/utils_metadata.py +267 -0
- dataagent/common_utils/storer_utils.py +113 -0
- dataagent/config/__init__.py +35 -0
- dataagent/config/config_manager.py +343 -0
- dataagent/core/__init__.py +30 -0
- dataagent/core/cbb/__init__.py +17 -0
- dataagent/core/cbb/agent_env.py +63 -0
- dataagent/core/cbb/base_agent.py +142 -0
- dataagent/core/cbb/base_hook.py +36 -0
- dataagent/core/cbb/base_node.py +148 -0
- dataagent/core/cbb/base_router.py +81 -0
- dataagent/core/cbb/base_state.py +19 -0
- dataagent/core/cbb/galatea_base_agent.py +114 -0
- dataagent/core/cbb/module.py +27 -0
- dataagent/core/cbb/runtime.py +327 -0
- dataagent/core/cbb/runtime_env.py +577 -0
- dataagent/core/context/contextIR.py +617 -0
- dataagent/core/context/context_trajectory.py +1129 -0
- dataagent/core/context/flex_context_formatting.py +179 -0
- dataagent/core/context/message_history.py +207 -0
- dataagent/core/context/todolist_manager.py +202 -0
- dataagent/core/context/util.py +113 -0
- dataagent/core/context/utils_context_storage.py +320 -0
- dataagent/core/context/utils_context_trajectory.py +323 -0
- dataagent/core/flex/agent.py +982 -0
- dataagent/core/flex/examples/arithmetic.yaml +29 -0
- dataagent/core/flex/examples/changping.yaml +87 -0
- dataagent/core/flex/examples/deep_analyze.yaml +7 -0
- dataagent/core/flex/examples/ecommerce_agent.yaml +55 -0
- dataagent/core/flex/examples/example.yaml +292 -0
- dataagent/core/flex/examples/icbc.yaml +69 -0
- dataagent/core/flex/examples/manufacturing.yaml +92 -0
- dataagent/core/flex/examples/nl2sql_flex_e2e_subagent.yaml +44 -0
- dataagent/core/flex/examples/ontology_sub_agent.yaml +42 -0
- dataagent/core/flex/examples/quickstart.yaml +15 -0
- dataagent/core/flex/examples/test_custom_prompt.yaml +65 -0
- dataagent/core/flex/examples/test_skills.yaml +36 -0
- dataagent/core/flex/examples/test_subagent_tool.yaml +40 -0
- dataagent/core/flex/examples/ueg.yaml +35 -0
- dataagent/core/flex/examples/yunhe.yaml +21 -0
- dataagent/core/flex/flex_default_configs.yaml +60 -0
- dataagent/core/flex/flex_runtime_from_config.py +330 -0
- dataagent/core/flex/hooks/__init__.py +15 -0
- dataagent/core/flex/hooks/agent_turn.py +54 -0
- dataagent/core/flex/hooks/cross_session_recall.py +200 -0
- dataagent/core/flex/hooks/history_writer.py +54 -0
- dataagent/core/flex/hooks/memory_indexer.py +128 -0
- dataagent/core/flex/hooks/metadata_tracker.py +263 -0
- dataagent/core/flex/hooks/organize_workspace.py +49 -0
- dataagent/core/flex/hooks/portraiter.py +346 -0
- dataagent/core/flex/hooks/pruner.py +84 -0
- dataagent/core/flex/hooks/registry.py +45 -0
- dataagent/core/flex/nodes/executor.py +1242 -0
- dataagent/core/flex/nodes/human_feedback.py +405 -0
- dataagent/core/flex/nodes/planner.py +435 -0
- dataagent/core/flex/utils/__init__.py +13 -0
- dataagent/core/flex/utils/context_from_state.py +68 -0
- dataagent/core/flex/utils/planner_prompt_builder.py +848 -0
- dataagent/core/flex/workflow/router.py +212 -0
- dataagent/core/flex/workflow/state.py +44 -0
- dataagent/core/framework_adapters/checkpoints/__init__.py +9 -0
- dataagent/core/framework_adapters/checkpoints/postgres_store.py +110 -0
- dataagent/core/framework_adapters/checkpoints/sqlite_store.py +124 -0
- dataagent/core/framework_adapters/checkpoints/types.py +24 -0
- dataagent/core/framework_adapters/runtime/__init__.py +38 -0
- dataagent/core/framework_adapters/runtime/context.py +373 -0
- dataagent/core/framework_adapters/runtime/workflow.py +176 -0
- dataagent/core/framework_adapters/runtime/workflow_backend.py +208 -0
- dataagent/core/framework_adapters/runtime/workflow_backend_factory.py +53 -0
- dataagent/core/framework_adapters/runtime/workflow_openjiuwen.py +707 -0
- dataagent/core/interface.py +101 -0
- dataagent/core/managers/action_manager/__init__.py +35 -0
- dataagent/core/managers/action_manager/base.py +220 -0
- dataagent/core/managers/action_manager/manager.py +1071 -0
- dataagent/core/managers/action_manager/registry.py +81 -0
- dataagent/core/managers/action_manager/schemas.py +322 -0
- dataagent/core/managers/galatea_action_manager.py +251 -0
- dataagent/core/managers/llm_manager/__init__.py +50 -0
- dataagent/core/managers/llm_manager/adapters.py +610 -0
- dataagent/core/managers/llm_manager/galatea_llm.py +46 -0
- dataagent/core/managers/llm_manager/llm_client.py +889 -0
- dataagent/core/managers/llm_manager/llm_config.py +82 -0
- dataagent/core/managers/llm_manager/llm_manager.py +115 -0
- dataagent/core/managers/prompt_manager/__init__.py +31 -0
- dataagent/core/managers/prompt_manager/template.py +120 -0
- dataagent/core/managers/prompt_manager/templates/__init__.py +12 -0
- dataagent/core/managers/prompt_manager/templates/context/fold_messages.md +80 -0
- dataagent/core/managers/prompt_manager/templates/human_feedback/system.md +13 -0
- dataagent/core/managers/prompt_manager/templates/human_feedback/user.md +7 -0
- dataagent/core/managers/prompt_manager/templates/irmanager/system.md +1 -0
- dataagent/core/managers/prompt_manager/templates/irmanager/system_update_state.md +8 -0
- dataagent/core/managers/prompt_manager/templates/irmanager/user.md +21 -0
- dataagent/core/managers/prompt_manager/templates/irmanager/user_update_state.md +11 -0
- dataagent/core/managers/prompt_manager/templates/knowledge_base/community_parent_report.md +39 -0
- dataagent/core/managers/prompt_manager/templates/knowledge_base/community_report.md +39 -0
- dataagent/core/managers/prompt_manager/templates/knowledge_base/extract_column_relationships.md +15 -0
- dataagent/core/managers/prompt_manager/templates/knowledge_base/extract_columns.md +14 -0
- dataagent/core/managers/prompt_manager/templates/knowledge_base/extract_tool_relationships.md +20 -0
- dataagent/core/managers/prompt_manager/templates/knowledge_base/extract_virtual_columns.md +18 -0
- dataagent/core/managers/prompt_manager/templates/knowledge_base/infer_data_type.md +24 -0
- dataagent/core/managers/prompt_manager/templates/knowledge_base/infer_file_description.md +22 -0
- dataagent/core/managers/prompt_manager/templates/knowledge_base/infer_schema_description.md +18 -0
- dataagent/core/managers/prompt_manager/templates/knowledge_base/update_column_description.md +28 -0
- dataagent/core/managers/prompt_manager/templates/knowledge_base/user_guide_kb.md +11 -0
- dataagent/core/managers/prompt_manager/templates/nl2sql_react/system.md +17 -0
- dataagent/core/managers/prompt_manager/templates/nl2sql_react/user.md +1 -0
- dataagent/core/managers/prompt_manager/templates/perceptor/keyword_extract_system.md +34 -0
- dataagent/core/managers/prompt_manager/templates/perceptor/keyword_extract_user.md +7 -0
- dataagent/core/managers/prompt_manager/templates/planner/system.md +128 -0
- dataagent/core/managers/prompt_manager/templates/planner/todo.md +38 -0
- dataagent/core/managers/prompt_manager/templates/planner/user.md +44 -0
- dataagent/core/managers/prompt_manager/templates/skill_selector/system.md +118 -0
- dataagent/core/managers/prompt_manager/templates/skill_selector/user.md +15 -0
- dataagent/core/swarm/__init__.py +15 -0
- dataagent/core/swarm/swarm_config.py +65 -0
- dataagent/core/swarm/worker_io.py +27 -0
- dataagent/core/swarm/worker_lock.py +105 -0
- dataagent/core/swarm/worker_memory.py +133 -0
- dataagent/core/swarm/worker_metadata.py +247 -0
- dataagent/core/swarm/worker_result.py +255 -0
- dataagent/core/utils/__init__.py +13 -0
- dataagent/core/utils/performance.py +597 -0
- dataagent/interface/cli/__init__.py +17 -0
- dataagent/interface/cli/main.py +640 -0
- dataagent/interface/rest_api/__init__.py +22 -0
- dataagent/interface/rest_api/app.py +110 -0
- dataagent/interface/rest_api/service.py +242 -0
- dataagent/interface/rest_api/start_service.py +52 -0
- dataagent/interface/sdk/__init__.py +24 -0
- dataagent/interface/sdk/agent.py +309 -0
- dataagent/interface/sdk/base_data_agent.py +425 -0
- dataagent/interface/sdk/builder.py +410 -0
- dataagent/interface/sdk/loader.py +34 -0
- dataagent/utils/__init__.py +12 -0
- dataagent/utils/builder_utils.py +368 -0
- dataagent/utils/cli/rich_renderer.py +889 -0
- dataagent/utils/cli/terminal_input.py +132 -0
- dataagent/utils/compression_utils.py +237 -0
- dataagent/utils/constants.py +472 -0
- dataagent/utils/converter/__init__.py +21 -0
- dataagent/utils/converter/graph_summary.py +230 -0
- dataagent/utils/converter/ir_converter_constants.py +188 -0
- dataagent/utils/converter/ir_message_consumer.py +220 -0
- dataagent/utils/converter/result_ir_converter.py +747 -0
- dataagent/utils/dag_utils.py +145 -0
- dataagent/utils/env_file_loader.py +255 -0
- dataagent/utils/env_utils.py +35 -0
- dataagent/utils/fix_md_image_path.py +300 -0
- dataagent/utils/formatting_utils.py +525 -0
- dataagent/utils/import_utils.py +154 -0
- dataagent/utils/info_utils.py +31 -0
- dataagent/utils/log/__init__.py +62 -0
- dataagent/utils/log/config.py +54 -0
- dataagent/utils/log/configs/default.yaml +49 -0
- dataagent/utils/log/configs/dev.yaml +18 -0
- dataagent/utils/log/configs/prod.yaml +19 -0
- dataagent/utils/log/dataagent_logger.py +311 -0
- dataagent/utils/messages_utils.py +390 -0
- dataagent/utils/parsing_utils.py +403 -0
- dataagent/utils/runtime_paths.py +167 -0
- dg_dataagent-0.1.0.dist-info/METADATA +273 -0
- dg_dataagent-0.1.0.dist-info/RECORD +310 -0
- dg_dataagent-0.1.0.dist-info/WHEEL +5 -0
- dg_dataagent-0.1.0.dist-info/licenses/LICENSE +201 -0
- dg_dataagent-0.1.0.dist-info/top_level.txt +1 -0
dataagent/__init__.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
2
|
+
# you may not use this file except in compliance with the License.
|
|
3
|
+
# You may obtain a copy of the License at
|
|
4
|
+
#
|
|
5
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
#
|
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
9
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
10
|
+
# See the License for the specific language governing permissions and
|
|
11
|
+
# limitations under the License.
|
|
12
|
+
# ============================================================================
|
|
13
|
+
"""dataagent 包入口。
|
|
14
|
+
|
|
15
|
+
注意:这里保持轻量,避免在 import dataagent 时就触发大量依赖初始化/循环导入。
|
|
16
|
+
需要 DataAgent 等对象时,通过属性懒加载获取。
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
__version__ = "0.1.0"
|
|
22
|
+
__author__ = "DataAgent Team"
|
|
23
|
+
|
|
24
|
+
from typing import Any
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def __getattr__(name: str) -> Any:
|
|
28
|
+
if name in ("DataAgent", "AgentBuilder", "load_agent_from_config", "BaseDataAgent"):
|
|
29
|
+
from dataagent.interface.sdk import AgentBuilder, BaseDataAgent, DataAgent, load_agent_from_config
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
"DataAgent": DataAgent,
|
|
33
|
+
"AgentBuilder": AgentBuilder,
|
|
34
|
+
"BaseDataAgent": BaseDataAgent,
|
|
35
|
+
"load_agent_from_config": load_agent_from_config,
|
|
36
|
+
}[name]
|
|
37
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
dataagent/__main__.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
2
|
+
# you may not use this file except in compliance with the License.
|
|
3
|
+
# You may obtain a copy of the License at
|
|
4
|
+
#
|
|
5
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
#
|
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
9
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
10
|
+
# See the License for the specific language governing permissions and
|
|
11
|
+
# limitations under the License.
|
|
12
|
+
# ============================================================================
|
|
13
|
+
from dataagent.interface.cli.main import main
|
|
14
|
+
|
|
15
|
+
if __name__ == "__main__":
|
|
16
|
+
main()
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
2
|
+
# you may not use this file except in compliance with the License.
|
|
3
|
+
# You may obtain a copy of the License at
|
|
4
|
+
#
|
|
5
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
#
|
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
9
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
10
|
+
# See the License for the specific language governing permissions and
|
|
11
|
+
# limitations under the License.
|
|
12
|
+
# ============================================================================
|
|
13
|
+
|
|
14
|
+
"""DataAgent A2A 1.0 Server — exposes DataAgent agents as A2A-compliant services."""
|
|
15
|
+
|
|
16
|
+
from dataagent.a2a_server.agent_card import build_agent_card
|
|
17
|
+
from dataagent.a2a_server.agent_executor import DataAgentExecutor
|
|
18
|
+
from dataagent.a2a_server.server import create_a2a_server, run_a2a_server
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"DataAgentExecutor",
|
|
22
|
+
"build_agent_card",
|
|
23
|
+
"create_a2a_server",
|
|
24
|
+
"run_a2a_server",
|
|
25
|
+
]
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
2
|
+
# you may not use this file except in compliance with the License.
|
|
3
|
+
# You may obtain a copy of the License at
|
|
4
|
+
#
|
|
5
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
#
|
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
9
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
10
|
+
# See the License for the specific language governing permissions and
|
|
11
|
+
# limitations under the License.
|
|
12
|
+
# ============================================================================
|
|
13
|
+
"""Build A2A 1.0 AgentCard from a DataAgent."""
|
|
14
|
+
|
|
15
|
+
from a2a.types.a2a_pb2 import (
|
|
16
|
+
AgentCapabilities,
|
|
17
|
+
AgentCard,
|
|
18
|
+
AgentInterface,
|
|
19
|
+
AgentSkill,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
from dataagent.interface.sdk.agent import DataAgent
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def build_agent_card(
|
|
26
|
+
agent: DataAgent,
|
|
27
|
+
host: str = "0.0.0.0",
|
|
28
|
+
port: int = 9999,
|
|
29
|
+
jsonrpc_path: str = "/a2a/jsonrpc",
|
|
30
|
+
rest_path: str = "/a2a/rest",
|
|
31
|
+
) -> AgentCard:
|
|
32
|
+
"""Create an A2A 1.0 AgentCard for the given DataAgent.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
agent: The DataAgent instance to expose.
|
|
36
|
+
host: Server host for the interface URLs.
|
|
37
|
+
port: Server port for the interface URLs.
|
|
38
|
+
jsonrpc_path: JSON-RPC route path.
|
|
39
|
+
rest_path: REST route path.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
A2A 1.0 AgentCard with supported_interfaces.
|
|
43
|
+
"""
|
|
44
|
+
agent_name = agent.name() or "DataAgent"
|
|
45
|
+
agent_desc = agent.description() or "DataAgent data analysis agent"
|
|
46
|
+
agent_ver = agent.version() or "0.1.0"
|
|
47
|
+
|
|
48
|
+
# AgentCard URL should use 127.0.0.1 for local access (host may be 0.0.0.0 for binding)
|
|
49
|
+
card_host = "127.0.0.1" if host in ("0.0.0.0", "::") else host
|
|
50
|
+
base_url = f"http://{card_host}:{port}"
|
|
51
|
+
|
|
52
|
+
# Build skills from agent info and known capabilities
|
|
53
|
+
skills = [
|
|
54
|
+
AgentSkill(
|
|
55
|
+
id="chat",
|
|
56
|
+
name="Chat",
|
|
57
|
+
description="Interactive conversational data analysis",
|
|
58
|
+
tags=["data-analysis", "chat"],
|
|
59
|
+
input_modes=["text/plain"],
|
|
60
|
+
output_modes=["text/plain"],
|
|
61
|
+
),
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
card = AgentCard(
|
|
65
|
+
name=agent_name,
|
|
66
|
+
description=agent_desc,
|
|
67
|
+
version=agent_ver,
|
|
68
|
+
default_input_modes=["text/plain"],
|
|
69
|
+
default_output_modes=["text/plain"],
|
|
70
|
+
capabilities=AgentCapabilities(streaming=True),
|
|
71
|
+
skills=skills,
|
|
72
|
+
supported_interfaces=[
|
|
73
|
+
AgentInterface(
|
|
74
|
+
protocol_binding="JSONRPC",
|
|
75
|
+
url=f"{base_url}{jsonrpc_path}",
|
|
76
|
+
protocol_version="1.0",
|
|
77
|
+
),
|
|
78
|
+
AgentInterface(
|
|
79
|
+
protocol_binding="HTTP+JSON",
|
|
80
|
+
url=f"{base_url}{rest_path}",
|
|
81
|
+
protocol_version="1.0",
|
|
82
|
+
),
|
|
83
|
+
],
|
|
84
|
+
)
|
|
85
|
+
return card
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
2
|
+
# you may not use this file except in compliance with the License.
|
|
3
|
+
# You may obtain a copy of the License at
|
|
4
|
+
#
|
|
5
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
#
|
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
9
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
10
|
+
# See the License for the specific language governing permissions and
|
|
11
|
+
# limitations under the License.
|
|
12
|
+
# ============================================================================
|
|
13
|
+
"""DataAgentExecutor — bridges A2A 1.0 requests to DataAgent."""
|
|
14
|
+
|
|
15
|
+
import asyncio
|
|
16
|
+
import uuid
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
from a2a.helpers import (
|
|
20
|
+
new_task,
|
|
21
|
+
new_text_artifact,
|
|
22
|
+
new_text_message,
|
|
23
|
+
new_text_status_update_event,
|
|
24
|
+
)
|
|
25
|
+
from a2a.server.agent_execution import AgentExecutor, RequestContext
|
|
26
|
+
from a2a.server.events import EventQueue
|
|
27
|
+
from a2a.types.a2a_pb2 import (
|
|
28
|
+
Role,
|
|
29
|
+
TaskArtifactUpdateEvent,
|
|
30
|
+
TaskState,
|
|
31
|
+
TaskStatus,
|
|
32
|
+
TaskStatusUpdateEvent,
|
|
33
|
+
)
|
|
34
|
+
from loguru import logger
|
|
35
|
+
|
|
36
|
+
from dataagent.interface.sdk.agent import DataAgent
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class DataAgentExecutor(AgentExecutor):
|
|
40
|
+
"""Bridges A2A 1.0 protocol requests to DataAgent execution."""
|
|
41
|
+
|
|
42
|
+
def __init__(self, agent: DataAgent):
|
|
43
|
+
self._agent = agent
|
|
44
|
+
self._cancellation_events: dict[str, asyncio.Event] = {}
|
|
45
|
+
self._run_counters: dict[str, int] = {}
|
|
46
|
+
self._run_lock = asyncio.Lock()
|
|
47
|
+
self._session_locks: dict[str, asyncio.Lock] = {}
|
|
48
|
+
self._session_locks_guard = asyncio.Lock()
|
|
49
|
+
|
|
50
|
+
async def execute(
|
|
51
|
+
self,
|
|
52
|
+
context: RequestContext,
|
|
53
|
+
event_queue: EventQueue,
|
|
54
|
+
) -> None:
|
|
55
|
+
"""Handle an A2A request by invoking the DataAgent.
|
|
56
|
+
|
|
57
|
+
Publishes task status updates and artifacts to the event queue,
|
|
58
|
+
supporting both sync and streaming (SSE) responses.
|
|
59
|
+
"""
|
|
60
|
+
task = context.current_task or new_task(
|
|
61
|
+
task_id=context.task_id,
|
|
62
|
+
context_id=context.context_id,
|
|
63
|
+
state=TaskState.TASK_STATE_SUBMITTED,
|
|
64
|
+
)
|
|
65
|
+
await event_queue.enqueue_event(task)
|
|
66
|
+
|
|
67
|
+
user_text = _extract_text_from_context(context)
|
|
68
|
+
if not user_text:
|
|
69
|
+
await self._fail_with_message(event_queue, context, "No user message provided.")
|
|
70
|
+
return
|
|
71
|
+
|
|
72
|
+
cancel_event = asyncio.Event()
|
|
73
|
+
self._cancellation_events[context.task_id] = cancel_event
|
|
74
|
+
|
|
75
|
+
session_id = context.context_id or context.task_id or f"a2a-{uuid.uuid4().hex[:12]}"
|
|
76
|
+
session_lock = await self._get_session_lock(session_id)
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
async with session_lock:
|
|
80
|
+
await event_queue.enqueue_event(
|
|
81
|
+
new_text_status_update_event(
|
|
82
|
+
task_id=context.task_id,
|
|
83
|
+
context_id=context.context_id,
|
|
84
|
+
state=TaskState.TASK_STATE_WORKING,
|
|
85
|
+
text="Processing request...",
|
|
86
|
+
)
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
response = await self._execute_agent_chat(user_text, session_id, context.task_id)
|
|
90
|
+
|
|
91
|
+
if cancel_event.is_set():
|
|
92
|
+
await event_queue.enqueue_event(
|
|
93
|
+
new_text_status_update_event(
|
|
94
|
+
task_id=context.task_id,
|
|
95
|
+
context_id=context.context_id,
|
|
96
|
+
state=TaskState.TASK_STATE_CANCELED,
|
|
97
|
+
text="Task was canceled.",
|
|
98
|
+
)
|
|
99
|
+
)
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
result_text = _extract_final_answer(response)
|
|
103
|
+
|
|
104
|
+
has_error = self._has_error_response(response)
|
|
105
|
+
if has_error:
|
|
106
|
+
error_msg = self._extract_error_message(response)
|
|
107
|
+
logger.error(f"Agent execution failed: {error_msg}")
|
|
108
|
+
await event_queue.enqueue_event(
|
|
109
|
+
new_text_status_update_event(
|
|
110
|
+
task_id=context.task_id,
|
|
111
|
+
context_id=context.context_id,
|
|
112
|
+
state=TaskState.TASK_STATE_FAILED,
|
|
113
|
+
text=str(error_msg) if error_msg else "Agent execution failed",
|
|
114
|
+
)
|
|
115
|
+
)
|
|
116
|
+
return
|
|
117
|
+
|
|
118
|
+
logger.info("Agent execution completed successfully")
|
|
119
|
+
|
|
120
|
+
await event_queue.enqueue_event(
|
|
121
|
+
TaskArtifactUpdateEvent(
|
|
122
|
+
task_id=context.task_id,
|
|
123
|
+
context_id=context.context_id,
|
|
124
|
+
artifact=new_text_artifact(name="dataagent_result", text=result_text),
|
|
125
|
+
)
|
|
126
|
+
)
|
|
127
|
+
await event_queue.enqueue_event(
|
|
128
|
+
new_text_status_update_event(
|
|
129
|
+
task_id=context.task_id,
|
|
130
|
+
context_id=context.context_id,
|
|
131
|
+
state=TaskState.TASK_STATE_COMPLETED,
|
|
132
|
+
text=result_text,
|
|
133
|
+
)
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
except asyncio.CancelledError:
|
|
137
|
+
await event_queue.enqueue_event(
|
|
138
|
+
new_text_status_update_event(
|
|
139
|
+
task_id=context.task_id,
|
|
140
|
+
context_id=context.context_id,
|
|
141
|
+
state=TaskState.TASK_STATE_CANCELED,
|
|
142
|
+
text="Task was canceled.",
|
|
143
|
+
)
|
|
144
|
+
)
|
|
145
|
+
except Exception as e:
|
|
146
|
+
logger.error(f"DataAgent execution failed: {e}")
|
|
147
|
+
await event_queue.enqueue_event(
|
|
148
|
+
new_text_status_update_event(
|
|
149
|
+
task_id=context.task_id,
|
|
150
|
+
context_id=context.context_id,
|
|
151
|
+
state=TaskState.TASK_STATE_FAILED,
|
|
152
|
+
text=f"Error: {str(e)}",
|
|
153
|
+
)
|
|
154
|
+
)
|
|
155
|
+
finally:
|
|
156
|
+
self._cancellation_events.pop(context.task_id, None)
|
|
157
|
+
|
|
158
|
+
async def cancel(self, context: RequestContext, event_queue: EventQueue) -> None:
|
|
159
|
+
"""Request cancellation of an ongoing task."""
|
|
160
|
+
cancel_event = self._cancellation_events.get(context.task_id)
|
|
161
|
+
if cancel_event:
|
|
162
|
+
cancel_event.set()
|
|
163
|
+
await event_queue.enqueue_event(
|
|
164
|
+
new_text_status_update_event(
|
|
165
|
+
task_id=context.task_id,
|
|
166
|
+
context_id=context.context_id,
|
|
167
|
+
state=TaskState.TASK_STATE_CANCELED,
|
|
168
|
+
text="Task was canceled.",
|
|
169
|
+
)
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
async def _fail_with_message(
|
|
173
|
+
self,
|
|
174
|
+
event_queue: EventQueue,
|
|
175
|
+
context: RequestContext,
|
|
176
|
+
message: str,
|
|
177
|
+
) -> None:
|
|
178
|
+
"""Publish a failed status event with the given message."""
|
|
179
|
+
await event_queue.enqueue_event(
|
|
180
|
+
TaskStatusUpdateEvent(
|
|
181
|
+
task_id=context.task_id,
|
|
182
|
+
context_id=context.context_id,
|
|
183
|
+
status=TaskStatus(
|
|
184
|
+
state=TaskState.TASK_STATE_FAILED,
|
|
185
|
+
message=new_text_message(
|
|
186
|
+
text=message,
|
|
187
|
+
role=Role.ROLE_AGENT,
|
|
188
|
+
task_id=context.task_id,
|
|
189
|
+
context_id=context.context_id,
|
|
190
|
+
),
|
|
191
|
+
),
|
|
192
|
+
)
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
async def _execute_agent_chat(
|
|
196
|
+
self,
|
|
197
|
+
user_text: str,
|
|
198
|
+
session_id: str,
|
|
199
|
+
task_id: str,
|
|
200
|
+
) -> Any:
|
|
201
|
+
"""Acquire session lock, increment run counter, and execute agent chat."""
|
|
202
|
+
async with self._run_lock:
|
|
203
|
+
run_id = self._run_counters.get(session_id, 0)
|
|
204
|
+
self._run_counters[session_id] = run_id + 1
|
|
205
|
+
initial_state = {"session_id": session_id, "run_id": run_id}
|
|
206
|
+
|
|
207
|
+
response = await self._agent.chat(user_query=user_text, session_id=session_id, initial_state=initial_state)
|
|
208
|
+
|
|
209
|
+
logger.info(f"[DEBUG] _agent.chat() returned type: {type(response)}")
|
|
210
|
+
if isinstance(response, dict):
|
|
211
|
+
logger.info(f"[DEBUG] _agent.chat() response keys: {list(response.keys())}")
|
|
212
|
+
logger.info(f"[DEBUG] _agent.chat() response: {response}")
|
|
213
|
+
|
|
214
|
+
return response
|
|
215
|
+
|
|
216
|
+
def _has_error_response(self, response: Any) -> bool:
|
|
217
|
+
"""Check if the response contains an error.
|
|
218
|
+
|
|
219
|
+
Errors can be in two places:
|
|
220
|
+
1. Top-level: response.get("error") - from DataAgent.chat() exception handling
|
|
221
|
+
2. Nested: response["messages"][-1].additional_kwargs.get("error") - from FlexAgent planner
|
|
222
|
+
"""
|
|
223
|
+
if not isinstance(response, dict):
|
|
224
|
+
return False
|
|
225
|
+
|
|
226
|
+
if "error" in response:
|
|
227
|
+
return True
|
|
228
|
+
|
|
229
|
+
if response.get("messages"):
|
|
230
|
+
last_msg = response["messages"][-1]
|
|
231
|
+
if isinstance(last_msg, dict):
|
|
232
|
+
return bool(last_msg.get("additional_kwargs", {}).get("error"))
|
|
233
|
+
return bool(getattr(last_msg, "additional_kwargs", {}).get("error"))
|
|
234
|
+
|
|
235
|
+
return False
|
|
236
|
+
|
|
237
|
+
def _extract_error_message(self, response: Any) -> str:
|
|
238
|
+
"""Extract the error message from a response."""
|
|
239
|
+
if not isinstance(response, dict):
|
|
240
|
+
return str(response)
|
|
241
|
+
|
|
242
|
+
if "error" in response:
|
|
243
|
+
return str(response.get("error"))
|
|
244
|
+
|
|
245
|
+
if response.get("messages"):
|
|
246
|
+
last_msg = response["messages"][-1]
|
|
247
|
+
if isinstance(last_msg, dict):
|
|
248
|
+
return str(last_msg.get("content", str(response)))
|
|
249
|
+
return str(getattr(last_msg, "content", str(response)))
|
|
250
|
+
|
|
251
|
+
return str(response)
|
|
252
|
+
|
|
253
|
+
async def _get_session_lock(self, session_id: str) -> asyncio.Lock:
|
|
254
|
+
"""Get or create a per-session Lock for FIFO task serialization."""
|
|
255
|
+
async with self._session_locks_guard:
|
|
256
|
+
lock = self._session_locks.get(session_id)
|
|
257
|
+
if lock is None:
|
|
258
|
+
lock = asyncio.Lock()
|
|
259
|
+
self._session_locks[session_id] = lock
|
|
260
|
+
return lock
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def _extract_text_from_context(context: RequestContext) -> str:
|
|
264
|
+
"""Extract text content from the request context message."""
|
|
265
|
+
# RequestContext.get_user_input() already handles text extraction
|
|
266
|
+
user_text = context.get_user_input()
|
|
267
|
+
if user_text:
|
|
268
|
+
return user_text
|
|
269
|
+
|
|
270
|
+
# Fallback: extract from context.message directly
|
|
271
|
+
message = context.message
|
|
272
|
+
if message is None:
|
|
273
|
+
return ""
|
|
274
|
+
|
|
275
|
+
parts_text = []
|
|
276
|
+
for part in message.parts:
|
|
277
|
+
if part.text:
|
|
278
|
+
parts_text.append(part.text)
|
|
279
|
+
elif part.data:
|
|
280
|
+
try:
|
|
281
|
+
import json
|
|
282
|
+
|
|
283
|
+
from google.protobuf.json_format import MessageToDict
|
|
284
|
+
|
|
285
|
+
data_dict = MessageToDict(part.data)
|
|
286
|
+
parts_text.append(json.dumps(data_dict, ensure_ascii=False))
|
|
287
|
+
except Exception:
|
|
288
|
+
parts_text.append(str(part.data))
|
|
289
|
+
|
|
290
|
+
return "".join(parts_text).strip()
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def _extract_final_answer(response: Any) -> str:
|
|
294
|
+
"""Extract the final answer text from a DataAgent response."""
|
|
295
|
+
if isinstance(response, dict):
|
|
296
|
+
for key in ("final_answer", "answer", "result", "response"):
|
|
297
|
+
if key in response:
|
|
298
|
+
val = response[key]
|
|
299
|
+
if isinstance(val, str):
|
|
300
|
+
return val
|
|
301
|
+
return str(val)
|
|
302
|
+
|
|
303
|
+
messages = response.get("messages", [])
|
|
304
|
+
if messages:
|
|
305
|
+
last_msg = messages[-1]
|
|
306
|
+
content = getattr(last_msg, "content", str(last_msg))
|
|
307
|
+
return str(content)
|
|
308
|
+
|
|
309
|
+
return str(response)
|
|
310
|
+
return str(response)
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
2
|
+
# you may not use this file except in compliance with the License.
|
|
3
|
+
# You may obtain a copy of the License at
|
|
4
|
+
#
|
|
5
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
#
|
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
9
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
10
|
+
# See the License for the specific language governing permissions and
|
|
11
|
+
# limitations under the License.
|
|
12
|
+
# ============================================================================
|
|
13
|
+
"""Assemble and run an A2A 1.0 server for a DataAgent."""
|
|
14
|
+
|
|
15
|
+
from a2a.server.agent_execution import AgentExecutor
|
|
16
|
+
from a2a.server.request_handlers import DefaultRequestHandler
|
|
17
|
+
from a2a.server.request_handlers.response_helpers import agent_card_to_dict
|
|
18
|
+
from a2a.server.routes import (
|
|
19
|
+
create_jsonrpc_routes,
|
|
20
|
+
create_rest_routes,
|
|
21
|
+
)
|
|
22
|
+
from a2a.server.tasks import InMemoryTaskStore
|
|
23
|
+
from a2a.types.a2a_pb2 import AgentCard
|
|
24
|
+
from a2a.utils.constants import AGENT_CARD_WELL_KNOWN_PATH
|
|
25
|
+
from starlette.applications import Starlette
|
|
26
|
+
from starlette.middleware import Middleware
|
|
27
|
+
from starlette.requests import Request
|
|
28
|
+
from starlette.responses import JSONResponse
|
|
29
|
+
from starlette.routing import Route
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _make_bearer_middleware(token: str, exclude_paths: tuple[str, ...]):
|
|
33
|
+
"""Create a Bearer token authentication middleware."""
|
|
34
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
35
|
+
|
|
36
|
+
class BearerAuthMiddleware(BaseHTTPMiddleware):
|
|
37
|
+
"""Validate Bearer token on every request, skipping excluded paths."""
|
|
38
|
+
|
|
39
|
+
async def dispatch(self, request: Request, call_next):
|
|
40
|
+
"""Check Authorization header against the expected token.
|
|
41
|
+
|
|
42
|
+
Returns 401 if the token is missing or invalid, otherwise
|
|
43
|
+
forwards the request to the next middleware/route handler.
|
|
44
|
+
"""
|
|
45
|
+
if request.url.path in exclude_paths:
|
|
46
|
+
return await call_next(request)
|
|
47
|
+
|
|
48
|
+
auth = request.headers.get("Authorization", "")
|
|
49
|
+
if not auth.startswith("Bearer ") or auth[7:] != token:
|
|
50
|
+
return JSONResponse(
|
|
51
|
+
status_code=401,
|
|
52
|
+
content={"detail": "Unauthorized: invalid or missing Bearer token"},
|
|
53
|
+
)
|
|
54
|
+
return await call_next(request)
|
|
55
|
+
|
|
56
|
+
return Middleware(BearerAuthMiddleware)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _create_dynamic_agent_card_routes(
|
|
60
|
+
agent_card: AgentCard,
|
|
61
|
+
jsonrpc_path: str,
|
|
62
|
+
rest_path: str,
|
|
63
|
+
) -> list:
|
|
64
|
+
"""Create agent card routes that use the requestʼs Host header for interface URLs.
|
|
65
|
+
|
|
66
|
+
Clients from any network can discover the correct URLs instead of always
|
|
67
|
+
receiving 127.0.0.1.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
async def _get_agent_card(request: Request) -> JSONResponse:
|
|
71
|
+
base_card = agent_card_to_dict(agent_card)
|
|
72
|
+
host = request.headers.get("Host", "127.0.0.1:9999")
|
|
73
|
+
scheme = request.headers.get("X-Forwarded-Proto", request.url.scheme or "http")
|
|
74
|
+
base_url = f"{scheme}://{host}"
|
|
75
|
+
|
|
76
|
+
for iface in base_card.get("supportedInterfaces", []):
|
|
77
|
+
protocol = iface.get("protocolBinding", "")
|
|
78
|
+
if protocol == "JSONRPC":
|
|
79
|
+
iface["url"] = f"{base_url}{jsonrpc_path}"
|
|
80
|
+
elif protocol == "HTTP+JSON":
|
|
81
|
+
iface["url"] = f"{base_url}{rest_path}"
|
|
82
|
+
|
|
83
|
+
return JSONResponse(base_card)
|
|
84
|
+
|
|
85
|
+
return [
|
|
86
|
+
Route(path=AGENT_CARD_WELL_KNOWN_PATH, endpoint=_get_agent_card, methods=["GET"]),
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def create_a2a_server(
|
|
91
|
+
agent_card: AgentCard,
|
|
92
|
+
executor: AgentExecutor,
|
|
93
|
+
jsonrpc_path: str = "/a2a/jsonrpc",
|
|
94
|
+
rest_path: str = "/a2a/rest",
|
|
95
|
+
enable_v0_3_compat: bool = False,
|
|
96
|
+
auth_token: str | None = None,
|
|
97
|
+
auth_exclude_paths: tuple[str, ...] = ("/.well-known/agent-card.json",),
|
|
98
|
+
) -> Starlette:
|
|
99
|
+
"""Create a Starlette application with A2A 1.0 routes.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
agent_card: The A2A AgentCard for this server.
|
|
103
|
+
executor: The AgentExecutor that handles requests.
|
|
104
|
+
jsonrpc_path: JSON-RPC route path (default: /a2a/jsonrpc).
|
|
105
|
+
rest_path: REST route path (default: /a2a/rest).
|
|
106
|
+
enable_v0_3_compat: Enable backward compatibility with A2A v0.3 clients.
|
|
107
|
+
auth_token: Bearer token for authentication. If None, no auth is required.
|
|
108
|
+
auth_exclude_paths: Paths to exclude from auth checks (default: AgentCard discovery).
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
A configured Starlette application.
|
|
112
|
+
"""
|
|
113
|
+
task_store = InMemoryTaskStore()
|
|
114
|
+
|
|
115
|
+
request_handler = DefaultRequestHandler(
|
|
116
|
+
agent_executor=executor,
|
|
117
|
+
task_store=task_store,
|
|
118
|
+
agent_card=agent_card,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
routes: list = []
|
|
122
|
+
routes.extend(_create_dynamic_agent_card_routes(agent_card, jsonrpc_path, rest_path))
|
|
123
|
+
routes.extend(create_jsonrpc_routes(request_handler, rpc_url=jsonrpc_path, enable_v0_3_compat=enable_v0_3_compat))
|
|
124
|
+
routes.extend(create_rest_routes(request_handler, path_prefix=rest_path, enable_v0_3_compat=enable_v0_3_compat))
|
|
125
|
+
|
|
126
|
+
middleware: list = []
|
|
127
|
+
if auth_token:
|
|
128
|
+
middleware.append(_make_bearer_middleware(auth_token, exclude_paths=auth_exclude_paths))
|
|
129
|
+
|
|
130
|
+
app = Starlette(routes=routes, middleware=middleware)
|
|
131
|
+
return app
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def run_a2a_server(
|
|
135
|
+
agent_card: AgentCard,
|
|
136
|
+
executor: AgentExecutor,
|
|
137
|
+
host: str = "",
|
|
138
|
+
port: int = 9999,
|
|
139
|
+
jsonrpc_path: str = "/a2a/jsonrpc",
|
|
140
|
+
rest_path: str = "/a2a/rest",
|
|
141
|
+
auth_token: str | None = None,
|
|
142
|
+
auth_exclude_paths: tuple[str, ...] = ("/.well-known/agent-card.json",),
|
|
143
|
+
) -> None:
|
|
144
|
+
"""Run the A2A server using uvicorn.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
agent_card: The A2A AgentCard.
|
|
148
|
+
executor: The AgentExecutor.
|
|
149
|
+
host: Server host.
|
|
150
|
+
port: Server port.
|
|
151
|
+
jsonrpc_path: JSON-RPC route path.
|
|
152
|
+
rest_path: REST route path.
|
|
153
|
+
auth_token: Bearer token for authentication. If None, no auth is required.
|
|
154
|
+
auth_exclude_paths: Paths excluded from auth checks.
|
|
155
|
+
"""
|
|
156
|
+
import uvicorn
|
|
157
|
+
|
|
158
|
+
if not host:
|
|
159
|
+
raise ValueError("Host must be specified")
|
|
160
|
+
app = create_a2a_server(
|
|
161
|
+
agent_card=agent_card,
|
|
162
|
+
executor=executor,
|
|
163
|
+
jsonrpc_path=jsonrpc_path,
|
|
164
|
+
rest_path=rest_path,
|
|
165
|
+
auth_token=auth_token,
|
|
166
|
+
auth_exclude_paths=auth_exclude_paths,
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
uvicorn.run(app, host=host, port=port, log_level="info")
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
2
|
+
# you may not use this file except in compliance with the License.
|
|
3
|
+
# You may obtain a copy of the License at
|
|
4
|
+
#
|
|
5
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
#
|
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
9
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
10
|
+
# See the License for the specific language governing permissions and
|
|
11
|
+
# limitations under the License.
|
|
12
|
+
# ============================================================================
|
|
13
|
+
"""Task store utilities for the A2A server.
|
|
14
|
+
|
|
15
|
+
For MVP we use the in-memory store from a2a-sdk.
|
|
16
|
+
For production, a PostgreSQL-backed store can be implemented here
|
|
17
|
+
using a2a-sdk[sql] extras.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from a2a.server.tasks import InMemoryTaskStore
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def create_task_store() -> InMemoryTaskStore:
|
|
24
|
+
"""Create an in-memory task store (MVP).
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
An InMemoryTaskStore instance.
|
|
28
|
+
"""
|
|
29
|
+
return InMemoryTaskStore()
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
2
|
+
# you may not use this file except in compliance with the License.
|
|
3
|
+
# You may obtain a copy of the License at
|
|
4
|
+
#
|
|
5
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
#
|
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
9
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
10
|
+
# See the License for the specific language governing permissions and
|
|
11
|
+
# limitations under the License.
|
|
12
|
+
# ============================================================================
|