Resolves #18536 Retreive conversation variables (#18581)

This commit is contained in:
Alex Chim 2025-04-25 11:52:25 +08:00 committed by GitHub
parent 2627e221f2
commit 12836f9db9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 691 additions and 1 deletions

View File

@ -14,6 +14,9 @@ from fields.conversation_fields import (
conversation_infinite_scroll_pagination_fields, conversation_infinite_scroll_pagination_fields,
simple_conversation_fields, simple_conversation_fields,
) )
from fields.conversation_variable_fields import (
conversation_variable_infinite_scroll_pagination_fields,
)
from libs.helper import uuid_value from libs.helper import uuid_value
from models.model import App, AppMode, EndUser from models.model import App, AppMode, EndUser
from services.conversation_service import ConversationService from services.conversation_service import ConversationService
@ -93,6 +96,31 @@ class ConversationRenameApi(Resource):
raise NotFound("Conversation Not Exists.") 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/<uuid:c_id>/name", endpoint="conversation_name") api.add_resource(ConversationRenameApi, "/conversations/<uuid:c_id>/name", endpoint="conversation_name")
api.add_resource(ConversationApi, "/conversations") api.add_resource(ConversationApi, "/conversations")
api.add_resource(ConversationDetailApi, "/conversations/<uuid:c_id>", endpoint="conversation_detail") api.add_resource(ConversationDetailApi, "/conversations/<uuid:c_id>", endpoint="conversation_detail")
api.add_resource(ConversationVariablesApi, "/conversations/<uuid:c_id>/variables", endpoint="conversation_variables")

View File

@ -19,3 +19,9 @@ paginated_conversation_variable_fields = {
"has_more": fields.Boolean, "has_more": fields.Boolean,
"data": fields.List(fields.Nested(conversation_variable_fields), attribute="data"), "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)),
}

View File

@ -9,9 +9,14 @@ from core.app.entities.app_invoke_entities import InvokeFrom
from core.llm_generator.llm_generator import LLMGenerator from core.llm_generator.llm_generator import LLMGenerator
from extensions.ext_database import db from extensions.ext_database import db
from libs.infinite_scroll_pagination import InfiniteScrollPagination from libs.infinite_scroll_pagination import InfiniteScrollPagination
from models import ConversationVariable
from models.account import Account from models.account import Account
from models.model import App, Conversation, EndUser, Message 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 from services.errors.message import MessageNotExistsError
@ -166,3 +171,50 @@ class ConversationService:
conversation.is_deleted = True conversation.is_deleted = True
conversation.updated_at = datetime.now(UTC).replace(tzinfo=None) conversation.updated_at = datetime.now(UTC).replace(tzinfo=None)
db.session.commit() 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)

View File

@ -11,3 +11,7 @@ class ConversationNotExistsError(BaseServiceError):
class ConversationCompletedError(Exception): class ConversationCompletedError(Exception):
pass pass
class ConversationVariableNotExistsError(BaseServiceError):
pass

View File

@ -845,6 +845,106 @@ Chat applications support session persistence, allowing previous chat history to
--- ---
<Heading
url='/conversations/:conversation_id/variables'
method='GET'
title='Get Conversation Variables'
name='#conversation-variables'
/>
<Row>
<Col>
Retrieve variables from a specific conversation. This endpoint is useful for extracting structured data that was captured during the conversation.
### Path Parameters
<Properties>
<Property name='conversation_id' type='string' key='conversation_id'>
The ID of the conversation to retrieve variables from.
</Property>
</Properties>
### Query Parameters
<Properties>
<Property name='user' type='string' key='user'>
The user identifier, defined by the developer, must ensure uniqueness within the application
</Property>
<Property name='last_id' type='string' key='last_id'>
(Optional) The ID of the last record on the current page, default is null.
</Property>
<Property name='limit' type='int' key='limit'>
(Optional) How many records to return in one request, default is the most recent 20 entries. Maximum 100, minimum 1.
</Property>
</Properties>
### 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
</Col>
<Col sticky>
<CodeGroup title="Request" tag="GET" label="/conversations/:conversation_id/variables" targetCode={`curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123' \\\n--header 'Authorization: Bearer {api_key}'`}>
```bash {{ title: 'cURL' }}
curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123' \
--header 'Authorization: Bearer {api_key}'
```
</CodeGroup>
<CodeGroup title="Request with variable name filter">
```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}'
```
</CodeGroup>
<CodeGroup title="Response">
```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
}
]
}
```
</CodeGroup>
</Col>
</Row>
---
<Heading <Heading
url='/audio-to-text' url='/audio-to-text'
method='POST' method='POST'

View File

@ -844,6 +844,106 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
--- ---
<Heading
url='/conversations/:conversation_id/variables'
method='GET'
title='会話変数の取得'
name='#conversation-variables'
/>
<Row>
<Col>
特定の会話から変数を取得します。このエンドポイントは、会話中に取得された構造化データを抽出するのに役立ちます。
### パスパラメータ
<Properties>
<Property name='conversation_id' type='string' key='conversation_id'>
変数を取得する会話のID。
</Property>
</Properties>
### クエリパラメータ
<Properties>
<Property name='user' type='string' key='user'>
ユーザー識別子。開発者によって定義されたルールに従い、アプリケーション内で一意である必要があります。
</Property>
<Property name='last_id' type='string' key='last_id'>
(Optional)現在のページの最後の記録のID、デフォルトはnullです。
</Property>
<Property name='limit' type='int' key='limit'>
(Optional)1回のリクエストで返す記録の数、デフォルトは最新の20件です。最大100、最小1。
</Property>
</Properties>
### レスポンス
- `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`, 会話が見つかりません
</Col>
<Col sticky>
<CodeGroup title="Request" tag="GET" label="/conversations/:conversation_id/variables" targetCode={`curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123' \\\n--header 'Authorization: Bearer {api_key}'`}>
```bash {{ title: 'cURL' }}
curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123' \
--header 'Authorization: Bearer {api_key}'
```
</CodeGroup>
<CodeGroup title="Request with variable name filter">
```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}'
```
</CodeGroup>
<CodeGroup title="Response">
```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
}
]
}
```
</CodeGroup>
</Col>
</Row>
---
<Heading <Heading
url='/audio-to-text' url='/audio-to-text'
method='POST' method='POST'

View File

@ -881,6 +881,106 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
--- ---
<Heading
url='/conversations/:conversation_id/variables'
method='GET'
title='获取对话变量'
name='#conversation-variables'
/>
<Row>
<Col>
从特定对话中检索变量。此端点对于提取对话过程中捕获的结构化数据非常有用。
### 路径参数
<Properties>
<Property name='conversation_id' type='string' key='conversation_id'>
要从中检索变量的对话ID。
</Property>
</Properties>
### 查询参数
<Properties>
<Property name='user' type='string' key='user'>
用户标识符,由开发人员定义的规则,在应用程序内必须唯一。
</Property>
<Property name='last_id' type='string' key='last_id'>
(选填)当前页最后面一条记录的 ID默认 null
</Property>
<Property name='limit' type='int' key='limit'>
(选填)一次请求返回多少条记录,默认 20 条,最大 100 条,最小 1 条。
</Property>
</Properties>
### 响应
- `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`, 对话不存在
</Col>
<Col sticky>
<CodeGroup title="Request" tag="GET" label="/conversations/:conversation_id/variables" targetCode={`curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123' \\\n--header 'Authorization: Bearer {api_key}'`}>
```bash {{ title: 'cURL' }}
curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123' \
--header 'Authorization: Bearer {api_key}'
```
</CodeGroup>
<CodeGroup title="Request with variable name filter">
```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}'
```
</CodeGroup>
<CodeGroup title="Response">
```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
}
]
}
```
</CodeGroup>
</Col>
</Row>
---
<Heading <Heading
url='/audio-to-text' url='/audio-to-text'
method='POST' method='POST'

View File

@ -878,6 +878,106 @@ Chat applications support session persistence, allowing previous chat history to
--- ---
<Heading
url='/conversations/:conversation_id/variables'
method='GET'
title='Get Conversation Variables'
name='#conversation-variables'
/>
<Row>
<Col>
Retrieve variables from a specific conversation. This endpoint is useful for extracting structured data that was captured during the conversation.
### Path Parameters
<Properties>
<Property name='conversation_id' type='string' key='conversation_id'>
The ID of the conversation to retrieve variables from.
</Property>
</Properties>
### Query Parameters
<Properties>
<Property name='user' type='string' key='user'>
The user identifier, defined by the developer, must ensure uniqueness within the application
</Property>
<Property name='last_id' type='string' key='last_id'>
(Optional) The ID of the last record on the current page, default is null.
</Property>
<Property name='limit' type='int' key='limit'>
(Optional) How many records to return in one request, default is the most recent 20 entries. Maximum 100, minimum 1.
</Property>
</Properties>
### 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
</Col>
<Col sticky>
<CodeGroup title="Request" tag="GET" label="/conversations/:conversation_id/variables" targetCode={`curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123' \\\n--header 'Authorization: Bearer {api_key}'`}>
```bash {{ title: 'cURL' }}
curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123' \
--header 'Authorization: Bearer {api_key}'
```
</CodeGroup>
<CodeGroup title="Request with variable name filter">
```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}'
```
</CodeGroup>
<CodeGroup title="Response">
```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
}
]
}
```
</CodeGroup>
</Col>
</Row>
---
<Heading <Heading
url='/audio-to-text' url='/audio-to-text'
method='POST' method='POST'

View File

@ -876,6 +876,106 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
--- ---
<Heading
url='/conversations/:conversation_id/variables'
method='GET'
title='会話変数の取得'
name='#conversation-variables'
/>
<Row>
<Col>
特定の会話から変数を取得します。このエンドポイントは、会話中に取得された構造化データを抽出するのに役立ちます。
### パスパラメータ
<Properties>
<Property name='conversation_id' type='string' key='conversation_id'>
変数を取得する会話のID。
</Property>
</Properties>
### クエリパラメータ
<Properties>
<Property name='user' type='string' key='user'>
ユーザー識別子。開発者によって定義されたルールに従い、アプリケーション内で一意である必要があります。
</Property>
<Property name='last_id' type='string' key='last_id'>
(Optional)現在のページの最後のレコードのID、デフォルトはnullです。
</Property>
<Property name='limit' type='int' key='limit'>
(Optional)1回のリクエストで返すレコードの数、デフォルトは最新の20件です。最大100、最小1。
</Property>
</Properties>
### レスポンス
- `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`, 会話が見つかりません
</Col>
<Col sticky>
<CodeGroup title="Request" tag="GET" label="/conversations/:conversation_id/variables" targetCode={`curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123' \\\n--header 'Authorization: Bearer {api_key}'`}>
```bash {{ title: 'cURL' }}
curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123' \
--header 'Authorization: Bearer {api_key}'
```
</CodeGroup>
<CodeGroup title="Request with variable name filter">
```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}'
```
</CodeGroup>
<CodeGroup title="Response">
```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
}
]
}
```
</CodeGroup>
</Col>
</Row>
---
<Heading <Heading
url='/audio-to-text' url='/audio-to-text'
method='POST' method='POST'

View File

@ -893,6 +893,106 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
--- ---
<Heading
url='/conversations/:conversation_id/variables'
method='GET'
title='获取对话变量'
name='#conversation-variables'
/>
<Row>
<Col>
从特定对话中检索变量。此端点对于提取对话过程中捕获的结构化数据非常有用。
### 路径参数
<Properties>
<Property name='conversation_id' type='string' key='conversation_id'>
要从中检索变量的对话ID。
</Property>
</Properties>
### 查询参数
<Properties>
<Property name='user' type='string' key='user'>
用户标识符,由开发人员定义的规则,在应用程序内必须唯一。
</Property>
<Property name='last_id' type='string' key='last_id'>
(选填)当前页最后面一条记录的 ID默认 null
</Property>
<Property name='limit' type='int' key='limit'>
(选填)一次请求返回多少条记录,默认 20 条,最大 100 条,最小 1 条。
</Property>
</Properties>
### 响应
- `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`, 对话不存在
</Col>
<Col sticky>
<CodeGroup title="Request" tag="GET" label="/conversations/:conversation_id/variables" targetCode={`curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123' \\\n--header 'Authorization: Bearer {api_key}'`}>
```bash {{ title: 'cURL' }}
curl -X GET '${props.appDetail.api_base_url}/conversations/{conversation_id}/variables?user=abc-123' \
--header 'Authorization: Bearer {api_key}'
```
</CodeGroup>
<CodeGroup title="Request with variable name filter">
```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}'
```
</CodeGroup>
<CodeGroup title="Response">
```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
}
]
}
```
</CodeGroup>
</Col>
</Row>
---
<Heading <Heading
url='/audio-to-text' url='/audio-to-text'
method='POST' method='POST'