ask-core 0.5.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 +69 -1
- 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,11 +65,27 @@ module Ask
|
|
|
65
65
|
raise NotImplementedError
|
|
66
66
|
end
|
|
67
67
|
|
|
68
|
-
# Remove all keys from the store.
|
|
68
|
+
# Remove all keys from the store (key-value only by default).
|
|
69
|
+
# Subclasses should override to also clear locks, queues, and lists.
|
|
69
70
|
def clear
|
|
70
71
|
raise NotImplementedError
|
|
71
72
|
end
|
|
72
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
|
+
|
|
73
89
|
# Atomically set a value only if the key does not already exist.
|
|
74
90
|
# @param key [String] the key
|
|
75
91
|
# @param value [Object] the value
|
|
@@ -152,6 +168,23 @@ module Ask
|
|
|
152
168
|
def close
|
|
153
169
|
# no-op by default
|
|
154
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
|
|
155
188
|
end
|
|
156
189
|
|
|
157
190
|
# In-process state backend backed by a Hash.
|
|
@@ -211,6 +244,33 @@ module Ask
|
|
|
211
244
|
def clear
|
|
212
245
|
@mutex.synchronize do
|
|
213
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) }
|
|
214
274
|
end
|
|
215
275
|
end
|
|
216
276
|
|
|
@@ -316,6 +376,14 @@ module Ask
|
|
|
316
376
|
@lists.clear
|
|
317
377
|
end
|
|
318
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
|
|
319
387
|
end
|
|
320
388
|
end
|
|
321
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
|