Skip to content

Commit 7358b86

Browse files
committed
fix: chat topic cannot be generated (#35)
1 parent 2424062 commit 7358b86

File tree

6 files changed

+49
-25
lines changed

6 files changed

+49
-25
lines changed

src/Everywhere.Abstractions/Extensions/EnumerableExtension.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,10 +250,26 @@ public static IReadOnlyList<T> ToReadOnlyList<T>(this IEnumerable<T> source)
250250

251251
public static void RemoveRange<T>(this IList<T> list, int index, int count)
252252
{
253-
// 从后往前删除,避免频繁移动元素
253+
// Delete from back to front to avoid frequent element shifting
254254
for (var i = index + count - 1; i >= index; i--)
255255
{
256256
list.RemoveAt(i);
257257
}
258258
}
259+
260+
/// <summary>
261+
/// Throws if the cancellation token is cancelled during enumeration
262+
/// </summary>
263+
/// <param name="source"></param>
264+
/// <param name="cancellationToken"></param>
265+
/// <typeparam name="T"></typeparam>
266+
/// <returns></returns>
267+
public static IEnumerable<T> WithCancellation<T>(this IEnumerable<T> source, CancellationToken cancellationToken)
268+
{
269+
foreach (var item in source)
270+
{
271+
cancellationToken.ThrowIfCancellationRequested();
272+
yield return item;
273+
}
274+
}
259275
}

src/Everywhere.Abstractions/Utilities/EncodingDetector.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,9 @@ static EncodingDetector()
3232
/// </summary>
3333
/// <param name="stream">The stream to analyze. Must be readable and seekable.</param>
3434
/// <param name="bufferSize">The number of bytes to read for detection. Default is 4096.</param>
35+
/// <param name="cancellationToken"></param>
3536
/// <returns>The detected Encoding, or null if it's binary or undetermined.</returns>
36-
public static async Task<Encoding?> DetectEncodingAsync(Stream stream, int bufferSize = 4096)
37+
public static async Task<Encoding?> DetectEncodingAsync(Stream stream, int bufferSize = 4096, CancellationToken cancellationToken = default)
3738
{
3839
if (!stream.CanRead) throw new ArgumentException("Stream must be readable.", nameof(stream));
3940
if (!stream.CanSeek) throw new ArgumentException("Stream must be seekable.", nameof(stream));
@@ -45,7 +46,7 @@ static EncodingDetector()
4546

4647
try
4748
{
48-
var bytesRead = await stream.ReadAsync(buffer.AsMemory(0, bufferSize));
49+
var bytesRead = await stream.ReadAsync(buffer.AsMemory(0, bufferSize), cancellationToken);
4950

5051
if (bytesRead == 0)
5152
{

src/Everywhere/AI/OpenAIKernelMixin.cs

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -38,22 +38,13 @@ public OpenAIKernelMixin(CustomAssistant customAssistant) : base(customAssistant
3838
).AsChatCompletionService();
3939
}
4040

41-
public override PromptExecutionSettings? GetPromptExecutionSettings(FunctionChoiceBehavior? functionChoiceBehavior = null)
41+
public override PromptExecutionSettings GetPromptExecutionSettings(FunctionChoiceBehavior? functionChoiceBehavior = null)
4242
{
4343
double? temperature = _customAssistant.Temperature.IsCustomValueSet ? _customAssistant.Temperature.ActualValue : null;
4444
double? topP = _customAssistant.TopP.IsCustomValueSet ? _customAssistant.TopP.ActualValue : null;
4545
double? presencePenalty = _customAssistant.PresencePenalty.IsCustomValueSet ? _customAssistant.PresencePenalty.ActualValue : null;
4646
double? frequencyPenalty = _customAssistant.FrequencyPenalty.IsCustomValueSet ? _customAssistant.FrequencyPenalty.ActualValue : null;
4747

48-
if (temperature is null &&
49-
topP is null &&
50-
presencePenalty is null &&
51-
frequencyPenalty is null &&
52-
functionChoiceBehavior is null)
53-
{
54-
return null;
55-
}
56-
5748
return new OpenAIPromptExecutionSettings
5849
{
5950
Temperature = temperature,
@@ -103,12 +94,13 @@ private sealed class OptimizedOpenAIApiClient(IChatClient client, OpenAIKernelMi
10394
private static PropertyInfo? _choiceDeltaProperty;
10495
private static PropertyInfo? _deltaRawDataProperty;
10596

106-
public Task<ChatResponse> GetResponseAsync(
97+
public async Task<ChatResponse> GetResponseAsync(
10798
IEnumerable<ChatMessage> messages,
10899
ChatOptions? options = null,
109100
CancellationToken cancellationToken = default)
110101
{
111-
return client.GetResponseAsync(messages, options, cancellationToken);
102+
var res = await client.GetResponseAsync(messages, options, cancellationToken);
103+
return res;
112104
}
113105

114106
public async IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(

src/Everywhere/AI/Prompts.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,13 @@ 2. Guess my intentions
4646
""";
4747

4848
// from: https://github.com/lobehub/lobe-chat/blob/main/src/chains/summaryTitle.ts#L4
49-
public const string TitleGeneratorPrompt =
49+
public const string TitleGeneratorSystemPrompt = "You are a conversation assistant named Everywhere.";
50+
51+
public const string TitleGeneratorUserPrompt =
5052
"""
51-
You are a conversation assistant named Everywhere. You need to summarize the user's conversation into a topic of 10 words or fewer.
53+
Generate a concise and descriptive title for the following conversation.
54+
The title should accurately reflect the main topic or purpose of the conversation in a few words.
55+
Avoid using generic titles like "Chat" or "Conversation".
5256
5357
User:
5458
```markdown

src/Everywhere/Chat/ChatService.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -980,8 +980,11 @@ private async Task GenerateTitleAsync(
980980
{
981981
new ChatMessageContent(
982982
AuthorRole.System,
983+
Prompts.TitleGeneratorSystemPrompt),
984+
new ChatMessageContent(
985+
AuthorRole.User,
983986
Prompts.RenderPrompt(
984-
Prompts.TitleGeneratorPrompt,
987+
Prompts.TitleGeneratorUserPrompt,
985988
new Dictionary<string, Func<string>>
986989
{
987990
{ "UserMessage", () => userMessage.SafeSubstring(0, 2048) },

src/Everywhere/Chat/Plugins/Impl/FileSystemPlugin.cs

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -65,15 +65,18 @@ public FileSystemPlugin(ILogger<FileSystemPlugin> logger) : base("file_system")
6565

6666
[KernelFunction("search_files")]
6767
[Description(
68-
"Search for files and directories in a specified path matching the given search pattern. This tool may slow; avoid using it to enumerate large numbers of files.")]
68+
"Search for files and directories in a specified path matching the given search pattern. " +
69+
"This tool may slow; avoid using it to enumerate large numbers of files. " +
70+
"DO NOT specify the value of `orderBy` when dealing with a large number of files.")]
6971
[DynamicResourceKey(LocaleKey.NativeChatPlugin_FileSystem_SearchFiles_Header)]
7072
[FriendlyFunctionCallContentRenderer(typeof(FileRenderer))]
7173
private string SearchFiles(
7274
string path,
7375
[Description("Regex search pattern to match file and directory names.")] string filePattern = ".*",
7476
int skip = 0,
7577
[Description("Maximum number of results to return. Max is 1000.")] int maxCount = 100,
76-
FilesOrderBy orderBy = FilesOrderBy.Default)
78+
FilesOrderBy orderBy = FilesOrderBy.Default,
79+
CancellationToken cancellationToken = default)
7780
{
7881
if (skip < 0) skip = 0;
7982
if (maxCount < 0) maxCount = 0;
@@ -90,6 +93,7 @@ private string SearchFiles(
9093
var regex = new Regex(filePattern, RegexOptions.IgnoreCase | RegexOptions.Compiled, RegexTimeout);
9194
ExpandFullPath(ref path);
9295
var query = new RegexFileSystemInfoEnumerable(EnsureDirectoryInfo(path).FullName, regex, true)
96+
.WithCancellation(cancellationToken)
9397
.OfType<FileSystemInfo>()
9498
.Select(i => new FileRecord(
9599
i.Name,
@@ -168,7 +172,9 @@ private async Task<string> SearchFileContentAsync(
168172
var filesToSearch = fileSystemInfo switch
169173
{
170174
FileInfo fileInfo when fileRegex.IsMatch(fileInfo.Name) => [fileInfo],
171-
DirectoryInfo directoryInfo => new RegexFileSystemInfoEnumerable(directoryInfo.FullName, fileRegex, true).OfType<FileInfo>(),
175+
DirectoryInfo directoryInfo => new RegexFileSystemInfoEnumerable(directoryInfo.FullName, fileRegex, true)
176+
.WithCancellation(cancellationToken)
177+
.OfType<FileInfo>(),
172178
_ => []
173179
};
174180

@@ -180,7 +186,7 @@ FileInfo fileInfo when fileRegex.IsMatch(fileInfo.Name) => [fileInfo],
180186
if (file.Length > maxSearchFileSize) continue;
181187

182188
await using var stream = new FileStream(file.FullName, FileMode.Open, FileAccess.Read, FileShare.Read);
183-
if (await EncodingDetector.DetectEncodingAsync(stream) is null)
189+
if (await EncodingDetector.DetectEncodingAsync(stream, cancellationToken: cancellationToken) is null)
184190
{
185191
continue;
186192
}
@@ -248,7 +254,7 @@ private async Task<string> ReadFileAsync(
248254
}
249255

250256
var stringBuilder = new StringBuilder();
251-
if (await EncodingDetector.DetectEncodingAsync(stream) is not { } encoding)
257+
if (await EncodingDetector.DetectEncodingAsync(stream, cancellationToken: cancellationToken) is not { } encoding)
252258
{
253259
stream.Seek(startBytes, SeekOrigin.Begin);
254260

@@ -386,7 +392,9 @@ private async Task<string> DeleteFilesAsync(
386392
case DirectoryInfo directoryInfo:
387393
{
388394
var regex = new Regex(filePattern, RegexOptions.IgnoreCase | RegexOptions.Compiled, RegexTimeout);
389-
infosToDelete = new RegexFileSystemInfoEnumerable(directoryInfo.FullName, regex, true).OfType<FileSystemInfo>();
395+
infosToDelete = new RegexFileSystemInfoEnumerable(directoryInfo.FullName, regex, true)
396+
.WithCancellation(cancellationToken)
397+
.OfType<FileSystemInfo>();
390398
break;
391399
}
392400
default:
@@ -511,7 +519,7 @@ private async Task<string> ReplaceFileContentAsync(
511519
}
512520

513521
await using var stream = new FileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.None);
514-
if (await EncodingDetector.DetectEncodingAsync(stream) is not { } encoding)
522+
if (await EncodingDetector.DetectEncodingAsync(stream, cancellationToken: cancellationToken) is not { } encoding)
515523
{
516524
throw new InvalidOperationException("Cannot replace content in a binary file.");
517525
}

0 commit comments

Comments
 (0)