1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# frozen_string_literal: true
# A vector collection inside an AgentDB database.
#
# Obtain an instance via AgentDB::Database#collection — do not construct
# directly.
#
# Example:
# col = db.collection("memories", 1536)
# col.upsert("mem1", [0.1, 0.2, ...], { topic: "ruby" })
# results = col.search([0.1, 0.2, ...], top_k: 5)
# @return [String] collection name
attr_reader :name
# @return [Integer] vector dimensionality
attr_reader :dim
# @param handle [FFI::Pointer] opaque AgentDbHandle pointer
# @param name [String] collection name
# @param dim [Integer] vector dimensionality
@handle = handle
@name = name.to_s
@dim = dim.to_i
end
# Insert or update a vector entry.
#
# @param id [String] unique identifier for this vector
# @param vector [Array<Float>] embedding values (length must equal dim)
# @param metadata [Hash, nil] arbitrary JSON-serialisable metadata
# @raise [AgentDB::Error] on failure
raise ArgumentError, if vector.size != @dim
buf, size = FFIBindings.pack_floats(vector)
meta_json = metadata ? metadata.to_json : nil
rc = FFIBindings.agentdb_vector_upsert(
@handle, @name, id.to_s, buf, size, meta_json
)
check_rc!(rc, )
self
end
# Search the collection by approximate nearest-neighbour.
#
# @param query [Array<Float>] query embedding (length must equal dim)
# @param top_k [Integer] number of results to return (default: 10)
# @param filter [Hash, nil] MongoDB-style metadata filter, e.g.
# { "topic" => { "$eq" => "ruby" } }
# @return [Array<Hash>] array of { "id", "score", "metadata" } hashes
# @raise [AgentDB::Error] on failure
raise ArgumentError, if query.size != @dim
buf, size = FFIBindings.pack_floats(query)
filter_json = filter ? filter.to_json : nil
ptr = FFIBindings.agentdb_vector_search(
@handle, @name, buf, size, top_k.to_i, filter_json
)
json_string = read_json_ptr!(ptr, )
JSON.parse(json_string)
end
# Delete a single vector by ID.
#
# @param id [String] vector identifier to remove
# @raise [AgentDB::Error] on failure
rc = FFIBindings.agentdb_vector_delete(@handle, @name, id.to_s, @dim)
check_rc!(rc, )
self
end
# Drop the entire collection and all its vectors.
#
# @raise [AgentDB::Error] on failure
rc = FFIBindings.agentdb_drop_collection(@handle, @name)
check_rc!(rc, )
nil
end
# Rebuild the HNSW index for this collection.
#
# Useful after bulk inserts to improve query performance.
# @raise [AgentDB::Error] on failure
rc = FFIBindings.agentdb_reindex(@handle, @name, @dim)
check_rc!(rc, )
self
end
# Index a text document in the full-text search engine, linking it to
# a vector entry so hybrid queries can combine both signals.
#
# @param vec_id [String] corresponding vector entry ID
# @param collection_id [String] collection-scoped document ID
# @param text [String] document body to index
# @raise [AgentDB::Error] on failure
rc = FFIBindings.agentdb_fts_index(
@handle, @name, vec_id.to_s, collection_id.to_s, text.to_s
)
check_rc!(rc, )
self
end
# Full-text search over the collection.
#
# @param query [String] search terms
# @param top_k [Integer] max results (default: 10)
# @return [Array<Hash>] array of { "id", "snippet", "rank" } hashes
# @raise [AgentDB::Error] on failure
ptr = FFIBindings.agentdb_fts_search(@handle, @name, query.to_s, top_k.to_i)
json_string = read_json_ptr!(ptr, )
JSON.parse(json_string)
end
# Delete a document from the FTS index.
#
# @param vec_id [String] vector entry ID whose text should be removed
# @raise [AgentDB::Error] on failure
rc = FFIBindings.agentdb_fts_delete(@handle, @name, vec_id.to_s)
check_rc!(rc, )
self
end
# Optimize (merge) FTS index segments for faster queries.
#
# @raise [AgentDB::Error] on failure
rc = FFIBindings.agentdb_fts_optimize(@handle, @name)
check_rc!(rc, )
self
end
private
return if rc >= 0
msg = FFIBindings.last_error ||
raise AgentDB::FFIError,
end
if ptr.nil? || ptr.null?
msg = FFIBindings.last_error ||
raise AgentDB::FFIError,
end
FFIBindings.read_and_free(ptr)
end
end
end