Skip to content

Commit 5171228

Browse files
committed
feat: enhance AI solver with LeetCode verification retry logic
- Add MAX_LEETCODE_FIX_ATTEMPTS config (default: 3) - Refactor _submit_to_leetcode to return detailed error feedback - Improve _run_conversation_loop to auto-fix LeetCode failures - Add _build_leetcode_error_feedback for better error analysis - Update auto_solver to handle LeetCode verification retries - Remove unused _fix methods (integrated into main loop)
1 parent 4ba45e3 commit 5171228

3 files changed

Lines changed: 131 additions & 154 deletions

File tree

script/leetcode/ai/auto_solver.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -159,14 +159,20 @@ def run_once(self) -> tuple[bool, Optional[str]]:
159159

160160
# 判断是否成功(通过题目数量变化 + LeetCode 验证通过)
161161
leetcode_passed = "LeetCode 验证通过!" in stdout_output or "LeetCode 验证通过" in stdout_output
162+
# 检查是否因为 LeetCode 修复次数耗尽而失败
163+
leetcode_fix_exhausted = "达到 LeetCode 验证失败最大修复次数" in stdout_output
162164

163165
if end_count > start_count and leetcode_passed:
164166
self._log("✅ 解题完成,新增题目", "SUCCESS")
165167
return True, None
168+
elif end_count > start_count and leetcode_fix_exhausted:
169+
# 文件创建了但 LeetCode 验证最终未能通过(已尝试修复但次数耗尽)
170+
self._log("⚠️ 本地文件已生成,但 LeetCode 验证最终未能通过(已尝试多次修复)", "WARNING")
171+
return False, "leetcode_verification_failed_after_retries"
166172
elif end_count > start_count and not leetcode_passed:
167-
# 文件创建了但 LeetCode 验证失败
168-
self._log(" 本地文件已生成,LeetCode 验证未通过", "WARNING")
169-
return False, "leetcode_verification_failed"
173+
# 文件创建了但 LeetCode 验证失败(可能是没有 Cookie 跳过验证)
174+
self._log("⚠️ 本地文件已生成,LeetCode 验证未进行或失败", "WARNING")
175+
return True, None # 本地测试通过也算成功,LeetCode 验证是额外的
170176

171177
# 分析失败原因
172178
output = stdout_output + stderr_output

script/leetcode/ai/solver.py

Lines changed: 121 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ def __init__(
9393

9494
# 编译错误计数(防止在编译错误上无限循环)
9595
self._compile_fix_count: int = 0
96+
# LeetCode 验证失败修复计数
97+
self._leetcode_fix_count: int = 0
9698

9799
def _load_env(self) -> None:
98100
"""加载环境变量"""
@@ -285,12 +287,13 @@ def _init_conversation(self, problem_id: int, is_daily: bool) -> None:
285287
]
286288
self._current_reasoning = ""
287289
self._compile_fix_count = 0 # 重置编译错误计数
290+
self._leetcode_fix_count = 0 # 重置 LeetCode 修复计数
288291

289292
def _run_conversation_loop(self) -> bool:
290293
"""运行对话循环
291294
292295
Returns:
293-
bool: 是否成功完成解题
296+
bool: 是否成功完成解题(包括本地测试通过和 LeetCode 验证通过)
294297
"""
295298
model_name = self.provider.model
296299
self._print_model_info(model_name)
@@ -317,14 +320,34 @@ def _run_conversation_loop(self) -> bool:
317320
handle_elapsed = time.time() - handle_start
318321
log_with_time(f"🛠️ 工具执行完成 ({handle_elapsed:.1f}s)", ColorCode.CYAN)
319322
else:
320-
# 没有工具调用,解题完成
323+
# 没有工具调用,表示 AI 认为解题完成
321324
round_elapsed = time.time() - round_start
322325
log_with_time(f"✅ 第 {iteration + 1} 轮完成 ({round_elapsed:.1f}s)", ColorCode.GREEN)
323326
self._print_completion(message)
324327
self._generate_solution_report()
328+
325329
# 自动提交到 LeetCode 验证
326-
self._submit_to_leetcode()
327-
return True
330+
leetcode_success, feedback = self._submit_to_leetcode()
331+
332+
if leetcode_success:
333+
# 验证通过,解题成功
334+
return True
335+
336+
# LeetCode 验证失败,需要继续修复
337+
self._leetcode_fix_count += 1
338+
max_leetcode_fix = getattr(AIConfig, 'MAX_LEETCODE_FIX_ATTEMPTS', 3)
339+
340+
if self._leetcode_fix_count >= max_leetcode_fix:
341+
log_with_time(f"⚠️ 达到 LeetCode 验证失败最大修复次数 ({max_leetcode_fix}),停止修复", ColorCode.YELLOW)
342+
return False
343+
344+
log_with_time(f"🔧 LeetCode 验证失败,开始第 {self._leetcode_fix_count}/{max_leetcode_fix} 次修复尝试...", ColorCode.YELLOW)
345+
346+
# 将错误反馈添加到对话,让 AI 继续修复
347+
self.messages.append({"role": "user", "content": feedback})
348+
349+
# 继续下一轮对话进行修复
350+
continue
328351

329352
# 达到最大迭代次数
330353
log_with_time("⚠️ 达到最大迭代次数,停止处理", ColorCode.YELLOW)
@@ -671,174 +694,121 @@ def _print_model_info(self, model_name: str) -> None:
671694
else:
672695
log_with_time(f"💬 使用 {model_name} 模型", ColorCode.CYAN)
673696

674-
def _submit_to_leetcode(self) -> bool:
697+
def _submit_to_leetcode(self) -> tuple[bool, Optional[str]]:
675698
"""本地测试通过后,提交到 LeetCode 验证
676699
677700
Returns:
678-
bool: 是否通过 LeetCode 验证
701+
tuple[bool, Optional[str]]: (是否通过验证, 错误反馈信息用于AI修复)
702+
- 通过验证: (True, None)
703+
- 未通过验证: (False, 错误反馈信息)
704+
- 无 Cookie 跳过验证: (True, None)
679705
"""
680706
if not self.problem_id:
681-
return False
707+
return False, "无法获取题目 ID"
682708

683709
# 检查是否有 LEETCODE_COOKIE
684710
if not os.getenv("LEETCODE_COOKIE"):
685711
print()
686712
print(color_text("💡 提示: 设置 LEETCODE_COOKIE 后可自动提交到 LeetCode 验证", ColorCode.CYAN.value))
687-
return True # 返回 True 表示本地已通过
713+
return True, None # 返回 True 表示本地已通过
688714

689-
max_retries = AIConfig.LEETCODE_SUBMIT_MAX_RETRIES
690-
for attempt in range(max_retries):
691-
print()
692-
log_with_time(f"🌐 第 {attempt + 1}/{max_retries} 次提交到 LeetCode 验证...", ColorCode.CYAN)
693-
694-
try:
695-
# 导入提交模块
696-
from script.leetcode.submit import LeetCodeSubmitter
697-
698-
submitter = LeetCodeSubmitter()
699-
# 提交并获取详细结果
700-
result = submitter.submit_problem_with_result(self.problem_id, solution_num=1)
701-
702-
if result.status == "Accepted":
703-
log_with_time("✅ LeetCode 验证通过!", ColorCode.GREEN)
704-
return True
705-
706-
# 处理不同类型的失败
707-
if result.status == "Wrong Answer" and result.failed_test_case:
708-
log_with_time(f"❌ Wrong Answer (测试用例 {result.passed_test_cases + 1}/{result.total_test_cases})", ColorCode.RED)
709-
710-
# 获取失败信息
711-
failed = result.failed_test_case
712-
log_with_time("\n失败的测试用例:", ColorCode.YELLOW)
713-
log_with_time(f"输入: {failed.get('input', 'N/A')[:200]}...")
714-
log_with_time(f"输出: {failed.get('actual', 'N/A')[:200]}...")
715-
log_with_time(f"期望: {failed.get('expected', 'N/A')[:200]}...")
716-
717-
# 更新本地测试用例并修复
718-
if attempt < max_retries - 1:
719-
log_with_time("🔧 更新本地测试用例并修复代码...", ColorCode.YELLOW)
720-
if self._fix_with_leetcode_test_case(failed):
721-
log_with_time("✅ 代码修复完成,重新提交...", ColorCode.GREEN)
722-
continue
723-
else:
724-
log_with_time("❌ 自动修复失败", ColorCode.RED)
725-
return False
726-
727-
elif result.status == "Runtime Error":
728-
log_with_time(f"💥 Runtime Error: {result.error_message}", ColorCode.RED)
729-
if attempt < max_retries - 1:
730-
log_with_time("🔧 尝试修复运行时错误...", ColorCode.YELLOW)
731-
if self._fix_runtime_error(result.error_message):
732-
continue
733-
return False
734-
735-
elif result.status == "Time Limit Exceeded":
736-
log_with_time("⏱️ Time Limit Exceeded", ColorCode.YELLOW)
737-
return False
738-
739-
else:
740-
log_with_time(f"❌ {result.status}", ColorCode.RED)
741-
return False
742-
743-
except Exception as e:
744-
log_with_time(f"⚠️ 提交到 LeetCode 时出错: {e}", ColorCode.YELLOW)
745-
return False
715+
print()
716+
log_with_time("🌐 正在提交到 LeetCode 验证...", ColorCode.CYAN)
746717

747-
return False
748-
749-
def _fix_with_leetcode_test_case(self, failed_test_case: Dict) -> bool:
750-
"""使用 LeetCode 失败的测试用例修复代码"""
751718
try:
752-
# 构建修复提示
753-
fix_prompt = f"""本地测试已通过,但 LeetCode 提交失败。
754-
755-
失败的测试用例:
756-
- 输入: {failed_test_case.get('input', 'N/A')}
757-
- 输出: {failed_test_case.get('actual', 'N/A')}
758-
- 期望: {failed_test_case.get('expected', 'N/A')}
759-
760-
请按以下步骤修复:
761-
762-
**步骤 1: 添加失败的测试用例**
763-
使用 `append_test_case` 工具将此测试用例添加到本地测试文件。
764-
test_name 建议使用 "WrongAnswerCase1" 或描述性名称如 "EdgeCaseEmptyArray"
765-
test_code 格式示例(注意缩进为2个空格):
766-
```
767-
// 输入: nums = [1,2,3], target = 4
768-
// 期望: [0,1]
769-
vector<int> nums = {{1, 2, 3}};
770-
int target = 4;
771-
vector<int> expected = {{0, 1}};
772-
vector<int> result = solution.twoSum(nums, target);
773-
EXPECT_EQ(expected, result);
774-
```
775-
776-
**步骤 2: 分析并修复**
777-
使用 `retrieve_file_content` 查看当前代码,分析失败原因,然后使用 `create_or_update_file` 修复源文件中的问题。
778-
779-
**步骤 3: 验证**
780-
调用 `compile_and_test` 确保修复后的代码通过所有测试。
781-
782-
请开始修复。"""
783-
784-
# 添加修复提示到对话
785-
self.messages.append({"role": "user", "content": fix_prompt})
719+
# 导入提交模块
720+
from script.leetcode.submit import LeetCodeSubmitter
786721

787-
# 运行一轮对话让 AI 修复
788-
message = self._call_api(self.provider.model)
789-
self.messages.append(self._build_message_to_save(message))
722+
submitter = LeetCodeSubmitter()
723+
# 提交并获取详细结果
724+
result = submitter.submit_problem_with_result(self.problem_id, solution_num=1)
790725

791-
if message.tool_calls:
792-
self._handle_tool_calls(message.tool_calls)
793-
# 修复后需要验证编译和测试是否通过
794-
log_with_time("🔍 验证修复结果...", ColorCode.CYAN)
795-
result = self.tool_executor.execute("compile_and_test", {"problem_id": self.problem_id})
796-
if not result.get("is_successful"):
797-
log_with_time("❌ 修复未完成", ColorCode.RED)
798-
return False
799-
log_with_time("✅ 编译和测试通过", ColorCode.GREEN)
800-
return True
801-
802-
return False
726+
if result.status == "Accepted":
727+
log_with_time("✅ LeetCode 验证通过!", ColorCode.GREEN)
728+
return True, None
803729

730+
# 构建错误反馈信息用于 AI 修复
731+
feedback = self._build_leetcode_error_feedback(result)
732+
return False, feedback
733+
804734
except Exception as e:
805-
print(color_text(f"修复过程出错: {e}", ColorCode.RED.value))
806-
return False
735+
error_msg = f"提交到 LeetCode 时出错: {e}"
736+
log_with_time(f"⚠️ {error_msg}", ColorCode.YELLOW)
737+
return False, error_msg
807738

808-
def _fix_runtime_error(self, error_message: str) -> bool:
809-
"""修复运行时错误"""
810-
try:
811-
fix_prompt = f"""代码出现 Runtime Error:
812-
813-
错误信息:
814-
{error_message}
815-
816-
请:
817-
1. 分析错误原因(数组越界?空指针?除以零?)
818-
2. 使用 `retrieve_file_content` 查看代码
819-
3. 修复问题并调用 `compile_and_test` 验证"""
820-
821-
self.messages.append({"role": "user", "content": fix_prompt})
822-
823-
message = self._call_api(self.provider.model)
824-
self.messages.append(self._build_message_to_save(message))
739+
def _build_leetcode_error_feedback(self, result) -> str:
740+
"""根据 LeetCode 返回结果构建 AI 修复提示"""
741+
from script.leetcode.submit import SubmissionResult
742+
743+
lines = ["LeetCode 提交失败,需要修复。", ""]
744+
745+
if result.status == "Wrong Answer":
746+
lines.append(f"❌ Wrong Answer (通过了 {result.passed_test_cases}/{result.total_test_cases} 个测试用例)")
747+
lines.append("")
748+
if result.failed_test_case:
749+
failed = result.failed_test_case
750+
lines.append("失败的测试用例:")
751+
lines.append(f"- 输入: {failed.get('input', 'N/A')}")
752+
lines.append(f"- 输出: {failed.get('actual', 'N/A')}")
753+
lines.append(f"- 期望: {failed.get('expected', 'N/A')}")
754+
lines.append("")
755+
lines.append("请按以下步骤修复:")
756+
lines.append("")
757+
lines.append("**步骤 1: 添加失败的测试用例到本地**")
758+
lines.append("使用 `append_test_case` 工具将此测试用例添加到本地测试文件。")
759+
lines.append("test_name 建议使用描述性名称如 'WrongAnswerCase1' 或 'EdgeCaseEmptyArray'")
760+
lines.append("test_code 格式示例(注意缩进为2个空格):")
761+
lines.append("```")
762+
lines.append(" // 输入: nums = [1,2,3], target = 4")
763+
lines.append(" // 期望: [0,1]")
764+
lines.append(" vector<int> nums = {{1, 2, 3}};")
765+
lines.append(" int target = 4;")
766+
lines.append(" vector<int> expected = {{0, 1}};")
767+
lines.append(" vector<int> result = solution.twoSum(nums, target);")
768+
lines.append(" EXPECT_EQ(expected, result);")
769+
lines.append("```")
770+
lines.append("")
771+
lines.append("**步骤 2: 分析并修复代码**")
772+
lines.append("使用 `retrieve_file_content` 查看当前代码,分析失败原因,")
773+
lines.append("然后使用 `create_or_update_file` 修复源文件中的问题。")
774+
lines.append("")
775+
lines.append("**步骤 3: 验证修复**")
776+
lines.append("调用 `compile_and_test` 确保修复后的代码通过所有测试。")
777+
778+
elif result.status == "Runtime Error":
779+
lines.append(f"💥 Runtime Error")
780+
if result.error_message:
781+
lines.append(f"错误信息: {result.error_message}")
782+
lines.append("")
783+
lines.append("请分析错误原因(数组越界?空指针?除以零?),然后:")
784+
lines.append("1. 使用 `retrieve_file_content` 查看代码")
785+
lines.append("2. 修复问题并使用 `create_or_update_file` 更新代码")
786+
lines.append("3. 调用 `compile_and_test` 验证修复")
825787

826-
if message.tool_calls:
827-
self._handle_tool_calls(message.tool_calls)
828-
# 修复后需要验证编译和测试是否通过
829-
log_with_time("🔍 验证修复结果...", ColorCode.CYAN)
830-
result = self.tool_executor.execute("compile_and_test", {"problem_id": self.problem_id})
831-
if not result.get("is_successful"):
832-
log_with_time("❌ 修复未完成", ColorCode.RED)
833-
return False
834-
log_with_time("✅ 编译和测试通过", ColorCode.GREEN)
835-
return True
788+
elif result.status == "Time Limit Exceeded":
789+
lines.append("⏱️ Time Limit Exceeded")
790+
lines.append("")
791+
lines.append("算法时间复杂度过高,请考虑:")
792+
lines.append("1. 优化算法(例如使用更高效的数据结构)")
793+
lines.append("2. 减少冗余计算")
794+
lines.append("3. 使用 `retrieve_file_content` 查看当前实现并改进")
836795

837-
return False
796+
elif result.status == "Compile Error":
797+
lines.append("❌ Compile Error")
798+
if result.error_message:
799+
lines.append(f"编译错误信息: {result.error_message}")
800+
lines.append("")
801+
lines.append("请检查代码语法并修复编译错误。")
838802

839-
except Exception as e:
840-
log_with_time(f"修复运行时错误出错: {e}", ColorCode.RED)
841-
return False
803+
else:
804+
lines.append(f"❌ {result.status}")
805+
if result.error_message:
806+
lines.append(f"错误信息: {result.error_message}")
807+
808+
lines.append("")
809+
lines.append("请开始修复。")
810+
811+
return "\n".join(lines)
842812

843813
@staticmethod
844814
def _get_system_prompt() -> str:

script/leetcode/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ class AIConfig:
6262

6363
# 修复策略
6464
MAX_COMPILE_FIX_ATTEMPTS = 5 # 连续编译错误的最大修复次数,超过则放弃
65+
MAX_LEETCODE_FIX_ATTEMPTS = 3 # LeetCode 验证失败后的最大修复次数,超过则放弃
6566

6667

6768
@dataclass(frozen=True)

0 commit comments

Comments
 (0)