execution.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  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. business_lease: object | None = None
  16. def release(self):
  17. if self.business_lease is not None:
  18. self.business_lease.release()
  19. self.slot.release()
  20. self.session.release()
  21. async def admit(user_name: str, session_id: str, *, check_billing: bool = True) -> Admission:
  22. session = concurrency.acquire_session(session_id)
  23. slot = None
  24. try:
  25. slot = concurrency.acquire_task_slot(user_name)
  26. if not await asyncio.to_thread(chat_store.get_session_for_user, session_id, user_name):
  27. raise concurrency.SessionMissingError("会话不存在")
  28. if check_billing:
  29. await asyncio.to_thread(guard.precheck, user_name)
  30. return Admission(session, slot)
  31. except BaseException:
  32. if slot:
  33. slot.release()
  34. session.release()
  35. raise
  36. async def build_execution(admission: Admission, *, agent=None, agent_name: str = "main", **kwargs) -> Execution:
  37. from agents import agent_registry
  38. from runtime.engine import Execution, ExecutionRequest
  39. try:
  40. from db import business_store
  41. if agent_name != "main":
  42. from extensions.registry import load_extensions
  43. load_extensions().business(agent_name)
  44. await asyncio.to_thread(business_store.bind_session, kwargs["session_id"], agent_name)
  45. if agent_name != "main":
  46. from runtime.business import BusinessLease
  47. from extensions.registry import load_extensions
  48. business = load_extensions().business(agent_name)
  49. admission.business_lease = BusinessLease(agent_name)
  50. kwargs.update(agent_name=agent_name, agent_cn_name=business.display_name,
  51. business_lease=admission.business_lease, result_validator=business.result_validator)
  52. if agent is None:
  53. agent = (await asyncio.to_thread(agent_registry.get_agent) if agent_name == "main"
  54. else await asyncio.to_thread(agent_registry.get_agent, agent_name))
  55. graph_config = kwargs["config"]
  56. if agent_name != 'main' and business.skill_path:
  57. import config
  58. graph_config.setdefault('recursion_limit', config.get_int('AGENT_EXPLORATION_RECURSION_LIMIT', 480))
  59. graph_config.setdefault("configurable", {}).update(
  60. workspace_user=kwargs["user_name"], workspace_session=kwargs["session_id"],
  61. business_agent=agent_name,
  62. )
  63. return Execution(ExecutionRequest(agent=agent, slot=admission.slot,
  64. session_lease=admission.session, **kwargs))
  65. except BaseException:
  66. admission.release()
  67. raise
  68. async def execute(*, user_name: str, session_id: str, message: str, agent=None,
  69. agent_name: str = "main", kind: str = "batch",
  70. graph_config: dict | None = None, trace_id: str = "") -> ExecutionResult:
  71. """非 HTTP 入口:对已有且归属当前用户的会话执行一次任务,返回完整结果。
  72. 独立批量任务应各自创建会话;额度与并发限制和聊天入口一致。
  73. """
  74. from obs import bind_trace_id, unbind_trace_id, get_trace_id, log_context
  75. token = bind_trace_id(trace_id or get_trace_id())
  76. try:
  77. with log_context(session_id=session_id, user_name=user_name, kind=kind):
  78. from copy import deepcopy
  79. admission = await admit(user_name, session_id)
  80. try:
  81. options = deepcopy(graph_config) if graph_config is not None else {}
  82. options.setdefault("configurable", {})["thread_id"] = session_id
  83. execution = await build_execution(admission, agent=agent, agent_name=agent_name, session_id=session_id,
  84. thread_id=session_id, user_name=user_name, message=message, original_user_message=message,
  85. kind=kind, config=options, trace_id=get_trace_id())
  86. from runtime.interrupts import pending_interrupts, ResumeError
  87. if await pending_interrupts(execution.req.agent, options):
  88. raise ResumeError("pending_interrupt", "请先处理当前待确认请求")
  89. return await execution.run()
  90. finally:
  91. admission.release()
  92. finally:
  93. unbind_trace_id(token)