samagotchi 0.155.0 → 0.157.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 +57 -1
- data/bin/chi +2 -1
- data/docs/architecture.md +22 -13
- data/docs/bundles.md +3 -1
- data/docs/cli.md +9 -8
- data/docs/configuration.md +36 -4
- data/docs/guardrails.md +9 -5
- data/docs/hooks.md +11 -5
- 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/bridge.md +83 -6
- data/docs/internals/bundles.md +28 -18
- data/docs/internals/context-telemetry.md +4 -3
- data/docs/internals/engine.md +641 -937
- data/docs/internals/llm-context-forget.md +8 -3
- data/docs/internals/objectives.md +2 -2
- data/docs/internals/participants.md +3 -2
- data/docs/internals/prompt-caching.md +4 -1
- data/docs/internals/remote-sessions.md +8 -3
- data/docs/internals/web-frontend.md +3 -2
- data/docs/internals/working-set.md +2 -2
- data/docs/memory.md +13 -2
- data/docs/plugins.md +35 -2
- data/docs/sessions.md +11 -11
- data/docs/sub-agent.md +17 -2
- data/docs/system-one.md +3 -2
- data/docs/testing.md +53 -2
- data/lib/samagotchi/answer_command.rb +2 -1
- data/lib/samagotchi/archive_store.rb +4 -3
- data/lib/samagotchi/bootstrap/config_writer.rb +1 -1
- data/lib/samagotchi/bootstrap_command.rb +3 -2
- data/lib/samagotchi/bridge/card_places.rb +10 -4
- data/lib/samagotchi/bridge/card_store.rb +3 -0
- data/lib/samagotchi/bridge/turn_accumulator.rb +109 -9
- data/lib/samagotchi/bridge/turn_drawings.rb +265 -0
- data/lib/samagotchi/bridge.rb +31 -6
- data/lib/samagotchi/broadcast/recipients.rb +8 -8
- data/lib/samagotchi/broadcast/triage_model.rb +1 -1
- data/lib/samagotchi/broadcast_command.rb +3 -3
- data/lib/samagotchi/bundle_command/check.rb +18 -0
- data/lib/samagotchi/bundle_command/try/sandbox.rb +2 -2
- data/lib/samagotchi/bundle_command/try/trace.rb +1 -1
- data/lib/samagotchi/bundle_command.rb +5 -2
- data/lib/samagotchi/bundles/known-names/manifest.yml +4 -7
- data/lib/samagotchi/bundles/known-names/{hooks/known_names.rb → plugin.rb} +17 -0
- data/lib/samagotchi/bundles/source-links/manifest.yml +4 -7
- data/lib/samagotchi/bundles/source-links/{hooks/source_links.rb → plugin.rb} +23 -4
- data/lib/samagotchi/bundles/system/config_modification_protocol.md +2 -2
- data/lib/samagotchi/bundles/system/manifest.yml +3 -3
- data/lib/samagotchi/bundles/system/memory_guide.md +1 -1
- data/lib/samagotchi/child_move.rb +13 -57
- data/lib/samagotchi/child_reports.rb +2 -2
- data/lib/samagotchi/children_status.rb +15 -21
- data/lib/samagotchi/cli/parent_wait.rb +9 -23
- data/lib/samagotchi/config.rb +20 -112
- data/lib/samagotchi/context_command.rb +1 -2
- data/lib/samagotchi/context_sources.rb +5 -4
- data/lib/samagotchi/context_window.rb +9 -12
- data/lib/samagotchi/desktop/macos.rb +1 -1
- data/lib/samagotchi/effective_model.rb +57 -22
- 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/plugins.rb +1 -1
- data/lib/samagotchi/engine/recap.rb +8 -4
- 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 +43 -12
- data/lib/samagotchi/events.rb +3 -2
- data/lib/samagotchi/extension_load.rb +6 -3
- data/lib/samagotchi/guardrails/targets.rb +24 -9
- data/lib/samagotchi/guardrails_command.rb +1 -1
- data/lib/samagotchi/host_address.rb +52 -0
- data/lib/samagotchi/host_config.rb +72 -1
- data/lib/samagotchi/host_model.rb +2 -6
- data/lib/samagotchi/host_registry.rb +58 -46
- data/lib/samagotchi/kernel_loop.rb +53 -124
- data/lib/samagotchi/llm/anthropic_wire.rb +2 -2
- data/lib/samagotchi/llm/chat_loop.rb +40 -21
- 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 +29 -67
- data/lib/samagotchi/llm_context_view.rb +9 -0
- data/lib/samagotchi/memory_bundle/asset_installer.rb +71 -76
- data/lib/samagotchi/memory_bundle/builder.rb +2 -1
- data/lib/samagotchi/memory_bundle/file_actions.rb +193 -0
- data/lib/samagotchi/memory_bundle/file_digest.rb +13 -0
- data/lib/samagotchi/memory_bundle/file_state.rb +152 -0
- data/lib/samagotchi/memory_bundle/installed_copy.rb +2 -1
- data/lib/samagotchi/memory_bundle/installed_manifest.rb +8 -1
- data/lib/samagotchi/memory_bundle/installer.rb +185 -169
- data/lib/samagotchi/memory_bundle/manifest.rb +14 -4
- data/lib/samagotchi/memory_bundle/merger.rb +69 -65
- data/lib/samagotchi/memory_bundle/plugin_ref.rb +6 -5
- data/lib/samagotchi/memory_bundle/profile.rb +5 -5
- data/lib/samagotchi/memory_bundle/provenance.rb +15 -1
- data/lib/samagotchi/memory_bundle/remerge.rb +25 -12
- data/lib/samagotchi/memory_bundle/shipped_update.rb +51 -6
- data/lib/samagotchi/memory_bundle/status.rb +14 -20
- data/lib/samagotchi/memory_bundle/system_bundle.rb +24 -6
- data/lib/samagotchi/memory_bundle/trash.rb +23 -8
- data/lib/samagotchi/memory_bundle/uninstaller.rb +16 -11
- data/lib/samagotchi/memory_usage.rb +1 -1
- data/lib/samagotchi/model_lookup.rb +75 -0
- data/lib/samagotchi/model_price.rb +4 -10
- data/lib/samagotchi/model_profile.rb +7 -6
- data/lib/samagotchi/model_settings.rb +37 -2
- data/lib/samagotchi/models_command.rb +1 -1
- data/lib/samagotchi/note_command.rb +2 -3
- data/lib/samagotchi/objectives_audit.rb +4 -3
- data/lib/samagotchi/participant_mode.rb +2 -1
- data/lib/samagotchi/participants.rb +2 -2
- data/lib/samagotchi/plugin/api.rb +41 -6
- data/lib/samagotchi/plugin/context.rb +1 -1
- data/lib/samagotchi/plugin/loader.rb +33 -10
- data/lib/samagotchi/plugin/sessions.rb +3 -6
- data/lib/samagotchi/plugin/tool_spec.rb +5 -3
- data/lib/samagotchi/project_env.rb +1 -1
- data/lib/samagotchi/reasoning_history.rb +14 -20
- data/lib/samagotchi/recap_place.rb +27 -0
- data/lib/samagotchi/reminder_carry.rb +1 -1
- data/lib/samagotchi/reply_wait/liveness.rb +38 -0
- data/lib/samagotchi/reply_wait/poll.rb +44 -15
- data/lib/samagotchi/reply_wait/target.rb +113 -0
- data/lib/samagotchi/reply_wait.rb +22 -2
- data/lib/samagotchi/reprefill_meter.rb +45 -0
- data/lib/samagotchi/review_command.rb +1 -1
- data/lib/samagotchi/sampling_settings.rb +9 -16
- data/lib/samagotchi/self_report.rb +36 -37
- data/lib/samagotchi/send_command.rb +17 -23
- data/lib/samagotchi/session.rb +370 -445
- data/lib/samagotchi/session_access/local.rb +12 -11
- data/lib/samagotchi/session_access/remote.rb +22 -6
- 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_field.rb +133 -0
- data/lib/samagotchi/session_inbox.rb +49 -1
- data/lib/samagotchi/session_manager/delivery.rb +254 -0
- data/lib/samagotchi/session_manager/emptiness.rb +139 -0
- data/lib/samagotchi/session_manager/lifecycle.rb +274 -0
- data/lib/samagotchi/session_manager/listing.rb +118 -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 +28 -1541
- data/lib/samagotchi/session_metrics.rb +8 -5
- data/lib/samagotchi/session_paths.rb +57 -0
- data/lib/samagotchi/session_retention.rb +2 -2
- data/lib/samagotchi/session_row.rb +200 -0
- data/lib/samagotchi/session_setup.rb +15 -12
- data/lib/samagotchi/sessions_command.rb +11 -129
- data/lib/samagotchi/share_command.rb +1 -1
- data/lib/samagotchi/starting_marker.rb +65 -0
- data/lib/samagotchi/steer_cut_after.rb +7 -14
- data/lib/samagotchi/system_one/recorder.rb +49 -0
- data/lib/samagotchi/terminal_ui/attached_loop.rb +6 -26
- data/lib/samagotchi/terminal_ui/exit_lines.rb +1 -1
- data/lib/samagotchi/terminal_ui/join_replay.rb +86 -8
- data/lib/samagotchi/terminal_ui/local_link.rb +2 -2
- data/lib/samagotchi/terminal_ui.rb +5 -7
- data/lib/samagotchi/testing/install.rb +2 -1
- data/lib/samagotchi/testing/sessions.rb +1 -1
- data/lib/samagotchi/thinking.rb +16 -21
- 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_response.rb +5 -3
- data/lib/samagotchi/tool_runner.rb +25 -5
- data/lib/samagotchi/toolkit_command.rb +1 -1
- data/lib/samagotchi/tools/delegate.rb +55 -45
- data/lib/samagotchi/tools/delegate_relay.rb +5 -5
- data/lib/samagotchi/tools/delegate_result.rb +5 -5
- data/lib/samagotchi/tools/delegate_wait.rb +41 -17
- data/lib/samagotchi/tools/index_hint.rb +14 -0
- data/lib/samagotchi/tools/list_sessions.rb +7 -7
- 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/update_command.rb +11 -7
- data/lib/samagotchi/verify_fresh.rb +7 -11
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/vision_support.rb +11 -14
- data/lib/samagotchi/web/app.rb +45 -28
- data/lib/samagotchi/web/message_display.rb +57 -7
- data/lib/samagotchi/web/participant_access.rb +3 -3
- data/lib/samagotchi/web/public/activity.js +16 -7
- data/lib/samagotchi/web/public/app.js +29 -8
- data/lib/samagotchi/web/public/bubbles.js +8 -3
- data/lib/samagotchi/web/public/cards_place.js +15 -0
- data/lib/samagotchi/web/public/commands.js +5 -1
- data/lib/samagotchi/web/public/data.js +4 -2
- data/lib/samagotchi/web/public/history_shape.js +220 -0
- data/lib/samagotchi/web/public/recap_stamp.js +17 -0
- data/lib/samagotchi/web/public/reload_check.js +71 -0
- data/lib/samagotchi/web/public/stage_model.js +7 -5
- data/lib/samagotchi/web/public/stage_view.js +7 -3
- data/lib/samagotchi/web/public/stream_handlers.js +8 -2
- data/lib/samagotchi/web/public/timing.js +43 -2
- data/lib/samagotchi/web/public/turn_block.js +215 -30
- data/lib/samagotchi/web/public/turn_drawing.js +43 -0
- data/lib/samagotchi/web/public/turn_events.js +10 -1
- data/lib/samagotchi/web/public/turn_pieces.js +29 -0
- data/lib/samagotchi/web/reload_check_route.rb +95 -0
- data/lib/samagotchi/web/server.rb +4 -2
- data/lib/samagotchi/web/session_access_routes.rb +6 -6
- data/lib/samagotchi/web/session_control.rb +4 -4
- data/lib/samagotchi/web/session_create.rb +3 -3
- data/lib/samagotchi/web/session_hub.rb +9 -17
- data/lib/samagotchi/web/session_show.rb +67 -5
- data/lib/samagotchi/web/session_summary.rb +39 -28
- data/lib/samagotchi/worker.rb +83 -32
- data/lib/samagotchi/worker_boot.rb +184 -0
- data/lib/samagotchi/worker_link.rb +3 -0
- data/lib/samagotchi/worker_signals.rb +1 -1
- data/lib/samagotchi/worker_task_wakes.rb +2 -2
- metadata +32 -4
- data/lib/samagotchi/send_command/wait_target.rb +0 -85
|
@@ -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
|
|
|
@@ -799,20 +792,24 @@ module Samagotchi
|
|
|
799
792
|
turn.qwen_attempts = 0
|
|
800
793
|
turn.qwen_partial = nil
|
|
801
794
|
runs = []
|
|
795
|
+
# The batch's ids before it runs (each run's events carry its own):
|
|
796
|
+
# nothing appends to the conversation during a batch, so they are the
|
|
797
|
+
# ids the entry would get after it (spec/tool_ids_pin_spec.rb).
|
|
798
|
+
ids = ToolIds.next_ids(turn.conversation, calls.size)
|
|
802
799
|
begin
|
|
803
800
|
ToolResponse.run_batch(tool_runner, calls, context: @context, iteration: turn.iteration, emit: turn.emit,
|
|
804
|
-
on_stream_event: turn.on_stream_event, cap: turn.cap) { |run, _| runs << run }
|
|
801
|
+
on_stream_event: turn.on_stream_event, cap: turn.cap, ids: ids) { |run, _| runs << run }
|
|
805
802
|
rescue Interrupt
|
|
806
803
|
# A Ctrl-C mid-batch: the entry still answers every call (the
|
|
807
804
|
# finished runs, then run_batch's interrupted ones), for #run to
|
|
808
805
|
# hand on with the conversation.
|
|
809
|
-
turn.conversation << ToolResponse.joined(runs, ids:
|
|
806
|
+
turn.conversation << ToolResponse.joined(runs, ids: ids.first(runs.size))
|
|
810
807
|
raise
|
|
811
808
|
end
|
|
812
809
|
turn.tool_activity.concat(ToolResponse.activities(runs))
|
|
813
810
|
# One entry for the batch: the capped outputs joined (ToolResponse.joined),
|
|
814
811
|
# an id per run (ToolIds).
|
|
815
|
-
turn.conversation << ToolResponse.joined(runs, ids:
|
|
812
|
+
turn.conversation << ToolResponse.joined(runs, ids: ids.first(runs.size))
|
|
816
813
|
turn.pending_tool_calls = true
|
|
817
814
|
:next
|
|
818
815
|
end
|
|
@@ -877,12 +874,6 @@ module Samagotchi
|
|
|
877
874
|
# Coerce a value to boolean — handles true/false, nil, and string "true"/"false".
|
|
878
875
|
def truthy?(val) = Tools::Builtins.truthy?(val)
|
|
879
876
|
|
|
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
877
|
# One request, cancelled by +generation_controller+ (the turn's child)
|
|
887
878
|
# when there is one.
|
|
888
879
|
def request_generation(prompt, generation_controller:, cancel_controller:, **)
|
|
@@ -1005,14 +996,10 @@ module Samagotchi
|
|
|
1005
996
|
WARMUP_CUT = "chi-warmup-cut"
|
|
1006
997
|
private_constant :WARMUP_CUT
|
|
1007
998
|
|
|
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
|
|
999
|
+
# What the native prompt and the warm-up send of the conversation
|
|
1000
|
+
# (LLMContextView.for), under the turn's strategy or the one given (the
|
|
1001
|
+
# warm-up's, the next turn's).
|
|
1002
|
+
def llm_context_view(llm_context = turn_settings&.llm_context) = LLMContextView.for(llm_context)
|
|
1016
1003
|
|
|
1017
1004
|
# The model's forget_outputs call (LLMContextForget) on the running
|
|
1018
1005
|
# turn (the loop's llm_context_turn), under +context+'s strategy (the
|
|
@@ -1042,84 +1029,19 @@ module Samagotchi
|
|
|
1042
1029
|
end
|
|
1043
1030
|
private :pinned_ids
|
|
1044
1031
|
|
|
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).
|
|
1032
|
+
# The strategy's edits on +conversation+ (LLMContextApply.apply!), under
|
|
1033
|
+
# the turn's strategy and model name by default: the native run's
|
|
1034
|
+
# requests and turn end, the warm-up's copy, a forget_outputs call.
|
|
1057
1035
|
# @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
|
|
1036
|
+
def apply_llm_context!(conversation, llm_context = turn_settings&.llm_context, model: turn_settings&.model_name, **)
|
|
1037
|
+
LLMContextApply.apply!(conversation, llm_context, model: model, **)
|
|
1116
1038
|
end
|
|
1117
1039
|
|
|
1118
1040
|
# The Engine started a turn-end warm-up: it prefills the next prompt
|
|
1119
1041
|
# off the clock, so the next request's cache counts are against it, not
|
|
1120
|
-
# the last request (
|
|
1042
|
+
# the last request (the re-prefill meter starts over, ReprefillMeter#reset!).
|
|
1121
1043
|
def warmed_up!
|
|
1122
|
-
@
|
|
1044
|
+
@reprefill.reset!
|
|
1123
1045
|
end
|
|
1124
1046
|
|
|
1125
1047
|
# Public wrapper so other loops (e.g. the chat loop) can strip
|
|
@@ -1212,21 +1134,28 @@ module Samagotchi
|
|
|
1212
1134
|
# first (see Samagotchi::LLM::NativeToolNormalizer); dispatch itself never
|
|
1213
1135
|
# parses provider text. +context+: the call's turn (TurnContext: its
|
|
1214
1136
|
# ToolSurface, the handler's ToolContext); a bare kernel's own
|
|
1215
|
-
# (#
|
|
1216
|
-
def dispatch_tool_call(call, context =
|
|
1137
|
+
# (#own_context) by default.
|
|
1138
|
+
def dispatch_tool_call(call, context = own_context)
|
|
1217
1139
|
dispatch(call, context)
|
|
1218
1140
|
end
|
|
1219
1141
|
|
|
1142
|
+
# The context a bare kernel (no Engine: a spec, a bench) runs in when
|
|
1143
|
+
# given none: #context, with a Tools::ToolSurface built now from the
|
|
1144
|
+
# kernel's tools when it has none (Tools::ToolSurface.bare). An
|
|
1145
|
+
# Engine's turn always hands its own down (Engine#generate's surface).
|
|
1146
|
+
# @return [TurnContext]
|
|
1147
|
+
def own_context = Tools::ToolSurface.bare(@context, tools)
|
|
1148
|
+
|
|
1220
1149
|
private
|
|
1221
1150
|
|
|
1222
1151
|
# The call runs on the entry the turn's surface gives it
|
|
1223
1152
|
# (Tools::ToolSurface#entry_for: a call_tool-reached one only while the
|
|
1224
1153
|
# toolkit reaches it that way, a reachable one by its name on a host
|
|
1225
1154
|
# that loads tools); without one it gets ToolSurface#unknown_tool_error.
|
|
1226
|
-
# A context without a surface
|
|
1155
|
+
# A context without a surface raises Tools::MissingSurface.
|
|
1227
1156
|
def dispatch(call, context)
|
|
1228
1157
|
model = context.settings&.model_name
|
|
1229
|
-
surface = Tools::ToolSurface.of(context
|
|
1158
|
+
surface = Tools::ToolSurface.of(context)
|
|
1230
1159
|
entry = surface.entry_for(call)
|
|
1231
1160
|
unless entry
|
|
1232
1161
|
result = surface.unknown_tool_error(call[:name], via: call[:via])
|
|
@@ -1271,10 +1200,10 @@ module Samagotchi
|
|
|
1271
1200
|
rescue StandardError => e
|
|
1272
1201
|
dump_log("tool_error", e.message, model: context.settings&.model_name, tool: call[:name], error: e.class.name)
|
|
1273
1202
|
result = "Error: #{e.message}"
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
}
|
|
1203
|
+
# No surface (Tools::MissingSurface from ToolSurface.of): no row either,
|
|
1204
|
+
# it would read the surface again.
|
|
1205
|
+
activity = surface && ToolActivity.tool_activity_event(call[:name], call, result, registry: tools, surface: surface)
|
|
1206
|
+
{ output: "[#{call[:name]}] #{result}", activity: activity }
|
|
1278
1207
|
end
|
|
1279
1208
|
end
|
|
1280
1209
|
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
|
|
|
@@ -1116,10 +1132,13 @@ module Samagotchi
|
|
|
1116
1132
|
def dispatch(tool_calls, iteration, cap)
|
|
1117
1133
|
log_direct_deferred(tool_calls)
|
|
1118
1134
|
calls = tool_calls.map { |tool_call| NativeToolNormalizer.normalize(tool_call) }
|
|
1135
|
+
# The batch's ids before it runs (each run's events carry its own):
|
|
1136
|
+
# the same as one next id per result as it lands
|
|
1137
|
+
# (spec/tool_ids_pin_spec.rb).
|
|
1138
|
+
ids = ToolIds.next_ids(@conversation, calls.size)
|
|
1119
1139
|
runs = ToolResponse.run_batch(@loop.tool_runner, calls, context: @turn_context, iteration: iteration, emit: method(:emit),
|
|
1120
|
-
on_stream_event: @on_stream_event, cap: cap) do |run, index|
|
|
1121
|
-
@conversation << ToolResponse.single(run, tool_call_id: tool_calls[index].id,
|
|
1122
|
-
ids: ToolIds.next_ids(@conversation, 1))
|
|
1140
|
+
on_stream_event: @on_stream_event, cap: cap, ids: ids) do |run, index|
|
|
1141
|
+
@conversation << ToolResponse.single(run, tool_call_id: tool_calls[index].id, ids: [ids[index]])
|
|
1123
1142
|
end
|
|
1124
1143
|
@tool_activity.concat(ToolResponse.activities(runs))
|
|
1125
1144
|
end
|
|
@@ -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
|