AI 聊天網站能力升級 - 使用 Tool 與 Agent 賦與模型新能力
| | | 0 | |
在上篇文章我們以 .NET AIChatWeb 專案為基礎,將其轉為可上傳圖檔詢問任意問題的互動式 AI 聊天網站。
如要進一步擴充能力,我們可加入自訂的 Tool、Agent,甚至 Skill,賦與 LLM 模式自訂的特殊技能。事實上,AIChatWeb 原本具備的 RAG 文件檢索查詢功能,就僅僅是在 chatOptions.Tools 宣告載入文件及搜尋文侔兩項工具,就實現了簡單的 RAG 查詢:(見中文註解)
protected override void OnInitialized()
{
statefulMessageCount = 0;
messages.Add(new(ChatRole.System, SystemPrompt));
chatOptions.Tools = [
// 使用片語或關鍵字搜尋文件
AIFunctionFactory.Create(SearchAsync),
// 載入所需文件(第一次搜尋前需執行完畢)
AIFunctionFactory.Create(LoadDocumentsAsync)
];
}
private async Task AddUserMessageAsync(ChatMessage userMessage)
{
CancelAnyCurrentResponse();
// Add the user message to the conversation
messages.Add(userMessage);
chatSuggestions?.Clear();
await chatInput!.FocusAsync();
// Stream and display a new response from the IChatClient
var responseText = new TextContent("");
currentResponseMessage = new ChatMessage(ChatRole.Assistant, [responseText]);
currentResponseCancellation = new();
// 呼叫 LLM 模型時傳入的 chatOptions 包含 Tools 資訊,LLM 會依需求調用這些工具
await foreach (var update in ChatClient.GetStreamingResponseAsync(messages.Skip(statefulMessageCount), chatOptions, currentResponseCancellation.Token))
{
messages.AddMessages(update, filter: c => c is not TextContent);
responseText.Text += update.Text;
chatOptions.ConversationId = update.ConversationId;
ChatMessageItem.NotifyChanged(currentResponseMessage);
}
// Store the final response in the conversation, and begin getting suggestions
messages.Add(currentResponseMessage!);
statefulMessageCount = chatOptions.ConversationId is not null ? messages.Count : 0;
currentResponseMessage = null;
chatSuggestions?.Update(messages);
}
要增加其他 Tool 的做法很簡單,在 chatOptions.Tools = [ ... ] 用 AIFunctionFactory.Create(方法) 增加我們寫好的自訂函式就可以了~ 這個簡便做法來自前陣子介紹過的 Microsoft Agent Framework,是微軟針對 AI 應用開發提出的最新程式框架與 SDK (取代 Semantic Kernel 及 AutoGen),想用 .NET 開發 AI 應用程式必學,而 Agent Framework SDK 的設計十分簡潔,用類似 AIFunctionFactory.Create()、AsAIAgent() 就能建構需要的型別,寫起來蠻方便的。
為了示範,我打算寫一個簡單的樂透選號 Tool 及一個簡單的農民曆擇日 Agent。
先看樂透選號,這個需求超簡單,知道選號規則(例:42 選 6、49 選 6 加特別號...)用隨機數產生即可,為了怕模型自己太聰明知道規則不需要工具也能自己生成,我設計了一個虛構的「黑大樂透」,數字由 0 ~ 31 (0x1f),32 取 8 (2 的三次方),如此便能 100% 確認模型是呼叫我們的工具函式選號。
public class PickNumberFunctions
{
static string[] KnownLotteries = new[] { "樂透", "大樂透", "威力彩", "黑大樂透" };
[Description("為樂透、大樂透、威力彩、黑大樂透等彩券選擇號碼")]
public Task<string> PickLotteryNumbersAsync(
[Description("彩券名稱,包含 '樂透'、'大樂透'、'威力彩'、'黑大樂透'。")] string lotteryName)
{
if (!KnownLotteries.Contains(lotteryName))
{
throw new ArgumentException($"不支援 {lotteryName}. 支援的彩券有: {string.Join(", ", KnownLotteries)}");
}
int count, min, max;
bool reqExtraNum = true;
switch (lotteryName)
{
case "樂透":
count = 6;
min = 1;
max = 49;
reqExtraNum = false;
break;
case "大樂透":
count = 6;
min = 1;
max = 49;
break;
case "威力彩":
count = 6;
min = 1;
max = 38;
break;
case "黑大樂透":
count = 8;
min = 0;
max = 31;
reqExtraNum = false;
break;
default:
throw new NotImplementedException();
}
var random = new Random();
var numbers = Enumerable.Range(min, max - min + 1)
.OrderBy(_ => random.Next()).Take(count)
.OrderBy(o => o).ToArray();
if (reqExtraNum)
{
var extraNum = random.Next(1, 9); // 額外號碼在 1-8 之間
return Task.FromResult($"號碼為 {string.Join(", ", numbers)},特別號為 {extraNum},祝您中獎!");
}
return Task.FromResult($"號碼為 {string.Join(", ", numbers)},祝您中獎!");
}
}
至於 Agent,我的點子是以前幾天提到的農民曆程式庫為核心,讓 AI 模型具備選日子的能力,算算哪天好動工,哪天宜嫁娶。這要寫成 Agent 不難,變兩個工具函式,一個取得現在時間,一個取得指定期間每天的宜、忌活動,再寫一段 Prompt 請模型依使用者需求找出合適的日子,剩下的交給冰雪聰明的 AI 模型,一切就搞定惹。(註:目前的 lunar-csharp NuGet 程式庫只支援簡體中文,我已經 Fork 專案增加繁體中文打算發 PR 看有沒有機會被合併,現階段簡繁轉換先交給模型處理)
public class PickDayAgent
{
// 註:NuGet 現有 lunar-sharp 版本僅支持簡體中文,此處借用 LLM 模型的多國語言能力內部翻譯
public ChatClient _chatClient { get; }
string[] KnownActivities = {"祭祀", "祈福", "求嗣", "开光", "塑绘", "齐醮", "斋醮", "沐浴", "酬神", "造庙", "祀灶", "焚香", "谢土", "出火", "雕刻", "嫁娶", "订婚", "纳采", "问名", "纳婿", "归宁", "安床", "合帐", "冠笄", "订盟", "进人口", "裁衣", "挽面", "开容", "修坟", "启钻", "破土", "安葬", "立碑", "成服", "除服", "开生坟", "合寿木", "入殓", "移柩", "普渡", "入宅", "安香", "安门", "修造", "起基", "动土", "上梁", "竖柱", "开井开池", "作陂放水", "拆卸", "破屋", "坏垣", "补垣", "伐木做梁", "作灶", "解除", "开柱眼", "穿屏扇架", "盖屋合脊", "开厕", "造仓", "塞穴", "平治道涂", "造桥", "作厕", "筑堤", "开池", "伐木", "开渠", "掘井", "扫舍", "放水", "造屋", "合脊", "造畜稠", "修门", "定磉", "作梁", "修饰垣墙", "架马", "开市", "挂匾", "纳财", "求财", "开仓", "买车", "置产", "雇佣", "出货财", "安机械", "造车器", "经络", "酝酿", "作染", "鼓铸", "造船", "割蜜", "栽种", "取渔", "结网", "牧养", "安碓磑", "习艺", "入学", "理发", "探病", "见贵", "乘船", "渡水", "针灸", "出行", "移徙", "分居", "剃头", "整手足甲", "纳畜", "捕捉", "畋猎", "教牛马", "会亲友", "赴任", "求医", "治病", "词讼", "起基动土", "破屋坏垣", "盖屋", "造仓库", "立券交易", "交易", "立券", "安机", "会友", "求医疗病", "诸事不宜", "馀事勿取", "行丧", "断蚁", "归岫", "无"};
ChatClientAgent agent;
public PickDayAgent(ChatClient chatClient)
{
_chatClient = chatClient;
agent = _chatClient.AsAIAgent(
instructions: $"""
你是一個擇日小助手,依據使用者提供的日期範圍及活動類型,使用 ShowRecommendedActivitiesAsync 工具挑選適合的日子。
- 若使用者指定相對日期區間(例如「未來一週」),使用 GetNowTimeAsync 工具取得現在的日期時間計算實際日期範圍。
- 使用 ShowRecommendedActivitiesAsync 工具來獲取每一天的宜、忌活動。
- 嘗試將活動歸類到以下項目之一: {string.Join(", ", KnownActivities)}等。
- 根據使用者提供的活動類型,找出宜、忌該活動的日子,並顯示當日宜忌項目的完整原文。
- 工具回傳結果可能包含簡體中文,請一律轉為繁體中文。
""",
tools: [
AIFunctionFactory.Create(GetNowTimeAsync), // 取得現在時間
// 列舉指定期間每天的宜忌項目
AIFunctionFactory.Create(ShowRecommendedActivitiesAsync)
]
);
}
[Description("根據使用者提供的日期範圍及活動類型,挑選適合的日子。")]
public async Task<string> PickDayAsync(string question)
{
var response = await agent.RunAsync(question);
return response.ToString() ?? string.Empty;
}
[Description("取得現在日期時間")]
public async Task<DateTime> GetNowTimeAsync() => DateTime.Now;
[Description("顯示指定日期範圍,每天的宜、忌活動")]
public async Task<string> ShowRecommendedActivitiesAsync(
[Description("開始日期")] DateTime startDate,
[Description("結束日期")] DateTime endDate
)
{
if (startDate.CompareTo(endDate) > 0)
{
(startDate, endDate) = (endDate, startDate);
}
var date = startDate;
var sb = new StringBuilder();
while (date.CompareTo(endDate) <= 0)
{
var lunarDate =Lunar.Lunar.FromDate(date);
var okActivities = string.Join(", ", lunarDate.GetDayYi());
var avoidActivities = string.Join(", ", lunarDate.GetDayJi());
var result = $"""
【{date:yyyy-MM-dd}】
- 宜 {okActivities}
- 忌 {avoidActivities}
""";
Console.WriteLine(result);
sb.AppendLine(result);
date = date.AddDays(1);
}
return sb.ToString();
}
}
工具跟 Agent 寫好,Tools 清單加兩行 AIFunctionFactory.Create() 把方法掛上去,相關說明用 Description Attribute 就近寫在函數及參數名稱,.Create() 會自動抓取。如此,AI 模型自會在需要時使用它們,MS Agent Framework 的這種簡潔設計,深得我心。

果不其然,現在 GPT 模型已知道怎麼為黑大樂透選號,也會查農民曆看日子囉~ 依此要領,要為 AI 應用加上什麼新能力,全憑你的想像力。
本文程式範例可參考 add-tool-n-agent 分支。
Demonstrates extending a .NET AIChatWeb app by adding custom Tools and an Agent using Microsoft Agent Framework. Shows how to implement a lottery number picker and a lunar calendar day-selection agent, highlighting how easily AI capabilities can be expanded.
Comments
Be the first to post a comment