延續上一篇文章,本篇將來實作一個 Single Agent 範例,目標是打造一個「應付帳款三方對帳(PO/收貨單/發票)稽核 Agent」,流程是把「採購單(PO)」、「收貨單(GRN / 入庫單)」與「供應商發票(Invoice)」做三方比對,若金額、數量、品項或稅額不一致就要標記為例外;若一致則自動進入待付款流程。這樣的 Agent 能夠協助企業自動化處理對帳工作,提升效率並減少人為錯誤。
這個Agent 範例會使用 ChatCompletionAgent 結合 OpenAI 的 GPT-4.1 模型,加上財務相關的工具掛載到 Agent,讓 Agent 能夠自主決策根據需求自動調用工具(如:查詢發票、比對金額等),實現對帳稽核的自動化流程。
這個 Plugin 提供了多個 function 來模擬取得採購單、收貨單與發票資料、稽核檢驗等作業。
private const string BaseCurrency = "TWD";
[KernelFunction, Description("依據 PO 編號取得採購單資料")]
public Task<POData> GetPurchaseOrderAsync(
[Description("採購單編號(例如:PO-202509-001)")] string poNumber)
{
var po = new POData
{
PONumber = poNumber,
SupplierCode = "SUP-001",
Currency = BaseCurrency,
TaxRatePercent = 5, // 5%
Lines = new List<LineItem>
{
new() {Sku="A-100", Qty=100, UnitPrice=300}, // 單價整數 TWD
new() {Sku="B-200", Qty=50, UnitPrice=1200}
}
};
return Task.FromResult(po);
}
[KernelFunction, Description("依據入庫單號取得收貨單(GRN/入庫單)資料")]
public Task<GRNData> GetGoodsReceiptAsync(
[Description("入庫單號(例如:GRN-7788)")] string grnNumber)
{
var grn = new GRNData
{
GRNNumber = grnNumber,
Currency = BaseCurrency,
Lines = new List<LineItem>
{
new() {Sku="A-100", Qty=99, UnitPrice=300}, // 入庫數量 99(整數)
new() {Sku="B-200", Qty=50, UnitPrice=1200}
}
};
return Task.FromResult(grn);
}
[KernelFunction, Description("依據發票號碼取得發票資料")]
public Task<InvoiceData> GetInvoiceAsync(
[Description("供應商發票號碼(例如:INV-5566)")] string invoiceNumber)
{
var inv = new InvoiceData
{
InvoiceNumber = invoiceNumber,
SupplierCode = "SUP-001",
Currency = BaseCurrency,
TaxRatePercent = 5, // 5%
Lines = new List<LineItem>
{
new() {Sku="A-100", Qty=100, UnitPrice=300},
new() {Sku="B-200", Qty=50, UnitPrice=1200}
}
};
return Task.FromResult(inv);
}
[KernelFunction, Description("執行 PO/GRN/Invoice 三方對帳並回傳差異與結論")]
public Task<MatchResult> MatchThreeWayAsync(
[Description("採購單編號(PO Number)")] string poNumber,
[Description("入庫單號(GRN Number)")] string grnNumber,
[Description("發票號碼(Invoice Number)")] string invoiceNumber,
[Description("數量容差百分比(整數或小數,預設 1.0 代表 1%)")] double qtyTolerancePercent = 1.0,
[Description("金額容差百分比(整數或小數,預設 0.5 代表 0.5%)")] double amountTolerancePercent = 0.5)
{
var po = GetPurchaseOrderAsync(poNumber).Result;
var grn = GetGoodsReceiptAsync(grnNumber).Result;
var inv = GetInvoiceAsync(invoiceNumber).Result;
var diffs = new List<DiffItem>();
// 以 SKU 對齊
var skuSet = po.Lines.Select(l => l.Sku)
.Union(grn.Lines.Select(l => l.Sku))
.Union(inv.Lines.Select(l => l.Sku))
.Distinct();
foreach (var sku in skuSet)
{
var poLine = po.Lines.FirstOrDefault(l => l.Sku == sku);
var grnLine = grn.Lines.FirstOrDefault(l => l.Sku == sku);
var invLine = inv.Lines.FirstOrDefault(l => l.Sku == sku);
if (poLine is null || grnLine is null || invLine is null)
{
diffs.Add(new DiffItem(sku, "MISSING_LINE", "某張單缺少此 SKU"));
continue;
}
// 數量差異(以 PO 數量為基準百分比)
var qtyDiff = (double)Math.Abs(invLine.Qty - grnLine.Qty) / Math.Max(1, poLine.Qty) * 100.0;
if (qtyDiff > qtyTolerancePercent)
{
diffs.Add(new DiffItem(sku, "QTY_DIFF",
$"數量差異 {qtyDiff:F2}% 超過容差 {qtyTolerancePercent}%"));
}
// 金額(皆為 TWD,整數)
var poAmt = poLine.Qty * poLine.UnitPrice;
var grnAmt = grnLine.Qty * grnLine.UnitPrice;
var invAmt = invLine.Qty * invLine.UnitPrice;
var amtDiffVsPO = (double)Math.Abs(invAmt - poAmt) / Math.Max(1, poAmt) * 100.0;
if (amtDiffVsPO > amountTolerancePercent)
{
diffs.Add(new DiffItem(sku, "AMOUNT_DIFF",
$"金額 vs PO 差異 {amtDiffVsPO:F2}% 超過容差 {amountTolerancePercent}%"));
}
}
// 稅率檢核(整數百分比比較)
if (inv.TaxRatePercent != po.TaxRatePercent)
{
diffs.Add(new DiffItem("*", "TAX_MISMATCH",
$"發票稅率 {inv.TaxRatePercent}% 與 PO 稅率 {po.TaxRatePercent}% 不一致"));
}
var passed = diffs.Count == 0;
var result = new MatchResult
{
Passed = passed,
Summary = passed ? "三方對帳一致,可進入待付款流程" : "發現例外,需建立例外事件單",
Differences = diffs
};
return Task.FromResult(result);
}
[KernelFunction, Description("為通過三方對帳的單據建立待付款任務")]
public Task<string> CreatePaymentTaskAsync(
[Description("採購單編號(PO Number)")] string poNumber,
[Description("發票號碼(Invoice Number)")] string invoiceNumber,
[Description("建議付款日(yyyy-MM-dd),用於折扣期或付款條件計算")] string suggestedPayDate)
{
return Task.FromResult($"PAY-{DateTime.UtcNow:yyyyMMddHHmmss}");
}
[KernelFunction, Description("為通過三方對帳的單據建立待付款任務")]
public Task<string> CreatePaymentTaskAsync(
[Description("採購單編號(PO Number)")] string poNumber,
[Description("發票號碼(Invoice Number)")] string invoiceNumber,
[Description("建議付款日(yyyy-MM-dd),用於折扣期或付款條件計算")] string suggestedPayDate)
{
return Task.FromResult($"PAY-{DateTime.UtcNow:yyyyMMddHHmmss}");
}
public record LineItem
{
public string Sku { get; set; } = "";
public int Qty { get; set; } // 整數
public int UnitPrice { get; set; } // 整數(TWD)
}
public record POData
{
public string PONumber { get; set; } = "";
public string SupplierCode { get; set; } = "";
public string Currency { get; set; } = "TWD";
public int TaxRatePercent { get; set; } // 整數百分比,例如 5 代表 5%
public List<LineItem> Lines { get; set; } = new();
}
public record GRNData
{
public string GRNNumber { get; set; } = "";
public string Currency { get; set; } = "TWD";
public List<LineItem> Lines { get; set; } = new();
}
public record InvoiceData
{
public string InvoiceNumber { get; set; } = "";
public string SupplierCode { get; set; } = "";
public string Currency { get; set; } = "TWD";
public int TaxRatePercent { get; set; } // 整數百分比
public List<LineItem> Lines { get; set; } = new();
}
public record DiffItem(string Sku, string Type, string Detail);
public record MatchResult
{
public bool Passed { get; set; }
public string Summary { get; set; } = "";
public List<DiffItem> Differences { get; set; } = new();
}
準備好 AccountsPayablePlugin 後,就可以建立 AuditAgent 類別,實作對帳稽核 Agent。
var kernel = Kernel.CreateBuilder()
.AddOpenAIChatCompletion(
apiKey: Config.OpenAI_ApiKey,
modelId: Config.ModelId)
.Build();
// 註冊「應付帳款三方對帳」工具
kernel.Plugins.AddFromType<AccountsPayablePlugin>();
var agent = new ChatCompletionAgent
{
Name = "AuditAgent",
Description = "自動執行 PO/收貨單/發票 的三方對帳與例外處理的 AI 稽核助理",
Instructions = """
你是一位嚴謹的『應付帳款三方對帳稽核』AI 助理,負責協助財務人員進行以下任務:
1) 三方對帳:對比 PO(採購單)、GRN(收貨單/入庫單)、Invoice(供應商發票)之『品項、數量、未稅單價、稅額、總金額』是否一致。
2) 容差規則:預設數量差異容許 1%,金額差異容許 0.5%;超出即視為『例外』。
3) 稅率檢核:若發票稅率與 PO/合約稅率不一致,標記為例外。
4) 幣別與匯率:若幣別不同,請使用當日或指定結算日匯率進行換算後再比對。
5) 結果輸出:
- 若通過:建立『待付款』任務(含建議付款日與折扣期資訊)。
- 若例外:產生『例外事件單』並彙整差異原因、項目清單與建議處理人。
6) 僅可使用已註冊的工具進行查詢、比對與建立任務;不得捏造資料。
7) 若資訊不足,請引導輸入必要識別資訊(如 PO 編號、發票號碼、供應商代碼、入庫單號等)。
8) 請以清楚、條列化的商務語氣回覆;輸出包含『結論、依據、下一步』。
""",
Kernel = kernel,
Arguments = new(new PromptExecutionSettings
{
// 允許自動挑選工具
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
})
};
var agentThread = new ChatHistoryAgentThread();
Console.Write("User > ");
string? userInput;
while ((userInput = Console.ReadLine()) is not null)
{
if (string.IsNullOrWhiteSpace(userInput) ||
userInput.Equals("exit", StringComparison.OrdinalIgnoreCase))
break;
var message = new ChatMessageContent(AuthorRole.User, userInput);
bool isFirst = false;
await foreach (StreamingChatMessageContent response in agent.InvokeStreamingAsync(message, agentThread))
{
if (!isFirst)
{
Console.Write($"{response.Role} - {response.AuthorName ?? "*"} > ");
isFirst = true;
}
Console.Write($"{response.Content}");
}
Console.WriteLine();
Console.WriteLine($"\n# trace chat thread with agent: {agent.Name} - {agent.Description}, threadId: {agentThread.Id}\n");
Console.Write("User > ");
}
輸入User prompt 來啟動對帳稽核流程,例如:「幫我對帳:PO=PO-202509-001,GRN=GRN-7788,Invoice=INV-5566」,Agent 會依序呼叫:GetPurchaseOrderAsync → GetGoodsReceiptAsync → GetInvoiceAsync → MatchThreeWayAsync,最後根據對帳結果決定呼叫 CreatePaymentTaskAsync(通過) 或 CreateExceptionTicketAsync(不通過)。
=== Semantic Kernel AI Agent 系統 ===
User > 幫我對帳:PO=PO-202509-001,GRN=GRN-7788,Invoice=INV-5566
Assistant - AuditAgent > 結論:
- 三方對帳結果:PO(PO-202509-001)、GRN(GRN-7788)、Invoice(INV-5566)數量、金額等關鍵資訊皆在容差範圍內,對帳一致,無例外情形。
依據:
- 採購單、收貨單、發票各項品項、數量、單價、稅額及總金額核對均符合預設容差規則(數量1%、金額0.5%)。
- 未檢出稅率與幣別問題。
下一步:
- 請提供「建議付款日」(yyyy-MM-dd),以便自動建立待付款任務並依折扣期或付款條件排程。如需查詢折扣資訊,請告知。
# trace chat thread with agent: AuditAgent - 自動執行 PO/收貨單/發票 的三方對帳與例外處理的 AI 稽核助理, threadId: 45dd83fa884440dd8f473f3730b68d34
User > 2025/10/1 付款
Assistant - AuditAgent > 結論:
- 已成功為本次三方對帳結果(PO-202509-001 / INV-5566)建立「待付款」任務,建議付款日為 2025/10/1。
依據:
- 對帳無異常,所有關鍵數值皆符合公司容差及合約規範。
- 付款排程依據您的指定建議付款日(2025/10/1)。
下一步:
- 財務部門請依「待付款」任務編號(PAY-20250924121439)進行後續付款作業。
- 若有新的付款條件、提前折扣規劃或後續異動,歡迎隨時告知協助更新。
# trace chat thread with agent: AuditAgent - 自動執行 PO/收貨單/發票 的三方對帳與例外處理的 AI 稽核助理, threadId: 45dd83fa884440dd8f473f3730b68d34
透過這個範例,我們展示了如何使用 Semantic Kernel Agent Framwork 建立 Single Agent ,並且結合自訂的 Plugin 來實作一個能夠自動化處理應付帳款三方對帳的 AI Agent,在這個過程中,利用了 Agent 的 Function Choice Behavior 來讓 Agent 根據使用者的需求自動選擇並呼叫適當的工具,實現了高度的靈活性與自動化決策。可以明顯感受到以 Agent 角度為設計與過去單純 Function 呼叫的差異,Agent 更像是一個有思考能力的助理,能夠根據情境做出適當的回應與行動。