Files
sure/app/models/vector_store/qdrant.rb
Juan José Mata 9e57954a99 Add Family vector search function call / support for document vault (#961)
* Add SearchFamilyImportedFiles assistant function with vector store support

Implement per-Family document search using OpenAI vector stores, allowing
the AI assistant to search through uploaded financial documents (tax returns,
statements, contracts, etc.). The architecture is modular with a provider-
agnostic VectorStoreConcept interface so other RAG backends can be added.

Key components:
- Assistant::Function::SearchFamilyImportedFiles - tool callable from any LLM
- Provider::VectorStoreConcept - abstract vector store interface
- Provider::Openai vector store methods (create, upload, search, delete)
- Family::VectorSearchable concern with document management
- FamilyDocument model for tracking uploaded files
- Migration adding vector_store_id to families and family_documents table

https://claude.ai/code/session_01TSkKc7a9Yu2ugm1RvSf4dh

* Extract VectorStore adapter layer for swappable backends

Replace the Provider::VectorStoreConcept mixin with a standalone adapter
architecture under VectorStore::. This cleanly separates vector store
concerns from the LLM provider and makes it trivial to swap backends.

Components:
- VectorStore::Base — abstract interface (create/delete/upload/remove/search)
- VectorStore::Openai — uses ruby-openai gem's native vector_stores.search
- VectorStore::Pgvector — skeleton for local pgvector + embedding model
- VectorStore::Qdrant — skeleton for Qdrant vector DB
- VectorStore::Registry — resolves adapter from VECTOR_STORE_PROVIDER env
- VectorStore::Response — success/failure wrapper (like Provider::Response)

Consumers updated to go through VectorStore.adapter:
- Family::VectorSearchable
- Assistant::Function::SearchFamilyImportedFiles
- FamilyDocument

Removed: Provider::VectorStoreConcept, vector store methods from Provider::Openai

https://claude.ai/code/session_01TSkKc7a9Yu2ugm1RvSf4dh

* Add Vector Store configuration docs to ai.md

Documents how to configure the document search feature, covering all
three supported backends (OpenAI, pgvector, Qdrant), environment
variables, Docker Compose examples, supported file types, and privacy
considerations.

https://claude.ai/code/session_01TSkKc7a9Yu2ugm1RvSf4dh

* No need to specify `imported` in code

* Missed a couple more places

* Tiny reordering for the human OCD

* Update app/models/assistant/function/search_family_files.rb

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Signed-off-by: Juan José Mata <jjmata@jjmata.com>

* PR comments

* More PR comments

---------

Signed-off-by: Juan José Mata <jjmata@jjmata.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-02-11 15:22:56 +01:00

82 lines
2.7 KiB
Ruby

# Adapter for Qdrant — a dedicated open-source vector database.
#
# Qdrant can run locally (Docker), self-hosted, or as a managed cloud service.
# Like the Pgvector adapter you still supply your own embedding model; Qdrant
# handles storage, indexing, and fast ANN search.
#
# Requirements (not yet wired up):
# - A running Qdrant instance (QDRANT_URL, default http://localhost:6333)
# - Optional QDRANT_API_KEY for authenticated clusters
# - An embedding model endpoint (EMBEDDING_MODEL_URL / EMBEDDING_MODEL_NAME)
# - gem "qdrant-ruby" or raw Faraday HTTP calls
#
# Mapping:
# store → Qdrant collection
# file → set of points sharing a file_id payload field
# search → query vector + payload filter on store_id
#
class VectorStore::Qdrant < VectorStore::Base
def initialize(url: "http://localhost:6333", api_key: nil)
@url = url
@api_key = api_key
end
def create_store(name:)
with_response do
# POST /collections/{collection_name} { vectors: { size: 1536, distance: "Cosine" } }
# collection_name could be a slugified version of `name` or a UUID.
raise VectorStore::Error, "Qdrant adapter is not yet implemented"
end
end
def delete_store(store_id:)
with_response do
# DELETE /collections/{store_id}
raise VectorStore::Error, "Qdrant adapter is not yet implemented"
end
end
def upload_file(store_id:, file_content:, filename:)
with_response do
# 1. chunk file → text chunks
# 2. embed each chunk
# 3. PUT /collections/{store_id}/points { points: [...] }
# each point: { id: uuid, vector: [...], payload: { file_id, filename, content } }
raise VectorStore::Error, "Qdrant adapter is not yet implemented"
end
end
def remove_file(store_id:, file_id:)
with_response do
# POST /collections/{store_id}/points/delete
# { filter: { must: [{ key: "file_id", match: { value: file_id } }] } }
raise VectorStore::Error, "Qdrant adapter is not yet implemented"
end
end
def search(store_id:, query:, max_results: 10)
with_response do
# 1. embed(query) → vector
# 2. POST /collections/{store_id}/points/search
# { vector: [...], limit: max_results, with_payload: true }
# 3. map results → [{ content:, filename:, score:, file_id: }]
raise VectorStore::Error, "Qdrant adapter is not yet implemented"
end
end
private
def connection
@connection ||= Faraday.new(url: @url) do |f|
f.request :json
f.response :json
f.adapter Faraday.default_adapter
f.headers["api-key"] = @api_key if @api_key.present?
end
end
def embed(text)
raise VectorStore::Error, "Embedding model not configured"
end
end