feat: compose native research workflows in QuantFlow

This commit is contained in:
yuxuanhui
2026-09-08 22:42:29 +08:00
parent 7860434b04
commit a6e36e50ec
22 changed files with 2274 additions and 85 deletions
+45 -9
View File
@@ -22,6 +22,7 @@ from .workspace_contracts import (
ImportCommit,
ImportPreview,
SettingVariants,
WorkflowSpec,
)
router = APIRouter(prefix="/api/v1/research", tags=["research"], dependencies=[Depends(require_auth)])
@@ -41,7 +42,7 @@ async def assets(
limit: int = Query(25, ge=1, le=100),
offset: int = Query(0, ge=0),
):
if kind not in ("template", "feature", "view"):
if kind not in ("template", "feature", "view", "workflow"):
raise HTTPException(422, "当前素材类型尚未开放")
async with request.app.state.sessions() as db:
return await Assets(db).list(kind, q, limit, offset)
@@ -49,7 +50,7 @@ async def assets(
@router.post("/assets", status_code=201)
async def save_asset(body: AssetWrite, request: Request):
if body.kind not in ("template", "feature", "view"):
if body.kind not in ("template", "feature", "view", "workflow"):
raise HTTPException(422, "当前素材类型尚未开放")
async with request.app.state.sessions.begin() as db:
return await Assets(db).save(body)
@@ -63,7 +64,7 @@ async def asset(asset_id: str, request: Request, version: int | None = Query(Non
@router.put("/assets/{asset_id}")
async def update_asset(asset_id: str, body: AssetWrite, request: Request):
if body.kind not in ("template", "feature", "view"):
if body.kind not in ("template", "feature", "view", "workflow"):
raise HTTPException(422, "当前素材类型尚未开放")
async with request.app.state.sessions.begin() as db:
return await Assets(db).save(body, asset_id)
@@ -240,21 +241,42 @@ async def fixed_recipe():
async def start_flow(body: FlowStart, request: Request):
from .workflows import Workflows
if body.workflow_id:
raise HTTPException(422, "自定义流程将在 QuantFlow 阶段开放")
async with request.app.state.sessions.begin() as db:
config = await request.app.state.ai.config(db)
result = await Workflows(db).start(body, config.revision)
from .workflows import fixed_workflow
from .workspace_contracts import WorkflowSpec
graph = (
WorkflowSpec.model_validate(
(await Assets(db).get(body.workflow_id, body.workflow_version, "workflow"))["content"]
)
if body.workflow_id
else fixed_workflow(body.budget.max_rounds)
)
needs_model = any(
n.type == "generate"
or (n.type == "feature" and not n.config.get("asset_id"))
or (n.type == "variant" and n.config.get("method", "structure") == "structure")
for n in graph.nodes
)
config = await request.app.state.ai.config(db) if needs_model else None
result = await Workflows(db).start(body, config.revision if config else None)
request.app.state.research.wake.set()
return result
@router.get("/flows/runs")
async def flow_runs(request: Request, limit: int = Query(25, ge=1, le=100), offset: int = Query(0, ge=0)):
async def flow_runs(
request: Request,
limit: int = Query(25, ge=1, le=100),
offset: int = Query(0, ge=0),
kind: str | None = None,
):
from .workflows import Workflows
async with request.app.state.sessions() as db:
return await Workflows(db).list(limit, offset)
if kind not in (None, "pipeline", "quantflow"):
raise HTTPException(422, "未知研究运行类型")
return await Workflows(db).list(limit, offset, kind)
@router.get("/flows/runs/{run_id}")
@@ -276,3 +298,17 @@ async def control_flow(run_id: str, body: FlowControl, request: Request):
request.app.state.research.wake.set()
request.app.state.runner.backtests.wake.set()
return result
@router.get("/flows/nodes")
async def flow_nodes():
from .workflows import NODE_TYPES
return {"items": [{"type": key, **value} for key, value in NODE_TYPES.items()]}
@router.post("/flows/validate")
async def validate_flow(body: WorkflowSpec):
from .workflows import validate_graph
return {"valid": True, "order": validate_graph(body)}