微软 Ship AI Agent 框架:详细解析与示例

本文有一个短视频:TikTok

1. 概述

Microsoft 刚刚正式 ship(GA - General Availability)一个统一框架,用于构建、编排和运行 AI Agent,通过合并之前的两个产品 Semantic Kernel 和 AutoGen。这不仅仅是产品新闻 — 它重新定义了你应该如何设计一个具有 LLM “自动决策” 能力的系统,而不是仅仅回答一个问题。

这篇文章解释了 AI Agent 与普通聊天机器人的区别,Microsoft 具体 ship 了什么,然后你将使用 Java 21 + Spring Boot 3 + Spring AI(集成 Azure OpenAI)自己构建一个极简 agent,以理解底层机制,尽管 Microsoft 的原框架运行在 .NET 和 Python 上。

2. AI Agent 是什么?

一个普通的聊天机器人接收一个问题,调用一次 LLM,返回一个答案。Agent 的区别在于它重复思考-行动循环多次,自己决定是否需要调用工具(API、数据库、文件……),然后才给出最终答案。

特性 聊天机器人 AI Agent
LLM 调用次数 每个问题 1 次 多次,自动循环
使用外部工具 通常不使用 使用,由模型自己选择
状态 无状态或保存聊天历史 有内存,可以保存检查点
延迟 低 高(多个往返)
可靠性 容易控制 需要防护栏、人类介入

2.1. ReAct(推理-行动)循环

大多数 agent 框架使用的核心机制是 ReAct:模型“思考”(Thought),选择一个行动(Action)即调用一个工具,接收结果(Observation),然后重复这个过程直到有足够信息回答(Final Answer)。

Thought: 用户问订单 ORD-001 的状态,我需要查询。
Action: lookupOrder("ORD-001")
Observation: 订单 ORD-001:状态 SHIPPED,总金额 450000 越南盾
Thought: 信息已足够。
Final Answer: 你的订单 ORD-001 正在配送中,总金额 450,000 越南盾。

这是 Microsoft Agent Framework(以及你下面将使用的 Spring AI)自动化的部分 — 你不需要手写这个循环,只需声明工具和系统提示。

3. Microsoft “ship” 了什么用于 AI Agent

在合并之前,Microsoft 有两个并行的开发方向:

  • Semantic Kernel:面向生产的 SDK,强调类型安全的插件、企业集成(Azure、.NET),适合稳定部署。
  • AutoGen:面向研究,擅长多 agent 对话 — 多个 agent 相互“聊天”来解决复杂任务。

新框架结合了两者的优势,包括主要能力:

  • 内置编排模式:顺序(agent A 完成后再到 B),并发(多个 agent 并行运行后汇总),移交(agent 将任务交给专门处理的 agent),群组聊天(多个 agent 讨论后回答)。
  • 可持久化/有状态执行:agent 可以在中途保存检查点,如果进程崩溃可以恢复 — 对于长期任务(几分钟到几小时)很重要。
  • 内置可观测性:按 OpenTelemetry 标准追踪,你可以在生产环境中看到每个 Thought/Action/Observation 步骤。
  • 人类在循环中:暂停 agent 以等待人工审批,然后再执行敏感操作(退款、删除数据……)。
  • Azure AI Foundry Agent Service:托管运行时以部署 agent,有现成的工具目录(Bing 搜索、代码解释器、文件检索)和支持 Model Context Protocol (MCP),用标准开放方式连接外部工具,不锁定供应商。

原框架运行在 .NET/Python 上,但模式是通用的。下面我用 Java 重新实现了这个模式,让你看到它在代码层面是如何工作的。

4. AI Agent 的核心架构

一个极简 agent 需要 4 个组件:Planner(就是 LLM),Tool(agent 可以调用的函数),Memory(对话历史/工具结果),Executor(协调循环)。Spring AI 通过 ChatClient 封装了 Executor 和 Planner,你只需声明 Tool。

4.1. Maven 依赖

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-azure-openai-spring-boot-starter</artifactId>
    <version>1.0.0</version>
</dependency>

配置 Azure OpenAI 连接(Microsoft 的 GPT 服务,通过 Azure 部署):

spring.ai.azure.openai.api-key=${AZURE_OPENAI_KEY}
spring.ai.azure.openai.endpoint=${AZURE_OPENAI_ENDPOINT}
spring.ai.azure.openai.chat.options.deployment-name=gpt-4o-mini

4.2. 为 agent 定义 Tool

Tool 是一个普通的 Java 方法,用 @Tool 标注,让模型知道它可以调用它以及用什么参数。

public record Order(String id, String status, BigDecimal total) {}

public interface OrderService {
    Order findById(String orderId);
}

@Service
public class OrderTools {

    private final OrderService orderService;

    public OrderTools(OrderService orderService) {
        this.orderService = orderService;
    }

    @Tool(description = "按订单号查询订单的状态和总金额")
    public String lookupOrder(String orderId) {
        Order order = orderService.findById(orderId);
        return "订单 %s:状态 %s,总金额 %s 越南盾"
            .formatted(order.id(), order.status(), order.total());
    }
}

@Tool 中的 description 是模型读取的内容,用来决定是否应该调用 lookupOrder — 写得清楚、具体,agent 会更准确地选择工具。

4.3. Agent 通过 ChatClient 自动调用工具

@Service
public class SupportAgentService {

    private final ChatClient chatClient;

    public SupportAgentService(ChatClient.Builder builder, OrderTools orderTools) {
        this.chatClient = builder
            .defaultSystem("""
                你是一个在线购物店的客户支持助手。
                如果问题涉及订单,请使用 lookupOrder 工具。
                回答简洁、友好,使用越南语。
                """)
            .defaultTools(orderTools)
            .build();
    }

    public String handle(String userMessage) {
        return chatClient.prompt()
            .user(userMessage)
            .call()
            .content();
    }
}

.defaultTools(orderTools) 这一行注册了工具集,ChatClient 在内部自动运行 ReAct 循环:发送 prompt 和工具列表给模型,如果模型返回调用 lookupOrder 的请求,Spring AI 自动执行相应的 Java 方法,将结果(Observation)返回给模型,最后接收最终答案。你不需要手写循环 — 这正是 Microsoft Agent Framework 和 Spring AI 在本质上做的相同的事情。

> handle("我的订单 ORD-001 在哪里?")
你的订单 ORD-001 正在配送中(SHIPPED),总金额 450,000 越南盾!

**注意:**模型只在“决定”需要时才调用工具 — 不是每个问题都会触发工具。如果用户问“店铺支持国际配送吗?”,模型会直接回答而不调用 lookupOrder。

5. 多 Agent:当一个 Agent 不够时

Microsoft ship 的突出能力之一是移交 — 在专门的 agent 之间转移对话。对于较小的问题,你可以用一个简单的路由器来模拟,无需复杂的多 agent 框架:

@Service
public class AgentRouter {

    private final SupportAgentService orderAgent;
    private final RefundAgentService refundAgent;

    public AgentRouter(SupportAgentService orderAgent, RefundAgentService refundAgent) {
        this.orderAgent = orderAgent;
        this.refundAgent = refundAgent;
    }

    public String route(String userMessage) {
        String lower = userMessage.toLowerCase();
        if (lower.contains("退款") || lower.contains("refund")) {
            return refundAgent.handle(userMessage);
        }
        return orderAgent.handle(userMessage);
    }
}

这个路由器充当“编排器”角色。在更大的规模上,选择哪个 agent 来处理这个决定本身也应该由 LLM 来做(通过意图分类模型),而不是硬编码 contains() — 手写代码只适合演示或非常小、稳定的意图集合。

6. 为工具和 Agent 编写测试

在 agent 系统中,最容易测试的部分就是工具 — 因为它是纯 Java 方法,不依赖 LLM。你应该仔细测试这部分,因为 agent 的“智能”只取决于它调用的工具的质量。

class OrderToolsTest {

    @Test
    void shouldReturnOrderSummaryWithStatusAndTotal() {
        OrderService fakeOrderService = orderId ->
            new Order(orderId, "SHIPPED", new BigDecimal("450000"));

        OrderTools tools = new OrderTools(fakeOrderService);

        String result = tools.lookupOrder("ORD-001");

        assertThat(result)
            .contains("ORD-001")
            .contains("SHIPPED")
            .contains("450000");
    }
}

因为 OrderService 只有一个方法 findById,我用 lambda 来做 fake 实现,而不是添加额外的 mock 框架依赖 — 对于这个单元测试很简洁。

class AgentRouterTest {

    @Test
    void shouldRouteRefundKeywordToRefundAgent() {
        SupportAgentService orderAgent = mock(SupportAgentService.class);
        RefundAgentService refundAgent = mock(RefundAgentService.class);
        when(refundAgent.handle(anyString())).thenReturn("已记录退款请求");

        AgentRouter router = new AgentRouter(orderAgent, refundAgent);

        String result = router.route("我想要退款订单 ORD-002");

        assertThat(result).isEqualTo("已记录退款请求");
        verify(refundAgent).handle(anyString());
        verify(orderAgent, never()).handle(anyString());
    }
}

**注意:**不要在单元测试中编写“端到端调用真实 LLM”的测试 — 结果不具有确定性(模型每次可能回答不同),调用 API 很贵,而且会让 CI 变慢/不稳定。应该 mock ChatClient 或按上面的方式分别测试工具/路由器;将真实 LLM 测试留给单独的集成测试(手动运行或夜间测试)。

7. 常见错误和何时不应该使用 Agent

  • 将 Agent 用于可以用 if-else 解决的事:如果选择行动的逻辑已知且固定(例如:当有 ORD-\d+ 格式的代码时总是查询订单),普通代码比让 LLM 每次“思考”便宜、快、易于测试得多。
  • 对敏感操作忽略防护栏:agent 有权调用删除数据、退款、发送邮件的工具 — 如果没有确认步骤(人类介入)或工具权限限制,一个 prompt 注入或模型推理错误可能造成真实后果。
  • 不限制循环次数:如果模型持续选错工具或数据不足以得出结论,ReAct 循环可能无限循环,浪费 token 和金钱。始终设置最大步数和超时。
  • 缺少可观测性:当 agent 回答错误时,你需要看到整个 Thought/Action/Observation 链来调试 — 没有详细的日志/追踪基本不可能找出模型“想了什么”导致结果错误。
  • 延迟和成本:循环中的每一步都是一次 LLM 调用。一个简单问题可能花 2-3 次调用(每次都需要几百毫秒到几秒)。对于需要立即响应的 API(例如低于 200ms),这类 agent 不合适。

8. 总结

Microsoft 将 Semantic Kernel 和 AutoGen 合并成一个框架这个事实表明了一个明确的趋势:AI Agent 正从“有趣演示”转向需要编排、状态、可观测性和严格权限控制的生产基础设施,就像其他企业系统一样。无论你使用 .NET、Python 还是 Java/Spring AI(如本文示例),基础架构仍然围绕四个块:Planner、Tool、Memory、Executor — 掌握这四个块,你就能理解和应用之后出现的任何 agent 框架。

AI AgentMicrosoftSpring AIAzure OpenAILLM