共计 4922 个字符,预计需要花费 13 分钟才能阅读完成。
Spring AI 实战:从零构建 AI Agent 的 MCP 架构指南
背景痛点
在传统 AI 服务开发中,经常遇到以下问题:

- 代码紧耦合:业务逻辑与 AI 模型调用代码混杂在一起
- 状态管理混乱:对话状态、上下文信息缺乏统一管理
- 可扩展性差:新增功能或更换模型需要大量修改代码
- 性能瓶颈:缺乏有效的并发控制和资源隔离机制
MCP 架构相比 Monolithic 架构的优势:
- 职责分离:各层专注单一职责
- 易于测试:可以分层独立测试
- 灵活扩展:各层可以独立演进
- 性能可控:资源使用可以精确管理
技术实现
MCP 架构核心组件
flowchart TD
A[Client] --> B[Presenter]
B --> C[Controller]
C --> D[Model]
D --> E[LLM Service]
D --> F[Knowledge Base]
1. Model 层实现
@Service
public class AiModelService {
private final OpenAiChatClient chatClient;
private final KnowledgeBaseRepository knowledgeRepo;
@Autowired
public AiModelService(OpenAiChatClient chatClient,
KnowledgeBaseRepository knowledgeRepo) {
this.chatClient = chatClient;
this.knowledgeRepo = knowledgeRepo;
}
@Retryable(maxAttempts = 3, backoff = @Backoff(delay = 1000))
public CompletionResult generateResponse(String prompt) {
// 结合知识库和 LLM 生成响应
String context = knowledgeRepo.findRelevantContext(prompt);
Prompt enhancedPrompt = new Prompt(context + "\n" + prompt);
return chatClient.generate(enhancedPrompt);
}
}
2. Controller 层实现
@Service
public class AgentController {
private final AiModelService modelService;
private final ConversationStateManager stateManager;
public AgentResponse handleRequest(AgentRequest request) {
// 1. 获取或创建对话状态
ConversationState state = stateManager.getOrCreateState(request.getSessionId());
// 2. 构建完整提示
String fullPrompt = buildPrompt(request, state);
// 3. 调用 Model 层
CompletionResult result = modelService.generateResponse(fullPrompt);
// 4. 更新对话状态
state.update(result);
stateManager.saveState(state);
return result;
}
}
3. Presenter 层实现
@RestController
@RequestMapping("/api/agent")
public class AgentPresenter {
private final AgentController agentController;
@PostMapping
public ResponseEntity<StandardResponse> handleApiRequest(@RequestBody ApiRequest request) {
try {
AgentResponse response = agentController.handleRequest(convertToAgentRequest(request));
return ResponseEntity.ok(StandardResponse.success(response));
} catch (Exception e) {return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(StandardResponse.error(e.getMessage()));
}
}
}
进阶考量
监控与性能优化
@Configuration
public class MonitoringConfig {
@Bean
public MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {return registry -> registry.config().commonTags(
"application", "ai-agent",
"region", System.getenv("REGION")
);
}
}
@Service
public class AiModelService {
private final Timer llmTimer;
@Autowired
public AiModelService(MeterRegistry registry) {this.llmTimer = registry.timer("llm.invocation.time");
}
public CompletionResult generateResponse(String prompt) {return llmTimer.record(() -> {// 实际 LLM 调用代码});
}
}
并发控制配置
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {@Bean("llmTaskExecutor")
public Executor getAsyncExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("LLM-Executor-");
executor.initialize();
return executor;
}
}
状态存储方案
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, ConversationState> redisTemplate(RedisConnectionFactory connectionFactory) {RedisTemplate<String, ConversationState> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
template.setDefaultSerializer(new Jackson2JsonRedisSerializer<>(ConversationState.class));
return template;
}
}
@Service
public class RedisStateManager implements ConversationStateManager {
private final RedisTemplate<String, ConversationState> redisTemplate;
@Override
public ConversationState getOrCreateState(String sessionId) {ConversationState state = redisTemplate.opsForValue().get(sessionId);
if (state == null) {state = new ConversationState(sessionId);
redisTemplate.opsForValue().set(sessionId, state, Duration.ofHours(2));
}
return state;
}
}
避坑指南
- 避免在 Controller 中直接调用 LLM
- 会导致业务逻辑与 API 层耦合
- 难以进行单元测试
-
无法统一处理重试和降级逻辑
-
异步回调的线程安全
- 使用 ThreadLocal 变量要特别小心
- 确保所有共享状态都是线程安全的
-
考虑使用不可变对象传递数据
-
生产环境日志规范
- 使用 MDC 添加请求追踪 ID
- 避免记录敏感信息
- 结构化日志格式示例:
logger.info("LLM invocation completed",
kv("sessionId", sessionId),
kv("durationMs", duration),
kv("model", modelName));
完整依赖配置
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
</dependencies>
动手实验
尝试扩展 Agent 功能:
- 实现一个技能插件系统
-
创建 Skill 接口
public interface Skill {boolean supports(String intent); SkillResult execute(SkillInput input); } -
实现天气查询技能
@Service public class WeatherSkill implements Skill { @Override public boolean supports(String intent) {return "weather_query".equals(intent); } @Override public SkillResult execute(SkillInput input) {// 调用天气 API} } -
在 Controller 中集成技能系统
@Service public class AgentController { private final List<Skill> skills; public AgentResponse handleRequest(AgentRequest request) {for (Skill skill : skills) {if (skill.supports(request.getIntent())) {return skill.execute(request); } } // 默认 LLM 处理 } }
通过这种方式,你可以轻松地为 Agent 添加新技能而不需要修改核心逻辑。
正文完
