Skip to content

Commit 1ad36e9

Browse files
authored
Merge pull request #3008 from JeffreySu/Developer
Developer
2 parents ec15df4 + d721b11 commit 1ad36e9

File tree

14 files changed

+423
-32
lines changed

14 files changed

+423
-32
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,4 @@ node_modules
6363
yarn.lock
6464

6565
/Samples/Work/Senparc.Weixin.Sample.Work/App_Data/WeChat_Work
66+
/Samples/All/net8-mvc/Senparc.Weixin.Sample.Net8/appsettings.Development.json

Samples with AI/readme.md

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
内容将涵盖:
1010

11-
1. [ ] 微信公众号 Chat 机器人(文字)
11+
1. [X] 微信公众号 Chat 机器人(文字) - 已于 2024 年 5 月 25 日上线
1212
2. [ ] 微信公众号 Chat 机器人(图片)
1313
3. [ ] 微信公众号 Chat 机器人(多模态混合)
1414
4. [ ] 微信公众号带搜索功能的 Chat 机器人
@@ -21,4 +21,53 @@
2121

2222
AI 功能将整合在 [/Samples/All/net8-mvc](../Samples/All/net8-mvc/Senparc.Weixin.Sample.Net8/) 集成案例中。
2323

24-
更多说明将在对应功能上线后在本文档中补充。
24+
更多说明将在对应功能上线后在本文档中补充。
25+
26+
## 【微信公众号 Chat 机器人(文字)】开发说明
27+
28+
1. 使用常规步骤开发微信公众号
29+
2.`OnTextRequestAsync` 事件中,加入对进入 AI 对话状态的激活关键字(从节约 AI 用量和用户体验,以及公众号实际功能考虑,建议不要始终保持 AI 对话),如:
30+
31+
```
32+
.Keyword("AI", () => this.StartAIChatAsync().Result)
33+
```
34+
35+
> [查看代码](https://github.com/JeffreySu/WeiXinMPSDK/blob/f28a5995b3e5f01b3be384b5c7462324ec6f0886/Samples/All/Senparc.Weixin.Sample.CommonService/MessageHandlers/CustomMessageHandler/CustomMessageHandler.cs#L194-L194)
36+
37+
其中 `StartAIChatAsync()` 用于激活当前用户对话山下文的 AI 对话状态
38+
39+
> [查看代码](https://github.com/JeffreySu/WeiXinMPSDK/blob/f28a5995b3e5f01b3be384b5c7462324ec6f0886/Samples/All/Senparc.Weixin.Sample.CommonService/AI/MessageHandlers/CustomMessageHandler_AI.cs#L41-L41)
40+
41+
42+
3. 为了能够让系统优先判断当前是否在 AI 状态,需要在上述代码执行前,加入尝试 AI 对话的代码,如:
43+
44+
```
45+
var aiResponseMessage = await this.AIChatAsync(requestMessage);
46+
if (aiResponseMessage != null)
47+
{
48+
return aiResponseMessage;
49+
}
50+
```
51+
52+
> [查看代码](https://github.com/JeffreySu/WeiXinMPSDK/blob/f28a5995b3e5f01b3be384b5c7462324ec6f0886/Samples/All/Senparc.Weixin.Sample.CommonService/MessageHandlers/CustomMessageHandler/CustomMessageHandler.cs#L179-L179)
53+
54+
其中 `AIChatAsync()` 方法用于提供尝试向 AI 发送对话消息的业务逻辑(如果不在对话状态则返回 null,程序继续执行常规代码)
55+
56+
> [查看代码](https://github.com/JeffreySu/WeiXinMPSDK/blob/f28a5995b3e5f01b3be384b5c7462324ec6f0886/Samples/All/Senparc.Weixin.Sample.CommonService/AI/MessageHandlers/CustomMessageHandler_AI.cs#L41-L41)
57+
58+
4. 配置 AI 参数,请参考 `Senparc.AI 【开发过程】第一步:配置账号`,在 appsettings.json 文件中追加 ”SenparcAiSetting“ 节点([查看](https://github.com/Senparc/Senparc.AI/blob/main/README.md#%E7%AC%AC%E4%B8%80%E6%AD%A5%E9%85%8D%E7%BD%AE%E8%B4%A6%E5%8F%B7))(注意:通常只需设置其中一种平台的配置)
59+
60+
5. 引用 Senparc.AI.Kernel 包,并在启动代码中激活 Senparc.AI:
61+
62+
```
63+
services.AddSenparcAI(Configuration) // 注册 AI
64+
```
65+
66+
> [查看代码](https://github.com/JeffreySu/WeiXinMPSDK/blob/f28a5995b3e5f01b3be384b5c7462324ec6f0886/Samples/All/net8-mvc/Senparc.Weixin.Sample.Net8/Startup.cs#L88-L88)
67+
68+
```
69+
registerService.UseSenparcAI();// 启用 AI
70+
```
71+
72+
> [查看代码](https://github.com/JeffreySu/WeiXinMPSDK/blob/f28a5995b3e5f01b3be384b5c7462324ec6f0886/Samples/All/net8-mvc/Senparc.Weixin.Sample.Net8/Startup.cs#L452-L452)
73+
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*----------------------------------------------------------------
2+
Copyright (C) 2024 Senparc
3+
4+
文件名:ChatStore.cs
5+
文件功能描述:按个人信息隔离的 Chat 缓存
6+
7+
8+
创建标识:Senparc - 20240524
9+
10+
----------------------------------------------------------------*/
11+
12+
using Senparc.AI.Kernel.Handlers;
13+
14+
namespace Senparc.Weixin.MP.Sample.CommonService.AI.MessageHandlers
15+
{
16+
/// <summary>
17+
/// 按个人信息隔离的 Chat 缓存
18+
/// </summary>
19+
public class ChatStore
20+
{
21+
public ChatStatus Status { get; set; }
22+
23+
public string History { get; set; }
24+
}
25+
26+
/// <summary>
27+
/// 聊天状态
28+
/// </summary>
29+
public enum ChatStatus
30+
{
31+
/// <summary>
32+
/// 默认状态(可能是转换失败)
33+
/// </summary>
34+
None,
35+
/// <summary>
36+
/// 聊天中
37+
/// </summary>
38+
Chat,
39+
/// <summary>
40+
/// 暂停
41+
/// </summary>
42+
Paused
43+
}
44+
}
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
/*----------------------------------------------------------------
2+
Copyright (C) 2024 Senparc
3+
4+
文件名:CustomMessageHandler_AI.cs
5+
文件功能描述:自定义MessageHandler(AI 方法)
6+
7+
8+
创建标识:Senparc - 20240524
9+
10+
----------------------------------------------------------------*/
11+
12+
using System;
13+
using System.Threading.Tasks;
14+
using Senparc.AI.Entities;
15+
using Senparc.AI.Kernel;
16+
using Senparc.CO2NET.Extensions;
17+
using Senparc.CO2NET.Helpers;
18+
using Senparc.CO2NET.Trace;
19+
using Senparc.NeuChar.Entities;
20+
using Senparc.Weixin.MP.Entities;
21+
using Senparc.Weixin.MP.Sample.CommonService.AI.MessageHandlers;
22+
23+
namespace Senparc.Weixin.Sample.CommonService.CustomMessageHandler
24+
{
25+
/// <summary>
26+
/// 自定义MessageHandler(公众号)
27+
/// </summary>
28+
public partial class CustomMessageHandler
29+
{
30+
31+
const string WELCOME_MESSAGE = @"
32+
33+
输入“p”暂停,可以暂时保留记忆
34+
输入“e”退出,彻底删除记忆
35+
36+
[结果由 AI 生成,仅供参考]";
37+
38+
/// <summary>
39+
/// 开始 AI 对话
40+
/// </summary>
41+
/// <param name="requestMessage"></param>
42+
/// <returns></returns>
43+
private async Task<IResponseMessageBase> StartAIChatAsync()
44+
{
45+
var currentMessageContext = await base.GetCurrentMessageContext();
46+
47+
48+
//新建个人对话缓存(由于使用了 CurrentMessageContext,多用户之前完全隔离,对话不会串)
49+
var storage = new ChatStore()
50+
{
51+
Status = ChatStatus.Chat,
52+
History = ""
53+
};
54+
55+
currentMessageContext.StorageData = storage.ToJson();//为了提升兼容性,采用字符格式
56+
57+
await GlobalMessageContext.UpdateMessageContextAsync(currentMessageContext);//储存到缓存
58+
59+
var responseMessage = base.CreateResponseMessage<ResponseMessageText>();
60+
responseMessage.Content = "小嗨 Bot 已启动!" + WELCOME_MESSAGE;
61+
62+
return responseMessage;
63+
}
64+
65+
/// <summary>
66+
/// 开始 AI 对话
67+
/// </summary>
68+
/// <param name="requestMessage"></param>
69+
/// <returns></returns>
70+
private async Task<IResponseMessageBase> AIChatAsync(RequestMessageBase requestMessage)
71+
{
72+
var currentMessageContext = await base.GetCurrentMessageContext();
73+
74+
if (!(currentMessageContext.StorageData is string chatJson))
75+
{
76+
return null;
77+
}
78+
79+
ChatStore chatStore;
80+
81+
try
82+
{
83+
chatStore = chatJson.GetObject<ChatStore>();
84+
if (chatStore == null || chatStore.Status == ChatStatus.None || chatStore.History == null)
85+
{
86+
return null;
87+
}
88+
}
89+
catch
90+
{
91+
return null;
92+
}
93+
94+
try
95+
{
96+
if (requestMessage is RequestMessageText requestMessageText)
97+
{
98+
string prompt;
99+
100+
if (requestMessageText.Content.Equals("E", StringComparison.OrdinalIgnoreCase))
101+
{
102+
prompt = $"我即将结束对话,请发送一段文字和我告别,并提醒我:输入“AI”可以再次启动对话。";
103+
104+
//消除状态记录
105+
await UpdateMessageContext(currentMessageContext, null);
106+
}
107+
else if (requestMessageText.Content.Equals("P", StringComparison.OrdinalIgnoreCase))
108+
{
109+
prompt = $"我即将临时暂停对话,请发送一段文字和我告别,并提醒我:输入“AI”可以再次启动对话。请记住,下次启动会话时,发送再次欢迎我回来的信息。";
110+
111+
// 修改状态记录
112+
chatStore.Status = ChatStatus.Paused;
113+
await UpdateMessageContext(currentMessageContext, chatStore);
114+
}
115+
else if (chatStore.Status == ChatStatus.Paused)
116+
{
117+
if (requestMessageText.Content.Equals("AI", StringComparison.OrdinalIgnoreCase))
118+
{
119+
prompt = @"我将重新开始对话,请发送一段欢迎信息,并且在最后提示我(注意保留换行):" + WELCOME_MESSAGE;
120+
121+
// 修改状态记录
122+
chatStore.Status = ChatStatus.Chat;
123+
await UpdateMessageContext(currentMessageContext, chatStore);
124+
}
125+
else
126+
{
127+
return null;
128+
}
129+
}
130+
else
131+
{
132+
prompt = requestMessageText.Content;
133+
}
134+
135+
#region 请求 AI 模型进入 Chat 的经典模式
136+
137+
/* 模型配置
138+
* 注意:需要在 appsettings.json 中的 <SenparcAiSetting> 节点配置 AI 模型参数,否则无法使用 AI 能力
139+
*/
140+
var setting = (SenparcAiSetting)Senparc.AI.Config.SenparcAiSetting;//也可以留空,将自动获取
141+
142+
//模型请求参数
143+
var parameter = new PromptConfigParameter()
144+
{
145+
MaxTokens = 2000,
146+
Temperature = 0.7,
147+
TopP = 0.5,
148+
};
149+
150+
//最大保存 AI 对话记录数
151+
var maxHistoryCount = 10;
152+
153+
//默认 SystemMessage(可根据自己需要修改)
154+
var systemMessage = Senparc.AI.DefaultSetting.DEFAULT_SYSTEM_MESSAGE;
155+
156+
var aiHandler = new SemanticAiHandler(setting);
157+
var iWantToRun = aiHandler.ChatConfig(parameter,
158+
userId: "Jeffrey",
159+
maxHistoryStore: maxHistoryCount,
160+
chatSystemMessage: systemMessage,
161+
senparcAiSetting: setting).iWantToRun;
162+
163+
//注入历史记录(也可以把 iWantToRun 对象缓存起来,其中会自动包含 history,不需要每次读取或者保存)
164+
iWantToRun.StoredAiArguments.Context["history"] = chatStore.History;
165+
166+
//获取请求(注意:因为微信需要一次返回所有文本,所以此处不使用 AI 流行的 Stream(流式)输出
167+
var result = await aiHandler.ChatAsync(iWantToRun, prompt);
168+
169+
#endregion
170+
171+
172+
//保存历史记录
173+
chatStore.History = iWantToRun.StoredAiArguments.Context["history"]?.ToString();
174+
await UpdateMessageContext(currentMessageContext, chatStore);
175+
176+
//组织返回消息
177+
var responseMessage = base.CreateResponseMessage<ResponseMessageText>();
178+
responseMessage.Content = result.OutputString;
179+
return responseMessage;
180+
}
181+
else
182+
{
183+
var responseMessage = base.CreateResponseMessage<ResponseMessageText>();
184+
responseMessage.Content = "暂时不支持此数据格式!";
185+
return responseMessage;
186+
}
187+
}
188+
catch (Exception ex)
189+
{
190+
SenparcTrace.BaseExceptionLog(ex);
191+
192+
var responseMessage = base.CreateResponseMessage<ResponseMessageText>();
193+
responseMessage.Content = "系统忙,请稍后再试!";
194+
return responseMessage;
195+
}
196+
197+
}
198+
199+
private async Task UpdateMessageContext(CustomMessageContext currentMessageContext, ChatStore chatStore)
200+
{
201+
currentMessageContext.StorageData = chatStore==null?null : chatStore.ToJson();
202+
await GlobalMessageContext.UpdateMessageContextAsync(currentMessageContext);//储存到缓存
203+
}
204+
}
205+
}

Samples/All/Senparc.Weixin.Sample.CommonService/MessageHandlers/CustomMessageHandler/Async/CustomMessageHandlerAsync.cs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,12 @@ public override async Task OnExecutingAsync(CancellationToken cancellationToken)
2828
{
2929
//演示:MessageContext.StorageData
3030

31-
var currentMessageContext = await base.GetUnsafeMessageContext();//为了在分布式缓存下提高读写效率,使用此方法,如果需要获取实时数据,应该使用 base.GetCurrentMessageContext()
32-
if (currentMessageContext.StorageData == null || !(currentMessageContext.StorageData is int))
31+
//var currentMessageContext = await base.GetUnsafeMessageContext();//为了在分布式缓存下提高读写效率,使用此方法,如果需要获取实时数据,应该使用 base.GetCurrentMessageContext()
32+
var currentMessageContext = await base.GetCurrentMessageContext();
33+
34+
if (currentMessageContext.StorageData == null)
3335
{
34-
currentMessageContext.StorageData = (int)0;
36+
currentMessageContext.StorageData = 0;
3537
//await GlobalMessageContext.UpdateMessageContextAsync(currentMessageContext);//储存到缓存
3638
}
3739
await base.OnExecutingAsync(cancellationToken);
@@ -41,10 +43,15 @@ public override async Task OnExecutedAsync(CancellationToken cancellationToken)
4143
{
4244
//演示:MessageContext.StorageData
4345

44-
var currentMessageContext = await base.GetUnsafeMessageContext();//为了在分布式缓存下提高读写效率,使用此方法,如果需要获取实时数据,应该使用 base.GetCurrentMessageContext()
45-
currentMessageContext.StorageData = ((int)currentMessageContext.StorageData) + 1;
46-
GlobalMessageContext.UpdateMessageContext(currentMessageContext);//储存到缓存
47-
await base.OnExecutedAsync(cancellationToken);
46+
//var currentMessageContext = await base.GetUnsafeMessageContext();//为了在分布式缓存下提高读写效率,使用此方法,如果需要获取实时数据,应该使用 base.GetCurrentMessageContext()
47+
var currentMessageContext = await base.GetCurrentMessageContext();
48+
49+
if (currentMessageContext.StorageData is int data)
50+
{
51+
currentMessageContext.StorageData = data + 1;
52+
GlobalMessageContext.UpdateMessageContext(currentMessageContext);//储存到缓存
53+
await base.OnExecutedAsync(cancellationToken);
54+
}
4855
}
4956
}
5057
}

0 commit comments

Comments
 (0)