"""look_at_image 的问题收敛与兼容兜底测试(不碰网络和数据库)。""" from __future__ import annotations import tempfile import unittest import uuid from contextlib import ExitStack from pathlib import Path from types import SimpleNamespace from unittest import mock from core.ark_client import ArkConfig from tools.look_at_image import LookAtImageTool, _DEFAULT_QUESTION class LookAtImageQuestionTests(unittest.TestCase): def _tool(self) -> LookAtImageTool: return LookAtImageTool( ark_cfg=ArkConfig(api_key="test", base_url="https://example.invalid", raw={}), vision_variant_cfg={ "model_id": "vision-test", "request_timeout_s": 1, "timeout_retries": 0, }, variant_key="test", working_dir=Path("."), task_id=uuid.uuid4(), user_id=uuid.uuid4(), base_dir=Path("."), user_root=Path("."), ) def test_native_vision_model_omits_look_tool_but_keeps_document_ocr(self): from core.tool_registry import ToolContext, build_tools ark = ArkConfig( api_key="test", base_url="https://example.invalid", raw={"vision": {"test": {"model_id": "vision-test"}}}, ) def build(native_image_input: bool) -> dict: with tempfile.TemporaryDirectory() as tmp, ExitStack() as stack: root = Path(tmp) stack.enter_context(mock.patch( "core.tool_registry._external_system_status_available", return_value=False, )) stack.enter_context(mock.patch( "core.tool_registry._external_systems_available", return_value=False, )) stack.enter_context(mock.patch( "core.tool_registry.smtp_configured", return_value=False, )) stack.enter_context(mock.patch( "core.tool_registry.wechat_push_available", return_value=False, )) stack.enter_context(mock.patch( "core.tool_registry.lfasr_configured", return_value=False, )) stack.enter_context(mock.patch( "core.tool_registry.BochaConfig.load", return_value=None, )) return build_tools(ToolContext( tool_base=root, ur_path=root, working_dir_path=root, task_id=uuid.uuid4(), uid=uuid.uuid4(), cfg={}, caps=SimpleNamespace( enable_run_python=False, native_image_input=native_image_input, ), skills=SimpleNamespace(skills={}), cancel_check=None, scheduled_run=True, deferred_actions=SimpleNamespace(), ark_cfg=ark, img_provider="", img_key="", img_cfg=None, img_provider_cfg=None, video_variant="", office_to_pdf_available=False, )) text_only = build(False) native = build(True) self.assertIn("look_at_image", text_only) self.assertIn("read_document", text_only) self.assertNotIn("look_at_image", native) self.assertIn("read_document", native) def _execute_and_question(self, question=None) -> str: captured = {} def fake_chat(_cfg, _endpoint, body, **_kwargs): captured["question"] = body["messages"][0]["content"][0]["text"] return { "choices": [{"finish_reason": "stop", "message": {"content": "ok"}}], "usage": {}, }, "" with mock.patch( "tools.look_at_image.load_image_as_data_url", return_value=("data:image/png;base64,AA==", "image.png", ""), ), mock.patch("tools.look_at_image.ark_chat_with_retry", side_effect=fake_chat), \ mock.patch("tools.look_at_image.record_usage_safe", return_value=0): self._tool().execute("image.png", question=question) return captured["question"] def test_specific_question_is_forwarded_unchanged(self): question = "只读出仪表盘当前数值,不要描述其他内容。" self.assertEqual(self._execute_and_question(question), question) def test_missing_question_uses_concise_compatibility_fallback(self): self.assertEqual(self._execute_and_question(), _DEFAULT_QUESTION) self.assertIn("简洁", _DEFAULT_QUESTION) self.assertIn("不要主动全文 OCR", _DEFAULT_QUESTION) self.assertNotIn("完整描述画面内容", _DEFAULT_QUESTION) self.assertNotIn("把其中的数据、坐标轴、图例", _DEFAULT_QUESTION) if __name__ == "__main__": unittest.main()