背景
前几天,公司的同事们一起吃了个饭,餐桌上大家聊到大模型的落地场景。我个人在去年已经利用百度千帆平台写过案例,并发过博客(传送门👉:利用文心千帆打造一个属于自己的小师爷),只不过那时候没有再继续深入真正做好大模型在项目上的落地。
这次刚刚开发完的一个考试系统,里面正好有一个落地场景,利用 AI 的能力来生成题库的解析内容,可以大幅提高效率。
准备工作
因为公司的云服务商是腾讯,所以我这次使用的是腾讯混元大模型,在开始集成工作前,要先去腾讯云的控制台开通相关的服务。
事实上,所有集成任何第三方云端大模型的步骤都差不多,这一步我就不多说了,参照文档操作即可(传送门👉:腾讯混元大模型)。
需要注意的是,新申请的服务,可以领取一定的免费额度,而且 lite 模型目前是一直免费的,这点和各家也都差不多。在这里插入图片描述
集成
接入方式
大模型集成到项目的方式有很多,有的可能就是一个模块,有的可能是一个独立服务,总之就是根据实际的项目情况,接入的方式,呈现的结果都是不一样的。
我这边把大模型在系统里的定位就是一个随叫随到的智能助手,在整个后台管理工作的场景中,都可以方便的和大模型交互。
业务代码之前
配置
"AiConfigs": [ { "IsOpenaiApi": "yes", "Model": "moonshotai", "SecretKey": "", "SecretId": "", "AppId": "", "ApiKey": "sk-{xxxxxxxxxx}" }, { "IsOpenaiApi": "no", "Model": "hunyuan", "SecretId": "{xxx}", "SecretKey": "{xxx}", "AppId": "xxx", "ApiKey": "" }
复制代码
这里,我是把请求模型的密钥参数放到配置文件里了,生产环境中更推荐的做法是把密钥参数放到系统的环境变量里更加安全。
这里这样配置是为以后扩展做准备,此次集成的是混元大模型的 sdk,申请的方式是按 sdk 的方式申请,云服务商一般会给到一组密钥对,包括 secretid,secretkey 等,而类似 openai api 的方式,是只有一个 appkey,所以我这里是这样定义的参数,方便后续反序列化。
其实目前各家基本都支持 openapi 的方式了,但因为我这里只有公司腾讯云的子账号,申请 openapi 风格的 key 需要主账号,而且要主账号提供 mfa 验证码,我有点社恐,没好意思找持有人申请,就暂时没用~
但我还是申请了一个个人的月之暗面(moonshot)账户(传送门👉:Moonshot AI),并获得了一个 openapi 风格的 apikey,确保两种接入方式都支持。
定义对照模型
public class AiConfig{ [JsonProperty("IsOpenaiApi")] public string IsOpenaiApi { get; set; }
[JsonProperty("Model")] public string Model { get; set; } [JsonProperty("SecretKey")] public string SecretKey { get; set; } [JsonProperty("SecretId")] public string SecretId { get; set; } [JsonProperty("AppId")] public string AppId { get; set; }
/// <summary> /// IsOpenaiApi为yes时,ApiKey为必填项 /// </summary> [JsonProperty("ApiKey")] public string ApiKey { get; set; } //public string Token { get; set; }}
复制代码
这里的就是参照配置文件的格式,创建对照模型,方便后续的序列化工作。
创建工厂类
public class AiConfigFactory{ private readonly List<AiConfig> _aiConfigs;
public AiConfigFactory(List<AiConfig> aiConfigs) { _aiConfigs = aiConfigs; }
public AiConfig GetConfigByModel(string Model) { return _aiConfigs.FirstOrDefault(config => config.Model.Equals(Model, StringComparison.OrdinalIgnoreCase)); }}
复制代码
注入服务
private static void ConfigureAi(this IServiceCollection services, IConfiguration configuration){ var aiConfigs = new List<AiConfig>(); configuration.GetSection("AiConfigs").Bind(aiConfigs); // 注册工厂为单例服务 services.AddSingleton(new AiConfigFactory(aiConfigs)); //...其他配置}...//这里我是把每个中间件都分离成一个小模块,配置完成后,在入口处统一注册//像这样builder.Services.ConfigureAi(_configuration);
复制代码
其他工作
因为是要全场景的运行 ai 助手,得充分发挥系统基础设施的能力,所以这里还有根据情况注入 Redis,消息队列,数据库等中间件,这部分代码都是很常见的,不再赘述。
接入混元 SDK
准备工作就绪以后,就可以引入混元 sdk 了,这部分就是标准的调参工作,非常简单,大家可以跳过本节,直接参照混元的接口文档(传送门👉:混元大模型),然后按照自己喜欢的放方式完成对接.
我这里简单介绍下
安装必要 sdk
安装混元 sdk,可以直接在 vs 的 nuget 包管理器搜索 TencentCloudSDK.Hunyuan 关键字安装,或者直接通过下面方式
# 命令行dotnet add package TencentCloudSDK.Hunyuan# 或者vs里打开程序包管理器控制台Install-Package TencentCloudSDK.Hunyuan
复制代码
创建控制器
这里我只给出几个关键的业务代码
//chat接口[HttpPost,ValidateAntiForgeryToken]public async Task<IActionResult> SimpleChat(ChatModel chatModel){ if (string.IsNullOrWhiteSpace(chatModel.prompt)) return Json(_resp.error("无输入")); try { if(string.IsNullOrEmpty(chatModel.admin)) chatModel.admin = adminId; await _capPublisher.PublishAsync(CapConsts.PREFIX + "GetHunyuanResponse", chatModel); return Json(_resp.success(0, "ok")); } catch (Exception e) { Assistant.Logger.Error(e); return Json(_resp.error("获取响应失败," + e.Message)); }}
[HttpGet("airesp")]public async Task AiResponseSse(string admin){ Response.Headers["Content-Type"] = "text/event-stream"; Response.Headers["Cache-Control"] = "no-cache"; if (HttpContext.Request.Protocol.StartsWith("HTTP/1.1")) { Response.Headers["Connection"] = "keep-alive"; } try { if (string.IsNullOrEmpty(admin)) admin = adminId; while (true) { await Task.Delay(50); // 从通道中读取消息(这里等待消息到来) if (!await _redisCachingProvider.KeyExistsAsync("cacheId" + admin)) return; var message = await _redisCachingProvider.LPopAsync<string>("cacheId" + admin); if (string.IsNullOrEmpty(message)) continue; // 按照SSE协议格式发送数据到客户端 await Response.WriteAsync($"data:{message}\n\n"); await Response.Body.FlushAsync();
}
} catch (Exception ex) { // 可以记录异常等处理 Console.WriteLine(ex.Message); }}
[NonAction][CapSubscribe(CapConsts.PREFIX + "GetHunyuanResponse")]public async Task GetHunyuanResponse(ChatModel chatModel){ try { Assistant.Logger.Warning("开始请求混元接口"); var commonParams = new HunyuanCommonParams(); // 实例化一个client选项,可选的,没有特殊需求可以跳过 ClientProfile clientProfile = new ClientProfile(); // 实例化一个http选项,可选的,没有特殊需求可以跳过 HttpProfile httpProfile = new HttpProfile(); httpProfile.Endpoint = commonParams.Endpoint; clientProfile.HttpProfile = httpProfile;
// 实例化要请求产品的client对象,clientProfile是可选的 HunyuanClient client = new HunyuanClient(_cred, commonParams.Region, clientProfile); // 实例化一个请求对象,每个接口都会对应一个request对象 ChatCompletionsRequest req = new ChatCompletionsRequest(); req.Model = HunyuanModels.Lite; if (!string.IsNullOrWhiteSpace(chatModel.model)) req.Model = chatModel.finalModel; Message message1 = new Message(); message1.Role = "user"; message1.Content = chatModel.prompt; req.Messages = [message1]; req.Stream = true; ChatCompletionsResponse resp = await client.ChatCompletions(req); // 输出json格式的字符串回包 if (resp.IsStream) { // 流式响应 foreach (var e in resp) { Assistant.Logger.Debug(e.Data); await _redisCachingProvider.RPushAsync("cacheId" + chatModel.admin, new List<string>() { e.Data }); } } else { // 非流式响应 Assistant.Logger.Debug(JsonConvert.SerializeObject(resp)); } } catch (Exception ex) { Assistant.Logger.Error(ex); } finally { await _redisCachingProvider.KeyExpireAsync("cacheId" + chatModel.admin, 600); }}
复制代码
简单解释下,这段代码,主要分 3 个逻辑来处理云端返回的消息,
第一步是前端通过接口,把结构化的消息提交到服务端,也就是 SimpleChat 接口
第二步,SimpleChat 接收到消息后,立刻返回,并发布任务让服务端后台开始和混元的服务端,获取相应结果,并暂存到 Redis 队列里
第三步是服务端通过 SSE 链接的方式把 Redis 队列里的消息,推到本地客户端
事实上,不暂存直接推也是可以的,我这里因为有其他业务交叉,所以这样处理了一下。
前端代码就不在展示了,按照喜好实现即可
效果
总结
目前来说只是初步接入到了系统,样式上还些问题需要处理,而且目前只支持文字模式,不支持图片,也没有完全和业务绑定,后续会把一些常见的场景,比如题目解析,智能分析用户的考卷等场景和 ai 深度结合。
好了,基本就这样了
评论