mirror of
https://github.com/langgenius/dify.git
synced 2026-06-17 06:21:07 +08:00
fix(agent): support agent-id chat and inline draft bindings (#37483)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
30506f7221
commit
b4e3a9095b
@ -1,5 +1,6 @@
|
||||
import logging
|
||||
from typing import Any, Literal
|
||||
from uuid import UUID
|
||||
|
||||
from flask import request
|
||||
from flask_restx import Resource
|
||||
@ -10,6 +11,7 @@ import services
|
||||
from controllers.common.fields import GeneratedAppResponse, 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_app_model
|
||||
from controllers.console.app.error import (
|
||||
AppUnavailableError,
|
||||
CompletionRequestError,
|
||||
@ -23,6 +25,7 @@ from controllers.console.wraps import (
|
||||
account_initialization_required,
|
||||
edit_permission_required,
|
||||
setup_required,
|
||||
with_current_tenant_id,
|
||||
with_current_user,
|
||||
with_current_user_id,
|
||||
)
|
||||
@ -186,51 +189,27 @@ class ChatMessageApi(Resource):
|
||||
@edit_permission_required
|
||||
@with_current_user
|
||||
def post(self, current_user: Account, app_model: App):
|
||||
raw_payload = console_ns.payload or {}
|
||||
args_model = ChatMessagePayload.model_validate(raw_payload)
|
||||
args = args_model.model_dump(exclude_none=True, by_alias=True)
|
||||
return _create_chat_message(current_user=current_user, app_model=app_model)
|
||||
|
||||
streaming = _resolve_debugger_chat_streaming(
|
||||
app_mode=AppMode.value_of(app_model.mode),
|
||||
response_mode=args_model.response_mode,
|
||||
response_mode_provided=isinstance(raw_payload, dict) and "response_mode" in raw_payload,
|
||||
)
|
||||
if AppMode.value_of(app_model.mode) == AppMode.AGENT:
|
||||
args["response_mode"] = "streaming"
|
||||
args["auto_generate_name"] = False
|
||||
|
||||
external_trace_id = get_external_trace_id(request)
|
||||
if external_trace_id:
|
||||
args["external_trace_id"] = external_trace_id
|
||||
|
||||
try:
|
||||
response = AppGenerateService.generate(
|
||||
app_model=app_model, user=current_user, args=args, invoke_from=InvokeFrom.DEBUGGER, streaming=streaming
|
||||
)
|
||||
|
||||
return helper.compact_generate_response(response)
|
||||
except services.errors.conversation.ConversationNotExistsError:
|
||||
raise NotFound("Conversation Not Exists.")
|
||||
except services.errors.conversation.ConversationCompletedError:
|
||||
raise ConversationCompletedError()
|
||||
except services.errors.app_model_config.AppModelConfigBrokenError:
|
||||
logger.exception("App model config broken.")
|
||||
raise AppUnavailableError()
|
||||
except ProviderTokenNotInitError as ex:
|
||||
raise ProviderNotInitializeError(ex.description)
|
||||
except QuotaExceededError:
|
||||
raise ProviderQuotaExceededError()
|
||||
except ModelCurrentlyNotSupportError:
|
||||
raise ProviderModelCurrentlyNotSupportError()
|
||||
except InvokeRateLimitError as ex:
|
||||
raise InvokeRateLimitHttpError(ex.description)
|
||||
except InvokeError as e:
|
||||
raise CompletionRequestError(e.description)
|
||||
except ValueError as e:
|
||||
raise e
|
||||
except Exception as e:
|
||||
logger.exception("internal server error.")
|
||||
raise InternalServerError()
|
||||
@console_ns.route("/agent/<uuid:agent_id>/chat-messages")
|
||||
class AgentChatMessageApi(Resource):
|
||||
@console_ns.doc("create_agent_chat_message")
|
||||
@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(400, "Invalid request parameters")
|
||||
@console_ns.response(404, "Agent or conversation not found")
|
||||
@setup_required
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@edit_permission_required
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
def post(self, current_tenant_id: str, current_user: Account, agent_id: UUID):
|
||||
app_model = resolve_agent_app_model(tenant_id=current_tenant_id, agent_id=agent_id)
|
||||
return _create_chat_message(current_user=current_user, app_model=app_model)
|
||||
|
||||
|
||||
@console_ns.route("/apps/<uuid:app_id>/chat-messages/<string:task_id>/stop")
|
||||
@ -245,12 +224,79 @@ class ChatMessageStopApi(Resource):
|
||||
@get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT, AppMode.AGENT])
|
||||
@with_current_user_id
|
||||
def post(self, current_user_id: str, app_model: App, task_id: str):
|
||||
return _stop_chat_message(current_user_id=current_user_id, app_model=app_model, task_id=task_id)
|
||||
|
||||
AppTaskService.stop_task(
|
||||
task_id=task_id,
|
||||
invoke_from=InvokeFrom.DEBUGGER,
|
||||
user_id=current_user_id,
|
||||
app_mode=AppMode.value_of(app_model.mode),
|
||||
|
||||
@console_ns.route("/agent/<uuid:agent_id>/chat-messages/<string:task_id>/stop")
|
||||
class AgentChatMessageStopApi(Resource):
|
||||
@console_ns.doc("stop_agent_chat_message")
|
||||
@console_ns.doc(description="Stop a running Agent App chat message generation")
|
||||
@console_ns.doc(params={"agent_id": "Agent ID", "task_id": "Task ID to stop"})
|
||||
@console_ns.response(200, "Task stopped successfully", console_ns.models[SimpleResultResponse.__name__])
|
||||
@setup_required
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@with_current_user_id
|
||||
@with_current_tenant_id
|
||||
def post(self, current_tenant_id: str, current_user_id: str, agent_id: UUID, task_id: str):
|
||||
app_model = resolve_agent_app_model(tenant_id=current_tenant_id, agent_id=agent_id)
|
||||
return _stop_chat_message(current_user_id=current_user_id, app_model=app_model, task_id=task_id)
|
||||
|
||||
|
||||
def _create_chat_message(*, current_user: Account, app_model: App):
|
||||
raw_payload = console_ns.payload or {}
|
||||
args_model = ChatMessagePayload.model_validate(raw_payload)
|
||||
args = args_model.model_dump(exclude_none=True, by_alias=True)
|
||||
|
||||
streaming = _resolve_debugger_chat_streaming(
|
||||
app_mode=AppMode.value_of(app_model.mode),
|
||||
response_mode=args_model.response_mode,
|
||||
response_mode_provided=isinstance(raw_payload, dict) and "response_mode" in raw_payload,
|
||||
)
|
||||
if AppMode.value_of(app_model.mode) == AppMode.AGENT:
|
||||
args["response_mode"] = "streaming"
|
||||
args["auto_generate_name"] = False
|
||||
|
||||
external_trace_id = get_external_trace_id(request)
|
||||
if external_trace_id:
|
||||
args["external_trace_id"] = external_trace_id
|
||||
|
||||
try:
|
||||
response = AppGenerateService.generate(
|
||||
app_model=app_model, user=current_user, args=args, invoke_from=InvokeFrom.DEBUGGER, streaming=streaming
|
||||
)
|
||||
|
||||
return {"result": "success"}, 200
|
||||
return helper.compact_generate_response(response)
|
||||
except services.errors.conversation.ConversationNotExistsError:
|
||||
raise NotFound("Conversation Not Exists.")
|
||||
except services.errors.conversation.ConversationCompletedError:
|
||||
raise ConversationCompletedError()
|
||||
except services.errors.app_model_config.AppModelConfigBrokenError:
|
||||
logger.exception("App model config broken.")
|
||||
raise AppUnavailableError()
|
||||
except ProviderTokenNotInitError as ex:
|
||||
raise ProviderNotInitializeError(ex.description)
|
||||
except QuotaExceededError:
|
||||
raise ProviderQuotaExceededError()
|
||||
except ModelCurrentlyNotSupportError:
|
||||
raise ProviderModelCurrentlyNotSupportError()
|
||||
except InvokeRateLimitError as ex:
|
||||
raise InvokeRateLimitHttpError(ex.description)
|
||||
except InvokeError as e:
|
||||
raise CompletionRequestError(e.description)
|
||||
except ValueError as e:
|
||||
raise e
|
||||
except Exception as e:
|
||||
logger.exception("internal server error.")
|
||||
raise InternalServerError()
|
||||
|
||||
|
||||
def _stop_chat_message(*, current_user_id: str, app_model: App, task_id: str):
|
||||
AppTaskService.stop_task(
|
||||
task_id=task_id,
|
||||
invoke_from=InvokeFrom.DEBUGGER,
|
||||
user_id=current_user_id,
|
||||
app_mode=AppMode.value_of(app_model.mode),
|
||||
)
|
||||
|
||||
return {"result": "success"}, 200
|
||||
|
||||
@ -13,6 +13,7 @@ from controllers.common.controller_schemas import MessageFeedbackPayload as _Mes
|
||||
from controllers.common.fields import SimpleResultResponse, TextFileResponse
|
||||
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.agent.app_helpers import resolve_agent_app_model
|
||||
from controllers.console.app.error import (
|
||||
CompletionRequestError,
|
||||
ProviderModelCurrentlyNotSupportError,
|
||||
@ -25,6 +26,7 @@ from controllers.console.wraps import (
|
||||
account_initialization_required,
|
||||
edit_permission_required,
|
||||
setup_required,
|
||||
with_current_tenant_id,
|
||||
with_current_user,
|
||||
)
|
||||
from core.app.entities.app_invoke_entities import InvokeFrom
|
||||
@ -183,67 +185,25 @@ class ChatMessageListApi(Resource):
|
||||
@get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT, AppMode.AGENT])
|
||||
@edit_permission_required
|
||||
def get(self, app_model: App):
|
||||
args = ChatMessagesQuery.model_validate(request.args.to_dict())
|
||||
return _list_chat_messages(app_model=app_model)
|
||||
|
||||
conversation = db.session.scalar(
|
||||
select(Conversation)
|
||||
.where(Conversation.id == args.conversation_id, Conversation.app_id == app_model.id)
|
||||
.limit(1)
|
||||
)
|
||||
|
||||
if not conversation:
|
||||
raise NotFound("Conversation Not Exists.")
|
||||
|
||||
if args.first_id:
|
||||
first_message = db.session.scalar(
|
||||
select(Message).where(Message.conversation_id == conversation.id, Message.id == args.first_id).limit(1)
|
||||
)
|
||||
|
||||
if not first_message:
|
||||
raise NotFound("First message not found")
|
||||
|
||||
history_messages = db.session.scalars(
|
||||
select(Message)
|
||||
.where(
|
||||
Message.conversation_id == conversation.id,
|
||||
Message.created_at < first_message.created_at,
|
||||
Message.id != first_message.id,
|
||||
)
|
||||
.order_by(Message.created_at.desc())
|
||||
.limit(args.limit)
|
||||
).all()
|
||||
else:
|
||||
history_messages = db.session.scalars(
|
||||
select(Message)
|
||||
.where(Message.conversation_id == conversation.id)
|
||||
.order_by(Message.created_at.desc())
|
||||
.limit(args.limit)
|
||||
).all()
|
||||
|
||||
# Initialize has_more based on whether we have a full page
|
||||
if len(history_messages) == args.limit:
|
||||
current_page_first_message = history_messages[-1]
|
||||
# Check if there are more messages before the current page
|
||||
has_more = db.session.scalar(
|
||||
select(
|
||||
exists().where(
|
||||
Message.conversation_id == conversation.id,
|
||||
Message.created_at < current_page_first_message.created_at,
|
||||
Message.id != current_page_first_message.id,
|
||||
)
|
||||
)
|
||||
)
|
||||
else:
|
||||
# If we don't have a full page, there are no more messages
|
||||
has_more = False
|
||||
|
||||
history_messages = list(reversed(history_messages))
|
||||
attach_message_extra_contents(history_messages)
|
||||
|
||||
return MessageInfiniteScrollPaginationResponse.model_validate(
|
||||
InfiniteScrollPagination(data=history_messages, limit=args.limit, has_more=has_more),
|
||||
from_attributes=True,
|
||||
).model_dump(mode="json")
|
||||
@console_ns.route("/agent/<uuid:agent_id>/chat-messages")
|
||||
class AgentChatMessageListApi(Resource):
|
||||
@console_ns.doc("list_agent_chat_messages")
|
||||
@console_ns.doc(description="Get Agent App chat messages for a conversation with pagination")
|
||||
@console_ns.doc(params={"agent_id": "Agent ID"})
|
||||
@console_ns.doc(params=query_params_from_model(ChatMessagesQuery))
|
||||
@console_ns.response(200, "Success", console_ns.models[MessageInfiniteScrollPaginationResponse.__name__])
|
||||
@console_ns.response(404, "Agent or conversation not found")
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@setup_required
|
||||
@edit_permission_required
|
||||
@with_current_tenant_id
|
||||
def get(self, current_tenant_id: str, agent_id: UUID):
|
||||
app_model = resolve_agent_app_model(tenant_id=current_tenant_id, agent_id=agent_id)
|
||||
return _list_chat_messages(app_model=app_model)
|
||||
|
||||
|
||||
@console_ns.route("/apps/<uuid:app_id>/feedbacks")
|
||||
@ -261,44 +221,25 @@ class MessageFeedbackApi(Resource):
|
||||
@account_initialization_required
|
||||
@with_current_user
|
||||
def post(self, current_user: Account, app_model: App):
|
||||
args = MessageFeedbackPayload.model_validate(console_ns.payload)
|
||||
return _update_message_feedback(current_user=current_user, app_model=app_model)
|
||||
|
||||
message_id = str(args.message_id)
|
||||
|
||||
message = db.session.scalar(
|
||||
select(Message).where(Message.id == message_id, Message.app_id == app_model.id).limit(1)
|
||||
)
|
||||
|
||||
if not message:
|
||||
raise NotFound("Message Not Exists.")
|
||||
|
||||
feedback = message.admin_feedback
|
||||
|
||||
if not args.rating and feedback:
|
||||
db.session.delete(feedback)
|
||||
elif args.rating and feedback:
|
||||
feedback.rating = FeedbackRating(args.rating)
|
||||
feedback.content = args.content
|
||||
elif not args.rating and not feedback:
|
||||
raise ValueError("rating cannot be None when feedback not exists")
|
||||
else:
|
||||
rating_value = args.rating
|
||||
if rating_value is None:
|
||||
raise ValueError("rating is required to create feedback")
|
||||
feedback = MessageFeedback(
|
||||
app_id=app_model.id,
|
||||
conversation_id=message.conversation_id,
|
||||
message_id=message.id,
|
||||
rating=FeedbackRating(rating_value),
|
||||
content=args.content,
|
||||
from_source=FeedbackFromSource.ADMIN,
|
||||
from_account_id=current_user.id,
|
||||
)
|
||||
db.session.add(feedback)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
return {"result": "success"}
|
||||
@console_ns.route("/agent/<uuid:agent_id>/feedbacks")
|
||||
class AgentMessageFeedbackApi(Resource):
|
||||
@console_ns.doc("create_agent_message_feedback")
|
||||
@console_ns.doc(description="Create or update Agent App message feedback")
|
||||
@console_ns.doc(params={"agent_id": "Agent ID"})
|
||||
@console_ns.expect(console_ns.models[MessageFeedbackPayload.__name__])
|
||||
@console_ns.response(200, "Feedback updated successfully", console_ns.models[SimpleResultResponse.__name__])
|
||||
@console_ns.response(404, "Agent or message not found")
|
||||
@setup_required
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
def post(self, current_tenant_id: str, current_user: Account, agent_id: UUID):
|
||||
app_model = resolve_agent_app_model(tenant_id=current_tenant_id, agent_id=agent_id)
|
||||
return _update_message_feedback(current_user=current_user, app_model=app_model)
|
||||
|
||||
|
||||
@console_ns.route("/apps/<uuid:app_id>/annotations/count")
|
||||
@ -340,31 +281,28 @@ class MessageSuggestedQuestionApi(Resource):
|
||||
@get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT, AppMode.AGENT])
|
||||
@with_current_user
|
||||
def get(self, current_user: Account, app_model: App, message_id: UUID):
|
||||
message_id_str = str(message_id)
|
||||
return _get_message_suggested_questions(current_user=current_user, app_model=app_model, message_id=message_id)
|
||||
|
||||
try:
|
||||
questions = MessageService.get_suggested_questions_after_answer(
|
||||
app_model=app_model, message_id=message_id_str, user=current_user, invoke_from=InvokeFrom.DEBUGGER
|
||||
)
|
||||
except MessageNotExistsError:
|
||||
raise NotFound("Message not found")
|
||||
except ConversationNotExistsError:
|
||||
raise NotFound("Conversation not found")
|
||||
except ProviderTokenNotInitError as ex:
|
||||
raise ProviderNotInitializeError(ex.description)
|
||||
except QuotaExceededError:
|
||||
raise ProviderQuotaExceededError()
|
||||
except ModelCurrentlyNotSupportError:
|
||||
raise ProviderModelCurrentlyNotSupportError()
|
||||
except InvokeError as e:
|
||||
raise CompletionRequestError(e.description)
|
||||
except SuggestedQuestionsAfterAnswerDisabledError:
|
||||
raise AppSuggestedQuestionsAfterAnswerDisabledError()
|
||||
except Exception:
|
||||
logger.exception("internal server error.")
|
||||
raise InternalServerError()
|
||||
|
||||
return {"data": questions}
|
||||
@console_ns.route("/agent/<uuid:agent_id>/chat-messages/<uuid:message_id>/suggested-questions")
|
||||
class AgentMessageSuggestedQuestionApi(Resource):
|
||||
@console_ns.doc("get_agent_message_suggested_questions")
|
||||
@console_ns.doc(description="Get suggested questions for an Agent App message")
|
||||
@console_ns.doc(params={"agent_id": "Agent ID", "message_id": "Message ID"})
|
||||
@console_ns.response(
|
||||
200,
|
||||
"Suggested questions retrieved successfully",
|
||||
console_ns.models[SuggestedQuestionsResponse.__name__],
|
||||
)
|
||||
@console_ns.response(404, "Agent, message, or conversation not found")
|
||||
@setup_required
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
def get(self, current_tenant_id: str, current_user: Account, agent_id: UUID, message_id: UUID):
|
||||
app_model = resolve_agent_app_model(tenant_id=current_tenant_id, agent_id=agent_id)
|
||||
return _get_message_suggested_questions(current_user=current_user, app_model=app_model, message_id=message_id)
|
||||
|
||||
|
||||
@console_ns.route("/apps/<uuid:app_id>/feedbacks/export")
|
||||
@ -423,14 +361,167 @@ class MessageApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
def get(self, app_model: App, message_id: UUID):
|
||||
message_id_str = str(message_id)
|
||||
return _get_message_detail(app_model=app_model, message_id=message_id)
|
||||
|
||||
message = db.session.scalar(
|
||||
select(Message).where(Message.id == message_id_str, Message.app_id == app_model.id).limit(1)
|
||||
|
||||
@console_ns.route("/agent/<uuid:agent_id>/messages/<uuid:message_id>")
|
||||
class AgentMessageApi(Resource):
|
||||
@console_ns.doc("get_agent_message")
|
||||
@console_ns.doc(description="Get Agent App message details by ID")
|
||||
@console_ns.doc(params={"agent_id": "Agent ID", "message_id": "Message ID"})
|
||||
@console_ns.response(200, "Message retrieved successfully", console_ns.models[MessageDetailResponse.__name__])
|
||||
@console_ns.response(404, "Agent or message not found")
|
||||
@setup_required
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@with_current_tenant_id
|
||||
def get(self, current_tenant_id: str, agent_id: UUID, message_id: UUID):
|
||||
app_model = resolve_agent_app_model(tenant_id=current_tenant_id, agent_id=agent_id)
|
||||
return _get_message_detail(app_model=app_model, message_id=message_id)
|
||||
|
||||
|
||||
def _list_chat_messages(*, app_model: App):
|
||||
args = ChatMessagesQuery.model_validate(request.args.to_dict())
|
||||
|
||||
conversation = db.session.scalar(
|
||||
select(Conversation)
|
||||
.where(Conversation.id == args.conversation_id, Conversation.app_id == app_model.id)
|
||||
.limit(1)
|
||||
)
|
||||
|
||||
if not conversation:
|
||||
raise NotFound("Conversation Not Exists.")
|
||||
|
||||
if args.first_id:
|
||||
first_message = db.session.scalar(
|
||||
select(Message).where(Message.conversation_id == conversation.id, Message.id == args.first_id).limit(1)
|
||||
)
|
||||
|
||||
if not message:
|
||||
raise NotFound("Message Not Exists.")
|
||||
if not first_message:
|
||||
raise NotFound("First message not found")
|
||||
|
||||
attach_message_extra_contents([message])
|
||||
return MessageDetailResponse.model_validate(message, from_attributes=True).model_dump(mode="json")
|
||||
history_messages = db.session.scalars(
|
||||
select(Message)
|
||||
.where(
|
||||
Message.conversation_id == conversation.id,
|
||||
Message.created_at < first_message.created_at,
|
||||
Message.id != first_message.id,
|
||||
)
|
||||
.order_by(Message.created_at.desc())
|
||||
.limit(args.limit)
|
||||
).all()
|
||||
else:
|
||||
history_messages = db.session.scalars(
|
||||
select(Message)
|
||||
.where(Message.conversation_id == conversation.id)
|
||||
.order_by(Message.created_at.desc())
|
||||
.limit(args.limit)
|
||||
).all()
|
||||
|
||||
# Initialize has_more based on whether we have a full page
|
||||
if len(history_messages) == args.limit:
|
||||
current_page_first_message = history_messages[-1]
|
||||
# Check if there are more messages before the current page
|
||||
has_more = db.session.scalar(
|
||||
select(
|
||||
exists().where(
|
||||
Message.conversation_id == conversation.id,
|
||||
Message.created_at < current_page_first_message.created_at,
|
||||
Message.id != current_page_first_message.id,
|
||||
)
|
||||
)
|
||||
)
|
||||
else:
|
||||
# If we don't have a full page, there are no more messages
|
||||
has_more = False
|
||||
|
||||
history_messages = list(reversed(history_messages))
|
||||
attach_message_extra_contents(history_messages)
|
||||
|
||||
return MessageInfiniteScrollPaginationResponse.model_validate(
|
||||
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 = db.session.scalar(
|
||||
select(Message).where(Message.id == message_id, Message.app_id == app_model.id).limit(1)
|
||||
)
|
||||
|
||||
if not message:
|
||||
raise NotFound("Message Not Exists.")
|
||||
|
||||
feedback = message.admin_feedback
|
||||
|
||||
if not args.rating and feedback:
|
||||
db.session.delete(feedback)
|
||||
elif args.rating and feedback:
|
||||
feedback.rating = FeedbackRating(args.rating)
|
||||
feedback.content = args.content
|
||||
elif not args.rating and not feedback:
|
||||
raise ValueError("rating cannot be None when feedback not exists")
|
||||
else:
|
||||
rating_value = args.rating
|
||||
if rating_value is None:
|
||||
raise ValueError("rating is required to create feedback")
|
||||
feedback = MessageFeedback(
|
||||
app_id=app_model.id,
|
||||
conversation_id=message.conversation_id,
|
||||
message_id=message.id,
|
||||
rating=FeedbackRating(rating_value),
|
||||
content=args.content,
|
||||
from_source=FeedbackFromSource.ADMIN,
|
||||
from_account_id=current_user.id,
|
||||
)
|
||||
db.session.add(feedback)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
return {"result": "success"}
|
||||
|
||||
|
||||
def _get_message_suggested_questions(*, current_user: Account, app_model: App, message_id: UUID):
|
||||
message_id_str = str(message_id)
|
||||
|
||||
try:
|
||||
questions = MessageService.get_suggested_questions_after_answer(
|
||||
app_model=app_model, message_id=message_id_str, user=current_user, invoke_from=InvokeFrom.DEBUGGER
|
||||
)
|
||||
except MessageNotExistsError:
|
||||
raise NotFound("Message not found")
|
||||
except ConversationNotExistsError:
|
||||
raise NotFound("Conversation not found")
|
||||
except ProviderTokenNotInitError as ex:
|
||||
raise ProviderNotInitializeError(ex.description)
|
||||
except QuotaExceededError:
|
||||
raise ProviderQuotaExceededError()
|
||||
except ModelCurrentlyNotSupportError:
|
||||
raise ProviderModelCurrentlyNotSupportError()
|
||||
except InvokeError as e:
|
||||
raise CompletionRequestError(e.description)
|
||||
except SuggestedQuestionsAfterAnswerDisabledError:
|
||||
raise AppSuggestedQuestionsAfterAnswerDisabledError()
|
||||
except Exception:
|
||||
logger.exception("internal server error.")
|
||||
raise InternalServerError()
|
||||
|
||||
return {"data": questions}
|
||||
|
||||
|
||||
def _get_message_detail(*, app_model: App, message_id: UUID):
|
||||
message_id_str = str(message_id)
|
||||
|
||||
message = db.session.scalar(
|
||||
select(Message).where(Message.id == message_id_str, Message.app_id == app_model.id).limit(1)
|
||||
)
|
||||
|
||||
if not message:
|
||||
raise NotFound("Message Not Exists.")
|
||||
|
||||
attach_message_extra_contents([message])
|
||||
return MessageDetailResponse.model_validate(message, from_attributes=True).model_dump(mode="json")
|
||||
|
||||
@ -392,6 +392,58 @@ Check if activation token is valid
|
||||
| 400 | Invalid request parameters | |
|
||||
| 403 | Insufficient permissions | |
|
||||
|
||||
### [GET] /agent/{agent_id}/chat-messages
|
||||
Get Agent App chat messages for a conversation with pagination
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Located in | Description | Required | Schema |
|
||||
| ---- | ---------- | ----------- | -------- | ------ |
|
||||
| agent_id | path | Agent ID | Yes | string |
|
||||
| conversation_id | query | Conversation ID | Yes | string |
|
||||
| first_id | query | First message ID for pagination | No | string |
|
||||
| limit | query | Number of messages to return (1-100) | No | integer, <br>**Default:** 20 |
|
||||
|
||||
#### Responses
|
||||
|
||||
| Code | Description | Schema |
|
||||
| ---- | ----------- | ------ |
|
||||
| 200 | Success | **application/json**: [MessageInfiniteScrollPaginationResponse](#messageinfinitescrollpaginationresponse)<br> |
|
||||
| 404 | Agent or conversation not found | |
|
||||
|
||||
### [GET] /agent/{agent_id}/chat-messages/{message_id}/suggested-questions
|
||||
Get suggested questions for an Agent App message
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Located in | Description | Required | Schema |
|
||||
| ---- | ---------- | ----------- | -------- | ------ |
|
||||
| agent_id | path | Agent ID | Yes | string |
|
||||
| message_id | path | Message ID | Yes | string |
|
||||
|
||||
#### Responses
|
||||
|
||||
| Code | Description | Schema |
|
||||
| ---- | ----------- | ------ |
|
||||
| 200 | Suggested questions retrieved successfully | **application/json**: [SuggestedQuestionsResponse](#suggestedquestionsresponse)<br> |
|
||||
| 404 | Agent, message, or conversation not found | |
|
||||
|
||||
### [POST] /agent/{agent_id}/chat-messages/{task_id}/stop
|
||||
Stop a running Agent App chat message generation
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Located in | Description | Required | Schema |
|
||||
| ---- | ---------- | ----------- | -------- | ------ |
|
||||
| agent_id | path | Agent ID | Yes | string |
|
||||
| task_id | path | Task ID to stop | Yes | string |
|
||||
|
||||
#### Responses
|
||||
|
||||
| Code | Description | Schema |
|
||||
| ---- | ----------- | ------ |
|
||||
| 200 | Task stopped successfully | **application/json**: [SimpleResultResponse](#simpleresultresponse)<br> |
|
||||
|
||||
### [GET] /agent/{agent_id}/composer
|
||||
#### Parameters
|
||||
|
||||
@ -527,6 +579,28 @@ Update an Agent App's presentation features (opener, follow-up, citations, ...)
|
||||
| 400 | Invalid configuration | |
|
||||
| 404 | Agent not found | |
|
||||
|
||||
### [POST] /agent/{agent_id}/feedbacks
|
||||
Create or update Agent App message feedback
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Located in | Description | Required | Schema |
|
||||
| ---- | ---------- | ----------- | -------- | ------ |
|
||||
| agent_id | path | Agent ID | Yes | string |
|
||||
|
||||
#### Request Body
|
||||
|
||||
| Required | Schema |
|
||||
| -------- | ------ |
|
||||
| Yes | **application/json**: [MessageFeedbackPayload](#messagefeedbackpayload)<br> |
|
||||
|
||||
#### Responses
|
||||
|
||||
| Code | Description | Schema |
|
||||
| ---- | ----------- | ------ |
|
||||
| 200 | Feedback updated successfully | **application/json**: [SimpleResultResponse](#simpleresultresponse)<br> |
|
||||
| 404 | Agent or message not found | |
|
||||
|
||||
### [DELETE] /agent/{agent_id}/files
|
||||
Delete one Agent App drive file by key
|
||||
|
||||
@ -564,6 +638,23 @@ Commit an uploaded file into the Agent App drive under files/<name>
|
||||
| ---- | ----------- | ------ |
|
||||
| 201 | File committed into the agent drive | **application/json**: [AgentDriveFileCommitResponse](#agentdrivefilecommitresponse)<br> |
|
||||
|
||||
### [GET] /agent/{agent_id}/messages/{message_id}
|
||||
Get Agent App message details by ID
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Located in | Description | Required | Schema |
|
||||
| ---- | ---------- | ----------- | -------- | ------ |
|
||||
| agent_id | path | Agent ID | Yes | string |
|
||||
| message_id | path | Message ID | Yes | string |
|
||||
|
||||
#### Responses
|
||||
|
||||
| Code | Description | Schema |
|
||||
| ---- | ----------- | ------ |
|
||||
| 200 | Message retrieved successfully | **application/json**: [MessageDetailResponse](#messagedetailresponse)<br> |
|
||||
| 404 | Agent or message not found | |
|
||||
|
||||
### [GET] /agent/{agent_id}/referencing-workflows
|
||||
List workflow apps that reference this Agent App's bound Agent (read-only)
|
||||
|
||||
|
||||
@ -9,7 +9,14 @@ from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from core.workflow.nodes.agent_v2.validators import WorkflowAgentNodeValidator
|
||||
from models.agent import Agent, AgentScope, AgentStatus, WorkflowAgentBindingType, WorkflowAgentNodeBinding
|
||||
from models.agent import (
|
||||
Agent,
|
||||
AgentConfigSnapshot,
|
||||
AgentScope,
|
||||
AgentStatus,
|
||||
WorkflowAgentBindingType,
|
||||
WorkflowAgentNodeBinding,
|
||||
)
|
||||
from models.agent_config_entities import DeclaredOutputConfig, WorkflowNodeJobConfig
|
||||
from models.workflow import Workflow
|
||||
|
||||
@ -66,7 +73,7 @@ class WorkflowAgentPublishService:
|
||||
WorkflowAgentNodeValidator.validate_draft_workflow(session=session, workflow=draft_workflow)
|
||||
|
||||
@classmethod
|
||||
def sync_roster_agent_bindings_for_draft(
|
||||
def sync_agent_bindings_for_draft(
|
||||
cls,
|
||||
*,
|
||||
session: Session,
|
||||
@ -96,7 +103,7 @@ class WorkflowAgentPublishService:
|
||||
continue
|
||||
if not isinstance(binding_payload, Mapping):
|
||||
raise ValueError(f"Workflow Agent node {node_id} has invalid agent_binding.")
|
||||
cls._sync_roster_agent_binding_for_node(
|
||||
cls._sync_agent_binding_for_node(
|
||||
session=session,
|
||||
draft_workflow=draft_workflow,
|
||||
node_id=node_id,
|
||||
@ -108,7 +115,21 @@ class WorkflowAgentPublishService:
|
||||
session.flush()
|
||||
|
||||
@classmethod
|
||||
def _sync_roster_agent_binding_for_node(
|
||||
def sync_roster_agent_bindings_for_draft(
|
||||
cls,
|
||||
*,
|
||||
session: Session,
|
||||
draft_workflow: Workflow,
|
||||
account_id: str,
|
||||
) -> None:
|
||||
cls.sync_agent_bindings_for_draft(
|
||||
session=session,
|
||||
draft_workflow=draft_workflow,
|
||||
account_id=account_id,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _sync_agent_binding_for_node(
|
||||
cls,
|
||||
*,
|
||||
session: Session,
|
||||
@ -120,26 +141,33 @@ class WorkflowAgentPublishService:
|
||||
account_id: str,
|
||||
) -> None:
|
||||
binding_type = node_binding.get("binding_type")
|
||||
if binding_type != WorkflowAgentBindingType.ROSTER_AGENT.value:
|
||||
raise ValueError(f"Workflow Agent node {node_id} only supports roster_agent graph binding.")
|
||||
agent_id = node_binding.get("agent_id")
|
||||
if not isinstance(agent_id, str) or not agent_id:
|
||||
raise ValueError(f"Workflow Agent node {node_id} roster_agent binding requires agent_id.")
|
||||
raise ValueError(f"Workflow Agent node {node_id} agent binding requires agent_id.")
|
||||
|
||||
agent = session.scalar(
|
||||
select(Agent)
|
||||
.where(
|
||||
Agent.tenant_id == draft_workflow.tenant_id,
|
||||
Agent.id == agent_id,
|
||||
Agent.scope == AgentScope.ROSTER,
|
||||
Agent.status == AgentStatus.ACTIVE,
|
||||
if binding_type == WorkflowAgentBindingType.ROSTER_AGENT.value:
|
||||
agent, current_snapshot_id = cls._resolve_roster_agent_graph_binding(
|
||||
session=session,
|
||||
draft_workflow=draft_workflow,
|
||||
node_id=node_id,
|
||||
agent_id=agent_id,
|
||||
)
|
||||
.limit(1)
|
||||
)
|
||||
if agent is None:
|
||||
raise ValueError(f"Workflow Agent node {node_id} references an unavailable roster agent.")
|
||||
if not agent.active_config_snapshot_id:
|
||||
raise ValueError(f"Workflow Agent node {node_id} roster agent has no active config snapshot.")
|
||||
resolved_binding_type = WorkflowAgentBindingType.ROSTER_AGENT
|
||||
elif binding_type == WorkflowAgentBindingType.INLINE_AGENT.value:
|
||||
raw_current_snapshot_id = node_binding.get("current_snapshot_id")
|
||||
if not isinstance(raw_current_snapshot_id, str) or not raw_current_snapshot_id:
|
||||
raise ValueError(f"Workflow Agent node {node_id} inline_agent binding requires current_snapshot_id.")
|
||||
current_snapshot_id = raw_current_snapshot_id
|
||||
agent = cls._resolve_inline_agent_graph_binding(
|
||||
session=session,
|
||||
draft_workflow=draft_workflow,
|
||||
node_id=node_id,
|
||||
agent_id=agent_id,
|
||||
current_snapshot_id=current_snapshot_id,
|
||||
)
|
||||
resolved_binding_type = WorkflowAgentBindingType.INLINE_AGENT
|
||||
else:
|
||||
raise ValueError(f"Workflow Agent node {node_id} has unsupported agent_binding type.")
|
||||
|
||||
binding = existing_binding
|
||||
node_job_config = cls._node_job_config_from_node_data(
|
||||
@ -160,11 +188,84 @@ class WorkflowAgentPublishService:
|
||||
else:
|
||||
binding.node_job_config = node_job_config
|
||||
|
||||
binding.binding_type = WorkflowAgentBindingType.ROSTER_AGENT
|
||||
binding.binding_type = resolved_binding_type
|
||||
binding.agent_id = agent.id
|
||||
binding.current_snapshot_id = agent.active_config_snapshot_id
|
||||
binding.current_snapshot_id = current_snapshot_id
|
||||
binding.updated_by = account_id
|
||||
|
||||
@classmethod
|
||||
def _resolve_roster_agent_graph_binding(
|
||||
cls,
|
||||
*,
|
||||
session: Session,
|
||||
draft_workflow: Workflow,
|
||||
node_id: str,
|
||||
agent_id: str,
|
||||
) -> tuple[Agent, str]:
|
||||
agent = session.scalar(
|
||||
select(Agent)
|
||||
.where(
|
||||
Agent.tenant_id == draft_workflow.tenant_id,
|
||||
Agent.id == agent_id,
|
||||
Agent.scope == AgentScope.ROSTER,
|
||||
Agent.status == AgentStatus.ACTIVE,
|
||||
)
|
||||
.limit(1)
|
||||
)
|
||||
if agent is None:
|
||||
raise ValueError(f"Workflow Agent node {node_id} references an unavailable roster agent.")
|
||||
if agent.scope != AgentScope.ROSTER:
|
||||
raise ValueError(f"Workflow Agent node {node_id} roster_agent binding must reference a roster agent.")
|
||||
if not agent.active_config_snapshot_id:
|
||||
raise ValueError(f"Workflow Agent node {node_id} roster agent has no active config snapshot.")
|
||||
return agent, agent.active_config_snapshot_id
|
||||
|
||||
@classmethod
|
||||
def _resolve_inline_agent_graph_binding(
|
||||
cls,
|
||||
*,
|
||||
session: Session,
|
||||
draft_workflow: Workflow,
|
||||
node_id: str,
|
||||
agent_id: str,
|
||||
current_snapshot_id: str,
|
||||
) -> Agent:
|
||||
agent = session.scalar(
|
||||
select(Agent)
|
||||
.where(
|
||||
Agent.tenant_id == draft_workflow.tenant_id,
|
||||
Agent.id == agent_id,
|
||||
Agent.scope == AgentScope.WORKFLOW_ONLY,
|
||||
Agent.app_id == draft_workflow.app_id,
|
||||
Agent.workflow_id == draft_workflow.id,
|
||||
Agent.workflow_node_id == node_id,
|
||||
Agent.status == AgentStatus.ACTIVE,
|
||||
)
|
||||
.limit(1)
|
||||
)
|
||||
if agent is None:
|
||||
raise ValueError(f"Workflow Agent node {node_id} references an unavailable inline agent.")
|
||||
if (
|
||||
agent.scope != AgentScope.WORKFLOW_ONLY
|
||||
or agent.app_id != draft_workflow.app_id
|
||||
or agent.workflow_id != draft_workflow.id
|
||||
or agent.workflow_node_id != node_id
|
||||
):
|
||||
raise ValueError(f"Workflow Agent node {node_id} inline_agent binding does not belong to this node.")
|
||||
|
||||
snapshot = session.scalar(
|
||||
select(AgentConfigSnapshot)
|
||||
.where(
|
||||
AgentConfigSnapshot.tenant_id == draft_workflow.tenant_id,
|
||||
AgentConfigSnapshot.agent_id == agent.id,
|
||||
AgentConfigSnapshot.id == current_snapshot_id,
|
||||
)
|
||||
.limit(1)
|
||||
)
|
||||
if snapshot is None or snapshot.agent_id != agent.id:
|
||||
raise ValueError(f"Workflow Agent node {node_id} references a missing inline agent config snapshot.")
|
||||
return agent
|
||||
|
||||
@classmethod
|
||||
def _node_job_config_from_node_data(
|
||||
cls,
|
||||
|
||||
@ -323,7 +323,7 @@ class WorkflowService:
|
||||
from services.agent.workflow_publish_service import WorkflowAgentPublishService
|
||||
|
||||
db.session.flush()
|
||||
WorkflowAgentPublishService.sync_roster_agent_bindings_for_draft(
|
||||
WorkflowAgentPublishService.sync_agent_bindings_for_draft(
|
||||
session=cast(Session, db.session),
|
||||
draft_workflow=workflow,
|
||||
account_id=account.id,
|
||||
|
||||
@ -4,6 +4,7 @@ from typing import Any, cast
|
||||
|
||||
import pytest
|
||||
from flask import Flask
|
||||
from werkzeug.exceptions import InternalServerError, NotFound
|
||||
|
||||
from controllers.console import console_ns
|
||||
from controllers.console.agent import composer as composer_controller
|
||||
@ -25,6 +26,15 @@ from controllers.console.agent.roster import (
|
||||
AgentRosterVersionDetailApi,
|
||||
AgentRosterVersionsApi,
|
||||
)
|
||||
from controllers.console.app import completion as completion_controller
|
||||
from controllers.console.app import message as message_controller
|
||||
from controllers.console.app.completion import AgentChatMessageApi, AgentChatMessageStopApi
|
||||
from controllers.console.app.message import (
|
||||
AgentChatMessageListApi,
|
||||
AgentMessageApi,
|
||||
AgentMessageFeedbackApi,
|
||||
AgentMessageSuggestedQuestionApi,
|
||||
)
|
||||
from services.entities.agent_entities import ComposerSaveStrategy, ComposerVariant
|
||||
|
||||
|
||||
@ -132,6 +142,11 @@ def test_agent_v2_console_routes_are_agent_id_first() -> None:
|
||||
"/agent/<uuid:agent_id>/sandbox/files",
|
||||
"/agent/<uuid:agent_id>/skills/upload",
|
||||
"/agent/<uuid:agent_id>/files",
|
||||
"/agent/<uuid:agent_id>/chat-messages",
|
||||
"/agent/<uuid:agent_id>/chat-messages/<string:task_id>/stop",
|
||||
"/agent/<uuid:agent_id>/feedbacks",
|
||||
"/agent/<uuid:agent_id>/chat-messages/<uuid:message_id>/suggested-questions",
|
||||
"/agent/<uuid:agent_id>/messages/<uuid:message_id>",
|
||||
"/agent/invite-options",
|
||||
):
|
||||
assert route in paths
|
||||
@ -471,6 +486,272 @@ def test_agent_composer_routes_resolve_app_from_agent_id(
|
||||
assert cast(dict[str, object], captured["candidates"])["app_id"] == "app-1"
|
||||
|
||||
|
||||
def test_agent_chat_generate_and_stop_routes_resolve_app_from_agent_id(
|
||||
app: Flask, monkeypatch: pytest.MonkeyPatch, account_id: str
|
||||
) -> None:
|
||||
agent_id = "00000000-0000-0000-0000-000000000001"
|
||||
app_model = SimpleNamespace(id="app-1", mode="agent")
|
||||
captured: dict[str, object] = {}
|
||||
|
||||
def resolve_agent_app_model(**kwargs: object) -> object:
|
||||
captured["resolve"] = kwargs
|
||||
return app_model
|
||||
|
||||
def create_chat_message(**kwargs: object) -> dict[str, object]:
|
||||
captured["create"] = kwargs
|
||||
return {"result": "generated"}
|
||||
|
||||
def stop_chat_message(**kwargs: object) -> tuple[dict[str, object], int]:
|
||||
captured["stop"] = kwargs
|
||||
return {"result": "success"}, 200
|
||||
|
||||
monkeypatch.setattr(completion_controller, "resolve_agent_app_model", resolve_agent_app_model)
|
||||
monkeypatch.setattr(completion_controller, "_create_chat_message", create_chat_message)
|
||||
monkeypatch.setattr(completion_controller, "_stop_chat_message", stop_chat_message)
|
||||
|
||||
with app.test_request_context(json={"inputs": {}, "query": "hello"}):
|
||||
assert unwrap(AgentChatMessageApi.post)(
|
||||
AgentChatMessageApi(), "tenant-1", SimpleNamespace(id=account_id), agent_id
|
||||
) == {"result": "generated"}
|
||||
|
||||
assert cast(dict[str, object], captured["resolve"]) == {"tenant_id": "tenant-1", "agent_id": agent_id}
|
||||
create_call = cast(dict[str, object], captured["create"])
|
||||
assert create_call["app_model"] is app_model
|
||||
assert cast(SimpleNamespace, create_call["current_user"]).id == account_id
|
||||
|
||||
assert unwrap(AgentChatMessageStopApi.post)(
|
||||
AgentChatMessageStopApi(), "tenant-1", account_id, agent_id, "task-1"
|
||||
) == ({"result": "success"}, 200)
|
||||
stop_call = cast(dict[str, object], captured["stop"])
|
||||
assert stop_call == {"current_user_id": account_id, "app_model": app_model, "task_id": "task-1"}
|
||||
|
||||
|
||||
def test_agent_chat_helper_forces_agent_streaming_and_external_trace(
|
||||
app: Flask, monkeypatch: pytest.MonkeyPatch, account_id: str
|
||||
) -> None:
|
||||
app_model = SimpleNamespace(id="app-1", mode="agent")
|
||||
current_user = SimpleNamespace(id=account_id)
|
||||
captured: dict[str, object] = {}
|
||||
|
||||
def generate(**kwargs: object) -> dict[str, object]:
|
||||
captured.update(kwargs)
|
||||
return {"answer": "ok"}
|
||||
|
||||
monkeypatch.setattr(completion_controller.AppGenerateService, "generate", generate)
|
||||
monkeypatch.setattr(
|
||||
completion_controller.helper,
|
||||
"compact_generate_response",
|
||||
lambda response: {"response": response},
|
||||
)
|
||||
|
||||
with app.test_request_context(
|
||||
json={"inputs": {}, "query": "hello", "response_mode": "streaming"},
|
||||
headers={"X-Trace-Id": "trace-1"},
|
||||
):
|
||||
result = completion_controller._create_chat_message(current_user=current_user, app_model=app_model)
|
||||
|
||||
assert result == {"response": {"answer": "ok"}}
|
||||
assert captured["app_model"] is app_model
|
||||
assert captured["user"] is current_user
|
||||
assert captured["streaming"] is True
|
||||
args = cast(dict[str, object], captured["args"])
|
||||
assert args["response_mode"] == "streaming"
|
||||
assert args["auto_generate_name"] is False
|
||||
assert args["external_trace_id"] == "trace-1"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("error", "expected"),
|
||||
[
|
||||
(completion_controller.services.errors.conversation.ConversationNotExistsError(), NotFound),
|
||||
(
|
||||
completion_controller.services.errors.conversation.ConversationCompletedError(),
|
||||
completion_controller.ConversationCompletedError,
|
||||
),
|
||||
(
|
||||
completion_controller.services.errors.app_model_config.AppModelConfigBrokenError(),
|
||||
completion_controller.AppUnavailableError,
|
||||
),
|
||||
(
|
||||
completion_controller.ProviderTokenNotInitError("not initialized"),
|
||||
completion_controller.ProviderNotInitializeError,
|
||||
),
|
||||
(completion_controller.QuotaExceededError(), completion_controller.ProviderQuotaExceededError),
|
||||
(
|
||||
completion_controller.ModelCurrentlyNotSupportError(),
|
||||
completion_controller.ProviderModelCurrentlyNotSupportError,
|
||||
),
|
||||
(completion_controller.InvokeRateLimitError("rate limited"), completion_controller.InvokeRateLimitHttpError),
|
||||
(completion_controller.InvokeError("invoke failed"), completion_controller.CompletionRequestError),
|
||||
(RuntimeError("unexpected"), InternalServerError),
|
||||
],
|
||||
)
|
||||
def test_agent_chat_helper_maps_generation_errors(
|
||||
app: Flask,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
error: Exception,
|
||||
expected: type[Exception],
|
||||
) -> None:
|
||||
app_model = SimpleNamespace(id="app-1", mode="chat")
|
||||
monkeypatch.setattr(completion_controller.AppGenerateService, "generate", lambda **_: (_ for _ in ()).throw(error))
|
||||
|
||||
with app.test_request_context(json={"inputs": {}, "query": "hello"}):
|
||||
with pytest.raises(expected):
|
||||
completion_controller._create_chat_message(
|
||||
current_user=SimpleNamespace(id="account-1"),
|
||||
app_model=app_model,
|
||||
)
|
||||
|
||||
|
||||
def test_agent_chat_message_routes_resolve_app_from_agent_id(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
agent_id = "00000000-0000-0000-0000-000000000001"
|
||||
message_id = "00000000-0000-0000-0000-000000000002"
|
||||
app_model = SimpleNamespace(id="app-1")
|
||||
current_user = SimpleNamespace(id="account-1")
|
||||
captured: dict[str, object] = {}
|
||||
|
||||
def resolve_agent_app_model(**kwargs: object) -> object:
|
||||
captured["resolve"] = kwargs
|
||||
return app_model
|
||||
|
||||
def list_chat_messages(**kwargs: object) -> dict[str, object]:
|
||||
captured["list"] = kwargs
|
||||
return {"data": []}
|
||||
|
||||
def update_message_feedback(**kwargs: object) -> dict[str, object]:
|
||||
captured["feedback"] = kwargs
|
||||
return {"result": "success"}
|
||||
|
||||
def get_message_suggested_questions(**kwargs: object) -> dict[str, object]:
|
||||
captured["suggested"] = kwargs
|
||||
return {"data": ["next"]}
|
||||
|
||||
def get_message_detail(**kwargs: object) -> dict[str, object]:
|
||||
captured["detail"] = kwargs
|
||||
return {"id": message_id}
|
||||
|
||||
monkeypatch.setattr(message_controller, "resolve_agent_app_model", resolve_agent_app_model)
|
||||
monkeypatch.setattr(message_controller, "_list_chat_messages", list_chat_messages)
|
||||
monkeypatch.setattr(message_controller, "_update_message_feedback", update_message_feedback)
|
||||
monkeypatch.setattr(message_controller, "_get_message_suggested_questions", get_message_suggested_questions)
|
||||
monkeypatch.setattr(message_controller, "_get_message_detail", get_message_detail)
|
||||
|
||||
assert unwrap(AgentChatMessageListApi.get)(AgentChatMessageListApi(), "tenant-1", agent_id) == {"data": []}
|
||||
assert cast(dict[str, object], captured["list"])["app_model"] is app_model
|
||||
|
||||
with app.test_request_context(json={"message_id": message_id, "rating": "like"}):
|
||||
assert unwrap(AgentMessageFeedbackApi.post)(AgentMessageFeedbackApi(), "tenant-1", current_user, agent_id) == {
|
||||
"result": "success"
|
||||
}
|
||||
feedback_call = cast(dict[str, object], captured["feedback"])
|
||||
assert feedback_call["app_model"] is app_model
|
||||
assert feedback_call["current_user"] is current_user
|
||||
|
||||
assert unwrap(AgentMessageSuggestedQuestionApi.get)(
|
||||
AgentMessageSuggestedQuestionApi(), "tenant-1", current_user, agent_id, message_id
|
||||
) == {"data": ["next"]}
|
||||
suggested_call = cast(dict[str, object], captured["suggested"])
|
||||
assert suggested_call["app_model"] is app_model
|
||||
assert suggested_call["current_user"] is current_user
|
||||
assert suggested_call["message_id"] == message_id
|
||||
|
||||
assert unwrap(AgentMessageApi.get)(AgentMessageApi(), "tenant-1", agent_id, message_id) == {"id": message_id}
|
||||
detail_call = cast(dict[str, object], captured["detail"])
|
||||
assert detail_call == {"app_model": app_model, "message_id": message_id}
|
||||
|
||||
|
||||
def test_list_chat_messages_supports_first_id_pagination(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
conversation_id = "00000000-0000-0000-0000-000000000010"
|
||||
first_message_id = "00000000-0000-0000-0000-000000000011"
|
||||
older_message_id = "00000000-0000-0000-0000-000000000012"
|
||||
conversation = SimpleNamespace(id=conversation_id)
|
||||
first_message = SimpleNamespace(id=first_message_id, created_at=2)
|
||||
older_message = SimpleNamespace(id=older_message_id, created_at=1)
|
||||
scalar_values = iter([conversation, first_message, True])
|
||||
scalars_result = SimpleNamespace(all=lambda: [older_message])
|
||||
session = SimpleNamespace(
|
||||
scalar=lambda _stmt: next(scalar_values),
|
||||
scalars=lambda _stmt: scalars_result,
|
||||
)
|
||||
|
||||
class FakeMessagePaginationResponse:
|
||||
@classmethod
|
||||
def model_validate(cls, pagination: object, from_attributes: bool = False) -> object:
|
||||
return SimpleNamespace(
|
||||
model_dump=lambda mode: {
|
||||
"data": [item.id for item in pagination.data],
|
||||
"limit": pagination.limit,
|
||||
"has_more": pagination.has_more,
|
||||
}
|
||||
)
|
||||
|
||||
monkeypatch.setattr(message_controller, "db", SimpleNamespace(session=session))
|
||||
monkeypatch.setattr(message_controller, "attach_message_extra_contents", lambda messages: None)
|
||||
monkeypatch.setattr(message_controller, "MessageInfiniteScrollPaginationResponse", FakeMessagePaginationResponse)
|
||||
|
||||
with app.test_request_context(
|
||||
"/console/api/agent/agent-1/chat-messages"
|
||||
f"?conversation_id={conversation_id}&first_id={first_message_id}&limit=1"
|
||||
):
|
||||
result = message_controller._list_chat_messages(app_model=SimpleNamespace(id="app-1"))
|
||||
|
||||
assert result == {"data": [older_message_id], "limit": 1, "has_more": True}
|
||||
|
||||
|
||||
def test_update_message_feedback_rejects_empty_rating_without_existing_feedback(
|
||||
app: Flask, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
message_id = "00000000-0000-0000-0000-000000000002"
|
||||
message = SimpleNamespace(id=message_id, app_id="app-1", admin_feedback=None)
|
||||
session = SimpleNamespace(scalar=lambda _stmt: message)
|
||||
monkeypatch.setattr(message_controller, "db", SimpleNamespace(session=session))
|
||||
|
||||
with app.test_request_context(json={"message_id": message_id, "rating": None}):
|
||||
with pytest.raises(ValueError, match="rating cannot be None"):
|
||||
message_controller._update_message_feedback(
|
||||
current_user=SimpleNamespace(id="account-1"),
|
||||
app_model=SimpleNamespace(id="app-1"),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("error", "expected"),
|
||||
[
|
||||
(message_controller.MessageNotExistsError(), NotFound),
|
||||
(message_controller.ConversationNotExistsError(), NotFound),
|
||||
(
|
||||
message_controller.ProviderTokenNotInitError("not initialized"),
|
||||
message_controller.ProviderNotInitializeError,
|
||||
),
|
||||
(message_controller.QuotaExceededError(), message_controller.ProviderQuotaExceededError),
|
||||
(message_controller.ModelCurrentlyNotSupportError(), message_controller.ProviderModelCurrentlyNotSupportError),
|
||||
(message_controller.InvokeError("invoke failed"), message_controller.CompletionRequestError),
|
||||
(
|
||||
message_controller.SuggestedQuestionsAfterAnswerDisabledError(),
|
||||
message_controller.AppSuggestedQuestionsAfterAnswerDisabledError,
|
||||
),
|
||||
(RuntimeError("unexpected"), InternalServerError),
|
||||
],
|
||||
)
|
||||
def test_get_message_suggested_questions_maps_service_errors(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
error: Exception,
|
||||
expected: type[Exception],
|
||||
) -> None:
|
||||
monkeypatch.setattr(
|
||||
message_controller.MessageService,
|
||||
"get_suggested_questions_after_answer",
|
||||
lambda **_: (_ for _ in ()).throw(error),
|
||||
)
|
||||
|
||||
with pytest.raises(expected):
|
||||
message_controller._get_message_suggested_questions(
|
||||
current_user=SimpleNamespace(id="account-1"),
|
||||
app_model=SimpleNamespace(id="app-1"),
|
||||
message_id="00000000-0000-0000-0000-000000000002",
|
||||
)
|
||||
|
||||
|
||||
def test_dify_tool_candidate_response_keeps_granularity_fields():
|
||||
"""Both selection granularities must survive the fields-layer model —
|
||||
the frontend needs granularity/tools_count to render the Tools menu."""
|
||||
|
||||
@ -1261,6 +1261,225 @@ class TestWorkflowAgentDraftBindingSync:
|
||||
],
|
||||
).model_dump(mode="json")
|
||||
|
||||
def test_creates_inline_binding_from_agent_node_graph(self):
|
||||
workflow = Workflow(
|
||||
id="workflow-1",
|
||||
tenant_id="tenant-1",
|
||||
app_id="app-1",
|
||||
version=Workflow.VERSION_DRAFT,
|
||||
graph=json.dumps(
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "agent-node",
|
||||
"data": {
|
||||
"type": "agent",
|
||||
"version": "2",
|
||||
"agent_task": "Use the current node context.",
|
||||
"agent_binding": {
|
||||
"binding_type": "inline_agent",
|
||||
"agent_id": "inline-agent-1",
|
||||
"current_snapshot_id": "inline-snapshot-1",
|
||||
},
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
),
|
||||
)
|
||||
agent = Agent(
|
||||
id="inline-agent-1",
|
||||
tenant_id="tenant-1",
|
||||
name="Workflow Agent agent-node",
|
||||
agent_kind=AgentKind.DIFY_AGENT,
|
||||
scope=AgentScope.WORKFLOW_ONLY,
|
||||
source=AgentSource.WORKFLOW,
|
||||
app_id="app-1",
|
||||
workflow_id="workflow-1",
|
||||
workflow_node_id="agent-node",
|
||||
status=AgentStatus.ACTIVE,
|
||||
active_config_snapshot_id="inline-snapshot-1",
|
||||
)
|
||||
snapshot = AgentConfigSnapshot(
|
||||
id="inline-snapshot-1",
|
||||
tenant_id="tenant-1",
|
||||
agent_id="inline-agent-1",
|
||||
version=1,
|
||||
config_snapshot=AgentSoulConfig(),
|
||||
)
|
||||
session = FakeSession(scalar=[agent, snapshot], scalars=[[]])
|
||||
|
||||
WorkflowAgentPublishService.sync_agent_bindings_for_draft(
|
||||
session=session,
|
||||
draft_workflow=workflow,
|
||||
account_id="account-1",
|
||||
)
|
||||
|
||||
binding = next(item for item in session.added if isinstance(item, WorkflowAgentNodeBinding))
|
||||
assert binding.binding_type == WorkflowAgentBindingType.INLINE_AGENT
|
||||
assert binding.agent_id == "inline-agent-1"
|
||||
assert binding.current_snapshot_id == "inline-snapshot-1"
|
||||
assert binding.node_job_config_dict == WorkflowNodeJobConfig(
|
||||
workflow_prompt="Use the current node context.",
|
||||
).model_dump(mode="json")
|
||||
|
||||
def test_rejects_inline_binding_for_agent_owned_by_another_node(self):
|
||||
workflow = Workflow(
|
||||
id="workflow-1",
|
||||
tenant_id="tenant-1",
|
||||
app_id="app-1",
|
||||
version=Workflow.VERSION_DRAFT,
|
||||
graph=json.dumps(
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "agent-node",
|
||||
"data": {
|
||||
"type": "agent",
|
||||
"version": "2",
|
||||
"agent_binding": {
|
||||
"binding_type": "inline_agent",
|
||||
"agent_id": "inline-agent-1",
|
||||
"current_snapshot_id": "inline-snapshot-1",
|
||||
},
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
),
|
||||
)
|
||||
agent = Agent(
|
||||
id="inline-agent-1",
|
||||
tenant_id="tenant-1",
|
||||
name="Workflow Agent other-node",
|
||||
agent_kind=AgentKind.DIFY_AGENT,
|
||||
scope=AgentScope.WORKFLOW_ONLY,
|
||||
source=AgentSource.WORKFLOW,
|
||||
app_id="app-1",
|
||||
workflow_id="workflow-1",
|
||||
workflow_node_id="other-node",
|
||||
status=AgentStatus.ACTIVE,
|
||||
active_config_snapshot_id="inline-snapshot-1",
|
||||
)
|
||||
session = FakeSession(scalar=[agent], scalars=[[]])
|
||||
|
||||
with pytest.raises(ValueError, match="inline_agent binding does not belong to this node"):
|
||||
WorkflowAgentPublishService.sync_agent_bindings_for_draft(
|
||||
session=session,
|
||||
draft_workflow=workflow,
|
||||
account_id="account-1",
|
||||
)
|
||||
|
||||
def test_rejects_agent_node_graph_binding_with_unsupported_type(self):
|
||||
workflow = Workflow(
|
||||
id="workflow-1",
|
||||
tenant_id="tenant-1",
|
||||
app_id="app-1",
|
||||
version=Workflow.VERSION_DRAFT,
|
||||
graph=json.dumps(
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "agent-node",
|
||||
"data": {
|
||||
"type": "agent",
|
||||
"version": "2",
|
||||
"agent_binding": {
|
||||
"binding_type": "unknown",
|
||||
"agent_id": "agent-1",
|
||||
},
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError, match="unsupported agent_binding type"):
|
||||
WorkflowAgentPublishService.sync_agent_bindings_for_draft(
|
||||
session=FakeSession(scalars=[[]]),
|
||||
draft_workflow=workflow,
|
||||
account_id="account-1",
|
||||
)
|
||||
|
||||
def test_rejects_inline_binding_without_current_snapshot_id(self):
|
||||
workflow = Workflow(
|
||||
id="workflow-1",
|
||||
tenant_id="tenant-1",
|
||||
app_id="app-1",
|
||||
version=Workflow.VERSION_DRAFT,
|
||||
graph=json.dumps(
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "agent-node",
|
||||
"data": {
|
||||
"type": "agent",
|
||||
"version": "2",
|
||||
"agent_binding": {
|
||||
"binding_type": "inline_agent",
|
||||
"agent_id": "inline-agent-1",
|
||||
},
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError, match="inline_agent binding requires current_snapshot_id"):
|
||||
WorkflowAgentPublishService.sync_agent_bindings_for_draft(
|
||||
session=FakeSession(scalars=[[]]),
|
||||
draft_workflow=workflow,
|
||||
account_id="account-1",
|
||||
)
|
||||
|
||||
def test_rejects_inline_binding_with_missing_snapshot(self):
|
||||
workflow = Workflow(
|
||||
id="workflow-1",
|
||||
tenant_id="tenant-1",
|
||||
app_id="app-1",
|
||||
version=Workflow.VERSION_DRAFT,
|
||||
graph=json.dumps(
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "agent-node",
|
||||
"data": {
|
||||
"type": "agent",
|
||||
"version": "2",
|
||||
"agent_binding": {
|
||||
"binding_type": "inline_agent",
|
||||
"agent_id": "inline-agent-1",
|
||||
"current_snapshot_id": "missing-snapshot",
|
||||
},
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
),
|
||||
)
|
||||
agent = Agent(
|
||||
id="inline-agent-1",
|
||||
tenant_id="tenant-1",
|
||||
name="Workflow Agent agent-node",
|
||||
agent_kind=AgentKind.DIFY_AGENT,
|
||||
scope=AgentScope.WORKFLOW_ONLY,
|
||||
source=AgentSource.WORKFLOW,
|
||||
app_id="app-1",
|
||||
workflow_id="workflow-1",
|
||||
workflow_node_id="agent-node",
|
||||
status=AgentStatus.ACTIVE,
|
||||
active_config_snapshot_id="inline-snapshot-1",
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError, match="missing inline agent config snapshot"):
|
||||
WorkflowAgentPublishService.sync_agent_bindings_for_draft(
|
||||
session=FakeSession(scalar=[agent, None], scalars=[[]]),
|
||||
draft_workflow=workflow,
|
||||
account_id="account-1",
|
||||
)
|
||||
|
||||
def test_updates_existing_roster_binding_prompt_from_agent_node_graph(self):
|
||||
workflow = Workflow(
|
||||
id="workflow-1",
|
||||
|
||||
@ -11,6 +11,11 @@ import {
|
||||
zDeleteAgentByAgentIdResponse,
|
||||
zDeleteAgentByAgentIdSkillsBySlugPath,
|
||||
zDeleteAgentByAgentIdSkillsBySlugResponse,
|
||||
zGetAgentByAgentIdChatMessagesByMessageIdSuggestedQuestionsPath,
|
||||
zGetAgentByAgentIdChatMessagesByMessageIdSuggestedQuestionsResponse,
|
||||
zGetAgentByAgentIdChatMessagesPath,
|
||||
zGetAgentByAgentIdChatMessagesQuery,
|
||||
zGetAgentByAgentIdChatMessagesResponse,
|
||||
zGetAgentByAgentIdComposerCandidatesPath,
|
||||
zGetAgentByAgentIdComposerCandidatesResponse,
|
||||
zGetAgentByAgentIdComposerPath,
|
||||
@ -24,6 +29,8 @@ import {
|
||||
zGetAgentByAgentIdDriveFilesPreviewResponse,
|
||||
zGetAgentByAgentIdDriveFilesQuery,
|
||||
zGetAgentByAgentIdDriveFilesResponse,
|
||||
zGetAgentByAgentIdMessagesByMessageIdPath,
|
||||
zGetAgentByAgentIdMessagesByMessageIdResponse,
|
||||
zGetAgentByAgentIdPath,
|
||||
zGetAgentByAgentIdReferencingWorkflowsPath,
|
||||
zGetAgentByAgentIdReferencingWorkflowsResponse,
|
||||
@ -43,12 +50,17 @@ import {
|
||||
zGetAgentQuery,
|
||||
zGetAgentResponse,
|
||||
zPostAgentBody,
|
||||
zPostAgentByAgentIdChatMessagesByTaskIdStopPath,
|
||||
zPostAgentByAgentIdChatMessagesByTaskIdStopResponse,
|
||||
zPostAgentByAgentIdComposerValidateBody,
|
||||
zPostAgentByAgentIdComposerValidatePath,
|
||||
zPostAgentByAgentIdComposerValidateResponse,
|
||||
zPostAgentByAgentIdFeaturesBody,
|
||||
zPostAgentByAgentIdFeaturesPath,
|
||||
zPostAgentByAgentIdFeaturesResponse,
|
||||
zPostAgentByAgentIdFeedbacksBody,
|
||||
zPostAgentByAgentIdFeedbacksPath,
|
||||
zPostAgentByAgentIdFeedbacksResponse,
|
||||
zPostAgentByAgentIdFilesBody,
|
||||
zPostAgentByAgentIdFilesPath,
|
||||
zPostAgentByAgentIdFilesResponse,
|
||||
@ -85,7 +97,79 @@ export const inviteOptions = {
|
||||
get,
|
||||
}
|
||||
|
||||
/**
|
||||
* Get suggested questions for an Agent App message
|
||||
*/
|
||||
export const get2 = oc
|
||||
.route({
|
||||
description: 'Get suggested questions for an Agent App message',
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
operationId: 'getAgentByAgentIdChatMessagesByMessageIdSuggestedQuestions',
|
||||
path: '/agent/{agent_id}/chat-messages/{message_id}/suggested-questions',
|
||||
tags: ['console'],
|
||||
})
|
||||
.input(z.object({ params: zGetAgentByAgentIdChatMessagesByMessageIdSuggestedQuestionsPath }))
|
||||
.output(zGetAgentByAgentIdChatMessagesByMessageIdSuggestedQuestionsResponse)
|
||||
|
||||
export const suggestedQuestions = {
|
||||
get: get2,
|
||||
}
|
||||
|
||||
export const byMessageId = {
|
||||
suggestedQuestions,
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop a running Agent App chat message generation
|
||||
*/
|
||||
export const post = oc
|
||||
.route({
|
||||
description: 'Stop a running Agent App chat message generation',
|
||||
inputStructure: 'detailed',
|
||||
method: 'POST',
|
||||
operationId: 'postAgentByAgentIdChatMessagesByTaskIdStop',
|
||||
path: '/agent/{agent_id}/chat-messages/{task_id}/stop',
|
||||
tags: ['console'],
|
||||
})
|
||||
.input(z.object({ params: zPostAgentByAgentIdChatMessagesByTaskIdStopPath }))
|
||||
.output(zPostAgentByAgentIdChatMessagesByTaskIdStopResponse)
|
||||
|
||||
export const stop = {
|
||||
post,
|
||||
}
|
||||
|
||||
export const byTaskId = {
|
||||
stop,
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Agent App chat messages for a conversation with pagination
|
||||
*/
|
||||
export const get3 = oc
|
||||
.route({
|
||||
description: 'Get Agent App chat messages for a conversation with pagination',
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
operationId: 'getAgentByAgentIdChatMessages',
|
||||
path: '/agent/{agent_id}/chat-messages',
|
||||
tags: ['console'],
|
||||
})
|
||||
.input(
|
||||
z.object({
|
||||
params: zGetAgentByAgentIdChatMessagesPath,
|
||||
query: zGetAgentByAgentIdChatMessagesQuery,
|
||||
}),
|
||||
)
|
||||
.output(zGetAgentByAgentIdChatMessagesResponse)
|
||||
|
||||
export const chatMessages = {
|
||||
get: get3,
|
||||
byMessageId,
|
||||
byTaskId,
|
||||
}
|
||||
|
||||
export const get4 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
@ -97,10 +181,10 @@ export const get2 = oc
|
||||
.output(zGetAgentByAgentIdComposerCandidatesResponse)
|
||||
|
||||
export const candidates = {
|
||||
get: get2,
|
||||
get: get4,
|
||||
}
|
||||
|
||||
export const post = oc
|
||||
export const post2 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'POST',
|
||||
@ -117,10 +201,10 @@ export const post = oc
|
||||
.output(zPostAgentByAgentIdComposerValidateResponse)
|
||||
|
||||
export const validate = {
|
||||
post,
|
||||
post: post2,
|
||||
}
|
||||
|
||||
export const get3 = oc
|
||||
export const get5 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
@ -143,7 +227,7 @@ export const put = oc
|
||||
.output(zPutAgentByAgentIdComposerResponse)
|
||||
|
||||
export const composer = {
|
||||
get: get3,
|
||||
get: get5,
|
||||
put,
|
||||
candidates,
|
||||
validate,
|
||||
@ -152,7 +236,7 @@ export const composer = {
|
||||
/**
|
||||
* Time-limited external signed URL for one Agent App drive value
|
||||
*/
|
||||
export const get4 = oc
|
||||
export const get6 = oc
|
||||
.route({
|
||||
description: 'Time-limited external signed URL for one Agent App drive value',
|
||||
inputStructure: 'detailed',
|
||||
@ -170,13 +254,13 @@ export const get4 = oc
|
||||
.output(zGetAgentByAgentIdDriveFilesDownloadResponse)
|
||||
|
||||
export const download = {
|
||||
get: get4,
|
||||
get: get6,
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncated text preview of one Agent App drive value
|
||||
*/
|
||||
export const get5 = oc
|
||||
export const get7 = oc
|
||||
.route({
|
||||
description: 'Truncated text preview of one Agent App drive value',
|
||||
inputStructure: 'detailed',
|
||||
@ -194,13 +278,13 @@ export const get5 = oc
|
||||
.output(zGetAgentByAgentIdDriveFilesPreviewResponse)
|
||||
|
||||
export const preview = {
|
||||
get: get5,
|
||||
get: get7,
|
||||
}
|
||||
|
||||
/**
|
||||
* List agent drive entries for an Agent App
|
||||
*/
|
||||
export const get6 = oc
|
||||
export const get8 = oc
|
||||
.route({
|
||||
description: 'List agent drive entries for an Agent App',
|
||||
inputStructure: 'detailed',
|
||||
@ -218,7 +302,7 @@ export const get6 = oc
|
||||
.output(zGetAgentByAgentIdDriveFilesResponse)
|
||||
|
||||
export const files = {
|
||||
get: get6,
|
||||
get: get8,
|
||||
download,
|
||||
preview,
|
||||
}
|
||||
@ -230,7 +314,7 @@ export const drive = {
|
||||
/**
|
||||
* Update an Agent App's presentation features (opener, follow-up, citations, ...)
|
||||
*/
|
||||
export const post2 = oc
|
||||
export const post3 = oc
|
||||
.route({
|
||||
description: 'Update an Agent App\'s presentation features (opener, follow-up, citations, ...)',
|
||||
inputStructure: 'detailed',
|
||||
@ -245,7 +329,28 @@ export const post2 = oc
|
||||
.output(zPostAgentByAgentIdFeaturesResponse)
|
||||
|
||||
export const features = {
|
||||
post: post2,
|
||||
post: post3,
|
||||
}
|
||||
|
||||
/**
|
||||
* Create or update Agent App message feedback
|
||||
*/
|
||||
export const post4 = oc
|
||||
.route({
|
||||
description: 'Create or update Agent App message feedback',
|
||||
inputStructure: 'detailed',
|
||||
method: 'POST',
|
||||
operationId: 'postAgentByAgentIdFeedbacks',
|
||||
path: '/agent/{agent_id}/feedbacks',
|
||||
tags: ['console'],
|
||||
})
|
||||
.input(
|
||||
z.object({ body: zPostAgentByAgentIdFeedbacksBody, params: zPostAgentByAgentIdFeedbacksPath }),
|
||||
)
|
||||
.output(zPostAgentByAgentIdFeedbacksResponse)
|
||||
|
||||
export const feedbacks = {
|
||||
post: post4,
|
||||
}
|
||||
|
||||
/**
|
||||
@ -268,7 +373,7 @@ export const delete_ = oc
|
||||
/**
|
||||
* Commit an uploaded file into the Agent App drive under files/<name>
|
||||
*/
|
||||
export const post3 = oc
|
||||
export const post5 = oc
|
||||
.route({
|
||||
description: 'Commit an uploaded file into the Agent App drive under files/<name>',
|
||||
inputStructure: 'detailed',
|
||||
@ -283,13 +388,36 @@ export const post3 = oc
|
||||
|
||||
export const files2 = {
|
||||
delete: delete_,
|
||||
post: post3,
|
||||
post: post5,
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Agent App message details by ID
|
||||
*/
|
||||
export const get9 = oc
|
||||
.route({
|
||||
description: 'Get Agent App message details by ID',
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
operationId: 'getAgentByAgentIdMessagesByMessageId',
|
||||
path: '/agent/{agent_id}/messages/{message_id}',
|
||||
tags: ['console'],
|
||||
})
|
||||
.input(z.object({ params: zGetAgentByAgentIdMessagesByMessageIdPath }))
|
||||
.output(zGetAgentByAgentIdMessagesByMessageIdResponse)
|
||||
|
||||
export const byMessageId2 = {
|
||||
get: get9,
|
||||
}
|
||||
|
||||
export const messages = {
|
||||
byMessageId: byMessageId2,
|
||||
}
|
||||
|
||||
/**
|
||||
* List workflow apps that reference this Agent App's bound Agent (read-only)
|
||||
*/
|
||||
export const get7 = oc
|
||||
export const get10 = oc
|
||||
.route({
|
||||
description: 'List workflow apps that reference this Agent App\'s bound Agent (read-only)',
|
||||
inputStructure: 'detailed',
|
||||
@ -302,13 +430,13 @@ export const get7 = oc
|
||||
.output(zGetAgentByAgentIdReferencingWorkflowsResponse)
|
||||
|
||||
export const referencingWorkflows = {
|
||||
get: get7,
|
||||
get: get10,
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a text/binary preview file in an Agent App conversation sandbox
|
||||
*/
|
||||
export const get8 = oc
|
||||
export const get11 = oc
|
||||
.route({
|
||||
description: 'Read a text/binary preview file in an Agent App conversation sandbox',
|
||||
inputStructure: 'detailed',
|
||||
@ -326,13 +454,13 @@ export const get8 = oc
|
||||
.output(zGetAgentByAgentIdSandboxFilesReadResponse)
|
||||
|
||||
export const read = {
|
||||
get: get8,
|
||||
get: get11,
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload one Agent App sandbox file as a Dify ToolFile mapping
|
||||
*/
|
||||
export const post4 = oc
|
||||
export const post6 = oc
|
||||
.route({
|
||||
description: 'Upload one Agent App sandbox file as a Dify ToolFile mapping',
|
||||
inputStructure: 'detailed',
|
||||
@ -350,13 +478,13 @@ export const post4 = oc
|
||||
.output(zPostAgentByAgentIdSandboxFilesUploadResponse)
|
||||
|
||||
export const upload = {
|
||||
post: post4,
|
||||
post: post6,
|
||||
}
|
||||
|
||||
/**
|
||||
* List a directory in an Agent App conversation sandbox
|
||||
*/
|
||||
export const get9 = oc
|
||||
export const get12 = oc
|
||||
.route({
|
||||
description: 'List a directory in an Agent App conversation sandbox',
|
||||
inputStructure: 'detailed',
|
||||
@ -374,7 +502,7 @@ export const get9 = oc
|
||||
.output(zGetAgentByAgentIdSandboxFilesResponse)
|
||||
|
||||
export const files3 = {
|
||||
get: get9,
|
||||
get: get12,
|
||||
read,
|
||||
upload,
|
||||
}
|
||||
@ -386,7 +514,7 @@ export const sandbox = {
|
||||
/**
|
||||
* Validate + standardize a Skill into an Agent App drive
|
||||
*/
|
||||
export const post5 = oc
|
||||
export const post7 = oc
|
||||
.route({
|
||||
description: 'Validate + standardize a Skill into an Agent App drive',
|
||||
inputStructure: 'detailed',
|
||||
@ -400,13 +528,13 @@ export const post5 = oc
|
||||
.output(zPostAgentByAgentIdSkillsStandardizeResponse)
|
||||
|
||||
export const standardize = {
|
||||
post: post5,
|
||||
post: post7,
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload + validate a Skill package for an Agent App
|
||||
*/
|
||||
export const post6 = oc
|
||||
export const post8 = oc
|
||||
.route({
|
||||
description: 'Upload + validate a Skill package for an Agent App',
|
||||
inputStructure: 'detailed',
|
||||
@ -420,13 +548,13 @@ export const post6 = oc
|
||||
.output(zPostAgentByAgentIdSkillsUploadResponse)
|
||||
|
||||
export const upload2 = {
|
||||
post: post6,
|
||||
post: post8,
|
||||
}
|
||||
|
||||
/**
|
||||
* Infer CLI tool + ENV suggestions from a standardized Agent App skill
|
||||
*/
|
||||
export const post7 = oc
|
||||
export const post9 = oc
|
||||
.route({
|
||||
description: 'Infer CLI tool + ENV suggestions from a standardized Agent App skill',
|
||||
inputStructure: 'detailed',
|
||||
@ -439,7 +567,7 @@ export const post7 = oc
|
||||
.output(zPostAgentByAgentIdSkillsBySlugInferToolsResponse)
|
||||
|
||||
export const inferTools = {
|
||||
post: post7,
|
||||
post: post9,
|
||||
}
|
||||
|
||||
/**
|
||||
@ -468,7 +596,7 @@ export const skills = {
|
||||
bySlug,
|
||||
}
|
||||
|
||||
export const get10 = oc
|
||||
export const get13 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
@ -480,10 +608,10 @@ export const get10 = oc
|
||||
.output(zGetAgentByAgentIdVersionsByVersionIdResponse)
|
||||
|
||||
export const byVersionId = {
|
||||
get: get10,
|
||||
get: get13,
|
||||
}
|
||||
|
||||
export const get11 = oc
|
||||
export const get14 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
@ -495,7 +623,7 @@ export const get11 = oc
|
||||
.output(zGetAgentByAgentIdVersionsResponse)
|
||||
|
||||
export const versions = {
|
||||
get: get11,
|
||||
get: get14,
|
||||
byVersionId,
|
||||
}
|
||||
|
||||
@ -511,7 +639,7 @@ export const delete3 = oc
|
||||
.input(z.object({ params: zDeleteAgentByAgentIdPath }))
|
||||
.output(zDeleteAgentByAgentIdResponse)
|
||||
|
||||
export const get12 = oc
|
||||
export const get15 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
@ -535,19 +663,22 @@ export const put2 = oc
|
||||
|
||||
export const byAgentId = {
|
||||
delete: delete3,
|
||||
get: get12,
|
||||
get: get15,
|
||||
put: put2,
|
||||
chatMessages,
|
||||
composer,
|
||||
drive,
|
||||
features,
|
||||
feedbacks,
|
||||
files: files2,
|
||||
messages,
|
||||
referencingWorkflows,
|
||||
sandbox,
|
||||
skills,
|
||||
versions,
|
||||
}
|
||||
|
||||
export const get13 = oc
|
||||
export const get16 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'GET',
|
||||
@ -558,7 +689,7 @@ export const get13 = oc
|
||||
.input(z.object({ query: zGetAgentQuery.optional() }))
|
||||
.output(zGetAgentResponse)
|
||||
|
||||
export const post8 = oc
|
||||
export const post10 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'POST',
|
||||
@ -571,8 +702,8 @@ export const post8 = oc
|
||||
.output(zPostAgentResponse)
|
||||
|
||||
export const agent = {
|
||||
get: get13,
|
||||
post: post8,
|
||||
get: get16,
|
||||
post: post10,
|
||||
inviteOptions,
|
||||
byAgentId,
|
||||
}
|
||||
|
||||
@ -66,6 +66,20 @@ export type UpdateAppPayload = {
|
||||
use_icon_as_answer_icon?: boolean | null
|
||||
}
|
||||
|
||||
export type MessageInfiniteScrollPaginationResponse = {
|
||||
data: Array<MessageDetailResponse>
|
||||
has_more: boolean
|
||||
limit: number
|
||||
}
|
||||
|
||||
export type SuggestedQuestionsResponse = {
|
||||
data: Array<string>
|
||||
}
|
||||
|
||||
export type SimpleResultResponse = {
|
||||
result: string
|
||||
}
|
||||
|
||||
export type AgentAppComposerResponse = {
|
||||
active_config_snapshot: AgentConfigSnapshotSummaryResponse
|
||||
agent: AgentComposerAgentResponse
|
||||
@ -129,8 +143,10 @@ export type AgentAppFeaturesPayload = {
|
||||
text_to_speech?: AgentTextToSpeechFeatureConfig | null
|
||||
}
|
||||
|
||||
export type SimpleResultResponse = {
|
||||
result: string
|
||||
export type MessageFeedbackPayload = {
|
||||
content?: string | null
|
||||
message_id: string
|
||||
rating?: 'dislike' | 'like' | null
|
||||
}
|
||||
|
||||
export type AgentDriveDeleteResponse = {
|
||||
@ -148,6 +164,35 @@ export type AgentDriveFileCommitResponse = {
|
||||
file: AgentDriveFileResponse
|
||||
}
|
||||
|
||||
export type MessageDetailResponse = {
|
||||
agent_thoughts?: Array<AgentThought>
|
||||
annotation?: ConversationAnnotation | null
|
||||
annotation_hit_history?: ConversationAnnotationHitHistory | null
|
||||
answer_tokens?: number | null
|
||||
conversation_id: string
|
||||
created_at?: number | null
|
||||
error?: string | null
|
||||
extra_contents?: Array<HumanInputContent>
|
||||
feedbacks?: Array<Feedback>
|
||||
from_account_id?: string | null
|
||||
from_end_user_id?: string | null
|
||||
from_source: string
|
||||
id: string
|
||||
inputs: {
|
||||
[key: string]: JsonValue
|
||||
}
|
||||
message?: JsonValue | null
|
||||
message_files?: Array<MessageFile>
|
||||
message_metadata_dict?: JsonValue | null
|
||||
message_tokens?: number | null
|
||||
parent_message_id?: string | null
|
||||
provider_response_latency?: number | null
|
||||
query: string
|
||||
re_sign_file_url_answer: string
|
||||
status: string
|
||||
workflow_run_id?: string | null
|
||||
}
|
||||
|
||||
export type AgentReferencingWorkflowsResponse = {
|
||||
data?: Array<AgentReferencingWorkflowResponse>
|
||||
}
|
||||
@ -476,6 +521,63 @@ export type AgentDriveFileResponse = {
|
||||
size?: number | null
|
||||
}
|
||||
|
||||
export type AgentThought = {
|
||||
chain_id?: string | null
|
||||
created_at?: number | null
|
||||
files: Array<string>
|
||||
id: string
|
||||
message_chain_id?: string | null
|
||||
message_id: string
|
||||
observation?: string | null
|
||||
position: number
|
||||
thought?: string | null
|
||||
tool?: string | null
|
||||
tool_input?: string | null
|
||||
tool_labels: JsonValue
|
||||
}
|
||||
|
||||
export type ConversationAnnotation = {
|
||||
account?: SimpleAccount | null
|
||||
content: string
|
||||
created_at?: number | null
|
||||
id: string
|
||||
question?: string | null
|
||||
}
|
||||
|
||||
export type ConversationAnnotationHitHistory = {
|
||||
annotation_create_account?: SimpleAccount | null
|
||||
created_at?: number | null
|
||||
id: string
|
||||
}
|
||||
|
||||
export type HumanInputContent = {
|
||||
form_definition?: HumanInputFormDefinition | null
|
||||
form_submission_data?: HumanInputFormSubmissionData | null
|
||||
submitted: boolean
|
||||
type?: ExecutionContentType
|
||||
workflow_run_id: string
|
||||
}
|
||||
|
||||
export type Feedback = {
|
||||
content?: string | null
|
||||
from_account?: SimpleAccount | null
|
||||
from_end_user_id?: string | null
|
||||
from_source: string
|
||||
rating: string
|
||||
}
|
||||
|
||||
export type MessageFile = {
|
||||
belongs_to?: string | null
|
||||
filename: string
|
||||
id: string
|
||||
mime_type?: string | null
|
||||
size?: number | null
|
||||
transfer_method: string
|
||||
type: string
|
||||
upload_file_id?: string | null
|
||||
url?: string | null
|
||||
}
|
||||
|
||||
export type AgentReferencingWorkflowResponse = {
|
||||
app_id: string
|
||||
app_mode: string
|
||||
@ -773,6 +875,40 @@ export type AgentModerationProviderConfig = {
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export type SimpleAccount = {
|
||||
email: string
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
|
||||
export type HumanInputFormDefinition = {
|
||||
actions?: Array<UserActionConfig>
|
||||
display_in_ui?: boolean
|
||||
expiration_time: number
|
||||
form_content: string
|
||||
form_id: string
|
||||
form_token?: string | null
|
||||
inputs?: Array<FormInputConfig>
|
||||
node_id: string
|
||||
node_title: string
|
||||
resolved_default_values?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
}
|
||||
|
||||
export type HumanInputFormSubmissionData = {
|
||||
action_id: string
|
||||
action_text: string
|
||||
node_id: string
|
||||
node_title: string
|
||||
rendered_content: string
|
||||
submitted_data?: {
|
||||
[key: string]: JsonValue2
|
||||
} | null
|
||||
}
|
||||
|
||||
export type ExecutionContentType = 'human_input'
|
||||
|
||||
export type EnvSuggestion = {
|
||||
key: string
|
||||
reason?: string
|
||||
@ -973,6 +1109,28 @@ export type AgentModerationIoConfig = {
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export type UserActionConfig = {
|
||||
button_style?: ButtonStyle
|
||||
id: string
|
||||
title: string
|
||||
}
|
||||
|
||||
export type FormInputConfig
|
||||
= | ({
|
||||
type: 'paragraph'
|
||||
} & ParagraphInputConfig)
|
||||
| ({
|
||||
type: 'select'
|
||||
} & SelectInputConfig)
|
||||
| ({
|
||||
type: 'file'
|
||||
} & FileInputConfig)
|
||||
| ({
|
||||
type: 'file-list'
|
||||
} & FileListInputConfig)
|
||||
|
||||
export type JsonValue2 = unknown
|
||||
|
||||
export type AgentModelResponseFormatConfig = {
|
||||
type?: string | null
|
||||
[key: string]: unknown
|
||||
@ -992,6 +1150,55 @@ export type DeclaredOutputRetryConfig = {
|
||||
retry_interval_ms?: number
|
||||
}
|
||||
|
||||
export type ButtonStyle = 'accent' | 'default' | 'ghost' | 'primary'
|
||||
|
||||
export type ParagraphInputConfig = {
|
||||
default?: StringSource | null
|
||||
output_variable_name: string
|
||||
type?: 'paragraph'
|
||||
}
|
||||
|
||||
export type SelectInputConfig = {
|
||||
option_source: StringListSource
|
||||
output_variable_name: string
|
||||
type?: 'select'
|
||||
}
|
||||
|
||||
export type FileInputConfig = {
|
||||
allowed_file_extensions?: Array<string>
|
||||
allowed_file_types?: Array<FileType>
|
||||
allowed_file_upload_methods?: Array<FileTransferMethod>
|
||||
output_variable_name: string
|
||||
type?: 'file'
|
||||
}
|
||||
|
||||
export type FileListInputConfig = {
|
||||
allowed_file_extensions?: Array<string>
|
||||
allowed_file_types?: Array<FileType>
|
||||
allowed_file_upload_methods?: Array<FileTransferMethod>
|
||||
number_limits?: number
|
||||
output_variable_name: string
|
||||
type?: 'file-list'
|
||||
}
|
||||
|
||||
export type StringSource = {
|
||||
selector?: Array<string>
|
||||
type: ValueSourceType
|
||||
value?: string
|
||||
}
|
||||
|
||||
export type StringListSource = {
|
||||
selector?: Array<string>
|
||||
type: ValueSourceType
|
||||
value?: Array<string>
|
||||
}
|
||||
|
||||
export type FileType = 'audio' | 'custom' | 'document' | 'image' | 'video'
|
||||
|
||||
export type FileTransferMethod = 'datasource_file' | 'local_file' | 'remote_url' | 'tool_file'
|
||||
|
||||
export type ValueSourceType = 'constant' | 'variable'
|
||||
|
||||
export type AppPaginationWritable = {
|
||||
data: Array<AppPartialWritable>
|
||||
has_more: boolean
|
||||
@ -1190,6 +1397,68 @@ export type PutAgentByAgentIdResponses = {
|
||||
|
||||
export type PutAgentByAgentIdResponse = PutAgentByAgentIdResponses[keyof PutAgentByAgentIdResponses]
|
||||
|
||||
export type GetAgentByAgentIdChatMessagesData = {
|
||||
body?: never
|
||||
path: {
|
||||
agent_id: string
|
||||
}
|
||||
query: {
|
||||
conversation_id: string
|
||||
first_id?: string
|
||||
limit?: number
|
||||
}
|
||||
url: '/agent/{agent_id}/chat-messages'
|
||||
}
|
||||
|
||||
export type GetAgentByAgentIdChatMessagesErrors = {
|
||||
404: unknown
|
||||
}
|
||||
|
||||
export type GetAgentByAgentIdChatMessagesResponses = {
|
||||
200: MessageInfiniteScrollPaginationResponse
|
||||
}
|
||||
|
||||
export type GetAgentByAgentIdChatMessagesResponse
|
||||
= GetAgentByAgentIdChatMessagesResponses[keyof GetAgentByAgentIdChatMessagesResponses]
|
||||
|
||||
export type GetAgentByAgentIdChatMessagesByMessageIdSuggestedQuestionsData = {
|
||||
body?: never
|
||||
path: {
|
||||
agent_id: string
|
||||
message_id: string
|
||||
}
|
||||
query?: never
|
||||
url: '/agent/{agent_id}/chat-messages/{message_id}/suggested-questions'
|
||||
}
|
||||
|
||||
export type GetAgentByAgentIdChatMessagesByMessageIdSuggestedQuestionsErrors = {
|
||||
404: unknown
|
||||
}
|
||||
|
||||
export type GetAgentByAgentIdChatMessagesByMessageIdSuggestedQuestionsResponses = {
|
||||
200: SuggestedQuestionsResponse
|
||||
}
|
||||
|
||||
export type GetAgentByAgentIdChatMessagesByMessageIdSuggestedQuestionsResponse
|
||||
= GetAgentByAgentIdChatMessagesByMessageIdSuggestedQuestionsResponses[keyof GetAgentByAgentIdChatMessagesByMessageIdSuggestedQuestionsResponses]
|
||||
|
||||
export type PostAgentByAgentIdChatMessagesByTaskIdStopData = {
|
||||
body?: never
|
||||
path: {
|
||||
agent_id: string
|
||||
task_id: string
|
||||
}
|
||||
query?: never
|
||||
url: '/agent/{agent_id}/chat-messages/{task_id}/stop'
|
||||
}
|
||||
|
||||
export type PostAgentByAgentIdChatMessagesByTaskIdStopResponses = {
|
||||
200: SimpleResultResponse
|
||||
}
|
||||
|
||||
export type PostAgentByAgentIdChatMessagesByTaskIdStopResponse
|
||||
= PostAgentByAgentIdChatMessagesByTaskIdStopResponses[keyof PostAgentByAgentIdChatMessagesByTaskIdStopResponses]
|
||||
|
||||
export type GetAgentByAgentIdComposerData = {
|
||||
body?: never
|
||||
path: {
|
||||
@ -1329,6 +1598,26 @@ export type PostAgentByAgentIdFeaturesResponses = {
|
||||
export type PostAgentByAgentIdFeaturesResponse
|
||||
= PostAgentByAgentIdFeaturesResponses[keyof PostAgentByAgentIdFeaturesResponses]
|
||||
|
||||
export type PostAgentByAgentIdFeedbacksData = {
|
||||
body: MessageFeedbackPayload
|
||||
path: {
|
||||
agent_id: string
|
||||
}
|
||||
query?: never
|
||||
url: '/agent/{agent_id}/feedbacks'
|
||||
}
|
||||
|
||||
export type PostAgentByAgentIdFeedbacksErrors = {
|
||||
404: unknown
|
||||
}
|
||||
|
||||
export type PostAgentByAgentIdFeedbacksResponses = {
|
||||
200: SimpleResultResponse
|
||||
}
|
||||
|
||||
export type PostAgentByAgentIdFeedbacksResponse
|
||||
= PostAgentByAgentIdFeedbacksResponses[keyof PostAgentByAgentIdFeedbacksResponses]
|
||||
|
||||
export type DeleteAgentByAgentIdFilesData = {
|
||||
body?: never
|
||||
path: {
|
||||
@ -1363,6 +1652,27 @@ export type PostAgentByAgentIdFilesResponses = {
|
||||
export type PostAgentByAgentIdFilesResponse
|
||||
= PostAgentByAgentIdFilesResponses[keyof PostAgentByAgentIdFilesResponses]
|
||||
|
||||
export type GetAgentByAgentIdMessagesByMessageIdData = {
|
||||
body?: never
|
||||
path: {
|
||||
agent_id: string
|
||||
message_id: string
|
||||
}
|
||||
query?: never
|
||||
url: '/agent/{agent_id}/messages/{message_id}'
|
||||
}
|
||||
|
||||
export type GetAgentByAgentIdMessagesByMessageIdErrors = {
|
||||
404: unknown
|
||||
}
|
||||
|
||||
export type GetAgentByAgentIdMessagesByMessageIdResponses = {
|
||||
200: MessageDetailResponse
|
||||
}
|
||||
|
||||
export type GetAgentByAgentIdMessagesByMessageIdResponse
|
||||
= GetAgentByAgentIdMessagesByMessageIdResponses[keyof GetAgentByAgentIdMessagesByMessageIdResponses]
|
||||
|
||||
export type GetAgentByAgentIdReferencingWorkflowsData = {
|
||||
body?: never
|
||||
path: {
|
||||
|
||||
@ -2,6 +2,20 @@
|
||||
|
||||
import * as z from 'zod'
|
||||
|
||||
/**
|
||||
* SuggestedQuestionsResponse
|
||||
*/
|
||||
export const zSuggestedQuestionsResponse = z.object({
|
||||
data: z.array(z.string()),
|
||||
})
|
||||
|
||||
/**
|
||||
* SimpleResultResponse
|
||||
*/
|
||||
export const zSimpleResultResponse = z.object({
|
||||
result: z.string(),
|
||||
})
|
||||
|
||||
/**
|
||||
* AgentDriveDownloadResponse
|
||||
*/
|
||||
@ -21,10 +35,12 @@ export const zAgentDrivePreviewResponse = z.object({
|
||||
})
|
||||
|
||||
/**
|
||||
* SimpleResultResponse
|
||||
* MessageFeedbackPayload
|
||||
*/
|
||||
export const zSimpleResultResponse = z.object({
|
||||
result: z.string(),
|
||||
export const zMessageFeedbackPayload = z.object({
|
||||
content: z.string().nullish(),
|
||||
message_id: z.string(),
|
||||
rating: z.enum(['dislike', 'like']).nullish(),
|
||||
})
|
||||
|
||||
/**
|
||||
@ -303,6 +319,39 @@ export const zAgentDriveFileCommitResponse = z.object({
|
||||
file: zAgentDriveFileResponse,
|
||||
})
|
||||
|
||||
/**
|
||||
* AgentThought
|
||||
*/
|
||||
export const zAgentThought = z.object({
|
||||
chain_id: z.string().nullish(),
|
||||
created_at: z.int().nullish(),
|
||||
files: z.array(z.string()),
|
||||
id: z.string(),
|
||||
message_chain_id: z.string().nullish(),
|
||||
message_id: z.string(),
|
||||
observation: z.string().nullish(),
|
||||
position: z.int(),
|
||||
thought: z.string().nullish(),
|
||||
tool: z.string().nullish(),
|
||||
tool_input: z.string().nullish(),
|
||||
tool_labels: zJsonValue,
|
||||
})
|
||||
|
||||
/**
|
||||
* MessageFile
|
||||
*/
|
||||
export const zMessageFile = z.object({
|
||||
belongs_to: z.string().nullish(),
|
||||
filename: z.string(),
|
||||
id: z.string(),
|
||||
mime_type: z.string().nullish(),
|
||||
size: z.int().nullish(),
|
||||
transfer_method: z.string(),
|
||||
type: z.string(),
|
||||
upload_file_id: z.string().nullish(),
|
||||
url: z.string().nullish(),
|
||||
})
|
||||
|
||||
/**
|
||||
* AgentReferencingWorkflowResponse
|
||||
*/
|
||||
@ -743,6 +792,51 @@ export const zAgentComposerFileCandidateResponse = z.object({
|
||||
url: z.string().nullish(),
|
||||
})
|
||||
|
||||
/**
|
||||
* SimpleAccount
|
||||
*/
|
||||
export const zSimpleAccount = z.object({
|
||||
email: z.string(),
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
})
|
||||
|
||||
/**
|
||||
* ConversationAnnotation
|
||||
*/
|
||||
export const zConversationAnnotation = z.object({
|
||||
account: zSimpleAccount.nullish(),
|
||||
content: z.string(),
|
||||
created_at: z.int().nullish(),
|
||||
id: z.string(),
|
||||
question: z.string().nullish(),
|
||||
})
|
||||
|
||||
/**
|
||||
* ConversationAnnotationHitHistory
|
||||
*/
|
||||
export const zConversationAnnotationHitHistory = z.object({
|
||||
annotation_create_account: zSimpleAccount.nullish(),
|
||||
created_at: z.int().nullish(),
|
||||
id: z.string(),
|
||||
})
|
||||
|
||||
/**
|
||||
* Feedback
|
||||
*/
|
||||
export const zFeedback = z.object({
|
||||
content: z.string().nullish(),
|
||||
from_account: zSimpleAccount.nullish(),
|
||||
from_end_user_id: z.string().nullish(),
|
||||
from_source: z.string(),
|
||||
rating: z.string(),
|
||||
})
|
||||
|
||||
/**
|
||||
* ExecutionContentType
|
||||
*/
|
||||
export const zExecutionContentType = z.enum(['human_input'])
|
||||
|
||||
/**
|
||||
* EnvSuggestion
|
||||
*/
|
||||
@ -1148,6 +1242,20 @@ export const zAgentSensitiveWordAvoidanceFeatureConfig = z.object({
|
||||
type: z.string().nullish(),
|
||||
})
|
||||
|
||||
export const zJsonValue2 = z.unknown()
|
||||
|
||||
/**
|
||||
* HumanInputFormSubmissionData
|
||||
*/
|
||||
export const zHumanInputFormSubmissionData = z.object({
|
||||
action_id: z.string(),
|
||||
action_text: z.string(),
|
||||
node_id: z.string(),
|
||||
node_title: z.string(),
|
||||
rendered_content: z.string(),
|
||||
submitted_data: z.record(z.string(), zJsonValue2).nullish(),
|
||||
})
|
||||
|
||||
/**
|
||||
* AgentModelResponseFormatConfig
|
||||
*/
|
||||
@ -1430,6 +1538,183 @@ export const zComposerSavePayload = z.object({
|
||||
version_note: z.string().nullish(),
|
||||
})
|
||||
|
||||
/**
|
||||
* ButtonStyle
|
||||
*
|
||||
* Button styles for user actions.
|
||||
*/
|
||||
export const zButtonStyle = z.enum(['accent', 'default', 'ghost', 'primary'])
|
||||
|
||||
/**
|
||||
* UserActionConfig
|
||||
*
|
||||
* User action configuration.
|
||||
*/
|
||||
export const zUserActionConfig = z.object({
|
||||
button_style: zButtonStyle.optional().default('default'),
|
||||
id: z.string().max(20),
|
||||
title: z.string().max(100),
|
||||
})
|
||||
|
||||
/**
|
||||
* FileType
|
||||
*/
|
||||
export const zFileType = z.enum(['audio', 'custom', 'document', 'image', 'video'])
|
||||
|
||||
/**
|
||||
* FileTransferMethod
|
||||
*/
|
||||
export const zFileTransferMethod = z.enum([
|
||||
'datasource_file',
|
||||
'local_file',
|
||||
'remote_url',
|
||||
'tool_file',
|
||||
])
|
||||
|
||||
/**
|
||||
* FileInputConfig
|
||||
*/
|
||||
export const zFileInputConfig = z.object({
|
||||
allowed_file_extensions: z.array(z.string()).optional(),
|
||||
allowed_file_types: z.array(zFileType).optional(),
|
||||
allowed_file_upload_methods: z.array(zFileTransferMethod).optional(),
|
||||
output_variable_name: z.string(),
|
||||
type: z.literal('file').optional().default('file'),
|
||||
})
|
||||
|
||||
/**
|
||||
* FileListInputConfig
|
||||
*/
|
||||
export const zFileListInputConfig = z.object({
|
||||
allowed_file_extensions: z.array(z.string()).optional(),
|
||||
allowed_file_types: z.array(zFileType).optional(),
|
||||
allowed_file_upload_methods: z.array(zFileTransferMethod).optional(),
|
||||
number_limits: z.int().gte(0).optional().default(0),
|
||||
output_variable_name: z.string(),
|
||||
type: z.literal('file-list').optional().default('file-list'),
|
||||
})
|
||||
|
||||
/**
|
||||
* ValueSourceType
|
||||
*
|
||||
* ValueSourceType records whether the value comes from a static setting
|
||||
* in form definiton, or a variable while the workflow is running.
|
||||
*/
|
||||
export const zValueSourceType = z.enum(['constant', 'variable'])
|
||||
|
||||
/**
|
||||
* StringSource
|
||||
*
|
||||
* Default configuration for form inputs.
|
||||
*/
|
||||
export const zStringSource = z.object({
|
||||
selector: z.array(z.string()).optional(),
|
||||
type: zValueSourceType,
|
||||
value: z.string().optional().default(''),
|
||||
})
|
||||
|
||||
/**
|
||||
* ParagraphInputConfig
|
||||
*
|
||||
* Form input definition.
|
||||
*/
|
||||
export const zParagraphInputConfig = z.object({
|
||||
default: zStringSource.nullish(),
|
||||
output_variable_name: z.string(),
|
||||
type: z.literal('paragraph').optional().default('paragraph'),
|
||||
})
|
||||
|
||||
/**
|
||||
* StringListSource
|
||||
*/
|
||||
export const zStringListSource = z.object({
|
||||
selector: z.array(z.string()).optional(),
|
||||
type: zValueSourceType,
|
||||
value: z.array(z.string()).optional(),
|
||||
})
|
||||
|
||||
/**
|
||||
* SelectInputConfig
|
||||
*/
|
||||
export const zSelectInputConfig = z.object({
|
||||
option_source: zStringListSource,
|
||||
output_variable_name: z.string(),
|
||||
type: z.literal('select').optional().default('select'),
|
||||
})
|
||||
|
||||
export const zFormInputConfig = z.discriminatedUnion('type', [
|
||||
zParagraphInputConfig.extend({ type: z.literal('paragraph') }),
|
||||
zSelectInputConfig.extend({ type: z.literal('select') }),
|
||||
zFileInputConfig.extend({ type: z.literal('file') }),
|
||||
zFileListInputConfig.extend({ type: z.literal('file-list') }),
|
||||
])
|
||||
|
||||
/**
|
||||
* HumanInputFormDefinition
|
||||
*/
|
||||
export const zHumanInputFormDefinition = z.object({
|
||||
actions: z.array(zUserActionConfig).optional(),
|
||||
display_in_ui: z.boolean().optional().default(false),
|
||||
expiration_time: z.int(),
|
||||
form_content: z.string(),
|
||||
form_id: z.string(),
|
||||
form_token: z.string().nullish(),
|
||||
inputs: z.array(zFormInputConfig).optional(),
|
||||
node_id: z.string(),
|
||||
node_title: z.string(),
|
||||
resolved_default_values: z.record(z.string(), z.unknown()).optional(),
|
||||
})
|
||||
|
||||
/**
|
||||
* HumanInputContent
|
||||
*/
|
||||
export const zHumanInputContent = z.object({
|
||||
form_definition: zHumanInputFormDefinition.nullish(),
|
||||
form_submission_data: zHumanInputFormSubmissionData.nullish(),
|
||||
submitted: z.boolean(),
|
||||
type: zExecutionContentType.optional().default('human_input'),
|
||||
workflow_run_id: z.string(),
|
||||
})
|
||||
|
||||
/**
|
||||
* MessageDetailResponse
|
||||
*/
|
||||
export const zMessageDetailResponse = z.object({
|
||||
agent_thoughts: z.array(zAgentThought).optional(),
|
||||
annotation: zConversationAnnotation.nullish(),
|
||||
annotation_hit_history: zConversationAnnotationHitHistory.nullish(),
|
||||
answer_tokens: z.int().nullish(),
|
||||
conversation_id: z.string(),
|
||||
created_at: z.int().nullish(),
|
||||
error: z.string().nullish(),
|
||||
extra_contents: z.array(zHumanInputContent).optional(),
|
||||
feedbacks: z.array(zFeedback).optional(),
|
||||
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_metadata_dict: zJsonValue.nullish(),
|
||||
message_tokens: z.int().nullish(),
|
||||
parent_message_id: z.string().nullish(),
|
||||
provider_response_latency: z.number().nullish(),
|
||||
query: z.string(),
|
||||
re_sign_file_url_answer: z.string(),
|
||||
status: z.string(),
|
||||
workflow_run_id: z.string().nullish(),
|
||||
})
|
||||
|
||||
/**
|
||||
* MessageInfiniteScrollPaginationResponse
|
||||
*/
|
||||
export const zMessageInfiniteScrollPaginationResponse = z.object({
|
||||
data: z.array(zMessageDetailResponse),
|
||||
has_more: z.boolean(),
|
||||
limit: z.int(),
|
||||
})
|
||||
|
||||
/**
|
||||
* AppPartial
|
||||
*/
|
||||
@ -1597,6 +1882,42 @@ export const zPutAgentByAgentIdPath = z.object({
|
||||
*/
|
||||
export const zPutAgentByAgentIdResponse = zAppDetailWithSite
|
||||
|
||||
export const zGetAgentByAgentIdChatMessagesPath = z.object({
|
||||
agent_id: z.string(),
|
||||
})
|
||||
|
||||
export const zGetAgentByAgentIdChatMessagesQuery = z.object({
|
||||
conversation_id: z.string(),
|
||||
first_id: z.string().optional(),
|
||||
limit: z.int().gte(1).lte(100).optional().default(20),
|
||||
})
|
||||
|
||||
/**
|
||||
* Success
|
||||
*/
|
||||
export const zGetAgentByAgentIdChatMessagesResponse = zMessageInfiniteScrollPaginationResponse
|
||||
|
||||
export const zGetAgentByAgentIdChatMessagesByMessageIdSuggestedQuestionsPath = z.object({
|
||||
agent_id: z.string(),
|
||||
message_id: z.string(),
|
||||
})
|
||||
|
||||
/**
|
||||
* Suggested questions retrieved successfully
|
||||
*/
|
||||
export const zGetAgentByAgentIdChatMessagesByMessageIdSuggestedQuestionsResponse
|
||||
= zSuggestedQuestionsResponse
|
||||
|
||||
export const zPostAgentByAgentIdChatMessagesByTaskIdStopPath = z.object({
|
||||
agent_id: z.string(),
|
||||
task_id: z.string(),
|
||||
})
|
||||
|
||||
/**
|
||||
* Task stopped successfully
|
||||
*/
|
||||
export const zPostAgentByAgentIdChatMessagesByTaskIdStopResponse = zSimpleResultResponse
|
||||
|
||||
export const zGetAgentByAgentIdComposerPath = z.object({
|
||||
agent_id: z.string(),
|
||||
})
|
||||
@ -1687,6 +2008,17 @@ export const zPostAgentByAgentIdFeaturesPath = z.object({
|
||||
*/
|
||||
export const zPostAgentByAgentIdFeaturesResponse = zSimpleResultResponse
|
||||
|
||||
export const zPostAgentByAgentIdFeedbacksBody = zMessageFeedbackPayload
|
||||
|
||||
export const zPostAgentByAgentIdFeedbacksPath = z.object({
|
||||
agent_id: z.string(),
|
||||
})
|
||||
|
||||
/**
|
||||
* Feedback updated successfully
|
||||
*/
|
||||
export const zPostAgentByAgentIdFeedbacksResponse = zSimpleResultResponse
|
||||
|
||||
export const zDeleteAgentByAgentIdFilesPath = z.object({
|
||||
agent_id: z.string(),
|
||||
})
|
||||
@ -1711,6 +2043,16 @@ export const zPostAgentByAgentIdFilesPath = z.object({
|
||||
*/
|
||||
export const zPostAgentByAgentIdFilesResponse = zAgentDriveFileCommitResponse
|
||||
|
||||
export const zGetAgentByAgentIdMessagesByMessageIdPath = z.object({
|
||||
agent_id: z.string(),
|
||||
message_id: z.string(),
|
||||
})
|
||||
|
||||
/**
|
||||
* Message retrieved successfully
|
||||
*/
|
||||
export const zGetAgentByAgentIdMessagesByMessageIdResponse = zMessageDetailResponse
|
||||
|
||||
export const zGetAgentByAgentIdReferencingWorkflowsPath = z.object({
|
||||
agent_id: z.string(),
|
||||
})
|
||||
|
||||
Loading…
Reference in New Issue
Block a user