add front_apis

This commit is contained in:
2026-02-11 16:57:55 +08:00
parent d021671610
commit 6db1353e79

View File

@@ -1,6 +1,7 @@
from typing import Dict, List, Optional from typing import Dict, List, Optional
import os import os
import os.path as osp import os.path as osp
import subprocess
import sys import sys
import uuid import uuid
@@ -26,6 +27,26 @@ class GraphConfigUpsertResponse(BaseModel):
tool_keys: List[str] tool_keys: List[str]
prompt_keys: List[str] prompt_keys: List[str]
class GraphConfigReadResponse(BaseModel):
pipeline_id: str
prompt_set_id: str
tool_keys: List[str]
prompt_dict: Dict[str, str]
class GraphConfigListItem(BaseModel):
pipeline_id: str
prompt_set_id: str
name: str
description: str
is_active: bool
tool_keys: List[str]
created_at: Optional[str] = Field(default=None)
updated_at: Optional[str] = Field(default=None)
class GraphConfigListResponse(BaseModel):
items: List[GraphConfigListItem]
count: int
class PipelineCreateRequest(BaseModel): class PipelineCreateRequest(BaseModel):
graph_id: str = Field( graph_id: str = Field(
description="Graph key from GRAPH_BUILD_FNCS, e.g. routing or react" description="Graph key from GRAPH_BUILD_FNCS, e.g. routing or react"
@@ -46,6 +67,23 @@ class PipelineCreateResponse(BaseModel):
url: str url: str
port: int port: int
class PipelineRunInfo(BaseModel):
run_id: str
pid: int
graph_id: str
pipeline_id: str
prompt_set_id: str
url: str
port: int
class PipelineListResponse(BaseModel):
items: List[PipelineRunInfo]
count: int
class PipelineStopResponse(BaseModel):
run_id: str
status: str
app = FastAPI( app = FastAPI(
title="Front APIs", title="Front APIs",
@@ -63,6 +101,15 @@ app.add_middleware(
_db = DBConfigManager() _db = DBConfigManager()
_running_pipelines: Dict[str, Dict[str, object]] = {} _running_pipelines: Dict[str, Dict[str, object]] = {}
def _prune_stopped_pipelines() -> None:
stale_ids: List[str] = []
for run_id, info in _running_pipelines.items():
proc = info["proc"]
if proc.poll() is not None:
stale_ids.append(run_id)
for run_id in stale_ids:
_running_pipelines.pop(run_id, None)
@app.get("/health") @app.get("/health")
async def health(): async def health():
@@ -75,9 +122,14 @@ async def root():
"message": "Front APIs", "message": "Front APIs",
"endpoints": [ "endpoints": [
"/v1/graph-configs (POST)", "/v1/graph-configs (POST)",
"/v1/graph-configs (GET)",
"/v1/graph-configs/default/{pipeline_id} (GET)",
"/v1/graph-configs/{pipeline_id}/{prompt_set_id} (GET)",
"/v1/graph-configs/{pipeline_id}/{prompt_set_id} (DELETE)", "/v1/graph-configs/{pipeline_id}/{prompt_set_id} (DELETE)",
"/v1/pipelines/graphs (GET)", "/v1/pipelines/graphs (GET)",
"/v1/pipelines (POST)", "/v1/pipelines (POST)",
"/v1/pipelines (GET)",
"/v1/pipelines/{run_id} (DELETE)",
], ],
} }
@@ -103,6 +155,74 @@ async def upsert_graph_config(body: GraphConfigUpsertRequest):
prompt_keys=list(body.prompt_dict.keys()), prompt_keys=list(body.prompt_dict.keys()),
) )
@app.get("/v1/graph-configs", response_model=GraphConfigListResponse)
async def list_graph_configs(pipeline_id: Optional[str] = None, graph_id: Optional[str] = None):
resolved_pipeline_id = pipeline_id or graph_id
try:
rows = _db.list_prompt_sets(pipeline_id=resolved_pipeline_id)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
items = [GraphConfigListItem(**row) for row in rows]
return GraphConfigListResponse(items=items, count=len(items))
@app.get("/v1/graph-configs/default/{pipeline_id}", response_model=GraphConfigReadResponse)
async def get_default_graph_config(pipeline_id: str):
try:
prompt_dict, tool_keys = _db.get_config(pipeline_id=pipeline_id, prompt_set_id=None)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
if not prompt_dict and not tool_keys:
raise HTTPException(
status_code=404,
detail=f"No active prompt set found for pipeline '{pipeline_id}'",
)
rows = _db.list_prompt_sets(pipeline_id=pipeline_id)
active = next((row for row in rows if row["is_active"]), None)
if active is None:
raise HTTPException(
status_code=404,
detail=f"No active prompt set found for pipeline '{pipeline_id}'",
)
return GraphConfigReadResponse(
pipeline_id=pipeline_id,
prompt_set_id=active["prompt_set_id"],
tool_keys=tool_keys,
prompt_dict=prompt_dict,
)
@app.get("/v1/graph-configs/{pipeline_id}/{prompt_set_id}", response_model=GraphConfigReadResponse)
async def get_graph_config(pipeline_id: str, prompt_set_id: str):
try:
meta = _db.get_prompt_set(pipeline_id=pipeline_id, prompt_set_id=prompt_set_id)
if meta is None:
raise HTTPException(
status_code=404,
detail=f"prompt_set_id '{prompt_set_id}' not found for pipeline '{pipeline_id}'",
)
prompt_dict, tool_keys = _db.get_config(
pipeline_id=pipeline_id,
prompt_set_id=prompt_set_id,
)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
return GraphConfigReadResponse(
pipeline_id=pipeline_id,
prompt_set_id=prompt_set_id,
tool_keys=tool_keys,
prompt_dict=prompt_dict,
)
@app.delete("/v1/graph-configs/{pipeline_id}/{prompt_set_id}") @app.delete("/v1/graph-configs/{pipeline_id}/{prompt_set_id}")
async def delete_graph_config(pipeline_id: str, prompt_set_id: str): async def delete_graph_config(pipeline_id: str, prompt_set_id: str):
@@ -124,6 +244,23 @@ async def delete_graph_config(pipeline_id: str, prompt_set_id: str):
async def available_graphs(): async def available_graphs():
return {"available_graphs": sorted(GRAPH_BUILD_FNCS.keys())} return {"available_graphs": sorted(GRAPH_BUILD_FNCS.keys())}
@app.get("/v1/pipelines", response_model=PipelineListResponse)
async def list_running_pipelines():
_prune_stopped_pipelines()
items = [
PipelineRunInfo(
run_id=run_id,
pid=info["proc"].pid,
graph_id=info["graph_id"],
pipeline_id=info["pipeline_id"],
prompt_set_id=info["prompt_set_id"],
url=info["url"],
port=info["port"],
)
for run_id, info in _running_pipelines.items()
]
return PipelineListResponse(items=items, count=len(items))
@app.post("/v1/pipelines", response_model=PipelineCreateResponse) @app.post("/v1/pipelines", response_model=PipelineCreateResponse)
async def create_pipeline(body: PipelineCreateRequest): async def create_pipeline(body: PipelineCreateRequest):
@@ -136,7 +273,7 @@ async def create_pipeline(body: PipelineCreateRequest):
try: try:
proc, url = build_fn( proc, url = build_fn(
pipelin_id=body.pipeline_id, pipeline_id=body.pipeline_id,
prompt_set=body.prompt_set_id, prompt_set=body.prompt_set_id,
tool_keys=body.tool_keys, tool_keys=body.tool_keys,
port=str(body.port), port=str(body.port),
@@ -165,3 +302,20 @@ async def create_pipeline(body: PipelineCreateRequest):
url=url, url=url,
port=body.port, port=body.port,
) )
@app.delete("/v1/pipelines/{run_id}", response_model=PipelineStopResponse)
async def stop_pipeline(run_id: str):
info = _running_pipelines.pop(run_id, None)
if info is None:
raise HTTPException(status_code=404, detail=f"run_id '{run_id}' not found")
proc = info["proc"]
if proc.poll() is None:
proc.terminate()
try:
proc.wait(timeout=5)
except subprocess.TimeoutExpired:
proc.kill()
proc.wait(timeout=5)
return PipelineStopResponse(run_id=run_id, status="stopped")