mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 09:20:22 +08:00
feat: 引入 Rust executor/gateway sidecar 及 Python 侧双后端适配
- 新增 Rust workspace crates: aether-contracts, aether-executor, aether-gateway - aether-executor: 支持 Unix Socket/TCP 双传输模式,处理同步/流式上游请求 - aether-gateway: 作为本地主入口代理,集成 /api/internal/gateway/resolve 认证预解析 - Python 侧新增 ExecutionPlan 契约和 RustExecutorClient,各 handler 支持 executor_backend=rust 时将可序列化请求转发给 Rust executor 执行 - 重构 dev.sh 支持 executor/gateway 进程编排与生命周期管理 - 新增 internal gateway 路由,提供 resolve/passthrough 端点 - handler 层(chat/cli/video/endpoint_checker 等)全面适配 Rust executor 回退逻辑 - pipeline 层支持 trusted auth context 跳过重复认证 - 新增 Rust CI workflow 及对应测试用例
This commit is contained in:
@@ -6,7 +6,8 @@ import pytest
|
||||
|
||||
from src.core.api_format.conversion.internal_video import InternalVideoPollResult, VideoStatus
|
||||
from src.models.database import VideoTask
|
||||
from src.services.task.video.poller_adapter import VideoTaskPollerAdapter
|
||||
from src.services.request.rust_executor_client import RustExecutorSyncResult
|
||||
from src.services.task.video.poller_adapter import VideoPollContext, VideoTaskPollerAdapter
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -78,3 +79,140 @@ async def test_update_task_after_poll_skips_terminal_cancelled_task(
|
||||
|
||||
finalize.assert_not_awaited()
|
||||
session.commit.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_video_poller_try_rust_payload_passes_proxy_snapshot(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
from src.services.request import rust_executor_client as rust_mod
|
||||
from src.services.task.video import poller_adapter as mod
|
||||
|
||||
adapter = VideoTaskPollerAdapter()
|
||||
monkeypatch.setattr(mod.config, "executor_backend", "rust")
|
||||
|
||||
proxy_snapshot = object()
|
||||
captured: dict[str, object] = {}
|
||||
|
||||
async def _fake_execute_sync_json(self: object, plan: object) -> RustExecutorSyncResult:
|
||||
captured["plan"] = plan
|
||||
return RustExecutorSyncResult(status_code=200, response_json={"id": "op_1", "done": False})
|
||||
|
||||
monkeypatch.setattr(rust_mod.RustExecutorClient, "execute_sync_json", _fake_execute_sync_json)
|
||||
|
||||
ctx = VideoPollContext(
|
||||
task_id="task-1",
|
||||
external_task_id="vid_1",
|
||||
provider_api_format="openai:video",
|
||||
base_url="https://api.openai.com",
|
||||
upstream_key="upstream",
|
||||
headers={"authorization": "Bearer test"},
|
||||
poll_count=0,
|
||||
retry_count=0,
|
||||
poll_interval_seconds=15,
|
||||
max_poll_count=10,
|
||||
current_status=VideoStatus.PROCESSING.value,
|
||||
proxy_snapshot=proxy_snapshot,
|
||||
)
|
||||
|
||||
payload = await adapter._try_rust_poll_payload(
|
||||
ctx=ctx,
|
||||
url="https://api.openai.com/v1/videos/vid_1",
|
||||
)
|
||||
|
||||
assert payload == {"id": "op_1", "done": False}
|
||||
assert getattr(captured["plan"], "method") == "GET"
|
||||
assert getattr(captured["plan"], "proxy") is proxy_snapshot
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_video_poller_openai_poll_prefers_rust_payload(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
from src.services.task.video import poller_adapter as mod
|
||||
|
||||
adapter = VideoTaskPollerAdapter()
|
||||
|
||||
rust_poll = AsyncMock(return_value={"id": "vid_1", "status": "processing"})
|
||||
monkeypatch.setattr(adapter, "_try_rust_poll_payload", rust_poll)
|
||||
|
||||
normalized = InternalVideoPollResult(status=VideoStatus.PROCESSING, progress_percent=42)
|
||||
normalizer = MagicMock(return_value=normalized)
|
||||
monkeypatch.setattr(adapter._openai_normalizer, "video_poll_to_internal", normalizer)
|
||||
|
||||
get_upstream_client = AsyncMock(side_effect=AssertionError("python upstream client should not be used"))
|
||||
monkeypatch.setattr(mod.HTTPClientPool, "get_upstream_client", get_upstream_client)
|
||||
|
||||
ctx = VideoPollContext(
|
||||
task_id="task-1",
|
||||
external_task_id="vid_1",
|
||||
provider_api_format="openai:video",
|
||||
base_url="https://api.openai.com",
|
||||
upstream_key="upstream",
|
||||
headers={"authorization": "Bearer test"},
|
||||
poll_count=0,
|
||||
retry_count=0,
|
||||
poll_interval_seconds=15,
|
||||
max_poll_count=10,
|
||||
current_status=VideoStatus.PROCESSING.value,
|
||||
)
|
||||
|
||||
result = await adapter._poll_openai_with_context(ctx)
|
||||
|
||||
assert result is normalized
|
||||
normalizer.assert_called_once_with({"id": "vid_1", "status": "processing"})
|
||||
get_upstream_client.assert_not_awaited()
|
||||
rust_poll.assert_awaited_once()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_video_poller_openai_poll_fallback_uses_transport_aware_client(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
from src.services.task.video import poller_adapter as mod
|
||||
|
||||
adapter = VideoTaskPollerAdapter()
|
||||
|
||||
monkeypatch.setattr(adapter, "_try_rust_poll_payload", AsyncMock(return_value=None))
|
||||
|
||||
response = MagicMock()
|
||||
response.status_code = 200
|
||||
response.json.return_value = {"id": "vid_2", "status": "processing"}
|
||||
|
||||
client = MagicMock()
|
||||
client.get = AsyncMock(return_value=response)
|
||||
|
||||
get_upstream_client = AsyncMock(return_value=client)
|
||||
monkeypatch.setattr(mod.HTTPClientPool, "get_upstream_client", get_upstream_client)
|
||||
|
||||
normalized = InternalVideoPollResult(status=VideoStatus.PROCESSING, progress_percent=7)
|
||||
normalizer = MagicMock(return_value=normalized)
|
||||
monkeypatch.setattr(adapter._openai_normalizer, "video_poll_to_internal", normalizer)
|
||||
|
||||
ctx = VideoPollContext(
|
||||
task_id="task-2",
|
||||
external_task_id="vid_2",
|
||||
provider_api_format="openai:video",
|
||||
base_url="https://api.openai.com",
|
||||
upstream_key="upstream",
|
||||
headers={"authorization": "Bearer test"},
|
||||
poll_count=0,
|
||||
retry_count=0,
|
||||
poll_interval_seconds=15,
|
||||
max_poll_count=10,
|
||||
current_status=VideoStatus.PROCESSING.value,
|
||||
proxy_config={"enabled": True, "url": "http://proxy.test:8080"},
|
||||
delegate_config={"node_id": "node-1", "tunnel": True},
|
||||
)
|
||||
|
||||
result = await adapter._poll_openai_with_context(ctx)
|
||||
|
||||
assert result is normalized
|
||||
get_upstream_client.assert_awaited_once_with(
|
||||
{"node_id": "node-1", "tunnel": True},
|
||||
proxy_config={"enabled": True, "url": "http://proxy.test:8080"},
|
||||
)
|
||||
client.get.assert_awaited_once_with(
|
||||
"https://api.openai.com/v1/videos/vid_2",
|
||||
headers={"authorization": "Bearer test"},
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user