fix(llm): add timeout to HTTP requests in LLM integration layer (#14313)

### What problem does this PR solve?

Multiple `requests.post()` calls across the LLM integration layer lack a
`timeout` parameter. Without a timeout, a single unresponsive upstream
service can block the calling thread **indefinitely**, eventually
exhausting the thread pool and degrading the entire system.

This is a well-known issue — Python's `requests` library defaults to
`timeout=None` (infinite wait), and [the library docs explicitly
recommend](https://requests.readthedocs.io/en/latest/user/advanced/#timeouts)
always setting a timeout.

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)

### Change

Added `timeout` to all `requests.post()` calls missing it:

| File | Calls fixed | Timeout |
|------|-------------|---------|
| `rag/llm/rerank_model.py` | 9 | 30s |
| `rag/llm/embedding_model.py` | 8 | 30s |
| `rag/llm/cv_model.py` | 3 | 60s |
| `rag/llm/tts_model.py` | 2 | 60s |
| `rag/llm/sequence2txt_model.py` | 2 | 60s |

Embedding/rerank calls use 30s (lightweight API calls). Vision, TTS, and
audio transcription use 60s (heavier workloads with file uploads).

Note: other files in the codebase (e.g. `check_minio_alive`,
`check_ragflow_server_alive`) already use `timeout=10`, so this PR
brings the LLM layer in line with existing practice.

Signed-off-by: Ricardo-M-L <Sibyl_Hartmanbnb@webname.com>
Co-authored-by: Kevin Hu <kevinhu.sh@gmail.com>
This commit is contained in:
Ricardo-M-L
2026-05-11 11:19:07 +08:00
committed by GitHub
parent 51b73850e1
commit 13922209e6
5 changed files with 26 additions and 19 deletions

View File

@@ -446,6 +446,7 @@ class Zhipu4V(GptV4):
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json",
},
timeout=60,
)
return response.json()
@@ -1029,6 +1030,7 @@ class NvidiaCV(Base):
"Authorization": f"Bearer {self.key}",
},
json={"messages": self.prompt(b64)},
timeout=60,
)
response = response.json()
return (
@@ -1046,6 +1048,7 @@ class NvidiaCV(Base):
"Authorization": f"Bearer {self.key}",
},
json={"messages": msg, **gen_conf},
timeout=60,
)
return response.json()

View File

@@ -409,7 +409,7 @@ class JinaMultiVecEmbed(Base):
data["task"] = task
data["truncate"] = True
response = requests.post(self.base_url, headers=self.headers, json=data)
response = requests.post(self.base_url, headers=self.headers, json=data, timeout=30)
try:
res = response.json()
for d in res["data"]:
@@ -687,7 +687,7 @@ class NvidiaEmbed(Base):
"encoding_format": "float",
"truncate": "END",
}
response = requests.post(self.base_url, headers=self.headers, json=payload)
response = requests.post(self.base_url, headers=self.headers, json=payload, timeout=30)
try:
res = response.json()
ress.extend([d["embedding"] for d in res["data"]])
@@ -827,7 +827,7 @@ class SILICONFLOWEmbed(Base):
"input": texts_batch,
"encoding_format": "float",
}
response = requests.post(self.base_url, json=payload, headers=self.headers)
response = requests.post(self.base_url, json=payload, headers=self.headers, timeout=30)
try:
res = response.json()
ress.extend([d["embedding"] for d in res["data"]])
@@ -844,7 +844,7 @@ class SILICONFLOWEmbed(Base):
"input": text,
"encoding_format": "float",
}
response = requests.post(self.base_url, json=payload, headers=self.headers)
response = requests.post(self.base_url, json=payload, headers=self.headers, timeout=30)
try:
res = response.json()
return np.array(res["data"][0]["embedding"]), total_token_count_from_response(res)
@@ -954,7 +954,7 @@ class HuggingFaceEmbed(Base):
self.base_url = base_url or "http://127.0.0.1:8080"
def encode(self, texts: list):
response = requests.post(f"{self.base_url}/embed", json={"inputs": texts}, headers={"Content-Type": "application/json"})
response = requests.post(f"{self.base_url}/embed", json={"inputs": texts}, headers={"Content-Type": "application/json"}, timeout=30)
if response.status_code == 200:
embeddings = response.json()
else:
@@ -962,7 +962,7 @@ class HuggingFaceEmbed(Base):
return np.array(embeddings), sum([num_tokens_from_string(text) for text in texts])
def encode_queries(self, text: str):
response = requests.post(f"{self.base_url}/embed", json={"inputs": text}, headers={"Content-Type": "application/json"})
response = requests.post(f"{self.base_url}/embed", json={"inputs": text}, headers={"Content-Type": "application/json"}, timeout=30)
if response.status_code == 200:
embedding = response.json()[0]
return np.array(embedding), num_tokens_from_string(text)
@@ -1163,7 +1163,7 @@ class PerplexityEmbed(Base):
"input": [[chunk] for chunk in batch],
"encoding_format": "base64_int8",
}
response = requests.post(url, headers=self.headers, json=payload)
response = requests.post(url, headers=self.headers, json=payload, timeout=30)
try:
res = response.json()
for doc in res["data"]:
@@ -1182,7 +1182,7 @@ class PerplexityEmbed(Base):
"input": batch,
"encoding_format": "base64_int8",
}
response = requests.post(url, headers=self.headers, json=payload)
response = requests.post(url, headers=self.headers, json=payload, timeout=30)
try:
res = response.json()
for d in res["data"]:

View File

@@ -65,7 +65,7 @@ class JinaRerank(Base):
def similarity(self, query: str, texts: list):
texts = [truncate(t, 8196) for t in texts]
data = {"model": self.model_name, "query": query, "documents": texts, "top_n": len(texts)}
res = requests.post(self.base_url, headers=self.headers, json=data).json()
res = requests.post(self.base_url, headers=self.headers, json=data, timeout=30).json()
rank = np.zeros(len(texts), dtype=float)
try:
for d in res["results"]:
@@ -97,7 +97,7 @@ class XInferenceRerank(Base):
for _, t in pairs:
token_count += num_tokens_from_string(t)
data = {"model": self.model_name, "query": query, "return_documents": "true", "return_len": "true", "documents": texts}
res = requests.post(self.base_url, headers=self.headers, json=data).json()
res = requests.post(self.base_url, headers=self.headers, json=data, timeout=30).json()
rank = np.zeros(len(texts), dtype=float)
try:
for d in res["results"]:
@@ -130,7 +130,7 @@ class LocalAIRerank(Base):
token_count = 0
for t in texts:
token_count += num_tokens_from_string(t)
res = requests.post(self.base_url, headers=self.headers, json=data).json()
res = requests.post(self.base_url, headers=self.headers, json=data, timeout=30).json()
rank = np.zeros(len(texts), dtype=float)
try:
for d in res["results"]:
@@ -173,7 +173,7 @@ class NvidiaRerank(Base):
"truncate": "END",
"top_n": len(texts),
}
res = requests.post(self.base_url, headers=self.headers, json=data).json()
res = requests.post(self.base_url, headers=self.headers, json=data, timeout=30).json()
rank = np.zeros(len(texts), dtype=float)
try:
for d in res["rankings"]:
@@ -217,7 +217,7 @@ class OpenAI_APIRerank(Base):
token_count = 0
for t in texts:
token_count += num_tokens_from_string(t)
res = requests.post(self.base_url, headers=self.headers, json=data).json()
res = requests.post(self.base_url, headers=self.headers, json=data, timeout=30).json()
rank = np.zeros(len(texts), dtype=float)
try:
for d in res["results"]:
@@ -298,7 +298,7 @@ class SILICONFLOWRerank(Base):
"max_chunks_per_doc": 1024,
"overlap_tokens": 80,
}
response_raw = requests.post(self.base_url, json=payload, headers=self.headers)
response_raw = requests.post(self.base_url, json=payload, headers=self.headers, timeout=30)
response = response_raw.json()
rank = np.zeros(len(texts), dtype=float)
try:
@@ -421,6 +421,7 @@ class HuggingfaceRerank(Base):
endpoint,
headers = {"Content-Type": "application/json"},
json = {"query": query, "texts": texts[i: i + batch_size], "raw_scores": False, "truncate": True},
timeout=30
)
for o in res.json():
scores[o["index"] + i] = o["score"]
@@ -468,7 +469,7 @@ class GPUStackRerank(Base):
}
try:
response = requests.post(self.base_url, json=payload, headers=self.headers)
response = requests.post(self.base_url, json=payload, headers=self.headers, timeout=30)
response.raise_for_status()
response_json = response.json()
@@ -570,7 +571,7 @@ class RAGconRerank(Base):
token_count = 0
for t in texts:
token_count += num_tokens_from_string(t)
res = requests.post(self._base_url + "/rerank", headers=self.headers, json=data).json()
res = requests.post(self._base_url + "/rerank", headers=self.headers, json=data, timeout=30).json()
rank = np.zeros(len(texts), dtype=float)
try:
for d in res["results"]:

View File

@@ -195,7 +195,7 @@ class XinferenceSeq2txt(Base):
files = {"file": (audio_file_name, audio_data, "audio/wav")}
try:
response = requests.post(f"{self.base_url}/v1/audio/transcriptions", files=files, data=payload)
response = requests.post(f"{self.base_url}/v1/audio/transcriptions", files=files, data=payload, timeout=60)
response.raise_for_status()
result = response.json()
@@ -377,6 +377,7 @@ class ZhipuSeq2txt(Base):
data=payload,
files=files,
headers=headers,
timeout=60,
)
body = response.json()
if response.status_code == 200:

View File

@@ -116,7 +116,8 @@ class HTTPBasedTTS(Base):
url,
headers=self.headers,
json=payload,
stream=stream
stream=stream,
timeout=60,
)
if response.status_code != 200:
@@ -532,7 +533,8 @@ class RAGconTTS(Base):
f"{self.base_url}/audio/speech",
headers=self.headers,
json=payload,
stream=stream
stream=stream,
timeout=60,
)
if response.status_code != 200: