workflows.py 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. from __future__ import annotations
  2. import json
  3. from datetime import datetime, timezone
  4. from typing import Any
  5. from sqlalchemy import select
  6. from sqlalchemy.orm import Session
  7. from app.agents.context import build_knowledge_context, build_mine_context
  8. from app.agents.prompts import (
  9. DIAGNOSIS_PROMPT,
  10. DOCUMENT_ANALYSIS_PROMPT,
  11. EVALUATION_PROMPT,
  12. KNOWLEDGE_QA_PROMPT,
  13. )
  14. from app.agents.runtime import get_agent_runtime
  15. from app.core.errors import APIError
  16. from app.db.base import new_id
  17. from app.db.models.agent import AgentRun, AgentSession
  18. from app.db.repositories import document as document_repo
  19. from app.schemas.diagnosis import DiagnosisRunRequest
  20. from app.schemas.evaluation import EvaluationRunRequest
  21. from app.schemas.work_order import WorkOrderCreate
  22. from app.services.diagnosis_service import run_diagnosis
  23. from app.services.evaluation_service import run_evaluation
  24. from app.services.work_order_service import create_work_order
  25. def _json_context(payload: dict[str, Any], limit: int = 14000) -> str:
  26. text = json.dumps(payload, ensure_ascii=False, default=str, indent=2)
  27. return text[:limit]
  28. def _fallback_output(workflow: str, context: dict[str, Any], error: str = "") -> str:
  29. if workflow == "diagnosis":
  30. high = context.get("result", {}).get("high_risk_count", 0)
  31. return f"规则引擎已完成诊断,识别高风险缺陷 {high} 项。请按证据链逐项复核并生成整改任务。"
  32. if workflow == "evaluation":
  33. result = context.get("result", {})
  34. return (
  35. f"规则引擎已完成评价,综合得分 {result.get('total_score', '--')},"
  36. f"等级 {result.get('grade', '--')}。建议按评价结果中的短板指标开展专项治理。"
  37. )
  38. if workflow == "knowledge_qa":
  39. count = context.get("knowledge", {}).get("total", 0)
  40. return f"检索到 {count} 条相关知识,请结合引用结果进行人工复核。"
  41. if workflow == "document_analysis":
  42. return "资料解析结果已生成,请对关键字段和缺失项进行人工确认。"
  43. return "工作流已执行。"
  44. def _get_or_create_session(
  45. db: Session,
  46. *,
  47. mine_id: str | None,
  48. thread_id: str | None,
  49. workflow: str,
  50. user_name: str,
  51. ) -> AgentSession:
  52. if thread_id:
  53. session = db.scalar(select(AgentSession).where(AgentSession.thread_id == thread_id))
  54. if session is not None:
  55. return session
  56. session = AgentSession(
  57. id=new_id(),
  58. mine_id=mine_id,
  59. user_name=user_name,
  60. thread_id=thread_id or new_id(),
  61. title=f"{workflow} 工作流",
  62. workflow=workflow,
  63. status="active",
  64. context={},
  65. last_message_at=datetime.now(timezone.utc),
  66. )
  67. db.add(session)
  68. db.commit()
  69. db.refresh(session)
  70. return session
  71. async def run_workflow(
  72. db: Session,
  73. *,
  74. mine_id: str,
  75. workflow: str,
  76. payload: dict[str, Any],
  77. requested_by: str = "",
  78. ) -> dict[str, Any]:
  79. session = _get_or_create_session(
  80. db,
  81. mine_id=mine_id,
  82. thread_id=payload.get("thread_id"),
  83. workflow=workflow,
  84. user_name=requested_by or "default",
  85. )
  86. run = AgentRun(
  87. id=new_id(),
  88. session_id=session.id,
  89. run_id=new_id(),
  90. workflow=workflow,
  91. status="running",
  92. input_payload=payload,
  93. output_payload={},
  94. model_name="",
  95. started_at=datetime.now(timezone.utc),
  96. )
  97. db.add(run)
  98. db.commit()
  99. db.refresh(run)
  100. context: dict[str, Any]
  101. prompt: str
  102. if workflow == "diagnosis":
  103. task = run_diagnosis(
  104. db,
  105. DiagnosisRunRequest(
  106. mine_id=mine_id,
  107. scene=payload.get("scene", "all"),
  108. context=payload.get("context", {}),
  109. requested_by=requested_by,
  110. ),
  111. )
  112. from app.db.repositories import diagnosis as diagnosis_repo
  113. results = diagnosis_repo.list_results(db, task.id)
  114. context = {
  115. "mine": build_mine_context(db, mine_id),
  116. "result": {
  117. "task_no": task.task_no,
  118. "scene": task.scene,
  119. "defects": [
  120. {
  121. "name": item.defect_name,
  122. "scene": item.scene,
  123. "severity": item.severity,
  124. "confidence": item.confidence,
  125. "conclusion": item.conclusion,
  126. "recommendation": item.recommendation,
  127. }
  128. for item in results
  129. ],
  130. "high_risk_count": sum(1 for item in results if item.severity == "high"),
  131. },
  132. }
  133. prompt = f"{DIAGNOSIS_PROMPT}\n\n业务上下文:\n{_json_context(context)}"
  134. elif workflow == "evaluation":
  135. evaluation = run_evaluation(
  136. db,
  137. EvaluationRunRequest(
  138. mine_id=mine_id,
  139. context=payload.get("context", {}),
  140. requested_by=requested_by,
  141. ),
  142. )
  143. context = {
  144. "mine": build_mine_context(db, mine_id),
  145. "result": {
  146. "run_no": evaluation.run_no,
  147. "total_score": evaluation.total_score,
  148. "grade": evaluation.grade,
  149. "summary": evaluation.summary,
  150. "recommendations": evaluation.recommendations,
  151. "model_output": evaluation.model_output,
  152. },
  153. }
  154. prompt = f"{EVALUATION_PROMPT}\n\n业务上下文:\n{_json_context(context)}"
  155. elif workflow == "knowledge_qa":
  156. question = str(payload.get("question") or payload.get("message") or "").strip()
  157. if not question:
  158. raise APIError(422, "question_required", "知识问答必须提供 question")
  159. knowledge = build_knowledge_context(db, mine_id, question)
  160. context = {"question": question, "knowledge": knowledge}
  161. prompt = f"{KNOWLEDGE_QA_PROMPT}\n\n业务上下文:\n{_json_context(context)}"
  162. elif workflow == "document_analysis":
  163. document_id = str(payload.get("document_id") or "")
  164. if not document_id:
  165. raise APIError(422, "document_id_required", "资料分析必须提供 document_id")
  166. document = document_repo.get_document(db, document_id)
  167. if document is None:
  168. raise APIError(404, "document_not_found", "业务资料不存在")
  169. extractions = document_repo.list_extractions(db, document_id)
  170. context = {
  171. "document": {
  172. "id": document.id,
  173. "title": document.title,
  174. "category": document.category,
  175. "parse_status": document.parse_status,
  176. "summary": document.summary,
  177. },
  178. "fields": [
  179. {
  180. "name": item.field_name,
  181. "value": item.field_value,
  182. "normalized_value": item.normalized_value,
  183. "verified": item.verified,
  184. }
  185. for item in extractions
  186. ],
  187. }
  188. prompt = f"{DOCUMENT_ANALYSIS_PROMPT}\n\n业务上下文:\n{_json_context(context)}"
  189. else:
  190. raise APIError(422, "unsupported_workflow", f"不支持的工作流:{workflow}")
  191. runtime = get_agent_runtime()
  192. agent_result = await runtime.invoke(
  193. message=prompt,
  194. thread_id=session.thread_id,
  195. context=context,
  196. )
  197. output = agent_result.output or _fallback_output(workflow, context, agent_result.error)
  198. if workflow == "diagnosis" and context["result"]["defects"]:
  199. first = context["result"]["defects"][0]
  200. create_work_order(
  201. db,
  202. WorkOrderCreate(
  203. mine_id=mine_id,
  204. title=f"处置:{first['name']}",
  205. content=output,
  206. priority="high" if first["severity"] == "high" else "medium",
  207. assignee_name="",
  208. department="通防科",
  209. metadata={"source": "diagnosis_workflow"},
  210. ),
  211. )
  212. run.status = "success" if agent_result.status != "failed" else "degraded"
  213. run.output_payload = {
  214. "output": output,
  215. "agent_status": agent_result.status,
  216. "agent_error": agent_result.error,
  217. "context": context,
  218. }
  219. run.model_name = agent_result.model
  220. run.finished_at = datetime.now(timezone.utc)
  221. session.last_message_at = datetime.now(timezone.utc)
  222. db.add_all([run, session])
  223. db.commit()
  224. db.refresh(run)
  225. return {
  226. "run_id": run.run_id,
  227. "session_id": session.id,
  228. "workflow": workflow,
  229. "status": run.status,
  230. "output": output,
  231. "agent": runtime.describe(),
  232. "result": context.get("result", {}),
  233. }