semantic-operators 0.1.0__tar.gz

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.
@@ -0,0 +1,21 @@
1
+ # Publishes to PyPI when a GitHub Release is published.
2
+ # Uses PyPI Trusted Publishing: no API token is stored anywhere.
3
+ # One-time setup on pypi.org: add a (pending) publisher for this repo,
4
+ # workflow "publish.yml", environment "pypi".
5
+ name: Publish to PyPI
6
+
7
+ on:
8
+ release:
9
+ types: [published]
10
+
11
+ jobs:
12
+ publish:
13
+ runs-on: ubuntu-latest
14
+ environment: pypi
15
+ permissions:
16
+ id-token: write # lets PyPI verify this workflow's identity
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+ - uses: astral-sh/setup-uv@v6
20
+ - run: uv build
21
+ - run: uv publish
@@ -0,0 +1,5 @@
1
+ .env
2
+ .venv/
3
+ __pycache__/
4
+ *.egg-info/
5
+ dist/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jason Duncan
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.
@@ -0,0 +1,195 @@
1
+ Metadata-Version: 2.5
2
+ Name: semantic-operators
3
+ Version: 0.1.0
4
+ Summary: One small, provider-neutral interface for System One models (Jev, Laya, and compatibles).
5
+ Project-URL: Homepage, https://github.com/jasonduncan/semantic-operators
6
+ Project-URL: Source, https://github.com/jasonduncan/semantic-operators
7
+ Project-URL: Issues, https://github.com/jasonduncan/semantic-operators/issues
8
+ Author: Jason Duncan
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: benchmark,classification,jev,laya,llm,system-one,typesafe
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
19
+ Classifier: Typing :: Typed
20
+ Requires-Python: >=3.11
21
+ Provides-Extra: jev
22
+ Requires-Dist: typesafe-sdk>=0.7.1; extra == 'jev'
23
+ Provides-Extra: laya
24
+ Requires-Dist: laya>=0.3.20; extra == 'laya'
25
+ Description-Content-Type: text/markdown
26
+
27
+ # Semantic Operators
28
+
29
+ One small interface for **System One models**: fast models that answer structured
30
+ questions about text or data with probabilities, not prose. [Jev](https://typesafe.ai)
31
+ was the first; [Laya](https://huggingface.co/convaiinnovations/laya) is an open-weight,
32
+ Jev-compatible alternative you can run locally. More are coming. This library lets you
33
+ write your code once and swap the model underneath.
34
+
35
+ | Provider | Where it runs | Install |
36
+ |----------|---------------|---------|
37
+ | `providers.jev.Jev` | TypeSafe's hosted API (needs `TYPESAFE_API_KEY`) | `[jev]` |
38
+ | `providers.laya.Laya` | on your machine (~800 MB download on first use) | `[laya]` |
39
+
40
+ ## Install
41
+
42
+ ```sh
43
+ pip install "semantic-operators[jev]" # Jev (hosted)
44
+ pip install "semantic-operators[laya]" # Laya (local; pulls in torch)
45
+ pip install "semantic-operators[jev,laya]" # both
46
+ ```
47
+
48
+ The core alone (`pip install semantic-operators`) has no dependencies.
49
+
50
+ ## The whole idea
51
+
52
+ A System One model is asked **named questions about a piece of state** and returns an
53
+ answer with probabilities for each. There are three kinds of question:
54
+
55
+ | Question | You give it | `answer.value` |
56
+ |-----------|-----------------------------------------------|--------------------------------------|
57
+ | `Boolean` | instructions (+ optional true/false meanings) | `True` / `False` |
58
+ | `Choice` | instructions + named options | the chosen option name |
59
+ | `Score` | instructions + ordered rubric levels | expected level as a float, e.g. `1.7` |
60
+
61
+ Every `Answer` also carries `probabilities` (a dict, in the question's option/level
62
+ order) and `raw` (the provider's own answer object).
63
+
64
+ A **provider** is anything with one method:
65
+
66
+ ```python
67
+ def ask(self, state, questions: dict[str, Question]) -> dict[str, Answer]
68
+ ```
69
+
70
+ That's the entire abstraction.
71
+
72
+ ## Quick start
73
+
74
+ ```sh
75
+ echo "TYPESAFE_API_KEY=..." > .env
76
+ uv run --env-file .env --extra jev python examples/hello.py
77
+ ```
78
+
79
+ ```python
80
+ from typesafe_sdk import TypeSafeClient
81
+ from semantic_operators import Boolean, Choice, Score
82
+ from semantic_operators.providers.jev import Jev
83
+
84
+ with TypeSafeClient() as client: # you create and own the SDK client
85
+ jev = Jev(client) # model defaults to "jev-latest"
86
+ answers = jev.ask(
87
+ "I was charged twice and I'm furious.",
88
+ {
89
+ "is_complaint": Boolean("Is the customer complaining?"),
90
+ "department": Choice("Which team should handle this?",
91
+ {"billing": "Payments, refunds", "other": "Anything else"}),
92
+ "urgency": Score("How urgent is this?", ["low", "medium", "high"]),
93
+ },
94
+ )
95
+
96
+ answers["department"].value # "billing"
97
+ answers["department"].probabilities # {"billing": 0.97, "other": 0.03}
98
+ ```
99
+
100
+ Swapping to Laya changes only how the provider is built:
101
+
102
+ ```python
103
+ import laya
104
+ from semantic_operators.providers.laya import Laya
105
+
106
+ provider = Laya(laya.load("convaiinnovations/laya")) # or Laya(laya.Router())
107
+ answers = provider.ask(state, questions) # same questions, same Answer type
108
+ ```
109
+
110
+ Compare both side by side:
111
+
112
+ ```sh
113
+ uv run --env-file .env --extra jev --extra laya python examples/compare.py
114
+ ```
115
+
116
+ ## Benchmark
117
+
118
+ `bench.run(provider, questions, cases)` asks each labeled case all questions in one call
119
+ and reports, per question, **accuracy** (Score values are rounded to the nearest level)
120
+ and **p(correct)**, the average probability the provider gave the right answer, plus
121
+ latency and every miss.
122
+
123
+ ```sh
124
+ uv run --env-file .env --extra jev --extra laya python benchmarks/run.py
125
+ ```
126
+
127
+ `benchmarks/support_tickets.py` holds 20 hand-written, hand-labeled support messages
128
+ and the same 3 questions in three wordings. `bench.stability(reports)` reports how often
129
+ a provider's decision stays the same when only the wording changes (labels play no part). It's a smoke test, not a verdict: small, authored, one person's labels.
130
+
131
+ ## Async
132
+
133
+ Every provider has an async twin with the same contract, `await provider.ask(...)`:
134
+
135
+ ```python
136
+ from typesafe_sdk import AsyncTypeSafeClient
137
+ from semantic_operators.providers.jev import AsyncJev
138
+ from semantic_operators.providers.laya import AsyncLaya
139
+
140
+ async with AsyncTypeSafeClient() as client:
141
+ answers = await AsyncJev(client).ask(state, questions)
142
+ ```
143
+
144
+ `AsyncLaya` runs the local model in a worker thread, one call at a time. Concurrency
145
+ speeds up a hosted API (many requests in flight), not a single local model.
146
+ `bench.run_async(provider, questions, cases, concurrency=8)` benchmarks async providers:
147
+
148
+ ```sh
149
+ uv run --env-file .env --extra jev --extra laya python benchmarks/run_async.py
150
+ ```
151
+
152
+ ## Layout
153
+
154
+ ```
155
+ src/semantic_operators/
156
+ types.py Boolean, Choice, Score, Answer: our vocabulary
157
+ provider.py Provider and AsyncProvider (one method each)
158
+ providers/jev.py translates to/from the TypeSafe SDK
159
+ providers/laya.py translates to/from the laya package
160
+ bench.py (higher layer) run labeled cases through a provider, score them
161
+ examples/
162
+ hello.py one real call to Jev
163
+ compare.py the same questions through Jev and Laya
164
+ benchmarks/
165
+ support_tickets.py 20 labeled messages + the questions
166
+ run.py runs the suite through Jev and Laya
167
+ run_async.py concurrency, and both providers at once
168
+ ```
169
+
170
+ ## Layers
171
+
172
+ Semantic Operators is built in layers inside one package:
173
+
174
+ 1. **Base layer:** a clean, provider-neutral abstraction over System One
175
+ models: `types.py`, `provider.py`, `providers/`.
176
+ 2. **Higher layers:** built only on the base layer. So far: `bench.py`. Later: reusable
177
+ named operators and composition.
178
+
179
+ The base layer never imports from a higher layer, so it could later be split out as its
180
+ own package without changing how it's used.
181
+
182
+ ## Rules
183
+
184
+ - The library never reads API keys or environment variables. You build the client.
185
+ - The core has no dependencies. Each provider's SDK is an optional extra (`[jev]`, `[laya]`).
186
+ - Our names, not the provider's: `Boolean`, not `noul`.
187
+
188
+ ## Not here yet (on purpose)
189
+
190
+ Reusable named operators, error types, and
191
+ "don't know" answers. Each will be added as its own small step.
192
+
193
+ ## License
194
+
195
+ MIT
@@ -0,0 +1,169 @@
1
+ # Semantic Operators
2
+
3
+ One small interface for **System One models**: fast models that answer structured
4
+ questions about text or data with probabilities, not prose. [Jev](https://typesafe.ai)
5
+ was the first; [Laya](https://huggingface.co/convaiinnovations/laya) is an open-weight,
6
+ Jev-compatible alternative you can run locally. More are coming. This library lets you
7
+ write your code once and swap the model underneath.
8
+
9
+ | Provider | Where it runs | Install |
10
+ |----------|---------------|---------|
11
+ | `providers.jev.Jev` | TypeSafe's hosted API (needs `TYPESAFE_API_KEY`) | `[jev]` |
12
+ | `providers.laya.Laya` | on your machine (~800 MB download on first use) | `[laya]` |
13
+
14
+ ## Install
15
+
16
+ ```sh
17
+ pip install "semantic-operators[jev]" # Jev (hosted)
18
+ pip install "semantic-operators[laya]" # Laya (local; pulls in torch)
19
+ pip install "semantic-operators[jev,laya]" # both
20
+ ```
21
+
22
+ The core alone (`pip install semantic-operators`) has no dependencies.
23
+
24
+ ## The whole idea
25
+
26
+ A System One model is asked **named questions about a piece of state** and returns an
27
+ answer with probabilities for each. There are three kinds of question:
28
+
29
+ | Question | You give it | `answer.value` |
30
+ |-----------|-----------------------------------------------|--------------------------------------|
31
+ | `Boolean` | instructions (+ optional true/false meanings) | `True` / `False` |
32
+ | `Choice` | instructions + named options | the chosen option name |
33
+ | `Score` | instructions + ordered rubric levels | expected level as a float, e.g. `1.7` |
34
+
35
+ Every `Answer` also carries `probabilities` (a dict, in the question's option/level
36
+ order) and `raw` (the provider's own answer object).
37
+
38
+ A **provider** is anything with one method:
39
+
40
+ ```python
41
+ def ask(self, state, questions: dict[str, Question]) -> dict[str, Answer]
42
+ ```
43
+
44
+ That's the entire abstraction.
45
+
46
+ ## Quick start
47
+
48
+ ```sh
49
+ echo "TYPESAFE_API_KEY=..." > .env
50
+ uv run --env-file .env --extra jev python examples/hello.py
51
+ ```
52
+
53
+ ```python
54
+ from typesafe_sdk import TypeSafeClient
55
+ from semantic_operators import Boolean, Choice, Score
56
+ from semantic_operators.providers.jev import Jev
57
+
58
+ with TypeSafeClient() as client: # you create and own the SDK client
59
+ jev = Jev(client) # model defaults to "jev-latest"
60
+ answers = jev.ask(
61
+ "I was charged twice and I'm furious.",
62
+ {
63
+ "is_complaint": Boolean("Is the customer complaining?"),
64
+ "department": Choice("Which team should handle this?",
65
+ {"billing": "Payments, refunds", "other": "Anything else"}),
66
+ "urgency": Score("How urgent is this?", ["low", "medium", "high"]),
67
+ },
68
+ )
69
+
70
+ answers["department"].value # "billing"
71
+ answers["department"].probabilities # {"billing": 0.97, "other": 0.03}
72
+ ```
73
+
74
+ Swapping to Laya changes only how the provider is built:
75
+
76
+ ```python
77
+ import laya
78
+ from semantic_operators.providers.laya import Laya
79
+
80
+ provider = Laya(laya.load("convaiinnovations/laya")) # or Laya(laya.Router())
81
+ answers = provider.ask(state, questions) # same questions, same Answer type
82
+ ```
83
+
84
+ Compare both side by side:
85
+
86
+ ```sh
87
+ uv run --env-file .env --extra jev --extra laya python examples/compare.py
88
+ ```
89
+
90
+ ## Benchmark
91
+
92
+ `bench.run(provider, questions, cases)` asks each labeled case all questions in one call
93
+ and reports, per question, **accuracy** (Score values are rounded to the nearest level)
94
+ and **p(correct)**, the average probability the provider gave the right answer, plus
95
+ latency and every miss.
96
+
97
+ ```sh
98
+ uv run --env-file .env --extra jev --extra laya python benchmarks/run.py
99
+ ```
100
+
101
+ `benchmarks/support_tickets.py` holds 20 hand-written, hand-labeled support messages
102
+ and the same 3 questions in three wordings. `bench.stability(reports)` reports how often
103
+ a provider's decision stays the same when only the wording changes (labels play no part). It's a smoke test, not a verdict: small, authored, one person's labels.
104
+
105
+ ## Async
106
+
107
+ Every provider has an async twin with the same contract, `await provider.ask(...)`:
108
+
109
+ ```python
110
+ from typesafe_sdk import AsyncTypeSafeClient
111
+ from semantic_operators.providers.jev import AsyncJev
112
+ from semantic_operators.providers.laya import AsyncLaya
113
+
114
+ async with AsyncTypeSafeClient() as client:
115
+ answers = await AsyncJev(client).ask(state, questions)
116
+ ```
117
+
118
+ `AsyncLaya` runs the local model in a worker thread, one call at a time. Concurrency
119
+ speeds up a hosted API (many requests in flight), not a single local model.
120
+ `bench.run_async(provider, questions, cases, concurrency=8)` benchmarks async providers:
121
+
122
+ ```sh
123
+ uv run --env-file .env --extra jev --extra laya python benchmarks/run_async.py
124
+ ```
125
+
126
+ ## Layout
127
+
128
+ ```
129
+ src/semantic_operators/
130
+ types.py Boolean, Choice, Score, Answer: our vocabulary
131
+ provider.py Provider and AsyncProvider (one method each)
132
+ providers/jev.py translates to/from the TypeSafe SDK
133
+ providers/laya.py translates to/from the laya package
134
+ bench.py (higher layer) run labeled cases through a provider, score them
135
+ examples/
136
+ hello.py one real call to Jev
137
+ compare.py the same questions through Jev and Laya
138
+ benchmarks/
139
+ support_tickets.py 20 labeled messages + the questions
140
+ run.py runs the suite through Jev and Laya
141
+ run_async.py concurrency, and both providers at once
142
+ ```
143
+
144
+ ## Layers
145
+
146
+ Semantic Operators is built in layers inside one package:
147
+
148
+ 1. **Base layer:** a clean, provider-neutral abstraction over System One
149
+ models: `types.py`, `provider.py`, `providers/`.
150
+ 2. **Higher layers:** built only on the base layer. So far: `bench.py`. Later: reusable
151
+ named operators and composition.
152
+
153
+ The base layer never imports from a higher layer, so it could later be split out as its
154
+ own package without changing how it's used.
155
+
156
+ ## Rules
157
+
158
+ - The library never reads API keys or environment variables. You build the client.
159
+ - The core has no dependencies. Each provider's SDK is an optional extra (`[jev]`, `[laya]`).
160
+ - Our names, not the provider's: `Boolean`, not `noul`.
161
+
162
+ ## Not here yet (on purpose)
163
+
164
+ Reusable named operators, error types, and
165
+ "don't know" answers. Each will be added as its own small step.
166
+
167
+ ## License
168
+
169
+ MIT
@@ -0,0 +1,38 @@
1
+ """Run the support-ticket benchmark through Jev and Laya, in every wording.
2
+
3
+ Run: uv run --env-file .env --extra jev --extra laya python benchmarks/run.py
4
+ Makes 63 Jev API calls (3 wordings x (1 warm-up + 20 cases)).
5
+ """
6
+
7
+ import statistics
8
+
9
+ import laya
10
+ from typesafe_sdk import TypeSafeClient
11
+
12
+ from semantic_operators import Provider
13
+ from semantic_operators.bench import run, stability
14
+ from semantic_operators.providers.jev import Jev
15
+ from semantic_operators.providers.laya import Laya
16
+ from support_tickets import cases, wordings
17
+
18
+
19
+ def benchmark(name: str, provider: Provider) -> None:
20
+ reports = {wording: run(provider, qs, cases) for wording, qs in wordings.items()}
21
+ stable = stability(list(reports.values()))
22
+ latencies = [ms for r in reports.values() for ms in r.latencies_ms]
23
+
24
+ print(f"\n{name}: median {statistics.median(latencies):.0f} ms per call")
25
+ print(" accuracy, p(correct) in brackets")
26
+ print(f" {'question':<14}" + "".join(f"{w:>18}" for w in reports) + f"{'stability':>12}")
27
+ for question in stable:
28
+ cells = "".join(
29
+ f"{f'{s.correct}/{s.total} ({s.mean_p_correct:.2f})':>18}"
30
+ for s in (r.questions[question] for r in reports.values())
31
+ )
32
+ print(f" {question:<14}{cells}{stable[question]:>12.0%}")
33
+
34
+
35
+ with TypeSafeClient() as client:
36
+ benchmark("Jev", Jev(client))
37
+
38
+ benchmark("Laya", Laya(laya.load("convaiinnovations/laya")))
@@ -0,0 +1,47 @@
1
+ """What concurrency buys each provider, and both providers benchmarked at once.
2
+
3
+ Run: uv run --env-file .env --extra jev --extra laya python benchmarks/run_async.py
4
+ Makes 63 Jev API calls (3 runs x (1 warm-up + 20 cases)).
5
+ """
6
+
7
+ import asyncio
8
+ import statistics
9
+
10
+ import laya
11
+ from typesafe_sdk import AsyncTypeSafeClient
12
+
13
+ from semantic_operators.bench import Report, run_async
14
+ from semantic_operators.providers.jev import AsyncJev
15
+ from semantic_operators.providers.laya import AsyncLaya
16
+ from support_tickets import cases, questions
17
+
18
+
19
+ def show(label: str, report: Report) -> None:
20
+ correct = sum(s.correct for s in report.questions.values())
21
+ total = sum(s.total for s in report.questions.values())
22
+ print(f" {label:<28} total {report.total_ms:>6.0f} ms "
23
+ f"per call: median {statistics.median(report.latencies_ms):>4.0f} ms, "
24
+ f"slowest {max(report.latencies_ms):>4.0f} ms correct {correct}/{total}")
25
+
26
+
27
+ async def main() -> None:
28
+ async with AsyncTypeSafeClient() as client:
29
+ jev = AsyncJev(client)
30
+ lay = AsyncLaya(laya.load("convaiinnovations/laya"))
31
+
32
+ for name, provider in [("Jev", jev), ("Laya", lay)]:
33
+ print(f"\n{name}")
34
+ for concurrency in (1, 8):
35
+ report = await run_async(provider, questions, cases, concurrency=concurrency)
36
+ show(f"concurrency {concurrency}", report)
37
+
38
+ print("\nBoth at once (concurrency 8 each)")
39
+ jev_report, laya_report = await asyncio.gather(
40
+ run_async(jev, questions, cases, concurrency=8),
41
+ run_async(lay, questions, cases, concurrency=8),
42
+ )
43
+ show("Jev", jev_report)
44
+ show("Laya", laya_report)
45
+
46
+
47
+ asyncio.run(main())
@@ -0,0 +1,111 @@
1
+ """Support-ticket triage: 20 hand-written messages, labeled by hand.
2
+
3
+ These are authored examples, not real customer data, and one person's labels.
4
+ "Urgency" in particular is a judgment call. Treat results as a smoke test of
5
+ the providers, not a verdict on them.
6
+ """
7
+
8
+ from semantic_operators import Boolean, Choice, Score
9
+ from semantic_operators.bench import Case
10
+
11
+ # The same three questions, worded three ways. Option names and level order are
12
+ # identical across wordings, so one set of labels applies to all of them.
13
+ wordings = {
14
+ "descriptive": {
15
+ "is_complaint": Boolean("Is the customer complaining or expressing dissatisfaction?"),
16
+ "department": Choice(
17
+ "Which team should handle this message?",
18
+ {
19
+ "billing": "Charges, invoices, payments, refunds, or pricing.",
20
+ "technical": "Bugs, errors, outages, or difficulty using the product.",
21
+ "account": "Logging in, passwords, profile details, or account access and closure.",
22
+ "other": "Anything else, such as feedback, partnerships, or general questions.",
23
+ },
24
+ ),
25
+ "urgency": Score(
26
+ "How urgently does this need a response?",
27
+ ["low: can wait days", "medium: should be handled today", "high: needs attention now"],
28
+ ),
29
+ },
30
+ "plain": {
31
+ "is_complaint": Boolean("Is this a complaint?"),
32
+ "department": Choice(
33
+ "Which department?",
34
+ {"billing": None, "technical": None, "account": None, "other": None},
35
+ ),
36
+ "urgency": Score("How urgent is this?", ["not urgent", "soon", "urgent"]),
37
+ },
38
+ "reworded": {
39
+ "is_complaint": Boolean("Does the writer express frustration, disappointment, or a grievance?"),
40
+ "department": Choice(
41
+ "Route this message to the right team.",
42
+ {
43
+ "billing": "Money: payments, invoices, refunds, prices.",
44
+ "technical": "The product not working as expected.",
45
+ "account": "Sign-in, credentials, account settings, or deleting the account.",
46
+ "other": "None of the above.",
47
+ },
48
+ ),
49
+ "urgency": Score(
50
+ "How quickly should support reply?",
51
+ ["whenever convenient", "within the day", "immediately"],
52
+ ),
53
+ },
54
+ }
55
+
56
+ questions = wordings["descriptive"]
57
+
58
+ LOW, MEDIUM, HIGH = 0, 1, 2 # urgency labels are level indexes
59
+
60
+
61
+ def case(state: str, complaint: bool, department: str, urgency: int) -> Case:
62
+ return Case(state, {"is_complaint": complaint, "department": department, "urgency": urgency})
63
+
64
+
65
+ cases = [
66
+ # Clear-cut
67
+ case("I was charged twice for my subscription this month. Please refund one of them.",
68
+ True, "billing", MEDIUM),
69
+ case("Your whole site has been down for an hour and our store can't take any orders!",
70
+ True, "technical", HIGH),
71
+ case("How do I change the email address on my profile?",
72
+ False, "account", LOW),
73
+ case("Can you send me a copy of last month's invoice for our accountant?",
74
+ False, "billing", LOW),
75
+ case("The export button does nothing when I click it. Tried Chrome and Safari.",
76
+ True, "technical", MEDIUM),
77
+ case("I think someone else logged into my account, I see orders I never placed. Help!",
78
+ True, "account", HIGH),
79
+ case("Just wanted to say the new dashboard is fantastic. Great work, team!",
80
+ False, "other", LOW),
81
+ case("We're a design agency interested in a partnership. Who should we talk to?",
82
+ False, "other", LOW),
83
+ case("Payment failed three times at checkout and now my card is locked by the bank.",
84
+ True, "billing", HIGH),
85
+ case("I forgot my password and the reset email never arrives.",
86
+ True, "account", MEDIUM),
87
+
88
+ # Harder: sarcasm, politeness masking a problem, mixed topics, JSON state
89
+ case("Oh great, another 'minor update' that wiped all my saved reports. Love it.",
90
+ True, "technical", HIGH),
91
+ case("No rush at all, but I noticed the annual plan price on your site doesn't match "
92
+ "what I was billed.",
93
+ True, "billing", LOW),
94
+ case("Please delete my account and all my data. I no longer want to use this service.",
95
+ False, "account", MEDIUM),
96
+ case("Is there a discount for nonprofits?",
97
+ False, "billing", LOW),
98
+ case("The app keeps crashing, and since I can't use it I want a refund for this month.",
99
+ True, "billing", MEDIUM),
100
+ case("Our CEO is presenting from your platform in 20 minutes and nothing loads.",
101
+ True, "technical", HIGH),
102
+ case("Thanks for fixing the login bug so quickly yesterday!",
103
+ False, "other", LOW),
104
+ case("I've emailed three times about my refund and nobody has replied. Unacceptable.",
105
+ True, "billing", HIGH),
106
+ case({"channel": "chat", "plan": "enterprise", "message": "SSO login is failing for all "
107
+ "500 of our users since this morning."},
108
+ True, "account", HIGH),
109
+ case({"channel": "email", "plan": "free", "message": "Where can I find your API docs?"},
110
+ False, "other", LOW),
111
+ ]
@@ -0,0 +1,51 @@
1
+ """Ask Jev (hosted) and Laya (local) the same questions, through the same interface.
2
+
3
+ Run: uv run --env-file .env --extra jev --extra laya python examples/compare.py
4
+ The first run downloads the Laya checkpoint (~800 MB) from Hugging Face.
5
+ """
6
+
7
+ import time
8
+
9
+ import laya
10
+ from typesafe_sdk import TypeSafeClient
11
+
12
+ from semantic_operators import Boolean, Choice, Provider, Score
13
+ from semantic_operators.providers.jev import Jev
14
+ from semantic_operators.providers.laya import Laya
15
+
16
+ message = "I was charged twice for my subscription this month and I'm furious."
17
+
18
+ questions = {
19
+ "is_complaint": Boolean("Is the customer complaining?"),
20
+ "department": Choice(
21
+ "Which team should handle this?",
22
+ {
23
+ "billing": "Charges, invoices, payments, or refunds.",
24
+ "technical": "A product defect or difficulty using the product.",
25
+ "other": "Anything else.",
26
+ },
27
+ ),
28
+ "urgency": Score("How urgent is this request?", ["low", "medium", "high"]),
29
+ }
30
+
31
+
32
+ def show(name: str, provider: Provider) -> None:
33
+ start = time.perf_counter()
34
+ answers = provider.ask(message, questions)
35
+ ms = (time.perf_counter() - start) * 1000
36
+ print(f"\n{name} ({ms:.0f} ms)")
37
+ for question, answer in answers.items():
38
+ value = f"{answer.value:.2f}" if isinstance(answer.value, float) else repr(answer.value)
39
+ probabilities = ", ".join(f"{k}={p:.2f}" for k, p in answer.probabilities.items())
40
+ print(f" {question:>13}: {value:<12} ({probabilities})")
41
+
42
+
43
+ print(f"Message: {message}")
44
+
45
+ with TypeSafeClient() as client:
46
+ show("Jev", Jev(client))
47
+
48
+ model = laya.load("convaiinnovations/laya") # English checkpoint, runs on this machine
49
+ laya_provider = Laya(model)
50
+ laya_provider.ask(message, questions) # warm-up: the first call pays one-time setup costs
51
+ show("Laya", laya_provider)