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:
NVIDIAN 2026-04-15 22:11:21 -07:00 committed by GitHub
parent b08665e598
commit b665eaa015
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 106 additions and 295 deletions

View File

@ -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):

View File

@ -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:

View File

@ -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: