Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion models/openai_api_compatible/manifest.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version: 0.0.65
version: 0.0.66
type: plugin
author: "langgenius"
name: "openai_api_compatible"
Expand Down
86 changes: 46 additions & 40 deletions models/openai_api_compatible/models/rerank/rerank.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import base64
import ipaddress
import json
import logging
import re
from typing import Mapping, Optional, Union, Any
from typing import Mapping, Optional
from urllib.parse import urlparse

import requests
Expand All @@ -27,6 +25,26 @@


class OpenAIRerankModel(OAICompatRerankModel):
@staticmethod
def _resolve_rerank_endpoint_url(credentials: Mapping) -> str:
rerank_endpoint_url = (credentials.get("rerank_endpoint_url") or "").strip()
if rerank_endpoint_url:
return rerank_endpoint_url

endpoint_url = credentials.get("endpoint_url", "").rstrip("/")
return f"{endpoint_url}/rerank"

@staticmethod
def _safe_request_error_detail(
error: requests.exceptions.RequestException,
) -> str:
if (
isinstance(error, requests.exceptions.HTTPError)
and error.response is not None
):
return f"HTTP {error.response.status_code}"
return type(error).__name__

def validate_credentials(self, model: str, credentials: dict) -> None:
"""
Validate model credentials
Expand Down Expand Up @@ -91,7 +109,7 @@ def _invoke(
return RerankResult(model=model, docs=[])

# Build API request
endpoint_url = credentials.get("endpoint_url", "").rstrip("/")
rerank_endpoint_url = self._resolve_rerank_endpoint_url(credentials)
api_key = credentials.get("api_key", "")
endpoint_model_name = credentials.get("endpoint_model_name", "") or model

Expand All @@ -112,20 +130,15 @@ def _invoke(
}

try:
logger.info(f"Rerank API Request (text mode) to {endpoint_url}/rerank")
logger.info("Rerank API Request (text mode)")

response = requests.post(
f"{endpoint_url}/rerank",
rerank_endpoint_url,
Comment thread
WH-2099 marked this conversation as resolved.
Resolved
headers=headers,
json=payload,
timeout=60,
)

if response.status_code != 200:
logger.error(
f"Rerank API Error {response.status_code}: {response.text[:1000]}"
)

response.raise_for_status()

result = response.json()
Expand Down Expand Up @@ -164,9 +177,12 @@ def _invoke(
)

except requests.exceptions.RequestException as ex:
raise InvokeServerUnavailableError(str(ex))
except Exception as ex:
raise InvokeError(str(ex))
detail = self._safe_request_error_detail(ex)
raise InvokeServerUnavailableError(
f"Rerank API request failed ({detail})"
) from None
except Exception:
raise InvokeError("Rerank API returned an invalid response") from None

def _invoke_multimodal(
self,
Expand Down Expand Up @@ -216,7 +232,7 @@ def _invoke_multimodal(
)

# Build API request
endpoint_url = credentials.get("endpoint_url", "").rstrip("/")
rerank_endpoint_url = self._resolve_rerank_endpoint_url(credentials)
api_key = credentials.get("api_key", "")
endpoint_model_name = credentials.get("endpoint_model_name", "") or model

Expand All @@ -228,13 +244,7 @@ def _invoke_multimodal(
headers["Authorization"] = f"Bearer {api_key}"

# Convert documents to ScoreMultiModalParam format
documents_params = []
for i, doc in enumerate(docs):
doc_param = self._to_score_multimodal_param(doc)
documents_params.append(doc_param)
logger.debug(
f"Document {i}: {json.dumps(doc_param, ensure_ascii=False)[:200]}"
)
documents_params = [self._to_score_multimodal_param(doc) for doc in docs]

# Build payload according to vLLM/Qwen3 format
# Use `top_n if top_n is not None else len(docs)` to correctly handle top_n=0
Expand All @@ -247,24 +257,17 @@ def _invoke_multimodal(

try:
logger.info(
f"Rerank API Request (multimodal mode) to {endpoint_url}/rerank"
"Rerank API Request (multimodal mode): %d documents",
len(documents_params),
)
logger.info(f"Query: {query_text[:100]}")
logger.info(f"Documents count: {len(documents_params)}")
logger.debug(f"Payload: {json.dumps(payload, ensure_ascii=False)[:1000]}")

response = requests.post(
f"{endpoint_url}/rerank",
rerank_endpoint_url,
headers=headers,
json=payload,
timeout=60,
)

if response.status_code != 200:
logger.error(
f"Rerank API Error {response.status_code}: {response.text[:1000]}"
)

response.raise_for_status()

result = response.json()
Expand Down Expand Up @@ -308,9 +311,12 @@ def _invoke_multimodal(
)

except requests.exceptions.RequestException as ex:
raise InvokeServerUnavailableError(str(ex))
except Exception as ex:
raise InvokeError(str(ex))
detail = self._safe_request_error_detail(ex)
raise InvokeServerUnavailableError(
f"Rerank API request failed ({detail})"
) from None
except Exception:
raise InvokeError("Rerank API returned an invalid response") from None

def _validate_image_url(self, url: str) -> str:
"""
Expand All @@ -330,7 +336,7 @@ def _validate_image_url(self, url: str) -> str:

# Only allow http/https URLs
if not (url.startswith("http://") or url.startswith("https://")):
logger.warning(f"Blocked non-HTTP URL: {url[:50]}...")
logger.warning("Blocked non-HTTP image URL")
return ""

# Parse URL to check for SSRF attempts
Expand All @@ -343,7 +349,7 @@ def _validate_image_url(self, url: str) -> str:

# Block localhost
if hostname in ("localhost", "127.0.0.1", "::1"):
logger.warning(f"Blocked localhost URL: {url[:50]}...")
logger.warning("Blocked localhost image URL")
return ""

# Block private IP ranges
Expand All @@ -356,15 +362,15 @@ def _validate_image_url(self, url: str) -> str:
or ip.is_link_local
or ip.is_reserved
):
logger.warning(f"Blocked private IP URL: {url[:50]}...")
logger.warning("Blocked private IP image URL")
return ""
except ValueError:
# Not an IP address, it's a hostname - allow it
pass

return url
except Exception as e:
logger.warning(f"URL validation failed: {e}")
except Exception:
logger.warning("Image URL validation failed")
return ""

def _to_score_multimodal_param(self, content: MultiModalContent) -> dict:
Expand Down
12 changes: 12 additions & 0 deletions models/openai_api_compatible/provider/openai_api_compatible.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,18 @@ model_credential_schema:
placeholder:
zh_Hans: Base URL, e.g. https://api.openai.com/v1
en_US: Base URL, e.g. https://api.openai.com/v1
- variable: rerank_endpoint_url
label:
zh_Hans: 重排序端点 URL
en_US: Rerank Endpoint URL
type: text-input
required: false
show_on:
- variable: __model_type
value: rerank
placeholder:
zh_Hans: 可选完整 URL;留空使用 API Base URL + /rerank;请勿包含凭据或令牌
en_US: Optional complete URL; defaults to API Base URL + /rerank; do not include credentials or tokens
- variable: endpoint_model_name
label:
zh_Hans: API endpoint中的模型名称
Expand Down
Loading
Loading