千千梦丶琪 发表于 2025-3-15 18:50:15

虚幻引擎实现流式Http接口并接入百度文心一言大模型(仅适用于UE5.2之后的版

在UE5.2(不包罗5.2)之后,UE的HTTP模块已经实现了对流式接口的支持,我也是偶尔间在写流式接接口时发现的,不过没有发现这方面的资料,现在来记录一下,实现流式接口接收百度的文心一言大模型。
注意:5.2之前的版本貌似是没有实现的,5.2之前的版本大概必要自己去扩展引擎的源码大概借助第三方库。
引入模块

起首在代码中引入对应模块,假如是在插件中实现,就在插件的Build.cs文件引入,假如在项目的Source中实现,就在项目的Build.cs文件引入,我这里是插件,以是在插件中引入
https://i-blog.csdnimg.cn/direct/42e8274af57b4865a40959cea25a75bc.png插件实现

我们在插件中创建名为AIFunction的文件,文件的类是继承UBlueprintAsyncActionBase
https://i-blog.csdnimg.cn/direct/c30a185f76434557ad35b46f2c13483d.png
在头文件中,起首我们创建一个布局体,用于我们传入的请求参数,
USTRUCT(BlueprintType)
struct FAIRequest
{
        GENERATED_BODY()


        UPROPERTY(BlueprintReadWrite)
        FString URL;
        UPROPERTY(BlueprintReadWrite)
        TArray<FString> Message;
        UPROPERTY(BlueprintReadWrite)
        FString APIKey;
        UPROPERTY(BlueprintReadWrite)
        FString APISecret;
        UPROPERTY(BlueprintReadWrite)
        FString AppID;
        UPROPERTY(BlueprintReadWrite)
        FString access_token;
        UPROPERTY(BlueprintReadWrite)
        bool stream;
}; 接着声明两个枚举范例,用来表示我们请求的AI范例与返回范例,用于蓝图中的多输出节点
UENUM(BlueprintType)
enum class EAIType: uint8
{
Baidu UMETA(DisplayName = "百度"),
        Tencent UMETA(DisplayName = "腾讯"),
        Ali UMETA(DisplayName = "阿里"),
        Dianxin UMETA(DisplayName = "电信"),
        XunFei UMETA(DisplayName = "讯飞")
};
UENUM()
enum class EResponseType: uint8
{
        Completed,
        Failed,
        Stream
}; 声明一个多播代理,用于在http返回时将数据传出
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FAIReceive,FString, Response); 接着我们声明一个SendAI函数,GetBaiduToken函数,BaiduAI函数,来实现调用百度的接口
UCLASS()
class PAASAIMODULE_API UAIFunction: public UBlueprintAsyncActionBase
{
        GENERATED_BODY()


public:


        UPROPERTY(BlueprintAssignable)
        FAIReceive OnCompleted;//请求完成时
        UPROPERTY(BlueprintAssignable)
        FAIReceive OnFailed;    //请求失败时
        UPROPERTY(BlueprintAssignable)
        FAIReceive OnStream;   //流式请求时


        FHttpRequestStreamDelegate StreamDelegate; //用于http流式请求的代理


       
        UFUNCTION(BlueprintCallable,meta=( BlueprintInternalUseOnly="true" ))
        staticUAIFunction* SendAI( EAIType Type,const FAIRequest& Request);
        UFUNCTION(BlueprintCallable,meta=( BlueprintInternalUseOnly="true" ))
        staticUAIFunction* GetBaiduToken(const FString& apikey, const FString& secretkey);
       
protected:
       
        //自动生成token待完成
        void BaiDuAI(const FAIRequest& Request);


        void DianXinAI(const FAIRequest& Request);
}; 我这里使用的异步蓝图节点,非壅闭式的请求http。对于蓝图的异步节点,大家可以自行了解。
完备的.h文件内容为下
// Fill out your copyright notice in the Description page of Project Settings.#pragma once#include "CoreMinimal.h"#include "Interfaces/IHttpRequest.h"#include "Kismet/BlueprintFunctionLibrary.h"#include "Kismet/BlueprintAsyncActionBase.h"#include "AIFunction.generated.h"USTRUCT(BlueprintType)
struct FAIRequest
{
        GENERATED_BODY()


        UPROPERTY(BlueprintReadWrite)
        FString URL;
        UPROPERTY(BlueprintReadWrite)
        TArray<FString> Message;
        UPROPERTY(BlueprintReadWrite)
        FString APIKey;
        UPROPERTY(BlueprintReadWrite)
        FString APISecret;
        UPROPERTY(BlueprintReadWrite)
        FString AppID;
        UPROPERTY(BlueprintReadWrite)
        FString access_token;
        UPROPERTY(BlueprintReadWrite)
        bool stream;
};UENUM()enum class EResponseType: uint8{        Completed,        Failed,        Stream};UENUM(BlueprintType)enum class EAIType: uint8{        Baidu UMETA(DisplayName = "百度"),        Tencent UMETA(DisplayName = "腾讯"),        Ali UMETA(DisplayName = "阿里"),        Dianxin UMETA(DisplayName = "电信"),        XunFei UMETA(DisplayName = "讯飞")        };/** **/DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FAIReceive,FString, Response);UCLASS()class PAASAIMODULE_API UAIFunction: public UBlueprintAsyncActionBase{        GENERATED_BODY()public:    UPROPERTY(BlueprintAssignable)        FAIReceive OnCompleted;//请求完成时        UPROPERTY(BlueprintAssignable)        FAIReceive OnFailed;    //请求失败时        UPROPERTY(BlueprintAssignable)        FAIReceive OnStream;   //流式请求时        FHttpRequestStreamDelegate StreamDelegate;//用于http流式请求的代理                UFUNCTION(BlueprintCallable,meta=( BlueprintInternalUseOnly="true" ))        staticUAIFunction* SendAI( EAIType Type,const FAIRequest& Request);        UFUNCTION(BlueprintCallable,meta=( BlueprintInternalUseOnly="true" ))        staticUAIFunction* GetBaiduToken(const FString& apikey, const FString& secretkey);        protected:                //自动生成token待完成        void BaiDuAI(const FAIRequest& Request);}; 接着我们在cpp文件中实现相应的函数
//函数根据AI类型去调用相应的AI请求
UAIFunction* UAIFunction::SendAI( EAIType Type,const FAIRequest& Request)
{
        UAIFunction* AIFunction = NewObject<UAIFunction>();
        switch (Type)
        {
        case EAIType::Baidu:
                AIFunction->BaiDuAI(Request);
                break;
        case EAIType::Ali:
                break;
        case EAIType::Tencent:
                break;
        case EAIType::Dianxin:
                break;
        default:
                break;
        }
       
        return AIFunction;
}
//获取Baidu的Token
UAIFunction* UAIFunction::GetBaiduToken(const FString& apikey, const FString& secretkey)
{
        UAIFunction* AIFunction = NewObject<UAIFunction>();
        TSharedRef<IHttpRequest, ESPMode::ThreadSafe> HttpRequest = FHttpModule::Get().CreateRequest();
        FString URL = FString::Printf(TEXT("https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=%s&client_secret=%s"),*apikey,*secretkey );
        HttpRequest->SetURL(URL);
        HttpRequest->SetVerb(TEXT("POST"));
        HttpRequest->SetHeader(TEXT("Content-Type"), TEXT("application/json"));
        HttpRequest->OnProcessRequestComplete().BindLambda(((FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
        {
                if (bWasSuccessful && Response.IsValid())
                {
                        FString ResponseContent =Response->GetContentAsString();
                        TSharedPtr<FJsonObject> ResultObj;
                        TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(ResponseContent);
                        FJsonSerializer::Deserialize(Reader,ResultObj);
                        FString DataString;
                        if (ResultObj->TryGetStringField(TEXT("access_token"),DataString))
                        {
                                AIFunction->OnCompleted.Broadcast(DataString);
                        }
                }
                else
                {
                        AIFunction->OnFailed.Broadcast(TEXT("失败"));
                }
               
        }));
        HttpRequest->ProcessRequest();
        return AIFunction;


}
//发送AI请求
void UAIFunction::BaiDuAI(const FAIRequest& Request)
{
        AddToRoot();
        TSharedRef<IHttpRequest, ESPMode::ThreadSafe> HttpRequest = FHttpModule::Get().CreateRequest();
        FString URL = FString::Printf(TEXT("%s?access_token=%s"), *Request.URL, *Request.access_token);
        HttpRequest->SetURL(URL);
        HttpRequest->SetVerb(TEXT("POST"));
        HttpRequest->SetHeader(TEXT("Content-Type"), TEXT("application/json"));
        FString OutJsonData;
        TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&OutJsonData);


        Writer->WriteObjectStart();                                                // JSON对象开始
        //Writer->WriteValue(L"category", InfoCategory);// 填充普通字段
        Writer->WriteArrayStart(L"messages");                        // Json 数组字段开始       
        for (FString str : Request.Message)
        {
                Writer->WriteObjectStart();
                Writer->WriteValue(L"role", L"user");                // 填充普通字段
                Writer->WriteValue(L"content", str);        // 填充普通字段
                Writer->WriteObjectEnd();
        }
        Writer->WriteArrayEnd();// Json 数组字段结束
        Writer->WriteValue(L"stream", Request.stream); //是否流式
        Writer->WriteObjectEnd();            //JSON对象结束
        Writer->Close();
        HttpRequest->SetContentAsString(OutJsonData); //设置流式请求代理


        if (Request.stream)
        {
                // 流式请求
                StreamDelegate.BindLambda([=,this](void* st,int64 length)
                {
                        if (length >0)
                        {
                                //处理数据
                                FString str = FString(UTF8_TO_TCHAR(static_cast<const char*>(st)));
                                str.RemoveAt(0,5); //去掉返回数据的前置字符,因为百度返回的数据前有一个data:的字符,无法解析
                                if (!str.IsEmpty())
                                {
                                        str.ReplaceInline(TEXT("\\n"),TEXT(""));
                                        str.RemoveSpacesInline();
                                        TSharedPtr<FJsonObject> ResultObj;
                                        TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(str);
                                        FString DataString;
                                        bool success =FJsonSerializer::Deserialize(Reader,ResultObj);
                                        FString Meaasge;
                                        if (!success)
                                        {
                                                return true;
                                        }
                                        if (ResultObj->TryGetStringField(TEXT("result"),Meaasge))
                                        {
                                                if (!Meaasge.IsEmpty())
                                                {
                                                        //Meaasge.RemoveFromStart(TEXT("\n")); 从开始删除第一个匹配的字符
                                                        this->OnStream.Broadcast(Meaasge);
                                                }
                                        }
                                        else
                                        {
                                                return true;
                                        }
                                }
                                return true;
                        }
                        return false;
                });
                HttpRequest->SetResponseBodyReceiveStreamDelegate(StreamDelegate);
        }
        HttpRequest->OnProcessRequestComplete().BindLambda(([=,this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
        {
                if (bWasSuccessful && Response.IsValid())
                {
                        this->StreamDelegate.Unbind();
                        FString ResponseContent =Response->GetContentAsString();
                        this->OnCompleted.Broadcast(ResponseContent);
                        RemoveFromRoot();
                }
               
        }));


        HttpRequest->ProcessRequest();
} 完成后,编译乐成后会有下面两个节点。
https://i-blog.csdnimg.cn/direct/1cfff96d59db4a67a567eed78190658b.png
注意:在选择了流式接口返回后,正常在请求Completed的时候是不会在返回数据的,也就是说Stream和Completed两个只会有一个有数据。
https://i-blog.csdnimg.cn/direct/80382e951c3f4c3db09717746b77ea8f.png
注:对于百度的模型,大家还是去看一下官方文档会对代码有更多的理解,而且必要去申请相应的账号。
文心一言API接入指南-百度开发者中心https://csdnimg.cn/release/blog_editor_html/release2.3.7/ckeditor/plugins/CsdnLink/icons/icon-default.png?t=O83Ahttps://developer.baidu.com/article/detail.html?id=1089328

开局逆风的才是主角。


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 虚幻引擎实现流式Http接口并接入百度文心一言大模型(仅适用于UE5.2之后的版