首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Python开发一个AI智能答题MCP插件

Python开发一个AI智能答题MCP插件

作者头像
SmileNicky
发布2025-10-14 08:39:46
发布2025-10-14 08:39:46
4300
代码可运行
举报
文章被收录于专栏:Nicky's blogNicky's blog
运行总次数:0
代码可运行

AI智能答题助手:基于MCP平台的数学题图片识别与解答插件

在当今数字化时代,人工智能技术正以前所未有的速度改变着我们的学习和工作方式。今天,我将向大家介绍一个非常实用的项目——AI智能答题助手,这是一个基于MCP平台的数学题图片识别与解答插件,能够帮助学生和教师快速解决数学问题。

项目简介

AI智能答题助手 是一个基于 PaddleOCRSiliconFlow API 的数学题图片识别与解答工具。用户可以上传包含数学题的图片,系统会自动识别题目并给出答案和详细的解题步骤。这个工具不仅支持中英文识别,还能优化数学公式的识别效果,非常适合学生在学习过程中遇到难题时使用。

功能特点

  • 图片识别数学题:支持上传图片识别数学题,优化数学公式和中文识别。
  • 自动解答与详细步骤:自动解答题目并返回详细解题步骤。
  • 简洁直观的用户界面:基于HTML、CSS和JavaScript开发的前端页面,操作简单。
  • 轻量后端服务:基于Flask框架的后端服务,部署方便。
  • 支持蓝耕云MCP平台:可以轻松部署到蓝耕云MCP平台,方便使用。

技术架构

  • 后端:Python + Flask框架
  • 前端:HTML + CSS + JavaScript
  • 图像识别:PaddleOCR(支持中英文和数学公式)
  • 解题核心:SiliconFlow API
  • 插件框架:FastMCP(支持MCP平台部署)

快速开始

本地运行
前置条件
  • Python 3.8.3
  • SiliconFlow API密钥
安装步骤

克隆项目并安装依赖:

代码语言:javascript
代码运行次数:0
运行
复制
git clone https://github.com/your-username/ai-answer-mcp-plugin.git
cd ai-answer-mcp-plugin
pip install -r backend/requirements.txt

配置环境变量:

代码语言:javascript
代码运行次数:0
运行
复制
cp .env.example .env

.env 文件中填写你的 SILICONFLOW_API_KEY

启动后端服务:

代码语言:javascript
代码运行次数:0
运行
复制
python backend/app.py

打开浏览器访问 http://127.0.0.1:8000/ 即可使用。

部署到蓝耕云MCP平台
前置条件
  • 蓝耕云平台账号
  • 已创建MCP应用空间
部署步骤

确保项目根目录包含 mcp.json 配置文件。

打包项目为ZIP文件(不含虚拟环境):

代码语言:javascript
代码运行次数:0
运行
复制
zip -r ai-answer-mcp-plugin.zip .

登录蓝耕云平台,进入MCP插件库。

点击“上传插件”,选择打包的ZIP文件。

配置插件参数(主要是 SILICONFLOW_API_KEY)。

部署插件并启动服务。

项目结构

代码语言:javascript
代码运行次数:0
运行
复制
ai-answer-mcp-plugin/
├── backend/ # 后端服务代码
│ ├── app.py # 主程序入口
│ ├── mcp_entry.py # MCP 插件入口
│ └── requirements.txt # 依赖清单
├── frontend/ # 前端页面
├── mcp.json # MCP 插件配置
├── .env # 本地环境变量
└── README.md # 项目说明

代码示例

后端代码(app.py
代码语言:javascript
代码运行次数:0
运行
复制
from flask import Flask, jsonify, redirect, url_for, request
import requests
from PIL import Image, ImageEnhance, ImageFilter
import io
import base64
import os
import re
import time
from dotenv import load_dotenv
from flask_cors import CORS
# 导入PaddleOCR
from paddleocr import PaddleOCR

# 加载环境变量
load_dotenv()

# 初始化Flask应用
app = Flask(__name__, static_folder='../frontend', static_url_path='/')

# 允许跨域请求
CORS(app, resources={r"/*": {"origins": "*"}})

# 初始化PaddleOCR,支持中英文和数学公式
# use_angle_cls=True 启用方向检测,lang='ch' 支持中英文
ocr = PaddleOCR(use_angle_cls=True, lang='ch', show_log=False)

# SiliconFlow API配置
SILICONFLOW_API_KEY = os.getenv("SILICONFLOW_API_KEY")
SILICONFLOW_API_URL = "https://api.siliconflow.cn/v1/chat/completions"

# 服务配置
HOST = "0.0.0.0"
PORT = 8000
DEBUG = True


def preprocess_image(image: Image.Image) -> Image.Image:
    """图片预处理以提高OCR识别率"""
    # 转为灰度图
    img = image.convert("L")

    # 增强对比度
    enhancer = ImageEnhance.Contrast(img)
    img = enhancer.enhance(1.5)

    # 轻微锐化
    img = img.filter(ImageFilter.SHARPEN)

    # 二值化处理
    threshold = 150
    img = img.point(lambda p: p > threshold and 255)

    return img


def image_to_base64(image: Image.Image) -> str:
    """将图片转换为带格式前缀的base64编码字符串"""
    try:
        buffer = io.BytesIO()
        image_format = image.format if image.format in ["JPEG", "PNG", "GIF"] else "PNG"
        image.save(buffer, format=image_format)

        base64_str = base64.b64encode(buffer.getvalue()).decode("utf-8")
        return f"data:image/{image_format.lower()};base64,{base64_str}"
    except Exception as e:
        raise Exception(f"图片转换失败: {str(e)}")


def ocr_image(image: Image.Image) -> str:
    """使用PaddleOCR识别图片中的文本,特别优化中文和数学公式"""
    try:
        # 预处理图片
        processed_img = preprocess_image(image)

        # 保存预处理后的图片到内存
        img_byte_arr = io.BytesIO()
        processed_img.save(img_byte_arr, format='PNG')
        img_byte_arr = img_byte_arr.getvalue()

        # 使用PaddleOCR识别
        # PaddleOCR接受文件路径或numpy数组,这里我们将图片转换为numpy数组
        import numpy as np
        img_np = np.array(processed_img)

        # 进行OCR识别
        result = ocr.ocr(img_np, cls=True)

        # 提取识别结果并按顺序拼接
        text = ""
        if result and len(result) > 0:
            # 按行排序
            lines = sorted(result[0], key=lambda x: x[0][0][1])
            for line in lines:
                # 每行的文本内容
                line_text = line[1][0]
                text += line_text + " "

        # 文本清理
        text = re.sub(r'\s+', ' ', text).strip()
        return text
    except Exception as e:
        raise Exception(f"OCR识别失败: {str(e)}")


def call_siliconflow_api(problem_text: str) -> dict:
    """调用SiliconFlow对话API解答数学题"""
    if not SILICONFLOW_API_KEY:
        raise Exception("未配置SiliconFlow API密钥,请检查环境变量")

    try:
        headers = {
            "Content-Type": "application/json",
            "Authorization": f"Bearer {SILICONFLOW_API_KEY}"
        }

        # 确认使用的模型是否有效,这可能是400错误的主要原因
        # 这里改用一个更通用的模型名称作为示例
        payload = {
            "model": "deepseek-ai/DeepSeek-R1-Distill-Qwen-7B",  # 更改模型名称为有效选项
            "messages": [
                {
                    "role": "system",
                    "content": """你是一名数学老师,需要清晰解答用户提供的数学题。
                    请按照以下格式回答:
                    1. 先给出解题步骤,分点说明
                    2. 最后用"答案:"开头给出最终答案
                    确保解答准确,步骤清晰易懂。"""
                },
                {
                    "role": "user",
                    "content": f"请解答以下数学题,并给出详细步骤:{problem_text}"
                }
            ],
            "temperature": 0.2,
            "max_tokens": 1000
        }

        start_time = time.time()
        response = requests.post(
            url=SILICONFLOW_API_URL,
            json=payload,
            headers=headers,
            timeout=60
        )

        # 打印响应内容以便调试
        print(f"API响应状态码: {response.status_code}")
        print(f"API响应内容: {response.text}")

        response.raise_for_status()
        result = response.json()

        # 添加响应时间信息
        result['response_time'] = time.time() - start_time
        return result
    except requests.exceptions.RequestException as e:
        # 更详细的错误信息
        raise Exception(f"API调用失败: {str(e)}. 响应内容: {getattr(response, 'text', '无响应内容')}")
    except Exception as e:
        raise Exception(f"处理API响应失败: {str(e)}")


@app.route("/", methods=["GET"])
def index():
    """服务首页"""
    return jsonify({
        "message": "数学题图片识别与解答服务",
        "endpoints": {
            "/solve_math_problem": "POST - 上传图片并解答数学题",
            "/frontend": "GET - 访问前端页面"
        }
    })


@app.route("/solve_math_problem", methods=["POST"])
def solve_math_problem():
    """处理数学题图片识别与解答请求"""
    try:
        # 检查是否有图片上传
        if "image" not in request.files:
            return jsonify({"error": "请上传图片文件"}), 400

        # 读取并处理图片
        image_file = request.files["image"]
        try:
            image = Image.open(image_file.stream)
        except Exception as e:
            return jsonify({"error": f"无效的图片文件: {str(e)}"}), 400

        # OCR识别题目文本
        try:
            problem_text = ocr_image(image)
            if not problem_text:
                return jsonify({"error": "未识别到题目文本,请上传清晰的图片"}), 400
        except Exception as e:
            return jsonify({"error": str(e)}), 500

        # 调用API解答
        try:
            result = call_siliconflow_api(problem_text)
            answer_content = result["choices"][0]["message"]["content"]
        except Exception as e:
            return jsonify({"error": str(e)}), 500

        # 解析答案和步骤
        answer = "未找到答案"
        steps = []

        if "答案:" in answer_content:
            parts = answer_content.split("答案:")
            steps_content = parts[0].strip()
            answer = parts[1].strip()

            # 分割步骤
            steps = [step.strip() for step in re.split(r'\n\d+\.', steps_content) if step.strip()]
            # 恢复编号
            for i in range(len(steps)):
                steps[i] = f"{i+1}. {steps[i]}"
        else:
            steps = answer_content.split("\n")
            steps = [step.strip() for step in steps if step.strip()]

        # 返回结果
        return jsonify({
            "success": True,
            "problem": problem_text,
            "answer": answer,
            "steps": steps,
            "processing_time": f"{result.get('response_time', 0):.2f} 秒",
            "tokens_used": result.get('usage', {}).get('total_tokens', 0)
        })

    except Exception as e:
        return jsonify({"error": f"服务内部错误: {str(e)}"}), 500


@app.route("/frontend")
def frontend():
    """访问前端页面"""
    return redirect(url_for('static', filename='index.html'))


if __name__ == "__main__":
    app.run(host=HOST, port=PORT, debug=DEBUG)
前端代码(index.html
代码语言:javascript
代码运行次数:0
运行
复制
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>数学题图片解答工具</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div class="container">
        <header>
            <h1>数学题图片解答工具</h1>
            <p>上传包含数学题的图片,获取答案和解题步骤</p>
        </header>

        <main>
            <div class="upload-area" id="uploadArea">
                <div class="upload-icon">📷</div>
                <p>点击或拖拽图片到此处上传</p>
                <input type="file" id="imageUpload" accept="image/*">
                <button id="submitBtn">开始解答</button>
                <div class="upload-tips">
                    <p>提示:请上传清晰的数学题图片,效果更佳</p>
                </div>
            </div>

            <div class="preview-area" id="previewArea">
                <h3>预览图片</h3>
                <div class="image-preview" id="imagePreview"></div>
            </div>

            <div class="result-area" id="resultArea">
                <h3>解答结果</h3>
                <div class="loading" id="loading" style="display: none;">
                    <div class="spinner"></div>
                    <p>处理中,请稍候...</p>
                </div>

                <div class="problem" id="problem">
                    <h4>识别到的题目:</h4>
                    <p id="problemText">-</p>
                </div>

                <div class="answer" id="answer">
                    <h4>答案:</h4>
                    <p id="answerText">-</p>
                </div>

                <div class="steps" id="steps">
                    <h4>解题步骤:</h4>
                    <ul id="stepsList"></ul>
                </div>

                <div class="stats">
                    <p>处理时间: <span id="processingTime">-</span></p>
                    <p> tokens使用: <span id="tokensUsed">-</span></p>
                </div>
            </div>
        </main>

        <footer>
            <p>© 2025 数学题图片解答工具 | 基于SiliconFlow API</p>
        </footer>
    </div>

    <script src="script.js"></script>
</body>
</html>
前端代码(script.js
代码语言:javascript
代码运行次数:0
运行
复制
document.addEventListener('DOMContentLoaded', () => {
    const uploadArea = document.getElementById('uploadArea');
    const imageUpload = document.getElementById('imageUpload');
    const imagePreview = document.getElementById('imagePreview');
    const submitBtn = document.getElementById('submitBtn');
    const loading = document.getElementById('loading');
    const problemText = document.getElementById('problemText');
    const answerText = document.getElementById('answerText');
    const stepsList = document.getElementById('stepsList');
    const processingTime = document.getElementById('processingTime');
    const tokensUsed = document.getElementById('tokensUsed');

    let selectedImage = null;

    // 点击上传区域触发文件选择
    uploadArea.addEventListener('click', (e) => {
        if (!e.target.closest('button')) {
            imageUpload.click();
        }
    });

    // 拖拽功能
    uploadArea.addEventListener('dragover', (e) => {
        e.preventDefault();
        uploadArea.style.borderColor = '#2980b9';
        uploadArea.style.backgroundColor = '#f0f8ff';
    });

    uploadArea.addEventListener('dragleave', () => {
        uploadArea.style.borderColor = '#3498db';
        uploadArea.style.backgroundColor = 'white';
    });

    uploadArea.addEventListener('drop', (e) => {
        e.preventDefault();
        uploadArea.style.borderColor = '#3498db';
        uploadArea.style.backgroundColor = 'white';

        if (e.dataTransfer.files.length) {
            handleImage(e.dataTransfer.files[0]);
        }
    });

    // 处理选择的图片
    imageUpload.addEventListener('change', () => {
        if (imageUpload.files.length) {
            handleImage(imageUpload.files[0]);
        }
    });

    // 显示预览图片
    function handleImage(file) {
        if (!file.type.startsWith('image/')) {
            alert('请上传图片文件(支持JPG、PNG等格式)');
            return;
        }

        // 检查文件大小(限制10MB)
        if (file.size > 10 * 1024 * 1024) {
            alert('图片文件过大,请上传小于10MB的图片');
            return;
        }

        selectedImage = file;
        const reader = new FileReader();

        reader.onload = (e) => {
            imagePreview.innerHTML = `<img src="${e.target.result}" alt="预览图片">`;
        };

        reader.readAsDataURL(file);
    }

    // 提交图片进行处理
    submitBtn.addEventListener('click', async () => {
        if (!selectedImage) {
            alert('请先选择一张图片');
            return;
        }

        // 重置结果区域
        problemText.textContent = '-';
        answerText.textContent = '-';
        stepsList.innerHTML = '';
        processingTime.textContent = '-';
        tokensUsed.textContent = '-';

        // 显示加载状态
        loading.style.display = 'block';

        try {
            // 创建FormData
            const formData = new FormData();
            formData.append('image', selectedImage);

            // 发送请求
            const response = await fetch('/solve_math_problem', {
                method: 'POST',
                body: formData
            });

            const result = await response.json();

            // 隐藏加载状态
            loading.style.display = 'none';

            if (!response.ok) {
                throw new Error(result.error || '处理失败,请重试');
            }

            // 显示结果
            problemText.textContent = result.problem || '未识别到有效题目';
            answerText.textContent = result.answer || '未获取到答案';

            // 显示统计信息
            processingTime.textContent = result.processing_time || '未知';
            tokensUsed.textContent = result.tokens_used || '未知';

            // 显示解题步骤
            if (result.steps && result.steps.length) {
                stepsList.innerHTML = result.steps.map(step => {
                    // 简单的数学公式处理(粗体显示数字和符号)
                    step = step.replace(/(\d+|\+|\-|\×|\÷|\=|\(|\)|\[|\]|\{|\}|\.|\/|\*)/g, '<strong>$1</strong>');
                    return `<li>${step}</li>`;
                }).join('');
            } else {
                stepsList.innerHTML = '<li>无解题步骤</li>';
            }

        } catch (error) {
            loading.style.display = 'none';
            alert(error.message);
            console.error('错误:', error);
        }
    });
});
前端代码(style.js
代码语言:javascript
代码运行次数:0
运行
复制
* {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}

body {
    background-color: #f5f7fa;
    color: #333;
    line-height: 1.6;
}

.container {
    max-width: 1200px;
    margin: 0 auto;
    padding: 20px;
}

header {
    text-align: center;
    margin-bottom: 40px;
    padding: 20px;
    background-color: white;
    border-radius: 10px;
    box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}

header h1 {
    color: #2c3e50;
    margin-bottom: 10px;
}

main {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 30px;
}

@media (max-width: 768px) {
    main {
        grid-template-columns: 1fr;
    }
}

.upload-area {
    background-color: white;
    border: 2px dashed #3498db;
    border-radius: 10px;
    padding: 40px 20px;
    text-align: center;
    cursor: pointer;
    transition: all 0.3s ease;
}

.upload-area:hover {
    border-color: #2980b9;
    box-shadow: 0 4px 15px rgba(0,0,0,0.1);
}

.upload-icon {
    font-size: 50px;
    margin-bottom: 20px;
    color: #3498db;
}

.upload-tips {
    margin-top: 15px;
    color: #7f8c8d;
    font-size: 14px;
}

#imageUpload {
    display: none;
}

#submitBtn {
    background-color: #3498db;
    color: white;
    border: none;
    padding: 10px 20px;
    border-radius: 5px;
    font-size: 16px;
    cursor: pointer;
    margin-top: 20px;
    transition: background-color 0.3s ease;
}

#submitBtn:hover {
    background-color: #2980b9;
}

.preview-area, .result-area {
    background-color: white;
    border-radius: 10px;
    padding: 20px;
    box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}

.preview-area h3, .result-area h3 {
    margin-bottom: 20px;
    color: #2c3e50;
    border-bottom: 2px solid #f1f1f1;
    padding-bottom: 10px;
}

.image-preview {
    width: 100%;
    min-height: 200px;
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: #f9f9f9;
    border-radius: 5px;
    overflow: hidden;
}

.image-preview img {
    max-width: 100%;
    max-height: 400px;
    object-fit: contain;
}

.result-area {
    grid-column: 1 / -1;
}

.loading {
    text-align: center;
    padding: 20px;
    color: #7f8c8d;
    font-style: italic;
}

.spinner {
    width: 40px;
    height: 40px;
    margin: 0 auto 15px;
    border: 4px solid #f3f3f3;
    border-top: 4px solid #3498db;
    border-radius: 50%;
    animation: spin 1s linear infinite;
}

@keyframes spin {
    0% { transform: rotate(0deg); }
    100% { transform: rotate(360deg); }
}

.problem, .answer, .steps {
    margin-bottom: 25px;
    padding: 15px;
    background-color: #f9f9f9;
    border-radius: 5px;
}

.problem h4, .answer h4, .steps h4 {
    margin-bottom: 10px;
    color: #34495e;
}

.stepsList {
    padding-left: 20px;
}

.stepsList li {
    margin-bottom: 10px;
    line-height: 1.8;
}

.stats {
    font-size: 14px;
    color: #7f8c8d;
    padding: 10px 15px;
    background-color: #f9f9f9;
    border-radius: 5px;
}

.stats p {
    display: inline-block;
    margin-right: 20px;
}

footer {
    text-align: center;
    margin-top: 50px;
    color: #7f8c8d;
    font-size: 14px;
}

总结

AI智能答题助手 是一个非常实用的工具,它结合了先进的图像识别技术和强大的解题算法,能够快速准确地解答数学题。无论是学生在学习中遇到难题,还是教师需要快速验证答案,这个工具都能提供很大的帮助。通过简单的部署,你可以在本地或蓝耕云MCP平台上使用它,希望这个项目能为你的学习和工作带来便利!

改进

对复杂数学题图片识别还是不够精准,可以使用其它收费的OCR识别平台,对复杂数学题的解答不够详细,大模型响应比较慢,建议使用付费功能更齐全的AI大模型

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-10-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • AI智能答题助手:基于MCP平台的数学题图片识别与解答插件
    • 项目简介
    • 功能特点
    • 技术架构
    • 快速开始
      • 本地运行
      • 部署到蓝耕云MCP平台
    • 项目结构
    • 代码示例
      • 后端代码(app.py)
      • 前端代码(index.html)
      • 前端代码(script.js)
      • 前端代码(style.js)
    • 总结
    • 改进
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档