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.

Files changed (99) hide show
  1. {MindsDB-25.1.2.0.dist-info → MindsDB-25.1.5.0.dist-info}/METADATA +258 -255
  2. {MindsDB-25.1.2.0.dist-info → MindsDB-25.1.5.0.dist-info}/RECORD +98 -85
  3. {MindsDB-25.1.2.0.dist-info → MindsDB-25.1.5.0.dist-info}/WHEEL +1 -1
  4. mindsdb/__about__.py +1 -1
  5. mindsdb/__main__.py +5 -3
  6. mindsdb/api/executor/__init__.py +0 -1
  7. mindsdb/api/executor/command_executor.py +2 -1
  8. mindsdb/api/executor/data_types/answer.py +1 -1
  9. mindsdb/api/executor/datahub/datanodes/datanode.py +1 -1
  10. mindsdb/api/executor/datahub/datanodes/information_schema_datanode.py +1 -1
  11. mindsdb/api/executor/datahub/datanodes/integration_datanode.py +8 -3
  12. mindsdb/api/executor/datahub/datanodes/project_datanode.py +9 -26
  13. mindsdb/api/executor/sql_query/__init__.py +1 -0
  14. mindsdb/api/executor/sql_query/result_set.py +36 -21
  15. mindsdb/api/executor/sql_query/steps/apply_predictor_step.py +1 -1
  16. mindsdb/api/executor/sql_query/steps/join_step.py +4 -4
  17. mindsdb/api/executor/sql_query/steps/map_reduce_step.py +6 -39
  18. mindsdb/api/executor/utilities/sql.py +2 -10
  19. mindsdb/api/http/namespaces/agents.py +3 -1
  20. mindsdb/api/http/namespaces/knowledge_bases.py +3 -3
  21. mindsdb/api/http/namespaces/sql.py +3 -1
  22. mindsdb/api/mysql/mysql_proxy/executor/mysql_executor.py +2 -1
  23. mindsdb/api/mysql/mysql_proxy/mysql_proxy.py +7 -0
  24. mindsdb/api/postgres/postgres_proxy/executor/executor.py +2 -1
  25. mindsdb/integrations/handlers/chromadb_handler/chromadb_handler.py +2 -2
  26. mindsdb/integrations/handlers/chromadb_handler/requirements.txt +1 -1
  27. mindsdb/integrations/handlers/databricks_handler/requirements.txt +1 -1
  28. mindsdb/integrations/handlers/file_handler/file_handler.py +1 -1
  29. mindsdb/integrations/handlers/file_handler/requirements.txt +0 -4
  30. mindsdb/integrations/handlers/file_handler/tests/test_file_handler.py +17 -1
  31. mindsdb/integrations/handlers/jira_handler/jira_handler.py +15 -1
  32. mindsdb/integrations/handlers/jira_handler/jira_table.py +52 -31
  33. mindsdb/integrations/handlers/langchain_embedding_handler/fastapi_embeddings.py +82 -0
  34. mindsdb/integrations/handlers/langchain_embedding_handler/langchain_embedding_handler.py +8 -1
  35. mindsdb/integrations/handlers/langchain_handler/requirements.txt +1 -1
  36. mindsdb/integrations/handlers/ms_one_drive_handler/ms_one_drive_handler.py +1 -1
  37. mindsdb/integrations/handlers/ms_one_drive_handler/ms_one_drive_tables.py +8 -0
  38. mindsdb/integrations/handlers/pgvector_handler/pgvector_handler.py +49 -12
  39. mindsdb/integrations/handlers/pinecone_handler/pinecone_handler.py +123 -72
  40. mindsdb/integrations/handlers/pinecone_handler/requirements.txt +1 -1
  41. mindsdb/integrations/handlers/postgres_handler/postgres_handler.py +12 -6
  42. mindsdb/integrations/handlers/ray_serve_handler/ray_serve_handler.py +5 -3
  43. mindsdb/integrations/handlers/slack_handler/slack_handler.py +13 -2
  44. mindsdb/integrations/handlers/slack_handler/slack_tables.py +21 -1
  45. mindsdb/integrations/handlers/web_handler/requirements.txt +0 -1
  46. mindsdb/integrations/libs/ml_handler_process/learn_process.py +2 -2
  47. mindsdb/integrations/utilities/files/__init__.py +0 -0
  48. mindsdb/integrations/utilities/files/file_reader.py +258 -0
  49. mindsdb/integrations/utilities/handlers/api_utilities/microsoft/ms_graph_api_utilities.py +2 -1
  50. mindsdb/integrations/utilities/handlers/auth_utilities/microsoft/ms_graph_api_auth_utilities.py +8 -3
  51. mindsdb/integrations/utilities/rag/chains/map_reduce_summarizer_chain.py +5 -9
  52. mindsdb/integrations/utilities/rag/loaders/vector_store_loader/pgvector.py +76 -27
  53. mindsdb/integrations/utilities/rag/loaders/vector_store_loader/vector_store_loader.py +18 -1
  54. mindsdb/integrations/utilities/rag/pipelines/rag.py +84 -20
  55. mindsdb/integrations/utilities/rag/rag_pipeline_builder.py +16 -1
  56. mindsdb/integrations/utilities/rag/rerankers/reranker_compressor.py +166 -108
  57. mindsdb/integrations/utilities/rag/retrievers/__init__.py +3 -0
  58. mindsdb/integrations/utilities/rag/retrievers/multi_hop_retriever.py +85 -0
  59. mindsdb/integrations/utilities/rag/retrievers/retriever_factory.py +57 -0
  60. mindsdb/integrations/utilities/rag/retrievers/sql_retriever.py +117 -48
  61. mindsdb/integrations/utilities/rag/settings.py +190 -17
  62. mindsdb/integrations/utilities/sql_utils.py +1 -1
  63. mindsdb/interfaces/agents/agents_controller.py +18 -8
  64. mindsdb/interfaces/agents/constants.py +1 -0
  65. mindsdb/interfaces/agents/langchain_agent.py +124 -157
  66. mindsdb/interfaces/agents/langfuse_callback_handler.py +4 -37
  67. mindsdb/interfaces/agents/mindsdb_database_agent.py +21 -13
  68. mindsdb/interfaces/chatbot/chatbot_controller.py +7 -11
  69. mindsdb/interfaces/chatbot/chatbot_task.py +16 -5
  70. mindsdb/interfaces/chatbot/memory.py +58 -13
  71. mindsdb/interfaces/database/integrations.py +5 -1
  72. mindsdb/interfaces/database/projects.py +55 -16
  73. mindsdb/interfaces/database/views.py +12 -25
  74. mindsdb/interfaces/knowledge_base/controller.py +38 -9
  75. mindsdb/interfaces/knowledge_base/preprocessing/document_loader.py +7 -26
  76. mindsdb/interfaces/model/functions.py +15 -4
  77. mindsdb/interfaces/model/model_controller.py +4 -7
  78. mindsdb/interfaces/skills/custom/text2sql/mindsdb_sql_toolkit.py +51 -40
  79. mindsdb/interfaces/skills/retrieval_tool.py +10 -3
  80. mindsdb/interfaces/skills/skill_tool.py +97 -54
  81. mindsdb/interfaces/skills/skills_controller.py +7 -3
  82. mindsdb/interfaces/skills/sql_agent.py +127 -41
  83. mindsdb/interfaces/storage/db.py +1 -1
  84. mindsdb/migrations/versions/2025-01-15_c06c35f7e8e1_project_company.py +88 -0
  85. mindsdb/utilities/cache.py +7 -4
  86. mindsdb/utilities/context.py +11 -1
  87. mindsdb/utilities/langfuse.py +279 -0
  88. mindsdb/utilities/log.py +20 -2
  89. mindsdb/utilities/otel/__init__.py +206 -0
  90. mindsdb/utilities/otel/logger.py +25 -0
  91. mindsdb/utilities/otel/meter.py +19 -0
  92. mindsdb/utilities/otel/metric_handlers/__init__.py +25 -0
  93. mindsdb/utilities/otel/tracer.py +16 -0
  94. mindsdb/utilities/partitioning.py +52 -0
  95. mindsdb/utilities/render/sqlalchemy_render.py +7 -1
  96. mindsdb/utilities/utils.py +34 -0
  97. mindsdb/utilities/otel.py +0 -72
  98. {MindsDB-25.1.2.0.dist-info → MindsDB-25.1.5.0.dist-info}/LICENSE +0 -0
  99. {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
- from typing import List, Optional
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 a pgvector query with metadata filters based on the user input. Provided
26
- metadata schemas & examples are used as additional context to generate the query.
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. Use a LLM to double check the generated pgvector query is correct.
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
- sql_prompt_template: str
39
- query_checker_template: str
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 _prepare_sql_prompt(self) -> PromptTemplate:
69
+ def _prepare_metadata_prompt(self) -> PromptTemplate:
48
70
  base_prompt_template = PromptTemplate(
49
- input_variables=['dialect', 'input', 'embeddings_table', 'source_table', 'embeddings', 'distance_function', 'schema', 'examples'],
50
- template=self.sql_prompt_template
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+2}. {schema.table} - {schema.description}
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
- examples_prompt_str += example_str
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, query: str, run_manager: CallbackManagerForRetrieverRun) -> str:
98
- # Incorporate metadata schemas & examples into prompt.
99
- sql_prompt = self._prepare_sql_prompt()
100
- sql_chain = LLMChain(llm=self.llm, prompt=sql_prompt)
101
- # Generate the initial pgvector query.
102
- sql_query = sql_chain.predict(
103
- # Only pgvector & similarity search is supported.
104
- dialect='postgres',
105
- input=query,
106
- embeddings_table=self.embeddings_table,
107
- source_table=self.source_table,
108
- distance_function=self.distance_function.value[0],
109
- k=self.search_kwargs.k,
110
- callbacks=run_manager.get_child() if run_manager else None
111
- )
112
- query_checker_prompt = PromptTemplate(
113
- input_variables=['dialect', 'query'],
114
- template=self.query_checker_template
115
- )
116
- query_checker_chain = LLMChain(llm=self.llm, prompt=query_checker_prompt)
117
- # Check the query & return the final result to be executed.
118
- return query_checker_chain.predict(
119
- dialect='postgres',
120
- query=sql_query
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.vector_store_handler.native_query(checked_sql_query_with_embeddings)
140
- if document_response.resp_type == RESPONSE_TYPE.ERROR:
141
- raise ValueError(f'Retrieving documents failed with error {document_response.error_message}')
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
- return retrieved_documents
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
- VECTOR_STORE = 'vector_store'
194
- AUTO = 'auto'
195
- MULTI = 'multi'
196
- SQL = 'sql'
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
- sql_prompt_template: str = Field(
287
- default=DEFAULT_SQL_PROMPT_TEMPLATE,
288
- description="""Prompt template to generate the SQL query to execute against the vector database. Currently only pgvector is supported.
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
- query_checker_template: str = Field(
293
- default=DEFAULT_QUERY_CHECKER_PROMPT_TEMPLATE,
294
- description="Prompt template to use for double checking SQL queries before execution. Has 'query' and 'dialect' input variables."
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.99
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"
@@ -178,7 +178,7 @@ def project_dataframe(df, targets, table_columns):
178
178
 
179
179
  # adapt column names to projection
180
180
  if len(df_col_rename) > 0:
181
- df = df.rename(columns=df_col_rename)
181
+ df.rename(columns=df_col_rename, inplace=True)
182
182
  return df
183
183
 
184
184
 
@@ -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 or not to stream the response
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 = []