Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 70 additions & 45 deletions src/ChatGPT.Net/ChatGPT.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
using ChatGPT.Net.DTO;
using ChatGPT.Net.DTO.ChatGPT;
using ChatGPT.Net.DTO.ChatGPTUnofficial;
using ChatGPT.Net.DTO.ChatGPT;
using Newtonsoft.Json;
using System.Net.Http.Headers;
using System.Net.Http.Json;

namespace ChatGPT.Net;

Expand All @@ -15,20 +11,49 @@ public class ChatGpt
public ChatGptOptions Config { get; set; } = new();
public List<ChatGptConversation> Conversations { get; set; } = new();
public string APIKey { get; set; }
/// <summary>
/// <para>Allows adjusting httpclient timeout for arbitrary long or short content</para>
/// <para>Default 100 second</para>
/// </summary>
public static int MaxTimeout { get; set; }

public ChatGpt(string apikey, ChatGptOptions? config = null)
public ChatGpt(string apikey,
ChatGptOptions? config = null,
int maxTimeout = 100)
{
Config = config ?? new ChatGptOptions();
SessionId = Guid.NewGuid();
APIKey = apikey;
MaxTimeout = maxTimeout;
}

/// <summary>
/// <para>Each HttpClient initialization will take up additional ports and will not immediately release itself when the program stops, leading to resource waste.</para>
/// <para>https://learn.microsoft.com/en-us/dotnet/fundamentals/networking/http/httpclient-guidelines</para>
/// </summary>
private static HttpClient _httpClient = null;
public static HttpClient httpClient
{
get
{
if (_httpClient == null)
{
_httpClient = new()
{
Timeout = TimeSpan.FromSeconds(MaxTimeout)
};
}

return _httpClient;
}
}

private async IAsyncEnumerable<string> StreamCompletion(Stream stream)
{
using var reader = new StreamReader(stream);
using StreamReader reader = new(stream);
while (!reader.EndOfStream)
{
var line = await reader.ReadLineAsync();
string? line = await reader.ReadLineAsync();
if (line != null)
{
yield return line;
Expand All @@ -38,7 +63,7 @@ private async IAsyncEnumerable<string> StreamCompletion(Stream stream)

public void SetConversationSystemMessage(string conversationId, string message)
{
var conversation = GetConversation(conversationId);
ChatGptConversation conversation = GetConversation(conversationId);
conversation.Messages.Add(new ChatGptMessage
{
Role = "system",
Expand All @@ -48,21 +73,21 @@ public void SetConversationSystemMessage(string conversationId, string message)

public void ReplaceConversationSystemMessage(string conversationId, string message)
{
var conversation = GetConversation(conversationId);
ChatGptConversation conversation = GetConversation(conversationId);
conversation.Messages = conversation.Messages.Where(x => x.Role != "system").ToList();
conversation.Messages.Add(new ChatGptMessage
{
Role = "system",
Content = message
});
}

public void RemoveConversationSystemMessages(string conversationId, string message)
{
var conversation = GetConversation(conversationId);
ChatGptConversation conversation = GetConversation(conversationId);
conversation.Messages = conversation.Messages.Where(x => x.Role != "system").ToList();
}

public List<ChatGptConversation> GetConversations()
{
return Conversations;
Expand All @@ -80,7 +105,7 @@ public ChatGptConversation GetConversation(string? conversationId)
return new ChatGptConversation();
}

var conversation = Conversations.FirstOrDefault(x => x.Id == conversationId);
ChatGptConversation? conversation = Conversations.FirstOrDefault(x => x.Id == conversationId);

if (conversation != null) return conversation;
conversation = new ChatGptConversation()
Expand All @@ -91,10 +116,10 @@ public ChatGptConversation GetConversation(string? conversationId)

return conversation;
}

public void SetConversation(string conversationId, ChatGptConversation conversation)
{
var conv = Conversations.FirstOrDefault(x => x.Id == conversationId);
ChatGptConversation? conv = Conversations.FirstOrDefault(x => x.Id == conversationId);

if (conv != null)
{
Expand All @@ -105,10 +130,10 @@ public void SetConversation(string conversationId, ChatGptConversation conversat
Conversations.Add(conversation);
}
}

public void RemoveConversation(string conversationId)
{
var conversation = Conversations.FirstOrDefault(x => x.Id == conversationId);
ChatGptConversation? conversation = Conversations.FirstOrDefault(x => x.Id == conversationId);

if (conversation != null)
{
Expand All @@ -118,7 +143,7 @@ public void RemoveConversation(string conversationId)

public void ResetConversation(string conversationId)
{
var conversation = Conversations.FirstOrDefault(x => x.Id == conversationId);
ChatGptConversation? conversation = Conversations.FirstOrDefault(x => x.Id == conversationId);

if (conversation == null) return;
conversation.Messages = new();
Expand All @@ -131,15 +156,15 @@ public void ClearConversations()

public async Task<string> Ask(string prompt, string? conversationId = null)
{
var conversation = GetConversation(conversationId);
ChatGptConversation conversation = GetConversation(conversationId);

conversation.Messages.Add(new ChatGptMessage
{
Role = "user",
Content = prompt
});
var reply = await SendMessage(new ChatGptRequest

ChatGptResponse reply = await SendMessage(new ChatGptRequest
{
Messages = conversation.Messages,
Model = Config.Model,
Expand All @@ -151,10 +176,10 @@ public async Task<string> Ask(string prompt, string? conversationId = null)
Stop = Config.Stop,
MaxTokens = Config.MaxTokens,
});

conversation.Updated = DateTime.Now;

var response = reply.Choices.FirstOrDefault()?.Message.Content ?? "";
string response = reply.Choices.FirstOrDefault()?.Message.Content ?? "";

conversation.Messages.Add(new ChatGptMessage
{
Expand All @@ -167,15 +192,15 @@ public async Task<string> Ask(string prompt, string? conversationId = null)

public async Task<string> AskStream(Action<string> callback, string prompt, string? conversationId = null)
{
var conversation = GetConversation(conversationId);
ChatGptConversation conversation = GetConversation(conversationId);

conversation.Messages.Add(new ChatGptMessage
{
Role = "user",
Content = prompt
});

var reply = await SendMessage(new ChatGptRequest
ChatGptResponse reply = await SendMessage(new ChatGptRequest
{
Messages = conversation.Messages,
Model = Config.Model,
Expand All @@ -188,20 +213,20 @@ public async Task<string> AskStream(Action<string> callback, string prompt, stri
MaxTokens = Config.MaxTokens,
}, response =>
{
var content = response.Choices.FirstOrDefault()?.Delta.Content;
string? content = response.Choices.FirstOrDefault()?.Delta.Content;
if (content is null) return;
if (!string.IsNullOrWhiteSpace(content)) callback(content);
});

conversation.Updated = DateTime.Now;

return reply.Choices.FirstOrDefault()?.Message.Content ?? "";
}

public async Task<ChatGptResponse> SendMessage(ChatGptRequest requestBody, Action<ChatGptStreamChunkResponse>? callback = null)
{
var client = new HttpClient();
var request = new HttpRequestMessage
HttpClient client = ChatGpt.httpClient;
HttpRequestMessage request = new()
{
Method = HttpMethod.Post,
RequestUri = new Uri($"{Config.BaseUrl}/v1/chat/completions"),
Expand All @@ -218,34 +243,34 @@ public async Task<ChatGptResponse> SendMessage(ChatGptRequest requestBody, Actio
}
};

var response = await client.SendAsync(request,HttpCompletionOption.ResponseHeadersRead);
HttpResponseMessage response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);

response.EnsureSuccessStatusCode();

if (requestBody.Stream)
{
var contentType = response.Content.Headers.ContentType?.MediaType;
string? contentType = response.Content.Headers.ContentType?.MediaType;
if (contentType != "text/event-stream")
{
var error = await response.Content.ReadFromJsonAsync<ChatGptResponse>();
ChatGptResponse? error = await response.Content.ReadFromJsonAsync<ChatGptResponse>();
throw new Exception(error?.Error?.Message ?? "Unknown error");
}

var concatMessages = string.Empty;
string concatMessages = string.Empty;

ChatGptStreamChunkResponse? reply = null;
var stream = await response.Content.ReadAsStreamAsync();
await foreach (var data in StreamCompletion(stream))
Stream stream = await response.Content.ReadAsStreamAsync();
await foreach (string data in StreamCompletion(stream))
{
var jsonString = data.Replace("data: ", "");
string jsonString = data.Replace("data: ", "");
if (string.IsNullOrWhiteSpace(jsonString)) continue;
if(jsonString == "[DONE]") break;
if (jsonString == "[DONE]") break;
reply = JsonConvert.DeserializeObject<ChatGptStreamChunkResponse>(jsonString);
if (reply is null) continue;
concatMessages += reply.Choices.FirstOrDefault()?.Delta.Content;
callback?.Invoke(reply);
}

return new ChatGptResponse
{
Id = reply?.Id ?? Guid.NewGuid().ToString(),
Expand All @@ -264,9 +289,9 @@ public async Task<ChatGptResponse> SendMessage(ChatGptRequest requestBody, Actio
};
}

var content = await response.Content.ReadFromJsonAsync<ChatGptResponse>();
if(content is null) throw new Exception("Unknown error");
if(content.Error is not null) throw new Exception(content.Error.Message);
ChatGptResponse? content = await response.Content.ReadFromJsonAsync<ChatGptResponse>();
if (content is null) throw new Exception("Unknown error");
if (content.Error is not null) throw new Exception(content.Error.Message);
return content;
}
}
Loading