fix: G10 断点续跑透传 model_provider(sub2api 续跑失败修法)

断点续跑容器不带 --model-provider 时 Server 的 provider 表里
没有 sub2api,thread/resume 报 provider not found(probe15 实弹)。
Docker/Local 断点方法加可选 model_provider 并转发,worker 两处
调用点传 gateway_provider;补单测断言断点命令含
--model-provider sub2api(旧代码下两新测试 1 失败 1 报错)。
This commit is contained in:
host committed 2026-10-29 11:28:39 +08:00
1 parent 0ef896c317
commit a19b1567bb
3 files changed
+96 -8

No files matched your search

+17 -6
View File
@@ -494,7 +494,8 @@ class LocalExecutor(Executor):
return self._goal_result_from_runner(
task["id"], self._run_local(cmd, extra_env))
def goal_resume_breakpoint_task(self, task) -> dict:
def goal_resume_breakpoint_task(self, task,
model_provider=None) -> dict:
"""G10:调 host.py goal-resume(无 --goal-text)断点续跑。
命令:host.py goal-resume --task <host 名>。不传
@@ -502,14 +503,18 @@ class LocalExecutor(Executor):
goal/verify_command/goal_max_rounds,从
goal_rounds_done 的下一轮接着跑同一循环,不开新循环、
不新建 worktree、不重置轮次。结论解析与 goal_task 同款。
(worker 只以 (task) 一个参数调本地断点方法,与 Docker
断点方法的签名不同,不要顺手统一。)
model_provider 可选但 worker 必须传:首轮 thread 在该
provider 下创建,续跑不带同一 --model-provider 时
thread/resume 报 provider not found(与 Docker 断点
方法同 bug,2026-10-29 probe15 实弹)。(task) 单参
调用仍可用;与 Docker 断点方法的签名不同,不要顺手统一。
"""
if not isinstance(task, dict) or task.get("id") is None:
raise ValueError("任务缺少 id")
cmd = self._base_command(model=task.get("model"),
effort=task.get("effort"),
approval=task.get("approval"))
approval=task.get("approval"),
model_provider=model_provider)
cmd += ["goal-resume", "--task", self._host_name(task)]
return self._goal_result_from_runner(
task["id"], self.runner(cmd))
@@ -1424,19 +1429,25 @@ class DockerExecutor(Executor):
def goal_resume_breakpoint_task(self, task, timeout=None,
extra_mounts=None, network=None,
extra_env=None) -> dict:
extra_env=None,
model_provider=None) -> dict:
"""G10:容器内调 host.py goal-resume(无 --goal-text)
断点续跑,与 LocalExecutor.goal_resume_breakpoint_task
同语义(从 goal_rounds_done 下一轮接着跑,不开新循环)。
解析与容器内 goal_resume_task 同款;真 Docker 实弹
未做,命令形状由单测覆盖。
model_provider 必须透传:首轮 thread 在该 provider 下
创建,续跑容器不带同一 --model-provider 时 Server 的
provider 表里没有它,thread/resume 报 provider not found
(2026-10-29 probe15 实弹)。
"""
if not isinstance(task, dict) or task.get("id") is None:
raise ValueError("任务缺少 id")
inner = self._inner_host_command(
"goal-resume", "--task", self._host_name(task),
model=task.get("model"), effort=task.get("effort"),
approval=task.get("approval"))
approval=task.get("approval"),
model_provider=model_provider)
cmd = self.build_run_command(task, inner,
extra_mounts=extra_mounts,
network=network,
+76
View File
@@ -414,6 +414,82 @@ class P1WorkerTestCase(unittest.TestCase):
self.assertIn("goal-resume", cmd)
self.assertNotIn("--goal-text", cmd)
# 12b. 断点续跑透传 model_provider:首轮 thread 在该
# provider 下创建,续跑命令必须带同一 --model-provider,
# 否则 thread/resume 因 provider 表里没有它而失败
# (2026-10-29 probe15 实弹:sub2api not found)。
def test_goal_resume_breakpoint_forwards_model_provider(self):
docker_calls = []
def dfake(cmd, timeout=None):
docker_calls.append(list(cmd))
return SimpleNamespace(
returncode=0,
stdout="goal met after rounds\n"
"usage input=10 cached=0 cache_write=0 output=5 "
"reasoning=0 total=15 completions=1\n",
stderr="")
task = {"id": "T1", "prompt": "orig goal"}
docker = DockerExecutor(runner=dfake, env=FAKE_ENV)
with mock.patch("cloud.sandbox.shutil.which",
return_value="/usr/bin/docker"):
docker.goal_resume_breakpoint_task(
task, model_provider="sub2api")
cmd = docker_calls[-1]
self.assertIn("goal-resume", cmd)
self.assertNotIn("--goal-text", cmd)
self.assertIn("--model-provider", cmd)
self.assertIn("sub2api", cmd)
# 不传 provider 时不带该 flag(透传语义,不是强制默认值)。
docker_calls.clear()
with mock.patch("cloud.sandbox.shutil.which",
return_value="/usr/bin/docker"):
docker.goal_resume_breakpoint_task(task)
self.assertNotIn("--model-provider", docker_calls[-1])
calls = []
def fake(cmd):
calls.append(list(cmd))
return SimpleNamespace(
returncode=0,
stdout="goal met after rounds\n"
"usage input=10 cached=0 cache_write=0 output=5 "
"reasoning=0 total=15 completions=1\n",
stderr="")
local = LocalExecutor("/fake/host.py", runner=fake)
local.goal_resume_breakpoint_task(task, model_provider="sub2api")
self.assertIn("--model-provider", calls[-1])
self.assertIn("sub2api", calls[-1])
# 12c. worker 本地断点调用点透传网关 provider:run_one
# 认领 goal_resume_pending 任务后,本地断点命令必须带
# 网关解析出的 --model-provider(Docker 调用点同理,
# 沙箱级已覆盖命令形状)。
def test_worker_local_breakpoint_forwards_gateway_provider(self):
def fake(cmd):
self.calls.append(list(cmd))
return SimpleNamespace(
returncode=0,
stdout="goal met after rounds\n"
"usage input=10 cached=0 cache_write=0 output=5 "
"reasoning=0 total=15 completions=1\n",
stderr="")
worker = self._worker(runner=fake)
worker._ensure_gateway = lambda task: ("sub2api", {})
_insert_task(self.db_path, "T1", goal_mode=1,
goal="orig goal", goal_max_rounds=3,
goal_resume_pending=1,
model="gpt-5.6-terra")
worker.run_one()
self.assertEqual(_status(self.db_path, "T1"), "completed")
self.assertTrue(any(
"--model-provider" in cmd and "sub2api" in cmd
for cmd in self.calls))
# 13. 开新目标:goal-resume 带 --goal-text(旧行为保留)
def test_goal_resume_new_goal_keeps_goal_text(self):
calls = []
+3 -2
View File
@@ -3029,7 +3029,8 @@ class Worker:
extra_mounts=tenant_mounts,
network=tenant_network,
extra_env=_merge_env(proxy_env,
gateway_env)),
gateway_env),
model_provider=gateway_provider),
quota_check)
elif task.get("_resume"):
# P1 断点续跑传 None(不传 --goal-text,
@@ -3091,7 +3092,7 @@ class Worker:
if task.get("_goal_resume_breakpoint") and isinstance(
self.executor, LocalExecutor):
result = self.executor.goal_resume_breakpoint_task(
task)
task, model_provider=gateway_provider)
elif task.get("_goal_resume_breakpoint"):
# 假执行器/CloudExecutor 没有断点方法时
# 绝不回落成 goal(会撞已存在)或