samagotchi 0.2.0 → 0.4.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 +198 -1
- data/README.md +56 -4
- data/bin/chi +118 -50
- data/docs/cli.md +184 -9
- data/docs/configuration.md +333 -47
- data/docs/desktop.md +45 -4
- data/docs/guardrails.md +11 -0
- data/docs/hooks.md +208 -5
- data/docs/plugins.md +68 -2
- data/docs/releasing.md +23 -13
- data/docs/sessions.md +45 -17
- data/lib/samagotchi/answer_display.rb +95 -0
- data/lib/samagotchi/archive_store.rb +90 -0
- data/lib/samagotchi/bootstrap/config_writer.rb +342 -0
- data/lib/samagotchi/bootstrap/probe.rb +262 -0
- data/lib/samagotchi/bootstrap_command.rb +347 -0
- data/lib/samagotchi/bridge/pending_card.rb +89 -0
- data/lib/samagotchi/bridge/turn_accumulator.rb +15 -3
- data/lib/samagotchi/bridge.rb +13 -1
- data/lib/samagotchi/bridge_client.rb +6 -2
- data/lib/samagotchi/bundles/check-in/manifest.yml +10 -0
- data/lib/samagotchi/bundles/check-in/plugin.rb +244 -0
- data/lib/samagotchi/bundles/source-links/hooks/source_links.rb +531 -0
- data/lib/samagotchi/bundles/source-links/manifest.yml +14 -0
- data/lib/samagotchi/bundles/source-links/source_links.md +5 -0
- data/lib/samagotchi/bundles/system/config_modification_protocol.md +10 -6
- data/lib/samagotchi/bundles/system/delegated.md +6 -7
- data/lib/samagotchi/bundles/system/manifest.yml +4 -4
- data/lib/samagotchi/bundles/system/self_map.md +8 -2
- data/lib/samagotchi/client.rb +81 -19
- data/lib/samagotchi/commands/registry.rb +8 -0
- data/lib/samagotchi/config.rb +252 -48
- data/lib/samagotchi/desktop/macos/App.swift +12 -8
- data/lib/samagotchi/desktop/macos/ChiRunner.swift +17 -9
- data/lib/samagotchi/desktop/macos/Images.swift +113 -0
- data/lib/samagotchi/desktop/macos/Info.plist.erb +6 -0
- data/lib/samagotchi/desktop/macos/Panel.swift +180 -25
- data/lib/samagotchi/desktop/macos.rb +59 -8
- data/lib/samagotchi/desktop_command.rb +6 -3
- data/lib/samagotchi/edit_preview.rb +82 -0
- data/lib/samagotchi/empty_answer_retry.rb +43 -0
- data/lib/samagotchi/engine.rb +434 -140
- data/lib/samagotchi/gem_update.rb +89 -0
- data/lib/samagotchi/guardrails/approval.rb +35 -4
- data/lib/samagotchi/guardrails/load_failures.rb +9 -3
- data/lib/samagotchi/guardrails/scratch_writes.rb +40 -0
- data/lib/samagotchi/guardrails.rb +1 -0
- data/lib/samagotchi/hooks/registry.rb +24 -5
- data/lib/samagotchi/host_registry.rb +9 -12
- data/lib/samagotchi/idle_client.rb +24 -15
- data/lib/samagotchi/idle_recap.rb +5 -1
- data/lib/samagotchi/idle_reminders.rb +2 -2
- data/lib/samagotchi/image_store.rb +10 -6
- data/lib/samagotchi/kernel_loop.rb +73 -94
- data/lib/samagotchi/live_versions.rb +59 -0
- data/lib/samagotchi/llm/api_key.rb +41 -0
- data/lib/samagotchi/llm/chat_loop.rb +132 -29
- data/lib/samagotchi/llm/errors.rb +41 -9
- data/lib/samagotchi/llm/http.rb +57 -17
- data/lib/samagotchi/llm/openai_chat.rb +17 -30
- data/lib/samagotchi/log_subscriber.rb +18 -3
- data/lib/samagotchi/memory_bundle/installer.rb +65 -63
- data/lib/samagotchi/memory_bundle/provenance.rb +51 -12
- data/lib/samagotchi/memory_bundle/shipped_update.rb +157 -0
- data/lib/samagotchi/memory_bundle/status.rb +4 -1
- data/lib/samagotchi/memory_bundle/system_bundle.rb +81 -53
- data/lib/samagotchi/model_profile.rb +24 -1
- data/lib/samagotchi/plugin/context.rb +22 -1
- data/lib/samagotchi/plugin/sessions.rb +3 -1
- data/lib/samagotchi/prompt.rb +4 -2
- data/lib/samagotchi/reminder_store.rb +1 -9
- data/lib/samagotchi/reply_wait.rb +126 -0
- data/lib/samagotchi/sampling_settings.rb +58 -0
- data/lib/samagotchi/self_report.rb +18 -3
- data/lib/samagotchi/send_command.rb +252 -11
- data/lib/samagotchi/session.rb +52 -11
- data/lib/samagotchi/session_archive_command.rb +107 -0
- data/lib/samagotchi/session_commands.rb +46 -7
- data/lib/samagotchi/session_manager.rb +115 -25
- data/lib/samagotchi/session_metrics.rb +222 -106
- data/lib/samagotchi/steer.rb +72 -0
- data/lib/samagotchi/terminal_ui/attached_loop.rb +57 -28
- data/lib/samagotchi/terminal_ui/event_renderer.rb +21 -11
- data/lib/samagotchi/terminal_ui/formatting.rb +40 -8
- data/lib/samagotchi/terminal_ui/input_support.rb +7 -19
- data/lib/samagotchi/terminal_ui/question_prompt.rb +35 -0
- data/lib/samagotchi/terminal_ui.rb +134 -247
- data/lib/samagotchi/text_diff.rb +181 -0
- data/lib/samagotchi/thinking.rb +115 -0
- data/lib/samagotchi/tool_activity.rb +3 -1
- data/lib/samagotchi/tool_runner.rb +34 -1
- data/lib/samagotchi/tools/ask_user_question.rb +41 -33
- data/lib/samagotchi/tools/builtins.rb +15 -4
- data/lib/samagotchi/tools/delegate_wait.rb +26 -69
- data/lib/samagotchi/tools/edit.rb +23 -9
- data/lib/samagotchi/tools/execute.rb +52 -14
- data/lib/samagotchi/tools/task_runtime.rb +19 -0
- data/lib/samagotchi/tools/task_wait.rb +27 -3
- data/lib/samagotchi/tools/write.rb +4 -0
- data/lib/samagotchi/turn_flow.rb +12 -2
- data/lib/samagotchi/turn_note.rb +60 -6
- data/lib/samagotchi/update_command.rb +308 -0
- data/lib/samagotchi/update_hint.rb +59 -0
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/vision_support.rb +7 -9
- data/lib/samagotchi/web/app.rb +91 -7
- data/lib/samagotchi/web/message_parts.rb +8 -3
- data/lib/samagotchi/web/public/activity.js +13 -1
- data/lib/samagotchi/web/public/annotate_presets.js +26 -0
- data/lib/samagotchi/web/public/annotations.js +13 -0
- data/lib/samagotchi/web/public/app.js +472 -111
- data/lib/samagotchi/web/public/card.js +5 -3
- data/lib/samagotchi/web/public/chat_view.js +13 -1
- data/lib/samagotchi/web/public/copy.js +20 -4
- data/lib/samagotchi/web/public/ctx.js +15 -0
- data/lib/samagotchi/web/public/data.js +23 -6
- data/lib/samagotchi/web/public/diff_view.js +58 -0
- data/lib/samagotchi/web/public/format.js +9 -0
- data/lib/samagotchi/web/public/index.html +60 -3
- data/lib/samagotchi/web/public/notify.js +175 -0
- data/lib/samagotchi/web/public/question_card.js +5 -2
- data/lib/samagotchi/web/public/sessions_list.js +7 -0
- data/lib/samagotchi/web/public/timing.js +39 -14
- data/lib/samagotchi/web/public/turn_events.js +75 -5
- data/lib/samagotchi/web/public/turn_view.js +49 -8
- data/lib/samagotchi/web/server.rb +8 -4
- data/lib/samagotchi/web/session_hub.rb +2 -1
- data/lib/samagotchi/web/session_summary.rb +24 -1
- data/lib/samagotchi/worker.rb +16 -4
- metadata +31 -1
|
@@ -13,11 +13,15 @@
|
|
|
13
13
|
- Sessions: each is one file, `<sessions dir>/<id>.json`. A `<id>/` dir beside it exists only
|
|
14
14
|
for background/web workers (input/, notes/, output/, pid). List them with `chi sessions list`
|
|
15
15
|
(`--live` for the ones a worker runs now); delete one with `chi sessions delete ID` (never by hand).
|
|
16
|
+
An archived session (`chi sessions archive ID`, a marker file `<id>/archived`) is hidden from
|
|
17
|
+
every list and kept for good; `chi sessions list --archived` shows it.
|
|
16
18
|
- `[CONTEXT NOTE from …]` messages are context notes (`chi note`, or another session's
|
|
17
19
|
`send_note`): background, not requests. `list_sessions` + `send_note` tell another session
|
|
18
20
|
something without starting a turn there; see `docs/sessions.md` "Context notes".
|
|
19
21
|
- `chi send -m TEXT <id>` is the other half: the text goes in as the user's message and a
|
|
20
22
|
turn runs (stdin piped too = quoted context above it); see `docs/sessions.md` "Sending a message".
|
|
23
|
+
`chi send --new --wait -m TEXT` starts a session the user can watch in the web and prints its
|
|
24
|
+
answer (exit 3: it waits for the user's answer); see "Starting a session".
|
|
21
25
|
- `delegate` hands a task to a child session that runs in parallel and returns only its final
|
|
22
26
|
reply (`delegate_result` waits for it; `session:` sends a follow-up to a child). A child is a
|
|
23
27
|
normal session: it shows in `chi sessions list` with `↳ <parent>`, and the user can steer it
|
|
@@ -26,8 +30,10 @@
|
|
|
26
30
|
commands (`anytime:` ones run mid-turn), model tools (they can return images I see), hooks,
|
|
27
31
|
cards, side answers (`ask_model`),
|
|
28
32
|
child sessions and services (e.g. MCP server processes). Shipped: `btw` (`/btw` side question),
|
|
29
|
-
`mcp` (MCP server tools; a screenshot comes as a picture), `guardrails` (rules), `known-names` (typo guard), `
|
|
30
|
-
(
|
|
33
|
+
`mcp` (MCP server tools; a screenshot comes as a picture), `guardrails` (rules), `known-names` (typo guard), `source-links`
|
|
34
|
+
(turns source refs like JIRA-123 in an answer into links in the web, and a one-line note), `loop-guard`
|
|
35
|
+
(denies a repeated tool call with the same result, stops the turn after a few), `check-in` (after N tool
|
|
36
|
+
calls with no answer, a card asks the user to nudge me, let me go on or stop; `/checkin`). `chi bundle list`
|
|
31
37
|
shows installed + available; `chi bundle install <name>`. Settings: config.yml `bundles: <name>:`
|
|
32
38
|
(`config_modification_protocol`), read at session start: after an install or a settings change,
|
|
33
39
|
tell the user to restart the session. API and bundle docs: `docs/plugins.md`.
|
data/lib/samagotchi/client.rb
CHANGED
|
@@ -8,6 +8,7 @@ require_relative "cancellation_controller"
|
|
|
8
8
|
require_relative "llm/http"
|
|
9
9
|
require_relative "vision_context"
|
|
10
10
|
require_relative "vision_support"
|
|
11
|
+
require_relative "sampling_settings"
|
|
11
12
|
|
|
12
13
|
module Samagotchi
|
|
13
14
|
# Thin HTTP client for llama.cpp's native /completion endpoint, or an
|
|
@@ -27,6 +28,10 @@ module Samagotchi
|
|
|
27
28
|
# budget and no retry (see #server_props).
|
|
28
29
|
CONTEXT_WINDOW_PROBE_OPEN_TIMEOUT = 1
|
|
29
30
|
CONTEXT_WINDOW_PROBE_READ_TIMEOUT = 2
|
|
31
|
+
# Seconds a probe that got no answer (refused, timed out) is remembered:
|
|
32
|
+
# a down or hung server then costs one probe per window, not one per
|
|
33
|
+
# generation, and a new turn doesn't ask again at once.
|
|
34
|
+
PROPS_FAILURE_TTL = 30
|
|
30
35
|
|
|
31
36
|
SERVER_TRANSPORT_ENV = "SAMAGOTCHI_SERVER_TRANSPORT"
|
|
32
37
|
DEFAULT_TRANSPORT = :llama_cpp
|
|
@@ -109,7 +114,7 @@ module Samagotchi
|
|
|
109
114
|
end
|
|
110
115
|
|
|
111
116
|
# One /props probe's outcome. `answered?` is false when the probe failed:
|
|
112
|
-
# a network error, a timeout or any non-200 (llama.cpp answers 503 while
|
|
117
|
+
# a network error, a timeout, a turn's cancel or any non-200 (llama.cpp answers 503 while
|
|
113
118
|
# it loads a model). `body` is the parsed JSON of a 200, or nil when it
|
|
114
119
|
# isn't JSON.
|
|
115
120
|
ServerProps = Data.define(:body, :status) do
|
|
@@ -121,21 +126,38 @@ module Samagotchi
|
|
|
121
126
|
# Moved to its own file; the old name keeps working.
|
|
122
127
|
CancellationController = Samagotchi::CancellationController
|
|
123
128
|
|
|
129
|
+
PROBE_CANCEL_KEY = :samagotchi_probe_cancel
|
|
130
|
+
|
|
131
|
+
# The cancel a /props probe made on this thread listens to: a turn sets
|
|
132
|
+
# its own (Engine#run_turn), so a Stop cuts the probes it makes before
|
|
133
|
+
# its first request. Probes on other threads (chi self, /model, a
|
|
134
|
+
# status snapshot) have none and run to their timeout.
|
|
135
|
+
def self.probe_cancel = Thread.current[PROBE_CANCEL_KEY]
|
|
136
|
+
|
|
137
|
+
# Sets this thread's probe cancel; returns the one it replaces.
|
|
138
|
+
def self.swap_probe_cancel(controller)
|
|
139
|
+
previous = Thread.current[PROBE_CANCEL_KEY]
|
|
140
|
+
Thread.current[PROBE_CANCEL_KEY] = controller
|
|
141
|
+
previous
|
|
142
|
+
end
|
|
143
|
+
|
|
124
144
|
# @param sleeper [#call, nil] waits between retries (specs pass a no-op)
|
|
125
145
|
# @param scheme [String, nil] "https" for a TLS server (default http)
|
|
126
146
|
# @param first_token_timeout [Numeric, nil] seconds a completion may take
|
|
127
147
|
# to stream its first text (LLM::HTTP); nil: no limit
|
|
128
148
|
# @param name [String, nil] the host's config name, for error lines
|
|
129
149
|
# (default: the transport's label)
|
|
150
|
+
# @param api_key_env [String, nil] the variable holding the host's API
|
|
151
|
+
# key (llama.cpp's --api-key), sent as a bearer token on every request;
|
|
152
|
+
# nil sends no Authorization header
|
|
153
|
+
# @param env [Hash] where the key variable is read
|
|
130
154
|
def initialize(host: nil, port: nil, open_timeout: nil, read_timeout: nil, transport: nil, sleeper: nil, scheme: nil,
|
|
131
|
-
first_token_timeout: nil, name: nil)
|
|
155
|
+
first_token_timeout: nil, name: nil, api_key_env: nil, env: ENV)
|
|
132
156
|
# Unified config precedence: CLI > ENV > file > default (via Samagotchi::Config)
|
|
133
157
|
cfg_host = nil; cfg_port = nil; cfg_transport_raw = nil
|
|
134
158
|
begin
|
|
135
159
|
cfg_host = Samagotchi::Config.get("server.host")
|
|
136
160
|
cfg_port = Samagotchi::Config.get("server.port")
|
|
137
|
-
cfg_open_timeout = Samagotchi::Config.get("server.open_timeout")
|
|
138
|
-
cfg_read_timeout = Samagotchi::Config.get("server.read_timeout")
|
|
139
161
|
cfg_transport_raw = Samagotchi::Config.get("server.transport")
|
|
140
162
|
rescue StandardError
|
|
141
163
|
nil
|
|
@@ -143,17 +165,19 @@ module Samagotchi
|
|
|
143
165
|
@host = host || cfg_host
|
|
144
166
|
@port = (port || cfg_port).to_i
|
|
145
167
|
@scheme = scheme || "http"
|
|
146
|
-
@open_timeout = (open_timeout
|
|
147
|
-
@read_timeout = (read_timeout
|
|
168
|
+
@open_timeout = Samagotchi::Config.positive_seconds("server.open_timeout", open_timeout)
|
|
169
|
+
@read_timeout = Samagotchi::Config.positive_seconds("server.read_timeout", read_timeout)
|
|
148
170
|
transport_fallback = cfg_transport_raw || ENV.fetch(SERVER_TRANSPORT_ENV, DEFAULT_TRANSPORT.to_s)
|
|
149
171
|
@transport = build_transport(resolve_transport(transport || transport_fallback))
|
|
150
172
|
@props_cache = {}
|
|
173
|
+
@props_failures = {}
|
|
151
174
|
@props_mutex = Mutex.new
|
|
152
175
|
@first_token_timeout = first_token_timeout
|
|
153
176
|
@host_name = name
|
|
154
177
|
@label = name.to_s.empty? ? @transport.label : name.to_s
|
|
155
178
|
@http = LLM::HTTP.new(label: @label, open_timeout: @open_timeout, read_timeout: @read_timeout,
|
|
156
|
-
sleeper: sleeper, first_token_timeout: first_token_timeout
|
|
179
|
+
sleeper: sleeper, first_token_timeout: first_token_timeout,
|
|
180
|
+
api_key: LLM::ApiKey.for(api_key_env, host: @label, env: env))
|
|
157
181
|
end
|
|
158
182
|
|
|
159
183
|
# Seconds a completion may take to stream its first text, or nil.
|
|
@@ -182,11 +206,14 @@ module Samagotchi
|
|
|
182
206
|
# @param on_retry [Proc, nil] optional callback before retry sleep
|
|
183
207
|
# @param images [Array<String>] base64 images, one per
|
|
184
208
|
# ImagePlan::NATIVE_PLACEHOLDER in the prompt (llama.cpp only)
|
|
209
|
+
# @param sampling [Hash] request fields to add (SamplingSettings):
|
|
210
|
+
# temperature, penalties, …; they can't replace the fields above
|
|
185
211
|
# @return [String] the generated text
|
|
186
212
|
def complete(prompt, stop: ["<end_of_turn>", "<|tool_response>"], n_predict: nil, model: nil, on_chunk: nil, cancel_controller: nil, on_retry: nil,
|
|
187
|
-
images: [])
|
|
213
|
+
images: [], sampling: {})
|
|
188
214
|
images = Array(images)
|
|
189
|
-
|
|
215
|
+
request = { stop: stop, n_predict: n_predict, model: model, sampling: sampling }
|
|
216
|
+
return stream_completion(scrub_utf8(prompt), request, on_chunk, cancel_controller, on_retry) if images.empty?
|
|
190
217
|
|
|
191
218
|
# The media marker is random per server process: a restart between the
|
|
192
219
|
# /props read and the request makes the prompt fail to tokenize, so
|
|
@@ -196,7 +223,7 @@ module Samagotchi
|
|
|
196
223
|
attempts += 1
|
|
197
224
|
payload_prompt = { prompt_string: scrub_utf8(prompt.gsub(ImagePlan::NATIVE_PLACEHOLDER, media_marker!(model))),
|
|
198
225
|
multimodal_data: images }
|
|
199
|
-
stream_completion(payload_prompt,
|
|
226
|
+
stream_completion(payload_prompt, request, on_chunk, cancel_controller, on_retry)
|
|
200
227
|
rescue LLM::BadRequest => e
|
|
201
228
|
raise unless attempts == 1 && e.message.include?("Failed to tokenize prompt")
|
|
202
229
|
|
|
@@ -205,11 +232,14 @@ module Samagotchi
|
|
|
205
232
|
end
|
|
206
233
|
end
|
|
207
234
|
|
|
208
|
-
|
|
235
|
+
OPENAI_API_HINT = "does this host speak the OpenAI API? set `api: openai` on it"
|
|
236
|
+
|
|
237
|
+
private def stream_completion(prompt, fields, on_chunk, cancel_controller, on_retry)
|
|
209
238
|
uri = completion_uri
|
|
210
239
|
request = Net::HTTP::Post.new(uri)
|
|
211
240
|
request["Content-Type"] = "application/json"
|
|
212
|
-
request.body = completion_payload(prompt,
|
|
241
|
+
request.body = completion_payload(prompt, **fields).to_json
|
|
242
|
+
model = fields[:model]
|
|
213
243
|
|
|
214
244
|
result = +""
|
|
215
245
|
reset_on_retry = lambda do |event|
|
|
@@ -219,7 +249,8 @@ module Samagotchi
|
|
|
219
249
|
end
|
|
220
250
|
@http.stream_lines(uri, request, cancel_controller: cancel_controller, on_retry: reset_on_retry,
|
|
221
251
|
on_network_error: ->(_error) { invalidate_context_window! },
|
|
222
|
-
log_fields: { model: model, purpose: "chat"
|
|
252
|
+
log_fields: { model: model, purpose: "chat",
|
|
253
|
+
sampling: SamplingSettings.log_text(sendable_sampling(fields[:sampling])) }) do |line, shown|
|
|
223
254
|
parsed_chunk = parse_stream_line(line)
|
|
224
255
|
next unless parsed_chunk
|
|
225
256
|
|
|
@@ -229,6 +260,11 @@ module Samagotchi
|
|
|
229
260
|
on_chunk&.call(content: content, payload: payload)
|
|
230
261
|
end
|
|
231
262
|
result
|
|
263
|
+
rescue LLM::BadRequest => e
|
|
264
|
+
raise unless e.status == 404 && @transport.name == :llama_cpp
|
|
265
|
+
|
|
266
|
+
# No native /completion here: likely an OpenAI-compatible server.
|
|
267
|
+
raise LLM::BadRequest.new(e.message, host: e.host, status: e.status, attempts: e.attempts, hint: OPENAI_API_HINT)
|
|
232
268
|
rescue RequestCancelled, LLM::ProviderError
|
|
233
269
|
raise
|
|
234
270
|
rescue StandardError => e
|
|
@@ -260,7 +296,9 @@ module Samagotchi
|
|
|
260
296
|
# a turn. The probe names the model (`?model=`): a llama.cpp router
|
|
261
297
|
# answers a stub without it, and a single-model server ignores it.
|
|
262
298
|
# Whatever the server answers (a non-200 too) is cached per model; a
|
|
263
|
-
# network failure
|
|
299
|
+
# network failure for PROPS_FAILURE_TTL seconds, then the next call asks
|
|
300
|
+
# again. A probe cut by this thread's Client.probe_cancel answers
|
|
301
|
+
# :cancelled, uncached, and the turn's next request ends it.
|
|
264
302
|
def server_props(model: nil)
|
|
265
303
|
path = @transport.props_path
|
|
266
304
|
return nil unless path
|
|
@@ -268,10 +306,21 @@ module Samagotchi
|
|
|
268
306
|
key = model.to_s
|
|
269
307
|
@props_mutex.synchronize do
|
|
270
308
|
return @props_cache[key] if @props_cache.key?(key)
|
|
309
|
+
|
|
310
|
+
failed, failed_at = @props_failures[key]
|
|
311
|
+
return failed if failed && monotonic_now - failed_at < PROPS_FAILURE_TTL
|
|
271
312
|
end
|
|
272
313
|
|
|
273
314
|
props = probe_props(path, key)
|
|
274
|
-
@props_mutex.synchronize
|
|
315
|
+
@props_mutex.synchronize do
|
|
316
|
+
case props.status
|
|
317
|
+
when :cancelled then nil
|
|
318
|
+
when :network_error then @props_failures[key] = [props, monotonic_now]
|
|
319
|
+
else
|
|
320
|
+
@props_cache[key] = props
|
|
321
|
+
@props_failures.delete(key)
|
|
322
|
+
end
|
|
323
|
+
end
|
|
275
324
|
props
|
|
276
325
|
end
|
|
277
326
|
|
|
@@ -286,23 +335,30 @@ module Samagotchi
|
|
|
286
335
|
|
|
287
336
|
# Forget cached /props answers: the server may have restarted with
|
|
288
337
|
# another -c, or a model switch may have loaded one with a different
|
|
289
|
-
# window.
|
|
338
|
+
# window. A recent failure stays until its PROPS_FAILURE_TTL ends: it
|
|
339
|
+
# holds no stale window, and asking a hung server again at every turn's
|
|
340
|
+
# start is what it saves.
|
|
290
341
|
def invalidate_context_window!
|
|
291
342
|
@props_mutex.synchronize { @props_cache.clear }
|
|
292
343
|
end
|
|
293
344
|
|
|
294
345
|
private
|
|
295
346
|
|
|
347
|
+
def monotonic_now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
348
|
+
|
|
296
349
|
def probe_props(path, model)
|
|
297
350
|
query = model.empty? ? "" : "?#{URI.encode_www_form(model: model)}"
|
|
298
351
|
uri = URI("#{@scheme}://#{@host}:#{@port}#{path}#{query}")
|
|
299
352
|
response = @http.fetch(uri, Net::HTTP::Get.new(uri), retries: false, check_status: false,
|
|
300
353
|
log_fields: { model: model.empty? ? nil : model, purpose: "probe" },
|
|
301
354
|
open_timeout: CONTEXT_WINDOW_PROBE_OPEN_TIMEOUT,
|
|
302
|
-
read_timeout: CONTEXT_WINDOW_PROBE_READ_TIMEOUT
|
|
355
|
+
read_timeout: CONTEXT_WINDOW_PROBE_READ_TIMEOUT,
|
|
356
|
+
cancel_controller: Client.probe_cancel)
|
|
303
357
|
return ServerProps.new(body: nil, status: :http_error) unless response.code.to_s == "200"
|
|
304
358
|
|
|
305
359
|
ServerProps.new(body: parse_props(response.body), status: :ok)
|
|
360
|
+
rescue RequestCancelled
|
|
361
|
+
ServerProps.new(body: nil, status: :cancelled)
|
|
306
362
|
rescue StandardError
|
|
307
363
|
ServerProps.new(body: nil, status: :network_error)
|
|
308
364
|
end
|
|
@@ -337,12 +393,18 @@ module Samagotchi
|
|
|
337
393
|
URI("#{@scheme}://#{@host}:#{@port}#{@transport.completion_path}")
|
|
338
394
|
end
|
|
339
395
|
|
|
340
|
-
def completion_payload(prompt, stop:, n_predict:, model:)
|
|
396
|
+
def completion_payload(prompt, stop:, n_predict:, model:, sampling: {})
|
|
341
397
|
payload = { prompt: prompt, stop: stop, stream: true }
|
|
342
398
|
payload[@transport.token_limit_key] = n_predict if n_predict && n_predict.to_i.positive?
|
|
343
399
|
model_name = @transport.model_for_payload(model)
|
|
344
400
|
payload[:model] = model_name if model_name
|
|
345
|
-
payload
|
|
401
|
+
sendable_sampling(sampling).merge(payload)
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
# The sampling fields that go out: no nil (nothing to drop here: the
|
|
405
|
+
# server's defaults apply) and none of the reserved request fields.
|
|
406
|
+
def sendable_sampling(sampling)
|
|
407
|
+
(sampling || {}).reject { |key, value| value.nil? || ConfigFile::SAMPLING_RESERVED_KEYS.include?(key.to_s) }
|
|
346
408
|
end
|
|
347
409
|
|
|
348
410
|
# Conversation content (system prompt + tool responses + model output) can
|
|
@@ -74,6 +74,14 @@ module Samagotchi
|
|
|
74
74
|
|
|
75
75
|
def command?(line) = !lookup(line).nil?
|
|
76
76
|
|
|
77
|
+
# @return [Entry, nil] the first entry the UI runs itself (local) that
|
|
78
|
+
# matches +line+, whatever its uis (a UI answers the others' too:
|
|
79
|
+
# the REPL's /detach note)
|
|
80
|
+
def lookup_local(line)
|
|
81
|
+
text = line.to_s.strip
|
|
82
|
+
@entries.find { |entry| entry.local && entry.match?(text) }
|
|
83
|
+
end
|
|
84
|
+
|
|
77
85
|
# @return [Array<Entry>] every entry, in registration order
|
|
78
86
|
def entries = @entries.dup
|
|
79
87
|
|