diff --git a/apps/application/flow/step_node/intent_node/i_intent_node.py b/apps/application/flow/step_node/intent_node/i_intent_node.py index d22d321c842..e57931da0a0 100644 --- a/apps/application/flow/step_node/intent_node/i_intent_node.py +++ b/apps/application/flow/step_node/intent_node/i_intent_node.py @@ -20,12 +20,14 @@ class IntentNodeSerializer(serializers.Serializer): model_id_type = serializers.CharField(required=False, default='custom', label=_("Model id type")) model_id_reference = serializers.ListField(required=False, child=serializers.CharField(), allow_empty=True, label=_("Reference Field")) + prompt_template = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_("Prompt template")) content_list = serializers.ListField(required=True, label=_("Text content")) dialogue_number = serializers.IntegerField(required=True, label= _("Number of multi-round conversations")) model_params_setting = serializers.DictField(required=False, label=_("Model parameter settings")) branch = IntentBranchSerializer(many=True) + output_reason = serializers.BooleanField(required=False, label=_("Output reason"), default=True) class IIntentNode(INode): diff --git a/apps/application/flow/step_node/intent_node/impl/base_intent_node.py b/apps/application/flow/step_node/intent_node/impl/base_intent_node.py index cdf8a769abf..ce51521b5e2 100644 --- a/apps/application/flow/step_node/intent_node/impl/base_intent_node.py +++ b/apps/application/flow/step_node/intent_node/impl/base_intent_node.py @@ -12,7 +12,7 @@ from application.flow.step_node.intent_node.i_intent_node import IIntentNode from models_provider.models import Model from models_provider.tools import get_model_instance_by_model_workspace_id, get_model_credential -from .prompt_template import PROMPT_TEMPLATE +from .prompt_template import DEFAULT_PROMPT_TEMPLATE def get_default_model_params_setting(model_id): @@ -52,7 +52,7 @@ def save_context(self, details, workflow_manage): self.context['branch_id'] = details.get('branch_id') self.context['category'] = details.get('category') - def execute(self, model_id, dialogue_number, history_chat_record, user_input, branch, + def execute(self, model_id, prompt_template, dialogue_number, history_chat_record, user_input, branch, output_reason, model_params_setting=None, model_id_type=None, model_id_reference=None, **kwargs) -> NodeResult: # 处理引用类型 if model_id_type == 'reference' and model_id_reference: @@ -75,18 +75,19 @@ def execute(self, model_id, dialogue_number, history_chat_record, user_input, br ) # 获取历史对话 - history_message = self.get_history_message(history_chat_record, dialogue_number) + history_message = self.get_history_message(history_chat_record, dialogue_number) if history_chat_record and dialogue_number > 0 else [] self.context['history_message'] = history_message # 保存问题到上下文 self.context['user_input'] = user_input # 构建分类提示词 - prompt = self.build_classification_prompt(user_input, branch) + prompt_template = self.workflow_manage.generate_prompt(prompt_template) if prompt_template else None + prompt = self.build_classification_prompt(prompt_template, user_input, branch, output_reason) + self.context['system'] = prompt # 生成消息列表 - system = self.build_system_prompt() - message_list = self.generate_message_list(system, prompt, history_message) + message_list = self.generate_message_list(prompt, history_message) self.context['message_list'] = message_list # 调用模型进行分类 @@ -104,7 +105,7 @@ def execute(self, model_id, dialogue_number, history_chat_record, user_input, br 'history_message': history_message, 'user_input': user_input, 'branch_id': matched_branch['id'], - 'reason': self.parse_result_reason(r.content), + 'reason': self.parse_result_reason(r.content) if output_reason is not False else '', 'category': matched_branch.get('content', matched_branch['id']) }, {}, _write_context=write_context) @@ -134,11 +135,7 @@ def get_history_message(history_chat_record, dialogue_number): message.content = re.sub('[\d\D]*?<\/form_rander>', '', message.content) return history_message - def build_system_prompt(self) -> str: - """构建系统提示词""" - return "你是一个专业的意图识别助手,请根据用户输入和意图选项,准确识别用户的真实意图。" - - def build_classification_prompt(self, user_input: str, branch: List[Dict]) -> str: + def build_classification_prompt(self, prompt_template: str, user_input: str, branch: List[Dict], output_reason: bool) -> str: """构建分类提示词""" classification_list = [] @@ -160,18 +157,19 @@ def build_classification_prompt(self, user_input: str, branch: List[Dict]) -> st }) classification_id += 1 - return PROMPT_TEMPLATE.format( - classification_list=classification_list, - user_input=user_input + # 构建输出JSON结构 + reason_field = ',\n"reason": ""' if output_reason is not False else '' + output_json = f'{{\n"classificationId": 0{reason_field}\n}}' + + return (prompt_template or DEFAULT_PROMPT_TEMPLATE).format( + classification_list=json.dumps(classification_list, ensure_ascii=False), + user_input=user_input, + output_json=output_json ) - def generate_message_list(self, system: str, prompt: str, history_message): + def generate_message_list(self, prompt: str, history_message): """生成消息列表""" - if system is None or len(system) == 0: - return [*history_message, HumanMessage(self.workflow_manage.generate_prompt(prompt))] - else: - return [SystemMessage(self.workflow_manage.generate_prompt(system)), *history_message, - HumanMessage(self.workflow_manage.generate_prompt(prompt))] + return [*history_message, HumanMessage(self.workflow_manage.generate_prompt(prompt))] def parse_classification_result(self, result: str, branch: List[Dict]) -> Dict[str, Any]: """解析分类结果""" @@ -191,14 +189,23 @@ def get_branch_by_id(category_id: int): return None try: - result_json = json.loads(result) - classification_id = result_json.get('classificationId') + classification_id = None + + # 如果长度小于5,先尝试解析为数字(增加自由度,在自定义提示词模板时,可提示大模型只输出意图分类的ID值) + if len(result) < 5: + classification_id = self.to_int(result) + + # 尝试解析为 JSON + if classification_id is None: + result_json = json.loads(result) + classification_id = result_json.get('classificationId') + # 如果是 0 ,返回其他分支 matched_branch = get_branch_by_id(classification_id) if matched_branch: return matched_branch - except Exception as e: + except Exception: # json 解析失败,re 提取 numbers = re.findall(r'"classificationId":\s*(\d+)', result) if numbers: @@ -216,7 +223,7 @@ def parse_result_reason(self, result: str): try: result_json = json.loads(result) return result_json.get('reason', '') - except Exception as e: + except Exception: reason_patterns = [ r'"reason":\s*"([^"]*)"', # 标准格式 r'"reason":\s*"([^"]*)', # 缺少结束引号 @@ -232,6 +239,12 @@ def parse_result_reason(self, result: str): return '' + def to_int(self, str): + try: + return int(str) + except ValueError: + return None + def find_other_branch(self, branch: List[Dict]) -> Dict[str, Any] | None: """查找其他分支""" for b in branch: diff --git a/apps/application/flow/step_node/intent_node/impl/prompt_template.py b/apps/application/flow/step_node/intent_node/impl/prompt_template.py index 1bcfd61743e..79062022edb 100644 --- a/apps/application/flow/step_node/intent_node/impl/prompt_template.py +++ b/apps/application/flow/step_node/intent_node/impl/prompt_template.py @@ -1,6 +1,6 @@ -PROMPT_TEMPLATE = """# Role +DEFAULT_PROMPT_TEMPLATE = """# Role You are an intention classification expert, good at being able to judge which classification the user's input belongs to. ## Skills @@ -20,10 +20,7 @@ - Strictly ensure that the output is in a valid JSON format. - Do not add prefix ```json or suffix ``` - The answer needs to include the following fields such as: -{{ -"classificationId": 0, -"reason": "" -}} +{output_json} ## Limit - Please do not reply in text.""" diff --git a/ui/src/components/execution-detail-card/index.vue b/ui/src/components/execution-detail-card/index.vue index 347ee55ea46..74c645e1bfc 100644 --- a/ui/src/components/execution-detail-card/index.vue +++ b/ui/src/components/execution-detail-card/index.vue @@ -194,7 +194,7 @@ {{ $t('views.application.form.roleSettings.label') }}
- {{ data.system || '-' }} +
{{ data.system || '-' }}
@@ -257,7 +257,7 @@ {{ $t('views.application.form.roleSettings.label') }}
- {{ data.system || '-' }} +
{{ data.system || '-' }}
@@ -547,7 +547,7 @@ {{ $t('views.application.form.roleSettings.label') }}
- {{ data.system || '-' }} +
{{ data.system || '-' }}
@@ -633,7 +633,7 @@ {{ $t('views.application.form.roleSettings.label') }}
- {{ data.system || '-' }} +
{{ data.system || '-' }}
diff --git a/ui/src/locales/lang/en-US/views/application.ts b/ui/src/locales/lang/en-US/views/application.ts index 072779082e7..7bd7272cae2 100644 --- a/ui/src/locales/lang/en-US/views/application.ts +++ b/ui/src/locales/lang/en-US/views/application.ts @@ -87,6 +87,11 @@ export default { - Please use concise and professional language to answer the user's question. `, }, + prompt_template: { + label: 'Prompt Template', + requiredMessage: 'Please enter Prompt template', + tooltip: 'Please pay attention to the placeholders in the template: {classification_list}、{user_input}、{output_json}', + }, historyRecord: { label: 'Chat History', }, diff --git a/ui/src/locales/lang/en-US/workflow.ts b/ui/src/locales/lang/en-US/workflow.ts index 74fa49f04b4..7b24d918972 100644 --- a/ui/src/locales/lang/en-US/workflow.ts +++ b/ui/src/locales/lang/en-US/workflow.ts @@ -188,7 +188,7 @@ export default { result: 'Search Results', searchParam: 'Search Parameters', select_variable: 'Select Variable', - valueMessage: `Value or name `, + valueMessage: 'Value or name', searchQuestion: { label: 'Search Question', @@ -447,9 +447,34 @@ You are a master of problem optimization, adept at accurately inferring user int classify: { label: 'Intent classify', }, + output_reason: 'Output Reason', input: { label: 'Input', }, + default_prompt_template: `# Role +You are an intention classification expert, good at being able to judge which classification the user's input belongs to. + +## Skills +Skill 1: Clearly determine which of the following intention classifications the user's input belongs to. +Intention classification list: +{classification_list} + +Note: +- Please determine the match between the user's input content and the Intention classification list content, without judging or categorizing the match with the classification ID. +- **When classifying, you must give higher weight to the context and intent continuity shown in the historical conversation. Do not rely solely on the literal meaning of the current input; instead, prioritize the most consistent classification with the previous dialogue flow.** + +## User Input +{user_input} + +## Reply requirements +- The answer must be returned in JSON format. +- Strictly ensure that the output is in a valid JSON format. +- Do not add prefix \`\`\`json or suffix \`\`\` +- The answer needs to include the following fields such as: +{output_json} + +## Limit +- Please do not reply in text.`, }, applicationNode: { label: 'Agent Node', @@ -541,6 +566,7 @@ You are a master of problem optimization, adept at accurately inferring user int }, SystemPromptPlaceholder: 'System Prompt, can reference variables in the system, such as', UserPromptPlaceholder: 'User Prompt, can reference variables in the system, such as', + PromptTemplatePlaceholder: 'Prompt Template, can reference variables in the system, such as', initiator: 'Iniiator', abnormalInformation: 'Abnormal Information', } diff --git a/ui/src/locales/lang/zh-CN/views/application.ts b/ui/src/locales/lang/zh-CN/views/application.ts index 6ebe33d24ce..72e414202ed 100644 --- a/ui/src/locales/lang/zh-CN/views/application.ts +++ b/ui/src/locales/lang/zh-CN/views/application.ts @@ -80,6 +80,11 @@ export default { 回答要求: - 请使用中文回答用户问题`, }, + prompt_template: { + label: '提示词模板', + requiredMessage: '请输入提示词模板', + tooltip: '请注意模板中的占位符: {classification_list}、{user_input}、{output_json}', + }, historyRecord: { label: '历史聊天记录', }, diff --git a/ui/src/locales/lang/zh-CN/workflow.ts b/ui/src/locales/lang/zh-CN/workflow.ts index 0f069ddeccd..3fa4e620d27 100644 --- a/ui/src/locales/lang/zh-CN/workflow.ts +++ b/ui/src/locales/lang/zh-CN/workflow.ts @@ -439,9 +439,34 @@ export default { classify: { label: '意图分类', }, + output_reason: '输出理由', input: { label: '输入', }, + default_prompt_template: `# 角色 +你是一位意图分类专家,擅长判断用户输入属于哪个分类。 + +## 技能 +技能1:明确判断用户输入属于以下哪种意图分类。 +意图分类列表: +{classification_list} + +注: +- 请判断用户输入内容与意图分类列表内容之间的匹配度,注意不要以分类ID作为评判或归类的依据。 +- **在分类时,必须更加重视历史对话中表现的上下文和意图连贯性。不要仅依赖当前输入的字面意思;相反,应优先考虑与先前对话流程最匹配的分类。** + +## 用户输入 +{user_input} + +## 回复要求 +- 回复内容必须以JSON格式返回。 +- 严格确保输出为有效的JSON格式。 +- 不要添加前缀 \`\`\`json 或 后缀 \`\`\` +- 回复内容需要包含以下字段: +{output_json} + +## 限制 +- 请勿以文本形式回复。`, }, applicationNode: { label: '智能体节点', @@ -532,6 +557,7 @@ export default { }, SystemPromptPlaceholder: '系统提示词,可以引用系统中的变量:如', UserPromptPlaceholder: '用户提示词,可以引用系统中的变量:如', + PromptTemplatePlaceholder: '提示词模板,可以引用系统中的变量:如', initiator: '发起人', abnormalInformation: '异常信息' diff --git a/ui/src/locales/lang/zh-Hant/views/application.ts b/ui/src/locales/lang/zh-Hant/views/application.ts index 9a613106305..c2fc674ca07 100644 --- a/ui/src/locales/lang/zh-Hant/views/application.ts +++ b/ui/src/locales/lang/zh-Hant/views/application.ts @@ -80,6 +80,11 @@ export default { 回答要求: - 請使用中文回答用戶問題`, }, + prompt_template: { + label: '提示詞模板', + requiredMessage: '請輸入提示詞模板', + tooltip: '請注意模板中的佔位符: {classification_list}、{user_input}、{output_json}', + }, historyRecord: { label: '歷史對話紀錄', }, diff --git a/ui/src/locales/lang/zh-Hant/workflow.ts b/ui/src/locales/lang/zh-Hant/workflow.ts index 25d844d783f..3d01130e9b5 100644 --- a/ui/src/locales/lang/zh-Hant/workflow.ts +++ b/ui/src/locales/lang/zh-Hant/workflow.ts @@ -439,9 +439,34 @@ export default { classify: { label: '意圖分類', }, + output_reason: '輸出理由', input: { label: '輸入', }, + default_prompt_template: `# 角色 +你是一位意圖分類專家,擅長判斷用戶輸入屬於哪個分類。 + +## 技能 +技能1:明確判斷用戶輸入屬於以下哪種意圖分類。 +意圖分類列表: +{classification_list} + +注: +- 請判斷用戶輸入內容與意圖分類列表內容之間的匹配度,注意不要以分類ID作爲評判或歸類的依據。 +- **在分類時,必須更加重視歷史對話中表現的上下文和意圖連貫性。不要僅依賴當前輸入的字面意思;相反,應優先考慮與先前對話流程最匹配的分類。** + +## 用戶輸入 +{user_input} + +## 回覆要求 +- 回覆內容必須以JSON格式返回。 +- 嚴格確保輸出爲有效的JSON格式。 +- 不要添加前綴 \`\`\`json 或 後綴 \`\`\` +- 回覆內容需要包含以下字段: +{output_json} + +## 限制 +- 請勿以文本形式回覆。`, }, applicationNode: { label: '智能體節點', @@ -526,6 +551,7 @@ export default { }, SystemPromptPlaceholder: '系統提示詞,可以引用系統中的變量:如', UserPromptPlaceholder: '用戶提示詞,可以引用系統中的變量:如', + PromptTemplatePlaceholder: '提示詞模板,可以引用系統中的變量:如', initiator: '發起人', abnormalInformation: '異常信息', } diff --git a/ui/src/styles/app.scss b/ui/src/styles/app.scss index 5d79261d168..364a5f8ca82 100644 --- a/ui/src/styles/app.scss +++ b/ui/src/styles/app.scss @@ -18,7 +18,7 @@ html { font-size: 100%; } -body { +body, .card-never pre { -moz-osx-font-smoothing: grayscale; -webkit-font-smoothing: antialiased; font-family: 'PingFang SC', AlibabaPuHuiTi !important; diff --git a/ui/src/workflow/nodes/intent-classify-node/index.ts b/ui/src/workflow/nodes/intent-classify-node/index.ts index c39167e9294..406a66f5278 100644 --- a/ui/src/workflow/nodes/intent-classify-node/index.ts +++ b/ui/src/workflow/nodes/intent-classify-node/index.ts @@ -48,11 +48,11 @@ class IntentModel extends AppNodeModel { if (branch_condition_list) { - const FORM_ITEMS_HEIGHT = 397 // 上方表单占用高度 - + const FORM_ITEMS_HEIGHT = 542 // 上方表单占用高度 + for (let index = 0; index < branch_condition_list.length; index++) { const element = branch_condition_list[index] - + anchors.push({ x: x + width / 2 - 10, y: showNode diff --git a/ui/src/workflow/nodes/intent-classify-node/index.vue b/ui/src/workflow/nodes/intent-classify-node/index.vue index 94078687bbc..2ca7af90902 100644 --- a/ui/src/workflow/nodes/intent-classify-node/index.vue +++ b/ui/src/workflow/nodes/intent-classify-node/index.vue @@ -76,6 +76,42 @@ v-model="form_data.model_id_reference" /> + + + +
+ +
+
+ {{ $t('workflow.nodes.intentNode.output_reason') }} +
+
+ +
+
+
@@ -304,10 +353,17 @@ const model_change = (model_id?: string) => { } } +const default_prompt_template = t('workflow.nodes.intentNode.default_prompt_template', { + classification_list: '{classification_list}', + user_input: '{user_input}', + output_json: '{output_json}', +}) + const form = { model_id: '', model_id_type: 'custom', model_id_reference: [], + prompt_template: default_prompt_template, branch: [ { id: randomId(), @@ -322,12 +378,17 @@ const form = { ], dialogue_number: 1, content_list: [], + output_reason: true, } function refreshParam(data: any) { set(props.nodeModel.properties.node_data, 'model_params_setting', data) } +function submitTemplateDialog(val: string) { + set(props.nodeModel.properties.node_data, 'prompt_template', val) +} + const openAIParamSettingDialog = (modelId: string) => { if (modelId) { AIModeParamSettingDialogRef.value?.open(modelId, id, form_data.value.model_params_setting) @@ -342,6 +403,9 @@ const form_data = computed({ if (!props.nodeModel.properties.node_data.model_id_reference) { set(props.nodeModel.properties.node_data, 'model_id_reference', []) } + if (!props.nodeModel.properties.node_data.prompt_template) { + set(props.nodeModel.properties.node_data, 'prompt_template', default_prompt_template) + } return props.nodeModel.properties.node_data } else { set(props.nodeModel.properties, 'node_data', form)