feat: 接入fastapi
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
"""异步执行与恢复大屏任务,当前使用单进程内存检查点"""
|
||||
|
||||
from uuid import uuid4
|
||||
|
||||
from langgraph.types import Command
|
||||
|
||||
from app.core.exceptions import BizException
|
||||
from app.observability.tracing import ainvoke_screen
|
||||
from app.schemas.agent import AgentResult, ResumeRequest
|
||||
from app.schemas.request import DesignRequest
|
||||
|
||||
|
||||
class AgentService:
|
||||
def __init__(self, graph):
|
||||
self.graph = graph
|
||||
self._active: set[str] = set()
|
||||
|
||||
async def run(self, request: DesignRequest) -> AgentResult:
|
||||
# 新请求始终独立建任务,不用 conversation_id 充当检查点 ID
|
||||
thread_id = str(uuid4())
|
||||
return await self._invoke(
|
||||
thread_id,
|
||||
{"request": request.model_dump(mode="json")},
|
||||
request.context.conversation_id,
|
||||
)
|
||||
|
||||
async def resume(self, request: ResumeRequest) -> AgentResult:
|
||||
thread_id = str(request.thread_id)
|
||||
# 检查和占用之间没有 await,同一事件循环内不会重复进入
|
||||
self._claim(thread_id)
|
||||
try:
|
||||
snapshot = await self.graph.aget_state(
|
||||
{"configurable": {"thread_id": thread_id}}
|
||||
)
|
||||
if not snapshot.values:
|
||||
raise BizException(404, "任务不存在或已过期")
|
||||
if not any(task.interrupts for task in snapshot.tasks):
|
||||
raise BizException(409, "任务当前未等待用户补充")
|
||||
# 恢复前校验选项,避免无效回答消耗一次 interrupt 恢复
|
||||
clarification = snapshot.values["clarification"]
|
||||
reply = request.reply
|
||||
if (
|
||||
not reply.cancel
|
||||
and reply.option_id is not None
|
||||
and reply.option_id not in {item.id for item in clarification.options}
|
||||
):
|
||||
raise BizException(422, "请选择当前问题的有效选项")
|
||||
session_id = (
|
||||
snapshot.values["request"].get("context", {}).get("conversation_id")
|
||||
)
|
||||
return await self._execute(
|
||||
thread_id, Command(resume=reply.model_dump(mode="json")), session_id
|
||||
)
|
||||
finally:
|
||||
self._active.discard(thread_id)
|
||||
|
||||
def _claim(self, thread_id: str) -> None:
|
||||
if thread_id in self._active:
|
||||
raise BizException(409, "任务正在执行,请勿重复提交")
|
||||
self._active.add(thread_id)
|
||||
|
||||
async def _invoke(self, thread_id, payload, session_id) -> AgentResult:
|
||||
self._claim(thread_id)
|
||||
try:
|
||||
return await self._execute(thread_id, payload, session_id)
|
||||
finally:
|
||||
# 异常和取消也必须释放占用,不阻塞后续请求
|
||||
self._active.discard(thread_id)
|
||||
|
||||
async def _execute(self, thread_id, payload, session_id) -> AgentResult:
|
||||
state = await ainvoke_screen(
|
||||
self.graph, payload, thread_id=thread_id, session_id=session_id
|
||||
)
|
||||
waiting = bool(state.get("__interrupt__"))
|
||||
# 只交付校验通过的完整动作批次,失败草稿留在后端
|
||||
return AgentResult(
|
||||
thread_id=thread_id,
|
||||
status="waiting_for_input" if waiting else state["status"],
|
||||
actions=state.get("actions") or []
|
||||
if state.get("validation_passed") is True and state["status"] == "completed"
|
||||
else [],
|
||||
answer=state.get("answer"),
|
||||
clarification=state.get("clarification") if waiting else None,
|
||||
failure_reason=state.get("failure_reason"),
|
||||
)
|
||||
Reference in New Issue
Block a user