Class: Lyman::Conversation

Inherits:
Object
  • Object
show all
Defined in:
lib/lyman/conversation.rb

Overview

The item that flows through the spine and the circuit: the whole conversation so far, plus the control data workers consult to decide whether to act (never which of several things to do).

An immutable value. Shifty 0.6 deep-freezes every value at a worker boundary (the :frozen handoff policy), so change is expressed as new values: each with_* method returns a new Conversation and leaves the receiver untouched. Data#with structurally shares the unchanged members — safe precisely because handed-off values are frozen.

Messages use string keys throughout, for clean round-tripping with OpenAI-compatible wire formats.

Instance Method Summary collapse

Constructor Details

#initialize(system_prompt: nil, messages: nil, rounds: 0, max_rounds: 10, finished: false) ⇒ Conversation

Returns a new instance of Conversation.



15
16
17
18
# File 'lib/lyman/conversation.rb', line 15

def initialize(system_prompt: nil, messages: nil, rounds: 0, max_rounds: 10, finished: false)
  messages ||= system_prompt ? [{"role" => "system", "content" => system_prompt}] : []
  super(messages: messages, rounds: rounds, max_rounds: max_rounds, finished: finished)
end

Instance Method Details

#finishObject



54
55
56
# File 'lib/lyman/conversation.rb', line 54

def finish
  with(finished: true)
end

#finished?Boolean

Returns:

  • (Boolean)


58
59
60
# File 'lib/lyman/conversation.rb', line 58

def finished?
  finished
end

#last_assistant_contentObject



49
50
51
52
# File 'lib/lyman/conversation.rb', line 49

def last_assistant_content
  message = messages.rfind { |m| m["role"] == "assistant" }
  message && message["content"]
end

#pending_tool_callsObject



43
44
45
46
47
# File 'lib/lyman/conversation.rb', line 43

def pending_tool_calls
  last = messages.last
  return [] unless last && last["role"] == "assistant"
  last["tool_calls"] || []
end

#runaway?Boolean

Returns:

  • (Boolean)


62
63
64
# File 'lib/lyman/conversation.rb', line 62

def runaway?
  rounds >= max_rounds
end

#with_assistant_message(message) ⇒ Object

A round is one model reply, so the counter lives here rather than in the transport worker — swap the transport and the runaway guard still can't be forgotten.



31
32
33
# File 'lib/lyman/conversation.rb', line 31

def with_assistant_message(message)
  with(messages: messages + [message], rounds: rounds + 1)
end

#with_tool_result(tool_call_id, content) ⇒ Object



35
36
37
38
39
40
41
# File 'lib/lyman/conversation.rb', line 35

def with_tool_result(tool_call_id, content)
  with(messages: messages + [{
    "role" => "tool",
    "tool_call_id" => tool_call_id,
    "content" => content
  }])
end

#with_user_message(text) ⇒ Object



20
21
22
23
24
25
26
# File 'lib/lyman/conversation.rb', line 20

def with_user_message(text)
  with(
    messages: messages + [{"role" => "user", "content" => text}],
    rounds: 0,
    finished: false
  )
end