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,415 @@
|
|
|
1
|
+
"""Azure OpenAI LLM adapter for hexDAG framework."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import time
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from hexdag.core.ports.healthcheck import HealthStatus
|
|
8
|
+
from hexdag.core.ports.llm import (
|
|
9
|
+
LLM,
|
|
10
|
+
ImageInput,
|
|
11
|
+
LLMResponse,
|
|
12
|
+
Message,
|
|
13
|
+
MessageList,
|
|
14
|
+
SupportsEmbedding,
|
|
15
|
+
SupportsGeneration,
|
|
16
|
+
ToolCall,
|
|
17
|
+
)
|
|
18
|
+
from openai import AsyncAzureOpenAI
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class AzureOpenAIAdapter(LLM, SupportsGeneration, SupportsEmbedding):
|
|
22
|
+
"""Azure OpenAI adapter for LLM port with embedding support.
|
|
23
|
+
|
|
24
|
+
Supports Azure-hosted OpenAI endpoints with deployment-based model access.
|
|
25
|
+
Compatible with GPT-4, GPT-3.5-turbo, fine-tuned models, and text-embedding models.
|
|
26
|
+
|
|
27
|
+
This adapter implements both LLM (text generation) and embedding functionality,
|
|
28
|
+
allowing unified API management for Azure OpenAI resources.
|
|
29
|
+
|
|
30
|
+
Parameters
|
|
31
|
+
----------
|
|
32
|
+
api_key : str
|
|
33
|
+
Azure OpenAI API key (auto-resolved from AZURE_OPENAI_API_KEY)
|
|
34
|
+
resource_name : str
|
|
35
|
+
Azure OpenAI resource name (e.g., "my-openai-resource")
|
|
36
|
+
deployment_id : str
|
|
37
|
+
Azure deployment name (e.g., "gpt-4", "gpt-35-turbo")
|
|
38
|
+
api_version : str, optional
|
|
39
|
+
Azure OpenAI API version (default: "2024-02-15-preview")
|
|
40
|
+
temperature : float, optional
|
|
41
|
+
Sampling temperature 0.0-2.0 (default: 0.7)
|
|
42
|
+
max_tokens : int, optional
|
|
43
|
+
Maximum tokens in response (default: None - model default)
|
|
44
|
+
timeout : float, optional
|
|
45
|
+
Request timeout in seconds (default: 30.0)
|
|
46
|
+
|
|
47
|
+
Examples
|
|
48
|
+
--------
|
|
49
|
+
YAML configuration::
|
|
50
|
+
|
|
51
|
+
nodes:
|
|
52
|
+
- kind: llm_node
|
|
53
|
+
metadata:
|
|
54
|
+
name: azure_analyzer
|
|
55
|
+
spec:
|
|
56
|
+
adapter:
|
|
57
|
+
type: azure_openai
|
|
58
|
+
params:
|
|
59
|
+
resource_name: "my-openai-eastus"
|
|
60
|
+
deployment_id: "gpt-4"
|
|
61
|
+
api_version: "2024-02-15-preview"
|
|
62
|
+
temperature: 0.7
|
|
63
|
+
prompt_template: "Analyze: {{input}}"
|
|
64
|
+
|
|
65
|
+
Python usage (LLM + Embeddings)::
|
|
66
|
+
|
|
67
|
+
from hexdag_plugins.azure import AzureOpenAIAdapter
|
|
68
|
+
|
|
69
|
+
# Unified adapter for both text generation and embeddings
|
|
70
|
+
adapter = AzureOpenAIAdapter(
|
|
71
|
+
api_key="your-key", # or auto-resolved from env
|
|
72
|
+
resource_name="my-openai-resource",
|
|
73
|
+
deployment_id="gpt-4",
|
|
74
|
+
embedding_deployment_id="text-embedding-3-small",
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# Text generation
|
|
78
|
+
messages = [{"role": "user", "content": "Hello"}]
|
|
79
|
+
response = await adapter.aresponse(messages)
|
|
80
|
+
|
|
81
|
+
# Embeddings
|
|
82
|
+
embedding = await adapter.aembed("Hello, world!")
|
|
83
|
+
embeddings = await adapter.aembed_batch(["Text 1", "Text 2"])
|
|
84
|
+
|
|
85
|
+
Pure embedding adapter::
|
|
86
|
+
|
|
87
|
+
# For embedding-only use cases, deployment_id is still required
|
|
88
|
+
# but can be a placeholder since aresponse() won't be used
|
|
89
|
+
adapter = AzureOpenAIAdapter(
|
|
90
|
+
resource_name="my-openai-resource",
|
|
91
|
+
deployment_id="gpt-4", # Required by protocol
|
|
92
|
+
embedding_deployment_id="text-embedding-3-small",
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# Only use embedding methods
|
|
96
|
+
embedding = await adapter.aembed("Document text")
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
def __init__(
|
|
100
|
+
self,
|
|
101
|
+
api_key: str | None = None,
|
|
102
|
+
resource_name: str = "",
|
|
103
|
+
deployment_id: str = "",
|
|
104
|
+
api_version: str = "2024-02-15-preview",
|
|
105
|
+
temperature: float = 0.7,
|
|
106
|
+
max_tokens: int | None = None,
|
|
107
|
+
timeout: float = 30.0,
|
|
108
|
+
embedding_deployment_id: str | None = None,
|
|
109
|
+
embedding_dimensions: int | None = None,
|
|
110
|
+
):
|
|
111
|
+
"""Initialize Azure OpenAI adapter.
|
|
112
|
+
|
|
113
|
+
Args
|
|
114
|
+
----
|
|
115
|
+
api_key: Azure OpenAI API key (auto-resolved from AZURE_OPENAI_API_KEY)
|
|
116
|
+
resource_name: Azure OpenAI resource name
|
|
117
|
+
deployment_id: Azure deployment name for text generation
|
|
118
|
+
api_version: API version (default: "2024-02-15-preview")
|
|
119
|
+
temperature: Sampling temperature (default: 0.7)
|
|
120
|
+
max_tokens: Maximum tokens in response (default: None)
|
|
121
|
+
timeout: Request timeout in seconds (default: 30.0)
|
|
122
|
+
embedding_deployment_id: Azure deployment name for embeddings (optional)
|
|
123
|
+
embedding_dimensions: Embedding dimensionality for text-embedding-3 models (optional)
|
|
124
|
+
"""
|
|
125
|
+
self.api_key = api_key or os.getenv("AZURE_OPENAI_API_KEY")
|
|
126
|
+
if not self.api_key:
|
|
127
|
+
raise ValueError("api_key required (pass directly or set AZURE_OPENAI_API_KEY)")
|
|
128
|
+
self.resource_name = resource_name
|
|
129
|
+
self.deployment_id = deployment_id
|
|
130
|
+
self.api_version = api_version
|
|
131
|
+
self.temperature = temperature
|
|
132
|
+
self.max_tokens = max_tokens
|
|
133
|
+
self.timeout = timeout
|
|
134
|
+
self.embedding_deployment_id = embedding_deployment_id
|
|
135
|
+
self.embedding_dimensions = embedding_dimensions
|
|
136
|
+
|
|
137
|
+
# Construct Azure endpoint
|
|
138
|
+
self.api_base = f"https://{resource_name}.openai.azure.com"
|
|
139
|
+
|
|
140
|
+
# Lazy import to avoid dependency issues
|
|
141
|
+
self._client = None
|
|
142
|
+
|
|
143
|
+
def _get_client(self) -> AsyncAzureOpenAI:
|
|
144
|
+
"""Get or create OpenAI client configured for Azure."""
|
|
145
|
+
if self._client is None:
|
|
146
|
+
self._client = AsyncAzureOpenAI(
|
|
147
|
+
api_key=self.api_key,
|
|
148
|
+
api_version=self.api_version,
|
|
149
|
+
azure_endpoint=self.api_base,
|
|
150
|
+
timeout=self.timeout,
|
|
151
|
+
)
|
|
152
|
+
return self._client
|
|
153
|
+
|
|
154
|
+
async def aresponse(self, messages: MessageList) -> str | None:
|
|
155
|
+
"""Generate response from Azure OpenAI.
|
|
156
|
+
|
|
157
|
+
Args
|
|
158
|
+
----
|
|
159
|
+
messages: List of conversation messages
|
|
160
|
+
|
|
161
|
+
Returns
|
|
162
|
+
-------
|
|
163
|
+
Generated response text or None if failed
|
|
164
|
+
"""
|
|
165
|
+
try:
|
|
166
|
+
client = self._get_client()
|
|
167
|
+
|
|
168
|
+
# Convert MessageList to OpenAI format
|
|
169
|
+
openai_messages = [{"role": msg.role, "content": msg.content} for msg in messages]
|
|
170
|
+
|
|
171
|
+
response = await client.chat.completions.create(
|
|
172
|
+
model=self.deployment_id, # Azure uses deployment_id as model
|
|
173
|
+
messages=openai_messages,
|
|
174
|
+
temperature=self.temperature,
|
|
175
|
+
max_tokens=self.max_tokens,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
return response.choices[0].message.content
|
|
179
|
+
|
|
180
|
+
except Exception as e:
|
|
181
|
+
# Log error but don't expose details
|
|
182
|
+
print(f"Azure OpenAI error: {e}")
|
|
183
|
+
return None
|
|
184
|
+
|
|
185
|
+
async def aresponse_with_tools(
|
|
186
|
+
self,
|
|
187
|
+
messages: MessageList,
|
|
188
|
+
tools: list[dict[str, Any]],
|
|
189
|
+
tool_choice: str | dict[str, Any] = "auto",
|
|
190
|
+
) -> LLMResponse:
|
|
191
|
+
"""Generate response with native tool calling support.
|
|
192
|
+
|
|
193
|
+
Args
|
|
194
|
+
----
|
|
195
|
+
messages: Conversation messages
|
|
196
|
+
tools: Tool definitions in OpenAI format
|
|
197
|
+
tool_choice: Tool selection strategy ("auto", "none", or specific tool)
|
|
198
|
+
|
|
199
|
+
Returns
|
|
200
|
+
-------
|
|
201
|
+
LLMResponse with content and optional tool calls
|
|
202
|
+
"""
|
|
203
|
+
try:
|
|
204
|
+
client = self._get_client()
|
|
205
|
+
|
|
206
|
+
# Convert MessageList to OpenAI format
|
|
207
|
+
openai_messages = [{"role": msg.role, "content": msg.content} for msg in messages]
|
|
208
|
+
|
|
209
|
+
response = await client.chat.completions.create(
|
|
210
|
+
model=self.deployment_id,
|
|
211
|
+
messages=openai_messages,
|
|
212
|
+
tools=tools,
|
|
213
|
+
tool_choice=tool_choice,
|
|
214
|
+
temperature=self.temperature,
|
|
215
|
+
max_tokens=self.max_tokens,
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
message = response.choices[0].message
|
|
219
|
+
finish_reason = response.choices[0].finish_reason
|
|
220
|
+
|
|
221
|
+
# Extract tool calls if present
|
|
222
|
+
tool_calls = None
|
|
223
|
+
if message.tool_calls:
|
|
224
|
+
tool_calls = [
|
|
225
|
+
ToolCall(
|
|
226
|
+
id=tc.id,
|
|
227
|
+
name=tc.function.name,
|
|
228
|
+
arguments=(
|
|
229
|
+
tc.function.arguments if isinstance(tc.function.arguments, dict) else {}
|
|
230
|
+
),
|
|
231
|
+
)
|
|
232
|
+
for tc in message.tool_calls
|
|
233
|
+
]
|
|
234
|
+
|
|
235
|
+
return LLMResponse(
|
|
236
|
+
content=message.content,
|
|
237
|
+
tool_calls=tool_calls,
|
|
238
|
+
finish_reason=finish_reason,
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
except Exception as e:
|
|
242
|
+
print(f"Azure OpenAI tool calling error: {e}")
|
|
243
|
+
return LLMResponse(
|
|
244
|
+
content=None,
|
|
245
|
+
tool_calls=None,
|
|
246
|
+
finish_reason="error",
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
async def ahealth_check(self) -> HealthStatus:
|
|
250
|
+
"""Check Azure OpenAI adapter health and connectivity.
|
|
251
|
+
|
|
252
|
+
Returns
|
|
253
|
+
-------
|
|
254
|
+
HealthStatus with connectivity and model availability details
|
|
255
|
+
"""
|
|
256
|
+
try:
|
|
257
|
+
start_time = time.time()
|
|
258
|
+
|
|
259
|
+
# Simple health check with minimal token usage
|
|
260
|
+
test_messages = [Message(role="user", content="Hi")]
|
|
261
|
+
response = await self.aresponse(test_messages)
|
|
262
|
+
|
|
263
|
+
latency_ms = (time.time() - start_time) * 1000
|
|
264
|
+
|
|
265
|
+
if response:
|
|
266
|
+
return HealthStatus(
|
|
267
|
+
status="healthy",
|
|
268
|
+
adapter_name=f"AzureOpenAI[{self.deployment_id}]",
|
|
269
|
+
latency_ms=latency_ms,
|
|
270
|
+
details={
|
|
271
|
+
"resource": self.resource_name,
|
|
272
|
+
"deployment": self.deployment_id,
|
|
273
|
+
"api_version": self.api_version,
|
|
274
|
+
},
|
|
275
|
+
)
|
|
276
|
+
else:
|
|
277
|
+
return HealthStatus(
|
|
278
|
+
status="unhealthy",
|
|
279
|
+
adapter_name=f"AzureOpenAI[{self.deployment_id}]",
|
|
280
|
+
latency_ms=latency_ms,
|
|
281
|
+
details={"error": "No response from API"},
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
except Exception as e:
|
|
285
|
+
return HealthStatus(
|
|
286
|
+
status="unhealthy",
|
|
287
|
+
adapter_name=f"AzureOpenAI[{self.deployment_id}]",
|
|
288
|
+
latency_ms=0.0,
|
|
289
|
+
details={"error": str(e)},
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
# ========== Embedding Methods (SupportsEmbedding Protocol) ==========
|
|
293
|
+
|
|
294
|
+
async def aembed(self, text: str) -> list[float]:
|
|
295
|
+
"""Generate embedding vector for a single text input.
|
|
296
|
+
|
|
297
|
+
Uses the embedding_deployment_id if configured, otherwise raises error.
|
|
298
|
+
|
|
299
|
+
Args
|
|
300
|
+
----
|
|
301
|
+
text: Text string to embed
|
|
302
|
+
|
|
303
|
+
Returns
|
|
304
|
+
-------
|
|
305
|
+
List of floats representing the embedding vector
|
|
306
|
+
|
|
307
|
+
Raises
|
|
308
|
+
------
|
|
309
|
+
ValueError: If embedding_deployment_id not configured
|
|
310
|
+
"""
|
|
311
|
+
if not self.embedding_deployment_id:
|
|
312
|
+
raise ValueError(
|
|
313
|
+
"embedding_deployment_id must be set to use embedding functionality. "
|
|
314
|
+
"Create adapter with: AzureOpenAIAdapter(..., "
|
|
315
|
+
"embedding_deployment_id='text-embedding-3-small')"
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
try:
|
|
319
|
+
client = self._get_client()
|
|
320
|
+
|
|
321
|
+
request_params: dict[str, Any] = {
|
|
322
|
+
"model": self.embedding_deployment_id,
|
|
323
|
+
"input": text,
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if self.embedding_dimensions is not None:
|
|
327
|
+
request_params["dimensions"] = self.embedding_dimensions
|
|
328
|
+
|
|
329
|
+
response = await client.embeddings.create(**request_params)
|
|
330
|
+
|
|
331
|
+
if response.data and len(response.data) > 0:
|
|
332
|
+
return response.data[0].embedding
|
|
333
|
+
|
|
334
|
+
return []
|
|
335
|
+
|
|
336
|
+
except Exception as e:
|
|
337
|
+
print(f"Azure OpenAI embedding error: {e}")
|
|
338
|
+
raise
|
|
339
|
+
|
|
340
|
+
async def aembed_batch(self, texts: list[str]) -> list[list[float]]:
|
|
341
|
+
"""Generate embeddings for multiple texts efficiently.
|
|
342
|
+
|
|
343
|
+
Args
|
|
344
|
+
----
|
|
345
|
+
texts: List of text strings to embed
|
|
346
|
+
|
|
347
|
+
Returns
|
|
348
|
+
-------
|
|
349
|
+
List of embedding vectors, one per input text
|
|
350
|
+
"""
|
|
351
|
+
if not self.embedding_deployment_id:
|
|
352
|
+
raise ValueError("embedding_deployment_id must be set to use embedding functionality")
|
|
353
|
+
|
|
354
|
+
try:
|
|
355
|
+
client = self._get_client()
|
|
356
|
+
|
|
357
|
+
request_params: dict[str, Any] = {
|
|
358
|
+
"model": self.embedding_deployment_id,
|
|
359
|
+
"input": texts,
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if self.embedding_dimensions is not None:
|
|
363
|
+
request_params["dimensions"] = self.embedding_dimensions
|
|
364
|
+
|
|
365
|
+
response = await client.embeddings.create(**request_params)
|
|
366
|
+
|
|
367
|
+
if response.data:
|
|
368
|
+
# Sort by index to ensure correct order
|
|
369
|
+
sorted_data = sorted(response.data, key=lambda x: x.index)
|
|
370
|
+
return [item.embedding for item in sorted_data]
|
|
371
|
+
|
|
372
|
+
return [[] for _ in texts]
|
|
373
|
+
|
|
374
|
+
except Exception as e:
|
|
375
|
+
print(f"Azure OpenAI batch embedding error: {e}")
|
|
376
|
+
raise
|
|
377
|
+
|
|
378
|
+
async def aembed_image(self, image: ImageInput) -> list[float]:
|
|
379
|
+
"""Generate embedding vector for a single image input.
|
|
380
|
+
|
|
381
|
+
Note: Azure OpenAI does not currently support image embeddings via the
|
|
382
|
+
embeddings API. This method is included for protocol compliance but will
|
|
383
|
+
raise NotImplementedError.
|
|
384
|
+
|
|
385
|
+
Args
|
|
386
|
+
----
|
|
387
|
+
image: Image to embed (file path, bytes, or base64)
|
|
388
|
+
|
|
389
|
+
Raises
|
|
390
|
+
------
|
|
391
|
+
NotImplementedError: Azure OpenAI doesn't support image embeddings
|
|
392
|
+
"""
|
|
393
|
+
raise NotImplementedError(
|
|
394
|
+
"Azure OpenAI does not support image embeddings via the embeddings API. "
|
|
395
|
+
"For multimodal embeddings, consider using vision models with aresponse_with_vision()."
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
async def aembed_image_batch(self, images: list[ImageInput]) -> list[list[float]]:
|
|
399
|
+
"""Generate embeddings for multiple images efficiently.
|
|
400
|
+
|
|
401
|
+
Note: Azure OpenAI does not currently support image embeddings via the
|
|
402
|
+
embeddings API. This method is included for protocol compliance but will
|
|
403
|
+
raise NotImplementedError.
|
|
404
|
+
|
|
405
|
+
Args
|
|
406
|
+
----
|
|
407
|
+
images: List of images to embed
|
|
408
|
+
|
|
409
|
+
Raises
|
|
410
|
+
------
|
|
411
|
+
NotImplementedError: Azure OpenAI doesn't support image embeddings
|
|
412
|
+
"""
|
|
413
|
+
raise NotImplementedError(
|
|
414
|
+
"Azure OpenAI does not support image embeddings via the embeddings API"
|
|
415
|
+
)
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "hexdag-azure"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Azure OpenAI adapter plugin for hexDAG framework"
|
|
5
|
+
authors = [
|
|
6
|
+
{ name = "HexDAG Team", email = "team@hexdag.ai" }
|
|
7
|
+
]
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
license = { text = "MIT" }
|
|
10
|
+
requires-python = ">=3.12"
|
|
11
|
+
keywords = ["hexdag", "azure", "openai", "llm", "adapter", "plugin"]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 4 - Beta",
|
|
14
|
+
"Intended Audience :: Developers",
|
|
15
|
+
"License :: OSI Approved :: MIT License",
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Programming Language :: Python :: 3.12",
|
|
18
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
19
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
dependencies = [
|
|
23
|
+
"openai>=1.0.0",
|
|
24
|
+
# hexdag is the parent project - not a PyPI dependency
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.optional-dependencies]
|
|
28
|
+
# Azure SDK dependencies (lazy-loaded by adapters)
|
|
29
|
+
blob = ["azure-storage-blob>=12.13.0", "azure-identity>=1.13.0"]
|
|
30
|
+
cosmos = ["azure-cosmos>=4.3.0", "azure-identity>=1.13.0"]
|
|
31
|
+
keyvault = ["azure-keyvault-secrets>=4.4.0", "azure-identity>=1.13.0"]
|
|
32
|
+
all = [
|
|
33
|
+
"azure-storage-blob>=12.13.0",
|
|
34
|
+
"azure-cosmos>=4.3.0",
|
|
35
|
+
"azure-keyvault-secrets>=4.4.0",
|
|
36
|
+
"azure-identity>=1.13.0",
|
|
37
|
+
]
|
|
38
|
+
dev = [
|
|
39
|
+
"pytest>=8.0.0",
|
|
40
|
+
"pytest-asyncio>=0.21.0",
|
|
41
|
+
"pytest-mock>=3.14.0",
|
|
42
|
+
"pytest-cov>=5.0.0",
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
[project.urls]
|
|
46
|
+
"Homepage" = "https://github.com/hexdag/hexdag-azure"
|
|
47
|
+
"Bug Reports" = "https://github.com/hexdag/hexdag-azure/issues"
|
|
48
|
+
"Source" = "https://github.com/hexdag/hexdag-azure"
|
|
49
|
+
"Documentation" = "https://hexdag.ai/docs/plugins/azure"
|
|
50
|
+
|
|
51
|
+
[build-system]
|
|
52
|
+
requires = ["hatchling"]
|
|
53
|
+
build-backend = "hatchling.build"
|
|
54
|
+
|
|
55
|
+
[tool.hatch.build]
|
|
56
|
+
include = [
|
|
57
|
+
"hexdag_plugins/azure/**/*.py",
|
|
58
|
+
"README.md",
|
|
59
|
+
"LICENSE",
|
|
60
|
+
]
|
|
61
|
+
exclude = [
|
|
62
|
+
"tests",
|
|
63
|
+
"*.pyc",
|
|
64
|
+
"__pycache__",
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
[tool.hatch.build.targets.wheel]
|
|
68
|
+
packages = ["hexdag_plugins/azure"]
|
|
69
|
+
|
|
70
|
+
# Plugin registration for hexDAG
|
|
71
|
+
[tool.hexdag.plugin]
|
|
72
|
+
name = "azure"
|
|
73
|
+
module = "hexdag_plugins.azure"
|
|
74
|
+
port = "llm"
|
|
75
|
+
description = "Production-ready Azure OpenAI adapter with deployment-based model access"
|
|
76
|
+
requires_env = ["AZURE_OPENAI_API_KEY"]
|
|
77
|
+
|
|
78
|
+
[tool.pytest.ini_options]
|
|
79
|
+
testpaths = ["tests"]
|
|
80
|
+
python_files = ["test_*.py"]
|
|
81
|
+
python_classes = ["Test*"]
|
|
82
|
+
python_functions = ["test_*"]
|
|
83
|
+
asyncio_mode = "auto"
|
|
84
|
+
|
|
85
|
+
[tool.ruff]
|
|
86
|
+
line-length = 100
|
|
87
|
+
target-version = "py312"
|
|
88
|
+
|
|
89
|
+
[tool.ruff.lint]
|
|
90
|
+
select = [
|
|
91
|
+
"E", # pycodestyle errors
|
|
92
|
+
"W", # pycodestyle warnings
|
|
93
|
+
"F", # pyflakes
|
|
94
|
+
"UP", # pyupgrade
|
|
95
|
+
"B", # flake8-bugbear
|
|
96
|
+
"SIM", # flake8-simplify
|
|
97
|
+
"I", # isort
|
|
98
|
+
]
|
|
99
|
+
ignore = [
|
|
100
|
+
"SIM105", # contextlib.suppress doesn't work with async
|
|
101
|
+
]
|
|
102
|
+
|
|
103
|
+
[tool.mypy]
|
|
104
|
+
python_version = "3.12"
|
|
105
|
+
warn_return_any = true
|
|
106
|
+
warn_unused_configs = true
|
|
107
|
+
disallow_untyped_defs = true
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Tests for hexdag-azure plugin."""
|