0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-11 02:59:07 +08:00
discourse/plugins/discourse-ai/lib/completions/token_usage_tracker.rb
Sam f86bdd738a
DEV: Make AI context compaction global (#41317)
Apply token-budgeted agent execution and context compaction to all AI
agent runs instead of gating it behind execution_mode. Remove the legacy
fixed-limit agent settings, keep compression_threshold defaulted to 80,
and trim prompt history from the latest compression checkpoint.

Why:
Preserving a stable compressed context across turns keeps important
conversation state available while allowing newer messages to append to
a
consistent prefix. That improves cache reuse and avoids repeatedly
throwing away useful context just to stay under model limits.

Compaction shape:

  before: turn 1 [full -> compact] | turn 2 [full -> compact]
  after:  [compact checkpoint] -> turn 1 -> turn 2 -> ...

---------

Co-authored-by: Rafael Silva <xfalcox@gmail.com>
2026-07-14 18:41:37 -03:00

71 lines
2.1 KiB
Ruby
Vendored

# frozen_string_literal: true
module DiscourseAi
module Completions
class TokenUsageTracker
def initialize(base_total: nil, base_request: nil, base_response: nil)
@mutex = Mutex.new
if base_request.nil? && base_response.nil?
total = base_total.to_i
initial_request = total / 2
@request = initial_request
@response = total - initial_request
else
if !base_total.nil?
raise ArgumentError, "base_total cannot be combined with base_request/base_response"
end
if base_request.nil? || base_response.nil?
raise ArgumentError, "base_request and base_response must both be provided"
end
@request = base_request.to_i
@response = base_response.to_i
end
end
def add_from_audit_log(log)
# request_tokens = non-cached input (already excludes cached)
# cache_write_tokens = newly cached (full cost)
# cache_read_tokens = served from cache (1/10 cost)
request =
log.request_tokens.to_i + log.cache_write_tokens.to_i +
(log.cache_read_tokens.to_i * 0.1).to_i
response = log.response_tokens.to_i
request = estimate_tokens(raw_payload(log, :raw_request_payload)) if request <= 0
response = estimate_tokens(raw_payload(log, :raw_response_payload)) if response <= 0
add_effective(request: request, response: response)
end
def raw_payload(log, name)
log.public_send(name) if log.respond_to?(name)
end
def estimate_tokens(payload)
return 0 if payload.blank?
(payload.to_s.bytesize / 3.0).ceil
end
def add_effective(request:, response:)
@mutex.synchronize do
@request += request.to_i
@response += response.to_i
end
end
def request
@mutex.synchronize { @request }
end
def response
@mutex.synchronize { @response }
end
def total
@mutex.synchronize { @request + @response }
end
end
end
end