排查"Ollama 模型没有常驻内存":systemd 里 OLLAMA_KEEP_ALIVE=-1 其实一直配置 正确,但这个环境变量只保证"加载过之后不因为空闲被换出",不负责启动时主动 预加载。查 model_calls 表,ollama provider 从来没有一条调用记录——因为它是 问答链路最后一级兜底,前面 NVIDIA/Gemini 一直调用成功,从未真正轮到它, 自然也就从未被加载进内存过。 新增 OllamaAdapter.warm_up():送一条 num_predict=1 的最小请求强制模型加载, 超时给够 180s(冷启动实测能到 1-2 分钟)。app.py 启动时在后台线程调用,不 阻塞主服务启动;找不到 ollama 配置或预热失败都只记警告,不影响服务本身。 实测部署验证:重启后 36 秒完成预热,ollama ps 显示 qwen2.5:7b 已加载, expires_at 显示不过期(keep_alive=-1 生效)——真正需要兜底的那一刻不会再有 冷启动延迟。 新增 test_ollama_adapter.py 4 个用例覆盖预热成功/HTTP失败/异常不上抛/超时 时长。
74 lines
2.4 KiB
Python
74 lines
2.4 KiB
Python
from fam_edge.model_adapters.ollama_adapter import OllamaAdapter
|
|
|
|
|
|
def _cfg(**overrides):
|
|
base = {
|
|
"provider": "ollama",
|
|
"role": "text",
|
|
"model_name": "qwen2.5:7b",
|
|
"base_url": "http://localhost:11434",
|
|
"circuit_breaker": {"enabled": False},
|
|
}
|
|
base.update(overrides)
|
|
return base
|
|
|
|
|
|
class _FakeResp:
|
|
def __init__(self, status_code=200, text=""):
|
|
self.status_code = status_code
|
|
self.text = text
|
|
|
|
def json(self):
|
|
return {"response": "ok"}
|
|
|
|
|
|
def test_warm_up_success(monkeypatch):
|
|
calls = {}
|
|
|
|
def fake_post(url, json=None, timeout=None):
|
|
calls["url"] = url
|
|
calls["json"] = json
|
|
calls["timeout"] = timeout
|
|
return _FakeResp(200)
|
|
monkeypatch.setattr(
|
|
"fam_edge.model_adapters.ollama_adapter.requests.post", fake_post)
|
|
a = OllamaAdapter(_cfg())
|
|
assert a.warm_up() is True
|
|
assert calls["url"] == "http://localhost:11434/api/generate"
|
|
assert calls["json"]["model"] == "qwen2.5:7b"
|
|
# 只为触发加载,不需要真的生成长文本
|
|
assert calls["json"]["options"]["num_predict"] == 1
|
|
|
|
|
|
def test_warm_up_http_error_returns_false(monkeypatch):
|
|
monkeypatch.setattr(
|
|
"fam_edge.model_adapters.ollama_adapter.requests.post",
|
|
lambda url, json=None, timeout=None: _FakeResp(500, "boom"))
|
|
a = OllamaAdapter(_cfg())
|
|
assert a.warm_up() is False
|
|
|
|
|
|
def test_warm_up_exception_does_not_raise(monkeypatch):
|
|
"""核心诉求: 预热失败(比如 Ollama 服务当时没起来)不能抛异常影响主服务
|
|
启动,只应该记警告日志、返回 False。"""
|
|
def raise_err(url, json=None, timeout=None):
|
|
raise ConnectionError("refused")
|
|
monkeypatch.setattr(
|
|
"fam_edge.model_adapters.ollama_adapter.requests.post", raise_err)
|
|
a = OllamaAdapter(_cfg())
|
|
assert a.warm_up() is False
|
|
|
|
|
|
def test_warm_up_uses_generous_timeout_for_cold_start(monkeypatch):
|
|
"""核心诉求: 冷启动实测能到 1-2 分钟,预热请求的超时不能沿用问答的短超时。"""
|
|
captured = {}
|
|
|
|
def fake_post(url, json=None, timeout=None):
|
|
captured["timeout"] = timeout
|
|
return _FakeResp(200)
|
|
monkeypatch.setattr(
|
|
"fam_edge.model_adapters.ollama_adapter.requests.post", fake_post)
|
|
a = OllamaAdapter(_cfg(timeout=20)) # chat() 用的短超时
|
|
a.warm_up()
|
|
assert captured["timeout"] >= 120
|