execution.py 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. """聊天、审批恢复和定时执行共用的准入与运行装配。"""
  2. from __future__ import annotations
  3. import asyncio
  4. from dataclasses import dataclass
  5. from typing import TYPE_CHECKING
  6. if TYPE_CHECKING:
  7. from runtime.engine import Execution, ExecutionResult
  8. from runtime import concurrency
  9. from billing import guard
  10. from db import chat_store
  11. @dataclass
  12. class Admission:
  13. session: concurrency.SessionLease
  14. slot: concurrency.TaskSlot
  15. def release(self):
  16. self.slot.release()
  17. self.session.release()
  18. async def admit(user_name: str, session_id: str) -> Admission:
  19. session = concurrency.acquire_session(session_id)
  20. slot = None
  21. try:
  22. slot = concurrency.acquire_task_slot(user_name)
  23. if not await asyncio.to_thread(chat_store.get_session_for_user, session_id, user_name):
  24. raise concurrency.SessionMissingError("会话不存在")
  25. await asyncio.to_thread(guard.precheck, user_name)
  26. return Admission(session, slot)
  27. except BaseException:
  28. if slot:
  29. slot.release()
  30. session.release()
  31. raise
  32. async def build_execution(admission: Admission, *, agent=None, **kwargs) -> Execution:
  33. from agents import agent_registry
  34. from runtime.engine import Execution, ExecutionRequest
  35. try:
  36. if agent is None:
  37. agent = await asyncio.to_thread(agent_registry.get_agent)
  38. graph_config = kwargs["config"]
  39. graph_config.setdefault("configurable", {}).update(
  40. workspace_user=kwargs["user_name"], workspace_session=kwargs["session_id"],
  41. )
  42. return Execution(ExecutionRequest(agent=agent, slot=admission.slot,
  43. session_lease=admission.session, **kwargs))
  44. except BaseException:
  45. admission.release()
  46. raise
  47. async def execute(*, user_name: str, session_id: str, message: str, agent=None,
  48. kind: str = "batch", graph_config: dict | None = None, trace_id: str = "") -> ExecutionResult:
  49. """非 HTTP 入口:对已有且归属当前用户的会话执行一次任务,返回完整结果。
  50. 独立批量任务应各自创建会话;额度与并发限制和聊天入口一致。
  51. """
  52. from obs import bind_trace_id, unbind_trace_id, get_trace_id, log_context
  53. token = bind_trace_id(trace_id or get_trace_id())
  54. try:
  55. with log_context(session_id=session_id, user_name=user_name, kind=kind):
  56. from copy import deepcopy
  57. admission = await admit(user_name, session_id)
  58. try:
  59. options = deepcopy(graph_config) if graph_config is not None else {}
  60. options.setdefault("configurable", {})["thread_id"] = session_id
  61. execution = await build_execution(admission, agent=agent, session_id=session_id,
  62. thread_id=session_id, user_name=user_name, message=message, original_user_message=message,
  63. kind=kind, config=options, trace_id=get_trace_id())
  64. from runtime.interrupts import pending_interrupts, ResumeError
  65. if await pending_interrupts(execution.req.agent, options):
  66. raise ResumeError("pending_interrupt", "请先处理当前待确认请求")
  67. return await execution.run()
  68. finally:
  69. admission.release()
  70. finally:
  71. unbind_trace_id(token)