mirror of
https://github.com/langgenius/dify.git
synced 2026-04-25 01:26:57 +08:00
refactor(api): migrate console conversation responses to BaseModel (#35294)
Co-authored-by: ai-hpc <ai-hpc@users.noreply.github.com>
This commit is contained in:
parent
b08665e598
commit
b665eaa015
@ -2,20 +2,37 @@ from typing import Literal
|
|||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
from flask import abort, request
|
from flask import abort, request
|
||||||
from flask_restx import Resource, fields, marshal_with
|
from flask_restx import Resource
|
||||||
from pydantic import BaseModel, Field, field_validator
|
from pydantic import BaseModel, Field, field_validator
|
||||||
from sqlalchemy import func, or_
|
from sqlalchemy import func, or_
|
||||||
from sqlalchemy.orm import selectinload
|
from sqlalchemy.orm import selectinload
|
||||||
from werkzeug.exceptions import NotFound
|
from werkzeug.exceptions import NotFound
|
||||||
|
|
||||||
|
from controllers.common.schema import register_schema_models
|
||||||
from controllers.console import console_ns
|
from controllers.console import console_ns
|
||||||
from controllers.console.app.wraps import get_app_model
|
from controllers.console.app.wraps import get_app_model
|
||||||
from controllers.console.wraps import account_initialization_required, edit_permission_required, setup_required
|
from controllers.console.wraps import account_initialization_required, edit_permission_required, setup_required
|
||||||
from core.app.entities.app_invoke_entities import InvokeFrom
|
from core.app.entities.app_invoke_entities import InvokeFrom
|
||||||
from extensions.ext_database import db
|
from extensions.ext_database import db
|
||||||
from fields.raws import FilesContainedField
|
from fields.conversation_fields import (
|
||||||
|
Conversation as ConversationResponse,
|
||||||
|
)
|
||||||
|
from fields.conversation_fields import (
|
||||||
|
ConversationDetail as ConversationDetailResponse,
|
||||||
|
)
|
||||||
|
from fields.conversation_fields import (
|
||||||
|
ConversationMessageDetail as ConversationMessageDetailResponse,
|
||||||
|
)
|
||||||
|
from fields.conversation_fields import (
|
||||||
|
ConversationPagination as ConversationPaginationResponse,
|
||||||
|
)
|
||||||
|
from fields.conversation_fields import (
|
||||||
|
ConversationWithSummaryPagination as ConversationWithSummaryPaginationResponse,
|
||||||
|
)
|
||||||
|
from fields.conversation_fields import (
|
||||||
|
ResultResponse,
|
||||||
|
)
|
||||||
from libs.datetime_utils import naive_utc_now, parse_time_range
|
from libs.datetime_utils import naive_utc_now, parse_time_range
|
||||||
from libs.helper import TimestampField
|
|
||||||
from libs.login import current_account_with_tenant, login_required
|
from libs.login import current_account_with_tenant, login_required
|
||||||
from models import Conversation, EndUser, Message, MessageAnnotation
|
from models import Conversation, EndUser, Message, MessageAnnotation
|
||||||
from models.model import AppMode
|
from models.model import AppMode
|
||||||
@ -62,267 +79,16 @@ console_ns.schema_model(
|
|||||||
ChatConversationQuery.model_json_schema(ref_template=DEFAULT_REF_TEMPLATE_SWAGGER_2_0),
|
ChatConversationQuery.model_json_schema(ref_template=DEFAULT_REF_TEMPLATE_SWAGGER_2_0),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Register models for flask_restx to avoid dict type issues in Swagger
|
register_schema_models(
|
||||||
# Register in dependency order: base models first, then dependent models
|
console_ns,
|
||||||
|
CompletionConversationQuery,
|
||||||
# Base models
|
ChatConversationQuery,
|
||||||
simple_account_model = console_ns.model(
|
ConversationResponse,
|
||||||
"SimpleAccount",
|
ConversationPaginationResponse,
|
||||||
{
|
ConversationMessageDetailResponse,
|
||||||
"id": fields.String,
|
ConversationWithSummaryPaginationResponse,
|
||||||
"name": fields.String,
|
ConversationDetailResponse,
|
||||||
"email": fields.String,
|
ResultResponse,
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
feedback_stat_model = console_ns.model(
|
|
||||||
"FeedbackStat",
|
|
||||||
{
|
|
||||||
"like": fields.Integer,
|
|
||||||
"dislike": fields.Integer,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
status_count_model = console_ns.model(
|
|
||||||
"StatusCount",
|
|
||||||
{
|
|
||||||
"success": fields.Integer,
|
|
||||||
"failed": fields.Integer,
|
|
||||||
"partial_success": fields.Integer,
|
|
||||||
"paused": fields.Integer,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
message_file_model = console_ns.model(
|
|
||||||
"MessageFile",
|
|
||||||
{
|
|
||||||
"id": fields.String,
|
|
||||||
"filename": fields.String,
|
|
||||||
"type": fields.String,
|
|
||||||
"url": fields.String,
|
|
||||||
"mime_type": fields.String,
|
|
||||||
"size": fields.Integer,
|
|
||||||
"transfer_method": fields.String,
|
|
||||||
"belongs_to": fields.String(default="user"),
|
|
||||||
"upload_file_id": fields.String(default=None),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
agent_thought_model = console_ns.model(
|
|
||||||
"AgentThought",
|
|
||||||
{
|
|
||||||
"id": fields.String,
|
|
||||||
"chain_id": fields.String,
|
|
||||||
"message_id": fields.String,
|
|
||||||
"position": fields.Integer,
|
|
||||||
"thought": fields.String,
|
|
||||||
"tool": fields.String,
|
|
||||||
"tool_labels": fields.Raw,
|
|
||||||
"tool_input": fields.String,
|
|
||||||
"created_at": TimestampField,
|
|
||||||
"observation": fields.String,
|
|
||||||
"files": fields.List(fields.String),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
simple_model_config_model = console_ns.model(
|
|
||||||
"SimpleModelConfig",
|
|
||||||
{
|
|
||||||
"model": fields.Raw(attribute="model_dict"),
|
|
||||||
"pre_prompt": fields.String,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
model_config_model = console_ns.model(
|
|
||||||
"ModelConfig",
|
|
||||||
{
|
|
||||||
"opening_statement": fields.String,
|
|
||||||
"suggested_questions": fields.Raw,
|
|
||||||
"model": fields.Raw,
|
|
||||||
"user_input_form": fields.Raw,
|
|
||||||
"pre_prompt": fields.String,
|
|
||||||
"agent_mode": fields.Raw,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
# Models that depend on simple_account_model
|
|
||||||
feedback_model = console_ns.model(
|
|
||||||
"Feedback",
|
|
||||||
{
|
|
||||||
"rating": fields.String,
|
|
||||||
"content": fields.String,
|
|
||||||
"from_source": fields.String,
|
|
||||||
"from_end_user_id": fields.String,
|
|
||||||
"from_account": fields.Nested(simple_account_model, allow_null=True),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
annotation_model = console_ns.model(
|
|
||||||
"Annotation",
|
|
||||||
{
|
|
||||||
"id": fields.String,
|
|
||||||
"question": fields.String,
|
|
||||||
"content": fields.String,
|
|
||||||
"account": fields.Nested(simple_account_model, allow_null=True),
|
|
||||||
"created_at": TimestampField,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
annotation_hit_history_model = console_ns.model(
|
|
||||||
"AnnotationHitHistory",
|
|
||||||
{
|
|
||||||
"annotation_id": fields.String(attribute="id"),
|
|
||||||
"annotation_create_account": fields.Nested(simple_account_model, allow_null=True),
|
|
||||||
"created_at": TimestampField,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class MessageTextField(fields.Raw):
|
|
||||||
def format(self, value):
|
|
||||||
return value[0]["text"] if value else ""
|
|
||||||
|
|
||||||
|
|
||||||
# Simple message detail model
|
|
||||||
simple_message_detail_model = console_ns.model(
|
|
||||||
"SimpleMessageDetail",
|
|
||||||
{
|
|
||||||
"inputs": FilesContainedField,
|
|
||||||
"query": fields.String,
|
|
||||||
"message": MessageTextField,
|
|
||||||
"answer": fields.String,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
# Message detail model that depends on multiple models
|
|
||||||
message_detail_model = console_ns.model(
|
|
||||||
"MessageDetail",
|
|
||||||
{
|
|
||||||
"id": fields.String,
|
|
||||||
"conversation_id": fields.String,
|
|
||||||
"inputs": FilesContainedField,
|
|
||||||
"query": fields.String,
|
|
||||||
"message": fields.Raw,
|
|
||||||
"message_tokens": fields.Integer,
|
|
||||||
"answer": fields.String(attribute="re_sign_file_url_answer"),
|
|
||||||
"answer_tokens": fields.Integer,
|
|
||||||
"provider_response_latency": fields.Float,
|
|
||||||
"from_source": fields.String,
|
|
||||||
"from_end_user_id": fields.String,
|
|
||||||
"from_account_id": fields.String,
|
|
||||||
"feedbacks": fields.List(fields.Nested(feedback_model)),
|
|
||||||
"workflow_run_id": fields.String,
|
|
||||||
"annotation": fields.Nested(annotation_model, allow_null=True),
|
|
||||||
"annotation_hit_history": fields.Nested(annotation_hit_history_model, allow_null=True),
|
|
||||||
"created_at": TimestampField,
|
|
||||||
"agent_thoughts": fields.List(fields.Nested(agent_thought_model)),
|
|
||||||
"message_files": fields.List(fields.Nested(message_file_model)),
|
|
||||||
"metadata": fields.Raw(attribute="message_metadata_dict"),
|
|
||||||
"status": fields.String,
|
|
||||||
"error": fields.String,
|
|
||||||
"parent_message_id": fields.String,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
# Conversation models
|
|
||||||
conversation_fields_model = console_ns.model(
|
|
||||||
"Conversation",
|
|
||||||
{
|
|
||||||
"id": fields.String,
|
|
||||||
"status": fields.String,
|
|
||||||
"from_source": fields.String,
|
|
||||||
"from_end_user_id": fields.String,
|
|
||||||
"from_end_user_session_id": fields.String(),
|
|
||||||
"from_account_id": fields.String,
|
|
||||||
"from_account_name": fields.String,
|
|
||||||
"read_at": TimestampField,
|
|
||||||
"created_at": TimestampField,
|
|
||||||
"updated_at": TimestampField,
|
|
||||||
"annotation": fields.Nested(annotation_model, allow_null=True),
|
|
||||||
"model_config": fields.Nested(simple_model_config_model),
|
|
||||||
"user_feedback_stats": fields.Nested(feedback_stat_model),
|
|
||||||
"admin_feedback_stats": fields.Nested(feedback_stat_model),
|
|
||||||
"message": fields.Nested(simple_message_detail_model, attribute="first_message"),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
conversation_pagination_model = console_ns.model(
|
|
||||||
"ConversationPagination",
|
|
||||||
{
|
|
||||||
"page": fields.Integer,
|
|
||||||
"limit": fields.Integer(attribute="per_page"),
|
|
||||||
"total": fields.Integer,
|
|
||||||
"has_more": fields.Boolean(attribute="has_next"),
|
|
||||||
"data": fields.List(fields.Nested(conversation_fields_model), attribute="items"),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
conversation_message_detail_model = console_ns.model(
|
|
||||||
"ConversationMessageDetail",
|
|
||||||
{
|
|
||||||
"id": fields.String,
|
|
||||||
"status": fields.String,
|
|
||||||
"from_source": fields.String,
|
|
||||||
"from_end_user_id": fields.String,
|
|
||||||
"from_account_id": fields.String,
|
|
||||||
"created_at": TimestampField,
|
|
||||||
"model_config": fields.Nested(model_config_model),
|
|
||||||
"message": fields.Nested(message_detail_model, attribute="first_message"),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
conversation_with_summary_model = console_ns.model(
|
|
||||||
"ConversationWithSummary",
|
|
||||||
{
|
|
||||||
"id": fields.String,
|
|
||||||
"status": fields.String,
|
|
||||||
"from_source": fields.String,
|
|
||||||
"from_end_user_id": fields.String,
|
|
||||||
"from_end_user_session_id": fields.String,
|
|
||||||
"from_account_id": fields.String,
|
|
||||||
"from_account_name": fields.String,
|
|
||||||
"name": fields.String,
|
|
||||||
"summary": fields.String(attribute="summary_or_query"),
|
|
||||||
"read_at": TimestampField,
|
|
||||||
"created_at": TimestampField,
|
|
||||||
"updated_at": TimestampField,
|
|
||||||
"annotated": fields.Boolean,
|
|
||||||
"model_config": fields.Nested(simple_model_config_model),
|
|
||||||
"message_count": fields.Integer,
|
|
||||||
"user_feedback_stats": fields.Nested(feedback_stat_model),
|
|
||||||
"admin_feedback_stats": fields.Nested(feedback_stat_model),
|
|
||||||
"status_count": fields.Nested(status_count_model),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
conversation_with_summary_pagination_model = console_ns.model(
|
|
||||||
"ConversationWithSummaryPagination",
|
|
||||||
{
|
|
||||||
"page": fields.Integer,
|
|
||||||
"limit": fields.Integer(attribute="per_page"),
|
|
||||||
"total": fields.Integer,
|
|
||||||
"has_more": fields.Boolean(attribute="has_next"),
|
|
||||||
"data": fields.List(fields.Nested(conversation_with_summary_model), attribute="items"),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
conversation_detail_model = console_ns.model(
|
|
||||||
"ConversationDetail",
|
|
||||||
{
|
|
||||||
"id": fields.String,
|
|
||||||
"status": fields.String,
|
|
||||||
"from_source": fields.String,
|
|
||||||
"from_end_user_id": fields.String,
|
|
||||||
"from_account_id": fields.String,
|
|
||||||
"created_at": TimestampField,
|
|
||||||
"updated_at": TimestampField,
|
|
||||||
"annotated": fields.Boolean,
|
|
||||||
"introduction": fields.String,
|
|
||||||
"model_config": fields.Nested(model_config_model),
|
|
||||||
"message_count": fields.Integer,
|
|
||||||
"user_feedback_stats": fields.Nested(feedback_stat_model),
|
|
||||||
"admin_feedback_stats": fields.Nested(feedback_stat_model),
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -332,13 +98,12 @@ class CompletionConversationApi(Resource):
|
|||||||
@console_ns.doc(description="Get completion conversations with pagination and filtering")
|
@console_ns.doc(description="Get completion conversations with pagination and filtering")
|
||||||
@console_ns.doc(params={"app_id": "Application ID"})
|
@console_ns.doc(params={"app_id": "Application ID"})
|
||||||
@console_ns.expect(console_ns.models[CompletionConversationQuery.__name__])
|
@console_ns.expect(console_ns.models[CompletionConversationQuery.__name__])
|
||||||
@console_ns.response(200, "Success", conversation_pagination_model)
|
@console_ns.response(200, "Success", console_ns.models[ConversationPaginationResponse.__name__])
|
||||||
@console_ns.response(403, "Insufficient permissions")
|
@console_ns.response(403, "Insufficient permissions")
|
||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@get_app_model(mode=AppMode.COMPLETION)
|
@get_app_model(mode=AppMode.COMPLETION)
|
||||||
@marshal_with(conversation_pagination_model)
|
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
def get(self, app_model):
|
def get(self, app_model):
|
||||||
current_user, _ = current_account_with_tenant()
|
current_user, _ = current_account_with_tenant()
|
||||||
@ -394,7 +159,9 @@ class CompletionConversationApi(Resource):
|
|||||||
|
|
||||||
conversations = db.paginate(query, page=args.page, per_page=args.limit, error_out=False)
|
conversations = db.paginate(query, page=args.page, per_page=args.limit, error_out=False)
|
||||||
|
|
||||||
return conversations
|
return ConversationPaginationResponse.model_validate(conversations, from_attributes=True).model_dump(
|
||||||
|
mode="json"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@console_ns.route("/apps/<uuid:app_id>/completion-conversations/<uuid:conversation_id>")
|
@console_ns.route("/apps/<uuid:app_id>/completion-conversations/<uuid:conversation_id>")
|
||||||
@ -402,19 +169,19 @@ class CompletionConversationDetailApi(Resource):
|
|||||||
@console_ns.doc("get_completion_conversation")
|
@console_ns.doc("get_completion_conversation")
|
||||||
@console_ns.doc(description="Get completion conversation details with messages")
|
@console_ns.doc(description="Get completion conversation details with messages")
|
||||||
@console_ns.doc(params={"app_id": "Application ID", "conversation_id": "Conversation ID"})
|
@console_ns.doc(params={"app_id": "Application ID", "conversation_id": "Conversation ID"})
|
||||||
@console_ns.response(200, "Success", conversation_message_detail_model)
|
@console_ns.response(200, "Success", console_ns.models[ConversationMessageDetailResponse.__name__])
|
||||||
@console_ns.response(403, "Insufficient permissions")
|
@console_ns.response(403, "Insufficient permissions")
|
||||||
@console_ns.response(404, "Conversation not found")
|
@console_ns.response(404, "Conversation not found")
|
||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@get_app_model(mode=AppMode.COMPLETION)
|
@get_app_model(mode=AppMode.COMPLETION)
|
||||||
@marshal_with(conversation_message_detail_model)
|
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
def get(self, app_model, conversation_id):
|
def get(self, app_model, conversation_id):
|
||||||
conversation_id = str(conversation_id)
|
conversation_id = str(conversation_id)
|
||||||
|
return ConversationMessageDetailResponse.model_validate(
|
||||||
return _get_conversation(app_model, conversation_id)
|
_get_conversation(app_model, conversation_id), from_attributes=True
|
||||||
|
).model_dump(mode="json")
|
||||||
|
|
||||||
@console_ns.doc("delete_completion_conversation")
|
@console_ns.doc("delete_completion_conversation")
|
||||||
@console_ns.doc(description="Delete a completion conversation")
|
@console_ns.doc(description="Delete a completion conversation")
|
||||||
@ -436,7 +203,7 @@ class CompletionConversationDetailApi(Resource):
|
|||||||
except ConversationNotExistsError:
|
except ConversationNotExistsError:
|
||||||
raise NotFound("Conversation Not Exists.")
|
raise NotFound("Conversation Not Exists.")
|
||||||
|
|
||||||
return {"result": "success"}, 204
|
return ResultResponse(result="success").model_dump(mode="json"), 204
|
||||||
|
|
||||||
|
|
||||||
@console_ns.route("/apps/<uuid:app_id>/chat-conversations")
|
@console_ns.route("/apps/<uuid:app_id>/chat-conversations")
|
||||||
@ -445,13 +212,12 @@ class ChatConversationApi(Resource):
|
|||||||
@console_ns.doc(description="Get chat conversations with pagination, filtering and summary")
|
@console_ns.doc(description="Get chat conversations with pagination, filtering and summary")
|
||||||
@console_ns.doc(params={"app_id": "Application ID"})
|
@console_ns.doc(params={"app_id": "Application ID"})
|
||||||
@console_ns.expect(console_ns.models[ChatConversationQuery.__name__])
|
@console_ns.expect(console_ns.models[ChatConversationQuery.__name__])
|
||||||
@console_ns.response(200, "Success", conversation_with_summary_pagination_model)
|
@console_ns.response(200, "Success", console_ns.models[ConversationWithSummaryPaginationResponse.__name__])
|
||||||
@console_ns.response(403, "Insufficient permissions")
|
@console_ns.response(403, "Insufficient permissions")
|
||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT])
|
@get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT])
|
||||||
@marshal_with(conversation_with_summary_pagination_model)
|
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
def get(self, app_model):
|
def get(self, app_model):
|
||||||
current_user, _ = current_account_with_tenant()
|
current_user, _ = current_account_with_tenant()
|
||||||
@ -546,7 +312,9 @@ class ChatConversationApi(Resource):
|
|||||||
|
|
||||||
conversations = db.paginate(query, page=args.page, per_page=args.limit, error_out=False)
|
conversations = db.paginate(query, page=args.page, per_page=args.limit, error_out=False)
|
||||||
|
|
||||||
return conversations
|
return ConversationWithSummaryPaginationResponse.model_validate(conversations, from_attributes=True).model_dump(
|
||||||
|
mode="json"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@console_ns.route("/apps/<uuid:app_id>/chat-conversations/<uuid:conversation_id>")
|
@console_ns.route("/apps/<uuid:app_id>/chat-conversations/<uuid:conversation_id>")
|
||||||
@ -554,19 +322,19 @@ class ChatConversationDetailApi(Resource):
|
|||||||
@console_ns.doc("get_chat_conversation")
|
@console_ns.doc("get_chat_conversation")
|
||||||
@console_ns.doc(description="Get chat conversation details")
|
@console_ns.doc(description="Get chat conversation details")
|
||||||
@console_ns.doc(params={"app_id": "Application ID", "conversation_id": "Conversation ID"})
|
@console_ns.doc(params={"app_id": "Application ID", "conversation_id": "Conversation ID"})
|
||||||
@console_ns.response(200, "Success", conversation_detail_model)
|
@console_ns.response(200, "Success", console_ns.models[ConversationDetailResponse.__name__])
|
||||||
@console_ns.response(403, "Insufficient permissions")
|
@console_ns.response(403, "Insufficient permissions")
|
||||||
@console_ns.response(404, "Conversation not found")
|
@console_ns.response(404, "Conversation not found")
|
||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
@get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT])
|
@get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT])
|
||||||
@marshal_with(conversation_detail_model)
|
|
||||||
@edit_permission_required
|
@edit_permission_required
|
||||||
def get(self, app_model, conversation_id):
|
def get(self, app_model, conversation_id):
|
||||||
conversation_id = str(conversation_id)
|
conversation_id = str(conversation_id)
|
||||||
|
return ConversationDetailResponse.model_validate(
|
||||||
return _get_conversation(app_model, conversation_id)
|
_get_conversation(app_model, conversation_id), from_attributes=True
|
||||||
|
).model_dump(mode="json")
|
||||||
|
|
||||||
@console_ns.doc("delete_chat_conversation")
|
@console_ns.doc("delete_chat_conversation")
|
||||||
@console_ns.doc(description="Delete a chat conversation")
|
@console_ns.doc(description="Delete a chat conversation")
|
||||||
@ -588,7 +356,7 @@ class ChatConversationDetailApi(Resource):
|
|||||||
except ConversationNotExistsError:
|
except ConversationNotExistsError:
|
||||||
raise NotFound("Conversation Not Exists.")
|
raise NotFound("Conversation Not Exists.")
|
||||||
|
|
||||||
return {"result": "success"}, 204
|
return ResultResponse(result="success").model_dump(mode="json"), 204
|
||||||
|
|
||||||
|
|
||||||
def _get_conversation(app_model, conversation_id):
|
def _get_conversation(app_model, conversation_id):
|
||||||
|
|||||||
@ -96,7 +96,7 @@ class ConversationAnnotation(ResponseModel):
|
|||||||
|
|
||||||
|
|
||||||
class ConversationAnnotationHitHistory(ResponseModel):
|
class ConversationAnnotationHitHistory(ResponseModel):
|
||||||
annotation_id: str
|
annotation_id: str = Field(validation_alias="id")
|
||||||
annotation_create_account: SimpleAccount | None = None
|
annotation_create_account: SimpleAccount | None = None
|
||||||
created_at: int | None = None
|
created_at: int | None = None
|
||||||
|
|
||||||
@ -143,7 +143,7 @@ class MessageDetail(ResponseModel):
|
|||||||
query: str
|
query: str
|
||||||
message: JSONValue
|
message: JSONValue
|
||||||
message_tokens: int
|
message_tokens: int
|
||||||
answer: str
|
answer: str = Field(validation_alias="re_sign_file_url_answer")
|
||||||
answer_tokens: int
|
answer_tokens: int
|
||||||
provider_response_latency: float
|
provider_response_latency: float
|
||||||
from_source: str
|
from_source: str
|
||||||
@ -156,7 +156,7 @@ class MessageDetail(ResponseModel):
|
|||||||
created_at: int | None = None
|
created_at: int | None = None
|
||||||
agent_thoughts: list[AgentThought]
|
agent_thoughts: list[AgentThought]
|
||||||
message_files: list[MessageFile]
|
message_files: list[MessageFile]
|
||||||
metadata: JSONValue
|
metadata: JSONValue = Field(validation_alias="message_metadata_dict")
|
||||||
status: str
|
status: str
|
||||||
error: str | None = None
|
error: str | None = None
|
||||||
parent_message_id: str | None = None
|
parent_message_id: str | None = None
|
||||||
@ -196,7 +196,7 @@ class ModelConfig(ResponseModel):
|
|||||||
|
|
||||||
|
|
||||||
class SimpleModelConfig(ResponseModel):
|
class SimpleModelConfig(ResponseModel):
|
||||||
model: JSONValue | None = None
|
model: JSONValue | None = Field(default=None, validation_alias="model_dict")
|
||||||
pre_prompt: str | None = None
|
pre_prompt: str | None = None
|
||||||
|
|
||||||
|
|
||||||
@ -211,6 +211,11 @@ class SimpleMessageDetail(ResponseModel):
|
|||||||
def _normalize_inputs(cls, value: JSONValue) -> JSONValue:
|
def _normalize_inputs(cls, value: JSONValue) -> JSONValue:
|
||||||
return format_files_contained(value)
|
return format_files_contained(value)
|
||||||
|
|
||||||
|
@field_validator("message", mode="before")
|
||||||
|
@classmethod
|
||||||
|
def _normalize_message(cls, value: JSONValue) -> str:
|
||||||
|
return message_text(value)
|
||||||
|
|
||||||
|
|
||||||
class Conversation(ResponseModel):
|
class Conversation(ResponseModel):
|
||||||
id: str
|
id: str
|
||||||
@ -227,15 +232,22 @@ class Conversation(ResponseModel):
|
|||||||
model_config_: SimpleModelConfig | None = Field(default=None, alias="model_config")
|
model_config_: SimpleModelConfig | None = Field(default=None, alias="model_config")
|
||||||
user_feedback_stats: FeedbackStat | None = None
|
user_feedback_stats: FeedbackStat | None = None
|
||||||
admin_feedback_stats: FeedbackStat | None = None
|
admin_feedback_stats: FeedbackStat | None = None
|
||||||
message: SimpleMessageDetail | None = None
|
message: SimpleMessageDetail | None = Field(default=None, validation_alias="first_message")
|
||||||
|
|
||||||
|
@field_validator("read_at", "created_at", "updated_at", mode="before")
|
||||||
|
@classmethod
|
||||||
|
def _normalize_timestamp(cls, value: datetime | int | None) -> int | None:
|
||||||
|
if isinstance(value, datetime):
|
||||||
|
return to_timestamp(value)
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
class ConversationPagination(ResponseModel):
|
class ConversationPagination(ResponseModel):
|
||||||
page: int
|
page: int
|
||||||
limit: int
|
limit: int = Field(validation_alias="per_page")
|
||||||
total: int
|
total: int
|
||||||
has_more: bool
|
has_more: bool = Field(validation_alias="has_next")
|
||||||
data: list[Conversation]
|
data: list[Conversation] = Field(validation_alias="items")
|
||||||
|
|
||||||
|
|
||||||
class ConversationMessageDetail(ResponseModel):
|
class ConversationMessageDetail(ResponseModel):
|
||||||
@ -246,7 +258,14 @@ class ConversationMessageDetail(ResponseModel):
|
|||||||
from_account_id: str | None = None
|
from_account_id: str | None = None
|
||||||
created_at: int | None = None
|
created_at: int | None = None
|
||||||
model_config_: ModelConfig | None = Field(default=None, alias="model_config")
|
model_config_: ModelConfig | None = Field(default=None, alias="model_config")
|
||||||
message: MessageDetail | None = None
|
message: MessageDetail | None = Field(default=None, validation_alias="first_message")
|
||||||
|
|
||||||
|
@field_validator("created_at", mode="before")
|
||||||
|
@classmethod
|
||||||
|
def _normalize_created_at(cls, value: datetime | int | None) -> int | None:
|
||||||
|
if isinstance(value, datetime):
|
||||||
|
return to_timestamp(value)
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
class ConversationWithSummary(ResponseModel):
|
class ConversationWithSummary(ResponseModel):
|
||||||
@ -258,7 +277,7 @@ class ConversationWithSummary(ResponseModel):
|
|||||||
from_account_id: str | None = None
|
from_account_id: str | None = None
|
||||||
from_account_name: str | None = None
|
from_account_name: str | None = None
|
||||||
name: str
|
name: str
|
||||||
summary: str
|
summary: str = Field(validation_alias="summary_or_query")
|
||||||
read_at: int | None = None
|
read_at: int | None = None
|
||||||
created_at: int | None = None
|
created_at: int | None = None
|
||||||
updated_at: int | None = None
|
updated_at: int | None = None
|
||||||
@ -269,13 +288,20 @@ class ConversationWithSummary(ResponseModel):
|
|||||||
admin_feedback_stats: FeedbackStat | None = None
|
admin_feedback_stats: FeedbackStat | None = None
|
||||||
status_count: StatusCount | None = None
|
status_count: StatusCount | None = None
|
||||||
|
|
||||||
|
@field_validator("read_at", "created_at", "updated_at", mode="before")
|
||||||
|
@classmethod
|
||||||
|
def _normalize_timestamp(cls, value: datetime | int | None) -> int | None:
|
||||||
|
if isinstance(value, datetime):
|
||||||
|
return to_timestamp(value)
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
class ConversationWithSummaryPagination(ResponseModel):
|
class ConversationWithSummaryPagination(ResponseModel):
|
||||||
page: int
|
page: int
|
||||||
limit: int
|
limit: int = Field(validation_alias="per_page")
|
||||||
total: int
|
total: int
|
||||||
has_more: bool
|
has_more: bool = Field(validation_alias="has_next")
|
||||||
data: list[ConversationWithSummary]
|
data: list[ConversationWithSummary] = Field(validation_alias="items")
|
||||||
|
|
||||||
|
|
||||||
class ConversationDetail(ResponseModel):
|
class ConversationDetail(ResponseModel):
|
||||||
@ -293,6 +319,13 @@ class ConversationDetail(ResponseModel):
|
|||||||
user_feedback_stats: FeedbackStat | None = None
|
user_feedback_stats: FeedbackStat | None = None
|
||||||
admin_feedback_stats: FeedbackStat | None = None
|
admin_feedback_stats: FeedbackStat | None = None
|
||||||
|
|
||||||
|
@field_validator("created_at", "updated_at", mode="before")
|
||||||
|
@classmethod
|
||||||
|
def _normalize_timestamp(cls, value: datetime | int | None) -> int | None:
|
||||||
|
if isinstance(value, datetime):
|
||||||
|
return to_timestamp(value)
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
def to_timestamp(value: datetime | None) -> int | None:
|
def to_timestamp(value: datetime | None) -> int | None:
|
||||||
if value is None:
|
if value is None:
|
||||||
|
|||||||
@ -33,12 +33,17 @@ def test_completion_conversation_list_returns_paginated_result(app, monkeypatch:
|
|||||||
monkeypatch.setattr(conversation_module, "parse_time_range", lambda *_args, **_kwargs: (None, None))
|
monkeypatch.setattr(conversation_module, "parse_time_range", lambda *_args, **_kwargs: (None, None))
|
||||||
|
|
||||||
paginate_result = MagicMock()
|
paginate_result = MagicMock()
|
||||||
|
paginate_result.page = 1
|
||||||
|
paginate_result.per_page = 20
|
||||||
|
paginate_result.total = 0
|
||||||
|
paginate_result.has_next = False
|
||||||
|
paginate_result.items = []
|
||||||
monkeypatch.setattr(conversation_module.db, "paginate", lambda *_args, **_kwargs: paginate_result)
|
monkeypatch.setattr(conversation_module.db, "paginate", lambda *_args, **_kwargs: paginate_result)
|
||||||
|
|
||||||
with app.test_request_context("/console/api/apps/app-1/completion-conversations", method="GET"):
|
with app.test_request_context("/console/api/apps/app-1/completion-conversations", method="GET"):
|
||||||
response = method(app_model=SimpleNamespace(id="app-1"))
|
response = method(app_model=SimpleNamespace(id="app-1"))
|
||||||
|
|
||||||
assert response is paginate_result
|
assert response == {"page": 1, "limit": 20, "total": 0, "has_more": False, "data": []}
|
||||||
|
|
||||||
|
|
||||||
def test_completion_conversation_list_invalid_time_range(app, monkeypatch: pytest.MonkeyPatch) -> None:
|
def test_completion_conversation_list_invalid_time_range(app, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
@ -71,12 +76,17 @@ def test_chat_conversation_list_advanced_chat_calls_paginate(app, monkeypatch: p
|
|||||||
monkeypatch.setattr(conversation_module, "parse_time_range", lambda *_args, **_kwargs: (None, None))
|
monkeypatch.setattr(conversation_module, "parse_time_range", lambda *_args, **_kwargs: (None, None))
|
||||||
|
|
||||||
paginate_result = MagicMock()
|
paginate_result = MagicMock()
|
||||||
|
paginate_result.page = 1
|
||||||
|
paginate_result.per_page = 20
|
||||||
|
paginate_result.total = 0
|
||||||
|
paginate_result.has_next = False
|
||||||
|
paginate_result.items = []
|
||||||
monkeypatch.setattr(conversation_module.db, "paginate", lambda *_args, **_kwargs: paginate_result)
|
monkeypatch.setattr(conversation_module.db, "paginate", lambda *_args, **_kwargs: paginate_result)
|
||||||
|
|
||||||
with app.test_request_context("/console/api/apps/app-1/chat-conversations", method="GET"):
|
with app.test_request_context("/console/api/apps/app-1/chat-conversations", method="GET"):
|
||||||
response = method(app_model=SimpleNamespace(id="app-1", mode=AppMode.ADVANCED_CHAT))
|
response = method(app_model=SimpleNamespace(id="app-1", mode=AppMode.ADVANCED_CHAT))
|
||||||
|
|
||||||
assert response is paginate_result
|
assert response == {"page": 1, "limit": 20, "total": 0, "has_more": False, "data": []}
|
||||||
|
|
||||||
|
|
||||||
def test_get_conversation_updates_read_at(monkeypatch: pytest.MonkeyPatch) -> None:
|
def test_get_conversation_updates_read_at(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user