AI 大模型应用开发初探:基于 Semantic Kernel 构建简易 Agent
近期,随着大语言模型(LLM)技术的飞速发展,AI Agent 被视为下一代应用开发的新范式。本文将深入探讨 AI Agent 的核心概念、架构原理,并基于 C# 和 Semantic Kernel 框架,手把手演示如何开发一个具备 Function Calling 能力的简易 Agent。
AI Agent:可以帮你执行任务的助手
学术界和工业界对术语'AI Agent'提出了各种定义。其中,OpenAI 将 AI Agent 定义为'以大语言模型为大脑驱动的系统,具备自主理解、感知、规划、记忆和使用工具的能力,能够自动化执行完成复杂任务的系统。'
通俗来说,大多数时候你给它一个最终你想要达成的目标,它能直接交付结果,过程你啥都不用管。如果说人和动物的区别是人会使用各种工具,那么 Agent 和大模型的区别亦然。
我们可以把 Agent 与 LLM 形象地比作生物体与其大脑。Agent 有手有脚,可以自己干活自己执行,而 LLM 呢,就是它的大脑。比如,如果你使用 LLM 大模型,它可能只能给你输出一份食谱,告诉你需要哪些食材和步骤来制作。但如果你使用 Agent,它可能就是不仅提供食谱和步骤,还会根据你的需求,帮你选择合适的食材甚至自动下单购买,监控烹饪过程,确保食物口感,最终为你呈上一份佳肴。
AI Agent 如何工作?
AI Agent 的架构是其智能行为的基础,它通常包括感知、规划、记忆、工具使用和行动等关键组件,这些组件协同工作以实现高效的智能行为。
1. 感知 (Perception)
Agent 首先需要感知环境。这包括接收用户的自然语言输入、读取传感器数据或访问外部 API 获取当前状态。感知模块负责将非结构化信息转化为 Agent 可理解的上下文。
2. 规划 (Planning)
这是 Agent 的'大脑'部分。基于感知到的信息和长期/短期记忆,LLM 会进行推理,制定达成目标的步骤序列。例如,用户要求'更新工单数量',Agent 需要规划出先查询工单是否存在,再校验状态,最后执行更新的逻辑链条。
3. 记忆 (Memory)
记忆机制允许 Agent 记住过去的交互历史、用户偏好以及任务执行过程中的中间状态。短期记忆通常用于当前的对话上下文,而长期记忆可以通过向量数据库存储历史经验,以便在类似场景下复用知识。
4. 工具使用 (Tool Use)
Agent 本身不具备物理操作能力,必须通过调用外部工具来改变世界。这包括调用 RESTful API、运行脚本、查询数据库或使用第三方服务。Function Calling 是实现这一能力的关键技术。
5. 行动 (Action)
执行规划好的工具调用,并将结果反馈给 LLM 进行下一轮决策,形成闭环。
AI Agent 的工作流程其实就是一个连续的循环过程。它从感知环境开始,经过信息处理、规划和决策,然后执行行动。最后,根据执行结果和环境反馈进行调整,以优化未来的行动和决策。通过这种结构化和层次化的方式,AI Agent 能够有效地处理信息,做出决策,并在复杂环境中执行任务。
如何开发 AI Agent?
目前业界开发 AI Agent 主要有两种模式:
模式一:代码驱动开发
基于 Python 或 C# 等编程语言,结合 LangChain 或 Semantic Kernel 等大模型应用开发框架,集成某个大模型 API 和企业内部的业务 API 能力,来完成具体领域的 Agent。这种方式灵活性高,适合复杂业务逻辑和深度定制。
模式二:低代码/平台化开发
基于 Coze、Dify、AutoGen 等 Agent 开发管理平台,通过拖拽的方式快速生成一个 Agent。与其说是开发,不如说是 Workflow 一样的配置。当然,也需要给这些平台注册封装好的企业内部 API 平台提供的能力供配置好的 Agent 去实现工具调用。这种方式上手快,适合快速原型验证。
本文重点介绍基于 C# + Semantic Kernel 的代码驱动开发模式。
使用 Semantic Kernel 开发 AI Agent
这里我们快速使用 Semantic Kernel 开发一个简易的 WorkOrder Agent(MES 工单助手),重点关注如何给 LLM 添加 Function Calling 能力,直观了解 Agent 规划任务和执行任务的效果。
效果对比
- 没有实现 Function Calling 的效果:它就只是个 Chatbot。用户询问工单信息时,它只能基于训练数据回答,无法获取实时数据。
- 实现了 Function Calling 的效果:它可以称为 Agent。用户发出指令后,它能自主决定调用哪个 API,并按顺序执行,最终返回准确结果。
我的需求其实包含两个步骤:第一步是更新工单的 Quantity,第二步是查询更新后的工单信息。而这两个步骤我们假设其实都是需要去调用 MES WorkOrderService API 才能获得的,这就需要我们给 LLM 加入 Function Calling 的能力,当然 LLM 自己得知道如何规划执行的步骤,哪个步骤先执行,哪个后执行。
示例代码结构
我们将项目分为几个关键部分:配置管理、API 处理、业务服务模拟、以及主程序逻辑。
1. 配置管理 (Shared)
首先定义 OpenAI 兼容服务的配置类,支持多提供商切换。
public class OpenAiConfiguration
{
public string Provider { get; set; }
public string ModelId { get; set; }
public string EndPoint { get; set; }
public string ApiKey { get; set; }
public OpenAiConfiguration(string modelId, string endPoint, string apiKey)
{
// 默认使用 OpenAI 兼容协议
Provider = ConfigConstants.LLMProviders.OpenAI;
ModelId = modelId;
EndPoint = endPoint;
ApiKey = apiKey;
}
public OpenAiConfiguration(string provider, string modelId, string endPoint, string apiKey)
{
Provider = provider;
ModelId = modelId;
EndPoint = endPoint;
ApiKey = apiKey;
}
}
为了支持不同的 LLM 提供商(如智谱 AI、通义千问等),我们需要自定义 HttpClientHandler 来重写请求路径。
public class CustomLLMApiHandler : HttpClientHandler
{
private readonly string _openAiProvider;
private readonly string _openAiBaseAddress;
public CustomLLMApiHandler(string openAiProvider, string openAiBaseAddress)
{
_openAiProvider = openAiProvider;
_openAiBaseAddress = openAiBaseAddress;
}
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
UriBuilder uriBuilder;
Uri uri = new Uri(_openAiBaseAddress);
switch (request.RequestUri?.LocalPath)
{
case "/v1/chat/completions":
switch (_openAiProvider)
{
case ConfigConstants.LLMProviders.ZhiPuAI:
uriBuilder = new UriBuilder(request.RequestUri)
{
Scheme = "https",
Host = uri.Host,
Path = ConfigConstants.LLMApiPaths.ZhiPuAIChatCompletions,
};
request.RequestUri = uriBuilder.Uri;
break;
default: // Default: OpenAI-Compatible API Providers
uriBuilder = new UriBuilder(request.RequestUri)
{
Scheme = "https",
Host = uri.Host,
Path = ConfigConstants.LLMApiPaths.OpenAIChatCompletions,
};
request.RequestUri = uriBuilder.Uri;
break;
}
break;
}
HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
return response;
}
}
2. 业务服务模拟 (WorkOrderService)
在实际场景中,这里应该替换为真实的 HTTP 客户端调用企业 ERP 或 MES 系统的 API。为了演示方便,我们直接在内存中模拟数据。
public class WorkOrderService
{
// 模拟数据库中的工单列表
private static List<WorkOrder> workOrders = new List<WorkOrder>
{
new WorkOrder { WorkOrderName = "9050100", ProductName = "A5E900100", ProductVersion = "001 / AB", Quantity = 100, Status = "Ready" },
new WorkOrder { WorkOrderName = "9050101", ProductName = "A5E900101", ProductVersion = "001 / AB", Quantity = 200, Status = "Ready" },
new WorkOrder { WorkOrderName = "9050102", ProductName = "A5E900102", ProductVersion = "001 / AB", Quantity = 300, Status = "InProcess" },
new WorkOrder { WorkOrderName = "9050103", ProductName = "A5E900103", ProductVersion = "001 / AB", Quantity = 400, Status = "InProcess" },
new WorkOrder { WorkOrderName = "9050104", ProductName = "A5E900104", ProductVersion = "001 / AB", Quantity = 500, Status = "Completed" }
};
// 查询工单详情
public WorkOrder GetWorkOrderInfo(string orderName)
{
return workOrders.Find(o => o.WorkOrderName == orderName);
}
// 更新工单状态
public string UpdateWorkOrderStatus(string orderName, string newStatus)
{
var workOrder = this.GetWorkOrderInfo(orderName);
if (workOrder == null)
return "Operate Failed : The work order is not existing!";
workOrder.Status = newStatus;
return "Operate Succeed!";
}
// 减少工单数量
public string ReduceWorkOrderQuantity(string orderName, int newQuantity)
{
var workOrder = this.GetWorkOrderInfo(orderName);
if (workOrder == null)
return "Operate Failed : The work order is not existing!";
// 业务校验逻辑
if (workOrder.Status == "Completed")
return "Operate Failed : The work order is completed, can not be reduced!";
if (newQuantity <= 1 || newQuantity >= workOrder.Quantity)
return "Operate Failed : The new quantity is invalid!";
workOrder.Quantity = newQuantity;
return "Operate Succeed!";
}
}
// 简单的数据模型
public class WorkOrder
{
public string WorkOrderName { get; set; }
public string ProductName { get; set; }
public string ProductVersion { get; set; }
public int Quantity { get; set; }
public string Status { get; set; }
}
3. 配置文件 (appsettings.json)
建议使用环境变量或密钥管理服务(如 Azure Key Vault、HashiCorp Vault)来管理敏感信息,避免硬编码在配置文件中。
{
"LLM_API_PROVIDER": "ZhiPuAI",
"LLM_API_MODEL": "glm-4",
"LLM_API_BASE_URL": "https://open.bigmodel.cn",
"LLM_API_KEY": "your_api_key_here"
}
4. 初始化 Kernel 与插件
在应用程序启动时,加载配置并初始化 Semantic Kernel。
private Kernel _kernel = null;
private OpenAIPromptExecutionSettings _settings = null;
private IChatCompletionService _chatCompletion = null;
private ChatHistory _chatHistory = null;
private void ChatForm_Load(object sender, EventArgs e)
{
// 1. 加载配置
var configuration = new ConfigurationBuilder().AddJsonFile($"appsettings.{ConfigConstants.LLM_API_PROVIDER}.json").Build();
var config = configuration;
var openAiConfiguration = new OpenAiConfiguration(
config["LLM_API_PROVIDER"],
config["LLM_API_MODEL"],
config["LLM_API_BASE_URL"],
config["LLM_API_KEY"]);
// 2. 创建带自定义路由的 HttpClient
var openAiClient = new HttpClient(new CustomLlmApiHandler(openAiConfiguration.Provider, openAiConfiguration.EndPoint));
// 3. 构建 Kernel
_kernel = Kernel.CreateBuilder()
.AddOpenAIChatCompletion(openAiConfiguration.ModelId, openAiConfiguration.ApiKey, httpClient: openAiClient)
.Build();
_chatCompletion = _kernel.GetRequiredService<IChatCompletionService>();
_chatHistory = new ChatHistory();
// 4. 设置系统提示词,定义 Agent 角色
_chatHistory.AddSystemMessage("You are one WorkOrder Assistant. You help users manage manufacturing work orders.");
}
接下来,注册 Functions 到 Kernel 中。这是让 LLM 拥有'手脚'的关键步骤。
_kernel.Plugins.Add(KernelPluginFactory.CreateFromFunctions("WorkOrderHelperPlugin",
new List<KernelFunction>
{
// 注册查询函数
_kernel.CreateFunctionFromMethod((string orderName) =>
{
var workOrderRepository = new WorkOrderService();
return workOrderRepository.GetWorkOrderInfo(orderName);
}, "GetWorkOrderInfo", "Get WorkOrder's Detail Information"),
// 注册减少数量函数
_kernel.CreateFunctionFromMethod((string orderName, int newQuantity) =>
{
var workOrderRepository = new WorkOrderService();
return workOrderRepository.ReduceWorkOrderQuantity(orderName, newQuantity);
}, "ReduceWorkOrderQuantity", "Reduce WorkOrder's Quantity to new Quantity"),
// 注册更新状态函数
_kernel.CreateFunctionFromMethod((string orderName, string newStatus) =>
{
var workOrderRepository = new WorkOrderService();
return workOrderRepository.UpdateWorkOrderStatus(orderName, newStatus);
}, "UpdateWorkOrderStatus", "Update WorkOrder's Status to new Status")
}));
开启自动调用 Function,告诉大模型可以自行决定调用相关 Functions,而且大模型会自行决定根据什么顺序来调用,这就是大模型作为 Agent 大脑的规划能力。当然,我们还可以通过定制化 Planner 来增强 agent 的规划能力。
_settings = new OpenAIPromptExecutionSettings
{
ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions
};
5. 发送用户提示词
最后,实现用户交互逻辑。区分是否启用 Function Calling 模式进行测试。
private void btnSend_Click(object sender, EventArgs e)
{
_chatHistory.AddUserMessage(tbxPrompt.Text);
ChatMessageContent chatResponse = null;
tbxResponse.Clear();
if (cbxUseFunctionCalling.Checked)
{
Task.Run(() =>
{
ShowProcessMessage("AI is handling your request now...");
try
{
chatResponse = _chatCompletion.GetChatMessageContentAsync(_chatHistory, _settings, _kernel)
.GetAwaiter()
.GetResult();
UpdateResponseContent(chatResponse.ToString());
ShowProcessMessage("AI Response:");
}
catch (Exception ex)
{
UpdateResponseContent($"Error: {ex.Message}");
}
});
}
else
{
Task.Run(() =>
{
ShowProcessMessage("AI is handling your request now...");
try
{
chatResponse = _chatCompletion.GetChatMessageContentAsync(_chatHistory, null, _kernel)
.GetAwaiter()
.GetResult();
UpdateResponseContent(chatResponse.ToString());
ShowProcessMessage("AI Response:");
}
catch (Exception ex)
{
UpdateResponseContent($"Error: {ex.Message}");
}
});
}
}
最佳实践与安全建议
在将 Agent 投入生产环境之前,还需要考虑以下关键点:
1. 安全与权限控制
- API Key 管理:切勿将 API Key 硬编码在代码库中。应使用环境变量、Azure Key Vault 或 AWS Secrets Manager。
- 权限最小化:Agent 调用的工具函数应遵循最小权限原则。例如,工单更新接口不应允许删除工单,除非有明确授权。
- 输入过滤:对用户输入的 Prompt 进行安全检查,防止 Prompt Injection 攻击导致 Agent 执行恶意操作。
2. 错误处理与重试
- 网络异常:LLM 调用和外部 API 调用都可能超时或失败。需要实现指数退避重试机制。
- 业务逻辑校验:如示例代码所示,在 Function 内部进行严格的参数校验,防止脏数据写入。
- 日志记录:详细记录 Agent 的思考过程(Thought)、工具调用参数及返回结果,便于问题排查和审计。
3. 性能优化
- 缓存策略:对于相同的查询请求,可以在 Service 层增加缓存,减少 LLM 调用次数和延迟。
- 流式输出:对于长文本回复,建议使用 Stream 模式,提升用户体验。
总结
本文简单介绍了 AI Agent 的基本概念和工作方式,对比了高代码手搓和低代码拖拉拽两种开发模式。最后,通过 C# + Semantic Kernel + 智谱 GLM-4 模型演示了如何快速开发一个简易的 AI Agent。
虽然这只是个 Demo,但希望对你快速了解 Agent 有所帮助。未来,随着 RAG(检索增强生成)技术的引入和更复杂的 Planner 设计,Agent 将在企业级应用中发挥更大的价值。开发者应关注如何平衡自动化程度与人工干预的边界,确保系统的安全可控。
扩展阅读
- Microsoft Semantic Kernel 官方文档
- OpenAI Function Calling 指南
- LangChain 框架架构分析
- 大模型在企业内部署的最佳实践

