MindsDB 25.1.2.0__py3-none-any.whl → 25.1.5.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.
Potentially problematic release.
This version of MindsDB might be problematic. Click here for more details.
- {MindsDB-25.1.2.0.dist-info → MindsDB-25.1.5.0.dist-info}/METADATA +258 -255
- {MindsDB-25.1.2.0.dist-info → MindsDB-25.1.5.0.dist-info}/RECORD +98 -85
- {MindsDB-25.1.2.0.dist-info → MindsDB-25.1.5.0.dist-info}/WHEEL +1 -1
- mindsdb/__about__.py +1 -1
- mindsdb/__main__.py +5 -3
- mindsdb/api/executor/__init__.py +0 -1
- mindsdb/api/executor/command_executor.py +2 -1
- mindsdb/api/executor/data_types/answer.py +1 -1
- mindsdb/api/executor/datahub/datanodes/datanode.py +1 -1
- mindsdb/api/executor/datahub/datanodes/information_schema_datanode.py +1 -1
- mindsdb/api/executor/datahub/datanodes/integration_datanode.py +8 -3
- mindsdb/api/executor/datahub/datanodes/project_datanode.py +9 -26
- mindsdb/api/executor/sql_query/__init__.py +1 -0
- mindsdb/api/executor/sql_query/result_set.py +36 -21
- mindsdb/api/executor/sql_query/steps/apply_predictor_step.py +1 -1
- mindsdb/api/executor/sql_query/steps/join_step.py +4 -4
- mindsdb/api/executor/sql_query/steps/map_reduce_step.py +6 -39
- mindsdb/api/executor/utilities/sql.py +2 -10
- mindsdb/api/http/namespaces/agents.py +3 -1
- mindsdb/api/http/namespaces/knowledge_bases.py +3 -3
- mindsdb/api/http/namespaces/sql.py +3 -1
- mindsdb/api/mysql/mysql_proxy/executor/mysql_executor.py +2 -1
- mindsdb/api/mysql/mysql_proxy/mysql_proxy.py +7 -0
- mindsdb/api/postgres/postgres_proxy/executor/executor.py +2 -1
- mindsdb/integrations/handlers/chromadb_handler/chromadb_handler.py +2 -2
- mindsdb/integrations/handlers/chromadb_handler/requirements.txt +1 -1
- mindsdb/integrations/handlers/databricks_handler/requirements.txt +1 -1
- mindsdb/integrations/handlers/file_handler/file_handler.py +1 -1
- mindsdb/integrations/handlers/file_handler/requirements.txt +0 -4
- mindsdb/integrations/handlers/file_handler/tests/test_file_handler.py +17 -1
- mindsdb/integrations/handlers/jira_handler/jira_handler.py +15 -1
- mindsdb/integrations/handlers/jira_handler/jira_table.py +52 -31
- mindsdb/integrations/handlers/langchain_embedding_handler/fastapi_embeddings.py +82 -0
- mindsdb/integrations/handlers/langchain_embedding_handler/langchain_embedding_handler.py +8 -1
- mindsdb/integrations/handlers/langchain_handler/requirements.txt +1 -1
- mindsdb/integrations/handlers/ms_one_drive_handler/ms_one_drive_handler.py +1 -1
- mindsdb/integrations/handlers/ms_one_drive_handler/ms_one_drive_tables.py +8 -0
- mindsdb/integrations/handlers/pgvector_handler/pgvector_handler.py +49 -12
- mindsdb/integrations/handlers/pinecone_handler/pinecone_handler.py +123 -72
- mindsdb/integrations/handlers/pinecone_handler/requirements.txt +1 -1
- mindsdb/integrations/handlers/postgres_handler/postgres_handler.py +12 -6
- mindsdb/integrations/handlers/ray_serve_handler/ray_serve_handler.py +5 -3
- mindsdb/integrations/handlers/slack_handler/slack_handler.py +13 -2
- mindsdb/integrations/handlers/slack_handler/slack_tables.py +21 -1
- mindsdb/integrations/handlers/web_handler/requirements.txt +0 -1
- mindsdb/integrations/libs/ml_handler_process/learn_process.py +2 -2
- mindsdb/integrations/utilities/files/__init__.py +0 -0
- mindsdb/integrations/utilities/files/file_reader.py +258 -0
- mindsdb/integrations/utilities/handlers/api_utilities/microsoft/ms_graph_api_utilities.py +2 -1
- mindsdb/integrations/utilities/handlers/auth_utilities/microsoft/ms_graph_api_auth_utilities.py +8 -3
- mindsdb/integrations/utilities/rag/chains/map_reduce_summarizer_chain.py +5 -9
- mindsdb/integrations/utilities/rag/loaders/vector_store_loader/pgvector.py +76 -27
- mindsdb/integrations/utilities/rag/loaders/vector_store_loader/vector_store_loader.py +18 -1
- mindsdb/integrations/utilities/rag/pipelines/rag.py +84 -20
- mindsdb/integrations/utilities/rag/rag_pipeline_builder.py +16 -1
- mindsdb/integrations/utilities/rag/rerankers/reranker_compressor.py +166 -108
- mindsdb/integrations/utilities/rag/retrievers/__init__.py +3 -0
- mindsdb/integrations/utilities/rag/retrievers/multi_hop_retriever.py +85 -0
- mindsdb/integrations/utilities/rag/retrievers/retriever_factory.py +57 -0
- mindsdb/integrations/utilities/rag/retrievers/sql_retriever.py +117 -48
- mindsdb/integrations/utilities/rag/settings.py +190 -17
- mindsdb/integrations/utilities/sql_utils.py +1 -1
- mindsdb/interfaces/agents/agents_controller.py +18 -8
- mindsdb/interfaces/agents/constants.py +1 -0
- mindsdb/interfaces/agents/langchain_agent.py +124 -157
- mindsdb/interfaces/agents/langfuse_callback_handler.py +4 -37
- mindsdb/interfaces/agents/mindsdb_database_agent.py +21 -13
- mindsdb/interfaces/chatbot/chatbot_controller.py +7 -11
- mindsdb/interfaces/chatbot/chatbot_task.py +16 -5
- mindsdb/interfaces/chatbot/memory.py +58 -13
- mindsdb/interfaces/database/integrations.py +5 -1
- mindsdb/interfaces/database/projects.py +55 -16
- mindsdb/interfaces/database/views.py +12 -25
- mindsdb/interfaces/knowledge_base/controller.py +38 -9
- mindsdb/interfaces/knowledge_base/preprocessing/document_loader.py +7 -26
- mindsdb/interfaces/model/functions.py +15 -4
- mindsdb/interfaces/model/model_controller.py +4 -7
- mindsdb/interfaces/skills/custom/text2sql/mindsdb_sql_toolkit.py +51 -40
- mindsdb/interfaces/skills/retrieval_tool.py +10 -3
- mindsdb/interfaces/skills/skill_tool.py +97 -54
- mindsdb/interfaces/skills/skills_controller.py +7 -3
- mindsdb/interfaces/skills/sql_agent.py +127 -41
- mindsdb/interfaces/storage/db.py +1 -1
- mindsdb/migrations/versions/2025-01-15_c06c35f7e8e1_project_company.py +88 -0
- mindsdb/utilities/cache.py +7 -4
- mindsdb/utilities/context.py +11 -1
- mindsdb/utilities/langfuse.py +279 -0
- mindsdb/utilities/log.py +20 -2
- mindsdb/utilities/otel/__init__.py +206 -0
- mindsdb/utilities/otel/logger.py +25 -0
- mindsdb/utilities/otel/meter.py +19 -0
- mindsdb/utilities/otel/metric_handlers/__init__.py +25 -0
- mindsdb/utilities/otel/tracer.py +16 -0
- mindsdb/utilities/partitioning.py +52 -0
- mindsdb/utilities/render/sqlalchemy_render.py +7 -1
- mindsdb/utilities/utils.py +34 -0
- mindsdb/utilities/otel.py +0 -72
- {MindsDB-25.1.2.0.dist-info → MindsDB-25.1.5.0.dist-info}/LICENSE +0 -0
- {MindsDB-25.1.2.0.dist-info → MindsDB-25.1.5.0.dist-info}/top_level.txt +0 -0
|
@@ -1,17 +1,37 @@
|
|
|
1
1
|
import json
|
|
2
|
-
|
|
2
|
+
import re
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
from typing import Any, List, Optional
|
|
3
5
|
|
|
4
6
|
from langchain.chains.llm import LLMChain
|
|
5
7
|
from langchain_core.callbacks.manager import CallbackManagerForRetrieverRun
|
|
6
8
|
from langchain_core.documents.base import Document
|
|
7
9
|
from langchain_core.embeddings import Embeddings
|
|
10
|
+
from langchain_core.exceptions import OutputParserException
|
|
8
11
|
from langchain_core.language_models.chat_models import BaseChatModel
|
|
12
|
+
from langchain_core.output_parsers import PydanticOutputParser
|
|
9
13
|
from langchain_core.prompts import PromptTemplate
|
|
10
14
|
from langchain_core.retrievers import BaseRetriever
|
|
11
15
|
|
|
12
16
|
from mindsdb.api.executor.data_types.response_type import RESPONSE_TYPE
|
|
17
|
+
from mindsdb.integrations.libs.response import HandlerResponse
|
|
13
18
|
from mindsdb.integrations.libs.vectordatabase_handler import DistanceFunction, VectorStoreHandler
|
|
14
19
|
from mindsdb.integrations.utilities.rag.settings import LLMExample, MetadataSchema, SearchKwargs
|
|
20
|
+
from mindsdb.utilities import log
|
|
21
|
+
|
|
22
|
+
logger = log.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class MetadataFilter(BaseModel):
|
|
26
|
+
'''Represents an LLM generated metadata filter to apply to a PostgreSQL query.'''
|
|
27
|
+
attribute: str = Field(description="Database column to apply filter to")
|
|
28
|
+
comparator: str = Field(description="PostgreSQL comparator to use to filter database column")
|
|
29
|
+
value: Any = Field(description="Value to use to filter database column")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class MetadataFilters(BaseModel):
|
|
33
|
+
'''List of LLM generated metadata filters to apply to a PostgreSQL query.'''
|
|
34
|
+
filters: List[MetadataFilter] = Field(description="List of PostgreSQL metadata filters to apply for user query")
|
|
15
35
|
|
|
16
36
|
|
|
17
37
|
class SQLRetriever(BaseRetriever):
|
|
@@ -22,32 +42,34 @@ class SQLRetriever(BaseRetriever):
|
|
|
22
42
|
1. Use a LLM to rewrite the user input to something more suitable for retrieval. For example:
|
|
23
43
|
"Show me documents containing how to finetune a LLM please" --> "how to finetune a LLM"
|
|
24
44
|
|
|
25
|
-
2. Use a LLM to generate
|
|
26
|
-
metadata schemas & examples are used as additional context
|
|
45
|
+
2. Use a LLM to generate structured metadata filters based on the user input. Provided
|
|
46
|
+
metadata schemas & examples are used as additional context.
|
|
27
47
|
|
|
28
|
-
3.
|
|
48
|
+
3. Generate a prepared PostgreSQL query from the structured metadata filters.
|
|
29
49
|
|
|
30
50
|
4. Actually execute the query against our vector database to retrieve documents & return them.
|
|
31
51
|
'''
|
|
52
|
+
fallback_retriever: BaseRetriever
|
|
32
53
|
vector_store_handler: VectorStoreHandler
|
|
33
54
|
metadata_schemas: Optional[List[MetadataSchema]] = None
|
|
34
55
|
examples: Optional[List[LLMExample]] = None
|
|
35
56
|
|
|
36
|
-
embeddings_model: Embeddings
|
|
37
57
|
rewrite_prompt_template: str
|
|
38
|
-
|
|
39
|
-
|
|
58
|
+
metadata_filters_prompt_template: str
|
|
59
|
+
embeddings_model: Embeddings
|
|
60
|
+
num_retries: int
|
|
40
61
|
embeddings_table: str
|
|
41
62
|
source_table: str
|
|
63
|
+
source_id_column: str = 'Id'
|
|
42
64
|
distance_function: DistanceFunction
|
|
43
65
|
search_kwargs: SearchKwargs
|
|
44
66
|
|
|
45
67
|
llm: BaseChatModel
|
|
46
68
|
|
|
47
|
-
def
|
|
69
|
+
def _prepare_metadata_prompt(self) -> PromptTemplate:
|
|
48
70
|
base_prompt_template = PromptTemplate(
|
|
49
|
-
input_variables=['
|
|
50
|
-
template=self.
|
|
71
|
+
input_variables=['format_instructions', 'schema', 'examples', 'input', 'embeddings'],
|
|
72
|
+
template=self.metadata_filters_prompt_template
|
|
51
73
|
)
|
|
52
74
|
schema_prompt_str = ''
|
|
53
75
|
if self.metadata_schemas is not None:
|
|
@@ -61,7 +83,7 @@ class SQLRetriever(BaseRetriever):
|
|
|
61
83
|
if column.values is not None:
|
|
62
84
|
column_mapping[column.name]['values'] = column.values
|
|
63
85
|
column_mapping_json_str = json.dumps(column_mapping, indent=4)
|
|
64
|
-
schema_str = f'''{i+
|
|
86
|
+
schema_str = f'''{i+1}. {schema.table} - {schema.description}
|
|
65
87
|
|
|
66
88
|
Columns:
|
|
67
89
|
```json
|
|
@@ -80,7 +102,7 @@ Output:
|
|
|
80
102
|
{example.output}
|
|
81
103
|
|
|
82
104
|
'''
|
|
83
|
-
|
|
105
|
+
examples_prompt_str += example_str
|
|
84
106
|
return base_prompt_template.partial(
|
|
85
107
|
schema=schema_prompt_str,
|
|
86
108
|
examples=examples_prompt_str
|
|
@@ -94,51 +116,94 @@ Output:
|
|
|
94
116
|
rewrite_chain = LLMChain(llm=self.llm, prompt=rewrite_prompt)
|
|
95
117
|
return rewrite_chain.predict(input=query)
|
|
96
118
|
|
|
97
|
-
def _prepare_pgvector_query(self,
|
|
98
|
-
#
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
119
|
+
def _prepare_pgvector_query(self, metadata_filters: List[MetadataFilter]) -> str:
|
|
120
|
+
# Base select JOINed with document source table.
|
|
121
|
+
base_query = f'''SELECT * FROM {self.embeddings_table} AS e INNER JOIN {self.source_table} AS s ON (e.metadata->>'original_row_id')::int = s."{self.source_id_column}" '''
|
|
122
|
+
col_to_schema = {}
|
|
123
|
+
if not self.metadata_schemas:
|
|
124
|
+
return ''
|
|
125
|
+
for schema in self.metadata_schemas:
|
|
126
|
+
for col in schema.columns:
|
|
127
|
+
col_to_schema[col.name] = schema
|
|
128
|
+
joined_schemas = set()
|
|
129
|
+
for filter in metadata_filters:
|
|
130
|
+
# Join schemas before filtering.
|
|
131
|
+
schema = col_to_schema.get(filter.attribute)
|
|
132
|
+
if schema is None or schema.table in joined_schemas or schema.table == self.source_table:
|
|
133
|
+
continue
|
|
134
|
+
joined_schemas.add(schema.table)
|
|
135
|
+
base_query += schema.join + ' '
|
|
136
|
+
# Actually construct WHERE conditions from metadata filters.
|
|
137
|
+
if metadata_filters:
|
|
138
|
+
base_query += 'WHERE '
|
|
139
|
+
for i, filter in enumerate(metadata_filters):
|
|
140
|
+
value = filter.value
|
|
141
|
+
if isinstance(value, str):
|
|
142
|
+
value = f"'{value}'"
|
|
143
|
+
base_query += f'"{filter.attribute}" {filter.comparator} {value}'
|
|
144
|
+
if i < len(metadata_filters) - 1:
|
|
145
|
+
base_query += ' AND '
|
|
146
|
+
base_query += f" ORDER BY e.embeddings {self.distance_function.value[0]} '{{embeddings}}' LIMIT {self.search_kwargs.k};"
|
|
147
|
+
return base_query
|
|
148
|
+
|
|
149
|
+
def _generate_metadata_filters(self, query: str) -> List[MetadataFilter]:
|
|
150
|
+
parser = PydanticOutputParser(pydantic_object=MetadataFilters)
|
|
151
|
+
metadata_prompt = self._prepare_metadata_prompt()
|
|
152
|
+
metadata_filters_chain = LLMChain(llm=self.llm, prompt=metadata_prompt)
|
|
153
|
+
metadata_filters_output = metadata_filters_chain.predict(
|
|
154
|
+
format_instructions=parser.get_format_instructions(),
|
|
155
|
+
input=query
|
|
121
156
|
)
|
|
157
|
+
# If the LLM outputs raw JSON, use it as-is.
|
|
158
|
+
# If the LLM outputs anything including a json markdown section, use the last one.
|
|
159
|
+
json_markdown_output = re.findall(r'```json.*```', metadata_filters_output, re.DOTALL)
|
|
160
|
+
if json_markdown_output:
|
|
161
|
+
metadata_filters_output = json_markdown_output[-1]
|
|
162
|
+
# Clean the json tags.
|
|
163
|
+
metadata_filters_output = metadata_filters_output[7:]
|
|
164
|
+
metadata_filters_output = metadata_filters_output[:-3]
|
|
165
|
+
metadata_filters = parser.invoke(metadata_filters_output)
|
|
166
|
+
return metadata_filters.filters
|
|
167
|
+
|
|
168
|
+
def _prepare_and_execute_query(self, query: str, embeddings_str: str) -> HandlerResponse:
|
|
169
|
+
try:
|
|
170
|
+
metadata_filters = self._generate_metadata_filters(query)
|
|
171
|
+
checked_sql_query = self._prepare_pgvector_query(metadata_filters)
|
|
172
|
+
checked_sql_query_with_embeddings = checked_sql_query.format(embeddings=embeddings_str)
|
|
173
|
+
return self.vector_store_handler.native_query(checked_sql_query_with_embeddings)
|
|
174
|
+
except OutputParserException as e:
|
|
175
|
+
logger.warning(f'LLM failed to generate structured metadata filters: {str(e)}')
|
|
176
|
+
return HandlerResponse(RESPONSE_TYPE.ERROR, error_message=str(e))
|
|
177
|
+
except Exception as e:
|
|
178
|
+
logger.warning(f'Failed to prepare and execute SQL query from structured metadata: {str(e)}')
|
|
179
|
+
return HandlerResponse(RESPONSE_TYPE.ERROR, error_message=str(e))
|
|
122
180
|
|
|
123
181
|
def _get_relevant_documents(
|
|
124
182
|
self, query: str, *, run_manager: CallbackManagerForRetrieverRun
|
|
125
183
|
) -> List[Document]:
|
|
126
184
|
# Rewrite query to be suitable for retrieval.
|
|
127
185
|
retrieval_query = self._prepare_retrieval_query(query)
|
|
128
|
-
|
|
129
|
-
# Generate & check the query to be executed
|
|
130
|
-
checked_sql_query = self._prepare_pgvector_query(query, run_manager)
|
|
131
|
-
|
|
132
186
|
# Embed the rewritten retrieval query & include it in the similarity search pgvector query.
|
|
133
187
|
embedded_query = self.embeddings_model.embed_query(retrieval_query)
|
|
134
|
-
checked_sql_query_with_embeddings = checked_sql_query.format(embeddings=str(embedded_query))
|
|
135
|
-
# Handle LLM output that has the ```sql delimiter possibly.
|
|
136
|
-
checked_sql_query_with_embeddings = checked_sql_query_with_embeddings.replace('```sql', '')
|
|
137
|
-
checked_sql_query_with_embeddings = checked_sql_query_with_embeddings.replace('```', '')
|
|
138
188
|
# Actually execute the similarity search with metadata filters.
|
|
139
|
-
document_response = self.
|
|
140
|
-
|
|
141
|
-
|
|
189
|
+
document_response = self._prepare_and_execute_query(retrieval_query, str(embedded_query))
|
|
190
|
+
num_retries = 0
|
|
191
|
+
while num_retries < self.num_retries:
|
|
192
|
+
if document_response.resp_type != RESPONSE_TYPE.ERROR and len(document_response.data_frame) > 0:
|
|
193
|
+
# Successfully retrieved documents.
|
|
194
|
+
break
|
|
195
|
+
if document_response.resp_type == RESPONSE_TYPE.ERROR:
|
|
196
|
+
# LLMs won't always generate structured metadata so we should have a fallback after retrying.
|
|
197
|
+
logger.info(f'SQL Retriever query failed with error {document_response.error_message}')
|
|
198
|
+
elif len(document_response.data_frame) == 0:
|
|
199
|
+
logger.info('No documents retrieved from SQL Retriever query')
|
|
200
|
+
|
|
201
|
+
document_response = self._prepare_and_execute_query(retrieval_query, str(embedded_query))
|
|
202
|
+
num_retries += 1
|
|
203
|
+
if num_retries >= self.num_retries:
|
|
204
|
+
logger.info('Using fallback retriever in SQL retriever.')
|
|
205
|
+
return self.fallback_retriever._get_relevant_documents(retrieval_query, run_manager=run_manager)
|
|
206
|
+
|
|
142
207
|
document_df = document_response.data_frame
|
|
143
208
|
retrieved_documents = []
|
|
144
209
|
for _, document_row in document_df.iterrows():
|
|
@@ -146,4 +211,8 @@ Output:
|
|
|
146
211
|
document_row.get('content', ''),
|
|
147
212
|
metadata=document_row.get('metadata', {})
|
|
148
213
|
))
|
|
149
|
-
|
|
214
|
+
if retrieved_documents:
|
|
215
|
+
return retrieved_documents
|
|
216
|
+
# If the SQL query constructed did not return any documents, fallback.
|
|
217
|
+
logger.info('No documents returned from SQL retriever. using fallback retriever.')
|
|
218
|
+
return self.fallback_retriever._get_relevant_documents(retrieval_query, run_manager=run_manager)
|
|
@@ -3,7 +3,6 @@ from typing import List, Union, Any, Optional, Dict
|
|
|
3
3
|
|
|
4
4
|
from langchain_community.vectorstores.chroma import Chroma
|
|
5
5
|
from langchain_community.vectorstores.pgvector import PGVector
|
|
6
|
-
from langchain_community.tools.sql_database.prompt import QUERY_CHECKER as DEFAULT_QUERY_CHECKER_PROMPT_TEMPLATE
|
|
7
6
|
from langchain_core.documents import Document
|
|
8
7
|
from langchain_core.embeddings import Embeddings
|
|
9
8
|
from langchain_core.language_models import BaseChatModel
|
|
@@ -94,6 +93,25 @@ Output only a single better search query and nothing else like in the example.
|
|
|
94
93
|
Here is the user input: {input}
|
|
95
94
|
'''
|
|
96
95
|
|
|
96
|
+
DEFAULT_METADATA_FILTERS_PROMPT_TEMPLATE = '''Construct a list of PostgreSQL metadata filters to filter documents in the database based on the user input.
|
|
97
|
+
|
|
98
|
+
<< INSTRUCTIONS >>
|
|
99
|
+
{format_instructions}
|
|
100
|
+
|
|
101
|
+
RETURN ONLY THE FINAL JSON. DO NOT EXPLAIN, JUST RETURN THE FINAL JSON.
|
|
102
|
+
|
|
103
|
+
<< TABLES YOU HAVE ACCESS TO >>
|
|
104
|
+
|
|
105
|
+
{schema}
|
|
106
|
+
|
|
107
|
+
<< EXAMPLES >>
|
|
108
|
+
|
|
109
|
+
{examples}
|
|
110
|
+
|
|
111
|
+
Here is the user input:
|
|
112
|
+
{input}
|
|
113
|
+
'''
|
|
114
|
+
|
|
97
115
|
DEFAULT_SQL_PROMPT_TEMPLATE = '''
|
|
98
116
|
Construct a valid {dialect} SQL query to select documents relevant to the user input.
|
|
99
117
|
Source documents are found in the {source_table} table. You may need to join with other tables to get additional document metadata.
|
|
@@ -136,7 +154,6 @@ Columns:
|
|
|
136
154
|
"description": "Metadata for the document chunk. Always select metadata and always join with the {source_table} table on the string metadata field 'original_row_id'"
|
|
137
155
|
}}
|
|
138
156
|
}}
|
|
139
|
-
```
|
|
140
157
|
|
|
141
158
|
{schema}
|
|
142
159
|
|
|
@@ -150,11 +167,118 @@ Here is the user input:
|
|
|
150
167
|
{input}
|
|
151
168
|
'''
|
|
152
169
|
|
|
170
|
+
DEFAULT_QUESTION_REFORMULATION_TEMPLATE = """Given the original question and the retrieved context,
|
|
171
|
+
analyze what additional information is needed for a complete, accurate answer.
|
|
172
|
+
|
|
173
|
+
Original Question: {question}
|
|
174
|
+
|
|
175
|
+
Retrieved Context:
|
|
176
|
+
{context}
|
|
177
|
+
|
|
178
|
+
Analysis Instructions:
|
|
179
|
+
1. Evaluate Context Coverage:
|
|
180
|
+
- Identify key entities and concepts from the question
|
|
181
|
+
- Check for temporal information (dates, periods, sequences)
|
|
182
|
+
- Verify causal relationships are explained
|
|
183
|
+
- Confirm presence of requested quantitative data
|
|
184
|
+
- Assess if geographic or spatial context is sufficient
|
|
185
|
+
|
|
186
|
+
2. Quality Assessment:
|
|
187
|
+
If the retrieved context is:
|
|
188
|
+
- Irrelevant or tangential
|
|
189
|
+
- Too general or vague
|
|
190
|
+
- Potentially contradictory
|
|
191
|
+
- Missing key perspectives
|
|
192
|
+
- Lacking proper evidence
|
|
193
|
+
Generate questions to address these specific gaps.
|
|
194
|
+
|
|
195
|
+
3. Follow-up Question Requirements:
|
|
196
|
+
- Questions must directly contribute to answering the original query
|
|
197
|
+
- Break complex relationships into simpler, sequential steps
|
|
198
|
+
- Maintain specificity rather than broad inquiries
|
|
199
|
+
- Avoid questions answerable from existing context
|
|
200
|
+
- Ensure questions build on each other logically
|
|
201
|
+
- Limit questions to 150 characters each
|
|
202
|
+
- Each question must be self-contained
|
|
203
|
+
- Questions must end with a question mark
|
|
204
|
+
|
|
205
|
+
4. Response Format:
|
|
206
|
+
- Return a JSON array of strings
|
|
207
|
+
- Use square brackets and double quotes
|
|
208
|
+
- Questions must be unique (no duplicates)
|
|
209
|
+
- If context is sufficient, return empty array []
|
|
210
|
+
- Maximum 3 follow-up questions
|
|
211
|
+
- Minimum length per question: 30 characters
|
|
212
|
+
- No null values or empty strings
|
|
213
|
+
|
|
214
|
+
Example:
|
|
215
|
+
Original: "How did the development of antibiotics affect military casualties in WWII?"
|
|
216
|
+
|
|
217
|
+
Invalid responses:
|
|
218
|
+
{'questions': ['What are antibiotics?']} // Wrong format
|
|
219
|
+
['What is WWII?'] // Too basic
|
|
220
|
+
['How did it impact things?'] // Too vague
|
|
221
|
+
['', 'Question 2'] // Contains empty string
|
|
222
|
+
['Same question?', 'Same question?'] // Duplicate
|
|
223
|
+
|
|
224
|
+
Valid response:
|
|
225
|
+
["What were military casualty rates from infections before widespread antibiotic use in 1942?",
|
|
226
|
+
"How did penicillin availability change throughout different stages of WWII?",
|
|
227
|
+
"What were the primary battlefield infections treated with antibiotics during WWII?"]
|
|
228
|
+
|
|
229
|
+
or [] if context fully answers the original question.
|
|
230
|
+
|
|
231
|
+
Your task: Based on the analysis of the original question and context,
|
|
232
|
+
output ONLY a JSON array of follow-up questions needed to provide a complete answer.
|
|
233
|
+
If no additional information is needed, output an empty array [].
|
|
234
|
+
|
|
235
|
+
Follow-up Questions:"""
|
|
236
|
+
|
|
237
|
+
DEFAULT_QUERY_RETRY_PROMPT_TEMPLATE = '''
|
|
238
|
+
{query}
|
|
239
|
+
|
|
240
|
+
The {dialect} query above failed with the error message: {error}.
|
|
241
|
+
|
|
242
|
+
<< TABLES YOU HAVE ACCESS TO >>
|
|
243
|
+
1. {embeddings_table} - Contains document chunks, vector embeddings, and metadata for documents.
|
|
244
|
+
|
|
245
|
+
Columns:
|
|
246
|
+
```json
|
|
247
|
+
{{
|
|
248
|
+
"id": {{
|
|
249
|
+
"type": "string",
|
|
250
|
+
"description": "Unique ID for this document chunk"
|
|
251
|
+
}},
|
|
252
|
+
"content": {{
|
|
253
|
+
"type": "string",
|
|
254
|
+
"description": "A document chunk (subset of the original document)"
|
|
255
|
+
}},
|
|
256
|
+
"embeddings": {{
|
|
257
|
+
"type": "vector",
|
|
258
|
+
"description": "Vector embeddings for the document chunk."
|
|
259
|
+
}},
|
|
260
|
+
"metadata": {{
|
|
261
|
+
"type": "jsonb",
|
|
262
|
+
"description": "Metadata for the document chunk."
|
|
263
|
+
}}
|
|
264
|
+
}}
|
|
265
|
+
|
|
266
|
+
{schema}
|
|
267
|
+
|
|
268
|
+
Rewrite the query so it works.
|
|
269
|
+
|
|
270
|
+
Output the final SQL query only.
|
|
271
|
+
|
|
272
|
+
SQL Query:
|
|
273
|
+
'''
|
|
274
|
+
|
|
275
|
+
DEFAULT_NUM_QUERY_RETRIES = 2
|
|
276
|
+
|
|
153
277
|
|
|
154
278
|
class LLMConfig(BaseModel):
|
|
155
279
|
model_name: str = Field(default=DEFAULT_LLM_MODEL, description='LLM model to use for generation')
|
|
156
280
|
provider: str = Field(default=DEFAULT_LLM_MODEL_PROVIDER, description='LLM model provider to use for generation')
|
|
157
|
-
params: Dict[str, Any] =
|
|
281
|
+
params: Dict[str, Any] = Field(default_factory=dict)
|
|
158
282
|
|
|
159
283
|
|
|
160
284
|
class MultiVectorRetrieverMode(Enum):
|
|
@@ -183,17 +307,21 @@ class VectorStoreConfig(BaseModel):
|
|
|
183
307
|
collection_name: str = DEFAULT_COLLECTION_NAME
|
|
184
308
|
connection_string: str = None
|
|
185
309
|
kb_table: Any = None
|
|
310
|
+
is_sparse: bool = False
|
|
311
|
+
vector_size: Optional[int] = None
|
|
186
312
|
|
|
187
313
|
class Config:
|
|
188
314
|
arbitrary_types_allowed = True
|
|
189
315
|
extra = "forbid"
|
|
190
316
|
|
|
191
317
|
|
|
192
|
-
class RetrieverType(Enum):
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
318
|
+
class RetrieverType(str, Enum):
|
|
319
|
+
"""Retriever type for RAG pipeline"""
|
|
320
|
+
VECTOR_STORE = "vector_store"
|
|
321
|
+
AUTO = "auto"
|
|
322
|
+
MULTI = "multi"
|
|
323
|
+
SQL = "sql"
|
|
324
|
+
MULTI_HOP = "multi_hop"
|
|
197
325
|
|
|
198
326
|
|
|
199
327
|
class SearchType(Enum):
|
|
@@ -267,6 +395,13 @@ class MetadataSchema(BaseModel):
|
|
|
267
395
|
columns: List[ColumnSchema] = Field(
|
|
268
396
|
description="List of column schemas describing the metadata columns available for the table"
|
|
269
397
|
)
|
|
398
|
+
join: str = Field(
|
|
399
|
+
description="SQL join string to join this table with source documents table",
|
|
400
|
+
default=''
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
class Config:
|
|
404
|
+
frozen = True
|
|
270
405
|
|
|
271
406
|
|
|
272
407
|
class LLMExample(BaseModel):
|
|
@@ -283,15 +418,13 @@ class SQLRetrieverConfig(BaseModel):
|
|
|
283
418
|
default_factory=LLMConfig,
|
|
284
419
|
description="LLM configuration to use for generating the final SQL query for retrieval"
|
|
285
420
|
)
|
|
286
|
-
|
|
287
|
-
default=
|
|
288
|
-
description="
|
|
289
|
-
Has 'dialect', 'input', 'embeddings_table', 'source_table', 'embeddings', 'distance_function', 'schema', and 'examples' input variables.
|
|
290
|
-
"""
|
|
421
|
+
metadata_filters_prompt_template: str = Field(
|
|
422
|
+
default=DEFAULT_METADATA_FILTERS_PROMPT_TEMPLATE,
|
|
423
|
+
description="Prompt template to generate PostgreSQL metadata filters. Has 'format_instructions', 'schema', 'examples', and 'input' input variables"
|
|
291
424
|
)
|
|
292
|
-
|
|
293
|
-
default=
|
|
294
|
-
description="
|
|
425
|
+
num_retries: int = Field(
|
|
426
|
+
default=DEFAULT_NUM_QUERY_RETRIES,
|
|
427
|
+
description="How many times for an LLM to try rewriting a failed SQL query before using the fallback retriever."
|
|
295
428
|
)
|
|
296
429
|
rewrite_prompt_template: str = Field(
|
|
297
430
|
default=DEFAULT_SEMANTIC_PROMPT_TEMPLATE,
|
|
@@ -332,8 +465,34 @@ class SummarizationConfig(BaseModel):
|
|
|
332
465
|
class RerankerConfig(BaseModel):
|
|
333
466
|
model: str = DEFAULT_RERANKING_MODEL
|
|
334
467
|
base_url: str = DEFAULT_LLM_ENDPOINT
|
|
335
|
-
filtering_threshold: float = 0.
|
|
468
|
+
filtering_threshold: float = 0.5
|
|
336
469
|
num_docs_to_keep: Optional[int] = None
|
|
470
|
+
max_concurrent_requests: int = 20
|
|
471
|
+
max_retries: int = 3
|
|
472
|
+
retry_delay: float = 1.0
|
|
473
|
+
early_stop: bool = True # Whether to enable early stopping
|
|
474
|
+
early_stop_threshold: float = 0.8 # Confidence threshold for early stopping
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
class MultiHopRetrieverConfig(BaseModel):
|
|
478
|
+
"""Configuration for multi-hop retrieval"""
|
|
479
|
+
base_retriever_type: RetrieverType = Field(
|
|
480
|
+
default=RetrieverType.VECTOR_STORE,
|
|
481
|
+
description="Type of base retriever to use for multi-hop retrieval"
|
|
482
|
+
)
|
|
483
|
+
max_hops: int = Field(
|
|
484
|
+
default=3,
|
|
485
|
+
description="Maximum number of follow-up questions to generate",
|
|
486
|
+
ge=1
|
|
487
|
+
)
|
|
488
|
+
reformulation_template: str = Field(
|
|
489
|
+
default=DEFAULT_QUESTION_REFORMULATION_TEMPLATE,
|
|
490
|
+
description="Template for reformulating questions"
|
|
491
|
+
)
|
|
492
|
+
llm_config: LLMConfig = Field(
|
|
493
|
+
default_factory=LLMConfig,
|
|
494
|
+
description="LLM configuration to use for generating follow-up questions"
|
|
495
|
+
)
|
|
337
496
|
|
|
338
497
|
|
|
339
498
|
class RAGPipelineModel(BaseModel):
|
|
@@ -462,6 +621,20 @@ class RAGPipelineModel(BaseModel):
|
|
|
462
621
|
description="Reranker configuration"
|
|
463
622
|
)
|
|
464
623
|
|
|
624
|
+
multi_hop_config: Optional[MultiHopRetrieverConfig] = Field(
|
|
625
|
+
default=None,
|
|
626
|
+
description="Configuration for multi-hop retrieval. Required when retriever_type is MULTI_HOP."
|
|
627
|
+
)
|
|
628
|
+
|
|
629
|
+
@field_validator("multi_hop_config")
|
|
630
|
+
@classmethod
|
|
631
|
+
def validate_multi_hop_config(cls, v: Optional[MultiHopRetrieverConfig], info):
|
|
632
|
+
"""Validate that multi_hop_config is set when using multi-hop retrieval."""
|
|
633
|
+
values = info.data
|
|
634
|
+
if values.get("retriever_type") == RetrieverType.MULTI_HOP and v is None:
|
|
635
|
+
raise ValueError("multi_hop_config must be set when using multi-hop retrieval")
|
|
636
|
+
return v
|
|
637
|
+
|
|
465
638
|
class Config:
|
|
466
639
|
arbitrary_types_allowed = True
|
|
467
640
|
extra = "forbid"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import datetime
|
|
2
|
-
from typing import Dict, Iterator, List, Union, Tuple
|
|
2
|
+
from typing import Dict, Iterator, List, Union, Tuple, Optional
|
|
3
3
|
|
|
4
4
|
from langchain_core.tools import BaseTool
|
|
5
5
|
from sqlalchemy.orm.attributes import flag_modified
|
|
@@ -70,7 +70,7 @@ class AgentsController:
|
|
|
70
70
|
|
|
71
71
|
return model, provider
|
|
72
72
|
|
|
73
|
-
def get_agent(self, agent_name: str, project_name: str = 'mindsdb') -> db.Agents:
|
|
73
|
+
def get_agent(self, agent_name: str, project_name: str = 'mindsdb') -> Optional[db.Agents]:
|
|
74
74
|
'''
|
|
75
75
|
Gets an agent by name.
|
|
76
76
|
|
|
@@ -79,7 +79,7 @@ class AgentsController:
|
|
|
79
79
|
project_name (str): The name of the containing project - must exist
|
|
80
80
|
|
|
81
81
|
Returns:
|
|
82
|
-
agent (db.Agents): The database agent object
|
|
82
|
+
agent (Optional[db.Agents]): The database agent object
|
|
83
83
|
'''
|
|
84
84
|
|
|
85
85
|
project = self.project_controller.get(name=project_name)
|
|
@@ -252,6 +252,16 @@ class AgentsController:
|
|
|
252
252
|
existing_agent = self.get_agent(agent_name, project_name=project_name)
|
|
253
253
|
if existing_agent is None:
|
|
254
254
|
raise EntityNotExistsError(f'Agent with name not found: {agent_name}')
|
|
255
|
+
is_demo = (existing_agent.params or {}).get('is_demo', False)
|
|
256
|
+
if (
|
|
257
|
+
is_demo and (
|
|
258
|
+
(name is not None and name != agent_name)
|
|
259
|
+
or (model_name or provider)
|
|
260
|
+
or (len(skills_to_add) > 0 or len(skills_to_remove) > 0 or len(skills_to_rewrite) > 0)
|
|
261
|
+
or (isinstance(params, dict) and len(params) > 1 and 'prompt_template' not in params)
|
|
262
|
+
)
|
|
263
|
+
):
|
|
264
|
+
raise ValueError("It is forbidden to change properties of the demo object")
|
|
255
265
|
|
|
256
266
|
if name is not None and name != agent_name:
|
|
257
267
|
# Check to see if updated name already exists
|
|
@@ -352,6 +362,8 @@ class AgentsController:
|
|
|
352
362
|
agent = self.get_agent(agent_name, project_name)
|
|
353
363
|
if agent is None:
|
|
354
364
|
raise ValueError(f'Agent with name does not exist: {agent_name}')
|
|
365
|
+
if isinstance(agent.params, dict) and agent.params.get('is_demo') is True:
|
|
366
|
+
raise ValueError('Unable to delete demo object')
|
|
355
367
|
agent.deleted_at = datetime.datetime.now()
|
|
356
368
|
db.session.commit()
|
|
357
369
|
|
|
@@ -362,24 +374,22 @@ class AgentsController:
|
|
|
362
374
|
project_name: str = 'mindsdb',
|
|
363
375
|
tools: List[BaseTool] = None,
|
|
364
376
|
stream: bool = False) -> Union[Iterator[object], pd.DataFrame]:
|
|
365
|
-
|
|
377
|
+
"""
|
|
366
378
|
Queries an agent to get a completion.
|
|
367
379
|
|
|
368
380
|
Parameters:
|
|
369
381
|
agent (db.Agents): Existing agent to get completion from
|
|
370
382
|
messages (List[Dict[str, str]]): Chat history to send to the agent
|
|
371
|
-
trace_id (str): ID of Langfuse trace to use
|
|
372
|
-
observation_id (str): ID of parent Langfuse observation to use
|
|
373
383
|
project_name (str): Project the agent belongs to (default mindsdb)
|
|
374
384
|
tools (List[BaseTool]): Tools to use while getting the completion
|
|
375
|
-
stream (bool): Whether
|
|
385
|
+
stream (bool): Whether to stream the response
|
|
376
386
|
|
|
377
387
|
Returns:
|
|
378
388
|
response (Union[Iterator[object], pd.DataFrame]): Completion as a DataFrame or iterator of completion chunks
|
|
379
389
|
|
|
380
390
|
Raises:
|
|
381
391
|
ValueError: Agent's model does not exist.
|
|
382
|
-
|
|
392
|
+
"""
|
|
383
393
|
if stream:
|
|
384
394
|
return self._get_completion_stream(
|
|
385
395
|
agent,
|
|
@@ -165,6 +165,7 @@ PROVIDER_TO_MODELS = MappingProxyType(
|
|
|
165
165
|
|
|
166
166
|
ASSISTANT_COLUMN = "answer"
|
|
167
167
|
CONTEXT_COLUMN = "context"
|
|
168
|
+
TRACE_ID_COLUMN = "trace_id"
|
|
168
169
|
DEFAULT_AGENT_TIMEOUT_SECONDS = 300
|
|
169
170
|
# These should require no additional arguments.
|
|
170
171
|
DEFAULT_AGENT_TOOLS = []
|