mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-11-04 23:27:22 +00:00
Fixed AI Studio discarding answers of the AI written in an unexpected JSON form (#1109)
This commit is contained in:
1 parent
d26139936a
commit
c7be04b727
11 files changed
+443
-256
No files matched your search
@@ -266,7 +266,7 @@ public sealed class AgentDataSourceSelection (ILogger<AgentDataSourceSelection>
|
||||
// We know how bad LLM may be in generating JSON without surrounding text.
|
||||
// Thus, we expect the worst and try to extract the JSON list from the text:
|
||||
//
|
||||
var json = ExtractJson(selectedDataSourcesJson);
|
||||
var json = ModelAnswerJson.ExtractObjectList(selectedDataSourcesJson);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -293,64 +293,4 @@ public sealed class AgentDataSourceSelection (ILogger<AgentDataSourceSelection>
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts the JSON list from the given text. The text may contain additional
|
||||
/// information around the JSON list. The method tries to extract the JSON list
|
||||
/// from the text.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Algorithm: The method searches for the first line that contains only a '[' character.
|
||||
/// Then, it searches for the first line that contains only a ']' character. The method
|
||||
/// returns the text between these two lines (including the brackets). When the method
|
||||
/// cannot find the JSON list, it returns an empty string.
|
||||
/// </remarks>
|
||||
/// <param name="text">The text that may contain the JSON list.</param>
|
||||
/// <returns>The extracted JSON list.</returns>
|
||||
private static ReadOnlySpan<char> ExtractJson(ReadOnlySpan<char> text)
|
||||
{
|
||||
var startIndex = -1;
|
||||
var endIndex = -1;
|
||||
var foundStart = false;
|
||||
var foundEnd = false;
|
||||
var lineStart = 0;
|
||||
|
||||
for (var i = 0; i <= text.Length; i++)
|
||||
{
|
||||
// Handle the end of the line or the end of the text:
|
||||
if (i == text.Length || text[i] == '\n')
|
||||
{
|
||||
if (IsCharacterAloneInLine(text, lineStart, i, '[') && !foundStart)
|
||||
{
|
||||
startIndex = lineStart;
|
||||
foundStart = true;
|
||||
}
|
||||
else if (IsCharacterAloneInLine(text, lineStart, i, ']') && foundStart && !foundEnd)
|
||||
{
|
||||
endIndex = i;
|
||||
foundEnd = true;
|
||||
break;
|
||||
}
|
||||
|
||||
lineStart = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (foundStart && foundEnd)
|
||||
{
|
||||
// Adjust endIndex for slicing, ensuring it's within bounds:
|
||||
return text.Slice(startIndex, Math.Min(text.Length, endIndex + 1) - startIndex);
|
||||
}
|
||||
|
||||
return ReadOnlySpan<char>.Empty;
|
||||
}
|
||||
|
||||
private static bool IsCharacterAloneInLine(ReadOnlySpan<char> text, int lineStart, int lineEnd, char character)
|
||||
{
|
||||
for (var i = lineStart; i < lineEnd; i++)
|
||||
if (!char.IsWhiteSpace(text[i]) && text[i] != character)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -308,9 +308,9 @@ public sealed class AgentRetrievalContextValidation (ILogger<AgentRetrievalConte
|
||||
|
||||
//
|
||||
// We know how bad LLM may be in generating JSON without surrounding text.
|
||||
// Thus, we expect the worst and try to extract the JSON list from the text:
|
||||
// Thus, we expect the worst and try to extract the JSON object from the text:
|
||||
//
|
||||
var json = ExtractJson(validationJson);
|
||||
var json = ModelAnswerJson.ExtractObject(validationJson);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -343,68 +343,4 @@ public sealed class AgentRetrievalContextValidation (ILogger<AgentRetrievalConte
|
||||
semaphore?.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private static ReadOnlySpan<char> ExtractJson(ReadOnlySpan<char> input)
|
||||
{
|
||||
//
|
||||
// 1. Expect the best case ;-)
|
||||
//
|
||||
if (CheckJsonObjectStart(input))
|
||||
return ExtractJsonPart(input);
|
||||
|
||||
//
|
||||
// 2. Okay, we have some garbage before the
|
||||
// JSON object. We expected that...
|
||||
//
|
||||
for (var index = 0; index < input.Length; index++)
|
||||
{
|
||||
if (input[index] is '{' && CheckJsonObjectStart(input[index..]))
|
||||
return ExtractJsonPart(input[index..]);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
private static bool CheckJsonObjectStart(ReadOnlySpan<char> area)
|
||||
{
|
||||
char[] expectedSymbols = ['{', '"', 'd'];
|
||||
var symbolIndex = 0;
|
||||
|
||||
foreach (var c in area)
|
||||
{
|
||||
if (symbolIndex >= expectedSymbols.Length)
|
||||
return true;
|
||||
|
||||
if (char.IsWhiteSpace(c))
|
||||
continue;
|
||||
|
||||
if (c == expectedSymbols[symbolIndex++])
|
||||
continue;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static ReadOnlySpan<char> ExtractJsonPart(ReadOnlySpan<char> input)
|
||||
{
|
||||
var insideString = false;
|
||||
for (var index = 0; index < input.Length; index++)
|
||||
{
|
||||
if (input[index] is '"')
|
||||
{
|
||||
insideString = !insideString;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (insideString)
|
||||
continue;
|
||||
|
||||
if (input[index] is '}')
|
||||
return input[..++index];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -50,7 +50,7 @@ public sealed class AssistantAuditAgent(ILogger<AssistantAuditAgent> logger, ILo
|
||||
"severity": "critical | medium | low",
|
||||
"category": "brief category",
|
||||
"location": "system prompt | BuildPrompt | component name | plugin.lua",
|
||||
"description": "what is risky",
|
||||
"description": "what is risky"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -259,7 +259,7 @@ public sealed class AssistantAuditAgent(ILogger<AssistantAuditAgent> logger, ILo
|
||||
};
|
||||
}
|
||||
|
||||
var json = ExtractJson(content.Text);
|
||||
var json = ModelAnswerJson.ExtractObject(content.Text);
|
||||
try
|
||||
{
|
||||
var result = JsonSerializer.Deserialize<AssistantAuditResult>(json, JSON_SERIALIZER_OPTIONS);
|
||||
@@ -302,44 +302,6 @@ public sealed class AssistantAuditAgent(ILogger<AssistantAuditAgent> logger, ILo
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts the first complete JSON object from a model response that may contain surrounding text.
|
||||
/// </summary>
|
||||
/// <param name="input">The raw model response.</param>
|
||||
/// <returns>The first complete JSON object, or an empty span when none can be found.</returns>
|
||||
private static ReadOnlySpan<char> ExtractJson(ReadOnlySpan<char> input)
|
||||
{
|
||||
var start = input.IndexOf('{');
|
||||
if (start < 0)
|
||||
return [];
|
||||
|
||||
var depth = 0;
|
||||
var insideString = false;
|
||||
for (var index = start; index < input.Length; index++)
|
||||
{
|
||||
if (input[index] == '"' && (index == 0 || input[index - 1] != '\\'))
|
||||
insideString = !insideString;
|
||||
|
||||
if (insideString)
|
||||
continue;
|
||||
|
||||
switch (input[index])
|
||||
{
|
||||
case '{':
|
||||
depth++;
|
||||
break;
|
||||
case '}':
|
||||
depth--;
|
||||
break;
|
||||
}
|
||||
|
||||
if (depth == 0)
|
||||
return input[start..(index + 1)];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Names the tools a plugin requests, so the auditor can weigh them against its stated purpose.
|
||||
/// </summary>
|
||||
|
||||
Reference in new issue
Block a user