nuclia 4.8.9__tar.gz → 4.9.0__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 (94) hide show
  1. {nuclia-4.8.9 → nuclia-4.9.0}/CHANGELOG.md +12 -0
  2. {nuclia-4.8.9 → nuclia-4.9.0}/PKG-INFO +4 -4
  3. nuclia-4.9.0/VERSION +1 -0
  4. {nuclia-4.8.9 → nuclia-4.9.0}/docs/05-search.md +30 -0
  5. {nuclia-4.8.9 → nuclia-4.9.0}/docs/11-activity-log.md +13 -15
  6. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/lib/kb.py +6 -90
  7. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/lib/models.py +2 -2
  8. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/sdk/logs.py +25 -59
  9. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/sdk/search.py +56 -0
  10. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/tests/fixtures.py +2 -2
  11. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/tests/test_kb/test_conversation.py +0 -5
  12. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/tests/test_kb/test_graph.py +40 -0
  13. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/tests/test_kb/test_logs.py +35 -20
  14. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia.egg-info/PKG-INFO +4 -4
  15. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia.egg-info/requires.txt +3 -3
  16. {nuclia-4.8.9 → nuclia-4.9.0}/pyproject.toml +3 -3
  17. {nuclia-4.8.9 → nuclia-4.9.0}/uv.lock +1731 -1729
  18. nuclia-4.8.9/VERSION +0 -1
  19. {nuclia-4.8.9 → nuclia-4.9.0}/.gitignore +0 -0
  20. {nuclia-4.8.9 → nuclia-4.9.0}/LICENSE +0 -0
  21. {nuclia-4.8.9 → nuclia-4.9.0}/MANIFEST.in +0 -0
  22. {nuclia-4.8.9 → nuclia-4.9.0}/Makefile +0 -0
  23. {nuclia-4.8.9 → nuclia-4.9.0}/README.md +0 -0
  24. {nuclia-4.8.9 → nuclia-4.9.0}/docs/01-README.md +0 -0
  25. {nuclia-4.8.9 → nuclia-4.9.0}/docs/02-auth.md +0 -0
  26. {nuclia-4.8.9 → nuclia-4.9.0}/docs/03-kb.md +0 -0
  27. {nuclia-4.8.9 → nuclia-4.9.0}/docs/04-upload.md +0 -0
  28. {nuclia-4.8.9 → nuclia-4.9.0}/docs/06-read.md +0 -0
  29. {nuclia-4.8.9 → nuclia-4.9.0}/docs/07-nua.md +0 -0
  30. {nuclia-4.8.9 → nuclia-4.9.0}/docs/08-import-export.md +0 -0
  31. {nuclia-4.8.9 → nuclia-4.9.0}/docs/09-kb-backup.md +0 -0
  32. {nuclia-4.8.9 → nuclia-4.9.0}/docs/10-manage.md +0 -0
  33. {nuclia-4.8.9 → nuclia-4.9.0}/docs/12-da-agents.md +0 -0
  34. {nuclia-4.8.9 → nuclia-4.9.0}/docs/13-ai-agents.md +0 -0
  35. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/__init__.py +0 -0
  36. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/cli/__init__.py +0 -0
  37. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/cli/run.py +0 -0
  38. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/cli/utils.py +0 -0
  39. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/config.py +0 -0
  40. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/data.py +0 -0
  41. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/decorators.py +0 -0
  42. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/exceptions.py +0 -0
  43. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/lib/__init__.py +0 -0
  44. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/lib/conversations.py +0 -0
  45. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/lib/nua.py +0 -0
  46. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/lib/nua_chat.py +0 -0
  47. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/lib/nua_responses.py +0 -0
  48. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/lib/utils.py +0 -0
  49. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/py.typed +0 -0
  50. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/sdk/__init__.py +0 -0
  51. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/sdk/accounts.py +0 -0
  52. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/sdk/agent.py +0 -0
  53. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/sdk/auth.py +0 -0
  54. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/sdk/backup.py +0 -0
  55. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/sdk/export_import.py +0 -0
  56. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/sdk/kb.py +0 -0
  57. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/sdk/kbs.py +0 -0
  58. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/sdk/logger.py +0 -0
  59. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/sdk/nua.py +0 -0
  60. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/sdk/nucliadb.py +0 -0
  61. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/sdk/predict.py +0 -0
  62. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/sdk/process.py +0 -0
  63. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/sdk/remi.py +0 -0
  64. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/sdk/resource.py +0 -0
  65. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/sdk/task.py +0 -0
  66. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/sdk/upload.py +0 -0
  67. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/sdk/zones.py +0 -0
  68. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/tests/__init__.py +0 -0
  69. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/tests/assets/conversation.json +0 -0
  70. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/tests/conftest.py +0 -0
  71. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/tests/test_kb/test_backup.py +0 -0
  72. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/tests/test_kb/test_export_import.py +0 -0
  73. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/tests/test_kb/test_labels.py +0 -0
  74. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/tests/test_kb/test_remi.py +0 -0
  75. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/tests/test_kb/test_resource.py +0 -0
  76. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/tests/test_kb/test_search.py +0 -0
  77. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/tests/test_kb/test_tasks.py +0 -0
  78. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/tests/test_manage/__init__.py +0 -0
  79. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/tests/test_manage/test_account.py +0 -0
  80. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/tests/test_manage/test_auth.py +0 -0
  81. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/tests/test_manage/test_kb.py +0 -0
  82. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/tests/test_nua/__init__.py +0 -0
  83. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/tests/test_nua/test_agent.py +0 -0
  84. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/tests/test_nua/test_predict.py +0 -0
  85. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/tests/test_nucliadb/__init__.py +0 -0
  86. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/tests/test_nucliadb/test_crud.py +0 -0
  87. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/tests/unit/__init__.py +0 -0
  88. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/tests/unit/test_export_import.py +0 -0
  89. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia/tests/unit/test_nua_responses.py +0 -0
  90. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia.egg-info/SOURCES.txt +0 -0
  91. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia.egg-info/dependency_links.txt +0 -0
  92. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia.egg-info/entry_points.txt +0 -0
  93. {nuclia-4.8.9 → nuclia-4.9.0}/nuclia.egg-info/top_level.txt +0 -0
  94. {nuclia-4.8.9 → nuclia-4.9.0}/setup.cfg +0 -0
@@ -1,6 +1,18 @@
1
1
  # Changelog
2
2
 
3
3
 
4
+ ## 4.9.0 (2025-06-05)
5
+
6
+
7
+ - Add search graph method
8
+
9
+
10
+ ## 4.8.10 (2025-05-28)
11
+
12
+
13
+ - Activity logs: Add event type `ASK` and deprecate `CHAT`
14
+
15
+
4
16
  ## 4.8.9 (2025-05-15)
5
17
 
6
18
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nuclia
3
- Version: 4.8.9
3
+ Version: 4.9.0
4
4
  Summary: Nuclia Python SDK
5
5
  Author-email: Nuclia <info@nuclia.com>
6
6
  License-Expression: MIT
@@ -24,9 +24,9 @@ Requires-Dist: requests
24
24
  Requires-Dist: httpx
25
25
  Requires-Dist: httpcore>=1.0.0
26
26
  Requires-Dist: prompt_toolkit
27
- Requires-Dist: nucliadb_sdk<7,>=6.4
28
- Requires-Dist: nucliadb_models<7,>=6.4
29
- Requires-Dist: nuclia-models>=0.39.0
27
+ Requires-Dist: nucliadb_sdk<7,>=6.5
28
+ Requires-Dist: nucliadb_models<7,>=6.5
29
+ Requires-Dist: nuclia-models>=0.41.1
30
30
  Requires-Dist: tqdm
31
31
  Requires-Dist: aiofiles
32
32
  Requires-Dist: backoff
nuclia-4.9.0/VERSION ADDED
@@ -0,0 +1 @@
1
+ 4.9.0
@@ -139,3 +139,33 @@ In these cases, you can just pass your query as a dictionnary in the `query` par
139
139
  search.find(query={"query": "My search", "filters": ["/icon/application/pdf", "/classification.labels/region/Asia"]})
140
140
  search.ask(query={"query": "My search","top_k": 5})
141
141
  ```
142
+
143
+ ## Graph queries
144
+
145
+ The Python SDK allows graph queries supported by the `/graph` endpoint. Although
146
+ a bit cumbersome, the knowledge graph can be queried as in this example:
147
+
148
+ - CLI:
149
+
150
+ ```bash
151
+ nuclia kb search graph --query='{"query": {"prop": "path", "source": {"value": "Rust"}, "destination": {"value": "Python"}}}'
152
+ ```
153
+
154
+ - SDK:
155
+
156
+ ```python
157
+ from nuclia import sdk
158
+ search = sdk.NucliaSearch()
159
+ search.graph(
160
+ query={
161
+ "query": {
162
+ "prop": "path",
163
+ "source": {"value": "Rust"},
164
+ "destination": {"value": "Python"}
165
+ }
166
+ }
167
+ )
168
+ ```
169
+
170
+ For more information about graph querying, please refer to [Nuclia's graph
171
+ doc](https://docs.nuclia.dev/docs/rag/advanced/graph) or the [API reference](https://docs.nuclia.dev/docs/api#tag/Search/operation/graph_search_knowledgebox_kb__kbid__graph_post)
@@ -35,7 +35,7 @@ See the examples for more information
35
35
 
36
36
  #### Event-Specific Fields
37
37
  - `SEARCH` events: Common fields + `question`, `resources_count`, `filter`, `learning_id`
38
- - `CHAT` events: Common fields + Search fields + `rephrased_question`, `answer`, `retrieved_context`, `chat_history`, `feedback_good`, `feedback_comment`, `model`, `rag_strategies_names`, `rag_strategies`, `status`, `time_to_first_char`
38
+ - `ASK` events: Common fields + Search fields + `rephrased_question`, `answer`, `retrieved_context`, `chat_history`, `feedback_good`, `feedback_comment`, `model`, `rag_strategies_names`, `rag_strategies`, `status`, `time_to_first_char`
39
39
 
40
40
 
41
41
  ### Query Examples
@@ -43,7 +43,7 @@ See the examples for more information
43
43
  #### CLI Example
44
44
 
45
45
  ```bash
46
- nuclia kb logs query --type=CHAT --query='{
46
+ nuclia kb logs query --type=ASK --query='{
47
47
  "year_month": "2024-10",
48
48
  "show": ["id", "date", "question", "answer", "feedback_good"],
49
49
  "filters": {
@@ -58,11 +58,10 @@ nuclia kb logs query --type=CHAT --query='{
58
58
 
59
59
  ```python
60
60
  from nuclia import sdk
61
- from nuclia.lib.kb import LogType
62
- from nuclia_models.events.activity_logs import ActivityLogsChatQuery, Pagination
61
+ from nuclia_models.events.activity_logs import ActivityLogsAskQuery, EventType, Pagination
63
62
 
64
63
  kb = sdk.NucliaKB()
65
- query = ActivityLogsChatQuery(
64
+ query = ActivityLogsAskQuery(
66
65
  year_month="2024-10",
67
66
  show=["id", "date", "question", "answer"],
68
67
  filters={
@@ -71,7 +70,7 @@ query = ActivityLogsChatQuery(
71
70
  },
72
71
  pagination=Pagination(limit=10)
73
72
  )
74
- kb.logs.query(type=LogType.CHAT, query=query)
73
+ kb.logs.query(type=EventType.ASK, query=query)
75
74
  ```
76
75
  ### Special Field: `audit_metadata`
77
76
  The `audit_metadata` field is a customizable dictionary. Use the `key` operator to target specific keys within the dictionary.
@@ -103,7 +102,7 @@ The `audit_metadata` field is a customizable dictionary. Use the `key` operator
103
102
  Request download and wait until the download url is generated
104
103
 
105
104
  ```bash
106
- >>> nuclia kb logs download --wait --type=CHAT --format=NDJSON --query='{
105
+ >>> nuclia kb logs download --wait --type=ASK --format=NDJSON --query='{
107
106
  "year_month": "2024-10",
108
107
  "show": ["id", "date", "question", "answer", "feedback_good"],
109
108
  "filters": {
@@ -119,7 +118,7 @@ download_url=https://your-download-url
119
118
 
120
119
  Request download and ask to be notified
121
120
  ```bash
122
- >>> nuclia kb logs download --type=CHAT --format=NDJSON --query='{
121
+ >>> nuclia kb logs download --type=ASK --format=NDJSON --query='{
123
122
  "year_month": "2024-10",
124
123
  "show": ["id", "date", "question", "answer", "feedback_good"],
125
124
  "filters": {
@@ -136,7 +135,7 @@ download_url=null
136
135
  ```
137
136
  Request download and poll for the status
138
137
  ```bash
139
- >>> nuclia kb logs download --type=CHAT --format=NDJSON --query='{
138
+ >>> nuclia kb logs download --type=ASK --format=NDJSON --query='{
140
139
  "year_month": "2024-10",
141
140
  "show": ["id", "date", "question", "answer", "feedback_good"],
142
141
  "filters": {
@@ -159,11 +158,10 @@ download_url=https://your-download-url
159
158
 
160
159
  ```python
161
160
  from nuclia import sdk
162
- from nuclia.lib.kb import LogType
163
- from nuclia_models.events.activity_logs import DownloadActivityLogsChatQuery
161
+ from nuclia_models.events.activity_logs import DownloadActivityLogsAskQuery, EventType
164
162
 
165
163
  kb = sdk.NucliaKB()
166
- query = DownloadActivityLogsChatQuery(
164
+ query = DownloadActivityLogsAskQuery(
167
165
  year_month="2024-10",
168
166
  show=["id", "date", "question", "answer"],
169
167
  filters={
@@ -171,7 +169,7 @@ query = DownloadActivityLogsChatQuery(
171
169
  "feedback_good": {"eq": True}
172
170
  },
173
171
  )
174
- request = kb.logs.download(type=LogType.CHAT, query=query, wait=True)
172
+ request = kb.logs.download(type=EventType.ASK, query=query, wait=True)
175
173
  return request.download_url
176
174
  ```
177
175
 
@@ -182,7 +180,7 @@ The REMi module provides tools to monitor the quality of your RAG pipeline to ge
182
180
 
183
181
  ## REMi Query
184
182
 
185
- Use `remi query` to retrieve a list of chat activity logs that match specified criteria for REMi scores.
183
+ Use `remi query` to retrieve a list of ask activity logs that match specified criteria for REMi scores.
186
184
 
187
185
  ### Basic Query
188
186
 
@@ -284,7 +282,7 @@ kb.remi.query(
284
282
 
285
283
  ### REMi Get
286
284
 
287
- Use `remi get_event` to fetch detailed information for a specific chat activity log. This command is useful for retrieving full context and score details of an entry obtained from a previous REMi query.
285
+ Use `remi get_event` to fetch detailed information for a specific ask activity log. This command is useful for retrieving full context and score details of an entry obtained from a previous REMi query.
288
286
 
289
287
  #### CLI Example
290
288
  ```bash
@@ -1,5 +1,4 @@
1
1
  import base64
2
- import csv
3
2
  import os
4
3
  from enum import Enum
5
4
  from typing import Dict, Optional, Union
@@ -15,10 +14,10 @@ from tqdm import tqdm
15
14
  from nuclia_models.events.activity_logs import ( # type: ignore
16
15
  ActivityLogsQuery,
17
16
  ActivityLogsSearchQuery,
18
- ActivityLogsChatQuery,
17
+ ActivityLogsAskQuery,
19
18
  DownloadActivityLogsQuery,
20
19
  DownloadActivityLogsSearchQuery,
21
- DownloadActivityLogsChatQuery,
20
+ DownloadActivityLogsAskQuery,
22
21
  EventType,
23
22
  DownloadFormat,
24
23
  )
@@ -46,11 +45,9 @@ DOWNLOAD_EXPORT_URL = "/export/{export_id}"
46
45
  DOWNLOAD_URL = "/{uri}"
47
46
  TUS_UPLOAD_RESOURCE_URL = "/resource/{rid}/file/{field}/tusupload"
48
47
  TUS_UPLOAD_URL = "/tusupload"
49
- LEGACY_ACTIVITY_LOG_URL = "/activity/download?type={type}&month={month}"
50
48
  ACTIVITY_LOG_URL = "/activity/{type}/query/download"
51
49
  ACTIVITY_LOG_DOWNLOAD_REQUEST_URL = "/activity/download_request/{request_id}"
52
50
  ACTIVITY_LOG_QUERY_URL = "/activity/{type}/query"
53
- FEEDBACK_LOG_URL = "/feedback/{month}"
54
51
  NOTIFICATIONS = "/notifications"
55
52
  REMI_QUERY_URL = "/remi/query"
56
53
  REMI_EVENT_URL = "/remi/events/{event_id}"
@@ -73,23 +70,6 @@ class Environment(str, Enum):
73
70
  OSS = "OSS"
74
71
 
75
72
 
76
- class LogType(str, Enum):
77
- # Nucliadb
78
- VISITED = "visited"
79
- MODIFIED = "modified"
80
- DELETED = "deleted"
81
- NEW = "new"
82
- SEARCH = "search"
83
- SUGGEST = "suggest"
84
- INDEXED = "indexed"
85
- CHAT = "chat"
86
- # Tasks
87
- STARTED = "started"
88
- STOPPED = "stopped"
89
- # Processor
90
- PROCESSED = "processed"
91
-
92
-
93
73
  class BaseNucliaDBClient:
94
74
  environment: Environment
95
75
  base_url: str
@@ -352,41 +332,10 @@ class NucliaDBClient(BaseNucliaDBClient):
352
332
  handle_http_sync_errors(response)
353
333
  return response
354
334
 
355
- def logs(self, type: LogType, month: str) -> list[list[str]]:
356
- if self.reader_session is None:
357
- raise Exception("KB not configured")
358
-
359
- if type != "feedback":
360
- url = LEGACY_ACTIVITY_LOG_URL.format(type=type.value, month=month)
361
- response: httpx.Response = self.reader_session.get(url)
362
- handle_http_sync_errors(response)
363
- return [row for row in csv.reader(response.iter_lines())]
364
- else:
365
- feedback_url = f"{self.url}{FEEDBACK_LOG_URL.format(month=month)}"
366
- feedback_response: httpx.Response = self.reader_session.get(feedback_url)
367
- handle_http_sync_errors(feedback_response)
368
- feedbacks = [row for row in csv.reader(feedback_response.iter_lines())]
369
- answers = self.logs(type=LogType.CHAT, month=month)
370
- # first row with the columns headers
371
- results = [[*feedbacks[0], *answers[0][:-1]]]
372
- for feedback in feedbacks[1:]:
373
- learning_id = feedback[1]
374
- # search for the corresponding question/answer
375
- # (the learning id is the same for both question/answer and feedback,
376
- # and is the second column in the Q/A csv)
377
- matching_answers = [
378
- answer for answer in answers if answer[1] == learning_id
379
- ]
380
- if len(matching_answers) > 0:
381
- results.append([*feedback, *matching_answers[0][:-1]])
382
- else:
383
- results.append(feedback)
384
- return results
385
-
386
335
  def logs_query(
387
336
  self,
388
337
  type: EventType,
389
- query: Union[ActivityLogsQuery, ActivityLogsSearchQuery, ActivityLogsChatQuery],
338
+ query: Union[ActivityLogsQuery, ActivityLogsSearchQuery, ActivityLogsAskQuery],
390
339
  ) -> requests.Response:
391
340
  if self.stream_session is None:
392
341
  raise Exception("KB not configured")
@@ -405,7 +354,7 @@ class NucliaDBClient(BaseNucliaDBClient):
405
354
  query: Union[
406
355
  DownloadActivityLogsQuery,
407
356
  DownloadActivityLogsSearchQuery,
408
- DownloadActivityLogsChatQuery,
357
+ DownloadActivityLogsAskQuery,
409
358
  ],
410
359
  download_format: DownloadFormat,
411
360
  ):
@@ -709,43 +658,10 @@ class AsyncNucliaDBClient(BaseNucliaDBClient):
709
658
  await handle_http_async_errors(response)
710
659
  return response
711
660
 
712
- async def logs(self, type: LogType, month: str) -> list[list[str]]:
713
- if self.reader_session is None:
714
- raise Exception("KB not configured")
715
-
716
- if type != "feedback":
717
- url = LEGACY_ACTIVITY_LOG_URL.format(type=type.value, month=month)
718
- response: httpx.Response = await self.reader_session.get(url)
719
- await handle_http_async_errors(response)
720
- return [row for row in csv.reader(response.iter_lines())]
721
- else:
722
- feedback_url = f"{self.url}{FEEDBACK_LOG_URL.format(month=month)}"
723
- feedback_response: httpx.Response = await self.reader_session.get(
724
- feedback_url
725
- )
726
- await handle_http_async_errors(feedback_response)
727
- feedbacks = [row for row in csv.reader(feedback_response.iter_lines())]
728
- answers = await self.logs(type=LogType.CHAT, month=month)
729
- # first row with the columns headers
730
- results = [[*feedbacks[0], *answers[0][:-1]]]
731
- for feedback in feedbacks[1:]:
732
- learning_id = feedback[1]
733
- # search for the corresponding question/answer
734
- # (the learning id is the same for both question/answer and feedback,
735
- # and is the second column in the Q/A csv)
736
- matching_answers = [
737
- answer for answer in answers if answer[1] == learning_id
738
- ]
739
- if len(matching_answers) > 0:
740
- results.append([*feedback, *matching_answers[0][:-1]])
741
- else:
742
- results.append(feedback)
743
- return results
744
-
745
661
  async def logs_query(
746
662
  self,
747
663
  type: EventType,
748
- query: Union[ActivityLogsQuery, ActivityLogsSearchQuery, ActivityLogsChatQuery],
664
+ query: Union[ActivityLogsQuery, ActivityLogsSearchQuery, ActivityLogsAskQuery],
749
665
  ) -> httpx.Response:
750
666
  if self.reader_session is None:
751
667
  raise Exception("KB not configured")
@@ -763,7 +679,7 @@ class AsyncNucliaDBClient(BaseNucliaDBClient):
763
679
  query: Union[
764
680
  DownloadActivityLogsQuery,
765
681
  DownloadActivityLogsSearchQuery,
766
- DownloadActivityLogsChatQuery,
682
+ DownloadActivityLogsAskQuery,
767
683
  ],
768
684
  download_format: DownloadFormat,
769
685
  ):
@@ -48,7 +48,7 @@ class GraphRelation(BaseModel):
48
48
  destination: GraphEntity
49
49
 
50
50
  def to_relation(self) -> Relation:
51
- return Relation.parse_obj(
51
+ return Relation.model_validate(
52
52
  {
53
53
  "from": self.source.to_dict(),
54
54
  "to": self.destination.to_dict(),
@@ -60,6 +60,6 @@ class GraphRelation(BaseModel):
60
60
 
61
61
  def get_relation(relation: Union[GraphRelation, dict]) -> GraphRelation:
62
62
  if isinstance(relation, dict):
63
- return GraphRelation.parse_obj(relation)
63
+ return GraphRelation.model_validate(relation)
64
64
  else:
65
65
  return relation
@@ -1,11 +1,11 @@
1
1
  from nuclia.decorators import kb
2
- from nuclia.lib.kb import LogType, NucliaDBClient, AsyncNucliaDBClient
2
+ from nuclia.lib.kb import NucliaDBClient, AsyncNucliaDBClient
3
3
  from nuclia_models.events.activity_logs import ( # type: ignore
4
4
  ActivityLogsQuery,
5
- ActivityLogsChatQuery,
5
+ ActivityLogsAskQuery,
6
6
  ActivityLogsSearchQuery,
7
7
  DownloadActivityLogsQuery,
8
- DownloadActivityLogsChatQuery,
8
+ DownloadActivityLogsAskQuery,
9
9
  DownloadActivityLogsSearchQuery,
10
10
  DownloadFormat,
11
11
  EventType,
@@ -25,23 +25,6 @@ WAIT_FOR_DOWNLOAD_TIMEOUT = 120
25
25
 
26
26
 
27
27
  class NucliaLogs:
28
- @kb
29
- def get(
30
- self, *args, type: Union[LogType, str], month: str, **kwargs
31
- ) -> list[list[str]]:
32
- """
33
- Download activity logs.
34
-
35
- :param type: VISITED, MODIFIED, DELETED, NEW, SEARCH, SUGGEST, INDEXED, CHAT, STARTED, STOPPED, PROCESSED
36
- :param month: YYYY-MM
37
- """
38
- if isinstance(type, str):
39
- type = LogType[type.upper()]
40
-
41
- ndb: NucliaDBClient = kwargs["ndb"]
42
- resp = ndb.logs(type=type, month=month)
43
- return resp
44
-
45
28
  @kb
46
29
  def query(
47
30
  self,
@@ -51,25 +34,25 @@ class NucliaLogs:
51
34
  dict,
52
35
  ActivityLogsQuery,
53
36
  ActivityLogsSearchQuery,
54
- ActivityLogsChatQuery,
37
+ ActivityLogsAskQuery,
55
38
  ],
56
39
  **kwargs,
57
40
  ) -> ActivityLogsOutput:
58
41
  """
59
42
  Query activity logs.
60
43
 
61
- :param type: VISITED, MODIFIED, DELETED, NEW, SEARCH, SUGGEST, INDEXED, CHAT, STARTED, STOPPED, PROCESSED
44
+ :param type: VISITED, MODIFIED, DELETED, NEW, SEARCH, SUGGEST, INDEXED, CHAT, ASK, STARTED, STOPPED, PROCESSED
62
45
  :param query: ActivityLogsQuery
63
46
  """
64
47
  _type = EventType[type.upper()] if isinstance(type, str) else type
65
48
  _query: Union[
66
49
  ActivityLogsQuery,
67
50
  ActivityLogsSearchQuery,
68
- ActivityLogsChatQuery,
51
+ ActivityLogsAskQuery,
69
52
  ]
70
53
  if isinstance(query, dict):
71
- if _type is EventType.CHAT:
72
- _query = ActivityLogsChatQuery.model_validate(query)
54
+ if _type in (EventType.ASK, EventType.CHAT): # TODO: deprecate chat event
55
+ _query = ActivityLogsAskQuery.model_validate(query)
73
56
  elif type is EventType.SEARCH:
74
57
  _query = ActivityLogsSearchQuery.model_validate(query)
75
58
  else:
@@ -95,7 +78,7 @@ class NucliaLogs:
95
78
  dict,
96
79
  DownloadActivityLogsQuery,
97
80
  DownloadActivityLogsSearchQuery,
98
- DownloadActivityLogsChatQuery,
81
+ DownloadActivityLogsAskQuery,
99
82
  ],
100
83
  download_format: Union[DownloadFormat, str],
101
84
  wait: bool = False,
@@ -104,8 +87,8 @@ class NucliaLogs:
104
87
  """
105
88
  Download activity logs.
106
89
 
107
- :param type: SEARCH, CHAT, VISITED, MODIFIED, DELETED, NEW, STARTED, STOPPED, PROCESSED
108
- :param type: NDJSON, CSV
90
+ :param type: VISITED, MODIFIED, DELETED, NEW, SEARCH, SUGGEST, INDEXED, CHAT, ASK, STARTED, STOPPED, PROCESSED
91
+ :param download_format: NDJSON, CSV
109
92
  :param query: DownloadActivityLogsQuery
110
93
  """
111
94
  _type = EventType[type.upper()] if isinstance(type, str) else type
@@ -119,11 +102,11 @@ class NucliaLogs:
119
102
  dict,
120
103
  DownloadActivityLogsQuery,
121
104
  DownloadActivityLogsSearchQuery,
122
- DownloadActivityLogsChatQuery,
105
+ DownloadActivityLogsAskQuery,
123
106
  ]
124
107
  if isinstance(query, dict):
125
- if _type is EventType.CHAT:
126
- _query = DownloadActivityLogsChatQuery.model_validate(query)
108
+ if _type in (EventType.ASK, EventType.CHAT): # TODO: deprecate chat event
109
+ _query = DownloadActivityLogsAskQuery.model_validate(query)
127
110
  elif type is EventType.SEARCH:
128
111
  _query = DownloadActivityLogsSearchQuery.model_validate(query)
129
112
  else:
@@ -168,23 +151,6 @@ class NucliaLogs:
168
151
 
169
152
 
170
153
  class AsyncNucliaLogs:
171
- @kb
172
- async def get(
173
- self, *args, type: Union[LogType, str], month: str, **kwargs
174
- ) -> list[list[str]]:
175
- """
176
- Download activity logs.
177
-
178
- :param type: VISITED, MODIFIED, DELETED, NEW, SEARCH, SUGGEST, INDEXED, CHAT, STARTED, STOPPED, PROCESSED
179
- :param month: YYYY-MM
180
- """
181
- if isinstance(type, str):
182
- type = LogType[type.upper()]
183
-
184
- ndb: AsyncNucliaDBClient = kwargs["ndb"]
185
- resp = await ndb.logs(type=type, month=month)
186
- return resp
187
-
188
154
  @kb
189
155
  async def query(
190
156
  self,
@@ -194,25 +160,25 @@ class AsyncNucliaLogs:
194
160
  dict,
195
161
  ActivityLogsQuery,
196
162
  ActivityLogsSearchQuery,
197
- ActivityLogsChatQuery,
163
+ ActivityLogsAskQuery,
198
164
  ],
199
165
  **kwargs,
200
166
  ) -> ActivityLogsOutput:
201
167
  """
202
168
  Query activity logs.
203
169
 
204
- :param type: VISITED, MODIFIED, DELETED, NEW, SEARCH, SUGGEST, INDEXED, CHAT, STARTED, STOPPED, PROCESSED
170
+ :param type: VISITED, MODIFIED, DELETED, NEW, SEARCH, SUGGEST, INDEXED, CHAT, ASK, STARTED, STOPPED, PROCESSED
205
171
  :param query: ActivityLogsQuery
206
172
  """
207
173
  _type = EventType[type.upper()] if isinstance(type, str) else type
208
174
  _query: Union[
209
175
  ActivityLogsQuery,
210
176
  ActivityLogsSearchQuery,
211
- ActivityLogsChatQuery,
177
+ ActivityLogsAskQuery,
212
178
  ]
213
179
  if isinstance(query, dict):
214
- if _type is EventType.CHAT:
215
- _query = ActivityLogsChatQuery.model_validate(query)
180
+ if _type in (EventType.ASK, EventType.CHAT): # TODO: deprecate chat event
181
+ _query = ActivityLogsAskQuery.model_validate(query)
216
182
  elif type is EventType.SEARCH:
217
183
  _query = ActivityLogsSearchQuery.model_validate(query)
218
184
  else:
@@ -238,7 +204,7 @@ class AsyncNucliaLogs:
238
204
  dict,
239
205
  DownloadActivityLogsQuery,
240
206
  DownloadActivityLogsSearchQuery,
241
- DownloadActivityLogsChatQuery,
207
+ DownloadActivityLogsAskQuery,
242
208
  ],
243
209
  download_format: Union[DownloadFormat, str],
244
210
  wait: bool = False,
@@ -247,8 +213,8 @@ class AsyncNucliaLogs:
247
213
  """
248
214
  Download activity logs.
249
215
 
250
- :param type: SEARCH, CHAT, VISITED, MODIFIED, DELETED, NEW, STARTED, STOPPED, PROCESSED
251
- :param type: NDJSON, CSV
216
+ :param type: VISITED, MODIFIED, DELETED, NEW, SEARCH, SUGGEST, INDEXED, CHAT, ASK, STARTED, STOPPED, PROCESSED
217
+ :param download_format: NDJSON, CSV
252
218
  :param query: DownloadActivityLogsQuery
253
219
  """
254
220
  _type = EventType[type.upper()] if isinstance(type, str) else type
@@ -262,11 +228,11 @@ class AsyncNucliaLogs:
262
228
  dict,
263
229
  DownloadActivityLogsQuery,
264
230
  DownloadActivityLogsSearchQuery,
265
- DownloadActivityLogsChatQuery,
231
+ DownloadActivityLogsAskQuery,
266
232
  ]
267
233
  if isinstance(query, dict):
268
- if _type is EventType.CHAT:
269
- _query = DownloadActivityLogsChatQuery.model_validate(query)
234
+ if _type in (EventType.ASK, EventType.CHAT): # TODO: deprecate chat event
235
+ _query = DownloadActivityLogsAskQuery.model_validate(query)
270
236
  elif type is EventType.SEARCH:
271
237
  _query = DownloadActivityLogsSearchQuery.model_validate(query)
272
238
  else:
@@ -20,6 +20,8 @@ from nucliadb_models.search import (
20
20
  SyncAskResponse,
21
21
  ChatModel,
22
22
  )
23
+ from nucliadb_models.graph.requests import GraphSearchRequest
24
+ from nucliadb_models.graph.responses import GraphSearchResponse
23
25
  from pydantic import ValidationError
24
26
 
25
27
  from nuclia.data import get_async_auth, get_auth
@@ -331,6 +333,33 @@ class NucliaSearch:
331
333
  result.tokens = ask_response.metadata.tokens.model_dump()
332
334
  return result
333
335
 
336
+ @kb
337
+ def graph(
338
+ self,
339
+ *,
340
+ query: Union[dict, GraphSearchRequest],
341
+ **kwargs,
342
+ ) -> GraphSearchResponse:
343
+ """
344
+ Perform a graph path query.
345
+
346
+ See https://docs.nuclia.dev/docs/api#tag/Search/operation/graph_search_knowledgebox_kb__kbid__graph_post
347
+ """
348
+ ndb: NucliaDBClient = kwargs["ndb"]
349
+
350
+ if isinstance(query, GraphSearchRequest):
351
+ req = query
352
+ elif isinstance(query, dict):
353
+ try:
354
+ req = GraphSearchRequest.model_validate(query)
355
+ except ValidationError:
356
+ logger.exception("Error validating query")
357
+ raise
358
+ else:
359
+ raise Exception(f"Invalid query: '{query}'")
360
+
361
+ return ndb.ndb.graph_search(req, kbid=ndb.kbid)
362
+
334
363
 
335
364
  class AsyncNucliaSearch:
336
365
  """
@@ -659,3 +688,30 @@ class AsyncNucliaSearch:
659
688
  else: # pragma: no cover
660
689
  warnings.warn(f"Unknown ask stream item type: {ask_response_item.type}")
661
690
  return result
691
+
692
+ @kb
693
+ async def graph(
694
+ self,
695
+ *,
696
+ query: Union[dict, GraphSearchRequest],
697
+ **kwargs,
698
+ ) -> GraphSearchResponse:
699
+ """
700
+ Perform a graph path query.
701
+
702
+ See https://docs.nuclia.dev/docs/api#tag/Search/operation/graph_search_knowledgebox_kb__kbid__graph_post
703
+ """
704
+ ndb: AsyncNucliaDBClient = kwargs["ndb"]
705
+
706
+ if isinstance(query, GraphSearchRequest):
707
+ req = query
708
+ elif isinstance(query, dict):
709
+ try:
710
+ req = GraphSearchRequest.model_validate(query)
711
+ except ValidationError:
712
+ logger.exception("Error validating query")
713
+ raise
714
+ else:
715
+ raise Exception(f"Invalid query: '{query}'")
716
+
717
+ return await ndb.ndb.graph_search(req, kbid=ndb.kbid)
@@ -49,8 +49,8 @@ def testing_config(testing_kb, testing_nua, testing_user):
49
49
  nuclia_auth._config.set_default_kb(TESTING_KBID)
50
50
  nuclia_auth._config.set_default_nua(client_id)
51
51
 
52
- yield
53
- reset_config_file()
52
+ yield
53
+ reset_config_file()
54
54
 
55
55
 
56
56
  @pytest.fixture(autouse=True)
@@ -2,7 +2,6 @@ import os
2
2
 
3
3
  from nucliadb_sdk.v2.exceptions import NotFoundError
4
4
 
5
- from nuclia.sdk.kb import NucliaKB
6
5
  from nuclia.sdk.resource import NucliaResource
7
6
  from nuclia.sdk.upload import NucliaUpload
8
7
 
@@ -10,7 +9,6 @@ path = f"{os.path.dirname(__file__)}/../assets/conversation.json"
10
9
 
11
10
 
12
11
  def test_conversation(testing_config):
13
- nkb = NucliaKB()
14
12
  nresource = NucliaResource()
15
13
  try:
16
14
  res = nresource.get(slug="conversation1")
@@ -18,9 +16,6 @@ def test_conversation(testing_config):
18
16
  except NotFoundError:
19
17
  pass
20
18
 
21
- res = nkb.list()
22
- assert len(res.resources) == 2
23
-
24
19
  nu = NucliaUpload()
25
20
  nu.conversation(path=path, slug="conversation1", field="c1")
26
21