山东大学软件学院项目实训-基于大模型的模拟面试系统-个人博客(十)

开发博客:AI面试官个性化出题MCP功能最终完善

本周作为项目开发的最后冲刺阶段,我们致力于进一步增强AI面试官在个性化题目生成方面的能力。核心工作是新增和优化了一系列MCP(Multi-turn Conversation Protocol)工具,旨在为AI面试官提供更丰富、更精准的用户画像和知识背景,从而生成更具针对性的面试题目。

目前整个MCP工具链的结构如下:
山东大学软件学院项目实训-基于大模型的模拟面试系统-个人博客(十)_第1张图片

本周主要完成的MCP功能模块包括:query_user_submission_statsquery_user_articles,以及search_articles_by_keyword。以下将对这些功能进行详细说明。

1. query_user_submission_stats:用户答题数据统计

  • 功能描述: 此工具通过分析用户在在线判题系统(OJ)中的答题记录,评估用户的代码熟练度和能力水平。统计信息包括用户尝试过的题目总数、成功解答的题目总数,以及成功解答题目按难度(简单、中等、困难)的分布情况。
  • 实现逻辑:
    1. 工具接收用户ID(userId)作为输入。首先对userId进行校验,确保其为有效的整型数字字符串。
    2. oj_code_submission数据库表中查询指定用户的所有提交记录,提取problem_idstatus
    3. 使用Set数据结构统计用户尝试过的所有题目ID(attemptedProblemIds)和状态为“accepted”的题目ID(acceptedProblemIds),以实现自动去重。
    4. 若用户有成功解答的题目(acceptedProblemIds不为空),则进一步从oj_problem表中查询这些已通过题目的难度(difficulty)。
    5. 根据查询到的难度信息,统计“简单”、“中等”、“困难”各自的数量。
    6. 最终返回一个包含总尝试题目数(totalAttempted)、总通过题目数(totalAccepted)以及按难度分类的通过题目数(acceptedByDifficulty)的JSON对象。
    7. 包含完整的错误处理机制,如无效userId或数据库查询失败等情况。
  • 价值: AI面试官可以利用这些数据了解候选人的实际编程能力和薄弱环节,从而调整面试题目的难度和类型,实现更精准的考察。
  • 代码实现:
    server.tool(
        "query_user_submission_stats",
        "统计用户的答题记录信息,包括已完成题目数和完成题目的按难度分类统计",
        {
            userId: z.string().describe("用户ID,为整形数字的字符串格式"),
        },
        async ({ userId }) => {
            try {
                const mysqlPool = getMySQL();
                const userIdInt = parseInt(userId);
              
                if (isNaN(userIdInt)) {
                    return {
                        content: [{ type: "text", text: `无效的userId格式: ${userId}` }],
                        isError: true
                    };
                }
    
                // 获取用户的提交记录
                const [submissions] = await mysqlPool.query(`
                    SELECT problem_id, status 
                    FROM oj_code_submission 
                    WHERE user_id = ?
                `, [userIdInt]);
              
         
    
                // 统计已尝试和已通过的题目
                const attemptedProblemIds = new Set();
                const acceptedProblemIds = new Set();
                const ACCEPTED_STATUS = "accepted";
    
                for (const submission of submissions) {
                    const problemId = submission.problem_id;
                    // 所有提交过的题目ID(自动去重)
                    attemptedProblemIds.add(problemId);
    
                    // 仅添加ACCEPTED状态的题目ID(自动去重)
                    if (ACCEPTED_STATUS.toLowerCase() === submission.status.toLowerCase()) {
                        acceptedProblemIds.add(problemId);
                    }
                }
    
                // 查询已通过题目的难度分布
                const acceptedProblemsList = Array.from(acceptedProblemIds);
                let difficultyStats = {
                    easy: 0,
                    medium: 0,
                    hard: 0
                };
              
                if (acceptedProblemsList.length > 0) {
                    const [problemDifficulties] = await mysqlPool.query(`
                        SELECT id, difficulty 
                        FROM oj_problem 
                        WHERE id IN (?)
                    `, [acceptedProblemsList]);
    
                    // 按难度统计
                    for (const problem of problemDifficulties) {
                        const difficulty = problem.difficulty ? problem.difficulty.toLowerCase() : 'unknown';
                        if (difficulty === '简单') difficultyStats.easy++;
                        else if (difficulty === '中等') difficultyStats.medium++;
                        else if (difficulty === '困难') difficultyStats.hard++;
                    }
                }
    
                // 构建统计结果
                const stats = {
                    totalAttempted: attemptedProblemIds.size,
                    totalAccepted: acceptedProblemIds.size,
                    acceptedByDifficulty: difficultyStats
                };
    
                return {
                    content: [{ type: "text", text: JSON.stringify(stats, null, 2) }]
                };
            } catch (error) {
                console.error(`查询用户答题统计时发生错误: ${error.message}`);
                return {
                    content: [{ type: "text", text: `查询用户答题统计时发生内部错误: ${error.message}` }],
                    isError: true
                };
            }
        }
    );
    

2. query_user_articles:用户论坛发帖分析

  • 功能描述: 该工具用于检索并分析用户近期在社区论坛中发布的文章。目的是从用户的发帖内容中提取其关注的技术方向、感兴趣的公司、讨论过的面试题目等信息,作为个性化出题的参考。
  • 实现逻辑:
    1. 工具接收用户ID(userId)作为输入,并进行有效性校验。
    2. 通过联合查询forest_article(文章主表)和forest_article_content(文章内容表),获取指定article_author_id用户的最多20篇最新发布的文章,包括文章标题、标签、浏览/评论/点赞数、创建时间、预览内容及完整内容。
    3. 若未找到用户文章,则返回相应提示。
    4. 如果查询到文章,且配置了DashScope的API密钥(DASHSCOPE_API_KEY)和应用ID(DASHSCOPE_SUM_APP_ID),则将获取的文章数据(JSON格式)作为输入,调用DashScope大模型服务进行内容分析和总结。Prompt中指定为“模式一以分析下面内容”。
    5. AI服务返回对用户发帖内容的提炼信息,该信息将作为出题参考返回。
    6. 若AI调用失败、未配置API密钥或应用ID,则直接返回原始查询到的文章数据(JSON格式)。
    7. 包含数据库查询和AI服务调用的错误处理。
  • 价值: 通过用户自身的言论,AI面试官能更深入地了解候选人的求职意向、技术栈偏好以及对特定问题的看法,为生成高度个性化的面试场景和问题提供素材。
  • 代码实现:
    server.tool(
        "query_user_articles",
        "根据用户ID获取该用户发布文章,用于作为用户个性化问题出题参考",
        {
            userId: z.string().describe("用户ID,为整形数字的字符串格式"),
        },
        async ({ userId }) => {
            try {
                const mysqlPool = getMySQL();
                const userIdInt = parseInt(userId);
              
                if (isNaN(userIdInt)) {
                    return {
                        content: [{ type: "text", text: `无效的userId格式: ${userId}` }],
                        isError: true
                    };
                }
    
                // 联合查询获取用户的文章及其内容
                const [articles] = await mysqlPool.query(`
                    SELECT 
                        a.id, 
                        a.article_title, 
                        a.article_tags, 
                        a.article_view_count,
                        a.article_comment_count,
                        a.article_thumbs_up_count,
                        a.created_time,
                        a.article_preview_content,
                        c.article_content
                    FROM 
                        forest_article a
                    LEFT JOIN 
                        forest_article_content c ON a.id = c.id_article
                    WHERE 
                        a.article_author_id = ?
                    ORDER BY 
                        a.created_time DESC
                    LIMIT 20
                `, [userIdInt]);
              
                if (!articles || articles.length === 0) {
                    return {
                        content: [{ type: "text", text: `未找到用户ID为${userId}的文章` }]
                    };
                }
    
                // 调用AI处理查询结果
                const apiKey = process.env.DASHSCOPE_API_KEY;
                const appId = process.env.DASHSCOPE_SUM_APP_ID;
    
                if (!apiKey || !appId) {
                    console.error('DashScope API Key or App ID not configured.');
                    return {
                        content: [{ type: "text", text: JSON.stringify(articles, null, 2) }]
                    };
                }
    
                const url = `https://dashscope.aliyuncs.com/api/v1/apps/${appId}/completion`;
              
                const data = {
                    input: {
                        prompt: `以模式一以分析下面内容:\n${JSON.stringify(articles)}`
                    },
                    parameters: {},
                    debug: {}
                };
    
                try {
                    const response = await axios.post(url, data, {
                        headers: {
                            'Authorization': `Bearer ${apiKey}`,
                            'Content-Type': 'application/json'
                        }
                    });
    
                    if (response.status === 200 && response.data.output && response.data.output.text) {
                        return {
                            content: [{ type: "text", text: "获取到以下信息作为你的出题参考"+response.data.output.text }]
                        };
                    } 
                } catch (aiError) {
                    console.error(`Error calling DashScope: ${aiError.message}`);
                    // 如果AI调用失败,返回原始查询结果
                    return {
                        content: [{ type: "text", text: JSON.stringify(articles, null, 2) }]
                    };
                }
            } catch (error) {
                console.error(`查询用户文章时发生错误: ${error.message}`);
                return {
                    content: [{ type: "text", text: `查询用户文章时发生内部错误: ${error.message}` }],
                    isError: true
                };
            }
        }
    );
    

3. search_articles_by_keyword:社区内容关键词检索

  • 功能描述: 此工具允许根据特定关键词(如目标公司名称“阿里”、“腾讯”等)在社区论坛中搜索相关的文章,如面经分享、技术探讨、解题思路等,为AI面试官提供针对性的出题参考。
  • 实现逻辑:
    1. 工具接收搜索关键词(keyword)作为输入,并校验关键词非空。
    2. 构建SQL的LIKE查询模式(%keyword%),在forest_article表的article_title字段和forest_article_content表的article_content字段中进行模糊匹配。
    3. 查询并返回按创建时间倒序排列的最多20篇相关文章,包含与query_user_articles类似的字段信息。
    4. 若未找到匹配文章,则返回相应提示。
    5. query_user_articles类似,如果查询到文章且配置了DashScope服务,则将文章数据发送给大模型进行分析总结。Prompt中指定为“模式二以分析下面内容”。
    6. AI服务返回对搜索结果的提炼信息,作为出题参考。
    7. 若AI调用失败、未配置或关键词无效,则返回原始文章数据或错误信息。
    8. 包含数据库查询和AI服务调用的错误处理。
  • 价值: 当AI面试官需要针对特定公司或技术领域出题时,此工具能快速从社区中聚合相关的高价值信息,确保面试问题的前沿性和针对性,例如了解某公司常考的知识点或最新的技术趋势。
  • 代码实现:
    server.tool(
        "search_articles_by_keyword",
        "根据企业相关关键词(如阿里,腾讯)获取社区中提及相关出题知识点,用于作为出题参考",
        {
            keyword: z.string().describe("搜索关键词"),
        },
        async ({ keyword }) => {
            try {
                if (!keyword || keyword.trim() === "") {
                    return {
                        content: [{ type: "text", text: "搜索关键词不能为空" }],
                        isError: true
                    };
                }
    
                const mysqlPool = getMySQL();
                const searchKeyword = `%${keyword}%`; // 构建LIKE匹配模式
              
                // 联合查询匹配标题或内容的文章
                const [articles] = await mysqlPool.query(`
                    SELECT 
                        a.id, 
                        a.article_title, 
                        a.article_tags, 
                        a.article_view_count,
                        a.article_comment_count,
                        a.article_thumbs_up_count,
                        a.created_time,
                        a.article_preview_content,
                        c.article_content
                    FROM 
                        forest_article a
                    LEFT JOIN 
                        forest_article_content c ON a.id = c.id_article
                    WHERE 
                        a.article_title LIKE ? 
                        OR c.article_content LIKE ?
                    ORDER BY 
                        a.created_time DESC
                    LIMIT 20
                `, [searchKeyword, searchKeyword]);
              
                if (!articles || articles.length === 0) {
                    return {
                        content: [{ type: "text", text: `未找到包含关键词"${keyword}"的文章` }]
                    };
                }
    
                // 调用AI处理查询结果
                const apiKey = process.env.DASHSCOPE_API_KEY;
                const appId = process.env.DASHSCOPE_SUM_APP_ID;
    
                if (!apiKey || !appId) {
                    console.error('DashScope API Key or App ID not configured.');
                    return {
                        content: [{ type: "text", text: JSON.stringify(articles, null, 2) }]
                    };
                }
    
                const url = `https://dashscope.aliyuncs.com/api/v1/apps/${appId}/completion`;
              
                const data = {
                    input: {
                        prompt: `以模式二以分析下面内容:\n${JSON.stringify(articles)}`
                    },
                    parameters: {},
                    debug: {}
                };
    
                try {
                    const response = await axios.post(url, data, {
                        headers: {
                            'Authorization': `Bearer ${apiKey}`,
                            'Content-Type': 'application/json'
                        }
                    });
    
                    if (response.status === 200 && response.data.output && response.data.output.text) {
                        return {
                            content: [{ type: "text", text:"获取到以下信息作为你的出题参考"+ response.data.output.text }]
                        };
                    } 
                } catch (aiError) {
                    console.error(`Error calling DashScope: ${aiError.message}`);
                    // 如果AI调用失败,返回原始查询结果
                    return {
                        content: [{ type: "text", text: JSON.stringify(articles, null, 2) }]
                    };
                }
            } catch (error) {
                console.error(`关键词搜索文章时发生错误: ${error.message}`);
                return {
                    content: [{ type: "text", text: `关键词搜索文章时发生内部错误: ${error.message}` }],
                    isError: true
                };
            }
        }
    );
    

代码集成说明

上述三个核心功能均已通过server.tool方法在服务端进行了注册。每个工具都明确了其名称、功能描述、输入参数模式(使用zod进行定义和校验)以及异步执行函数。这种模块化的工具设计,使得AI面试官能够根据对话上下文和面试需求,灵活地调用这些外部能力。代码实现细节已在各功能模块中展示。

本周工作总结与意义

通过本周新增的这三个MCP工具,AI面试官获取个性化信息的能力得到了显著加强:

  1. 更精准的能力评估: 基于用户的实际刷题数据,准确判断其编程水平。
  2. 更深入的意向洞察: 通过分析用户发帖,了解其技术偏好和求职目标。
  3. 更相关的知识获取: 结合社区中关于目标公司的热门讨论,使面试题目更贴近实际。

这些工具的组合使AI面试官能够构建一个更全面的用户画像,从而设计出既能考察核心能力,又能体现候选人特点和意向的面试流程。

你可能感兴趣的:(面试,职场和发展)