Class: Envoy::Conversation
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- Envoy::Conversation
- Defined in:
- app/models/envoy/conversation.rb
Instance Method Summary collapse
-
#effective_system_prompt ⇒ Object
Per-conversation ad-hoc instructions, appended last by Runner.
-
#heal_interrupted_tail! ⇒ Object
Truncate an interrupted tail so the next turn starts from a valid state.
- #prompt_body ⇒ Object
-
#read_only? ⇒ Boolean
A surface may pin a chat read-only; so may the conversation's own status.
- #resolved_model_id ⇒ Object
-
#surface ⇒ Object
Embed conversations resolve everything through their surface, so editing a surface reaches chats that already exist.
- #toolset ⇒ Object
Instance Method Details
#effective_system_prompt ⇒ Object
Per-conversation ad-hoc instructions, appended last by Runner.
36 37 38 |
# File 'app/models/envoy/conversation.rb', line 36 def effective_system_prompt system_prompt.presence end |
#heal_interrupted_tail! ⇒ Object
Truncate an interrupted tail so the next turn starts from a valid state.
acts_as_chat replays the whole message log to the provider every turn. If a turn died without a RubyLLM::Error (a deploy restart mid-turn, an OOM, a network kill), RubyLLM's own cleanup never ran and the log can end with a blank assistant row or an assistant tool_use that has no result — both of which the provider rejects, bricking every future turn. Drop everything after the last completed assistant turn (non-blank content, every tool_use answered); if there is no such checkpoint yet, drop everything but a leading system prefix. Returns the number of messages removed. A steady conversation already ending on a completed turn is a no-op.
Safe because a completed assistant turn could only have been produced if the log before it already replayed cleanly — so nothing before the checkpoint can be malformed.
55 56 57 58 59 60 61 62 63 64 65 |
# File 'app/models/envoy/conversation.rb', line 55 def heal_interrupted_tail! ordered = .order(:id).to_a checkpoint = ordered.rindex { |m| completed_assistant_turn?(m) } stale = checkpoint ? ordered[(checkpoint + 1)..] : ordered.reject { |m| m.role == "system" } return 0 if stale.empty? # Newest first so a tool-result message is removed before the tool_call it # points at (destroyed via the assistant's dependent: :destroy). stale.reverse_each(&:destroy!) stale.size end |
#prompt_body ⇒ Object
25 26 27 28 |
# File 'app/models/envoy/conversation.rb', line 25 def prompt_body return surface.prompt_body if surface prompt_key.present? ? Envoy.prompt(prompt_key).full_body : nil end |
#read_only? ⇒ Boolean
A surface may pin a chat read-only; so may the conversation's own status.
31 32 33 |
# File 'app/models/envoy/conversation.rb', line 31 def read_only? status == "read_only" || !!surface&.read_only? end |
#resolved_model_id ⇒ Object
21 22 23 |
# File 'app/models/envoy/conversation.rb', line 21 def resolved_model_id surface&.model_id || model_id || Envoy.config.default_model end |
#surface ⇒ Object
Embed conversations resolve everything through their surface, so editing a surface reaches chats that already exist. Console conversations have no surface and fall back to their own columns.
13 14 15 |
# File 'app/models/envoy/conversation.rb', line 13 def surface surface_key.present? ? Envoy.surface(surface_key) : nil end |