vanna 0.7.9__py3-none-any.whl → 2.0.0__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 (302) hide show
  1. vanna/__init__.py +167 -395
  2. vanna/agents/__init__.py +7 -0
  3. vanna/capabilities/__init__.py +17 -0
  4. vanna/capabilities/agent_memory/__init__.py +21 -0
  5. vanna/capabilities/agent_memory/base.py +103 -0
  6. vanna/capabilities/agent_memory/models.py +53 -0
  7. vanna/capabilities/file_system/__init__.py +14 -0
  8. vanna/capabilities/file_system/base.py +71 -0
  9. vanna/capabilities/file_system/models.py +25 -0
  10. vanna/capabilities/sql_runner/__init__.py +13 -0
  11. vanna/capabilities/sql_runner/base.py +37 -0
  12. vanna/capabilities/sql_runner/models.py +13 -0
  13. vanna/components/__init__.py +92 -0
  14. vanna/components/base.py +11 -0
  15. vanna/components/rich/__init__.py +83 -0
  16. vanna/components/rich/containers/__init__.py +7 -0
  17. vanna/components/rich/containers/card.py +20 -0
  18. vanna/components/rich/data/__init__.py +9 -0
  19. vanna/components/rich/data/chart.py +17 -0
  20. vanna/components/rich/data/dataframe.py +93 -0
  21. vanna/components/rich/feedback/__init__.py +21 -0
  22. vanna/components/rich/feedback/badge.py +16 -0
  23. vanna/components/rich/feedback/icon_text.py +14 -0
  24. vanna/components/rich/feedback/log_viewer.py +41 -0
  25. vanna/components/rich/feedback/notification.py +19 -0
  26. vanna/components/rich/feedback/progress.py +37 -0
  27. vanna/components/rich/feedback/status_card.py +28 -0
  28. vanna/components/rich/feedback/status_indicator.py +14 -0
  29. vanna/components/rich/interactive/__init__.py +21 -0
  30. vanna/components/rich/interactive/button.py +95 -0
  31. vanna/components/rich/interactive/task_list.py +58 -0
  32. vanna/components/rich/interactive/ui_state.py +93 -0
  33. vanna/components/rich/specialized/__init__.py +7 -0
  34. vanna/components/rich/specialized/artifact.py +20 -0
  35. vanna/components/rich/text.py +16 -0
  36. vanna/components/simple/__init__.py +15 -0
  37. vanna/components/simple/image.py +15 -0
  38. vanna/components/simple/link.py +15 -0
  39. vanna/components/simple/text.py +11 -0
  40. vanna/core/__init__.py +193 -0
  41. vanna/core/_compat.py +19 -0
  42. vanna/core/agent/__init__.py +10 -0
  43. vanna/core/agent/agent.py +1407 -0
  44. vanna/core/agent/config.py +123 -0
  45. vanna/core/audit/__init__.py +28 -0
  46. vanna/core/audit/base.py +299 -0
  47. vanna/core/audit/models.py +131 -0
  48. vanna/core/component_manager.py +329 -0
  49. vanna/core/components.py +53 -0
  50. vanna/core/enhancer/__init__.py +11 -0
  51. vanna/core/enhancer/base.py +94 -0
  52. vanna/core/enhancer/default.py +118 -0
  53. vanna/core/enricher/__init__.py +10 -0
  54. vanna/core/enricher/base.py +59 -0
  55. vanna/core/errors.py +47 -0
  56. vanna/core/evaluation/__init__.py +81 -0
  57. vanna/core/evaluation/base.py +186 -0
  58. vanna/core/evaluation/dataset.py +254 -0
  59. vanna/core/evaluation/evaluators.py +376 -0
  60. vanna/core/evaluation/report.py +289 -0
  61. vanna/core/evaluation/runner.py +313 -0
  62. vanna/core/filter/__init__.py +10 -0
  63. vanna/core/filter/base.py +67 -0
  64. vanna/core/lifecycle/__init__.py +10 -0
  65. vanna/core/lifecycle/base.py +83 -0
  66. vanna/core/llm/__init__.py +16 -0
  67. vanna/core/llm/base.py +40 -0
  68. vanna/core/llm/models.py +61 -0
  69. vanna/core/middleware/__init__.py +10 -0
  70. vanna/core/middleware/base.py +69 -0
  71. vanna/core/observability/__init__.py +11 -0
  72. vanna/core/observability/base.py +88 -0
  73. vanna/core/observability/models.py +47 -0
  74. vanna/core/recovery/__init__.py +11 -0
  75. vanna/core/recovery/base.py +84 -0
  76. vanna/core/recovery/models.py +32 -0
  77. vanna/core/registry.py +278 -0
  78. vanna/core/rich_component.py +156 -0
  79. vanna/core/simple_component.py +27 -0
  80. vanna/core/storage/__init__.py +14 -0
  81. vanna/core/storage/base.py +46 -0
  82. vanna/core/storage/models.py +46 -0
  83. vanna/core/system_prompt/__init__.py +13 -0
  84. vanna/core/system_prompt/base.py +36 -0
  85. vanna/core/system_prompt/default.py +157 -0
  86. vanna/core/tool/__init__.py +18 -0
  87. vanna/core/tool/base.py +70 -0
  88. vanna/core/tool/models.py +84 -0
  89. vanna/core/user/__init__.py +17 -0
  90. vanna/core/user/base.py +29 -0
  91. vanna/core/user/models.py +25 -0
  92. vanna/core/user/request_context.py +70 -0
  93. vanna/core/user/resolver.py +42 -0
  94. vanna/core/validation.py +164 -0
  95. vanna/core/workflow/__init__.py +12 -0
  96. vanna/core/workflow/base.py +254 -0
  97. vanna/core/workflow/default.py +789 -0
  98. vanna/examples/__init__.py +1 -0
  99. vanna/examples/__main__.py +44 -0
  100. vanna/examples/anthropic_quickstart.py +80 -0
  101. vanna/examples/artifact_example.py +293 -0
  102. vanna/examples/claude_sqlite_example.py +236 -0
  103. vanna/examples/coding_agent_example.py +300 -0
  104. vanna/examples/custom_system_prompt_example.py +174 -0
  105. vanna/examples/default_workflow_handler_example.py +208 -0
  106. vanna/examples/email_auth_example.py +340 -0
  107. vanna/examples/evaluation_example.py +269 -0
  108. vanna/examples/extensibility_example.py +262 -0
  109. vanna/examples/minimal_example.py +67 -0
  110. vanna/examples/mock_auth_example.py +227 -0
  111. vanna/examples/mock_custom_tool.py +311 -0
  112. vanna/examples/mock_quickstart.py +79 -0
  113. vanna/examples/mock_quota_example.py +145 -0
  114. vanna/examples/mock_rich_components_demo.py +396 -0
  115. vanna/examples/mock_sqlite_example.py +223 -0
  116. vanna/examples/openai_quickstart.py +83 -0
  117. vanna/examples/primitive_components_demo.py +305 -0
  118. vanna/examples/quota_lifecycle_example.py +139 -0
  119. vanna/examples/visualization_example.py +251 -0
  120. vanna/integrations/__init__.py +17 -0
  121. vanna/integrations/anthropic/__init__.py +9 -0
  122. vanna/integrations/anthropic/llm.py +270 -0
  123. vanna/integrations/azureopenai/__init__.py +9 -0
  124. vanna/integrations/azureopenai/llm.py +329 -0
  125. vanna/integrations/azuresearch/__init__.py +7 -0
  126. vanna/integrations/azuresearch/agent_memory.py +413 -0
  127. vanna/integrations/bigquery/__init__.py +5 -0
  128. vanna/integrations/bigquery/sql_runner.py +81 -0
  129. vanna/integrations/chromadb/__init__.py +104 -0
  130. vanna/integrations/chromadb/agent_memory.py +416 -0
  131. vanna/integrations/clickhouse/__init__.py +5 -0
  132. vanna/integrations/clickhouse/sql_runner.py +82 -0
  133. vanna/integrations/duckdb/__init__.py +5 -0
  134. vanna/integrations/duckdb/sql_runner.py +65 -0
  135. vanna/integrations/faiss/__init__.py +7 -0
  136. vanna/integrations/faiss/agent_memory.py +431 -0
  137. vanna/integrations/google/__init__.py +9 -0
  138. vanna/integrations/google/gemini.py +370 -0
  139. vanna/integrations/hive/__init__.py +5 -0
  140. vanna/integrations/hive/sql_runner.py +87 -0
  141. vanna/integrations/local/__init__.py +17 -0
  142. vanna/integrations/local/agent_memory/__init__.py +7 -0
  143. vanna/integrations/local/agent_memory/in_memory.py +285 -0
  144. vanna/integrations/local/audit.py +59 -0
  145. vanna/integrations/local/file_system.py +242 -0
  146. vanna/integrations/local/file_system_conversation_store.py +255 -0
  147. vanna/integrations/local/storage.py +62 -0
  148. vanna/integrations/marqo/__init__.py +7 -0
  149. vanna/integrations/marqo/agent_memory.py +354 -0
  150. vanna/integrations/milvus/__init__.py +7 -0
  151. vanna/integrations/milvus/agent_memory.py +458 -0
  152. vanna/integrations/mock/__init__.py +9 -0
  153. vanna/integrations/mock/llm.py +65 -0
  154. vanna/integrations/mssql/__init__.py +5 -0
  155. vanna/integrations/mssql/sql_runner.py +66 -0
  156. vanna/integrations/mysql/__init__.py +5 -0
  157. vanna/integrations/mysql/sql_runner.py +92 -0
  158. vanna/integrations/ollama/__init__.py +7 -0
  159. vanna/integrations/ollama/llm.py +252 -0
  160. vanna/integrations/openai/__init__.py +10 -0
  161. vanna/integrations/openai/llm.py +267 -0
  162. vanna/integrations/openai/responses.py +163 -0
  163. vanna/integrations/opensearch/__init__.py +7 -0
  164. vanna/integrations/opensearch/agent_memory.py +411 -0
  165. vanna/integrations/oracle/__init__.py +5 -0
  166. vanna/integrations/oracle/sql_runner.py +75 -0
  167. vanna/integrations/pinecone/__init__.py +7 -0
  168. vanna/integrations/pinecone/agent_memory.py +329 -0
  169. vanna/integrations/plotly/__init__.py +5 -0
  170. vanna/integrations/plotly/chart_generator.py +313 -0
  171. vanna/integrations/postgres/__init__.py +9 -0
  172. vanna/integrations/postgres/sql_runner.py +112 -0
  173. vanna/integrations/premium/agent_memory/__init__.py +7 -0
  174. vanna/integrations/premium/agent_memory/premium.py +186 -0
  175. vanna/integrations/presto/__init__.py +5 -0
  176. vanna/integrations/presto/sql_runner.py +107 -0
  177. vanna/integrations/qdrant/__init__.py +7 -0
  178. vanna/integrations/qdrant/agent_memory.py +461 -0
  179. vanna/integrations/snowflake/__init__.py +5 -0
  180. vanna/integrations/snowflake/sql_runner.py +147 -0
  181. vanna/integrations/sqlite/__init__.py +9 -0
  182. vanna/integrations/sqlite/sql_runner.py +65 -0
  183. vanna/integrations/weaviate/__init__.py +7 -0
  184. vanna/integrations/weaviate/agent_memory.py +428 -0
  185. vanna/{ZhipuAI → legacy/ZhipuAI}/ZhipuAI_embeddings.py +11 -11
  186. vanna/legacy/__init__.py +403 -0
  187. vanna/legacy/adapter.py +463 -0
  188. vanna/{advanced → legacy/advanced}/__init__.py +3 -1
  189. vanna/{anthropic → legacy/anthropic}/anthropic_chat.py +9 -7
  190. vanna/{azuresearch → legacy/azuresearch}/azuresearch_vector.py +79 -41
  191. vanna/{base → legacy/base}/base.py +224 -217
  192. vanna/legacy/bedrock/__init__.py +1 -0
  193. vanna/{bedrock → legacy/bedrock}/bedrock_converse.py +13 -12
  194. vanna/{chromadb → legacy/chromadb}/chromadb_vector.py +3 -1
  195. vanna/legacy/cohere/__init__.py +2 -0
  196. vanna/{cohere → legacy/cohere}/cohere_chat.py +19 -14
  197. vanna/{cohere → legacy/cohere}/cohere_embeddings.py +25 -19
  198. vanna/{deepseek → legacy/deepseek}/deepseek_chat.py +5 -6
  199. vanna/legacy/faiss/__init__.py +1 -0
  200. vanna/{faiss → legacy/faiss}/faiss.py +113 -59
  201. vanna/{flask → legacy/flask}/__init__.py +84 -43
  202. vanna/{flask → legacy/flask}/assets.py +5 -5
  203. vanna/{flask → legacy/flask}/auth.py +5 -4
  204. vanna/{google → legacy/google}/bigquery_vector.py +75 -42
  205. vanna/{google → legacy/google}/gemini_chat.py +7 -3
  206. vanna/{hf → legacy/hf}/hf.py +0 -1
  207. vanna/{milvus → legacy/milvus}/milvus_vector.py +58 -35
  208. vanna/{mock → legacy/mock}/llm.py +0 -1
  209. vanna/legacy/mock/vectordb.py +67 -0
  210. vanna/legacy/ollama/ollama.py +110 -0
  211. vanna/{openai → legacy/openai}/openai_chat.py +2 -6
  212. vanna/legacy/opensearch/opensearch_vector.py +369 -0
  213. vanna/legacy/opensearch/opensearch_vector_semantic.py +200 -0
  214. vanna/legacy/oracle/oracle_vector.py +584 -0
  215. vanna/{pgvector → legacy/pgvector}/pgvector.py +42 -13
  216. vanna/{qdrant → legacy/qdrant}/qdrant.py +2 -6
  217. vanna/legacy/qianfan/Qianfan_Chat.py +170 -0
  218. vanna/legacy/qianfan/Qianfan_embeddings.py +36 -0
  219. vanna/legacy/qianwen/QianwenAI_chat.py +132 -0
  220. vanna/{remote.py → legacy/remote.py} +28 -26
  221. vanna/{utils.py → legacy/utils.py} +6 -11
  222. vanna/{vannadb → legacy/vannadb}/vannadb_vector.py +115 -46
  223. vanna/{vllm → legacy/vllm}/vllm.py +5 -6
  224. vanna/{weaviate → legacy/weaviate}/weaviate_vector.py +59 -40
  225. vanna/{xinference → legacy/xinference}/xinference.py +6 -6
  226. vanna/py.typed +0 -0
  227. vanna/servers/__init__.py +16 -0
  228. vanna/servers/__main__.py +8 -0
  229. vanna/servers/base/__init__.py +18 -0
  230. vanna/servers/base/chat_handler.py +65 -0
  231. vanna/servers/base/models.py +111 -0
  232. vanna/servers/base/rich_chat_handler.py +141 -0
  233. vanna/servers/base/templates.py +331 -0
  234. vanna/servers/cli/__init__.py +7 -0
  235. vanna/servers/cli/server_runner.py +204 -0
  236. vanna/servers/fastapi/__init__.py +7 -0
  237. vanna/servers/fastapi/app.py +163 -0
  238. vanna/servers/fastapi/routes.py +183 -0
  239. vanna/servers/flask/__init__.py +7 -0
  240. vanna/servers/flask/app.py +132 -0
  241. vanna/servers/flask/routes.py +137 -0
  242. vanna/tools/__init__.py +41 -0
  243. vanna/tools/agent_memory.py +322 -0
  244. vanna/tools/file_system.py +879 -0
  245. vanna/tools/python.py +222 -0
  246. vanna/tools/run_sql.py +165 -0
  247. vanna/tools/visualize_data.py +195 -0
  248. vanna/utils/__init__.py +0 -0
  249. vanna/web_components/__init__.py +44 -0
  250. vanna-2.0.0.dist-info/METADATA +485 -0
  251. vanna-2.0.0.dist-info/RECORD +289 -0
  252. vanna-2.0.0.dist-info/entry_points.txt +3 -0
  253. vanna/bedrock/__init__.py +0 -1
  254. vanna/cohere/__init__.py +0 -2
  255. vanna/faiss/__init__.py +0 -1
  256. vanna/mock/vectordb.py +0 -55
  257. vanna/ollama/ollama.py +0 -103
  258. vanna/opensearch/opensearch_vector.py +0 -392
  259. vanna/opensearch/opensearch_vector_semantic.py +0 -175
  260. vanna/oracle/oracle_vector.py +0 -585
  261. vanna/qianfan/Qianfan_Chat.py +0 -165
  262. vanna/qianfan/Qianfan_embeddings.py +0 -36
  263. vanna/qianwen/QianwenAI_chat.py +0 -133
  264. vanna-0.7.9.dist-info/METADATA +0 -408
  265. vanna-0.7.9.dist-info/RECORD +0 -79
  266. /vanna/{ZhipuAI → legacy/ZhipuAI}/ZhipuAI_Chat.py +0 -0
  267. /vanna/{ZhipuAI → legacy/ZhipuAI}/__init__.py +0 -0
  268. /vanna/{anthropic → legacy/anthropic}/__init__.py +0 -0
  269. /vanna/{azuresearch → legacy/azuresearch}/__init__.py +0 -0
  270. /vanna/{base → legacy/base}/__init__.py +0 -0
  271. /vanna/{chromadb → legacy/chromadb}/__init__.py +0 -0
  272. /vanna/{deepseek → legacy/deepseek}/__init__.py +0 -0
  273. /vanna/{exceptions → legacy/exceptions}/__init__.py +0 -0
  274. /vanna/{google → legacy/google}/__init__.py +0 -0
  275. /vanna/{hf → legacy/hf}/__init__.py +0 -0
  276. /vanna/{local.py → legacy/local.py} +0 -0
  277. /vanna/{marqo → legacy/marqo}/__init__.py +0 -0
  278. /vanna/{marqo → legacy/marqo}/marqo.py +0 -0
  279. /vanna/{milvus → legacy/milvus}/__init__.py +0 -0
  280. /vanna/{mistral → legacy/mistral}/__init__.py +0 -0
  281. /vanna/{mistral → legacy/mistral}/mistral.py +0 -0
  282. /vanna/{mock → legacy/mock}/__init__.py +0 -0
  283. /vanna/{mock → legacy/mock}/embedding.py +0 -0
  284. /vanna/{ollama → legacy/ollama}/__init__.py +0 -0
  285. /vanna/{openai → legacy/openai}/__init__.py +0 -0
  286. /vanna/{openai → legacy/openai}/openai_embeddings.py +0 -0
  287. /vanna/{opensearch → legacy/opensearch}/__init__.py +0 -0
  288. /vanna/{oracle → legacy/oracle}/__init__.py +0 -0
  289. /vanna/{pgvector → legacy/pgvector}/__init__.py +0 -0
  290. /vanna/{pinecone → legacy/pinecone}/__init__.py +0 -0
  291. /vanna/{pinecone → legacy/pinecone}/pinecone_vector.py +0 -0
  292. /vanna/{qdrant → legacy/qdrant}/__init__.py +0 -0
  293. /vanna/{qianfan → legacy/qianfan}/__init__.py +0 -0
  294. /vanna/{qianwen → legacy/qianwen}/QianwenAI_embeddings.py +0 -0
  295. /vanna/{qianwen → legacy/qianwen}/__init__.py +0 -0
  296. /vanna/{types → legacy/types}/__init__.py +0 -0
  297. /vanna/{vannadb → legacy/vannadb}/__init__.py +0 -0
  298. /vanna/{vllm → legacy/vllm}/__init__.py +0 -0
  299. /vanna/{weaviate → legacy/weaviate}/__init__.py +0 -0
  300. /vanna/{xinference → legacy/xinference}/__init__.py +0 -0
  301. {vanna-0.7.9.dist-info → vanna-2.0.0.dist-info}/WHEEL +0 -0
  302. {vanna-0.7.9.dist-info → vanna-2.0.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,329 @@
1
+ """
2
+ Component state management and update protocol for rich components.
3
+ """
4
+
5
+ import uuid
6
+ from datetime import datetime
7
+ from enum import Enum
8
+ from typing import Any, Dict, List, Optional, Set, Union
9
+
10
+ from pydantic import BaseModel, Field
11
+
12
+ from ..components.rich import ComponentLifecycle, RichComponent
13
+
14
+
15
+ class UpdateOperation(str, Enum):
16
+ """Types of component update operations."""
17
+
18
+ CREATE = "create"
19
+ UPDATE = "update"
20
+ REPLACE = "replace"
21
+ REMOVE = "remove"
22
+ REORDER = "reorder"
23
+ BULK_UPDATE = "bulk_update"
24
+
25
+
26
+ class Position(BaseModel):
27
+ """Position specification for component placement."""
28
+
29
+ index: Optional[int] = None
30
+ anchor_id: Optional[str] = None
31
+ relation: str = "after" # "before", "after", "inside", "replace"
32
+
33
+
34
+ class ComponentUpdate(BaseModel):
35
+ """Represents a change to the component tree."""
36
+
37
+ operation: UpdateOperation
38
+ target_id: str # Component being affected
39
+ component: Optional[RichComponent] = None # New/updated component data
40
+ updates: Optional[Dict[str, Any]] = None # Partial updates for UPDATE operation
41
+ position: Optional[Position] = None # For positioning operations
42
+ timestamp: str = Field(default_factory=lambda: datetime.utcnow().isoformat())
43
+ batch_id: Optional[str] = None # For grouping related updates
44
+
45
+ def serialize_for_frontend(self) -> Dict[str, Any]:
46
+ """Return update payload with nested components normalized."""
47
+ payload = self.model_dump()
48
+
49
+ # Normalise enum values for the frontend contract.
50
+ payload["operation"] = self.operation.value
51
+
52
+ if self.component:
53
+ payload["component"] = self.component.serialize_for_frontend()
54
+
55
+ return payload
56
+
57
+
58
+ class ComponentNode(BaseModel):
59
+ """Node in the component tree."""
60
+
61
+ component: RichComponent
62
+ children: List["ComponentNode"] = Field(default_factory=list)
63
+ parent_id: Optional[str] = None
64
+
65
+ def find_child(self, component_id: str) -> Optional["ComponentNode"]:
66
+ """Find a child node by component ID."""
67
+ for child in self.children:
68
+ if child.component.id == component_id:
69
+ return child
70
+ found = child.find_child(component_id)
71
+ if found:
72
+ return found
73
+ return None
74
+
75
+ def remove_child(self, component_id: str) -> bool:
76
+ """Remove a child component by ID."""
77
+ for i, child in enumerate(self.children):
78
+ if child.component.id == component_id:
79
+ self.children.pop(i)
80
+ return True
81
+ if child.remove_child(component_id):
82
+ return True
83
+ return False
84
+
85
+ def get_all_ids(self) -> Set[str]:
86
+ """Get all component IDs in this subtree."""
87
+ ids = {self.component.id}
88
+ for child in self.children:
89
+ ids.update(child.get_all_ids())
90
+ return ids
91
+
92
+
93
+ class ComponentTree(BaseModel):
94
+ """Hierarchical structure for managing component layout."""
95
+
96
+ root: Optional[ComponentNode] = None
97
+ flat_index: Dict[str, ComponentNode] = Field(default_factory=dict)
98
+
99
+ def add_component(
100
+ self, component: RichComponent, position: Optional[Position] = None
101
+ ) -> ComponentUpdate:
102
+ """Add a component to the tree."""
103
+ node = ComponentNode(component=component)
104
+ self.flat_index[component.id] = node
105
+
106
+ if self.root is None:
107
+ self.root = node
108
+ else:
109
+ parent_node = self._find_parent(position)
110
+ if parent_node is not None:
111
+ node.parent_id = parent_node.component.id
112
+ parent_node.children.append(node)
113
+
114
+ return ComponentUpdate(
115
+ operation=UpdateOperation.CREATE,
116
+ target_id=component.id,
117
+ component=component,
118
+ position=position,
119
+ )
120
+
121
+ def update_component(
122
+ self, component_id: str, updates: Dict[str, Any]
123
+ ) -> Optional[ComponentUpdate]:
124
+ """Update a component's properties."""
125
+ node = self.flat_index.get(component_id)
126
+ if not node:
127
+ return None
128
+
129
+ # Create updated component
130
+ component_data = node.component.model_dump()
131
+ component_data.update(updates)
132
+ component_data["lifecycle"] = ComponentLifecycle.UPDATE
133
+ component_data["timestamp"] = datetime.utcnow().isoformat()
134
+
135
+ updated_component = node.component.__class__(**component_data)
136
+ node.component = updated_component
137
+
138
+ return ComponentUpdate(
139
+ operation=UpdateOperation.UPDATE,
140
+ target_id=component_id,
141
+ component=updated_component,
142
+ updates=updates,
143
+ )
144
+
145
+ def replace_component(
146
+ self, old_id: str, new_component: RichComponent
147
+ ) -> Optional[ComponentUpdate]:
148
+ """Replace one component with another."""
149
+ old_node = self.flat_index.get(old_id)
150
+ if not old_node:
151
+ return None
152
+
153
+ # Update the component in place
154
+ old_node.component = new_component
155
+
156
+ # Update index
157
+ del self.flat_index[old_id]
158
+ self.flat_index[new_component.id] = old_node
159
+
160
+ return ComponentUpdate(
161
+ operation=UpdateOperation.REPLACE, target_id=old_id, component=new_component
162
+ )
163
+
164
+ def remove_component(self, component_id: str) -> Optional[ComponentUpdate]:
165
+ """Remove a component and its children."""
166
+ node = self.flat_index.get(component_id)
167
+ if not node:
168
+ return None
169
+
170
+ # Remove from parent
171
+ if self.root and self.root.component.id == component_id:
172
+ self.root = None
173
+ else:
174
+ if self.root:
175
+ self.root.remove_child(component_id)
176
+
177
+ # Remove from flat index (including all children)
178
+ removed_ids = node.get_all_ids()
179
+ for removed_id in removed_ids:
180
+ self.flat_index.pop(removed_id, None)
181
+
182
+ return ComponentUpdate(operation=UpdateOperation.REMOVE, target_id=component_id)
183
+
184
+ def get_component(self, component_id: str) -> Optional[RichComponent]:
185
+ """Get a component by ID."""
186
+ node = self.flat_index.get(component_id)
187
+ return node.component if node else None
188
+
189
+ def _find_parent(self, position: Optional[Position]) -> Optional[ComponentNode]:
190
+ """Find the parent node for a new component."""
191
+ if not position or not position.anchor_id:
192
+ return self.root
193
+
194
+ anchor_node = self.flat_index.get(position.anchor_id)
195
+ if not anchor_node:
196
+ return self.root
197
+
198
+ if position.relation == "inside":
199
+ return anchor_node
200
+ elif position.relation in ["before", "after", "replace"]:
201
+ # Find the parent of the anchor
202
+ if anchor_node.parent_id:
203
+ parent_node = self.flat_index.get(anchor_node.parent_id)
204
+ return parent_node if parent_node else self.root
205
+ else:
206
+ return self.root
207
+ else:
208
+ return self.root
209
+
210
+
211
+ class ComponentManager:
212
+ """Manages component lifecycle and state updates."""
213
+
214
+ def __init__(self) -> None:
215
+ self.components: Dict[str, RichComponent] = {}
216
+ self.component_tree = ComponentTree()
217
+ self.update_history: List[ComponentUpdate] = []
218
+ self.active_batch: Optional[str] = None
219
+
220
+ def emit(self, component: RichComponent) -> Optional[ComponentUpdate]:
221
+ """Emit a component with smart lifecycle management."""
222
+ if component.id in self.components:
223
+ # Existing component - determine if this is an update or replace
224
+ existing = self.components[component.id]
225
+
226
+ if component.lifecycle == ComponentLifecycle.UPDATE:
227
+ # Extract changes
228
+ old_data = existing.model_dump()
229
+ new_data = component.model_dump()
230
+ updates = {k: v for k, v in new_data.items() if old_data.get(k) != v}
231
+
232
+ update = self.component_tree.update_component(component.id, updates)
233
+ else:
234
+ # Replace
235
+ update = self.component_tree.replace_component(component.id, component)
236
+ else:
237
+ # New component - always append
238
+ update = self.component_tree.add_component(component, None)
239
+
240
+ if update:
241
+ self.components[component.id] = component
242
+ self.update_history.append(update)
243
+
244
+ if self.active_batch:
245
+ update.batch_id = self.active_batch
246
+
247
+ return update
248
+
249
+ def update_component(
250
+ self, component_id: str, **updates: Any
251
+ ) -> Optional[ComponentUpdate]:
252
+ """Update specific fields of an existing component."""
253
+ update = self.component_tree.update_component(component_id, updates)
254
+ if update and update.component:
255
+ self.components[component_id] = update.component
256
+ self.update_history.append(update)
257
+
258
+ if self.active_batch:
259
+ update.batch_id = self.active_batch
260
+
261
+ return update
262
+
263
+ def replace_component(
264
+ self, old_id: str, new_component: RichComponent
265
+ ) -> Optional[ComponentUpdate]:
266
+ """Replace one component with another."""
267
+ update = self.component_tree.replace_component(old_id, new_component)
268
+ if update:
269
+ self.components.pop(old_id, None)
270
+ self.components[new_component.id] = new_component
271
+ self.update_history.append(update)
272
+
273
+ if self.active_batch:
274
+ update.batch_id = self.active_batch
275
+
276
+ return update
277
+
278
+ def remove_component(self, component_id: str) -> Optional[ComponentUpdate]:
279
+ """Remove a component and handle cleanup."""
280
+ update = self.component_tree.remove_component(component_id)
281
+ if update:
282
+ self.components.pop(component_id, None)
283
+ self.update_history.append(update)
284
+
285
+ if self.active_batch:
286
+ update.batch_id = self.active_batch
287
+
288
+ return update
289
+
290
+ def get_component(self, component_id: str) -> Optional[RichComponent]:
291
+ """Get a component by ID."""
292
+ return self.components.get(component_id)
293
+
294
+ def get_all_components(self) -> List[RichComponent]:
295
+ """Get all components in the manager."""
296
+ return list(self.components.values())
297
+
298
+ def start_batch(self) -> str:
299
+ """Start a batch of related updates."""
300
+ self.active_batch = str(uuid.uuid4())
301
+ return self.active_batch
302
+
303
+ def end_batch(self) -> Optional[str]:
304
+ """End the current batch."""
305
+ batch_id = self.active_batch
306
+ self.active_batch = None
307
+ return batch_id
308
+
309
+ def get_updates_since(
310
+ self, timestamp: Optional[str] = None
311
+ ) -> List[ComponentUpdate]:
312
+ """Get all updates since a given timestamp."""
313
+ if not timestamp:
314
+ return self.update_history.copy()
315
+
316
+ try:
317
+ cutoff = datetime.fromisoformat(timestamp.replace("Z", "+00:00"))
318
+ return [
319
+ update
320
+ for update in self.update_history
321
+ if datetime.fromisoformat(update.timestamp.replace("Z", "+00:00"))
322
+ > cutoff
323
+ ]
324
+ except ValueError:
325
+ return self.update_history.copy()
326
+
327
+ def clear_history(self) -> None:
328
+ """Clear the update history."""
329
+ self.update_history.clear()
@@ -0,0 +1,53 @@
1
+ """
2
+ UI component base class.
3
+
4
+ This module defines the UiComponent class which is the return type for tool executions.
5
+ It's placed in core/ because it's a fundamental type that tools return, not just a UI concern.
6
+ """
7
+
8
+ from datetime import datetime
9
+ from typing import Any, Optional
10
+
11
+ from pydantic import BaseModel, Field, model_validator
12
+
13
+
14
+ class UiComponent(BaseModel):
15
+ """Base class for UI components streamed to client.
16
+
17
+ This wraps both rich and simple component representations,
18
+ allowing tools to return structured UI updates.
19
+
20
+ Note: We use Any for component types to avoid circular dependencies.
21
+ Type validation happens at runtime through validators.
22
+ """
23
+
24
+ timestamp: str = Field(default_factory=lambda: datetime.utcnow().isoformat())
25
+ rich_component: Any = Field(
26
+ ..., description="Rich component for advanced rendering"
27
+ )
28
+ simple_component: Optional[Any] = Field(
29
+ None, description="Simple component for basic rendering"
30
+ )
31
+
32
+ @model_validator(mode="after")
33
+ def validate_components(self) -> "UiComponent":
34
+ """Validate that components are the correct types at runtime."""
35
+ # Import from core - clean imports, no circular dependency
36
+ from .rich_component import RichComponent
37
+ from .simple_component import SimpleComponent
38
+
39
+ if not isinstance(self.rich_component, RichComponent):
40
+ raise ValueError(
41
+ f"rich_component must be a RichComponent, got {type(self.rich_component)}"
42
+ )
43
+
44
+ if self.simple_component is not None and not isinstance(
45
+ self.simple_component, SimpleComponent
46
+ ):
47
+ raise ValueError(
48
+ f"simple_component must be a SimpleComponent or None, got {type(self.simple_component)}"
49
+ )
50
+
51
+ return self
52
+
53
+ model_config = {"arbitrary_types_allowed": True}
@@ -0,0 +1,11 @@
1
+ """
2
+ LLM context enhancement system for adding context to prompts and messages.
3
+
4
+ This module provides interfaces for enriching LLM system prompts and messages
5
+ with additional context before LLM calls (e.g., from memory, RAG, documentation).
6
+ """
7
+
8
+ from .base import LlmContextEnhancer
9
+ from .default import DefaultLlmContextEnhancer
10
+
11
+ __all__ = ["LlmContextEnhancer", "DefaultLlmContextEnhancer"]
@@ -0,0 +1,94 @@
1
+ """
2
+ LLM context enhancer interface.
3
+
4
+ LLM context enhancers allow you to add additional context to the system prompt
5
+ and user messages before LLM calls.
6
+ """
7
+
8
+ from abc import ABC
9
+ from typing import TYPE_CHECKING, Optional
10
+
11
+ if TYPE_CHECKING:
12
+ from ..user.models import User
13
+ from ..llm.models import LlmMessage
14
+
15
+
16
+ class LlmContextEnhancer(ABC):
17
+ """Enhancer for adding context to LLM prompts and messages.
18
+
19
+ Subclass this to create custom enhancers that can:
20
+ - Add relevant context to the system prompt based on the user's initial message
21
+ - Enrich user messages with additional context (e.g., from memory/RAG)
22
+ - Inject relevant examples or documentation
23
+ - Add temporal or environmental context
24
+
25
+ Example:
26
+ class MemoryBasedEnhancer(LlmContextEnhancer):
27
+ def __init__(self, agent_memory):
28
+ self.agent_memory = agent_memory
29
+
30
+ async def enhance_system_prompt(
31
+ self,
32
+ system_prompt: str,
33
+ user_message: str,
34
+ user: User
35
+ ) -> str:
36
+ # Add relevant examples from memory based on user message
37
+ examples = await self.agent_memory.search_similar(user_message)
38
+ return system_prompt + "\\n\\nRelevant examples:\\n" + examples
39
+
40
+ async def enhance_user_messages(
41
+ self,
42
+ messages: list[LlmMessage],
43
+ user: User
44
+ ) -> list[LlmMessage]:
45
+ # Could modify or add to messages
46
+ return messages
47
+
48
+ agent = Agent(
49
+ llm_service=...,
50
+ llm_context_enhancer=MemoryBasedEnhancer(agent_memory)
51
+ )
52
+ """
53
+
54
+ async def enhance_system_prompt(
55
+ self, system_prompt: str, user_message: str, user: "User"
56
+ ) -> str:
57
+ """Enhance the system prompt with additional context.
58
+
59
+ This method is called before the first LLM request with the initial
60
+ user message, allowing you to add relevant context to the system prompt.
61
+
62
+ Args:
63
+ system_prompt: The original system prompt
64
+ user_message: The initial user message
65
+ user: The user making the request
66
+
67
+ Returns:
68
+ Enhanced system prompt with additional context
69
+
70
+ Note:
71
+ This is called once per conversation turn, before any tool calls.
72
+ """
73
+ return system_prompt
74
+
75
+ async def enhance_user_messages(
76
+ self, messages: list["LlmMessage"], user: "User"
77
+ ) -> list["LlmMessage"]:
78
+ """Enhance user messages with additional context.
79
+
80
+ This method is called to potentially modify or add context to user messages
81
+ before sending them to the LLM.
82
+
83
+ Args:
84
+ messages: The list of messages to enhance
85
+ user: The user making the request
86
+
87
+ Returns:
88
+ Enhanced list of messages
89
+
90
+ Note:
91
+ This is called before each LLM request, including after tool calls.
92
+ Be careful not to add context repeatedly on each iteration.
93
+ """
94
+ return messages
@@ -0,0 +1,118 @@
1
+ """
2
+ Default LLM context enhancer implementation using AgentMemory.
3
+
4
+ This implementation enriches the system prompt with relevant memories
5
+ based on the user's initial message.
6
+ """
7
+
8
+ from typing import TYPE_CHECKING, List, Optional
9
+ from .base import LlmContextEnhancer
10
+
11
+ if TYPE_CHECKING:
12
+ from ..user.models import User
13
+ from ..llm.models import LlmMessage
14
+ from ...capabilities.agent_memory import AgentMemory, TextMemorySearchResult
15
+
16
+
17
+ class DefaultLlmContextEnhancer(LlmContextEnhancer):
18
+ """Default enhancer that uses AgentMemory to add relevant context.
19
+
20
+ This enhancer searches the agent's memory for relevant examples and
21
+ tool use patterns based on the user's message, and adds them to the
22
+ system prompt.
23
+
24
+ Example:
25
+ agent = Agent(
26
+ llm_service=...,
27
+ agent_memory=agent_memory,
28
+ llm_context_enhancer=DefaultLlmContextEnhancer(agent_memory)
29
+ )
30
+ """
31
+
32
+ def __init__(self, agent_memory: Optional["AgentMemory"] = None):
33
+ """Initialize with optional agent memory.
34
+
35
+ Args:
36
+ agent_memory: Optional AgentMemory instance. If not provided,
37
+ enhancement will be skipped.
38
+ """
39
+ self.agent_memory = agent_memory
40
+
41
+ async def enhance_system_prompt(
42
+ self, system_prompt: str, user_message: str, user: "User"
43
+ ) -> str:
44
+ """Enhance system prompt with relevant memories.
45
+
46
+ Searches agent memory for relevant text memories based on the
47
+ user's message and adds them to the system prompt.
48
+
49
+ Args:
50
+ system_prompt: The original system prompt
51
+ user_message: The initial user message
52
+ user: The user making the request
53
+
54
+ Returns:
55
+ Enhanced system prompt with relevant examples from memory
56
+ """
57
+ if not self.agent_memory:
58
+ return system_prompt
59
+
60
+ try:
61
+ # Import here to avoid circular dependency
62
+ from ..tool import ToolContext
63
+ import uuid
64
+
65
+ # Create a temporary context for memory search
66
+ context = ToolContext(
67
+ user=user,
68
+ conversation_id="temp",
69
+ request_id=str(uuid.uuid4()),
70
+ agent_memory=self.agent_memory,
71
+ )
72
+
73
+ # Search for relevant text memories based on user message
74
+ memories: List[
75
+ "TextMemorySearchResult"
76
+ ] = await self.agent_memory.search_text_memories(
77
+ query=user_message, context=context, limit=5
78
+ )
79
+
80
+ if not memories:
81
+ return system_prompt
82
+
83
+ # Format memories as context snippets to add to system prompt
84
+ examples_section = "\n\n## Relevant Context from Memory\n\n"
85
+ examples_section += "The following domain knowledge and context from prior interactions may be relevant:\n\n"
86
+
87
+ for result in memories:
88
+ memory = result.memory
89
+ examples_section += f"• {memory.content}\n"
90
+
91
+ # Append examples to system prompt
92
+ return system_prompt + examples_section
93
+
94
+ except Exception as e:
95
+ # If memory search fails, return original prompt
96
+ # Don't fail the entire request due to memory issues
97
+ import logging
98
+
99
+ logger = logging.getLogger(__name__)
100
+ logger.warning(f"Failed to enhance system prompt with memories: {e}")
101
+ return system_prompt
102
+
103
+ async def enhance_user_messages(
104
+ self, messages: list["LlmMessage"], user: "User"
105
+ ) -> list["LlmMessage"]:
106
+ """Enhance user messages.
107
+
108
+ The default implementation doesn't modify user messages.
109
+ Override this to add context to user messages if needed.
110
+
111
+ Args:
112
+ messages: The list of messages
113
+ user: The user making the request
114
+
115
+ Returns:
116
+ Original list of messages (unmodified)
117
+ """
118
+ return messages
@@ -0,0 +1,10 @@
1
+ """
2
+ Context enrichment system for adding data to tool execution context.
3
+
4
+ This module provides interfaces for enriching ToolContext with additional
5
+ data before tool execution.
6
+ """
7
+
8
+ from .base import ToolContextEnricher
9
+
10
+ __all__ = ["ToolContextEnricher"]
@@ -0,0 +1,59 @@
1
+ """
2
+ Base context enricher interface.
3
+
4
+ Context enrichers allow you to add additional data to the ToolContext
5
+ before tools are executed.
6
+ """
7
+
8
+ from abc import ABC
9
+ from typing import TYPE_CHECKING
10
+
11
+ if TYPE_CHECKING:
12
+ from ..tool.models import ToolContext
13
+
14
+
15
+ class ToolContextEnricher(ABC):
16
+ """Enricher for adding data to ToolContext.
17
+
18
+ Subclass this to create custom enrichers that can:
19
+ - Add user preferences from database
20
+ - Inject session state
21
+ - Add temporal context (timezone, current date)
22
+ - Include user history or profile data
23
+ - Add environment-specific configuration
24
+
25
+ Example:
26
+ class UserPreferencesEnricher(ToolContextEnricher):
27
+ def __init__(self, db):
28
+ self.db = db
29
+
30
+ async def enrich_context(self, context: ToolContext) -> ToolContext:
31
+ # Fetch user preferences
32
+ prefs = await self.db.get_user_preferences(context.user.id)
33
+
34
+ # Add to context metadata
35
+ context.metadata["preferences"] = prefs
36
+ context.metadata["timezone"] = prefs.get("timezone", "UTC")
37
+
38
+ return context
39
+
40
+ agent = AgentRunner(
41
+ llm_service=...,
42
+ context_enrichers=[UserPreferencesEnricher(db), SessionEnricher()]
43
+ )
44
+ """
45
+
46
+ async def enrich_context(self, context: "ToolContext") -> "ToolContext":
47
+ """Enrich the tool execution context with additional data.
48
+
49
+ Args:
50
+ context: The tool context to enrich
51
+
52
+ Returns:
53
+ Enriched context (typically modified in-place)
54
+
55
+ Note:
56
+ Enrichers typically modify the context.metadata dict to add
57
+ additional data that tools can access.
58
+ """
59
+ return context