agent.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. from typing import Annotated, Any
  2. from fastapi import APIRouter, Depends, Request, status
  3. from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
  4. from pydantic import BaseModel, Field
  5. from starlette.responses import StreamingResponse
  6. from app.api.responses import success_response
  7. from app.core.errors import AppError
  8. from app.domains.agent.service import AgentThreadService
  9. from app.domains.identity.models import AdminUser, H5User
  10. from app.domains.identity.service import AdminAuthService, H5AuthService
  11. agent_bearer_scheme = HTTPBearer(auto_error=False)
  12. class CreateThreadRequest(BaseModel):
  13. title: str | None = Field(default=None, max_length=128)
  14. class TextContent(BaseModel):
  15. type: str = Field(pattern="^text$")
  16. text: str = Field(min_length=1, max_length=4000)
  17. class SendMessageRequest(BaseModel):
  18. content: TextContent
  19. page_context: dict[str, Any] | None = None
  20. client_message_id: str = Field(min_length=1, max_length=64)
  21. def create_agent_router(
  22. thread_service: AgentThreadService,
  23. h5_auth_service: H5AuthService,
  24. admin_auth_service: AdminAuthService,
  25. ) -> APIRouter:
  26. router = APIRouter(tags=["agent"])
  27. def current_h5_user(
  28. credentials: Annotated[
  29. HTTPAuthorizationCredentials | None,
  30. Depends(agent_bearer_scheme),
  31. ],
  32. ) -> H5User:
  33. if credentials is None:
  34. raise AppError("AUTH_REQUIRED", "请先登录", 401)
  35. return h5_auth_service.authenticate_access(credentials.credentials)
  36. def current_admin(
  37. credentials: Annotated[
  38. HTTPAuthorizationCredentials | None,
  39. Depends(agent_bearer_scheme),
  40. ],
  41. ) -> AdminUser:
  42. if credentials is None:
  43. raise AppError("AUTH_REQUIRED", "请先登录后台", 401)
  44. return admin_auth_service.authenticate_access(credentials.credentials)
  45. @router.post("/agent/threads", status_code=status.HTTP_201_CREATED)
  46. def create_customer_thread(
  47. payload: CreateThreadRequest,
  48. request: Request,
  49. user: Annotated[H5User, Depends(current_h5_user)],
  50. ) -> dict[str, Any]:
  51. return success_response(
  52. request,
  53. thread_service.create_customer_thread(user, title=payload.title),
  54. )
  55. @router.post(
  56. "/agent/threads/{thread_id}/messages",
  57. status_code=status.HTTP_202_ACCEPTED,
  58. )
  59. def send_customer_message(
  60. thread_id: str,
  61. payload: SendMessageRequest,
  62. request: Request,
  63. user: Annotated[H5User, Depends(current_h5_user)],
  64. ) -> dict[str, Any]:
  65. return success_response(
  66. request,
  67. thread_service.send_customer_message(
  68. user,
  69. thread_id=thread_id,
  70. text=payload.content.text,
  71. ),
  72. )
  73. @router.post("/agent/threads/{thread_id}/messages/stream")
  74. def stream_customer_message(
  75. thread_id: str,
  76. payload: SendMessageRequest,
  77. user: Annotated[H5User, Depends(current_h5_user)],
  78. ) -> StreamingResponse:
  79. events = thread_service.stream_customer_message(
  80. user,
  81. thread_id=thread_id,
  82. text=payload.content.text,
  83. )
  84. return StreamingResponse(
  85. (event.to_sse() for event in events),
  86. media_type="text/event-stream",
  87. headers={
  88. "Cache-Control": "no-cache",
  89. "X-Accel-Buffering": "no",
  90. },
  91. )
  92. @router.get("/agent/threads/{thread_id}/messages")
  93. def list_customer_messages(
  94. thread_id: str,
  95. request: Request,
  96. user: Annotated[H5User, Depends(current_h5_user)],
  97. ) -> dict[str, Any]:
  98. return success_response(
  99. request,
  100. thread_service.list_customer_messages(user, thread_id=thread_id),
  101. )
  102. @router.get("/agent/runs/{run_id}")
  103. def get_customer_run(
  104. run_id: str,
  105. request: Request,
  106. user: Annotated[H5User, Depends(current_h5_user)],
  107. ) -> dict[str, Any]:
  108. return success_response(
  109. request,
  110. thread_service.get_customer_run(user, run_id=run_id),
  111. )
  112. @router.get("/agent/runs/{run_id}/events")
  113. def replay_customer_run_events(
  114. run_id: str,
  115. user: Annotated[H5User, Depends(current_h5_user)],
  116. ) -> StreamingResponse:
  117. events = thread_service.list_customer_run_events(user, run_id=run_id)
  118. return StreamingResponse(
  119. (event.to_sse() for event in events),
  120. media_type="text/event-stream",
  121. headers={"Cache-Control": "no-cache"},
  122. )
  123. @router.post(
  124. "/admin/agent/threads",
  125. status_code=status.HTTP_201_CREATED,
  126. )
  127. def create_operation_thread(
  128. payload: CreateThreadRequest,
  129. request: Request,
  130. user: Annotated[AdminUser, Depends(current_admin)],
  131. ) -> dict[str, Any]:
  132. return success_response(
  133. request,
  134. thread_service.create_operation_thread(user, title=payload.title),
  135. )
  136. @router.post(
  137. "/admin/agent/threads/{thread_id}/messages",
  138. status_code=status.HTTP_202_ACCEPTED,
  139. )
  140. def send_operation_message(
  141. thread_id: str,
  142. payload: SendMessageRequest,
  143. request: Request,
  144. user: Annotated[AdminUser, Depends(current_admin)],
  145. ) -> dict[str, Any]:
  146. return success_response(
  147. request,
  148. thread_service.send_operation_message(
  149. user,
  150. thread_id=thread_id,
  151. text=payload.content.text,
  152. ),
  153. )
  154. @router.get("/admin/agent/threads/{thread_id}/messages")
  155. def list_operation_messages(
  156. thread_id: str,
  157. request: Request,
  158. user: Annotated[AdminUser, Depends(current_admin)],
  159. ) -> dict[str, Any]:
  160. return success_response(
  161. request,
  162. thread_service.list_operation_messages(user, thread_id=thread_id),
  163. )
  164. @router.get("/admin/agent/runs/{run_id}")
  165. def get_operation_run(
  166. run_id: str,
  167. request: Request,
  168. user: Annotated[AdminUser, Depends(current_admin)],
  169. ) -> dict[str, Any]:
  170. return success_response(
  171. request,
  172. thread_service.get_operation_run(user, run_id=run_id),
  173. )
  174. @router.get("/admin/agent/runs")
  175. def list_operation_runs(
  176. request: Request,
  177. user: Annotated[AdminUser, Depends(current_admin)],
  178. limit: int = 20,
  179. ) -> dict[str, Any]:
  180. return success_response(
  181. request,
  182. thread_service.list_operation_runs(user, limit=min(max(limit, 1), 100)),
  183. )
  184. return router