mirror of
https://github.com/jihe520/MathModelAgent.git
synced 2026-10-02 02:44:56 +08:00
fix docs and interpreter, update tests
This commit is contained in:
@@ -24,12 +24,12 @@
|
||||
## ✨ 功能特性
|
||||
|
||||
- 🔍 自动分析问题,数学建模,编写代码,纠正错误,撰写论文
|
||||
- 💻 Code Interperter
|
||||
- loacl Interperter: 基于 jupyter , 代码保存为 notebook 方便再编辑
|
||||
- 云端 code interperter: [E2B](https://e2b.dev/) 和 [daytona](https://app.daytona.io/)
|
||||
- 💻 Code Interpreter
|
||||
- local Interpreter: 基于 jupyter , 代码保存为 notebook 方便再编辑
|
||||
- 云端 code interpreter: [E2B](https://e2b.dev/) 和 [daytona](https://app.daytona.io/)
|
||||
- 📝 生成一份编排好格式的论文
|
||||
- 🤝 muti-agents: 建模手,代码手,论文手等
|
||||
- 🔄 muti-llms: 每个 agent 设置不同的、合适的模型
|
||||
- 🤝 multi-agents: 建模手,代码手,论文手等
|
||||
- 🔄 multi-llms: 每个 agent 设置不同的、合适的模型
|
||||
- 🤖 支持所有模型: [litellm](https://docs.litellm.ai/docs/providers)
|
||||
- 💰 成本低:workflow agentless,不依赖 agent 框架
|
||||
- 🧩 自定义模板:prompt inject 为每个 subtask 单独设置需求
|
||||
|
||||
@@ -1,146 +0,0 @@
|
||||
import re
|
||||
import os
|
||||
import json
|
||||
|
||||
|
||||
def split_footnotes(text: str) -> tuple[str, list[tuple[str, str]]]:
|
||||
"""分离正文和脚注定义"""
|
||||
main_text = re.sub(
|
||||
r"\n\[\^\d+\]:.*?(?=\n\[\^|\n\n|\Z)", "", text, flags=re.DOTALL
|
||||
).strip()
|
||||
|
||||
# 匹配脚注定义
|
||||
footnotes = re.findall(r"\[\^(\d+)\]:\s*(.+?)(?=\n\[\^|\n\n|\Z)", text, re.DOTALL)
|
||||
return main_text, footnotes
|
||||
|
||||
|
||||
def extract_footnote_references(text: str) -> list[tuple[str, str]]:
|
||||
"""提取文本中的脚注引用和对应的脚注内容"""
|
||||
# 匹配脚注引用模式:[^数字]: 内容(处理多种可能的结束方式)
|
||||
# 支持以句号、换行或文本结尾结束的脚注
|
||||
pattern = r"\[\^(\d+)\]:\s*([^[\n]+?)(?=\[\^|\n\n|\n-|\n\d+\.|\n\*|$)"
|
||||
matches = re.findall(pattern, text, re.DOTALL)
|
||||
|
||||
# 清理匹配结果
|
||||
cleaned_matches = []
|
||||
for num, content in matches:
|
||||
# 移除末尾的句号和多余空白
|
||||
content = content.strip()
|
||||
if content.endswith("。"):
|
||||
content = content[:-1]
|
||||
if content.endswith("."):
|
||||
content = content[:-1]
|
||||
cleaned_matches.append((num, content))
|
||||
|
||||
return cleaned_matches
|
||||
|
||||
|
||||
class UserOutput:
|
||||
def __init__(self):
|
||||
self.res: dict[str, dict] = {}
|
||||
self.cost_time = 0.0
|
||||
self.initialized = True
|
||||
|
||||
def get_result_to_save(self, ques_count):
|
||||
# 保存 res.json 文件
|
||||
|
||||
# 动态顺序获取拼接res value,正确拼接顺序
|
||||
ques_str = [f"ques{i}" for i in range(1, ques_count + 1)]
|
||||
|
||||
# 修改:调整章节顺序,确保符合论文结构
|
||||
seq = [
|
||||
"firstPage", # 标题、摘要、关键词
|
||||
"RepeatQues", # 一、问题重述
|
||||
"analysisQues", # 二、问题分析
|
||||
"modelAssumption", # 三、模型假设
|
||||
"symbol", # 四、符号说明和数据预处理
|
||||
"eda", # 四、数据预处理(EDA部分)
|
||||
*ques_str, # 五、模型的建立与求解(问题1、2...)
|
||||
"sensitivity_analysis", # 六、模型的分析与检验
|
||||
"judge", # 七、模型的评价、改进与推广
|
||||
]
|
||||
|
||||
# 全局文献列表,用于去重和统一编号
|
||||
global_footnotes = [] # 存储所有唯一的文献内容
|
||||
footnote_content_to_id = {} # 文献内容到全局ID的映射
|
||||
|
||||
# 处理后的各节内容
|
||||
processed_sections = []
|
||||
|
||||
for key in seq:
|
||||
if key not in self.res:
|
||||
continue
|
||||
|
||||
content = self.res[key]["response_content"]
|
||||
print(f"\n处理节: {key}")
|
||||
|
||||
# 1. 提取当前节的文献引用
|
||||
current_footnotes = extract_footnote_references(content)
|
||||
print(f"当前节文献: {current_footnotes}")
|
||||
|
||||
# 2. 处理每个文献引用
|
||||
section_footnote_mapping = {} # 当前节内的旧编号到新编号映射
|
||||
|
||||
for old_num, footnote_content in current_footnotes:
|
||||
# 清理文献内容
|
||||
footnote_content = footnote_content.strip()
|
||||
if footnote_content.endswith("。"):
|
||||
footnote_content = footnote_content[:-1]
|
||||
|
||||
# 3. 检查是否已存在于全局列表
|
||||
if footnote_content in footnote_content_to_id:
|
||||
# 文献已存在,使用已有的全局编号
|
||||
global_id = footnote_content_to_id[footnote_content]
|
||||
print(f"文献已存在: {footnote_content} -> 使用编号 {global_id}")
|
||||
else:
|
||||
# 新文献,添加到全局列表
|
||||
global_footnotes.append(footnote_content)
|
||||
global_id = len(global_footnotes)
|
||||
footnote_content_to_id[footnote_content] = global_id
|
||||
print(f"新文献: {footnote_content} -> 分配编号 {global_id}")
|
||||
|
||||
section_footnote_mapping[old_num] = global_id
|
||||
|
||||
# 4. 更新当前节中的脚注引用编号
|
||||
processed_content = content
|
||||
|
||||
# 更新引用编号
|
||||
for old_num, new_num in section_footnote_mapping.items():
|
||||
# 替换文中的引用,但不处理脚注定义
|
||||
processed_content = re.sub(
|
||||
rf"\[\^{old_num}\](?!:)", f"[^{new_num}]", processed_content
|
||||
)
|
||||
|
||||
# 移除原有的脚注定义(以换行开头的完整定义行)
|
||||
processed_content = re.sub(
|
||||
r"\n\[\^\d+\]:\s*[^[\n]+?(?=\n\[\^|\n\n|\n-|\n\d+\.|\n\*|$)",
|
||||
"",
|
||||
processed_content,
|
||||
flags=re.DOTALL,
|
||||
).strip()
|
||||
|
||||
processed_sections.append(processed_content)
|
||||
print("processed_sections:", processed_sections)
|
||||
print(f"节 {key} 脚注映射: {section_footnote_mapping}")
|
||||
|
||||
# 5. 合并所有处理后的内容
|
||||
full_content = "\n\n".join(processed_sections)
|
||||
|
||||
# 6. 添加重新编号后的脚注到文档末尾
|
||||
if global_footnotes:
|
||||
full_content += "\n\n## 参考文献\n\n"
|
||||
for i, footnote_content in enumerate(global_footnotes, 1):
|
||||
full_content += f"[^{i}]: {footnote_content}\n\n"
|
||||
|
||||
print(f"\n最终全局文献列表: {global_footnotes}")
|
||||
return full_content
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
with open("res.json", "r", encoding="utf-8") as f:
|
||||
res = json.load(f)
|
||||
user_output = UserOutput()
|
||||
user_output.res = res
|
||||
|
||||
with open("res.md", "w", encoding="utf-8") as f:
|
||||
f.write(user_output.get_result_to_save(ques_count=1))
|
||||
@@ -0,0 +1,15 @@
|
||||
import unittest
|
||||
|
||||
from app.utils.common_utils import split_footnotes
|
||||
|
||||
|
||||
class TestCommonUtils(unittest.TestCase):
|
||||
def test_split_footnotes(self):
|
||||
text = "Example[^1]\n\n[^1]: Footnote content"
|
||||
main, notes = split_footnotes(text)
|
||||
self.assertEqual(main, "Example")
|
||||
self.assertEqual(notes, [("1", "Footnote content")])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -1,22 +1,28 @@
|
||||
import os
|
||||
import asyncio
|
||||
import unittest
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
from app.tools.e2b_interpreter import E2BCodeInterpreter
|
||||
from app.utils.common_utils import create_task_id, create_work_dir
|
||||
from app.utils.common_utils import create_work_dir
|
||||
from app.tools.notebook_serializer import NotebookSerializer
|
||||
|
||||
|
||||
class TestE2BCodeInterpreter(unittest.TestCase):
|
||||
def setUp(self):
|
||||
load_dotenv()
|
||||
_, dirs = create_work_dir("20250312-104132-d3625cab")
|
||||
notebook = NotebookSerializer(dirs["jupyter"])
|
||||
self.task_id = "20250312-104132-d3625cab"
|
||||
self.work_dir = create_work_dir(self.task_id)
|
||||
notebook = NotebookSerializer(self.work_dir)
|
||||
self.code_interpreter = E2BCodeInterpreter(
|
||||
dirs, "20250312-104132-d3625cab", notebook
|
||||
self.task_id, self.work_dir, notebook
|
||||
)
|
||||
|
||||
def test_execute_code(self):
|
||||
if not os.getenv("E2B_API_KEY"):
|
||||
self.skipTest("E2B_API_KEY not set")
|
||||
|
||||
code = """
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
@@ -39,6 +45,8 @@ plt.grid(True)
|
||||
plt.legend()
|
||||
|
||||
# 显示图像
|
||||
plt.show()
|
||||
plt.show()
|
||||
"""
|
||||
self.code_interpreter.execute_code(code)
|
||||
asyncio.run(self.code_interpreter.initialize())
|
||||
asyncio.run(self.code_interpreter.execute_code(code))
|
||||
|
||||
|
||||
@@ -219,9 +219,14 @@ class LocalCodeInterpreter(BaseCodeInterpreter):
|
||||
self.interrupt_signal = True
|
||||
|
||||
def restart_jupyter_kernel(self):
|
||||
self.kernel_client.shutdown()
|
||||
self.kernel_manager, self.kernel_client = (
|
||||
jupyter_client.manager.start_new_kernel(kernel_name="python3")
|
||||
"""Restart the Jupyter kernel and recreate the work directory."""
|
||||
self.kc.shutdown()
|
||||
self.km, self.kc = jupyter_client.manager.start_new_kernel(
|
||||
kernel_name="python3"
|
||||
)
|
||||
self.interrupt_signal = False
|
||||
self._create_work_dir()
|
||||
|
||||
def _create_work_dir(self):
|
||||
"""Ensure the working directory exists after a restart."""
|
||||
os.makedirs(self.work_dir, exist_ok=True)
|
||||
|
||||
Reference in New Issue
Block a user