Signed-off-by: Zhanshenzhui <99891002+zyforsure@users.noreply.github.com> Co-authored-by: amy-why-3459 <wuhaiyan17@huawei.com>
76 lines
2.9 KiB
Python
76 lines
2.9 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
|
|
|
|
"""OpenAI-compatible local judge client."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
import pybase64 as base64
|
|
import requests
|
|
|
|
|
|
def _build_openai_url(base_url: str, api_path: str) -> str:
|
|
base = base_url.rstrip("/")
|
|
normalized_path = api_path if api_path.startswith("/") else f"/{api_path}"
|
|
if base.endswith(normalized_path):
|
|
return base
|
|
if base.endswith("/v1"):
|
|
return f"{base}{normalized_path}"
|
|
return f"{base}/v1{normalized_path}"
|
|
|
|
|
|
class DuplexJudge:
|
|
def __init__(self, base_url: str, model: str, *, api_key: str = "EMPTY", timeout: int = 600) -> None:
|
|
self.base_url, self.model, self.api_key, self.timeout = base_url, model, api_key, timeout
|
|
|
|
def chat(self, content: Any, *, system: str | None = None, max_tokens: int = 1200) -> str:
|
|
messages = ([{"role": "system", "content": system}] if system else []) + [{"role": "user", "content": content}]
|
|
response = requests.post(
|
|
_build_openai_url(self.base_url, "/chat/completions"),
|
|
json={
|
|
"model": self.model,
|
|
"messages": messages,
|
|
"temperature": 0.1,
|
|
"max_tokens": max_tokens,
|
|
},
|
|
headers={"Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json"},
|
|
timeout=self.timeout,
|
|
)
|
|
if not response.ok:
|
|
raise requests.HTTPError(
|
|
f"{response.status_code} response from judge: {response.text[:500]}", response=response
|
|
)
|
|
return str(response.json()["choices"][0]["message"]["content"])
|
|
|
|
def temporal(self, prompt: str, frames: list[bytes]) -> str:
|
|
content: list[dict[str, Any]] = [{"type": "text", "text": prompt}]
|
|
content.extend(
|
|
{"type": "image_url", "image_url": {"url": "data:image/jpeg;base64," + base64.b64encode(frame).decode()}}
|
|
for frame in frames
|
|
)
|
|
return self.chat(content, system="You are a careful evaluator for real-time multimodal systems.")
|
|
|
|
def content(
|
|
self,
|
|
prompt: str,
|
|
video: str | Path | None = None,
|
|
frames: list[bytes] | None = None,
|
|
*,
|
|
mode: str = "video_url",
|
|
) -> str:
|
|
content: list[dict[str, Any]] = [{"type": "text", "text": prompt}]
|
|
if mode == "video_url" and video is not None:
|
|
content.append({"type": "video_url", "video_url": {"url": "file://" + str(Path(video).resolve())}})
|
|
else:
|
|
content.extend(
|
|
{
|
|
"type": "image_url",
|
|
"image_url": {"url": "data:image/jpeg;base64," + base64.b64encode(frame).decode()},
|
|
}
|
|
for frame in (frames or [])
|
|
)
|
|
return self.chat(content)
|