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.
Files changed (261) hide show
  1. hexdag/__init__.py +116 -0
  2. hexdag/__main__.py +30 -0
  3. hexdag/adapters/executors/__init__.py +5 -0
  4. hexdag/adapters/executors/local_executor.py +316 -0
  5. hexdag/builtin/__init__.py +6 -0
  6. hexdag/builtin/adapters/__init__.py +51 -0
  7. hexdag/builtin/adapters/anthropic/__init__.py +5 -0
  8. hexdag/builtin/adapters/anthropic/anthropic_adapter.py +151 -0
  9. hexdag/builtin/adapters/database/__init__.py +6 -0
  10. hexdag/builtin/adapters/database/csv/csv_adapter.py +249 -0
  11. hexdag/builtin/adapters/database/pgvector/__init__.py +5 -0
  12. hexdag/builtin/adapters/database/pgvector/pgvector_adapter.py +478 -0
  13. hexdag/builtin/adapters/database/sqlalchemy/sqlalchemy_adapter.py +252 -0
  14. hexdag/builtin/adapters/database/sqlite/__init__.py +5 -0
  15. hexdag/builtin/adapters/database/sqlite/sqlite_adapter.py +410 -0
  16. hexdag/builtin/adapters/local/README.md +59 -0
  17. hexdag/builtin/adapters/local/__init__.py +7 -0
  18. hexdag/builtin/adapters/local/local_observer_manager.py +696 -0
  19. hexdag/builtin/adapters/memory/__init__.py +47 -0
  20. hexdag/builtin/adapters/memory/file_memory_adapter.py +297 -0
  21. hexdag/builtin/adapters/memory/in_memory_memory.py +216 -0
  22. hexdag/builtin/adapters/memory/schemas.py +57 -0
  23. hexdag/builtin/adapters/memory/session_memory.py +178 -0
  24. hexdag/builtin/adapters/memory/sqlite_memory_adapter.py +215 -0
  25. hexdag/builtin/adapters/memory/state_memory.py +280 -0
  26. hexdag/builtin/adapters/mock/README.md +89 -0
  27. hexdag/builtin/adapters/mock/__init__.py +15 -0
  28. hexdag/builtin/adapters/mock/hexdag.toml +50 -0
  29. hexdag/builtin/adapters/mock/mock_database.py +225 -0
  30. hexdag/builtin/adapters/mock/mock_embedding.py +223 -0
  31. hexdag/builtin/adapters/mock/mock_llm.py +177 -0
  32. hexdag/builtin/adapters/mock/mock_tool_adapter.py +192 -0
  33. hexdag/builtin/adapters/mock/mock_tool_router.py +232 -0
  34. hexdag/builtin/adapters/openai/__init__.py +5 -0
  35. hexdag/builtin/adapters/openai/openai_adapter.py +634 -0
  36. hexdag/builtin/adapters/secret/__init__.py +7 -0
  37. hexdag/builtin/adapters/secret/local_secret_adapter.py +248 -0
  38. hexdag/builtin/adapters/unified_tool_router.py +280 -0
  39. hexdag/builtin/macros/__init__.py +17 -0
  40. hexdag/builtin/macros/conversation_agent.py +390 -0
  41. hexdag/builtin/macros/llm_macro.py +151 -0
  42. hexdag/builtin/macros/reasoning_agent.py +423 -0
  43. hexdag/builtin/macros/tool_macro.py +380 -0
  44. hexdag/builtin/nodes/__init__.py +38 -0
  45. hexdag/builtin/nodes/_discovery.py +123 -0
  46. hexdag/builtin/nodes/agent_node.py +696 -0
  47. hexdag/builtin/nodes/base_node_factory.py +242 -0
  48. hexdag/builtin/nodes/composite_node.py +926 -0
  49. hexdag/builtin/nodes/data_node.py +201 -0
  50. hexdag/builtin/nodes/expression_node.py +487 -0
  51. hexdag/builtin/nodes/function_node.py +454 -0
  52. hexdag/builtin/nodes/llm_node.py +491 -0
  53. hexdag/builtin/nodes/loop_node.py +920 -0
  54. hexdag/builtin/nodes/mapped_input.py +518 -0
  55. hexdag/builtin/nodes/port_call_node.py +269 -0
  56. hexdag/builtin/nodes/tool_call_node.py +195 -0
  57. hexdag/builtin/nodes/tool_utils.py +390 -0
  58. hexdag/builtin/prompts/__init__.py +68 -0
  59. hexdag/builtin/prompts/base.py +422 -0
  60. hexdag/builtin/prompts/chat_prompts.py +303 -0
  61. hexdag/builtin/prompts/error_correction_prompts.py +320 -0
  62. hexdag/builtin/prompts/tool_prompts.py +160 -0
  63. hexdag/builtin/tools/builtin_tools.py +84 -0
  64. hexdag/builtin/tools/database_tools.py +164 -0
  65. hexdag/cli/__init__.py +17 -0
  66. hexdag/cli/__main__.py +7 -0
  67. hexdag/cli/commands/__init__.py +27 -0
  68. hexdag/cli/commands/build_cmd.py +812 -0
  69. hexdag/cli/commands/create_cmd.py +208 -0
  70. hexdag/cli/commands/docs_cmd.py +293 -0
  71. hexdag/cli/commands/generate_types_cmd.py +252 -0
  72. hexdag/cli/commands/init_cmd.py +188 -0
  73. hexdag/cli/commands/pipeline_cmd.py +494 -0
  74. hexdag/cli/commands/plugin_dev_cmd.py +529 -0
  75. hexdag/cli/commands/plugins_cmd.py +441 -0
  76. hexdag/cli/commands/studio_cmd.py +101 -0
  77. hexdag/cli/commands/validate_cmd.py +221 -0
  78. hexdag/cli/main.py +84 -0
  79. hexdag/core/__init__.py +83 -0
  80. hexdag/core/config/__init__.py +20 -0
  81. hexdag/core/config/loader.py +479 -0
  82. hexdag/core/config/models.py +150 -0
  83. hexdag/core/configurable.py +294 -0
  84. hexdag/core/context/__init__.py +37 -0
  85. hexdag/core/context/execution_context.py +378 -0
  86. hexdag/core/docs/__init__.py +26 -0
  87. hexdag/core/docs/extractors.py +678 -0
  88. hexdag/core/docs/generators.py +890 -0
  89. hexdag/core/docs/models.py +120 -0
  90. hexdag/core/domain/__init__.py +10 -0
  91. hexdag/core/domain/dag.py +1225 -0
  92. hexdag/core/exceptions.py +234 -0
  93. hexdag/core/expression_parser.py +569 -0
  94. hexdag/core/logging.py +449 -0
  95. hexdag/core/models/__init__.py +17 -0
  96. hexdag/core/models/base.py +138 -0
  97. hexdag/core/orchestration/__init__.py +46 -0
  98. hexdag/core/orchestration/body_executor.py +481 -0
  99. hexdag/core/orchestration/components/__init__.py +97 -0
  100. hexdag/core/orchestration/components/adapter_lifecycle_manager.py +113 -0
  101. hexdag/core/orchestration/components/checkpoint_manager.py +134 -0
  102. hexdag/core/orchestration/components/execution_coordinator.py +360 -0
  103. hexdag/core/orchestration/components/health_check_manager.py +176 -0
  104. hexdag/core/orchestration/components/input_mapper.py +143 -0
  105. hexdag/core/orchestration/components/lifecycle_manager.py +583 -0
  106. hexdag/core/orchestration/components/node_executor.py +377 -0
  107. hexdag/core/orchestration/components/secret_manager.py +202 -0
  108. hexdag/core/orchestration/components/wave_executor.py +158 -0
  109. hexdag/core/orchestration/constants.py +17 -0
  110. hexdag/core/orchestration/events/README.md +312 -0
  111. hexdag/core/orchestration/events/__init__.py +104 -0
  112. hexdag/core/orchestration/events/batching.py +330 -0
  113. hexdag/core/orchestration/events/decorators.py +139 -0
  114. hexdag/core/orchestration/events/events.py +573 -0
  115. hexdag/core/orchestration/events/observers/__init__.py +30 -0
  116. hexdag/core/orchestration/events/observers/core_observers.py +690 -0
  117. hexdag/core/orchestration/events/observers/models.py +111 -0
  118. hexdag/core/orchestration/events/taxonomy.py +269 -0
  119. hexdag/core/orchestration/hook_context.py +237 -0
  120. hexdag/core/orchestration/hooks.py +437 -0
  121. hexdag/core/orchestration/models.py +418 -0
  122. hexdag/core/orchestration/orchestrator.py +910 -0
  123. hexdag/core/orchestration/orchestrator_factory.py +275 -0
  124. hexdag/core/orchestration/port_wrappers.py +327 -0
  125. hexdag/core/orchestration/prompt/__init__.py +32 -0
  126. hexdag/core/orchestration/prompt/template.py +332 -0
  127. hexdag/core/pipeline_builder/__init__.py +21 -0
  128. hexdag/core/pipeline_builder/component_instantiator.py +386 -0
  129. hexdag/core/pipeline_builder/include_tag.py +265 -0
  130. hexdag/core/pipeline_builder/pipeline_config.py +133 -0
  131. hexdag/core/pipeline_builder/py_tag.py +223 -0
  132. hexdag/core/pipeline_builder/tag_discovery.py +268 -0
  133. hexdag/core/pipeline_builder/yaml_builder.py +1196 -0
  134. hexdag/core/pipeline_builder/yaml_validator.py +569 -0
  135. hexdag/core/ports/__init__.py +65 -0
  136. hexdag/core/ports/api_call.py +133 -0
  137. hexdag/core/ports/database.py +489 -0
  138. hexdag/core/ports/embedding.py +215 -0
  139. hexdag/core/ports/executor.py +237 -0
  140. hexdag/core/ports/file_storage.py +117 -0
  141. hexdag/core/ports/healthcheck.py +87 -0
  142. hexdag/core/ports/llm.py +551 -0
  143. hexdag/core/ports/memory.py +70 -0
  144. hexdag/core/ports/observer_manager.py +130 -0
  145. hexdag/core/ports/secret.py +145 -0
  146. hexdag/core/ports/tool_router.py +94 -0
  147. hexdag/core/ports_builder.py +623 -0
  148. hexdag/core/protocols.py +273 -0
  149. hexdag/core/resolver.py +304 -0
  150. hexdag/core/schema/__init__.py +9 -0
  151. hexdag/core/schema/generator.py +742 -0
  152. hexdag/core/secrets.py +242 -0
  153. hexdag/core/types.py +413 -0
  154. hexdag/core/utils/async_warnings.py +206 -0
  155. hexdag/core/utils/schema_conversion.py +78 -0
  156. hexdag/core/utils/sql_validation.py +86 -0
  157. hexdag/core/validation/secure_json.py +148 -0
  158. hexdag/core/yaml_macro.py +517 -0
  159. hexdag/mcp_server.py +3120 -0
  160. hexdag/studio/__init__.py +10 -0
  161. hexdag/studio/build_ui.py +92 -0
  162. hexdag/studio/server/__init__.py +1 -0
  163. hexdag/studio/server/main.py +100 -0
  164. hexdag/studio/server/routes/__init__.py +9 -0
  165. hexdag/studio/server/routes/execute.py +208 -0
  166. hexdag/studio/server/routes/export.py +558 -0
  167. hexdag/studio/server/routes/files.py +207 -0
  168. hexdag/studio/server/routes/plugins.py +419 -0
  169. hexdag/studio/server/routes/validate.py +220 -0
  170. hexdag/studio/ui/index.html +13 -0
  171. hexdag/studio/ui/package-lock.json +2992 -0
  172. hexdag/studio/ui/package.json +31 -0
  173. hexdag/studio/ui/postcss.config.js +6 -0
  174. hexdag/studio/ui/public/hexdag.svg +5 -0
  175. hexdag/studio/ui/src/App.tsx +251 -0
  176. hexdag/studio/ui/src/components/Canvas.tsx +408 -0
  177. hexdag/studio/ui/src/components/ContextMenu.tsx +187 -0
  178. hexdag/studio/ui/src/components/FileBrowser.tsx +123 -0
  179. hexdag/studio/ui/src/components/Header.tsx +181 -0
  180. hexdag/studio/ui/src/components/HexdagNode.tsx +193 -0
  181. hexdag/studio/ui/src/components/NodeInspector.tsx +512 -0
  182. hexdag/studio/ui/src/components/NodePalette.tsx +262 -0
  183. hexdag/studio/ui/src/components/NodePortsSection.tsx +403 -0
  184. hexdag/studio/ui/src/components/PluginManager.tsx +347 -0
  185. hexdag/studio/ui/src/components/PortsEditor.tsx +481 -0
  186. hexdag/studio/ui/src/components/PythonEditor.tsx +195 -0
  187. hexdag/studio/ui/src/components/ValidationPanel.tsx +105 -0
  188. hexdag/studio/ui/src/components/YamlEditor.tsx +196 -0
  189. hexdag/studio/ui/src/components/index.ts +8 -0
  190. hexdag/studio/ui/src/index.css +92 -0
  191. hexdag/studio/ui/src/main.tsx +10 -0
  192. hexdag/studio/ui/src/types/index.ts +123 -0
  193. hexdag/studio/ui/src/vite-env.d.ts +1 -0
  194. hexdag/studio/ui/tailwind.config.js +29 -0
  195. hexdag/studio/ui/tsconfig.json +37 -0
  196. hexdag/studio/ui/tsconfig.node.json +13 -0
  197. hexdag/studio/ui/vite.config.ts +35 -0
  198. hexdag/visualization/__init__.py +69 -0
  199. hexdag/visualization/dag_visualizer.py +1020 -0
  200. hexdag-0.5.0.dev1.dist-info/METADATA +369 -0
  201. hexdag-0.5.0.dev1.dist-info/RECORD +261 -0
  202. hexdag-0.5.0.dev1.dist-info/WHEEL +4 -0
  203. hexdag-0.5.0.dev1.dist-info/entry_points.txt +4 -0
  204. hexdag-0.5.0.dev1.dist-info/licenses/LICENSE +190 -0
  205. hexdag_plugins/.gitignore +43 -0
  206. hexdag_plugins/README.md +73 -0
  207. hexdag_plugins/__init__.py +1 -0
  208. hexdag_plugins/azure/LICENSE +21 -0
  209. hexdag_plugins/azure/README.md +414 -0
  210. hexdag_plugins/azure/__init__.py +21 -0
  211. hexdag_plugins/azure/azure_blob_adapter.py +450 -0
  212. hexdag_plugins/azure/azure_cosmos_adapter.py +383 -0
  213. hexdag_plugins/azure/azure_keyvault_adapter.py +314 -0
  214. hexdag_plugins/azure/azure_openai_adapter.py +415 -0
  215. hexdag_plugins/azure/pyproject.toml +107 -0
  216. hexdag_plugins/azure/tests/__init__.py +1 -0
  217. hexdag_plugins/azure/tests/test_azure_blob_adapter.py +350 -0
  218. hexdag_plugins/azure/tests/test_azure_cosmos_adapter.py +323 -0
  219. hexdag_plugins/azure/tests/test_azure_keyvault_adapter.py +330 -0
  220. hexdag_plugins/azure/tests/test_azure_openai_adapter.py +329 -0
  221. hexdag_plugins/hexdag_etl/README.md +168 -0
  222. hexdag_plugins/hexdag_etl/__init__.py +53 -0
  223. hexdag_plugins/hexdag_etl/examples/01_simple_pandas_transform.py +270 -0
  224. hexdag_plugins/hexdag_etl/examples/02_simple_pandas_only.py +149 -0
  225. hexdag_plugins/hexdag_etl/examples/03_file_io_pipeline.py +109 -0
  226. hexdag_plugins/hexdag_etl/examples/test_pandas_transform.py +84 -0
  227. hexdag_plugins/hexdag_etl/hexdag.toml +25 -0
  228. hexdag_plugins/hexdag_etl/hexdag_etl/__init__.py +48 -0
  229. hexdag_plugins/hexdag_etl/hexdag_etl/nodes/__init__.py +13 -0
  230. hexdag_plugins/hexdag_etl/hexdag_etl/nodes/api_extract.py +230 -0
  231. hexdag_plugins/hexdag_etl/hexdag_etl/nodes/base_node_factory.py +181 -0
  232. hexdag_plugins/hexdag_etl/hexdag_etl/nodes/file_io.py +415 -0
  233. hexdag_plugins/hexdag_etl/hexdag_etl/nodes/outlook.py +492 -0
  234. hexdag_plugins/hexdag_etl/hexdag_etl/nodes/pandas_transform.py +563 -0
  235. hexdag_plugins/hexdag_etl/hexdag_etl/nodes/sql_extract_load.py +112 -0
  236. hexdag_plugins/hexdag_etl/pyproject.toml +82 -0
  237. hexdag_plugins/hexdag_etl/test_transform.py +54 -0
  238. hexdag_plugins/hexdag_etl/tests/test_plugin_integration.py +62 -0
  239. hexdag_plugins/mysql_adapter/LICENSE +21 -0
  240. hexdag_plugins/mysql_adapter/README.md +224 -0
  241. hexdag_plugins/mysql_adapter/__init__.py +6 -0
  242. hexdag_plugins/mysql_adapter/mysql_adapter.py +408 -0
  243. hexdag_plugins/mysql_adapter/pyproject.toml +93 -0
  244. hexdag_plugins/mysql_adapter/tests/test_mysql_adapter.py +259 -0
  245. hexdag_plugins/storage/README.md +184 -0
  246. hexdag_plugins/storage/__init__.py +19 -0
  247. hexdag_plugins/storage/file/__init__.py +5 -0
  248. hexdag_plugins/storage/file/local.py +325 -0
  249. hexdag_plugins/storage/ports/__init__.py +5 -0
  250. hexdag_plugins/storage/ports/vector_store.py +236 -0
  251. hexdag_plugins/storage/sql/__init__.py +7 -0
  252. hexdag_plugins/storage/sql/base.py +187 -0
  253. hexdag_plugins/storage/sql/mysql.py +27 -0
  254. hexdag_plugins/storage/sql/postgresql.py +27 -0
  255. hexdag_plugins/storage/tests/__init__.py +1 -0
  256. hexdag_plugins/storage/tests/test_local_file_storage.py +161 -0
  257. hexdag_plugins/storage/tests/test_sql_adapters.py +212 -0
  258. hexdag_plugins/storage/vector/__init__.py +7 -0
  259. hexdag_plugins/storage/vector/chromadb.py +223 -0
  260. hexdag_plugins/storage/vector/in_memory.py +285 -0
  261. 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."""