Files
codex-app-server/cloud/tests/test_child_model.py
T
host 14757f15c4 feat: 子任务默认模型/强度(child_model/child_effort)
主任务可显式声明派生默认,或在 prompt 里写“子任务用 gpt-6-luna max”
由服务端解析填入;marker task-create 与协调员分工未显式写模型/强度时
按 块/分工 > 派生默认 > 父任务模型/强度 继承,fork 复制派生默认,
continue 可改派生默认;console 新建/继续表单与任务详情同步展示。
2026-10-29 05:27:22 +08:00

293 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""子任务默认模型/强度(child_model / child_effort)单测。
不跑真实模型、不联网:解析是纯函数;服务端用本地临时端口
+ 临时 SQLite(与 test_server_endpoints 同款骨架);worker
派生用临时 SQLite + _deliver_marker_tools(与
test_marker_tools 同款骨架,不走 run_one 全量流程)。
"""
import http.client
import json
import os
import sqlite3
import sys
import tempfile
import threading
import time
import unittest
from pathlib import Path
try:
from cloud.auth import AuthStore
from cloud.marker_tools import parse_spawn_defaults
from cloud.server import create_server, init_db
from cloud.worker import Worker
except ImportError:
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
from cloud.auth import AuthStore
from cloud.marker_tools import parse_spawn_defaults
from cloud.server import create_server, init_db
from cloud.worker import Worker
FAKE_ENV = {"XCM_API_KEY": "test-placeholder"}
class ParseSpawnDefaultsTestCase(unittest.TestCase):
def test_user_example(self):
self.assertEqual(
parse_spawn_defaults("子代理用 gpt-6-luna max"),
{"model": "gpt-6-luna", "effort": "max"})
def test_labelled_forms(self):
self.assertEqual(
parse_spawn_defaults("子任务模型:gpt-6-luna,强度:max"),
{"model": "gpt-6-luna", "effort": "max"})
self.assertEqual(
parse_spawn_defaults("子任务用 gpt-6.1-sol,effort high"),
{"model": "gpt-6.1-sol", "effort": "high"})
def test_english_and_reversed(self):
self.assertEqual(
parse_spawn_defaults("subtasks use gpt-6-luna with effort max"),
{"model": "gpt-6-luna", "effort": "max"})
self.assertEqual(
parse_spawn_defaults("用 gpt-6-luna max 跑子任务"),
{"model": "gpt-6-luna", "effort": "max"})
def test_no_false_positives(self):
self.assertEqual(
parse_spawn_defaults("子任务要写报告,不要用默认模型"), {})
self.assertEqual(
parse_spawn_defaults("模型选择很重要,子任务用默认配置"), {})
# 裸说主线用 X 是本任务自己的模型,不算派生默认。
self.assertEqual(parse_spawn_defaults("主线用 gpt-6-luna max"), {})
# 跨子句的强度词不算。
self.assertEqual(
parse_spawn_defaults("子任务用 gpt-6-luna。下一句强度是 high。"),
{"model": "gpt-6-luna"})
class ServerChildModelTestCase(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.db_path = os.path.join(self.tmp.name, "control.db")
self.auth = AuthStore(self.db_path)
self.owner_id, self.owner_key = self.auth.create_user("owner")
self.server = create_server(self.db_path, "127.0.0.1", 0)
self.port = self.server.server_address[1]
self.thread = threading.Thread(
target=self.server.serve_forever, daemon=True)
self.thread.start()
status, self.project = self._req(
"POST", "/projects", {"name": "demo"}, self.owner_key)
self.assertEqual(status, 201)
def tearDown(self):
self.server.shutdown()
self.server.server_close()
self.thread.join(timeout=5)
try:
self.auth.conn.close()
except Exception:
pass
self.tmp.cleanup()
def _req(self, method, path, body=None, key=None):
conn = http.client.HTTPConnection("127.0.0.1", self.port, timeout=10)
hdrs = {}
payload = None
if body is not None:
payload = json.dumps(body).encode("utf-8")
hdrs["Content-Type"] = "application/json"
if key is not None:
hdrs["X-API-Key"] = key
try:
conn.request(method, path, body=payload, headers=hdrs)
resp = conn.getresponse()
data = resp.read()
finally:
conn.close()
try:
parsed = json.loads(data.decode("utf-8")) if data else None
except ValueError:
parsed = None
return resp.status, parsed
def _create(self, prompt, **extra):
body = {"project_id": self.project["id"], "prompt": prompt}
body.update(extra)
status, task = self._req("POST", "/tasks", body, self.owner_key)
self.assertEqual(status, 201, task)
return task
def _set_terminal(self, task_id):
conn = sqlite3.connect(self.db_path)
try:
conn.execute(
"UPDATE tasks SET status = 'completed' WHERE id = ?",
(task_id,))
conn.commit()
finally:
conn.close()
def test_create_explicit_child_fields(self):
task = self._create(
"做事", model="gpt-5.6-terra", effort="low",
child_model="gpt-6-luna", child_effort="max")
self.assertEqual(task["model"], "gpt-5.6-terra")
self.assertEqual(task["child_model"], "gpt-6-luna")
self.assertEqual(task["child_effort"], "max")
def test_create_prompt_directive_fills_child_fields(self):
task = self._create("子代理用 gpt-6-luna max,做三件事")
self.assertEqual(task["child_model"], "gpt-6-luna")
self.assertEqual(task["child_effort"], "max")
# prompt 指令只填派生默认,不改本任务自己的模型。
self.assertIsNone(task["model"])
def test_create_explicit_wins_over_prompt(self):
task = self._create(
"子代理用 gpt-6-luna max", child_model="gpt-6-sol",
child_effort="high")
self.assertEqual(task["child_model"], "gpt-6-sol")
self.assertEqual(task["child_effort"], "high")
def test_create_invalid_child_effort_400(self):
status, body = self._req("POST", "/tasks", {
"project_id": self.project["id"], "prompt": "做事",
"child_effort": "huge"}, self.owner_key)
self.assertEqual(status, 400)
def test_fork_copies_child_fields(self):
parent = self._create(
"做事", model="gpt-5.6-terra", child_model="gpt-6-luna",
child_effort="max")
self._set_terminal(parent["id"])
status, forked = self._req(
"POST", "/tasks/%s/fork" % parent["id"],
{"message": "继续"}, self.owner_key)
self.assertEqual(status, 201, forked)
self.assertEqual(forked["model"], "gpt-5.6-terra")
self.assertEqual(forked["child_model"], "gpt-6-luna")
self.assertEqual(forked["child_effort"], "max")
def test_continue_updates_child_fields(self):
task = self._create("做事")
self._set_terminal(task["id"])
status, continued = self._req(
"POST", "/tasks/%s/continue" % task["id"],
{"message": "再跑一轮", "child_model": "gpt-6-luna",
"child_effort": "max"}, self.owner_key)
self.assertEqual(status, 200, continued)
self.assertEqual(continued["child_model"], "gpt-6-luna")
self.assertEqual(continued["child_effort"], "max")
class WorkerChildModelTestCase(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.db_path = os.path.join(self.tmp.name, "control.db")
init_db(self.db_path)
self.auth = AuthStore(self.db_path)
self.user_id, _key = self.auth.create_user("tester")
self._insert("PM", status="running", model="gpt-5.6-terra",
effort="low", child_model="gpt-6-luna",
child_effort="max")
def tearDown(self):
try:
self.auth.conn.close()
except Exception:
pass
self.tmp.cleanup()
def _insert(self, task_id, status="queued", **cols):
now = time.time()
conn = sqlite3.connect(self.db_path)
try:
conn.execute(
"INSERT INTO tasks(id, project_id, prompt, status,"
" idempotency_key, depends_on, priority, created_by,"
" created_at, updated_at, last_heartbeat)"
" VALUES(?, 'p1', 'do work', ?, NULL, '[]', 0, 'tester',"
" ?, ?, NULL)",
(task_id, status, now, now))
for key, value in cols.items():
conn.execute(
"UPDATE tasks SET %s = ? WHERE id = ?" % key,
(value, task_id))
conn.commit()
finally:
conn.close()
def _child_row(self):
conn = sqlite3.connect(self.db_path)
try:
return conn.execute(
"SELECT model, effort, child_model, child_effort"
" FROM tasks WHERE parent_task_id = 'PM'").fetchone()
finally:
conn.close()
def _deliver(self, creates):
worker = Worker(self.db_path, "/fake/host.py", env=FAKE_ENV)
worker._deliver_marker_tools(worker.get_task("PM"), {
"status": "completed", "task_creates": creates,
"task_polls": [], "wait_threads": [], "task_cancels": [],
"task_retries": []})
def test_marker_inherits_child_defaults_when_block_blank(self):
self._deliver([{"name": "E1", "prompt": "do e1",
"model": None, "effort": None, "executor": None}])
row = self._child_row()
self.assertIsNotNone(row)
self.assertEqual(row[0], "gpt-6-luna")
self.assertEqual(row[1], "max")
# 派生默认本身也传给子任务,隔代派生不丢。
self.assertEqual(row[2], "gpt-6-luna")
self.assertEqual(row[3], "max")
def test_marker_explicit_block_wins(self):
self._deliver([{"name": "E1", "prompt": "do e1",
"model": "gpt-6-sol", "effort": "high",
"executor": None}])
row = self._child_row()
self.assertEqual(row[0], "gpt-6-sol")
self.assertEqual(row[1], "high")
def test_marker_falls_back_to_parent_model_without_child_defaults(self):
conn = sqlite3.connect(self.db_path)
try:
conn.execute(
"UPDATE tasks SET child_model = NULL,"
" child_effort = NULL WHERE id = 'PM'")
conn.commit()
finally:
conn.close()
self._deliver([{"name": "E1", "prompt": "do e1",
"model": None, "effort": None, "executor": None}])
row = self._child_row()
self.assertEqual(row[0], "gpt-5.6-terra")
self.assertEqual(row[1], "low")
def test_coordinator_inherits_child_defaults(self):
worker = Worker(self.db_path, "/fake/host.py", env=FAKE_ENV)
parent = worker.get_task("PM")
children = worker._coord_sync_children(parent, [
{"name": "后端", "prompt": "做后端", "model": None,
"effort": None, "executor": None}])
self.assertEqual(len(children), 1)
conn = sqlite3.connect(self.db_path)
try:
row = conn.execute(
"SELECT model, effort FROM tasks WHERE id = ?",
(children[0]["id"],)).fetchone()
finally:
conn.close()
self.assertEqual(row[0], "gpt-6-luna")
self.assertEqual(row[1], "max")
if __name__ == "__main__":
unittest.main()