Class: Insika::Wiring::GraphChat

Inherits:
Object
  • Object
show all
Defined in:
lib/insika/wiring/graph_chat.rb

Overview

A synchronous, in-process "chat" seam over an assembled Graph::Result: dispatch a Command, drain the event stream to its terminal event, hand back the outcome. Extracted from DSL::Runtime#chat/#run_workflow (C3.1) so a graph built OUTSIDE the DSL — config/deployment.rb, the actual production/round1 composition root config.ru boots — can hand a #chat-capable object to anything that needs one (today: run_persona_eval's Evals::GraphTransport seam), without a second copy of this dispatch+drain plumbing. DSL::Runtime now delegates here too — one implementation, exercised by both roots.

Constant Summary collapse

TERMINAL =
%i[task_completed task_failed task_cancelled].freeze

Instance Method Summary collapse

Constructor Details

#initialize(graph:) ⇒ GraphChat

Returns a new instance of GraphChat.



17
18
19
# File 'lib/insika/wiring/graph_chat.rb', line 17

def initialize(graph:)
  @graph = graph
end

Instance Method Details

#chat(message, agent:, session_id: nil, timeout: nil) ⇒ Object

One turn, in-process -> the assistant's text. agent: is REQUIRED — this seam has no notion of "the default agent" (that is a DSL::Runtime concept, filled in by its own caller before delegating here). Raises Insika::Error on a failed/cancelled turn, or the command's own rejection (unknown agent, bad schema) verbatim.



26
27
28
29
30
31
32
# File 'lib/insika/wiring/graph_chat.rb', line 26

def chat(message, agent:, session_id: nil, timeout: nil)
  command = Insika::Command.build(
    :send_message, { agent: agent.to_s, message: message, session_id: session_id },
    transport: :cli
  )
  run_command(command, session_id: session_id, timeout: timeout)[:text]
end

#run_command(command, session_id:, timeout:) ⇒ Object

Dispatch + drain, generic over any Command — the seam a workflow run also needs (a :trigger_workflow Command carries :output, not :text). Subscribes BEFORE dispatching (the fiber may emit eagerly) and binds to the dispatched task. A synchronous handler error (unknown agent/ workflow, bad input against the schema) propagates untouched — the caller sees the real ValidationError/NotFoundError, not a turn failure.

Raises:



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/insika/wiring/graph_chat.rb', line 40

def run_command(command, session_id:, timeout:)
  require "async"
  outcome = {}
  rejected = nil
  Async do |task|
    serving = !session_id.nil?
    @graph.executor.supervised = true if serving
    ensure_session(session_id) if session_id
    sub = @graph.event_stream.subscribe
    res = begin
      @graph.bus.dispatch(command)
    rescue StandardError => e
      # CAPTURED, not re-raised inside the reactor: letting it escape the
      # Async block logs "Task may have ended with unhandled exception" —
      # alarming noise for a DOCUMENTED rejection (a schema violation, an
      # unknown agent). Re-raised verbatim below, outside the reactor.
      sub.close
      rejected = e
      next
    end
    sub.bind(task_id: res[:task_id])
    drain(sub, outcome, task, timeout)
    teardown_serving if serving
  end.wait
  raise rejected if rejected
  raise Insika::Error, "turn #{outcome[:error]}" if outcome[:error]

  outcome
end