Restored the short form of large token numbers

This commit is contained in:
Thorsten Sommer 2026-09-23 18:18:22 +02:00
parent 39c93f6812
commit bbd73f389b
Signed by untrusted user who does not match committer: tsommer
GPG Key ID: 371BBA77A02C0108
3 changed files with 27 additions and 21 deletions

View File

@ -6,11 +6,10 @@ namespace AIStudio.Chat;
/// Writes a number of tokens the way a person reads it next to their input field. /// Writes a number of tokens the way a person reads it next to their input field.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Written out in full up to a million, because below that the short form saves nothing: "11.86k" /// A context window of a million tokens written out in full is eight characters of noise under a
/// and "11,860" are both six characters, and "999.99k" and "999,990" are both seven. All the /// text field, and nobody reads the last five of them. So everything from a thousand on is
/// prefix does in that range is ask the reader to know what it stands for, and not every reader /// shortened, and two decimals keep the resolution a person acts on: the difference between 1.20k
/// does. From a million on it earns its place -- nine characters of digits become five -- and two /// and 1.80k is one they can see, while the last three digits of 1,234 are not.
/// decimals there keep the resolution a person acts on.
/// ///
/// The culture is passed in rather than taken from the thread. AI Studio's language is chosen in /// The culture is passed in rather than taken from the thread. AI Studio's language is chosen in
/// its settings and does not move the thread's culture along with it, so a German who picked German /// its settings and does not move the thread's culture along with it, so a German who picked German
@ -21,7 +20,7 @@ public static class TokenAmount
/// <summary> /// <summary>
/// Below this, the exact number is shown. /// Below this, the exact number is shown.
/// </summary> /// </summary>
private const int EXACT_BELOW = 1_000_000; private const int EXACT_BELOW = 1_000;
/// <summary> /// <summary>
/// Writes a number of tokens. /// Writes a number of tokens.
@ -34,6 +33,15 @@ public static class TokenAmount
if (tokens < EXACT_BELOW) if (tokens < EXACT_BELOW)
return tokens.ToString("N0", culture); return tokens.ToString("N0", culture);
//
// Rounded before the unit is chosen, not after. Otherwise the few hundred tokens just below
// a million round up inside their own unit and read as "1,000.00k", which is a number
// nobody writes.
//
var thousands = tokens / 1_000d;
if (Math.Round(thousands, 2) < 1_000d)
return $"{thousands.ToString("N2", culture)}k";
return $"{(tokens / 1_000_000d).ToString("N2", culture)}M"; return $"{(tokens / 1_000_000d).ToString("N2", culture)}M";
} }
} }

View File

@ -23,7 +23,6 @@
- Added a live read of that context window at the providers which report it, among them Mistral, Groq, OpenRouter, and self-hosted vLLM servers. You then get the window your own server was started with, not the one the model card advertises. - Added a live read of that context window at the providers which report it, among them Mistral, Groq, OpenRouter, and self-hosted vLLM servers. You then get the window your own server was started with, not the one the model card advertises.
- Added a token count below the message field, so you always see how much of the conversation you have used. It counts everything that travels along: your messages, the files you attached, what your data sources contributed, and the tools you offered the AI. It can be an estimate when a provider does not give AI Studio everything it needs to count exactly. - Added a token count below the message field, so you always see how much of the conversation you have used. It counts everything that travels along: your messages, the files you attached, what your data sources contributed, and the tools you offered the AI. It can be an estimate when a provider does not give AI Studio everything it needs to count exactly.
- Added the exact token count to that number, wherever your provider reports one. What the conversation has cost so far is then no longer estimated but taken from the provider which charged for it, and only the message you are still writing is estimated — shown as a number of its own, so you can tell the two apart. Editing or regenerating an answer drops the reported number along with it, and so does switching to another model, because another model counts the same conversation differently. Providers which report nothing keep the estimate exactly as before, and so do Anthropic and OpenAI's Responses API for the time being. - Added the exact token count to that number, wherever your provider reports one. What the conversation has cost so far is then no longer estimated but taken from the provider which charged for it, and only the message you are still writing is estimated — shown as a number of its own, so you can tell the two apart. Editing or regenerating an answer drops the reported number along with it, and so does switching to another model, because another model counts the same conversation differently. Providers which report nothing keep the estimate exactly as before, and so do Anthropic and OpenAI's Responses API for the time being.
- Changed the token count to write its numbers out in full up to a million, so it reads 11,860 instead of 11.86k. Below a million the short form saves no space at all — both are six characters — while asking you to know what the prefix stands for. From a million on it is still shortened.
- Added a warning when your conversation holds more images than the model accepts, wherever we know that limit. The Visual Briefing assistant stops before anything is uploaded, instead of letting the provider refuse it afterward. - Added a warning when your conversation holds more images than the model accepts, wherever we know that limit. The Visual Briefing assistant stops before anything is uploaded, instead of letting the provider refuse it afterward.
- Added the context window and the image limits to the expert provider settings, next to the abilities you could already state there. Leave a field empty, and AI Studio keeps its own answer, which you see as the placeholder. IT departments can state the same numbers for the providers they roll out. - Added the context window and the image limits to the expert provider settings, next to the abilities you could already state there. Leave a field empty, and AI Studio keeps its own answer, which you see as the placeholder. IT departments can state the same numbers for the providers they roll out.
- Added model plugins, so IT departments can describe the models their organization runs itself. - Added model plugins, so IT departments can describe the models their organization runs itself.

View File

@ -22,14 +22,14 @@ public sealed class TokenAmountTests
[TestCase(0, "0")] [TestCase(0, "0")]
[TestCase(7, "7")] [TestCase(7, "7")]
[TestCase(847, "847")] [TestCase(847, "847")]
[TestCase(999, "999")] [TestCase(999, "999", Description = "The last number written out in full.")]
[TestCase(1_000, "1,000")] [TestCase(1_000, "1.00k")]
[TestCase(1_234, "1,234")] [TestCase(1_234, "1.23k")]
[TestCase(12_347, "12,347")] [TestCase(12_347, "12.35k")]
[TestCase(128_000, "128,000")] [TestCase(128_000, "128.00k")]
[TestCase(400_000, "400,000")] [TestCase(400_000, "400.00k")]
[TestCase(999_499, "999,499")] [TestCase(999_499, "999.50k")]
[TestCase(999_999, "999,999", Description = "The last number written out in full.")] [TestCase(999_999, "1.00M", Description = "Rounded before the unit is chosen, so it does not read as 1,000.00k.")]
[TestCase(1_000_000, "1.00M")] [TestCase(1_000_000, "1.00M")]
[TestCase(1_048_576, "1.05M")] [TestCase(1_048_576, "1.05M")]
[TestCase(1_050_000, "1.05M", Description = "Which is how OpenAI writes it themselves.")] [TestCase(1_050_000, "1.05M", Description = "Which is how OpenAI writes it themselves.")]
@ -40,16 +40,15 @@ public sealed class TokenAmountTests
} }
[TestCase(999, "999")] [TestCase(999, "999")]
[TestCase(1_234, "1.234")] [TestCase(1_234, "1,23k")]
[TestCase(400_000, "400.000")] [TestCase(400_000, "400,00k")]
[TestCase(1_048_576, "1,05M")] [TestCase(1_048_576, "1,05M")]
public void TheSeparatorsAreTheOnesTheUserKnows(int tokens, string wanted) public void TheSeparatorsAreTheOnesTheUserKnows(int tokens, string wanted)
{ {
// //
// A German reads 1.234 where an American reads 1,234, and 1,05M where an American reads // A German reads 1,23k where an American reads 1.23k. Writing either of them the other way
// 1.05M. Writing either of them the other way around reads as a number a thousand times // around reads as a number a thousand times off.
// off.
// //
Assert.That(TokenAmount.Format(tokens, GERMAN), Is.EqualTo(wanted)); Assert.That(TokenAmount.Format(tokens, GERMAN), Is.EqualTo(wanted));
} }
} }