nucliadb-agentic-api 1.0.0.post294__tar.gz → 1.0.0.post301__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 (49) hide show
  1. {nucliadb_agentic_api-1.0.0.post294 → nucliadb_agentic_api-1.0.0.post301}/PKG-INFO +1 -1
  2. nucliadb_agentic_api-1.0.0.post301/VERSION +1 -0
  3. nucliadb_agentic_api-1.0.0.post301/alembic/versions/b3781ec6a8ad_migration_of_aws_claude_4_1_opus.py +26 -0
  4. {nucliadb_agentic_api-1.0.0.post294 → nucliadb_agentic_api-1.0.0.post301}/pyproject.toml +1 -1
  5. {nucliadb_agentic_api-1.0.0.post294 → nucliadb_agentic_api-1.0.0.post301}/pyproject.toml.orig +1 -1
  6. {nucliadb_agentic_api-1.0.0.post294 → nucliadb_agentic_api-1.0.0.post301}/src/nucliadb_agentic_api/v1/mcp_nucliadb.py +147 -25
  7. nucliadb_agentic_api-1.0.0.post294/VERSION +0 -1
  8. {nucliadb_agentic_api-1.0.0.post294 → nucliadb_agentic_api-1.0.0.post301}/README.md +0 -0
  9. {nucliadb_agentic_api-1.0.0.post294 → nucliadb_agentic_api-1.0.0.post301}/alembic/README +0 -0
  10. {nucliadb_agentic_api-1.0.0.post294 → nucliadb_agentic_api-1.0.0.post301}/alembic/env.py +0 -0
  11. {nucliadb_agentic_api-1.0.0.post294 → nucliadb_agentic_api-1.0.0.post301}/alembic/script.py.mako +0 -0
  12. {nucliadb_agentic_api-1.0.0.post294 → nucliadb_agentic_api-1.0.0.post301}/alembic/versions/0fe6c7b5f33d_migrate_sonnet_and_gpt_5_6_models.py +0 -0
  13. {nucliadb_agentic_api-1.0.0.post294 → nucliadb_agentic_api-1.0.0.post301}/alembic/versions/7f3b2f92a8c1_add_agentic_config_table.py +0 -0
  14. {nucliadb_agentic_api-1.0.0.post294 → nucliadb_agentic_api-1.0.0.post301}/alembic/versions/b3c4d5e6f7a8_add_sources_table.py +0 -0
  15. {nucliadb_agentic_api-1.0.0.post294 → nucliadb_agentic_api-1.0.0.post301}/alembic/versions/c4d5e6f7a8b9_add_agentic_config_soft_deletion.py +0 -0
  16. {nucliadb_agentic_api-1.0.0.post294 → nucliadb_agentic_api-1.0.0.post301}/alembic.ini +0 -0
  17. {nucliadb_agentic_api-1.0.0.post294 → nucliadb_agentic_api-1.0.0.post301}/src/nucliadb_agentic_api/VERSION +0 -0
  18. {nucliadb_agentic_api-1.0.0.post294 → nucliadb_agentic_api-1.0.0.post301}/src/nucliadb_agentic_api/__init__.py +0 -0
  19. {nucliadb_agentic_api-1.0.0.post294 → nucliadb_agentic_api-1.0.0.post301}/src/nucliadb_agentic_api/agentic/__init__.py +0 -0
  20. {nucliadb_agentic_api-1.0.0.post294 → nucliadb_agentic_api-1.0.0.post301}/src/nucliadb_agentic_api/agentic/ask_handler.py +0 -0
  21. {nucliadb_agentic_api-1.0.0.post294 → nucliadb_agentic_api-1.0.0.post301}/src/nucliadb_agentic_api/agentic/ask_result.py +0 -0
  22. {nucliadb_agentic_api-1.0.0.post294 → nucliadb_agentic_api-1.0.0.post301}/src/nucliadb_agentic_api/agentic/ask_transform_to_interaction.py +0 -0
  23. {nucliadb_agentic_api-1.0.0.post294 → nucliadb_agentic_api-1.0.0.post301}/src/nucliadb_agentic_api/app.py +0 -0
  24. {nucliadb_agentic_api-1.0.0.post294 → nucliadb_agentic_api-1.0.0.post301}/src/nucliadb_agentic_api/commands.py +0 -0
  25. {nucliadb_agentic_api-1.0.0.post294 → nucliadb_agentic_api-1.0.0.post301}/src/nucliadb_agentic_api/db/__init__.py +0 -0
  26. {nucliadb_agentic_api-1.0.0.post294 → nucliadb_agentic_api-1.0.0.post301}/src/nucliadb_agentic_api/db/agentic_configs.py +0 -0
  27. {nucliadb_agentic_api-1.0.0.post294 → nucliadb_agentic_api-1.0.0.post301}/src/nucliadb_agentic_api/db/llm_migration_utils.py +0 -0
  28. {nucliadb_agentic_api-1.0.0.post294 → nucliadb_agentic_api-1.0.0.post301}/src/nucliadb_agentic_api/db/settings.py +0 -0
  29. {nucliadb_agentic_api-1.0.0.post294 → nucliadb_agentic_api-1.0.0.post301}/src/nucliadb_agentic_api/db/sources.py +0 -0
  30. {nucliadb_agentic_api-1.0.0.post294 → nucliadb_agentic_api-1.0.0.post301}/src/nucliadb_agentic_api/db/transform.py +0 -0
  31. {nucliadb_agentic_api-1.0.0.post294 → nucliadb_agentic_api-1.0.0.post301}/src/nucliadb_agentic_api/exceptions.py +0 -0
  32. {nucliadb_agentic_api-1.0.0.post294 → nucliadb_agentic_api-1.0.0.post301}/src/nucliadb_agentic_api/logging.py +0 -0
  33. {nucliadb_agentic_api-1.0.0.post294 → nucliadb_agentic_api-1.0.0.post301}/src/nucliadb_agentic_api/models.py +0 -0
  34. {nucliadb_agentic_api-1.0.0.post294 → nucliadb_agentic_api-1.0.0.post301}/src/nucliadb_agentic_api/py.typed +0 -0
  35. {nucliadb_agentic_api-1.0.0.post294 → nucliadb_agentic_api-1.0.0.post301}/src/nucliadb_agentic_api/server/__init__.py +0 -0
  36. {nucliadb_agentic_api-1.0.0.post294 → nucliadb_agentic_api-1.0.0.post301}/src/nucliadb_agentic_api/server/server.py +0 -0
  37. {nucliadb_agentic_api-1.0.0.post294 → nucliadb_agentic_api-1.0.0.post301}/src/nucliadb_agentic_api/server/session.py +0 -0
  38. {nucliadb_agentic_api-1.0.0.post294 → nucliadb_agentic_api-1.0.0.post301}/src/nucliadb_agentic_api/server/settings.py +0 -0
  39. {nucliadb_agentic_api-1.0.0.post294 → nucliadb_agentic_api-1.0.0.post301}/src/nucliadb_agentic_api/settings.py +0 -0
  40. {nucliadb_agentic_api-1.0.0.post294 → nucliadb_agentic_api-1.0.0.post301}/src/nucliadb_agentic_api/utils.py +0 -0
  41. {nucliadb_agentic_api-1.0.0.post294 → nucliadb_agentic_api-1.0.0.post301}/src/nucliadb_agentic_api/v1/__init__.py +0 -0
  42. {nucliadb_agentic_api-1.0.0.post294 → nucliadb_agentic_api-1.0.0.post301}/src/nucliadb_agentic_api/v1/agentic_configs.py +0 -0
  43. {nucliadb_agentic_api-1.0.0.post294 → nucliadb_agentic_api-1.0.0.post301}/src/nucliadb_agentic_api/v1/ask.py +0 -0
  44. {nucliadb_agentic_api-1.0.0.post294 → nucliadb_agentic_api-1.0.0.post301}/src/nucliadb_agentic_api/v1/ask_websocket.py +0 -0
  45. {nucliadb_agentic_api-1.0.0.post294 → nucliadb_agentic_api-1.0.0.post301}/src/nucliadb_agentic_api/v1/mcp_content.py +0 -0
  46. {nucliadb_agentic_api-1.0.0.post294 → nucliadb_agentic_api-1.0.0.post301}/src/nucliadb_agentic_api/v1/oauth.py +0 -0
  47. {nucliadb_agentic_api-1.0.0.post294 → nucliadb_agentic_api-1.0.0.post301}/src/nucliadb_agentic_api/v1/router.py +0 -0
  48. {nucliadb_agentic_api-1.0.0.post294 → nucliadb_agentic_api-1.0.0.post301}/src/nucliadb_agentic_api/v1/sources.py +0 -0
  49. {nucliadb_agentic_api-1.0.0.post294 → nucliadb_agentic_api-1.0.0.post301}/src/nucliadb_agentic_api/v1/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nucliadb_agentic_api
3
- Version: 1.0.0.post294
3
+ Version: 1.0.0.post301
4
4
  Summary: NucliaDB Agentic API for Hyperforge
5
5
  Author: AI Data Team
6
6
  Author-email: AI Data Team <learning@nuclia.com>
@@ -0,0 +1 @@
1
+ 1.0.0.post301
@@ -0,0 +1,26 @@
1
+ """migration of aws claude 4 1 opus
2
+
3
+ Revision ID: b3781ec6a8ad
4
+ Revises: 0fe6c7b5f33d
5
+ Create Date: 2026-10-01 13:04:16.465758
6
+
7
+ """
8
+
9
+ from typing import Sequence, Union
10
+
11
+ from alembic import op
12
+ from nucliadb_agentic_api.db.llm_migration_utils import migrate_llm_models
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision: str = "b3781ec6a8ad"
16
+ down_revision: Union[str, Sequence[str], None] = "0fe6c7b5f33d"
17
+ branch_labels: Union[str, Sequence[str], None] = None
18
+ depends_on: Union[str, Sequence[str], None] = None
19
+
20
+
21
+ def upgrade() -> None:
22
+ migrate_llm_models(op.get_bind(), {"aws-claude-4-1-opus": "aws-claude-4-8-opus"})
23
+
24
+
25
+ def downgrade() -> None:
26
+ pass
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "nucliadb_agentic_api"
7
- version = "1.0.0.post294"
7
+ version = "1.0.0.post301"
8
8
  description = "NucliaDB Agentic API for Hyperforge"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "nucliadb_agentic_api"
7
- version = "1.0.0.post294"
7
+ version = "1.0.0.post301"
8
8
  authors = [{ name = "AI Data Team", email = "learning@nuclia.com" }]
9
9
  description = "NucliaDB Agentic API for Hyperforge"
10
10
  readme = "README.md"
@@ -45,6 +45,7 @@ from hyperforge_nucliadb_agentic.ask.exceptions import (
45
45
  )
46
46
  from hyperforge_nucliadb_agentic.ask.model import (
47
47
  AskRequest,
48
+ ChatOptions,
48
49
  CitationsType,
49
50
  FieldExtensionStrategy,
50
51
  Filter,
@@ -52,6 +53,7 @@ from hyperforge_nucliadb_agentic.ask.model import (
52
53
  MetadataExtensionType,
53
54
  NeighbouringParagraphsStrategy,
54
55
  )
56
+ from hyperforge_nucliadb_agentic.ask.search import rpc
55
57
  from hyperforge_nucliadb_agentic.ask.search.ask import (
56
58
  AskResult,
57
59
  ask,
@@ -100,11 +102,11 @@ class SearchDocumentsArgs(BaseModel):
100
102
  )
101
103
  search_configuration: str | None = Field(
102
104
  None,
103
- description="Optional. The name of the Search Configuration to use. If not provided, default settings are used.",
105
+ description="Optional. The name of a saved Search Configuration (ask or find) whose settings (filters, retrieval and RAG strategies, prompts, model...) are applied to the search. If not provided, default settings are used.",
104
106
  )
105
107
  filters: list[Filter] | None = Field(
106
108
  None,
107
- description="Optional. A list of filter objects to narrow results. Each filter can include 'all' (AND), 'any' (OR), and/or 'not_all' (NOT AND) label paths like '/l/labelset/label'.",
109
+ description="Optional. A list of filter objects to narrow results. Each filter can include 'all' (AND), 'any' (OR), and/or 'not_all' (NOT AND) label paths like '/l/labelset/label'. When a Search Configuration is used, these filters are combined (AND) with its saved filters.",
108
110
  )
109
111
 
110
112
 
@@ -217,35 +219,155 @@ def _append_document_ids_to_answer(
217
219
  return f"{answer_text}\n\nDocument IDs:\n{document_ids_text}"
218
220
 
219
221
 
220
- async def _tool_search_documents(
221
- context: MCPContext,
222
- args: SearchDocumentsArgs,
223
- ) -> Sequence[TextContent | ImageContent | EmbeddedResource]:
224
- ask_request = AskRequest(query=args.query)
225
- ask_request.audit_metadata = {"source": "mcp_tool"}
226
-
227
- ask_request.rag_strategies = [
228
- FieldExtensionStrategy(fields=["a/title", "a/summary"]),
229
- NeighbouringParagraphsStrategy(before=5, after=5),
230
- MetadataExtensionStrategy(
231
- types=[
232
- MetadataExtensionType.CLASSIFICATION_LABELS,
233
- MetadataExtensionType.ORIGIN,
234
- ]
235
- ),
222
+ def _mcp_default_ask_parameters() -> dict[str, Any]:
223
+ """Parameters used by the MCP tool when the search configuration does not set them."""
224
+ return {
225
+ "rag_strategies": [
226
+ FieldExtensionStrategy(fields=["a/title", "a/summary"]),
227
+ NeighbouringParagraphsStrategy(before=5, after=5),
228
+ MetadataExtensionStrategy(
229
+ types=[
230
+ MetadataExtensionType.CLASSIFICATION_LABELS,
231
+ MetadataExtensionType.ORIGIN,
232
+ ]
233
+ ),
234
+ ],
235
+ "debug": True,
236
+ }
237
+
238
+
239
+ # Parameters the MCP tool response format depends on (answer text + footnote
240
+ # citations mapped to documents), so they always win over the search configuration.
241
+ _MCP_FORCED_ASK_PARAMETERS: dict[str, Any] = {
242
+ "generate_answer": True,
243
+ "citations": CitationsType.LLM_FOOTNOTES,
244
+ }
245
+
246
+
247
+ async def _load_search_configuration(context: MCPContext, name: str) -> dict[str, Any]:
248
+ """Fetch a saved Search Configuration and return the ask parameters it sets.
249
+
250
+ Both `ask` and `find` configurations are supported: `search_documents` runs an
251
+ ask, so `find` configurations contribute their retrieval parameters (filters,
252
+ top_k, features, reranker, ...) and parameters with no ask equivalent are ignored.
253
+ """
254
+ try:
255
+ search_config = await rpc.get_search_configuration(
256
+ context.ndb_reader, context.kbid, name=name
257
+ )
258
+ except Exception as e:
259
+ logger.exception(f"Error fetching search configuration '{name}'")
260
+ raise ResourceError(
261
+ f"Failed to load search configuration '{name}'"
262
+ + (f": {e}" if str(e) else "")
263
+ ) from e
264
+ if search_config is None:
265
+ raise ResourceError(f"Search configuration not found: '{name}'")
266
+
267
+ # AskConfig/FindConfig are built with `create_model`, so mypy can't see them as models
268
+ config_model: BaseModel = search_config.config
269
+ params = config_model.model_dump(mode="json", exclude_unset=True)
270
+ params.pop("query", None) # the tool query always wins
271
+ ignored = sorted(set(params) - set(AskRequest.model_fields))
272
+ if "features" in params:
273
+ # Find supports features ask does not (e.g. `graph`)
274
+ ask_features = {o.value for o in ChatOptions}
275
+ features = [f for f in params["features"] if f in ask_features]
276
+ if len(features) != len(params["features"]):
277
+ ignored.append(f"features={sorted(set(params['features']) - ask_features)}")
278
+ if features:
279
+ params["features"] = features
280
+ else:
281
+ params.pop("features") # nothing left, use the defaults
282
+ if ignored:
283
+ logger.info(
284
+ f"Search configuration '{name}' ({search_config.kind}) parameters not "
285
+ f"applicable to search_documents are ignored: {ignored}"
286
+ )
287
+ return {k: v for k, v in params.items() if k in AskRequest.model_fields}
288
+
289
+
290
+ def _combine_filters(
291
+ config_params: dict[str, Any], tool_filters: list[Filter], name: str
292
+ ) -> list[dict[str, Any]]:
293
+ """AND the tool filters with the search configuration filters.
294
+
295
+ The tool filters can only narrow results further: they never replace the
296
+ filters saved in the search configuration.
297
+ """
298
+ if config_params.get("filter_expression") is not None:
299
+ raise ResourceError(
300
+ f"Search configuration '{name}' defines a filter expression, which "
301
+ "cannot be combined with the 'filters' argument. Retry without 'filters'."
302
+ )
303
+ combined: list[dict[str, Any]] = [
304
+ {"all": [f]} if isinstance(f, str) else f
305
+ for f in config_params.get("filters") or []
236
306
  ]
307
+ combined.extend(f.model_dump(mode="json", exclude_unset=True) for f in tool_filters)
308
+ return combined
237
309
 
238
- if args.filters is not None:
239
- ask_request.filters = args.filters
240
310
 
311
+ async def _build_ask_request(
312
+ context: MCPContext, args: SearchDocumentsArgs
313
+ ) -> AskRequest:
314
+ """Build the ask request for `search_documents`.
315
+
316
+ Precedence: tool query/filters and MCP output requirements > saved search
317
+ configuration > MCP defaults.
318
+ """
319
+ params: dict[str, Any] = {}
241
320
  if args.search_configuration is not None:
242
- ask_request.search_configuration = args.search_configuration
321
+ params = await _load_search_configuration(context, args.search_configuration)
322
+ if params.get("answer_json_schema") is not None:
323
+ # The MCP response only renders text answers with footnote citations
324
+ raise ResourceError(
325
+ f"Search configuration '{args.search_configuration}' uses a structured "
326
+ "answer (answer_json_schema), which search_documents does not support"
327
+ )
328
+ params["search_configuration"] = args.search_configuration
329
+
330
+ for key, value in _mcp_default_ask_parameters().items():
331
+ params.setdefault(key, value)
332
+
333
+ if args.filters is not None:
334
+ if args.search_configuration is not None:
335
+ params["filters"] = _combine_filters(
336
+ params, args.filters, args.search_configuration
337
+ )
338
+ else:
339
+ params["filters"] = args.filters
340
+
341
+ params.update(_MCP_FORCED_ASK_PARAMETERS)
342
+ params["audit_metadata"] = {
343
+ **(params.get("audit_metadata") or {}),
344
+ "source": "mcp_tool",
345
+ }
346
+ params["query"] = args.query
347
+
348
+ try:
349
+ return AskRequest.model_validate(params)
350
+ except ValidationError as e:
351
+ error_details = "; ".join(
352
+ f"'{'.'.join(str(p) for p in err['loc'])}': {err['msg']}"
353
+ for err in e.errors()
354
+ )
355
+ raise ResourceError(
356
+ "Invalid search parameters"
357
+ + (
358
+ f" from search configuration '{args.search_configuration}'"
359
+ if args.search_configuration
360
+ else ""
361
+ )
362
+ + f": {error_details}"
363
+ ) from e
243
364
 
244
- # citations does not work with rag strategies yet
245
365
 
246
- ask_request.generate_answer = True
247
- ask_request.citations = CitationsType.LLM_FOOTNOTES
248
- ask_request.debug = True
366
+ async def _tool_search_documents(
367
+ context: MCPContext,
368
+ args: SearchDocumentsArgs,
369
+ ) -> Sequence[TextContent | ImageContent | EmbeddedResource]:
370
+ ask_request = await _build_ask_request(context, args)
249
371
  try:
250
372
  ask_result: AskResult = await ask(
251
373
  search_sdk=context.ndb_search,
@@ -1 +0,0 @@
1
- 1.0.0.post294