pipelines: route sub-steps through the front; tools use the public nginx port

* pipelines.py: /v1/pipelines/* (image-to-video, story, video-dub, audio-dub)
  chained their steps by calling the image/video/text/TTS handlers DIRECTLY
  in-process, forcing every modality onto whichever engine received the pipeline
  request. In a multi-engine deployment each model may live on a different engine.
  Route every sub-step through the front via execute_api_request (same fix as
  characters/environments) so each lands on the engine that owns that model;
  in-process fallback for single-process mode. Drops the unused TestClient import.

* supervisord.conf: the bundled tools (video_editor, videogen, township) now use
  --base-url http://127.0.0.1:8776 — the PUBLIC nginx port — so they talk to
  coderai exactly like any external client (nginx already proxies the full API
  with 4G bodies / 1h timeouts). The front's own bind stays CODERAI_PORT=18776.
Co-Authored-By: 's avatarClaude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RdMufYvtTbtGDWsiZVoXce
parent 84f6cfbb
...@@ -21,41 +21,35 @@ router = APIRouter() ...@@ -21,41 +21,35 @@ router = APIRouter()
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
async def _post_json(path: str, body: dict, http_request: Request): async def _post_json(path: str, body: dict, http_request: Request):
"""Call an internal endpoint by importing its handler directly.""" """Issue a pipeline sub-request to another coderai API endpoint, returning the
from codai.api import app as _app_module parsed JSON result.
from fastapi.testclient import TestClient # only for internal calls
# We avoid HTTP round-trips by calling handlers directly via their routers. A pipeline chains several modalities (image, video, text, TTS, transcription),
# Import lazily to avoid circular imports. and each model may live on a DIFFERENT engine. So we must NOT call the handlers
if path.startswith('/v1/images/generations'): in-process (that would force every step onto whichever engine received the
from codai.api.images import create_image_generation pipeline request). Route each sub-step through the front (the single API), which
from codai.pydantic.imagerequest import ImageGenerationRequest dispatches it to the engine that owns that model; falls back to in-process in
req = ImageGenerationRequest(**body) single-process mode. Mirrors codai.api.characters / environments."""
return await create_image_generation(req, http_request) import json as _json
from fastapi import HTTPException
if path.startswith('/v1/video/generations'): from codai.broker.asgi_bridge import execute_api_request
from codai.api.video import create_video_generation resp = await execute_api_request(
from codai.pydantic.videorequest import VideoGenerationRequest http_request, method="POST", path=path,
req = VideoGenerationRequest(**body) headers={"Content-Type": "application/json"},
return await create_video_generation(req, http_request) body=_json.dumps(body).encode())
status = resp.get("status_code", 500)
if path.startswith('/v1/video/dub'): raw = resp.get("body") or b""
from codai.api.video import create_video_dub if status >= 400:
from codai.pydantic.videorequest import VideoDubRequest try:
req = VideoDubRequest(**body) detail = _json.loads(raw).get("detail", raw.decode("utf-8", "replace"))
return await create_video_dub(req, http_request) except Exception:
detail = raw.decode("utf-8", "replace")
if path.startswith('/v1/audio/speech'): raise HTTPException(status_code=status, detail=f"{path} failed: {detail}")
from codai.api.tts import create_speech, TTSRequest try:
req = TTSRequest(**body) return _json.loads(raw) if raw else {}
return await create_speech(req) except Exception as e:
raise HTTPException(status_code=502,
if path.startswith('/v1/chat/completions'): detail=f"{path} returned a non-JSON response: {e}")
from codai.api.text import chat_completions
from codai.pydantic.textrequest import ChatCompletionRequest
req = ChatCompletionRequest(**body)
return await chat_completions(req, http_request)
raise ValueError(f"Unknown internal path: {path}")
def _img_url(result) -> str: def _img_url(result) -> str:
......
...@@ -52,7 +52,7 @@ redirect_stderr=true ...@@ -52,7 +52,7 @@ redirect_stderr=true
[program:video_editor] [program:video_editor]
command=/usr/local/bin/with-env /opt/coderai/python/bin/python3 /opt/coderai/app/tools/video_editor.py command=/usr/local/bin/with-env /opt/coderai/python/bin/python3 /opt/coderai/app/tools/video_editor.py
--no-browser --host 127.0.0.1 --port 8420 --no-browser --host 127.0.0.1 --port 8420
--base-url http://127.0.0.1:18776 --base-url http://127.0.0.1:8776
--config /cache/video_editor/video_editor.config.json --config /cache/video_editor/video_editor.config.json
directory=/opt/coderai/app directory=/opt/coderai/app
autostart=%(ENV_CODERAI_TOOL_VIDEO_EDITOR)s autostart=%(ENV_CODERAI_TOOL_VIDEO_EDITOR)s
...@@ -67,7 +67,7 @@ redirect_stderr=true ...@@ -67,7 +67,7 @@ redirect_stderr=true
[program:videogen] [program:videogen]
command=/usr/local/bin/with-env /opt/coderai/python/bin/python3 /opt/coderai/app/tools/videogen.py command=/usr/local/bin/with-env /opt/coderai/python/bin/python3 /opt/coderai/app/tools/videogen.py
--host 127.0.0.1 --web-port 7790 --host 127.0.0.1 --web-port 7790
--base-url http://127.0.0.1:18776 --base-url http://127.0.0.1:8776
--out-dir /cache/videogen_output --out-dir /cache/videogen_output
directory=/opt/coderai/app directory=/opt/coderai/app
autostart=%(ENV_CODERAI_TOOL_VIDEOGEN)s autostart=%(ENV_CODERAI_TOOL_VIDEOGEN)s
...@@ -82,7 +82,7 @@ redirect_stderr=true ...@@ -82,7 +82,7 @@ redirect_stderr=true
[program:township] [program:township]
command=/usr/local/bin/with-env /opt/coderai/python/bin/python3 /opt/coderai/app/tools/gen_township_fighters.py command=/usr/local/bin/with-env /opt/coderai/python/bin/python3 /opt/coderai/app/tools/gen_township_fighters.py
--web-port 7788 --web-port 7788
--base-url http://127.0.0.1:18776 --base-url http://127.0.0.1:8776
--out-dir /cache/township_output --out-dir /cache/township_output
--config /cache/township_output/township_config.json --config /cache/township_output/township_config.json
directory=/opt/coderai/app directory=/opt/coderai/app
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment