最近這個ChatGPT很火啊,看了B站上很多影片,自己非常手癢,高低自己得整一個啊,但是讓我很難受的是,翻遍了github前十頁,竟然沒有一個C#的ChatGPT專案,我好難受啊!那能怎麼辦?自己搞一個吧。
但是,等等,現在的ChatGPT專案基本都是網頁逆向獲取幾個token,我不會啊,我都不知道哪些cookie是重要的,那我只能找一個其它語言的ChatGPT API專案,自己造(翻譯)一個c#的ChatGPT API庫了。
ChatGPT是啥?
先了解GTP,摘自官網的一段話“A set of models that can understand and generate natural language”,其實,就是一個自然語言處理的模型。所以ChatGPT顧名思義就是基於GPT3的一個聊天AI。
但是想讓他做苦力幫忙寫程式碼的話所需要的其實不是這個模型,而是Codex這個模型,按照官網所說“Most capable Codex model. Particularly good at translating natural language to code. In addition to completing code, also supports inserting completions within code.”。真的是碉堡了!
準備
第一步:註冊OpenAI賬號
因為對大陸以及中國香港地區不開放,所以我們需要小小的科學一下。
來一個我就是參照著申請賬號的園子的文章:
https://www.cnblogs.com/chatgpt/p/how-to-register-chatgpt-in-china.html
當然手機那裡可以淘寶找,幾塊錢就能幫忙註冊手機,賬號裡面預設還有18美元餘額。
注意點:如果搭建了科學還是提示不對你的國家提供服務的話,嘗試清空瀏覽器快取或者開啟瀏覽器的無痕視窗。Chrome預設在右上角三個點開啟就能找到“開啟新的無痕式視窗”。
萬事具備,直接擼程式碼
1.網頁獲取所需token和cookie
我們需要三個東西:UserAgent,CfClearance,Session_token
我們需要先開啟ChatGPT官方網站:https://chat.openai.com/chat 然後按下F12開啟瀏覽器的開發者模式
UserAgent在網路裡(只需要複製UserAgent:後面的值):
CfClearance和Session_token在應用程式->cookie裡面
2.建立一個session用來表示一個會話
public OpenAISession(string session_token,string cfClearance,string userAgent)
{
Session_token = session_token;
CfClearance = cfClearance;
UserAgent = userAgent;
Headers = new Dictionary<string, string>();
Cookies = new Dictionary<string, string>();
Proxies = new Dictionary<string, string>();
}
Session可以重新整理自己的AccessToken和Session_token
public async Task RefreshSessionAsync()
{
if (string.IsNullOrEmpty(Session_token))
{
throw new Exception("No tokens provided");
}
// Set cookies
Cookies.Put("__Secure-next-auth.session-token", Session_token);
Cookies.Put("cf_clearance", CfClearance);
string cookiesString = GetCookiesString();
Dictionary<string, string> map = new Dictionary<string, string>();
Headers.Put("User-Agent", UserAgent);
Headers.Put("cookie", cookiesString);
Headers.Put("Cookie", cookiesString);
var response = await GetAsync("https://chat.openai.com/api/auth/session");
if (!response.IsSuccessStatusCode)
{
Console.WriteLine("err code: " + response.StatusCode);
Console.WriteLine("cf_clearance: " + CfClearance);
Console.WriteLine("token: " + Session_token);
Console.WriteLine("userAgent: " + UserAgent);
Console.WriteLine("請檢查以上引數是否正確,是否過期。");
throw new Exception("無法獲取token!請重試");
}
try
{
string name = "__Secure-next-auth.session-token=";
var cookies = response.Headers.GetValues("Set-Cookie");
var stoken = cookies.FirstOrDefault(x => x.StartsWith(name));
Session_token = stoken == null ? Session_token : stoken.Substring(name.Length, stoken.IndexOf(";") - name.Length);
Cookies.Put("__Secure-next-auth.session-token", Session_token);
var result = await response.Content.ReadAsStringAsync();
AccessToken = JsonSerializer.Deserialize<Profile>(result, _jsonSerializerOptions)?.AccessToken;
RefreshHeaders();
}
catch (Exception ex)
{
Console.WriteLine($"Error {nameof(RefreshSessionAsync)}:{ex}");
throw new Exception($"Error {nameof(RefreshSessionAsync)}", ex);
}
}
獲取到的最新的AccessToken更新到header裡,Session_token更新到cookie裡
private void RefreshHeaders()
{
Headers.Put("Host", "chat.openai.com");
Headers.Put("Accept", "text/event-stream");
Headers.Put("Authorization", $"Bearer {AccessToken}");
Headers.Put("User-Agent", UserAgent);
Headers.Put("X-Openai-Assistant-App-Id", string.Empty);
Headers.Put("Connection", "close");
Headers.Put("Accept-Language", "en-US,en;q=0.9");
Headers.Put("Referer", "https://chat.openai.com/chat");
}
string name = "__Secure-next-auth.session-token=";
var cookies = response.Headers.GetValues("Set-Cookie");
var stoken = cookies.FirstOrDefault(x => x.StartsWith(name));
Session_token = stoken == null ? Session_token : stoken.Substring(name.Length, stoken.IndexOf(";") - name.Length);
Cookies.Put("__Secure-next-auth.session-token", Session_token);
3.建立機器人繫結一個會話
public Chatbot(OpenAISession openAISession)
{
OpenAISession = openAISession;
ResetConversation();
}
/// <summary>
/// 重置Conversation,開啟一個新的會話
/// </summary>
public void ResetConversation()
{
_conversationId = null;
_parentMessageId = Guid.NewGuid().ToString();
}
如何保持上下文聊天以及多使用者隔離?
上下文聊天已經內建,會自動與機器人的回覆進行關聯,當需要重新開啟一個對話的時候,可以呼叫ResetConversation,這樣對應的機器人物件發過去就是開啟了一個新的對話。
如何多使用者隔離?比如聯合微信機器人使用,可以將一個人或者一個群對應一個Chatbot物件,並且利用id與機器人做關聯。
當然這個_clientID需要使用者做唯一性管理
//可以作為隔離不同客戶端聊天上下文的憑據,即一個機器人繫結一個客戶端
//客戶端編號可以是,微信id,qq id,飛書id,亦或者自己開發的軟體的使用者id
private readonly string _clientID;
public Chatbot(OpenAISession openAISession, string clientID)
{
OpenAISession = openAISession;
_clientID = clientID == null ? Guid.NewGuid().ToString() : clientID;
ResetConversation();
}
最後構造對話物件,傳送到對應api
public async Task<Reply> GetChatReplyAsync(string prompt)
{
var conversation = new Conversation();
conversation.Conversation_id = _conversationId;
conversation.Parent_message_id = _parentMessageId;
conversation.Messages = new Message[]
{
new Message()
{
Content = new Content
{
Parts = new string []{ prompt }
}
}
};
return await GetChatResponseAsync(conversation);
}
/// <summary>
/// 獲取響應
/// </summary>
/// <param name="conversation"></param>
/// <returns></returns>
/// <exception cref="Exception">伺服器返回非200</exception>
private async Task<Reply> GetChatResponseAsync(Conversation conversation)
{
using (var client = new HttpClient())
{
var response = await OpenAISession.PostAsync(_conversation, JsonSerializer.Serialize(conversation, _jsonSerializerOptions));
if (!response.IsSuccessStatusCode)
{
throw new Exception($"Faild to request.StatusCode:{response.StatusCode}");
}
var msg = await response.Content.ReadAsStringAsync();
var data = msg.Split("\n")?.ToList().Where(x => !string.IsNullOrEmpty(x) && !x.Contains("data: [DONE]")).LastOrDefault()?.Substring(5);
var reply = JsonSerializer.Deserialize<Reply>(data, _jsonSerializerOptions);
_conversationId = reply.Conversation_id;
return reply;
}
}
效果截圖(僅測試)
OpenAISession openAIOptions = new OpenAISession(Session_token, CfClearance, UserAgent);
await openAIOptions.RefreshSessionAsync();
Chatbot chatbot = new Chatbot(openAIOptions);
Console.WriteLine("用c++寫個冒泡查詢");
var reply = await chatbot.GetChatReplyAsync("用c++寫個冒泡查詢");
Console.WriteLine(reply.Message.Content.Parts.FirstOrDefault());