From dcd614ca7790bf7e9d6fd9e5b638ea8ef2853cba Mon Sep 17 00:00:00 2001 From: Novice Date: Tue, 24 Mar 2026 08:45:11 +0800 Subject: [PATCH] feat: add LLM quota deduction functionality and enhance model configuration handling in llm_utils.py; update test cases for LLM node context handling --- api/dify_graph/nodes/llm/llm_utils.py | 13 ++++++--- .../workflow/nodes/test_llm_node_streaming.py | 9 ++++++- web/i18n/en-US/workflow.json | 27 ++++++++++++++----- web/i18n/zh-Hans/workflow.json | 27 ++++++++++++++----- 4 files changed, 57 insertions(+), 19 deletions(-) diff --git a/api/dify_graph/nodes/llm/llm_utils.py b/api/dify_graph/nodes/llm/llm_utils.py index fbb80b9480..87aa645ed1 100644 --- a/api/dify_graph/nodes/llm/llm_utils.py +++ b/api/dify_graph/nodes/llm/llm_utils.py @@ -22,6 +22,7 @@ from dify_graph.model_runtime.entities import ( TextPromptMessageContent, ToolPromptMessage, ) +from dify_graph.model_runtime.entities.llm_entities import LLMUsage from dify_graph.model_runtime.entities.message_entities import ( AssistantPromptMessage, PromptMessageContentUnionTypes, @@ -37,7 +38,7 @@ from dify_graph.runtime import VariablePool from dify_graph.variables import ArrayFileSegment, FileSegment from dify_graph.variables.segments import ArrayAnySegment, NoneSegment, StringSegment -from .entities import LLMNodeChatModelMessage, LLMNodeCompletionModelPromptTemplate +from .entities import LLMNodeChatModelMessage, LLMNodeCompletionModelPromptTemplate, ModelConfig from .exc import ( InvalidVariableTypeError, MemoryRolePrefixRequiredError, @@ -47,9 +48,7 @@ from .exc import ( from .protocols import TemplateRenderer -def fetch_model_config( - *, tenant_id: str, node_data_model: ModelConfig -) -> tuple[ModelInstance, Any]: +def fetch_model_config(*, tenant_id: str, node_data_model: ModelConfig) -> tuple[ModelInstance, Any]: from core.app.llm.model_access import build_dify_model_access from core.app.llm.model_access import fetch_model_config as _fetch @@ -61,6 +60,12 @@ def fetch_model_config( ) +def deduct_llm_quota(*, tenant_id: str, model_instance: ModelInstance, usage: LLMUsage) -> None: + from core.app.llm.quota import deduct_llm_quota as _deduct + + _deduct(tenant_id=tenant_id, model_instance=model_instance, usage=usage) + + def fetch_model_schema(*, model_instance: ModelInstance) -> AIModelEntity: model_schema = cast(LargeLanguageModel, model_instance.model_type_instance).get_model_schema( model_instance.model_name, diff --git a/api/tests/unit_tests/core/workflow/nodes/test_llm_node_streaming.py b/api/tests/unit_tests/core/workflow/nodes/test_llm_node_streaming.py index 3204b349ec..4a6b104c28 100644 --- a/api/tests/unit_tests/core/workflow/nodes/test_llm_node_streaming.py +++ b/api/tests/unit_tests/core/workflow/nodes/test_llm_node_streaming.py @@ -35,7 +35,14 @@ def patch_deduct_llm_quota(monkeypatch): def _make_llm_node(reasoning_format: str) -> LLMNode: node = LLMNode.__new__(LLMNode) object.__setattr__(node, "_node_data", types.SimpleNamespace(reasoning_format=reasoning_format, tools=[])) - object.__setattr__(node, "tenant_id", "tenant") + object.__setattr__( + node, + "_run_context", + {"_dify": types.SimpleNamespace( + tenant_id="tenant", app_id="app", user_id="user", + user_from="account", invoke_from="debugger", + )}, + ) return node diff --git a/web/i18n/en-US/workflow.json b/web/i18n/en-US/workflow.json index 66a4359a88..d7948ed199 100644 --- a/web/i18n/en-US/workflow.json +++ b/web/i18n/en-US/workflow.json @@ -799,7 +799,8 @@ "nodes.llm.computerUse.title": "Computer Use", "nodes.llm.computerUse.tooltip": "Enable the model to interact with a computer desktop environment", "nodes.llm.context": "context", - "nodes.llm.contextMissing": "Context is missing", + "nodes.llm.contextBlock": "Context Block", + "nodes.llm.contextMissing": "Context missing: {{nodeName}}", "nodes.llm.contextTooltip": "You can import Knowledge as context", "nodes.llm.contextUnknownNode": "Unknown node", "nodes.llm.files": "Files", @@ -943,17 +944,22 @@ "nodes.templateTransform.codeSupportTip": "Only supports Jinja2", "nodes.templateTransform.inputVars": "Input Variables", "nodes.templateTransform.outputVars.output": "Transformed content", - "nodes.tool.agentPlaceholder": "Select an agent...", + "nodes.tool.agentPlaceholder": "Enter value for {{paramKey}}...", "nodes.tool.agentPopupHeader": "Select Agent", "nodes.tool.assembleVariables": "Assemble Variables", "nodes.tool.authorizationRequired": "Authorization required", "nodes.tool.authorize": "Authorize", "nodes.tool.contextGenerate.apply": "Apply", + "nodes.tool.contextGenerate.code": "Code", + "nodes.tool.contextGenerate.codeBlock": "Code Block", + "nodes.tool.contextGenerate.codeLanguage.javascript": "JavaScript", + "nodes.tool.contextGenerate.codeLanguage.python3": "Python 3", "nodes.tool.contextGenerate.defaultAssistantMessage": "I'll help you generate the context code.", "nodes.tool.contextGenerate.generatedCode": "Generated Code", "nodes.tool.contextGenerate.generating": "Generating...", "nodes.tool.contextGenerate.initPlaceholder": "Describe what you want to generate...", "nodes.tool.contextGenerate.inputPlaceholder": "Type your message...", + "nodes.tool.contextGenerate.instruction": "Instruction", "nodes.tool.contextGenerate.output": "Output", "nodes.tool.contextGenerate.resizeHandle": "Resize", "nodes.tool.contextGenerate.rightSidePlaceholder": "Generated content will appear here", @@ -1173,6 +1179,7 @@ "panel.scrollToSelectedNode": "Scroll to selected node", "panel.selectNextStep": "Select Next Step", "panel.startNode": "Start Node", + "panel.ungroup": "Ungroup", "panel.userInputField": "User Input Field", "publishLimit.startNodeDesc": "You’ve reached the limit of 2 triggers per workflow for this plan. Upgrade to publish this workflow.", "publishLimit.startNodeTitlePrefix": "Upgrade to", @@ -1193,7 +1200,7 @@ "singleRun.reRun": "Re-run", "singleRun.running": "Running", "singleRun.startRun": "Start Run", - "singleRun.subgraph.nullOutputError": "Subgraph returned null output", + "singleRun.subgraph.nullOutputError": "Sub-graph returned null for output: {{output}}", "singleRun.testRun": "Test Run", "singleRun.testRunIteration": "Test Run Iteration", "singleRun.testRunLoop": "Test Run Loop", @@ -1238,7 +1245,7 @@ "skillEditor.previewUnavailable": "Preview unavailable", "skillEditor.referenceFiles": "Reference Files", "skillEditor.toolMissing": "Tool Missing", - "skillEditor.toolMissingDesc": "The referenced tool is missing or has been removed", + "skillEditor.toolMissingDesc": "The referenced tool is missing. Go to Plugins to install it.", "skillEditor.unsupportedPreview": "Unsupported file type for preview", "skillEditor.uploadFiles": "Upload Files", "skillEditor.uploadIn": "Upload in", @@ -1306,16 +1313,22 @@ "skillSidebar.uploadSuccess": "Upload successful", "skillSidebar.uploadSuccessDetail": "{{count}} files uploaded", "skillSidebar.uploadingItems": "Uploading {{count}} items...", + "subGraphModal.canvasPlaceholder": "Select a node to view its details", + "subGraphModal.defaultValueHint": "Default value will be used when output is null", "subGraphModal.internalStructure": "Internal Structure", - "subGraphModal.internalStructureDesc": "View the internal workflow structure of this sub-graph", + "subGraphModal.internalStructureDesc": "View the internal workflow structure of {{name}}", + "subGraphModal.lastRun": "Last Run", "subGraphModal.noRunHistory": "No run history", "subGraphModal.outputVariables": "Output Variables", + "subGraphModal.settings": "Settings", + "subGraphModal.sourceNode": "Source Node", "subGraphModal.title": "Sub-Graph Details", "subGraphModal.whenOutputIsNone": "When Output is None", "subGraphModal.whenOutputNone.default": "Use Default", "subGraphModal.whenOutputNone.defaultDesc": "Use a default value when sub-graph output is null", "subGraphModal.whenOutputNone.error": "Throw Error", "subGraphModal.whenOutputNone.errorDesc": "Throw an error when sub-graph output is null", + "subGraphModal.whenOutputNone.skip": "Skip", "tabs.-": "Default", "tabs.addAll": "Add all", "tabs.agent": "Agent Strategy", @@ -1350,7 +1363,7 @@ "tabs.usePlugin": "Select tool", "tabs.utilities": "Utilities", "tabs.workflowTool": "Workflow", - "toolGroup.actionsEnabled": "{{count}} actions enabled", + "toolGroup.actionsEnabled": "{{num}} actions enabled", "toolGroup.byAuthor": "By {{author}}", "tracing.stopBy": "Stop by {{user}}", "triggerStatus.disabled": "TRIGGER • DISABLED", @@ -1364,7 +1377,7 @@ "versionHistory.action.deleteFailure": "Failed to delete version", "versionHistory.action.deleteSuccess": "Version deleted", "versionHistory.action.restoreFailure": "Failed to restore version", - "versionHistory.action.restoreInProgress": "Restoring...", + "versionHistory.action.restoreInProgress": "{{userName}} is restoring version {{versionName}}...", "versionHistory.action.restoreSuccess": "Version restored", "versionHistory.action.updateFailure": "Failed to update version", "versionHistory.action.updateSuccess": "Version updated", diff --git a/web/i18n/zh-Hans/workflow.json b/web/i18n/zh-Hans/workflow.json index 7e74f11f8b..b4600af538 100644 --- a/web/i18n/zh-Hans/workflow.json +++ b/web/i18n/zh-Hans/workflow.json @@ -799,7 +799,8 @@ "nodes.llm.computerUse.title": "计算机操作", "nodes.llm.computerUse.tooltip": "允许模型与计算机桌面环境交互", "nodes.llm.context": "上下文", - "nodes.llm.contextMissing": "缺少上下文", + "nodes.llm.contextBlock": "上下文块", + "nodes.llm.contextMissing": "缺少上下文:{{nodeName}}", "nodes.llm.contextTooltip": "您可以导入知识库作为上下文", "nodes.llm.contextUnknownNode": "未知节点", "nodes.llm.files": "文件", @@ -943,17 +944,22 @@ "nodes.templateTransform.codeSupportTip": "只支持 Jinja2", "nodes.templateTransform.inputVars": "输入变量", "nodes.templateTransform.outputVars.output": "转换后内容", - "nodes.tool.agentPlaceholder": "选择 Agent...", + "nodes.tool.agentPlaceholder": "输入 {{paramKey}} 的值...", "nodes.tool.agentPopupHeader": "选择 Agent", "nodes.tool.assembleVariables": "组装变量", "nodes.tool.authorizationRequired": "需要授权", "nodes.tool.authorize": "授权", "nodes.tool.contextGenerate.apply": "应用", + "nodes.tool.contextGenerate.code": "代码", + "nodes.tool.contextGenerate.codeBlock": "代码块", + "nodes.tool.contextGenerate.codeLanguage.javascript": "JavaScript", + "nodes.tool.contextGenerate.codeLanguage.python3": "Python 3", "nodes.tool.contextGenerate.defaultAssistantMessage": "我将帮你生成上下文代码。", "nodes.tool.contextGenerate.generatedCode": "生成的代码", "nodes.tool.contextGenerate.generating": "生成中...", "nodes.tool.contextGenerate.initPlaceholder": "描述你想要生成的内容...", "nodes.tool.contextGenerate.inputPlaceholder": "输入消息...", + "nodes.tool.contextGenerate.instruction": "指令", "nodes.tool.contextGenerate.output": "输出", "nodes.tool.contextGenerate.resizeHandle": "调整大小", "nodes.tool.contextGenerate.rightSidePlaceholder": "生成的内容将显示在这里", @@ -1173,6 +1179,7 @@ "panel.scrollToSelectedNode": "滚动至选中节点", "panel.selectNextStep": "选择下一个节点", "panel.startNode": "开始节点", + "panel.ungroup": "取消分组", "panel.userInputField": "用户输入字段", "publishLimit.startNodeDesc": "您已达到此计划上每个工作流最多 2 个触发器的限制。请升级后再发布此工作流。", "publishLimit.startNodeTitlePrefix": "升级以", @@ -1193,7 +1200,7 @@ "singleRun.reRun": "重新运行", "singleRun.running": "运行中", "singleRun.startRun": "开始运行", - "singleRun.subgraph.nullOutputError": "子图返回了空输出", + "singleRun.subgraph.nullOutputError": "子图的输出 {{output}} 返回了空值", "singleRun.testRun": "测试运行", "singleRun.testRunIteration": "测试运行迭代", "singleRun.testRunLoop": "测试运行循环", @@ -1238,7 +1245,7 @@ "skillEditor.previewUnavailable": "预览不可用", "skillEditor.referenceFiles": "引用文件", "skillEditor.toolMissing": "工具缺失", - "skillEditor.toolMissingDesc": "引用的工具缺失或已被移除", + "skillEditor.toolMissingDesc": "引用的工具缺失或已被移除。前往插件安装。", "skillEditor.unsupportedPreview": "不支持预览的文件类型", "skillEditor.uploadFiles": "上传文件", "skillEditor.uploadIn": "上传至", @@ -1306,16 +1313,22 @@ "skillSidebar.uploadSuccess": "上传成功", "skillSidebar.uploadSuccessDetail": "已上传 {{count}} 个文件", "skillSidebar.uploadingItems": "正在上传 {{count}} 个项目...", + "subGraphModal.canvasPlaceholder": "选择一个节点以查看其详情", + "subGraphModal.defaultValueHint": "输出为空时将使用默认值", "subGraphModal.internalStructure": "内部结构", - "subGraphModal.internalStructureDesc": "查看此子图的内部工作流结构", + "subGraphModal.internalStructureDesc": "查看 {{name}} 的内部工作流结构", + "subGraphModal.lastRun": "上次运行", "subGraphModal.noRunHistory": "暂无运行历史", "subGraphModal.outputVariables": "输出变量", + "subGraphModal.settings": "设置", + "subGraphModal.sourceNode": "源节点", "subGraphModal.title": "子图详情", "subGraphModal.whenOutputIsNone": "当输出为空时", "subGraphModal.whenOutputNone.default": "使用默认值", "subGraphModal.whenOutputNone.defaultDesc": "当子图输出为空时使用默认值", "subGraphModal.whenOutputNone.error": "抛出错误", "subGraphModal.whenOutputNone.errorDesc": "当子图输出为空时抛出错误", + "subGraphModal.whenOutputNone.skip": "跳过", "tabs.-": "默认", "tabs.addAll": "添加全部", "tabs.agent": "Agent 策略", @@ -1350,7 +1363,7 @@ "tabs.usePlugin": "选择工具", "tabs.utilities": "工具", "tabs.workflowTool": "工作流", - "toolGroup.actionsEnabled": "已启用 {{count}} 个操作", + "toolGroup.actionsEnabled": "已启用 {{num}} 个操作", "toolGroup.byAuthor": "作者 {{author}}", "tracing.stopBy": "由{{user}}终止", "triggerStatus.disabled": "触发器 • 已禁用", @@ -1364,7 +1377,7 @@ "versionHistory.action.deleteFailure": "删除失败", "versionHistory.action.deleteSuccess": "版本已删除", "versionHistory.action.restoreFailure": "回滚失败", - "versionHistory.action.restoreInProgress": "恢复中...", + "versionHistory.action.restoreInProgress": "{{userName}} 正在恢复版本 {{versionName}}...", "versionHistory.action.restoreSuccess": "回滚成功", "versionHistory.action.updateFailure": "更新失败", "versionHistory.action.updateSuccess": "版本信息已更新",