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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 384e1b71886280e9db2a3a6d737bc8e272522543715a3b307518eb71ab735957
4
- data.tar.gz: 8c66d39ca24f2b7fb8aec39aa5855f3862e2f3f7456c619b11c04903cafb4398
3
+ metadata.gz: e9500049544d02d1e7a13d939c7e1bf7a532d5d45682302e9666d0e6d63b4488
4
+ data.tar.gz: 9f249f01b749bda7700131ce107bc0d312477849dd6006e6a3e2ca4b814e5b31
5
5
  SHA512:
6
- metadata.gz: 10ada0c35161dabb17005329f643a7514df4e67922b8b086728b88207afa670eb7d35e4bc5d10378431d8ff732ed4f44ce24806c5872ae43e8f0e5275c5f52b8
7
- data.tar.gz: be4235f9c942e894c83e14859835a43b4017815fc68f5dd11edffe60f46c717fa563fe9fdd3c8160d73d485571f3190fa4c18e8c0d862db6e62bf4b197677a41
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
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ask
4
- VERSION = "0.5.0"
4
+ VERSION = "0.6.0"
5
5
  end
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.5.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