开发博客:AI面试官个性化出题MCP功能最终完善
本周作为项目开发的最后冲刺阶段,我们致力于进一步增强AI面试官在个性化题目生成方面的能力。核心工作是新增和优化了一系列MCP(Multi-turn Conversation Protocol)工具,旨在为AI面试官提供更丰富、更精准的用户画像和知识背景,从而生成更具针对性的面试题目。
本周主要完成的MCP功能模块包括:query_user_submission_stats
,query_user_articles
,以及search_articles_by_keyword
。以下将对这些功能进行详细说明。
1. query_user_submission_stats
:用户答题数据统计
userId
)作为输入。首先对userId
进行校验,确保其为有效的整型数字字符串。oj_code_submission
数据库表中查询指定用户的所有提交记录,提取problem_id
和status
。Set
数据结构统计用户尝试过的所有题目ID(attemptedProblemIds
)和状态为“accepted”的题目ID(acceptedProblemIds
),以实现自动去重。acceptedProblemIds
不为空),则进一步从oj_problem
表中查询这些已通过题目的难度(difficulty
)。totalAttempted
)、总通过题目数(totalAccepted
)以及按难度分类的通过题目数(acceptedByDifficulty
)的JSON对象。userId
或数据库查询失败等情况。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
:用户论坛发帖分析
userId
)作为输入,并进行有效性校验。forest_article
(文章主表)和forest_article_content
(文章内容表),获取指定article_author_id
用户的最多20篇最新发布的文章,包括文章标题、标签、浏览/评论/点赞数、创建时间、预览内容及完整内容。DASHSCOPE_API_KEY
)和应用ID(DASHSCOPE_SUM_APP_ID
),则将获取的文章数据(JSON格式)作为输入,调用DashScope大模型服务进行内容分析和总结。Prompt中指定为“模式一以分析下面内容”。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
:社区内容关键词检索
keyword
)作为输入,并校验关键词非空。LIKE
查询模式(%keyword%
),在forest_article
表的article_title
字段和forest_article_content
表的article_content
字段中进行模糊匹配。query_user_articles
类似的字段信息。query_user_articles
类似,如果查询到文章且配置了DashScope服务,则将文章数据发送给大模型进行分析总结。Prompt中指定为“模式二以分析下面内容”。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面试官获取个性化信息的能力得到了显著加强:
这些工具的组合使AI面试官能够构建一个更全面的用户画像,从而设计出既能考察核心能力,又能体现候选人特点和意向的面试流程。