|
|
@@ -61,7 +61,8 @@ app = FastAPI(
|
|
|
|
|
|
# 定时调度器(默认用中国时区,避免容器 UTC 导致错过预期时间点)
|
|
|
SCHEDULER_TIMEZONE = os.getenv("SCHEDULER_TIMEZONE", os.getenv("TZ", "Asia/Shanghai"))
|
|
|
-scheduler = AsyncIOScheduler(timezone=ZoneInfo(SCHEDULER_TIMEZONE))
|
|
|
+SCHEDULER_TZ = ZoneInfo(SCHEDULER_TIMEZONE)
|
|
|
+scheduler = AsyncIOScheduler(timezone=SCHEDULER_TZ)
|
|
|
|
|
|
# 并发控制
|
|
|
MAX_CONCURRENT_TASKS = int(os.getenv("MAX_CONCURRENT_TASKS", "1"))
|
|
|
@@ -85,6 +86,7 @@ stats = {
|
|
|
class TaskRequest(BaseModel):
|
|
|
query: Optional[str] = None
|
|
|
demand_id: Optional[int] = None
|
|
|
+ suggestion: Optional[str] = None
|
|
|
|
|
|
|
|
|
class TaskResponse(BaseModel):
|
|
|
@@ -107,6 +109,7 @@ def _update_scheduled_task_complete(demand_id: int, trace_id: str, status: int)
|
|
|
async def execute_task(
|
|
|
query: str,
|
|
|
demand_id: Optional[int] = None,
|
|
|
+ suggestion: str = "",
|
|
|
task_type: str = "api",
|
|
|
):
|
|
|
"""
|
|
|
@@ -115,13 +118,14 @@ async def execute_task(
|
|
|
Args:
|
|
|
query: 查询内容
|
|
|
demand_id: 需求 id(demand_content.id,关联 demand_content 表)
|
|
|
+ suggestion: 补充信息(定时任务与 demand_content.suggestion 一致)
|
|
|
task_type: 任务类型("api" 或 "scheduled")
|
|
|
"""
|
|
|
async with task_semaphore:
|
|
|
current_concurrent = MAX_CONCURRENT_TASKS - task_semaphore._value + 1
|
|
|
logger.info(f"任务开始 [{task_type}]: query={query[:50]}..., 当前并发={current_concurrent}/{MAX_CONCURRENT_TASKS}")
|
|
|
|
|
|
- start_time = datetime.now()
|
|
|
+ start_time = datetime.now(SCHEDULER_TZ)
|
|
|
stats["total_tasks"] += 1
|
|
|
if task_type == "scheduled":
|
|
|
stats["scheduled_tasks"] += 1
|
|
|
@@ -135,11 +139,15 @@ async def execute_task(
|
|
|
try:
|
|
|
result = await asyncio.wait_for(
|
|
|
core.run_agent(
|
|
|
- query, demand_id=demand_id, stream_output=False, log_assistant_text=True
|
|
|
+ query,
|
|
|
+ demand_id=demand_id,
|
|
|
+ suggestion=suggestion or None,
|
|
|
+ stream_output=False,
|
|
|
+ log_assistant_text=True,
|
|
|
),
|
|
|
timeout=float(TASK_TIMEOUT_SECONDS),
|
|
|
)
|
|
|
- duration = (datetime.now() - start_time).total_seconds()
|
|
|
+ duration = (datetime.now(SCHEDULER_TZ) - start_time).total_seconds()
|
|
|
|
|
|
if result["status"] == "completed":
|
|
|
stats["completed_tasks"] += 1
|
|
|
@@ -154,7 +162,7 @@ async def execute_task(
|
|
|
|
|
|
except asyncio.TimeoutError:
|
|
|
stats["failed_tasks"] += 1
|
|
|
- duration = (datetime.now() - start_time).total_seconds()
|
|
|
+ duration = (datetime.now(SCHEDULER_TZ) - start_time).total_seconds()
|
|
|
logger.error(
|
|
|
f"任务超时 [{task_type}]: 超过 {TASK_TIMEOUT_SECONDS}s,记为失败, 耗时={duration:.1f}s"
|
|
|
)
|
|
|
@@ -163,7 +171,7 @@ async def execute_task(
|
|
|
|
|
|
except Exception as e:
|
|
|
stats["failed_tasks"] += 1
|
|
|
- duration = (datetime.now() - start_time).total_seconds()
|
|
|
+ duration = (datetime.now(SCHEDULER_TZ) - start_time).total_seconds()
|
|
|
logger.error(f"任务异常 [{task_type}]: {e}, 耗时={duration:.1f}s", exc_info=True)
|
|
|
if task_type == "scheduled" and demand_id is not None:
|
|
|
_update_scheduled_task_complete(demand_id, "", STATUS_FAILED)
|
|
|
@@ -171,7 +179,7 @@ async def execute_task(
|
|
|
|
|
|
def _today_dt_int() -> int:
|
|
|
"""当天 demand_content.dt 约定为 YYYYMMDD 整数(如 20260402),与定时器时区一致。"""
|
|
|
- return int(datetime.now(ZoneInfo(SCHEDULER_TIMEZONE)).strftime("%Y%m%d"))
|
|
|
+ return int(datetime.now(SCHEDULER_TZ).strftime("%Y%m%d"))
|
|
|
|
|
|
|
|
|
def _has_running_content_task() -> bool:
|
|
|
@@ -202,6 +210,7 @@ async def scheduled_tick():
|
|
|
|
|
|
demand_content_id = item.get("demand_content_id")
|
|
|
query = (item.get("query") or "").strip()
|
|
|
+ suggestion = (item.get("suggestion") or "").strip()
|
|
|
if demand_content_id is None or not query:
|
|
|
logger.info("定时任务跳过:查询结果无效")
|
|
|
return
|
|
|
@@ -212,7 +221,12 @@ async def scheduled_tick():
|
|
|
f"dt={dt}, score={score}"
|
|
|
)
|
|
|
create_task_record(demand_content_id)
|
|
|
- await execute_task(query=query, demand_id=demand_content_id, task_type="scheduled")
|
|
|
+ await execute_task(
|
|
|
+ query=query,
|
|
|
+ demand_id=demand_content_id,
|
|
|
+ suggestion=suggestion,
|
|
|
+ task_type="scheduled",
|
|
|
+ )
|
|
|
|
|
|
|
|
|
async def run_startup_resume():
|
|
|
@@ -227,12 +241,18 @@ async def run_startup_resume():
|
|
|
|
|
|
demand_content_id = row.get("demand_content_id")
|
|
|
query = (row.get("query") or "").strip()
|
|
|
+ suggestion = (row.get("suggestion") or "").strip()
|
|
|
if demand_content_id is None or not query:
|
|
|
logger.warning("启动恢复:执行中任务数据不完整,跳过")
|
|
|
return
|
|
|
|
|
|
logger.info(f"启动恢复:执行 demand_find_task status=1, demand_content_id={demand_content_id}")
|
|
|
- await execute_task(query=query, demand_id=int(demand_content_id), task_type="scheduled")
|
|
|
+ await execute_task(
|
|
|
+ query=query,
|
|
|
+ demand_id=int(demand_content_id),
|
|
|
+ suggestion=suggestion,
|
|
|
+ task_type="scheduled",
|
|
|
+ )
|
|
|
except Exception as e:
|
|
|
logger.error(f"启动恢复失败: {e}", exc_info=True)
|
|
|
|
|
|
@@ -255,9 +275,10 @@ async def create_task(request: TaskRequest):
|
|
|
"message": "任务已启动,结果将保存到 .cache/traces/xxx/"
|
|
|
}
|
|
|
"""
|
|
|
- # 获取 query 和 demand_id
|
|
|
+ # 获取 query、demand_id、suggestion(API 显式传入;与库表字段同名便于对齐)
|
|
|
query = request.query or core.DEFAULT_QUERY
|
|
|
demand_id = request.demand_id
|
|
|
+ suggestion_str = (request.suggestion or "").strip()
|
|
|
|
|
|
# 用 Event 等待 trace_id
|
|
|
trace_id_ready = asyncio.Event()
|
|
|
@@ -279,7 +300,12 @@ async def create_task(request: TaskRequest):
|
|
|
prompt = SimplePrompt(prompt_path)
|
|
|
trace_dir = os.getenv("TRACE_DIR", ".cache/traces")
|
|
|
demand_id_str = str(demand_id) if demand_id is not None else ""
|
|
|
- messages = prompt.build_messages(query=query, trace_dir=trace_dir, demand_id=demand_id_str)
|
|
|
+ messages = prompt.build_messages(
|
|
|
+ query=query,
|
|
|
+ suggestion=suggestion_str,
|
|
|
+ trace_dir=trace_dir,
|
|
|
+ demand_id=demand_id_str,
|
|
|
+ )
|
|
|
|
|
|
api_key = os.getenv("OPEN_ROUTER_API_KEY")
|
|
|
model_name = prompt.config.get("model", "sonnet-4.6")
|
|
|
@@ -348,7 +374,7 @@ async def create_task(request: TaskRequest):
|
|
|
stats["failed_tasks"] += 1
|
|
|
logger.error(f"任务异常 [api]: {e}", exc_info=True)
|
|
|
if not trace_id_holder["id"]:
|
|
|
- trace_id_holder["id"] = f"error_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
|
|
|
+ trace_id_holder["id"] = f"error_{datetime.now(SCHEDULER_TZ).strftime('%Y%m%d_%H%M%S')}"
|
|
|
trace_id_ready.set()
|
|
|
|
|
|
# 启动后台任务
|