Add opt-in /assistent/chat-eval for AssistentChat via Debug API.

Lets agents POST/GET a real Assistent turn (tunnel or SSH) without folding it into cheap /snapshot; documents VRAM/Sqlite side effects.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Leonid Pershin
2026-08-23 07:21:13 +03:00
co-authored by Cursor
parent 23b07672d6
commit 719d77efd9
4 changed files with 766 additions and 16 deletions
+106
View File
@@ -144,6 +144,13 @@ def test_debug_server_routes(monkeypatch, tmp_path):
code, data = get("/openapi.json")
assert "/assistent/extension" in data["paths"]
assert "/assistent/api" in data["paths"]
assert "/assistent/chat-eval" in data["paths"]
assert "post" in data["paths"]["/assistent/chat-eval"]
code, data = get("/assistent/chat-eval")
assert "ok" in data
assert data.get("error") == debug_checks.SSH_UNAVAILABLE or not data.get("ok")
assert "hints" in data
with pytest.raises(Exception):
urllib.request.urlopen(f"http://127.0.0.1:{port}/nope", timeout=2)
@@ -151,6 +158,105 @@ def test_debug_server_routes(monkeypatch, tmp_path):
debug_api.stop_debug_server(srv)
def test_extract_assistent_patch():
from gpu_rent.debug_assistent import extract_assistent_patch
bare = extract_assistent_patch("просто текст без json")
assert bare["patch"] is None
assert "просто текст" in bare["prose"]
text = (
"Ок, вот параметры:\n"
"```json\n"
'{"prompt":"a cat","steps":4,"cfg":1.0,"aspect":"1:1","actions":["generate"]}\n'
"```\n"
)
got = extract_assistent_patch(text)
assert got["patch"] is not None
assert got["patch"]["steps"] == 4
assert got["patch"]["cfg"] == 1.0
assert got["patch"]["aspect"] == "1:1"
assert got["patch"]["generate"] is True
assert "Ок" in got["prose"]
assert "```" not in got["prose"]
# last fence wins
multi = (
"```json\n{\"steps\":1}\n```\n"
"mid\n"
"```json\n{\"steps\":8,\"cfg\":2}\n```"
)
got2 = extract_assistent_patch(multi)
assert got2["patch"]["steps"] == 8
assert got2["patch"]["cfg"] == 2
def test_chat_eval_mocked_http(monkeypatch, tmp_path):
from gpu_rent import debug_assistent
_auth_env(monkeypatch, tmp_path)
cfg = load_config(require_auth=True)
monkeypatch.setattr(
debug_assistent,
"_swarm_base",
lambda cfg: ("http://127.0.0.1:17801", "local"),
)
monkeypatch.setattr(
debug_assistent,
"_session_id",
lambda base: ("sess-1", None, 5.0),
)
calls: list[tuple[str, dict]] = []
def fake_http(url, *, method="GET", body=None, timeout=12.0):
if url.endswith("/API/AssistentListModels"):
return True, {"preferred": "qwen3-vl:8b", "models": ["qwen3-vl:8b"]}, 10.0
if url.endswith("/API/AssistentChat"):
calls.append((url, body or {}))
reply = (
"Используй turbo.\n"
'```json\n{"prompt":"portrait","steps":4,"cfg":1,"aspect":"3:4",'
'"actions":["generate"]}\n```'
)
return True, {"success": True, "reply": reply, "model": "qwen3-vl:8b"}, 42.0
return False, "unexpected " + url, 1.0
monkeypatch.setattr(debug_assistent, "_http_json", fake_http)
out = debug_assistent.run_assistent_chat_eval(
cfg,
message="какой checkpoint?",
persona="leonid",
timeout=60,
)
assert out["ok"] is True
assert out["via"] == "local"
assert out["model"] == "qwen3-vl:8b"
assert out["preferred"] == "qwen3-vl:8b"
assert out["persona"] == "leonid"
assert out["patch"]["steps"] == 4
assert out["patch"]["cfg"] == 1
assert out["patch"]["aspect"] == "3:4"
assert "turbo" in (out["reply_prose"] or "").lower() or "turbo" in (out["reply"] or "").lower()
assert calls and calls[0][1]["session_id"] == "sess-1"
assert calls[0][1]["messages"][0]["content"] == "какой checkpoint?"
assert calls[0][1]["includeBase"] is True
def test_chat_eval_timeout_clamp():
from gpu_rent.debug_assistent import (
MAX_CHAT_EVAL_TIMEOUT,
MIN_CHAT_EVAL_TIMEOUT,
_clamp_chat_eval_timeout,
)
assert _clamp_chat_eval_timeout(5) == MIN_CHAT_EVAL_TIMEOUT
assert _clamp_chat_eval_timeout(9999) == MAX_CHAT_EVAL_TIMEOUT
assert _clamp_chat_eval_timeout("90") == 90.0
def test_assistent_compact_and_roles_local(monkeypatch, tmp_path):
from gpu_rent import debug_assistent