fix: keep Zep timeouts internal
This commit is contained in:
parent
c81d628975
commit
407c391f35
|
|
@ -8,10 +8,6 @@ LLM_MODEL_NAME=qwen-plus
|
|||
# ===== ZEP记忆图谱配置 =====
|
||||
# 每月免费额度即可支撑简单使用:https://app.getzep.com/
|
||||
ZEP_API_KEY=your_zep_api_key_here
|
||||
# 单次 Zep Cloud 请求超时(秒)
|
||||
ZEP_REQUEST_TIMEOUT_SECONDS=30
|
||||
# 文档批次或模拟 episode 等待处理完成的最长时间(秒)
|
||||
ZEP_INGESTION_TIMEOUT_SECONDS=600
|
||||
|
||||
# ===== 加速 LLM 配置(可选)=====
|
||||
# 注意如果不使用加速配置,env文件中就不要出现下面的配置项
|
||||
|
|
|
|||
|
|
@ -14,29 +14,6 @@ else:
|
|||
load_dotenv(override=True)
|
||||
|
||||
|
||||
def _parse_number(name, raw_value, *, default, cast):
|
||||
"""Parse a numeric setting without making module import fail."""
|
||||
|
||||
try:
|
||||
return cast(raw_value), None
|
||||
except (TypeError, ValueError):
|
||||
return default, f"{name} must be a number"
|
||||
|
||||
|
||||
_zep_request_timeout, _zep_request_timeout_error = _parse_number(
|
||||
"ZEP_REQUEST_TIMEOUT_SECONDS",
|
||||
os.environ.get("ZEP_REQUEST_TIMEOUT_SECONDS", "30"),
|
||||
default=30.0,
|
||||
cast=float,
|
||||
)
|
||||
_zep_ingestion_timeout, _zep_ingestion_timeout_error = _parse_number(
|
||||
"ZEP_INGESTION_TIMEOUT_SECONDS",
|
||||
os.environ.get("ZEP_INGESTION_TIMEOUT_SECONDS", "600"),
|
||||
default=600,
|
||||
cast=int,
|
||||
)
|
||||
|
||||
|
||||
class Config:
|
||||
"""Flask配置类"""
|
||||
|
||||
|
|
@ -54,13 +31,6 @@ class Config:
|
|||
|
||||
# Zep配置
|
||||
ZEP_API_KEY = os.environ.get('ZEP_API_KEY')
|
||||
ZEP_REQUEST_TIMEOUT_SECONDS = _zep_request_timeout
|
||||
ZEP_INGESTION_TIMEOUT_SECONDS = _zep_ingestion_timeout
|
||||
_ZEP_CONFIG_PARSE_ERRORS = tuple(
|
||||
error
|
||||
for error in (_zep_request_timeout_error, _zep_ingestion_timeout_error)
|
||||
if error
|
||||
)
|
||||
|
||||
# 文件上传配置
|
||||
MAX_CONTENT_LENGTH = 50 * 1024 * 1024 # 50MB
|
||||
|
|
@ -100,11 +70,6 @@ class Config:
|
|||
errors.append("ZEP_API_KEY 未配置")
|
||||
if os.environ.get("ZEP_API_URL"):
|
||||
errors.append("ZEP_API_URL 不受支持;MiroFish 仅连接 Zep Cloud")
|
||||
errors.extend(cls._ZEP_CONFIG_PARSE_ERRORS)
|
||||
if cls.ZEP_REQUEST_TIMEOUT_SECONDS <= 0:
|
||||
errors.append("ZEP_REQUEST_TIMEOUT_SECONDS 必须大于 0")
|
||||
if cls.ZEP_INGESTION_TIMEOUT_SECONDS <= 0:
|
||||
errors.append("ZEP_INGESTION_TIMEOUT_SECONDS 必须大于 0")
|
||||
if cls.DEBUG:
|
||||
import warnings
|
||||
warnings.warn("Flask DEBUG mode is enabled. Do not use in production.", RuntimeWarning)
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ from ..utils.ontology import (
|
|||
normalize_ontology_source_targets,
|
||||
)
|
||||
from ..utils.zep import (
|
||||
ZEP_INGESTION_WAIT_TIMEOUT_SECONDS,
|
||||
call_zep_read_with_retry,
|
||||
get_zep_client,
|
||||
is_retryable_zep_error,
|
||||
|
|
@ -635,7 +636,7 @@ class GraphBuilderService:
|
|||
) -> List[str]:
|
||||
"""Wait for a Batch API terminal state and validate every item."""
|
||||
|
||||
timeout = timeout or Config.ZEP_INGESTION_TIMEOUT_SECONDS
|
||||
timeout = timeout or ZEP_INGESTION_WAIT_TIMEOUT_SECONDS
|
||||
start_time = time.time()
|
||||
terminal_states = {"succeeded", "partial", "failed", "invalid", "canceled"}
|
||||
|
||||
|
|
@ -720,7 +721,7 @@ class GraphBuilderService:
|
|||
self,
|
||||
episode_uuids: List[str],
|
||||
progress_callback: Optional[Callable] = None,
|
||||
timeout: int = 600
|
||||
timeout: int = ZEP_INGESTION_WAIT_TIMEOUT_SECONDS
|
||||
):
|
||||
"""等待所有 episode 处理完成(通过查询每个 episode 的 processed 状态)"""
|
||||
if not episode_uuids:
|
||||
|
|
|
|||
|
|
@ -21,6 +21,10 @@ from queue import Queue
|
|||
from ..config import Config
|
||||
from ..utils.logger import get_logger
|
||||
from ..utils.locale import get_locale, set_locale
|
||||
from ..utils.zep import (
|
||||
ZEP_HTTP_REQUEST_TIMEOUT_SECONDS,
|
||||
ZEP_INGESTION_WAIT_TIMEOUT_SECONDS,
|
||||
)
|
||||
from .zep_graph_memory_updater import ZepGraphMemoryManager
|
||||
from .simulation_ipc import SimulationIPCClient, CommandType, IPCResponse
|
||||
|
||||
|
|
@ -1021,8 +1025,8 @@ class SimulationRunner:
|
|||
):
|
||||
wait_timeout = max(
|
||||
30.0,
|
||||
Config.ZEP_INGESTION_TIMEOUT_SECONDS
|
||||
+ Config.ZEP_REQUEST_TIMEOUT_SECONDS
|
||||
ZEP_INGESTION_WAIT_TIMEOUT_SECONDS
|
||||
+ ZEP_HTTP_REQUEST_TIMEOUT_SECONDS
|
||||
+ 5,
|
||||
)
|
||||
monitor.join(timeout=wait_timeout)
|
||||
|
|
|
|||
|
|
@ -13,7 +13,11 @@ from queue import Queue, Empty
|
|||
from ..config import Config
|
||||
from ..utils.logger import get_logger
|
||||
from ..utils.locale import get_locale, set_locale
|
||||
from ..utils.zep import call_zep_read_with_retry, get_zep_client
|
||||
from ..utils.zep import (
|
||||
ZEP_INGESTION_WAIT_TIMEOUT_SECONDS,
|
||||
call_zep_read_with_retry,
|
||||
get_zep_client,
|
||||
)
|
||||
|
||||
logger = get_logger('mirofish.zep_graph_memory_updater')
|
||||
|
||||
|
|
@ -308,7 +312,7 @@ class ZepGraphMemoryUpdater:
|
|||
|
||||
def stop(self):
|
||||
"""Drain the worker, flush tail events, and wait for Cloud ingestion."""
|
||||
deadline = time.time() + Config.ZEP_INGESTION_TIMEOUT_SECONDS
|
||||
deadline = time.time() + ZEP_INGESTION_WAIT_TIMEOUT_SECONDS
|
||||
# Serialize the accepting->closed transition with add_activity's
|
||||
# check+enqueue operation. This closes the small race where a producer
|
||||
# could enqueue after both the worker and final flush had exited.
|
||||
|
|
@ -598,7 +602,7 @@ class ZepGraphMemoryUpdater:
|
|||
return
|
||||
|
||||
if deadline is None:
|
||||
deadline = time.time() + Config.ZEP_INGESTION_TIMEOUT_SECONDS
|
||||
deadline = time.time() + ZEP_INGESTION_WAIT_TIMEOUT_SECONDS
|
||||
while pending:
|
||||
if time.time() >= deadline:
|
||||
raise TimeoutError(
|
||||
|
|
|
|||
|
|
@ -19,6 +19,13 @@ logger = get_logger("mirofish.zep")
|
|||
T = TypeVar("T")
|
||||
|
||||
ZEP_CLOUD_BASE_URL = "https://api.getzep.com/api/v2"
|
||||
# Keep request behavior aligned with the zep-cloud 3.25.0 SDK default that
|
||||
# MiroFish used before introducing the shared client. This is an internal
|
||||
# integration policy, not a deployment setting users need to tune.
|
||||
ZEP_HTTP_REQUEST_TIMEOUT_SECONDS = 60.0
|
||||
# Zep ingestion is asynchronous and may take several minutes. Preserve the
|
||||
# original GraphBuilder deadline while keeping it separate from HTTP timeout.
|
||||
ZEP_INGESTION_WAIT_TIMEOUT_SECONDS = 600
|
||||
MAX_ZEP_SEARCH_QUERY_CHARS = 400
|
||||
MAX_ZEP_SEARCH_RESULTS = 50
|
||||
|
||||
|
|
@ -58,9 +65,6 @@ def _cached_zep_client(api_key: str, timeout: float) -> Zep:
|
|||
def get_zep_client(api_key: str | None = None, timeout: float | None = None) -> Zep:
|
||||
"""Return a process-shared, explicitly configured Zep Cloud client."""
|
||||
|
||||
if Config._ZEP_CONFIG_PARSE_ERRORS:
|
||||
raise ValueError("; ".join(Config._ZEP_CONFIG_PARSE_ERRORS))
|
||||
|
||||
# zep-cloud gives ZEP_API_URL precedence even when base_url is explicit.
|
||||
# Reject it so this Cloud-only integration cannot silently target a
|
||||
# self-hosted or compatibility endpoint.
|
||||
|
|
@ -72,7 +76,7 @@ def get_zep_client(api_key: str | None = None, timeout: float | None = None) ->
|
|||
raise ValueError("ZEP_API_KEY 未配置")
|
||||
|
||||
request_timeout = float(
|
||||
timeout if timeout is not None else Config.ZEP_REQUEST_TIMEOUT_SECONDS
|
||||
timeout if timeout is not None else ZEP_HTTP_REQUEST_TIMEOUT_SECONDS
|
||||
)
|
||||
if request_timeout <= 0:
|
||||
raise ValueError("Zep request timeout must be greater than 0")
|
||||
|
|
|
|||
|
|
@ -187,7 +187,7 @@ def test_pending_episode_wait_has_a_deadline(monkeypatch):
|
|||
processed=False
|
||||
)
|
||||
timestamps = iter([0.0, 2.0])
|
||||
monkeypatch.setattr(updater_module.Config, "ZEP_INGESTION_TIMEOUT_SECONDS", 1)
|
||||
monkeypatch.setattr(updater_module, "ZEP_INGESTION_WAIT_TIMEOUT_SECONDS", 1)
|
||||
monkeypatch.setattr(updater_module.time, "time", lambda: next(timestamps))
|
||||
monkeypatch.setattr(updater_module.time, "sleep", lambda _seconds: None)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
from zep_cloud.core.api_error import ApiError as ZepApiError
|
||||
|
||||
from app.utils import zep
|
||||
from app import config
|
||||
|
||||
|
||||
def test_permanent_zep_errors_fail_without_retry():
|
||||
|
|
@ -79,24 +79,34 @@ def test_zep_client_rejects_self_hosted_endpoint_override(monkeypatch):
|
|||
zep.get_zep_client("test-key")
|
||||
|
||||
|
||||
def test_malformed_timeout_config_is_reported_without_import_failure():
|
||||
value, error = config._parse_number(
|
||||
"ZEP_REQUEST_TIMEOUT_SECONDS",
|
||||
"not-a-number",
|
||||
default=30.0,
|
||||
cast=float,
|
||||
)
|
||||
def test_zep_client_uses_internal_timeout_and_ignores_env_overrides(monkeypatch):
|
||||
created = []
|
||||
|
||||
assert value == 30.0
|
||||
assert "must be a number" in error
|
||||
def fake_zep(**kwargs):
|
||||
created.append(kwargs)
|
||||
return SimpleNamespace(kwargs=kwargs)
|
||||
|
||||
monkeypatch.delenv("ZEP_API_URL", raising=False)
|
||||
monkeypatch.setenv("ZEP_REQUEST_TIMEOUT_SECONDS", "1")
|
||||
monkeypatch.setenv("ZEP_INGESTION_TIMEOUT_SECONDS", "1")
|
||||
monkeypatch.setattr(zep, "Zep", fake_zep)
|
||||
zep.clear_zep_client_cache()
|
||||
|
||||
zep.get_zep_client("test-key")
|
||||
|
||||
assert created == [{
|
||||
"api_key": "test-key",
|
||||
"base_url": zep.ZEP_CLOUD_BASE_URL,
|
||||
"timeout": zep.ZEP_HTTP_REQUEST_TIMEOUT_SECONDS,
|
||||
}]
|
||||
assert zep.ZEP_HTTP_REQUEST_TIMEOUT_SECONDS == 60.0
|
||||
assert zep.ZEP_INGESTION_WAIT_TIMEOUT_SECONDS == 600
|
||||
zep.clear_zep_client_cache()
|
||||
|
||||
|
||||
def test_zep_client_rejects_a_recorded_timeout_parse_error(monkeypatch):
|
||||
monkeypatch.setattr(
|
||||
zep.Config,
|
||||
"_ZEP_CONFIG_PARSE_ERRORS",
|
||||
("ZEP_REQUEST_TIMEOUT_SECONDS must be a number",),
|
||||
)
|
||||
def test_zep_timeout_policy_is_not_exposed_in_env_example():
|
||||
env_example = Path(__file__).resolve().parents[2] / ".env.example"
|
||||
contents = env_example.read_text(encoding="utf-8")
|
||||
|
||||
with pytest.raises(ValueError, match="must be a number"):
|
||||
zep.get_zep_client("test-key")
|
||||
assert "ZEP_REQUEST_TIMEOUT_SECONDS" not in contents
|
||||
assert "ZEP_INGESTION_TIMEOUT_SECONDS" not in contents
|
||||
|
|
|
|||
Loading…
Reference in New Issue