diff --git a/apps/application/urls.py b/apps/application/urls.py index 05707a9f48f..e6776f161c3 100644 --- a/apps/application/urls.py +++ b/apps/application/urls.py @@ -42,7 +42,6 @@ path('workspace//application//speech_to_text', views.SpeechToText.as_view()), path('workspace//application//play_demo_text', views.PlayDemoText.as_view()), path('workspace//application//mcp_tools', views.McpServers.as_view()), - path('workspace//application//model//prompt_generate', views.PromptGenerateView.as_view()), path('workspace//application//chat//chat_message', views.ChatView.as_view()), path('workspace//application//chat//cancel_chat_message', views.CancelWorkflowView.as_view()), path('workspace//application//chat//chat_record//resume_chat_message', views.ResumeStreamView.as_view()), diff --git a/apps/application/views/application_chat.py b/apps/application/views/application_chat.py index 01dbcb464ec..9582cd6f1f9 100644 --- a/apps/application/views/application_chat.py +++ b/apps/application/views/application_chat.py @@ -20,16 +20,16 @@ ) from application.models import ChatUserType, Application, ChatSourceChoices from application.serializers.application_chat import ApplicationChatQuerySerializers -from chat.api.chat_api import ChatAPI, PromptGenerateAPI, PageHistoricalConversationAPI, HistoricalConversationRecordAPI +from chat.api.chat_api import ChatAPI, PageHistoricalConversationAPI, HistoricalConversationRecordAPI from chat.api.chat_authentication_api import ChatOpenAPI -from chat.serializers.chat import DebugChatSerializers, OpenChatSerializers, PromptGenerateSerializer, ResumeSerializers +from chat.serializers.chat import DebugChatSerializers, OpenChatSerializers, ResumeSerializers from common.auth import TokenAuth from common.auth.authentication import has_permissions from common.auth.constants.compare_constants import CompareConstants from common.auth.constants.permission_constants import PermissionConstants from common.auth.constants.role_constants import RoleConstants from common.auth.struct.aggregate_permission import ViewPermission -from common.log.log import log, _get_ip_address +from common.log.log import _get_ip_address from common.result import result from common.utils.common import query_params_to_single_dict @@ -275,40 +275,6 @@ def post(self, request: Request, workspace_id: str, application_id: str, chat_id return ResumeSerializers(data={"chat_id": chat_id, "chat_record_id": chat_record_id}).resume(request) -class PromptGenerateView(APIView): - authentication_classes = [TokenAuth] - - @extend_schema( - methods=["POST"], - description=_("generate prompt"), - summary=_("generate prompt"), - operation_id=_("generate prompt"), # type: ignore - request=PromptGenerateAPI.get_request(), - parameters=PromptGenerateAPI.get_parameters(), - responses=None, - tags=[_("Application")], # type: ignore - ) - @has_permissions( - PermissionConstants.APPLICATION_READ.get_workspace_application_permission(), - PermissionConstants.APPLICATION_READ.get_workspace_permission_workspace_manage_role(), - ViewPermission( - [RoleConstants.USER.get_workspace_role()], - [PermissionConstants.APPLICATION.get_workspace_application_permission()], - compare=CompareConstants.AND, - ), - RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), - ) - @log( - menu="Application", - operate="Generate prompt", - get_operation_object=lambda r, k: get_application_operation_object(k.get("application_id")), - ) - def post(self, request: Request, workspace_id: str, model_id: str, application_id: str): - return PromptGenerateSerializer( - data={"workspace_id": workspace_id, "model_id": model_id, "application_id": application_id} - ).generate_prompt(instance=request.data) - - class DebugHistoricalConversation(APIView): authentication_classes = [TokenAuth] diff --git a/apps/chat/api/chat_api.py b/apps/chat/api/chat_api.py index c3cb5c02873..1bf82c5032a 100644 --- a/apps/chat/api/chat_api.py +++ b/apps/chat/api/chat_api.py @@ -1,63 +1,35 @@ # coding=utf-8 """ - @project: MaxKB - @Author:虎虎 - @file: chat_api.py - @date:2025/6/9 15:23 - @desc: +@project: MaxKB +@Author:虎虎 +@file: chat_api.py +@date:2025/6/9 15:23 +@desc: """ + from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiParameter from application.serializers.application_chat_record import ChatRecordSerializerModel -from chat.serializers.chat import ChatMessageSerializers, GeneratePromptSerializers +from chat.serializers.chat import ChatMessageSerializers from chat.serializers.chat_record import HistoryChatModel, EditAbstractSerializer from common.mixins.api_mixin import APIMixin from common.result import ResultSerializer, ResultPageSerializer, DefaultResultSerializer -class PromptGenerateAPI(APIMixin): +class ChatAPI(APIMixin): @staticmethod def get_parameters(): - return [OpenApiParameter( - name="workspace_id", - description="工作空间id", - type=OpenApiTypes.STR, - location='path', - required=True, - ), - OpenApiParameter( - name="model_id", - description="模型id", - type=OpenApiTypes.STR, - location='path', - required=True, - ), + return [ OpenApiParameter( - name="application_id", - description="应用id", + name="chat_id", + description="对话id", type=OpenApiTypes.STR, - location='path', + location="path", required=True, - ), + ) ] - @staticmethod - def get_request(): - return GeneratePromptSerializers - - -class ChatAPI(APIMixin): - @staticmethod - def get_parameters(): - return [OpenApiParameter( - name="chat_id", - description="对话id", - type=OpenApiTypes.STR, - location='path', - required=True, - )] - @staticmethod def get_request(): return ChatMessageSerializers @@ -106,13 +78,11 @@ def get_response(): class HistoricalConversationOperateAPI(APIMixin): @staticmethod def get_parameters(): - return [OpenApiParameter( - name="chat_id", - description="对话id", - type=OpenApiTypes.STR, - location='path', - required=True - )] + return [ + OpenApiParameter( + name="chat_id", description="对话id", type=OpenApiTypes.STR, location="path", required=True + ) + ] @staticmethod def get_request(): @@ -126,13 +96,15 @@ def get_response(): class HistoricalConversationRecordAPI(APIMixin): @staticmethod def get_parameters(): - return [OpenApiParameter( - name="chat_id", - description="对话id", - type=OpenApiTypes.STR, - location='path', - required=True, - )] + return [ + OpenApiParameter( + name="chat_id", + description="对话id", + type=OpenApiTypes.STR, + location="path", + required=True, + ) + ] @staticmethod def get_response(): @@ -142,13 +114,15 @@ def get_response(): class PageHistoricalConversationRecordAPI(APIMixin): @staticmethod def get_parameters(): - return [OpenApiParameter( - name="chat_id", - description="对话id", - type=OpenApiTypes.STR, - location='path', - required=True, - )] + return [ + OpenApiParameter( + name="chat_id", + description="对话id", + type=OpenApiTypes.STR, + location="path", + required=True, + ) + ] @staticmethod def get_response(): diff --git a/apps/chat/serializers/chat.py b/apps/chat/serializers/chat.py index d3b08ce854d..4a3e851e477 100644 --- a/apps/chat/serializers/chat.py +++ b/apps/chat/serializers/chat.py @@ -8,7 +8,6 @@ """ import json -import os import queue import queue as thread_queue import threading @@ -20,7 +19,6 @@ from django.http import StreamingHttpResponse from django.utils import timezone from django.utils.translation import gettext_lazy as _ -from langchain_core.messages import HumanMessage, AIMessage, SystemMessage from rest_framework import serializers from rest_framework.request import Request @@ -53,11 +51,8 @@ from common.handle.base_to_response import BaseToResponse from common.handle.impl.response.openai_to_response import OpenaiToResponse from common.handle.impl.response.system_to_response import SystemToResponse -from common.utils.common import get_file_content from common.utils.logger import maxkb_logger -from maxkb.conf import PROJECT_DIR -from models_provider.models import Model, Status -from models_provider.tools import get_model_instance_by_model_workspace_id +from models_provider.models import Status from system_manage.models.chat_user_token_quota import ChatUserTokenQuota _CHAT_UNSET = object() @@ -676,93 +671,6 @@ def _stream_from_db(self, chat_record, start_id: str): yield "data: [DONE]\n\n" -# ==================== 提示词生成 ==================== - -SYSTEM_ROLE = get_file_content(os.path.join(PROJECT_DIR, "apps", "chat", "template", "generate_prompt_system")) - - -class ChatMessagesSerializers(serializers.Serializer): - role = serializers.CharField(required=True, label=_("Role")) - content = serializers.CharField(required=True, label=_("Content")) - - -class GeneratePromptSerializers(serializers.Serializer): - prompt = serializers.CharField(required=True, label=_("Prompt template")) - messages = serializers.ListSerializer(child=ChatMessagesSerializers(), required=True, label=_("Chat context")) - - def is_valid(self, *, raise_exception=False): - super().is_valid(raise_exception=True) - messages = self.data.get("messages") - - if len(messages) > 30: - raise AppApiException(400, _("Too many messages")) - - for index in range(len(messages)): - role = messages[index].get("role") - if role == "ai" and index % 2 != 1: - raise AppApiException(400, _("Authentication failed. Please verify that the parameters are correct.")) - if role == "user" and index % 2 != 0: - raise AppApiException(400, _("Authentication failed. Please verify that the parameters are correct.")) - if role not in ["user", "ai"]: - raise AppApiException(400, _("Authentication failed. Please verify that the parameters are correct.")) - - -class PromptGenerateSerializer(serializers.Serializer): - workspace_id = serializers.CharField(required=False, label=_("Workspace ID")) - model_id = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_("Model")) - application_id = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_("Application")) - - def is_valid(self, *, raise_exception=False): - super().is_valid(raise_exception=True) - workspace_id = self.data.get("workspace_id") - query_set = QuerySet(Application).filter(id=self.data.get("application_id")) - if workspace_id: - query_set = query_set.filter(workspace_id=workspace_id) - application = query_set.first() - if application is None: - raise AppApiException(500, _("Application id does not exist")) - return application - - def generate_prompt(self, instance: dict): - application = self.is_valid(raise_exception=True) - GeneratePromptSerializers(data=instance).is_valid(raise_exception=True) - workspace_id = self.data.get("workspace_id") - model_id = self.data.get("model_id") - prompt = instance.get("prompt") - messages = instance.get("messages") - - message = messages[-1]["content"] - q = prompt.replace("{userInput}", message) - - messages[-1]["content"] = q - SUPPORTED_MODEL_TYPES = ["LLM", "IMAGE"] - model_exist = QuerySet(Model).filter(id=model_id, model_type__in=SUPPORTED_MODEL_TYPES).exists() - if not model_exist: - raise Exception(_("Model does not exists or is not an LLM model")) - - def process(): - model = get_model_instance_by_model_workspace_id( - model_id=model_id, workspace_id=workspace_id, **application.model_params_setting - ) - try: - for r in model.stream( - [ - SystemMessage(content=SYSTEM_ROLE), - *[ - HumanMessage(content=m.get("content")) - if m.get("role") == "user" - else AIMessage(content=m.get("content")) - for m in messages - ], - ] - ): - yield "data: " + json.dumps({"content": r.content}) + "\n\n" - except Exception as e: - yield "data: " + json.dumps({"error": str(e)}) + "\n\n" - - return to_stream_response_simple(process()) - - # ==================== 语音 ==================== diff --git a/apps/models_provider/api/prompt_generate.py b/apps/models_provider/api/prompt_generate.py new file mode 100644 index 00000000000..09513d27e4f --- /dev/null +++ b/apps/models_provider/api/prompt_generate.py @@ -0,0 +1,36 @@ +# coding=utf-8 +from common.mixins.api_mixin import APIMixin +from django.utils.translation import gettext_lazy as _ +from drf_spectacular.types import OpenApiTypes +from drf_spectacular.utils import OpenApiParameter +from models_provider.serializers.prompt_generate_serializers import PromptGenerateSerializers + + +class PromptGenerateAPI(APIMixin): + @staticmethod + def get_parameters(workspace: bool = True): + parameters = [] + if workspace: + parameters.append( + OpenApiParameter( + name="workspace_id", + description=_("workspace id"), + type=OpenApiTypes.STR, + location=OpenApiParameter.PATH, # type: ignore + required=True, + ) + ) + parameters.append( + OpenApiParameter( + name="model_id", + description=_("model id"), + type=OpenApiTypes.STR, + location=OpenApiParameter.PATH, # type: ignore + required=True, + ) + ) + return parameters + + @staticmethod + def get_request(): + return PromptGenerateSerializers diff --git a/apps/models_provider/serializers/prompt_generate_serializers.py b/apps/models_provider/serializers/prompt_generate_serializers.py new file mode 100644 index 00000000000..d1584b5f97f --- /dev/null +++ b/apps/models_provider/serializers/prompt_generate_serializers.py @@ -0,0 +1,105 @@ +# coding=utf-8 +""" +@project: MaxKB +@Author:虎虎 +@file: prompt_generate_serializers.py +@desc: +""" + +import json +import os + +from common.config.embedding_config import ModelManage +from common.exception.app_exception import AppApiException +from common.utils.common import get_file_content, to_stream_response_simple +from django.db.models import QuerySet +from django.utils.translation import gettext_lazy as _ +from langchain_core.messages import AIMessage, HumanMessage, SystemMessage +from maxkb.conf import PROJECT_DIR +from models_provider.base_model_provider import ModelTypeConst +from models_provider.models import Model +from models_provider.tools import ( + get_model, + get_model_default_params, + get_model_instance_by_model_workspace_id, + reset_model_params, +) +from rest_framework import serializers + +SYSTEM_ROLE = get_file_content(os.path.join(PROJECT_DIR, "apps", "chat", "template", "generate_prompt_system")) + + +class PromptMessageSerializers(serializers.Serializer): + role = serializers.CharField(required=True, label=_("Role")) + content = serializers.CharField(required=True, label=_("Content")) + + +class PromptGenerateSerializers(serializers.Serializer): + workspace_id = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_("Workspace ID")) + model_id = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_("Model")) + model_params_setting = serializers.JSONField(required=False, default=dict, label=_("Model params setting")) + prompt = serializers.CharField(required=True, label=_("Prompt template")) + messages = PromptMessageSerializers(many=True, required=True, label=_("Chat context")) + + def is_valid(self, *, raise_exception=False): + super().is_valid(raise_exception=True) + messages = self.data.get("messages") or [] + if len(messages) > 30: + raise AppApiException(400, _("Too many messages")) + for index in range(len(messages)): + role = messages[index].get("role") + if role == "ai" and index % 2 != 1: + raise AppApiException(400, _("Authentication failed. Please verify that the parameters are correct.")) + if role == "user" and index % 2 != 0: + raise AppApiException(400, _("Authentication failed. Please verify that the parameters are correct.")) + if role not in ["user", "ai"]: + raise AppApiException(400, _("Authentication failed. Please verify that the parameters are correct.")) + model_id = self.data.get("model_id") + model = QuerySet(Model).filter(id=model_id).first() + if model is None: + raise AppApiException(500, _("Model does not exist")) + if model.model_type not in (ModelTypeConst.LLM.name, ModelTypeConst.IMAGE.name): + raise AppApiException(400, _("Model does not exists or is not an LLM model")) + # 共享/系统级模型:workspace_id 为字符串 "None",不校验传入的工作空间;否则必须属于传入的工作空间 + if model.workspace_id != "None" and self.data.get("workspace_id") != model.workspace_id: + raise AppApiException(403, _("Model is not visible to the workspace")) + return model + + def generate_prompt(self): + model = self.is_valid(raise_exception=True) + workspace_id = self.data.get("workspace_id") + model_id = self.data.get("model_id") + model_params_setting = self.data.get("model_params_setting") or {} + prompt = self.data.get("prompt") + messages = [dict(m) for m in self.data.get("messages") or []] + + message = messages[-1]["content"] + messages[-1]["content"] = prompt.replace("{userInput}", message) + + def process(): + if model.workspace_id == "None": + # 共享/系统级模型:不做工作空间授权,直接用模型自带认证信息实例化 + default_model_params = get_model_default_params(model) + model_params = reset_model_params(default_model_params, **model_params_setting) + model_instance = ModelManage.get_model(model_id, lambda _id: get_model(model, **model_params)) + else: + model_instance = get_model_instance_by_model_workspace_id( + model_id=model_id, workspace_id=workspace_id, **model_params_setting + ) + try: + for r in model_instance.stream( + [ + SystemMessage(content=SYSTEM_ROLE), + *[ + HumanMessage(content=m.get("content")) + if m.get("role") == "user" + else AIMessage(content=m.get("content")) + for m in messages + ], + ] + ): + yield "data: " + json.dumps({"content": r.content}) + "\n\n" + except Exception as e: + yield "data: " + json.dumps({"error": str(e)}) + "\n\n" + + return to_stream_response_simple(process()) diff --git a/apps/models_provider/urls.py b/apps/models_provider/urls.py index 431d09594ad..324cf2bc216 100644 --- a/apps/models_provider/urls.py +++ b/apps/models_provider/urls.py @@ -16,6 +16,7 @@ path('workspace//model', views.ModelSetting.as_view()), path('workspace//model_list', views.ModelList.as_view()), path('workspace//model//model_params_form', views.ModelSetting.ModelParamsForm.as_view()), + path('workspace//model//prompt_generate', views.ModelPromptGenerateView.as_view()), path('workspace//model/', views.ModelSetting.Operate.as_view()), path('workspace//model//pause_download', views.ModelSetting.PauseDownload.as_view()), path('workspace//model//meta', views.ModelSetting.ModelMeta.as_view()), diff --git a/apps/models_provider/views/model.py b/apps/models_provider/views/model.py index 61a12a15633..40283b6bda8 100644 --- a/apps/models_provider/views/model.py +++ b/apps/models_provider/views/model.py @@ -20,9 +20,11 @@ from django.utils.translation import gettext_lazy as _ from drf_spectacular.utils import extend_schema from models_provider.api.model import DefaultModelResponse, GetModelApi, ModelCreateAPI, ModelEditApi, ModelListResponse +from models_provider.api.prompt_generate import PromptGenerateAPI from models_provider.api.provide import ProvideApi from models_provider.models import Model from models_provider.serializers.model_serializer import ModelSerializer, WorkspaceSharedModelSerializer +from models_provider.serializers.prompt_generate_serializers import PromptGenerateSerializers from rest_framework.request import Request from rest_framework.views import APIView from system_manage.views import encryption_str @@ -381,3 +383,33 @@ def get(self, request: Request, workspace_id: str): data={**query_params_to_single_dict(request.query_params), "user_id": str(request.user.id)} ).model_list(workspace_id=workspace_id, with_valid=True) ) + + +class ModelPromptGenerateView(APIView): + authentication_classes = [TokenAuth] + + @extend_schema( + methods=["POST"], + summary=_("Generate prompt"), + description=_("Generate prompt by model"), + operation_id=_("Generate prompt"), # type: ignore + parameters=PromptGenerateAPI.get_parameters(workspace=True), + request=PromptGenerateAPI.get_request(), + responses=None, + tags=[_("Model")], # type: ignore + ) + @has_permissions( + PermissionConstants.MODEL_READ.get_workspace_model_permission(), + PermissionConstants.MODEL_READ.get_workspace_permission_workspace_manage_role(), + RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), + RoleConstants.USER.get_workspace_role(), + ) + @log( + menu="model", + operate="Generate prompt", + get_operation_object=lambda r, k: get_model_operation_object(k.get("model_id")), + ) + def post(self, request: Request, workspace_id: str, model_id: str): + return PromptGenerateSerializers( + data={**request.data, "workspace_id": workspace_id, "model_id": model_id} + ).generate_prompt() diff --git a/ui/src/api/API_README.md b/ui/src/api/API_README.md index 972402a68f1..5585dd2fcc3 100644 --- a/ui/src/api/API_README.md +++ b/ui/src/api/API_README.md @@ -198,14 +198,13 @@ v3 编辑表单提交 `{ name, description }`,标题上限 64、更新说明 ### 系统提示词生成 -Workspace、System 资源管理和 System 共享资源的 `application/application.ts` 均提供 -`postPromptGenerate(applicationId, modelId, payload)`,请求结构共用 `PromptGeneratePayload`。 -路径分别为 `/workspace//application//model//prompt_generate`、 -`/system/resource/application//model//prompt_generate` 和 -`/system/shared/application//model//prompt_generate`,System 接口不依赖工作空间 ID。 +Workspace、System 资源管理和 System 共享资源均把 `postPromptGenerate(modelId, payload)` 放在对应范围的 +`model.ts`,请求结构共用 `PromptGeneratePayload`(含 `model_params_setting`)。 +路径为 `/workspace//model//prompt_generate` 与 `/system/model//prompt_generate`, +System 接口不依赖工作空间 ID。 均返回原始 SSE Response,由公共生成组件读取。画布的 `PromptGenerate` 通过 `useWorkflowStore(apiType).postPromptGenerate` 调用;三个范围的 Store 适配器分别引用对应的 -Application API,直接转发生成请求,不使用查询缓存或请求去重。 +Model API,直接转发生成请求,不使用查询缓存或请求去重。 ### 知识库维护 diff --git a/ui/src/api/admin/system/resource-management/application/application.ts b/ui/src/api/admin/system/resource-management/application/application.ts index 1dc0da66134..5c1a6630a5a 100644 --- a/ui/src/api/admin/system/resource-management/application/application.ts +++ b/ui/src/api/admin/system/resource-management/application/application.ts @@ -1,7 +1,6 @@ -import { del, get, getExportFile, postStream } from '@/api/admin/core/request' +import { del, get, getExportFile } from '@/api/admin/core/request' import type { ParamsPage, ResponsePage } from '@/api/admin/core/types' -import type { ApplicationDetail, Dict, PromptGeneratePayload } from '@/api/types' -import { ADMIN_API_BASE_PATH } from '@/api/constants' +import type { ApplicationDetail, Dict } from '@/api/types' const prefix = '/system/resource/application' @@ -10,10 +9,6 @@ const getApplicationPage = (page: ParamsPage, query?: Dict) => { return get>(`${prefix}/${page.currentPage}/${page.pageSize}`, query) } -/** 使用指定模型流式生成或优化 System 资源智能体的系统提示词。 */ -const postPromptGenerate = (applicationId: string, modelId: string, payload: PromptGeneratePayload) => { - return postStream(ADMIN_API_BASE_PATH, `${prefix}/${applicationId}/model/${modelId}/prompt_generate`, payload) -} /** 删除系统资源管理智能体。 */ const deleteApplication = (applicationId: string) => { @@ -27,7 +22,6 @@ const exportApplication = (applicationId: string, applicationName: string) => { export default { getApplicationPage, - postPromptGenerate, deleteApplication, exportApplication, } diff --git a/ui/src/api/admin/system/resource-management/model.ts b/ui/src/api/admin/system/resource-management/model.ts index 2d33fe88a73..722abbf0333 100644 --- a/ui/src/api/admin/system/resource-management/model.ts +++ b/ui/src/api/admin/system/resource-management/model.ts @@ -1,6 +1,7 @@ -import { del, get, post, put } from '../../core/request' +import { del, get, post, postStream, put } from '../../core/request' import type { ParamsPage, ResponsePage } from '../../core/types' -import type { Dict, DynamicFormField, ModelItem, ModelPayload } from '@/api/types' +import type { Dict, DynamicFormField, ModelItem, ModelPayload, PromptGeneratePayload } from '@/api/types' +import { ADMIN_API_BASE_PATH } from '@/api/constants' const prefix = '/system/resource/model' @@ -34,4 +35,10 @@ const getModelParamsForm = (modelId: string) => get(`${prefi const putModelParamsForm = (modelId: string, payload: DynamicFormField[]) => put(`${prefix}/${modelId}/model_params_form`, payload) -export default { getModelPage, getModelListWithShared, getModelDetail, postModel, putModel, deleteModel, getModelParamsForm, putModelParamsForm } + +/** 使用指定模型流式生成或优化系统提示词。 */ +const postPromptGenerate = (modelId: string, payload: PromptGeneratePayload) => { + return postStream(ADMIN_API_BASE_PATH, `/system/resource/model/${modelId}/prompt_generate`, { ...payload }) +} + +export default { getModelPage, getModelListWithShared, getModelDetail, postModel, putModel, deleteModel, getModelParamsForm, putModelParamsForm, postPromptGenerate } diff --git a/ui/src/api/admin/system/shared-resources/application/application.ts b/ui/src/api/admin/system/shared-resources/application/application.ts deleted file mode 100644 index b30d9f02d8e..00000000000 --- a/ui/src/api/admin/system/shared-resources/application/application.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { postStream } from '@/api/admin/core/request' -import type { PromptGeneratePayload } from '@/api/types' -import { ADMIN_API_BASE_PATH } from '@/api/constants' - -const prefix = '/system/shared/application' - -/** 使用指定模型流式生成或优化 System 共享智能体的系统提示词。 */ -const postPromptGenerate = (applicationId: string, modelId: string, payload: PromptGeneratePayload) => { - return postStream(ADMIN_API_BASE_PATH, `${prefix}/${applicationId}/model/${modelId}/prompt_generate`, payload) -} - -export default { - postPromptGenerate, -} diff --git a/ui/src/api/admin/system/shared-resources/model.ts b/ui/src/api/admin/system/shared-resources/model.ts index f62d0aa818c..b2ed0174103 100644 --- a/ui/src/api/admin/system/shared-resources/model.ts +++ b/ui/src/api/admin/system/shared-resources/model.ts @@ -1,5 +1,6 @@ -import { del, get, post, put } from '../../core/request' -import type { Dict, DynamicFormField, ModelItem, ModelPayload } from '@/api/types' +import { del, get, post, postStream, put } from '../../core/request' +import type { Dict, DynamicFormField, ModelItem, ModelPayload, PromptGeneratePayload } from '@/api/types' +import { ADMIN_API_BASE_PATH } from '@/api/constants' const prefix = '/system/shared/model' @@ -56,6 +57,12 @@ const putPauseModelDownload = (modelId: string) => { return put(`${prefix}/${modelId}/pause_download`) } + +/** 使用指定模型流式生成或优化系统提示词。 */ +const postPromptGenerate = (modelId: string, payload: PromptGeneratePayload) => { + return postStream(ADMIN_API_BASE_PATH, `/system/shared/model/${modelId}/prompt_generate`, { ...payload }) +} + export default { deleteModel, getModelDetail, @@ -64,6 +71,7 @@ export default { getModelMeta, getModelParamsForm, postModel, + postPromptGenerate, putModel, putModelParamsForm, putPauseModelDownload, diff --git a/ui/src/api/admin/workspace/application/application.ts b/ui/src/api/admin/workspace/application/application.ts index 6b9ad58522f..224bb5f8701 100644 --- a/ui/src/api/admin/workspace/application/application.ts +++ b/ui/src/api/admin/workspace/application/application.ts @@ -1,8 +1,7 @@ -import { del, get, getExportFile, post, postStream, put } from '../../core/request' +import { del, get, getExportFile, post, put } from '../../core/request' import type { ParamsPage, ResponsePage } from '../../core/types' -import type { ApplicationDetail, ApplicationFormPayload, Dict, PromptGeneratePayload } from '@/api/types' +import type { ApplicationDetail, ApplicationFormPayload, Dict } from '@/api/types' import { getWorkspaceId } from '@/utils/resource-context' -import { ADMIN_API_BASE_PATH } from '@/api/constants' const getPrefix = () => { const workspaceId = getWorkspaceId() @@ -76,10 +75,6 @@ const putApplicationPublish = (applicationId: string, publishName: string, publi }) } -/** 使用指定模型流式生成或优化系统提示词。 */ -const postPromptGenerate = (applicationId: string, modelId: string, payload: PromptGeneratePayload) => { - return postStream(ADMIN_API_BASE_PATH, `${getPrefix()}/${applicationId}/model/${modelId}/prompt_generate`, payload) -} export default { getApplicationPage, @@ -94,5 +89,4 @@ export default { putBatchMoveApplications, putApplicationPublish, getAllApplication, - postPromptGenerate, } diff --git a/ui/src/api/admin/workspace/model.ts b/ui/src/api/admin/workspace/model.ts index cbd6733dab9..632a33591d4 100644 --- a/ui/src/api/admin/workspace/model.ts +++ b/ui/src/api/admin/workspace/model.ts @@ -1,6 +1,8 @@ -import { del, get, post, put } from '../core/request' +import { del, get, post, postStream, put } from '../core/request' import type { Dict, DynamicFormField, ModelPayload, ModelItem } from '@/api/types' import { getWorkspaceId } from '@/utils/resource-context' +import type { PromptGeneratePayload } from '@/api/types' +import { ADMIN_API_BASE_PATH } from '@/api/constants' const getPrefix = () => { const workspaceId = getWorkspaceId() @@ -62,6 +64,14 @@ const putPauseModelDownload = (modelId: string) => { return put(`${getPrefix()}/${modelId}/pause_download`) } + +/** 使用指定模型流式生成或优化系统提示词。 */ +const postPromptGenerate = (modelId: string, payload: PromptGeneratePayload) => { + return postStream(ADMIN_API_BASE_PATH, `/workspace/${getWorkspaceId()}/model/${modelId}/prompt_generate`, { + ...payload, + }) +} + export default { deleteModel, getModelDetail, @@ -70,6 +80,7 @@ export default { getModelMeta, getModelParamsForm, postModel, + postPromptGenerate, putModel, putModelParamsForm, putPauseModelDownload, diff --git a/ui/src/api/types/application.ts b/ui/src/api/types/application.ts index bada2f48a9a..158e70d81f0 100644 --- a/ui/src/api/types/application.ts +++ b/ui/src/api/types/application.ts @@ -86,6 +86,7 @@ export interface PromptGenerateMessage { export interface PromptGeneratePayload { messages: PromptGenerateMessage[] prompt: string + model_params_setting?: Record } /** 智能体模板复用工作流模板的展示与下载元数据。 */ diff --git a/ui/src/workflow-canvas/WORKFLOW_README.md b/ui/src/workflow-canvas/WORKFLOW_README.md index 071d53f5481..3273f3ebfcd 100644 --- a/ui/src/workflow-canvas/WORKFLOW_README.md +++ b/ui/src/workflow-canvas/WORKFLOW_README.md @@ -134,7 +134,7 @@ LogicFlow 的节点拖拽;仅拦截 `mousedown` 无法隔离当前版本的 Po `useWorkflowStore(apiType)` 是画布资源接口适配器,不是 Pinia Store。它自动收集 `store/api/*/index.ts`, 查询方法按范围、方法与查询参数缓存结果并复用在途请求;`store.force.xxx()` 跳过已完成缓存,仍复用同键在途请求。 -`postPromptGenerate` 是流式生成方法,始终直接转发到对应范围的 Application API,不缓存或去重; +`postPromptGenerate` 是流式生成方法,始终直接转发到对应范围的 Model API,不缓存或去重; 通过普通 Store 或 `force` 调用都会发起新请求,返回独立的 Response 流。 模型选项统一调用 `store.getModelListWithShared(query)`,由对应范围 API 查询;Workspace 和 System 资源管理合并普通与共享模型,System 共享范围仅返回标记 `source: 'shared'` 的共享模型。 @@ -330,10 +330,10 @@ AI 对话节点的提示词、历史记录、视觉理解和输出思考表单 图片理解和视频理解使用 IMAGE 模型,AI 对话使用 LLM 模型,现有提示词接口支持这两种类型。 共享组件维护提示词模板和请求上下文,不依赖某个节点,也不接收未使用的模型或供应商选项。 组件将画布注入的 `apiType` 传给 `useWorkflowStore`,通过 `store.postPromptGenerate` 发起生成; -Store 适配器统一选择 Workspace、System 资源管理或 System 共享资源的 Application API。 +Store 适配器统一选择 Workspace、System 资源管理或 System 共享资源的 Model API。 循环画布已转发同一上下文,组件不再单独判断资源范围。生成方法不使用 Store 的查询缓存或请求去重。 -接口仍要求当前路由的智能体 ID,生成使用智能体已保存的模型参数;没有智能体 ID 时禁用入口, -工具和知识库工作流尚未接入对应的提示词生成接口。弹窗不提供独立模型或参数设置。 +生成接口不再依赖应用,改用节点自身已保存的模型参数(`model_params_setting`)并随请求体提交, +因此智能体、工具和知识库工作流均可使用;弹窗不提供独立模型或参数设置。 公共组件统一维护主题输入、会话消息与 `ConversationStream`;重新生成在历史中追加用户消息 `Re generate` 后请求,停止通过 `cancel()` 终止读取,关闭动画结束后清理会话。 点击替换后通过 `replace` 交由节点回写系统提示词。 diff --git a/ui/src/workflow-canvas/component/PromptGenerate.vue b/ui/src/workflow-canvas/component/PromptGenerate.vue index 0e6de328106..82f1daa6135 100644 --- a/ui/src/workflow-canvas/component/PromptGenerate.vue +++ b/ui/src/workflow-canvas/component/PromptGenerate.vue @@ -1,15 +1,14 @@ @@ -106,7 +103,7 @@ function requestGeneratePrompt(messages: PromptGenerateMessage[]) { title="生成提示词" placeholder="请输入提示词" empty-text="提示词显示在这里" - :disabled="!modelId || !route.params.applicationId" + :disabled="!modelId" :request="requestGeneratePrompt" @open="initGenerate" @closed="resetData" diff --git a/ui/src/workflow-canvas/nodes/ai-chat-node/index.vue b/ui/src/workflow-canvas/nodes/ai-chat-node/index.vue index f5ac118774e..3f4ac70ef5b 100644 --- a/ui/src/workflow-canvas/nodes/ai-chat-node/index.vue +++ b/ui/src/workflow-canvas/nodes/ai-chat-node/index.vue @@ -150,7 +150,7 @@ onMounted(() => { - + {
- +
diff --git a/ui/src/workflow-canvas/nodes/video-understand-node/index.vue b/ui/src/workflow-canvas/nodes/video-understand-node/index.vue index faf04c0f8f6..41d6adff9ad 100644 --- a/ui/src/workflow-canvas/nodes/video-understand-node/index.vue +++ b/ui/src/workflow-canvas/nodes/video-understand-node/index.vue @@ -137,7 +137,7 @@ onMounted(() => {
- +
diff --git a/ui/src/workflow-canvas/store/api/system-resource/index.ts b/ui/src/workflow-canvas/store/api/system-resource/index.ts index 72bd5e036ba..29c84a5967f 100644 --- a/ui/src/workflow-canvas/store/api/system-resource/index.ts +++ b/ui/src/workflow-canvas/store/api/system-resource/index.ts @@ -1,7 +1,6 @@ import { get, post } from '@/api/admin/core/request' import type { KnowledgeTagGroup } from '@/api/types' import type { McpTool } from '@/workflow-canvas/store' -import ApplicationApi from '@/api/admin/system/resource-management/application/application' import ModelApi from '@/api/admin/system/resource-management/model' import ToolApi from '@/api/admin/system/resource-management/tool/tool' import ProviderApi from '@/api/admin/model-provider' @@ -14,7 +13,7 @@ const getMcpTools = (resourceType: string, resourceId: string, mcpServers: strin const getAllTags = (knowledgeIds: string[]) => get('/system/resource/knowledge/tags', { 'knowledge_ids[]': knowledgeIds }) export default { - postPromptGenerate: ApplicationApi.postPromptGenerate, + postPromptGenerate: ModelApi.postPromptGenerate, getMcpTools, getAllTags, getModelListWithShared: ModelApi.getModelListWithShared, diff --git a/ui/src/workflow-canvas/store/api/system-shared/index.ts b/ui/src/workflow-canvas/store/api/system-shared/index.ts index 89e5fc1671a..4fcd820a441 100644 --- a/ui/src/workflow-canvas/store/api/system-shared/index.ts +++ b/ui/src/workflow-canvas/store/api/system-shared/index.ts @@ -1,7 +1,6 @@ import { get, post } from '@/api/admin/core/request' import type { KnowledgeTagGroup } from '@/api/types' import type { McpTool } from '@/workflow-canvas/store' -import ApplicationApi from '@/api/admin/system/shared-resources/application/application' import ModelApi from '@/api/admin/system/shared-resources/model' import ToolApi from '@/api/admin/system/shared-resources/tool/tool' import ProviderApi from '@/api/admin/model-provider' @@ -14,7 +13,7 @@ const getMcpTools = (resourceType: string, resourceId: string, mcpServers: strin const getAllTags = (knowledgeIds: string[]) => get('/system/shared/knowledge/tags', { 'knowledge_ids[]': knowledgeIds }) export default { - postPromptGenerate: ApplicationApi.postPromptGenerate, + postPromptGenerate: ModelApi.postPromptGenerate, getMcpTools, getAllTags, getModelListWithShared: ModelApi.getModelListWithShared, diff --git a/ui/src/workflow-canvas/store/api/workspace/index.ts b/ui/src/workflow-canvas/store/api/workspace/index.ts index bdc6de9e1f5..70551f2d112 100644 --- a/ui/src/workflow-canvas/store/api/workspace/index.ts +++ b/ui/src/workflow-canvas/store/api/workspace/index.ts @@ -1,6 +1,5 @@ import { get, post } from '@/api/admin/core/request' import type { KnowledgeTagGroup } from '@/api/types' -import ApplicationApi from '@/api/admin/workspace/application/application' import modelAPI from '@/api/admin/workspace/model' import providerAPI from '@/api/admin/model-provider' import toolAPI from '@/api/admin/workspace/tool/tool' @@ -21,7 +20,7 @@ const getAllTags = (knowledgeIds: string[]) => { } export default { - postPromptGenerate: ApplicationApi.postPromptGenerate, + postPromptGenerate: modelAPI.postPromptGenerate, getModelListWithShared: modelAPI.getModelListWithShared, getProviderList: providerAPI.getProviderList, getModelParamsForm: modelAPI.getModelParamsForm, diff --git a/ui/src/workflow-canvas/store/index.ts b/ui/src/workflow-canvas/store/index.ts index ad939f026c7..f365065fdfc 100644 --- a/ui/src/workflow-canvas/store/index.ts +++ b/ui/src/workflow-canvas/store/index.ts @@ -17,7 +17,7 @@ type ApiModule = { getToolListWithShared?: (query?: Dict) => Promise getToolById?: (toolId: string) => Promise getKnowledgeTags?: (knowledgeIds: string[]) => Promise - postPromptGenerate: (applicationId: string, modelId: string, payload: PromptGeneratePayload) => Promise + postPromptGenerate: (modelId: string, payload: PromptGeneratePayload) => Promise } // 查询默认走缓存,通过 store.force.xxx() 强制刷新;生成请求始终直接转发。 @@ -30,7 +30,7 @@ export type WorkflowStoreApi = { getToolListWithShared: (query?: Dict) => Promise getToolById: (toolId: string) => Promise getKnowledgeTags: (knowledgeIds: string[]) => Promise - postPromptGenerate: (applicationId: string, modelId: string, payload: PromptGeneratePayload) => Promise + postPromptGenerate: (modelId: string, payload: PromptGeneratePayload) => Promise } type WorkflowStore = WorkflowStoreApi & { force: WorkflowStoreApi }