Spring AI实战:从零构建AI Agent的MCP架构指南

1次阅读
没有评论

共计 4922 个字符,预计需要花费 13 分钟才能阅读完成。

image.webp

Spring AI 实战:从零构建 AI Agent 的 MCP 架构指南

背景痛点

在传统 AI 服务开发中,经常遇到以下问题:

Spring AI 实战:从零构建 AI Agent 的 MCP 架构指南

  • 代码紧耦合:业务逻辑与 AI 模型调用代码混杂在一起
  • 状态管理混乱:对话状态、上下文信息缺乏统一管理
  • 可扩展性差:新增功能或更换模型需要大量修改代码
  • 性能瓶颈:缺乏有效的并发控制和资源隔离机制

MCP 架构相比 Monolithic 架构的优势:

  1. 职责分离:各层专注单一职责
  2. 易于测试:可以分层独立测试
  3. 灵活扩展:各层可以独立演进
  4. 性能可控:资源使用可以精确管理

技术实现

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;
    }
}

避坑指南

  1. 避免在 Controller 中直接调用 LLM
  2. 会导致业务逻辑与 API 层耦合
  3. 难以进行单元测试
  4. 无法统一处理重试和降级逻辑

  5. 异步回调的线程安全

  6. 使用 ThreadLocal 变量要特别小心
  7. 确保所有共享状态都是线程安全的
  8. 考虑使用不可变对象传递数据

  9. 生产环境日志规范

  10. 使用 MDC 添加请求追踪 ID
  11. 避免记录敏感信息
  12. 结构化日志格式示例:
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 功能:

  1. 实现一个技能插件系统
  2. 创建 Skill 接口

    public interface Skill {boolean supports(String intent);
        SkillResult execute(SkillInput input);
    }

  3. 实现天气查询技能

    @Service
    public class WeatherSkill implements Skill {
        @Override
        public boolean supports(String intent) {return "weather_query".equals(intent);
        }
    
        @Override
        public SkillResult execute(SkillInput input) {// 调用天气 API}
    }

  4. 在 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 添加新技能而不需要修改核心逻辑。

正文完
 0
评论(没有评论)