vanna 0.7.8__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.
- vanna/__init__.py +167 -395
- vanna/agents/__init__.py +7 -0
- vanna/capabilities/__init__.py +17 -0
- vanna/capabilities/agent_memory/__init__.py +21 -0
- vanna/capabilities/agent_memory/base.py +103 -0
- vanna/capabilities/agent_memory/models.py +53 -0
- vanna/capabilities/file_system/__init__.py +14 -0
- vanna/capabilities/file_system/base.py +71 -0
- vanna/capabilities/file_system/models.py +25 -0
- vanna/capabilities/sql_runner/__init__.py +13 -0
- vanna/capabilities/sql_runner/base.py +37 -0
- vanna/capabilities/sql_runner/models.py +13 -0
- vanna/components/__init__.py +92 -0
- vanna/components/base.py +11 -0
- vanna/components/rich/__init__.py +83 -0
- vanna/components/rich/containers/__init__.py +7 -0
- vanna/components/rich/containers/card.py +20 -0
- vanna/components/rich/data/__init__.py +9 -0
- vanna/components/rich/data/chart.py +17 -0
- vanna/components/rich/data/dataframe.py +93 -0
- vanna/components/rich/feedback/__init__.py +21 -0
- vanna/components/rich/feedback/badge.py +16 -0
- vanna/components/rich/feedback/icon_text.py +14 -0
- vanna/components/rich/feedback/log_viewer.py +41 -0
- vanna/components/rich/feedback/notification.py +19 -0
- vanna/components/rich/feedback/progress.py +37 -0
- vanna/components/rich/feedback/status_card.py +28 -0
- vanna/components/rich/feedback/status_indicator.py +14 -0
- vanna/components/rich/interactive/__init__.py +21 -0
- vanna/components/rich/interactive/button.py +95 -0
- vanna/components/rich/interactive/task_list.py +58 -0
- vanna/components/rich/interactive/ui_state.py +93 -0
- vanna/components/rich/specialized/__init__.py +7 -0
- vanna/components/rich/specialized/artifact.py +20 -0
- vanna/components/rich/text.py +16 -0
- vanna/components/simple/__init__.py +15 -0
- vanna/components/simple/image.py +15 -0
- vanna/components/simple/link.py +15 -0
- vanna/components/simple/text.py +11 -0
- vanna/core/__init__.py +193 -0
- vanna/core/_compat.py +19 -0
- vanna/core/agent/__init__.py +10 -0
- vanna/core/agent/agent.py +1407 -0
- vanna/core/agent/config.py +123 -0
- vanna/core/audit/__init__.py +28 -0
- vanna/core/audit/base.py +299 -0
- vanna/core/audit/models.py +131 -0
- vanna/core/component_manager.py +329 -0
- vanna/core/components.py +53 -0
- vanna/core/enhancer/__init__.py +11 -0
- vanna/core/enhancer/base.py +94 -0
- vanna/core/enhancer/default.py +118 -0
- vanna/core/enricher/__init__.py +10 -0
- vanna/core/enricher/base.py +59 -0
- vanna/core/errors.py +47 -0
- vanna/core/evaluation/__init__.py +81 -0
- vanna/core/evaluation/base.py +186 -0
- vanna/core/evaluation/dataset.py +254 -0
- vanna/core/evaluation/evaluators.py +376 -0
- vanna/core/evaluation/report.py +289 -0
- vanna/core/evaluation/runner.py +313 -0
- vanna/core/filter/__init__.py +10 -0
- vanna/core/filter/base.py +67 -0
- vanna/core/lifecycle/__init__.py +10 -0
- vanna/core/lifecycle/base.py +83 -0
- vanna/core/llm/__init__.py +16 -0
- vanna/core/llm/base.py +40 -0
- vanna/core/llm/models.py +61 -0
- vanna/core/middleware/__init__.py +10 -0
- vanna/core/middleware/base.py +69 -0
- vanna/core/observability/__init__.py +11 -0
- vanna/core/observability/base.py +88 -0
- vanna/core/observability/models.py +47 -0
- vanna/core/recovery/__init__.py +11 -0
- vanna/core/recovery/base.py +84 -0
- vanna/core/recovery/models.py +32 -0
- vanna/core/registry.py +278 -0
- vanna/core/rich_component.py +156 -0
- vanna/core/simple_component.py +27 -0
- vanna/core/storage/__init__.py +14 -0
- vanna/core/storage/base.py +46 -0
- vanna/core/storage/models.py +46 -0
- vanna/core/system_prompt/__init__.py +13 -0
- vanna/core/system_prompt/base.py +36 -0
- vanna/core/system_prompt/default.py +157 -0
- vanna/core/tool/__init__.py +18 -0
- vanna/core/tool/base.py +70 -0
- vanna/core/tool/models.py +84 -0
- vanna/core/user/__init__.py +17 -0
- vanna/core/user/base.py +29 -0
- vanna/core/user/models.py +25 -0
- vanna/core/user/request_context.py +70 -0
- vanna/core/user/resolver.py +42 -0
- vanna/core/validation.py +164 -0
- vanna/core/workflow/__init__.py +12 -0
- vanna/core/workflow/base.py +254 -0
- vanna/core/workflow/default.py +789 -0
- vanna/examples/__init__.py +1 -0
- vanna/examples/__main__.py +44 -0
- vanna/examples/anthropic_quickstart.py +80 -0
- vanna/examples/artifact_example.py +293 -0
- vanna/examples/claude_sqlite_example.py +236 -0
- vanna/examples/coding_agent_example.py +300 -0
- vanna/examples/custom_system_prompt_example.py +174 -0
- vanna/examples/default_workflow_handler_example.py +208 -0
- vanna/examples/email_auth_example.py +340 -0
- vanna/examples/evaluation_example.py +269 -0
- vanna/examples/extensibility_example.py +262 -0
- vanna/examples/minimal_example.py +67 -0
- vanna/examples/mock_auth_example.py +227 -0
- vanna/examples/mock_custom_tool.py +311 -0
- vanna/examples/mock_quickstart.py +79 -0
- vanna/examples/mock_quota_example.py +145 -0
- vanna/examples/mock_rich_components_demo.py +396 -0
- vanna/examples/mock_sqlite_example.py +223 -0
- vanna/examples/openai_quickstart.py +83 -0
- vanna/examples/primitive_components_demo.py +305 -0
- vanna/examples/quota_lifecycle_example.py +139 -0
- vanna/examples/visualization_example.py +251 -0
- vanna/integrations/__init__.py +17 -0
- vanna/integrations/anthropic/__init__.py +9 -0
- vanna/integrations/anthropic/llm.py +270 -0
- vanna/integrations/azureopenai/__init__.py +9 -0
- vanna/integrations/azureopenai/llm.py +329 -0
- vanna/integrations/azuresearch/__init__.py +7 -0
- vanna/integrations/azuresearch/agent_memory.py +413 -0
- vanna/integrations/bigquery/__init__.py +5 -0
- vanna/integrations/bigquery/sql_runner.py +81 -0
- vanna/integrations/chromadb/__init__.py +104 -0
- vanna/integrations/chromadb/agent_memory.py +416 -0
- vanna/integrations/clickhouse/__init__.py +5 -0
- vanna/integrations/clickhouse/sql_runner.py +82 -0
- vanna/integrations/duckdb/__init__.py +5 -0
- vanna/integrations/duckdb/sql_runner.py +65 -0
- vanna/integrations/faiss/__init__.py +7 -0
- vanna/integrations/faiss/agent_memory.py +431 -0
- vanna/integrations/google/__init__.py +9 -0
- vanna/integrations/google/gemini.py +370 -0
- vanna/integrations/hive/__init__.py +5 -0
- vanna/integrations/hive/sql_runner.py +87 -0
- vanna/integrations/local/__init__.py +17 -0
- vanna/integrations/local/agent_memory/__init__.py +7 -0
- vanna/integrations/local/agent_memory/in_memory.py +285 -0
- vanna/integrations/local/audit.py +59 -0
- vanna/integrations/local/file_system.py +242 -0
- vanna/integrations/local/file_system_conversation_store.py +255 -0
- vanna/integrations/local/storage.py +62 -0
- vanna/integrations/marqo/__init__.py +7 -0
- vanna/integrations/marqo/agent_memory.py +354 -0
- vanna/integrations/milvus/__init__.py +7 -0
- vanna/integrations/milvus/agent_memory.py +458 -0
- vanna/integrations/mock/__init__.py +9 -0
- vanna/integrations/mock/llm.py +65 -0
- vanna/integrations/mssql/__init__.py +5 -0
- vanna/integrations/mssql/sql_runner.py +66 -0
- vanna/integrations/mysql/__init__.py +5 -0
- vanna/integrations/mysql/sql_runner.py +92 -0
- vanna/integrations/ollama/__init__.py +7 -0
- vanna/integrations/ollama/llm.py +252 -0
- vanna/integrations/openai/__init__.py +10 -0
- vanna/integrations/openai/llm.py +267 -0
- vanna/integrations/openai/responses.py +163 -0
- vanna/integrations/opensearch/__init__.py +7 -0
- vanna/integrations/opensearch/agent_memory.py +411 -0
- vanna/integrations/oracle/__init__.py +5 -0
- vanna/integrations/oracle/sql_runner.py +75 -0
- vanna/integrations/pinecone/__init__.py +7 -0
- vanna/integrations/pinecone/agent_memory.py +329 -0
- vanna/integrations/plotly/__init__.py +5 -0
- vanna/integrations/plotly/chart_generator.py +313 -0
- vanna/integrations/postgres/__init__.py +9 -0
- vanna/integrations/postgres/sql_runner.py +112 -0
- vanna/integrations/premium/agent_memory/__init__.py +7 -0
- vanna/integrations/premium/agent_memory/premium.py +186 -0
- vanna/integrations/presto/__init__.py +5 -0
- vanna/integrations/presto/sql_runner.py +107 -0
- vanna/integrations/qdrant/__init__.py +7 -0
- vanna/integrations/qdrant/agent_memory.py +461 -0
- vanna/integrations/snowflake/__init__.py +5 -0
- vanna/integrations/snowflake/sql_runner.py +147 -0
- vanna/integrations/sqlite/__init__.py +9 -0
- vanna/integrations/sqlite/sql_runner.py +65 -0
- vanna/integrations/weaviate/__init__.py +7 -0
- vanna/integrations/weaviate/agent_memory.py +428 -0
- vanna/{ZhipuAI → legacy/ZhipuAI}/ZhipuAI_embeddings.py +11 -11
- vanna/legacy/__init__.py +403 -0
- vanna/legacy/adapter.py +463 -0
- vanna/{advanced → legacy/advanced}/__init__.py +3 -1
- vanna/{anthropic → legacy/anthropic}/anthropic_chat.py +9 -7
- vanna/{azuresearch → legacy/azuresearch}/azuresearch_vector.py +79 -41
- vanna/{base → legacy/base}/base.py +247 -223
- vanna/legacy/bedrock/__init__.py +1 -0
- vanna/{bedrock → legacy/bedrock}/bedrock_converse.py +13 -12
- vanna/{chromadb → legacy/chromadb}/chromadb_vector.py +3 -1
- vanna/legacy/cohere/__init__.py +2 -0
- vanna/{cohere → legacy/cohere}/cohere_chat.py +19 -14
- vanna/{cohere → legacy/cohere}/cohere_embeddings.py +25 -19
- vanna/{deepseek → legacy/deepseek}/deepseek_chat.py +5 -6
- vanna/legacy/faiss/__init__.py +1 -0
- vanna/{faiss → legacy/faiss}/faiss.py +113 -59
- vanna/{flask → legacy/flask}/__init__.py +84 -43
- vanna/{flask → legacy/flask}/assets.py +5 -5
- vanna/{flask → legacy/flask}/auth.py +5 -4
- vanna/{google → legacy/google}/bigquery_vector.py +75 -42
- vanna/{google → legacy/google}/gemini_chat.py +7 -3
- vanna/{hf → legacy/hf}/hf.py +0 -1
- vanna/{milvus → legacy/milvus}/milvus_vector.py +58 -35
- vanna/{mock → legacy/mock}/llm.py +0 -1
- vanna/legacy/mock/vectordb.py +67 -0
- vanna/legacy/ollama/ollama.py +110 -0
- vanna/{openai → legacy/openai}/openai_chat.py +2 -6
- vanna/legacy/opensearch/opensearch_vector.py +369 -0
- vanna/legacy/opensearch/opensearch_vector_semantic.py +200 -0
- vanna/legacy/oracle/oracle_vector.py +584 -0
- vanna/{pgvector → legacy/pgvector}/pgvector.py +42 -13
- vanna/{qdrant → legacy/qdrant}/qdrant.py +2 -6
- vanna/legacy/qianfan/Qianfan_Chat.py +170 -0
- vanna/legacy/qianfan/Qianfan_embeddings.py +36 -0
- vanna/legacy/qianwen/QianwenAI_chat.py +132 -0
- vanna/{remote.py → legacy/remote.py} +28 -26
- vanna/{utils.py → legacy/utils.py} +6 -11
- vanna/{vannadb → legacy/vannadb}/vannadb_vector.py +115 -46
- vanna/{vllm → legacy/vllm}/vllm.py +5 -6
- vanna/{weaviate → legacy/weaviate}/weaviate_vector.py +59 -40
- vanna/{xinference → legacy/xinference}/xinference.py +6 -6
- vanna/py.typed +0 -0
- vanna/servers/__init__.py +16 -0
- vanna/servers/__main__.py +8 -0
- vanna/servers/base/__init__.py +18 -0
- vanna/servers/base/chat_handler.py +65 -0
- vanna/servers/base/models.py +111 -0
- vanna/servers/base/rich_chat_handler.py +141 -0
- vanna/servers/base/templates.py +331 -0
- vanna/servers/cli/__init__.py +7 -0
- vanna/servers/cli/server_runner.py +204 -0
- vanna/servers/fastapi/__init__.py +7 -0
- vanna/servers/fastapi/app.py +163 -0
- vanna/servers/fastapi/routes.py +183 -0
- vanna/servers/flask/__init__.py +7 -0
- vanna/servers/flask/app.py +132 -0
- vanna/servers/flask/routes.py +137 -0
- vanna/tools/__init__.py +41 -0
- vanna/tools/agent_memory.py +322 -0
- vanna/tools/file_system.py +879 -0
- vanna/tools/python.py +222 -0
- vanna/tools/run_sql.py +165 -0
- vanna/tools/visualize_data.py +195 -0
- vanna/utils/__init__.py +0 -0
- vanna/web_components/__init__.py +44 -0
- vanna-2.0.0.dist-info/METADATA +485 -0
- vanna-2.0.0.dist-info/RECORD +289 -0
- vanna-2.0.0.dist-info/entry_points.txt +3 -0
- vanna/bedrock/__init__.py +0 -1
- vanna/cohere/__init__.py +0 -2
- vanna/faiss/__init__.py +0 -1
- vanna/mock/vectordb.py +0 -55
- vanna/ollama/ollama.py +0 -103
- vanna/opensearch/opensearch_vector.py +0 -392
- vanna/opensearch/opensearch_vector_semantic.py +0 -175
- vanna/oracle/oracle_vector.py +0 -585
- vanna/qianfan/Qianfan_Chat.py +0 -165
- vanna/qianfan/Qianfan_embeddings.py +0 -36
- vanna/qianwen/QianwenAI_chat.py +0 -133
- vanna-0.7.8.dist-info/METADATA +0 -408
- vanna-0.7.8.dist-info/RECORD +0 -79
- /vanna/{ZhipuAI → legacy/ZhipuAI}/ZhipuAI_Chat.py +0 -0
- /vanna/{ZhipuAI → legacy/ZhipuAI}/__init__.py +0 -0
- /vanna/{anthropic → legacy/anthropic}/__init__.py +0 -0
- /vanna/{azuresearch → legacy/azuresearch}/__init__.py +0 -0
- /vanna/{base → legacy/base}/__init__.py +0 -0
- /vanna/{chromadb → legacy/chromadb}/__init__.py +0 -0
- /vanna/{deepseek → legacy/deepseek}/__init__.py +0 -0
- /vanna/{exceptions → legacy/exceptions}/__init__.py +0 -0
- /vanna/{google → legacy/google}/__init__.py +0 -0
- /vanna/{hf → legacy/hf}/__init__.py +0 -0
- /vanna/{local.py → legacy/local.py} +0 -0
- /vanna/{marqo → legacy/marqo}/__init__.py +0 -0
- /vanna/{marqo → legacy/marqo}/marqo.py +0 -0
- /vanna/{milvus → legacy/milvus}/__init__.py +0 -0
- /vanna/{mistral → legacy/mistral}/__init__.py +0 -0
- /vanna/{mistral → legacy/mistral}/mistral.py +0 -0
- /vanna/{mock → legacy/mock}/__init__.py +0 -0
- /vanna/{mock → legacy/mock}/embedding.py +0 -0
- /vanna/{ollama → legacy/ollama}/__init__.py +0 -0
- /vanna/{openai → legacy/openai}/__init__.py +0 -0
- /vanna/{openai → legacy/openai}/openai_embeddings.py +0 -0
- /vanna/{opensearch → legacy/opensearch}/__init__.py +0 -0
- /vanna/{oracle → legacy/oracle}/__init__.py +0 -0
- /vanna/{pgvector → legacy/pgvector}/__init__.py +0 -0
- /vanna/{pinecone → legacy/pinecone}/__init__.py +0 -0
- /vanna/{pinecone → legacy/pinecone}/pinecone_vector.py +0 -0
- /vanna/{qdrant → legacy/qdrant}/__init__.py +0 -0
- /vanna/{qianfan → legacy/qianfan}/__init__.py +0 -0
- /vanna/{qianwen → legacy/qianwen}/QianwenAI_embeddings.py +0 -0
- /vanna/{qianwen → legacy/qianwen}/__init__.py +0 -0
- /vanna/{types → legacy/types}/__init__.py +0 -0
- /vanna/{vannadb → legacy/vannadb}/__init__.py +0 -0
- /vanna/{vllm → legacy/vllm}/__init__.py +0 -0
- /vanna/{weaviate → legacy/weaviate}/__init__.py +0 -0
- /vanna/{xinference → legacy/xinference}/__init__.py +0 -0
- {vanna-0.7.8.dist-info → vanna-2.0.0.dist-info}/WHEEL +0 -0
- {vanna-0.7.8.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()
|
vanna/core/components.py
ADDED
|
@@ -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
|