Files

1 line
90 KiB
JSON

{"repo": "archyixwang/markup", "files": {"apps/api/tests/test_ai_resources_platform_wallet.py": {"52eb5e98e1df1ada1877509476b51df27c75ea4d": {"t": 1785747519.6868668, "n": 43118, "h": "6cfa31b8cf263b61", "c": "import os\n\nos.environ.setdefault(\"SECRET_KEY\", \"test-secret-key-with-strong-length\")\nos.environ.setdefault(\"MONGODB_URL\", \"mongomock://localhost\")\nos.environ.setdefault(\"MONGODB_DATABASE\", \"markup_test\")\n\nfrom fastapi import Request\n\nfrom app.core.database import get_database, reset_database\nfrom app.core.security import encrypt_secret\nfrom app.models.resource import AiCallLog, AiProviderConfig, TeamAiWallet, TeamAiWalletLedger\nfrom app.models.team import Team\nfrom app.models.upload import UploadedFile\nfrom app.models.user import User\nfrom app.services.resource_service import (\n create_provider_config,\n list_team_ai_history,\n list_provider_configs,\n recharge_points_budget,\n recharge_team_ai_wallet,\n record_platform_ai_wallet_spend,\n require_team_ai_wallet_positive_balance,\n set_points_budget_payment_password,\n team_ai_wallet_payload,\n team_points_budget_payload,\n transfer_team_points_to_ai_wallet,\n)\nfrom app.services.file_storage import write_storage_file\nfrom app.services import production_service, resource_service\nfrom app.services.team_service import create_team_for_admin\n\n\ndef setup_module() -> None:\n reset_database()\n\n\ndef build_request() -> Request:\n scope = {\n \"type\": \"http\",\n \"method\": \"POST\",\n \"path\": \"/test\",\n \"headers\": [],\n \"client\": (\"127.0.0.1\", 12345),\n }\n return Request(scope)\n\n\ndef provider_create_payload(*, scope: str, team_id: str | None, route_name: str, is_platform_default: bool = False) -> dict:\n return {\n \"route_name\": route_name,\n \"provider_kind\": \"OpenAI Compatible\",\n \"scope\": scope,\n \"is_platform_default\": is_platform_default,\n \"team_id\": team_id,\n \"api_base\": \"https://provider.example.com/v1\",\n \"api_key\": \"sk-test-123\",\n \"model_id\": \"gpt-4.1-mini\",\n \"pricing\": {\n \"input_price_per_million\": 4.2,\n \"output_price_per_million\": 11.8,\n \"cache_hit_price_per_million\": 1.1,\n },\n \"capabilities\": [\"text\"],\n \"runtime_config\": {\"temperature\": 0, \"max_output_tokens\": 2048, \"timeout_ms\": 15000},\n \"status\": \"enabled\",\n \"remark\": \"test\",\n }\n\n\ndef provider_config(*, provider_kind: str = \"OpenAI Compatible\", timeout_ms: int = 15000) -> AiProviderConfig:\n return AiProviderConfig(\n scope=\"platform\",\n team_id=None,\n route_name=\"Provider Test\",\n provider_kind=provider_kind,\n provider=provider_kind,\n api_base=\"https://provider.example.com/v1\",\n encrypted_api_key=\"encrypted\",\n api_key_configured=True,\n model_id=\"model-a\",\n default_model=\"model-a\",\n models=[\"model-a\"],\n runtime_config={\"temperature\": 0, \"max_output_tokens\": 128, \"timeout_ms\": timeout_ms},\n status=\"enabled\",\n )\n\n\ndef test_generation_timeout_uses_provider_runtime_config() -> None:\n item = provider_config(timeout_ms=15000)\n\n assert resource_service._provider_generation_timeout_seconds(item) == 15.0\n assert resource_service._provider_connection_test_timeout_seconds(item) == resource_service.PROVIDER_CONNECTION_TEST_TIMEOUT_SECONDS\n\n\ndef test_provider_connection_test_uses_lightweight_token_limit_without_affecting_generation() -> None:\n item = provider_config(provider_kind=\"OpenAI Compatible\")\n item.runtime_config = {\"temperature\": 0, \"max_output_tokens\": 4096, \"timeout_ms\": 15000}\n\n _url, _headers, test_body = resource_service._provider_test_request(item, \"sk-test\", \"ping\")\n _gen_url, _gen_headers, generation_body = resource_service._provider_generation_request(item, \"sk-test\", \"model-a\", \"write a summary\")\n\n assert test_body[\"max_tokens\"] == resource_service.PROVIDER_CONNECTION_TEST_MAX_OUTPUT_TOKENS\n assert generation_body[\"max_tokens\"] == 4096\n\n\ndef test_provider_http_client_ignores_environment_proxy_by_default(monkeypatch) -> None:\n captured_kwargs: list[dict] = []\n\n class DummyResponse:\n status_code = 200\n headers: dict = {}\n\n @staticmethod\n def json() -> dict:\n return {\"usage\": {\"prompt_tokens\": 1, \"completion_tokens\": 1, \"total_tokens\": 2}}\n\n class DummyClient:\n def __init__(self, *args, **kwargs) -> None:\n captured_kwargs.append(kwargs)\n\n def __enter__(self):\n return self\n\n def __exit__(self, exc_type, exc, tb) -> None:\n return None\n\n def post(self, url: str, headers: dict | None = None, json: dict | None = None):\n assert json is not None\n assert json[\"max_tokens\"] == resource_service.PROVIDER_CONNECTION_TEST_MAX_OUTPUT_TOKENS\n return DummyResponse()\n\n monkeypatch.setattr(resource_service.httpx, \"Client\", DummyClient)\n item = provider_config(provider_kind=\"OpenAI Compatible\")\n\n result = resource_service._run_provider_test(item, api_key=\"sk-test\", message=\"ping\", request=build_request())\n\n assert result[\"status\"] == \"success\"\n assert captured_kwargs[0][\"timeout\"] == resource_service.PROVIDER_CONNECTION_TEST_TIMEOUT_SECONDS\n assert captured_kwargs[0][\"trust_env\"] is False\n\n\ndef test_provider_http_client_allows_explicit_environment_proxy_opt_in(monkeypatch) -> None:\n captured_kwargs: list[dict] = []\n\n class DummyClient:\n def __init__(self, *args, **kwargs) -> None:\n captured_kwargs.append(kwargs)\n\n monkeypatch.setattr(resource_service.httpx, \"Client\", DummyClient)\n item = provider_config(provider_kind=\"OpenAI Compatible\")\n item.runtime_config = {\"trust_env_proxy\": True}\n\n resource_service._provider_http_client(item, timeout=3.0)\n\n assert captured_kwargs[0][\"trust_env\"] is True\n\n\ndef test_openai_compatible_structured_generation_uses_json_object() -> None:\n item = provider_config(provider_kind=\"OpenAI Compatible\")\n schema = {\"type\": \"object\", \"properties\": {\"decision\": {\"type\": \"string\"}}}\n\n _url, _headers, body = resource_service._provider_generation_request(item, \"sk-test\", \"model-a\", \"return json\", structured_output_schema=schema)\n\n assert body[\"response_format\"] == {\"type\": \"json_object\"}\n\n\ndef test_deepseek_compatible_route_is_recognized_as_deepseek() -> None:\n item = provider_config(provider_kind=\"OpenAI Compatible\")\n item.route_name = \"deepseek\"\n item.api_base = \"https://api.deepseek.com\"\n item.model_id = \"deepseek-chat\"\n\n assert resource_service._provider_kind(item) == \"DeepSeek\"\n\n\ndef test_openai_structured_generation_uses_json_schema() -> None:\n item = provider_config(provider_kind=\"OpenAI\")\n schema = {\"type\": \"object\", \"properties\": {\"decision\": {\"type\": \"string\"}}}\n\n _url, _headers, body = resource_service._provider_generation_request(item, \"sk-test\", \"model-a\", \"return json\", structured_output_schema=schema)\n\n assert body[\"response_format\"][\"type\"] == \"json_schema\"\n assert body[\"response_format\"][\"json_schema\"][\"schema\"] == schema\n\n\ndef test_uploaded_video_uses_provider_managed_transport_when_public_url_is_not_reachable(monkeypatch) -> None:\n reset_database()\n db = get_database()\n team = Team(company_name=\"Inline Media Team\", owner_user_id=\"owner-inline\")\n provider = AiProviderConfig(\n scope=\"team\",\n team_id=team.id,\n route_name=\"Ark Video\",\n provider_kind=\"方舟\",\n provider=\"方舟\",\n protocol_profile=\"ark_chat\",\n api_base=\"https://ark.cn-beijing.volces.com/api/v3\",\n encrypted_api_key=encrypt_secret(\"sk-test\"),\n api_key_configured=True,\n model_id=\"doubao-seed-2-0-lite\",\n default_model=\"doubao-seed-2-0-lite\",\n models=[\"doubao-seed-2-0-lite\"],\n capabilities=[\"text\", \"video\"],\n transport_modes=[\"external_url\", \"inline_data\"],\n capability_profile={\n \"text\": {\"enabled\": True, \"transport_modes\": [], \"request_part_type\": None, \"supports_streaming\": True, \"options\": {}},\n \"video\": {\n \"enabled\": True,\n \"transport_modes\": [\"external_url\", \"inline_data\"],\n \"request_part_type\": \"video_url\",\n \"supports_streaming\": True,\n \"options\": {},\n },\n },\n runtime_config={\"temperature\": 0, \"max_output_tokens\": 128, \"timeout_ms\": 15000},\n status=\"enabled\",\n )\n video_path = write_storage_file(f\"uploads/{team.id}/inline-video.mp4\", b\"fake-video-binary\")\n upload = UploadedFile(\n team_id=team.id,\n owner_id=\"owner-inline\",\n filename=\"inline-video.mp4\",\n content_type=\"video/mp4\",\n category=\"media\",\n size=len(b\"fake-video-binary\"),\n storage=\"filesystem\",\n path=video_path,\n url=\"\",\n )\n upload.url = f\"/api/v1/uploads/{upload.id}/download\"\n db.add(team)\n db.add(provider)\n db.add(upload)\n db.commit()\n\n calls: list[tuple[str, dict | None, dict | None]] = []\n\n class DummyResponse:\n def __init__(self, payload: dict) -> None:\n self.status_code = 200\n self.text = \"\"\n self.headers = {\"x-request-id\": \"req-inline\"}\n self._payload = payload\n\n def json(self) -> dict:\n return self._payload\n\n class DummyClient:\n def __init__(self, *args, **kwargs) -> None:\n pass\n\n def __enter__(self):\n return self\n\n def __exit__(self, exc_type, exc, tb) -> None:\n return None\n\n def post(self, url: str, headers: dict | None = None, json: dict | None = None, data: dict | None = None, files=None):\n calls.append((url, json, data))\n if url.endswith(\"/files\"):\n assert data is not None\n assert data[\"purpose\"] == \"user_data\"\n assert \"preprocess_configs[video][fps]\" in data\n assert files is not None\n return DummyResponse({\"id\": \"file-inline-1\", \"status\": \"processed\"})\n assert json is not None\n return DummyResponse(\n {\n \"output\": [{\"content\": [{\"type\": \"output_text\", \"text\": \"ok\"}]}],\n \"usage\": {\"input_tokens\": 12, \"output_tokens\": 3, \"total_tokens\": 15},\n }\n )\n\n monkeypatch.setattr(resource_service.httpx, \"Client\", DummyClient)\n\n result = resource_service.run_provider_messages_generation(\n db,\n team_id=team.id,\n provider_id=provider.id,\n model=provider.model_id,\n messages=[\n {\n \"role\": \"user\",\n \"content\": [\n {\"type\": \"text\", \"text\": \"analyze this video\"},\n {\"type\": \"video_url\", \"video_url\": {\"url\": upload.url}, \"label\": \"sample-video\"},\n ],\n }\n ],\n operation_type=\"labeling_ai_assist_preview\",\n operator_id=\"owner-inline\",\n request=build_request(),\n charge_ai_resource=False,\n )\n\n assert result[\"content\"] == \"ok\"\n assert any(url.endswith(\"/files\") for url, _json, _data in calls)\n responses_payload = next(payload for url, payload, _data in calls if url.endswith(\"/responses\"))\n input_video = next(part for part in responses_payload[\"input\"][0][\"content\"] if part[\"type\"] == \"input_video\")\n assert input_video[\"file_id\"] == \"file-inline-1\"\n\n\ndef test_uploaded_audio_prefers_public_url_for_audio_url_provider(monkeypatch) -> None:\n reset_database()\n db = get_database()\n team = Team(company_name=\"Public Audio Team\", owner_user_id=\"owner-audio\")\n provider = AiProviderConfig(\n scope=\"team\",\n team_id=team.id,\n route_name=\"Audio URL Provider\",\n provider_kind=\"OpenAI Compatible\",\n provider=\"OpenAI Compatible\",\n api_base=\"https://provider.example.com/v1\",\n encrypted_api_key=encrypt_secret(\"sk-test\"),\n api_key_configured=True,\n model_id=\"audio-model\",\n default_model=\"audio-model\",\n models=[\"audio-model\"],\n capabilities=[\"text\", \"audio\"],\n capability_profile={\n \"text\": {\"enabled\": True, \"transport_modes\": [], \"request_part_type\": None, \"supports_streaming\": True, \"options\": {}},\n \"audio\": {\n \"enabled\": True,\n \"transport_modes\": [\"external_url\", \"inline_data\"],\n \"request_part_type\": \"audio_url\",\n \"supports_streaming\": True,\n \"options\": {},\n },\n },\n status=\"enabled\",\n )\n audio_path = write_storage_file(f\"uploads/{team.id}/public-audio.wav\", b\"audio-bytes\")\n upload = UploadedFile(team_id=team.id, owner_id=\"owner-audio\", filename=\"public-audio.wav\", content_type=\"audio/wav\", category=\"media\", size=11, path=audio_path)\n upload.url = f\"/api/v1/uploads/{upload.id}/download\"\n db.add(team)\n db.add(provider)\n db.add(upload)\n db.commit()\n monkeypatch.setattr(resource_service.settings, \"public_api_base_url\", \"https://www.markuplabel.cn\")\n\n captured_payloads: list[dict] = []\n\n class DummyResponse:\n status_code = 200\n text = \"\"\n headers = {\"x-request-id\": \"req-audio-url\"}\n\n def json(self) -> dict:\n return {\"choices\": [{\"message\": {\"content\": \"ok\"}}], \"usage\": {\"total_tokens\": 1}}\n\n class DummyClient:\n def __init__(self, *args, **kwargs) -> None:\n pass\n\n def __enter__(self):\n return self\n\n def __exit__(self, exc_type, exc, tb) -> None:\n return None\n\n def post(self, url: str, headers: dict | None = None, json: dict | None = None, data: dict | None = None, files=None):\n assert json is not None\n captured_payloads.append(json)\n return DummyResponse()\n\n monkeypatch.setattr(resource_service.httpx, \"Client\", DummyClient)\n\n result = resource_service.run_provider_messages_generation(\n db,\n team_id=team.id,\n provider_id=provider.id,\n model=provider.model_id,\n messages=[{\"role\": \"user\", \"content\": [{\"type\": \"text\", \"text\": \"listen\"}, {\"type\": \"audio_url\", \"audio_url\": {\"url\": upload.url}, \"label\": \"clip\"}]}],\n operation_type=\"labeling_ai_assist_preview\",\n operator_id=\"owner-audio\",\n request=build_request(),\n charge_ai_resource=False,\n )\n\n assert result[\"content\"] == \"ok\"\n audio_part = next(part for part in captured_payloads[0][\"messages\"][0][\"content\"] if part[\"type\"] == \"audio_url\")\n audio_url = audio_part[\"audio_url\"][\"url\"]\n assert audio_url.startswith(f\"https://www.markuplabel.cn/api/v1/uploads/{upload.id}/playback?token=\")\n assert not audio_url.startswith(\"data:audio/\")\n log = db.find(AiCallLog, {\"operation_type\": \"labeling_ai_assist_preview\"})[0]\n assert log.meta[\"debug\"][\"media_parts\"][0][\"transport_mode\"] == \"external_url\"\n\n\ndef test_uploaded_video_prefers_public_url_for_video_url_provider(monkeypatch) -> None:\n reset_database()\n db = get_database()\n team = Team(company_name=\"Public Video Team\", owner_user_id=\"owner-video\")\n provider = AiProviderConfig(\n scope=\"team\",\n team_id=team.id,\n route_name=\"Video URL Provider\",\n provider_kind=\"OpenAI Compatible\",\n provider=\"OpenAI Compatible\",\n api_base=\"https://provider.example.com/v1\",\n encrypted_api_key=encrypt_secret(\"sk-test\"),\n api_key_configured=True,\n model_id=\"video-model\",\n default_model=\"video-model\",\n models=[\"video-model\"],\n capabilities=[\"text\", \"video\"],\n capability_profile={\n \"text\": {\"enabled\": True, \"transport_modes\": [], \"request_part_type\": None, \"supports_streaming\": True, \"options\": {}},\n \"video\": {\n \"enabled\": True,\n \"transport_modes\": [\"external_url\", \"inline_data\"],\n \"request_part_type\": \"video_url\",\n \"supports_streaming\": True,\n \"options\": {\"fps\": 1},\n },\n },\n status=\"enabled\",\n )\n video_path = write_storage_file(f\"uploads/{team.id}/public-video.mp4\", b\"video-bytes\")\n upload = UploadedFile(team_id=team.id, owner_id=\"owner-video\", filename=\"public-video.mp4\", content_type=\"video/mp4\", category=\"media\", size=11, path=video_path)\n upload.url = f\"/api/v1/uploads/{upload.id}/download\"\n db.add(team)\n db.add(provider)\n db.add(upload)\n db.commit()\n monkeypatch.setattr(resource_service.settings, \"public_api_base_url\", \"https://www.markuplabel.cn\")\n\n captured_payloads: list[dict] = []\n\n class DummyResponse:\n status_code = 200\n text = \"\"\n headers = {\"x-request-id\": \"req-video-url\"}\n\n def json(self) -> dict:\n return {\"choices\": [{\"message\": {\"content\": \"ok\"}}], \"usage\": {\"total_tokens\": 1}}\n\n class DummyClient:\n def __init__(self, *args, **kwargs) -> None:\n pass\n\n def __enter__(self):\n return self\n\n def __exit__(self, exc_type, exc, tb) -> None:\n return None\n\n def post(self, url: str, headers: dict | None = None, json: dict | None = None, data: dict | None = None, files=None):\n assert json is not None\n captured_payloads.append(json)\n return DummyResponse()\n\n monkeypatch.setattr(resource_service.httpx, \"Client\", DummyClient)\n\n result = resource_service.run_provider_messages_generation(\n db,\n team_id=team.id,\n provider_id=provider.id,\n model=provider.model_id,\n messages=[{\"role\": \"user\", \"content\": [{\"type\": \"text\", \"text\": \"watch\"}, {\"type\": \"video_url\", \"video_url\": {\"url\": upload.url}, \"label\": \"clip\"}]}],\n operation_type=\"labeling_ai_assist_preview\",\n operator_id=\"owner-video\",\n request=build_request(),\n charge_ai_resource=False,\n )\n\n assert result[\"content\"] == \"ok\"\n video_part = next(part for part in captured_payloads[0][\"messages\"][0][\"content\"] if part[\"type\"] == \"video_url\")\n video_url = video_part[\"video_url\"][\"url\"]\n assert video_url.startswith(f\"https://www.markuplabel.cn/api/v1/uploads/{upload.id}/playback?token=\")\n assert not video_url.startswith(\"data:video/\")\n log = db.find(AiCallLog, {\"operation_type\": \"labeling_ai_assist_preview\"})[0]\n assert log.meta[\"debug\"][\"media_parts\"][0][\"transport_mode\"] == \"external_url\"\n\n\ndef test_platform_messages_generation_uses_public_url_for_uploaded_image(monkeypatch) -> None:\n reset_database()\n db = get_database()\n team = Team(company_name=\"Platform Public Image Team\", owner_user_id=\"owner-image\")\n provider = AiProviderConfig(\n scope=\"team\",\n team_id=team.id,\n route_name=\"Platform Vision Provider\",\n provider_kind=\"OpenAI Compatible\",\n provider=\"OpenAI Compatible\",\n api_base=\"https://provider.example.com/v1\",\n encrypted_api_key=encrypt_secret(\"sk-test\"),\n api_key_configured=True,\n model_id=\"vision-model\",\n default_model=\"vision-model\",\n models=[\"vision-model\"],\n capabilities=[\"text\", \"image\"],\n capability_profile={\n \"text\": {\"enabled\": True, \"transport_modes\": [], \"request_part_type\": None, \"supports_streaming\": True, \"options\": {}},\n \"image\": {\n \"enabled\": True,\n \"transport_modes\": [\"external_url\", \"inline_data\"],\n \"request_part_type\": \"image_url\",\n \"supports_streaming\": True,\n \"options\": {\"detail\": \"auto\"},\n },\n },\n status=\"enabled\",\n )\n image_path = write_storage_file(f\"uploads/{team.id}/public-image.jpg\", b\"image-bytes\")\n upload = UploadedFile(team_id=team.id, owner_id=\"owner-image\", filename=\"public-image.jpg\", content_type=\"image/jpeg\", category=\"media\", size=11, path=image_path)\n upload.url = f\"/api/v1/uploads/{upload.id}/download\"\n db.add(team)\n db.add(provider)\n db.add(upload)\n db.commit()\n monkeypatch.setattr(resource_service.settings, \"public_api_base_url\", \"https://www.markuplabel.cn\")\n\n captured_payloads: list[dict] = []\n\n class DummyResponse:\n status_code = 200\n text = \"\"\n headers = {\"x-request-id\": \"req-platform-image\"}\n\n def json(self) -> dict:\n return {\"choices\": [{\"message\": {\"content\": \"ok\"}}], \"usage\": {\"total_tokens\": 1}}\n\n class DummyClient:\n def __init__(self, *args, **kwargs) -> None:\n pass\n\n def __enter__(self):\n return self\n\n def __exit__(self, exc_type, exc, tb) -> None:\n return None\n\n def post(self, url: str, headers: dict | None = None, json: dict | None = None, data: dict | None = None, files=None):\n assert json is not None\n captured_payloads.append(json)\n return DummyResponse()\n\n monkeypatch.setattr(resource_service.httpx, \"Client\", DummyClient)\n\n result = production_service.run_platform_provider_messages_generation(\n db,\n team_id=team.id,\n provider_id=provider.id,\n model=provider.model_id,\n messages=[{\"role\": \"user\", \"content\": [{\"type\": \"text\", \"text\": \"look\"}, {\"type\": \"image_url\", \"image_url\": {\"url\": upload.url}, \"label\": \"photo\"}]}],\n operation_type=\"ai_review_execute\",\n operator_id=\"owner-image\",\n request=build_request(),\n charge_ai_resource=False,\n )\n\n assert result[\"content\"] == \"ok\"\n image_part = next(part for part in captured_payloads[0][\"messages\"][0][\"content\"] if part[\"type\"] == \"image_url\")\n image_url = image_part[\"image_url\"][\"url\"]\n assert image_url.startswith(f\"https://www.markuplabel.cn/api/v1/uploads/{upload.id}/playback?token=\")\n assert not image_url.startswith(\"data:image/\")\n log = db.find(AiCallLog, {\"operation_type\": \"ai_review_execute\"})[0]\n assert log.meta[\"debug\"][\"media_parts\"][0][\"transport_mode\"] == \"external_url\"\n\n\ndef test_ark_video_file_api_upload_uses_responses_file_id(monkeypatch) -> None:\n reset_database()\n db = get_database()\n team = Team(company_name=\"File API Team\", owner_user_id=\"owner-file\")\n provider = AiProviderConfig(\n scope=\"team\",\n team_id=team.id,\n route_name=\"Ark File Video\",\n provider_kind=\"方舟\",\n provider=\"方舟\",\n protocol_profile=\"ark_chat\",\n api_base=\"https://ark.cn-beijing.volces.com/api/v3\",\n encrypted_api_key=encrypt_secret(\"sk-test\"),\n api_key_configured=True,\n model_id=\"doubao-seed-2-0-lite\",\n default_model=\"doubao-seed-2-0-lite\",\n models=[\"doubao-seed-2-0-lite\"],\n capabilities=[\"text\", \"video\"],\n transport_modes=[\"file_api\"],\n capability_profile={\n \"text\": {\"enabled\": True, \"transport_modes\": [], \"request_part_type\": None, \"supports_streaming\": True, \"options\": {}},\n \"video\": {\n \"enabled\": True,\n \"transport_modes\": [\"file_api\"],\n \"request_part_type\": \"input_video\",\n \"supports_streaming\": False,\n \"options\": {},\n },\n },\n runtime_config={\"temperature\": 0, \"max_output_tokens\": 128, \"timeout_ms\": 15000},\n status=\"enabled\",\n )\n video_path = write_storage_file(f\"uploads/{team.id}/file-api-video.mp4\", b\"file-api-video\")\n upload = UploadedFile(\n team_id=team.id,\n owner_id=\"owner-file\",\n filename=\"file-api-video.mp4\",\n content_type=\"video/mp4\",\n category=\"media\",\n size=len(b\"file-api-video\"),\n storage=\"filesystem\",\n path=video_path,\n url=\"\",\n )\n upload.url = f\"/api/v1/uploads/{upload.id}/download\"\n db.add(team)\n db.add(provider)\n db.add(upload)\n db.commit()\n\n calls: list[tuple[str, dict | None, dict | None]] = []\n\n class DummyResponse:\n def __init__(self, status_code: int, payload: dict) -> None:\n self.status_code = status_code\n self._payload = payload\n self.text = \"\"\n self.headers = {\"x-request-id\": \"req-file-api\"}\n\n def json(self) -> dict:\n return self._payload\n\n class DummyClient:\n def __init__(self, *args, **kwargs) -> None:\n pass\n\n def __enter__(self):\n return self\n\n def __exit__(self, exc_type, exc, tb) -> None:\n return None\n\n def post(self, url: str, headers: dict | None = None, json: dict | None = None, data: dict | None = None, files=None):\n calls.append((url, json, data))\n if url.endswith(\"/files\"):\n assert data is not None\n assert data[\"purpose\"] == \"user_data\"\n assert \"preprocess_configs[video][fps]\" in data\n assert files is not None\n return DummyResponse(200, {\"id\": \"file-provider-1\", \"status\": \"processed\"})\n return DummyResponse(\n 200,\n {\n \"output\": [\n {\n \"content\": [\n {\"type\": \"output_text\", \"text\": \"ok\"},\n ]\n }\n ],\n \"usage\": {\"input_tokens\": 9, \"output_tokens\": 2, \"total_tokens\": 11},\n },\n )\n\n monkeypatch.setattr(resource_service.httpx, \"Client\", DummyClient)\n\n result = resource_service.run_provider_messages_generation(\n db,\n team_id=team.id,\n provider_id=provider.id,\n model=provider.model_id,\n messages=[\n {\n \"role\": \"user\",\n \"content\": [\n {\"type\": \"text\", \"text\": \"inspect video\"},\n {\"type\": \"video_url\", \"video_url\": {\"url\": upload.url}, \"label\": \"sample-video\"},\n ],\n }\n ],\n operation_type=\"labeling_ai_assist_preview\",\n operator_id=\"owner-file\",\n request=build_request(),\n charge_ai_resource=False,\n )\n\n assert result[\"content\"] == \"ok\"\n assert calls[0][0].endswith(\"/files\")\n assert calls[1][0].endswith(\"/responses\")\n responses_payload = calls[1][1]\n assert responses_payload is not None\n input_video = next(part for part in responses_payload[\"input\"][0][\"content\"] if part[\"type\"] == \"input_video\")\n assert input_video[\"file_id\"] == \"file-provider-1\"\n logs = db.find(AiCallLog, {\"operation_type\": \"labeling_ai_assist_preview\"})\n assert len(logs) == 1\n assert logs[0].meta[\"debug\"][\"request_endpoint\"] == \"/responses\"\n assert logs[0].meta[\"debug\"][\"media_parts\"][0][\"provider_file_id\"] == \"file-provider-1\"\n\n\ndef test_ark_uploaded_video_auto_upgrades_legacy_external_url_config_to_file_api(monkeypatch) -> None:\n reset_database()\n db = get_database()\n team = Team(company_name=\"Legacy Ark Config Team\", owner_user_id=\"owner-legacy\")\n provider = AiProviderConfig(\n scope=\"team\",\n team_id=team.id,\n route_name=\"Ark Legacy Video\",\n provider_kind=\"方舟\",\n provider=\"方舟\",\n protocol_profile=\"ark_chat\",\n api_base=\"https://ark.cn-beijing.volces.com/api/v3\",\n encrypted_api_key=encrypt_secret(\"sk-test\"),\n api_key_configured=True,\n model_id=\"doubao-seed-2-0-lite\",\n default_model=\"doubao-seed-2-0-lite\",\n models=[\"doubao-seed-2-0-lite\"],\n capabilities=[\"text\", \"video\"],\n transport_modes=[\"external_url\"],\n capability_profile={\n \"text\": {\"enabled\": True, \"transport_modes\": [], \"request_part_type\": None, \"supports_streaming\": True, \"options\": {}},\n \"video\": {\n \"enabled\": True,\n \"transport_modes\": [\"external_url\"],\n \"request_part_type\": \"video_url\",\n \"supports_streaming\": False,\n \"options\": {},\n },\n },\n runtime_config={\"temperature\": 0, \"max_output_tokens\": 128, \"timeout_ms\": 15000},\n status=\"enabled\",\n )\n video_path = write_storage_file(f\"uploads/{team.id}/legacy-video.mp4\", b\"legacy-video\")\n upload = UploadedFile(\n team_id=team.id,\n owner_id=\"owner-legacy\",\n filename=\"legacy-video.mp4\",\n content_type=\"video/mp4\",\n category=\"media\",\n size=len(b\"legacy-video\"),\n storage=\"filesystem\",\n path=video_path,\n url=\"\",\n )\n upload.url = f\"/api/v1/uploads/{upload.id}/download\"\n db.add(team)\n db.add(provider)\n db.add(upload)\n db.commit()\n\n calls: list[str] = []\n\n class DummyResponse:\n def __init__(self, status_code: int, payload: dict) -> None:\n self.status_code = status_code\n self._payload = payload\n self.text = \"\"\n self.headers = {\"x-request-id\": \"req-legacy-auto\"}\n\n def json(self) -> dict:\n return self._payload\n\n class DummyClient:\n def __init__(self, *args, **kwargs) -> None:\n pass\n\n def __enter__(self):\n return self\n\n def __exit__(self, exc_type, exc, tb) -> None:\n return None\n\n def post(self, url: str, headers: dict | None = None, json: dict | None = None, data: dict | None = None, files=None):\n calls.append(url)\n if url.endswith(\"/files\"):\n return DummyResponse(200, {\"id\": \"file-auto-legacy\", \"status\": \"processed\"})\n return DummyResponse(\n 200,\n {\n \"output\": [{\"content\": [{\"type\": \"output_text\", \"text\": \"ok\"}]}],\n \"usage\": {\"input_tokens\": 7, \"output_tokens\": 2, \"total_tokens\": 9},\n },\n )\n\n monkeypatch.setattr(resource_service.httpx, \"Client\", DummyClient)\n\n result = resource_service.run_provider_messages_generation(\n db,\n team_id=team.id,\n provider_id=provider.id,\n model=provider.model_id,\n messages=[\n {\n \"role\": \"user\",\n \"content\": [\n {\"type\": \"text\", \"text\": \"inspect uploaded video\"},\n {\"type\": \"video_url\", \"video_url\": {\"url\": upload.url}, \"label\": \"legacy-video\"},\n ],\n }\n ],\n operation_type=\"labeling_ai_assist_preview\",\n operator_id=\"owner-legacy\",\n request=build_request(),\n charge_ai_resource=False,\n )\n\n assert result[\"content\"] == \"ok\"\n assert any(url.endswith(\"/files\") for url in calls)\n assert any(url.endswith(\"/responses\") for url in calls)\n\n\ndef test_platform_messages_deepseek_structured_generation_uses_json_object_and_prompt_schema() -> None:\n item = provider_config(provider_kind=\"DeepSeek\")\n schema = {\"type\": \"object\", \"properties\": {\"answers\": {\"type\": \"object\"}}}\n\n _url, _headers, body = production_service.platform_provider_messages_request(\n item,\n \"sk-test\",\n \"model-a\",\n [{\"role\": \"user\", \"content\": [{\"type\": \"text\", \"text\": \"return json\"}]}],\n structured_output_schema=schema,\n )\n\n assert body[\"response_format\"] == {\"type\": \"json_object\"}\n text_parts = [part[\"text\"] for part in body[\"messages\"][0][\"content\"] if part[\"type\"] == \"text\"]\n assert any(\"Structured output JSON schema\" in text for text in text_parts)\n\n\ndef test_platform_messages_openai_structured_generation_uses_json_schema() -> None:\n item = provider_config(provider_kind=\"OpenAI\")\n schema = {\"type\": \"object\", \"properties\": {\"answers\": {\"type\": \"object\"}}}\n\n _url, _headers, body = production_service.platform_provider_messages_request(\n item,\n \"sk-test\",\n \"model-a\",\n [{\"role\": \"user\", \"content\": \"return json\"}],\n structured_output_schema=schema,\n )\n\n assert body[\"response_format\"][\"type\"] == \"json_schema\"\n assert body[\"response_format\"][\"json_schema\"][\"schema\"] == schema\n\n\ndef test_platform_messages_generation_retries_with_text_only_prompt_schema_on_provider_400(monkeypatch) -> None:\n reset_database()\n db = get_database()\n team = Team(company_name=\"Fallback Provider Team\", owner_user_id=\"owner-1\")\n provider = AiProviderConfig(\n scope=\"team\",\n team_id=team.id,\n route_name=\"Compatible Provider\",\n provider_kind=\"OpenAI Compatible\",\n provider=\"OpenAI Compatible\",\n api_base=\"https://provider.example.com/v1\",\n encrypted_api_key=encrypt_secret(\"sk-test\"),\n api_key_configured=True,\n model_id=\"model-a\",\n default_model=\"model-a\",\n models=[\"model-a\"],\n runtime_config={\"temperature\": 0, \"max_output_tokens\": 128, \"timeout_ms\": 15000},\n status=\"enabled\",\n )\n db.add(team)\n db.add(provider)\n db.commit()\n posted_bodies: list[dict] = []\n\n class DummyResponse:\n def __init__(self, status_code: int, payload: dict, text: str = \"\") -> None:\n self.status_code = status_code\n self._payload = payload\n self.text = text\n self.headers = {\"x-request-id\": f\"retry-{len(posted_bodies)}\"}\n\n def json(self) -> dict:\n return self._payload\n\n class DummyClient:\n def __init__(self, *args, **kwargs) -> None:\n pass\n\n def __enter__(self):\n return self\n\n def __exit__(self, exc_type, exc, tb) -> None:\n return None\n\n def post(self, url: str, headers: dict | None = None, json: dict | None = None):\n assert json is not None\n posted_bodies.append(json)\n if len(posted_bodies) == 1:\n return DummyResponse(400, {}, \"unsupported response_format or content parts\")\n return DummyResponse(\n 200,\n {\n \"choices\": [{\"message\": {\"content\": '{\"answers\":{},\"explanation\":\"ok\",\"field_explanations\":{},\"image_annotations\":[]}'}}],\n \"usage\": {\"prompt_tokens\": 10, \"completion_tokens\": 5, \"total_tokens\": 15},\n },\n )\n\n monkeypatch.setattr(resource_service.httpx, \"Client\", DummyClient)\n\n result = production_service.run_platform_provider_messages_generation(\n db,\n team_id=team.id,\n messages=[{\"role\": \"user\", \"content\": [{\"type\": \"text\", \"text\": \"return json\"}]}],\n operation_type=\"labeling_ai_assist_preview\",\n operator_id=\"owner-1\",\n request=build_request(),\n provider_id=provider.id,\n structured_output_schema={\"type\": \"object\", \"properties\": {\"answers\": {\"type\": \"object\"}}},\n charge_ai_resource=False,\n )\n\n assert result[\"content\"] == '{\"answers\":{},\"explanation\":\"ok\",\"field_explanations\":{},\"image_annotations\":[]}'\n assert len(posted_bodies) == 2\n assert posted_bodies[0][\"response_format\"] == {\"type\": \"json_object\"}\n assert \"response_format\" not in posted_bodies[1]\n fallback_content = posted_bodies[1][\"messages\"][0][\"content\"]\n assert isinstance(fallback_content, str)\n assert \"Structured output JSON schema\" in fallback_content\n logs = db.find(AiCallLog, {\"operation_type\": \"labeling_ai_assist_preview\"})\n assert len(logs) == 1\n assert logs[0].status == \"success\"\n\n\ndef test_create_team_initializes_ai_wallet() -> None:\n reset_database()\n db = get_database()\n owner = User(username=\"teamowner\", email=\"teamowner@example.com\", global_role=\"admin\", email_verified=True)\n db.add(owner)\n\n detail = create_team_for_admin(\n db,\n owner=owner,\n company_name=\"AI Wallet Team\",\n industry=\"ai\",\n contact_phone=None,\n description=None,\n logo_url=None,\n website=None,\n address=None,\n billing_info=None,\n mailing_info=None,\n request=build_request(),\n )\n\n wallet = db.find_one(TeamAiWallet, {\"team_id\": detail[\"team_id\"]})\n assert wallet is not None\n assert wallet.balance_points == 0.0\n\n\ndef test_platform_default_provider_is_unique_and_visible_readonly_to_team() -> None:\n reset_database()\n db = get_database()\n team = Team(company_name=\"Provider Team\", owner_user_id=\"owner-1\")\n db.add(team)\n\n first = create_provider_config(\n db,\n provider_create_payload(scope=\"platform\", team_id=None, route_name=\"平台共享 A\", is_platform_default=True),\n \"platform-admin\",\n build_request(),\n )\n second = create_provider_config(\n db,\n provider_create_payload(scope=\"platform\", team_id=None, route_name=\"平台共享 B\", is_platform_default=True),\n \"platform-admin\",\n build_request(),\n )\n create_provider_config(\n db,\n provider_create_payload(scope=\"team\", team_id=team.id, route_name=\"企业自有路由\"),\n \"team-admin\",\n build_request(),\n )\n\n platform_items = db.find(AiProviderConfig, {\"scope\": \"platform\"})\n assert len([item for item in platform_items if item.is_platform_default]) == 1\n assert any(item.id == second[\"provider_id\"] and item.is_platform_default for item in platform_items)\n assert all(item.id == second[\"provider_id\"] or not item.is_platform_default for item in platform_items)\n\n team_items = list_provider_configs(db, team.id, manage_platform=False)\n platform_view = next(item for item in team_items if item[\"provider_id\"] == second[\"provider_id\"])\n assert platform_view[\"scope\"] == \"platform\"\n assert platform_view[\"is_platform_default\"] is True\n assert platform_view[\"team_can_manage\"] is False\n assert first[\"provider_id\"] != second[\"provider_id\"]\n\n\ndef test_ai_wallet_recharge_and_platform_spend() -> None:\n reset_database()\n db = get_database()\n team = Team(company_name=\"Wallet Ledger Team\", owner_user_id=\"owner-1\")\n db.add(team)\n\n recharged = recharge_team_ai_wallet(\n db,\n team_id=team.id,\n amount=200,\n payment_method=\"wechat\",\n operator_id=\"team-admin\",\n request=build_request(),\n )\n assert recharged[\"balance_points\"] == 200\n\n require_team_ai_wallet_positive_balance(db, team.id)\n\n spent = record_platform_ai_wallet_spend(\n db,\n team_id=team.id,\n provider_id=\"provider-platform-1\",\n route_name=\"平台共享默认路由\",\n amount_points=211.5,\n source_type=\"ai_review\",\n source_id=\"job-1\",\n request_id=\"req-wallet-1\",\n operator_id=\"team-admin\",\n )\n assert spent[\"balance_points\"] == -11.5\n\n wallet = team_ai_wallet_payload(db, team.id)\n assert wallet[\"balance_points\"] == -11.5\n\n ledger_items = db.find(TeamAiWalletLedger, {\"team_id\": team.id})\n assert len(ledger_items) == 2\n assert any(item.transaction_type == \"recharge\" for item in ledger_items)\n assert any(item.transaction_type == \"ai_spend\" and item.balance_after == -11.5 for item in ledger_items)\n\n\ndef test_transfer_team_points_to_ai_wallet_and_history() -> None:\n reset_database()\n db = get_database()\n team = Team(company_name=\"Transfer Wallet Team\", owner_user_id=\"owner-1\")\n db.add(team)\n\n recharge_points_budget(\n db,\n team_id=team.id,\n amount=500,\n payment_method=\"wechat\",\n operator_id=\"team-admin\",\n request=build_request(),\n )\n set_points_budget_payment_password(\n db,\n team_id=team.id,\n new_password=\"123456\",\n confirm_password=\"123456\",\n operator_id=\"team-admin\",\n request=build_request(),\n )\n\n transferred = transfer_team_points_to_ai_wallet(\n db,\n team_id=team.id,\n amount=120,\n payment_password=\"123456\",\n operator_id=\"team-admin\",\n request=build_request(),\n )\n assert transferred[\"balance_points\"] == 120\n assert team_points_budget_payload(db, team.id)[\"available_points\"] == 380\n\n db.add(\n AiCallLog(\n team_id=team.id,\n user_id=\"team-admin\",\n provider_id=\"provider-platform-1\",\n route_name=\"平台共享默认路由\",\n operation_type=\"ai_review\",\n provider=\"OpenAI Compatible\",\n model=\"gpt-4.1-mini\",\n tokens=64,\n cost=11.5,\n latency_ms=320,\n status=\"success\",\n request_id=\"req-history-1\",\n )\n )\n record_platform_ai_wallet_spend(\n db,\n team_id=team.id,\n provider_id=\"provider-platform-1\",\n route_name=\"平台共享默认路由\",\n amount_points=11.5,\n source_type=\"ai_review\",\n source_id=\"job-history-1\",\n request_id=\"req-history-1\",\n operator_id=\"team-admin\",\n )\n\n history_items = list_team_ai_history(db, team.id)\n transfer_row = next(item for item in history_items if item[\"record_type\"] == \"transfer_in\")\n call_row = next(item for item in history_items if item[\"record_type\"] == \"ai_call\")\n\n assert transfer_row[\"points_delta\"] == 120\n assert transfer_row[\"source_label\"] == \"企业积分钱包\"\n assert call_row[\"request_id\"] == \"req-history-1\"\n assert call_row[\"points_delta\"] == -11.5\n assert call_row[\"balance_after\"] == 108.5\n\n\ndef test_ai_history_matches_spend_without_request_id_by_source_and_keeps_unmatched_spend() -> None:\n reset_database()\n db = get_database()\n team = Team(company_name=\"No Request AI History Team\", owner_user_id=\"owner-1\")\n db.add(team)\n\n recharge_team_ai_wallet(\n db,\n team_id=team.id,\n amount=20,\n payment_method=\"wechat\",\n operator_id=\"team-admin\",\n request=build_request(),\n )\n db.add(\n AiCallLog(\n team_id=team.id,\n user_id=\"team-admin\",\n provider_id=\"provider-platform-1\",\n route_name=\"平台共享默认路由\",\n operation_type=\"labeling_ai_assist\",\n provider=\"OpenAI Compatible\",\n model=\"gpt-4.1-mini\",\n tokens=4096,\n cost=1.25,\n billable=True,\n charged_points=1.25,\n source_type=\"labeling_ai_assist\",\n source_id=\"submission-no-request\",\n latency_ms=320,\n status=\"success\",\n request_id=None,\n )\n )\n record_platform_ai_wallet_spend(\n db,\n team_id=team.id,\n provider_id=\"provider-platform-1\",\n route_name=\"平台共享默认路由\",\n amount_points=1.25,\n source_type=\"labeling_ai_assist\",\n source_id=\"submission-no-request\",\n request_id=None,\n operator_id=\"team-admin\",\n )\n record_platform_ai_wallet_spend(\n db,\n team_id=team.id,\n provider_id=\"provider-platform-1\",\n route_name=\"平台共享默认路由\",\n amount_points=2.5,\n source_type=\"ai_review_execute\",\n source_id=\"job-without-call-log\",\n request_id=None,\n operator_id=\"team-admin\",\n )\n\n history_items = list_team_ai_history(db, team.id)\n matched_call = next(item for item in history_items if item[\"history_id\"].startswith(\"call:\"))\n unmatched_spend = next(item for item in history_items if item[\"request_id\"] == \"job-without-call-log\")\n\n assert matched_call[\"tokens\"] == 4096\n assert matched_call[\"points_delta\"] == -1.25\n assert matched_call[\"balance_after\"] == 18.75\n assert unmatched_spend[\"record_type\"] == \"ai_call\"\n assert unmatched_spend[\"tokens\"] is None\n assert unmatched_spend[\"points_delta\"] == -2.5\n assert unmatched_spend[\"balance_after\"] == 16.25\n"}, "fc20147620cdfcf5bb57e3ffd21a3d84d7543a40": {"t": 1785773719.8483636, "n": 43118, "h": "6cfa31b8cf263b61", "c": "import os\n\nos.environ.setdefault(\"SECRET_KEY\", \"test-secret-key-with-strong-length\")\nos.environ.setdefault(\"MONGODB_URL\", \"mongomock://localhost\")\nos.environ.setdefault(\"MONGODB_DATABASE\", \"markup_test\")\n\nfrom fastapi import Request\n\nfrom app.core.database import get_database, reset_database\nfrom app.core.security import encrypt_secret\nfrom app.models.resource import AiCallLog, AiProviderConfig, TeamAiWallet, TeamAiWalletLedger\nfrom app.models.team import Team\nfrom app.models.upload import UploadedFile\nfrom app.models.user import User\nfrom app.services.resource_service import (\n create_provider_config,\n list_team_ai_history,\n list_provider_configs,\n recharge_points_budget,\n recharge_team_ai_wallet,\n record_platform_ai_wallet_spend,\n require_team_ai_wallet_positive_balance,\n set_points_budget_payment_password,\n team_ai_wallet_payload,\n team_points_budget_payload,\n transfer_team_points_to_ai_wallet,\n)\nfrom app.services.file_storage import write_storage_file\nfrom app.services import production_service, resource_service\nfrom app.services.team_service import create_team_for_admin\n\n\ndef setup_module() -> None:\n reset_database()\n\n\ndef build_request() -> Request:\n scope = {\n \"type\": \"http\",\n \"method\": \"POST\",\n \"path\": \"/test\",\n \"headers\": [],\n \"client\": (\"127.0.0.1\", 12345),\n }\n return Request(scope)\n\n\ndef provider_create_payload(*, scope: str, team_id: str | None, route_name: str, is_platform_default: bool = False) -> dict:\n return {\n \"route_name\": route_name,\n \"provider_kind\": \"OpenAI Compatible\",\n \"scope\": scope,\n \"is_platform_default\": is_platform_default,\n \"team_id\": team_id,\n \"api_base\": \"https://provider.example.com/v1\",\n \"api_key\": \"sk-test-123\",\n \"model_id\": \"gpt-4.1-mini\",\n \"pricing\": {\n \"input_price_per_million\": 4.2,\n \"output_price_per_million\": 11.8,\n \"cache_hit_price_per_million\": 1.1,\n },\n \"capabilities\": [\"text\"],\n \"runtime_config\": {\"temperature\": 0, \"max_output_tokens\": 2048, \"timeout_ms\": 15000},\n \"status\": \"enabled\",\n \"remark\": \"test\",\n }\n\n\ndef provider_config(*, provider_kind: str = \"OpenAI Compatible\", timeout_ms: int = 15000) -> AiProviderConfig:\n return AiProviderConfig(\n scope=\"platform\",\n team_id=None,\n route_name=\"Provider Test\",\n provider_kind=provider_kind,\n provider=provider_kind,\n api_base=\"https://provider.example.com/v1\",\n encrypted_api_key=\"encrypted\",\n api_key_configured=True,\n model_id=\"model-a\",\n default_model=\"model-a\",\n models=[\"model-a\"],\n runtime_config={\"temperature\": 0, \"max_output_tokens\": 128, \"timeout_ms\": timeout_ms},\n status=\"enabled\",\n )\n\n\ndef test_generation_timeout_uses_provider_runtime_config() -> None:\n item = provider_config(timeout_ms=15000)\n\n assert resource_service._provider_generation_timeout_seconds(item) == 15.0\n assert resource_service._provider_connection_test_timeout_seconds(item) == resource_service.PROVIDER_CONNECTION_TEST_TIMEOUT_SECONDS\n\n\ndef test_provider_connection_test_uses_lightweight_token_limit_without_affecting_generation() -> None:\n item = provider_config(provider_kind=\"OpenAI Compatible\")\n item.runtime_config = {\"temperature\": 0, \"max_output_tokens\": 4096, \"timeout_ms\": 15000}\n\n _url, _headers, test_body = resource_service._provider_test_request(item, \"sk-test\", \"ping\")\n _gen_url, _gen_headers, generation_body = resource_service._provider_generation_request(item, \"sk-test\", \"model-a\", \"write a summary\")\n\n assert test_body[\"max_tokens\"] == resource_service.PROVIDER_CONNECTION_TEST_MAX_OUTPUT_TOKENS\n assert generation_body[\"max_tokens\"] == 4096\n\n\ndef test_provider_http_client_ignores_environment_proxy_by_default(monkeypatch) -> None:\n captured_kwargs: list[dict] = []\n\n class DummyResponse:\n status_code = 200\n headers: dict = {}\n\n @staticmethod\n def json() -> dict:\n return {\"usage\": {\"prompt_tokens\": 1, \"completion_tokens\": 1, \"total_tokens\": 2}}\n\n class DummyClient:\n def __init__(self, *args, **kwargs) -> None:\n captured_kwargs.append(kwargs)\n\n def __enter__(self):\n return self\n\n def __exit__(self, exc_type, exc, tb) -> None:\n return None\n\n def post(self, url: str, headers: dict | None = None, json: dict | None = None):\n assert json is not None\n assert json[\"max_tokens\"] == resource_service.PROVIDER_CONNECTION_TEST_MAX_OUTPUT_TOKENS\n return DummyResponse()\n\n monkeypatch.setattr(resource_service.httpx, \"Client\", DummyClient)\n item = provider_config(provider_kind=\"OpenAI Compatible\")\n\n result = resource_service._run_provider_test(item, api_key=\"sk-test\", message=\"ping\", request=build_request())\n\n assert result[\"status\"] == \"success\"\n assert captured_kwargs[0][\"timeout\"] == resource_service.PROVIDER_CONNECTION_TEST_TIMEOUT_SECONDS\n assert captured_kwargs[0][\"trust_env\"] is False\n\n\ndef test_provider_http_client_allows_explicit_environment_proxy_opt_in(monkeypatch) -> None:\n captured_kwargs: list[dict] = []\n\n class DummyClient:\n def __init__(self, *args, **kwargs) -> None:\n captured_kwargs.append(kwargs)\n\n monkeypatch.setattr(resource_service.httpx, \"Client\", DummyClient)\n item = provider_config(provider_kind=\"OpenAI Compatible\")\n item.runtime_config = {\"trust_env_proxy\": True}\n\n resource_service._provider_http_client(item, timeout=3.0)\n\n assert captured_kwargs[0][\"trust_env\"] is True\n\n\ndef test_openai_compatible_structured_generation_uses_json_object() -> None:\n item = provider_config(provider_kind=\"OpenAI Compatible\")\n schema = {\"type\": \"object\", \"properties\": {\"decision\": {\"type\": \"string\"}}}\n\n _url, _headers, body = resource_service._provider_generation_request(item, \"sk-test\", \"model-a\", \"return json\", structured_output_schema=schema)\n\n assert body[\"response_format\"] == {\"type\": \"json_object\"}\n\n\ndef test_deepseek_compatible_route_is_recognized_as_deepseek() -> None:\n item = provider_config(provider_kind=\"OpenAI Compatible\")\n item.route_name = \"deepseek\"\n item.api_base = \"https://api.deepseek.com\"\n item.model_id = \"deepseek-chat\"\n\n assert resource_service._provider_kind(item) == \"DeepSeek\"\n\n\ndef test_openai_structured_generation_uses_json_schema() -> None:\n item = provider_config(provider_kind=\"OpenAI\")\n schema = {\"type\": \"object\", \"properties\": {\"decision\": {\"type\": \"string\"}}}\n\n _url, _headers, body = resource_service._provider_generation_request(item, \"sk-test\", \"model-a\", \"return json\", structured_output_schema=schema)\n\n assert body[\"response_format\"][\"type\"] == \"json_schema\"\n assert body[\"response_format\"][\"json_schema\"][\"schema\"] == schema\n\n\ndef test_uploaded_video_uses_provider_managed_transport_when_public_url_is_not_reachable(monkeypatch) -> None:\n reset_database()\n db = get_database()\n team = Team(company_name=\"Inline Media Team\", owner_user_id=\"owner-inline\")\n provider = AiProviderConfig(\n scope=\"team\",\n team_id=team.id,\n route_name=\"Ark Video\",\n provider_kind=\"方舟\",\n provider=\"方舟\",\n protocol_profile=\"ark_chat\",\n api_base=\"https://ark.cn-beijing.volces.com/api/v3\",\n encrypted_api_key=encrypt_secret(\"sk-test\"),\n api_key_configured=True,\n model_id=\"doubao-seed-2-0-lite\",\n default_model=\"doubao-seed-2-0-lite\",\n models=[\"doubao-seed-2-0-lite\"],\n capabilities=[\"text\", \"video\"],\n transport_modes=[\"external_url\", \"inline_data\"],\n capability_profile={\n \"text\": {\"enabled\": True, \"transport_modes\": [], \"request_part_type\": None, \"supports_streaming\": True, \"options\": {}},\n \"video\": {\n \"enabled\": True,\n \"transport_modes\": [\"external_url\", \"inline_data\"],\n \"request_part_type\": \"video_url\",\n \"supports_streaming\": True,\n \"options\": {},\n },\n },\n runtime_config={\"temperature\": 0, \"max_output_tokens\": 128, \"timeout_ms\": 15000},\n status=\"enabled\",\n )\n video_path = write_storage_file(f\"uploads/{team.id}/inline-video.mp4\", b\"fake-video-binary\")\n upload = UploadedFile(\n team_id=team.id,\n owner_id=\"owner-inline\",\n filename=\"inline-video.mp4\",\n content_type=\"video/mp4\",\n category=\"media\",\n size=len(b\"fake-video-binary\"),\n storage=\"filesystem\",\n path=video_path,\n url=\"\",\n )\n upload.url = f\"/api/v1/uploads/{upload.id}/download\"\n db.add(team)\n db.add(provider)\n db.add(upload)\n db.commit()\n\n calls: list[tuple[str, dict | None, dict | None]] = []\n\n class DummyResponse:\n def __init__(self, payload: dict) -> None:\n self.status_code = 200\n self.text = \"\"\n self.headers = {\"x-request-id\": \"req-inline\"}\n self._payload = payload\n\n def json(self) -> dict:\n return self._payload\n\n class DummyClient:\n def __init__(self, *args, **kwargs) -> None:\n pass\n\n def __enter__(self):\n return self\n\n def __exit__(self, exc_type, exc, tb) -> None:\n return None\n\n def post(self, url: str, headers: dict | None = None, json: dict | None = None, data: dict | None = None, files=None):\n calls.append((url, json, data))\n if url.endswith(\"/files\"):\n assert data is not None\n assert data[\"purpose\"] == \"user_data\"\n assert \"preprocess_configs[video][fps]\" in data\n assert files is not None\n return DummyResponse({\"id\": \"file-inline-1\", \"status\": \"processed\"})\n assert json is not None\n return DummyResponse(\n {\n \"output\": [{\"content\": [{\"type\": \"output_text\", \"text\": \"ok\"}]}],\n \"usage\": {\"input_tokens\": 12, \"output_tokens\": 3, \"total_tokens\": 15},\n }\n )\n\n monkeypatch.setattr(resource_service.httpx, \"Client\", DummyClient)\n\n result = resource_service.run_provider_messages_generation(\n db,\n team_id=team.id,\n provider_id=provider.id,\n model=provider.model_id,\n messages=[\n {\n \"role\": \"user\",\n \"content\": [\n {\"type\": \"text\", \"text\": \"analyze this video\"},\n {\"type\": \"video_url\", \"video_url\": {\"url\": upload.url}, \"label\": \"sample-video\"},\n ],\n }\n ],\n operation_type=\"labeling_ai_assist_preview\",\n operator_id=\"owner-inline\",\n request=build_request(),\n charge_ai_resource=False,\n )\n\n assert result[\"content\"] == \"ok\"\n assert any(url.endswith(\"/files\") for url, _json, _data in calls)\n responses_payload = next(payload for url, payload, _data in calls if url.endswith(\"/responses\"))\n input_video = next(part for part in responses_payload[\"input\"][0][\"content\"] if part[\"type\"] == \"input_video\")\n assert input_video[\"file_id\"] == \"file-inline-1\"\n\n\ndef test_uploaded_audio_prefers_public_url_for_audio_url_provider(monkeypatch) -> None:\n reset_database()\n db = get_database()\n team = Team(company_name=\"Public Audio Team\", owner_user_id=\"owner-audio\")\n provider = AiProviderConfig(\n scope=\"team\",\n team_id=team.id,\n route_name=\"Audio URL Provider\",\n provider_kind=\"OpenAI Compatible\",\n provider=\"OpenAI Compatible\",\n api_base=\"https://provider.example.com/v1\",\n encrypted_api_key=encrypt_secret(\"sk-test\"),\n api_key_configured=True,\n model_id=\"audio-model\",\n default_model=\"audio-model\",\n models=[\"audio-model\"],\n capabilities=[\"text\", \"audio\"],\n capability_profile={\n \"text\": {\"enabled\": True, \"transport_modes\": [], \"request_part_type\": None, \"supports_streaming\": True, \"options\": {}},\n \"audio\": {\n \"enabled\": True,\n \"transport_modes\": [\"external_url\", \"inline_data\"],\n \"request_part_type\": \"audio_url\",\n \"supports_streaming\": True,\n \"options\": {},\n },\n },\n status=\"enabled\",\n )\n audio_path = write_storage_file(f\"uploads/{team.id}/public-audio.wav\", b\"audio-bytes\")\n upload = UploadedFile(team_id=team.id, owner_id=\"owner-audio\", filename=\"public-audio.wav\", content_type=\"audio/wav\", category=\"media\", size=11, path=audio_path)\n upload.url = f\"/api/v1/uploads/{upload.id}/download\"\n db.add(team)\n db.add(provider)\n db.add(upload)\n db.commit()\n monkeypatch.setattr(resource_service.settings, \"public_api_base_url\", \"https://www.markuplabel.cn\")\n\n captured_payloads: list[dict] = []\n\n class DummyResponse:\n status_code = 200\n text = \"\"\n headers = {\"x-request-id\": \"req-audio-url\"}\n\n def json(self) -> dict:\n return {\"choices\": [{\"message\": {\"content\": \"ok\"}}], \"usage\": {\"total_tokens\": 1}}\n\n class DummyClient:\n def __init__(self, *args, **kwargs) -> None:\n pass\n\n def __enter__(self):\n return self\n\n def __exit__(self, exc_type, exc, tb) -> None:\n return None\n\n def post(self, url: str, headers: dict | None = None, json: dict | None = None, data: dict | None = None, files=None):\n assert json is not None\n captured_payloads.append(json)\n return DummyResponse()\n\n monkeypatch.setattr(resource_service.httpx, \"Client\", DummyClient)\n\n result = resource_service.run_provider_messages_generation(\n db,\n team_id=team.id,\n provider_id=provider.id,\n model=provider.model_id,\n messages=[{\"role\": \"user\", \"content\": [{\"type\": \"text\", \"text\": \"listen\"}, {\"type\": \"audio_url\", \"audio_url\": {\"url\": upload.url}, \"label\": \"clip\"}]}],\n operation_type=\"labeling_ai_assist_preview\",\n operator_id=\"owner-audio\",\n request=build_request(),\n charge_ai_resource=False,\n )\n\n assert result[\"content\"] == \"ok\"\n audio_part = next(part for part in captured_payloads[0][\"messages\"][0][\"content\"] if part[\"type\"] == \"audio_url\")\n audio_url = audio_part[\"audio_url\"][\"url\"]\n assert audio_url.startswith(f\"https://www.markuplabel.cn/api/v1/uploads/{upload.id}/playback?token=\")\n assert not audio_url.startswith(\"data:audio/\")\n log = db.find(AiCallLog, {\"operation_type\": \"labeling_ai_assist_preview\"})[0]\n assert log.meta[\"debug\"][\"media_parts\"][0][\"transport_mode\"] == \"external_url\"\n\n\ndef test_uploaded_video_prefers_public_url_for_video_url_provider(monkeypatch) -> None:\n reset_database()\n db = get_database()\n team = Team(company_name=\"Public Video Team\", owner_user_id=\"owner-video\")\n provider = AiProviderConfig(\n scope=\"team\",\n team_id=team.id,\n route_name=\"Video URL Provider\",\n provider_kind=\"OpenAI Compatible\",\n provider=\"OpenAI Compatible\",\n api_base=\"https://provider.example.com/v1\",\n encrypted_api_key=encrypt_secret(\"sk-test\"),\n api_key_configured=True,\n model_id=\"video-model\",\n default_model=\"video-model\",\n models=[\"video-model\"],\n capabilities=[\"text\", \"video\"],\n capability_profile={\n \"text\": {\"enabled\": True, \"transport_modes\": [], \"request_part_type\": None, \"supports_streaming\": True, \"options\": {}},\n \"video\": {\n \"enabled\": True,\n \"transport_modes\": [\"external_url\", \"inline_data\"],\n \"request_part_type\": \"video_url\",\n \"supports_streaming\": True,\n \"options\": {\"fps\": 1},\n },\n },\n status=\"enabled\",\n )\n video_path = write_storage_file(f\"uploads/{team.id}/public-video.mp4\", b\"video-bytes\")\n upload = UploadedFile(team_id=team.id, owner_id=\"owner-video\", filename=\"public-video.mp4\", content_type=\"video/mp4\", category=\"media\", size=11, path=video_path)\n upload.url = f\"/api/v1/uploads/{upload.id}/download\"\n db.add(team)\n db.add(provider)\n db.add(upload)\n db.commit()\n monkeypatch.setattr(resource_service.settings, \"public_api_base_url\", \"https://www.markuplabel.cn\")\n\n captured_payloads: list[dict] = []\n\n class DummyResponse:\n status_code = 200\n text = \"\"\n headers = {\"x-request-id\": \"req-video-url\"}\n\n def json(self) -> dict:\n return {\"choices\": [{\"message\": {\"content\": \"ok\"}}], \"usage\": {\"total_tokens\": 1}}\n\n class DummyClient:\n def __init__(self, *args, **kwargs) -> None:\n pass\n\n def __enter__(self):\n return self\n\n def __exit__(self, exc_type, exc, tb) -> None:\n return None\n\n def post(self, url: str, headers: dict | None = None, json: dict | None = None, data: dict | None = None, files=None):\n assert json is not None\n captured_payloads.append(json)\n return DummyResponse()\n\n monkeypatch.setattr(resource_service.httpx, \"Client\", DummyClient)\n\n result = resource_service.run_provider_messages_generation(\n db,\n team_id=team.id,\n provider_id=provider.id,\n model=provider.model_id,\n messages=[{\"role\": \"user\", \"content\": [{\"type\": \"text\", \"text\": \"watch\"}, {\"type\": \"video_url\", \"video_url\": {\"url\": upload.url}, \"label\": \"clip\"}]}],\n operation_type=\"labeling_ai_assist_preview\",\n operator_id=\"owner-video\",\n request=build_request(),\n charge_ai_resource=False,\n )\n\n assert result[\"content\"] == \"ok\"\n video_part = next(part for part in captured_payloads[0][\"messages\"][0][\"content\"] if part[\"type\"] == \"video_url\")\n video_url = video_part[\"video_url\"][\"url\"]\n assert video_url.startswith(f\"https://www.markuplabel.cn/api/v1/uploads/{upload.id}/playback?token=\")\n assert not video_url.startswith(\"data:video/\")\n log = db.find(AiCallLog, {\"operation_type\": \"labeling_ai_assist_preview\"})[0]\n assert log.meta[\"debug\"][\"media_parts\"][0][\"transport_mode\"] == \"external_url\"\n\n\ndef test_platform_messages_generation_uses_public_url_for_uploaded_image(monkeypatch) -> None:\n reset_database()\n db = get_database()\n team = Team(company_name=\"Platform Public Image Team\", owner_user_id=\"owner-image\")\n provider = AiProviderConfig(\n scope=\"team\",\n team_id=team.id,\n route_name=\"Platform Vision Provider\",\n provider_kind=\"OpenAI Compatible\",\n provider=\"OpenAI Compatible\",\n api_base=\"https://provider.example.com/v1\",\n encrypted_api_key=encrypt_secret(\"sk-test\"),\n api_key_configured=True,\n model_id=\"vision-model\",\n default_model=\"vision-model\",\n models=[\"vision-model\"],\n capabilities=[\"text\", \"image\"],\n capability_profile={\n \"text\": {\"enabled\": True, \"transport_modes\": [], \"request_part_type\": None, \"supports_streaming\": True, \"options\": {}},\n \"image\": {\n \"enabled\": True,\n \"transport_modes\": [\"external_url\", \"inline_data\"],\n \"request_part_type\": \"image_url\",\n \"supports_streaming\": True,\n \"options\": {\"detail\": \"auto\"},\n },\n },\n status=\"enabled\",\n )\n image_path = write_storage_file(f\"uploads/{team.id}/public-image.jpg\", b\"image-bytes\")\n upload = UploadedFile(team_id=team.id, owner_id=\"owner-image\", filename=\"public-image.jpg\", content_type=\"image/jpeg\", category=\"media\", size=11, path=image_path)\n upload.url = f\"/api/v1/uploads/{upload.id}/download\"\n db.add(team)\n db.add(provider)\n db.add(upload)\n db.commit()\n monkeypatch.setattr(resource_service.settings, \"public_api_base_url\", \"https://www.markuplabel.cn\")\n\n captured_payloads: list[dict] = []\n\n class DummyResponse:\n status_code = 200\n text = \"\"\n headers = {\"x-request-id\": \"req-platform-image\"}\n\n def json(self) -> dict:\n return {\"choices\": [{\"message\": {\"content\": \"ok\"}}], \"usage\": {\"total_tokens\": 1}}\n\n class DummyClient:\n def __init__(self, *args, **kwargs) -> None:\n pass\n\n def __enter__(self):\n return self\n\n def __exit__(self, exc_type, exc, tb) -> None:\n return None\n\n def post(self, url: str, headers: dict | None = None, json: dict | None = None, data: dict | None = None, files=None):\n assert json is not None\n captured_payloads.append(json)\n return DummyResponse()\n\n monkeypatch.setattr(resource_service.httpx, \"Client\", DummyClient)\n\n result = production_service.run_platform_provider_messages_generation(\n db,\n team_id=team.id,\n provider_id=provider.id,\n model=provider.model_id,\n messages=[{\"role\": \"user\", \"content\": [{\"type\": \"text\", \"text\": \"look\"}, {\"type\": \"image_url\", \"image_url\": {\"url\": upload.url}, \"label\": \"photo\"}]}],\n operation_type=\"ai_review_execute\",\n operator_id=\"owner-image\",\n request=build_request(),\n charge_ai_resource=False,\n )\n\n assert result[\"content\"] == \"ok\"\n image_part = next(part for part in captured_payloads[0][\"messages\"][0][\"content\"] if part[\"type\"] == \"image_url\")\n image_url = image_part[\"image_url\"][\"url\"]\n assert image_url.startswith(f\"https://www.markuplabel.cn/api/v1/uploads/{upload.id}/playback?token=\")\n assert not image_url.startswith(\"data:image/\")\n log = db.find(AiCallLog, {\"operation_type\": \"ai_review_execute\"})[0]\n assert log.meta[\"debug\"][\"media_parts\"][0][\"transport_mode\"] == \"external_url\"\n\n\ndef test_ark_video_file_api_upload_uses_responses_file_id(monkeypatch) -> None:\n reset_database()\n db = get_database()\n team = Team(company_name=\"File API Team\", owner_user_id=\"owner-file\")\n provider = AiProviderConfig(\n scope=\"team\",\n team_id=team.id,\n route_name=\"Ark File Video\",\n provider_kind=\"方舟\",\n provider=\"方舟\",\n protocol_profile=\"ark_chat\",\n api_base=\"https://ark.cn-beijing.volces.com/api/v3\",\n encrypted_api_key=encrypt_secret(\"sk-test\"),\n api_key_configured=True,\n model_id=\"doubao-seed-2-0-lite\",\n default_model=\"doubao-seed-2-0-lite\",\n models=[\"doubao-seed-2-0-lite\"],\n capabilities=[\"text\", \"video\"],\n transport_modes=[\"file_api\"],\n capability_profile={\n \"text\": {\"enabled\": True, \"transport_modes\": [], \"request_part_type\": None, \"supports_streaming\": True, \"options\": {}},\n \"video\": {\n \"enabled\": True,\n \"transport_modes\": [\"file_api\"],\n \"request_part_type\": \"input_video\",\n \"supports_streaming\": False,\n \"options\": {},\n },\n },\n runtime_config={\"temperature\": 0, \"max_output_tokens\": 128, \"timeout_ms\": 15000},\n status=\"enabled\",\n )\n video_path = write_storage_file(f\"uploads/{team.id}/file-api-video.mp4\", b\"file-api-video\")\n upload = UploadedFile(\n team_id=team.id,\n owner_id=\"owner-file\",\n filename=\"file-api-video.mp4\",\n content_type=\"video/mp4\",\n category=\"media\",\n size=len(b\"file-api-video\"),\n storage=\"filesystem\",\n path=video_path,\n url=\"\",\n )\n upload.url = f\"/api/v1/uploads/{upload.id}/download\"\n db.add(team)\n db.add(provider)\n db.add(upload)\n db.commit()\n\n calls: list[tuple[str, dict | None, dict | None]] = []\n\n class DummyResponse:\n def __init__(self, status_code: int, payload: dict) -> None:\n self.status_code = status_code\n self._payload = payload\n self.text = \"\"\n self.headers = {\"x-request-id\": \"req-file-api\"}\n\n def json(self) -> dict:\n return self._payload\n\n class DummyClient:\n def __init__(self, *args, **kwargs) -> None:\n pass\n\n def __enter__(self):\n return self\n\n def __exit__(self, exc_type, exc, tb) -> None:\n return None\n\n def post(self, url: str, headers: dict | None = None, json: dict | None = None, data: dict | None = None, files=None):\n calls.append((url, json, data))\n if url.endswith(\"/files\"):\n assert data is not None\n assert data[\"purpose\"] == \"user_data\"\n assert \"preprocess_configs[video][fps]\" in data\n assert files is not None\n return DummyResponse(200, {\"id\": \"file-provider-1\", \"status\": \"processed\"})\n return DummyResponse(\n 200,\n {\n \"output\": [\n {\n \"content\": [\n {\"type\": \"output_text\", \"text\": \"ok\"},\n ]\n }\n ],\n \"usage\": {\"input_tokens\": 9, \"output_tokens\": 2, \"total_tokens\": 11},\n },\n )\n\n monkeypatch.setattr(resource_service.httpx, \"Client\", DummyClient)\n\n result = resource_service.run_provider_messages_generation(\n db,\n team_id=team.id,\n provider_id=provider.id,\n model=provider.model_id,\n messages=[\n {\n \"role\": \"user\",\n \"content\": [\n {\"type\": \"text\", \"text\": \"inspect video\"},\n {\"type\": \"video_url\", \"video_url\": {\"url\": upload.url}, \"label\": \"sample-video\"},\n ],\n }\n ],\n operation_type=\"labeling_ai_assist_preview\",\n operator_id=\"owner-file\",\n request=build_request(),\n charge_ai_resource=False,\n )\n\n assert result[\"content\"] == \"ok\"\n assert calls[0][0].endswith(\"/files\")\n assert calls[1][0].endswith(\"/responses\")\n responses_payload = calls[1][1]\n assert responses_payload is not None\n input_video = next(part for part in responses_payload[\"input\"][0][\"content\"] if part[\"type\"] == \"input_video\")\n assert input_video[\"file_id\"] == \"file-provider-1\"\n logs = db.find(AiCallLog, {\"operation_type\": \"labeling_ai_assist_preview\"})\n assert len(logs) == 1\n assert logs[0].meta[\"debug\"][\"request_endpoint\"] == \"/responses\"\n assert logs[0].meta[\"debug\"][\"media_parts\"][0][\"provider_file_id\"] == \"file-provider-1\"\n\n\ndef test_ark_uploaded_video_auto_upgrades_legacy_external_url_config_to_file_api(monkeypatch) -> None:\n reset_database()\n db = get_database()\n team = Team(company_name=\"Legacy Ark Config Team\", owner_user_id=\"owner-legacy\")\n provider = AiProviderConfig(\n scope=\"team\",\n team_id=team.id,\n route_name=\"Ark Legacy Video\",\n provider_kind=\"方舟\",\n provider=\"方舟\",\n protocol_profile=\"ark_chat\",\n api_base=\"https://ark.cn-beijing.volces.com/api/v3\",\n encrypted_api_key=encrypt_secret(\"sk-test\"),\n api_key_configured=True,\n model_id=\"doubao-seed-2-0-lite\",\n default_model=\"doubao-seed-2-0-lite\",\n models=[\"doubao-seed-2-0-lite\"],\n capabilities=[\"text\", \"video\"],\n transport_modes=[\"external_url\"],\n capability_profile={\n \"text\": {\"enabled\": True, \"transport_modes\": [], \"request_part_type\": None, \"supports_streaming\": True, \"options\": {}},\n \"video\": {\n \"enabled\": True,\n \"transport_modes\": [\"external_url\"],\n \"request_part_type\": \"video_url\",\n \"supports_streaming\": False,\n \"options\": {},\n },\n },\n runtime_config={\"temperature\": 0, \"max_output_tokens\": 128, \"timeout_ms\": 15000},\n status=\"enabled\",\n )\n video_path = write_storage_file(f\"uploads/{team.id}/legacy-video.mp4\", b\"legacy-video\")\n upload = UploadedFile(\n team_id=team.id,\n owner_id=\"owner-legacy\",\n filename=\"legacy-video.mp4\",\n content_type=\"video/mp4\",\n category=\"media\",\n size=len(b\"legacy-video\"),\n storage=\"filesystem\",\n path=video_path,\n url=\"\",\n )\n upload.url = f\"/api/v1/uploads/{upload.id}/download\"\n db.add(team)\n db.add(provider)\n db.add(upload)\n db.commit()\n\n calls: list[str] = []\n\n class DummyResponse:\n def __init__(self, status_code: int, payload: dict) -> None:\n self.status_code = status_code\n self._payload = payload\n self.text = \"\"\n self.headers = {\"x-request-id\": \"req-legacy-auto\"}\n\n def json(self) -> dict:\n return self._payload\n\n class DummyClient:\n def __init__(self, *args, **kwargs) -> None:\n pass\n\n def __enter__(self):\n return self\n\n def __exit__(self, exc_type, exc, tb) -> None:\n return None\n\n def post(self, url: str, headers: dict | None = None, json: dict | None = None, data: dict | None = None, files=None):\n calls.append(url)\n if url.endswith(\"/files\"):\n return DummyResponse(200, {\"id\": \"file-auto-legacy\", \"status\": \"processed\"})\n return DummyResponse(\n 200,\n {\n \"output\": [{\"content\": [{\"type\": \"output_text\", \"text\": \"ok\"}]}],\n \"usage\": {\"input_tokens\": 7, \"output_tokens\": 2, \"total_tokens\": 9},\n },\n )\n\n monkeypatch.setattr(resource_service.httpx, \"Client\", DummyClient)\n\n result = resource_service.run_provider_messages_generation(\n db,\n team_id=team.id,\n provider_id=provider.id,\n model=provider.model_id,\n messages=[\n {\n \"role\": \"user\",\n \"content\": [\n {\"type\": \"text\", \"text\": \"inspect uploaded video\"},\n {\"type\": \"video_url\", \"video_url\": {\"url\": upload.url}, \"label\": \"legacy-video\"},\n ],\n }\n ],\n operation_type=\"labeling_ai_assist_preview\",\n operator_id=\"owner-legacy\",\n request=build_request(),\n charge_ai_resource=False,\n )\n\n assert result[\"content\"] == \"ok\"\n assert any(url.endswith(\"/files\") for url in calls)\n assert any(url.endswith(\"/responses\") for url in calls)\n\n\ndef test_platform_messages_deepseek_structured_generation_uses_json_object_and_prompt_schema() -> None:\n item = provider_config(provider_kind=\"DeepSeek\")\n schema = {\"type\": \"object\", \"properties\": {\"answers\": {\"type\": \"object\"}}}\n\n _url, _headers, body = production_service.platform_provider_messages_request(\n item,\n \"sk-test\",\n \"model-a\",\n [{\"role\": \"user\", \"content\": [{\"type\": \"text\", \"text\": \"return json\"}]}],\n structured_output_schema=schema,\n )\n\n assert body[\"response_format\"] == {\"type\": \"json_object\"}\n text_parts = [part[\"text\"] for part in body[\"messages\"][0][\"content\"] if part[\"type\"] == \"text\"]\n assert any(\"Structured output JSON schema\" in text for text in text_parts)\n\n\ndef test_platform_messages_openai_structured_generation_uses_json_schema() -> None:\n item = provider_config(provider_kind=\"OpenAI\")\n schema = {\"type\": \"object\", \"properties\": {\"answers\": {\"type\": \"object\"}}}\n\n _url, _headers, body = production_service.platform_provider_messages_request(\n item,\n \"sk-test\",\n \"model-a\",\n [{\"role\": \"user\", \"content\": \"return json\"}],\n structured_output_schema=schema,\n )\n\n assert body[\"response_format\"][\"type\"] == \"json_schema\"\n assert body[\"response_format\"][\"json_schema\"][\"schema\"] == schema\n\n\ndef test_platform_messages_generation_retries_with_text_only_prompt_schema_on_provider_400(monkeypatch) -> None:\n reset_database()\n db = get_database()\n team = Team(company_name=\"Fallback Provider Team\", owner_user_id=\"owner-1\")\n provider = AiProviderConfig(\n scope=\"team\",\n team_id=team.id,\n route_name=\"Compatible Provider\",\n provider_kind=\"OpenAI Compatible\",\n provider=\"OpenAI Compatible\",\n api_base=\"https://provider.example.com/v1\",\n encrypted_api_key=encrypt_secret(\"sk-test\"),\n api_key_configured=True,\n model_id=\"model-a\",\n default_model=\"model-a\",\n models=[\"model-a\"],\n runtime_config={\"temperature\": 0, \"max_output_tokens\": 128, \"timeout_ms\": 15000},\n status=\"enabled\",\n )\n db.add(team)\n db.add(provider)\n db.commit()\n posted_bodies: list[dict] = []\n\n class DummyResponse:\n def __init__(self, status_code: int, payload: dict, text: str = \"\") -> None:\n self.status_code = status_code\n self._payload = payload\n self.text = text\n self.headers = {\"x-request-id\": f\"retry-{len(posted_bodies)}\"}\n\n def json(self) -> dict:\n return self._payload\n\n class DummyClient:\n def __init__(self, *args, **kwargs) -> None:\n pass\n\n def __enter__(self):\n return self\n\n def __exit__(self, exc_type, exc, tb) -> None:\n return None\n\n def post(self, url: str, headers: dict | None = None, json: dict | None = None):\n assert json is not None\n posted_bodies.append(json)\n if len(posted_bodies) == 1:\n return DummyResponse(400, {}, \"unsupported response_format or content parts\")\n return DummyResponse(\n 200,\n {\n \"choices\": [{\"message\": {\"content\": '{\"answers\":{},\"explanation\":\"ok\",\"field_explanations\":{},\"image_annotations\":[]}'}}],\n \"usage\": {\"prompt_tokens\": 10, \"completion_tokens\": 5, \"total_tokens\": 15},\n },\n )\n\n monkeypatch.setattr(resource_service.httpx, \"Client\", DummyClient)\n\n result = production_service.run_platform_provider_messages_generation(\n db,\n team_id=team.id,\n messages=[{\"role\": \"user\", \"content\": [{\"type\": \"text\", \"text\": \"return json\"}]}],\n operation_type=\"labeling_ai_assist_preview\",\n operator_id=\"owner-1\",\n request=build_request(),\n provider_id=provider.id,\n structured_output_schema={\"type\": \"object\", \"properties\": {\"answers\": {\"type\": \"object\"}}},\n charge_ai_resource=False,\n )\n\n assert result[\"content\"] == '{\"answers\":{},\"explanation\":\"ok\",\"field_explanations\":{},\"image_annotations\":[]}'\n assert len(posted_bodies) == 2\n assert posted_bodies[0][\"response_format\"] == {\"type\": \"json_object\"}\n assert \"response_format\" not in posted_bodies[1]\n fallback_content = posted_bodies[1][\"messages\"][0][\"content\"]\n assert isinstance(fallback_content, str)\n assert \"Structured output JSON schema\" in fallback_content\n logs = db.find(AiCallLog, {\"operation_type\": \"labeling_ai_assist_preview\"})\n assert len(logs) == 1\n assert logs[0].status == \"success\"\n\n\ndef test_create_team_initializes_ai_wallet() -> None:\n reset_database()\n db = get_database()\n owner = User(username=\"teamowner\", email=\"teamowner@example.com\", global_role=\"admin\", email_verified=True)\n db.add(owner)\n\n detail = create_team_for_admin(\n db,\n owner=owner,\n company_name=\"AI Wallet Team\",\n industry=\"ai\",\n contact_phone=None,\n description=None,\n logo_url=None,\n website=None,\n address=None,\n billing_info=None,\n mailing_info=None,\n request=build_request(),\n )\n\n wallet = db.find_one(TeamAiWallet, {\"team_id\": detail[\"team_id\"]})\n assert wallet is not None\n assert wallet.balance_points == 0.0\n\n\ndef test_platform_default_provider_is_unique_and_visible_readonly_to_team() -> None:\n reset_database()\n db = get_database()\n team = Team(company_name=\"Provider Team\", owner_user_id=\"owner-1\")\n db.add(team)\n\n first = create_provider_config(\n db,\n provider_create_payload(scope=\"platform\", team_id=None, route_name=\"平台共享 A\", is_platform_default=True),\n \"platform-admin\",\n build_request(),\n )\n second = create_provider_config(\n db,\n provider_create_payload(scope=\"platform\", team_id=None, route_name=\"平台共享 B\", is_platform_default=True),\n \"platform-admin\",\n build_request(),\n )\n create_provider_config(\n db,\n provider_create_payload(scope=\"team\", team_id=team.id, route_name=\"企业自有路由\"),\n \"team-admin\",\n build_request(),\n )\n\n platform_items = db.find(AiProviderConfig, {\"scope\": \"platform\"})\n assert len([item for item in platform_items if item.is_platform_default]) == 1\n assert any(item.id == second[\"provider_id\"] and item.is_platform_default for item in platform_items)\n assert all(item.id == second[\"provider_id\"] or not item.is_platform_default for item in platform_items)\n\n team_items = list_provider_configs(db, team.id, manage_platform=False)\n platform_view = next(item for item in team_items if item[\"provider_id\"] == second[\"provider_id\"])\n assert platform_view[\"scope\"] == \"platform\"\n assert platform_view[\"is_platform_default\"] is True\n assert platform_view[\"team_can_manage\"] is False\n assert first[\"provider_id\"] != second[\"provider_id\"]\n\n\ndef test_ai_wallet_recharge_and_platform_spend() -> None:\n reset_database()\n db = get_database()\n team = Team(company_name=\"Wallet Ledger Team\", owner_user_id=\"owner-1\")\n db.add(team)\n\n recharged = recharge_team_ai_wallet(\n db,\n team_id=team.id,\n amount=200,\n payment_method=\"wechat\",\n operator_id=\"team-admin\",\n request=build_request(),\n )\n assert recharged[\"balance_points\"] == 200\n\n require_team_ai_wallet_positive_balance(db, team.id)\n\n spent = record_platform_ai_wallet_spend(\n db,\n team_id=team.id,\n provider_id=\"provider-platform-1\",\n route_name=\"平台共享默认路由\",\n amount_points=211.5,\n source_type=\"ai_review\",\n source_id=\"job-1\",\n request_id=\"req-wallet-1\",\n operator_id=\"team-admin\",\n )\n assert spent[\"balance_points\"] == -11.5\n\n wallet = team_ai_wallet_payload(db, team.id)\n assert wallet[\"balance_points\"] == -11.5\n\n ledger_items = db.find(TeamAiWalletLedger, {\"team_id\": team.id})\n assert len(ledger_items) == 2\n assert any(item.transaction_type == \"recharge\" for item in ledger_items)\n assert any(item.transaction_type == \"ai_spend\" and item.balance_after == -11.5 for item in ledger_items)\n\n\ndef test_transfer_team_points_to_ai_wallet_and_history() -> None:\n reset_database()\n db = get_database()\n team = Team(company_name=\"Transfer Wallet Team\", owner_user_id=\"owner-1\")\n db.add(team)\n\n recharge_points_budget(\n db,\n team_id=team.id,\n amount=500,\n payment_method=\"wechat\",\n operator_id=\"team-admin\",\n request=build_request(),\n )\n set_points_budget_payment_password(\n db,\n team_id=team.id,\n new_password=\"123456\",\n confirm_password=\"123456\",\n operator_id=\"team-admin\",\n request=build_request(),\n )\n\n transferred = transfer_team_points_to_ai_wallet(\n db,\n team_id=team.id,\n amount=120,\n payment_password=\"123456\",\n operator_id=\"team-admin\",\n request=build_request(),\n )\n assert transferred[\"balance_points\"] == 120\n assert team_points_budget_payload(db, team.id)[\"available_points\"] == 380\n\n db.add(\n AiCallLog(\n team_id=team.id,\n user_id=\"team-admin\",\n provider_id=\"provider-platform-1\",\n route_name=\"平台共享默认路由\",\n operation_type=\"ai_review\",\n provider=\"OpenAI Compatible\",\n model=\"gpt-4.1-mini\",\n tokens=64,\n cost=11.5,\n latency_ms=320,\n status=\"success\",\n request_id=\"req-history-1\",\n )\n )\n record_platform_ai_wallet_spend(\n db,\n team_id=team.id,\n provider_id=\"provider-platform-1\",\n route_name=\"平台共享默认路由\",\n amount_points=11.5,\n source_type=\"ai_review\",\n source_id=\"job-history-1\",\n request_id=\"req-history-1\",\n operator_id=\"team-admin\",\n )\n\n history_items = list_team_ai_history(db, team.id)\n transfer_row = next(item for item in history_items if item[\"record_type\"] == \"transfer_in\")\n call_row = next(item for item in history_items if item[\"record_type\"] == \"ai_call\")\n\n assert transfer_row[\"points_delta\"] == 120\n assert transfer_row[\"source_label\"] == \"企业积分钱包\"\n assert call_row[\"request_id\"] == \"req-history-1\"\n assert call_row[\"points_delta\"] == -11.5\n assert call_row[\"balance_after\"] == 108.5\n\n\ndef test_ai_history_matches_spend_without_request_id_by_source_and_keeps_unmatched_spend() -> None:\n reset_database()\n db = get_database()\n team = Team(company_name=\"No Request AI History Team\", owner_user_id=\"owner-1\")\n db.add(team)\n\n recharge_team_ai_wallet(\n db,\n team_id=team.id,\n amount=20,\n payment_method=\"wechat\",\n operator_id=\"team-admin\",\n request=build_request(),\n )\n db.add(\n AiCallLog(\n team_id=team.id,\n user_id=\"team-admin\",\n provider_id=\"provider-platform-1\",\n route_name=\"平台共享默认路由\",\n operation_type=\"labeling_ai_assist\",\n provider=\"OpenAI Compatible\",\n model=\"gpt-4.1-mini\",\n tokens=4096,\n cost=1.25,\n billable=True,\n charged_points=1.25,\n source_type=\"labeling_ai_assist\",\n source_id=\"submission-no-request\",\n latency_ms=320,\n status=\"success\",\n request_id=None,\n )\n )\n record_platform_ai_wallet_spend(\n db,\n team_id=team.id,\n provider_id=\"provider-platform-1\",\n route_name=\"平台共享默认路由\",\n amount_points=1.25,\n source_type=\"labeling_ai_assist\",\n source_id=\"submission-no-request\",\n request_id=None,\n operator_id=\"team-admin\",\n )\n record_platform_ai_wallet_spend(\n db,\n team_id=team.id,\n provider_id=\"provider-platform-1\",\n route_name=\"平台共享默认路由\",\n amount_points=2.5,\n source_type=\"ai_review_execute\",\n source_id=\"job-without-call-log\",\n request_id=None,\n operator_id=\"team-admin\",\n )\n\n history_items = list_team_ai_history(db, team.id)\n matched_call = next(item for item in history_items if item[\"history_id\"].startswith(\"call:\"))\n unmatched_spend = next(item for item in history_items if item[\"request_id\"] == \"job-without-call-log\")\n\n assert matched_call[\"tokens\"] == 4096\n assert matched_call[\"points_delta\"] == -1.25\n assert matched_call[\"balance_after\"] == 18.75\n assert unmatched_spend[\"record_type\"] == \"ai_call\"\n assert unmatched_spend[\"tokens\"] is None\n assert unmatched_spend[\"points_delta\"] == -2.5\n assert unmatched_spend[\"balance_after\"] == 16.25\n"}}}}