65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Angular、ASP.NET API 和 Azure 构建 OpenAI 聊天应用程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (3投票s)

2023年10月13日

CPOL

4分钟阅读

viewsIcon

5140

如何使用 Angular、ASP.NET API 和 Azure 构建 OpenAI 聊天应用程序

在这个项目中,我将向您展示如何使用 Angular、ASP.NET API 和 Azure 构建 OpenAI 聊天应用程序。

有关此项目的最新更新,请访问DotNetLead 上的原始文章

我们将介绍

  • 在 Azure 上所需的设置
  • 在 ASP.NET API 上所需的代码
  • 在 Angular 前端所需的代码

Azure OpenAI 设置

首先是创建如下所示的 Azure OpenAI 资源。 它是一个默认情况下在 Azure 中不可用的资源,您必须申请访问权限。 微软审核您的请求后(需要几天时间),您就可以创建 Azure OpenAI 资源。 您可以在此处申请访问权限。

创建 Azure OpenAI 资源后,转到模型部署部分以选择要使用的 OpenAI 模型,如下所示。 Azure 会将您带到 Azure AI Studio,您还可以在其网站上测试聊天功能。

通过选择您喜欢的 ChatGpt 版本来创建部署,如下所示。 在发布时,我选择了 gpt-35-turbo。

创建模型后,记下部署的名称,如下所示。 您将需要部署的名称来构建 API。

返回 Azure OpenAI 资源并记下构建 API 所需的 EndPoint 和 Key,如下所示。 您只需要 API 中的 1 个密钥。

ASP.NET API 设置

从 Azure OpenAI 获取聊天响应的代码如下所示。 首先,创建 OpenAIClient,然后创建 ChatCompletionsOptions 以准备查询,即用户聊天输入,然后调用 GetChatCompletionsAsync 方法以从 Azure OpenAI 获取响应。 在控制器方法中,我们只返回 OpenAI 响应的内容。

[HttpGet]
        public async Task<ChatMsg> GetResponse(string query)
        {
            OpenAIClient client = new OpenAIClient(
                new Uri(config["AzureEndPoint"]),
                new AzureKeyCredential(config["AzureKey"])
            );

            ChatCompletionsOptions option = new ChatCompletionsOptions { };
            option.Messages.Add(new ChatMessage(ChatRole.User, query));

            Response<ChatCompletions> response = await client.GetChatCompletionsAsync(
                    config["DeploymentName"],
                    option
                );
            return new ChatMsg { Message = response.Value.Choices[0].Message.Content };
        }

ReadMe.txt 文件中所述,您将需要以下三个配置,使用 .NET Secret Manager,以便配置不会存储在纯文本文件中。

AzureEndPoint
AzureKey
DeploymentName

有关如何使用 .NET Secret Manager 的说明,请查看使用 Secret Manager 和 Azure Key Vault 保护 ASP.NET Core 配置

使用 Angular 构建前端聊天 UI

构建聊天前端需要多个属性

  1. 聊天窗口应保持静态。 换句话说,它不应因聊天窗口中的内容而扩展或收缩,并且应保持在同一位置。 当文本超出窗口空间时,应允许滚动。
  2. 聊天消息的显示顺序应从下到上,最新的用户输入或响应从最底部开始。

为了使聊天窗口保持静态并允许随着内容的扩展而滚动,请声明一个固定位置的 flexbox 容器(如下面的代码中所示的 chat-container 类),并且第一个 flexbox 项目将只是另一个 flexbox(如下面的代码中所示的 messages 类),它具有 flex-direction 作为 column-reverse 以从下到上显示项目,overflow-y 作为 scroll 用于垂直滚动,overflow-x 作为 hidden 以隐藏水平滚动。

用户输入和 OpenAI 响应需要以不同的方式显示,这可以通过声明两个不同的类来完成,分别是用于显示 OpenAI 响应的 bot-message 类和用于显示用户输入的用户消息类。 请注意,user-message 类具有 align-self 作为 flex-end,因此它与右侧对齐。

以下是 HTML

<div class="chat-container">
        <div class="messages">   
            <div *ngFor="let msg of chatHistory"
                [ngClass]="{'user-message': msg.type == 1, 
                            'bot-message': msg.type == 2}" >
                <p>{{msg.message}}</p>
            </div>
        </div>
        ...

以下是 scss

.chat-container {
    position: fixed;
    top: 4rem;
    bottom: 4rem;
    display: flex;
    flex-direction: column;
    width: 42rem;
    box-shadow: $shadow;
    .messages {
        flex: 1;
        display: flex;
        flex-direction: column-reverse; //default scroll to the bottom
        overflow-y: scroll;
        overflow-x: hidden;             //hide bottom scroll bar
        padding: 1rem 1.5rem;
        .bot-message {
            display: flex;
            align-items: center;
            p {
                @include message-item($bot-color);
            }                
        }
        .user-message {
            display: flex;
            align-items: center;                
            align-self: flex-end;   //move to the right
            p {
                @include message-item($user-color);
            }
        }
    }
    ...

每个消息将具有消息类型 1(用于用户输入)或 2(用于 OpenAI 响应),如上面的 html 中所示。 消息的定义如下所示

export enum MessageType {
    Send = 1,
    Receive = 2
}

export interface IChatMsg {
    type: MessageType,
    message: string
}

以下是组件的代码

export class ChatComponent implements OnInit {

    frmQuery: FormGroup;
    chatHistory: IChatMsg[];

    constructor(private chatSvc: ChatService,
        private fb: FormBuilder) { }

    ngOnInit(): void {
        this.frmQuery = this.fb.group({
            txtQuery: [null]
        });

        this.chatSvc.chat(null)
            .subscribe(aa => this.chatHistory = aa);
    }

    submitQuery() {
        //ignore if no query given
        if (!this.frmQuery.get("txtQuery").value)
            return;
        let queryMessage: IChatMsg = {
            type: MessageType.Send,
            message: this.frmQuery.get("txtQuery").value
        };
        this.chatSvc.chat(queryMessage);
        //blank out textbox for next user input
        this.frmQuery.get("txtQuery").setValue(null);
    }
}

接下来,我们需要在最底部显示最新的消息,并将之前的消息向上推送。 尽管 CSS 定义将从下到上显示消息,但消息的顺序是从上到下,需要反转。 此外,每次调用 API 仅仅是一个请求和一个响应,并且需要保留聊天记录。 这些可以通过声明一个数组来存储聊天历史记录来解决,并使用 reverse 方法将聊天历史记录作为 Observable 返回。 聊天服务的代码如下所示

export class ChatService {
    readonly url = this.appConfig.get('apiChat');

    private chatHistory = new BehaviorSubject<IChatMsg[] | null>(null);
    chatHistory$ = this.chatHistory.asObservable();

    //stores the chat history
    chatHistoryRecord : IChatMsg[] = [];

    constructor(private http:HttpClient,
                private appConfig:AppConfigService){}

    chat(queryMessage: IChatMsg): Observable<IChatMsg[]>{
        if (!queryMessage)
            return this.chatHistory$;
        this.chatHistoryRecord.push(queryMessage);
        this.chatHistory.next(this.chatHistoryRecord.slice().reverse());
        //get response
        let queryUrl = this.url + "?query=" + queryMessage.message;
        this.http.get<IChatMsg>(queryUrl).pipe()
            .subscribe(result=>{
                if (result){
                    const receiveMsg : IChatMsg = 
                        { type: MessageType.Receive, message : result.message };
                    this.chatHistoryRecord.push(receiveMsg);
                    this.chatHistory.next(this.chatHistoryRecord.slice().reverse());
                }                    
            }
        )
        return this.chatHistory$;
    }
}

就这样,希望您会发现这个项目有用。

© . All rights reserved.