routes.py 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. from fastapi import APIRouter, Depends, HTTPException, Query
  2. from sqlalchemy import text
  3. from sqlalchemy.orm import Session
  4. from core_domain import ServiceHealth
  5. from app.application.services import AgentApplicationService, build_agent_application_service
  6. from app.bootstrap.settings import AgentServiceSettings
  7. from app.db.session import get_db
  8. from app.schemas.agent import (
  9. AgentCreateRequest,
  10. AgentResponse,
  11. AgentRunCreateRequest,
  12. AgentRunExecuteRequest,
  13. AgentRunExecuteResponse,
  14. AgentRunResponse,
  15. AgentRunStatusUpdateRequest,
  16. AgentStatusUpdateRequest,
  17. AgentToolInvocationResponse,
  18. AgentWorkerExecuteNextRequest,
  19. AgentWorkerExecuteNextResponse,
  20. AgentVersionCreateRequest,
  21. AgentVersionResponse,
  22. )
  23. router = APIRouter()
  24. def get_agent_service_settings() -> AgentServiceSettings:
  25. return AgentServiceSettings()
  26. def get_agent_application_service(
  27. db: Session = Depends(get_db),
  28. settings: AgentServiceSettings = Depends(get_agent_service_settings),
  29. ) -> AgentApplicationService:
  30. return build_agent_application_service(db=db, settings=settings)
  31. @router.get("/health", response_model=ServiceHealth)
  32. def health_check(db: Session = Depends(get_db)) -> ServiceHealth:
  33. db.execute(text("SELECT 1"))
  34. return ServiceHealth(service="agent-service", status="ok", database="ok")
  35. @router.post("", response_model=AgentResponse)
  36. def create_agent(
  37. payload: AgentCreateRequest,
  38. service: AgentApplicationService = Depends(get_agent_application_service),
  39. ) -> AgentResponse:
  40. entity = service.create_agent(payload)
  41. return AgentResponse.from_entity(entity)
  42. @router.get("", response_model=list[AgentResponse])
  43. def list_agents(
  44. tenant_id: str = Query(...),
  45. service: AgentApplicationService = Depends(get_agent_application_service),
  46. ) -> list[AgentResponse]:
  47. return [AgentResponse.from_entity(item) for item in service.list_agents(tenant_id=tenant_id)]
  48. @router.patch("/{agent_id}/status", response_model=AgentResponse)
  49. def update_agent_status(
  50. agent_id: str,
  51. payload: AgentStatusUpdateRequest,
  52. service: AgentApplicationService = Depends(get_agent_application_service),
  53. ) -> AgentResponse:
  54. entity = service.update_agent_status(agent_id=agent_id, payload=payload)
  55. if entity is None:
  56. raise HTTPException(status_code=404, detail=f"agent not found: {agent_id}")
  57. return AgentResponse.from_entity(entity)
  58. @router.post("/versions", response_model=AgentVersionResponse)
  59. def create_agent_version(
  60. payload: AgentVersionCreateRequest,
  61. service: AgentApplicationService = Depends(get_agent_application_service),
  62. ) -> AgentVersionResponse:
  63. try:
  64. entity = service.create_agent_version(payload)
  65. except ValueError as exc:
  66. raise HTTPException(status_code=422, detail=str(exc)) from exc
  67. return AgentVersionResponse.from_entity(entity)
  68. @router.get("/versions", response_model=list[AgentVersionResponse])
  69. def list_agent_versions(
  70. tenant_id: str = Query(...),
  71. agent_id: str = Query(...),
  72. service: AgentApplicationService = Depends(get_agent_application_service),
  73. ) -> list[AgentVersionResponse]:
  74. return [
  75. AgentVersionResponse.from_entity(item)
  76. for item in service.list_agent_versions(tenant_id=tenant_id, agent_id=agent_id)
  77. ]
  78. @router.post("/runs", response_model=AgentRunResponse)
  79. def create_agent_run(
  80. payload: AgentRunCreateRequest,
  81. service: AgentApplicationService = Depends(get_agent_application_service),
  82. ) -> AgentRunResponse:
  83. try:
  84. entity = service.create_agent_run(payload)
  85. except ValueError as exc:
  86. raise HTTPException(status_code=422, detail=str(exc)) from exc
  87. return AgentRunResponse.from_entity(entity)
  88. @router.get("/runs", response_model=list[AgentRunResponse])
  89. def list_agent_runs(
  90. tenant_id: str = Query(...),
  91. agent_id: str | None = Query(default=None),
  92. session_id: str | None = Query(default=None),
  93. service: AgentApplicationService = Depends(get_agent_application_service),
  94. ) -> list[AgentRunResponse]:
  95. return [
  96. AgentRunResponse.from_entity(item)
  97. for item in service.list_agent_runs(
  98. tenant_id=tenant_id,
  99. agent_id=agent_id,
  100. session_id=session_id,
  101. )
  102. ]
  103. @router.get(
  104. "/runs/{agent_run_id}/tool-invocations",
  105. response_model=list[AgentToolInvocationResponse],
  106. )
  107. def list_agent_tool_invocations(
  108. agent_run_id: str,
  109. tenant_id: str = Query(...),
  110. service: AgentApplicationService = Depends(get_agent_application_service),
  111. ) -> list[AgentToolInvocationResponse]:
  112. return [
  113. AgentToolInvocationResponse.from_entity(item)
  114. for item in service.list_agent_tool_invocations(
  115. tenant_id=tenant_id,
  116. agent_run_id=agent_run_id,
  117. )
  118. ]
  119. @router.post("/runs/{agent_run_id}/status", response_model=AgentRunResponse)
  120. def update_agent_run_status(
  121. agent_run_id: str,
  122. payload: AgentRunStatusUpdateRequest,
  123. service: AgentApplicationService = Depends(get_agent_application_service),
  124. ) -> AgentRunResponse:
  125. entity = service.update_agent_run_status(agent_run_id=agent_run_id, payload=payload)
  126. if entity is None:
  127. raise HTTPException(status_code=404, detail=f"agent_run not found: {agent_run_id}")
  128. return AgentRunResponse.from_entity(entity)
  129. @router.post("/runs/{agent_run_id}/execute", response_model=AgentRunExecuteResponse)
  130. def execute_agent_run(
  131. agent_run_id: str,
  132. payload: AgentRunExecuteRequest,
  133. service: AgentApplicationService = Depends(get_agent_application_service),
  134. ) -> AgentRunExecuteResponse:
  135. entity = service.execute_agent_run(agent_run_id=agent_run_id, payload=payload)
  136. if entity is None:
  137. raise HTTPException(status_code=404, detail=f"agent_run not found: {agent_run_id}")
  138. output_json = entity.output_json or {}
  139. model_value = output_json.get("model")
  140. dry_run_value = output_json.get("dry_run")
  141. return AgentRunExecuteResponse(
  142. run=AgentRunResponse.from_entity(entity),
  143. model=model_value if isinstance(model_value, str) else None,
  144. dry_run=dry_run_value if isinstance(dry_run_value, bool) else False,
  145. )
  146. @router.post("/workers/execute-next", response_model=AgentWorkerExecuteNextResponse)
  147. def execute_next_worker_task(
  148. payload: AgentWorkerExecuteNextRequest,
  149. settings: AgentServiceSettings = Depends(get_agent_service_settings),
  150. service: AgentApplicationService = Depends(get_agent_application_service),
  151. ) -> AgentWorkerExecuteNextResponse:
  152. result = service.execute_next_claimed_agent_run(
  153. worker_key=payload.worker_key,
  154. lease_seconds=payload.lease_seconds or settings.worker_lease_seconds,
  155. dry_run=payload.dry_run if payload.dry_run is not None else settings.worker_dry_run,
  156. )
  157. if result is None:
  158. raise HTTPException(status_code=404, detail="queued agent_run not found")
  159. entity, released_lease_count = result
  160. output_json = entity.output_json or {}
  161. model_value = output_json.get("model")
  162. dry_run_value = output_json.get("dry_run")
  163. return AgentWorkerExecuteNextResponse(
  164. run=AgentRunResponse.from_entity(entity),
  165. model=model_value if isinstance(model_value, str) else None,
  166. dry_run=dry_run_value if isinstance(dry_run_value, bool) else False,
  167. released_lease_count=released_lease_count,
  168. )