ask-core 0.8.0 → 0.9.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 +27 -0
- data/README.md +48 -243
- data/lib/ask/result.rb +76 -15
- data/lib/ask/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8cf33a5ee5e4a2e658e9e9b7244f0b368a188cb1a9a81d580003d7ed7f5bcb32
|
|
4
|
+
data.tar.gz: 4f0b398650815bde265b4becd15da23ae13f868ff06791b6a410e4c561775826
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 807a0ccb0851c2d1d51f4351072188f681c3356afbc74a477e4f795fc4c3456cf523b5d623f5d827bc5a76ab36d62e13348d13cba3298ddd6cd7822aa757814d
|
|
7
|
+
data.tar.gz: c9e02adf485410de34cbfbc8154a893aaffb2c5c72d618aeaeb6551d9a8c6482680683a5e5528a9f4e5398cef462bfd6018582e1021a1e70915b59277a8bc42d
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,30 @@
|
|
|
1
|
+
## [0.9.0] — 2026-08-03
|
|
2
|
+
|
|
3
|
+
### Changed
|
|
4
|
+
|
|
5
|
+
- **`Ask::Result` is now the single result type for the whole ecosystem** — it
|
|
6
|
+
supports both the foundational API (`success`/`failure`/`aborted`/`blocked`
|
|
7
|
+
with `content` and `status`) and the tool API (`ok`/`error` with `ok?`,
|
|
8
|
+
`output`, and `error_message`), including both constructor keyword sets.
|
|
9
|
+
Previously `ask-tools` defined its own incompatible `Ask::Result`; whichever
|
|
10
|
+
gem loaded last won, so `Ask::Result.success` raised `ArgumentError` in any
|
|
11
|
+
app loading both (e.g. every ask-agent app), and `ask-rag`'s `raw.output`
|
|
12
|
+
call failed on provider embedding results. ask-tools now depends on
|
|
13
|
+
ask-core instead of redefining the class.
|
|
14
|
+
- `Result#to_h` now serializes to `{ok:, output:, error:, metadata:}` (the
|
|
15
|
+
tool shape). `Result#inspect` is `ok=true output=...` / `ok=false error=...`.
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
Ask::Result.success("Data processed").to_h
|
|
19
|
+
# => {ok: true, output: "Data processed", error: nil, metadata: {}}
|
|
20
|
+
|
|
21
|
+
Ask::Result.ok(data: "Data processed") # same class, same result
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Tested
|
|
25
|
+
|
|
26
|
+
- ask-core test suite: all pass.
|
|
27
|
+
|
|
1
28
|
## [0.8.0] — 2026-07-28
|
|
2
29
|
|
|
3
30
|
### Changed
|
data/README.md
CHANGED
|
@@ -1,284 +1,89 @@
|
|
|
1
1
|
# ask-core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://badge.fury.io/rb/ask-core)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Foundation gem for the ask-rb ecosystem. Provides the value objects and interfaces every other gem builds on: messages, conversations, streaming primitives, the provider contract, model catalog, tool definitions, and structured errors. Zero external dependencies, Ruby stdlib only (`json`, `net/http`, `date`, `time`).
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
9
9
|
```ruby
|
|
10
|
-
# In your Gemfile
|
|
11
10
|
gem "ask-core"
|
|
12
11
|
```
|
|
13
12
|
|
|
14
|
-
##
|
|
15
|
-
|
|
16
|
-
| Component | File | Purpose |
|
|
17
|
-
|---|---|---|
|
|
18
|
-
| `Ask::Provider` | `lib/ask/provider.rb` | Abstract base class for all LLM providers |
|
|
19
|
-
| `Ask::Conversation` | `lib/ask/conversation.rb` | Message container with role normalization |
|
|
20
|
-
| `Ask::Stream` / `Ask::Chunk` | `lib/ask/stream.rb` | Streaming primitives |
|
|
21
|
-
| `Ask::ModelCatalog` | `lib/ask/models.rb` | Model name to provider resolution |
|
|
22
|
-
| `Ask::ToolDef` | `lib/ask/tool_def.rb` | Immutable tool metadata struct |
|
|
23
|
-
| `Ask::Result` | `lib/ask/result.rb` | Standardized tool return value |
|
|
24
|
-
| `Ask::Error` | `lib/ask/errors.rb` | Structured error types |
|
|
25
|
-
|
|
26
|
-
## Usage
|
|
27
|
-
|
|
28
|
-
### Provider (abstract base class)
|
|
29
|
-
|
|
30
|
-
Provider gems subclass `Ask::Provider` and implement the abstract methods:
|
|
13
|
+
## Quick Start
|
|
31
14
|
|
|
32
15
|
```ruby
|
|
33
|
-
|
|
34
|
-
def api_base
|
|
35
|
-
"https://api.example.com/v1"
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def headers
|
|
39
|
-
{ "Authorization" => "Bearer #{@config.api_key}" }
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def chat(messages, model:, tools: nil, temperature: nil, stream: nil, schema: nil, **params, &block)
|
|
43
|
-
# Return an Ask::Message or yield Ask::Chunks
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def embed(text, model:)
|
|
47
|
-
# Return an array of floats
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def list_models
|
|
51
|
-
# Return an array of Ask::ModelInfo
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
class << self
|
|
55
|
-
def configuration_options
|
|
56
|
-
[:api_key, :api_base]
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def configuration_requirements
|
|
60
|
-
[:api_key]
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
# Register the provider
|
|
66
|
-
Ask::Provider.register(:my_provider, MyProvider)
|
|
67
|
-
|
|
68
|
-
# Resolve by name
|
|
69
|
-
Ask::Provider.resolve(:my_provider) # => MyProvider
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
### Conversation
|
|
73
|
-
|
|
74
|
-
Build and manipulate conversations with role-normalized messages:
|
|
16
|
+
require "ask-core"
|
|
75
17
|
|
|
76
|
-
```ruby
|
|
77
18
|
conv = Ask::Conversation.new
|
|
78
|
-
|
|
79
|
-
# Convenience methods
|
|
80
19
|
conv.system("You are a helpful assistant.")
|
|
81
20
|
conv.user("What's the weather in Tokyo?")
|
|
82
|
-
conv.assistant("Let me check...", tool_calls: [{ name: "get_weather", arguments: { location: "Tokyo" } }])
|
|
83
|
-
conv.tool_result("72°F, sunny", tool_call_id: "call_123")
|
|
84
|
-
|
|
85
|
-
# Iteration
|
|
86
|
-
conv.each { |msg| puts "#{msg.role}: #{msg.content}" }
|
|
87
21
|
|
|
88
|
-
#
|
|
89
|
-
conv.
|
|
90
|
-
conv.system_messages # => [Ask::Message, ...]
|
|
22
|
+
conv.last.role # => :user
|
|
23
|
+
conv.last.user? # => true
|
|
91
24
|
|
|
92
|
-
#
|
|
93
|
-
conv.to_a
|
|
25
|
+
# Serialize for a provider API
|
|
26
|
+
conv.to_a # => [{ role: :user, content: "..." }, ...]
|
|
94
27
|
```
|
|
95
28
|
|
|
96
|
-
|
|
29
|
+
## The core types
|
|
97
30
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
31
|
+
| Type | Purpose |
|
|
32
|
+
|---|---|
|
|
33
|
+
| `Ask::Conversation`, `Ask::Message` | Message container with role normalization (`:system`, `:user`, `:assistant`, `:tool`) and immutable message value objects |
|
|
34
|
+
| `Ask::Stream`, `Ask::Chunk` | Streaming primitives with text accumulation and usage tracking |
|
|
35
|
+
| `Ask::Provider` | Abstract base class for LLM providers, with a thread-safe registry (`register` / `resolve`) |
|
|
36
|
+
| `Ask::ModelCatalog`, `Ask::ModelInfo` | Model metadata: find by ID/provider, filter by family, refresh from models.dev |
|
|
37
|
+
| `Ask::ToolDef` | Immutable tool metadata for provider function calling |
|
|
38
|
+
| `Ask::Result` | Standardized tool return value: `success`, `failure`, `aborted`, `blocked` |
|
|
39
|
+
| `Ask::Content` | Multi-modal content blocks: `Text`, `Image`, `Audio`, `Video`, `File` |
|
|
40
|
+
| `Ask::Document` | Text + metadata value object for RAG pipelines |
|
|
41
|
+
| `Ask::ProviderTool` | Provider-executed tools (e.g. `web_search`, `file_search`) |
|
|
42
|
+
| `Ask::State::Adapter` | Abstract contract for state backends (implemented by ask-state-providers) |
|
|
43
|
+
| `Ask::Error` and subclasses | Structured errors (`ConfigurationError`, `RateLimitError`, `ProviderError`, and more) |
|
|
104
44
|
|
|
105
|
-
|
|
106
|
-
msg.tool_call? # => true
|
|
45
|
+
## Defining a provider
|
|
107
46
|
|
|
108
|
-
|
|
109
|
-
msg.tool_result? # => true
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
Valid roles: `:system`, `:user`, `:assistant`, `:tool`
|
|
113
|
-
|
|
114
|
-
### Streaming
|
|
47
|
+
Provider gems subclass `Ask::Provider`, implement the abstract methods, and register themselves:
|
|
115
48
|
|
|
116
49
|
```ruby
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
stream.finish!
|
|
124
|
-
|
|
125
|
-
# Accumulate the full response
|
|
126
|
-
stream.accumulated_text # => "Hello World"
|
|
127
|
-
stream.to_s # => "Hello World"
|
|
128
|
-
|
|
129
|
-
# Track token usage
|
|
130
|
-
stream.accumulated_usage # => { input_tokens: 10, output_tokens: 20 }
|
|
131
|
-
|
|
132
|
-
# Iterate
|
|
133
|
-
stream.each { |chunk| print chunk.content }
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
### Chunks
|
|
137
|
-
|
|
138
|
-
```ruby
|
|
139
|
-
chunk = Ask::Chunk.new(content: "Hello")
|
|
140
|
-
chunk.content # => "Hello"
|
|
141
|
-
chunk.finished? # => false
|
|
142
|
-
chunk.tool_call? # => false
|
|
143
|
-
chunk.finish_reason # => nil (or "stop", "length", "tool_calls")
|
|
144
|
-
|
|
145
|
-
chunk = Ask::Chunk.new(tool_calls: [{ name: "get_weather" }])
|
|
146
|
-
chunk.tool_call? # => true
|
|
147
|
-
|
|
148
|
-
chunk = Ask::Chunk.new(usage: { input_tokens: 10, output_tokens: 20 })
|
|
149
|
-
chunk.usage # => { input_tokens: 10, output_tokens: 20 }
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
### Model Catalog
|
|
153
|
-
|
|
154
|
-
Query available models from the registry:
|
|
155
|
-
|
|
156
|
-
```ruby
|
|
157
|
-
catalog = Ask::ModelCatalog.new([
|
|
158
|
-
Ask::ModelInfo.new(id: "gpt-4o", provider: "openai", capabilities: ["function_calling", "vision"]),
|
|
159
|
-
Ask::ModelInfo.new(id: "claude-sonnet-4", provider: "anthropic", capabilities: ["function_calling", "reasoning"])
|
|
160
|
-
])
|
|
161
|
-
|
|
162
|
-
# Find by ID (prefers most common provider)
|
|
163
|
-
catalog.find("gpt-4o")
|
|
164
|
-
|
|
165
|
-
# Find with specific provider
|
|
166
|
-
catalog.find("gpt-4o", "openai")
|
|
167
|
-
|
|
168
|
-
# Filter by type
|
|
169
|
-
catalog.chat_models
|
|
170
|
-
catalog.embedding_models
|
|
171
|
-
|
|
172
|
-
# Filter by provider or family
|
|
173
|
-
catalog.by_provider("openai")
|
|
174
|
-
catalog.by_family("gpt")
|
|
175
|
-
|
|
176
|
-
# Singleton instance
|
|
177
|
-
Ask::ModelCatalog.instance
|
|
178
|
-
Ask::ModelCatalog.find("gpt-4o")
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
### ModelInfo
|
|
182
|
-
|
|
183
|
-
```ruby
|
|
184
|
-
info = Ask::ModelInfo.new(
|
|
185
|
-
id: "gpt-4o",
|
|
186
|
-
provider: "openai",
|
|
187
|
-
capabilities: ["function_calling", "vision"],
|
|
188
|
-
context_window: 128_000,
|
|
189
|
-
pricing: { text_tokens: { standard: { input_per_million: 2.5, output_per_million: 10 } } }
|
|
190
|
-
)
|
|
191
|
-
|
|
192
|
-
info.supports?(:function_calling) # => true
|
|
193
|
-
info.chat? # => true
|
|
194
|
-
info.embedding? # => false
|
|
195
|
-
info.context_window # => 128_000
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
### Tool Definitions
|
|
199
|
-
|
|
200
|
-
Immutable tool metadata for provider function calling:
|
|
201
|
-
|
|
202
|
-
```ruby
|
|
203
|
-
tool = Ask::ToolDef.new(
|
|
204
|
-
name: "get_weather",
|
|
205
|
-
description: "Get current weather for a location",
|
|
206
|
-
parameters: {
|
|
207
|
-
type: "object",
|
|
208
|
-
properties: {
|
|
209
|
-
location: { type: "string", description: "City name" },
|
|
210
|
-
unit: { type: "string", enum: ["celsius", "fahrenheit"] }
|
|
211
|
-
},
|
|
212
|
-
required: ["location"]
|
|
213
|
-
}
|
|
214
|
-
)
|
|
215
|
-
|
|
216
|
-
tool.name # => "get_weather"
|
|
217
|
-
tool.description # => "Get current weather for a location"
|
|
50
|
+
class MyProvider < Ask::Provider
|
|
51
|
+
def api_base = "https://api.example.com/v1"
|
|
52
|
+
def chat(messages, model:, **opts) = Ask::Message.new(role: :assistant, content: "Hello")
|
|
53
|
+
def embed(text, model:) = [0.1, 0.2, 0.3]
|
|
54
|
+
def list_models = [Ask::ModelInfo.new(id: "my-model", provider: "my_provider")]
|
|
55
|
+
end
|
|
218
56
|
|
|
219
|
-
|
|
220
|
-
|
|
57
|
+
Ask::Provider.register(:my_provider, MyProvider)
|
|
58
|
+
Ask::Provider.resolve(:my_provider) # => MyProvider
|
|
221
59
|
```
|
|
222
60
|
|
|
223
|
-
|
|
61
|
+
## Architecture & ownership
|
|
224
62
|
|
|
225
|
-
|
|
63
|
+
ask-core owns the shared value objects and base contracts (`Ask::Message`,
|
|
64
|
+
`Ask::Conversation`, `Ask::Content`, `Ask::Stream`, `Ask::Provider`,
|
|
65
|
+
`Ask::Result`, `Ask::ModelCatalog`, `Ask::ToolDef`, `Ask::Document`, the
|
|
66
|
+
`Ask::Error` hierarchy). It is zero-dependency on purpose: `ask-llm-providers`,
|
|
67
|
+
`ask-rag`, `ask-graph`, and `ask-state-providers` depend on it *without*
|
|
68
|
+
pulling in the tool framework.
|
|
226
69
|
|
|
227
|
-
|
|
228
|
-
Ask::Result
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
Ask::Result.blocked("Permission denied")
|
|
70
|
+
Feature gems extend what ask-core provides — they never redefine it.
|
|
71
|
+
`Ask::Result` is the single result type for the whole ecosystem: foundational
|
|
72
|
+
API (`success`/`failure`/`aborted`/`blocked`) and tool API (`ok`/`error`) in
|
|
73
|
+
one class. See
|
|
74
|
+
[Architecture & Ownership](https://ask-rb.github.io/ask-docs/reference/architecture).
|
|
233
75
|
|
|
234
|
-
|
|
235
|
-
result.success? # => true
|
|
236
|
-
result.error? # => false
|
|
237
|
-
result.aborted? # => false
|
|
238
|
-
result.blocked? # => false
|
|
239
|
-
result.to_s # => "OK"
|
|
240
|
-
result.to_h # => { content: "OK", status: :success, metadata: {} }
|
|
241
|
-
```
|
|
76
|
+
## Full documentation
|
|
242
77
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
```ruby
|
|
246
|
-
Ask::Error # Base class (rescue Ask::Error to catch all)
|
|
247
|
-
Ask::ConfigurationError # Missing/incorrect configuration
|
|
248
|
-
Ask::UnknownProvider # Provider not registered
|
|
249
|
-
Ask::ModelNotFound # Model not in catalog
|
|
250
|
-
Ask::InvalidRole # Invalid message role
|
|
251
|
-
Ask::InvalidToolDefinition # Invalid tool name/definition
|
|
252
|
-
Ask::ProviderError # Provider API error (with status_code, response_body)
|
|
253
|
-
Ask::ContextLengthExceeded # Context window exceeded
|
|
254
|
-
Ask::RateLimitError # Rate limited
|
|
255
|
-
Ask::Unauthorized # Authentication failure
|
|
256
|
-
Ask::ServerError # 5xx server error
|
|
257
|
-
Ask::ServiceUnavailable # Service temporarily unavailable
|
|
258
|
-
Ask::UnsupportedFeature # Feature not supported by provider/model
|
|
259
|
-
Ask::MissingCredential # Required credential not found
|
|
260
|
-
Ask::InvalidCredential # Credential is invalid/expired
|
|
261
|
-
```
|
|
78
|
+
The full ask-rb documentation lives at https://ask-rb.github.io/ask-docs. [ask-core in depth](https://ask-rb.github.io/ask-docs/core/ask-core) covers the types, streaming, and the provider contract. API reference: https://ask-rb.github.io/ask-docs/reference/api.
|
|
262
79
|
|
|
263
80
|
## Development
|
|
264
81
|
|
|
265
|
-
```
|
|
82
|
+
```
|
|
83
|
+
bundle install
|
|
266
84
|
bundle exec rake test
|
|
267
85
|
```
|
|
268
86
|
|
|
269
|
-
## Testing
|
|
270
|
-
|
|
271
|
-
- Uses Minitest (not RSpec) — consistent with the ask-rb ecosystem.
|
|
272
|
-
- Unit tests for every public method.
|
|
273
|
-
- Run the full suite before every commit: `bundle exec rake test`.
|
|
274
|
-
|
|
275
|
-
## Design Principles
|
|
276
|
-
|
|
277
|
-
1. **Zero runtime dependencies** — stdlib only. Provider gems add their own HTTP clients.
|
|
278
|
-
2. **Immutable value objects** — `Message`, `ToolDef`, `Result`, `Chunk`, and `ModelInfo` are frozen after construction.
|
|
279
|
-
3. **Abstract interface** — `Ask::Provider` defines the contract. Provider gems implement the wire format.
|
|
280
|
-
4. **Provider registry** — providers register themselves for runtime resolution by name.
|
|
281
|
-
|
|
282
87
|
## License
|
|
283
88
|
|
|
284
89
|
MIT
|
data/lib/ask/result.rb
CHANGED
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Ask
|
|
4
|
-
# Standardized return value from tool execution.
|
|
5
|
-
#
|
|
4
|
+
# Standardized return value from tool execution.
|
|
5
|
+
#
|
|
6
|
+
# This is the single +Ask::Result+ for the whole ecosystem. It supports both
|
|
7
|
+
# the foundational API (+success+/+failure+/+aborted+/+blocked+ with
|
|
8
|
+
# +content+ and +status+) and the tool API (+ok+/+error+ with +ok?+,
|
|
9
|
+
# +output+, and +error_message+):
|
|
6
10
|
#
|
|
7
11
|
# Ask::Result.success("Data processed")
|
|
8
|
-
# Ask::Result.
|
|
12
|
+
# Ask::Result.ok(data: "Data processed")
|
|
13
|
+
#
|
|
14
|
+
# Both are the same class and share the same +to_h+ shape, so a result
|
|
15
|
+
# created by a provider's +embed+ or a tool's +execute+ can be inspected
|
|
16
|
+
# uniformly.
|
|
9
17
|
#
|
|
18
|
+
# Feature gems extend this class or build on it — they never redefine it.
|
|
19
|
+
# See ask-docs "Architecture & ownership" for the rule.
|
|
10
20
|
class Result
|
|
11
21
|
STATUSES = %i[success error aborted blocked short_circuited].freeze
|
|
12
22
|
|
|
@@ -23,11 +33,12 @@ module Ask
|
|
|
23
33
|
|
|
24
34
|
# Create a failure result.
|
|
25
35
|
# @param message [String] the error description
|
|
26
|
-
# @param error [Object, nil] the underlying error object
|
|
36
|
+
# @param error [Object, nil] the underlying error object (defaults to
|
|
37
|
+
# +message+ so `failure(msg).error` reads as the message)
|
|
27
38
|
# @param metadata [Hash] additional metadata
|
|
28
39
|
# @return [Ask::Result]
|
|
29
40
|
def failure(message, error: nil, metadata: {})
|
|
30
|
-
new(content: message, status: :error, error: error, metadata: metadata)
|
|
41
|
+
new(content: message, status: :error, error: error.nil? ? message : error, metadata: metadata)
|
|
31
42
|
end
|
|
32
43
|
|
|
33
44
|
# Create an aborted result (cancelled by sibling failure).
|
|
@@ -43,24 +54,58 @@ module Ask
|
|
|
43
54
|
def blocked(reason)
|
|
44
55
|
new(content: reason, status: :blocked)
|
|
45
56
|
end
|
|
57
|
+
|
|
58
|
+
# Create a successful result (tool API — alias for +success+).
|
|
59
|
+
# @param data [Object] the tool's output
|
|
60
|
+
# @param metadata [Hash] optional metadata
|
|
61
|
+
# @return [Ask::Result]
|
|
62
|
+
def ok(data:, metadata: {})
|
|
63
|
+
new(content: data, status: :success, metadata: metadata)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Create a failed result (tool API).
|
|
67
|
+
# @param message [String] description of the failure
|
|
68
|
+
# @param metadata [Hash] optional metadata
|
|
69
|
+
# @return [Ask::Result]
|
|
70
|
+
def error(message:, metadata: {})
|
|
71
|
+
new(content: message, status: :error, error: message, metadata: metadata)
|
|
72
|
+
end
|
|
46
73
|
# @!endgroup
|
|
47
74
|
end
|
|
48
75
|
|
|
49
|
-
# @return [Object, nil] the result content
|
|
76
|
+
# @return [Object, nil] the result content (for +success+/+ok+ results,
|
|
77
|
+
# this is the payload; for +failure+ it is the error message)
|
|
50
78
|
attr_reader :content
|
|
51
79
|
|
|
52
80
|
# @return [Symbol] the status (:success, :error, :aborted, :blocked, :short_circuited)
|
|
53
81
|
attr_reader :status
|
|
54
82
|
|
|
55
|
-
# @return [Object, nil] the underlying error, if any
|
|
83
|
+
# @return [Object, nil] the error message or underlying error object, if any
|
|
56
84
|
attr_reader :error
|
|
57
85
|
|
|
58
86
|
# @return [Hash] additional metadata
|
|
59
87
|
attr_reader :metadata
|
|
60
88
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
89
|
+
# Accepts both the foundational keywords (+content+, +status+) and the
|
|
90
|
+
# tool keywords (+ok+, +output+). +ok:+ derives the status: +true+ means
|
|
91
|
+
# +:success+, +false+ means +:error+.
|
|
92
|
+
#
|
|
93
|
+
# @param content [Object, nil] result content
|
|
94
|
+
# @param output [Object, nil] tool output (takes precedence over +content+)
|
|
95
|
+
# @param status [Symbol] result status
|
|
96
|
+
# @param ok [Boolean, nil] whether the result is a success (derives status)
|
|
97
|
+
# @param error [Object, nil] error message or underlying error object
|
|
98
|
+
# @param metadata [Hash] additional metadata
|
|
99
|
+
def initialize(content: nil, output: nil, status: :success, ok: nil, error: nil, metadata: {})
|
|
100
|
+
@content = output.nil? ? content : output
|
|
101
|
+
@status = if ok == true
|
|
102
|
+
:success
|
|
103
|
+
elsif ok == false
|
|
104
|
+
:error
|
|
105
|
+
else
|
|
106
|
+
status
|
|
107
|
+
end
|
|
108
|
+
@status = validate_status!(@status)
|
|
64
109
|
@error = error
|
|
65
110
|
@metadata = metadata.dup.freeze
|
|
66
111
|
freeze
|
|
@@ -69,6 +114,12 @@ module Ask
|
|
|
69
114
|
# @return [Boolean] true if status is :success
|
|
70
115
|
def success? = @status == :success
|
|
71
116
|
|
|
117
|
+
# @return [Boolean] true if the result is a success (tool API)
|
|
118
|
+
def ok? = @status == :success
|
|
119
|
+
|
|
120
|
+
# @return [Boolean] true if the result is a success (tool API)
|
|
121
|
+
def ok = @status == :success
|
|
122
|
+
|
|
72
123
|
# @return [Boolean] true if status is :error
|
|
73
124
|
def error? = @status == :error
|
|
74
125
|
|
|
@@ -78,7 +129,13 @@ module Ask
|
|
|
78
129
|
# @return [Boolean] true if status is :blocked
|
|
79
130
|
def blocked? = @status == :blocked
|
|
80
131
|
|
|
81
|
-
# @return [
|
|
132
|
+
# @return [Object, nil] the output data when successful, nil otherwise (tool API)
|
|
133
|
+
def output = @status == :success ? @content : nil
|
|
134
|
+
|
|
135
|
+
# @return [Object, nil] the error message or underlying error object
|
|
136
|
+
def error_message = @error
|
|
137
|
+
|
|
138
|
+
# @return [String] the content as a string (for failures, the message)
|
|
82
139
|
def to_s
|
|
83
140
|
@content.to_s
|
|
84
141
|
end
|
|
@@ -86,16 +143,20 @@ module Ask
|
|
|
86
143
|
# @return [Hash] serialized representation
|
|
87
144
|
def to_h
|
|
88
145
|
{
|
|
89
|
-
|
|
90
|
-
status: @
|
|
146
|
+
ok: @status == :success,
|
|
147
|
+
output: @status == :success ? @content : nil,
|
|
91
148
|
error: @error,
|
|
92
149
|
metadata: @metadata
|
|
93
|
-
}
|
|
150
|
+
}
|
|
94
151
|
end
|
|
95
152
|
|
|
96
153
|
# @return [String] human-readable representation
|
|
97
154
|
def inspect
|
|
98
|
-
|
|
155
|
+
if @status == :success
|
|
156
|
+
"#<Ask::Result ok=true output=#{@content.inspect}>"
|
|
157
|
+
else
|
|
158
|
+
"#<Ask::Result ok=false error=#{@error.inspect} status=#{@status.inspect}>"
|
|
159
|
+
end
|
|
99
160
|
end
|
|
100
161
|
|
|
101
162
|
private
|
data/lib/ask/version.rb
CHANGED