routes.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. import json
  2. from core_domain import ServiceHealth
  3. from fastapi import APIRouter, Depends, HTTPException, Query
  4. from fastapi.responses import StreamingResponse
  5. from sqlalchemy import text
  6. from sqlalchemy.orm import Session
  7. from app.application.services import AgentApplicationService, build_agent_application_service
  8. from app.bootstrap.settings import AgentServiceSettings
  9. from app.db.session import get_db
  10. from app.schemas.agent import (
  11. AgentConfigCreateRequest,
  12. AgentConfigListRequest,
  13. AgentConfigResponse,
  14. AgentCreateRequest,
  15. AgentDeleteRequest,
  16. AgentDetailRequest,
  17. AgentListRequest,
  18. AgentResponse,
  19. AgentRunCreateRequest,
  20. AgentRunDetailRequest,
  21. AgentRunExecutePostRequest,
  22. AgentRunExecuteRequest,
  23. AgentRunExecuteResponse,
  24. AgentRunListRequest,
  25. AgentRunResponse,
  26. AgentRunStatusPostRequest,
  27. AgentRunStatusUpdateRequest,
  28. AgentStatusUpdateRequest,
  29. AgentStatusPostRequest,
  30. AgentToolInvocationListRequest,
  31. AgentUpdateRequest,
  32. AgentToolInvocationResponse,
  33. AgentWorkerExecuteNextRequest,
  34. AgentWorkerExecuteNextResponse,
  35. DeleteData,
  36. )
  37. router = APIRouter()
  38. def json_dump(payload: dict[str, object]) -> str:
  39. return json.dumps(payload, ensure_ascii=False, default=str)
  40. def get_agent_service_settings() -> AgentServiceSettings:
  41. return AgentServiceSettings()
  42. def get_agent_application_service(
  43. db: Session = Depends(get_db),
  44. settings: AgentServiceSettings = Depends(get_agent_service_settings)) -> AgentApplicationService:
  45. return build_agent_application_service(db=db, settings=settings)
  46. @router.get("/health", response_model=ServiceHealth)
  47. def health_check(db: Session = Depends(get_db)) -> ServiceHealth:
  48. db.execute(text("SELECT 1"))
  49. return ServiceHealth(service="agent-service", status="ok", database="ok")
  50. @router.post("", response_model=AgentResponse)
  51. def create_agent(
  52. payload: AgentCreateRequest,
  53. service: AgentApplicationService = Depends(get_agent_application_service)) -> AgentResponse:
  54. entity = service.create_agent(payload)
  55. return AgentResponse.from_entity(entity)
  56. @router.get("", response_model=list[AgentResponse])
  57. def list_agents(
  58. service: AgentApplicationService = Depends(get_agent_application_service)) -> list[AgentResponse]:
  59. return [AgentResponse.from_entity(item) for item in service.list_agents()]
  60. @router.post("/list", response_model=list[AgentResponse])
  61. def list_agents_post(
  62. payload: AgentListRequest,
  63. service: AgentApplicationService = Depends(get_agent_application_service)) -> list[AgentResponse]:
  64. return [AgentResponse.from_entity(item) for item in service.list_agents()]
  65. @router.post("/detail", response_model=AgentResponse)
  66. def detail_agent(
  67. payload: AgentDetailRequest,
  68. service: AgentApplicationService = Depends(get_agent_application_service)) -> AgentResponse:
  69. entity = service.get_agent(agent_id=payload.agent_id)
  70. if entity is None:
  71. raise HTTPException(status_code=404, detail=f"agent not found: {payload.agent_id}")
  72. return AgentResponse.from_entity(entity)
  73. @router.post("/update", response_model=AgentResponse)
  74. def update_agent(
  75. payload: AgentUpdateRequest,
  76. service: AgentApplicationService = Depends(get_agent_application_service)) -> AgentResponse:
  77. entity = service.update_agent(payload)
  78. if entity is None:
  79. raise HTTPException(status_code=404, detail=f"agent not found: {payload.agent_id}")
  80. return AgentResponse.from_entity(entity)
  81. @router.patch("/{agent_id}/status", response_model=AgentResponse)
  82. def update_agent_status(
  83. agent_id: str,
  84. payload: AgentStatusUpdateRequest,
  85. service: AgentApplicationService = Depends(get_agent_application_service)) -> AgentResponse:
  86. entity = service.update_agent_status(agent_id=agent_id, payload=payload)
  87. if entity is None:
  88. raise HTTPException(status_code=404, detail=f"agent not found: {agent_id}")
  89. return AgentResponse.from_entity(entity)
  90. @router.post("/status", response_model=AgentResponse)
  91. def update_agent_status_post(
  92. payload: AgentStatusPostRequest,
  93. service: AgentApplicationService = Depends(get_agent_application_service)) -> AgentResponse:
  94. entity = service.update_agent_status(
  95. agent_id=payload.agent_id,
  96. payload=AgentStatusUpdateRequest(status=payload.status))
  97. if entity is None:
  98. raise HTTPException(status_code=404, detail=f"agent not found: {payload.agent_id}")
  99. return AgentResponse.from_entity(entity)
  100. @router.post("/delete", response_model=DeleteData)
  101. def delete_agent_post(
  102. payload: AgentDeleteRequest,
  103. service: AgentApplicationService = Depends(get_agent_application_service)) -> DeleteData:
  104. return DeleteData(
  105. deleted=service.delete_agent(agent_id=payload.agent_id),
  106. agent_id=payload.agent_id)
  107. @router.post("/configs/create", response_model=AgentConfigResponse)
  108. def create_agent_config(
  109. payload: AgentConfigCreateRequest,
  110. service: AgentApplicationService = Depends(get_agent_application_service)) -> AgentConfigResponse:
  111. try:
  112. entity = service.create_agent_config(payload)
  113. except ValueError as exc:
  114. raise HTTPException(status_code=422, detail=str(exc)) from exc
  115. return AgentConfigResponse.from_entity(entity)
  116. @router.post("/configs/list", response_model=list[AgentConfigResponse])
  117. def list_agent_configs(
  118. payload: AgentConfigListRequest,
  119. service: AgentApplicationService = Depends(get_agent_application_service)) -> list[AgentConfigResponse]:
  120. return [
  121. AgentConfigResponse.from_entity(item)
  122. for item in service.list_agent_configs(agent_id=payload.agent_id)
  123. ]
  124. @router.post("/runs", response_model=AgentRunResponse)
  125. def create_agent_run(
  126. payload: AgentRunCreateRequest,
  127. service: AgentApplicationService = Depends(get_agent_application_service)) -> AgentRunResponse:
  128. try:
  129. entity = service.create_agent_run(payload)
  130. except ValueError as exc:
  131. raise HTTPException(status_code=422, detail=str(exc)) from exc
  132. return AgentRunResponse.from_entity(entity)
  133. @router.get("/runs", response_model=list[AgentRunResponse])
  134. def list_agent_runs(
  135. agent_id: str | None = Query(default=None),
  136. session_id: str | None = Query(default=None),
  137. service: AgentApplicationService = Depends(get_agent_application_service)) -> list[AgentRunResponse]:
  138. return [
  139. AgentRunResponse.from_entity(item)
  140. for item in service.list_agent_runs(
  141. agent_id=agent_id,
  142. session_id=session_id)
  143. ]
  144. @router.post("/runs/list", response_model=list[AgentRunResponse])
  145. def list_agent_runs_post(
  146. payload: AgentRunListRequest,
  147. service: AgentApplicationService = Depends(get_agent_application_service)) -> list[AgentRunResponse]:
  148. return [
  149. AgentRunResponse.from_entity(item)
  150. for item in service.list_agent_runs(
  151. agent_id=payload.agent_id,
  152. session_id=payload.session_id)
  153. ]
  154. @router.post("/runs/detail", response_model=AgentRunResponse)
  155. def get_agent_run(
  156. payload: AgentRunDetailRequest,
  157. service: AgentApplicationService = Depends(get_agent_application_service)) -> AgentRunResponse:
  158. entity = service.get_agent_run(payload)
  159. if entity is None:
  160. raise HTTPException(status_code=404, detail=f"agent_run not found: {payload.agent_run_id}")
  161. return AgentRunResponse.from_entity(entity)
  162. @router.get(
  163. "/runs/{agent_run_id}/tool-invocations",
  164. response_model=list[AgentToolInvocationResponse])
  165. def list_agent_tool_invocations(
  166. agent_run_id: str,
  167. service: AgentApplicationService = Depends(get_agent_application_service)) -> list[AgentToolInvocationResponse]:
  168. return [
  169. AgentToolInvocationResponse.from_entity(item)
  170. for item in service.list_agent_tool_invocations(
  171. agent_run_id=agent_run_id)
  172. ]
  173. @router.post(
  174. "/runs/tool-invocations/list",
  175. response_model=list[AgentToolInvocationResponse])
  176. def list_agent_tool_invocations_post(
  177. payload: AgentToolInvocationListRequest,
  178. service: AgentApplicationService = Depends(get_agent_application_service)) -> list[AgentToolInvocationResponse]:
  179. return [
  180. AgentToolInvocationResponse.from_entity(item)
  181. for item in service.list_agent_tool_invocations(
  182. agent_run_id=payload.agent_run_id)
  183. ]
  184. @router.post("/runs/{agent_run_id}/status", response_model=AgentRunResponse)
  185. def update_agent_run_status(
  186. agent_run_id: str,
  187. payload: AgentRunStatusUpdateRequest,
  188. service: AgentApplicationService = Depends(get_agent_application_service)) -> AgentRunResponse:
  189. entity = service.update_agent_run_status(agent_run_id=agent_run_id, payload=payload)
  190. if entity is None:
  191. raise HTTPException(status_code=404, detail=f"agent_run not found: {agent_run_id}")
  192. return AgentRunResponse.from_entity(entity)
  193. @router.post("/runs/status", response_model=AgentRunResponse)
  194. def update_agent_run_status_post(
  195. payload: AgentRunStatusPostRequest,
  196. service: AgentApplicationService = Depends(get_agent_application_service)) -> AgentRunResponse:
  197. entity = service.update_agent_run_status(
  198. agent_run_id=payload.agent_run_id,
  199. payload=AgentRunStatusUpdateRequest(
  200. status=payload.status,
  201. worker_key=payload.worker_key,
  202. output_text=payload.output_text,
  203. output_json=payload.output_json,
  204. error_code=payload.error_code,
  205. error_message=payload.error_message))
  206. if entity is None:
  207. raise HTTPException(status_code=404, detail=f"agent_run not found: {payload.agent_run_id}")
  208. return AgentRunResponse.from_entity(entity)
  209. @router.post("/runs/{agent_run_id}/execute", response_model=AgentRunExecuteResponse)
  210. def execute_agent_run(
  211. agent_run_id: str,
  212. payload: AgentRunExecuteRequest,
  213. service: AgentApplicationService = Depends(get_agent_application_service)) -> AgentRunExecuteResponse:
  214. entity = service.execute_agent_run(agent_run_id=agent_run_id, payload=payload)
  215. if entity is None:
  216. raise HTTPException(status_code=404, detail=f"agent_run not found: {agent_run_id}")
  217. output_json = entity.output_json or {}
  218. model_value = output_json.get("model")
  219. dry_run_value = output_json.get("dry_run")
  220. return AgentRunExecuteResponse(
  221. run=AgentRunResponse.from_entity(entity),
  222. model=model_value if isinstance(model_value, str) else None,
  223. dry_run=dry_run_value if isinstance(dry_run_value, bool) else False)
  224. @router.post("/runs/{agent_run_id}/execute-stream")
  225. def execute_agent_run_stream(
  226. agent_run_id: str,
  227. payload: AgentRunExecuteRequest,
  228. service: AgentApplicationService = Depends(get_agent_application_service)) -> StreamingResponse:
  229. if service.get_agent_run(AgentRunDetailRequest(agent_run_id=agent_run_id)) is None:
  230. raise HTTPException(status_code=404, detail=f"agent_run not found: {agent_run_id}")
  231. def events():
  232. for item in service.execute_agent_run_stream(agent_run_id=agent_run_id, payload=payload):
  233. event = item.get("event")
  234. event_name = event if isinstance(event, str) else "message"
  235. data = {key: value for key, value in item.items() if key != "event"}
  236. yield f"event: {event_name}\ndata: {json_dump(data)}\n\n"
  237. return StreamingResponse(
  238. events(),
  239. media_type="text/event-stream",
  240. headers=_sse_headers())
  241. def _sse_headers() -> dict[str, str]:
  242. return {
  243. "Cache-Control": "no-cache",
  244. "X-Accel-Buffering": "no",
  245. }
  246. @router.post("/runs/execute", response_model=AgentRunExecuteResponse)
  247. def execute_agent_run_post(
  248. payload: AgentRunExecutePostRequest,
  249. service: AgentApplicationService = Depends(get_agent_application_service)) -> AgentRunExecuteResponse:
  250. entity = service.execute_agent_run(
  251. agent_run_id=payload.agent_run_id,
  252. payload=AgentRunExecuteRequest(
  253. worker_key=payload.worker_key,
  254. dry_run=payload.dry_run))
  255. if entity is None:
  256. raise HTTPException(status_code=404, detail=f"agent_run not found: {payload.agent_run_id}")
  257. output_json = entity.output_json or {}
  258. model_value = output_json.get("model")
  259. dry_run_value = output_json.get("dry_run")
  260. return AgentRunExecuteResponse(
  261. run=AgentRunResponse.from_entity(entity),
  262. model=model_value if isinstance(model_value, str) else None,
  263. dry_run=dry_run_value if isinstance(dry_run_value, bool) else False)
  264. @router.post("/workers/execute-next", response_model=AgentWorkerExecuteNextResponse)
  265. def execute_next_worker_task(
  266. payload: AgentWorkerExecuteNextRequest,
  267. settings: AgentServiceSettings = Depends(get_agent_service_settings),
  268. service: AgentApplicationService = Depends(get_agent_application_service)) -> AgentWorkerExecuteNextResponse:
  269. result = service.execute_next_claimed_agent_run(
  270. worker_key=payload.worker_key,
  271. lease_seconds=payload.lease_seconds or settings.worker_lease_seconds,
  272. dry_run=payload.dry_run if payload.dry_run is not None else settings.worker_dry_run)
  273. if result is None:
  274. raise HTTPException(status_code=404, detail="queued agent_run not found")
  275. entity, released_lease_count = result
  276. output_json = entity.output_json or {}
  277. model_value = output_json.get("model")
  278. dry_run_value = output_json.get("dry_run")
  279. return AgentWorkerExecuteNextResponse(
  280. run=AgentRunResponse.from_entity(entity),
  281. model=model_value if isinstance(model_value, str) else None,
  282. dry_run=dry_run_value if isinstance(dry_run_value, bool) else False,
  283. released_lease_count=released_lease_count)