Kept the conversation, tool results, and draft apart in the token count

This commit is contained in:
Thorsten Sommer committed 2026-09-23 19:32:42 +02:00
1 parent ffc6536ce3
commit f26305336f
6 files changed
+283 -148

No files matched your search

@@ -158,9 +158,11 @@ public partial class ChatComponent : MSGComponentBase
/// What the helper text under the input field says about the token budget.
/// </summary>
/// <remarks>
/// Four sentences rather than one built from pieces, because a translator needs to see the
/// whole thing: which of the two numbers is the limit, and where the word for "about" belongs,
/// are decisions no language makes the same way.
/// The number of the conversation and the window stand in four whole sentences, because a
/// translator needs to see the whole thing: which of the two numbers is the limit, and where the
/// word for "about" belongs, are decisions no language makes the same way. What follows -- the
/// tools' share, the draft, the pictures -- is added as clauses which are whole phrases in turn,
/// each with the one number it is about.
///
/// The images are named rather than counted. Every vendor charges a picture differently, and
/// none of those rules can be applied without decoding the file, so the honest answer is to say
@@ -174,34 +176,42 @@ public partial class ChatComponent : MSGComponentBase
return string.Empty;
//
// Three statements, and which of them can be trusted differs. What the conversation
// Several statements, and which of them can be trusted differs. What the conversation
// has cost is exact wherever the provider said it; the window is whatever somebody
// wrote down about the model; what is being written has never been sent and can only
// ever be estimated. So the conversation and the draft are named apart, and the word
// which marks a guess sits where the guess is.
// wrote down about the model; what the tools returned is only there while they work;
// what is being written has never been sent and can only ever be estimated. So they
// are named apart, and the word which marks a guess sits where the guess is.
//
var history = TokenAmount.Format(this.conversationTokens.Tokens - this.conversationTokens.DraftTokens, this.currentCulture);
var history = TokenAmount.Format(this.conversationTokens.HistoryTokens, this.currentCulture);
var historyIsExact = this.conversationTokens.HistoryIsReported || !this.conversationTokens.IsEstimate;
var budget = this.conversationTokens.Window.IsKnown
? string.Format(historyIsExact ? this.T("{0} of {1} tokens") : this.T("approx. {0} of {1} tokens"), history, TokenAmount.Format(this.conversationTokens.Window.DefaultTokens, this.currentCulture))
: string.Format(historyIsExact ? this.T("{0} tokens") : this.T("approx. {0} tokens"), history);
//
// Said while the tools work, so that the number does not climb and fall back without a
// reason anybody could see: all of it is sent with every round of this request, and none
// of it with the next message.
//
if (this.conversationTokens.ToolTokens > 0)
budget = string.Format(this.T("{0}, of which approx. {1} from tool results"), budget, TokenAmount.Format(this.conversationTokens.ToolTokens, this.currentCulture));
if (this.conversationTokens.DraftTokens > 0)
budget = string.Format(this.T("{0}, plus approx. {1} for your message"), budget, TokenAmount.Format(this.conversationTokens.DraftTokens, this.currentCulture));
if (this.conversationTokens.UncountedImages is 0)
return budget;
//
// The pictures of the whole conversation, not of the message being written: every one
// of them is sent again with every further message, so a chat runs past the model's
// limit long after anybody last thought about images.
// limit long after anybody last thought about images. That is worth saying even when the
// provider counted all of them.
//
var images = this.conversationTokens.TooManyImages
? string.Format(this.T("plus {0} image(s), which is more than the {1} this model accepts"), this.conversationTokens.UncountedImages, this.conversationTokens.ImageLimits.MaxInOneMessage)
: string.Format(this.T("plus {0} image(s), which cannot be counted"), this.conversationTokens.UncountedImages);
if (this.conversationTokens.TooManyImages)
return $"{budget} {string.Format(this.T("plus {0} image(s), which is more than the {1} this model accepts"), this.conversationTokens.Images, this.conversationTokens.ImageLimits.MaxInOneMessage)}";
return $"{budget} {images}";
if (this.conversationTokens.UncountedImages is 0)
return budget;
return $"{budget} {string.Format(this.T("plus {0} image(s), which cannot be counted"), this.conversationTokens.UncountedImages)}";
}
}