feat: plugin call dify

This commit is contained in:
Yeuoly 2024-07-08 22:37:20 +08:00
parent 603187393a
commit 364df36ac4
No known key found for this signature in database
GPG Key ID: A66E7E320FB19F61
9 changed files with 161 additions and 5 deletions

View File

@ -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

View File

@ -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,

View File

@ -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

View File

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

View File

@ -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')

View File

@ -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)

View File

@ -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')

View File

@ -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

View File

@ -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.
"""