字符串定义智能体
前面几章我们创建智能体,都是写 C# 代码:new ChatClientAgent(...),通过 ChatClientAgentOptions 把名字、指令、模型、温度一个一个塞进去。这种命令式写法很灵活,但有个明显的痛点,配置和代码混在一起。
声明式智能体(Declarative Agent)就是来解决这件事的把智能体的定义从代码里抽出来,写成一段 YAML(字符串或文件),运行时再加载、装配成 AIAgent。配置归配置,代码归代码。改 prompt 不用动代码,分享一个智能体就是分享一个 agent.yaml。
声明式能力不包含在核心包里,需要单独装 Microsoft.Agents.AI.Declarative。
这个包提供两个关键角色:
ChatClientPromptAgentFactory:把 YAML 定义 + 一个IChatClient装配成一个AIAgent。CreateFromYamlAsync:扩展方法,吃一段 YAML 字符串,吐出一个智能体。
先看一份最典型的智能体定义,理解一下字段结构:
kind: Prompt # 智能体类型,Prompt 表示基于提示词的智能体
name: Assistant # 智能体名字(编程标识,英文)
description: Helpful assistant
instructions: | # 系统提示词,支持多行
You are a helpful assistant.
You answer questions in the language specified by the user.
You return your answers in a JSON format.
model:
options: # 模型生成参数
temperature: 0.9
topP: 0.95
outputSchema: # 结构化输出的 JSON Schema(可选)
properties:
language:
type: string
required: true
description: The language of the answer.
answer:
type: string
required: true
description: The answer text.
name、description、instructions、model.options、outputSchema,这些字段和 ChatClientAgentOptions 里的属性几乎是,对应的。本质上,声明式定义就是 把 new ChatClientAgentOptions { ... } 这段 C# 对象初始化器,换成了 YAML 来写。运行时框架把这些字段读出来,转成同样的 ChatOptions,再交给 ChatClientAgent。
kind 是个关键字段,告诉工厂这段定义是哪一类智能体。这里写 Prompt,工厂就会按基于提示词的智能体去装配。instructions 还支持 Power Fx 表达式做动态拼接,不过日常用静态字符串就够了。
除了上面这些,YAML 里还可以声明
tools(函数工具、MCP 工具、文件搜索、网页搜索、代码解释器等)。
用字符串创建智能体
最直接的方式:把 YAML 写成内联字符串,传给 CreateFromYamlAsync。
var yamlDefinition = File.ReadAllText("define.yaml");
IChatClient chatClient = new OpenAIClient(
credential: new ApiKeyCredential("1234"),
options: new OpenAIClientOptions { Endpoint = new Uri("http://127.0.0.1:1234/v1") })
.GetChatClient("qwen/qwen3.5-9b")
.AsIChatClient();
// 工厂 + YAML,得到一个 AIAgent
var agentFactory = new ChatClientPromptAgentFactory(chatClient);
var agent = await agentFactory.CreateFromYamlAsync(yamlDefinition);
// 之后就跟普通智能体一样用了
Console.WriteLine(await agent!.RunAsync("Tell me a joke about a pirate in English."));
// 流式也一样
await foreach (var update in agent!.RunStreamingAsync("Tell me a joke about a pirate in French."))
{
Console.WriteLine(update);
}
拿到 agent 之后,调用方式和前面章节手写出来的智能体完全一样,RunAsync、RunStreamingAsync,结构化输出、线程(AgentThread)这些能力也都保留。
给智能体挂工具
YAML 能声明 "我要用某个工具" ,但工具的实现是代码,所以构造工厂时要把函数一起传进去:
using System.ComponentModel;
[Description("Get the weather for a given location.")]
static string GetWeather(
[Description("The city and state, e.g. San Francisco, CA")] string location,
[Description("The unit of temperature. Possible values are 'celsius' and 'fahrenheit'.")] string unit)
=> $"The weather in {location} is cloudy with a high of {(unit.Equals("celsius", StringComparison.Ordinal) ? "15°C" : "59°F")}.";
// 把函数作为工具注入工厂
var agentFactory = new ChatClientPromptAgentFactory(
chatClient,
[AIFunctionFactory.Create(GetWeather, "GetWeather")]);
var agent = await agentFactory.CreateFromYamlAsync(yamlDefinition);
ChatClientPromptAgentFactory 的构造函数第二个参数 IList<AIFunction>? 就是干这个的——YAML 里声明要调 GetWeather,工厂把这里注册的 AIFunction 绑定上去。MCP 工具、文件搜索等其它工具类型,原理一样:声明在 YAML,实现在代码。