在当今数字化时代,人工智能技术正以前所未有的速度改变着我们的学习和工作方式。今天,我将向大家介绍一个非常实用的项目——AI智能答题助手,这是一个基于MCP平台的数学题图片识别与解答插件,能够帮助学生和教师快速解决数学问题。
AI智能答题助手 是一个基于 PaddleOCR 和 SiliconFlow API 的数学题图片识别与解答工具。用户可以上传包含数学题的图片,系统会自动识别题目并给出答案和详细的解题步骤。这个工具不仅支持中英文识别,还能优化数学公式的识别效果,非常适合学生在学习过程中遇到难题时使用。
克隆项目并安装依赖:
git clone https://github.com/your-username/ai-answer-mcp-plugin.git
cd ai-answer-mcp-plugin
pip install -r backend/requirements.txt
配置环境变量:
cp .env.example .env
在 .env
文件中填写你的 SILICONFLOW_API_KEY
。
启动后端服务:
python backend/app.py
打开浏览器访问 http://127.0.0.1:8000/ 即可使用。
确保项目根目录包含 mcp.json
配置文件。
打包项目为ZIP文件(不含虚拟环境):
zip -r ai-answer-mcp-plugin.zip .
登录蓝耕云平台,进入MCP插件库。
点击“上传插件”,选择打包的ZIP文件。
配置插件参数(主要是 SILICONFLOW_API_KEY
)。
部署插件并启动服务。
ai-answer-mcp-plugin/
├── backend/ # 后端服务代码
│ ├── app.py # 主程序入口
│ ├── mcp_entry.py # MCP 插件入口
│ └── requirements.txt # 依赖清单
├── frontend/ # 前端页面
├── mcp.json # MCP 插件配置
├── .env # 本地环境变量
└── README.md # 项目说明
app.py
)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
)<!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
)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
)* {
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大模型