diff --git a/docs.json b/docs.json
index 6ce81286..e3f2041c 100644
--- a/docs.json
+++ b/docs.json
@@ -1,2594 +1,2836 @@
{
- "$schema": "https://mintlify.com/docs.json",
- "theme": "mint",
- "name": "Dify Docs",
- "colors": {
- "primary": "#346DDB",
- "light": "#688FE8",
- "dark": "#346DDB"
- },
- "favicon": "/dify-logo.png",
- "logo": {
- "light": "/logo/dify-logo-black.svg",
- "dark": "/logo/dify-logo-white.svg"
- },
- "navigation": {
- "versions": [
- {
- "version": "English",
- "tabs": [
- {
- "tab": "Documentation",
- "groups": [
- {
- "group": "Getting Started",
- "pages": [
- {
- "group": "Welcome to Dify",
- "pages": [
- "en/introduction",
- "en/getting-started/readme/features-and-specifications",
- "en/getting-started/readme/model-providers"
- ]
- },
- {
- "group": "Dify Community",
- "pages": [
- "en/getting-started/install-self-hosted/readme",
- "en/getting-started/install-self-hosted/docker-compose",
- "en/getting-started/install-self-hosted/local-source-code",
- "en/getting-started/install-self-hosted/aa-panel",
- "en/getting-started/install-self-hosted/start-the-frontend-docker-container",
- "en/getting-started/install-self-hosted/environments",
- "en/getting-started/install-self-hosted/faqs"
- ]
- },
- "en/getting-started/cloud",
- "en/getting-started/dify-premium",
- "en/getting-started/dify-for-education"
- ]
- },
- {
- "group": "Guide",
- "pages": [
- {
- "group": "Model Configuration",
- "pages": [
- "en/guides/model-configuration/readme",
- "en/guides/model-configuration/new-provider",
- "en/guides/model-configuration/predefined-model",
- "en/guides/model-configuration/customizable-model",
- "en/guides/model-configuration/interfaces",
- "en/guides/model-configuration/schema",
- "en/guides/model-configuration/load-balancing"
- ]
- },
- {
- "group": "Application Orchestration",
- "pages": [
- "en/guides/application-orchestrate/readme",
- "en/guides/application-orchestrate/creating-an-application",
- "en/guides/application-orchestrate/chatbot-application",
- "en/guides/application-orchestrate/multiple-llms-debugging",
- "en/guides/application-orchestrate/text-generator",
- "en/guides/application-orchestrate/agent",
- {
- "group": "Application Toolkits",
- "pages": [
- "en/guides/application-orchestrate/app-toolkits/readme",
- "en/guides/application-orchestrate/app-toolkits/moderation-tool"
- ]
- }
- ]
- },
- {
- "group": "Workflow",
- "pages": [
- "en/guides/workflow/README",
- "en/guides/workflow/key-concepts",
- "en/guides/workflow/variables",
- {
- "group": "Node Description",
- "pages": [
- "en/guides/workflow/node/start",
- "en/guides/workflow/node/end",
- "en/guides/workflow/node/answer",
- "en/guides/workflow/node/llm",
- "en/guides/workflow/node/knowledge-retrieval",
- "en/guides/workflow/node/question-classifier",
- "en/guides/workflow/node/ifelse",
- "en/guides/workflow/node/code",
- "en/guides/workflow/node/template",
- "en/guides/workflow/node/doc-extractor",
- "en/guides/workflow/node/list-operator",
- "en/guides/workflow/node/variable-aggregator",
- "en/guides/workflow/node/variable-assigner",
- "en/guides/workflow/node/iteration",
- "en/guides/workflow/node/parameter-extractor",
- "en/guides/workflow/node/http-request",
- "en/guides/workflow/node/agent",
- "en/guides/workflow/node/tools",
- "en/guides/workflow/node/loop"
- ]
- },
- "en/guides/workflow/shortcut-key",
- "en/guides/workflow/orchestrate-node",
- "en/guides/workflow/file-upload",
- {
- "group": "Error Handling",
- "pages": [
- "en/guides/workflow/error-handling/readme",
- "en/guides/workflow/error-handling/predefined-error-handling-logic",
- "en/guides/workflow/error-handling/error-type"
- ]
- },
- "en/guides/workflow/additional-features",
- {
- "group": "Debug and Preview",
- "pages": [
- "en/guides/workflow/debug-and-preview/preview-and-run",
- "en/guides/workflow/debug-and-preview/step-run",
- "en/guides/workflow/debug-and-preview/log",
- "en/guides/workflow/debug-and-preview/checklist",
- "en/guides/workflow/debug-and-preview/history"
- ]
- },
- "en/guides/workflow/publish",
- "en/guides/workflow/structured-outputs",
- "en/guides/workflow/bulletin"
- ]
- },
- {
- "group": "Knowledge",
- "pages": [
- "en/guides/knowledge-base/readme",
- {
- "group": "Create Knowledge",
- "pages": [
- "en/guides/knowledge-base/knowledge-base-creation/introduction",
- {
- "group": "1. Import Text Data",
- "pages": [
- "en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/readme",
- "en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-notion",
- "en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website"
- ]
- },
- "en/guides/knowledge-base/create-knowledge-and-upload-documents/chunking-and-cleaning-text",
- "en/guides/knowledge-base/create-knowledge-and-upload-documents/setting-indexing-methods"
- ]
- },
- {
- "group": "Manage Knowledge",
- "pages": [
- "en/guides/knowledge-base/knowledge-and-documents-maintenance/introduction",
- "en/guides/knowledge-base/knowledge-and-documents-maintenance/maintain-knowledge-documents",
- "en/guides/knowledge-base/knowledge-and-documents-maintenance/maintain-dataset-via-api"
- ]
- },
- "en/guides/knowledge-base/metadata",
- "en/guides/knowledge-base/integrate-knowledge-within-application",
- "en/guides/knowledge-base/retrieval-test-and-citation",
- "en/guides/knowledge-base/knowledge-request-rate-limit",
- "en/guides/knowledge-base/connect-external-knowledge-base",
- "en/guides/knowledge-base/external-knowledge-api"
- ]
- },
- {
- "group": "Publishing",
+ "$schema": "https://mintlify.com/docs.json",
+ "theme": "mint",
+ "name": "Dify Docs",
+ "colors": {
+ "primary": "#346DDB",
+ "light": "#688FE8",
+ "dark": "#346DDB"
+ },
+ "favicon": "/dify-logo.png",
+ "logo": {
+ "light": "/logo/dify-logo-black.svg",
+ "dark": "/logo/dify-logo-white.svg"
+ },
+ "navigation": {
+ "versions": [
+ {
+ "version": "English",
+ "tabs": [
+ {
+ "tab": "Documentation",
+ "groups": [
+ {
+ "group": "Getting Started",
+ "pages": [
+ {
+ "group": "Welcome to Dify",
+ "pages": [
+ "en/introduction",
+ "en/getting-started/readme/features-and-specifications",
+ "en/getting-started/readme/model-providers"
+ ]
+ },
+ {
+ "group": "Dify Community",
+ "pages": [
+ "en/getting-started/install-self-hosted/readme",
+ "en/getting-started/install-self-hosted/docker-compose",
+ "en/getting-started/install-self-hosted/local-source-code",
+ "en/getting-started/install-self-hosted/aa-panel",
+ "en/getting-started/install-self-hosted/start-the-frontend-docker-container",
+ "en/getting-started/install-self-hosted/environments",
+ "en/getting-started/install-self-hosted/faqs"
+ ]
+ },
+ "en/getting-started/cloud",
+ "en/getting-started/dify-premium",
+ "en/getting-started/dify-for-education"
+ ]
+ },
+ {
+ "group": "Guide",
+ "pages": [
+ {
+ "group": "Model Configuration",
+ "pages": [
+ "en/guides/model-configuration/readme",
+ "en/guides/model-configuration/new-provider",
+ "en/guides/model-configuration/predefined-model",
+ "en/guides/model-configuration/customizable-model",
+ "en/guides/model-configuration/interfaces",
+ "en/guides/model-configuration/schema",
+ "en/guides/model-configuration/load-balancing"
+ ]
+ },
+ {
+ "group": "Application Orchestration",
+ "pages": [
+ "en/guides/application-orchestrate/readme",
+ "en/guides/application-orchestrate/creating-an-application",
+ "en/guides/application-orchestrate/chatbot-application",
+ "en/guides/application-orchestrate/multiple-llms-debugging",
+ "en/guides/application-orchestrate/text-generator",
+ "en/guides/application-orchestrate/agent",
+ {
+ "group": "Application Toolkits",
+ "pages": [
+ "en/guides/application-orchestrate/app-toolkits/readme",
+ "en/guides/application-orchestrate/app-toolkits/moderation-tool"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "Workflow",
+ "pages": [
+ "en/guides/workflow/README",
+ "en/guides/workflow/key-concepts",
+ "en/guides/workflow/variables",
+ {
+ "group": "Node Description",
+ "pages": [
+ "en/guides/workflow/node/start",
+ "en/guides/workflow/node/end",
+ "en/guides/workflow/node/answer",
+ "en/guides/workflow/node/llm",
+ "en/guides/workflow/node/knowledge-retrieval",
+ "en/guides/workflow/node/question-classifier",
+ "en/guides/workflow/node/ifelse",
+ "en/guides/workflow/node/code",
+ "en/guides/workflow/node/template",
+ "en/guides/workflow/node/doc-extractor",
+ "en/guides/workflow/node/list-operator",
+ "en/guides/workflow/node/variable-aggregator",
+ "en/guides/workflow/node/variable-assigner",
+ "en/guides/workflow/node/iteration",
+ "en/guides/workflow/node/parameter-extractor",
+ "en/guides/workflow/node/http-request",
+ "en/guides/workflow/node/agent",
+ "en/guides/workflow/node/tools",
+ "en/guides/workflow/node/loop"
+ ]
+ },
+ "en/guides/workflow/shortcut-key",
+ "en/guides/workflow/orchestrate-node",
+ "en/guides/workflow/file-upload",
+ {
+ "group": "Error Handling",
+ "pages": [
+ "en/guides/workflow/error-handling/readme",
+ "en/guides/workflow/error-handling/predefined-error-handling-logic",
+ "en/guides/workflow/error-handling/error-type"
+ ]
+ },
+ "en/guides/workflow/additional-features",
+ {
+ "group": "Debug and Preview",
+ "pages": [
+ "en/guides/workflow/debug-and-preview/preview-and-run",
+ "en/guides/workflow/debug-and-preview/step-run",
+ "en/guides/workflow/debug-and-preview/log",
+ "en/guides/workflow/debug-and-preview/checklist",
+ "en/guides/workflow/debug-and-preview/history"
+ ]
+ },
+ "en/guides/workflow/publish",
+ "en/guides/workflow/structured-outputs",
+ "en/guides/workflow/bulletin"
+ ]
+ },
+ {
+ "group": "Knowledge",
+ "pages": [
+ "en/guides/knowledge-base/readme",
+ {
+ "group": "Create Knowledge",
+ "pages": [
+ "en/guides/knowledge-base/knowledge-base-creation/introduction",
+ {
+ "group": "1. Import Text Data",
+ "pages": [
+ "en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/readme",
+ "en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-notion",
+ "en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website"
+ ]
+ },
+ "en/guides/knowledge-base/create-knowledge-and-upload-documents/chunking-and-cleaning-text",
+ "en/guides/knowledge-base/create-knowledge-and-upload-documents/setting-indexing-methods"
+ ]
+ },
+ {
+ "group": "Manage Knowledge",
+ "pages": [
+ "en/guides/knowledge-base/knowledge-and-documents-maintenance/introduction",
+ "en/guides/knowledge-base/knowledge-and-documents-maintenance/maintain-knowledge-documents",
+ "en/guides/knowledge-base/knowledge-and-documents-maintenance/maintain-dataset-via-api"
+ ]
+ },
+ "en/guides/knowledge-base/metadata",
+ "en/guides/knowledge-base/integrate-knowledge-within-application",
+ "en/guides/knowledge-base/retrieval-test-and-citation",
+ "en/guides/knowledge-base/knowledge-request-rate-limit",
+ "en/guides/knowledge-base/connect-external-knowledge-base",
+ "en/guides/knowledge-base/external-knowledge-api"
+ ]
+ },
+ {
+ "group": "Publishing",
"pages": [ {
- "group": "Publish as a Single-page Web App",
- "pages": [
- "en/guides/application-publishing/launch-your-webapp-quickly/README",
- "en/guides/application-publishing/launch-your-webapp-quickly/web-app-settings",
- "en/guides/application-publishing/launch-your-webapp-quickly/text-generator",
- "en/guides/application-publishing/launch-your-webapp-quickly/conversation-application"
- ]
- },
- "en/guides/application-publishing/embedding-in-websites",
- "en/guides/application-publishing/developing-with-apis",
- "en/guides/application-publishing/based-on-frontend-templates"
- ]
- },
- {
- "group": "Annotation",
- "pages": [
- "en/guides/annotation/logs",
- "en/guides/annotation/annotation-reply"
- ]
- },
- {
- "group": "Monitoring",
- "pages": [
- "en/guides/monitoring/README",
- "en/guides/monitoring/analysis",
- {
- "group": "Integrate External Ops Tools",
- "pages": [
- "en/guides/monitoring/integrate-external-ops-tools/README",
- "en/guides/monitoring/integrate-external-ops-tools/integrate-langsmith",
- "en/guides/monitoring/integrate-external-ops-tools/integrate-langfuse",
- "en/guides/monitoring/integrate-external-ops-tools/integrate-opik",
- "en/guides/monitoring/integrate-external-ops-tools/integrate-weave"
- ]
- }
- ]
- },
- {
- "group": "Extensions",
- "pages": [
- {
- "group": "API-Based Extension",
- "pages": [
- "en/guides/extension/api-based-extension/README",
- "en/guides/extension/api-based-extension/external-data-tool",
- "en/guides/extension/api-based-extension/cloudflare-workers",
- "en/guides/extension/api-based-extension/moderation"
- ]
- },
- {
- "group": "Code-Based Extension",
- "pages": [
- "en/guides/extension/code-based-extension/README",
- "en/guides/extension/code-based-extension/external-data-tool",
- "en/guides/extension/code-based-extension/moderation"
- ]
- }
- ]
- },
- {
- "group": "Collaboration",
- "pages": [
- "en/guides/workspace/README",
- "en/guides/workspace/app",
- "en/guides/workspace/invite-and-manage-members"
- ]
- },
- {
- "group": "Management",
- "pages": [
- "en/guides/management/app-management",
- "en/guides/management/team-members-management",
- "en/guides/management/personal-account-management",
- "en/guides/management/subscription-management",
- "en/guides/management/version-control"
- ]
- }
- ]
- },
- {
- "group": "Workshop",
- "pages": [
- "en/workshop/README",
- {
- "group": "Basic",
- "pages": [
- "en/workshop/basic/build-ai-image-generation-app"
- ]
- },
- {
- "group": "Intermediate",
- "pages": [
- "en/workshop/intermediate/article-reader",
- "en/workshop/intermediate/customer-service-bot",
- "en/workshop/intermediate/twitter-chatflow"
+ "group": "Publish as a Single-page Web App",
+ "pages": [
+ "en/guides/application-publishing/launch-your-webapp-quickly/README",
+ "en/guides/application-publishing/launch-your-webapp-quickly/web-app-settings",
+ "en/guides/application-publishing/launch-your-webapp-quickly/text-generator",
+ "en/guides/application-publishing/launch-your-webapp-quickly/conversation-application"
+ ]
+ },
+ "en/guides/application-publishing/embedding-in-websites",
+ "en/guides/application-publishing/developing-with-apis",
+ "en/guides/application-publishing/based-on-frontend-templates"
+ ]
+ },
+ {
+ "group": "Annotation",
+ "pages": [
+ "en/guides/annotation/logs",
+ "en/guides/annotation/annotation-reply"
+ ]
+ },
+ {
+ "group": "Monitoring",
+ "pages": [
+ "en/guides/monitoring/README",
+ "en/guides/monitoring/analysis",
+ {
+ "group": "Integrate External Ops Tools",
+ "pages": [
+ "en/guides/monitoring/integrate-external-ops-tools/README",
+ "en/guides/monitoring/integrate-external-ops-tools/integrate-langsmith",
+ "en/guides/monitoring/integrate-external-ops-tools/integrate-langfuse",
+ "en/guides/monitoring/integrate-external-ops-tools/integrate-opik",
+ "en/guides/monitoring/integrate-external-ops-tools/integrate-weave"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "Extensions",
+ "pages": [
+ {
+ "group": "API-Based Extension",
+ "pages": [
+ "en/guides/extension/api-based-extension/README",
+ "en/guides/extension/api-based-extension/external-data-tool",
+ "en/guides/extension/api-based-extension/cloudflare-workers",
+ "en/guides/extension/api-based-extension/moderation"
+ ]
+ },
+ {
+ "group": "Code-Based Extension",
+ "pages": [
+ "en/guides/extension/code-based-extension/README",
+ "en/guides/extension/code-based-extension/external-data-tool",
+ "en/guides/extension/code-based-extension/moderation"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "Collaboration",
+ "pages": [
+ "en/guides/workspace/README",
+ "en/guides/workspace/app",
+ "en/guides/workspace/invite-and-manage-members"
+ ]
+ },
+ {
+ "group": "Management",
+ "pages": [
+ "en/guides/management/app-management",
+ "en/guides/management/team-members-management",
+ "en/guides/management/personal-account-management",
+ "en/guides/management/subscription-management",
+ "en/guides/management/version-control"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "Workshop",
+ "pages": [
+ "en/workshop/README",
+ {
+ "group": "Basic",
+ "pages": [
+ "en/workshop/basic/build-ai-image-generation-app"
+ ]
+ },
+ {
+ "group": "Intermediate",
+ "pages": [
+ "en/workshop/intermediate/article-reader",
+ "en/workshop/intermediate/customer-service-bot",
+ "en/workshop/intermediate/twitter-chatflow"
- ]
- }
- ]
- },
- {
- "group": "Community",
- "pages": [
- "en/community/support",
- "en/community/contribution",
- "en/community/docs-contribution"
- ]
- },
- {
- "group": "Plugins",
- "pages": [
- "en/plugins/introduction",
- {
- "group": "Quick Start",
- "pages": [
- "en/plugins/quick-start/README",
- "en/plugins/quick-start/install-plugins",
- {
- "group": "Develop Plugins",
- "pages": [
- "en/plugins/quick-start/develop-plugins/README",
- "en/plugins/quick-start/develop-plugins/initialize-development-tools",
- "en/plugins/quick-start/develop-plugins/tool-plugin",
- {
- "group": "Model Plugin",
- "pages": [
- "en/plugins/quick-start/develop-plugins/model-plugin/README",
- "en/plugins/quick-start/develop-plugins/model-plugin/create-model-providers",
- "en/plugins/quick-start/develop-plugins/model-plugin/predefined-model",
- "en/plugins/quick-start/develop-plugins/model-plugin/customizable-model"
- ]
- },
- "en/plugins/quick-start/develop-plugins/agent-strategy-plugin",
- "en/plugins/quick-start/develop-plugins/extension-plugin",
- "en/plugins/quick-start/develop-plugins/bundle"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "Community",
+ "pages": [
+ "en/community/support",
+ "en/community/contribution",
+ "en/community/docs-contribution"
+ ]
+ },
+ {
+ "group": "Plugins",
+ "pages": [
+ "en/plugins/introduction",
+ {
+ "group": "Quick Start",
+ "pages": [
+ "en/plugins/quick-start/README",
+ "en/plugins/quick-start/install-plugins",
+ {
+ "group": "Develop Plugins",
+ "pages": [
+ "en/plugins/quick-start/develop-plugins/README",
+ "en/plugins/quick-start/develop-plugins/initialize-development-tools",
+ "en/plugins/quick-start/develop-plugins/tool-plugin",
+ {
+ "group": "Model Plugin",
+ "pages": [
+ "en/plugins/quick-start/develop-plugins/model-plugin/README",
+ "en/plugins/quick-start/develop-plugins/model-plugin/create-model-providers",
+ "en/plugins/quick-start/develop-plugins/model-plugin/predefined-model",
+ "en/plugins/quick-start/develop-plugins/model-plugin/customizable-model"
+ ]
+ },
+ "en/plugins/quick-start/develop-plugins/agent-strategy-plugin",
+ "en/plugins/quick-start/develop-plugins/extension-plugin",
+ "en/plugins/quick-start/develop-plugins/bundle"
+ ]
+ },
+ "en/plugins/quick-start/debug-plugin"
+ ]
+ },
+ "en/plugins/manage-plugins",
+ {
+ "group": "Schema Specification",
+ "pages": [
+ "en/plugins/schema-definition/manifest",
+ "en/plugins/schema-definition/endpoint",
+ "en/plugins/schema-definition/tool",
+ "en/plugins/schema-definition/agent",
+ {
+ "group": "Model",
+ "pages": [
+ "en/plugins/schema-definition/model/model-designing-rules",
+ "en/plugins/schema-definition/model/model-schema"
+ ]
+ },
+ "en/plugins/schema-definition/general-specifications",
+ "en/plugins/schema-definition/persistent-storage",
+ {
+ "group": "Reverse Invocation of the Dify Service",
+ "pages": [
+ "en/plugins/schema-definition/reverse-invocation-of-the-dify-service/README",
+ "en/plugins/schema-definition/reverse-invocation-of-the-dify-service/app",
+ "en/plugins/schema-definition/reverse-invocation-of-the-dify-service/model",
+ "en/plugins/schema-definition/reverse-invocation-of-the-dify-service/tool",
+ "en/plugins/schema-definition/reverse-invocation-of-the-dify-service/node"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "Best Practice",
+ "pages": [
+ "en/plugins/best-practice/README",
+ "en/plugins/best-practice/develop-a-slack-bot-plugin",
+ "en/plugins/best-practice/how-to-use-mcp-zapier"
+ ]
+ },
+ {
+ "group": "Publish Plugins",
+ "pages": [
+ "en/plugins/publish-plugins/README",
+ "en/plugins/publish-plugins/plugin-auto-publish-pr",
+ {
+ "group": "Publish to Dify Marketplace",
+ "pages": [
+ "en/plugins/publish-plugins/publish-to-dify-marketplace/README",
+ "en/plugins/publish-plugins/publish-to-dify-marketplace/plugin-developer-guidelines",
+ "en/plugins/publish-plugins/publish-to-dify-marketplace/plugin-privacy-protection-guidelines"
+ ]
+ },
+ "en/plugins/publish-plugins/publish-plugin-on-personal-github-repo",
+ "en/plugins/publish-plugins/package-plugin-file-and-publish",
+ "en/plugins/publish-plugins/signing-plugins-for-third-party-signature-verification"
+ ]
+ },
+ "en/plugins/faq"
+ ]
+ },
+ {
+ "group": "Development",
+ "pages": [
+ {
+ "group": "Backend",
+ "pages": [
+ "en/development/backend/sandbox/README",
+ "en/development/backend/sandbox/contribution"
+ ]
+ },
+ {
+ "group": "Models Integration",
+ "pages": [
+ "en/development/models-integration/hugging-face",
+ "en/development/models-integration/replicate",
+ "en/development/models-integration/xinference",
+ "en/development/models-integration/openllm",
+ "en/development/models-integration/localai",
+ "en/development/models-integration/ollama",
+ "en/development/models-integration/litellm",
+ "en/development/models-integration/gpustack",
+ "en/development/models-integration/aws-bedrock-deepseek"
+ ]
+ },
+ {
+ "group": "Migration",
+ "pages": [
+ "en/development/migration/migrate-to-v1"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "Learn More",
+ "pages": [
+ {
+ "group": "Use Cases",
+ "pages": [
+ "en/learn-more/use-cases/integrate-deepseek-to-build-an-ai-app",
+ "en/learn-more/use-cases/private-ai-ollama-deepseek-dify",
+ "en/learn-more/use-cases/build-an-notion-ai-assistant",
+ "en/learn-more/use-cases/create-a-midjourney-prompt-bot-with-dify",
+ "en/learn-more/use-cases/create-an-ai-chatbot-with-business-data-in-minutes",
+ "en/learn-more/use-cases/how-to-integrate-dify-chatbot-to-your-wix-website",
+ "en/learn-more/use-cases/how-to-connect-aws-bedrock",
+ "en/learn-more/use-cases/dify-schedule",
+ "en/learn-more/use-cases/building-an-ai-thesis-slack-bot"
+ ]
+ },
+ {
+ "group": "Extended Reading",
+ "pages": [
+ "en/learn-more/extended-reading/what-is-llmops",
+ {
+ "group": "Retrieval-Augmented Generation (RAG)",
+ "pages": [
+ "en/learn-more/extended-reading/retrieval-augment/README",
+ "en/learn-more/extended-reading/retrieval-augment/hybrid-search",
+ "en/learn-more/extended-reading/retrieval-augment/rerank",
+ "en/learn-more/extended-reading/retrieval-augment/retrieval"
+ ]
+ },
+ "en/learn-more/extended-reading/how-to-use-json-schema-in-dify"
+ ]
+ },
+ {
+ "group": "FAQ",
+ "pages": [
+ "en/learn-more/faq/install-faq",
+ "en/learn-more/faq/use-llms-faq",
+ "en/learn-more/faq/plugins"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "Policies",
+ "pages": [
+ "en/policies/open-source",
+ {
+ "group": "User Agreement",
+ "pages": [
+ "en/policies/agreement/README",
+ "en/policies/agreement/tos",
+ "en/policies/agreement/privacy",
+ "en/policies/agreement/get-compliance-report"
+ ]
+ }
+ ]
+ }
]
- },
- "en/plugins/quick-start/debug-plugin"
- ]
- },
- "en/plugins/manage-plugins",
- {
- "group": "Schema Specification",
- "pages": [
- "en/plugins/schema-definition/manifest",
- "en/plugins/schema-definition/endpoint",
- "en/plugins/schema-definition/tool",
- "en/plugins/schema-definition/agent",
- {
- "group": "Model",
+ },
+ {
+ "tab": "Termbase",
"pages": [
- "en/plugins/schema-definition/model/model-designing-rules",
- "en/plugins/schema-definition/model/model-schema"
+ "en/termbase/termbase"
]
- },
- "en/plugins/schema-definition/general-specifications",
- "en/plugins/schema-definition/persistent-storage",
- {
- "group": "Reverse Invocation of the Dify Service",
+ },
+ {
+ "tab": "Plugin Development",
+ "groups": [
+ {
+ "group": "Concepts & Getting Started",
+ "pages": [
+ {
+ "group": "Overview",
+ "pages": [
+ "plugin_dev_en/0111-getting-started-dify-plugin.en"
+ ]
+ },
+ "plugin_dev_en/0131-cheatsheet.en"
+ ]
+ },
+ {
+ "group": "Development Practices",
+ "pages": [
+ {
+ "group": "Quick Start",
+ "pages": [
+ "plugin_dev_en/0211-getting-started-by-prompt.en",
+ "plugin_dev_en/0211-getting-started-dify-tool.en",
+ "plugin_dev_en/0211-getting-started-new-model.en"
+ ]
+ },
+ {
+ "group": "Developing Dify Plugins",
+ "pages": [
+ "plugin_dev_en/0221-initialize-development-tools.en",
+ "plugin_dev_en/0222-creating-new-model-provider-extra.en",
+ "plugin_dev_en/0222-creating-new-model-provider.en",
+ "plugin_dev_en/0222-tool-plugin.en"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "Contribution & Publishing",
+ "pages": [
+ {
+ "group": "Code of Conduct & Standards",
+ "pages": [
+ "plugin_dev_en/0312-contributor-covenant-code-of-conduct.en",
+ "plugin_dev_en/0312-privacy-protection-guidelines.en"
+ ]
+ },
+ {
+ "group": "Publishing & Listing",
+ "pages": [
+ "plugin_dev_en/0321-release-overview.en",
+ "plugin_dev_en/0322-release-by-file.en",
+ "plugin_dev_en/0322-release-to-dify-marketplace.en",
+ "plugin_dev_en/0322-release-to-individual-github-repo.en"
+ ]
+ },
+ {
+ "group": "FAQ",
+ "pages": [
+ "plugin_dev_en/0331-faq.en"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "Examples & Use Cases",
+ "pages": [
+ {
+ "group": "Development Examples",
+ "pages": [
+ "plugin_dev_en/0432-develop-a-slack-bot-plugin.en",
+ "plugin_dev_en/0432-endpoint.en"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "Advanced Development",
+ "pages": [
+ {
+ "group": "Extension & Agent",
+ "pages": [
+ "plugin_dev_en/9231-extension-plugin.en",
+ "plugin_dev_en/9232-agent.en",
+ "plugin_dev_en/9433-agent-strategy-plugin.en"
+ ]
+ },
+ {
+ "group": "Reverse Calling",
+ "pages": [
+ "plugin_dev_en/9241-bundle.en",
+ "plugin_dev_en/9241-reverse-invocation.en",
+ "plugin_dev_en/9242-reverse-invocation-app.en",
+ "plugin_dev_en/9242-reverse-invocation-model.en",
+ "plugin_dev_en/9242-reverse-invocation-tool.en",
+ "plugin_dev_en/9243-customizable-model.en",
+ "plugin_dev_en/9243-reverse-invocation-node.en"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "Reference & Specifications",
+ "pages": [
+ {
+ "group": "Core Specifications & Features",
+ "pages": [
+ "plugin_dev_en/0411-general-specifications.en",
+ "plugin_dev_en/0411-model-designing-rules.en",
+ "plugin_dev_en/0411-model-plugin-introduction.en",
+ "plugin_dev_en/0411-persistent-storage-kv.en",
+ "plugin_dev_en/0411-plugin-info-by-manifest.en",
+ "plugin_dev_en/0411-remote-debug-a-plugin.en",
+ "plugin_dev_en/0411-tool.en",
+ "plugin_dev_en/0412-model-schema.en"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "version": "简体中文",
+ "tabs": [
+ {
+ "tab": "使用文档",
+ "groups": [
+ {
+ "group": "入门",
+ "pages": [
+ {
+ "group": "欢迎使用 Dify",
+ "pages": [
+ "zh-hans/introduction",
+ "zh-hans/getting-started/readme/features-and-specifications",
+ "zh-hans/getting-started/readme/model-providers"
+ ]
+ },
+ "zh-hans/getting-started/cloud",
+ {
+ "group": "Dify 社区版",
+ "pages": [
+ "zh-hans/getting-started/install-self-hosted/readme",
+ "zh-hans/getting-started/install-self-hosted/docker-compose",
+ "zh-hans/getting-started/install-self-hosted/local-source-code",
+ "zh-hans/getting-started/install-self-hosted/bt-panel",
+ "zh-hans/getting-started/install-self-hosted/start-the-frontend-docker-container",
+ "zh-hans/getting-started/install-self-hosted/environments",
+ "zh-hans/getting-started/install-self-hosted/faq"
+ ]
+ },
+ "zh-hans/getting-started/dify-premium",
+ "zh-hans/getting-started/dify-for-education"
+ ]
+ },
+ {
+ "group": "手册",
+ "pages": [
+ {
+ "group": "接入大模型",
+ "pages": [
+ "zh-hans/guides/model-configuration/readme",
+ "zh-hans/guides/model-configuration/new-provider",
+ "zh-hans/guides/model-configuration/predefined-model",
+ "zh-hans/guides/model-configuration/customizable-model",
+ "zh-hans/guides/model-configuration/interfaces",
+ "zh-hans/guides/model-configuration/schema",
+ "zh-hans/guides/model-configuration/load-balancing"
+ ]
+ },
+ {
+ "group": "构建应用",
+ "pages": [
+ "zh-hans/guides/application-orchestrate/readme",
+ "zh-hans/guides/application-orchestrate/creating-an-application",
+ {
+ "group": "聊天助手",
+ "pages": [
+ "zh-hans/guides/application-orchestrate/chatbot-application",
+ "zh-hans/guides/application-orchestrate/multiple-llms-debugging"
+ ]
+ },
+ "zh-hans/guides/application-orchestrate/agent",
+ {
+ "group": "应用工具箱",
+ "pages": [
+ "zh-hans/guides/application-orchestrate/app-toolkits/readme",
+ "zh-hans/guides/application-orchestrate/app-toolkits/moderation-tool"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "工作流",
+ "pages": [
+ "zh-hans/guides/workflow/readme",
+ "zh-hans/guides/workflow/key-concept",
+ "zh-hans/guides/workflow/variables",
+ {
+ "group": "节点说明",
+ "pages": [
+ "zh-hans/guides/workflow/node/start",
+ "zh-hans/guides/workflow/node/llm",
+ "zh-hans/guides/workflow/node/knowledge-retrieval",
+ "zh-hans/guides/workflow/node/question-classifier",
+ "zh-hans/guides/workflow/node/ifelse",
+ "zh-hans/guides/workflow/node/code",
+ "zh-hans/guides/workflow/node/template",
+ "zh-hans/guides/workflow/node/doc-extractor",
+ "zh-hans/guides/workflow/node/list-operator",
+ "zh-hans/guides/workflow/node/variable-aggregator",
+ "zh-hans/guides/workflow/node/variable-assigner",
+ "zh-hans/guides/workflow/node/iteration",
+ "zh-hans/guides/workflow/node/parameter-extractor",
+ "zh-hans/guides/workflow/node/http-request",
+ "zh-hans/guides/workflow/node/agent",
+ "zh-hans/guides/workflow/node/tools",
+ "zh-hans/guides/workflow/node/end",
+ "zh-hans/guides/workflow/node/answer",
+ "zh-hans/guides/workflow/node/loop"
+ ]
+ },
+ "zh-hans/guides/workflow/shortcut-key",
+ "zh-hans/guides/workflow/orchestrate-node",
+ "zh-hans/guides/workflow/file-upload",
+ {
+ "group": "异常处理",
+ "pages": [
+ "zh-hans/guides/workflow/error-handling/readme",
+ "zh-hans/guides/workflow/error-handling/predefined-nodes-failure-logic",
+ "zh-hans/guides/workflow/error-handling/error-type"
+ ]
+ },
+ "zh-hans/guides/workflow/additional-feature",
+ {
+ "group": "预览与调试",
+ "pages": [
+ "zh-hans/guides/workflow/debug-and-preview/preview-and-run",
+ "zh-hans/guides/workflow/debug-and-preview/step-run",
+ "zh-hans/guides/workflow/debug-and-preview/log",
+ "zh-hans/guides/workflow/debug-and-preview/checklist",
+ "zh-hans/guides/workflow/debug-and-preview/history"
+ ]
+ },
+ "zh-hans/guides/workflow/publish",
+ "zh-hans/guides/workflow/structured-outputs",
+ "zh-hans/guides/workflow/bulletin"
+ ]
+ },
+ {
+ "group": "知识库",
+ "pages": [
+ "zh-hans/guides/knowledge-base/readme",
+ {
+ "group": "创建知识库",
+ "pages": [
+ "zh-hans/guides/knowledge-base/knowledge-base-creation/introduction",
+ {
+ "group": "1. 导入文本数据",
+ "pages": [
+ "zh-hans/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/readme",
+ "zh-hans/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-notion",
+ "zh-hans/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website"
+ ]
+ },
+ "zh-hans/guides/knowledge-base/create-knowledge-and-upload-documents/chunking-and-cleaning-text",
+ "zh-hans/guides/knowledge-base/create-knowledge-and-upload-documents/setting-indexing-methods"
+ ]
+ },
+ {
+ "group": "管理知识库",
+ "pages": [
+ "zh-hans/guides/knowledge-base/knowledge-and-documents-maintenance/introduction",
+ "zh-hans/guides/knowledge-base/knowledge-and-documents-maintenance/maintain-knowledge-documents",
+ "zh-hans/guides/knowledge-base/knowledge-and-documents-maintenance/maintain-dataset-via-api"
+ ]
+ },
+ "zh-hans/guides/knowledge-base/metadata",
+ "zh-hans/guides/knowledge-base/integrate-knowledge-within-application",
+ "zh-hans/guides/knowledge-base/retrieval-test-and-citation",
+ "zh-hans/guides/knowledge-base/knowledge-request-rate-limit",
+ "zh-hans/guides/knowledge-base/connect-external-knowledge-base",
+ "zh-hans/guides/knowledge-base/api-documentation/external-knowledge-api-documentation"
+ ]
+ },
+ {
+ "group": "工具",
+ "pages": [
+ "zh-hans/guides/tools/readme",
+ "zh-hans/guides/tools/quick-tool-integration",
+ "zh-hans/guides/tools/advanced-tool-integration",
+ "zh-hans/guides/tools/tool-configuration/readme",
+ {
+ "group": "工具配置详情",
+ "pages": [
+ "zh-hans/guides/tools/tool-configuration/google",
+ "zh-hans/guides/tools/tool-configuration/bing",
+ "zh-hans/guides/tools/tool-configuration/searchapi",
+ "zh-hans/guides/tools/tool-configuration/stable-diffusion",
+ "zh-hans/guides/tools/tool-configuration/dall-e",
+ "zh-hans/guides/tools/tool-configuration/perplexity",
+ "zh-hans/guides/tools/tool-configuration/alphavantage",
+ "zh-hans/guides/tools/tool-configuration/searxng",
+ "zh-hans/guides/tools/tool-configuration/serper",
+ "zh-hans/guides/tools/tool-configuration/siliconflow",
+ "zh-hans/guides/tools/tool-configuration/comfyui"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "发布",
+ "pages": [
+ {
+ "group": "发布为公开 Web 站点",
+ "pages": [
+ "zh-hans/guides/application-publishing/launch-your-webapp-quickly/readme",
+ "zh-hans/guides/application-publishing/launch-your-webapp-quickly/web-app-settings",
+ "zh-hans/guides/application-publishing/launch-your-webapp-quickly/text-generator",
+ "zh-hans/guides/application-publishing/launch-your-webapp-quickly/conversation-application"
+ ]
+ },
+ "zh-hans/guides/application-publishing/embedding-in-websites",
+ "zh-hans/guides/application-publishing/developing-with-apis",
+ "zh-hans/guides/application-publishing/based-on-frontend-templates"
+ ]
+ },
+ {
+ "group": "标注",
+ "pages": [
+ "zh-hans/guides/annotation/logs",
+ "zh-hans/guides/annotation/annotation-reply"
+ ]
+ },
+ {
+ "group": "监测",
+ "pages": [
+ "zh-hans/guides/monitoring/README",
+ {
+ "group": "集成外部与 Ops 工具",
+ "pages": [
+ "zh-hans/guides/monitoring/integrate-external-ops-tools/readme",
+ "zh-hans/guides/monitoring/integrate-external-ops-tools/integrate-langfuse",
+ "zh-hans/guides/monitoring/integrate-external-ops-tools/integrate-langsmith",
+ "zh-hans/guides/monitoring/integrate-external-ops-tools/integrate-opik",
+ "zh-hans/guides/monitoring/integrate-external-ops-tools/integrate-weave"
+ ]
+ },
+ "zh-hans/guides/monitoring/analysis"
+ ]
+ },
+ {
+ "group": "扩展",
+ "pages": [
+ "zh-hans/guides/tools/extensions/README",
+ {
+ "group": "API 扩展",
+ "pages": [
+ "zh-hans/guides/tools/extensions/api-based/api-based-extension",
+ "zh-hans/guides/tools/extensions/api-based/external-data-tool",
+ "zh-hans/guides/tools/extensions/api-based/cloudflare-workers",
+ "zh-hans/guides/tools/extensions/api-based/moderation"
+ ]
+ },
+ {
+ "group": "代码扩展",
+ "pages": [
+ "zh-hans/guides/tools/extensions/code-based/README",
+ "zh-hans/guides/tools/extensions/code-based/external-data-tool",
+ "zh-hans/guides/tools/extensions/code-based/moderation"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "协同",
+ "pages": [
+ "zh-hans/guides/workspace/readme",
+ "zh-hans/guides/workspace/app",
+ "zh-hans/guides/workspace/invite-and-manage-members"
+ ]
+ },
+ {
+ "group": "管理",
+ "pages": [
+ "zh-hans/guides/management/app-management",
+ "zh-hans/guides/management/team-members-management",
+ "zh-hans/guides/management/personal-account-management",
+ "zh-hans/guides/management/subscription-management",
+ "zh-hans/guides/management/version-control"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "动手实验室",
+ "pages": [
+ "zh-hans/workshop/readme",
+ {
+ "group": "初级",
+ "pages": [
+ "zh-hans/workshop/basic/build-ai-image-generation-app",
+ "zh-hans/workshop/basic/travel-assistant"
+ ]
+ },
+ {
+ "group": "中级",
+ "pages": [
+ "zh-hans/workshop/intermediate/article-reader",
+ "zh-hans/workshop/intermediate/customer-service-bot",
+ "zh-hans/workshop/intermediate/twitter-chatflow"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "社区",
+ "pages": [
+ "zh-hans/community/support",
+ "zh-hans/community/contribution",
+ "zh-hans/community/docs-contribution"
+ ]
+ },
+ {
+ "group": "插件",
+ "pages": [
+ "zh-hans/plugins/introduction",
+ {
+ "group": "快速开始",
+ "pages": [
+ "zh-hans/plugins/quick-start/README",
+ "zh-hans/plugins/quick-start/install-plugins",
+ {
+ "group": "插件开发",
+ "pages": [
+ "zh-hans/plugins/quick-start/develop-plugins/README",
+ "zh-hans/plugins/quick-start/develop-plugins/initialize-development-tools",
+ "zh-hans/plugins/quick-start/develop-plugins/tool-plugin",
+ {
+ "group": "Model 插件",
+ "pages": [
+ "zh-hans/plugins/quick-start/develop-plugins/model-plugin/README",
+ "zh-hans/plugins/quick-start/develop-plugins/model-plugin/create-model-providers",
+ "zh-hans/plugins/quick-start/develop-plugins/model-plugin/integrate-the-predefined-model",
+ "zh-hans/plugins/quick-start/develop-plugins/model-plugin/customizable-model"
+ ]
+ },
+ "zh-hans/plugins/quick-start/develop-plugins/agent-strategy-plugin",
+ "zh-hans/plugins/quick-start/develop-plugins/extension-plugin",
+ "zh-hans/plugins/quick-start/develop-plugins/bundle"
+ ]
+ },
+ "zh-hans/plugins/quick-start/debug-plugin"
+ ]
+ },
+ "zh-hans/plugins/manage-plugins",
+ {
+ "group": "接口定义",
+ "pages": [
+ "zh-hans/plugins/schema-definition/manifest",
+ "zh-hans/plugins/schema-definition/endpoint",
+ "zh-hans/plugins/schema-definition/tool",
+ "zh-hans/plugins/schema-definition/agent",
+ {
+ "group": "Model",
+ "pages": [
+ "zh-hans/plugins/schema-definition/model/model-designing-rules",
+ "zh-hans/plugins/schema-definition/model/model-schema"
+ ]
+ },
+ "zh-hans/plugins/schema-definition/general-specifications",
+ "zh-hans/plugins/schema-definition/persistent-storage",
+ {
+ "group": "反向调用 Dify 服务",
+ "pages": [
+ "zh-hans/plugins/schema-definition/reverse-invocation-of-the-dify-service/README",
+ "zh-hans/plugins/schema-definition/reverse-invocation-of-the-dify-service/app",
+ "zh-hans/plugins/schema-definition/reverse-invocation-of-the-dify-service/model",
+ "zh-hans/plugins/schema-definition/reverse-invocation-of-the-dify-service/tool",
+ "zh-hans/plugins/schema-definition/reverse-invocation-of-the-dify-service/node"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "最佳实践",
+ "pages": [
+ "zh-hans/plugins/best-practice/develop-a-slack-bot-plugin",
+ "zh-hans/plugins/best-practice/how-to-use-mcp-zapier"
+ ]
+ },
+ {
+ "group": "发布插件",
+ "pages": [
+ "zh-hans/plugins/publish-plugins/README",
+ "zh-hans/plugins/publish-plugins/plugin-auto-publish-pr",
+ {
+ "group": "发布至 Dify Marketplace",
+ "pages": [
+ "zh-hans/plugins/publish-plugins/publish-to-dify-marketplace/README",
+ "zh-hans/plugins/publish-plugins/publish-to-dify-marketplace/plugin-developer-guidelines",
+ "zh-hans/plugins/publish-plugins/publish-to-dify-marketplace/plugin-privacy-protection-guidelines"
+ ]
+ },
+ "zh-hans/plugins/publish-plugins/publish-plugin-on-personal-github-repo",
+ "zh-hans/plugins/publish-plugins/package-plugin-file-and-publish",
+ "zh-hans/plugins/publish-plugins/signing-plugins-for-third-party-signature-verification"
+ ]
+ },
+ "zh-hans/plugins/faq"
+ ]
+ },
+ {
+ "group": "研发",
+ "pages": [
+ {
+ "group": "DifySandbox",
+ "pages": [
+ "zh-hans/development/backend/sandbox/README",
+ "zh-hans/development/backend/sandbox/contribution"
+ ]
+ },
+ {
+ "group": "模型接入",
+ "pages": [
+ "zh-hans/development/models-integration/hugging-face",
+ "zh-hans/development/models-integration/replicate",
+ "zh-hans/development/models-integration/xinference",
+ "zh-hans/development/models-integration/openllm",
+ "zh-hans/development/models-integration/localai",
+ "zh-hans/development/models-integration/ollama",
+ "zh-hans/development/models-integration/litellm",
+ "zh-hans/development/models-integration/gpustack",
+ "zh-hans/development/models-integration/aws-bedrock-deepseek"
+ ]
+ },
+ {
+ "group": "迁移",
+ "pages": [
+ "zh-hans/development/migration/migrate-to-v1"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "阅读更多",
+ "pages": [
+ {
+ "group": "应用案例",
+ "pages": [
+ "zh-hans/learn-more/use-cases/integrate-deepseek-to-build-an-ai-app",
+ "zh-hans/learn-more/use-cases/private-ai-ollama-deepseek-dify",
+ "zh-hans/learn-more/use-cases/train-a-qa-chatbot-that-belongs-to-you",
+ "zh-hans/learn-more/use-cases/create-a-midjoureny-prompt-word-robot-with-zero-code",
+ "zh-hans/learn-more/use-cases/build-an-notion-ai-assistant",
+ "zh-hans/learn-more/use-cases/building-an-ai-thesis-slack-bot",
+ "zh-hans/learn-more/use-cases/connect-dify-to-various-im-platforms-by-using-langbot",
+ "zh-hans/learn-more/use-cases/create-an-ai-chatbot-with-business-data-in-minutes",
+ "zh-hans/learn-more/use-cases/practical-implementation-of-building-llm-applications-using-a-full-set-of-open-source-tools",
+ "zh-hans/learn-more/use-cases/dify-on-wechat",
+ "zh-hans/learn-more/use-cases/dify-on-dingtalk",
+ "zh-hans/learn-more/use-cases/dify-on-teams",
+ "zh-hans/learn-more/use-cases/how-to-make-llm-app-provide-a-progressive-chat-experience",
+ "zh-hans/learn-more/use-cases/how-to-integrate-dify-chatbot-to-your-wix-website",
+ "zh-hans/learn-more/use-cases/how-to-connect-aws-bedrock",
+ "zh-hans/learn-more/use-cases/dify-schedule",
+ "zh-hans/learn-more/use-cases/dify-model-arena"
+ ]
+ },
+ {
+ "group": "扩展阅读",
+ "pages": [
+ "zh-hans/learn-more/extended-reading/what-is-llmops",
+ "zh-hans/learn-more/extended-reading/what-is-array-variable",
+ {
+ "group": "检索增强生成(RAG)",
+ "pages": [
+ "zh-hans/learn-more/extended-reading/retrieval-augment/README",
+ "zh-hans/learn-more/extended-reading/retrieval-augment/hybrid-search",
+ "zh-hans/learn-more/extended-reading/retrieval-augment/rerank",
+ "zh-hans/learn-more/extended-reading/retrieval-augment/retrieval"
+ ]
+ },
+ "zh-hans/learn-more/extended-reading/prompt-engineering",
+ "zh-hans/learn-more/extended-reading/how-to-use-json-schema-in-dify"
+ ]
+ },
+ {
+ "group": "常见问题",
+ "pages": [
+ "zh-hans/learn-more/faq/install-faq",
+ "zh-hans/learn-more/faq/llms-use-faq",
+ "zh-hans/learn-more/faq/plugins"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "政策",
+ "pages": [
+ "zh-hans/policies/open-source",
+ {
+ "group": "用户协议",
+ "pages": [
+ "zh-hans/policies/agreement/README",
+ "zh-hans/policies/agreement/tos",
+ "zh-hans/policies/agreement/privacy",
+ "zh-hans/policies/agreement/get-compliance-report"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "tab": "术语表",
"pages": [
- "en/plugins/schema-definition/reverse-invocation-of-the-dify-service/README",
- "en/plugins/schema-definition/reverse-invocation-of-the-dify-service/app",
- "en/plugins/schema-definition/reverse-invocation-of-the-dify-service/model",
- "en/plugins/schema-definition/reverse-invocation-of-the-dify-service/tool",
- "en/plugins/schema-definition/reverse-invocation-of-the-dify-service/node"
+ "zh-hans/termbase/termbase"
]
- }
- ]
- },
- {
- "group": "Best Practice",
- "pages": [
- "en/plugins/best-practice/README",
- "en/plugins/best-practice/develop-a-slack-bot-plugin",
- "en/plugins/best-practice/how-to-use-mcp-zapier"
- ]
- },
- {
- "group": "Publish Plugins",
- "pages": [
- "en/plugins/publish-plugins/README",
- "en/plugins/publish-plugins/plugin-auto-publish-pr",
- {
- "group": "Publish to Dify Marketplace",
+ },
+ {
+ "tab": "插件开发",
+ "groups": [
+ {
+ "group": "概念与入门",
+ "pages": [
+ {
+ "group": "概览",
+ "pages": [
+ "plugin_dev_zh/0111-getting-started-dify-plugin.zh"
+ ]
+ },
+ "plugin_dev_zh/0131-cheatsheet.zh"
+ ]
+ },
+ {
+ "group": "开发实践",
+ "pages": [
+ {
+ "group": "快速开始",
+ "pages": [
+ "plugin_dev_zh/0211-getting-started-by-prompt.zh",
+ "plugin_dev_zh/0211-getting-started-dify-tool.zh",
+ "plugin_dev_zh/0211-getting-started-new-model.zh"
+ ]
+ },
+ {
+ "group": "开发 Dify 插件",
+ "pages": [
+ "plugin_dev_zh/0221-initialize-development-tools.zh",
+ "plugin_dev_zh/0222-creating-new-model-provider-extra.zh",
+ "plugin_dev_zh/0222-creating-new-model-provider.zh",
+ "plugin_dev_zh/0222-tool-plugin.zh"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "贡献与发布",
+ "pages": [
+ {
+ "group": "行为准则与规范",
+ "pages": [
+ "plugin_dev_zh/0312-contributor-covenant-code-of-conduct.zh",
+ "plugin_dev_zh/0312-privacy-protection-guidelines.zh"
+ ]
+ },
+ {
+ "group": "发布与上架",
+ "pages": [
+ "plugin_dev_zh/0321-release-overview.zh",
+ "plugin_dev_zh/0322-release-by-file.zh",
+ "plugin_dev_zh/0322-release-to-dify-marketplace.zh",
+ "plugin_dev_zh/0322-release-to-individual-github-repo.zh"
+ ]
+ },
+ {
+ "group": "常见问题解答",
+ "pages": [
+ "plugin_dev_zh/0331-faq.zh"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "实践案例与示例",
+ "pages": [
+ {
+ "group": "开发示例",
+ "pages": [
+ "plugin_dev_zh/0432-develop-a-slack-bot-plugin.zh",
+ "plugin_dev_zh/0432-endpoint.zh"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "高级开发",
+ "pages": [
+ {
+ "group": "Extension 与 Agent",
+ "pages": [
+ "plugin_dev_zh/9231-extension-plugin.zh",
+ "plugin_dev_zh/9232-agent.zh",
+ "plugin_dev_zh/9433-agent-strategy-plugin.zh"
+ ]
+ },
+ {
+ "group": "反向调用",
+ "pages": [
+ "plugin_dev_zh/9241-bundle.zh",
+ "plugin_dev_zh/9241-reverse-invocation.zh",
+ "plugin_dev_zh/9242-reverse-invocation-app.zh",
+ "plugin_dev_zh/9242-reverse-invocation-model.zh",
+ "plugin_dev_zh/9242-reverse-invocation-tool.zh",
+ "plugin_dev_zh/9243-customizable-model.zh",
+ "plugin_dev_zh/9243-reverse-invocation-node.zh"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "Reference & Specifications",
+ "pages": [
+ {
+ "group": "核心规范与功能",
+ "pages": [
+ "plugin_dev_zh/0411-general-specifications.zh",
+ "plugin_dev_zh/0411-model-designing-rules.zh",
+ "plugin_dev_zh/0411-model-plugin-introduction.zh",
+ "plugin_dev_zh/0411-persistent-storage-kv.zh",
+ "plugin_dev_zh/0411-plugin-info-by-manifest.zh",
+ "plugin_dev_zh/0411-remote-debug-a-plugin.zh",
+ "plugin_dev_zh/0411-tool.zh",
+ "plugin_dev_zh/0412-model-schema.zh"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "version": "日本語",
+ "tabs": [
+ {
+ "tab": "ドキュメント",
+ "groups": [
+ {
+ "group": "入門",
+ "pages": [
+ {
+ "group": "Difyへようこそ",
+ "pages": [
+ "ja-jp/introduction",
+ "ja-jp/getting-started/readme/features-and-specifications",
+ "ja-jp/getting-started/readme/model-providers"
+ ]
+ },
+ "ja-jp/getting-started/cloud",
+ {
+ "group": "Dify コミュニティ版",
+ "pages": [
+ "ja-jp/getting-started/install-self-hosted/README",
+ "ja-jp/getting-started/install-self-hosted/docker-compose",
+ "ja-jp/getting-started/install-self-hosted/local-source-code",
+ "ja-jp/getting-started/install-self-hosted/bt-panel",
+ "ja-jp/getting-started/install-self-hosted/start-the-frontend-docker-container",
+ "ja-jp/getting-started/install-self-hosted/environments",
+ "ja-jp/getting-started/install-self-hosted/faq"
+ ]
+ },
+ "ja-jp/getting-started/dify-premium",
+ "ja-jp/getting-started/dify-for-education"
+ ]
+ },
+ {
+ "group": "マニュアル",
+ "pages": [
+ {
+ "group": "モデル",
+ "pages": [
+ "ja-jp/guides/model-configuration/README",
+ "ja-jp/guides/model-configuration/new-provider",
+ "ja-jp/guides/model-configuration/predefined-model",
+ "ja-jp/guides/model-configuration/customizable-model",
+ "ja-jp/guides/model-configuration/interfaces",
+ "ja-jp/guides/model-configuration/schema",
+ "ja-jp/guides/model-configuration/load-balancing"
+ ]
+ },
+ {
+ "group": "アプリ・オーケストレーション",
+ "pages": [
+ "ja-jp/guides/application-orchestrate/README",
+ "ja-jp/guides/application-orchestrate/creating-an-application",
+ {
+ "group": "チャットボット",
+ "pages": [
+ "ja-jp/guides/application-orchestrate/chatbot-application",
+ "ja-jp/guides/application-orchestrate/multiple-llms-debugging"
+ ]
+ },
+ "ja-jp/guides/application-orchestrate/agent",
+ {
+ "group": "ツールキット",
+ "pages": [
+ "ja-jp/guides/application-orchestrate/app-toolkits/README",
+ "ja-jp/guides/application-orchestrate/app-toolkits/moderation-tool"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "ワークフロー",
+ "pages": [
+ "ja-jp/guides/workflow/concepts",
+ "ja-jp/guides/workflow/variables",
+ {
+ "group": "ノードの説明",
+ "pages": [
+ "ja-jp/guides/workflow/node/start",
+ "ja-jp/guides/workflow/node/end",
+ "ja-jp/guides/workflow/node/answer",
+ "ja-jp/guides/workflow/node/llm",
+ "ja-jp/guides/workflow/node/knowledge-retrieval",
+ "ja-jp/guides/workflow/node/question-classifier",
+ "ja-jp/guides/workflow/node/ifelse",
+ "ja-jp/guides/workflow/node/code",
+ "ja-jp/guides/workflow/node/template",
+ "ja-jp/guides/workflow/node/doc-extractor",
+ "ja-jp/guides/workflow/node/list-operator",
+ "ja-jp/guides/workflow/node/variable-aggregator",
+ "ja-jp/guides/workflow/node/variable-assigner",
+ "ja-jp/guides/workflow/node/iteration",
+ "ja-jp/guides/workflow/node/parameter-extractor",
+ "ja-jp/guides/workflow/node/http-request",
+ "ja-jp/guides/workflow/node/agent",
+ "ja-jp/guides/workflow/node/tools",
+ "ja-jp/guides/workflow/node/loop"
+ ]
+ },
+ "ja-jp/guides/workflow/shortcut-key",
+ "ja-jp/guides/workflow/orchestrate-node",
+ "ja-jp/guides/workflow/file-upload",
+ {
+ "group": "エラー処理",
+ "pages": [
+ "ja-jp/guides/workflow/error-handling/README",
+ "ja-jp/guides/workflow/error-handling/predefined-nodes-failure-logic",
+ "ja-jp/guides/workflow/error-handling/error-type"
+ ]
+ },
+ "ja-jp/guides/workflow/additional-feature",
+ {
+ "group": "プレビューとデバッグ",
+ "pages": [
+ "ja-jp/guides/workflow/debug-and-preview/step-run",
+ "ja-jp/guides/workflow/debug-and-preview/log",
+ "ja-jp/guides/workflow/debug-and-preview/checklist",
+ "ja-jp/guides/workflow/debug-and-preview/history"
+ ]
+ },
+ "ja-jp/guides/workflow/publish",
+ "ja-jp/guides/workflow/structured-outputs",
+ "ja-jp/guides/workflow/bulletin"
+ ]
+ },
+ {
+ "group": "ナレッジベース",
+ "pages": [
+ "ja-jp/guides/knowledge-base/readme",
+ {
+ "group": "ナレッジベース作成",
+ "pages": [
+ "ja-jp/guides/knowledge-base/knowledge-base-creation/introduction",
+ {
+ "group": "1. オンラインデータソースの活用",
+ "pages": [
+ "ja-jp/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/readme",
+ "ja-jp/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-notion",
+ "ja-jp/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website"
+ ]
+ },
+ "ja-jp/guides/knowledge-base/create-knowledge-and-upload-documents/chunking-and-cleaning-text",
+ "ja-jp/guides/knowledge-base/create-knowledge-and-upload-documents/setting-indexing-methods"
+ ]
+ },
+ {
+ "group": "ナレッジベースの管理",
+ "pages": [
+ "ja-jp/guides/knowledge-base/knowledge-and-documents-maintenance/readme",
+ "ja-jp/guides/knowledge-base/knowledge-and-documents-maintenance/maintain-knowledge-documents",
+ "ja-jp/guides/knowledge-base/knowledge-and-documents-maintenance/maintain-dataset-via-api"
+ ]
+ },
+ "ja-jp/guides/knowledge-base/metadata",
+ "ja-jp/guides/knowledge-base/integrate-knowledge-within-application",
+ "ja-jp/guides/knowledge-base/retrieval-test-and-citation",
+ "ja-jp/guides/knowledge-base/knowledge-request-rate-limit",
+ "ja-jp/guides/knowledge-base/connect-external-knowledge-base",
+ "ja-jp/guides/knowledge-base/api-documentation/external-knowledge-api-documentation"
+ ]
+ },
+ {
+ "group": "アプリ公開",
+ "pages": [
+ {
+ "group": "シングルページWebアプリとして公開",
+ "pages": [
+ "ja-jp/guides/application-publishing/launch-your-webapp-quickly/web-app-settings",
+ "ja-jp/guides/application-publishing/launch-your-webapp-quickly/text-generator",
+ "ja-jp/guides/application-publishing/launch-your-webapp-quickly/conversation-application"
+ ]
+ },
+ "ja-jp/guides/application-publishing/embedding-in-websites",
+ "ja-jp/guides/application-publishing/developing-with-apis",
+ "ja-jp/guides/application-publishing/based-on-frontend-templates"
+ ]
+ },
+ {
+ "group": "アノテーション",
+ "pages": [
+ "ja-jp/guides/annotation/logs",
+ "ja-jp/guides/annotation/annotation-reply"
+ ]
+ },
+ {
+ "group": "モニタリング",
+ "pages": [
+ "ja-jp/guides/monitoring/analysis",
+ {
+ "group": "外部Opsツール統合",
+ "pages": [
+ "ja-jp/guides/monitoring/integrate-external-ops-tools/integrate-langfuse",
+ "ja-jp/guides/monitoring/integrate-external-ops-tools/integrate-langsmith",
+ "ja-jp/guides/monitoring/integrate-external-ops-tools/integrate-opik",
+ "ja-jp/guides/monitoring/integrate-external-ops-tools/integrate-weave"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "拡張",
+ "pages": [
+ {
+ "group": "API 拡張",
+ "pages": [
+ "ja-jp/guides/extension/api-based-extension/README",
+ "ja-jp/guides/extension/api-based-extension/cloudflare-workers",
+ "ja-jp/guides/extension/api-based-extension/moderation"
+ ]
+ },
+ {
+ "group": "コード拡張",
+ "pages": [
+ "ja-jp/guides/extension/code-based-extension/external-data-tool",
+ "ja-jp/guides/extension/code-based-extension/moderation"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "コラボレーション",
+ "pages": [
+ "ja-jp/guides/workspace/readme",
+ "ja-jp/guides/workspace/app",
+ "ja-jp/guides/workspace/invite-and-manage-members"
+ ]
+ },
+ {
+ "group": "管理",
+ "pages": [
+ "ja-jp/guides/management/app-management",
+ "ja-jp/guides/management/team-members-management",
+ "ja-jp/guides/management/personal-account-management",
+ "ja-jp/guides/management/subscription-management",
+ "ja-jp/guides/management/version-control"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "ハンドオン工房",
+ "pages": [
+ {
+ "group": "初級編",
+ "pages": [
+ "ja-jp/workshop/basic/README",
+ "ja-jp/workshop/basic/build-ai-image-generation-app",
+ "ja-jp/workshop/basic/travel-assistant"
+ ]
+ },
+ {
+ "group": "中級編",
+ "pages": [
+ "ja-jp/workshop/intermediate/article-reader",
+ "ja-jp/workshop/intermediate/twitter-chatflow"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "コミュニティ",
+ "pages": [
+ "ja-jp/community/support",
+ "ja-jp/community/contribution",
+ "ja-jp/community/docs-contribution"
+ ]
+ },
+ {
+ "group": "プラグイン",
+ "pages": [
+ "ja-jp/plugins/introduction",
+ {
+ "group": "クイックスタート",
+ "pages": [
+ "ja-jp/plugins/quick-start/README",
+ "ja-jp/plugins/quick-start/install-plugins",
+ {
+ "group": "プラグイン開発の入門",
+ "pages": [
+ "ja-jp/plugins/quick-start/develop-plugins/README",
+ "ja-jp/plugins/quick-start/develop-plugins/initialize-development-tools",
+ "ja-jp/plugins/quick-start/develop-plugins/tool-plugin",
+ {
+ "group": "モデル型プラグイン",
+ "pages": [
+ "ja-jp/plugins/quick-start/develop-plugins/model-plugin/README",
+ "ja-jp/plugins/quick-start/develop-plugins/model-plugin/create-model-providers",
+ "ja-jp/plugins/quick-start/develop-plugins/model-plugin/integrate-the-predefined-model",
+ "ja-jp/plugins/quick-start/develop-plugins/model-plugin/customizable-model"
+ ]
+ },
+ "ja-jp/plugins/quick-start/develop-plugins/agent-strategy-plugin",
+ "ja-jp/plugins/quick-start/develop-plugins/extension-plugin",
+ "ja-jp/plugins/quick-start/develop-plugins/bundle"
+ ]
+ },
+ "ja-jp/plugins/quick-start/debug-plugin"
+ ]
+ },
+ "ja-jp/plugins/manage-plugins",
+ {
+ "group": "スキーマ仕様",
+ "pages": [
+ "ja-jp/plugins/schema-definition/README",
+ "ja-jp/plugins/schema-definition/manifest",
+ "ja-jp/plugins/schema-definition/endpoint",
+ "ja-jp/plugins/schema-definition/tool",
+ "ja-jp/plugins/schema-definition/agent",
+ {
+ "group": "Model",
+ "pages": [
+ "ja-jp/plugins/schema-definition/model/README",
+ "ja-jp/plugins/schema-definition/model/model-designing-rules",
+ "ja-jp/plugins/schema-definition/model/model-schema"
+ ]
+ },
+ "ja-jp/plugins/schema-definition/general-specifications",
+ "ja-jp/plugins/schema-definition/persistent-storage",
+ {
+ "group": "Difyサービスの逆呼び出し",
+ "pages": [
+ "ja-jp/plugins/schema-definition/reverse-invocation-of-the-dify-service/README",
+ "ja-jp/plugins/schema-definition/reverse-invocation-of-the-dify-service/app",
+ "ja-jp/plugins/schema-definition/reverse-invocation-of-the-dify-service/model",
+ "ja-jp/plugins/schema-definition/reverse-invocation-of-the-dify-service/tool",
+ "ja-jp/plugins/schema-definition/reverse-invocation-of-the-dify-service/node"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "ベストプラクティス",
+ "pages": [
+ "ja-jp/plugins/best-practice/README",
+ "ja-jp/plugins/best-practice/develop-a-slack-bot-plugin",
+ "ja-jp/plugins/best-practice/how-to-use-mcp-zapier"
+ ]
+ },
+ {
+ "group": "プラグインの公開",
+ "pages": [
+ "ja-jp/plugins/publish-plugins/README",
+ "ja-jp/plugins/publish-plugins/plugin-auto-publish-pr",
+ {
+ "group": "Difyマーケットプレイスへの公開",
+ "pages": [
+ "ja-jp/plugins/publish-plugins/publish-to-dify-marketplace/README",
+ "ja-jp/plugins/publish-plugins/publish-to-dify-marketplace/plugin-developer-guidelines",
+ "ja-jp/plugins/publish-plugins/publish-to-dify-marketplace/plugin-privacy-protection-guidelines"
+ ]
+ },
+ "ja-jp/plugins/publish-plugins/publish-plugin-on-personal-github-repo",
+ "ja-jp/plugins/publish-plugins/package-plugin-file-and-publish",
+ "ja-jp/plugins/publish-plugins/signing-plugins-for-third-party-signature-verification"
+ ]
+ },
+ "ja-jp/plugins/faq"
+ ]
+ },
+ {
+ "group": "開発",
+ "pages": [
+ {
+ "group": "バックエンド",
+ "pages": [
+ "ja-jp/development/backend/sandbox/README",
+ "ja-jp/development/backend/sandbox/contribution"
+ ]
+ },
+ {
+ "group": "モデルの統合",
+ "pages": [
+ "ja-jp/development/models-integration/hugging-face",
+ "ja-jp/development/models-integration/replicate",
+ "ja-jp/development/models-integration/xinference",
+ "ja-jp/development/models-integration/openllm",
+ "ja-jp/development/models-integration/localai",
+ "ja-jp/development/models-integration/ollama",
+ "ja-jp/development/models-integration/litellm",
+ "ja-jp/development/models-integration/gpustack",
+ "ja-jp/development/models-integration/aws-bedrock-deepseek"
+ ]
+ },
+ {
+ "group": "移行",
+ "pages": [
+ "ja-jp/development/migration/migrate-to-v1"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "もっと読む",
+ "pages": [
+ {
+ "group": "活用事例",
+ "pages": [
+ "ja-jp/learn-more/use-cases/integrate-deepseek-to-build-an-ai-app",
+ "ja-jp/learn-more/use-cases/private-ai-ollama-deepseek-dify",
+ "ja-jp/learn-more/use-cases/create-a-midjoureny-prompt-word-robot-with-zero-code",
+ "ja-jp/learn-more/use-cases/build-an-notion-ai-assistant",
+ "ja-jp/learn-more/use-cases/building-an-ai-thesis-slack-bot",
+ "ja-jp/learn-more/use-cases/create-an-ai-chatbot-with-business-data-in-minutes",
+ "ja-jp/learn-more/use-cases/how-to-integrate-dify-chatbot-to-your-wix-website",
+ "ja-jp/learn-more/use-cases/how-to-connect-aws-bedrock",
+ "ja-jp/learn-more/use-cases/dify-model-arena",
+ "ja-jp/learn-more/use-cases/dify-schedule",
+ "ja-jp/learn-more/use-cases/building-an-ai-thesis-slack-bot"
+ ]
+ },
+ {
+ "group": "さらに読む",
+ "pages": [
+ "ja-jp/learn-more/extended-reading/what-is-llmops",
+ "ja-jp/learn-more/extended-reading/what-is-array-variable",
+ {
+ "group": "检索增强生成(RAG)",
+ "pages": [
+ "ja-jp/learn-more/extended-reading/retrieval-augment/README",
+ "ja-jp/learn-more/extended-reading/retrieval-augment/hybrid-search",
+ "ja-jp/learn-more/extended-reading/retrieval-augment/rerank",
+ "ja-jp/learn-more/extended-reading/retrieval-augment/retrieval"
+ ]
+ },
+ "ja-jp/learn-more/extended-reading/how-to-use-json-schema-in-dify"
+ ]
+ },
+ {
+ "group": "常见问题",
+ "pages": [
+ "ja-jp/learn-more/faq/README",
+ "ja-jp/learn-more/faq/install-faq",
+ "ja-jp/learn-more/faq/llms-use-faq",
+ "ja-jp/learn-more/faq/plugins"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "ポリシー",
+ "pages": [
+ "ja-jp/policies/open-source",
+ {
+ "group": "ユーザ規約",
+ "pages": [
+ "ja-jp/policies/agreement/README",
+ "ja-jp/policies/agreement/tos",
+ "ja-jp/policies/agreement/privacy",
+ "ja-jp/policies/agreement/get-compliance-report"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "tab": "用語ベース",
"pages": [
- "en/plugins/publish-plugins/publish-to-dify-marketplace/README",
- "en/plugins/publish-plugins/publish-to-dify-marketplace/plugin-developer-guidelines",
- "en/plugins/publish-plugins/publish-to-dify-marketplace/plugin-privacy-protection-guidelines"
+ "ja-jp/termbase/termbase"
]
- },
- "en/plugins/publish-plugins/publish-plugin-on-personal-github-repo",
- "en/plugins/publish-plugins/package-plugin-file-and-publish",
- "en/plugins/publish-plugins/signing-plugins-for-third-party-signature-verification"
- ]
- },
- "en/plugins/faq"
+ }
]
- },
- {
- "group": "Development",
- "pages": [
- {
- "group": "Backend",
- "pages": [
- "en/development/backend/sandbox/README",
- "en/development/backend/sandbox/contribution"
- ]
- },
- {
- "group": "Models Integration",
- "pages": [
- "en/development/models-integration/hugging-face",
- "en/development/models-integration/replicate",
- "en/development/models-integration/xinference",
- "en/development/models-integration/openllm",
- "en/development/models-integration/localai",
- "en/development/models-integration/ollama",
- "en/development/models-integration/litellm",
- "en/development/models-integration/gpustack",
- "en/development/models-integration/aws-bedrock-deepseek"
- ]
- },
- {
- "group": "Migration",
- "pages": [
- "en/development/migration/migrate-to-v1"
- ]
- }
- ]
- },
- {
- "group": "Learn More",
- "pages": [
- {
- "group": "Use Cases",
- "pages": [
- "en/learn-more/use-cases/integrate-deepseek-to-build-an-ai-app",
- "en/learn-more/use-cases/private-ai-ollama-deepseek-dify",
- "en/learn-more/use-cases/build-an-notion-ai-assistant",
- "en/learn-more/use-cases/create-a-midjourney-prompt-bot-with-dify",
- "en/learn-more/use-cases/create-an-ai-chatbot-with-business-data-in-minutes",
- "en/learn-more/use-cases/how-to-integrate-dify-chatbot-to-your-wix-website",
- "en/learn-more/use-cases/how-to-connect-aws-bedrock",
- "en/learn-more/use-cases/dify-schedule",
- "en/learn-more/use-cases/building-an-ai-thesis-slack-bot"
- ]
- },
- {
- "group": "Extended Reading",
- "pages": [
- "en/learn-more/extended-reading/what-is-llmops",
- {
- "group": "Retrieval-Augmented Generation (RAG)",
- "pages": [
- "en/learn-more/extended-reading/retrieval-augment/README",
- "en/learn-more/extended-reading/retrieval-augment/hybrid-search",
- "en/learn-more/extended-reading/retrieval-augment/rerank",
- "en/learn-more/extended-reading/retrieval-augment/retrieval"
- ]
- },
- "en/learn-more/extended-reading/how-to-use-json-schema-in-dify"
- ]
- },
- {
- "group": "FAQ",
- "pages": [
- "en/learn-more/faq/install-faq",
- "en/learn-more/faq/use-llms-faq",
- "en/learn-more/faq/plugins"
- ]
- }
- ]
- },
- {
- "group": "Policies",
- "pages": [
- "en/policies/open-source",
- {
- "group": "User Agreement",
- "pages": [
- "en/policies/agreement/README",
- "en/policies/agreement/tos",
- "en/policies/agreement/privacy",
- "en/policies/agreement/get-compliance-report"
- ]
- }
- ]
- }
- ]
- },
- {
- "tab": "Termbase",
- "pages": [
- "en/termbase/termbase"
- ]
- }
+ },
+ {
+ "version": "Archived",
+ "href": "https://legacy-docs.dify.ai/"
+ }
]
- },
- {
- "version": "简体中文",
- "tabs": [
- {
- "tab": "使用文档",
- "groups": [
- {
- "group": "入门",
- "pages": [
- {
- "group": "欢迎使用 Dify",
- "pages": [
- "zh-hans/introduction",
- "zh-hans/getting-started/readme/features-and-specifications",
- "zh-hans/getting-started/readme/model-providers"
- ]
- },
- "zh-hans/getting-started/cloud",
- {
- "group": "Dify 社区版",
- "pages": [
- "zh-hans/getting-started/install-self-hosted/readme",
- "zh-hans/getting-started/install-self-hosted/docker-compose",
- "zh-hans/getting-started/install-self-hosted/local-source-code",
- "zh-hans/getting-started/install-self-hosted/bt-panel",
- "zh-hans/getting-started/install-self-hosted/start-the-frontend-docker-container",
- "zh-hans/getting-started/install-self-hosted/environments",
- "zh-hans/getting-started/install-self-hosted/faq"
- ]
- },
- "zh-hans/getting-started/dify-premium",
- "zh-hans/getting-started/dify-for-education"
- ]
- },
- {
- "group": "手册",
- "pages": [
- {
- "group": "接入大模型",
- "pages": [
- "zh-hans/guides/model-configuration/readme",
- "zh-hans/guides/model-configuration/new-provider",
- "zh-hans/guides/model-configuration/predefined-model",
- "zh-hans/guides/model-configuration/customizable-model",
- "zh-hans/guides/model-configuration/interfaces",
- "zh-hans/guides/model-configuration/schema",
- "zh-hans/guides/model-configuration/load-balancing"
- ]
- },
- {
- "group": "构建应用",
- "pages": [
- "zh-hans/guides/application-orchestrate/readme",
- "zh-hans/guides/application-orchestrate/creating-an-application",
- {
- "group": "聊天助手",
- "pages": [
- "zh-hans/guides/application-orchestrate/chatbot-application",
- "zh-hans/guides/application-orchestrate/multiple-llms-debugging"
- ]
- },
- "zh-hans/guides/application-orchestrate/agent",
- {
- "group": "应用工具箱",
- "pages": [
- "zh-hans/guides/application-orchestrate/app-toolkits/readme",
- "zh-hans/guides/application-orchestrate/app-toolkits/moderation-tool"
- ]
- }
- ]
- },
- {
- "group": "工作流",
- "pages": [
- "zh-hans/guides/workflow/readme",
- "zh-hans/guides/workflow/key-concept",
- "zh-hans/guides/workflow/variables",
- {
- "group": "节点说明",
- "pages": [
- "zh-hans/guides/workflow/node/start",
- "zh-hans/guides/workflow/node/llm",
- "zh-hans/guides/workflow/node/knowledge-retrieval",
- "zh-hans/guides/workflow/node/question-classifier",
- "zh-hans/guides/workflow/node/ifelse",
- "zh-hans/guides/workflow/node/code",
- "zh-hans/guides/workflow/node/template",
- "zh-hans/guides/workflow/node/doc-extractor",
- "zh-hans/guides/workflow/node/list-operator",
- "zh-hans/guides/workflow/node/variable-aggregator",
- "zh-hans/guides/workflow/node/variable-assigner",
- "zh-hans/guides/workflow/node/iteration",
- "zh-hans/guides/workflow/node/parameter-extractor",
- "zh-hans/guides/workflow/node/http-request",
- "zh-hans/guides/workflow/node/agent",
- "zh-hans/guides/workflow/node/tools",
- "zh-hans/guides/workflow/node/end",
- "zh-hans/guides/workflow/node/answer",
- "zh-hans/guides/workflow/node/loop"
- ]
- },
- "zh-hans/guides/workflow/shortcut-key",
- "zh-hans/guides/workflow/orchestrate-node",
- "zh-hans/guides/workflow/file-upload",
- {
- "group": "异常处理",
- "pages": [
- "zh-hans/guides/workflow/error-handling/readme",
- "zh-hans/guides/workflow/error-handling/predefined-nodes-failure-logic",
- "zh-hans/guides/workflow/error-handling/error-type"
- ]
- },
- "zh-hans/guides/workflow/additional-feature",
- {
- "group": "预览与调试",
- "pages": [
- "zh-hans/guides/workflow/debug-and-preview/preview-and-run",
- "zh-hans/guides/workflow/debug-and-preview/step-run",
- "zh-hans/guides/workflow/debug-and-preview/log",
- "zh-hans/guides/workflow/debug-and-preview/checklist",
- "zh-hans/guides/workflow/debug-and-preview/history"
- ]
- },
- "zh-hans/guides/workflow/publish",
- "zh-hans/guides/workflow/structured-outputs",
- "zh-hans/guides/workflow/bulletin"
- ]
- },
- {
- "group": "知识库",
- "pages": [
- "zh-hans/guides/knowledge-base/readme",
- {
- "group": "创建知识库",
- "pages": [
- "zh-hans/guides/knowledge-base/knowledge-base-creation/introduction",
- {
- "group": "1. 导入文本数据",
- "pages": [
- "zh-hans/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/readme",
- "zh-hans/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-notion",
- "zh-hans/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website"
- ]
- },
- "zh-hans/guides/knowledge-base/create-knowledge-and-upload-documents/chunking-and-cleaning-text",
- "zh-hans/guides/knowledge-base/create-knowledge-and-upload-documents/setting-indexing-methods"
- ]
- },
- {
- "group": "管理知识库",
- "pages": [
- "zh-hans/guides/knowledge-base/knowledge-and-documents-maintenance/introduction",
- "zh-hans/guides/knowledge-base/knowledge-and-documents-maintenance/maintain-knowledge-documents",
- "zh-hans/guides/knowledge-base/knowledge-and-documents-maintenance/maintain-dataset-via-api"
- ]
- },
- "zh-hans/guides/knowledge-base/metadata",
- "zh-hans/guides/knowledge-base/integrate-knowledge-within-application",
- "zh-hans/guides/knowledge-base/retrieval-test-and-citation",
- "zh-hans/guides/knowledge-base/knowledge-request-rate-limit",
- "zh-hans/guides/knowledge-base/connect-external-knowledge-base",
- "zh-hans/guides/knowledge-base/api-documentation/external-knowledge-api-documentation"
- ]
- },
- {
- "group": "工具",
- "pages": [
- "zh-hans/guides/tools/readme",
- "zh-hans/guides/tools/quick-tool-integration",
- "zh-hans/guides/tools/advanced-tool-integration",
- "zh-hans/guides/tools/tool-configuration/readme",
- {
- "group": "工具配置详情",
- "pages": [
- "zh-hans/guides/tools/tool-configuration/google",
- "zh-hans/guides/tools/tool-configuration/bing",
- "zh-hans/guides/tools/tool-configuration/searchapi",
- "zh-hans/guides/tools/tool-configuration/stable-diffusion",
- "zh-hans/guides/tools/tool-configuration/dall-e",
- "zh-hans/guides/tools/tool-configuration/perplexity",
- "zh-hans/guides/tools/tool-configuration/alphavantage",
- "zh-hans/guides/tools/tool-configuration/searxng",
- "zh-hans/guides/tools/tool-configuration/serper",
- "zh-hans/guides/tools/tool-configuration/siliconflow",
- "zh-hans/guides/tools/tool-configuration/comfyui"
- ]
- }
- ]
- },
- {
- "group": "发布",
- "pages": [
- {
- "group": "发布为公开 Web 站点",
- "pages": [
- "zh-hans/guides/application-publishing/launch-your-webapp-quickly/readme",
- "zh-hans/guides/application-publishing/launch-your-webapp-quickly/web-app-settings",
- "zh-hans/guides/application-publishing/launch-your-webapp-quickly/text-generator",
- "zh-hans/guides/application-publishing/launch-your-webapp-quickly/conversation-application"
- ]
- },
- "zh-hans/guides/application-publishing/embedding-in-websites",
- "zh-hans/guides/application-publishing/developing-with-apis",
- "zh-hans/guides/application-publishing/based-on-frontend-templates"
- ]
- },
- {
- "group": "标注",
- "pages": [
- "zh-hans/guides/annotation/logs",
- "zh-hans/guides/annotation/annotation-reply"
- ]
- },
- {
- "group": "监测",
- "pages": [
- "zh-hans/guides/monitoring/README",
- {
- "group": "集成外部与 Ops 工具",
- "pages": [
- "zh-hans/guides/monitoring/integrate-external-ops-tools/readme",
- "zh-hans/guides/monitoring/integrate-external-ops-tools/integrate-langfuse",
- "zh-hans/guides/monitoring/integrate-external-ops-tools/integrate-langsmith",
- "zh-hans/guides/monitoring/integrate-external-ops-tools/integrate-opik",
- "zh-hans/guides/monitoring/integrate-external-ops-tools/integrate-weave"
- ]
- },
- "zh-hans/guides/monitoring/analysis"
- ]
- },
- {
- "group": "扩展",
- "pages": [
- "zh-hans/guides/tools/extensions/README",
- {
- "group": "API 扩展",
- "pages": [
- "zh-hans/guides/tools/extensions/api-based/api-based-extension",
- "zh-hans/guides/tools/extensions/api-based/external-data-tool",
- "zh-hans/guides/tools/extensions/api-based/cloudflare-workers",
- "zh-hans/guides/tools/extensions/api-based/moderation"
- ]
- },
- {
- "group": "代码扩展",
- "pages": [
- "zh-hans/guides/tools/extensions/code-based/README",
- "zh-hans/guides/tools/extensions/code-based/external-data-tool",
- "zh-hans/guides/tools/extensions/code-based/moderation"
- ]
- }
- ]
- },
- {
- "group": "协同",
- "pages": [
- "zh-hans/guides/workspace/readme",
- "zh-hans/guides/workspace/app",
- "zh-hans/guides/workspace/invite-and-manage-members"
- ]
- },
- {
- "group": "管理",
- "pages": [
- "zh-hans/guides/management/app-management",
- "zh-hans/guides/management/team-members-management",
- "zh-hans/guides/management/personal-account-management",
- "zh-hans/guides/management/subscription-management",
- "zh-hans/guides/management/version-control"
- ]
- }
- ]
- },
- {
- "group": "动手实验室",
- "pages": [
- "zh-hans/workshop/readme",
- {
- "group": "初级",
- "pages": [
- "zh-hans/workshop/basic/build-ai-image-generation-app",
- "zh-hans/workshop/basic/travel-assistant"
- ]
- },
- {
- "group": "中级",
- "pages": [
- "zh-hans/workshop/intermediate/article-reader",
- "zh-hans/workshop/intermediate/customer-service-bot",
- "zh-hans/workshop/intermediate/twitter-chatflow"
- ]
- }
- ]
- },
- {
- "group": "社区",
- "pages": [
- "zh-hans/community/support",
- "zh-hans/community/contribution",
- "zh-hans/community/docs-contribution"
- ]
- },
- {
- "group": "插件",
- "pages": [
- "zh-hans/plugins/introduction",
- {
- "group": "快速开始",
- "pages": [
- "zh-hans/plugins/quick-start/README",
- "zh-hans/plugins/quick-start/install-plugins",
- {
- "group": "插件开发",
- "pages": [
- "zh-hans/plugins/quick-start/develop-plugins/README",
- "zh-hans/plugins/quick-start/develop-plugins/initialize-development-tools",
- "zh-hans/plugins/quick-start/develop-plugins/tool-plugin",
- {
- "group": "Model 插件",
- "pages": [
- "zh-hans/plugins/quick-start/develop-plugins/model-plugin/README",
- "zh-hans/plugins/quick-start/develop-plugins/model-plugin/create-model-providers",
- "zh-hans/plugins/quick-start/develop-plugins/model-plugin/integrate-the-predefined-model",
- "zh-hans/plugins/quick-start/develop-plugins/model-plugin/customizable-model"
- ]
- },
- "zh-hans/plugins/quick-start/develop-plugins/agent-strategy-plugin",
- "zh-hans/plugins/quick-start/develop-plugins/extension-plugin",
- "zh-hans/plugins/quick-start/develop-plugins/bundle"
- ]
- },
- "zh-hans/plugins/quick-start/debug-plugin"
- ]
- },
- "zh-hans/plugins/manage-plugins",
- {
- "group": "接口定义",
- "pages": [
- "zh-hans/plugins/schema-definition/manifest",
- "zh-hans/plugins/schema-definition/endpoint",
- "zh-hans/plugins/schema-definition/tool",
- "zh-hans/plugins/schema-definition/agent",
- {
- "group": "Model",
- "pages": [
- "zh-hans/plugins/schema-definition/model/model-designing-rules",
- "zh-hans/plugins/schema-definition/model/model-schema"
- ]
- },
- "zh-hans/plugins/schema-definition/general-specifications",
- "zh-hans/plugins/schema-definition/persistent-storage",
- {
- "group": "反向调用 Dify 服务",
- "pages": [
- "zh-hans/plugins/schema-definition/reverse-invocation-of-the-dify-service/README",
- "zh-hans/plugins/schema-definition/reverse-invocation-of-the-dify-service/app",
- "zh-hans/plugins/schema-definition/reverse-invocation-of-the-dify-service/model",
- "zh-hans/plugins/schema-definition/reverse-invocation-of-the-dify-service/tool",
- "zh-hans/plugins/schema-definition/reverse-invocation-of-the-dify-service/node"
- ]
- }
- ]
- },
- {
- "group": "最佳实践",
- "pages": [
- "zh-hans/plugins/best-practice/develop-a-slack-bot-plugin",
- "zh-hans/plugins/best-practice/how-to-use-mcp-zapier"
- ]
- },
- {
- "group": "发布插件",
- "pages": [
- "zh-hans/plugins/publish-plugins/README",
- "zh-hans/plugins/publish-plugins/plugin-auto-publish-pr",
- {
- "group": "发布至 Dify Marketplace",
- "pages": [
- "zh-hans/plugins/publish-plugins/publish-to-dify-marketplace/README",
- "zh-hans/plugins/publish-plugins/publish-to-dify-marketplace/plugin-developer-guidelines",
- "zh-hans/plugins/publish-plugins/publish-to-dify-marketplace/plugin-privacy-protection-guidelines"
- ]
- },
- "zh-hans/plugins/publish-plugins/publish-plugin-on-personal-github-repo",
- "zh-hans/plugins/publish-plugins/package-plugin-file-and-publish",
- "zh-hans/plugins/publish-plugins/signing-plugins-for-third-party-signature-verification"
- ]
- },
- "zh-hans/plugins/faq"
- ]
- },
- {
- "group": "研发",
- "pages": [
- {
- "group": "DifySandbox",
- "pages": [
- "zh-hans/development/backend/sandbox/README",
- "zh-hans/development/backend/sandbox/contribution"
- ]
- },
- {
- "group": "模型接入",
- "pages": [
- "zh-hans/development/models-integration/hugging-face",
- "zh-hans/development/models-integration/replicate",
- "zh-hans/development/models-integration/xinference",
- "zh-hans/development/models-integration/openllm",
- "zh-hans/development/models-integration/localai",
- "zh-hans/development/models-integration/ollama",
- "zh-hans/development/models-integration/litellm",
- "zh-hans/development/models-integration/gpustack",
- "zh-hans/development/models-integration/aws-bedrock-deepseek"
- ]
- },
- {
- "group": "迁移",
- "pages": [
- "zh-hans/development/migration/migrate-to-v1"
- ]
- }
- ]
- },
- {
- "group": "阅读更多",
- "pages": [
- {
- "group": "应用案例",
- "pages": [
- "zh-hans/learn-more/use-cases/integrate-deepseek-to-build-an-ai-app",
- "zh-hans/learn-more/use-cases/private-ai-ollama-deepseek-dify",
- "zh-hans/learn-more/use-cases/train-a-qa-chatbot-that-belongs-to-you",
- "zh-hans/learn-more/use-cases/create-a-midjoureny-prompt-word-robot-with-zero-code",
- "zh-hans/learn-more/use-cases/build-an-notion-ai-assistant",
- "zh-hans/learn-more/use-cases/building-an-ai-thesis-slack-bot",
- "zh-hans/learn-more/use-cases/connect-dify-to-various-im-platforms-by-using-langbot",
- "zh-hans/learn-more/use-cases/create-an-ai-chatbot-with-business-data-in-minutes",
- "zh-hans/learn-more/use-cases/practical-implementation-of-building-llm-applications-using-a-full-set-of-open-source-tools",
- "zh-hans/learn-more/use-cases/dify-on-wechat",
- "zh-hans/learn-more/use-cases/dify-on-dingtalk",
- "zh-hans/learn-more/use-cases/dify-on-teams",
- "zh-hans/learn-more/use-cases/how-to-make-llm-app-provide-a-progressive-chat-experience",
- "zh-hans/learn-more/use-cases/how-to-integrate-dify-chatbot-to-your-wix-website",
- "zh-hans/learn-more/use-cases/how-to-connect-aws-bedrock",
- "zh-hans/learn-more/use-cases/dify-schedule",
- "zh-hans/learn-more/use-cases/dify-model-arena"
- ]
- },
- {
- "group": "扩展阅读",
- "pages": [
- "zh-hans/learn-more/extended-reading/what-is-llmops",
- "zh-hans/learn-more/extended-reading/what-is-array-variable",
- {
- "group": "检索增强生成(RAG)",
- "pages": [
- "zh-hans/learn-more/extended-reading/retrieval-augment/README",
- "zh-hans/learn-more/extended-reading/retrieval-augment/hybrid-search",
- "zh-hans/learn-more/extended-reading/retrieval-augment/rerank",
- "zh-hans/learn-more/extended-reading/retrieval-augment/retrieval"
- ]
- },
- "zh-hans/learn-more/extended-reading/prompt-engineering",
- "zh-hans/learn-more/extended-reading/how-to-use-json-schema-in-dify"
- ]
- },
- {
- "group": "常见问题",
- "pages": [
- "zh-hans/learn-more/faq/install-faq",
- "zh-hans/learn-more/faq/llms-use-faq",
- "zh-hans/learn-more/faq/plugins"
- ]
- }
- ]
- },
- {
- "group": "政策",
- "pages": [
- "zh-hans/policies/open-source",
- {
- "group": "用户协议",
- "pages": [
- "zh-hans/policies/agreement/README",
- "zh-hans/policies/agreement/tos",
- "zh-hans/policies/agreement/privacy",
- "zh-hans/policies/agreement/get-compliance-report"
- ]
- }
- ]
- }
- ]
- },
- {
- "tab": "术语表",
- "pages": [
- "zh-hans/termbase/termbase"
- ]
- }
+ },
+ "contextual": {
+ "options": [
+ "copy",
+ "view"
]
- },
- {
- "version": "日本語",
- "tabs": [
- {
- "tab": "ドキュメント",
- "groups": [
- {
- "group": "入門",
- "pages": [
- {
- "group": "Difyへようこそ",
- "pages": [
- "ja-jp/introduction",
- "ja-jp/getting-started/readme/features-and-specifications",
- "ja-jp/getting-started/readme/model-providers"
- ]
- },
- "ja-jp/getting-started/cloud",
- {
- "group": "Dify コミュニティ版",
- "pages": [
- "ja-jp/getting-started/install-self-hosted/README",
- "ja-jp/getting-started/install-self-hosted/docker-compose",
- "ja-jp/getting-started/install-self-hosted/local-source-code",
- "ja-jp/getting-started/install-self-hosted/bt-panel",
- "ja-jp/getting-started/install-self-hosted/start-the-frontend-docker-container",
- "ja-jp/getting-started/install-self-hosted/environments",
- "ja-jp/getting-started/install-self-hosted/faq"
- ]
- },
- "ja-jp/getting-started/dify-premium",
- "ja-jp/getting-started/dify-for-education"
- ]
- },
- {
- "group": "マニュアル",
- "pages": [
- {
- "group": "モデル",
- "pages": [
- "ja-jp/guides/model-configuration/README",
- "ja-jp/guides/model-configuration/new-provider",
- "ja-jp/guides/model-configuration/predefined-model",
- "ja-jp/guides/model-configuration/customizable-model",
- "ja-jp/guides/model-configuration/interfaces",
- "ja-jp/guides/model-configuration/schema",
- "ja-jp/guides/model-configuration/load-balancing"
- ]
- },
- {
- "group": "アプリ・オーケストレーション",
- "pages": [
- "ja-jp/guides/application-orchestrate/README",
- "ja-jp/guides/application-orchestrate/creating-an-application",
- {
- "group": "チャットボット",
- "pages": [
- "ja-jp/guides/application-orchestrate/chatbot-application",
- "ja-jp/guides/application-orchestrate/multiple-llms-debugging"
- ]
- },
- "ja-jp/guides/application-orchestrate/agent",
- {
- "group": "ツールキット",
- "pages": [
- "ja-jp/guides/application-orchestrate/app-toolkits/README",
- "ja-jp/guides/application-orchestrate/app-toolkits/moderation-tool"
- ]
- }
- ]
- },
- {
- "group": "ワークフロー",
- "pages": [
- "ja-jp/guides/workflow/concepts",
- "ja-jp/guides/workflow/variables",
- {
- "group": "ノードの説明",
- "pages": [
- "ja-jp/guides/workflow/node/start",
- "ja-jp/guides/workflow/node/end",
- "ja-jp/guides/workflow/node/answer",
- "ja-jp/guides/workflow/node/llm",
- "ja-jp/guides/workflow/node/knowledge-retrieval",
- "ja-jp/guides/workflow/node/question-classifier",
- "ja-jp/guides/workflow/node/ifelse",
- "ja-jp/guides/workflow/node/code",
- "ja-jp/guides/workflow/node/template",
- "ja-jp/guides/workflow/node/doc-extractor",
- "ja-jp/guides/workflow/node/list-operator",
- "ja-jp/guides/workflow/node/variable-aggregator",
- "ja-jp/guides/workflow/node/variable-assigner",
- "ja-jp/guides/workflow/node/iteration",
- "ja-jp/guides/workflow/node/parameter-extractor",
- "ja-jp/guides/workflow/node/http-request",
- "ja-jp/guides/workflow/node/agent",
- "ja-jp/guides/workflow/node/tools",
- "ja-jp/guides/workflow/node/loop"
- ]
- },
- "ja-jp/guides/workflow/shortcut-key",
- "ja-jp/guides/workflow/orchestrate-node",
- "ja-jp/guides/workflow/file-upload",
- {
- "group": "エラー処理",
- "pages": [
- "ja-jp/guides/workflow/error-handling/README",
- "ja-jp/guides/workflow/error-handling/predefined-nodes-failure-logic",
- "ja-jp/guides/workflow/error-handling/error-type"
- ]
- },
- "ja-jp/guides/workflow/additional-feature",
- {
- "group": "プレビューとデバッグ",
- "pages": [
- "ja-jp/guides/workflow/debug-and-preview/step-run",
- "ja-jp/guides/workflow/debug-and-preview/log",
- "ja-jp/guides/workflow/debug-and-preview/checklist",
- "ja-jp/guides/workflow/debug-and-preview/history"
- ]
- },
- "ja-jp/guides/workflow/publish",
- "ja-jp/guides/workflow/structured-outputs",
- "ja-jp/guides/workflow/bulletin"
- ]
- },
- {
- "group": "ナレッジベース",
- "pages": [
- "ja-jp/guides/knowledge-base/readme",
- {
- "group": "ナレッジベース作成",
- "pages": [
- "ja-jp/guides/knowledge-base/knowledge-base-creation/introduction",
- {
- "group": "1. オンラインデータソースの活用",
- "pages": [
- "ja-jp/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/readme",
- "ja-jp/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-notion",
- "ja-jp/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website"
- ]
- },
- "ja-jp/guides/knowledge-base/create-knowledge-and-upload-documents/chunking-and-cleaning-text",
- "ja-jp/guides/knowledge-base/create-knowledge-and-upload-documents/setting-indexing-methods"
- ]
- },
- {
- "group": "ナレッジベースの管理",
- "pages": [
- "ja-jp/guides/knowledge-base/knowledge-and-documents-maintenance/readme",
- "ja-jp/guides/knowledge-base/knowledge-and-documents-maintenance/maintain-knowledge-documents",
- "ja-jp/guides/knowledge-base/knowledge-and-documents-maintenance/maintain-dataset-via-api"
- ]
- },
- "ja-jp/guides/knowledge-base/metadata",
- "ja-jp/guides/knowledge-base/integrate-knowledge-within-application",
- "ja-jp/guides/knowledge-base/retrieval-test-and-citation",
- "ja-jp/guides/knowledge-base/knowledge-request-rate-limit",
- "ja-jp/guides/knowledge-base/connect-external-knowledge-base",
- "ja-jp/guides/knowledge-base/api-documentation/external-knowledge-api-documentation"
- ]
- },
- {
- "group": "アプリ公開",
- "pages": [
- {
- "group": "シングルページWebアプリとして公開",
- "pages": [
- "ja-jp/guides/application-publishing/launch-your-webapp-quickly/web-app-settings",
- "ja-jp/guides/application-publishing/launch-your-webapp-quickly/text-generator",
- "ja-jp/guides/application-publishing/launch-your-webapp-quickly/conversation-application"
- ]
- },
- "ja-jp/guides/application-publishing/embedding-in-websites",
- "ja-jp/guides/application-publishing/developing-with-apis",
- "ja-jp/guides/application-publishing/based-on-frontend-templates"
- ]
- },
- {
- "group": "アノテーション",
- "pages": [
- "ja-jp/guides/annotation/logs",
- "ja-jp/guides/annotation/annotation-reply"
- ]
- },
- {
- "group": "モニタリング",
- "pages": [
- "ja-jp/guides/monitoring/analysis",
- {
- "group": "外部Opsツール統合",
- "pages": [
- "ja-jp/guides/monitoring/integrate-external-ops-tools/integrate-langfuse",
- "ja-jp/guides/monitoring/integrate-external-ops-tools/integrate-langsmith",
- "ja-jp/guides/monitoring/integrate-external-ops-tools/integrate-opik",
- "ja-jp/guides/monitoring/integrate-external-ops-tools/integrate-weave"
- ]
- }
- ]
- },
- {
- "group": "拡張",
- "pages": [
- {
- "group": "API 拡張",
- "pages": [
- "ja-jp/guides/extension/api-based-extension/README",
- "ja-jp/guides/extension/api-based-extension/cloudflare-workers",
- "ja-jp/guides/extension/api-based-extension/moderation"
- ]
- },
- {
- "group": "コード拡張",
- "pages": [
- "ja-jp/guides/extension/code-based-extension/external-data-tool",
- "ja-jp/guides/extension/code-based-extension/moderation"
- ]
- }
- ]
- },
- {
- "group": "コラボレーション",
- "pages": [
- "ja-jp/guides/workspace/readme",
- "ja-jp/guides/workspace/app",
- "ja-jp/guides/workspace/invite-and-manage-members"
- ]
- },
- {
- "group": "管理",
- "pages": [
- "ja-jp/guides/management/app-management",
- "ja-jp/guides/management/team-members-management",
- "ja-jp/guides/management/personal-account-management",
- "ja-jp/guides/management/subscription-management",
- "ja-jp/guides/management/version-control"
- ]
- }
- ]
- },
- {
- "group": "ハンドオン工房",
- "pages": [
- {
- "group": "初級編",
- "pages": [
- "ja-jp/workshop/basic/README",
- "ja-jp/workshop/basic/build-ai-image-generation-app",
- "ja-jp/workshop/basic/travel-assistant"
- ]
- },
- {
- "group": "中級編",
- "pages": [
- "ja-jp/workshop/intermediate/article-reader",
- "ja-jp/workshop/intermediate/twitter-chatflow"
- ]
- }
- ]
- },
- {
- "group": "コミュニティ",
- "pages": [
- "ja-jp/community/support",
- "ja-jp/community/contribution",
- "ja-jp/community/docs-contribution"
- ]
- },
- {
- "group": "プラグイン",
- "pages": [
- "ja-jp/plugins/introduction",
- {
- "group": "クイックスタート",
- "pages": [
- "ja-jp/plugins/quick-start/README",
- "ja-jp/plugins/quick-start/install-plugins",
- {
- "group": "プラグイン開発の入門",
- "pages": [
- "ja-jp/plugins/quick-start/develop-plugins/README",
- "ja-jp/plugins/quick-start/develop-plugins/initialize-development-tools",
- "ja-jp/plugins/quick-start/develop-plugins/tool-plugin",
- {
- "group": "モデル型プラグイン",
- "pages": [
- "ja-jp/plugins/quick-start/develop-plugins/model-plugin/README",
- "ja-jp/plugins/quick-start/develop-plugins/model-plugin/create-model-providers",
- "ja-jp/plugins/quick-start/develop-plugins/model-plugin/integrate-the-predefined-model",
- "ja-jp/plugins/quick-start/develop-plugins/model-plugin/customizable-model"
- ]
- },
- "ja-jp/plugins/quick-start/develop-plugins/agent-strategy-plugin",
- "ja-jp/plugins/quick-start/develop-plugins/extension-plugin",
- "ja-jp/plugins/quick-start/develop-plugins/bundle"
- ]
- },
- "ja-jp/plugins/quick-start/debug-plugin"
- ]
- },
- "ja-jp/plugins/manage-plugins",
- {
- "group": "スキーマ仕様",
- "pages": [
- "ja-jp/plugins/schema-definition/README",
- "ja-jp/plugins/schema-definition/manifest",
- "ja-jp/plugins/schema-definition/endpoint",
- "ja-jp/plugins/schema-definition/tool",
- "ja-jp/plugins/schema-definition/agent",
- {
- "group": "Model",
- "pages": [
- "ja-jp/plugins/schema-definition/model/README",
- "ja-jp/plugins/schema-definition/model/model-designing-rules",
- "ja-jp/plugins/schema-definition/model/model-schema"
- ]
- },
- "ja-jp/plugins/schema-definition/general-specifications",
- "ja-jp/plugins/schema-definition/persistent-storage",
- {
- "group": "Difyサービスの逆呼び出し",
- "pages": [
- "ja-jp/plugins/schema-definition/reverse-invocation-of-the-dify-service/README",
- "ja-jp/plugins/schema-definition/reverse-invocation-of-the-dify-service/app",
- "ja-jp/plugins/schema-definition/reverse-invocation-of-the-dify-service/model",
- "ja-jp/plugins/schema-definition/reverse-invocation-of-the-dify-service/tool",
- "ja-jp/plugins/schema-definition/reverse-invocation-of-the-dify-service/node"
- ]
- }
- ]
- },
- {
- "group": "ベストプラクティス",
- "pages": [
- "ja-jp/plugins/best-practice/README",
- "ja-jp/plugins/best-practice/develop-a-slack-bot-plugin",
- "ja-jp/plugins/best-practice/how-to-use-mcp-zapier"
- ]
- },
- {
- "group": "プラグインの公開",
- "pages": [
- "ja-jp/plugins/publish-plugins/README",
- "ja-jp/plugins/publish-plugins/plugin-auto-publish-pr",
- {
- "group": "Difyマーケットプレイスへの公開",
- "pages": [
- "ja-jp/plugins/publish-plugins/publish-to-dify-marketplace/README",
- "ja-jp/plugins/publish-plugins/publish-to-dify-marketplace/plugin-developer-guidelines",
- "ja-jp/plugins/publish-plugins/publish-to-dify-marketplace/plugin-privacy-protection-guidelines"
- ]
- },
- "ja-jp/plugins/publish-plugins/publish-plugin-on-personal-github-repo",
- "ja-jp/plugins/publish-plugins/package-plugin-file-and-publish",
- "ja-jp/plugins/publish-plugins/signing-plugins-for-third-party-signature-verification"
- ]
- },
- "ja-jp/plugins/faq"
- ]
- },
- {
- "group": "開発",
- "pages": [
- {
- "group": "バックエンド",
- "pages": [
- "ja-jp/development/backend/sandbox/README",
- "ja-jp/development/backend/sandbox/contribution"
- ]
- },
- {
- "group": "モデルの統合",
- "pages": [
- "ja-jp/development/models-integration/hugging-face",
- "ja-jp/development/models-integration/replicate",
- "ja-jp/development/models-integration/xinference",
- "ja-jp/development/models-integration/openllm",
- "ja-jp/development/models-integration/localai",
- "ja-jp/development/models-integration/ollama",
- "ja-jp/development/models-integration/litellm",
- "ja-jp/development/models-integration/gpustack",
- "ja-jp/development/models-integration/aws-bedrock-deepseek"
- ]
- },
- {
- "group": "移行",
- "pages": [
- "ja-jp/development/migration/migrate-to-v1"
- ]
- }
- ]
- },
- {
- "group": "もっと読む",
- "pages": [
- {
- "group": "活用事例",
- "pages": [
- "ja-jp/learn-more/use-cases/integrate-deepseek-to-build-an-ai-app",
- "ja-jp/learn-more/use-cases/private-ai-ollama-deepseek-dify",
- "ja-jp/learn-more/use-cases/create-a-midjoureny-prompt-word-robot-with-zero-code",
- "ja-jp/learn-more/use-cases/build-an-notion-ai-assistant",
- "ja-jp/learn-more/use-cases/building-an-ai-thesis-slack-bot",
- "ja-jp/learn-more/use-cases/create-an-ai-chatbot-with-business-data-in-minutes",
- "ja-jp/learn-more/use-cases/how-to-integrate-dify-chatbot-to-your-wix-website",
- "ja-jp/learn-more/use-cases/how-to-connect-aws-bedrock",
- "ja-jp/learn-more/use-cases/dify-model-arena",
- "ja-jp/learn-more/use-cases/dify-schedule",
- "ja-jp/learn-more/use-cases/building-an-ai-thesis-slack-bot"
- ]
- },
- {
- "group": "さらに読む",
- "pages": [
- "ja-jp/learn-more/extended-reading/what-is-llmops",
- "ja-jp/learn-more/extended-reading/what-is-array-variable",
- {
- "group": "检索增强生成(RAG)",
- "pages": [
- "ja-jp/learn-more/extended-reading/retrieval-augment/README",
- "ja-jp/learn-more/extended-reading/retrieval-augment/hybrid-search",
- "ja-jp/learn-more/extended-reading/retrieval-augment/rerank",
- "ja-jp/learn-more/extended-reading/retrieval-augment/retrieval"
- ]
- },
- "ja-jp/learn-more/extended-reading/how-to-use-json-schema-in-dify"
- ]
- },
- {
- "group": "常见问题",
- "pages": [
- "ja-jp/learn-more/faq/README",
- "ja-jp/learn-more/faq/install-faq",
- "ja-jp/learn-more/faq/llms-use-faq",
- "ja-jp/learn-more/faq/plugins"
- ]
- }
- ]
- },
- {
- "group": "ポリシー",
- "pages": [
- "ja-jp/policies/open-source",
- {
- "group": "ユーザ規約",
- "pages": [
- "ja-jp/policies/agreement/README",
- "ja-jp/policies/agreement/tos",
- "ja-jp/policies/agreement/privacy",
- "ja-jp/policies/agreement/get-compliance-report"
- ]
- }
- ]
- }
- ]
- },
- {
- "tab": "用語ベース",
- "pages": [
- "ja-jp/termbase/termbase"
- ]
- }
- ]
- },
- {
- "version": "Archived",
- "href": "https://legacy-docs.dify.ai/"
- }
- ]
- },
- "contextual": {
- "options": [
- "copy",
- "view"
- ]
- },
- "redirects": [
- {
- "source": "plugins/schema-definition/model",
- "destination": "en/plugins/schema-definition/model/model-designing-rules"
},
- {
- "source": "plugins/schema-definition",
- "destination": "en/plugins/schema-definition/manifest"
- },
- {
- "source": "guides/application-orchestrate/app-toolkits",
- "destination": "guides/application-orchestrate/app-toolkits/readme"
- },
- {
- "source": "ja-jp/plugins/plugin-auto-publish-pr",
- "destination": "ja-jp/plugins/publish-plugins/plugin-auto-publish-pr"
- },
- {
- "source": "zh-hans/plugins/plugin-auto-publish-pr",
- "destination": "zh-hans/plugins/publish-plugins/plugin-auto-publish-pr"
- },
- {
- "source": "en/plugins/plugin-auto-publish-pr",
- "destination": "en/plugins/publish-plugins/plugin-auto-publish-pr"
- },
- {
- "source": "zh-hans/getting-started/install-self-hosted",
- "destination": "zh-hans/getting-started/install-self-hosted/readme"
- },
- {
- "source": "zh-hans/guides/model-configuration",
- "destination": "zh-hans/guides/model-configuration/readme"
- },
- {
- "source": "zh-hans/guides/application-orchestrate",
- "destination": "zh-hans/guides/application-orchestrate/readme"
- },
- {
- "source": "zh-hans/guides/application-orchestrate/app-toolkits",
- "destination": "zh-hans/guides/application-orchestrate/app-toolkits/readme"
- },
- {
- "source": "zh-hans/guides/workflow",
- "destination": "zh-hans/guides/workflow/readme"
- },
- {
- "source": "zh-hans/guides/workflow/node",
- "destination": "zh-hans/guides/workflow/node/start"
- },
- {
- "source": "zh-hans/guides/workflow/error-handling",
- "destination": "zh-hans/guides/workflow/error-handling/readme"
- },
- {
- "source": "zh-hans/guides/workflow/debug-and-preview",
- "destination": "zh-hans/guides/workflow/debug-and-preview/preview-and-run"
- },
- {
- "source": "zh-hans/guides/workflow/debug-and-preview/yu-lan-yu-yun-hang",
- "destination": "zh-hans/guides/workflow/debug-and-preview/preview-and-run"
- },
- {
- "source": "zh-hans/guides/knowledge-base",
- "destination": "zh-hans/guides/knowledge-base/readme"
- },
- {
- "source": "zh-hans/guides/knowledge-base/create-knowledge-and-upload-documents",
- "destination": "zh-hans/guides/knowledge-base/knowledge-base-creation/introduction"
- },
- {
- "source": "zh-hans/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data",
- "destination": "zh-hans/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/readme"
- },
- {
- "source": "zh-hans/guides/knowledge-base/knowledge-and-documents-maintenance",
- "destination": "zh-hans/guides/knowledge-base/knowledge-and-documents-maintenance/introduction"
- },
- {
- "source": "zh-hans/guides/tools",
- "destination": "zh-hans/guides/tools/readme"
- },
- {
- "source": "zh-hans/guides/tools/tool-configuration",
- "destination": "zh-hans/guides/tools/tool-configuration/readme"
- },
- {
- "source": "zh-hans/guides/application-publishing",
- "destination": "zh-hans/guides/application-publishing/readme"
- },
- {
- "source": "zh-hans/guides/application-publishing/launch-your-webapp-quickly",
- "destination": "zh-hans/guides/application-publishing/launch-your-webapp-quickly/readme"
- },
- {
- "source": "zh-hans/guides/annotation",
- "destination": "zh-hans/guides/annotation/readme"
- },
- {
- "source": "zh-hans/guides/monitoring",
- "destination": "zh-hans/guides/monitoring/readme"
- },
- {
- "source": "zh-hans/guides/monitoring/integrate-external-ops-tools",
- "destination": "zh-hans/guides/monitoring/integrate-external-ops-tools/readme"
- },
- {
- "source": "zh-hans/guides/extension",
- "destination": "zh-hans/guides/extension/readme"
- },
- {
- "source": "zh-hans/guides/extension/api-based-extension",
- "destination": "zh-hans/guides/extension/api-based-extension/readme"
- },
- {
- "source": "zh-hans/guides/extension/code-based-extension",
- "destination": "zh-hans/guides/extension/code-based-extension/readme"
- },
- {
- "source": "zh-hans/guides/workspace",
- "destination": "zh-hans/guides/workspace/readme"
- },
- {
- "source": "zh-hans/guides/management",
- "destination": "zh-hans/guides/management/readme"
- },
- {
- "source": "zh-hans/workshop/basic",
- "destination": "zh-hans/workshop/readme"
- },
- {
- "source": "zh-hans/workshop/intermediate",
- "destination": "zh-hans/workshop/intermediate/readme"
- },
- {
- "source": "zh-hans/plugins/quick-start",
- "destination": "zh-hans/plugins/quick-start/readme"
- },
- {
- "source": "zh-hans/plugins/quick-start/develop-plugins",
- "destination": "zh-hans/plugins/quick-start/develop-plugins/readme"
- },
- {
- "source": "zh-hans/plugins/quick-start/develop-plugins/model-plugin",
- "destination": "zh-hans/plugins/quick-start/develop-plugins/model-plugin/readme"
- },
- {
- "source": "zh-hans/plugins/schema-definition",
- "destination": "zh-hans/plugins/schema-definition/readme"
- },
- {
- "source": "zh-hans/plugins/schema-definition/model",
- "destination": "zh-hans/plugins/schema-definition/model/readme"
- },
- {
- "source": "zh-hans/plugins/schema-definition/reverse-invocation-of-the-dify-service",
- "destination": "zh-hans/plugins/schema-definition/reverse-invocation-of-the-dify-service/readme"
- },
- {
- "source": "zh-hans/plugins/best-practice",
- "destination": "zh-hans/plugins/best-practice/develop-a-slack-bot-plugin"
- },
- {
- "source": "zh-hans/plugins/publish-plugins",
- "destination": "zh-hans/plugins/publish-plugins/README"
- },
- {
- "source": "zh-hans/plugins/publish-plugins/publish-to-dify-marketplace",
- "destination": "zh-hans/plugins/publish-plugins/publish-to-dify-marketplace/README"
- },
- {
- "source": "zh-hans/development/backend",
- "destination": "zh-hans/development/backend/readme"
- },
- {
- "source": "zh-hans/development/backend/sandbox",
- "destination": "zh-hans/development/backend/sandbox/readme"
- },
- {
- "source": "zh-hans/development/models-integration",
- "destination": "zh-hans/development/models-integration/readme"
- },
- {
- "source": "zh-hans/development/migration",
- "destination": "zh-hans/development/migration/readme"
- },
- {
- "source": "zh-hans/learn-more/use-cases",
- "destination": "zh-hans/learn-more/use-cases/readme"
- },
- {
- "source": "zh-hans/learn-more/extended-reading",
- "destination": "zh-hans/learn-more/extended-reading/readme"
- },
- {
- "source": "zh-hans/learn-more/extended-reading/retrieval-augment",
- "destination": "zh-hans/learn-more/extended-reading/retrieval-augment/readme"
- },
- {
- "source": "zh-hans/learn-more/faq",
- "destination": "zh-hans/learn-more/faq/readme"
- },
- {
- "source": "zh-hans/policies/agreement",
- "destination": "zh-hans/policies/agreement/readme"
- },
- {
- "source": "/zh-hans",
- "destination": "/zh-hans/introduction"
- },
- {
- "source": "/introduction",
- "destination": "/en/introduction"
- },
- {
- "source": "/getting-started/readme/features-and-specifications",
- "destination": "/en/getting-started/readme/features-and-specifications"
- },
- {
- "source": "/getting-started/readme/model-providers",
- "destination": "/en/getting-started/readme/model-providers"
- },
- {
- "source": "/getting-started/install-self-hosted/readme",
- "destination": "/en/getting-started/install-self-hosted/readme"
- },
- {
- "source": "/getting-started/install-self-hosted/docker-compose",
- "destination": "/en/getting-started/install-self-hosted/docker-compose"
- },
- {
- "source": "/getting-started/install-self-hosted/local-source-code",
- "destination": "/en/getting-started/install-self-hosted/local-source-code"
- },
- {
- "source": "/getting-started/install-self-hosted/bt-panel",
- "destination": "/en/getting-started/install-self-hosted/bt-panel"
- },
- {
- "source": "/getting-started/install-self-hosted/start-the-frontend-docker-container",
- "destination": "/en/getting-started/install-self-hosted/start-the-frontend-docker-container"
- },
- {
- "source": "/getting-started/install-self-hosted/environments",
- "destination": "/en/getting-started/install-self-hosted/environments"
- },
- {
- "source": "/getting-started/install-self-hosted/faqs",
- "destination": "/en/getting-started/install-self-hosted/faqs"
- },
- {
- "source": "/getting-started/cloud",
- "destination": "/en/getting-started/cloud"
- },
- {
- "source": "/getting-started/dify-premium",
- "destination": "/en/getting-started/dify-premium"
- },
- {
- "source": "/guides/model-configuration/readme",
- "destination": "/en/guides/model-configuration/readme"
- },
- {
- "source": "/guides/model-configuration/new-provider",
- "destination": "/en/guides/model-configuration/new-provider"
- },
- {
- "source": "/guides/model-configuration/predefined-model",
- "destination": "/en/guides/model-configuration/predefined-model"
- },
- {
- "source": "/guides/model-configuration/customizable-model",
- "destination": "/en/guides/model-configuration/customizable-model"
- },
- {
- "source": "/guides/model-configuration/interfaces",
- "destination": "/en/guides/model-configuration/interfaces"
- },
- {
- "source": "/guides/model-configuration/schema",
- "destination": "/en/guides/model-configuration/schema"
- },
- {
- "source": "/guides/model-configuration/load-balancing",
- "destination": "/en/guides/model-configuration/load-balancing"
- },
- {
- "source": "/guides/application-orchestrate/readme",
- "destination": "/en/guides/application-orchestrate/readme"
- },
- {
- "source": "/guides/application-orchestrate/creating-an-application",
- "destination": "/en/guides/application-orchestrate/creating-an-application"
- },
- {
- "source": "/guides/application-orchestrate/chatbot-application",
- "destination": "/en/guides/application-orchestrate/chatbot-application"
- },
- {
- "source": "/guides/application-orchestrate/multiple-llms-debugging",
- "destination": "/en/guides/application-orchestrate/multiple-llms-debugging"
- },
- {
- "source": "/guides/application-orchestrate/agent",
- "destination": "/en/guides/application-orchestrate/agent"
- },
- {
- "source": "/guides/application-orchestrate/app-toolkits/readme",
- "destination": "/en/guides/application-orchestrate/app-toolkits/readme"
- },
- {
- "source": "/guides/application-orchestrate/app-toolkits/moderation-tool",
- "destination": "/en/guides/application-orchestrate/app-toolkits/moderation-tool"
- },
- {
- "source": "/guides/workflow/readme",
- "destination": "/en/guides/workflow/readme"
- },
- {
- "source": "/guides/workflow/key-concepts",
- "destination": "/en/guides/workflow/key-concepts"
- },
- {
- "source": "/guides/workflow/variables",
- "destination": "/en/guides/workflow/variables"
- },
- {
- "source": "/guides/workflow/node/readme",
- "destination": "/en/guides/workflow/node/readme"
- },
- {
- "source": "/guides/workflow/node/start",
- "destination": "/en/guides/workflow/node/start"
- },
- {
- "source": "/guides/workflow/node/end",
- "destination": "/en/guides/workflow/node/end"
- },
- {
- "source": "/guides/workflow/node/answer",
- "destination": "/en/guides/workflow/node/answer"
- },
- {
- "source": "/guides/workflow/node/llm",
- "destination": "/en/guides/workflow/node/llm"
- },
- {
- "source": "/guides/workflow/node/knowledge-retrieval",
- "destination": "/en/guides/workflow/node/knowledge-retrieval"
- },
- {
- "source": "/guides/workflow/node/question-classifier",
- "destination": "/en/guides/workflow/node/question-classifier"
- },
- {
- "source": "/guides/workflow/node/ifelse",
- "destination": "/en/guides/workflow/node/ifelse"
- },
- {
- "source": "/guides/workflow/node/code",
- "destination": "/en/guides/workflow/node/code"
- },
- {
- "source": "/guides/workflow/node/template",
- "destination": "/en/guides/workflow/node/template"
- },
- {
- "source": "/guides/workflow/node/doc-extractor",
- "destination": "/en/guides/workflow/node/doc-extractor"
- },
- {
- "source": "/guides/workflow/node/list-operator",
- "destination": "/en/guides/workflow/node/list-operator"
- },
- {
- "source": "/guides/workflow/node/variable-aggregator",
- "destination": "/en/guides/workflow/node/variable-aggregator"
- },
- {
- "source": "/guides/workflow/node/variable-assigner",
- "destination": "/en/guides/workflow/node/variable-assigner"
- },
- {
- "source": "/guides/workflow/node/iteration",
- "destination": "/en/guides/workflow/node/iteration"
- },
- {
- "source": "/guides/workflow/node/parameter-extractor",
- "destination": "/en/guides/workflow/node/parameter-extractor"
- },
- {
- "source": "/guides/workflow/node/http-request",
- "destination": "/en/guides/workflow/node/http-request"
- },
- {
- "source": "/guides/workflow/node/agent",
- "destination": "/en/guides/workflow/node/agent"
- },
- {
- "source": "/guides/workflow/node/tools",
- "destination": "/en/guides/workflow/node/tools"
- },
- {
- "source": "/guides/workflow/node/loop",
- "destination": "/en/guides/workflow/node/loop"
- },
- {
- "source": "/guides/workflow/shortcut-key",
- "destination": "/en/guides/workflow/shortcut-key"
- },
- {
- "source": "/guides/workflow/orchestrate-node",
- "destination": "/en/guides/workflow/orchestrate-node"
- },
- {
- "source": "/guides/workflow/file-upload",
- "destination": "/en/guides/workflow/file-upload"
- },
- {
- "source": "/guides/workflow/error-handling/readme",
- "destination": "/en/guides/workflow/error-handling/readme"
- },
- {
- "source": "/guides/workflow/error-handling/predefined-error-handling-logic",
- "destination": "/en/guides/workflow/error-handling/predefined-error-handling-logic"
- },
- {
- "source": "/guides/workflow/error-handling/error-type",
- "destination": "/en/guides/workflow/error-handling/error-type"
- },
- {
- "source": "/guides/workflow/additional-features",
- "destination": "/en/guides/workflow/additional-features"
- },
- {
- "source": "/guides/workflow/debug-and-preview/readme",
- "destination": "/en/guides/workflow/debug-and-preview/readme"
- },
- {
- "source": "/guides/workflow/debug-and-preview/yu-lan-yu-yun-hang",
- "destination": "/en/guides/workflow/debug-and-preview/yu-lan-yu-yun-hang"
- },
- {
- "source": "/guides/workflow/debug-and-preview/preview-and-run",
- "destination": "/en/guides/workflow/debug-and-preview/preview-and-run"
- },
- {
- "source": "/guides/workflow/debug-and-preview/step-run",
- "destination": "/en/guides/workflow/debug-and-preview/step-run"
- },
- {
- "source": "/guides/workflow/debug-and-preview/log",
- "destination": "/en/guides/workflow/debug-and-preview/log"
- },
- {
- "source": "/guides/workflow/debug-and-preview/checklist",
- "destination": "/en/guides/workflow/debug-and-preview/checklist"
- },
- {
- "source": "/guides/workflow/debug-and-preview/history",
- "destination": "/en/guides/workflow/debug-and-preview/history"
- },
- {
- "source": "/guides/workflow/publish",
- "destination": "/en/guides/workflow/publish"
- },
- {
- "source": "/guides/workflow/bulletin",
- "destination": "/en/guides/workflow/bulletin"
- },
- {
- "source": "/guides/knowledge-base/readme",
- "destination": "/en/guides/knowledge-base/readme"
- },
- {
- "source": "/guides/knowledge-base/knowledge-base-creation/introduction",
- "destination": "/en/guides/knowledge-base/knowledge-base-creation/introduction"
- },
- {
- "source": "/guides/knowledge-base/create-knowledge-and-upload-documents",
- "destination": "/en/guides/knowledge-base/create-knowledge-and-upload-documents"
- },
- {
- "source": "/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/readme",
- "destination": "/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/readme"
- },
- {
- "source": "/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-notion",
- "destination": "/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-notion"
- },
- {
- "source": "/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website",
- "destination": "/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website"
- },
- {
- "source": "/guides/knowledge-base/create-knowledge-and-upload-documents/chunking-and-cleaning-text",
- "destination": "/en/guides/knowledge-base/create-knowledge-and-upload-documents/chunking-and-cleaning-text"
- },
- {
- "source": "/guides/knowledge-base/create-knowledge-and-upload-documents/setting-indexing-methods",
- "destination": "/en/guides/knowledge-base/create-knowledge-and-upload-documents/setting-indexing-methods"
- },
- {
- "source": "/guides/knowledge-base/knowledge-and-documents-maintenance/readme",
- "destination": "/en/guides/knowledge-base/knowledge-and-documents-maintenance/readme"
- },
- {
- "source": "/guides/knowledge-base/knowledge-and-documents-maintenance/introduction",
- "destination": "/en/guides/knowledge-base/knowledge-and-documents-maintenance/introduction"
- },
- {
- "source": "/guides/knowledge-base/knowledge-and-documents-maintenance/maintain-knowledge-documents",
- "destination": "/en/guides/knowledge-base/knowledge-and-documents-maintenance/maintain-knowledge-documents"
- },
- {
- "source": "/guides/knowledge-base/knowledge-and-documents-maintenance/maintain-dataset-via-api",
- "destination": "/en/guides/knowledge-base/knowledge-and-documents-maintenance/maintain-dataset-via-api"
- },
- {
- "source": "/guides/knowledge-base/metadata",
- "destination": "/en/guides/knowledge-base/metadata"
- },
- {
- "source": "/guides/knowledge-base/integrate-knowledge-within-application",
- "destination": "/en/guides/knowledge-base/integrate-knowledge-within-application"
- },
- {
- "source": "/guides/knowledge-base/retrieval-test-and-citation",
- "destination": "/en/guides/knowledge-base/retrieval-test-and-citation"
- },
- {
- "source": "/guides/knowledge-base/knowledge-request-rate-limit",
- "destination": "/en/guides/knowledge-base/knowledge-request-rate-limit"
- },
- {
- "source": "/guides/knowledge-base/connect-external-knowledge-base",
- "destination": "/en/guides/knowledge-base/connect-external-knowledge-base"
- },
- {
- "source": "/guides/knowledge-base/external-knowledge-api",
- "destination": "/en/guides/knowledge-base/external-knowledge-api"
- },
- {
- "source": "/guides/knowledge-base/external-knowledge-api-documentation",
- "destination": "/en/guides/knowledge-base/external-knowledge-api-documentation"
- },
- {
- "source": "/guides/tools/readme",
- "destination": "/en/guides/tools/readme"
- },
- {
- "source": "/guides/tools/quick-tool-integration",
- "destination": "/en/guides/tools/quick-tool-integration"
- },
- {
- "source": "/guides/tools/advanced-tool-integration",
- "destination": "/en/guides/tools/advanced-tool-integration"
- },
- {
- "source": "/guides/tools/tool-configuration/readme",
- "destination": "/en/guides/tools/tool-configuration/readme"
- },
- {
- "source": "/guides/tools/tool-configuration/google",
- "destination": "/en/guides/tools/tool-configuration/google"
- },
- {
- "source": "/guides/tools/tool-configuration/bing",
- "destination": "/en/guides/tools/tool-configuration/bing"
- },
- {
- "source": "/guides/tools/tool-configuration/searchapi",
- "destination": "/en/guides/tools/tool-configuration/searchapi"
- },
- {
- "source": "/guides/tools/tool-configuration/stable-diffusion",
- "destination": "/en/guides/tools/tool-configuration/stable-diffusion"
- },
- {
- "source": "/guides/tools/tool-configuration/dall-e",
- "destination": "/en/guides/tools/tool-configuration/dall-e"
- },
- {
- "source": "/guides/tools/tool-configuration/perplexity",
- "destination": "/en/guides/tools/tool-configuration/perplexity"
- },
- {
- "source": "/guides/tools/tool-configuration/alphavantage",
- "destination": "/en/guides/tools/tool-configuration/alphavantage"
- },
- {
- "source": "/guides/tools/tool-configuration/youtube",
- "destination": "/en/guides/tools/tool-configuration/youtube"
- },
- {
- "source": "/guides/tools/tool-configuration/searxng",
- "destination": "/en/guides/tools/tool-configuration/searxng"
- },
- {
- "source": "/guides/tools/tool-configuration/serper",
- "destination": "/en/guides/tools/tool-configuration/serper"
- },
- {
- "source": "/guides/tools/tool-configuration/siliconflow",
- "destination": "/en/guides/tools/tool-configuration/siliconflow"
- },
- {
- "source": "/guides/tools/tool-configuration/comfyui",
- "destination": "/en/guides/tools/tool-configuration/comfyui"
- },
- {
- "source": "/guides/application-publishing/readme",
- "destination": "/en/guides/application-publishing/readme"
- },
- {
- "source": "/guides/application-publishing/launch-your-webapp-quickly/readme",
- "destination": "/en/guides/application-publishing/launch-your-webapp-quickly/readme"
- },
- {
- "source": "/guides/application-publishing/launch-your-webapp-quickly/web-app-settings",
- "destination": "/en/guides/application-publishing/launch-your-webapp-quickly/web-app-settings"
- },
- {
- "source": "/guides/application-publishing/launch-your-webapp-quickly/text-generator",
- "destination": "/en/guides/application-publishing/launch-your-webapp-quickly/text-generator"
- },
- {
- "source": "/guides/application-publishing/launch-your-webapp-quickly/conversation-application",
- "destination": "/en/guides/application-publishing/launch-your-webapp-quickly/conversation-application"
- },
- {
- "source": "/guides/application-publishing/embedding-in-websites",
- "destination": "/en/guides/application-publishing/embedding-in-websites"
- },
- {
- "source": "/guides/application-publishing/developing-with-apis",
- "destination": "/en/guides/application-publishing/developing-with-apis"
- },
- {
- "source": "/guides/application-publishing/based-on-frontend-templates",
- "destination": "/en/guides/application-publishing/based-on-frontend-templates"
- },
- {
- "source": "/guides/annotation/readme",
- "destination": "/en/guides/annotation/readme"
- },
- {
- "source": "/guides/annotation/logs",
- "destination": "/en/guides/annotation/logs"
- },
- {
- "source": "/guides/annotation/annotation-reply",
- "destination": "/en/guides/annotation/annotation-reply"
- },
- {
- "source": "/guides/monitoring/readme",
- "destination": "/en/guides/monitoring/readme"
- },
- {
- "source": "/guides/monitoring/analysis",
- "destination": "/en/guides/monitoring/analysis"
- },
- {
- "source": "/guides/monitoring/integrate-external-ops-tools/readme",
- "destination": "/en/guides/monitoring/integrate-external-ops-tools/readme"
- },
- {
- "source": "/guides/monitoring/integrate-external-ops-tools/integrate-langsmith",
- "destination": "/en/guides/monitoring/integrate-external-ops-tools/integrate-langsmith"
- },
- {
- "source": "/guides/monitoring/integrate-external-ops-tools/integrate-langfuse",
- "destination": "/en/guides/monitoring/integrate-external-ops-tools/integrate-langfuse"
- },
- {
- "source": "/guides/monitoring/integrate-external-ops-tools/integrate-opik",
- "destination": "/en/guides/monitoring/integrate-external-ops-tools/integrate-opik"
- },
- {
- "source": "/guides/extension/readme",
- "destination": "/en/guides/extension/readme"
- },
- {
- "source": "/guides/extension/api-based-extension/readme",
- "destination": "/en/guides/extension/api-based-extension/readme"
- },
- {
- "source": "/guides/extension/api-based-extension/external-data-tool",
- "destination": "/en/guides/extension/api-based-extension/external-data-tool"
- },
- {
- "source": "/guides/extension/api-based-extension/cloudflare-workers",
- "destination": "/en/guides/extension/api-based-extension/cloudflare-workers"
- },
- {
- "source": "/guides/extension/api-based-extension/moderation",
- "destination": "/en/guides/extension/api-based-extension/moderation"
- },
- {
- "source": "/guides/extension/code-based-extension/readme",
- "destination": "/en/guides/extension/code-based-extension/readme"
- },
- {
- "source": "/guides/extension/code-based-extension/external-data-tool",
- "destination": "/en/guides/extension/code-based-extension/external-data-tool"
- },
- {
- "source": "/guides/extension/code-based-extension/moderation",
- "destination": "/en/guides/extension/code-based-extension/moderation"
- },
- {
- "source": "/guides/workspace/readme",
- "destination": "/en/guides/workspace/readme"
- },
- {
- "source": "/guides/workspace/app",
- "destination": "/en/guides/workspace/app"
- },
- {
- "source": "/guides/workspace/invite-and-manage-members",
- "destination": "/en/guides/workspace/invite-and-manage-members"
- },
- {
- "source": "/guides/management/readme",
- "destination": "/en/guides/management/readme"
- },
- {
- "source": "/guides/management/app-management",
- "destination": "/en/guides/management/app-management"
- },
- {
- "source": "/guides/management/team-members-management",
- "destination": "/en/guides/management/team-members-management"
- },
- {
- "source": "/guides/management/personal-account-management",
- "destination": "/en/guides/management/personal-account-management"
- },
- {
- "source": "/guides/management/subscription-management",
- "destination": "/en/guides/management/subscription-management"
- },
- {
- "source": "/guides/management/version-control",
- "destination": "/en/guides/management/version-control"
- },
- {
- "source": "/workshop/readme",
- "destination": "/en/workshop/readme"
- },
- {
- "source": "/workshop/basic/readme",
- "destination": "/en/workshop/basic/readme"
- },
- {
- "source": "/workshop/basic/build-ai-image-generation-app",
- "destination": "/en/workshop/basic/build-ai-image-generation-app"
- },
- {
- "source": "/workshop/intermediate/readme",
- "destination": "/en/workshop/intermediate/readme"
- },
- {
- "source": "/workshop/intermediate/article-reader",
- "destination": "/en/workshop/intermediate/article-reader"
- },
- {
- "source": "/workshop/intermediate/customer-service-bot",
- "destination": "/en/workshop/intermediate/customer-service-bot"
- },
- {
- "source": "/workshop/intermediate/twitter-chatflow",
- "destination": "/en/workshop/intermediate/twitter-chatflow"
- },
- {
- "source": "/community/support",
- "destination": "/en/community/support"
- },
- {
- "source": "/community/contribution",
- "destination": "/en/community/contribution"
- },
- {
- "source": "/community/docs-contribution",
- "destination": "/en/community/docs-contribution"
- },
- {
- "source": "/plugins/introduction",
- "destination": "/en/plugins/introduction"
- },
- {
- "source": "/plugins/quick-start/readme",
- "destination": "/en/plugins/quick-start/readme"
- },
- {
- "source": "/plugins/quick-start/install-plugins",
- "destination": "/en/plugins/quick-start/install-plugins"
- },
- {
- "source": "/plugins/quick-start/develop-plugins/readme",
- "destination": "/en/plugins/quick-start/develop-plugins/readme"
- },
- {
- "source": "/plugins/quick-start/develop-plugins/initialize-development-tools",
- "destination": "/en/plugins/quick-start/develop-plugins/initialize-development-tools"
- },
- {
- "source": "/plugins/quick-start/develop-plugins/tool-plugin",
- "destination": "/en/plugins/quick-start/develop-plugins/tool-plugin"
- },
- {
- "source": "/plugins/quick-start/develop-plugins/model-plugin/readme",
- "destination": "/en/plugins/quick-start/develop-plugins/model-plugin/readme"
- },
- {
- "source": "/plugins/quick-start/develop-plugins/model-plugin/create-model-providers",
- "destination": "/en/plugins/quick-start/develop-plugins/model-plugin/create-model-providers"
- },
- {
- "source": "/plugins/quick-start/develop-plugins/model-plugin/predefined-model",
- "destination": "/en/plugins/quick-start/develop-plugins/model-plugin/predefined-model"
- },
- {
- "source": "/plugins/quick-start/develop-plugins/model-plugin/customizable-model",
- "destination": "/en/plugins/quick-start/develop-plugins/model-plugin/customizable-model"
- },
- {
- "source": "/plugins/quick-start/develop-plugins/agent-strategy-plugin",
- "destination": "/en/plugins/quick-start/develop-plugins/agent-strategy-plugin"
- },
- {
- "source": "/plugins/quick-start/develop-plugins/extension-plugin",
- "destination": "/en/plugins/quick-start/develop-plugins/extension-plugin"
- },
- {
- "source": "/plugins/quick-start/develop-plugins/bundle",
- "destination": "/en/plugins/quick-start/develop-plugins/bundle"
- },
- {
- "source": "/plugins/quick-start/debug-plugin",
- "destination": "/en/plugins/quick-start/debug-plugin"
- },
- {
- "source": "/plugins/manage-plugins",
- "destination": "/en/plugins/manage-plugins"
- },
- {
- "source": "/plugins/schema-definition/readme",
- "destination": "/en/plugins/schema-definition/readme"
- },
- {
- "source": "/plugins/schema-definition/manifest",
- "destination": "/en/plugins/schema-definition/manifest"
- },
- {
- "source": "/plugins/schema-definition/endpoint",
- "destination": "/en/plugins/schema-definition/endpoint"
- },
- {
- "source": "/plugins/schema-definition/tool",
- "destination": "/en/plugins/schema-definition/tool"
- },
- {
- "source": "/plugins/schema-definition/agent",
- "destination": "/en/plugins/schema-definition/agent"
- },
- {
- "source": "/plugins/schema-definition/model/readme",
- "destination": "/en/plugins/schema-definition/model/readme"
- },
- {
- "source": "/plugins/schema-definition/model/model-designing-rules",
- "destination": "/en/plugins/schema-definition/model/model-designing-rules"
- },
- {
- "source": "/plugins/schema-definition/model/model-schema",
- "destination": "/en/plugins/schema-definition/model/model-schema"
- },
- {
- "source": "/plugins/schema-definition/general-specifications",
- "destination": "/en/plugins/schema-definition/general-specifications"
- },
- {
- "source": "/plugins/schema-definition/persistent-storage",
- "destination": "/en/plugins/schema-definition/persistent-storage"
- },
- {
- "source": "/plugins/schema-definition/reverse-invocation-of-the-dify-service/readme",
- "destination": "/en/plugins/schema-definition/reverse-invocation-of-the-dify-service/readme"
- },
- {
- "source": "/plugins/schema-definition/reverse-invocation-of-the-dify-service/app",
- "destination": "/en/plugins/schema-definition/reverse-invocation-of-the-dify-service/app"
- },
- {
- "source": "/plugins/schema-definition/reverse-invocation-of-the-dify-service/model",
- "destination": "/en/plugins/schema-definition/reverse-invocation-of-the-dify-service/model"
- },
- {
- "source": "/plugins/schema-definition/reverse-invocation-of-the-dify-service/tool",
- "destination": "/en/plugins/schema-definition/reverse-invocation-of-the-dify-service/tool"
- },
- {
- "source": "/plugins/schema-definition/reverse-invocation-of-the-dify-service/node",
- "destination": "/en/plugins/schema-definition/reverse-invocation-of-the-dify-service/node"
- },
- {
- "source": "/plugins/best-practice",
- "destination": "/en/plugins/best-practice/README"
- },
- {
- "source": "/plugins/best-practice/develop-a-slack-bot-plugin",
- "destination": "/en/plugins/best-practice/develop-a-slack-bot-plugin"
- },
- {
- "source": "/plugins/publish-plugins",
- "destination": "/en/plugins/publish-plugins/README"
- },
- {
- "source": "/plugins/publish-plugins/publish-to-dify-marketplace",
- "destination": "/en/plugins/publish-plugins/publish-to-dify-marketplace/README"
- },
- {
- "source": "/zh-hans/plugins/publish-plugins/publish-to-dify-marketplace",
- "destination": "/zh-hans/plugins/publish-plugins/publish-to-dify-marketplace/README"
- },
- {
- "source": "/ja-jp/plugins/publish-plugins/publish-to-dify-marketplace",
- "destination": "/ja-jp/plugins/publish-plugins/publish-to-dify-marketplace/README"
- },
- {
- "source": "/plugins/publish-plugins/publish-to-dify-marketplace/plugin-developer-guidelines",
- "destination": "/en/plugins/publish-plugins/publish-to-dify-marketplace/plugin-developer-guidelines"
- },
- {
- "source": "/plugins/publish-plugins/publish-to-dify-marketplace/plugin-privacy-protection-guidelines",
- "destination": "/en/plugins/publish-plugins/publish-to-dify-marketplace/plugin-privacy-protection-guidelines"
- },
- {
- "source": "/plugins/publish-plugins/publish-plugin-on-personal-github-repo",
- "destination": "/en/plugins/publish-plugins/publish-plugin-on-personal-github-repo"
- },
- {
- "source": "/plugins/publish-plugins/package-plugin-file-and-publish",
- "destination": "/en/plugins/publish-plugins/package-plugin-file-and-publish"
- },
- {
- "source": "/plugins/faq",
- "destination": "/en/plugins/faq"
- },
- {
- "source": "/development/backend",
- "destination": "/en/development/backend/sandbox/README"
- },
- {
- "source": "/development/backend/sandbox/readme",
- "destination": "/en/development/backend/sandbox/readme"
- },
- {
- "source": "/development/backend/sandbox/contribution",
- "destination": "/en/development/backend/sandbox/contribution"
- },
- {
- "source": "/development/models-integration",
- "destination": "/en/development/models-integration/hugging-face"
- },
- {
- "source": "/development/models-integration/hugging-face",
- "destination": "/en/development/models-integration/hugging-face"
- },
- {
- "source": "/development/models-integration/replicate",
- "destination": "/en/development/models-integration/replicate"
- },
- {
- "source": "/development/models-integration/xinference",
- "destination": "/en/development/models-integration/xinference"
- },
- {
- "source": "/development/models-integration/openllm",
- "destination": "/en/development/models-integration/openllm"
- },
- {
- "source": "/development/models-integration/localai",
- "destination": "/en/development/models-integration/localai"
- },
- {
- "source": "/development/models-integration/ollama",
- "destination": "/en/development/models-integration/ollama"
- },
- {
- "source": "/development/models-integration/litellm",
- "destination": "/en/development/models-integration/litellm"
- },
- {
- "source": "/development/models-integration/gpustack",
- "destination": "/en/development/models-integration/gpustack"
- },
- {
- "source": "/development/models-integration/aws-bedrock-deepseek",
- "destination": "/en/development/models-integration/aws-bedrock-deepseek"
- },
- {
- "source": "/development/migration",
- "destination": "/en/development/migration/migrate-to-v1"
- },
- {
- "source": "/development/migration/migrate-to-v1",
- "destination": "/en/development/migration/migrate-to-v1"
- },
- {
- "source": "/learn-more/use-cases",
- "destination": "/en/learn-more/use-cases/integrate-deepseek-to-build-an-ai-app"
- },
- {
- "source": "/learn-more/use-cases/integrate-deepseek-to-build-an-ai-app",
- "destination": "/en/learn-more/use-cases/integrate-deepseek-to-build-an-ai-app"
- },
- {
- "source": "/learn-more/use-cases/private-ai-ollama-deepseek-dify",
- "destination": "/en/learn-more/use-cases/private-ai-ollama-deepseek-dify"
- },
- {
- "source": "/learn-more/use-cases/build-an-notion-ai-assistant",
- "destination": "/en/learn-more/use-cases/build-an-notion-ai-assistant"
- },
- {
- "source": "/learn-more/use-cases/create-a-midjourney-prompt-bot-with-dify",
- "destination": "/en/learn-more/use-cases/create-a-midjourney-prompt-bot-with-dify"
- },
- {
- "source": "/learn-more/use-cases/create-an-ai-chatbot-with-business-data-in-minutes",
- "destination": "/en/learn-more/use-cases/create-an-ai-chatbot-with-business-data-in-minutes"
- },
- {
- "source": "/learn-more/use-cases/how-to-integrate-dify-chatbot-to-your-wix-website",
- "destination": "/en/learn-more/use-cases/how-to-integrate-dify-chatbot-to-your-wix-website"
- },
- {
- "source": "/learn-more/use-cases/how-to-connect-aws-bedrock",
- "destination": "/en/learn-more/use-cases/how-to-connect-aws-bedrock"
- },
- {
- "source": "/learn-more/use-cases/dify-schedule",
- "destination": "/en/learn-more/use-cases/dify-schedule"
- },
- {
- "source": "/learn-more/use-cases/building-an-ai-thesis-slack-bot",
- "destination": "/en/learn-more/use-cases/building-an-ai-thesis-slack-bot"
- },
- {
- "source": "/learn-more/extended-reading",
- "destination": "/en/learn-more/extended-reading/what-is-llmops"
- },
- {
- "source": "/learn-more/extended-reading/what-is-llmops",
- "destination": "/en/learn-more/extended-reading/what-is-llmops"
- },
- {
- "source": "/learn-more/extended-reading/retrieval-augment",
- "destination": "/en/learn-more/extended-reading/retrieval-augment/README"
- },
- {
- "source": "/learn-more/extended-reading/retrieval-augment/hybrid-search",
- "destination": "/en/learn-more/extended-reading/retrieval-augment/hybrid-search"
- },
- {
- "source": "/learn-more/extended-reading/retrieval-augment/rerank",
- "destination": "/en/learn-more/extended-reading/retrieval-augment/rerank"
- },
- {
- "source": "/learn-more/extended-reading/retrieval-augment/retrieval",
- "destination": "/en/learn-more/extended-reading/retrieval-augment/retrieval"
- },
- {
- "source": "/learn-more/extended-reading/how-to-use-json-schema-in-dify",
- "destination": "/en/learn-more/extended-reading/how-to-use-json-schema-in-dify"
- },
- {
- "source": "/learn-more/faq",
- "destination": "/en/learn-more/faq/install-faq"
- },
- {
- "source": "/learn-more/faq/install-faq",
- "destination": "/en/learn-more/faq/install-faq"
- },
- {
- "source": "/learn-more/faq/use-llms-faq",
- "destination": "/en/learn-more/faq/use-llms-faq"
- },
- {
- "source": "/learn-more/faq/plugins",
- "destination": "/en/learn-more/faq/plugins"
- },
- {
- "source": "/policies/open-source",
- "destination": "/en/policies/open-source"
- },
- {
- "source": "/policies/agreement/readme",
- "destination": "/en/policies/agreement/readme"
- },
- {
- "source": "/policies/agreement/get-compliance-report",
- "destination": "/en/policies/agreement/get-compliance-report"
- },
- {
- "source": "/features/workflow",
- "destination": "/en/guides/workflow/README"
- }
-],
- "navbar": {
- "links": [
- {
- "label": "Blog",
- "href": "https://dify.ai/blog"
- },
- {
- "label": "Support",
- "href": "mailto:support@dify.ai"
- }
+ "redirects": [
+ {
+ "source": "plugins/schema-definition/model",
+ "destination": "en/plugins/schema-definition/model/model-designing-rules"
+ },
+ {
+ "source": "plugins/schema-definition",
+ "destination": "en/plugins/schema-definition/manifest"
+ },
+ {
+ "source": "guides/application-orchestrate/app-toolkits",
+ "destination": "guides/application-orchestrate/app-toolkits/readme"
+ },
+ {
+ "source": "ja-jp/plugins/plugin-auto-publish-pr",
+ "destination": "ja-jp/plugins/publish-plugins/plugin-auto-publish-pr"
+ },
+ {
+ "source": "zh-hans/plugins/plugin-auto-publish-pr",
+ "destination": "zh-hans/plugins/publish-plugins/plugin-auto-publish-pr"
+ },
+ {
+ "source": "en/plugins/plugin-auto-publish-pr",
+ "destination": "en/plugins/publish-plugins/plugin-auto-publish-pr"
+ },
+ {
+ "source": "zh-hans/getting-started/install-self-hosted",
+ "destination": "zh-hans/getting-started/install-self-hosted/readme"
+ },
+ {
+ "source": "zh-hans/guides/model-configuration",
+ "destination": "zh-hans/guides/model-configuration/readme"
+ },
+ {
+ "source": "zh-hans/guides/application-orchestrate",
+ "destination": "zh-hans/guides/application-orchestrate/readme"
+ },
+ {
+ "source": "zh-hans/guides/application-orchestrate/app-toolkits",
+ "destination": "zh-hans/guides/application-orchestrate/app-toolkits/readme"
+ },
+ {
+ "source": "zh-hans/guides/workflow",
+ "destination": "zh-hans/guides/workflow/readme"
+ },
+ {
+ "source": "zh-hans/guides/workflow/node",
+ "destination": "zh-hans/guides/workflow/node/start"
+ },
+ {
+ "source": "zh-hans/guides/workflow/error-handling",
+ "destination": "zh-hans/guides/workflow/error-handling/readme"
+ },
+ {
+ "source": "zh-hans/guides/workflow/debug-and-preview",
+ "destination": "zh-hans/guides/workflow/debug-and-preview/preview-and-run"
+ },
+ {
+ "source": "zh-hans/guides/workflow/debug-and-preview/yu-lan-yu-yun-hang",
+ "destination": "zh-hans/guides/workflow/debug-and-preview/preview-and-run"
+ },
+ {
+ "source": "zh-hans/guides/knowledge-base",
+ "destination": "zh-hans/guides/knowledge-base/readme"
+ },
+ {
+ "source": "zh-hans/guides/knowledge-base/create-knowledge-and-upload-documents",
+ "destination": "zh-hans/guides/knowledge-base/knowledge-base-creation/introduction"
+ },
+ {
+ "source": "zh-hans/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data",
+ "destination": "zh-hans/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/readme"
+ },
+ {
+ "source": "zh-hans/guides/knowledge-base/knowledge-and-documents-maintenance",
+ "destination": "zh-hans/guides/knowledge-base/knowledge-and-documents-maintenance/introduction"
+ },
+ {
+ "source": "zh-hans/guides/tools",
+ "destination": "zh-hans/guides/tools/readme"
+ },
+ {
+ "source": "zh-hans/guides/tools/tool-configuration",
+ "destination": "zh-hans/guides/tools/tool-configuration/readme"
+ },
+ {
+ "source": "zh-hans/guides/application-publishing",
+ "destination": "zh-hans/guides/application-publishing/readme"
+ },
+ {
+ "source": "zh-hans/guides/application-publishing/launch-your-webapp-quickly",
+ "destination": "zh-hans/guides/application-publishing/launch-your-webapp-quickly/readme"
+ },
+ {
+ "source": "zh-hans/guides/annotation",
+ "destination": "zh-hans/guides/annotation/readme"
+ },
+ {
+ "source": "zh-hans/guides/monitoring",
+ "destination": "zh-hans/guides/monitoring/readme"
+ },
+ {
+ "source": "zh-hans/guides/monitoring/integrate-external-ops-tools",
+ "destination": "zh-hans/guides/monitoring/integrate-external-ops-tools/readme"
+ },
+ {
+ "source": "zh-hans/guides/extension",
+ "destination": "zh-hans/guides/extension/readme"
+ },
+ {
+ "source": "zh-hans/guides/extension/api-based-extension",
+ "destination": "zh-hans/guides/extension/api-based-extension/readme"
+ },
+ {
+ "source": "zh-hans/guides/extension/code-based-extension",
+ "destination": "zh-hans/guides/extension/code-based-extension/readme"
+ },
+ {
+ "source": "zh-hans/guides/workspace",
+ "destination": "zh-hans/guides/workspace/readme"
+ },
+ {
+ "source": "zh-hans/guides/management",
+ "destination": "zh-hans/guides/management/readme"
+ },
+ {
+ "source": "zh-hans/workshop/basic",
+ "destination": "zh-hans/workshop/readme"
+ },
+ {
+ "source": "zh-hans/workshop/intermediate",
+ "destination": "zh-hans/workshop/intermediate/readme"
+ },
+ {
+ "source": "zh-hans/plugins/quick-start",
+ "destination": "zh-hans/plugins/quick-start/readme"
+ },
+ {
+ "source": "zh-hans/plugins/quick-start/develop-plugins",
+ "destination": "zh-hans/plugins/quick-start/develop-plugins/readme"
+ },
+ {
+ "source": "zh-hans/plugins/quick-start/develop-plugins/model-plugin",
+ "destination": "zh-hans/plugins/quick-start/develop-plugins/model-plugin/readme"
+ },
+ {
+ "source": "zh-hans/plugins/schema-definition",
+ "destination": "zh-hans/plugins/schema-definition/readme"
+ },
+ {
+ "source": "zh-hans/plugins/schema-definition/model",
+ "destination": "zh-hans/plugins/schema-definition/model/readme"
+ },
+ {
+ "source": "zh-hans/plugins/schema-definition/reverse-invocation-of-the-dify-service",
+ "destination": "zh-hans/plugins/schema-definition/reverse-invocation-of-the-dify-service/readme"
+ },
+ {
+ "source": "zh-hans/plugins/best-practice",
+ "destination": "zh-hans/plugins/best-practice/develop-a-slack-bot-plugin"
+ },
+ {
+ "source": "zh-hans/plugins/publish-plugins",
+ "destination": "zh-hans/plugins/publish-plugins/README"
+ },
+ {
+ "source": "zh-hans/plugins/publish-plugins/publish-to-dify-marketplace",
+ "destination": "zh-hans/plugins/publish-plugins/publish-to-dify-marketplace/README"
+ },
+ {
+ "source": "zh-hans/development/backend",
+ "destination": "zh-hans/development/backend/readme"
+ },
+ {
+ "source": "zh-hans/development/backend/sandbox",
+ "destination": "zh-hans/development/backend/sandbox/readme"
+ },
+ {
+ "source": "zh-hans/development/models-integration",
+ "destination": "zh-hans/development/models-integration/readme"
+ },
+ {
+ "source": "zh-hans/development/migration",
+ "destination": "zh-hans/development/migration/readme"
+ },
+ {
+ "source": "zh-hans/learn-more/use-cases",
+ "destination": "zh-hans/learn-more/use-cases/readme"
+ },
+ {
+ "source": "zh-hans/learn-more/extended-reading",
+ "destination": "zh-hans/learn-more/extended-reading/readme"
+ },
+ {
+ "source": "zh-hans/learn-more/extended-reading/retrieval-augment",
+ "destination": "zh-hans/learn-more/extended-reading/retrieval-augment/readme"
+ },
+ {
+ "source": "zh-hans/learn-more/faq",
+ "destination": "zh-hans/learn-more/faq/readme"
+ },
+ {
+ "source": "zh-hans/policies/agreement",
+ "destination": "zh-hans/policies/agreement/readme"
+ },
+ {
+ "source": "/zh-hans",
+ "destination": "/zh-hans/introduction"
+ },
+ {
+ "source": "/introduction",
+ "destination": "/en/introduction"
+ },
+ {
+ "source": "/getting-started/readme/features-and-specifications",
+ "destination": "/en/getting-started/readme/features-and-specifications"
+ },
+ {
+ "source": "/getting-started/readme/model-providers",
+ "destination": "/en/getting-started/readme/model-providers"
+ },
+ {
+ "source": "/getting-started/install-self-hosted/readme",
+ "destination": "/en/getting-started/install-self-hosted/readme"
+ },
+ {
+ "source": "/getting-started/install-self-hosted/docker-compose",
+ "destination": "/en/getting-started/install-self-hosted/docker-compose"
+ },
+ {
+ "source": "/getting-started/install-self-hosted/local-source-code",
+ "destination": "/en/getting-started/install-self-hosted/local-source-code"
+ },
+ {
+ "source": "/getting-started/install-self-hosted/bt-panel",
+ "destination": "/en/getting-started/install-self-hosted/bt-panel"
+ },
+ {
+ "source": "/getting-started/install-self-hosted/start-the-frontend-docker-container",
+ "destination": "/en/getting-started/install-self-hosted/start-the-frontend-docker-container"
+ },
+ {
+ "source": "/getting-started/install-self-hosted/environments",
+ "destination": "/en/getting-started/install-self-hosted/environments"
+ },
+ {
+ "source": "/getting-started/install-self-hosted/faqs",
+ "destination": "/en/getting-started/install-self-hosted/faqs"
+ },
+ {
+ "source": "/getting-started/cloud",
+ "destination": "/en/getting-started/cloud"
+ },
+ {
+ "source": "/getting-started/dify-premium",
+ "destination": "/en/getting-started/dify-premium"
+ },
+ {
+ "source": "/guides/model-configuration/readme",
+ "destination": "/en/guides/model-configuration/readme"
+ },
+ {
+ "source": "/guides/model-configuration/new-provider",
+ "destination": "/en/guides/model-configuration/new-provider"
+ },
+ {
+ "source": "/guides/model-configuration/predefined-model",
+ "destination": "/en/guides/model-configuration/predefined-model"
+ },
+ {
+ "source": "/guides/model-configuration/customizable-model",
+ "destination": "/en/guides/model-configuration/customizable-model"
+ },
+ {
+ "source": "/guides/model-configuration/interfaces",
+ "destination": "/en/guides/model-configuration/interfaces"
+ },
+ {
+ "source": "/guides/model-configuration/schema",
+ "destination": "/en/guides/model-configuration/schema"
+ },
+ {
+ "source": "/guides/model-configuration/load-balancing",
+ "destination": "/en/guides/model-configuration/load-balancing"
+ },
+ {
+ "source": "/guides/application-orchestrate/readme",
+ "destination": "/en/guides/application-orchestrate/readme"
+ },
+ {
+ "source": "/guides/application-orchestrate/creating-an-application",
+ "destination": "/en/guides/application-orchestrate/creating-an-application"
+ },
+ {
+ "source": "/guides/application-orchestrate/chatbot-application",
+ "destination": "/en/guides/application-orchestrate/chatbot-application"
+ },
+ {
+ "source": "/guides/application-orchestrate/multiple-llms-debugging",
+ "destination": "/en/guides/application-orchestrate/multiple-llms-debugging"
+ },
+ {
+ "source": "/guides/application-orchestrate/agent",
+ "destination": "/en/guides/application-orchestrate/agent"
+ },
+ {
+ "source": "/guides/application-orchestrate/app-toolkits/readme",
+ "destination": "/en/guides/application-orchestrate/app-toolkits/readme"
+ },
+ {
+ "source": "/guides/application-orchestrate/app-toolkits/moderation-tool",
+ "destination": "/en/guides/application-orchestrate/app-toolkits/moderation-tool"
+ },
+ {
+ "source": "/guides/workflow/readme",
+ "destination": "/en/guides/workflow/readme"
+ },
+ {
+ "source": "/guides/workflow/key-concepts",
+ "destination": "/en/guides/workflow/key-concepts"
+ },
+ {
+ "source": "/guides/workflow/variables",
+ "destination": "/en/guides/workflow/variables"
+ },
+ {
+ "source": "/guides/workflow/node/readme",
+ "destination": "/en/guides/workflow/node/readme"
+ },
+ {
+ "source": "/guides/workflow/node/start",
+ "destination": "/en/guides/workflow/node/start"
+ },
+ {
+ "source": "/guides/workflow/node/end",
+ "destination": "/en/guides/workflow/node/end"
+ },
+ {
+ "source": "/guides/workflow/node/answer",
+ "destination": "/en/guides/workflow/node/answer"
+ },
+ {
+ "source": "/guides/workflow/node/llm",
+ "destination": "/en/guides/workflow/node/llm"
+ },
+ {
+ "source": "/guides/workflow/node/knowledge-retrieval",
+ "destination": "/en/guides/workflow/node/knowledge-retrieval"
+ },
+ {
+ "source": "/guides/workflow/node/question-classifier",
+ "destination": "/en/guides/workflow/node/question-classifier"
+ },
+ {
+ "source": "/guides/workflow/node/ifelse",
+ "destination": "/en/guides/workflow/node/ifelse"
+ },
+ {
+ "source": "/guides/workflow/node/code",
+ "destination": "/en/guides/workflow/node/code"
+ },
+ {
+ "source": "/guides/workflow/node/template",
+ "destination": "/en/guides/workflow/node/template"
+ },
+ {
+ "source": "/guides/workflow/node/doc-extractor",
+ "destination": "/en/guides/workflow/node/doc-extractor"
+ },
+ {
+ "source": "/guides/workflow/node/list-operator",
+ "destination": "/en/guides/workflow/node/list-operator"
+ },
+ {
+ "source": "/guides/workflow/node/variable-aggregator",
+ "destination": "/en/guides/workflow/node/variable-aggregator"
+ },
+ {
+ "source": "/guides/workflow/node/variable-assigner",
+ "destination": "/en/guides/workflow/node/variable-assigner"
+ },
+ {
+ "source": "/guides/workflow/node/iteration",
+ "destination": "/en/guides/workflow/node/iteration"
+ },
+ {
+ "source": "/guides/workflow/node/parameter-extractor",
+ "destination": "/en/guides/workflow/node/parameter-extractor"
+ },
+ {
+ "source": "/guides/workflow/node/http-request",
+ "destination": "/en/guides/workflow/node/http-request"
+ },
+ {
+ "source": "/guides/workflow/node/agent",
+ "destination": "/en/guides/workflow/node/agent"
+ },
+ {
+ "source": "/guides/workflow/node/tools",
+ "destination": "/en/guides/workflow/node/tools"
+ },
+ {
+ "source": "/guides/workflow/node/loop",
+ "destination": "/en/guides/workflow/node/loop"
+ },
+ {
+ "source": "/guides/workflow/shortcut-key",
+ "destination": "/en/guides/workflow/shortcut-key"
+ },
+ {
+ "source": "/guides/workflow/orchestrate-node",
+ "destination": "/en/guides/workflow/orchestrate-node"
+ },
+ {
+ "source": "/guides/workflow/file-upload",
+ "destination": "/en/guides/workflow/file-upload"
+ },
+ {
+ "source": "/guides/workflow/error-handling/readme",
+ "destination": "/en/guides/workflow/error-handling/readme"
+ },
+ {
+ "source": "/guides/workflow/error-handling/predefined-error-handling-logic",
+ "destination": "/en/guides/workflow/error-handling/predefined-error-handling-logic"
+ },
+ {
+ "source": "/guides/workflow/error-handling/error-type",
+ "destination": "/en/guides/workflow/error-handling/error-type"
+ },
+ {
+ "source": "/guides/workflow/additional-features",
+ "destination": "/en/guides/workflow/additional-features"
+ },
+ {
+ "source": "/guides/workflow/debug-and-preview/readme",
+ "destination": "/en/guides/workflow/debug-and-preview/readme"
+ },
+ {
+ "source": "/guides/workflow/debug-and-preview/yu-lan-yu-yun-hang",
+ "destination": "/en/guides/workflow/debug-and-preview/yu-lan-yu-yun-hang"
+ },
+ {
+ "source": "/guides/workflow/debug-and-preview/preview-and-run",
+ "destination": "/en/guides/workflow/debug-and-preview/preview-and-run"
+ },
+ {
+ "source": "/guides/workflow/debug-and-preview/step-run",
+ "destination": "/en/guides/workflow/debug-and-preview/step-run"
+ },
+ {
+ "source": "/guides/workflow/debug-and-preview/log",
+ "destination": "/en/guides/workflow/debug-and-preview/log"
+ },
+ {
+ "source": "/guides/workflow/debug-and-preview/checklist",
+ "destination": "/en/guides/workflow/debug-and-preview/checklist"
+ },
+ {
+ "source": "/guides/workflow/debug-and-preview/history",
+ "destination": "/en/guides/workflow/debug-and-preview/history"
+ },
+ {
+ "source": "/guides/workflow/publish",
+ "destination": "/en/guides/workflow/publish"
+ },
+ {
+ "source": "/guides/workflow/bulletin",
+ "destination": "/en/guides/workflow/bulletin"
+ },
+ {
+ "source": "/guides/knowledge-base/readme",
+ "destination": "/en/guides/knowledge-base/readme"
+ },
+ {
+ "source": "/guides/knowledge-base/knowledge-base-creation/introduction",
+ "destination": "/en/guides/knowledge-base/knowledge-base-creation/introduction"
+ },
+ {
+ "source": "/guides/knowledge-base/create-knowledge-and-upload-documents",
+ "destination": "/en/guides/knowledge-base/create-knowledge-and-upload-documents"
+ },
+ {
+ "source": "/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/readme",
+ "destination": "/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/readme"
+ },
+ {
+ "source": "/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-notion",
+ "destination": "/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-notion"
+ },
+ {
+ "source": "/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website",
+ "destination": "/en/guides/knowledge-base/create-knowledge-and-upload-documents/import-content-data/sync-from-website"
+ },
+ {
+ "source": "/guides/knowledge-base/create-knowledge-and-upload-documents/chunking-and-cleaning-text",
+ "destination": "/en/guides/knowledge-base/create-knowledge-and-upload-documents/chunking-and-cleaning-text"
+ },
+ {
+ "source": "/guides/knowledge-base/create-knowledge-and-upload-documents/setting-indexing-methods",
+ "destination": "/en/guides/knowledge-base/create-knowledge-and-upload-documents/setting-indexing-methods"
+ },
+ {
+ "source": "/guides/knowledge-base/knowledge-and-documents-maintenance/readme",
+ "destination": "/en/guides/knowledge-base/knowledge-and-documents-maintenance/readme"
+ },
+ {
+ "source": "/guides/knowledge-base/knowledge-and-documents-maintenance/introduction",
+ "destination": "/en/guides/knowledge-base/knowledge-and-documents-maintenance/introduction"
+ },
+ {
+ "source": "/guides/knowledge-base/knowledge-and-documents-maintenance/maintain-knowledge-documents",
+ "destination": "/en/guides/knowledge-base/knowledge-and-documents-maintenance/maintain-knowledge-documents"
+ },
+ {
+ "source": "/guides/knowledge-base/knowledge-and-documents-maintenance/maintain-dataset-via-api",
+ "destination": "/en/guides/knowledge-base/knowledge-and-documents-maintenance/maintain-dataset-via-api"
+ },
+ {
+ "source": "/guides/knowledge-base/metadata",
+ "destination": "/en/guides/knowledge-base/metadata"
+ },
+ {
+ "source": "/guides/knowledge-base/integrate-knowledge-within-application",
+ "destination": "/en/guides/knowledge-base/integrate-knowledge-within-application"
+ },
+ {
+ "source": "/guides/knowledge-base/retrieval-test-and-citation",
+ "destination": "/en/guides/knowledge-base/retrieval-test-and-citation"
+ },
+ {
+ "source": "/guides/knowledge-base/knowledge-request-rate-limit",
+ "destination": "/en/guides/knowledge-base/knowledge-request-rate-limit"
+ },
+ {
+ "source": "/guides/knowledge-base/connect-external-knowledge-base",
+ "destination": "/en/guides/knowledge-base/connect-external-knowledge-base"
+ },
+ {
+ "source": "/guides/knowledge-base/external-knowledge-api",
+ "destination": "/en/guides/knowledge-base/external-knowledge-api"
+ },
+ {
+ "source": "/guides/knowledge-base/external-knowledge-api-documentation",
+ "destination": "/en/guides/knowledge-base/external-knowledge-api-documentation"
+ },
+ {
+ "source": "/guides/tools/readme",
+ "destination": "/en/guides/tools/readme"
+ },
+ {
+ "source": "/guides/tools/quick-tool-integration",
+ "destination": "/en/guides/tools/quick-tool-integration"
+ },
+ {
+ "source": "/guides/tools/advanced-tool-integration",
+ "destination": "/en/guides/tools/advanced-tool-integration"
+ },
+ {
+ "source": "/guides/tools/tool-configuration/readme",
+ "destination": "/en/guides/tools/tool-configuration/readme"
+ },
+ {
+ "source": "/guides/tools/tool-configuration/google",
+ "destination": "/en/guides/tools/tool-configuration/google"
+ },
+ {
+ "source": "/guides/tools/tool-configuration/bing",
+ "destination": "/en/guides/tools/tool-configuration/bing"
+ },
+ {
+ "source": "/guides/tools/tool-configuration/searchapi",
+ "destination": "/en/guides/tools/tool-configuration/searchapi"
+ },
+ {
+ "source": "/guides/tools/tool-configuration/stable-diffusion",
+ "destination": "/en/guides/tools/tool-configuration/stable-diffusion"
+ },
+ {
+ "source": "/guides/tools/tool-configuration/dall-e",
+ "destination": "/en/guides/tools/tool-configuration/dall-e"
+ },
+ {
+ "source": "/guides/tools/tool-configuration/perplexity",
+ "destination": "/en/guides/tools/tool-configuration/perplexity"
+ },
+ {
+ "source": "/guides/tools/tool-configuration/alphavantage",
+ "destination": "/en/guides/tools/tool-configuration/alphavantage"
+ },
+ {
+ "source": "/guides/tools/tool-configuration/youtube",
+ "destination": "/en/guides/tools/tool-configuration/youtube"
+ },
+ {
+ "source": "/guides/tools/tool-configuration/searxng",
+ "destination": "/en/guides/tools/tool-configuration/searxng"
+ },
+ {
+ "source": "/guides/tools/tool-configuration/serper",
+ "destination": "/en/guides/tools/tool-configuration/serper"
+ },
+ {
+ "source": "/guides/tools/tool-configuration/siliconflow",
+ "destination": "/en/guides/tools/tool-configuration/siliconflow"
+ },
+ {
+ "source": "/guides/tools/tool-configuration/comfyui",
+ "destination": "/en/guides/tools/tool-configuration/comfyui"
+ },
+ {
+ "source": "/guides/application-publishing/readme",
+ "destination": "/en/guides/application-publishing/readme"
+ },
+ {
+ "source": "/guides/application-publishing/launch-your-webapp-quickly/readme",
+ "destination": "/en/guides/application-publishing/launch-your-webapp-quickly/readme"
+ },
+ {
+ "source": "/guides/application-publishing/launch-your-webapp-quickly/web-app-settings",
+ "destination": "/en/guides/application-publishing/launch-your-webapp-quickly/web-app-settings"
+ },
+ {
+ "source": "/guides/application-publishing/launch-your-webapp-quickly/text-generator",
+ "destination": "/en/guides/application-publishing/launch-your-webapp-quickly/text-generator"
+ },
+ {
+ "source": "/guides/application-publishing/launch-your-webapp-quickly/conversation-application",
+ "destination": "/en/guides/application-publishing/launch-your-webapp-quickly/conversation-application"
+ },
+ {
+ "source": "/guides/application-publishing/embedding-in-websites",
+ "destination": "/en/guides/application-publishing/embedding-in-websites"
+ },
+ {
+ "source": "/guides/application-publishing/developing-with-apis",
+ "destination": "/en/guides/application-publishing/developing-with-apis"
+ },
+ {
+ "source": "/guides/application-publishing/based-on-frontend-templates",
+ "destination": "/en/guides/application-publishing/based-on-frontend-templates"
+ },
+ {
+ "source": "/guides/annotation/readme",
+ "destination": "/en/guides/annotation/readme"
+ },
+ {
+ "source": "/guides/annotation/logs",
+ "destination": "/en/guides/annotation/logs"
+ },
+ {
+ "source": "/guides/annotation/annotation-reply",
+ "destination": "/en/guides/annotation/annotation-reply"
+ },
+ {
+ "source": "/guides/monitoring/readme",
+ "destination": "/en/guides/monitoring/readme"
+ },
+ {
+ "source": "/guides/monitoring/analysis",
+ "destination": "/en/guides/monitoring/analysis"
+ },
+ {
+ "source": "/guides/monitoring/integrate-external-ops-tools/readme",
+ "destination": "/en/guides/monitoring/integrate-external-ops-tools/readme"
+ },
+ {
+ "source": "/guides/monitoring/integrate-external-ops-tools/integrate-langsmith",
+ "destination": "/en/guides/monitoring/integrate-external-ops-tools/integrate-langsmith"
+ },
+ {
+ "source": "/guides/monitoring/integrate-external-ops-tools/integrate-langfuse",
+ "destination": "/en/guides/monitoring/integrate-external-ops-tools/integrate-langfuse"
+ },
+ {
+ "source": "/guides/monitoring/integrate-external-ops-tools/integrate-opik",
+ "destination": "/en/guides/monitoring/integrate-external-ops-tools/integrate-opik"
+ },
+ {
+ "source": "/guides/extension/readme",
+ "destination": "/en/guides/extension/readme"
+ },
+ {
+ "source": "/guides/extension/api-based-extension/readme",
+ "destination": "/en/guides/extension/api-based-extension/readme"
+ },
+ {
+ "source": "/guides/extension/api-based-extension/external-data-tool",
+ "destination": "/en/guides/extension/api-based-extension/external-data-tool"
+ },
+ {
+ "source": "/guides/extension/api-based-extension/cloudflare-workers",
+ "destination": "/en/guides/extension/api-based-extension/cloudflare-workers"
+ },
+ {
+ "source": "/guides/extension/api-based-extension/moderation",
+ "destination": "/en/guides/extension/api-based-extension/moderation"
+ },
+ {
+ "source": "/guides/extension/code-based-extension/readme",
+ "destination": "/en/guides/extension/code-based-extension/readme"
+ },
+ {
+ "source": "/guides/extension/code-based-extension/external-data-tool",
+ "destination": "/en/guides/extension/code-based-extension/external-data-tool"
+ },
+ {
+ "source": "/guides/extension/code-based-extension/moderation",
+ "destination": "/en/guides/extension/code-based-extension/moderation"
+ },
+ {
+ "source": "/guides/workspace/readme",
+ "destination": "/en/guides/workspace/readme"
+ },
+ {
+ "source": "/guides/workspace/app",
+ "destination": "/en/guides/workspace/app"
+ },
+ {
+ "source": "/guides/workspace/invite-and-manage-members",
+ "destination": "/en/guides/workspace/invite-and-manage-members"
+ },
+ {
+ "source": "/guides/management/readme",
+ "destination": "/en/guides/management/readme"
+ },
+ {
+ "source": "/guides/management/app-management",
+ "destination": "/en/guides/management/app-management"
+ },
+ {
+ "source": "/guides/management/team-members-management",
+ "destination": "/en/guides/management/team-members-management"
+ },
+ {
+ "source": "/guides/management/personal-account-management",
+ "destination": "/en/guides/management/personal-account-management"
+ },
+ {
+ "source": "/guides/management/subscription-management",
+ "destination": "/en/guides/management/subscription-management"
+ },
+ {
+ "source": "/guides/management/version-control",
+ "destination": "/en/guides/management/version-control"
+ },
+ {
+ "source": "/workshop/readme",
+ "destination": "/en/workshop/readme"
+ },
+ {
+ "source": "/workshop/basic/readme",
+ "destination": "/en/workshop/basic/readme"
+ },
+ {
+ "source": "/workshop/basic/build-ai-image-generation-app",
+ "destination": "/en/workshop/basic/build-ai-image-generation-app"
+ },
+ {
+ "source": "/workshop/intermediate/readme",
+ "destination": "/en/workshop/intermediate/readme"
+ },
+ {
+ "source": "/workshop/intermediate/article-reader",
+ "destination": "/en/workshop/intermediate/article-reader"
+ },
+ {
+ "source": "/workshop/intermediate/customer-service-bot",
+ "destination": "/en/workshop/intermediate/customer-service-bot"
+ },
+ {
+ "source": "/workshop/intermediate/twitter-chatflow",
+ "destination": "/en/workshop/intermediate/twitter-chatflow"
+ },
+ {
+ "source": "/community/support",
+ "destination": "/en/community/support"
+ },
+ {
+ "source": "/community/contribution",
+ "destination": "/en/community/contribution"
+ },
+ {
+ "source": "/community/docs-contribution",
+ "destination": "/en/community/docs-contribution"
+ },
+ {
+ "source": "/plugins/introduction",
+ "destination": "/en/plugins/introduction"
+ },
+ {
+ "source": "/plugins/quick-start/readme",
+ "destination": "/en/plugins/quick-start/readme"
+ },
+ {
+ "source": "/plugins/quick-start/install-plugins",
+ "destination": "/en/plugins/quick-start/install-plugins"
+ },
+ {
+ "source": "/plugins/quick-start/develop-plugins/readme",
+ "destination": "/en/plugins/quick-start/develop-plugins/readme"
+ },
+ {
+ "source": "/plugins/quick-start/develop-plugins/initialize-development-tools",
+ "destination": "/en/plugins/quick-start/develop-plugins/initialize-development-tools"
+ },
+ {
+ "source": "/plugins/quick-start/develop-plugins/tool-plugin",
+ "destination": "/en/plugins/quick-start/develop-plugins/tool-plugin"
+ },
+ {
+ "source": "/plugins/quick-start/develop-plugins/model-plugin/readme",
+ "destination": "/en/plugins/quick-start/develop-plugins/model-plugin/readme"
+ },
+ {
+ "source": "/plugins/quick-start/develop-plugins/model-plugin/create-model-providers",
+ "destination": "/en/plugins/quick-start/develop-plugins/model-plugin/create-model-providers"
+ },
+ {
+ "source": "/plugins/quick-start/develop-plugins/model-plugin/predefined-model",
+ "destination": "/en/plugins/quick-start/develop-plugins/model-plugin/predefined-model"
+ },
+ {
+ "source": "/plugins/quick-start/develop-plugins/model-plugin/customizable-model",
+ "destination": "/en/plugins/quick-start/develop-plugins/model-plugin/customizable-model"
+ },
+ {
+ "source": "/plugins/quick-start/develop-plugins/agent-strategy-plugin",
+ "destination": "/en/plugins/quick-start/develop-plugins/agent-strategy-plugin"
+ },
+ {
+ "source": "/plugins/quick-start/develop-plugins/extension-plugin",
+ "destination": "/en/plugins/quick-start/develop-plugins/extension-plugin"
+ },
+ {
+ "source": "/plugins/quick-start/develop-plugins/bundle",
+ "destination": "/en/plugins/quick-start/develop-plugins/bundle"
+ },
+ {
+ "source": "/plugins/quick-start/debug-plugin",
+ "destination": "/en/plugins/quick-start/debug-plugin"
+ },
+ {
+ "source": "/plugins/manage-plugins",
+ "destination": "/en/plugins/manage-plugins"
+ },
+ {
+ "source": "/plugins/schema-definition/readme",
+ "destination": "/en/plugins/schema-definition/readme"
+ },
+ {
+ "source": "/plugins/schema-definition/manifest",
+ "destination": "/en/plugins/schema-definition/manifest"
+ },
+ {
+ "source": "/plugins/schema-definition/endpoint",
+ "destination": "/en/plugins/schema-definition/endpoint"
+ },
+ {
+ "source": "/plugins/schema-definition/tool",
+ "destination": "/en/plugins/schema-definition/tool"
+ },
+ {
+ "source": "/plugins/schema-definition/agent",
+ "destination": "/en/plugins/schema-definition/agent"
+ },
+ {
+ "source": "/plugins/schema-definition/model/readme",
+ "destination": "/en/plugins/schema-definition/model/readme"
+ },
+ {
+ "source": "/plugins/schema-definition/model/model-designing-rules",
+ "destination": "/en/plugins/schema-definition/model/model-designing-rules"
+ },
+ {
+ "source": "/plugins/schema-definition/model/model-schema",
+ "destination": "/en/plugins/schema-definition/model/model-schema"
+ },
+ {
+ "source": "/plugins/schema-definition/general-specifications",
+ "destination": "/en/plugins/schema-definition/general-specifications"
+ },
+ {
+ "source": "/plugins/schema-definition/persistent-storage",
+ "destination": "/en/plugins/schema-definition/persistent-storage"
+ },
+ {
+ "source": "/plugins/schema-definition/reverse-invocation-of-the-dify-service/readme",
+ "destination": "/en/plugins/schema-definition/reverse-invocation-of-the-dify-service/readme"
+ },
+ {
+ "source": "/plugins/schema-definition/reverse-invocation-of-the-dify-service/app",
+ "destination": "/en/plugins/schema-definition/reverse-invocation-of-the-dify-service/app"
+ },
+ {
+ "source": "/plugins/schema-definition/reverse-invocation-of-the-dify-service/model",
+ "destination": "/en/plugins/schema-definition/reverse-invocation-of-the-dify-service/model"
+ },
+ {
+ "source": "/plugins/schema-definition/reverse-invocation-of-the-dify-service/tool",
+ "destination": "/en/plugins/schema-definition/reverse-invocation-of-the-dify-service/tool"
+ },
+ {
+ "source": "/plugins/schema-definition/reverse-invocation-of-the-dify-service/node",
+ "destination": "/en/plugins/schema-definition/reverse-invocation-of-the-dify-service/node"
+ },
+ {
+ "source": "/plugins/best-practice",
+ "destination": "/en/plugins/best-practice/README"
+ },
+ {
+ "source": "/plugins/best-practice/develop-a-slack-bot-plugin",
+ "destination": "/en/plugins/best-practice/develop-a-slack-bot-plugin"
+ },
+ {
+ "source": "/plugins/publish-plugins",
+ "destination": "/en/plugins/publish-plugins/README"
+ },
+ {
+ "source": "/plugins/publish-plugins/publish-to-dify-marketplace",
+ "destination": "/en/plugins/publish-plugins/publish-to-dify-marketplace/README"
+ },
+ {
+ "source": "/zh-hans/plugins/publish-plugins/publish-to-dify-marketplace",
+ "destination": "/zh-hans/plugins/publish-plugins/publish-to-dify-marketplace/README"
+ },
+ {
+ "source": "/ja-jp/plugins/publish-plugins/publish-to-dify-marketplace",
+ "destination": "/ja-jp/plugins/publish-plugins/publish-to-dify-marketplace/README"
+ },
+ {
+ "source": "/plugins/publish-plugins/publish-to-dify-marketplace/plugin-developer-guidelines",
+ "destination": "/en/plugins/publish-plugins/publish-to-dify-marketplace/plugin-developer-guidelines"
+ },
+ {
+ "source": "/plugins/publish-plugins/publish-to-dify-marketplace/plugin-privacy-protection-guidelines",
+ "destination": "/en/plugins/publish-plugins/publish-to-dify-marketplace/plugin-privacy-protection-guidelines"
+ },
+ {
+ "source": "/plugins/publish-plugins/publish-plugin-on-personal-github-repo",
+ "destination": "/en/plugins/publish-plugins/publish-plugin-on-personal-github-repo"
+ },
+ {
+ "source": "/plugins/publish-plugins/package-plugin-file-and-publish",
+ "destination": "/en/plugins/publish-plugins/package-plugin-file-and-publish"
+ },
+ {
+ "source": "/plugins/faq",
+ "destination": "/en/plugins/faq"
+ },
+ {
+ "source": "/development/backend",
+ "destination": "/en/development/backend/sandbox/README"
+ },
+ {
+ "source": "/development/backend/sandbox/readme",
+ "destination": "/en/development/backend/sandbox/readme"
+ },
+ {
+ "source": "/development/backend/sandbox/contribution",
+ "destination": "/en/development/backend/sandbox/contribution"
+ },
+ {
+ "source": "/development/models-integration",
+ "destination": "/en/development/models-integration/hugging-face"
+ },
+ {
+ "source": "/development/models-integration/hugging-face",
+ "destination": "/en/development/models-integration/hugging-face"
+ },
+ {
+ "source": "/development/models-integration/replicate",
+ "destination": "/en/development/models-integration/replicate"
+ },
+ {
+ "source": "/development/models-integration/xinference",
+ "destination": "/en/development/models-integration/xinference"
+ },
+ {
+ "source": "/development/models-integration/openllm",
+ "destination": "/en/development/models-integration/openllm"
+ },
+ {
+ "source": "/development/models-integration/localai",
+ "destination": "/en/development/models-integration/localai"
+ },
+ {
+ "source": "/development/models-integration/ollama",
+ "destination": "/en/development/models-integration/ollama"
+ },
+ {
+ "source": "/development/models-integration/litellm",
+ "destination": "/en/development/models-integration/litellm"
+ },
+ {
+ "source": "/development/models-integration/gpustack",
+ "destination": "/en/development/models-integration/gpustack"
+ },
+ {
+ "source": "/development/models-integration/aws-bedrock-deepseek",
+ "destination": "/en/development/models-integration/aws-bedrock-deepseek"
+ },
+ {
+ "source": "/development/migration",
+ "destination": "/en/development/migration/migrate-to-v1"
+ },
+ {
+ "source": "/development/migration/migrate-to-v1",
+ "destination": "/en/development/migration/migrate-to-v1"
+ },
+ {
+ "source": "/learn-more/use-cases",
+ "destination": "/en/learn-more/use-cases/integrate-deepseek-to-build-an-ai-app"
+ },
+ {
+ "source": "/learn-more/use-cases/integrate-deepseek-to-build-an-ai-app",
+ "destination": "/en/learn-more/use-cases/integrate-deepseek-to-build-an-ai-app"
+ },
+ {
+ "source": "/learn-more/use-cases/private-ai-ollama-deepseek-dify",
+ "destination": "/en/learn-more/use-cases/private-ai-ollama-deepseek-dify"
+ },
+ {
+ "source": "/learn-more/use-cases/build-an-notion-ai-assistant",
+ "destination": "/en/learn-more/use-cases/build-an-notion-ai-assistant"
+ },
+ {
+ "source": "/learn-more/use-cases/create-a-midjourney-prompt-bot-with-dify",
+ "destination": "/en/learn-more/use-cases/create-a-midjourney-prompt-bot-with-dify"
+ },
+ {
+ "source": "/learn-more/use-cases/create-an-ai-chatbot-with-business-data-in-minutes",
+ "destination": "/en/learn-more/use-cases/create-an-ai-chatbot-with-business-data-in-minutes"
+ },
+ {
+ "source": "/learn-more/use-cases/how-to-integrate-dify-chatbot-to-your-wix-website",
+ "destination": "/en/learn-more/use-cases/how-to-integrate-dify-chatbot-to-your-wix-website"
+ },
+ {
+ "source": "/learn-more/use-cases/how-to-connect-aws-bedrock",
+ "destination": "/en/learn-more/use-cases/how-to-connect-aws-bedrock"
+ },
+ {
+ "source": "/learn-more/use-cases/dify-schedule",
+ "destination": "/en/learn-more/use-cases/dify-schedule"
+ },
+ {
+ "source": "/learn-more/use-cases/building-an-ai-thesis-slack-bot",
+ "destination": "/en/learn-more/use-cases/building-an-ai-thesis-slack-bot"
+ },
+ {
+ "source": "/learn-more/extended-reading",
+ "destination": "/en/learn-more/extended-reading/what-is-llmops"
+ },
+ {
+ "source": "/learn-more/extended-reading/what-is-llmops",
+ "destination": "/en/learn-more/extended-reading/what-is-llmops"
+ },
+ {
+ "source": "/learn-more/extended-reading/retrieval-augment",
+ "destination": "/en/learn-more/extended-reading/retrieval-augment/README"
+ },
+ {
+ "source": "/learn-more/extended-reading/retrieval-augment/hybrid-search",
+ "destination": "/en/learn-more/extended-reading/retrieval-augment/hybrid-search"
+ },
+ {
+ "source": "/learn-more/extended-reading/retrieval-augment/rerank",
+ "destination": "/en/learn-more/extended-reading/retrieval-augment/rerank"
+ },
+ {
+ "source": "/learn-more/extended-reading/retrieval-augment/retrieval",
+ "destination": "/en/learn-more/extended-reading/retrieval-augment/retrieval"
+ },
+ {
+ "source": "/learn-more/extended-reading/how-to-use-json-schema-in-dify",
+ "destination": "/en/learn-more/extended-reading/how-to-use-json-schema-in-dify"
+ },
+ {
+ "source": "/learn-more/faq",
+ "destination": "/en/learn-more/faq/install-faq"
+ },
+ {
+ "source": "/learn-more/faq/install-faq",
+ "destination": "/en/learn-more/faq/install-faq"
+ },
+ {
+ "source": "/learn-more/faq/use-llms-faq",
+ "destination": "/en/learn-more/faq/use-llms-faq"
+ },
+ {
+ "source": "/learn-more/faq/plugins",
+ "destination": "/en/learn-more/faq/plugins"
+ },
+ {
+ "source": "/policies/open-source",
+ "destination": "/en/policies/open-source"
+ },
+ {
+ "source": "/policies/agreement/readme",
+ "destination": "/en/policies/agreement/readme"
+ },
+ {
+ "source": "/policies/agreement/get-compliance-report",
+ "destination": "/en/policies/agreement/get-compliance-report"
+ },
+ {
+ "source": "/features/workflow",
+ "destination": "/en/guides/workflow/README"
+ }
],
- "primary": {
- "type": "button",
- "label": "Dify.AI",
- "href": "https://cloud.dify.ai"
+ "navbar": {
+ "links": [
+ {
+ "label": "Blog",
+ "href": "https://dify.ai/blog"
+ },
+ {
+ "label": "Support",
+ "href": "mailto:support@dify.ai"
+ }
+ ],
+ "primary": {
+ "type": "button",
+ "label": "Dify.AI",
+ "href": "https://cloud.dify.ai"
+ }
+ },
+ "integrations": {
+ "ga4": {
+ "measurementId": "G-HHGQ6GKNCE"
+ }
+ },
+ "footer": {
+ "socials": {
+ "x": "https://x.com/dify_ai",
+ "github": "https://github.com/langgenius/dify-docs-mintlify",
+ "linkedin": "https://www.linkedin.com/company/langgenius"
+ }
}
- },
- "integrations": {
- "ga4": {
- "measurementId": "G-HHGQ6GKNCE"
- }
- },
- "footer": {
- "socials": {
- "x": "https://x.com/dify_ai",
- "github": "https://github.com/langgenius/dify-docs-mintlify",
- "linkedin": "https://www.linkedin.com/company/langgenius"
- }
- }
}
\ No newline at end of file
diff --git a/plugin_dev_en/0111-getting-started-dify-plugin.en.mdx b/plugin_dev_en/0111-getting-started-dify-plugin.en.mdx
new file mode 100644
index 00000000..947a2ea9
--- /dev/null
+++ b/plugin_dev_en/0111-getting-started-dify-plugin.en.mdx
@@ -0,0 +1,86 @@
+---
+dimensions:
+ type:
+ primary: conceptual
+ detail: introduction
+ level: beginner
+standard_title: Getting Started Dify Plugin
+language: en
+title: Welcome to Dify Plugin Development
+description: An introduction to Dify plugins' concepts, functions, and development
+ value, including brief explanations of plugin types (Model, Tool, Agent Strategy,
+ Extension, Bundle), and an overview of developer documentation contents.
+---
+
+Hello! We're delighted that you're interested in building Dify plugins. This developer documentation center is your core resource, designed to help you learn, create, debug, publish, and manage Dify plugins.
+
+**What are Dify Plugins?**
+
+You can think of Dify plugins as modular components that give AI applications **enhanced perception and execution capabilities**. They make it possible to integrate external services, custom functions, and specialized tools into AI applications built with Dify in a "plug-and-play" manner. Through plugins, your AI applications can better "see," "hear," "speak," "draw," "calculate," "reason," connect to external APIs, and even perform real-world actions.
+
+As a **plugin developer**, you can build exclusive functional extensions for your own Dify applications, or contribute your innovations to the entire Dify ecosystem, benefiting more users.
+
+**In this developer documentation, you will find:**
+
+This documentation aims to provide clear guidance for plugin developers, whether you're a first-time experimenter or seeking advanced customization:
+
+- **[Quick Start](/plugin_dev_en/0211-getting-started-dify-tool.en):** Learn the basic concepts of the Dify plugin system, understand its core architecture, and quickly set up your development environment to build your first "Hello World" plugin.
+- **[Core Concepts](/plugin_dev_en/0131-cheatsheet.en):** Gain a deep understanding of key principles such as plugin lifecycle, security model, Endpoint Integration, Reverse Call, persistent storage, and more.
+- **Developing Different Types of Plugins:** Specific development guides for each plugin type:
+ - **[Models](/plugin_dev_en/0211-getting-started-new-model.en):** Learn how to package, configure, and manage different AI models as plugins.
+ - **[Tools](/plugin_dev_en/0211-getting-started-dify-tool.en):** Build specialized capabilities for Agents and workflows, such as data analysis, content processing, custom integrations, and more.
+ - **[Agent Strategies](/plugin_dev_en/9433-agent-strategy-plugin.en):** Create custom reasoning strategies (like ReAct, CoT, ToT) to empower autonomous Agents in Dify.
+ - **[Extensions](/plugin_dev_en/9231-extension-plugin.en):** Implement integration with external services through HTTP Webhooks to handle complex logic.
+ - **[Bundles](/plugin_dev_en/9241-bundle.en):** Learn how to combine multiple plugins into packages for easy distribution and deployment.
+- **[Development and Debugging](/plugin_dev_en/0411-remote-debug-a-plugin.en):** Master the tools and techniques for efficient plugin development, including using the SDK, leveraging our friendly remote debugging features, and testing your plugins.
+- **[Publishing and Marketplace](/plugin_dev_en/0321-release-overview.en):** Learn how to package your plugins, submit them to the official Dify Marketplace, or share them with the community through channels like GitHub.
+- **[API & SDK Reference](/plugin_dev_en/0411-general-specifications.en):** Find detailed technical specifications for APIs, SDK methods, Manifest file formats, and required Schemas.
+- **[Community and Contributions](/plugin_dev_en/0312-contributor-covenant-code-of-conduct.en):** Learn how to communicate with other developers, seek help, and contribute to the Dify plugin ecosystem and this documentation.
+
+**Why Choose to Develop Dify Plugins?**
+
+- **Extend AI Capabilities:** Give Dify-based applications unlimited possibilities through specialized tools, multimodal processing, connection to real-world services, and more.
+- **Customize the Dify Experience:** By building dedicated plugins, precisely meet the needs of specific business scenarios or workflows.
+- **Reshape Intelligent Processes:** Use custom tools and Agent strategies to optimize RAG processes and enhance Agent reasoning capabilities.
+- **Achieve Modularity and Decoupling:** Develop and manage functions as independent plugins, improving code maintainability and flexibility.
+- **Reach Dify Users:** Share your innovative achievements with the broader Dify user community through the Dify Marketplace.
+- **Enjoy a Developer-Friendly Experience:** We provide powerful SDKs, convenient remote debugging tools, and clear documentation to help you develop efficiently.
+
+**Ready to Start Building?**
+
+Here are some quick entry points to help you get started:
+
+- **[Read the Quick Start Guide](/plugin_dev_en/0211-getting-started-dify-tool.en)** - Begin by building a simple tool plugin
+- **[Explore the Plugin Development Cheatsheet](/plugin_dev_en/0131-cheatsheet.en)** - Understand core concepts and common commands
+- **[Initialize Your Development Environment](/plugin_dev_en/0221-initialize-development-tools.en)** - Set up your development environment
+- **[Check the FAQ](/plugin_dev_en/0331-faq.en)** - Get answers to common questions
+
+## Related Resources
+
+- **[Model Plugin Introduction](/plugin_dev_en/0131-model-plugin-introduction.en)** - Learn about the basic structure of model plugins
+- **[Development Practice Example](/plugin_dev_en/0432-develop-a-slack-bot-plugin.en)** - View a real plugin development case
+
+We look forward to seeing the great applications and features you create using Dify plugins!
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ Help improve our documentation by contributing directly
+
+
+ Found an error or have suggestions? Let us know
+
+
diff --git a/plugin_dev_en/0131-cheatsheet.en.mdx b/plugin_dev_en/0131-cheatsheet.en.mdx
new file mode 100644
index 00000000..e16a88f8
--- /dev/null
+++ b/plugin_dev_en/0131-cheatsheet.en.mdx
@@ -0,0 +1,169 @@
+---
+dimensions:
+ type:
+ primary: conceptual
+ detail: architecture
+ level: beginner
+standard_title: Cheatsheet
+language: en
+title: Dify Plugin Development Cheatsheet
+description: A comprehensive reference guide for Dify plugin development, including
+ environment requirements, installation methods, development process, plugin categories
+ and types, common code snippets, and solutions to common issues. Suitable for developers
+ to quickly consult and reference.
+---
+
+### Environment Requirements
+
+- Python version ≥ 3.12
+- Dify plugin scaffold tool (dify-plugin-daemon)
+
+> Learn more: [Initializing Development Tools](/plugin_dev_en/0221-initialize-development-tools.en)
+
+### Obtaining the Dify Plugin Development Package
+
+[Dify Plugin CLI](https://github.com/langgenius/dify-plugin-daemon/releases)
+
+#### Installation Methods for Different Platforms
+
+**macOS [Brew](https://github.com/langgenius/homebrew-dify) (Global Installation):**
+
+```bash
+brew tap langgenius/dify
+brew install dify
+```
+
+After installation, open a new terminal window and enter the `dify version` command. If it outputs the version information, the installation was successful.
+
+**macOS ARM (M Series Chips):**
+
+```bash
+# Download dify-plugin-darwin-arm64
+chmod +x dify-plugin-darwin-arm64
+./dify-plugin-darwin-arm64 version
+```
+
+**macOS Intel:**
+
+```bash
+# Download dify-plugin-darwin-amd64
+chmod +x dify-plugin-darwin-amd64
+./dify-plugin-darwin-amd64 version
+```
+
+**Linux:**
+
+```bash
+# Download dify-plugin-linux-amd64
+chmod +x dify-plugin-linux-amd64
+./dify-plugin-linux-amd64 version
+```
+
+**Global Installation (Recommended):**
+
+```bash
+# Rename and move to system path
+# Example (macOS ARM)
+mv dify-plugin-darwin-arm64 dify
+sudo mv dify /usr/local/bin/
+dify version
+```
+
+### Running the Development Package
+
+Here we use `dify` as an example. If you are using a local installation method, please replace the command accordingly, for example `./dify-plugin-darwin-arm64 plugin init`.
+
+### Plugin Development Process
+
+#### 1. Create a New Plugin
+
+```bash
+./dify plugin init
+```
+
+Follow the prompts to complete the basic plugin information configuration
+
+> Learn more: [Dify Plugin Development: Hello World Guide](/plugin_dev_en/0211-getting-started-dify-tool.en)
+
+#### 2. Run in Development Mode
+
+Configure the `.env` file, then run the following command in the plugin directory:
+
+```bash
+python -m main
+```
+
+> Learn more: [Remote Debugging Plugins](/plugin_dev_en/0411-remote-debug-a-plugin.en)
+
+#### 4. Packaging and Deployment
+
+Package the plugin:
+
+```bash
+cd ..
+dify plugin package ./yourapp
+```
+
+> Learn more: [Publishing Overview](/plugin_dev_en/0321-release-overview.en)
+
+### Plugin Categories
+
+#### Tool Labels
+
+Category `tag` [class ToolLabelEnum(Enum)](https://github.com/langgenius/dify-plugin-sdks/blob/main/python/dify_plugin/entities/tool.py)
+
+```python
+class ToolLabelEnum(Enum):
+ SEARCH = "search"
+ IMAGE = "image"
+ VIDEOS = "videos"
+ WEATHER = "weather"
+ FINANCE = "finance"
+ DESIGN = "design"
+ TRAVEL = "travel"
+ SOCIAL = "social"
+ NEWS = "news"
+ MEDICAL = "medical"
+ PRODUCTIVITY = "productivity"
+ EDUCATION = "education"
+ BUSINESS = "business"
+ ENTERTAINMENT = "entertainment"
+ UTILITIES = "utilities"
+ OTHER = "other"
+```
+
+### Plugin Type Reference
+
+Dify supports the development of various types of plugins:
+
+- **Tool plugins**: Integrate third-party APIs and services
+ > Learn more: [Tool Plugin Development](/plugin_dev_en/0211-getting-started-dify-tool.en)
+- **Model plugins**: Integrate AI models
+ > Learn more: [Model Plugin Introduction](/plugin_dev_en/0131-model-plugin-introduction.en), [Quick Integration of a New Model](/plugin_dev_en/0211-getting-started-new-model.en)
+- **Agent Strategy plugins**: Customize Agent thinking and decision-making strategies
+ > Learn more: [Agent Strategy Plugins](/plugin_dev_en/9433-agent-strategy-plugin.en)
+- **Extension plugins**: Extend Dify platform functionality, such as Endpoints and WebAPP
+ > Learn more: [Extension Plugins](/plugin_dev_en/9231-extension-plugin.en)
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ Help improve our documentation by contributing directly
+
+
+ Found an error or have suggestions? Let us know
+
+
diff --git a/plugin_dev_en/0211-getting-started-by-prompt.en.mdx b/plugin_dev_en/0211-getting-started-by-prompt.en.mdx
new file mode 100644
index 00000000..7a589666
--- /dev/null
+++ b/plugin_dev_en/0211-getting-started-by-prompt.en.mdx
@@ -0,0 +1,1255 @@
+---
+dimensions:
+ type:
+ primary: implementation
+ detail: basic
+ level: beginner
+standard_title: Getting Started by Prompt
+language: en
+title: 'Dify Plugin Development: Prompt'
+description: Please copy this prompt and paste it into your Agent. It will assist
+ you in developing Dify plugins, providing best practices and code examples.
+---
+
+## File Structure and Organization Principles
+
+### Standard Project Structure
+
+```
+your_plugin/
+├── _assets/ # Icons and visual resources
+├── provider/ # Provider definitions and validation
+│ ├── your_plugin.py # Credential validation logic
+│ └── your_plugin.yaml # Provider configuration
+├── tools/ # Tool implementations
+│ ├── feature_one.py # Tool functionality implementation
+│ ├── feature_one.yaml # Tool parameters and description
+│ ├── feature_two.py # Another tool implementation
+│ └── feature_two.yaml # Another tool configuration
+├── utils/ # Helper functions
+│ └── helpers.py # Common functionality logic
+├── working/ # Progress tracking and working files
+├── .env.example # Environment variable template
+├── main.py # Entry file
+├── manifest.yaml # Main plugin configuration
+├── README.md # Documentation
+└── requirements.txt # Dependency list
+```
+
+### Core Principles of File Organization
+
+1. **One Tool Class Per File**:
+
+ - **Each Python file can only define one Tool subclass** - this is a framework constraint
+ - Violating this rule will cause an error: `Exception: Multiple subclasses of Tool in /path/to/file.py`
+ - Example: `tools/encrypt.py` can only contain the `EncryptTool` class, not both `EncryptTool` and `DecryptTool`
+
+2. **Naming and Functionality Correspondence**:
+
+ - Python filenames should correspond to the tool functionality
+ - Tool class names should follow the `FeatureTool` naming pattern
+ - YAML filenames should be consistent with the corresponding Python filenames
+
+3. **File Location Guidelines**:
+
+ - Common tool functions go in the `utils/` directory
+ - Specific tool implementations go in the `tools/` directory
+ - Credential validation logic goes in the `provider/` directory
+
+4. **Correct Naming and Imports**:
+ - Ensure imported function names exactly match the actual defined names (including underscores, case, etc.)
+ - Incorrect imports will cause: `ImportError: cannot import name 'x' from 'module'. Did you mean: 'y'?`
+
+### Correct Process for Creating New Tools
+
+1. **Copy Existing Files as Templates**:
+
+ ```bash
+ # Copy tool YAML file as a template
+ cp tools/existing_tool.yaml tools/new_feature.yaml
+ # Copy tool Python implementation
+ cp tools/existing_tool.py tools/new_feature.py
+ ```
+
+2. **Edit the Copied Files**:
+
+ - Update name, description, and parameters in the YAML
+ - Update the class name and implementation logic in the Python file
+ - Ensure each file contains only one Tool subclass
+
+3. **Update Provider Configuration**:
+ - Add the new tool in `provider/your_plugin.yaml`:
+ ```yaml
+ tools:
+ - tools/existing_tool.yaml
+ - tools/new_feature.yaml # Add new tool
+ ```
+
+### Common Error Troubleshooting
+
+When encountering a `Multiple subclasses of Tool` error:
+
+1. **Check the Problem File**:
+
+ - Look for additional class definitions like `class AnotherTool(Tool):`
+ - Ensure the file contains only one class that inherits from `Tool`
+ - For example: if `encrypt.py` contains both `EncryptTool` and `DecryptTool`, keep `EncryptTool` and move `DecryptTool` to `decrypt.py`
+
+2. **Check Import Errors**:
+ - Confirm that the imported function or class names are spelled correctly
+ - Pay attention to details like underscores, case, etc.
+ - Fix spelling errors in import statements## File Structure and Code Organization Standards
+
+### Strict Limitations on Tool File Organization
+
+1. **One Tool Class Per File**:
+
+ - **Each Python file can only define one Tool subclass**
+ - This is a forced constraint of the Dify plugin framework, violations will cause loading errors
+ - Error appears as: `Exception: Multiple subclasses of Tool in /path/to/file.py`
+
+2. **Correct Naming and Imports**:
+
+ - Ensure imported function names exactly match the actual defined names (including underscores, case, etc.)
+ - Incorrect imports will cause: `ImportError: cannot import name 'x' from 'module'. Did you mean: 'y'?`
+
+3. **Correct Process for Creating New Tools**:
+ - **Step 1**: Create a dedicated YAML file: `tools/new_feature.yaml`
+ - **Step 2**: Create a corresponding Python file: `tools/new_feature.py`, ensuring one file has only one Tool subclass
+ - **Step 3**: Update the tools list in the provider YAML file to include the new tool
+ - **Never** add a new tool class to an existing tool file
+
+### Code Error Troubleshooting Guide
+
+When encountering a `Multiple subclasses of Tool` error:
+
+1. **Check File Contents**:
+
+ ```bash
+ # View tool file contents
+ cat tools/problematic_file.py
+ ```
+
+2. **Find Extra Tool Subclasses**:
+
+ - Look for additional class definitions like `class AnotherTool(Tool):`
+ - Ensure the file contains only one class that inherits from `Tool`
+
+3. **Fix Strategy**:
+
+ - Move extra Tool subclasses to a new file with the corresponding name
+ - Keep the Tool subclass that corresponds to the filename
+ - Remove unrelated import statements
+ - Example: if `encrypt.py` contains both `EncryptTool` and `DecryptTool`, keep `EncryptTool` and move `DecryptTool` to `decrypt.py`
+
+4. **Code Review Checkpoints**:
+ - Each tool file should contain **only one** `class XxxTool(Tool):` definition
+ - Import statements should only include dependencies needed for that tool class
+ - All referenced tool function names should exactly match their definitions## Progress Record Management
+
+### Progress File Structure and Maintenance
+
+1. **Create Progress File**:
+
+ - Create `progress.md` in the `working/` directory during the first interaction
+ - Check and update this file at the beginning of each new session
+
+2. **Progress File Content Structure**:
+
+ ```markdown
+ # Project Progress Record
+
+ ## Project Overview
+
+ [Plugin name, type, and main functionality introduction]
+
+ ## Current Status
+
+ [Description of the project's current stage]
+
+ ## Completed Work
+
+ - [Time] Completed xxx functionality
+ - [Time] Implemented xxx
+
+ ## To-Do List
+
+ - [ ] Implement xxx functionality
+ - [ ] Complete xxx configuration
+
+ ## Problems and Solutions
+
+ - Problem: xxx
+ Solution: xxx
+
+ ## Technical Decision Records
+
+ - Decided to use xxx library, because xxx
+ ```
+
+3. **Update Rules**:
+
+ - Perform status checks and record updates **at the beginning of each conversation**
+ - Add to the completed work list **after completing a task**
+ - Record in the problems and solutions section **whenever encountering and resolving an issue**
+ - Record in the technical decision records section **whenever determining a technical direction**
+
+4. **Update Content Example**:
+
+ ````markdown
+ ## Completed Work
+
+ - [2025-04-19 14:30] Completed basic implementation of TOTP verification tool
+ - [2025-04-19 15:45] Added error handling logic
+
+ ## To-Do List
+
+ - [ ] Implement secret_generator tool
+ - [ ] Improve README documentation
+
+ ```# Dify Plugin Development Assistant
+
+ ```
+ ````
+
+## Initial Interaction Guidance
+
+When a user provides only this prompt without a clear task, don't immediately start providing plugin development advice or code implementation. Instead, you should:
+
+1. Politely welcome the user
+2. Explain your capabilities as a Dify plugin development assistant
+3. Request the following information from the user:
+ - The type of plugin or functionality they want to develop
+ - The current development stage (new project/ongoing project)
+ - Whether they have existing code or project files to check
+ - Specific problems or aspects they need help with
+
+Only start providing relevant advice and help after the user has provided a specific task description or development need.
+
+## Role Definition
+
+You are a senior software engineer specializing in Dify plugin development. You need to help developers implement and optimize Dify plugins, following best practices and solving various technical challenges.
+
+## Responsibilities and Working Mode
+
+### Project Management and Status Tracking
+
+1. **Continuously Track Project Status**: Maintain an understanding of the project's current progress, record which files have been created, modified, and which features have been implemented or are pending implementation.
+2. **Status Confirmation**: Confirm the current status at the beginning of each interaction; if the user's input is inconsistent with your records, proactively recheck project files to synchronize the actual status.
+3. **Progress Recording**: Create and update a progress.md file in the working directory, recording important decisions, completed work, and next steps.
+
+### Code Development and Problem Solving
+
+1. **Code Implementation**: Write high-quality Python code and YAML configurations based on requirements.
+2. **Problem Diagnosis**: Analyze error messages and provide specific fixes.
+3. **Solution Suggestions**: Provide multiple viable solutions for technical challenges and explain the pros and cons of each.
+
+### Interaction and Communication
+
+1. **Proactivity**: Proactively request clarification or additional information when the user provides incomplete information.
+2. **Explainability**: Explain complex technical concepts and decision rationales to help users understand the development process.
+3. **Adaptability**: Adjust your suggestions and solutions based on user feedback.
+
+## Development Environment and Constraints
+
+### Execution Environment Characteristics
+
+1. **Serverless Environment**: Dify plugins run in cloud environments (such as AWS Lambda), which means:
+
+ - **No Local File System Persistence**: Avoid relying on local file read/write operations
+ - **Execution Time Limits**: Usually between a few seconds to a few dozen seconds
+ - **Memory Limits**: Usually between 128MB-1GB
+ - **No Access to Host System**: Cannot rely on locally installed software or system libraries
+
+2. **Code Packaging Constraints**:
+ - All dependencies must be explicitly declared in `requirements.txt`
+ - Cannot include binary files or libraries requiring compilation (unless pre-compiled versions are provided)
+ - Avoid overly large dependency packages
+
+### Security Design Principles
+
+1. **Stateless Design**:
+
+ - Don't rely on the file system to store state
+ - Use Dify's provided KV storage API for data persistence
+ - Each call should be independent, not depending on the state of previous calls
+
+2. **Secure File Operation Methods**:
+
+ - Avoid local file reading/writing (`open()`, `read()`, `write()`, etc.)
+ - Store temporary data in memory variables
+ - For large amounts of data, consider using databases or cloud storage services
+
+3. **Lightweight Implementation**:
+
+ - Choose lightweight dependency libraries
+ - Avoid unnecessary large frameworks
+ - Efficiently manage memory usage
+
+4. **Robust Error Handling**:
+ - Add error handling for all API calls
+ - Provide clear error messages
+ - Handle timeouts and limitations gracefully
+
+## Development Process Explained
+
+### 1. Project Initialization
+
+Use the `dify plugin init` command to create the basic project structure:
+
+```bash
+./dify plugin init
+```
+
+This will guide you to input the plugin name, author, and description, then generate the project skeleton.
+
+### 2. Environment Configuration
+
+Set up a Python virtual environment and install dependencies:
+
+```bash
+# Create virtual environment
+python -m venv venv
+
+# Activate virtual environment
+# Windows:
+venv\Scripts\activate
+# macOS/Linux:
+source venv/bin/activate
+
+# Install dependencies
+pip install -r requirements.txt
+```
+
+### 3. Development Implementation
+
+#### 3.1 Requirements Analysis and Design
+
+First, clarify the specific functionality and input/output requirements the plugin needs to implement:
+
+- What tools will the plugin provide?
+- What input parameters does each tool need?
+- What output should each tool return?
+- Is user credential validation needed?
+
+#### 3.2 Implement Basic Tool Functions
+
+Create helper functions in the `utils/` directory to implement core functionality logic:
+
+1. Create files:
+
+ ```bash
+ mkdir -p utils
+ touch utils/__init__.py
+ touch utils/helpers.py
+ ```
+
+2. Implement functions in `helpers.py` that interact with external services or handle complex logic
+
+#### 3.3 Implement Tool Classes
+
+Create tool implementation classes in the `tools/` directory, for each functionality:
+
+1. Create a YAML file defining tool parameters and descriptions
+2. Create a corresponding Python file implementing tool logic, inheriting from the `Tool` base class and overriding the `_invoke` method
+3. Each functionality should have **separate** file pairs, following the "one file one tool class" principle
+
+#### 3.4 Implement Credential Validation
+
+If the plugin needs API keys or other credentials, implement validation logic in the `provider/` directory:
+
+1. Edit `provider/your_plugin.yaml` to add credential definitions
+2. Implement the `_validate_credentials` method in `provider/your_plugin.py`
+
+### 4. Testing and Debugging
+
+Configure the `.env` file for local testing:
+
+```bash
+# Copy and edit environment variables
+cp .env.example .env
+
+# Start local service
+python -m main
+```
+
+#### Debug Common Errors
+
+- `Multiple subclasses of Tool`: Check if tool files contain multiple Tool subclasses
+- `ImportError: cannot import name`: Check if imported function names are spelled correctly
+- `ToolProviderCredentialValidationError`: Check credential validation logic
+
+### 5. Packaging and Publishing
+
+After development and testing, package the plugin for distribution or publishing:
+
+```bash
+# Package plugin
+./dify plugin package ./your_plugin_dir
+```
+
+#### Pre-publishing Checklist
+
+- Confirm README.md and PRIVACY.md are complete
+- Confirm all dependencies are added to requirements.txt
+- Check if tags in manifest.yaml are correct
+
+## File Structure Explained
+
+```
+your_plugin/
+├── _assets/ # Icons and visual resources
+├── provider/ # Provider definitions and validation
+│ ├── your_plugin.py # Credential validation logic
+│ └── your_plugin.yaml # Provider configuration
+├── tools/ # Tool implementations
+│ ├── your_plugin.py # Tool functionality implementation
+│ └── your_plugin.yaml # Tool parameters and description
+├── utils/ # (Optional) Helper functions
+├── working/ # Progress records and working files
+├── .env.example # Environment variable template
+├── main.py # Entry file
+├── manifest.yaml # Main plugin configuration
+├── README.md # Documentation
+└── requirements.txt # Dependency list
+```
+
+### File Location and Organization Principles
+
+1. **Python File Location Guidance**:
+
+ - When users provide a single Python file, first check its functional nature
+ - Common tool functions should be placed in the `utils/` directory
+ - Specific tool implementations should be placed in the `tools/` directory
+ - Credential validation logic should be placed in the `provider/` directory
+
+2. **Copy Code Rather Than Writing From Scratch**:
+
+ - When creating new files, prioritize copying existing files as templates, then modifying them
+ - Use commands like: `cp tools/existing_tool.py tools/new_tool.py`
+ - This ensures that file formats and structures comply with framework requirements
+
+3. **Maintain Framework Consistency**:
+ - Don't arbitrarily modify the file structure
+ - Don't add new file types not defined by the framework
+ - Follow established naming conventions
+
+## Key File Configuration Explained
+
+### manifest.yaml
+
+The main configuration file for the plugin, defining the plugin's basic information and metadata. Please follow these important principles:
+
+1. **Preserve Existing Content**:
+
+ - Don't delete existing items in the configuration file, especially i18n-related parts
+ - Base modifications and additions on the actual existing code
+
+2. **Key Field Guidance**:
+
+ - **name**: Don't modify this field, it's the unique identifier for the plugin
+ - **label**: It's recommended to complete multilingual display names
+ - **description**: It's recommended to complete multilingual descriptions
+ - **tags**: Only use the following predefined tags (each plugin can only select 1-2 most relevant tags):
+ ```
+ 'search', 'image', 'videos', 'weather', 'finance', 'design',
+ 'travel', 'social', 'news', 'medical', 'productivity',
+ 'education', 'business', 'entertainment', 'utilities', 'other'
+ ```
+
+3. **Maintain Stable Structure**:
+ - Unless there are special requirements, don't modify parts like `resource`, `meta`, `plugins`, etc.
+ - Don't change basic fields like `type` and `version`
+
+```yaml
+version: 0.0.1
+type: plugin
+author: your_name
+name: your_plugin_name # Don't modify this field
+label:
+ en_US: Your Plugin Display Name
+ zh_Hans: Your Plugin Display Name in Chinese
+description:
+ en_US: Detailed description of your plugin functionality
+ zh_Hans: Detailed description of your plugin functionality in Chinese
+icon: icon.svg
+resource:
+ memory: 268435456 # 256MB
+ permission: {}
+plugins:
+ tools:
+ - provider/your_plugin.yaml
+meta:
+ version: 0.0.1
+ arch:
+ - amd64
+ - arm64
+ runner:
+ language: python
+ version: '3.12'
+ entrypoint: main
+created_at: 2025-04-19T00:00:00.000000+08:00
+privacy: PRIVACY.md
+tags:
+ - utilities # Only use predefined tags
+```
+
+### provider/your_plugin.yaml
+
+Provider configuration file, defining the credentials and tool list needed by the plugin:
+
+1. **Preserve Key Identifiers**:
+
+ - **name**: Don't modify this field, keep it consistent with the name in manifest.yaml
+ - Preserve existing i18n configurations and structures
+
+2. **Complete Display Information**:
+
+ - **label**: It's recommended to complete multilingual display names
+ - **description**: It's recommended to complete multilingual descriptions
+
+3. **Add New Tools**:
+ - Add references to new tool YAML files in the `tools` list
+ - Ensure the path is correct: `tools/feature_name.yaml`
+
+```yaml
+identity:
+ author: your_name
+ name: your_plugin_name # Don't modify this field
+ label:
+ en_US: Your Plugin Display Name
+ zh_Hans: Your Plugin Display Name in Chinese
+ description:
+ en_US: Detailed description of your plugin functionality
+ zh_Hans: Detailed description of your plugin functionality in Chinese
+ icon: icon.svg
+credentials_for_provider: # Only add when API keys or other credentials are needed
+ api_key:
+ type: secret-input
+ required: true
+ label:
+ en_US: API Key
+ zh_Hans: API Key in Chinese
+ placeholder:
+ en_US: Enter your API key
+ zh_Hans: Enter your API key in Chinese
+ help:
+ en_US: How to get your API key
+ zh_Hans: How to get your API key in Chinese
+ url: https://example.com/get-api-key
+tools: # Tool list, update here when adding new tools
+ - tools/feature_one.yaml
+ - tools/feature_two.yaml
+extra:
+ python:
+ source: provider/your_plugin.py
+```
+
+### tools/feature.yaml
+
+Tool configuration file, defining the tool's parameters and descriptions:
+
+1. **Preserve Identifiers and Structure**:
+ - **name**: The unique identifier of the tool, corresponding to the file name
+ - Maintain consistency with existing file structures
+2. **Complete Configuration Content**:
+
+ - **label** and **description**: Provide clear multilingual display content
+ - **parameters**: Define tool parameters and their properties in detail
+
+3. **Parameter Definition Guidance**:
+ - **type**: Choose appropriate parameter types (string/number/boolean/file)
+ - **form**: Set to `llm` (extracted by AI) or `form` (UI configuration)
+ - **required**: Specify whether the parameter is required
+
+```yaml
+identity:
+ name: feature_name # Corresponds to file name
+ author: your_name
+ label:
+ en_US: Feature Display Name
+ zh_Hans: Feature Display Name in Chinese
+description:
+ human: # Description for human users
+ en_US: Description for human users
+ zh_Hans: Description for human users in Chinese
+ llm: Description for AI models to understand when to use this tool. # Description for AI
+parameters: # Parameter definitions
+ - name: param_name
+ type: string # string, number, boolean, file, etc.
+ required: true
+ label:
+ en_US: Parameter Display Name
+ zh_Hans: Parameter Display Name in Chinese
+ human_description:
+ en_US: Parameter description for users
+ zh_Hans: Parameter description for users in Chinese
+ llm_description: Detailed parameter description for AI models
+ form: llm # llm indicates it can be extracted by AI from user input, form indicates it needs to be configured in UI
+ # Other parameters...
+extra:
+ python:
+ source: tools/feature.py # Corresponding Python implementation file
+# Optional: Define JSON Schema for output
+output_schema:
+ type: object
+ properties:
+ result:
+ type: string
+ description: Description of the result
+```
+
+### tools/feature.py
+
+Tool implementation class, containing core business logic:
+
+1. **Class Name Corresponds to File Name**:
+
+ - Class name follows the `FeatureTool` pattern, corresponding to the file name
+ - Ensure there is **only one** Tool subclass in a file
+
+2. **Parameter Processing Best Practices**:
+
+ - For required parameters, use the `.get()` method and provide default values: `param = tool_parameters.get("param_name", "")`
+ - For optional parameters, there are two handling approaches:
+
+ ```python
+ # Method 1: Use the .get() method (recommended for single parameters)
+ optional_param = tool_parameters.get("optional_param") # Returns None if not present
+
+ # Method 2: Use try-except (handle multiple optional parameters)
+ try:
+ name = tool_parameters["name"]
+ issuer_name = tool_parameters["issuer_name"]
+ except KeyError:
+ name = None
+ issuer_name = None
+ ```
+
+ - This try-except approach is a temporary solution for handling multiple optional parameters
+ - Always validate the existence and validity of parameters before using them
+
+3. **Output Methods**:
+ - Use `yield` to return various types of messages
+ - Support text, JSON, links, and variable outputs
+
+```python
+from collections.abc import Generator
+from typing import Any
+
+from dify_plugin import Tool
+from dify_plugin.entities.tool import ToolInvokeMessage
+
+# Import tool functions, ensure function names are spelled correctly
+from utils.helpers import process_data
+
+class FeatureTool(Tool):
+ def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage, None, None]:
+ try:
+ # 1. Get required parameters
+ param = tool_parameters.get("param_name", "")
+
+ # 2. Get optional parameters - using try-except approach
+ try:
+ optional_param1 = tool_parameters["optional_param1"]
+ optional_param2 = tool_parameters["optional_param2"]
+ except KeyError:
+ optional_param1 = None
+ optional_param2 = None
+
+ # Another approach for optional parameters - using .get() method
+ another_optional = tool_parameters.get("another_optional") # Returns None if not present
+
+ # 3. Validate required parameters
+ if not param:
+ yield self.create_text_message("Parameter is required.")
+ return
+
+ # 4. Implement business logic
+ result = self._process_data(param, optional_param1, optional_param2)
+
+ # 5. Return results
+ # Text output
+ yield self.create_text_message(f"Processed result: {result}")
+ # JSON output
+ yield self.create_json_message({"result": result})
+ # Variable output (for workflows)
+ yield self.create_variable_message("result_var", result)
+
+ except Exception as e:
+ # Error handling
+ yield self.create_text_message(f"Error: {str(e)}")
+
+ def _process_data(self, param: str, opt1=None, opt2=None) -> str:
+ """
+ Implement specific business logic
+
+ Args:
+ param: Required parameter
+ opt1: Optional parameter 1
+ opt2: Optional parameter 2
+
+ Returns:
+ Processing result
+ """
+ # Execute different logic based on whether parameters exist
+ if opt1 and opt2:
+ return f"Processed with all options: {param}, {opt1}, {opt2}"
+ elif opt1:
+ return f"Processed with option 1: {param}, {opt1}"
+ elif opt2:
+ return f"Processed with option 2: {param}, {opt2}"
+ else:
+ return f"Processed basic: {param}"
+```
+
+### utils/helper.py
+
+Helper functions implementing reusable functionality logic:
+
+1. **Function Separation**:
+
+ - Extract common functionality into separate functions
+ - Focus on single responsibility
+ - Pay attention to function naming consistency (avoid import errors)
+
+2. **Error Handling**:
+ - Include appropriate exception handling
+ - Use specific exception types
+ - Provide meaningful error messages
+
+```python
+import requests
+from typing import Dict, Any, Optional
+
+def call_external_api(endpoint: str, params: Dict[str, Any], api_key: str) -> Dict[str, Any]:
+ """
+ General function for calling external APIs
+
+ Args:
+ endpoint: API endpoint URL
+ params: Request parameters
+ api_key: API key
+
+ Returns:
+ JSON data from API response
+
+ Raises:
+ Exception: If API call fails
+ """
+ headers = {
+ "Authorization": f"Bearer {api_key}",
+ "Content-Type": "application/json"
+ }
+
+ try:
+ response = requests.get(endpoint, params=params, headers=headers, timeout=10)
+ response.raise_for_status() # Throw exception if status code is not 200
+ return response.json()
+ except requests.RequestException as e:
+ raise Exception(f"API call failed: {str(e)}")
+```
+
+### requirements.txt
+
+Dependency list, specifying the Python libraries needed by the plugin:
+
+1. **Version Specifications**:
+
+ - Use `~=` to specify dependency version ranges
+ - Avoid overly loose version requirements
+
+2. **Necessary Dependencies**:
+ - Must include `dify_plugin`
+ - Add all third-party libraries needed for plugin functionality
+
+```
+dify_plugin~=0.0.1b76
+requests~=2.31.0
+# Other dependencies...
+```
+
+## Tool Development Best Practices
+
+### 1. Parameter Handling Patterns
+
+1. **Required Parameter Handling**:
+
+ - Use the `.get()` method and provide default values: `param = tool_parameters.get("param_name", "")`
+ - Validate parameter validity: `if not param: yield self.create_text_message("Error: Required parameter missing.")`
+
+2. **Optional Parameter Handling**:
+
+ - **Single Optional Parameter**: Use the `.get()` method, allowing it to return None: `optional = tool_parameters.get("optional_param")`
+ - **Multiple Optional Parameters**: Use try-except pattern to handle KeyError:
+ ```python
+ try:
+ param1 = tool_parameters["optional_param1"]
+ param2 = tool_parameters["optional_param2"]
+ except KeyError:
+ param1 = None
+ param2 = None
+ ```
+ - This try-except approach is a temporary solution for handling multiple optional parameters
+
+3. **Parameter Validation**:
+ - Validate required parameters: `if not required_param: return error_message`
+ - Handle optional parameters conditionally: `if optional_param: do_something()`
+
+### 2. Secure File Operation Methods
+
+1. **Avoid Local File Reading/Writing**:
+
+ - Dify plugins run in serverless environments (like AWS Lambda), where local file system operations may be unreliable
+ - Don't use `open()`, `read()`, `write()`, or other direct file operations
+ - Don't rely on local files for state storage
+
+2. **Use Memory or APIs Instead**:
+ - Store temporary data in memory variables
+ - Use Dify's provided KV storage API for persistent data
+ - For large amounts of data, consider using databases or cloud storage services
+
+### 3. Copy Existing Files Rather Than Creating From Scratch
+
+For cases where you're uncertain about the correct structure, strongly recommend using the following method:
+
+```bash
+# Copy tool YAML file as a template
+cp tools/existing_tool.yaml tools/new_tool.yaml
+
+# Copy tool Python implementation
+cp tools/existing_tool.py tools/new_tool.py
+
+# Similarly applies to provider files
+cp provider/existing.yaml provider/new.yaml
+```
+
+This ensures that file structures and formats comply with the requirements of the Dify plugin framework, then make targeted modifications.
+
+### 4. Split Tool Functionality
+
+Split complex functionality into multiple simple tools, with each tool focusing on a single function:
+
+```
+tools/
+├── search.py # Search functionality
+├── search.yaml
+├── create.py # Create functionality
+├── create.yaml
+├── update.py # Update functionality
+├── update.yaml
+├── delete.py # Delete functionality
+└── delete.yaml
+```
+
+### 2. Parameter Design Principles
+
+- **Necessity**: Only require necessary parameters, provide reasonable default values
+- **Type Definition**: Choose appropriate parameter types (string/number/boolean/file)
+- **Clear Description**: Provide clear parameter descriptions for humans and AI
+- **Form Definition**: Correctly distinguish between llm (AI extraction) and form (UI configuration) parameters
+
+### 3. Error Handling
+
+```python
+try:
+ # Try to perform operation
+ result = some_operation()
+ yield self.create_text_message("Operation successful")
+except ValueError as e:
+ # Parameter error
+ yield self.create_text_message(f"Parameter error: {str(e)}")
+except requests.RequestException as e:
+ # API call error
+ yield self.create_text_message(f"API call failed: {str(e)}")
+except Exception as e:
+ # Other unexpected errors
+ yield self.create_text_message(f"An error occurred: {str(e)}")
+```
+
+### 4. Code Organization and Reuse
+
+Extract reusable logic to the utils directory:
+
+```python
+# In tool implementation
+from utils.api_client import ApiClient
+
+class SearchTool(Tool):
+ def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage, None, None]:
+ client = ApiClient(self.runtime.credentials["api_key"])
+ results = client.search(tool_parameters["query"])
+ yield self.create_json_message(results)
+```
+
+### 5. Output Formats
+
+Dify supports multiple output formats:
+
+```python
+# Text output
+yield self.create_text_message("This is a text message")
+
+# JSON output
+yield self.create_json_message({"key": "value"})
+
+# Link output
+yield self.create_link_message("https://example.com")
+
+# Variable output (for workflows)
+yield self.create_variable_message("variable_name", "variable_value")
+```
+
+## Common Errors and Solutions
+
+### Loading and Initialization Errors
+
+1. **Multiple Tool Subclasses Error**
+
+ ```
+ Exception: Multiple subclasses of Tool in /path/to/file.py
+ ```
+
+ - **Cause**: Multiple classes inheriting from Tool defined in the same Python file
+ - **Solution**:
+ - Check file contents: `cat tools/problematic_file.py`
+ - Keep one Tool subclass corresponding to the file name in each file
+ - Move other Tool subclasses to separate files
+
+2. **Import Error**
+
+ ```
+ ImportError: cannot import name 'x' from 'module'. Did you mean: 'y'?
+ ```
+
+ - **Cause**: Imported function name doesn't match actual definition
+ - **Solution**:
+ - Check function names in utils: `cat utils/the_module.py`
+ - Fix spelling errors in import statements
+ - Pay attention to underscores, case, etc. in function names
+
+3. **Credential Validation Failure**
+ ```
+ ToolProviderCredentialValidationError: Invalid API key
+ ```
+ - **Cause**: Credential validation logic failed
+ - **Solution**:
+ - Check `_validate_credentials` method implementation
+ - Ensure API key format is correct
+ - Add detailed error prompt information
+
+### Runtime Errors
+
+1. **Parameter Retrieval Error**
+
+ ```
+ KeyError: 'parameter_name'
+ ```
+
+ - **Cause**: Attempting to access a non-existent parameter
+ - **Solution**:
+ - Use `get()` instead of direct indexing: `param = tool_parameters.get("param_name", "")`
+ - Ensure parameter names match YAML definitions
+ - Add parameter existence checks
+
+2. **API Call Error**
+
+ ```
+ requests.exceptions.RequestException: Connection error
+ ```
+
+ - **Cause**: External API call failed
+ - **Solution**:
+ - Add timeout parameter: `timeout=10`
+ - Use `try/except` to catch exceptions
+ - Implement retry logic
+
+3. **Execution Timeout**
+ ```
+ TimeoutError: Function execution timed out
+ ```
+ - **Cause**: Operation takes too long
+ - **Solution**:
+ - Optimize API calls
+ - Break complex operations into multiple steps
+ - Set reasonable timeout limits
+
+### Configuration and Packaging Errors
+
+1. **YAML Format Error**
+
+ ```
+ yaml.YAMLError: mapping values are not allowed in this context
+ ```
+
+ - **Cause**: Incorrect YAML format
+ - **Solution**:
+ - Check indentation (use spaces, not tabs)
+ - Ensure colons are followed by spaces
+ - Use a YAML validator to check
+
+2. **Packaging Failure**
+ ```
+ Error: Failed to pack plugin
+ ```
+ - **Cause**: File structure or dependency issues
+ - **Solution**:
+ - Check manifest.yaml configuration
+ - Ensure all referenced files exist
+ - Review requirements.txt content
+
+## Code Example: TOTP Tool
+
+Here's a complete example of a TOTP (Time-based One-Time Password) plugin, demonstrating good code organization and best practices:
+
+### utils/totp_verify.py
+
+```python
+import pyotp
+import time
+
+def verify_totp(secret_key, totp_code, offset=5, strict=False):
+ """
+ Verify a Time-based One-Time Password (TOTP).
+
+ Args:
+ secret_key: The secret key or configuration URL used to generate the TOTP
+ totp_code: The dynamic token submitted by the user
+ offset: The number of seconds allowed for early or late verification
+ strict: Whether to use strict verification (only return success on exact match)
+
+ Returns:
+ A dictionary containing:
+ - 'status': 'success' or 'fail'
+ - 'detail': Internal message (not for end users)
+ """
+ try:
+ # Detect if it's a configuration URL
+ if secret_key.startswith('otpauth://'):
+ totp = pyotp.parse_uri(secret_key)
+ else:
+ totp = pyotp.TOTP(secret_key)
+
+ current_time = time.time()
+
+ # Exact time verification
+ if totp.verify(totp_code):
+ return {'status': 'success', 'detail': 'Token is valid'}
+
+ # Offset verification
+ early_valid = totp.verify(totp_code, for_time=current_time + offset)
+ late_valid = totp.verify(totp_code, for_time=current_time - offset)
+ off_time_valid = early_valid or late_valid
+
+ detail_message = (
+ f"Token is valid but not on time. "
+ f"{'Early' if early_valid else 'Late'} within {offset} seconds"
+ if off_time_valid else
+ "Token is invalid"
+ )
+
+ if strict:
+ return {'status': 'fail', 'detail': detail_message}
+ else:
+ return (
+ {'status': 'success', 'detail': detail_message}
+ if off_time_valid
+ else {'status': 'fail', 'detail': detail_message}
+ )
+ except Exception as e:
+ return {'status': 'fail', 'detail': f'Verification error: {str(e)}'}
+```
+
+### tools/totp.yaml
+
+```yaml
+identity:
+ name: totp
+ author: alterxyz
+ label:
+ en_US: TOTP Validator
+ zh_Hans: TOTP Validator
+description:
+ human:
+ en_US: Time-based one-time password (TOTP) validator
+ zh_Hans: Time-based one-time password (TOTP) validator
+ llm: Time-based one-time password (TOTP) validator, this tool is used to validate a 6 digit TOTP code with a secret key or provisioning URI.
+parameters:
+ - name: secret_key
+ type: string
+ required: true
+ label:
+ en_US: TOTP secret key or provisioning URI
+ zh_Hans: TOTP secret key or provisioning URI
+ human_description:
+ en_US: The secret key or provisioning URI used to generate the TOTP
+ zh_Hans: The secret key or provisioning URI used to generate the TOTP
+ llm_description: The secret key or provisioning URI (starting with 'otpauth://') used to generate the TOTP, this is highly sensitive and should be kept secret.
+ form: llm
+ - name: user_code
+ type: string
+ required: true
+ label:
+ en_US: 6 digit TOTP code to validate
+ zh_Hans: 6 digit TOTP code to validate
+ human_description:
+ en_US: 6 digit TOTP code to validate
+ zh_Hans: 6 digit TOTP code to validate
+ llm_description: 6 digit TOTP code to validate
+ form: llm
+extra:
+ python:
+ source: tools/totp.py
+output_schema:
+ type: object
+ properties:
+ True_or_False:
+ type: string
+ description: Whether the TOTP is valid or not, return in string format, "True" or "False".
+```
+
+### tools/totp.py
+
+```python
+from collections.abc import Generator
+from typing import Any
+
+# Correctly import tool functions
+from utils.totp_verify import verify_totp
+
+from dify_plugin import Tool
+from dify_plugin.entities.tool import ToolInvokeMessage
+
+# One file contains only one Tool subclass
+class TotpTool(Tool):
+ def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage, None, None]:
+ """Verify a Time-based One-Time Password (TOTP)"""
+ # Get parameters, use get() to avoid KeyError
+ secret_key = tool_parameters.get("secret_key")
+ totp_code = tool_parameters.get("user_code")
+
+ # Parameter validation
+ if not secret_key:
+ yield self.create_text_message("Error: Secret key is required.")
+ return
+ if not totp_code:
+ yield self.create_text_message("Error: TOTP code is required.")
+ return
+
+ try:
+ # Call tool function
+ result = verify_totp(secret_key, totp_code)
+
+ # Return results
+ yield self.create_json_message(result)
+
+ # Return different messages based on verification results
+ if result["status"] == "success":
+ yield self.create_text_message("Valid")
+ yield self.create_variable_message("True_or_False", "True")
+ else:
+ yield self.create_text_message("Invalid")
+ yield self.create_variable_message("True_or_False", "False")
+
+ except Exception as e:
+ # Error handling
+ yield self.create_text_message(f"Verification error: {str(e)}")
+```
+
+### tools/secret_generator.py
+
+```python
+from collections.abc import Generator
+from typing import Any
+
+import pyotp
+
+from dify_plugin import Tool
+from dify_plugin.entities.tool import ToolInvokeMessage
+
+# Note: One file contains only one Tool subclass
+class SecretGenerator(Tool):
+ def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage, None, None]:
+ """Generate TOTP secret key"""
+ try:
+ # Generate random secret key
+ secret_key = pyotp.random_base32()
+ yield self.create_text_message(secret_key)
+
+ # Safely get optional parameters
+ name = tool_parameters.get("name")
+ issuer_name = tool_parameters.get("issuer_name")
+
+ # Generate configuration URI if name or issuer is provided
+ if name or issuer_name:
+ provisioning_uri = pyotp.totp.TOTP(secret_key).provisioning_uri(
+ name=name,
+ issuer_name=issuer_name
+ )
+ yield self.create_variable_message("provisioning_uri", provisioning_uri)
+
+ except Exception as e:
+ yield self.create_text_message(f"Error generating secret: {str(e)}")
+```
+
+### requirements.txt
+
+```
+dify_plugin~=0.0.1b76
+pyotp~=2.9.0
+```
+
+This example demonstrates:
+
+- Clear functional separation (tool functions in utils, tool classes in tools)
+- Good error handling and parameter validation
+- One file containing only one Tool subclass
+- Detailed comments and docstrings
+- Well-designed YAML configuration
+
+## Status Synchronization Mechanism
+
+If the user's description differs from your recorded project status, or you need to confirm current progress, perform these operations:
+
+1. Check the project file structure
+2. Read key files
+3. Clearly inform the user: "I notice that the project status may differ from my previous understanding. I have rechecked the project files and updated my understanding."
+4. Describe the actual status you discovered
+5. Update the progress record in the working directory
+
+## First Launch Behavior
+
+When a user first activates you via "@ai" or similar means, you should:
+
+1. **Don't assume project goals**: Don't assume what type of plugin or functionality the user wants to develop
+2. **Don't start writing code**: Don't generate or modify code without clear instructions
+3. **Ask about user intent**: Politely ask what type of plugin the user hopes to develop and what problems they need help solving
+4. **Provide capability overview**: Briefly explain what types of help you can provide (code implementation, debugging, design suggestions, etc.)
+5. **Request project information**: Ask the user to share the current project status or file structure so you can provide more targeted help
+
+Only start providing specific development suggestions or code implementation after receiving clear instructions.
+
+Remember, your main goal is to assist users in efficiently completing Dify plugin development by continuously tracking status, providing professional advice, and solving technical challenges.
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ Help improve our documentation by contributing directly
+
+
+ Found an error or have suggestions? Let us know
+
+
diff --git a/plugin_dev_en/0211-getting-started-dify-tool.en.mdx b/plugin_dev_en/0211-getting-started-dify-tool.en.mdx
new file mode 100644
index 00000000..36699512
--- /dev/null
+++ b/plugin_dev_en/0211-getting-started-dify-tool.en.mdx
@@ -0,0 +1,567 @@
+---
+dimensions:
+ type:
+ primary: implementation
+ detail: basic
+ level: beginner
+standard_title: Getting Started Dify Tool
+language: en
+title: 'Dify Plugin Development: Hello World Guide'
+description: This document provides a detailed tutorial for Dify plugin development
+ from scratch, using the Telegraph publishing plugin as an example, covering environment
+ preparation, project initialization, virtual environment configuration, plugin core
+ logic development, local debugging, plugin metadata enhancement, and packaging for
+ release.
+---
+
+Welcome to the beginner's guide for Dify plugin development! This tutorial assumes you have basic programming knowledge and experience using the Dify platform. After completing this tutorial, you will master the fundamental process of creating a simple Dify plugin, taking you from a Dify user to a plugin contributor.
+
+We will demonstrate step by step how to develop a simple Telegraph publishing plugin. This tutorial primarily uses command line operations, with examples that use VSCode as the code editor. **We recommend first reading through the entire tutorial to understand the overall process, then following the steps to work hands-on.**
+
+> **Note for LLMs:** You can also use our built prompt to assist you in developing plugins: [Dify Plugin Development: Prompt](/plugin_dev_en/0211-getting-started-by-prompt.en)
+
+Before you begin, you can always refer to our [**Developer Cheatsheet**](/plugin_dev_en/0131-cheatsheet.en) for common commands and information, or consult the complete developer documentation when facing more complex issues.
+
+## 1. Preparing the Development Environment
+
+Before starting Dify plugin development, ensure you have the following tools ready in your environment:
+
+- **Dify Plugin Development Scaffold (CLI):** This is the core tool for developing, debugging, and packaging plugins, also known as `dify-plugin-daemon` or "Plugin Development SDK."
+- **Python Environment:** Python 3.12 or higher is required.
+
+### 1.1 Installing the Dify Plugin Development Scaffold (CLI)
+
+> For a more detailed guide on preparing the development environment, please refer to [Initializing Development Tools](/plugin_dev_en/0221-initialize-development-tools.en).
+
+1. **Download:** Visit the [Dify Plugin CLI Releases](https://github.com/langgenius/dify-plugin-daemon/releases) page. Download the latest version of the binary file corresponding to your operating system (Windows, macOS Intel/ARM, Linux).
+2. **Set Execute Permissions (macOS / Linux):**
+
+ - **The following steps use macOS (Apple Silicon / M series chips) as an example**, assuming the downloaded file is named `dify-plugin-darwin-arm64`. In the terminal, navigate to the directory containing the file and execute the following command to grant execution permissions:
+
+ ```bash
+ chmod +x dify-plugin-darwin-arm64
+ ```
+
+ - For Linux users, download the corresponding Linux version file and execute a similar command like `chmod +x `.
+ - For Windows users, after downloading the `.exe` file, you can typically run it directly.
+
+3. **Verify Installation:**
+
+ - In the terminal, execute the following command to check if the tool runs properly (replace `./dify-plugin-darwin-arm64` with the actual filename or path you downloaded):
+
+ ```bash
+ ./dify-plugin-darwin-arm64 version
+ ```
+
+ - If the terminal successfully outputs version information (e.g., `v0.0.1-beta.15`), then the installation is successful.
+
+> **Tips:**
+>
+> - **macOS Security Prompt:** If macOS initially prompts "Apple cannot verify" or "Cannot open," go to "System Settings" → "Privacy & Security" → "Security" section, find the related prompt and click "Open Anyway" or "Allow."
+> - **Simplify Command:** You can rename the downloaded binary file to a shorter name (e.g., `dify` or `dify-plugin`) for easier use. Example: `mv dify-plugin-darwin-arm64 dify`, then you can use `./dify version`.
+> - **Global Installation (Optional):** If you want to run the command from any path in your system (e.g., directly typing `dify` instead of `./dify`), you can move the renamed file to a directory included in your system's `PATH` environment variable, such as `/usr/local/bin` (macOS/Linux) or add it to Windows environment variables.
+> - For example (macOS/Linux): `sudo mv dify /usr/local/bin/`
+> - After configuration, typing `dify version` directly in the terminal should successfully output the version number.
+
+**For convenience, this article will use `./dify` as an example command for the Dify plugin development scaffold. Please replace it with your corresponding command based on your actual situation.**
+
+## 2. Initializing the Plugin Project
+
+Now, let's use the scaffold tool to create a new plugin project.
+
+1. Open a terminal and execute the initialization command:
+
+ ```bash
+ ./dify plugin init
+ ```
+
+2. Enter the basic information for the plugin according to the prompts:
+ - **Plugin name:** A unique identifier for the plugin. For example: `telegraph`
+ - _Constraints: 1-128 characters, can only contain lowercase letters, numbers, hyphens (-), and underscores (_)._
+ - **Author:** The identifier for the plugin author. For example: `alterxyz`
+ - _Constraints: 1-64 characters, can only contain lowercase letters, numbers, hyphens (-), and underscores (_)._
+ - **Description:** A brief description of the plugin's functionality. For example: `A Telegraph plugin that allows you to publish your content easily`
+3. **Select Development Language:** When prompted `Select language`, choose `python`.
+4. **Select Plugin Type:** When prompted `Select plugin type`, for this tutorial, choose `tool`.
+5. **Select Additional Features:** Next, you'll be prompted if you need to include Provider validation, persistent storage, and other additional features. For this simple Hello World plugin, we don't need these yet, so you can press **Enter** to skip all options until you see the success message.
+6. **Confirm Creation Success:** When the terminal outputs information similar to the following, it indicates that the plugin project has been successfully created:
+
+ ```bash
+ [INFO] plugin telegraph created successfully, you can refer to `telegraph/GUIDE.md` for more information about how to develop it
+ ```
+
+Now, a new folder named `telegraph` (or the plugin name you specified) should appear in your current directory, which is your plugin project.
+
+## 3. Configuring Python Virtual Environment and Dependencies
+
+To isolate project dependencies, we recommend using a Python virtual environment.
+
+### 3.1 Creating and Activating a Virtual Environment (Command Line Method)
+
+This is the **recommended and universal** method, not dependent on any specific IDE:
+
+1. **Navigate to the Project Directory:**
+
+ ```bash
+ cd telegraph
+ ```
+
+2. **Create a Virtual Environment:** (Recommended to name it `venv`)
+
+ ```bash
+ python -m venv venv
+ ```
+
+3. **Activate the Virtual Environment:**
+
+ - **macOS / Linux:**
+
+ ```bash
+ source venv/bin/activate
+ ```
+
+ - **Windows (cmd.exe):**
+
+ ```bash
+ venv\Scripts\activate.bat
+ ```
+
+ - **Windows (PowerShell):**
+
+ ```bash
+ venv\Scripts\Activate.ps1
+ ```
+
+ - After successful activation, your terminal prompt will typically display `(venv)` at the beginning.
+
+### 3.2 Installing Basic Dependencies
+
+The `requirements.txt` file generated during project initialization already includes the basic library `dify_plugin` needed for plugin development. After activating the virtual environment, execute the following command to install:
+
+```bash
+pip install -r requirements.txt
+```
+
+### 3.3 (Optional) VSCode Integrated Environment Configuration
+
+If you use VSCode as your code editor, you can leverage its integrated features to manage the Python environment:
+
+1. **Open the Project Folder:** Use VSCode to open the `telegraph` folder you just created.
+2. **Select Python Interpreter:**
+ - Open the command palette (macOS: `Cmd+Shift+P`, Windows/Linux: `Ctrl+Shift+P`).
+ - Type and select `Python: Select Interpreter`.
+ - In the pop-up list, select the Python interpreter in the virtual environment you just created (usually the path includes `.venv/bin/python` or `venv\Scripts\python.exe`). If the list doesn't automatically display it, you can select `Enter interpreter path...` to manually find it.
+ - _(Please refer to your local corresponding screenshot, which shows the interpreter selection interface)_
+3. **Install Dependencies (If VSCode Prompts):** VSCode may detect the `requirements.txt` file and prompt you to install its dependencies. If prompted, confirm the installation.
+ - _(Please refer to your local corresponding screenshot, which shows the interface for confirming dependency installation)_
+
+**Please ensure that all subsequent `pip install` commands and running `python -m main` operations are performed in the activated virtual environment.**
+
+## 4. Developing the Plugin Core Logic
+
+Now let's write the plugin code. This example will implement a simple tool for publishing specified content to Telegraph.
+
+### 4.1 Example Dependency Library: `your-telegraph`
+
+We will use a Python library called `your-telegraph` to interact with the Telegraph API. _(This is a hypothetical library name, please ensure that the library you actually use is valid)_.
+
+> `your-telegraph` is a Python wrapper that simplifies Telegraph API operations, allowing you to easily publish content with just a few lines of code.
+
+Its basic usage might be as follows:
+
+```python
+# Example code, not plugin code
+from ytelegraph import TelegraphAPI
+
+# Requires an access_token
+ph = TelegraphAPI(access_token="your_telegraph_access_token")
+
+# Create a page and get the link
+ph_link = ph.create_page_md("My First Page", "# Hello, Telegraph!\n\nThis is my first Telegraph page.")
+print(ph_link)
+```
+
+Our goal is to implement similar functionality in a Dify plugin.
+
+### 4.2 Adding and Configuring Project Dependencies
+
+1. **Install the Dependency Library:** Ensure your virtual environment is activated, then execute in the terminal:
+
+ ```bash
+ pip install your-telegraph
+ ```
+
+2. **Update `requirements.txt`:** Open the `requirements.txt` file in the `telegraph` project root directory, and add a line below `dify_plugin` with the name of the library you just installed:
+
+ ```plaintext
+ dify_plugin
+ your-telegraph
+ ```
+
+ This ensures that other developers or deployment environments can easily install all the required dependencies.
+
+### 4.3 Configuring Provider Credentials
+
+Our example requires a `telegraph_access_token`. We need to define this credential in the Provider configuration so that users can input it when using the plugin. For more information about Provider configuration, please refer to [General Specification Definitions](/plugin_dev_en/0411-general-specifications.en).
+
+1. **Edit the Provider YAML:** Open the `telegraph/provider/telegraph.yaml` file.
+2. **Add `credentials_for_provider`:** Add the following content at the end of the file (or at an appropriate location):
+
+ ```yaml
+ # ... (keep existing identity, name, label, description, icon, etc. unchanged) ...
+
+ credentials_for_provider:
+ telegraph_access_token: # This is the internal name of the credential, to be used in Python code
+ type: secret-input # Input type is a password field
+ required: true # This credential is required
+ label: # Label displayed in the Dify UI (supports multiple languages)
+ en_US: Telegraph Access Token
+ zh_Hans: Telegraph Access Token
+ # ... (other languages)
+ placeholder: # Placeholder text for the input field (supports multiple languages)
+ en_US: Enter your Telegraph access token
+ zh_Hans: Please enter your Telegraph access token
+ # ... (other languages)
+ help: # Help prompt information (supports multiple languages)
+ en_US: How to get your Telegraph access token
+ zh_Hans: How to get your Telegraph access token
+ # ... (other languages)
+ url: https://telegra.ph/api#createAccount # URL to jump to when clicking the help prompt
+ ```
+
+ - **Field Explanations:**
+ - `telegraph_access_token`: Unique identifier for the credential, accessed in code via `self.runtime.credentials["telegraph_access_token"]`.
+ - `type: secret-input`: Indicates that it will be displayed as a password input field in the Dify interface.
+ - `required: true`: Indicates that users must fill in this credential to use tools provided by this plugin.
+ - `label`, `placeholder`, `help`: Provide multilingual interface text.
+ - `url`: (Optional) Provides a help link for obtaining the credential.
+
+### 4.4 Implementing Tool Logic
+
+Now let's write the code that actually performs the publishing operation.
+
+1. **Edit the Tool Python File:** Open `telegraph/tools/telegraph.py`.
+2. **Implement the `_invoke` Method:** Replace the file contents with the following code:
+
+ ```python
+ from collections.abc import Generator
+ from typing import Any
+ from ytelegraph import TelegraphAPI # Import the library we're using
+
+ from dify_plugin import Tool
+ from dify_plugin.entities.tool import ToolInvokeMessage
+
+ class TelegraphTool(Tool):
+ """
+ A simple Telegraph publishing tool.
+ """
+
+ def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage, None, None]:
+ """
+ Create a new Telegraph page based on the input title and content.
+
+ Args:
+ tool_parameters: A dictionary containing the tool input parameters:
+ - p_title (str): The title of the Telegraph page.
+ - p_content (str): The content to publish in Markdown format.
+
+ Yields:
+ ToolInvokeMessage: A message containing the URL of the successfully created Telegraph page.
+
+ Raises:
+ Exception: If page creation fails, an exception with error information is thrown.
+ """
+ # 1. Get credentials from runtime
+ try:
+ access_token = self.runtime.credentials["telegraph_access_token"]
+ except KeyError:
+ raise Exception("Telegraph Access Token is not configured or invalid. Please provide it in the plugin settings.")
+
+ # 2. Get tool input parameters
+ title = tool_parameters.get("p_title", "Untitled") # Use .get to provide default value
+ content = tool_parameters.get("p_content", "")
+
+ if not content:
+ raise Exception("Publication content cannot be empty.")
+
+ # 3. Call the library to execute operations
+ try:
+ telegraph = TelegraphAPI(access_token) # Initialize Telegraph API
+ ph_link = telegraph.create_page_md(title, content) # Create page
+ except Exception as e:
+ # If the library call fails, throw an exception
+ raise Exception(f"Telegraph API call failed: {e}")
+
+ # 4. Return results
+ # Use create_link_message to generate an output message containing a link
+ yield self.create_link_message(ph_link)
+ ```
+
+ - **Key Points:**
+ - Get credentials from `self.runtime.credentials`.
+ - Get the tool's input parameters from `tool_parameters` (parameter names will be defined in YAML in the next step). Using `.get()` is a more robust approach.
+ - Call the `ytelegraph` library to perform the actual operation.
+ - Use `try...except` to catch possible errors and throw exceptions.
+ - Use `yield self.create_link_message(url)` to return a result containing a URL to Dify.
+
+### 4.5 Configuring Tool Parameters
+
+We need to tell Dify which input parameters this tool accepts.
+
+1. **Edit the Tool YAML File:** Open `telegraph/tools/telegraph.yaml`.
+2. **Define Parameters:** Replace or modify the file contents to:
+
+ ```yaml
+ identity:
+ name: telegraph_publisher # Unique internal name of the tool
+ author: alterxyz
+ label: # Tool name displayed in the Dify UI (multilingual)
+ en_US: Publish to Telegraph
+ zh_Hans: Publish to Telegraph
+ # ... (other languages)
+ description:
+ human: # Tool description for human users (multilingual)
+ en_US: Publish content to Telegraph as a new page.
+ zh_Hans: Publish content to Telegraph as a new page.
+ # ... (other languages)
+ llm: # Tool description for LLM (used in Agent mode)
+ A tool that takes a title and markdown content, then publishes it as a new page on Telegraph, returning the URL of the published page. Use this when the user wants to publish formatted text content publicly via Telegraph.
+ parameters: # Define the list of input parameters for the tool
+ - name: p_title # Internal name of the parameter, corresponding to the key in Python code
+ type: string # Parameter type
+ required: true # Whether required
+ label: # Parameter label displayed in the Dify UI (multilingual)
+ en_US: Post Title
+ zh_Hans: Post Title
+ human_description: # Parameter description for human users (multilingual)
+ en_US: The title for the Telegraph page.
+ zh_Hans: The title for the Telegraph page.
+ llm_description: # Parameter description for LLM (guiding Agent how to fill)
+ The title of the post. Should be a concise and meaningful plain text string.
+ form: llm # Parameter form type ('llm' or 'form')
+ - name: p_content
+ type: string
+ required: true
+ label:
+ en_US: Content (Markdown)
+ zh_Hans: Content (Markdown)
+ human_description:
+ en_US: The main content for the Telegraph page, written in Markdown format.
+ zh_Hans: The main content for the Telegraph page, written in Markdown format.
+ llm_description: # Emphasizing format requirements is important for LLM
+ The full content to be published on the Telegraph page. Must be provided in Markdown format. Ensure proper Markdown syntax for formatting like headings, lists, links, etc.
+ form: llm
+ extra: # Additional configuration
+ python:
+ source: tools/telegraph.py # Points to the Python file implementing the tool logic
+ ```
+
+ - **Field Explanations:**
+ - `identity`: Basic information about the tool, `name` is a unique identifier.
+ - `description`: Divided into `human` (for users) and `llm` (for Agent). **The `llm` description is crucial for the Agent to correctly understand and use the tool.**
+ - `parameters`: Defines each input parameter.
+ - `name`: Internal name, must match the key in the Python code's `tool_parameters.get("...")`.
+ - `type`: Data type (such as `string`, `number`, `boolean`, etc.).
+ - `required`: Whether it must be provided.
+ - `label`, `human_description`, `llm_description`: Similar to descriptions in `identity`, but for specific parameters. **`llm_description` should clearly guide the LLM on how to generate or obtain the parameter value, including format requirements (such as Markdown here).**
+ - `form`: Defines how the parameter is presented and filled in Dify. `llm` indicates that the parameter value can be input by the user, passed through variables, or determined by the LLM in Agent mode; `form` typically indicates a configuration item that needs to be fixed by the user in the interface. For tool inputs, `llm` is more common.
+ - `extra.python.source`: Specifies the path to the Python file implementing this tool's logic (relative to the project root directory).
+
+### 4.6 Implementing Provider Credential Validation (Optional but Recommended)
+
+To ensure that the credentials provided by users are valid, we should implement validation logic.
+
+1. **Edit the Provider Python File:** Open `telegraph/provider/telegraph.py`.
+2. **Implement the `_validate_credentials` Method:** Replace the file contents with:
+
+ ```python
+ from typing import Any
+ from dify_plugin import ToolProvider
+ from dify_plugin.errors.tool import ToolProviderCredentialValidationError
+
+ class TelegraphProvider(ToolProvider):
+ def _validate_credentials(self, credentials: dict[str, Any]) -> None:
+ """
+ Verify that the provided Telegraph Access Token is valid.
+ Attempt to create a test page using the token.
+ If validation fails, a ToolProviderCredentialValidationError exception should be thrown.
+ """
+ access_token = credentials.get("telegraph_access_token")
+ if not access_token:
+ raise ToolProviderCredentialValidationError("Telegraph Access Token cannot be empty.")
+
+ try:
+ # Try to perform a simple operation requiring credentials to validate
+ from ytelegraph import TelegraphAPI
+ ph = TelegraphAPI(access_token=access_token)
+ # Try to create a temporary, harmless page as a validation method
+ # Note: A better validation method might be to call read-only methods like 'getAccountInfo' (if available)
+ test_page = ph.create_page_md("Dify Validation Test", "This is a test page created by Dify plugin validation.")
+ # If needed, consider immediately editing or deleting this test page, but this would add complexity
+ # print(f"Validation successful. Test page created: {test_page}")
+ except Exception as e:
+ # If the API call fails, the credentials are likely invalid
+ raise ToolProviderCredentialValidationError(f"Telegraph credential validation failed: {e}")
+
+ ```
+
+ - **Key Points:**
+ - Get the credentials from the `credentials` dictionary.
+ - Perform an API call that requires the credential (preferably a read-only operation like getting account information; if not available, creating a harmless test page is also acceptable, but be aware of potential side effects).
+ - If the API call succeeds, don't throw an exception, indicating validation passed.
+ - If the API call fails, catch the exception and throw a `ToolProviderCredentialValidationError`, including the original error message.
+
+## 5. Local Running and Debugging
+
+Now you can run the plugin locally and debug it in Dify.
+
+1. **Prepare the `.env` File:**
+
+ - Make sure you're still in the `telegraph` project directory.
+ - Copy the environment variable template file:
+
+ ```bash
+ cp .env.example .env
+ ```
+
+ - **Edit the `.env` File:** Open the `.env` file you just created and fill in your Dify environment information:
+
+ ```dotenv
+ DIFY_API_HOST=https://your-dify-host.com # Replace with your Dify instance address (e.g., https://cloud.dify.ai)
+ DIFY_API_KEY=your-api-key # Replace with your Dify API key
+ ```
+
+ - **Get Host and Key:** Log in to your Dify environment, click the "Plugins" icon in the top right corner, then click the debug icon (or something that looks like a bug). In the pop-up window, copy the "API Key" and "Host Address". _(Please refer to your local corresponding screenshot, which shows the interface for obtaining the key and host address)_
+
+2. **Start the Local Plugin Service:**
+
+ - Ensure your Python virtual environment is activated.
+ - In the `telegraph` directory, run the main program:
+
+ ```bash
+ python -m main
+ ```
+
+ - **Observe Terminal Output:** If everything is normal, you should see log information similar to the following, indicating that the plugin tool has been successfully loaded and connected to Dify:
+
+ ```json
+ {"event": "log", "data": {"level": "INFO", "message": "Installed tool: telegraph_publisher", "timestamp": 1678886400.123456}}
+ {"event": "log", "data": {"level": "INFO", "message": "Plugin daemon started, waiting for requests...", "timestamp": 1678886400.123457}}
+ ```
+
+3. **View and Test in Dify:**
+
+ - **Refresh the Dify Page:** Return to your Dify environment (in the browser), refresh the plugin management page (typically `https://your-dify-host.com/plugins`).
+ - **Find the Plugin:** You should see a plugin named "Telegraph" (or the `label` you defined in the Provider YAML) in the list, possibly with a "Debugging" mark.
+ - **Add Credentials:** Click on the plugin, and you'll be prompted to enter the "Telegraph Access Token" you defined earlier in `provider/telegraph.yaml`. Enter a valid token and save. If your validation logic (`_validate_credentials`) is implemented correctly, validation will be performed here. _(Please refer to your local corresponding screenshot, which shows the plugin appearing in the list and requesting authorization)_
+ - **Use in Applications:** Now, you can add this tool node in Dify applications (such as Chatbot or Workflow) and try to call it! When you run the application and trigger the tool, the request will be forwarded to your local `python -m main` process for processing. You can see related log output in your local terminal for debugging.
+
+4. **Stop the Local Service:** Press `Ctrl + C` in the terminal to stop the local plugin service.
+
+This run -> test -> stop -> modify code -> run again cycle is the main workflow for plugin development.
+
+## 6. Enhancing Plugin Metadata
+
+To make the plugin more professional, discoverable, and understandable, we need to enhance some display information.
+
+1. **Icon:**
+ - Place an icon file representing your plugin in the `telegraph/_assets` directory (e.g., `icon.png`, `icon.svg`). Square, clear images are recommended.
+2. **Provider Information (`provider/telegraph.yaml`):**
+
+ - Ensure that the `label` (display name), `description` (function description), and `icon` (icon filename, such as `icon.png`) in the `identity` section are filled in and support multiple languages. This information is primarily displayed to users who _use_ the plugin in the Dify application orchestration interface.
+
+ ```yaml
+ identity:
+ author: alterxyz
+ name: telegraph # Internal name, keep unchanged
+ label:
+ en_US: Telegraph
+ zh_Hans: Telegraph Publish Article
+ description:
+ en_US: A Telegraph plugin that allow you publish your content easily
+ zh_Hans: A Telegraph plugin that allows you to publish your content easily
+ icon: icon.png # Reference to the icon filename in the _assets directory
+ ```
+
+3. **Plugin Manifest (`manifest.yaml`):**
+
+ - Edit the `manifest.yaml` file in the project root directory. This is the "ID card" for the entire plugin, and its information will be displayed on the **plugin management page** and **Plugin Marketplace** in Dify.
+ - Be sure to update or confirm the following fields:
+ - `label`: The **main display name** of the plugin (supports multiple languages).
+ - `description`: An **overall introduction** to the plugin's functionality (supports multiple languages), which should clearly summarize its core value. Note that the marketplace display may have length limitations.
+ - `icon`: The **main icon** of the plugin (directly enter the icon filename in the `_assets` directory, such as `icon.png`).
+ - `tags`: Add category tags to the plugin, which helps users filter in the marketplace. For optional values, please refer to Dify's enumerations or documentation (such as `media`, `tools`, `data-processing`, etc.). You can refer to the [ToolLabelEnum definition](https://github.com/langgenius/dify-plugin-sdks/blob/main/python/dify_plugin/entities/tool.py).
+
+ ```yaml
+ label:
+ en_US: Telegraph Publisher
+ zh_Hans: Telegraph Publishing Assistant
+ description:
+ en_US: Easily publish content to Telegraph pages directly from your Dify applications. Supports Markdown formatting.
+ zh_Hans: Easily publish content to Telegraph pages directly from your Dify applications. Supports Markdown formatting.
+ icon: icon.png
+ tags: ['media', 'content-creation'] # Example tags
+ # ... (Keep other fields like author, name unchanged)
+ ```
+
+4. **README and Privacy Policy:**
+ - `README.md`: Edit the `README.md` file in the project root directory. It will serve as the detailed introduction page for the plugin on the **Marketplace**, and should include richer information such as detailed functionality, usage examples, configuration guide, frequently asked questions, etc. You can refer to the style of the [AWS plugin marketplace page](https://marketplace.dify.ai/plugins/langgenius/aws_tools).
+ - `PRIVACY.md`: If you plan to publish the plugin to the official Marketplace, you need to provide a privacy policy document `PRIVACY.md`, describing how the plugin handles data.
+
+## 7. Packaging the Plugin
+
+When plugin development is complete and passes local testing, you can package it into a `.difypkg` file for distribution or installation. For detailed information about plugin packaging and publishing, please refer to [Publishing Overview](/plugin_dev_en/0321-release-overview.en).
+
+1. **Return to Parent Directory:** Ensure your terminal's current path is at **one level above** the `telegraph` folder.
+
+ ```bash
+ cd ..
+ ```
+
+2. **Execute the Packaging Command:**
+
+ ```bash
+ ./dify plugin package ./telegraph
+ ```
+
+ (Replace `./telegraph` with the actual path to your plugin project)
+
+3. **Get the Package File:** After successful execution, a file named `telegraph.difypkg` (or `your_plugin_name.difypkg`) will be generated in the current directory.
+
+This `.difypkg` file is a complete plugin package. You can:
+
+- **Upload and install** it manually on the Dify plugin management page.
+- **Share** it with others to install.
+- According to Dify's specifications and processes, **publish it to the official Plugin Marketplace**, allowing all Dify users to discover and use your plugin. For specific publishing procedures, please refer to [Publish to Dify Marketplace](/plugin_dev_en/0322-release-to-dify-marketplace.en).
+
+Congratulations! You have completed the entire process of developing, debugging, enhancing, and packaging your first Dify plugin. Now you can explore more complex and powerful plugin features based on this foundation.
+
+## Next Learning Steps
+
+- [Remote Debugging Plugins](/plugin_dev_en/0411-remote-debug-a-plugin.en) - Learn more advanced plugin debugging techniques
+- [Persistent Storage](/plugin_dev_en/0411-persistent-storage-kv.en) - Learn how to use data storage in plugins
+- [Slack Bot Plugin Development Example](/plugin_dev_en/0432-develop-a-slack-bot-plugin.en) - View a more complex plugin development case
+- [Tool Plugin](/plugin_dev_en/0222-tool-plugin.en) - Explore advanced features of tool plugins
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ Help improve our documentation by contributing directly
+
+
+ Found an error or have suggestions? Let us know
+
+
diff --git a/plugin_dev_en/0211-getting-started-new-model.en.mdx b/plugin_dev_en/0211-getting-started-new-model.en.mdx
new file mode 100644
index 00000000..2f983f68
--- /dev/null
+++ b/plugin_dev_en/0211-getting-started-new-model.en.mdx
@@ -0,0 +1,141 @@
+---
+dimensions:
+ type:
+ primary: implementation
+ detail: basic
+ level: beginner
+standard_title: Getting Started New Model
+language: en
+title: Quick Integration of a New Model
+description: This document guides non-professional developers on how to add new models
+ to Dify, focusing on adding new model versions to existing model providers by modifying
+ configuration files. Includes the complete process of forking the repository, copying
+ and modifying model configurations, updating provider version, local testing, and
+ submitting contributions.
+---
+
+Welcome to the world of Dify plugin development! Dify's powerful functionality depends on the collective efforts of community contributors. We believe that even if you're not a professional programmer, as long as you're passionate about AI technology and willing to research information, you can contribute to Dify—for example, by helping Dify support more and newer AI models.
+
+This article will guide you, in the simplest way possible, through completing the most common and straightforward contribution: adding a **new model version** to a model provider that Dify **already supports**. This approach typically **only requires modifying configuration files**, without writing code, making it perfect for your first contribution!
+
+> **Related Concepts**: Before starting, we recommend reading the [Model Plugin](/plugin_dev_en/0131-model-plugin-introduction.en) documentation to understand the basic concepts and structure of model plugins.
+
+**This quick integration method is suitable for:**
+
+- New models that belong to providers already supported by Dify plugins (such as OpenAI, Google Gemini, Anthropic Claude, etc.).
+- New models that use the same API authentication and base calling logic as other models in the same series.
+- Models that primarily differ in model ID, context length, maximum token count, pricing, and other configuration parameters.
+
+_(If the model you need to add requires new API logic or supports special functionality, it will involve Python code writing. Please refer to [Creating a New Model Provider](/plugin_dev_en/0222-creating-new-model-provider.en) for more detailed guidance.)_
+
+**Preparation:**
+
+- Familiarity with basic Git operations (Fork, Clone, Pull Request).
+- A GitHub account.
+- Installing and configuring the Dify plugin development toolkit (refer to [Initializing Development Tools](/plugin_dev_en/0221-initialize-development-tools.en)).
+
+**Steps:**
+
+1. **Fork & Clone the Official Plugin Repository:**
+
+ - Visit the Dify official plugin repository `https://github.com/langgenius/dify-official-plugins`.
+ - Click the "Fork" button to fork the repository to your own GitHub account.
+ - Use Git to clone your forked repository to your local computer.
+
+2. **Find and Copy the Model Configuration File:**
+
+ - In your local repository, navigate to the `models/` directory, find the provider folder for the model you want to add, for example `vertex_ai`.
+ - Enter the corresponding model type subdirectory for that provider, usually `models/llm/` (if it's a text generation model).
+ - In that directory, find a YAML configuration file for an existing model that is most similar to the new version you want to add (for example, `gemini-1.0-pro-001.yaml`).
+ - Copy this YAML file and rename it to clearly identify the new version (for example, `gemini-1.5-pro-latest.yaml`).
+
+3. **Modify the Model Configuration (YAML):**
+
+ - Open the YAML file you just renamed (e.g., `gemini-1.5-pro-latest.yaml`).
+ - **Core Step:** Refer to the **model provider's official documentation**, carefully verify and modify the following key information in the file:
+ - `model`: **Must** be updated to the official API identifier for the new version.
+ - `label`: **Must** be updated to the model name displayed to users in the Dify interface (recommend providing both `en_US` and `zh_Hans` languages).
+ - `model_properties`: Update `context_size` (context window size).
+ - `parameter_rules`: Check and update model parameter limitations, especially the `default`, `min`, and `max` values for `max_tokens` (maximum output token count).
+ - `pricing`: Update the model's `input` and `output` pricing, as well as the `unit` (typically `0.000001` representing per million tokens) and `currency`.
+ - _(Reference)_ For detailed specifications of all fields in the model YAML file, please consult [Model Design Rules](/plugin_dev_en/0411-model-designing-rules.en) and [Model Schema Definition](/plugin_dev_en/0412-model-schema.en).
+
+ **Example (Adding Gemini 1.5 Pro):**
+
+ | Parameter | Old Model (Example) | New Gemini 1.5 Pro (Example) | Notes |
+ | :---------------- | :------------------- | :--------------------------- | :----------------------------------- |
+ | `model` | `gemini-1.0-pro-001` | `gemini-1.5-pro-latest` | **Must** change to official model ID |
+ | `label: en_US` | Gemini 1.0 Pro | Gemini 1.5 Pro | **Must** change user-visible label |
+ | `context_size` | 30720 | 1048576 | **Must** change per official docs |
+ | `max_tokens` (below) | 2048 | 8192 | **Must** change default/max values |
+
+4. **Update the Provider Manifest Version:**
+
+ - Return to the root directory of the model provider (e.g., `models/vertex_ai/`).
+ - Find and open the `manifest.yaml` file.
+ - Increment the `version` field by a minor version number (e.g., `version: 0.0.8` -> `version: 0.0.9`). This tells Dify that this is an update.
+
+5. **Package and Local Testing:**
+
+ - Open your terminal (command line tool).
+ - **Make sure your current directory is the root of the `dify-official-plugins` repository** (the directory containing folders like `models`, `tools`, etc.).
+ - Run the packaging command:
+
+ ```bash
+ # Replace with the actual provider directory name, such as cohere or vertex_ai
+ dify plugin package models/
+ ```
+
+ - _After success, you'll see a prompt like `plugin packaged successfully, output path: .difypkg`, and a plugin package file named `.difypkg` will be generated in the current project root directory._
+ - Log in to your Dify instance (local deployment or cloud version).
+ - Click the **"Plugins"** menu item on the top right of the Dify navigation bar.
+ - On the plugins page, click the **"Install Plugin"** button.
+ - Select the **"Local Plugin"** tab.
+ - Click the upload area, select or drag and drop the `.difypkg` file you just generated locally.
+ - Wait for the plugin installation or update to complete.
+ - After successful installation, you typically need to go to "Settings" -> "Model Providers" to find the corresponding provider and configure your API credentials (if you haven't configured them before).
+ - Create a new Dify application or edit an existing one, in the "Prompt Orchestration" -> "Model" settings, try selecting your newly added model. Conduct some simple conversations or call tests to ensure it works properly and returns expected results.
+
+6. **Submit Your Contribution:**
+ - After verifying that local testing is working properly, commit your changes (the new model YAML file and updated `manifest.yaml`) via Git and push them to your forked GitHub repository.
+ - On GitHub, initiate a Pull Request (PR) to the main `langgenius/dify-official-plugins` repository. In the PR description, briefly mention which model you added and include a link to the model's official documentation to facilitate reviewer verification of parameters.
+
+---
+
+**What's Next?**
+
+Once your PR is reviewed, approved, and merged, your contribution becomes part of the official Dify plugins, and all Dify users can easily use this new model!
+
+This quick integration method is the fastest way to make Dify support new models. Of course, if this model needs to support more complex features in the future (such as image input, function calling, etc.), then experienced developers may need to update the plugin at the code level. But the step you've completed now is already a very valuable contribution!
+
+**Explore More:**
+
+- [Model Schema Definition](/plugin_dev_en/0412-model-schema.en) (Learn detailed rules for model YAML files)
+- [Model Design Rules](/plugin_dev_en/0411-model-designing-rules.en) (Learn specifications for model parameter design)
+- [General Specifications](/plugin_dev_en/0411-general-specifications.en) (Understand the role of `manifest.yaml`)
+- [Creating a New Model Provider](/plugin_dev_en/0222-creating-new-model-provider.en) (Learn how to add a new model provider)
+- [Publish to Dify Marketplace](/plugin_dev_en/0322-release-to-dify-marketplace.en) (Learn how to publish your plugin)
+- [Dify Official Plugin Repository](https://github.com/langgenius/dify-official-plugins) (View examples of other plugins)
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ Help improve our documentation by contributing directly
+
+
+ Found an error or have suggestions? Let us know
+
+
diff --git a/plugin_dev_en/0221-initialize-development-tools.en.mdx b/plugin_dev_en/0221-initialize-development-tools.en.mdx
new file mode 100644
index 00000000..f59df7ef
--- /dev/null
+++ b/plugin_dev_en/0221-initialize-development-tools.en.mdx
@@ -0,0 +1,98 @@
+---
+dimensions:
+ type:
+ primary: implementation
+ detail: standard
+ level: beginner
+standard_title: Initialize Development Tools
+language: en
+title: Initializing Development Tools
+description: This document details the necessary preparations before developing Dify
+ plugins, including complete steps for installing the Dify plugin scaffold tool (dify-plugin-daemon)
+ and configuring a Python environment (version requirement ≥3.12). The document also
+ provides reference links for developing various types of plugins.
+---
+
+Before start to develop Dify plugins please prepare the following prerequisites:
+
+* Dify plugin scaffolding tool
+* Python environment, version ≥ 3.12
+
+> The Dify plugin development scaffolding tool, also known as `dify-plugin-daemon`, can be regarded as a plugin development SDK.
+
+### **1. Installing the Dify Plugin Development Scaffolding Tool**
+
+Visit the [Dify plugin GitHub page](https://github.com/langgenius/dify-plugin-daemon/releases) and select and download the version suitable for your operating system.
+
+Using **macOS with M-series chips** as an example: Download the `dify-plugin-darwin-arm64` file from the project address mentioned above. Then, in the terminal, navigate to the file's location and grant it execution permissions:
+
+```
+chmod +x dify-plugin-darwin-arm64
+```
+
+Run the following command to verify successful installation.
+
+```
+./dify-plugin-darwin-arm64 version
+```
+
+> If the system shows an "Apple cannot verify" error, go to **Settings → Privacy & Security → Security**, and click the "Open Anyway" button.
+
+After running the command, the installation is successful, if the terminal returns version information like `v0.0.1-beta.15`.
+
+
+**Tips:**
+
+If you want to use the `dify` command globally in your system to run the scaffolding tool, it's recommended to rename the binary file to `dify` and copy it to the `/usr/local/bin` system path.
+
+After configuration, entering the `dify version` command in the terminal will output the version number.
+
+
+
+
+### **2. Initialize Python Environment**
+
+For detailed instructions, please refer to the [Python installation](https://pythontest.com/python/installing-python-3-11/) tutorial. Python version 3.12 or higher is required.
+
+### 3. **Develop plugins**
+
+Please refer to the following content for examples of different types of plugin development:
+
+- [Tool Plugin Development Guide](/plugin_dev_en/0211-getting-started-dify-tool.en) - Hello World introductory tutorial
+- [Model Plugin Development Guide](/plugin_dev_en/0211-getting-started-new-model.en) - Quickly integrate a new model
+- [Agent Strategy Plugin Development Guide](/plugin_dev_en/9433-agent-strategy-plugin.en) - Create custom reasoning strategies
+- [Extension Plugin Development Guide](/plugin_dev_en/9231-extension-plugin.en) - Implement external service integration via Webhook
+- [Plugin Packaging and Publishing](/plugin_dev_en/0321-release-overview.en) - Publish your plugin
+
+## Next Learning Steps
+
+- [Dify Plugin Development: Hello World Guide](/plugin_dev_en/0211-getting-started-dify-tool.en) - Start your first plugin development
+- [Developer Cheatsheet](/plugin_dev_en/0131-cheatsheet.en) - Learn common commands and concepts
+- [General Specification Definitions](/plugin_dev_en/0411-general-specifications.en) - Learn about plugin metadata configuration
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ Help improve our documentation by contributing directly
+
+
+ Found an error or have suggestions? Let us know
+
+
diff --git a/plugin_dev_en/0222-creating-new-model-provider-extra.en.mdx b/plugin_dev_en/0222-creating-new-model-provider-extra.en.mdx
new file mode 100644
index 00000000..d2199cc4
--- /dev/null
+++ b/plugin_dev_en/0222-creating-new-model-provider-extra.en.mdx
@@ -0,0 +1,301 @@
+---
+dimensions:
+ type:
+ primary: implementation
+ detail: standard
+ level: intermediate
+standard_title: Creating New Model Provider Extra
+language: en
+title: Implementing Standard Model Integration
+description: This document is aimed at developers who need to write Python code to
+ add or enhance Dify model support, providing detailed guidance on creating directory
+ structures, writing model configurations, implementing model calling logic, and
+ the complete process of debugging and publishing plugins, including details of core
+ method implementation and error handling.
+---
+
+This document is a standard guide for developers who need to write Python code to add or enhance model support for Dify. You'll need to follow the steps in this guide when the model you're adding involves new API call logic, special parameter handling, or new features that Dify needs to explicitly support (such as Vision, Tool Calling).
+
+**Before reading this document, it's recommended that you:**
+
+* Have basic Python programming skills and a basic understanding of object-oriented programming.
+* Be familiar with the API documentation and authentication methods provided by the model provider you want to integrate.
+* Have installed and configured the Dify plugin development toolkit (refer to [Initializing Development Tools](/plugin_dev_en/0221-initialize-development-tools.en)).
+* (Optional) Read the [Model Plugin Introduction](/plugin_dev_en/0131-model-plugin-introduction.en) document to understand the basic concepts and architecture of model plugins.
+
+This guide will walk you through the complete process of creating a directory structure, writing model configurations (YAML), implementing model calling logic (Python), and debugging and publishing plugins.
+
+---
+
+## Step 1: Create Directory Structure
+
+A well-organized directory structure is the foundation for developing maintainable plugins. You need to create specific directories and files for your model provider plugin.
+
+1. **Locate or Create Provider Directory:** In the `models/` directory of your plugin project (typically a local clone of `dify-official-plugins`), find or create a folder named after the model provider (e.g., `models/my_new_provider`).
+2. **Create `models` Subdirectory:** In the provider directory, create a `models` subdirectory.
+3. **Create Subdirectories by Model Type:** In the `models/models/` directory, create a subdirectory for **each model type** you need to support. Common types include:
+ * `llm`: Text generation models
+ * `text_embedding`: Text Embedding models
+ * `rerank`: Rerank models
+ * `speech2text`: Speech-to-text models
+ * `tts`: Text-to-speech models
+ * `moderation`: Content moderation models
+4. **Prepare Implementation Files:**
+ * In each model type directory (e.g., `models/models/llm/`), you need to create a Python file to implement the calling logic for that type of model (e.g., `llm.py`).
+ * Also in that directory, you need to create a YAML configuration file for each specific model of that type (e.g., `my-model-v1.yaml`).
+ * (Optional) You can create a `_position.yaml` file to control the display order of models of that type in the Dify UI.
+
+**Example Structure (assuming provider `my_provider` supports LLM and Embedding):**
+
+```bash
+models/my_provider/
+├── models # Model implementation and configuration directory
+│ ├── llm # LLM type
+│ │ ├── _position.yaml (Optional, controls sorting)
+│ │ ├── my-llm-model-v1.yaml
+│ │ ├── my-llm-model-v2.yaml
+│ │ └── llm.py # LLM implementation logic
+│ └── text_embedding # Embedding type
+│ ├── _position.yaml (Optional, controls sorting)
+│ ├── my-embedding-model.yaml
+│ └── text_embedding.py # Embedding implementation logic
+├── provider # Provider-level code directory
+│ └── my_provider.py (For credential validation, etc., refer to "Creating Model Provider" document)
+└── manifest.yaml # Plugin manifest file
+```
+
+---
+
+## Step 2: Define Model Configuration (YAML)
+
+For each specific model, you need to create a YAML file to describe its properties, parameters, and features so that Dify can correctly understand and use it.
+
+1. **Create YAML File:** In the corresponding model type directory (e.g., `models/models/llm/`), create a YAML file for the model you want to add. The filename typically matches or is descriptive of the model ID (e.g., `my-llm-model-v1.yaml`).
+2. **Write Configuration Content:** Follow the [AIModelEntity Schema Definition](/plugin_dev_en/0412-model-schema.en) specification to write the content. Key fields include:
+ * `model`: (Required) The official API identifier for the model.
+ * `label`: (Required) The name displayed in the Dify UI (supports multiple languages).
+ * `model_type`: (Required) Must match the directory type (e.g., `llm`).
+ * `features`: (Optional) Declare special features supported by the model (e.g., `vision`, `tool-call`, `stream-tool-call`, etc.).
+ * `model_properties`: (Required) Define inherent model properties, such as `mode` (`chat` or `completion`), `context_size`.
+ * `parameter_rules`: (Required) Define user-adjustable parameters and their rules (name `name`, type `type`, whether required `required`, default value `default`, range `min`/`max`, options `options`, etc.). You can use `use_template` to reference predefined templates to simplify configuration of common parameters (such as `temperature`, `max_tokens`).
+ * `pricing`: (Optional) Define billing information for the model.
+
+**Example (`claude-3-5-sonnet-20240620.yaml`):**
+
+```yaml
+model: claude-3-5-sonnet-20240620
+label:
+ en_US: claude-3-5-sonnet-20240620
+model_type: llm
+features:
+ - agent-thought
+ - vision
+ - tool-call
+ - stream-tool-call
+ - document
+model_properties:
+ mode: chat
+ context_size: 200000
+parameter_rules:
+ - name: temperature
+ use_template: temperature
+ - name: top_p
+ use_template: top_p
+ - name: max_tokens
+ use_template: max_tokens
+ required: true
+ default: 8192
+ min: 1
+ max: 8192 # Note that Dify may have limitations at its level
+pricing:
+ input: '3.00'
+ output: '15.00'
+ unit: '0.000001' # Per million tokens
+ currency: USD
+```
+
+---
+
+## Step 3: Write Model Calling Code (Python)
+
+This is the core step for implementing model functionality. You need to write code in the corresponding model type's Python file (e.g., `llm.py`) to handle API calls, parameter conversion, and result returns.
+
+1. **Create/Edit Python File:** Create or open the corresponding Python file (e.g., `llm.py`) in the model type directory (e.g., `models/models/llm/`).
+2. **Define Implementation Class:**
+ * Define a class, for example, `MyProviderLargeLanguageModel`.
+ * This class must inherit from the **model type base class** in the Dify plugin SDK. For example, for LLM, you need to inherit from `dify_plugin.provider_kits.llm.LargeLanguageModel`.
+
+ ```python
+ import logging
+ from typing import Union, Generator, Optional, List
+ from dify_plugin.provider_kits.llm import LargeLanguageModel # Import base class
+ from dify_plugin.provider_kits.llm import LLMResult, LLMResultChunk, LLMUsage # Import result and usage classes
+ from dify_plugin.provider_kits.llm import PromptMessage, PromptMessageTool # Import message and tool classes
+ from dify_plugin.errors.provider_error import InvokeError, InvokeAuthorizationError # Import error classes
+ # Assuming you have a vendor_sdk for calling the API
+ # import vendor_sdk
+
+ logger = logging.getLogger(__name__)
+
+ class MyProviderLargeLanguageModel(LargeLanguageModel):
+ # ... implement methods ...
+ ```
+
+3. **Implement Key Methods:** (The specific methods to implement depend on the base class inherited, using LLM as an example below)
+ * `_invoke(...)`: **Core calling method**.
+ * **Signature:** `def _invoke(self, model: str, credentials: dict, prompt_messages: List[PromptMessage], model_parameters: dict, tools: Optional[List[PromptMessageTool]] = None, stop: Optional[List[str]] = None, stream: bool = True, user: Optional[str] = None) -> Union[LLMResult, Generator[LLMResultChunk, None, None]]:`
+ * **Responsibilities:**
+ * Prepare API requests using `credentials` and `model_parameters`.
+ * Convert Dify's `prompt_messages` format to the format required by the provider API.
+ * Handle the `tools` parameter to support Function Calling / Tool Use (if the model supports it).
+ * Decide whether to make a streaming call or a synchronous call based on the `stream` parameter.
+ * **Streaming Return:** If `stream=True`, this method must return a generator (`Generator`) that uses `yield` to return `LLMResultChunk` objects piece by piece. Each chunk contains partial results (text, tool calling blocks, etc.) and optional usage information.
+ * **Synchronous Return:** If `stream=False`, this method must return a complete `LLMResult` object, containing the final text result, a complete list of tool calls, and total usage information (`LLMUsage`).
+ * **Implementation Pattern:** It is strongly recommended to split synchronous and streaming logic into internal helper methods.
+
+ ```python
+ def _invoke(self, ..., stream: bool = True, ...) -> Union[LLMResult, Generator[LLMResultChunk, None, None]]:
+ # Prepare API request parameters (authentication, model parameter conversion, message format conversion, etc.)
+ api_params = self._prepare_api_params(credentials, model_parameters, prompt_messages, tools, stop)
+
+ try:
+ if stream:
+ return self._invoke_stream(model, api_params, user)
+ else:
+ return self._invoke_sync(model, api_params, user)
+ except vendor_sdk.APIError as e:
+ # Handle API errors, map to Dify errors (reference _invoke_error_mapping)
+ # ... raise mapped_error ...
+ pass # Replace with actual error handling
+ except Exception as e:
+ logger.exception("Unknown error during model invocation")
+ raise e # Or raise a generic InvokeError
+
+ def _invoke_stream(self, model: str, api_params: dict, user: Optional[str]) -> Generator[LLMResultChunk, None, None]:
+ # Call the vendor_sdk's streaming interface
+ # for api_chunk in vendor_sdk.create_stream(...):
+ # # Convert api_chunk to LLMResultChunk
+ # dify_chunk = self._convert_api_chunk_to_llm_result_chunk(api_chunk)
+ # yield dify_chunk
+ pass # Replace with actual implementation
+
+ def _invoke_sync(self, model: str, api_params: dict, user: Optional[str]) -> LLMResult:
+ # Call the vendor_sdk's synchronous interface
+ # api_response = vendor_sdk.create_sync(...)
+ # Convert api_response to LLMResult (including message.content, tools, usage)
+ # dify_result = self._convert_api_response_to_llm_result(api_response)
+ # return dify_result
+ pass # Replace with actual implementation
+ ```
+
+ * `validate_credentials(self, model: str, credentials: dict) -> None`: (Required) Used to validate the validity of credentials when a user adds or modifies them. This is typically implemented by calling a simple, low-cost API endpoint (such as listing available models, checking balance, etc.). If validation fails, a `CredentialsValidateFailedError` or its subclass should be thrown.
+ * `get_num_tokens(self, model: str, credentials: dict, prompt_messages: List[PromptMessage], tools: Optional[List[PromptMessageTool]] = None) -> int`: (Optional but recommended) Used to estimate the number of tokens for a given input. If it cannot be calculated accurately or the API does not support it, you can return 0.
+ * `@property _invoke_error_mapping(self) -> dict[type[InvokeError], list[type[Exception]]]`: (Required) Define an **error mapping** dictionary. The keys are standard `InvokeError` subclasses from Dify, and the values are lists of exception types that may be thrown by the vendor SDK that need to be mapped to that standard error. This is crucial for Dify to handle errors from different providers uniformly.
+
+ ```python
+ @property
+ def _invoke_error_mapping(self) -> dict[type[InvokeError], list[type[Exception]]]:
+ # Example mapping
+ mapping = {
+ InvokeAuthorizationError: [
+ vendor_sdk.AuthenticationError,
+ vendor_sdk.PermissionDeniedError,
+ ],
+ InvokeRateLimitError: [
+ vendor_sdk.RateLimitError,
+ ],
+ # ... other mappings ...
+ }
+ # Can add default mappings from the base class here (if provided by the base class)
+ # base_mapping = super()._invoke_error_mapping
+ # mapping.update(base_mapping) # Note the merging strategy
+ return mapping
+ ```
+
+---
+
+## Step 4: Debug Plugin
+
+Thorough testing and debugging are essential before contributing your plugin to the community. Dify provides remote debugging capabilities, allowing you to modify code locally and test the effects in real-time in a Dify instance.
+
+1. **Get Debugging Information:**
+ * In your Dify instance, go to the "Plugin Management" page (may require administrator privileges).
+ * Click "Debug Plugin" in the top right corner of the page to get your `Debug Key` and `Remote Server Address` (e.g., `http://:5003`).
+2. **Configure Local Environment:**
+ * In the **root directory** of your local plugin project, find or create a `.env` file (can be copied from `.env.example`).
+ * Edit the `.env` file, filling in the debugging information:
+
+ ```dotenv
+ INSTALL_METHOD=remote
+ REMOTE_INSTALL_HOST= # Dify server address
+ REMOTE_INSTALL_PORT=5003 # Debug port
+ REMOTE_INSTALL_KEY=****-****-****-****-**** # Your Debug Key
+ ```
+
+3. **Start Local Plugin Service:**
+ * In the plugin project root directory, make sure your Python environment is activated (if using a virtual environment).
+ * Run the main program:
+
+ ```bash
+ python -m main
+ ```
+
+ * Observe the terminal output. If the connection is successful, there will typically be a corresponding log prompt.
+4. **Test in Dify:**
+ * Refresh the "Plugins" or "Model Providers" page in Dify, and you should see your local plugin instance, possibly with a "Debugging" identifier.
+ * Go to "Settings" -> "Model Providers", find your plugin, and configure valid API credentials.
+ * Select and use your model in a Dify application for testing. Your local modifications to the Python code (which will typically auto-reload the service after saving) will directly affect the calling behavior in Dify. Using Dify's debug preview feature can help you check input/output and error messages.
+
+---
+
+## Step 5: Package and Publish
+
+When you've completed development and debugging, and are satisfied with the plugin's functionality, you can package it and contribute it to the Dify community.
+
+1. **Package Plugin:**
+ * Stop the local debugging service (`Ctrl+C`).
+ * Run the packaging command in the plugin project **root directory**:
+
+ ```bash
+ # Replace with your provider directory name
+ dify plugin package models/
+ ```
+
+ * This will generate a `.difypkg` file in the project root directory.
+2. **Submit Pull Request:**
+ * Ensure your code style is good and follows Dify's [plugin publishing specifications](https://docs.dify.ai/plugins/publish-plugins/publish-to-dify-marketplace).
+ * Push your local Git commits to your forked `dify-official-plugins` repository.
+ * Initiate a Pull Request to the main `langgenius/dify-official-plugins` repository on GitHub. Clearly describe the changes you've made, the models or features you've added, and any necessary testing instructions in the PR description.
+ * Wait for review by the Dify team. After review and merging, your contribution will be included in the official plugins and available on the [Dify Marketplace](https://marketplace.dify.ai/).
+
+---
+
+## Explore More
+
+* [Model Schema Definition](/plugin_dev_en/0412-model-schema.en) (Model YAML specifications)
+* [Plugin Manifest Structure](/plugin_dev_en/0411-general-specifications.en) (`manifest.yaml` specifications)
+* [Dify Plugin SDK Reference](https://github.com/langgenius/dify-plugin-sdks) (Look up base classes, data structures, and error types)
+* [Dify Official Plugins Repository](https://github.com/langgenius/dify-official-plugins) (View implementations of existing plugins)
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ Help improve our documentation by contributing directly
+
+
+ Found an error or have suggestions? Let us know
+
+
diff --git a/plugin_dev_en/0222-creating-new-model-provider.en.mdx b/plugin_dev_en/0222-creating-new-model-provider.en.mdx
new file mode 100644
index 00000000..854d61cd
--- /dev/null
+++ b/plugin_dev_en/0222-creating-new-model-provider.en.mdx
@@ -0,0 +1,288 @@
+---
+dimensions:
+ type:
+ primary: implementation
+ detail: standard
+ level: intermediate
+standard_title: Creating New Model Provider
+language: en
+title: Creating Model Providers
+description: This document provides detailed guidance on creating model provider plugins,
+ including project initialization, choosing model configuration methods (predefined
+ models and custom models), creating provider configuration YAML files, and the complete
+ process of writing provider code.
+---
+
+The first step in creating a Model type plugin is to initialize the plugin project and create the model provider file, followed by integrating specific predefined/custom models. If you only want to add a new model to an existing model provider, please refer to [Quick Integration of a New Model](/plugin_dev_en/0211-getting-started-new-model.en).
+
+### Prerequisites
+
+* Dify plugin scaffolding tool
+* Python environment, version ≥ 3.12
+
+For detailed instructions on preparing the plugin development scaffolding tool, please refer to [Initializing Development Tools](/plugin_dev_en/0221-initialize-development-tools.en). Before you begin, it's recommended that you understand the basic concepts and structure of [Model Plugins](/plugin_dev_en/0131-model-plugin-introduction.en).
+
+### Create New Project
+
+In the scaffolding command-line tool path, create a new dify plugin project.
+
+```
+./dify-plugin-darwin-arm64 plugin init
+```
+
+If you have renamed the binary file to `dify` and copied it to the `/usr/local/bin` path, you can run the following command to create a new plugin project:
+
+```bash
+dify plugin init
+```
+
+### Choose Model Plugin Template
+
+All templates in the scaffolding tool provide complete code projects. Choose the `LLM` type plugin template.
+
+
+
+#### Configure Plugin Permissions
+
+Configure the following permissions for this LLM plugin:
+
+* Models
+* LLM
+* Storage
+
+
+
+#### Model Configuration Methods Explanation
+
+Model providers support the following two model configuration methods:
+
+* `predefined-model` **Predefined Models**
+
+ Common large model types that only require unified provider credentials to use the predefined models under the provider. For example, the `OpenAI` model provider offers a series of predefined models such as `gpt-3.5-turbo-0125` and `gpt-4o-2024-05-13`. For detailed development instructions, please refer to Integrating Predefined Models.
+* `customizable-model` **Custom Models**
+
+ Requires manually adding credential configurations for each model. For example, `Xinference` supports both LLM and Text Embedding, but each model has a unique **model\_uid**. If you want to integrate both, you need to configure a **model\_uid** for each model. For detailed development instructions, please refer to Integrating Custom Models.
+
+These two configuration methods **can coexist**, meaning a provider can support combinations like `predefined-model` + `customizable-model` or `predefined-model`. This means that with configured unified provider credentials, you can use predefined models and models fetched from remote sources, and if you add new models, you can additionally use custom models on top of this foundation.
+
+### Adding a New Model Provider
+
+Adding a new model provider mainly includes the following steps:
+
+1. **Create Model Provider Configuration YAML File**
+
+ Add a YAML file in the provider directory to describe the provider's basic information and parameter configuration. Write content according to ProviderSchema requirements to ensure consistency with system specifications.
+2. **Write Model Provider Code**
+
+ Create provider class code, implementing a Python class that meets system interface requirements for connecting with the provider's API and implementing core functionality.
+
+***
+
+Here are the complete operation details for each step.
+
+#### 1. **Create Model Provider Configuration File**
+
+Manifest is a YAML format file that declares the model provider's basic information, supported model types, configuration methods, and credential rules. The plugin project template will automatically generate configuration files under the `/providers` path.
+
+Here's an example of the `anthropic.yaml` configuration file for `Anthropic`:
+
+```yaml
+provider: anthropic
+label:
+ en_US: Anthropic
+description:
+ en_US: Anthropic's powerful models, such as Claude 3.
+ zh_Hans: Anthropic 的强大模型,例如 Claude 3。
+icon_small:
+ en_US: icon_s_en.svg
+icon_large:
+ en_US: icon_l_en.svg
+background: "#F0F0EB"
+help:
+ title:
+ en_US: Get your API Key from Anthropic
+ zh_Hans: 从 Anthropic 获取 API Key
+ url:
+ en_US: https://console.anthropic.com/account/keys
+supported_model_types:
+ - llm
+configurate_methods:
+ - predefined-model
+provider_credential_schema:
+ credential_form_schemas:
+ - variable: anthropic_api_key
+ label:
+ en_US: API Key
+ type: secret-input
+ required: true
+ placeholder:
+ zh_Hans: 在此输入您的 API Key
+ en_US: Enter your API Key
+ - variable: anthropic_api_url
+ label:
+ en_US: API URL
+ type: text-input
+ required: false
+ placeholder:
+ zh_Hans: 在此输入您的 API URL
+ en_US: Enter your API URL
+models:
+ llm:
+ predefined:
+ - "models/llm/*.yaml"
+ position: "models/llm/_position.yaml"
+extra:
+ python:
+ provider_source: provider/anthropic.py
+ model_sources:
+ - "models/llm/llm.py"
+```
+
+If the provider you're integrating offers custom models, such as `OpenAI` providing fine-tuned models, you need to add the `model_credential_schema` field.
+
+Here's a sample code for the `OpenAI` family of models:
+
+```yaml
+model_credential_schema:
+ model: # Fine-tuned model name
+ label:
+ en_US: Model Name
+ zh_Hans: 模型名称
+ placeholder:
+ en_US: Enter your model name
+ zh_Hans: 输入模型名称
+ credential_form_schemas:
+ - variable: openai_api_key
+ label:
+ en_US: API Key
+ type: secret-input
+ required: true
+ placeholder:
+ zh_Hans: 在此输入您的 API Key
+ en_US: Enter your API Key
+ - variable: openai_organization
+ label:
+ zh_Hans: 组织 ID
+ en_US: Organization
+ type: text-input
+ required: false
+ placeholder:
+ zh_Hans: 在此输入您的组织 ID
+ en_US: Enter your Organization ID
+ - variable: openai_api_base
+ label:
+ zh_Hans: API Base
+ en_US: API Base
+ type: text-input
+ required: false
+ placeholder:
+ zh_Hans: 在此输入您的 API Base
+ en_US: Enter your API Base
+```
+
+For more complete model provider YAML specifications, please refer to the [Model Schema](/plugin_dev_en/0412-model-schema.en) documentation.
+
+#### 2. **Write Model Provider Code**
+
+Create a python file with the same name, e.g., `anthropic.py`, in the `/providers` folder and implement a `class` that inherits from the `__base.provider.Provider` base class, e.g., `AnthropicProvider`.
+
+Here's an example code for `Anthropic`:
+
+```python
+import logging
+from dify_plugin.entities.model import ModelType
+from dify_plugin.errors.model import CredentialsValidateFailedError
+from dify_plugin import ModelProvider
+
+logger = logging.getLogger(__name__)
+
+
+class AnthropicProvider(ModelProvider):
+ def validate_provider_credentials(self, credentials: dict) -> None:
+ """
+ Validate provider credentials
+
+ if validate failed, raise exception
+
+ :param credentials: provider credentials, credentials form defined in `provider_credential_schema`.
+ """
+ try:
+ model_instance = self.get_model_instance(ModelType.LLM)
+ model_instance.validate_credentials(model="claude-3-opus-20240229", credentials=credentials)
+ except CredentialsValidateFailedError as ex:
+ raise ex
+ except Exception as ex:
+ logger.exception(f"{self.get_provider_schema().provider} credentials validate failed")
+ raise ex
+```
+
+Providers need to inherit the `__base.model_provider.ModelProvider` base class and implement the `validate_provider_credentials` method for validating unified provider credentials.
+
+```python
+def validate_provider_credentials(self, credentials: dict) -> None:
+ """
+ Validate provider credentials
+ You can choose any validate_credentials method of model type or implement validate method by yourself,
+ such as: get model list api
+
+ if validate failed, raise exception
+
+ :param credentials: provider credentials, credentials form defined in `provider_credential_schema`.
+ """
+```
+
+Of course, you can also initially reserve the `validate_provider_credentials` implementation, and reuse it directly after the model credential verification method is implemented.
+
+#### **Custom Model Providers**
+
+For other types of model providers, please refer to the following configuration methods.
+
+For custom model providers like `Xinference`, you can skip the full implementation step. Simply create an empty class called `XinferenceProvider` and implement an empty `validate_provider_credentials` method in it.
+
+**Detailed Explanation:**
+
+• `XinferenceProvider` is a placeholder class used to identify custom model providers.
+
+• While the `validate_provider_credentials` method won't be actually called, it must exist because its parent class is abstract and requires all child classes to implement this method. By providing an empty implementation, we can avoid instantiation errors that would occur from not implementing the abstract method.
+
+```python
+class XinferenceProvider(Provider):
+ def validate_provider_credentials(self, credentials: dict) -> None:
+ pass
+```
+
+After initializing the model provider, the next step is to integrate specific llm models provided by the provider. For detailed instructions, please refer to:
+
+* [Model Design Rules](/plugin_dev_en/0411-model-designing-rules.en) - Learn the standards for integrating predefined models
+* [Model Schema](/plugin_dev_en/0412-model-schema.en) - Learn the standards for integrating custom models
+* [Publishing Overview](/plugin_dev_en/0321-release-overview.en) - Learn the plugin publishing process
+
+## Reference Resources
+
+- [Quick Integration of a New Model](/plugin_dev_en/0211-getting-started-new-model.en) - How to add new models to existing providers
+- [Basic Concepts of Plugin Development](/plugin_dev_en/0111-getting-started-dify-plugin.en) - Return to the plugin development getting started guide
+- [Creating New Model Provider Extra](/plugin_dev_en/0222-creating-new-model-provider-extra.en) - Learn more advanced configurations
+- [General Specifications](/plugin_dev_en/0411-general-specifications.en) - Learn about plugin manifest file configuration
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ Help improve our documentation by contributing directly
+
+
+ Found an error or have suggestions? Let us know
+
+
diff --git a/plugin_dev_en/0222-tool-plugin.en.mdx b/plugin_dev_en/0222-tool-plugin.en.mdx
new file mode 100644
index 00000000..3d01f963
--- /dev/null
+++ b/plugin_dev_en/0222-tool-plugin.en.mdx
@@ -0,0 +1,403 @@
+---
+dimensions:
+ type:
+ primary: implementation
+ detail: standard
+ level: intermediate
+standard_title: Tool Plugin
+language: en
+title: Tool Plugin
+description: This document provides detailed instructions on how to develop tool plugins
+ for Dify, using Google Search as an example to demonstrate a complete tool plugin
+ development process. The content includes plugin initialization, template selection,
+ tool provider configuration file definition, adding third-party service credentials,
+ tool functionality code implementation, debugging, and packaging for release.
+---
+
+Tools refer to third-party services that can be called by Chatflow / Workflow / Agent-type applications, providing complete API implementation capabilities to enhance Dify applications. For example, adding extra features like online search, image generation, and more.
+
+
+
+In this article, **"Tool Plugin"** refers to a complete project that includes tool provider files, functional code, and other structures. A tool provider can include multiple Tools (which can be understood as additional features provided within a single tool), structured as follows:
+
+```
+- Tool Provider
+ - Tool A
+ - Tool B
+```
+
+
+
+This article will use `Google Search` as an example to demonstrate how to quickly develop a tool plugin.
+
+### Prerequisites
+
+- Dify plugin scaffolding tool
+- Python environment, version ≥ 3.12
+
+For detailed instructions on how to prepare the plugin development scaffolding tool, please refer to [Initializing Development Tools](/plugin_dev_en/0221-initialize-development-tools.en). If you are developing a plugin for the first time, it is recommended to read [Dify Plugin Development: Hello World Guide](/plugin_dev_en/0211-getting-started-dify-tool.en) first.
+
+### Creating a New Project
+
+Run the scaffolding command line tool to create a new Dify plugin project.
+
+```bash
+./dify-plugin-darwin-arm64 plugin init
+```
+
+If you have renamed the binary file to `dify` and copied it to the `/usr/local/bin` path, you can run the following command to create a new plugin project:
+
+```bash
+dify plugin init
+```
+
+> In the following text, `dify` will be used as a command line example. If you encounter any issues, please replace the `dify` command with the path to the command line tool.
+
+### Choosing Plugin Type and Template
+
+All templates in the scaffolding tool provide complete code projects. In this example, select the `Tool` plugin.
+
+> If you are already familiar with plugin development and do not need to rely on templates, you can refer to the [General Specifications](/plugin_dev_en/0411-general-specifications.en) guide to complete the development of different types of plugins.
+
+
+
+#### Configuring Plugin Permissions
+
+The plugin also needs permissions to read from the Dify platform. Grant the following permissions to this example plugin:
+
+- Tools
+- Apps
+- Enable persistent storage Storage, allocate default size storage
+- Allow registering Endpoints
+
+> Use the arrow keys in the terminal to select permissions, and use the "Tab" button to grant permissions.
+
+After checking all permission items, press Enter to complete the plugin creation. The system will automatically generate the plugin project code.
+
+
+
+### Developing the Tool Plugin
+
+#### 1. Creating the Tool Provider File
+
+The tool provider file is a yaml format file, which can be understood as the basic configuration entry for the tool plugin, used to provide necessary authorization information to the tool.
+
+Go to the `/provider` path in the plugin template project and rename the yaml file to `google.yaml`. This `yaml` file will contain information about the tool provider, including the provider's name, icon, author, and other details. This information will be displayed when installing the plugin.
+
+**Example Code**
+
+```yaml
+identity: # Basic information of the tool provider
+ author: Your-name # Author
+ name: google # Name, unique, cannot have the same name as other providers
+ label: # Label, for frontend display
+ en_US: Google # English label
+ zh_Hans: Google # Chinese label
+ description: # Description, for frontend display
+ en_US: Google # English description
+ zh_Hans: Google # Chinese description
+ icon: icon.svg # Tool icon, needs to be placed in the _assets folder
+ tags: # Tags, for frontend display
+ - search
+```
+
+Make sure the file path is in the `/tools` directory, with the complete path as follows:
+
+```yaml
+plugins:
+ tools:
+ - 'google.yaml'
+```
+
+Where `google.yaml` needs to use its absolute path in the plugin project. In this example, it is located in the project root directory. The identity field in the YAML file is explained as follows: `identity` contains basic information about the tool provider, including author, name, label, description, icon, etc.
+
+- The icon needs to be an attachment resource and needs to be placed in the `_assets` folder in the project root directory.
+- Tags can help users quickly find plugins through categories. Below are all the currently supported tags.
+
+```python
+class ToolLabelEnum(Enum):
+ SEARCH = 'search'
+ IMAGE = 'image'
+ VIDEOS = 'videos'
+ WEATHER = 'weather'
+ FINANCE = 'finance'
+ DESIGN = 'design'
+ TRAVEL = 'travel'
+ SOCIAL = 'social'
+ NEWS = 'news'
+ MEDICAL = 'medical'
+ PRODUCTIVITY = 'productivity'
+ EDUCATION = 'education'
+ BUSINESS = 'business'
+ ENTERTAINMENT = 'entertainment'
+ UTILITIES = 'utilities'
+ OTHER = 'other'
+```
+
+#### **2. Completing Third-Party Service Credentials**
+
+For development convenience, we choose to use the Google Search API provided by the third-party service `SerpApi`. `SerpApi` requires an API Key for use, so we need to add the `credentials_for_provider` field in the `yaml` file.
+
+The complete code is as follows:
+
+```yaml
+identity:
+ author: Dify
+ name: google
+ label:
+ en_US: Google
+ zh_Hans: Google
+ pt_BR: Google
+ description:
+ en_US: Google
+ zh_Hans: GoogleSearch
+ pt_BR: Google
+ icon: icon.svg
+ tags:
+ - search
+credentials_for_provider: #Add credentials_for_provider field
+ serpapi_api_key:
+ type: secret-input
+ required: true
+ label:
+ en_US: SerpApi API key
+ zh_Hans: SerpApi API key
+ placeholder:
+ en_US: Please input your SerpApi API key
+ zh_Hans: Please enter your SerpApi API key
+ help:
+ en_US: Get your SerpApi API key from SerpApi
+ zh_Hans: Get your SerpApi API key from SerpApi
+ url: https://serpapi.com/manage-api-key
+tools:
+ - tools/google_search.yaml
+extra:
+ python:
+ source: google.py
+```
+
+- The sub-level structure of `credentials_for_provider` needs to meet the requirements of [General Specifications](/plugin_dev_en/0411-general-specifications.en).
+- You need to specify which tools the provider includes. This example only includes one `tools/google_search.yaml` file.
+- As a provider, in addition to defining its basic information, you also need to implement some of its code logic, so you need to specify its implementation logic. In this example, we put the code file for the functionality in `google.py`, but we won't implement it yet, but instead write the code for `google_search` first.
+
+#### 3. Filling in the Tool YAML File
+
+A tool plugin can have multiple tool functions, and each tool function needs a `yaml` file for description, including basic information about the tool function, parameters, output, etc.
+
+Still using the `GoogleSearch` tool as an example, create a new `google_search.yaml` file in the `/tools` folder.
+
+```yaml
+identity:
+ name: google_search
+ author: Dify
+ label:
+ en_US: GoogleSearch
+ zh_Hans: Google Search
+ pt_BR: GoogleSearch
+description:
+ human:
+ en_US: A tool for performing a Google SERP search and extracting snippets and webpages. Input should be a search query.
+ zh_Hans: A tool for performing a Google SERP search and extracting snippets and webpages. Input should be a search query.
+ pt_BR: A tool for performing a Google SERP search and extracting snippets and webpages. Input should be a search query.
+ llm: A tool for performing a Google SERP search and extracting snippets and webpages. Input should be a search query.
+parameters:
+ - name: query
+ type: string
+ required: true
+ label:
+ en_US: Query string
+ zh_Hans: Query string
+ pt_BR: Query string
+ human_description:
+ en_US: used for searching
+ zh_Hans: used for searching web content
+ pt_BR: used for searching
+ llm_description: key words for searching
+ form: llm
+extra:
+ python:
+ source: tools/google_search.py
+```
+
+- `identity` contains basic information about the tool, including name, author, label, description, etc.
+- `parameters` parameter list
+ - `name` (required) parameter name, unique, cannot have the same name as other parameters.
+ - `type` (required) parameter type, currently supports `string`, `number`, `boolean`, `select`, `secret-input` five types, corresponding to string, number, boolean, dropdown, encrypted input box. For sensitive information, please use the `secret-input` type.
+ - `label` (required) parameter label, for frontend display.
+ - `form` (required) form type, currently supports `llm`, `form` two types.
+ - In Agent applications, `llm` means that the parameter is inferred by the LLM itself, `form` means that parameters can be preset in advance to use this tool.
+ - In Workflow applications, both `llm` and `form` need to be filled in by the frontend, but `llm` parameters will be used as input variables for the tool node.
+ - `required` whether required
+ - In `llm` mode, if the parameter is required, the Agent will be required to infer this parameter.
+ - In `form` mode, if the parameter is required, the user will be required to fill in this parameter on the frontend before the conversation begins.
+ - `options` parameter options
+ - In `llm` mode, Dify will pass all options to the LLM, and the LLM can infer based on these options.
+ - In `form` mode, when `type` is `select`, the frontend will display these options.
+ - `default` default value.
+ - `min` minimum value, can be set when the parameter type is `number`.
+ - `max` maximum value, can be set when the parameter type is `number`.
+ - `human_description` introduction for frontend display, supports multiple languages.
+ - `placeholder` prompt text for the input field, can be set when the form type is `form` and the parameter type is `string`, `number`, `secret-input`, supports multiple languages.
+ - `llm_description` introduction passed to the LLM. To make the LLM better understand this parameter, please write as detailed information as possible about this parameter here, so that the LLM can understand the parameter.
+
+#### 4. Preparing Tool Code
+
+After filling in the configuration information for the tool, you can start writing the code for the tool's functionality, implementing the logical purpose of the tool. Create `google_search.py` in the `/tools` directory with the following content:
+
+```python
+from collections.abc import Generator
+from typing import Any
+
+import requests
+
+from dify_plugin import Tool
+from dify_plugin.entities.tool import ToolInvokeMessage
+
+SERP_API_URL = "https://serpapi.com/search"
+
+class GoogleSearchTool(Tool):
+ def _parse_response(self, response: dict) -> dict:
+ result = {}
+ if "knowledge_graph" in response:
+ result["title"] = response["knowledge_graph"].get("title", "")
+ result["description"] = response["knowledge_graph"].get("description", "")
+ if "organic_results" in response:
+ result["organic_results"] = [
+ {
+ "title": item.get("title", ""),
+ "link": item.get("link", ""),
+ "snippet": item.get("snippet", ""),
+ }
+ for item in response["organic_results"]
+ ]
+ return result
+
+ def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage]:
+ params = {
+ "api_key": self.runtime.credentials["serpapi_api_key"],
+ "q": tool_parameters["query"],
+ "engine": "google",
+ "google_domain": "google.com",
+ "gl": "us",
+ "hl": "en",
+ }
+
+ response = requests.get(url=SERP_API_URL, params=params, timeout=5)
+ response.raise_for_status()
+ valuable_res = self._parse_response(response.json())
+
+ yield self.create_json_message(valuable_res)
+```
+
+This example means requesting `serpapi` and using `self.create_json_message` to return a formatted `json` data string. If you want to learn more about return data types, you can refer to the [Remote Debugging Plugins](/plugin_dev_en/0411-remote-debug-a-plugin.en) and [Persistent Storage KV](/plugin_dev_en/0411-persistent-storage-kv.en) documents.
+
+#### 4. Completing the Tool Provider Code
+
+Finally, you need to create an implementation code for the provider to implement the credential validation logic. If credential validation fails, a `ToolProviderCredentialValidationError` exception will be thrown. After validation succeeds, the `google_search` tool service will be correctly requested.
+
+Create a `google.py` file in the `/provider` directory with the following content:
+
+```python
+from typing import Any
+
+from dify_plugin import ToolProvider
+from dify_plugin.errors.tool import ToolProviderCredentialValidationError
+from tools.google_search import GoogleSearchTool
+
+class GoogleProvider(ToolProvider):
+ def _validate_credentials(self, credentials: dict[str, Any]) -> None:
+ try:
+ for _ in GoogleSearchTool.from_credentials(credentials).invoke(
+ tool_parameters={"query": "test", "result_type": "link"},
+ ):
+ pass
+ except Exception as e:
+ raise ToolProviderCredentialValidationError(str(e))
+```
+
+### Debugging the Plugin
+
+After completing plugin development, you need to test whether the plugin can function properly. Dify provides a convenient remote debugging method to help you quickly verify the plugin's functionality in a test environment.
+
+Go to the ["Plugin Management"](https://cloud.dify.ai/plugins) page to obtain the remote server address and debugging Key.
+
+
+
+Return to the plugin project, copy the `.env.example` file and rename it to `.env`, then fill in the remote server address and debugging Key information you obtained.
+
+`.env` file:
+
+```bash
+INSTALL_METHOD=remote
+REMOTE_INSTALL_HOST=remote
+REMOTE_INSTALL_PORT=5003
+REMOTE_INSTALL_KEY=****-****-****-****-****
+```
+
+Run the `python -m main` command to start the plugin. On the plugins page, you can see that the plugin has been installed in the Workspace, and other members of the team can also access the plugin.
+
+
+
+### Packaging the Plugin (Optional)
+
+After confirming that the plugin can run normally, you can package and name the plugin using the following command line tool. After running, you will discover a `google.difypkg` file in the current folder, which is the final plugin package.
+
+```bash
+# Replace ./google with the actual path of the plugin project
+
+dify plugin package ./google
+```
+
+Congratulations, you have completed the entire process of developing, debugging, and packaging a tool-type plugin!
+
+### Publishing the Plugin (Optional)
+
+If you want to publish the plugin to the Dify Marketplace, please ensure that your plugin follows the specifications in [Publish to Dify Marketplace](/plugin_dev_en/0322-release-to-dify-marketplace.en). After passing the review, the code will be merged into the main branch and automatically launched to the [Dify Marketplace](https://marketplace.dify.ai/).
+
+[Publishing Overview](/plugin_dev_en/0321-release-overview.en)
+
+### Explore More
+
+#### **Quick Start:**
+
+- [Developing Extension Plugins](/plugin_dev_en/9231-extension-plugin.en)
+- [Developing Model Plugins](/plugin_dev_en/0211-getting-started-new-model.en)
+- [Bundle Plugins: Packaging Multiple Plugins](/plugin_dev_en/9241-bundle.en)
+
+#### **Plugin Interface Documentation:**
+
+- [General Specifications](/plugin_dev_en/0411-general-specifications.en) - Manifest Structure and Tool Specifications
+- [Endpoint](/plugin_dev_en/0432-endpoint.en) - Detailed Endpoint Definition
+- [Reverse Invocation](/plugin_dev_en/9241-reverse-invocation.en) - Reverse Invocation of Dify Capabilities
+- [Model Schema](/plugin_dev_en/0412-model-schema.en) - Models
+- [Agent Plugins](/plugin_dev_en/9232-agent.en) - Extending Agent Strategies
+
+## Next Learning Steps
+
+- [Remote Debugging Plugins](/plugin_dev_en/0411-remote-debug-a-plugin.en) - Learn more advanced debugging techniques
+- [Persistent Storage](/plugin_dev_en/0411-persistent-storage-kv.en) - Learn how to use data storage in plugins
+- [Slack Bot Plugin Development Example](/plugin_dev_en/0432-develop-a-slack-bot-plugin.en) - View a more complex plugin development case
+- [Tool Plugin](/plugin_dev_en/0411-tool.en) - Explore advanced features of tool plugins
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ Help improve our documentation by contributing directly
+
+
+ Found an error or have suggestions? Let us know
+
+
diff --git a/plugin_dev_en/0312-contributor-covenant-code-of-conduct.en.mdx b/plugin_dev_en/0312-contributor-covenant-code-of-conduct.en.mdx
new file mode 100644
index 00000000..8b67faee
--- /dev/null
+++ b/plugin_dev_en/0312-contributor-covenant-code-of-conduct.en.mdx
@@ -0,0 +1,90 @@
+---
+dimensions:
+ type:
+ primary: operational
+ detail: setup
+ level: intermediate
+standard_title: Contributor Covenant Code of Conduct
+language: en
+title: Plugin Developer Guidelines
+description: This document provides guidelines that Dify plugin developers need to
+ follow before submitting a Pull Request, including ensuring that the plugin functions
+ properly, has complete documentation, provides unique value, and complies with data
+ privacy and security standards. It includes documentation requirements, guidelines
+ for avoiding duplicate plugins, and requirements for privacy information collection
+ declarations.
+---
+
+### Before Submitting a Pull Request (PR)
+
+1. **Ensure the Plugin Functions Properly and Documentation is Complete**
+
+* Verify that the plugin's functionality works correctly. For details, please refer to [Remote Debugging Plugins](/plugin_dev_en/0411-remote-debug-a-plugin.en).
+* Provide a comprehensive **README file**, including:
+ * Setup instructions and usage guide.
+ * Code, APIs, credentials, or other information that plugin users need to connect the plugin to the service.
+* Ensure that collected user information is only used for connecting services and improving plugin functionality.
+* Prepare a privacy policy content file or online document URL according to the [Plugin Privacy Data Protection Guide](/plugin_dev_en/0312-privacy-protection-guidelines.en).
+
+2. **Verify the Value Contribution of the Plugin**
+
+* Ensure the plugin provides unique value to Dify users.
+* The plugin should introduce functionality or services not yet provided by Dify or other plugins.
+* Follow community standards:
+ * Content should be non-violent and respectful to the global user community.
+ * Comply with the relevant policies of the integrated service.
+* **How to check if similar plugins already exist?**
+ * Avoid submitting plugins with functionality that duplicates existing plugins or PRs, unless the new plugin has the following characteristics:
+ * Introduces new features.
+ * Provides performance improvements.
+ * **How to determine if a plugin is unique enough:**
+ * If the plugin only makes minor adjustments to existing functionality (e.g., adding language parameters), it is recommended to extend the existing plugin directly.
+ * If the plugin implements significant functional changes (e.g., optimizing batch processing or improving error handling), it can be submitted as a new plugin.
+ * Not sure? Please include a brief explanation in your PR submission, explaining why a new plugin is needed.
+
+**Example:** Taking the Google search plugin as an example, it accepts a single input query and outputs a list of Google search results using the Google Search API. If you provide a new Google search plugin with similar underlying implementation but with minor input adjustments (e.g., adding new language parameters), we recommend extending the existing plugin. On the other hand, if you implement the plugin in a new way with optimized batch search and error handling capabilities, it can be considered as a separate plugin for review.
+
+3. **Ensure the Plugin Complies with the Following Privacy Data Standards**
+
+### Information Disclosure Requirements:
+
+* Developers **must** declare whether they collect any type of user personal data when submitting an application/tool. For details, please refer to the [Plugin Privacy Data Protection Guide](/plugin_dev_en/0312-privacy-protection-guidelines.en).
+* If collected, **simply list** the types of data collected (e.g., username, email, device ID, location information, etc.), **no need to be overly detailed**.
+* Developers **must** provide a privacy policy link. The privacy policy only needs to state what information is collected, how the information is used, what information is disclosed to third parties, and include links to third-party privacy policies.
+
+**Review Focus:**
+
+* **Form Review:** Check if data collection has been declared as required.
+* **High-risk Data Screening:** Focus on whether sensitive data (e.g., health information, financial information, children's personal information, etc.) is collected. If sensitive data is collected, **additional review** of its purpose of use and security measures is required.
+* **Malicious Behavior Screening:** Check for obvious malicious behavior, such as collecting data without user consent or uploading user data to unknown servers.
+
+## Related Resources
+
+- [Basic Concepts of Plugin Development](/plugin_dev_en/0111-getting-started-dify-plugin.en) - Learn the basics of plugin development
+- [Publishing Plugins](/plugin_dev_en/0321-release-overview.en) - Overview of the plugin publishing process
+- [Plugin Privacy Data Protection Guide](/plugin_dev_en/0312-privacy-protection-guidelines.en) - Guide for writing privacy policies
+- [Publish to Dify Marketplace](/plugin_dev_en/0322-release-to-dify-marketplace.en) - Publish plugins on the official marketplace
+- [Remote Debugging Plugins](/plugin_dev_en/0411-remote-debug-a-plugin.en) - Plugin debugging guide
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ Help improve our documentation by contributing directly
+
+
+ Found an error or have suggestions? Let us know
+
+
diff --git a/plugin_dev_en/0312-privacy-protection-guidelines.en.mdx b/plugin_dev_en/0312-privacy-protection-guidelines.en.mdx
new file mode 100644
index 00000000..1225ae9a
--- /dev/null
+++ b/plugin_dev_en/0312-privacy-protection-guidelines.en.mdx
@@ -0,0 +1,118 @@
+---
+dimensions:
+ type:
+ primary: operational
+ detail: setup
+ level: intermediate
+standard_title: Privacy Protection Guidelines
+language: en
+title: Plugin Privacy Policy Guidelines
+description: This document describes guidelines for developers on how to write a privacy
+ policy when submitting plugins to the Dify Marketplace. It includes how to identify
+ and list the types of personal data collected (direct identification information,
+ indirect identification information, combined information), how to fill out the
+ plugin privacy policy, how to include the privacy policy statement in the Manifest
+ file, and answers to related common questions.
+---
+
+When you are submitting your Plugin to Dify Marketplace, you are required to be transparent in how you handle user data. The following guidelines focus on how to address privacy-related questions and user data processing for your plugin.
+
+Center your privacy policy around the following points:
+
+**Does your plugin collect and use any user personal data?** If it does, please list the types of data collected.
+
+> “Personal data” refers to any information that can identify a specific individual—either on its own or when combined with other data—such as information used to locate, contact, or otherwise target a unique person.
+
+#### 1. List the types of data collected
+
+**Type A:** **Direct Identifiers**
+
+* Name (e.g., full name, first name, last name)
+* Email address
+* Phone number
+* Home address or other physical address
+* Government-issued identification numbers (e.g., Social Security number, passport number, driver's license number)
+
+**Type B**: **Indirect Identifiers**
+
+* Device identifiers (e.g., IMEI, MAC address, device ID)
+* IP address
+* Location data (e.g., GPS coordinates, city, region)
+* Online identifiers (e.g., cookies, advertising IDs)
+* Usernames
+* Profile pictures
+* Biometric data (e.g., fingerprints, facial recognition data)
+* Web browsing history
+* Purchase history
+* Health information
+* Financial information
+
+**Type C: Data that can be combined with other data to identify an individual:**
+
+* Age
+* Gender
+* Occupation
+* Interests
+
+While your plugin may not collect any personal information, you also need to make sure the use of third-party services within your plugin may involve data collection or processing. As the plugin developer, you are responsible for disclosing all data collection activities associated within your plugin, including those performed by third-party services. Thus, make sure to read through the privacy policy of the third-party service and verify any data collected by your plugin is claimed in your submission.
+
+For example, if the plugin you are developing involves Slack services, make sure to reference [Slack’s privacy policy](https://slack.com/trust/privacy/privacy-policy) in your plugin’s privacy policy statement and clearly disclose the data collection practices.
+
+#### **2. Submit the most up to date Privacy Policy of your Plugin**
+
+**The Privacy Policy** should contain:
+
+* What types of data are collected.
+* How the collected data is used.
+* Whether any data is shared with third parties, and if so, identify those third parties and provide links to their privacy policies.
+* If you have no clue of how to write a privacy policy, you can also check the privacy policy of Plugins issued by Dify Team.
+
+#### 3. Introducing a privacy policy statement within the plugin Manifest file
+
+For detailed instructions on filling out specific fields, please refer to the [General Specifications](/plugin_dev_en/0411-general-specifications.en) documentation.
+
+**FAQ**
+
+1. **What does "collect and use" mean regarding user personal data? Are there any common examples of how personal data is collected and used in any Plugin?**
+
+"Collect and use" user data generally refers to the collection, transmission, use, or sharing of user data. Common examples of how products may handle personal or sensitive user data include:
+
+* Using forms that gather any kind of personally identifiable information.
+* Implementing login features, even when using third-party authentication services.
+* Collecting information about input or resources that may contain personally identifiable information.
+* Implementing analytics to track user behavior, interactions, and usage patterns.
+* Storing communication data like messages, chat logs, or email addresses.
+* Accessing user profiles or data from connected social media accounts.
+* Collecting health and fitness data such as activity levels, heart rate, or medical information.
+* Storing search queries or tracking browsing behavior.
+* Processing financial information including bank details, credit scores, or transaction history.
+
+## Related Resources
+
+- [Publishing Overview](/plugin_dev_en/0321-release-overview.en) - Understand the plugin publishing process
+- [Publish to Dify Marketplace](/plugin_dev_en/0322-release-to-dify-marketplace.en) - Learn how to submit plugins to the official marketplace
+- [Plugin Developer Guidelines](/plugin_dev_en/0312-contributor-covenant-code-of-conduct.en) - Understand plugin submission guidelines
+- [General Specifications](/plugin_dev_en/0411-general-specifications.en) - Plugin metadata configuration
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ Help improve our documentation by contributing directly
+
+
+ Found an error or have suggestions? Let us know
+
+
diff --git a/plugin_dev_en/0321-release-overview.en.mdx b/plugin_dev_en/0321-release-overview.en.mdx
new file mode 100644
index 00000000..5e145d66
--- /dev/null
+++ b/plugin_dev_en/0321-release-overview.en.mdx
@@ -0,0 +1,111 @@
+---
+dimensions:
+ type:
+ primary: operational
+ detail: deployment
+ level: beginner
+standard_title: Release Overview
+language: en
+title: Publishing Plugins
+description: This document introduces three ways to publish Dify plugins - official
+ Marketplace, open-source GitHub repository, and local plugin file package. It details
+ the characteristics, publishing process, and applicable scenarios for each method,
+ and provides specific publishing recommendations to meet the needs of different
+ developers.
+---
+
+### Publishing Methods
+
+To meet the publishing needs of different developers, Dify provides the following three plugin publishing methods. Before publishing, please ensure that you have completed the development and testing of your plugin, and have read [Basic Concepts of Plugin Development](/plugin_dev_en/0111-getting-started-dify-plugin.en) and [Plugin Developer Guidelines](/plugin_dev_en/0312-contributor-covenant-code-of-conduct.en).
+
+#### **1. Marketplace**
+
+**Introduction**: The official plugin marketplace provided by Dify, where users can browse, search, and install various plugins with one click.
+
+**Features**:
+
+* Plugins are reviewed before going online, ensuring they are **safe and reliable**.
+* Can be directly installed in personal or team **Workspaces**.
+
+**Publishing Process**:
+
+* Submit the plugin project to the **Dify Marketplace** [code repository](https://github.com/langgenius/dify-plugins).
+* After official review, the plugin will be publicly available in the marketplace for other users to install and use.
+
+For detailed instructions, please refer to:
+
+[Publish to Dify Marketplace](/plugin_dev_en/0322-release-to-dify-marketplace.en)
+
+#### 2. **GitHub Repository**
+
+**Introduction**: Open-source or host your plugin on **GitHub** for others to view, download, and install.
+
+**Features**:
+
+* Convenient for **version management** and **open-source sharing**.
+* Users can install directly via the plugin link, without platform review.
+
+**Publishing Process**:
+
+* Push the plugin code to a GitHub repository.
+* Share the repository link, users can integrate the plugin into their **Dify Workspace** through the link.
+
+For detailed instructions, please refer to:
+
+[Publish to Individual GitHub Repository](/plugin_dev_en/0322-release-to-individual-github-repo.en)
+
+#### 3. Plugin File Package (Local Installation)
+
+**Introduction**: Package the plugin as a local file (such as `.difypkg` format) and share it for others to install.
+
+**Features**:
+
+* Not dependent on online platforms, **quick and flexible** way to share plugins.
+* Suitable for **private plugins** or **internal testing**.
+
+**Publishing Process**:
+
+* Package the plugin project as a local file.
+* Click **Upload Plugin** on the Dify plugins page and select the local file to install the plugin.
+
+You can package your plugin project as a local file and share it with others. After uploading the file on the plugins page, you can install the plugin into your Dify Workspace.
+
+For detailed instructions, please refer to:
+
+[Package as Local File and Share](/plugin_dev_en/0322-release-by-file.en)
+
+### **Publishing Recommendations**
+
+* **Want to promote your plugin** → **Recommended to use Marketplace**, ensuring plugin quality through official review and increasing exposure.
+* **Open-source sharing project** → **Recommended to use GitHub**, convenient for version management and community collaboration.
+* **Quick distribution or internal testing** → **Recommended to use plugin files**, simple and efficient way to install and share.
+
+## Related Resources
+
+- [Basic Concepts of Plugin Development](/plugin_dev_en/0111-getting-started-dify-plugin.en) - Comprehensive understanding of Dify plugin development
+- [Plugin Developer Guidelines](/plugin_dev_en/0312-contributor-covenant-code-of-conduct.en) - Understand the standards for plugin submission
+- [Plugin Privacy Data Protection Guide](/plugin_dev_en/0312-privacy-protection-guidelines.en) - Understand the requirements for writing privacy policies
+- [General Specifications](/plugin_dev_en/0411-general-specifications.en) - Understand the configuration of plugin manifest files
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ Help improve our documentation by contributing directly
+
+
+ Found an error or have suggestions? Let us know
+
+
diff --git a/plugin_dev_en/0322-release-by-file.en.mdx b/plugin_dev_en/0322-release-by-file.en.mdx
new file mode 100644
index 00000000..05688173
--- /dev/null
+++ b/plugin_dev_en/0322-release-by-file.en.mdx
@@ -0,0 +1,90 @@
+---
+dimensions:
+ type:
+ primary: operational
+ detail: deployment
+ level: intermediate
+standard_title: Release by File
+language: en
+title: Package as Local File and Share
+description: This document provides detailed steps on how to package a Dify plugin
+ project as a local file and share it with others. It covers the preparation work
+ before packaging a plugin, using the Dify plugin development tool to execute packaging
+ commands, how to install the generated .difypkg file, and how to share plugin files
+ with other users.
+---
+
+After completing plugin development, you can package the plugin project as a local file and share it with others. After obtaining the plugin file, it can be installed into a Dify Workspace. If you haven't developed a plugin yet, you can refer to the [Plugin Development: Hello World Guide](/plugin_dev_en/0211-getting-started-dify-tool.en).
+
+* **Features**:
+ * Not dependent on online platforms, **quick and flexible** way to share plugins.
+ * Suitable for **private plugins** or **internal testing**.
+* **Publishing Process**:
+ * Package the plugin project as a local file.
+ * Upload the file on the Dify plugins page to install the plugin.
+
+This article will introduce how to package a plugin project as a local file and how to install a plugin using a local file.
+
+### Prerequisites
+
+* **Dify Plugin Development Tool**, for detailed instructions, please refer to [Initializing Development Tools](/plugin_dev_en/0221-initialize-development-tools.en).
+
+After configuration, enter the `dify version` command in the terminal to check if it outputs version information to confirm that the necessary development tools have been installed.
+
+### Packaging the Plugin
+
+> Before packaging the plugin, please ensure that the `author` field in the plugin's `manifest.yaml` file and the `.yaml` file under the `/provider` path is consistent with your GitHub ID. For detailed information about the manifest file, please refer to [General Specifications](/plugin_dev_en/0411-general-specifications.en).
+
+After completing the plugin project development, make sure you have completed the [remote debugging test](/plugin_dev_en/0411-remote-debug-a-plugin.en). Navigate to the directory above your plugin project and run the following plugin packaging command:
+
+```bash
+dify plugin package ./your_plugin_project
+```
+
+After running the command, a file with the `.difypkg` extension will be generated in the current path.
+
+
+
+### Installing the Plugin
+
+Visit the Dify plugin management page, click **Install Plugin** in the upper right corner → **Via Local File** to install, or drag and drop the plugin file to a blank area of the page to install the plugin.
+
+
+
+### Publishing the Plugin
+
+You can share the plugin file with others or upload it to the internet for others to download. If you want to share your plugin more widely, you can consider:
+
+1. [Publish to Individual GitHub Repository](/plugin_dev_en/0322-release-to-individual-github-repo.en) - Share the plugin through GitHub
+2. [Publish to Dify Marketplace](/plugin_dev_en/0322-release-to-dify-marketplace.en) - Publish the plugin on the official marketplace
+
+## Related Resources
+
+- [Publishing Plugins](/plugin_dev_en/0321-release-overview.en) - Learn about various publishing methods
+- [Initializing Development Tools](/plugin_dev_en/0221-initialize-development-tools.en) - Configure plugin development environment
+- [Remote Debugging Plugins](/plugin_dev_en/0411-remote-debug-a-plugin.en) - Learn plugin debugging methods
+- [General Specifications](/plugin_dev_en/0411-general-specifications.en) - Define plugin metadata
+- [Plugin Development: Hello World Guide](/plugin_dev_en/0211-getting-started-dify-tool.en) - Develop a plugin from scratch
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ Help improve our documentation by contributing directly
+
+
+ Found an error or have suggestions? Let us know
+
+
diff --git a/plugin_dev_en/0322-release-to-dify-marketplace.en.mdx b/plugin_dev_en/0322-release-to-dify-marketplace.en.mdx
new file mode 100644
index 00000000..f9a6d58d
--- /dev/null
+++ b/plugin_dev_en/0322-release-to-dify-marketplace.en.mdx
@@ -0,0 +1,126 @@
+---
+dimensions:
+ type:
+ primary: operational
+ detail: deployment
+ level: intermediate
+standard_title: Release to Dify Marketplace
+language: en
+title: Publish to Dify Marketplace
+description: This guide provides detailed instructions on the complete process of
+ publishing plugins to the Dify Marketplace, including submitting PRs, the review
+ process, post-release maintenance, and other key steps and considerations.
+---
+
+Dify Marketplace welcomes plugin submissions from partners and community developers. Your contributions will further enrich the possibilities of Dify plugins. This guide provides a clear publishing process and best practice recommendations to help your plugin get published smoothly and bring value to the community. If you haven't developed a plugin yet, you can refer to the [Plugin Development: Hello World Guide](/plugin_dev_en/0211-getting-started-dify-tool.en).
+
+Please follow these steps to submit your plugin Pull Request (PR) to the [GitHub repository](https://github.com/langgenius/dify-plugins) for review. After approval, your plugin will be officially launched on the Dify Marketplace.
+
+### Plugin Publishing Process
+
+Publishing a plugin to the Dify Marketplace involves the following steps:
+
+1. Complete plugin development and testing according to the [Plugin Developer Guidelines](/plugin_dev_en/0312-contributor-covenant-code-of-conduct.en);
+2. Write a privacy policy for the plugin according to the [Plugin Privacy Data Protection Guide](/plugin_dev_en/0312-privacy-protection-guidelines.en), and include the file path or URL of the privacy policy in the plugin [General Specifications](/plugin_dev_en/0411-general-specifications.en);
+3. Complete plugin packaging;
+4. Fork the [Dify Plugins](https://github.com/langgenius/dify-plugins) code repository;
+5. Create an Organization directory, create a plugin name directory under the Organization directory, and upload the plugin code and pkg file to the corresponding plugin name directory;
+6. Submit a Pull Request (PR) following the PR Template format in GitHub and wait for review;
+7. After the review is approved, the plugin code will be merged into the Main branch, and the plugin will be automatically launched on the [Dify Marketplace](https://marketplace.dify.ai/).
+
+Plugin submission, review, and publication flow chart:
+
+
+
+> **Note**: The Contributor Agreement in the above diagram refers to the [Plugin Developer Guidelines](/plugin_dev_en/0312-contributor-covenant-code-of-conduct.en).
+
+***
+
+### During Pull Request (PR) Review
+
+Actively respond to reviewer questions and feedback:
+
+* PR comments unresolved within **14 days** will be marked as stale (can be reopened).
+* PR comments unresolved within **30 days** will be closed (cannot be reopened, a new PR needs to be created).
+
+***
+
+### **After Pull Request (PR) Approval**
+
+**1. Ongoing Maintenance**
+
+* Address user-reported issues and feature requests.
+* Migrate plugins when significant API changes occur:
+ * Dify will publish change notifications and migration instructions in advance.
+ * Dify engineers can provide migration support.
+
+**2. Restrictions during the Marketplace Public Beta Testing Phase**
+
+* Avoid introducing breaking changes to existing plugins.
+
+***
+
+### Review Process
+
+**1. Review Order**
+
+* PRs are processed on a **first-come, first-reviewed** basis. Review will begin within 1 week. If there is a delay, reviewers will notify the PR author via comments.
+
+**2. Review Focus**
+
+* Check if the plugin name, description, and setup instructions are clear and instructive.
+* Check if the plugin's [General Specifications](/plugin_dev_en/0411-general-specifications.en) meets format standards and includes valid author contact information.
+
+**3. Plugin Functionality and Relevance**
+
+* Test plugins according to the [Plugin Development Guide](/plugin_dev_en/0111-getting-started-dify-plugin.en).
+* Ensure the plugin has a reasonable purpose in the Dify ecosystem.
+
+[Dify.AI](https://dify.ai/) reserves the right to accept or reject plugin submissions.
+
+***
+
+### Frequently Asked Questions
+
+1. **How to determine if a plugin is unique?**
+
+Example: A Google search plugin that only adds multilingual versions should be considered an optimization of an existing plugin. However, if the plugin implements significant functional improvements (such as optimized batch processing or error handling), it can be submitted as a new plugin.
+
+2. **What if my PR is marked as stale or closed?**
+
+PRs marked as stale can be reopened after addressing feedback. Closed PRs (over 30 days) require creating a new PR.
+
+3. **Can I update plugins during the Beta testing phase?**
+
+Yes, but breaking changes should be avoided.
+
+## Related Resources
+
+- [Publishing Plugins](/plugin_dev_en/0321-release-overview.en) - Learn about various publishing methods
+- [Plugin Developer Guidelines](/plugin_dev_en/0312-contributor-covenant-code-of-conduct.en) - Plugin submission standards
+- [Plugin Privacy Data Protection Guide](/plugin_dev_en/0312-privacy-protection-guidelines.en) - Privacy policy writing requirements
+- [Package as Local File and Share](/plugin_dev_en/0322-release-by-file.en) - Plugin packaging methods
+- [General Specifications](/plugin_dev_en/0411-general-specifications.en) - Define plugin metadata
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ Help improve our documentation by contributing directly
+
+
+ Found an error or have suggestions? Let us know
+
+
diff --git a/plugin_dev_en/0322-release-to-individual-github-repo.en.mdx b/plugin_dev_en/0322-release-to-individual-github-repo.en.mdx
new file mode 100644
index 00000000..721c412a
--- /dev/null
+++ b/plugin_dev_en/0322-release-to-individual-github-repo.en.mdx
@@ -0,0 +1,107 @@
+---
+dimensions:
+ type:
+ primary: operational
+ detail: deployment
+ level: intermediate
+standard_title: Release to Individual GitHub Repo
+language: en
+title: Publish to Individual GitHub Repository
+description: This document provides detailed instructions on how to publish Dify plugins
+ to a personal GitHub repository, including preparation work, initializing a local
+ plugin repository, connecting to a remote repository, uploading plugin files, packaging
+ plugin code, and the complete process of installing plugins via GitHub. This method
+ allows developers to fully manage their own plugin code and updates.
+---
+
+### Publish Methods
+
+To accommodate the various publishing needs of developers, Dify provides three plugin publish methods:
+
+#### **1. Marketplace**
+
+**Introduction**: The official Dify plugin marketplace allows users to browse, search, and install a variety of plugins with just one click.
+
+**Features**:
+
+* Plugins become available after passing a review, ensuring they are **trustworthy** and **high-quality**.
+* Can be installed directly into an individual or team **Workspaces**.
+
+**Publication Process**:
+
+* Submit the plugin project to the **Dify Marketplace** [code repository](https://github.com/langgenius/dify-plugins).
+* After an official review, the plugin will be publicly released in the marketplace for other users to install and use.
+
+For detailed instructions, please refer to:
+
+
+
+
+#### 2. **GitHub Repository**
+
+**Introduction**: Open-source or host the plugin on **GitHub** makes it easy for others to view, download, and install.
+
+**Features**:
+
+* Convenient for **version management** and **open-source sharing**.
+* Users can install the plugin directly via a link, bypassing platform review.
+
+**Publication Process**:
+
+* Push the plugin code to a GitHub repository.
+* Share the repository link, users can integrate the plugin into their **Dify Workspace** through the link.
+
+For detailed instructions, please refer to:
+
+
+
+
+#### Plugin File (Local Installation)
+
+**Introduction**: Package the plugin as a local file (e.g., `.difypkg` format) and share it for others to install.
+
+**Features**:
+
+* Does not depend on an online platform, enabling **quick and flexible** sharing of plugins.
+* Suitable for **private plugins** or **internal testing**.
+
+**Publication Process**:
+
+* Package the plugin project as a local file.
+* Click **Upload Plugin** on the Dify plugin page and select the local file to install the plugin.
+
+You can package the plugin project as a local file and share it with others. After uploading the file on the plugin page, the plugin can be installed into the Dify Workspace.
+
+For detailed instructions, please refer to:
+
+
+
+
+### **Publication Recommendations**
+
+* **Looking to promote a plugin** → **Recommended to use the Marketplace**, ensuring plugin quality through official review and increasing exposure.
+* **Open-source sharing project** → **Recommended to use GitHub**, convenient for version management and community collaboration.
+* **Quick distribution or internal testing** → **Recommended to use plugin file**, allowing for straightforward and efficient installation and sharing.
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ Help improve our documentation by contributing directly
+
+
+ Found an error or have suggestions? Let us know
+
+
diff --git a/plugin_dev_en/0331-faq.en.mdx b/plugin_dev_en/0331-faq.en.mdx
new file mode 100644
index 00000000..42408b0f
--- /dev/null
+++ b/plugin_dev_en/0331-faq.en.mdx
@@ -0,0 +1,63 @@
+---
+dimensions:
+ type:
+ primary: operational
+ detail: maintenance
+ level: beginner
+todo: Developers (Contributors) should thoroughly test before releasing; debugging
+ should not be the user's (Dify User / Consumer) responsibility.
+standard_title: Faq
+language: en
+title: Frequently Asked Questions
+description: This document answers common questions about Dify plugin development
+ and installation, including how to resolve plugin upload failures (by modifying
+ the author field) and how to handle verification exceptions during plugin installation
+ (by setting the FORCE_VERIFYING_SIGNATURE environment variable).
+---
+
+## What should I do if the plugin upload fails during installation?
+
+**Error Details**: An error message `PluginDaemonBadRequestError: plugin_unique_identifier is not valid` appears.
+
+**Solution**: Modify the `author` field in the `manifest.yaml` file under the plugin project and the `.yaml` file under the `/provider` path to your GitHub ID.
+
+Rerun the plugin packaging command and install the new plugin package.
+
+## How should I handle exceptions encountered during plugin installation?
+
+**Problem Description**: An exception message is encountered during plugin installation: `plugin verification has been enabled, and the plugin you want to install has a bad signature`. How should this be handled?
+
+**Solution**: Add the field `FORCE_VERIFYING_SIGNATURE=false` to the end of the `/docker/.env` configuration file. Then, run the following commands to restart the Dify service:
+
+```bash
+cd docker
+docker compose down
+docker compose up -d
+```
+
+After adding this field, the Dify platform will allow the installation of all plugins not listed (reviewed) on the Dify Marketplace, which may pose security risks.
+
+It is recommended to install plugins in a testing/sandbox environment first and confirm their safety before installing them in a production environment.
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ Help improve our documentation by contributing directly
+
+
+ Found an error or have suggestions? Let us know
+
+
diff --git a/plugin_dev_en/0411-general-specifications.en.mdx b/plugin_dev_en/0411-general-specifications.en.mdx
new file mode 100644
index 00000000..c0488bcc
--- /dev/null
+++ b/plugin_dev_en/0411-general-specifications.en.mdx
@@ -0,0 +1,146 @@
+---
+dimensions:
+ type:
+ primary: reference
+ detail: core
+ level: beginner
+standard_title: General Specifications
+language: en
+title: General Specification Definitions
+description: This document provides a detailed introduction to the common structures
+ and specifications in Dify plugin development, including path specifications, internationalization
+ objects (I18nObject), provider form structures (ProviderConfig), model configurations
+ (ModelConfig), node responses (NodeResponse), and tool selectors (ToolSelector),
+ defining these important data structures and their uses.
+---
+
+This article will briefly introduce common structures in plugin development. During development, it is strongly recommended to read this alongside [Basic Concepts of Plugin Development](/plugin_dev_en/0111-getting-started-dify-plugin.en) and the [Developer Cheatsheet](/plugin_dev_en/0131-cheatsheet.en) for a better understanding of the overall architecture.
+
+### Path Specifications
+
+When filling in file paths in Manifest or any yaml files, follow these two specifications depending on the type of file:
+
+* If the target file is a multimedia file such as an image or video, for example when filling in the plugin's `icon`, you should place these files in the `_assets` folder under the plugin's root directory.
+* If the target file is a regular text file, such as `.py` or `.yaml` code files, you should fill in the absolute path of the file within the plugin project.
+
+### Common Structures
+
+When defining plugins, there are some data structures that can be shared between tools, models, and Endpoints. These shared structures are defined here.
+
+#### I18nObject
+
+`I18nObject` is an internationalization structure that conforms to the [IETF BCP 47](https://tools.ietf.org/html/bcp47) standard. Currently, four languages are supported:
+
+* en_US
+* zh_Hans
+* ja_JP
+* pt_BR
+
+#### ProviderConfig
+
+`ProviderConfig` is a common provider form structure, applicable to both `Tool` and `Endpoint`
+
+* `name` (string): Form item name
+* `label` ([I18nObject](#i18nobject), required): Following [IETF BCP 47](https://tools.ietf.org/html/bcp47)
+* `type` ([provider_config_type](#providerconfigtype-string), required): Form type
+* `scope` ([provider_config_scope](#providerconfigscope-string)): Optional range, varies based on `type`
+* `required` (bool): Cannot be empty
+* `default` (any): Default value, only supports basic types `float` `int` `string`
+* `options` (list\[[provider_config_option](#providerconfigoption-object)]): Options, only used when type is `select`
+* `helper` (object): Help document link label, following [IETF BCP 47](https://tools.ietf.org/html/bcp47)
+* `url` (string): Help document link
+* `placeholder` (object): Following [IETF BCP 47](https://tools.ietf.org/html/bcp47)
+
+#### ProviderConfigOption(object)
+
+* `value` (string, required): Value
+* `label` (object, required): Following [IETF BCP 47](https://tools.ietf.org/html/bcp47)
+
+#### ProviderConfigType(string)
+
+* `secret-input` (string): Configuration information will be encrypted
+* `text-input` (string): Plain text
+* `select` (string): Dropdown
+* `boolean` (bool): Switch
+* `model-selector` (object): Model configuration information, including provider name, model name, model parameters, etc.
+* `app-selector` (object): app id
+* `tool-selector` (object): Tool configuration information, including tool provider, name, parameters, etc.
+* `dataset-selector` (string): TBD
+
+#### ProviderConfigScope(string)
+
+* When `type` is `model-selector`
+ * `all`
+ * `llm`
+ * `text-embedding`
+ * `rerank`
+ * `tts`
+ * `speech2text`
+ * `moderation`
+ * `vision`
+* When `type` is `app-selector`
+ * `all`
+ * `chat`
+ * `workflow`
+ * `completion`
+* When `type` is `tool-selector`
+ * `all`
+ * `plugin`
+ * `api`
+ * `workflow`
+
+#### ModelConfig
+
+* `provider` (string): Model provider name containing plugin_id, in the form of `langgenius/openai/openai`.
+* `model` (string): Specific model name.
+* `model_type` (enum): Enumeration of model types, refer to the [Model Design Rules](/plugin_dev_en/0411-model-designing-rules.en#modeltype) document.
+
+#### NodeResponse
+
+* `inputs` (dict): Variables that are finally input to the node.
+* `outputs` (dict): Output results of the node.
+* `process_data` (dict): Data generated during node execution.
+
+#### ToolSelector
+
+* `provider_id` (string): Tool provider name
+* `tool_name` (string): Tool name
+* `tool_description` (string): Tool description
+* `tool_configuration` (dict\[str, Any]): Tool configuration information
+* `tool_parameters` (dict\[str, dict]): Parameters that need LLM reasoning
+ * `name` (string): Parameter name
+ * `type` (string): Parameter type
+ * `required` (bool): Whether required
+ * `description` (string): Parameter description
+ * `default` (any): Default
+ * `options` (list\[string]): Options
+
+## Related Resources
+
+- [Basic Concepts of Plugin Development](/plugin_dev_en/0111-getting-started-dify-plugin.en) - Comprehensive understanding of Dify plugin development
+- [Developer Cheatsheet](/plugin_dev_en/0131-cheatsheet.en) - Quick reference for common commands and concepts in plugin development
+- [Tool Plugin Development Details](/plugin_dev_en/0222-tool-plugin.en) - Understanding how to define plugin information and the tool plugin development process
+- [Model Design Rules](/plugin_dev_en/0411-model-designing-rules.en) - Understanding the standards for model configuration
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ Help improve our documentation by contributing directly
+
+
+ Found an error or have suggestions? Let us know
+
+
diff --git a/plugin_dev_en/0411-model-designing-rules.en.mdx b/plugin_dev_en/0411-model-designing-rules.en.mdx
new file mode 100644
index 00000000..9b894de6
--- /dev/null
+++ b/plugin_dev_en/0411-model-designing-rules.en.mdx
@@ -0,0 +1,242 @@
+---
+dimensions:
+ type:
+ primary: reference
+ detail: core
+ level: beginner
+standard_title: Model Designing Rules
+language: en
+title: Model Design Rules
+description: This document defines in detail the core concepts and structures for
+ Dify model plugin development, including model providers (Provider), AI model entities
+ (AIModelEntity), model types (ModelType), configuration methods (ConfigurateMethod),
+ model features (ModelFeature), parameter rules (ParameterRule), price configuration
+ (PriceConfig), and detailed data structure specifications for various credential
+ modes.
+---
+
+* Model provider rules are based on the [Provider](#provider) entity.
+* Model rules are based on the [AIModelEntity](#aimodelentity) entity.
+
+> All entities below are based on `Pydantic BaseModel`, and can be found in the `entities` module.
+
+### Provider
+
+* `provider` (string) Provider identifier, e.g.: `openai`
+* `label` (object) Provider display name, i18n, can set both `en_US` (English) and `zh_Hans` (Chinese) languages
+ * `zh_Hans` (string) \[optional] Chinese label, if not set, will default to using `en_US`
+ * `en_US` (string) English label
+* `description` (object) \[optional] Provider description, i18n
+ * `zh_Hans` (string) \[optional] Chinese description
+ * `en_US` (string) English description
+* `icon_small` (string) \[optional] Provider small icon, stored in the `_assets` directory under the corresponding provider implementation directory, same language strategy as `label`
+ * `zh_Hans` (string) \[optional] Chinese icon
+ * `en_US` (string) English icon
+* `icon_large` (string) \[optional] Provider large icon, stored in the `_assets` directory under the corresponding provider implementation directory, same language strategy as label
+ * `zh_Hans` (string) \[optional] Chinese icon
+ * `en_US` (string) English icon
+* `background` (string) \[optional] Background color value, e.g.: #FFFFFF, if empty, will display the frontend default color value
+* `help` (object) \[optional] Help information
+ * `title` (object) Help title, i18n
+ * `zh_Hans` (string) \[optional] Chinese title
+ * `en_US` (string) English title
+ * `url` (object) Help link, i18n
+ * `zh_Hans` (string) \[optional] Chinese link
+ * `en_US` (string) English link
+* `supported_model_types` (array\[[ModelType](#modeltype)]) Supported model types
+* `configurate_methods` (array\[[ConfigurateMethod](#configuratemethod)]) Configuration methods
+* `provider_credential_schema` (\[[ProviderCredentialSchema](#providercredentialschema)]) Provider credential specifications
+* `model_credential_schema` (\[[ModelCredentialSchema](#modelcredentialschema)]) Model credential specifications
+
+### AIModelEntity
+
+* `model` (string) Model identifier, e.g.: `gpt-3.5-turbo`
+* `label` (object) \[optional] Model display name, i18n, can set both `en_US` (English) and `zh_Hans` (Chinese) languages
+ * `zh_Hans` (string) \[optional] Chinese label
+ * `en_US` (string) English label
+* `model_type` ([ModelType](#modeltype)) Model type
+* `features` (array\[[ModelFeature](#modelfeature)]) \[optional] List of supported features
+* `model_properties` (object) Model properties
+ * `mode` ([LLMMode](#llmmode)) Mode (available for model type `llm`)
+ * `context_size` (int) Context size (available for model types `llm` and `text-embedding`)
+ * `max_chunks` (int) Maximum number of chunks (available for model types `text-embedding moderation`)
+ * `file_upload_limit` (int) Maximum file upload limit, unit: MB. (available for model type `speech2text`)
+ * `supported_file_extensions` (string) Supported file extension formats, e.g.: mp3,mp4 (available for model type `speech2text`)
+ * `default_voice` (string) Default voice, required: alloy,echo,fable,onyx,nova,shimmer (available for model type `tts`)
+ * `voices` (list) List of available voices.
+ * `mode` (string) Voice model. (available for model type `tts`)
+ * `name` (string) Voice model display name. (available for model type `tts`)
+ * `language` (string) Supported languages for voice model. (available for model type `tts`)
+ * `word_limit` (int) Word limit for single conversion, defaults to paragraph segmentation (available for model type `tts`)
+ * `audio_type` (string) Supported audio file extension formats, e.g.: mp3,wav (available for model type `tts`)
+ * `max_workers` (int) Number of concurrent tasks supported for text-to-audio conversion (available for model type `tts`)
+ * `max_characters_per_chunk` (int) Maximum characters per chunk (available for model type `moderation`)
+* `parameter_rules` (array\[[ParameterRule](#parameterrule)]) \[optional] Model call parameter rules
+* `pricing` (\[[PriceConfig](#priceconfig)]) \[optional] Pricing information
+* `deprecated` (bool) Whether deprecated. If deprecated, the model list will no longer display it, but those already configured can continue to be used. Default is False.
+
+### ModelType
+
+* `llm` Text generation model
+* `text-embedding` Text embedding model
+* `rerank` Rerank model
+* `speech2text` Speech to text
+* `tts` Text to speech
+* `moderation` Content moderation
+
+### ConfigurateMethod
+
+* `predefined-model` Predefined model
+
+Indicates that the user only needs to configure unified provider credentials to use predefined models under the provider.
+
+* `customizable-model` Customizable model
+
+The user needs to add credential configuration for each model.
+
+* `fetch-from-remote` Fetch from remote
+
+Similar to the `predefined-model` configuration method, only unified provider credentials are needed, but the models are fetched from the provider using the credential information.
+
+
+### ModelFeature
+
+* `agent-thought` Agent reasoning, generally models over 70B have chain-of-thought capabilities.
+* `vision` Vision, i.e.: image understanding.
+* `tool-call` Tool calling
+* `multi-tool-call` Multiple tool calling
+* `stream-tool-call` Streaming tool calling
+
+### FetchFrom
+
+* `predefined-model` Predefined model
+* `fetch-from-remote` Remote model
+
+### LLMMode
+
+* `completion` Text completion
+* `chat` Chat
+
+### ParameterRule
+
+* `name` (string) Actual parameter name for model call
+* `use_template` (string) \[optional] Use template
+
+> For details on using templates, you can refer to the examples in [Creating a New Model Provider](/plugin_dev_en/0222-creating-new-model-provider.en).
+
+There are 5 pre-configured variable content templates by default:
+
+* `temperature`
+* `top_p`
+* `frequency_penalty`
+* `presence_penalty`
+* `max_tokens`
+
+You can directly set the template variable name in `use_template`, which will use the default configuration from entities.defaults.PARAMETER\_RULE\_TEMPLATE without needing to set any parameters other than `name` and `use_template`. If additional configuration parameters are set, they will override the default configuration. You can refer to `openai/llm/gpt-3.5-turbo.yaml` for examples.
+
+* `label` (object) \[optional] Label, i18n
+* `zh_Hans`(string) \[optional] Chinese label
+* `en_US` (string) English label
+* `type`(string) \[optional] Parameter type
+ * `int` Integer
+ * `float` Floating point
+ * `string` String
+ * `boolean` Boolean
+* `help` (string) \[optional] Help information
+* `zh_Hans` (string) \[optional] Chinese help information
+* `en_US` (string) English help information
+* `required` (bool) Whether required, default is False.
+* `default`(int/float/string/bool) \[optional] Default value
+* `min`(int/float) \[optional] Minimum value, only applicable to numeric types
+* `max`(int/float) \[optional] Maximum value, only applicable to numeric types
+* `precision`(int) \[optional] Precision, decimal places to retain, only applicable to numeric types
+* `options` (array\[string]) \[optional] Dropdown option values, only applicable when `type` is `string`, if not set or is null, then option values are not restricted
+
+### PriceConfig
+
+* `input` (float) Input unit price, i.e., Prompt unit price
+* `output` (float) Output unit price, i.e., returned content unit price
+* `unit` (float) Price unit, e.g., if priced per 1M tokens, then the unit token number corresponding to the unit price is `0.000001`
+* `currency` (string) Currency unit
+
+### ProviderCredentialSchema
+
+* `credential_form_schemas` (array\[[CredentialFormSchema](#credentialformschema)]) Credential form specifications
+
+### ModelCredentialSchema
+
+* `model` (object) Model identifier, default variable name is `model`
+ * `label` (object) Model form item display name
+ * `en_US` (string) English
+ * `zh_Hans`(string) \[optional] Chinese
+ * `placeholder` (object) Model prompt content
+ * `en_US`(string) English
+ * `zh_Hans`(string) \[optional] Chinese
+* `credential_form_schemas` (array\[[CredentialFormSchema](#credentialformschema)]) Credential form specifications
+
+### CredentialFormSchema
+
+* `variable` (string) Form item variable name
+* `label` (object) Form item label
+ * `en_US`(string) English
+ * `zh_Hans` (string) \[optional] Chinese
+* `type` ([FormType](#formtype)) Form item type
+* `required` (bool) Whether required
+* `default`(string) Default value
+* `options` (array\[[FormOption](#formoption)]) Form item attribute specific to `select` or `radio`, defines dropdown content
+* `placeholder`(object) Form item attribute specific to `text-input`, form item placeholder
+ * `en_US`(string) English
+ * `zh_Hans` (string) \[optional] Chinese
+* `max_length` (int) Form item attribute specific to `text-input`, defines maximum input length, 0 means no limit.
+* `show_on` (array\[[FormShowOnObject](#formshowonobject)]) Display when other form item values meet conditions, empty means always display.
+
+#### FormType
+
+* `text-input` Text input component
+* `secret-input` Password input component
+* `select` Single-select dropdown
+* `radio` Radio component
+* `switch` Switch component, only supports `true` and `false`
+
+#### FormOption
+
+* `label` (object) Label
+ * `en_US`(string) English
+ * `zh_Hans`(string) \[optional] Chinese
+* `value` (string) Dropdown option value
+* `show_on` (array\[[FormShowOnObject](#formshowonobject)]) Display when other form item values meet conditions, empty means always display.
+
+#### FormShowOnObject
+
+* `variable` (string) Other form item variable name
+* `value` (string) Other form item variable value
+
+## Related Resources
+
+- [Model Architecture Details](/plugin_dev_en/0412-model-schema.en) - Deep dive into the architecture specifications of model plugins
+- [Quickly Integrate a New Model](/plugin_dev_en/0211-getting-started-new-model.en) - Learn how to apply these rules to add new models
+- [General Specifications](/plugin_dev_en/0411-general-specifications.en) - Understand the configuration of plugin manifest files
+- [Create a New Model Provider](/plugin_dev_en/0222-creating-new-model-provider.en) - Develop brand new model provider plugins
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ Help improve our documentation by contributing directly
+
+
+ Found an error or have suggestions? Let us know
+
+
diff --git a/plugin_dev_en/0411-model-plugin-introduction.en.mdx b/plugin_dev_en/0411-model-plugin-introduction.en.mdx
new file mode 100644
index 00000000..49c211d7
--- /dev/null
+++ b/plugin_dev_en/0411-model-plugin-introduction.en.mdx
@@ -0,0 +1,108 @@
+---
+dimensions:
+ type:
+ primary: reference
+ detail: core
+ level: beginner
+standard_title: Model Plugin Introduction
+language: en
+title: Model Plugins
+description: Introduction to the basic concepts and structure of model plugins. Model
+ plugins allow Dify to call various models from different providers (such as OpenAI,
+ Anthropic, Google, etc.), including large language models (LLMs), text embeddings,
+ speech-to-text, and other types.
+---
+
+Model plugins enable the Dify platform to call all LLMs from a specific model provider. For example, after installing the OpenAI model plugin, the Dify platform can call models provided by OpenAI such as `GPT-4`, `GPT-4o-2024-05-13`, etc.
+
+## Model Plugin Structure
+
+To facilitate understanding the concepts involved in developing model plugins, here is a brief introduction to the structure within model plugins:
+
+- **Model Provider**: Companies that develop large models, such as **OpenAI, Anthropic, Google**, etc.
+- **Model Categories**: Depending on the model provider, there are categories like Large Language Models (LLM), Text Embedding models, Speech-to-Text models, etc.
+- **Specific Models**: `claude-3-5-sonnet`, `gpt-4-turbo`, etc.
+
+Code hierarchy structure in plugin projects:
+
+```bash
+- Model Provider
+ - Model Category
+ - Specific Models
+```
+
+Taking **Anthropic** as an example, the model plugin structure looks like this:
+
+```bash
+- Anthropic
+ - llm
+ claude-3-5-sonnet-20240620
+ claude-3-haiku-20240307
+ claude-3-opus-20240229
+ claude-3-sonnet-20240229
+ claude-instant-1.2
+ claude-instant-1
+```
+
+Taking OpenAI as an example, since it supports multiple model types, there are multiple model categories, structured as follows:
+
+```bash
+├── models
+│ ├── llm
+│ │ ├── chatgpt-4o-latest
+│ │ ├── gpt-3.5-turbo
+│ │ ├── gpt-4-0125-preview
+│ │ ├── gpt-4-turbo
+│ │ ├── gpt-4o
+│ │ ├── llm
+│ │ ├── o1-preview
+│ │ └── text-davinci-003
+│ ├── moderation
+│ │ ├── moderation
+│ │ └── text-moderation-stable
+│ ├── speech2text
+│ │ ├── speech2text
+│ │ └── whisper-1
+│ ├── text_embedding
+│ │ ├── text-embedding-3-large
+│ │ └── text_embedding
+│ └── tts
+│ ├── tts-1-hd
+│ ├── tts-1
+│ └── tts
+```
+
+## Model Configuration
+
+Model plugins define model behavior and properties through configuration files. For detailed model design rules and configuration formats, please refer to the [Model Design Rules](/plugin_dev_en/0411-model-designing-rules.en) document and [Model Schema](/plugin_dev_en/0412-model-schema.en) specifications.
+
+## Further Reading
+
+- [Quick Integration of a New Model](/plugin_dev_en/0211-getting-started-new-model.en) - Learn how to add new models for already supported providers
+- [Model Design Rules](/plugin_dev_en/0411-model-designing-rules.en) - Learn detailed specifications for model configuration
+- [Model Schema](/plugin_dev_en/0412-model-schema.en) - Gain a deeper understanding of model plugin architecture
+- [General Specification Definitions](/plugin_dev_en/0411-general-specifications.en) - Learn how to define plugin metadata
+- [Basic Concepts of Plugin Development](/plugin_dev_en/0111-getting-started-dify-plugin.en) - Return to the plugin development getting started guide
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ Help improve our documentation by contributing directly
+
+
+ Found an error or have suggestions? Let us know
+
+
diff --git a/plugin_dev_en/0411-persistent-storage-kv.en.mdx b/plugin_dev_en/0411-persistent-storage-kv.en.mdx
new file mode 100644
index 00000000..b27cf4f4
--- /dev/null
+++ b/plugin_dev_en/0411-persistent-storage-kv.en.mdx
@@ -0,0 +1,88 @@
+---
+dimensions:
+ type:
+ primary: reference
+ detail: core
+ level: beginner
+standard_title: Persistent Storage KV
+language: en
+title: Persistent Storage
+description: This document introduces the persistent storage functionality in Dify
+ plugins, detailing how to use the KV database in plugins to store, retrieve, and
+ delete data. This feature enables plugins to persistently store data within the
+ same Workspace, meeting the needs for data preservation across sessions.
+---
+
+When examining Tools and Endpoints in plugins individually, it's not difficult to see that in most cases, they can only complete single-round interactions: request, return data, and the task ends.
+
+If there is data that needs to be stored long-term, such as implementing persistent memory, the plugin needs to have persistent storage capabilities. **The persistent storage mechanism allows plugins to have the ability to persistently store data within the same Workspace**. Currently, a KV database is provided to meet storage needs, and more flexible and powerful storage interfaces may be introduced in the future based on actual usage.
+
+### Storing Keys
+
+#### **Entry Point**
+
+```python
+ self.session.storage
+```
+
+#### **Interface**
+
+```python
+ def set(self, key: str, val: bytes) -> None:
+ pass
+```
+
+Note that what is passed in is bytes, so you can actually store files in it.
+
+### Getting Keys
+
+#### **Entry Point**
+
+```python
+ self.session.storage
+```
+
+#### **Interface**
+
+```python
+ def get(self, key: str) -> bytes:
+ pass
+```
+
+### Deleting Keys
+
+#### **Entry Point**
+
+```python
+ self.session.storage
+```
+
+#### **Interface**
+
+```python
+ def delete(self, key: str) -> None:
+ pass
+```
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ Help improve our documentation by contributing directly
+
+
+ Found an error or have suggestions? Let us know
+
+
diff --git a/plugin_dev_en/0411-plugin-info-by-manifest.en.mdx b/plugin_dev_en/0411-plugin-info-by-manifest.en.mdx
new file mode 100644
index 00000000..ded7673e
--- /dev/null
+++ b/plugin_dev_en/0411-plugin-info-by-manifest.en.mdx
@@ -0,0 +1,146 @@
+---
+dimensions:
+ type:
+ primary: reference
+ detail: core
+ level: beginner
+standard_title: Plugin info by Manifest
+language: en
+title: Manifest
+description: Authors Yeuoly, Allen. This document details the Manifest file in Dify
+ plugins, a YAML file defining basic plugin information. It includes complete code
+ examples and detailed structure explanations, covering configurations like plugin
+ version, type, author, name, resource usage, permission requests, feature definitions,
+ and runtime.
+---
+
+# Manifest
+
+The Manifest is a YAML-compliant file that defines the most basic information of a **plugin**, including but not limited to the plugin name, author, included tools, models, etc. For the overall architecture of the plugin, please refer to [Basic Concepts of Plugin Development](/plugin_dev_en/0111-getting-started-dify-plugin.en) and [Developer Cheatsheet](/plugin_dev_en/0131-cheatsheet.en).
+
+If the format of this file is incorrect, the parsing and packaging process of the plugin will fail.
+
+### Code Example
+
+Below is a simple example of a Manifest file. The meaning and function of each data item will be explained later.
+
+For reference code of other plugins, please refer to the [GitHub code repository](https://github.com/langgenius/dify-official-plugins/blob/main/tools/google/manifest.yaml).
+
+```yaml
+version: 0.0.1
+type: "plugin"
+author: "Yeuoly"
+name: "neko"
+label:
+ en_US: "Neko"
+created_at: "2024-07-12T08:03:44.658609186Z"
+icon: "icon.svg"
+resource:
+ memory: 1048576
+ permission:
+ tool:
+ enabled: true
+ model:
+ enabled: true
+ llm: true
+ endpoint:
+ enabled: true
+ app:
+ enabled: true
+ storage:
+ enabled: true
+ size: 1048576
+plugins:
+ endpoints:
+ - "provider/neko.yaml"
+meta:
+ version: 0.0.1
+ arch:
+ - "amd64"
+ - "arm64"
+ runner:
+ language: "python"
+ version: "3.10"
+ entrypoint: "main"
+privacy: "./privacy.md"
+```
+
+### Structure
+
+* `version` (version, required): The version of the plugin.
+* `type` (type, required): Plugin type, currently only `plugin` is supported, `bundle` will be supported in the future.
+* `author` (string, required): Author, defined as the organization name in the Marketplace.
+* `label` (label, required): Multilingual name.
+* `created_at` (RFC3339, required): Creation time, required by the Marketplace not to be later than the current time.
+* `icon` (asset, required): Icon path.
+* `resource` (object): Resources to apply for.
+ * `memory` (int64): Maximum memory usage, mainly related to AWS Lambda resource application on SaaS, unit in bytes.
+ * `permission` (object): Permission application.
+ * `tool` (object): Permission for reverse invocation of tools.
+ * `enabled` (bool)
+ * `model` (object): Permission for reverse invocation of models.
+ * `enabled` (bool)
+ * `llm` (bool)
+ * `text_embedding` (bool)
+ * `rerank` (bool)
+ * `tts` (bool)
+ * `speech2text` (bool)
+ * `moderation` (bool)
+ * `node` (object): Permission for reverse invocation of nodes.
+ * `enabled` (bool)
+ * `endpoint` (object): Permission to register `endpoint`.
+ * `enabled` (bool)
+ * `app` (object): Permission for reverse invocation of `app`.
+ * `enabled` (bool)
+ * `storage` (object): Permission to apply for persistent storage.
+ * `enabled` (bool)
+ * `size` (int64): Maximum allowed persistent memory size, unit in bytes.
+* `plugins` (object, required): A list of `yaml` files for the specific capabilities extended by the plugin. Absolute path within the plugin package. For example, if you need to extend a model, you need to define a file similar to `openai.yaml`, fill in the file path here, and the file at this path must actually exist, otherwise packaging will fail.
+ * Format
+ * `tools` (list[string]): Plugin extension for [Tool](/plugin_dev_en/0222-tool-plugin.en) providers.
+ * `models` (list[string]): Plugin extension for [Model](/plugin_dev_en/0131-model-plugin-introduction.en) providers.
+ * `endpoints` (list[string]): Plugin extension for [Endpoints](/plugin_dev_en/0432-develop-a-slack-bot-plugin.en) providers. (Note: Link updated to closest available English document)
+ * `agent_strategies` (list[string]): Plugin extension for [Agent Strategy](/plugin_dev_en/9433-agent-strategy-plugin.en) providers.
+ * Restrictions
+ * Extending both tools and models simultaneously is not allowed.
+ * Having no extensions is not allowed.
+ * Extending both models and Endpoints simultaneously is not allowed.
+ * Currently, only one provider is supported for each type of extension.
+* `meta` (object)
+ * `version` (version, required): `manifest` format version, initial version `0.0.1`.
+ * `arch` (list[string], required): Supported architectures, currently only `amd64` `arm64` are supported.
+ * `runner` (object, required): Runtime configuration.
+ * `language` (string): Currently only python is supported.
+ * `version` (string): Language version, currently only `3.12` is supported.
+ * `entrypoint` (string): Program entry point, should be `main` under python.
+* `privacy` (string, optional): Optional, specifies the relative path or URL of the plugin's privacy policy file, e.g., `"./privacy.md"` or `"https://your-web/privacy"`. If you plan to list the plugin on the Dify Marketplace, **this field is required** to provide clear user data usage and privacy statements. For detailed filling guidelines, please refer to [Plugin Privacy Data Protection Guidelines](/plugin_dev_en/0312-privacy-protection-guidelines.en).
+
+## Related Resources
+
+* [Basic Concepts of Plugin Development](/plugin_dev_en/0111-getting-started-dify-plugin.en) - Comprehensive understanding of Dify plugin development.
+* [Quickly Integrate a New Model](/plugin_dev_en/0211-getting-started-new-model.en) - Learn how to add new models for existing providers.
+* [General Specifications Definition](/plugin_dev_en/0411-general-specifications.en) - Understand the common structures in plugin development.
+* [Release Overview](/plugin_dev_en/0321-release-overview.en) - Learn about the plugin release process.
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ Help improve our documentation by contributing directly
+
+
+ Found an error or have suggestions? Let us know
+
+
diff --git a/plugin_dev_en/0411-remote-debug-a-plugin.en.mdx b/plugin_dev_en/0411-remote-debug-a-plugin.en.mdx
new file mode 100644
index 00000000..64da36fb
--- /dev/null
+++ b/plugin_dev_en/0411-remote-debug-a-plugin.en.mdx
@@ -0,0 +1,59 @@
+---
+dimensions:
+ type:
+ primary: reference
+ detail: core
+ level: beginner
+standard_title: Remote Debug a Plugin
+language: en
+title: Plugin Debugging
+description: This document introduces how to use Dify's remote debugging feature to
+ test plugins. It provides detailed instructions on obtaining debugging information,
+ configuring environment variable files, starting plugin remote debugging, and verifying
+ plugin installation status. Through this method, developers can test plugins in
+ the Dify environment in real-time while developing locally.
+---
+
+After completing plugin development, the next step is to test whether the plugin can function properly. Dify provides a convenient remote debugging method to help you quickly verify plugin functionality in a test environment.
+
+Go to the ["Plugin Management"](https://cloud.dify.ai/plugins) page to obtain the remote server address and debugging Key.
+
+
+
+Return to the plugin project, copy the `.env.example` file and rename it to `.env`, then fill in the remote server address and debugging Key information you obtained.
+
+`.env` file:
+
+```bash
+INSTALL_METHOD=remote
+REMOTE_INSTALL_HOST=remote
+REMOTE_INSTALL_PORT=5003
+REMOTE_INSTALL_KEY=****-****-****-****-****
+```
+
+Run the `python -m main` command to start the plugin. On the plugins page, you can see that the plugin has been installed in the Workspace, and other members of the team can also access the plugin.
+
+
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ Help improve our documentation by contributing directly
+
+
+ Found an error or have suggestions? Let us know
+
+
diff --git a/plugin_dev_en/0411-tool.en.mdx b/plugin_dev_en/0411-tool.en.mdx
new file mode 100644
index 00000000..9f81a006
--- /dev/null
+++ b/plugin_dev_en/0411-tool.en.mdx
@@ -0,0 +1,147 @@
+---
+dimensions:
+ type:
+ primary: reference
+ detail: core
+ level: beginner
+standard_title: Tool
+language: en
+title: Tool Return
+description: This document provides a detailed introduction to the data structure
+ and usage of Tools in Dify plugins. It covers how to return different types of messages
+ (image URLs, links, text, files, JSON), how to create variable and streaming variable
+ messages, and how to define tool output variable schemas for reference in workflows.
+---
+
+Before reading the detailed interface documentation, please make sure you have a general understanding of the tool integration process for Dify plugins.
+
+### Data Structure
+
+#### Message Return
+
+Dify supports various message types such as `text`, `links`, `images`, `file BLOBs`, and `JSON`. You can return different types of messages through the following interfaces.
+
+By default, a tool's output in a `workflow` includes three fixed variables: `files`, `text`, and `json`. You can use the methods below to return data for these three variables.
+
+For example, you can use `create_image_message` to return an image, but tools also support custom output variables, making it more convenient to reference these variables in a `workflow`.
+
+#### **Image URL**
+
+Simply pass the image URL, and Dify will automatically download the image through the link and return it to the user.
+
+```python
+ def create_image_message(self, image: str) -> ToolInvokeMessage:
+ pass
+```
+
+#### **Link**
+
+If you need to return a link, use the following interface.
+
+```python
+ def create_link_message(self, link: str) -> ToolInvokeMessage:
+ pass
+```
+
+#### **Text**
+
+If you need to return a text message, use the following interface.
+
+```python
+ def create_text_message(self, text: str) -> ToolInvokeMessage:
+ pass
+```
+
+**File**
+
+If you need to return the raw data of a file, such as images, audio, video, PPT, Word, Excel, etc., you can use the following interface.
+
+* `blob` The raw data of the file, in bytes type.
+* `meta` The metadata of the file. If developers need a specific file type, please specify `mime_type`, otherwise Dify will use `octet/stream` as the default type.
+
+```python
+ def create_blob_message(self, blob: bytes, meta: dict = None) -> ToolInvokeMessage:
+ pass
+```
+
+#### **JSON**
+
+If you need to return a formatted JSON, you can use the following interface. This is typically used for data transmission between nodes in a workflow. In agent mode, most large models can also read and understand JSON.
+
+* `object` A Python dictionary object that will be automatically serialized to JSON.
+
+```python
+ def create_json_message(self, json: dict) -> ToolInvokeMessage:
+ pass
+```
+
+#### **Variable**
+
+For non-streaming output variables, you can use the following interface to return them. If you create multiple instances, the latter will override the former.
+
+```python
+ def create_variable_message(self, variable_name: str, variable_value: Any) -> ToolInvokeMessage:
+ pass
+```
+
+#### **Streaming Variable**
+
+If you want to output text with a "typewriter" effect, you can use streaming variables to output text. If you use an `answer` node in a `chatflow` application and reference this variable, the text will be output with a "typewriter" effect. However, currently this method only supports string type data.
+
+```python
+ def create_stream_variable_message(
+ self, variable_name: str, variable_value: str
+ ) -> ToolInvokeMessage:
+```
+
+#### Returning Custom Variables
+
+If you want to reference a tool's output variables in a `workflow` application, it's necessary to define in advance which variables might be output. Dify plugins support output variable definitions in [`json_schema`](https://json-schema.org/) format. Here's a simple example:
+
+```yaml
+identity:
+ author: author
+ name: tool
+ label:
+ en_US: label
+ zh_Hans: 标签
+ ja_JP: レベル
+ pt_BR: etiqueta
+description:
+ human:
+ en_US: description
+ zh_Hans: 描述
+ ja_JP: 説明
+ pt_BR: descrição
+ llm: description
+output_schema:
+ type: object
+ properties:
+ name:
+ type: string
+```
+
+The example code above defines a simple tool and specifies an `output_schema` for it, which includes a `name` field that can be referenced in a `workflow`. However, please note that you still need to return a variable in the tool's implementation code to actually use it, otherwise you will get a `None` return result.
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ Help improve our documentation by contributing directly
+
+
+ Found an error or have suggestions? Let us know
+
+
diff --git a/plugin_dev_en/0412-model-schema.en.mdx b/plugin_dev_en/0412-model-schema.en.mdx
new file mode 100644
index 00000000..ec1f6539
--- /dev/null
+++ b/plugin_dev_en/0412-model-schema.en.mdx
@@ -0,0 +1,713 @@
+---
+dimensions:
+ type:
+ primary: reference
+ detail: core
+ level: intermediate
+standard_title: Model Schema
+language: en
+title: Model API Interface
+description: This document provides detailed interface specifications required for
+ Dify model plugin development, including model provider implementation, interface
+ definitions for five model types (LLM, TextEmbedding, Rerank, Speech2text, Text2speech),
+ and complete specifications for related data structures such as PromptMessage and
+ LLMResult. The document serves as a development reference for developers implementing
+ various model integrations.
+---
+
+This section introduces the interface methods and parameter descriptions that providers and each model type need to implement. Before developing a model plugin, you may first need to read [Model Design Rules](/plugin_dev_en/0411-model-designing-rules.en) and [Model Plugin Introduction](/plugin_dev_en/0131-model-plugin-introduction.en).
+
+### Model Provider
+
+Inherit the `__base.model_provider.ModelProvider` base class and implement the following interface:
+
+```python
+def validate_provider_credentials(self, credentials: dict) -> None:
+ """
+ Validate provider credentials
+ You can choose any validate_credentials method of model type or implement validate method by yourself,
+ such as: get model list api
+
+ if validate failed, raise exception
+
+ :param credentials: provider credentials, credentials form defined in `provider_credential_schema`.
+ """
+```
+
+* `credentials` (object) Credential information
+
+The credential parameters are defined by the provider YAML configuration file's `provider_credential_schema`, passed in as `api_key`, etc. If validation fails, please throw a `errors.validate.CredentialsValidateFailedError` error. **Note: Predefined models need to fully implement this interface, while custom model providers only need to implement it simply as follows:**
+
+```python
+class XinferenceProvider(Provider):
+ def validate_provider_credentials(self, credentials: dict) -> None:
+ pass
+```
+
+### Models
+
+Models are divided into 5 different types, with different base classes to inherit from and different methods to implement for each type.
+
+#### Common Interfaces
+
+All models need to implement the following 2 methods consistently:
+
+* Model credential validation
+
+Similar to provider credential validation, this validates individual models.
+
+```python
+def validate_credentials(self, model: str, credentials: dict) -> None:
+ """
+ Validate model credentials
+
+ :param model: model name
+ :param credentials: model credentials
+ :return:
+ """
+```
+
+Parameters:
+
+* `model` (string) Model name
+* `credentials` (object) Credential information
+
+The credential parameters are defined by the provider YAML configuration file's `provider_credential_schema` or `model_credential_schema`, passed in as `api_key`, etc. If validation fails, please throw a `errors.validate.CredentialsValidateFailedError` error.
+
+* Invocation error mapping table
+
+When a model invocation exception occurs, it needs to be mapped to a specified `InvokeError` type in Runtime, which helps Dify handle different errors differently. Runtime Errors:
+
+* `InvokeConnectionError` Connection error during invocation
+* `InvokeServerUnavailableError` Service provider unavailable
+* `InvokeRateLimitError` Rate limit reached
+* `InvokeAuthorizationError` Authentication failed
+* `InvokeBadRequestError` Incorrect parameters passed
+
+```python
+@property
+def _invoke_error_mapping(self) -> dict[type[InvokeError], list[type[Exception]]]:
+ """
+ Map model invoke error to unified error
+ The key is the error type thrown to the caller
+ The value is the error type thrown by the model,
+ which needs to be converted into a unified error type for the caller.
+
+ :return: Invoke error mapping
+ """
+```
+
+You can also directly throw corresponding Errors and define them as follows, so that in subsequent calls you can directly throw exceptions like `InvokeConnectionError`.
+
+#### LLM
+
+Inherit the `__base.large_language_model.LargeLanguageModel` base class and implement the following interface:
+
+* LLM Invocation
+
+Implement the core method for LLM invocation, which can support both streaming and synchronous responses.
+
+```python
+def _invoke(self, model: str, credentials: dict,
+ prompt_messages: list[PromptMessage], model_parameters: dict,
+ tools: Optional[list[PromptMessageTool]] = None, stop: Optional[list[str]] = None,
+ stream: bool = True, user: Optional[str] = None) \
+ -> Union[LLMResult, Generator]:
+ """
+ Invoke large language model
+
+ :param model: model name
+ :param credentials: model credentials
+ :param prompt_messages: prompt messages
+ :param model_parameters: model parameters
+ :param tools: tools for tool calling
+ :param stop: stop words
+ :param stream: is stream response
+ :param user: unique user id
+ :return: full response or stream response chunk generator result
+ """
+```
+
+* Parameters:
+ * `model` (string) Model name
+ * `credentials` (object) Credential information
+
+The credential parameters are defined by the provider YAML configuration file's `provider_credential_schema` or `model_credential_schema`, passed in as `api_key`, etc.
+
+* `prompt_messages` (array\[[PromptMessage](#promptmessage)]) Prompt list
+
+If the model is of `Completion` type, the list only needs to include one [UserPromptMessage](#userpromptmessage) element; if the model is of `Chat` type, different messages need to be passed in as a list of [SystemPromptMessage](#systempromptmessage), [UserPromptMessage](#userpromptmessage), [AssistantPromptMessage](#assistantpromptmessage), [ToolPromptMessage](#toolpromptmessage) elements
+
+* `model_parameters` (object) Model parameters defined by the model YAML configuration's `parameter_rules`.
+
+* `tools` (array\[[PromptMessageTool](#promptmessagetool)]) \[optional] Tool list, equivalent to `function` in `function calling`. This is the tool list passed to tool calling.
+
+* `stop` (array\[string]) \[optional] Stop sequence. The model response will stop output before the string defined in the stop sequence.
+
+* `stream` (bool) Whether to stream output, default is True
+For streaming output, it returns Generator\[[LLMResultChunk](#llmresultchunk)], for non-streaming output, it returns [LLMResult](#llmresult).
+
+* `user` (string) \[optional] A unique identifier for the user that can help the provider monitor and detect abuse.
+
+* Return Value
+
+For streaming output, it returns Generator\[[LLMResultChunk](#llmresultchunk)], for non-streaming output, it returns [LLMResult](#llmresult).
+
+* Pre-calculate input tokens
+
+If the model does not provide a pre-calculation tokens interface, you can directly return 0.
+
+```python
+def get_num_tokens(self, model: str, credentials: dict, prompt_messages: list[PromptMessage],
+ tools: Optional[list[PromptMessageTool]] = None) -> int:
+ """
+ Get number of tokens for given prompt messages
+
+ :param model: model name
+ :param credentials: model credentials
+ :param prompt_messages: prompt messages
+ :param tools: tools for tool calling
+ :return:
+ """
+```
+
+Parameter explanations are the same as in `LLM Invocation` above. This interface needs to calculate based on the appropriate `tokenizer` for the corresponding `model`. If the corresponding model does not provide a `tokenizer`, you can use the `_get_num_tokens_by_gpt2(text: str)` method in the `AIModel` base class for calculation.
+
+* Get custom model rules [optional]
+
+```python
+def get_customizable_model_schema(self, model: str, credentials: dict) -> Optional[AIModelEntity]:
+ """
+ Get customizable model schema
+
+ :param model: model name
+ :param credentials: model credentials
+ :return: model schema
+ """
+```
+
+When a provider supports adding custom LLMs, this method can be implemented to allow custom models to obtain model rules. By default, it returns None.
+
+For most fine-tuned models under the `OpenAI` provider, the base model can be obtained through the fine-tuned model name, such as `gpt-3.5-turbo-1106`, and then return the predefined parameter rules of the base model. Refer to the specific implementation of [OpenAI](https://github.com/langgenius/dify-official-plugins/tree/main/models/openai).
+
+#### TextEmbedding
+
+Inherit the `__base.text_embedding_model.TextEmbeddingModel` base class and implement the following interface:
+
+* Embedding Invocation
+
+```python
+def _invoke(self, model: str, credentials: dict,
+ texts: list[str], user: Optional[str] = None) \
+ -> TextEmbeddingResult:
+ """
+ Invoke large language model
+
+ :param model: model name
+ :param credentials: model credentials
+ :param texts: texts to embed
+ :param user: unique user id
+ :return: embeddings result
+ """
+```
+
+* Parameters:
+
+* `model` (string) Model name
+* `credentials` (object) Credential information
+
+The credential parameters are defined by the provider YAML configuration file's `provider_credential_schema` or `model_credential_schema`, passed in as `api_key`, etc.
+
+* `texts` (array\[string]) Text list, can be processed in batch
+* `user` (string) \[optional] A unique identifier for the user
+Can help the provider monitor and detect abuse.
+
+* Return:
+
+[TextEmbeddingResult](#textembeddingresult) entity.
+
+* Pre-calculate tokens
+
+```python
+def get_num_tokens(self, model: str, credentials: dict, texts: list[str]) -> int:
+ """
+ Get number of tokens for given prompt messages
+
+ :param model: model name
+ :param credentials: model credentials
+ :param texts: texts to embed
+ :return:
+ """
+```
+
+Parameter explanations can be found in the `Embedding Invocation` section above.
+
+Similar to the `LargeLanguageModel` above, this interface needs to calculate based on the appropriate `tokenizer` for the corresponding `model`. If the corresponding model does not provide a `tokenizer`, you can use the `_get_num_tokens_by_gpt2(text: str)` method in the `AIModel` base class for calculation.
+
+#### Rerank
+
+Inherit the `__base.rerank_model.RerankModel` base class and implement the following interface:
+
+* Rerank Invocation
+
+```python
+def _invoke(self, model: str, credentials: dict,
+ query: str, docs: list[str], score_threshold: Optional[float] = None, top_n: Optional[int] = None,
+ user: Optional[str] = None) \
+ -> RerankResult:
+ """
+ Invoke rerank model
+
+ :param model: model name
+ :param credentials: model credentials
+ :param query: search query
+ :param docs: docs for reranking
+ :param score_threshold: score threshold
+ :param top_n: top n
+ :param user: unique user id
+ :return: rerank result
+ """
+```
+
+* Parameters:
+
+* `model` (string) Model name
+* `credentials` (object) Credential information
+The credential parameters are defined by the provider YAML configuration file's `provider_credential_schema` or `model_credential_schema`, passed in as `api_key`, etc.
+* `query` (string) Query request content
+* `docs` (array\[string]) List of segments that need to be reranked
+* `score_threshold` (float) \[optional] Score threshold
+* `top_n` (int) \[optional] Take the top n segments
+* `user` (string) \[optional] A unique identifier for the user
+Can help the provider monitor and detect abuse.
+
+* Return:
+
+[RerankResult](#rerankresult) entity.
+
+#### Speech2text
+
+Inherit the `__base.speech2text_model.Speech2TextModel` base class and implement the following interface:
+
+* Invoke
+
+```python
+def _invoke(self, model: str, credentials: dict,
+ file: IO[bytes], user: Optional[str] = None) \
+ -> str:
+ """
+ Invoke large language model
+
+ :param model: model name
+ :param credentials: model credentials
+ :param file: audio file
+ :param user: unique user id
+ :return: text for given audio file
+ """
+```
+
+* Parameters:
+
+* `model` (string) Model name
+* `credentials` (object) Credential information
+The credential parameters are defined by the provider YAML configuration file's `provider_credential_schema` or `model_credential_schema`, passed in as `api_key`, etc.
+* `file` (File) File stream
+* `user` (string) \[optional] A unique identifier for the user
+Can help the provider monitor and detect abuse.
+
+* Return:
+
+String after speech conversion.
+
+#### Text2speech
+
+Inherit the `__base.text2speech_model.Text2SpeechModel` base class and implement the following interface:
+
+* Invoke
+
+```python
+def _invoke(self, model: str, credentials: dict, content_text: str, streaming: bool, user: Optional[str] = None):
+ """
+ Invoke large language model
+
+ :param model: model name
+ :param credentials: model credentials
+ :param content_text: text content to be translated
+ :param streaming: output is streaming
+ :param user: unique user id
+ :return: translated audio file
+ """
+```
+
+* Parameters:
+
+* `model` (string) Model name
+* `credentials` (object) Credential information
+The credential parameters are defined by the provider YAML configuration file's `provider_credential_schema` or `model_credential_schema`, passed in as `api_key`, etc.
+* `content_text` (string) Text content to be converted
+* `streaming` (bool) Whether to stream output
+* `user` (string) \[optional] A unique identifier for the user
+Can help the provider monitor and detect abuse.
+
+* Return:
+
+Audio stream after text conversion.
+
+
+#### Moderation
+
+Inherit the `__base.moderation_model.ModerationModel` base class and implement the following interface:
+
+* Invoke
+
+```python
+def _invoke(self, model: str, credentials: dict,
+ text: str, user: Optional[str] = None) \
+ -> bool:
+ """
+ Invoke large language model
+
+ :param model: model name
+ :param credentials: model credentials
+ :param text: text to moderate
+ :param user: unique user id
+ :return: false if text is safe, true otherwise
+ """
+```
+
+* Parameters:
+
+* `model` (string) Model name
+* `credentials` (object) Credential information
+The credential parameters are defined by the provider YAML configuration file's `provider_credential_schema` or `model_credential_schema`, passed in as `api_key`, etc.
+* `text` (string) Text content
+* `user` (string) \[optional] A unique identifier for the user
+Can help the provider monitor and detect abuse.
+
+* Return:
+
+False indicates the input text is safe, True indicates it is not.
+
+### Entities
+
+#### PromptMessageRole
+
+Message role
+
+```python
+class PromptMessageRole(Enum):
+ """
+ Enum class for prompt message.
+ """
+ SYSTEM = "system"
+ USER = "user"
+ ASSISTANT = "assistant"
+ TOOL = "tool"
+```
+
+#### PromptMessageContentType
+
+Message content type, divided into plain text and images.
+
+```python
+class PromptMessageContentType(Enum):
+ """
+ Enum class for prompt message content type.
+ """
+ TEXT = 'text'
+ IMAGE = 'image'
+```
+
+#### PromptMessageContent
+
+Message content base class, used only for parameter declaration, cannot be initialized.
+
+```python
+class PromptMessageContent(BaseModel):
+ """
+ Model class for prompt message content.
+ """
+ type: PromptMessageContentType
+ data: str # Content data
+```
+
+Currently supports two types: text and images, and can support text and multiple images simultaneously.
+You need to initialize `TextPromptMessageContent` and `ImagePromptMessageContent` separately.
+
+#### TextPromptMessageContent
+
+```python
+class TextPromptMessageContent(PromptMessageContent):
+ """
+ Model class for text prompt message content.
+ """
+ type: PromptMessageContentType = PromptMessageContentType.TEXT
+```
+
+When passing in text and images, text needs to be constructed as this entity as part of the `content` list.
+
+#### ImagePromptMessageContent
+
+```python
+class ImagePromptMessageContent(PromptMessageContent):
+ """
+ Model class for image prompt message content.
+ """
+ class DETAIL(Enum):
+ LOW = 'low'
+ HIGH = 'high'
+
+ type: PromptMessageContentType = PromptMessageContentType.IMAGE
+ detail: DETAIL = DETAIL.LOW # Resolution
+```
+
+When passing in text and images, images need to be constructed as this entity as part of the `content` list.
+`data` can be a `url` or an image `base64` encoded string.
+
+#### PromptMessage
+
+Base class for all Role message bodies, used only for parameter declaration, cannot be initialized.
+
+```python
+class PromptMessage(ABC, BaseModel):
+ """
+ Model class for prompt message.
+ """
+ role: PromptMessageRole # Message role
+ content: Optional[str | list[PromptMessageContent]] = None # Supports two types: string and content list. The content list is for multimodal needs, see PromptMessageContent for details.
+ name: Optional[str] = None # Name, optional.
+```
+
+#### UserPromptMessage
+
+UserMessage message body, represents user messages.
+
+```python
+class UserPromptMessage(PromptMessage):
+ """
+ Model class for user prompt message.
+ """
+ role: PromptMessageRole = PromptMessageRole.USER
+```
+
+#### AssistantPromptMessage
+
+Represents model response messages, typically used for `few-shots` or chat history input.
+
+```python
+class AssistantPromptMessage(PromptMessage):
+ """
+ Model class for assistant prompt message.
+ """
+ class ToolCall(BaseModel):
+ """
+ Model class for assistant prompt message tool call.
+ """
+ class ToolCallFunction(BaseModel):
+ """
+ Model class for assistant prompt message tool call function.
+ """
+ name: str # Tool name
+ arguments: str # Tool parameters
+
+ id: str # Tool ID, only effective for OpenAI tool call, a unique ID for tool invocation, the same tool can be called multiple times
+ type: str # Default is function
+ function: ToolCallFunction # Tool call information
+
+ role: PromptMessageRole = PromptMessageRole.ASSISTANT
+ tool_calls: list[ToolCall] = [] # Model's tool call results (only returned when tools are passed in and the model decides to call them)
+```
+
+Here `tool_calls` is the list of `tool call` returned by the model after passing in `tools` to the model.
+
+#### SystemPromptMessage
+
+Represents system messages, typically used to set system instructions for the model.
+
+```python
+class SystemPromptMessage(PromptMessage):
+ """
+ Model class for system prompt message.
+ """
+ role: PromptMessageRole = PromptMessageRole.SYSTEM
+```
+
+#### ToolPromptMessage
+
+Represents tool messages, used to pass results to the model for next-step planning after a tool has been executed.
+
+```python
+class ToolPromptMessage(PromptMessage):
+ """
+ Model class for tool prompt message.
+ """
+ role: PromptMessageRole = PromptMessageRole.TOOL
+ tool_call_id: str # Tool call ID, if OpenAI tool call is not supported, you can also pass in the tool name
+```
+
+The base class's `content` passes in the tool execution result.
+
+#### PromptMessageTool
+
+```python
+class PromptMessageTool(BaseModel):
+ """
+ Model class for prompt message tool.
+ """
+ name: str # Tool name
+ description: str # Tool description
+ parameters: dict # Tool parameters dict
+
+```
+
+***
+
+#### LLMResult
+
+```python
+class LLMResult(BaseModel):
+ """
+ Model class for llm result.
+ """
+ model: str # Actually used model
+ prompt_messages: list[PromptMessage] # Prompt message list
+ message: AssistantPromptMessage # Reply message
+ usage: LLMUsage # Tokens used and cost information
+ system_fingerprint: Optional[str] = None # Request fingerprint, refer to OpenAI parameter definition
+```
+
+#### LLMResultChunkDelta
+
+Delta entity within each iteration in streaming response
+
+```python
+class LLMResultChunkDelta(BaseModel):
+ """
+ Model class for llm result chunk delta.
+ """
+ index: int # Sequence number
+ message: AssistantPromptMessage # Reply message
+ usage: Optional[LLMUsage] = None # Tokens used and cost information, only returned in the last message
+ finish_reason: Optional[str] = None # Completion reason, only returned in the last message
+```
+
+#### LLMResultChunk
+
+Iteration entity in streaming response
+
+```python
+class LLMResultChunk(BaseModel):
+ """
+ Model class for llm result chunk.
+ """
+ model: str # Actually used model
+ prompt_messages: list[PromptMessage] # Prompt message list
+ system_fingerprint: Optional[str] = None # Request fingerprint, refer to OpenAI parameter definition
+ delta: LLMResultChunkDelta # Changes in content for each iteration
+```
+
+#### LLMUsage
+
+```python
+class LLMUsage(ModelUsage):
+ """
+ Model class for llm usage.
+ """
+ prompt_tokens: int # Tokens used by prompt
+ prompt_unit_price: Decimal # Prompt unit price
+ prompt_price_unit: Decimal # Prompt price unit, i.e., unit price based on how many tokens
+ prompt_price: Decimal # Prompt cost
+ completion_tokens: int # Tokens used by completion
+ completion_unit_price: Decimal # Completion unit price
+ completion_price_unit: Decimal # Completion price unit, i.e., unit price based on how many tokens
+ completion_price: Decimal # Completion cost
+ total_tokens: int # Total tokens used
+ total_price: Decimal # Total cost
+ currency: str # Currency unit
+ latency: float # Request time (s)
+```
+
+***
+
+#### TextEmbeddingResult
+
+```python
+class TextEmbeddingResult(BaseModel):
+ """
+ Model class for text embedding result.
+ """
+ model: str # Actually used model
+ embeddings: list[list[float]] # Embedding vector list, corresponding to the input texts list
+ usage: EmbeddingUsage # Usage information
+```
+
+#### EmbeddingUsage
+
+```python
+class EmbeddingUsage(ModelUsage):
+ """
+ Model class for embedding usage.
+ """
+ tokens: int # Tokens used
+ total_tokens: int # Total tokens used
+ unit_price: Decimal # Unit price
+ price_unit: Decimal # Price unit, i.e., unit price based on how many tokens
+ total_price: Decimal # Total cost
+ currency: str # Currency unit
+ latency: float # Request time (s)
+```
+
+***
+
+#### RerankResult
+
+```python
+class RerankResult(BaseModel):
+ """
+ Model class for rerank result.
+ """
+ model: str # Actually used model
+ docs: list[RerankDocument] # List of reranked segments
+```
+
+#### RerankDocument
+
+```python
+class RerankDocument(BaseModel):
+ """
+ Model class for rerank document.
+ """
+ index: int # Original sequence number
+ text: str # Segment text content
+ score: float # Score
+```
+
+## Related Resources
+
+- [Model Design Rules](/plugin_dev_en/0411-model-designing-rules.en) - Understand the standards for model configuration
+- [Model Plugin Introduction](/plugin_dev_en/0131-model-plugin-introduction.en) - Quickly understand the basic concepts of model plugins
+- [Quickly Integrate a New Model](/plugin_dev_en/0211-getting-started-new-model.en) - Learn how to add new models to existing providers
+- [Create a New Model Provider](/plugin_dev_en/0222-creating-new-model-provider.en) - Learn how to develop brand new model providers
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ Help improve our documentation by contributing directly
+
+
+ Found an error or have suggestions? Let us know
+
+
diff --git a/plugin_dev_en/0432-develop-a-slack-bot-plugin.en.mdx b/plugin_dev_en/0432-develop-a-slack-bot-plugin.en.mdx
new file mode 100644
index 00000000..e8bbd207
--- /dev/null
+++ b/plugin_dev_en/0432-develop-a-slack-bot-plugin.en.mdx
@@ -0,0 +1,373 @@
+---
+dimensions:
+ type:
+ primary: reference
+ detail: examples
+ level: intermediate
+standard_title: Develop A Slack Bot Plugin
+language: en
+title: Develop A Slack Bot Plugin
+description: This guide provides a complete walkthrough for developing a Slack Bot
+ plugin, covering project initialization, configuration form editing, feature implementation,
+ debugging, endpoint setup, verification, and packaging. You'll need the Dify plugin
+ scaffolding tool and a pre-created Slack App to build an AI-powered chatbot on Slack.
+---
+
+**What You’ll Learn:**
+
+Gain a solid understanding of how to build a Slack Bot that’s powered by AI—one that can respond to user questions right inside Slack. If you haven't developed a plugin before, we recommend reading the [Plugin Development Quick Start Guide](/plugin_dev_en/0211-getting-started-dify-tool.en) first.
+
+### Project Background
+
+The Dify plugin ecosystem focuses on making integrations simpler and more accessible. In this guide, we’ll use Slack as an example, walking you through the process of developing a Slack Bot plugin. This allows your team to chat directly with an LLM within Slack, significantly improving how efficiently they can use AI.
+
+Slack is an open, real-time communication platform with a robust API. Among its features is a webhook-based event system, which is quite straightforward to develop on. We’ll leverage this system to create a Slack Bot plugin, illustrated in the diagram below:
+
+
+
+> To avoid confusion, the following concepts are explained:
+>
+> * **Slack Bot** A chatbot on the Slack platform, acting as a virtual user you can interact with in real-time.
+> * **Slack Bot Plugin** A plugin in the Dify Marketplace that connects a Dify application with Slack. This guide focuses on how to develop that plugin.
+
+**How It Works (A Simple Overview):**
+
+1. **Send a Message to the Slack Bot**
+
+ When a user in Slack sends a message to the Bot, the Slack Bot immediately issues a webhook request to the Dify platform.
+
+2. **Forward the Message to the Slack Bot Plugin**
+
+ The Dify platform triggers the Slack Bot plugin, which relays the details to the Dify application—similar to entering a recipient’s address in an email system. By setting up a Slack webhook address through Slack’s API and entering it in the Slack Bot plugin, you establish this connection. The plugin then processes the Slack request and sends it on to the Dify application, where the LLM analyzes the user’s input and generates a response.
+
+3. **Return the Response to Slack**
+
+ Once the Slack Bot plugin receives the reply from the Dify application, it sends the LLM’s answer back through the same route to the Slack Bot. Users in Slack then see a more intelligent, interactive experience right where they’re chatting.
+
+### Prerequisites
+
+- **Dify plugin developing tool**: For more information, see [Initializing the Development Tool](/plugin_dev_en/0221-initialize-development-tools.en).
+- **Python environment (version ≥ 3.12)**: Refer to this [Python Installation Tutorial](https://pythontest.com/python/installing-python-3-11/) or ask an LLM for a complete setup guide.
+- Create a Slack App and Get an OAuth Token
+
+Go to the [Slack API platform](https://api.slack.com/apps), create a Slack app from scratch, and pick the workspace where it will be deployed.
+
+
+
+1. **Enable Webhooks:**
+
+
+
+2. **Install the App in Your Slack Workspace:**
+
+
+
+3. **Obtain an OAuth Token** for future plugin development:
+
+
+
+### 1. Developing the Plugin
+
+Now we’ll dive into the actual coding. Before starting, make sure you’ve read [Quick Start: Developing an Extension Plugin](/plugin_dev_en/9231-extension-plugin.en) or have already built a Dify plugin before.
+
+#### 1.1 Initialize the Project
+
+Run the following command to set up your plugin development environment:
+
+```bash
+dify plugin init
+```
+
+Follow the prompts to provide basic project info. Select the `extension` template, and grant both `Apps` and `Endpoints` permissions.
+
+For additional details on reverse-invoking Dify services within a plugin, see [Reverse Invocation: App](/plugin_dev_en/9242-reverse-invocation-app.en).
+
+
+
+#### 1.2 Edit the Configuration Form
+
+This plugin needs to know which Dify app should handle the replies, as well as the Slack App token to authenticate the bot’s responses. Therefore, you’ll add these two fields to the plugin’s form.
+
+Modify the YAML file in the group directory—for example, `group/slack.yaml`. The form’s filename is determined by the info you provided when creating the plugin, so adjust it accordingly.
+
+**Sample Code:**
+
+`slack.yaml`
+
+```yaml
+settings:
+ - name: bot_token
+ type: secret-input
+ required: true
+ label:
+ en_US: Bot Token
+ zh_Hans: Bot Token
+ pt_BR: Token do Bot
+ ja_JP: Bot Token
+ placeholder:
+ en_US: Please input your Bot Token
+ zh_Hans: 请输入你的 Bot Token
+ pt_BR: Por favor, insira seu Token do Bot
+ ja_JP: ボットトークンを入力してください
+ - name: allow_retry
+ type: boolean
+ required: false
+ label:
+ en_US: Allow Retry
+ zh_Hans: 允许重试
+ pt_BR: Permitir Retentativas
+ ja_JP: 再試行を許可
+ default: false
+ - name: app
+ type: app-selector
+ required: true
+ label:
+ en_US: App
+ zh_Hans: 应用
+ pt_BR: App
+ ja_JP: アプリ
+ placeholder:
+ en_US: the app you want to use to answer Slack messages
+ zh_Hans: 你想要用来回答 Slack 消息的应用
+ pt_BR: o app que você deseja usar para responder mensagens do Slack
+ ja_JP: あなたが Slack メッセージに回答するために使用するアプリ
+endpoints:
+ - endpoints/slack.yaml
+```
+
+Explanation of the Configuration Fields:
+
+```
+ - name: app
+ type: app-selector
+ scope: chat
+```
+
+* **type**: Set to app-selector, which allows users to forward messages to a specific Dify app when using this plugin.
+
+* **scope**: Set to chat, meaning the plugin can only interact with app types such as agent, chatbot, or chatflow.
+
+Finally, in the `endpoints/slack.yaml` file, change the request method to POST to handle incoming Slack messages properly.
+
+**Sample Code:**
+
+`endpoints/slack.yaml`
+
+```yaml
+path: "/"
+method: "POST"
+extra:
+ python:
+ source: "endpoints/slack.py"
+```
+
+#### 2. Edit the function code
+
+Modify the `endpoints/slack.py` file and add the following code:
+
+```python
+import json
+import traceback
+from typing import Mapping
+from werkzeug import Request, Response
+from dify_plugin import Endpoint
+from slack_sdk import WebClient
+from slack_sdk.errors import SlackApiError
+
+
+class SlackEndpoint(Endpoint):
+ def _invoke(self, r: Request, values: Mapping, settings: Mapping) -> Response:
+ """
+ Invokes the endpoint with the given request.
+ """
+ retry_num = r.headers.get("X-Slack-Retry-Num")
+ if (not settings.get("allow_retry") and (r.headers.get("X-Slack-Retry-Reason") == "http_timeout" or ((retry_num is not None and int(retry_num) > 0)))):
+ return Response(status=200, response="ok")
+ data = r.get_json()
+
+ # Handle Slack URL verification challenge
+ if data.get("type") == "url_verification":
+ return Response(
+ response=json.dumps({"challenge": data.get("challenge")}),
+ status=200,
+ content_type="application/json"
+ )
+
+ if (data.get("type") == "event_callback"):
+ event = data.get("event")
+ if (event.get("type") == "app_mention"):
+ message = event.get("text", "")
+ if message.startswith("<@"):
+ message = message.split("> ", 1)[1] if "> " in message else message
+ channel = event.get("channel", "")
+ blocks = event.get("blocks", [])
+ blocks[0]["elements"][0]["elements"] = blocks[0].get("elements")[0].get("elements")[1:]
+ token = settings.get("bot_token")
+ client = WebClient(token=token)
+ try:
+ response = self.session.app.chat.invoke(
+ app_id=settings["app"]["app_id"],
+ query=message,
+ inputs={},
+ response_mode="blocking",
+ )
+ try:
+ blocks[0]["elements"][0]["elements"][0]["text"] = response.get("answer")
+ result = client.chat_postMessage(
+ channel=channel,
+ text=response.get("answer"),
+ blocks=blocks
+ )
+ return Response(
+ status=200,
+ response=json.dumps(result),
+ content_type="application/json"
+ )
+ except SlackApiError as e:
+ raise e
+ except Exception as e:
+ err = traceback.format_exc()
+ return Response(
+ status=200,
+ response="Sorry, I'm having trouble processing your request. Please try again later." + str(err),
+ content_type="text/plain",
+ )
+ else:
+ return Response(status=200, response="ok")
+ else:
+ return Response(status=200, response="ok")
+ else:
+ return Response(status=200, response="ok")
+```
+
+### 2. Debug the Plugin
+
+Go to the Dify platform and obtain the remote debugging address and key for your plugin.
+
+
+
+Back in your plugin project, copy the `.env.example` file and rename it to `.env`.
+
+```bash
+INSTALL_METHOD=remote
+REMOTE_INSTALL_HOST=remote
+REMOTE_INSTALL_PORT=5003
+REMOTE_INSTALL_KEY=****-****-****-****-****
+```
+
+Run `python -m main` to start the plugin. You should now see your plugin installed in the Workspace on Dify’s plugin management page. Other team members will also be able to access it.
+
+```bash
+python -m main
+```
+
+#### Configure the Plugin Endpoint
+
+From the plugin management page in Dify, locate the newly installed test plugin and create a new endpoint. Provide a name, a Bot token, and select the app you want to connect.
+
+
+
+After saving, a **POST** request URL is generated:
+
+
+
+Next, complete the Slack App setup:
+
+1. **Enable Event Subscriptions**
+ 
+
+ Paste the POST request URL you generated above.
+ 
+
+2. **Grant Required Permissions**
+ 
+
+---
+
+### 3. Verify the Plugin
+
+In your code, `self.session.app.chat.invoke` is used to call the Dify application, passing in parameters such as `app_id` and `query`. The response is then returned to the Slack Bot. Run `python -m main` again to restart your plugin for debugging, and check whether Slack correctly displays the Dify App’s reply:
+
+
+
+---
+
+### 4. Package the Plugin (Optional)
+
+Once you confirm that the plugin works correctly, you can package and name it via the following command. After it runs, you’ll find a `slack_bot.difypkg` file in the current directory—your final plugin package. For detailed packaging steps, refer to [Package as a Local File and Share](/plugin_dev_en/0322-release-by-file.en).
+
+```bash
+# Replace ./slack_bot with your actual plugin project path.
+
+dify plugin package ./slack_bot
+```
+
+Congratulations! You’ve successfully developed, tested, and packaged a plugin!
+
+---
+
+### 5. Publish the Plugin (Optional)
+
+You can now upload it to the [Dify Marketplace repository](https://github.com/langgenius/dify-plugins) for public release. Before publishing, ensure your plugin meets the [Publishing to Dify Marketplace Guidelines](/plugin_dev_en/0322-release-to-dify-marketplace.en). Once approved, your code is merged into the main branch, and the plugin goes live on the [Dify Marketplace](https://marketplace.dify.ai/).
+
+---
+
+## Related Resources
+
+- [Plugin Development Basics](/plugin_dev_en/0111-getting-started-dify-plugin.en) - Comprehensive overview of Dify plugin development
+- [Plugin Development Quick Start Guide](/plugin_dev_en/0211-getting-started-dify-tool.en) - Start developing plugins from scratch
+- [Develop an Extension Plugin](/plugin_dev_en/9231-extension-plugin.en) - Learn about extension plugin development
+- [Reverse Invocation of Dify Services](/plugin_dev_en/9241-reverse-invocation.en) - Understand how to call Dify platform capabilities
+- [Reverse Invocation: App](/plugin_dev_en/9242-reverse-invocation-app.en) - Learn how to call apps within the platform
+- [Publishing Plugins](/plugin_dev_en/0321-release-overview.en) - Learn the publishing process
+- [Publishing to Dify Marketplace](/plugin_dev_en/0322-release-to-dify-marketplace.en) - Marketplace publishing guide
+- [Endpoint Detailed Definition](/plugin_dev_en/0432-endpoint.en) - Detailed Endpoint definition
+
+### Further Reading
+
+For a complete Dify plugin project example, visit the [GitHub repository](https://github.com/langgenius/dify-plugins). You’ll also find additional plugins with full source code and implementation details.
+
+If you want to explore more about plugin development, check the following:
+
+**Quick Starts:**
+- [Develop an Extension Plugin](/plugin_dev_en/9231-extension-plugin.en)
+- [Develop a Model Plugin](/plugin_dev_en/0211-getting-started-new-model.en)
+- [Bundle Plugins: Packaging Multiple Plugins](/plugin_dev_en/9241-bundle.en)
+
+**Plugin Interface Docs:**
+- [Defining Plugin Information via Manifest File](/plugin_dev_en/0411-plugin-info-by-manifest.en) - Manifest structure
+- [Endpoint](/plugin_dev_en/0432-endpoint.en) - Endpoint detailed definition
+- [Reverse Invocation](/plugin_dev_en/9241-reverse-invocation.en) - Reverse-calling Dify capabilities
+- [General Specifications](/plugin_dev_en/0411-general-specifications.en) - Tool specifications
+- [Model Schema](/plugin_dev_en/0412-model-schema.en) - Model
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ Help improve our documentation by contributing directly
+
+
+ Found an error or have suggestions? Let us know
+
+
diff --git a/plugin_dev_en/0432-endpoint.en.mdx b/plugin_dev_en/0432-endpoint.en.mdx
new file mode 100644
index 00000000..fc9c5c76
--- /dev/null
+++ b/plugin_dev_en/0432-endpoint.en.mdx
@@ -0,0 +1,143 @@
+---
+dimensions:
+ type:
+ primary: reference
+ detail: examples
+ level: intermediate
+standard_title: Endpoint
+language: en
+title: Endpoint
+description: Authors Yeuoly, Allen. This document details the structure and implementation
+ of Endpoints in Dify plugins, using the Neko Cat project as an example. It covers
+ defining Endpoint groups, configuring interfaces, implementing the _invoke method,
+ and handling requests and responses. The document explains the meaning and usage
+ of various YAML configuration fields.
+---
+
+# Endpoint
+
+This document uses the [Neko Cat](/plugin_dev_en/9231-extension-plugin.en.mdx) project as an example to explain the structure of Endpoints within a plugin. Endpoints are HTTP interfaces exposed by the plugin, which can be used for integration with external systems. For the complete plugin code, please refer to the [GitHub repository](https://github.com/langgenius/dify-plugin-sdks/tree/main/python/examples/neko).
+
+### Group Definition
+
+An `Endpoint` group is a collection of multiple `Endpoints`. When creating a new `Endpoint` within a Dify plugin, you might need to fill in the following configuration.
+
+
+
+Besides the `Endpoint Name`, you can add new form items by writing the group's configuration information. After clicking save, you can see the multiple interfaces it contains, which will use the same configuration information.
+
+
+
+#### **Structure**
+
+* `settings` (map[string] [ProviderConfig](/plugin_dev_en/0411-general-specifications.en.mdx#providerconfig)): Endpoint configuration definition. (Note: Assuming the anchor `#providerconfig` exists in the target file).
+* `endpoints` (list[string], required): Points to the specific `endpoint` interface definitions.
+
+```yaml
+settings:
+ api_key:
+ type: secret-input
+ required: true
+ label:
+ en_US: API key
+ zh_Hans: API key
+ ja_Jp: API key
+ pt_BR: API key
+ placeholder:
+ en_US: Please input your API key
+ zh_Hans: 请输入你的 API key
+ ja_Jp: あなたの API key を入れてください
+ pt_BR: Por favor, insira sua chave API
+endpoints:
+ - endpoints/duck.yaml
+ - endpoints/neko.yaml
+```
+
+### Interface Definition
+
+* `path` (string): Follows the Werkzeug interface standard.
+* `method` (string): Interface method, only supports `HEAD`, `GET`, `POST`, `PUT`, `DELETE`, `OPTIONS`.
+* `extra` (object): Configuration information beyond the basic details.
+ * `python` (object)
+ * `source` (string): The source code that implements this interface.
+
+```yaml
+path: "/duck/"
+method: "GET"
+extra:
+ python:
+ source: "endpoints/duck.py"
+```
+
+### Interface Implementation
+
+You need to implement a subclass that inherits from `dify_plugin.Endpoint` and implement the `_invoke` method.
+
+* **Input Parameters**
+ * `r` (Request): The `Request` object from `werkzeug`.
+ * `values` (Mapping): Path parameters parsed from the path.
+ * `settings` (Mapping): Configuration information for this `Endpoint`.
+* **Return**
+ * A `Response` object from `werkzeug`, supports streaming responses.
+ * Directly returning a string is not supported.
+
+Example code:
+
+```python
+import json
+from typing import Mapping
+from werkzeug import Request, Response
+from dify_plugin import Endpoint
+
+class Duck(Endpoint):
+ def _invoke(self, r: Request, values: Mapping, settings: Mapping) -> Response:
+ """
+ Invokes the endpoint with the given request.
+ """
+ app_id = values["app_id"]
+
+ def generator():
+ yield f"{app_id}
"
+
+ return Response(generator(), status=200, content_type="text/html")
+```
+
+## Notes
+
+* Endpoints are only instantiated when the plugin is called; they are not long-running services.
+* Pay attention to security when developing Endpoints and avoid executing dangerous operations.
+* Endpoints can be used to handle Webhook callbacks or provide interfaces for other systems to connect.
+
+If you are learning plugin development, it is recommended to first read the [Getting Started with Plugin Development](/plugin_dev_en/0211-getting-started-dify-tool.en.mdx) and the [Developer Cheatsheet](/plugin_dev_en/0131-cheatsheet.en.mdx).
+
+## Related Resources
+
+* [Basic Concepts of Plugin Development](/plugin_dev_en/0111-getting-started-dify-plugin.en.mdx) - Understand the overall architecture of plugin development.
+* [Neko Cat Example](/plugin_dev_en/9231-extension-plugin.en.mdx) - An example of extension plugin development.
+* [General Specifications Definition](/plugin_dev_en/0411-general-specifications.en.mdx) - Understand common structures like ProviderConfig.
+* [Develop a Slack Bot Plugin Example](/plugin_dev_en/0432-develop-a-slack-bot-plugin.en.mdx) - Another plugin development example.
+* [Getting Started with Plugin Development](/plugin_dev_en/0211-getting-started-dify-tool.en.mdx) - Develop a plugin from scratch.
+* [Reverse Invocation of Dify Services](/plugin_dev_en/9241-reverse-invocation-app.en.mdx) - Learn how to use the reverse invocation feature. (Note: Link updated to closest available English document)
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ Help improve our documentation by contributing directly
+
+
+ Found an error or have suggestions? Let us know
+
+
diff --git a/plugin_dev_en/9231-extension-plugin.en.mdx b/plugin_dev_en/9231-extension-plugin.en.mdx
new file mode 100644
index 00000000..d839446b
--- /dev/null
+++ b/plugin_dev_en/9231-extension-plugin.en.mdx
@@ -0,0 +1,311 @@
+---
+dimensions:
+ type:
+ primary: implementation
+ detail: high
+ level: beginner
+standard_title: Extension Plugin
+language: en
+title: Extension Plugin
+description: This document provides a complete tutorial for developing Extension type
+ plugins, detailing the entire process including environment preparation, project
+ creation, defining plugin request entry points, writing functional code, debugging,
+ packaging, and publishing. The example project is a Nyan Cat plugin that demonstrates
+ how to handle HTTP requests and provide web services through an Extension plugin.
+---
+
+This article will guide you through quickly developing an Extension type plugin to help you understand the basic plugin development process.
+
+### Prerequisites
+
+* Dify plugin scaffolding tool
+* Python environment, version ≥ 3.12
+
+For detailed instructions on preparing the plugin development scaffolding tool, please refer to [Initializing Development Tools](/plugin_dev_en/0221-initialize-development-tools.en).
+
+### Creating a New Project
+
+In the current path, run the scaffolding command line tool to create a new Dify plugin project.
+
+```
+./dify-plugin-darwin-arm64 plugin init
+```
+
+If you have renamed the binary file to `dify` and copied it to the `/usr/local/bin` path, you can run the following command to create a new plugin project:
+
+```bash
+dify plugin init
+```
+
+### **Filling in Plugin Information**
+
+Follow the prompts to configure the plugin name, author information, and plugin description. If you are working collaboratively as a team, you can also enter an organization name as the author.
+
+> The plugin name must be 1-128 characters long and can only contain letters, numbers, hyphens, and underscores.
+
+
+
+Once completed, select Python as the plugin development language.
+
+
+
+### 3. Select Plugin Type and Initialize Project Template
+
+All templates in the scaffolding tool provide complete code projects. For demonstration purposes, this article will use the `Extension` type plugin template as an example. For developers already familiar with plugin development, templates are not necessary, and you can refer to the [interface documentation](/plugin_dev_en/0411-general-specifications.en) to guide the development of different types of plugins.
+
+
+
+#### Configure Plugin Permissions
+
+The plugin also needs permissions to read from the Dify main platform to connect properly. Grant the following permissions to this example plugin:
+
+* Tools
+* LLMs
+* Apps
+* Enable persistent storage Storage, allocate default size storage
+* Allow registering Endpoints
+
+> Use the arrow keys in the terminal to select permissions, and use the "Tab" button to grant permissions.
+
+After checking all permission items, press Enter to complete the plugin creation. The system will automatically generate the plugin project code.
+
+
+
+The basic file structure of the plugin includes the following:
+
+```
+.
+├── GUIDE.md
+├── README.md
+├── _assets
+│ └── icon.svg
+├── endpoints
+│ ├── your-project.py
+│ └── your-project.yaml
+├── group
+│ └── your-project.yaml
+├── main.py
+├── manifest.yaml
+└── requirements.txt
+```
+
+* `GUIDE.md` A short tutorial guiding you through the plugin writing process.
+* `README.md` Brief introduction about the current plugin, where you need to fill in the introduction and usage instructions for the plugin.
+* `_assets` Stores all multimedia files related to the current plugin.
+* `endpoints` An `Extension` type plugin template created according to the CLI guidance, this directory stores all Endpoint implementation code.
+* `group` Specifies the key type, multilingual settings, and the file path of the API definition.
+* `main.py` The entry file for the entire project.
+* `manifest.yaml` The basic configuration file for the entire plugin, containing configuration information such as what permissions the plugin needs and what type of extension it is.
+* `requirements.txt` Stores Python environment dependencies.
+
+### Developing the Plugin
+
+#### 1. Define the Plugin's Request Entry Point (Endpoint)
+
+Edit `endpoints/test_plugin.yaml`, referring to the following code for modification:
+
+```yaml
+path: "/neko"
+method: "GET"
+extra:
+ python:
+ source: "endpoints/test_plugin.py"
+```
+
+The intent of this code is to define the entry path for the plugin as `/neko`, with the request method as GET type. The plugin's functional implementation code is in the `endpoints/test_plugin.py` file.
+
+#### 2. Write Plugin Functionality
+
+Plugin functionality: Request service, output a Nyan Cat.
+
+Write the plugin's functional implementation code in the `endpoints/test_plugin.py` file, referring to the following example code:
+
+```python
+from typing import Mapping
+from werkzeug import Request, Response
+from flask import Flask, render_template_string
+from dify_plugin import Endpoint
+
+app = Flask(__name__)
+
+class NekoEndpoint(Endpoint):
+ def _invoke(self, r: Request, values: Mapping, settings: Mapping) -> Response:
+ ascii_art = '''
+⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛⬛️⬜️⬜️⬜️⬜️⬜⬜️⬜️️
+🟥🟥⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️🟥🟥🟥🟥🟥🟥🟥🟥⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬛🥧🥧🥧🥧🥧🥧🥧🥧🥧🥧🥧🥧🥧🥧🥧🥧🥧⬛️⬜️⬜️⬜️⬜️⬜⬜️️
+🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥⬛️🥧🥧🥧💟💟💟💟💟💟💟💟💟💟💟💟💟🥧🥧🥧⬛️⬜️⬜️⬜️⬜⬜️️
+🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥⬛️🥧🥧💟💟💟💟💟💟🍓💟💟🍓💟💟💟💟💟🥧🥧⬛️⬜️⬜️⬜️⬜️⬜️️
+🟧🟧🟥🟥🟥🟥🟥🟥🟥🟥🟧🟧🟧🟧🟧🟧🟧🟧🟥🟥🟥🟥🟥🟥🟥⬛🥧💟💟🍓💟💟💟💟💟💟💟💟💟💟💟💟💟💟🥧⬛️⬜️⬜️⬜️⬜⬜️️
+🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧⬛️🥧💟💟💟💟💟💟💟💟💟💟⬛️⬛️💟💟🍓💟💟🥧⬛️⬜️⬛️️⬛️️⬜⬜️️
+🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧⬛️🥧💟💟💟💟💟💟💟💟💟⬛️🌫🌫⬛💟💟💟💟🥧⬛️⬛️🌫🌫⬛⬜️️
+🟨🟨🟧🟧🟧🟧🟧🟧🟧🟧🟨🟨🟨🟨🟨🟨🟨🟨🟧⬛️⬛️⬛️⬛️🟧🟧⬛️🥧💟💟💟💟💟💟🍓💟💟⬛️🌫🌫🌫⬛💟💟💟🥧⬛️🌫🌫🌫⬛⬜️️
+🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨⬛️🌫🌫⬛️⬛️🟧⬛️🥧💟💟💟💟💟💟💟💟💟⬛️🌫🌫🌫🌫⬛️⬛️⬛️⬛️🌫🌫🌫🌫⬛⬜️️
+🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨⬛️⬛️🌫🌫⬛️⬛️⬛️🥧💟💟💟🍓💟💟💟💟💟⬛️🌫🌫🌫🌫🌫🌫🌫🌫🌫🌫🌫🌫⬛⬜️️
+🟩🟩🟨🟨🟨🟨🟨🟨🟨🟨🟩🟩🟩🟩🟩🟩🟩🟩🟨🟨⬛⬛️🌫🌫⬛️⬛️🥧💟💟💟💟💟💟💟🍓⬛️🌫🌫🌫🌫🌫🌫🌫🌫🌫🌫🌫🌫🌫🌫⬛️
+🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩⬛️⬛️🌫🌫⬛️🥧💟🍓💟💟💟💟💟💟⬛️🌫🌫🌫⬜️⬛️🌫🌫🌫🌫🌫⬜️⬛️🌫🌫⬛️
+️🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩⬛️⬛️⬛️⬛️🥧💟💟💟💟💟💟💟💟⬛️🌫🌫🌫⬛️⬛️🌫🌫🌫⬛️🌫⬛️⬛️🌫🌫⬛️
+🟦🟦🟩🟩🟩🟩🟩🟩🟩🟩🟦🟦🟦🟦🟦🟦🟦🟦🟩🟩🟩🟩🟩🟩⬛️⬛️🥧💟💟💟💟💟🍓💟💟⬛🌫🟥🟥🌫🌫🌫🌫🌫🌫🌫🌫🌫🟥🟥⬛️
+🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦⬛️🥧🥧💟🍓💟💟💟💟💟⬛️🌫🟥🟥🌫⬛️🌫🌫⬛️🌫🌫⬛️🌫🟥🟥⬛️
+🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦⬛️🥧🥧🥧💟💟💟💟💟💟💟⬛️🌫🌫🌫⬛️⬛️⬛️⬛️⬛️⬛️⬛️🌫🌫⬛️⬜️
+🟪🟪🟦🟦🟦🟦🟦🟦🟦🟦🟪🟪🟪🟪🟪🟪🟪🟪🟦🟦🟦🟦🟦🟦⬛️⬛️⬛️🥧🥧🥧🥧🥧🥧🥧🥧🥧🥧⬛️🌫🌫🌫🌫🌫🌫🌫🌫🌫🌫⬛️⬜️⬜️
+🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪⬛️🌫🌫🌫⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬜️⬜️⬜️
+🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪⬛️🌫🌫⬛️⬛️⬜️⬛️🌫🌫⬛️⬜️⬜️⬜️⬜️⬜️⬛️🌫🌫⬛️⬜️⬛️🌫🌫⬛️⬜️⬜️⬜️⬜️
+⬜️⬜️🟪🟪🟪🟪🟪🟪🟪🟪⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️🟪🟪🟪🟪🟪⬛️⬛️⬛️⬛⬜️⬜️⬛️⬛️⬛️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬛️⬛️⬛️⬜️⬜️⬛️⬛️⬜️⬜️⬜️⬜️⬜️️
+ '''
+ ascii_art_lines = ascii_art.strip().split('\n')
+ with app.app_context():
+ return Response(render_template_string('''
+
+
+
+
+
+
+
+
+
+
+ ''', ascii_art_lines=ascii_art_lines), status=200, content_type="text/html")
+```
+
+To run this code, you need to first install the following Python dependency packages:
+
+```python
+pip install werkzeug
+pip install flask
+pip install dify-plugin
+```
+
+### Debugging the Plugin
+
+Next, you need to test whether the plugin can function properly. Dify provides a remote debugging method. Go to the "Plugin Management" page to obtain the debugging Key and remote server address.
+
+
+
+Return to the plugin project, copy the `.env.example` file and rename it to `.env`, then fill in the remote server address and debugging Key information you obtained.
+
+`.env` file
+
+```bash
+INSTALL_METHOD=remote
+REMOTE_INSTALL_HOST=remote
+REMOTE_INSTALL_PORT=5003
+REMOTE_INSTALL_KEY=****-****-****-****-****
+```
+
+Run the `python -m main` command to start the plugin. On the plugins page, you can see that the plugin has been installed in the Workspace. Other team members can also access the plugin.
+
+
+
+Add a new Endpoint in the plugin, fill in the name and `api_key` information as desired. Visit the automatically generated URL to see the web service provided by the plugin.
+
+
+
+### Packaging the Plugin
+
+After confirming that the plugin can run normally, you can package and name the plugin using the following command line tool. After running, you will discover a `neko.difypkg` file in the current folder, which is the final plugin package.
+
+```bash
+# Replace ./neko with the actual path of the plugin project
+
+dify plugin package ./neko
+```
+
+Congratulations, you have completed the entire process of developing, testing, and packaging a plugin!
+
+### Publishing the Plugin
+
+Now you can upload it to the [Dify Plugins code repository](https://github.com/langgenius/dify-plugins) to publish your plugin! Before uploading, please ensure that your plugin follows the [plugin publishing specifications](/plugin_dev_en/0322-release-to-dify-marketplace.en). After the review is approved, the code will be merged into the main branch and automatically launched to the [Dify Marketplace](https://marketplace.dify.ai/).
+
+### Explore More
+
+**Quick Start:**
+
+* [Tool Plugin: Google Search](/plugin_dev_en/0222-tool-plugin.en)
+* [Model Plugin](/plugin_dev_en/0211-getting-started-new-model.en)
+* [Bundle Plugin: Packaging Multiple Plugins](/plugin_dev_en/9241-bundle.en)
+
+**Plugin Interface Documentation:**
+
+* [Manifest](/plugin_dev_en/0411-general-specifications.en) Structure
+* [Endpoint](/plugin_dev_en/0411-general-specifications.en) Detailed Definition
+* [Reverse Invocation of Dify Capabilities](/plugin_dev_en/9241-reverse-invocation.en)
+* [Tools](/plugin_dev_en/0411-tool.en)
+* [Models](/plugin_dev_en/0412-model-schema.en)
+* [Extending Agent Strategies](/plugin_dev_en/9232-agent.en)
+
+**Best Practices:**
+
+[Developing a Slack Bot Plugin](/plugin_dev_en/0432-develop-a-slack-bot-plugin.en)
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ Help improve our documentation by contributing directly
+
+
+ Found an error or have suggestions? Let us know
+
+
diff --git a/plugin_dev_en/9232-agent.en.mdx b/plugin_dev_en/9232-agent.en.mdx
new file mode 100644
index 00000000..cd4293f8
--- /dev/null
+++ b/plugin_dev_en/9232-agent.en.mdx
@@ -0,0 +1,453 @@
+---
+dimensions:
+ type:
+ primary: implementation
+ detail: high
+ level: intermediate
+standard_title: Agent
+language: en
+title: Agent
+description: This document details the development process for Dify's Agent strategy
+ plugins, including adding Agent strategy fields in the Manifest file, defining Agent
+ providers, and the core steps for implementing Agent strategies. It provides complete
+ example code for getting parameters, invoking models, invoking tools, and generating
+ and managing logs.
+---
+
+An Agent strategy is an extensible template that defines standard input content and output formats. By developing the functional code for specific Agent strategy interfaces, you can implement various Agent strategies such as CoT (Chain of Thought) / ToT (Tree of Thoughts) / GoT (Graph of Thoughts) / BoT (Skeleton of Thought), enabling complex strategies like [Semantic Kernel](https://learn.microsoft.com/en-us/semantic-kernel/overview/).
+
+### Add Fields in Manifest
+
+To add an Agent strategy in a plugin, you need to add the `plugins.agent_strategies` field in the `manifest.yaml` file and also define the Agent provider. Here is an example:
+
+```yaml
+version: 0.0.2
+type: plugin
+author: "langgenius"
+name: "agent"
+plugins:
+ agent_strategies:
+ - "provider/agent.yaml"
+```
+
+Some irrelevant fields in the `manifest` file have been omitted here. For the detailed format of the Manifest, please refer to the [Define Plugin Information via Manifest File](/plugin_dev_en/0411-general-specifications.en) document.
+
+### Define Agent Provider
+
+Next, you need to create a new `agent.yaml` file and fill in the basic Agent provider information.
+
+```yaml
+identity:
+ author: langgenius
+ name: agent
+ label:
+ en_US: Agent
+ zh_Hans: Agent
+ pt_BR: Agent
+ description:
+ en_US: Agent
+ zh_Hans: Agent
+ pt_BR: Agent
+ icon: icon.svg
+strategies:
+ - strategies/function_calling.yaml
+```
+
+It mainly contains basic descriptive content and specifies which strategies the current provider includes. In the example code above, only the most basic `function_calling.yaml` strategy file is specified.
+
+### Define and Implement Agent Strategy
+
+#### Definition
+
+Next, you need to define the code that implements the Agent strategy. Create a new `function_calling.yaml` file:
+
+```yaml
+identity:
+ name: function_calling
+ author: Dify
+ label:
+ en_US: FunctionCalling
+ zh_Hans: FunctionCalling
+ pt_BR: FunctionCalling
+description:
+ en_US: Function Calling is a basic strategy for agent, model will use the tools provided to perform the task.
+ zh_Hans: Function Calling 是一个基本的 Agent 策略,模型将使用提供的工具来执行任务。
+ pt_BR: Function Calling is a basic strategy for agent, model will use the tools provided to perform the task.
+parameters:
+ - name: model
+ type: model-selector
+ scope: tool-call&llm
+ required: true
+ label:
+ en_US: Model
+ zh_Hans: 模型
+ pt_BR: Model
+ - name: tools
+ type: array[tools]
+ required: true
+ label:
+ en_US: Tools list
+ zh_Hans: 工具列表
+ pt_BR: Tools list
+ - name: query
+ type: string
+ required: true
+ label:
+ en_US: Query
+ zh_Hans: 用户提问
+ pt_BR: Query
+ - name: max_iterations
+ type: number
+ required: false
+ default: 5
+ label:
+ en_US: Max Iterations
+ zh_Hans: 最大迭代次数
+ pt_BR: Max Iterations
+ max: 50
+ min: 1
+extra:
+ python:
+ source: strategies/function_calling.py
+```
+
+The code format is similar to the [`Tool` standard format](/plugin_dev_en/0411-tool.en), defining four parameters: `model`, `tools`, `query`, and `max_iterations`, to implement the most basic Agent strategy. This code allows users to select a model and the tools to use, configure the maximum number of iterations, and finally input a query to start executing the Agent.
+
+#### Write Functional Implementation Code
+
+**Get Parameters**
+
+Based on the four parameters defined above, the model type parameter is `model-selector`, and the tool type parameter is a special `array[tools]`. The forms obtained in the parameters can be converted using the built-in `AgentModelConfig` and `list[ToolEntity]` in the SDK.
+
+```python
+from dify_plugin.interfaces.agent import AgentModelConfig, AgentStrategy, ToolEntity
+
+class FunctionCallingParams(BaseModel):
+ query: str
+ model: AgentModelConfig
+ tools: list[ToolEntity] | None
+ maximum_iterations: int = 3
+
+ class FunctionCallingAgentStrategy(AgentStrategy):
+ def _invoke(self, parameters: dict[str, Any]) -> Generator[AgentInvokeMessage]:
+ """
+ Run FunctionCall agent application
+ """
+ fc_params = FunctionCallingParams(**parameters)
+```
+
+**Invoke Model**
+
+Invoking the specified model is an essential capability in Agent plugins. Use the `session.model.invoke()` function in the SDK to invoke the model. You can get the required input parameters from the model.
+
+Example method signature for invoking the model:
+
+```python
+def invoke(
+ self,
+ model_config: LLMModelConfig,
+ prompt_messages: list[PromptMessage],
+ tools: list[PromptMessageTool] | None = None,
+ stop: list[str] | None = None,
+ stream: bool = True,
+ ) -> Generator[LLMResultChunk, None, None] | LLMResult:
+```
+
+You need to pass the model information `model_config`, prompt information `prompt_messages`, and tool information `tools`.
+
+The `prompt_messages` parameter can be invoked using the example code below; the `tool_messages` require some conversion.
+
+Please refer to the example code for using invoke model:
+
+```python
+from collections.abc import Generator
+from typing import Any
+
+from pydantic import BaseModel
+
+from dify_plugin.entities.agent import AgentInvokeMessage
+from dify_plugin.entities.model.llm import LLMModelConfig
+from dify_plugin.entities.model.message import (
+ PromptMessageTool,
+ SystemPromptMessage,
+ UserPromptMessage,
+)
+from dify_plugin.entities.tool import ToolParameter
+from dify_plugin.interfaces.agent import AgentModelConfig, AgentStrategy, ToolEntity
+
+class FunctionCallingParams(BaseModel):
+ query: str
+ instruction: str | None
+ model: AgentModelConfig
+ tools: list[ToolEntity] | None
+ maximum_iterations: int = 3
+
+class FunctionCallingAgentStrategy(AgentStrategy):
+ def _invoke(self, parameters: dict[str, Any]) -> Generator[AgentInvokeMessage]:
+ """
+ Run FunctionCall agent application
+ """
+ # init params
+ fc_params = FunctionCallingParams(**parameters)
+ query = fc_params.query
+ model = fc_params.model
+ stop = fc_params.model.completion_params.get("stop", []) if fc_params.model.completion_params else []
+ prompt_messages = [
+ SystemPromptMessage(content="your system prompt message"),
+ UserPromptMessage(content=query),
+ ]
+ tools = fc_params.tools
+ prompt_messages_tools = self._init_prompt_tools(tools)
+
+ # invoke llm
+ chunks = self.session.model.llm.invoke(
+ model_config=LLMModelConfig(**model.model_dump(mode="json")),
+ prompt_messages=prompt_messages,
+ stream=True,
+ stop=stop,
+ tools=prompt_messages_tools,
+ )
+
+ def _init_prompt_tools(self, tools: list[ToolEntity] | None) -> list[PromptMessageTool]:
+ """
+ Init tools
+ """
+
+ prompt_messages_tools = []
+ for tool in tools or []:
+ try:
+ prompt_tool = self._convert_tool_to_prompt_message_tool(tool)
+ except Exception:
+ # api tool may be deleted
+ continue
+
+ # save prompt tool
+ prompt_messages_tools.append(prompt_tool)
+
+ return prompt_messages_tools
+
+ def _convert_tool_to_prompt_message_tool(self, tool: ToolEntity) -> PromptMessageTool:
+ """
+ convert tool to prompt message tool
+ """
+ message_tool = PromptMessageTool(
+ name=tool.identity.name,
+ description=tool.description.llm if tool.description else "",
+ parameters={
+ "type": "object",
+ "properties": {},
+ "required": [],
+ },
+ )
+
+ parameters = tool.parameters
+ for parameter in parameters:
+ if parameter.form != ToolParameter.ToolParameterForm.LLM:
+ continue
+
+ parameter_type = parameter.type
+ if parameter.type in {
+ ToolParameter.ToolParameterType.FILE,
+ ToolParameter.ToolParameterType.FILES,
+ }:
+ continue
+ enum = []
+ if parameter.type == ToolParameter.ToolParameterType.SELECT:
+ enum = [option.value for option in parameter.options] if parameter.options else []
+
+ message_tool.parameters["properties"][parameter.name] = {
+ "type": parameter_type,
+ "description": parameter.llm_description or "",
+ }
+
+ if len(enum) > 0:
+ message_tool.parameters["properties"][parameter.name]["enum"] = enum
+
+ if parameter.required:
+ message_tool.parameters["required"].append(parameter.name)
+
+ return message_tool
+
+```
+
+**Invoke Tool**
+
+Invoking tools is also an essential capability in Agent plugins. You can use `self.session.tool.invoke()` to call them. Example method signature for invoking a tool:
+
+```python
+def invoke(
+ self,
+ provider_type: ToolProviderType,
+ provider: str,
+ tool_name: str,
+ parameters: dict[str, Any],
+ ) -> Generator[ToolInvokeMessage, None, None]
+```
+
+The required parameters are `provider_type`, `provider`, `tool_name`, and `parameters`. In Function Calling, `tool_name` and `parameters` are often generated by the LLM. Example code for using invoke tool:
+
+```python
+from dify_plugin.entities.tool import ToolProviderType
+
+class FunctionCallingAgentStrategy(AgentStrategy):
+ def _invoke(self, parameters: dict[str, Any]) -> Generator[AgentInvokeMessage]:
+ """
+ Run FunctionCall agent application
+ """
+ fc_params = FunctionCallingParams(**parameters)
+
+ # tool_call_name and tool_call_args parameter is obtained from the output of LLM
+ tool_instances = {tool.identity.name: tool for tool in fc_params.tools} if fc_params.tools else {}
+ tool_instance = tool_instances[tool_call_name]
+ tool_invoke_responses = self.session.tool.invoke(
+ provider_type=ToolProviderType.BUILT_IN,
+ provider=tool_instance.identity.provider,
+ tool_name=tool_instance.identity.name,
+ # add the default value
+ parameters={**tool_instance.runtime_parameters, **tool_call_args},
+ )
+```
+
+The output of the `self.session.tool.invoke()` function is a Generator, which means it also needs to be parsed streamingly.
+
+Please refer to the following function for the parsing method:
+
+```python
+import json
+from collections.abc import Generator
+from typing import cast
+
+from dify_plugin.entities.agent import AgentInvokeMessage
+from dify_plugin.entities.tool import ToolInvokeMessage
+
+def parse_invoke_response(tool_invoke_responses: Generator[AgentInvokeMessage]) -> str:
+ result = ""
+ for response in tool_invoke_responses:
+ if response.type == ToolInvokeMessage.MessageType.TEXT:
+ result += cast(ToolInvokeMessage.TextMessage, response.message).text
+ elif response.type == ToolInvokeMessage.MessageType.LINK:
+ result += (
+ f"result link: {cast(ToolInvokeMessage.TextMessage, response.message).text}."
+ + " please tell user to check it."
+ )
+ elif response.type in {
+ ToolInvokeMessage.MessageType.IMAGE_LINK,
+ ToolInvokeMessage.MessageType.IMAGE,
+ }:
+ result += (
+ "image has been created and sent to user already, "
+ + "you do not need to create it, just tell the user to check it now."
+ )
+ elif response.type == ToolInvokeMessage.MessageType.JSON:
+ text = json.dumps(cast(ToolInvokeMessage.JsonMessage, response.message).json_object, ensure_ascii=False)
+ result += f"tool response: {text}."
+ else:
+ result += f"tool response: {response.message!r}."
+ return result
+```
+
+#### Log
+
+If you want to see the Agent's thinking process, besides viewing the normally returned messages, you can use a dedicated interface to display the entire Agent's thinking process in a tree structure.
+
+**Create Log**
+
+* This interface creates and returns an `AgentLogMessage`, which represents a node in the log tree.
+* If `parent` is passed, it indicates that the node has a parent node.
+* The status defaults to "Success". However, if you want to better display the task execution process, you can first set the status to "start" to show a "running" log, and then update the log's status to "Success" after the task is completed. This allows users to clearly see the entire process from start to finish.
+* `label` will be used to display the log title to the user.
+
+```python
+ def create_log_message(
+ self,
+ label: str,
+ data: Mapping[str, Any],
+ status: AgentInvokeMessage.LogMessage.LogStatus = AgentInvokeMessage.LogMessage.LogStatus.SUCCESS,
+ parent: AgentInvokeMessage | None = None,
+ ) -> AgentInvokeMessage
+```
+
+**Finish Log**
+
+If you chose the `start` status as the initial state in the previous step, you can use the finish log interface to change the status.
+
+```python
+ def finish_log_message(
+ self,
+ log: AgentInvokeMessage,
+ status: AgentInvokeMessage.LogMessage.LogStatus = AgentInvokeMessage.LogMessage.LogStatus.SUCCESS,
+ error: Optional[str] = None,
+ ) -> AgentInvokeMessage
+```
+
+**Example**
+
+This example shows a simple two-step execution process: first, output a log with the status "Thinking", then complete the actual task processing.
+
+```python
+class FunctionCallingAgentStrategy(AgentStrategy):
+ def _invoke(self, parameters: dict[str, Any]) -> Generator[AgentInvokeMessage]:
+ thinking_log = self.create_log_message(
+ data={
+ "Query": parameters.get("query"),
+ },
+ label="Thinking",
+ status=AgentInvokeMessage.LogMessage.LogStatus.START,
+ )
+
+ yield thinking_log
+
+ llm_response = self.session.model.llm.invoke(
+ model_config=LLMModelConfig(
+ provider="openai",
+ model="gpt-4o-mini",
+ mode="chat",
+ completion_params={},
+ ),
+ prompt_messages=[
+ SystemPromptMessage(content="you are a helpful assistant"),
+ UserPromptMessage(content=parameters.get("query")),
+ ],
+ stream=False,
+ tools=[],
+ )
+
+ thinking_log = self.finish_log_message(
+ log=thinking_log,
+ )
+
+ yield thinking_log
+
+ yield self.create_text_message(text=llm_response.message.content)
+```
+
+## Related Resources
+
+- [Getting Started with Dify Plugins](/plugin_dev_en/0111-getting-started-dify-plugin.en) - Understand the overall architecture of plugin development
+- [Agent Strategy Plugin Example](/plugin_dev_en/9433-agent-strategy-plugin.en) - A practical example of Agent strategy plugin development
+- [Define Plugin Information via Manifest File](/plugin_dev_en/0411-general-specifications.en) - Understand the detailed format of the Manifest file
+- [Reverse Invocation: Model](/plugin_dev_en/9242-reverse-invocation-model.en) - Learn how to invoke model capabilities within the platform
+- [Reverse Invocation: Tool](/plugin_dev_en/9242-reverse-invocation-tool.en) - Learn how to invoke other plugins
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ Help improve our documentation by contributing directly
+
+
+ Found an error or have suggestions? Let us know
+
+
diff --git a/plugin_dev_en/9241-bundle.en.mdx b/plugin_dev_en/9241-bundle.en.mdx
new file mode 100644
index 00000000..7cbb8150
--- /dev/null
+++ b/plugin_dev_en/9241-bundle.en.mdx
@@ -0,0 +1,121 @@
+---
+dimensions:
+ type:
+ primary: implementation
+ detail: advanced
+ level: beginner
+standard_title: Bundle
+language: en
+title: Bundle Plugin Package
+description: This document introduces the concept and development method of Bundle
+ plugin packages. Bundle plugin packages can aggregate multiple plugins together,
+ supporting three types (Marketplace, GitHub, and Package). The document details
+ the entire process of creating a Bundle project, adding different types of dependencies,
+ and packaging the Bundle project.
+---
+
+A Bundle plugin package is a collection of multiple plugins. It allows packaging several plugins within a single plugin, enabling batch installation and providing more powerful services.
+
+You can use the Dify CLI tool to package multiple plugins into a Bundle. Bundle plugin packages come in three types:
+
+* `Marketplace` type. Stores the plugin's ID and version information. During import, the specific plugin package will be downloaded from the Dify Marketplace.
+* `GitHub` type. Stores the GitHub repository address, release version number, and asset filename. During import, Dify will access the corresponding GitHub repository to download the plugin package.
+* `Package` type. The plugin package is stored directly within the Bundle. It does not store reference sources, but this might lead to a larger Bundle package size.
+
+### Prerequisites
+
+* Dify plugin scaffolding tool
+* Python environment, version ≥ 3.10
+
+For detailed instructions on how to prepare the plugin development scaffolding tool, please refer to [Initialize Development Tools](/plugin_dev_en/0221-initialize-development-tools.en).
+
+### Create a Bundle Project
+
+In the current directory, run the scaffolding command-line tool to create a new plugin package project.
+
+```bash
+./dify-plugin-darwin-arm64 bundle init
+```
+
+If you have renamed the binary file to `dify` and copied it to the `/usr/local/bin` path, you can run the following command to create a new plugin project:
+
+```bash
+dify bundle init
+```
+
+#### 1. Fill in Plugin Information
+
+Follow the prompts to configure the plugin name, author information, and plugin description. If you are collaborating as a team, you can also enter the organization name as the author.
+
+> The name must be 1-128 characters long and can only contain letters, numbers, hyphens, and underscores.
+
+
+
+After filling in the information and pressing Enter, the Bundle plugin project directory will be automatically created.
+
+
+
+#### 2. Add Dependencies
+
+* **Marketplace**
+
+Execute the following command:
+
+```bash
+dify-plugin bundle append marketplace . --marketplace_pattern=langgenius/openai:0.0.1
+```
+
+Where `marketplace_pattern` is the reference to the plugin in the marketplace, in the format `organization_name/plugin_name:version_number`.
+
+* **Github**
+
+Execute the following command:
+
+```bash
+dify-plugin bundle append github . --repo_pattern=langgenius/openai:0.0.1/openai.difypkg
+```
+
+Where `repo_pattern` is the reference to the plugin on GitHub, in the format `organization_name/repository_name:release/asset_name`.
+
+* **Package**
+
+Execute the following command:
+
+```bash
+dify-plugin bundle append package . --package_path=./openai.difypkg
+```
+
+Where `package_path` is the directory of the plugin package.
+
+### Package the Bundle Project
+
+Run the following command to package the Bundle plugin:
+
+```bash
+dify-plugin bundle package ./bundle
+```
+
+After executing the command, a `bundle.difybndl` file will be automatically created in the current directory. This file is the final packaged result.
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ Help improve our documentation by contributing directly
+
+
+ Found an error or have suggestions? Let us know
+
+
diff --git a/plugin_dev_en/9241-reverse-invocation.en.mdx b/plugin_dev_en/9241-reverse-invocation.en.mdx
new file mode 100644
index 00000000..6b9b2c5b
--- /dev/null
+++ b/plugin_dev_en/9241-reverse-invocation.en.mdx
@@ -0,0 +1,62 @@
+---
+dimensions:
+ type:
+ primary: implementation
+ detail: advanced
+ level: beginner
+standard_title: Reverse Invocation
+language: en
+title: Reverse Invocation of Dify Services
+description: This document briefly introduces the reverse invocation capability of
+ Dify plugins, meaning plugins can call specified services within the main Dify platform.
+ It lists four types of modules that can be invoked, App (access App data), Model
+ (call model capabilities within the platform), Tool (call other tool plugins within
+ the platform), and Node (call nodes within a Chatflow/Workflow application).
+---
+
+Plugins can freely call some services within the main Dify platform to enhance their capabilities.
+
+### Callable Dify Modules
+
+* [App](/plugin_dev_en/9242-reverse-invocation-app.en.mdx)
+
+ Plugins can access data from Apps within the Dify platform.
+* [Model](/plugin_dev_en/9242-reverse-invocation-model.en.mdx)
+
+ Plugins can reverse invoke LLM capabilities within the Dify platform, including all model types and functions within the platform, such as TTS, Rerank, etc.
+* [Tool](/plugin_dev_en/9242-reverse-invocation-tool.en.mdx)
+
+ Plugins can call other tool-type plugins within the Dify platform.
+* [Node](/plugin_dev_en/9243-reverse-invocation-node.en.mdx)
+
+ Plugins can call nodes within a specific Chatflow/Workflow application in the Dify platform.
+
+## Related Resources
+
+- [Develop Extension Plugins](/plugin_dev_en/9231-extension-plugin.en.mdx) - Learn how to develop plugins that integrate with external systems
+- [Develop a Slack Bot Plugin](/plugin_dev_en/0432-develop-a-slack-bot-plugin.en.mdx) - An example of using reverse invocation to integrate with the Slack platform
+- [Bundle Type Plugins](/plugin_dev_en/9241-bundle.en.mdx) - Learn how to package multiple plugins that use reverse invocation
+- [Using Persistent Storage](/plugin_dev_en/0411-persistent-storage-kv.en.mdx) - Enhance plugin capabilities through KV storage
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ Help improve our documentation by contributing directly
+
+
+ Found an error or have suggestions? Let us know
+
+
diff --git a/plugin_dev_en/9242-reverse-invocation-app.en.mdx b/plugin_dev_en/9242-reverse-invocation-app.en.mdx
new file mode 100644
index 00000000..d80c9004
--- /dev/null
+++ b/plugin_dev_en/9242-reverse-invocation-app.en.mdx
@@ -0,0 +1,156 @@
+---
+dimensions:
+ type:
+ primary: implementation
+ detail: advanced
+ level: intermediate
+standard_title: Reverse Invocation App
+language: en
+title: App
+description: This document details how plugins can reverse invoke App services within
+ the Dify platform. It covers three types of interfaces Chat interface (for Chatbot/Agent/Chatflow
+ applications), Workflow interface, and Completion interface, providing entry points,
+ invocation specifications, and practical code examples for each.
+---
+
+Reverse invoking an App means that a plugin can access data from an App within Dify. This module supports both streaming and non-streaming App calls. If you are unfamiliar with the basic concepts of reverse invocation, please first read [Reverse Invocation of Dify Services](/plugin_dev_en/9241-reverse-invocation.en.mdx).
+
+**Interface Types:**
+
+* For `Chatbot/Agent/Chatflow` type applications, they are all chat-based applications and thus share the same input and output parameter types. Therefore, they can be uniformly treated as the **Chat Interface.**
+* For Workflow applications, they occupy a separate **Workflow Interface.**
+* For Completion (text generation application) applications, they occupy a separate **Completion Interface**.
+
+Please note that plugins are only allowed to access Apps within the Workspace where the plugin resides.
+
+### Calling the Chat Interface
+
+#### **Entry Point**
+
+```python
+ self.session.app.chat
+```
+
+#### **Interface Specification**
+
+```python
+ def invoke(
+ self,
+ app_id: str,
+ inputs: dict,
+ response_mode: Literal["streaming", "blocking"],
+ conversation_id: str,
+ files: list,
+ ) -> Generator[dict, None, None] | dict:
+ pass
+```
+
+When `response_mode` is `streaming`, this interface will directly return `Generator[dict]`. Otherwise, it returns `dict`. For specific interface fields, please refer to the return results of `ServiceApi`.
+
+#### **Use Case**
+
+We can call a Chat type App within an `Endpoint` and return the result directly.
+
+```python
+import json
+from typing import Mapping
+from werkzeug import Request, Response
+from dify_plugin import Endpoint
+
+class Duck(Endpoint):
+ def _invoke(self, r: Request, values: Mapping, settings: Mapping) -> Response:
+ """
+ Invokes the endpoint with the given request.
+ """
+ app_id = values["app_id"]
+
+ def generator():
+ # Note: The original example incorrectly called self.session.app.workflow.invoke
+ # It should call self.session.app.chat.invoke for a chat app.
+ # Assuming a chat app is intended here based on the section title.
+ response = self.session.app.chat.invoke(
+ app_id=app_id,
+ inputs={}, # Provide actual inputs as needed
+ response_mode="streaming",
+ conversation_id="some-conversation-id", # Provide a conversation ID if needed
+ files=[]
+ )
+
+ for data in response:
+ yield f"{json.dumps(data)}
"
+
+ return Response(generator(), status=200, content_type="text/html")
+```
+
+### Calling the Workflow Interface
+
+#### **Entry Point**
+
+```python
+ self.session.app.workflow
+```
+
+#### **Interface Specification**
+
+```python
+ def invoke(
+ self,
+ app_id: str,
+ inputs: dict,
+ response_mode: Literal["streaming", "blocking"],
+ files: list,
+ ) -> Generator[dict, None, None] | dict:
+ pass
+```
+
+### Calling the Completion Interface
+
+#### **Entry Point**
+
+```python
+ self.session.app.completion
+```
+
+**Interface Specification**
+
+```python
+ def invoke(
+ self,
+ app_id: str,
+ inputs: dict,
+ response_mode: Literal["streaming", "blocking"],
+ files: list,
+ ) -> Generator[dict, None, None] | dict:
+ pass
+```
+
+## Related Resources
+
+- [Reverse Invocation of Dify Services](/plugin_dev_en/9241-reverse-invocation.en.mdx) - Understand the fundamental concepts of reverse invocation
+- [Reverse Invocation Model](/plugin_dev_en/9242-reverse-invocation-model.en.mdx) - Learn how to call model capabilities within the platform
+- [Reverse Invocation Tool](/plugin_dev_en/9242-reverse-invocation-tool.en.mdx) - Learn how to call other plugins
+- [Develop a Slack Bot Plugin](/plugin_dev_en/0432-develop-a-slack-bot-plugin.en.mdx) - A practical application case using reverse invocation
+- [Develop Extension Plugins](/plugin_dev_en/9231-extension-plugin.en.mdx) - Learn how to develop extension plugins
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ Help improve our documentation by contributing directly
+
+
+ Found an error or have suggestions? Let us know
+
+
diff --git a/plugin_dev_en/9242-reverse-invocation-model.en.mdx b/plugin_dev_en/9242-reverse-invocation-model.en.mdx
new file mode 100644
index 00000000..e4bfe9d5
--- /dev/null
+++ b/plugin_dev_en/9242-reverse-invocation-model.en.mdx
@@ -0,0 +1,310 @@
+---
+dimensions:
+ type:
+ primary: implementation
+ detail: advanced
+ level: intermediate
+standard_title: Reverse Invocation Model
+language: en
+title: Reverse Invocation Model
+description: This document details how plugins can reverse invoke model services within
+ the Dify platform. It covers specific methods for reverse invoking LLM, Summary,
+ TextEmbedding, Rerank, TTS, Speech2Text, and Moderation models. Each model invocation
+ includes its entry point, interface parameter descriptions, practical usage code
+ examples, and best practice recommendations for invoking models.
+---
+
+Reverse invoking a Model refers to the ability of a plugin to call Dify's internal LLM capabilities, including all model types and functions within the platform, such as TTS, Rerank, etc. If you are not familiar with the basic concepts of reverse invocation, please read [Reverse Invocation of Dify Services](/plugin_dev_en/9241-reverse-invocation.en) first.
+
+However, please note that invoking a model requires passing a `ModelConfig` type parameter. Its structure can be referenced in the [General Specifications Definition](/plugin_dev_en/0411-general-specifications.en), and this structure will have slight differences for different types of models.
+
+For example, for `LLM` type models, it also needs to include `completion_params` and `mode` parameters. You can manually construct this structure or use `model-selector` type parameters or configurations.
+
+### Invoke LLM
+
+#### **Entry Point**
+
+```python
+ self.session.model.llm
+```
+
+#### **Endpoint**
+
+```python
+ def invoke(
+ self,
+ model_config: LLMModelConfig,
+ prompt_messages: list[PromptMessage],
+ tools: list[PromptMessageTool] | None = None,
+ stop: list[str] | None = None,
+ stream: bool = True,
+ ) -> Generator[LLMResultChunk, None, None] | LLMResult:
+ pass
+```
+
+Please note that if the model you are invoking does not have `tool_call` capability, the `tools` passed here will not take effect.
+
+#### **Use Case**
+
+If you want to invoke OpenAI's `gpt-4o-mini` model within a `Tool`, please refer to the following example code:
+
+```python
+from collections.abc import Generator
+from typing import Any
+
+from dify_plugin import Tool
+from dify_plugin.entities.model.llm import LLMModelConfig
+from dify_plugin.entities.tool import ToolInvokeMessage
+from dify_plugin.entities.model.message import SystemPromptMessage, UserPromptMessage
+
+class LLMTool(Tool):
+ def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage]:
+ response = self.session.model.llm.invoke(
+ model_config=LLMModelConfig(
+ provider='openai',
+ model='gpt-4o-mini',
+ mode='chat',
+ completion_params={}
+ ),
+ prompt_messages=[
+ SystemPromptMessage(
+ content='you are a helpful assistant'
+ ),
+ UserPromptMessage(
+ content=tool_parameters.get('query')
+ )
+ ],
+ stream=True
+ )
+
+ for chunk in response:
+ if chunk.delta.message:
+ assert isinstance(chunk.delta.message.content, str)
+ yield self.create_text_message(text=chunk.delta.message.content)
+```
+
+Note that the `query` parameter from `tool_parameters` is passed in the code.
+
+### **Best Practice**
+
+It is not recommended to manually construct `LLMModelConfig`. Instead, allow users to select the model they want to use on the UI. In this case, you can modify the tool's parameter list by adding a `model` parameter as follows:
+
+```yaml
+identity:
+ name: llm
+ author: Dify
+ label:
+ en_US: LLM
+ zh_Hans: LLM
+ pt_BR: LLM
+description:
+ human:
+ en_US: A tool for invoking a large language model
+ zh_Hans: 用于调用大型语言模型的工具
+ pt_BR: A tool for invoking a large language model
+ llm: A tool for invoking a large language model
+parameters:
+ - name: prompt
+ type: string
+ required: true
+ label:
+ en_US: Prompt string
+ zh_Hans: 提示字符串
+ pt_BR: Prompt string
+ human_description:
+ en_US: used for searching
+ zh_Hans: 用于搜索网页内容
+ pt_BR: used for searching
+ llm_description: key words for searching
+ form: llm
+ - name: model
+ type: model-selector
+ scope: llm
+ required: true
+ label:
+ en_US: Model
+ zh_Hans: 使用的模型
+ pt_BR: Model
+ human_description:
+ en_US: Model
+ zh_Hans: 使用的模型
+ pt_BR: Model
+ llm_description: which Model to invoke
+ form: form
+extra:
+ python:
+ source: tools/llm.py
+```
+
+Please note that in this example, the `scope` of `model` is specified as `llm`. This means the user can only select `llm` type parameters. Thus, the code from the previous use case can be modified as follows:
+
+```python
+from collections.abc import Generator
+from typing import Any
+
+from dify_plugin import Tool
+from dify_plugin.entities.model.llm import LLMModelConfig
+from dify_plugin.entities.tool import ToolInvokeMessage
+from dify_plugin.entities.model.message import SystemPromptMessage, UserPromptMessage
+
+class LLMTool(Tool):
+ def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage]:
+ response = self.session.model.llm.invoke(
+ model_config=tool_parameters.get('model'),
+ prompt_messages=[
+ SystemPromptMessage(
+ content='you are a helpful assistant'
+ ),
+ UserPromptMessage(
+ content=tool_parameters.get('query') # Assuming 'query' is still needed, otherwise use 'prompt' from parameters
+ )
+ ],
+ stream=True
+ )
+
+ for chunk in response:
+ if chunk.delta.message:
+ assert isinstance(chunk.delta.message.content, str)
+ yield self.create_text_message(text=chunk.delta.message.content)
+```
+
+### Invoke Summary
+
+You can request this endpoint to summarize a piece of text. It will use the system model within your current workspace to summarize the text.
+
+**Entry Point**
+
+```python
+ self.session.model.summary
+```
+
+**Endpoint**
+
+* `text` is the text to be summarized.
+* `instruction` is the additional instruction you want to add, allowing you to summarize the text stylistically.
+
+```python
+ def invoke(
+ self, text: str, instruction: str,
+ ) -> str:
+```
+
+### Invoke TextEmbedding
+
+**Entry Point**
+
+```python
+ self.session.model.text_embedding
+```
+
+**Endpoint**
+
+```python
+ def invoke(
+ self, model_config: TextEmbeddingResult, texts: list[str]
+ ) -> TextEmbeddingResult:
+ pass
+```
+
+### Invoke Rerank
+
+**Entry Point**
+
+```python
+ self.session.model.rerank
+```
+
+**Endpoint**
+
+```python
+ def invoke(
+ self, model_config: RerankModelConfig, docs: list[str], query: str
+ ) -> RerankResult:
+ pass
+```
+
+### Invoke TTS
+
+**Entry Point**
+
+```python
+ self.session.model.tts
+```
+
+**Endpoint**
+
+```python
+ def invoke(
+ self, model_config: TTSModelConfig, content_text: str
+ ) -> Generator[bytes, None, None]:
+ pass
+```
+
+Please note that the `bytes` stream returned by the `tts` endpoint is an `mp3` audio byte stream. Each iteration returns a complete audio segment. If you want to perform more in-depth processing tasks, please choose an appropriate library.
+
+### Invoke Speech2Text
+
+**Entry Point**
+
+```python
+ self.session.model.speech2text
+```
+
+**Endpoint**
+
+```python
+ def invoke(
+ self, model_config: Speech2TextModelConfig, file: IO[bytes]
+ ) -> str:
+ pass
+```
+
+Where `file` is an audio file encoded in `mp3` format.
+
+### Invoke Moderation
+
+**Entry Point**
+
+```python
+ self.session.model.moderation
+```
+
+**Endpoint**
+
+```python
+ def invoke(self, model_config: ModerationModelConfig, text: str) -> bool:
+ pass
+```
+
+If this endpoint returns `true`, it indicates that the `text` contains sensitive content.
+
+## Related Resources
+
+- [Reverse Invocation of Dify Services](/plugin_dev_en/9241-reverse-invocation.en) - Understand the fundamental concepts of reverse invocation
+- [Reverse Invocation of App](/plugin_dev_en/9242-reverse-invocation-app.en) - Learn how to invoke Apps within the platform
+- [Reverse Invocation of Tool](/plugin_dev_en/9242-reverse-invocation-tool.en) - Learn how to invoke other plugins
+- [Model Plugin Development Guide](/plugin_dev_en/0211-getting-started-new-model.en) - Learn how to develop custom model plugins
+- [Model Designing Rules](/plugin_dev_en/0411-model-designing-rules.en) - Understand the design principles of model plugins
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ Help improve our documentation by contributing directly
+
+
+ Found an error or have suggestions? Let us know
+
+
diff --git a/plugin_dev_en/9242-reverse-invocation-tool.en.mdx b/plugin_dev_en/9242-reverse-invocation-tool.en.mdx
new file mode 100644
index 00000000..9e0f7cc5
--- /dev/null
+++ b/plugin_dev_en/9242-reverse-invocation-tool.en.mdx
@@ -0,0 +1,118 @@
+---
+dimensions:
+ type:
+ primary: implementation
+ detail: advanced
+ level: intermediate
+standard_title: Reverse Invocation Tool
+language: en
+title: Tool
+description: This document details how plugins can reverse invoke Tool services within
+ the Dify platform. It covers three types of tool invocation methods calling installed
+ tools (Built-in Tool), calling Workflow as Tool, and calling custom tools (Custom
+ Tool). Each method includes corresponding entry points and interface parameter descriptions.
+---
+
+Reverse invoking a Tool means that a plugin can call other tool-type plugins within the Dify platform. If you are unfamiliar with the basic concepts of reverse invocation, please first read [Reverse Invocation of Dify Services](/plugin_dev_en/9241-reverse-invocation.en.mdx).
+
+Consider the following scenarios:
+
+* A tool-type plugin has implemented a function, but the result is not as expected, requiring post-processing of the data.
+* A task requires a web scraper, and you want the flexibility to choose the scraping service.
+* You need to aggregate results from multiple tools, but it's difficult to handle using a Workflow application.
+
+In these cases, you need to call other existing tools within your plugin. These tools might be from the marketplace, a self-built Workflow as a Tool, or a custom tool.
+
+These requirements can be met by calling the `self.session.tool` field of the plugin.
+
+### Calling Installed Tools
+
+Allows the plugin to call various tools installed in the current Workspace, including other tool-type plugins.
+
+**Entry Point**
+
+```python
+ self.session.tool
+```
+
+**Interface**
+
+```python
+ def invoke_builtin_tool(
+ self, provider: str, tool_name: str, parameters: dict[str, Any]
+ ) -> Generator[ToolInvokeMessage, None, None]:
+ pass
+```
+
+Here, `provider` is the plugin ID plus the tool provider name, formatted like `langgenius/google/google`. `tool_name` is the specific tool name, and `parameters` are the arguments passed to the tool.
+
+### Calling Workflow as Tool
+
+For more information on Workflow as Tool, please refer to the [Tool Plugin documentation](/plugin_dev_en/0222-tool-plugin.en.mdx). (Note: Original link `/plugin_dev_zh/9223-tool.zh` does not exist in English list, linked to closest match).
+
+**Entry Point**
+
+```python
+ self.session.tool
+```
+
+**Interface**
+
+```python
+ def invoke_workflow_tool(
+ self, provider: str, tool_name: str, parameters: dict[str, Any]
+ ) -> Generator[ToolInvokeMessage, None, None]:
+ pass
+```
+
+In this case, `provider` is the ID of this tool, and `tool_name` is specified during the creation of the tool.
+
+### Calling Custom Tool
+
+**Entry Point**
+
+```python
+ self.session.tool
+```
+
+**Interface**
+
+```python
+ def invoke_api_tool(
+ self, provider: str, tool_name: str, parameters: dict[str, Any]
+ ) -> Generator[ToolInvokeMessage, None, None]:
+ pass
+```
+
+Here, `provider` is the ID of this tool, and `tool_name` is the `operation_id` from the OpenAPI specification. If it doesn't exist, it's the `tool_name` automatically generated by Dify, which can be found on the tool management page.
+
+## Related Resources
+
+- [Reverse Invocation of Dify Services](/plugin_dev_en/9241-reverse-invocation.en.mdx) - Understand the fundamental concepts of reverse invocation
+- [Reverse Invocation App](/plugin_dev_en/9242-reverse-invocation-app.en.mdx) - Learn how to call Apps within the platform
+- [Reverse Invocation Model](/plugin_dev_en/9242-reverse-invocation-model.en.mdx) - Learn how to call model capabilities within the platform
+- [Tool Plugin Development Guide](/plugin_dev_en/0211-getting-started-dify-tool.en.mdx) - Learn how to develop tool plugins
+- [Advanced Tool Plugins](/plugin_dev_en/0222-tool-plugin.en.mdx) - Learn about advanced features like Workflow as Tool (Note: Original link `/plugin_dev_zh/9223-tool.zh` does not exist in English list, linked to closest match)
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ Help improve our documentation by contributing directly
+
+
+ Found an error or have suggestions? Let us know
+
+
diff --git a/plugin_dev_en/9243-customizable-model.en.mdx b/plugin_dev_en/9243-customizable-model.en.mdx
new file mode 100644
index 00000000..8064083e
--- /dev/null
+++ b/plugin_dev_en/9243-customizable-model.en.mdx
@@ -0,0 +1,376 @@
+---
+dimensions:
+ type:
+ primary: implementation
+ detail: advanced
+ level: advanced
+standard_title: Customizable Model
+language: en
+title: Integrating Custom Models
+description: This document details how to integrate custom models into Dify, using
+ the Xinference model as an example. It covers the complete process, including creating
+ model provider files, writing code based on model type, implementing model invocation
+ logic, handling exceptions, debugging, and publishing. It specifically details the
+ implementation of core methods like LLM invocation, token calculation, credential
+ validation, and parameter generation.
+---
+
+A **custom model** refers to an LLM that you deploy or configure on your own. This document uses the [Xinference model](https://inference.readthedocs.io/en/latest/) as an example to demonstrate how to integrate a custom model into your **model plugin**.
+
+By default, a custom model automatically includes two parameters—its **model type** and **model name**—and does not require additional definitions in the provider YAML file.
+
+You do not need to implement `validate_provider_credential` in your provider configuration file. During runtime, based on the user’s choice of model type or model name, Dify automatically calls the corresponding model layer’s `validate_credentials` method to verify credentials.
+
+## Integrating a Custom Model Plugin
+
+Below are the steps to integrate a custom model:
+
+1. **Create a Model Provider File**\
+ Identify the model types your custom model will include.
+2. **Create Code Files by Model Type**\
+ Depending on the model’s type (e.g., `llm` or `text_embedding`), create separate code files. Ensure that each model type is organized into distinct logical layers for easier maintenance and future expansion.
+3. **Develop the Model Invocation Logic**\
+ Within each model-type module, create a Python file named for that model type (for example, `llm.py`). Define a class in the file that implements the specific model logic, conforming to the system’s model interface specifications.
+4. **Debug the Plugin**\
+ Write unit and integration tests for the new provider functionality, ensuring that all components work as intended.
+
+***
+
+### 1. **Create a Model Provider File**
+
+In your plugin’s `/provider` directory, create a `xinference.yaml` file.
+
+The `Xinference` family of models supports **LLM**, **Text Embedding**, and **Rerank** model types, so your `xinference.yaml` must include all three.
+
+**Example:**
+
+```yaml
+provider: xinference # Identifies the provider
+label: # Display name; can set both en_US (English) and zh_Hans (Chinese). If zh_Hans is not set, en_US is used by default.
+ en_US: Xorbits Inference
+icon_small: # Small icon; store in the _assets folder of this provider’s directory. The same multi-language logic applies as with label.
+ en_US: icon_s_en.svg
+icon_large: # Large icon
+ en_US: icon_l_en.svg
+help: # Help information
+ title:
+ en_US: How to deploy Xinference
+ zh_Hans: 如何部署 Xinference
+ url:
+ en_US: https://github.com/xorbitsai/inference
+
+supported_model_types: # Model types Xinference supports: LLM/Text Embedding/Rerank
+- llm
+- text-embedding
+- rerank
+
+configurate_methods: # Xinference is locally deployed and does not offer predefined models. Refer to its documentation to learn which model to use. Thus, we choose a customizable-model approach.
+- customizable-model
+
+provider_credential_schema:
+ credential_form_schemas:
+```
+
+Next, define the `provider_credential_schema`. Since `Xinference` supports text-generation, embeddings, and reranking models, you can configure it as follows:
+
+```yaml
+provider_credential_schema:
+ credential_form_schemas:
+ - variable: model_type
+ type: select
+ label:
+ en_US: Model type
+ zh_Hans: 模型类型
+ required: true
+ options:
+ - value: text-generation
+ label:
+ en_US: Language Model
+ zh_Hans: 语言模型
+ - value: embeddings
+ label:
+ en_US: Text Embedding
+ - value: reranking
+ label:
+ en_US: Rerank
+```
+
+Every model in Xinference requires a `model_name`:
+
+```yaml
+ - variable: model_name
+ type: text-input
+ label:
+ en_US: Model name
+ zh_Hans: 模型名称
+ required: true
+ placeholder:
+ zh_Hans: 填写模型名称
+ en_US: Input model name
+```
+
+Because Xinference must be locally deployed, users need to supply the server address (server\_url) and model UID. For instance:
+
+```yaml
+ - variable: server_url
+ label:
+ zh_Hans: 服务器 URL
+ en_US: Server url
+ type: text-input
+ required: true
+ placeholder:
+ zh_Hans: 在此输入 Xinference 的服务器地址,如 https://example.com/xxx
+ en_US: Enter the url of your Xinference, for example https://example.com/xxx
+
+ - variable: model_uid
+ label:
+ zh_Hans: 模型 UID
+ en_US: Model uid
+ type: text-input
+ required: true
+ placeholder:
+ zh_Hans: 在此输入您的 Model UID
+ en_US: Enter the model uid
+```
+
+Once you’ve defined these parameters, the YAML configuration for your custom model provider is complete. Next, create the functional code files for each model defined in this config.
+
+### 2. Develop the Model Code
+
+Since Xinference supports llm, rerank, speech2text, and tts, you should create corresponding directories under /models, each containing its respective feature code.
+
+Below is an example for an llm-type model. You’d create a file named llm.py, then define a class—such as XinferenceAILargeLanguageModel—that extends \_\_base.large\_language\_model.LargeLanguageModel. This class should include:
+
+* **LLM Invocation**
+
+The core method for invoking the LLM, supporting both streaming and synchronous responses:
+
+```python
+def _invoke(
+ self,
+ model: str,
+ credentials: dict,
+ prompt_messages: list[PromptMessage],
+ model_parameters: dict,
+ tools: Optional[list[PromptMessageTool]] = None,
+ stop: Optional[list[str]] = None,
+ stream: bool = True,
+ user: Optional[str] = None
+) -> Union[LLMResult, Generator]:
+ """
+ Invoke the large language model.
+
+ :param model: model name
+ :param credentials: model credentials
+ :param prompt_messages: prompt messages
+ :param model_parameters: model parameters
+ :param tools: tools for tool calling
+ :param stop: stop words
+ :param stream: determines if response is streamed
+ :param user: unique user id
+ :return: full response or a chunk generator
+ """
+```
+
+You’ll need two separate functions to handle streaming and synchronous responses. Python treats any function containing `yield` as a generator returning type `Generator`, so it’s best to split them:
+
+```yaml
+def _invoke(self, stream: bool, **kwargs) -> Union[LLMResult, Generator]:
+ if stream:
+ return self._handle_stream_response(**kwargs)
+ return self._handle_sync_response(**kwargs)
+
+def _handle_stream_response(self, **kwargs) -> Generator:
+ for chunk in response:
+ yield chunk
+
+def _handle_sync_response(self, **kwargs) -> LLMResult:
+ return LLMResult(**response)
+```
+
+* **Pre-calculating Input Tokens**
+
+If your model doesn’t provide a token-counting interface, simply return 0:
+
+```python
+def get_num_tokens(
+ self,
+ model: str,
+ credentials: dict,
+ prompt_messages: list[PromptMessage],
+ tools: Optional[list[PromptMessageTool]] = None
+) -> int:
+ """
+ Get the number of tokens for the given prompt messages.
+ """
+ return 0
+```
+
+Alternatively, you can call `self._get_num_tokens_by_gpt2(text: str)` from the `AIModel` base class, which uses a GPT-2 tokenizer. Remember this is an approximation and may not match your model exactly.
+
+* **Validating Model Credentials**
+
+Similar to provider-level credential checks, but scoped to a single model:
+
+```python
+def validate_credentials(self, model: str, credentials: dict) -> None:
+ """
+ Validate model credentials.
+ """
+```
+
+* **Dynamic Model Parameters Schema**
+
+Unlike [predefined models](/en/plugins/quick-start/develop-plugins/model-plugin/predefined-model), no YAML is defining which parameters a model supports. You must generate a parameter schema dynamically.
+
+For example, Xinference supports `max_tokens`, `temperature`, and `top_p`. Some other providers (e.g., `OpenLLM`) may support parameters like `top_k` only for certain models. This means you need to adapt your schema to each model’s capabilities:
+
+```python
+def get_customizable_model_schema(self, model: str, credentials: dict) -> AIModelEntity | None:
+ """
+ used to define customizable model schema
+ """
+ rules = [
+ ParameterRule(
+ name='temperature', type=ParameterType.FLOAT,
+ use_template='temperature',
+ label=I18nObject(
+ zh_Hans='温度', en_US='Temperature'
+ )
+ ),
+ ParameterRule(
+ name='top_p', type=ParameterType.FLOAT,
+ use_template='top_p',
+ label=I18nObject(
+ zh_Hans='Top P', en_US='Top P'
+ )
+ ),
+ ParameterRule(
+ name='max_tokens', type=ParameterType.INT,
+ use_template='max_tokens',
+ min=1,
+ default=512,
+ label=I18nObject(
+ zh_Hans='最大生成长度', en_US='Max Tokens'
+ )
+ )
+ ]
+
+ # if model is A, add top_k to rules
+ if model == 'A':
+ rules.append(
+ ParameterRule(
+ name='top_k', type=ParameterType.INT,
+ use_template='top_k',
+ min=1,
+ default=50,
+ label=I18nObject(
+ zh_Hans='Top K', en_US='Top K'
+ )
+ )
+ )
+
+ """
+ some NOT IMPORTANT code here
+ """
+
+ entity = AIModelEntity(
+ model=model,
+ label=I18nObject(
+ en_US=model
+ ),
+ fetch_from=FetchFrom.CUSTOMIZABLE_MODEL,
+ model_type=model_type,
+ model_properties={
+ ModelPropertyKey.MODE: ModelType.LLM,
+ },
+ parameter_rules=rules
+ )
+
+ return entity
+```
+
+* **Error Mapping**
+
+When an error occurs during model invocation, map it to the appropriate InvokeError type recognized by the runtime. This lets Dify handle different errors in a standardized manner:
+
+Runtime Errors:
+
+```
+• `InvokeConnectionError`
+• `InvokeServerUnavailableError`
+• `InvokeRateLimitError`
+• `InvokeAuthorizationError`
+• `InvokeBadRequestError`
+```
+
+```python
+@property
+def _invoke_error_mapping(self) -> dict[type[InvokeError], list[type[Exception]]]:
+ """
+ Map model invocation errors to unified error types.
+ The key is the error type thrown to the caller.
+ The value is the error type thrown by the model, which needs to be mapped to a
+ unified Dify error for consistent handling.
+ """
+ # return {
+ # InvokeConnectionError: [requests.exceptions.ConnectionError],
+ # ...
+ # }
+```
+
+For more details on interface methods, see the [Model Documentation](https://docs.dify.ai/zh-hans/plugins/schema-definition/model).
+
+To view the complete code files discussed in this guide, visit the [GitHub Repository](https://github.com/langgenius/dify-official-plugins/tree/main/models/xinference).
+
+### 3. Debug the Plugin
+
+After finishing development, test the plugin to ensure it runs correctly. For more details, refer to:
+
+
+
+
+### 4. Publish the Plugin
+
+If you’d like to list this plugin on the Dify Marketplace, see:
+
+Publish to Dify Marketplace
+
+## Explore More
+
+**Quick Start:**
+
+* [Develop Extension Plugin](/en/plugins/quick-start/develop-plugins/extension-plugin)
+* [Develop Tool Plugin](/en/plugins/quick-start/develop-plugins/tool-plugin)
+* [Bundle Plugins: Package Multiple Plugins](/en/plugins/quick-start/develop-plugins/bundle)
+
+**Plugins Endpoint Docs:**
+
+* [Manifest](/en/plugins/schema-definition/manifest) Structure
+* [Endpoint](/en/plugins/schema-definition/endpoint) Definitions
+* [Reverse-Invocation of the Dify Service](/en/plugins/schema-definition/reverse-invocation-of-the-dify-service)
+* [Tools](/en/plugins/schema-definition/tool)
+* [Models](/en/plugins/schema-definition/model)
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ Help improve our documentation by contributing directly
+
+
+ Found an error or have suggestions? Let us know
+
+
diff --git a/plugin_dev_en/9243-reverse-invocation-node.en.mdx b/plugin_dev_en/9243-reverse-invocation-node.en.mdx
new file mode 100644
index 00000000..71ba7c08
--- /dev/null
+++ b/plugin_dev_en/9243-reverse-invocation-node.en.mdx
@@ -0,0 +1,125 @@
+---
+dimensions:
+ type:
+ primary: implementation
+ detail: advanced
+ level: advanced
+standard_title: Reverse Invocation Node
+language: en
+title: Node
+description: This document describes how plugins can reverse invoke the functionality
+ of Chatflow/Workflow application nodes within the Dify platform. It primarily covers
+ the invocation methods for two specific nodes, ParameterExtractor and QuestionClassifier.
+ The document details the entry points, interface parameters, and example code for
+ invoking these two nodes.
+---
+
+Reverse invoking a Node means that a plugin can access the capabilities of certain nodes within a Dify Chatflow/Workflow application.
+
+The `ParameterExtractor` and `QuestionClassifier` nodes in `Workflow` encapsulate complex Prompt and code logic, enabling tasks that are difficult to solve with hardcoding through LLMs. Plugins can call these two nodes.
+
+### Calling the Parameter Extractor Node
+
+#### **Entry Point**
+
+```python
+ self.session.workflow_node.parameter_extractor
+```
+
+#### **Interface**
+
+```python
+ def invoke(
+ self,
+ parameters: list[ParameterConfig],
+ model: ModelConfig,
+ query: str,
+ instruction: str = "",
+ ) -> NodeResponse
+ pass
+```
+
+Here, `parameters` is a list of parameters to be extracted, `model` conforms to the `LLMModelConfig` specification, `query` is the source text for parameter extraction, and `instruction` provides any additional instructions that might be needed for the LLM. For the structure of `NodeResponse`, please refer to this [document](/plugin_dev_en/0411-general-specifications.en.mdx#noderesponse).
+
+#### **Use Case**
+
+To extract a person's name from a conversation, you can refer to the following code:
+
+```python
+from collections.abc import Generator
+from dify_plugin.entities.tool import ToolInvokeMessage
+from dify_plugin import Tool
+from dify_plugin.entities.workflow_node import ModelConfig, ParameterConfig, NodeResponse # Assuming NodeResponse is importable
+
+class ParameterExtractorTool(Tool):
+ def _invoke(
+ self, tool_parameters: dict
+ ) -> Generator[ToolInvokeMessage, None, None]:
+ response: NodeResponse = self.session.workflow_node.parameter_extractor.invoke(
+ parameters=[
+ ParameterConfig(
+ name="name",
+ description="name of the person",
+ required=True,
+ type="string",
+ )
+ ],
+ model=ModelConfig(
+ provider="langgenius/openai/openai",
+ name="gpt-4o-mini",
+ completion_params={},
+ ),
+ query="My name is John Doe",
+ instruction="Extract the name of the person",
+ )
+
+ # Assuming NodeResponse has an 'outputs' attribute which is a dictionary
+ extracted_name = response.outputs.get("name", "Name not found")
+ yield self.create_text_message(extracted_name)
+```
+
+### Calling the Question Classifier Node
+
+#### **Entry Point**
+
+```python
+ self.session.workflow_node.question_classifier
+```
+
+#### **Interface**
+
+```python
+ def invoke(
+ self,
+ classes: list[ClassConfig], # Assuming ClassConfig is defined/imported
+ model: ModelConfig,
+ query: str,
+ instruction: str = "",
+ ) -> NodeResponse:
+ pass
+```
+
+The interface parameters are consistent with `ParameterExtractor`. The final result is stored in `NodeResponse.outputs['class_name']`.
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ Help improve our documentation by contributing directly
+
+
+ Found an error or have suggestions? Let us know
+
+
diff --git a/plugin_dev_en/9433-agent-strategy-plugin.en.mdx b/plugin_dev_en/9433-agent-strategy-plugin.en.mdx
new file mode 100644
index 00000000..6ea21933
--- /dev/null
+++ b/plugin_dev_en/9433-agent-strategy-plugin.en.mdx
@@ -0,0 +1,1119 @@
+---
+dimensions:
+ type:
+ primary: reference
+ detail: examples
+ level: advanced
+standard_title: Agent Strategy Plugin
+language: en
+title: Agent Strategy Plugin
+description: This document details how to develop an Agent strategy plugin, covering
+ the entire process from initializing the plugin template to invoking models, invoking
+ tools, outputting logs, and packaging for release. It provides detailed code examples,
+ including how to implement automated tool invocation features that help LLMs perform
+ reasoning or decision-making logic.
+---
+
+An **Agent Strategy Plugin** helps an LLM carry out tasks like reasoning or decision-making, including choosing and calling tools, as well as handling results. This allows the system to address problems more autonomously.
+
+Below, you’ll see how to develop a plugin that supports **Function Calling** to automatically fetch the current time.
+
+### Prerequisites
+
+- Dify plugin scaffolding tool
+- Python environment (version ≥ 3.12)
+
+For details on preparing the plugin development tool, see [Initializing the Development Tool](/plugin_dev_en/0221-initialize-development-tools.en).
+
+
+**Tip**: Run `dify version` in your terminal to confirm that the scaffolding tool is installed.
+
+
+---
+
+### 1. Initializing the Plugin Template
+
+Run the following command to create a development template for your Agent plugin:
+
+```
+dify plugin init
+```
+
+Follow the on-screen prompts and refer to the sample comments for guidance.
+
+```bash
+➜ Dify Plugins Developing dify plugin init
+Edit profile of the plugin
+Plugin name (press Enter to next step): # Enter the plugin name
+Author (press Enter to next step): Author name # Enter the plugin author
+Description (press Enter to next step): Description # Enter the plugin description
+---
+Select the language you want to use for plugin development, and press Enter to con
+BTW, you need Python 3.12+ to develop the Plugin if you choose Python.
+-> python # Select Python environment
+ go (not supported yet)
+---
+Based on the ability you want to extend, we have divided the Plugin into four type
+
+- Tool: It's a tool provider, but not only limited to tools, you can implement an
+- Model: Just a model provider, extending others is not allowed.
+- Extension: Other times, you may only need a simple http service to extend the fu
+- Agent Strategy: Implement your own logics here, just by focusing on Agent itself
+
+What's more, we have provided the template for you, you can choose one of them b
+ tool
+-> agent-strategy # Select Agent strategy template
+ llm
+ text-embedding
+---
+Configure the permissions of the plugin, use up and down to navigate, tab to sel
+Backwards Invocation:
+Tools:
+ Enabled: [✔] You can invoke tools inside Dify if it's enabled # Enabled by default
+Models:
+ Enabled: [✔] You can invoke models inside Dify if it's enabled # Enabled by default
+ LLM: [✔] You can invoke LLM models inside Dify if it's enabled # Enabled by default
+ Text Embedding: [✘] You can invoke text embedding models inside Dify if it'
+ Rerank: [✘] You can invoke rerank models inside Dify if it's enabled
+...
+```
+
+After initialization, you’ll get a folder containing all the resources needed for plugin development. Familiarizing yourself with the overall structure of an Agent Strategy Plugin will streamline the development process:
+
+```text
+├── GUIDE.md # User guide and documentation
+├── PRIVACY.md # Privacy policy and data handling guidelines
+├── README.md # Project overview and setup instructions
+├── _assets/ # Static assets directory
+│ └── icon.svg # Agent strategy provider icon/logo
+├── main.py # Main application entry point
+├── manifest.yaml # Basic plugin configuration
+├── provider/ # Provider configurations directory
+│ └── basic_agent.yaml # Your agent provider settings
+├── requirements.txt # Python dependencies list
+└── strategies/ # Strategy implementation directory
+ ├── basic_agent.py # Basic agent strategy implementation
+ └── basic_agent.yaml # Basic agent strategy configuration
+```
+
+All key functionality for this plugin is in the `strategies/` directory.
+
+---
+
+### 2. Developing the Plugin
+
+Agent Strategy Plugin development revolves around two files:
+
+- **Plugin Declaration**: `strategies/basic_agent.yaml`
+- **Plugin Implementation**: `strategies/basic_agent.py`
+
+#### 2.1 Defining Parameters
+
+To build an Agent plugin, start by specifying the necessary parameters in `strategies/basic_agent.yaml`. These parameters define the plugin’s core features, such as calling an LLM or using tools.
+
+We recommend including the following four parameters first:
+
+1. **model**: The large language model to call (e.g., GPT-4, GPT-4o-mini).
+2. **tools**: A list of tools that enhance your plugin’s functionality.
+3. **query**: The user input or prompt content sent to the model.
+4. **maximum_iterations**: The maximum iteration count to prevent excessive computation.
+
+Example Code:
+
+```yaml
+identity:
+ name: basic_agent # the name of the agent_strategy
+ author: novice # the author of the agent_strategy
+ label:
+ en_US: BasicAgent # the engilish label of the agent_strategy
+description:
+ en_US: BasicAgent # the english description of the agent_strategy
+parameters:
+ - name: model # the name of the model parameter
+ type: model-selector # model-type
+ scope: tool-call&llm # the scope of the parameter
+ required: true
+ label:
+ en_US: Model
+ zh_Hans: 模型
+ pt_BR: Model
+ - name: tools # the name of the tools parameter
+ type: array[tools] # the type of tool parameter
+ required: true
+ label:
+ en_US: Tools list
+ zh_Hans: 工具列表
+ pt_BR: Tools list
+ - name: query # the name of the query parameter
+ type: string # the type of query parameter
+ required: true
+ label:
+ en_US: Query
+ zh_Hans: 查询
+ pt_BR: Query
+ - name: maximum_iterations
+ type: number
+ required: false
+ default: 5
+ label:
+ en_US: Maxium Iterations
+ zh_Hans: 最大迭代次数
+ pt_BR: Maxium Iterations
+ max: 50 # if you set the max and min value, the display of the parameter will be a slider
+ min: 1
+extra:
+ python:
+ source: strategies/basic_agent.py
+```
+
+Once you’ve configured these parameters, the plugin will automatically generate a user-friendly interface so you can easily manage them:
+
+
+
+#### 2.2 Retrieving Parameters and Execution
+
+After users fill out these basic fields, your plugin needs to process the submitted parameters. In `strategies/basic_agent.py`, define a parameter class for the Agent, then retrieve and apply these parameters in your logic.
+
+Verify incoming parameters:
+
+```python
+from dify_plugin.entities.agent import AgentInvokeMessage
+from dify_plugin.interfaces.agent import AgentModelConfig, AgentStrategy, ToolEntity
+from pydantic import BaseModel
+
+class BasicParams(BaseModel):
+ maximum_iterations: int
+ model: AgentModelConfig
+ tools: list[ToolEntity]
+ query: str
+```
+
+After getting the parameters, the specific business logic is executed:
+
+```python
+class BasicAgentAgentStrategy(AgentStrategy):
+ def _invoke(self, parameters: dict[str, Any]) -> Generator[AgentInvokeMessage]:
+ params = BasicParams(**parameters)
+```
+
+### 3. Invoking the Model
+
+In an Agent Strategy Plugin, **invoking the model** is central to the workflow. You can invoke an LLM efficiently using `session.model.llm.invoke()` from the SDK, handling text generation, dialogue, and so forth.
+
+If you want the LLM **handle tools**, ensure it outputs structured parameters to match a tool’s interface. In other words, the LLM must produce input arguments that the tool can accept based on the user’s instructions.
+
+Construct the following parameters:
+
+* model
+* prompt\_messages
+* tools
+* stop
+* stream
+
+Example code for method definition:
+
+```python
+def invoke(
+ self,
+ model_config: LLMModelConfig,
+ prompt_messages: list[PromptMessage],
+ tools: list[PromptMessageTool] | None = None,
+ stop: list[str] | None = None,
+ stream: bool = True,
+ ) -> Generator[LLMResultChunk, None, None] | LLMResult:...
+```
+
+To view the complete functionality implementation, please refer to the Example Code for model invocation.
+
+This code achieves the following functionality: after a user inputs a command, the Agent strategy plugin automatically calls the LLM, constructs the necessary parameters for tool invocation based on the generated results, and enables the model to flexibly dispatch integrated tools to efficiently complete complex tasks.
+
+
+
+### 4. Handle a Tool
+
+After specifying the tool parameters, the Agent Strategy Plugin must actually call these tools. Use `session.tool.invoke()` to make those requests.
+
+Construct the following parameters:
+
+- provider
+- tool\_name
+- parameters
+
+Example code for method definition:
+
+```python
+ def invoke(
+ self,
+ provider_type: ToolProviderType,
+ provider: str,
+ tool_name: str,
+ parameters: dict[str, Any],
+ ) -> Generator[ToolInvokeMessage, None, None]:...
+```
+
+If you’d like the LLM itself to generate the parameters needed for tool calls, you can do so by combining the model’s output with your tool-calling code.
+
+```python
+tool_instances = (
+ {tool.identity.name: tool for tool in params.tools} if params.tools else {}
+)
+for tool_call_id, tool_call_name, tool_call_args in tool_calls:
+ tool_instance = tool_instances[tool_call_name]
+ self.session.tool.invoke(
+ provider_type=ToolProviderType.BUILT_IN,
+ provider=tool_instance.identity.provider,
+ tool_name=tool_instance.identity.name,
+ parameters={**tool_instance.runtime_parameters, **tool_call_args},
+ )
+```
+
+With this in place, your Agent Strategy Plugin can automatically perform **Function Calling**—for instance, retrieving the current time.
+
+
+
+### 5. Creating Logs
+
+Often, multiple steps are necessary to complete a complex task in an **Agent Strategy Plugin**. It’s crucial for developers to track each step’s results, analyze the decision process, and optimize strategy. Using `create_log_message` and `finish_log_message` from the SDK, you can log real-time states before and after calls, aiding in quick problem diagnosis.
+
+For example:
+
+- Log a “starting model call” message before calling the model, clarifying the task’s execution progress.
+- Log a “call succeeded” message once the model responds, ensuring the model’s output can be traced end to end.
+
+```python
+model_log = self.create_log_message(
+ label=f"{params.model.model} Thought",
+ data={},
+ metadata={"start_at": model_started_at, "provider": params.model.provider},
+ status=ToolInvokeMessage.LogMessage.LogStatus.START,
+ )
+yield model_log
+self.session.model.llm.invoke(...)
+yield self.finish_log_message(
+ log=model_log,
+ data={
+ "output": response,
+ "tool_name": tool_call_names,
+ "tool_input": tool_call_inputs,
+ },
+ metadata={
+ "started_at": model_started_at,
+ "finished_at": time.perf_counter(),
+ "elapsed_time": time.perf_counter() - model_started_at,
+ "provider": params.model.provider,
+ },
+)
+```
+
+When the setup is complete, the workflow log will output the execution results:
+
+
+
+If multiple rounds of logs occur, you can structure them hierarchically by setting a `parent` parameter in your log calls, making them easier to follow.
+
+Reference method:
+
+```python
+function_call_round_log = self.create_log_message(
+ label="Function Call Round1 ",
+ data={},
+ metadata={},
+)
+yield function_call_round_log
+
+model_log = self.create_log_message(
+ label=f"{params.model.model} Thought",
+ data={},
+ metadata={"start_at": model_started_at, "provider": params.model.provider},
+ status=ToolInvokeMessage.LogMessage.LogStatus.START,
+ # add parent log
+ parent=function_call_round_log,
+)
+yield model_log
+```
+
+#### Sample code for agent-plugin functions
+
+
+
+ #### Invoke Model
+
+The following code demonstrates how to give the Agent strategy plugin the ability to invoke the model:
+
+```python
+import json
+from collections.abc import Generator
+from typing import Any, cast
+
+from dify_plugin.entities.agent import AgentInvokeMessage
+from dify_plugin.entities.model.llm import LLMModelConfig, LLMResult, LLMResultChunk
+from dify_plugin.entities.model.message import (
+ PromptMessageTool,
+ UserPromptMessage,
+)
+from dify_plugin.entities.tool import ToolInvokeMessage, ToolParameter, ToolProviderType
+from dify_plugin.interfaces.agent import AgentModelConfig, AgentStrategy, ToolEntity
+from pydantic import BaseModel
+
+class BasicParams(BaseModel):
+ maximum_iterations: int
+ model: AgentModelConfig
+ tools: list[ToolEntity]
+ query: str
+
+class BasicAgentAgentStrategy(AgentStrategy):
+ def _invoke(self, parameters: dict[str, Any]) -> Generator[AgentInvokeMessage]:
+ params = BasicParams(**parameters)
+ chunks: Generator[LLMResultChunk, None, None] | LLMResult = (
+ self.session.model.llm.invoke(
+ model_config=LLMModelConfig(**params.model.model_dump(mode="json")),
+ prompt_messages=[UserPromptMessage(content=params.query)],
+ tools=[
+ self._convert_tool_to_prompt_message_tool(tool)
+ for tool in params.tools
+ ],
+ stop=params.model.completion_params.get("stop", [])
+ if params.model.completion_params
+ else [],
+ stream=True,
+ )
+ )
+ response = ""
+ tool_calls = []
+ tool_instances = (
+ {tool.identity.name: tool for tool in params.tools} if params.tools else {}
+ )
+
+ for chunk in chunks:
+ # check if there is any tool call
+ if self.check_tool_calls(chunk):
+ tool_calls = self.extract_tool_calls(chunk)
+ tool_call_names = ";".join([tool_call[1] for tool_call in tool_calls])
+ try:
+ tool_call_inputs = json.dumps(
+ {tool_call[1]: tool_call[2] for tool_call in tool_calls},
+ ensure_ascii=False,
+ )
+ except json.JSONDecodeError:
+ # ensure ascii to avoid encoding error
+ tool_call_inputs = json.dumps(
+ {tool_call[1]: tool_call[2] for tool_call in tool_calls}
+ )
+ print(tool_call_names, tool_call_inputs)
+ if chunk.delta.message and chunk.delta.message.content:
+ if isinstance(chunk.delta.message.content, list):
+ for content in chunk.delta.message.content:
+ response += content.data
+ print(content.data, end="", flush=True)
+ else:
+ response += str(chunk.delta.message.content)
+ print(str(chunk.delta.message.content), end="", flush=True)
+
+ if chunk.delta.usage:
+ # usage of the model
+ usage = chunk.delta.usage
+
+ yield self.create_text_message(
+ text=f"{response or json.dumps(tool_calls, ensure_ascii=False)}\n"
+ )
+ result = ""
+ for tool_call_id, tool_call_name, tool_call_args in tool_calls:
+ tool_instance = tool_instances[tool_call_name]
+ tool_invoke_responses = self.session.tool.invoke(
+ provider_type=ToolProviderType.BUILT_IN,
+ provider=tool_instance.identity.provider,
+ tool_name=tool_instance.identity.name,
+ parameters={**tool_instance.runtime_parameters, **tool_call_args},
+ )
+ if not tool_instance:
+ tool_invoke_responses = {
+ "tool_call_id": tool_call_id,
+ "tool_call_name": tool_call_name,
+ "tool_response": f"there is not a tool named {tool_call_name}",
+ }
+ else:
+ # invoke tool
+ tool_invoke_responses = self.session.tool.invoke(
+ provider_type=ToolProviderType.BUILT_IN,
+ provider=tool_instance.identity.provider,
+ tool_name=tool_instance.identity.name,
+ parameters={**tool_instance.runtime_parameters, **tool_call_args},
+ )
+ result = ""
+ for tool_invoke_response in tool_invoke_responses:
+ if tool_invoke_response.type == ToolInvokeMessage.MessageType.TEXT:
+ result += cast(
+ ToolInvokeMessage.TextMessage, tool_invoke_response.message
+ ).text
+ elif (
+ tool_invoke_response.type == ToolInvokeMessage.MessageType.LINK
+ ):
+ result += (
+ f"result link: {cast(ToolInvokeMessage.TextMessage, tool_invoke_response.message).text}."
+ + " please tell user to check it."
+ )
+ elif tool_invoke_response.type in {
+ ToolInvokeMessage.MessageType.IMAGE_LINK,
+ ToolInvokeMessage.MessageType.IMAGE,
+ }:
+ result += (
+ "image has been created and sent to user already, "
+ + "you do not need to create it, just tell the user to check it now."
+ )
+ elif (
+ tool_invoke_response.type == ToolInvokeMessage.MessageType.JSON
+ ):
+ text = json.dumps(
+ cast(
+ ToolInvokeMessage.JsonMessage,
+ tool_invoke_response.message,
+ ).json_object,
+ ensure_ascii=False,
+ )
+ result += f"tool response: {text}."
+ else:
+ result += f"tool response: {tool_invoke_response.message!r}."
+
+ tool_response = {
+ "tool_call_id": tool_call_id,
+ "tool_call_name": tool_call_name,
+ "tool_response": result,
+ }
+ yield self.create_text_message(result)
+
+ def _convert_tool_to_prompt_message_tool(
+ self, tool: ToolEntity
+ ) -> PromptMessageTool:
+ """
+ convert tool to prompt message tool
+ """
+ message_tool = PromptMessageTool(
+ name=tool.identity.name,
+ description=tool.description.llm if tool.description else "",
+ parameters={
+ "type": "object",
+ "properties": {},
+ "required": [],
+ },
+ )
+
+ parameters = tool.parameters
+ for parameter in parameters:
+ if parameter.form != ToolParameter.ToolParameterForm.LLM:
+ continue
+
+ parameter_type = parameter.type
+ if parameter.type in {
+ ToolParameter.ToolParameterType.FILE,
+ ToolParameter.ToolParameterType.FILES,
+ }:
+ continue
+ enum = []
+ if parameter.type == ToolParameter.ToolParameterType.SELECT:
+ enum = (
+ [option.value for option in parameter.options]
+ if parameter.options
+ else []
+ )
+
+ message_tool.parameters["properties"][parameter.name] = {
+ "type": parameter_type,
+ "description": parameter.llm_description or "",
+ }
+
+ if len(enum) > 0:
+ message_tool.parameters["properties"][parameter.name]["enum"] = enum
+
+ if parameter.required:
+ message_tool.parameters["required"].append(parameter.name)
+
+ return message_tool
+
+ def check_tool_calls(self, llm_result_chunk: LLMResultChunk) -> bool:
+ """
+ Check if there is any tool call in llm result chunk
+ """
+ return bool(llm_result_chunk.delta.message.tool_calls)
+
+ def extract_tool_calls(
+ self, llm_result_chunk: LLMResultChunk
+ ) -> list[tuple[str, str, dict[str, Any]]]:
+ """
+ Extract tool calls from llm result chunk
+
+ Returns:
+ List[Tuple[str, str, Dict[str, Any]]]: [(tool_call_id, tool_call_name, tool_call_args)]
+ """
+ tool_calls = []
+ for prompt_message in llm_result_chunk.delta.message.tool_calls:
+ args = {}
+ if prompt_message.function.arguments != "":
+ args = json.loads(prompt_message.function.arguments)
+
+ tool_calls.append(
+ (
+ prompt_message.id,
+ prompt_message.function.name,
+ args,
+ )
+ )
+
+ return tool_calls
+```
+
+
+ #### Handle Tools
+
+The following code shows how to implement model calls for the Agent strategy plugin and send canonicalized requests to the tool.
+
+```python
+import json
+from collections.abc import Generator
+from typing import Any, cast
+
+from dify_plugin.entities.agent import AgentInvokeMessage
+from dify_plugin.entities.model.llm import LLMModelConfig, LLMResult, LLMResultChunk
+from dify_plugin.entities.model.message import (
+ PromptMessageTool,
+ UserPromptMessage,
+)
+from dify_plugin.entities.tool import ToolInvokeMessage, ToolParameter, ToolProviderType
+from dify_plugin.interfaces.agent import AgentModelConfig, AgentStrategy, ToolEntity
+from pydantic import BaseModel
+
+class BasicParams(BaseModel):
+ maximum_iterations: int
+ model: AgentModelConfig
+ tools: list[ToolEntity]
+ query: str
+
+class BasicAgentAgentStrategy(AgentStrategy):
+ def _invoke(self, parameters: dict[str, Any]) -> Generator[AgentInvokeMessage]:
+ params = BasicParams(**parameters)
+ chunks: Generator[LLMResultChunk, None, None] | LLMResult = (
+ self.session.model.llm.invoke(
+ model_config=LLMModelConfig(**params.model.model_dump(mode="json")),
+ prompt_messages=[UserPromptMessage(content=params.query)],
+ tools=[
+ self._convert_tool_to_prompt_message_tool(tool)
+ for tool in params.tools
+ ],
+ stop=params.model.completion_params.get("stop", [])
+ if params.model.completion_params
+ else [],
+ stream=True,
+ )
+ )
+ response = ""
+ tool_calls = []
+ tool_instances = (
+ {tool.identity.name: tool for tool in params.tools} if params.tools else {}
+ )
+
+ for chunk in chunks:
+ # check if there is any tool call
+ if self.check_tool_calls(chunk):
+ tool_calls = self.extract_tool_calls(chunk)
+ tool_call_names = ";".join([tool_call[1] for tool_call in tool_calls])
+ try:
+ tool_call_inputs = json.dumps(
+ {tool_call[1]: tool_call[2] for tool_call in tool_calls},
+ ensure_ascii=False,
+ )
+ except json.JSONDecodeError:
+ # ensure ascii to avoid encoding error
+ tool_call_inputs = json.dumps(
+ {tool_call[1]: tool_call[2] for tool_call in tool_calls}
+ )
+ print(tool_call_names, tool_call_inputs)
+ if chunk.delta.message and chunk.delta.message.content:
+ if isinstance(chunk.delta.message.content, list):
+ for content in chunk.delta.message.content:
+ response += content.data
+ print(content.data, end="", flush=True)
+ else:
+ response += str(chunk.delta.message.content)
+ print(str(chunk.delta.message.content), end="", flush=True)
+
+ if chunk.delta.usage:
+ # usage of the model
+ usage = chunk.delta.usage
+
+ yield self.create_text_message(
+ text=f"{response or json.dumps(tool_calls, ensure_ascii=False)}\n"
+ )
+ result = ""
+ for tool_call_id, tool_call_name, tool_call_args in tool_calls:
+ tool_instance = tool_instances[tool_call_name]
+ tool_invoke_responses = self.session.tool.invoke(
+ provider_type=ToolProviderType.BUILT_IN,
+ provider=tool_instance.identity.provider,
+ tool_name=tool_instance.identity.name,
+ parameters={**tool_instance.runtime_parameters, **tool_call_args},
+ )
+ if not tool_instance:
+ tool_invoke_responses = {
+ "tool_call_id": tool_call_id,
+ "tool_call_name": tool_call_name,
+ "tool_response": f"there is not a tool named {tool_call_name}",
+ }
+ else:
+ # invoke tool
+ tool_invoke_responses = self.session.tool.invoke(
+ provider_type=ToolProviderType.BUILT_IN,
+ provider=tool_instance.identity.provider,
+ tool_name=tool_instance.identity.name,
+ parameters={**tool_instance.runtime_parameters, **tool_call_args},
+ )
+ result = ""
+ for tool_invoke_response in tool_invoke_responses:
+ if tool_invoke_response.type == ToolInvokeMessage.MessageType.TEXT:
+ result += cast(
+ ToolInvokeMessage.TextMessage, tool_invoke_response.message
+ ).text
+ elif (
+ tool_invoke_response.type == ToolInvokeMessage.MessageType.LINK
+ ):
+ result += (
+ f"result link: {cast(ToolInvokeMessage.TextMessage, tool_invoke_response.message).text}."
+ + " please tell user to check it."
+ )
+ elif tool_invoke_response.type in {
+ ToolInvokeMessage.MessageType.IMAGE_LINK,
+ ToolInvokeMessage.MessageType.IMAGE,
+ }:
+ result += (
+ "image has been created and sent to user already, "
+ + "you do not need to create it, just tell the user to check it now."
+ )
+ elif (
+ tool_invoke_response.type == ToolInvokeMessage.MessageType.JSON
+ ):
+ text = json.dumps(
+ cast(
+ ToolInvokeMessage.JsonMessage,
+ tool_invoke_response.message,
+ ).json_object,
+ ensure_ascii=False,
+ )
+ result += f"tool response: {text}."
+ else:
+ result += f"tool response: {tool_invoke_response.message!r}."
+
+ tool_response = {
+ "tool_call_id": tool_call_id,
+ "tool_call_name": tool_call_name,
+ "tool_response": result,
+ }
+ yield self.create_text_message(result)
+
+ def _convert_tool_to_prompt_message_tool(
+ self, tool: ToolEntity
+ ) -> PromptMessageTool:
+ """
+ convert tool to prompt message tool
+ """
+ message_tool = PromptMessageTool(
+ name=tool.identity.name,
+ description=tool.description.llm if tool.description else "",
+ parameters={
+ "type": "object",
+ "properties": {},
+ "required": [],
+ },
+ )
+
+ parameters = tool.parameters
+ for parameter in parameters:
+ if parameter.form != ToolParameter.ToolParameterForm.LLM:
+ continue
+
+ parameter_type = parameter.type
+ if parameter.type in {
+ ToolParameter.ToolParameterType.FILE,
+ ToolParameter.ToolParameterType.FILES,
+ }:
+ continue
+ enum = []
+ if parameter.type == ToolParameter.ToolParameterType.SELECT:
+ enum = (
+ [option.value for option in parameter.options]
+ if parameter.options
+ else []
+ )
+
+ message_tool.parameters["properties"][parameter.name] = {
+ "type": parameter_type,
+ "description": parameter.llm_description or "",
+ }
+
+ if len(enum) > 0:
+ message_tool.parameters["properties"][parameter.name]["enum"] = enum
+
+ if parameter.required:
+ message_tool.parameters["required"].append(parameter.name)
+
+ return message_tool
+
+ def check_tool_calls(self, llm_result_chunk: LLMResultChunk) -> bool:
+ """
+ Check if there is any tool call in llm result chunk
+ """
+ return bool(llm_result_chunk.delta.message.tool_calls)
+
+ def extract_tool_calls(
+ self, llm_result_chunk: LLMResultChunk
+ ) -> list[tuple[str, str, dict[str, Any]]]:
+ """
+ Extract tool calls from llm result chunk
+
+ Returns:
+ List[Tuple[str, str, Dict[str, Any]]]: [(tool_call_id, tool_call_name, tool_call_args)]
+ """
+ tool_calls = []
+ for prompt_message in llm_result_chunk.delta.message.tool_calls:
+ args = {}
+ if prompt_message.function.arguments != "":
+ args = json.loads(prompt_message.function.arguments)
+
+ tool_calls.append(
+ (
+ prompt_message.id,
+ prompt_message.function.name,
+ args,
+ )
+ )
+
+ return tool_calls
+```
+
+
+ #### Example of a complete function code
+
+A complete sample plugin code that includes a **invoking model, handling tool** and a **function to output multiple rounds of logs**:
+
+```python
+import json
+import time
+from collections.abc import Generator
+from typing import Any, cast
+
+from dify_plugin.entities.agent import AgentInvokeMessage
+from dify_plugin.entities.model.llm import LLMModelConfig, LLMResult, LLMResultChunk
+from dify_plugin.entities.model.message import (
+ PromptMessageTool,
+ UserPromptMessage,
+)
+from dify_plugin.entities.tool import ToolInvokeMessage, ToolParameter, ToolProviderType
+from dify_plugin.interfaces.agent import AgentModelConfig, AgentStrategy, ToolEntity
+from pydantic import BaseModel
+
+class BasicParams(BaseModel):
+ maximum_iterations: int
+ model: AgentModelConfig
+ tools: list[ToolEntity]
+ query: str
+
+class BasicAgentAgentStrategy(AgentStrategy):
+ def _invoke(self, parameters: dict[str, Any]) -> Generator[AgentInvokeMessage]:
+ params = BasicParams(**parameters)
+ function_call_round_log = self.create_log_message(
+ label="Function Call Round1 ",
+ data={},
+ metadata={},
+ )
+ yield function_call_round_log
+ model_started_at = time.perf_counter()
+ model_log = self.create_log_message(
+ label=f"{params.model.model} Thought",
+ data={},
+ metadata={"start_at": model_started_at, "provider": params.model.provider},
+ status=ToolInvokeMessage.LogMessage.LogStatus.START,
+ parent=function_call_round_log,
+ )
+ yield model_log
+ chunks: Generator[LLMResultChunk, None, None] | LLMResult = (
+ self.session.model.llm.invoke(
+ model_config=LLMModelConfig(**params.model.model_dump(mode="json")),
+ prompt_messages=[UserPromptMessage(content=params.query)],
+ tools=[
+ self._convert_tool_to_prompt_message_tool(tool)
+ for tool in params.tools
+ ],
+ stop=params.model.completion_params.get("stop", [])
+ if params.model.completion_params
+ else [],
+ stream=True,
+ )
+ )
+ response = ""
+ tool_calls = []
+ tool_instances = (
+ {tool.identity.name: tool for tool in params.tools} if params.tools else {}
+ )
+ tool_call_names = ""
+ tool_call_inputs = ""
+ for chunk in chunks:
+ # check if there is any tool call
+ if self.check_tool_calls(chunk):
+ tool_calls = self.extract_tool_calls(chunk)
+ tool_call_names = ";".join([tool_call[1] for tool_call in tool_calls])
+ try:
+ tool_call_inputs = json.dumps(
+ {tool_call[1]: tool_call[2] for tool_call in tool_calls},
+ ensure_ascii=False,
+ )
+ except json.JSONDecodeError:
+ # ensure ascii to avoid encoding error
+ tool_call_inputs = json.dumps(
+ {tool_call[1]: tool_call[2] for tool_call in tool_calls}
+ )
+ print(tool_call_names, tool_call_inputs)
+ if chunk.delta.message and chunk.delta.message.content:
+ if isinstance(chunk.delta.message.content, list):
+ for content in chunk.delta.message.content:
+ response += content.data
+ print(content.data, end="", flush=True)
+ else:
+ response += str(chunk.delta.message.content)
+ print(str(chunk.delta.message.content), end="", flush=True)
+
+ if chunk.delta.usage:
+ # usage of the model
+ usage = chunk.delta.usage
+
+ yield self.finish_log_message(
+ log=model_log,
+ data={
+ "output": response,
+ "tool_name": tool_call_names,
+ "tool_input": tool_call_inputs,
+ },
+ metadata={
+ "started_at": model_started_at,
+ "finished_at": time.perf_counter(),
+ "elapsed_time": time.perf_counter() - model_started_at,
+ "provider": params.model.provider,
+ },
+ )
+ yield self.create_text_message(
+ text=f"{response or json.dumps(tool_calls, ensure_ascii=False)}\n"
+ )
+ result = ""
+ for tool_call_id, tool_call_name, tool_call_args in tool_calls:
+ tool_instance = tool_instances[tool_call_name]
+ tool_invoke_responses = self.session.tool.invoke(
+ provider_type=ToolProviderType.BUILT_IN,
+ provider=tool_instance.identity.provider,
+ tool_name=tool_instance.identity.name,
+ parameters={**tool_instance.runtime_parameters, **tool_call_args},
+ )
+ if not tool_instance:
+ tool_invoke_responses = {
+ "tool_call_id": tool_call_id,
+ "tool_call_name": tool_call_name,
+ "tool_response": f"there is not a tool named {tool_call_name}",
+ }
+ else:
+ # invoke tool
+ tool_invoke_responses = self.session.tool.invoke(
+ provider_type=ToolProviderType.BUILT_IN,
+ provider=tool_instance.identity.provider,
+ tool_name=tool_instance.identity.name,
+ parameters={**tool_instance.runtime_parameters, **tool_call_args},
+ )
+ result = ""
+ for tool_invoke_response in tool_invoke_responses:
+ if tool_invoke_response.type == ToolInvokeMessage.MessageType.TEXT:
+ result += cast(
+ ToolInvokeMessage.TextMessage, tool_invoke_response.message
+ ).text
+ elif (
+ tool_invoke_response.type == ToolInvokeMessage.MessageType.LINK
+ ):
+ result += (
+ f"result link: {cast(ToolInvokeMessage.TextMessage, tool_invoke_response.message).text}."
+ + " please tell user to check it."
+ )
+ elif tool_invoke_response.type in {
+ ToolInvokeMessage.MessageType.IMAGE_LINK,
+ ToolInvokeMessage.MessageType.IMAGE,
+ }:
+ result += (
+ "image has been created and sent to user already, "
+ + "you do not need to create it, just tell the user to check it now."
+ )
+ elif (
+ tool_invoke_response.type == ToolInvokeMessage.MessageType.JSON
+ ):
+ text = json.dumps(
+ cast(
+ ToolInvokeMessage.JsonMessage,
+ tool_invoke_response.message,
+ ).json_object,
+ ensure_ascii=False,
+ )
+ result += f"tool response: {text}."
+ else:
+ result += f"tool response: {tool_invoke_response.message!r}."
+
+ tool_response = {
+ "tool_call_id": tool_call_id,
+ "tool_call_name": tool_call_name,
+ "tool_response": result,
+ }
+ yield self.create_text_message(result)
+
+ def _convert_tool_to_prompt_message_tool(
+ self, tool: ToolEntity
+ ) -> PromptMessageTool:
+ """
+ convert tool to prompt message tool
+ """
+ message_tool = PromptMessageTool(
+ name=tool.identity.name,
+ description=tool.description.llm if tool.description else "",
+ parameters={
+ "type": "object",
+ "properties": {},
+ "required": [],
+ },
+ )
+
+ parameters = tool.parameters
+ for parameter in parameters:
+ if parameter.form != ToolParameter.ToolParameterForm.LLM:
+ continue
+
+ parameter_type = parameter.type
+ if parameter.type in {
+ ToolParameter.ToolParameterType.FILE,
+ ToolParameter.ToolParameterType.FILES,
+ }:
+ continue
+ enum = []
+ if parameter.type == ToolParameter.ToolParameterType.SELECT:
+ enum = (
+ [option.value for option in parameter.options]
+ if parameter.options
+ else []
+ )
+
+ message_tool.parameters["properties"][parameter.name] = {
+ "type": parameter_type,
+ "description": parameter.llm_description or "",
+ }
+
+ if len(enum) > 0:
+ message_tool.parameters["properties"][parameter.name]["enum"] = enum
+
+ if parameter.required:
+ message_tool.parameters["required"].append(parameter.name)
+
+ return message_tool
+
+ def check_tool_calls(self, llm_result_chunk: LLMResultChunk) -> bool:
+ """
+ Check if there is any tool call in llm result chunk
+ """
+ return bool(llm_result_chunk.delta.message.tool_calls)
+
+ def extract_tool_calls(
+ self, llm_result_chunk: LLMResultChunk
+ ) -> list[tuple[str, str, dict[str, Any]]]:
+ """
+ Extract tool calls from llm result chunk
+
+ Returns:
+ List[Tuple[str, str, Dict[str, Any]]]: [(tool_call_id, tool_call_name, tool_call_args)]
+ """
+ tool_calls = []
+ for prompt_message in llm_result_chunk.delta.message.tool_calls:
+ args = {}
+ if prompt_message.function.arguments != "":
+ args = json.loads(prompt_message.function.arguments)
+
+ tool_calls.append(
+ (
+ prompt_message.id,
+ prompt_message.function.name,
+ args,
+ )
+ )
+
+ return tool_calls
+```
+
+
+
+### 3. Debugging the Plugin
+
+After finalizing the plugin’s declaration file and implementation code, run `python -m main` in the plugin directory to restart it. Next, confirm the plugin runs correctly. Dify offers remote debugging—go to [“Plugin Management”](https://console-plugin.dify.dev/plugins) to obtain your debug key and remote server address.
+
+
+
+Back in your plugin project, copy `.env.example` to `.env` and insert the relevant remote server and debug key info.
+
+```bash
+INSTALL_METHOD=remote
+REMOTE_INSTALL_HOST=remote
+REMOTE_INSTALL_PORT=5003
+REMOTE_INSTALL_KEY=****-****-****-****-****
+```
+
+Then run:
+
+```bash
+python -m main
+```
+
+You’ll see the plugin installed in your Workspace, and team members can also access it.
+
+
+
+### Packaging the Plugin (Optional)
+
+Once everything works, you can package your plugin by running:
+
+```bash
+# Replace ./basic_agent/ with your actual plugin project path.
+
+dify plugin package ./basic_agent/
+```
+
+A file named `google.difypkg` (for example) appears in your current folder—this is your final plugin package.
+
+**Congratulations!** You’ve fully developed, tested, and packaged your Agent Strategy Plugin.
+
+### Publishing the Plugin (Optional)
+
+You can now upload it to the [Dify Plugins repository](https://github.com/langgenius/dify-plugins). Before doing so, ensure it meets the [Plugin Publishing Guidelines](https://docs.dify.ai/plugins/publish-plugins/publish-to-dify-marketplace). Once approved, your code merges into the main branch, and the plugin automatically goes live on the [Dify Marketplace](https://marketplace.dify.ai/).
+
+---
+
+### Further Exploration
+
+Complex tasks often need multiple rounds of thinking and tool calls, typically repeating **model invoke → tool use** until the task ends or a maximum iteration limit is reached. Managing prompts effectively is crucial in this process. Check out the [complete Function Calling implementation](https://github.com/langgenius/dify-official-plugins/blob/main/agent-strategies/cot_agent/strategies/function_calling.py) for a standardized approach to letting models call external tools and handle their outputs.
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ Help improve our documentation by contributing directly
+
+
+ Found an error or have suggestions? Let us know
+
+
diff --git a/plugin_dev_zh/0111-getting-started-dify-plugin.zh.mdx b/plugin_dev_zh/0111-getting-started-dify-plugin.zh.mdx
new file mode 100644
index 00000000..ef3511b7
--- /dev/null
+++ b/plugin_dev_zh/0111-getting-started-dify-plugin.zh.mdx
@@ -0,0 +1,85 @@
+---
+dimensions:
+ type:
+ primary: conceptual
+ detail: introduction
+ level: beginner
+standard_title: Getting Started Dify Plugin
+language: zh
+title: 欢迎开始 Dify 插件开发
+description: 介绍Dify插件的概念、功能和开发价值,包括插件类型(模型、工具、Agent策略、扩展、包)的简要说明,以及开发者文档的内容概览。
+---
+
+你好!我们非常高兴你对构建 Dify 插件感兴趣。本开发者文档中心是你的核心资源库,旨在帮助你学习、创建、调试、发布和管理 Dify 插件。
+
+
+**Dify 插件是什么?**
+
+你可以将 Dify 插件想象成赋予 AI 应用**增强感知和执行能力**的模块化组件。它们使得将外部服务、自定义功能以及专用工具以"即插即用"的简洁方式集成到基于 Dify 构建的 AI 应用中成为可能。通过插件,你的 AI 应用可以更好地"看"、"听"、"说"、"画"、"计算"、"推理",连接外部 API,甚至执行真实世界的操作。
+
+作为**插件开发者**,你可以为自己的 Dify 应用构建专属的功能扩展,或者将你的创新贡献给整个 Dify 生态系统,让更多用户受益。
+
+**在本开发者文档中,你将找到:**
+
+本文档旨在为插件开发者提供清晰的指引,无论你是初次尝试还是寻求高级定制:
+
+- **[快速入门](/plugin_dev_zh/0211-getting-started-dify-tool.zh):** 学习 Dify 插件系统的基本概念,理解其核心架构,并快速搭建你的开发环境,构建第一个"Hello World"插件。
+- **[核心概念](/plugin_dev_zh/0131-cheatsheet.zh):** 深入理解插件生命周期、安全模型、端点集成 (Endpoint Integration)、反向调用 (Reverse Call)、持久化存储等关键原理。
+- **开发不同类型的插件:** 针对每种插件类型,提供专门的开发指南:
+ - **[模型 (Models)](/plugin_dev_zh/0211-getting-started-new-model.zh):** 学习如何将不同的 AI 模型打包、配置并作为插件进行管理。
+ - **[工具 (Tools)](/plugin_dev_zh/0211-getting-started-dify-tool.zh):** 为 Agent 和工作流构建专业能力,如数据分析、内容处理、自定义集成等。
+ - **[Agent 策略 (Agent Strategies)](/plugin_dev_zh/9433-agent-strategy-plugin.zh):** 创建自定义的推理策略(如 ReAct, CoT, ToT)来赋能 Dify 中的自主 Agent。
+ - **[扩展 (Extensions)](/plugin_dev_zh/9231-extension-plugin.zh):** 通过 HTTP Webhook 实现与外部服务的集成,处理复杂逻辑。
+ - **[包 (Bundles)](/plugin_dev_zh/9241-bundle.zh):** 了解如何将多个插件组合打包,以便于分发和部署。
+- **[开发与调试](/plugin_dev_zh/0411-remote-debug-a-plugin.zh):** 掌握高效插件开发的工具和技巧,包括使用 SDK、利用我们友好的远程调试功能,以及如何测试你的插件。
+- **[发布与市场](/plugin_dev_zh/0321-release-overview.zh):** 学习如何打包你的插件,将其提交到官方的 Dify Marketplace,或通过 GitHub 等渠道与社区分享。
+- **[API & SDK 参考](/plugin_dev_zh/0411-general-specifications.zh):** 查找 API、SDK 方法、Manifest 文件格式以及所需 Schema 的详细技术规范。
+- **[社区与贡献](/plugin_dev_zh/0312-contributor-covenant-code-of-conduct.zh):** 了解如何与其他开发者交流、寻求帮助,以及如何为 Dify 插件生态和本文档做出贡献。
+
+**为什么选择开发 Dify 插件?**
+
+- **扩展 AI 能力:** 为基于 Dify 的应用赋予专业工具、多模态处理、连接现实世界服务等无限可能。
+- **定制化 Dify 体验:** 通过构建专属插件,精准满足特定业务场景或工作流的需求。
+- **重塑智能流程:** 利用自定义工具和 Agent 策略,优化 RAG 流程、增强 Agent 推理能力。
+- **实现模块化与解耦:** 将功能作为独立的插件进行开发和管理,提高代码的可维护性和灵活性。
+- **触达 Dify 用户:** 通过 Dify Marketplace 将你的创新成果分享给广大的 Dify 用户群体。
+- **享受开发者友好体验:** 我们提供强大的 SDK、便捷的远程调试工具和清晰的文档,助你高效开发。
+
+**准备好开始构建了吗?**
+
+以下是一些快速入口,助你启程:
+
+- **[阅读快速入门指南](/plugin_dev_zh/0211-getting-started-dify-tool.zh)** - 从构建一个简单的工具插件开始
+- **[探索插件开发速查表](/plugin_dev_zh/0131-cheatsheet.zh)** - 了解核心概念和常用命令
+- **[初始化开发环境](/plugin_dev_zh/0221-initialize-development-tools.zh)** - 搭建你的开发环境
+- **[查看常见问题解答](/plugin_dev_zh/0331-faq.zh)** - 解决常见疑问
+
+## 相关资源
+
+- **[模型插件介绍](/plugin_dev_zh/0131-model-plugin-introduction.zh)** - 了解模型插件的基本结构
+- **[开发实践示例](/plugin_dev_zh/0432-develop-a-slack-bot-plugin.zh)** - 查看实际的插件开发案例
+
+我们期待看到你使用 Dify 插件创造出色的应用和功能!
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ 通过直接提交修改来帮助改进文档内容
+
+
+ 发现错误或有改进建议?请提交问题反馈
+
+
diff --git a/plugin_dev_zh/0131-cheatsheet.zh.mdx b/plugin_dev_zh/0131-cheatsheet.zh.mdx
new file mode 100644
index 00000000..b8630564
--- /dev/null
+++ b/plugin_dev_zh/0131-cheatsheet.zh.mdx
@@ -0,0 +1,168 @@
+---
+dimensions:
+ type:
+ primary: conceptual
+ detail: architecture
+ level: beginner
+standard_title: Cheatsheet
+language: zh
+title: Dify Plugin 开发速查表
+description: 全面的Dify插件开发参考指南,包括环境要求、安装方法、开发流程、插件分类及类型、常用代码片段和常见问题解决方案。适合开发者快速查阅和参考。
+---
+
+### 环境要求
+
+- Python 版本 ≥ 3.12
+- Dify 插件脚手架工具 (dify-plugin-daemon)
+
+> 了解更多:[初始化开发工具](/plugin_dev_zh/0221-initialize-development-tools.zh)
+
+### 获取 Dify Plugin 开发包
+
+[Dify Plugin CLI](https://github.com/langgenius/dify-plugin-daemon/releases)
+
+#### 不同平台安装方法
+
+**macOS [Brew](https://github.com/langgenius/homebrew-dify)(全局安装):**
+
+```bash
+brew tap langgenius/dify
+brew install dify
+```
+
+安装完成后,新建任意终端窗口,输出 `dify version` 命令,若输出版本号信息,则说明安装成功。
+
+**macOS ARM (M 系列芯片):**
+
+```bash
+# 下载 dify-plugin-darwin-arm64
+chmod +x dify-plugin-darwin-arm64
+./dify-plugin-darwin-arm64 version
+```
+
+**macOS Intel:**
+
+```bash
+# 下载 dify-plugin-darwin-amd64
+chmod +x dify-plugin-darwin-amd64
+./dify-plugin-darwin-amd64 version
+```
+
+**Linux:**
+
+```bash
+# 下载 dify-plugin-linux-amd64
+chmod +x dify-plugin-linux-amd64
+./dify-plugin-linux-amd64 version
+```
+
+**全局安装 (推荐):**
+
+```bash
+# 重命名并移动到系统路径
+# 示例 (macOS ARM)
+mv dify-plugin-darwin-arm64 dify
+sudo mv dify /usr/local/bin/
+dify version
+```
+
+### 运行开发包
+
+这里以 `dify` 为例。如果你使用的是局部的安装方式,请根据情况替换指令例如 `./dify-plugin-darwin-arm64 plugin init`。
+
+
+
+### 插件开发流程
+
+#### 1. 新建插件
+
+```bash
+./dify plugin init
+```
+
+按提示完成插件基本信息配置
+
+> 了解更多:[Dify 插件开发:Hello World 指南](/plugin_dev_zh/0211-getting-started-dify-tool.zh)
+
+#### 2. 开发模式运行
+
+配置 `.env` 文件,然后在插件目录下运行以下命令:
+
+```bash
+python -m main
+```
+
+> 了解更多:[远程调试插件](/plugin_dev_zh/0411-remote-debug-a-plugin.zh)
+
+#### 4. 打包与部署
+
+打包插件:
+
+```bash
+cd ..
+dify plugin package ./yourapp
+```
+
+> 了解更多:[发布概览](/plugin_dev_zh/0321-release-overview.zh)
+
+### 插件分类
+
+#### 工具标签
+
+分类 `tag` [class ToolLabelEnum(Enum)](https://github.com/langgenius/dify-plugin-sdks/blob/main/python/dify_plugin/entities/tool.py)
+
+```python
+class ToolLabelEnum(Enum):
+ SEARCH = "search"
+ IMAGE = "image"
+ VIDEOS = "videos"
+ WEATHER = "weather"
+ FINANCE = "finance"
+ DESIGN = "design"
+ TRAVEL = "travel"
+ SOCIAL = "social"
+ NEWS = "news"
+ MEDICAL = "medical"
+ PRODUCTIVITY = "productivity"
+ EDUCATION = "education"
+ BUSINESS = "business"
+ ENTERTAINMENT = "entertainment"
+ UTILITIES = "utilities"
+ OTHER = "other"
+```
+
+### 插件类型参考
+
+Dify 支持多种类型的插件开发:
+
+- **工具插件**: 集成第三方 API 和服务
+ > 了解更多:[工具插件开发](/plugin_dev_zh/0211-getting-started-dify-tool.zh)
+- **模型插件**: 集成 AI 模型
+ > 了解更多:[模型插件介绍](/plugin_dev_zh/0131-model-plugin-introduction.zh)、[快速接入一个新模型](/plugin_dev_zh/0211-getting-started-new-model.zh)
+- **Agent 策略插件**: 自定义 Agent 思考和决策策略
+ > 了解更多:[Agent 策略插件](/plugin_dev_zh/9433-agent-strategy-plugin.zh)
+- **扩展插件**: 扩展 Dify 平台功能,例如 Endpoint 和 WebAPP
+ > 了解更多:[扩展插件](/plugin_dev_zh/9231-extension-plugin.zh)
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ 通过直接提交修改来帮助改进文档内容
+
+
+ 发现错误或有改进建议?请提交问题反馈
+
+
diff --git a/plugin_dev_zh/0211-getting-started-by-prompt.zh.mdx b/plugin_dev_zh/0211-getting-started-by-prompt.zh.mdx
new file mode 100644
index 00000000..e117a00f
--- /dev/null
+++ b/plugin_dev_zh/0211-getting-started-by-prompt.zh.mdx
@@ -0,0 +1,1165 @@
+---
+dimensions:
+ type:
+ primary: implementation
+ detail: basic
+ level: beginner
+standard_title: Getting Started by Prompt
+language: zh
+title: Dify 插件开发:Prompt
+description: 请复制这个prompt,并将其粘贴到你的 Agent 中。它将协助你开发 Dify 插件,提供最佳实践和代码示例。
+---
+
+## 文件结构与组织原则
+
+### 标准项目结构
+
+```
+your_plugin/
+├── _assets/ # 图标和视觉资源
+├── provider/ # 提供者定义和验证
+│ ├── your_plugin.py # 凭证验证逻辑
+│ └── your_plugin.yaml # 提供者配置
+├── tools/ # 工具实现
+│ ├── feature_one.py # 工具功能实现
+│ ├── feature_one.yaml # 工具参数和描述
+│ ├── feature_two.py # 另一个工具实现
+│ └── feature_two.yaml # 另一个工具配置
+├── utils/ # 辅助函数
+│ └── helpers.py # 通用功能逻辑
+├── working/ # 进度记录和工作文件
+├── .env.example # 环境变量模板
+├── main.py # 入口文件
+├── manifest.yaml # 插件主配置
+├── README.md # 文档
+└── requirements.txt # 依赖列表
+```
+
+### 文件组织核心原则
+
+1. **一个文件一个工具类**:
+ - **每个Python文件只能定义一个Tool子类** - 这是框架的强制限制
+ - 违反此规则会导致错误:`Exception: Multiple subclasses of Tool in /path/to/file.py`
+ - 示例:`tools/encrypt.py`只能包含`EncryptTool`类,不能同时包含`DecryptTool`
+
+2. **命名和功能对应**:
+ - Python文件名应与工具功能相对应
+ - 工具类名应遵循`FeatureTool`的命名模式
+ - YAML文件名应与对应的Python文件名保持一致
+
+3. **文件位置指导**:
+ - 通用工具函数放在`utils/`目录
+ - 具体工具实现放在`tools/`目录
+ - 凭证验证逻辑放在`provider/`目录
+
+4. **正确的命名和导入**:
+ - 确保导入的函数名与实际定义的名称完全匹配(包括下划线、大小写等)
+ - 错误导入会导致:`ImportError: cannot import name 'x' from 'module'. Did you mean: 'y'?`
+
+### 创建新工具的正确流程
+
+1. **复制现有文件作为模板**:
+ ```bash
+ # 复制工具YAML文件作为模板
+ cp tools/existing_tool.yaml tools/new_feature.yaml
+ # 复制工具Python实现
+ cp tools/existing_tool.py tools/new_feature.py
+ ```
+
+2. **编辑复制的文件**:
+ - 更新YAML中的名称、描述和参数
+ - 更新Python文件中的类名和实现逻辑
+ - 确保每个文件只包含一个Tool子类
+
+3. **更新provider配置**:
+ - 在`provider/your_plugin.yaml`中添加新工具:
+ ```yaml
+ tools:
+ - tools/existing_tool.yaml
+ - tools/new_feature.yaml # 添加新工具
+ ```
+
+### 常见错误排查
+
+当遇到`Multiple subclasses of Tool`错误时:
+
+1. **检查问题文件**:
+ - 寻找形如`class AnotherTool(Tool):`的额外类定义
+ - 确保文件中只有一个继承自`Tool`的类
+ - 例如:如果`encrypt.py`包含`EncryptTool`和`DecryptTool`,保留`EncryptTool`并将`DecryptTool`移至`decrypt.py`
+
+2. **检查导入错误**:
+ - 确认导入的函数名或类名是否拼写正确
+ - 注意下划线、大小写等细节
+ - 修正导入语句中的拼写错误## 文件结构与代码组织规范
+
+### 工具文件组织的严格限制
+
+1. **一个文件一个工具类**:
+ - **每个Python文件只能定义一个Tool子类**
+ - 这是Dify插件框架的强制限制,违反会导致加载错误
+ - 错误表现为:`Exception: Multiple subclasses of Tool in /path/to/file.py`
+
+2. **正确的命名和导入**:
+ - 确保导入的函数名与实际定义的名称完全匹配(包括下划线、大小写等)
+ - 错误导入会导致:`ImportError: cannot import name 'x' from 'module'. Did you mean: 'y'?`
+
+3. **创建新工具的正确流程**:
+ - **步骤1**: 创建专门的YAML文件:`tools/new_feature.yaml`
+ - **步骤2**: 创建对应的Python文件:`tools/new_feature.py`,确保一个文件只有一个Tool子类
+ - **步骤3**: 更新provider YAML文件中的tools列表以包含新工具
+ - **切勿**在现有工具文件中添加新工具类
+
+### 代码错误排查指南
+
+当遇到 `Multiple subclasses of Tool` 错误时:
+
+1. **检查文件内容**:
+ ```bash
+ # 查看工具文件内容
+ cat tools/problematic_file.py
+ ```
+
+2. **查找多余的Tool子类**:
+ - 寻找形如 `class AnotherTool(Tool):` 的额外类定义
+ - 确保文件中只有一个继承自`Tool`的类
+
+3. **修复策略**:
+ - 将多余的Tool子类移动到对应名称的新文件中
+ - 保留文件名对应的Tool子类
+ - 移除不相关的导入语句
+ - 示例:如果`encrypt.py`包含`EncryptTool`和`DecryptTool`,则保留`EncryptTool`并将`DecryptTool`移至`decrypt.py`
+
+4. **代码审查检查点**:
+ - 每个工具文件只应包含**一个**`class XxxTool(Tool):`定义
+ - 导入语句应只引入该工具类需要的依赖
+ - 所有引用的工具函数名称应该与其定义完全一致## 进度记录管理
+
+### 进度文件结构与维护
+
+1. **创建进度文件**:
+ - 首次交互时在`working/`目录创建`progress.md`
+ - 每次新会话开始时首先检查并更新此文件
+
+2. **进度文件内容结构**:
+ ```markdown
+ # 项目进度记录
+
+ ## 项目概述
+ [插件名称、类型和主要功能简介]
+
+ ## 当前状态
+ [描述项目当前所处阶段]
+
+ ## 已完成工作
+ - [时间] 完成了xxx功能
+ - [时间] 实现了xxx
+
+ ## 待办事项
+ - [ ] 实现xxx功能
+ - [ ] 完成xxx配置
+
+ ## 问题与解决方案
+ - 问题:xxx
+ 解决方案:xxx
+
+ ## 技术决策记录
+ - 决定使用xxx库,原因是xxx
+ ```
+
+3. **更新规则**:
+ - **每次对话开始**时进行状态检查和记录更新
+ - **每次完成任务**后添加到已完成工作列表
+ - **每次遇到并解决问题**时记录在问题与解决方案部分
+ - **每次确定技术方向**时记录在技术决策记录部分
+
+4. **更新内容示例**:
+ ```markdown
+ ## 已完成工作
+ - [2025-04-19 14:30] 完成了TOTP验证工具的基本实现
+ - [2025-04-19 15:45] 添加了错误处理逻辑
+
+ ## 待办事项
+ - [ ] 实现secret_generator工具
+ - [ ] 完善README文档
+ ```# Dify插件开发助手
+
+## 初始交互指导
+
+当用户仅提供了这个prompt但没有明确任务时,不要立即开始提供插件开发建议或代码实现。相反,你应该:
+
+1. 礼貌地欢迎用户
+2. 解释你作为Dify插件开发助手的能力
+3. 请求用户提供以下信息:
+ - 他们想要开发的插件类型或功能
+ - 当前开发阶段(新项目/进行中的项目)
+ - 是否有现有代码或项目文件可以检查
+ - 具体面临的问题或需要帮助的方面
+
+只有在用户提供了具体任务描述或开发需求后,才开始提供相应的建议和帮助。
+
+## 角色定义
+你是一位资深软件工程师,专门负责Dify插件开发。你需要帮助开发者实现和优化Dify插件,遵循最佳实践并解决各种技术挑战。
+
+## 责任与工作模式
+
+### 项目管理与状态追踪
+1. **持续跟踪项目状态**:维护对项目当前进度的理解,记录哪些文件已被创建、修改,以及哪些功能已实现或待实现。
+2. **状态确认**:在每次交互开始时确认当前状态,如果用户输入与你的记录不一致,主动重新检查项目文件来同步实际状态。
+3. **进度记录**:在working目录中创建并更新progress.md文件,记录重要决策、已完成工作和下一步计划。
+
+### 代码开发与问题解决
+1. **代码实现**:根据需求编写高质量的Python代码和YAML配置。
+2. **问题诊断**:分析错误信息,提供具体的修复方案。
+3. **解决方案建议**:为技术难题提供多个可行的解决方案,并解释各自的优缺点。
+
+### 交互与沟通
+1. **主动性**:当用户提供不完整信息时,主动请求澄清或补充信息。
+2. **解释性**:解释复杂的技术概念和决策理由,帮助用户理解开发过程。
+3. **适应性**:根据用户反馈调整你的建议和方案。
+
+## 开发环境与限制
+
+### 执行环境特性
+
+1. **无服务器环境**:Dify插件在云环境(如AWS Lambda)中运行,这意味着:
+ - **无本地文件系统持久性**:避免依赖本地文件读写操作
+ - **有执行时间限制**:通常在几秒到几十秒之间
+ - **有内存限制**:通常在128MB-1GB之间
+ - **无法访问主机系统**:不能依赖本地安装的软件或系统库
+
+2. **代码打包限制**:
+ - 所有依赖必须在`requirements.txt`中明确声明
+ - 不能包含二进制文件或需要编译的库(除非提供预编译版本)
+ - 避免过大的依赖包
+
+### 安全设计原则
+
+1. **无状态设计**:
+ - 不要依赖于文件系统来存储状态
+ - 如需持久化数据,使用Dify提供的KV存储API
+ - 每次调用都应该是独立的,不依赖于之前的调用状态
+
+2. **安全的文件操作方式**:
+ - 避免本地文件读写(`open()`, `read()`, `write()`等)
+ - 临时数据使用内存变量存储
+ - 对于大量数据,考虑使用数据库或云存储服务
+
+3. **轻量级实现**:
+ - 选择轻量级的依赖库
+ - 避免不必要的大型框架
+ - 高效管理内存使用
+
+4. **健壮的错误处理**:
+ - 为所有API调用添加错误处理
+ - 提供明确的错误信息
+ - 优雅地处理超时和限制
+
+## 开发流程详解
+
+### 1. 项目初始化
+使用`dify plugin init`命令创建基本项目结构:
+
+```bash
+./dify plugin init
+```
+
+这将引导你输入插件名称、作者和描述,然后生成项目骨架。
+
+### 2. 环境配置
+设置Python虚拟环境并安装依赖:
+
+```bash
+# 创建虚拟环境
+python -m venv venv
+
+# 激活虚拟环境
+# Windows:
+venv\Scripts\activate
+# macOS/Linux:
+source venv/bin/activate
+
+# 安装依赖
+pip install -r requirements.txt
+```
+
+### 3. 开发实现
+
+#### 3.1 需求分析与设计
+首先明确插件需要实现的具体功能和输入/输出要求:
+- 插件将提供哪些工具?
+- 每个工具需要哪些输入参数?
+- 每个工具应该返回什么输出?
+- 是否需要验证用户凭证?
+
+#### 3.2 实现基础工具函数
+在`utils/`目录中创建辅助函数,实现核心功能逻辑:
+
+1. 创建文件:
+ ```bash
+ mkdir -p utils
+ touch utils/__init__.py
+ touch utils/helpers.py
+ ```
+
+2. 在`helpers.py`中实现与外部服务交互或处理复杂逻辑的函数
+
+#### 3.3 实现工具类
+在`tools/`目录中创建工具实现类,对每个功能:
+
+1. 创建YAML文件定义工具参数和描述
+2. 创建对应的Python文件实现工具逻辑,继承`Tool`基类并重写`_invoke`方法
+3. 每个功能应该有**单独的**文件对,遵循"一个文件一个工具类"原则
+
+#### 3.4 实现凭证验证
+如果插件需要API密钥等凭证,在`provider/`目录中实现验证逻辑:
+
+1. 编辑`provider/your_plugin.yaml`添加凭证定义
+2. 在`provider/your_plugin.py`中实现`_validate_credentials`方法
+
+### 4. 测试与调试
+配置`.env`文件进行本地测试:
+
+```bash
+# 复制并编辑环境变量
+cp .env.example .env
+
+# 启动本地服务
+python -m main
+```
+
+#### 调试常见错误
+- `Multiple subclasses of Tool`:检查工具文件是否包含多个Tool子类
+- `ImportError: cannot import name`:检查导入的函数名是否拼写正确
+- `ToolProviderCredentialValidationError`:检查凭证验证逻辑
+
+### 5. 打包与发布
+完成开发后,打包插件并可选择发布到市场:
+
+```bash
+# 打包插件
+./dify plugin package ./your_plugin_dir
+```
+
+#### 发布前检查
+- 确认README.md和PRIVACY.md已完善
+- 确认所有依赖都已添加到requirements.txt
+- 检查manifest.yaml中的标签是否正确
+
+## 文件结构详解
+
+```
+your_plugin/
+├── _assets/ # 图标和视觉资源
+├── provider/ # 提供者定义和验证
+│ ├── your_plugin.py # 凭证验证逻辑
+│ └── your_plugin.yaml # 提供者配置
+├── tools/ # 工具实现
+│ ├── your_plugin.py # 工具功能实现
+│ └── your_plugin.yaml # 工具参数和描述
+├── utils/ # (可选) 辅助函数
+├── working/ # 进度记录和工作文件
+├── .env.example # 环境变量模板
+├── main.py # 入口文件
+├── manifest.yaml # 插件主配置
+├── README.md # 文档
+└── requirements.txt # 依赖列表
+```
+
+### 文件位置与组织原则
+
+1. **Python文件位置指导**:
+ - 当用户提供单个Python文件时,应先检查其功能性质
+ - 通用工具函数应放在`utils/`目录下
+ - 具体工具实现应放在`tools/`目录下
+ - 凭证验证逻辑应放在`provider/`目录下
+
+2. **代码复制而非从头编写**:
+ - 创建新文件时,优先通过复制现有文件作为模板,然后进行修改
+ - 使用命令如:`cp tools/existing_tool.py tools/new_tool.py`
+ - 这样可确保文件格式和结构符合框架要求
+
+3. **保持框架一致性**:
+ - 不随意修改文件结构
+ - 不添加框架未定义的新文件类型
+ - 遵循既定的命名约定
+
+## 关键文件配置详解
+
+### manifest.yaml
+插件的主配置文件,定义了插件的基本信息和元数据。请遵循以下重要原则:
+
+1. **保留已有内容**:
+ - 不要删除配置文件中已有的项目,尤其是i18n相关部分
+ - 以实际已有代码为基准进行修改和添加
+
+2. **关键字段指导**:
+ - **name**:不要修改此字段,它是插件的唯一标识符
+ - **label**:建议完善多语言显示名称
+ - **description**:建议完善多语言描述
+ - **tags**:只能使用以下预定义的标签(每个插件只能选择1-2个最相关的标签):
+ ```
+ 'search', 'image', 'videos', 'weather', 'finance', 'design',
+ 'travel', 'social', 'news', 'medical', 'productivity',
+ 'education', 'business', 'entertainment', 'utilities', 'other'
+ ```
+
+3. **保持结构稳定**:
+ - 除非有特殊需求,不要修改`resource`、`meta`、`plugins`等部分
+ - 不要更改`type`和`version`等基础字段
+
+```yaml
+version: 0.0.1
+type: plugin
+author: your_name
+name: your_plugin_name # 不要修改此字段
+label:
+ en_US: Your Plugin Display Name
+ zh_Hans: 你的插件显示名称
+description:
+ en_US: Detailed description of your plugin functionality
+ zh_Hans: 插件功能的详细描述
+icon: icon.svg
+resource:
+ memory: 268435456 # 256MB
+ permission: {}
+plugins:
+ tools:
+ - provider/your_plugin.yaml
+meta:
+ version: 0.0.1
+ arch:
+ - amd64
+ - arm64
+ runner:
+ language: python
+ version: "3.12"
+ entrypoint: main
+created_at: 2025-04-19T00:00:00.000000+08:00
+privacy: PRIVACY.md
+tags:
+ - utilities # 只使用预定义的标签
+```
+
+### provider/your_plugin.yaml
+提供者配置文件,定义了插件所需的凭证和工具列表:
+
+1. **保留关键标识**:
+ - **name**:不要修改此字段,保持与manifest.yaml中的name一致
+ - 保留已有的i18n配置和结构
+
+2. **完善显示信息**:
+ - **label**:建议完善多语言显示名称
+ - **description**:建议完善多语言描述
+
+3. **添加新工具**:
+ - 在`tools`列表中添加对新工具YAML文件的引用
+ - 注意保持路径正确:`tools/feature_name.yaml`
+
+```yaml
+identity:
+ author: your_name
+ name: your_plugin_name # 不要修改此字段
+ label:
+ en_US: Your Plugin Display Name
+ zh_Hans: 你的插件显示名称
+ description:
+ en_US: Detailed description of your plugin functionality
+ zh_Hans: 插件功能的详细描述
+ icon: icon.svg
+credentials_for_provider: # 仅在需要API密钥等凭证时添加
+ api_key:
+ type: secret-input
+ required: true
+ label:
+ en_US: API Key
+ zh_Hans: API密钥
+ placeholder:
+ en_US: Enter your API key
+ zh_Hans: 输入你的API密钥
+ help:
+ en_US: How to get your API key
+ zh_Hans: 如何获取API密钥
+ url: https://example.com/get-api-key
+tools: # 工具列表,添加新工具时在此更新
+ - tools/feature_one.yaml
+ - tools/feature_two.yaml
+extra:
+ python:
+ source: provider/your_plugin.py
+```
+
+### tools/feature.yaml
+工具配置文件,定义了工具的参数和描述:
+
+1. **保留标识与结构**:
+ - **name**:工具的唯一标识,与文件名相对应
+ - 保持与现有文件结构一致
+
+2. **完善配置内容**:
+ - **label**和**description**:提供清晰的多语言显示内容
+ - **parameters**:详细定义工具参数及其属性
+
+3. **参数定义指导**:
+ - **type**:选择适当的参数类型(string/number/boolean/file)
+ - **form**:设置为`llm`(由AI提取)或`form`(UI配置)
+ - **required**:明确是否为必需参数
+
+```yaml
+identity:
+ name: feature_name # 与文件名对应
+ author: your_name
+ label:
+ en_US: Feature Display Name
+ zh_Hans: 功能显示名称
+description:
+ human: # 给人类用户看的描述
+ en_US: Description for human users
+ zh_Hans: 面向用户的功能描述
+ llm: Description for AI models to understand when to use this tool. # 给AI看的描述
+parameters: # 参数定义
+ - name: param_name
+ type: string # string, number, boolean, file等
+ required: true
+ label:
+ en_US: Parameter Display Name
+ zh_Hans: 参数显示名称
+ human_description:
+ en_US: Parameter description for users
+ zh_Hans: 面向用户的参数描述
+ llm_description: Detailed parameter description for AI models
+ form: llm # llm表示可由AI从用户输入中提取,form表示需要在UI中配置
+ # 其他参数...
+extra:
+ python:
+ source: tools/feature.py # 对应的Python实现文件
+# 可选:定义输出的JSON Schema
+output_schema:
+ type: object
+ properties:
+ result:
+ type: string
+ description: Description of the result
+```
+
+### tools/feature.py
+工具实现类,包含核心业务逻辑:
+
+1. **类名与文件名对应**:
+ - 类名遵循`FeatureTool`模式,与文件名相对应
+ - 确保一个文件中**只有一个**Tool子类
+
+2. **参数处理最佳实践**:
+ - 对于必需参数,使用`.get()`方法并提供默认值:`param = tool_parameters.get("param_name", "")`
+ - 对于可选参数,有两种处理方式:
+
+ ```python
+ # 方法1: 使用.get()方法(推荐用于单个参数)
+ optional_param = tool_parameters.get("optional_param") # 如果不存在返回None
+
+ # 方法2: 使用try-except(处理多个可选参数)
+ try:
+ name = tool_parameters["name"]
+ issuer_name = tool_parameters["issuer_name"]
+ except KeyError:
+ name = None
+ issuer_name = None
+ ```
+
+ - 这种try-except方式是当前处理多个可选参数的临时解决方案
+ - 始终在使用参数前验证其存在性和有效性
+
+3. **输出方式**:
+ - 使用`yield`返回各种类型的消息
+ - 支持文本、JSON、链接和变量输出
+
+```python
+from collections.abc import Generator
+from typing import Any
+
+from dify_plugin import Tool
+from dify_plugin.entities.tool import ToolInvokeMessage
+
+# 导入工具函数,确保函数名称拼写正确
+from utils.helpers import process_data
+
+class FeatureTool(Tool):
+ def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage, None, None]:
+ try:
+ # 1. 获取必需参数
+ param = tool_parameters.get("param_name", "")
+
+ # 2. 获取可选参数 - 使用try-except方式
+ try:
+ optional_param1 = tool_parameters["optional_param1"]
+ optional_param2 = tool_parameters["optional_param2"]
+ except KeyError:
+ optional_param1 = None
+ optional_param2 = None
+
+ # 另一种可选参数获取方式 - 使用.get()方法
+ another_optional = tool_parameters.get("another_optional") # 如果不存在返回None
+
+ # 3. 验证必需参数
+ if not param:
+ yield self.create_text_message("Parameter is required.")
+ return
+
+ # 4. 实现业务逻辑
+ result = self._process_data(param, optional_param1, optional_param2)
+
+ # 5. 返回结果
+ # 文本输出
+ yield self.create_text_message(f"Processed result: {result}")
+ # JSON输出
+ yield self.create_json_message({"result": result})
+ # 变量输出 (用于工作流)
+ yield self.create_variable_message("result_var", result)
+
+ except Exception as e:
+ # 错误处理
+ yield self.create_text_message(f"Error: {str(e)}")
+
+ def _process_data(self, param: str, opt1=None, opt2=None) -> str:
+ """
+ 实现具体的业务逻辑
+
+ Args:
+ param: 必需的参数
+ opt1: 可选参数1
+ opt2: 可选参数2
+
+ Returns:
+ 处理结果
+ """
+ # 根据参数是否存在执行不同的逻辑
+ if opt1 and opt2:
+ return f"Processed with all options: {param}, {opt1}, {opt2}"
+ elif opt1:
+ return f"Processed with option 1: {param}, {opt1}"
+ elif opt2:
+ return f"Processed with option 2: {param}, {opt2}"
+ else:
+ return f"Processed basic: {param}"
+```
+
+### utils/helper.py
+辅助函数,实现可复用的功能逻辑:
+
+1. **功能分离**:
+ - 将通用功能抽取为单独的函数
+ - 专注于单一职责
+ - 注意函数命名的一致性(避免导入错误)
+
+2. **错误处理**:
+ - 包含适当的异常处理
+ - 使用明确的异常类型
+ - 提供有意义的错误消息
+
+```python
+import requests
+from typing import Dict, Any, Optional
+
+def call_external_api(endpoint: str, params: Dict[str, Any], api_key: str) -> Dict[str, Any]:
+ """
+ 调用外部API的通用函数
+
+ Args:
+ endpoint: API端点URL
+ params: 请求参数
+ api_key: API密钥
+
+ Returns:
+ API响应的JSON数据
+
+ Raises:
+ Exception: 如果API调用失败
+ """
+ headers = {
+ "Authorization": f"Bearer {api_key}",
+ "Content-Type": "application/json"
+ }
+
+ try:
+ response = requests.get(endpoint, params=params, headers=headers, timeout=10)
+ response.raise_for_status() # 如果状态码不是200,抛出异常
+ return response.json()
+ except requests.RequestException as e:
+ raise Exception(f"API调用失败: {str(e)}")
+```
+
+### requirements.txt
+依赖列表,指定插件所需的Python库:
+
+1. **版本规范**:
+ - 使用`~=`指定依赖版本范围
+ - 避免过于宽松的版本要求
+
+2. **必要依赖**:
+ - 必须包含`dify_plugin`
+ - 添加插件功能所需的所有第三方库
+
+```
+dify_plugin~=0.0.1b76
+requests~=2.31.0
+# 其他依赖...
+```
+
+## 工具开发最佳实践
+
+### 1. 参数处理模式
+
+1. **必需参数处理**:
+ - 使用`.get()`方法并提供默认值:`param = tool_parameters.get("param_name", "")`
+ - 验证参数有效性:`if not param: yield self.create_text_message("Error: Required parameter missing.")`
+
+2. **可选参数处理**:
+ - **单个可选参数**:使用`.get()`方法,允许返回None:`optional = tool_parameters.get("optional_param")`
+ - **多个可选参数**:使用try-except模式处理KeyError:
+ ```python
+ try:
+ param1 = tool_parameters["optional_param1"]
+ param2 = tool_parameters["optional_param2"]
+ except KeyError:
+ param1 = None
+ param2 = None
+ ```
+ - 这种try-except方式是当前处理多个可选参数的临时解决方案
+
+3. **参数验证**:
+ - 对必需参数进行验证:`if not required_param: return error_message`
+ - 对可选参数进行条件处理:`if optional_param: do_something()`
+
+### 2. 安全的文件操作方式
+
+1. **避免本地文件读写**:
+ - Dify插件运行在无服务器环境(如AWS Lambda)中,本地文件系统操作可能不可靠
+ - 不要使用`open()`, `read()`, `write()`等直接文件操作
+ - 不依赖本地文件作为状态存储
+
+2. **使用内存或API替代**:
+ - 临时数据使用内存变量存储
+ - 持久化数据使用Dify提供的KV存储API
+ - 对于大量数据,考虑使用数据库或云存储服务
+
+### 3. 复制现有文件而非从头创建
+
+对于不确定结构正确性的情况,强烈建议使用下列方法:
+
+```bash
+# 复制工具YAML文件作为模板
+cp tools/existing_tool.yaml tools/new_tool.yaml
+
+# 复制工具Python实现
+cp tools/existing_tool.py tools/new_tool.py
+
+# 同理适用于provider文件
+cp provider/existing.yaml provider/new.yaml
+```
+
+这样可以确保文件结构和格式符合Dify插件框架的要求,然后再进行针对性修改。
+
+### 4. 拆分工具功能
+将复杂功能拆分为多个简单工具,每个工具专注于单一功能:
+
+```
+tools/
+├── search.py # 搜索功能
+├── search.yaml
+├── create.py # 创建功能
+├── create.yaml
+├── update.py # 更新功能
+├── update.yaml
+├── delete.py # 删除功能
+└── delete.yaml
+```
+
+### 2. 参数设计原则
+- **必要性**:只要求必要的参数,提供合理默认值
+- **类型定义**:选择合适的参数类型(string/number/boolean/file)
+- **清晰描述**:为人类和AI提供清晰的参数描述
+- **表单定义**:正确区分llm(AI提取)和form(UI配置)参数
+
+### 3. 错误处理
+```python
+try:
+ # 尝试执行操作
+ result = some_operation()
+ yield self.create_text_message("操作成功")
+except ValueError as e:
+ # 参数错误
+ yield self.create_text_message(f"参数错误: {str(e)}")
+except requests.RequestException as e:
+ # API调用错误
+ yield self.create_text_message(f"API调用失败: {str(e)}")
+except Exception as e:
+ # 其他未预期错误
+ yield self.create_text_message(f"发生错误: {str(e)}")
+```
+
+### 4. 代码组织与复用
+将可复用的逻辑抽取到utils目录:
+```python
+# 在工具实现中
+from utils.api_client import ApiClient
+
+class SearchTool(Tool):
+ def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage, None, None]:
+ client = ApiClient(self.runtime.credentials["api_key"])
+ results = client.search(tool_parameters["query"])
+ yield self.create_json_message(results)
+```
+
+### 5. 输出格式
+Dify支持多种输出格式:
+```python
+# 文本输出
+yield self.create_text_message("这是文本消息")
+
+# JSON输出
+yield self.create_json_message({"key": "value"})
+
+# 链接输出
+yield self.create_link_message("https://example.com")
+
+# 变量输出 (用于工作流)
+yield self.create_variable_message("variable_name", "variable_value")
+```
+
+## 常见错误与解决方案
+
+### 加载和初始化错误
+
+1. **多个Tool子类错误**
+ ```
+ Exception: Multiple subclasses of Tool in /path/to/file.py
+ ```
+ - **原因**:同一个Python文件中定义了多个继承自Tool的类
+ - **解决**:
+ - 检查文件内容:`cat tools/problematic_file.py`
+ - 每个文件保留一个与文件名对应的Tool子类
+ - 将其他Tool子类移至对应的单独文件
+
+2. **导入错误**
+ ```
+ ImportError: cannot import name 'x' from 'module'. Did you mean: 'y'?
+ ```
+ - **原因**:导入的函数名与实际定义不匹配
+ - **解决**:
+ - 检查utils中的函数名称:`cat utils/the_module.py`
+ - 修正导入语句中的拼写错误
+ - 注意函数名中的下划线、大小写等
+
+3. **凭证验证失败**
+ ```
+ ToolProviderCredentialValidationError: Invalid API key
+ ```
+ - **原因**:凭证验证逻辑失败
+ - **解决**:
+ - 检查`_validate_credentials`方法实现
+ - 确保API密钥格式正确
+ - 添加详细的错误提示信息
+
+### 运行时错误
+
+1. **参数获取错误**
+ ```
+ KeyError: 'parameter_name'
+ ```
+ - **原因**:尝试访问不存在的参数
+ - **解决**:
+ - 使用`get()`代替直接索引:`param = tool_parameters.get("param_name", "")`
+ - 确保参数名与YAML定义一致
+ - 添加参数存在性检查
+
+2. **API调用错误**
+ ```
+ requests.exceptions.RequestException: Connection error
+ ```
+ - **原因**:外部API调用失败
+ - **解决**:
+ - 添加超时参数:`timeout=10`
+ - 使用`try/except`捕获异常
+ - 实现重试逻辑
+
+3. **执行超时**
+ ```
+ TimeoutError: Function execution timed out
+ ```
+ - **原因**:操作耗时过长
+ - **解决**:
+ - 优化API调用
+ - 分解复杂操作为多个步骤
+ - 设置合理的超时限制
+
+### 配置和打包错误
+
+1. **YAML格式错误**
+ ```
+ yaml.YAMLError: mapping values are not allowed in this context
+ ```
+ - **原因**:YAML格式不正确
+ - **解决**:
+ - 检查缩进(使用空格而非制表符)
+ - 确保冒号后有空格
+ - 使用YAML验证器检查
+
+2. **打包失败**
+ ```
+ Error: Failed to pack plugin
+ ```
+ - **原因**:文件结构或依赖问题
+ - **解决**:
+ - 检查manifest.yaml配置
+ - 确保所有引用的文件存在
+ - 审查requirements.txt内容
+
+## 代码示例:TOTP工具
+
+以下是一个完整的TOTP (Time-based One-Time Password) 插件示例,展示了良好的代码组织和最佳实践:
+
+### utils/totp_verify.py
+```python
+import pyotp
+import time
+
+def verify_totp(secret_key, totp_code, offset=5, strict=False):
+ """
+ 验证基于时间的一次性密码(TOTP)。
+
+ Args:
+ secret_key: 用于生成TOTP的密钥或配置URL
+ totp_code: 用户提交的动态令牌
+ offset: 允许提前或延迟验证的秒数
+ strict: 是否使用严格验证(仅在精确匹配时返回成功)
+
+ Returns:
+ 包含以下内容的字典:
+ - 'status': 'success' 或 'fail'
+ - 'detail': 内部消息(不面向终端用户)
+ """
+ try:
+ # 检测是否为配置URL
+ if secret_key.startswith('otpauth://'):
+ totp = pyotp.parse_uri(secret_key)
+ else:
+ totp = pyotp.TOTP(secret_key)
+
+ current_time = time.time()
+
+ # 精确时间验证
+ if totp.verify(totp_code):
+ return {'status': 'success', 'detail': 'Token is valid'}
+
+ # 偏移验证
+ early_valid = totp.verify(totp_code, for_time=current_time + offset)
+ late_valid = totp.verify(totp_code, for_time=current_time - offset)
+ off_time_valid = early_valid or late_valid
+
+ detail_message = (
+ f"Token is valid but not on time. "
+ f"{'Early' if early_valid else 'Late'} within {offset} seconds"
+ if off_time_valid else
+ "Token is invalid"
+ )
+
+ if strict:
+ return {'status': 'fail', 'detail': detail_message}
+ else:
+ return (
+ {'status': 'success', 'detail': detail_message}
+ if off_time_valid
+ else {'status': 'fail', 'detail': detail_message}
+ )
+ except Exception as e:
+ return {'status': 'fail', 'detail': f'Verification error: {str(e)}'}
+```
+
+### tools/totp.yaml
+```yaml
+identity:
+ name: totp
+ author: alterxyz
+ label:
+ en_US: TOTP Validator
+ zh_Hans: TOTP 验证器
+description:
+ human:
+ en_US: Time-based one-time password (TOTP) validator
+ zh_Hans: 基于时间的一次性密码 (TOTP) 验证器
+ llm: Time-based one-time password (TOTP) validator, this tool is used to validate a 6 digit TOTP code with a secret key or provisioning URI.
+parameters:
+ - name: secret_key
+ type: string
+ required: true
+ label:
+ en_US: TOTP secret key or provisioning URI
+ zh_Hans: TOTP 私钥或 URI
+ human_description:
+ en_US: The secret key or provisioning URI used to generate the TOTP
+ zh_Hans: 用于生成 TOTP 的私钥或 URI
+ llm_description: The secret key or provisioning URI (starting with 'otpauth://') used to generate the TOTP, this is highly sensitive and should be kept secret.
+ form: llm
+ - name: user_code
+ type: string
+ required: true
+ label:
+ en_US: 6 digit TOTP code to validate
+ zh_Hans: 要验证的 6 位 TOTP 代码
+ human_description:
+ en_US: 6 digit TOTP code to validate
+ zh_Hans: 要验证的 6 位 TOTP 代码
+ llm_description: 6 digit TOTP code to validate
+ form: llm
+extra:
+ python:
+ source: tools/totp.py
+output_schema:
+ type: object
+ properties:
+ True_or_False:
+ type: string
+ description: Whether the TOTP is valid or not, return in string format, "True" or "False".
+```
+
+### tools/totp.py
+```python
+from collections.abc import Generator
+from typing import Any
+
+# 正确导入工具函数
+from utils.totp_verify import verify_totp
+
+from dify_plugin import Tool
+from dify_plugin.entities.tool import ToolInvokeMessage
+
+# 一个文件只包含一个Tool子类
+class TotpTool(Tool):
+ def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage, None, None]:
+ """验证基于时间的一次性密码(TOTP)"""
+ # 获取参数,使用get()避免KeyError
+ secret_key = tool_parameters.get("secret_key")
+ totp_code = tool_parameters.get("user_code")
+
+ # 参数验证
+ if not secret_key:
+ yield self.create_text_message("Error: Secret key is required.")
+ return
+ if not totp_code:
+ yield self.create_text_message("Error: TOTP code is required.")
+ return
+
+ try:
+ # 调用工具函数
+ result = verify_totp(secret_key, totp_code)
+
+ # 返回结果
+ yield self.create_json_message(result)
+
+ # 基于验证结果返回不同的消息
+ if result["status"] == "success":
+ yield self.create_text_message("Valid")
+ yield self.create_variable_message("True_or_False", "True")
+ else:
+ yield self.create_text_message("Invalid")
+ yield self.create_variable_message("True_or_False", "False")
+
+ except Exception as e:
+ # 错误处理
+ yield self.create_text_message(f"Verification error: {str(e)}")
+```
+
+### tools/secret_generator.py
+```python
+from collections.abc import Generator
+from typing import Any
+
+import pyotp
+
+from dify_plugin import Tool
+from dify_plugin.entities.tool import ToolInvokeMessage
+
+# 注意:一个文件只包含一个Tool子类
+class SecretGenerator(Tool):
+ def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage, None, None]:
+ """生成TOTP密钥"""
+ try:
+ # 生成随机密钥
+ secret_key = pyotp.random_base32()
+ yield self.create_text_message(secret_key)
+
+ # 安全获取可选参数
+ name = tool_parameters.get("name")
+ issuer_name = tool_parameters.get("issuer_name")
+
+ # 如果提供了名称或发行方,生成配置URI
+ if name or issuer_name:
+ provisioning_uri = pyotp.totp.TOTP(secret_key).provisioning_uri(
+ name=name,
+ issuer_name=issuer_name
+ )
+ yield self.create_variable_message("provisioning_uri", provisioning_uri)
+
+ except Exception as e:
+ yield self.create_text_message(f"Error generating secret: {str(e)}")
+```
+
+### requirements.txt
+```
+dify_plugin~=0.0.1b76
+pyotp~=2.9.0
+```
+
+这个示例展示了:
+- 清晰的功能分离(utils中的工具函数,tools中的工具类)
+- 良好的错误处理和参数验证
+- 一个文件只包含一个Tool子类
+- 详细的注释和文档字符串
+- 精心设计的YAML配置
+
+## 状态同步机制
+
+如果用户的描述与你记录的项目状态不同,或者你需要确认当前进度,请执行以下操作:
+
+1. 检查项目文件结构
+2. 阅读关键文件
+3. 明确告知用户:"我注意到项目状态可能与我之前的理解不同,我已重新检查了项目文件并更新了我的认知。"
+4. 描述你发现的实际状态
+5. 更新working目录中的进度记录
+
+## 首次启动行为
+
+当用户通过"@ai"或类似方式首次激活你时,你应该:
+
+1. **不要假设项目目标**:不要自行假定用户想开发什么类型的插件或功能
+2. **不要开始编写代码**:不要在没有明确指示的情况下就开始生成或修改代码
+3. **询问用户意图**:礼貌地询问用户希望开发什么类型的插件,需要帮助解决什么问题
+4. **提供能力概述**:简要说明你可以提供哪些类型的帮助(代码实现、调试、设计建议等)
+5. **请求项目信息**:请用户分享当前项目状态或文件结构,以便你提供更有针对性的帮助
+
+只有在收到明确指示后,才开始提供具体的开发建议或代码实现。
+
+记住,你的主要目标是协助用户高效地完成Dify插件开发,通过持续跟踪状态、提供专业建议和解决技术挑战来实现这一目标。
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ 通过直接提交修改来帮助改进文档内容
+
+
+ 发现错误或有改进建议?请提交问题反馈
+
+
diff --git a/plugin_dev_zh/0211-getting-started-dify-tool.zh.mdx b/plugin_dev_zh/0211-getting-started-dify-tool.zh.mdx
new file mode 100644
index 00000000..5f1fabab
--- /dev/null
+++ b/plugin_dev_zh/0211-getting-started-dify-tool.zh.mdx
@@ -0,0 +1,563 @@
+---
+dimensions:
+ type:
+ primary: implementation
+ detail: basic
+ level: beginner
+standard_title: Getting Started Dify Tool
+language: zh
+title: Dify 插件开发:Hello World 指南
+description: 本文档提供了从零开始Dify插件开发的详细教程,以创建Telegraph发布插件为例,涉及环境准备、项目初始化、虚拟环境配置、插件核心逻辑开发、本地运行调试、插件元信息完善以及打包发布等环节。
+---
+
+欢迎阅读 Dify 插件开发的入门指南!本教程假设您具备基本的编程背景知识,并有使用 Dify 平台的经验。完成本教程后,您将掌握创建简单 Dify 插件的基础流程,从 Dify 用户迈向插件贡献者。
+
+我们将一步步示范如何开发一个简单的 Telegraph 发布插件。本教程主要使用命令行操作,并辅以 VSCode 作为代码编辑器的示例说明。**建议您先快速通读一遍以了解整体流程,然后再跟着步骤动手操作。**
+
+> **面向 LLM 的说明:** 你也可以在这里使用我们构建的 prompt 来协助你开发插件:[Dify 插件开发:Prompt](/plugin_dev_zh/0211-getting-started-by-prompt.zh)
+
+在开始之前,您可以随时查阅我们提供的 [**开发者速查表 (Cheatsheet)**](/plugin_dev_zh/0131-cheatsheet.zh) 获取常用指令和信息,或在遇到更复杂问题时参考完整的开发者文档。
+
+## 1. 准备开发环境
+
+在开始开发 Dify 插件之前,请确保您的环境中已准备好以下工具:
+
+- **Dify 插件开发脚手架 (CLI):** 这是开发、调试和打包插件的核心工具,也称为 `dify-plugin-daemon` 或“插件开发 SDK”。
+- **Python 环境:** 需要 Python 3.12 或更高版本。
+
+### 1.1 安装 Dify 插件开发脚手架 (CLI)
+
+> 更详细的开发环境准备指南,请参考[初始化开发工具](/plugin_dev_zh/0221-initialize-development-tools.zh)。
+
+1. **下载:** 访问 [Dify Plugin CLI Releases](https://github.com/langgenius/dify-plugin-daemon/releases) 页面。根据您的操作系统(Windows, macOS Intel/ARM, Linux)下载对应的最新版本二进制文件。
+2. **设置执行权限 (macOS / Linux):**
+
+ - **以下步骤以 macOS (Apple Silicon / M 系列芯片) 为例**,假设下载的文件名为 `dify-plugin-darwin-arm64`。在终端中,进入文件所在目录,并执行以下命令赋予其执行权限:
+
+ ```bash
+ chmod +x dify-plugin-darwin-arm64
+ ```
+
+ - 对于 Linux 用户,请下载对应的 Linux 版本文件并执行类似 `chmod +x ` 的命令。
+ - 对于 Windows 用户,下载 `.exe` 文件后通常可直接运行。
+
+3. **验证安装:**
+
+ - 在终端中,执行以下命令检查工具是否能正常运行(请将 `./dify-plugin-darwin-arm64` 替换为您下载的实际文件名或路径):
+
+ ```bash
+ ./dify-plugin-darwin-arm64 version
+ ```
+
+ - 如果终端成功输出了版本号信息(例如 `v0.0.1-beta.15`),则说明安装成功。
+
+> **提示 (Tips):**
+>
+> - **macOS 安全提示:** 若在 macOS 上首次运行时提示“Apple 无法验证”或“无法打开”,请前往“系统设置”→“隐私与安全性”→“安全性”部分,找到相关提示并点击“仍要打开”或“允许”。
+> - **简化命令:** 您可以将下载的二进制文件重命名为一个更短的名称(例如 `dify` 或 `dify-plugin`),以便后续使用。示例:`mv dify-plugin-darwin-arm64 dify`,之后即可使用 `./dify version`。
+> - **全局安装 (可选):** 如果希望在系统的任何路径下都能直接运行该命令(例如,直接输入 `dify` 而不是 `./dify`),可以将重命名后的文件移动到系统的 `PATH` 环境变量包含的目录中,例如 `/usr/local/bin` (macOS/Linux) 或添加到 Windows 的环境变量中。
+> - 例如 (macOS/Linux): `sudo mv dify /usr/local/bin/`
+> - 配置完成后,直接在终端输入 `dify version` 应能成功输出版本号。
+
+**为方便起见,本文后续将使用 `./dify` 作为 Dify 插件开发脚手架命令的示例。请根据您的实际情况替换为对应的命令。**
+
+## 2. 初始化插件项目
+
+现在,让我们使用脚手架工具创建一个新的插件项目。
+
+1. 打开终端,执行初始化命令:
+
+ ```bash
+ ./dify plugin init
+ ```
+
+2. 根据提示依次输入插件的基本信息:
+ - **Plugin name:** 插件的唯一标识符。例如:`telegraph`
+ - _约束: 长度 1-128 字符,只能包含小写字母、数字、连字符(-)和下划线(\_)。_
+ - **Author:** 插件作者的标识符。例如:`alterxyz`
+ - _约束: 长度 1-64 字符,只能包含小写字母、数字、连字符(-)和下划线(\_)。_
+ - **Description:** 对插件功能的简短描述。例如:`A Telegraph plugin that allows you to publish your content easily`
+3. **选择开发语言:** 当提示 `Select language` 时,请选择 `python`。
+4. **选择插件类型:** 当提示 `Select plugin type` 时,对于本教程,请选择 `tool`。
+5. **选择附加功能:** 接下来会提示是否需要包含 Provider 验证、持久存储等附加功能。对于这个简单的 Hello World 插件,我们暂时不需要这些,可以直接按 **回车键** 跳过所有选项,直到看到成功信息。
+6. **确认创建成功:** 当终端输出类似以下信息时,表示插件项目已成功创建:
+
+ ```bash
+ [INFO] plugin telegraph created successfully, you can refer to `telegraph/GUIDE.md` for more information about how to develop it
+ ```
+
+现在,您的当前目录下应该出现了一个名为 `telegraph` (或您指定的插件名) 的新文件夹,这就是您的插件项目。
+
+## 3. 配置 Python 虚拟环境与依赖
+
+为了隔离项目依赖,推荐使用 Python 虚拟环境。
+
+### 3.1 创建并激活虚拟环境 (命令行方式)
+
+这是**推荐且通用**的方法,不依赖特定 IDE:
+
+1. **进入项目目录:**
+
+ ```bash
+ cd telegraph
+ ```
+
+2. **创建虚拟环境:** (建议命名为 `venv`)
+
+ ```bash
+ python -m venv venv
+ ```
+
+3. **激活虚拟环境:**
+
+ - **macOS / Linux:**
+
+ ```bash
+ source venv/bin/activate
+ ```
+
+ - **Windows (cmd.exe):**
+
+ ```bash
+ venv\Scripts\activate.bat
+ ```
+
+ - **Windows (PowerShell):**
+
+ ```bash
+ venv\Scripts\Activate.ps1
+ ```
+
+ - 激活成功后,您的终端提示符前通常会显示 `(venv)` 字样。
+
+### 3.2 安装基础依赖
+
+项目初始化时生成的 `requirements.txt` 文件已包含插件开发所需的基础库 `dify_plugin`。激活虚拟环境后,执行以下命令安装:
+
+```bash
+pip install -r requirements.txt
+```
+
+### 3.3 (可选) VSCode 集成环境配置
+
+如果您使用 VSCode 作为代码编辑器,可以利用其集成功能来管理 Python 环境:
+
+1. **打开项目文件夹:** 使用 VSCode 打开刚刚创建的 `telegraph` 文件夹。
+2. **选择 Python 解释器:**
+ - 打开命令面板 (macOS: `Cmd+Shift+P`, Windows/Linux: `Ctrl+Shift+P`)。
+ - 输入并选择 `Python: Select Interpreter`。
+ - 在弹出的列表中,选择您刚刚创建的虚拟环境中的 Python 解释器(通常路径包含 `.venv/bin/python` 或 `venv\Scripts\python.exe`)。如果列表没有自动显示,您可以选择 `Enter interpreter path...` 手动查找。
+ - _(请参考您本地对应的截图 ,它展示了选择解释器的界面)_
+3. **安装依赖 (若 VSCode 提示):** VSCode 可能会检测到 `requirements.txt` 文件并提示您安装其中的依赖项。如果出现提示,请确认安装。
+ - _(请参考您本地对应的截图 ,它展示了确认安装依赖的界面)_
+
+**请确保后续的所有 `pip install` 命令和运行 `python -m main` 的操作都在已激活的虚拟环境中执行。**
+
+## 4. 开发插件核心逻辑
+
+现在我们开始编写插件代码。本示例将实现一个简单的工具,用于将指定内容发布到 Telegraph。
+
+### 4.1 示例依赖库: `your-telegraph`
+
+我们将使用一个名为 `your-telegraph` 的 Python 库来与 Telegraph API 交互。(_这是一个假设的库名,请确保您实际使用的库是有效的_)。
+
+> `your-telegraph` 是一个简化 Telegraph API 操作的 Python 包装器,可以用几行代码轻松发布内容。
+
+其基本用法可能如下:
+
+```python
+# 示例代码,非插件内代码
+from ytelegraph import TelegraphAPI
+
+# 需要一个 access_token
+ph = TelegraphAPI(access_token="your_telegraph_access_token")
+
+# 创建页面并获取链接
+ph_link = ph.create_page_md("My First Page", "# Hello, Telegraph!\n\nThis is my first Telegraph page.")
+print(ph_link)
+```
+
+我们的目标是在 Dify 插件中实现类似的功能。
+
+### 4.2 添加并配置项目依赖
+
+1. **安装依赖库:** 确保您的虚拟环境已激活,然后在终端中执行:
+
+ ```bash
+ pip install your-telegraph
+ ```
+
+2. **更新 `requirements.txt`:** 打开项目根目录下的 `telegraph/requirements.txt` 文件,在 `dify_plugin` 下面添加一行,写入刚刚安装的库名:
+
+ ```plaintext
+ dify_plugin
+ your-telegraph
+ ```
+
+ 这样做可以确保其他开发者或部署环境能够方便地安装所有必需的依赖。
+
+### 4.3 配置 Provider 凭证
+
+我们的示例需要 `telegraph_access_token`。我们需要在 Provider 配置中定义这个凭证,以便用户在使用插件时可以输入。关于 Provider 配置的更多信息,请参考[一般规范定义](/plugin_dev_zh/0411-general-specifications.zh)。
+
+1. **编辑 Provider YAML:** 打开 `telegraph/provider/telegraph.yaml` 文件。
+2. **添加 `credentials_for_provider`:** 在文件末尾(或适当位置)添加以下内容:
+
+ ```yaml
+ # ... (文件可能已有的 identity, name, label, description, icon 等保持不变) ...
+
+ credentials_for_provider:
+ telegraph_access_token: # 这是凭证的内部名称,将在 Python 代码中使用
+ type: secret-input # 输入类型为密码框
+ required: true # 此凭证是必需的
+ label: # 在 Dify UI 中显示的标签 (支持多语言)
+ en_US: Telegraph Access Token
+ zh_Hans: Telegraph 访问令牌
+ # ... (其他语言)
+ placeholder: # 输入框的提示文字 (支持多语言)
+ en_US: Enter your Telegraph access token
+ zh_Hans: 请输入您的 Telegraph 访问令牌
+ # ... (其他语言)
+ help: # 帮助提示信息 (支持多语言)
+ en_US: How to get your Telegraph access token
+ zh_Hans: 如何获取 Telegraph 访问令牌
+ # ... (其他语言)
+ url: https://telegra.ph/api#createAccount # 点击帮助提示时跳转的 URL
+ ```
+
+ - **字段解释:**
+ - `telegraph_access_token`: 凭证的唯一标识符,代码中通过 `self.runtime.credentials["telegraph_access_token"]` 来访问用户输入的值。
+ - `type: secret-input`: 表示在 Dify 界面上会显示为密码输入框。
+ - `required: true`: 表示用户必须填写此凭证才能使用该插件提供的工具。
+ - `label`, `placeholder`, `help`: 提供多语言界面文本。
+ - `url`: (可选) 提供一个获取凭证的帮助链接。
+
+### 4.4 实现工具 (Tool) 逻辑
+
+现在我们来编写实际执行发布操作的工具代码。
+
+1. **编辑 Tool Python 文件:** 打开 `telegraph/tools/telegraph.py`。
+2. **实现 `_invoke` 方法:** 将文件内容替换为以下代码:
+
+ ```python
+ from collections.abc import Generator
+ from typing import Any
+ from ytelegraph import TelegraphAPI # 导入我们使用的库
+
+ from dify_plugin import Tool
+ from dify_plugin.entities.tool import ToolInvokeMessage
+
+ class TelegraphTool(Tool):
+ """
+ 一个简单的 Telegraph 发布工具。
+ """
+
+ def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage, None, None]:
+ """
+ 根据输入的标题和内容,创建一个新的 Telegraph 页面。
+
+ Args:
+ tool_parameters: 一个包含工具输入参数的字典:
+ - p_title (str): Telegraph 页面的标题。
+ - p_content (str): 要发布的 Markdown 格式的内容。
+
+ Yields:
+ ToolInvokeMessage: 包含成功创建的 Telegraph 页面 URL 的消息。
+
+ Raises:
+ Exception: 如果页面创建失败,则抛出包含错误信息的异常。
+ """
+ # 1. 从运行时获取凭证
+ try:
+ access_token = self.runtime.credentials["telegraph_access_token"]
+ except KeyError:
+ raise Exception("Telegraph Access Token 未配置或无效。请在插件设置中提供。")
+
+ # 2. 获取工具输入参数
+ title = tool_parameters.get("p_title", "Untitled") # 使用 .get 提供默认值
+ content = tool_parameters.get("p_content", "")
+
+ if not content:
+ raise Exception("发布内容不能为空。")
+
+ # 3. 调用库执行操作
+ try:
+ telegraph = TelegraphAPI(access_token) # 初始化 Telegraph API
+ ph_link = telegraph.create_page_md(title, content) # 创建页面
+ except Exception as e:
+ # 如果库调用失败,抛出异常
+ raise Exception(f"调用 Telegraph API 失败: {e}")
+
+ # 4. 返回结果
+ # 使用 create_link_message 生成一个包含链接的输出消息
+ yield self.create_link_message(ph_link)
+ ```
+
+ - **关键点:**
+ - 从 `self.runtime.credentials` 获取凭证。
+ - 从 `tool_parameters` 获取工具的输入参数(参数名将在下一步的 YAML 中定义)。使用 `.get()` 是更健壮的方式。
+ - 调用 `ytelegraph` 库执行实际操作。
+ - 使用 `try...except` 捕获可能的错误并抛出异常。
+ - 使用 `yield self.create_link_message(url)` 返回一个包含 URL 的结果给 Dify。
+
+### 4.5 配置工具 (Tool) 参数
+
+我们需要告诉 Dify 这个工具接收哪些输入参数。
+
+1. **编辑 Tool YAML 文件:** 打开 `telegraph/tools/telegraph.yaml`。
+2. **定义参数:** 将文件内容替换或修改为:
+
+ ```yaml
+ identity:
+ name: telegraph_publisher # 工具的唯一内部名称
+ author: alterxyz
+ label: # 在 Dify UI 中显示的工具名称 (多语言)
+ en_US: Publish to Telegraph
+ zh_Hans: 发布到 Telegraph
+ # ... (其他语言)
+ description:
+ human: # 给人类用户看的工具描述 (多语言)
+ en_US: Publish content to Telegraph as a new page.
+ zh_Hans: 将内容作为新页面发布到 Telegraph。
+ # ... (其他语言)
+ llm: # 给 LLM 看的工具描述 (用于 Agent 模式)
+ A tool that takes a title and markdown content, then publishes it as a new page on Telegraph, returning the URL of the published page. Use this when the user wants to publish formatted text content publicly via Telegraph.
+ parameters: # 定义工具的输入参数列表
+ - name: p_title # 参数的内部名称,与 Python 代码中的 key 对应
+ type: string # 参数类型
+ required: true # 是否必需
+ label: # 在 Dify UI 中显示的参数标签 (多语言)
+ en_US: Post Title
+ zh_Hans: 文章标题
+ human_description: # 给人类用户看的参数描述 (多语言)
+ en_US: The title for the Telegraph page.
+ zh_Hans: Telegraph 页面的标题。
+ llm_description: # 给 LLM 看的参数描述 (指导 Agent 如何填充)
+ The title of the post. Should be a concise and meaningful plain text string.
+ form: llm # 参数表单类型 ('llm' 或 'form')
+ - name: p_content
+ type: string
+ required: true
+ label:
+ en_US: Content (Markdown)
+ zh_Hans: 内容 (Markdown)
+ human_description:
+ en_US: The main content for the Telegraph page, written in Markdown format.
+ zh_Hans: Telegraph 页面的主要内容,请使用 Markdown 格式编写。
+ llm_description: # 强调格式要求对 LLM 很重要
+ The full content to be published on the Telegraph page. Must be provided in Markdown format. Ensure proper Markdown syntax for formatting like headings, lists, links, etc.
+ form: llm
+ extra: # 额外配置
+ python:
+ source: tools/telegraph.py # 指向实现该工具逻辑的 Python 文件
+ ```
+
+ - **字段解释:**
+ - `identity`: 工具的基本信息,`name` 是唯一标识。
+ - `description`: 分为 `human` (给用户看) 和 `llm` (给 Agent 看)。**`llm` 描述对于 Agent 能否正确理解和使用工具至关重要。**
+ - `parameters`: 定义每个输入参数。
+ - `name`: 内部名称,需与 Python 代码中 `tool_parameters.get("...")` 的键一致。
+ - `type`: 数据类型 (如 `string`, `number`, `boolean` 等)。
+ - `required`: 是否必须提供。
+ - `label`, `human_description`, `llm_description`: 类似 `identity` 中的描述,但针对具体参数。**`llm_description` 应清晰指导 LLM 如何生成或获取该参数的值,包括格式要求(如此处的 Markdown)。**
+ - `form`: 定义参数如何在 Dify 中呈现和填充。`llm` 表示该参数值可以由用户输入、通过变量传入,或者在 Agent 模式下由 LLM 自主决定;`form` 通常表示需要用户在界面上固定填写的配置项。对于工具输入,`llm` 更常见。
+ - `extra.python.source`: 指明实现此工具逻辑的 Python 文件路径(相对于项目根目录)。
+
+### 4.6 实现 Provider 凭证验证 (可选但推荐)
+
+为了确保用户提供的凭证有效,我们应该实现验证逻辑。
+
+1. **编辑 Provider Python 文件:** 打开 `telegraph/provider/telegraph.py`。
+2. **实现 `_validate_credentials` 方法:** 将文件内容替换为:
+
+ ```python
+ from typing import Any
+ from dify_plugin import ToolProvider
+ from dify_plugin.errors.tool import ToolProviderCredentialValidationError
+
+ class TelegraphProvider(ToolProvider):
+ def _validate_credentials(self, credentials: dict[str, Any]) -> None:
+ """
+ 验证提供的 Telegraph Access Token 是否有效。
+ 尝试使用该 token 创建一个测试页面。
+ 如果验证失败,应抛出 ToolProviderCredentialValidationError 异常。
+ """
+ access_token = credentials.get("telegraph_access_token")
+ if not access_token:
+ raise ToolProviderCredentialValidationError("Telegraph Access Token 不能为空。")
+
+ try:
+ # 尝试执行一个需要凭证的简单操作来验证
+ from ytelegraph import TelegraphAPI
+ ph = TelegraphAPI(access_token=access_token)
+ # 尝试创建一个临时的、无害的页面作为验证手段
+ # 注意:更好的验证方式可能是调用 API 的 'getAccountInfo' 等只读方法(如果存在)
+ test_page = ph.create_page_md("Dify Validation Test", "This is a test page created by Dify plugin validation.")
+ # 如果需要,可以考虑立即编辑或删除这个测试页面,但这会增加复杂性
+ # print(f"Validation successful. Test page created: {test_page}")
+ except Exception as e:
+ # 如果 API 调用失败,说明凭证很可能无效
+ raise ToolProviderCredentialValidationError(f"Telegraph 凭证验证失败: {e}")
+
+ ```
+
+ - **关键点:**
+ - 从 `credentials` 字典中获取凭证。
+ - 执行一个需要该凭证的 API 调用(最好是只读操作,如获取账户信息;如果没有,创建一个无害的测试页面也可以,但要注意可能产生的副作用)。
+ - 如果 API 调用成功,则不抛出异常,表示验证通过。
+ - 如果 API 调用失败,则捕获异常并抛出 `ToolProviderCredentialValidationError`,将原始错误信息包含在内。
+
+## 5. 本地运行与调试
+
+现在可以在本地运行插件,并在 Dify 中进行调试了。
+
+1. **准备 `.env` 文件:**
+
+ - 确保您仍在 `telegraph` 项目目录下。
+ - 复制环境变量模板文件:
+
+ ```bash
+ cp .env.example .env
+ ```
+
+ - **编辑 `.env` 文件:** 打开刚刚创建的 `.env` 文件,填入您的 Dify 环境信息:
+
+ ```dotenv
+ DIFY_API_HOST=https://your-dify-host.com # 替换为您的 Dify 实例地址 (例如 https://cloud.dify.ai)
+ DIFY_API_KEY=your-api-key # 替换为您的 Dify API 密钥
+ ```
+
+ - **获取 Host 和 Key:** 登录您的 Dify 环境,点击右上角的“插件”图标,然后点击调试图标(或类似虫子形状)。在弹出的窗口中,复制“API 密钥 (Key)”和“主机地址 (Host)”。 _(请参考您本地对应的截图 ,它展示了获取密钥和主机地址的界面)_
+
+2. **启动本地插件服务:**
+
+ - 确保您的 Python 虚拟环境已激活。
+ - 在 `telegraph` 目录下,运行主程序:
+
+ ```bash
+ python -m main
+ ```
+
+ - **观察终端输出:** 如果一切正常,您应该会看到类似以下的日志信息,表示插件工具已成功加载并连接到 Dify:
+
+ ```json
+ {"event": "log", "data": {"level": "INFO", "message": "Installed tool: telegraph_publisher", "timestamp": 1678886400.123456}}
+ {"event": "log", "data": {"level": "INFO", "message": "Plugin daemon started, waiting for requests...", "timestamp": 1678886400.123457}}
+ ```
+
+3. **在 Dify 中查看并测试:**
+
+ - **刷新 Dify 页面:** 回到您的 Dify 环境(浏览器中),刷新插件管理页面 (通常是 `https://your-dify-host.com/plugins`)。
+ - **查找插件:** 您应该能在列表中看到名为 "Telegraph" (或您在 Provider YAML 中定义的 `label`) 的插件,并且可能带有一个“调试中”的标记。
+ - **添加凭证:** 点击该插件,系统会提示您输入之前在 `provider/telegraph.yaml` 中定义的 "Telegraph Access Token"。输入有效的 token 并保存。如果您的验证逻辑 (`_validate_credentials`) 实现正确,这里会进行验证。 _(请参考您本地对应的截图 ,它展示了插件出现在列表并请求授权的界面)_
+ - **在应用中使用:** 现在,您可以在 Dify 的应用(如 Chatbot 或 Workflow)中添加这个工具节点,并尝试调用它了!当您在应用中运行并触发该工具时,请求会被转发到您本地运行的 `python -m main` 进程进行处理。您可以在本地终端看到相关的日志输出,并进行调试。
+
+4. **停止本地服务:** 在终端中按下 `Ctrl + C` 可以停止本地插件服务。
+
+这个运行 -> 测试 -> 停止 -> 修改代码 -> 重新运行 的循环是插件开发的主要流程。
+
+## 6. 完善插件元信息
+
+为了让插件更专业、更易于被发现和理解,我们需要完善一些展示性的信息。
+
+1. **图标 (Icon):**
+ - 在 `telegraph/_assets` 目录下放置一个代表您插件的图标文件(例如 `icon.png`, `icon.svg`)。推荐使用正方形、清晰的图片。
+2. **Provider 信息 (`provider/telegraph.yaml`):**
+
+ - 确保 `identity` 部分的 `label` (显示名称), `description` (功能描述), 和 `icon` (填写图标文件名,如 `icon.png`) 已填写并支持多语言。这部分信息主要在 Dify 应用编排界面中展示给_使用_插件的用户。
+
+ ```yaml
+ identity:
+ author: alterxyz
+ name: telegraph # 内部名称,保持不变
+ label:
+ en_US: Telegraph
+ zh_Hans: Telegraph 发布文章
+ description:
+ en_US: A Telegraph plugin that allow you publish your content easily
+ zh_Hans: 一个让您轻松发布内容的Telegraph插件
+ icon: icon.png # 引用 _assets 目录下的图标文件名
+ ```
+
+3. **插件清单 (`manifest.yaml`):**
+
+ - 编辑项目根目录下的 `telegraph/manifest.yaml` 文件。这是整个插件的“身份证”,其信息将展示在 Dify 的**插件管理页面**和**插件市场 (Marketplace)** 中。
+ - 务必更新或确认以下字段:
+ - `label`: 插件的**主要显示名称** (支持多语言)。
+ - `description`: 对插件功能的**整体简介** (支持多语言),应清晰概括其核心价值。注意市场展示可能有长度限制。
+ - `icon`: 插件的**主图标** (直接填写 `_assets` 目录下的图标文件名,如 `icon.png`)。
+ - `tags`: 为插件添加分类标签,有助于用户在市场中筛选。可选值请参考 Dify 提供的枚举或文档说明 (例如 `media`, `tools`, `data-processing` 等)。可参考 [ToolLabelEnum 定义](https://github.com/langgenius/dify-plugin-sdks/blob/main/python/dify_plugin/entities/tool.py)。
+
+ ```yaml
+ label:
+ en_US: Telegraph Publisher
+ zh_Hans: Telegraph 发布助手
+ description:
+ en_US: Easily publish content to Telegraph pages directly from your Dify applications. Supports Markdown formatting.
+ zh_Hans: 从 Dify 应用中轻松将内容发布为 Telegraph 页面,支持 Markdown 格式。
+ icon: icon.png
+ tags: ['media', 'content-creation'] # 示例标签
+ # ... (author, name 等其他字段保持不变)
+ ```
+
+4. **README 和隐私政策:**
+ - `README.md`: 编辑项目根目录下的 `README.md` 文件。它将作为插件在 **Marketplace** 上的详细介绍页面,应包含更丰富的信息,如功能详述、使用示例、配置指南、常见问题等。可参考 [AWS 插件市场页面](https://marketplace.dify.ai/plugins/langgenius/aws_tools) 的样式。
+ - `PRIVACY.md`: 如果您计划将插件发布到官方 Marketplace,需要提供隐私政策说明文件 `PRIVACY.md`,描述插件如何处理数据。
+
+## 7. 打包插件
+
+当插件开发完成并通过本地测试后,您可以将其打包成一个 `.difypkg` 文件,用于分发或安装。关于插件打包和发布的详细信息,请参考[发布概览](/plugin_dev_zh/0321-release-overview.zh)。
+
+1. **返回上级目录:** 确保您的终端当前路径在 `telegraph` 文件夹的**上一级**。
+
+ ```bash
+ cd ..
+ ```
+
+2. **执行打包命令:**
+
+ ```bash
+ ./dify plugin package ./telegraph
+ ```
+
+ (请将 `./telegraph` 替换为您插件项目的实际路径)
+
+3. **获取打包文件:** 命令执行成功后,将在当前目录下生成一个名为 `telegraph.difypkg` (或 `您的插件名.difypkg`) 的文件。
+
+这个 `.difypkg` 文件就是一个完整的插件包。您可以:
+
+- 在 Dify 的插件管理页面手动**上传并安装**它。
+- 将其**分享**给其他人安装。
+- 按照 Dify 的规范和流程,将其**发布到官方的插件市场 (Marketplace)**,让所有 Dify 用户都能发现和使用您的插件。具体发布流程请参考[发布到 Dify 市场](/plugin_dev_zh/0322-release-to-dify-marketplace.zh)。
+
+恭喜!您已经完成了第一个 Dify 插件的开发、调试、完善和打包的全过程。现在您可以基于这个基础,探索更复杂、更强大的插件功能了。
+
+## 下一步学习
+
+- [远程调试插件](/plugin_dev_zh/0411-remote-debug-a-plugin.zh) - 了解更高级的插件调试技巧
+- [持久化存储](/plugin_dev_zh/0411-persistent-storage-kv.zh) - 学习如何在插件中使用数据存储
+- [Slack 机器人插件开发示例](/plugin_dev_zh/0432-develop-a-slack-bot-plugin.zh) - 查看更复杂的插件开发案例
+- [工具插件](/plugin_dev_zh/0222-tool-plugin.zh) - 探索工具插件的高级功能
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ 通过直接提交修改来帮助改进文档内容
+
+
+ 发现错误或有改进建议?请提交问题反馈
+
+
diff --git a/plugin_dev_zh/0211-getting-started-new-model.zh.mdx b/plugin_dev_zh/0211-getting-started-new-model.zh.mdx
new file mode 100644
index 00000000..8df608cd
--- /dev/null
+++ b/plugin_dev_zh/0211-getting-started-new-model.zh.mdx
@@ -0,0 +1,137 @@
+---
+dimensions:
+ type:
+ primary: implementation
+ detail: basic
+ level: beginner
+standard_title: Getting Started New Model
+language: zh
+title: 快速接入一个新模型
+description: 本文档指导非专业开发者如何为Dify添加新模型,重点在于通过修改配置文件为现有模型供应商添加新型号模型。包括Fork仓库、复制与修改模型配置、更新供应商版本、本地测试以及提交贡献的完整流程。
+---
+
+欢迎来到 Dify 的插件开发世界!Dify 的强大功能离不开社区贡献者的共同努力。我们相信,即使你不是专业的程序员,只要你对 AI 技术充满热情并愿意查阅资料,也能为 Dify 贡献自己的一份力量,例如帮助 Dify 支持更多、更新的 AI 模型。
+
+本文将以最简明的方式,带你完成最常见也最简单的贡献:为一个 Dify **已经支持**的模型供应商,添加一个**新型号**的模型。这种方式通常**只需要修改配置文件**,无需编写代码,非常适合作为你的第一个贡献!
+
+> **相关概念**:在开始前,建议你阅读[模型插件](/plugin_dev_zh/0131-model-plugin-introduction.zh)文档,了解模型插件的基本概念和结构。
+
+**这种快速接入方式适用于:**
+
+- 新模型属于 Dify 已有插件支持的供应商(如 OpenAI, Google Gemini, Anthropic Claude 等)。
+- 新模型与同系列其他模型使用相同的 API 认证和基础调用逻辑。
+- 主要区别在于模型 ID、上下文长度、最大 Token 数、定价等配置参数。
+
+_(如果你需要添加的模型需要新的 API 逻辑或支持特殊功能,那将涉及到 Python 代码编写,请参考 [创建新模型提供者](/plugin_dev_zh/0222-creating-new-model-provider.zh) 获取更详细的指南。)_
+
+**准备工作:**
+
+- 熟悉基本的 Git 操作 (Fork, Clone, Pull Request)。
+- 一个 GitHub 账号。
+- 安装并配置好 Dify 插件开发工具包 (参考 [初始化开发工具](/plugin_dev_zh/0221-initialize-development-tools.zh))。
+
+**操作步骤:**
+
+1. **Fork & Clone 官方插件仓库:**
+
+ - 访问 Dify 官方插件仓库 `https://github.com/langgenius/dify-official-plugins`。
+ - 点击 "Fork" 按钮,将仓库复刻到你自己的 GitHub 账号下。
+ - 使用 Git 将你 Fork 的仓库 Clone 到你的本地电脑。
+
+2. **找到并复制模型配置文件:**
+
+ - 在本地仓库中,导航到 `models/` 目录下,找到你想要添加模型的供应商文件夹,例如 `vertex_ai`。
+ - 进入该供应商对应的模型类型子目录,通常是 `models/llm/` (如果是文本生成模型)。
+ - 在该目录下,找到一个与你要添加的新型号最相似的现有模型的 YAML 配置文件(例如 `gemini-1.0-pro-001.yaml`)。
+ - 复制这个 YAML 文件,并将其重命名为能清晰标识新型号的名称(例如 `gemini-1.5-pro-latest.yaml`)。
+
+3. **修改模型配置 (YAML):**
+
+ - 打开你刚刚重命名的 YAML 文件 (例如 `gemini-1.5-pro-latest.yaml`)。
+ - **核心步骤:** 参考**模型供应商的官方文档**,仔细核对并修改文件中的以下关键信息:
+ - `model`: **必须**更新为新型号的官方 API 标识符。
+ - `label`: **必须**更新为在 Dify 界面中展示给用户的模型名称 (建议提供 `en_US` 和 `zh_Hans` 两种语言)。
+ - `model_properties`: 更新 `context_size` (上下文窗口大小)。
+ - `parameter_rules`: 检查并更新模型参数的限制,特别是 `max_tokens` (最大输出 Token 数) 的 `default`, `min`, `max` 值。
+ - `pricing`: 更新模型的输入 (`input`) 和输出 (`output`) 定价,以及单位 (`unit`, 通常是 `0.000001` 表示百万 Token) 和货币 (`currency`)。
+ - _(参考)_ 关于模型 YAML 文件各字段的详细规范,请查阅 [模型设计规则](/plugin_dev_zh/0411-model-designing-rules.zh) 和 [模型 Schema 定义](/plugin_dev_zh/0412-model-schema.zh)。
+
+ **示例 (添加 Gemini 1.5 Pro):**
+
+ | 参数 | 可能的旧模型 (示例) | 新 Gemini 1.5 Pro (示例) | 说明 |
+ | :---------------- | :------------------- | :----------------------- | :---------------------------------- |
+ | `model` | `gemini-1.0-pro-001` | `gemini-1.5-pro-latest` | **必须**修改为官方模型 ID |
+ | `label: en_US` | Gemini 1.0 Pro | Gemini 1.5 Pro | **必须**修改为用户可见标签 |
+ | `context_size` | 30720 | 1048576 | **必须**根据官方文档修改 |
+ | `max_tokens` (下) | 2048 | 8192 | **必须**根据官方文档修改默认/最大值 |
+
+4. **更新供应商 Manifest 版本:**
+
+ - 回到该模型供应商的根目录 (例如 `models/vertex_ai/`)。
+ - 找到并打开 `manifest.yaml` 文件。
+ - 将其中的 `version` 字段递增一个小版本号 (例如 `version: 0.0.8` -> `version: 0.0.9`)。这告诉 Dify 这是一个更新。
+
+5. **打包与本地测试:**
+
+ - 打开你的终端 (命令行工具)。
+ - **确保你的当前目录是 `dify-official-plugins` 仓库的根目录** (即包含 `models`, `tools` 等文件夹的那个目录)。
+ - 运行打包命令:
+
+ ```bash
+ # 将 替换为实际的供应商目录名,例如 cohere 或 vertex_ai
+ dify plugin package models/
+ ```
+
+ - _成功后,你会看到类似 `plugin packaged successfully, output path: .difypkg` 的提示,并在当前项目根目录下生成一个名为 `.difypkg` 的插件包文件。_
+ - 登录你的 Dify 实例 (本地部署或云版本均可)。
+ - 点击 Dify 页面最顶部导航栏右侧的 **"插件"** 菜单项。
+ - 在插件页面,点击 **"安装插件"** 按钮。
+ - 选择 **"本地插件"** 选项卡。
+ - 点击上传区域,选择或拖拽你刚刚在本地生成的 `.difypkg` 文件进行上传。
+ - 等待插件安装或更新完成。
+ - 安装成功后,通常需要前往 "设置" -> "模型供应商" 找到对应的供应商,并配置你的 API 凭证(如果你之前没有配置过的话)。
+ - 创建一个新的 Dify 应用或编辑现有应用,在 "提示词编排" -> "模型" 设置中,尝试选择你新添加的模型。进行一些简单的对话或调用测试,确保它能正常工作并返回预期结果。
+
+6. **提交你的贡献:**
+ - 本地测试无误后,将你的修改(新的模型 YAML 文件和更新后的 `manifest.yaml`)通过 Git 提交 (commit) 并推送 (push) 到你 Fork 的 GitHub 仓库。
+ - 在 GitHub 上,向 `langgenius/dify-official-plugins` 主仓库发起一个 Pull Request (PR)。在 PR 描述中,可以简要说明你添加了哪个模型,并附上该模型官方文档的链接,方便审核者确认参数。
+
+---
+
+**然后呢?**
+
+一旦你的 PR 被审核通过并合并,你的贡献就会成为 Dify 官方插件的一部分,所有 Dify 用户都能方便地使用这个新模型了!
+
+这种快速接入方法是让 Dify 支持新模型的最快途径。当然,如果未来这个模型需要支持更复杂的功能(例如图片输入、函数调用等),那么可能就需要有经验的开发者对插件进行代码层面的更新了。但你现在完成的这一步,已经是非常有价值的贡献!
+
+**探索更多:**
+
+- [模型 Schema 定义](/plugin_dev_zh/0412-model-schema.zh) (了解模型 YAML 文件的详细规则)
+- [模型设计规则](/plugin_dev_zh/0411-model-designing-rules.zh) (了解模型参数设计的规范)
+- [一般规范定义](/plugin_dev_zh/0411-general-specifications.zh) (了解 `manifest.yaml` 的作用)
+- [创建新模型提供者](/plugin_dev_zh/0222-creating-new-model-provider.zh) (了解如何添加新的模型提供商)
+- [发布至 Dify 市场](/plugin_dev_zh/0322-release-to-dify-marketplace.zh) (学习如何发布你的插件)
+- [Dify 官方插件仓库](https://github.com/langgenius/dify-official-plugins) (查看其他插件的例子)
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ 通过直接提交修改来帮助改进文档内容
+
+
+ 发现错误或有改进建议?请提交问题反馈
+
+
diff --git a/plugin_dev_zh/0221-initialize-development-tools.zh.mdx b/plugin_dev_zh/0221-initialize-development-tools.zh.mdx
new file mode 100644
index 00000000..ca641c3e
--- /dev/null
+++ b/plugin_dev_zh/0221-initialize-development-tools.zh.mdx
@@ -0,0 +1,89 @@
+---
+dimensions:
+ type:
+ primary: implementation
+ detail: standard
+ level: beginner
+standard_title: Initialize Development Tools
+language: zh
+title: 初始化开发工具
+description: 本文档详细介绍了Dify插件开发前的必要准备工作,包括安装Dify插件脚手架工具(dify-plugin-daemon)和配置Python环境(版本要求≥3.12)的完整步骤。文档还提供了各类型插件开发的参考链接。
+---
+
+开发 Dify 插件需要进行以下准备。本文档是开始[插件开发](/plugin_dev_zh/0111-getting-started-dify-plugin.zh)的第一步。
+
+* Dify 插件脚手架工具
+* Python 环境,版本号 ≥ 3.12
+
+> Dify 插件开发脚手架工具又称为 `dify-plugin-daemon`,可以被视作**插件开发 SDK**。
+
+### **1. 安装 Dify 插件开发脚手架工具**
+
+访问 [Dify Plugin CLI](https://github.com/langgenius/dify-plugin-daemon/releases) 项目地址,下载并安装最新版本号和对应操作系统的工具。
+
+本文**以装载 M 系列芯片的 macOS** 为例。下载 `dify-plugin-darwin-arm64` 文件后,赋予其执行权限。
+
+```
+chmod +x dify-plugin-darwin-arm64
+```
+
+运行以下命令检查安装是否成功。
+
+```
+./dify-plugin-darwin-arm64 version
+```
+
+> 若提示 “Apple 无法验证” 错误,请前往 **“设置 → 隐私与安全性 → 安全性”**,轻点 “仍要打开” 按钮。
+
+运行命令后,终端若返回类似 `v0.0.1-beta.15` 的版本号信息,则说明安装成功。
+
+> **💡 提示:**
+>
+> 如果想要在系统全局使用 `dify` 命令运行脚手架工具,建议将该二进制文件重命名为 `dify` 并拷贝至 `/usr/local/bin` 系统路径内。
+>
+> 配置完成后,在终端输入 `dify version` 命令后将输出版本号信息。
+>
+>
+
+### **2. 初始化 Python 环境**
+
+详细说明请参考 [Python 安装教程](https://pythontest.com/python/installing-python-3-11/),或询问 LLM 安装版本号 ≥ 3.12 的 Python 环境。
+
+### 3. 开发插件
+
+请参考以下内容查看不同类型的插件开发示例:
+
+- [工具插件开发指南](/plugin_dev_zh/0211-getting-started-dify-tool.zh) - Hello World 入门教程
+- [模型插件开发指南](/plugin_dev_zh/0211-getting-started-new-model.zh) - 快速接入一个新模型
+- [Agent策略插件开发指南](/plugin_dev_zh/9433-agent-strategy-plugin.zh) - 创建自定义推理策略
+- [扩展插件开发指南](/plugin_dev_zh/9231-extension-plugin.zh) - 通过 Webhook 实现外部服务集成
+- [插件打包和发布](/plugin_dev_zh/0321-release-overview.zh) - 发布您的插件
+
+## 下一步学习
+
+- [Dify 插件开发:Hello World 指南](/plugin_dev_zh/0211-getting-started-dify-tool.zh) - 开始您的第一个插件开发
+- [插件开发速查表](/plugin_dev_zh/0131-cheatsheet.zh) - 了解常用命令和概念
+- [一般规范定义](/plugin_dev_zh/0411-general-specifications.zh) - 学习插件元数据配置
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ 通过直接提交修改来帮助改进文档内容
+
+
+ 发现错误或有改进建议?请提交问题反馈
+
+
diff --git a/plugin_dev_zh/0222-creating-new-model-provider-extra.zh.mdx b/plugin_dev_zh/0222-creating-new-model-provider-extra.zh.mdx
new file mode 100644
index 00000000..1c79993b
--- /dev/null
+++ b/plugin_dev_zh/0222-creating-new-model-provider-extra.zh.mdx
@@ -0,0 +1,297 @@
+---
+dimensions:
+ type:
+ primary: implementation
+ detail: standard
+ level: intermediate
+standard_title: Creating New Model Provider Extra
+language: zh
+title: 实现标准的模型集成
+description: 本文档面向需要编写Python代码以添加或增强Dify模型支持的开发者,详细指导如何创建目录结构、编写模型配置、实现模型调用逻辑、调试和发布插件的完整流程,包含了核心方法实现和错误处理的细节。
+---
+
+本文档是为需要通过编写 Python 代码来为 Dify 添加或增强模型支持的开发者准备的标准指南。当你需要添加的模型涉及新的 API 调用逻辑、特殊参数处理或 Dify 需要显式支持的新功能(如 Vision, Tool Calling)时,就需要遵循本指南的步骤。
+
+**阅读本文前,建议你:**
+
+* 具备 Python 编程基础和面向对象编程的基本理解。
+* 熟悉你想要集成的模型供应商提供的 API 文档和认证方式。
+* 已安装并配置好 Dify 插件开发工具包 (参考 [初始化开发工具](../initialize-development-tools.md))。
+* (可选)阅读 [模型插件介绍](link-to-conceptual-intro) 文档,了解模型插件的基本概念和架构。
+
+本指南将引导你完成创建目录结构、编写模型配置 (YAML)、实现模型调用逻辑 (Python) 以及调试和发布插件的全过程。
+
+---
+
+## 第 1 步: 创建目录结构
+
+一个组织良好的目录结构是开发可维护插件的基础。你需要为你的模型供应商插件创建特定的目录和文件。
+
+1. **定位或创建供应商目录:** 在你的插件项目(通常是 `dify-official-plugins` 的本地克隆)的 `models/` 目录下,找到或创建以模型供应商命名的文件夹(例如 `models/my_new_provider`)。
+2. **创建 `models` 子目录:** 在供应商目录下,创建 `models` 子目录。
+3. **按模型类型创建子目录:** 在 `models/models/` 目录下,为你需要支持的**每种模型类型**创建一个子目录。常见的类型包括:
+ * `llm`: 文本生成模型
+ * `text_embedding`: 文本 Embedding 模型
+ * `rerank`: Rerank 模型
+ * `speech2text`: 语音转文字模型
+ * `tts`: 文字转语音模型
+ * `moderation`: 内容审查模型
+4. **准备实现文件:**
+ * 在每个模型类型目录下(例如 `models/models/llm/`),你需要创建一个 Python 文件来实现该类型模型的调用逻辑(例如 `llm.py`)。
+ * 同样在该目录下,你需要为该类型下的每个具体模型创建一个 YAML 配置文件(例如 `my-model-v1.yaml`)。
+ * (可选)可以创建一个 `_position.yaml` 文件来控制该类型下模型在 Dify UI 中的显示顺序。
+
+**示例结构 (假设供应商 `my_provider` 支持 LLM 和 Embedding):**
+
+```bash
+models/my_provider/
+├── models # 模型实现和配置目录
+│ ├── llm # LLM 类型
+│ │ ├── _position.yaml (可选, 控制排序)
+│ │ ├── my-llm-model-v1.yaml
+│ │ ├── my-llm-model-v2.yaml
+│ │ └── llm.py # LLM 实现逻辑
+│ └── text_embedding # Embedding 类型
+│ ├── _position.yaml (可选, 控制排序)
+│ ├── my-embedding-model.yaml
+│ └── text_embedding.py # Embedding 实现逻辑
+├── provider # 供应商级别代码目录
+│ └── my_provider.py (用于凭证验证等, 参考“创建模型供应商”文档)
+└── manifest.yaml # 插件清单文件
+```
+
+---
+
+## 第 2 步: 定义模型配置 (YAML)
+
+对于每个具体模型,你需要创建一个 YAML 文件来描述其属性、参数和功能,以便 Dify 能够正确地理解和使用它。
+
+1. **创建 YAML 文件:** 在对应的模型类型目录下(例如 `models/models/llm/`),为你要添加的模型创建一个 YAML 文件,文件名通常与模型 ID 保持一致或具有描述性(例如 `my-llm-model-v1.yaml`)。
+2. **编写配置内容:** 遵循 [AIModelEntity Schema Definition](../../../schema-definition/model/model-designing-rules.md#aimodelentity) 规范编写内容。关键字段包括:
+ * `model`: (必需) 模型的官方 API 标识符。
+ * `label`: (必需) 在 Dify UI 中显示的名称 (支持多语言)。
+ * `model_type`: (必需) 必须与所在目录类型一致 (如 `llm`)。
+ * `features`: (可选) 声明模型支持的特殊功能 (如 `vision`, `tool-call`, `stream-tool-call` 等)。
+ * `model_properties`: (必需) 定义模型固有属性,如 `mode` (`chat` 或 `completion`), `context_size`。
+ * `parameter_rules`: (必需) 定义用户可调参数及其规则 (名称 `name`, 类型 `type`, 是否必须 `required`, 默认值 `default`, 范围 `min`/`max`, 选项 `options` 等)。可以使用 `use_template` 引用预定义模板简化常见参数(如 `temperature`, `max_tokens`)的配置。
+ * `pricing`: (可选) 定义模型的计费信息。
+
+**示例 (`claude-3-5-sonnet-20240620.yaml`):**
+
+```yaml
+model: claude-3-5-sonnet-20240620
+label:
+ en_US: claude-3-5-sonnet-20240620
+model_type: llm
+features:
+ - agent-thought
+ - vision
+ - tool-call
+ - stream-tool-call
+ - document
+model_properties:
+ mode: chat
+ context_size: 200000
+parameter_rules:
+ - name: temperature
+ use_template: temperature
+ - name: top_p
+ use_template: top_p
+ - name: max_tokens
+ use_template: max_tokens
+ required: true
+ default: 8192
+ min: 1
+ max: 8192 # 注意 Dify 层面可能有限制
+pricing:
+ input: '3.00'
+ output: '15.00'
+ unit: '0.000001' # 每百万 token
+ currency: USD
+```
+
+---
+
+## 第 3 步: 编写模型调用代码 (Python)
+
+这是实现模型功能的和核心步骤。你需要在对应模型类型的 Python 文件中(例如 `llm.py`)编写代码来处理 API 调用、参数转换和结果返回。
+
+1. **创建/编辑 Python 文件:** 在模型类型目录下(例如 `models/models/llm/`)创建或打开相应的 Python 文件(例如 `llm.py`)。
+2. **定义实现类:**
+ * 定义一个类,例如 `MyProviderLargeLanguageModel`。
+ * 该类必须继承自 Dify 插件 SDK 中对应的**模型类型基类**。例如,对于 LLM,需要继承 `dify_plugin.provider_kits.llm.LargeLanguageModel`。
+
+ ```python
+ import logging
+ from typing import Union, Generator, Optional, List
+ from dify_plugin.provider_kits.llm import LargeLanguageModel # 导入基类
+ from dify_plugin.provider_kits.llm import LLMResult, LLMResultChunk, LLMUsage # 导入结果和用量类
+ from dify_plugin.provider_kits.llm import PromptMessage, PromptMessageTool # 导入消息和工具类
+ from dify_plugin.errors.provider_error import InvokeError, InvokeAuthorizationError # 导入错误类
+ # 假设你有一个 vendor_sdk 用于调用 API
+ # import vendor_sdk
+
+ logger = logging.getLogger(__name__)
+
+ class MyProviderLargeLanguageModel(LargeLanguageModel):
+ # ... 实现方法 ...
+ ```
+
+3. **实现关键方法:** (具体需要实现的方法取决于继承的基类,以下以 LLM 为例)
+ * `_invoke(...)`: **核心调用方法**。
+ * **签名:** `def _invoke(self, model: str, credentials: dict, prompt_messages: List[PromptMessage], model_parameters: dict, tools: Optional[List[PromptMessageTool]] = None, stop: Optional[List[str]] = None, stream: bool = True, user: Optional[str] = None) -> Union[LLMResult, Generator[LLMResultChunk, None, None]]:`
+ * **职责:**
+ * 使用 `credentials` 和 `model_parameters` 准备 API 请求。
+ * 将 Dify 的 `prompt_messages` 格式转换为供应商 API 所需的格式。
+ * 处理 `tools` 参数以支持 Function Calling / Tool Use (如果模型支持)。
+ * 根据 `stream` 参数决定是进行流式调用还是同步调用。
+ * **流式返回:** 如果 `stream=True`,此方法必须返回一个生成器 (`Generator`),通过 `yield` 逐块返回 `LLMResultChunk` 对象。每个 chunk 包含部分结果(文本、工具调用块等)和可选的用量信息。
+ * **同步返回:** 如果 `stream=False`,此方法必须返回一个完整的 `LLMResult` 对象,包含最终的文本结果、完整的工具调用列表以及总的用量信息 (`LLMUsage`)。
+ * **实现模式:** 强烈建议将同步和流式逻辑拆分到内部帮助方法中。
+
+ ```python
+ def _invoke(self, ..., stream: bool = True, ...) -> Union[LLMResult, Generator[LLMResultChunk, None, None]]:
+ # 准备 API 请求参数 (认证、模型参数转换、消息格式转换等)
+ api_params = self._prepare_api_params(credentials, model_parameters, prompt_messages, tools, stop)
+
+ try:
+ if stream:
+ return self._invoke_stream(model, api_params, user)
+ else:
+ return self._invoke_sync(model, api_params, user)
+ except vendor_sdk.APIError as e:
+ # 处理 API 错误, 映射到 Dify 错误 (参考 _invoke_error_mapping)
+ # ... raise mapped_error ...
+ pass # Replace with actual error handling
+ except Exception as e:
+ logger.exception("Unknown error during model invocation")
+ raise e # Or raise a generic InvokeError
+
+ def _invoke_stream(self, model: str, api_params: dict, user: Optional[str]) -> Generator[LLMResultChunk, None, None]:
+ # 调用 vendor_sdk 的流式接口
+ # for api_chunk in vendor_sdk.create_stream(...):
+ # # 将 api_chunk 转换为 LLMResultChunk
+ # dify_chunk = self._convert_api_chunk_to_llm_result_chunk(api_chunk)
+ # yield dify_chunk
+ pass # Replace with actual implementation
+
+ def _invoke_sync(self, model: str, api_params: dict, user: Optional[str]) -> LLMResult:
+ # 调用 vendor_sdk 的同步接口
+ # api_response = vendor_sdk.create_sync(...)
+ # 将 api_response 转换为 LLMResult (包括 message.content, tools, usage)
+ # dify_result = self._convert_api_response_to_llm_result(api_response)
+ # return dify_result
+ pass # Replace with actual implementation
+ ```
+
+ * `validate_credentials(self, model: str, credentials: dict) -> None`: (必需) 用于在用户添加或修改凭证时验证其有效性。通常通过调用一个简单的、低成本的 API 端点(如列出可用模型、检查余额等)来实现。如果验证失败,应抛出 `CredentialsValidateFailedError` 或其子类。
+ * `get_num_tokens(self, model: str, credentials: dict, prompt_messages: List[PromptMessage], tools: Optional[List[PromptMessageTool]] = None) -> int`: (可选但推荐) 用于预估给定输入的 Token 数量。如果无法准确计算或 API 不支持,可以返回 0。
+ * `@property _invoke_error_mapping(self) -> dict[type[InvokeError], list[type[Exception]]]`: (必需) 定义一个**错误映射**字典。键是 Dify 的标准 `InvokeError` 子类,值是供应商 SDK 可能抛出的需要被映射到该标准错误的异常类型列表。这对于 Dify 统一处理不同供应商的错误至关重要。
+
+ ```python
+ @property
+ def _invoke_error_mapping(self) -> dict[type[InvokeError], list[type[Exception]]]:
+ # 示例映射
+ mapping = {
+ InvokeAuthorizationError: [
+ vendor_sdk.AuthenticationError,
+ vendor_sdk.PermissionDeniedError,
+ ],
+ InvokeRateLimitError: [
+ vendor_sdk.RateLimitError,
+ ],
+ # ... 其他映射 ...
+ }
+ # 可以在这里加入基类的默认映射 (如果基类提供的话)
+ # base_mapping = super()._invoke_error_mapping
+ # mapping.update(base_mapping) # 注意合并策略
+ return mapping
+ ```
+
+---
+
+## 第 4 步: 调试插件
+
+在将插件贡献给社区之前,充分的测试和调试是必不可少的。Dify 提供了远程调试功能,让你可以在本地修改代码并实时在 Dify 实例中测试效果。
+
+1. **获取调试信息:**
+ * 在你的 Dify 实例中,进入 "插件管理" 页面 (可能需要管理员权限)。
+ * 点击页面右上角的 "调试插件",获取你的 `调试 Key` 和 `远程服务器地址` (例如 `http://:5003`)。
+2. **配置本地环境:**
+ * 在你的本地插件项目**根目录**下,找到或创建 `.env` 文件 (可从 `.env.example` 复制)。
+ * 编辑 `.env` 文件,填入调试信息:
+
+ ```dotenv
+ INSTALL_METHOD=remote
+ REMOTE_INSTALL_HOST= # Dify 服务器地址
+ REMOTE_INSTALL_PORT=5003 # 调试端口
+ REMOTE_INSTALL_KEY=****-****-****-****-**** # 你的调试 Key
+ ```
+
+3. **启动本地插件服务:**
+ * 在插件项目根目录下,确保你的 Python 环境已激活(如果使用虚拟环境)。
+ * 运行主程序:
+
+ ```bash
+ python -m main
+ ```
+
+ * 观察终端输出,如果连接成功,通常会有相应的日志提示。
+4. **在 Dify 中测试:**
+ * 刷新 Dify 的 "插件" 或 "模型供应商" 页面,你应该能看到你的本地插件实例,可能带有 "调试中" 标识。
+ * 前往 "设置" -> "模型供应商",找到你的插件,配置有效的 API 凭证。
+ * 在 Dify 应用中选择并使用你的模型进行测试。你在本地对 Python 代码的修改(保存后通常会自动重新加载服务)会直接影响 Dify 中的调用行为。使用 Dify 的调试预览功能可以帮助你检查输入输出和错误信息。
+
+---
+
+## 第 5 步: 打包与发布
+
+当你完成了开发和调试,并对插件的功能满意后,就可以将其打包并贡献给 Dify 社区了。
+
+1. **打包插件:**
+ * 停止本地调试服务 (`Ctrl+C`)。
+ * 在插件项目**根目录**下运行打包命令:
+
+ ```bash
+ # 将 替换为你的供应商目录名
+ dify plugin package models/
+ ```
+
+ * 这将在项目根目录下生成一个 `.difypkg` 文件。
+2. **提交 Pull Request:**
+ * 确保你的代码风格良好,并遵循 Dify 的[插件发布规范](https://docs.dify.ai/zh-hans/plugins/publish-plugins/publish-to-dify-marketplace)。
+ * 将你的本地 Git 提交推送到你 Fork 的 `dify-official-plugins` 仓库。
+ * 在 GitHub 上向 `langgenius/dify-official-plugins` 主仓库发起 Pull Request。在 PR 描述中清晰说明你所做的更改、添加的模型或功能,以及任何必要的测试说明。
+ * 等待 Dify 团队审核。审核通过并合并后,你的贡献将包含在官方插件中,并在 [Dify Marketplace](https://marketplace.dify.ai/) 上可用。
+
+---
+
+## 探索更多
+
+* [模型 Schema 定义](/plugin_dev_zh/0412-model-schema.zh) (模型 YAML 规范)
+* [插件 Manifest 结构](/plugin_dev_zh/0411-general-specifications.zh) (`manifest.yaml` 规范)
+* [Dify Plugin SDK 参考](https://github.com/langgenius/dify-plugin-sdks) (查找基类、数据结构和错误类型)
+* [Dify 官方插件仓库](https://github.com/langgenius/dify-official-plugins) (查看现有插件的实现)
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ 通过直接提交修改来帮助改进文档内容
+
+
+ 发现错误或有改进建议?请提交问题反馈
+
+
diff --git a/plugin_dev_zh/0222-creating-new-model-provider.zh.mdx b/plugin_dev_zh/0222-creating-new-model-provider.zh.mdx
new file mode 100644
index 00000000..eedd9cac
--- /dev/null
+++ b/plugin_dev_zh/0222-creating-new-model-provider.zh.mdx
@@ -0,0 +1,285 @@
+---
+dimensions:
+ type:
+ primary: implementation
+ detail: standard
+ level: intermediate
+standard_title: Creating New Model Provider
+language: zh
+title: 创建模型供应商
+description: 本文档详细指导如何创建模型供应商插件,包括项目初始化、模型配置方式(预定义模型和自定义模型)的选择、创建供应商配置YAML文件以及编写供应商代码的完整流程。
+---
+
+创建 Model 类型插件的第一步是初始化插件项目并创建模型供应商文件,随后编写具体的预定义 / 自定义模型代码。如果您只是想为已有的模型供应商添加新模型,请参考[快速接入一个新模型](/plugin_dev_zh/0211-getting-started-new-model.zh)。
+
+### 前置准备
+
+* Dify 插件脚手架工具
+* Python 环境,版本号 ≥ 3.12
+
+关于如何准备插件开发的脚手架工具,详细说明请参考[初始化开发工具](/plugin_dev_zh/0221-initialize-development-tools.zh)。在开始之前,建议您先了解[模型插件](/plugin_dev_zh/0131-model-plugin-introduction.zh)的基本概念和结构。
+
+### 创建新项目
+
+在脚手架命令行工具的路径下,创建一个新的 dify 插件项目。
+
+```
+./dify-plugin-darwin-arm64 plugin init
+```
+
+如果你已将该二进制文件重命名为了 `dify` 并拷贝到了 `/usr/local/bin` 路径下,可以运行以下命令创建新的插件项目:
+
+```bash
+dify plugin init
+```
+
+### 选择模型插件模板
+
+脚手架工具内的所有模板均已提供完整的代码项目,选择 `LLM` 类型插件模板。
+
+
+
+#### 配置插件权限
+
+为该 LLM 插件配置以下权限:
+
+* Models
+* LLM
+* Storage
+
+
+
+#### 模型类型配置说明
+
+模型供应商支持以下两种模型的配置方式:
+
+* `predefined-model` **预定义模型**
+
+ 常见的大模型类型,只需要配置统一的供应商凭据即可使用模型供应商下的预定义模型。例如,`OpenAI` 模型供应商下提供 `gpt-3.5-turbo-0125` 和 `gpt-4o-2024-05-13` 等一系列预定义模型。详细开发说明请参考接入预定义模型。
+* `customizable-model` **自定义模型**
+
+ 需要手动新增每个模型的凭据配置,例如 `Xinference`,它同时支持 LLM 和 Text Embedding,但是每个模型都有唯一的 **model\_uid**,如果想要将两者同时接入,需要为每个模型配置一个 **model\_uid**。详细开发说明请参考接入自定义模型。
+
+两种配置方式**支持共存**,即存在供应商支持 `predefined-model` + `customizable-model` 或 `predefined-model` 等,即配置了供应商统一凭据可以使用预定义模型和从远程获取的模型,若新增了模型,则可以在此基础上额外使用自定义的模型。
+
+### 新增模型供应商
+
+新增一个模型供应商主要包含以下几个步骤:
+
+1. **创建模型供应商配置 YAML** **文件**
+
+ 在供应商目录下新增一个 YAML 文件,用于描述供应商的基本信息和参数配置。按照 ProviderSchema 的要求编写内容,确保与系统的规范保持一致。
+2. **编写模型供应商代码**
+
+ 创建供应商 class 代码,实现一个符合系统接口要求的 Python class 用于对接供应商的 API,完成核心功能实现。
+
+***
+
+以下是每个步骤的完整操作详情。
+
+#### 1. **创建模型供应商配置文件**
+
+Manifest 是 YAML 格式文件,声明了模型供应商基础信息、所支持的模型类型、配置方式、凭据规则。插件项目模板将在 `/providers` 路径下自动生成配置文件。
+
+以下是 `Anthropic` 模型配置文件 `anthropic.yaml` 的示例代码:
+
+```yaml
+provider: anthropic
+label:
+ en_US: Anthropic
+description:
+ en_US: Anthropic's powerful models, such as Claude 3.
+ zh_Hans: Anthropic 的强大模型,例如 Claude 3。
+icon_small:
+ en_US: icon_s_en.svg
+icon_large:
+ en_US: icon_l_en.svg
+background: "#F0F0EB"
+help:
+ title:
+ en_US: Get your API Key from Anthropic
+ zh_Hans: 从 Anthropic 获取 API Key
+ url:
+ en_US: https://console.anthropic.com/account/keys
+supported_model_types:
+ - llm
+configurate_methods:
+ - predefined-model
+provider_credential_schema:
+ credential_form_schemas:
+ - variable: anthropic_api_key
+ label:
+ en_US: API Key
+ type: secret-input
+ required: true
+ placeholder:
+ zh_Hans: 在此输入您的 API Key
+ en_US: Enter your API Key
+ - variable: anthropic_api_url
+ label:
+ en_US: API URL
+ type: text-input
+ required: false
+ placeholder:
+ zh_Hans: 在此输入您的 API URL
+ en_US: Enter your API URL
+models:
+ llm:
+ predefined:
+ - "models/llm/*.yaml"
+ position: "models/llm/_position.yaml"
+extra:
+ python:
+ provider_source: provider/anthropic.py
+ model_sources:
+ - "models/llm/llm.py"
+```
+
+如果接入的供应商提供自定义模型,比如`OpenAI`提供微调模型,需要添加`model_credential_schema` 字段。
+
+以下是 `OpenAI` 家族模型的示例代码:
+
+```yaml
+model_credential_schema:
+ model: # 微调模型名称
+ label:
+ en_US: Model Name
+ zh_Hans: 模型名称
+ placeholder:
+ en_US: Enter your model name
+ zh_Hans: 输入模型名称
+ credential_form_schemas:
+ - variable: openai_api_key
+ label:
+ en_US: API Key
+ type: secret-input
+ required: true
+ placeholder:
+ zh_Hans: 在此输入您的 API Key
+ en_US: Enter your API Key
+ - variable: openai_organization
+ label:
+ zh_Hans: 组织 ID
+ en_US: Organization
+ type: text-input
+ required: false
+ placeholder:
+ zh_Hans: 在此输入您的组织 ID
+ en_US: Enter your Organization ID
+ - variable: openai_api_base
+ label:
+ zh_Hans: API Base
+ en_US: API Base
+ type: text-input
+ required: false
+ placeholder:
+ zh_Hans: 在此输入您的 API Base
+ en_US: Enter your API Base
+```
+
+如需查看更多完整的模型供应商 YAML 规范,详情请参考[模型架构](/plugin_dev_zh/0412-model-schema.zh)文档。
+
+#### 2. **编写模型供应商代码**
+
+在 `/providers` 文件夹下创建一个同名的 python 文件,例如 `anthropic.py` 并实现一个 `class` ,继承 `__base.provider.Provider` 基类,例如 `AnthropicProvider`。
+
+以下是 `Anthropic` 示例代码:
+
+```python
+import logging
+from dify_plugin.entities.model import ModelType
+from dify_plugin.errors.model import CredentialsValidateFailedError
+from dify_plugin import ModelProvider
+
+logger = logging.getLogger(__name__)
+
+
+class AnthropicProvider(ModelProvider):
+ def validate_provider_credentials(self, credentials: dict) -> None:
+ """
+ Validate provider credentials
+
+ if validate failed, raise exception
+
+ :param credentials: provider credentials, credentials form defined in `provider_credential_schema`.
+ """
+ try:
+ model_instance = self.get_model_instance(ModelType.LLM)
+ model_instance.validate_credentials(model="claude-3-opus-20240229", credentials=credentials)
+ except CredentialsValidateFailedError as ex:
+ raise ex
+ except Exception as ex:
+ logger.exception(f"{self.get_provider_schema().provider} credentials validate failed")
+ raise ex
+```
+
+供应商需要继承 `__base.model_provider.ModelProvider` 基类,实现 `validate_provider_credentials` 供应商统一凭据校验方法即可。
+
+```python
+def validate_provider_credentials(self, credentials: dict) -> None:
+ """
+ Validate provider credentials
+ You can choose any validate_credentials method of model type or implement validate method by yourself,
+ such as: get model list api
+
+ if validate failed, raise exception
+
+ :param credentials: provider credentials, credentials form defined in `provider_credential_schema`.
+ """
+```
+
+当然也可以先预留 `validate_provider_credentials` 实现,在模型凭据校验方法实现后直接复用。
+
+#### **自定义模型供应商**
+
+对于其它类型模型供应商而言,请参考以下配置方法。
+
+对于像 `Xinference` 这样的自定义模型供应商,可以跳过完整实现的步骤。只需创建一个名为 `XinferenceProvider` 的空类,并在其中实现一个空的 `validate_provider_credentials` 方法。
+
+**具体说明:**
+
+• `XinferenceProvider` 是一个占位类,用于标识自定义模型供应商。
+
+• `validate_provider_credentials` 方法虽然不会被实际调用,但必须存在,这是因为其父类是抽象类,要求所有子类都实现这个方法。通过提供一个空实现,可以避免因未实现抽象方法而导致的实例化错误。
+
+```python
+class XinferenceProvider(Provider):
+ def validate_provider_credentials(self, credentials: dict) -> None:
+ pass
+```
+
+初始化模型供应商后,接下来需要接入供应商所提供的具体 llm 模型。详细说明请参考以下内容:
+
+* [模型设计规则](/plugin_dev_zh/0411-model-designing-rules.zh) - 了解接入预定义模型的规范
+* [模型架构](/plugin_dev_zh/0412-model-schema.zh) - 了解接入自定义模型的规范
+* [发布概览](/plugin_dev_zh/0321-release-overview.zh) - 学习插件发布流程
+
+## 参考资源
+
+- [快速接入一个新模型](/plugin_dev_zh/0211-getting-started-new-model.zh) - 如何为现有供应商添加新模型
+- [插件开发基本概念](/plugin_dev_zh/0111-getting-started-dify-plugin.zh) - 返回插件开发入门指南
+- [创建新模型提供者补充](/plugin_dev_zh/0222-creating-new-model-provider-extra.zh) - 了解更多高级配置
+- [一般规范定义](/plugin_dev_zh/0411-general-specifications.zh) - 了解插件清单文件的配置
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ 通过直接提交修改来帮助改进文档内容
+
+
+ 发现错误或有改进建议?请提交问题反馈
+
+
diff --git a/plugin_dev_zh/0222-tool-plugin.zh.mdx b/plugin_dev_zh/0222-tool-plugin.zh.mdx
new file mode 100644
index 00000000..72043ea8
--- /dev/null
+++ b/plugin_dev_zh/0222-tool-plugin.zh.mdx
@@ -0,0 +1,399 @@
+---
+dimensions:
+ type:
+ primary: implementation
+ detail: standard
+ level: intermediate
+standard_title: Tool Plugin
+language: zh
+title: Tool 插件
+description: 本文档详细介绍了如何开发Dify的工具插件,以Google Search为例实现了一个完整的工具插件开发流程。内容包括插件初始化、模板选择、模板选择、工具供应商配置文件定义、添加第三方服务凭证、工具功能代码实现、调试和打包发布等完整环节。
+---
+
+工具指的是能够被 Chatflow / Workflow / Agent 类型应用所调用的第三方服务,提供完整的 API 实现能力,用于增强 Dify 应用的能力。例如为应用添加在线搜索、图片生成等额外功能。
+
+
+
+在本文中,**"工具插件"** 指的是一个完整的项目,其中包含工具供应商文件、功能代码等结构。一个工具供应商内允许包含多个 Tools(可以理解为单个工具中提供的额外功能),结构如下:
+
+```
+- 工具供应商
+ - Tool A
+ - Tool B
+```
+
+
+
+本文将以 `Google Search` 为例,介绍如何快速开发一个工具插件。
+
+### 前置准备
+
+- Dify 插件脚手架工具
+- Python 环境,版本号 ≥ 3.12
+
+关于如何准备插件开发的脚手架工具,详细说明请参考[初始化开发工具](/plugin_dev_zh/0221-initialize-development-tools.zh)。如果你是初次开发插件,建议先阅读[Dify 插件开发:Hello World 指南](/plugin_dev_zh/0211-getting-started-dify-tool.zh)。
+
+### 创建新项目
+
+运行脚手架命令行工具,创建一个新的 dify 插件项目。
+
+```bash
+./dify-plugin-darwin-arm64 plugin init
+```
+
+如果你已将该二进制文件重命名为 `dify` 并拷贝至 `/usr/local/bin` 路径下,可以运行以下命令创建新的插件项目:
+
+```bash
+dify plugin init
+```
+
+> 下文将使用 `dify` 作为命令行示例。如遇到问题,请将 `dify` 命令替换为命令行工具的所在路径。
+
+### 选择插件类型和模板
+
+脚手架工具内的所有模板均已提供完整的代码项目。在本文实例中,选择 `Tool` 插件。
+
+> 如果你已熟悉插件开发,无需借助模板,可参考[通用规范](/plugin_dev_zh/0411-general-specifications.zh)指引完成不同类型的插件开发。
+
+
+
+#### 配置插件权限
+
+插件还需读取 Dify 平台的权限,为该示例插件授予以下权限:
+
+- Tools
+- Apps
+- 启用持久化存储 Storage,分配默认大小存储
+- 允许注册 Endpoint
+
+> 在终端内使用方向键选择权限,使用 "Tab" 按钮授予权限。
+
+勾选所有权限项后,轻点回车完成插件的创建。系统将自动生成插件项目代码。
+
+
+
+### 开发工具插件
+
+#### 1. 创建工具供应商文件
+
+工具供应商文件为 yaml 格式文件,可以理解为工具插件的基础配置入口,用于向工具提供必要的授权信息。
+
+前往插件模板项目中的 `/provider` 路径,将其中的 yaml 文件重命名为 `google.yaml`。该 `yaml` 文件将包含工具供应商的信息,包括供应商名称、图标、作者等详情。这部分信息将在安装插件时进行展示。
+
+**示例代码**
+
+```yaml
+identity: # 工具供应商的基本信息
+ author: Your-name # 作者
+ name: google # 名称,唯一,不允许和其他供应商重名
+ label: # 标签,用于前端展示
+ en_US: Google # 英文标签
+ zh_Hans: Google # 中文标签
+ description: # 描述,用于前端展示
+ en_US: Google # 英文描述
+ zh_Hans: Google # 中文描述
+ icon: icon.svg # 工具图标,需要放置在 _assets 文件夹下
+ tags: # 标签,用于前端展示
+ - search
+```
+
+确保该文件路径位于 `/tools` 目录,完整的路径如下:
+
+```yaml
+plugins:
+ tools:
+ - 'google.yaml'
+```
+
+其中 `google.yaml` 文件需要使用其在插件项目的绝对路径。在本例中,它位于项目根目录。YAML 文件中的 identity 字段解释如下:`identity` 包含了工具供应商的基本信息,包括作者、名称、标签、描述、图标等。
+
+- 图标需要属于附件资源,需要将其放置在项目根目录的 `_assets` 文件夹下。
+- 标签可以帮助用户通过分类快速找到插件,以下是目前所支持的所有标签。
+
+```python
+class ToolLabelEnum(Enum):
+ SEARCH = 'search'
+ IMAGE = 'image'
+ VIDEOS = 'videos'
+ WEATHER = 'weather'
+ FINANCE = 'finance'
+ DESIGN = 'design'
+ TRAVEL = 'travel'
+ SOCIAL = 'social'
+ NEWS = 'news'
+ MEDICAL = 'medical'
+ PRODUCTIVITY = 'productivity'
+ EDUCATION = 'education'
+ BUSINESS = 'business'
+ ENTERTAINMENT = 'entertainment'
+ UTILITIES = 'utilities'
+ OTHER = 'other'
+```
+
+#### **2. 补全第三方服务凭据**
+
+为了便于开发,选择采用第三方服务 `SerpApi` 所提供的 Google Search API 。 `SerpApi` 要求填写 API Key 进行使用,因此需要在 `yaml` 文件内添加 `credentials_for_provider` 字段。
+
+完整代码如下:
+
+```yaml
+identity:
+ author: Dify
+ name: google
+ label:
+ en_US: Google
+ zh_Hans: Google
+ pt_BR: Google
+ description:
+ en_US: Google
+ zh_Hans: GoogleSearch
+ pt_BR: Google
+ icon: icon.svg
+ tags:
+ - search
+credentials_for_provider: #添加 credentials_for_provider 字段
+ serpapi_api_key:
+ type: secret-input
+ required: true
+ label:
+ en_US: SerpApi API key
+ zh_Hans: SerpApi API key
+ placeholder:
+ en_US: Please input your SerpApi API key
+ zh_Hans: 请输入你的 SerpApi API key
+ help:
+ en_US: Get your SerpApi API key from SerpApi
+ zh_Hans: 从 SerpApi 获取您的 SerpApi API key
+ url: https://serpapi.com/manage-api-key
+tools:
+ - tools/google_search.yaml
+extra:
+ python:
+ source: google.py
+```
+
+- 其中 `credentials_for_provider` 的子级结构需要满足 [通用规范](/plugin_dev_zh/0411-general-specifications.zh) 的要求。
+- 需要指定该供应商包含了哪些工具。本示例仅包含了一个 `tools/google_search.yaml` 文件。
+- 作为供应商,除了定义其基础信息外,还需要实现一些它的代码逻辑,因此需要指定其实现逻辑,在本例子中,将功能的代码文件放在了 `google.py` 中,但是暂时不实现它,而是先编写 `google_search` 的代码。
+
+#### 3. 填写工具 yaml 文件
+
+一个工具插件下可以有多个工具功能,每个工具功能需要一个 `yaml` 文件进行描述,包含工具功能的基本信息、参数、输出等。
+
+仍以 `GoogleSearch` 工具为例,在 `/tools`文件夹内新建一个 `google_search.yaml` 文件。
+
+```yaml
+identity:
+ name: google_search
+ author: Dify
+ label:
+ en_US: GoogleSearch
+ zh_Hans: 谷歌搜索
+ pt_BR: GoogleSearch
+description:
+ human:
+ en_US: A tool for performing a Google SERP search and extracting snippets and webpages.Input should be a search query.
+ zh_Hans: 一个用于执行 Google SERP 搜索并提取片段和网页的工具。输入应该是一个搜索查询。
+ pt_BR: A tool for performing a Google SERP search and extracting snippets and webpages.Input should be a search query.
+ llm: A tool for performing a Google SERP search and extracting snippets and webpages.Input should be a search query.
+parameters:
+ - name: query
+ type: string
+ required: true
+ label:
+ en_US: Query string
+ zh_Hans: 查询语句
+ pt_BR: Query string
+ human_description:
+ en_US: used for searching
+ zh_Hans: 用于搜索网页内容
+ pt_BR: used for searching
+ llm_description: key words for searching
+ form: llm
+extra:
+ python:
+ source: tools/google_search.py
+```
+
+- `identity` 包含了工具的基本信息,包括名称、作者、标签、描述等。
+- `parameters` 参数列表
+ - `name` (必填)参数名称,唯一,不允许和其他参数重名。
+ - `type` (必填)参数类型,目前支持`string`、`number`、`boolean`、`select`、`secret-input` 五种类型,分别对应字符串、数字、布尔值、下拉框、加密输入框,对于敏感信息,请使用`secret-input`类型。
+ - `label`(必填)参数标签,用于前端展示。
+ - `form` (必填)表单类型,目前支持`llm`、`form`两种类型。
+ - 在 Agent 应用中,`llm` 表示该参数 LLM 自行推理,`form` 表示要使用该工具可提前设定的参数。
+ - 在 Workflow 应用中,`llm`和`form` 均需要前端填写,但 `llm` 的参数会做为工具节点的输入变量。
+ - `required` 是否必填
+ - 在 `llm` 模式下,如果参数为必填,则会要求 Agent 必须要推理出这个参数。
+ - 在 `form` 模式下,如果参数为必填,则会要求用户在对话开始前在前端填写这个参数。
+ - `options` 参数选项
+ - 在 `llm` 模式下,Dify 会将所有选项传递给 LLM,LLM 可以根据这些选项进行推理。
+ - 在 `form` 模式下,`type` 为 `select` 时,前端会展示这些选项。
+ - `default` 默认值。
+ - `min` 最小值,当参数类型为`number`时可以设定。
+ - `max` 最大值,当参数类型为`number`时可以设定。
+ - `human_description` 用于前端展示的介绍,支持多语言。
+ - `placeholder` 字段输入框的提示文字,在表单类型为`form`,参数类型为`string`、`number`、`secret-input`时,可以设定,支持多语言。
+ - `llm_description` 传递给 LLM 的介绍。为了使得 LLM 更好理解这个参数,请在这里写上关于这个参数尽可能详细的信息,以便 LLM 能够理解该参数。
+
+#### 4. 准备工具代码
+
+填写工具的配置信息以后,可以开始编写工具的功能代码,实现工具的逻辑目的。在`/tools`目录下创建`google_search.py`,内容如下:
+
+```python
+from collections.abc import Generator
+from typing import Any
+
+import requests
+
+from dify_plugin import Tool
+from dify_plugin.entities.tool import ToolInvokeMessage
+
+SERP_API_URL = "https://serpapi.com/search"
+
+class GoogleSearchTool(Tool):
+ def _parse_response(self, response: dict) -> dict:
+ result = {}
+ if "knowledge_graph" in response:
+ result["title"] = response["knowledge_graph"].get("title", "")
+ result["description"] = response["knowledge_graph"].get("description", "")
+ if "organic_results" in response:
+ result["organic_results"] = [
+ {
+ "title": item.get("title", ""),
+ "link": item.get("link", ""),
+ "snippet": item.get("snippet", ""),
+ }
+ for item in response["organic_results"]
+ ]
+ return result
+
+ def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage]:
+ params = {
+ "api_key": self.runtime.credentials["serpapi_api_key"],
+ "q": tool_parameters["query"],
+ "engine": "google",
+ "google_domain": "google.com",
+ "gl": "us",
+ "hl": "en",
+ }
+
+ response = requests.get(url=SERP_API_URL, params=params, timeout=5)
+ response.raise_for_status()
+ valuable_res = self._parse_response(response.json())
+
+ yield self.create_json_message(valuable_res)
+```
+
+该例子的含义为请求 `serpapi`,并使用 `self.create_json_message` 返回一串 `json` 的格式化数据,如果想了解更多的返回数据类型,可以参考[远程调试插件](/plugin_dev_zh/0411-remote-debug-a-plugin.zh)和[持久化存储 KV](/plugin_dev_zh/0411-persistent-storage-kv.zh)文档。
+
+#### 4. 完成工具供应商代码
+
+最后需要创建一个供应商的实现代码,用于实现凭据验证逻辑。如果凭据验证失败,将会抛出`ToolProviderCredentialValidationError`异常。验证成功后,将正确请求 `google_search` 工具服务。
+
+在 `/provider` 目录下创建 `google.py` 文件,代码的内容如下:
+
+```python
+from typing import Any
+
+from dify_plugin import ToolProvider
+from dify_plugin.errors.tool import ToolProviderCredentialValidationError
+from tools.google_search import GoogleSearchTool
+
+class GoogleProvider(ToolProvider):
+ def _validate_credentials(self, credentials: dict[str, Any]) -> None:
+ try:
+ for _ in GoogleSearchTool.from_credentials(credentials).invoke(
+ tool_parameters={"query": "test", "result_type": "link"},
+ ):
+ pass
+ except Exception as e:
+ raise ToolProviderCredentialValidationError(str(e))
+```
+
+### 调试插件
+
+插件开发完成后,接下来需要测试插件是否可以正常运行。Dify 提供便捷地远程调试方式,帮助你快速在测试环境中验证插件功能。
+
+前往["插件管理"](https://cloud.dify.ai/plugins)页获取远程服务器地址和调试 Key。
+
+
+
+回到插件项目,拷贝 `.env.example` 文件并重命名为 `.env`,将获取的远程服务器地址和调试 Key 等信息填入其中。
+
+`.env` 文件:
+
+```bash
+INSTALL_METHOD=remote
+REMOTE_INSTALL_HOST=remote
+REMOTE_INSTALL_PORT=5003
+REMOTE_INSTALL_KEY=****-****-****-****-****
+```
+
+运行 `python -m main` 命令启动插件。在插件页即可看到该插件已被安装至 Workspace 内,团队中的其他成员也可以访问该插件。
+
+
+
+### 打包插件(可选)
+
+确认插件能够正常运行后,可以通过以下命令行工具打包并命名插件。运行以后你可以在当前文件夹发现 `google.difypkg` 文件,该文件为最终的插件包。
+
+```bash
+# 将 ./google 替换为插件项目的实际路径
+
+dify plugin package ./google
+```
+
+恭喜,你已完成一个工具类型插件的完整开发、调试与打包过程!
+
+### 发布插件(可选)
+
+如果想要将插件发布至 Dify Marketplace,请确保你的插件遵循了[发布到 Dify 市场](/plugin_dev_zh/0322-release-to-dify-marketplace.zh)中的规范。审核通过后,代码将合并至主分支并自动上线至 [Dify Marketplace](https://marketplace.dify.ai/)。
+
+[发布概览](/plugin_dev_zh/0321-release-overview.zh)
+
+### 探索更多
+
+#### **快速开始:**
+
+- [开发 Extension 插件](/plugin_dev_zh/9231-extension-plugin.zh)
+- [开发 Model 插件](/plugin_dev_zh/0211-getting-started-new-model.zh)
+- [Bundle 插件:将多个插件打包](/plugin_dev_zh/9241-bundle.zh)
+
+#### **插件接口文档:**
+
+- [一般规范定义](/plugin_dev_zh/0411-general-specifications.zh) - Manifest 结构和工具规范
+- [端点](/plugin_dev_zh/0432-endpoint.zh) - Endpoint 详细定义
+- [反向调用](/plugin_dev_zh/9241-reverse-invocation.zh) - 反向调用 Dify 能力
+- [模型架构](/plugin_dev_zh/0412-model-schema.zh) - 模型
+- [Agent 插件](/plugin_dev_zh/9232-agent.zh) - 扩展 Agent 策略
+
+## 下一步学习
+
+- [远程调试插件](/plugin_dev_zh/0411-remote-debug-a-plugin.zh) - 了解更高级的调试技巧
+- [持久化存储](/plugin_dev_zh/0411-persistent-storage-kv.zh) - 学习如何在插件中使用数据存储
+- [Slack 机器人插件开发示例](/plugin_dev_zh/0432-develop-a-slack-bot-plugin.zh) - 查看更复杂的插件开发案例
+- [工具插件](/plugin_dev_zh/0411-tool.zh) - 探索工具插件的高级功能
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ 通过直接提交修改来帮助改进文档内容
+
+
+ 发现错误或有改进建议?请提交问题反馈
+
+
diff --git a/plugin_dev_zh/0312-contributor-covenant-code-of-conduct.zh.mdx b/plugin_dev_zh/0312-contributor-covenant-code-of-conduct.zh.mdx
new file mode 100644
index 00000000..ec15a6ef
--- /dev/null
+++ b/plugin_dev_zh/0312-contributor-covenant-code-of-conduct.zh.mdx
@@ -0,0 +1,85 @@
+---
+dimensions:
+ type:
+ primary: operational
+ detail: setup
+ level: intermediate
+standard_title: Contributor Covenant Code of Conduct
+language: zh
+title: 插件开发者准则
+description: 本文档提供了Dify插件开发者在提交Pull Request前需要遵循的准则,包括確保插件功能正常、文档完整、提供独特价值,以及符合数据隐私和安全规范。包含了说明文档要求、应避免重复插件的指南以及隐私信息收集声明要求。
+---
+
+### 提交 Pull Request (PR) 之前
+
+1. **确保插件功能正常且文档齐全**
+
+* 验证插件的功能是否正常运行。详细请参考[远程调试插件](/plugin_dev_zh/0411-remote-debug-a-plugin.zh)。
+* 提供全面的 **README 文件**,包括:
+ * 设置说明和使用指南。
+ * 插件用户需用的代码、API、凭据或其他信息,以便连接插件到服务。
+* 确保收集的用户信息仅用于连接服务和改进插件功能。
+* 根据[插件隐私数据保护指南](/plugin_dev_zh/0312-privacy-protection-guidelines.zh)准备隐私政策内容文件或在线文档 URL。
+
+2. **验证插件的贡献价值**
+
+* 确保插件为 Dify 用户提供了独特价值。
+* 插件应引入 Dify 或其他插件尚未提供的功能或服务。
+* 遵循社区标准:
+ * 内容非暴力,尊重全球用户群体。
+ * 符合集成服务的相关政策。
+* **如何检查是否已有类似插件?**
+ * 避免提交与现有插件或 PR 重复的功能,除非新插件具备以下特点:
+ * 引入新功能。
+ * 提供性能改进。
+ * **如何判断插件是否足够独特:**
+ * 如果插件仅在现有功能上做出小幅调整(如添加语言参数),建议直接扩展现有插件。
+ * 如果插件实现了重大功能变化(如优化批量处理或改进错误处理),可以提交为新插件。
+ * 不确定?请在提交 PR 中附上简要说明,解释为什么需要提交新插件。
+
+**示例:** 以Google 搜索插件为例,它接受单个输入查询并使用 Google 搜索 API 输出 Google 搜索结果列表。如果您提供具有类似底层实现的新 Google 搜索插件,但对输入进行了细微调整(例如添加新的语言参数),我们建议扩展现有插件。另一方面,如果您以新的方式实现了插件执行优化的批量搜索和错误处理能力,则可被视作单独的插件进行审核。
+
+3. **确保插件符合下列隐私数据规范**
+
+### 信息披露要求:
+
+* 要求开发者在提交应用/工具时,**必须**声明是否收集任何类型的用户个人数据。详细请参考[插件隐私数据保护指南](/plugin_dev_zh/0312-privacy-protection-guidelines.zh)。
+* 如果收集,需要**简单列出**收集的数据类型(例如:用户名、邮箱、设备ID、位置信息等),**无需过于详细**。
+* 开发者**必须**提供隐私政策链接,隐私政策只需要陈述收集了什么信息、怎么使用这些信息、以及哪些信息会和第三方披露,以及第三方相关的隐私政策链接。
+
+**审核重点:**
+
+* **形式审核:** 检查是否按要求声明了数据收集情况。
+* **高危数据排查:** 重点关注是否收集了敏感数据(例如:健康信息、财务信息、儿童个人信息等),如果收集了敏感数据,则需要**额外审核**其使用目的和安全性措施。
+* **恶意行为排查:** 检查是否存在明显恶意行为,例如未经用户同意私自收集数据、上传用户数据到未知服务器等。
+
+## 相关资源
+
+- [插件开发基本概念](/plugin_dev_zh/0111-getting-started-dify-plugin.zh) - 了解插件开发基础
+- [发布插件](/plugin_dev_zh/0321-release-overview.zh) - 插件发布流程概览
+- [插件隐私数据保护指南](/plugin_dev_zh/0312-privacy-protection-guidelines.zh) - 编写隐私政策指南
+- [发布至 Dify Marketplace](/plugin_dev_zh/0322-release-to-dify-marketplace.zh) - 在官方市场发布插件
+- [远程调试插件](/plugin_dev_zh/0411-remote-debug-a-plugin.zh) - 插件调试指南
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ 通过直接提交修改来帮助改进文档内容
+
+
+ 发现错误或有改进建议?请提交问题反馈
+
+
diff --git a/plugin_dev_zh/0312-privacy-protection-guidelines.zh.mdx b/plugin_dev_zh/0312-privacy-protection-guidelines.zh.mdx
new file mode 100644
index 00000000..13176d4f
--- /dev/null
+++ b/plugin_dev_zh/0312-privacy-protection-guidelines.zh.mdx
@@ -0,0 +1,113 @@
+---
+dimensions:
+ type:
+ primary: operational
+ detail: setup
+ level: intermediate
+standard_title: Privacy Protection Guidelines
+language: zh
+title: 插件隐私政策准则
+description: 本文档描述了开发者向Dify Marketplace提交插件时如何撰写隐私政策的准则。内容包括如何确认和列出所收集的个人数据类型(直接识别信息、间接识别信息、组合信息)、如何填写插件隐私政策、如何在Manifest文件中引入隐私政策声明,以及相关常见问题的解答。
+---
+
+向 Dify Marketplace 提交插件上架申请时,您需要公开说明如何处理用户数据。以下是关于填写插件相关隐私问题和用户数据处理的指南。如果你还没有开发插件,可以参考[插件开发入门指南](/plugin_dev_zh/0211-getting-started-dify-tool.zh)。
+
+插件隐私政策声明内容围绕以下问题展开:
+
+**您的插件是否收集和使用用户个人数据?** 如有,请整理并列出具体类型。
+
+> 个人数据指可识别特定个人的任何信息,包括单独或与其他信息结合后可用于识别、联系或定位个人的信息。
+
+#### **1. 列出所收集的数据类型**
+
+**类型一:直接识别信息**
+
+* 姓名(全名、名、姓)
+* 邮箱
+* 手机号
+* 住址
+* 身份证件号(身份证、护照、驾照等)
+
+**类型二:间接识别信息**
+
+* 设备识别码(IMEI、MAC 地址、设备 ID)
+* IP 地址
+* 位置信息(GPS 坐标、城市、地区)
+* 在线标识(cookies、广告 ID)
+* 用户名
+* 头像
+* 生物特征(指纹、人脸识别)
+* 浏览记录
+* 购物记录
+* 健康数据
+* 财务信息
+
+**类型三:被用于识别个人的组合信息**
+
+* 年龄
+* 性别
+* 职业
+* 兴趣爱好
+
+即使您的插件本身不收集个人信息,也请确认插件使用的第三方服务是否涉及数据收集或处理。作为开发者,您需要公开所有数据收集活动,包括第三方服务的数据处理。请务必阅读第三方服务的隐私政策,确保在提交时声明插件涉及的所有数据收集情况。
+
+例如,当你正在开发的插件涉及 Slack 服务时,请在插件的隐私政策声明文件中引用 [Slack 的隐私政策](https://slack.com/trust/privacy/privacy-policy)并声明数据收集情况。
+
+#### 2. 填写最新版本的插件隐私政策
+
+**插件隐私政策**必须包含:
+
+* 收集的数据类型
+* 数据用途
+* 是否与第三方共享数据(如有,请列出第三方服务的名称及其隐私政策链接)
+* 如果不熟悉隐私政策写法,可以参考由 Dify 官方维护插件内的隐私政策
+
+#### **3. 在插件 Manifest 文件内引入隐私政策声明**
+
+详细字段的填写说明请参考[通过清单文件定义插件信息](/plugin_dev_zh/0411-plugin-info-by-manifest.zh)。
+
+### **常见问题**
+
+1. **什么是"收集和使用"用户个人数据?有哪些常见案例?**
+
+"收集和使用"指对用户数据进行收集、传输、使用或共享。常见情况包括:
+
+* 通过表单收集个人信息;
+* 使用登录功能(含第三方认证);
+* 收集可能包含个人信息的输入或资源;
+* 分析用户行为、互动和使用情况;
+* 存储通讯内容,如消息、聊天记录、邮箱;
+* 访问关联社交媒体的用户资料;
+* 收集健康数据,如运动、心率、医疗信息;
+* 保存搜索记录或浏览行为;
+* 处理财务信息,如银行资料、信用分、交易记录。
+
+## 相关资源
+
+- [发布插件](/plugin_dev_zh/0321-release-overview.zh) - 了解插件发布流程
+- [发布至 Dify Marketplace](/plugin_dev_zh/0322-release-to-dify-marketplace.zh) - 学习如何往官方市场提交插件
+- [插件开发者准则](/plugin_dev_zh/0312-contributor-covenant-code-of-conduct.zh) - 了解插件提交规范
+- [通过清单文件定义插件信息](/plugin_dev_zh/0411-plugin-info-by-manifest.zh) - 插件元数据配置
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ 通过直接提交修改来帮助改进文档内容
+
+
+ 发现错误或有改进建议?请提交问题反馈
+
+
diff --git a/plugin_dev_zh/0321-release-overview.zh.mdx b/plugin_dev_zh/0321-release-overview.zh.mdx
new file mode 100644
index 00000000..5e6d1055
--- /dev/null
+++ b/plugin_dev_zh/0321-release-overview.zh.mdx
@@ -0,0 +1,107 @@
+---
+dimensions:
+ type:
+ primary: operational
+ detail: deployment
+ level: beginner
+standard_title: Release Overview
+language: zh
+title: 发布插件
+description: 本文档介绍了Dify插件的三种发布方式:官方Marketplace、开源GitHub仓库和本地插件文件包。详细说明了每种发布方式的特点、发布流程以及适用场景,并提供了具体的发布建议以满足不同开发者的需求。
+---
+
+### 发布方式
+
+为了满足不同开发者的发布需求,Dify 提供了以下三种插件发布方式。在发布前,请确保您已完成插件的开发和测试,并阅读[插件开发基本概念](/plugin_dev_zh/0111-getting-started-dify-plugin.zh)和[插件开发者准则](/plugin_dev_zh/0312-contributor-covenant-code-of-conduct.zh)。
+
+#### **1. Marketplace**
+
+**简介**:Dify 官方提供的插件市场,用户可以在此浏览、搜索并一键安装各类插件。
+
+**特点**:
+
+* 插件经审核后上线,**安全可靠**。
+* 可直接安装至个人或团队的 **Workspace** 中。
+
+**发布流程**:
+
+* 将插件项目提交至 **Dify Marketplace** [代码仓库](https://github.com/langgenius/dify-plugins)。
+* 经过官方审核后,插件将在市场内公开发布,供其他用户安装使用。
+
+详细说明请参考:
+
+[发布至 Dify Marketplace](/plugin_dev_zh/0322-release-to-dify-marketplace.zh)
+
+#### 2. **GitHub 仓库**
+
+**简介**:将插件开源或托管在 **GitHub** 上,方便他人查看、下载和安装。
+
+**特点**:
+
+* 便于**版本管理**和**开源共享**。
+* 用户可通过插件链接直接安装,无需平台审核。
+
+**发布流程**:
+
+* 将插件代码推送至 GitHub 仓库。
+* 分享仓库链接,用户可通过链接将插件集成至 **Dify Workspace**。
+
+详细说明请参考:
+
+[发布至个人 GitHub 仓库](/plugin_dev_zh/0322-release-to-individual-github-repo.zh)
+
+#### 3. 插件文件包(本地安装)
+
+**简介**:将插件打包成本地文件(如 `.difypkg` 格式),通过文件分享的方式供他人安装。
+
+**特点**:
+
+* 不依赖在线平台,**快速灵活**地分享插件。
+* 适用于**私有插件**或**内部测试**。
+
+**发布流程**:
+
+* 将插件项目打包为本地文件。
+* 在 Dify 插件页面点击**上传插件**,选择本地文件安装插件。
+
+你可以将插件项目打包为一个本地文件并分享给他人,在插件页上传文件后即可将插件安装至 Dify Workspace 内。
+
+详细说明请参考:
+
+[打包为本地文件与分享](/plugin_dev_zh/0322-release-by-file.zh)
+
+### **发布建议**
+
+* **想要推广插件** → **推荐使用 Marketplace**,通过官方审核保障插件质量,提升曝光度。
+* **开源共享项目** → **推荐使用 GitHub**,方便版本管理与社区协作。
+* **快速分发或内部测试** → **推荐使用插件文件**,简单高效地安装和分享。
+
+## 相关资源
+
+- [插件开发基本概念](/plugin_dev_zh/0111-getting-started-dify-plugin.zh) - 全面了解Dify插件开发
+- [插件开发者准则](/plugin_dev_zh/0312-contributor-covenant-code-of-conduct.zh) - 了解插件提交的规范
+- [插件隐私数据保护指南](/plugin_dev_zh/0312-privacy-protection-guidelines.zh) - 了解隐私政策编写要求
+- [一般规范定义](/plugin_dev_zh/0411-general-specifications.zh) - 了解插件清单文件的配置
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ 通过直接提交修改来帮助改进文档内容
+
+
+ 发现错误或有改进建议?请提交问题反馈
+
+
diff --git a/plugin_dev_zh/0322-release-by-file.zh.mdx b/plugin_dev_zh/0322-release-by-file.zh.mdx
new file mode 100644
index 00000000..43f607d3
--- /dev/null
+++ b/plugin_dev_zh/0322-release-by-file.zh.mdx
@@ -0,0 +1,86 @@
+---
+dimensions:
+ type:
+ primary: operational
+ detail: deployment
+ level: intermediate
+standard_title: Release by File
+language: zh
+title: 打包为本地文件与分享
+description: 本文档介绍了如何将Dify插件项目打包为本地文件并分享给他人的详细步骤。内容包括插件打包前的准备工作、使用Dify插件开发工具执行打包命令、生成的.difypkg文件的安装方式以及如何分享插件文件给其他用户。
+---
+
+完成插件开发后,你可以将插件项目打包为一个本地文件并分享给他人,通过插件文件后即可安装至 Dify Workspace 内。如果您还没有开发插件,可以参考[插件开发入门指南](/plugin_dev_zh/0211-getting-started-dify-tool.zh)。
+
+* **特点**:
+ * 不依赖在线平台,**快速灵活**地分享插件。
+ * 适用于**私有插件**或**内部测试**。
+* **发布流程**:
+ * 将插件项目打包为本地文件。
+ * 在 Dify 插件页面上传文件安装插件。
+
+本文将介绍如何将插件项目打包为本地文件,以及如何使用本地文件安装插件。
+
+### 前置准备
+
+* **Dify 插件开发工具**,详细说明请参考[初始化开发工具](/plugin_dev_zh/0221-initialize-development-tools.zh)。
+
+配置完成后,在终端输入 `dify version` 命令,检查是否输出版本号信息以确认已安装必要的开发工具。
+
+### 打包插件
+
+> 打包插件前,请确保插件的 `manifest.yaml` 文件和 `/provider` 路径下的 `.yaml` 文件中的 `author` 字段与 GitHub ID 保持一致。关于清单文件的详细信息,请参考[一般规范定义](/plugin_dev_zh/0411-general-specifications.zh)。
+
+插件项目开发完成后,请确保已完成[远程调试测试](/plugin_dev_zh/0411-remote-debug-a-plugin.zh)。需前往插件项目的上一级目录,运行以下插件打包命令:
+
+```bash
+dify plugin package ./your_plugin_project
+```
+
+运行命令后将在当前路径下生成以 `.difypkg` 后缀结尾的文件。
+
+
+
+### 安装插件
+
+访问 Dify 插件管理页,轻点右上角的**安装插件** → **通过本地文件**安装,或将插件文件拖拽至页面空白处安装插件。
+
+
+
+### 发布插件
+
+你可以将插件文件分享给他人,或上传至互联网供他人下载。如果你希望更广泛地分享你的插件,可以考虑:
+
+1. [发布至个人GitHub仓库](/plugin_dev_zh/0322-release-to-individual-github-repo.zh) - 通过GitHub分享插件
+2. [发布至Dify Marketplace](/plugin_dev_zh/0322-release-to-dify-marketplace.zh) - 在官方市场发布插件
+
+## 相关资源
+
+- [发布插件](/plugin_dev_zh/0321-release-overview.zh) - 了解各种发布方式
+- [初始化开发工具](/plugin_dev_zh/0221-initialize-development-tools.zh) - 配置插件开发环境
+- [远程调试插件](/plugin_dev_zh/0411-remote-debug-a-plugin.zh) - 学习插件调试方法
+- [一般规范定义](/plugin_dev_zh/0411-general-specifications.zh) - 定义插件元数据
+- [插件开发入门指南](/plugin_dev_zh/0211-getting-started-dify-tool.zh) - 从零开始开发插件
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ 通过直接提交修改来帮助改进文档内容
+
+
+ 发现错误或有改进建议?请提交问题反馈
+
+
diff --git a/plugin_dev_zh/0322-release-to-dify-marketplace.zh.mdx b/plugin_dev_zh/0322-release-to-dify-marketplace.zh.mdx
new file mode 100644
index 00000000..c7abf231
--- /dev/null
+++ b/plugin_dev_zh/0322-release-to-dify-marketplace.zh.mdx
@@ -0,0 +1,124 @@
+---
+dimensions:
+ type:
+ primary: operational
+ detail: deployment
+ level: intermediate
+standard_title: Release to Dify Marketplace
+language: zh
+title: 发布至 Dify Marketplace
+description: 本指引详细介绍了如何将插件发布到Dify Marketplace的完整流程,包括提交PR、审核流程、发布后维护等关键步骤和注意事项。
+---
+
+Dify Marketplace 欢迎来自合作伙伴和社区开发者的插件上架申请,您的贡献将进一步丰富 Dify 插件的可能性。本指引将提供清晰的发布流程和最佳实践建议,让您的插件能够顺利发布,并为社区带来价值。如果您还没有开发插件,可以参考[插件开发入门指南](/plugin_dev_zh/0211-getting-started-dify-tool.zh)。
+
+请按照以下步骤,在 [GitHub 代码仓库](https://github.com/langgenius/dify-plugins)提交您的插件 Pull Request(PR)并接受审核,通过后插件将正式上线至 Dify Marketplace。
+
+### 插件的发布流程
+
+将插件发布至 Dify Marketplace 包含以下步骤:
+
+1. 根据[插件开发者准则](/plugin_dev_zh/0312-contributor-covenant-code-of-conduct.zh)完成插件的开发和测试;
+2. 根据[插件隐私数据保护指南](/plugin_dev_zh/0312-privacy-protection-guidelines.zh)撰写该插件隐私政策,并将该隐私政策的文件路径或 URL 写入插件[一般规范定义](/plugin_dev_zh/0411-general-specifications.zh);
+3. 完成插件打包;
+4. Fork [Dify Plugins](https://github.com/langgenius/dify-plugins) 代码仓库;
+5. 创建 Organization 目录,在 Organization 目录下创建插件名目录,将插件的代码和 pkg 文件上传至对应的插件名目录下;
+6. 遵循 GitHub 中的 PR Template 内容格式提交 Pull Request (PR) ,等待审核;
+7. 审核通过后,插件代码将合并至 Main 分支,插件自动上架至 [Dify Marketplace](https://marketplace.dify.ai/)。
+
+插件提交、审核和上架流程图:
+
+
+
+> **Note**: 上图中的 Contributor Agreement 指的是[插件开发者准则](/plugin_dev_zh/0312-contributor-covenant-code-of-conduct.zh)。
+
+***
+
+### Pull Request (PR) 审核期间
+
+积极回应审查人员的提问和反馈:
+
+* **14 天内**未解决的 PR 评论将被标记为过时(可重新开启)。
+* **30 天内**未解决的 PR 评论将被关闭(不可重新开启,需要创建新 PR)。
+
+***
+
+### **Pull Request (PR) 审核通过后**
+
+**1. 持续维护**
+
+* 处理用户报告的问题和功能请求。
+* 在发生重大 API 变更时迁移插件:
+ * Dify 将提前发布变更通知和迁移说明。
+ * Dify 工程师可提供迁移支持。
+
+**2. Marketplace 公开 Beta 测试阶段的限制**
+
+* 避免对现有插件引入破坏性更改。
+
+***
+
+### 审核流程
+
+**1. 审核顺序**
+
+* 按照 **先到先审** 的顺序处理 PR。审核将在 1 周内开始。如有延迟,审查人员将通过评论通知 PR 作者。
+
+**2. 审核重点**
+
+* 检查插件名称、描述和设置说明是否清晰且具有指导性。
+* 检查插件的 [一般规范定义](/plugin_dev_zh/0411-general-specifications.zh)是否符合格式规范,并包含有效的作者联系信息。
+
+3. **插件的功能性和相关性**
+
+* 根据[插件开发指南](/plugin_dev_zh/0111-getting-started-dify-plugin.zh)测试插件。
+* 确保插件在 Dify 生态系统中的用途合理。
+
+[Dify.AI](https://dify.ai/) 保留接受或拒绝插件提交的权利。
+
+***
+
+### 常见问题
+
+1. **如何判断插件是否独特?**
+
+示例:一个 Google 搜索插件仅增加了多语言版本,应被视为现有插件的优化。但如果插件实现了显著的功能改进(如优化批量处理或错误处理),则可以作为新插件提交。
+
+2. **如果我的 PR 被标记为过时或关闭怎么办?**
+
+被标记为过时的 PR 可以在解决反馈后重新开启。被关闭的 PR(超过 30 天)需要重新创建一个新 PR。
+
+3. **Beta 测试阶段可以更新插件吗?**
+
+可以,但应避免引入破坏性更改。
+
+## 相关资源
+
+- [发布插件](/plugin_dev_zh/0321-release-overview.zh) - 了解各种发布方式
+- [插件开发者准则](/plugin_dev_zh/0312-contributor-covenant-code-of-conduct.zh) - 插件提交规范
+- [插件隐私数据保护指南](/plugin_dev_zh/0312-privacy-protection-guidelines.zh) - 隐私政策编写要求
+- [打包为本地文件与分享](/plugin_dev_zh/0322-release-by-file.zh) - 插件打包方法
+- [一般规范定义](/plugin_dev_zh/0411-general-specifications.zh) - 插件元数据定义
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ 通过直接提交修改来帮助改进文档内容
+
+
+ 发现错误或有改进建议?请提交问题反馈
+
+
diff --git a/plugin_dev_zh/0322-release-to-individual-github-repo.zh.mdx b/plugin_dev_zh/0322-release-to-individual-github-repo.zh.mdx
new file mode 100644
index 00000000..7c053683
--- /dev/null
+++ b/plugin_dev_zh/0322-release-to-individual-github-repo.zh.mdx
@@ -0,0 +1,131 @@
+---
+dimensions:
+ type:
+ primary: operational
+ detail: deployment
+ level: intermediate
+standard_title: Release to Individual GitHub Repo
+language: zh
+title: 发布至个人 GitHub 仓库
+description: 本文档详细介绍了如何将Dify插件发布到个人的GitHub仓库中,包括准备工作、初始化本地插件仓库、连接远程仓库、上传插件文件、打包插件代码以及如何通过GitHub安装插件的完整流程。该方式允许开发者完全管理自己的插件代码和更新。
+---
+
+支持通过 GitHub 仓库链接安装插件。插件开发完成后,你可以选择将插件发布至公开 GitHub 仓库供其他人下载和使用。如果您还没有开发插件,可以参考[插件开发入门指南](/plugin_dev_zh/0211-getting-started-dify-tool.zh)。
+
+该方式具备以下优势:
+
+• **个人管理**:你可以完全控制插件的代码和更新
+
+• **快速分享**:通过 GitHub 链接即可轻松分享给其他用户或团队成员,便于测试和使用
+
+• **协作与反馈**:将插件开源后,有可能会吸引 GitHub 上潜在的协作者,帮助您快速改进插件
+
+本文将指导你如何将插件发布到 GitHub 仓库。
+
+### 准备工作
+
+首先要确认您已开发并测试了插件,并阅读了[插件开发基本概念](/plugin_dev_zh/0111-getting-started-dify-plugin.zh)和[插件开发者准则](/plugin_dev_zh/0312-contributor-covenant-code-of-conduct.zh)。在发布插件前,请确保您本地已安装以下工具:
+
+* GitHub 账号
+* 创建一个新的公开 GitHub 仓库
+* 本地已安装 Git 工具
+
+关于 GitHub 的基础知识,请参考 [GitHub 文档](https://docs.github.com/en/repositories/creating-and-managing-repositories/creating-a-new-repository)。
+
+### 1. 完善插件项目
+
+将上传至公开的 GitHub 意味着你将公开插件。请确保你已完成插件的调试和验证,并已完善插件的 `README.md` 文件说明。
+
+建议说明文件包含以下内容:
+
+* 插件的简介和功能描述
+* 安装和配置步骤
+* 使用示例
+* 联系方式或贡献指南
+
+### 2. 初始化本地插件仓库
+
+将插件公开上传至 GitHub 之前,请确保已完成插件的调试和验证工作,详细请参考[远程调试插件](/plugin_dev_zh/0411-remote-debug-a-plugin.zh)。在终端中导航到插件项目文件夹,并运行以下命令:
+
+```bash
+git init
+git add .
+git commit -m "Initial commit: Add plugin files"
+```
+
+如果你是第一次使用 Git,可能还需要配置 Git 用户名和邮箱:
+
+```bash
+git config --global user.name "Your Name"
+git config --global user.email "your.email@example.com"
+```
+
+### 3. 连接远程仓库
+
+使用以下命令,将本地仓库连接到 GitHub 仓库:
+
+```bash
+git remote add origin https://github.com//.git
+```
+
+### 4. 上传插件文件
+
+> 打包插件前,请确保插件的 `manifest.yaml` 文件和 `/provider` 路径下的 `.yaml` 文件中的 author 字段与 GitHub ID 保持一致。关于清单文件的详细规范,请参考[通过清单文件定义插件信息](/plugin_dev_zh/0411-plugin-info-by-manifest.zh)。
+
+将插件项目推送到 GitHub 仓库:
+
+```bash
+git branch -M main
+git push -u origin main
+```
+
+上传代码时建议附上标签,以便后续打包代码。
+
+```bash
+git tag -a v0.0.1 -m "Release version 0.0.1"
+
+git push origin v0.0.1
+```
+
+### 5. 打包插件代码
+
+前往 GitHub 代码仓库的 Releases 页,创建一个新的版本发布。发布版本时需上传插件文件。关于如何打包插件文件,详细说明请阅读[打包为本地文件与分享](/plugin_dev_zh/0322-release-by-file.zh)。
+
+
+
+### 通过 GitHub 安装插件
+
+其他人可以通过 GitHub 仓库地址安装该插件。访问 Dify 平台的插件管理页,选择通过 GitHub 安装插件,输入仓库地址后,选择版本号和包文件完成安装。
+
+
+
+## 相关资源
+
+- [发布插件](/plugin_dev_zh/0321-release-overview.zh) - 了解各种发布方式
+- [打包为本地文件与分享](/plugin_dev_zh/0322-release-by-file.zh) - 插件打包方法
+- [通过清单文件定义插件信息](/plugin_dev_zh/0411-plugin-info-by-manifest.zh) - 定义插件元数据
+- [插件开发者准则](/plugin_dev_zh/0312-contributor-covenant-code-of-conduct.zh) - 了解插件开发规范
+- [远程调试插件](/plugin_dev_zh/0411-remote-debug-a-plugin.zh) - 学习插件调试方法
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ 通过直接提交修改来帮助改进文档内容
+
+
+ 发现错误或有改进建议?请提交问题反馈
+
+
diff --git a/plugin_dev_zh/0331-faq.zh.mdx b/plugin_dev_zh/0331-faq.zh.mdx
new file mode 100644
index 00000000..44fa1261
--- /dev/null
+++ b/plugin_dev_zh/0331-faq.zh.mdx
@@ -0,0 +1,59 @@
+---
+dimensions:
+ type:
+ primary: operational
+ detail: maintenance
+ level: beginner
+todo: 开发者(Developer / Contributor)应该充分测试后才上架, debug 不该是用户(Dify User / Consumer) 该做的事.
+standard_title: Faq
+language: zh
+title: 常见问题
+description: Author Allen 本文档回答了Dify插件开发和安装过程中的常见问题,包括插件上传失败的解决方法(修改author字段)以及如何处理插件安装过程中的验证异常(设置FORCE_VERIFYING_SIGNATURE环境变量)。
+---
+
+## 安装插件时提示上传失败如何处理?
+
+**错误详情**:出现 `PluginDaemonBadRequestError: plugin_unique_identifier is not valid` 报错提示。
+
+**解决办法**:将插件项目下的 `manifest.yaml` 文件和 `/provider` 路径下的 `.yaml` 文件中的 `author` 字段修改为 GitHub ID。
+
+重新运行插件打包命令并安装新的插件包。
+
+## 装插件时遇到异常应如何处理安?
+
+**问题描述**:安装插件时遇到异常信息:`plugin verification has been enabled, and the plugin you want to install has a bad signature`,应该如何处理?
+
+**解决办法**:在 `/docker/.env` 配置文件的末尾添加 `FORCE_VERIFYING_SIGNATURE=false` 字段,运行以下命令重启 Dify 服务:
+
+```bash
+cd docker
+docker compose down
+docker compose up -d
+```
+
+添加该字段后,Dify 平台将允许安装所有未在 Dify Marketplace 上架(审核)的插件,可能存在安全隐患。
+
+建议在测试 / 沙箱环境内安装插件,确认安全后再安装至生产环境。
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ 通过直接提交修改来帮助改进文档内容
+
+
+ 发现错误或有改进建议?请提交问题反馈
+
+
diff --git a/plugin_dev_zh/0411-general-specifications.zh.mdx b/plugin_dev_zh/0411-general-specifications.zh.mdx
new file mode 100644
index 00000000..26163c97
--- /dev/null
+++ b/plugin_dev_zh/0411-general-specifications.zh.mdx
@@ -0,0 +1,143 @@
+---
+dimensions:
+ type:
+ primary: reference
+ detail: core
+ level: beginner
+standard_title: General Specifications
+language: zh
+title: 通用规范定义
+description: 本文档详细介绍了Dify插件开发中的通用结构和规范,包括路径规范、国际化对象(I18nObject)、供应商表单结构(ProviderConfig)、模型配置(ModelConfig)、节点响应(NodeResponse)和工具选择器(ToolSelector)等重要数据结构的定义和用途。
+---
+
+本文将简要介绍插件开发中常见的结构。在开发过程中,强烈建议配合[插件开发基本概念](/plugin_dev_zh/0111-getting-started-dify-plugin.zh)和[开发者速查表](/plugin_dev_zh/0131-cheatsheet.zh)一起阅读,以便更好地理解整体架构。
+
+### 路径规范
+
+在 Manifest 或任意 yaml 文件中填写文件路径时,根据不同的类型的文件,遵循下面两条规范:
+
+* 如果目标文件是一个图片或视频等多媒体文件时,例如填写插件的 `icon` ,你应该将这些文件放置于插件根目录下的 `_assets` 文件夹中。
+* 如果目标文件是一个普通文本文件,如 `.py` `.yaml` 等代码文件,你应该填写该文件在插件项目内的绝对路径。
+
+### 通用结构
+
+在定义插件时,有一些数据结构是可以在工具、模型、Endpoint 之间共用的,这里定义了这些共用结构。
+
+#### I18nObject
+
+`I18nObject` 是一个符合 [IETF BCP 47](https://tools.ietf.org/html/bcp47) 标准的国际化结构,目前支持的四种语言为
+
+* en\_US
+* zh\_Hans
+* ja\_Jp
+* pt\_BR
+
+#### ProviderConfig
+
+`ProviderConfig` 为一个通用的供应商表单结构,适用于 `Tool`与`Endpoint`
+
+* `name`(string):表单项名称
+* `label`([I18nObject](#i18nobject), requierd):遵循 [IETF BCP 47](https://tools.ietf.org/html/bcp47)
+* `type`([provider\_config\_type](#providerconfigtype-string), requierd):表单类型
+* `scope`([provider\_config\_scope](#providerconfigscope-string)):可选项范围,根据`type`变动
+* `required`(bool):不能为空
+* `default`(any):默认值,仅支持基础类型 `float` `int` `string`
+* `options`(list\[[provider\_config\_option](#providerconfigoption-object)]):可选项,仅当 type 为 `select` 时使用
+* `helper`(object):帮助文档链接的 label,遵循 [IETF BCP 47](https://tools.ietf.org/html/bcp47)
+* `url` (string):帮助文档链接
+* `placeholder`(object):遵循 [IETF BCP 47](https://tools.ietf.org/html/bcp47)
+
+#### ProviderConfigOption(object)
+
+* `value`(string, required):值
+* `label`(object, required):遵循 [IETF BCP 47](https://tools.ietf.org/html/bcp47)
+
+#### ProviderConfigType(string)
+
+* `secret-input` (string):配置信息将被加密
+* `text-input`(string):普通文本
+* `select`(string):下拉框
+* `boolean`(bool):开关
+* `model-selector`(object):模型配置信息,包含供应商名称、模型名称、模型参数等
+* `app-selector`(object):app id
+* `tool-selector`(object):工具配置信息,包含工具供应商、名称、参数等
+* `dataset-selector`(string):TBD
+
+#### ProviderConfigScope(string)
+
+* 当 `type` 为 `model-selector` 时
+ * `all`
+ * `llm`
+ * `text-embedding`
+ * `rerank`
+ * `tts`
+ * `speech2text`
+ * `moderation`
+ * `vision`
+* 当 `type` 为 `app-selector` 时
+ * `all`
+ * `chat`
+ * `workflow`
+ * `completion`
+* 当`type` 为 `tool-selector` 时
+ * `all`
+ * `plugin`
+ * `api`
+ * `workflow`
+
+#### ModelConfig
+
+* `provider` (string): 包含 plugin\_id 的模型供应商名称,形如 `langgenius/openai/openai`。
+* `model` (string): 具体的模型名称。
+* `model_type` (enum): 模型类型的枚举,可以参考[模型设计规则](/plugin_dev_zh/0411-model-designing-rules#modeltype.zh)文档。
+
+#### NodeResponse
+
+* `inputs` (dict): 最终输入到节点中的变量。
+* `outputs` (dict): 节点的输出结果。
+* `process_data` (dict): 节点运行过程中产生的数据。
+
+#### ToolSelector
+
+* `provider_id` (string): 工具供应商名称
+* `tool_name` (string): 工具名称
+* `tool_description` (string): 工具描述
+* `tool_configuration` (dict\[str, Any]): 工具的配置信息
+* `tool_parameters` (dict\[str, dict]): 需要 LLM 推理的参数
+ * `name` (string): 参数名称
+ * `type` (string): 参数类型
+ * `required` (bool): 是否必填
+ * `description` (string): 参数描述
+ * `default` (any): 默认
+ * `options`(list\[string]): 可选项
+
+## 相关资源
+
+- [插件开发基本概念](/plugin_dev_zh/0111-getting-started-dify-plugin.zh) - 全面了解Dify插件开发
+- [开发者速查表](/plugin_dev_zh/0131-cheatsheet.zh) - 插件开发常用命令和概念速查
+
+- [工具插件开发详情](/plugin_dev_zh/0222-tool-plugin.zh) - 详细了解工具插件开发流程
+- [模型设计规则](/plugin_dev_zh/0411-model-designing-rules.zh) - 了解模型配置的规范
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ 通过直接提交修改来帮助改进文档内容
+
+
+ 发现错误或有改进建议?请提交问题反馈
+
+
diff --git a/plugin_dev_zh/0411-model-designing-rules.zh.mdx b/plugin_dev_zh/0411-model-designing-rules.zh.mdx
new file mode 100644
index 00000000..4e7ab2b1
--- /dev/null
+++ b/plugin_dev_zh/0411-model-designing-rules.zh.mdx
@@ -0,0 +1,237 @@
+---
+dimensions:
+ type:
+ primary: reference
+ detail: core
+ level: beginner
+standard_title: Model Designing Rules
+language: zh
+title: 模型设计规则
+description: 本文档详细定义了Dify模型插件开发的核心概念和结构,包括模型供应商(Provider)、AI模型实体(AIModelEntity)、模型类型(ModelType)、配置方法(ConfigurateMethod)、模型特性(ModelFeature)、参数规则(ParameterRule)、价格配置(PriceConfig)及各种凭证模式的详细数据结构规范。
+---
+
+* 模型供应商规则基于 [Provider](#provider) 实体。
+* 模型规则基于 [AIModelEntity](#aimodelentity) 实体。
+
+> 以下所有实体均基于 `Pydantic BaseModel`,可在 `entities` 模块中找到对应实体。
+
+### Provider
+
+* `provider` (string) 供应商标识,如:`openai`
+* `label` (object) 供应商展示名称,i18n,可设置 `en_US` 英文、`zh_Hans` 中文两种语言
+ * `zh_Hans` (string) \[optional] 中文标签名,`zh_Hans` 不设置将默认使用 `en_US`。
+ * `en_US` (string) 英文标签名
+* `description` (object) \[optional] 供应商描述,i18n
+ * `zh_Hans` (string) \[optional] 中文描述
+ * `en_US` (string) 英文描述
+* `icon_small` (string) \[optional] 供应商小 ICON,存储在对应供应商实现目录下的 `_assets` 目录,中英文策略同 `label`
+ * `zh_Hans` (string) \[optional] 中文 ICON
+ * `en_US` (string) 英文 ICON
+* `icon_large` (string) \[optional] 供应商大 ICON,存储在对应供应商实现目录下的 \_assets 目录,中英文策略同 label
+ * `zh_Hans` (string) \[optional] 中文 ICON
+ * `en_US` (string) 英文 ICON
+* `background` (string) \[optional] 背景颜色色值,例:#FFFFFF,为空则展示前端默认色值。
+* `help` (object) \[optional] 帮助信息
+ * `title` (object) 帮助标题,i18n
+ * `zh_Hans` (string) \[optional] 中文标题
+ * `en_US` (string) 英文标题
+ * `url` (object) 帮助链接,i18n
+ * `zh_Hans` (string) \[optional] 中文链接
+ * `en_US` (string) 英文链接
+* `supported_model_types` (array\[[ModelType](#modeltype)]) 支持的模型类型
+* `configurate_methods` (array\[[ConfigurateMethod](#configuratemethod)]) 配置方式
+* `provider_credential_schema` (\[[ProviderCredentialSchema](#providercredentialschema)]) 供应商凭据规格
+* `model_credential_schema` (\[[ModelCredentialSchema](#modelcredentialschema)]) 模型凭据规格
+
+### AIModelEntity
+
+* `model` (string) 模型标识,如:`gpt-3.5-turbo`
+* `label` (object) \[optional] 模型展示名称,i18n,可设置 `en_US` 英文、`zh_Hans` 中文两种语言
+ * `zh_Hans` (string) \[optional] 中文标签名
+ * `en_US` (string) 英文标签名
+* `model_type` ([ModelType](#modeltype)) 模型类型
+* `features` (array\[[ModelFeature](#modelfeature)]) \[optional] 支持功能列表
+* `model_properties` (object) 模型属性
+ * `mode` ([LLMMode](#llmmode)) 模式 (模型类型 `llm` 可用)
+ * `context_size` (int) 上下文大小 (模型类型 `llm` `text-embedding` 可用)
+ * `max_chunks` (int) 最大分块数量 (模型类型 `text-embedding moderation` 可用)
+ * `file_upload_limit` (int) 文件最大上传限制,单位:MB。(模型类型 `speech2text` 可用)
+ * `supported_file_extensions` (string) 支持文件扩展格式,如:mp3,mp4(模型类型 `speech2text` 可用)
+ * `default_voice` (string) 缺省音色,必选:alloy,echo,fable,onyx,nova,shimmer(模型类型 `tts` 可用)
+ * `voices` (list) 可选音色列表。
+ * `mode` (string) 音色模型。(模型类型 `tts` 可用)
+ * `name` (string) 音色模型显示名称。(模型类型 `tts` 可用)
+ * `language` (string) 音色模型支持语言。(模型类型 `tts` 可用)
+ * `word_limit` (int) 单次转换字数限制,默认按段落分段(模型类型 `tts` 可用)
+ * `audio_type` (string) 支持音频文件扩展格式,如:mp3,wav(模型类型 `tts` 可用)
+ * `max_workers` (int) 支持文字音频转换并发任务数(模型类型 `tts` 可用)
+ * `max_characters_per_chunk` (int) 每块最大字符数 (模型类型 `moderation` 可用)
+* `parameter_rules` (array\[[ParameterRule](#parameterrule)]) \[optional] 模型调用参数规则
+* `pricing` (\[[PriceConfig](#priceconfig)]) \[optional] 价格信息
+* `deprecated` (bool) 是否废弃。若废弃,模型列表将不再展示,但已经配置的可以继续使用,默认 False。
+
+### ModelType
+
+* `llm` 文本生成模型
+* `text-embedding` 文本 Embedding 模型
+* `rerank` Rerank 模型
+* `speech2text` 语音转文字
+* `tts` 文字转语音
+* `moderation` 审查
+
+### ConfigurateMethod
+
+* `predefined-model` 预定义模型
+
+表示用户只需要配置统一的供应商凭据即可使用供应商下的预定义模型。
+
+* `customizable-model` 自定义模型
+
+用户需要新增每个模型的凭据配置。
+
+* `fetch-from-remote` 从远程获取
+
+与 `predefined-model` 配置方式一致,只需要配置统一的供应商凭据即可,模型通过凭据信息从供应商获取。
+
+
+### ModelFeature
+
+* `agent-thought` Agent 推理,一般超过 70B 有思维链能力。
+* `vision` 视觉,即:图像理解。
+* `tool-call` 工具调用
+* `multi-tool-call` 多工具调用
+* `stream-tool-call` 流式工具调用
+
+### FetchFrom
+
+* `predefined-model` 预定义模型
+* `fetch-from-remote` 远程模型
+
+### LLMMode
+
+* `completion` 文本补全
+* `chat` 对话
+
+### ParameterRule
+
+* `name` (string) 调用模型实际参数名
+* `use_template` (string) \[optional] 使用模板
+
+> 关于模板的具体使用,可以参考[创建新模型提供者](/plugin_dev_zh/0222-creating-new-model-provider.zh)中的示例。
+
+默认预置了 5 种变量内容配置模板:
+
+* `temperature`
+* `top_p`
+* `frequency_penalty`
+* `presence_penalty`
+* `max_tokens`
+
+可在 use\_template 中直接设置模板变量名,将会使用 entities.defaults.PARAMETER\_RULE\_TEMPLATE 中的默认配置不用设置除 `name` 和 `use_template` 之外的所有参数,若设置了额外的配置参数,将覆盖默认配置。可参考 `openai/llm/gpt-3.5-turbo.yaml`。
+
+* `label` (object) \[optional] 标签,i18n
+* `zh_Hans`(string) \[optional] 中文标签名
+* `en_US` (string) 英文标签名
+* `type`(string) \[optional] 参数类型
+ * `int` 整数
+ * `float` 浮点数
+ * `string` 字符串
+ * `boolean` 布尔型
+* `help` (string) \[optional] 帮助信息
+* `zh_Hans` (string) \[optional] 中文帮助信息
+* `en_US` (string) 英文帮助信息
+* `required` (bool) 是否必填,默认 False。
+* `default`(int/float/string/bool) \[optional] 默认值
+* `min`(int/float) \[optional] 最小值,仅数字类型适用
+* `max`(int/float) \[optional] 最大值,仅数字类型适用
+* `precision`(int) \[optional] 精度,保留小数位数,仅数字类型适用
+* `options` (array\[string]) \[optional] 下拉选项值,仅当 `type` 为 `string` 时适用,若不设置或为 null 则不限制选项值
+
+### PriceConfig
+
+* `input` (float) 输入单价,即 Prompt 单价
+* `output` (float) 输出单价,即返回内容单价
+* `unit` (float) 价格单位,如以 1M tokens 计价,则单价对应的单位 token 数为 `0.000001`
+* `currency` (string) 货币单位
+
+### ProviderCredentialSchema
+
+* `credential_form_schemas` (array\[[CredentialFormSchema](#credentialformschema)]) 凭据表单规范
+
+### ModelCredentialSchema
+
+* `model` (object) 模型标识,变量名默认 `model`
+ * `label` (object) 模型表单项展示名称
+ * `en_US` (string) 英文
+ * `zh_Hans`(string) \[optional] 中文
+ * `placeholder` (object) 模型提示内容
+ * `en_US`(string) 英文
+ * `zh_Hans`(string) \[optional] 中文
+* `credential_form_schemas` (array\[[CredentialFormSchema](#credentialformschema)]) 凭据表单规范
+
+### CredentialFormSchema
+
+* `variable` (string) 表单项变量名
+* `label` (object) 表单项标签名
+ * `en_US`(string) 英文
+ * `zh_Hans` (string) \[optional] 中文
+* `type` ([FormType](#formtype)) 表单项类型
+* `required` (bool) 是否必填
+* `default`(string) 默认值
+* `options` (array\[[FormOption](#formoption)]) 表单项为 `select` 或 `radio` 专有属性,定义下拉内容
+* `placeholder`(object) 表单项为 `text-input` 专有属性,表单项 PlaceHolder
+ * `en_US`(string) 英文
+ * `zh_Hans` (string) \[optional] 中文
+* `max_length` (int) 表单项为`text-input`专有属性,定义输入最大长度,0 为不限制。
+* `show_on` (array\[[FormShowOnObject](#formshowonobject)]) 当其他表单项值符合条件时显示,为空则始终显示。
+
+#### FormType
+
+* `text-input` 文本输入组件
+* `secret-input` 密码输入组件
+* `select` 单选下拉
+* `radio` Radio 组件
+* `switch` 开关组件,仅支持 `true` 和 `false`
+
+#### FormOption
+
+* `label` (object) 标签
+ * `en_US`(string) 英文
+ * `zh_Hans`(string) \[optional] 中文
+* `value` (string) 下拉选项值
+* `show_on` (array\[[FormShowOnObject](#formshowonobject)]) 当其他表单项值符合条件时显示,为空则始终显示。
+
+#### FormShowOnObject
+
+* `variable` (string) 其他表单项变量名
+* `value` (string) 其他表单项变量值
+
+## 相关资源
+
+- [模型架构详解](/plugin_dev_zh/0412-model-schema.zh) - 深入了解模型插件的架构规范
+- [快速接入一个新模型](/plugin_dev_zh/0211-getting-started-new-model.zh) - 学习如何应用这些规则添加新模型
+- [一般规范定义](/plugin_dev_zh/0411-general-specifications.zh) - 了解插件清单文件的配置
+- [创建新模型提供者](/plugin_dev_zh/0222-creating-new-model-provider.zh) - 开发全新的模型提供商插件
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ 通过直接提交修改来帮助改进文档内容
+
+
+ 发现错误或有改进建议?请提交问题反馈
+
+
diff --git a/plugin_dev_zh/0411-model-plugin-introduction.zh.mdx b/plugin_dev_zh/0411-model-plugin-introduction.zh.mdx
new file mode 100644
index 00000000..84c2cfd6
--- /dev/null
+++ b/plugin_dev_zh/0411-model-plugin-introduction.zh.mdx
@@ -0,0 +1,105 @@
+---
+dimensions:
+ type:
+ primary: reference
+ detail: core
+ level: beginner
+standard_title: Model Plugin Introduction
+language: zh
+title: Model 插件
+description: 介绍模型插件的基本概念和结构。模型插件让Dify能够调用不同供应商(如OpenAI、Anthropic、Google等)的各类模型,包括大语言模型(LLM)、文本嵌入、语音转文字等不同类型。
+---
+
+Model 模型插件使 Dify 平台能够调用该模型供应商下的所有 LLM。例如,安装 OpenAI 模型插件后,Dify 平台即可调用 OpenAI 提供的 `GPT-4`、`GPT-4o-2024-05-13` 等模型。
+
+## 模型插件结构
+
+为了便于理解在开发模型插件过程中可能涉及的概念,以下是模型插件内的结构简介:
+
+* **模型供应商**:大模型的开发公司,例如 **OpenAI、Anthropic、Google** 等;
+* **模型分类**:根据模型供应商的不同,存在大语言模型(LLM)、文本嵌入模型(Text embedding)、语音转文字(Speech2text)等分类;
+* **具体模型**:`claude-3-5-sonnet`、`gpt-4-turbo` 等。
+
+插件项目中的代码层级结构:
+
+```bash
+- 模型供应商
+ - 模型分类
+ - 具体模型
+```
+
+以 **Anthropic** 为例,模型插件的示例结构如下:
+
+```bash
+- Anthropic
+ - llm
+ claude-3-5-sonnet-20240620
+ claude-3-haiku-20240307
+ claude-3-opus-20240229
+ claude-3-sonnet-20240229
+ claude-instant-1.2
+ claude-instant-1
+```
+
+以 OpenAI 为例,因为它支持多种模型类型,所以存在多层模型分类,结构如下:
+
+```bash
+├── models
+│ ├── llm
+│ │ ├── chatgpt-4o-latest
+│ │ ├── gpt-3.5-turbo
+│ │ ├── gpt-4-0125-preview
+│ │ ├── gpt-4-turbo
+│ │ ├── gpt-4o
+│ │ ├── llm
+│ │ ├── o1-preview
+│ │ └── text-davinci-003
+│ ├── moderation
+│ │ ├── moderation
+│ │ └── text-moderation-stable
+│ ├── speech2text
+│ │ ├── speech2text
+│ │ └── whisper-1
+│ ├── text_embedding
+│ │ ├── text-embedding-3-large
+│ │ └── text_embedding
+│ └── tts
+│ ├── tts-1-hd
+│ ├── tts-1
+│ └── tts
+```
+
+## 模型配置
+
+模型插件通过配置文件定义模型的行为和属性。详细的模型设计规则和配置格式请参考[模型设计规则](/plugin_dev_zh/0411-model-designing-rules.zh)文档和[模型架构](/plugin_dev_zh/0412-model-schema.zh)规范。
+
+## 进一步阅读
+
+- [快速接入一个新模型](/plugin_dev_zh/0211-getting-started-new-model.zh) - 学习如何为已支持的供应商添加新模型
+- [模型设计规则](/plugin_dev_zh/0411-model-designing-rules.zh) - 详细了解模型配置的规范
+- [模型架构](/plugin_dev_zh/0412-model-schema.zh) - 深入理解模型插件的架构
+- [通用规范定义](/plugin_dev_zh/0411-general-specifications.zh) - 了解插件元数据定义方式
+- [插件开发基本概念](/plugin_dev_zh/0111-getting-started-dify-plugin.zh) - 返回插件开发入门指南
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ 通过直接提交修改来帮助改进文档内容
+
+
+ 发现错误或有改进建议?请提交问题反馈
+
+
diff --git a/plugin_dev_zh/0411-persistent-storage-kv.zh.mdx b/plugin_dev_zh/0411-persistent-storage-kv.zh.mdx
new file mode 100644
index 00000000..c3cb454d
--- /dev/null
+++ b/plugin_dev_zh/0411-persistent-storage-kv.zh.mdx
@@ -0,0 +1,85 @@
+---
+dimensions:
+ type:
+ primary: reference
+ detail: core
+ level: beginner
+standard_title: Persistent Storage KV
+language: zh
+title: 持久化存储
+description: 本文档介绍了Dify插件中的持久化存储功能,详细说明了如何在插件中使用KV数据库来储存、获取和删除数据。这一功能使插件能够在相同Workspace内持久化存储数据,满足跨会话数据保存的需求。
+---
+
+如果单独审视插件中的 Tool 及 Endpoint,不难发现大多数情况下其只能完成单轮交互,请求后返回数据,任务结束。
+
+如果有需要长期储存的数据,如实现持久化的记忆,需要插件具备持久化存储能力。**持久化储机制能够让插件具备在相同 Workspace 持久存储数据的能力**,目前通过提供 KV 数据库满足存储需求,未来可能会根据实际的使用情况推出更灵活更强大的储存接口。
+
+### 储存 Key
+
+#### **入口**
+
+```python
+ self.session.storage
+```
+
+#### **接口**
+
+```python
+ def set(self, key: str, val: bytes) -> None:
+ pass
+```
+
+可以注意到传入的是一个 bytes,因此实际上你可以在其中储存文件。
+
+### 获取 Key
+
+#### **入口**
+
+```python
+ self.session.storage
+```
+
+#### **接口**
+
+```python
+ def get(self, key: str) -> bytes:
+ pass
+```
+
+### 删除 Key
+
+#### **入口**
+
+```python
+ self.session.storage
+```
+
+#### **接口**
+
+```python
+ def delete(self, key: str) -> None:
+ pass
+```
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ 通过直接提交修改来帮助改进文档内容
+
+
+ 发现错误或有改进建议?请提交问题反馈
+
+
diff --git a/plugin_dev_zh/0411-plugin-info-by-manifest.zh.mdx b/plugin_dev_zh/0411-plugin-info-by-manifest.zh.mdx
new file mode 100644
index 00000000..df55bd97
--- /dev/null
+++ b/plugin_dev_zh/0411-plugin-info-by-manifest.zh.mdx
@@ -0,0 +1,140 @@
+---
+dimensions:
+ type:
+ primary: reference
+ detail: core
+ level: beginner
+standard_title: Plugin info by Manifest
+language: zh
+title: Manifest
+description: Author Yeuoly,Allen 本文档详细介绍了Dify插件中的Manifest文件,它是定义插件基本信息的YAML文件。文档包含完整的代码示例和详细的结构说明,涵盖插件版本、类型、作者、名称、资源使用、权限申请、功能定义、运行时等各方面的配置信息。
+---
+
+Manifest 是一个符合 yaml 规范的文件,它定义了**插件**最基础的信息,包括但不限于插件名称、作者、包含的工具、模型等信息。关于插件的整体架构,请参考[插件开发基本概念](/plugin_dev_zh/0111-getting-started-dify-plugin.zh)和[开发者速查表](/plugin_dev_zh/0131-cheatsheet.zh)。
+
+若该文件的格式错误,插件的解析和打包过程都将会失败。
+
+### 代码示例
+
+下面是一个 Manifest 文件的简单示例,将在下文解释各个数据的含义和作用。
+
+如需参考其它插件的代码,请参考 [GitHub 代码仓库](https://github.com/langgenius/dify-official-plugins/blob/main/tools/google/manifest.yaml)。
+
+```yaml
+version: 0.0.1
+type: "plugin"
+author: "Yeuoly"
+name: "neko"
+label:
+ en_US: "Neko"
+created_at: "2024-07-12T08:03:44.658609186Z"
+icon: "icon.svg"
+resource:
+ memory: 1048576
+ permission:
+ tool:
+ enabled: true
+ model:
+ enabled: true
+ llm: true
+ endpoint:
+ enabled: true
+ app:
+ enabled: true
+ storage:
+ enabled: true
+ size: 1048576
+plugins:
+ endpoints:
+ - "provider/neko.yaml"
+meta:
+ version: 0.0.1
+ arch:
+ - "amd64"
+ - "arm64"
+ runner:
+ language: "python"
+ version: "3.10"
+ entrypoint: "main"
+privacy: "./privacy.md"
+```
+
+### 结构
+
+* `version`(version, required):插件的版本
+* `type`(type, required):插件类型,目前仅支持 `plugin`,未来支持 `bundle`
+* `author`(string, required):作者,在 Marketplace 中定义为组织名
+* `label`(label, required):多语言名称
+* `created_at`(RFC3339, required):创建时间,Marketplace 上要求创建时间不得大于当前时间
+* `icon`(asset, required):图标路径
+* `resource` (object):需要申请的资源
+ * `memory` (int64):最大内存占用,主要与 SaaS 上的 AWS Lambda 资源申请相关,单位字节
+ * `permission`(object):权限申请
+ * `tool`(object):反向调用工具的权限
+ * `enabled` (bool)
+ * `model`(object):反向调用模型的权限
+ * `enabled`(bool)
+ * `llm`(bool)
+ * `text_embedding`(bool)
+ * `rerank`(bool)
+ * `tts`(bool)
+ * `speech2text`(bool)
+ * `moderation`(bool)
+ * `node`(object):反向调用节点的权限
+ * `enabled`(bool)
+ * `endpoint`(object):允许注册 `endpoint` 的权限
+ * `enabled`(bool)
+ * `app`(object):反向调用`app`的权限
+ * `enabled`(bool)
+ * `storage`(object):申请持久化存储的权限
+ * `enabled`(bool)
+ * `size`(int64):最大允许多大的持久化内存,单位字节
+* `plugins`(object, required):插件扩展的具体能力的`yaml`文件列表,插件包内的绝对路径,如需要扩展模型,则需要定义一个类似于 `openai.yaml`的文件,并将该文件路径填写在此处,且该路径上的文件必须真实存在,否则打包将失败。
+ * 格式
+ * `tools`(list\[string]):插件扩展的[工具](/plugin_dev_zh/0222-tool-plugin.zh)供应商
+ * `models`(list\[string]):插件扩展的[模型](/plugin_dev_zh/0131-model-plugin-introduction.zh)供应商
+ * `endpoints`(list\[string]):插件扩展的 [Endpoints](/plugin_dev_zh/0432-endpoint.zh) 供应商
+ * `agent_strategies` (list\[string]):插件扩展的 [Agent 策略](/plugin_dev_zh/9433-agent-strategy-plugin.zh)供应商
+ * 限制
+ * 不允许同时扩展工具与模型
+ * 不允许没有任意扩展
+ * 不允许同时扩展模型与 Endpoint
+ * 目前仅支持各类型扩展最多一个供应商
+* `meta`(object)
+ * `version`(version, required):`manifest` 格式版本,初始版本 `0.0.1`
+ * `arch`(list\[string], required):支持的架构,目前仅支持 `amd64` `arm64`
+ * `runner`(object, required):运行时配置
+ * `language`(string):目前仅支持 python
+ * `version`(string):语言的版本,目前仅支持 `3.12`
+ * `entrypoint`(string):程序入口,在 python 下应为 `main`
+* `privacy` (string, optional):可选项,指定插件隐私政策文件的相对路径或 URL,例如 `"./privacy.md"` 或 `"https://your-web/privacy"`。如果计划将插件上架至 Dify Marketplace,**该字段为必填项**,用于提供明确的用户数据使用和隐私声明。详细填写指引请参考[插件隐私数据保护指南](/plugin_dev_zh/0312-privacy-protection-guidelines.zh)。
+
+## 相关资源
+
+- [插件开发基本概念](/plugin_dev_zh/0111-getting-started-dify-plugin.zh) - 全面了解Dify插件开发
+- [快速接入一个新模型](/plugin_dev_zh/0211-getting-started-new-model.zh) - 学习为已有的供应商添加新模型
+- [通用规范定义](/plugin_dev_zh/0411-general-specifications.zh) - 了解插件开发中的通用结构
+- [发布概览](/plugin_dev_zh/0321-release-overview.zh) - 学习插件发布流程
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ 通过直接提交修改来帮助改进文档内容
+
+
+ 发现错误或有改进建议?请提交问题反馈
+
+
diff --git a/plugin_dev_zh/0411-remote-debug-a-plugin.zh.mdx b/plugin_dev_zh/0411-remote-debug-a-plugin.zh.mdx
new file mode 100644
index 00000000..4027c8b6
--- /dev/null
+++ b/plugin_dev_zh/0411-remote-debug-a-plugin.zh.mdx
@@ -0,0 +1,55 @@
+---
+dimensions:
+ type:
+ primary: reference
+ detail: core
+ level: beginner
+standard_title: Remote Debug a Plugin
+language: zh
+title: 插件调试
+description: 本文档介绍了如何使用Dify的远程调试功能来测试插件。详细说明了获取调试信息、配置环境变量文件、启动插件远程调试以及验证插件安装状态的完整流程。通过这种方式,开发者可以在本地开发的同时在Dify环境中实时测试插件。
+---
+
+插件开发完成后,接下来需要测试插件是否可以正常运行。Dify 提供便捷地远程调试方式,帮助你快速在测试环境中验证插件功能。
+
+前往[“插件管理”](https://cloud.dify.ai/plugins)页获取远程服务器地址和调试 Key。
+
+
+
+回到插件项目,拷贝 `.env.example` 文件并重命名为 `.env`,将获取的远程服务器地址和调试 Key 等信息填入其中。
+
+`.env` 文件:
+
+```bash
+INSTALL_METHOD=remote
+REMOTE_INSTALL_HOST=remote
+REMOTE_INSTALL_PORT=5003
+REMOTE_INSTALL_KEY=****-****-****-****-****
+```
+
+运行 `python -m main` 命令启动插件。在插件页即可看到该插件已被安装至 Workspace 内,团队中的其他成员也可以访问该插件。
+
+
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ 通过直接提交修改来帮助改进文档内容
+
+
+ 发现错误或有改进建议?请提交问题反馈
+
+
diff --git a/plugin_dev_zh/0411-tool.zh.mdx b/plugin_dev_zh/0411-tool.zh.mdx
new file mode 100644
index 00000000..2619bda1
--- /dev/null
+++ b/plugin_dev_zh/0411-tool.zh.mdx
@@ -0,0 +1,144 @@
+---
+dimensions:
+ type:
+ primary: reference
+ detail: core
+ level: beginner
+standard_title: Tool
+language: zh
+title: Tool Return
+description: 本文档详细介绍了Dify插件中Tool的数据结构和使用方法。内容包括如何返回不同类型的消息(图片URL、链接、文本、文件、JSON)、如何创建变量和流式变量消息以及如何定义工具的输出变量模式以便于在workflow中引用。
+---
+
+在阅读详细的接口文档之前,请确保你对 Dify 插件的工具接入流程已有大致了解。
+
+### 数据结构
+
+#### 消息返回
+
+Dify 支持`文本` `链接` `图片` `文件BLOB` `JSON` 等多种消息类型,你可以通过以下不同的接口返回不同类型的消息。
+
+在默认情况下,一个工具在 `workflow` 中的输出会包含 `files` `text` `json` 三个固定变量,且你可以通过下面的方法来返回这三个变量的数据。
+
+例如使用 `create_image_message` 来返回图片,但是同时工具也支持自定义的输出变量,从而可以更方便地在 `workflow` 中引用这些变量。
+
+#### **图片 URL**
+
+只需要传递图片的 URL,Dify 将通过链接自动下载图片并返回给用户。
+
+```python
+ def create_image_message(self, image: str) -> ToolInvokeMessage:
+ pass
+```
+
+#### **链接**
+
+如果你需要返回一个链接,使用以下接口。
+
+```python
+ def create_link_message(self, link: str) -> ToolInvokeMessage:
+ pass
+```
+
+#### **文本**
+
+如果你需要返回一个文本消息,使用以下接口。
+
+```python
+ def create_text_message(self, text: str) -> ToolInvokeMessage:
+ pass
+```
+
+**文件**
+
+如果你需要返回文件的原始数据,如图片、音频、视频、PPT、Word、Excel 等,可以使用以下接口。
+
+* `blob` 文件的原始数据,bytes 类型。
+* `meta` 文件的元数据。如果开发者需要明确的文件类型,请指定`mime_type`,否则 Dify 将使用`octet/stream`作为默认类型。
+
+```python
+ def create_blob_message(self, blob: bytes, meta: dict = None) -> ToolInvokeMessage:
+ pass
+```
+
+#### **JSON**
+
+如果你需要返回一个格式化的 JSON,可以使用以下接口。这通常用于 workflow 中的节点间的数据传递。在 agent 模式中,大部分大模型也都能够阅读和理解 JSON。
+
+* `object` 一个 Python 的字典对象,会被自动序列化为 JSON。
+
+```python
+ def create_json_message(self, json: dict) -> ToolInvokeMessage:
+ pass
+```
+
+#### **变量**
+
+对于非流式输出的变量,你可以使用以下接口返回,如创建多份,后者将覆盖前者。
+
+```python
+ def create_variable_message(self, variable_name: str, variable_value: Any) -> ToolInvokeMessage:
+ pass
+```
+
+#### **流式变量**
+
+如果你想以“打字机”效果输出一段文字,可以使用流式变量输出文本。如果你在 `chatflow` 应用中使用 `answer` 节点并引用了该变量,那么文本将以“打字机”的效果输出。但目前该方法仅支持字符串类型的数据。
+
+```python
+ def create_stream_variable_message(
+ self, variable_name: str, variable_value: str
+ ) -> ToolInvokeMessage:
+```
+
+#### 返回自定义变量
+
+如果想要在 `workflow` 应用中引用 `tool` 的输出变量,则有必要提前定义有哪些变量可能被输出。Dify 插件支持使用 [`json_schema`](https://json-schema.org/)格式的输出变量定义,以下是一个简单的示例:
+
+```yaml
+identity:
+ author: author
+ name: tool
+ label:
+ en_US: label
+ zh_Hans: 标签
+ ja_JP: レベル
+ pt_BR: etiqueta
+description:
+ human:
+ en_US: description
+ zh_Hans: 描述
+ ja_JP: 説明
+ pt_BR: descrição
+ llm: description
+output_schema:
+ type: object
+ properties:
+ name:
+ type: string
+```
+
+上述示例代码定义了一个简单的工具,并为它指定了 `output_schema`,其中包含一个 `name` 字段,此时可以在 `workflow` 中引用该字段。但是请注意,还需要在工具的实现代码中返回一个变量才可以真正使用,否则将得到一个 `None` 返回结果。
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ 通过直接提交修改来帮助改进文档内容
+
+
+ 发现错误或有改进建议?请提交问题反馈
+
+
diff --git a/plugin_dev_zh/0412-model-schema.zh.mdx b/plugin_dev_zh/0412-model-schema.zh.mdx
new file mode 100644
index 00000000..b2692684
--- /dev/null
+++ b/plugin_dev_zh/0412-model-schema.zh.mdx
@@ -0,0 +1,713 @@
+---
+dimensions:
+ type:
+ primary: reference
+ detail: core
+ level: intermediate
+standard_title: Model Schema
+language: zh
+title: 模型接口
+description: 本文档详细介绍了Dify模型插件开发所需的接口规范,包括模型供应商实现、五种模型类型(LLM、TextEmbedding、Rerank、Speech2text、Text2speech)的接口定义以及相关数据结构如PromptMessage、LLMResult等的完整规范。文档适用于开发者实现各种模型集成的开发参考。
+---
+
+这里介绍供应商和各模型类型需要实现的接口方法和参数说明。在开发模型插件前,您可能需要首先阅读[模型设计规则](/plugin_dev_zh/0411-model-designing-rules.zh)和[模型插件介绍](/plugin_dev_zh/0131-model-plugin-introduction.zh)。
+
+### 模型供应商
+
+继承 `__base.model_provider.ModelProvider` 基类,实现以下接口:
+
+```python
+def validate_provider_credentials(self, credentials: dict) -> None:
+ """
+ Validate provider credentials
+ You can choose any validate_credentials method of model type or implement validate method by yourself,
+ such as: get model list api
+
+ if validate failed, raise exception
+
+ :param credentials: provider credentials, credentials form defined in `provider_credential_schema`.
+ """
+```
+
+* `credentials` (object) 凭据信息
+
+凭据信息的参数由供应商 YAML 配置文件的 `provider_credential_schema` 定义,传入如:`api_key` 等。验证失败请抛出 `errors.validate.CredentialsValidateFailedError` 错误。**注:预定义模型需完整实现该接口,自定义模型供应商只需要如下简单实现即可:**
+
+```python
+class XinferenceProvider(Provider):
+ def validate_provider_credentials(self, credentials: dict) -> None:
+ pass
+```
+
+### 模型
+
+模型分为 5 种不同的模型类型,不同模型类型继承的基类不同,需要实现的方法也不同。
+
+#### 通用接口
+
+所有模型均需要统一实现下面 2 个方法:
+
+* 模型凭据校验
+
+与供应商凭据校验类似,这里针对单个模型进行校验。
+
+```python
+def validate_credentials(self, model: str, credentials: dict) -> None:
+ """
+ Validate model credentials
+
+ :param model: model name
+ :param credentials: model credentials
+ :return:
+ """
+```
+
+参数:
+
+* `model` (string) 模型名称
+* `credentials` (object) 凭据信息
+
+凭据信息的参数由供应商 YAML 配置文件的 `provider_credential_schema` 或 `model_credential_schema` 定义,传入如:`api_key` 等。验证失败请抛出 `errors.validate.CredentialsValidateFailedError` 错误。
+
+* 调用异常错误映射表
+
+当模型调用异常时需要映射到 Runtime 指定的 `InvokeError` 类型,方便 Dify 针对不同错误做不同后续处理。Runtime Errors:
+
+* `InvokeConnectionError` 调用连接错误
+* `InvokeServerUnavailableError` 调用服务方不可用
+* `InvokeRateLimitError` 调用达到限额
+* `InvokeAuthorizationError` 调用鉴权失败
+* `InvokeBadRequestError` 调用传参有误
+
+```python
+@property
+def _invoke_error_mapping(self) -> dict[type[InvokeError], list[type[Exception]]]:
+ """
+ Map model invoke error to unified error
+ The key is the error type thrown to the caller
+ The value is the error type thrown by the model,
+ which needs to be converted into a unified error type for the caller.
+
+ :return: Invoke error mapping
+ """
+```
+
+也可以直接抛出对应 Erros,并做如下定义,这样在之后的调用中可以直接抛出`InvokeConnectionError`等异常。
+
+#### LLM
+
+继承 `__base.large_language_model.LargeLanguageModel` 基类,实现以下接口:
+
+* LLM 调用
+
+实现 LLM 调用的核心方法,可同时支持流式和同步返回。
+
+```python
+def _invoke(self, model: str, credentials: dict,
+ prompt_messages: list[PromptMessage], model_parameters: dict,
+ tools: Optional[list[PromptMessageTool]] = None, stop: Optional[list[str]] = None,
+ stream: bool = True, user: Optional[str] = None) \
+ -> Union[LLMResult, Generator]:
+ """
+ Invoke large language model
+
+ :param model: model name
+ :param credentials: model credentials
+ :param prompt_messages: prompt messages
+ :param model_parameters: model parameters
+ :param tools: tools for tool calling
+ :param stop: stop words
+ :param stream: is stream response
+ :param user: unique user id
+ :return: full response or stream response chunk generator result
+ """
+```
+
+* 参数:
+ * `model` (string) 模型名称
+ * `credentials` (object) 凭据信息
+
+凭据信息的参数由供应商 YAML 配置文件的 `provider_credential_schema` 或 `model_credential_schema` 定义,传入如:`api_key` 等。
+
+* `prompt_messages` (array\[[PromptMessage](#promptmessage)]) Prompt 列表
+
+若模型为 `Completion` 类型,则列表只需要传入一个 [UserPromptMessage](#userpromptmessage) 元素即可;若模型为 `Chat` 类型,需要根据消息不同传入 [SystemPromptMessage](#systempromptmessage), [UserPromptMessage](#userpromptmessage), [AssistantPromptMessage](#assistantpromptmessage), [ToolPromptMessage](#toolpromptmessage) 元素列表
+
+* `model_parameters` (object) 模型参数模型参数由模型 YAML 配置的 `parameter_rules` 定义。
+
+* `tools` (array\[[PromptMessageTool](#promptmessagetool)]) \[optional] 工具列表,等同于 `function calling` 中的 `function`。即传入 tool calling 的工具列表。
+
+* `stop` (array\[string]) \[optional] 停止序列模型返回将在停止序列定义的字符串之前停止输出。
+
+* `stream` (bool) 是否流式输出,默认 True
+流式输出返回 Generator\[[LLMResultChunk](#llmresultchunk)],非流式输出返回 [LLMResult](#llmresult)。
+
+* `user` (string) \[optional] 用户的唯一标识符可以帮助供应商监控和检测滥用行为。
+
+* 返回
+
+流式输出返回 Generator\[[LLMResultChunk](#llmresultchunk)],非流式输出返回 [LLMResult](#llmresult)。
+
+* 预计算输入 tokens
+
+若模型未提供预计算 tokens 接口,可直接返回 0。
+
+```python
+def get_num_tokens(self, model: str, credentials: dict, prompt_messages: list[PromptMessage],
+ tools: Optional[list[PromptMessageTool]] = None) -> int:
+ """
+ Get number of tokens for given prompt messages
+
+ :param model: model name
+ :param credentials: model credentials
+ :param prompt_messages: prompt messages
+ :param tools: tools for tool calling
+ :return:
+ """
+```
+
+参数说明见上述 `LLM 调用`。该接口需要根据对应`model`选择合适的`tokenizer`进行计算,如果对应模型没有提供`tokenizer`,可以使用`AIModel`基类中的`_get_num_tokens_by_gpt2(text: str)`方法进行计算。
+
+* 获取自定义模型规则 \[可选]
+
+```python
+def get_customizable_model_schema(self, model: str, credentials: dict) -> Optional[AIModelEntity]:
+ """
+ Get customizable model schema
+
+ :param model: model name
+ :param credentials: model credentials
+ :return: model schema
+ """
+```
+
+当供应商支持增加自定义 LLM 时,可实现此方法让自定义模型可获取模型规则,默认返回 None。
+
+对于 `OpenAI` 供应商下的大部分微调模型,可以通过其微调模型名称获取到其基类模型,如`gpt-3.5-turbo-1106`,然后返回基类模型的预定义参数规则,参考 [OpenAI](https://github.com/langgenius/dify-official-plugins/tree/main/models/openai) 的具体实现。
+
+#### TextEmbedding
+
+继承 `__base.text_embedding_model.TextEmbeddingModel` 基类,实现以下接口:
+
+* Embedding 调用
+
+```python
+def _invoke(self, model: str, credentials: dict,
+ texts: list[str], user: Optional[str] = None) \
+ -> TextEmbeddingResult:
+ """
+ Invoke large language model
+
+ :param model: model name
+ :param credentials: model credentials
+ :param texts: texts to embed
+ :param user: unique user id
+ :return: embeddings result
+ """
+```
+
+* 参数:
+
+* `model` (string) 模型名称
+
+* `credentials` (object) 凭据信息
+
+凭据信息的参数由供应商 YAML 配置文件的 `provider_credential_schema` 或 `model_credential_schema` 定义,传入如:`api_key` 等。
+
+* `texts` (array\[string]) 文本列表,可批量处理
+
+* `user` (string) \[optional] 用户的唯一标识符
+可以帮助供应商监控和检测滥用行为。
+
+* 返回:
+
+[TextEmbeddingResult](#textembeddingresult) 实体。
+
+* 预计算 tokens
+
+```python
+def get_num_tokens(self, model: str, credentials: dict, texts: list[str]) -> int:
+ """
+ Get number of tokens for given prompt messages
+
+ :param model: model name
+ :param credentials: model credentials
+ :param texts: texts to embed
+ :return:
+ """
+```
+
+参数说明见上述 `Embedding 调用`。
+
+同上述 `LargeLanguageModel`,该接口需要根据对应 `model` 选择合适的 `tokenizer` 进行计算,如果对应模型没有提供 `tokenizer`,可以使用`AIModel`基类中的`_get_num_tokens_by_gpt2(text: str)`方法进行计算。
+
+#### Rerank
+
+继承 `__base.rerank_model.RerankModel` 基类,实现以下接口:
+
+* rerank 调用
+
+```python
+def _invoke(self, model: str, credentials: dict,
+ query: str, docs: list[str], score_threshold: Optional[float] = None, top_n: Optional[int] = None,
+ user: Optional[str] = None) \
+ -> RerankResult:
+ """
+ Invoke rerank model
+
+ :param model: model name
+ :param credentials: model credentials
+ :param query: search query
+ :param docs: docs for reranking
+ :param score_threshold: score threshold
+ :param top_n: top n
+ :param user: unique user id
+ :return: rerank result
+ """
+```
+
+* 参数:
+
+* `model` (string) 模型名称
+* `credentials` (object) 凭据信息
+凭据信息的参数由供应商 YAML 配置文件的 `provider_credential_schema` 或 `model_credential_schema` 定义,传入如:`api_key` 等。
+* `query` (string) 查询请求内容
+* `docs` (array\[string]) 需要重排的分段列表
+* `score_threshold` (float) \[optional] Score 阈值
+* `top_n` (int) \[optional] 取前 n 个分段
+* `user` (string) \[optional] 用户的唯一标识符
+可以帮助供应商监控和检测滥用行为。
+
+
+* 返回:
+
+[RerankResult](#rerankresult) 实体。
+
+#### Speech2text
+
+继承 `__base.speech2text_model.Speech2TextModel` 基类,实现以下接口:
+
+
+* Invoke 调用
+
+```python
+def _invoke(self, model: str, credentials: dict,
+ file: IO[bytes], user: Optional[str] = None) \
+ -> str:
+ """
+ Invoke large language model
+
+ :param model: model name
+ :param credentials: model credentials
+ :param file: audio file
+ :param user: unique user id
+ :return: text for given audio file
+ """
+```
+
+* 参数:
+
+* `model` (string) 模型名称
+* `credentials` (object) 凭据信息
+凭据信息的参数由供应商 YAML 配置文件的 `provider_credential_schema` 或 `model_credential_schema` 定义,传入如:`api_key` 等。
+* `file` (File) 文件流
+* `user` (string) \[optional] 用户的唯一标识符
+可以帮助供应商监控和检测滥用行为。
+
+* 返回:
+
+语音转换后的字符串。
+
+#### Text2speech
+
+继承 `__base.text2speech_model.Text2SpeechModel` 基类,实现以下接口:
+
+* Invoke 调用
+
+```python
+def _invoke(self, model: str, credentials: dict, content_text: str, streaming: bool, user: Optional[str] = None):
+ """
+ Invoke large language model
+
+ :param model: model name
+ :param credentials: model credentials
+ :param content_text: text content to be translated
+ :param streaming: output is streaming
+ :param user: unique user id
+ :return: translated audio file
+ """
+```
+
+* 参数:
+
+* `model` (string) 模型名称
+* `credentials` (object) 凭据信息
+凭据信息的参数由供应商 YAML 配置文件的 `provider_credential_schema` 或 `model_credential_schema` 定义,传入如:`api_key` 等。
+* `content_text` (string) 需要转换的文本内容
+* `streaming` (bool) 是否进行流式输出
+* `user` (string) \[optional] 用户的唯一标识符
+可以帮助供应商监控和检测滥用行为。
+
+* 返回:
+
+文本转换后的语音流。
+
+
+#### Moderation
+
+继承 `__base.moderation_model.ModerationModel` 基类,实现以下接口:
+
+* Invoke 调用
+
+```python
+def _invoke(self, model: str, credentials: dict,
+ text: str, user: Optional[str] = None) \
+ -> bool:
+ """
+ Invoke large language model
+
+ :param model: model name
+ :param credentials: model credentials
+ :param text: text to moderate
+ :param user: unique user id
+ :return: false if text is safe, true otherwise
+ """
+```
+
+* 参数:
+
+* `model` (string) 模型名称
+* `credentials` (object) 凭据信息
+凭据信息的参数由供应商 YAML 配置文件的 `provider_credential_schema` 或 `model_credential_schema` 定义,传入如:`api_key` 等。
+* `text` (string) 文本内容
+* `user` (string) \[optional] 用户的唯一标识符
+可以帮助供应商监控和检测滥用行为。
+
+
+* 返回:
+
+False 代表传入的文本安全,True 则反之。
+
+### 实体
+
+#### PromptMessageRole
+
+消息角色
+
+```python
+class PromptMessageRole(Enum):
+ """
+ Enum class for prompt message.
+ """
+ SYSTEM = "system"
+ USER = "user"
+ ASSISTANT = "assistant"
+ TOOL = "tool"
+```
+
+#### PromptMessageContentType
+
+消息内容类型,分为纯文本和图片。
+
+```python
+class PromptMessageContentType(Enum):
+ """
+ Enum class for prompt message content type.
+ """
+ TEXT = 'text'
+ IMAGE = 'image'
+```
+
+#### PromptMessageContent
+
+消息内容基类,仅作为参数声明用,不可初始化。
+
+```python
+class PromptMessageContent(BaseModel):
+ """
+ Model class for prompt message content.
+ """
+ type: PromptMessageContentType
+ data: str # 内容数据
+```
+
+当前支持文本和图片两种类型,可支持同时传入文本和多图。
+需要分别初始化 `TextPromptMessageContent` 和 `ImagePromptMessageContent` 传入。
+
+#### TextPromptMessageContent
+
+```python
+class TextPromptMessageContent(PromptMessageContent):
+ """
+ Model class for text prompt message content.
+ """
+ type: PromptMessageContentType = PromptMessageContentType.TEXT
+```
+
+若传入图文,其中文字需要构造此实体作为 `content` 列表中的一部分。
+
+#### ImagePromptMessageContent
+
+```python
+class ImagePromptMessageContent(PromptMessageContent):
+ """
+ Model class for image prompt message content.
+ """
+ class DETAIL(Enum):
+ LOW = 'low'
+ HIGH = 'high'
+
+ type: PromptMessageContentType = PromptMessageContentType.IMAGE
+ detail: DETAIL = DETAIL.LOW # 分辨率
+```
+
+若传入图文,其中图片需要构造此实体作为 `content` 列表中的一部分
+`data` 可以为 `url` 或者图片 `base64` 加密后的字符串。
+
+#### PromptMessage
+
+所有 Role 消息体的基类,仅作为参数声明用,不可初始化。
+
+```python
+class PromptMessage(ABC, BaseModel):
+ """
+ Model class for prompt message.
+ """
+ role: PromptMessageRole # 消息角色
+ content: Optional[str | list[PromptMessageContent]] = None # 支持两种类型,字符串和内容列表,内容列表是为了满足多模态的需要,可详见 PromptMessageContent 说明。
+ name: Optional[str] = None # 名称,可选。
+```
+
+#### UserPromptMessage
+
+UserMessage 消息体,代表用户消息。
+
+```python
+class UserPromptMessage(PromptMessage):
+ """
+ Model class for user prompt message.
+ """
+ role: PromptMessageRole = PromptMessageRole.USER
+```
+
+#### AssistantPromptMessage
+
+代表模型返回消息,通常用于 `few-shots` 或聊天历史传入。
+
+```python
+class AssistantPromptMessage(PromptMessage):
+ """
+ Model class for assistant prompt message.
+ """
+ class ToolCall(BaseModel):
+ """
+ Model class for assistant prompt message tool call.
+ """
+ class ToolCallFunction(BaseModel):
+ """
+ Model class for assistant prompt message tool call function.
+ """
+ name: str # 工具名称
+ arguments: str # 工具参数
+
+ id: str # 工具 ID,仅在 OpenAI tool call 生效,为工具调用的唯一 ID,同一个工具可以调用多次
+ type: str # 默认 function
+ function: ToolCallFunction # 工具调用信息
+
+ role: PromptMessageRole = PromptMessageRole.ASSISTANT
+ tool_calls: list[ToolCall] = [] # 模型回复的工具调用结果(仅当传入 tools,并且模型认为需要调用工具时返回)
+```
+
+其中 `tool_calls` 为调用模型传入 `tools` 后,由模型返回的 `tool call` 列表。
+
+#### SystemPromptMessage
+
+代表系统消息,通常用于设定给模型的系统指令。
+
+```python
+class SystemPromptMessage(PromptMessage):
+ """
+ Model class for system prompt message.
+ """
+ role: PromptMessageRole = PromptMessageRole.SYSTEM
+```
+
+#### ToolPromptMessage
+
+代表工具消息,用于工具执行后将结果交给模型进行下一步计划。
+
+```python
+class ToolPromptMessage(PromptMessage):
+ """
+ Model class for tool prompt message.
+ """
+ role: PromptMessageRole = PromptMessageRole.TOOL
+ tool_call_id: str # 工具调用 ID,若不支持 OpenAI tool call,也可传入工具名称
+```
+
+基类的 `content` 传入工具执行结果。
+
+#### PromptMessageTool
+
+```python
+class PromptMessageTool(BaseModel):
+ """
+ Model class for prompt message tool.
+ """
+ name: str # 工具名称
+ description: str # 工具描述
+ parameters: dict # 工具参数 dict
+
+```
+
+***
+
+#### LLMResult
+
+```python
+class LLMResult(BaseModel):
+ """
+ Model class for llm result.
+ """
+ model: str # 实际使用模型
+ prompt_messages: list[PromptMessage] # prompt 消息列表
+ message: AssistantPromptMessage # 回复消息
+ usage: LLMUsage # 使用的 tokens 及费用信息
+ system_fingerprint: Optional[str] = None # 请求指纹,可参考 OpenAI 该参数定义
+```
+
+#### LLMResultChunkDelta
+
+流式返回中每个迭代内部 `delta` 实体
+
+```python
+class LLMResultChunkDelta(BaseModel):
+ """
+ Model class for llm result chunk delta.
+ """
+ index: int # 序号
+ message: AssistantPromptMessage # 回复消息
+ usage: Optional[LLMUsage] = None # 使用的 tokens 及费用信息,仅最后一条返回
+ finish_reason: Optional[str] = None # 结束原因,仅最后一条返回
+```
+
+#### LLMResultChunk
+
+流式返回中每个迭代实体
+
+```python
+class LLMResultChunk(BaseModel):
+ """
+ Model class for llm result chunk.
+ """
+ model: str # 实际使用模型
+ prompt_messages: list[PromptMessage] # prompt 消息列表
+ system_fingerprint: Optional[str] = None # 请求指纹,可参考 OpenAI 该参数定义
+ delta: LLMResultChunkDelta # 每个迭代存在变化的内容
+```
+
+#### LLMUsage
+
+```python
+class LLMUsage(ModelUsage):
+ """
+ Model class for llm usage.
+ """
+ prompt_tokens: int # prompt 使用 tokens
+ prompt_unit_price: Decimal # prompt 单价
+ prompt_price_unit: Decimal # prompt 价格单位,即单价基于多少 tokens
+ prompt_price: Decimal # prompt 费用
+ completion_tokens: int # 回复使用 tokens
+ completion_unit_price: Decimal # 回复单价
+ completion_price_unit: Decimal # 回复价格单位,即单价基于多少 tokens
+ completion_price: Decimal # 回复费用
+ total_tokens: int # 总使用 token 数
+ total_price: Decimal # 总费用
+ currency: str # 货币单位
+ latency: float # 请求耗时(s)
+```
+
+***
+
+#### TextEmbeddingResult
+
+```python
+class TextEmbeddingResult(BaseModel):
+ """
+ Model class for text embedding result.
+ """
+ model: str # 实际使用模型
+ embeddings: list[list[float]] # embedding 向量列表,对应传入的 texts 列表
+ usage: EmbeddingUsage # 使用信息
+```
+
+#### EmbeddingUsage
+
+```python
+class EmbeddingUsage(ModelUsage):
+ """
+ Model class for embedding usage.
+ """
+ tokens: int # 使用 token 数
+ total_tokens: int # 总使用 token 数
+ unit_price: Decimal # 单价
+ price_unit: Decimal # 价格单位,即单价基于多少 tokens
+ total_price: Decimal # 总费用
+ currency: str # 货币单位
+ latency: float # 请求耗时(s)
+```
+
+***
+
+#### RerankResult
+
+```python
+class RerankResult(BaseModel):
+ """
+ Model class for rerank result.
+ """
+ model: str # 实际使用模型
+ docs: list[RerankDocument] # 重排后的分段列表
+```
+
+#### RerankDocument
+
+```python
+class RerankDocument(BaseModel):
+ """
+ Model class for rerank document.
+ """
+ index: int # 原序号
+ text: str # 分段文本内容
+ score: float # 分数
+```
+
+## 相关资源
+
+- [模型设计规则](/plugin_dev_zh/0411-model-designing-rules.zh) - 了解模型配置的规范
+- [模型插件介绍](/plugin_dev_zh/0131-model-plugin-introduction.zh) - 快速了解模型插件的基本概念
+- [快速接入一个新模型](/plugin_dev_zh/0211-getting-started-new-model.zh) - 学习为已有的供应商添加新模型
+- [创建新模型提供者](/plugin_dev_zh/0222-creating-new-model-provider.zh) - 学习如何开发全新的模型供应商
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ 通过直接提交修改来帮助改进文档内容
+
+
+ 发现错误或有改进建议?请提交问题反馈
+
+
diff --git a/plugin_dev_zh/0432-develop-a-slack-bot-plugin.zh.mdx b/plugin_dev_zh/0432-develop-a-slack-bot-plugin.zh.mdx
new file mode 100644
index 00000000..1d81b822
--- /dev/null
+++ b/plugin_dev_zh/0432-develop-a-slack-bot-plugin.zh.mdx
@@ -0,0 +1,370 @@
+---
+dimensions:
+ type:
+ primary: reference
+ detail: examples
+ level: intermediate
+standard_title: Develop A Slack Bot Plugin
+language: zh
+title: 开发 Slack Bot 插件
+description: 本文档提供了完整的Slack Bot插件开发指南,包括遍历项目初始化、编辑配置表单、实现功能代码、插件调试、设置Endpoint、效果验证到打包发布的完整流程。需要使用Dify插件脚手架工具和已创建的Slack
+ App以实现在Slack平台上搞建由AI驱动的聊天机器人。
+---
+
+**本文将帮助你:**
+
+深入掌握 Slack Bot 的搭建方法,创建由 AI 驱动的 Slack 聊天机器人,在 Slack 平台上智能回答用户的问题。如果你还没有开发插件的经验,建议先阅读[插件开发入门指南](/plugin_dev_zh/0211-getting-started-dify-tool.zh)。
+
+### 项目背景
+
+Dify 插件生态致力于支持更简单、更易用的接入方式。本文将以 Slack 为例,详细介绍如何开发一个 Slack Bot 插件,便于团队中的成员直接在 Slack 平台内与 LLM 对话,提升 AI 服务的使用效率。
+
+Dify 插件生态旨在提供更简单、更便捷的接入方式。本文将以 Slack 为例,详细讲解如何开发一个 Slack Bot 插件,帮助团队成员直接在 Slack 平台使用 AI 应用,提升办公效率。
+
+Slack 是一个自由开放的实时办公通信平台,拥有丰富的 API。其中,基于事件机制的 Webhook 功能易于上手开发。我们将利用该机制创建 Slack Bot 插件,其原理如下图所示:
+
+
+
+> 为了避免混乱,现对以下概念作出解释:
+>
+> * **Slack Bot** 是在 Slack 平台上的一个聊天机器人,可以被视为虚拟角色,你可以与它进行聊天互动
+> * **Slack Bot 插件**指的是 Dify Marketplace上的一款插件,用于连接 Dify 应用与 Slack 平台。本文将主要围绕该插件开发展开。
+
+**原理简介:**
+
+1. **向 Slack Bot 发送消息**
+
+ 当用户在 Slack 中向 Bot 机器人发出一条消息的时候,Slack Bot 会发出一个 Webhook 请求到 Dify 平台。
+2. **消息转发至 Slack Bot 插件**
+
+ 用户在与 Slack bot 对话时,需要将消息转发至 Dify 应用。好比邮件系统需要一位收件人的邮箱,此时可以通过 Slack 的 API 配置一个 Slack Webhook 的地址,并将其填入至 Slack Bot 插件并建立连接。
+3. **插件在接受到消息后,返回至某个 Dify 应用**
+
+ Slack Bot 插件将处理 Slack 请求,发送至 Dify 中的应用。由 LLM 分析用户输入的内容并给出回应。
+4. **Dify 应用回应后,将消息返回至 Slack Bot 并回答用户**
+
+ Slack Bot 获取 Dify 应用的回复后,通过插件将消息原路返回至 Slack Bot,使得用户能够在使用 Slack 时直接与 Dify 应用互动
+
+### 前置准备
+
+* Dify 插件脚手架工具,详细说明请参考[初始化开发工具](/plugin_dev_zh/0221-initialize-development-tools.zh)。
+* Python 环境,版本号 ≥ 3.12,详细说明请参考 [Python 安装教程](https://pythontest.com/python/installing-python-3-11/),或询问 LLM 获取完整的安装教程。
+* 创建 Slack App 并获取 OAuth Token
+
+前往 [Slack API](https://api.slack.com/apps) 平台, 选择以 scratch 方式创建 Slack APP,并选择需部署应用的 Slack 空间。
+
+
+
+开启 Webhooks 功能。
+
+
+
+将 App 安装至 Slack 工作区内。
+
+
+
+获取 OAuth Token,用于后续的插件开发。
+
+
+
+### 1. 开发插件
+
+现在开始实际的插件编码工作。在开始之前,请确保你已经阅读过[开发 Extension 插件](/plugin_dev_zh/9231-extension-plugin.zh),或已动手开发过一次 Dify 插件。
+
+#### 初始化项目
+
+运行以下命令初始化插件开发项目:
+
+```bash
+dify plugin init
+```
+
+按照提示填写项目的基础信息,选择 `extension` 模板,并且授予 `Apps` 和 `Endpoints` 两个权限。
+
+如需了解更多关于插件反向调用 Dify 平台能力,请参考[反向调用](/plugin_dev_zh/9241-reverse-invocation.zh)。
+
+
+
+#### 1. 编辑配置表单
+
+在这个插件中,需要指定使用哪个 Dify 的 App 进行回复,并且在回复的时候需要使用到 Slack 的 App token,因此需要在插件表单中加上这两个字段。
+
+修改 group 路径下的 yaml 文件,例如 `group/slack.yaml。`表单配置文件的名称由创建插件时填写的基础信息决定,你可以修改对应的 yaml 文件。
+
+**示例代码:**
+
+`slack.yaml`
+
+```yaml
+settings:
+ - name: bot_token
+ type: secret-input
+ required: true
+ label:
+ en_US: Bot Token
+ zh_Hans: Bot Token
+ pt_BR: Token do Bot
+ ja_JP: Bot Token
+ placeholder:
+ en_US: Please input your Bot Token
+ zh_Hans: 请输入你的 Bot Token
+ pt_BR: Por favor, insira seu Token do Bot
+ ja_JP: ボットトークンを入力してください
+ - name: allow_retry
+ type: boolean
+ required: false
+ label:
+ en_US: Allow Retry
+ zh_Hans: 允许重试
+ pt_BR: Permitir Retentativas
+ ja_JP: 再試行を許可
+ default: false
+ - name: app
+ type: app-selector
+ required: true
+ label:
+ en_US: App
+ zh_Hans: 应用
+ pt_BR: App
+ ja_JP: アプリ
+ placeholder:
+ en_US: the app you want to use to answer Slack messages
+ zh_Hans: 你想要用来回答 Slack 消息的应用
+ pt_BR: o app que você deseja usar para responder mensagens do Slack
+ ja_JP: あなたが Slack メッセージに回答するために使用するアプリ
+endpoints:
+ - endpoints/slack.yaml
+```
+
+代码数据结构说明:
+
+```
+ - name: app
+ type: app-selector
+ scope: chat
+```
+
+* type 字段指定为 app-selector 字段
+
+ 用户在使用插件时可以访问某个 Dify 应用并进行消息转发。
+* scope 字段指定为 chat 字段
+
+ 只能使用 `agent` 、`chatbot` 、`chatflow` 等类型的 app。
+
+最后修改 `endpoints/slack.yaml` 文件中的请求路径和请求方式,需要将 method 修改为 POST 方式。
+
+**示例代码:**
+
+`endpoints/slack.yaml`
+
+```yaml
+path: "/"
+method: "POST"
+extra:
+ python:
+ source: "endpoints/slack.py"
+```
+
+#### 2. 编辑功能代码
+
+修改 `endpoints/slack.py`文件,并在其中添加下面的代码:
+
+````python
+import json
+import traceback
+from typing import Mapping
+from werkzeug import Request, Response
+from dify_plugin import Endpoint
+from slack_sdk import WebClient
+from slack_sdk.errors import SlackApiError
+
+
+class SlackEndpoint(Endpoint):
+ def _invoke(self, r: Request, values: Mapping, settings: Mapping) -> Response:
+ """
+ Invokes the endpoint with the given request.
+ """
+ retry_num = r.headers.get("X-Slack-Retry-Num")
+ if (not settings.get("allow_retry") and (r.headers.get("X-Slack-Retry-Reason") == "http_timeout" or ((retry_num is not None and int(retry_num) > 0)))):
+ return Response(status=200, response="ok")
+ data = r.get_json()
+
+ # Handle Slack URL verification challenge
+ if data.get("type") == "url_verification":
+ return Response(
+ response=json.dumps({"challenge": data.get("challenge")}),
+ status=200,
+ content_type="application/json"
+ )
+
+ if (data.get("type") == "event_callback"):
+ event = data.get("event")
+ if (event.get("type") == "app_mention"):
+ message = event.get("text", "")
+ if message.startswith("<@"):
+ message = message.split("> ", 1)[1] if "> " in message else message
+ channel = event.get("channel", "")
+ blocks = event.get("blocks", [])
+ blocks[0]["elements"][0]["elements"] = blocks[0].get("elements")[0].get("elements")[1:]
+ token = settings.get("bot_token")
+ client = WebClient(token=token)
+ try:
+ response = self.session.app.chat.invoke(
+ app_id=settings["app"]["app_id"],
+ query=message,
+ inputs={},
+ response_mode="blocking",
+ )
+ try:
+ blocks[0]["elements"][0]["elements"][0]["text"] = response.get("answer")
+ result = client.chat_postMessage(
+ channel=channel,
+ text=response.get("answer"),
+ blocks=blocks
+ )
+ return Response(
+ status=200,
+ response=json.dumps(result),
+ content_type="application/json"
+ )
+ except SlackApiError as e:
+ raise e
+ except Exception as e:
+ err = traceback.format_exc()
+ return Response(
+ status=200,
+ response="Sorry, I'm having trouble processing your request. Please try again later." + str(err),
+ content_type="text/plain",
+ )
+ else:
+ return Response(status=200, response="ok")
+ else:
+ return Response(status=200, response="ok")
+ else:
+ return Response(status=200, response="ok")
+
+```
+````
+
+为了便于测试,插件功能目前仅能重复用户输入的内容,暂不调用 Dify app。
+
+### 2. 调试插件
+
+前往 Dify 平台,获取 Dify 插件远程调试的连接地址和密钥。
+
+
+
+回到插件项目,复制 `.env.example` 文件并重命名为 `.env`。
+
+```bash
+INSTALL_METHOD=remote
+REMOTE_INSTALL_HOST=remote
+REMOTE_INSTALL_PORT=5003
+REMOTE_INSTALL_KEY=****-****-****-****-****
+```
+
+运行 `python -m main` 命令启动插件。在插件页即可看到该插件已被安装至 Workspace 内。其他团队成员也可以访问该插件。
+
+```bash
+python -m main
+```
+
+#### 设置插件 Endpoint
+
+在 Dify 的插件管理页中找到自动安装的测试插件,新建一个 Endpoint,填写名称、Bot token、选择需要连接的 app。
+
+
+
+保存后将生成一个 POST 请求地址。
+
+
+
+接下来还需要完成 Slack App 的设置。
+
+1. 启用 Event 订阅
+
+
+
+在其中粘贴上文中生成的插件 POST 请求地址。
+
+
+
+勾选 Slack App 所需具备的权限。
+
+
+
+### 3. 验证插件效果
+
+代码使用了 `self.session.app.chat.invoke` 调用 Dify 平台内的 App,并传递了 `app_id` 和 `query` 等信息,最后将 response 的内容返回至 Slack Bot。运行 `python -m main` 命令重启插件进行调试,确认 Slack Bot 是否能够正确输出 Dify App 的答复消息。
+
+
+
+### 4. 打包插件(可选)
+
+确认插件能够正常运行后,可以通过以下命令行工具打包并命名插件。运行以后你可以在当前文件夹发现 `slack_bot.difypkg` 文件,该文件为最终的插件包。关于打包的详细步骤,请参考[打包为本地文件与分享](/plugin_dev_zh/0322-release-by-file.zh)。
+
+```bash
+# Replace ./slack_bot with your actual plugin project path.
+
+dify plugin package ./slack_bot
+```
+
+恭喜,你已完成一个插件的完整开发、测试打包过程!
+
+### 5. 发布插件(可选)
+
+现在可以将它上传至 [Dify Marketplace 仓库](https://github.com/langgenius/dify-plugins) 来发布你的插件了!不过在发布前,请确保你的插件遵循了[发布至 Dify Marketplace](/plugin_dev_zh/0322-release-to-dify-marketplace.zh)中的规范。
+
+## 相关资源
+
+- [插件开发基本概念](/plugin_dev_zh/0111-getting-started-dify-plugin.zh) - 全面了解Dify插件开发
+- [插件开发入门指南](/plugin_dev_zh/0211-getting-started-dify-tool.zh) - 从零开始开发插件
+- [开发 Extension 插件](/plugin_dev_zh/9231-extension-plugin.zh) - 了解扩展插件开发
+- [反向调用 Dify 服务](/plugin_dev_zh/9241-reverse-invocation.zh) - 了解如何调用 Dify 平台能力
+- [反向调用 App](/plugin_dev_zh/9242-reverse-invocation-app.zh) - 了解如何调用平台内的 App
+- [发布插件](/plugin_dev_zh/0321-release-overview.zh) - 学习发布流程
+- [发布至 Dify Marketplace](/plugin_dev_zh/0322-release-to-dify-marketplace.zh) - 市场发布指南
+- [端点详细定义](/plugin_dev_zh/0432-endpoint.zh) - Endpoint 详细定义
+
+### 参考阅读
+
+如果你想要查看完整 Dify 插件的项目代码,请前往 [Github 代码仓库](https://github.com/langgenius/dify-plugins)。除此之外,你可以看到其它插件的完整代码与具体细节。
+
+如果想要了解更多插件,请参考以下内容。
+
+**快速开始:**
+
+* [开发 Extension 插件](/plugin_dev_zh/9231-extension-plugin.zh)
+* [开发 Model 插件](/plugin_dev_zh/0211-getting-started-new-model.zh)
+* [Bundle 类型插件:将多个插件打包](/plugin_dev_zh/9241-bundle.zh)
+
+**插件接口文档:**
+
+* [通过清单文件定义插件信息](/plugin_dev_zh/0411-plugin-info-by-manifest.zh) - Manifest 结构
+* [端点](/plugin_dev_zh/0432-endpoint.zh) - Endpoint 详细定义
+* [反向调用](/plugin_dev_zh/9241-reverse-invocation.zh) - 反向调用 Dify 能力
+* [通用规范](/plugin_dev_zh/0411-general-specifications.zh) - 工具规范
+* [模型架构](/plugin_dev_zh/0412-model-schema.zh) - 模型
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ 通过直接提交修改来帮助改进文档内容
+
+
+ 发现错误或有改进建议?请提交问题反馈
+
+
diff --git a/plugin_dev_zh/0432-endpoint.zh.mdx b/plugin_dev_zh/0432-endpoint.zh.mdx
new file mode 100644
index 00000000..4265764a
--- /dev/null
+++ b/plugin_dev_zh/0432-endpoint.zh.mdx
@@ -0,0 +1,137 @@
+---
+dimensions:
+ type:
+ primary: reference
+ detail: examples
+ level: intermediate
+standard_title: Endpoint
+language: zh
+title: Endpoint
+description: Author Yeuoly,Allen 本文档详细介绍了Dify插件中Endpoint的结构和实现方式,以彩虹猫项目为例。内容包括如何定义Endpoint组、配置接口、实现_invoke方法以及处理请求和响应。文档详细解释了各种YAML配置字段的含义和使用方法。
+---
+
+本文将以[彩虹猫](/plugin_dev_zh/9231-extension-plugin.zh)项目为例,说明插件内的 Endpoint 的结构。Endpoint是插件对外暴露的HTTP接口,可用于与外部系统集成。完整的插件代码请参考 [Github 仓库](https://github.com/langgenius/dify-plugin-sdks/tree/main/python/examples/neko)。
+
+### 组定义
+
+一个 `Endpoint` 组是多个 `Endpoint` 的集合,在 `Dify` 插件内新建 `Endpoint` 时可能需要填写如下配置。
+
+
+
+除了 `Endpoint Name` 外,你可以通过编写组的配置信息来添加新的表单项,点击保存后,你可以看到其中包含的多个接口,它们将使用相同的配置信息。
+
+
+
+#### **结构**
+
+* `settings`(map\[string] [ProviderConfig](/plugin_dev_zh/0411-general-specifications#providerconfig.zh) ):Endpoint 配置定义
+* `endpoints`(list\[string], required):指向具体的 `endpoint` 接口定义
+
+```yaml
+settings:
+ api_key:
+ type: secret-input
+ required: true
+ label:
+ en_US: API key
+ zh_Hans: API key
+ ja_Jp: API key
+ pt_BR: API key
+ placeholder:
+ en_US: Please input your API key
+ zh_Hans: 请输入你的 API key
+ ja_Jp: あなたの API key を入れてください
+ pt_BR: Por favor, insira sua chave API
+endpoints:
+ - endpoints/duck.yaml
+ - endpoints/neko.yaml
+```
+
+### 接口定义
+
+* `path`(string):遵循 werkzeug 接口标准
+* `method`(string):接口方法,仅支持`HEAD` `GET` `POST` `PUT` `DELETE` `OPTIONS`
+* `extra`(object):除基础信息外的配置信息
+ * `python`(object)
+ * `source`(string):实现该接口的源代码
+
+```yaml
+path: "/duck/"
+method: "GET"
+extra:
+ python:
+ source: "endpoints/duck.py"
+```
+
+### 接口实现
+
+需要实现一个继承自 `dify_plugin.Endpoint` 子类,并实现 `_invoke` 方法。
+
+* **输入参数**
+ * `r`(Request):`werkzeug` 中的 `Request` 对象
+ * `values`(Mapping):从 path 中解析到的路径参数
+ * `settings`(Mapping):该 `Endpoint` 的配置信息
+* **返回**
+ * `werkzeug` 中的 `Response` 对象,支持流式返回
+ * 不支持直接返回字符串
+
+示例代码:
+
+```python
+import json
+from typing import Mapping
+from werkzeug import Request, Response
+from dify_plugin import Endpoint
+
+class Duck(Endpoint):
+ def _invoke(self, r: Request, values: Mapping, settings: Mapping) -> Response:
+ """
+ Invokes the endpoint with the given request.
+ """
+ app_id = values["app_id"]
+
+ def generator():
+ yield f"{app_id}
"
+
+ return Response(generator(), status=200, content_type="text/html")
+```
+
+## 注意事项
+
+* Endpoint 只在插件被调用时才会实例化,并不是长期运行的服务
+* 请在开发 Endpoint 时注意安全性,避免执行危险操作
+* Endpoint 可以用于处理 Webhook 回调或提供接口给其他系统连接
+
+如果您正在学习插件开发,建议先阅读[插件开发入门指南](/plugin_dev_zh/0211-getting-started-dify-tool.zh)和[开发者速查表](/plugin_dev_zh/0131-cheatsheet.zh)。
+
+## 相关资源
+
+- [插件开发基本概念](/plugin_dev_zh/0111-getting-started-dify-plugin.zh) - 了解插件开发的整体架构
+- [彩虹猫实例](/plugin_dev_zh/9231-extension-plugin.zh) - 扩展插件开发示例
+- [通用规范定义](/plugin_dev_zh/0411-general-specifications.zh) - 了解 ProviderConfig 等通用结构
+- [Slack 机器人插件开发示例](/plugin_dev_zh/0432-develop-a-slack-bot-plugin.zh) - 另一个插件开发示例
+- [插件开发入门指南](/plugin_dev_zh/0211-getting-started-dify-tool.zh) - 从零开始开发插件
+- [反向调用 Dify 服务](/plugin_dev_zh/9241-reverse-invocation.zh) - 了解如何使用反向调用功能
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ 通过直接提交修改来帮助改进文档内容
+
+
+ 发现错误或有改进建议?请提交问题反馈
+
+
diff --git a/plugin_dev_zh/9231-extension-plugin.zh.mdx b/plugin_dev_zh/9231-extension-plugin.zh.mdx
new file mode 100644
index 00000000..e47d4ed0
--- /dev/null
+++ b/plugin_dev_zh/9231-extension-plugin.zh.mdx
@@ -0,0 +1,307 @@
+---
+dimensions:
+ type:
+ primary: implementation
+ detail: high
+ level: beginner
+standard_title: Extension Plugin
+language: zh
+title: Extension 插件
+description: 本文档提供了开发Extension类型插件的完整教程,详细介绍了环境准备、创建项目、定义插件请求入口、编写功能代码、调试、打包发布等全过程。示例项目是一个彩虹猫插件,展示了如何通过Extension插件处理HTTP请求并提供网页服务。
+---
+
+本文将引导你快速开发一个 Extension 类型的插件,以帮助你了解插件开发的基础流程。
+
+### 前置准备
+
+* Dify 插件脚手架工具
+* Python 环境,版本号 ≥ 3.12
+
+关于如何准备插件开发的脚手架工具,详细说明请参考[初始化开发工具](initialize-development-tools.md)。
+
+### 创建新项目
+
+在当前路径下,运行脚手架命令行工具,创建一个新的 dify 插件项目。
+
+```
+./dify-plugin-darwin-arm64 plugin init
+```
+
+如果你已将该二进制文件重命名为了 `dify` 并拷贝到了 `/usr/local/bin` 路径下,可以运行以下命令创建新的插件项目:
+
+```bash
+dify plugin init
+```
+
+### **填写插件信息**
+
+按照提示配置插件名称、作者信息与插件描述。如果你是团队协作,也可以将作者填写为组织名。
+
+> 插件名称长度必须为 1-128 个字符,并且只能包含字母、数字、破折号和下划线。
+
+
+
+填写完成后,在插件开发语言环节中选择 Python。
+
+
+
+### 3. 选择插件类型并初始化项目模板
+
+脚手架工具内的所有模板均已提供完整的代码项目。出于演示说明,本文将以 `Extension` 类型插件模板作为示例。对于已熟悉插件的开发者而言,无需借助模板,可参考[接口文档](../../schema-definition/)指引完成不同类型的插件开发。
+
+
+
+#### 配置插件权限
+
+插件还需要读取 Dify 主平台的权限才能正常连接。需要为该示例插件授予以下权限:
+
+* Tools
+* LLMs
+* Apps
+* 启用持久化存储 Storage,分配默认大小存储
+* 允许注册 Endpoint
+
+> 在终端内使用方向键选择权限,使用 “Tab” 按钮授予权限。
+
+勾选所有权限项后,轻点回车完成插件的创建。系统将自动生成插件项目代码。
+
+
+
+插件的基础文件结构包含以下内容:
+
+```
+.
+├── GUIDE.md
+├── README.md
+├── _assets
+│ └── icon.svg
+├── endpoints
+│ ├── your-project.py
+│ └── your-project.yaml
+├── group
+│ └── your-project.yaml
+├── main.py
+├── manifest.yaml
+└── requirements.txt
+```
+
+* `GUIDE.md` 一个简短的引导教程,带领你完成插件的编写流程。
+* `README.md` 关于当前插件的简介信息,你需要把有关该插件的介绍和使用方法填写至该文件内。
+* `_assets` 存储所有与当前插件相关的多媒体文件。
+* `endpoints` 按照 cli 中的引导创建的一个 `Extension` 类型插件模板,该目录存放所有 Endpoint 的功能实现代码。
+* `group` 指定密钥类型、多语言设置以及 API 定义的文件路径。
+* `main.py` 整个项目的入口文件。
+* `manifest.yaml` 整个插件的基础配置文件,包含该插件需要什么权限、是什么类型的扩展等配置信息。
+* `requirements.txt` 存放 Python 环境的依赖项。
+
+### 开发插件
+
+#### 1. 定义插件的请求入口 Endpoint
+
+编辑 `endpoints/test_plugin.yaml` ,参考以下代码进行修改:
+
+```yaml
+path: "/neko"
+method: "GET"
+extra:
+ python:
+ source: "endpoints/test_plugin.py"
+```
+
+该代码的意图是定义该插件的入口路径为 `/neko`,请求方法为 GET 类型。插件的功能实现代码为 `endpoints/test_plugin.py` 文件。
+
+#### 2. 编写插件功能
+
+插件功能:请求服务,输出一只彩虹猫。
+
+编写插件的功能实现代码 `endpoints/test_plugin.py` 文件,参考以下示例代码:
+
+```python
+from typing import Mapping
+from werkzeug import Request, Response
+from flask import Flask, render_template_string
+from dify_plugin import Endpoint
+
+app = Flask(__name__)
+
+class NekoEndpoint(Endpoint):
+ def _invoke(self, r: Request, values: Mapping, settings: Mapping) -> Response:
+ ascii_art = '''
+⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛⬛️⬜️⬜️⬜️⬜️⬜⬜️⬜️️
+🟥🟥⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️🟥🟥🟥🟥🟥🟥🟥🟥⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬛🥧🥧🥧🥧🥧🥧🥧🥧🥧🥧🥧🥧🥧🥧🥧🥧🥧⬛️⬜️⬜️⬜️⬜️⬜⬜️️
+🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥⬛️🥧🥧🥧💟💟💟💟💟💟💟💟💟💟💟💟💟🥧🥧🥧⬛️⬜️⬜️⬜️⬜⬜️️
+🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥⬛️🥧🥧💟💟💟💟💟💟🍓💟💟🍓💟💟💟💟💟🥧🥧⬛️⬜️⬜️⬜️⬜️⬜️️
+🟧🟧🟥🟥🟥🟥🟥🟥🟥🟥🟧🟧🟧🟧🟧🟧🟧🟧🟥🟥🟥🟥🟥🟥🟥⬛🥧💟💟🍓💟💟💟💟💟💟💟💟💟💟💟💟💟💟🥧⬛️⬜️⬜️⬜️⬜⬜️️
+🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧⬛️🥧💟💟💟💟💟💟💟💟💟💟⬛️⬛️💟💟🍓💟💟🥧⬛️⬜️⬛️️⬛️️⬜⬜️️
+🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧🟧⬛️🥧💟💟💟💟💟💟💟💟💟⬛️🌫🌫⬛💟💟💟💟🥧⬛️⬛️🌫🌫⬛⬜️️
+🟨🟨🟧🟧🟧🟧🟧🟧🟧🟧🟨🟨🟨🟨🟨🟨🟨🟨🟧⬛️⬛️⬛️⬛️🟧🟧⬛️🥧💟💟💟💟💟💟🍓💟💟⬛️🌫🌫🌫⬛💟💟💟🥧⬛️🌫🌫🌫⬛⬜️️
+🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨⬛️🌫🌫⬛️⬛️🟧⬛️🥧💟💟💟💟💟💟💟💟💟⬛️🌫🌫🌫🌫⬛️⬛️⬛️⬛️🌫🌫🌫🌫⬛⬜️️
+🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨⬛️⬛️🌫🌫⬛️⬛️⬛️🥧💟💟💟🍓💟💟💟💟💟⬛️🌫🌫🌫🌫🌫🌫🌫🌫🌫🌫🌫🌫⬛⬜️️
+🟩🟩🟨🟨🟨🟨🟨🟨🟨🟨🟩🟩🟩🟩🟩🟩🟩🟩🟨🟨⬛⬛️🌫🌫⬛️⬛️🥧💟💟💟💟💟💟💟🍓⬛️🌫🌫🌫🌫🌫🌫🌫🌫🌫🌫🌫🌫🌫🌫⬛️
+🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩⬛️⬛️🌫🌫⬛️🥧💟🍓💟💟💟💟💟💟⬛️🌫🌫🌫⬜️⬛️🌫🌫🌫🌫🌫⬜️⬛️🌫🌫⬛️
+️🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩⬛️⬛️⬛️⬛️🥧💟💟💟💟💟💟💟💟⬛️🌫🌫🌫⬛️⬛️🌫🌫🌫⬛️🌫⬛️⬛️🌫🌫⬛️
+🟦🟦🟩🟩🟩🟩🟩🟩🟩🟩🟦🟦🟦🟦🟦🟦🟦🟦🟩🟩🟩🟩🟩🟩⬛️⬛️🥧💟💟💟💟💟🍓💟💟⬛🌫🟥🟥🌫🌫🌫🌫🌫🌫🌫🌫🌫🟥🟥⬛️
+🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦⬛️🥧🥧💟🍓💟💟💟💟💟⬛️🌫🟥🟥🌫⬛️🌫🌫⬛️🌫🌫⬛️🌫🟥🟥⬛️
+🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦⬛️🥧🥧🥧💟💟💟💟💟💟💟⬛️🌫🌫🌫⬛️⬛️⬛️⬛️⬛️⬛️⬛️🌫🌫⬛️⬜️
+🟪🟪🟦🟦🟦🟦🟦🟦🟦🟦🟪🟪🟪🟪🟪🟪🟪🟪🟦🟦🟦🟦🟦🟦⬛️⬛️⬛️🥧🥧🥧🥧🥧🥧🥧🥧🥧🥧⬛️🌫🌫🌫🌫🌫🌫🌫🌫🌫🌫⬛️⬜️⬜️
+🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪⬛️🌫🌫🌫⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬜️⬜️⬜️
+🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪🟪⬛️🌫🌫⬛️⬛️⬜️⬛️🌫🌫⬛️⬜️⬜️⬜️⬜️⬜️⬛️🌫🌫⬛️⬜️⬛️🌫🌫⬛️⬜️⬜️⬜️⬜️
+⬜️⬜️🟪🟪🟪🟪🟪🟪🟪🟪⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬜️🟪🟪🟪🟪🟪⬛️⬛️⬛️⬛⬜️⬜️⬛️⬛️⬛️⬜️⬜️⬜️⬜️⬜️⬜️⬜️⬛️⬛️⬛️⬜️⬜️⬛️⬛️⬜️⬜️⬜️⬜️⬜️️
+ '''
+ ascii_art_lines = ascii_art.strip().split('\n')
+ with app.app_context():
+ return Response(render_template_string('''
+
+
+
+
+
+
+
+
+
+
+ ''', ascii_art_lines=ascii_art_lines), status=200, content_type="text/html")
+```
+
+运行此代码需要先安装以下 Python 依赖包:
+
+```python
+pip install werkzeug
+pip install flask
+pip install dify-plugin
+```
+
+### 调试插件
+
+接下来需测试插件是否可以正常运行。Dify 提供远程调试方式,前往“插件管理”页获取调试 Key 和远程服务器地址。
+
+
+
+回到插件项目,拷贝 `.env.example` 文件并重命名为 `.env`,将获取的远程服务器地址和调试 Key 等信息填入其中。
+
+`.env` 文件
+
+```bash
+INSTALL_METHOD=remote
+REMOTE_INSTALL_HOST=remote
+REMOTE_INSTALL_PORT=5003
+REMOTE_INSTALL_KEY=****-****-****-****-****
+```
+
+运行 `python -m main` 命令启动插件。在插件页即可看到该插件已被安装至 Workspace 内。其他团队成员也可以访问该插件。
+
+
+
+在插件内新增 Endpoint,随意填写名称和 `api_key` 等信息。访问自动生成的 URL,即可看到由插件提供的网页服务。
+
+
+
+### 打包插件
+
+确认插件能够正常运行后,可以通过以下命令行工具打包并命名插件。运行以后你可以在当前文件夹发现 `neko.difypkg` 文件,该文件为最终的插件包。
+
+```bash
+# 将 ./neko 替换为插件项目的实际路径
+
+dify plugin package ./neko
+```
+
+恭喜,你已完成一个插件的完整开发、测试打包过程!
+
+### 发布插件
+
+现在可以将它上传至 [Dify Plugins 代码仓库](https://github.com/langgenius/dify-plugins) 来发布你的插件了!上传前,请确保你的插件遵循了[插件发布规范](https://docs.dify.ai/zh-hans/plugins/publish-plugins/publish-to-dify-marketplace)。审核通过后,代码将合并至主分支并自动上线至 [Dify Marketplace](https://marketplace.dify.ai/)。
+
+### 探索更多
+
+**快速开始:**
+
+* [Tool 插件:Google Search](tool-plugin.md)
+* [Model 插件](model-plugin/)
+* [Bundle 插件:将多个插件打包](bundle.md)
+
+**插件接口文档:**
+
+* [Manifest](../../schema-definition/manifest.md) 结构
+* [Endpoint](../../schema-definition/endpoint.md) 详细定义
+* [反向调用 Dify 能力](../../schema-definition/reverse-invocation-of-the-dify-service/)
+* [工具](../../schema-definition/tool.md)
+* [模型](../../schema-definition/model/)
+* [扩展 Agent 策略](../../schema-definition/agent.md)
+
+**最佳实践:**
+
+[开发 Slack Bot 插件](../../best-practice/develop-a-slack-bot-plugin.md)
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ 通过直接提交修改来帮助改进文档内容
+
+
+ 发现错误或有改进建议?请提交问题反馈
+
+
diff --git a/plugin_dev_zh/9232-agent.zh.mdx b/plugin_dev_zh/9232-agent.zh.mdx
new file mode 100644
index 00000000..82566f68
--- /dev/null
+++ b/plugin_dev_zh/9232-agent.zh.mdx
@@ -0,0 +1,449 @@
+---
+dimensions:
+ type:
+ primary: implementation
+ detail: high
+ level: intermediate
+standard_title: Agent
+language: zh
+title: Agent
+description: 本文档详细介绍了Dify的Agent策略插件开发流程,包括在Manifest文件中添加Agent策略字段、定义Agent供应商以及实现Agent策略的核心步骤。文档详细介绍了如何获取参数、调用模型、调用工具以及生成和管理日志的完整示例代码。
+---
+
+Agent 策略是一个定义了标准输入内容与输出格式的可扩展模板。通过开发具体 Agent 策略接口的功能代码,你可以实现众多不同的 Agent 策略如 CoT(思维链)/ ToT(思维树)/ GoT(思维图)/ BoT(思维骨架),实现一些诸如 [Sementic Kernel](https://learn.microsoft.com/en-us/semantic-kernel/overview/) 的复杂策略。
+
+### 在 Manifest 内添加字段
+
+在插件中添加 Agent 策略需要在 `manifest.yaml` 文件内新增 `plugins.agent_strategies` 字段,并且也需要定义 Agent 供应商,示例代码如下
+
+```yaml
+version: 0.0.2
+type: plugin
+author: "langgenius"
+name: "agent"
+plugins:
+ agent_strategies:
+ - "provider/agent.yaml"
+```
+
+此处已省去 `manifest` 文件内部分无关的字段。如需了解 Manifest 的详细格式,请参考 [通过清单文件定义插件信息](/plugin_dev_zh/0411-plugin-info-by-manifest.zh) 文档。
+
+### 定义 Agent 供应商
+
+随后,你需要新建 `agent.yaml` 文件并填写基础的 Agent 供应商信息。
+
+```yaml
+identity:
+ author: langgenius
+ name: agent
+ label:
+ en_US: Agent
+ zh_Hans: Agent
+ pt_BR: Agent
+ description:
+ en_US: Agent
+ zh_Hans: Agent
+ pt_BR: Agent
+ icon: icon.svg
+strategies:
+ - strategies/function_calling.yaml
+```
+
+其主要包含一些描述性质的基础内容,并且指明当前供应商包含哪些策略。在上述示例代码中仅指定了一个最基础的 `function_calling.yaml` 策略文件。
+
+### 定义并实现 Agent 策略
+
+#### 定义
+
+接下来需要定义能够实现 Agent 策略的代码。新建一个 `function_calling.yaml` 文件:
+
+```yaml
+identity:
+ name: function_calling
+ author: Dify
+ label:
+ en_US: FunctionCalling
+ zh_Hans: FunctionCalling
+ pt_BR: FunctionCalling
+description:
+ en_US: Function Calling is a basic strategy for agent, model will use the tools provided to perform the task.
+ zh_Hans: Function Calling 是一个基本的 Agent 策略,模型将使用提供的工具来执行任务。
+ pt_BR: Function Calling is a basic strategy for agent, model will use the tools provided to perform the task.
+parameters:
+ - name: model
+ type: model-selector
+ scope: tool-call&llm
+ required: true
+ label:
+ en_US: Model
+ zh_Hans: 模型
+ pt_BR: Model
+ - name: tools
+ type: array[tools]
+ required: true
+ label:
+ en_US: Tools list
+ zh_Hans: 工具列表
+ pt_BR: Tools list
+ - name: query
+ type: string
+ required: true
+ label:
+ en_US: Query
+ zh_Hans: 用户提问
+ pt_BR: Query
+ - name: max_iterations
+ type: number
+ required: false
+ default: 5
+ label:
+ en_US: Max Iterations
+ zh_Hans: 最大迭代次数
+ pt_BR: Max Iterations
+ max: 50
+ min: 1
+extra:
+ python:
+ source: strategies/function_calling.py
+```
+
+代码格式类似 [`Tool` 标准格式](tool.md),定义了 `model` `tools` `query` `max_iterations` 等一共四个参数,以便于实现最基础的 Agent 策略。该代码的含义是可以允许用户选择模型和需要使用的工具,配置最大迭代次数并最终传入一个 query 后开始执行 Agent。
+
+#### 编写功能实现代码
+
+**获取参数**
+
+根据上文定义的四个参数,其中 model 类型参数为`model-selector`,tool 类型参数为特殊的 `array[tools]。`在参数中获取到的形式可以通过 SDK 中内置的 `AgentModelConfig` 和 `list[ToolEntity]`进行转换。
+
+```python
+from dify_plugin.interfaces.agent import AgentModelConfig, AgentStrategy, ToolEntity
+
+class FunctionCallingParams(BaseModel):
+ query: str
+ model: AgentModelConfig
+ tools: list[ToolEntity] | None
+ maximum_iterations: int = 3
+
+ class FunctionCallingAgentStrategy(AgentStrategy):
+ def _invoke(self, parameters: dict[str, Any]) -> Generator[AgentInvokeMessage]:
+ """
+ Run FunctionCall agent application
+ """
+ fc_params = FunctionCallingParams(**parameters)
+```
+
+**调用模型**
+
+调用指定模型是 Agent 插件中必不可少的能力。通过 SDK 中的 `session.model.invoke()` 函数调用模型。可以从 model 中获取所需的传入参数。
+
+invoke model 的方法签名示例代码:
+
+```python
+def invoke(
+ self,
+ model_config: LLMModelConfig,
+ prompt_messages: list[PromptMessage],
+ tools: list[PromptMessageTool] | None = None,
+ stop: list[str] | None = None,
+ stream: bool = True,
+ ) -> Generator[LLMResultChunk, None, None] | LLMResult:
+```
+
+需要传入模型信息 `model_config`,prompt 信息 `prompt_messages` 和工具信息 `tools`。
+
+其中`prompt_messages`参数可以参考以下示例代码调用;而`tool_messages`则需要进行一定的转换。
+
+请参考 invoke model 使用方法的示例代码:
+
+```python
+from collections.abc import Generator
+from typing import Any
+
+from pydantic import BaseModel
+
+from dify_plugin.entities.agent import AgentInvokeMessage
+from dify_plugin.entities.model.llm import LLMModelConfig
+from dify_plugin.entities.model.message import (
+ PromptMessageTool,
+ SystemPromptMessage,
+ UserPromptMessage,
+)
+from dify_plugin.entities.tool import ToolParameter
+from dify_plugin.interfaces.agent import AgentModelConfig, AgentStrategy, ToolEntity
+
+class FunctionCallingParams(BaseModel):
+ query: str
+ instruction: str | None
+ model: AgentModelConfig
+ tools: list[ToolEntity] | None
+ maximum_iterations: int = 3
+
+class FunctionCallingAgentStrategy(AgentStrategy):
+ def _invoke(self, parameters: dict[str, Any]) -> Generator[AgentInvokeMessage]:
+ """
+ Run FunctionCall agent application
+ """
+ # init params
+ fc_params = FunctionCallingParams(**parameters)
+ query = fc_params.query
+ model = fc_params.model
+ stop = fc_params.model.completion_params.get("stop", []) if fc_params.model.completion_params else []
+ prompt_messages = [
+ SystemPromptMessage(content="your system prompt message"),
+ UserPromptMessage(content=query),
+ ]
+ tools = fc_params.tools
+ prompt_messages_tools = self._init_prompt_tools(tools)
+
+ # invoke llm
+ chunks = self.session.model.llm.invoke(
+ model_config=LLMModelConfig(**model.model_dump(mode="json")),
+ prompt_messages=prompt_messages,
+ stream=True,
+ stop=stop,
+ tools=prompt_messages_tools,
+ )
+
+ def _init_prompt_tools(self, tools: list[ToolEntity] | None) -> list[PromptMessageTool]:
+ """
+ Init tools
+ """
+
+ prompt_messages_tools = []
+ for tool in tools or []:
+ try:
+ prompt_tool = self._convert_tool_to_prompt_message_tool(tool)
+ except Exception:
+ # api tool may be deleted
+ continue
+
+ # save prompt tool
+ prompt_messages_tools.append(prompt_tool)
+
+ return prompt_messages_tools
+
+ def _convert_tool_to_prompt_message_tool(self, tool: ToolEntity) -> PromptMessageTool:
+ """
+ convert tool to prompt message tool
+ """
+ message_tool = PromptMessageTool(
+ name=tool.identity.name,
+ description=tool.description.llm if tool.description else "",
+ parameters={
+ "type": "object",
+ "properties": {},
+ "required": [],
+ },
+ )
+
+ parameters = tool.parameters
+ for parameter in parameters:
+ if parameter.form != ToolParameter.ToolParameterForm.LLM:
+ continue
+
+ parameter_type = parameter.type
+ if parameter.type in {
+ ToolParameter.ToolParameterType.FILE,
+ ToolParameter.ToolParameterType.FILES,
+ }:
+ continue
+ enum = []
+ if parameter.type == ToolParameter.ToolParameterType.SELECT:
+ enum = [option.value for option in parameter.options] if parameter.options else []
+
+ message_tool.parameters["properties"][parameter.name] = {
+ "type": parameter_type,
+ "description": parameter.llm_description or "",
+ }
+
+ if len(enum) > 0:
+ message_tool.parameters["properties"][parameter.name]["enum"] = enum
+
+ if parameter.required:
+ message_tool.parameters["required"].append(parameter.name)
+
+ return message_tool
+
+```
+
+**调用工具**
+
+调用工具同样是 Agent 插件必不可少的能力。可以通过`self.session.tool.invoke()`进行调用。invoke tool 的方法签名示例代码:
+
+```python
+def invoke(
+ self,
+ provider_type: ToolProviderType,
+ provider: str,
+ tool_name: str,
+ parameters: dict[str, Any],
+ ) -> Generator[ToolInvokeMessage, None, None]
+```
+
+必须的参数有 `provider_type`, `provider`, `tool_name`, `parameters`。其中 `tool_name` 和`parameters`在 Function Calling 中往往都由 LLM 生成。使用 invoke tool 的示例代码:
+
+```python
+from dify_plugin.entities.tool import ToolProviderType
+
+class FunctionCallingAgentStrategy(AgentStrategy):
+ def _invoke(self, parameters: dict[str, Any]) -> Generator[AgentInvokeMessage]:
+ """
+ Run FunctionCall agent application
+ """
+ fc_params = FunctionCallingParams(**parameters)
+
+ # tool_call_name and tool_call_args parameter is obtained from the output of LLM
+ tool_instances = {tool.identity.name: tool for tool in fc_params.tools} if fc_params.tools else {}
+ tool_instance = tool_instances[tool_call_name]
+ tool_invoke_responses = self.session.tool.invoke(
+ provider_type=ToolProviderType.BUILT_IN,
+ provider=tool_instance.identity.provider,
+ tool_name=tool_instance.identity.name,
+ # add the default value
+ parameters={**tool_instance.runtime_parameters, **tool_call_args},
+ )
+```
+
+`self.session.tool.invoke()`函数的输出是一个 Generator,代表着同样需要进行流式解析。
+
+解析方法请参考以下函数:
+
+```python
+import json
+from collections.abc import Generator
+from typing import cast
+
+from dify_plugin.entities.agent import AgentInvokeMessage
+from dify_plugin.entities.tool import ToolInvokeMessage
+
+def parse_invoke_response(tool_invoke_responses: Generator[AgentInvokeMessage]) -> str:
+ result = ""
+ for response in tool_invoke_responses:
+ if response.type == ToolInvokeMessage.MessageType.TEXT:
+ result += cast(ToolInvokeMessage.TextMessage, response.message).text
+ elif response.type == ToolInvokeMessage.MessageType.LINK:
+ result += (
+ f"result link: {cast(ToolInvokeMessage.TextMessage, response.message).text}."
+ + " please tell user to check it."
+ )
+ elif response.type in {
+ ToolInvokeMessage.MessageType.IMAGE_LINK,
+ ToolInvokeMessage.MessageType.IMAGE,
+ }:
+ result += (
+ "image has been created and sent to user already, "
+ + "you do not need to create it, just tell the user to check it now."
+ )
+ elif response.type == ToolInvokeMessage.MessageType.JSON:
+ text = json.dumps(cast(ToolInvokeMessage.JsonMessage, response.message).json_object, ensure_ascii=False)
+ result += f"tool response: {text}."
+ else:
+ result += f"tool response: {response.message!r}."
+ return result
+```
+
+#### Log
+
+如果你希望看到 Agent 思考的过程,除了通过查看正常返回的消息以外,还可以使用专门的接口实现以树状结构展示整个 Agent 的思考过程。
+
+**创建日志**
+
+* 该接口创建并返回一个 `AgentLogMessage`,该 Message 表示日志中树的一个节点。
+* 如果有传入 parent 则表示该节点具备父节点。
+* 状态默认为"Success"(成功)。但如果你想要更好地展示任务执行过程,可以先设置状态为"start"来显示"正在执行"的日志,等任务完成后再将该日志的状态更新为"Success"。这样用户就能清楚地看到任务从开始到完成的整个过程。
+* label 将用于最终给用户展示日志标题。
+
+```python
+ def create_log_message(
+ self,
+ label: str,
+ data: Mapping[str, Any],
+ status: AgentInvokeMessage.LogMessage.LogStatus = AgentInvokeMessage.LogMessage.LogStatus.SUCCESS,
+ parent: AgentInvokeMessage | None = None,
+ ) -> AgentInvokeMessage
+```
+
+**完成日志**
+
+如果在前一个步骤选择了 start 状态作为初始状态,可以使用完成日志的接口来更改状态。
+
+```python
+ def finish_log_message(
+ self,
+ log: AgentInvokeMessage,
+ status: AgentInvokeMessage.LogMessage.LogStatus = AgentInvokeMessage.LogMessage.LogStatus.SUCCESS,
+ error: Optional[str] = None,
+ ) -> AgentInvokeMessage
+```
+
+**实例**
+
+这个示例展示了一个简单的两步执行过程:首先输出一条"正在思考"的状态日志,然后完成实际的任务处理。
+
+```python
+class FunctionCallingAgentStrategy(AgentStrategy):
+ def _invoke(self, parameters: dict[str, Any]) -> Generator[AgentInvokeMessage]:
+ thinking_log = self.create_log_message(
+ data={
+ "Query": parameters.get("query"),
+ },
+ label="Thinking",
+ status=AgentInvokeMessage.LogMessage.LogStatus.START,
+ )
+
+ yield thinking_log
+
+ llm_response = self.session.model.llm.invoke(
+ model_config=LLMModelConfig(
+ provider="openai",
+ model="gpt-4o-mini",
+ mode="chat",
+ completion_params={},
+ ),
+ prompt_messages=[
+ SystemPromptMessage(content="you are a helpful assistant"),
+ UserPromptMessage(content=parameters.get("query")),
+ ],
+ stream=False,
+ tools=[],
+ )
+
+ thinking_log = self.finish_log_message(
+ log=thinking_log,
+ )
+
+ yield thinking_log
+
+ yield self.create_text_message(text=llm_response.message.content)
+```
+
+## 相关资源
+
+- [插件开发基本概念](/plugin_dev_zh/0111-getting-started-dify-plugin.zh) - 了解插件开发的整体架构
+- [Agent 策略插件开发示例](/plugin_dev_zh/9433-agent-strategy-plugin.zh) - 实际的 Agent 策略插件开发示例
+- [通过清单文件定义插件信息](/plugin_dev_zh/0411-plugin-info-by-manifest.zh) - 了解 Manifest 文件的详细格式
+- [反向调用 Model](/plugin_dev_zh/9242-reverse-invocation-model.zh) - 了解如何调用平台内的模型能力
+- [反向调用 Tool](/plugin_dev_zh/9242-reverse-invocation-tool.zh) - 了解如何调用其它插件
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ 通过直接提交修改来帮助改进文档内容
+
+
+ 发现错误或有改进建议?请提交问题反馈
+
+
diff --git a/plugin_dev_zh/9241-bundle.zh.mdx b/plugin_dev_zh/9241-bundle.zh.mdx
new file mode 100644
index 00000000..db6d6ca0
--- /dev/null
+++ b/plugin_dev_zh/9241-bundle.zh.mdx
@@ -0,0 +1,117 @@
+---
+dimensions:
+ type:
+ primary: implementation
+ detail: advanced
+ level: beginner
+standard_title: Bundle
+language: zh
+title: Bundle 插件包
+description: 本文档介绍了Bundle插件包的概念和开发方法。Bundle插件包可以将多个插件集合到一起,支持三种类型(Marketplace类型、GitHub类型和Package类型)。文档详细说明了创建Bundle项目、添加不同类型依赖以及打包Bundle项目的全过程。
+---
+
+Bundle 插件包是多个插件的集合。它可以将多个插件打包在一个插件内,以达到批量安装插件的效果,同时提供更强大的服务。
+
+你可以通过 Dify cli 工具将多个插件打包为 Bundle。Bundle 插件包提供三种类型,分别为:
+
+* `Marketplace` 类型。储存了插件的 id 与版本信息。导入时会通过 Dify Marketplace 下载具体的插件包。
+* `GitHub` 类型。储存了 GitHub 的仓库地址、 release 版本号和 asset 文件名。导入时 Dify 会访问对应的 GitHub 仓库下载插件包。
+* `Package` 类型。插件包会直接被储存在 Bundle 中。它不储存引用源,但可能会造成 Bundle 包体积较大的问题。
+
+### 前置准备
+
+* Dify 插件脚手架工具
+* Python 环境,版本号 ≥ 3.10
+
+关于如何准备插件开发的脚手架工具,详细说明请参考[初始化开发工具](initialize-development-tools.md)。
+
+### 创建 Bundle 项目
+
+在当前路径下,运行脚手架命令行工具,创建一个新的插件包项目。
+
+```bash
+./dify-plugin-darwin-arm64 bundle init
+```
+
+如果你已将该二进制文件重命名为了 `dify` 并拷贝到了 `/usr/local/bin` 路径下,可以运行以下命令创建新的插件项目:
+
+```bash
+dify bundle init
+```
+
+#### 1. 填写插件信息
+
+按照提示配置插件名称、作者信息与插件描述。如果你是团队协作,也可以将作者填写为组织名。
+
+> 名称长度必须为 1-128 个字符,并且只能包含字母、数字、破折号和下划线。
+
+
+
+填写信息后敲击回车,将自动创建 Bundle 插件项目目录。
+
+
+
+#### 2. 添加依赖
+
+* **Marketplace**
+
+执行以下命令:
+
+```bash
+dify-plugin bundle append marketplace . --marketplace_pattern=langgenius/openai:0.0.1
+```
+
+其中 marketplace\_pattern 为插件在 marketplace 中的引用,格式为 `组织名/插件名:版本号`。
+
+* **Github**
+
+执行以下命令:
+
+```bash
+dify-plugin bundle append github . --repo_pattern=langgenius/openai:0.0.1/openai.difypkg
+```
+
+其中 repo\_pattern 为插件在 github 中的引用,格式为 `组织名/仓库名:release/附件名`。
+
+* **Package**
+
+执行以下命令:
+
+```bash
+dify-plugin bundle append package . --package_path=./openai.difypkg
+```
+
+其中 package\_path 为插件包的目录。
+
+### 打包 Bundle 项目
+
+运行以下命令打包 Bundle 插件:
+
+```bash
+dify-plugin bundle package ./bundle
+```
+
+执行命令后,当前目录下将自动创建 `bundle.difybndl` 文件,该文件即为最后的打包结果。
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ 通过直接提交修改来帮助改进文档内容
+
+
+ 发现错误或有改进建议?请提交问题反馈
+
+
diff --git a/plugin_dev_zh/9241-reverse-invocation.zh.mdx b/plugin_dev_zh/9241-reverse-invocation.zh.mdx
new file mode 100644
index 00000000..0fdae041
--- /dev/null
+++ b/plugin_dev_zh/9241-reverse-invocation.zh.mdx
@@ -0,0 +1,58 @@
+---
+dimensions:
+ type:
+ primary: implementation
+ detail: advanced
+ level: beginner
+standard_title: Reverse Invocation
+language: zh
+title: 反向调用 Dify 服务
+description: 本文档简要介绍了Dify插件的反向调用能力,即插件可以调用Dify主平台内的指定服务。文档列出了四类可被调用的模块:App(访问App数据)、Model(调用平台内的模型能力)、Tool(调用平台内的其他工具插件)和Node(调用Chatflow/Workflow应用内的节点)。
+---
+
+插件可以自由调用 Dify 主平台内的部分服务,用以提升插件的能力。
+
+### 可被调用的 Dify 模块
+
+* [App](/plugin_dev_zh/9242-reverse-invocation-app.zh)
+
+ 插件能够访问 Dify 平台内 App 的数据。
+* [Model](/plugin_dev_zh/9242-reverse-invocation-model.zh)
+
+ 插件能够反向调用 Dify 平台内的 LLM 能力,包括平台内的所有模型类型与功能,例如 TTS、Rerank 等。
+* [Tool](/plugin_dev_zh/9242-reverse-invocation-tool.zh)
+
+ 插件能够调用 Dify 平台内的其它工具类型插件。
+* [Node](/plugin_dev_zh/9243-reverse-invocation-node.zh)
+
+ 插件能够调用 Dify 平台内某个 Chatflow/Workflow 应用内的节点。
+
+## 相关资源
+
+- [开发 Extension 插件](/plugin_dev_zh/9231-extension-plugin.zh) - 学习如何开发与外部系统集成的插件
+- [开发 Slack Bot 插件](/plugin_dev_zh/0432-develop-a-slack-bot-plugin.zh) - 使用反向调用实现与 Slack 平台集成的实例
+- [Bundle 类型插件](/plugin_dev_zh/9241-bundle.zh) - 了解如何打包多个使用反向调用的插件
+- [使用持久化存储](/plugin_dev_zh/0411-persistent-storage-kv.zh) - 通过 KV 存储提升插件能力
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ 通过直接提交修改来帮助改进文档内容
+
+
+ 发现错误或有改进建议?请提交问题反馈
+
+
diff --git a/plugin_dev_zh/9242-reverse-invocation-app.zh.mdx b/plugin_dev_zh/9242-reverse-invocation-app.zh.mdx
new file mode 100644
index 00000000..a06e9f0a
--- /dev/null
+++ b/plugin_dev_zh/9242-reverse-invocation-app.zh.mdx
@@ -0,0 +1,146 @@
+---
+dimensions:
+ type:
+ primary: implementation
+ detail: advanced
+ level: intermediate
+standard_title: Reverse Invocation App
+language: zh
+title: App
+description: 本文档详细介绍了插件如何反向调用Dify平台中的App服务。内容包括三种不同类型的接口:聊天接口(适用于Chatbot/Agent/Chatflow类型应用)、Workflow接口和Completion接口,并提供了每种接口的入口方式、调用规范以及实际的调用示例代码。
+---
+
+反向调用 App 指的是插件能够访问 Dify 中的 App 数据。该模块同时支持流式与非流式的 App 调用。如果你对反向调用的基本概念还不熟悉,请先阅读[反向调用 Dify 服务](/plugin_dev_zh/9241-reverse-invocation.zh)。
+
+**接口类型:**
+
+* 对于 `Chatbot/Agent/Chatflow` 类型应用而言,它们都属于聊天类型的应用,因此拥有相同类型的输入参数和输出参数,因此可被统一视为**聊天接口。**
+* 对于 Workflow 应用而言,它单独占据一个 **Workflow 接口。**
+* 对于 Completion(文本生成应用)应用而言,它单独占据一个 **Completion 接口**。
+
+请注意,插件仅允许访问插件所在的 Workspace 中的 App。
+
+### 调用聊天接口
+
+#### **入口**
+
+```python
+ self.session.app.chat
+```
+
+#### **接口规范**
+
+```python
+ def invoke(
+ self,
+ app_id: str,
+ inputs: dict,
+ response_mode: Literal["streaming", "blocking"],
+ conversation_id: str,
+ files: list,
+ ) -> Generator[dict, None, None] | dict:
+ pass
+```
+
+当 `response_mode` 为 `streaming` 时,该接口将直接返回 `Generator[dict]`,否则直接返回 `dict`,具体的接口字段请参考 `ServiceApi` 的返回结果。
+
+#### **用例**
+
+我们可以在一个 `Endpoint` 中调用 Chat 类型的 App,并将结果直接返回。
+
+```python
+import json
+from typing import Mapping
+from werkzeug import Request, Response
+from dify_plugin import Endpoint
+
+class Duck(Endpoint):
+ def _invoke(self, r: Request, values: Mapping, settings: Mapping) -> Response:
+ """
+ Invokes the endpoint with the given request.
+ """
+ app_id = values["app_id"]
+
+ def generator():
+ response = self.session.app.workflow.invoke(
+ app_id=app_id, inputs={}, response_mode="streaming", files=[]
+ )
+
+ for data in response:
+ yield f"{json.dumps(data)}
"
+
+ return Response(generator(), status=200, content_type="text/html")
+```
+
+### 调用 Workflow 接口
+
+#### **入口**
+
+```python
+ self.session.app.workflow
+```
+
+#### **接口规范**
+
+```python
+ def invoke(
+ self,
+ app_id: str,
+ inputs: dict,
+ response_mode: Literal["streaming", "blocking"],
+ files: list,
+ ) -> Generator[dict, None, None] | dict:
+ pass
+```
+
+### 调用 Completion 接口
+
+#### **入口**
+
+```python
+ self.session.app.completion
+```
+
+**接口规范**
+
+```python
+ def invoke(
+ self,
+ app_id: str,
+ inputs: dict,
+ response_mode: Literal["streaming", "blocking"],
+ files: list,
+ ) -> Generator[dict, None, None] | dict:
+ pass
+```
+
+## 相关资源
+
+- [反向调用 Dify 服务](/plugin_dev_zh/9241-reverse-invocation.zh) - 了解反向调用的根本概念
+- [反向调用 Model](/plugin_dev_zh/9242-reverse-invocation-model.zh) - 了解如何调用平台内的模型能力
+- [反向调用 Tool](/plugin_dev_zh/9242-reverse-invocation-tool.zh) - 了解如何调用其它插件
+- [开发 Slack Bot 插件](/plugin_dev_zh/0432-develop-a-slack-bot-plugin.zh) - 使用反向调用的实际应用案例
+- [开发 Extension 插件](/plugin_dev_zh/9231-extension-plugin.zh) - 学习如何开发扩展插件
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ 通过直接提交修改来帮助改进文档内容
+
+
+ 发现错误或有改进建议?请提交问题反馈
+
+
diff --git a/plugin_dev_zh/9242-reverse-invocation-model.zh.mdx b/plugin_dev_zh/9242-reverse-invocation-model.zh.mdx
new file mode 100644
index 00000000..460d4116
--- /dev/null
+++ b/plugin_dev_zh/9242-reverse-invocation-model.zh.mdx
@@ -0,0 +1,306 @@
+---
+dimensions:
+ type:
+ primary: implementation
+ detail: advanced
+ level: intermediate
+standard_title: Reverse Invocation Model
+language: zh
+title: Model
+description: 本文档详细介绍了插件如何反向调用Dify平台中的模型服务。内容包括反向调用LLM、Summary、TextEmbedding、Rerank、TTS、Speech2Text和Moderation等模型的具体方法,每种模型调用都配有对应的入口、接口参数说明以及实际的使用示例代码,并提供了调用模型的最佳实践建议。
+---
+
+反向调用 Model 指的是插件能够反向调用 Dify 内 LLM 的能力,包括平台内的所有模型类型与功能,例如 TTS、Rerank 等。如果你对反向调用的基本概念还不熟悉,请先阅读[反向调用 Dify 服务](/plugin_dev_zh/9241-reverse-invocation.zh)。
+
+不过请注意,调用模型需要传入一个 `ModelConfig` 类型的参数,它的结构可以参考 [通用规范定义](/plugin_dev_zh/0411-general-specifications.zh),并且对于不同类型的模型,该结构会存在细微的差别。
+
+例如对于 `LLM` 类型的模型,还需要包含 `completion_params` 与 `mode` 参数,你可以手动构建该结构,或者使用 `model-selector` 类型的参数或配置。
+
+### 调用 LLM
+
+#### **入口**
+
+```python
+ self.session.model.llm
+```
+
+#### **端点**
+
+```python
+ def invoke(
+ self,
+ model_config: LLMModelConfig,
+ prompt_messages: list[PromptMessage],
+ tools: list[PromptMessageTool] | None = None,
+ stop: list[str] | None = None,
+ stream: bool = True,
+ ) -> Generator[LLMResultChunk, None, None] | LLMResult:
+ pass
+```
+
+请注意,如果你调用的模型不具备 `tool_call` 的能力,那么此处传入的 `tools` 将不会生效。
+
+#### **用例**
+
+如果想在 `Tool` 中调用 `OpenAI` 的 `gpt-4o-mini` 模型,请参考以下示例代码:
+
+```python
+from collections.abc import Generator
+from typing import Any
+
+from dify_plugin import Tool
+from dify_plugin.entities.model.llm import LLMModelConfig
+from dify_plugin.entities.tool import ToolInvokeMessage
+from dify_plugin.entities.model.message import SystemPromptMessage, UserPromptMessage
+
+class LLMTool(Tool):
+ def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage]:
+ response = self.session.model.llm.invoke(
+ model_config=LLMModelConfig(
+ provider='openai',
+ model='gpt-4o-mini',
+ mode='chat',
+ completion_params={}
+ ),
+ prompt_messages=[
+ SystemPromptMessage(
+ content='you are a helpful assistant'
+ ),
+ UserPromptMessage(
+ content=tool_parameters.get('query')
+ )
+ ],
+ stream=True
+ )
+
+ for chunk in response:
+ if chunk.delta.message:
+ assert isinstance(chunk.delta.message.content, str)
+ yield self.create_text_message(text=chunk.delta.message.content)
+```
+
+可以留意到代码中传入了 `tool_parameters` 中的 `query` 参数。
+
+### **最佳实践**
+
+并不建议手动来构建 `LLMModelConfig`,而是允许用户可以在 UI 上选择自己想使用的模型,在这种情况下可以修改一下工具的参数列表,按照如下配置,添加一个 `model` 参数:
+
+```yaml
+identity:
+ name: llm
+ author: Dify
+ label:
+ en_US: LLM
+ zh_Hans: LLM
+ pt_BR: LLM
+description:
+ human:
+ en_US: A tool for invoking a large language model
+ zh_Hans: 用于调用大型语言模型的工具
+ pt_BR: A tool for invoking a large language model
+ llm: A tool for invoking a large language model
+parameters:
+ - name: prompt
+ type: string
+ required: true
+ label:
+ en_US: Prompt string
+ zh_Hans: 提示字符串
+ pt_BR: Prompt string
+ human_description:
+ en_US: used for searching
+ zh_Hans: 用于搜索网页内容
+ pt_BR: used for searching
+ llm_description: key words for searching
+ form: llm
+ - name: model
+ type: model-selector
+ scope: llm
+ required: true
+ label:
+ en_US: Model
+ zh_Hans: 使用的模型
+ pt_BR: Model
+ human_description:
+ en_US: Model
+ zh_Hans: 使用的模型
+ pt_BR: Model
+ llm_description: which Model to invoke
+ form: form
+extra:
+ python:
+ source: tools/llm.py
+```
+
+请注意在该例子中指定了 `model` 的 `scope` 为 `llm`,那么此时用户就只能选择 `llm` 类型的参数,从而可以将上述用例的代码改成以下代码:
+
+```python
+from collections.abc import Generator
+from typing import Any
+
+from dify_plugin import Tool
+from dify_plugin.entities.model.llm import LLMModelConfig
+from dify_plugin.entities.tool import ToolInvokeMessage
+from dify_plugin.entities.model.message import SystemPromptMessage, UserPromptMessage
+
+class LLMTool(Tool):
+ def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage]:
+ response = self.session.model.llm.invoke(
+ model_config=tool_parameters.get('model'),
+ prompt_messages=[
+ SystemPromptMessage(
+ content='you are a helpful assistant'
+ ),
+ UserPromptMessage(
+ content=tool_parameters.get('query')
+ )
+ ],
+ stream=True
+ )
+
+ for chunk in response:
+ if chunk.delta.message:
+ assert isinstance(chunk.delta.message.content, str)
+ yield self.create_text_message(text=chunk.delta.message.content)
+```
+
+### 调用 Summary
+
+你可以请求该端点来总结一段文本,它会使用你当前 workspace 内的系统模型来总结文本。
+
+**入口**
+
+```python
+ self.session.model.summary
+```
+
+**端点**
+
+* `text` 为需要被总结的文本。
+* `instruction` 为你想要额外添加的指令,它可以让你风格化地总结文本。
+
+```python
+ def invoke(
+ self, text: str, instruction: str,
+ ) -> str:
+```
+
+### 调用 TextEmbedding
+
+**入口**
+
+```python
+ self.session.model.text_embedding
+```
+
+**端点**
+
+```python
+ def invoke(
+ self, model_config: TextEmbeddingResult, texts: list[str]
+ ) -> TextEmbeddingResult:
+ pass
+```
+
+### 调用 Rerank
+
+**入口**
+
+```python
+ self.session.model.rerank
+```
+
+**端点**
+
+```python
+ def invoke(
+ self, model_config: RerankModelConfig, docs: list[str], query: str
+ ) -> RerankResult:
+ pass
+```
+
+### 调用 TTS
+
+**入口**
+
+```python
+ self.session.model.tts
+```
+
+**端点**
+
+```python
+ def invoke(
+ self, model_config: TTSModelConfig, content_text: str
+ ) -> Generator[bytes, None, None]:
+ pass
+```
+
+请注意 `tts` 端点返回的 `bytes` 流是一个 `mp3` 音频字节流,每一轮迭代返回的都是一个完整的音频。如果你想做更深入的处理任务,请选择合适的库进行。
+
+### 调用 Speech2Text
+
+**入口**
+
+```python
+ self.session.model.speech2text
+```
+
+**端点**
+
+```python
+ def invoke(
+ self, model_config: Speech2TextModelConfig, file: IO[bytes]
+ ) -> str:
+ pass
+```
+
+其中 `file` 是一个 `mp3` 格式编码的音频文件。
+
+### 调用 Moderation
+
+**入口**
+
+```python
+ self.session.model.moderation
+```
+
+**端点**
+
+```python
+ def invoke(self, model_config: ModerationModelConfig, text: str) -> bool:
+ pass
+```
+
+若该端点返回 `true` 则表示 `text` 中包含敏感内容。
+
+## 相关资源
+
+- [反向调用 Dify 服务](/plugin_dev_zh/9241-reverse-invocation.zh) - 了解反向调用的根本概念
+- [反向调用 App](/plugin_dev_zh/9242-reverse-invocation-app.zh) - 了解如何调用平台内的 App
+- [反向调用 Tool](/plugin_dev_zh/9242-reverse-invocation-tool.zh) - 了解如何调用其它插件
+- [模型插件开发指南](/plugin_dev_zh/0211-getting-started-new-model.zh) - 学习如何开发自定义模型插件
+- [模型设计规则](/plugin_dev_zh/0411-model-designing-rules.zh) - 了解模型插件的设计原则
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ 通过直接提交修改来帮助改进文档内容
+
+
+ 发现错误或有改进建议?请提交问题反馈
+
+
diff --git a/plugin_dev_zh/9242-reverse-invocation-tool.zh.mdx b/plugin_dev_zh/9242-reverse-invocation-tool.zh.mdx
new file mode 100644
index 00000000..cae86661
--- /dev/null
+++ b/plugin_dev_zh/9242-reverse-invocation-tool.zh.mdx
@@ -0,0 +1,116 @@
+---
+dimensions:
+ type:
+ primary: implementation
+ detail: advanced
+ level: intermediate
+standard_title: Reverse Invocation Tool
+language: zh
+title: Tool
+description: 本文档详细介绍了插件如何反向调用Dify平台中的工具服务。内容涉及三种不同类型的工具调用方法:调用已安装的工具(Built-in Tool)、调用Workflow
+ as Tool以及调用自定义工具(Custom Tool)。每种调用方式都配有对应的入口和接口参数说明。
+---
+
+反向调用 Tool 指的是插件能够调用 Dify 平台内的其它工具类型插件。如果你对反向调用的基本概念还不熟悉,请先阅读[反向调用 Dify 服务](/plugin_dev_zh/9241-reverse-invocation.zh)。
+
+当遇到以下需求时:
+
+* 某个工具类型插件已经实现好了一个功能,但效果未达预期,需要对数据进行二次加工。
+* 某个任务需要使用到爬虫,希望能够自由选择爬虫服务。
+* 需要集合多个工具的返回结果,但是通过 Workflow 应用不好处理。
+
+此时需要在插件中调用其他已经实现好的工具,该工具可能是市场中的某个工具插件,可能是自主构建的 Workflow as a Tool,亦或是自定义工具。
+
+上述需求可以通过调用插件的 `self.session.tool` 字段来实现。
+
+### 调用已安装的工具
+
+允许插件调用已安装在当前 Workspace 内的各个工具,其中也包含其它工具类型的插件。
+
+**入口**
+
+```python
+ self.session.tool
+```
+
+**接口**
+
+```python
+ def invoke_builtin_tool(
+ self, provider: str, tool_name: str, parameters: dict[str, Any]
+ ) -> Generator[ToolInvokeMessage, None, None]:
+ pass
+```
+
+其中 provider 为 plugin 的 ID 加上工具供应商名称,格式形如 `langgenius/google/google`,tool\_name 为具体的工具名称,`parameters` 为最后传递给该工具的参数。
+
+### 调用 Workflow as Tool
+
+如需了解关于 Workflow as Tool 的更多说明,请参考[工具插件文档](/plugin_dev_zh/9223-tool.zh)。
+
+**入口**
+
+```python
+ self.session.tool
+```
+
+**接口**
+
+```python
+ def invoke_workflow_tool(
+ self, provider: str, tool_name: str, parameters: dict[str, Any]
+ ) -> Generator[ToolInvokeMessage, None, None]:
+ pass
+```
+
+此时的 provider 为该 tool 的 ID,tool\_name 在创建该 tool 的时候会要求填写。
+
+### 调用 Custom Tool
+
+**入口**
+
+```python
+ self.session.tool
+```
+
+**接口**
+
+```python
+ def invoke_api_tool(
+ self, provider: str, tool_name: str, parameters: dict[str, Any]
+ ) -> Generator[ToolInvokeMessage, None, None]:
+ pass
+```
+
+此时的 `provider` 为该 tool 的 ID,`tool_name` 为 OpenAPI 中的 `operation_id`,若不存在,即为 Dify 自动生成的 `tool_name`,可以在工具管理页中看到具体的名称。
+
+## 相关资源
+
+- [反向调用 Dify 服务](/plugin_dev_zh/9241-reverse-invocation.zh) - 了解反向调用的根本概念
+- [反向调用 App](/plugin_dev_zh/9242-reverse-invocation-app.zh) - 了解如何调用平台内的 App
+- [反向调用 Model](/plugin_dev_zh/9242-reverse-invocation-model.zh) - 了解如何调用平台内的模型能力
+- [工具插件开发指南](/plugin_dev_zh/0211-getting-started-dify-tool.zh) - 学习如何开发工具插件
+- [高级工具插件](/plugin_dev_zh/9223-tool.zh) - 了解 Workflow as Tool 等高级功能\
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ 通过直接提交修改来帮助改进文档内容
+
+
+ 发现错误或有改进建议?请提交问题反馈
+
+
diff --git a/plugin_dev_zh/9243-customizable-model.zh.mdx b/plugin_dev_zh/9243-customizable-model.zh.mdx
new file mode 100644
index 00000000..b09c2bd8
--- /dev/null
+++ b/plugin_dev_zh/9243-customizable-model.zh.mdx
@@ -0,0 +1,370 @@
+---
+dimensions:
+ type:
+ primary: implementation
+ detail: advanced
+ level: advanced
+standard_title: Customizable Model
+language: zh
+title: 接入自定义模型
+description: 本文档详细介绍了如何在Dify中接入自定义模型,以Xinference模型为例。文档涉及创建模型供应商文件、根据模型类型编写对应代码、实现模型调用逻辑和异常处理及调试发布等完整流程。特别详细地说明了LLM调用、Token计算、凭证校验和参数生成等核心方法的实现。
+---
+
+自定义模型指的是需要自行部署或配置的 LLM。本文将以 [Xinference 模型](https://inference.readthedocs.io/en/latest/)为例,演示如何在模型插件内接入自定义模型。
+
+自定义模型默认包含模型类型和模型名称两个参数,无需在供应商 yaml 文件定义。
+
+供应商配置文件无需实现 `validate_provider_credential`。Runtime 会根据用户选择的模型类型或模型名称,自动调用对应模型层的 `validate_credentials` 方法进行验证。
+
+### 接入自定义模型插件
+
+接入自定义模型分为以下步骤:
+
+1. **创建模型供应商文件**
+
+ 明确自定义模型中所包含的模型类型。
+2. **根据模型类型创建代码文件**
+
+ 根据模型的类型(如 `llm` 或 `text_embedding`)创建代码文件。确保每种模型类型有独立的逻辑分层,便于维护和扩展。
+3. **根据不同的模型模块,编写模型调用代码**
+
+ 在对应的模型类型模块下,创建一个与模型类型同名的 Python 文件(例如 llm.py)。在文件中定义实现具体模型逻辑的类,该类应符合系统的模型接口规范。
+4. **调试插件**
+
+ 为新增的供应商功能编写单元测试和集成测试,确保所有功能模块符合预期,并能够正常运行。
+
+***
+
+### 1. **创建模型供应商文件**
+
+在插件项目的 `/provider` 路径下,新建 `xinference.yaml` 文件。
+
+`Xinference` 家族模型支持 `LLM``,Text Embedding` 和 `Rerank` 模型类型,因此需要在 `xinference.yaml` 文件中包含上述模型类型。
+
+示例代码:
+
+```yaml
+provider: xinference # 确定供应商标识
+label: # 供应商展示名称,可设置 en_US 英文、zh_Hans 中文两种语言,zh_Hans 不设置将默认使用 en_US。
+ en_US: Xorbits Inference
+icon_small: # 小图标,可以参考其他供应商的图标,存储在对应供应商实现目录下的 _assets 目录,中英文策略同 label
+ en_US: icon_s_en.svg
+icon_large: # 大图标
+ en_US: icon_l_en.svg
+help: # 帮助
+ title:
+ en_US: How to deploy Xinference
+ zh_Hans: 如何部署 Xinference
+ url:
+ en_US: https://github.com/xorbitsai/inference
+supported_model_types: # 支持的模型类型,Xinference 同时支持 LLM/Text Embedding/Rerank
+- llm
+- text-embedding
+- rerank
+configurate_methods: # Xinference 为本地部署的供应商,并且没有预定义模型,需要用什么模型需要根据 Xinference 的文档进行部署,因此此处的方法为自定义模型。
+- customizable-model
+provider_credential_schema:
+ credential_form_schemas:
+```
+
+接着需要定义 `provider_credential_schema` 字段。`Xinference 支持` text-generation,embeddings 和 reranking 模型,示例代码如下:
+
+```yaml
+provider_credential_schema:
+ credential_form_schemas:
+ - variable: model_type
+ type: select
+ label:
+ en_US: Model type
+ zh_Hans: 模型类型
+ required: true
+ options:
+ - value: text-generation
+ label:
+ en_US: Language Model
+ zh_Hans: 语言模型
+ - value: embeddings
+ label:
+ en_US: Text Embedding
+ - value: reranking
+ label:
+ en_US: Rerank
+```
+
+Xinference 中的每个模型都需要定义名称 `model_name`。
+
+```yaml
+ - variable: model_name
+ type: text-input
+ label:
+ en_US: Model name
+ zh_Hans: 模型名称
+ required: true
+ placeholder:
+ zh_Hans: 填写模型名称
+ en_US: Input model name
+```
+
+Xinference 模型需要使用者输入模型的本地部署地址,插件内需要提供允许填写 Xinference 模型的本地部署地址(server\_url)和模型 UID 的位置,示例代码如下:
+
+```yaml
+ - variable: server_url
+ label:
+ zh_Hans: 服务器 URL
+ en_US: Server url
+ type: text-input
+ required: true
+ placeholder:
+ zh_Hans: 在此输入 Xinference 的服务器地址,如 https://example.com/xxx
+ en_US: Enter the url of your Xinference, for example https://example.com/xxx
+ - variable: model_uid
+ label:
+ zh_Hans: 模型 UID
+ en_US: Model uid
+ type: text-input
+ required: true
+ placeholder:
+ zh_Hans: 在此输入您的 Model UID
+ en_US: Enter the model uid
+```
+
+填写所有参数后即可完成自定义模型供应商 yaml 配置文件的创建。接下来需为配置文件内定义的模型添加具体的功能代码文件。
+
+### 2. 编写模型代码
+
+Xinference 模型供应商的模型类型包含 llm、rerank、speech2text、tts 类型,因此需要在 /models 路径下为每个模型类型创建独立的分组,并创建对应的功能代码文件。
+
+下文将以 llm 类型为例,说明如何创建 `llm.py` 代码文件。创建代码时需创建一个 Xinference LLM 类,可以取名为 `XinferenceAILargeLanguageModel`,继承 `__base.large_language_model.LargeLanguageModel` 基类,实现以下几个方法:
+
+* **LLM 调用**
+
+LLM 调用的核心方法,同时支持流式和同步返回。
+
+```python
+def _invoke(self, model: str, credentials: dict,
+ prompt_messages: list[PromptMessage], model_parameters: dict,
+ tools: Optional[list[PromptMessageTool]] = None, stop: Optional[list[str]] = None,
+ stream: bool = True, user: Optional[str] = None) \
+ -> Union[LLMResult, Generator]:
+ """
+ Invoke large language model
+
+ :param model: model name
+ :param credentials: model credentials
+ :param prompt_messages: prompt messages
+ :param model_parameters: model parameters
+ :param tools: tools for tool calling
+ :param stop: stop words
+ :param stream: is stream response
+ :param user: unique user id
+ :return: full response or stream response chunk generator result
+ """
+```
+
+实现代码时,需要注意使用两个函数来返回数据,分别用于处理同步返回和流式返回。
+
+Python 会将函数中包含 `yield` 的关键字函数识别为生成器函数,返回的数据类型固定为 `Generator`,因此需要分别实现同步和流式返回,例如以下示例代码:
+
+> 该示例使用了简化参数,实际编写代码时需参考上文中的参数列表。
+
+```python
+def _invoke(self, stream: bool, **kwargs) \
+ -> Union[LLMResult, Generator]:
+ if stream:
+ return self._handle_stream_response(**kwargs)
+ return self._handle_sync_response(**kwargs)
+
+def _handle_stream_response(self, **kwargs) -> Generator:
+ for chunk in response:
+ yield chunk
+def _handle_sync_response(self, **kwargs) -> LLMResult:
+ return LLMResult(**response)
+```
+
+* **预计算输入 Tokens**
+
+如果模型未提供预计算 tokens 的接口,可以直接返回 0。
+
+```python
+def get_num_tokens(self, model: str, credentials: dict, prompt_messages: list[PromptMessage],
+ tools: Optional[list[PromptMessageTool]] = None) -> int:
+ """
+ Get number of tokens for given prompt messages
+
+ :param model: model name
+ :param credentials: model credentials
+ :param prompt_messages: prompt messages
+ :param tools: tools for tool calling
+ :return:
+ """
+```
+
+在某些情况下,如果不想直接返回 0,可以使用 `self._get_num_tokens_by_gpt2(text: str)` 方法计算 tokens。该方法位于 `AIModel` 基类中,使用 GPT-2 的 Tokenizer 进行计算。但请注意,这是一个替代方案,计算结果可能存在一定误差。
+
+* **模型凭据校验**
+
+与供应商凭据校验类似,这里针对单个模型进行校验。
+
+```python
+def validate_credentials(self, model: str, credentials: dict) -> None:
+ """
+ Validate model credentials
+
+ :param model: model name
+ :param credentials: model credentials
+ :return:
+ """
+```
+
+* **模型参数 Schema**
+
+与[预定义模型类型](integrate-the-predefined-model.md)不同,由于未在 YAML 文件中预设模型所支持的参数,因此需要动态生成模型参数的 Schema。
+
+例如,Xinference 支持 `max_tokens`、`temperature` 和 `top_p` 三种模型参数。然而一些供应商(例如 OpenLLM)会根据具体模型支持不同的参数。
+
+举例来说,供应商 `OpenLLM` 的 A 模型支持 `top_k` 参数,而 B 模型则不支持 `top_k`。在该情况下,需要动态生成每个模型对应的参数 Schema,示例代码如下:
+
+```python
+ def get_customizable_model_schema(self, model: str, credentials: dict) -> AIModelEntity | None:
+ """
+ used to define customizable model schema
+ """
+ rules = [
+ ParameterRule(
+ name='temperature', type=ParameterType.FLOAT,
+ use_template='temperature',
+ label=I18nObject(
+ zh_Hans='温度', en_US='Temperature'
+ )
+ ),
+ ParameterRule(
+ name='top_p', type=ParameterType.FLOAT,
+ use_template='top_p',
+ label=I18nObject(
+ zh_Hans='Top P', en_US='Top P'
+ )
+ ),
+ ParameterRule(
+ name='max_tokens', type=ParameterType.INT,
+ use_template='max_tokens',
+ min=1,
+ default=512,
+ label=I18nObject(
+ zh_Hans='最大生成长度', en_US='Max Tokens'
+ )
+ )
+ ]
+
+ # if model is A, add top_k to rules
+ if model == 'A':
+ rules.append(
+ ParameterRule(
+ name='top_k', type=ParameterType.INT,
+ use_template='top_k',
+ min=1,
+ default=50,
+ label=I18nObject(
+ zh_Hans='Top K', en_US='Top K'
+ )
+ )
+ )
+
+ """
+ some NOT IMPORTANT code here
+ """
+
+ entity = AIModelEntity(
+ model=model,
+ label=I18nObject(
+ en_US=model
+ ),
+ fetch_from=FetchFrom.CUSTOMIZABLE_MODEL,
+ model_type=model_type,
+ model_properties={
+ ModelPropertyKey.MODE: ModelType.LLM,
+ },
+ parameter_rules=rules
+ )
+
+ return entity
+```
+
+* **调用异常错误映射表**
+
+当模型调用异常时需要映射到 Runtime 指定的 `InvokeError` 类型,方便 Dify 针对不同错误做不同后续处理。
+
+Runtime Errors:
+
+* `InvokeConnectionError` 调用连接错误
+* `InvokeServerUnavailableError` 调用服务方不可用
+* `InvokeRateLimitError` 调用达到限额
+* `InvokeAuthorizationError` 调用鉴权失败
+* `InvokeBadRequestError` 调用传参有误
+
+```python
+@property
+def _invoke_error_mapping(self) -> dict[type[InvokeError], list[type[Exception]]]:
+ """
+ Map model invoke error to unified error
+ The key is the error type thrown to the caller
+ The value is the error type thrown by the model,
+ which needs to be converted into a unified error type for the caller.
+
+ :return: Invoke error mapping
+ """
+```
+
+如需了解更多接口方法,请参考[接口文档:Model](../../../schema-definition/model/)。
+
+如需获取本文所涉及的完整代码文件,请访问 [GitHub 代码仓库](https://github.com/langgenius/dify-official-plugins/tree/main/models/xinference)。
+
+### 3. 调试插件
+
+插件开发完成后,接下来需测试插件是否可以正常运行。详细说明请参考:
+
+[debug-plugin.md](../../debug-plugin.md)
+
+### 4. 发布插件
+
+如果想要将插件发布至 Dify Marketplace,请参考以下内容:
+
+[publish-to-dify-marketplace](../../../publish-plugins/publish-to-dify-marketplace/)
+
+### **探索更多**
+
+**快速开始:**
+
+* [开发 Extension 插件](../extension-plugin.md)
+* [开发 Tool 插件](../tool-plugin.md)
+* [Bundle 插件:将多个插件打包](../bundle.md)
+
+**插件接口文档:**
+
+* [Manifest](../../../schema-definition/manifest.md) 结构
+* [Endpoint](../../../schema-definition/endpoint.md) 详细定义
+* [反向调用 Dify 能力](../../../schema-definition/reverse-invocation-of-the-dify-service/)
+* [工具](../../../schema-definition/tool.md)
+* [模型](../../../schema-definition/model/)
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ 通过直接提交修改来帮助改进文档内容
+
+
+ 发现错误或有改进建议?请提交问题反馈
+
+
diff --git a/plugin_dev_zh/9243-reverse-invocation-node.zh.mdx b/plugin_dev_zh/9243-reverse-invocation-node.zh.mdx
new file mode 100644
index 00000000..8a9b7973
--- /dev/null
+++ b/plugin_dev_zh/9243-reverse-invocation-node.zh.mdx
@@ -0,0 +1,119 @@
+---
+dimensions:
+ type:
+ primary: implementation
+ detail: advanced
+ level: advanced
+standard_title: Reverse Invocation Node
+language: zh
+title: Node
+description: 本文档介绍了插件如何反向调用Dify平台中的Chatflow/Workflow应用节点功能。内容主要涉及两种特殊节点的调用方法:参数提取器节点(ParameterExtractor)和问题分类节点(QuestionClassifier)。文档详细说明了这两种节点的调用入口、接口参数和用例代码。
+---
+
+反向调用 Node 指的是插件能够访问 Dify 中 Chatflow/Workflow 应用内部分节点的能力。
+
+`Workflow` 中的 `ParameterExtractor(参数提取器)`与 `QuestionClassifier(问题分类)`节点封装了较为复杂的 Prompt 与代码逻辑,可以通过 LLM 来完成许多硬编码难以解决的任务。插件能够调用这两个节点。
+
+### 调用参数提取器节点;
+
+#### **入口**
+
+```python
+ self.session.workflow_node.parameter_extractor
+```
+
+#### **接口**
+
+```python
+ def invoke(
+ self,
+ parameters: list[ParameterConfig],
+ model: ModelConfig,
+ query: str,
+ instruction: str = "",
+ ) -> NodeResponse
+ pass
+```
+
+其中 `parameters` 是需要提取出的参数的列表,`model` 符合 `LLMModelConfig` 规范,`query` 为提取参数的源文本,`instruction` 为一些可能额外需要给到 LLM 的指令,`NodeResponse` 的结构请参考该[文档](../general-specifications.md#noderesponse)。
+
+#### **用例**
+
+如果想要提取对话中的某个人名,可以参考以下代码:
+
+```python
+from collections.abc import Generator
+from dify_plugin.entities.tool import ToolInvokeMessage
+from dify_plugin import Tool
+from dify_plugin.entities.workflow_node import ModelConfig, ParameterConfig
+
+class ParameterExtractorTool(Tool):
+ def _invoke(
+ self, tool_parameters: dict
+ ) -> Generator[ToolInvokeMessage, None, None]:
+ response = self.session.workflow_node.parameter_extractor.invoke(
+ parameters=[
+ ParameterConfig(
+ name="name",
+ description="name of the person",
+ required=True,
+ type="string",
+ )
+ ],
+ model=ModelConfig(
+ provider="langgenius/openai/openai",
+ name="gpt-4o-mini",
+ completion_params={},
+ ),
+ query="My name is John Doe",
+ instruction="Extract the name of the person",
+ )
+
+ yield self.create_text_message(response.outputs["name"])
+```
+
+### 调用问题分类节点
+
+#### **入口**
+
+```python
+ self.session.workflow_node.question_classifier
+```
+
+#### **接口**
+
+```python
+ def invoke(
+ self,
+ classes: list[ClassConfig],
+ model: ModelConfig,
+ query: str,
+ instruction: str = "",
+ ) -> NodeResponse:
+ pass
+```
+
+该接口参数与 `ParameterExtractor` 一致,最终的返回结果储存在 `NodeResponse.outputs['class_name']` 中。\
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ 通过直接提交修改来帮助改进文档内容
+
+
+ 发现错误或有改进建议?请提交问题反馈
+
+
diff --git a/plugin_dev_zh/9433-agent-strategy-plugin.zh.mdx b/plugin_dev_zh/9433-agent-strategy-plugin.zh.mdx
new file mode 100644
index 00000000..c6d15d19
--- /dev/null
+++ b/plugin_dev_zh/9433-agent-strategy-plugin.zh.mdx
@@ -0,0 +1,1117 @@
+---
+dimensions:
+ type:
+ primary: reference
+ detail: examples
+ level: advanced
+standard_title: Agent Strategy Plugin
+language: zh
+title: Agent 策略插件
+description: 本文档详细介绍了如何开发Agent策略插件,从初始化插件模板到调用模型、调用工具、输出日志和打包发布的整个过程。文档提供了详尽的代码示例,包括如何实现帮助LLM执行推理或决策逻辑的自动化工具调用功能。
+---
+
+Agent 策略插件能够帮助 LLM 执行推理或决策逻辑,包括工具选择、调用和结果处理,以更加自动化的方式处理问题。
+
+本文将演示如何创建一个具备工具调用(Function Calling)能力,自动获取当前准确时间的插件。
+
+### 前置准备
+
+* Dify 插件脚手架工具
+* Python 环境,版本号 ≥ 3.12
+
+关于如何准备插件开发的脚手架工具,详细说明请参考[初始化开发工具](/plugin_dev_zh/0221-initialize-development-tools.zh)。
+
+**Tips**:在终端运行 `dify version` 命令,检查是否出现版本号以确认成功安装脚手架工具。
+
+### 1. 初始化插件模板
+
+运行以下命令,初始化 Agent 插件开发模板。
+
+```
+dify plugin init
+```
+
+按照页面提示,填写对应信息。参考以下代码中的备注信息,进行设置。
+
+```
+➜ Dify Plugins Developing dify plugin init
+Edit profile of the plugin
+Plugin name (press Enter to next step): # 填写插件的名称
+Author (press Enter to next step): Author name # 填写插件作者
+Description (press Enter to next step): Description # 填写插件的描述
+---
+Select the language you want to use for plugin development, and press Enter to con
+BTW, you need Python 3.12+ to develop the Plugin if you choose Python.
+-> python # 选择 Python 环境
+ go (not supported yet)
+---
+Based on the ability you want to extend, we have divided the Plugin into four type
+
+- Tool: It's a tool provider, but not only limited to tools, you can implement an
+- Model: Just a model provider, extending others is not allowed.
+- Extension: Other times, you may only need a simple http service to extend the fu
+- Agent Strategy: Implement your own logics here, just by focusing on Agent itself
+
+What's more, we have provided the template for you, you can choose one of them b
+ tool
+-> agent-strategy # 选择 Agent 策略模板
+ llm
+ text-embedding
+---
+Configure the permissions of the plugin, use up and down to navigate, tab to sel
+Backwards Invocation:
+Tools:
+ Enabled: [✔] You can invoke tools inside Dify if it's enabled # 默认开启
+Models:
+ Enabled: [✔] You can invoke models inside Dify if it's enabled # 默认开启
+ LLM: [✔] You can invoke LLM models inside Dify if it's enabled # 默认开启
+ Text Embedding: [✘] You can invoke text embedding models inside Dify if it'
+ Rerank: [✘] You can invoke rerank models inside Dify if it's enabled
+...
+```
+
+初始化插件模板后将生成一个代码文件夹,包含插件开发过程中所需的完整资源。熟悉 Agent 策略插件的整体代码结构有助于插件的开发过程。
+
+```
+├── GUIDE.md # User guide and documentation
+├── PRIVACY.md # Privacy policy and data handling guidelines
+├── README.md # Project overview and setup instructions
+├── _assets/ # Static assets directory
+│ └── icon.svg # Agent strategy provider icon/logo
+├── main.py # Main application entry point
+├── manifest.yaml # Basic plugin configuration
+├── provider/ # Provider configurations directory
+│ └── basic_agent.yaml # Your agent provider settings
+├── requirements.txt # Python dependencies list
+└── strategies/ # Strategy implementation directory
+ ├── basic_agent.py # Basic agent strategy implementation
+ └── basic_agent.yaml # Basic agent strategy configuration
+```
+
+插件的功能代码集中在 `strategies/` 目录内。
+
+### 2. 开发插件功能
+
+Agent 策略插件的开发主要围绕以下两个文件展开:
+
+* 插件声明文件:`strategies/basic_agent.yaml`
+* 插件功能代码:`strategies/basic_agent.py`
+
+#### 2.1 定义参数
+
+要创建一个 Agent 插件,首先需要在 `strategies/basic_agent.yaml` 文件中定义插件所需的参数。这些参数决定了插件的核心功能,例如调用 LLM 模型和使用工具的能力。
+
+建议优先配置以下四个基础参数:
+
+1\. **model**:指定要调用的大语言模型(LLM),如 GPT-4、GPT-4o-mini 等。
+
+2\. **tools**:定义插件可以使用的工具列表,增强插件功能。
+
+3\. **query**:设置与模型交互的提示词或输入内容。
+
+4\. **maximum\_iterations**:限制插件执行的最大迭代次数,避免过度计算。
+
+示例代码:
+
+```yaml
+identity:
+ name: basic_agent # the name of the agent_strategy
+ author: novice # the author of the agent_strategy
+ label:
+ en_US: BasicAgent # the engilish label of the agent_strategy
+description:
+ en_US: BasicAgent # the english description of the agent_strategy
+parameters:
+ - name: model # the name of the model parameter
+ type: model-selector # model-type
+ scope: tool-call&llm # the scope of the parameter
+ required: true
+ label:
+ en_US: Model
+ zh_Hans: 模型
+ pt_BR: Model
+ - name: tools # the name of the tools parameter
+ type: array[tools] # the type of tool parameter
+ required: true
+ label:
+ en_US: Tools list
+ zh_Hans: 工具列表
+ pt_BR: Tools list
+ - name: query # the name of the query parameter
+ type: string # the type of query parameter
+ required: true
+ label:
+ en_US: Query
+ zh_Hans: 查询
+ pt_BR: Query
+ - name: maximum_iterations
+ type: number
+ required: false
+ default: 5
+ label:
+ en_US: Maxium Iterations
+ zh_Hans: 最大迭代次数
+ pt_BR: Maxium Iterations
+ max: 50 # if you set the max and min value, the display of the parameter will be a slider
+ min: 1
+extra:
+ python:
+ source: strategies/basic_agent.py
+
+```
+
+完成参数配置后,插件将在自动生成相应的设置的使用页面,方便你进行直观、便捷的调整和使用。
+
+
+
+#### 2.2 获取参数并执行
+
+当使用者在插件的使用页面完成基础的信息填写后,插件需要处理已填写的传入参数。因此需要先在 `strategies/basic_agent.py` 文件内定义 Agent 参数类供后续使用。
+
+校验传入参数:
+
+```python
+from dify_plugin.entities.agent import AgentInvokeMessage
+from dify_plugin.interfaces.agent import AgentModelConfig, AgentStrategy, ToolEntity
+from pydantic import BaseModel
+
+class BasicParams(BaseModel):
+ maximum_iterations: int
+ model: AgentModelConfig
+ tools: list[ToolEntity]
+ query: str
+
+```
+
+获取参数后,执行具体的业务逻辑:
+
+```python
+class BasicAgentAgentStrategy(AgentStrategy):
+ def _invoke(self, parameters: dict[str, Any]) -> Generator[AgentInvokeMessage]:
+ params = BasicParams(**parameters)
+```
+
+#### 3. 调用模型
+
+在 Agent 策略插件中,**调用模型**是核心执行逻辑之一。可以通过 SDK 提供的 `session.model.llm.invoke()` 方法高效地调用 LLM 模型,实现文本生成、对话处理等功能。
+
+如果希望模型具备**调用工具**的能力,首先需要确保模型能够输出符合工具调用格式的输入参数。也就是说,模型需要根据用户指令生成符合工具接口要求的参数。
+
+构造以下参数:
+
+* model:模型信息
+* prompt\_messages:提示词
+* tools:工具信息(Function Calling 相关)
+* stop:停止符
+* stream:是否支持流式输出
+
+方法定义示例代码:
+
+```python
+def invoke(
+ self,
+ model_config: LLMModelConfig,
+ prompt_messages: list[PromptMessage],
+ tools: list[PromptMessageTool] | None = None,
+ stop: list[str] | None = None,
+ stream: bool = True,
+ ) -> Generator[LLMResultChunk, None, None] | LLMResult:...
+```
+
+要查看完整的功能实现,请参考模型调用[示例代码](agent-strategy-plugin.md#diao-yong-gong-ju-1)。
+
+该代码实现了以下功能:用户输入指令后,Agent 策略插件会自动调用 LLM,根据生成结果构建并传递工具调用所需的参数,使模型能够灵活调度已接入的工具,高效完成复杂任务。
+
+
+
+#### 4. 调用工具
+
+填写工具参数后,需赋予 Agent 策略插件实际调用工具的能力。可以通过 SDK 中的`session.tool.invoke()` 函数进行工具调用。
+
+构造以下参数:
+
+* provider:工具提供商
+* tool\_name:工具名称
+* parameters:输入参数
+
+方法定义示例代码:
+
+```python
+ def invoke(
+ self,
+ provider_type: ToolProviderType,
+ provider: str,
+ tool_name: str,
+ parameters: dict[str, Any],
+ ) -> Generator[ToolInvokeMessage, None, None]:...
+```
+
+若希望通过 LLM 直接生成参数完成工具调用,请参考以下工具调用的示例代码:
+
+```python
+tool_instances = (
+ {tool.identity.name: tool for tool in params.tools} if params.tools else {}
+)
+for tool_call_id, tool_call_name, tool_call_args in tool_calls:
+ tool_instance = tool_instances[tool_call_name]
+ self.session.tool.invoke(
+ provider_type=ToolProviderType.BUILT_IN,
+ provider=tool_instance.identity.provider,
+ tool_name=tool_instance.identity.name,
+ parameters={**tool_instance.runtime_parameters, **tool_call_args},
+ )
+```
+
+如需查看完整的功能代码,请阅读调用工具[示例代码](agent-strategy-plugin.md#diao-yong-gong-ju-1)。
+
+实现这部分的功能代码后,Agent 策略插件将具备自动 Function Calling 的能力,例如自动获取当前时间:
+
+
+
+#### 5. 日志创建
+
+在 **Agent 策略插件**中,通常需要执行多轮操作才能完成复杂任务。记录每轮操作的执行结果对于开发者来说非常重要,有助于追踪 Agent 的执行过程、分析每一步的决策依据,从而更好地评估和优化策略效果。
+
+为了实现这一功能,可以利用 SDK 中的 `create_log_message` 和 `finish_log_message` 方法记录日志。这种方式不仅可以在模型调用前后实时记录操作状态,还能帮助开发者快速定位问题。
+
+场景示例:
+
+* 在模型调用之前,记录一条“开始调用模型”的日志,帮助开发者明确任务执行进度。
+* 在模型调用成功后,记录一条“调用成功”的日志,方便追踪模型响应的完整性。
+
+```python
+model_log = self.create_log_message(
+ label=f"{params.model.model} Thought",
+ data={},
+ metadata={"start_at": model_started_at, "provider": params.model.provider},
+ status=ToolInvokeMessage.LogMessage.LogStatus.START,
+ )
+yield model_log
+self.session.model.llm.invoke(...)
+yield self.finish_log_message(
+ log=model_log,
+ data={
+ "output": response,
+ "tool_name": tool_call_names,
+ "tool_input": tool_call_inputs,
+ },
+ metadata={
+ "started_at": model_started_at,
+ "finished_at": time.perf_counter(),
+ "elapsed_time": time.perf_counter() - model_started_at,
+ "provider": params.model.provider,
+ },
+)
+```
+
+设置完成后,工作流日志将输出执行结果:
+
+
+
+在 Agent 执行的过程中,有可能会产生多轮日志。若日志能具备层级结构将有助于开发者查看。通过在日志记录时传入 parent 参数,不同轮次的日志可以形成上下级关系,使日志展示更加清晰、易于追踪。
+
+**引用方法:**
+
+```python
+function_call_round_log = self.create_log_message(
+ label="Function Call Round1 ",
+ data={},
+ metadata={},
+)
+yield function_call_round_log
+
+model_log = self.create_log_message(
+ label=f"{params.model.model} Thought",
+ data={},
+ metadata={"start_at": model_started_at, "provider": params.model.provider},
+ status=ToolInvokeMessage.LogMessage.LogStatus.START,
+ # add parent log
+ parent=function_call_round_log,
+)
+yield model_log
+```
+
+#### 插件功能示例代码
+
+
+#### 调用模型
+
+以下代码将演示如何赋予 Agent 策略插件调用模型的能力:
+
+```python
+import json
+from collections.abc import Generator
+from typing import Any, cast
+
+from dify_plugin.entities.agent import AgentInvokeMessage
+from dify_plugin.entities.model.llm import LLMModelConfig, LLMResult, LLMResultChunk
+from dify_plugin.entities.model.message import (
+ PromptMessageTool,
+ UserPromptMessage,
+)
+from dify_plugin.entities.tool import ToolInvokeMessage, ToolParameter, ToolProviderType
+from dify_plugin.interfaces.agent import AgentModelConfig, AgentStrategy, ToolEntity
+from pydantic import BaseModel
+
+class BasicParams(BaseModel):
+ maximum_iterations: int
+ model: AgentModelConfig
+ tools: list[ToolEntity]
+ query: str
+
+class BasicAgentAgentStrategy(AgentStrategy):
+ def _invoke(self, parameters: dict[str, Any]) -> Generator[AgentInvokeMessage]:
+ params = BasicParams(**parameters)
+ chunks: Generator[LLMResultChunk, None, None] | LLMResult = (
+ self.session.model.llm.invoke(
+ model_config=LLMModelConfig(**params.model.model_dump(mode="json")),
+ prompt_messages=[UserPromptMessage(content=params.query)],
+ tools=[
+ self._convert_tool_to_prompt_message_tool(tool)
+ for tool in params.tools
+ ],
+ stop=params.model.completion_params.get("stop", [])
+ if params.model.completion_params
+ else [],
+ stream=True,
+ )
+ )
+ response = ""
+ tool_calls = []
+ tool_instances = (
+ {tool.identity.name: tool for tool in params.tools} if params.tools else {}
+ )
+
+ for chunk in chunks:
+ # check if there is any tool call
+ if self.check_tool_calls(chunk):
+ tool_calls = self.extract_tool_calls(chunk)
+ tool_call_names = ";".join([tool_call[1] for tool_call in tool_calls])
+ try:
+ tool_call_inputs = json.dumps(
+ {tool_call[1]: tool_call[2] for tool_call in tool_calls},
+ ensure_ascii=False,
+ )
+ except json.JSONDecodeError:
+ # ensure ascii to avoid encoding error
+ tool_call_inputs = json.dumps(
+ {tool_call[1]: tool_call[2] for tool_call in tool_calls}
+ )
+ print(tool_call_names, tool_call_inputs)
+ if chunk.delta.message and chunk.delta.message.content:
+ if isinstance(chunk.delta.message.content, list):
+ for content in chunk.delta.message.content:
+ response += content.data
+ print(content.data, end="", flush=True)
+ else:
+ response += str(chunk.delta.message.content)
+ print(str(chunk.delta.message.content), end="", flush=True)
+
+ if chunk.delta.usage:
+ # usage of the model
+ usage = chunk.delta.usage
+
+ yield self.create_text_message(
+ text=f"{response or json.dumps(tool_calls, ensure_ascii=False)}\n"
+ )
+ result = ""
+ for tool_call_id, tool_call_name, tool_call_args in tool_calls:
+ tool_instance = tool_instances[tool_call_name]
+ tool_invoke_responses = self.session.tool.invoke(
+ provider_type=ToolProviderType.BUILT_IN,
+ provider=tool_instance.identity.provider,
+ tool_name=tool_instance.identity.name,
+ parameters={**tool_instance.runtime_parameters, **tool_call_args},
+ )
+ if not tool_instance:
+ tool_invoke_responses = {
+ "tool_call_id": tool_call_id,
+ "tool_call_name": tool_call_name,
+ "tool_response": f"there is not a tool named {tool_call_name}",
+ }
+ else:
+ # invoke tool
+ tool_invoke_responses = self.session.tool.invoke(
+ provider_type=ToolProviderType.BUILT_IN,
+ provider=tool_instance.identity.provider,
+ tool_name=tool_instance.identity.name,
+ parameters={**tool_instance.runtime_parameters, **tool_call_args},
+ )
+ result = ""
+ for tool_invoke_response in tool_invoke_responses:
+ if tool_invoke_response.type == ToolInvokeMessage.MessageType.TEXT:
+ result += cast(
+ ToolInvokeMessage.TextMessage, tool_invoke_response.message
+ ).text
+ elif (
+ tool_invoke_response.type == ToolInvokeMessage.MessageType.LINK
+ ):
+ result += (
+ f"result link: {cast(ToolInvokeMessage.TextMessage, tool_invoke_response.message).text}."
+ + " please tell user to check it."
+ )
+ elif tool_invoke_response.type in {
+ ToolInvokeMessage.MessageType.IMAGE_LINK,
+ ToolInvokeMessage.MessageType.IMAGE,
+ }:
+ result += (
+ "image has been created and sent to user already, "
+ + "you do not need to create it, just tell the user to check it now."
+ )
+ elif (
+ tool_invoke_response.type == ToolInvokeMessage.MessageType.JSON
+ ):
+ text = json.dumps(
+ cast(
+ ToolInvokeMessage.JsonMessage,
+ tool_invoke_response.message,
+ ).json_object,
+ ensure_ascii=False,
+ )
+ result += f"tool response: {text}."
+ else:
+ result += f"tool response: {tool_invoke_response.message!r}."
+
+ tool_response = {
+ "tool_call_id": tool_call_id,
+ "tool_call_name": tool_call_name,
+ "tool_response": result,
+ }
+ yield self.create_text_message(result)
+
+ def _convert_tool_to_prompt_message_tool(
+ self, tool: ToolEntity
+ ) -> PromptMessageTool:
+ """
+ convert tool to prompt message tool
+ """
+ message_tool = PromptMessageTool(
+ name=tool.identity.name,
+ description=tool.description.llm if tool.description else "",
+ parameters={
+ "type": "object",
+ "properties": {},
+ "required": [],
+ },
+ )
+
+ parameters = tool.parameters
+ for parameter in parameters:
+ if parameter.form != ToolParameter.ToolParameterForm.LLM:
+ continue
+
+ parameter_type = parameter.type
+ if parameter.type in {
+ ToolParameter.ToolParameterType.FILE,
+ ToolParameter.ToolParameterType.FILES,
+ }:
+ continue
+ enum = []
+ if parameter.type == ToolParameter.ToolParameterType.SELECT:
+ enum = (
+ [option.value for option in parameter.options]
+ if parameter.options
+ else []
+ )
+
+ message_tool.parameters["properties"][parameter.name] = {
+ "type": parameter_type,
+ "description": parameter.llm_description or "",
+ }
+
+ if len(enum) > 0:
+ message_tool.parameters["properties"][parameter.name]["enum"] = enum
+
+ if parameter.required:
+ message_tool.parameters["required"].append(parameter.name)
+
+ return message_tool
+
+ def check_tool_calls(self, llm_result_chunk: LLMResultChunk) -> bool:
+ """
+ Check if there is any tool call in llm result chunk
+ """
+ return bool(llm_result_chunk.delta.message.tool_calls)
+
+ def extract_tool_calls(
+ self, llm_result_chunk: LLMResultChunk
+ ) -> list[tuple[str, str, dict[str, Any]]]:
+ """
+ Extract tool calls from llm result chunk
+
+ Returns:
+ List[Tuple[str, str, Dict[str, Any]]]: [(tool_call_id, tool_call_name, tool_call_args)]
+ """
+ tool_calls = []
+ for prompt_message in llm_result_chunk.delta.message.tool_calls:
+ args = {}
+ if prompt_message.function.arguments != "":
+ args = json.loads(prompt_message.function.arguments)
+
+ tool_calls.append(
+ (
+ prompt_message.id,
+ prompt_message.function.name,
+ args,
+ )
+ )
+
+ return tool_calls
+```
+
+
+#### 调用工具
+
+以下代码展示了如何为 Agent 策略插件实现模型调用并向工具发送规范化请求。
+
+```python
+import json
+from collections.abc import Generator
+from typing import Any, cast
+
+from dify_plugin.entities.agent import AgentInvokeMessage
+from dify_plugin.entities.model.llm import LLMModelConfig, LLMResult, LLMResultChunk
+from dify_plugin.entities.model.message import (
+ PromptMessageTool,
+ UserPromptMessage,
+)
+from dify_plugin.entities.tool import ToolInvokeMessage, ToolParameter, ToolProviderType
+from dify_plugin.interfaces.agent import AgentModelConfig, AgentStrategy, ToolEntity
+from pydantic import BaseModel
+
+class BasicParams(BaseModel):
+ maximum_iterations: int
+ model: AgentModelConfig
+ tools: list[ToolEntity]
+ query: str
+
+class BasicAgentAgentStrategy(AgentStrategy):
+ def _invoke(self, parameters: dict[str, Any]) -> Generator[AgentInvokeMessage]:
+ params = BasicParams(**parameters)
+ chunks: Generator[LLMResultChunk, None, None] | LLMResult = (
+ self.session.model.llm.invoke(
+ model_config=LLMModelConfig(**params.model.model_dump(mode="json")),
+ prompt_messages=[UserPromptMessage(content=params.query)],
+ tools=[
+ self._convert_tool_to_prompt_message_tool(tool)
+ for tool in params.tools
+ ],
+ stop=params.model.completion_params.get("stop", [])
+ if params.model.completion_params
+ else [],
+ stream=True,
+ )
+ )
+ response = ""
+ tool_calls = []
+ tool_instances = (
+ {tool.identity.name: tool for tool in params.tools} if params.tools else {}
+ )
+
+ for chunk in chunks:
+ # check if there is any tool call
+ if self.check_tool_calls(chunk):
+ tool_calls = self.extract_tool_calls(chunk)
+ tool_call_names = ";".join([tool_call[1] for tool_call in tool_calls])
+ try:
+ tool_call_inputs = json.dumps(
+ {tool_call[1]: tool_call[2] for tool_call in tool_calls},
+ ensure_ascii=False,
+ )
+ except json.JSONDecodeError:
+ # ensure ascii to avoid encoding error
+ tool_call_inputs = json.dumps(
+ {tool_call[1]: tool_call[2] for tool_call in tool_calls}
+ )
+ print(tool_call_names, tool_call_inputs)
+ if chunk.delta.message and chunk.delta.message.content:
+ if isinstance(chunk.delta.message.content, list):
+ for content in chunk.delta.message.content:
+ response += content.data
+ print(content.data, end="", flush=True)
+ else:
+ response += str(chunk.delta.message.content)
+ print(str(chunk.delta.message.content), end="", flush=True)
+
+ if chunk.delta.usage:
+ # usage of the model
+ usage = chunk.delta.usage
+
+ yield self.create_text_message(
+ text=f"{response or json.dumps(tool_calls, ensure_ascii=False)}\n"
+ )
+ result = ""
+ for tool_call_id, tool_call_name, tool_call_args in tool_calls:
+ tool_instance = tool_instances[tool_call_name]
+ tool_invoke_responses = self.session.tool.invoke(
+ provider_type=ToolProviderType.BUILT_IN,
+ provider=tool_instance.identity.provider,
+ tool_name=tool_instance.identity.name,
+ parameters={**tool_instance.runtime_parameters, **tool_call_args},
+ )
+ if not tool_instance:
+ tool_invoke_responses = {
+ "tool_call_id": tool_call_id,
+ "tool_call_name": tool_call_name,
+ "tool_response": f"there is not a tool named {tool_call_name}",
+ }
+ else:
+ # invoke tool
+ tool_invoke_responses = self.session.tool.invoke(
+ provider_type=ToolProviderType.BUILT_IN,
+ provider=tool_instance.identity.provider,
+ tool_name=tool_instance.identity.name,
+ parameters={**tool_instance.runtime_parameters, **tool_call_args},
+ )
+ result = ""
+ for tool_invoke_response in tool_invoke_responses:
+ if tool_invoke_response.type == ToolInvokeMessage.MessageType.TEXT:
+ result += cast(
+ ToolInvokeMessage.TextMessage, tool_invoke_response.message
+ ).text
+ elif (
+ tool_invoke_response.type == ToolInvokeMessage.MessageType.LINK
+ ):
+ result += (
+ f"result link: {cast(ToolInvokeMessage.TextMessage, tool_invoke_response.message).text}."
+ + " please tell user to check it."
+ )
+ elif tool_invoke_response.type in {
+ ToolInvokeMessage.MessageType.IMAGE_LINK,
+ ToolInvokeMessage.MessageType.IMAGE,
+ }:
+ result += (
+ "image has been created and sent to user already, "
+ + "you do not need to create it, just tell the user to check it now."
+ )
+ elif (
+ tool_invoke_response.type == ToolInvokeMessage.MessageType.JSON
+ ):
+ text = json.dumps(
+ cast(
+ ToolInvokeMessage.JsonMessage,
+ tool_invoke_response.message,
+ ).json_object,
+ ensure_ascii=False,
+ )
+ result += f"tool response: {text}."
+ else:
+ result += f"tool response: {tool_invoke_response.message!r}."
+
+ tool_response = {
+ "tool_call_id": tool_call_id,
+ "tool_call_name": tool_call_name,
+ "tool_response": result,
+ }
+ yield self.create_text_message(result)
+
+ def _convert_tool_to_prompt_message_tool(
+ self, tool: ToolEntity
+ ) -> PromptMessageTool:
+ """
+ convert tool to prompt message tool
+ """
+ message_tool = PromptMessageTool(
+ name=tool.identity.name,
+ description=tool.description.llm if tool.description else "",
+ parameters={
+ "type": "object",
+ "properties": {},
+ "required": [],
+ },
+ )
+
+ parameters = tool.parameters
+ for parameter in parameters:
+ if parameter.form != ToolParameter.ToolParameterForm.LLM:
+ continue
+
+ parameter_type = parameter.type
+ if parameter.type in {
+ ToolParameter.ToolParameterType.FILE,
+ ToolParameter.ToolParameterType.FILES,
+ }:
+ continue
+ enum = []
+ if parameter.type == ToolParameter.ToolParameterType.SELECT:
+ enum = (
+ [option.value for option in parameter.options]
+ if parameter.options
+ else []
+ )
+
+ message_tool.parameters["properties"][parameter.name] = {
+ "type": parameter_type,
+ "description": parameter.llm_description or "",
+ }
+
+ if len(enum) > 0:
+ message_tool.parameters["properties"][parameter.name]["enum"] = enum
+
+ if parameter.required:
+ message_tool.parameters["required"].append(parameter.name)
+
+ return message_tool
+
+ def check_tool_calls(self, llm_result_chunk: LLMResultChunk) -> bool:
+ """
+ Check if there is any tool call in llm result chunk
+ """
+ return bool(llm_result_chunk.delta.message.tool_calls)
+
+ def extract_tool_calls(
+ self, llm_result_chunk: LLMResultChunk
+ ) -> list[tuple[str, str, dict[str, Any]]]:
+ """
+ Extract tool calls from llm result chunk
+
+ Returns:
+ List[Tuple[str, str, Dict[str, Any]]]: [(tool_call_id, tool_call_name, tool_call_args)]
+ """
+ tool_calls = []
+ for prompt_message in llm_result_chunk.delta.message.tool_calls:
+ args = {}
+ if prompt_message.function.arguments != "":
+ args = json.loads(prompt_message.function.arguments)
+
+ tool_calls.append(
+ (
+ prompt_message.id,
+ prompt_message.function.name,
+ args,
+ )
+ )
+
+ return tool_calls
+```
+
+
+#### 完整功能代码示例
+
+包含**调用模型、调用工具**以及**输出多轮日志功能**的完整插件代码示例:
+
+```python
+import json
+import time
+from collections.abc import Generator
+from typing import Any, cast
+
+from dify_plugin.entities.agent import AgentInvokeMessage
+from dify_plugin.entities.model.llm import LLMModelConfig, LLMResult, LLMResultChunk
+from dify_plugin.entities.model.message import (
+ PromptMessageTool,
+ UserPromptMessage,
+)
+from dify_plugin.entities.tool import ToolInvokeMessage, ToolParameter, ToolProviderType
+from dify_plugin.interfaces.agent import AgentModelConfig, AgentStrategy, ToolEntity
+from pydantic import BaseModel
+
+class BasicParams(BaseModel):
+ maximum_iterations: int
+ model: AgentModelConfig
+ tools: list[ToolEntity]
+ query: str
+
+class BasicAgentAgentStrategy(AgentStrategy):
+ def _invoke(self, parameters: dict[str, Any]) -> Generator[AgentInvokeMessage]:
+ params = BasicParams(**parameters)
+ function_call_round_log = self.create_log_message(
+ label="Function Call Round1 ",
+ data={},
+ metadata={},
+ )
+ yield function_call_round_log
+ model_started_at = time.perf_counter()
+ model_log = self.create_log_message(
+ label=f"{params.model.model} Thought",
+ data={},
+ metadata={"start_at": model_started_at, "provider": params.model.provider},
+ status=ToolInvokeMessage.LogMessage.LogStatus.START,
+ parent=function_call_round_log,
+ )
+ yield model_log
+ chunks: Generator[LLMResultChunk, None, None] | LLMResult = (
+ self.session.model.llm.invoke(
+ model_config=LLMModelConfig(**params.model.model_dump(mode="json")),
+ prompt_messages=[UserPromptMessage(content=params.query)],
+ tools=[
+ self._convert_tool_to_prompt_message_tool(tool)
+ for tool in params.tools
+ ],
+ stop=params.model.completion_params.get("stop", [])
+ if params.model.completion_params
+ else [],
+ stream=True,
+ )
+ )
+ response = ""
+ tool_calls = []
+ tool_instances = (
+ {tool.identity.name: tool for tool in params.tools} if params.tools else {}
+ )
+ tool_call_names = ""
+ tool_call_inputs = ""
+ for chunk in chunks:
+ # check if there is any tool call
+ if self.check_tool_calls(chunk):
+ tool_calls = self.extract_tool_calls(chunk)
+ tool_call_names = ";".join([tool_call[1] for tool_call in tool_calls])
+ try:
+ tool_call_inputs = json.dumps(
+ {tool_call[1]: tool_call[2] for tool_call in tool_calls},
+ ensure_ascii=False,
+ )
+ except json.JSONDecodeError:
+ # ensure ascii to avoid encoding error
+ tool_call_inputs = json.dumps(
+ {tool_call[1]: tool_call[2] for tool_call in tool_calls}
+ )
+ print(tool_call_names, tool_call_inputs)
+ if chunk.delta.message and chunk.delta.message.content:
+ if isinstance(chunk.delta.message.content, list):
+ for content in chunk.delta.message.content:
+ response += content.data
+ print(content.data, end="", flush=True)
+ else:
+ response += str(chunk.delta.message.content)
+ print(str(chunk.delta.message.content), end="", flush=True)
+
+ if chunk.delta.usage:
+ # usage of the model
+ usage = chunk.delta.usage
+
+ yield self.finish_log_message(
+ log=model_log,
+ data={
+ "output": response,
+ "tool_name": tool_call_names,
+ "tool_input": tool_call_inputs,
+ },
+ metadata={
+ "started_at": model_started_at,
+ "finished_at": time.perf_counter(),
+ "elapsed_time": time.perf_counter() - model_started_at,
+ "provider": params.model.provider,
+ },
+ )
+ yield self.create_text_message(
+ text=f"{response or json.dumps(tool_calls, ensure_ascii=False)}\n"
+ )
+ result = ""
+ for tool_call_id, tool_call_name, tool_call_args in tool_calls:
+ tool_instance = tool_instances[tool_call_name]
+ tool_invoke_responses = self.session.tool.invoke(
+ provider_type=ToolProviderType.BUILT_IN,
+ provider=tool_instance.identity.provider,
+ tool_name=tool_instance.identity.name,
+ parameters={**tool_instance.runtime_parameters, **tool_call_args},
+ )
+ if not tool_instance:
+ tool_invoke_responses = {
+ "tool_call_id": tool_call_id,
+ "tool_call_name": tool_call_name,
+ "tool_response": f"there is not a tool named {tool_call_name}",
+ }
+ else:
+ # invoke tool
+ tool_invoke_responses = self.session.tool.invoke(
+ provider_type=ToolProviderType.BUILT_IN,
+ provider=tool_instance.identity.provider,
+ tool_name=tool_instance.identity.name,
+ parameters={**tool_instance.runtime_parameters, **tool_call_args},
+ )
+ result = ""
+ for tool_invoke_response in tool_invoke_responses:
+ if tool_invoke_response.type == ToolInvokeMessage.MessageType.TEXT:
+ result += cast(
+ ToolInvokeMessage.TextMessage, tool_invoke_response.message
+ ).text
+ elif (
+ tool_invoke_response.type == ToolInvokeMessage.MessageType.LINK
+ ):
+ result += (
+ f"result link: {cast(ToolInvokeMessage.TextMessage, tool_invoke_response.message).text}."
+ + " please tell user to check it."
+ )
+ elif tool_invoke_response.type in {
+ ToolInvokeMessage.MessageType.IMAGE_LINK,
+ ToolInvokeMessage.MessageType.IMAGE,
+ }:
+ result += (
+ "image has been created and sent to user already, "
+ + "you do not need to create it, just tell the user to check it now."
+ )
+ elif (
+ tool_invoke_response.type == ToolInvokeMessage.MessageType.JSON
+ ):
+ text = json.dumps(
+ cast(
+ ToolInvokeMessage.JsonMessage,
+ tool_invoke_response.message,
+ ).json_object,
+ ensure_ascii=False,
+ )
+ result += f"tool response: {text}."
+ else:
+ result += f"tool response: {tool_invoke_response.message!r}."
+
+ tool_response = {
+ "tool_call_id": tool_call_id,
+ "tool_call_name": tool_call_name,
+ "tool_response": result,
+ }
+ yield self.create_text_message(result)
+
+ def _convert_tool_to_prompt_message_tool(
+ self, tool: ToolEntity
+ ) -> PromptMessageTool:
+ """
+ convert tool to prompt message tool
+ """
+ message_tool = PromptMessageTool(
+ name=tool.identity.name,
+ description=tool.description.llm if tool.description else "",
+ parameters={
+ "type": "object",
+ "properties": {},
+ "required": [],
+ },
+ )
+
+ parameters = tool.parameters
+ for parameter in parameters:
+ if parameter.form != ToolParameter.ToolParameterForm.LLM:
+ continue
+
+ parameter_type = parameter.type
+ if parameter.type in {
+ ToolParameter.ToolParameterType.FILE,
+ ToolParameter.ToolParameterType.FILES,
+ }:
+ continue
+ enum = []
+ if parameter.type == ToolParameter.ToolParameterType.SELECT:
+ enum = (
+ [option.value for option in parameter.options]
+ if parameter.options
+ else []
+ )
+
+ message_tool.parameters["properties"][parameter.name] = {
+ "type": parameter_type,
+ "description": parameter.llm_description or "",
+ }
+
+ if len(enum) > 0:
+ message_tool.parameters["properties"][parameter.name]["enum"] = enum
+
+ if parameter.required:
+ message_tool.parameters["required"].append(parameter.name)
+
+ return message_tool
+
+ def check_tool_calls(self, llm_result_chunk: LLMResultChunk) -> bool:
+ """
+ Check if there is any tool call in llm result chunk
+ """
+ return bool(llm_result_chunk.delta.message.tool_calls)
+
+ def extract_tool_calls(
+ self, llm_result_chunk: LLMResultChunk
+ ) -> list[tuple[str, str, dict[str, Any]]]:
+ """
+ Extract tool calls from llm result chunk
+
+ Returns:
+ List[Tuple[str, str, Dict[str, Any]]]: [(tool_call_id, tool_call_name, tool_call_args)]
+ """
+ tool_calls = []
+ for prompt_message in llm_result_chunk.delta.message.tool_calls:
+ args = {}
+ if prompt_message.function.arguments != "":
+ args = json.loads(prompt_message.function.arguments)
+
+ tool_calls.append(
+ (
+ prompt_message.id,
+ prompt_message.function.name,
+ args,
+ )
+ )
+
+ return tool_calls
+```
+
+
+
+### 3. 调试插件
+
+配置插件的声明文件与功能代码后,在插件的目录内运行 `python -m main` 命令重启插件。接下来需测试插件是否可以正常运行。Dify 提供远程调试方式,前往[“插件管理”](https://console-plugin.dify.dev/plugins)获取调试 Key 和远程服务器地址。
+
+
+
+回到插件项目,拷贝 `.env.example` 文件并重命名为 `.env`,将获取的远程服务器地址和调试 Key 等信息填入至 `REMOTE_INSTALL_HOST` 与 `REMOTE_INSTALL_KEY` 参数内。
+
+```bash
+INSTALL_METHOD=remote
+REMOTE_INSTALL_HOST=remote
+REMOTE_INSTALL_PORT=5003
+REMOTE_INSTALL_KEY=****-****-****-****-****
+```
+
+运行 `python -m main` 命令启动插件。在插件页即可看到该插件已被安装至 Workspace 内。其他团队成员也可以访问该插件。
+
+
+
+### 打包插件(可选)
+
+确认插件能够正常运行后,可以通过以下命令行工具打包并命名插件。运行以后你可以在当前文件夹发现 `google.difypkg` 文件,该文件为最终的插件包。
+
+```bash
+# 将 ./basic_agent 替换为插件项目的实际路径
+dify plugin package ./basic_agent/
+```
+
+恭喜,你已完成一个工具类型插件的完整开发、调试与打包过程!
+
+### 发布插件(可选)
+
+现在可以将它上传至 [Dify Plugins 代码仓库](https://github.com/langgenius/dify-plugins)来发布你的插件了!上传前,请确保插件已遵循[插件发布规范](https://docs.dify.ai/zh-hans/plugins/publish-plugins/publish-to-dify-marketplace)。审核通过后,代码将合并至主分支并自动上线至 [Dify Marketplace](https://marketplace.dify.ai/)。
+
+### 探索更多
+
+复杂任务往往需要多轮思考和多次工具调用。为了实现更智能的任务处理,通常采用循环执行的策略:**模型调用 → 工具调用**,直到任务完成或达到设定的最大迭代次数。
+
+在这个过程中,提示词(Prompt)管理变得尤为重要。为了高效地组织和动态调整模型输入,建议参考插件内 Function Calling 功能的[完整实现代码](https://github.com/langgenius/dify-official-plugins/blob/main/agent-strategies/cot_agent/strategies/function_calling.py),了解如何通过标准化的方式来让模型调用外部工具并处理返回结果。
+
+## 相关资源
+
+- [Agent 插件开发基础](/plugin_dev_zh/9232-agent.zh) - 了解 Agent 策略插件的基本概念
+- [插件开发基础概念](/plugin_dev_zh/0111-getting-started-dify-plugin.zh) - 了解插件开发的整体架构
+- [初始化开发工具](/plugin_dev_zh/0221-initialize-development-tools.zh) - 学习如何搭建开发环境
+- [反向调用 Model](/plugin_dev_zh/9242-reverse-invocation-model.zh) - 了解如何调用平台内的模型能力
+- [反向调用 Tool](/plugin_dev_zh/9242-reverse-invocation-tool.zh) - 了解如何调用其它插件
+- [发布插件概述](/plugin_dev_zh/0321-release-overview.zh) - 学习插件发布流程
+
+{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}
+
+
+
+ 通过直接提交修改来帮助改进文档内容
+
+
+ 发现错误或有改进建议?请提交问题反馈
+
+
diff --git a/plugin_dev_zh/README.md b/plugin_dev_zh/README.md
new file mode 100644
index 00000000..6f09a160
--- /dev/null
+++ b/plugin_dev_zh/README.md
@@ -0,0 +1,212 @@
+# Dify 开发者文档结构化与排序规范:**dimensions** 系统详解
+
+## 目录
+
+- [Dify 开发者文档结构化与排序规范:**dimensions** 系统详解](#dify-开发者文档结构化与排序规范dimensions-系统详解)
+ - [目录](#目录)
+ - [背景与动机:应对开发者生态的演进](#背景与动机应对开发者生态的演进)
+ - [核心原则:元数据作为规范性数据源](#核心原则元数据作为规范性数据源)
+ - [元数据规范](#元数据规范)
+ - [dimensions 深度解析:为内容赋予意义](#dimensions-深度解析为内容赋予意义)
+ - [排序机制:**PWXY** 前缀与优先级规则](#排序机制pwxy-前缀与优先级规则)
+ - [文件名生成策略](#文件名生成策略)
+ - [面向受众的设计原则:渐进式披露与路径优化](#面向受众的设计原则渐进式披露与路径优化)
+ - [系统收益](#系统收益)
+ - [创作流程、角色与治理:应对现实挑战](#创作流程角色与治理应对现实挑战)
+ - [协作模型中的关键角色](#协作模型中的关键角色)
+ - [应对文档协作的现实挑战](#应对文档协作的现实挑战)
+ - [扩展性与代码的角色](#扩展性与代码的角色)
+
+---
+
+## 背景与动机:应对开发者生态的演进
+
+Dify 最初的成功很大程度上源于其产品的直观易用性。对于核心用户(User/Buyer),产品本身的体验往往足以引导他们;对于少数需要深度定制的早期开发者而言,他们通常具备直接阅读源代码的能力。因此,初期的文档建设并非最高优先级。
+
+然而,随着 **Dify Plugin 插件生态的上线和蓬勃发展**,情况发生了显著变化:
+
+1. 受众扩大化: 我们的开发者社区不再局限于能够深入源码的核心贡献者。大量拥有一定技术背景但可能不熟悉 Dify 底层或特定编程范式的**“半专业开发者” (Citizen Developers)** 或集成开发者涌现,他们需要清晰、循序渐进的指导来构建和贡献插件。
+2. 内容交织与目标模糊: 现有的文档往往将面向最终用户的**帮助内容**(如何使用插件)与面向开发者的**技术内容**(如何开发插件)混合在一起,导致两类受众都感到困惑,增加了信息查找成本。
+3. 结构缺失与导航困难: 文档缺乏统一的组织结构和逻辑顺序,使得开发者(尤其是新加入者)难以找到所需信息,也无法形成对插件开发生命周期的完整认知。现有导航难以承载日益增长的内容。
+4. 构建开发者关系 (DevRel) 的基石: 一个清晰、有效、结构优异的开发者文档体系,是构建成功开发者关系、繁荣插件生态不可或缺的一环。它直接影响开发者的体验、贡献意愿和成功率。
+
+基于以上挑战,以及对业界优秀范例(如 Zapier Developer Platform, Cloudflare Docs)的分析——它们都展现了清晰的受众分离、结构化的内容组织和面向不同技能水平的路径设计——我们认识到对 Dify 的开发者文档进行**系统性重构**的迫切性。
+
+dimensions**系统**应运而生。它并非一套僵化的格式要求,而是我们为了解决上述问题,将**结构化思维、受众细分、渐进式学习**等理念,通过一套**基于元数据的自动化流程**沉淀下来的解决方案。它旨在为 Dify 插件开发者提供一个清晰、一致且易于导航的技术知识库。
+
+## 核心原则:元数据作为规范性数据源
+
+本系统的基石在于以下核心原则:
+
+- 元数据驱动 (Metadata-Driven): 每篇 Markdown 文档的 Front Matter 中定义的元数据(特别是 dimensions, standard_title, language)是描述该文档内容属性、目标受众和预期用途的**规范性数据源 (Normative Data Source)**。
+- 内容与表现分离 (Decoupling Content from Presentation): 文档的最终呈现形式——包括文件名、在文档网站上的排序、导航菜单结构——都**必须由自动化工具根据这些元数据动态生成**。作者应专注于内容本身及其元数据描述的准确性。
+- 自动化保障一致性与可维护性 (Automation for Consistency & Maintainability): 自动化流程确保了整个文档库在结构、命名和排序上的一致性。对分类体系或排序逻辑的调整,只需修改中央配置和生成脚本即可全局应用,极大地提高了可维护性。
+
+这种方法论让内容创作者(作者)和技术实现者(文档系统维护者/DevOps)能够有效协作,共同服务于最终开发者读者的需求。
+
+## 元数据规范
+
+每篇开发者文档(Markdown 文件)必须包含一个 YAML Front Matter 块,定义如下关键字段:
+
+```yaml
+---
+# --- 核心元数据 ---
+
+# 'dimensions' 用于内容分类和自动排序
+dimensions:
+ type: # 主要类别 (Conceptual, Implementation, Operational, Reference) - 决定宏观顺序 (W)
+ primary: # 次要类别/细节 (Introduction, Basic, Core, Examples...) - 决定章节内顺序 (X)
+ detail:
+ # 目标读者级别/内容复杂度 (Beginner, Intermediate, Advanced) - 决定微观顺序 (Y) 及优先级 (P)
+ level:
+
+# 'standard_title' 用于生成可读的文件名,并供概览页面使用
+standard_title: '文档的标准、描述性标题'
+
+# 'language' (ISO 639-1 代码如 'en', 'zh') 用于国际化(i18n)和文件名后缀
+language:
+
+# --- 其他可选元数据 ---
+
+# 'summary': 文档内容的简短摘要 (可选, 可能用于列表页或 SEO)
+# 'tags': [关键词列表] (可选, 用于过滤或关联内容)
+# ... 其他未来可能需要的字段
+---
+```
+
+```markdown
+Type Dimension (Primary)
+
+- conceptual: Theoretical or explanatory content
+- implementation: Code and development content
+- operational: Content related to running and maintaining the system
+- reference: Reference materials
+
+Type Detail Dimension
+
+- For conceptual: introduction, principles, architecture
+- For implementation: basic, standard, high, advanced
+- For operational: setup, deployment, maintenance
+- For reference: core, configuration, examples
+
+Level Dimension
+
+- beginner: For newcomers to the topic
+- intermediate: For users with basic understanding
+- advanced: For advanced users
+```
+
+## dimensions 深度解析:为内容赋予意义
+
+dimensions 元数据是本系统的核心,它要求我们为每一篇文档回答三个基本问题,从而赋予其明确的**语境、范围和受众定位**。这种分类是后续所有自动化结构和排序的基础。
+
+- dimensions.type.primary:定义内容的“性质”
+
+ - 核心问题: 这篇文档从根本上属于哪一类知识?它是关于“是什么/为什么”(理论),“如何做”(实践),“如何运行/维护”(运维),还是“精确查找”(参考)?
+ - 作用: 这个维度确立了文档在整个知识体系中的**宏观类别**。我们采用 conceptual, implementation, operational, reference 这几个业界通用的分类,来划分开发者关注的主要领域。这个分类决定了文档所属的**顶层章节**或主题区域,是构建文档信息架构的第一步。
+
+- dimensions.type.detail:明确内容的“主题焦点”
+
+ - 核心问题: 在上述宏观类别下,这篇文档具体是关于该类别的哪个**特定方面或子主题**?例如,是某个概念的“入门介绍”,还是某个实现的“标准用法”,或是某个参考信息的“核心 API”?
+ - 作用: 它在主类别内部提供了**更精细的主题划分**。这使得相关的文档能够聚集在一起,形成逻辑连贯的**子章节**或段落。选择恰当的 detail 有助于用户在浏览特定章节时快速定位到具体内容点。
+
+- dimensions.level:标定内容的“复杂度与受众”
+ - 核心问题: 这篇特定主题的文档,主要是为哪个**经验水平的读者**准备的?或者说,其内容的**复杂度**如何?是入门 (beginner),通用 (intermediate),还是深入/专门 (advanced)?
+ - 作用: 这个维度具有**双重关键作用**:
+ 1. 细粒度排序: 在同一具体主题下,它允许内容按从易到难的顺序排列,优化学习曲线。
+ 2. 优先级判定: 它(尤其是 advanced 级别)是系统区分“核心内容”与“进阶/深水区内容”的关键输入,直接影响内容是否被后置(见第 5 节)。
+
+为内容精准地打上这三个维度的标签,是确保文档库结构合理、易于导航、满足不同用户需求的前提。
+
+## 排序机制:**PWXY** 前缀与优先级规则
+
+为实现一致且逻辑清晰的文档呈现顺序,系统将根据 dimensions 元数据为每篇文档自动生成一个 4 位、零填充的数字前缀 (PWXY),并以此作为文件名的一部分来实现排序。
+
+- P **优先级 (Priority)**: 第 1 位。区分常规 (P=0) 与后置/深水区 (P=9) 内容。
+- W **主类编号 (Primary Type)**: 第 2 位。体现 primary 类型的宏观顺序。
+- X **子类编号 (Detail Type)**: 第 3 位。体现 detail 类型在主类内的顺序。
+- Y **难度级别编号 (Level)**: 第 4 位。体现 level 在子类内的顺序。
+
+优先级 (**P=9**) 规则详解:
+
+将内容标记为 P=9 的目的是,将对入门和常规使用非必需的、复杂度较高的内容**自动后置**,从而为主流用户群体提供更平滑的学习曲线和更聚焦的核心内容流。触发 P=9 的条件是:
+
+1. 内容本身定义为高级: level: advanced。
+2. 实现类目下的特定复杂细节: primary: implementation 且 detail 为 high 或 advanced。
+
+P=9 的内容可被视为文档库的“附录”、“深度探讨”或“高级参考”。
+
+## 文件名生成策略
+
+最终部署的文件名由自动化脚本基于元数据生成,其概念格式为:
+`PWXY-[sanitized_standard_title].language.md`
+
+此文件名主要用于**机器排序**和内部标识。文档的**人类可读标题**以 `standard_title` 元数据和文档内的 H1 标题为准。自动化脚本负责处理元数据提取、PWXY 计算、标题清理、语言后缀、基于 GitHub 的[编辑与反馈按钮的自动实现](/tools/contributing_in_page.py)等所有细节。
+
+## 面向受众的设计原则:渐进式披露与路径优化
+
+本系统的设计贯穿着“渐进式信息披露” (Progressive Disclosure) 的核心原则,旨在优化不同背景开发者的信息获取路径。
+
+1. 优先级机制 (P 值区分): 这是实现渐进式披露的主要手段。系统性地将基础、核心、高频使用的内容 (P=0) 与高级、复杂、特定场景的内容 (P=9) 分离开来。这保证了用户默认首先接触到的是一个相对精简、聚焦核心的知识体系。
+2. 结构化分类 (基于 type.primary 和 type.detail): 即便在同一优先级内,内容也被清晰地组织成不同类别和主题,为所有用户(尤其是经验丰富的用户)提供了一个可预测的“信息地图”。
+
+基于以上原则,系统致力于为不同目标受众提供优化的体验:
+
+- 学习者与初探者 (Learners & Newcomers, incl. Citizen Developers): 通过默认阅览 P=0 的内容序列,获得一条从概念到基础实践的、结构化的学习路径,从而循序渐进,降低初期认知负荷。
+- 经验丰富的开发者与贡献者 (Experienced Developers & Contributors): 利用清晰的结构进行高效的**随机访问**,快速定位特定参考信息或实现细节,或根据需要直接深入 P=9 的高级内容。文档库对其而言更像一本可快速查阅的技术手册。
+- 自动化工具与服务 (e.g., LLMs): 承认其与人类不同的信息消费模式。结构化的元数据**使我们能够**为其生成专门优化的、可能更扁平化的数据输入流(如 `llms-full.txt`),提升其理解效率。
+
+总而言之,dimensions 系统并非试图用一种方式满足所有人,而是通过**结构化分类、优先级排序和元数据驱动的自动化**,为不同需求的用户群体提供各自相对最优的信息访问和学习路径。
+
+## 系统收益
+
+- 结构清晰 & 导航改善: 基于可靠的分类和可预测的排序。
+- 一致性体验: 自动化确保所有文档遵循相同模式。
+- 受众适配: 通过优先级区分,提供差异化的阅读路径。
+- 高可维护性: 调整分类或排序逻辑只需修改中心配置和脚本。
+- 可扩展性: 易于添加新的内容类型或难度级别。
+- 促进协作: 为作者、审阅者和系统维护者提供了共同的理解基础和协作标准。
+
+## 创作流程、角色与治理:应对现实挑战
+
+建立高质量文档库是一个涉及多方协作的过程,并面临现实挑战。dimensions 系统旨在成为这个过程中的一个结构化框架和协作工具。
+
+### 协作模型中的关键角色
+
+- 创作者 (Creator / Author):
+
+ - 核心职责: 创作准确、清晰、有价值的文档内容。
+ - 协作要点: 学习并尝试理解 dimensions 分类体系,在提交时为文档**尽力**打上最合适的元数据标签(dimensions, standard_title, language)。准确的初始分类有助于后续流程。
+
+- 审阅者 (Owner / Reviewer):
+
+ - 核心职责: 对文档内容的**准确性、清晰度、范围**以及**元数据的恰当性**进行最终把关。这是确保文档库质量和结构一致性的关键环节。
+ - 能力要求: 需要具备对相关技术领域的深刻理解,**同时**要能理解 dimensions 系统背后的**整体信息架构和受众细分逻辑**(这往往需要一定的系统性思维,类似于架构师视角)。
+ - 协作要点: 利用 dimensions 作为客观“标尺”来评审内容定位。如果元数据不准确或内容边界模糊,应指导创作者修改或直接修正元数据。**最终合并到主分支的元数据决定了文档的呈现**。
+
+- 运营者 (Operator / DevOps / SRE / Doc System Maintainer):
+
+ - 核心职责: 维护和优化文档自动化构建、测试、部署的流程与工具。
+ - 协作要点: 确保自动化脚本正确、高效地解析元数据并生成最终产物(排序的文件、导航数据等)。他们关注的是**流程的健壮性**,而非单篇文档的内容细节。
+
+### 应对文档协作的现实挑战
+
+这种角色划分有助于我们正视并尝试缓解一些常见的文档困境:
+
+- “程序员不爱写文档” vs. “写文档的不懂技术”:
+
+ - dimensions 系统**降低了对创作者(程序员)的要求**:首要任务是写准技术内容,元数据分类可以“尽力而为”,因为有审阅者把关。
+ - 同时,它**提升了对审阅者的要求**,承认了高质量文档需要深刻的技术理解和架构性思维,并将这种高阶能力引导到对文档结构和元数据的把控上。
+ - 对于可能存在的“专职文档工程师”,他们可以作为创作者,或者辅助审阅者进行元数据校验和内容润色,角色更加灵活。
+
+- 高质量审阅者稀缺且“性价比”疑虑:
+
+ - 确实,具备所需技术深度和架构思维的审阅者是宝贵的。他们可能会觉得审阅文档相比核心功能开发“价值较低”。
+ - 关键在于提升认知: 必须强调,**高质量、结构化的文档对于开发者生态的成功、降低支持成本、提升产品采用率具有极高的战略价值**。dimensions 系统正是将审阅者的高阶思维**显性化、规范化**,并**直接转化为可衡量的文档体验提升**的工具。审阅文档不再是“杂活”,而是塑造开发者体验、构建知识体系的关键一环。
+
+- 流程保障: dimensions 系统提供了一个**协作框架**。它鼓励创作者思考内容定位,赋予审阅者清晰的评估标准和修正权限,并让运营者可以独立地维护自动化流程。通过将元数据检查纳入 PR 流程(类似 Code Review),可以制度化地保障最终产出的质量。
+
+## 扩展性与代码的角色
+
+- 系统扩展: dimensions 的分类体系并非静态。随着 Dify 功能的演进,可以(也应该)适时增加新的 primary 类型、detail 类型或 level。这主要通过更新中央映射配置和(如有必要)调整生成脚本来实现,体现了系统的灵活性。
+- 文档与代码的关系: 开发者文档(特别是 Reference 和高级 Implementation 部分)的核心目标之一是作为理解和使用**源代码**的**有效向导**。它应解释代码层面的设计、用法、约束,并通过链接等方式促进开发者在需要时**无缝地深入到源代码**这一最终的、最精确的内容中。文档不是代码的替代品,而是其**高质量的另一面和解释器**。
diff --git a/tools/contributing_in_page.py b/tools/contributing_in_page.py
new file mode 100644
index 00000000..b0af6431
--- /dev/null
+++ b/tools/contributing_in_page.py
@@ -0,0 +1,192 @@
+import os
+
+BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+
+
+def generate_contributing_section(repo_owner, repo_name, target_dir_relative, filename, language):
+ repo_url = f"https://github.com/{repo_owner}/{repo_name}"
+ if language == "zh":
+ contributing_section = f"""
+{{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}}
+
+
+
+ 通过直接提交修改来帮助改进文档内容
+
+
+ 发现错误或有改进建议?请提交问题反馈
+
+
+"""
+ elif language == "en":
+ contributing_section = f"""
+{{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}}
+
+
+
+ Help improve our documentation by contributing directly
+
+
+ Found an error or have suggestions? Let us know
+
+
+"""
+ elif language == "jp":
+ contributing_section = f"""
+{{/*
+Contributing Section
+DO NOT edit this section!
+It will be automatically generated by the script.
+*/}}
+
+
+
+ 直接貢献することでドキュメントの改善にご協力ください
+
+
+ エラーを見つけたり提案がありますか?お知らせください
+
+
+"""
+ else:
+ raise ValueError(
+ "Unsupported language. Supported languages are 'zh', 'en', and 'jp'.")
+
+ return contributing_section
+
+
+def append_content_to_files(
+ target_dir_relative, repo_owner, repo_name, language, base_dir=BASE_DIR, file_extension=".mdx"
+):
+ target_path = os.path.join(base_dir, target_dir_relative)
+
+ if not os.path.isdir(target_path):
+ print(f"Error: Directory '{target_path}' not found.")
+ return
+ fix_md_endings(target_dir_relative, base_dir, file_extension)
+ appended_count = 0
+ error_count = 0
+ for filename in os.listdir(target_path):
+ if filename.endswith(file_extension):
+ filepath = os.path.join(target_path, filename)
+ try:
+ # Open in text append mode to write
+ with open(filepath, "a", encoding="utf-8") as f:
+ f.write(generate_contributing_section(
+ repo_owner, repo_name, target_dir_relative, filename, language))
+ appended_count += 1
+ except (IOError, OSError) as e:
+ print(f"Error processing file {filepath}: {e}")
+ error_count += 1
+ print(
+ f"Finished processing directory '{target_path}'. "
+ f"Appended to {appended_count} files. Encountered {error_count} errors."
+ )
+
+
+def remove_contributing_section(target_dir_relative, base_dir=BASE_DIR, file_extension=".mdx"):
+ import re
+ target_path = os.path.join(base_dir, target_dir_relative)
+ if not os.path.isdir(target_path):
+ print(f"Error: Directory '{target_path}' not found.")
+ return
+ removed_count = 0
+ error_count = 0
+ for filename in os.listdir(target_path):
+ if filename.endswith(file_extension):
+ filepath = os.path.join(target_path, filename)
+ try:
+ with open(filepath, "r", encoding="utf-8") as f:
+ content = f.read()
+ # 更新正则表达式以更精确地匹配注释和 CardGroup 结构
+ # 这将匹配从 {/* Contributing Section ... */} 开始到最近的 结束的块
+ pattern = re.compile(
+ r"\{\/\*\s*Contributing Section[\s\S]*?\*\/\}[\s\S]*?[\s\S]*?", re.DOTALL)
+ # 移除 count=1 以确保替换所有匹配项
+ new_content = re.sub(pattern, "", content)
+ if new_content != content:
+ with open(filepath, "w", encoding="utf-8") as f:
+ f.write(new_content)
+ removed_count += 1
+ except (IOError, OSError) as e:
+ print(f"Error processing file {filepath}: {e}")
+ error_count += 1
+ fix_md_endings(target_dir_relative, base_dir, file_extension)
+ print(
+ f"Removed from {removed_count} files. Encountered {error_count} errors.")
+
+
+def fix_md_endings(target_dir_relative, base_dir=BASE_DIR, file_extension=".mdx"):
+ target_path = os.path.join(base_dir, target_dir_relative)
+ if not os.path.isdir(target_path):
+ print(f"Error: Directory '{target_path}' not found.")
+ return
+ fixed_count = 0
+ error_count = 0
+ for filename in os.listdir(target_path):
+ if filename.endswith(file_extension):
+ filepath = os.path.join(target_path, filename)
+ try:
+ with open(filepath, "r", encoding="utf-8") as f:
+ content = f.read()
+ processed_content = content.replace("\r\n", "\n")
+ processed_content = processed_content.rstrip()
+ processed_content += "\n"
+ if processed_content != content:
+ with open(filepath, "w", encoding="utf-8") as f:
+ f.write(processed_content)
+ fixed_count += 1
+ except (IOError, OSError) as e:
+ print(f"Error processing file {filepath}: {e}")
+ error_count += 1
+ print(
+ f"Fixed line endings and trailing lines in {fixed_count} files. Encountered {error_count} errors.")
+
+
+if __name__ == "__main__":
+ remove_contributing_section(target_dir_relative="plugin_dev_en")
+ remove_contributing_section(target_dir_relative="plugin_dev_zh")
+ append_content_to_files(
+ target_dir_relative="plugin_dev_en",
+ repo_owner="langgenius",
+ repo_name="dify-docs-mintlify",
+ language="en"
+ )
+ append_content_to_files(
+ target_dir_relative="plugin_dev_zh",
+ repo_owner="langgenius",
+ repo_name="dify-docs-mintlify",
+ language="zh"
+ )
diff --git a/tools/letsgo.py b/tools/letsgo.py
new file mode 100644
index 00000000..e17fa2df
--- /dev/null
+++ b/tools/letsgo.py
@@ -0,0 +1,601 @@
+import json
+import os
+import re
+from collections import defaultdict
+
+# --- 配置 ---
+refresh = True # 如果为 True,将清空指定版本的 tabs
+DOCS_JSON_PATH = 'docs.json'
+
+# --- 简体中文配置(docs_config) ---
+PLUGIN_DEV_ZH = {
+ "DOCS_DIR": "plugin_dev_zh", # 插件开发文档目录
+ "LANGUAGE_CODE": "简体中文", # 注意:虽然变量名是 LANGUAGE_CODE,但会部署为 docs.json 中的 'version' 值。
+ "FILE_EXTENSION": ".zh.mdx",
+ "TARGET_TAB_NAME": "插件开发", # 新增:目标 Tab 名称
+ "FILENAME_PATTERN": re.compile(r'^(\d{4})-(.*?)\.zh\.mdx$'), # 新增:文件名匹配模式
+ "PWX_TO_GROUP_MAP": {
+ # --- PWX 到 Group 名称的映射 (统一到 "插件开发" Tab) ---
+ # (P, W, X) -> (tab_name, group_name, nested_group_name)
+ # Tab: 插件开发
+ # Group: 概念与入门
+ ('0', '1', '1'): ("插件开发", "概念与入门", "概览"),
+ ('0', '1', '3'): ("插件开发", "概念与入门", None),
+ # Group: 开发实践
+ ('0', '2', '1'): ("插件开发", "开发实践", "快速开始"),
+ ('0', '2', '2'): ("插件开发", "开发实践", "开发 Dify 插件"),
+ # Group: 贡献与发布
+ ('0', '3', '1'): ("插件开发", "贡献与发布", "行为准则与规范"),
+ ('0', '3', '2'): ("插件开发", "贡献与发布", "发布与上架"),
+ ('0', '3', '3'): ("插件开发", "贡献与发布", "常见问题解答"),
+ # Group: 实践案例与示例
+ ('0', '4', '3'): ("插件开发", "实践案例与示例", "开发示例"),
+ # Group: 高级开发
+ ('9', '2', '2'): ("插件开发", "高级开发", "Extension 与 Agent"),
+ ('9', '2', '3'): ("插件开发", "高级开发", "Extension 与 Agent"),
+ ('9', '4', '3'): ("插件开发", "高级开发", "Extension 与 Agent"),
+ ('9', '2', '4'): ("插件开发", "高级开发", "反向调用"),
+ # Group: Reference & Specifications
+ ('0', '4', '1'): ("插件开发", "Reference & Specifications", "核心规范与功能"),
+ },
+ "DESIRED_GROUP_ORDER": [
+ "概念与入门",
+ "开发实践",
+ "贡献与发布",
+ "实践案例与示例",
+ "高级开发",
+ "Reference & Specifications" # 确保这个在最后
+ ]
+}
+
+# --- English Configuration ---
+PLUGIN_DEV_EN = {
+ "DOCS_DIR": "plugin_dev_en", # Plugin development documentation directory
+ "LANGUAGE_CODE": "English", # Note: Although the variable name is LANGUAGE_CODE, it will be deployed as the 'version' value in docs.json.
+ "FILE_EXTENSION": ".en.mdx",
+ "TARGET_TAB_NAME": "Plugin Development",
+ "FILENAME_PATTERN": re.compile(r'^(\d{4})-(.*?)\.en\.mdx$'),
+ "PWX_TO_GROUP_MAP": {
+ # --- PWX to Group Name Mapping (Unified under the "Plugin Development" Tab) ---
+ # (P, W, X) -> (tab_name, group_name, nested_group_name)
+ # Tab: Plugin Development
+ # Group: Concepts & Getting Started
+ ('0', '1', '1'): ("Plugin Development", "Concepts & Getting Started", "Overview"),
+ ('0', '1', '3'): ("Plugin Development", "Concepts & Getting Started", None),
+ # Group: Development Practices
+ ('0', '2', '1'): ("Plugin Development", "Development Practices", "Quick Start"),
+ ('0', '2', '2'): ("Plugin Development", "Development Practices", "Developing Dify Plugins"),
+ # Group: Contribution & Publishing
+ ('0', '3', '1'): ("Plugin Development", "Contribution & Publishing", "Code of Conduct & Standards"),
+ ('0', '3', '2'): ("Plugin Development", "Contribution & Publishing", "Publishing & Listing"),
+ ('0', '3', '3'): ("Plugin Development", "Contribution & Publishing", "FAQ"),
+ # Group: Examples & Use Cases
+ ('0', '4', '3'): ("Plugin Development", "Examples & Use Cases", "Development Examples"),
+ # Group: Advanced Development
+ ('9', '2', '2'): ("Plugin Development", "Advanced Development", "Extension & Agent"),
+ ('9', '2', '3'): ("Plugin Development", "Advanced Development", "Extension & Agent"),
+ ('9', '4', '3'): ("Plugin Development", "Advanced Development", "Extension & Agent"),
+ ('9', '2', '4'): ("Plugin Development", "Advanced Development", "Reverse Calling"),
+ # Group: Reference & Specifications
+ ('0', '4', '1'): ("Plugin Development", "Reference & Specifications", "Core Specifications & Features"),
+ },
+ "DESIRED_GROUP_ORDER": [
+ "Concepts & Getting Started",
+ "Development Practices",
+ "Contribution & Publishing",
+ "Examples & Use Cases",
+ "Advanced Development",
+ "Reference & Specifications" # Ensure this is last
+ ]
+}
+
+# --- 辅助函数 ---
+
+def clear_tabs_if_refresh(navigation_data, version_code, target_tab_name, do_refresh):
+ """如果 do_refresh 为 True,则查找指定版本和目标 Tab,并清空其 groups 列表"""
+ if not do_refresh:
+ return False # 未执行清空
+
+ if not navigation_data or 'versions' not in navigation_data:
+ print("警告: 'navigation.versions' 未找到,无法清空 tabs。")
+ return False
+
+ version_found = False
+ tab_found_and_cleared = False
+ for version_nav in navigation_data.get('versions', []):
+ if version_nav.get('version') == version_code:
+ version_found = True
+ target_tab = None
+ if 'tabs' in version_nav and isinstance(version_nav['tabs'], list):
+ for tab in version_nav['tabs']:
+ if isinstance(tab, dict) and tab.get('tab') == target_tab_name:
+ target_tab = tab
+ break
+
+ if target_tab:
+ if 'groups' in target_tab:
+ target_tab['groups'] = []
+ print(f"信息: 已清空版本 '{version_code}' 下 Tab '{target_tab_name}' 的 groups (因为 refresh=True)。")
+ tab_found_and_cleared = True
+ else:
+ # 如果 'groups' 不存在,也视为一种“清空”状态,或者可以创建一个空的
+ target_tab['groups'] = []
+ print(f"信息: 版本 '{version_code}' 下 Tab '{target_tab_name}' 没有 'groups' 键,已确保其为空列表 (因为 refresh=True)。")
+ tab_found_and_cleared = True
+ else:
+ print(f"警告: 在版本 '{version_code}' 中未找到目标 Tab '{target_tab_name}',无法清空其 groups。")
+ break # 找到版本后即可退出循环
+
+ if not version_found:
+ print(f"警告: 未找到版本 '{version_code}',无法清空任何 Tab。")
+ return False
+
+ return tab_found_and_cleared
+
+
+def get_page_path(filename, docs_config): # docs_config 参数保留,但 FILE_EXTENSION 不再用于此处的后缀移除
+ """从 mdx 文件名获取 mintlify 页面路径 (固定去掉末尾 .mdx 后缀)"""
+ docs_dir = docs_config["DOCS_DIR"]
+ # 固定移除末尾的 .mdx,以保留 .zh 或 .en 等语言标识
+ if filename.endswith('.mdx'):
+ base_filename = filename[:-len('.mdx')]
+ else:
+ # 如果不以 .mdx 结尾,则引发错误,因为这是预期格式
+ raise ValueError(f"错误: 文件名 '{filename}' 不以 '.mdx' 结尾,无法处理。")
+
+ return os.path.join(docs_dir, base_filename)
+
+
+def extract_existing_pages(navigation_data, version_code, target_tab_name):
+ """递归提取指定版本和目标 Tab 下所有已存在的页面路径"""
+ existing_pages = set()
+ target_version_nav = None
+ target_tab_nav = None # 新增:用于存储找到的目标 Tab 对象
+
+ if not navigation_data or 'versions' not in navigation_data:
+ print("警告: 'navigation.versions' 未找到")
+ return existing_pages, None, None # 返回三个值
+
+ # 查找目标版本
+ for version_nav in navigation_data.get('versions', []):
+ if version_nav.get('version') == version_code:
+ target_version_nav = version_nav
+ break
+
+ if not target_version_nav:
+ print(f"警告: 版本 '{version_code}' 在 docs.json 中未找到")
+ return existing_pages, None, None # 返回三个值
+
+ # 在目标版本中查找目标 Tab
+ for tab in target_version_nav.get('tabs', []):
+ if isinstance(tab, dict) and tab.get('tab') == target_tab_name:
+ target_tab_nav = tab # 存储找到的 Tab 对象
+ # 仅从目标 Tab 中提取页面
+ for group in tab.get('groups', []):
+ if isinstance(group, dict):
+ _recursive_extract(group, existing_pages)
+ break # 找到目标 Tab 后即可退出循环
+
+ if not target_tab_nav:
+ print(f"警告: 在版本 '{version_code}' 中未找到 Tab '{target_tab_name}',无法提取现有页面。")
+ # 即使 Tab 不存在,也返回版本导航对象,以便后续可能创建 Tab
+ return existing_pages, target_version_nav, None
+
+ # 返回提取到的页面、版本导航对象和目标 Tab 对象
+ return existing_pages, target_version_nav, target_tab_nav
+
+
+def _recursive_extract(group_item, pages_set):
+ """递归辅助函数"""
+ # Ensure group_item is a dictionary before proceeding
+ if not isinstance(group_item, dict):
+ return
+
+ if 'pages' in group_item and isinstance(group_item['pages'], list):
+ for page in group_item['pages']:
+ if isinstance(page, str):
+ pages_set.add(page)
+ elif isinstance(page, dict) and 'group' in page:
+ # Recurse into nested groups
+ _recursive_extract(page, pages_set)
+
+
+def remove_obsolete_pages(target_tab_data, pages_to_remove):
+ """递归地从目标 Tab 的 groups 结构中移除失效页面路径。
+ 注意:此函数直接修改传入的 target_tab_data 字典。
+ """
+ if not isinstance(target_tab_data, dict) or 'groups' not in target_tab_data:
+ # 如果输入不是预期的 Tab 结构,则直接返回
+ return
+
+ groups = target_tab_data.get('groups', [])
+ if not isinstance(groups, list):
+ # 如果 groups 不是列表,也无法处理
+ return
+
+ # 使用索引迭代以安全地移除项
+ i = 0
+ while i < len(groups):
+ group_item = groups[i]
+ if isinstance(group_item, dict):
+ # 递归处理 group 内部的 pages
+ _remove_obsolete_from_group(group_item, pages_to_remove)
+ # 如果处理后 group 的 pages 为空(且没有嵌套 group),可以选择移除该 group
+ # 当前逻辑:保留空 group 结构
+ if not group_item.get('pages'):
+ print(f"信息: Group '{group_item.get('group')}' 清理后为空,已保留结构。")
+ i += 1
+ else:
+ # 如果 groups 列表中包含非字典项(不符合预期),则跳过
+ i += 1
+
+def _remove_obsolete_from_group(group_dict, pages_to_remove):
+ """辅助函数,递归处理单个 group 或 nested group 内的 pages"""
+ if not isinstance(group_dict, dict) or 'pages' not in group_dict:
+ return
+
+ pages = group_dict.get('pages', [])
+ if not isinstance(pages, list):
+ return
+
+ new_pages = []
+ for page_item in pages:
+ if isinstance(page_item, str):
+ if page_item not in pages_to_remove:
+ new_pages.append(page_item)
+ else:
+ print(f" - {page_item} (从 Group '{group_dict.get('group')}' 移除)")
+ elif isinstance(page_item, dict) and 'group' in page_item:
+ # 递归处理嵌套的 group
+ _remove_obsolete_from_group(page_item, pages_to_remove)
+ # 保留嵌套 group 结构,即使它变空
+ if page_item or page_item.get('pages'): # 检查字典是否为空或 pages 是否存在
+ new_pages.append(page_item)
+ else:
+ print(f"信息: 嵌套 Group '{page_item.get('group')}' 清理后为空,已保留结构。")
+ new_pages.append(page_item) # 仍然添加空的嵌套组结构
+ else:
+ # 保留无法识别的项
+ new_pages.append(page_item)
+ group_dict['pages'] = new_pages
+
+
+def find_or_create_target_group(target_version_nav, tab_name, group_name, nested_group_name):
+ # 注意:target_version_nav 是特定版本对象,例如 {"version": "简体中文", "tabs": [...]}
+ target_tab = None
+ # Ensure 'tabs' exists and is a list
+ if 'tabs' not in target_version_nav or not isinstance(target_version_nav['tabs'], list):
+ target_version_nav['tabs'] = []
+
+ for tab in target_version_nav['tabs']:
+ if isinstance(tab, dict) and tab.get('tab') == tab_name:
+ target_tab = tab
+ break
+ if target_tab is None:
+ target_tab = {'tab': tab_name, 'groups': []}
+ target_version_nav['tabs'].append(target_tab)
+
+ target_group = None
+ # Ensure 'groups' exists and is a list
+ if 'groups' not in target_tab or not isinstance(target_tab['groups'], list):
+ target_tab['groups'] = []
+
+ for group in target_tab['groups']:
+ if isinstance(group, dict) and group.get('group') == group_name:
+ target_group = group
+ break
+ if target_group is None:
+ target_group = {'group': group_name, 'pages': []}
+ target_tab['groups'].append(target_group)
+
+ # Ensure 'pages' exists in the target_group and is a list
+ if 'pages' not in target_group or not isinstance(target_group['pages'], list):
+ target_group['pages'] = []
+
+ # Default container is the top-level group's pages list
+ target_pages_container = target_group['pages']
+
+ if nested_group_name:
+ target_nested_group = None
+ # Find existing nested group
+ for item in target_group['pages']:
+ if isinstance(item, dict) and item.get('group') == nested_group_name:
+ target_nested_group = item
+ # Ensure pages list exists in nested group
+ target_pages_container = target_nested_group.setdefault(
+ 'pages', [])
+ # Ensure it's actually a list after setdefault
+ if not isinstance(target_pages_container, list):
+ target_nested_group['pages'] = []
+ target_pages_container = target_nested_group['pages']
+ break
+ # If not found, create it
+ if target_nested_group is None:
+ target_nested_group = {'group': nested_group_name, 'pages': []}
+ # Check if target_group['pages'] is already the container we want to add to
+ # This logic assumes nested groups are *always* dicts within the parent's 'pages' list
+ target_group['pages'].append(target_nested_group)
+ target_pages_container = target_nested_group['pages']
+
+ # Final check before returning
+ if not isinstance(target_pages_container, list):
+ # 这表示内部逻辑错误,应该引发异常
+ raise RuntimeError(
+ f"内部错误: 无法为 Tab='{tab_name}', Group='{group_name}', Nested='{nested_group_name}' 获取有效的 pages 列表。")
+
+ return target_pages_container
+
+# --- 主逻辑 ---
+
+
+def get_group_sort_key(group_dict, docs_config):
+ """为排序提供 key,根据 DESIRED_GROUP_ORDER 返回索引,未知组放在最后"""
+ group_name = group_dict.get('group', '')
+ desired_order = docs_config["DESIRED_GROUP_ORDER"]
+ try:
+ return desired_order.index(group_name)
+ except ValueError:
+ return len(desired_order) # 将未在列表中的组排在最后
+
+
+def main(docs_config, navigation_data): # navigation_data: 传入内存中的 navigation 字典供直接修改
+ """处理单个文档配置,并直接修改传入的 navigation_data"""
+ print(f"\n--- 开始处理版本: {docs_config['LANGUAGE_CODE']} / Tab: {docs_config['TARGET_TAB_NAME']} ---")
+
+ # 从 docs_config 获取配置值
+ language_code = docs_config["LANGUAGE_CODE"]
+ docs_dir = docs_config["DOCS_DIR"]
+ file_extension = docs_config["FILE_EXTENSION"]
+ pwx_to_group_map = docs_config["PWX_TO_GROUP_MAP"]
+ filename_pattern = docs_config["FILENAME_PATTERN"] # 使用配置中的 pattern
+ target_tab_name = docs_config["TARGET_TAB_NAME"] # 使用配置中的 tab name
+
+ # 1. 清理或准备版本导航 (不再加载 JSON,直接使用传入的 navigation_data)
+ navigation = navigation_data # 使用传入的 navigation 对象进行操作
+
+ # 使用 language_code 和 target_tab_name 清理目标 Tab
+ was_refreshed = clear_tabs_if_refresh(navigation, language_code, target_tab_name, refresh)
+ if was_refreshed:
+ print(f"继续执行 Tab '{target_tab_name}' 的后续页面提取和添加操作...")
+
+ # 2. 提取目标 Tab 的现有页面或创建版本/Tab 导航
+ existing_pages, target_version_nav, target_tab_nav = extract_existing_pages(
+ navigation, language_code, target_tab_name)
+
+ if target_version_nav is None:
+ print(f"信息:在导航数据中未找到版本 '{language_code}',将创建。")
+ if 'versions' not in navigation: # 确保 versions 列表存在
+ navigation['versions'] = []
+ target_version_nav = {"version": language_code, "tabs": []}
+ navigation['versions'].append(target_version_nav)
+ existing_pages = set()
+ target_tab_nav = None # 版本是新建的,Tab 肯定不存在
+
+ # 如果目标 Tab 不存在,需要创建它
+ if target_tab_nav is None:
+ print(f"信息: 在版本 '{language_code}' 中未找到 Tab '{target_tab_name}',将创建。")
+ target_tab_nav = {'tab': target_tab_name, 'groups': []}
+ # 确保 target_version_nav['tabs'] 是列表
+ if 'tabs' not in target_version_nav or not isinstance(target_version_nav['tabs'], list):
+ target_version_nav['tabs'] = []
+ target_version_nav['tabs'].append(target_tab_nav)
+ existing_pages = set() # 新 Tab 没有现有页面
+
+ print(f"找到 {len(existing_pages)} 个已存在的页面 (版本: '{language_code}', Tab: '{target_tab_name}')。")
+
+ # 3. 扫描文件系统 (这部分不变,扫描目录下的所有匹配文件)
+ filesystem_pages = set()
+ valid_files = []
+ if not os.path.isdir(docs_dir):
+ # 如果目录不存在,则无法继续处理此配置,引发错误
+ raise FileNotFoundError(f"错误: 配置 '{language_code}' 的文档目录 '{docs_dir}' 不存在。")
+ else:
+ for filename in os.listdir(docs_dir):
+ # 使用配置中的 filename_pattern
+ if filename.endswith(file_extension) and filename_pattern.match(filename):
+ try: # 添加 try-except 块以捕获 get_page_path 可能引发的 ValueError
+ page_path = get_page_path(filename, docs_config)
+ filesystem_pages.add(page_path)
+ valid_files.append(filename)
+ except ValueError as e:
+ # 从 get_page_path 捕获到错误,打印并继续处理其他文件,或重新引发以停止
+ print(f"错误处理文件 '{filename}': {e}。将跳过此文件。")
+ # 如果希望停止整个过程,取消注释下一行:
+ # raise e
+ print(f"在 '{docs_dir}' 找到 {len(filesystem_pages)} 个有效的文档文件。")
+
+
+ # 4. 计算差异 (相对于目标 Tab 的 existing_pages)
+ new_files_paths = filesystem_pages - existing_pages
+ removed_files_paths = existing_pages - filesystem_pages
+
+ print(f"新增文件数 (相对于 Tab '{target_tab_name}'): {len(new_files_paths)}")
+ print(f"移除文件数 (相对于 Tab '{target_tab_name}'): {len(removed_files_paths)}")
+
+ # 5. 移除失效页面 (仅从目标 Tab 移除)
+ if removed_files_paths and target_tab_nav: # 确保目标 Tab 存在
+ print(f"正在从 Tab '{target_tab_name}' 移除失效页面...")
+ remove_obsolete_pages(target_tab_nav, removed_files_paths) # 直接传入目标 Tab 对象
+ print(f"已处理从 Tab '{target_tab_name}' 移除: {removed_files_paths}")
+ elif removed_files_paths:
+ print(f"警告: 存在失效页面 {removed_files_paths},但未找到目标 Tab '{target_tab_name}' 进行移除。")
+
+
+ # 6. 添加新页面 (逻辑不变,但 find_or_create_target_group 会确保添加到正确的 Tab 和 Group)
+ if new_files_paths:
+ print(f"正在向 Tab '{target_tab_name}' 添加新页面...")
+ new_files_sorted = sorted(
+ [f for f in valid_files if get_page_path(f, docs_config) in new_files_paths])
+
+ groups_to_add = defaultdict(list)
+ for filename in new_files_sorted:
+ match = filename_pattern.match(filename) # 使用配置中的 pattern
+ if match:
+ pwxy = match.group(1)
+ if len(pwxy) >= 3:
+ p, w, x = pwxy[0], pwxy[1], pwxy[2]
+ try: # 包裹 get_page_path 调用
+ page_path = get_page_path(filename, docs_config)
+ except ValueError as e:
+ print(f"错误处理文件 '{filename}' (添加阶段): {e}。将跳过此文件。")
+ continue # 跳过这个文件
+
+ group_key = (p, w, x)
+ if group_key in pwx_to_group_map:
+ map_result = pwx_to_group_map[group_key]
+ current_tab_name_from_map = map_result[0]
+ # 强制使用配置的目标 Tab 名称
+ if current_tab_name_from_map != target_tab_name:
+ print(f"警告: 文件 '{filename}' 根据 PWX 映射到 Tab '{current_tab_name_from_map}',但当前配置强制处理 Tab '{target_tab_name}'。将添加到 '{target_tab_name}'。")
+ # 始终使用配置中定义的 target_tab_name
+ tab_name_to_use = target_tab_name
+
+ if len(map_result) == 3:
+ _, group_name, nested_group_name = map_result
+ else: # 兼容旧格式或只有两项的情况
+ if len(map_result) >= 2:
+ _, group_name = map_result[:2] # 取前两项
+ else:
+ # 处理 map_result 项数不足的情况
+ print(f"错误: PWX_TO_GROUP_MAP 中键 '{group_key}' 的值 '{map_result}' 格式不正确,至少需要两项。跳过文件 '{filename}'。")
+ continue
+ nested_group_name = None # 假设没有嵌套组
+
+ groups_to_add[(tab_name_to_use, group_name, nested_group_name)].append(
+ page_path)
+ else:
+ print(
+ f"警告: 文件 '{filename}' 的 PWX 前缀 ('{p}', '{w}', '{x}') 在 PWX_TO_GROUP_MAP 中没有找到映射,将跳过添加。")
+ else:
+ # 数字前缀不足3位是文件名格式错误,应引发异常
+ raise ValueError(
+ f"错误: 文件 '{filename}' 的数字前缀 '{pwxy}' 不足3位,无法解析 PWX。")
+
+ for (tab_name, group_name, nested_group_name), pages_to_append in groups_to_add.items():
+ # 确保只添加到目标 Tab 下 (此检查现在是多余的,因为上面强制使用了 target_tab_name)
+ # if tab_name == target_tab_name:
+ print(
+ f" 添加到 Tab='{tab_name}', Group='{group_name}', Nested='{nested_group_name or '[无]'}' : {len(pages_to_append)} 个页面")
+ # find_or_create_target_group 现在需要 target_version_nav 来定位或创建 Tab
+ target_pages_list = find_or_create_target_group(
+ target_version_nav, tab_name, group_name, nested_group_name) # tab_name 此时应等于 target_tab_name
+
+ if isinstance(target_pages_list, list):
+ for new_page in pages_to_append:
+ if new_page not in target_pages_list:
+ target_pages_list.append(new_page)
+ print(f" + {new_page}")
+ else:
+ # find_or_create_target_group 内部出错时会抛出 RuntimeError
+ # 这里可以加日志,但理论上不应到达
+ print(f"错误: 未能为 Tab='{tab_name}', Group='{group_name}', Nested='{nested_group_name}' 获取有效的 pages 列表进行添加。")
+ # else: # 这个 else 分支现在不会被触发
+ # print(f"信息: 跳过向非目标 Tab '{tab_name}' 添加页面 (目标 Tab: '{target_tab_name}')。")
+
+
+ # <-- 排序 Group (仅排序目标 Tab 内的 Group) -->
+ print(f"正在排序 Tab '{target_tab_name}' 内的 Group...")
+ if target_tab_nav and 'groups' in target_tab_nav: # 确保目标 Tab 和 groups 存在
+ groups_list = [g for g in target_tab_nav['groups'] if isinstance(g, dict)]
+ groups_list.sort(key=lambda g: get_group_sort_key(g, docs_config))
+ target_tab_nav['groups'] = groups_list
+ print(f" 已对 Tab '{target_tab_name}' 中的 Group 进行排序。")
+ elif target_tab_nav:
+ print(f" Tab '{target_tab_name}' 中没有 'groups' 或为空,无需排序。")
+ else:
+ print(f" 未找到 Tab '{target_tab_name}',无法排序 Group。")
+
+
+ # 不再返回 docs_data,因为直接修改了传入的 navigation_data
+ print(f"--- 完成处理版本: {docs_config['LANGUAGE_CODE']} / Tab: {docs_config['TARGET_TAB_NAME']} ---")
+
+
+def load_docs_data(path):
+ """加载 JSON 文件,处理文件不存在和格式错误的情况"""
+ try:
+ with open(path, 'r', encoding='utf-8') as f:
+ return json.load(f)
+ except FileNotFoundError:
+ print(f"信息: {path} 未找到,将创建新的结构。")
+ return {"navigation": {"versions": []}} # 返回初始结构
+ except json.JSONDecodeError as e:
+ # 引发更具体的错误,而不是返回 None
+ raise json.JSONDecodeError(f"错误: {path} 格式错误。无法继续。- {e.msg}", e.doc, e.pos)
+
+def save_docs_data(path, data):
+ """保存 JSON 数据到文件"""
+ try:
+ with open(path, 'w', encoding='utf-8') as f:
+ json.dump(data, f, ensure_ascii=False, indent=4)
+ print(f"\n成功更新 {path},包含所有已处理的版本。")
+ # 不再需要返回 True/False,因为异常会处理失败情况
+ except IOError as e:
+ # 引发 IO 错误
+ raise IOError(f"错误: 无法写入 {path} - {e}")
+ except Exception as e:
+ # 引发其他未知错误
+ raise Exception(f"写入 {path} 时发生未知错误: {e}")
+
+def process_configurations(configs, docs_path):
+ """加载数据,处理所有有效配置,然后保存数据"""
+ # 1. 加载初始数据
+ try:
+ current_docs_data = load_docs_data(docs_path)
+ except json.JSONDecodeError as e:
+ print(e) # 打印加载错误信息
+ return # 加载失败则退出
+ # current_docs_data 不会是 None,因为 load_docs_data 要么返回数据要么引发异常
+
+ # 2. 确保基本结构存在
+ navigation_data = current_docs_data.setdefault('navigation', {}) # 获取 navigation 字典
+ navigation_data.setdefault('versions', [])
+
+ # 3. 筛选有效配置
+ valid_configs = []
+ for config in configs:
+ required_keys = ["DOCS_DIR", "LANGUAGE_CODE", "FILE_EXTENSION",
+ "PWX_TO_GROUP_MAP", "DESIRED_GROUP_ORDER",
+ "TARGET_TAB_NAME", "FILENAME_PATTERN"]
+ if all(k in config for k in required_keys):
+ # 可选:检查 PWX_TO_GROUP_MAP 和 DESIRED_GROUP_ORDER 是否为空
+ # 并且检查 FILENAME_PATTERN 是否是编译后的正则表达式对象
+ if (config.get("PWX_TO_GROUP_MAP") and
+ config.get("DESIRED_GROUP_ORDER") and
+ isinstance(config.get("FILENAME_PATTERN"), re.Pattern)):
+ valid_configs.append(config)
+ else:
+ reason = []
+ if not config.get("PWX_TO_GROUP_MAP"): reason.append("PWX_TO_GROUP_MAP 为空或不存在")
+ if not config.get("DESIRED_GROUP_ORDER"): reason.append("DESIRED_GROUP_ORDER 为空或不存在")
+ if not isinstance(config.get("FILENAME_PATTERN"), re.Pattern): reason.append("FILENAME_PATTERN 不是有效的正则表达式对象")
+ print(f"警告: 配置 {config.get('LANGUAGE_CODE', '未知')} 无效 ({'; '.join(reason)}),跳过处理。")
+ else:
+ missing_keys = [k for k in required_keys if k not in config]
+ print(f"警告: 配置 {config.get('LANGUAGE_CODE', '未知')} 不完整 (缺少: {', '.join(missing_keys)}),跳过处理。")
+
+ # 4. 处理有效配置
+ if not valid_configs:
+ print("没有有效的配置可供处理。")
+ else:
+ try: # 包裹所有配置的处理过程
+ for config in valid_configs:
+ # 将 navigation_data 传递给 main 函数进行修改
+ main(config, navigation_data) # main 函数会直接修改这个 navigation_data 字典
+
+ # 5. 所有配置处理完毕后,统一写回文件
+ save_docs_data(docs_path, current_docs_data)
+ except (FileNotFoundError, ValueError, RuntimeError, IOError, Exception) as e:
+ # 捕获 main 或 save_docs_data 中可能引发的已知错误
+ print(f"\n处理过程中发生错误: {e}")
+ print("操作已终止,文件可能未完全更新。")
+ # 根据需要,可以在这里决定是否尝试保存部分结果或直接退出
+
+if __name__ == "__main__":
+ # 定义要处理的配置列表
+ CONFIGS_TO_PROCESS = [
+ PLUGIN_DEV_ZH,
+ PLUGIN_DEV_EN, # 取消注释以处理英文配置
+ ]
+
+ # 调用主处理函数
+ process_configurations(CONFIGS_TO_PROCESS, DOCS_JSON_PATH)
diff --git a/tools/rename_en.py b/tools/rename_en.py
new file mode 100644
index 00000000..45b36969
--- /dev/null
+++ b/tools/rename_en.py
@@ -0,0 +1,352 @@
+import os
+import yaml
+import re
+import datetime
+
+# --- Path Setup ---
+# BASE_DIR should be the project root, which is the parent of the 'tools' directory
+BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+
+# --- Configuration ---
+timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
+plugin_dev_en_path = os.path.join(BASE_DIR, "plugin_dev_en")
+if os.path.exists(plugin_dev_en_path):
+ plugin_dev_en_timestamp = f"plugin_dev_en_{timestamp}"
+ os.rename(plugin_dev_en_path, os.path.join(BASE_DIR, plugin_dev_en_timestamp))
+ SOURCE_DIR_NAME = plugin_dev_en_timestamp
+ TARGET_DIR_NAME = "plugin_dev_en"
+else:
+ print(f"Warning: 'plugin_dev_en' directory not found in {BASE_DIR}")
+ print("Creating a new 'plugin_dev_en' directory...")
+ SOURCE_DIR_NAME = "plugin_dev_en_empty_source"
+ TARGET_DIR_NAME = "plugin_dev_en"
+ os.makedirs(
+ os.path.join(BASE_DIR, SOURCE_DIR_NAME), exist_ok=True
+ )
+
+ARCHIVE_PREFIX = "plugin_dev_en_new_archive_" # Prefix for archived directories
+
+# --- Mapping Configuration ---
+# (Mappings remain the same as the previous version)
+PRIMARY_TYPE_MAP = {
+ "conceptual": 1,
+ "implementation": 2,
+ "operational": 3,
+ "reference": 4,
+}
+DEFAULT_W = 0
+DETAIL_TYPE_MAPS = {
+ "conceptual": {"introduction": 1, "principles": 2, "architecture": 3},
+ "implementation": {"basic": 1, "standard": 2, "high": 3, "advanced": 4},
+ "operational": {"setup": 1, "deployment": 2, "maintenance": 3},
+ "reference": {"core": 1, "configuration": 2, "examples": 3},
+}
+DEFAULT_X = 0
+LEVEL_MAP = {
+ "beginner": 1,
+ "intermediate": 2,
+ "advanced": 3,
+}
+DEFAULT_Y = 0
+PRIORITY_NORMAL = 0
+PRIORITY_HIGH = 9
+PRIORITY_ADVANCED_LEVEL_KEY = "advanced"
+PRIORITY_IMPLEMENTATION_PRIMARY_KEY = "implementation"
+PRIORITY_IMPLEMENTATION_DETAIL_KEYS = {"high", "advanced"}
+
+# --- Configuration End ---
+
+# --- Helper Functions ---
+# (extract_front_matter remains the same)
+
+
+def extract_front_matter(content):
+ match = re.match(r"^\s*---\s*$(.*?)^---\s*$(.*)", content, re.DOTALL | re.MULTILINE)
+ if match:
+ yaml_str = match.group(1).strip()
+ markdown_content = match.group(2).strip()
+ try:
+ front_matter = yaml.safe_load(yaml_str)
+ if front_matter is None:
+ return {}, markdown_content
+ return (
+ front_matter if isinstance(front_matter, dict) else {}
+ ), markdown_content
+ except yaml.YAMLError as e:
+ print(f" [Error] YAML Parsing Failed: {e}")
+ return None, content
+ else:
+ return {}, content
+
+
+# (sanitize_filename_part remains mostly the same, ensures non-empty return)
+
+
+def sanitize_filename_part(part):
+ if not isinstance(part, str):
+ part = str(part)
+ part = part.lower()
+ # Replace common problematic characters first
+ part = part.replace("&", "and").replace("@", "at")
+ part = re.sub(r"\s+", "-", part) # Whitespace to hyphen
+ # Keep letters, numbers, underscore, hyphen. Remove others.
+ part = re.sub(r"[^\w\-]+", "", part)
+ part = part.strip(".-_") # Remove leading/trailing separators
+ # Ensure it's not empty, provide a default if it becomes empty
+ return part or "untitled"
+
+
+# --- Main Processing Function ---
+
+
+def process_markdown_files(source_dir, target_dir):
+ """
+ Processes mdx files, archives old target dir, uses PWXY-[title].en.mdx format.
+ """
+ print("Starting processing...")
+ print(f"Source Directory: {source_dir}")
+ print(f"Target Directory: {target_dir}")
+
+ # --- Archive Existing Target Directory ---
+ if os.path.exists(target_dir):
+ if os.path.isdir(target_dir):
+ timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
+ archive_dir = os.path.join(BASE_DIR, f"{ARCHIVE_PREFIX}{timestamp}")
+ try:
+ os.rename(target_dir, archive_dir)
+ print(f"Archived existing target directory to: {archive_dir}")
+ except OSError as e:
+ print(f"[Error] Failed to archive existing target directory: {e}")
+ print("Aborting to prevent data loss.")
+ return # Stop execution if archiving fails
+ else:
+ print(
+ f"[Error] Target path '{target_dir}' exists but is not a directory. Please remove or rename it manually."
+ )
+ print("Aborting.")
+ return
+
+ # --- Create New Target Directory ---
+ try:
+ # Should not exist after archiving
+ os.makedirs(target_dir, exist_ok=False)
+ print(f"Created new target directory: {target_dir}")
+ except OSError as e:
+ print(f"[Error] Failed to create target directory '{target_dir}': {e}")
+ print("Aborting.")
+ return
+
+ processed_count = 0
+ skipped_count = 0
+ error_count = 0
+ warning_count = 0 # Counts files with at least one warning
+
+ total_files = sum(
+ 1
+ for root, _, files in os.walk(source_dir)
+ for file in files
+ if file.lower().endswith(".mdx")
+ )
+ print(f"Found {total_files} MDX files to process")
+
+ for root, _, files in os.walk(source_dir):
+ for filename in files:
+ if not filename.lower().endswith(".mdx"):
+ continue
+
+ original_filepath = os.path.join(root, filename)
+ relative_path = os.path.relpath(original_filepath, BASE_DIR).replace(
+ os.sep, "/"
+ )
+
+ current_warnings = 0
+
+ try:
+ with open(original_filepath, "r", encoding="utf-8") as f:
+ content = f.read()
+
+ front_matter, markdown_content = extract_front_matter(content)
+
+ if front_matter is None:
+ print(f"\nProcessing: {relative_path}")
+ print(" [Skipping] YAML Error in file.")
+ error_count += 1
+ continue
+
+ # --- Extract Metadata (including new fields) ---
+ dimensions = front_matter.get("dimensions", {})
+ type_info = dimensions.get("type", {})
+ primary = type_info.get("primary")
+ detail = type_info.get("detail")
+ level = dimensions.get("level")
+ standard_title = front_matter.get("standard_title")
+ # language = front_matter.get("language") # No longer needed for filename
+
+ # --- Determine P, W, X, Y (Logic remains the same) ---
+ P = PRIORITY_NORMAL
+ # (Priority logic based on level and implementation/detail)
+ if level == PRIORITY_ADVANCED_LEVEL_KEY:
+ P = PRIORITY_HIGH
+ if (
+ primary == PRIORITY_IMPLEMENTATION_PRIMARY_KEY
+ and detail in PRIORITY_IMPLEMENTATION_DETAIL_KEYS
+ ):
+ P = PRIORITY_HIGH
+
+ W = PRIMARY_TYPE_MAP.get(primary, DEFAULT_W)
+ primary_detail_map = DETAIL_TYPE_MAPS.get(primary, {})
+ X = primary_detail_map.get(detail, DEFAULT_X)
+ Y = LEVEL_MAP.get(level, DEFAULT_Y)
+
+ # --- Warnings for missing dimension data (same as before) ---
+ warnings_messages = []
+ if primary is None:
+ current_warnings += 1
+ warnings_messages.append(
+ " [Warning] Missing dimensions.type.primary"
+ )
+ elif W == DEFAULT_W:
+ current_warnings += 1
+ warnings_messages.append(
+ f" [Warning] Unmapped primary type: '{primary}'. Using W={DEFAULT_W}"
+ )
+ if detail is None:
+ current_warnings += 1
+ warnings_messages.append(
+ " [Warning] Missing dimensions.type.detail"
+ )
+ elif X == DEFAULT_X and primary in DETAIL_TYPE_MAPS:
+ current_warnings += 1
+ warnings_messages.append(
+ f" [Warning] Unmapped detail type: '{detail}' for primary '{primary}'. Using X={DEFAULT_X}"
+ )
+ elif primary not in DETAIL_TYPE_MAPS and primary is not None:
+ current_warnings += 1
+ warnings_messages.append(
+ f" [Warning] No detail map defined for primary type: '{primary}'. Using X={DEFAULT_X}"
+ )
+ if level is None:
+ current_warnings += 1
+ warnings_messages.append(" [Warning] Missing dimensions.level")
+ elif Y == DEFAULT_Y:
+ current_warnings += 1
+ warnings_messages.append(
+ f" [Warning] Unmapped level: '{level}'. Using Y={DEFAULT_Y}"
+ )
+
+ # --- Construct New Filename using standard_title and hardcoded .en suffix ---
+ prefix_str = f"{P}{W}{X}{Y}"
+ try:
+ numeric_prefix = int(prefix_str)
+ padded_prefix = f"{numeric_prefix:04d}"
+ except ValueError:
+ print(f"\nProcessing: {relative_path}")
+ print(
+ f" [Error] Could not form numeric prefix from P={P}, W={W}, X={X}, Y={Y}. Using '0000'."
+ )
+ error_count += 1
+ continue # Skip file
+
+ # Determine title part (use standard_title or fallback)
+ title_part_to_use = standard_title
+ if not title_part_to_use:
+ current_warnings += 1
+ warnings_messages.append(
+ " [Warning] Missing 'standard_title'. Using original filename base as fallback."
+ )
+ title_part_to_use = os.path.splitext(filename)[0] # Fallback
+
+ sanitized_title = sanitize_filename_part(title_part_to_use)
+
+ # --- Removed language suffix logic ---
+ # No longer check front_matter['language']
+
+ # Combine parts with hardcoded '.en.mdx' suffix
+ new_filename = f"{padded_prefix}-{sanitized_title}.en.mdx"
+ target_filepath = os.path.join(target_dir, new_filename)
+
+ # --- Check for Collisions ---
+ if os.path.exists(target_filepath):
+ print(f"\nProcessing: {relative_path}")
+ print(f" [Skipping] Target file already exists: {new_filename}")
+ skipped_count += 1
+ continue
+
+ # --- Prepare New Content ---
+ try:
+ # Ensure language field is preserved if it exists, or add it if missing
+ if 'language' not in front_matter:
+ front_matter['language'] = 'en'
+ elif not front_matter['language']: # Handle empty language field
+ front_matter['language'] = 'en'
+
+ new_yaml_str = yaml.dump(
+ front_matter,
+ allow_unicode=True,
+ default_flow_style=False,
+ sort_keys=False,
+ )
+ except Exception as dump_error:
+ print(f"\nProcessing: {relative_path}")
+ print(f" [Error] Failed to dump updated YAML: {dump_error}")
+ error_count += 1
+ continue
+
+ new_content = f"---\n{new_yaml_str}---\n\n{markdown_content}"
+
+ # --- Write New File ---
+ with open(target_filepath, "w", encoding="utf-8") as f:
+ f.write(new_content)
+
+ if current_warnings > 0:
+ print(f"\nProcessing: {relative_path}")
+ for warning in warnings_messages:
+ print(warning)
+ warning_count += (
+ 1 # Increment file warning count if this file had warnings
+ )
+
+ processed_count += 1
+ if processed_count % 10 == 0 or processed_count == total_files:
+ print(
+ f"Progress: {processed_count}/{total_files} files processed",
+ end="\r",
+ )
+
+ except FileNotFoundError:
+ print(f"\nProcessing: {relative_path}")
+ print(
+ f" [Error] File not found during processing: {original_filepath}"
+ )
+ error_count += 1
+ except Exception as e:
+ print(f"\nProcessing: {relative_path}")
+ print(
+ f" [Error] Unexpected error processing file '{relative_path}': {e}"
+ )
+ import traceback
+
+ traceback.print_exc()
+ error_count += 1
+
+ print("\n") # Add a newline after progress counter
+ # --- Final Report ---
+ print("\n--- Processing Complete ---")
+ print(f"Successfully processed: {processed_count} files")
+ print(f"Skipped (target exists): {skipped_count} files")
+ print(f"Files with warnings (missing/unmapped data): {warning_count}")
+ print(f"Errors encountered: {error_count} files")
+ print("-" * 27)
+
+
+if __name__ == "__main__":
+ SOURCE_PATH = os.path.join(BASE_DIR, SOURCE_DIR_NAME)
+ TARGET_PATH = os.path.join(BASE_DIR, TARGET_DIR_NAME)
+ process_markdown_files(SOURCE_PATH, TARGET_PATH)
+
+ if SOURCE_DIR_NAME == "plugin_dev_en_empty_source" and os.path.exists(SOURCE_PATH):
+ try:
+ os.rmdir(SOURCE_PATH)
+ print(f"Removed temporary source directory: {SOURCE_PATH}")
+ except OSError as e:
+ print(f"Note: Could not remove temporary directory: {e}")
diff --git a/tools/rename_zh.py b/tools/rename_zh.py
new file mode 100644
index 00000000..1a88e972
--- /dev/null
+++ b/tools/rename_zh.py
@@ -0,0 +1,361 @@
+import os
+import yaml
+import re
+import datetime
+
+# --- Path Setup ---
+# BASE_DIR should be the project root, which is the parent of the 'tools' directory
+BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+
+# --- Configuration ---
+timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
+plugin_dev_zh_path = os.path.join(BASE_DIR, "plugin_dev_zh")
+if os.path.exists(plugin_dev_zh_path):
+ plugin_dev_zh_timestamp = f"plugin_dev_zh_{timestamp}"
+ os.rename(plugin_dev_zh_path, os.path.join(BASE_DIR, plugin_dev_zh_timestamp))
+ SOURCE_DIR_NAME = plugin_dev_zh_timestamp
+ TARGET_DIR_NAME = "plugin_dev_zh"
+else:
+ print(f"Warning: 'plugin_dev_zh' directory not found in {BASE_DIR}")
+ print("Creating a new 'plugin_dev_zh' directory...")
+ SOURCE_DIR_NAME = "plugin_dev_zh_empty_source"
+ TARGET_DIR_NAME = "plugin_dev_zh"
+ os.makedirs(
+ os.path.join(BASE_DIR, SOURCE_DIR_NAME), exist_ok=True
+ )
+
+ARCHIVE_PREFIX = "plugin_dev_zh_new_archive_" # Prefix for archived directories
+
+# --- Mapping Configuration ---
+# (Mappings remain the same as the previous version)
+PRIMARY_TYPE_MAP = {
+ "conceptual": 1,
+ "implementation": 2,
+ "operational": 3,
+ "reference": 4,
+}
+DEFAULT_W = 0
+DETAIL_TYPE_MAPS = {
+ "conceptual": {"introduction": 1, "principles": 2, "architecture": 3},
+ "implementation": {"basic": 1, "standard": 2, "high": 3, "advanced": 4},
+ "operational": {"setup": 1, "deployment": 2, "maintenance": 3},
+ "reference": {"core": 1, "configuration": 2, "examples": 3},
+}
+DEFAULT_X = 0
+LEVEL_MAP = {
+ "beginner": 1,
+ "intermediate": 2,
+ "advanced": 3,
+}
+DEFAULT_Y = 0
+PRIORITY_NORMAL = 0
+PRIORITY_HIGH = 9
+PRIORITY_ADVANCED_LEVEL_KEY = "advanced"
+PRIORITY_IMPLEMENTATION_PRIMARY_KEY = "implementation"
+PRIORITY_IMPLEMENTATION_DETAIL_KEYS = {"high", "advanced"}
+
+# --- Configuration End ---
+
+# --- Helper Functions ---
+# (extract_front_matter remains the same)
+
+
+def extract_front_matter(content):
+ match = re.match(r"^\s*---\s*$(.*?)^---\s*$(.*)", content, re.DOTALL | re.MULTILINE)
+ if match:
+ yaml_str = match.group(1).strip()
+ markdown_content = match.group(2).strip()
+ try:
+ front_matter = yaml.safe_load(yaml_str)
+ if front_matter is None:
+ return {}, markdown_content
+ return (
+ front_matter if isinstance(front_matter, dict) else {}
+ ), markdown_content
+ except yaml.YAMLError as e:
+ print(f" [Error] YAML Parsing Failed: {e}")
+ return None, content
+ else:
+ return {}, content
+
+
+# (sanitize_filename_part remains mostly the same, ensures non-empty return)
+
+
+def sanitize_filename_part(part):
+ if not isinstance(part, str):
+ part = str(part)
+ part = part.lower()
+ # Replace common problematic characters first
+ part = part.replace("&", "and").replace("@", "at")
+ part = re.sub(r"\s+", "-", part) # Whitespace to hyphen
+ # Keep letters, numbers, underscore, hyphen. Remove others.
+ part = re.sub(r"[^\w\-]+", "", part)
+ part = part.strip(".-_") # Remove leading/trailing separators
+ # Ensure it's not empty, provide a default if it becomes empty
+ return part or "untitled"
+
+
+# --- Main Processing Function ---
+
+
+def process_markdown_files(source_dir, target_dir):
+ """
+ Processes mdx files, archives old target dir, uses PWXY-[title].lang.mdx format.
+ """
+ print("Starting processing...")
+ print(f"Source Directory: {source_dir}")
+ print(f"Target Directory: {target_dir}")
+
+ # --- Archive Existing Target Directory ---
+ if os.path.exists(target_dir):
+ if os.path.isdir(target_dir):
+ timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
+ archive_dir = os.path.join(BASE_DIR, f"{ARCHIVE_PREFIX}{timestamp}")
+ try:
+ os.rename(target_dir, archive_dir)
+ print(f"Archived existing target directory to: {archive_dir}")
+ except OSError as e:
+ print(f"[Error] Failed to archive existing target directory: {e}")
+ print("Aborting to prevent data loss.")
+ return # Stop execution if archiving fails
+ else:
+ print(
+ f"[Error] Target path '{target_dir}' exists but is not a directory. Please remove or rename it manually."
+ )
+ print("Aborting.")
+ return
+
+ # --- Create New Target Directory ---
+ try:
+ # Should not exist after archiving
+ os.makedirs(target_dir, exist_ok=False)
+ print(f"Created new target directory: {target_dir}")
+ except OSError as e:
+ print(f"[Error] Failed to create target directory '{target_dir}': {e}")
+ print("Aborting.")
+ return
+
+ processed_count = 0
+ skipped_count = 0
+ error_count = 0
+ warning_count = 0 # Counts files with at least one warning
+
+ total_files = sum(
+ 1
+ for root, _, files in os.walk(source_dir)
+ for file in files
+ if file.lower().endswith(".mdx") # Changed from .md
+ )
+ print(f"Found {total_files} MDX files to process") # Changed from Markdown
+
+ for root, _, files in os.walk(source_dir):
+ for filename in files:
+ if not filename.lower().endswith(".mdx"): # Changed from .md
+ continue
+
+ original_filepath = os.path.join(root, filename)
+ relative_path = os.path.relpath(original_filepath, BASE_DIR).replace(
+ os.sep, "/"
+ )
+
+ current_warnings = 0
+
+ try:
+ with open(original_filepath, "r", encoding="utf-8") as f:
+ content = f.read()
+
+ front_matter, markdown_content = extract_front_matter(content)
+
+ if front_matter is None:
+ print(f"\nProcessing: {relative_path}")
+ print(" [Skipping] YAML Error in file.")
+ error_count += 1
+ continue
+
+ # --- Extract Metadata (including new fields) ---
+ dimensions = front_matter.get("dimensions", {})
+ type_info = dimensions.get("type", {})
+ primary = type_info.get("primary")
+ detail = type_info.get("detail")
+ level = dimensions.get("level")
+ standard_title = front_matter.get("standard_title") # New
+ language = front_matter.get("language") # New
+
+ # --- Determine P, W, X, Y (Logic remains the same) ---
+ P = PRIORITY_NORMAL
+ # (Priority logic based on level and implementation/detail)
+ if level == PRIORITY_ADVANCED_LEVEL_KEY:
+ P = PRIORITY_HIGH
+ if (
+ primary == PRIORITY_IMPLEMENTATION_PRIMARY_KEY
+ and detail in PRIORITY_IMPLEMENTATION_DETAIL_KEYS
+ ):
+ P = PRIORITY_HIGH
+
+ W = PRIMARY_TYPE_MAP.get(primary, DEFAULT_W)
+ primary_detail_map = DETAIL_TYPE_MAPS.get(primary, {})
+ X = primary_detail_map.get(detail, DEFAULT_X)
+ Y = LEVEL_MAP.get(level, DEFAULT_Y)
+
+ # --- Warnings for missing dimension data (same as before) ---
+ warnings_messages = []
+ if primary is None:
+ current_warnings += 1
+ warnings_messages.append(
+ " [Warning] Missing dimensions.type.primary"
+ )
+ elif W == DEFAULT_W:
+ current_warnings += 1
+ warnings_messages.append(
+ f" [Warning] Unmapped primary type: '{primary}'. Using W={DEFAULT_W}"
+ )
+ if detail is None:
+ current_warnings += 1
+ warnings_messages.append(
+ " [Warning] Missing dimensions.type.detail"
+ )
+ elif X == DEFAULT_X and primary in DETAIL_TYPE_MAPS:
+ current_warnings += 1
+ warnings_messages.append(
+ f" [Warning] Unmapped detail type: '{detail}' for primary '{primary}'. Using X={DEFAULT_X}"
+ )
+ elif primary not in DETAIL_TYPE_MAPS and primary is not None:
+ current_warnings += 1
+ warnings_messages.append(
+ f" [Warning] No detail map defined for primary type: '{primary}'. Using X={DEFAULT_X}"
+ )
+ if level is None:
+ current_warnings += 1
+ warnings_messages.append(" [Warning] Missing dimensions.level")
+ elif Y == DEFAULT_Y:
+ current_warnings += 1
+ warnings_messages.append(
+ f" [Warning] Unmapped level: '{level}'. Using Y={DEFAULT_Y}"
+ )
+
+ # --- Construct New Filename using standard_title and language ---
+ prefix_str = f"{P}{W}{X}{Y}"
+ try:
+ numeric_prefix = int(prefix_str)
+ padded_prefix = f"{numeric_prefix:04d}"
+ except ValueError:
+ print(f"\nProcessing: {relative_path}")
+ print(
+ f" [Error] Could not form numeric prefix from P={P}, W={W}, X={X}, Y={Y}. Using '0000'."
+ )
+ error_count += 1
+ continue # Skip file
+
+ # Determine title part (use standard_title or fallback)
+ title_part_to_use = standard_title
+ if not title_part_to_use:
+ current_warnings += 1
+ warnings_messages.append(
+ " [Warning] Missing 'standard_title'. Using original filename base as fallback."
+ )
+ title_part_to_use = os.path.splitext(filename)[0] # Fallback
+
+ sanitized_title = sanitize_filename_part(title_part_to_use)
+
+ # Determine language suffix
+ lang_suffix = ""
+ if language:
+ lang_code = str(language).strip().lower()
+ if lang_code:
+ lang_suffix = f".{lang_code}"
+ else:
+ current_warnings += 1
+ warnings_messages.append(
+ " [Warning] Empty 'language' field found. Omitting suffix."
+ )
+ else:
+ current_warnings += 1
+ warnings_messages.append(
+ " [Warning] Missing 'language' field. Omitting suffix."
+ )
+
+ # Combine parts
+ # Removed brackets around sanitized_title
+ new_filename = f"{padded_prefix}-{sanitized_title}{lang_suffix}.mdx"
+ target_filepath = os.path.join(target_dir, new_filename)
+
+ # --- Check for Collisions ---
+ if os.path.exists(target_filepath):
+ print(f"\nProcessing: {relative_path}")
+ print(f" [Skipping] Target file already exists: {new_filename}")
+ skipped_count += 1
+ continue
+
+ # --- Prepare New Content ---
+ try:
+ new_yaml_str = yaml.dump(
+ front_matter,
+ allow_unicode=True,
+ default_flow_style=False,
+ sort_keys=False,
+ )
+ except Exception as dump_error:
+ print(f"\nProcessing: {relative_path}")
+ print(f" [Error] Failed to dump updated YAML: {dump_error}")
+ error_count += 1
+ continue
+
+ new_content = f"---\n{new_yaml_str}---\n\n{markdown_content}"
+
+ # --- Write New File ---
+ with open(target_filepath, "w", encoding="utf-8") as f:
+ f.write(new_content)
+
+ if current_warnings > 0:
+ print(f"\nProcessing: {relative_path}")
+ for warning in warnings_messages:
+ print(warning)
+ warning_count += (
+ 1 # Increment file warning count if this file had warnings
+ )
+
+ processed_count += 1
+ if processed_count % 10 == 0 or processed_count == total_files:
+ print(
+ f"Progress: {processed_count}/{total_files} files processed",
+ end="\r",
+ )
+
+ except FileNotFoundError:
+ print(f"\nProcessing: {relative_path}")
+ print(
+ f" [Error] File not found during processing: {original_filepath}"
+ )
+ error_count += 1
+ except Exception as e:
+ print(f"\nProcessing: {relative_path}")
+ print(
+ f" [Error] Unexpected error processing file '{relative_path}': {e}"
+ )
+ import traceback
+
+ traceback.print_exc()
+ error_count += 1
+
+ print("\n") # Add a newline after progress counter
+ # --- Final Report ---
+ print("\n--- Processing Complete ---")
+ print(f"Successfully processed: {processed_count} files")
+ print(f"Skipped (target exists): {skipped_count} files")
+ print(f"Files with warnings (missing/unmapped data): {warning_count}")
+ print(f"Errors encountered: {error_count} files")
+ print("-" * 27)
+
+
+if __name__ == "__main__":
+ SOURCE_PATH = os.path.join(BASE_DIR, SOURCE_DIR_NAME)
+ TARGET_PATH = os.path.join(BASE_DIR, TARGET_DIR_NAME)
+ process_markdown_files(SOURCE_PATH, TARGET_PATH)
+
+ if SOURCE_DIR_NAME == "plugin_dev_zh_empty_source" and os.path.exists(SOURCE_PATH):
+ try:
+ os.rmdir(SOURCE_PATH)
+ print(f"Removed temporary source directory: {SOURCE_PATH}")
+ except OSError as e:
+ print(f"Note: Could not remove temporary directory: {e}")