Class: LLM::Client
- Inherits:
-
Object
- Object
- LLM::Client
- Defined in:
- lib/llm/client.rb
Overview
Convenience layer over Providers::Anthropic for phantom sessions (Mneme, Melete, Mneme::L2Runner) that need a multi-round tool-use loop driven from plain Ruby objects rather than the main drain pipeline.
The main agent loop does NOT use this class — DrainJob talks to the provider directly and emits Events::LLMResponded for Events::Subscribers::LLMResponseHandler to process. The tool loop here is deliberately minimal: no events, no AASM transitions, no interrupt handling — phantom sessions don’t interact with those machineries.
Constant Summary collapse
- INTERRUPT_MESSAGE =
Synthetic tool_result text shown when a tool run is aborted by the user’s Escape press. Mirrored into the interrupt subsystem so both the bash tool and any future interrupt handler share the phrasing.
"Your human wants your attention"
Instance Attribute Summary collapse
-
#max_tokens ⇒ Integer
readonly
Maximum tokens in the response.
-
#model ⇒ String
readonly
The model identifier used for API calls.
-
#provider ⇒ Providers::Anthropic
readonly
The underlying API provider.
Instance Method Summary collapse
-
#chat_with_tools(messages, registry:, **options) ⇒ Hash
Runs a minimal multi-round tool-use cycle: call the LLM, execute any requested tools, feed results back, repeat until the LLM produces a final text response.
-
#initialize(model: Anima::Settings.model, max_tokens: Anima::Settings.max_tokens, provider: nil, logger: nil) ⇒ Client
constructor
A new instance of Client.
Constructor Details
#initialize(model: Anima::Settings.model, max_tokens: Anima::Settings.max_tokens, provider: nil, logger: nil) ⇒ Client
Returns a new instance of Client.
40 41 42 43 44 45 |
# File 'lib/llm/client.rb', line 40 def initialize(model: Anima::Settings.model, max_tokens: Anima::Settings.max_tokens, provider: nil, logger: nil) @provider = build_provider(provider) @model = model @max_tokens = max_tokens @logger = logger end |
Instance Attribute Details
#max_tokens ⇒ Integer (readonly)
Returns maximum tokens in the response.
33 34 35 |
# File 'lib/llm/client.rb', line 33 def max_tokens @max_tokens end |
#model ⇒ String (readonly)
Returns the model identifier used for API calls.
30 31 32 |
# File 'lib/llm/client.rb', line 30 def model @model end |
#provider ⇒ Providers::Anthropic (readonly)
Returns the underlying API provider.
27 28 29 |
# File 'lib/llm/client.rb', line 27 def provider @provider end |
Instance Method Details
#chat_with_tools(messages, registry:, **options) ⇒ Hash
Runs a minimal multi-round tool-use cycle: call the LLM, execute any requested tools, feed results back, repeat until the LLM produces a final text response.
Intended for phantom sessions (Mneme, Melete). No events are emitted and no persistence happens — the caller is responsible for capturing whatever state the tool runs produce.
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/llm/client.rb', line 60 def chat_with_tools(, registry:, **) = .dup rounds = 0 last_api_metrics = nil loop do rounds += 1 max_rounds = Anima::Settings.max_tool_rounds if rounds > max_rounds return {text: "[Tool loop exceeded #{max_rounds} rounds — halting]", api_metrics: last_api_metrics} end response = provider.( model: model, messages: , max_tokens: max_tokens, tools: registry.schemas, include_metrics: true, ** ) last_api_metrics = response.api_metrics if response.respond_to?(:api_metrics) log(:debug, "stop_reason=#{response["stop_reason"]} content_types=#{(response["content"] || []).map { |b| b["type"] }.join(",")}") if response["stop_reason"] == "tool_use" tool_results = execute_tools(response, registry) += [ {role: "assistant", content: response["content"]}, {role: "user", content: tool_results} ] else return {text: extract_text(response), api_metrics: last_api_metrics} end end end |