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,422 @@
1
+ """Base prompt template classes for building composable prompts.
2
+
3
+ These are the foundational prompt types that all builtin prompts inherit from.
4
+ They provide the core functionality for chat-style prompts, few-shot learning,
5
+ and combinations thereof.
6
+
7
+ Moved from core.orchestration.prompt.template to keep core minimal and make
8
+ these templates part of the builtin library.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import re
14
+ from collections.abc import Callable
15
+ from typing import Any
16
+
17
+ from hexdag.core.orchestration.prompt.template import PromptTemplate, _extract_variables_cached
18
+
19
+
20
+ class FewShotPromptTemplate(PromptTemplate):
21
+ r"""PromptTemplate with built-in support for few-shot examples.
22
+
23
+ Simplified implementation that builds the template once during initialization.
24
+
25
+ Examples
26
+ --------
27
+ Basic few-shot template::
28
+
29
+ template = FewShotPromptTemplate(
30
+ template="Classify: {{text}}",
31
+ examples=[
32
+ {"input": "Great product!", "output": "positive"},
33
+ {"input": "Terrible", "output": "negative"}
34
+ ]
35
+ )
36
+
37
+ result = template.format(text="Amazing!")
38
+ # Output includes examples + prompt
39
+
40
+ Custom example formatting::
41
+
42
+ def custom_formatter(ex):
43
+ return f"Q: {ex['input']}\nA: {ex['output']}"
44
+
45
+ template = FewShotPromptTemplate(
46
+ template="Q: {{question}}\nA:",
47
+ examples=[...],
48
+ format_example=custom_formatter
49
+ )
50
+ """
51
+
52
+ def __init__(
53
+ self,
54
+ template: str,
55
+ examples: list[dict[str, Any]] | None = None,
56
+ *,
57
+ format_example: Callable[[dict[str, Any]], str] | None = None,
58
+ example_separator: str = "\n\n",
59
+ **kwargs: Any,
60
+ ) -> None:
61
+ r"""Initialize FewShotPromptTemplate with examples.
62
+
63
+ Args
64
+ ----
65
+ template: Base template string with {{variables}}
66
+ examples: List of example dicts (typically with 'input' and 'output' keys)
67
+ format_example: Function to format each example (optional)
68
+ example_separator: String to separate examples (default: "\n\n")
69
+ **kwargs: Additional arguments passed to PromptTemplate
70
+ """
71
+ self.examples: list[dict[str, Any]] = examples or []
72
+ self.example_separator = example_separator
73
+ self.base_template = template
74
+
75
+ if format_example is None:
76
+
77
+ def _default_formatter(ex: dict[str, Any]) -> str:
78
+ inp = ex.get("input", "")
79
+ out = ex.get("output", "")
80
+ return f"Input: {inp}\nOutput: {out}"
81
+
82
+ format_example = _default_formatter
83
+
84
+ self.format_example = format_example
85
+
86
+ full_template = self._build_template()
87
+
88
+ super().__init__(full_template, **kwargs)
89
+
90
+ def _build_template(self) -> str:
91
+ """Build the complete template with examples prepended.
92
+
93
+ Returns
94
+ -------
95
+ str
96
+ Complete template string with examples and base template
97
+ """
98
+ if not self.examples:
99
+ return self.base_template
100
+
101
+ examples_text = self.example_separator.join(
102
+ self.format_example(example) for example in self.examples
103
+ )
104
+
105
+ return f"{examples_text}{self.example_separator}{self.base_template}"
106
+
107
+ def add_example(self, example: dict[str, Any]) -> None:
108
+ """Add an example and rebuild the template.
109
+
110
+ Args
111
+ ----
112
+ example: Example dict to add
113
+ """
114
+ self.examples.append(example)
115
+
116
+ # Rebuild the complete template
117
+ self.template = self._build_template()
118
+
119
+ # Re-extract variables from the new template using cached function
120
+ self.input_vars = list(_extract_variables_cached(self.template))
121
+
122
+ def __add__(self, other: str | PromptTemplate) -> FewShotPromptTemplate:
123
+ """Add text or template using + operator.
124
+
125
+ Returns
126
+ -------
127
+ FewShotPromptTemplate
128
+ New FewShotPromptTemplate instance with enhanced content
129
+ """
130
+ if isinstance(other, PromptTemplate):
131
+ enhanced_base_template = self.base_template + other.template
132
+ else:
133
+ enhanced_base_template = self.base_template + other
134
+
135
+ return FewShotPromptTemplate(
136
+ enhanced_base_template,
137
+ self.examples,
138
+ format_example=self.format_example,
139
+ example_separator=self.example_separator,
140
+ )
141
+
142
+ def add(self, text: str | PromptTemplate) -> FewShotPromptTemplate:
143
+ """Add text to template (alias for + operator).
144
+
145
+ Returns
146
+ -------
147
+ FewShotPromptTemplate
148
+ New FewShotPromptTemplate instance with enhanced content
149
+ """
150
+ return self + text
151
+
152
+
153
+ class ChatPromptTemplate(PromptTemplate):
154
+ """Enhanced prompt template supporting multi-message conversations like LangChain.
155
+
156
+ Supports:
157
+ - System messages
158
+ - Conversation history from context
159
+ - Multi-message templates
160
+ - Role-based message construction
161
+
162
+ Examples
163
+ --------
164
+ Simple system + user::
165
+
166
+ template = ChatPromptTemplate(
167
+ system_message="You are an expert {{domain}} analyst.",
168
+ human_message="Analyze this {{data_type}}: {{data}}"
169
+ )
170
+
171
+ messages = template.to_messages(
172
+ domain="financial",
173
+ data_type="report",
174
+ data="Q4 earnings..."
175
+ )
176
+
177
+ Multi-message template::
178
+
179
+ template = ChatPromptTemplate(messages=[
180
+ {"role": "system", "content": "You are an expert analyst."},
181
+ {"role": "user", "content": "Context: {{context}}"},
182
+ {"role": "assistant", "content": "I understand."},
183
+ {"role": "user", "content": "Analyze: {{data}}"}
184
+ ])
185
+ """
186
+
187
+ def __init__(
188
+ self,
189
+ messages: list[dict[str, str]] | None = None,
190
+ system_message: str | None = None,
191
+ human_message: str | None = None,
192
+ **kwargs: Any,
193
+ ) -> None:
194
+ """Initialize ChatPromptTemplate with multiple message types.
195
+
196
+ Args
197
+ ----
198
+ messages: List of message templates with role and content
199
+ system_message: System message template (optional)
200
+ human_message: Human/user message template (optional)
201
+ **kwargs: Additional arguments passed to PromptTemplate
202
+ """
203
+ self.system_message = system_message
204
+ self.human_message = human_message
205
+ self.message_templates = messages or []
206
+
207
+ if not self.message_templates:
208
+ if system_message and human_message:
209
+ combined_template = f"{system_message}\n{human_message}"
210
+ elif human_message:
211
+ combined_template = human_message
212
+ elif system_message:
213
+ combined_template = system_message
214
+ else:
215
+ combined_template = ""
216
+ else:
217
+ combined_template = "\n".join(msg.get("content", "") for msg in self.message_templates)
218
+
219
+ super().__init__(combined_template, **kwargs)
220
+
221
+ def to_messages(
222
+ self,
223
+ system_prompt: str | None = None,
224
+ context_history: list[dict[str, str]] | None = None,
225
+ **kwargs: Any,
226
+ ) -> list[dict[str, str]]:
227
+ """Convert to role-based messages with conversation history support.
228
+
229
+ Args
230
+ ----
231
+ system_prompt: Optional system prompt to include
232
+ context_history: Previous conversation messages from context
233
+ **kwargs: Variable values for template substitution
234
+
235
+ Returns
236
+ -------
237
+ list[dict[str, str]]
238
+ List of message dictionaries ready for LLM
239
+ """
240
+ messages = []
241
+
242
+ if system_prompt:
243
+ system_content = self._render_template(system_prompt, **kwargs)
244
+ messages.append({"role": "system", "content": system_content})
245
+ elif self.system_message:
246
+ system_content = self._render_template(self.system_message, **kwargs)
247
+ messages.append({"role": "system", "content": system_content})
248
+
249
+ if context_history:
250
+ messages.extend(context_history)
251
+
252
+ if self.message_templates:
253
+ for msg_template in self.message_templates:
254
+ role = msg_template.get("role", "user")
255
+ content_template = msg_template.get("content", "")
256
+ rendered_content = self._render_template(content_template, **kwargs)
257
+ messages.append({"role": role, "content": rendered_content})
258
+ elif self.human_message:
259
+ human_content = self._render_template(self.human_message, **kwargs)
260
+ messages.append({"role": "user", "content": human_content})
261
+
262
+ return messages
263
+
264
+ def _render_template(self, template: str, **kwargs: Any) -> str:
265
+ """Render a single template string with variables.
266
+
267
+ Returns
268
+ -------
269
+ str
270
+ Rendered template string with variables substituted
271
+ """
272
+ # Use the parent class variable substitution logic
273
+ pattern = r"\{\{\s*([a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*)\s*\}\}"
274
+
275
+ def replace_var(match: Any) -> str:
276
+ var_path = match.group(1).strip()
277
+ try:
278
+ value = self._get_nested_value(kwargs, var_path)
279
+ return str(value)
280
+ except (KeyError, AttributeError):
281
+ return f"{{{{{var_path}}}}}" # Keep unreplaced variables
282
+
283
+ return re.sub(pattern, replace_var, template)
284
+
285
+ @classmethod
286
+ def from_messages(cls, messages: list[dict[str, str]]) -> ChatPromptTemplate:
287
+ """Create ChatPromptTemplate from a list of message dictionaries.
288
+
289
+ Args
290
+ ----
291
+ messages: List of messages with 'role' and 'content' keys
292
+
293
+ Returns
294
+ -------
295
+ ChatPromptTemplate
296
+ ChatPromptTemplate instance
297
+ """
298
+ return cls(messages=messages)
299
+
300
+ def __add__(self, other: str | PromptTemplate) -> ChatPromptTemplate:
301
+ """Add text or template to system message using + operator.
302
+
303
+ Returns
304
+ -------
305
+ ChatPromptTemplate
306
+ New ChatPromptTemplate instance with enhanced system message
307
+ """
308
+ if isinstance(other, PromptTemplate):
309
+ enhanced_system = (self.system_message or "") + other.template
310
+ else:
311
+ enhanced_system = (self.system_message or "") + other
312
+
313
+ return ChatPromptTemplate(
314
+ messages=self.message_templates,
315
+ system_message=enhanced_system,
316
+ human_message=self.human_message,
317
+ )
318
+
319
+ def add(self, text: str | PromptTemplate) -> ChatPromptTemplate:
320
+ """Add text to system message (alias for + operator).
321
+
322
+ Returns
323
+ -------
324
+ ChatPromptTemplate
325
+ New ChatPromptTemplate instance with enhanced system message
326
+ """
327
+ return self + text
328
+
329
+
330
+ class ChatFewShotTemplate(ChatPromptTemplate):
331
+ """Chat template with few-shot examples support.
332
+
333
+ Combines the role-based messaging of ChatPromptTemplate with the example formatting of
334
+ FewShotPromptTemplate. Has the EXACT same API as ChatPromptTemplate, just enhanced with
335
+ examples.
336
+
337
+ Examples
338
+ --------
339
+ Chat with examples::
340
+
341
+ template = ChatFewShotTemplate(
342
+ system_message="You are a classifier",
343
+ human_message="Classify: {{text}}",
344
+ examples=[
345
+ {"input": "Great!", "output": "positive"},
346
+ {"input": "Bad", "output": "negative"}
347
+ ]
348
+ )
349
+
350
+ messages = template.to_messages(text="Amazing!")
351
+ # System message includes examples
352
+ """
353
+
354
+ def __init__(
355
+ self,
356
+ messages: list[dict[str, str]] | None = None,
357
+ system_message: str | None = None,
358
+ human_message: str | None = None,
359
+ examples: list[dict[str, Any]] | None = None,
360
+ example_separator: str = "\n\n",
361
+ format_example: Callable[[dict[str, Any]], str] | None = None,
362
+ **kwargs: Any,
363
+ ) -> None:
364
+ """Initialize ChatFewShotTemplate with same API as ChatPromptTemplate plus examples.
365
+
366
+ Args
367
+ ----
368
+ messages: List of message templates with role and content (same as ChatPromptTemplate)
369
+ system_message: System message template (same as ChatPromptTemplate)
370
+ human_message: Human/user message template (same as ChatPromptTemplate)
371
+ examples: List of example dicts for few-shot learning (NEW)
372
+ example_separator: String to separate examples (NEW)
373
+ format_example: Function to format each example (NEW)
374
+ **kwargs: Additional arguments passed to ChatPromptTemplate
375
+ """
376
+ self.examples = examples or []
377
+ self.example_separator = example_separator
378
+
379
+ if format_example is None:
380
+
381
+ def _default_formatter(ex: dict[str, Any]) -> str:
382
+ inp = ex.get("input", "")
383
+ out = ex.get("output", "")
384
+ return f"Input: {inp}\nOutput: {out}"
385
+
386
+ format_example = _default_formatter
387
+ self.format_example = format_example
388
+
389
+ # If we have examples, enhance the system message
390
+ enhanced_system_message = system_message
391
+ if self.examples:
392
+ examples_text = self.example_separator.join(
393
+ self.format_example(example) for example in self.examples
394
+ )
395
+
396
+ if system_message:
397
+ enhanced_system_message = f"{examples_text}{self.example_separator}{system_message}"
398
+ else:
399
+ enhanced_system_message = examples_text
400
+
401
+ super().__init__(
402
+ messages=messages,
403
+ system_message=enhanced_system_message,
404
+ human_message=human_message,
405
+ **kwargs,
406
+ )
407
+
408
+ self._original_system_message = system_message
409
+
410
+ def add_example(self, example: dict[str, Any]) -> None:
411
+ """Add an example and rebuild system message."""
412
+ self.examples.append(example)
413
+
414
+ # Rebuild system message with new examples
415
+ examples_text = self.example_separator.join(self.format_example(ex) for ex in self.examples)
416
+
417
+ if self._original_system_message:
418
+ self.system_message = (
419
+ f"{examples_text}{self.example_separator}{self._original_system_message}"
420
+ )
421
+ else:
422
+ self.system_message = examples_text