Spring AI Alibaba实战:通过MCP协议串联Graph编排与RAG检索

本文介绍如何通过MCP协议实现Graph编排层与RAG检索层的串联,外部AI工具通过MCP作为统一入口触发完整链路。涉及代码审查场景的完整实现,包括MCP工具注册、Graph子图编排、RAG知识注入、并行节点执行、HITL人工确认等核心机制。

技术栈:Spring AI 1.1.6 · Spring Boot 3.5.5 · JDK 21 · MCP SDK 0.18.2

前置知识 :建议先阅读MCP协议原理与Java实现Spring AI Alibaba Graph编排实战等系列文章


一、问题背景

RAG与Graph是AI Agent中两个核心能力:

  • Graph:支持多步骤任务编排,具备并行执行、HITL中断、条件分支等能力
  • RAG:从知识库检索上下文,为LLM提供领域知识注入

单独使用各有优势,但实际应用中常面临一个问题:外部系统如何触发这条链路?

典型场景:

  • Claude Desktop想调用代码审查能力
  • Cursor IDE需要接入内部知识库检索
  • 第三方系统需要触发Graph编排流程

这些外部系统无法直接调用内部模块,需要一层协议适配------这正是MCP协议的价值所在。


二、架构设计

2.1 三层职责划分

层级 模块 职责 协议
接入层 MCP Server 接收外部请求,协议解析与路由 MCP/SSE
编排层 Graph Supervisor 子图调度,任务流控制 Java方法调用
执行层 RAG + LLM 知识检索,具体推理执行 Feign/HTTP

核心原则:接入层(MCP)做协议翻译,编排层(Graph)做任务调度,执行层(RAG+LLM)做具体推理。

2.2 完整调用链路

bash 复制代码
外部 MCP Client(Claude Desktop / Cursor / MCP Inspector)
  ↓ SSE 长连接
McpCodeReviewServer(8091端口)
  ↓ MCP协议路由
GraphMcpToolContributor.graphCodeReview()
  ↓ Java方法调用
SupervisorGraphTestService.runSupervisor()
  ↓ 启动主图
Supervisor 主图 → IntentRouter → CodeReview 子图
  ↓
  RAGRetrievalNode(查编码规范知识库)
  ↓
  ┌─ StyleCheckNode(正则规则检查)
  └─ CodeCheckNode(LLM 语义审查)
  ↓ 并行执行后合并
  RiskAssessNode(风险评分)
  ↓
  评分 ≥ 阈值 → ReportNode(直接返回报告)
  评分 < 阈值 → HumanReviewNode(人工确认)
  ↓
返回 Markdown 格式审查报告

三、MCP工具注册

3.1 Maven依赖

bash 复制代码
<!-- dream-ai-graph/pom.xml -->
<dependencies>
    <!-- MCP SDK -->
    <dependency>
        <groupId>io.modelcontextprotocol</groupId>
        <artifactId>spring-ai-starter-mcp-server</artifactId>
        <version>0.18.2</version>
    </dependency>
    
    <!-- Spring AI Alibaba -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-ai-alibaba-starter</artifactId>
        <version>1.1.2.2</version>
    </dependency>
</dependencies>

3.2 MCP Server配置

bash 复制代码
# application.yml
spring:
  ai:
    mcp:
      server:
        enabled: true
        port: 8091
        name: dream-saas-graph
        version: 1.0.0
        description: Dream-SaaS Graph编排服务

dream-saas:
  mcp:
    server:
      enabled: true

3.3 核心接点:GraphMcpToolContributor

这是串联的关键类,同时实现MCP工具接口和Graph调度逻辑:

bash 复制代码
package com.zhu.dream.ai.graph.mcp;

@Component
@Lazy
@ConditionalOnProperty(
    prefix = "dream-saas.mcp.server",
    name = "enabled",
    havingValue = "true"
)
public class GraphMcpToolContributor implements McpToolContributor {

    private static final Logger log = LoggerFactory.getLogger(GraphMcpToolContributor.class);
    
    private final SupervisorGraphTestService supervisorGraphTestService;
    private final ObjectMapper objectMapper;

    public GraphMcpToolContributor(
            SupervisorGraphTestService supervisorGraphTestService,
            ObjectMapper objectMapper) {
        this.supervisorGraphTestService = supervisorGraphTestService;
        this.objectMapper = objectMapper;
    }

    @Override
    public String moduleId() {
        return McpModuleIds.GRAPH;
    }

    @Override
    public Object[] toolObjects() {
        return new Object[] {this};
    }

    /**
     * 完整代码审查链路
     * 内部调用Graph编排,执行RAG检索→LLM审查→风险评估→报告生成
     */
    @Tool(
        name = "graph_code_review",
        description = "通过Graph编排执行完整代码审查:RAG知识检索 + LLM深度审查 + 风险评估 + Markdown报告生成"
    )
    public String graphCodeReview(
            @ToolParam(description = "待审查的源代码") String code,
            @ToolParam(description = "审查说明,如重点关注空指针、并发安全等", required = false) String instruction) {
        
        long start = System.nanoTime();
        log.info("[MCP-GRAPH] invoke tool=graph_code_review codeChars={} instruction={}",
                code != null ? code.length() : 0,
                StringUtils.hasText(instruction) ? instruction : "(none)");
        
        try {
            // 核心调用:触发Graph编排
            Map<String, Object> result = supervisorGraphTestService.runSupervisor(
                    StringUtils.hasText(instruction) ? instruction : "",
                    StringUtils.hasText(code) ? code : "");
            
            String json = toJson(result);
            long durationMs = (System.nanoTime() - start) / 1_000_000L;
            log.info("[MCP-GRAPH] success tool=graph_code_review durationMs={}", durationMs);
            
            return json;
        } catch (Exception ex) {
            long durationMs = (System.nanoTime() - start) / 1_000_000L;
            log.warn("[MCP-GRAPH] failed tool=graph_code_review durationMs={}: {}", 
                    durationMs, ex.getMessage(), ex);
            return toJson(Map.of("error", ex.getMessage(), "type", ex.getClass().getSimpleName()));
        }
    }

    /**
     * 意图路由分析
     * 仅分析用户意图,不执行具体子图
     */
    @Tool(
        name = "graph_route_analyze",
        description = "分析用户意图路由(ragRoute/reviewCodeRoute/chatRoute),不执行子图"
    )
    public String graphRouteAnalyze(
            @ToolParam(description = "用户输入文本") String query) {
        
        long start = System.nanoTime();
        log.info("[MCP-GRAPH] invoke tool=graph_route_analyze query={}", query);
        
        try {
            Map<String, Object> result = supervisorGraphTestService.analyzeRoute(
                    StringUtils.hasText(query) ? query : "");
            
            String json = toJson(result);
            long durationMs = (System.nanoTime() - start) / 1_000_000L;
            log.info("[MCP-GRAPH] success tool=graph_route_analyze durationMs={}", durationMs);
            
            return json;
        } catch (Exception ex) {
            long durationMs = (System.nanoTime() - start) / 1_000_000L;
            log.warn("[MCP-GRAPH] failed tool=graph_route_analyze durationMs={}: {}",
                    durationMs, ex.getMessage());
            return toJson(Map.of("error", ex.getMessage()));
        }
    }

    private String toJson(Object value) {
        try {
            return objectMapper.writeValueAsString(value);
        } catch (JsonProcessingException e) {
            log.error("序列化失败", e);
            return "{\"error\":\"序列化失败: " + e.getMessage() + "\"}";
        }
    }
}

关键设计点

  1. @ConditionalOnProperty 控制是否启用MCP服务
  2. @Lazy 延迟加载,避免循环依赖
  3. 方法返回值是JSON字符串,MCP协议要求工具返回文本
  4. 统一错误处理,返回结构化错误信息

四、Graph主图调度

4.1 Supervisor主图入口

bash 复制代码
@Service
public class SupervisorGraphTestService {

    private final ApplicationContext applicationContext;
    private final GraphCodeReviewSubGraph codeReviewSubGraph;

    public SupervisorGraphTestService(
            ApplicationContext applicationContext,
            GraphCodeReviewSubGraph codeReviewSubGraph) {
        this.applicationContext = applicationContext;
        this.codeReviewSubGraph = codeReviewSubGraph;
    }

    /**
     * 主图入口:根据意图路由到对应子图
     */
    public Map<String, Object> runSupervisor(String instruction, String code) {
        // 1. 意图识别
        String intent = analyzeIntent(code, instruction);
        
        // 2. 根据意图路由到子图
        switch (intent) {
            case "reviewCode":
                return runCodeReviewSubGraph(code, instruction);
            case "ragRoute":
                return runRagRoute(instruction);
            default:
                return Map.of("error", "未知意图: " + intent);
        }
    }

    /**
     * 意图识别:根据code特征判断任务类型
     */
    private String analyzeIntent(String code, String instruction) {
        // 代码非空 → 代码审查
        if (StringUtils.hasText(code)) {
            return "reviewCode";
        }
        // 纯文本 → RAG检索
        if (StringUtils.hasText(instruction)) {
            return "ragRoute";
        }
        return "unknown";
    }
}

4.2 CodeReview子图执行

bash 复制代码
@Component
public class GraphCodeReviewSubGraph {

    private final RAGRetrievalNode ragRetrievalNode;
    private final StyleCheckNode styleCheckNode;
    private final CodeCheckNode codeCheckNode;
    private final RiskAssessNode riskAssessNode;
    private final HumanReviewNode humanReviewNode;
    private final ReportNode reportNode;

    /**
     * 构建CodeReview子图
     * 流程:RAG检索 → 并行执行(规则检查+LLM审查) → 风险评估 → 报告生成
     */
    @Bean
    public Graph codeReviewGraph() {
        return Graph.builder()
            .id("code-review")
            .stateInspector(new JsonStringStateInspector())
            
            // 开始节点:接收用户消息
            .start(USER_MESSAGE)
            
            // 第一步:RAG检索注入领域知识
            .add(ragRetrievalNode)  // RAGRetrievalNode
            .add(RAGooterNode.class) // 拼装prompt
            
            // 第二步:并行执行规则检查与LLM审查
            .parallel(
                styleCheckNode,  // StyleCheckNode:正则规则
                codeCheckNode     // CodeCheckNode:LLM语义
            )
            
            // 第三步:风险评估
            .add(riskAssessNode)
            
            // 第四步:条件分支
            .choice(
                // 条件1:风险评分>=阈值 → 直接生成报告
                c -> c.if_(state -> state.get("needsHumanReview", Boolean.class))
                       .then(humanReviewNode),  // HITL人工确认
                // 条件2:风险评分<阈值 → 直接返回
                c -> c.else_()
                       .then(reportNode)  // 生成Markdown报告
            )
            
            .end(FINAL_OUTPUT)
            .build();
    }

    /**
     * 执行子图
     */
    public Map<String, Object> execute(String code, String instruction) {
        GraphState state = new GraphState();
        state.put("code", code);
        state.put("instruction", instruction);
        state.put("userMessage", instruction + "\n\n待审查代码:\n" + code);
        
        return codeReviewGraph().execute(state);
    }
}

五、核心节点实现

5.1 RAG检索节点

bash 复制代码
@Component
@Singleton
public class RAGRetrievalNode implements Node {

    private static final Logger log = LoggerFactory.getLogger(RAGRetrievalNode.class);
    private static final int TOP_K = 5;
    
    private final KnowledgeBaseService knowledgeBaseService;

    public RAGRetrievalNode(KnowledgeBaseService knowledgeBaseService) {
        this.knowledgeBaseService = knowledgeBaseService;
    }

    @Override
    public GraphState execute(GraphState state) {
        String code = state.get("code", String.class);
        String instruction = state.get("instruction", String.class);
        
        log.info("[RAG] 开始检索知识库 codeLength={}", code.length());
        
        // 构造检索query
        String query = buildRetrievalQuery(code, instruction);
        
        // 调用RAG服务检索编码规范
        List<KnowledgeChunk> chunks = knowledgeBaseService.retrieve(query, TOP_K);
        
        // 拼装检索结果作为上下文
        String context = chunks.stream()
            .map(KnowledgeChunk::getContent)
            .collect(Collectors.joining("\n\n---\n\n"));
        
        state.put("ragContext", context);
        state.put("ragChunkCount", chunks.size());
        
        log.info("[RAG] 检索完成 chunks={}", chunks.size());
        return state;
    }

    private String buildRetrievalQuery(String code, String instruction) {
        return String.format("%s\n\n%s", 
            StringUtils.hasText(instruction) ? instruction : "",
            StringUtils.hasText(code) ? code.substring(0, Math.min(code.length(), 500)) : ""
        );
    }
}

RAG检索的价值:解决"LLM不知道团队编码规范"的问题。通过检索将规范从知识库查出来,注入后续LLM节点的prompt。

5.2 规则检查节点

bash 复制代码
@Component
@Singleton
public class StyleCheckNode implements Node {

    private static final Logger log = LoggerFactory.getLogger(StyleCheckNode.class);
    
    // 内置规则
    private static final List<CheckRule> RULES = List.of(
        CheckRule.builder()
            .name("命名规范-方法名")
            .pattern("\\b[a-z][a-zA-Z0-9]*\\([^(]*\\)")
            .message("方法名应使用驼峰命名")
            .severity(Severity.WARNING)
            .build(),
        CheckRule.builder()
            .name("空指针风险-equals对比")
            .pattern("\"[^\"]*\".equals\\([^)]+\\)")
            .message("建议使用Objects.equals()避免空指针")
            .severity(Severity.ERROR)
            .build(),
        // ... 更多规则
    );

    @Override
    public GraphState execute(GraphState state) {
        String code = state.get("code", String.class);
        
        log.info("[StyleCheck] 开始规则检查");
        
        List<ReviewIssue> issues = new ArrayList<>();
        for (CheckRule rule : RULES) {
            Matcher matcher = Pattern.compile(rule.getPattern()).matcher(code);
            while (matcher.find()) {
                issues.add(ReviewIssue.builder()
                    .rule(rule.getName())
                    .message(rule.getMessage())
                    .severity(rule.getSeverity())
                    .location(matcher.start() + "-" + matcher.end())
                    .snippet(matcher.group())
                    .build());
            }
        }
        
        state.put("styleIssues", issues);
        log.info("[StyleCheck] 检查完成 issues={}", issues.size());
        
        return state;
    }
}

规则检查的价值:快速定位格式问题和明显错误,无需LLM调用,毫秒级完成。

5.3 LLM审查节点

bash 复制代码
@Component
@Singleton
public class CodeCheckNode implements Node {

    private static final Logger log = LoggerFactory.getLogger(CodeCheckNode.class);
    
    private final ChatClient chatClient;
    private final ObjectMapper objectMapper;

    public CodeCheckNode(ChatClient chatClient, ObjectMapper objectMapper) {
        this.chatClient = chatClient;
        this.objectMapper = objectMapper;
    }

    @Override
    public GraphState execute(GraphState state) {
        String code = state.get("code", String.class);
        String ragContext = state.get("ragContext", String.class);
        
        log.info("[CodeCheck] 开始LLM审查");
        
        // 构造prompt
        String prompt = buildPrompt(code, ragContext);
        
        // 调用LLM
        String response = chatClient.prompt()
            .user(prompt)
            .call()
            .content();
        
        // 解析LLM返回的审查结果
        List<ReviewIssue> issues = parseIssues(response);
        
        state.put("llmIssues", issues);
        log.info("[CodeCheck] LLM审查完成 issues={}", issues.size());
        
        return state;
    }

    private String buildPrompt(String code, String ragContext) {
        return String.format("""
            # 代码审查任务
            
            ## 编码规范上下文
            %s
            
            ## 待审查代码
            ```java
            %s
            ```
            
            ## 审查要求
            请从以下维度审查代码:
            1. 空指针风险
            2. 并发安全问题
            3. 资源泄漏
            4. 逻辑错误
            5. 性能问题
            
            返回JSON格式的审查结果:
            ```json
            {
              "issues": [
                {"severity": "ERROR|WARNING", "message": "...", "line": N}
              ]
            }
            ```
            """, ragContext, code);
    }

    private List<ReviewIssue> parseIssues(String response) {
        try {
            // 提取JSON部分
            String json = extractJson(response);
            Map<String, Object> result = objectMapper.readValue(json, Map.class);
            List<Map<String, Object>> issueList = (List<Map<String, Object>>) result.get("issues");
            
            return issueList.stream()
                .map(m -> ReviewIssue.builder()
                    .severity(Severity.valueOf(String.valueOf(m.get("severity")).toUpperCase()))
                    .message(String.valueOf(m.get("message")))
                    .line((Integer) m.get("line"))
                    .build())
                .collect(Collectors.toList());
        } catch (Exception e) {
            log.warn("[CodeCheck] 解析LLM返回失败: {}", e.getMessage());
            return Collections.emptyList();
        }
    }
}

LLM审查的价值:理解上下文语义,发现规则检查抓不到的逻辑漏洞,如业务逻辑错误、安全隐患等。

5.4 风险评估节点

bash 复制代码
@Component
@Singleton
public class RiskAssessNode implements Node {

    private static final Logger log = LoggerFactory.getLogger(RiskAssessNode.class);
    private static final int THRESHOLD = 70;  // 风险评分阈值
    
    // 严重程度权重
    private static final int ERROR_WEIGHT = 10;
    private static final int WARNING_WEIGHT = 3;

    @Override
    public GraphState execute(GraphState state) {
        List<ReviewIssue> styleIssues = state.get("styleIssues", List.class);
        List<ReviewIssue> llmIssues = state.get("llmIssues", List.class);
        
        // 合并所有问题
        List<ReviewIssue> allIssues = new ArrayList<>();
        if (styleIssues != null) allIssues.addAll(styleIssues);
        if (llmIssues != null) allIssues.addAll(llmIssues);
        
        // 计算风险评分
        int riskScore = calculateRiskScore(allIssues);
        
        // 判断是否需要人工确认
        boolean needsHumanReview = riskScore < THRESHOLD;
        
        state.put("riskScore", riskScore);
        state.put("needsHumanReview", needsHumanReview);
        state.put("allIssues", allIssues);
        
        log.info("[RiskAssess] 风险评分={} needsHumanReview={}", riskScore, needsHumanReview);
        
        return state;
    }

    private int calculateRiskScore(List<ReviewIssue> issues) {
        if (issues == null || issues.isEmpty()) {
            return 100;  // 无问题满分
        }
        
        int penalty = issues.stream()
            .mapToInt(issue -> {
                switch (issue.getSeverity()) {
                    case ERROR: return ERROR_WEIGHT;
                    case WARNING: return WARNING_WEIGHT;
                    default: return 1;
                }
            })
            .sum();
        
        return Math.max(0, 100 - penalty);
    }
}

风险评估的价值:量化代码质量,决定是否需要人工介入。评分>=阈值自动放行,<阈值触发HITL中断。

5.5 HITL人工确认节点

bash 复制代码
@Component
@Singleton
public class HumanReviewNode implements Node {

    private static final Logger log = LoggerFactory.getLogger(HumanReviewNode.class);
    
    private final AtomicReference<Map<String, Object>> pendingReview = new AtomicReference<>();

    @Override
    public GraphState execute(GraphState state) {
        List<ReviewIssue> issues = state.get("allIssues", List.class);
        Integer riskScore = state.get("riskScore", Integer.class);
        
        // 构造人工确认请求
        Map<String, Object> reviewRequest = Map.of(
            "issues", issues,
            "riskScore", riskScore,
            "status", "PENDING"
        );
        
        // 保存待确认状态(实际实现中可能存入数据库或消息队列)
        pendingReview.set(reviewRequest);
        
        log.info("[HITL] 触发人工确认 riskScore={} issues={}", riskScore, issues.size());
        
        // 设置状态,等待人工确认
        state.put("status", "AWAITING_HUMAN_REVIEW");
        state.put("reviewRequest", reviewRequest);
        
        return state;
    }

    /**
     * 处理人工确认结果
     */
    public GraphState handleHumanDecision(GraphState state, boolean approved, String comment) {
        log.info("[HITL] 人工确认结果 approved={} comment={}", approved, comment);
        
        state.put("humanApproved", approved);
        state.put("humanComment", comment);
        state.put("status", approved ? "APPROVED" : "REJECTED");
        
        return state;
    }
}

HITL的价值:AI拿不准时有人类兜底,既保证自动化效率,又不失安全性。适用于高风险操作或低置信度场景。


六、调用验证

6.1 启动服务

bash 复制代码
# 1. 启动MCP Server(8091端口)
cd dream-ai-code-review && mvn spring-boot:run

# 2. 启动Graph服务(8098端口)
cd dream-ai-graph && mvn spring-boot:run

# 3. 验证MCP Server健康状态
curl http://localhost:8091/actuator/health

6.2 MCP Inspector测试

bash 复制代码
# 安装MCP Inspector
npx @modelcontextprotocol/inspector

# 或使用CLI直接调用
npx @modelcontextprotocol/cli call graph_code_review \
  --arg '{
    "code": "public class UserService {\n  private String name;\n  \n  public void setName(String name) {\n    this.name = name;\n  }\n  \n  public String getName() {\n    return name;\n  }\n}",
    "instruction": "请重点关注空指针风险和命名规范"
  }'

6.3 预期输出

bash 复制代码
{
  "success": true,
  "data": {
    "riskScore": 75,
    "needsHumanReview": false,
    "report": "# 代码审查报告\n\n## 问题汇总\n| 严重程度 | 问题描述 | 位置 |\n|---------|---------|------|\n| WARNING | 成员变量name缺少非空校验 | setName() |\n\n## 详细分析\n...(Markdown格式报告)",
    "stats": {
      "totalIssues": 1,
      "errors": 0,
      "warnings": 1
    }
  }
}

6.4 日志观察点

bash 复制代码
# 启动时日志
[MCP] GraphMcpToolContributor registered, tools: [graph_code_review, graph_route_analyze]
[MCP] Server started on port 8091

# 调用时日志链路
[MCP-GRAPH] invoke tool=graph_code_review codeChars=267
[Supervisor] Intent recognized: reviewCode
[Supervisor] Launching CodeReview sub-graph
[RAG] Retrieving from knowledge base chunks=5
[StyleCheck] Rule check completed issues=2
[CodeCheck] LLM review completed issues=1
[RiskAssess] Score calculated: 75 >= threshold(70), auto-pass
[Report] Generating Markdown report
[MCP-GRAPH] success tool=graph_code_review durationMs=2340

七、MCP串联适用场景分析

7.1 适合场景

场景 说明 收益
外部AI工具接入 Claude Desktop/Cursor/Zed等支持MCP 一次实现,多方调用
跨语言集成 Java后端 + Python前端 + TS CLI 统一协议,无需多语言SDK
工具发现与版本管理 MCP原生支持动态发现 新增工具无需更新文档

7.2 不适合场景

场景 替代方案 原因
内部微服务调用 Feign/Dubbo MCP协议开销不必要
毫秒级延迟场景 直接方法调用 SSE/HTTP有网络开销
单体应用内部 直接Bean调用 无需网络传输

7.3 判断标准

bash 复制代码
问自己:这个调用是"外部系统触发"还是"内部模块协作"?
- 外部 → MCP
- 内部 → Feign/Direct

八、项目结构

bash 复制代码
dream-ai/
├── dream-ai-graph/                    # Graph编排模块
│   └── src/main/java/com/zhu/dream/ai/graph/
│       ├── mcp/
│       │   ├── GraphMcpToolContributor.java   # ⭐ MCP→Graph接点
│       │   └── McpModuleIds.java               # 模块ID定义
│       ├── subgraph/
│       │   └── codereview/
│       │       ├── GraphCodeReviewSubGraph.java  # ⭐ 子图定义
│       │       ├── RAGRetrievalNode.java          # RAG检索节点
│       │       ├── StyleCheckNode.java            # 规则检查节点
│       │       ├── CodeCheckNode.java             # LLM审查节点
│       │       ├── RiskAssessNode.java            # 风险评估节点
│       │       ├── HumanReviewNode.java           # HITL节点
│       │       └── ReportNode.java                # 报告生成节点
│       └── supervisor/
│           └── SupervisorGraphTestService.java     # ⭐ 主图调度器
│
├── dream-ai-code-review/              # MCP Server模块
│   └── src/main/java/com/zhu/ai/agent/codeReview/
│       ├── mcp/
│       │   ├── McpCodeReviewServer.java
│       │   └── CodeReviewMcpContributor.java
│       └── service/
│           └── CodeReviewServiceNew.java
│
└── dream-ai-rag/                      # RAG知识库模块
    └── ...

关键串联文件只有3个

  1. GraphMcpToolContributor.java --- MCP工具定义,内部调用Graph
  2. SupervisorGraphTestService.java --- 主图调度器,路由到子图
  3. GraphCodeReviewSubGraph.java --- 子图定义,编排执行流程

九、踩坑记录

9.1 MCP方法返回类型

问题:MCP协议要求工具返回文本,不能直接返回对象

解决方案

bash 复制代码
// ❌ 错误:返回了Java对象
public Map<String, Object> graphCodeReview(String code) { ... }

// ✅ 正确:序列化为JSON字符串
public String graphCodeReview(String code) {
    return objectMapper.writeValueAsString(result);
}

9.2 循环依赖

问题SupervisorGraphTestService注入GraphCodeReviewSubGraphGraphCodeReviewSubGraph又注入依赖SupervisorGraphTestService的节点

解决方案 :使用@Lazy注解延迟加载

bash 复制代码
@Component
@Lazy
public class GraphMcpToolContributor { ... }

9.3 Graph状态传递

问题:并行节点执行后,状态合并顺序不确定

解决方案:显式合并结果

bash 复制代码
// 在并行节点之后显式添加合并节点
.parallel(styleCheckNode, codeCheckNode)
.add(ResultMergerNode.class)  // 显式合并allIssues

9.4 HITL超时处理

问题:人工确认可能长时间不响应

解决方案:设置超时,自动拒绝或使用上次结果

bash 复制代码
@Scheduled(fixedDelay = 300000)  // 5分钟超时检查
public void checkPendingReviews() {
    // 超时逻辑处理
}

总结

MCP串联Graph与RAG的核心要点:

  1. 接点在GraphMcpToolContributor:MCP工具实现内部调用Graph调度器,两个模块通过它接在一起
  2. 分层各司其职:接入层(MCP)协议翻译,编排层(Graph)任务调度,执行层(RAG+LLM)具体推理
  3. MCP是对外接口:外部系统用MCP接入,内部模块用Feign/Direct调用
  4. HITL保障安全:AI拿不准时有兜底,既保证自动化效率,又不失安全性

技术栈

  • spring-ai-bom 1.1.6
  • spring-ai-alibaba 1.1.2.2
  • MCP SDK 0.18.2
  • Spring Boot 3.5.5
  • JDK 21

相关推荐
勇敢的先登1 小时前
MCP 是什么?为什么 Function Call 之后还需要它
agent·ai编程
付玉祥1 小时前
Agent Loop 的运行流程:从单体循环到三阶段 Pipeline
agent
码流怪侠2 小时前
【GitHub】 Headroom 深度解析:AI Agent 上下文压缩层的完整技术拆解
人工智能·github·agent
leeyi2 小时前
Tool 组件:让 Agent 学会「动手」的统一接口
aigc·agent·ai编程
啾啾Fun2 小时前
【LLM应用可靠性】3-Agent 事故响应:当 AI 系统行为异常时的 SRE Runbook
ai·llm·agent·生产应用
张申傲2 小时前
拆解 harness9(4):Skills 系统架构
aigc·agent·deepseek·harness
小七-七牛开发者2 小时前
周一上线|瑞幸把咖啡做进 CLI,Fable 5 短暂登场,Stonk Rider 骑上 K 线图
ai·chatgpt·大模型·agent·claude·codex·skill·claudecode·ai coding
Solis程序员2 小时前
Raft:分布式系统的定海神针
java·分布式·kafka·rabbitmq·agent·raft
云烟成雨TD2 小时前
Agent Scope Java 2.x 系列【13】权限系统
java·人工智能·agent