从0到1:如何在dify中自定义工具并进行使用

前言

本章介绍的是如何在Dify中使用自定义工具,前提是已经在本地部署了Dify,还没有部署的可以参考上篇博客。其实Dify提供的服务已经很全面了,大多数情况下是不需要自定义工具的,但是难免有少数已有工具不能满足要求的时候,这里结合自己的实际例子记录一下。

本文以获取给定url对应的网页源码为例(但其实这个功能Dify已经提供了,这里着重讲一下搭建过程)
打开Dify的自定义工具页面发现有两个必填项:名称和Schema。名称随便填,重点就是这个Schema该如何写

1、本地搭建http服务

1.1 搭建SpringBoot项目

我们使用IDEA新建一个SpringBoot项目,工程目录如下:

SendHttpRequest.java

package com.example.difyurl.controller;

import com.example.difyurl.service.GetHtmlContentService;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.text.SimpleDateFormat;
import java.util.Date;

@RestController
public class SendHttpRequest {
    @Autowired
    private GetHtmlContentService getHtmlContentService;

    @PostMapping("/send")
    public String sendGet(@RequestHeader(value = "Authorization", required = true) String authorization, @RequestParam(value = "url") String url)
    {
        try {
            if (!authorization.equals("Bearer nanfeng")) {
                return "authorization 错误";
            }
            if (Strings.isBlank(url) || Strings.isEmpty(url)) {
                return "url 格式错误";
            }
            return getHtmlContentService.getHtmlContent(url);
        } catch (Exception e) {
            return "系统内部错误";
        }
    }
}

GetHtmlContentService.java

package com.example.difyurl.service;

public interface GetHtmlContentService {

    String getHtmlContent(String url);
}

GetHtmlContentServiceImpl.java

package com.example.difyurl.service.impl;

import com.example.difyurl.service.GetHtmlContentService;
import org.springframework.stereotype.Service;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;

@Service
public class GetHtmlContentServiceImpl implements GetHtmlContentService {
    @Override
    public String getHtmlContent(String url) {
        URL url1 = null;
        try {
            url1 = new URL(url);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }

        StringBuffer contentBuffer = new StringBuffer();

        int responseCode = -1;
        HttpURLConnection con = null;
        try {
            con = (HttpURLConnection) url1.openConnection();
            /*
             * 此处的urlConnection对象实际上是根据URL的
             *  请求协议(此处是http)生成的URLConnection类
             *  的子类HttpURLConnection,故此处最好将其转化
             *  为HttpURLConnection类型的对象,以便用到
             *  HttpURLConnection更多的API
             * */
            con.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36");
            con.setConnectTimeout(60000);
            con.setReadTimeout(60000);
            // 获得网页返回信息码
            responseCode = con.getResponseCode();
            if (responseCode == -1) {
                System.out.println(url.toString() + " : connection is failure...");
                con.disconnect();
                return null;
            }
            if (responseCode >= 400) // 请求失败
            {
                System.out.println("请求失败:get response code: " + responseCode);
                con.disconnect();
                return null;
            }

            InputStream inStr = con.getInputStream();
            InputStreamReader istreamReader = new InputStreamReader(inStr, "utf-8");
            BufferedReader buffStr = new BufferedReader(istreamReader);

            String str = null;
            while ((str = buffStr.readLine()) != null)
                contentBuffer.append(str);
            inStr.close();
        } catch (IOException e) {
            e.printStackTrace();
            contentBuffer = null;
            System.out.println("error: " + url.toString());
        } finally {
            con.disconnect();
        }
        return contentBuffer.toString();
    }
}

可以看到项目中只有一个接口,而这个接口就是实现接收url并获取对应网页源码功能的。接下来我们启动服务,并测试接口的功能。

1.2 测试接口

我们使用Postman来发送和接收请求,服务运行在本地,我设置的端口为8081,所以地址为http://localhost:8081/send,根据接口的sendGet(@RequestHeader(value = “Authorization”, required = true) String authorization, @RequestParam(value = “url”) String url)参数可知,除了要给出url之外,还要在请求头中设置Authorization参数(之所以要设置Authorization是后面在Dify中可以设置鉴权)


点击send发送请求可以看到返回的结果


如果想检验返回的结果是否正确,可以自行进行对比

2、生成OpenAPI schema

如果上一步能测试成功,我们进一步生成该接口的curl命令。点击右上角的图标就会生成右侧的命令

接下来需要使用大模型将我们的curl命令转为OpenAPI schema,这里以ChatGpt为例,提示词如下:

请把curl请求命令转成openapi 3.1.0 版本的json schema,不需要包含response信息
<curl>
curl --location 'http://localhost:8081/send' \
--header 'Authorization: Bearer nanfeng' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'url=https://blog.csdn.net/shi_jiaye/article/details/119175407'
</curl>
json schema请参照下面的例子
<json-schema>
{
     "openapi": "3.1.0",
     "info": {
       "title": "Get weather data",
       "description": "Retrieves current weather data for a location.",
       "version": "v1.0.0"
     },
     "servers": [
       {
         "url": ""
       }
     ],
     "paths": {},
     "components": {
       "schemas": {}
     }
   }
</json-schema>

生成结果如下

{
  "openapi": "3.1.0",
  "info": {
    "title": "Send URL",
    "description": "Sends a URL for processing",
    "version": "v1.0.0"
  },
  "servers": [
    {
      "url": "http://localhost:8081"
    }
  ],
  "paths": {
    "/send": {
      "post": {
        "description": "Send a URL for processing",
        "operationId": "SendUrl",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "description": "The URL to be sent for processing"
                  }
                },
                "required": ["url"]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "URL successfully processed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "example": "success"
                    },
                    "message": {
                      "type": "string",
                      "example": "URL processed successfully"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad Request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "500": {
            "description": "Internal Server Error"
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT"
      }
    }
  }
}

但是这里有个坑,我们可以看到上面给出的url地址是http://localhost:8081,我们确实是在本地运行的,但是Dify服务是部署在Docker上的,所以本地是Docker的宿主机,所以要将url改为http://host.docker.internal:8081才能访问本地的服务

3、在Dify中自定义工具

3.1 创建自定义工具

到这里我们就获取到了心心念念的Schema,我们将生成的代码粘贴到对应的区域,“可用工具”这一栏就会识别到服务,还记得最开始我们设置了Authorization这个参数,就是用来鉴权用的

保存之后我们点击“测试”,输入url


可以看到返回了正确的结果,并且查看此时本地的服务日志,接口也是被调用了一次
如果发现返回的是“Reached maximum retries (3) for URL http://localhost:8081/send”,说明前面没将url改为http://host.docker.internal:8081,导致访问不到服务

3.2 使用自定义工具

我们创建一个工作流,并引入自定义的工具

紧接着使用大模型,给的提示词是“将{{#1728877649433.text#}}的内容提取出来,并进行总结概括,100字左右”,运行工作流,可以看到输出了结果

至此,自定义工具并使用完成,定义其他工具也是同理,希望对你们使用有帮助。

作者:南南的sky

物联沃分享整理
物联沃-IOTWORD物联网 » 从0到1:如何在dify中自定义工具并进行使用

发表回复