mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-09-27 00:53:37 +00:00
Cover the tool conversation in the token count tests
This commit is contained in:
parent
21a6244720
commit
275f4acf3d
@ -11,7 +11,7 @@
|
||||
- Added support for OpenAI's GPT-6 Astra.
|
||||
- Added the context window to what AI Studio knows about a model, wherever its metadata states one.
|
||||
- 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 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 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 model plugins, so IT departments can describe the models their organization runs itself.
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
using System.Text.Json;
|
||||
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Tools.ToolCallingSystem;
|
||||
|
||||
namespace AIStudio.Tests.Chat;
|
||||
|
||||
@ -11,6 +14,10 @@ namespace AIStudio.Tests.Chat;
|
||||
/// travels with it. So what is collected here has to be what the message builder actually sends --
|
||||
/// no more, because a number which counts something that stays behind is wrong in the direction
|
||||
/// that makes a person stop writing.
|
||||
///
|
||||
/// Beyond the messages, a request carries the schema of every tool the model may call, and, while
|
||||
/// it runs, everything those tools have returned so far. Both are invisible on the screen, and the
|
||||
/// second one is where a window fills up fastest.
|
||||
/// </remarks>
|
||||
[TestFixture]
|
||||
public sealed class ConversationPartsTests
|
||||
@ -226,6 +233,118 @@ public sealed class ConversationPartsTests
|
||||
Assert.That(parts.Images, Is.Zero);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ABlockWithoutTextCountsWhileItsToolsAreStillRunning()
|
||||
{
|
||||
//
|
||||
// While a model calls tools there is no text yet: the answer arrives in one piece at the
|
||||
// end, and everything in between travels with every further round of the same request. The
|
||||
// block which looks emptiest is therefore the one whose request is growing the fastest --
|
||||
// and the one which used to be skipped for having nothing to say.
|
||||
//
|
||||
var running = Block(string.Empty);
|
||||
((ContentText)running.Content!).PendingToolConversation = ["What the web search found.", "What the page said."];
|
||||
|
||||
var parts = ConversationParts.Of(new() { Blocks = [running] }, string.Empty, string.Empty, null, imagesAreSent: true, toolDefinitions: null);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(parts.Texts, Is.Empty);
|
||||
Assert.That(parts.GrowingTexts, Is.EqualTo(new[] { "What the web search found.", "What the page said." }));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TwoToolResultsWhichReadTheSameCostTwice()
|
||||
{
|
||||
//
|
||||
// The request carries both, so both are paid for. Folding them into one would promise a
|
||||
// smaller request than the one which is sent -- and a model reading the same page twice is
|
||||
// not a rare accident but a thing that happens on any busy search.
|
||||
//
|
||||
var running = Block(string.Empty);
|
||||
((ContentText)running.Content!).PendingToolConversation = ["The same page.", "The same page."];
|
||||
|
||||
var parts = ConversationParts.Of(new() { Blocks = [running] }, string.Empty, string.Empty, null, imagesAreSent: true, toolDefinitions: null);
|
||||
|
||||
Assert.That(parts.GrowingTexts, Is.EqualTo(new[] { "The same page.", "The same page." }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OnceTheAnswerStandsTheToolConversationIsGone()
|
||||
{
|
||||
//
|
||||
// It travels with the rounds of one request and with nothing afterwards: the next request is
|
||||
// built from the messages alone. A number which kept counting it would report a window
|
||||
// fuller than it is, and would never fall back.
|
||||
//
|
||||
var answered = Block("Here is what I found.");
|
||||
var content = (ContentText)answered.Content!;
|
||||
content.PendingToolConversation = ["What the web search found."];
|
||||
content.EndToolRun();
|
||||
|
||||
var parts = ConversationParts.Of(new() { Blocks = [answered] }, string.Empty, string.Empty, null, imagesAreSent: true, toolDefinitions: null);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(parts.Texts, Is.EqualTo(new[] { "Here is what I found." }));
|
||||
Assert.That(parts.GrowingTexts, Is.Empty);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TheToolSchemasCountAndTheyCountWithWhatStands()
|
||||
{
|
||||
//
|
||||
// Every request carries the schema of every offered tool, whether or not the model calls a
|
||||
// single one of them. They belong with the lasting texts: a schema is the same string all
|
||||
// session long, so its count is worth remembering.
|
||||
//
|
||||
var parts = ConversationParts.Of(null, string.Empty, string.Empty, null, imagesAreSent: true, toolDefinitions:
|
||||
[
|
||||
Tool("web_search", "Searches the web.", """{"type":"object"}"""),
|
||||
Tool("read_web_page", "Reads one page.", """{"type":"string"}"""),
|
||||
]);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(parts.Texts, Is.EqualTo(new[]
|
||||
{
|
||||
"""web_searchSearches the web.{"type":"object"}""",
|
||||
"""read_web_pageReads one page.{"type":"string"}""",
|
||||
}));
|
||||
|
||||
Assert.That(parts.GrowingTexts, Is.Empty);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AToolWhichStatesNoArgumentsCountsLikeAnyOther()
|
||||
{
|
||||
//
|
||||
// A definition which never names a parameter schema leaves an empty JSON element behind,
|
||||
// and asking such an element for its text throws. A tool arriving from a plugin may well
|
||||
// say nothing about its arguments, and the number under the input field is not the place
|
||||
// to find that out.
|
||||
//
|
||||
var parts = ConversationParts.Of(null, string.Empty, string.Empty, null, imagesAreSent: true, toolDefinitions:
|
||||
[
|
||||
new() { Function = new() { Name = "ping", DescriptionForLLM = "Says hello." } },
|
||||
]);
|
||||
|
||||
Assert.That(parts.Texts, Is.EqualTo(new[] { "pingSays hello." }));
|
||||
}
|
||||
|
||||
private static ToolDefinition Tool(string name, string description, string parameterSchema) => new()
|
||||
{
|
||||
Function = new()
|
||||
{
|
||||
Name = name,
|
||||
DescriptionForLLM = description,
|
||||
Parameters = JsonDocument.Parse(parameterSchema).RootElement.Clone(),
|
||||
},
|
||||
};
|
||||
|
||||
private static ContentBlock Block(string text) => new()
|
||||
{
|
||||
ContentType = ContentType.TEXT,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user