mirror of
https://github.com/langgenius/dify-docs.git
synced 2026-03-26 13:18:34 +07:00
Merge branch 'main' into main
This commit is contained in:
130
en/development/models-integration/gpt-oss-local-deployment.md
Normal file
130
en/development/models-integration/gpt-oss-local-deployment.md
Normal file
@@ -0,0 +1,130 @@
|
||||
---
|
||||
title: " gpt-oss Local Deployment with Ollama and Dify "
|
||||
---
|
||||
# I. Introduction
|
||||
|
||||
The gpt-oss series is an open-source model first released by OpenAI in August 2025.
|
||||
|
||||
> gpt-oss:20b (fits systems with ~16 GB memory)
|
||||
>
|
||||
> gpt-oss:120b (designed for ≥ 60 GB memory)
|
||||
|
||||
You can run it locally with Ollama. No cloud calls. Data stays on your machine, which helps with privacy and latency.
|
||||
|
||||
Dify is an open-source platform for building AI Agents and Workflows. This guide shows how to run gpt-oss with Ollama and plug it into Dify for a private, high-performance setup.
|
||||
|
||||
* * *
|
||||
|
||||
# II. Environment Setup
|
||||
|
||||
## Step 1: Run gpt-oss with Ollama
|
||||
|
||||
1. Install Ollama
|
||||
|
||||
|
||||
[Download](https://ollama.com/) and install for macOS, Windows, or Linux. If anything is unclear, see the [official docs](https://docs.dify.ai/en/development/models-integration/ollama#integrate-local-models-deployed-by-ollama).
|
||||
|
||||
|
||||
|
||||
2. Pull a model
|
||||
|
||||
|
||||
```Bash
|
||||
# Recommended for most dev machines
|
||||
ollama pull gpt-oss:20b
|
||||
|
||||
# For large GPUs or multi-GPU hosts
|
||||
ollama pull gpt-oss:120b
|
||||
```
|
||||
|
||||
Both models are pre‑quantized in **MXFP4** , suitable for local deployment.
|
||||
|
||||
|
||||
|
||||
3. Start Ollama
|
||||
|
||||
|
||||
The default endpoint is http://localhost:11434.
|
||||
|
||||
## Step 2: Install Dify locally
|
||||
|
||||
See the [Dify docs](https://docs.dify.ai/en/getting-started/install-self-hosted/readme) for full instructions. Or follow the quick tutorial below.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Install [Docker](https://www.docker.com/products/docker-desktop/) and make sure Docker Engine is running.
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
### Install steps
|
||||
|
||||
```Bash
|
||||
git clone https://github.com/langgenius/Dify.git
|
||||
cd Dify/docker
|
||||
cp .env.example .env
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||

|
||||
|
||||
Open your local Dify instance and complete the initial setup.
|
||||
|
||||

|
||||
|
||||
# III. Add the model and test chat
|
||||
|
||||
1. In **Settings → Model Providers → Ollama** , click **Add Ollama model type** .
|
||||
|
||||
|
||||

|
||||
|
||||
2. Set **Base URL** to `http://localhost:11434`, select **Model name** `gpt-oss:20b`, and fill in the required fields.
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
3. Create a blank template
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
4. Select the app or workflow type you want to build.
|
||||
|
||||
|
||||

|
||||
|
||||
* * *
|
||||
|
||||
# IV. Verification and Usage
|
||||
|
||||
* On Dify’s **model testing** page, send a prompt and check that the response looks correct.
|
||||
|
||||
* In a workflow, add an **LLM** node, select `gpt-oss:20b`, and connect the nodes end to end.
|
||||
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
* * *
|
||||
|
||||
# V. Appendix — FAQ and tips
|
||||
|
||||
**Model Pull Is Slow**
|
||||
|
||||
* *Configure a Docker proxy or use an image mirror to speed up downloads.*
|
||||
|
||||
|
||||
**Insufficient GPU Memory**
|
||||
|
||||
* *Use* *`gpt-oss:20b`*. You can enable CPU offloading, but responses will be slower.*
|
||||
|
||||
**Port Access Issues**
|
||||
|
||||
* *Check firewall rules, port bindings, and Docker network settings to ensure connectivity.*
|
||||
@@ -13,7 +13,7 @@ Dify allows integration with LocalAI for local deployment of large language mode
|
||||
|
||||
You can refer to the official [Getting Started](https://localai.io/basics/getting_started/) guide for deployment, or quickly integrate following the steps below:
|
||||
|
||||
(These steps are derived from [LocalAI Data query example](https://github.com/go-skynet/LocalAI/blob/master/examples/langchain-chroma/README))
|
||||
(These steps are derived from [LocalAI Data query example](https://github.com/mudler/LocalAI-examples/tree/main/langchain-chroma/README.md))
|
||||
|
||||
1. First, clone the LocalAI code repository and navigate to the specified directory.
|
||||
|
||||
|
||||
@@ -2,14 +2,32 @@
|
||||
title: Embedding In Websites
|
||||
---
|
||||
|
||||
Dify supports embedding your AI application into your business website, enabling you to build an AI customer‑service chatbot, knowledge‑base Q&A, and other applications with business data in just minutes.
|
||||
|
||||
Dify Apps can be embedded in websites using an iframe. This allows you to integrate your Dify App into your website, blog, or any other web page.
|
||||
You can embed your AI application in three different ways: using an `<iframe>` tag, using a `<script>` tag, or installing the Dify Chrome extension.
|
||||
|
||||
When use Dify Chatbot Bubble Button embed in your website, you can customize the button style, position, and other settings.
|
||||
Click the **Embed** button on the WebApp card, copy the embed code, and paste it into the target location on your site.
|
||||
|
||||
## Using the `<iframe>` Tag
|
||||
|
||||
Copy the `<iframe>` code into the element on your site where the AI application should appear, such as a `<div>` or `<section>`.
|
||||
|
||||
## Using the `<script>` Tag
|
||||
|
||||
Copy the `<script>` code into your site’s `<head>` or `<body>` tag.
|
||||
This will display a Dify chatbot bubble button on your site.
|
||||
|
||||
If you paste the `<script>` code into the site’s `<body>`, you will get a full‑page AI chatbot.
|
||||
|
||||
## Installing the Dify Chrome Extension
|
||||
|
||||
Follow the prompts to install the [Dify Chatbot Chrome extension](https://chromewebstore.google.com/detail/dify-chatbot/ceehdapohffmjmkdcifjofadiaoeggaf).
|
||||
|
||||
## Customizing the Dify Chatbot Bubble Button
|
||||
|
||||
The Dify Chatbot Bubble Button can be customized through the following configuration options:
|
||||
When you embed the Dify chatbot bubble button (i.e., embedding via the `<script>` tag), you can customize the button’s style, position, and other settings.
|
||||
|
||||
The Dify chatbot bubble button can be customized with the following configuration options:
|
||||
|
||||
```javascript
|
||||
window.difyChatbotConfig = {
|
||||
@@ -25,131 +43,138 @@ window.difyChatbotConfig = {
|
||||
draggable: false,
|
||||
// Optional, The axis along which the button is allowed to be dragged, default is `both`, can be `x`, `y`, `both`
|
||||
dragAxis: 'both',
|
||||
// Optional, An object of system variables that set in the dify chatbot
|
||||
systemVariables: {
|
||||
// key is the system variable name
|
||||
// e.g.
|
||||
// user_id: "YOU CAN DEFINE USER ID HERE",
|
||||
// conversation_id: "YOU CAN DEFINE CONVERSATION ID HERE, IT MUST BE A VALID UUID"
|
||||
},
|
||||
// Optional, An object of inputs that set in the dify chatbot
|
||||
inputs: {
|
||||
// key is the variable name
|
||||
// e.g.
|
||||
// name: "NAME"
|
||||
},
|
||||
// Optional, An object of system variables that set in the dify chatbot
|
||||
systemVariables: {
|
||||
// e.g.
|
||||
// user_id: 'USER_ID',
|
||||
// conversation_id: 'CONVERSATION_ID',
|
||||
},
|
||||
// Optional, An object of user variables that set in the dify chatbot
|
||||
userVariables: {
|
||||
// e.g.
|
||||
// avatar_url: 'AVATAR_URL',
|
||||
// name: 'NAME',
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Overriding Default Button Styles
|
||||
### Overriding Default Button Styles
|
||||
|
||||
You can override the default button style using CSS variables or the `containerProps` option. Apply these methods based on CSS specificity to achieve your desired customizations.
|
||||
You can override the default button styles using CSS variables or the `containerProps` option, according to CSS specificity rules.
|
||||
|
||||
### 1.Modifying CSS Variables
|
||||
* **Modify CSS Variables**
|
||||
|
||||
The following CSS variables are supported for customization:
|
||||
The following CSS variables are supported:
|
||||
|
||||
```css
|
||||
/* Button distance to bottom, default is `1rem` */
|
||||
--dify-chatbot-bubble-button-bottom
|
||||
```css
|
||||
/* Distance from bottom, default `1rem` */
|
||||
--dify-chatbot-bubble-button-bottom
|
||||
|
||||
/* Button distance to right, default is `1rem` */
|
||||
--dify-chatbot-bubble-button-right
|
||||
/* Distance from right, default `1rem` */
|
||||
--dify-chatbot-bubble-button-right
|
||||
|
||||
/* Button distance to left, default is `unset` */
|
||||
--dify-chatbot-bubble-button-left
|
||||
/* Distance from left, default `unset` */
|
||||
--dify-chatbot-bubble-button-left
|
||||
|
||||
/* Button distance to top, default is `unset` */
|
||||
--dify-chatbot-bubble-button-top
|
||||
/* Distance from top, default `unset` */
|
||||
--dify-chatbot-bubble-button-top
|
||||
|
||||
/* Button background color, default is `#155EEF` */
|
||||
--dify-chatbot-bubble-button-bg-color
|
||||
/* Background color, default `#155EEF` */
|
||||
--dify-chatbot-bubble-button-bg-color
|
||||
|
||||
/* Button width, default is `50px` */
|
||||
--dify-chatbot-bubble-button-width
|
||||
/* Width, default `50px` */
|
||||
--dify-chatbot-bubble-button-width
|
||||
|
||||
/* Button height, default is `50px` */
|
||||
--dify-chatbot-bubble-button-height
|
||||
/* Height, default `50px` */
|
||||
--dify-chatbot-bubble-button-height
|
||||
|
||||
/* Button border radius, default is `25px` */
|
||||
--dify-chatbot-bubble-button-border-radius
|
||||
/* Border radius, default `25px` */
|
||||
--dify-chatbot-bubble-button-border-radius
|
||||
|
||||
/* Button box shadow, default is `rgba(0, 0, 0, 0.2) 0px 4px 8px 0px)` */
|
||||
--dify-chatbot-bubble-button-box-shadow
|
||||
/* Box shadow, default `rgba(0, 0, 0, 0.2) 0px 4px 8px 0px` */
|
||||
--dify-chatbot-bubble-button-box-shadow
|
||||
|
||||
/* Button hover transform, default is `scale(1.1)` */
|
||||
--dify-chatbot-bubble-button-hover-transform
|
||||
```
|
||||
/* Hover transform, default `scale(1.1)` */
|
||||
--dify-chatbot-bubble-button-hover-transform
|
||||
```
|
||||
|
||||
To change the background color to #ABCDEF, add this CSS:
|
||||
Example: change background color to `#ABCDEF`:
|
||||
|
||||
```css
|
||||
#dify-chatbot-bubble-button {
|
||||
--dify-chatbot-bubble-button-bg-color: #ABCDEF;
|
||||
}
|
||||
```
|
||||
```css
|
||||
#dify-chatbot-bubble-button {
|
||||
--dify-chatbot-bubble-button-bg-color: #ABCDEF;
|
||||
}
|
||||
```
|
||||
|
||||
### 2.Using `containerProps`
|
||||
* **Use `containerProps`**
|
||||
|
||||
Set inline styles using the `style` attribute:
|
||||
Inline style:
|
||||
|
||||
```javascript
|
||||
window.difyChatbotConfig = {
|
||||
// ...other config
|
||||
containerProps: {
|
||||
style: {
|
||||
backgroundColor: '#ABCDEF',
|
||||
width: '60px',
|
||||
height: '60px',
|
||||
borderRadius: '30px',
|
||||
},
|
||||
// For minor overrides, you can also use a string:
|
||||
// style: 'background-color:#ABCDEF;width:60px;'
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
CSS class:
|
||||
|
||||
```javascript
|
||||
window.difyChatbotConfig = {
|
||||
// ...other config
|
||||
containerProps: {
|
||||
className: 'dify-chatbot-bubble-button-custom my-custom-class',
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## Passing `inputs` to the Embedded App
|
||||
|
||||
You can pass initial inputs to your embedded AI application. Four input types are supported:
|
||||
|
||||
1. **`text-input`**: Accepts any value; truncated if it exceeds the maximum length.
|
||||
2. **`paragraph`**: Similar to `text-input`; accepts any value and truncates if too long.
|
||||
3. **`number`**: Accepts a number or numeric string; strings are converted via `Number`.
|
||||
4. **`options`**: Accepts any value matching a pre‑configured option.
|
||||
|
||||
The method differs depending on whether you embed via `<iframe>` or `<script>`.
|
||||
|
||||
Example: variable `name` with value `apple`.
|
||||
|
||||
### Using the `<iframe>` Tag
|
||||
|
||||
For `<iframe>` embedding, append the parameter to the URL:
|
||||
|
||||
1. GZIP‑compress the value.
|
||||
2. Base64‑encode it.
|
||||
3. `encodeURIComponent` the result.
|
||||
|
||||
Example result for `apple`:
|
||||
`http://localhost/chatbot/{token}?name=H4sIAAAAAAAAA0ssKMhJBQBQ0C6pBQAAAA%3D%3D`
|
||||
|
||||
### Using the `<script>` Tag
|
||||
|
||||
For `<script>` embedding, pass inputs via the config:
|
||||
|
||||
```javascript
|
||||
window.difyChatbotConfig = {
|
||||
// ... other configurations
|
||||
containerProps: {
|
||||
style: {
|
||||
backgroundColor: '#ABCDEF',
|
||||
width: '60px',
|
||||
height: '60px',
|
||||
borderRadius: '30px',
|
||||
},
|
||||
// For minor style overrides, you can also use a string value for the `style` attribute:
|
||||
// style: 'background-color: #ABCDEF; width: 60px;',
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Apply CSS classes using the `className` attribute:
|
||||
|
||||
```javascript
|
||||
window.difyChatbotConfig = {
|
||||
// ... other configurations
|
||||
containerProps: {
|
||||
className: 'dify-chatbot-bubble-button-custom my-custom-class',
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Passing `inputs`
|
||||
|
||||
There are four types of inputs supported:
|
||||
|
||||
1. **`text-input`**: Accepts any value. The input string will be truncated if its length exceeds the maximum allowed length.
|
||||
2. **`paragraph`**: Similar to `text-input`, it accepts any value and truncates the string if it's longer than the maximum length.
|
||||
3. **`number`**: Accepts a number or a numerical string. If a string is provided, it will be converted to a number using the `Number` function.
|
||||
4. **`options`**: Accepts any value, provided it matches one of the pre-configured options.
|
||||
|
||||
Example configuration:
|
||||
|
||||
```javascript
|
||||
window.difyChatbotConfig = {
|
||||
// Other configuration settings...
|
||||
// ...other config
|
||||
inputs: {
|
||||
name: 'apple',
|
||||
},
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Note: When using the embed.js script to create an iframe, each input value will be processed—compressed using GZIP and encoded in base64—before being appended to the URL.
|
||||
|
||||
For example, the URL with processed input values will look like this:
|
||||
`http://localhost/chatbot/{token}?name=H4sIAKUlmWYA%2FwWAIQ0AAACDsl7gLuiv2PQEUNAuqQUAAAA%3D`
|
||||
|
||||
{/*
|
||||
Contributing Section
|
||||
DO NOT edit this section!
|
||||
|
||||
379
en/plugins/quick-start/develop-plugins/tool-oauth.mdx
Normal file
379
en/plugins/quick-start/develop-plugins/tool-oauth.mdx
Normal file
@@ -0,0 +1,379 @@
|
||||
---
|
||||
title: "Adding OAuth Support to Your Tool Plugin"
|
||||
---
|
||||
|
||||

|
||||
|
||||
This guide teachs you how to build [OAuth](https://oauth.net/2/) support into your tool plugin. OAuth is a better way to authorize tool plugins that need to access user data from third-party services, like Gmail or GitHub. Instead of requiring the user to manually enter API keys, OAuth lets the tool act on behalf of the user with their explicit consent.
|
||||
|
||||
## Background
|
||||
|
||||
OAuth in Dify involves **two separate flows** that developers should understand and design for:
|
||||
|
||||
### Flow 1: OAuth Client Setup (Admin / Developer Flow)
|
||||
|
||||
<Note>
|
||||
On Dify Cloud, Dify team would create OAuth apps for popular tool plugins and set up OAuth clients, saving users the trouble to configure this themselves.
|
||||
|
||||
\
|
||||
Admins of Self-Hosted Dify instances must go through this set up flow.
|
||||
</Note>
|
||||
|
||||
Dify instance's admin / developer first need to register an OAuth app at the third-party service as a trusted application. From this, they'll be able to obtain the necessary credentials to configure the Dify tool provider as an OAuth client. As an example, here are the steps to setting up an OAuth client for Dify's Gmail tool provider:
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Create a Google Cloud Project">
|
||||
1. Go to [Google Cloud Console](https://console.cloud.google.com) and create a new project, or select existing one
|
||||
2. Enable the required APIs (e.g., Gmail API)
|
||||
</Accordion>
|
||||
<Accordion title="Configure OAuth Consent Screen:">
|
||||
1. Navigate to "APIs & Services" → "OAuth consent screen"
|
||||
2. Choose "External" user type for public plugins
|
||||
3. Fill in application name, user support email, and developer contact
|
||||
4. Add authorized domains if needed
|
||||
5. For testing: Add test users in the "Test users" section
|
||||
</Accordion>
|
||||
<Accordion title="Create OAuth 2.0 Credentials">
|
||||
1. Go to "APIs & Services" → "Credentials"
|
||||
2. Click "Create Credentials" → "OAuth 2.0 Client IDs"
|
||||
3. Choose "Web application" type
|
||||
4. A`client_id` and a`client_secret` will be generated. Save these as the credentials.
|
||||
</Accordion>
|
||||
<Accordion title="Enter Credentials in Dify">
|
||||
Enter the client_id and aclient_secret on the OAuth Client configuration popup to set up the tool provider as a client.
|
||||
|
||||
<img
|
||||
src="/images/acd5f5057235c3a0c554abaedcf276fb48f80567f0231eae9158a795f8e1c45d.png"
|
||||
alt="acd5f5057235c3a0c554abaedcf276fb48f80567f0231eae9158a795f8e1c45d.png"
|
||||
title="acd5f5057235c3a0c554abaedcf276fb48f80567f0231eae9158a795f8e1c45d.png"
|
||||
className="mx-auto"
|
||||
style={{ width:"66%" }}
|
||||
/>
|
||||
</Accordion>
|
||||
<Accordion title="Authorize Redirect URI">
|
||||
Register the redirect URI generated by Dify on the Google OAuth Client's page:
|
||||
|
||||
<img
|
||||
src="/images/dfe60a714a275c5bf65f814673bd2f0a0db4fda27573a2f0b28a1c39e4c61da2.png"
|
||||
alt="dfe60a714a275c5bf65f814673bd2f0a0db4fda27573a2f0b28a1c39e4c61da2.png"
|
||||
title="dfe60a714a275c5bf65f814673bd2f0a0db4fda27573a2f0b28a1c39e4c61da2.png"
|
||||
className="mx-auto"
|
||||
style={{ width:"77%" }}
|
||||
/>
|
||||
|
||||
<Info>
|
||||
Dify displays the `redirect_uri` in the OAuth Client configuration popup. It usually follows the format:
|
||||
|
||||
```bash
|
||||
https://{your-dify-domain}/console/api/oauth/plugin/{plugin-id}/{provider-name}/{tool-name}/callback
|
||||
```
|
||||
|
||||
For self-hosted Dify, the \{your-dify-domain\} should be consistent with the `CONSOLE_WEB_URL`.
|
||||
|
||||
</Info>
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
<Tip>
|
||||
Each service has unique requirements, so always consult the specific OAuth documentation for the services you're integrating with.
|
||||
</Tip>
|
||||
|
||||
### Flow 2: User Authorization (Dify User Flow)
|
||||
|
||||
After configuring OAuth clients, individual Dify users can now authorize your plugin to access their personal accounts.
|
||||
|
||||
<img
|
||||
src="/images/833c205f5441910763b27d3e3ff0c4449a730a690da91abc3ce032c70da04223.png"
|
||||
alt="833c205f5441910763b27d3e3ff0c4449a730a690da91abc3ce032c70da04223.png"
|
||||
title="833c205f5441910763b27d3e3ff0c4449a730a690da91abc3ce032c70da04223.png"
|
||||
className="mx-auto"
|
||||
style={{ width:"67%" }}
|
||||
/>
|
||||
|
||||
## Implementation
|
||||
|
||||
### 1. Define OAuth schema in provider manifest
|
||||
|
||||
The `oauth_schema` section of the provider manifest definitions tell Dify what credentials your plugin OAuth needs and what the OAuth flow will produce. Two schemas are required for setting up OAuth:
|
||||
|
||||
#### client_schema
|
||||
|
||||
This defines the input for OAuth client setup:
|
||||
|
||||
```yaml gmail.yaml
|
||||
oauth_schema:
|
||||
client_schema:
|
||||
- name: "client_id"
|
||||
type: "secret-input"
|
||||
required: true
|
||||
url: "https://developers.google.com/identity/protocols/oauth2"
|
||||
- name: "client_secret"
|
||||
type: "secret-input"
|
||||
required: true
|
||||
```
|
||||
|
||||
<Info>
|
||||
The `url` field links directly to help documentations for the third-party service. This helps confused admins / developers.
|
||||
</Info>
|
||||
|
||||
#### credentials_schema
|
||||
|
||||
Specifies what the user authorization flow produces (Dify manages these automatically):
|
||||
|
||||
```yaml
|
||||
# also under oauth_schema
|
||||
credentials_schema:
|
||||
- name: "access_token"
|
||||
type: "secret-input"
|
||||
- name: "refresh_token"
|
||||
type: "secret-input"
|
||||
- name: "expires_at"
|
||||
type: "secret-input"
|
||||
```
|
||||
|
||||
<Info>
|
||||
Include both `oauth_schema` and `credentials_for_provider` to offer OAuth \+ API key auth options.
|
||||
</Info>
|
||||
|
||||
### 2. Complete required OAuth methods in Tool Provider
|
||||
|
||||
Add these imports to where your `ToolProvider` is implemented:
|
||||
|
||||
```python
|
||||
from dify_plugin.entities.oauth import ToolOAuthCredentials
|
||||
from dify_plugin.errors.tool import ToolProviderCredentialValidationError, ToolProviderOAuthError
|
||||
```
|
||||
|
||||
Your `ToolProvider` class must implement these three OAuth methods (taking `GmailProvider` as an example):
|
||||
|
||||
<Warning>
|
||||
Under no circumstances should the `client_secret` be returned in the credentials of `ToolOAuthCredentials`, as this could lead to security issues.
|
||||
</Warning>
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```python _oauth_get_authorization_url expandable
|
||||
def _oauth_get_authorization_url(self, redirect_uri: str, system_credentials: Mapping[str, Any]) -> str:
|
||||
"""
|
||||
Generate the authorization URL using credentials from OAuth Client Setup Flow.
|
||||
This URL is where users grant permissions.
|
||||
"""
|
||||
# Generate random state for CSRF protection (recommended for all OAuth flows)
|
||||
state = secrets.token_urlsafe(16)
|
||||
|
||||
# Define Gmail-specific scopes - request minimal necessary permissions
|
||||
scope = "read:user read:data" # Replace with your required scopes
|
||||
|
||||
# Assemble Gmail-specific payload
|
||||
params = {
|
||||
"client_id": system_credentials["client_id"], # From OAuth Client Setup
|
||||
"redirect_uri": redirect_uri, # Dify generates this - DON'T modify
|
||||
"scope": scope,
|
||||
"response_type": "code", # Standard OAuth authorization code flow
|
||||
"access_type": "offline", # Critical: gets refresh token (if supported)
|
||||
"prompt": "consent", # Forces reauth when scopes change (if supported)
|
||||
"state": state, # CSRF protection
|
||||
}
|
||||
|
||||
return f"{self._AUTH_URL}?{urllib.parse.urlencode(params)}"
|
||||
```
|
||||
|
||||
|
||||
```python _oauth_get_credentials expandable
|
||||
def _oauth_get_credentials(
|
||||
self, redirect_uri: str, system_credentials: Mapping[str, Any], request: Request
|
||||
) -> ToolOAuthCredentials:
|
||||
"""
|
||||
Exchange authorization code for access token and refresh token. This is called
|
||||
to creates ONE credential set for one account connection
|
||||
"""
|
||||
# Extract authorization code from OAuth callback
|
||||
code = request.args.get("code")
|
||||
if not code:
|
||||
raise ToolProviderOAuthError("Authorization code not provided")
|
||||
|
||||
# Check for authorization errors from OAuth provider
|
||||
error = request.args.get("error")
|
||||
if error:
|
||||
error_description = request.args.get("error_description", "")
|
||||
raise ToolProviderOAuthError(f"OAuth authorization failed: {error} - {error_description}")
|
||||
|
||||
# Exchange authorization code for tokens using OAuth Client Setup credentials
|
||||
|
||||
# Assemble Gmail-specific payload
|
||||
data = {
|
||||
"client_id": system_credentials["client_id"], # From OAuth Client Setup
|
||||
"client_secret": system_credentials["client_secret"], # From OAuth Client Setup
|
||||
"code": code, # From user's authorization
|
||||
"grant_type": "authorization_code", # Standard OAuth flow type
|
||||
"redirect_uri": redirect_uri, # Must exactly match authorization URL
|
||||
}
|
||||
|
||||
headers = {"Content-Type": "application/x-www-form-urlencoded"}
|
||||
|
||||
try:
|
||||
response = requests.post(
|
||||
self._TOKEN_URL,
|
||||
data=data,
|
||||
headers=headers,
|
||||
timeout=10
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
token_data = response.json()
|
||||
|
||||
# Handle OAuth provider errors in response
|
||||
if "error" in token_data:
|
||||
error_desc = token_data.get('error_description', token_data['error'])
|
||||
raise ToolProviderOAuthError(f"Token exchange failed: {error_desc}")
|
||||
|
||||
access_token = token_data.get("access_token")
|
||||
if not access_token:
|
||||
raise ToolProviderOAuthError("No access token received from provider")
|
||||
|
||||
# Build credentials dict matching your credentials_schema
|
||||
credentials = {
|
||||
"access_token": access_token,
|
||||
"token_type": token_data.get("token_type", "Bearer"),
|
||||
}
|
||||
|
||||
# Include refresh token if provided (critical for long-term access)
|
||||
refresh_token = token_data.get("refresh_token")
|
||||
if refresh_token:
|
||||
credentials["refresh_token"] = refresh_token
|
||||
|
||||
# Handle token expiration - some providers don't provide expires_in
|
||||
expires_in = token_data.get("expires_in", 3600) # Default to 1 hour
|
||||
expires_at = int(time.time()) + expires_in
|
||||
|
||||
return ToolOAuthCredentials(credentials=credentials, expires_at=expires_at)
|
||||
|
||||
except requests.RequestException as e:
|
||||
raise ToolProviderOAuthError(f"Network error during token exchange: {str(e)}")
|
||||
except Exception as e:
|
||||
raise ToolProviderOAuthError(f"Failed to exchange authorization code: {str(e)}")
|
||||
```
|
||||
|
||||
|
||||
```python _oauth_refresh_credentials
|
||||
def _oauth_refresh_credentials(
|
||||
self, redirect_uri: str, system_credentials: Mapping[str, Any], credentials: Mapping[str, Any]
|
||||
) -> ToolOAuthCredentials:
|
||||
"""
|
||||
Refresh the credentials using refresh token.
|
||||
Dify calls this automatically when tokens expire
|
||||
"""
|
||||
refresh_token = credentials.get("refresh_token")
|
||||
if not refresh_token:
|
||||
raise ToolProviderOAuthError("No refresh token available")
|
||||
|
||||
# Standard OAuth refresh token flow
|
||||
data = {
|
||||
"client_id": system_credentials["client_id"], # From OAuth Client Setup
|
||||
"client_secret": system_credentials["client_secret"], # From OAuth Client Setup
|
||||
"refresh_token": refresh_token, # From previous authorization
|
||||
"grant_type": "refresh_token", # OAuth refresh flow
|
||||
}
|
||||
|
||||
headers = {"Content-Type": "application/x-www-form-urlencoded"}
|
||||
|
||||
try:
|
||||
response = requests.post(
|
||||
self._TOKEN_URL,
|
||||
data=data,
|
||||
headers=headers,
|
||||
timeout=10
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
token_data = response.json()
|
||||
|
||||
# Handle refresh errors
|
||||
if "error" in token_data:
|
||||
error_desc = token_data.get('error_description', token_data['error'])
|
||||
raise ToolProviderOAuthError(f"Token refresh failed: {error_desc}")
|
||||
|
||||
access_token = token_data.get("access_token")
|
||||
if not access_token:
|
||||
raise ToolProviderOAuthError("No access token received from provider")
|
||||
|
||||
# Build new credentials, preserving existing refresh token
|
||||
new_credentials = {
|
||||
"access_token": access_token,
|
||||
"token_type": token_data.get("token_type", "Bearer"),
|
||||
"refresh_token": refresh_token, # Keep existing refresh token
|
||||
}
|
||||
|
||||
# Handle token expiration
|
||||
expires_in = token_data.get("expires_in", 3600)
|
||||
|
||||
# update refresh token if new one provided
|
||||
new_refresh_token = token_data.get("refresh_token")
|
||||
if new_refresh_token:
|
||||
new_credentials["refresh_token"] = new_refresh_token
|
||||
|
||||
# Calculate new expiration timestamp for Dify's token management
|
||||
expires_at = int(time.time()) + expires_in
|
||||
|
||||
return ToolOAuthCredentials(credentials=new_credentials, expires_at=expires_at)
|
||||
|
||||
except requests.RequestException as e:
|
||||
raise ToolProviderOAuthError(f"Network error during token refresh: {str(e)}")
|
||||
except Exception as e:
|
||||
raise ToolProviderOAuthError(f"Failed to refresh credentials: {str(e)}")
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
### 3. Access Tokens in Your Tools
|
||||
|
||||
You may use OAuth credentials to make authenticated API calls in your `Tool` implementation like so:
|
||||
|
||||
```python
|
||||
class YourTool(BuiltinTool):
|
||||
def _invoke(self, user_id: str, tool_parameters: dict[str, Any]) -> ToolInvokeMessage:
|
||||
if self.runtime.credential_type == CredentialType.OAUTH:
|
||||
access_token = self.runtime.credentials["access_token"]
|
||||
|
||||
response = requests.get("https://api.service.com/data",
|
||||
headers={"Authorization": f"Bearer {access_token}"})
|
||||
return self.create_text_message(response.text)
|
||||
```
|
||||
|
||||
`self.runtime.credentials` automatically provides the current user's tokens. Dify handles refresh automatically.
|
||||
|
||||
For plugins that support both OAuth and API_KEY authentication, you can use `self.runtime.credential_type` to differentiate between the two authentication types.
|
||||
|
||||
### 4. Specifying the Correct Versions
|
||||
|
||||
Previous versions of the plugin SDK and Dify do not support OAuth authentication. Therefore, you need to set the plugin SDK version to:
|
||||
|
||||
```
|
||||
dify_plugin>=0.4.2,<0.5.0.
|
||||
```
|
||||
|
||||
In `manifest.yaml`, add the minimum Dify version:
|
||||
|
||||
```yaml
|
||||
meta:
|
||||
version: 0.0.1
|
||||
arch:
|
||||
- amd64
|
||||
- arm64
|
||||
runner:
|
||||
language: python
|
||||
version: "3.12"
|
||||
entrypoint: main
|
||||
minimum_dify_version: 1.7.1
|
||||
```
|
||||
|
||||
{/*
|
||||
Contributing Section
|
||||
DO NOT edit this section!
|
||||
It will be automatically generated by the script.
|
||||
*/}
|
||||
|
||||
---
|
||||
|
||||
[Edit this page](https://github.com/langgenius/dify-docs/edit/main/en/plugins/quick-start/develop-plugins/tool-oauth.mdx) | [Report an issue](https://github.com/langgenius/dify-docs/issues/new?template=docs.yml)
|
||||
|
||||
Reference in New Issue
Block a user