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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0627352ab282ec9a72607ebbc8d39bc73e2577231c567b4c66a7fc74e6140a0e
4
- data.tar.gz: 218abce1d559c90b75e1b6bcf5872ff3f661d453dc6dd5c9bd15613141a89f1a
3
+ metadata.gz: 7545ebcb0bdc5d72f1038ae64b4305c3d5aeb6110b0b5e53b0c37cb88de77ee5
4
+ data.tar.gz: 50b7471d5f3c5c5dad8c1c4b14c0dbc3f2b062fe0ee7e4cd076d2ff44b4b46e0
5
5
  SHA512:
6
- metadata.gz: ff6c07bf97933605fe0aa29c6d63fc6ed2390fa9cde14e9fc0016d17c373b45a85482f58ccf98b4df5279bb1c43e9174db8521faa4599c0ab2656eb94bb04b48
7
- data.tar.gz: d6cd7d83fcff85966c653434fa64a5fd87a0a5fa23a7970643c5669ac298b367a4ead66d7eb44ebefdb32ab4f2a54963af1bdf5a9eeb2254952ecca9b966addd
6
+ metadata.gz: 3d8c3637a1d48b857019ac364b7aca9875cf0accad92023021a02f00488569b518c685afceccb8c117e98bf72e80f63eae3469d632b213c745e7b6b7fd88a4ca
7
+ data.tar.gz: f4f8167797e08f99b23a0faa879ffe10e7d01370487e4c18d4472ef52256835df82c63426c37bb8d847672502f71902624f375520108880447880aace31c00ba
data/CHANGELOG.md CHANGED
@@ -1,34 +1,76 @@
1
1
  # Changelog
2
2
 
3
- ## 0.7.0
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
- - Add 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`.
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
- ## 0.4.1
41
+ - Gemspec metadata: point `source_code_uri`/`changelog_uri` and author/email at the `dtheofr` GitHub account.
17
42
 
18
- - Fix gemspec metadata: point `source_code_uri`/`changelog_uri` and author/email at the `dtheofr` GitHub account.
43
+ ## [0.4.0] - 2026-09-20
19
44
 
20
- ## 0.4.0
45
+ ### Added
21
46
 
22
- - Add `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)`.
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
- - Add `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.
51
+ ### Added
27
52
 
28
- ## 0.2.0
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
- - Add 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`.
55
+ ## [0.2.0] - 2026-09-20
31
56
 
32
- ## 0.1.0
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
- Ruby client for [Jev](https://docs.typesafe.ai), TypeSafe's System One model — starting point, under construction.
3
+ [![CI](https://github.com/dtheofr/typesafe-jev-ruby/actions/workflows/ci.yml/badge.svg)](https://github.com/dtheofr/typesafe-jev-ruby/actions/workflows/ci.yml)
4
+ [![Gem Version](https://img.shields.io/gem/v/typesafe-jev?logo=rubygems&color=brightgreen)](https://rubygems.org/gems/typesafe-jev)
5
+ [![Ruby](https://img.shields.io/badge/ruby-%3E%3D%203.1-red?logo=ruby)](https://rubygems.org/gems/typesafe-jev)
6
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](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 test
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)
@@ -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 that
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
@@ -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 `{"detail": "Unknown model: jev-99"}`
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.
@@ -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 it is the Hash key
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Typesafe
4
- VERSION = "0.7.0"
4
+ VERSION = "1.0.0"
5
5
  end
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.7.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://docs.typesafe.ai
79
+ homepage: https://github.com/dtheofr/typesafe-jev-ruby
80
80
  licenses:
81
81
  - MIT
82
82
  metadata:
83
- homepage_uri: https://docs.typesafe.ai
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