{"id":3283,"date":"2023-03-25T08:00:08","date_gmt":"2023-03-25T08:00:08","guid":{"rendered":"https:\/\/jamiemaguire.net\/?p=3283"},"modified":"2023-03-26T07:48:07","modified_gmt":"2023-03-26T06:48:07","slug":"how-to-build-an-ai-bot-using-azure-functions-chat-gpt-and-the-twitter-api-v2-with-c","status":"publish","type":"post","link":"https:\/\/jamiemaguire.net\/index.php\/2023\/03\/25\/how-to-build-an-ai-bot-using-azure-functions-chat-gpt-and-the-twitter-api-v2-with-c\/","title":{"rendered":"How To Build an AI Bot using Azure Functions, Chat GPT and the Twitter API v2 with C#"},"content":{"rendered":"<p>Chat GPT is everywhere at the moment.\u00a0 I\u2019ve been digging into the APIs and playing around with the endpoints.<\/p>\n<p>In this guide, you\u2019ll see how to build a Twitter bot using an Azure Function and Chat GPT in C#.<\/p>\n<p>The bot will generate tweets from a single prompt using Chat GPT Completions endpoint and post these to Twitter.<\/p>\n<p>It\u2019ll take less than an hour.<\/p>\n<h2>Required NuGet Packages<\/h2>\n<p>The following NuGet packages are used to connect to the Twitter API v2 and Chat GPT:<\/p>\n<ul>\n<li>Social Opinion API<\/li>\n<li>OpenAI.GPT3<\/li>\n<\/ul>\n<p>&nbsp;<\/p>\n<p>Download the Social Opinion NuGet package <a href=\"https:\/\/www.nuget.org\/packages\/SocialOpinionAPI\/\">here<\/a>.\u00a0 Download the Chat GPT NuGet package from Betalgo <a href=\"https:\/\/betalgo.github.io\/openai\/\">here<\/a>.<\/p>\n<h2>Process and Configuration<\/h2>\n<p>The process is relatively straightforward and as follows:<\/p>\n<ol>\n<li>Construct a request for the Chat GPT Completions endpoint<\/li>\n<li>Send the request using the Chat GPT NuGet package<\/li>\n<li>Parse the text response<\/li>\n<li>Create a tweet using the text response for Chat GPT<\/li>\n<li>Send the Tweet using the Social Opinion NuGet package<\/li>\n<\/ol>\n<p>&nbsp;<\/p>\n<p>The following sequence diagram shows this in action:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"1703\" height=\"674\" class=\"alignnone wp-image-3284 size-full\" src=\"https:\/\/jamiemaguire.net\/wp-content\/uploads\/2023\/03\/Pasted-10.png\" srcset=\"https:\/\/jamiemaguire.net\/wp-content\/uploads\/2023\/03\/Pasted-10.png 1703w, https:\/\/jamiemaguire.net\/wp-content\/uploads\/2023\/03\/Pasted-10-750x297.png 750w, https:\/\/jamiemaguire.net\/wp-content\/uploads\/2023\/03\/Pasted-10-1400x554.png 1400w, https:\/\/jamiemaguire.net\/wp-content\/uploads\/2023\/03\/Pasted-10-768x304.png 768w, https:\/\/jamiemaguire.net\/wp-content\/uploads\/2023\/03\/Pasted-10-1536x608.png 1536w, https:\/\/jamiemaguire.net\/wp-content\/uploads\/2023\/03\/Pasted-10-816x323.png 816w\" sizes=\"auto, (max-width: 1703px) 100vw, 1703px\" \/><\/p>\n<h2>Creating the Chat GPT Request<\/h2>\n<p>To generate text using the Chat GPT Completions endpoint, you need an API Key.\u00a0 You can do this by visiting your developer profile at Open AI:<\/p>\n<p>After fetching your API Key, the following steps are performed in the code:<\/p>\n<p>&nbsp;<\/p>\n<ol>\n<li>Construct a CompletionCreateRequest<\/li>\n<li>Seed the prompt with a phrase<\/li>\n<li>Specify the max number of tokens and language model you want to use<\/li>\n<li>Send the request and parse the result<\/li>\n<\/ol>\n<p>&nbsp;<\/p>\n<p>This code is encapsulated within a method CreateTweetUsingOpenAI :<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"csharp\">private static async Task&lt;string&gt; CreateTweetUsingOpenAI()\r\n{\r\nOpenAIService service = new OpenAIService(new OpenAI.GPT3.OpenAiOptions\r\n  {\r\n    ApiKey = \"YOUR-OPEN-API-KEY\"\r\n  });\r\n\r\nvar completionResult = await service.Completions.CreateCompletion(new CompletionCreateRequest\r\n\r\n{\r\n  Prompt = \"Write a funny but true observation about software development tweet in 280 characters or less.\u00a0 Do not include hashtags.\",\r\n  MaxTokens = 280,\r\n  LogProbs = 1,\r\n  Model = Models.TextDavinciV3\r\n});\r\nif (completionResult.Successful)\r\n{\r\n  return completionResult.Choices.FirstOrDefault().Text;\r\n}\r\nelse\r\n{\r\nif (completionResult.Error == null)\r\n{\r\n  throw new Exception(\"Unknown Error\");\r\n}\r\n  Console.WriteLine($\"{completionResult.Error.Code}: {completionResult.Error.Message}\");\r\n}\r\n  return completionResult.Choices.FirstOrDefault().Text;\r\n}<\/pre>\n<p>&nbsp;<\/p>\n<p>The generated text from the above method is then returned to the caller.<\/p>\n<h2>Posting the Tweet<\/h2>\n<p>The method PostTweet accepts the content to post on Twitter and simple posts it.<\/p>\n<p>The OAuth object is instantiated with the relevant keys :<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"csharp\">private static Task&lt;TweetModel&gt; PostTweet(string tweetContent)\r\n{\r\n  string consumerKey = \"YOUR_CONSUMER_KEY\";\r\n  string consumerSecret = \" YOUR_CONSUMER_SECRET\";\r\n  string accessToken = \" YOUR_ACCESS_TOKEN\";\r\n  string accessTokenSecret = \" YOUR_ACCESS_TOKEN_SECRET\";\r\n\r\nTweetService service = new TweetService(new SocialOpinionAPI.Core.OAuthInfo\r\n{\r\n  AccessSecret = accessTokenSecret,\r\n  AccessToken = accessToken,\r\n  ConsumerKey = consumerKey,\r\n  ConsumerSecret = consumerSecret\r\n});\r\nreturn Task.FromResult(service.PostTweetV1(tweetContent));\r\n}<\/pre>\n<p>&nbsp;<\/p>\n<p>You can obtain the keys for your account by visiting https:\/\/developer.twitter.com :<\/p>\n<p>A serialized tweet model is returned to the caller:<\/p>\n<p>&nbsp;<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"json\">{\r\n\u00a0\u00a0\u00a0\u00a0\"data\":\u00a0{\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"author_id\":\u00a0null,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"created_at\":\u00a0\"2023-03-13T10:49:08\",\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"id\":\u00a0\"1635231512579678208\",\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"lang\":\u00a0\"en\",\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"possibly_sensitive\":\u00a0false,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"source\":\u00a0\"&lt;a\u00a0href=\\\"https:\/\/socialopinion.co.uk\/\\\"\u00a0rel=\\\"nofollow\\\"&gt;DeveloperFun&lt;\/a&gt;\",\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"text\":\u00a0\"Software\u00a0development:\u00a0It's\u00a0so\u00a0complex,\u00a0it\u00a0feels\u00a0like\u00a0we're\u00a0trying\u00a0to\u00a0unravel\u00a0an\u00a0endless\u00a0ball\u00a0of\u00a0tangled\u00a0yarn,\u00a0but\u00a0l\u2026\u00a0https:\/\/t.co\/E2UYqxGMi9\"\r\n\u00a0\u00a0\u00a0\u00a0},\r\n\u00a0\u00a0\u00a0\u00a0\"includes\":\u00a0null\r\n}<\/pre>\n<p>&nbsp;<\/p>\n<h2>Azure Function<\/h2>\n<p>A single Azure function invokes each of the above methods.\u00a0 The code is relatively straight forward and uses the default Azure Function project template in Visual Studio 2020 :<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"csharp\">[FunctionName(\"GenerateTweet\")]\r\npublic static async Task&lt;IActionResult&gt; Run([HttpTrigger(AuthorizationLevel.Function,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \"get\",\"post\", Route = null)] HttpRequest req,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ILogger log)\r\n\r\n{\r\ntry\r\n{\r\n  log.LogInformation(\"C# HTTP trigger function processed a request.\");\r\n  string tweet = await CreateTweetUsingOpenAI();\r\n  var tweetModel = await PostTweet(tweet);\r\n  return new OkObjectResult(JsonConvert.SerializeObject(tweetModel));\r\n}\r\ncatch(Exception ex)\r\n{\r\n  return new BadRequestObjectResult(ex.Message);\r\n}\r\n}<\/pre>\n<p>&nbsp;<\/p>\n<p>In the above code, the function accepts and POST or GET.\u00a0 The tweet is generated using Open AI and then posted to Twitter.<\/p>\n<h2>Demo<\/h2>\n<p>You can see a demo of this in action here. In this demo, you\u2019ll see a POST being sent using Postman to a locally running instance of the Azure Function.<\/p>\n<p>The function is invoked, and content generated using AI is automatically posted to Twitter:<\/p>\n<div class=\"entry-content-asset videofit\"><iframe loading=\"lazy\" title=\"How To Build an AI Bot using Azure Functions, Chat GPT and the Twitter API v2 with C#\" width=\"720\" height=\"405\" src=\"https:\/\/www.youtube.com\/embed\/UxQ04gJsw64?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><\/div>\n<h2>Ideas, Further Reading, and Resources<\/h2>\n<p>Some notes from the above code:<\/p>\n<ul>\n<li>API keys and secrets have been hard coded. You don\u2019t really want to do this. Store these in Azure Key Vault and read from there.<\/li>\n<li>The Chat GPT prompt is also hard coded. Introduce configuration to make this more dynamic.<\/li>\n<li>The Azure Function could be run on a Timer Trigger as opposed to an HTTP Trigger.<\/li>\n<li>Run the Azure Function at specific periods to schedule content.<\/li>\n<\/ul>\n<p>&nbsp;<\/p>\n<p>For tips on how to implement the above read the useful links that follow.<\/p>\n<h2>Useful Links:<\/h2>\n<ul>\n<li><strong>Azure Function Configuration Settings: <\/strong><a href=\"https:\/\/learn.microsoft.com\/en-us\/azure\/azure-app-configuration\/quickstart-azure-functions-csharp?tabs=in-process#connect-to-an-app-configuration-store\">Quickstart for Azure App Configuration with Azure Functions | Microsoft Learn<\/a><\/li>\n<li><strong>Securing Azure Function Secrets: <\/strong><a href=\"https:\/\/sandervandevelde.wordpress.com\/2019\/05\/01\/it-only-takes-simple-five-steps-to-secure-your-secrets-in-azure-functions\/\">It only takes five simple steps to secure your secrets in Azure Functions \u2013 Sander van de Velde (wordpress.com)<\/a><\/li>\n<li><strong>Timer Triggers and Azure Functions: <\/strong><a href=\"https:\/\/learn.microsoft.com\/en-us\/training\/modules\/execute-azure-function-with-triggers\/?source=recommendations\">Execute an Azure Function with triggers &#8211; Training | Microsoft Learn<\/a><\/li>\n<li><strong>Open AI Completions API Endpoint: <\/strong><a href=\"https:\/\/platform.openai.com\/docs\/guides\/completion\">Text completion &#8211; OpenAI API<\/a><\/li>\n<\/ul>\n<p>&nbsp;<\/p>\n<h2>Summary<\/h2>\n<p>There are loads of possibilities with the Open AI APIs and generative AI. Mesh them with different APIs and web services to help create innovative solutions.\u00a0 How will you use them?<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Chat GPT is everywhere at the moment.\u00a0 I\u2019ve been digging into the APIs and playing around with the endpoints. In this guide, you\u2019ll see how to build a Twitter bot using an Azure Function and&#8230;<\/p>\n","protected":false},"author":1,"featured_media":3285,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_exactmetrics_skip_tracking":false,"_exactmetrics_sitenote_active":false,"_exactmetrics_sitenote_note":"","_exactmetrics_sitenote_category":0,"_kad_post_transparent":"","_kad_post_title":"","_kad_post_layout":"","_kad_post_sidebar_id":"","_kad_post_content_style":"","_kad_post_vertical_padding":"","_kad_post_feature":"","_kad_post_feature_position":"","_kad_post_header":false,"_kad_post_footer":false,"_kad_post_classname":"","footnotes":""},"categories":[81,65,102,6,101,100,8,71,70],"tags":[103],"class_list":["post-3283","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-ai","category-azure","category-azure-functions","category-c","category-chat-gpt","category-openai","category-prototyping","category-social-opinion-api","category-twitter-api-v2","tag-openai"],"_links":{"self":[{"href":"https:\/\/jamiemaguire.net\/index.php\/wp-json\/wp\/v2\/posts\/3283","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/jamiemaguire.net\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/jamiemaguire.net\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/jamiemaguire.net\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/jamiemaguire.net\/index.php\/wp-json\/wp\/v2\/comments?post=3283"}],"version-history":[{"count":0,"href":"https:\/\/jamiemaguire.net\/index.php\/wp-json\/wp\/v2\/posts\/3283\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/jamiemaguire.net\/index.php\/wp-json\/wp\/v2\/media\/3285"}],"wp:attachment":[{"href":"https:\/\/jamiemaguire.net\/index.php\/wp-json\/wp\/v2\/media?parent=3283"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/jamiemaguire.net\/index.php\/wp-json\/wp\/v2\/categories?post=3283"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/jamiemaguire.net\/index.php\/wp-json\/wp\/v2\/tags?post=3283"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}