V10.417 protect embedding fallback routing
All checks were successful
CD Pipeline / deploy (push) Successful in 1m4s
All checks were successful
CD Pipeline / deploy (push) Successful in 1m4s
This commit is contained in:
@@ -56,6 +56,9 @@ DEFAULT_MODEL = os.getenv('OLLAMA_MODEL', 'llama3.1:8b') # 較快速的模型
|
||||
TIMEOUT = int(os.getenv('OLLAMA_TIMEOUT', '120')) # 秒 - 2 分鐘
|
||||
COPY_TIMEOUT = int(os.getenv('OLLAMA_COPY_TIMEOUT', '180')) # 文案生成專用超時 - 3 分鐘
|
||||
EMBED_TIMEOUT = int(os.getenv('OLLAMA_EMBED_TIMEOUT', os.getenv('EMBEDDING_TIMEOUT', '45')))
|
||||
EMBED_MAX_TIMEOUT = int(os.getenv('OLLAMA_EMBED_MAX_TIMEOUT', '15'))
|
||||
EMBED_KEEP_ALIVE = os.getenv('OLLAMA_EMBED_KEEP_ALIVE', '1m')
|
||||
EMBED_MAX_CHARS = int(os.getenv('OLLAMA_EMBED_MAX_CHARS', '4000'))
|
||||
FALLBACK_111_KEEP_ALIVE = os.getenv('OLLAMA_111_KEEP_ALIVE', '5m')
|
||||
FALLBACK_111_MAX_TIMEOUT = int(os.getenv('OLLAMA_111_MAX_TIMEOUT', '20'))
|
||||
FALLBACK_111_NUM_CTX = int(os.getenv('OLLAMA_111_NUM_CTX', '4096'))
|
||||
@@ -881,7 +884,8 @@ class OllamaService:
|
||||
return []
|
||||
|
||||
def generate_embedding(self, text: str, model: str = "bge-m3:latest",
|
||||
host: str = None, timeout: int = None) -> List[float]:
|
||||
host: str = None, timeout: int = None,
|
||||
allow_111_fallback: bool = True) -> List[float]:
|
||||
"""
|
||||
[ADR-007] Embedding — 含三主機自動 retry(HOTFIX 2026-05-04)
|
||||
|
||||
@@ -889,7 +893,18 @@ class OllamaService:
|
||||
每次失敗 mark_unhealthy 觸發 resolve cache 失效,下次 resolve 取新主機。
|
||||
caller 顯式 host=... 時凍結(不 retry)。
|
||||
"""
|
||||
request_timeout = timeout or EMBED_TIMEOUT
|
||||
clean_text = (text or "").strip()
|
||||
if not clean_text:
|
||||
return []
|
||||
if len(clean_text) > EMBED_MAX_CHARS:
|
||||
logger.info(
|
||||
"[Embed] input clipped from %s to %s chars for model=%s",
|
||||
len(clean_text),
|
||||
EMBED_MAX_CHARS,
|
||||
model,
|
||||
)
|
||||
clean_text = clean_text[:EMBED_MAX_CHARS]
|
||||
request_timeout = min(timeout or EMBED_TIMEOUT, EMBED_MAX_TIMEOUT)
|
||||
|
||||
def _embed_one(target_host: str) -> List[float]:
|
||||
"""單次 embedding 嘗試 — 成功回 vec,失敗回 [] + mark_unhealthy"""
|
||||
@@ -897,7 +912,7 @@ class OllamaService:
|
||||
# /api/embed 主路徑
|
||||
response = requests.post(
|
||||
f"{target_host}/api/embed",
|
||||
json={"model": model, "input": text},
|
||||
json={"model": model, "input": clean_text, "keep_alive": EMBED_KEEP_ALIVE},
|
||||
timeout=request_timeout,
|
||||
)
|
||||
if response.status_code == 200:
|
||||
@@ -913,7 +928,7 @@ class OllamaService:
|
||||
# /api/embeddings legacy fallback
|
||||
legacy = requests.post(
|
||||
f"{target_host}/api/embeddings",
|
||||
json={"model": model, "prompt": text},
|
||||
json={"model": model, "prompt": clean_text},
|
||||
timeout=request_timeout,
|
||||
)
|
||||
if legacy.status_code == 200:
|
||||
@@ -929,20 +944,48 @@ class OllamaService:
|
||||
|
||||
# caller 顯式指定 host → 凍結不 retry
|
||||
if host:
|
||||
if not allow_111_fallback and _is_111_fallback_host(host):
|
||||
logger.warning("[Embed] 111 fallback disabled; explicit host skipped: %s", host)
|
||||
return []
|
||||
return _embed_one(host.rstrip("/"))
|
||||
|
||||
# HOTFIX 三主機 retry 鏈(與 generate() 同模式)
|
||||
attempted_hosts: List[str] = []
|
||||
for attempt in range(3):
|
||||
target_host = (approved_ollama_env("EMBEDDING_HOST") or resolve_ollama_host()).rstrip("/")
|
||||
canonical_hosts = _canonical_host_chain()
|
||||
allowed_hosts = [
|
||||
candidate for candidate in canonical_hosts
|
||||
if allow_111_fallback or not _is_111_fallback_host(candidate)
|
||||
]
|
||||
max_attempts = len(canonical_hosts) if allow_111_fallback else max(1, len(allowed_hosts))
|
||||
for attempt in range(max_attempts):
|
||||
configured_host = (approved_ollama_env("EMBEDDING_HOST") or "").rstrip("/")
|
||||
if configured_host and (allow_111_fallback or not _is_111_fallback_host(configured_host)):
|
||||
target_host = configured_host
|
||||
else:
|
||||
if configured_host and _is_111_fallback_host(configured_host):
|
||||
logger.warning("[Embed] 111 fallback disabled; ignoring EMBEDDING_HOST=%s", configured_host)
|
||||
target_host = resolve_ollama_host().rstrip("/")
|
||||
if not allow_111_fallback and _is_111_fallback_host(target_host):
|
||||
logger.warning("[Embed] 111 fallback disabled; no approved GCP embedding host available")
|
||||
break
|
||||
if target_host in attempted_hosts:
|
||||
break # cache 還沒過期或同主機,避免無限迴圈
|
||||
next_host = None
|
||||
if target_host in allowed_hosts:
|
||||
next_host = next((candidate for candidate in allowed_hosts if candidate not in attempted_hosts), None)
|
||||
if not next_host:
|
||||
break # cache 還沒過期或同主機,避免無限迴圈
|
||||
logger.info(
|
||||
"[Embed] resolver returned attempted host=%s; forcing next host=%s",
|
||||
target_host,
|
||||
next_host,
|
||||
)
|
||||
target_host = next_host
|
||||
attempted_hosts.append(target_host)
|
||||
|
||||
vec = _embed_one(target_host)
|
||||
if vec:
|
||||
return vec
|
||||
logger.info(f"[Embed] retry #{attempt+1}/3 — {target_host} failed, mark_unhealthy + 取新主機")
|
||||
logger.info(f"[Embed] retry #{attempt+1}/{max_attempts} — {target_host} failed, mark_unhealthy + 取新主機")
|
||||
|
||||
logger.error(f"[Embed] all {len(attempted_hosts)} hosts failed; tried={attempted_hosts}")
|
||||
return []
|
||||
|
||||
@@ -119,7 +119,11 @@ def _process_one_embedding(row_id: int, target_table: str, target_id: int,
|
||||
)
|
||||
session.commit()
|
||||
|
||||
vec = ollama_service.generate_embedding(text_content, model=model)
|
||||
vec = ollama_service.generate_embedding(
|
||||
text_content,
|
||||
model=model,
|
||||
allow_111_fallback=False,
|
||||
)
|
||||
if not vec:
|
||||
raise RuntimeError("embedding 回傳空值")
|
||||
|
||||
@@ -441,7 +445,7 @@ def _build_semantic_rag_context(session, query: str, insight_type: str = None,
|
||||
if not query:
|
||||
return ""
|
||||
try:
|
||||
vec = ollama_service.generate_embedding(query)
|
||||
vec = ollama_service.generate_embedding(query, allow_111_fallback=False)
|
||||
if not vec:
|
||||
return ""
|
||||
filters = ["embedding IS NOT NULL", "status IN ('approved', 'active', 'executed')"]
|
||||
|
||||
@@ -349,7 +349,11 @@ class RAGService:
|
||||
query_vec: Optional[List[float]] = None
|
||||
try:
|
||||
from services.ollama_service import ollama_service
|
||||
query_vec = ollama_service.generate_embedding(text, model=RAG_EMBED_MODEL)
|
||||
query_vec = ollama_service.generate_embedding(
|
||||
text,
|
||||
model=RAG_EMBED_MODEL,
|
||||
allow_111_fallback=False,
|
||||
)
|
||||
if not query_vec:
|
||||
logger.warning(
|
||||
"[RAGService] embedding empty (caller=%s, len=%d) — fallback LLM",
|
||||
|
||||
Reference in New Issue
Block a user