Link

Dify 相关

目录

  1. Dify 相关
  2. Dify API 扩展功能使用
    1. 说明
    2. 外部查询(工具)Hello World
      1. 通过 postman 测试通信
      2. 将 API 插件添加为工具
      3. 在 Agent 中调用工具进行测试
    3. PANW AI Security API 工具
    4. 内容审查 API
      1. Hello World
        1. 具体的参数说明
      2. 调用 PANW 的审查模型

Dify API 扩展功能使用

说明

Dify API 扩展有多种使用方法:

  • 进行内容审查:支持在 Agent、Chatbot、chatflow>功能中开启(详见后面的内容审查 API);
  • 进行外部查询:在 Agent/Chatbot 中以”基于API的变量”进行调用,比如拿来查天气等;
  • 封装成工具进行外部查询:编写 OpenAPI 规范,将 API 变成 Dify 自定义工具。在 Agent、Chatbot、chatflow 等中进行调用。

三者架构区别如下:

image-20241224195241451

本文主要讲述 3 和 1 的实现,2 的用途比较有限本文就不做阐述了。

外部查询(工具)Hello World

参考文档:https://docs.dify.ai/zh-hans/guides/extension/api-based-extension

https://docs.dify.ai/zh-hans/guides/extension/api-based-extension/cloudflare-workers

本文介绍下官方文档中的示例,介绍下如何自定义里面的东西(官方的很清楚了,但是距离给小白用还缺点东西)。

大致的流程:

  • 前提条件:安装 npm、wrangler(Cloudflare 的 CLI 插件)
  • 下载官方的适合于 Cloudflare 的 JavaScript 示例
  • 修改项目文件
  • 将项目部署到 Cloudflare

安装 weangler:

npm install wrangler --save-dev
npm WARN deprecated [email protected]: Please use @jridgewell/sourcemap-codec instead
npm WARN deprecated [email protected]: This package has been deprecated and is no longer maintained. Please use @rollup/plugin-inject.

added 68 packages, and audited 71 packages in 48s

11 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

项目文件下载:

git clone https://github.com/crazywoola/dify-extension-workers.git
cd dify-extension-workers
cp wrangler.toml.example wrangler.toml

修改项目的信息:

# 修改 wrangler.toml 中的 name(这个 name 未来会和在 Cloudflare 上部署的 workers 一致)
name = "dify-extension-example"
compatibility_date = "2024-12-23"

# 修改 API access Token,未来调用部署出来的服务时会通过此 Token 进行认证。
[vars]
TOKEN = "7d5f1a2b1c"

# [[kv_namespaces]]
# binding = "MY_KV_NAMESPACE"
# id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

# [[r2_buckets]]
# binding = "MY_BUCKET"
# bucket_name = "my-bucket"

# [[d1_databases]]
# binding = "DB"
# database_name = "my-database"
# database_id = ""

其他暂时保持不变。

(Option1)运行下列命令进行本地调试:

cd dify-extension-workers
npm install
# 在首次运行下列命令时,会自动打开系统默认浏览器进行 Cloudflare 认证,认证完成后会显示 Successfully logged in。然后系统会自动部署 Workers
npm run dev

(Option2)运行下列命令进行部署到 Cloudflare:

cd dify-extension-workers
npm install
# 在首次运行下列命令时,会自动打开系统默认浏览器进行 Cloudflare 认证,认证完成后会显示 Successfully logged in。然后系统会自动部署 Workers
npm run deploy

相关的日志:

[User@dify-extension-workers]$ npm install
npm WARN deprecated [email protected]: This package has been deprecated and is no longer maintained. Please use @rollup/plugin-inject.
npm WARN deprecated [email protected]: Please use @jridgewell/sourcemap-codec instead

added 75 packages, and audited 76 packages in 4s

11 packages are looking for funding
  run `npm fund` for details

1 moderate severity vulnerability

To address all issues (including breaking changes), run:
  npm audit fix --force

Run `npm audit` for details.
[User@dify-extension-workers]$ npm run deploy

> deploy
> wrangler deploy --minify src/index.ts

 ⛅️ wrangler 3.99.0
-------------------

Attempting to login via OAuth...
Opening a link in your default browser: https://dash.cloudflare.com/oauth2/auth?response_type=code&client_id=54d11594-84e4-41aa-b438-e81b8fa78ee7&redirect_uri=http%3A%2F%2FlocalXXXtPnLQIckDfNt9rw258&code_challenge_method=S256
Successfully logged in.

Total Upload: 168.82 KiB / gzip: 43.49 KiB
Worker Startup Time: 6 ms
Your worker has access to the following bindings:
- Vars:
  - TOKEN: "bananaiscool"
Uploaded dify-extension-example (3.15 sec)
Deployed dify-extension-example triggers (1.28 sec)
  https://dify-extension-example.xxx.workers.dev
Current Version ID: 6158d709-1801-4176-a8ee-d07f1cae9eb8

部署完成后在 Cloudflare 侧的部署:

image-20241223202806361

通过 postman 测试通信

可以通过 postman 进行请求,具体参数可以见下面的 curl 命令:

image-20241223203001750

curl --location 'https://dify-extension-example.xxx.workers.dev/endpoint' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer bananaiscool' \
--data '{
    "point": "app.external_data_tool.query",
    "params": {
        "inputs": {
            "count": "2"
        }
    }
}'

上面的 point 是必须的,如果值为 ping,则最终返回结果是会 pong(源代码中 src/index.ts 中定义的)

image-20241223203206360

params 用于定义其他变量,比如默认的示例中,会读取 params.inputs.count,这个值会在脚本 call 其他 API 时被调用:


    // ⬇️ impliment your logic here ⬇️
    // point === "app.external_data_tool.query"
    // https://api.breakingbadquotes.xyz/v1/quotes
    // 这里进行 count 赋值
    const count = params?.inputs?.count ?? 1;
   // 这里调用 count,count 表示要让下面的 url 返回几句话,默认值为 1
    const url = `https://api.breakingbadquotes.xyz/v1/quotes/${count}`;
    const result = await fetch(url).then(res => res.text())
    // ⬆️ impliment your logic here ⬆️

将 API 插件添加为工具

在将 API 添加为工具前,建议做下列修改:

  • 原始提供的代码中有两个变量,需要变成一个,默认 Agent 只能把参数传给第一个变量

修改后的代码如下:

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 }>();

const schema = z.object({
  point: z.any(), 
});

// Generate OpenAPI schema
app.get("/", (c) => {
  return c.json(generateSchema(schema));
});


app.post(
  "/endpoint",
  (c, next) => {
    const auth = bearerAuth({ token: c.env.TOKEN });
    return auth(c, next);
  },
  zValidator("json", schema),
  async (c) => {
    const { point } = c.req.valid("json");
    if (point === "ping") {
      return c.json({
        result: "pong",
      });
    }
    // ⬇️ impliment your logic here ⬇️
    // point === "app.external_data_tool.query"
    // https://api.breakingbadquotes.xyz/v1/quotes
    const count = point ?? 1;
    const url = `https://api.breakingbadquotes.xyz/v1/quotes/${count}`;
    const result = await fetch(url).then(res => res.text())
    // ⬆️ impliment your logic here ⬆️
    return c.json({
      result
    });
  }
);

export default app;

在工具>自定义中创建自定义工具:

image-20241223205157971

API 鉴权使用 Bearer,秘钥填写之前代码中写的秘钥(此处为默认的 bananaiscool)

image-20241223213428661

通过 AI 把之间的 Curl 请求转换成了 OpenAPI 兼容的 Schema,内容如下:

openapi: 3.0.0
info:
  title: Dify Extension API
  version: 1.0.0

servers:
  - url: https://dify-extension-example.xxx.workers.dev

paths:
  /endpoint:
    post:
      summary: Ping Endpoint
      description: Send a ping request to the endpoint
      operationId: pingEndpoint
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                point:
                  type: string
                  default: "ping"
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                # Define the response schema here

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer

之后系统自动会出现一个可用工具,点击测试:

image-20241223205312171

填写 point 和 params (貌似不支持直接输入 count,需要完整的 json)进行测试,最后可以看到运行成功:

image-20241223213411253

在 Agent 中调用工具进行测试

告诉工具需要提供的参数值为 2 (表示需要从 API 获得两句话,默认会把用户输入的关键词作为参数值)。

具体效果如下:

image-20241223213133846

相关日志:

当然,我很乐意为您写一首诗。为了获得一些灵感和素材,我需要先调用一个工具来获取一些信息。请允许我进行这个操作。

已使用
pingEndpoint
请求
{"pingEndpoint": {"point": "2"}}
响应
{"pingEndpoint": "{\"result\": \"[{\\\"quote\\\":\\\"You are not the guy. You're not capable of being the guy. I had a guy but now I don't. You are not the guy.\\\",\\\"author\\\":\\\"Mike Ehrmantraut\\\"},{\\\"quote\\\":\\\"They're minerals, Marie! Jesus!\\\",\\\"author\\\":\\\"Hank Schrader\\\"}]\"}"}

感谢您的请求。我已经获得了一些灵感来源,让我为您创作一首诗。这首诗的灵感来自于我获得的一些引言,但我会进行创造性的改编,以避免任何版权问题。下面是这首诗:

《不是那个人》

你不是那个人,
也无法成为那个人。
曾经我有一个人,
如今已经不在。

生命如同矿物,
坚硬而又易碎。
我们追寻真相,
却常常误解彼此。

在这个复杂的世界里,
我们都在寻找自己的位置。
有时我们是石头,
有时我们是宝石。

但请记住,亲爱的,
你永远是你自己。
不需要成为别人,
因为你已经很特别。

这首诗融合了人物角色和身份认同的主题,以及生活中的误解和自我发现。它借鉴了获得的引言中的一些元素,但进行了创造性的转化,形成了一首全新的、原创的诗作。希望这首诗能够给您带来一些启发和感悟。

PANW AI Security API 工具

基于官方的模板,写了个进行 PANW API 转换的脚本:

index.ts:


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 }>();

const schema = z.object({
  query: z.any(), 
});


// Generate OpenAPI schema
app.get("/", (c) => {
  return c.json(generateSchema(schema));
});

// 监听发送给 /Prompt 的请求,进行 Prompt 查询
app.post(
  "/prompt",
  (c, next) => {
    const auth = bearerAuth({ token: c.env.TOKEN });
    return auth(c, next);
  },
  zValidator("json", schema),
  async (c) => {
    const { query } = c.req.valid("json");
    if (query === "ping") {
      return c.json({
        result: "pong",
      });
    }
    // ⬇️ implemented logic here ⬇️
    // point === "app.external_data_tool.query"
    const url = 'https://service.api.aisecurity.paloaltonetworks.com/v1/scan/sync/request';
    
    const headers = {
      'Content-Type': 'application/json',
      'x-pan-token': "IMKmSxxkKc52o" // Assuming you store the token in environment variables
    };

    const body = {
      metadata: {
        ai_model: "DifyAI model",
        app_name: "Dify Secure app",
        app_user: "Dify-user-1"
      },
      contents: [
        {
          prompt: query
        }
      ],
      ai_profile: {
        profile_name: "matt"
      }
    };

    const result = await fetch(url, {
      method: 'POST',
      headers: headers,
      body: JSON.stringify(body)
    }).then(res => res.text());
    // ⬆️ implemented logic here ⬆️
    return c.json({
      result
    });
  },
  
);

// 监听发送给 /response 的请求,进行 Prompt 查询
app.post(
  "/response",
  (c, next) => {
    const auth = bearerAuth({ token: c.env.TOKEN });
    return auth(c, next);
  },
  zValidator("json", schema),
  async (c) => {
    const { query } = c.req.valid("json");
    if (query === "ping") {
      return c.json({
        result: "pong",
      });
    }
    // ⬇️ implemented logic here ⬇️
    // point === "app.external_data_tool.query"
    const url = 'https://service.api.aisecurity.paloaltonetworks.com/v1/scan/sync/request';
    
    const headers = {
      'Content-Type': 'application/json',
      'x-pan-token': "IMKxxOpbGnAypLGjSuZxkKc52o" // Assuming you store the token in environment variables
    };

    const body = {
      metadata: {
        ai_model: "DifyAI model",
        app_name: "Dify Secure app",
        app_user: "Dify-user-1"
      },
      contents: [
        {
          response: query
        }
      ],
      ai_profile: {
        profile_name: "matt"
      }
    };

    const result = await fetch(url, {
      method: 'POST',
      headers: headers,
      body: JSON.stringify(body)
    }).then(res => res.text());
    // ⬆️ implemented logic here ⬆️
    return c.json({
      result
    });
  },
  
);



//   const result = await fetch(url).then(res => res.text())

export default app;

OpenAPI 兼容的 Schema,内容如下:

openapi: 3.0.0
info:
  title: PANW AI Security API
  version: 1.0.0

servers:
  - url: https://panw-ai-security.xxx.workers.dev

paths:
  /prompt:
    post:
      summary: Check prompt
      description: Check prompt
      operationId: promptcheck
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                query:
                  type: string
                  default: "ping"
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                # Define the response schema here
  /response:
    post:
      summary: Check response
      description: Check response
      operationId: responsecheck
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                query:
                  type: string
                  default: "ping"
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                # Define the response schema here
components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer

工作流中调用:

image-20241224095500019

内容审查 API

Hello World

基于官方示例以及此文档,做了个简单的示例(静态返回 flagged,用于调试接口)。将默认的 endpoint 修改为 /

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 }>();

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 } = c.req.valid("json");
    if (point === "ping") {
      return c.json({
        result: "pong",
      });
    }
    // ⬇️ impliment your logic here ⬇️
    // point === "app.external_data_tool.query"
    // https://api.breakingbadquotes.xyz/v1/quotes
const result = {
  "id": "modr-AB8CjOTu2jiq12hp1AQPfeqFWaORR",
  "flagged": true,
  "model": "text-moderation-007",
  "action": "direct_output",
  "preset_response": "Your content violates our usage policy."
};

    // ⬆️ impliment your logic here ⬆️
    return c.json(result);
  }
);

export default app;

开启输入内容检查:

image-20241224175548828

开启后输入的效果(不管用户输入什么,都会输出静态内容 ):

image-20241224131121940

也可以通过 curl 进行 API 请求测试(下面的请求 body 是模拟 Dify 发出的 body 内容):

curl --location 'https://dify-extension-example.xxx.workers.dev/' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer bananaiscool' \
--data '{
    "point": "app.moderation.input",
    "params": {
        "app_id": "61248ab4-1125-45be-ae32-0ce91334d021",
        "inputs": {
            "var_1": "I will kill you."
        },
        "query": "Happy everydays."
    }
}'

具体的参数说明

point(Dify 默认都会带此参数):

用于让后面的代码根据不同输入执行不同的操作,只允许输入下列值(上面代码的 z.object 中进行了定义)

  • ping:用于添加 API 时验证
  • app.external_data_tool.query:当此 API 在 Dify 中被用来进行第三方数据获取时的标识(比如获取天气)
  • app.moderation.input:内容审查的输入检查标识
  • app.moderation.ouput:内容审查的输出检查标识

Params:

如果开启了输入内容审查,则 Params 用于传递会话的各种值,默认包含三个参数:

  • app_id:并不知道干啥的,可以忽略
  • inputs:应该是传递变量的,感觉通常用不上,只需要检测 query
  • query:传递用户的问题,内容审查应该是要审查这个才对

Params:如果开启了输出内容审查,默认包含两个参数:

  • app_id:并不知道干啥的,可以忽略
  • text:大模型返回的内容

调用 PANW 的审查模型

下面记录下如何借助 PANW 的安全接口,进行内容输入和输出审查。

代码工作流如下:

  • 监听 /路径, 根据 point 的值调用不同的函数
    • 如果是 ping,则返回 pang,用于接口测试
    • 如果是 app.moderation.input,则调用 PANW 的 Prompt API 进行检查
    • 如果是 app.moderation.output,则调用 PANW 的 Response API 进行检查
  • 判断检查结果,如果是 Block,则设置 flagged=true,然后静态返回某个值(这两个会封装到同一个 json body);如果是 allow,则返回 flagged=false。

最终成品示例:

// index.ts
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 }>();

/*
const schema = z.object({
  point: z.any(), 
});
*/

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 userquery= params.query;
    const url = 'https://service.api.aisecurity.paloaltonetworks.com/v1/scan/sync/request';
    const headers = {
      'Content-Type': 'application/json',
      'x-pan-token': "IMKmS6If7yRhxxLGjSuZxkKc52o" // Assuming you store the token in environment variables
    };
   
    const body = {
      metadata: {
        ai_model: "DifyAI model",
        app_name: "Dify Secure app",
        app_user: "Dify-user-1"
      },
      contents: [
        {
          prompt: userquery
        }
      ],
      ai_profile: {
        profile_name: "matt"
      }
    };

    const result = await fetch(url, {
      method: 'POST',
      headers: headers,
      body: JSON.stringify(body)
    }).then(res => res.text());
    /* debug PANW 接口返回 ⬇️
    return c.json({
      result
    });
    */
   let jsonResult = JSON.parse(result);
if ( jsonResult.action  === "block")
  {
    return c.json({
      "flagged": true,
      "action": "direct_output",
      "preset_response": "输入存在违法内容,请换个问题再试!"
    });
  }

else {
  return c.json({
    "flagged": false,
    "action": "direct_output",
    "preset_response": "输入无异常"
  });
};
    // 输入检查完毕 
    }
    
    else {
      // 输出检查 ⬇️
      const llmresponse= params.text;
      const url = 'https://service.api.aisecurity.paloaltonetworks.com/v1/scan/sync/request';
      const headers = {
        'Content-Type': 'application/json',
        'x-pan-token': "IMKmS6If7yRhAVxxGjSuZxkKc52o" // Assuming you store the token in environment variables
      };
     
      const body = {
        metadata: {
          ai_model: "DifyAI model",
          app_name: "Dify Secure app",
          app_user: "Dify-user-1"
        },
        contents: [
          {
            response: llmresponse
          }
        ],
        ai_profile: {
          profile_name: "matt"
        }
      };
  
      const result = await fetch(url, {
        method: 'POST',
        headers: headers,
        body: JSON.stringify(body)
      }).then(res => res.text());
      /* debug PANW 接口返回 ⬇️
      return c.json({
        result
      });
      */
     let jsonResult = JSON.parse(result);
  if ( jsonResult.action  === "block")
    {
      return c.json({
        "flagged": true,
        "action": "direct_output",
        "preset_response": "输出存在敏感内容,已被系统过滤,请换个问题再问!"
      });
    }
  
  else {
    return c.json({
      "flagged": false,
      "action": "direct_output",
      "preset_response": "输出无异常"
    });
  };
    }

  }
);


export default app;

测试:

image-20241224175548828

image-20241224175522757