diff --git a/docs.json b/docs.json index 7b482a61..b544c50d 100644 --- a/docs.json +++ b/docs.json @@ -210,7 +210,17 @@ "en/use-dify/workspace/app-management", "en/use-dify/workspace/team-members-management", "en/use-dify/workspace/personal-account-management", - "en/use-dify/workspace/subscription-management" + "en/use-dify/workspace/subscription-management", + { + "group": "API Extension", + "icon":"puzzle-piece-simple", + "pages": [ + "en/use-dify/workspace/api-extension/api-extension", + "en/use-dify/workspace/api-extension/external-data-tool-api-extension", + "en/use-dify/workspace/api-extension/moderation-api-extension", + "en/use-dify/workspace/api-extension/cloudflare-worker" + ] + } ] }, { @@ -585,7 +595,17 @@ "zh/use-dify/workspace/app-management", "zh/use-dify/workspace/team-members-management", "zh/use-dify/workspace/personal-account-management", - "zh/use-dify/workspace/subscription-management" + "zh/use-dify/workspace/subscription-management", + { + "group": "API 扩展", + "icon":"puzzle-piece-simple", + "pages": [ + "zh/use-dify/workspace/api-extension/api-extension", + "zh/use-dify/workspace/api-extension/external-data-tool-api-extension", + "zh/use-dify/workspace/api-extension/moderation-api-extension", + "zh/use-dify/workspace/api-extension/cloudflare-worker" + ] + } ] }, { @@ -960,7 +980,17 @@ "ja/use-dify/workspace/app-management", "ja/use-dify/workspace/team-members-management", "ja/use-dify/workspace/personal-account-management", - "ja/use-dify/workspace/subscription-management" + "ja/use-dify/workspace/subscription-management", + { + "group": "API 拡張", + "icon":"puzzle-piece-simple", + "pages": [ + "ja/use-dify/workspace/api-extension/api-extension", + "ja/use-dify/workspace/api-extension/external-data-tool-api-extension", + "ja/use-dify/workspace/api-extension/moderation-api-extension", + "ja/use-dify/workspace/api-extension/cloudflare-worker" + ] + } ] }, { diff --git a/en/use-dify/workspace/api-extension/api-extension.mdx b/en/use-dify/workspace/api-extension/api-extension.mdx new file mode 100644 index 00000000..956f6dfc --- /dev/null +++ b/en/use-dify/workspace/api-extension/api-extension.mdx @@ -0,0 +1,261 @@ +--- +title: API Extensions +sidebarTitle: Overview +--- + +You can extend module capabilities through API extensions. Currently, the following extension types are supported: + +* `moderation` Sensitive content moderation +* `external_data_tool` External data tools + +Before extending module capabilities, you need to prepare an API and an API Key for authentication. + +In addition to developing the corresponding module capabilities, you also need to follow the specifications below to ensure Dify correctly calls the API. + +## API Specification + +Dify will call your interface with the following specification: + +``` +POST {Your-API-Endpoint} +``` + +### Header + +| Header | Value | Desc | +| --------------- | ----------------- | --------------------------------------------------------------------- | +| `Content-Type` | application/json | The request content is in JSON format. | +| `Authorization` | Bearer {api_key} | The API Key is transmitted as a Token. You need to parse the `api_key` and verify that it matches the provided API Key to ensure interface security. | + +### Request Body + +``` +{ + "point": string, // Extension point, different modules may contain multiple extension points + "params": { + ... // Parameters passed to each module extension point + } +} +``` + +### API Response +``` +{ + ... // Content returned by the API, see the specification design of different modules for different extension point returns +} +``` + +## Validation + +When configuring an API-based Extension in Dify, Dify will send a request to the API Endpoint to verify API availability. + +When the API Endpoint receives `point=ping`, the interface should return `result=pong`, as follows: + +### Header + +``` +Content-Type: application/json +Authorization: Bearer {api_key} +``` + +### Request Body + +``` +{ + "point": "ping" +} +``` + +### Expected API Response + +``` +{ + "result": "pong" +} +``` + +## Example + +Here we use an external data tool as an example, where the scenario is to retrieve external weather information by region as context. + +### API Example + +``` +POST https://fake-domain.com/api/dify/receive +``` + +**Header** + +``` +Content-Type: application/json +Authorization: Bearer 123456 +``` + +**Request Body** + +``` +{ + "point": "app.external_data_tool.query", + "params": { + "app_id": "61248ab4-1125-45be-ae32-0ce91334d021", + "tool_variable": "weather_retrieve", + "inputs": { + "location": "London" + }, + "query": "How's the weather today?" + } +} +``` + +**API Response** + +``` +{ + "result": "City: London\nTemperature: 10°C\nRealFeel®: 8°C\nAir Quality: Poor\nWind Direction: ENE\nWind Speed: 8 km/h\nWind Gusts: 14 km/h\nPrecipitation: Light rain" +} +``` + +### Code Example + +The code is based on the Python FastAPI framework. + +1. Install dependencies + + ``` + pip install fastapi[all] uvicorn + ``` +2. Write code according to the interface specification + + ``` + from fastapi import FastAPI, Body, HTTPException, Header + from pydantic import BaseModel + + app = FastAPI() + + + class InputData(BaseModel): + point: str + params: dict = {} + + + @app.post("/api/dify/receive") + async def dify_receive(data: InputData = Body(...), authorization: str = Header(None)): + """ + Receive API query data from Dify. + """ + expected_api_key = "123456" # TODO Your API key of this API + auth_scheme, _, api_key = authorization.partition(' ') + + if auth_scheme.lower() != "bearer" or api_key != expected_api_key: + raise HTTPException(status_code=401, detail="Unauthorized") + + point = data.point + + # for debug + print(f"point: {point}") + + if point == "ping": + return { + "result": "pong" + } + if point == "app.external_data_tool.query": + return handle_app_external_data_tool_query(params=data.params) + # elif point == "{point name}": + # TODO other point implementation here + + raise HTTPException(status_code=400, detail="Not implemented") + + + def handle_app_external_data_tool_query(params: dict): + app_id = params.get("app_id") + tool_variable = params.get("tool_variable") + inputs = params.get("inputs") + query = params.get("query") + + # for debug + print(f"app_id: {app_id}") + print(f"tool_variable: {tool_variable}") + print(f"inputs: {inputs}") + print(f"query: {query}") + + # TODO your external data tool query implementation here, + # return must be a dict with key "result", and the value is the query result + if inputs.get("location") == "London": + return { + "result": "City: London\nTemperature: 10°C\nRealFeel®: 8°C\nAir Quality: Poor\nWind Direction: ENE\nWind " + "Speed: 8 km/h\nWind Gusts: 14 km/h\nPrecipitation: Light rain" + } + else: + return {"result": "Unknown city"} + ``` +3. Start the API service. The default port is 8000, the complete API address is: `http://127.0.0.1:8000/api/dify/receive`, and the configured API Key is `123456`. + + ``` + uvicorn main:app --reload --host 0.0.0.0 + ``` +4. Configure this API in Dify. + +5. Select this API extension in the App. + +When debugging the App, Dify will request the configured API and send the following content (example): + +``` +{ + "point": "app.external_data_tool.query", + "params": { + "app_id": "61248ab4-1125-45be-ae32-0ce91334d021", + "tool_variable": "weather_retrieve", + "inputs": { + "location": "London" + }, + "query": "How's the weather today?" + } +} +``` + +The API response is: + +``` +{ + "result": "City: London\nTemperature: 10°C\nRealFeel®: 8°C\nAir Quality: Poor\nWind Direction: ENE\nWind Speed: 8 km/h\nWind Gusts: 14 km/h\nPrecipitation: Light rain" +} +``` + +## Local Debugging + +Since the Dify cloud version cannot access internal network API services, you can use [Ngrok](https://ngrok.com) to expose the API service endpoint to the public network to enable cloud debugging of local code. Steps: + +1. Go to [https://ngrok.com](https://ngrok.com), register and download the Ngrok file. + + ![Download](https://assets-docs.dify.ai/dify-enterprise-mintlify/zh_CN/guides/extension/api-based-extension/c44d6cc5425508daac8d31bc4af113df.png) + +2. After downloading, go to the download directory, extract the archive according to the instructions below, and execute the initialization script in the instructions. + ```Shell + unzip /path/to/ngrok.zip + ./ngrok config add-authtoken your-token + ``` +3. Check the port of your local API service: + + ![Check port](https://assets-docs.dify.ai/dify-enterprise-mintlify/zh_CN/guides/extension/api-based-extension/7ac8ee0f0955f36255e0261b36499db7.png) + + And run the following command to start: + + ```Shell + ./ngrok http port-number + ``` + + A successful startup example looks like this: + + ![Ngrok startup](https://assets-docs.dify.ai/dify-enterprise-mintlify/zh_CN/guides/extension/api-based-extension/2b4adbe0bb1ff203da521ea6eea401f8.png) + +4. Find the Forwarding address, as shown above: `https://177e-159-223-41-52.ngrok-free.app` (this is an example domain, please replace with your own), which is the public domain. + +Following the example above, we expose the locally started service endpoint and replace the code example interface: `http://127.0.0.1:8000/api/dify/receive` with `https://177e-159-223-41-52.ngrok-free.app/api/dify/receive` + +This API endpoint can now be accessed publicly. At this point, we can configure this API endpoint in Dify for local code debugging. For configuration steps, please refer to [External Data Tool](/en/use-dify/workspace/api-extension/external-data-tool-api-extension). + +## Deploy API Extensions Using Cloudflare Workers + +We recommend using Cloudflare Workers to deploy your API extensions because Cloudflare Workers can conveniently provide a public network address and can be used for free. + +For detailed instructions, see [Deploy API Extensions Using Cloudflare Workers](/en/use-dify/workspace/api-extension/cloudflare-worker). diff --git a/en/use-dify/workspace/api-extension/cloudflare-worker.mdx b/en/use-dify/workspace/api-extension/cloudflare-worker.mdx new file mode 100644 index 00000000..82a6c9a6 --- /dev/null +++ b/en/use-dify/workspace/api-extension/cloudflare-worker.mdx @@ -0,0 +1,131 @@ +--- +title: Deploy API Extensions Using Cloudflare Workers +--- + +## Procedure + +Since Dify API extensions require a publicly accessible address as the API Endpoint, the API extension needs to be deployed to a public address. + +Here we use Cloudflare Workers to deploy the API extension. + +Clone the [Example GitHub Repository](https://github.com/crazywoola/dify-extension-workers). This repository contains a simple API extension that can be modified as a starting point. + +```bash +git clone https://github.com/crazywoola/dify-extension-workers.git +cp wrangler.toml.example wrangler.toml +``` + +Open the `wrangler.toml` file and modify `name` and `compatibility_date` to your application name and compatibility date. + +The configuration we need to pay attention to here is the `TOKEN` in `vars`. When adding an API extension in Dify, we need to fill in this Token. For security reasons, we recommend using a random string as the Token. You should not write the Token directly in the source code, but pass it through environment variables. Therefore, please do not commit wrangler.toml to your code repository. + +```toml +name = "dify-extension-example" +compatibility_date = "2023-01-01" + +[vars] +TOKEN = "bananaiscool" +``` + +This API extension will return a random Breaking Bad quote. You can modify the logic of this API extension in `src/index.ts`. This example demonstrates how to interact with third-party APIs. + +```typescript +// ⬇️ implement your logic here ⬇️ +// point === "app.external_data_tool.query" +// https://api.breakingbadquotes.xyz/v1/quotes +const count = params?.inputs?.count ?? 1; +const url = `https://api.breakingbadquotes.xyz/v1/quotes/${count}`; +const result = await fetch(url).then(res => res.text()) +// ⬆️ implement your logic here ⬆️ +``` + +This repository simplifies all configurations except business logic. You can directly use `npm` commands to deploy your API extension. + +```bash +npm install +npm run deploy +``` + +After successful deployment, you will get a public address that you can add in Dify as the API Endpoint. Please note not to omit the `endpoint` path. The specific definition of this path can be found in `src/index.ts`. + +![Add API Endpoint in Dify](https://assets-docs.dify.ai/dify-enterprise-mintlify/zh_CN/guides/extension/api-based-extension/9433a486a441713ade6270e9dc6c0544.png) + +Alternatively, you can use the `npm run dev` command to deploy locally for testing. + +```bash +npm install +npm run dev +``` + +Related output: + +```bash +$ npm run dev +> dev +> wrangler dev src/index.ts + + ⛅️ wrangler 3.99.0 +------------------- + +Your worker has access to the following bindings: +- Vars: + - TOKEN: "ban****ool" +⎔ Starting local server... +[wrangler:inf] Ready on http://localhost:58445 +``` + +After that, you can use tools like Postman for local interface debugging. + +## About Bearer Auth + +```typescript +import { bearerAuth } from "hono/bearer-auth"; + +(c, next) => { + const auth = bearerAuth({ token: c.env.TOKEN }); + return auth(c, next); +}, +``` + +Our Bearer validation logic is in the above code. We use the `hono/bearer-auth` package to implement Bearer validation. You can use `c.env.TOKEN` in `src/index.ts` to get the Token. + +## About Parameter Validation + +```typescript +import { z } from "zod"; +import { zValidator } from "@hono/zod-validator"; + +const schema = z.object({ + point: z.union([ + z.literal("ping"), + z.literal("app.external_data_tool.query"), + ]), // Restricts 'point' to two specific values + params: z + .object({ + app_id: z.string().optional(), + tool_variable: z.string().optional(), + inputs: z.record(z.any()).optional(), + query: z.any().optional(), // string or null + }) + .optional(), +}); + +``` + +We use `zod` to define parameter types here. You can use `zValidator` in `src/index.ts` to validate parameters. Use `const { point, params } = c.req.valid("json");` to get the validated parameters. + +The `point` here has only two values, so we use `z.union` to define it. `params` is an optional parameter, so we use `z.optional` to define it. There will be an `inputs` parameter, which is a `Record` type. This type represents an object with string keys and any values. This type can represent any object. You can use `params?.inputs?.count` in `src/index.ts` to get the `count` parameter. + +## Get Cloudflare Workers Logs + +```bash +wrangler tail +``` + +--- + +**Reference**: + +* [Cloudflare Workers](https://workers.cloudflare.com/) +* [Cloudflare Workers CLI](https://developers.cloudflare.com/workers/cli-wrangler/install-update) +* [Example GitHub Repository](https://github.com/crazywoola/dify-extension-workers) diff --git a/en/use-dify/workspace/api-extension/external-data-tool-api-extension.mdx b/en/use-dify/workspace/api-extension/external-data-tool-api-extension.mdx new file mode 100644 index 00000000..a1c615b0 --- /dev/null +++ b/en/use-dify/workspace/api-extension/external-data-tool-api-extension.mdx @@ -0,0 +1,63 @@ +--- +title: External Data Tool +--- + +When creating AI applications, you can use external tools to obtain additional data through [API Extensions](/en/use-dify/workspace/api-extension/api-extension) and assemble it into the prompt as additional information for the LLM. + +## Extension Point + +`app.external_data_tool.query`: Application external data tool query extension point. + +This extension point passes the application variable content input by the end user and the conversation input content (fixed parameter for conversational applications) to the API as parameters. + +You need to implement the corresponding tool query logic and return query results as string type. + +### Request Body + +``` +{ + "point": "app.external_data_tool.query", // Extension point type, fixed as app.external_data_tool.query + "params": { + "app_id": string, // Application ID + "tool_variable": string, // External data tool variable name, indicating the source of the corresponding variable tool call + "inputs": { // Variable values passed by end user, key is variable name, value is variable value + "var_1": "value_1", + "var_2": "value_2", + ... + }, + "query": string | null // Current conversation input content from end user, fixed parameter for conversational applications. + } +} +``` + +**Example**: + + ``` + { + "point": "app.external_data_tool.query", + "params": { + "app_id": "61248ab4-1125-45be-ae32-0ce91334d021", + "tool_variable": "weather_retrieve", + "inputs": { + "location": "London" + }, + "query": "How's the weather today?" + } + } + ``` + +### API Response + +``` +{ + "result": string +} +``` + +**Example**: + + ``` + { + "result": "City: London\nTemperature: 10°C\nRealFeel®: 8°C\nAir Quality: Poor\nWind Direction: ENE\nWind Speed: 8 km/h\nWind Gusts: 14 km/h\nPrecipitation: Light rain" + } + ``` diff --git a/en/use-dify/workspace/api-extension/moderation-api-extension.mdx b/en/use-dify/workspace/api-extension/moderation-api-extension.mdx new file mode 100644 index 00000000..1a290d79 --- /dev/null +++ b/en/use-dify/workspace/api-extension/moderation-api-extension.mdx @@ -0,0 +1,255 @@ +--- +title: Sensitive Content Moderation +--- + +This module is used to review the content input by end users and the content output by LLM in applications. It is divided into two extension point types. + +## Extension Points + +* `app.moderation.input`: End user input content review extension point + * Used to review variable content passed in by end users and conversation input content in conversational applications. +* `app.moderation.output`: LLM output content review extension point + * Used to review content output by LLM. + * When the LLM output is streaming, the output content will be segmented into 100-character chunks for API requests to avoid delayed reviews when output content is lengthy. + +### app.moderation.input + +When **Content Moderation > Review Input Content** is enabled in applications such as Chatflow, Agent, or Chatbot, Dify will send the following HTTP POST request to the corresponding API extension: + +#### Request Body + +``` +{ + "point": "app.moderation.input", // Extension point type, fixed as app.moderation.input + "params": { + "app_id": string, // Application ID + "inputs": { // Variable values passed by end user, key is variable name, value is variable value + "var_1": "value_1", + "var_2": "value_2", + ... + }, + "query": string | null // Current conversation input content from end user, fixed parameter for conversational applications. + } +} +``` + +**Example**: + + ``` + { + "point": "app.moderation.input", + "params": { + "app_id": "61248ab4-1125-45be-ae32-0ce91334d021", + "inputs": { + "var_1": "I will kill you.", + "var_2": "I will fuck you." + }, + "query": "Happy everydays." + } + } + ``` + +#### API Response + +``` +{ + "flagged": bool, // Whether it violates validation rules + "action": string, // Action: direct_output outputs preset response; overridden overwrites input variable values + "preset_response": string, // Preset response (returned only when action=direct_output) + "inputs": { // Variable values passed by end user, key is variable name, value is variable value (returned only when action=overridden) + "var_1": "value_1", + "var_2": "value_2", + ... + }, + "query": string | null // Overwritten current conversation input content from end user, fixed parameter for conversational applications. (returned only when action=overridden) +} +``` + +**Example**: + + * `action=direct_output` + ``` + { + "flagged": true, + "action": "direct_output", + "preset_response": "Your content violates our usage policy." + } + ``` + * `action=overridden` + ``` + { + "flagged": true, + "action": "overridden", + "inputs": { + "var_1": "I will *** you.", + "var_2": "I will *** you." + }, + "query": "Happy everydays." + } + ``` + +### app.moderation.output + +When **Content Moderation > Review Output Content** is enabled in applications such as Chatflow, Agent, or Chat Assistant, Dify will send the following HTTP POST request to the corresponding API extension: + +#### Request Body + +``` +{ + "point": "app.moderation.output", // Extension point type, fixed as app.moderation.output + "params": { + "app_id": string, // Application ID + "text": string // LLM response content. When LLM output is streaming, this is content segmented into 100-character chunks. + } +} +``` + +**Example**: + ``` + { + "point": "app.moderation.output", + "params": { + "app_id": "61248ab4-1125-45be-ae32-0ce91334d021", + "text": "I will kill you." + } + } + ``` + +#### API Response + +``` +{ + "flagged": bool, // Whether it violates validation rules + "action": string, // Action: direct_output outputs preset response; overridden overwrites input variable values + "preset_response": string, // Preset response (returned only when action=direct_output) + "text": string // Overwritten LLM response content. (returned only when action=overridden) +} +``` + +**Example**: + + * `action=direct_output` + ``` + { + "flagged": true, + "action": "direct_output", + "preset_response": "Your content violates our usage policy." + } + ``` + * `action=overridden` + ``` + { + "flagged": true, + "action": "overridden", + "text": "I will *** you." + } + ``` + +## Code Example + +Below is a piece of `src/index.ts` code that can be deployed on Cloudflare. (For complete Cloudflare usage, please refer to [this documentation](/en/use-dify/workspace/api-extension/cloudflare-worker)) + +The code works by performing keyword matching to filter both Input (content entered by users) and Output (content returned by the model). Users can modify the matching logic according to their needs. +``` +import { Hono } from "hono"; +import { bearerAuth } from "hono/bearer-auth"; +import { z } from "zod"; +import { zValidator } from "@hono/zod-validator"; +import { generateSchema } from '@anatine/zod-openapi'; + +type Bindings = { + TOKEN: string; +}; + +const app = new Hono<{ Bindings: Bindings }>(); + +// API format validation ⬇️ +const schema = z.object({ + point: z.union([ + z.literal("ping"), + z.literal("app.external_data_tool.query"), + z.literal("app.moderation.input"), + z.literal("app.moderation.output"), + ]), // Restricts 'point' to two specific values + params: z + .object({ + app_id: z.string().optional(), + tool_variable: z.string().optional(), + inputs: z.record(z.any()).optional(), + query: z.any(), + text: z.any() + }) + .optional(), +}); + + +// Generate OpenAPI schema +app.get("/", (c) => { + return c.json(generateSchema(schema)); +}); + +app.post( + "/", + (c, next) => { + const auth = bearerAuth({ token: c.env.TOKEN }); + return auth(c, next); + }, + zValidator("json", schema), + async (c) => { + const { point, params } = c.req.valid("json"); + if (point === "ping") { + return c.json({ + result: "pong", + }); + } + // ⬇️ implement your logic here ⬇️ + // point === "app.external_data_tool.query" + else if (point === "app.moderation.input"){ + // Input check ⬇️ + const inputkeywords = ["input filter test 1", "input filter test 2", "input filter test 3"]; + + if (inputkeywords.some(keyword => params.query.includes(keyword))) + { + return c.json({ + "flagged": true, + "action": "direct_output", + "preset_response": "The input contains illegal content. Please try a different question!" + }); + } else { + return c.json({ + "flagged": false, + "action": "direct_output", + "preset_response": "Input is normal" + }); + } + // Input check complete + } + + else { + // Output check ⬇️ + const outputkeywords = ["output filter test 1", "output filter test 2", "output filter test 3"]; + + if (outputkeywords.some(keyword => params.text.includes(keyword))) + { + return c.json({ + "flagged": true, + "action": "direct_output", + "preset_response": "The output contains sensitive content and has been filtered by the system. Please try a different question!" + }); + } + + else { + return c.json({ + "flagged": false, + "action": "direct_output", + "preset_response": "Output is normal" + }); + }; + } + // Output check complete + } +); + +export default app; + +``` diff --git a/ja/use-dify/workspace/api-extension/api-extension.mdx b/ja/use-dify/workspace/api-extension/api-extension.mdx new file mode 100644 index 00000000..d1aba75c --- /dev/null +++ b/ja/use-dify/workspace/api-extension/api-extension.mdx @@ -0,0 +1,262 @@ +--- +title: API 拡張 +sidebarTitle: 概要 +--- + +開発者は API 拡張モジュール機能を使用してモジュール機能を拡張できます。現在、以下のモジュール拡張がサポートされています: + +* `moderation` センシティブコンテンツ審査 +* `external_data_tool` 外部データツール + +モジュール機能を拡張する前に、API と認証用の API Key を準備する必要があります。 + +対応するモジュール機能を開発する必要があるだけでなく、Dify が API を正しく呼び出せるように、以下の仕様に従う必要があります。 + +## API 仕様 + +Dify は以下の仕様でインターフェースを呼び出します: + +``` +POST {Your-API-Endpoint} +``` + +### Header + +| Header | Value | Desc | +| --------------- | ----------------- | --------------------------------------------------------------------- | +| `Content-Type` | application/json | リクエストコンテンツは JSON 形式です。 | +| `Authorization` | Bearer {api_key} | API Key はトークン形式で送信されます。この `api_key` を解析し、提供された API Key と一致することを確認して、インターフェースのセキュリティを確保する必要があります。 | + +### Request Body + +``` +{ + "point": string, // 拡張ポイント、異なるモジュールには複数の拡張ポイントが含まれる場合があります + "params": { + ... // 各モジュール拡張ポイントに渡されるパラメータ + } +} +``` + +### API レスポンス + +``` +{ + ... // API が返すコンテンツ、異なる拡張ポイントのレスポンスについては各モジュールの仕様設計を参照してください +} +``` + +## 検証 + +Dify で API-based Extension を設定する際、Dify は API の可用性を確認するために API Endpoint にリクエストを送信します。 + +API Endpoint が `point=ping` を受信したとき、インターフェースは `result=pong` を返す必要があります。具体的には以下の通りです: + +### Header + +``` +Content-Type: application/json +Authorization: Bearer {api_key} +``` + +### Request Body + +``` +{ + "point": "ping" +} +``` + +### API 期待レスポンス + +``` +{ + "result": "pong" +} +``` + +## サンプル + +ここでは外部データツールを例として、地域に基づいて外部の天気情報をコンテキストとして取得するシナリオを示します。 + +### API サンプル + +``` +POST https://fake-domain.com/api/dify/receive +``` + +**Header** + +``` +Content-Type: application/json +Authorization: Bearer 123456 +``` + +**Request Body** + +``` +{ + "point": "app.external_data_tool.query", + "params": { + "app_id": "61248ab4-1125-45be-ae32-0ce91334d021", + "tool_variable": "weather_retrieve", + "inputs": { + "location": "London" + }, + "query": "How's the weather today?" + } +} +``` + +**API レスポンス** + +``` +{ + "result": "City: London\nTemperature: 10°C\nRealFeel®: 8°C\nAir Quality: Poor\nWind Direction: ENE\nWind Speed: 8 km/h\nWind Gusts: 14 km/h\nPrecipitation: Light rain" +} +``` + +### コードサンプル + +コードは Python FastAPI フレームワークに基づいています。 + +1. 依存関係をインストール + + ``` + pip install fastapi[all] uvicorn + ``` +2. インターフェース仕様に従ってコードを記述 + + ``` + from fastapi import FastAPI, Body, HTTPException, Header + from pydantic import BaseModel + + app = FastAPI() + + + class InputData(BaseModel): + point: str + params: dict = {} + + + @app.post("/api/dify/receive") + async def dify_receive(data: InputData = Body(...), authorization: str = Header(None)): + """ + Receive API query data from Dify. + """ + expected_api_key = "123456" # TODO Your API key of this API + auth_scheme, _, api_key = authorization.partition(' ') + + if auth_scheme.lower() != "bearer" or api_key != expected_api_key: + raise HTTPException(status_code=401, detail="Unauthorized") + + point = data.point + + # for debug + print(f"point: {point}") + + if point == "ping": + return { + "result": "pong" + } + if point == "app.external_data_tool.query": + return handle_app_external_data_tool_query(params=data.params) + # elif point == "{point name}": + # TODO other point implementation here + + raise HTTPException(status_code=400, detail="Not implemented") + + + def handle_app_external_data_tool_query(params: dict): + app_id = params.get("app_id") + tool_variable = params.get("tool_variable") + inputs = params.get("inputs") + query = params.get("query") + + # for debug + print(f"app_id: {app_id}") + print(f"tool_variable: {tool_variable}") + print(f"inputs: {inputs}") + print(f"query: {query}") + + # TODO your external data tool query implementation here, + # return must be a dict with key "result", and the value is the query result + if inputs.get("location") == "London": + return { + "result": "City: London\nTemperature: 10°C\nRealFeel®: 8°C\nAir Quality: Poor\nWind Direction: ENE\nWind " + "Speed: 8 km/h\nWind Gusts: 14 km/h\nPrecipitation: Light rain" + } + else: + return {"result": "Unknown city"} + ``` +3. API サービスを起動します。デフォルトポートは 8000 で、完全な API アドレスは `http://127.0.0.1:8000/api/dify/receive`、設定された API Key は `123456` です。 + + ``` + uvicorn main:app --reload --host 0.0.0.0 + ``` +4. Dify でこの API を設定します。 + +5. App でこの API 拡張を選択します。 + +App のデバッグ時、Dify は設定された API にリクエストを送信し、以下の内容を送信します(サンプル): + +``` +{ + "point": "app.external_data_tool.query", + "params": { + "app_id": "61248ab4-1125-45be-ae32-0ce91334d021", + "tool_variable": "weather_retrieve", + "inputs": { + "location": "London" + }, + "query": "How's the weather today?" + } +} +``` + +API レスポンスは: + +``` +{ + "result": "City: London\nTemperature: 10°C\nRealFeel®: 8°C\nAir Quality: Poor\nWind Direction: ENE\nWind Speed: 8 km/h\nWind Gusts: 14 km/h\nPrecipitation: Light rain" +} +``` + +## ローカルデバッグ + +Dify クラウド版では内部ネットワークの API サービスにアクセスできないため、ローカルで API サービスをデバッグするために、[Ngrok](https://ngrok.com) を使用して API サービスのエンドポイントを公開ネットワークに公開し、クラウドでローカルコードをデバッグできるようにします。操作手順: + +1. [https://ngrok.com](https://ngrok.com) の公式サイトにアクセスし、登録して Ngrok ファイルをダウンロードします。 + + ![Download](https://assets-docs.dify.ai/dify-enterprise-mintlify/zh_CN/guides/extension/api-based-extension/c44d6cc5425508daac8d31bc4af113df.png) + +2. ダウンロード完了後、ダウンロードディレクトリに移動し、以下の説明に従って圧縮ファイルを解凍し、説明の初期化スクリプトを実行します。 + ```Shell + unzip /path/to/ngrok.zip + ./ngrok config add-authtoken あなたのToken + ``` +3. ローカル API サービスのポートを確認します: + + ![ポート確認](https://assets-docs.dify.ai/dify-enterprise-mintlify/zh_CN/guides/extension/api-based-extension/7ac8ee0f0955f36255e0261b36499db7.png) + + そして、以下のコマンドを実行して起動します: + + ```Shell + ./ngrok http ポート番号 + ``` + + 起動成功のサンプルは以下の通りです: + + ![Ngrok 起動](https://assets-docs.dify.ai/dify-enterprise-mintlify/zh_CN/guides/extension/api-based-extension/2b4adbe0bb1ff203da521ea6eea401f8.png) + +4. Forwarding の中から、上図のように `https://177e-159-223-41-52.ngrok-free.app`(これはサンプルドメインです、自分のものに置き換えてください)が公開ネットワークドメインです。 + +上記のサンプルに従って、ローカルで既に起動されているサービスエンドポイントを公開し、コードサンプルのインターフェース `http://127.0.0.1:8000/api/dify/receive` を `https://177e-159-223-41-52.ngrok-free.app/api/dify/receive` に置き換えます。 + +この API エンドポイントは公開ネットワークでアクセス可能になります。これで、Dify でこの API エンドポイントを設定してローカルコードをデバッグできます。設定手順については、[外部データツール](/ja/use-dify/workspace/api-extension/external-data-tool-api-extension) を参照してください。 + +## Cloudflare Workers を使用した API 拡張のデプロイ + +API 拡張をデプロイするには Cloudflare Workers の使用をお勧めします。Cloudflare Workers は簡単に公開ネットワークアドレスを提供でき、無料で使用できます。 + +詳細な説明については、[Cloudflare Workers を使用した API 拡張のデプロイ](/ja/use-dify/workspace/api-extension/cloudflare-worker) を参照してください。 diff --git a/ja/use-dify/workspace/api-extension/cloudflare-worker.mdx b/ja/use-dify/workspace/api-extension/cloudflare-worker.mdx new file mode 100644 index 00000000..decc1237 --- /dev/null +++ b/ja/use-dify/workspace/api-extension/cloudflare-worker.mdx @@ -0,0 +1,131 @@ +--- +title: Cloudflare Workers を使用した API 拡張のデプロイ +--- + +## はじめに + +Dify API 拡張は API Endpoint としてアクセス可能な公開ネットワークアドレスを使用する必要があるため、API 拡張を公開ネットワークアドレスにデプロイする必要があります。 + +ここでは Cloudflare Workers を使用して API 拡張をデプロイします。 + +[Example GitHub Repository](https://github.com/crazywoola/dify-extension-workers) をクローンします。このリポジトリには簡単な API 拡張が含まれており、これをベースに変更できます。 + +```bash +git clone https://github.com/crazywoola/dify-extension-workers.git +cp wrangler.toml.example wrangler.toml +``` + +`wrangler.toml` ファイルを開き、`name` と `compatibility_date` をアプリケーション名と互換性日付に変更します。 + +ここで注意が必要な設定は、`vars` 内の `TOKEN` です。Dify で API 拡張を追加する際に、この Token を入力する必要があります。セキュリティ上の理由から、Token としてランダムな文字列を使用することをお勧めします。ソースコードに直接 Token を書き込むのではなく、環境変数を使用して Token を渡すべきです。そのため、wrangler.toml をコードリポジトリにコミットしないでください。 + +```toml +name = "dify-extension-example" +compatibility_date = "2023-01-01" + +[vars] +TOKEN = "bananaiscool" +``` + +この API 拡張はランダムな Breaking Bad の名言を返します。`src/index.ts` でこの API 拡張のロジックを変更できます。この例は、サードパーティ API とのやり取り方法を示しています。 + +```typescript +// ⬇️ implement your logic here ⬇️ +// point === "app.external_data_tool.query" +// https://api.breakingbadquotes.xyz/v1/quotes +const count = params?.inputs?.count ?? 1; +const url = `https://api.breakingbadquotes.xyz/v1/quotes/${count}`; +const result = await fetch(url).then(res => res.text()) +// ⬆️ implement your logic here ⬆️ +``` + +このリポジトリはビジネスロジック以外のすべての設定を簡略化しています。`npm` コマンドを直接使用して API 拡張をデプロイできます。 + +```bash +npm install +npm run deploy +``` + +デプロイが成功すると、公開ネットワークアドレスが取得できます。このアドレスを Dify の API Endpoint として追加できます。`endpoint` パスを忘れないでください。このパスの具体的な定義は `src/index.ts` で確認できます。 + +![Dify で API Endpoint を追加](https://assets-docs.dify.ai/dify-enterprise-mintlify/zh_CN/guides/extension/api-based-extension/9433a486a441713ade6270e9dc6c0544.png) + +また、`npm run dev` コマンドを使用してローカルにデプロイしてテストすることもできます。 + +```bash +npm install +npm run dev +``` + +関連する出力: + +```bash +$ npm run dev +> dev +> wrangler dev src/index.ts + + ⛅️ wrangler 3.99.0 +------------------- + +Your worker has access to the following bindings: +- Vars: + - TOKEN: "ban****ool" +⎔ Starting local server... +[wrangler:inf] Ready on http://localhost:58445 +``` + +その後、Postman などのツールを使用してローカルインターフェースをデバッグできます。 + +## Bearer Auth について + +```typescript +import { bearerAuth } from "hono/bearer-auth"; + +(c, next) => { + const auth = bearerAuth({ token: c.env.TOKEN }); + return auth(c, next); +}, +``` + +Bearer 検証ロジックは上記のコードにあります。`hono/bearer-auth` パッケージを使用して Bearer 検証を実装しています。`src/index.ts` で `c.env.TOKEN` を使用して Token を取得できます。 + +## パラメータ検証について + +```typescript +import { z } from "zod"; +import { zValidator } from "@hono/zod-validator"; + +const schema = z.object({ + point: z.union([ + z.literal("ping"), + z.literal("app.external_data_tool.query"), + ]), // Restricts 'point' to two specific values + params: z + .object({ + app_id: z.string().optional(), + tool_variable: z.string().optional(), + inputs: z.record(z.any()).optional(), + query: z.any().optional(), // string or null + }) + .optional(), +}); + +``` + +ここでは `zod` を使用してパラメータの型を定義しています。`src/index.ts` で `zValidator` を使用してパラメータを検証できます。`const { point, params } = c.req.valid("json");` で検証済みパラメータを取得します。 + +ここでの `point` は 2 つの値しかないため、`z.union` を使用して定義しています。`params` はオプションのパラメータなので、`z.optional` を使用して定義しています。その中には `inputs` パラメータがあり、これは `Record` 型です。この型は、キーが string で値が any のオブジェクトを表します。この型は任意のオブジェクトを表すことができます。`src/index.ts` で `params?.inputs?.count` を使用して `count` パラメータを取得できます。 + +## Cloudflare Workers のログを取得 + +```bash +wrangler tail +``` + +--- + +**参考資料** + +* [Cloudflare Workers](https://workers.cloudflare.com/) +* [Cloudflare Workers CLI](https://developers.cloudflare.com/workers/cli-wrangler/install-update) +* [Example GitHub Repository](https://github.com/crazywoola/dify-extension-workers) diff --git a/ja/use-dify/workspace/api-extension/external-data-tool-api-extension.mdx b/ja/use-dify/workspace/api-extension/external-data-tool-api-extension.mdx new file mode 100644 index 00000000..c9ab6081 --- /dev/null +++ b/ja/use-dify/workspace/api-extension/external-data-tool-api-extension.mdx @@ -0,0 +1,63 @@ +--- +title: 外部データツール +--- + +AI アプリケーションを作成する際、開発者は [API 拡張](/ja/use-dify/workspace/api-extension/api-extension) を通じて外部ツールを使用して追加データを取得し、それを Prompt に組み込んで LLM の追加情報として使用できます。 + +## 拡張ポイント + +`app.external_data_tool.query`:アプリケーション外部データツールクエリ拡張ポイント。 + +この拡張ポイントは、エンドユーザーが入力したアプリケーション変数の内容と会話入力内容(会話型アプリケーションの固定パラメータ)をパラメータとして API に渡します。 + +開発者は対応するツールのクエリロジックを実装し、文字列型のクエリ結果を返す必要があります。 + +### Request Body + +``` +{ + "point": "app.external_data_tool.query", // 拡張ポイントタイプ、ここでは app.external_data_tool.query に固定 + "params": { + "app_id": string, // アプリケーション ID + "tool_variable": string, // 外部データツール変数名、対応する変数ツール呼び出しのソースを示す + "inputs": { // エンドユーザーが渡した変数値、key は変数名、value は変数値 + "var_1": "value_1", + "var_2": "value_2", + ... + }, + "query": string | null // エンドユーザーの現在の会話入力内容、会話型アプリケーションの固定パラメータ。 + } +} +``` + +**例**: + + ``` + { + "point": "app.external_data_tool.query", + "params": { + "app_id": "61248ab4-1125-45be-ae32-0ce91334d021", + "tool_variable": "weather_retrieve", + "inputs": { + "location": "London" + }, + "query": "How's the weather today?" + } + } + ``` + +### API レスポンス + +``` +{ + "result": string +} +``` + +**例**: + + ``` + { + "result": "City: London\nTemperature: 10°C\nRealFeel®: 8°C\nAir Quality: Poor\nWind Direction: ENE\nWind Speed: 8 km/h\nWind Gusts: 14 km/h\nPrecipitation: Light rain" + } + ``` diff --git a/ja/use-dify/workspace/api-extension/moderation-api-extension.mdx b/ja/use-dify/workspace/api-extension/moderation-api-extension.mdx new file mode 100644 index 00000000..9cbb0954 --- /dev/null +++ b/ja/use-dify/workspace/api-extension/moderation-api-extension.mdx @@ -0,0 +1,254 @@ +--- +title: センシティブコンテンツ審査 +--- + +このモジュールは、アプリケーション内でエンドユーザーが入力したコンテンツと LLM が出力したコンテンツを審査するために使用され、2 つの拡張ポイントタイプに分かれています。 + +## 拡張ポイント + +* `app.moderation.input`:エンドユーザー入力コンテンツ審査拡張ポイント + * エンドユーザーが渡した変数内容と会話型アプリケーションの会話入力内容を審査するために使用されます。 +* `app.moderation.output`:LLM 出力コンテンツ審査拡張ポイント + * LLM が出力したコンテンツを審査するために使用されます。 + * LLM の出力がストリーミングの場合、出力内容は 100 文字を 1 セグメントとして API にリクエストされ、出力内容が長い場合の審査の遅延を可能な限り回避します。 + +### app.moderation.input + +Chatflow、Agent、チャットアシスタントなどのアプリケーションで**コンテンツ審査 > 入力コンテンツを審査**が有効になっている場合、Dify は対応する API 拡張に以下の HTTP POST リクエストを送信します: + +#### Request Body + +``` +{ + "point": "app.moderation.input", // 拡張ポイントタイプ、ここでは app.moderation.input に固定 + "params": { + "app_id": string, // アプリケーション ID + "inputs": { // エンドユーザーが渡した変数値、key は変数名、value は変数値 + "var_1": "value_1", + "var_2": "value_2", + ... + }, + "query": string | null // エンドユーザーの現在の会話入力内容、会話型アプリケーションの固定パラメータ。 + } +} +``` + +**例**: + + ``` + { + "point": "app.moderation.input", + "params": { + "app_id": "61248ab4-1125-45be-ae32-0ce91334d021", + "inputs": { + "var_1": "I will kill you.", + "var_2": "I will fuck you." + }, + "query": "Happy everydays." + } + } + ``` + +#### API レスポンス仕様 + +``` +{ + "flagged": bool, // 検証ルールに違反しているかどうか + "action": string, // アクション、direct_output は事前設定された回答を直接出力; overridden は入力変数値を上書き + "preset_response": string, // 事前設定された回答(action=direct_output の場合にのみ返される) + "inputs": { // エンドユーザーが渡した変数値、key は変数名、value は変数値(action=overridden の場合にのみ返される) + "var_1": "value_1", + "var_2": "value_2", + ... + }, + "query": string | null // 上書きされたエンドユーザーの現在の会話入力内容、会話型アプリケーションの固定パラメータ。(action=overridden の場合にのみ返される) +} +``` + +**例**: + + * `action=direct_output` + ``` + { + "flagged": true, + "action": "direct_output", + "preset_response": "Your content violates our usage policy." + } + ``` + * `action=overridden` + ``` + { + "flagged": true, + "action": "overridden", + "inputs": { + "var_1": "I will *** you.", + "var_2": "I will *** you." + }, + "query": "Happy everydays." + } + ``` + +### app.moderation.output + +Chatflow、Agent、チャットアシスタントなどのアプリケーションで**コンテンツ審査 > 出力コンテンツを審査**が有効になっている場合、Dify は対応する API 拡張に以下の HTTP POST リクエストを送信します: + +#### Request Body + +``` +{ + "point": "app.moderation.output", // 拡張ポイントタイプ、ここでは app.moderation.output に固定 + "params": { + "app_id": string, // アプリケーション ID + "text": string // LLM 応答内容。LLM の出力がストリーミングの場合、ここでは 100 文字を 1 セグメントとした内容。 + } +} +``` + +**例**: + ``` + { + "point": "app.moderation.output", + "params": { + "app_id": "61248ab4-1125-45be-ae32-0ce91334d021", + "text": "I will kill you." + } + } + ``` + +#### API レスポンス + +``` +{ + "flagged": bool, // 検証ルールに違反しているかどうか + "action": string, // アクション、direct_output は事前設定された回答を直接出力; overridden は入力変数値を上書き + "preset_response": string, // 事前設定された回答(action=direct_output の場合にのみ返される) + "text": string // 上書きされた LLM 応答内容。(action=overridden の場合にのみ返される) +} +``` + +**例**: + + * `action=direct_output` + ``` + { + "flagged": true, + "action": "direct_output", + "preset_response": "Your content violates our usage policy." + } + ``` + * `action=overridden` + ``` + { + "flagged": true, + "action": "overridden", + "text": "I will *** you." + } + ``` + +## コードサンプル +以下は、Cloudflare にデプロイ可能な `src/index.ts` コードの一部です。(Cloudflare の完全な使用方法については [このドキュメント](/ja/use-dify/workspace/api-extension/cloudflare-worker) を参照してください) + +コードの動作原理はキーワードマッチングを行い、Input(ユーザーが入力したコンテンツ)および Output(大規模モデルが返したコンテンツ)をフィルタリングすることです。ユーザーは必要に応じてマッチングロジックを変更できます。 +``` +import { Hono } from "hono"; +import { bearerAuth } from "hono/bearer-auth"; +import { z } from "zod"; +import { zValidator } from "@hono/zod-validator"; +import { generateSchema } from '@anatine/zod-openapi'; + +type Bindings = { + TOKEN: string; +}; + +const app = new Hono<{ Bindings: Bindings }>(); + +// API フォーマット検証 ⬇️ +const schema = z.object({ + point: z.union([ + z.literal("ping"), + z.literal("app.external_data_tool.query"), + z.literal("app.moderation.input"), + z.literal("app.moderation.output"), + ]), // Restricts 'point' to two specific values + params: z + .object({ + app_id: z.string().optional(), + tool_variable: z.string().optional(), + inputs: z.record(z.any()).optional(), + query: z.any(), + text: z.any() + }) + .optional(), +}); + + +// Generate OpenAPI schema +app.get("/", (c) => { + return c.json(generateSchema(schema)); +}); + +app.post( + "/", + (c, next) => { + const auth = bearerAuth({ token: c.env.TOKEN }); + return auth(c, next); + }, + zValidator("json", schema), + async (c) => { + const { point, params } = c.req.valid("json"); + if (point === "ping") { + return c.json({ + result: "pong", + }); + } + // ⬇️ impliment your logic here ⬇️ + // point === "app.external_data_tool.query" + else if (point === "app.moderation.input"){ + // 入力チェック ⬇️ + const inputkeywords = ["入力フィルターテスト1", "入力フィルターテスト2", "入力フィルターテスト3"]; + + if (inputkeywords.some(keyword => params.query.includes(keyword))) + { + return c.json({ + "flagged": true, + "action": "direct_output", + "preset_response": "入力に違法コンテンツが含まれています。別の質問で試してください!" + }); + } else { + return c.json({ + "flagged": false, + "action": "direct_output", + "preset_response": "入力は正常です" + }); + } + // 入力チェック完了 + } + + else { + // 出力チェック ⬇️ + const outputkeywords = ["出力フィルターテスト1", "出力フィルターテスト2", "出力フィルターテスト3"]; + + if (outputkeywords.some(keyword => params.text.includes(keyword))) + { + return c.json({ + "flagged": true, + "action": "direct_output", + "preset_response": "出力にセンシティブコンテンツが含まれており、システムによってフィルタリングされました。別の質問で試してください!" + }); + } + + else { + return c.json({ + "flagged": false, + "action": "direct_output", + "preset_response": "出力は正常です" + }); + }; + } + // 出力チェック完了 + } +); + +export default app; + +``` diff --git a/zh/use-dify/workspace/api-extension/api-extension.mdx b/zh/use-dify/workspace/api-extension/api-extension.mdx new file mode 100644 index 00000000..77f219d1 --- /dev/null +++ b/zh/use-dify/workspace/api-extension/api-extension.mdx @@ -0,0 +1,261 @@ +--- +title: API 扩展 +sidebarTitle: 概览 +--- + +开发者可通过 API 扩展模块能力,当前支持以下模块扩展: + +* `moderation` 敏感内容审计 +* `external_data_tool` 外部数据工具 + +在扩展模块能力之前,你需要准备一个 API 和用于鉴权的 API Key。 + +除了需要开发对应的模块能力,还需要遵照以下规范,以便 Dify 正确调用 API。 + +## API 规范 + +Dify 将会以以下规范调用你的接口: + +``` +POST {Your-API-Endpoint} +``` + +### Header + +| Header | Value | Desc | +| --------------- | ----------------- | --------------------------------------------------------------------- | +| `Content-Type` | application/json | 请求内容为 JSON 格式。 | +| `Authorization` | Bearer {api_key} | API Key 以 Token 令牌的方式传输,你需要解析该 `api_key` 并确认是否和提供的 API Key 一致,保证接口安全。 | + +### Request Body + +``` +{ + "point": string, // 扩展点,不同模块可能包含多个扩展点 + "params": { + ... // 各模块扩展点传入参数 + } +} +``` + +### API 返回 + +``` +{ + ... // API 返回的内容,不同扩展点返回见不同模块的规范设计 +} +``` + +## 校验 + +在 Dify 配置 API-based Extension 时,Dify 将会发送一个请求至 API Endpoint,以检验 API 的可用性。 + +当 API Endpoint 接收到 `point=ping` 时,接口应返回 `result=pong`,具体如下: + +### Header + +``` +Content-Type: application/json +Authorization: Bearer {api_key} +``` + +### Request Body + +``` +{ + "point": "ping" +} +``` + +### API 期望返回 +``` +{ + "result": "pong" +} +``` + +## 范例 + +此处以外部数据工具为例,场景为根据地区获取外部天气信息作为上下文。 + +### API 范例 + +``` +POST https://fake-domain.com/api/dify/receive +``` + +**Header** + +``` +Content-Type: application/json +Authorization: Bearer 123456 +``` + +**Request Body** + +``` +{ + "point": "app.external_data_tool.query", + "params": { + "app_id": "61248ab4-1125-45be-ae32-0ce91334d021", + "tool_variable": "weather_retrieve", + "inputs": { + "location": "London" + }, + "query": "How's the weather today?" + } +} +``` + +**API 返回** + +``` +{ + "result": "City: London\nTemperature: 10°C\nRealFeel®: 8°C\nAir Quality: Poor\nWind Direction: ENE\nWind Speed: 8 km/h\nWind Gusts: 14 km/h\nPrecipitation: Light rain" +} +``` + +### 代码范例 + +代码基于 Python FastAPI 框架。 + +1. 安装依赖 + + ``` + pip install fastapi[all] uvicorn + ``` +2. 按照接口规范编写代码 + + ``` + from fastapi import FastAPI, Body, HTTPException, Header + from pydantic import BaseModel + + app = FastAPI() + + + class InputData(BaseModel): + point: str + params: dict = {} + + + @app.post("/api/dify/receive") + async def dify_receive(data: InputData = Body(...), authorization: str = Header(None)): + """ + Receive API query data from Dify. + """ + expected_api_key = "123456" # TODO Your API key of this API + auth_scheme, _, api_key = authorization.partition(' ') + + if auth_scheme.lower() != "bearer" or api_key != expected_api_key: + raise HTTPException(status_code=401, detail="Unauthorized") + + point = data.point + + # for debug + print(f"point: {point}") + + if point == "ping": + return { + "result": "pong" + } + if point == "app.external_data_tool.query": + return handle_app_external_data_tool_query(params=data.params) + # elif point == "{point name}": + # TODO other point implementation here + + raise HTTPException(status_code=400, detail="Not implemented") + + + def handle_app_external_data_tool_query(params: dict): + app_id = params.get("app_id") + tool_variable = params.get("tool_variable") + inputs = params.get("inputs") + query = params.get("query") + + # for debug + print(f"app_id: {app_id}") + print(f"tool_variable: {tool_variable}") + print(f"inputs: {inputs}") + print(f"query: {query}") + + # TODO your external data tool query implementation here, + # return must be a dict with key "result", and the value is the query result + if inputs.get("location") == "London": + return { + "result": "City: London\nTemperature: 10°C\nRealFeel®: 8°C\nAir Quality: Poor\nWind Direction: ENE\nWind " + "Speed: 8 km/h\nWind Gusts: 14 km/h\nPrecipitation: Light rain" + } + else: + return {"result": "Unknown city"} + ``` +3. 启动 API 服务,默认端口为 8000,API 完整地址为:`http://127.0.0.1:8000/api/dify/receive`,配置的 API Key 为 `123456`。 + + ``` + uvicorn main:app --reload --host 0.0.0.0 + ``` +4. 在 Dify 配置该 API。 + +5. 在 App 中选择该 API 扩展。 + +App 调试时,Dify 将请求配置的 API,并发送以下内容(范例): + +``` +{ + "point": "app.external_data_tool.query", + "params": { + "app_id": "61248ab4-1125-45be-ae32-0ce91334d021", + "tool_variable": "weather_retrieve", + "inputs": { + "location": "London" + }, + "query": "How's the weather today?" + } +} +``` + +API 返回为: + +``` +{ + "result": "City: London\nTemperature: 10°C\nRealFeel®: 8°C\nAir Quality: Poor\nWind Direction: ENE\nWind Speed: 8 km/h\nWind Gusts: 14 km/h\nPrecipitation: Light rain" +} +``` + +## 本地调试 + +由于 Dify 云端版无法访问内网 API 服务,为了方便本地调试 API 服务,可以使用 [Ngrok](https://ngrok.com) 将 API 服务的端点暴露到公网,实现云端调试本地代码。操作步骤: + +1. 进入 [https://ngrok.com](https://ngrok.com) 官网,注册并下载 Ngrok 文件。 + + ![Download](https://assets-docs.dify.ai/dify-enterprise-mintlify/zh_CN/guides/extension/api-based-extension/c44d6cc5425508daac8d31bc4af113df.png) + +2. 下载完成后,进入下载目录,根据下方说明解压压缩包,并执行说明中的初始化脚本。 + ```Shell + unzip /path/to/ngrok.zip + ./ngrok config add-authtoken 你的Token + ``` +3. 查看本地 API 服务的端口: + + ![查看端口](https://assets-docs.dify.ai/dify-enterprise-mintlify/zh_CN/guides/extension/api-based-extension/7ac8ee0f0955f36255e0261b36499db7.png) + + 并运行以下命令启动: + + ```Shell + ./ngrok http 端口号 + ``` + + 启动成功的样例如下: + + ![Ngrok 启动](https://assets-docs.dify.ai/dify-enterprise-mintlify/zh_CN/guides/extension/api-based-extension/2b4adbe0bb1ff203da521ea6eea401f8.png) + +4. 我们找到 Forwarding 中,如上图:`https://177e-159-223-41-52.ngrok-free.app`(此为示例域名,请替换为自己的)即为公网域名。 + +按照上述的范例,我们把本地已经启动的服务端点暴露出去,将代码范例接口:`http://127.0.0.1:8000/api/dify/receive` 替换为 `https://177e-159-223-41-52.ngrok-free.app/api/dify/receive` + +此 API 端点即可公网访问。至此,我们即可在 Dify 配置该 API 端点进行本地调试代码,配置步骤请参考 [外部数据工具](/zh/use-dify/workspace/api-extension/external-data-tool-api-extension)。 + +## 使用 Cloudflare Workers 部署 API 扩展 + +我们推荐你使用 Cloudflare Workers 来部署你的 API 扩展,因为 Cloudflare Workers 可以方便的提供一个公网地址,而且可以免费使用。 + +详细说明请参考 [使用 Cloudflare Workers 部署 API 扩展](/zh/use-dify/workspace/api-extension/cloudflare-worker)。 \ No newline at end of file diff --git a/zh/use-dify/workspace/api-extension/cloudflare-worker.mdx b/zh/use-dify/workspace/api-extension/cloudflare-worker.mdx new file mode 100644 index 00000000..74ffc0a3 --- /dev/null +++ b/zh/use-dify/workspace/api-extension/cloudflare-worker.mdx @@ -0,0 +1,131 @@ +--- +title: 使用 Cloudflare Workers 部署 API 扩展 +--- + +## 步骤 + +因为 Dify API 扩展需要使用可以访问的公网地址作为 API Endpoint,所以需要将 API 扩展部署到一个公网地址上。 + +这里使用 Cloudflare Workers 来部署 API 扩展。 + +Clone [Example GitHub Repository](https://github.com/crazywoola/dify-extension-workers),这个仓库包含了一个简单的 API 扩展,可以在这个基础上进行修改。 + +```bash +git clone https://github.com/crazywoola/dify-extension-workers.git +cp wrangler.toml.example wrangler.toml +``` + +打开 `wrangler.toml` 文件,修改 `name` 和 `compatibility_date` 为你的应用名称和兼容日期。 + +这里我们需要注意的配置就是 `vars` 里面的 `TOKEN`,在 Dify 添加 API 扩展的时候,我们需要填写这个 Token。出于安全考虑,我们建议你使用一个随机的字符串作为 Token,你不应该在源代码中直接写入 Token,而是使用环境变量的方式来传递 Token。所以请不要把 wrangler.toml 提交到你的代码仓库中。 + +```toml +name = "dify-extension-example" +compatibility_date = "2023-01-01" + +[vars] +TOKEN = "bananaiscool" +``` + +这个 API 扩展会返回一个随机的 Breaking Bad 名言。你可以在 `src/index.ts` 中修改这个 API 扩展的逻辑。这个例子展示了与第三方 API 进行交互的方式。 + +```typescript +// ⬇️ implement your logic here ⬇️ +// point === "app.external_data_tool.query" +// https://api.breakingbadquotes.xyz/v1/quotes +const count = params?.inputs?.count ?? 1; +const url = `https://api.breakingbadquotes.xyz/v1/quotes/${count}`; +const result = await fetch(url).then(res => res.text()) +// ⬆️ implement your logic here ⬆️ +``` + +这个仓库简化了除了业务逻辑之外所有的配置,你可以直接使用 `npm` 命令来部署你的 API 扩展。 + +```bash +npm install +npm run deploy +``` + +部署成功之后,你会得到一个公网地址,你可以在 Dify 中添加这个地址作为 API Endpoint。请注意不要遗漏 `endpoint` 这个路径,此路径的具体定义在 `src/index.ts` 中可以查看。 + +![在 Dify 中添加 API Endpoint](https://assets-docs.dify.ai/dify-enterprise-mintlify/zh_CN/guides/extension/api-based-extension/9433a486a441713ade6270e9dc6c0544.png) + +另外,也可以直接使用 `npm run dev` 命令来部署到本地进行测试。 + +```bash +npm install +npm run dev +``` + +相关输出: + +```bash +$ npm run dev +> dev +> wrangler dev src/index.ts + + ⛅️ wrangler 3.99.0 +------------------- + +Your worker has access to the following bindings: +- Vars: + - TOKEN: "ban****ool" +⎔ Starting local server... +[wrangler:inf] Ready on http://localhost:58445 +``` + +之后就可以使用 Postman 等工具进行本地接口调试。 + +## 关于 Bearer Auth + +```typescript +import { bearerAuth } from "hono/bearer-auth"; + +(c, next) => { + const auth = bearerAuth({ token: c.env.TOKEN }); + return auth(c, next); +}, +``` + +我们的 Bearer 校验逻辑在如上代码中,我们使用了 `hono/bearer-auth` 这个包来实现 Bearer 校验。你可以在 `src/index.ts` 中使用 `c.env.TOKEN` 来获取 Token。 + +## 关于参数验证 + +```typescript +import { z } from "zod"; +import { zValidator } from "@hono/zod-validator"; + +const schema = z.object({ + point: z.union([ + z.literal("ping"), + z.literal("app.external_data_tool.query"), + ]), // Restricts 'point' to two specific values + params: z + .object({ + app_id: z.string().optional(), + tool_variable: z.string().optional(), + inputs: z.record(z.any()).optional(), + query: z.any().optional(), // string or null + }) + .optional(), +}); + +``` + +我们这里使用了 `zod` 来定义参数的类型,你可以在 `src/index.ts` 中使用 `zValidator` 来校验参数。通过 `const { point, params } = c.req.valid("json");` 来获取校验后的参数。 + +我们这里的 point 只有两个值,所以我们使用了 `z.union` 来定义。 params 是一个可选的参数,所以我们使用了 `z.optional` 来定义。 其中会有一个 `inputs` 的参数,这个参数是一个 `Record` 类型,这个类型表示一个 key 为 string,value 为 any 的对象。这个类型可以表示任意的对象,你可以在 `src/index.ts` 中使用 `params?.inputs?.count` 来获取 `count` 参数。 + +## 获取 Cloudflare Workers 的日志 + +```bash +wrangler tail +``` + +--- + +**参考内容**: + +* [Cloudflare Workers](https://workers.cloudflare.com/) +* [Cloudflare Workers CLI](https://developers.cloudflare.com/workers/cli-wrangler/install-update) +* [Example GitHub Repository](https://github.com/crazywoola/dify-extension-workers) \ No newline at end of file diff --git a/zh/use-dify/workspace/api-extension/external-data-tool-api-extension.mdx b/zh/use-dify/workspace/api-extension/external-data-tool-api-extension.mdx new file mode 100644 index 00000000..2fcb5361 --- /dev/null +++ b/zh/use-dify/workspace/api-extension/external-data-tool-api-extension.mdx @@ -0,0 +1,63 @@ +--- +title: 外部数据工具 +--- + +在创建 AI 应用时,开发者可以通过 [API 扩展](/zh/use-dify/workspace/api-extension/api-extension) 的方式实现使用外部工具获取额外数据组装至 Prompt 中作为 LLM 额外信息。 + +## 扩展点 + +`app.external_data_tool.query`:应用外部数据工具查询扩展点。 + +该扩展点将终端用户传入的应用变量内容和对话输入内容(对话型应用固定参数)作为参数,传给 API。 + +开发者需要实现对应工具的查询逻辑,并返回字符串类型的查询结果。 + +### Request Body + +``` +{ + "point": "app.external_data_tool.query", // 扩展点类型,此处固定为 app.external_data_tool.query + "params": { + "app_id": string, // 应用 ID + "tool_variable": string, // 外部数据工具变量名称,表示对应变量工具调用来源 + "inputs": { // 终端用户传入变量值,key 为变量名,value 为变量值 + "var_1": "value_1", + "var_2": "value_2", + ... + }, + "query": string | null // 终端用户当前对话输入内容,对话型应用固定参数。 + } +} +``` + +**Example**: + + ``` + { + "point": "app.external_data_tool.query", + "params": { + "app_id": "61248ab4-1125-45be-ae32-0ce91334d021", + "tool_variable": "weather_retrieve", + "inputs": { + "location": "London" + }, + "query": "How's the weather today?" + } + } + ``` + +### API 返回 + +``` +{ + "result": string +} +``` + +**Example**: + + ``` + { + "result": "City: London\nTemperature: 10°C\nRealFeel®: 8°C\nAir Quality: Poor\nWind Direction: ENE\nWind Speed: 8 km/h\nWind Gusts: 14 km/h\nPrecipitation: Light rain" + } + ``` diff --git a/zh/use-dify/workspace/api-extension/moderation-api-extension.mdx b/zh/use-dify/workspace/api-extension/moderation-api-extension.mdx new file mode 100644 index 00000000..f6f3278a --- /dev/null +++ b/zh/use-dify/workspace/api-extension/moderation-api-extension.mdx @@ -0,0 +1,254 @@ +--- +title: 敏感内容审查 +--- + +该模块用于审查应用中终端用户输入的内容和 LLM 输出的内容,分为两个扩展点类型。 + +## 扩展点 + +* `app.moderation.input`:终端用户输入的内容审查扩展点 + * 用于审查终端用户传入的变量内容以及对话型应用中对话的输入内容。 +* `app.moderation.output`:LLM 输出的内容审查扩展点 + * 用于审查 LLM 输出的内容, + * 当 LLM 输出为流式时,输出的内容将分 100 字为一个分段进行请求 API,尽可能避免输出内容较长时,审查不及时的问题。 + +### app.moderation.input 扩展点 + +当在 Chatflow、Agent、聊天助手等应用下开启**内容审查>审查输入内容**时,Dify 会给相应的 API 扩展发送下列 HTTP POST 请求: + +#### Request Body + +``` +{ + "point": "app.moderation.input", // 扩展点类型,此处固定为 app.moderation.input + "params": { + "app_id": string, // 应用 ID + "inputs": { // 终端用户传入变量值,key 为变量名,value 为变量值 + "var_1": "value_1", + "var_2": "value_2", + ... + }, + "query": string | null // 终端用户当前对话输入内容,对话型应用固定参数。 + } +} +``` + +**Example**: + + ``` + { + "point": "app.moderation.input", + "params": { + "app_id": "61248ab4-1125-45be-ae32-0ce91334d021", + "inputs": { + "var_1": "I will kill you.", + "var_2": "I will fuck you." + }, + "query": "Happy everydays." + } + } + ``` + +#### API 返回规范 + +``` +{ + "flagged": bool, // 是否违反校验规则 + "action": string, // 动作,direct_output 直接输出预设回答; overridden 覆写传入变量值 + "preset_response": string, // 预设回答(仅当 action=direct_output 返回) + "inputs": { // 终端用户传入变量值,key 为变量名,value 为变量值(仅当 action=overridden 返回) + "var_1": "value_1", + "var_2": "value_2", + ... + }, + "query": string | null // 覆写的终端用户当前对话输入内容,对话型应用固定参数。(仅当 action=overridden 返回) +} +``` + +**Example**: + + * `action=direct_output` + ``` + { + "flagged": true, + "action": "direct_output", + "preset_response": "Your content violates our usage policy." + } + ``` + * `action=overridden` + ``` + { + "flagged": true, + "action": "overridden", + "inputs": { + "var_1": "I will *** you.", + "var_2": "I will *** you." + }, + "query": "Happy everydays." + } + ``` + +### app.moderation.output 扩展点 + +当在 Chatflow、Agent、聊天助手等应用下开启**内容审查>审查输出内容**时,Dify 会给相应的 API 扩展发送下列 HTTP POST 请求: + +#### Request Body + +``` +{ + "point": "app.moderation.output", // 扩展点类型,此处固定为 app.moderation.output + "params": { + "app_id": string, // 应用 ID + "text": string // LLM 回答内容。当 LLM 输出为流式时,此处为 100 字为一个分段的内容。 + } +} +``` + +**Example**: + ``` + { + "point": "app.moderation.output", + "params": { + "app_id": "61248ab4-1125-45be-ae32-0ce91334d021", + "text": "I will kill you." + } + } + ``` + +#### API 返回 + +``` +{ + "flagged": bool, // 是否违反校验规则 + "action": string, // 动作,direct_output 直接输出预设回答; overridden 覆写传入变量值 + "preset_response": string, // 预设回答(仅当 action=direct_output 返回) + "text": string // 覆写的 LLM 回答内容。(仅当 action=overridden 返回) +} +``` + +**Example**: + + * `action=direct_output` + ``` + { + "flagged": true, + "action": "direct_output", + "preset_response": "Your content violates our usage policy." + } + ``` + * `action=overridden` + ``` + { + "flagged": true, + "action": "overridden", + "text": "I will *** you." + } + ``` + +## 代码示例 +下面展示一段可部署在 Cloudflare 的 `src/index.ts` 代码。(Cloudflare 完整的使用方法参见 [此文档](/zh/use-dify/workspace/api-extension/cloudflare-worker)) + +代码工作原理是进行关键词匹配,实现对 Input (用户输入的内容)以及输出(大模型返回的内容)进行过滤。用户可以按照需求自行修改匹配逻辑。 +``` +import { Hono } from "hono"; +import { bearerAuth } from "hono/bearer-auth"; +import { z } from "zod"; +import { zValidator } from "@hono/zod-validator"; +import { generateSchema } from '@anatine/zod-openapi'; + +type Bindings = { + TOKEN: string; +}; + +const app = new Hono<{ Bindings: Bindings }>(); + +// API 格式校验 ⬇️ +const schema = z.object({ + point: z.union([ + z.literal("ping"), + z.literal("app.external_data_tool.query"), + z.literal("app.moderation.input"), + z.literal("app.moderation.output"), + ]), // Restricts 'point' to two specific values + params: z + .object({ + app_id: z.string().optional(), + tool_variable: z.string().optional(), + inputs: z.record(z.any()).optional(), + query: z.any(), + text: z.any() + }) + .optional(), +}); + + +// Generate OpenAPI schema +app.get("/", (c) => { + return c.json(generateSchema(schema)); +}); + +app.post( + "/", + (c, next) => { + const auth = bearerAuth({ token: c.env.TOKEN }); + return auth(c, next); + }, + zValidator("json", schema), + async (c) => { + const { point, params } = c.req.valid("json"); + if (point === "ping") { + return c.json({ + result: "pong", + }); + } + // ⬇️ impliment your logic here ⬇️ + // point === "app.external_data_tool.query" + else if (point === "app.moderation.input"){ + // 输入检查 ⬇️ + const inputkeywords = ["输入过滤测试1", "输入过滤测试2", "输入过滤测试3"]; + + if (inputkeywords.some(keyword => params.query.includes(keyword))) + { + return c.json({ + "flagged": true, + "action": "direct_output", + "preset_response": "输入存在违法内容,请换个问题再试!" + }); + } else { + return c.json({ + "flagged": false, + "action": "direct_output", + "preset_response": "输入无异常" + }); + } + // 输入检查完毕 + } + + else { + // 输出检查 ⬇️ + const outputkeywords = ["输出过滤测试1", "输出过滤测试2", "输出过滤测试3"]; + + if (outputkeywords.some(keyword => params.text.includes(keyword))) + { + return c.json({ + "flagged": true, + "action": "direct_output", + "preset_response": "输出存在敏感内容,已被系统过滤,请换个问题再问!" + }); + } + + else { + return c.json({ + "flagged": false, + "action": "direct_output", + "preset_response": "输出无异常" + }); + }; + } + // 输出检查完毕 + } +); + +export default app; + +``` \ No newline at end of file diff --git a/zh/use-dify/workspace/readme.mdx b/zh/use-dify/workspace/readme.mdx index 068aed78..022c4de8 100644 --- a/zh/use-dify/workspace/readme.mdx +++ b/zh/use-dify/workspace/readme.mdx @@ -1,6 +1,6 @@ --- title: "概览" -description: "Workspaces are the foundational organizational unit in Dify—everything your team builds, configures, and manages exists within a workspace" +description: "工作空间是 Dify 中的基础组织单位——你的团队所构建、配置和管理的一切都存在于工作空间中" icon: "building" ---