dao-ai 0.1.5__py3-none-any.whl → 0.1.6__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.
dao_ai/cli.py CHANGED
@@ -7,7 +7,7 @@ import sys
7
7
  import traceback
8
8
  from argparse import ArgumentParser, Namespace
9
9
  from pathlib import Path
10
- from typing import Optional, Sequence
10
+ from typing import Any, Optional, Sequence
11
11
 
12
12
  from dotenv import find_dotenv, load_dotenv
13
13
  from loguru import logger
@@ -114,6 +114,7 @@ Examples:
114
114
  dao-ai validate -c config/model_config.yaml # Validate a specific configuration file
115
115
  dao-ai graph -o architecture.png -c my_config.yaml -v # Generate visual graph with verbose output
116
116
  dao-ai chat -c config/retail.yaml --custom-input store_num=87887 # Start interactive chat session
117
+ dao-ai list-mcp-tools -c config/mcp_config.yaml --apply-filters # List filtered MCP tools only
117
118
  dao-ai validate # Validate with detailed logging
118
119
  dao-ai bundle --deploy # Deploy the DAO AI asset bundle
119
120
  """,
@@ -309,6 +310,53 @@ Examples:
309
310
  help="Path to the model configuration file to validate",
310
311
  )
311
312
 
313
+ # List MCP tools command
314
+ list_mcp_parser: ArgumentParser = subparsers.add_parser(
315
+ "list-mcp-tools",
316
+ help="List available MCP tools from configuration",
317
+ description="""
318
+ List all available MCP tools from the configured MCP servers.
319
+ This command shows:
320
+ - All MCP servers/functions in the configuration
321
+ - Available tools from each server
322
+ - Full descriptions for each tool (no truncation)
323
+ - Tool parameters in readable format (type, required/optional, descriptions)
324
+ - Which tools are included/excluded based on filters
325
+ - Filter patterns (include_tools, exclude_tools)
326
+
327
+ Use this command to:
328
+ - Discover available tools before configuring agents
329
+ - Review tool descriptions and parameter schemas
330
+ - Debug tool filtering configuration
331
+ - Verify MCP server connectivity
332
+
333
+ Options:
334
+ - Use --apply-filters to only show tools that will be loaded (hides excluded tools)
335
+ - Without --apply-filters, see all available tools with include/exclude status
336
+
337
+ Note: Schemas are displayed in a concise, readable format instead of verbose JSON
338
+ """,
339
+ epilog="""Examples:
340
+ dao-ai list-mcp-tools -c config/model_config.yaml
341
+ dao-ai list-mcp-tools -c config/model_config.yaml --apply-filters
342
+ """,
343
+ formatter_class=argparse.RawDescriptionHelpFormatter,
344
+ )
345
+ list_mcp_parser.add_argument(
346
+ "-c",
347
+ "--config",
348
+ type=str,
349
+ default="./config/model_config.yaml",
350
+ required=False,
351
+ metavar="FILE",
352
+ help="Path to the model configuration file (default: ./config/model_config.yaml)",
353
+ )
354
+ list_mcp_parser.add_argument(
355
+ "--apply-filters",
356
+ action="store_true",
357
+ help="Only show tools that pass include/exclude filters (hide excluded tools)",
358
+ )
359
+
312
360
  chat_parser: ArgumentParser = subparsers.add_parser(
313
361
  "chat",
314
362
  help="Interactive chat with the DAO AI system",
@@ -704,6 +752,275 @@ def handle_validate_command(options: Namespace) -> None:
704
752
  sys.exit(1)
705
753
 
706
754
 
755
+ def _format_schema_pretty(schema: dict[str, Any], indent: int = 0) -> str:
756
+ """
757
+ Format a JSON schema in a more readable, concise format.
758
+
759
+ Args:
760
+ schema: The JSON schema to format
761
+ indent: Current indentation level
762
+
763
+ Returns:
764
+ Pretty-formatted schema string
765
+ """
766
+ if not schema:
767
+ return ""
768
+
769
+ lines: list[str] = []
770
+ indent_str = " " * indent
771
+
772
+ # Get required fields
773
+ required_fields = set(schema.get("required", []))
774
+
775
+ # Handle object type with properties
776
+ if schema.get("type") == "object" and "properties" in schema:
777
+ properties = schema["properties"]
778
+
779
+ for prop_name, prop_schema in properties.items():
780
+ is_required = prop_name in required_fields
781
+ req_marker = " (required)" if is_required else " (optional)"
782
+
783
+ prop_type = prop_schema.get("type", "any")
784
+ prop_desc = prop_schema.get("description", "")
785
+
786
+ # Handle different types
787
+ if prop_type == "array":
788
+ items = prop_schema.get("items", {})
789
+ item_type = items.get("type", "any")
790
+ type_str = f"array<{item_type}>"
791
+ elif prop_type == "object":
792
+ type_str = "object"
793
+ else:
794
+ type_str = prop_type
795
+
796
+ # Format enum values if present
797
+ if "enum" in prop_schema:
798
+ enum_values = ", ".join(str(v) for v in prop_schema["enum"])
799
+ type_str = f"{type_str} (one of: {enum_values})"
800
+
801
+ # Build the line
802
+ line = f"{indent_str}{prop_name}: {type_str}{req_marker}"
803
+ if prop_desc:
804
+ line += f"\n{indent_str} └─ {prop_desc}"
805
+
806
+ lines.append(line)
807
+
808
+ # Recursively handle nested objects
809
+ if prop_type == "object" and "properties" in prop_schema:
810
+ nested = _format_schema_pretty(prop_schema, indent + 1)
811
+ if nested:
812
+ lines.append(nested)
813
+
814
+ # Handle simple types without properties
815
+ elif "type" in schema:
816
+ schema_type = schema["type"]
817
+ if schema.get("description"):
818
+ lines.append(f"{indent_str}Type: {schema_type}")
819
+ lines.append(f"{indent_str}└─ {schema['description']}")
820
+ else:
821
+ lines.append(f"{indent_str}Type: {schema_type}")
822
+
823
+ return "\n".join(lines)
824
+
825
+
826
+ def handle_list_mcp_tools_command(options: Namespace) -> None:
827
+ """
828
+ List available MCP tools from configuration.
829
+
830
+ Shows all MCP servers and their available tools, indicating which
831
+ are included/excluded based on filter configuration.
832
+ """
833
+ logger.debug(f"Listing MCP tools from configuration: {options.config}")
834
+
835
+ try:
836
+ from dao_ai.config import McpFunctionModel
837
+ from dao_ai.tools.mcp import MCPToolInfo, _matches_pattern, list_mcp_tools
838
+
839
+ # Load configuration
840
+ config: AppConfig = AppConfig.from_file(options.config)
841
+
842
+ # Find all MCP tools in configuration
843
+ mcp_tools_config: list[tuple[str, McpFunctionModel]] = []
844
+ if config.tools:
845
+ for tool_name, tool_model in config.tools.items():
846
+ logger.debug(
847
+ f"Checking tool: {tool_name}, function type: {type(tool_model.function)}"
848
+ )
849
+ if tool_model.function and isinstance(
850
+ tool_model.function, McpFunctionModel
851
+ ):
852
+ mcp_tools_config.append((tool_name, tool_model.function))
853
+
854
+ if not mcp_tools_config:
855
+ logger.warning("No MCP tools found in configuration")
856
+ print("\n⚠️ No MCP tools configured in this file.")
857
+ print(f" Configuration: {options.config}")
858
+ print(
859
+ "\nTo add MCP tools, define them in the 'tools' section with 'type: mcp'"
860
+ )
861
+ sys.exit(0)
862
+
863
+ # Collect all results first (aggregate before displaying)
864
+ results: list[dict[str, Any]] = []
865
+ for tool_name, mcp_function in mcp_tools_config:
866
+ result = {
867
+ "tool_name": tool_name,
868
+ "mcp_function": mcp_function,
869
+ "error": None,
870
+ "all_tools": [],
871
+ "included_tools": [],
872
+ "excluded_tools": [],
873
+ }
874
+
875
+ try:
876
+ logger.info(f"Connecting to MCP server: {mcp_function.mcp_url}")
877
+
878
+ # Get all available tools (unfiltered)
879
+ all_tools: list[MCPToolInfo] = list_mcp_tools(
880
+ mcp_function, apply_filters=False
881
+ )
882
+
883
+ # Get filtered tools (what will actually be loaded)
884
+ filtered_tools: list[MCPToolInfo] = list_mcp_tools(
885
+ mcp_function, apply_filters=True
886
+ )
887
+
888
+ included_names = {t.name for t in filtered_tools}
889
+
890
+ # Categorize tools
891
+ for tool in sorted(all_tools, key=lambda t: t.name):
892
+ if tool.name in included_names:
893
+ result["included_tools"].append(tool)
894
+ else:
895
+ # Determine why it was excluded
896
+ reason = ""
897
+ if mcp_function.exclude_tools:
898
+ if _matches_pattern(tool.name, mcp_function.exclude_tools):
899
+ matching_patterns = [
900
+ p
901
+ for p in mcp_function.exclude_tools
902
+ if _matches_pattern(tool.name, [p])
903
+ ]
904
+ reason = f" (matches exclude pattern: {', '.join(matching_patterns)})"
905
+ if not reason and mcp_function.include_tools:
906
+ reason = " (not in include list)"
907
+ result["excluded_tools"].append((tool, reason))
908
+
909
+ result["all_tools"] = all_tools
910
+
911
+ except KeyboardInterrupt:
912
+ result["error"] = "Connection interrupted by user"
913
+ results.append(result)
914
+ break
915
+ except Exception as e:
916
+ logger.error(f"Failed to list tools from MCP server: {e}")
917
+ result["error"] = str(e)
918
+
919
+ results.append(result)
920
+
921
+ # Now display all results at once (no logging interleaving)
922
+ print(f"\n{'=' * 80}")
923
+ print("MCP TOOLS DISCOVERY")
924
+ print(f"Configuration: {options.config}")
925
+ print(f"{'=' * 80}\n")
926
+
927
+ for result in results:
928
+ tool_name = result["tool_name"]
929
+ mcp_function = result["mcp_function"]
930
+
931
+ print(f"📦 Tool: {tool_name}")
932
+ print(f" Server: {mcp_function.mcp_url}")
933
+
934
+ # Show connection type
935
+ if mcp_function.connection:
936
+ print(f" Connection: UC Connection '{mcp_function.connection.name}'")
937
+ else:
938
+ print(f" Transport: {mcp_function.transport.value}")
939
+
940
+ # Show filters if configured
941
+ if mcp_function.include_tools or mcp_function.exclude_tools:
942
+ print("\n Filters:")
943
+ if mcp_function.include_tools:
944
+ print(f" Include: {', '.join(mcp_function.include_tools)}")
945
+ if mcp_function.exclude_tools:
946
+ print(f" Exclude: {', '.join(mcp_function.exclude_tools)}")
947
+
948
+ # Check for errors
949
+ if result["error"]:
950
+ print(f"\n ❌ Error: {result['error']}")
951
+ print(" Could not connect to MCP server")
952
+ if result["error"] != "Connection interrupted by user":
953
+ print(
954
+ " Tip: Verify server URL, authentication, and network connectivity"
955
+ )
956
+ else:
957
+ all_tools = result["all_tools"]
958
+ included_tools = result["included_tools"]
959
+ excluded_tools = result["excluded_tools"]
960
+
961
+ # Show stats based on --apply-filters flag
962
+ if options.apply_filters:
963
+ # Simplified view: only show filtered tools count
964
+ print(
965
+ f"\n Available Tools: {len(included_tools)} (after filters)"
966
+ )
967
+ else:
968
+ # Full view: show all, included, and excluded counts
969
+ print(f"\n Available Tools: {len(all_tools)} total")
970
+ print(f" ├─ ✓ Included: {len(included_tools)}")
971
+ print(f" └─ ✗ Excluded: {len(excluded_tools)}")
972
+
973
+ # Show included tools with FULL descriptions and schemas
974
+ if included_tools:
975
+ if options.apply_filters:
976
+ print(f"\n Tools ({len(included_tools)}):")
977
+ else:
978
+ print(f"\n ✓ Included Tools ({len(included_tools)}):")
979
+
980
+ for tool in included_tools:
981
+ print(f"\n • {tool.name}")
982
+ if tool.description:
983
+ # Show full description (no truncation)
984
+ print(f" Description: {tool.description}")
985
+ if tool.input_schema:
986
+ # Pretty print schema in readable format
987
+ print(" Parameters:")
988
+ pretty_schema = _format_schema_pretty(
989
+ tool.input_schema, indent=0
990
+ )
991
+ if pretty_schema:
992
+ # Indent the schema for better readability
993
+ for line in pretty_schema.split("\n"):
994
+ print(f" {line}")
995
+ else:
996
+ print(" (none)")
997
+
998
+ # Show excluded tools only if NOT applying filters
999
+ if excluded_tools and not options.apply_filters:
1000
+ print(f"\n ✗ Excluded Tools ({len(excluded_tools)}):")
1001
+ for tool, reason in excluded_tools:
1002
+ print(f" • {tool.name}{reason}")
1003
+
1004
+ print(f"\n{'-' * 80}\n")
1005
+
1006
+ # Summary
1007
+ print(f"{'=' * 80}")
1008
+ print(f"Summary: Found {len(mcp_tools_config)} MCP server(s)")
1009
+ print(f"{'=' * 80}\n")
1010
+
1011
+ sys.exit(0)
1012
+
1013
+ except FileNotFoundError:
1014
+ logger.error(f"Configuration file not found: {options.config}")
1015
+ print(f"\n❌ Error: Configuration file not found: {options.config}")
1016
+ sys.exit(1)
1017
+ except Exception as e:
1018
+ logger.error(f"Failed to list MCP tools: {e}")
1019
+ logger.debug(traceback.format_exc())
1020
+ print(f"\n❌ Error: {e}")
1021
+ sys.exit(1)
1022
+
1023
+
707
1024
  def setup_logging(verbosity: int) -> None:
708
1025
  levels: dict[int, str] = {
709
1026
  0: "ERROR",
@@ -925,6 +1242,8 @@ def main() -> None:
925
1242
  handle_deploy_command(options)
926
1243
  case "chat":
927
1244
  handle_chat_command(options)
1245
+ case "list-mcp-tools":
1246
+ handle_list_mcp_tools_command(options)
928
1247
  case _:
929
1248
  logger.error(f"Unknown command: {options.command}")
930
1249
  sys.exit(1)
dao_ai/config.py CHANGED
@@ -391,10 +391,17 @@ class PermissionModel(BaseModel):
391
391
 
392
392
  class SchemaModel(BaseModel, HasFullName):
393
393
  model_config = ConfigDict(use_enum_values=True, extra="forbid")
394
- catalog_name: str
395
- schema_name: str
394
+ catalog_name: AnyVariable
395
+ schema_name: AnyVariable
396
396
  permissions: Optional[list[PermissionModel]] = Field(default_factory=list)
397
397
 
398
+ @model_validator(mode="after")
399
+ def resolve_variables(self) -> Self:
400
+ """Resolve AnyVariable fields to their actual string values."""
401
+ self.catalog_name = value_of(self.catalog_name)
402
+ self.schema_name = value_of(self.schema_name)
403
+ return self
404
+
398
405
  @property
399
406
  def full_name(self) -> str:
400
407
  return f"{self.catalog_name}.{self.schema_name}"
@@ -410,7 +417,13 @@ class SchemaModel(BaseModel, HasFullName):
410
417
  class DatabricksAppModel(IsDatabricksResource, HasFullName):
411
418
  model_config = ConfigDict(use_enum_values=True, extra="forbid")
412
419
  name: str
413
- url: str
420
+ url: AnyVariable
421
+
422
+ @model_validator(mode="after")
423
+ def resolve_variables(self) -> Self:
424
+ """Resolve AnyVariable fields to their actual string values."""
425
+ self.url = value_of(self.url)
426
+ return self
414
427
 
415
428
  @property
416
429
  def full_name(self) -> str:
@@ -1845,6 +1858,26 @@ class McpFunctionModel(BaseFunctionModel, IsDatabricksResource):
1845
1858
  genie_room: Optional[GenieRoomModel] = None
1846
1859
  sql: Optional[bool] = None
1847
1860
  vector_search: Optional[VectorStoreModel] = None
1861
+ # Tool filtering
1862
+ include_tools: Optional[list[str]] = Field(
1863
+ default=None,
1864
+ description=(
1865
+ "Optional list of tool names or glob patterns to include from the MCP server. "
1866
+ "If specified, only tools matching these patterns will be loaded. "
1867
+ "Supports glob patterns: * (any chars), ? (single char), [abc] (char set). "
1868
+ "Examples: ['execute_query', 'list_*', 'get_?_data']"
1869
+ ),
1870
+ )
1871
+ exclude_tools: Optional[list[str]] = Field(
1872
+ default=None,
1873
+ description=(
1874
+ "Optional list of tool names or glob patterns to exclude from the MCP server. "
1875
+ "Tools matching these patterns will not be loaded. "
1876
+ "Takes precedence over include_tools. "
1877
+ "Supports glob patterns: * (any chars), ? (single char), [abc] (char set). "
1878
+ "Examples: ['drop_*', 'delete_*', 'execute_ddl']"
1879
+ ),
1880
+ )
1848
1881
 
1849
1882
  @property
1850
1883
  def api_scopes(self) -> Sequence[str]:
@@ -2020,6 +2053,26 @@ class McpFunctionModel(BaseFunctionModel, IsDatabricksResource):
2020
2053
  self.headers[key] = value_of(value)
2021
2054
  return self
2022
2055
 
2056
+ @model_validator(mode="after")
2057
+ def validate_tool_filters(self) -> "McpFunctionModel":
2058
+ """Validate tool filter configuration."""
2059
+ from loguru import logger
2060
+
2061
+ # Warn if both are empty lists (explicit but pointless)
2062
+ if self.include_tools is not None and len(self.include_tools) == 0:
2063
+ logger.warning(
2064
+ "include_tools is empty list - no tools will be loaded. "
2065
+ "Remove field to load all tools."
2066
+ )
2067
+
2068
+ if self.exclude_tools is not None and len(self.exclude_tools) == 0:
2069
+ logger.warning(
2070
+ "exclude_tools is empty list - has no effect. "
2071
+ "Remove field or add patterns."
2072
+ )
2073
+
2074
+ return self
2075
+
2023
2076
  def as_tools(self, **kwargs: Any) -> Sequence[RunnableLike]:
2024
2077
  from dao_ai.tools import create_mcp_tools
2025
2078
 
@@ -6,6 +6,7 @@ from langchain.agents.middleware import (
6
6
  ClearToolUsesEdit,
7
7
  ContextEditingMiddleware,
8
8
  HumanInTheLoopMiddleware,
9
+ LLMToolSelectorMiddleware,
9
10
  ModelCallLimitMiddleware,
10
11
  ModelRetryMiddleware,
11
12
  PIIMiddleware,
@@ -82,6 +83,7 @@ from dao_ai.middleware.summarization import (
82
83
  )
83
84
  from dao_ai.middleware.tool_call_limit import create_tool_call_limit_middleware
84
85
  from dao_ai.middleware.tool_retry import create_tool_retry_middleware
86
+ from dao_ai.middleware.tool_selector import create_llm_tool_selector_middleware
85
87
 
86
88
  __all__ = [
87
89
  # Base class (from LangChain)
@@ -105,6 +107,7 @@ __all__ = [
105
107
  "ModelCallLimitMiddleware",
106
108
  "ToolRetryMiddleware",
107
109
  "ModelRetryMiddleware",
110
+ "LLMToolSelectorMiddleware",
108
111
  "ContextEditingMiddleware",
109
112
  "ClearToolUsesEdit",
110
113
  "PIIMiddleware",
@@ -150,6 +153,8 @@ __all__ = [
150
153
  "create_model_call_limit_middleware",
151
154
  "create_tool_retry_middleware",
152
155
  "create_model_retry_middleware",
156
+ # Tool selection middleware factory functions
157
+ "create_llm_tool_selector_middleware",
153
158
  # Context editing middleware factory functions
154
159
  "create_context_editing_middleware",
155
160
  "create_clear_tool_uses_edit",
@@ -0,0 +1,129 @@
1
+ """
2
+ Tool selector middleware for intelligently filtering tools before LLM calls.
3
+
4
+ This middleware uses an LLM to select relevant tools from a large set, improving
5
+ performance and accuracy by reducing context size and improving focus.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Any
11
+
12
+ from langchain.agents.middleware import LLMToolSelectorMiddleware
13
+ from langchain_core.language_models import LanguageModelLike
14
+ from loguru import logger
15
+
16
+ from dao_ai.config import ToolModel
17
+
18
+
19
+ def create_llm_tool_selector_middleware(
20
+ model: LanguageModelLike,
21
+ max_tools: int = 3,
22
+ always_include: list[str | ToolModel | dict[str, Any]] | None = None,
23
+ ) -> LLMToolSelectorMiddleware:
24
+ """
25
+ Create an LLMToolSelectorMiddleware for intelligent tool selection.
26
+
27
+ Uses an LLM to analyze the current query and select the most relevant tools
28
+ before calling the main model. This is particularly useful for agents with
29
+ many tools (10+) where most aren't relevant for any given query.
30
+
31
+ Benefits:
32
+ - Reduces token usage by filtering irrelevant tools
33
+ - Improves model focus and accuracy
34
+ - Optimizes cost for agents with large tool sets
35
+ - Maintains context window efficiency
36
+
37
+ Args:
38
+ model: The LLM to use for tool selection. Typically a smaller, faster
39
+ model like "gpt-4o-mini" or similar.
40
+ max_tools: Maximum number of tools to select for each query.
41
+ Default 3. Adjust based on your use case - higher values
42
+ increase context but improve tool coverage.
43
+ always_include: List of tools that should always be included regardless
44
+ of the LLM's selection. Can be:
45
+ - str: Tool name
46
+ - ToolModel: Full tool configuration
47
+ - dict: Tool configuration dictionary
48
+ Use this for critical tools that should always be available.
49
+
50
+ Returns:
51
+ LLMToolSelectorMiddleware configured with the specified parameters
52
+
53
+ Example:
54
+ from dao_ai.middleware import create_llm_tool_selector_middleware
55
+ from dao_ai.llms import create_llm
56
+
57
+ # Use a fast, cheap model for tool selection
58
+ selector_llm = create_llm("databricks-gpt-4o-mini")
59
+
60
+ middleware = create_llm_tool_selector_middleware(
61
+ model=selector_llm,
62
+ max_tools=3,
63
+ always_include=["search_web"], # Always include search
64
+ )
65
+
66
+ Use Cases:
67
+ - Large tool sets (10+ tools) where most are specialized
68
+ - Cost optimization by reducing tokens in main model calls
69
+ - Improved accuracy by reducing tool confusion
70
+ - Dynamic tool filtering based on query relevance
71
+
72
+ Note:
73
+ The selector model makes an additional LLM call for each agent turn.
74
+ Choose a fast, inexpensive model to minimize latency and cost overhead.
75
+ """
76
+ # Extract tool names from always_include
77
+ always_include_names: list[str] = []
78
+ if always_include:
79
+ always_include_names = _resolve_tool_names(always_include)
80
+
81
+ logger.debug(
82
+ "Creating LLM tool selector middleware",
83
+ max_tools=max_tools,
84
+ always_include_count=len(always_include_names),
85
+ always_include=always_include_names,
86
+ )
87
+
88
+ return LLMToolSelectorMiddleware(
89
+ model=model,
90
+ max_tools=max_tools,
91
+ always_include=always_include_names if always_include_names else None,
92
+ )
93
+
94
+
95
+ def _resolve_tool_names(tools: list[str | ToolModel | dict[str, Any]]) -> list[str]:
96
+ """
97
+ Extract tool names from a list of tool specifications.
98
+
99
+ Args:
100
+ tools: List of tool specifications (strings, ToolModels, or dicts)
101
+
102
+ Returns:
103
+ List of tool names as strings
104
+ """
105
+ names: list[str] = []
106
+
107
+ for tool_spec in tools:
108
+ if isinstance(tool_spec, str):
109
+ # Simple string tool name
110
+ names.append(tool_spec)
111
+ elif isinstance(tool_spec, ToolModel):
112
+ # ToolModel - use its name
113
+ names.append(tool_spec.name)
114
+ elif isinstance(tool_spec, dict):
115
+ # Dictionary - try to extract name
116
+ if "name" in tool_spec:
117
+ names.append(tool_spec["name"])
118
+ else:
119
+ logger.warning(
120
+ "Tool dict missing 'name' field, skipping",
121
+ tool_spec=tool_spec,
122
+ )
123
+ else:
124
+ logger.warning(
125
+ "Unknown tool specification type, skipping",
126
+ tool_spec_type=type(tool_spec).__name__,
127
+ )
128
+
129
+ return names
dao_ai/prompts.py CHANGED
@@ -61,19 +61,14 @@ def make_prompt(
61
61
  @dynamic_prompt
62
62
  def dynamic_system_prompt(request: ModelRequest) -> str:
63
63
  """Generate dynamic system prompt based on runtime context."""
64
- # Get parameters from runtime context
64
+ # Initialize parameters for template variables
65
65
  params: dict[str, Any] = {
66
66
  input_variable: "" for input_variable in prompt_template.input_variables
67
67
  }
68
68
 
69
- # Access context from runtime
69
+ # Apply context fields as template parameters
70
70
  context: Context = request.runtime.context
71
71
  if context:
72
- if context.user_id and "user_id" in params:
73
- params["user_id"] = context.user_id
74
- if context.thread_id and "thread_id" in params:
75
- params["thread_id"] = context.thread_id
76
- # Apply all context fields as template parameters
77
72
  context_dict = context.model_dump()
78
73
  for key, value in context_dict.items():
79
74
  if key in params and value is not None:
@@ -89,56 +84,3 @@ def make_prompt(
89
84
  return formatted_prompt
90
85
 
91
86
  return dynamic_system_prompt
92
-
93
-
94
- def create_prompt_middleware(
95
- base_system_prompt: Optional[str | PromptModel],
96
- ) -> AgentMiddleware | None:
97
- """
98
- Create a dynamic prompt middleware from configuration.
99
-
100
- This always returns an AgentMiddleware suitable for use with
101
- LangChain v1's middleware system.
102
-
103
- Args:
104
- base_system_prompt: The system prompt string or PromptModel
105
-
106
- Returns:
107
- An AgentMiddleware created by @dynamic_prompt, or None if no prompt
108
- """
109
- if not base_system_prompt:
110
- return None
111
-
112
- # Extract template string from PromptModel or use string directly
113
- template_str: str
114
- if isinstance(base_system_prompt, PromptModel):
115
- template_str = base_system_prompt.template
116
- else:
117
- template_str = base_system_prompt
118
-
119
- prompt_template: PromptTemplate = PromptTemplate.from_template(template_str)
120
-
121
- @dynamic_prompt
122
- def prompt_middleware(request: ModelRequest) -> str:
123
- """Generate system prompt based on runtime context."""
124
- # Get parameters from runtime context
125
- params: dict[str, Any] = {
126
- input_variable: "" for input_variable in prompt_template.input_variables
127
- }
128
-
129
- # Access context from runtime
130
- context: Context = request.runtime.context
131
- if context:
132
- # Apply all context fields as template parameters
133
- context_dict = context.model_dump()
134
- for key, value in context_dict.items():
135
- if key in params and value is not None:
136
- params[key] = value
137
-
138
- # Format the prompt
139
- formatted_prompt: str = prompt_template.format(**params)
140
- logger.trace("Formatted dynamic prompt with context")
141
-
142
- return formatted_prompt
143
-
144
- return prompt_middleware
dao_ai/tools/__init__.py CHANGED
@@ -4,7 +4,7 @@ from dao_ai.tools.agent import create_agent_endpoint_tool
4
4
  from dao_ai.tools.core import create_tools, say_hello_tool
5
5
  from dao_ai.tools.email import create_send_email_tool
6
6
  from dao_ai.tools.genie import create_genie_tool
7
- from dao_ai.tools.mcp import create_mcp_tools
7
+ from dao_ai.tools.mcp import MCPToolInfo, create_mcp_tools, list_mcp_tools
8
8
  from dao_ai.tools.memory import create_search_memory_tool
9
9
  from dao_ai.tools.python import create_factory_tool, create_python_tool
10
10
  from dao_ai.tools.search import create_search_tool
@@ -30,6 +30,8 @@ __all__ = [
30
30
  "create_genie_tool",
31
31
  "create_hooks",
32
32
  "create_mcp_tools",
33
+ "list_mcp_tools",
34
+ "MCPToolInfo",
33
35
  "create_python_tool",
34
36
  "create_search_memory_tool",
35
37
  "create_search_tool",
dao_ai/tools/mcp.py CHANGED
@@ -7,10 +7,16 @@ MCP SDK and langchain-mcp-adapters library.
7
7
  For compatibility with Databricks APIs, we use manual tool wrappers
8
8
  that give us full control over the response format.
9
9
 
10
+ Public API:
11
+ - list_mcp_tools(): List available tools from an MCP server (for discovery/UI)
12
+ - create_mcp_tools(): Create LangChain tools for agent execution
13
+
10
14
  Reference: https://docs.langchain.com/oss/python/langchain/mcp
11
15
  """
12
16
 
13
17
  import asyncio
18
+ import fnmatch
19
+ from dataclasses import dataclass
14
20
  from typing import Any, Sequence
15
21
 
16
22
  from langchain_core.runnables.base import RunnableLike
@@ -26,6 +32,117 @@ from dao_ai.config import (
26
32
  )
27
33
 
28
34
 
35
+ @dataclass
36
+ class MCPToolInfo:
37
+ """
38
+ Information about an MCP tool for display and selection.
39
+
40
+ This is a simplified representation of an MCP tool that contains
41
+ only the information needed for UI display and tool selection.
42
+ It's designed to be easily serializable for use in web UIs.
43
+
44
+ Attributes:
45
+ name: The unique identifier/name of the tool
46
+ description: Human-readable description of what the tool does
47
+ input_schema: JSON Schema describing the tool's input parameters
48
+ """
49
+
50
+ name: str
51
+ description: str | None
52
+ input_schema: dict[str, Any]
53
+
54
+ def to_dict(self) -> dict[str, Any]:
55
+ """Convert to dictionary for JSON serialization."""
56
+ return {
57
+ "name": self.name,
58
+ "description": self.description,
59
+ "input_schema": self.input_schema,
60
+ }
61
+
62
+
63
+ def _matches_pattern(tool_name: str, patterns: list[str]) -> bool:
64
+ """
65
+ Check if tool name matches any of the provided patterns.
66
+
67
+ Supports glob patterns:
68
+ - * matches any characters
69
+ - ? matches single character
70
+ - [abc] matches any char in set
71
+ - [!abc] matches any char NOT in set
72
+
73
+ Args:
74
+ tool_name: Name of the tool to check
75
+ patterns: List of exact names or glob patterns
76
+
77
+ Returns:
78
+ True if tool name matches any pattern
79
+
80
+ Examples:
81
+ >>> _matches_pattern("query_sales", ["query_*"])
82
+ True
83
+ >>> _matches_pattern("list_tables", ["query_*"])
84
+ False
85
+ >>> _matches_pattern("tool_a", ["tool_?"])
86
+ True
87
+ """
88
+ for pattern in patterns:
89
+ if fnmatch.fnmatch(tool_name, pattern):
90
+ return True
91
+ return False
92
+
93
+
94
+ def _should_include_tool(
95
+ tool_name: str,
96
+ include_tools: list[str] | None,
97
+ exclude_tools: list[str] | None,
98
+ ) -> bool:
99
+ """
100
+ Determine if a tool should be included based on include/exclude filters.
101
+
102
+ Logic:
103
+ 1. If exclude_tools specified and tool matches: EXCLUDE (highest priority)
104
+ 2. If include_tools specified and tool matches: INCLUDE
105
+ 3. If include_tools specified and tool doesn't match: EXCLUDE
106
+ 4. If no filters specified: INCLUDE (default)
107
+
108
+ Args:
109
+ tool_name: Name of the tool
110
+ include_tools: Optional list of tools/patterns to include
111
+ exclude_tools: Optional list of tools/patterns to exclude
112
+
113
+ Returns:
114
+ True if tool should be included
115
+
116
+ Examples:
117
+ >>> _should_include_tool("query_sales", ["query_*"], None)
118
+ True
119
+ >>> _should_include_tool("drop_table", None, ["drop_*"])
120
+ False
121
+ >>> _should_include_tool("query_sales", ["query_*"], ["*_sales"])
122
+ False # exclude takes precedence
123
+ """
124
+ # Exclude has highest priority
125
+ if exclude_tools and _matches_pattern(tool_name, exclude_tools):
126
+ logger.debug("Tool excluded by exclude_tools", tool_name=tool_name)
127
+ return False
128
+
129
+ # If include list exists, tool must match it
130
+ if include_tools:
131
+ if _matches_pattern(tool_name, include_tools):
132
+ logger.debug("Tool included by include_tools", tool_name=tool_name)
133
+ return True
134
+ else:
135
+ logger.debug(
136
+ "Tool not in include_tools",
137
+ tool_name=tool_name,
138
+ include_patterns=include_tools,
139
+ )
140
+ return False
141
+
142
+ # Default: include all tools
143
+ return True
144
+
145
+
29
146
  def _build_connection_config(
30
147
  function: McpFunctionModel,
31
148
  ) -> dict[str, Any]:
@@ -124,69 +241,33 @@ def _extract_text_content(result: CallToolResult) -> str:
124
241
  return "\n".join(text_parts)
125
242
 
126
243
 
127
- def create_mcp_tools(
128
- function: McpFunctionModel,
129
- ) -> Sequence[RunnableLike]:
244
+ def _fetch_tools_from_server(function: McpFunctionModel) -> list[Tool]:
130
245
  """
131
- Create tools for invoking Databricks MCP functions.
246
+ Fetch raw MCP tools from the server.
132
247
 
133
- Supports both direct MCP connections and UC Connection-based MCP access.
134
- Uses manual tool wrappers to ensure response format compatibility with
135
- Databricks APIs (which reject extra fields in tool results).
136
-
137
- Based on: https://docs.databricks.com/aws/en/generative-ai/mcp/external-mcp
248
+ This is the core async operation that connects to the MCP server
249
+ and retrieves the list of available tools.
138
250
 
139
251
  Args:
140
252
  function: The MCP function model configuration.
141
253
 
142
254
  Returns:
143
- A sequence of LangChain tools that can be used by agents.
144
- """
145
- mcp_url = function.mcp_url
146
- logger.debug("Creating MCP tools", mcp_url=mcp_url)
255
+ List of raw MCP Tool objects from the server.
147
256
 
257
+ Raises:
258
+ RuntimeError: If connection to MCP server fails.
259
+ """
148
260
  connection_config = _build_connection_config(function)
149
-
150
- if function.connection:
151
- logger.debug(
152
- "Using UC Connection for MCP",
153
- connection_name=function.connection.name,
154
- mcp_url=mcp_url,
155
- )
156
- else:
157
- logger.debug(
158
- "Using direct connection for MCP",
159
- transport=function.transport,
160
- mcp_url=mcp_url,
161
- )
162
-
163
- # Create client to list available tools
164
261
  client = MultiServerMCPClient({"mcp_function": connection_config})
165
262
 
166
- async def _list_tools() -> list[Tool]:
167
- """List available MCP tools from the server."""
263
+ async def _list_tools_async() -> list[Tool]:
264
+ """Async helper to list tools from MCP server."""
168
265
  async with client.session("mcp_function") as session:
169
266
  result = await session.list_tools()
170
267
  return result.tools if hasattr(result, "tools") else list(result)
171
268
 
172
269
  try:
173
- mcp_tools: list[Tool] = asyncio.run(_list_tools())
174
-
175
- # Log discovered tools
176
- logger.info(
177
- "Discovered MCP tools",
178
- tools_count=len(mcp_tools),
179
- mcp_url=mcp_url,
180
- )
181
- for mcp_tool in mcp_tools:
182
- logger.debug(
183
- "MCP tool discovered",
184
- tool_name=mcp_tool.name,
185
- tool_description=(
186
- mcp_tool.description[:100] if mcp_tool.description else None
187
- ),
188
- )
189
-
270
+ return asyncio.run(_list_tools_async())
190
271
  except Exception as e:
191
272
  if function.connection:
192
273
  logger.error(
@@ -210,6 +291,216 @@ def create_mcp_tools(
210
291
  f"and URL '{function.url}': {e}"
211
292
  ) from e
212
293
 
294
+
295
+ def list_mcp_tools(
296
+ function: McpFunctionModel,
297
+ apply_filters: bool = True,
298
+ ) -> list[MCPToolInfo]:
299
+ """
300
+ List available tools from an MCP server.
301
+
302
+ This function connects to an MCP server and returns information about
303
+ all available tools. It's designed for:
304
+ - Tool discovery and exploration
305
+ - UI-based tool selection (e.g., in DAO AI Builder)
306
+ - Debugging and validation of MCP configurations
307
+
308
+ The returned MCPToolInfo objects contain all information needed to
309
+ display tools in a UI and allow users to select which tools to use.
310
+
311
+ Args:
312
+ function: The MCP function model configuration containing:
313
+ - Connection details (url, connection, headers, etc.)
314
+ - Optional filtering (include_tools, exclude_tools)
315
+ apply_filters: Whether to apply include_tools/exclude_tools filters.
316
+ Set to False to get the complete list of available tools
317
+ regardless of filter configuration. Default True.
318
+
319
+ Returns:
320
+ List of MCPToolInfo objects describing available tools.
321
+ Each contains name, description, and input_schema.
322
+
323
+ Raises:
324
+ RuntimeError: If connection to MCP server fails.
325
+
326
+ Example:
327
+ # List all tools from a DBSQL MCP server
328
+ from dao_ai.config import McpFunctionModel
329
+ from dao_ai.tools.mcp import list_mcp_tools
330
+
331
+ function = McpFunctionModel(sql=True)
332
+ tools = list_mcp_tools(function)
333
+
334
+ for tool in tools:
335
+ print(f"{tool.name}: {tool.description}")
336
+
337
+ # Get unfiltered list (ignore include_tools/exclude_tools)
338
+ all_tools = list_mcp_tools(function, apply_filters=False)
339
+
340
+ Note:
341
+ For creating executable LangChain tools, use create_mcp_tools() instead.
342
+ This function is for discovery/display purposes only.
343
+ """
344
+ mcp_url = function.mcp_url
345
+ logger.debug("Listing MCP tools", mcp_url=mcp_url, apply_filters=apply_filters)
346
+
347
+ # Log connection type
348
+ if function.connection:
349
+ logger.debug(
350
+ "Using UC Connection for MCP",
351
+ connection_name=function.connection.name,
352
+ mcp_url=mcp_url,
353
+ )
354
+ else:
355
+ logger.debug(
356
+ "Using direct connection for MCP",
357
+ transport=function.transport,
358
+ mcp_url=mcp_url,
359
+ )
360
+
361
+ # Fetch tools from server
362
+ mcp_tools: list[Tool] = _fetch_tools_from_server(function)
363
+
364
+ # Log discovered tools
365
+ logger.info(
366
+ "Discovered MCP tools from server",
367
+ tools_count=len(mcp_tools),
368
+ tool_names=[t.name for t in mcp_tools],
369
+ mcp_url=mcp_url,
370
+ )
371
+
372
+ # Apply filtering if requested and configured
373
+ if apply_filters and (function.include_tools or function.exclude_tools):
374
+ original_count = len(mcp_tools)
375
+ mcp_tools = [
376
+ tool
377
+ for tool in mcp_tools
378
+ if _should_include_tool(
379
+ tool.name,
380
+ function.include_tools,
381
+ function.exclude_tools,
382
+ )
383
+ ]
384
+ filtered_count = original_count - len(mcp_tools)
385
+
386
+ logger.info(
387
+ "Filtered MCP tools",
388
+ original_count=original_count,
389
+ filtered_count=filtered_count,
390
+ final_count=len(mcp_tools),
391
+ include_patterns=function.include_tools,
392
+ exclude_patterns=function.exclude_tools,
393
+ )
394
+
395
+ # Convert to MCPToolInfo for cleaner API
396
+ tool_infos: list[MCPToolInfo] = []
397
+ for mcp_tool in mcp_tools:
398
+ tool_info = MCPToolInfo(
399
+ name=mcp_tool.name,
400
+ description=mcp_tool.description,
401
+ input_schema=mcp_tool.inputSchema or {},
402
+ )
403
+ tool_infos.append(tool_info)
404
+
405
+ logger.debug(
406
+ "MCP tool available",
407
+ tool_name=mcp_tool.name,
408
+ tool_description=(
409
+ mcp_tool.description[:100] if mcp_tool.description else None
410
+ ),
411
+ )
412
+
413
+ return tool_infos
414
+
415
+
416
+ def create_mcp_tools(
417
+ function: McpFunctionModel,
418
+ ) -> Sequence[RunnableLike]:
419
+ """
420
+ Create executable LangChain tools for invoking Databricks MCP functions.
421
+
422
+ Supports both direct MCP connections and UC Connection-based MCP access.
423
+ Uses manual tool wrappers to ensure response format compatibility with
424
+ Databricks APIs (which reject extra fields in tool results).
425
+
426
+ This function:
427
+ 1. Fetches available tools from the MCP server
428
+ 2. Applies include_tools/exclude_tools filters
429
+ 3. Wraps each tool for LangChain agent execution
430
+
431
+ For tool discovery without creating executable tools, use list_mcp_tools().
432
+
433
+ Based on: https://docs.databricks.com/aws/en/generative-ai/mcp/external-mcp
434
+
435
+ Args:
436
+ function: The MCP function model configuration containing:
437
+ - Connection details (url, connection, headers, etc.)
438
+ - Optional filtering (include_tools, exclude_tools)
439
+
440
+ Returns:
441
+ A sequence of LangChain tools that can be used by agents.
442
+
443
+ Raises:
444
+ RuntimeError: If connection to MCP server fails.
445
+
446
+ Example:
447
+ from dao_ai.config import McpFunctionModel
448
+ from dao_ai.tools.mcp import create_mcp_tools
449
+
450
+ function = McpFunctionModel(sql=True)
451
+ tools = create_mcp_tools(function)
452
+
453
+ # Use tools in an agent
454
+ agent = create_agent(model=model, tools=tools)
455
+ """
456
+ mcp_url = function.mcp_url
457
+ logger.debug("Creating MCP tools", mcp_url=mcp_url)
458
+
459
+ # Fetch and filter tools using shared logic
460
+ # We need the raw Tool objects here, not MCPToolInfo
461
+ mcp_tools: list[Tool] = _fetch_tools_from_server(function)
462
+
463
+ # Log discovered tools
464
+ logger.info(
465
+ "Discovered MCP tools from server",
466
+ tools_count=len(mcp_tools),
467
+ tool_names=[t.name for t in mcp_tools],
468
+ mcp_url=mcp_url,
469
+ )
470
+
471
+ # Apply filtering if configured
472
+ if function.include_tools or function.exclude_tools:
473
+ original_count = len(mcp_tools)
474
+ mcp_tools = [
475
+ tool
476
+ for tool in mcp_tools
477
+ if _should_include_tool(
478
+ tool.name,
479
+ function.include_tools,
480
+ function.exclude_tools,
481
+ )
482
+ ]
483
+ filtered_count = original_count - len(mcp_tools)
484
+
485
+ logger.info(
486
+ "Filtered MCP tools",
487
+ original_count=original_count,
488
+ filtered_count=filtered_count,
489
+ final_count=len(mcp_tools),
490
+ include_patterns=function.include_tools,
491
+ exclude_patterns=function.exclude_tools,
492
+ )
493
+
494
+ # Log final tool list
495
+ for mcp_tool in mcp_tools:
496
+ logger.debug(
497
+ "MCP tool available",
498
+ tool_name=mcp_tool.name,
499
+ tool_description=(
500
+ mcp_tool.description[:100] if mcp_tool.description else None
501
+ ),
502
+ )
503
+
213
504
  def _create_tool_wrapper(mcp_tool: Tool) -> RunnableLike:
214
505
  """
215
506
  Create a LangChain tool wrapper for an MCP tool.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dao-ai
3
- Version: 0.1.5
3
+ Version: 0.1.6
4
4
  Summary: DAO AI: A modular, multi-agent orchestration framework for complex AI workflows. Supports agent handoff, tool integration, and dynamic configuration via YAML.
5
5
  Project-URL: Homepage, https://github.com/natefleming/dao-ai
6
6
  Project-URL: Documentation, https://natefleming.github.io/dao-ai
@@ -1,15 +1,15 @@
1
1
  dao_ai/__init__.py,sha256=18P98ExEgUaJ1Byw440Ct1ty59v6nxyWtc5S6Uq2m9Q,1062
2
2
  dao_ai/agent_as_code.py,sha256=xIlLDpPVfmDVzLvbdY_V_CrC4Jvj2ItCWJ-NzdrszTo,538
3
3
  dao_ai/catalog.py,sha256=sPZpHTD3lPx4EZUtIWeQV7VQM89WJ6YH__wluk1v2lE,4947
4
- dao_ai/cli.py,sha256=FWzddUceOoER2EIMwBx_mIMhCWFFZHoAL3KHy7f0Euc,35377
5
- dao_ai/config.py,sha256=GWBmrbiixMG0ZszLk_XTRKRIS0QqOk_TIQhauK--MIY,120863
4
+ dao_ai/cli.py,sha256=7LGrVDRgSBpznr8c8EksAhzPW_8NJ9h4St3DSpx-0z4,48196
5
+ dao_ai/config.py,sha256=rUm2wg0TPfj6YwzSoNxy6rgHi6GKWxXIRJ3NgGOjB04,123037
6
6
  dao_ai/graph.py,sha256=1-uQlo7iXZQTT3uU8aYu0N5rnhw5_g_2YLwVsAs6M-U,1119
7
7
  dao_ai/logging.py,sha256=lYy4BmucCHvwW7aI3YQkQXKJtMvtTnPDu9Hnd7_O4oc,1556
8
8
  dao_ai/messages.py,sha256=4ZBzO4iFdktGSLrmhHzFjzMIt2tpaL-aQLHOQJysGnY,6959
9
9
  dao_ai/models.py,sha256=AwzwTRTNZF-UOh59HsuXEgFk_YH6q6M-mERNDe64Z8k,81783
10
10
  dao_ai/nodes.py,sha256=7W6Ek6Uk9-pKa-H06nVCwuDllCrgX02IYy3rHtuL0aM,10777
11
11
  dao_ai/optimization.py,sha256=phK6t4wYmWPObCjGUBHdZzsaFXGhQOjhAek2bAEfwXo,22971
12
- dao_ai/prompts.py,sha256=G0ng5f2PkzfgdKrSl03Rnd6riZn5APedof0GAzsWQI8,4792
12
+ dao_ai/prompts.py,sha256=4cz5bZ7cOzrjyQ8hMp-K4evK6cVYrkGrAGdUl8-KDEM,2784
13
13
  dao_ai/state.py,sha256=0wbbzfQmldkCu26gdTE5j0Rl-_pfilza-YIHPbSWlvI,6394
14
14
  dao_ai/types.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
15
  dao_ai/utils.py,sha256=_Urd7Nj2VzrgPKf3NS4E6vt0lWRhEUddBqWN9BksqeE,11543
@@ -28,7 +28,7 @@ dao_ai/memory/base.py,sha256=99nfr2UZJ4jmfTL_KrqUlRSCoRxzkZyWyx5WqeUoMdQ,338
28
28
  dao_ai/memory/core.py,sha256=38H-JLIyUrRDIECLvpXK3iJlWG35X97E-DTo_4c3Jzc,6317
29
29
  dao_ai/memory/databricks.py,sha256=SM6nwLjhSRJO4hLc3GUuht5YydYtTi3BAOae6jPwTm4,14377
30
30
  dao_ai/memory/postgres.py,sha256=q9IIAGs0wuaV-3rUIn4dtzOxbkCCoB-yv1Rtod7ohjI,16467
31
- dao_ai/middleware/__init__.py,sha256=scEk9Mt9POJe-j9e9t5sf3FNMNPBsu15_oxrlV9IkVE,4871
31
+ dao_ai/middleware/__init__.py,sha256=Qy8wbvjXF7TrUzi3tWziOwxqsrUcT1rzE3UWd3x5CrU,5108
32
32
  dao_ai/middleware/assertions.py,sha256=C1K-TnNZfBEwWouioHCt6c48i1ux9QKfQaX6AzghhgE,27408
33
33
  dao_ai/middleware/base.py,sha256=uG2tpdnjL5xY5jCKvb_m3UTBtl4ZC6fJQUkDsQvV8S4,1279
34
34
  dao_ai/middleware/context_editing.py,sha256=5rNKqH1phFFQTVW-4nzlVH5cbqomD-HFEIy2Z841D4I,7687
@@ -42,6 +42,7 @@ dao_ai/middleware/pii.py,sha256=zetfoz1WlJ-V0vjJp37v8NGimXB27EkZfetUHpGCXno,5137
42
42
  dao_ai/middleware/summarization.py,sha256=gp2s9uc4DEJat-mWjWEzMaR-zAAeUOXYvu5EEYtqae4,7143
43
43
  dao_ai/middleware/tool_call_limit.py,sha256=WQ3NmA3pLo-pNPBmwM7KwkYpT1segEnWqkhgW1xNkCE,6321
44
44
  dao_ai/middleware/tool_retry.py,sha256=QfJ7yTHneME8VtnA88QcmnjXIegSFeJztyngy49wTgM,5568
45
+ dao_ai/middleware/tool_selector.py,sha256=POj72YdzZEiNGfW4AQXPBeVVS1RUBsiG7PBuSENEhe0,4516
45
46
  dao_ai/orchestration/__init__.py,sha256=i85CLfRR335NcCFhaXABcMkn6WZfXnJ8cHH4YZsZN0s,1622
46
47
  dao_ai/orchestration/core.py,sha256=qoU7uMXBJCth-sqfu0jRE1L0GOn5H4LoZdRUY1Ib3DI,9585
47
48
  dao_ai/orchestration/supervisor.py,sha256=alKMEEo9G5LhdpMvTVdAMel234cZj5_MguWl4wFB7XQ,9873
@@ -49,12 +50,12 @@ dao_ai/orchestration/swarm.py,sha256=8tp1eGmsQqqWpaDcjPoJckddPWohZdmmN0RGRJ_xzOA
49
50
  dao_ai/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
50
51
  dao_ai/providers/base.py,sha256=-fjKypCOk28h6vioPfMj9YZSw_3Kcbi2nMuAyY7vX9k,1383
51
52
  dao_ai/providers/databricks.py,sha256=XxYkyoDYkwGV_Xg1IJBpGOl4d7U5HiFP4RtjjSLgenI,61437
52
- dao_ai/tools/__init__.py,sha256=SRd7W2DOCXKbWWy8lclRtJiCskz7SDAm94qaFF47urQ,1664
53
+ dao_ai/tools/__init__.py,sha256=NfRpAKds_taHbx6gzLPWgtPXve-YpwzkoOAUflwxceM,1734
53
54
  dao_ai/tools/agent.py,sha256=plIWALywRjaDSnot13nYehBsrHRpBUpsVZakoGeajOE,1858
54
55
  dao_ai/tools/core.py,sha256=bRIN3BZhRQX8-Kpu3HPomliodyskCqjxynQmYbk6Vjs,3783
55
56
  dao_ai/tools/email.py,sha256=A3TsCoQgJR7UUWR0g45OPRGDpVoYwctFs1MOZMTt_d4,7389
56
57
  dao_ai/tools/genie.py,sha256=4e_5MeAe7kDzHbYeXuNPFbY5z8ci3ouj8l5254CZ2lA,8874
57
- dao_ai/tools/mcp.py,sha256=EFcKo_f-kPMnyR5w6oh0g4Hy4jyuVJEcUGzSiI9dXlg,8505
58
+ dao_ai/tools/mcp.py,sha256=0OfP4b4skcjeF2rzkOLYqd65ti1Mj55N_l8VoQlH9qo,17818
58
59
  dao_ai/tools/memory.py,sha256=lwObKimAand22Nq3Y63tsv-AXQ5SXUigN9PqRjoWKes,1836
59
60
  dao_ai/tools/python.py,sha256=jWFnZPni2sCdtd8D1CqXnZIPHnWkdK27bCJnBXpzhvo,1879
60
61
  dao_ai/tools/search.py,sha256=cJ3D9FKr1GAR6xz55dLtRkjtQsI0WRueGt9TPDFpOxc,433
@@ -63,8 +64,8 @@ dao_ai/tools/sql.py,sha256=tKd1gjpLuKdQDyfmyYYtMiNRHDW6MGRbdEVaeqyB8Ok,7632
63
64
  dao_ai/tools/time.py,sha256=tufJniwivq29y0LIffbgeBTIDE6VgrLpmVf8Qr90qjw,9224
64
65
  dao_ai/tools/unity_catalog.py,sha256=AjQfW7bvV8NurqDLIyntYRv2eJuTwNdbvex1L5CRjOk,15534
65
66
  dao_ai/tools/vector_search.py,sha256=oe2uBwl2TfeJIXPpwiS6Rmz7wcHczSxNyqS9P3hE6co,14542
66
- dao_ai-0.1.5.dist-info/METADATA,sha256=Iz_x_25jQut9i8yghsPmQV08iyLBJdrXTAjFmYOjx1Q,16685
67
- dao_ai-0.1.5.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
68
- dao_ai-0.1.5.dist-info/entry_points.txt,sha256=Xa-UFyc6gWGwMqMJOt06ZOog2vAfygV_DSwg1AiP46g,43
69
- dao_ai-0.1.5.dist-info/licenses/LICENSE,sha256=YZt3W32LtPYruuvHE9lGk2bw6ZPMMJD8yLrjgHybyz4,1069
70
- dao_ai-0.1.5.dist-info/RECORD,,
67
+ dao_ai-0.1.6.dist-info/METADATA,sha256=eux_1l0ANLlbQnRnq0IRgc8Q-ksY6sFmQa1_Vzd5_Kc,16685
68
+ dao_ai-0.1.6.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
69
+ dao_ai-0.1.6.dist-info/entry_points.txt,sha256=Xa-UFyc6gWGwMqMJOt06ZOog2vAfygV_DSwg1AiP46g,43
70
+ dao_ai-0.1.6.dist-info/licenses/LICENSE,sha256=YZt3W32LtPYruuvHE9lGk2bw6ZPMMJD8yLrjgHybyz4,1069
71
+ dao_ai-0.1.6.dist-info/RECORD,,
File without changes