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.
- dao_ai/apps/__init__.py +24 -0
- dao_ai/apps/handlers.py +105 -0
- dao_ai/apps/model_serving.py +29 -0
- dao_ai/apps/resources.py +1122 -0
- dao_ai/apps/server.py +39 -0
- dao_ai/cli.py +546 -37
- dao_ai/config.py +1179 -139
- dao_ai/evaluation.py +543 -0
- dao_ai/genie/__init__.py +55 -7
- dao_ai/genie/cache/__init__.py +34 -7
- dao_ai/genie/cache/base.py +143 -2
- dao_ai/genie/cache/context_aware/__init__.py +31 -0
- dao_ai/genie/cache/context_aware/base.py +1151 -0
- dao_ai/genie/cache/context_aware/in_memory.py +609 -0
- dao_ai/genie/cache/context_aware/persistent.py +802 -0
- dao_ai/genie/cache/context_aware/postgres.py +1166 -0
- dao_ai/genie/cache/core.py +1 -1
- dao_ai/genie/cache/lru.py +257 -75
- dao_ai/genie/cache/optimization.py +890 -0
- dao_ai/genie/core.py +235 -11
- dao_ai/memory/postgres.py +175 -39
- dao_ai/middleware/__init__.py +38 -0
- dao_ai/middleware/assertions.py +3 -3
- dao_ai/middleware/context_editing.py +230 -0
- dao_ai/middleware/core.py +4 -4
- dao_ai/middleware/guardrails.py +3 -3
- dao_ai/middleware/human_in_the_loop.py +3 -2
- dao_ai/middleware/message_validation.py +4 -4
- dao_ai/middleware/model_call_limit.py +77 -0
- dao_ai/middleware/model_retry.py +121 -0
- dao_ai/middleware/pii.py +157 -0
- dao_ai/middleware/summarization.py +1 -1
- dao_ai/middleware/tool_call_limit.py +210 -0
- dao_ai/middleware/tool_retry.py +174 -0
- dao_ai/middleware/tool_selector.py +129 -0
- dao_ai/models.py +327 -370
- dao_ai/nodes.py +9 -16
- dao_ai/orchestration/core.py +33 -9
- dao_ai/orchestration/supervisor.py +29 -13
- dao_ai/orchestration/swarm.py +6 -1
- dao_ai/{prompts.py → prompts/__init__.py} +12 -61
- dao_ai/prompts/instructed_retriever_decomposition.yaml +58 -0
- dao_ai/prompts/instruction_reranker.yaml +14 -0
- dao_ai/prompts/router.yaml +37 -0
- dao_ai/prompts/verifier.yaml +46 -0
- dao_ai/providers/base.py +28 -2
- dao_ai/providers/databricks.py +363 -33
- dao_ai/state.py +1 -0
- dao_ai/tools/__init__.py +5 -3
- dao_ai/tools/genie.py +103 -26
- dao_ai/tools/instructed_retriever.py +366 -0
- dao_ai/tools/instruction_reranker.py +202 -0
- dao_ai/tools/mcp.py +539 -97
- dao_ai/tools/router.py +89 -0
- dao_ai/tools/slack.py +13 -2
- dao_ai/tools/sql.py +7 -3
- dao_ai/tools/unity_catalog.py +32 -10
- dao_ai/tools/vector_search.py +493 -160
- dao_ai/tools/verifier.py +159 -0
- dao_ai/utils.py +182 -2
- dao_ai/vector_search.py +46 -1
- {dao_ai-0.1.2.dist-info → dao_ai-0.1.20.dist-info}/METADATA +45 -9
- dao_ai-0.1.20.dist-info/RECORD +89 -0
- dao_ai/agent_as_code.py +0 -22
- dao_ai/genie/cache/semantic.py +0 -970
- dao_ai-0.1.2.dist-info/RECORD +0 -64
- {dao_ai-0.1.2.dist-info → dao_ai-0.1.20.dist-info}/WHEEL +0 -0
- {dao_ai-0.1.2.dist-info → dao_ai-0.1.20.dist-info}/entry_points.txt +0 -0
- {dao_ai-0.1.2.dist-info → dao_ai-0.1.20.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Instruction-aware reranker for constraint-based document reordering.
|
|
3
|
+
|
|
4
|
+
Runs after FlashRank to apply user instructions and constraints to the ranking.
|
|
5
|
+
General-purpose component usable with any retrieval strategy.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
import mlflow
|
|
12
|
+
import yaml
|
|
13
|
+
from langchain_core.documents import Document
|
|
14
|
+
from langchain_core.language_models import BaseChatModel
|
|
15
|
+
from loguru import logger
|
|
16
|
+
from mlflow.entities import SpanType
|
|
17
|
+
|
|
18
|
+
from dao_ai.config import ColumnInfo, RankingResult
|
|
19
|
+
|
|
20
|
+
# Load prompt template
|
|
21
|
+
_PROMPT_PATH = Path(__file__).parent.parent / "prompts" / "instruction_reranker.yaml"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _load_prompt_template() -> dict[str, Any]:
|
|
25
|
+
"""Load the instruction reranker prompt template from YAML."""
|
|
26
|
+
with open(_PROMPT_PATH) as f:
|
|
27
|
+
return yaml.safe_load(f)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _format_documents(documents: list[Document]) -> str:
|
|
31
|
+
"""Format documents for the reranking prompt."""
|
|
32
|
+
if not documents:
|
|
33
|
+
return "No documents to rerank."
|
|
34
|
+
|
|
35
|
+
formatted = []
|
|
36
|
+
for i, doc in enumerate(documents):
|
|
37
|
+
metadata_str = ", ".join(
|
|
38
|
+
f"{k}: {v}"
|
|
39
|
+
for k, v in doc.metadata.items()
|
|
40
|
+
if not k.startswith("_") and k not in ("rrf_score", "reranker_score")
|
|
41
|
+
)
|
|
42
|
+
content_preview = (
|
|
43
|
+
doc.page_content[:300] + "..."
|
|
44
|
+
if len(doc.page_content) > 300
|
|
45
|
+
else doc.page_content
|
|
46
|
+
)
|
|
47
|
+
formatted.append(
|
|
48
|
+
f"[{i}] Content: {content_preview}\n Metadata: {metadata_str}"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
return "\n\n".join(formatted)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _format_column_info(columns: list[ColumnInfo] | None) -> str:
|
|
55
|
+
"""Format column info for the reranking prompt."""
|
|
56
|
+
if not columns:
|
|
57
|
+
return ""
|
|
58
|
+
return ", ".join(f"{c.name} ({c.type})" for c in columns)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@mlflow.trace(name="instruction_aware_rerank", span_type=SpanType.LLM)
|
|
62
|
+
def instruction_aware_rerank(
|
|
63
|
+
llm: BaseChatModel,
|
|
64
|
+
query: str,
|
|
65
|
+
documents: list[Document],
|
|
66
|
+
instructions: str | None = None,
|
|
67
|
+
schema_description: str | None = None,
|
|
68
|
+
columns: list[ColumnInfo] | None = None,
|
|
69
|
+
top_n: int | None = None,
|
|
70
|
+
) -> list[Document]:
|
|
71
|
+
"""
|
|
72
|
+
Rerank documents based on user instructions and constraints.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
llm: Language model for reranking
|
|
76
|
+
query: User's search query
|
|
77
|
+
documents: Documents to rerank (typically FlashRank output)
|
|
78
|
+
instructions: Custom reranking instructions
|
|
79
|
+
schema_description: Column names and types for context
|
|
80
|
+
columns: Structured column info for dynamic instruction generation
|
|
81
|
+
top_n: Number of documents to return (None = all scored documents)
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
Reranked documents with instruction_rerank_score in metadata
|
|
85
|
+
"""
|
|
86
|
+
if not documents:
|
|
87
|
+
return []
|
|
88
|
+
|
|
89
|
+
prompt_config = _load_prompt_template()
|
|
90
|
+
prompt_template = prompt_config["template"]
|
|
91
|
+
|
|
92
|
+
# Build dynamic default instructions based on columns
|
|
93
|
+
if columns:
|
|
94
|
+
column_names = ", ".join(c.name for c in columns)
|
|
95
|
+
default_instructions = (
|
|
96
|
+
f"Prioritize results that best match the user's explicit constraints "
|
|
97
|
+
f"on these columns: {column_names}. Prefer more specific matches over general results."
|
|
98
|
+
)
|
|
99
|
+
else:
|
|
100
|
+
default_instructions = (
|
|
101
|
+
"Prioritize results that best match the user's explicit constraints. "
|
|
102
|
+
"Prefer more specific matches over general results."
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# Build effective instructions - use columns for context (ignore verbose schema_description)
|
|
106
|
+
effective_instructions = instructions or default_instructions
|
|
107
|
+
|
|
108
|
+
# Add column context if available (simpler than full schema_description)
|
|
109
|
+
if columns:
|
|
110
|
+
effective_instructions += (
|
|
111
|
+
f"\n\nAvailable metadata fields: {_format_column_info(columns)}"
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
prompt = prompt_template.format(
|
|
115
|
+
query=query,
|
|
116
|
+
instructions=effective_instructions,
|
|
117
|
+
documents=_format_documents(documents),
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
logger.trace("Instruction reranking", query=query[:100], num_docs=len(documents))
|
|
121
|
+
|
|
122
|
+
logger.debug(
|
|
123
|
+
"Invoking structured output for reranking",
|
|
124
|
+
query=query[:50],
|
|
125
|
+
num_docs=len(documents),
|
|
126
|
+
prompt_length=len(prompt),
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
try:
|
|
130
|
+
structured_llm = llm.with_structured_output(RankingResult)
|
|
131
|
+
result: RankingResult = structured_llm.invoke(prompt)
|
|
132
|
+
logger.debug(
|
|
133
|
+
"Structured output succeeded",
|
|
134
|
+
num_rankings=len(result.rankings),
|
|
135
|
+
)
|
|
136
|
+
except Exception as e:
|
|
137
|
+
logger.warning(
|
|
138
|
+
"Structured output invocation failed",
|
|
139
|
+
error=str(e),
|
|
140
|
+
query=query[:50],
|
|
141
|
+
)
|
|
142
|
+
result = None
|
|
143
|
+
if result is None or not result.rankings:
|
|
144
|
+
logger.warning(
|
|
145
|
+
"Failed to get structured output from reranker, returning original order",
|
|
146
|
+
query=query[:50],
|
|
147
|
+
)
|
|
148
|
+
# Return fallback with decreasing scores based on original order
|
|
149
|
+
return [
|
|
150
|
+
Document(
|
|
151
|
+
page_content=doc.page_content,
|
|
152
|
+
metadata={
|
|
153
|
+
**doc.metadata,
|
|
154
|
+
"instruction_rerank_score": 1.0 - (i / len(documents)),
|
|
155
|
+
"instruction_rerank_reason": "fallback: extraction failed",
|
|
156
|
+
},
|
|
157
|
+
)
|
|
158
|
+
for i, doc in enumerate(documents[:top_n] if top_n else documents)
|
|
159
|
+
]
|
|
160
|
+
|
|
161
|
+
# Build reranked document list
|
|
162
|
+
reranked: list[Document] = []
|
|
163
|
+
for ranking in result.rankings:
|
|
164
|
+
if ranking.index < 0 or ranking.index >= len(documents):
|
|
165
|
+
logger.warning("Invalid document index from reranker", index=ranking.index)
|
|
166
|
+
continue
|
|
167
|
+
|
|
168
|
+
original_doc = documents[ranking.index]
|
|
169
|
+
reranked_doc = Document(
|
|
170
|
+
page_content=original_doc.page_content,
|
|
171
|
+
metadata={
|
|
172
|
+
**original_doc.metadata,
|
|
173
|
+
"instruction_rerank_score": ranking.score,
|
|
174
|
+
"instruction_rerank_reason": ranking.reason,
|
|
175
|
+
},
|
|
176
|
+
)
|
|
177
|
+
reranked.append(reranked_doc)
|
|
178
|
+
|
|
179
|
+
# Sort by score (highest first) - don't rely on LLM to sort
|
|
180
|
+
reranked.sort(
|
|
181
|
+
key=lambda d: d.metadata.get("instruction_rerank_score", 0),
|
|
182
|
+
reverse=True,
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
# Apply top_n limit after sorting
|
|
186
|
+
if top_n is not None and len(reranked) > top_n:
|
|
187
|
+
reranked = reranked[:top_n]
|
|
188
|
+
|
|
189
|
+
# Calculate and log average score
|
|
190
|
+
if reranked:
|
|
191
|
+
avg_score = sum(
|
|
192
|
+
d.metadata.get("instruction_rerank_score", 0) for d in reranked
|
|
193
|
+
) / len(reranked)
|
|
194
|
+
mlflow.set_tag("reranker.instruction_avg_score", f"{avg_score:.3f}")
|
|
195
|
+
|
|
196
|
+
logger.debug(
|
|
197
|
+
"Instruction reranking complete",
|
|
198
|
+
input_count=len(documents),
|
|
199
|
+
output_count=len(reranked),
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
return reranked
|