From 12836f9db9fb9da8eab85d414ddf0ec68f8f3434 Mon Sep 17 00:00:00 2001 From: Alex Chim <132866042+AlexChim1231@users.noreply.github.com> Date: Fri, 25 Apr 2025 11:52:25 +0800 Subject: [PATCH] Resolves #18536 Retreive conversation variables (#18581) --- .../service_api/app/conversation.py | 28 +++++ api/fields/conversation_variable_fields.py | 6 ++ api/services/conversation_service.py | 54 +++++++++- api/services/errors/conversation.py | 4 + .../template/template_advanced_chat.en.mdx | 100 ++++++++++++++++++ .../template/template_advanced_chat.ja.mdx | 100 ++++++++++++++++++ .../template/template_advanced_chat.zh.mdx | 100 ++++++++++++++++++ .../develop/template/template_chat.en.mdx | 100 ++++++++++++++++++ .../develop/template/template_chat.ja.mdx | 100 ++++++++++++++++++ .../develop/template/template_chat.zh.mdx | 100 ++++++++++++++++++ 10 files changed, 691 insertions(+), 1 deletion(-) diff --git a/api/controllers/service_api/app/conversation.py b/api/controllers/service_api/app/conversation.py index 334f2c5620..55600a3fd0 100644 --- a/api/controllers/service_api/app/conversation.py +++ b/api/controllers/service_api/app/conversation.py @@ -14,6 +14,9 @@ from fields.conversation_fields import ( conversation_infinite_scroll_pagination_fields, simple_conversation_fields, ) +from fields.conversation_variable_fields import ( + conversation_variable_infinite_scroll_pagination_fields, +) from libs.helper import uuid_value from models.model import App, AppMode, EndUser from services.conversation_service import ConversationService @@ -93,6 +96,31 @@ class ConversationRenameApi(Resource): raise NotFound("Conversation Not Exists.") +class ConversationVariablesApi(Resource): + @validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.QUERY)) + @marshal_with(conversation_variable_infinite_scroll_pagination_fields) + def get(self, app_model: App, end_user: EndUser, c_id): + # conversational variable only for chat app + app_mode = AppMode.value_of(app_model.mode) + if app_mode not in {AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT}: + raise NotChatAppError() + + conversation_id = str(c_id) + + parser = reqparse.RequestParser() + parser.add_argument("last_id", type=uuid_value, location="args") + parser.add_argument("limit", type=int_range(1, 100), required=False, default=20, location="args") + args = parser.parse_args() + + try: + return ConversationService.get_conversational_variable( + app_model, conversation_id, end_user, args["limit"], args["last_id"] + ) + except services.errors.conversation.ConversationNotExistsError: + raise NotFound("Conversation Not Exists.") + + api.add_resource(ConversationRenameApi, "/conversations//name", endpoint="conversation_name") api.add_resource(ConversationApi, "/conversations") api.add_resource(ConversationDetailApi, "/conversations/", endpoint="conversation_detail") +api.add_resource(ConversationVariablesApi, "/conversations//variables", endpoint="conversation_variables") diff --git a/api/fields/conversation_variable_fields.py b/api/fields/conversation_variable_fields.py index c6385efb5a..3aa3838def 100644 --- a/api/fields/conversation_variable_fields.py +++ b/api/fields/conversation_variable_fields.py @@ -19,3 +19,9 @@ paginated_conversation_variable_fields = { "has_more": fields.Boolean, "data": fields.List(fields.Nested(conversation_variable_fields), attribute="data"), } + +conversation_variable_infinite_scroll_pagination_fields = { + "limit": fields.Integer, + "has_more": fields.Boolean, + "data": fields.List(fields.Nested(conversation_variable_fields)), +} diff --git a/api/services/conversation_service.py b/api/services/conversation_service.py index 6485cbf37d..afdaa49465 100644 --- a/api/services/conversation_service.py +++ b/api/services/conversation_service.py @@ -9,9 +9,14 @@ from core.app.entities.app_invoke_entities import InvokeFrom from core.llm_generator.llm_generator import LLMGenerator from extensions.ext_database import db from libs.infinite_scroll_pagination import InfiniteScrollPagination +from models import ConversationVariable from models.account import Account from models.model import App, Conversation, EndUser, Message -from services.errors.conversation import ConversationNotExistsError, LastConversationNotExistsError +from services.errors.conversation import ( + ConversationNotExistsError, + ConversationVariableNotExistsError, + LastConversationNotExistsError, +) from services.errors.message import MessageNotExistsError @@ -166,3 +171,50 @@ class ConversationService: conversation.is_deleted = True conversation.updated_at = datetime.now(UTC).replace(tzinfo=None) db.session.commit() + + @classmethod + def get_conversational_variable( + cls, + app_model: App, + conversation_id: str, + user: Optional[Union[Account, EndUser]], + limit: int, + last_id: Optional[str], + ) -> InfiniteScrollPagination: + conversation = cls.get_conversation(app_model, conversation_id, user) + + stmt = ( + select(ConversationVariable) + .where(ConversationVariable.app_id == app_model.id) + .where(ConversationVariable.conversation_id == conversation.id) + .order_by(ConversationVariable.created_at) + ) + + with Session(db.engine) as session: + if last_id: + last_variable = session.scalar(stmt.where(ConversationVariable.id == last_id)) + if not last_variable: + raise ConversationVariableNotExistsError() + + # Filter for variables created after the last_id + stmt = stmt.where(ConversationVariable.created_at > last_variable.created_at) + + # Apply limit to query + query_stmt = stmt.limit(limit) # Get one extra to check if there are more + rows = session.scalars(query_stmt).all() + + has_more = False + if len(rows) > limit: + has_more = True + rows = rows[:limit] # Remove the extra item + + variables = [ + { + "created_at": row.created_at, + "updated_at": row.updated_at, + **row.to_variable().model_dump(), + } + for row in rows + ] + + return InfiniteScrollPagination(variables, limit, has_more) diff --git a/api/services/errors/conversation.py b/api/services/errors/conversation.py index 139dd9a70a..f8051e3417 100644 --- a/api/services/errors/conversation.py +++ b/api/services/errors/conversation.py @@ -11,3 +11,7 @@ class ConversationNotExistsError(BaseServiceError): class ConversationCompletedError(Exception): pass + + +class ConversationVariableNotExistsError(BaseServiceError): + pass diff --git a/web/app/components/develop/template/template_advanced_chat.en.mdx b/web/app/components/develop/template/template_advanced_chat.en.mdx index 9502f20124..f645133030 100644 --- a/web/app/components/develop/template/template_advanced_chat.en.mdx +++ b/web/app/components/develop/template/template_advanced_chat.en.mdx @@ -845,6 +845,106 @@ Chat applications support session persistence, allowing previous chat history to --- + + + + Retrieve variables from a specific conversation. This endpoint is useful for extracting structured data that was captured during the conversation. + + ### Path Parameters + + + + The ID of the conversation to retrieve variables from. + + + + ### Query Parameters + + + + The user identifier, defined by the developer, must ensure uniqueness within the application + + + (Optional) The ID of the last record on the current page, default is null. + + + (Optional) How many records to return in one request, default is the most recent 20 entries. Maximum 100, minimum 1. + + + + ### Response + + - `limit` (int) Number of items per page + - `has_more` (bool) Whether there is a next page + - `data` (array[object]) List of variables + - `id` (string) Variable ID + - `name` (string) Variable name + - `value_type` (string) Variable type (string, number, object, etc.) + - `value` (string) Variable value + - `description` (string) Variable description + - `created_at` (int) Creation timestamp + - `updated_at` (int) Last update timestamp + + ### Errors + - 404, `conversation_not_exists`, Conversation not found + + + + + + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123' \ + --header 'Authorization: Bearer {api_key}' + ``` + + + + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123&variable_name=customer_name' \ + --header 'Authorization: Bearer {api_key}' + ``` + + + + ```json {{ title: 'Response' }} + { + "limit": 100, + "has_more": false, + "data": [ + { + "id": "variable-uuid-1", + "name": "customer_name", + "value_type": "string", + "value": "John Doe", + "description": "Customer name extracted from the conversation", + "created_at": 1650000000000, + "updated_at": 1650000000000 + }, + { + "id": "variable-uuid-2", + "name": "order_details", + "value_type": "json", + "value": "{\"product\":\"Widget\",\"quantity\":5,\"price\":19.99}", + "description": "Order details from the customer", + "created_at": 1650000000000, + "updated_at": 1650000000000 + } + ] + } + ``` + + + + +--- + + + + 特定の会話から変数を取得します。このエンドポイントは、会話中に取得された構造化データを抽出するのに役立ちます。 + + ### パスパラメータ + + + + 変数を取得する会話のID。 + + + + ### クエリパラメータ + + + + ユーザー識別子。開発者によって定義されたルールに従い、アプリケーション内で一意である必要があります。 + + + (Optional)現在のページの最後の記録のID、デフォルトはnullです。 + + + (Optional)1回のリクエストで返す記録の数、デフォルトは最新の20件です。最大100、最小1。 + + + + ### レスポンス + + - `limit` (int) ページごとのアイテム数 + - `has_more` (bool) さらにアイテムがあるかどうか + - `data` (array[object]) 変数のリスト + - `id` (string) 変数ID + - `name` (string) 変数名 + - `value_type` (string) 変数タイプ(文字列、数値、真偽値など) + - `value` (string) 変数値 + - `description` (string) 変数の説明 + - `created_at` (int) 作成タイムスタンプ + - `updated_at` (int) 最終更新タイムスタンプ + + ### エラー + - 404, `conversation_not_exists`, 会話が見つかりません + + + + + + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123' \ + --header 'Authorization: Bearer {api_key}' + ``` + + + + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123&variable_name=customer_name' \ + --header 'Authorization: Bearer {api_key}' + ``` + + + + ```json {{ title: 'Response' }} + { + "limit": 100, + "has_more": false, + "data": [ + { + "id": "variable-uuid-1", + "name": "customer_name", + "value_type": "string", + "value": "John Doe", + "description": "会話から抽出された顧客名", + "created_at": 1650000000000, + "updated_at": 1650000000000 + }, + { + "id": "variable-uuid-2", + "name": "order_details", + "value_type": "json", + "value": "{\"product\":\"Widget\",\"quantity\":5,\"price\":19.99}", + "description": "顧客の注文詳細", + "created_at": 1650000000000, + "updated_at": 1650000000000 + } + ] + } + ``` + + + + +--- + + + + 从特定对话中检索变量。此端点对于提取对话过程中捕获的结构化数据非常有用。 + + ### 路径参数 + + + + 要从中检索变量的对话ID。 + + + + ### 查询参数 + + + + 用户标识符,由开发人员定义的规则,在应用程序内必须唯一。 + + + (选填)当前页最后面一条记录的 ID,默认 null + + + (选填)一次请求返回多少条记录,默认 20 条,最大 100 条,最小 1 条。 + + + + ### 响应 + + - `limit` (int) 每页项目数 + - `has_more` (bool) 是否有更多项目 + - `data` (array[object]) 变量列表 + - `id` (string) 变量ID + - `name` (string) 变量名称 + - `value_type` (string) 变量类型(字符串、数字、布尔等) + - `value` (string) 变量值 + - `description` (string) 变量描述 + - `created_at` (int) 创建时间戳 + - `updated_at` (int) 最后更新时间戳 + + ### 错误 + - 404, `conversation_not_exists`, 对话不存在 + + + + + + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123' \ + --header 'Authorization: Bearer {api_key}' + ``` + + + + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123&variable_name=customer_name' \ + --header 'Authorization: Bearer {api_key}' + ``` + + + + ```json {{ title: 'Response' }} + { + "limit": 100, + "has_more": false, + "data": [ + { + "id": "variable-uuid-1", + "name": "customer_name", + "value_type": "string", + "value": "John Doe", + "description": "客户名称(从对话中提取)", + "created_at": 1650000000000, + "updated_at": 1650000000000 + }, + { + "id": "variable-uuid-2", + "name": "order_details", + "value_type": "json", + "value": "{\"product\":\"Widget\",\"quantity\":5,\"price\":19.99}", + "description": "客户的订单详情", + "created_at": 1650000000000, + "updated_at": 1650000000000 + } + ] + } + ``` + + + + +--- + + + + Retrieve variables from a specific conversation. This endpoint is useful for extracting structured data that was captured during the conversation. + + ### Path Parameters + + + + The ID of the conversation to retrieve variables from. + + + + ### Query Parameters + + + + The user identifier, defined by the developer, must ensure uniqueness within the application + + + (Optional) The ID of the last record on the current page, default is null. + + + (Optional) How many records to return in one request, default is the most recent 20 entries. Maximum 100, minimum 1. + + + + ### Response + + - `limit` (int) Number of items per page + - `has_more` (bool) Whether there is a next page + - `data` (array[object]) List of variables + - `id` (string) Variable ID + - `name` (string) Variable name + - `value_type` (string) Variable type (string, number, object, etc.) + - `value` (string) Variable value + - `description` (string) Variable description + - `created_at` (int) Creation timestamp + - `updated_at` (int) Last update timestamp + + ### Errors + - 404, `conversation_not_exists`, Conversation not found + + + + + + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123' \ + --header 'Authorization: Bearer {api_key}' + ``` + + + + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123&variable_name=customer_name' \ + --header 'Authorization: Bearer {api_key}' + ``` + + + + ```json {{ title: 'Response' }} + { + "limit": 100, + "has_more": false, + "data": [ + { + "id": "variable-uuid-1", + "name": "customer_name", + "value_type": "string", + "value": "John Doe", + "description": "Customer name extracted from the conversation", + "created_at": 1650000000000, + "updated_at": 1650000000000 + }, + { + "id": "variable-uuid-2", + "name": "order_details", + "value_type": "json", + "value": "{\"product\":\"Widget\",\"quantity\":5,\"price\":19.99}", + "description": "Order details from the customer", + "created_at": 1650000000000, + "updated_at": 1650000000000 + } + ] + } + ``` + + + + +--- + + + + 特定の会話から変数を取得します。このエンドポイントは、会話中に取得された構造化データを抽出するのに役立ちます。 + + ### パスパラメータ + + + + 変数を取得する会話のID。 + + + + ### クエリパラメータ + + + + ユーザー識別子。開発者によって定義されたルールに従い、アプリケーション内で一意である必要があります。 + + + (Optional)現在のページの最後のレコードのID、デフォルトはnullです。 + + + (Optional)1回のリクエストで返すレコードの数、デフォルトは最新の20件です。最大100、最小1。 + + + + ### レスポンス + + - `limit` (int) ページごとのアイテム数 + - `has_more` (bool) さらにアイテムがあるかどうか + - `data` (array[object]) 変数のリスト + - `id` (string) 変数ID + - `name` (string) 変数名 + - `value_type` (string) 変数タイプ(文字列、数値、真偽値など) + - `value` (string) 変数値 + - `description` (string) 変数の説明 + - `created_at` (int) 作成タイムスタンプ + - `updated_at` (int) 最終更新タイムスタンプ + + ### エラー + - 404, `conversation_not_exists`, 会話が見つかりません + + + + + + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123' \ + --header 'Authorization: Bearer {api_key}' + ``` + + + + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123&variable_name=customer_name' \ + --header 'Authorization: Bearer {api_key}' + ``` + + + + ```json {{ title: 'Response' }} + { + "limit": 100, + "has_more": false, + "data": [ + { + "id": "variable-uuid-1", + "name": "customer_name", + "value_type": "string", + "value": "John Doe", + "description": "会話から抽出された顧客名", + "created_at": 1650000000000, + "updated_at": 1650000000000 + }, + { + "id": "variable-uuid-2", + "name": "order_details", + "value_type": "json", + "value": "{\"product\":\"Widget\",\"quantity\":5,\"price\":19.99}", + "description": "顧客の注文詳細", + "created_at": 1650000000000, + "updated_at": 1650000000000 + } + ] + } + ``` + + + + +--- + + + + 从特定对话中检索变量。此端点对于提取对话过程中捕获的结构化数据非常有用。 + + ### 路径参数 + + + + 要从中检索变量的对话ID。 + + + + ### 查询参数 + + + + 用户标识符,由开发人员定义的规则,在应用程序内必须唯一。 + + + (选填)当前页最后面一条记录的 ID,默认 null + + + (选填)一次请求返回多少条记录,默认 20 条,最大 100 条,最小 1 条。 + + + + ### 响应 + + - `limit` (int) 每页项目数 + - `has_more` (bool) 是否有更多项目 + - `data` (array[object]) 变量列表 + - `id` (string) 变量ID + - `name` (string) 变量名称 + - `value_type` (string) 变量类型(字符串、数字、布尔等) + - `value` (string) 变量值 + - `description` (string) 变量描述 + - `created_at` (int) 创建时间戳 + - `updated_at` (int) 最后更新时间戳 + + ### 错误 + - 404, `conversation_not_exists`, 对话不存在 + + + + + + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123' \ + --header 'Authorization: Bearer {api_key}' + ``` + + + + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123&variable_name=customer_name' \ + --header 'Authorization: Bearer {api_key}' + ``` + + + + ```json {{ title: 'Response' }} + { + "limit": 100, + "has_more": false, + "data": [ + { + "id": "variable-uuid-1", + "name": "customer_name", + "value_type": "string", + "value": "John Doe", + "description": "客户名称(从对话中提取)", + "created_at": 1650000000000, + "updated_at": 1650000000000 + }, + { + "id": "variable-uuid-2", + "name": "order_details", + "value_type": "json", + "value": "{\"product\":\"Widget\",\"quantity\":5,\"price\":19.99}", + "description": "客户的订单详情", + "created_at": 1650000000000, + "updated_at": 1650000000000 + } + ] + } + ``` + + + + +--- +