微软 Ship AI Agent 框架:详细解析与示例
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 框架。