refactor(api): migrate console app chat endpoints to BaseModel (#37952)

Co-authored-by: Asuka Minato <i@asukaminato.eu.org>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
chariri 2026-07-02 13:43:58 +09:00 committed by GitHub
parent 61650d34ce
commit 5d0576de0a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 457 additions and 528 deletions

View File

@ -1,7 +1,7 @@
from typing import Any, Literal
from uuid import UUID
from flask import abort, make_response, request
from flask import abort, request
from flask_restx import Resource
from pydantic import BaseModel, Field, TypeAdapter, field_validator
from sqlalchemy import select
@ -28,10 +28,12 @@ from fields.annotation_fields import (
AnnotationExportList,
AnnotationHitHistory,
AnnotationHitHistoryList,
AnnotationJobStatusDetailResponse,
AnnotationJobStatusResponse,
AnnotationList,
)
from fields.base import ResponseModel
from libs.helper import uuid_value
from libs.helper import dump_response, uuid_value
from libs.login import current_account_with_tenant, login_required
from models.model import App
from services.annotation_service import (
@ -113,23 +115,23 @@ class AnnotationFilePayload(BaseModel):
return uuid_value(value)
class AnnotationJobStatusResponse(ResponseModel):
job_id: str | None = None
job_status: str | None = None
error_msg: str | None = None
record_count: int | None = None
class AnnotationEmbeddingModelResponse(ResponseModel):
class AnnotationSettingEmbeddingModelResponse(ResponseModel):
embedding_provider_name: str | None = None
embedding_model_name: str | None = None
class AnnotationSettingResponse(ResponseModel):
id: str | None = None
enabled: bool
id: str | None = None
score_threshold: float | None = None
embedding_model: AnnotationEmbeddingModelResponse | None = None
embedding_model: AnnotationSettingEmbeddingModelResponse | None = None
class AnnotationBatchImportResponse(ResponseModel):
job_id: str | None = None
job_status: str | None = None
record_count: int | None = None
error_msg: str | None = None
register_schema_models(
@ -156,7 +158,10 @@ register_response_schema_models(
AnnotationHitHistory,
AnnotationHitHistoryList,
AnnotationJobStatusResponse,
AnnotationJobStatusDetailResponse,
AnnotationSettingEmbeddingModelResponse,
AnnotationSettingResponse,
AnnotationBatchImportResponse,
)
@ -186,7 +191,7 @@ class AnnotationReplyActionApi(Resource):
result = AppAnnotationService.enable_app_annotation(enable_args, str(app_id))
case "disable":
result = AppAnnotationService.disable_app_annotation(str(app_id))
return result, 200
return dump_response(AnnotationJobStatusResponse, result), 200
@console_ns.route("/apps/<uuid:app_id>/annotation-setting")
@ -207,7 +212,7 @@ class AppAnnotationSettingDetailApi(Resource):
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_VIEW_LAYOUT)
def get(self, app_id: UUID):
result = AppAnnotationService.get_app_annotation_setting_by_app_id(str(app_id))
return result, 200
return dump_response(AnnotationSettingResponse, result), 200
@console_ns.route("/apps/<uuid:app_id>/annotation-settings/<uuid:annotation_setting_id>")
@ -232,7 +237,7 @@ class AppAnnotationSettingUpdateApi(Resource):
result = AppAnnotationService.update_app_annotation_setting(
str(app_id), annotation_setting_id_str, setting_args
)
return result, 200
return dump_response(AnnotationSettingResponse, result), 200
@console_ns.route("/apps/<uuid:app_id>/annotation-reply/<string:action>/status/<uuid:job_id>")
@ -241,9 +246,7 @@ class AnnotationReplyActionStatusApi(Resource):
@console_ns.doc(description="Get status of annotation reply action job")
@console_ns.doc(params={"app_id": "Application ID", "job_id": "Job ID", "action": "Action type"})
@console_ns.response(
200,
"Job status retrieved successfully",
console_ns.models[AnnotationJobStatusResponse.__name__],
200, "Job status retrieved successfully", console_ns.models[AnnotationJobStatusDetailResponse.__name__]
)
@console_ns.response(403, "Insufficient permissions")
@setup_required
@ -265,7 +268,9 @@ class AnnotationReplyActionStatusApi(Resource):
app_annotation_error_key = f"{action}_app_annotation_error_{job_id_str}"
error_msg = redis_client.get(app_annotation_error_key).decode()
return {"job_id": job_id_str, "job_status": job_status, "error_msg": error_msg}, 200
return AnnotationJobStatusDetailResponse(
job_id=job_id_str, job_status=job_status, error_msg=error_msg
).model_dump(mode="json"), 200
@console_ns.route("/apps/<uuid:app_id>/annotations")
@ -289,14 +294,9 @@ class AnnotationApi(Resource):
annotation_list, total = AppAnnotationService.get_annotation_list_by_app_id(str(app_id), page, limit, keyword)
annotation_models = TypeAdapter(list[Annotation]).validate_python(annotation_list, from_attributes=True)
response = AnnotationList(
data=annotation_models,
has_more=len(annotation_list) == limit,
limit=limit,
total=total,
page=page,
)
return response.model_dump(mode="json"), 200
return AnnotationList(
data=annotation_models, has_more=len(annotation_list) == limit, limit=limit, total=total, page=page
).model_dump(mode="json"), 200
@console_ns.doc("create_annotation")
@console_ns.doc(description="Create a new annotation for an app")
@ -322,7 +322,7 @@ class AnnotationApi(Resource):
if args.question is not None:
upsert_args["question"] = args.question
annotation = AppAnnotationService.up_insert_app_annotation_from_message(upsert_args, str(app_id))
return Annotation.model_validate(annotation, from_attributes=True).model_dump(mode="json")
return dump_response(Annotation, annotation), 201
@setup_required
@login_required
@ -372,14 +372,14 @@ class AnnotationExportApi(Resource):
def get(self, app_id: UUID):
annotation_list = AppAnnotationService.export_annotation_list_by_app_id(str(app_id))
annotation_models = TypeAdapter(list[Annotation]).validate_python(annotation_list, from_attributes=True)
response_data = AnnotationExportList(data=annotation_models).model_dump(mode="json")
# Create response with secure headers for CSV export
response = make_response(response_data, 200)
response.headers["Content-Type"] = "application/json; charset=utf-8"
response.headers["X-Content-Type-Options"] = "nosniff"
return response
return (
AnnotationExportList(data=annotation_models).model_dump(mode="json"),
200,
{
"Content-Type": "application/json; charset=utf-8",
"X-Content-Type-Options": "nosniff",
},
)
@console_ns.route("/apps/<uuid:app_id>/annotations/<uuid:annotation_id>")
@ -428,9 +428,7 @@ class AnnotationBatchImportApi(Resource):
@console_ns.doc(description="Batch import annotations from CSV file with rate limiting and security checks")
@console_ns.doc(params={"app_id": "Application ID"})
@console_ns.response(
200,
"Batch import started successfully",
console_ns.models[AnnotationJobStatusResponse.__name__],
200, "Batch import started successfully", console_ns.models[AnnotationBatchImportResponse.__name__]
)
@console_ns.response(403, "Insufficient permissions")
@console_ns.response(400, "No file uploaded or too many files")
@ -477,7 +475,10 @@ class AnnotationBatchImportApi(Resource):
if file_size == 0:
raise ValueError("The uploaded file is empty")
return AppAnnotationService.batch_import_app_annotations(str(app_id), file)
return dump_response(
AnnotationBatchImportResponse,
AppAnnotationService.batch_import_app_annotations(str(app_id), file),
)
@console_ns.route("/apps/<uuid:app_id>/annotations/batch-import-status/<uuid:job_id>")
@ -486,9 +487,7 @@ class AnnotationBatchImportStatusApi(Resource):
@console_ns.doc(description="Get status of batch import job")
@console_ns.doc(params={"app_id": "Application ID", "job_id": "Job ID"})
@console_ns.response(
200,
"Job status retrieved successfully",
console_ns.models[AnnotationJobStatusResponse.__name__],
200, "Job status retrieved successfully", console_ns.models[AnnotationJobStatusDetailResponse.__name__]
)
@console_ns.response(403, "Insufficient permissions")
@setup_required
@ -508,7 +507,9 @@ class AnnotationBatchImportStatusApi(Resource):
indexing_error_msg_key = f"app_annotation_batch_import_error_msg_{str(job_id)}"
error_msg = redis_client.get(indexing_error_msg_key).decode()
return {"job_id": job_id, "job_status": job_status, "error_msg": error_msg}, 200
return AnnotationJobStatusDetailResponse(
job_id=str(job_id), job_status=job_status, error_msg=error_msg
).model_dump(mode="json"), 200
@console_ns.route("/apps/<uuid:app_id>/annotations/<uuid:annotation_id>/hit-histories")
@ -541,11 +542,6 @@ class AnnotationHitHistoryListApi(Resource):
history_models = TypeAdapter(list[AnnotationHitHistory]).validate_python(
annotation_hit_history_list, from_attributes=True
)
response = AnnotationHitHistoryList(
data=history_models,
has_more=len(annotation_hit_history_list) == limit,
limit=limit,
total=total,
page=page,
)
return response.model_dump(mode="json")
return AnnotationHitHistoryList(
data=history_models, has_more=len(annotation_hit_history_list) == limit, limit=limit, total=total, page=page
).model_dump(mode="json")

View File

@ -1,5 +1,4 @@
import logging
from typing import Any
from flask import request
from flask_restx import Resource
@ -7,7 +6,6 @@ from pydantic import BaseModel, Field, RootModel
from werkzeug.exceptions import InternalServerError
import services
from controllers.common.fields import AudioBinaryResponse
from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models
from controllers.console import console_ns
from controllers.console.app.error import (
@ -31,7 +29,9 @@ from controllers.console.wraps import (
)
from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError
from extensions.ext_database import db
from fields.base import ResponseModel
from graphon.model_runtime.errors.invoke import InvokeError
from libs.helper import dump_response
from libs.login import current_user, login_required
from models import App, AppMode
from services.app_ref_service import AppRefService
@ -57,16 +57,27 @@ class TextToSpeechVoiceQuery(BaseModel):
language: str = Field(..., description="Language code")
class AudioTranscriptResponse(BaseModel):
class AudioTranscriptResponse(ResponseModel):
text: str = Field(description="Transcribed text from audio")
class TextToSpeechVoiceListResponse(RootModel[list[dict[str, Any]]]):
root: list[dict[str, Any]]
class TextToSpeechVoiceResponse(ResponseModel):
# see api/core/plugin/impl/model.py
name: str = Field(description="Voice display name")
value: str = Field(description="Voice identifier")
register_schema_models(console_ns, AudioTranscriptResponse, TextToSpeechPayload, TextToSpeechVoiceQuery)
register_response_schema_models(console_ns, AudioBinaryResponse, TextToSpeechVoiceListResponse)
class TextToSpeechVoiceListResponse(RootModel[list[TextToSpeechVoiceResponse]]):
root: list[TextToSpeechVoiceResponse] = Field(description="Available voices")
register_schema_models(console_ns, TextToSpeechPayload, TextToSpeechVoiceQuery)
register_response_schema_models(
console_ns,
AudioTranscriptResponse,
TextToSpeechVoiceResponse,
TextToSpeechVoiceListResponse,
)
@console_ns.route("/apps/<uuid:app_id>/audio-to-text")
@ -95,7 +106,7 @@ class ChatMessageAudioApi(Resource):
end_user=None,
)
return response
return dump_response(AudioTranscriptResponse, response)
except services.errors.app_model_config.AppModelConfigBrokenError:
logger.exception("App model config broken.")
raise AppUnavailableError()
@ -128,11 +139,8 @@ class ChatMessageTextApi(Resource):
@console_ns.doc(description="Convert text to speech for chat messages")
@console_ns.doc(params={"app_id": "App ID"})
@console_ns.expect(console_ns.models[TextToSpeechPayload.__name__])
@console_ns.response(
200,
"Text to speech conversion successful",
console_ns.models[AudioBinaryResponse.__name__],
)
# TTS returns provider audio bytes, so the success response is intentionally schema-less.
@console_ns.response(200, "Text to speech conversion successful")
@console_ns.response(400, "Bad request - Invalid parameters")
@setup_required
@login_required
@ -150,7 +158,8 @@ class ChatMessageTextApi(Resource):
account_id=current_user.id,
)
response = AudioService.transcript_tts(
# response-contract:ignore
return AudioService.transcript_tts(
app_model=app_model,
session=db.session,
text=payload.text,
@ -158,7 +167,6 @@ class ChatMessageTextApi(Resource):
message_ref=message_ref,
is_draft=True,
)
return response
except services.errors.app_model_config.AppModelConfigBrokenError:
logger.exception("App model config broken.")
raise AppUnavailableError()
@ -189,8 +197,7 @@ class ChatMessageTextApi(Resource):
class TextModesApi(Resource):
@console_ns.doc("get_text_to_speech_voices")
@console_ns.doc(description="Get available TTS voices for a specific language")
@console_ns.doc(params={"app_id": "App ID"})
@console_ns.doc(params=query_params_from_model(TextToSpeechVoiceQuery))
@console_ns.doc(params={"app_id": "App ID", **query_params_from_model(TextToSpeechVoiceQuery)})
@console_ns.response(
200,
"TTS voices retrieved successfully",
@ -211,7 +218,7 @@ class TextModesApi(Resource):
language=args.language,
)
return response
return dump_response(TextToSpeechVoiceListResponse, response)
except services.errors.audio.ProviderNotSupportTextToSpeechLanageServiceError:
raise AppUnavailableError("Text to audio voices language parameter loss.")
except NoAudioUploadedServiceError:

View File

@ -10,7 +10,7 @@ from pydantic import BaseModel, Field, field_validator
from werkzeug.exceptions import BadRequest, InternalServerError, NotFound
import services
from controllers.common.fields import GeneratedAppResponse, SimpleResultResponse
from controllers.common.fields import SimpleResultResponse
from controllers.common.schema import register_response_schema_models, register_schema_models
from controllers.console import console_ns
from controllers.console.agent.app_helpers import resolve_agent_runtime_app_model
@ -136,7 +136,7 @@ After config persistence completes, respond FINISHED."""
register_schema_models(console_ns, CompletionMessagePayload, ChatMessagePayload)
register_response_schema_models(console_ns, GeneratedAppResponse, SimpleResultResponse)
register_response_schema_models(console_ns, SimpleResultResponse)
# define completion message api for user
@ -146,7 +146,7 @@ class CompletionMessageApi(Resource):
@console_ns.doc(description="Generate completion message for debugging")
@console_ns.doc(params={"app_id": "Application ID"})
@console_ns.expect(console_ns.models[CompletionMessagePayload.__name__])
@console_ns.response(200, "Completion generated successfully", console_ns.models[GeneratedAppResponse.__name__])
@console_ns.response(200, "Completion generated successfully")
@console_ns.response(400, "Invalid request parameters")
@console_ns.response(404, "App not found")
@setup_required
@ -167,6 +167,7 @@ class CompletionMessageApi(Resource):
app_model=app_model, user=current_user, args=args, invoke_from=InvokeFrom.DEBUGGER, streaming=streaming
)
# response-contract:ignore compact_generate_response
return helper.compact_generate_response(response)
except services.errors.conversation.ConversationNotExistsError:
raise NotFound("Conversation Not Exists.")
@ -210,7 +211,7 @@ class CompletionMessageStopApi(Resource):
app_mode=AppMode.value_of(app_model.mode),
)
return {"result": "success"}, 200
return SimpleResultResponse(result="success").model_dump(mode="json"), 200
@console_ns.route("/apps/<uuid:app_id>/chat-messages")
@ -219,7 +220,7 @@ class ChatMessageApi(Resource):
@console_ns.doc(description="Generate chat message for debugging")
@console_ns.doc(params={"app_id": "Application ID"})
@console_ns.expect(console_ns.models[ChatMessagePayload.__name__])
@console_ns.response(200, "Chat message generated successfully", console_ns.models[GeneratedAppResponse.__name__])
@console_ns.response(200, "Chat message generated successfully")
@console_ns.response(400, "Invalid request parameters")
@console_ns.response(404, "App or conversation not found")
@setup_required
@ -240,7 +241,7 @@ class AgentChatMessageApi(Resource):
@console_ns.doc(description="Generate an Agent App chat message for debugging")
@console_ns.doc(params={"agent_id": "Agent ID"})
@console_ns.expect(console_ns.models[ChatMessagePayload.__name__])
@console_ns.response(200, "Chat message generated successfully", console_ns.models[GeneratedAppResponse.__name__])
@console_ns.response(200, "Chat message generated successfully")
@console_ns.response(400, "Invalid request parameters")
@console_ns.response(404, "Agent or conversation not found")
@setup_required
@ -520,4 +521,4 @@ def _stop_chat_message(*, current_user_id: str, app_model: App, task_id: str):
app_mode=AppMode.value_of(app_model.mode),
)
return {"result": "success"}, 200
return SimpleResultResponse(result="success").model_dump(mode="json"), 200

View File

@ -9,7 +9,7 @@ from sqlalchemy import func, or_
from sqlalchemy.orm import selectinload
from werkzeug.exceptions import NotFound
from controllers.common.schema import query_params_from_model, register_schema_models
from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models
from controllers.console import console_ns
from controllers.console.app.wraps import get_app_model
from controllers.console.wraps import (
@ -39,6 +39,7 @@ from fields.conversation_fields import (
ConversationWithSummaryPagination as ConversationWithSummaryPaginationResponse,
)
from libs.datetime_utils import naive_utc_now, parse_time_range
from libs.helper import dump_response
from libs.login import login_required
from models import Conversation, EndUser, Message, MessageAnnotation
from models.account import Account
@ -79,13 +80,14 @@ register_schema_models(
console_ns,
CompletionConversationQuery,
ChatConversationQuery,
)
register_response_schema_models(
console_ns,
ConversationResponse,
ConversationPaginationResponse,
ConversationMessageDetailResponse,
ConversationWithSummaryPaginationResponse,
ConversationDetailResponse,
CompletionConversationQuery,
ChatConversationQuery,
)
@ -93,8 +95,7 @@ register_schema_models(
class CompletionConversationApi(Resource):
@console_ns.doc("list_completion_conversations")
@console_ns.doc(description="Get completion conversations with pagination and filtering")
@console_ns.doc(params={"app_id": "Application ID"})
@console_ns.doc(params=query_params_from_model(CompletionConversationQuery))
@console_ns.doc(params={"app_id": "Application ID", **query_params_from_model(CompletionConversationQuery)})
@console_ns.response(200, "Success", console_ns.models[ConversationPaginationResponse.__name__])
@console_ns.response(403, "Insufficient permissions")
@setup_required
@ -157,9 +158,7 @@ class CompletionConversationApi(Resource):
conversations = db.paginate(query, page=args.page, per_page=args.limit, error_out=False)
return ConversationPaginationResponse.model_validate(conversations, from_attributes=True).model_dump(
mode="json"
)
return dump_response(ConversationPaginationResponse, conversations)
@console_ns.route("/apps/<uuid:app_id>/completion-conversations/<uuid:conversation_id>")
@ -179,9 +178,9 @@ class CompletionConversationDetailApi(Resource):
@get_app_model(mode=AppMode.COMPLETION)
def get(self, current_user: Account, app_model: App, conversation_id: UUID):
conversation_id_str = str(conversation_id)
return ConversationMessageDetailResponse.model_validate(
_get_conversation(current_user, app_model, conversation_id_str), from_attributes=True
).model_dump(mode="json")
return dump_response(
ConversationMessageDetailResponse, _get_conversation(current_user, app_model, conversation_id_str)
)
@console_ns.doc("delete_completion_conversation")
@console_ns.doc(description="Delete a completion conversation")
@ -211,8 +210,7 @@ class CompletionConversationDetailApi(Resource):
class ChatConversationApi(Resource):
@console_ns.doc("list_chat_conversations")
@console_ns.doc(description="Get chat conversations with pagination, filtering and summary")
@console_ns.doc(params={"app_id": "Application ID"})
@console_ns.doc(params=query_params_from_model(ChatConversationQuery))
@console_ns.doc(params={"app_id": "Application ID", **query_params_from_model(ChatConversationQuery)})
@console_ns.response(200, "Success", console_ns.models[ConversationWithSummaryPaginationResponse.__name__])
@console_ns.response(403, "Insufficient permissions")
@setup_required
@ -314,9 +312,7 @@ class ChatConversationApi(Resource):
conversations = db.paginate(query, page=args.page, per_page=args.limit, error_out=False)
return ConversationWithSummaryPaginationResponse.model_validate(conversations, from_attributes=True).model_dump(
mode="json"
)
return dump_response(ConversationWithSummaryPaginationResponse, conversations)
@console_ns.route("/apps/<uuid:app_id>/chat-conversations/<uuid:conversation_id>")
@ -336,9 +332,9 @@ class ChatConversationDetailApi(Resource):
@get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT, AppMode.AGENT])
def get(self, current_user: Account, app_model: App, conversation_id: UUID):
conversation_id_str = str(conversation_id)
return ConversationDetailResponse.model_validate(
_get_conversation(current_user, app_model, conversation_id_str), from_attributes=True
).model_dump(mode="json")
return dump_response(
ConversationDetailResponse, _get_conversation(current_user, app_model, conversation_id_str)
)
@console_ns.doc("delete_chat_conversation")
@console_ns.doc(description="Delete a chat conversation")

View File

@ -22,7 +22,7 @@ from controllers.console.wraps import (
from extensions.ext_database import db
from fields._value_type_serializer import serialize_value_type
from fields.base import ResponseModel
from libs.helper import to_timestamp
from libs.helper import dump_response, to_timestamp
from libs.login import login_required
from models import ConversationVariable
from models.model import App, AppMode
@ -119,7 +119,8 @@ class ConversationVariablesApi(Resource):
with sessionmaker(db.engine, expire_on_commit=False).begin() as session:
rows = session.scalars(stmt).all()
response = PaginatedConversationVariableResponse.model_validate(
return dump_response(
PaginatedConversationVariableResponse,
{
"page": page,
"limit": page_size,
@ -135,6 +136,5 @@ class ConversationVariablesApi(Resource):
)
for row in rows
],
}
},
)
return response.model_dump(mode="json")

View File

@ -1,5 +1,4 @@
import logging
from datetime import datetime
from typing import Literal
from uuid import UUID
@ -38,16 +37,10 @@ from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotIni
from extensions.ext_database import db
from fields.base import ResponseModel
from fields.conversation_fields import (
AgentThought,
ConversationAnnotation,
ConversationAnnotationHitHistory,
Feedback,
JSONValue,
MessageFile,
format_files_contained,
MessageDetail as BaseMessageDetailResponse,
)
from graphon.model_runtime.errors.invoke import InvokeError
from libs.helper import to_timestamp, uuid_value
from libs.helper import dump_response, uuid_value
from libs.infinite_scroll_pagination import InfiniteScrollPagination
from libs.login import login_required
from models.account import Account
@ -111,49 +104,16 @@ class FeedbackExportQuery(BaseModel):
raise ValueError("has_comment must be a boolean value")
class AnnotationCountResponse(BaseModel):
class AnnotationCountResponse(ResponseModel):
count: int = Field(description="Number of annotations")
class SuggestedQuestionsResponse(BaseModel):
class SuggestedQuestionsResponse(ResponseModel):
data: list[str] = Field(description="Suggested question")
class MessageDetailResponse(ResponseModel):
id: str
conversation_id: str
inputs: dict[str, JSONValue]
query: str
message: JSONValue | None = None
message_tokens: int | None = None
answer: str = Field(validation_alias="re_sign_file_url_answer")
answer_tokens: int | None = None
provider_response_latency: float | None = None
from_source: str
from_end_user_id: str | None = None
from_account_id: str | None = None
feedbacks: list[Feedback] = Field(default_factory=list)
workflow_run_id: str | None = None
annotation: ConversationAnnotation | None = None
annotation_hit_history: ConversationAnnotationHitHistory | None = None
created_at: int | None = None
agent_thoughts: list[AgentThought] = Field(default_factory=list)
message_files: list[MessageFile] = Field(default_factory=list)
class MessageDetailResponse(BaseMessageDetailResponse):
extra_contents: list[ExecutionExtraContentDomainModel] = Field(default_factory=list)
metadata: JSONValue | None = Field(default=None, validation_alias="message_metadata_dict")
status: str
error: str | None = None
parent_message_id: str | None = None
@field_validator("inputs", mode="before")
@classmethod
def _normalize_inputs(cls, value: JSONValue) -> JSONValue:
return format_files_contained(value)
@field_validator("created_at", mode="before")
@classmethod
def _normalize_created_at(cls, value: datetime | int | None) -> int | None:
return to_timestamp(value)
class MessageInfiniteScrollPaginationResponse(ResponseModel):
@ -183,8 +143,7 @@ register_response_schema_models(
class ChatMessageListApi(Resource):
@console_ns.doc("list_chat_messages")
@console_ns.doc(description="Get chat messages for a conversation with pagination")
@console_ns.doc(params={"app_id": "Application ID"})
@console_ns.doc(params=query_params_from_model(ChatMessagesQuery))
@console_ns.doc(params={"app_id": "Application ID", **query_params_from_model(ChatMessagesQuery)})
@console_ns.response(200, "Success", console_ns.models[MessageInfiniteScrollPaginationResponse.__name__])
@console_ns.response(404, "Conversation not found")
@login_required
@ -274,7 +233,7 @@ class MessageAnnotationCountApi(Resource):
select(func.count(MessageAnnotation.id)).where(MessageAnnotation.app_id == app_model.id)
)
return {"count": count}
return AnnotationCountResponse(count=count or 0).model_dump(mode="json")
@console_ns.route("/apps/<uuid:app_id>/chat-messages/<uuid:message_id>/suggested-questions")
@ -323,13 +282,12 @@ class AgentMessageSuggestedQuestionApi(Resource):
class MessageFeedbackExportApi(Resource):
@console_ns.doc("export_feedbacks")
@console_ns.doc(description="Export user feedback data for Google Sheets")
@console_ns.doc(params={"app_id": "Application ID"})
@console_ns.doc(params=query_params_from_model(FeedbackExportQuery))
@console_ns.response(
200,
"Feedback data exported successfully",
console_ns.models[TextFileResponse.__name__],
)
@console_ns.doc(params={"app_id": "Application ID", **query_params_from_model(FeedbackExportQuery)})
@console_ns.response(400, "Invalid parameters")
@console_ns.response(500, "Internal server error")
@setup_required
@ -354,7 +312,6 @@ class MessageFeedbackExportApi(Resource):
end_date=args.end_date,
format_type=args.format,
)
return export_data
except ValueError as e:
@ -465,16 +422,16 @@ def _list_chat_messages(*, app_model: App, current_user: Account | None = None):
history_messages = list(reversed(history_messages))
attach_message_extra_contents(history_messages)
return MessageInfiniteScrollPaginationResponse.model_validate(
return dump_response(
MessageInfiniteScrollPaginationResponse,
InfiniteScrollPagination(data=history_messages, limit=args.limit, has_more=has_more),
from_attributes=True,
).model_dump(mode="json")
)
def _update_message_feedback(*, current_user: Account, app_model: App):
args = MessageFeedbackPayload.model_validate(console_ns.payload)
message_id = str(args.message_id)
message_id = args.message_id
message = db.session.scalar(
select(Message).where(Message.id == message_id, Message.app_id == app_model.id).limit(1)
@ -509,7 +466,7 @@ def _update_message_feedback(*, current_user: Account, app_model: App):
db.session.commit()
return {"result": "success"}
return SimpleResultResponse(result="success").model_dump(mode="json")
def _get_message_suggested_questions(*, current_user: Account, app_model: App, message_id: UUID):
@ -537,7 +494,7 @@ def _get_message_suggested_questions(*, current_user: Account, app_model: App, m
logger.exception("internal server error.")
raise InternalServerError()
return {"data": questions}
return dump_response(SuggestedQuestionsResponse, {"data": questions})
def _get_message_detail(*, app_model: App, message_id: UUID):
@ -551,4 +508,4 @@ def _get_message_detail(*, app_model: App, message_id: UUID):
raise NotFound("Message Not Exists.")
attach_message_extra_contents([message])
return MessageDetailResponse.model_validate(message, from_attributes=True).model_dump(mode="json")
return dump_response(MessageDetailResponse, message)

View File

@ -1,7 +1,7 @@
from decimal import Decimal
import sqlalchemy as sa
from flask import abort, jsonify, request
from flask import abort, request
from flask_restx import Resource
from pydantic import BaseModel, Field, field_validator
@ -20,7 +20,7 @@ from core.app.entities.app_invoke_entities import InvokeFrom
from extensions.ext_database import db
from fields.base import ResponseModel
from libs.datetime_utils import parse_time_range
from libs.helper import convert_datetime_to_date
from libs.helper import convert_datetime_to_date, dump_response
from libs.login import login_required
from models import AppMode
from models.account import Account
@ -44,8 +44,15 @@ class DailyMessageStatisticItem(ResponseModel):
message_count: int
class DailyMessageStatisticResponse(ResponseModel):
data: list[DailyMessageStatisticItem]
register_schema_models(console_ns, StatisticTimeRangeQuery)
class StatisticDataResponse[T](ResponseModel):
data: list[T]
class DailyMessageStatisticResponse(StatisticDataResponse[DailyMessageStatisticItem]):
pass
class DailyConversationStatisticItem(ResponseModel):
@ -53,8 +60,8 @@ class DailyConversationStatisticItem(ResponseModel):
conversation_count: int
class DailyConversationStatisticResponse(ResponseModel):
data: list[DailyConversationStatisticItem]
class DailyConversationStatisticResponse(StatisticDataResponse[DailyConversationStatisticItem]):
pass
class DailyTerminalStatisticItem(ResponseModel):
@ -62,19 +69,19 @@ class DailyTerminalStatisticItem(ResponseModel):
terminal_count: int
class DailyTerminalStatisticResponse(ResponseModel):
data: list[DailyTerminalStatisticItem]
class DailyTerminalStatisticResponse(StatisticDataResponse[DailyTerminalStatisticItem]):
pass
class DailyTokenCostStatisticItem(ResponseModel):
date: str
token_count: int
total_price: str | float
currency: str
token_count: int | None = None
total_price: Decimal | None = None
currency: str | None = None
class DailyTokenCostStatisticResponse(ResponseModel):
data: list[DailyTokenCostStatisticItem]
class DailyTokenCostStatisticResponse(StatisticDataResponse[DailyTokenCostStatisticItem]):
pass
class AverageSessionInteractionStatisticItem(ResponseModel):
@ -82,8 +89,8 @@ class AverageSessionInteractionStatisticItem(ResponseModel):
interactions: float
class AverageSessionInteractionStatisticResponse(ResponseModel):
data: list[AverageSessionInteractionStatisticItem]
class AverageSessionInteractionStatisticResponse(StatisticDataResponse[AverageSessionInteractionStatisticItem]):
pass
class UserSatisfactionRateStatisticItem(ResponseModel):
@ -91,8 +98,8 @@ class UserSatisfactionRateStatisticItem(ResponseModel):
rate: float
class UserSatisfactionRateStatisticResponse(ResponseModel):
data: list[UserSatisfactionRateStatisticItem]
class UserSatisfactionRateStatisticResponse(StatisticDataResponse[UserSatisfactionRateStatisticItem]):
pass
class AverageResponseTimeStatisticItem(ResponseModel):
@ -100,8 +107,8 @@ class AverageResponseTimeStatisticItem(ResponseModel):
latency: float
class AverageResponseTimeStatisticResponse(ResponseModel):
data: list[AverageResponseTimeStatisticItem]
class AverageResponseTimeStatisticResponse(StatisticDataResponse[AverageResponseTimeStatisticItem]):
pass
class TokensPerSecondStatisticItem(ResponseModel):
@ -109,20 +116,27 @@ class TokensPerSecondStatisticItem(ResponseModel):
tps: float
class TokensPerSecondStatisticResponse(ResponseModel):
data: list[TokensPerSecondStatisticItem]
class TokensPerSecondStatisticResponse(StatisticDataResponse[TokensPerSecondStatisticItem]):
pass
register_schema_models(console_ns, StatisticTimeRangeQuery)
register_response_schema_models(
console_ns,
DailyMessageStatisticItem,
DailyMessageStatisticResponse,
DailyConversationStatisticItem,
DailyConversationStatisticResponse,
DailyTerminalStatisticItem,
DailyTerminalStatisticResponse,
DailyTokenCostStatisticItem,
DailyTokenCostStatisticResponse,
AverageSessionInteractionStatisticItem,
AverageSessionInteractionStatisticResponse,
UserSatisfactionRateStatisticItem,
UserSatisfactionRateStatisticResponse,
AverageResponseTimeStatisticItem,
AverageResponseTimeStatisticResponse,
TokensPerSecondStatisticItem,
TokensPerSecondStatisticResponse,
)
@ -131,8 +145,7 @@ register_response_schema_models(
class DailyMessageStatistic(Resource):
@console_ns.doc("get_daily_message_statistics")
@console_ns.doc(description="Get daily message statistics for an application")
@console_ns.doc(params={"app_id": "Application ID"})
@console_ns.doc(params=query_params_from_model(StatisticTimeRangeQuery))
@console_ns.doc(params={"app_id": "Application ID", **query_params_from_model(StatisticTimeRangeQuery)})
@console_ns.response(
200,
"Daily message statistics retrieved successfully",
@ -185,15 +198,14 @@ WHERE
for i in rs:
response_data.append({"date": str(i.date), "message_count": i.message_count})
return jsonify({"data": response_data})
return dump_response(DailyMessageStatisticResponse, {"data": response_data})
@console_ns.route("/apps/<uuid:app_id>/statistics/daily-conversations")
class DailyConversationStatistic(Resource):
@console_ns.doc("get_daily_conversation_statistics")
@console_ns.doc(description="Get daily conversation statistics for an application")
@console_ns.doc(params={"app_id": "Application ID"})
@console_ns.doc(params=query_params_from_model(StatisticTimeRangeQuery))
@console_ns.doc(params={"app_id": "Application ID", **query_params_from_model(StatisticTimeRangeQuery)})
@console_ns.response(
200,
"Daily conversation statistics retrieved successfully",
@ -245,15 +257,14 @@ WHERE
for i in rs:
response_data.append({"date": str(i.date), "conversation_count": i.conversation_count})
return jsonify({"data": response_data})
return dump_response(DailyConversationStatisticResponse, {"data": response_data})
@console_ns.route("/apps/<uuid:app_id>/statistics/daily-end-users")
class DailyTerminalsStatistic(Resource):
@console_ns.doc("get_daily_terminals_statistics")
@console_ns.doc(description="Get daily terminal/end-user statistics for an application")
@console_ns.doc(params={"app_id": "Application ID"})
@console_ns.doc(params=query_params_from_model(StatisticTimeRangeQuery))
@console_ns.doc(params={"app_id": "Application ID", **query_params_from_model(StatisticTimeRangeQuery)})
@console_ns.response(
200,
"Daily terminal statistics retrieved successfully",
@ -306,15 +317,14 @@ WHERE
for i in rs:
response_data.append({"date": str(i.date), "terminal_count": i.terminal_count})
return jsonify({"data": response_data})
return dump_response(DailyTerminalStatisticResponse, {"data": response_data})
@console_ns.route("/apps/<uuid:app_id>/statistics/token-costs")
class DailyTokenCostStatistic(Resource):
@console_ns.doc("get_daily_token_cost_statistics")
@console_ns.doc(description="Get daily token cost statistics for an application")
@console_ns.doc(params={"app_id": "Application ID"})
@console_ns.doc(params=query_params_from_model(StatisticTimeRangeQuery))
@console_ns.doc(params={"app_id": "Application ID", **query_params_from_model(StatisticTimeRangeQuery)})
@console_ns.response(
200,
"Daily token cost statistics retrieved successfully",
@ -370,15 +380,14 @@ WHERE
{"date": str(i.date), "token_count": i.token_count, "total_price": i.total_price, "currency": "USD"}
)
return jsonify({"data": response_data})
return dump_response(DailyTokenCostStatisticResponse, {"data": response_data})
@console_ns.route("/apps/<uuid:app_id>/statistics/average-session-interactions")
class AverageSessionInteractionStatistic(Resource):
@console_ns.doc("get_average_session_interaction_statistics")
@console_ns.doc(description="Get average session interaction statistics for an application")
@console_ns.doc(params={"app_id": "Application ID"})
@console_ns.doc(params=query_params_from_model(StatisticTimeRangeQuery))
@console_ns.doc(params={"app_id": "Application ID", **query_params_from_model(StatisticTimeRangeQuery)})
@console_ns.response(
200,
"Average session interaction statistics retrieved successfully",
@ -450,15 +459,14 @@ ORDER BY
{"date": str(i.date), "interactions": float(i.interactions.quantize(Decimal("0.01")))}
)
return jsonify({"data": response_data})
return dump_response(AverageSessionInteractionStatisticResponse, {"data": response_data})
@console_ns.route("/apps/<uuid:app_id>/statistics/user-satisfaction-rate")
class UserSatisfactionRateStatistic(Resource):
@console_ns.doc("get_user_satisfaction_rate_statistics")
@console_ns.doc(description="Get user satisfaction rate statistics for an application")
@console_ns.doc(params={"app_id": "Application ID"})
@console_ns.doc(params=query_params_from_model(StatisticTimeRangeQuery))
@console_ns.doc(params={"app_id": "Application ID", **query_params_from_model(StatisticTimeRangeQuery)})
@console_ns.response(
200,
"User satisfaction rate statistics retrieved successfully",
@ -520,15 +528,14 @@ WHERE
}
)
return jsonify({"data": response_data})
return dump_response(UserSatisfactionRateStatisticResponse, {"data": response_data})
@console_ns.route("/apps/<uuid:app_id>/statistics/average-response-time")
class AverageResponseTimeStatistic(Resource):
@console_ns.doc("get_average_response_time_statistics")
@console_ns.doc(description="Get average response time statistics for an application")
@console_ns.doc(params={"app_id": "Application ID"})
@console_ns.doc(params=query_params_from_model(StatisticTimeRangeQuery))
@console_ns.doc(params={"app_id": "Application ID", **query_params_from_model(StatisticTimeRangeQuery)})
@console_ns.response(
200,
"Average response time statistics retrieved successfully",
@ -581,15 +588,14 @@ WHERE
for i in rs:
response_data.append({"date": str(i.date), "latency": round(i.latency * 1000, 4)})
return jsonify({"data": response_data})
return dump_response(AverageResponseTimeStatisticResponse, {"data": response_data})
@console_ns.route("/apps/<uuid:app_id>/statistics/tokens-per-second")
class TokensPerSecondStatistic(Resource):
@console_ns.doc("get_tokens_per_second_statistics")
@console_ns.doc(description="Get tokens per second statistics for an application")
@console_ns.doc(params={"app_id": "Application ID"})
@console_ns.doc(params=query_params_from_model(StatisticTimeRangeQuery))
@console_ns.doc(params={"app_id": "Application ID", **query_params_from_model(StatisticTimeRangeQuery)})
@console_ns.response(
200,
"Tokens per second statistics retrieved successfully",
@ -645,4 +651,4 @@ WHERE
for i in rs:
response_data.append({"date": str(i.date), "tps": round(i.tokens_per_second, 4)})
return jsonify({"data": response_data})
return dump_response(TokensPerSecondStatisticResponse, {"data": response_data})

View File

@ -1,6 +1,7 @@
from __future__ import annotations
from datetime import datetime
from typing import Literal
from pydantic import Field, field_validator
@ -29,6 +30,15 @@ class AnnotationList(ResponseModel):
page: int
class AnnotationJobStatusResponse(ResponseModel):
job_id: str
job_status: Literal["waiting", "processing", "completed", "error"] | str
class AnnotationJobStatusDetailResponse(AnnotationJobStatusResponse):
error_msg: str = ""
class AnnotationExportList(ResponseModel):
data: list[Annotation]

View File

@ -3,25 +3,14 @@ from __future__ import annotations
from datetime import datetime
from typing import Any
from flask_restx import fields
from pydantic import field_validator
from fields.base import ResponseModel
from graphon.variables.types import SegmentType
from libs.helper import TimestampField, to_timestamp
from libs.helper import to_timestamp
from ._value_type_serializer import serialize_value_type
conversation_variable_fields = {
"id": fields.String,
"name": fields.String,
"value_type": fields.String(attribute=serialize_value_type),
"value": fields.String,
"description": fields.String,
"created_at": TimestampField,
"updated_at": TimestampField,
}
class ConversationVariableResponse(ResponseModel):
id: str

View File

@ -2371,7 +2371,7 @@ Get status of annotation reply action job
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Job status retrieved successfully | **application/json**: [AnnotationJobStatusResponse](#annotationjobstatusresponse)<br> |
| 200 | Job status retrieved successfully | **application/json**: [AnnotationJobStatusDetailResponse](#annotationjobstatusdetailresponse)<br> |
| 403 | Insufficient permissions | |
### [GET] /apps/{app_id}/annotation-setting
@ -2480,7 +2480,7 @@ Batch import annotations from CSV file with rate limiting and security checks
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Batch import started successfully | **application/json**: [AnnotationJobStatusResponse](#annotationjobstatusresponse)<br> |
| 200 | Batch import started successfully | **application/json**: [AnnotationBatchImportResponse](#annotationbatchimportresponse)<br> |
| 400 | No file uploaded or too many files | |
| 403 | Insufficient permissions | |
| 413 | File too large | |
@ -2500,7 +2500,7 @@ Get status of batch import job
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Job status retrieved successfully | **application/json**: [AnnotationJobStatusResponse](#annotationjobstatusresponse)<br> |
| 200 | Job status retrieved successfully | **application/json**: [AnnotationJobStatusDetailResponse](#annotationjobstatusdetailresponse)<br> |
| 403 | Insufficient permissions | |
### [GET] /apps/{app_id}/annotations/count
@ -2816,11 +2816,11 @@ Generate completion message for debugging
#### Responses
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Completion generated successfully | **application/json**: [GeneratedAppResponse](#generatedappresponse)<br> |
| 400 | Invalid request parameters | |
| 404 | App not found | |
| Code | Description |
| ---- | ----------- |
| 200 | Completion generated successfully |
| 400 | Invalid request parameters |
| 404 | App not found |
### [POST] /apps/{app_id}/completion-messages/{task_id}/stop
Stop a running completion message generation
@ -3379,10 +3379,10 @@ Convert text to speech for chat messages
#### Responses
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Text to speech conversion successful | **application/json**: [AudioBinaryResponse](#audiobinaryresponse)<br> |
| 400 | Bad request - Invalid parameters | |
| Code | Description |
| ---- | ----------- |
| 200 | Text to speech conversion successful |
| 400 | Bad request - Invalid parameters |
### [GET] /apps/{app_id}/text-to-audio/voices
Get available TTS voices for a specific language
@ -14644,19 +14644,21 @@ Soft lifecycle state for Agent records.
| id | string | | Yes |
| question | string | | No |
#### AnnotationBatchImportResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| error_msg | string | | No |
| job_id | string | | No |
| job_status | string | | No |
| record_count | integer | | No |
#### AnnotationCountResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| count | integer | Number of annotations | Yes |
#### AnnotationEmbeddingModelResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| embedding_model_name | string | | No |
| embedding_provider_name | string | | No |
#### AnnotationExportList
| Name | Type | Description | Required |
@ -14698,14 +14700,20 @@ Soft lifecycle state for Agent records.
| limit | integer, <br>**Default:** 20 | Page size | No |
| page | integer, <br>**Default:** 1 | Page number | No |
#### AnnotationJobStatusResponse
#### AnnotationJobStatusDetailResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| error_msg | string | | No |
| job_id | string | | No |
| job_status | string | | No |
| record_count | integer | | No |
| job_id | string | | Yes |
| job_status | string<br>string | | Yes |
#### AnnotationJobStatusResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| job_id | string | | Yes |
| job_status | string<br>string | | Yes |
#### AnnotationList
@ -14739,11 +14747,18 @@ Soft lifecycle state for Agent records.
| ---- | ---- | ----------- | -------- |
| action | string, <br>**Available values:** "disable", "enable" | *Enum:* `"disable"`, `"enable"` | Yes |
#### AnnotationSettingEmbeddingModelResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| embedding_model_name | string | | No |
| embedding_provider_name | string | | No |
#### AnnotationSettingResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| embedding_model | [AnnotationEmbeddingModelResponse](#annotationembeddingmodelresponse) | | No |
| embedding_model | [AnnotationSettingEmbeddingModelResponse](#annotationsettingembeddingmodelresponse) | | No |
| enabled | boolean | | Yes |
| id | string | | No |
| score_threshold | number | | No |
@ -15712,13 +15727,13 @@ Enum class for configurate method of provider model.
| admin_feedback_stats | [FeedbackStat](#feedbackstat) | | No |
| annotation | [ConversationAnnotation](#conversationannotation) | | No |
| created_at | integer | | No |
| first_message | [SimpleMessageDetail](#simplemessagedetail) | | No |
| from_account_id | string | | No |
| from_account_name | string | | No |
| from_end_user_id | string | | No |
| from_end_user_session_id | string | | No |
| from_source | string | | Yes |
| id | string | | Yes |
| message | [SimpleMessageDetail](#simplemessagedetail) | | No |
| model_config | [SimpleModelConfig](#simplemodelconfig) | | No |
| read_at | integer | | No |
| status | string | | Yes |
@ -15782,11 +15797,11 @@ Enum class for configurate method of provider model.
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| created_at | integer | | No |
| first_message | [MessageDetail](#messagedetail) | | No |
| from_account_id | string | | No |
| from_end_user_id | string | | No |
| from_source | string | | Yes |
| id | string | | Yes |
| message | [MessageDetail](#messagedetail) | | No |
| model_config | [ModelConfig](#modelconfig) | | No |
| status | string | | Yes |
@ -15794,10 +15809,10 @@ Enum class for configurate method of provider model.
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| has_next | boolean | | Yes |
| items | [ [Conversation](#conversation) ] | | Yes |
| data | [ [Conversation](#conversation) ] | | Yes |
| has_more | boolean | | Yes |
| limit | integer | | Yes |
| page | integer | | Yes |
| per_page | integer | | Yes |
| total | integer | | Yes |
#### ConversationRenamePayload
@ -15860,7 +15875,7 @@ Enum class for configurate method of provider model.
| read_at | integer | | No |
| status | string | | Yes |
| status_count | [StatusCount](#statuscount) | | No |
| summary_or_query | string | | Yes |
| summary | string | | Yes |
| updated_at | integer | | No |
| user_feedback_stats | [FeedbackStat](#feedbackstat) | | No |
@ -15868,10 +15883,10 @@ Enum class for configurate method of provider model.
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| has_next | boolean | | Yes |
| items | [ [ConversationWithSummary](#conversationwithsummary) ] | | Yes |
| data | [ [ConversationWithSummary](#conversationwithsummary) ] | | Yes |
| has_more | boolean | | Yes |
| limit | integer | | Yes |
| page | integer | | Yes |
| per_page | integer | | Yes |
| total | integer | | Yes |
#### ConvertToWorkflowPayload
@ -16044,10 +16059,10 @@ Model class for provider custom model configuration.
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| currency | string | | Yes |
| currency | string | | No |
| date | string | | Yes |
| token_count | integer | | Yes |
| total_price | string<br>number | | Yes |
| token_count | integer | | No |
| total_price | string | | No |
#### DailyTokenCostStatisticResponse
@ -18344,6 +18359,7 @@ Enum class for large language model mode.
| agent_thoughts | [ [AgentThought](#agentthought) ] | | Yes |
| annotation | [ConversationAnnotation](#conversationannotation) | | No |
| annotation_hit_history | [ConversationAnnotationHitHistory](#conversationannotationhithistory) | | No |
| answer | string | | Yes |
| answer_tokens | integer | | Yes |
| conversation_id | string | | Yes |
| created_at | integer | | No |
@ -18356,12 +18372,11 @@ Enum class for large language model mode.
| inputs | object | | Yes |
| message | [JSONValue](#jsonvalue) | | Yes |
| message_files | [ [MessageFile](#messagefile) ] | | Yes |
| message_metadata_dict | [JSONValue](#jsonvalue) | | Yes |
| message_tokens | integer | | Yes |
| metadata | [JSONValue](#jsonvalue) | | Yes |
| parent_message_id | string | | No |
| provider_response_latency | number | | Yes |
| query | string | | Yes |
| re_sign_file_url_answer | string | | Yes |
| status | string | | Yes |
| workflow_run_id | string | | No |
@ -18369,27 +18384,27 @@ Enum class for large language model mode.
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| agent_thoughts | [ [AgentThought](#agentthought) ] | | No |
| agent_thoughts | [ [AgentThought](#agentthought) ] | | Yes |
| annotation | [ConversationAnnotation](#conversationannotation) | | No |
| annotation_hit_history | [ConversationAnnotationHitHistory](#conversationannotationhithistory) | | No |
| answer | string | | Yes |
| answer_tokens | integer | | No |
| answer_tokens | integer | | Yes |
| conversation_id | string | | Yes |
| created_at | integer | | No |
| error | string | | No |
| extra_contents | [ [HumanInputContent](#humaninputcontent) ] | | No |
| feedbacks | [ [Feedback](#feedback) ] | | No |
| feedbacks | [ [Feedback](#feedback) ] | | Yes |
| from_account_id | string | | No |
| from_end_user_id | string | | No |
| from_source | string | | Yes |
| id | string | | Yes |
| inputs | object | | Yes |
| message | [JSONValue](#jsonvalue) | | No |
| message_files | [ [MessageFile](#messagefile) ] | | No |
| message_tokens | integer | | No |
| metadata | [JSONValue](#jsonvalue) | | No |
| message | [JSONValue](#jsonvalue) | | Yes |
| message_files | [ [MessageFile](#messagefile) ] | | Yes |
| message_tokens | integer | | Yes |
| metadata | [JSONValue](#jsonvalue) | | Yes |
| parent_message_id | string | | No |
| provider_response_latency | number | | No |
| provider_response_latency | number | | Yes |
| query | string | | Yes |
| status | string | | Yes |
| workflow_run_id | string | | No |
@ -20612,7 +20627,7 @@ Whitelist scopes accepted by RBAC app and dataset access config APIs.
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| model_dict | [JSONValue](#jsonvalue) | | No |
| model | [JSONValue](#jsonvalue) | | No |
| pre_prompt | string | | No |
#### SimpleProviderEntityResponse
@ -21235,9 +21250,11 @@ Tag type
#### TextToSpeechVoiceListResponse
Available voices
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| TextToSpeechVoiceListResponse | array | | |
| TextToSpeechVoiceListResponse | array | Available voices | |
#### TextToSpeechVoiceQuery
@ -21245,6 +21262,13 @@ Tag type
| ---- | ---- | ----------- | -------- |
| language | string | Language code | Yes |
#### TextToSpeechVoiceResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| name | string | Voice display name | Yes |
| value | string | Voice identifier | Yes |
#### TokensPerSecondStatisticItem
| Name | Type | Description | Required |

View File

@ -148,7 +148,8 @@ def test_console_text_api_error_mapping(app: Flask, monkeypatch: pytest.MonkeyPa
def test_console_text_modes_success(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(AudioService, "transcript_tts_voices", lambda **_kwargs: ["voice-1"])
expected_voices = [{"name": "Voice 1", "value": "voice-1"}]
monkeypatch.setattr(AudioService, "transcript_tts_voices", lambda **_kwargs: expected_voices)
api = TextModesApi()
handler = unwrap(api.get)
@ -157,7 +158,7 @@ def test_console_text_modes_success(app: Flask, monkeypatch: pytest.MonkeyPatch)
with app.test_request_context("/console/api/apps/app/text-to-audio/voices?language=en", method="GET"):
response = handler(api, app_model=app_model)
assert response == ["voice-1"]
assert response == expected_voices
def test_console_text_modes_language_error(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
@ -242,7 +243,8 @@ def test_text_to_audio_voices_success(app: Flask, monkeypatch: pytest.MonkeyPatc
api = TextModesApi()
method = unwrap(api.get)
monkeypatch.setattr(AudioService, "transcript_tts_voices", lambda **_kwargs: ["voice-1"])
expected_voices = [{"name": "Voice 1", "value": "voice-1"}]
monkeypatch.setattr(AudioService, "transcript_tts_voices", lambda **_kwargs: expected_voices)
app_model = SimpleNamespace(tenant_id="tenant-1")
@ -253,7 +255,7 @@ def test_text_to_audio_voices_success(app: Flask, monkeypatch: pytest.MonkeyPatc
):
response = method(api, app_model=app_model)
assert response == ["voice-1"]
assert response == expected_voices
def test_audio_to_text_with_invalid_file(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
@ -300,7 +302,7 @@ def test_text_to_audio_voices_with_language_filter(app: Flask, monkeypatch: pyte
monkeypatch.setattr(
AudioService,
"transcript_tts_voices",
lambda **_kwargs: [{"id": "voice-1", "name": "Voice 1"}],
lambda **_kwargs: [{"name": "Voice 1", "value": "voice-1"}],
)
app_model = SimpleNamespace(tenant_id="tenant-1")

View File

@ -1,149 +0,0 @@
from __future__ import annotations
import io
from inspect import unwrap
from types import SimpleNamespace
import pytest
from flask import Flask
from controllers.console.app import audio as audio_module
from controllers.console.app.error import AudioTooLargeError
from services.errors.audio import AudioTooLargeServiceError
def test_audio_to_text_success(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
api = audio_module.ChatMessageAudioApi()
method = unwrap(api.post)
response_payload = {"text": "hello"}
monkeypatch.setattr(audio_module.AudioService, "transcript_asr", lambda **_kwargs: response_payload)
app_model = SimpleNamespace(id="app-1")
data = {"file": (io.BytesIO(b"x"), "sample.wav")}
with app.test_request_context(
"/console/api/apps/app-1/audio-to-text",
method="POST",
data=data,
content_type="multipart/form-data",
):
response = method(api, app_model=app_model)
assert response == response_payload
def test_audio_to_text_maps_audio_too_large(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
api = audio_module.ChatMessageAudioApi()
method = unwrap(api.post)
monkeypatch.setattr(
audio_module.AudioService,
"transcript_asr",
lambda **_kwargs: (_ for _ in ()).throw(AudioTooLargeServiceError("too large")),
)
app_model = SimpleNamespace(id="app-1")
data = {"file": (io.BytesIO(b"x"), "sample.wav")}
with app.test_request_context(
"/console/api/apps/app-1/audio-to-text",
method="POST",
data=data,
content_type="multipart/form-data",
):
with pytest.raises(AudioTooLargeError):
method(api, app_model=app_model)
def test_text_to_audio_success(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
api = audio_module.ChatMessageTextApi()
method = unwrap(api.post)
monkeypatch.setattr(audio_module.AudioService, "transcript_tts", lambda **_kwargs: {"audio": "ok"})
app_model = SimpleNamespace(id="app-1")
with app.test_request_context(
"/console/api/apps/app-1/text-to-audio",
method="POST",
json={"text": "hello"},
):
response = method(api, app_model=app_model)
assert response == {"audio": "ok"}
def test_text_to_audio_voices_success(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
api = audio_module.TextModesApi()
method = unwrap(api.get)
monkeypatch.setattr(audio_module.AudioService, "transcript_tts_voices", lambda **_kwargs: ["voice-1"])
app_model = SimpleNamespace(tenant_id="tenant-1")
with app.test_request_context(
"/console/api/apps/app-1/text-to-audio/voices",
method="GET",
query_string={"language": "en-US"},
):
response = method(api, app_model=app_model)
assert response == ["voice-1"]
def test_audio_to_text_with_invalid_file(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
api = audio_module.ChatMessageAudioApi()
method = unwrap(api.post)
monkeypatch.setattr(audio_module.AudioService, "transcript_asr", lambda **_kwargs: {"text": "test"})
app_model = SimpleNamespace(id="app-1")
data = {"file": (io.BytesIO(b"invalid"), "sample.xyz")}
with app.test_request_context(
"/console/api/apps/app-1/audio-to-text",
method="POST",
data=data,
content_type="multipart/form-data",
):
# Should not raise, AudioService is mocked
response = method(api, app_model=app_model)
assert response == {"text": "test"}
def test_text_to_audio_with_language_param(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
api = audio_module.ChatMessageTextApi()
method = unwrap(api.post)
monkeypatch.setattr(audio_module.AudioService, "transcript_tts", lambda **_kwargs: {"audio": "test"})
app_model = SimpleNamespace(id="app-1")
with app.test_request_context(
"/console/api/apps/app-1/text-to-audio",
method="POST",
json={"text": "hello", "language": "en-US"},
):
response = method(api, app_model=app_model)
assert response == {"audio": "test"}
def test_text_to_audio_voices_with_language_filter(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
api = audio_module.TextModesApi()
method = unwrap(api.get)
monkeypatch.setattr(
audio_module.AudioService,
"transcript_tts_voices",
lambda **_kwargs: [{"id": "voice-1", "name": "Voice 1"}],
)
app_model = SimpleNamespace(tenant_id="tenant-1")
with app.test_request_context(
"/console/api/apps/app-1/text-to-audio/voices?language=en-US",
method="GET",
):
response = method(api, app_model=app_model)
assert isinstance(response, list)

View File

@ -125,13 +125,57 @@ def test_message_detail_response_normalizes_aliases_and_timestamp(app: Flask, mo
"conversation_id": "550e8400-e29b-41d4-a716-446655440001",
"inputs": {"foo": "bar"},
"query": "hello",
"re_sign_file_url_answer": "world",
"message": [{"text": "hello"}],
"message_tokens": 7,
"answer": "world",
"answer_tokens": 11,
"provider_response_latency": 1.25,
"from_source": "user",
"from_end_user_id": None,
"from_account_id": "550e8400-e29b-41d4-a716-446655440002",
"feedbacks": [],
"workflow_run_id": None,
"annotation": None,
"annotation_hit_history": None,
"status": "normal",
"created_at": created_at,
"agent_thoughts": [],
"message_files": [],
"message_metadata_dict": {"token_usage": 3},
"error": None,
"parent_message_id": None,
"extra_contents": [],
}
)
assert response.answer == "world"
assert response.message_tokens == 7
assert response.answer_tokens == 11
assert response.provider_response_latency == 1.25
assert response.metadata == {"token_usage": 3}
assert response.created_at == int(created_at.timestamp())
assert response.model_dump(mode="json") == {
"id": "550e8400-e29b-41d4-a716-446655440000",
"conversation_id": "550e8400-e29b-41d4-a716-446655440001",
"inputs": {"foo": "bar"},
"query": "hello",
"message": [{"text": "hello"}],
"message_tokens": 7,
"answer": "world",
"answer_tokens": 11,
"provider_response_latency": 1.25,
"from_source": "user",
"from_end_user_id": None,
"from_account_id": "550e8400-e29b-41d4-a716-446655440002",
"feedbacks": [],
"workflow_run_id": None,
"annotation": None,
"annotation_hit_history": None,
"created_at": int(created_at.timestamp()),
"agent_thoughts": [],
"message_files": [],
"metadata": {"token_usage": 3},
"status": "normal",
"error": None,
"parent_message_id": None,
"extra_contents": [],
}

View File

@ -3,6 +3,7 @@ from __future__ import annotations
from decimal import Decimal
from inspect import unwrap
from types import SimpleNamespace
from typing import Any
import pytest
from flask import Flask
@ -39,6 +40,10 @@ def _install_common(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(statistic_module, "convert_datetime_to_date", lambda field: field)
def _json_payload(response: Any) -> dict[str, Any]:
return response if isinstance(response, dict) else response.get_json()
def test_daily_message_statistic_returns_rows(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
api = statistic_module.DailyMessageStatistic()
method = unwrap(api.get)
@ -50,7 +55,7 @@ def test_daily_message_statistic_returns_rows(app: Flask, monkeypatch: pytest.Mo
with app.test_request_context("/console/api/apps/app-1/statistics/daily-messages", method="GET"):
response = method(api, SimpleNamespace(timezone="UTC"), app_model=SimpleNamespace(id="app-1"))
assert response.get_json() == {"data": [{"date": "2024-01-01", "message_count": 3}]}
assert _json_payload(response) == {"data": [{"date": "2024-01-01", "message_count": 3}]}
def test_daily_conversation_statistic_returns_rows(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
@ -64,7 +69,7 @@ def test_daily_conversation_statistic_returns_rows(app: Flask, monkeypatch: pyte
with app.test_request_context("/console/api/apps/app-1/statistics/daily-conversations", method="GET"):
response = method(api, SimpleNamespace(timezone="UTC"), app_model=SimpleNamespace(id="app-1"))
assert response.get_json() == {"data": [{"date": "2024-01-02", "conversation_count": 5}]}
assert _json_payload(response) == {"data": [{"date": "2024-01-02", "conversation_count": 5}]}
def test_daily_token_cost_statistic_returns_rows(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
@ -78,11 +83,11 @@ def test_daily_token_cost_statistic_returns_rows(app: Flask, monkeypatch: pytest
with app.test_request_context("/console/api/apps/app-1/statistics/token-costs", method="GET"):
response = method(api, SimpleNamespace(timezone="UTC"), app_model=SimpleNamespace(id="app-1"))
data = response.get_json()
data = _json_payload(response)
assert len(data["data"]) == 1
assert data["data"][0]["date"] == "2024-01-03"
assert data["data"][0]["token_count"] == 10
assert data["data"][0]["total_price"] == 0.25
assert Decimal(data["data"][0]["total_price"]) == Decimal("0.25")
def test_daily_terminals_statistic_returns_rows(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
@ -96,7 +101,7 @@ def test_daily_terminals_statistic_returns_rows(app: Flask, monkeypatch: pytest.
with app.test_request_context("/console/api/apps/app-1/statistics/daily-end-users", method="GET"):
response = method(api, SimpleNamespace(timezone="UTC"), app_model=SimpleNamespace(id="app-1"))
assert response.get_json() == {"data": [{"date": "2024-01-04", "terminal_count": 7}]}
assert _json_payload(response) == {"data": [{"date": "2024-01-04", "terminal_count": 7}]}
def test_average_session_interaction_statistic_requires_chat_mode(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
@ -139,7 +144,7 @@ def test_daily_message_statistic_multiple_rows(app: Flask, monkeypatch: pytest.M
with app.test_request_context("/console/api/apps/app-1/statistics/daily-messages", method="GET"):
response = method(api, SimpleNamespace(timezone="UTC"), app_model=SimpleNamespace(id="app-1"))
data = response.get_json()
data = _json_payload(response)
assert len(data["data"]) == 3
@ -153,7 +158,7 @@ def test_daily_message_statistic_empty_result(app: Flask, monkeypatch: pytest.Mo
with app.test_request_context("/console/api/apps/app-1/statistics/daily-messages", method="GET"):
response = method(api, SimpleNamespace(timezone="UTC"), app_model=SimpleNamespace(id="app-1"))
assert response.get_json() == {"data": []}
assert _json_payload(response) == {"data": []}
def test_daily_conversation_statistic_with_time_range(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
@ -172,7 +177,7 @@ def test_daily_conversation_statistic_with_time_range(app: Flask, monkeypatch: p
with app.test_request_context("/console/api/apps/app-1/statistics/daily-conversations", method="GET"):
response = method(api, SimpleNamespace(timezone="UTC"), app_model=SimpleNamespace(id="app-1"))
assert response.get_json() == {"data": [{"date": "2024-01-02", "conversation_count": 5}]}
assert _json_payload(response) == {"data": [{"date": "2024-01-02", "conversation_count": 5}]}
def test_daily_token_cost_with_multiple_currencies(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
@ -189,5 +194,5 @@ def test_daily_token_cost_with_multiple_currencies(app: Flask, monkeypatch: pyte
with app.test_request_context("/console/api/apps/app-1/statistics/token-costs", method="GET"):
response = method(api, SimpleNamespace(timezone="UTC"), app_model=SimpleNamespace(id="app-1"))
data = response.get_json()
data = _json_payload(response)
assert len(data["data"]) == 2

View File

@ -378,16 +378,16 @@ export type AgentLogMessageListResponse = {
}
export type MessageDetailResponse = {
agent_thoughts?: Array<AgentThought>
agent_thoughts: Array<AgentThought>
annotation?: ConversationAnnotation | null
annotation_hit_history?: ConversationAnnotationHitHistory | null
answer: string
answer_tokens?: number | null
answer_tokens: number
conversation_id: string
created_at?: number | null
error?: string | null
extra_contents?: Array<HumanInputContent>
feedbacks?: Array<Feedback>
feedbacks: Array<Feedback>
from_account_id?: string | null
from_end_user_id?: string | null
from_source: string
@ -395,12 +395,12 @@ export type MessageDetailResponse = {
inputs: {
[key: string]: JsonValue
}
message?: JsonValue | null
message_files?: Array<MessageFile>
message_tokens?: number | null
metadata?: JsonValue | null
message: JsonValue
message_files: Array<MessageFile>
message_tokens: number
metadata: JsonValue
parent_message_id?: string | null
provider_response_latency?: number | null
provider_response_latency: number
query: string
status: string
workflow_run_id?: string | null

View File

@ -2525,27 +2525,27 @@ export const zHumanInputContent = z.object({
* MessageDetailResponse
*/
export const zMessageDetailResponse = z.object({
agent_thoughts: z.array(zAgentThought).optional(),
agent_thoughts: z.array(zAgentThought),
annotation: zConversationAnnotation.nullish(),
annotation_hit_history: zConversationAnnotationHitHistory.nullish(),
answer: z.string(),
answer_tokens: z.int().nullish(),
answer_tokens: z.int(),
conversation_id: z.string(),
created_at: z.int().nullish(),
error: z.string().nullish(),
extra_contents: z.array(zHumanInputContent).optional(),
feedbacks: z.array(zFeedback).optional(),
feedbacks: z.array(zFeedback),
from_account_id: z.string().nullish(),
from_end_user_id: z.string().nullish(),
from_source: z.string(),
id: z.string(),
inputs: z.record(z.string(), zJsonValue),
message: zJsonValue.nullish(),
message_files: z.array(zMessageFile).optional(),
message_tokens: z.int().nullish(),
metadata: zJsonValue.nullish(),
message: zJsonValue,
message_files: z.array(zMessageFile),
message_tokens: z.int(),
metadata: zJsonValue,
parent_message_id: z.string().nullish(),
provider_response_latency: z.number().nullish(),
provider_response_latency: z.number(),
query: z.string(),
status: z.string(),
workflow_run_id: z.string().nullish(),

View File

@ -329,14 +329,18 @@ export type AnnotationReplyPayload = {
}
export type AnnotationJobStatusResponse = {
error_msg?: string | null
job_id?: string | null
job_status?: string | null
record_count?: number | null
job_id: string
job_status: 'completed' | 'error' | 'processing' | 'waiting' | string
}
export type AnnotationJobStatusDetailResponse = {
error_msg?: string
job_id: string
job_status: 'completed' | 'error' | 'processing' | 'waiting' | string
}
export type AnnotationSettingResponse = {
embedding_model?: AnnotationEmbeddingModelResponse | null
embedding_model?: AnnotationSettingEmbeddingModelResponse | null
enabled: boolean
id?: string | null
score_threshold?: number | null
@ -372,6 +376,13 @@ export type Annotation = {
question?: string | null
}
export type AnnotationBatchImportResponse = {
error_msg?: string | null
job_id?: string | null
job_status?: string | null
record_count?: number | null
}
export type AnnotationCountResponse = {
count: number
}
@ -429,10 +440,10 @@ export type AudioTranscriptResponse = {
}
export type ConversationWithSummaryPagination = {
has_next: boolean
items: Array<ConversationWithSummary>
data: Array<ConversationWithSummary>
has_more: boolean
limit: number
page: number
per_page: number
total: number
}
@ -467,20 +478,20 @@ export type SimpleResultResponse = {
}
export type ConversationPagination = {
has_next: boolean
items: Array<Conversation>
data: Array<Conversation>
has_more: boolean
limit: number
page: number
per_page: number
total: number
}
export type ConversationMessageDetail = {
created_at?: number | null
first_message?: MessageDetail | null
from_account_id?: string | null
from_end_user_id?: string | null
from_source: string
id: string
message?: MessageDetail | null
model_config?: ModelConfig | null
status: string
}
@ -555,16 +566,16 @@ export type AppIconPayload = {
}
export type MessageDetailResponse = {
agent_thoughts?: Array<AgentThought>
agent_thoughts: Array<AgentThought>
annotation?: ConversationAnnotation | null
annotation_hit_history?: ConversationAnnotationHitHistory | null
answer: string
answer_tokens?: number | null
answer_tokens: number
conversation_id: string
created_at?: number | null
error?: string | null
extra_contents?: Array<HumanInputContent>
feedbacks?: Array<Feedback>
feedbacks: Array<Feedback>
from_account_id?: string | null
from_end_user_id?: string | null
from_source: string
@ -572,12 +583,12 @@ export type MessageDetailResponse = {
inputs: {
[key: string]: JsonValue
}
message?: JsonValue | null
message_files?: Array<MessageFile>
message_tokens?: number | null
metadata?: JsonValue | null
message: JsonValue
message_files: Array<MessageFile>
message_tokens: number
metadata: JsonValue
parent_message_id?: string | null
provider_response_latency?: number | null
provider_response_latency: number
query: string
status: string
workflow_run_id?: string | null
@ -736,11 +747,7 @@ export type TextToSpeechPayload = {
voice?: string | null
}
export type AudioBinaryResponse = Blob | File
export type TextToSpeechVoiceListResponse = Array<{
[key: string]: unknown
}>
export type TextToSpeechVoiceListResponse = Array<TextToSpeechVoiceResponse>
export type AppTraceResponse = {
enabled?: boolean
@ -1544,7 +1551,7 @@ export type CliToolSuggestion = {
name: string
}
export type AnnotationEmbeddingModelResponse = {
export type AnnotationSettingEmbeddingModelResponse = {
embedding_model_name?: string | null
embedding_provider_name?: string | null
}
@ -1575,7 +1582,7 @@ export type ConversationWithSummary = {
read_at?: number | null
status: string
status_count?: StatusCount | null
summary_or_query: string
summary: string
updated_at?: number | null
user_feedback_stats?: FeedbackStat | null
}
@ -1589,13 +1596,13 @@ export type Conversation = {
admin_feedback_stats?: FeedbackStat | null
annotation?: ConversationAnnotation | null
created_at?: number | null
first_message?: SimpleMessageDetail | null
from_account_id?: string | null
from_account_name?: string | null
from_end_user_id?: string | null
from_end_user_session_id?: string | null
from_source: string
id: string
message?: SimpleMessageDetail | null
model_config?: SimpleModelConfig | null
read_at?: number | null
status: string
@ -1607,6 +1614,7 @@ export type MessageDetail = {
agent_thoughts: Array<AgentThought>
annotation?: ConversationAnnotation | null
annotation_hit_history?: ConversationAnnotationHitHistory | null
answer: string
answer_tokens: number
conversation_id: string
created_at?: number | null
@ -1621,12 +1629,11 @@ export type MessageDetail = {
}
message: JsonValue
message_files: Array<MessageFile>
message_metadata_dict: JsonValue
message_tokens: number
metadata: JsonValue
parent_message_id?: string | null
provider_response_latency: number
query: string
re_sign_file_url_answer: string
status: string
workflow_run_id?: string | null
}
@ -1725,10 +1732,10 @@ export type DailyMessageStatisticItem = {
}
export type DailyTokenCostStatisticItem = {
currency: string
currency?: string | null
date: string
token_count: number
total_price: string | number
token_count?: number | null
total_price?: string | null
}
export type TokensPerSecondStatisticItem = {
@ -1741,6 +1748,11 @@ export type UserSatisfactionRateStatisticItem = {
rate: number
}
export type TextToSpeechVoiceResponse = {
name: string
value: string
}
export type WorkflowAppLogPartialResponse = {
created_at?: number | null
created_by_account?: SimpleAccount | null
@ -2224,7 +2236,7 @@ export type EnvSuggestion = {
}
export type SimpleModelConfig = {
model_dict?: JsonValue | null
model?: JsonValue | null
pre_prompt?: string | null
}
@ -4045,7 +4057,7 @@ export type GetAppsByAppIdAnnotationReplyByActionStatusByJobIdErrors = {
}
export type GetAppsByAppIdAnnotationReplyByActionStatusByJobIdResponses = {
200: AnnotationJobStatusResponse
200: AnnotationJobStatusDetailResponse
}
export type GetAppsByAppIdAnnotationReplyByActionStatusByJobIdResponse
@ -4169,7 +4181,7 @@ export type PostAppsByAppIdAnnotationsBatchImportErrors = {
}
export type PostAppsByAppIdAnnotationsBatchImportResponses = {
200: AnnotationJobStatusResponse
200: AnnotationBatchImportResponse
}
export type PostAppsByAppIdAnnotationsBatchImportResponse
@ -4190,7 +4202,7 @@ export type GetAppsByAppIdAnnotationsBatchImportStatusByJobIdErrors = {
}
export type GetAppsByAppIdAnnotationsBatchImportStatusByJobIdResponses = {
200: AnnotationJobStatusResponse
200: AnnotationJobStatusDetailResponse
}
export type GetAppsByAppIdAnnotationsBatchImportStatusByJobIdResponse
@ -4556,7 +4568,9 @@ export type PostAppsByAppIdCompletionMessagesErrors = {
}
export type PostAppsByAppIdCompletionMessagesResponses = {
200: GeneratedAppResponse
200: {
[key: string]: unknown
}
}
export type PostAppsByAppIdCompletionMessagesResponse
@ -5130,7 +5144,9 @@ export type PostAppsByAppIdTextToAudioErrors = {
}
export type PostAppsByAppIdTextToAudioResponses = {
200: AudioBinaryResponse
200: {
[key: string]: unknown
}
}
export type PostAppsByAppIdTextToAudioResponse

View File

@ -188,10 +188,17 @@ export const zAnnotationReplyPayload = z.object({
* AnnotationJobStatusResponse
*/
export const zAnnotationJobStatusResponse = z.object({
error_msg: z.string().nullish(),
job_id: z.string().nullish(),
job_status: z.string().nullish(),
record_count: z.int().nullish(),
job_id: z.string(),
job_status: z.union([z.enum(['completed', 'error', 'processing', 'waiting']), z.string()]),
})
/**
* AnnotationJobStatusDetailResponse
*/
export const zAnnotationJobStatusDetailResponse = z.object({
error_msg: z.string().optional().default(''),
job_id: z.string(),
job_status: z.union([z.enum(['completed', 'error', 'processing', 'waiting']), z.string()]),
})
/**
@ -234,6 +241,16 @@ export const zAnnotationList = z.object({
total: z.int(),
})
/**
* AnnotationBatchImportResponse
*/
export const zAnnotationBatchImportResponse = z.object({
error_msg: z.string().nullish(),
job_id: z.string().nullish(),
job_status: z.string().nullish(),
record_count: z.int().nullish(),
})
/**
* AnnotationCountResponse
*/
@ -449,16 +466,6 @@ export const zTextToSpeechPayload = z.object({
voice: z.string().nullish(),
})
/**
* AudioBinaryResponse
*/
export const zAudioBinaryResponse = z.custom<Blob | File>()
/**
* TextToSpeechVoiceListResponse
*/
export const zTextToSpeechVoiceListResponse = z.array(z.record(z.string(), z.unknown()))
/**
* AppTraceResponse
*/
@ -1251,9 +1258,9 @@ export const zAgentSkillUploadResponse = z.object({
})
/**
* AnnotationEmbeddingModelResponse
* AnnotationSettingEmbeddingModelResponse
*/
export const zAnnotationEmbeddingModelResponse = z.object({
export const zAnnotationSettingEmbeddingModelResponse = z.object({
embedding_model_name: z.string().nullish(),
embedding_provider_name: z.string().nullish(),
})
@ -1262,7 +1269,7 @@ export const zAnnotationEmbeddingModelResponse = z.object({
* AnnotationSettingResponse
*/
export const zAnnotationSettingResponse = z.object({
embedding_model: zAnnotationEmbeddingModelResponse.nullish(),
embedding_model: zAnnotationSettingEmbeddingModelResponse.nullish(),
enabled: z.boolean(),
id: z.string().nullish(),
score_threshold: z.number().nullish(),
@ -1456,10 +1463,13 @@ export const zDailyMessageStatisticResponse = z.object({
* DailyTokenCostStatisticItem
*/
export const zDailyTokenCostStatisticItem = z.object({
currency: z.string(),
currency: z.string().nullish(),
date: z.string(),
token_count: z.int(),
total_price: z.union([z.string(), z.number()]),
token_count: z.int().nullish(),
total_price: z
.string()
.regex(/^(?![-+.]*$)[+-]?\d*(?:\.\d*)?$/)
.nullish(),
})
/**
@ -1499,6 +1509,21 @@ export const zUserSatisfactionRateStatisticResponse = z.object({
data: z.array(zUserSatisfactionRateStatisticItem),
})
/**
* TextToSpeechVoiceResponse
*/
export const zTextToSpeechVoiceResponse = z.object({
name: z.string(),
value: z.string(),
})
/**
* TextToSpeechVoiceListResponse
*
* Available voices
*/
export const zTextToSpeechVoiceListResponse = z.array(zTextToSpeechVoiceResponse)
/**
* SimpleAccount
*/
@ -1574,6 +1599,7 @@ export const zMessageDetail = z.object({
agent_thoughts: z.array(zAgentThought),
annotation: zConversationAnnotation.nullish(),
annotation_hit_history: zConversationAnnotationHitHistory.nullish(),
answer: z.string(),
answer_tokens: z.int(),
conversation_id: z.string(),
created_at: z.int().nullish(),
@ -1586,12 +1612,11 @@ export const zMessageDetail = z.object({
inputs: z.record(z.string(), zJsonValue),
message: zJsonValue,
message_files: z.array(zMessageFile),
message_metadata_dict: zJsonValue,
message_tokens: z.int(),
metadata: zJsonValue,
parent_message_id: z.string().nullish(),
provider_response_latency: z.number(),
query: z.string(),
re_sign_file_url_answer: z.string(),
status: z.string(),
workflow_run_id: z.string().nullish(),
})
@ -2375,11 +2400,11 @@ export const zConversationDetail = z.object({
*/
export const zConversationMessageDetail = z.object({
created_at: z.int().nullish(),
first_message: zMessageDetail.nullish(),
from_account_id: z.string().nullish(),
from_end_user_id: z.string().nullish(),
from_source: z.string(),
id: z.string(),
message: zMessageDetail.nullish(),
model_config: zModelConfig.nullish(),
status: z.string(),
})
@ -2525,7 +2550,7 @@ export const zSkillToolInferenceResult = z.object({
* SimpleModelConfig
*/
export const zSimpleModelConfig = z.object({
model_dict: zJsonValue.nullish(),
model: zJsonValue.nullish(),
pre_prompt: z.string().nullish(),
})
@ -2558,7 +2583,7 @@ export const zConversationWithSummary = z.object({
read_at: z.int().nullish(),
status: z.string(),
status_count: zStatusCount.nullish(),
summary_or_query: z.string(),
summary: z.string(),
updated_at: z.int().nullish(),
user_feedback_stats: zFeedbackStat.nullish(),
})
@ -2567,10 +2592,10 @@ export const zConversationWithSummary = z.object({
* ConversationWithSummaryPagination
*/
export const zConversationWithSummaryPagination = z.object({
has_next: z.boolean(),
items: z.array(zConversationWithSummary),
data: z.array(zConversationWithSummary),
has_more: z.boolean(),
limit: z.int(),
page: z.int(),
per_page: z.int(),
total: z.int(),
})
@ -2591,13 +2616,13 @@ export const zConversation = z.object({
admin_feedback_stats: zFeedbackStat.nullish(),
annotation: zConversationAnnotation.nullish(),
created_at: z.int().nullish(),
first_message: zSimpleMessageDetail.nullish(),
from_account_id: z.string().nullish(),
from_account_name: z.string().nullish(),
from_end_user_id: z.string().nullish(),
from_end_user_session_id: z.string().nullish(),
from_source: z.string(),
id: z.string(),
message: zSimpleMessageDetail.nullish(),
model_config: zSimpleModelConfig.nullish(),
read_at: z.int().nullish(),
status: z.string(),
@ -2609,10 +2634,10 @@ export const zConversation = z.object({
* ConversationPagination
*/
export const zConversationPagination = z.object({
has_next: z.boolean(),
items: z.array(zConversation),
data: z.array(zConversation),
has_more: z.boolean(),
limit: z.int(),
page: z.int(),
per_page: z.int(),
total: z.int(),
})
@ -3847,27 +3872,27 @@ export const zHumanInputContent = z.object({
* MessageDetailResponse
*/
export const zMessageDetailResponse = z.object({
agent_thoughts: z.array(zAgentThought).optional(),
agent_thoughts: z.array(zAgentThought),
annotation: zConversationAnnotation.nullish(),
annotation_hit_history: zConversationAnnotationHitHistory.nullish(),
answer: z.string(),
answer_tokens: z.int().nullish(),
answer_tokens: z.int(),
conversation_id: z.string(),
created_at: z.int().nullish(),
error: z.string().nullish(),
extra_contents: z.array(zHumanInputContent).optional(),
feedbacks: z.array(zFeedback).optional(),
feedbacks: z.array(zFeedback),
from_account_id: z.string().nullish(),
from_end_user_id: z.string().nullish(),
from_source: z.string(),
id: z.string(),
inputs: z.record(z.string(), zJsonValue),
message: zJsonValue.nullish(),
message_files: z.array(zMessageFile).optional(),
message_tokens: z.int().nullish(),
metadata: zJsonValue.nullish(),
message: zJsonValue,
message_files: z.array(zMessageFile),
message_tokens: z.int(),
metadata: zJsonValue,
parent_message_id: z.string().nullish(),
provider_response_latency: z.number().nullish(),
provider_response_latency: z.number(),
query: z.string(),
status: z.string(),
workflow_run_id: z.string().nullish(),
@ -4886,7 +4911,7 @@ export const zGetAppsByAppIdAnnotationReplyByActionStatusByJobIdPath = z.object(
* Job status retrieved successfully
*/
export const zGetAppsByAppIdAnnotationReplyByActionStatusByJobIdResponse
= zAnnotationJobStatusResponse
= zAnnotationJobStatusDetailResponse
export const zGetAppsByAppIdAnnotationSettingPath = z.object({
app_id: z.uuid(),
@ -4953,7 +4978,7 @@ export const zPostAppsByAppIdAnnotationsBatchImportPath = z.object({
/**
* Batch import started successfully
*/
export const zPostAppsByAppIdAnnotationsBatchImportResponse = zAnnotationJobStatusResponse
export const zPostAppsByAppIdAnnotationsBatchImportResponse = zAnnotationBatchImportResponse
export const zGetAppsByAppIdAnnotationsBatchImportStatusByJobIdPath = z.object({
app_id: z.uuid(),
@ -4964,7 +4989,7 @@ export const zGetAppsByAppIdAnnotationsBatchImportStatusByJobIdPath = z.object({
* Job status retrieved successfully
*/
export const zGetAppsByAppIdAnnotationsBatchImportStatusByJobIdResponse
= zAnnotationJobStatusResponse
= zAnnotationJobStatusDetailResponse
export const zGetAppsByAppIdAnnotationsCountPath = z.object({
app_id: z.uuid(),
@ -5165,7 +5190,7 @@ export const zPostAppsByAppIdCompletionMessagesPath = z.object({
/**
* Completion generated successfully
*/
export const zPostAppsByAppIdCompletionMessagesResponse = zGeneratedAppResponse
export const zPostAppsByAppIdCompletionMessagesResponse = z.record(z.string(), z.unknown())
export const zPostAppsByAppIdCompletionMessagesByTaskIdStopPath = z.object({
app_id: z.uuid(),
@ -5509,7 +5534,7 @@ export const zPostAppsByAppIdTextToAudioPath = z.object({
/**
* Text to speech conversion successful
*/
export const zPostAppsByAppIdTextToAudioResponse = zAudioBinaryResponse
export const zPostAppsByAppIdTextToAudioResponse = z.record(z.string(), z.unknown())
export const zGetAppsByAppIdTextToAudioVoicesPath = z.object({
app_id: z.uuid(),