Skip to content

Commit 78e0762

Browse files
authored
KnowPro.NET: Fixed SqliteProvider ordering bug (#1771)
SqliteProvider Bug: - For bulk lookups (list of ordinals etc.) was not returning values in the order of asked for - The "IN" clause does not guarantee order - Collection semantics require values to be returned in the precise order ids were provided
1 parent cf38f8b commit 78e0762

File tree

10 files changed

+169
-88
lines changed

10 files changed

+169
-88
lines changed

dotnet/typeagent/examples/knowProConsole/PodcastCommands.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ private Command PodcastLoadDef()
5353
return cmd;
5454
}
5555

56-
private async Task PodcastLoadAsync(ParseResult args)
56+
private Task PodcastLoadAsync(ParseResult args)
5757
{
5858
NamedArgs namedArgs = new(args);
5959
string name = namedArgs.GetRequired("name");
@@ -71,6 +71,7 @@ private async Task PodcastLoadAsync(ParseResult args)
7171
KnowProWriter.WriteLine(ConsoleColor.Cyan, $"Loaded {name}");
7272

7373
//await podcast.BuildSecondaryIndexesAsync();
74+
return Task.CompletedTask;
7475
}
7576

7677
private Command PodcastImportIndexDef()
@@ -165,6 +166,9 @@ private async Task ImportExistingIndexAsync(NamedArgs namedArgs, string filePath
165166
count = await podcast.ImportPropertyIndexAsync(data.SemanticRefs, cancellationToken);
166167
KnowProWriter.WriteLine($"{count} properties imported");
167168

169+
await podcast.UpdateMessageIndexAsync(false, cancellationToken);
170+
await podcast.BuildSecondaryIndexesAsync(cancellationToken);
171+
168172
SetCurrent(podcast);
169173
}
170174
catch

dotnet/typeagent/examples/knowProConsole/TestCommands.cs

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -387,14 +387,15 @@ private Command AnswerDef()
387387
{
388388
Command cmd = new("kpTestAnswer")
389389
{
390-
Args.Arg<string>("query")
390+
Args.Arg<string>("query"),
391+
Options.Arg<bool>("debug", false)
391392
};
392393
cmd.TreatUnmatchedTokensAsErrors = false;
393394
cmd.SetAction(this.AnswerAsync);
394395
return cmd;
395396
}
396397

397-
private async Task AnswerAsync(ParseResult args, CancellationToken cancellationToken)
398+
private async Task AnswerAsync(ParseResult args)
398399
{
399400
IConversation conversation = EnsureConversation();
400401

@@ -404,6 +405,7 @@ private async Task AnswerAsync(ParseResult args, CancellationToken cancellationT
404405
{
405406
return;
406407
}
408+
/*
407409
IList<AnswerResponse> answers = await conversation.AnswerQuestionAsync(
408410
query,
409411
null,
@@ -413,23 +415,19 @@ private async Task AnswerAsync(ParseResult args, CancellationToken cancellationT
413415
cancellationToken
414416
).ConfigureAwait(false);
415417
KnowProWriter.WriteJson(answers);
416-
/*
417-
AnswerContext context = new AnswerContext();
418-
419-
IList<ConcreteEntity> entities = await conversation.SemanticRefs.GetAllEntitiesAsync(cancellationToken);
420-
entities = [.. entities.ToDistinct()];
421-
422-
IList<Topic> topics = await conversation.SemanticRefs.GetAllTopicsAsync(cancellationToken);
423-
topics = [.. topics.ToDistinct()];
424-
425-
context.Entities = entities.Map((e) => new RelevantEntity { Entity = e });
426-
context.Topics = topics.Map((t) => new RelevantTopic { Topic = t });
427-
428-
List<IMessage> messages = await conversation.Messages.GetAllAsync(cancellationToken);
429-
context.Messages = messages.Map((m) => new RelevantMessage(m));
430-
string prompt = context.ToPromptString();
431-
ConsoleWriter.WriteLine(prompt);
432418
*/
419+
var searchResults = await conversation.SearchAsync(query);
420+
foreach (var searchResult in searchResults)
421+
{
422+
AnswerContext context = await AnswerContext.FromSearchResultAsync(conversation, searchResult);
423+
if (namedArgs.Get<bool>("debug"))
424+
{
425+
KnowProWriter.WriteLine(ConsoleColor.Cyan, context.ToJson());
426+
}
427+
428+
var answer = await conversation.AnswerQuestionAsync(query, context);
429+
KnowProWriter.WriteJson(answer);
430+
}
433431
}
434432

435433
private IConversation EnsureConversation()

dotnet/typeagent/src/common/DictionaryExtensions.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,18 @@ public static TValue GetValueOrDefault<TKey, TValue>(this IDictionary<TKey, TVal
1414
{
1515
return dictionary.TryGetValue(key, out TValue? value) ? value : defaultValue;
1616
}
17+
18+
// Gets values in the same order as the keys
19+
public static IEnumerable<TValue> GetValues<TKey, TValue>(
20+
this IDictionary<TKey, TValue> dictionary,
21+
IEnumerable<TKey> keysInOrder
22+
)
23+
{
24+
ArgumentVerify.ThrowIfNull(keysInOrder, nameof(keysInOrder));
25+
26+
foreach (TKey key in keysInOrder)
27+
{
28+
yield return dictionary[key];
29+
}
30+
}
1731
}

dotnet/typeagent/src/knowpro/Answer/AnswerContextBuilder.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -253,11 +253,11 @@ CancellationToken cancellationToken
253253
return rangeOrdinals.Count != meta.Count || rangeOrdinals.Count != timestamps.Count
254254
? throw new InvalidOperationException("ordinal list to meta list mismatch")
255255
: new EnclosingMetadata
256-
{
257-
Ordinals = rangeOrdinals,
258-
Meta = meta,
259-
Timestamps = timestamps
260-
};
256+
{
257+
Ordinals = rangeOrdinals,
258+
Meta = meta,
259+
Timestamps = timestamps
260+
};
261261
}
262262

263263
private TimestampRange? GetTimeRange(string? min, string? max)
@@ -271,7 +271,7 @@ private struct EnclosingMetadata
271271
{
272272
public List<int> Ordinals { get; set; }
273273

274-
public IList<IMessageMetadata> Meta { get; set;}
274+
public IList<IMessageMetadata> Meta { get; set; }
275275

276276
public IList<string> Timestamps { get; set; }
277277
}

dotnet/typeagent/src/knowpro/Answer/AnswerContextSchema.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,14 @@ public partial class AnswerContext
4949
{
5050
// Relevant entities
5151
// Use the 'name' and 'type' properties of entities to PRECISELY identify those that answer the user question.
52+
[JsonPropertyName("entities")]
5253
public IList<RelevantEntity>? Entities { get; set; }
5354

5455
// Relevant topics
56+
[JsonPropertyName("topics")]
5557
public IList<RelevantTopic> Topics { get; set; }
5658

5759
// Relevant messages
60+
[JsonPropertyName("messages")]
5861
public IList<RelevantMessage>? Messages { get; set; }
5962
};

dotnet/typeagent/src/knowpro/Answer/AnswerContextSchemaImpl.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ public RelevantMessage(IMessage message)
3030

3131
public partial class AnswerContext
3232
{
33+
public string ToJson()
34+
{
35+
return Serializer.ToJsonIndented(this);
36+
}
37+
3338
public string ToPromptString()
3439
{
3540
var json = new StringBuilder();
@@ -53,6 +58,23 @@ public string ToPromptString()
5358
return json.ToString();
5459
}
5560

61+
public static ValueTask<AnswerContext> FromSearchResultAsync(
62+
IConversation conversation,
63+
ConversationSearchResult searchResult,
64+
AnswerContextOptions? contextOptions = null,
65+
CancellationToken cancellationToken = default
66+
)
67+
{
68+
AnswerContextBuilder contextBuilder = new AnswerContextBuilder(conversation);
69+
return contextBuilder.FromSearchResultAsync(
70+
searchResult,
71+
contextOptions,
72+
cancellationToken
73+
);
74+
75+
76+
}
77+
5678
private int AddPrompt(StringBuilder text, int propertyCount, string name, object value)
5779
{
5880
if (propertyCount > 0)

dotnet/typeagent/src/knowpro/ConversationAnswer.cs

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,13 @@ namespace TypeAgent.KnowPro;
99
public static class ConversationAnswer
1010
{
1111
public static async ValueTask<AnswerResponse> AnswerQuestionAsync(
12-
this IConversation conversation,
13-
string question,
14-
ConversationSearchResult searchResult,
15-
AnswerContextOptions? contextOptions = null,
16-
CancellationToken cancellationToken = default
12+
this IConversation conversation,
13+
string question,
14+
AnswerContext context,
15+
CancellationToken cancellationToken = default
1716
)
1817
{
19-
ArgumentVerify.ThrowIfNull(searchResult, nameof(searchResult));
20-
21-
AnswerContextBuilder contextBuilder = new AnswerContextBuilder(conversation);
22-
23-
AnswerContext context = await contextBuilder.FromSearchResultAsync(
24-
searchResult,
25-
contextOptions,
26-
cancellationToken
27-
).ConfigureAwait(false);
18+
ArgumentVerify.ThrowIfNull(context, nameof(context));
2819

2920
IAnswerGenerator generator = conversation.Settings.AnswerGenerator;
3021

@@ -47,6 +38,30 @@ public static async ValueTask<AnswerResponse> AnswerQuestionAsync(
4738
throw new NotImplementedException("Answer chunking");
4839
}
4940

41+
42+
public static async ValueTask<AnswerResponse> AnswerQuestionAsync(
43+
this IConversation conversation,
44+
string question,
45+
ConversationSearchResult searchResult,
46+
AnswerContextOptions? contextOptions = null,
47+
CancellationToken cancellationToken = default
48+
)
49+
{
50+
ArgumentVerify.ThrowIfNull(searchResult, nameof(searchResult));
51+
AnswerContext context = await AnswerContext.FromSearchResultAsync(
52+
conversation,
53+
searchResult,
54+
contextOptions,
55+
cancellationToken
56+
).ConfigureAwait(false);
57+
58+
return await conversation.AnswerQuestionAsync(
59+
question,
60+
context,
61+
cancellationToken
62+
).ConfigureAwait(false);
63+
}
64+
5065
public static async ValueTask<IList<AnswerResponse>> AnswerQuestionAsync(
5166
this IConversation conversation,
5267
string question,
@@ -87,5 +102,4 @@ public static async ValueTask<IList<AnswerResponse>> AnswerQuestionAsync(
87102

88103
return answerResponses;
89104
}
90-
91105
}

dotnet/typeagent/src/knowproStorage/Sqlite/SqliteDatabase.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,29 @@ public IEnumerable<T> Enumerate<T>(string sql, Func<SqliteDataReader, T> rowDese
118118
return Enumerate<T>(sql, null, rowDeserializer);
119119
}
120120

121+
public Dictionary<TKey, TValue> GetKeyValues<TKey, TValue>(
122+
string sql,
123+
Action<SqliteCommand> addParams,
124+
Func<SqliteDataReader, KeyValuePair<TKey, TValue>> rowDeserializer,
125+
Dictionary<TKey, TValue>? keyValues = null
126+
)
127+
{
128+
using var cmd = _connection.CreateCommand();
129+
cmd.CommandText = sql;
130+
if (addParams is not null)
131+
{
132+
addParams(cmd);
133+
}
134+
using var reader = cmd.ExecuteReader();
135+
keyValues ??= [];
136+
while (reader.Read())
137+
{
138+
var kv = rowDeserializer(reader);
139+
keyValues.Add(kv.Key, kv.Value);
140+
}
141+
return keyValues;
142+
}
143+
121144
public IEnumerable<KeyValuePair<int, NormalizedEmbeddingB>> EnumerateEmbeddings(string sql)
122145
{
123146
return Enumerate<KeyValuePair<int, NormalizedEmbeddingB>>(

0 commit comments

Comments
 (0)