Browse Source

feat: plugin call dify

Yeuoly 11 months ago
parent
commit
364df36ac4

+ 3 - 0
api/.env.example

@@ -238,3 +238,6 @@ WORKFLOW_CALL_MAX_DEPTH=5
 # App configuration
 APP_MAX_EXECUTION_TIME=1200
 
+# Plugin configuration
+PLUGIN_INNER_API_URL=http://127.0.0.1:5002
+PLUGIN_INNER_API_KEY=lYkiYYT6owG+71oLerGzA7GXCgOT++6ovaezWAjpCjf+Sjc3ZtU+qUEi

+ 14 - 0
api/configs/feature/__init__.py

@@ -47,6 +47,19 @@ class CodeExecutionSandboxConfig(BaseSettings):
         default='dify-sandbox',
     )
 
+class PluginConfig(BaseSettings):
+    """
+    Plugin configs
+    """
+    PLUGIN_INNER_API_URL: str = Field(
+        description='Plugin inner API URL',
+        default='http://plugin:8194',
+    )
+
+    PLUGIN_INNER_API_KEY: str = Field(
+        description='Plugin inner API key',
+        default='dify-inner-api-key',
+    )
 
 class EndpointConfig(BaseSettings):
     """
@@ -431,6 +444,7 @@ class FeatureConfig(
     AppExecutionConfig,
     BillingConfig,
     CodeExecutionSandboxConfig,
+    PluginConfig,
     DataSetConfig,
     EndpointConfig,
     FileAccessConfig,

+ 1 - 1
api/controllers/inner_api/__init__.py

@@ -5,5 +5,5 @@ from libs.external_api import ExternalApi
 bp = Blueprint('inner_api', __name__, url_prefix='/inner/api')
 api = ExternalApi(bp)
 
+from .plugin import plugin
 from .workspace import workspace
-

+ 1 - 0
api/controllers/inner_api/plugin/__init__.py

@@ -0,0 +1 @@
+from .plugin import *

+ 59 - 0
api/controllers/inner_api/plugin/plugin.py

@@ -0,0 +1,59 @@
+
+from flask_restful import Resource, reqparse
+
+from controllers.console.setup import setup_required
+from controllers.inner_api import api
+from controllers.inner_api.plugin.wraps import get_tenant
+from controllers.inner_api.wraps import plugin_inner_api_only
+from libs.helper import compact_generate_response
+from models.account import Tenant
+from services.plugin.plugin_invoke_service import PluginInvokeService
+
+
+class PluginInvokeModelApi(Resource):
+    @setup_required
+    @plugin_inner_api_only
+    @get_tenant
+    def post(self, user_id: str, tenant_model: Tenant):
+        parser = reqparse.RequestParser()
+        parser.add_argument('provider', type=dict, required=True, location='json')
+        parser.add_argument('model', type=dict, required=True, location='json')
+        parser.add_argument('parameters', type=dict, required=True, location='json')
+
+        args = parser.parse_args()
+        
+
+class PluginInvokeToolApi(Resource):
+    @setup_required
+    @plugin_inner_api_only
+    @get_tenant
+    def post(self, user_id: str, tenant_model: Tenant):
+        parser = reqparse.RequestParser()
+        parser.add_argument('provider', type=dict, required=True, location='json')
+        parser.add_argument('tool', type=dict, required=True, location='json')
+        parser.add_argument('parameters', type=dict, required=True, location='json')
+
+        args = parser.parse_args()
+
+        response = PluginInvokeService.invoke_tool(user_id, tenant_model, 
+                                                   args['provider'], args['tool'], 
+                                                   args['parameters'])
+        return compact_generate_response(response)
+
+
+class PluginInvokeNodeApi(Resource):
+    @setup_required
+    @plugin_inner_api_only
+    @get_tenant
+    def post(self, user_id: str, tenant_model: Tenant):
+        parser = reqparse.RequestParser()
+        args = parser.parse_args()
+
+        return {
+            'message': 'success'
+        }
+
+
+api.add_resource(PluginInvokeModelApi, '/invoke/model')
+api.add_resource(PluginInvokeToolApi, '/invoke/tool')
+api.add_resource(PluginInvokeNodeApi, '/invoke/node')

+ 47 - 0
api/controllers/inner_api/plugin/wraps.py

@@ -0,0 +1,47 @@
+from collections.abc import Callable
+from functools import wraps
+from typing import Optional
+
+from flask_restful import reqparse
+
+from extensions.ext_database import db
+from models.account import Tenant
+
+
+def get_tenant(view: Optional[Callable] = None):
+    def decorator(view_func):
+        @wraps(view_func)
+        def decorated_view(*args, **kwargs):
+            # fetch json body
+            parser = reqparse.RequestParser()
+            parser.add_argument('tenant_id', type=str, required=True, location='json')
+            parser.add_argument('user_id', type=str, required=True, location='json')
+
+            kwargs = parser.parse_args()
+
+            user_id = kwargs.get('user_id')
+            tenant_id = kwargs.get('tenant_id')
+
+            del kwargs['tenant_id']
+            del kwargs['user_id']
+
+            try:
+                tenant_model = db.session.query(Tenant).filter(
+                    Tenant.id == tenant_id,
+                ).first()
+            except Exception:
+                raise ValueError('tenant not found')
+
+            if not tenant_model:
+                raise ValueError('tenant not found')
+
+            kwargs['tenant_model'] = tenant_model
+            kwargs['user_id'] = user_id
+
+            return view_func(*args, **kwargs)
+        return decorated_view
+
+    if view is None:
+        return decorator
+    else:
+        return decorator(view)

+ 2 - 2
api/controllers/inner_api/workspace/workspace.py

@@ -2,7 +2,7 @@ from flask_restful import Resource, reqparse
 
 from controllers.console.setup import setup_required
 from controllers.inner_api import api
-from controllers.inner_api.wraps import inner_api_only
+from controllers.inner_api.wraps import enterprise_inner_api_only
 from events.tenant_event import tenant_was_created
 from models.account import Account
 from services.account_service import TenantService
@@ -11,7 +11,7 @@ from services.account_service import TenantService
 class EnterpriseWorkspace(Resource):
 
     @setup_required
-    @inner_api_only
+    @enterprise_inner_api_only
     def post(self):
         parser = reqparse.RequestParser()
         parser.add_argument('name', type=str, required=True, location='json')

+ 18 - 2
api/controllers/inner_api/wraps.py

@@ -5,11 +5,12 @@ from hmac import new as hmac_new
 
 from flask import abort, current_app, request
 
+from configs import dify_config
 from extensions.ext_database import db
 from models.model import EndUser
 
 
-def inner_api_only(view):
+def enterprise_inner_api_only(view):
     @wraps(view)
     def decorated(*args, **kwargs):
         if not current_app.config['INNER_API']:
@@ -25,7 +26,7 @@ def inner_api_only(view):
     return decorated
 
 
-def inner_api_user_auth(view):
+def enterprise_inner_api_user_auth(view):
     @wraps(view)
     def decorated(*args, **kwargs):
         if not current_app.config['INNER_API']:
@@ -59,3 +60,18 @@ def inner_api_user_auth(view):
         return view(*args, **kwargs)
 
     return decorated
+
+def plugin_inner_api_only(view):
+    @wraps(view)
+    def decorated(*args, **kwargs):
+        if not dify_config.PLUGIN_INNER_API_KEY:
+            abort(404)
+
+        # get header 'X-Inner-Api-Key'
+        inner_api_key = request.headers.get('X-Inner-Api-Key')
+        if not inner_api_key or inner_api_key != dify_config.PLUGIN_INNER_API_KEY:
+            abort(404)
+
+        return view(*args, **kwargs)
+
+    return decorated

+ 16 - 0
api/services/plugin/plugin_invoke_service.py

@@ -0,0 +1,16 @@
+from collections.abc import Generator
+from typing import Any
+
+from core.tools.entities.tool_entities import ToolInvokeMessage
+from models.account import Tenant
+
+
+class PluginInvokeService:
+    @classmethod
+    def invoke_tool(cls, user_id: str, tenant: Tenant, 
+                    tool_provider: str, tool_name: str,
+                    tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage]:
+        """
+        Invokes a tool with the given user ID and tool parameters.
+        """
+