mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-11-13 16:47:21 +00:00
Improved how the AI remembers its earlier tool calls (#1185)
Co-authored-by: Thorsten Sommer <SommerEngineering@users.noreply.github.com>
This commit is contained in:
1 parent
0885862150
commit
382db4b8d0
22 files changed
+1323
-24
No files matched your search
@@ -57,6 +57,45 @@ public sealed class ContentText : IContent
|
||||
|
||||
public List<ToolInvocationTrace> ToolInvocations { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// The web addresses the tools returned while this answer came about and which a later message
|
||||
/// still shows the model, as request keys, see WebAddresses.CreateRequestKey.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Stored with the answer, so that the model may read them again in a later message, also after
|
||||
/// a restart, while its free address choice is off, see ChatThread.IsWebAddressGivenToTheModel.
|
||||
/// A tool returned them, so the model neither made them up nor wrote anything of the chat into
|
||||
/// them; addresses it wrote into the call itself are left out, see ToolCallOutcome.ReturnedWebAddresses.<br/><br/>
|
||||
///
|
||||
/// Only those the model can still see later are kept: in the text of the answer, among its
|
||||
/// sources, or in the arguments of a call which ran, see EndToolRun. A page a tool read returns
|
||||
/// all of its links, often hundreds. Once the result is gone, the model has no way of knowing
|
||||
/// them, short of reading the page again, which returns them once more. Storing them all would
|
||||
/// only make the chat grow.<br/><br/>
|
||||
///
|
||||
/// Kept per answer rather than per chat: an answer which is generated anew takes its addresses
|
||||
/// along, and so does one which is deleted, once the chat is opened again, see
|
||||
/// ChatThread.RuntimeWebAddressesFromTools.
|
||||
/// </remarks>
|
||||
public HashSet<string> WebAddressesFromTools { get; set; } = new(StringComparer.Ordinal);
|
||||
|
||||
/// <summary>
|
||||
/// The web addresses the tools of the running request returned so far, as request keys.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Never stored: a chat is saved while its answer is still being written, and would hold every
|
||||
/// link of every page read until then. EndToolRun keeps those which a later message shows, in
|
||||
/// WebAddressesFromTools.
|
||||
/// </remarks>
|
||||
[JsonIgnore]
|
||||
public HashSet<string> PendingWebAddressesFromTools { get; private set; } = new(StringComparer.Ordinal);
|
||||
|
||||
/// <summary>
|
||||
/// Notes the web addresses a tool returned for this answer, see PendingWebAddressesFromTools.
|
||||
/// </summary>
|
||||
/// <param name="requestKeys">The addresses as request keys, see ToolCallOutcome.ReturnedWebAddresses.</param>
|
||||
public void KeepWebAddressesFromTools(IEnumerable<string> requestKeys) => this.PendingWebAddressesFromTools.UnionWith(requestKeys);
|
||||
|
||||
/// <summary>
|
||||
/// Optional structured data for a specialized visual presentation of this text.
|
||||
/// </summary>
|
||||
@@ -231,13 +270,15 @@ public sealed class ContentText : IContent
|
||||
/// Clears what the previous run of the tools left behind.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Both parts at once, because both belong to one request: the traces the user reads and the
|
||||
/// payload the counting needs. They were cleared separately for exactly as long as there was
|
||||
/// only one of them.
|
||||
/// All parts at once, because all belong to one request: the traces the user reads, the
|
||||
/// payload the counting needs, and the web addresses the tools returned. They were cleared
|
||||
/// separately for exactly as long as there was only one of them.
|
||||
/// </remarks>
|
||||
public void BeginToolRun()
|
||||
{
|
||||
this.ToolInvocations.Clear();
|
||||
this.WebAddressesFromTools = new(StringComparer.Ordinal);
|
||||
this.PendingWebAddressesFromTools = new(StringComparer.Ordinal);
|
||||
this.PendingToolConversation = [];
|
||||
}
|
||||
|
||||
@@ -246,9 +287,50 @@ public sealed class ContentText : IContent
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The traces stay -- they are what the user reads afterwards to see how the answer came
|
||||
/// about. What goes is the payload, which belonged to a request that is over.
|
||||
/// about. What goes is the payload, which belonged to a request that is over.<br/><br/>
|
||||
///
|
||||
/// Of the web addresses the tools returned, the answer keeps those which a later message still
|
||||
/// shows the model, see WebAddressesFromTools. The set is replaced as a whole, since the chat
|
||||
/// may be saved meanwhile. Whoever ends a request may call this once more, which keeps what the
|
||||
/// first call kept.
|
||||
/// </remarks>
|
||||
public void EndToolRun() => this.PendingToolConversation = [];
|
||||
public void EndToolRun()
|
||||
{
|
||||
this.PendingToolConversation = [];
|
||||
|
||||
var shownLater = this.FindRequestKeysShownLater();
|
||||
var kept = new HashSet<string>(StringComparer.Ordinal);
|
||||
kept.UnionWith(this.WebAddressesFromTools.Where(shownLater.Contains));
|
||||
kept.UnionWith(this.PendingWebAddressesFromTools.Where(shownLater.Contains));
|
||||
|
||||
this.WebAddressesFromTools = kept;
|
||||
this.PendingWebAddressesFromTools = new(StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The request keys of the web addresses a later message shows the model with this answer.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Those in its text, those of its sources, and those in the arguments of its calls which ran,
|
||||
/// as the note in front of the answer names them, see ToolCallHistoryNote. The values of
|
||||
/// sensitive arguments do not count, since the note hides them.
|
||||
/// </remarks>
|
||||
private HashSet<string> FindRequestKeysShownLater()
|
||||
{
|
||||
var texts = new List<string> { this.Text };
|
||||
texts.AddRange(this.Sources.Select(source => source.URL));
|
||||
foreach (var trace in this.ToolInvocations.Where(trace => trace is { Status: ToolInvocationTraceStatus.SUCCESS, WasExecuted: true }))
|
||||
texts.AddRange(trace.FindKeptTexts());
|
||||
|
||||
var requestKeys = new HashSet<string>(StringComparer.Ordinal);
|
||||
foreach (var address in texts.SelectMany(WebAddresses.Find))
|
||||
{
|
||||
if (WebAddresses.TryCreateRequestKey(address, out var requestKey))
|
||||
requestKeys.Add(requestKey);
|
||||
}
|
||||
|
||||
return requestKeys;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Keeps what the provider says the request behind this answer cost.
|
||||
@@ -550,6 +632,7 @@ public sealed class ContentText : IContent
|
||||
Sources = [..this.Sources],
|
||||
SourcePassages = [..this.SourcePassages],
|
||||
FileAttachments = [..this.FileAttachments],
|
||||
WebAddressesFromTools = new HashSet<string>(this.WebAddressesFromTools, StringComparer.Ordinal),
|
||||
Presentation = this.Presentation?.DeepClone(),
|
||||
ToolInvocations = [..this.ToolInvocations.Select(x => new ToolInvocationTrace
|
||||
{
|
||||
@@ -562,6 +645,7 @@ public sealed class ContentText : IContent
|
||||
WasExecuted = x.WasExecuted,
|
||||
StatusMessage = x.StatusMessage,
|
||||
Arguments = new Dictionary<string, string>(x.Arguments, StringComparer.Ordinal),
|
||||
ArgumentsJson = x.ArgumentsJson,
|
||||
Result = x.Result,
|
||||
JsonResult = x.JsonResult?.DeepClone(),
|
||||
})],
|
||||
|
||||
Reference in new issue
Block a user