hyperforge-nucliadb-agentic 1.0.0.post96__tar.gz → 1.0.0.post108__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. {hyperforge_nucliadb_agentic-1.0.0.post96 → hyperforge_nucliadb_agentic-1.0.0.post108}/PKG-INFO +1 -1
  2. {hyperforge_nucliadb_agentic-1.0.0.post96 → hyperforge_nucliadb_agentic-1.0.0.post108}/pyproject.toml +1 -1
  3. {hyperforge_nucliadb_agentic-1.0.0.post96 → hyperforge_nucliadb_agentic-1.0.0.post108}/pyproject.toml.orig +1 -1
  4. {hyperforge_nucliadb_agentic-1.0.0.post96 → hyperforge_nucliadb_agentic-1.0.0.post108}/src/hyperforge_nucliadb_agentic/agent.py +86 -35
  5. {hyperforge_nucliadb_agentic-1.0.0.post96 → hyperforge_nucliadb_agentic-1.0.0.post108}/src/hyperforge_nucliadb_agentic/ask/audit.py +7 -2
  6. {hyperforge_nucliadb_agentic-1.0.0.post96 → hyperforge_nucliadb_agentic-1.0.0.post108}/src/hyperforge_nucliadb_agentic/ask/search/rpc.py +32 -2
  7. {hyperforge_nucliadb_agentic-1.0.0.post96 → hyperforge_nucliadb_agentic-1.0.0.post108}/src/hyperforge_nucliadb_agentic/config.py +4 -0
  8. {hyperforge_nucliadb_agentic-1.0.0.post96 → hyperforge_nucliadb_agentic-1.0.0.post108}/README.md +0 -0
  9. {hyperforge_nucliadb_agentic-1.0.0.post96 → hyperforge_nucliadb_agentic-1.0.0.post108}/src/hyperforge_nucliadb_agentic/__init__.py +0 -0
  10. {hyperforge_nucliadb_agentic-1.0.0.post96 → hyperforge_nucliadb_agentic-1.0.0.post108}/src/hyperforge_nucliadb_agentic/ask/__init__.py +0 -0
  11. {hyperforge_nucliadb_agentic-1.0.0.post96 → hyperforge_nucliadb_agentic-1.0.0.post108}/src/hyperforge_nucliadb_agentic/ask/exceptions.py +0 -0
  12. {hyperforge_nucliadb_agentic-1.0.0.post96 → hyperforge_nucliadb_agentic-1.0.0.post108}/src/hyperforge_nucliadb_agentic/ask/lifespan.py +0 -0
  13. {hyperforge_nucliadb_agentic-1.0.0.post96 → hyperforge_nucliadb_agentic-1.0.0.post108}/src/hyperforge_nucliadb_agentic/ask/model.py +0 -0
  14. {hyperforge_nucliadb_agentic-1.0.0.post96 → hyperforge_nucliadb_agentic-1.0.0.post108}/src/hyperforge_nucliadb_agentic/ask/predict.py +0 -0
  15. {hyperforge_nucliadb_agentic-1.0.0.post96 → hyperforge_nucliadb_agentic-1.0.0.post108}/src/hyperforge_nucliadb_agentic/ask/predict_models.py +0 -0
  16. {hyperforge_nucliadb_agentic-1.0.0.post96 → hyperforge_nucliadb_agentic-1.0.0.post108}/src/hyperforge_nucliadb_agentic/ask/search/__init__.py +0 -0
  17. {hyperforge_nucliadb_agentic-1.0.0.post96 → hyperforge_nucliadb_agentic-1.0.0.post108}/src/hyperforge_nucliadb_agentic/ask/search/ask.py +0 -0
  18. {hyperforge_nucliadb_agentic-1.0.0.post96 → hyperforge_nucliadb_agentic-1.0.0.post108}/src/hyperforge_nucliadb_agentic/ask/search/graph_strategy.py +0 -0
  19. {hyperforge_nucliadb_agentic-1.0.0.post96 → hyperforge_nucliadb_agentic-1.0.0.post108}/src/hyperforge_nucliadb_agentic/ask/search/highlight.py +0 -0
  20. {hyperforge_nucliadb_agentic-1.0.0.post96 → hyperforge_nucliadb_agentic-1.0.0.post108}/src/hyperforge_nucliadb_agentic/ask/search/hydrator.py +0 -0
  21. {hyperforge_nucliadb_agentic-1.0.0.post96 → hyperforge_nucliadb_agentic-1.0.0.post108}/src/hyperforge_nucliadb_agentic/ask/search/metrics.py +0 -0
  22. {hyperforge_nucliadb_agentic-1.0.0.post96 → hyperforge_nucliadb_agentic-1.0.0.post108}/src/hyperforge_nucliadb_agentic/ask/search/parsers/__init__.py +0 -0
  23. {hyperforge_nucliadb_agentic-1.0.0.post96 → hyperforge_nucliadb_agentic-1.0.0.post108}/src/hyperforge_nucliadb_agentic/ask/search/parsers/ask.py +0 -0
  24. {hyperforge_nucliadb_agentic-1.0.0.post96 → hyperforge_nucliadb_agentic-1.0.0.post108}/src/hyperforge_nucliadb_agentic/ask/search/parsers/fetcher.py +0 -0
  25. {hyperforge_nucliadb_agentic-1.0.0.post96 → hyperforge_nucliadb_agentic-1.0.0.post108}/src/hyperforge_nucliadb_agentic/ask/search/parsers/find.py +0 -0
  26. {hyperforge_nucliadb_agentic-1.0.0.post96 → hyperforge_nucliadb_agentic-1.0.0.post108}/src/hyperforge_nucliadb_agentic/ask/search/prompt.py +0 -0
  27. {hyperforge_nucliadb_agentic-1.0.0.post96 → hyperforge_nucliadb_agentic-1.0.0.post108}/src/hyperforge_nucliadb_agentic/ask/search/rank_fusion.py +0 -0
  28. {hyperforge_nucliadb_agentic-1.0.0.post96 → hyperforge_nucliadb_agentic-1.0.0.post108}/src/hyperforge_nucliadb_agentic/ask/search/rerankers.py +0 -0
  29. {hyperforge_nucliadb_agentic-1.0.0.post96 → hyperforge_nucliadb_agentic-1.0.0.post108}/src/hyperforge_nucliadb_agentic/ask/search/retrieval.py +0 -0
  30. {hyperforge_nucliadb_agentic-1.0.0.post96 → hyperforge_nucliadb_agentic-1.0.0.post108}/src/hyperforge_nucliadb_agentic/ask/settings.py +0 -0
  31. {hyperforge_nucliadb_agentic-1.0.0.post96 → hyperforge_nucliadb_agentic-1.0.0.post108}/src/hyperforge_nucliadb_agentic/ask/utils/ids.py +0 -0
  32. {hyperforge_nucliadb_agentic-1.0.0.post96 → hyperforge_nucliadb_agentic-1.0.0.post108}/src/hyperforge_nucliadb_agentic/ask/utils/proto.py +0 -0
  33. {hyperforge_nucliadb_agentic-1.0.0.post96 → hyperforge_nucliadb_agentic-1.0.0.post108}/src/hyperforge_nucliadb_agentic/ask/utils/responses.py +0 -0
  34. {hyperforge_nucliadb_agentic-1.0.0.post96 → hyperforge_nucliadb_agentic-1.0.0.post108}/src/hyperforge_nucliadb_agentic/ask/utils/text_blocks.py +0 -0
  35. {hyperforge_nucliadb_agentic-1.0.0.post96 → hyperforge_nucliadb_agentic-1.0.0.post108}/src/hyperforge_nucliadb_agentic/internal_driver.py +0 -0
  36. {hyperforge_nucliadb_agentic-1.0.0.post96 → hyperforge_nucliadb_agentic-1.0.0.post108}/src/hyperforge_nucliadb_agentic/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hyperforge_nucliadb_agentic
3
- Version: 1.0.0.post96
3
+ Version: 1.0.0.post108
4
4
  Summary: NucliaDB Hyperforge agent
5
5
  Author: Nuclia
6
6
  Author-email: Nuclia <nucliadb@nuclia.com>
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "hyperforge_nucliadb_agentic"
7
- version = "1.0.0.post96"
7
+ version = "1.0.0.post108"
8
8
  license = "Apache-2.0"
9
9
  description = "NucliaDB Hyperforge agent"
10
10
  readme = "README.md"
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "hyperforge_nucliadb_agentic"
7
- version = "1.0.0.post96"
7
+ version = "1.0.0.post108"
8
8
  license = "Apache-2.0"
9
9
  description = "NucliaDB Hyperforge agent"
10
10
  authors = [{ name = "Nuclia", email = "nucliadb@nuclia.com" }]
@@ -55,9 +55,31 @@ from hyperforge_nucliadb_agentic.ask.model import (
55
55
  RagStrategies,
56
56
  SyncAskResponse,
57
57
  )
58
+ from hyperforge_nucliadb_agentic.ask.search import rpc
58
59
  from hyperforge_nucliadb_agentic.ask.search.ask import ask
59
60
  from hyperforge_nucliadb_agentic.config import NucliaDBAgentConfig
60
61
 
62
+
63
+ async def choose_sources(
64
+ memory: QuestionMemory,
65
+ manager: Manager,
66
+ sources: List[str],
67
+ question: str,
68
+ ident: str,
69
+ step_title: str,
70
+ ) -> list[Source]:
71
+ if len(sources) == 1:
72
+ return [Source.model_construct(id=sources[0])]
73
+ return await choose_source(
74
+ memory,
75
+ manager,
76
+ sources,
77
+ question,
78
+ ident=ident,
79
+ step_title=step_title,
80
+ )
81
+
82
+
61
83
  # Example filter expressions for catalog search
62
84
  EXAMPLE_FILTER_EXP1 = [
63
85
  {
@@ -746,7 +768,7 @@ class NucliaDBAgent(ContextAgent, Agent[NucliaDBAgentConfig]):
746
768
  or_filters = [f"/l/{labelset}/{lv}" for lv in label_values]
747
769
 
748
770
  # Choose sources based on the question/s and the in
749
- chosen_sources = await choose_source(
771
+ chosen_sources = await choose_sources(
750
772
  memory,
751
773
  manager,
752
774
  sources,
@@ -783,7 +805,7 @@ class NucliaDBAgent(ContextAgent, Agent[NucliaDBAgentConfig]):
783
805
  ) -> List[tuple[str, str]]:
784
806
  sources = self.config.sources
785
807
  # Choose sources based on the question/s and the in
786
- chosen_sources = await choose_source(
808
+ chosen_sources = await choose_sources(
787
809
  memory,
788
810
  manager,
789
811
  sources,
@@ -949,6 +971,57 @@ class NucliaDBAgent(ContextAgent, Agent[NucliaDBAgentConfig]):
949
971
  else:
950
972
  return combine_catalog_filter_expressions(to_combine, operator="and")
951
973
 
974
+ async def prepare_ask_request(
975
+ self,
976
+ nucliadb_driver: NucliaDBDriver,
977
+ question: str,
978
+ ask_request_json: str | None,
979
+ rag_strategies: list[RagStrategies],
980
+ ) -> AskRequest:
981
+ """Apply agent defaults, search config, public ASK overrides, and runtime query."""
982
+ # Load explicit options from the public ASK request.
983
+ ask_request: AskRequest | None = None
984
+ if ask_request_json:
985
+ try:
986
+ ask_request = AskRequest.model_validate_json(ask_request_json)
987
+ except ValidationError as e:
988
+ logger.error(
989
+ f"Failed to validate AskRequest received as memory argument: {e}"
990
+ )
991
+
992
+ # The SmartAgent query always wins.
993
+ if ask_request is None:
994
+ ask_request = AskRequest(query=question)
995
+ else:
996
+ ask_request = ask_request.model_copy(update={"query": question})
997
+
998
+ # Search config fills fields not explicitly set by the public ASK.
999
+ if self.config.search_config is not None:
1000
+ ask_request = ask_request.model_copy(
1001
+ update={"search_configuration": self.config.search_config}
1002
+ )
1003
+ ask_request = await rpc.apply_ask_search_configuration(
1004
+ nucliadb_driver.driver,
1005
+ nucliadb_driver.config.kbid,
1006
+ ask_request,
1007
+ )
1008
+
1009
+ # Agent defaults fill any remaining fields.
1010
+ fallback_values = {
1011
+ "show": [ResourceProperties.BASIC, ResourceProperties.ORIGIN],
1012
+ "citations": CitationsType.LLM_FOOTNOTES,
1013
+ "generative_model": self.config.generative_model,
1014
+ "rag_strategies": rag_strategies,
1015
+ "generate_answer": self.config.generate_inner_answer,
1016
+ }
1017
+ return ask_request.model_copy(
1018
+ update={
1019
+ field: value
1020
+ for field, value in fallback_values.items()
1021
+ if field not in ask_request.model_fields_set
1022
+ }
1023
+ )
1024
+
952
1025
  async def inner_rag(
953
1026
  self,
954
1027
  source_obj: Source,
@@ -992,21 +1065,12 @@ class NucliaDBAgent(ContextAgent, Agent[NucliaDBAgentConfig]):
992
1065
  MetadataExtensionStrategy(types=["classification_labels", "origin"]), # type: ignore
993
1066
  ]
994
1067
 
995
- ask_request_json = memory.arguments.get("ask_request")
996
- if ask_request_json:
997
- # Preserve the request options from the public ask endpoint while
998
- # using the query selected by the SmartAgent for this retrieval.
999
- try:
1000
- ask_request = AskRequest.model_validate_json(
1001
- ask_request_json
1002
- ).model_copy(update={"query": question})
1003
- except ValidationError as e:
1004
- logger.error(
1005
- f"Failed to validate AskRequest received as memory argument: {e}"
1006
- )
1007
- ask_request = None
1008
- else:
1009
- ask_request = None
1068
+ ask_request = await self.prepare_ask_request(
1069
+ nucliadb_driver,
1070
+ question,
1071
+ memory.arguments.get("ask_request"),
1072
+ rag_strategies,
1073
+ )
1010
1074
 
1011
1075
  filter_expression = await self.build_filter_expression(
1012
1076
  nucliadb_driver,
@@ -1015,22 +1079,9 @@ class NucliaDBAgent(ContextAgent, Agent[NucliaDBAgentConfig]):
1015
1079
  and_filters=and_filters,
1016
1080
  or_filters=or_filters,
1017
1081
  resource_filters=resource_filters,
1018
- filter_expression=ask_request.filter_expression
1019
- if ask_request is not None
1020
- else None,
1082
+ filter_expression=ask_request.filter_expression,
1021
1083
  )
1022
- if ask_request is None:
1023
- ask_request = AskRequest(
1024
- query=question,
1025
- show=[ResourceProperties.BASIC, ResourceProperties.ORIGIN],
1026
- citations=CitationsType.LLM_FOOTNOTES,
1027
- generative_model=self.config.generative_model,
1028
- filter_expression=filter_expression,
1029
- rag_strategies=rag_strategies,
1030
- generate_answer=self.config.generate_inner_answer,
1031
- )
1032
- else:
1033
- ask_request.filter_expression = filter_expression
1084
+ ask_request.filter_expression = filter_expression
1034
1085
 
1035
1086
  await memory.add_step(
1036
1087
  step_module="nucliadb_agent",
@@ -1174,7 +1225,7 @@ class NucliaDBAgent(ContextAgent, Agent[NucliaDBAgentConfig]):
1174
1225
  ) -> List[Context]:
1175
1226
  sources = self.config.sources
1176
1227
  # Perform catalog faceted search
1177
- chosen_sources = await choose_source(
1228
+ chosen_sources = await choose_sources(
1178
1229
  memory,
1179
1230
  manager,
1180
1231
  sources,
@@ -1333,7 +1384,7 @@ class NucliaDBAgent(ContextAgent, Agent[NucliaDBAgentConfig]):
1333
1384
  ) -> List[Context]:
1334
1385
  sources = self.config.sources
1335
1386
  # Perform catalog faceted search
1336
- chosen_sources = await choose_source(
1387
+ chosen_sources = await choose_sources(
1337
1388
  memory,
1338
1389
  manager,
1339
1390
  sources,
@@ -1453,7 +1504,7 @@ class NucliaDBAgent(ContextAgent, Agent[NucliaDBAgentConfig]):
1453
1504
  ) -> List[Context]:
1454
1505
  sources = self.config.sources
1455
1506
  # Perform catalog search
1456
- chosen_sources = await choose_source(
1507
+ chosen_sources = await choose_sources(
1457
1508
  memory,
1458
1509
  manager,
1459
1510
  sources,
@@ -187,7 +187,6 @@ class StreamAuditStorage:
187
187
  nats_subject=cast(str, usage_settings.usage_jetstream_subject),
188
188
  )
189
189
  await self.kb_usage_utility.initialize()
190
-
191
190
  self.initialized = True
192
191
 
193
192
  async def finalize(self):
@@ -214,8 +213,14 @@ class StreamAuditStorage:
214
213
  for event in step.external_usage or []
215
214
  ]
216
215
  if not predicts or self.kb_usage_utility is None:
216
+ logger.warning(
217
+ "Skipping KB usage report account=%s kb=%s has_predicts=%s utility_available=%s",
218
+ account_id,
219
+ kbid,
220
+ bool(predicts),
221
+ self.kb_usage_utility is not None,
222
+ )
217
223
  return
218
-
219
224
  self.kb_usage_utility.send_kb_usage(
220
225
  service=Service.RAO,
221
226
  account_id=account_id,
@@ -2,7 +2,7 @@ import base64
2
2
  from typing import Literal
3
3
 
4
4
  from nucliadb_models.augment import AugmentRequest, AugmentResponse
5
- from nucliadb_models.configuration import SearchConfiguration
5
+ from nucliadb_models.configuration import AskConfig, SearchConfiguration
6
6
  from nucliadb_models.graph.requests import GraphNodesSearchRequest, GraphSearchRequest
7
7
  from nucliadb_models.graph.responses import (
8
8
  GraphNodesSearchResponse,
@@ -25,7 +25,7 @@ from hyperforge_nucliadb_agentic.ask.exceptions import (
25
25
  KnowledgeBoxNotFound,
26
26
  NucliaDBError,
27
27
  )
28
- from hyperforge_nucliadb_agentic.ask.model import Image
28
+ from hyperforge_nucliadb_agentic.ask.model import AskRequest, Image
29
29
  from hyperforge_nucliadb_agentic.ask.settings import settings
30
30
  from hyperforge_nucliadb_agentic.ask.utils.ids import FieldId
31
31
 
@@ -60,6 +60,36 @@ async def get_search_configuration(
60
60
  return config
61
61
 
62
62
 
63
+ class SearchConfigurationNotFound(Exception):
64
+ pass
65
+
66
+
67
+ class InvalidAskSearchConfiguration(Exception):
68
+ pass
69
+
70
+
71
+ async def apply_ask_search_configuration(
72
+ reader_sdk: NucliaDBAsync,
73
+ kbid: str,
74
+ ask_request: AskRequest,
75
+ ) -> AskRequest:
76
+ if ask_request.search_configuration is None:
77
+ return ask_request
78
+
79
+ search_config = await get_search_configuration(
80
+ reader_sdk, kbid, name=ask_request.search_configuration
81
+ )
82
+ if search_config is None:
83
+ raise SearchConfigurationNotFound
84
+ if not isinstance(search_config.config, AskConfig):
85
+ raise InvalidAskSearchConfiguration
86
+
87
+ return AskRequest.model_validate(
88
+ search_config.config.model_dump(exclude_unset=True)
89
+ | ask_request.model_dump(exclude_unset=True)
90
+ )
91
+
92
+
63
93
  async def find(
64
94
  search_sdk: NucliaDBAsync,
65
95
  kbid: str,
@@ -9,6 +9,10 @@ from pydantic.config import ConfigDict
9
9
  class NucliaDBAgentConfig(ContextAgentConfig):
10
10
  model_config = ConfigDict(title="Knowledge Box Agent")
11
11
  module: Literal["nucliadb_agent"] = "nucliadb_agent"
12
+ search_config: Optional[str] = Field(
13
+ default=None,
14
+ description="ID of an ask search configuration applied to this agent",
15
+ )
12
16
  sources: List[str] = Field(
13
17
  default_factory=list,
14
18
  json_schema_extra={