ask-core 0.4.0 → 0.6.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -0
- data/lib/ask/document.rb +71 -0
- data/lib/ask/state.rb +79 -0
- data/lib/ask/version.rb +1 -1
- data/lib/ask.rb +2 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e9500049544d02d1e7a13d939c7e1bf7a532d5d45682302e9666d0e6d63b4488
|
|
4
|
+
data.tar.gz: 9f249f01b749bda7700131ce107bc0d312477849dd6006e6a3e2ca4b814e5b31
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b5ac63d0cf689c3bc22fb4ad4bac215fbc9aa022b74e63b453a87ab7c7d7a5c781fff2fc2c9b694d6fb9e669c858ec0f81c13bf21fac50753654f8ebed2c3c58
|
|
7
|
+
data.tar.gz: 23a06ce41d67eee03322691919623dfa888e301d4607c9812dbb85a2c46ace50b16d99aa9d1c1ba879104f84f74981bbc9cf8d5c985132331211c6bcbd00509a
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,25 @@
|
|
|
1
|
+
## [0.6.0] — 2026-07-26
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
|
|
5
|
+
- **`Ask::Document`** — a new frozen value object representing text content with metadata. Used as the universal currency in RAG pipelines: loaded by document loaders, split by text splitters, embedded and retrieved by vector stores. Immutable, equality-checked by content+metadata, and JSON-serializable.
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
doc = Ask::Document.new(
|
|
9
|
+
content: "Ruby was created by Matz in 1995.",
|
|
10
|
+
metadata: { source: "history.pdf", page: 3 }
|
|
11
|
+
)
|
|
12
|
+
doc.content # => "Ruby was created by Matz in 1995."
|
|
13
|
+
doc.metadata # => { source: "history.pdf", page: 3 }
|
|
14
|
+
doc.to_h # => { content: "...", metadata: { source: "history.pdf", page: 3 } }
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## [0.5.0] — 2026-07-22
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- `Ask::CostCalculator` — compute API costs from model pricing data.
|
|
22
|
+
|
|
1
23
|
## [0.4.0] — 2026-07-21
|
|
2
24
|
|
|
3
25
|
### Added
|
data/lib/ask/document.rb
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ask
|
|
4
|
+
# A piece of text content with associated metadata.
|
|
5
|
+
#
|
|
6
|
+
# Document is the universal value object for the RAG pipeline. It flows
|
|
7
|
+
# through every stage: loaded from files by {Ask::Loader::Base loaders},
|
|
8
|
+
# split into smaller pieces by {Ask::TextSplitter::Base splitters},
|
|
9
|
+
# embedded and stored by vector stores, and returned by retrieval queries.
|
|
10
|
+
#
|
|
11
|
+
# Immutable after creation. Two documents are equal when their content and
|
|
12
|
+
# metadata match.
|
|
13
|
+
#
|
|
14
|
+
# @example
|
|
15
|
+
# doc = Ask::Document.new(
|
|
16
|
+
# content: "Ruby was created by Matz in 1995.",
|
|
17
|
+
# metadata: { source: "history.pdf", page: 3 }
|
|
18
|
+
# )
|
|
19
|
+
# doc.content # => "Ruby was created by Matz in 1995."
|
|
20
|
+
# doc.metadata # => { source: "history.pdf", page: 3 }
|
|
21
|
+
# doc.id # => nil
|
|
22
|
+
#
|
|
23
|
+
class Document
|
|
24
|
+
# @return [String] the text content of this document
|
|
25
|
+
attr_reader :content
|
|
26
|
+
|
|
27
|
+
# @return [Hash] arbitrary metadata (source, page, chunk index, etc.)
|
|
28
|
+
attr_reader :metadata
|
|
29
|
+
|
|
30
|
+
# @return [String, nil] optional unique identifier
|
|
31
|
+
attr_reader :id
|
|
32
|
+
|
|
33
|
+
# @param content [String] the text content
|
|
34
|
+
# @param metadata [Hash] arbitrary metadata (default: {})
|
|
35
|
+
# @param id [String, nil] optional identifier
|
|
36
|
+
def initialize(content:, metadata: {}, id: nil)
|
|
37
|
+
@content = content.to_s
|
|
38
|
+
@metadata = metadata.dup.freeze
|
|
39
|
+
@id = id
|
|
40
|
+
freeze
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# @return [Hash] serializable representation
|
|
44
|
+
def to_h
|
|
45
|
+
{ content: @content, metadata: @metadata, id: @id }.compact
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @return [String] JSON representation
|
|
49
|
+
def to_json(*args)
|
|
50
|
+
to_h.to_json(*args)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Two documents are equal if their content and metadata match.
|
|
54
|
+
def ==(other)
|
|
55
|
+
return false unless other.is_a?(Document)
|
|
56
|
+
|
|
57
|
+
@content == other.content && @metadata == other.metadata
|
|
58
|
+
end
|
|
59
|
+
alias eql? ==
|
|
60
|
+
|
|
61
|
+
def hash
|
|
62
|
+
[@content, @metadata].hash
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# @return [String]
|
|
66
|
+
def inspect
|
|
67
|
+
preview = @content.length > 60 ? "#{@content[0, 60]}..." : @content
|
|
68
|
+
"#<Ask::Document content=#{preview.inspect}>"
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
data/lib/ask/state.rb
CHANGED
|
@@ -65,6 +65,27 @@ module Ask
|
|
|
65
65
|
raise NotImplementedError
|
|
66
66
|
end
|
|
67
67
|
|
|
68
|
+
# Remove all keys from the store (key-value only by default).
|
|
69
|
+
# Subclasses should override to also clear locks, queues, and lists.
|
|
70
|
+
def clear
|
|
71
|
+
raise NotImplementedError
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Check if a key exists and has not expired.
|
|
75
|
+
# @param key [String] the key
|
|
76
|
+
# @return [Boolean] true if the key exists and is not expired
|
|
77
|
+
def exists?(key)
|
|
78
|
+
!get(key).nil?
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Return all non-expired keys, optionally filtered by a glob pattern.
|
|
82
|
+
# Glob syntax: * matches any sequence, ? matches any single character.
|
|
83
|
+
# @param pattern [String, nil] optional glob pattern (e.g., "session:*")
|
|
84
|
+
# @return [Array<String>] matching keys
|
|
85
|
+
def keys(pattern: nil)
|
|
86
|
+
raise NotImplementedError
|
|
87
|
+
end
|
|
88
|
+
|
|
68
89
|
# Atomically set a value only if the key does not already exist.
|
|
69
90
|
# @param key [String] the key
|
|
70
91
|
# @param value [Object] the value
|
|
@@ -147,6 +168,23 @@ module Ask
|
|
|
147
168
|
def close
|
|
148
169
|
# no-op by default
|
|
149
170
|
end
|
|
171
|
+
|
|
172
|
+
# -- Glob pattern helpers --
|
|
173
|
+
|
|
174
|
+
# Convert a glob pattern (*, ?) to a SQL LIKE pattern.
|
|
175
|
+
# @param pattern [String] glob pattern (e.g., "session:*")
|
|
176
|
+
# @return [String] LIKE pattern (e.g., "session:%")
|
|
177
|
+
def self.glob_to_like(pattern)
|
|
178
|
+
pattern.gsub("*", "%").gsub("?", "_")
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Convert a glob pattern (*, ?) to a Regexp.
|
|
182
|
+
# @param pattern [String] glob pattern (e.g., "session:*")
|
|
183
|
+
# @return [Regexp] matching regex (e.g., /\Asession:.*\z/)
|
|
184
|
+
def self.glob_to_regex(pattern)
|
|
185
|
+
escaped = Regexp.escape(pattern)
|
|
186
|
+
Regexp.new("\\A#{escaped.gsub("\\*", ".*").gsub("\\?", ".")}\\z")
|
|
187
|
+
end
|
|
150
188
|
end
|
|
151
189
|
|
|
152
190
|
# In-process state backend backed by a Hash.
|
|
@@ -203,6 +241,39 @@ module Ask
|
|
|
203
241
|
end
|
|
204
242
|
end
|
|
205
243
|
|
|
244
|
+
def clear
|
|
245
|
+
@mutex.synchronize do
|
|
246
|
+
@data.clear
|
|
247
|
+
@locks.clear
|
|
248
|
+
@queues.clear
|
|
249
|
+
@lists.clear
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def exists?(key)
|
|
254
|
+
@mutex.synchronize do
|
|
255
|
+
expiry = @data[key]&.dig(:expires_at)
|
|
256
|
+
return false if expiry && Time.now >= expiry
|
|
257
|
+
@data.key?(key)
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def keys(pattern: nil)
|
|
262
|
+
@mutex.synchronize do
|
|
263
|
+
# Filter out expired keys
|
|
264
|
+
active = @data.select do |_k, v|
|
|
265
|
+
expiry = v[:expires_at]
|
|
266
|
+
expiry.nil? || Time.now < expiry
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
keys = active.keys.map(&:to_s)
|
|
270
|
+
return keys unless pattern
|
|
271
|
+
|
|
272
|
+
regex = self.class.glob_to_regex(pattern)
|
|
273
|
+
keys.select { |k| k.match?(regex) }
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
206
277
|
# -- locking --
|
|
207
278
|
|
|
208
279
|
def acquire_lock(key, ttl: 10)
|
|
@@ -305,6 +376,14 @@ module Ask
|
|
|
305
376
|
@lists.clear
|
|
306
377
|
end
|
|
307
378
|
end
|
|
379
|
+
|
|
380
|
+
private
|
|
381
|
+
|
|
382
|
+
# Convert a glob pattern (*, ?) to a Regexp.
|
|
383
|
+
# Delegates to the shared class method for consistency.
|
|
384
|
+
def glob_to_regex(pattern)
|
|
385
|
+
self.class.glob_to_regex(pattern)
|
|
386
|
+
end
|
|
308
387
|
end
|
|
309
388
|
end
|
|
310
389
|
end
|
data/lib/ask/version.rb
CHANGED
data/lib/ask.rb
CHANGED
|
@@ -8,6 +8,7 @@ require_relative "ask/version"
|
|
|
8
8
|
# Ask::Conversation is a message container with role normalization.
|
|
9
9
|
# Ask::Stream provides streaming primitives for incremental responses.
|
|
10
10
|
# Ask::ModelCatalog resolves model names to provider metadata.
|
|
11
|
+
# Ask::Document is a text+metadata value object for RAG pipelines.
|
|
11
12
|
# Ask::ToolDef is an immutable tool definition struct.
|
|
12
13
|
# Ask::Result standardizes tool execution return values.
|
|
13
14
|
# Ask::Error provides structured error types.
|
|
@@ -21,5 +22,6 @@ require_relative "ask/stream"
|
|
|
21
22
|
require_relative "ask/conversation"
|
|
22
23
|
require_relative "ask/provider"
|
|
23
24
|
require_relative "ask/models"
|
|
25
|
+
require_relative "ask/document"
|
|
24
26
|
require_relative "ask/state"
|
|
25
27
|
require_relative "ask/provider_tool"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ask-core
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kaka Ruto
|
|
@@ -65,6 +65,7 @@ files:
|
|
|
65
65
|
- README.md
|
|
66
66
|
- lib/ask.rb
|
|
67
67
|
- lib/ask/conversation.rb
|
|
68
|
+
- lib/ask/document.rb
|
|
68
69
|
- lib/ask/errors.rb
|
|
69
70
|
- lib/ask/models.rb
|
|
70
71
|
- lib/ask/provider.rb
|