Repository: livepeer/ai-runner
Pull request #579: live: Add manifest ID to openapi and logs
Author: login: j0sh
Created: 2025-05-15T08:09:14Z | Merged: 2025-05-22T20:47:44Z

Helps filter logs by orchestrator once swapping is in.


Diff:
diff --git a/runner/app/live/api/api.py b/runner/app/live/api/api.py
index c35fd5f9a..c656a9403 100644
--- a/runner/app/live/api/api.py
+++ b/runner/app/live/api/api.py
@@ -57,6 +57,10 @@ class StartStreamParams(BaseModel):
         str,
         Field(default="", description="Unique identifier for the request."),
     ]
+    manifest_id: Annotated[
+        str,
+        Field(default="", description="Orchestrator identifier for the request."),
+    ]
     stream_id: Annotated[
         str,
         Field(default="", description="Unique identifier for the stream."),
@@ -116,7 +120,7 @@ async def handle_start_stream(request: web.Request):
         except Exception as e:
             logging.error(f"Error saving last params to file: {e}")
 
-        config_logging(request_id=params.request_id, stream_id=params.stream_id)
+        config_logging(request_id=params.request_id, manifest_id=params.manifest_id, stream_id=params.stream_id)
 
         protocol = TrickleProtocol(
             params.subscribe_url,
@@ -128,6 +132,7 @@ async def handle_start_stream(request: web.Request):
             protocol,
             process,
             params.request_id,
+            params.manifest_id,
             params.stream_id,
         )
 
diff --git a/runner/app/live/infer.py b/runner/app/live/infer.py
index 9fc588fbc..dcb30a60e 100644
--- a/runner/app/live/infer.py
+++ b/runner/app/live/infer.py
@@ -52,6 +52,7 @@ async def main(
     pipeline: str,
     params: dict,
     request_id: str,
+    manifest_id: str,
     stream_id: str,
 ):
     loop = asyncio.get_event_loop()
@@ -165,6 +166,9 @@ def signal_handler(sig, _):
         default="",
         help="The Livepeer request ID associated with this video stream",
     )
+    parser.add_argument(
+        "--manifest-id", type=str, default="", help="The orchestrator manifest ID"
+    )
     parser.add_argument(
         "--stream-id", type=str, default="", help="The Livepeer stream ID"
     )
@@ -181,6 +185,7 @@ def signal_handler(sig, _):
     config_logging(
         log_level=logging.DEBUG if os.getenv("VERBOSE_LOGGING")=="1" else logging.INFO,
         request_id=args.request_id,
+        manifest_id=args.manifest_id,
         stream_id=args.stream_id,
     )
 
@@ -196,6 +201,7 @@ def signal_handler(sig, _):
                 pipeline=args.pipeline,
                 params=params,
                 request_id=args.request_id,
+                manifest_id=args.manifest_id,
                 stream_id=args.stream_id,
             )
         )
diff --git a/runner/app/live/log.py b/runner/app/live/log.py
index beb507734..895e38568 100644
--- a/runner/app/live/log.py
+++ b/runner/app/live/log.py
@@ -7,19 +7,19 @@
 handler: logging.Handler | None = None
 
 
-def config_logging(*, log_level: int = 0, request_id: str = "", stream_id: str = ""):
+def config_logging(*, log_level: int = 0, request_id: str = "", manifest_id: str = "", stream_id: str = ""):
     global logger, handler
     if logger and handler:
         if log_level:
             logger.setLevel(log_level)
             handler.setLevel(log_level)
-        config_logging_fields(handler, request_id, stream_id)
+        config_logging_fields(handler, request_id, manifest_id, stream_id)
         return logger
 
     handler = logging.StreamHandler()
     if log_level:
         handler.setLevel(log_level)
-    config_logging_fields(handler, request_id, stream_id)
+    config_logging_fields(handler, request_id, manifest_id, stream_id)
 
     logger = logging.getLogger()  # Root logger
     if log_level:
@@ -28,10 +28,10 @@ def config_logging(*, log_level: int = 0, request_id: str = "", stream_id: str =
     return logger
 
 
-def config_logging_fields(handler: logging.Handler, request_id: str, stream_id: str):
+def config_logging_fields(handler: logging.Handler, request_id: str, manifest_id: str, stream_id: str):
     formatter = logging.Formatter(
-        "timestamp=%(asctime)s level=%(levelname)s location=%(filename)s:%(lineno)d:%(funcName)s gateway_request_id=%(request_id)s stream_id=%(stream_id)s message=%(message)s",
-        defaults={"request_id": request_id, "stream_id": stream_id},
+        "timestamp=%(asctime)s level=%(levelname)s location=%(filename)s:%(lineno)d:%(funcName)s gateway_request_id=%(request_id)s manifest_id=%(manifest_id)s stream_id=%(stream_id)s message=%(message)s",
+        defaults={"request_id": request_id, "manifest_id": manifest_id, "stream_id": stream_id},
         datefmt="%Y-%m-%d %H:%M:%S",
     )
 
diff --git a/runner/app/live/streamer/process.py b/runner/app/live/streamer/process.py
index 26f9c428a..0ffe8dfde 100644
--- a/runner/app/live/streamer/process.py
+++ b/runner/app/live/streamer/process.py
@@ -83,13 +83,13 @@ def is_pipeline_initialized(self):
     def update_params(self, params: dict):
         self.param_update_queue.put(params)
 
-    def reset_stream(self, request_id: str, stream_id: str):
+    def reset_stream(self, request_id: str, manifest_id: str, stream_id: str):
         clear_queue(self.input_queue)
         clear_queue(self.output_queue)
         clear_queue(self.param_update_queue)
         clear_queue(self.error_queue)
         clear_queue(self.log_queue)
-        self.param_update_queue.put({"request_id": request_id, "stream_id": stream_id})
+        self.param_update_queue.put({"request_id": request_id, "manifest_id": manifest_id, "stream_id": stream_id})
 
     # TODO: Once audio is implemented, combined send_input with input_loop
     # We don't need additional queueing as comfystream already maintains a queue
@@ -143,11 +143,11 @@ def process_loop(self):
 
 
     def _handle_logging_params(self, params: dict) -> dict:
-        if isinstance(params, dict) and "request_id" in params and "stream_id" in params:
-            logging.info(f"PipelineProcess: Resetting logging fields with request_id={params['request_id']}, stream_id={params['stream_id']}")
+        if isinstance(params, dict) and "request_id" in params and "manifest_id" in params and "stream_id" in params:
+            logging.info(f"PipelineProcess: Resetting logging fields with request_id={params['request_id']}, manifest_id={params['manifest_id']} stream_id={params['stream_id']}")
             self.request_id = params["request_id"]
             self._reset_logging_fields(
-                params["request_id"], params["stream_id"]
+                params["request_id"], params["manifest_id"], params["stream_id"]
             )
             return {}
         return params
@@ -267,7 +267,7 @@ def _setup_logging(self):
         )
         logger = config_logging(log_level=level)
         queue_handler = LogQueueHandler(self)
-        config_logging_fields(queue_handler, "", "")
+        config_logging_fields(queue_handler, "", "", "")
         logger.addHandler(queue_handler)
 
         self.queue_handler = queue_handler
@@ -276,9 +276,9 @@ def _setup_logging(self):
         sys.stdout = QueueTeeStream(sys.stdout, self)
         sys.stderr = QueueTeeStream(sys.stderr, self)
 
-    def _reset_logging_fields(self, request_id: str, stream_id: str):
-        config_logging(request_id=request_id, stream_id=stream_id)
-        config_logging_fields(self.queue_handler, request_id, stream_id)
+    def _reset_logging_fields(self, request_id: str, manifest_id: str, stream_id: str):
+        config_logging(request_id=request_id, manifest_id=manifest_id, stream_id=stream_id)
+        config_logging_fields(self.queue_handler, request_id, manifest_id, stream_id)
 
     def _queue_put_fifo(self, _queue: mp.Queue, item: Any):
         """Helper to put an item on a queue, dropping oldest items if needed"""
diff --git a/runner/app/live/streamer/process_guardian.py b/runner/app/live/streamer/process_guardian.py
index 59f45abad..eb3fe933c 100644
--- a/runner/app/live/streamer/process_guardian.py
+++ b/runner/app/live/streamer/process_guardian.py
@@ -75,6 +75,7 @@ async def stop(self):
     async def reset_stream(
         self,
         request_id: str,
+        manifest_id: str,
         stream_id: str,
         params: dict,
         streamer: StreamerCallbacks | None = None,
@@ -87,7 +88,7 @@ async def reset_stream(
         self.output_fps_counter.reset()
         self.streamer = streamer or _NoopStreamerCallbacks()
 
-        self.process.reset_stream(request_id, stream_id)
+        self.process.reset_stream(request_id, manifest_id, stream_id)
         await self.update_params(params)
         self.status.update_state(PipelineState.ONLINE)
 
diff --git a/runner/app/live/streamer/streamer.py b/runner/app/live/streamer/streamer.py
index afb70cfce..7490ee5ba 100644
--- a/runner/app/live/streamer/streamer.py
+++ b/runner/app/live/streamer/streamer.py
@@ -23,6 +23,7 @@ def __init__(
         protocol: StreamProtocol,
         process: ProcessGuardian,
         request_id: str,
+        manifest_id: str,
         stream_id: str,
     ):
         self.protocol = protocol
@@ -34,6 +35,7 @@ def __init__(
         self.main_tasks: list[asyncio.Task] = []
         self.tasks_supervisor_task: asyncio.Task | None = None
         self.request_id = request_id
+        self.manifest_id = manifest_id
         self.stream_id = stream_id
 
     async def start(self, params: dict):
@@ -41,7 +43,7 @@ async def start(self, params: dict):
             raise RuntimeError("Streamer already started")
 
         await self.process.reset_stream(
-            self.request_id, self.stream_id, params, self
+            self.request_id, self.manifest_id, self.stream_id, params, self
         )
 
         self.stop_event.clear()
diff --git a/runner/app/pipelines/live_video_to_video.py b/runner/app/pipelines/live_video_to_video.py
index 50482d3dd..09896b4ff 100644
--- a/runner/app/pipelines/live_video_to_video.py
+++ b/runner/app/pipelines/live_video_to_video.py
@@ -30,7 +30,7 @@ def __init__(self, model_id: str):
         self.start_process()
 
     def __call__(  # type: ignore
-        self, *, subscribe_url: str, publish_url: str, control_url: str, events_url: str, params: dict, request_id: str, stream_id: str, **kwargs
+        self, *, subscribe_url: str, publish_url: str, control_url: str, events_url: str, params: dict, request_id: str, manifest_id: str, stream_id: str, **kwargs
     ):
         if not self.process:
             raise RuntimeError("Pipeline process not running")
@@ -53,6 +53,7 @@ def __call__(  # type: ignore
                             "events_url": events_url,
                             "params": params,
                             "request_id": request_id or "",
+                            "manifest_id": manifest_id or "",
                             "stream_id": stream_id or "",
                         }
                     ),
diff --git a/runner/app/routes/live_video_to_video.py b/runner/app/routes/live_video_to_video.py
index 5b8470068..5614ba898 100644
--- a/runner/app/routes/live_video_to_video.py
+++ b/runner/app/routes/live_video_to_video.py
@@ -79,6 +79,13 @@ class LiveVideoToVideoParams(BaseModel):
             description="The ID of the Gateway request (for logging purposes)."
         ),
     ]
+    manifest_id: Annotated[
+        str,
+        Field(
+            default="",
+            description="The manifest ID from the orchestrator (for logging purposes)."
+        ),
+    ]
     stream_id: Annotated[
         str,
         Field(
diff --git a/runner/app/routes/utils.py b/runner/app/routes/utils.py
index 157f69556..5ed185f8b 100644
--- a/runner/app/routes/utils.py
+++ b/runner/app/routes/utils.py
@@ -135,6 +135,10 @@ class LiveVideoToVideoResponse(BaseModel):
         default='',
         description="The ID generated for this request",
     )
+    manifest_id: str = Field(
+        default='',
+        description="Orchestrator manifest ID for this request",
+    )
 
 
 class APIError(BaseModel):
diff --git a/runner/gateway.openapi.yaml b/runner/gateway.openapi.yaml
index 2a84a1d03..c2243160a 100644
--- a/runner/gateway.openapi.yaml
+++ b/runner/gateway.openapi.yaml
@@ -3,7 +3,7 @@ openapi: 3.1.0
 info:
   title: Livepeer AI Runner
   description: An application to run AI pipelines
-  version: 0.1.0
+  version: 0.13.5
 servers:
 - url: https://dream-gateway.livepeer.cloud
   description: Livepeer Cloud Community Gateway
@@ -1019,6 +1019,11 @@ components:
           title: Gateway Request Id
           description: The ID of the Gateway request (for logging purposes).
           default: ''
+        manifest_id:
+          type: string
+          title: Manifest Id
+          description: The manifest ID from the orchestrator (for logging purposes).
+          default: ''
         stream_id:
           type: string
           title: Stream Id
@@ -1055,6 +1060,11 @@ components:
           title: Request Id
           description: The ID generated for this request
           default: ''
+        manifest_id:
+          type: string
+          title: Manifest Id
+          description: Orchestrator manifest ID for this request
+          default: ''
       type: object
       required:
       - subscribe_url
diff --git a/runner/openapi.yaml b/runner/openapi.yaml
index fc24c15fc..e3aba929d 100644
--- a/runner/openapi.yaml
+++ b/runner/openapi.yaml
@@ -3,7 +3,7 @@ openapi: 3.1.0
 info:
   title: Livepeer AI Runner
   description: An application to run AI pipelines
-  version: 0.1.0
+  version: 0.13.5
 servers:
 - url: https://dream-gateway.livepeer.cloud
   description: Livepeer Cloud Community Gateway
@@ -992,6 +992,7 @@ components:
         status:
           type: string
           enum:
+          - LOADING
           - OK
           - ERROR
           - IDLE
@@ -1176,6 +1177,11 @@ components:
           title: Gateway Request Id
           description: The ID of the Gateway request (for logging purposes).
           default: ''
+        manifest_id:
+          type: string
+          title: Manifest Id
+          description: The manifest ID from the orchestrator (for logging purposes).
+          default: ''
         stream_id:
           type: string
           title: Stream Id
@@ -1211,6 +1217,11 @@ components:
           title: Request Id
           description: The ID generated for this request
           default: ''
+        manifest_id:
+          type: string
+          title: Manifest Id
+          description: Orchestrator manifest ID for this request
+          default: ''
       type: object
       required:
       - subscribe_url


Reviews:
user: login: leszko
body: Added one comment, other than that LGTM
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T10:54:09Z
user: login: j0sh
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T17:49:32Z
user: login: victorges
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T17:55:13Z
user: login: mjh1
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-21T10:39:04Z

Review comments:
diff_hunk: @@ -878,11 +876,9 @@ components:
           title: Finish Reason
           default: ''
         delta:
-          allOf:
-          - $ref: '#/components/schemas/LLMMessage'
+          $ref: '#/components/schemas/LLMMessage'
         message:
-          allOf:
-          - $ref: '#/components/schemas/LLMMessage'
+          $ref: '#/components/schemas/LLMMessage'
path: runner/gateway.openapi.yaml
user: login: leszko
body: I think you need to revert this OpenAPI changes from `allOf`, because the CI is failing. Maybe you're using some different lib version for generating OpenAPI.
created_at: 2025-05-15T10:53:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -878,11 +876,9 @@ components:
           title: Finish Reason
           default: ''
         delta:
-          allOf:
-          - $ref: '#/components/schemas/LLMMessage'
+          $ref: '#/components/schemas/LLMMessage'
         message:
-          allOf:
-          - $ref: '#/components/schemas/LLMMessage'
+          $ref: '#/components/schemas/LLMMessage'
path: runner/gateway.openapi.yaml
user: login: j0sh
body: Ah yeah this is an [older syntax](https://stackoverflow.com/a/73490368). Just tried a bunch of things to get CI to pick up the newer syntax (updated fastapi, updated python) but nothing seemed to work so I just manually picked the changes in https://github.com/livepeer/ai-runner/pull/579/commits/0aaed81f4ade646083dd2ef491b028c36357fd9a

> In OpenAPI 3.1 (which uses JSON Schema 2020-12 by default), there's even no need for allOf if you only have one $ref because $ref now [allows sibling keywords](https://stackoverflow.com/a/41752575/113116).
created_at: 2025-05-15T17:49:32Z
author_association: CONTRIBUTOR

PR comments:
user: login: j0sh
created_at: 2025-05-15T19:20:10Z
body: Holding off on merging this until after https://github.com/livepeer/ai-runner/pull/568
author_association: CONTRIBUTOR
user: login: j0sh
created_at: 2025-05-22T04:28:44Z
body: Rebased onto master and fixed merge conflicts, no functional changes.

Had to update the OpenAPI yamls in [9fb11ad](https://github.com/livepeer/ai-runner/pull/579/commits/9fb11ad146611ed9467bc670932a0d9ec3e2f8bc) to make CI happy since they weren't regenerated in  https://github.com/livepeer/ai-runner/pull/568 . This also probably needs a go-livepeer update (although I suspect it is not a functional change) - https://github.com/livepeer/go-livepeer/pull/3583
author_association: CONTRIBUTOR