diff --git a/gateway/platforms/yuanbao.py b/gateway/platforms/yuanbao.py index 69d5b75b5aa..23c0d4d45f6 100644 --- a/gateway/platforms/yuanbao.py +++ b/gateway/platforms/yuanbao.py @@ -2618,6 +2618,27 @@ class MediaResolveMiddleware(InboundMiddleware): cls._resource_cache.pop(k, None) cls._resource_cache[resource_id] = (local_path, mime, time.time()) + @classmethod + def _append_cached_resource( + cls, + adapter, + resource_id: str, + media_paths: List[str], + mimes: List[str], + ) -> bool: + """Append a cached resource to output lists when available.""" + hit = cls._get_cached_resource(resource_id) + if hit is None: + return False + local_path, mime = hit + logger.debug( + "[%s] resource cache hit: rid=%s path=%s", + adapter.name, resource_id, local_path, + ) + media_paths.append(local_path) + mimes.append(mime) + return True + @staticmethod def _guess_image_ext_from_url(url: str) -> str: """Guess image extension from URL path.""" @@ -2805,6 +2826,8 @@ class MediaResolveMiddleware(InboundMiddleware): # Extract resourceId from the placeholder URL for cache dedup. rid = ExtractContentMiddleware._parse_resource_id(url) + if rid and cls._append_cached_resource(adapter, rid, media_urls, media_types): + continue try: fetch_url = await cls._resolve_download_url(adapter, url) @@ -2846,6 +2869,8 @@ class MediaResolveMiddleware(InboundMiddleware): for rid, kind, filename in refs: if kind not in _RESOLVABLE_MEDIA_KINDS: continue + if cls._append_cached_resource(adapter, rid, media_paths, mimes): + continue try: fresh_url = await cls._fetch_resource_url(adapter, rid) except Exception as exc: diff --git a/tests/test_yuanbao_pipeline.py b/tests/test_yuanbao_pipeline.py index 53613abd1ec..d55dad7e007 100644 --- a/tests/test_yuanbao_pipeline.py +++ b/tests/test_yuanbao_pipeline.py @@ -1505,6 +1505,100 @@ class TestResolveYbresRefs: assert paths == [] assert mimes == [] + @pytest.mark.asyncio + async def test_cache_hit_skips_resource_url_resolve(self, tmp_path): + """A resourceId cache hit must not await ``_fetch_resource_url`` at all.""" + adapter = make_adapter() + cached_file = tmp_path / "rid-cached.jpg" + cached_file.write_bytes(b"cached-image") + MediaResolveMiddleware._resource_cache.clear() + try: + MediaResolveMiddleware._put_cached_resource( + "rid-cached", str(cached_file), "image/jpeg", + ) + + with patch.object( + MediaResolveMiddleware, "_fetch_resource_url", + new=AsyncMock(return_value="https://fresh/never"), + ) as p_fetch: + paths, mimes = await MediaResolveMiddleware._resolve_ybres_refs( + adapter, [("rid-cached", "image", "")], log_prefix="test", + ) + + assert paths == [str(cached_file)] + assert mimes == ["image/jpeg"] + p_fetch.assert_not_awaited() + finally: + MediaResolveMiddleware._resource_cache.clear() + + @pytest.mark.asyncio + async def test_cache_miss_still_resolves(self, tmp_path): + """Uncached refs still pay the resolve; cached ones are served in place.""" + adapter = make_adapter() + cached_file = tmp_path / "rid-cached.jpg" + cached_file.write_bytes(b"cached-image") + MediaResolveMiddleware._resource_cache.clear() + try: + MediaResolveMiddleware._put_cached_resource( + "rid-cached", str(cached_file), "image/jpeg", + ) + + with patch.object( + MediaResolveMiddleware, "_fetch_resource_url", + new=AsyncMock(return_value="https://fresh/new"), + ) as p_fetch, patch.object( + MediaResolveMiddleware, "_download_and_cache", + new=AsyncMock(return_value=("/cache/new.jpg", "image/jpeg")), + ): + paths, mimes = await MediaResolveMiddleware._resolve_ybres_refs( + adapter, + [("rid-cached", "image", ""), ("rid-new", "image", "")], + log_prefix="test", + ) + + assert paths == [str(cached_file), "/cache/new.jpg"] + assert mimes == ["image/jpeg", "image/jpeg"] + p_fetch.assert_awaited_once() + finally: + MediaResolveMiddleware._resource_cache.clear() + + +class TestResolveMediaUrlsCacheHit: + """Current-message media cache hits must skip the download-URL resolve.""" + + @pytest.mark.asyncio + async def test_cache_hit_skips_resolve_download_url(self, tmp_path): + adapter = make_adapter() + cached_file = tmp_path / "rid-cached.jpg" + cached_file.write_bytes(b"cached-image") + MediaResolveMiddleware._resource_cache.clear() + try: + MediaResolveMiddleware._put_cached_resource( + "rid-cached", str(cached_file), "image/jpeg", + ) + + with patch.object( + MediaResolveMiddleware, "_resolve_download_url", + new=AsyncMock(return_value="https://fresh/never"), + ) as p_resolve, patch.object( + MediaResolveMiddleware, "_fetch_resource_url", + new=AsyncMock(return_value="https://fresh/never"), + ) as p_fetch: + paths, mimes = await MediaResolveMiddleware._resolve_media_urls( + adapter, + [{ + "kind": "image", + "url": "https://hunyuan.tencent.com/api/resource/download?resourceId=rid-cached", + }], + ) + + assert paths == [str(cached_file)] + assert mimes == ["image/jpeg"] + p_resolve.assert_not_awaited() + p_fetch.assert_not_awaited() + finally: + MediaResolveMiddleware._resource_cache.clear() + class TestMediaResolveMiddlewareRouting: """Branch-routing tests for MediaResolveMiddleware.handle()."""