Files
sure/app/models/assistant/function/search_family_files.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

119 lines
2.9 KiB
Ruby

class Assistant::Function::SearchFamilyFiles < Assistant::Function
class << self
def name
"search_family_files"
end
def description
<<~DESC
Search through documents that the family has uploaded to their financial document store.
Use this when the user asks questions about their uploaded financial documents such as
tax returns, bank statements, contracts, insurance policies, investment reports, or any
other files they've imported.
Returns relevant excerpts from matching documents along with the source filename and
a relevance score.
Supported file types include: PDF, DOCX, XLSX, PPTX, TXT, CSV, JSON, XML, HTML, MD,
and common source code formats.
Example:
```
search_family_files({
query: "What was the total income on my 2024 tax return?"
})
```
DESC
end
end
def strict_mode?
false
end
def params_schema
build_schema(
required: [ "query" ],
properties: {
query: {
type: "string",
description: "The search query to find relevant information in the family's uploaded documents"
},
max_results: {
type: "integer",
description: "Maximum number of results to return (default: 10, max: 20)"
}
}
)
end
def call(params = {})
query = params["query"]
max_results = (params["max_results"] || 10).to_i.clamp(1, 20)
unless family.vector_store_id.present?
return {
success: false,
error: "no_documents",
message: "No documents have been uploaded to the family document store yet."
}
end
adapter = VectorStore.adapter
unless adapter
return {
success: false,
error: "provider_not_configured",
message: "No vector store is configured. Set VECTOR_STORE_PROVIDER or configure OpenAI."
}
end
response = adapter.search(
store_id: family.vector_store_id,
query: query,
max_results: max_results
)
unless response.success?
return {
success: false,
error: "search_failed",
message: "Failed to search documents: #{response.error&.message}"
}
end
results = response.data
if results.empty?
return {
success: true,
results: [],
message: "No matching documents found for the query."
}
end
{
success: true,
query: query,
result_count: results.size,
results: results.map do |result|
{
content: result[:content],
filename: result[:filename],
score: result[:score]
}
end
}
rescue => e
Rails.logger.error("SearchFamilyFiles error: #{e.class.name} - #{e.message}")
{
success: false,
error: "search_failed",
message: "An error occurred while searching documents: #{e.message.truncate(200)}"
}
end
end