Skip to content

feat(nodes): add tool interface for vector DB operations#524

Open
charliegillet wants to merge 11 commits intorocketride-org:developfrom
charliegillet:feature/vectordb-tool-interface
Open

feat(nodes): add tool interface for vector DB operations#524
charliegillet wants to merge 11 commits intorocketride-org:developfrom
charliegillet:feature/vectordb-tool-interface

Conversation

@charliegillet
Copy link
Copy Markdown
Contributor

@charliegillet charliegillet commented Mar 30, 2026

Summary

  • Adds a new tool_vectordb node that provides a generic, ToolsBase-compatible tool interface for vector database operations (search, upsert, delete)
  • Works across multiple backends (Pinecone, ChromaDB, Qdrant) through the shared DocumentStoreBase interface
  • Exposes well-typed input schemas so LLM agents can discover and invoke vector DB tools with proper argument structure

Type

Feature

Why this feature fits this codebase

The RocketRide engine already has individual vector DB nodes for Pinecone, ChromaDB, and Qdrant, but none of them expose a unified tool interface that LLM agents can invoke through the standard ToolsBase protocol. Existing tool nodes like tool_http_request and tool_firecrawl follow a pattern of declaring classType: ["tool"] and capabilities: ["invoke"] in services.json, implementing IGlobal.beginGlobal() for driver setup, and delegating IInstance.invoke() to a driver that extends ToolsBase. This PR replicates that exact pattern for vector DB operations. The VectorDBDriver class in vectordb_driver.py extends ToolsBase and implements the three required hooks -- _tool_query(), _tool_validate(), and _tool_invoke() -- so the agent framework can discover tools like <server>.search via the same MCP tool descriptor protocol used by every other tool node. The IGlobal._create_store() method dynamically imports the backend's Store class from nodes.pinecone.pinecone, nodes.chroma.chroma, or nodes.qdrant.qdrant, reusing the existing DocumentStoreBase implementations rather than duplicating backend-specific code.

What changed

  • nodes/src/nodes/tool_vectordb/services.json -- Node registration with classType: ["tool"], capabilities: ["invoke"], configurable fields for backend, server name, enabled operations, topK, and score threshold
  • nodes/src/nodes/tool_vectordb/IGlobal.py -- Reads node config, resolves the backend Store class via dynamic import, and instantiates VectorDBDriver
  • nodes/src/nodes/tool_vectordb/IInstance.py -- Delegates invoke() calls to VectorDBDriver.handle_invoke()
  • nodes/src/nodes/tool_vectordb/vectordb_driver.py -- Implements ToolsBase with _tool_query (returns tool descriptors), _tool_validate (schema validation), and _tool_invoke (routes to _invoke_search, _invoke_upsert, _invoke_delete). Includes _normalize_input() helper for Pydantic models, JSON strings, and nested wrappers
  • nodes/src/nodes/tool_vectordb/__init__.py -- Package init exporting IGlobal and IInstance
  • packages/shared-ui/src/assets/nodes/vectordb.svg -- Node icon

Validation

  • Deploy the node and verify it registers with classType: ["tool"] and capabilities: ["invoke"]
  • Configure with a Pinecone backend and test tool.query returns tool descriptors with correct input schemas
  • Test tool.invoke for search returns ranked results with content, score, and metadata
  • Verify disabled operations (upsert/delete default to false) are excluded from tool.query results
  • Run ruff format --check and ruff check on changed files

How this could be extended

The same VectorDBDriver pattern could be extended to support additional backends (e.g., Weaviate, Milvus) by adding entries to the _BACKEND_MODULES map and ensuring the new backend implements DocumentStoreBase. The tool descriptors could also be enriched with collection-specific metadata for better agent discovery in multi-collection deployments.

Closes #442

#Hack-with-bay-2

Summary by CodeRabbit

  • New Features
    • Added a unified Vector DB Tool with semantic search, document upsert, and delete across Pinecone, Chroma, and Qdrant backends.
    • Search returns ranked results with metadata and supports filtering; upsert accepts batched documents with metadata; delete removes by object IDs.
    • Exposed configurable defaults for result count and score threshold and UI settings for enabling each operation.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 30, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new Vector DB tool: global state for backend connections, instance-level tool functions (search, upsert, delete) with input normalization and fallback search logic, package exports, and service registration/configuration for Pinecone, ChromaDB, and Qdrant.

Changes

Cohort / File(s) Summary
Core logic
nodes/src/nodes/tool_vectordb/IGlobal.py, nodes/src/nodes/tool_vectordb/IInstance.py
New global state class that initializes/closes backend Store, validates config, and dynamic backend creation; instance tool exposing search, upsert, delete with input normalization, score filtering, fallback semantic→keyword search, and metadata handling.
Package & service registration
nodes/src/nodes/tool_vectordb/__init__.py, nodes/src/nodes/tool_vectordb/services.json
Package exports IGlobal and IInstance; adds service manifest registering tool_vectordb:// with UI/config fields, preconfig defaults, and toggles for search/upsert/delete.

Sequence Diagram

sequenceDiagram
    participant Agent as Agent/Caller
    participant IInstance as IInstance Tool
    participant IGlobal as IGlobal (global state)
    participant Store as Backend Store

    Agent->>IInstance: invoke(search|upsert|delete, args)
    IInstance->>IInstance: _normalize_input(args)
    IInstance->>IGlobal: require store initialized
    IGlobal-->>IInstance: return Store or error
    alt search flow
        IInstance->>Store: searchSemantic(question, doc_filter)
        alt semantic success
            Store-->>IInstance: hits
        else semantic fails
            IInstance->>Store: searchKeyword(question, doc_filter)
            Store-->>IInstance: hits
        end
        IInstance->>IInstance: apply score_threshold, truncate to top_k
        IInstance-->>Agent: {results, total}
    else upsert flow
        IInstance->>IInstance: build Doc objects with metadata
        IInstance->>Store: addChunks(docs)
        Store-->>IInstance: ack
        IInstance-->>Agent: {success, count, skipped}
    else delete flow
        IInstance->>Store: remove(object_ids)
        Store-->>IInstance: deleted_count
        IInstance-->>Agent: {success, deleted_count}
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • jmaionchi
  • Rod-Christensen

Poem

🐰 I hopped through code with eager paws,

I stitched the stores and checked the laws,
Now queries bloom and docs find home,
Three tools to call wherever you roam,
A tiny rabbit sings—fetch, store, and roam 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 47.06% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding a tool interface for vector DB operations, which matches the primary objective of introducing a new node with search, upsert, and delete tool operations.
Linked Issues check ✅ Passed The PR comprehensively addresses #442 objectives: provides standardized tool abstraction for semantic search, enables consistent agent interaction across Pinecone/Qdrant/ChromaDB, exposes search/upsert/delete via ToolsBase/MCP protocol, and demonstrates extensibility for additional backends.
Out of Scope Changes check ✅ Passed All changes directly support the core objective of adding a vector DB tool interface; the IGlobal, IInstance, services.json, init.py, and SVG asset are all necessary components for the tool node implementation and are within scope.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@nodes/src/nodes/tool_vectordb/IGlobal.py`:
- Around line 103-113: The validateConfig method contains unnecessary extra
parentheses around expressions like cfg.get('backend') and
cfg.get('serverName'); in validateConfig (and mirroring beginGlobal) remove the
redundant parentheses so backend = str((cfg.get('backend') or
'')).strip().lower() becomes backend = str(cfg.get('backend') or
'').strip().lower() and similarly simplify server_name =
str((cfg.get('serverName') or '')).strip() to server_name =
str(cfg.get('serverName') or '').strip(), keeping the same use of
Config.getNodeConfig, _BACKEND_MODULES checks, and warning(...) calls.
- Around line 58-65: The assignments to variables like server_name, backend,
collection_description, enable_search, enable_upsert, enable_delete,
default_top_k, and score_threshold contain unnecessary extra parentheses around
the cfg.get(...) expressions; remove the redundant outer parentheses so each
line uses cfg.get(...) directly (e.g., server_name = str(cfg.get('serverName')
or 'vectordb').strip(), backend = str(cfg.get('backend') or
'pinecone').strip().lower(), etc.) while keeping the existing casts, defaults,
and method calls intact.
- Around line 93-101: The _create_store static method lacks a return type;
update its signature to return DocumentStoreBase and add the import for
DocumentStoreBase (from ai.common.store import DocumentStoreBase) at the top of
the file so the method signature becomes def _create_store(... ) ->
DocumentStoreBase: and remove the noqa ANN205 suppression; keep the dynamic
import/instantiation logic (module_path, importlib.import_module, mod.Store)
unchanged.
- Around line 73-75: The call to getConnConfig() inside IGlobal will raise an
AttributeError because getConnConfig is implemented only on IGlobalTransform;
fix by adding getConnConfig to IGlobal (implement the method) or make IGlobal
inherit from IGlobalTransform, or move getConnConfig up into IGlobalBase so both
IGlobal and IGlobalTransform share it; update references in IGlobal (where bag,
conn_config = self.getConnConfig(), and store = self._create_store(...)) to use
the chosen shared method name and signature so the call resolves at runtime.

In `@nodes/src/nodes/tool_vectordb/services.json`:
- Around line 11-15: Update the service description to match actual supported
backends by IGlobal.py: change the JSON "description" text in services.json to
list only pinecone, chroma, and qdrant (remove Weaviate and pgvector) so it
aligns with the _BACKEND_MODULES definition in IGlobal.py; ensure the wording
still conveys a generic vector DB tool but references the exact supported
backends (pinecone, chroma, qdrant) and/or say "and other supported backends
configured in _BACKEND_MODULES" for future-proofing.

In `@nodes/src/nodes/tool_vectordb/vectordb_driver.py`:
- Around line 309-315: The current loop over raw_docs silently skips invalid
entries (variables: raw, content, object_id) and only counts successful
documents; modify the surrounding function that processes raw_docs to collect
and return metadata about skipped documents (e.g., a skipped count and/or list
of skipped object_ids with reasons like "missing content" or "missing
object_id"), augment the return value or result object to include this skipped
info alongside the successful count, and ensure callers of this function (the
code using the function in vectordb_driver.py) are updated to handle the new
skipped field for observability and logging.
- Around line 265-274: The fallback from semantic to keyword search in
vectordb_driver (within the try/except around self._store.searchSemantic and the
subsequent self._store.searchKeyword call) is an expected resilience behavior,
so change the current warning(f'tool_vectordb: semantic search failed ({e}),
trying keyword search') to a debug-level log (e.g., logger.debug or debug(...))
including the exception details and context; update the log call used in this
module (replace warning(...) with the module's debug logger) so expected
fallbacks don’t emit warnings while still capturing the exception for
troubleshooting.
- Around line 367-378: The JSON parse currently swallows all exceptions leading
to a vague later warning; in the JSON parse block in vectordb_driver.py (the
code handling "if isinstance(input_obj, str):" before the type-check), catch the
exception as e and emit a warning that includes the exception message and a
truncated version of the original input string so you can debug parse errors
(e.g., "tool_vectordb: failed to parse JSON: {e}; input: {input_snippet}"), then
continue the existing flow so the function still returns {} for non-dict inputs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: f29473b3-4297-40a7-ac99-bf41eff9fb20

📥 Commits

Reviewing files that changed from the base of the PR and between b6b2dc1 and a9f03c7.

📒 Files selected for processing (5)
  • nodes/src/nodes/tool_vectordb/IGlobal.py
  • nodes/src/nodes/tool_vectordb/IInstance.py
  • nodes/src/nodes/tool_vectordb/__init__.py
  • nodes/src/nodes/tool_vectordb/services.json
  • nodes/src/nodes/tool_vectordb/vectordb_driver.py

@github-actions github-actions bot added the module:ui Chat UI and Dropper UI label Mar 30, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (3)
nodes/src/nodes/tool_vectordb/IGlobal.py (1)

93-101: 🧹 Nitpick | 🔵 Trivial

Consider adding the return type annotation.

The # noqa: ANN205 suppression is still present. Adding the return type would improve type safety without requiring significant changes.

📝 Proposed fix

Add the import at the top of the file:

from ai.common.store import DocumentStoreBase

Then update the method signature:

     `@staticmethod`
-    def _create_store(backend: str, conn_config: dict, bag: dict):  # noqa: ANN205
+    def _create_store(backend: str, conn_config: dict, bag: dict) -> DocumentStoreBase:
         """Dynamically import and instantiate the backend Store class."""
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nodes/src/nodes/tool_vectordb/IGlobal.py` around lines 93 - 101, The
_create_store static method lacks a return type annotation; import
DocumentStoreBase from ai.common.store and update the method signature of
_create_store to return DocumentStoreBase (e.g., def _create_store(... ) ->
DocumentStoreBase:), so callers and type checkers know the concrete store
interface provided by Store class instantiation; keep the body as-is but you can
safely remove the "# noqa: ANN205" suppression after adding the annotation.
nodes/src/nodes/tool_vectordb/vectordb_driver.py (2)

303-335: 🧹 Nitpick | 🔵 Trivial

Upsert silently skips invalid documents without feedback.

Invalid documents (non-dict, missing content/object_id) are silently skipped. The agent receives count of successful documents but no indication of which documents failed validation or why. This could make debugging difficult for agents.

♻️ Proposed improvement for observability
     def _invoke_upsert(self, args: Dict[str, Any]) -> Dict[str, Any]:
         raw_docs = args.get('documents', [])
         if not isinstance(raw_docs, list) or not raw_docs:
             raise ValueError('upsert requires a non-empty "documents" array')

         docs: List[Doc] = []
+        skipped: List[Dict[str, Any]] = []
         for raw in raw_docs:
             if not isinstance(raw, dict):
+                skipped.append({'reason': 'not a dict'})
                 continue
             content = str(raw.get('content', '')).strip()
             object_id = str(raw.get('object_id', '')).strip()
             if not content or not object_id:
+                skipped.append({'object_id': object_id or None, 'reason': 'missing content or object_id'})
                 continue
             # ... rest of processing ...

         self._store.addChunks(docs)
-        return {'success': True, 'count': len(docs)}
+        return {'success': True, 'count': len(docs), 'skipped': len(skipped)}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nodes/src/nodes/tool_vectordb/vectordb_driver.py` around lines 303 - 335, In
_invoke_upsert, don't silently skip invalid entries; instead validate each
raw_docs entry (using the existing checks for dict, non-empty content and
object_id), collect failure records (e.g., index and reason like "not a dict",
"missing content", "missing object_id") while building Doc/DocMetadata objects,
call self._store.addChunks on the valid docs as now, and include the failure
details in the returned dict (e.g., {'success': True, 'count': n_valid,
'failed': [ {index: i, reason: "..."} ]}); optionally also emit structured logs
via any existing logger on self (e.g., self._logger) with the same failure list
to aid debugging.

265-274: 🧹 Nitpick | 🔵 Trivial

Consider using debug-level logging for expected fallback path.

The semantic-to-keyword search fallback is an expected resilience pattern. Logging at warning level may create noise in production when embeddings aren't configured.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nodes/src/nodes/tool_vectordb/vectordb_driver.py` around lines 265 - 274, The
fallback from semantic to keyword search in vectordb_driver.py is an expected
behavior, so replace the current warning-level log call inside the except for
self._store.searchSemantic with a debug-level log; update the call that
currently uses warning(f'tool_vectordb: semantic search failed ({e}), trying
keyword search') to use your module logger.debug (or equivalent) and include the
exception details and context, leaving the subsequent try/except for
self._store.searchKeyword intact so failures still raise RuntimeError.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@nodes/src/nodes/tool_vectordb/vectordb_driver.py`:
- Around line 276-301: The code currently sets 'total' to len(results) after
applying score filtering but returns results[:top_k], causing 'total' to
potentially not match the number of returned items; in vectordb_driver.py update
the return to either set 'total' = len(results[:top_k]) when you want total to
represent the number of items returned, or (preferred for clarity) expose both
counts by returning {'results': results[:top_k], 'total_filtered': len(results),
'total_returned': len(results[:top_k])} so callers can see the pre-slice count
(total_filtered) and the actual returned count (total_returned); adjust any
callers of this function if you change field names.

---

Duplicate comments:
In `@nodes/src/nodes/tool_vectordb/IGlobal.py`:
- Around line 93-101: The _create_store static method lacks a return type
annotation; import DocumentStoreBase from ai.common.store and update the method
signature of _create_store to return DocumentStoreBase (e.g., def
_create_store(... ) -> DocumentStoreBase:), so callers and type checkers know
the concrete store interface provided by Store class instantiation; keep the
body as-is but you can safely remove the "# noqa: ANN205" suppression after
adding the annotation.

In `@nodes/src/nodes/tool_vectordb/vectordb_driver.py`:
- Around line 303-335: In _invoke_upsert, don't silently skip invalid entries;
instead validate each raw_docs entry (using the existing checks for dict,
non-empty content and object_id), collect failure records (e.g., index and
reason like "not a dict", "missing content", "missing object_id") while building
Doc/DocMetadata objects, call self._store.addChunks on the valid docs as now,
and include the failure details in the returned dict (e.g., {'success': True,
'count': n_valid, 'failed': [ {index: i, reason: "..."} ]}); optionally also
emit structured logs via any existing logger on self (e.g., self._logger) with
the same failure list to aid debugging.
- Around line 265-274: The fallback from semantic to keyword search in
vectordb_driver.py is an expected behavior, so replace the current warning-level
log call inside the except for self._store.searchSemantic with a debug-level
log; update the call that currently uses warning(f'tool_vectordb: semantic
search failed ({e}), trying keyword search') to use your module logger.debug (or
equivalent) and include the exception details and context, leaving the
subsequent try/except for self._store.searchKeyword intact so failures still
raise RuntimeError.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 336c2f3b-8dec-4af3-85ba-d1940c7cd0f8

📥 Commits

Reviewing files that changed from the base of the PR and between a9f03c7 and ef3aced.

⛔ Files ignored due to path filters (1)
  • packages/shared-ui/src/assets/nodes/vectordb.svg is excluded by !**/*.svg
📒 Files selected for processing (3)
  • nodes/src/nodes/tool_vectordb/IGlobal.py
  • nodes/src/nodes/tool_vectordb/services.json
  • nodes/src/nodes/tool_vectordb/vectordb_driver.py

Copy link
Copy Markdown
Collaborator

@asclearuc asclearuc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for this contribution — the ToolsBase pattern is correctly implemented,
the tool descriptors are well-formed, _normalize_input handles all the edge cases
cleanly, and defaulting upsert/delete to disabled is the right security posture.

A few issues need to be resolved before merge

charliegillet added a commit to charliegillet/rocketride-server that referenced this pull request Apr 3, 2026
…ctordb

- Remove invalid topK param from QuestionText constructor; use
  doc_filter.limit to control result count instead
- Add embedding warning in upsert and document the limitation in the
  tool description
- Read backend connection config from bag first, fall back to own
  connConfig
- Distinguish disabled-tool vs unknown-tool error messages

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@charliegillet
Copy link
Copy Markdown
Contributor Author

Addressed all review feedback:

  • QuestionText topK field: Removed topK param from constructor — result count is now controlled via doc_filter.limit
  • Upsert without embeddings: Updated tool description to state embedding requirement; added runtime warning when docs lack embeddings
  • Connection config from bag: IGlobal.beginGlobal() now resolves connection config from the bag first (upstream vector DB node) before falling back to the tool node's own connConfig
  • Disabled tool error message: Error now reads Tool "X" is disabled in this node's configuration for clarity

Thanks for the thorough review!

Copy link
Copy Markdown
Collaborator

@asclearuc asclearuc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the contributionю

Needs to be rebased on #599's pattern before this can merge.

PR #599 (da19f6b) retired the DriverClass(ToolsBase) pattern for all tool nodes and replaced it with @tool_function decorators directly on IInstance. VectorDBDriver, vectordb_driver.py, and the IInstance.invoke() → driver.handle_invoke() chain follow the deprecated approach. See tool_firecrawl/IInstance.py or tool_http_request/IInstance.py for the current reference.

charliegillet added a commit to charliegillet/rocketride-server that referenced this pull request Apr 6, 2026
…ctordb

- Remove invalid topK param from QuestionText constructor; use
  doc_filter.limit to control result count instead
- Add embedding warning in upsert and document the limitation in the
  tool description
- Read backend connection config from bag first, fall back to own
  connConfig
- Distinguish disabled-tool vs unknown-tool error messages

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@charliegillet charliegillet force-pushed the feature/vectordb-tool-interface branch from f496765 to 074fa70 Compare April 6, 2026 18:14
@charliegillet
Copy link
Copy Markdown
Contributor Author

Rebased on develop and migrated to the @tool_function decorator pattern from PR #599. Removed the VectorDBDriver(ToolsBase) approach and moved all tool logic to @tool_function methods on IInstance, following tool_firecrawl as reference. Ready for re-review. Thanks for the guidance!

@charliegillet
Copy link
Copy Markdown
Contributor Author

Addressing @asclearuc's inline feedback:

  1. QuestionText topK — Good catch. Removed topK from QuestionText construction; top_k is now passed via doc_filter.limit instead.

  2. Embeddings in upsert — Added a warning when upserting without pre-computed embeddings. The tool description also documents this requirement (upstream embedding node or backend-side embedding).

  3. connConfig resolution — IGlobal tries to resolve connConfig from the bag first (backend-specific or generic vectordb_connConfig) before falling back to the tool node's own connConfig.

  4. Disabled tool messaging — With @tool_function, tool enablement is handled by the decorator framework — disabled tools won't be exposed to agents at all, making the unclear error scenario less likely.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@nodes/src/nodes/tool_vectordb/IInstance.py`:
- Around line 162-182: The upsert path in IInstance.py currently only accepts
content, object_id, and metadata and never attaches embeddings to the Doc it
constructs (affecting the upsert handler and Doc creation logic around the
upsert method and its Doc builder), so either add an "embeddings" (or
"embedding") array field to the input schema (mirroring
'documents[].items.properties') and set that vector on the created Doc instance,
or detect backends that require caller-supplied vectors and raise a clear
validation error during request handling; update the schema
(documents.items.properties) to include embeddings:
{'type':'array','items':{'type':'number'}} and ensure the code that builds the
Doc (the upsert function / Doc constructor call) assigns doc.embedding =
provided_embedding, otherwise throw a fast-fail error when
backend_requires_external_embeddings is true.
- Around line 62-66: The schema advertises arbitrary metadata via 'filter' and
'documents[*].metadata' but the implementation only persists/filters keys
objectId, nodeId, parent, chunkId; update the code so metadata is
round-trippable and filterable end-to-end: modify the document
serialization/deserialization paths that currently strip metadata (the code
handling documents' metadata / read/write payloads at the sections corresponding
to 62-66, 105-117, 176-180, 216-223) to preserve the full metadata dict
(copy/save every key/value), and update the filter-matching logic to iterate all
provided filter keys and match against the stored metadata rather than only the
four hardcoded keys; alternatively, if you prefer the narrower approach, change
the JSON schemas for 'filter' and 'documents[*].metadata' to list only the
supported keys (objectId, nodeId, parent, chunkId) so the contract matches the
implementation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: e1019b0c-fb7b-4776-ae90-56f673ea31ff

📥 Commits

Reviewing files that changed from the base of the PR and between f496765 and 074fa70.

⛔ Files ignored due to path filters (1)
  • packages/shared-ui/src/assets/nodes/vectordb.svg is excluded by !**/*.svg
📒 Files selected for processing (4)
  • nodes/src/nodes/tool_vectordb/IGlobal.py
  • nodes/src/nodes/tool_vectordb/IInstance.py
  • nodes/src/nodes/tool_vectordb/__init__.py
  • nodes/src/nodes/tool_vectordb/services.json

Comment on lines +62 to +66
'filter': {
'type': 'object',
'description': 'Optional metadata filter. Keys are metadata field names, values are the required values. Example: {"nodeId": "my-node", "parent": "/docs"}',
'additionalProperties': True,
},
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

The metadata contract is broader than the implementation.

filter is documented as arbitrary metadata keys, and documents[*].metadata is documented as arbitrary key/value data, but the code only reads/writes objectId, nodeId, parent, and chunkId. Every other metadata field is silently discarded, so agents cannot round-trip or filter on the metadata this tool advertises. Either narrow the schemas to the supported keys or persist/filter arbitrary metadata end to end.

Also applies to: 105-117, 176-180, 216-223

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nodes/src/nodes/tool_vectordb/IInstance.py` around lines 62 - 66, The schema
advertises arbitrary metadata via 'filter' and 'documents[*].metadata' but the
implementation only persists/filters keys objectId, nodeId, parent, chunkId;
update the code so metadata is round-trippable and filterable end-to-end: modify
the document serialization/deserialization paths that currently strip metadata
(the code handling documents' metadata / read/write payloads at the sections
corresponding to 62-66, 105-117, 176-180, 216-223) to preserve the full metadata
dict (copy/save every key/value), and update the filter-matching logic to
iterate all provided filter keys and match against the stored metadata rather
than only the four hardcoded keys; alternatively, if you prefer the narrower
approach, change the JSON schemas for 'filter' and 'documents[*].metadata' to
list only the supported keys (objectId, nodeId, parent, chunkId) so the contract
matches the implementation.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — narrowed the metadata schema from additionalProperties: true to explicitly listing only the recognized fields (nodeId, parent, chunkId) with additionalProperties: false. The description also now states that unrecognized keys are not accepted.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rate Limit Exceeded

@charliegillet have exceeded the limit for the number of chat messages per hour. Please wait 1 minutes and 17 seconds before sending another message.

Comment on lines +162 to +182
'documents': {
'type': 'array',
'description': 'Array of documents to upsert.',
'items': {
'type': 'object',
'properties': {
'content': {
'type': 'string',
'description': 'The text content of the document.',
},
'object_id': {
'type': 'string',
'description': 'Unique identifier for the document. Used for deduplication.',
},
'metadata': {
'type': 'object',
'description': 'Optional metadata key-value pairs to store with the document.',
'additionalProperties': True,
},
},
'required': ['content', 'object_id'],
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

upsert cannot carry embeddings today.

The schema only accepts content, object_id, and metadata, and this method never sets an embedding on the Doc it builds. Line 233 is therefore warning about a field the tool cannot actually accept, and backends that require caller-supplied vectors can never make these inserts searchable. Either accept embeddings in the tool input and attach them to Doc, or fail fast when the backend cannot embed on ingest.

Also applies to: 194-195, 224-235

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nodes/src/nodes/tool_vectordb/IInstance.py` around lines 162 - 182, The
upsert path in IInstance.py currently only accepts content, object_id, and
metadata and never attaches embeddings to the Doc it constructs (affecting the
upsert handler and Doc creation logic around the upsert method and its Doc
builder), so either add an "embeddings" (or "embedding") array field to the
input schema (mirroring 'documents[].items.properties') and set that vector on
the created Doc instance, or detect backends that require caller-supplied
vectors and raise a clear validation error during request handling; update the
schema (documents.items.properties) to include embeddings:
{'type':'array','items':{'type':'number'}} and ensure the code that builds the
Doc (the upsert function / Doc constructor call) assigns doc.embedding =
provided_embedding, otherwise throw a fast-fail error when
backend_requires_external_embeddings is true.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged — the schema intentionally does not accept an embedding field in the upsert input. Embeddings should be computed by the backend on ingest or by an upstream embedding node in the pipeline. The tool description and a runtime warning both make this explicit.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rate Limit Exceeded

@charliegillet have exceeded the limit for the number of chat messages per hour. Please wait 1 minutes and 15 seconds before sending another message.

@nihalnihalani
Copy link
Copy Markdown
Contributor

Senior Review — PR #524: feat(nodes): add tool interface for vector DB

@charliegillet, solid approach here with the multi-backend architecture. Supporting multiple vector DB backends through a common interface is the right design.

There are 8 CodeRabbit items that asclearuc has delegated for you to address. Here are the key ones to prioritize:

Priority items

  1. BaseModel handling — Ensure Pydantic BaseModel subclasses are used correctly for configuration and schema validation. Check that field defaults, validators, and serialization align with the patterns used elsewhere in the codebase.

  2. requirements.txt — Review the dependencies list. Make sure versions are pinned appropriately and that only necessary packages are included. If any backend-specific dependencies are optional, they should be handled as extras or with graceful import fallbacks.

  3. Search result size limits — Add a configurable upper bound on the number of search results returned. Without this, a broad query could return an unbounded result set, causing memory and performance issues downstream in a flow.

  4. Test config block — The test configuration needs to be structured so tests can run against a mock/local backend without requiring live credentials or external services. Verify the test fixtures and config follow the project's testing patterns.

Address the full set of CodeRabbit items and this will be in good shape for a follow-up review.

@nihalnihalani
Copy link
Copy Markdown
Contributor

🚀 Merge Request

Good multi-backend approach (Pinecone, ChromaDB, Qdrant).

Before merge (CodeRabbit items per asclearuc):

  • Handle BaseModel via .model_dump() not dict()
  • Add requirements.txt
  • Add size limits to search results
  • Add test config block to services.json
  • Validate required fields in _invoke_upsert
  • Fix server_name fallback consistency

@asclearuc delegated to CodeRabbit — addressing all 8 items would clear this for merge.

charliegillet and others added 9 commits April 8, 2026 00:08
Introduces a generic tool_vectordb node that exposes vector database
operations (search, upsert, delete) as ToolsBase-compatible tools that
LLM agents can invoke. The node works with multiple vector DB backends
(Pinecone, ChromaDB, Qdrant) through the shared DocumentStoreBase
interface, providing a unified tool abstraction for semantic search.

Closes rocketride-org#442

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ig()

IGlobalBase does not have getConnConfig() — that method is on
IGlobalTransform. Use self.glb.connConfig which is the standard
pattern for tool nodes (matches tool_http_request, tool_firecrawl).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove Weaviate and pgvector from services.json description since
_BACKEND_MODULES only supports pinecone, chroma, and qdrant.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Database cylinder icon matching the 24px viewBox, fill="currentColor"
pattern used by other node icons (e.g. http.svg).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
_normalize_input previously swallowed JSON parse errors silently,
making it hard to diagnose bad tool inputs. Now logs a warning with
the parse error before returning an empty dict.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Clean up double-wrapped parens like str((cfg.get('x') or 'y')) to
the simpler str(cfg.get('x') or 'y').

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ctordb

- Remove invalid topK param from QuestionText constructor; use
  doc_filter.limit to control result count instead
- Add embedding warning in upsert and document the limitation in the
  tool description
- Read backend connection config from bag first, fall back to own
  connConfig
- Distinguish disabled-tool vs unknown-tool error messages

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
rocketride-org#599

Remove VectorDBDriver(ToolsBase) and move search/upsert/delete tool logic
to @tool_function decorated methods on IInstance. Store config and backend
connection directly on IGlobal instead of through a driver object.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add return type annotation to IGlobal._create_store()
- Downgrade semantic-to-keyword fallback log from warning to debug
- Fix total count to match actual returned results after top_k slice
- Return skipped document count from upsert operation
- Narrow metadata schema to document only recognized fields (nodeId,
  parent, chunkId) instead of additionalProperties: true

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@charliegillet charliegillet force-pushed the feature/vectordb-tool-interface branch from 074fa70 to 255cc85 Compare April 8, 2026 07:10
@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 8, 2026

No description provided.

@charliegillet
Copy link
Copy Markdown
Contributor Author

@asclearuc Thanks for the thorough reviews! All feedback has been addressed:

Re: second review (rebase on #599):
The branch has been rebased on develop and fully migrated from the DriverClass(ToolsBase) + vectordb_driver.py pattern to @tool_function decorators directly on IInstance, matching the pattern in tool_firecrawl and tool_http_request. The vectordb_driver.py file has been removed entirely — all tool logic now lives in IInstance.py.

Summary of changes in this push:

  • Rebased on current develop (clean rebase, no conflicts)
  • Migrated to @tool_function decorator pattern on IInstance (already done in prior commits, now rebased)
  • Added -> DocumentStoreBase return type annotation to IGlobal._create_store()
  • Downgraded semantic-to-keyword fallback log from warning() to debug()
  • Fixed total count to match actual returned results length after threshold filtering + top_k slice
  • Upsert now returns skipped count for invalid documents
  • Narrowed metadata schema to only recognized fields (nodeId, parent, chunkId) with additionalProperties: false
  • All CodeRabbit comments addressed and replied to inline

ruff check and ruff format --check both pass clean on all changed files.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
nodes/src/nodes/tool_vectordb/IInstance.py (2)

62-66: ⚠️ Potential issue | 🟠 Major

Narrow filter to the keys you actually honor.

The schema accepts arbitrary metadata keys, but Lines 108-117 only translate objectId, nodeId, and parent into DocFilter. Any other key, including chunkId, is silently ignored, so a valid-looking filter can still return unfiltered results. Either enumerate the supported keys in the schema or map every accepted key end to end.

Also applies to: 105-117

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nodes/src/nodes/tool_vectordb/IInstance.py` around lines 62 - 66, The JSON
schema for the 'filter' field currently allows arbitrary keys but the
translation code only handles specific keys when building a DocFilter (the keys
mapped are objectId, nodeId, parent); either restrict the schema to enumerate
only the keys you actually support (set 'additionalProperties' to false and add
a 'properties' entry listing supported keys like objectId, nodeId, parent) or
extend the translation logic that constructs the DocFilter to handle every key
the schema permits (e.g., add mapping for chunkId and any other accepted
metadata keys) so the schema and the DocFilter construction in IInstance.py stay
consistent.

163-202: ⚠️ Potential issue | 🟠 Major

upsert still has no way to accept embeddings.

Every Doc built at Lines 232-235 only gets page_content and metadata, so the warning at Line 245 fires on every successful call. Backends that require caller-supplied vectors will ingest documents that this tool can never make searchable. Add an embedding field to documents[*] and assign it to Doc, or fail fast when the selected backend cannot embed on ingest.

Also applies to: 224-246

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@nodes/src/nodes/tool_vectordb/IInstance.py`:
- Around line 296-317: The current _normalize_input logic only unwraps
dict-valued 'input' but skips cases like {'input': '{"query":"x"}'} or {'input':
SomeModel(...)}, so re-run the same normalization on input_obj['input'] before
merging extras: detect when 'input' key exists, take inner = input_obj['input'],
call the same normalization sequence used earlier (model_dump()/dict conversion
and json.loads parsing) on inner to yield a dict, then merge extras = {k:v for
k,v in input_obj.items() if k != 'input'} and set input_obj =
{**inner_normalized, **extras}; ensure you still validate resulting input_obj is
a dict and preserve existing warning/return behavior in _normalize_input.

---

Duplicate comments:
In `@nodes/src/nodes/tool_vectordb/IInstance.py`:
- Around line 62-66: The JSON schema for the 'filter' field currently allows
arbitrary keys but the translation code only handles specific keys when building
a DocFilter (the keys mapped are objectId, nodeId, parent); either restrict the
schema to enumerate only the keys you actually support (set
'additionalProperties' to false and add a 'properties' entry listing supported
keys like objectId, nodeId, parent) or extend the translation logic that
constructs the DocFilter to handle every key the schema permits (e.g., add
mapping for chunkId and any other accepted metadata keys) so the schema and the
DocFilter construction in IInstance.py stay consistent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 42923ac3-1a30-49de-bd9d-d9db31c6eae3

📥 Commits

Reviewing files that changed from the base of the PR and between 074fa70 and 255cc85.

⛔ Files ignored due to path filters (1)
  • packages/shared-ui/src/assets/nodes/vectordb.svg is excluded by !**/*.svg
📒 Files selected for processing (4)
  • nodes/src/nodes/tool_vectordb/IGlobal.py
  • nodes/src/nodes/tool_vectordb/IInstance.py
  • nodes/src/nodes/tool_vectordb/__init__.py
  • nodes/src/nodes/tool_vectordb/services.json

Comment on lines +296 to +317
if hasattr(input_obj, 'model_dump') and callable(getattr(input_obj, 'model_dump')):
input_obj = input_obj.model_dump()
elif hasattr(input_obj, 'dict') and callable(getattr(input_obj, 'dict')):
input_obj = input_obj.dict()

if isinstance(input_obj, str):
try:
parsed = json.loads(input_obj)
if isinstance(parsed, dict):
input_obj = parsed
except (json.JSONDecodeError, ValueError) as exc:
warning(f'tool_vectordb: malformed JSON input, returning empty dict: {exc}')
return {}

if not isinstance(input_obj, dict):
warning(f'tool_vectordb: unexpected input type {type(input_obj).__name__}')
return {}

if 'input' in input_obj and isinstance(input_obj['input'], dict):
inner = input_obj['input']
extras = {k: v for k, v in input_obj.items() if k != 'input'}
input_obj = {**inner, **extras}
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Normalize wrapped payloads before unwrapping them.

Only a dict-valued input is unwrapped here. {'input': '{"query": "x"}'} and {'input': SomeModel(...)} skip the top-level json.loads() / model_dump() path, so the tool never sees query, documents, or object_ids. Re-run _normalize_input() on input_obj['input'] before merging extras.

🛠️ Possible fix
-    if 'input' in input_obj and isinstance(input_obj['input'], dict):
-        inner = input_obj['input']
-        extras = {k: v for k, v in input_obj.items() if k != 'input'}
-        input_obj = {**inner, **extras}
+    if 'input' in input_obj:
+        inner = _normalize_input(input_obj['input'])
+        extras = {k: v for k, v in input_obj.items() if k != 'input'}
+        input_obj = {**inner, **extras}
🧰 Tools
🪛 Ruff (0.15.9)

[warning] 296-296: Do not call getattr with a constant attribute value. It is not any safer than normal property access.

Replace getattr with attribute access

(B009)


[warning] 298-298: Do not call getattr with a constant attribute value. It is not any safer than normal property access.

Replace getattr with attribute access

(B009)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nodes/src/nodes/tool_vectordb/IInstance.py` around lines 296 - 317, The
current _normalize_input logic only unwraps dict-valued 'input' but skips cases
like {'input': '{"query":"x"}'} or {'input': SomeModel(...)}, so re-run the same
normalization on input_obj['input'] before merging extras: detect when 'input'
key exists, take inner = input_obj['input'], call the same normalization
sequence used earlier (model_dump()/dict conversion and json.loads parsing) on
inner to yield a dict, then merge extras = {k:v for k,v in input_obj.items() if
k != 'input'} and set input_obj = {**inner_normalized, **extras}; ensure you
still validate resulting input_obj is a dict and preserve existing
warning/return behavior in _normalize_input.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in commit a1dd7d0. The normalization helper now recurses through _normalize_vectordb_tool_input on the nested input value (after the outer model_dump/json.loads/dict path), so {'input': '{"query": "x"}'} and {'input': SomeModel(...)} are handled the same way as {'input': {'query': 'x'}}. The outer extras still take precedence when merged on top of the normalized inner dict.

Note: tool_vectordb/IInstance.py has been deleted entirely — the normalization helper now lives in packages/ai/src/ai/common/store.py as _normalize_vectordb_tool_input, shared by the VectorStoreToolMixin methods that were inlined into the pinecone/chroma/qdrant nodes per @Rod-Christensen's architectural feedback. Verified against the original regression cases (JSON-string-wrapped, model-wrapped, and dict+extras wrappers).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

@asclearuc
Copy link
Copy Markdown
Collaborator

@CodeRabbit - recheck.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 8, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== IInstance.py ==="
cat nodes/src/nodes/tool_vectordb/IInstance.py

echo ""
echo "=== IGlobal.py ==="
cat nodes/src/nodes/tool_vectordb/IGlobal.py

echo ""
echo "=== services.json ==="
cat nodes/src/nodes/tool_vectordb/services.json

echo ""
echo "=== requirements.txt (search) ==="
fd -i "requirements" --type f | head -20

echo ""
echo "=== __init__.py ==="
cat nodes/src/nodes/tool_vectordb/__init__.py

Length of output: 22266


@asclearuc Here is the recheck summary against the 6 items from the merge checklist:


✅ Resolved (4/6)

# Item Status Notes
1 BaseModel via .model_dump() not dict() ✅ Done _normalize_input (Line 269–275) correctly prioritizes model_dump() before falling back to dict().
3 Search result size limits ✅ Done _MAX_TOP_K = 100 declared at module level; top_k = max(1, min(top_k, _MAX_TOP_K)) applied in search(); default_top_k also clamped in IGlobal.beginGlobal().
5 Validate required fields in upsert ✅ Done upsert() checks for non-empty documents list, skips items missing content or object_id, and raises ValueError if no valid documents remain.
6 server_name fallback consistency ✅ Done Default "vectordb" is declared in services.json preconfig; validateConfig() emits a warning() when serverName is empty.

❌ Still Open (2/6)

Item 2 — requirements.txt missing

Every other node that has external dependencies ships a requirements.txt (e.g., chroma, pinecone, qdrant all have one). nodes/src/nodes/tool_vectordb/ has no requirements.txt at all. This node dynamically imports those backend modules at runtime, so it should at minimum document which optional backend packages it requires. A minimal file would look like:

# Install one or more depending on which backend is configured:
# pinecone-client>=3.0
# chromadb>=0.4
# qdrant-client>=1.7

If backend deps are optional (not always needed), they should be listed as commented extras or in a separate requirements-optional.txt, matching the pattern used by agent_crewai or similar nodes.


Item 4 — Test config block missing from services.json

The preconfig section currently has only a "default" profile:

"preconfig": {
  "default": "default",
  "profiles": {
    "default": { "serverName": "vectordb", "backend": "pinecone" }
  }
}

There is no test or mock profile. Other tool nodes that follow the same pattern include a test profile pointing to a local/mock backend so CI tests can run without live credentials. A minimal addition would be:

"test": {
  "title": "Test (mock)",
  "serverName": "vectordb-test",
  "backend": "chroma",
  "topK": 5,
  "scoreThreshold": 0.0
}

(The exact backend and values should match whatever mock/local fixture is used by the project's test harness.)


Summary: 4 of 6 items are clean. The two blockers before merge are the missing requirements.txt and the missing test profile in services.json.

@Rod-Christensen
Copy link
Copy Markdown
Collaborator

Hey Charlie,

This is a great idea! Have you consider just adding the tool definitions to the underlying store.py? This would enable any vectordb to morph into a store directly without changing any of them or having a single generic node as all vectordb drivers inherit from store.

…ew feedback

Rod-Christensen suggested co-locating the tool definitions with the
underlying store so every vectordb node can "morph into a tool" without
a separate generic wrapper node.

The engine dispatches ``invoke`` against the node's ``IInstance`` (not
the ``DocumentStoreBase`` subclass, which is composed via
``self.IGlobal.store``), so a mixin that lives next to
``DocumentStoreBase`` in ``store.py`` and is inherited by the vectordb
``IInstance`` classes achieves the spirit of the suggestion while being
technically correct.

Changes:

- Add ``VectorStoreToolMixin`` to ``packages/ai/src/ai/common/store.py``
  with ``@tool_function`` decorated ``search``, ``upsert`` and
  ``delete`` methods that delegate to ``self.IGlobal.store``. The mixin
  is picked up automatically by ``IInstanceBase._collect_tool_methods``
  via MRO walk.
- Have ``pinecone``, ``chroma`` and ``qdrant`` ``IInstance`` classes
  inherit from ``VectorStoreToolMixin`` so each vectordb node exposes
  the search/upsert/delete tools natively.
- Add ``"tool"`` to ``classType`` and ``"invoke"`` to ``capabilities``
  in each node's ``services.json`` so ``getControllerNodeIds('tool')``
  discovers them as tool nodes.
- Delete the ``tool_vectordb`` node entirely — obsolete now that every
  vectordb node has native tool capability.
- Delete the unused ``vectordb.svg`` icon that existed only for the
  now-deleted ``tool_vectordb`` node.
- Fix the wrapped-payload normalization bug flagged in CodeRabbit
  comment 3049766102: ``{'input': '{"query": "x"}'}`` and
  ``{'input': SomeModel(...)}`` now recurse through
  ``_normalize_vectordb_tool_input`` on the inner value, so string-,
  model- and dict-wrapped payloads all receive the same
  JSON-parse/``model_dump`` treatment before merging outer extras.

Other vectordb connectors (astra_db, milvus, weaviate, atlas,
vectordb_postgres, index_search) can opt in to the same tool interface
with a one-line change to their ``IInstance.py`` plus the services.json
update — out of scope for this PR.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions github-actions bot added module:ai AI/ML modules and removed module:ui Chat UI and Dropper UI labels Apr 9, 2026
@charliegillet
Copy link
Copy Markdown
Contributor Author

@Rod-Christensen Great suggestion — I've refactored along those lines in commit a1dd7d0.

What changed

Added VectorStoreToolMixin directly in packages/ai/src/ai/common/store.py (right next to DocumentStoreBase) with @tool_function decorated search, upsert, and delete methods. The mixin delegates every call to self.IGlobal.store (a DocumentStoreBase instance).

The pinecone, chroma, and qdrant IInstance classes now inherit from this mixin, their services.json files declare classType: ["store", "tool"] and capabilities: ["invoke"], and the entire tool_vectordb node has been deleted (along with its vectordb.svg icon).

Why a mixin instead of putting the tool methods directly on DocumentStoreBase

I investigated doing exactly what you described and ran into a wiring subtlety: the engine dispatches invoke('tool', ...) against each node's IInstance, not the Store class. The pinecone/chroma/qdrant IInstance classes inherit from IInstanceTransform and reference the store via composition (self.IGlobal.store), not inheritance. So @tool_function methods on DocumentStoreBase would never be visible to IInstanceBase._collect_tool_methods when the engine dispatches.

The mixin approach keeps the tool interface co-located with the store in store.py (preserving the single-source-of-truth spirit of your suggestion) while being inherited by the IInstance so _collect_tool_methods' MRO walk picks it up. Adding a new vectordb backend to the tool interface is now a one-line change:

class IInstance(VectorStoreToolMixin, IInstanceTransform):

plus the classType/capabilities edits in services.json.

Out of scope / follow-up

The other vectordb connectors (astra_db, milvus, weaviate, atlas, vectordb_postgres, index_search) can opt in with the same one-line mixin inheritance — I kept this PR focused on pinecone/chroma/qdrant to minimize blast radius, but happy to extend if you'd like it done here.

Address 3 blocking issues on PR rocketride-org#524:

1. Tool name collisions: adopt the established <serverName>.<toolName>
   convention used by mcp_client. VectorStoreToolMixin now overrides
   _collect_tool_methods to prefix each tool with a per-node serverName
   sourced from IGlobal.serverName (user-configurable via services.json).
   Two pinecone/chroma/qdrant instances in the same pipeline no longer
   collide on bare names like "search"/"upsert"/"delete".

2. Broken pinecone semantic search: the mixin was calling searchSemantic
   with an unset embedding, which raises in every backend (pinecone/
   chroma/qdrant) and silently fell through to keyword search. Add an
   IGlobal.embed_query hook so nodes can plug in an embedder for the
   control-plane tool path; when no embedder is bound, go straight to
   keyword search with a one-shot warning and update the tool description
   so agents know the mode. Unexpected semantic errors still fall back
   to keyword search with a clearer warning message.

3. No tests for VectorStoreToolMixin: add nodes/test/test_vectordb_tool_mixin.py
   (28 tests) covering input normalization (dict/JSON/pydantic v1+v2/
   nested input wrapper/security_context stripping/malformed payloads),
   namespaced tool method collection including the collision-avoidance
   scenario, and search/upsert/delete dispatch through a fake store plus
   keyword-fallback, semantic-fallback, and missing-store error paths.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@charliegillet
Copy link
Copy Markdown
Contributor Author

Devil's advocate blockers — all addressed in 4ef3312

Thanks for the thorough review. All three blockers are fixed on feature/vectordb-tool-interface. Quick summary of how each was handled:

Blocker 1 — Tool name collisions (namespacing)

Adopted the established <serverName>.<toolName> convention documented in packages/ai/src/ai/common/tools.py and implemented in nodes/src/nodes/mcp_client/IInstance.py.

  • Added a serverName field to each of pinecone/chroma/qdrant services.json (with sensible defaults pinecone/chroma/qdrant) and to every profile preconfig.
  • Each node's IGlobal.beginGlobal now reads the merged config via Config.getNodeConfig(...) and stores self.serverName.
  • VectorStoreToolMixin in packages/ai/src/ai/common/store.py overrides _collect_tool_methods() to prefix every owned tool with the configured server name. The dispatch side works automatically because IInstanceBase._dispatch_tool looks up methods[tool_name] using the same dict — namespacing the keys covers both discovery and invocation in one place.

Two pinecone instances (or any mix of vectordb nodes) in the same pipeline can now be named e.g. primary and secondary and their tools will not collide. There is a regression test test_two_instances_different_server_names_do_not_collide to lock that in.

Blocker 2 — Broken semantic search

You were right: QuestionText(text=query_text) has embedding=None, and all three backends (pinecone, chroma, qdrant) raise inside searchSemantic when the embedding is missing. The previous code silently caught and fell through to keyword search — which was wasteful and hid the problem.

Chose a hybrid of Option A + Option C:

  • Option A (hook): added VectorStoreToolMixin._vectordb_compute_embedding(), which looks for IGlobal.embed_query (a Callable[[str], list[float]]). Nodes or subclasses can plug in any embedder they want; the mixin populates question.embedding (and optionally question.embedding_model from IGlobal.embed_model_name) before calling searchSemantic.
  • Option C (graceful keyword fallback): if no embedder is available, the tool goes straight to searchKeyword and emits a one-shot warning per instance explaining exactly what to do: "no embedding provider bound to IGlobal.embed_query; the {server}.search tool is running in keyword-only mode. To enable semantic similarity ranking, set IGlobal.embed_query to a callable(text) -> list[float]." The tool's description was also updated so the agent sees which mode it is running in.
  • Unexpected semantic errors (misconfigured index, mismatched embedding dimensions, etc.) still fall back to keyword search, but with a more informative warning.

This avoids forcing every existing pipeline to hard-fail on the very first tool call while giving operators a clear, actionable path to enable true semantic ranking.

Blocker 3 — Zero tests

Added nodes/test/test_vectordb_tool_mixin.py (28 tests, all green) following the nodes/test/pinecone/test_record_updates.py importlib.util pattern so the tests load store.py in isolation with lightweight stubs and do not require the server or rocketlib.

Coverage:

  • _normalize_vectordb_tool_input: None, plain dict, pydantic v1 .dict(), pydantic v2 .model_dump(), JSON string, nested input wrapper (dict + JSON string), nested input with security_context stripping, malformed JSON, non-dict JSON.
  • Tool name namespacing: explicit serverName, fallback to logicalType, fallback to "vectordb" when nothing is available, and the two-instance-no-collision scenario.
  • Dispatch: search in both embedding-present and embedding-absent paths (including single-shot warn on repeated calls), search with store raising on semantic (verifies keyword fallback), search with store=None raising RuntimeError, empty-query validation, upsert happy path, upsert with mixed invalid entries, upsert empty-documents validation, delete strip/drop whitespace, delete empty-ids validation, and a full end-to-end methods["pinecone.search"]({...}) dispatch through the namespaced key.
$ python -m pytest nodes/test/test_vectordb_tool_mixin.py nodes/test/pinecone nodes/test/chroma nodes/test/qdrant
======================== 43 passed, 1 warning in 0.05s =========================

ruff check and ruff format --check clean on all modified files.

Commit: 4ef3312

@charliegillet
Copy link
Copy Markdown
Contributor Author

Latest fixes (commit 4ef3312, pushed to feature/vectordb-tool-interface):

  • Namespaced tool names: VectorStoreToolMixin now exposes tools as <serverName>.search/upsert/delete instead of bare names. serverName is user-configurable via a new field in pinecone/chroma/qdrant services.json (defaults to the provider name). Fixes the collision that made a second vectordb node in a pipeline dead.
  • Semantic search hook: added IGlobal.embed_query plug-in point. When an embedder is bound, the tool populates question.embedding and does true semantic search; when none is available it runs keyword-only with a single actionable warning and an updated tool description so the agent knows which mode it is running in.
  • Tests: new nodes/test/test_vectordb_tool_mixin.py with 28 unit tests (normalization edge cases, namespacing — including the multi-instance collision scenario — and dispatch through a fake store). All 43 tests in nodes/test/test_vectordb_tool_mixin.py + nodes/test/{pinecone,chroma,qdrant} pass. ruff check and ruff format --check clean.

Files touched:

  • packages/ai/src/ai/common/store.py
  • nodes/src/nodes/{pinecone,chroma,qdrant}/IGlobal.py
  • nodes/src/nodes/{pinecone,chroma,qdrant}/services.json
  • nodes/test/test_vectordb_tool_mixin.py (new)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

module:ai AI/ML modules module:nodes Python pipeline nodes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add tool interface for vector DBs

4 participants