ask-core 0.3.0 → 0.5.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: 6f85f689da590da523c0fcf5ead1b02469865eaf957720c8965084d439f69f74
4
- data.tar.gz: dec0a975ed1b82346e98a9d84820e8cad192db0f0e308d97a1dcbdc5303ebee9
3
+ metadata.gz: 384e1b71886280e9db2a3a6d737bc8e272522543715a3b307518eb71ab735957
4
+ data.tar.gz: 8c66d39ca24f2b7fb8aec39aa5855f3862e2f3f7456c619b11c04903cafb4398
5
5
  SHA512:
6
- metadata.gz: 4cb0d8b10f05b778fd66717cb54571d486d21cc93ba95334d5ecd3517d8aa4ccacd92b78fa7dfdc26893d89f5871d18593584cf204967f34d64d1545add1efc0
7
- data.tar.gz: f00616ed87027aedb9eeadc69445078dd203dee9910dcd014c1c89f6461544241bd1b0e08f6a39c30d60c40452b38f529424c09775e098ed6900aad4b9988279
6
+ metadata.gz: 10ada0c35161dabb17005329f643a7514df4e67922b8b086728b88207afa670eb7d35e4bc5d10378431d8ff732ed4f44ce24806c5872ae43e8f0e5275c5f52b8
7
+ data.tar.gz: be4235f9c942e894c83e14859835a43b4017815fc68f5dd11edffe60f46c717fa563fe9fdd3c8160d73d485571f3190fa4c18e8c0d862db6e62bf4b197677a41
data/CHANGELOG.md CHANGED
@@ -1,3 +1,29 @@
1
+ ## [0.4.0] — 2026-07-21
2
+
3
+ ### Added
4
+
5
+ - **Provider-executed tools** — `Ask::ProviderTool` value objects for configuring built-in tools that run on the LLM provider's infrastructure (web search, file search, code execution). Supports factory methods for OpenAI's built-in tools.
6
+
7
+ ```ruby
8
+ Ask::ProviderTool.web_search(search_context_size: "high")
9
+ Ask::ProviderTool.file_search(vector_store_ids: ["vs_abc"], max_num_results: 10)
10
+ Ask::ProviderTool.code_interpreter(file_ids: ["file_1"])
11
+ ```
12
+
13
+ Provider tools are identified by a fully qualified `id` (e.g. `"openai.web_search"`) and carry provider-specific `args`. They are `frozen` value objects with equality based on `id` + `args`.
14
+
15
+ ### Changed
16
+
17
+ - **ask-llm-providers OpenAI** — `chat` now splits provider tools from regular tools. When provider tools are present, the Responses API is used instead of Chat Completions. Regular function tools continue to use the existing Chat Completions path.
18
+ - **ask-agent Loop** — `ResponseMessage` now carries `tool_results` for pre-computed provider-executed results. The loop adds them directly to the conversation without local execution, then continues with any remaining user tool calls.
19
+ - **ResponseMessage** — `tool_results` field added with default `{}`. All existing call sites are backward compatible via the custom `initialize` with keyword defaults.
20
+
21
+ ### Tested
22
+
23
+ - 13 new tests for `Ask::ProviderTool`: creation, factory methods, args, frozen, equality, flags.
24
+ - 13 new integration tests for provider-executed tools: loop handling with mixed tools, only provider tools, tool splitting in OpenAI provider, Responses API tool formatting.
25
+ - Full test suite: 248 ask-core tests, 329 ask-agent tests — 0 failures.
26
+
1
27
  ## [0.3.0] — 2026-07-21
2
28
 
3
29
  ### Added
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ask
4
+ # Configuration for a provider-defined or provider-executed tool.
5
+ #
6
+ # Provider tools are built-in capabilities that the LLM provider offers
7
+ # natively — web search, file search, code execution, image generation,
8
+ # and so on. They are not implemented by user code but by the provider
9
+ # itself.
10
+ #
11
+ # @example Configuring a provider-executed web search
12
+ # Ask::ProviderTool.new(
13
+ # id: "openai.web_search",
14
+ # name: "web_search",
15
+ # description: "Search the internet",
16
+ # args: { search_context_size: "medium" }
17
+ # )
18
+ #
19
+ # @example Using shorthand factory methods
20
+ # Ask::ProviderTool.web_search(search_context_size: "high")
21
+ # Ask::ProviderTool.file_search(max_num_results: 10)
22
+ class ProviderTool
23
+ # @return [String] fully qualified tool identifier (e.g. "openai.web_search")
24
+ attr_reader :id
25
+
26
+ # @return [String] short tool name
27
+ attr_reader :name
28
+
29
+ # @return [String] human-readable description
30
+ attr_reader :description
31
+
32
+ # @return [Hash] provider-specific configuration arguments
33
+ attr_reader :args
34
+
35
+ # @return [Boolean] true if the provider handles execution on its side
36
+ def provider_executed?
37
+ true
38
+ end
39
+
40
+ # @return [Boolean] true — marks this as a provider tool for routing
41
+ def provider_tool?
42
+ true
43
+ end
44
+
45
+ def initialize(id:, name:, description: "", args: {})
46
+ @id = id
47
+ @name = name
48
+ @description = description
49
+ @args = args.dup.freeze
50
+ freeze
51
+ end
52
+
53
+ class << self
54
+ # OpenAI web search tool.
55
+ # @param search_context_size [String, nil] "low", "medium", or "high"
56
+ # @param user_location [Hash, nil] approximate location { type: "approximate", country: "US", ... }
57
+ def web_search(search_context_size: nil, user_location: nil, allowed_domains: nil)
58
+ args = {}.tap do |h|
59
+ h[:search_context_size] = search_context_size if search_context_size
60
+ h[:user_location] = user_location if user_location
61
+ h[:allowed_domains] = allowed_domains if allowed_domains
62
+ end
63
+ new(id: "openai.web_search", name: "web_search", description: "Search the internet for current information", args: args)
64
+ end
65
+
66
+ # OpenAI file search tool. Requires a vector store.
67
+ # @param vector_store_ids [Array<String>] IDs of vector stores to search
68
+ # @param max_num_results [Integer, nil] maximum number of results
69
+ def file_search(vector_store_ids:, max_num_results: nil)
70
+ args = { vector_store_ids: vector_store_ids }.tap do |h|
71
+ h[:max_num_results] = max_num_results if max_num_results
72
+ end
73
+ new(id: "openai.file_search", name: "file_search", description: "Search through uploaded files", args: args)
74
+ end
75
+
76
+ # OpenAI code interpreter tool.
77
+ # @param file_ids [Array<String>, nil] IDs of files to make available
78
+ def code_interpreter(file_ids: nil)
79
+ args = file_ids ? { file_ids: file_ids } : {}
80
+ new(id: "openai.code_interpreter", name: "code_interpreter", description: "Execute Python code in a sandboxed environment", args: args)
81
+ end
82
+ end
83
+
84
+ # Equality based on id + args.
85
+ def ==(other)
86
+ return false unless other.is_a?(ProviderTool)
87
+ id == other.id && args == other.args
88
+ end
89
+ alias eql? ==
90
+
91
+ def hash
92
+ [id, args].hash
93
+ end
94
+ end
95
+ end
data/lib/ask/state.rb CHANGED
@@ -65,6 +65,11 @@ module Ask
65
65
  raise NotImplementedError
66
66
  end
67
67
 
68
+ # Remove all keys from the store.
69
+ def clear
70
+ raise NotImplementedError
71
+ end
72
+
68
73
  # Atomically set a value only if the key does not already exist.
69
74
  # @param key [String] the key
70
75
  # @param value [Object] the value
@@ -203,6 +208,12 @@ module Ask
203
208
  end
204
209
  end
205
210
 
211
+ def clear
212
+ @mutex.synchronize do
213
+ @data.clear
214
+ end
215
+ end
216
+
206
217
  # -- locking --
207
218
 
208
219
  def acquire_lock(key, ttl: 10)
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.3.0"
4
+ VERSION = "0.5.0"
5
5
  end
data/lib/ask.rb CHANGED
@@ -22,3 +22,4 @@ require_relative "ask/conversation"
22
22
  require_relative "ask/provider"
23
23
  require_relative "ask/models"
24
24
  require_relative "ask/state"
25
+ 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.3.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kaka Ruto
@@ -68,6 +68,7 @@ files:
68
68
  - lib/ask/errors.rb
69
69
  - lib/ask/models.rb
70
70
  - lib/ask/provider.rb
71
+ - lib/ask/provider_tool.rb
71
72
  - lib/ask/result.rb
72
73
  - lib/ask/state.rb
73
74
  - lib/ask/stream.rb