Step 2:工具调用
Agent 和普通 Chat API 最大的区别:Agent 可以调用你定义的工具函数。这是 Step 2 的核心内容。
Function Calling 原理
大语言模型本身不执行代码。Function Calling 的工作流程:
用户:"北京今天多少度?"
① Agent 把用户问题 + 可用工具描述发给 LLM
② LLM 分析后决定调用 get_weather(city="北京")
③ LLM 返回 JSON:{name:"get_weather", arguments: {city:"北京"}}
④ Agent 执行 GetWeather("北京") → "25°C,晴"
⑤ Agent 把结果传回给 LLM
⑥ LLM 基于结果生成回答:"北京今天 25°C,天气晴朗。"关键理解:LLM 只决定"该调用哪个函数",真正执行的是你的 C# 代码。
定义工具函数
using System.ComponentModel;
public class WeatherTools
{
[Description("根据城市名获取当前天气信息")]
public string GetWeather(
[Description("城市名,中文,如:北京、上海")] string city)
{
var data = new Dictionary<string, (double temp, string desc)>
{
["北京"] = (25.0, "晴 ☀️"),
["上海"] = (28.0, "多云 ⛅"),
["广州"] = (32.0, "阵雨 🌦️"),
["深圳"] = (30.0, "多云转阴 ☁️"),
};
return data.TryGetValue(city, out var w)
? $"📍 {city}:{w.temp}°C,{w.desc}"
: $"❌ 没有 {city} 的数据";
}
}[Description] 特性至关重要——LLM 通过这些描述来判断"这个工具是干什么的"以及"参数应该怎么填"。描述越清晰,LLM 调用的准确率越高。
注册工具并创建 Agent
using Azure.AI.Projects;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI; // AIFunctionFactory
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException();
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
// 创建带工具的 Agent
// tools 参数接收一个 AIFunction 数组,AIFunctionFactory.Create 自动提取方法的元数据
AIAgent agent = new AIProjectClient(new Uri(endpoint), new DefaultAzureCredential())
.AsAIAgent(
model: deploymentName,
instructions: "你是天气助手。用户问天气时请使用 GetWeather 工具。",
tools: [AIFunctionFactory.Create(WeatherTools.GetWeather)]);
// Agent 会自动判断何时调用工具
var reply = await agent.RunAsync("北京和上海今天哪个更热?差几度?");
Console.WriteLine(reply);工具设计的原则
描述精准原则
[Description] 是你和 LLM 之间的"合同",描述不仅要说明工具"是什么",还要说明"什么时候该用它"。比如"获取天气"太模糊,改成"根据城市名获取当前天气数据。当用户询问某城市的天气、温度或气候情况时调用此工具"要好得多。描述中甚至可以包含"不要在用户问历史天气时调用"这样的负面提示。
参数友好原则
参数的类型和格式要让 LLM 容易推断。例如 city 参数注明"中文城市名",LLM 就知道应该传"北京"而不是"Beijing"。如果是日期参数,注明格式"yyyy-MM-dd"能避免 LLM 猜错格式。如果是枚举值,在描述中列出所有可选值。
错误优雅原则
工具函数应该做好异常处理。如果数据库连接失败、API 超时、参数不合法,工具应该返回友好的错误信息而不是抛出未处理的异常。Agent 框架会把工具返回的文本传给 LLM,所以错误信息也应该是自然语言。
幂等性原则
尽可能让工具函数是幂等的——多次调用相同参数应该得到相同结果,且不会产生副作用。特别是查询类的工具,幂等是天然要求。对于写入类的工具(如创建订单、发送邮件),需要考虑重复调用保护。因为 LLM 在 function calling 失败时可能会自动重试,如果工具不是幂等的,同一笔订单可能被创建两次。
多个工具协作
真实场景中一个 Agent 通常需要多个工具配合。创建一个运维工具箱:
public class OpsTools
{
[Description("检查指定服务器是否在线")]
public string PingServer(
[Description("服务器 IP 地址,如 10.0.0.1")] string host)
{
var status = new Dictionary<string, string>
{
["10.0.0.1"] = "🟢 在线 (12ms)",
["10.0.0.2"] = "🟢 在线 (8ms)",
["10.0.0.3"] = "🔴 离线 (超时)",
};
return status.TryGetValue(host, out var s) ? s : "❌ 未知主机";
}
[Description("获取服务器的 CPU 和内存使用率")]
public string GetSystemMetrics(
[Description("服务器 IP 地址")] string serverIp)
{
var data = new Dictionary<string, (double cpu, double mem)>
{
["10.0.0.3"] = (99.1, 95.7),
["10.0.0.1"] = (45.2, 62.0),
};
if (data.TryGetValue(serverIp, out var m))
return $"CPU:{m.cpu}% {(m.cpu > 90 ? "⚠️" : "✅")},内存:{m.mem}%";
return $"❌ 没有 {serverIp} 的监控数据";
}
[Description("重启指定服务器。注意:此操作会导致服务中断")]
public string RebootServer(
[Description("要重启的服务器 IP")] string serverIp)
{
return $"✅ {serverIp} 重启命令已下发,预计 60 秒恢复";
}
}
// 注册多个工具
var ops = new OpsTools();
AIAgent agent = projectClient.AsAIAgent(
model: deploymentName,
instructions: "你是运维工程师助手。收到服务器告警时先检查状态,分析指标,必要时重启。",
tools: [
AIFunctionFactory.Create(ops.PingServer),
AIFunctionFactory.Create(ops.GetSystemMetrics),
AIFunctionFactory.Create(ops.RebootServer),
]);Agent 会自动编排工具调用顺序:先 Ping 检测离线,再查系统指标,分析错误原因,最后执行重启。
下一步:Step 3:多轮对话 — 使用 Session 保持对话状态。