avatarFuji Nguyen

Summary

The provided content outlines a comprehensive guide on integrating ChatGPT with an Angular 17 and .NET Core 8 application to enhance a Talent Management SPA's functionality, focusing on leveraging AI for job description content.

Abstract

The article delves into the technical process of integrating ChatGPT into a .NET Core 8 WebAPI, which is part of a series on building a Talent Management SPA with Angular 17. It emphasizes the benefits of using ChatGPT for improving job descriptions, including grammar, tone, skill matching, and SEO optimization. The integration involves setting up a backend service to communicate with OpenAI's API and connecting it to the Angular frontend. The guide discusses the architecture of the integration, the steps to obtain and securely store an OpenAI API key, and the implementation of CQRS for handling requests. It also provides source code examples and addresses frequently asked questions, offering practical insights for developers looking to incorporate AI capabilities into their applications.

Opinions

  • The author, Fuji Nguyen, views ChatGPT as an essential functionality akin to other external services like weather services or Google Maps.
  • Nguyen is currently focused on implementing straightforward completion prompts but is considering the potential expansion to include features like DALL·E 3 or videos, which would influence the integration approach.
  • The author recommends using a dedicated infrastructure project for ChatGPT integration to isolate and manage its functionality effectively, especially when planning for future scalability and integration with other services like ElasticSearch.
  • The author emphasizes the importance of not hardcoding the API key and using environment variables or a secrets manager for security purposes.
  • Nguyen suggests that calling ChatGPT directly from Angular is technically possible but not recommended due to the risk of exposing the API key.
  • The author has personal experience using the ChatGPT extension in Visual Studio for coding productivity and provides a cost perspective on using ChatGPT tokens.

Angular 17 and .NET Core 8: ChatGPT and WebAPI Integration

Appendix F

Preface

This is another installment in our comprehensive series, Building a Talent Management SPA with Angular 17 and .NET Core 8. For a broader view of the series and to access other parts, you can refer back to the series’ table of contents, where each segment is meticulously organized to guide you through every stage of building a robust Talent Management SPA.

Introduction

Welcome to Appendix F of this blog series! We’ll be diving deep into integrating ChatGPT into the demo Talent Management application. We will extend Talent Management WebAPI with ChatGPT, taking advantage of Generative AI capability to improve the job description content.

Using ChatGPT for Talent Management job descriptions enhances:

  • Grammar and clarity.
  • Inclusive tone analysis.
  • Skill matching and gap identification.
  • Competitive positioning.
  • SEO keyword optimization.
  • Feedback question generation.
  • Unconscious bias detection.
  • Technical role simulation for clearer role understanding.

Content

Integrating ChatGPT into an Angular application involves setting up a backend service (NetCore WebAPI) that interfaces with OpenAI’s API (which provides access to ChatGPT) and then connecting the Angular frontend to this service. This blog will cover the following:

Part 1: Integration Overview Describe the interconnectivity between Angular, NetCore WebAPI, and ChatGPT.

Part 2: ChatGPT and NetCore Web API Integration Set up a robust NetCore Web API to connect your Web API with ChatGPT. Create the project, secure your API key, establish communication with OpenAI, and fine-tune responses.

In this blog, I will focus on integrating WebAPI with ChatGPT. Updating Angular to call a new WebAPI endpoint with ChatGPT integration will not be covered in this post. — Fuji Nguyen

Part 1: Integration Overview

In the integrated system, the user interacts with the Angular application, which captures the user’s input and sends it to the NetCore WebAPI via HTTP requests. The WebAPI, having received the request, authenticates and communicates with the ChatGPT API, sending the user’s input and awaiting a response. Once the ChatGPT API processes the input, it generates a text response that is sent back to the NetCore WebAPI. This response is then formatted as required by the application’s business logic and returned to the Angular application, where it is finally presented to the user.

1.1 Angular: The Client-Side Framework

Angular operates as the client-side framework that constructs the user interface of the application. It is responsible for rendering the interactive elements that the user engages with, such as forms and buttons. Within Angular:

  • Components encapsulate the logic for processing user inputs and presenting data.
  • Services are used to abstract and encapsulate external interactions with data sources, which in this context, is the NetCore WebAPI.
  • Modules define the boundaries of related functionalities and serve as containers for a cohesive block of code dedicated to an application domain.

1.2 NetCore WebAPI: The Server-Side Interface

The NetCore WebAPI functions as an intermediary between the Angular front-end and the ChatGPT service. It is tasked with handling HTTP requests from the Angular application, interfacing with the ChatGPT API, and returning the AI-generated responses. Key responsibilities include:

  • Request Handling: Accepting and interpreting requests from the Angular client.
  • API Key Management: Safeguarding sensitive credentials required to authenticate with the OpenAI API.
  • Response Processing: Formatting and relaying the ChatGPT responses back to the Angular application.

1.3 ChatGPT: The AI Model

ChatGPT, provided by OpenAI, is an advanced language model that generates human-like text based on the input it receives. It is accessed via an API which requires secure authentication. The role of ChatGPT in this system is to:

  • Receive Prompts: Accept input in the form of prompts via API requests, typically relayed by the NetCore WebAPI.
  • Generate Responses: Process these prompts and generate text responses based on the patterns it has learned during its training.
  • Return Data: Send these responses back to the NetCore WebAPI, which then passes them on to the Angular front-end.

Part 2: ChatGPT and NetCore Web API Integration

One consideration in integrating ChatGPT into the clean architecture is determining where within the architecture it fits best. The TalentManagement project follows the TemplateOnionAPI structure, comprising five projects organized into three layers: CORE, Infrastructure, and Presentation.

Here are the options for incorporating the ChatGPT code:

  1. TalentManagementApi.WebApi: Integrate ChatGPT directly into the web API project. This option offers simplicity and direct access to the API endpoints.
  2. TalentManagementApi.Application and TalentManagementApi.WebApi: Implement ChatGPT within both the application and web API layers. This approach ensures that ChatGPT functionality is accessible both internally within the application and externally via the API.
  3. TalentManagementApi.Infrastructure.Shared, TalentManagementApi.Application, and TalentManagementApi.WebApi: Utilize shared infrastructure components to integrate ChatGPT across multiple layers. This option promotes code reusability and maintains consistency throughout the architecture.
  4. Create a new project in the infrastructure folder, TalentManagementApi.Infrastructure.GenerativeAI, and wire it to TalentManagementApi.Application and TalentManagementApi.WebApi: Establish a dedicated infrastructure project specifically for ChatGPT integration. This isolates ChatGPT-related functionality, making it easier to manage and maintain.

Each option offers advantages depending on factors such as project complexity, scalability requirements, and maintenance considerations. Carefully evaluate these factors to determine the most suitable integration approach for your specific use case.

For this blog post, I found myself torn between options 3 and 4. I view ChatGPT akin to a weather service or Google Maps, providing essential functionality. Currently, I’m focused on implementing a straightforward completion prompt thus I go with option 4. However, should I expand to include features like integrating images (DALL·E 3) or videos (Sora), option 4 becomes a more enticing prospect. Looking ahead, when I tackle integration with ElasticSearch, particularly involving CRUD, option 4 will be my preferred choice. — Fuji Nguyen

2.1 Obtaining an OpenAI API key

OpenAI has emerged as a beacon of innovation, offering tools like ChatGPT that have revolutionized how we interact with technology. For developers looking to harness this power within their applications, obtaining an OpenAI API key is the first crucial step. This guide will walk you through the process of acquiring an OpenAI API key and integrating it into a NetCore WebAPI project, paving the way for you to leverage AI capabilities in your applications.

Step 1: Sign Up with OpenAI

Before you can dive into the world of AI, you need to create an account with OpenAI. Visit the OpenAI website and sign up. The registration process is straightforward — fill in the required details, verify your email, and you’re set to explore the OpenAI platform.

Step 2: Generate Your OpenAI API Key

Once your account is active, navigate to the API section in your dashboard. Here, you’ll find the option to generate a new API key. This key is your passport to accessing OpenAI’s suite of AI tools, so keep it secure. When generated, make sure to copy and store it safely; it’s your unique identifier and should not be shared.

Step 3: Securely Store Your API Key

Security is paramount when handling API keys. Instead of hardcoding your key into your application, use environment variables or a secrets manager. For local development, storing the key in Visual Studio Secret Manager is sufficient. On production servers, utilize secure storage solutions like AWS Secrets Manager, Azure Key Vault, or HashiCorp Vault. This approach minimizes the risk of exposing your key in source code or version control systems.

See below for an example of storing the key in Visual Studio Secret Manager during development. This prevents checking the key into GitHub.

2.2 Integrating ChatGPT into your NetCore WebAPI

For ChatGPT integration, I use the .NET library at OpenAI-API-dotnet on GitHub. It simplifies using OpenAI’s AI models like GPT, Codex, and DALL·E in .NET apps. Check out the repo and give it a star! — Fuji Nguyen

Here are the steps to integrate NetCore WebAPI with OpenAI ChatGPT

2.2.1 Set up a NetCore Web API project

Start with the Talent Management Web API project in .NET Core 8 and create a feature branch ChatGPT. This project will serve as the intermediary between your application’s frontend and OpenAI’s services.

You can find the feature branch ChatGPT on GitHub.

2.2.2 Integrate OpenAI API

To integrate the OpenAI API into your project, create a service class PromptService within your NetCore project that utilizes the HttpClient to make requests to OpenAI. This service will use your securely stored API key to authenticate requests.

Here is the listing of the source code in the PromptService.cs

using Microsoft.Extensions.Configuration;
using OpenAI_API;
using OpenAI_API.Chat;
using OpenAI_API.Models;
using System.Threading.Tasks;
using TalentManagementApi.Application.Interfaces;

namespace PromptAPI.Services
{
    public class PromptService : IPromptService
    {
        public readonly IConfiguration _configuration;

        public PromptService(IConfiguration configuration)
        {
            _configuration = configuration;
        }

        public async Task<string> CreateChatCompletionAsync(string prompt)
        {
            var apiKey = _configuration.GetValue<string>("OpenAISetting:APIKey");

            // https://github.com/OkGoDoIt/OpenAI-API-dotnet/blob/master/README.md
            OpenAIAPI api = new OpenAIAPI(apiKey); // shorthand

            var result = await api.Chat.CreateChatCompletionAsync(new ChatRequest()
            {
                Model = Model.ChatGPTTurbo,
                Temperature = 0.7f,
                MaxTokens = 100,
                Messages = new ChatMessage[] {
            new ChatMessage(ChatMessageRole.User, prompt)
                }
            });

            var reply = result.Choices[0].Message.TextContent;

            return reply;
        }
    }
}

This C# code snippet demonstrates how a .NET application can integrate with the OpenAI API to send prompts and receive responses from the ChatGPT model, specifically using the OpenAI .NET library. The PromptService class is designed to encapsulate the logic required to interact with OpenAI's Chat API. Let's dissect the code:

Namespaces

  • Microsoft.Extensions.Configuration: This namespace is used to access application settings, such as the API key needed to authenticate requests to OpenAI’s API.
  • OpenAI_API: The main namespace for the OpenAI .NET library, providing access to OpenAI’s services.
  • OpenAI_API.Chat: Contains functionality specific to interacting with OpenAI’s chat models.
  • OpenAI_API.Models: Includes data models used by the OpenAI API client to structure requests and responses.
  • System.Threading.Tasks: Supports asynchronous programming, allowing the service to perform non-blocking API calls.
  • TalentManagementApi.Application.Interfaces: Presumably contains the IPromptService interface, defining the contract that PromptService implements.

Class and Method Summary

  • PromptService: Implements IPromptService, providing a specific implementation to interact with OpenAI's Chat API.
  • _configuration: A field holding the IConfiguration instance, used for accessing application configurations like the OpenAI API key.
  • PromptService Constructor: Initializes the service with application configuration data.
  • CreateChatCompletionAsync Method: Asynchronously sends a prompt to the OpenAI Chat API and returns the response.

Key Operations

Configuration Access: Retrieves the OpenAI API key from the application’s configuration settings, ensuring sensitive information is not hard-coded.

OpenAIAPI Initialization: Creates an instance of the OpenAIAPI client, using the API key for authentication. This client is the gateway to interacting with OpenAI services.

Chat Request: Constructs a ChatRequest object with specific parameters:

  • Model: Sets the model to Model.ChatGPTTurbo, indicating the use of a particular version of ChatGPT optimized for quick responses.
  • Temperature: Sets to 0.7f, controlling the randomness of the generated responses. A value of 0 would result in deterministic outputs, whereas higher values encourage more diverse outputs.
  • MaxTokens: Limits the response to 100 tokens, managing the length of the generated text.
  • Messages: Contains the user’s prompt, packaged in a ChatMessage object, indicating the message's role (from the user) and content.

API Request: Sends the chat request asynchronously to OpenAI’s API and awaits the response. This is done using api.Chat.CreateChatCompletionAsync, demonstrating the async-await pattern for non-blocking I/O operations.

Response Processing: Extracts the chat response from the first item in the result.Choices array, specifically the content of the message. This assumes the API call was successful and that a response was received.

Return: The method returns the content of the chat response, which is the AI-generated text based on the input prompt.

The PromptService class provides a clear example of how to structure code for calling OpenAI's Chat API within a .NET application, leveraging the OpenAI .NET library. It showcases essential practices like using configuration for API keys, asynchronous programming for API calls, and structured access to API responses. This code snippet can serve as a foundation for developing applications that require interaction with AI-powered text generation services.

2.2.2 OpenAI ChatGPT Completion Endpoint

Create an endpoint in your Web API that accepts requests from your Angular frontend and forwards them to the OpenAI API.

Here is the source code listing of the PromptController.cs

using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
using TalentManagementApi.Application.Features.Prompts.Queries.GetPrompts;
using TalentManagementApi.Application.Interfaces;

namespace TalentManagementApi.WebApi.Controllers.v1
{
    public class PromptController : BaseApiController
    {
        private readonly IPromptService _promptService;

        public PromptController(IPromptService promptService)
        {
            _promptService = promptService;
        }

        [HttpGet]
        public async Task<IActionResult> CreateChatCompletionAsync([FromQuery] string prompt)
        {
            return Ok(await Mediator.Send(new GetPromptQuery { Prompt = prompt }));
        }
    }
}

The provided code is an example of a controller in an ASP.NET Core Web API project, specifically designed for handling requests related to generating chat completions, presumably by interfacing with an AI service like OpenAI’s GPT models. Let’s break down the key components of this code:

Namespace Imports:

  • Microsoft.AspNetCore.Mvc: Essential for creating controllers in ASP.NET Core. It provides the base controller class, result types, and attributes for routing and model binding.
  • System.Threading.Tasks: Enables asynchronous programming, allowing for non-blocking I/O operations.
  • TalentManagementApi.Application.Features.Prompts.Queries.GetPrompts: Likely contains a CQRS query object that represents a request to get prompts from the application's domain logic.
  • TalentManagementApi.Application.Interfaces: Contains interfaces for the application's service layer, including IPromptService which the controller depends on to process prompts.

Controller Class:

  • PromptController inherits from BaseApiController, which might provide shared functionality across all API controllers in the application, such as error handling, response formatting, or common dependencies.
  • It’s part of a versioned API structure (v1), indicating the API's version and facilitating future updates with minimal impact on existing clients.

Dependencies:

  • A private readonly field _promptService of type IPromptService is declared, following dependency inversion principles. This service is responsible for the business logic related to handling prompts.
  • The controller’s constructor accepts an IPromptService instance, which is provided through dependency injection. ASP.NET Core's built-in DI container will resolve and inject this dependency at runtime.

Action Method:

  • CreateChatCompletionAsync: An asynchronous action method that handles HTTP GET requests. Despite the name implying creation, this method is designed to fetch data based on the provided prompt, which is more typical of a GET operation in RESTful APIs.
  • [HttpGet] attribute without a route template indicates that this method will respond to GET requests made to the controller's base route.
  • [FromQuery] string prompt: The method expects a prompt parameter from the query string of the request URL. This parameter is passed to the business logic to generate a chat completion.
  • The method asynchronously sends a GetPromptQuery object, containing the prompt, to some mediator service. This is indicative of the CQRS (Command Query Responsibility Segregation) pattern, where GetPromptQuery represents a query (read operation) in this architecture.
  • await Mediator.Send(...): This line asynchronously waits for the result of processing the GetPromptQuery. The Mediator is likely a part of the application's CQRS infrastructure, responsible for routing the query to its appropriate handler and awaiting the result.
  • return Ok(...);: Wraps the result in an OkObjectResult, which is an HTTP 200 OK response, and returns it to the client. This encapsulates the chat completion data generated based on the input prompt.

This controller is anl example of an ASP.NET Core API controller designed following modern application architecture principles, including dependency injection, asynchronous programming, and CQRS. It serves as an entry point for clients to interact with the application’s AI-based chat completion features by sending prompts and receiving generated responses.

2.2.3 Implementing CQRS (Command Query Responsibility Segregation)

In the previous section, the GetPromptQuery class was utilized in the controller, following the Mediator pattern. Let’s review the implementation of GetPromptQueryclass. Below is its source code listing in GetPromptQuery.cs

using MediatR;
using System.Threading;
using System.Threading.Tasks;
using TalentManagementApi.Application.Interfaces;
using TalentManagementApi.Application.Wrappers;

namespace TalentManagementApi.Application.Features.Prompts.Queries.GetPrompts
{
    /// <summary>
    /// GetPromptQuery - handles media IRequest BaseRequestParameter
    /// </summary>
    public class GetPromptQuery : IRequest<Response<GetPromptViewModel>>
    {
        public string Prompt { get; set; }

        public class GetPromptQueryHandler : IRequestHandler<GetPromptQuery, Response<GetPromptViewModel>>
        {
            private readonly IPromptService _promptService;

            /// <summary>
            /// Constructor for GetPromptQueryHandler class.
            /// </summary>
            /// <param name="repository">IPromptRepositoryAsync object.</param>
            /// <param name="modelHelper">IModelHelper object.</param>
            /// <returns>GetPromptQueryHandler object.</returns>
            public GetPromptQueryHandler(IPromptService repository)
            {
                _promptService = repository;
            }

            /// <summary>
            /// Handles the GetPromptQuery request and returns a PagedResponse containing the requested data.
            /// </summary>
            /// <param name="request">The GetPromptQuery request.</param>
            /// <param name="cancellationToken">The cancellation token.</param>
            /// <returns>A PagedResponse containing the requested data.</returns>
            public async Task<Response<GetPromptViewModel>> Handle(GetPromptQuery request, CancellationToken cancellationToken)
            {
                var completion = await _promptService.CreateChatCompletionAsync(request.Prompt);
                var response = new GetPromptViewModel();
                response.Completion = completion;
                return new Response<GetPromptViewModel>(response);
            }
        }
    }
}

This C# code snippet utilizes the MediatR library for implementing the CQRS (Command Query Responsibility Segregation) and Mediator patterns. It defines a query operation to retrieve a prompt’s completion from an AI service, OpenAI’s ChatGPT, abstracted through a service layer. Let’s break down the main components and their roles in the application.

Namespaces

  • MediatR: A library that implements the Mediator pattern, allowing for decoupling of in-process messaging by sending requests to handlers, typically used for organizing business logic in CQRS architectures.
  • System.Threading, System.Threading.Tasks: Namespaces for supporting asynchronous programming, enabling non-blocking operations and concurrent execution.
  • TalentManagementApi.Application.Interfaces: Contains interfaces that define contracts for services and repositories within the application, ensuring decoupling and adherence to SOLID principles.
  • TalentManagementApi.Application.Wrappers: Contains wrapper classes used for standardizing responses from the application layer, such as encapsulating results in a common response format.

GetPromptQuery Class

  • Implements IRequest<Response<GetPromptViewModel>>. This setup indicates that GetPromptQuery is a request object capable of carrying input parameters (in this case, a Prompt) for MediatR to handle.
  • The Prompt property is what the application user wants to get a completion for from the AI service.

GetPromptQueryHandler Class

  • Implements IRequestHandler<GetPromptQuery, Response<GetPromptViewModel>>, indicating it is the handler responsible for processing GetPromptQuery requests and returning a Response<GetPromptViewModel>.
  • Contains a constructor that accepts an IPromptService implementation, which abstracts the logic for interacting with the underlying AI or chat service. This service is responsible for generating chat completions based on the prompt.

Constructor

  • The GetPromptQueryHandler constructor injects an IPromptService. This follows the Dependency Injection (DI) pattern, promoting loose coupling and making the code more maintainable and testable.

Handle Method

  • The Handle method is an asynchronous operation that takes a GetPromptQuery and a CancellationToken as parameters. This method is where the logic to process the query and generate a response is implemented.
  • It calls _promptService.CreateChatCompletionAsync(request.Prompt), asynchronously waiting for the chat completion result from the AI service.
  • Constructs a GetPromptViewModel and sets its Completion property with the result from the AI service.
  • Wraps the GetPromptViewModel in a Response<T> object, standardizing the response format for the client consuming this API or service.

This code snippet showcases an application of the CQRS and Mediator patterns in a .NET Core application using MediatR. It abstracts the process of fetching AI-generated text completions into a query handled by a specific handler, promoting separation of concerns and clean architecture principles. The GetPromptQuery represents the query with its input, while the GetPromptQueryHandler encapsulates the logic to fulfill that query, interfacing with services that interact with AI models to generate responses based on prompts.

Source Code

The source code for this blog post is available in the feature branch ChatGPT on GitHub.

You can download and run the project on localhost. Be sure to update the OpenAI key in the application.json. Once you have the key update, you can run the project and test the prompt via Swagger. See below for an example screenshot of the response for the prompt “What is ChatGPT?”.

Frequently Asked Questions

Question 1: How to change the model such as ChatGPT 3 to 4? You can switch the model via the PromptService. Refer to the screenshot below for more details. For more information, see https://github.com/OkGoDoIt/OpenAI-API-dotnet/blob/master/README.md

Question 2: How to view the token count for both prompt and completion? You can see token usage via chat.MostRecentApiResult.Usage.PromptTokens and related properties. Enable debug mode and check the result under “Usage”. Below is a screenshot example for the question “What is ChatGPT?”, showing a total of 89 tokens for both Completion and Prompt.

Question 3: What is temperature in OpenAI API? See example code “Temperature = 0.7f” on line 29 in the screenshot above. The temperature parameter controls the randomness of the output. A lower temperature (closer to 0) makes the model’s responses more predictable and conservative. On the flip side, a higher temperature (closer to 1) increases the diversity of the responses, making them more varied and sometimes more creative. It’s a handy knob to tweak depending on whether you’re looking for more stability or creativity in the responses you’re generating.

Question 4: What is token? ChatGPT is like an arcade game. You use tokens to play. Each word ChatGPT says costs a bit of a token. Ask simple things, use fewer tokens. Ask big stories, use more tokens. When tokens run out, you need more to keep playing!

Question 5: Can I call ChatGPT directly from Angular? Technically, the answer is yes. However, in doing so, you will expose the API Key in the Angular client application. It’s akin to posting your credit card number on Facebook and hoping nobody finds it.

Question 5: What is the price of the ChatGPT token? Multiple models, each with different capabilities and price points. Prices can be viewed in units of either per 1M or 1K tokens. You can think of tokens as pieces of words, where 1,000 tokens is about 750 words. This paragraph is 35 tokens. See this pricing link. Here is an example of pricing for GPT-3-Turbo

Summary

I have used the ChatGPT extension in Visual Studio for coding productivity. I prepaid $20 for ChatGPT tokens, which lasts about 1 year. For more information, see Explore ChatGPT extension in Visual Studio 2022. — Fuji Nguyen

In this blog post, I’ve walked you through the intricacies of integrating ChatGPT with your backend WebAPI. Let’s delve deeper into its potential applications beyond the scope of our current discussion. Imagine seamlessly integrating ChatGPT with your back office products, enhancing productivity and streamlining workflows. For instance, you could employ ChatGPT to assist customer service representatives in responding to inquiries more efficiently or leverage its capabilities to automate routine tasks within your administrative systems.

However, it’s crucial to keep a watchful eye on the usage costs associated with OpenAPI. As you integrate ChatGPT into your backend infrastructure, be mindful of the potential impact on your overall operational expenses. By monitoring and optimizing usage, you can ensure cost-effectiveness while maximizing the benefits of this powerful integration.

ChatGPT
Csharp
Webapi
Recommended from ReadMedium