c2a40e3cc5
Parse alternate reasoning fields, retry with a larger max_tokens budget, cap review images, and fall back to text-only when vision returns empty. Co-authored-by: Cursor <cursoragent@cursor.com>
60 lines
1.8 KiB
Python
60 lines
1.8 KiB
Python
"""ai_client message parsing / empty-content retries."""
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
import unittest
|
|
from pathlib import Path
|
|
from unittest import mock
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
sys.path.insert(0, str(ROOT))
|
|
|
|
from lib.ai.ai_client import _openai_message_text, ai_review # noqa: E402
|
|
|
|
|
|
class TestOpenaiMessageText(unittest.TestCase):
|
|
def test_prefers_content(self):
|
|
self.assertEqual(
|
|
_openai_message_text({"content": "正文", "reasoning": "think"}),
|
|
"正文",
|
|
)
|
|
|
|
def test_falls_back_to_reasoning_content(self):
|
|
self.assertEqual(
|
|
_openai_message_text({"content": "", "reasoning_content": "备选正文"}),
|
|
"备选正文",
|
|
)
|
|
|
|
def test_skips_english_chain_of_thought(self):
|
|
self.assertEqual(
|
|
_openai_message_text(
|
|
{
|
|
"content": "",
|
|
"reasoning": "Here's a thinking process that leads to the answer...",
|
|
}
|
|
),
|
|
"",
|
|
)
|
|
|
|
|
|
class TestAiReviewImageCap(unittest.TestCase):
|
|
def test_caps_images_and_sets_max_tokens(self):
|
|
captured = {}
|
|
|
|
def fake_generate(prompt, **kwargs):
|
|
captured["prompt"] = prompt
|
|
captured.update(kwargs)
|
|
return "OK_REVIEW"
|
|
|
|
with mock.patch("lib.ai.ai_client.ai_generate", side_effect=fake_generate):
|
|
with mock.patch.dict("os.environ", {"AI_REVIEW_MAX_IMAGES": "2"}, clear=False):
|
|
out = ai_review("记录", "每日", image_paths=["a.png", "b.png", "c.png"])
|
|
self.assertIn("OK_REVIEW", out)
|
|
self.assertEqual(captured.get("image_paths"), ["a.png", "b.png"])
|
|
self.assertEqual(captured.get("max_tokens"), 8192)
|
|
self.assertIn("另跳过 1 张", out)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|