hyperforge-nucliadb-agentic 1.0.0.post64__tar.gz → 1.0.0.post83__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.post64 → hyperforge_nucliadb_agentic-1.0.0.post83}/PKG-INFO +3 -3
  2. hyperforge_nucliadb_agentic-1.0.0.post83/pyproject.toml +49 -0
  3. hyperforge_nucliadb_agentic-1.0.0.post64/pyproject.toml → hyperforge_nucliadb_agentic-1.0.0.post83/pyproject.toml.orig +3 -3
  4. {hyperforge_nucliadb_agentic-1.0.0.post64 → hyperforge_nucliadb_agentic-1.0.0.post83}/src/hyperforge_nucliadb_agentic/agent.py +1 -1
  5. {hyperforge_nucliadb_agentic-1.0.0.post64 → hyperforge_nucliadb_agentic-1.0.0.post83}/src/hyperforge_nucliadb_agentic/ask/audit.py +64 -1
  6. {hyperforge_nucliadb_agentic-1.0.0.post64 → hyperforge_nucliadb_agentic-1.0.0.post83}/src/hyperforge_nucliadb_agentic/ask/predict.py +12 -7
  7. {hyperforge_nucliadb_agentic-1.0.0.post64 → hyperforge_nucliadb_agentic-1.0.0.post83}/src/hyperforge_nucliadb_agentic/ask/search/ask.py +7 -4
  8. {hyperforge_nucliadb_agentic-1.0.0.post64 → hyperforge_nucliadb_agentic-1.0.0.post83}/README.md +0 -0
  9. {hyperforge_nucliadb_agentic-1.0.0.post64 → hyperforge_nucliadb_agentic-1.0.0.post83}/src/hyperforge_nucliadb_agentic/__init__.py +0 -0
  10. {hyperforge_nucliadb_agentic-1.0.0.post64 → hyperforge_nucliadb_agentic-1.0.0.post83}/src/hyperforge_nucliadb_agentic/ask/__init__.py +0 -0
  11. {hyperforge_nucliadb_agentic-1.0.0.post64 → hyperforge_nucliadb_agentic-1.0.0.post83}/src/hyperforge_nucliadb_agentic/ask/exceptions.py +0 -0
  12. {hyperforge_nucliadb_agentic-1.0.0.post64 → hyperforge_nucliadb_agentic-1.0.0.post83}/src/hyperforge_nucliadb_agentic/ask/lifespan.py +0 -0
  13. {hyperforge_nucliadb_agentic-1.0.0.post64 → hyperforge_nucliadb_agentic-1.0.0.post83}/src/hyperforge_nucliadb_agentic/ask/model.py +0 -0
  14. {hyperforge_nucliadb_agentic-1.0.0.post64 → hyperforge_nucliadb_agentic-1.0.0.post83}/src/hyperforge_nucliadb_agentic/ask/predict_models.py +0 -0
  15. {hyperforge_nucliadb_agentic-1.0.0.post64 → hyperforge_nucliadb_agentic-1.0.0.post83}/src/hyperforge_nucliadb_agentic/ask/search/__init__.py +0 -0
  16. {hyperforge_nucliadb_agentic-1.0.0.post64 → hyperforge_nucliadb_agentic-1.0.0.post83}/src/hyperforge_nucliadb_agentic/ask/search/graph_strategy.py +0 -0
  17. {hyperforge_nucliadb_agentic-1.0.0.post64 → hyperforge_nucliadb_agentic-1.0.0.post83}/src/hyperforge_nucliadb_agentic/ask/search/highlight.py +0 -0
  18. {hyperforge_nucliadb_agentic-1.0.0.post64 → hyperforge_nucliadb_agentic-1.0.0.post83}/src/hyperforge_nucliadb_agentic/ask/search/hydrator.py +0 -0
  19. {hyperforge_nucliadb_agentic-1.0.0.post64 → hyperforge_nucliadb_agentic-1.0.0.post83}/src/hyperforge_nucliadb_agentic/ask/search/metrics.py +0 -0
  20. {hyperforge_nucliadb_agentic-1.0.0.post64 → hyperforge_nucliadb_agentic-1.0.0.post83}/src/hyperforge_nucliadb_agentic/ask/search/parsers/__init__.py +0 -0
  21. {hyperforge_nucliadb_agentic-1.0.0.post64 → hyperforge_nucliadb_agentic-1.0.0.post83}/src/hyperforge_nucliadb_agentic/ask/search/parsers/ask.py +0 -0
  22. {hyperforge_nucliadb_agentic-1.0.0.post64 → hyperforge_nucliadb_agentic-1.0.0.post83}/src/hyperforge_nucliadb_agentic/ask/search/parsers/fetcher.py +0 -0
  23. {hyperforge_nucliadb_agentic-1.0.0.post64 → hyperforge_nucliadb_agentic-1.0.0.post83}/src/hyperforge_nucliadb_agentic/ask/search/parsers/find.py +0 -0
  24. {hyperforge_nucliadb_agentic-1.0.0.post64 → hyperforge_nucliadb_agentic-1.0.0.post83}/src/hyperforge_nucliadb_agentic/ask/search/prompt.py +0 -0
  25. {hyperforge_nucliadb_agentic-1.0.0.post64 → hyperforge_nucliadb_agentic-1.0.0.post83}/src/hyperforge_nucliadb_agentic/ask/search/rank_fusion.py +0 -0
  26. {hyperforge_nucliadb_agentic-1.0.0.post64 → hyperforge_nucliadb_agentic-1.0.0.post83}/src/hyperforge_nucliadb_agentic/ask/search/rerankers.py +0 -0
  27. {hyperforge_nucliadb_agentic-1.0.0.post64 → hyperforge_nucliadb_agentic-1.0.0.post83}/src/hyperforge_nucliadb_agentic/ask/search/retrieval.py +0 -0
  28. {hyperforge_nucliadb_agentic-1.0.0.post64 → hyperforge_nucliadb_agentic-1.0.0.post83}/src/hyperforge_nucliadb_agentic/ask/search/rpc.py +0 -0
  29. {hyperforge_nucliadb_agentic-1.0.0.post64 → hyperforge_nucliadb_agentic-1.0.0.post83}/src/hyperforge_nucliadb_agentic/ask/settings.py +0 -0
  30. {hyperforge_nucliadb_agentic-1.0.0.post64 → hyperforge_nucliadb_agentic-1.0.0.post83}/src/hyperforge_nucliadb_agentic/ask/utils/ids.py +0 -0
  31. {hyperforge_nucliadb_agentic-1.0.0.post64 → hyperforge_nucliadb_agentic-1.0.0.post83}/src/hyperforge_nucliadb_agentic/ask/utils/proto.py +0 -0
  32. {hyperforge_nucliadb_agentic-1.0.0.post64 → hyperforge_nucliadb_agentic-1.0.0.post83}/src/hyperforge_nucliadb_agentic/ask/utils/responses.py +0 -0
  33. {hyperforge_nucliadb_agentic-1.0.0.post64 → hyperforge_nucliadb_agentic-1.0.0.post83}/src/hyperforge_nucliadb_agentic/ask/utils/text_blocks.py +0 -0
  34. {hyperforge_nucliadb_agentic-1.0.0.post64 → hyperforge_nucliadb_agentic-1.0.0.post83}/src/hyperforge_nucliadb_agentic/config.py +0 -0
  35. {hyperforge_nucliadb_agentic-1.0.0.post64 → hyperforge_nucliadb_agentic-1.0.0.post83}/src/hyperforge_nucliadb_agentic/internal_driver.py +0 -0
  36. {hyperforge_nucliadb_agentic-1.0.0.post64 → hyperforge_nucliadb_agentic-1.0.0.post83}/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.post64
3
+ Version: 1.0.0.post83
4
4
  Summary: NucliaDB Hyperforge agent
5
5
  Author: Nuclia
6
6
  Author-email: Nuclia <nucliadb@nuclia.com>
@@ -11,10 +11,10 @@ Classifier: Programming Language :: Python :: 3.11
11
11
  Classifier: Programming Language :: Python :: 3.12
12
12
  Classifier: Programming Language :: Python :: 3 :: Only
13
13
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
14
- Requires-Dist: hyperforge>=1.0.0.post76
14
+ Requires-Dist: hyperforge>=1.0.0.post184
15
15
  Requires-Dist: lingua-language-detector
16
16
  Requires-Dist: nucliadb-sdk
17
- Requires-Python: >=3.10, <4
17
+ Requires-Python: >=3.12, <4
18
18
  Project-URL: Homepage, https://progress.com
19
19
  Project-URL: Repository, https://github.com/nuclia/nucliadb_agentic_api
20
20
  Project-URL: Changelog, https://github.com/nuclia/nucliadb_agentic_api/blob/main/agents/nucliadb/CHANGELOG.md
@@ -0,0 +1,49 @@
1
+ [build-system]
2
+ requires = ["uv_build>=0.11.19,<0.12"]
3
+ build-backend = "uv_build"
4
+
5
+ [project]
6
+ name = "hyperforge_nucliadb_agentic"
7
+ version = "1.0.0.post83"
8
+ license = "Apache-2.0"
9
+ description = "NucliaDB Hyperforge agent"
10
+ readme = "README.md"
11
+ classifiers = [
12
+ "Programming Language :: Python",
13
+ "Programming Language :: Python :: 3.10",
14
+ "Programming Language :: Python :: 3.11",
15
+ "Programming Language :: Python :: 3.12",
16
+ "Programming Language :: Python :: 3 :: Only",
17
+ "Topic :: Software Development :: Libraries :: Python Modules",
18
+ ]
19
+ requires-python = ">=3.12, <4"
20
+ dependencies = [
21
+ "hyperforge>=1.0.0.post184",
22
+ "lingua-language-detector",
23
+ "nucliadb_sdk",
24
+ ]
25
+
26
+ [[project.authors]]
27
+ name = "Nuclia"
28
+ email = "nucliadb@nuclia.com"
29
+
30
+ [project.urls]
31
+ Homepage = "https://progress.com"
32
+ Repository = "https://github.com/nuclia/nucliadb_agentic_api"
33
+ Changelog = "https://github.com/nuclia/nucliadb_agentic_api/blob/main/agents/nucliadb/CHANGELOG.md"
34
+
35
+ [dependency-groups]
36
+ dev = [
37
+ "pytest",
38
+ "pytest-benchmark",
39
+ "pytest-docker-fixtures>=1.4.2",
40
+ "pytest-lazy-fixtures",
41
+ "hyperforge_rephrase",
42
+ ]
43
+
44
+ [tool.pytest.ini_options]
45
+ asyncio_mode = "auto"
46
+ testpaths = ["tests"]
47
+
48
+ [tool.uv.build-backend]
49
+ source-exclude = ["tests/**/*"]
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "hyperforge_nucliadb_agentic"
7
- version = "1.0.0.post64"
7
+ version = "1.0.0.post83"
8
8
  license = "Apache-2.0"
9
9
  description = "NucliaDB Hyperforge agent"
10
10
  authors = [{ name = "Nuclia", email = "nucliadb@nuclia.com" }]
@@ -17,9 +17,9 @@ classifiers = [
17
17
  "Programming Language :: Python :: 3 :: Only",
18
18
  "Topic :: Software Development :: Libraries :: Python Modules",
19
19
  ]
20
- requires-python = ">=3.10, <4"
20
+ requires-python = ">=3.12, <4"
21
21
  dependencies = [
22
- "hyperforge>=1.0.0.post76",
22
+ "hyperforge>=1.0.0.post184",
23
23
  "lingua-language-detector",
24
24
  "nucliadb_sdk",
25
25
  ]
@@ -1056,7 +1056,7 @@ class NucliaDBAgent(ContextAgent, Agent[NucliaDBAgentConfig]):
1056
1056
  )
1057
1057
  )
1058
1058
 
1059
- paragraphs = await paragraphs_result.model_dump()
1059
+ paragraphs = await paragraphs_result.to_sync_response()
1060
1060
  answer = None
1061
1061
  input_tokens = (
1062
1062
  paragraphs.consumption.normalized_tokens.input
@@ -9,6 +9,7 @@ import mmh3
9
9
  import nats
10
10
  from fastapi import Request
11
11
  from hyperforge.feature_flag import Features, has_feature
12
+ from hyperforge.models import ExternalUsage, Step
12
13
  from nucliadb_models.retrieval import RawQuery, RetrievalRequest
13
14
  from nucliadb_models.search import (
14
15
  NucliaDBClientType,
@@ -19,9 +20,19 @@ from nucliadb_protos.audit_pb2 import (
19
20
  ChatContext,
20
21
  RetrievedContext,
21
22
  )
23
+ from nucliadb_protos.kb_usage_pb2 import (
24
+ ActivityLogMatch,
25
+ ActivityLogMatchType,
26
+ ClientType as KbUsageClientType,
27
+ KBSource,
28
+ Predict,
29
+ PredictType,
30
+ Service,
31
+ )
22
32
  from nucliadb_telemetry.jetstream import get_traced_jetstream, get_traced_nats_client
23
33
  from nucliadb_utils import logger
24
- from nucliadb_utils.settings import AuditSettings
34
+ from nucliadb_utils.nuclia_usage.utils.kb_usage_report import KbUsageReportUtility
35
+ from nucliadb_utils.settings import AuditSettings, usage_settings
25
36
  from nucliadb_utils.utilities import Utility, clean_utility, get_utility, set_utility
26
37
  from opentelemetry.trace import INVALID_SPAN, format_trace_id, get_current_span
27
38
  from starlette.background import BackgroundTask
@@ -38,6 +49,21 @@ from hyperforge_nucliadb_agentic.ask.predict import AnswerStatusCode
38
49
  from hyperforge_nucliadb_agentic.ask.utils.proto import client_type
39
50
 
40
51
 
52
+ def external_usage_to_predict(
53
+ event: ExternalUsage, ndb_client_type: NucliaDBClientType
54
+ ) -> Predict:
55
+ return Predict(
56
+ client=KbUsageClientType.Value(ndb_client_type.name),
57
+ type=PredictType.QUESTION_ANSWER,
58
+ model=f"{event.provider}/{event.model}",
59
+ input=event.input_tokens,
60
+ output=event.output_tokens,
61
+ num_predicts=event.requests,
62
+ image=event.image,
63
+ customer_key=False,
64
+ )
65
+
66
+
41
67
  class RequestContext:
42
68
  def __init__(self: "RequestContext"):
43
69
  self.audit_request: AuditRequest = AuditRequest()
@@ -120,6 +146,7 @@ class StreamAuditStorage:
120
146
  self.service = service
121
147
  self.task = None
122
148
  self.initialized = False
149
+ self.kb_usage_utility: KbUsageReportUtility | None = None
123
150
 
124
151
  def get_partition(self, kbid: str):
125
152
  return mmh3.hash(kbid, self.seed, signed=False) % self.partitions
@@ -155,10 +182,17 @@ class StreamAuditStorage:
155
182
 
156
183
  self.js = get_traced_jetstream(self.nc, self.service)
157
184
  self.task = asyncio.create_task(self.run())
185
+ self.kb_usage_utility = KbUsageReportUtility(
186
+ nats_stream=self.js,
187
+ nats_subject=cast(str, usage_settings.usage_jetstream_subject),
188
+ )
189
+ await self.kb_usage_utility.initialize()
158
190
 
159
191
  self.initialized = True
160
192
 
161
193
  async def finalize(self):
194
+ if self.kb_usage_utility is not None:
195
+ await self.kb_usage_utility.finalize()
162
196
  if self.task is not None:
163
197
  self.task.cancel()
164
198
  if self.nc:
@@ -166,6 +200,35 @@ class StreamAuditStorage:
166
200
  await self.nc.close()
167
201
  self.nc = None
168
202
 
203
+ def report_step_usage(
204
+ self,
205
+ *,
206
+ account_id: str,
207
+ kbid: str,
208
+ client_type: NucliaDBClientType,
209
+ step: Step,
210
+ trace_id: str | None = None,
211
+ ) -> None:
212
+ predicts = [
213
+ external_usage_to_predict(event, client_type)
214
+ for event in step.external_usage or []
215
+ ]
216
+ if not predicts or self.kb_usage_utility is None:
217
+ return
218
+
219
+ self.kb_usage_utility.send_kb_usage(
220
+ service=Service.RAO,
221
+ account_id=account_id,
222
+ kb_id=kbid,
223
+ kb_source=KBSource.HOSTED,
224
+ predicts=predicts,
225
+ activity_log_match=(
226
+ ActivityLogMatch(id=trace_id, type=ActivityLogMatchType.TRACE_ID)
227
+ if trace_id
228
+ else None
229
+ ),
230
+ )
231
+
169
232
  async def run(self):
170
233
  while True:
171
234
  item_dequeued = False
@@ -296,13 +296,18 @@ class PredictEngine:
296
296
  headers = self.get_predict_headers(kbid)
297
297
  headers["Accept"] = "application/x-ndjson"
298
298
 
299
- resp = await self.make_stream_request(
300
- "POST",
301
- url=self.get_predict_url(CHAT, kbid),
302
- json=item.model_dump(),
303
- headers={**headers, **(extra_headers or {})},
304
- timeout=httpx.Timeout(180.0, read=120.0),
305
- )
299
+ try:
300
+ resp = await self.make_stream_request(
301
+ "POST",
302
+ url=self.get_predict_url(CHAT, kbid),
303
+ json=item.model_dump(),
304
+ headers={**headers, **(extra_headers or {})},
305
+ timeout=httpx.Timeout(30.0, read=None),
306
+ )
307
+ except httpx.TimeoutException as exc:
308
+ raise ProxiedPredictAPIError(
309
+ status=504, detail="Timeout while connecting to predict"
310
+ ) from exc
306
311
  await self.check_response(kbid, resp, expected_status=200)
307
312
  ident = resp.headers.get(NUCLIA_LEARNING_ID_HEADER) or "unknown"
308
313
  model = resp.headers.get(NUCLIA_LEARNING_MODEL_HEADER) or "unknown"
@@ -378,7 +378,7 @@ class AskResult:
378
378
  metrics=self.metrics.dump(),
379
379
  )
380
380
 
381
- async def model_dump(self) -> SyncAskResponse:
381
+ async def to_sync_response(self) -> SyncAskResponse:
382
382
  # Run the stream to completion to ensure all data is available in memory
383
383
  # First, run the stream in memory to get all the data in memory
384
384
  async for _ in self._stream():
@@ -458,7 +458,7 @@ class AskResult:
458
458
  return response
459
459
 
460
460
  async def json(self) -> str:
461
- response = await self.model_dump()
461
+ response = await self.to_sync_response()
462
462
  return response.model_dump_json(exclude_none=True, by_alias=True)
463
463
 
464
464
  async def get_relations_results(self) -> Relations:
@@ -551,7 +551,7 @@ class NotEnoughContextAskResult(AskResult):
551
551
  )
552
552
  )
553
553
 
554
- async def json(self) -> str:
554
+ async def to_sync_response(self) -> SyncAskResponse:
555
555
  prequeries = (
556
556
  {
557
557
  prequery.id or f"prequery_{index}": prequery_result
@@ -567,7 +567,10 @@ class NotEnoughContextAskResult(AskResult):
567
567
  retrieval_results=self.main_results,
568
568
  prequeries=prequeries,
569
569
  status=AnswerStatusCode.NO_RETRIEVAL_DATA.prettify(),
570
- ).model_dump_json()
570
+ )
571
+
572
+ async def json(self) -> str:
573
+ return (await self.to_sync_response()).model_dump_json()
571
574
 
572
575
 
573
576
  async def rephrase_query(