hyperforge-nucliadb 1.0.0.post22__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.
@@ -0,0 +1,310 @@
1
+ from asyncio import gather
2
+ from copy import deepcopy
3
+ from time import time
4
+ from typing import Any, Dict, List
5
+
6
+ from hyperforge.manager import Manager
7
+ from hyperforge.memory.memory import QuestionMemory, Source
8
+ from nuclia_models.predict.run_agents import RunTextAgentsRequest
9
+ from nuclia_models.worker.triggers import Relation
10
+ from nucliadb_models import RelationNodeType
11
+ from nucliadb_models.search import Filter, KnowledgeGraphEntity
12
+
13
+ from hyperforge import PROMPT_ENVIRONMENT
14
+ from hyperforge_nucliadb.ask.config import AskAgentConfig
15
+ from hyperforge_nucliadb.ask.kb_analysis import get_knowledge_base_analysis
16
+ from hyperforge_nucliadb.ask.models import Analysis
17
+ from hyperforge_nucliadb.ask.prompt_analysis import configure_prompts
18
+ from hyperforge_nucliadb.ask.query_analysis import pre_query_analysis
19
+ from hyperforge_nucliadb.driver import NucliaDBDriver
20
+
21
+ KEYWORD_FILTER_PROMPT = "Extract if any the keywords that should appear on the retrieved results and its a must match, make sure that are keywords that are not common words, and that are not the same as the question or answer. Please define ONLY the keywords without any explanation. Only one or two words maximum. JUST A LIST OF KEYWORDS. DO NOT ADD ANY EXTRA NOTES AT THE END, JUST A LIST OF KEYWORDS"
22
+
23
+ REPHRASE_SEMANTIC_PROMPT = "Rephrase this question so its better for semantic retrieval, and keep the rephrased question in the same language as the original. Please define ONLY the question without any explanation. JUST A SENTENCE. DO NOT ADD ANY EXTRA NOTES AT THE END, JUST ONE SENTENCE"
24
+
25
+ REPHRASE_LEXICAL_PROMPT = "Rephrase this question so its better for lexical retrieval, and keep the rephrased question in the same language as the original. Please define ONLY the question without any explanation. JUST A SENTENCE. DO NOT ADD ANY EXTRA NOTES AT THE END, JUST ONE SENTENCE"
26
+
27
+
28
+ ASK_JSON_SCHEMA = {
29
+ "title": "ask_configuration",
30
+ "description": "Configuration extracted from reasoning engine",
31
+ "parameters": {
32
+ "type": "object",
33
+ "properties": {
34
+ "link": {
35
+ "type": "boolean",
36
+ "description": "The user wants link reference to the answer?",
37
+ },
38
+ "knowledge_scan": {
39
+ "type": "string",
40
+ "description": "If the query requires a knowledge aggregation or scan search to answer define the entities, labels and relations to query in the KB. Example queries: How many ...",
41
+ },
42
+ "semantic_query": {
43
+ "type": "string",
44
+ "description": REPHRASE_SEMANTIC_PROMPT,
45
+ },
46
+ "lexical_query": {"type": "string", "description": REPHRASE_LEXICAL_PROMPT},
47
+ "visual": {
48
+ "type": "boolean",
49
+ "description": "Is required an analysis of an image to answer this question, answer with false or true",
50
+ },
51
+ "keywords_filter": {
52
+ "type": "array",
53
+ "items": {"type": "string"},
54
+ "description": KEYWORD_FILTER_PROMPT,
55
+ },
56
+ "reason": {"type": "string"},
57
+ "entities": {
58
+ "type": "array",
59
+ "description": "Entities related to the user question to query in the KB",
60
+ "items": {
61
+ "type": "string",
62
+ },
63
+ },
64
+ "relations": {
65
+ "type": "array",
66
+ "description": "Relations related to the user question to query in the KB",
67
+ "items": {
68
+ "type": "string",
69
+ },
70
+ },
71
+ "pre_queries": {
72
+ "type": "array",
73
+ "items": {
74
+ "type": "string",
75
+ },
76
+ "description": "Pre queries to run before the main query to gather more information",
77
+ },
78
+ },
79
+ "required": [
80
+ "semantic_query",
81
+ "lexical_query",
82
+ "visual",
83
+ "keywords_filter",
84
+ "reason",
85
+ "pre_queries",
86
+ ],
87
+ },
88
+ }
89
+
90
+
91
+ ASK_PROMPT = """
92
+
93
+ Information about the KB:
94
+
95
+ # {{sid}}
96
+
97
+ description: {{sdescription}}
98
+
99
+ And given the question: {{question}}
100
+
101
+ ## labels: {{slabels}}
102
+
103
+ ## Facets:
104
+
105
+ {{facets}}
106
+
107
+ # Important rules to follow
108
+
109
+ {% for rule in rules %}
110
+ {{rule}}
111
+ {% endfor -%}
112
+
113
+
114
+ """
115
+
116
+ ASK_PROMPT_TEMPLATE = PROMPT_ENVIRONMENT.from_string(ASK_PROMPT)
117
+
118
+
119
+ async def question_analysis(
120
+ memory: QuestionMemory,
121
+ manager: Manager,
122
+ nucliadb_driver: NucliaDBDriver,
123
+ source: Source,
124
+ config: AskAgentConfig,
125
+ question: str,
126
+ step_title: str = "Knowledge Box Ask: Choose parameters",
127
+ ):
128
+ if question is None:
129
+ raise Exception("Question is None")
130
+ question = question.strip()
131
+
132
+ if source is None:
133
+ raise Exception("Source is None")
134
+
135
+ kb_analysis = get_knowledge_base_analysis(source)
136
+
137
+ query_analysis_result = pre_query_analysis(config, question, kb_analysis)
138
+
139
+ ask_json_schema_copy: Dict[str, Any] = deepcopy(ASK_JSON_SCHEMA)
140
+ configure_prompts(config, ask_json_schema_copy, kb_analysis, query_analysis_result)
141
+
142
+ facets = ""
143
+ facets += "## Content Types\n"
144
+ facets += "The following content types are available in the KB:\n\n"
145
+ for key, count in kb_analysis.content_types.items():
146
+ facets += f"- {key}: {count}\n"
147
+
148
+ facets += "The following languages are available in the KB:\n\n"
149
+ for key, count in kb_analysis.languages.items():
150
+ facets += f"- {key}: {count}\n"
151
+
152
+ prompt = ASK_PROMPT_TEMPLATE.render(
153
+ source=source,
154
+ rules=memory.get_rules(),
155
+ question=question,
156
+ facets=facets,
157
+ sid=source.id,
158
+ sdescription=source.description,
159
+ slabels=source.labels,
160
+ )
161
+
162
+ t0 = time()
163
+ input_tokens = 0.0
164
+ output_tokens = 0.0
165
+
166
+ task1 = manager.execute_json(
167
+ user_id="arag-ask",
168
+ prompt=prompt,
169
+ model=config.configuration_model,
170
+ schema=ask_json_schema_copy,
171
+ tracking=memory.get_tracking_info(),
172
+ )
173
+
174
+ task2 = nucliadb_driver.driver.run_text_agents(
175
+ kbid=nucliadb_driver.config.kbid,
176
+ content=RunTextAgentsRequest(user_id="arag-ask", texts=[question]),
177
+ )
178
+
179
+ task3 = manager.tokens_predict(
180
+ text=question,
181
+ model=kb_analysis.entity_model,
182
+ )
183
+
184
+ # Extract configuration from reasoning engine
185
+ (configuration_json, input, output), das_answer, tokens = await gather(
186
+ task1, task2, task3
187
+ )
188
+
189
+ input_tokens += input
190
+ output_tokens += output
191
+
192
+ entities_to_filter = deepcopy(config.query_entities)
193
+ relations_to_filter: List[Relation] = []
194
+ labels_to_filter: List[Filter] = []
195
+ if len(config.filters) > 0:
196
+ labels_to_filter.append(Filter(all=config.filters))
197
+ new_text_fields: List[str] = []
198
+ new_json_fields: Dict[str, Any] = {}
199
+
200
+ for detected_token in tokens.tokens:
201
+ entities_to_filter.append(
202
+ KnowledgeGraphEntity(
203
+ name=detected_token.text,
204
+ type=RelationNodeType.ENTITY,
205
+ subtype=detected_token.ner,
206
+ )
207
+ )
208
+
209
+ for result in das_answer.results:
210
+ input_tokens += result.input_nuclia_tokens
211
+ output_tokens += result.output_nuclia_tokens
212
+ for payload in result.payloads:
213
+ # we could implement for guards
214
+ if payload.entities is not None:
215
+ for entity in payload.entities:
216
+ for entity_class, entity_value in entity.labels.items():
217
+ entities_to_filter.append(
218
+ KnowledgeGraphEntity(
219
+ name=entity_value,
220
+ type=RelationNodeType.ENTITY,
221
+ subtype=entity_class,
222
+ )
223
+ )
224
+
225
+ if payload.relations is not None:
226
+ for relations in payload.relations:
227
+ relations_to_filter.extend(relations.relations)
228
+
229
+ if payload.labels is not None:
230
+ das_filters: list[str] = []
231
+ for label in payload.labels:
232
+ if isinstance(label.labels, list):
233
+ for label_obj_str in label.labels:
234
+ das_filters.append(label_obj_str)
235
+ elif isinstance(label.labels, dict):
236
+ for key, value in label.labels.items():
237
+ das_filters.append(f"{key}:{value}")
238
+ else:
239
+ raise Exception(f"Unknown label type: {label.labels}")
240
+ labels_to_filter.append(Filter(all=das_filters))
241
+
242
+ if payload.asks is not None:
243
+ for ask in payload.asks:
244
+ if ask.empty is False and ask.text is not None:
245
+ new_text_fields.append(ask.text)
246
+ elif ask.empty is False and ask.json_output is not None:
247
+ new_json_fields.update(ask.json_output)
248
+
249
+ for entity_llm in configuration_json.get("entities", []):
250
+ entities_to_filter.append(
251
+ KnowledgeGraphEntity(
252
+ name=entity_llm,
253
+ type=RelationNodeType.ENTITY,
254
+ )
255
+ )
256
+
257
+ if kb_analysis.default_semantic_model is None and (
258
+ kb_analysis.semantic_models is None or len(kb_analysis.semantic_models) == 0
259
+ ):
260
+ raise Exception("No semantic model found in the knowledge base")
261
+
262
+ semantic_model = (
263
+ kb_analysis.default_semantic_model
264
+ if kb_analysis.default_semantic_model
265
+ else kb_analysis.semantic_models[0]
266
+ )
267
+
268
+ semantic_query = configuration_json["semantic_query"]
269
+ lexical_query = configuration_json["lexical_query"]
270
+ visual = configuration_json.get("visual", False)
271
+ link = configuration_json.get("link", False)
272
+ knowledge_graph = configuration_json.get("knowledge_graph", None)
273
+ keywords_filter: List[str] = configuration_json.get("keywords_filter", [])
274
+ if isinstance(keywords_filter, str):
275
+ keywords_filter = [word.strip() for word in keywords_filter.split(",")]
276
+
277
+ reason = configuration_json.get("reason")
278
+ pre_queries = configuration_json.get("pre_queries", [])
279
+ relations = configuration_json.get("relations", [])
280
+ await memory.add_step(
281
+ step_module="ask",
282
+ step_title=step_title,
283
+ step_agent_path=f"/context/{config.id if config.id else 'default'}",
284
+ step_value=str(configuration_json),
285
+ step_reason=reason if reason is not None else "",
286
+ timeit=time() - t0,
287
+ input_nuclia_tokens=input_tokens,
288
+ output_nuclia_tokens=output_tokens,
289
+ )
290
+
291
+ return Analysis(
292
+ semantic_query=semantic_query,
293
+ lexical_query=lexical_query,
294
+ visual=visual,
295
+ keywords_filter=keywords_filter,
296
+ reason=reason,
297
+ input_tokens=input_tokens,
298
+ output_tokens=output_tokens,
299
+ labels=labels_to_filter,
300
+ semantic_model=semantic_model,
301
+ entities=entities_to_filter,
302
+ pre_queries=pre_queries,
303
+ relations=relations_to_filter,
304
+ new_text_fields=new_text_fields,
305
+ new_json_fields=new_json_fields,
306
+ semantic_config=kb_analysis.semantic_model_configs[semantic_model],
307
+ top_k=config.top_k,
308
+ link=link,
309
+ knowledge_graph=knowledge_graph,
310
+ )
@@ -0,0 +1,326 @@
1
+ import asyncio
2
+ from time import time
3
+ from typing import Any, ClassVar, Dict, List, Optional, cast
4
+
5
+ from hyperforge.agent import Agent
6
+ from hyperforge.configure import agent
7
+ from hyperforge.context.agent import ContextAgent, trace_agent
8
+ from hyperforge.definition import FunctionDefinition
9
+ from hyperforge.manager import Manager
10
+ from hyperforge.memory.memory import Context, QuestionMemory, Source
11
+
12
+ from hyperforge import logger
13
+ from hyperforge_nucliadb.ask.analysis import question_analysis
14
+ from hyperforge_nucliadb.ask.config import AskAgentConfig
15
+ from hyperforge_nucliadb.ask.hydrate import hydrate
16
+ from hyperforge_nucliadb.ask.knowledge_scan import knowledge_scan
17
+ from hyperforge_nucliadb.ask.models import Analysis
18
+ from hyperforge_nucliadb.ask.multi import choose_source
19
+ from hyperforge_nucliadb.ask.nucliadb import query_ndb, standard_query_ndb
20
+ from hyperforge_nucliadb.ask.rerank import rerank
21
+ from hyperforge_nucliadb.driver import NucliaDBDriver
22
+
23
+
24
+ @agent(
25
+ id="ask",
26
+ agent_type="context",
27
+ title="Knowledge Box Ask",
28
+ description="Ask a question to the knowledge box and retrieve relevant information",
29
+ config_schema=AskAgentConfig,
30
+ )
31
+ class AskAgent(ContextAgent, Agent[AskAgentConfig]):
32
+ __published_functions__: ClassVar[Dict[str, FunctionDefinition]] = {
33
+ "search_by_title": FunctionDefinition(
34
+ name="search_by_title",
35
+ description="Search for context in the Knowledge Box by title. Useful for specific queries where the title is known.",
36
+ parameters={
37
+ "title": {
38
+ "type": "string",
39
+ "description": "The title to search for in the Knowledge Box.",
40
+ },
41
+ "filters": {
42
+ "type": "array",
43
+ "items": {"type": "string"},
44
+ "description": "Filters to apply when searching in the Knowledge Box.",
45
+ },
46
+ },
47
+ ),
48
+ "ask_analysis_query": FunctionDefinition(
49
+ name="ask_analysis_query",
50
+ description="Generate filters to be used when querying the Knowledge Box. Useful to refine search results based on the question.",
51
+ parameters={
52
+ "question": {
53
+ "type": "string",
54
+ "description": "The question based on which to generate filters.",
55
+ },
56
+ "kbid": {
57
+ "type": "string",
58
+ "description": "The Knowledge Box ID to use for generating the analysis. If not provided, the default sources will be used.",
59
+ },
60
+ },
61
+ ),
62
+ "ask_agent": FunctionDefinition(
63
+ name="ask_agent",
64
+ description="Search for context in the Knowledge Box by title. Useful for specific queries where the title is known.",
65
+ parameters={
66
+ "question": {
67
+ "type": "string",
68
+ "description": "The question to search for in the Knowledge Box.",
69
+ },
70
+ },
71
+ ),
72
+ }
73
+
74
+ fallback: Optional[ContextAgent] = None
75
+
76
+ async def ask_analysis_query(
77
+ self,
78
+ memory: QuestionMemory,
79
+ manager: Manager,
80
+ question: str,
81
+ **kwargs: Any,
82
+ ) -> List[Analysis]:
83
+ sources = await self.get_source(question, memory, manager)
84
+ result = []
85
+ for source in sources:
86
+ nucliadb_driver: Optional[NucliaDBDriver] = cast(
87
+ NucliaDBDriver, manager.drivers.get(source.id)
88
+ )
89
+ if nucliadb_driver is None:
90
+ raise Exception("No NDB available")
91
+ analysis = await question_analysis(
92
+ memory,
93
+ manager,
94
+ nucliadb_driver,
95
+ source,
96
+ config=self.config,
97
+ question=question,
98
+ step_title=self.step_title("Choose parameters"),
99
+ ) # type: ignore
100
+ result.append(analysis)
101
+ return result
102
+
103
+ async def work(
104
+ self,
105
+ memory: QuestionMemory,
106
+ manager: Manager,
107
+ context: Context,
108
+ nucliadb_driver: NucliaDBDriver,
109
+ source: Source,
110
+ question: str,
111
+ ):
112
+ # Generate semantic questions required to answer
113
+ analysis: Optional[Analysis] = None
114
+ if self.config.ai_parameter_search:
115
+ try:
116
+ analysis = await question_analysis(
117
+ memory,
118
+ manager,
119
+ nucliadb_driver,
120
+ source,
121
+ config=self.config,
122
+ question=question,
123
+ step_title=self.step_title("Choose parameters"),
124
+ )
125
+ except Exception as e:
126
+ logger.exception("Error analyzing question")
127
+ raise e
128
+
129
+ try:
130
+ if analysis.knowledge_graph is not None:
131
+ search_result = await knowledge_scan(
132
+ memory,
133
+ manager,
134
+ analysis,
135
+ nucliadb_driver,
136
+ config=self.config,
137
+ )
138
+ else:
139
+ search_result = await query_ndb(
140
+ memory,
141
+ manager,
142
+ analysis,
143
+ nucliadb_driver,
144
+ config=self.config,
145
+ )
146
+
147
+ except Exception as e:
148
+ logger.exception("Error querying NDB")
149
+ raise e
150
+ else:
151
+ search_result = await standard_query_ndb(
152
+ memory,
153
+ manager,
154
+ nucliadb_driver,
155
+ config=self.config,
156
+ question=question,
157
+ )
158
+
159
+ await hydrate(
160
+ context,
161
+ search_result,
162
+ nucliadb_driver=nucliadb_driver,
163
+ vllm=self.config.vllm,
164
+ after=self.config.after,
165
+ before=self.config.before,
166
+ full_resource=self.config.full_resource,
167
+ visual=analysis.visual if analysis is not None else False,
168
+ link=analysis.link if analysis is not None else False,
169
+ module=self.config.module,
170
+ )
171
+ await rerank(context, manager=manager, top_k=analysis.top_k if analysis else 20)
172
+
173
+ async def get_source(
174
+ self, question: str, memory: QuestionMemory, manager: Manager
175
+ ) -> List[Source]:
176
+ if len(self.config.sources) == 0:
177
+ # Do we really want to do this without telling the user?
178
+ sources = manager.nucliadbs()
179
+ else:
180
+ sources = self.config.sources
181
+
182
+ # Only makes sense if we have more than one source
183
+ chosen_sources = await choose_source(
184
+ memory,
185
+ manager,
186
+ sources,
187
+ question,
188
+ ident=self.config.id if self.config.id else "default",
189
+ step_title=self.step_title("Choose sources"),
190
+ )
191
+ return chosen_sources
192
+
193
+ @trace_agent
194
+ async def get_question_context(
195
+ self,
196
+ memory: QuestionMemory,
197
+ manager: Manager,
198
+ question_uuid: str,
199
+ question: str,
200
+ flow_id: str,
201
+ extra_context: Optional[Dict[str, Any]] = None,
202
+ ):
203
+ if len(self.config.sources) == 0:
204
+ # Do we really want to do this without telling the user?
205
+ sources = manager.nucliadbs()
206
+ else:
207
+ sources = self.config.sources
208
+
209
+ # Only makes sense if we have more than one source
210
+ chosen_sources = await choose_source(
211
+ memory,
212
+ manager,
213
+ sources,
214
+ question,
215
+ ident=self.config.id if self.config.id else "default",
216
+ step_title=self.step_title("Choose sources"),
217
+ )
218
+
219
+ await asyncio.gather(
220
+ *[
221
+ self.rag(
222
+ memory, manager, source, question_uuid, question, flow_id=flow_id
223
+ )
224
+ for source in chosen_sources
225
+ ]
226
+ )
227
+
228
+ async def rag(
229
+ self,
230
+ memory: QuestionMemory,
231
+ manager: Manager,
232
+ source_obj: Source,
233
+ question_uuid: str,
234
+ question: str,
235
+ flow_id: str,
236
+ ):
237
+ source = source_obj.id
238
+ nucliadb_driver: Optional[NucliaDBDriver] = cast(
239
+ NucliaDBDriver, manager.drivers.get(source)
240
+ )
241
+ if nucliadb_driver is None:
242
+ raise Exception("No NDB available")
243
+
244
+ context = Context(
245
+ agent_id=self.config.id,
246
+ original_question_uuid=memory.original_question_uuid,
247
+ actual_question_uuid=question_uuid,
248
+ question=question,
249
+ source=source,
250
+ agent="ask",
251
+ title=self.config.title
252
+ if self.config.title
253
+ else f"Retrieval on {source} Knowledge Box",
254
+ )
255
+
256
+ await self.work(memory, manager, context, nucliadb_driver, source_obj, question)
257
+ ident = self.config.id if self.config.id else "default"
258
+ # If no chunks were found, we can skip the summarization
259
+ if context.chunks is not None and len(context.chunks) != 0:
260
+ # These missing questions are not used as of now
261
+ missing_question = await self.save_ctx_and_return_missing(
262
+ context=context,
263
+ question=question,
264
+ memory=memory,
265
+ manager=manager,
266
+ flow_id=flow_id,
267
+ )
268
+ else:
269
+ missing_question = (question_uuid, question)
270
+ await memory.add_step(
271
+ step_module=self.config.module,
272
+ step_title=self.step_title("NucliaDB context"),
273
+ step_reason="No context found",
274
+ step_value="Unsuccessful retrieval",
275
+ timeit=time() - time(),
276
+ step_agent_path=f"/context/{ident}",
277
+ )
278
+ # Query NDB
279
+ if missing_question is not None:
280
+ missing_uuid, missing = missing_question
281
+ # This is always failing back to another nucliadb call in case of missing information
282
+ # TODO: decide if we want to do this or not
283
+ # implicit fallback to another NDB call may introduce unnecessary delay
284
+ # for now I'm only adding it if we have no fallback agent configured
285
+ if self.config.fallback is None:
286
+ context = Context(
287
+ agent_id=self.config.id,
288
+ original_question_uuid=memory.original_question_uuid,
289
+ actual_question_uuid=missing_uuid,
290
+ question=missing,
291
+ source=source,
292
+ agent="ask",
293
+ title=self.config.title
294
+ if self.config.title
295
+ else f"Retrieval on {source} Knowledge Box",
296
+ )
297
+
298
+ await self.work(
299
+ memory,
300
+ manager,
301
+ context,
302
+ nucliadb_driver,
303
+ source_obj,
304
+ question,
305
+ )
306
+ new_missing_questions = await self.save_ctx_and_return_missing(
307
+ context=context,
308
+ question=missing,
309
+ memory=memory,
310
+ manager=manager,
311
+ flow_id=flow_id,
312
+ )
313
+ if new_missing_questions is not None:
314
+ # TODO: add proper structure to notify the user that the answer is missing
315
+ logger.warning(
316
+ f"Missing information after fallback: {[m[1] for m in new_missing_questions]}. No fallback agent configured."
317
+ )
318
+ elif self.fallback is not None:
319
+ # TODO: Add next agent
320
+ await self.fallback.get_question_context(
321
+ memory,
322
+ manager,
323
+ question=missing,
324
+ question_uuid=missing_uuid,
325
+ flow_id=flow_id,
326
+ )
@@ -0,0 +1,52 @@
1
+ from typing import List, Literal, Optional
2
+
3
+ from hyperforge.context.config import ContextAgentConfig
4
+ from hyperforge.utils import WidgetType
5
+ from nucliadb_models.search import KnowledgeGraphEntity
6
+ from pydantic import Field
7
+ from pydantic.config import ConfigDict
8
+
9
+
10
+ class AskAgentConfig(ContextAgentConfig):
11
+ model_config = ConfigDict(title="Knowledge Box Ask")
12
+ module: Literal["ask"] = "ask"
13
+ published_functions: Optional[tuple[str, ...]] = Field(
14
+ default=("search_by_title", "ask_analysis_query", "ask_agent"),
15
+ title="Published functions",
16
+ description="List of functions published by this agent to be used by other agents in the chain",
17
+ json_schema_extra={
18
+ "widget": WidgetType.NOT_SHOWN,
19
+ },
20
+ )
21
+ pre_queries: List[str] = Field(default_factory=list)
22
+ filters: List[str] = Field(default_factory=list)
23
+ security_groups: List[str] = Field(default_factory=list)
24
+ rephrase_semantic_custom_prompt: Optional[str] = None
25
+ rephrase_lexical_custom_prompt: Optional[str] = None
26
+ keywords_custom_prompt: Optional[str] = None
27
+ visual_enable_prompt: Optional[str] = None
28
+ date_range_enabled: bool = False
29
+ before: int = 2
30
+ after: int = 2
31
+ top_k: int = 20
32
+ extra_fields: List[str] = Field(default_factory=list)
33
+ full_resource: bool = False
34
+ vllm: bool = True
35
+ query_entities: List[KnowledgeGraphEntity] = Field(default_factory=list)
36
+ retrieve_related: Optional[str] = None
37
+ sources: List[str] = Field(
38
+ default_factory=list,
39
+ json_schema_extra={
40
+ "show_in_node": True,
41
+ },
42
+ )
43
+ configuration_model: str = Field(
44
+ default="gemini-2.5-flash",
45
+ title="Generative model",
46
+ description="Model used to generate the configuration",
47
+ json_schema_extra={"widget": WidgetType.MODEL_SELECT},
48
+ )
49
+ fast_answer: bool = True
50
+ # Setting this to True so that this PR will not break the current behavior
51
+ # of the agent
52
+ ai_parameter_search: bool = True