## 主要改进 ### 🚀 Gemini 3 Pro 集成 - 支持通过 OpenRouter API 调用 Google Gemini 3 Pro Preview 模型 - 针对数学试卷优化 prompt,增强数学公式识别能力 - 自动检测 Gemini 模型并启用数学优化模式 - 设置 Gemini 3 Pro 为默认解析模型(AI_MODEL 环境变量) ### 🔧 JSON 转义修复 - 实现 fix_json_escaping() 函数处理 LaTeX 公式中的反斜杠 - 智能识别并转义 LaTeX 命令(\frac, \sqrt, \pm 等) - 保留有效的 JSON 转义序列(\", \\, \/, \uXXXX) - 解决 Gemini 返回的 LaTeX 公式 JSON 解析失败问题 ### 📐 数学解析增强 - 详细的 LaTeX 公式转换说明(分数、根式、积分、矩阵等) - 特殊数学符号支持(不等式、集合、逻辑、箭头等) - 几何图形识别和描述 - 完整的解题步骤和思路分析 ### 📝 新增文件 - GEMINI_INTEGRATION.md: 完整的集成文档 - test_gemini_math.py: Claude vs Gemini 对比测试脚本 - test_gemini_fixed.py: JSON 修复验证测试 - example_gemini_usage.py: 使用示例和最佳实践 - quick_test_gemini.sh: 快速测试脚本 ### 🎯 测试结果 - ✅ 成功解析数学试卷(5 道题,100 分) - ✅ 正确提取所有 LaTeX 公式 - ✅ 详细的解题步骤和知识点标注 - ✅ 准确的难度评估(难度 2-5) ## 配置说明 在 .env 文件中设置: ```bash AI_MODEL=google/gemini-3-pro-preview USE_CLAUDE_PARSER=true ``` 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
264 lines
8.4 KiB
Python
Executable File
264 lines
8.4 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Gemini 3 Pro 数学试卷解析示例
|
|
展示如何使用 Gemini 模型进行数学试卷解析
|
|
"""
|
|
|
|
import os
|
|
import json
|
|
from dotenv import load_dotenv
|
|
from claude_exam_parser import ClaudeExamParser
|
|
|
|
# 加载环境变量
|
|
load_dotenv()
|
|
|
|
|
|
def example_1_basic_usage():
|
|
"""示例1: 基本使用 - 使用 Gemini 解析数学试卷"""
|
|
print("=" * 80)
|
|
print("示例1: 使用 Gemini 3 Pro 解析数学试卷")
|
|
print("=" * 80)
|
|
|
|
# 创建解析器,明确指定使用 Gemini 模型
|
|
parser = ClaudeExamParser(
|
|
model="google/gemini-3-pro-preview"
|
|
)
|
|
|
|
# 解析数学试卷
|
|
# 注意:请替换为实际的试卷文件路径
|
|
result = parser.parse_exam_file(
|
|
file_path="sample_math_exam.pdf", # 替换为实际文件
|
|
subject="数学",
|
|
grade="高一"
|
|
)
|
|
|
|
if result["success"]:
|
|
data = result["result"]
|
|
print(f"\n✅ 解析成功!")
|
|
print(f"学科: {data['subject']}")
|
|
print(f"年级: {data.get('grade', 'N/A')}")
|
|
print(f"题目总数: {data['total_questions']}")
|
|
print(f"\n前3道题目:")
|
|
|
|
for q in data["questions"][:3]:
|
|
print(f"\n 题目 {q['number']} ({q['type']})")
|
|
print(f" 题干: {q['question'][:100]}...")
|
|
if q.get("formulas"):
|
|
print(f" 公式: {q['formulas'][:2]}")
|
|
print(f" 难度: {q.get('difficulty', 'N/A')}")
|
|
else:
|
|
print(f"\n❌ 解析失败: {result['error']}")
|
|
|
|
|
|
def example_2_compare_models():
|
|
"""示例2: 对比不同模型的解析效果"""
|
|
print("\n\n" + "=" * 80)
|
|
print("示例2: 对比 Claude 和 Gemini 的解析效果")
|
|
print("=" * 80)
|
|
|
|
file_path = "sample_math_exam.pdf" # 替换为实际文件
|
|
models = [
|
|
"anthropic/claude-sonnet-4.5",
|
|
"google/gemini-3-pro-preview"
|
|
]
|
|
|
|
results = {}
|
|
|
|
for model in models:
|
|
print(f"\n正在使用 {model} 解析...")
|
|
parser = ClaudeExamParser(model=model)
|
|
result = parser.parse_exam_file(file_path, subject="数学")
|
|
|
|
if result["success"]:
|
|
data = result["result"]
|
|
# 统计公式数量
|
|
formula_count = sum(
|
|
len(q.get("formulas", []))
|
|
for q in data.get("questions", [])
|
|
)
|
|
results[model] = {
|
|
"total_questions": data.get("total_questions", 0),
|
|
"formula_count": formula_count
|
|
}
|
|
print(f" ✅ 识别题目: {results[model]['total_questions']}")
|
|
print(f" ✅ 提取公式: {results[model]['formula_count']}")
|
|
else:
|
|
print(f" ❌ 解析失败: {result['error']}")
|
|
|
|
print(f"\n对比结果:")
|
|
print(json.dumps(results, indent=2, ensure_ascii=False))
|
|
|
|
|
|
def example_3_extract_formulas():
|
|
"""示例3: 提取并分析数学公式"""
|
|
print("\n\n" + "=" * 80)
|
|
print("示例3: 提取并分析数学公式")
|
|
print("=" * 80)
|
|
|
|
parser = ClaudeExamParser(model="google/gemini-3-pro-preview")
|
|
result = parser.parse_exam_file(
|
|
file_path="sample_math_exam.pdf", # 替换为实际文件
|
|
subject="数学"
|
|
)
|
|
|
|
if result["success"]:
|
|
data = result["result"]
|
|
all_formulas = []
|
|
|
|
# 收集所有公式
|
|
for q in data["questions"]:
|
|
formulas = q.get("formulas", [])
|
|
if formulas:
|
|
all_formulas.extend([
|
|
{
|
|
"question_number": q["number"],
|
|
"formula": f
|
|
}
|
|
for f in formulas
|
|
])
|
|
|
|
print(f"\n找到 {len(all_formulas)} 个公式:")
|
|
for i, item in enumerate(all_formulas[:10], 1): # 显示前10个
|
|
print(f"{i}. 题目 {item['question_number']}: {item['formula']}")
|
|
|
|
# 分析公式类型
|
|
formula_types = {
|
|
"分数": 0,
|
|
"根式": 0,
|
|
"积分": 0,
|
|
"求和": 0,
|
|
"向量": 0
|
|
}
|
|
|
|
for item in all_formulas:
|
|
formula = item["formula"]
|
|
if "\\frac" in formula:
|
|
formula_types["分数"] += 1
|
|
if "\\sqrt" in formula:
|
|
formula_types["根式"] += 1
|
|
if "\\int" in formula:
|
|
formula_types["积分"] += 1
|
|
if "\\sum" in formula:
|
|
formula_types["求和"] += 1
|
|
if "\\vec" in formula or "\\overrightarrow" in formula:
|
|
formula_types["向量"] += 1
|
|
|
|
print(f"\n公式类型分布:")
|
|
for ftype, count in formula_types.items():
|
|
if count > 0:
|
|
print(f" {ftype}: {count}")
|
|
|
|
|
|
def example_4_analyze_difficulty():
|
|
"""示例4: 分析题目难度分布"""
|
|
print("\n\n" + "=" * 80)
|
|
print("示例4: 分析题目难度分布")
|
|
print("=" * 80)
|
|
|
|
parser = ClaudeExamParser(model="google/gemini-3-pro-preview")
|
|
result = parser.parse_exam_file(
|
|
file_path="sample_math_exam.pdf", # 替换为实际文件
|
|
subject="数学"
|
|
)
|
|
|
|
if result["success"]:
|
|
data = result["result"]
|
|
|
|
# 难度分布
|
|
difficulty_dist = data.get("stats", {}).get("difficulty_distribution", {})
|
|
|
|
if difficulty_dist:
|
|
print(f"\n难度分布:")
|
|
total = sum(difficulty_dist.values())
|
|
for level in sorted(difficulty_dist.keys()):
|
|
count = difficulty_dist[level]
|
|
percentage = (count / total) * 100
|
|
bar = "█" * int(percentage / 5)
|
|
print(f" 难度 {level}: {count:2d} 题 ({percentage:5.1f}%) {bar}")
|
|
|
|
# 按难度分组题目
|
|
by_difficulty = {}
|
|
for q in data["questions"]:
|
|
diff = q.get("difficulty", 0)
|
|
if diff not in by_difficulty:
|
|
by_difficulty[diff] = []
|
|
by_difficulty[diff].append(q["number"])
|
|
|
|
print(f"\n各难度题目编号:")
|
|
for level in sorted(by_difficulty.keys()):
|
|
questions = by_difficulty[level]
|
|
print(f" 难度 {level}: {', '.join(map(str, questions))}")
|
|
|
|
|
|
def example_5_knowledge_points():
|
|
"""示例5: 分析知识点覆盖"""
|
|
print("\n\n" + "=" * 80)
|
|
print("示例5: 分析知识点覆盖")
|
|
print("=" * 80)
|
|
|
|
parser = ClaudeExamParser(model="google/gemini-3-pro-preview")
|
|
result = parser.parse_exam_file(
|
|
file_path="sample_math_exam.pdf", # 替换为实际文件
|
|
subject="数学"
|
|
)
|
|
|
|
if result["success"]:
|
|
data = result["result"]
|
|
|
|
# 统计知识点
|
|
knowledge_point_count = {}
|
|
|
|
for q in data["questions"]:
|
|
points = q.get("knowledge_points", [])
|
|
for point in points:
|
|
knowledge_point_count[point] = knowledge_point_count.get(point, 0) + 1
|
|
|
|
# 按出现频率排序
|
|
sorted_points = sorted(
|
|
knowledge_point_count.items(),
|
|
key=lambda x: x[1],
|
|
reverse=True
|
|
)
|
|
|
|
print(f"\n知识点覆盖 (共 {len(sorted_points)} 个知识点):")
|
|
for point, count in sorted_points[:15]: # 显示前15个
|
|
bar = "●" * count
|
|
print(f" {point:20s}: {count:2d} 题 {bar}")
|
|
|
|
|
|
def main():
|
|
"""主函数 - 运行所有示例"""
|
|
print("\n")
|
|
print("╔" + "=" * 78 + "╗")
|
|
print("║" + " " * 20 + "Gemini 3 Pro 数学试卷解析示例" + " " * 28 + "║")
|
|
print("╚" + "=" * 78 + "╝")
|
|
|
|
# 检查 API Key
|
|
if not os.getenv("OPENROUTER_API_KEY"):
|
|
print("\n❌ 错误: 未设置 OPENROUTER_API_KEY")
|
|
print("请在 .env 文件中设置,或运行: export OPENROUTER_API_KEY='your-key'")
|
|
return
|
|
|
|
# 运行示例(注意:需要实际的PDF文件)
|
|
print("\n⚠️ 注意: 这些示例需要实际的PDF试卷文件")
|
|
print("请将 'sample_math_exam.pdf' 替换为实际的文件路径\n")
|
|
|
|
# 可以取消注释运行各个示例
|
|
# example_1_basic_usage()
|
|
# example_2_compare_models()
|
|
# example_3_extract_formulas()
|
|
# example_4_analyze_difficulty()
|
|
# example_5_knowledge_points()
|
|
|
|
print("\n使用说明:")
|
|
print("1. 准备一份数学试卷 PDF 文件")
|
|
print("2. 编辑此文件,将 'sample_math_exam.pdf' 替换为实际文件路径")
|
|
print("3. 取消注释要运行的示例")
|
|
print("4. 运行: python example_gemini_usage.py")
|
|
print("\n或者直接使用测试脚本:")
|
|
print(" ./quick_test_gemini.sh your_math_exam.pdf 数学 高一")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|