This commit is contained in:
@@ -83,7 +83,11 @@ class ResearchAccess(SuperResearchAccess):
|
||||
"create_with": "create_research_template", "required_scope": "research:write",
|
||||
"version_with": "create_research_template_version",
|
||||
"read_with": "get_research_template", "search_with": "search_research_templates",
|
||||
"authored_by": "caller", "max_source_items": 20,
|
||||
"authored_by": "caller", "source_items_required": False, "max_source_items": 20,
|
||||
"expand_with": "expand_research_template", "candidates_with": "get_template_candidates",
|
||||
"backtest_with": "start_template_backtest", "execution_scope": "backtests:execute",
|
||||
"max_candidates": 10000, "page_size_max": 100,
|
||||
"validation": "syntax_and_preparation_settings_combination",
|
||||
"source_items_with": "get_backtest_results", "starts_backtests": False,
|
||||
"web_url": f"{self.public_origin}/#templates",
|
||||
},
|
||||
@@ -311,6 +315,52 @@ class ResearchAccess(SuperResearchAccess):
|
||||
result["web_url"] = f"{self.public_origin}/#templates"
|
||||
return await self.remember(operation, args, digest, result, business_id=args.template_id)
|
||||
|
||||
async def expand_template(self, args):
|
||||
"""Idempotently freeze a version and preparations; never start execution."""
|
||||
from ..research.experiments import Experiments
|
||||
from ..research.workspace_contracts import Expansion
|
||||
|
||||
operation = "expand_research_template"
|
||||
previous, digest = await self.previous(operation, args)
|
||||
if previous:
|
||||
return previous.response
|
||||
service = Experiments(self.db)
|
||||
asset = await service.assets.get(args.template_id, args.version, "template")
|
||||
experiment = await service.create(Expansion(
|
||||
asset_id=args.template_id, version=args.version, preparation_refs=args.preparation_refs,
|
||||
settings=args.settings, mode=args.mode, limit=args.limit, seed=args.seed,
|
||||
hypothesis=asset["content"].get("description", "").strip() or f"使用模板:{asset['name']}",
|
||||
), extra_evidence={"method": "template", "mcp_token_id": self.principal.token_id,
|
||||
"admin_id": self.principal.admin_id})
|
||||
result = await service.template_candidates(experiment["id"])
|
||||
result.update({"read_with": "get_template_candidates", "backtest_with": "start_template_backtest",
|
||||
"starts_backtests": False})
|
||||
return await self.remember(operation, args, digest, result, business_id=experiment["id"])
|
||||
|
||||
async def template_candidates(self, args):
|
||||
from ..research.experiments import Experiments
|
||||
|
||||
return await Experiments(self.db).template_candidates(args.experiment_id, args.limit, args.offset)
|
||||
|
||||
async def start_template_backtest(self, args):
|
||||
"""Execute only stored candidate IDs; authorization comes from the caller's execute scope."""
|
||||
from ..research.experiments import Experiments
|
||||
from ..research.workspace_contracts import TemplateBacktest
|
||||
|
||||
operation = "start_template_backtest"
|
||||
previous, digest = await self.previous(operation, args)
|
||||
if previous:
|
||||
return previous.response
|
||||
provenance = {"mcp_token_id": self.principal.token_id, "admin_id": self.principal.admin_id}
|
||||
result = await Experiments(self.db).start_template_backtest(
|
||||
args.experiment_id,
|
||||
TemplateBacktest(candidate_ids=args.candidate_ids, idempotency_key="mcp-template-" + str(uuid4())),
|
||||
backtests=Backtests(self.db, provenance),
|
||||
)
|
||||
result = {**result, "input_digest": digest, "web_url": self.run_url(result["backtest_run_id"])}
|
||||
return await self.remember(operation, args, digest, result,
|
||||
business_id=result["backtest_run_id"], wake="backtests")
|
||||
|
||||
async def previous(self, operation, args):
|
||||
# PostgreSQL row lock is shared with HTTP start and catalog/job creation.
|
||||
account = await self.db.scalar(select(Account).where(Account.id == self.principal.account_id).with_for_update())
|
||||
|
||||
Reference in New Issue
Block a user