
在实际企业招聘和求职场景中简历与岗位的匹配度分析长期依赖人工筛选效率低下且主观性强。随着大语言模型能力的提升结合其强大的语义理解能力我们可以构建一个智能化的AI Agent系统自动完成简历解析、岗位分析、技能匹配和求职规划。本文将基于Spring Boot、Spring AI以及新兴的MCPModel Context Protocol协议手把手带你搭建一个具备实用价值的“AI Agent岗位匹配与求职规划系统”。这个系统将不再是简单的关键词匹配而是通过AI Agent理解岗位描述中的隐性要求如“抗压能力”、“团队协作”和简历中的项目经验、技能熟练度进行多维度、可解释的匹配度评分并为求职者生成个性化的能力提升建议。我们将从零开始涵盖环境搭建、核心模块设计、Spring AI集成、MCP Server开发、匹配算法实现到最终的系统验证与问题排查确保每一步都有清晰的操作、代码和解释。1. 理解系统核心Spring AI、MCP与AI Agent的协同在动手编码之前必须理清几个核心概念及其在本系统中的作用这决定了后续架构设计的合理性。1.1 Spring AI统一的大模型调用抽象层Spring AI是一个Spring官方项目旨在为Java应用提供一套统一的API来调用各种大语言模型如OpenAI GPT、Azure OpenAI、Ollama本地模型等。它的核心价值在于解耦你的业务代码不直接依赖某个特定AI服务商的SDK而是通过Spring AI的ChatClient、EmbeddingClient等接口进行交互。当需要切换模型提供商时只需修改配置无需重写代码。在本系统中我们主要使用Spring AI的以下能力ChatClient: 用于与LLM对话完成简历摘要生成、岗位要求分析、匹配度评述等文本生成任务。EmbeddingClient: 用于将文本如岗位描述、简历技能转换为高维向量。这是实现语义匹配而非关键词匹配的技术基础。VectorStore: 用于存储这些向量并执行相似性搜索。我们可以将岗位向量化后存入向量数据库当有新的简历输入时快速找到最相关的岗位。1.2 MCPModel Context Protocol为AI Agent注入“工具”能力MCP是一个由Anthropic等公司推动的开放协议它定义了一套标准让AI模型如Claude能够安全、可控地调用外部工具、访问数据和执行操作。你可以把它理解为AI模型的“插件”或“驱动程序”标准。在本系统中MCP扮演着能力扩展者的角色。一个只会聊天的AI Agent用处有限但通过MCP我们可以让AI Agent获得以下“超能力”访问实时数据例如连接招聘网站API获取最新的岗位列表。执行具体操作例如调用简历解析服务、查询技能图谱数据库、甚至模拟投递操作。处理特定格式文件例如读取用户上传的PDF、Word格式的简历。我们将实现一个MCP Server它对外暴露一系列工具Tools如parse_resume、search_jobs、calculate_match_score。然后我们的AI Agent通过Spring AI构建可以“理解”这些工具的描述并在需要时“决定”调用哪个工具并将结果整合到对话中。1.3 AI Agent具备自主规划与执行能力的智能体AI Agent不是简单的聊天机器人。它是一个具有目标、能够规划步骤、使用工具执行、并根据结果反思调整的系统。在本项目中我们构建的AI Agent的核心工作流如下目标理解接收用户请求如“帮我分析这份简历与Java开发岗位的匹配度”。规划分解Agent自主规划需要执行的步骤a. 解析简历文件b. 获取目标岗位详情c. 提取双方的关键信息技能、经验、项目d. 进行多维度对比分析e. 生成报告和建议。工具调用在规划过程中Agent发现需要“解析简历”于是调用我们通过MCP暴露的parse_resume工具需要“获取岗位详情”则调用search_jobs工具。结果整合与输出Agent收集所有工具的执行结果组织成结构化的、人性化的分析报告输出给用户。1.4 系统架构总览理解了上述组件后整个系统的架构就清晰了用户请求 | v [Spring Boot Web层] (接收请求管理会话) | v [AI Agent 核心] (基于Spring AI ChatClient负责规划与决策) | v [MCP Client] (通过MCP协议调用外部工具) | v [MCP Server] (我们自研的服务提供简历解析、岗位搜索等具体工具实现) | v [外部服务/数据库] (如PDF解析库、岗位数据库、向量数据库)Spring Boot作为容器整合所有组件Spring AI提供与大模型交互的核心能力MCP协议是Agent与专用工具之间的桥梁而具体的业务逻辑解析、匹配、搜索则在MCP Server中实现。2. 环境准备与项目初始化我们将使用Java 17或以上版本Spring Boot 3.x以及Spring AI的稳定版本。请注意Spring AI和MCP相关库版本迭代较快以下配置以当前稳定版为例实际开发前请核对官方文档。2.1 基础环境与依赖首先确保本地已安装JDK 17Maven 3.6可选Docker用于运行向量数据库如PgVector、Redis Stack使用Spring Initializrstart.spring.io创建项目选择Project: MavenLanguage: JavaSpring Boot: 3.2.xDependencies:Spring Web,Spring AI(如果Initializr未提供需手动添加)在生成的pom.xml中需要手动添加或确认以下关键依赖dependencies !-- Spring Boot 基础 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- Spring AI OpenAI (以OpenAI为例也可换为Azure、Ollama等) -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-openai-spring-boot-starter/artifactId version0.8.1/version !-- 请使用最新稳定版 -- /dependency !-- Spring AI VectorStore (以Redis为例) -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-redis-store-spring-boot-starter/artifactId version0.8.1/version /dependency !-- MCP 协议 Java SDK (示例可能需要从GitHub引入) -- !-- 注意截至撰写时正式的MCP Java SDK可能仍在发展中此处以概念性依赖为例 -- !-- 实际开发中你可能需要引入anthropic或社区提供的mcp-java-client -- dependency groupIdcom.example/groupId !-- 示例GroupId -- artifactIdmcp-client/artifactId version0.1.0/version scopesystem/scope systemPath${project.basedir}/lib/mcp-client.jar/systemPath /dependency !-- 工具类用于PDF解析 -- dependency groupIdorg.apache.pdfbox/groupId artifactIdpdfbox/artifactId version2.0.29/version /dependency !-- 工具类用于JSON处理 -- dependency groupIdcom.fasterxml.jackson.core/groupId artifactIdjackson-databind/artifactId /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies注意Spring AI和MCP相关依赖的版本和可用性变化较快。务必查阅Spring AI官方文档和Anthropic的MCP仓库获取最新信息。如果找不到成熟的Java MCP客户端可能需要基于MCP的JSON-RPC协议自行实现一个轻量级客户端。2.2 核心配置文件在application.yml中配置关键信息spring: application: name: ai-job-agent # Spring AI - OpenAI 配置 (示例请替换为你的API Key) ai: openai: api-key: ${OPENAI_API_KEY:your-openai-api-key-here} chat: options: model: gpt-4-turbo-preview # 或 gpt-3.5-turbo temperature: 0.2 # 降低随机性使分析更稳定 embedding: options: model: text-embedding-3-small # Redis 配置 (用于VectorStore和缓存) data: redis: host: localhost port: 6379 password: database: 0 # 自定义配置 app: mcp: server-url: http://localhost:8081/mcp # 我们即将创建的MCP Server地址 job: >// model/McpToolRequest.java Data public class McpToolRequest { JsonProperty(jsonrpc) private String jsonrpc 2.0; private String id; private String method; private Params params; Data public static class Params { private MapString, Object arguments; private String name; // 工具名如 “parse_resume” } } // model/McpToolResponse.java Data public class McpToolResponse { JsonProperty(jsonrpc) private String jsonrpc 2.0; private String id; private Result result; private Error error; Data public static class Result { private Object content; // 工具执行的结果 } Data public static class Error { private int code; private String message; } }3.2 实现核心工具简历解析简历解析是系统的关键输入。我们实现一个简单的PDF解析工具提取文本信息并可用LLM进一步结构化。// tools/ResumeParserTool.java Service Slf4j public class ResumeParserTool { Value(${spring.ai.openai.api-key}) private String openAiApiKey; /** * MCP工具解析简历文件提取结构化信息 * param filePath 简历文件路径Base64编码或URL * return 结构化的简历信息JSON */ public McpToolResponse parseResume(String filePath) { McpToolResponse response new McpToolResponse(); response.setId(1); try { // 1. 从文件路径或Base64数据获取PDF内容此处简化假设filePath是本地路径 File file new File(filePath); if (!file.exists()) { throw new IllegalArgumentException(Resume file not found: filePath); } // 2. 使用PDFBox提取原始文本 String rawText extractTextFromPdf(file); // 3. 调用OpenAI API将原始文本结构化使用Spring AI的ChatClient // 注意这里为了简化直接注入ChatClient。更规范的做法是通过配置类注入。 OpenAiChatClient chatClient new OpenAiChatClient(new OpenAiApi(https://api.openai.com/v1, openAiApiKey)); String prompt 请将以下简历文本解析为结构化的JSON格式包含以下字段 - name: 姓名 - contact: 联系方式邮箱、电话 - education: 教育背景列表学校、专业、学历、时间 - workExperience: 工作经历列表公司、职位、时间、描述 - skills: 技能列表编程语言、框架、工具等 - projects: 项目经历列表项目名、角色、时间、描述、所用技术 - selfEvaluation: 自我评价 简历文本 %s 只输出JSON不要有任何额外解释。 .formatted(rawText.substring(0, Math.min(rawText.length(), 3000))); // 限制长度 ChatResponse chatResponse chatClient.call(new Prompt(prompt)); String jsonOutput chatResponse.getResult().getOutput().getContent(); // 4. 解析JSON构建响应 ObjectMapper mapper new ObjectMapper(); JsonNode resumeJson mapper.readTree(jsonOutput); response.setResult(new McpToolResponse.Result()); response.getResult().setContent(resumeJson); } catch (Exception e) { log.error(Failed to parse resume, e); McpToolResponse.Error error new McpToolResponse.Error(); error.setCode(500); error.setMessage(Resume parsing failed: e.getMessage()); response.setError(error); } return response; } private String extractTextFromPdf(File file) throws IOException { // 使用PDFBox提取文本的简单实现 PDDocument document PDDocument.load(file); PDFTextStripper stripper new PDFTextStripper(); String text stripper.getText(document); document.close(); return text; } }3.3 实现核心工具岗位搜索与匹配度计算// tools/JobSearchTool.java Service public class JobSearchTool { Autowired private JobRepository jobRepository; Autowired private VectorStoreService vectorStoreService; /** * MCP工具根据查询条件搜索岗位 */ public McpToolResponse searchJobs(String keyword, String location, Integer limit) { ListJobPosition jobs; if (keyword ! null !keyword.isBlank()) { // 语义搜索将关键词向量化在向量库中搜索 jobs vectorStoreService.similaritySearch(keyword, limit ! null ? limit : 10); } else { // 普通过滤搜索 jobs jobRepository.findByLocationContaining(location); if (limit ! null jobs.size() limit) { jobs jobs.subList(0, limit); } } McpToolResponse response new McpToolResponse(); response.setId(2); response.setResult(new McpToolResponse.Result()); response.getResult().setContent(jobs); return response; } } // tools/MatchCalculatorTool.java Service public class MatchCalculatorTool { /** * MCP工具计算简历与特定岗位的匹配度 */ public McpToolResponse calculateMatch(JsonNode resumeJson, Long jobId) { // 1. 获取岗位信息 JobPosition job jobRepository.findById(jobId).orElseThrow(...); // 2. 提取简历和岗位的关键向量技能、经验要求等 String resumeSkillsText extractSkillsText(resumeJson); String jobRequirementsText job.getDescription() String.join( , job.getRequiredSkills()); // 3. 通过EmbeddingClient获取向量 EmbeddingClient embeddingClient ... // 通过配置注入 ListDouble resumeVector embeddingClient.embed(resumeSkillsText); ListDouble jobVector embeddingClient.embed(jobRequirementsText); // 4. 计算余弦相似度作为基础匹配分 double cosineSimilarity calculateCosineSimilarity(resumeVector, jobVector); double baseScore (cosineSimilarity 1) / 2 * 100; // 归一化到0-100 // 5. 应用业务规则加权例如年限匹配、学历匹配、技能完全匹配项加分 double ruleBonus calculateRuleBonus(resumeJson, job); double finalScore Math.min(baseScore ruleBonus, 100.0); // 6. 生成匹配分析报告可再次调用LLM String analysis generateMatchAnalysis(resumeJson, job, finalScore); MapString, Object result Map.of( score, finalScore, analysis, analysis, matchedSkills, findMatchedSkills(resumeJson, job), missingSkills, findMissingSkills(resumeJson, job) ); McpToolResponse response new McpToolResponse(); response.setId(3); response.setResult(new McpToolResponse.Result()); response.getResult().setContent(result); return response; } private double calculateCosineSimilarity(ListDouble vecA, ListDouble vecB) { // 实现余弦相似度计算 double dotProduct 0.0; double normA 0.0; double normB 0.0; for (int i 0; i vecA.size(); i) { dotProduct vecA.get(i) * vecB.get(i); normA Math.pow(vecA.get(i), 2); normB Math.pow(vecB.get(i), 2); } return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB)); } }3.4 暴露MCP Server端点创建一个控制器接收标准的MCP JSON-RPC请求并根据method和params.name路由到相应的工具。// controller/McpServerController.java RestController RequestMapping(/mcp) public class McpServerController { Autowired private ResumeParserTool resumeParserTool; Autowired private JobSearchTool jobSearchTool; Autowired private MatchCalculatorTool matchCalculatorTool; PostMapping(/call) public McpToolResponse handleToolCall(RequestBody McpToolRequest request) { String toolName request.getParams().getName(); MapString, Object args request.getParams().getArguments(); return switch (toolName) { case parse_resume - { String filePath (String) args.get(file_path); yield resumeParserTool.parseResume(filePath); } case search_jobs - { String keyword (String) args.get(keyword); String location (String) args.get(location); Integer limit (Integer) args.get(limit); yield jobSearchTool.searchJobs(keyword, location, limit); } case calculate_match - { JsonNode resume (JsonNode) args.get(resume); Long jobId ((Integer) args.get(job_id)).longValue(); yield matchCalculatorTool.calculateMatch(resume, jobId); } default - { McpToolResponse errorResponse new McpToolResponse(); errorResponse.setId(request.getId()); McpToolResponse.Error error new McpToolResponse.Error(); error.setCode(404); error.setMessage(Tool not found: toolName); errorResponse.setError(error); yield errorResponse; } }; } }至此一个提供核心功能的MCP Server就搭建好了。它运行在http://localhost:8081/mcp/call能够处理三种工具调用。4. 构建AI Agent核心规划与执行现在我们回到主应用构建能够调用MCP工具的AI Agent。4.1 配置MCP客户端与Spring AI首先配置一个能调用我们MCP Server的客户端。// config/McpClientConfig.java Configuration public class McpClientConfig { Value(${app.mcp.server-url}) private String mcpServerUrl; Bean public RestTemplate mcpRestTemplate() { return new RestTemplate(); } Bean public McpToolInvoker mcpToolInvoker(RestTemplate mcpRestTemplate) { return new McpToolInvoker(mcpRestTemplate, mcpServerUrl); } } // agent/tools/McpToolInvoker.java Component Slf4j public class McpToolInvoker { private final RestTemplate restTemplate; private final String mcpServerUrl; public McpToolInvoker(RestTemplate restTemplate, String mcpServerUrl) { this.restTemplate restTemplate; this.mcpServerUrl mcpServerUrl; } public JsonNode callTool(String toolName, MapString, Object arguments) { McpToolRequest request new McpToolRequest(); request.setId(UUID.randomUUID().toString()); request.setMethod(call_tool); request.setParams(new McpToolRequest.Params()); request.getParams().setName(toolName); request.getParams().setArguments(arguments); try { McpToolResponse response restTemplate.postForObject(mcpServerUrl /call, request, McpToolResponse.class); if (response ! null response.getError() null) { return (JsonNode) response.getResult().getContent(); } else { log.error(MCP tool call failed: {}, response ! null ? response.getError().getMessage() : Unknown error); throw new RuntimeException(Tool invocation failed); } } catch (Exception e) { log.error(Failed to invoke MCP tool: {}, toolName, e); throw new RuntimeException(Failed to invoke tool: toolName, e); } } }4.2 实现Agent规划与执行逻辑这是系统的“大脑”。我们设计一个JobMatchingAgent它接收用户请求生成一个规划并按步骤执行。// agent/core/JobMatchingAgent.java Service public class JobMatchingAgent { Autowired private ChatClient chatClient; // Spring AI 注入的ChatClient Autowired private McpToolInvoker toolInvoker; public AgentResponse processRequest(String userQuery, String resumeFilePath) { // 步骤1规划。让LLM根据用户查询决定需要调用哪些工具顺序如何。 String planPrompt 你是一个专业的求职顾问AI Agent。用户的需求是%s 你可以调用的工具有 1. parse_resume: 解析简历文件输入参数 {file_path: 简历文件路径}输出结构化的简历信息。 2. search_jobs: 搜索相关岗位输入参数 {keyword: 搜索关键词, location: 地点, limit: 结果数量}。 3. calculate_match: 计算简历与岗位的匹配度输入参数 {resume: 简历JSON, job_id: 岗位ID}。 请根据用户需求生成一个JSON格式的执行计划plan。plan是一个步骤steps数组。 每个步骤包含 - step_id: 步骤序号 - tool_name: 要调用的工具名 - arguments: 调用参数如果某个参数需要上一步的结果用 ${step_id.output.field} 表示例如第一步的简历信息是 ${1.output} - depends_on: 依赖的步骤ID没有则为空数组 示例如果用户说“分析我的简历”那么plan可能是 { steps: [ {step_id: 1, tool_name: parse_resume, arguments: {file_path: /path/to/resume.pdf}, depends_on: []}, {step_id: 2, tool_name: search_jobs, arguments: {keyword: Java 开发, limit: 5}, depends_on: []}, {step_id: 3, tool_name: calculate_match, arguments: {resume: ${1.output}, job_id: ${2.output[0].id}}, depends_on: [1,2]} ] } 现在请为以下需求生成plan。只输出JSON不要有其他内容。 用户需求%s .formatted(userQuery, userQuery); String planJson chatClient.call(planPrompt).getResult().getOutput().getContent(); // 步骤2执行。解析plan按依赖顺序调用工具。 ObjectMapper mapper new ObjectMapper(); JsonNode planNode mapper.readTree(planJson); MapInteger, JsonNode stepResults new HashMap(); ListJsonNode steps planNode.get(steps).findValues(...); // 简化这里需要实现一个依赖解析和执行引擎。为了示例我们假设顺序执行。 for (JsonNode step : steps) { int stepId step.get(step_id).asInt(); String toolName step.get(tool_name).asText(); JsonNode argsNode step.get(arguments); // 需要解析argsNode中的变量占位符如 ${1.output}替换为实际值 MapString, Object resolvedArgs resolveArguments(argsNode, stepResults); JsonNode result toolInvoker.callTool(toolName, resolvedArgs); stepResults.put(stepId, result); } // 步骤3汇总与报告。让LLM根据所有步骤的结果生成最终的用户报告。 String finalResult stepResults.get(steps.size()).toString(); // 假设最后一步是calculate_match String reportPrompt 你是一名求职顾问。以下是一份简历与岗位的匹配分析数据 %s 请生成一份给求职者的友好、专业、具有建设性的分析报告。报告应包含 1. 总体匹配度评分及解读。 2. 技能匹配情况优势技能、缺失技能。 3. 经验匹配分析。 4. 具体的、可操作的能力提升建议例如建议学习某个框架、补充某个类型的项目经验。 5. 后续行动建议例如如何优化简历、可以尝试投递哪些类似岗位。 请用中文输出报告。 .formatted(finalResult); String finalReport chatClient.call(reportPrompt).getResult().getOutput().getContent(); AgentResponse response new AgentResponse(); response.setPlan(planJson); response.setRawResults(stepResults); response.setFinalReport(finalReport); return response; } private MapString, Object resolveArguments(JsonNode argsNode, MapInteger, JsonNode stepResults) { // 实现参数解析逻辑将 ${step_id.output.field} 替换为实际值 // 此处为简化直接转换 ObjectMapper mapper new ObjectMapper(); return mapper.convertValue(argsNode, new TypeReferenceMapString, Object() {}); } }4.3 创建Web入口最后创建一个简单的REST控制器接收用户请求。// controller/JobMatchController.java RestController RequestMapping(/api/job-agent) Slf4j public class JobMatchController { Autowired private JobMatchingAgent agent; PostMapping(/analyze) public ResponseEntityAgentResponse analyzeResume( RequestParam(query) String userQuery, RequestParam(resumeFile) MultipartFile resumeFile) { // 1. 保存上传的简历文件 String filePath ./uploads/ resumeFile.getOriginalFilename(); try { resumeFile.transferTo(new File(filePath)); } catch (IOException e) { log.error(Failed to save resume file, e); return ResponseEntity.status(500).build(); } // 2. 交给Agent处理 AgentResponse response agent.processRequest(userQuery, filePath); // 3. 返回结果 return ResponseEntity.ok(response); } }5. 运行验证与结果分析5.1 启动与测试流程启动服务确保Redis已运行。依次启动MCP Server端口8081和主应用端口8080。准备数据在./data/jobs.json中放入一些模拟的岗位数据。使用VectorStoreService在应用启动时将岗位描述向量化并存入Redis。上传简历使用Postman或curl发送一个POST请求到http://localhost:8080/api/job-agent/analyze。form-data参数query“帮我分析这份简历并推荐匹配的Java开发岗位”resumeFile你的简历PDF文件。观察日志与结果在控制台观察Agent的规划、工具调用顺序。最终会收到一个结构化的JSON响应包含plan、rawResults和格式良好的finalReport。5.2 预期输出示例{ plan: {\steps\:[...]}, rawResults: { 1: {name: 张三, skills: [Java, Spring Boot, MySQL], ...}, 2: [{id: 101, title: Java后端开发工程师, ...}, ...], 3: {score: 85.5, analysis: ..., matchedSkills: [Java, Spring Boot], missingSkills: [Redis, Kafka]} }, finalReport: 【求职分析报告】\n\n尊敬的张三您好\n\n基于您的简历我们对您与“Java后端开发工程师”岗位的匹配度进行了分析\n\n**1. 总体匹配度85.5分优秀**\n您的核心技能与岗位要求高度契合...\n\n**2. 技能匹配详情**\n✅ **优势技能**Java、Spring Boot、MySQL...\n⚠️ **建议补充**Redis、Kafka...\n\n**3. 项目经验分析**\n您的电商项目经验与岗位描述中的“高并发系统”经验要求相关...\n\n**4. 能力提升建议**\n1. 建议花2周时间学习Redis的基本数据结构与缓存策略...\n2. 可以尝试在个人项目中引入Kafka处理异步消息...\n\n**5. 后续行动建议**\n1. 在简历中突出您的电商项目在高并发处理方面的细节...\n2. 除了该岗位您还可以尝试投递“Java高级开发工程师-支付方向”...\n }6. 常见问题排查与优化在实际部署和运行中你可能会遇到以下问题。6.1 启动与连接问题问题现象可能原因检查方式处理建议应用启动失败报BeanCreationExceptionSpring AI或Redis依赖缺失或版本冲突OpenAI API Key未配置。1. 检查pom.xml依赖版本。2. 检查application.yml中spring.ai.openai.api-key是否正确。3. 查看完整堆栈错误日志。1. 确认Spring Boot、Spring AI版本兼容性。2. 将API Key放入环境变量OPENAI_API_KEY在配置中使用${OPENAI_API_KEY}引用。调用MCP工具超时或连接拒绝MCP Server未启动网络端口不通app.mcp.server-url配置错误。1. 使用curl http://localhost:8081/actuator/health检查MCP Server健康状态。2. 检查主应用日志中MCP调用报错信息。1. 确保MCP Server应用先启动。2. 确认配置的URL和端口与MCP Server实际运行地址一致。向量相似度搜索返回空结果Redis VectorStore未初始化数据Embedding模型调用失败。1. 检查Redis中是否有对应的向量索引。2. 检查VectorStoreService初始化日志。3. 检查OpenAI Embedding API调用是否成功。1. 在应用启动时编写初始化脚本将岗位数据向量化后存入VectorStore。2. 检查Embedding模型的API配额和可用性。6.2 功能与逻辑问题问题现象可能原因检查方式处理建议简历解析结果混乱或为空PDF解析失败加密、扫描版LLM结构化提示词Prompt不清晰。1. 检查ResumeParserTool中extractTextFromPdf提取的原始文本。2. 查看发送给LLM的完整Prompt和返回的原始响应。1. 增加对PDF解析异常的处理尝试其他解析库如Apache Tika。2. 优化Prompt要求LLM严格按指定JSON格式输出并增加示例Few-shot。匹配度分数始终很低或不准向量模型不适用于中文余弦相似度计算方式有误业务规则权重不合理。1. 检查Embedding后的向量维度是否一致。2. 手动计算几个已知匹配/不匹配的样本对验证分数。3. 打印出用于生成向量的文本内容。1. 考虑使用针对中文优化的Embedding模型如M3E、BGE。2. 复核calculateCosineSimilarity函数。3. 调整业务规则加权的逻辑或引入更复杂的匹配模型如基于特征工程的分类器。AI Agent规划不合理乱调用工具规划Prompt设计有缺陷LLM如GPT-3.5推理能力不足。1. 查看Agent生成的planJSON是否合理。2. 尝试在Prompt中提供更详细、更严格的规划示例。1. 升级到推理能力更强的模型如GPT-4。2. 将规划步骤固定化Hardcode不依赖LLM生成而是根据用户查询类型匹配预定义流程。6.3 性能与生产环境优化建议缓存策略简历解析、岗位向量化结果可以缓存。对于同一份简历或岗位的多次分析避免重复调用昂贵的LLM和Embedding API。异步处理简历解析和匹配分析可能是耗时操作。将JobMatchController的/analyze接口改为异步立即返回一个任务ID通过WebSocket或轮询通知用户结果。提示词工程将ResumeParserTool和JobMatchingAgent中的Prompt模板外置到配置文件或数据库中便于迭代优化而无需重新部署代码。MCP Server健壮性当前的MCP Server是简单的HTTP端点。生产环境应考虑身份认证、限流、监控和更完善的错误处理。可以参考MCP官方规范实现Stdio Server以获得更好的工具发现和管理能力。向量数据库选型对于海量岗位数据Redis可能不是最佳选择。可以考虑专业的向量数据库如Milvus、Pinecone或PgVectorPostgreSQL扩展。安全与隐私简历文件包含敏感个人信息。务必确保文件上传目录不可通过Web直接访问处理完成后及时清理临时文件。考虑对简历文本进行脱敏处理后再发送给第三方LLM API。7. 扩展方向与总结本项目实现了一个基于Spring AI和MCP的AI Agent求职匹配系统原型展示了如何将大语言模型的推理能力与专用工具的执行能力相结合。你可以在此基础上进行多方面扩展工具扩展在MCP Server中增加更多工具如get_salary_trend查询薪资趋势、optimize_resume优化简历措辞、simulate_interview模拟面试。记忆与多轮对话为Agent引入对话历史管理使其能进行多轮交互例如根据用户反馈调整搜索条件或分析维度。可视化前端构建一个Web界面上传简历后可视化展示匹配度雷达图、技能对比图、生成的学习路径图。集成真实数据源将JobSearchTool连接到真实的招聘平台API如联调获取实时岗位数据。评估与迭代收集用户对匹配结果的反馈“相关”/“不相关”用于持续优化匹配算法和Prompt。最关键的是理解这种架构模式Spring AI作为“大脑”负责规划和决策MCP作为“手和脚”负责执行具体任务。这种模式可以泛化到许多其他领域如智能客服、数据分析助手、自动化运维等。掌握它你就掌握了构建下一代AI原生应用的核心方法。