typesafe-jev 0.7.0 → 1.0.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 +56 -14
- data/LICENSE +21 -0
- data/README.md +136 -3
- data/lib/typesafe/answer.rb +1 -1
- data/lib/typesafe/errors.rb +3 -3
- data/lib/typesafe/response.rb +1 -1
- data/lib/typesafe/version.rb +1 -1
- metadata +5 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7545ebcb0bdc5d72f1038ae64b4305c3d5aeb6110b0b5e53b0c37cb88de77ee5
|
|
4
|
+
data.tar.gz: 50b7471d5f3c5c5dad8c1c4b14c0dbc3f2b062fe0ee7e4cd076d2ff44b4b46e0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3d8c3637a1d48b857019ac364b7aca9875cf0accad92023021a02f00488569b518c685afceccb8c117e98bf72e80f63eae3469d632b213c745e7b6b7fd88a4ca
|
|
7
|
+
data.tar.gz: f4f8167797e08f99b23a0faa879ffe10e7d01370487e4c18d4472ef52256835df82c63426c37bb8d847672502f71902624f375520108880447880aace31c00ba
|
data/CHANGELOG.md
CHANGED
|
@@ -1,34 +1,76 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [1.0.0] - 2026-09-20
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Full MIT `LICENSE` text, a complete README (quickstart, question/answer/error guide), a `CONTRIBUTING.md` guide and GitHub issue templates.
|
|
12
|
+
- Gemspec metadata: `documentation_uri` (rubydoc.info) and `rubygems_mfa_required`; homepage now points at the GitHub repository.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- **Stability**: the public API shipped in 0.7.0 is now declared stable and covered by Semantic Versioning; breaking changes will require a major version bump from here on.
|
|
17
|
+
|
|
18
|
+
## [0.7.0] - 2026-09-20
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
4
21
|
|
|
5
22
|
- **Breaking**: `Typesafe::Client#evaluate` (and `Typesafe::Jev.evaluate`) now raises typed `Typesafe::Error` subclasses on any non-2xx response instead of raw `Net::HTTPClientException`/`Net::HTTPFatalError`: `BadRequestError` (400), `AuthenticationError` (401), `PermissionDeniedError` (403), `NotFoundError` (404), `UnprocessableEntityError` (422, with `#errors`), `RateLimitError` (429, with `#retry_after`), `OverloadedError` (529) and `ServerError` (5xx), all under the `Typesafe::APIError` base with `status`, `body`, `headers`, `request_id` and `#retryable?`. `Typesafe::Error` is the root rescuable class.
|
|
6
23
|
|
|
7
|
-
## 0.6.0
|
|
24
|
+
## [0.6.0] - 2026-09-20
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
8
27
|
|
|
9
28
|
- **Breaking**: `Typesafe::Client#evaluate` (and `Typesafe::Jev.evaluate`) now returns a typed `Typesafe::Response` instead of the raw parsed Hash; answers come back as `NoulAnswer`/`ChoiceAnswer`/`ScoreAnswer` objects accessible via `response[question_id]` (String or Symbol). Use `response.to_h` for the previous raw-Hash behavior.
|
|
10
29
|
|
|
11
|
-
## 0.5.0
|
|
30
|
+
## [0.5.0] - 2026-09-20
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
|
|
34
|
+
- Response classes mirroring the question classes: abstract `Typesafe::Answer` base with `Answer.from_h` dispatching on the `type` tag, plus `Typesafe::NoulAnswer` (`noul`), `Typesafe::ChoiceAnswer` (`choice`, `probabilities`, `confidence`) and `Typesafe::ScoreAnswer` (`score`, `legend`, `probabilities`, `confidence`). Frozen, immutable value objects that validate their inputs and serialize via `#to_h`/`#to_json`.
|
|
35
|
+
- `Typesafe::Usage` (token counts) and `Typesafe::Response` (`model`, `answers`, `usage`), with `Response.from_json`/`from_h` to parse an API response body into typed Answer objects; `Response#[]` accepts String or Symbol question ids.
|
|
36
|
+
|
|
37
|
+
## [0.4.1] - 2026-09-20
|
|
12
38
|
|
|
13
|
-
|
|
14
|
-
- Add `Typesafe::Usage` (token counts) and `Typesafe::Response` (`model`, `answers`, `usage`), with `Response.from_json`/`from_h` to parse an API response body into typed Answer objects; `Response#[]` accepts String or Symbol question ids.
|
|
39
|
+
### Fixed
|
|
15
40
|
|
|
16
|
-
|
|
41
|
+
- Gemspec metadata: point `source_code_uri`/`changelog_uri` and author/email at the `dtheofr` GitHub account.
|
|
17
42
|
|
|
18
|
-
|
|
43
|
+
## [0.4.0] - 2026-09-20
|
|
19
44
|
|
|
20
|
-
|
|
45
|
+
### Added
|
|
21
46
|
|
|
22
|
-
-
|
|
47
|
+
- `Typesafe::Jev`, a `Typesafe::Client` subclass with the model pinned to `jev-latest`: `Jev.new(api_key: nil).evaluate(...)` plus a one-shot `Typesafe::Jev.evaluate(state:, questions:, api_key: nil)`.
|
|
23
48
|
|
|
24
|
-
## 0.3.0
|
|
49
|
+
## [0.3.0] - 2026-09-20
|
|
25
50
|
|
|
26
|
-
|
|
51
|
+
### Added
|
|
27
52
|
|
|
28
|
-
|
|
53
|
+
- `Typesafe::Client`: `initialize(api_key:, model:)` (key from argument or `TYPESAFE_API_KEY`, model defaults to `jev-latest`) and `evaluate(state:, questions:, model: nil)` posting to the System One endpoint. Returns the parsed JSON response; non-2xx responses raise the stdlib `Net::HTTP` error as-is.
|
|
29
54
|
|
|
30
|
-
|
|
55
|
+
## [0.2.0] - 2026-09-20
|
|
31
56
|
|
|
32
|
-
|
|
57
|
+
### Added
|
|
58
|
+
|
|
59
|
+
- Question classes: `Typesafe::Noul`, `Typesafe::Choice`, `Typesafe::Score` with the abstract `Typesafe::Question` base. Frozen, immutable value objects that validate their inputs and serialize to the TypeSafe API shape via `#to_h`/`#to_json`.
|
|
60
|
+
|
|
61
|
+
## [0.1.0] - 2026-09-20
|
|
62
|
+
|
|
63
|
+
### Added
|
|
33
64
|
|
|
34
65
|
- Initial gem scaffold.
|
|
66
|
+
|
|
67
|
+
[unreleased]: https://github.com/dtheofr/typesafe-jev-ruby/compare/v1.0.0...HEAD
|
|
68
|
+
[1.0.0]: https://github.com/dtheofr/typesafe-jev-ruby/compare/v0.7.0...v1.0.0
|
|
69
|
+
[0.7.0]: https://github.com/dtheofr/typesafe-jev-ruby/compare/v0.6.0...v0.7.0
|
|
70
|
+
[0.6.0]: https://github.com/dtheofr/typesafe-jev-ruby/compare/v0.5.0...v0.6.0
|
|
71
|
+
[0.5.0]: https://github.com/dtheofr/typesafe-jev-ruby/compare/v0.4.1...v0.5.0
|
|
72
|
+
[0.4.1]: https://github.com/dtheofr/typesafe-jev-ruby/compare/v0.4.0...v0.4.1
|
|
73
|
+
[0.4.0]: https://github.com/dtheofr/typesafe-jev-ruby/compare/v0.3.0...v0.4.0
|
|
74
|
+
[0.3.0]: https://github.com/dtheofr/typesafe-jev-ruby/compare/v0.2.0...v0.3.0
|
|
75
|
+
[0.2.0]: https://github.com/dtheofr/typesafe-jev-ruby/compare/v0.1.0...v0.2.0
|
|
76
|
+
[0.1.0]: https://github.com/dtheofr/typesafe-jev-ruby/releases/tag/v0.1.0
|
data/LICENSE
CHANGED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 dtheofr
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
CHANGED
|
@@ -1,14 +1,147 @@
|
|
|
1
1
|
# typesafe-jev
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://github.com/dtheofr/typesafe-jev-ruby/actions/workflows/ci.yml)
|
|
4
|
+
[](https://rubygems.org/gems/typesafe-jev)
|
|
5
|
+
[](https://rubygems.org/gems/typesafe-jev)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
Ruby client for [Jev](https://docs.typesafe.ai), TypeSafe's System One model. Ask natural-language questions about your application state and get typed, probabilistic answers you can program against.
|
|
9
|
+
|
|
10
|
+
**Zero runtime dependencies**: pure Ruby stdlib (`Net::HTTP`, `JSON`), so it works in plain Ruby and Rails alike.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
gem install typesafe-jev
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
or in your `Gemfile`:
|
|
19
|
+
|
|
20
|
+
```ruby
|
|
21
|
+
gem "typesafe-jev"
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quickstart
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
require "typesafe"
|
|
28
|
+
|
|
29
|
+
response = Typesafe::Jev.evaluate(
|
|
30
|
+
state: { ticket: { text: "My payouts failed twice this week." } },
|
|
31
|
+
questions: {
|
|
32
|
+
refund_requested: Typesafe::Noul.new("Does the customer request a refund?"),
|
|
33
|
+
category: Typesafe::Choice.new("Which category does this ticket belong to?", criteria: {
|
|
34
|
+
billing: "Payment, invoices, refunds",
|
|
35
|
+
technical: "Bugs and outages",
|
|
36
|
+
sales: "Pre-sales questions"
|
|
37
|
+
}),
|
|
38
|
+
severity: Typesafe::Score.new("How severe is this ticket?", criteria: [
|
|
39
|
+
"Low: routine request",
|
|
40
|
+
"Medium: degraded experience",
|
|
41
|
+
"High: blocked user",
|
|
42
|
+
"Critical: money lost or security issue"
|
|
43
|
+
])
|
|
44
|
+
}
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
response[:refund_requested].noul # => 0.95 (yes/no probability, 0..1)
|
|
48
|
+
response[:category].choice # => "billing"
|
|
49
|
+
response[:category].probabilities # => { "billing" => 0.88, "technical" => 0.12, "sales" => 0.0 }
|
|
50
|
+
response[:severity].score # => "High: blocked user"
|
|
51
|
+
response.usage.input_tokens # => 296
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`Typesafe::Jev.evaluate` is the one-shot facade with the model pinned to `jev-latest`. For another model, use `Typesafe::Client` directly.
|
|
55
|
+
|
|
56
|
+
## Questions
|
|
57
|
+
|
|
58
|
+
Questions are frozen, immutable value objects. The question **id is not in the object**; it is the Hash key in the `questions:` map, and answers come back under the same key.
|
|
59
|
+
|
|
60
|
+
| Class | Answers with | Criteria |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| `Typesafe::Noul` | a yes/no probability (`noul`, 0..1) | optional `{ true: ..., false: ... }` |
|
|
63
|
+
| `Typesafe::Choice` | one option + full probability distribution + `confidence` | non-empty `{ key: "description" }` |
|
|
64
|
+
| `Typesafe::Score` | a level + `legend` + distribution + `confidence` | non-empty `["Level 1", "Level 2", ...]` |
|
|
65
|
+
|
|
66
|
+
```ruby
|
|
67
|
+
Typesafe::Noul.new("Does the customer request a refund?",
|
|
68
|
+
criteria: { true: "The customer explicitly asks for their money back",
|
|
69
|
+
false: "The customer does not mention refunds" })
|
|
70
|
+
|
|
71
|
+
Typesafe::Choice.new("Which category does this ticket belong to?",
|
|
72
|
+
criteria: { billing: "Payment, invoices, refunds", technical: "Bugs and outages" })
|
|
73
|
+
|
|
74
|
+
Typesafe::Score.new("How severe is this ticket?",
|
|
75
|
+
criteria: ["Low", "Medium", "High", "Critical"])
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Invalid input raises `ArgumentError` at construction time, so bad questions fail fast, before any HTTP call.
|
|
79
|
+
|
|
80
|
+
## Responses
|
|
81
|
+
|
|
82
|
+
`Client#evaluate` returns a frozen `Typesafe::Response`:
|
|
83
|
+
|
|
84
|
+
- `response[id]` (or `response.answers`): a typed `NoulAnswer`, `ChoiceAnswer` or `ScoreAnswer`; String and Symbol ids both work
|
|
85
|
+
- `response.model`: the model that performed the evaluation
|
|
86
|
+
- `response.usage`: `Typesafe::Usage` with `input_tokens` / `output_tokens`
|
|
87
|
+
- `response.to_h` / `response.to_json`: the raw API shape
|
|
88
|
+
|
|
89
|
+
Each answer type exposes its own accessors (`#noul`, `#choice`, `#probabilities`, `#confidence`, `#score`, `#legend`, …) and serializes via `#to_h`/`#to_json`.
|
|
90
|
+
|
|
91
|
+
## Errors
|
|
92
|
+
|
|
93
|
+
Any non-2xx HTTP response raises a typed error. All inherit from `Typesafe::APIError` (root: `Typesafe::Error`), which carries `#status`, `#body`, `#headers`, `#request_id` and `#retryable?`.
|
|
94
|
+
|
|
95
|
+
| Error | Status | Extras |
|
|
96
|
+
|---|---|---|
|
|
97
|
+
| `Typesafe::BadRequestError` | 400 | |
|
|
98
|
+
| `Typesafe::AuthenticationError` | 401 | |
|
|
99
|
+
| `Typesafe::PermissionDeniedError` | 403 | |
|
|
100
|
+
| `Typesafe::NotFoundError` | 404 | |
|
|
101
|
+
| `Typesafe::UnprocessableEntityError` | 422 | `#errors` (validation details) |
|
|
102
|
+
| `Typesafe::RateLimitError` | 429 | `#retry_after` |
|
|
103
|
+
| `Typesafe::OverloadedError` | 529 | |
|
|
104
|
+
| `Typesafe::ServerError` | 5xx | |
|
|
105
|
+
|
|
106
|
+
```ruby
|
|
107
|
+
begin
|
|
108
|
+
Typesafe::Jev.evaluate(state: state, questions: questions)
|
|
109
|
+
rescue Typesafe::RateLimitError => e
|
|
110
|
+
sleep(e.retry_after || 1.0)
|
|
111
|
+
retry
|
|
112
|
+
rescue Typesafe::OverloadedError
|
|
113
|
+
sleep(2**attempt)
|
|
114
|
+
retry
|
|
115
|
+
rescue Typesafe::APIError => e
|
|
116
|
+
raise "typesafe request failed (#{e.status}, request #{e.request_id}): #{e.body}"
|
|
117
|
+
end
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Configuration
|
|
121
|
+
|
|
122
|
+
The API key is read from the `TYPESAFE_API_KEY` environment variable unless passed explicitly:
|
|
123
|
+
|
|
124
|
+
```ruby
|
|
125
|
+
jev = Typesafe::Jev.new # key from ENV
|
|
126
|
+
jev = Typesafe::Jev.new(api_key: "sk-...") # or explicit
|
|
127
|
+
|
|
128
|
+
client = Typesafe::Client.new(api_key: "sk-...", model: "other-model")
|
|
129
|
+
client.evaluate(state:, questions:, model: "one-off-model") # per-call override
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Documentation
|
|
133
|
+
|
|
134
|
+
TypeSafe docs live at [docs.typesafe.ai](https://docs.typesafe.ai). The full API reference for this gem is on [rubydoc.info](https://rubydoc.info/gems/typesafe-jev).
|
|
4
135
|
|
|
5
136
|
## Development
|
|
6
137
|
|
|
7
138
|
```sh
|
|
8
139
|
bundle install
|
|
9
|
-
bundle exec rake
|
|
140
|
+
bundle exec rake spec # or: bundle exec rspec
|
|
10
141
|
```
|
|
11
142
|
|
|
143
|
+
The test suite runs against Ruby 3.1 and 3.4 in CI. See [CONTRIBUTING.md](CONTRIBUTING.md) for conventions.
|
|
144
|
+
|
|
12
145
|
## License
|
|
13
146
|
|
|
14
|
-
MIT
|
|
147
|
+
[MIT](LICENSE)
|
data/lib/typesafe/answer.rb
CHANGED
|
@@ -5,7 +5,7 @@ module Typesafe
|
|
|
5
5
|
# System One API: {NoulAnswer}, {ChoiceAnswer}, and {ScoreAnswer}.
|
|
6
6
|
#
|
|
7
7
|
# An answer is an immutable value object mirroring its question: it carries
|
|
8
|
-
# what the model returned for one question, but not the question id
|
|
8
|
+
# what the model returned for one question, but not the question id; that
|
|
9
9
|
# is the Hash key under which the answer appears in a {Response}.
|
|
10
10
|
#
|
|
11
11
|
# Use {Answer.from_h} (or {Response.from_json}) to build typed answers from
|
data/lib/typesafe/errors.rb
CHANGED
|
@@ -16,10 +16,10 @@ module Typesafe
|
|
|
16
16
|
# The `detail` field of an error body comes in three shapes, all of which
|
|
17
17
|
# are rendered into the exception message:
|
|
18
18
|
#
|
|
19
|
-
# * a plain String
|
|
20
|
-
# * a Hash with error_type/message
|
|
19
|
+
# * a plain String: `{"detail": "Unknown model: jev-99"}`
|
|
20
|
+
# * a Hash with error_type/message:
|
|
21
21
|
# `{"detail": {"error_type": "authentication_error", "message": "..."}}`
|
|
22
|
-
# * an Array of validation entries (FastAPI/pydantic style)
|
|
22
|
+
# * an Array of validation entries (FastAPI/pydantic style):
|
|
23
23
|
# `{"detail": [{"type": "missing", "loc": ["body", "questions"], "msg": "Field required"}]}`
|
|
24
24
|
class APIError < Error
|
|
25
25
|
# @return [Integer] the HTTP status code.
|
data/lib/typesafe/response.rb
CHANGED
|
@@ -8,7 +8,7 @@ module Typesafe
|
|
|
8
8
|
# response = Typesafe::Response.from_json(json)
|
|
9
9
|
# response[:refund_requested] # => #<Typesafe::NoulAnswer @noul=0.95>
|
|
10
10
|
#
|
|
11
|
-
# The answer objects do not carry their question id
|
|
11
|
+
# The answer objects do not carry their question id: it is the Hash key
|
|
12
12
|
# under which each answer appears in {#answers}, mirroring how questions
|
|
13
13
|
# are sent.
|
|
14
14
|
class Response
|
data/lib/typesafe/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: typesafe-jev
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- dtheofr
|
|
@@ -76,13 +76,15 @@ files:
|
|
|
76
76
|
- lib/typesafe/score_answer.rb
|
|
77
77
|
- lib/typesafe/usage.rb
|
|
78
78
|
- lib/typesafe/version.rb
|
|
79
|
-
homepage: https://
|
|
79
|
+
homepage: https://github.com/dtheofr/typesafe-jev-ruby
|
|
80
80
|
licenses:
|
|
81
81
|
- MIT
|
|
82
82
|
metadata:
|
|
83
|
-
homepage_uri: https://
|
|
83
|
+
homepage_uri: https://github.com/dtheofr/typesafe-jev-ruby
|
|
84
84
|
source_code_uri: https://github.com/dtheofr/typesafe-jev-ruby
|
|
85
85
|
changelog_uri: https://github.com/dtheofr/typesafe-jev-ruby/blob/main/CHANGELOG.md
|
|
86
|
+
documentation_uri: https://rubydoc.info/gems/typesafe-jev
|
|
87
|
+
rubygems_mfa_required: 'true'
|
|
86
88
|
rdoc_options: []
|
|
87
89
|
require_paths:
|
|
88
90
|
- lib
|