llm_task_service.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. """
  2. A/B 组 LLM 任务表服务:读取最新任务 prompt / 首句
  3. """
  4. import logging
  5. import time
  6. from typing import Any, Dict, Optional, TypedDict
  7. from sqlalchemy import desc, func, select
  8. from database import db_manager
  9. from models_db import LlmTask
  10. logger = logging.getLogger(__name__)
  11. _CACHE_TTL_SECONDS = 60.0
  12. _cached_task: Optional[Dict[str, Any]] = None
  13. _cached_at: float = 0.0
  14. class LlmTaskPayload(TypedDict):
  15. task_name: str
  16. task_prompt: str
  17. lumi_first_line: str
  18. class LlmTaskService:
  19. async def get_latest_task(self) -> Optional[LlmTaskPayload]:
  20. """取 created_at 最新的一条任务。"""
  21. try:
  22. async with db_manager.get_session() as session:
  23. result = await session.execute(
  24. select(LlmTask).order_by(desc(LlmTask.created_at), desc(LlmTask.id)).limit(1)
  25. )
  26. row = result.scalar_one_or_none()
  27. if not row:
  28. return None
  29. return {
  30. "task_name": row.task_name,
  31. "task_prompt": row.task_prompt,
  32. "lumi_first_line": row.lumi_first_line,
  33. }
  34. except Exception as e:
  35. logger.warning("[LlmTask] get_latest_task failed: %s", e)
  36. return None
  37. async def get_latest_task_cached(self, force_refresh: bool = False) -> Optional[LlmTaskPayload]:
  38. global _cached_task, _cached_at
  39. now = time.time()
  40. if (
  41. not force_refresh
  42. and _cached_task is not None
  43. and (now - _cached_at) < _CACHE_TTL_SECONDS
  44. ):
  45. return _cached_task
  46. task = await self.get_latest_task()
  47. if task:
  48. _cached_task = task
  49. _cached_at = now
  50. return task
  51. async def list_tasks(self, page: int = 1, page_size: int = 20) -> Dict[str, Any]:
  52. page = max(1, page)
  53. page_size = max(1, min(page_size, 100))
  54. offset = (page - 1) * page_size
  55. try:
  56. async with db_manager.get_session() as session:
  57. total = int(
  58. (await session.execute(select(func.count()).select_from(LlmTask))).scalar_one()
  59. )
  60. result = await session.execute(
  61. select(LlmTask)
  62. .order_by(desc(LlmTask.created_at), desc(LlmTask.id))
  63. .offset(offset)
  64. .limit(page_size)
  65. )
  66. rows = result.scalars().all()
  67. items = [
  68. self._row_to_dict(row)
  69. for row in rows
  70. ]
  71. return {"items": items, "total": total, "page": page, "page_size": page_size}
  72. except Exception as e:
  73. logger.warning("[LlmTask] list_tasks failed: %s", e)
  74. raise
  75. async def create_task(
  76. self,
  77. task_name: str,
  78. task_prompt: str,
  79. lumi_first_line: str,
  80. ) -> Dict[str, Any]:
  81. task_name = task_name.strip()
  82. task_prompt = task_prompt.strip()
  83. lumi_first_line = lumi_first_line.strip()
  84. if not task_name or not task_prompt or not lumi_first_line:
  85. raise ValueError("task_name, task_prompt, lumi_first_line 均不能为空")
  86. try:
  87. async with db_manager.get_session() as session:
  88. row = LlmTask(
  89. task_name=task_name,
  90. task_prompt=task_prompt,
  91. lumi_first_line=lumi_first_line,
  92. )
  93. session.add(row)
  94. await session.flush()
  95. await session.refresh(row)
  96. payload = self._row_to_dict(row)
  97. await session.commit()
  98. self.invalidate_cache()
  99. return payload
  100. except Exception as e:
  101. logger.warning("[LlmTask] create_task failed: %s", e)
  102. raise
  103. async def get_task_by_id(self, task_id: int) -> Optional[Dict[str, Any]]:
  104. try:
  105. async with db_manager.get_session() as session:
  106. row = await session.get(LlmTask, task_id)
  107. if not row:
  108. return None
  109. return self._row_to_dict(row)
  110. except Exception as e:
  111. logger.warning("[LlmTask] get_task_by_id failed: %s", e)
  112. raise
  113. async def update_task(
  114. self,
  115. task_id: int,
  116. task_name: str,
  117. task_prompt: str,
  118. lumi_first_line: str,
  119. ) -> Optional[Dict[str, Any]]:
  120. task_name = task_name.strip()
  121. task_prompt = task_prompt.strip()
  122. lumi_first_line = lumi_first_line.strip()
  123. if not task_name or not task_prompt or not lumi_first_line:
  124. raise ValueError("task_name, task_prompt, lumi_first_line 均不能为空")
  125. try:
  126. async with db_manager.get_session() as session:
  127. row = await session.get(LlmTask, task_id)
  128. if not row:
  129. return None
  130. row.task_name = task_name
  131. row.task_prompt = task_prompt
  132. row.lumi_first_line = lumi_first_line
  133. await session.flush()
  134. await session.refresh(row)
  135. payload = self._row_to_dict(row)
  136. await session.commit()
  137. self.invalidate_cache()
  138. return payload
  139. except Exception as e:
  140. logger.warning("[LlmTask] update_task failed: %s", e)
  141. raise
  142. async def delete_task(self, task_id: int) -> bool:
  143. try:
  144. async with db_manager.get_session() as session:
  145. row = await session.get(LlmTask, task_id)
  146. if not row:
  147. return False
  148. await session.delete(row)
  149. await session.commit()
  150. self.invalidate_cache()
  151. return True
  152. except Exception as e:
  153. logger.warning("[LlmTask] delete_task failed: %s", e)
  154. raise
  155. @staticmethod
  156. def _row_to_dict(row: LlmTask) -> Dict[str, Any]:
  157. return {
  158. "id": row.id,
  159. "task_name": row.task_name,
  160. "task_prompt": row.task_prompt,
  161. "lumi_first_line": row.lumi_first_line,
  162. "created_at": row.created_at.isoformat() if row.created_at else None,
  163. "updated_at": row.updated_at.isoformat() if row.updated_at else None,
  164. }
  165. def invalidate_cache(self) -> None:
  166. global _cached_task, _cached_at
  167. _cached_task = None
  168. _cached_at = 0.0
  169. llm_task_service = LlmTaskService()