Skip to content

Dify adapter

For Dify — but different in shape from every other adapter. Dify tools aren't in-process Python objects passed to a constructor; they're deployable plugin artifacts: a Python class subclassing dify_plugin.tool.Tool plus a manifest.yaml.

The adapter offers two paths:

PathUse when
GovernedDifyTool base classBuilding a real, deployable Dify plugin. Recommended.
DifyAdapter.dify_tools()Adapter-level governance unit tests only — output is a list of dicts that cannot be deployed to a real Dify instance

Install

bash
pip install kitelogik dify-plugin-sdk

dify-plugin-sdk is not a hard dependency — the module imports cleanly without it for unit testing. Real plugin deployments need it.

Subclass GovernedDifyTool instead of dify_plugin.tool.Tool, and override _invoke_governed(tool_parameters) instead of _invoke. The base class wraps your method with the policy pipeline:

python
from kitelogik import OPAClient, PolicyGate, SessionContext
from kitelogik.adapters.dify import GovernedDifyTool

class GetCustomerTool(GovernedDifyTool):
    # Configure governance via class attributes (or set in __init__).
    gate = PolicyGate(opa_client=OPAClient())
    context = SessionContext(
        session_id="dify_sess_001",
        user_role="analyst",
        session_scopes=["read_customer"],
    )
    action = "get_customer"

    def _invoke_governed(self, tool_parameters):
        customer_id = tool_parameters["customer_id"]
        # Run only if governance allows
        yield self.create_text_message(f"record:{customer_id}")

Ship the resulting class as your plugin. Dify's runtime calls _invoke(...), which:

  1. Builds a ToolCallInput from the registered action + tool params
  2. Evaluates the policy gate
  3. On deny, yields a single text message with {"blocked": true, "reason": ...} JSON
  4. On allow, calls your _invoke_governed(...) and yields each result message
  5. Sanitises the text payload of each yielded ToolInvokeMessage

Configuration attributes

Class attributeRequiredDefault
gateyesNone (raises RuntimeError if not set)
contextyesNone (raises RuntimeError if not set)
actionnofalls back to self.__class__.__name__
sanitizenoTrue
deny_messageno"Action blocked by governance policy."

Helper — make_governed_dify_tool

Have a plain Python function and want it deployable as a Dify plugin without writing a class? make_governed_dify_tool builds a GovernedDifyTool subclass dynamically:

python
from kitelogik.adapters.dify import make_governed_dify_tool

def get_customer(customer_id: str) -> str:
    return f"Customer {customer_id}: Acme Corp"

GetCustomerTool = make_governed_dify_tool(
    get_customer,
    gate=gate,
    context=context,
    action="get_customer",                   # defaults to fn.__name__
    deny_message="Customer lookup blocked.",  # optional override
)

# `GetCustomerTool` is a class. Instantiate it inside the Dify plugin runtime.

The returned class inherits from GovernedDifyTool, has the right _invoke_governed body, and is named after the function (or the name= kwarg).

Path 2 — DifyAdapter.dify_tools (testing only)

For governance unit tests where you don't want to spin up the Dify plugin runtime:

python
from kitelogik.adapters.dify import DifyAdapter

adapter = DifyAdapter(gate=gate, context=context)
adapter.register("get_customer", get_customer, description="Look up a customer")

descriptors = adapter.dify_tools()
# [{"name": "get_customer", "description": "...", "function": <governed callable>}]

These dicts cannot be deployed — Dify's plugin loader expects the Tool subclass shape, not dicts. Use BaseGovernedAdapter.execute() or call descriptor["function"](**args) directly in tests.

What happens on a deny

GovernedDifyTool._invoke yields a single Dify text message (ToolInvokeMessage of type TEXT) carrying the JSON payload:

json
{"blocked": true, "reason": "Action blocked by governance policy."}

For unit tests where dify_plugin isn't installed, the same shape is returned as a plain dict ({"type": "text", "text": "<json>"}) so test assertions don't need to depend on the SDK.

Source

kitelogik/adapters/dify.py on GitHub.

Released under the Apache 2.0 License.