Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

conversation.py 10KB

vor 2 Jahren
vor 2 Jahren
vor 2 Jahren
vor 2 Jahren
vor 2 Jahren
vor 2 Jahren
vor 2 Jahren
vor 2 Jahren
vor 2 Jahren
vor 2 Jahren
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. from flask_restx import Resource, reqparse
  2. from flask_restx._http import HTTPStatus
  3. from flask_restx.inputs import int_range
  4. from sqlalchemy.orm import Session
  5. from werkzeug.exceptions import BadRequest, NotFound
  6. import services
  7. from controllers.service_api import service_api_ns
  8. from controllers.service_api.app.error import NotChatAppError
  9. from controllers.service_api.wraps import FetchUserArg, WhereisUserArg, validate_app_token
  10. from core.app.entities.app_invoke_entities import InvokeFrom
  11. from extensions.ext_database import db
  12. from fields.conversation_fields import (
  13. build_conversation_delete_model,
  14. build_conversation_infinite_scroll_pagination_model,
  15. build_simple_conversation_model,
  16. )
  17. from fields.conversation_variable_fields import (
  18. build_conversation_variable_infinite_scroll_pagination_model,
  19. build_conversation_variable_model,
  20. )
  21. from libs.helper import uuid_value
  22. from models.model import App, AppMode, EndUser
  23. from services.conversation_service import ConversationService
  24. # Define parsers for conversation APIs
  25. conversation_list_parser = reqparse.RequestParser()
  26. conversation_list_parser.add_argument(
  27. "last_id", type=uuid_value, location="args", help="Last conversation ID for pagination"
  28. )
  29. conversation_list_parser.add_argument(
  30. "limit",
  31. type=int_range(1, 100),
  32. required=False,
  33. default=20,
  34. location="args",
  35. help="Number of conversations to return",
  36. )
  37. conversation_list_parser.add_argument(
  38. "sort_by",
  39. type=str,
  40. choices=["created_at", "-created_at", "updated_at", "-updated_at"],
  41. required=False,
  42. default="-updated_at",
  43. location="args",
  44. help="Sort order for conversations",
  45. )
  46. conversation_rename_parser = reqparse.RequestParser()
  47. conversation_rename_parser.add_argument("name", type=str, required=False, location="json", help="New conversation name")
  48. conversation_rename_parser.add_argument(
  49. "auto_generate", type=bool, required=False, default=False, location="json", help="Auto-generate conversation name"
  50. )
  51. conversation_variables_parser = reqparse.RequestParser()
  52. conversation_variables_parser.add_argument(
  53. "last_id", type=uuid_value, location="args", help="Last variable ID for pagination"
  54. )
  55. conversation_variables_parser.add_argument(
  56. "limit", type=int_range(1, 100), required=False, default=20, location="args", help="Number of variables to return"
  57. )
  58. conversation_variable_update_parser = reqparse.RequestParser()
  59. # using lambda is for passing the already-typed value without modification
  60. # if no lambda, it will be converted to string
  61. # the string cannot be converted using json.loads
  62. conversation_variable_update_parser.add_argument(
  63. "value", required=True, location="json", type=lambda x: x, help="New value for the conversation variable"
  64. )
  65. @service_api_ns.route("/conversations")
  66. class ConversationApi(Resource):
  67. @service_api_ns.expect(conversation_list_parser)
  68. @service_api_ns.doc("list_conversations")
  69. @service_api_ns.doc(description="List all conversations for the current user")
  70. @service_api_ns.doc(
  71. responses={
  72. 200: "Conversations retrieved successfully",
  73. 401: "Unauthorized - invalid API token",
  74. 404: "Last conversation not found",
  75. }
  76. )
  77. @validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.QUERY))
  78. @service_api_ns.marshal_with(build_conversation_infinite_scroll_pagination_model(service_api_ns))
  79. def get(self, app_model: App, end_user: EndUser):
  80. """List all conversations for the current user.
  81. Supports pagination using last_id and limit parameters.
  82. """
  83. app_mode = AppMode.value_of(app_model.mode)
  84. if app_mode not in {AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT}:
  85. raise NotChatAppError()
  86. args = conversation_list_parser.parse_args()
  87. try:
  88. with Session(db.engine) as session:
  89. return ConversationService.pagination_by_last_id(
  90. session=session,
  91. app_model=app_model,
  92. user=end_user,
  93. last_id=args["last_id"],
  94. limit=args["limit"],
  95. invoke_from=InvokeFrom.SERVICE_API,
  96. sort_by=args["sort_by"],
  97. )
  98. except services.errors.conversation.LastConversationNotExistsError:
  99. raise NotFound("Last Conversation Not Exists.")
  100. @service_api_ns.route("/conversations/<uuid:c_id>")
  101. class ConversationDetailApi(Resource):
  102. @service_api_ns.doc("delete_conversation")
  103. @service_api_ns.doc(description="Delete a specific conversation")
  104. @service_api_ns.doc(params={"c_id": "Conversation ID"})
  105. @service_api_ns.doc(
  106. responses={
  107. 204: "Conversation deleted successfully",
  108. 401: "Unauthorized - invalid API token",
  109. 404: "Conversation not found",
  110. }
  111. )
  112. @validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.JSON))
  113. @service_api_ns.marshal_with(build_conversation_delete_model(service_api_ns), code=HTTPStatus.NO_CONTENT)
  114. def delete(self, app_model: App, end_user: EndUser, c_id):
  115. """Delete a specific conversation."""
  116. app_mode = AppMode.value_of(app_model.mode)
  117. if app_mode not in {AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT}:
  118. raise NotChatAppError()
  119. conversation_id = str(c_id)
  120. try:
  121. ConversationService.delete(app_model, conversation_id, end_user)
  122. except services.errors.conversation.ConversationNotExistsError:
  123. raise NotFound("Conversation Not Exists.")
  124. return {"result": "success"}, 204
  125. @service_api_ns.route("/conversations/<uuid:c_id>/name")
  126. class ConversationRenameApi(Resource):
  127. @service_api_ns.expect(conversation_rename_parser)
  128. @service_api_ns.doc("rename_conversation")
  129. @service_api_ns.doc(description="Rename a conversation or auto-generate a name")
  130. @service_api_ns.doc(params={"c_id": "Conversation ID"})
  131. @service_api_ns.doc(
  132. responses={
  133. 200: "Conversation renamed successfully",
  134. 401: "Unauthorized - invalid API token",
  135. 404: "Conversation not found",
  136. }
  137. )
  138. @validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.JSON))
  139. @service_api_ns.marshal_with(build_simple_conversation_model(service_api_ns))
  140. def post(self, app_model: App, end_user: EndUser, c_id):
  141. """Rename a conversation or auto-generate a name."""
  142. app_mode = AppMode.value_of(app_model.mode)
  143. if app_mode not in {AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT}:
  144. raise NotChatAppError()
  145. conversation_id = str(c_id)
  146. args = conversation_rename_parser.parse_args()
  147. try:
  148. return ConversationService.rename(app_model, conversation_id, end_user, args["name"], args["auto_generate"])
  149. except services.errors.conversation.ConversationNotExistsError:
  150. raise NotFound("Conversation Not Exists.")
  151. @service_api_ns.route("/conversations/<uuid:c_id>/variables")
  152. class ConversationVariablesApi(Resource):
  153. @service_api_ns.expect(conversation_variables_parser)
  154. @service_api_ns.doc("list_conversation_variables")
  155. @service_api_ns.doc(description="List all variables for a conversation")
  156. @service_api_ns.doc(params={"c_id": "Conversation ID"})
  157. @service_api_ns.doc(
  158. responses={
  159. 200: "Variables retrieved successfully",
  160. 401: "Unauthorized - invalid API token",
  161. 404: "Conversation not found",
  162. }
  163. )
  164. @validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.QUERY))
  165. @service_api_ns.marshal_with(build_conversation_variable_infinite_scroll_pagination_model(service_api_ns))
  166. def get(self, app_model: App, end_user: EndUser, c_id):
  167. """List all variables for a conversation.
  168. Conversational variables are only available for chat applications.
  169. """
  170. # conversational variable only for chat app
  171. app_mode = AppMode.value_of(app_model.mode)
  172. if app_mode not in {AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT}:
  173. raise NotChatAppError()
  174. conversation_id = str(c_id)
  175. args = conversation_variables_parser.parse_args()
  176. try:
  177. return ConversationService.get_conversational_variable(
  178. app_model, conversation_id, end_user, args["limit"], args["last_id"]
  179. )
  180. except services.errors.conversation.ConversationNotExistsError:
  181. raise NotFound("Conversation Not Exists.")
  182. @service_api_ns.route("/conversations/<uuid:c_id>/variables/<uuid:variable_id>")
  183. class ConversationVariableDetailApi(Resource):
  184. @service_api_ns.expect(conversation_variable_update_parser)
  185. @service_api_ns.doc("update_conversation_variable")
  186. @service_api_ns.doc(description="Update a conversation variable's value")
  187. @service_api_ns.doc(params={"c_id": "Conversation ID", "variable_id": "Variable ID"})
  188. @service_api_ns.doc(
  189. responses={
  190. 200: "Variable updated successfully",
  191. 400: "Bad request - type mismatch",
  192. 401: "Unauthorized - invalid API token",
  193. 404: "Conversation or variable not found",
  194. }
  195. )
  196. @validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.JSON))
  197. @service_api_ns.marshal_with(build_conversation_variable_model(service_api_ns))
  198. def put(self, app_model: App, end_user: EndUser, c_id, variable_id):
  199. """Update a conversation variable's value.
  200. Allows updating the value of a specific conversation variable.
  201. The value must match the variable's expected type.
  202. """
  203. app_mode = AppMode.value_of(app_model.mode)
  204. if app_mode not in {AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT}:
  205. raise NotChatAppError()
  206. conversation_id = str(c_id)
  207. variable_id = str(variable_id)
  208. args = conversation_variable_update_parser.parse_args()
  209. try:
  210. return ConversationService.update_conversation_variable(
  211. app_model, conversation_id, variable_id, end_user, args["value"]
  212. )
  213. except services.errors.conversation.ConversationNotExistsError:
  214. raise NotFound("Conversation Not Exists.")
  215. except services.errors.conversation.ConversationVariableNotExistsError:
  216. raise NotFound("Conversation Variable Not Exists.")
  217. except services.errors.conversation.ConversationVariableTypeMismatchError as e:
  218. raise BadRequest(str(e))