hexdag 0.5.0.dev1__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.
- hexdag/__init__.py +116 -0
- hexdag/__main__.py +30 -0
- hexdag/adapters/executors/__init__.py +5 -0
- hexdag/adapters/executors/local_executor.py +316 -0
- hexdag/builtin/__init__.py +6 -0
- hexdag/builtin/adapters/__init__.py +51 -0
- hexdag/builtin/adapters/anthropic/__init__.py +5 -0
- hexdag/builtin/adapters/anthropic/anthropic_adapter.py +151 -0
- hexdag/builtin/adapters/database/__init__.py +6 -0
- hexdag/builtin/adapters/database/csv/csv_adapter.py +249 -0
- hexdag/builtin/adapters/database/pgvector/__init__.py +5 -0
- hexdag/builtin/adapters/database/pgvector/pgvector_adapter.py +478 -0
- hexdag/builtin/adapters/database/sqlalchemy/sqlalchemy_adapter.py +252 -0
- hexdag/builtin/adapters/database/sqlite/__init__.py +5 -0
- hexdag/builtin/adapters/database/sqlite/sqlite_adapter.py +410 -0
- hexdag/builtin/adapters/local/README.md +59 -0
- hexdag/builtin/adapters/local/__init__.py +7 -0
- hexdag/builtin/adapters/local/local_observer_manager.py +696 -0
- hexdag/builtin/adapters/memory/__init__.py +47 -0
- hexdag/builtin/adapters/memory/file_memory_adapter.py +297 -0
- hexdag/builtin/adapters/memory/in_memory_memory.py +216 -0
- hexdag/builtin/adapters/memory/schemas.py +57 -0
- hexdag/builtin/adapters/memory/session_memory.py +178 -0
- hexdag/builtin/adapters/memory/sqlite_memory_adapter.py +215 -0
- hexdag/builtin/adapters/memory/state_memory.py +280 -0
- hexdag/builtin/adapters/mock/README.md +89 -0
- hexdag/builtin/adapters/mock/__init__.py +15 -0
- hexdag/builtin/adapters/mock/hexdag.toml +50 -0
- hexdag/builtin/adapters/mock/mock_database.py +225 -0
- hexdag/builtin/adapters/mock/mock_embedding.py +223 -0
- hexdag/builtin/adapters/mock/mock_llm.py +177 -0
- hexdag/builtin/adapters/mock/mock_tool_adapter.py +192 -0
- hexdag/builtin/adapters/mock/mock_tool_router.py +232 -0
- hexdag/builtin/adapters/openai/__init__.py +5 -0
- hexdag/builtin/adapters/openai/openai_adapter.py +634 -0
- hexdag/builtin/adapters/secret/__init__.py +7 -0
- hexdag/builtin/adapters/secret/local_secret_adapter.py +248 -0
- hexdag/builtin/adapters/unified_tool_router.py +280 -0
- hexdag/builtin/macros/__init__.py +17 -0
- hexdag/builtin/macros/conversation_agent.py +390 -0
- hexdag/builtin/macros/llm_macro.py +151 -0
- hexdag/builtin/macros/reasoning_agent.py +423 -0
- hexdag/builtin/macros/tool_macro.py +380 -0
- hexdag/builtin/nodes/__init__.py +38 -0
- hexdag/builtin/nodes/_discovery.py +123 -0
- hexdag/builtin/nodes/agent_node.py +696 -0
- hexdag/builtin/nodes/base_node_factory.py +242 -0
- hexdag/builtin/nodes/composite_node.py +926 -0
- hexdag/builtin/nodes/data_node.py +201 -0
- hexdag/builtin/nodes/expression_node.py +487 -0
- hexdag/builtin/nodes/function_node.py +454 -0
- hexdag/builtin/nodes/llm_node.py +491 -0
- hexdag/builtin/nodes/loop_node.py +920 -0
- hexdag/builtin/nodes/mapped_input.py +518 -0
- hexdag/builtin/nodes/port_call_node.py +269 -0
- hexdag/builtin/nodes/tool_call_node.py +195 -0
- hexdag/builtin/nodes/tool_utils.py +390 -0
- hexdag/builtin/prompts/__init__.py +68 -0
- hexdag/builtin/prompts/base.py +422 -0
- hexdag/builtin/prompts/chat_prompts.py +303 -0
- hexdag/builtin/prompts/error_correction_prompts.py +320 -0
- hexdag/builtin/prompts/tool_prompts.py +160 -0
- hexdag/builtin/tools/builtin_tools.py +84 -0
- hexdag/builtin/tools/database_tools.py +164 -0
- hexdag/cli/__init__.py +17 -0
- hexdag/cli/__main__.py +7 -0
- hexdag/cli/commands/__init__.py +27 -0
- hexdag/cli/commands/build_cmd.py +812 -0
- hexdag/cli/commands/create_cmd.py +208 -0
- hexdag/cli/commands/docs_cmd.py +293 -0
- hexdag/cli/commands/generate_types_cmd.py +252 -0
- hexdag/cli/commands/init_cmd.py +188 -0
- hexdag/cli/commands/pipeline_cmd.py +494 -0
- hexdag/cli/commands/plugin_dev_cmd.py +529 -0
- hexdag/cli/commands/plugins_cmd.py +441 -0
- hexdag/cli/commands/studio_cmd.py +101 -0
- hexdag/cli/commands/validate_cmd.py +221 -0
- hexdag/cli/main.py +84 -0
- hexdag/core/__init__.py +83 -0
- hexdag/core/config/__init__.py +20 -0
- hexdag/core/config/loader.py +479 -0
- hexdag/core/config/models.py +150 -0
- hexdag/core/configurable.py +294 -0
- hexdag/core/context/__init__.py +37 -0
- hexdag/core/context/execution_context.py +378 -0
- hexdag/core/docs/__init__.py +26 -0
- hexdag/core/docs/extractors.py +678 -0
- hexdag/core/docs/generators.py +890 -0
- hexdag/core/docs/models.py +120 -0
- hexdag/core/domain/__init__.py +10 -0
- hexdag/core/domain/dag.py +1225 -0
- hexdag/core/exceptions.py +234 -0
- hexdag/core/expression_parser.py +569 -0
- hexdag/core/logging.py +449 -0
- hexdag/core/models/__init__.py +17 -0
- hexdag/core/models/base.py +138 -0
- hexdag/core/orchestration/__init__.py +46 -0
- hexdag/core/orchestration/body_executor.py +481 -0
- hexdag/core/orchestration/components/__init__.py +97 -0
- hexdag/core/orchestration/components/adapter_lifecycle_manager.py +113 -0
- hexdag/core/orchestration/components/checkpoint_manager.py +134 -0
- hexdag/core/orchestration/components/execution_coordinator.py +360 -0
- hexdag/core/orchestration/components/health_check_manager.py +176 -0
- hexdag/core/orchestration/components/input_mapper.py +143 -0
- hexdag/core/orchestration/components/lifecycle_manager.py +583 -0
- hexdag/core/orchestration/components/node_executor.py +377 -0
- hexdag/core/orchestration/components/secret_manager.py +202 -0
- hexdag/core/orchestration/components/wave_executor.py +158 -0
- hexdag/core/orchestration/constants.py +17 -0
- hexdag/core/orchestration/events/README.md +312 -0
- hexdag/core/orchestration/events/__init__.py +104 -0
- hexdag/core/orchestration/events/batching.py +330 -0
- hexdag/core/orchestration/events/decorators.py +139 -0
- hexdag/core/orchestration/events/events.py +573 -0
- hexdag/core/orchestration/events/observers/__init__.py +30 -0
- hexdag/core/orchestration/events/observers/core_observers.py +690 -0
- hexdag/core/orchestration/events/observers/models.py +111 -0
- hexdag/core/orchestration/events/taxonomy.py +269 -0
- hexdag/core/orchestration/hook_context.py +237 -0
- hexdag/core/orchestration/hooks.py +437 -0
- hexdag/core/orchestration/models.py +418 -0
- hexdag/core/orchestration/orchestrator.py +910 -0
- hexdag/core/orchestration/orchestrator_factory.py +275 -0
- hexdag/core/orchestration/port_wrappers.py +327 -0
- hexdag/core/orchestration/prompt/__init__.py +32 -0
- hexdag/core/orchestration/prompt/template.py +332 -0
- hexdag/core/pipeline_builder/__init__.py +21 -0
- hexdag/core/pipeline_builder/component_instantiator.py +386 -0
- hexdag/core/pipeline_builder/include_tag.py +265 -0
- hexdag/core/pipeline_builder/pipeline_config.py +133 -0
- hexdag/core/pipeline_builder/py_tag.py +223 -0
- hexdag/core/pipeline_builder/tag_discovery.py +268 -0
- hexdag/core/pipeline_builder/yaml_builder.py +1196 -0
- hexdag/core/pipeline_builder/yaml_validator.py +569 -0
- hexdag/core/ports/__init__.py +65 -0
- hexdag/core/ports/api_call.py +133 -0
- hexdag/core/ports/database.py +489 -0
- hexdag/core/ports/embedding.py +215 -0
- hexdag/core/ports/executor.py +237 -0
- hexdag/core/ports/file_storage.py +117 -0
- hexdag/core/ports/healthcheck.py +87 -0
- hexdag/core/ports/llm.py +551 -0
- hexdag/core/ports/memory.py +70 -0
- hexdag/core/ports/observer_manager.py +130 -0
- hexdag/core/ports/secret.py +145 -0
- hexdag/core/ports/tool_router.py +94 -0
- hexdag/core/ports_builder.py +623 -0
- hexdag/core/protocols.py +273 -0
- hexdag/core/resolver.py +304 -0
- hexdag/core/schema/__init__.py +9 -0
- hexdag/core/schema/generator.py +742 -0
- hexdag/core/secrets.py +242 -0
- hexdag/core/types.py +413 -0
- hexdag/core/utils/async_warnings.py +206 -0
- hexdag/core/utils/schema_conversion.py +78 -0
- hexdag/core/utils/sql_validation.py +86 -0
- hexdag/core/validation/secure_json.py +148 -0
- hexdag/core/yaml_macro.py +517 -0
- hexdag/mcp_server.py +3120 -0
- hexdag/studio/__init__.py +10 -0
- hexdag/studio/build_ui.py +92 -0
- hexdag/studio/server/__init__.py +1 -0
- hexdag/studio/server/main.py +100 -0
- hexdag/studio/server/routes/__init__.py +9 -0
- hexdag/studio/server/routes/execute.py +208 -0
- hexdag/studio/server/routes/export.py +558 -0
- hexdag/studio/server/routes/files.py +207 -0
- hexdag/studio/server/routes/plugins.py +419 -0
- hexdag/studio/server/routes/validate.py +220 -0
- hexdag/studio/ui/index.html +13 -0
- hexdag/studio/ui/package-lock.json +2992 -0
- hexdag/studio/ui/package.json +31 -0
- hexdag/studio/ui/postcss.config.js +6 -0
- hexdag/studio/ui/public/hexdag.svg +5 -0
- hexdag/studio/ui/src/App.tsx +251 -0
- hexdag/studio/ui/src/components/Canvas.tsx +408 -0
- hexdag/studio/ui/src/components/ContextMenu.tsx +187 -0
- hexdag/studio/ui/src/components/FileBrowser.tsx +123 -0
- hexdag/studio/ui/src/components/Header.tsx +181 -0
- hexdag/studio/ui/src/components/HexdagNode.tsx +193 -0
- hexdag/studio/ui/src/components/NodeInspector.tsx +512 -0
- hexdag/studio/ui/src/components/NodePalette.tsx +262 -0
- hexdag/studio/ui/src/components/NodePortsSection.tsx +403 -0
- hexdag/studio/ui/src/components/PluginManager.tsx +347 -0
- hexdag/studio/ui/src/components/PortsEditor.tsx +481 -0
- hexdag/studio/ui/src/components/PythonEditor.tsx +195 -0
- hexdag/studio/ui/src/components/ValidationPanel.tsx +105 -0
- hexdag/studio/ui/src/components/YamlEditor.tsx +196 -0
- hexdag/studio/ui/src/components/index.ts +8 -0
- hexdag/studio/ui/src/index.css +92 -0
- hexdag/studio/ui/src/main.tsx +10 -0
- hexdag/studio/ui/src/types/index.ts +123 -0
- hexdag/studio/ui/src/vite-env.d.ts +1 -0
- hexdag/studio/ui/tailwind.config.js +29 -0
- hexdag/studio/ui/tsconfig.json +37 -0
- hexdag/studio/ui/tsconfig.node.json +13 -0
- hexdag/studio/ui/vite.config.ts +35 -0
- hexdag/visualization/__init__.py +69 -0
- hexdag/visualization/dag_visualizer.py +1020 -0
- hexdag-0.5.0.dev1.dist-info/METADATA +369 -0
- hexdag-0.5.0.dev1.dist-info/RECORD +261 -0
- hexdag-0.5.0.dev1.dist-info/WHEEL +4 -0
- hexdag-0.5.0.dev1.dist-info/entry_points.txt +4 -0
- hexdag-0.5.0.dev1.dist-info/licenses/LICENSE +190 -0
- hexdag_plugins/.gitignore +43 -0
- hexdag_plugins/README.md +73 -0
- hexdag_plugins/__init__.py +1 -0
- hexdag_plugins/azure/LICENSE +21 -0
- hexdag_plugins/azure/README.md +414 -0
- hexdag_plugins/azure/__init__.py +21 -0
- hexdag_plugins/azure/azure_blob_adapter.py +450 -0
- hexdag_plugins/azure/azure_cosmos_adapter.py +383 -0
- hexdag_plugins/azure/azure_keyvault_adapter.py +314 -0
- hexdag_plugins/azure/azure_openai_adapter.py +415 -0
- hexdag_plugins/azure/pyproject.toml +107 -0
- hexdag_plugins/azure/tests/__init__.py +1 -0
- hexdag_plugins/azure/tests/test_azure_blob_adapter.py +350 -0
- hexdag_plugins/azure/tests/test_azure_cosmos_adapter.py +323 -0
- hexdag_plugins/azure/tests/test_azure_keyvault_adapter.py +330 -0
- hexdag_plugins/azure/tests/test_azure_openai_adapter.py +329 -0
- hexdag_plugins/hexdag_etl/README.md +168 -0
- hexdag_plugins/hexdag_etl/__init__.py +53 -0
- hexdag_plugins/hexdag_etl/examples/01_simple_pandas_transform.py +270 -0
- hexdag_plugins/hexdag_etl/examples/02_simple_pandas_only.py +149 -0
- hexdag_plugins/hexdag_etl/examples/03_file_io_pipeline.py +109 -0
- hexdag_plugins/hexdag_etl/examples/test_pandas_transform.py +84 -0
- hexdag_plugins/hexdag_etl/hexdag.toml +25 -0
- hexdag_plugins/hexdag_etl/hexdag_etl/__init__.py +48 -0
- hexdag_plugins/hexdag_etl/hexdag_etl/nodes/__init__.py +13 -0
- hexdag_plugins/hexdag_etl/hexdag_etl/nodes/api_extract.py +230 -0
- hexdag_plugins/hexdag_etl/hexdag_etl/nodes/base_node_factory.py +181 -0
- hexdag_plugins/hexdag_etl/hexdag_etl/nodes/file_io.py +415 -0
- hexdag_plugins/hexdag_etl/hexdag_etl/nodes/outlook.py +492 -0
- hexdag_plugins/hexdag_etl/hexdag_etl/nodes/pandas_transform.py +563 -0
- hexdag_plugins/hexdag_etl/hexdag_etl/nodes/sql_extract_load.py +112 -0
- hexdag_plugins/hexdag_etl/pyproject.toml +82 -0
- hexdag_plugins/hexdag_etl/test_transform.py +54 -0
- hexdag_plugins/hexdag_etl/tests/test_plugin_integration.py +62 -0
- hexdag_plugins/mysql_adapter/LICENSE +21 -0
- hexdag_plugins/mysql_adapter/README.md +224 -0
- hexdag_plugins/mysql_adapter/__init__.py +6 -0
- hexdag_plugins/mysql_adapter/mysql_adapter.py +408 -0
- hexdag_plugins/mysql_adapter/pyproject.toml +93 -0
- hexdag_plugins/mysql_adapter/tests/test_mysql_adapter.py +259 -0
- hexdag_plugins/storage/README.md +184 -0
- hexdag_plugins/storage/__init__.py +19 -0
- hexdag_plugins/storage/file/__init__.py +5 -0
- hexdag_plugins/storage/file/local.py +325 -0
- hexdag_plugins/storage/ports/__init__.py +5 -0
- hexdag_plugins/storage/ports/vector_store.py +236 -0
- hexdag_plugins/storage/sql/__init__.py +7 -0
- hexdag_plugins/storage/sql/base.py +187 -0
- hexdag_plugins/storage/sql/mysql.py +27 -0
- hexdag_plugins/storage/sql/postgresql.py +27 -0
- hexdag_plugins/storage/tests/__init__.py +1 -0
- hexdag_plugins/storage/tests/test_local_file_storage.py +161 -0
- hexdag_plugins/storage/tests/test_sql_adapters.py +212 -0
- hexdag_plugins/storage/vector/__init__.py +7 -0
- hexdag_plugins/storage/vector/chromadb.py +223 -0
- hexdag_plugins/storage/vector/in_memory.py +285 -0
- hexdag_plugins/storage/vector/pgvector.py +502 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Documentation generation framework for hexDAG.
|
|
2
|
+
|
|
3
|
+
This module provides tools to extract documentation from code artifacts
|
|
4
|
+
(decorators, signatures, docstrings) and generate up-to-date documentation
|
|
5
|
+
for the MCP server and other consumers.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from hexdag.core.docs.extractors import DocExtractor
|
|
9
|
+
from hexdag.core.docs.generators import GuideGenerator
|
|
10
|
+
from hexdag.core.docs.models import (
|
|
11
|
+
AdapterDoc,
|
|
12
|
+
ComponentDoc,
|
|
13
|
+
NodeDoc,
|
|
14
|
+
ParameterDoc,
|
|
15
|
+
ToolDoc,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"AdapterDoc",
|
|
20
|
+
"ComponentDoc",
|
|
21
|
+
"DocExtractor",
|
|
22
|
+
"GuideGenerator",
|
|
23
|
+
"NodeDoc",
|
|
24
|
+
"ParameterDoc",
|
|
25
|
+
"ToolDoc",
|
|
26
|
+
]
|
|
@@ -0,0 +1,678 @@
|
|
|
1
|
+
"""Documentation extractors for hexDAG components.
|
|
2
|
+
|
|
3
|
+
This module provides utilities to extract documentation from code artifacts
|
|
4
|
+
including signatures, docstrings, decorators, and explicit schema attributes.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import inspect
|
|
8
|
+
import re
|
|
9
|
+
from collections.abc import Callable
|
|
10
|
+
from typing import Any, get_type_hints
|
|
11
|
+
|
|
12
|
+
from hexdag.core.docs.models import (
|
|
13
|
+
AdapterDoc,
|
|
14
|
+
NodeDoc,
|
|
15
|
+
ParameterDoc,
|
|
16
|
+
ToolDoc,
|
|
17
|
+
)
|
|
18
|
+
from hexdag.core.logging import get_logger
|
|
19
|
+
from hexdag.core.schema import SchemaGenerator
|
|
20
|
+
from hexdag.core.secrets import SecretDescriptor
|
|
21
|
+
|
|
22
|
+
logger = get_logger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class DocExtractor:
|
|
26
|
+
"""Extract documentation from Python code artifacts.
|
|
27
|
+
|
|
28
|
+
This class provides static methods to extract structured documentation
|
|
29
|
+
from adapters, nodes, tools, and other callables by inspecting their
|
|
30
|
+
signatures, docstrings, and special attributes.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
@staticmethod
|
|
34
|
+
def extract_parameters(obj: Callable | type) -> list[ParameterDoc]:
|
|
35
|
+
"""Extract parameter documentation from a callable or class __init__.
|
|
36
|
+
|
|
37
|
+
Parameters
|
|
38
|
+
----------
|
|
39
|
+
obj : Callable | type
|
|
40
|
+
Function, method, or class to extract parameters from
|
|
41
|
+
|
|
42
|
+
Returns
|
|
43
|
+
-------
|
|
44
|
+
list[ParameterDoc]
|
|
45
|
+
List of documented parameters
|
|
46
|
+
"""
|
|
47
|
+
# Get the target to inspect
|
|
48
|
+
if isinstance(obj, type):
|
|
49
|
+
target = obj.__init__
|
|
50
|
+
elif callable(obj) and not inspect.isfunction(obj):
|
|
51
|
+
target = obj.__call__
|
|
52
|
+
else:
|
|
53
|
+
target = obj
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
sig = inspect.signature(target)
|
|
57
|
+
except (ValueError, TypeError) as e:
|
|
58
|
+
logger.warning(f"Could not get signature for {obj}: {e}")
|
|
59
|
+
return []
|
|
60
|
+
|
|
61
|
+
# Extract docstring parameter descriptions
|
|
62
|
+
param_docs = SchemaGenerator._extract_param_docs(target)
|
|
63
|
+
|
|
64
|
+
# Try to get type hints
|
|
65
|
+
try:
|
|
66
|
+
hints = get_type_hints(target)
|
|
67
|
+
except Exception:
|
|
68
|
+
hints = {}
|
|
69
|
+
|
|
70
|
+
parameters = []
|
|
71
|
+
for param_name, param in sig.parameters.items():
|
|
72
|
+
if param_name in ("self", "cls", "args", "kwargs"):
|
|
73
|
+
continue
|
|
74
|
+
|
|
75
|
+
# Skip VAR_POSITIONAL and VAR_KEYWORD
|
|
76
|
+
if param.kind in (
|
|
77
|
+
inspect.Parameter.VAR_POSITIONAL,
|
|
78
|
+
inspect.Parameter.VAR_KEYWORD,
|
|
79
|
+
):
|
|
80
|
+
continue
|
|
81
|
+
|
|
82
|
+
# Get type hint
|
|
83
|
+
type_hint = hints.get(param_name, param.annotation)
|
|
84
|
+
if type_hint == inspect.Parameter.empty:
|
|
85
|
+
type_hint_str = "Any"
|
|
86
|
+
else:
|
|
87
|
+
type_hint_str = DocExtractor._format_type_hint(type_hint)
|
|
88
|
+
|
|
89
|
+
# Check for secret descriptor
|
|
90
|
+
default_value = param.default
|
|
91
|
+
is_secret = isinstance(default_value, SecretDescriptor)
|
|
92
|
+
|
|
93
|
+
# Get default value
|
|
94
|
+
if default_value == inspect.Parameter.empty:
|
|
95
|
+
default_str = None
|
|
96
|
+
required = True
|
|
97
|
+
elif is_secret:
|
|
98
|
+
default_str = f"secret(env='{default_value.env_var}')"
|
|
99
|
+
required = default_value.required
|
|
100
|
+
else:
|
|
101
|
+
default_str = repr(default_value)
|
|
102
|
+
required = False
|
|
103
|
+
|
|
104
|
+
# Get description from docstring
|
|
105
|
+
description = param_docs.get(param_name, "")
|
|
106
|
+
|
|
107
|
+
# Check for enum/Literal values
|
|
108
|
+
enum_values = DocExtractor._extract_enum_values(type_hint)
|
|
109
|
+
|
|
110
|
+
parameters.append(
|
|
111
|
+
ParameterDoc(
|
|
112
|
+
name=param_name,
|
|
113
|
+
type_hint=type_hint_str,
|
|
114
|
+
description=description,
|
|
115
|
+
required=required,
|
|
116
|
+
default=default_str,
|
|
117
|
+
enum_values=enum_values,
|
|
118
|
+
)
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
return parameters
|
|
122
|
+
|
|
123
|
+
@staticmethod
|
|
124
|
+
def _format_type_hint(hint: Any) -> str:
|
|
125
|
+
"""Format a type hint as a readable string.
|
|
126
|
+
|
|
127
|
+
Parameters
|
|
128
|
+
----------
|
|
129
|
+
hint : Any
|
|
130
|
+
Type annotation
|
|
131
|
+
|
|
132
|
+
Returns
|
|
133
|
+
-------
|
|
134
|
+
str
|
|
135
|
+
Human-readable type string
|
|
136
|
+
"""
|
|
137
|
+
if hint is None or hint is type(None):
|
|
138
|
+
return "None"
|
|
139
|
+
|
|
140
|
+
# Handle string annotations
|
|
141
|
+
if isinstance(hint, str):
|
|
142
|
+
return hint
|
|
143
|
+
|
|
144
|
+
# Get origin and args for generic types
|
|
145
|
+
origin = getattr(hint, "__origin__", None)
|
|
146
|
+
args = getattr(hint, "__args__", ())
|
|
147
|
+
|
|
148
|
+
# Handle Union types (including | syntax)
|
|
149
|
+
if origin is type(int | str): # UnionType
|
|
150
|
+
parts = [DocExtractor._format_type_hint(arg) for arg in args]
|
|
151
|
+
return " | ".join(parts)
|
|
152
|
+
|
|
153
|
+
# Handle Optional (Union with None)
|
|
154
|
+
try:
|
|
155
|
+
from typing import Union
|
|
156
|
+
|
|
157
|
+
if origin is Union:
|
|
158
|
+
non_none = [arg for arg in args if arg is not type(None)]
|
|
159
|
+
if len(non_none) == 1 and type(None) in args:
|
|
160
|
+
return f"{DocExtractor._format_type_hint(non_none[0])} | None"
|
|
161
|
+
parts = [DocExtractor._format_type_hint(arg) for arg in args]
|
|
162
|
+
return " | ".join(parts)
|
|
163
|
+
except ImportError:
|
|
164
|
+
pass
|
|
165
|
+
|
|
166
|
+
# Handle Literal
|
|
167
|
+
try:
|
|
168
|
+
from typing import Literal, get_origin
|
|
169
|
+
|
|
170
|
+
if get_origin(hint) is Literal:
|
|
171
|
+
values = ", ".join(repr(v) for v in args)
|
|
172
|
+
return f"Literal[{values}]"
|
|
173
|
+
except (ImportError, TypeError):
|
|
174
|
+
pass
|
|
175
|
+
|
|
176
|
+
# Handle list, dict, etc.
|
|
177
|
+
if origin is list:
|
|
178
|
+
if args:
|
|
179
|
+
return f"list[{DocExtractor._format_type_hint(args[0])}]"
|
|
180
|
+
return "list"
|
|
181
|
+
if origin is dict:
|
|
182
|
+
if len(args) == 2:
|
|
183
|
+
key_type = DocExtractor._format_type_hint(args[0])
|
|
184
|
+
val_type = DocExtractor._format_type_hint(args[1])
|
|
185
|
+
return f"dict[{key_type}, {val_type}]"
|
|
186
|
+
return "dict"
|
|
187
|
+
if origin is set:
|
|
188
|
+
if args:
|
|
189
|
+
return f"set[{DocExtractor._format_type_hint(args[0])}]"
|
|
190
|
+
return "set"
|
|
191
|
+
if origin is tuple:
|
|
192
|
+
if args:
|
|
193
|
+
parts = [DocExtractor._format_type_hint(arg) for arg in args]
|
|
194
|
+
return f"tuple[{', '.join(parts)}]"
|
|
195
|
+
return "tuple"
|
|
196
|
+
|
|
197
|
+
# Handle basic types
|
|
198
|
+
if hasattr(hint, "__name__"):
|
|
199
|
+
return hint.__name__
|
|
200
|
+
|
|
201
|
+
# Fallback
|
|
202
|
+
return str(hint)
|
|
203
|
+
|
|
204
|
+
@staticmethod
|
|
205
|
+
def _extract_enum_values(hint: Any) -> list[str] | None:
|
|
206
|
+
"""Extract enum values from Literal type hints.
|
|
207
|
+
|
|
208
|
+
Parameters
|
|
209
|
+
----------
|
|
210
|
+
hint : Any
|
|
211
|
+
Type annotation to check
|
|
212
|
+
|
|
213
|
+
Returns
|
|
214
|
+
-------
|
|
215
|
+
list[str] | None
|
|
216
|
+
List of allowed values or None
|
|
217
|
+
"""
|
|
218
|
+
try:
|
|
219
|
+
from typing import Literal, get_args, get_origin
|
|
220
|
+
|
|
221
|
+
if get_origin(hint) is Literal:
|
|
222
|
+
return [str(v) for v in get_args(hint)]
|
|
223
|
+
except (ImportError, TypeError):
|
|
224
|
+
pass
|
|
225
|
+
return None
|
|
226
|
+
|
|
227
|
+
@staticmethod
|
|
228
|
+
def extract_docstring_parts(obj: Any) -> tuple[str, str, list[str]]:
|
|
229
|
+
"""Extract description, full docstring, and examples from docstring.
|
|
230
|
+
|
|
231
|
+
Parameters
|
|
232
|
+
----------
|
|
233
|
+
obj : Any
|
|
234
|
+
Object with __doc__ attribute
|
|
235
|
+
|
|
236
|
+
Returns
|
|
237
|
+
-------
|
|
238
|
+
tuple[str, str, list[str]]
|
|
239
|
+
(first_line_description, full_docstring, examples)
|
|
240
|
+
"""
|
|
241
|
+
docstring = inspect.getdoc(obj) or ""
|
|
242
|
+
if not docstring:
|
|
243
|
+
return "", "", []
|
|
244
|
+
|
|
245
|
+
# First line as description
|
|
246
|
+
lines = docstring.split("\n")
|
|
247
|
+
description = lines[0].strip() if lines else ""
|
|
248
|
+
|
|
249
|
+
# Extract examples
|
|
250
|
+
examples = DocExtractor._extract_examples(docstring)
|
|
251
|
+
|
|
252
|
+
return description, docstring, examples
|
|
253
|
+
|
|
254
|
+
@staticmethod
|
|
255
|
+
def _extract_examples(docstring: str) -> list[str]:
|
|
256
|
+
"""Extract code examples from docstring.
|
|
257
|
+
|
|
258
|
+
Parameters
|
|
259
|
+
----------
|
|
260
|
+
docstring : str
|
|
261
|
+
Full docstring text
|
|
262
|
+
|
|
263
|
+
Returns
|
|
264
|
+
-------
|
|
265
|
+
list[str]
|
|
266
|
+
List of code examples
|
|
267
|
+
"""
|
|
268
|
+
examples = []
|
|
269
|
+
|
|
270
|
+
# Find Examples section
|
|
271
|
+
patterns = [
|
|
272
|
+
r"Examples?\s*[-=]*\s*\n(.*?)(?=\n\s*[A-Z][a-z]+\s*[-=]*\s*\n|\Z)",
|
|
273
|
+
r"Examples?\s*\n\s*-+\s*\n(.*?)(?=\n\s*[A-Z][a-z]+\s*\n\s*-+|\Z)",
|
|
274
|
+
]
|
|
275
|
+
|
|
276
|
+
for pattern in patterns:
|
|
277
|
+
match = re.search(pattern, docstring, re.DOTALL | re.IGNORECASE)
|
|
278
|
+
if match:
|
|
279
|
+
examples_text = match.group(1)
|
|
280
|
+
# Extract code blocks
|
|
281
|
+
code_blocks = re.findall(
|
|
282
|
+
r"```(?:python)?\n(.*?)```|>>> (.*?)(?=\n(?!\.\.\.|\s)|\Z)",
|
|
283
|
+
examples_text,
|
|
284
|
+
re.DOTALL,
|
|
285
|
+
)
|
|
286
|
+
for block in code_blocks:
|
|
287
|
+
code = block[0] or block[1]
|
|
288
|
+
if code.strip():
|
|
289
|
+
examples.append(code.strip())
|
|
290
|
+
break
|
|
291
|
+
|
|
292
|
+
return examples
|
|
293
|
+
|
|
294
|
+
@staticmethod
|
|
295
|
+
def extract_adapter_doc(cls: type) -> AdapterDoc:
|
|
296
|
+
"""Extract documentation from an adapter class.
|
|
297
|
+
|
|
298
|
+
Parameters
|
|
299
|
+
----------
|
|
300
|
+
cls : type
|
|
301
|
+
Adapter class to document
|
|
302
|
+
|
|
303
|
+
Returns
|
|
304
|
+
-------
|
|
305
|
+
AdapterDoc
|
|
306
|
+
Extracted adapter documentation
|
|
307
|
+
"""
|
|
308
|
+
description, full_docstring, examples = DocExtractor.extract_docstring_parts(cls)
|
|
309
|
+
parameters = DocExtractor.extract_parameters(cls)
|
|
310
|
+
|
|
311
|
+
# Determine port type from class name or protocol
|
|
312
|
+
port_type = DocExtractor._guess_port_type(cls)
|
|
313
|
+
|
|
314
|
+
# Extract secrets from signature
|
|
315
|
+
secrets = DocExtractor._extract_secrets(cls)
|
|
316
|
+
|
|
317
|
+
# Generate module path
|
|
318
|
+
module_path = f"{cls.__module__}.{cls.__name__}"
|
|
319
|
+
|
|
320
|
+
# Generate decorator example
|
|
321
|
+
decorator_example = DocExtractor._generate_adapter_decorator_example(
|
|
322
|
+
cls, port_type, secrets
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
# Generate YAML example
|
|
326
|
+
yaml_example = DocExtractor._generate_adapter_yaml_example(cls, port_type)
|
|
327
|
+
|
|
328
|
+
return AdapterDoc(
|
|
329
|
+
name=cls.__name__,
|
|
330
|
+
module_path=module_path,
|
|
331
|
+
description=description,
|
|
332
|
+
full_docstring=full_docstring,
|
|
333
|
+
parameters=parameters,
|
|
334
|
+
examples=examples,
|
|
335
|
+
yaml_example=yaml_example,
|
|
336
|
+
port_type=port_type,
|
|
337
|
+
secrets=secrets,
|
|
338
|
+
decorator_example=decorator_example,
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
@staticmethod
|
|
342
|
+
def _guess_port_type(cls: type) -> str:
|
|
343
|
+
"""Guess port type from adapter class name or implemented protocols.
|
|
344
|
+
|
|
345
|
+
Parameters
|
|
346
|
+
----------
|
|
347
|
+
cls : type
|
|
348
|
+
Adapter class
|
|
349
|
+
|
|
350
|
+
Returns
|
|
351
|
+
-------
|
|
352
|
+
str
|
|
353
|
+
Guessed port type
|
|
354
|
+
"""
|
|
355
|
+
name_lower = cls.__name__.lower()
|
|
356
|
+
|
|
357
|
+
# Check class name patterns
|
|
358
|
+
if "llm" in name_lower or "openai" in name_lower or "anthropic" in name_lower:
|
|
359
|
+
return "llm"
|
|
360
|
+
if "memory" in name_lower:
|
|
361
|
+
return "memory"
|
|
362
|
+
if "database" in name_lower or "sql" in name_lower:
|
|
363
|
+
return "database"
|
|
364
|
+
if "secret" in name_lower or "keyvault" in name_lower:
|
|
365
|
+
return "secret"
|
|
366
|
+
if "storage" in name_lower or "blob" in name_lower:
|
|
367
|
+
return "storage"
|
|
368
|
+
if "tool" in name_lower and "router" in name_lower:
|
|
369
|
+
return "tool_router"
|
|
370
|
+
if "embedding" in name_lower:
|
|
371
|
+
return "embedding"
|
|
372
|
+
if "observer" in name_lower:
|
|
373
|
+
return "observer_manager"
|
|
374
|
+
|
|
375
|
+
# Check implemented protocols via base classes
|
|
376
|
+
for base in cls.__mro__:
|
|
377
|
+
base_name = base.__name__.lower()
|
|
378
|
+
if "generation" in base_name or "llm" in base_name:
|
|
379
|
+
return "llm"
|
|
380
|
+
if "memory" in base_name:
|
|
381
|
+
return "memory"
|
|
382
|
+
if "database" in base_name:
|
|
383
|
+
return "database"
|
|
384
|
+
|
|
385
|
+
return "unknown"
|
|
386
|
+
|
|
387
|
+
@staticmethod
|
|
388
|
+
def _extract_secrets(cls: type) -> dict[str, str]:
|
|
389
|
+
"""Extract secret declarations from __init__ signature.
|
|
390
|
+
|
|
391
|
+
Parameters
|
|
392
|
+
----------
|
|
393
|
+
cls : type
|
|
394
|
+
Class to inspect
|
|
395
|
+
|
|
396
|
+
Returns
|
|
397
|
+
-------
|
|
398
|
+
dict[str, str]
|
|
399
|
+
Mapping of parameter name to environment variable name
|
|
400
|
+
"""
|
|
401
|
+
try:
|
|
402
|
+
sig = inspect.signature(cls.__init__)
|
|
403
|
+
except (ValueError, TypeError):
|
|
404
|
+
return {}
|
|
405
|
+
|
|
406
|
+
secrets = {}
|
|
407
|
+
for param_name, param in sig.parameters.items():
|
|
408
|
+
if param_name in ("self", "cls"):
|
|
409
|
+
continue
|
|
410
|
+
|
|
411
|
+
if isinstance(param.default, SecretDescriptor):
|
|
412
|
+
secrets[param_name] = param.default.env_var
|
|
413
|
+
|
|
414
|
+
return secrets
|
|
415
|
+
|
|
416
|
+
@staticmethod
|
|
417
|
+
def _generate_adapter_decorator_example(
|
|
418
|
+
cls: type, port_type: str, secrets: dict[str, str]
|
|
419
|
+
) -> str:
|
|
420
|
+
"""Generate example @adapter decorator usage.
|
|
421
|
+
|
|
422
|
+
Parameters
|
|
423
|
+
----------
|
|
424
|
+
cls : type
|
|
425
|
+
Adapter class
|
|
426
|
+
port_type : str
|
|
427
|
+
Port type
|
|
428
|
+
secrets : dict[str, str]
|
|
429
|
+
Secret mappings
|
|
430
|
+
|
|
431
|
+
Returns
|
|
432
|
+
-------
|
|
433
|
+
str
|
|
434
|
+
Example decorator code
|
|
435
|
+
"""
|
|
436
|
+
name = cls.__name__.replace("Adapter", "").lower()
|
|
437
|
+
|
|
438
|
+
if secrets:
|
|
439
|
+
secrets_str = ", ".join(f'"{k}": "{v}"' for k, v in secrets.items())
|
|
440
|
+
return f'@adapter("{port_type}", name="{name}", secrets={{{secrets_str}}})'
|
|
441
|
+
return f'@adapter("{port_type}", name="{name}")'
|
|
442
|
+
|
|
443
|
+
@staticmethod
|
|
444
|
+
def _generate_adapter_yaml_example(cls: type, port_type: str) -> str:
|
|
445
|
+
"""Generate YAML usage example for adapter.
|
|
446
|
+
|
|
447
|
+
Parameters
|
|
448
|
+
----------
|
|
449
|
+
cls : type
|
|
450
|
+
Adapter class
|
|
451
|
+
port_type : str
|
|
452
|
+
Port type
|
|
453
|
+
|
|
454
|
+
Returns
|
|
455
|
+
-------
|
|
456
|
+
str
|
|
457
|
+
YAML example
|
|
458
|
+
"""
|
|
459
|
+
module_path = f"{cls.__module__}.{cls.__name__}"
|
|
460
|
+
return f"""ports:
|
|
461
|
+
{port_type}:
|
|
462
|
+
adapter: {module_path}
|
|
463
|
+
config:
|
|
464
|
+
# Add configuration here"""
|
|
465
|
+
|
|
466
|
+
@staticmethod
|
|
467
|
+
def extract_node_doc(cls: type) -> NodeDoc:
|
|
468
|
+
"""Extract documentation from a node factory class.
|
|
469
|
+
|
|
470
|
+
Parameters
|
|
471
|
+
----------
|
|
472
|
+
cls : type
|
|
473
|
+
Node factory class to document
|
|
474
|
+
|
|
475
|
+
Returns
|
|
476
|
+
-------
|
|
477
|
+
NodeDoc
|
|
478
|
+
Extracted node documentation
|
|
479
|
+
"""
|
|
480
|
+
description, full_docstring, examples = DocExtractor.extract_docstring_parts(cls)
|
|
481
|
+
|
|
482
|
+
# Check for _yaml_schema
|
|
483
|
+
yaml_schema = getattr(cls, "_yaml_schema", None)
|
|
484
|
+
|
|
485
|
+
# If has explicit schema, extract parameters from it
|
|
486
|
+
if yaml_schema and isinstance(yaml_schema, dict):
|
|
487
|
+
parameters = DocExtractor._extract_params_from_schema(yaml_schema)
|
|
488
|
+
if "description" in yaml_schema:
|
|
489
|
+
description = yaml_schema["description"]
|
|
490
|
+
else:
|
|
491
|
+
# Extract from __call__ method
|
|
492
|
+
try:
|
|
493
|
+
instance = cls()
|
|
494
|
+
parameters = DocExtractor.extract_parameters(instance)
|
|
495
|
+
except Exception:
|
|
496
|
+
parameters = DocExtractor.extract_parameters(cls)
|
|
497
|
+
|
|
498
|
+
# Generate module path
|
|
499
|
+
module_path = f"{cls.__module__}.{cls.__name__}"
|
|
500
|
+
|
|
501
|
+
# Determine kind from class name
|
|
502
|
+
kind = DocExtractor._class_name_to_kind(cls.__name__)
|
|
503
|
+
|
|
504
|
+
# Generate YAML example
|
|
505
|
+
yaml_example = DocExtractor._generate_node_yaml_example(cls, kind, yaml_schema)
|
|
506
|
+
|
|
507
|
+
return NodeDoc(
|
|
508
|
+
name=cls.__name__,
|
|
509
|
+
module_path=module_path,
|
|
510
|
+
description=description,
|
|
511
|
+
full_docstring=full_docstring,
|
|
512
|
+
parameters=parameters,
|
|
513
|
+
examples=examples,
|
|
514
|
+
yaml_example=yaml_example,
|
|
515
|
+
namespace="core",
|
|
516
|
+
yaml_schema=yaml_schema,
|
|
517
|
+
kind=kind,
|
|
518
|
+
)
|
|
519
|
+
|
|
520
|
+
@staticmethod
|
|
521
|
+
def _class_name_to_kind(class_name: str) -> str:
|
|
522
|
+
"""Convert class name to YAML kind.
|
|
523
|
+
|
|
524
|
+
Parameters
|
|
525
|
+
----------
|
|
526
|
+
class_name : str
|
|
527
|
+
Class name (e.g., "LLMNode", "FunctionNode")
|
|
528
|
+
|
|
529
|
+
Returns
|
|
530
|
+
-------
|
|
531
|
+
str
|
|
532
|
+
YAML kind (e.g., "llm_node", "function_node")
|
|
533
|
+
"""
|
|
534
|
+
# Split on uppercase letters and join with underscore
|
|
535
|
+
words = re.findall(r"[A-Z][a-z]*", class_name)
|
|
536
|
+
return "_".join(word.lower() for word in words)
|
|
537
|
+
|
|
538
|
+
@staticmethod
|
|
539
|
+
def _extract_params_from_schema(schema: dict[str, Any]) -> list[ParameterDoc]:
|
|
540
|
+
"""Extract parameters from _yaml_schema.
|
|
541
|
+
|
|
542
|
+
Parameters
|
|
543
|
+
----------
|
|
544
|
+
schema : dict[str, Any]
|
|
545
|
+
JSON Schema dict
|
|
546
|
+
|
|
547
|
+
Returns
|
|
548
|
+
-------
|
|
549
|
+
list[ParameterDoc]
|
|
550
|
+
Extracted parameters
|
|
551
|
+
"""
|
|
552
|
+
parameters = []
|
|
553
|
+
properties = schema.get("properties", {})
|
|
554
|
+
required = set(schema.get("required", []))
|
|
555
|
+
|
|
556
|
+
for prop_name, prop_schema in properties.items():
|
|
557
|
+
param_type = prop_schema.get("type", "any")
|
|
558
|
+
description = prop_schema.get("description", "")
|
|
559
|
+
default = prop_schema.get("default")
|
|
560
|
+
|
|
561
|
+
# Handle enum
|
|
562
|
+
enum_values = prop_schema.get("enum")
|
|
563
|
+
if enum_values:
|
|
564
|
+
enum_values = [str(v) for v in enum_values]
|
|
565
|
+
|
|
566
|
+
parameters.append(
|
|
567
|
+
ParameterDoc(
|
|
568
|
+
name=prop_name,
|
|
569
|
+
type_hint=param_type,
|
|
570
|
+
description=description,
|
|
571
|
+
required=prop_name in required,
|
|
572
|
+
default=repr(default) if default is not None else None,
|
|
573
|
+
enum_values=enum_values,
|
|
574
|
+
)
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
return parameters
|
|
578
|
+
|
|
579
|
+
@staticmethod
|
|
580
|
+
def _generate_node_yaml_example(
|
|
581
|
+
cls: type, kind: str, yaml_schema: dict[str, Any] | None
|
|
582
|
+
) -> str:
|
|
583
|
+
"""Generate YAML usage example for node.
|
|
584
|
+
|
|
585
|
+
Parameters
|
|
586
|
+
----------
|
|
587
|
+
cls : type
|
|
588
|
+
Node class
|
|
589
|
+
kind : str
|
|
590
|
+
Node kind
|
|
591
|
+
yaml_schema : dict[str, Any] | None
|
|
592
|
+
Explicit schema if available
|
|
593
|
+
|
|
594
|
+
Returns
|
|
595
|
+
-------
|
|
596
|
+
str
|
|
597
|
+
YAML example
|
|
598
|
+
"""
|
|
599
|
+
if yaml_schema:
|
|
600
|
+
return SchemaGenerator.generate_example_yaml(kind, yaml_schema)
|
|
601
|
+
|
|
602
|
+
# Fallback basic example
|
|
603
|
+
return f"""- kind: {kind}
|
|
604
|
+
metadata:
|
|
605
|
+
name: my_{kind.replace("_node", "")}
|
|
606
|
+
spec:
|
|
607
|
+
# Add configuration here
|
|
608
|
+
dependencies: []"""
|
|
609
|
+
|
|
610
|
+
@staticmethod
|
|
611
|
+
def extract_tool_doc(func: Callable) -> ToolDoc:
|
|
612
|
+
"""Extract documentation from a tool function.
|
|
613
|
+
|
|
614
|
+
Parameters
|
|
615
|
+
----------
|
|
616
|
+
func : Callable
|
|
617
|
+
Tool function to document
|
|
618
|
+
|
|
619
|
+
Returns
|
|
620
|
+
-------
|
|
621
|
+
ToolDoc
|
|
622
|
+
Extracted tool documentation
|
|
623
|
+
"""
|
|
624
|
+
description, full_docstring, examples = DocExtractor.extract_docstring_parts(func)
|
|
625
|
+
parameters = DocExtractor.extract_parameters(func)
|
|
626
|
+
|
|
627
|
+
# Get return type
|
|
628
|
+
try:
|
|
629
|
+
hints = get_type_hints(func)
|
|
630
|
+
return_type = hints.get("return")
|
|
631
|
+
return_type_str = DocExtractor._format_type_hint(return_type) if return_type else "Any"
|
|
632
|
+
except Exception:
|
|
633
|
+
return_type_str = "Any"
|
|
634
|
+
|
|
635
|
+
# Check if async
|
|
636
|
+
is_async = inspect.iscoroutinefunction(func)
|
|
637
|
+
|
|
638
|
+
# Generate module path
|
|
639
|
+
module_path = f"{func.__module__}.{func.__name__}"
|
|
640
|
+
|
|
641
|
+
# Generate YAML example for agent usage
|
|
642
|
+
yaml_example = DocExtractor._generate_tool_yaml_example(func)
|
|
643
|
+
|
|
644
|
+
return ToolDoc(
|
|
645
|
+
name=func.__name__,
|
|
646
|
+
module_path=module_path,
|
|
647
|
+
description=description,
|
|
648
|
+
full_docstring=full_docstring,
|
|
649
|
+
parameters=parameters,
|
|
650
|
+
examples=examples,
|
|
651
|
+
yaml_example=yaml_example,
|
|
652
|
+
namespace="core",
|
|
653
|
+
return_type=return_type_str,
|
|
654
|
+
is_async=is_async,
|
|
655
|
+
)
|
|
656
|
+
|
|
657
|
+
@staticmethod
|
|
658
|
+
def _generate_tool_yaml_example(func: Callable) -> str:
|
|
659
|
+
"""Generate YAML example showing tool usage with agents.
|
|
660
|
+
|
|
661
|
+
Parameters
|
|
662
|
+
----------
|
|
663
|
+
func : Callable
|
|
664
|
+
Tool function
|
|
665
|
+
|
|
666
|
+
Returns
|
|
667
|
+
-------
|
|
668
|
+
str
|
|
669
|
+
YAML example
|
|
670
|
+
"""
|
|
671
|
+
module_path = f"{func.__module__}.{func.__name__}"
|
|
672
|
+
return f"""- kind: agent_node
|
|
673
|
+
metadata:
|
|
674
|
+
name: my_agent
|
|
675
|
+
spec:
|
|
676
|
+
tools:
|
|
677
|
+
- {module_path}
|
|
678
|
+
# ... other config"""
|