samagotchi 0.155.0 → 0.156.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 +14 -1
- data/bin/chi +1 -1
- data/docs/architecture.md +10 -6
- data/docs/internals/anthropic.md +1 -1
- data/docs/internals/attached-context.md +3 -2
- data/docs/internals/background-tasks.md +1 -1
- data/docs/internals/engine.md +625 -935
- data/docs/internals/llm-context-forget.md +8 -3
- data/docs/internals/objectives.md +2 -2
- data/docs/internals/prompt-caching.md +4 -1
- data/docs/internals/remote-sessions.md +3 -1
- data/docs/internals/working-set.md +1 -1
- data/docs/sessions.md +2 -2
- data/docs/system-one.md +3 -2
- data/lib/samagotchi/bootstrap/config_writer.rb +1 -1
- data/lib/samagotchi/broadcast_command.rb +3 -3
- data/lib/samagotchi/bundle_command/try/sandbox.rb +1 -1
- data/lib/samagotchi/bundles/system/config_modification_protocol.md +2 -2
- data/lib/samagotchi/bundles/system/manifest.yml +2 -2
- data/lib/samagotchi/child_move.rb +7 -51
- data/lib/samagotchi/children_status.rb +1 -1
- data/lib/samagotchi/cli/parent_wait.rb +1 -1
- data/lib/samagotchi/config.rb +17 -112
- data/lib/samagotchi/context_command.rb +1 -2
- data/lib/samagotchi/context_window.rb +3 -3
- data/lib/samagotchi/desktop/macos.rb +1 -1
- data/lib/samagotchi/engine/context_judge.rb +19 -11
- data/lib/samagotchi/engine/messages.rb +1 -1
- data/lib/samagotchi/engine/models.rb +5 -2
- data/lib/samagotchi/engine/objectives.rb +29 -11
- data/lib/samagotchi/engine/side_requests.rb +21 -11
- data/lib/samagotchi/engine/toolkit.rb +10 -9
- data/lib/samagotchi/engine/turn_end.rb +1 -1
- data/lib/samagotchi/engine/turn_helpers.rb +7 -7
- data/lib/samagotchi/engine/verify.rb +1 -1
- data/lib/samagotchi/engine.rb +14 -9
- data/lib/samagotchi/guardrails/targets.rb +24 -9
- data/lib/samagotchi/host_config.rb +72 -1
- data/lib/samagotchi/host_model.rb +2 -6
- data/lib/samagotchi/host_registry.rb +1 -1
- data/lib/samagotchi/kernel_loop.rb +46 -121
- data/lib/samagotchi/llm/anthropic_wire.rb +2 -2
- data/lib/samagotchi/llm/chat_loop.rb +34 -18
- data/lib/samagotchi/llm/native_backend.rb +1 -1
- data/lib/samagotchi/llm_context_apply.rb +54 -1
- data/lib/samagotchi/llm_context_notice.rb +1 -1
- data/lib/samagotchi/llm_context_strategy.rb +7 -9
- data/lib/samagotchi/llm_context_view.rb +9 -0
- data/lib/samagotchi/model_price.rb +4 -10
- data/lib/samagotchi/model_profile.rb +6 -6
- data/lib/samagotchi/model_settings.rb +37 -2
- data/lib/samagotchi/note_command.rb +1 -2
- data/lib/samagotchi/objectives_audit.rb +4 -3
- data/lib/samagotchi/participants.rb +2 -2
- data/lib/samagotchi/plugin/context.rb +1 -1
- data/lib/samagotchi/plugin/sessions.rb +2 -5
- data/lib/samagotchi/plugin/tool_spec.rb +5 -3
- data/lib/samagotchi/project_env.rb +1 -1
- data/lib/samagotchi/reasoning_history.rb +3 -3
- data/lib/samagotchi/reminder_carry.rb +1 -1
- data/lib/samagotchi/reprefill_meter.rb +45 -0
- data/lib/samagotchi/review_command.rb +1 -1
- data/lib/samagotchi/sampling_settings.rb +3 -3
- data/lib/samagotchi/self_report.rb +7 -7
- data/lib/samagotchi/session.rb +50 -7
- data/lib/samagotchi/session_access/local.rb +7 -5
- data/lib/samagotchi/session_access/remote.rb +17 -3
- data/lib/samagotchi/session_archive_command.rb +2 -2
- data/lib/samagotchi/session_chain.rb +7 -6
- data/lib/samagotchi/session_continue.rb +6 -5
- data/lib/samagotchi/session_delete_command.rb +1 -1
- data/lib/samagotchi/session_manager/delivery.rb +254 -0
- data/lib/samagotchi/session_manager/emptiness.rb +138 -0
- data/lib/samagotchi/session_manager/lifecycle.rb +274 -0
- data/lib/samagotchi/session_manager/listing.rb +189 -0
- data/lib/samagotchi/session_manager/removal.rb +190 -0
- data/lib/samagotchi/session_manager/spawn.rb +242 -0
- data/lib/samagotchi/session_manager/worker_command.rb +157 -0
- data/lib/samagotchi/session_manager.rb +26 -1540
- data/lib/samagotchi/session_metrics.rb +1 -1
- data/lib/samagotchi/sessions_command.rb +1 -1
- data/lib/samagotchi/share_command.rb +1 -1
- data/lib/samagotchi/starting_marker.rb +65 -0
- data/lib/samagotchi/steer_cut_after.rb +3 -3
- data/lib/samagotchi/system_one/recorder.rb +49 -0
- data/lib/samagotchi/terminal_ui/exit_lines.rb +1 -1
- data/lib/samagotchi/terminal_ui.rb +2 -6
- data/lib/samagotchi/testing/sessions.rb +1 -1
- data/lib/samagotchi/thinking.rb +3 -3
- data/lib/samagotchi/token_usage.rb +1 -1
- data/lib/samagotchi/tool_activity.rb +10 -4
- data/lib/samagotchi/tool_declarations.rb +2 -2
- data/lib/samagotchi/tool_output_cut.rb +17 -0
- data/lib/samagotchi/tool_runner.rb +7 -4
- data/lib/samagotchi/toolkit_command.rb +1 -1
- data/lib/samagotchi/tools/delegate.rb +10 -5
- data/lib/samagotchi/tools/delegate_result.rb +4 -4
- data/lib/samagotchi/tools/index_hint.rb +14 -0
- data/lib/samagotchi/tools/registry.rb +28 -11
- data/lib/samagotchi/tools/send_note.rb +4 -6
- data/lib/samagotchi/tools/tool_index.rb +52 -51
- data/lib/samagotchi/tools/tool_surface.rb +22 -8
- data/lib/samagotchi/turn_context.rb +2 -1
- data/lib/samagotchi/turn_stages/context_judge.rb +38 -36
- data/lib/samagotchi/turn_stages/objectives.rb +56 -52
- data/lib/samagotchi/turn_stages/toolkit_suggestion.rb +13 -17
- data/lib/samagotchi/turn_stages/verify_anchors.rb +9 -10
- data/lib/samagotchi/turn_stages/warmup.rb +14 -15
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/vision_support.rb +3 -3
- data/lib/samagotchi/web/app.rb +15 -8
- data/lib/samagotchi/web/participant_access.rb +2 -2
- data/lib/samagotchi/web/session_access_routes.rb +3 -3
- data/lib/samagotchi/web/session_control.rb +3 -3
- data/lib/samagotchi/web/session_create.rb +3 -3
- data/lib/samagotchi/web/session_hub.rb +1 -1
- data/lib/samagotchi/web/session_show.rb +3 -3
- data/lib/samagotchi/web/session_summary.rb +3 -4
- data/lib/samagotchi/worker.rb +55 -27
- data/lib/samagotchi/worker_boot.rb +184 -0
- data/lib/samagotchi/worker_signals.rb +1 -1
- data/lib/samagotchi/worker_task_wakes.rb +2 -2
- metadata +13 -1
|
@@ -27,6 +27,8 @@ require_relative "tools/builtin_calls"
|
|
|
27
27
|
require_relative "tools/tool_surface"
|
|
28
28
|
require_relative "muted_memories"
|
|
29
29
|
require_relative "tool_activity"
|
|
30
|
+
require_relative "reprefill_meter"
|
|
31
|
+
require_relative "tool_output_cut"
|
|
30
32
|
require_relative "tool_runner"
|
|
31
33
|
require_relative "tool_response"
|
|
32
34
|
require_relative "tool_ids"
|
|
@@ -123,13 +125,12 @@ module Samagotchi
|
|
|
123
125
|
# conversation (KernelLoop#forget_outputs).
|
|
124
126
|
def forget_outputs(call) = @kernel.forget_outputs(call, @context)
|
|
125
127
|
|
|
126
|
-
# The turn's Tools::ToolSurface (TurnContext#surface
|
|
127
|
-
#
|
|
128
|
+
# The turn's Tools::ToolSurface (TurnContext#surface; a bare kernel's
|
|
129
|
+
# own context has one too, KernelLoop#own_context): what the tool
|
|
128
130
|
# index's handlers (find_tools, call_tool) read.
|
|
129
|
-
def surface = Tools::ToolSurface.of(@context
|
|
131
|
+
def surface = Tools::ToolSurface.of(@context)
|
|
130
132
|
end
|
|
131
133
|
|
|
132
|
-
DEFAULT_MAX_TOOL_OUTPUT_CHARS = 10_000
|
|
133
134
|
QWEN_INCOMPLETE_TOOL_CALL_RECOVERY_LIMIT = 2
|
|
134
135
|
# How much of a malformed generation's end the warning keeps.
|
|
135
136
|
MALFORMED_TAIL_CHARS = 400
|
|
@@ -172,12 +173,16 @@ module Samagotchi
|
|
|
172
173
|
|
|
173
174
|
# @param services [Services, nil] what the kernel holds of its Engine
|
|
174
175
|
# (Services.none by default: the built-in tools alone)
|
|
176
|
+
# @param reprefill [ReprefillMeter, nil] the re-prefill baseline its
|
|
177
|
+
# requests count against (the Engine's, shared with its ChatLoop; its
|
|
178
|
+
# own by default)
|
|
175
179
|
# @param sleeper [#call, nil] waits the given seconds before a dropped
|
|
176
180
|
# stream's step is asked again (specs pass one that doesn't sleep)
|
|
177
|
-
def initialize(client: nil, profile: nil, model_name: nil, model_key: nil, services: nil, sleeper: nil)
|
|
181
|
+
def initialize(client: nil, profile: nil, model_name: nil, model_key: nil, services: nil, reprefill: nil, sleeper: nil)
|
|
178
182
|
@client = client || Client.new
|
|
179
183
|
@sleeper = sleeper || ->(seconds) { sleep(seconds) }
|
|
180
184
|
@services = services || Services.none
|
|
185
|
+
@reprefill = reprefill || ReprefillMeter.new
|
|
181
186
|
resolved_model_name = ModelProfile.required_model_name(model_name)
|
|
182
187
|
# The context of the run in progress, else of the last run (#context):
|
|
183
188
|
# a kernel that ran none has its own (no turn, no settings but its
|
|
@@ -193,11 +198,14 @@ module Samagotchi
|
|
|
193
198
|
# @return [Services] what the kernel holds of its Engine (#attach)
|
|
194
199
|
attr_reader :services
|
|
195
200
|
|
|
196
|
-
# Hand the kernel its Engine's services, all at once
|
|
197
|
-
#
|
|
201
|
+
# Hand the kernel its Engine's services, all at once, and its
|
|
202
|
+
# re-prefill meter (nil keeps the one it has): the Engine does for a
|
|
203
|
+
# kernel it is given (specs) what KernelLoop.new does for its own.
|
|
198
204
|
# @param services [Services]
|
|
205
|
+
# @param reprefill [ReprefillMeter, nil]
|
|
199
206
|
# @return [Services]
|
|
200
|
-
def attach(services)
|
|
207
|
+
def attach(services, reprefill: nil)
|
|
208
|
+
@reprefill = reprefill if reprefill
|
|
201
209
|
@services = services
|
|
202
210
|
end
|
|
203
211
|
|
|
@@ -288,16 +296,16 @@ module Samagotchi
|
|
|
288
296
|
# their thought blocks (#prepare_conversation), as within one run. nil:
|
|
289
297
|
# every earlier thought is stripped.
|
|
290
298
|
# @param context [TurnContext] the run's turn (Engine#generate's); a
|
|
291
|
-
# bare kernel's
|
|
299
|
+
# bare kernel's own (#own_context) by default. Its settings take the
|
|
292
300
|
# run's model name: this run's own copy, never published.
|
|
293
301
|
# @return [LLM::ModelResult] final visible response with continuation metadata
|
|
294
302
|
def run(messages, max_iterations: IterationLimit::DEFAULT, on_stream_event: nil, cancel_controller: nil, model_name: nil, max_tool_output_chars: nil, pending_input: nil,
|
|
295
|
-
reasoning_since: nil, context:
|
|
303
|
+
reasoning_since: nil, context: own_context)
|
|
296
304
|
settings = context.settings || LLM::TurnSettings.none
|
|
297
305
|
@context = context.with(settings: settings.with(model_name: completion_model_name(model_name)))
|
|
298
306
|
@last_slot = nil
|
|
299
307
|
turn = start_turn(messages, on_stream_event: on_stream_event, cancel_controller: cancel_controller,
|
|
300
|
-
pending_input: pending_input, cap:
|
|
308
|
+
pending_input: pending_input, cap: ToolOutputCut.max_chars(max_tool_output_chars),
|
|
301
309
|
reasoning_since: reasoning_since)
|
|
302
310
|
|
|
303
311
|
max_iterations.times do |iteration_index|
|
|
@@ -330,21 +338,6 @@ module Samagotchi
|
|
|
330
338
|
@profile_source = resolution.label
|
|
331
339
|
end
|
|
332
340
|
|
|
333
|
-
# Resolve the per-output character cap for the tool outputs the model
|
|
334
|
-
# gets back and the emitted tool call events.
|
|
335
|
-
#
|
|
336
|
-
# Precedence: an explicit override wins, then max_tool_output_chars
|
|
337
|
-
# (Config). A non-positive value falls back to
|
|
338
|
-
# DEFAULT_MAX_TOOL_OUTPUT_CHARS (there is intentionally no "unlimited" — live UIs get a
|
|
339
|
-
# bounded `output:` plus a truthful `output_truncated:` flag). The one
|
|
340
|
-
# exception is memory_read, which ToolRunner leaves out of this cap
|
|
341
|
-
# (memory.read_max_chars, 0: no fixed cap; a memory is read whole).
|
|
342
|
-
# Class-level so the chat loop resolves it the same way.
|
|
343
|
-
def self.resolve_output_char_cap(override)
|
|
344
|
-
parsed = (override || Samagotchi::Config.get("max_tool_output_chars")).to_i
|
|
345
|
-
parsed.positive? ? parsed : DEFAULT_MAX_TOOL_OUTPUT_CHARS
|
|
346
|
-
end
|
|
347
|
-
|
|
348
341
|
private
|
|
349
342
|
|
|
350
343
|
# ── One turn (#run) ────────────────────────────────────────────────────────
|
|
@@ -659,7 +652,7 @@ module Samagotchi
|
|
|
659
652
|
def cache_fields(cache) = cache ? TokenUsage.cache_fields(**cache) : {}
|
|
660
653
|
|
|
661
654
|
def reprefill_field(model, cache)
|
|
662
|
-
tokens = cache &&
|
|
655
|
+
tokens = cache && @reprefill.tokens(model, client: @client, prompt: cache[:prompt], cached: cache[:cached])
|
|
663
656
|
tokens ? { reprefill_tokens: tokens } : {}
|
|
664
657
|
end
|
|
665
658
|
|
|
@@ -877,12 +870,6 @@ module Samagotchi
|
|
|
877
870
|
# Coerce a value to boolean — handles true/false, nil, and string "true"/"false".
|
|
878
871
|
def truthy?(val) = Tools::Builtins.truthy?(val)
|
|
879
872
|
|
|
880
|
-
# ── Output char cap resolution ─────────────────────────────────────────────
|
|
881
|
-
|
|
882
|
-
def resolve_output_char_cap(override)
|
|
883
|
-
self.class.resolve_output_char_cap(override)
|
|
884
|
-
end
|
|
885
|
-
|
|
886
873
|
# One request, cancelled by +generation_controller+ (the turn's child)
|
|
887
874
|
# when there is one.
|
|
888
875
|
def request_generation(prompt, generation_controller:, cancel_controller:, **)
|
|
@@ -1005,14 +992,10 @@ module Samagotchi
|
|
|
1005
992
|
WARMUP_CUT = "chi-warmup-cut"
|
|
1006
993
|
private_constant :WARMUP_CUT
|
|
1007
994
|
|
|
1008
|
-
# What
|
|
1009
|
-
# turn's strategy
|
|
1010
|
-
#
|
|
1011
|
-
|
|
1012
|
-
# the prompt the next turn sends.
|
|
1013
|
-
def llm_context_view(llm_context = turn_settings&.llm_context)
|
|
1014
|
-
LLMContextView.new(strategy: llm_context&.strategy || LLMContextView::NONE)
|
|
1015
|
-
end
|
|
995
|
+
# What the native prompt and the warm-up send of the conversation
|
|
996
|
+
# (LLMContextView.for), under the turn's strategy or the one given (the
|
|
997
|
+
# warm-up's, the next turn's).
|
|
998
|
+
def llm_context_view(llm_context = turn_settings&.llm_context) = LLMContextView.for(llm_context)
|
|
1016
999
|
|
|
1017
1000
|
# The model's forget_outputs call (LLMContextForget) on the running
|
|
1018
1001
|
# turn (the loop's llm_context_turn), under +context+'s strategy (the
|
|
@@ -1042,84 +1025,19 @@ module Samagotchi
|
|
|
1042
1025
|
end
|
|
1043
1026
|
private :pinned_ids
|
|
1044
1027
|
|
|
1045
|
-
#
|
|
1046
|
-
#
|
|
1047
|
-
#
|
|
1048
|
-
# stage their edits on +conversation+ and the apply rule
|
|
1049
|
-
# (LLMContextApply, llm_context.apply) saves the batch it lets through
|
|
1050
|
-
# as applied, so the request about to be sent (or the next turn's
|
|
1051
|
-
# first, and the warm-up) is the first with the stubs. +context+: the
|
|
1052
|
-
# turn's ContextStatus (payoff applies in its top bucket; an applied
|
|
1053
|
-
# batch rebases its estimate, ContextStatus#edited!). none changes
|
|
1054
|
-
# nothing. Logged as llm_context_apply when a batch is applied, and
|
|
1055
|
-
# shown to the user as a ✂ row (+emit+ takes the :llm_context_edited
|
|
1056
|
-
# event, LLMContextNotice; the warm-up's own copy has none).
|
|
1028
|
+
# The strategy's edits on +conversation+ (LLMContextApply.apply!), under
|
|
1029
|
+
# the turn's strategy and model name by default: the native run's
|
|
1030
|
+
# requests and turn end, the warm-up's copy, a forget_outputs call.
|
|
1057
1031
|
# @return [LLMContextApply::Outcome]
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
warmup: false, emit: nil, model: turn_settings&.model_name)
|
|
1061
|
-
view = llm_context_view(llm_context)
|
|
1062
|
-
return LLMContextApply::Outcome.none if view.none?
|
|
1063
|
-
|
|
1064
|
-
outcome = LLMContextApply.run!(conversation, layers: view.layers, rule: llm_context.apply, moment: moment,
|
|
1065
|
-
protect_steps: llm_context.protect_steps,
|
|
1066
|
-
top_bucket: context&.top_bucket? || false,
|
|
1067
|
-
changes: llm_context.stale_edits)
|
|
1068
|
-
if outcome.applied?
|
|
1069
|
-
# The prompt just shrank: the context estimate counts from it.
|
|
1070
|
-
context&.edited!
|
|
1071
|
-
unless warmup
|
|
1072
|
-
log_llm_context(outcome, llm_context, moment, model)
|
|
1073
|
-
emit_llm_context(emit, conversation, outcome, moment) if emit
|
|
1074
|
-
end
|
|
1075
|
-
end
|
|
1076
|
-
outcome
|
|
1077
|
-
end
|
|
1078
|
-
|
|
1079
|
-
def log_llm_context(outcome, llm_context, moment, model)
|
|
1080
|
-
Log.info(:model, "llm_context_apply", rule: llm_context.apply, at: moment, why: outcome.why,
|
|
1081
|
-
applied: outcome.applied.size, ids: outcome.applied.map(&:id).join(","),
|
|
1082
|
-
staged: outcome.staged, freed: (outcome.freed_chars / TokenUsage::CHARS_PER_TOKEN).ceil,
|
|
1083
|
-
tail: (outcome.tail_chars / TokenUsage::CHARS_PER_TOKEN).ceil,
|
|
1084
|
-
model: model)
|
|
1085
|
-
end
|
|
1086
|
-
private :log_llm_context
|
|
1087
|
-
|
|
1088
|
-
# The batch's ✂ row; a row that can't be built never breaks the turn.
|
|
1089
|
-
def emit_llm_context(emit, conversation, outcome, moment)
|
|
1090
|
-
emit.call(LLMContextNotice.event(conversation, outcome, moment: moment))
|
|
1091
|
-
rescue StandardError => e
|
|
1092
|
-
Log.warn(:model, "llm_context_notice_failed", error: e.class.name, message: e.message)
|
|
1093
|
-
end
|
|
1094
|
-
private :emit_llm_context
|
|
1095
|
-
|
|
1096
|
-
# A request's re-prefilled tokens, for both loops: of the prompt the
|
|
1097
|
-
# last request on +model+ through this client (the host) sent (this
|
|
1098
|
-
# kernel's, any turn), what the server didn't reuse from its cache this
|
|
1099
|
-
# time (that prompt less +cached+), so an LLM context edit's cache break
|
|
1100
|
-
# shows, and so does anything else that rewrote the prompt's middle or
|
|
1101
|
-
# lost the cache. nil under TokenUsage::REPREFILL_MIN_TOKENS (a cache
|
|
1102
|
-
# block's rounding), for a server that reports no cached count (or 0: a
|
|
1103
|
-
# cold cache isn't told from no cache), the first request, a model
|
|
1104
|
-
# switch (another bare model, or the same one on another host: the
|
|
1105
|
-
# Engine points #client at the new host's; SessionMetrics counts that
|
|
1106
|
-
# prefill as the switch's), and the first request after a warm-up
|
|
1107
|
-
# (#warmed_up!).
|
|
1108
|
-
# @return [Integer, nil]
|
|
1109
|
-
def reprefilled_tokens(model, prompt:, cached:)
|
|
1110
|
-
previous = @reprefill_baseline
|
|
1111
|
-
@reprefill_baseline = { model: model, client: @client, prompt: prompt.to_i } if prompt.to_i.positive?
|
|
1112
|
-
return nil unless previous && previous[:model] == model && previous[:client].equal?(@client) && cached.to_i.positive?
|
|
1113
|
-
|
|
1114
|
-
tokens = previous[:prompt] - cached.to_i
|
|
1115
|
-
tokens >= TokenUsage::REPREFILL_MIN_TOKENS ? tokens : nil
|
|
1032
|
+
def apply_llm_context!(conversation, llm_context = turn_settings&.llm_context, model: turn_settings&.model_name, **)
|
|
1033
|
+
LLMContextApply.apply!(conversation, llm_context, model: model, **)
|
|
1116
1034
|
end
|
|
1117
1035
|
|
|
1118
1036
|
# The Engine started a turn-end warm-up: it prefills the next prompt
|
|
1119
1037
|
# off the clock, so the next request's cache counts are against it, not
|
|
1120
|
-
# the last request (
|
|
1038
|
+
# the last request (the re-prefill meter starts over, ReprefillMeter#reset!).
|
|
1121
1039
|
def warmed_up!
|
|
1122
|
-
@
|
|
1040
|
+
@reprefill.reset!
|
|
1123
1041
|
end
|
|
1124
1042
|
|
|
1125
1043
|
# Public wrapper so other loops (e.g. the chat loop) can strip
|
|
@@ -1212,21 +1130,28 @@ module Samagotchi
|
|
|
1212
1130
|
# first (see Samagotchi::LLM::NativeToolNormalizer); dispatch itself never
|
|
1213
1131
|
# parses provider text. +context+: the call's turn (TurnContext: its
|
|
1214
1132
|
# ToolSurface, the handler's ToolContext); a bare kernel's own
|
|
1215
|
-
# (#
|
|
1216
|
-
def dispatch_tool_call(call, context =
|
|
1133
|
+
# (#own_context) by default.
|
|
1134
|
+
def dispatch_tool_call(call, context = own_context)
|
|
1217
1135
|
dispatch(call, context)
|
|
1218
1136
|
end
|
|
1219
1137
|
|
|
1138
|
+
# The context a bare kernel (no Engine: a spec, a bench) runs in when
|
|
1139
|
+
# given none: #context, with a Tools::ToolSurface built now from the
|
|
1140
|
+
# kernel's tools when it has none (Tools::ToolSurface.bare). An
|
|
1141
|
+
# Engine's turn always hands its own down (Engine#generate's surface).
|
|
1142
|
+
# @return [TurnContext]
|
|
1143
|
+
def own_context = Tools::ToolSurface.bare(@context, tools)
|
|
1144
|
+
|
|
1220
1145
|
private
|
|
1221
1146
|
|
|
1222
1147
|
# The call runs on the entry the turn's surface gives it
|
|
1223
1148
|
# (Tools::ToolSurface#entry_for: a call_tool-reached one only while the
|
|
1224
1149
|
# toolkit reaches it that way, a reachable one by its name on a host
|
|
1225
1150
|
# that loads tools); without one it gets ToolSurface#unknown_tool_error.
|
|
1226
|
-
# A context without a surface
|
|
1151
|
+
# A context without a surface raises Tools::MissingSurface.
|
|
1227
1152
|
def dispatch(call, context)
|
|
1228
1153
|
model = context.settings&.model_name
|
|
1229
|
-
surface = Tools::ToolSurface.of(context
|
|
1154
|
+
surface = Tools::ToolSurface.of(context)
|
|
1230
1155
|
entry = surface.entry_for(call)
|
|
1231
1156
|
unless entry
|
|
1232
1157
|
result = surface.unknown_tool_error(call[:name], via: call[:via])
|
|
@@ -1271,10 +1196,10 @@ module Samagotchi
|
|
|
1271
1196
|
rescue StandardError => e
|
|
1272
1197
|
dump_log("tool_error", e.message, model: context.settings&.model_name, tool: call[:name], error: e.class.name)
|
|
1273
1198
|
result = "Error: #{e.message}"
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
}
|
|
1199
|
+
# No surface (Tools::MissingSurface from ToolSurface.of): no row either,
|
|
1200
|
+
# it would read the surface again.
|
|
1201
|
+
activity = surface && ToolActivity.tool_activity_event(call[:name], call, result, registry: tools, surface: surface)
|
|
1202
|
+
{ output: "[#{call[:name]}] #{result}", activity: activity }
|
|
1278
1203
|
end
|
|
1279
1204
|
end
|
|
1280
1205
|
end
|
|
@@ -4,7 +4,7 @@ require "digest"
|
|
|
4
4
|
require "json"
|
|
5
5
|
require_relative "../log"
|
|
6
6
|
require_relative "utf8_scrub"
|
|
7
|
-
require_relative "../tools/
|
|
7
|
+
require_relative "../tools/index_hint"
|
|
8
8
|
|
|
9
9
|
module Samagotchi
|
|
10
10
|
module LLM
|
|
@@ -278,7 +278,7 @@ module Samagotchi
|
|
|
278
278
|
# don't change; only this wire says it).
|
|
279
279
|
def loaded_text(text, names)
|
|
280
280
|
line = "Loaded: #{names.join(", ")}. #{LOADED_HINT}"
|
|
281
|
-
text = text.sub("#{Tools::
|
|
281
|
+
text = text.sub("#{Tools::IndexHint::CALL_HINT}\n\n", "")
|
|
282
282
|
blank?(text) ? line : "#{line}\n\n#{text}"
|
|
283
283
|
end
|
|
284
284
|
|
|
@@ -20,6 +20,7 @@ require_relative "../cut_policy"
|
|
|
20
20
|
require_relative "../context_status"
|
|
21
21
|
require_relative "../context_window"
|
|
22
22
|
require_relative "../context_note"
|
|
23
|
+
require_relative "../tool_output_cut"
|
|
23
24
|
require_relative "../tool_runner"
|
|
24
25
|
require_relative "../tool_response"
|
|
25
26
|
require_relative "../tool_ids"
|
|
@@ -27,7 +28,10 @@ require_relative "../tool_declarations"
|
|
|
27
28
|
require_relative "../tools/tool_surface"
|
|
28
29
|
require_relative "../tool_loads"
|
|
29
30
|
require_relative "../vision_context"
|
|
31
|
+
require_relative "../llm_context_apply"
|
|
32
|
+
require_relative "../llm_context_view"
|
|
30
33
|
require_relative "../log"
|
|
34
|
+
require_relative "../reprefill_meter"
|
|
31
35
|
require_relative "../empty_answer_retry"
|
|
32
36
|
require_relative "../steer"
|
|
33
37
|
require_relative "../reasoning_history"
|
|
@@ -66,6 +70,12 @@ module Samagotchi
|
|
|
66
70
|
# The session every request is for; Engine sets it with the adapter.
|
|
67
71
|
# Goes out as a Session-Id header (OpenAIChat#chat).
|
|
68
72
|
attr_accessor :session_id
|
|
73
|
+
# The effective model's host's client (EffectiveModel#client): what
|
|
74
|
+
# #context_window probes on a local host (a llama.cpp /props) and what
|
|
75
|
+
# a request's re-prefill is counted against. Engine sets it with the
|
|
76
|
+
# adapter (Engine#backend_for, under its chat backend mutex, from any
|
|
77
|
+
# thread a model switch runs on); nil until then (no probe).
|
|
78
|
+
attr_accessor :client
|
|
69
79
|
# The last counted request's chars per token
|
|
70
80
|
# (ContextStatus#counted_chars_per_token), carried to the next turn's
|
|
71
81
|
# tracker: its first estimate counts the tool schemas the chars leave
|
|
@@ -73,6 +83,9 @@ module Samagotchi
|
|
|
73
83
|
attr_accessor :counted_chars_per_token
|
|
74
84
|
|
|
75
85
|
# @param kernel [KernelLoop] tool dispatch, thought stripping, hooks
|
|
86
|
+
# @param reprefill [ReprefillMeter, nil] the re-prefill baseline its
|
|
87
|
+
# requests count against (the Engine's, shared with its KernelLoop;
|
|
88
|
+
# its own by default)
|
|
76
89
|
# @param adapter [OpenAIChat, nil] the host to talk to; Engine sets it
|
|
77
90
|
# per turn for the effective model's host
|
|
78
91
|
# @param stable_length [#call, nil] text → the length of the system
|
|
@@ -81,8 +94,9 @@ module Samagotchi
|
|
|
81
94
|
# stream's step is asked again (specs pass one that doesn't sleep)
|
|
82
95
|
# @param context [TurnContext, nil] the turn its helpers read before any
|
|
83
96
|
# #complete (a bench building wire messages); a run's replaces it
|
|
84
|
-
def initialize(kernel:, adapter: nil, stable_length: nil, sleeper: nil, context: nil)
|
|
97
|
+
def initialize(kernel:, reprefill: nil, adapter: nil, stable_length: nil, sleeper: nil, context: nil)
|
|
85
98
|
@kernel = kernel
|
|
99
|
+
@reprefill = reprefill || ReprefillMeter.new
|
|
86
100
|
@turn_context = context
|
|
87
101
|
@adapter = adapter
|
|
88
102
|
@stable_length = stable_length
|
|
@@ -106,7 +120,7 @@ module Samagotchi
|
|
|
106
120
|
@turn_context = context if context
|
|
107
121
|
conversation = Array(messages).map(&:dup)
|
|
108
122
|
Run.new(self, conversation, on_stream_event, cancel_controller, model_name, pending_input, start: reasoning_since)
|
|
109
|
-
.call(max_iterations: max_iterations || 1, cap:
|
|
123
|
+
.call(max_iterations: max_iterations || 1, cap: ToolOutputCut.max_chars(max_tool_output_chars))
|
|
110
124
|
rescue StandardError, Interrupt => e
|
|
111
125
|
# A failure, or a Ctrl-C (its calls answered: ToolResponse.run_batch),
|
|
112
126
|
# carries the conversation so far for the Engine to keep.
|
|
@@ -137,10 +151,9 @@ module Samagotchi
|
|
|
137
151
|
# (ToolSurface#deferred_for).
|
|
138
152
|
def deferred_tools(conversation) = surface.deferred_for(conversation)
|
|
139
153
|
|
|
140
|
-
# The turn's ToolSurface (#turn_context's)
|
|
141
|
-
# the kernel's tools (a bare loop).
|
|
154
|
+
# The turn's ToolSurface (#turn_context's).
|
|
142
155
|
# @return [Tools::ToolSurface]
|
|
143
|
-
def surface = Tools::ToolSurface.of(turn_context
|
|
156
|
+
def surface = Tools::ToolSurface.of(turn_context)
|
|
144
157
|
|
|
145
158
|
# Whether the adapter takes tools it doesn't declare up front for
|
|
146
159
|
# +model+ (AnthropicMessages#loads_tools?); OpenAIChat doesn't.
|
|
@@ -159,9 +172,12 @@ module Samagotchi
|
|
|
159
172
|
|
|
160
173
|
# The TurnContext of the run in progress, else of the last run
|
|
161
174
|
# (#complete's context:); before any, the one it was built with, else a
|
|
162
|
-
# context of the kernel's own settings
|
|
175
|
+
# context of the kernel's own settings and a surface of its tools
|
|
176
|
+
# built now (a bare loop, Tools::ToolSurface.bare). Read on the turn's thread only.
|
|
163
177
|
# @return [TurnContext]
|
|
164
|
-
def turn_context
|
|
178
|
+
def turn_context
|
|
179
|
+
@turn_context || Tools::ToolSurface.bare(TurnContext.none(settings: @kernel.turn_settings), tools)
|
|
180
|
+
end
|
|
165
181
|
|
|
166
182
|
# The turn's LLM::TurnSettings (#turn_context's).
|
|
167
183
|
def settings = turn_context.settings
|
|
@@ -173,16 +189,17 @@ module Samagotchi
|
|
|
173
189
|
# when none was resolved.
|
|
174
190
|
def llm_context = settings&.llm_context
|
|
175
191
|
|
|
176
|
-
#
|
|
177
|
-
|
|
192
|
+
# The request's re-prefilled tokens on the loop's meter, against this
|
|
193
|
+
# loop's host (#client): ReprefillMeter#tokens.
|
|
194
|
+
def reprefilled_tokens(model, **) = @reprefill.tokens(model, client: @client, **)
|
|
178
195
|
|
|
179
|
-
# The
|
|
180
|
-
def llm_context_view =
|
|
196
|
+
# The LLMContextView under the turn's strategy (LLMContextView.for).
|
|
197
|
+
def llm_context_view = LLMContextView.for(llm_context)
|
|
181
198
|
|
|
182
199
|
# The strategy's own edits before a request and at turn end
|
|
183
|
-
# (
|
|
200
|
+
# (LLMContextApply.apply!), under the turn's strategy and model name.
|
|
184
201
|
def apply_llm_context!(conversation, **)
|
|
185
|
-
|
|
202
|
+
LLMContextApply.apply!(conversation, llm_context, model: settings&.model_name, **)
|
|
186
203
|
end
|
|
187
204
|
|
|
188
205
|
# The running turn, for forget_outputs (KernelLoop#llm_context_turn).
|
|
@@ -398,14 +415,13 @@ module Samagotchi
|
|
|
398
415
|
@tool_runner ||= ToolRunner.new(@kernel, window: ->(run_settings) { context_window(run_settings&.model_name, run_settings) })
|
|
399
416
|
end
|
|
400
417
|
|
|
401
|
-
# The window for +model+: the
|
|
402
|
-
# points at (
|
|
403
|
-
# llama.cpp answers /props), then the host's model list, the model's
|
|
418
|
+
# The window for +model+: the loop's client (#client) probes the server
|
|
419
|
+
# it points at (a local llama.cpp answers /props), then the host's model list, the model's
|
|
404
420
|
# or host's window_tokens, config, env and the default. A remote
|
|
405
421
|
# provider has no /props to probe. +run_settings+: the LLM::TurnSettings
|
|
406
422
|
# whose window_setting counts (the turn's by default).
|
|
407
423
|
def context_window(model, run_settings = settings)
|
|
408
|
-
client = @adapter.remote? ? nil : @
|
|
424
|
+
client = @adapter.remote? ? nil : @client
|
|
409
425
|
ContextWindow.resolve(client: client, model: model, adapter: @adapter, setting: run_settings&.window_setting)
|
|
410
426
|
rescue StandardError
|
|
411
427
|
nil
|
|
@@ -897,7 +913,7 @@ module Samagotchi
|
|
|
897
913
|
count
|
|
898
914
|
end
|
|
899
915
|
|
|
900
|
-
# The request's re-prefilled tokens (
|
|
916
|
+
# The request's re-prefilled tokens (ChatLoop#reprefilled_tokens).
|
|
901
917
|
def reprefill_field(usage)
|
|
902
918
|
return {} unless usage&.source == :server
|
|
903
919
|
|
|
@@ -4,8 +4,14 @@ require "time"
|
|
|
4
4
|
require_relative "llm_context_edit"
|
|
5
5
|
require_relative "llm_context_stale"
|
|
6
6
|
require_relative "llm_context_view"
|
|
7
|
+
require_relative "log"
|
|
8
|
+
require_relative "token_usage"
|
|
7
9
|
|
|
8
10
|
module Samagotchi
|
|
11
|
+
# The ✂ row's builder (#apply!), loaded on first use: the test kit loads
|
|
12
|
+
# this file (through the strategy) and never emits a row.
|
|
13
|
+
autoload :LLMContextNotice, File.expand_path("llm_context_notice", __dir__)
|
|
14
|
+
|
|
9
15
|
# When a layer's edits reach the prompt: the apply rule
|
|
10
16
|
# (llm_context.apply, LLMContextStrategy). Every edit breaks the prompt
|
|
11
17
|
# cache from the earliest entry it changes, so the edits due at one point
|
|
@@ -59,6 +65,53 @@ module Samagotchi
|
|
|
59
65
|
|
|
60
66
|
module_function
|
|
61
67
|
|
|
68
|
+
# Before each request of both loops (+moment+ :request; the warm-up
|
|
69
|
+
# too, on its own copy, as the next turn's first request) and at the
|
|
70
|
+
# end of a turn the model answered (:turn_end): +llm_context+'s layers
|
|
71
|
+
# (LLMContextStrategy::Resolved, the turn's) stage their edits on
|
|
72
|
+
# +conversation+ and its apply rule (#run!) saves the batch it lets
|
|
73
|
+
# through as applied, so the request about to be sent (or the next
|
|
74
|
+
# turn's first, and the warm-up) is the first with the stubs.
|
|
75
|
+
# +context+: the turn's ContextStatus (payoff applies in its top bucket;
|
|
76
|
+
# an applied batch rebases its estimate, ContextStatus#edited!). none
|
|
77
|
+
# changes nothing. Logged as llm_context_apply when a batch is applied
|
|
78
|
+
# (+model+: the run's model name, in the line), and shown to the user as
|
|
79
|
+
# a ✂ row (+emit+ takes the :llm_context_edited event, LLMContextNotice;
|
|
80
|
+
# the warm-up's own copy, +warmup+, has neither).
|
|
81
|
+
# @return [Outcome]
|
|
82
|
+
def apply!(conversation, llm_context, moment: :request, context: nil, warmup: false, emit: nil, model: nil)
|
|
83
|
+
view = LLMContextView.for(llm_context)
|
|
84
|
+
return Outcome.none if view.none?
|
|
85
|
+
|
|
86
|
+
outcome = run!(conversation, layers: view.layers, rule: llm_context.apply, moment: moment,
|
|
87
|
+
protect_steps: llm_context.protect_steps, top_bucket: context&.top_bucket? || false,
|
|
88
|
+
changes: llm_context.stale_edits)
|
|
89
|
+
if outcome.applied?
|
|
90
|
+
# The prompt just shrank: the context estimate counts from it.
|
|
91
|
+
context&.edited!
|
|
92
|
+
unless warmup
|
|
93
|
+
log_applied(outcome, llm_context, moment, model)
|
|
94
|
+
emit_applied(emit, conversation, outcome, moment) if emit
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
outcome
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def log_applied(outcome, llm_context, moment, model)
|
|
101
|
+
Log.info(:model, "llm_context_apply", rule: llm_context.apply, at: moment, why: outcome.why,
|
|
102
|
+
applied: outcome.applied.size, ids: outcome.applied.map(&:id).join(","),
|
|
103
|
+
staged: outcome.staged, freed: (outcome.freed_chars / TokenUsage::CHARS_PER_TOKEN).ceil,
|
|
104
|
+
tail: (outcome.tail_chars / TokenUsage::CHARS_PER_TOKEN).ceil,
|
|
105
|
+
model: model)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# The batch's ✂ row; a row that can't be built never breaks the turn.
|
|
109
|
+
def emit_applied(emit, conversation, outcome, moment)
|
|
110
|
+
emit.call(LLMContextNotice.event(conversation, outcome, moment: moment))
|
|
111
|
+
rescue StandardError => e
|
|
112
|
+
Log.warn(:model, "llm_context_notice_failed", error: e.class.name, message: e.message)
|
|
113
|
+
end
|
|
114
|
+
|
|
62
115
|
# Stages what +layers+ find on +conversation+ and applies what +rule+
|
|
63
116
|
# lets through at +moment+.
|
|
64
117
|
# @param top_bucket [Boolean] the context is in the top ContextStatus bucket
|
|
@@ -160,6 +213,6 @@ module Samagotchi
|
|
|
160
213
|
end
|
|
161
214
|
end
|
|
162
215
|
|
|
163
|
-
private_class_method :stage, :choose, :measure, :saved_staged
|
|
216
|
+
private_class_method :stage, :choose, :measure, :saved_staged, :log_applied, :emit_applied
|
|
164
217
|
end
|
|
165
218
|
end
|
|
@@ -7,7 +7,7 @@ require_relative "token_usage"
|
|
|
7
7
|
module Samagotchi
|
|
8
8
|
# The ✂ row a turn gets for a batch of LLM context edits the apply rule
|
|
9
9
|
# let through (LLMContextApply::Outcome#applied?): the :llm_context_edited
|
|
10
|
-
# event both loops emit (
|
|
10
|
+
# event both loops emit (LLMContextApply.apply!, its +emit+), at a
|
|
11
11
|
# request (+moment+ :request; a forget_outputs call's batch too) or at the
|
|
12
12
|
# end of a turn (:turn_end); never for the warm-up, a held or a staged
|
|
13
13
|
# batch, or a restore (the forget_outputs row says it). The web and the
|
|
@@ -35,11 +35,9 @@ module Samagotchi
|
|
|
35
35
|
LAYERS = %i[stale forget].freeze
|
|
36
36
|
# The layers a turn can run under so far.
|
|
37
37
|
BUILT = %i[stale forget].freeze
|
|
38
|
-
# The sentence forget_outputs' description carries (global only
|
|
39
|
-
#
|
|
38
|
+
# The sentence forget_outputs' description carries (global only; its
|
|
39
|
+
# default is Config::DEFAULT_LLM_CONTEXT_POLICY).
|
|
40
40
|
POLICY_SETTING = "llm_context.policy"
|
|
41
|
-
DEFAULT_POLICY = "Tidy at subtask boundaries: once a subtask is done, forget its tool outputs and note what it " \
|
|
42
|
-
"established; keep anything you'll still edit against."
|
|
43
41
|
|
|
44
42
|
APPLY_SETTING = "llm_context.apply"
|
|
45
43
|
APPLY_KEY = "llm_context_apply"
|
|
@@ -174,7 +172,7 @@ module Samagotchi
|
|
|
174
172
|
# The strategy for +target+'s turn.
|
|
175
173
|
# @param target [HostRegistry::ModelTarget, nil]
|
|
176
174
|
# @param names [Array<String>] HostRegistry#lookup_names
|
|
177
|
-
# @param models [Hash{String => ModelSettings}, nil]
|
|
175
|
+
# @param models [Hash{String => ModelSettings}, nil] ModelSettings.all (specs)
|
|
178
176
|
# @param session [Array<Symbol>, nil] the session's own layers ([]: none)
|
|
179
177
|
# @param session_apply [Symbol, nil] the session's own apply rule
|
|
180
178
|
# @param session_budget [Integer, nil] the session's own budget (0: off)
|
|
@@ -187,7 +185,7 @@ module Samagotchi
|
|
|
187
185
|
# #resolve, with where each value came from.
|
|
188
186
|
# @return [Explained]
|
|
189
187
|
def explain(target, names:, models: nil, session: nil, session_apply: nil, session_budget: nil)
|
|
190
|
-
models ||=
|
|
188
|
+
models ||= ModelSettings.all
|
|
191
189
|
layers, source, where = layers_for(target, names, models, session)
|
|
192
190
|
apply, apply_origin = apply_for(target, names, models, session_apply)
|
|
193
191
|
budget, budget_origin = budget_for(target, names, models, session_budget)
|
|
@@ -236,7 +234,7 @@ module Samagotchi
|
|
|
236
234
|
def budget_for(target, names, models, session_budget)
|
|
237
235
|
return [session_budget.positive? ? session_budget : nil, SESSION_ORIGIN] unless session_budget.nil?
|
|
238
236
|
|
|
239
|
-
if (setting =
|
|
237
|
+
if (setting = ModelSettings.find(names, :llm_context_budget_tokens, models: models))
|
|
240
238
|
return [setting.value, Origin.new(source: :model_setting, where: "models: #{setting.key}")]
|
|
241
239
|
end
|
|
242
240
|
|
|
@@ -249,7 +247,7 @@ module Samagotchi
|
|
|
249
247
|
def layers_for(target, names, models, session)
|
|
250
248
|
return [session, :session, SESSION_ORIGIN.where] if session
|
|
251
249
|
|
|
252
|
-
if (setting =
|
|
250
|
+
if (setting = ModelSettings.find(names, :llm_context_strategy, models: models))
|
|
253
251
|
return [setting.value, :model_setting, "models: #{setting.key}"]
|
|
254
252
|
end
|
|
255
253
|
|
|
@@ -263,7 +261,7 @@ module Samagotchi
|
|
|
263
261
|
def apply_for(target, names, models, session_apply)
|
|
264
262
|
return [session_apply, SESSION_ORIGIN] if session_apply
|
|
265
263
|
|
|
266
|
-
if (setting =
|
|
264
|
+
if (setting = ModelSettings.find(names, :llm_context_apply, models: models))
|
|
267
265
|
return [setting.value, Origin.new(source: :model_setting, where: "models: #{setting.key}")]
|
|
268
266
|
end
|
|
269
267
|
|
|
@@ -73,6 +73,15 @@ module Samagotchi
|
|
|
73
73
|
end
|
|
74
74
|
end
|
|
75
75
|
|
|
76
|
+
# The view under +llm_context+'s strategy (LLMContextStrategy::Resolved,
|
|
77
|
+
# a turn's TurnSettings#llm_context), none without one: what both loops
|
|
78
|
+
# format the conversation through, so the warm-up warms the prompt the
|
|
79
|
+
# next turn sends.
|
|
80
|
+
# @return [LLMContextView]
|
|
81
|
+
def self.for(llm_context)
|
|
82
|
+
new(strategy: llm_context&.strategy || NONE)
|
|
83
|
+
end
|
|
84
|
+
|
|
76
85
|
# @param strategy [Symbol, Array<Symbol>] :none, or the layers
|
|
77
86
|
def initialize(strategy: NONE)
|
|
78
87
|
@layers = strategy == NONE ? [] : Array(strategy).map(&:to_sym) & LAYERS
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "config"
|
|
4
|
+
|
|
3
5
|
module Samagotchi
|
|
4
6
|
# A model's price on a host (hosts.<name>.models.<id>.price), in USD per
|
|
5
7
|
# 1M tokens: what a generation's cost is estimated from when the provider
|
|
@@ -13,7 +15,7 @@ module Samagotchi
|
|
|
13
15
|
def self.parse(raw, where)
|
|
14
16
|
return nil if raw.nil?
|
|
15
17
|
|
|
16
|
-
values = raw.is_a?(Hash) ? raw.transform_keys(&:to_s).slice(*
|
|
18
|
+
values = raw.is_a?(Hash) ? raw.transform_keys(&:to_s).slice(*Config::MODEL_PRICE_KEYS) : nil
|
|
17
19
|
valid = values && values.key?("input") && values.key?("output") &&
|
|
18
20
|
# YAML's .inf and .nan are Floats too: an infinite price
|
|
19
21
|
# isn't one, and NaN poisons the sums.
|
|
@@ -26,12 +28,7 @@ module Samagotchi
|
|
|
26
28
|
cache_write: values.fetch("cache_write", values["input"]), output: values["output"])
|
|
27
29
|
end
|
|
28
30
|
|
|
29
|
-
|
|
30
|
-
# required here only when a warning needs it.
|
|
31
|
-
def self.warn_once(message)
|
|
32
|
-
require_relative "config"
|
|
33
|
-
ConfigFile.warn_once(message)
|
|
34
|
-
end
|
|
31
|
+
def self.warn_once(message) = ConfigFile.warn_once(message)
|
|
35
32
|
private_class_method :warn_once
|
|
36
33
|
|
|
37
34
|
# The cost rule, one place for a turn's generation (SessionMetrics) and
|
|
@@ -63,7 +60,4 @@ module Samagotchi
|
|
|
63
60
|
(completion_tokens * output)) / 1e6
|
|
64
61
|
end
|
|
65
62
|
end
|
|
66
|
-
|
|
67
|
-
# The keys a price may hold (config validation checks them too).
|
|
68
|
-
ModelPrice::KEYS = %w[input cache_read cache_write output].freeze
|
|
69
63
|
end
|