Skip to content

Commit

Permalink
Code cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
ronnygunawan committed Dec 3, 2023
1 parent 9432ab3 commit 1280ce4
Show file tree
Hide file tree
Showing 18 changed files with 188 additions and 105 deletions.
5 changes: 4 additions & 1 deletion BotNet.Services/BotCommands/OpenAI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Threading.Tasks;
using BotNet.Services.MarkdownV2;
using BotNet.Services.OpenAI;
using BotNet.Services.OpenAI.Skills;
using BotNet.Services.RateLimit;
using Microsoft.Extensions.DependencyInjection;
using RG.Ninja;
Expand Down Expand Up @@ -760,14 +761,15 @@ await botClient.SendTextMessageAsync(
}
}

public static async Task StreamChatWithFriendlyBotAsync(ITelegramBotClient botClient, IServiceProvider serviceProvider, Message message, string callSign, CancellationToken cancellationToken) {
public static async Task StreamChatWithFriendlyBotAsync(ITelegramBotClient botClient, IServiceProvider serviceProvider, Message message, CancellationToken cancellationToken) {
try {
(message.Chat.Type == ChatType.Private
? CHAT_PRIVATE_RATE_LIMITER
: CHAT_GROUP_RATE_LIMITER
).ValidateActionRate(message.Chat.Id, message.From!.Id);
await serviceProvider.GetRequiredService<FriendlyBot>().StreamChatAsync(
message: message.Text!,
from: message.From!,
chatId: message.Chat.Id,
replyToMessageId: message.MessageId
);
Expand Down Expand Up @@ -809,6 +811,7 @@ public static async Task StreamChatWithFriendlyBotAsync(ITelegramBotClient botCl
await serviceProvider.GetRequiredService<FriendlyBot>().StreamChatAsync(
message: message.Text!,
thread: thread,
from: message.From!,
chatId: message.Chat.Id,
replyToMessageId: message.MessageId
);
Expand Down
9 changes: 3 additions & 6 deletions BotNet.Services/GoogleMap/GeoCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,11 @@ namespace BotNet.Services.GoogleMap {
/// This class intended to get geocoding from address.
/// </summary>
public class GeoCode {


private readonly string? _apiKey;
private string _uriTemplate = "https://maps.googleapis.com/maps/api/geocode/json";
private const string URI_TEMPLATE = "https://maps.googleapis.com/maps/api/geocode/json";
private readonly HttpClientHandler _httpClientHandler;
private readonly HttpClient _httpClient;


public GeoCode(IOptions<GoogleMapOptions> options) {
_apiKey = options.Value.ApiKey;
_httpClientHandler = new();
Expand All @@ -46,14 +43,14 @@ public GeoCode(IOptions<GoogleMapOptions> options) {
throw new HttpRequestException("Api key is needed");
}

Uri uri = new(_uriTemplate + $"?address={place}&key={_apiKey}");
Uri uri = new(URI_TEMPLATE + $"?address={place}&key={_apiKey}");
HttpResponseMessage response = await _httpClient.GetAsync(uri.AbsoluteUri);

if (response is not { StatusCode: HttpStatusCode.OK, Content.Headers.ContentType.MediaType: string contentType }) {
throw new HttpRequestException("Unable to find location.");
}

if (response.Content is not object && contentType is not "application/json") {
if (response.Content is null && contentType is not "application/json") {
throw new HttpRequestException("Failed to parse result.");
}

Expand Down
15 changes: 6 additions & 9 deletions BotNet.Services/GoogleMap/StaticMap.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
using System;
using BotNet.Services.GoogleMap.Models;
using Microsoft.Extensions.Options;

namespace BotNet.Services.GoogleMap {

/// <summary>
/// Get static map image from google map api
/// </summary>
public class StaticMap {
private readonly string? _apiKey;
public class StaticMap(
IOptions<GoogleMapOptions> options
) {
private readonly string? _apiKey = options.Value.ApiKey;
protected string mapPosition = "center";
protected int zoom = 13;
protected string size = "600x300";
protected string marker = "color:red";
private string _uriTemplate = "https://maps.googleapis.com/maps/api/staticmap";

public StaticMap(IOptions<GoogleMapOptions> options) {
_apiKey = options.Value.ApiKey;
}
private const string URI_TEMPLATE = "https://maps.googleapis.com/maps/api/staticmap";

/// <summary>
/// Get static map image from google map api
Expand All @@ -33,7 +30,7 @@ public string SearchPlace(string? place) {
return "Api key is needed";
}

Uri uri = new(_uriTemplate + $"?{mapPosition}={place}&zoom={zoom}&size={size}&markers={marker}|{place}&key={_apiKey}");
Uri uri = new(URI_TEMPLATE + $"?{mapPosition}={place}&zoom={zoom}&size={size}&markers={marker}|{place}&key={_apiKey}");

return uri.ToString();
}
Expand Down
4 changes: 2 additions & 2 deletions BotNet.Services/MarkdownV2/MarkdownV2Sanitizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@

namespace BotNet.Services.MarkdownV2 {
public static class MarkdownV2Sanitizer {
private static readonly HashSet<char> CHARACTERS_TO_ESCAPE = new() {
private static readonly HashSet<char> CHARACTERS_TO_ESCAPE = [
'_', '*', '[', ']', '(', ')', '~', '>', '#',
'+', '-', '=', '|', '{', '}', '.', '!'
};
];

public static string Sanitize(string input) {
if (string.IsNullOrEmpty(input))
Expand Down
84 changes: 84 additions & 0 deletions BotNet.Services/OpenAI/IntentDetector.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using BotNet.Services.OpenAI.Models;

namespace BotNet.Services.OpenAI {
public sealed class IntentDetector(
OpenAIClient openAIClient
) {
private readonly OpenAIClient _openAIClient = openAIClient;

public async Task<ChatIntent> DetectChatIntentAsync(
string message,
CancellationToken cancellationToken
) {
List<ChatMessage> messages = [
new("user", $$"""
These are available intents that one might query when they provide a text prompt:
Question,
ImageGeneration
Which intent is this query asking for? If none match, respond with Unknown.
{{message}}
Intent:
""")
];

string answer = await _openAIClient.ChatAsync(
model: "gpt-3.5-turbo",
messages: messages,
maxTokens: 128,
cancellationToken: cancellationToken
);

return answer switch {
"Question" => ChatIntent.Question,
"ImageGeneration" => ChatIntent.ImageGeneration,
"Unknown" => ChatIntent.Question,
_ => ChatIntent.Question
};
}

public async Task<ImagePromptIntent> DetectImagePromptIntentAsync(
string message,
CancellationToken cancellationToken
) {
List<ChatMessage> messages = [
new("user", $$"""
These are available intents that one might query when they provide a prompt which contain an image:
Vision,
ImageEdit,
ImageVariation
Which intent is this query asking for? If none match, respond with Unknown.
{{message}}
Intent:
""")
];

string answer = await _openAIClient.ChatAsync(
model: "gpt-3.5-turbo",
messages: messages,
maxTokens: 128,
cancellationToken: cancellationToken
);

return answer switch {
"Vision" => ImagePromptIntent.Vision,
"ImageEdit" => ImagePromptIntent.ImageEdit,
"ImageVariation" => ImagePromptIntent.ImageVariation,
"Unknown" => ImagePromptIntent.Vision,
_ => ImagePromptIntent.Vision
};
}
}
}
6 changes: 6 additions & 0 deletions BotNet.Services/OpenAI/Models/ChatIntent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace BotNet.Services.OpenAI.Models {
public enum ChatIntent {
Question,
ImageGeneration
}
}
9 changes: 9 additions & 0 deletions BotNet.Services/OpenAI/Models/ImagePromptIntent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace BotNet.Services.OpenAI.Models {
public enum ImagePromptIntent {
Vision,
ImageEdit,
ImageVariation

// TODO: OCR, VisualSearch, ImageCaptioning, ImageOutpainting
}
}
22 changes: 9 additions & 13 deletions BotNet.Services/OpenAI/OpenAIClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,17 @@
using RG.Ninja;

namespace BotNet.Services.OpenAI {
public class OpenAIClient {
public class OpenAIClient(
HttpClient httpClient,
IOptions<OpenAIOptions> openAIOptionsAccessor
) {
private const string COMPLETION_URL_TEMPLATE = "https://api.openai.com/v1/engines/{0}/completions";
private const string CHAT_URL = "https://api.openai.com/v1/chat/completions";
private static readonly JsonSerializerOptions JSON_SERIALIZER_OPTIONS = new() {
PropertyNamingPolicy = new SnakeCaseNamingPolicy()
};
private readonly HttpClient _httpClient;
private readonly string _apiKey;

public OpenAIClient(
HttpClient httpClient,
IOptions<OpenAIOptions> openAIOptionsAccessor
) {
_httpClient = httpClient;
_apiKey = openAIOptionsAccessor.Value.ApiKey!;
}
private readonly HttpClient _httpClient = httpClient;
private readonly string _apiKey = openAIOptionsAccessor.Value.ApiKey!;

public async Task<string> AutocompleteAsync(string engine, string prompt, string[]? stop, int maxTokens, double frequencyPenalty, double presencePenalty, double temperature, double topP, CancellationToken cancellationToken) {
using HttpRequestMessage request = new(HttpMethod.Post, string.Format(COMPLETION_URL_TEMPLATE, engine)) {
Expand Down Expand Up @@ -57,7 +52,7 @@ public async Task<string> AutocompleteAsync(string engine, string prompt, string
using Stream stream = await response.Content.ReadAsStreamAsync(cancellationToken);
using StreamReader streamReader = new(stream);
while (!streamReader.EndOfStream) {
string? line = await streamReader.ReadLineAsync();
string? line = await streamReader.ReadLineAsync(cancellationToken);
if (line == null) break;
if (line == "") continue;
if (!line.StartsWith("data: ", out string? json)) break;
Expand Down Expand Up @@ -103,7 +98,8 @@ [EnumeratorCancellation] CancellationToken cancellationToken
) {
using HttpRequestMessage request = new(HttpMethod.Post, CHAT_URL) {
Headers = {
{ "Authorization", $"Bearer {_apiKey}" },
{ "Authorization", $"Bearer {_apiKey}" },
{ "Accept", "text/event-stream" }
},
Content = JsonContent.Create(
inputValue: new {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
using Telegram.Bot.Types.Enums;

namespace BotNet.Services.OpenAI {
public sealed class StreamingResponseController(
public sealed class OpenAIStreamingClient(
IServiceProvider serviceProvider
) {
private readonly IServiceProvider _serviceProvider = serviceProvider;
Expand All @@ -21,6 +21,7 @@ public async Task StreamChatAsync(
string model,
IEnumerable<ChatMessage> messages,
int maxTokens,
User from,
long chatId,
int replyToMessageId
) {
Expand Down Expand Up @@ -107,15 +108,24 @@ await telegramBotClient.EditMessageTextAsync(

await downstreamTask;

// Finalize message
try {
// Finalize message
await telegramBotClient.EditMessageTextAsync(
chatId: chatId,
messageId: incompleteMessage.MessageId,
text: MarkdownV2Sanitizer.Sanitize(lastResult!),
parseMode: ParseMode.MarkdownV2,
cancellationToken: cts.Token
);

// Track thread
ThreadTracker threadTracker = serviceScope.ServiceProvider.GetRequiredService<ThreadTracker>();
threadTracker.TrackMessage(
messageId: incompleteMessage.MessageId,
sender: $"{from.FirstName}{from.LastName?.Let(lastName => " " + lastName)}",
text: lastResult!,
replyToMessageId: replyToMessageId
);
} catch {
// Message might be deleted, suppress exception
}
Expand Down
6 changes: 4 additions & 2 deletions BotNet.Services/OpenAI/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
using Microsoft.Extensions.DependencyInjection;
using BotNet.Services.OpenAI.Skills;
using Microsoft.Extensions.DependencyInjection;

namespace BotNet.Services.OpenAI {
public static class ServiceCollectionExtensions {
public static IServiceCollection AddOpenAIClient(this IServiceCollection services) {
services.AddTransient<OpenAIClient>();
services.AddTransient<OpenAIStreamingClient>();
services.AddTransient<ThreadTracker>();
services.AddTransient<CodeExplainer>();
services.AddTransient<AssistantBot>();
Expand All @@ -13,7 +15,7 @@ public static IServiceCollection AddOpenAIClient(this IServiceCollection service
services.AddTransient<SarcasticBot>();
services.AddTransient<AttachmentGenerator>();
services.AddTransient<TldrGenerator>();
services.AddTransient<StreamingResponseController>();
services.AddTransient<IntentDetector>();
return services;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
using System.Threading;
using System.Threading.Tasks;

namespace BotNet.Services.OpenAI {
public class AssistantBot {
private readonly OpenAIClient _openAIClient;

public AssistantBot(
OpenAIClient openAIClient
) {
_openAIClient = openAIClient;
}
namespace BotNet.Services.OpenAI.Skills {
public class AssistantBot(
OpenAIClient openAIClient
) {
private readonly OpenAIClient _openAIClient = openAIClient;

public Task<string> AskSomethingAsync(string name, string question, CancellationToken cancellationToken) {
string prompt = $"I am a highly intelligent question answering bot. If you ask me a question that is rooted in truth, I will give you the answer. If you ask me a question that is nonsense, trickery, or has no clear answer, I will respond with \"Unknown\".\n\n"
Expand All @@ -32,7 +28,7 @@ public Task<string> AskSomethingAsync(string name, string question, Cancellation
return _openAIClient.AutocompleteAsync(
engine: "text-davinci-002",
prompt: prompt,
stop: new[] { "\n" },
stop: ["\n"],
maxTokens: 100,
frequencyPenalty: 0.0,
presencePenalty: 0.0,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
using System.Threading;
using System.Threading.Tasks;

namespace BotNet.Services.OpenAI {
public class CodeExplainer {
private readonly OpenAIClient _openAIClient;

public CodeExplainer(
OpenAIClient openAIClient
) {
_openAIClient = openAIClient;
}
namespace BotNet.Services.OpenAI.Skills {
public class CodeExplainer(
OpenAIClient openAIClient
) {
private readonly OpenAIClient _openAIClient = openAIClient;

public async Task<string> ExplainCodeInEnglishAsync(string code, CancellationToken cancellationToken) {
string prompt = code + "\n\n\"\"\"\nHere's what the above code is doing:\n1.";
string explanation = await _openAIClient.AutocompleteAsync(
engine: "code-davinci-002",
prompt: prompt,
stop: new[] { "\"\"\"" },
stop: ["\"\"\""],
maxTokens: 128,
frequencyPenalty: 0.5,
presencePenalty: 0.0,
Expand All @@ -32,7 +28,7 @@ public async Task<string> ExplainCodeInIndonesianAsync(string code, Cancellation
string explanation = await _openAIClient.AutocompleteAsync(
engine: "code-davinci-002",
prompt: prompt,
stop: new[] { "\"\"\"" },
stop: ["\"\"\""],
maxTokens: 128,
frequencyPenalty: 0.5,
presencePenalty: 0.0,
Expand Down
Loading

0 comments on commit 1280ce4

Please sign in to comment.