pinecall 0.0.5 → 0.0.7

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: 0571abe150af681966473dd3a8e8123137743bbecf63d6c4144632f986eb304a
4
- data.tar.gz: 55c632d74f2440250049ecc84bad96d611b7d270cea5130d01154674b84761ad
3
+ metadata.gz: f33416ca47b6dac53cbee96b457cd9d8961c27439b7f5568834ca64c561c5e89
4
+ data.tar.gz: deaf2b2a704a6dbda1a8b88f21f46fabb327ee7dc22eab9f858cf2e5dca7d694
5
5
  SHA512:
6
- metadata.gz: c476476012157ce39d48b2e1b07ccd33c5533d217ff2d33037e423d05b48a49986e556638fbca2410890c739aeb00b13da0076415e1c2f62c6a8f1a0452a239e
7
- data.tar.gz: d42a9901721b9e179fbddd3ffba1b2c4ff1805c3abceaaafe4b5d77aecf0990dc1265685e762164cdbad4d11b3a4b77a20f6bba5ceeab06ccd5b95cc8ee7445e
6
+ metadata.gz: 671735c18ef00423d7e4c7a1fbefbd87c0f5087b23a0d9fa556bc151a19df928fa0c7bbebcb9977858bdf3cb54645a7d570d0e3e0fe6ad5bb5fb8977575d45ea
7
+ data.tar.gz: 5ed8532ef612a31608e11e70016045e8da92d35dcfe078e6dd976680e729695b4e8b6eee2448cb4567991592294fe67cb4519ad53ed05bbf27878c81ed1d66e0
data/CHANGELOG.md CHANGED
@@ -6,6 +6,23 @@ version number is the human's call.
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.0.7] — Every judge is a model's answer, and the class names the judge's model (2026-10-10)
10
+
11
+ - `judge "<vendor>/<model>", builds:, options:`: the model the agent's calls are judged on, over
12
+ the org's choice and Pinecall's. On a key of the org's own — a local model through
13
+ `options: { base_url: "…" }` among them — its evals are never billed.
14
+ - The wire reads what the runtime now writes: a judge's `na` and `classified` answers with their
15
+ `choice` or `score`, `call.score`'s `evals` and `own_key`, and `call.summary`'s `simulated`.
16
+
17
+ ## [0.0.6] — A greeting is words or :improvise, a hangup is words or true, and the ears choose who ends the turn (2026-10-10)
18
+
19
+ - `greeting "…"` (said as written), `greeting :improvise` (the model opens on its prompt) and
20
+ `greeting improvise("…")` (with an instruction); `interruptible: true` for an opening the caller
21
+ may cut short, which by default they cannot. The `say:` / `reply:` keywords are gone.
22
+ - `hangup "…"` (when, in your words) or `hangup true` (whenever the model judges); `when:` is gone.
23
+ - `stt "…", end_of_turn: :stt | :livekit | :smart_turn`: who says the caller's turn is over, as
24
+ @pinecall/agents 0.9.26 says it. Needs runtime 0.1.43.
25
+
9
26
  ## [0.0.5] — The class declares its voice, its models and the rest, and wins (2026-10-10)
10
27
 
11
28
  - **A class declares its environment again, and it wins over the settings.** Class macros `voice
@@ -13,15 +13,41 @@ module Pinecall
13
13
  AS_WRITTEN = %i[phone whatsapp web channel_rules].freeze
14
14
 
15
15
  # The settings a class may declare. Must match the TypeScript package's `ENVIRONMENT`.
16
- ENVIRONMENT = %i[language voice llm stt greeting hangup turn says hears knowledge docs memory record].freeze
16
+ ENVIRONMENT = %i[language voice llm stt judge greeting hangup turn says hears knowledge docs memory record].freeze
17
17
 
18
18
  # The ones written as the wire's own fields, keyword by keyword: `greeting say: "…"`.
19
- AS_KEYWORDS = %i[greeting hangup turn knowledge docs memory].freeze
19
+ AS_KEYWORDS = %i[turn knowledge docs memory].freeze
20
20
 
21
21
  NO_VOICE = "a voice the class declares names its vendor and the voice: voice \"<vendor>\", \"<voice id>\""
22
22
 
23
+ NO_OPENING = "a greeting is the words, as a string, or :improvise for the model's own: improvise(\"…\") gives it an instruction"
24
+ NO_ENDING = "hangup is when the model may end the call, in your words, or true whenever it judges the call done"
25
+
26
+ # The model opens the call: on its prompt alone (an empty instruction), or with this one.
27
+ Improvised = Data.define(:instruction, :interruptible)
28
+
23
29
  NOTHING = Object.new.freeze
24
30
 
31
+ # An opening as the wire says it: words to `say`, or a `reply` the model opens on.
32
+ def self.greeting_of(opening, interruptible)
33
+ said = case opening
34
+ when :improvise then { reply: "" }
35
+ when Improvised then { reply: opening.instruction }
36
+ when String then { say: opening }
37
+ else raise DeclarationRefused, NO_OPENING
38
+ end
39
+ given = opening.is_a?(Improvised) ? opening.interruptible : interruptible
40
+ given.nil? ? said : { **said, allow_interruptions: given }
41
+ end
42
+
43
+ # When the model may end the call: an empty `when` is whenever it judges.
44
+ def self.hangup_of(ending)
45
+ return { when: "" } if ending == true
46
+ raise DeclarationRefused, NO_ENDING unless ending.is_a?(String) && !ending.strip.empty?
47
+
48
+ { when: ending }
49
+ end
50
+
25
51
  # `vendor/model`, or a vendor alone; the model id keeps every slash after the vendor's.
26
52
  def self.model_of(named)
27
53
  provider, _slash, model = named.to_s.partition("/")
@@ -55,11 +81,41 @@ module Pinecall
55
81
  environment[:llm] = { **Config.model_of(model), temperature:, builds:, options: }.compact
56
82
  end
57
83
 
58
- # The ears, `vendor/model` or a vendor alone.
59
- def stt(model = NOTHING, builds: nil, options: nil)
84
+ # The ears, `vendor/model` or a vendor alone, and who says the caller's turn is over:
85
+ # `end_of_turn: "stt"` the ears themselves (Deepgram Flux), `"livekit"` or `"smart-turn"`.
86
+ def stt(model = NOTHING, builds: nil, options: nil, end_of_turn: nil)
60
87
  return environment[:stt] if model.equal?(NOTHING)
61
88
 
62
- environment[:stt] = { **Config.model_of(model), builds:, options: }.compact
89
+ ends = end_of_turn&.to_s&.tr("_", "-")
90
+ environment[:stt] = { **Config.model_of(model), builds:, options:, end_of_turn: ends }.compact
91
+ end
92
+
93
+ # The model the agent's calls are judged on, over the org's choice and the platform's:
94
+ # `judge "openai/qwen3-32b", options: { base_url: "http://gpu:8000/v1" }`. On a key of the
95
+ # org's own, a local model's server among them, its evals are never billed; `builds` and
96
+ # `options` run on the org's own key alone.
97
+ def judge(model = NOTHING, builds: nil, options: nil)
98
+ return environment[:judge] if model.equal?(NOTHING)
99
+
100
+ environment[:judge] = { **Config.model_of(model), builds:, options: }.compact
101
+ end
102
+
103
+ # How a call opens: the words, said as written, or :improvise for the model's own. The
104
+ # caller cannot cut it short unless `interruptible: true`.
105
+ def greeting(opening = NOTHING, interruptible: nil)
106
+ return environment[:greeting] if opening.equal?(NOTHING)
107
+
108
+ environment[:greeting] = Config.greeting_of(opening, interruptible)
109
+ end
110
+
111
+ # The model opens the call with this instruction: `greeting improvise("…")`.
112
+ def improvise(instruction = "", interruptible: nil) = Improvised.new(instruction, interruptible)
113
+
114
+ # When the model may end the call: in your words, or `true` whenever it judges it done.
115
+ def hangup(ending = NOTHING)
116
+ return environment[:hangup] if ending.equal?(NOTHING)
117
+
118
+ environment[:hangup] = Config.hangup_of(ending)
63
119
  end
64
120
 
65
121
  # `language "es"`, `says [{ word: "GSA", spoken: "ge ese a" }]`, `record false`.
@@ -78,8 +134,8 @@ module Pinecall
78
134
  environment[:hears] = words.flatten
79
135
  end
80
136
 
81
- # `greeting say: "…"`, `hangup when: "…"`, `turn endpointing_ms: 300`, `docs base: "…", k: 4`,
82
- # `memory remember: […], forget: […]`, `knowledge path: "…", text: "…"`.
137
+ # `turn endpointing_ms: 300`, `docs base: "…", k: 4`, `memory remember: […], forget: […]`,
138
+ # `knowledge path: "…", text: "…"`.
83
139
  AS_KEYWORDS.each do |field|
84
140
  define_method(field) do |**given|
85
141
  return environment[field] if given.empty?
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Pinecall
4
4
  # Set by the maintainer at release time.
5
- VERSION = "0.0.5"
5
+ VERSION = "0.0.7"
6
6
  end
@@ -34,9 +34,10 @@ module Pinecall
34
34
  # Whose action ended the call. platform covers timeouts, errors and a drained worker.
35
35
  ENDED_BY = %w[caller agent supervisor platform].freeze
36
36
  # What one judge answered about a finished call. Held: the rule held. Broken: it did not,
37
- # and the reason names the evidence. Deferred: the judge was asked and could not settle it.
38
- # Skipped: nobody asked it — no model was reachable inside the call's judging budget.
39
- SCORE_VERDICT = %w[held broken deferred skipped].freeze
37
+ # and the reason names the evidence. Classified: a choice or a score was answered. N/A: the
38
+ # question did not apply. Deferred: the judge was asked and could not settle it. Skipped:
39
+ # nobody asked it — no model, the ceiling reached, or the org's evals used up.
40
+ SCORE_VERDICT = %w[held broken classified na deferred skipped].freeze
40
41
  # Cold: the caller is sent on with a REFER on their SIP leg and the call ends here. Warm:
41
42
  # the number is dialled into the call's own room, the agent stays on the line until the
42
43
  # other side answers and then falls silent; the call ends when either of them hangs up.
@@ -81,6 +81,8 @@ module Pinecall
81
81
  judges: { kind: :list, items: { kind: :ref, ref: "Judgment" }, required: true },
82
82
  panel: { kind: :list, items: { kind: :str } },
83
83
  judge_calls: { kind: :int, required: true },
84
+ evals: { kind: :int },
85
+ own_key: { kind: :bool },
84
86
  judge_cost_usd: { kind: :float },
85
87
  judged_by: { kind: :ref, ref: "JudgedBy" }
86
88
  }.freeze,
@@ -108,7 +110,8 @@ module Pinecall
108
110
  turns: { kind: :int, required: true },
109
111
  usage: { kind: :list, items: { kind: :ref, ref: "ModelUsage" }, required: true },
110
112
  cost: { kind: :ref, ref: "Cost", required: true },
111
- recording: { kind: :str }
113
+ recording: { kind: :str },
114
+ simulated: { kind: :bool }
112
115
  }.freeze,
113
116
  "CallTransferred" => {
114
117
  to: { kind: :str, required: true },
@@ -23,7 +23,8 @@ module Pinecall
23
23
  model: { kind: :str, required: true },
24
24
  temperature: { kind: :float },
25
25
  builds: { kind: :str },
26
- options: { kind: :json }
26
+ options: { kind: :json },
27
+ end_of_turn: { kind: :enum, values: %w[stt livekit smart-turn] }
27
28
  }.freeze,
28
29
  "TurnConfig" => {
29
30
  min_interruption_words: { kind: :int },
@@ -23,6 +23,7 @@ module Pinecall
23
23
  voice: { kind: :ref, ref: "VoiceConfig" },
24
24
  llm: { kind: :ref, ref: "ModelConfig" },
25
25
  stt: { kind: :ref, ref: "ModelConfig" },
26
+ judge: { kind: :ref, ref: "ModelConfig" },
26
27
  turn: { kind: :ref, ref: "TurnConfig" },
27
28
  says: { kind: :list, items: { kind: :ref, ref: "Pronunciation" } },
28
29
  hears: { kind: :list, items: { kind: :str } },
@@ -318,7 +318,9 @@ module Pinecall
318
318
  verdict: { kind: :ref, ref: "ScoreVerdict", required: true },
319
319
  criteria: { kind: :str, required: true },
320
320
  reason: { kind: :str, required: true },
321
- evidence: { kind: :ref, ref: "JudgmentEvidence", required: true }
321
+ evidence: { kind: :ref, ref: "JudgmentEvidence", required: true },
322
+ choice: { kind: :str, null: true },
323
+ score: { kind: :int, null: true }
322
324
  }.freeze,
323
325
  "JudgmentEvidence" => {
324
326
  seqs: { kind: :list, items: { kind: :int }, required: true },
data/sig/pinecall.rbs CHANGED
@@ -87,13 +87,15 @@ module Pinecall
87
87
  # the environment the class declares, each over the agent's settings
88
88
  def self.voice: (?String, ?String?, ?model: String?, ?builds: String?, ?options: Hash[untyped, untyped]?) -> untyped
89
89
  def self.llm: (?String, ?temperature: Float?, ?builds: String?, ?options: Hash[untyped, untyped]?) -> untyped
90
- def self.stt: (?String, ?builds: String?, ?options: Hash[untyped, untyped]?) -> untyped
90
+ def self.stt: (?String, ?builds: String?, ?options: Hash[untyped, untyped]?, ?end_of_turn: (String | Symbol)?) -> untyped
91
+ def self.judge: (?String, ?builds: String?, ?options: Hash[untyped, untyped]?) -> untyped
91
92
  def self.language: (?String) -> untyped
92
93
  def self.says: (?Array[Hash[Symbol, String]]) -> untyped
93
94
  def self.record: (?bool) -> untyped
94
95
  def self.hears: (*String | Array[String]) -> untyped
95
- def self.greeting: (**untyped) -> untyped
96
- def self.hangup: (**untyped) -> untyped
96
+ def self.greeting: (?(String | Symbol | untyped), ?interruptible: bool?) -> untyped
97
+ def self.improvise: (?String, ?interruptible: bool?) -> untyped
98
+ def self.hangup: (?(String | true)) -> untyped
97
99
  def self.turn: (**untyped) -> untyped
98
100
  def self.knowledge: (**untyped) -> untyped
99
101
  def self.docs: (**untyped) -> untyped
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pinecall
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pinecall