字符串定义智能体

前面几章我们创建智能体,都是写 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.


namedescriptioninstructionsmodel.optionsoutputSchema,这些字段和 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 之后,调用方式和前面章节手写出来的智能体完全一样RunAsyncRunStreamingAsync,结构化输出、线程(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,实现在代码。