aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-08-21 22:54:09 +0200
committerDanilo M. <danix@danix.xyz>2026-08-21 22:54:09 +0200
commita241128e5a70cb75df9b7846f34f725ea84e4eeb (patch)
tree1e6a143c823c850a32dfc2d28b8c00fb95d33658
parenta7422ddeb7771983e984350b31092fe4898897c6 (diff)
downloadllamachat-a241128e5a70cb75df9b7846f34f725ea84e4eeb.tar.gz
llamachat-a241128e5a70cb75df9b7846f34f725ea84e4eeb.zip
fix: store per-turn token sums so searched turns price every round
A searched turn makes one API call per search round and all of them are billed, but prompt_tokens/completion_tokens held only the last round's counts, so a searched turn under-reported its cost. The columns now hold the sum across rounds; usage_json keeps each round's raw usage block as a JSON array for external consumers to verify against. A single-round turn stores the same numbers as before.
-rw-r--r--CHANGELOG.md11
-rw-r--r--README.md11
-rw-r--r--llamachat/ui.py17
3 files changed, 26 insertions, 13 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 34314f8..2a503a7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -41,9 +41,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
as a JSON array (a searched turn makes one API call per search round, and
all of them are billed, so all of them survive) for external consumers
such as the cost dashboard's ingester; and `reported_cost_usd` stores the
- GUI's hand-entered-price estimate for the reply, summed across rounds, as
- a sanity check only, staying NULL for an unpriced model rather than
- claiming it cost nothing.
+ GUI's hand-entered-price estimate for the reply as a sanity check only,
+ staying NULL for an unpriced model rather than claiming it cost nothing.
+ `prompt_tokens` and `completion_tokens` are now the turn's totals summed
+ across rounds rather than the last round's counts, so a searched turn is
+ priced for every API call it made.
- Per-provider `thinking_budget` option, for endpoints (currently SiliconFlow)
that cap chain-of-thought tokens separately from the final answer. Unset
providers skip the key entirely so other endpoints do not receive an unknown
@@ -56,6 +58,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- On-demand diagnostic log. Setting `LLAMACHAT_DEBUG_LOG` to a path appends
timestamped provider-error records (status, request body, full response) to
that file; unset, it writes nothing, so a normal launch never grows a log.
+- A dismiss (✕) button on status-line messages. They previously stayed until
+ some later action replaced them; any status message can now be cleared
+ without waiting.
### Fixed
diff --git a/README.md b/README.md
index 416a84c..f53d167 100644
--- a/README.md
+++ b/README.md
@@ -312,11 +312,12 @@ web pages would compete with messages the user actually wrote.
`provider` records which provider served the reply (`local` or a configured
provider name), written when the reply finishes so history stays
self-describing even if `config.toml` later renames or drops the provider.
-`usage_json` keeps every round's raw usage block from the stream as a JSON
-array — a searched turn makes one API call per search round, and each round
-is billed separately, so all of them are kept verbatim and token details
-beyond the two counts (e.g. cached or reasoning tokens on a cloud endpoint)
-are recoverable later without re-guessing a field mapping.
+`prompt_tokens` and `completion_tokens` hold the turn's totals summed across
+every round — a searched turn makes one API call per search round and all of
+them are billed, so the last round's counts alone would under-report it.
+`usage_json` keeps each round's raw usage block as a JSON array, verbatim, so
+token details beyond the two counts (e.g. cached or reasoning tokens on a
+cloud endpoint) are recoverable later without re-guessing a field mapping.
`reported_cost_usd` is the GUI's hand-entered-price estimate for that one
reply (summed across rounds), stored as a sanity check for external
consumers — it is approximate and never a source of truth; it stays NULL for
diff --git a/llamachat/ui.py b/llamachat/ui.py
index 32f7242..11fffae 100644
--- a/llamachat/ui.py
+++ b/llamachat/ui.py
@@ -1290,12 +1290,19 @@ class ChatWindow(QMainWindow):
"""Keep every round's usage block for the finished turn.
A searched turn makes several API calls (one per search round) and
- the dashboard prices each round's tokens, so all of them must
- survive -- not just the last one the meter shows. The block is a
- JSON array of the raw per-round usage objects; the ingester sums
- what it needs.
+ each round is billed, so the stored counts are the SUM across rounds
+ -- the honest "what this turn cost" -- while usage_json keeps every
+ round's raw block for the dashboard's ingester to verify against.
+ For a single-round turn the sum is the same number as before.
"""
- self.turn_usage_json = blocks if blocks != "[]" else None
+ parsed = json.loads(blocks)
+ self.turn_usage_json = blocks if parsed else None
+ self.turn_prompt_tokens = (
+ sum(b.get("prompt_tokens") or 0 for b in parsed) or None
+ )
+ self.turn_completion_tokens = (
+ sum(b.get("completion_tokens") or 0 for b in parsed) or None
+ )
@Slot(str)
def _on_reasoning(self, piece: str) -> None: