diff options
| author | Danilo M. <danix@danix.xyz> | 2026-08-21 22:54:09 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-08-21 22:54:09 +0200 |
| commit | a241128e5a70cb75df9b7846f34f725ea84e4eeb (patch) | |
| tree | 1e6a143c823c850a32dfc2d28b8c00fb95d33658 | |
| parent | a7422ddeb7771983e984350b31092fe4898897c6 (diff) | |
| download | llamachat-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.md | 11 | ||||
| -rw-r--r-- | README.md | 11 | ||||
| -rw-r--r-- | llamachat/ui.py | 17 |
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 @@ -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: |
