dao-ai 0.1.2__py3-none-any.whl → 0.1.20__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 (69) hide show
  1. dao_ai/apps/__init__.py +24 -0
  2. dao_ai/apps/handlers.py +105 -0
  3. dao_ai/apps/model_serving.py +29 -0
  4. dao_ai/apps/resources.py +1122 -0
  5. dao_ai/apps/server.py +39 -0
  6. dao_ai/cli.py +546 -37
  7. dao_ai/config.py +1179 -139
  8. dao_ai/evaluation.py +543 -0
  9. dao_ai/genie/__init__.py +55 -7
  10. dao_ai/genie/cache/__init__.py +34 -7
  11. dao_ai/genie/cache/base.py +143 -2
  12. dao_ai/genie/cache/context_aware/__init__.py +31 -0
  13. dao_ai/genie/cache/context_aware/base.py +1151 -0
  14. dao_ai/genie/cache/context_aware/in_memory.py +609 -0
  15. dao_ai/genie/cache/context_aware/persistent.py +802 -0
  16. dao_ai/genie/cache/context_aware/postgres.py +1166 -0
  17. dao_ai/genie/cache/core.py +1 -1
  18. dao_ai/genie/cache/lru.py +257 -75
  19. dao_ai/genie/cache/optimization.py +890 -0
  20. dao_ai/genie/core.py +235 -11
  21. dao_ai/memory/postgres.py +175 -39
  22. dao_ai/middleware/__init__.py +38 -0
  23. dao_ai/middleware/assertions.py +3 -3
  24. dao_ai/middleware/context_editing.py +230 -0
  25. dao_ai/middleware/core.py +4 -4
  26. dao_ai/middleware/guardrails.py +3 -3
  27. dao_ai/middleware/human_in_the_loop.py +3 -2
  28. dao_ai/middleware/message_validation.py +4 -4
  29. dao_ai/middleware/model_call_limit.py +77 -0
  30. dao_ai/middleware/model_retry.py +121 -0
  31. dao_ai/middleware/pii.py +157 -0
  32. dao_ai/middleware/summarization.py +1 -1
  33. dao_ai/middleware/tool_call_limit.py +210 -0
  34. dao_ai/middleware/tool_retry.py +174 -0
  35. dao_ai/middleware/tool_selector.py +129 -0
  36. dao_ai/models.py +327 -370
  37. dao_ai/nodes.py +9 -16
  38. dao_ai/orchestration/core.py +33 -9
  39. dao_ai/orchestration/supervisor.py +29 -13
  40. dao_ai/orchestration/swarm.py +6 -1
  41. dao_ai/{prompts.py → prompts/__init__.py} +12 -61
  42. dao_ai/prompts/instructed_retriever_decomposition.yaml +58 -0
  43. dao_ai/prompts/instruction_reranker.yaml +14 -0
  44. dao_ai/prompts/router.yaml +37 -0
  45. dao_ai/prompts/verifier.yaml +46 -0
  46. dao_ai/providers/base.py +28 -2
  47. dao_ai/providers/databricks.py +363 -33
  48. dao_ai/state.py +1 -0
  49. dao_ai/tools/__init__.py +5 -3
  50. dao_ai/tools/genie.py +103 -26
  51. dao_ai/tools/instructed_retriever.py +366 -0
  52. dao_ai/tools/instruction_reranker.py +202 -0
  53. dao_ai/tools/mcp.py +539 -97
  54. dao_ai/tools/router.py +89 -0
  55. dao_ai/tools/slack.py +13 -2
  56. dao_ai/tools/sql.py +7 -3
  57. dao_ai/tools/unity_catalog.py +32 -10
  58. dao_ai/tools/vector_search.py +493 -160
  59. dao_ai/tools/verifier.py +159 -0
  60. dao_ai/utils.py +182 -2
  61. dao_ai/vector_search.py +46 -1
  62. {dao_ai-0.1.2.dist-info → dao_ai-0.1.20.dist-info}/METADATA +45 -9
  63. dao_ai-0.1.20.dist-info/RECORD +89 -0
  64. dao_ai/agent_as_code.py +0 -22
  65. dao_ai/genie/cache/semantic.py +0 -970
  66. dao_ai-0.1.2.dist-info/RECORD +0 -64
  67. {dao_ai-0.1.2.dist-info → dao_ai-0.1.20.dist-info}/WHEEL +0 -0
  68. {dao_ai-0.1.2.dist-info → dao_ai-0.1.20.dist-info}/entry_points.txt +0 -0
  69. {dao_ai-0.1.2.dist-info → dao_ai-0.1.20.dist-info}/licenses/LICENSE +0 -0
@@ -13,10 +13,92 @@ from dataclasses import dataclass
13
13
  from datetime import datetime
14
14
  from typing import TYPE_CHECKING
15
15
 
16
- from databricks_ai_bridge.genie import GenieResponse
16
+ from databricks.sdk import WorkspaceClient
17
+ from databricks.sdk.service.dashboards import GenieFeedbackRating
18
+ from loguru import logger
17
19
 
18
20
  if TYPE_CHECKING:
19
21
  from dao_ai.genie.cache.base import CacheResult
22
+ from dao_ai.genie.core import GenieResponse
23
+
24
+
25
+ def get_latest_message_id(
26
+ workspace_client: WorkspaceClient,
27
+ space_id: str,
28
+ conversation_id: str,
29
+ ) -> str | None:
30
+ """
31
+ Look up the most recent message ID for a conversation.
32
+
33
+ This is used when sending feedback without a specific message_id.
34
+
35
+ Args:
36
+ workspace_client: The Databricks workspace client
37
+ space_id: The Genie space ID
38
+ conversation_id: The conversation ID to look up
39
+
40
+ Returns:
41
+ The message_id of the most recent message, or None if not found
42
+ """
43
+ try:
44
+ response = workspace_client.genie.list_conversation_messages(
45
+ space_id=space_id,
46
+ conversation_id=conversation_id,
47
+ )
48
+ if response.messages:
49
+ # Messages are returned in order; get the last one (most recent)
50
+ messages = list(response.messages)
51
+ if messages:
52
+ # Use message_id if available, fall back to id (legacy)
53
+ latest = messages[-1]
54
+ return latest.message_id if latest.message_id else latest.id
55
+ return None
56
+ except Exception as e:
57
+ logger.warning(
58
+ "Failed to look up latest message_id",
59
+ space_id=space_id,
60
+ conversation_id=conversation_id,
61
+ error=str(e),
62
+ )
63
+ return None
64
+
65
+
66
+ def get_message_content(
67
+ workspace_client: WorkspaceClient,
68
+ space_id: str,
69
+ conversation_id: str,
70
+ message_id: str,
71
+ ) -> str | None:
72
+ """
73
+ Get the content (question text) of a specific message.
74
+
75
+ This is used to find matching cache entries when invalidating on negative feedback.
76
+
77
+ Args:
78
+ workspace_client: The Databricks workspace client
79
+ space_id: The Genie space ID
80
+ conversation_id: The conversation ID
81
+ message_id: The message ID to look up
82
+
83
+ Returns:
84
+ The message content (question text), or None if not found
85
+ """
86
+ try:
87
+ message = workspace_client.genie.get_message(
88
+ space_id=space_id,
89
+ conversation_id=conversation_id,
90
+ message_id=message_id,
91
+ )
92
+ return message.content if message else None
93
+ except Exception as e:
94
+ logger.warning(
95
+ "Failed to get message content",
96
+ space_id=space_id,
97
+ conversation_id=conversation_id,
98
+ message_id=message_id,
99
+ error=str(e),
100
+ )
101
+ return None
20
102
 
21
103
 
22
104
  class GenieServiceBase(ABC):
@@ -40,6 +122,46 @@ class GenieServiceBase(ABC):
40
122
  """The space ID for the Genie service."""
41
123
  pass
42
124
 
125
+ @property
126
+ @abstractmethod
127
+ def workspace_client(self) -> WorkspaceClient | None:
128
+ """Optional WorkspaceClient for API operations like from_space() and feedback."""
129
+ pass
130
+
131
+ @abstractmethod
132
+ def send_feedback(
133
+ self,
134
+ conversation_id: str,
135
+ rating: GenieFeedbackRating,
136
+ message_id: str | None = None,
137
+ was_cache_hit: bool = False,
138
+ ) -> None:
139
+ """
140
+ Send feedback for a Genie message.
141
+
142
+ This method sends user feedback (positive/negative/none) to the Genie service
143
+ and handles cache invalidation for negative feedback.
144
+
145
+ Args:
146
+ conversation_id: The conversation containing the message
147
+ rating: The feedback rating (POSITIVE, NEGATIVE, or NONE)
148
+ message_id: Optional message ID. If None, looks up the most recent message
149
+ in the conversation.
150
+ was_cache_hit: Whether the response being rated was served from cache.
151
+ If True and rating is NEGATIVE, the cache entry is invalidated but
152
+ no feedback is sent to the Genie API (since no Genie message exists
153
+ for cached responses).
154
+
155
+ Note:
156
+ For cached responses (was_cache_hit=True), the message_id from the
157
+ original Genie response is stored in the cache entry. This enables
158
+ sending feedback to Genie even for cache hits.
159
+
160
+ The cache_entry_id in CacheResult can be used to trace which cache
161
+ entry was used for a particular prompt in genie_prompt_history.
162
+ """
163
+ pass
164
+
43
165
 
44
166
  @dataclass
45
167
  class SQLCacheEntry:
@@ -48,12 +170,22 @@ class SQLCacheEntry:
48
170
 
49
171
  Instead of caching the full result, we cache the SQL query so that
50
172
  on cache hit we can re-execute it to get fresh data.
173
+
174
+ Attributes:
175
+ query: The SQL query to execute
176
+ description: Description of the query
177
+ conversation_id: The conversation ID where this query originated
178
+ created_at: When the entry was created
179
+ message_id: The original Genie message ID (for feedback on cache hits)
180
+ cache_entry_id: The database row ID (for persistent caches)
51
181
  """
52
182
 
53
183
  query: str
54
184
  description: str
55
185
  conversation_id: str
56
186
  created_at: datetime
187
+ message_id: str | None = None
188
+ cache_entry_id: int | None = None
57
189
 
58
190
 
59
191
  @dataclass
@@ -62,11 +194,20 @@ class CacheResult:
62
194
  Result of a cache-aware query with metadata about cache behavior.
63
195
 
64
196
  Attributes:
65
- response: The GenieResponse (fresh data, possibly from cached SQL)
197
+ response: The GenieResponse (fresh data, possibly from cached SQL).
198
+ This is the extended dao_ai.genie.GenieResponse which includes message_id,
199
+ not the base databricks_ai_bridge.genie.GenieResponse.
66
200
  cache_hit: Whether the SQL query came from cache
67
201
  served_by: Name of the layer that served the cached SQL (None if from origin)
202
+ message_id: The Genie message ID (for sending feedback). Available from
203
+ the extended Genie class on cache miss, or from stored cache entry on hit.
204
+ cache_entry_id: The database row ID of the cache entry that served this hit.
205
+ Only populated on cache hits from persistent caches (PostgreSQL).
206
+ Can be used to trace back to genie_prompt_history entries.
68
207
  """
69
208
 
70
209
  response: GenieResponse
71
210
  cache_hit: bool
72
211
  served_by: str | None = None
212
+ message_id: str | None = None
213
+ cache_entry_id: int | None = None
@@ -0,0 +1,31 @@
1
+ """
2
+ Context-aware cache implementations for Genie SQL queries.
3
+
4
+ This package provides context-aware caching layers that use semantic similarity
5
+ matching with dual embeddings (question + conversation context) for high-precision
6
+ cache lookups.
7
+
8
+ Available implementations:
9
+ - InMemoryContextAwareGenieService: In-memory cache with L2 distance matching
10
+ - PostgresContextAwareGenieService: PostgreSQL pg_vector-based persistent cache
11
+
12
+ Base classes:
13
+ - ContextAwareGenieService: Abstract base for all context-aware cache implementations
14
+ - PersistentContextAwareGenieCacheService: Abstract base for database-backed implementations
15
+ """
16
+
17
+ from dao_ai.genie.cache.context_aware.base import ContextAwareGenieService
18
+ from dao_ai.genie.cache.context_aware.in_memory import InMemoryContextAwareGenieService
19
+ from dao_ai.genie.cache.context_aware.persistent import (
20
+ PersistentContextAwareGenieCacheService,
21
+ )
22
+ from dao_ai.genie.cache.context_aware.postgres import PostgresContextAwareGenieService
23
+
24
+ __all__ = [
25
+ # Base classes
26
+ "ContextAwareGenieService",
27
+ "PersistentContextAwareGenieCacheService",
28
+ # Implementations
29
+ "InMemoryContextAwareGenieService",
30
+ "PostgresContextAwareGenieService",
31
+ ]