基于OpenAI Function Call 结合Lucene实现本地化的知识搜索

简介: 这是一个OpenAI Function 接入的Demo,通过Lucene+OpenAI实现个人知识库,但是目前只有服务端,没有页面之类的东西,并且实现的非常简单,所以只能当做一个Demo使用。

https://github.com/zhouzhupianbei/OpenAI-ChatGPT-Function-Demo.git

image.png

介绍

这是一个OpenAI Function 接入的Demo,通过Lucene+OpenAI实现个人知识库,但是目前只有服务端,没有页面之类的东西,并且实现的非常简单,所以只能当做一个Demo使用。

感谢https://github.com/Grt1228/chatgpt-java项目,不过由于我Maven配置问题,拉不到1.0.14版本的包,我直接把这个项目源代码拷贝到了我的项目里。

这个项目更新的非常及时,提供了OpenAI最新API的接入,很好用的SDK。

启动服务

准备工作

1、 OpenAI Token,到OpenAI官网申请,这里不再赘述

2、 我在服务里写了一个查询天气的Demo,如果有兴趣的话,可以去申请一个天气查询KEY,申请地址:https://www.weatherapi.com/ 免费的,够用一阵了。

3、 修改配置信息

server:
  port: 9000 #端口

spring:
  servlet:
    multipart: # 文件上传限制
      max-file-size: 10MB 
      max-request-size: 10MB

openai:
  model: "gpt-3.5-turbo-0613" # 模型,暂时没什么用
  tokens: "" # OpenAI Token,多个使用逗号隔开,注意是英文逗号,不过多个token我没试过

base:
  proxy: # 代理信息,如果需要的话,国内肯定得用,不会了百度或者私信我
    url: 127.0.0.1
    port: 7890
  file:
    save-path: /out/files # 文件保存路径
functions:
  weather: # 天气的配置
    api:
      url: http://api.weatherapi.com/v1/current.json
      key:  # 天气查询的key
  lucene:
    index:
      path: # 索引保存路径 
    timeout: 10s
    fragment-size: 200
    default-page-size: 10

启动

正常的SpringBoot项目的启动方式。

修改

删除一个Function

比如不想用天气的函数,可以到com.lgf.warehouse.modules.ai.openai.functions.FunctionFactory中删除。

    @Autowired
    private WeatherApiService weatherApiService;
    @Autowired
    private LuceneSearchService luceneSearchService;
    /**
     * 注册服务到FunctionFactory
     */
    @PostConstruct()
    public void register() throws NoSuchMethodException {
        this.functionBeanMap = new HashMap<>();
        this.functions = new ArrayList<>();
        //注册服务
        this.registerFunction(this.weatherApiService);
        this.registerFunction(this.luceneSearchService);
        log.info(String.format("注册了%d个服务", this.functionBeanMap.size()));
    }

默认是注册了两个服务:天气服务(WeatherApiService)和Lucene搜索服务(LuceneSearchService),如果不想用天气服务,可以删除这个注册。

系统使用的是SpringBoot的自动注入,所以只要删除了这个注册,就不会再使用这个服务了。

添加一个Function

比如我想添加一个查询百度的功能,那么需要做如下几步:

1、 添加一个Service,继承AbsFunctionService,比如我这里添加一个BaiduSearchService

package com.lgf.warehouse.modules.ai.openai.functions.weather.service;

import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.lgf.warehouse.modules.ai.openai.functions.AbsFunctionService;
import com.lgf.warehouse.modules.ai.openai.functions.FunctionAnnotation;
import com.lgf.warehouse.modules.ai.openai.functions.weather.vo.Weather;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;

@Service
public class WeatherApiService  extends AbsFunctionService {

    @Value("${functions.weather.api.url}")
    private String api;
    @Value("${functions.weather.api.key}")
    private String key;


    @FunctionAnnotation(describe = "获取天气预报")
    public Weather getWeatherByCity(@FunctionAnnotation(describe = "城市名称,如果是中文需要转换为拼音,比如洛阳,需要转换为LuoYang",required = true) String city,
                              @FunctionAnnotation(describe = "要查询的是哪一天的天气,今天是1,明天是2,后天是3,最多查询3天的天气,如果超出3天,则返回1",required = true,enums = {"1","2","3"}) Integer days) {
        //调用天气接口
        Map<String,Object> params=new HashMap<>();
        params.put("key",this.key);
        params.put("q",city);
        params.put("days",days);
        String result= HttpUtil.get(this.api,params);
        if(JSONUtil.isTypeJSONObject(result)){
            JSONObject response=JSONUtil.parseObj(result);
//            {"location":{"name":"Luoyang","region":"Henan","country":"China","lat":34.68,"lon":112.45,"tz_id":"Asia/Shanghai","localtime_epoch":1687575565,"localtime":"2023-06-24 10:59"},"current":{"last_updated_epoch":1687574700,"last_updated":"2023-06-24 10:45","temp_c":30.9,"temp_f":87.6,"is_day":1,"condition":{"text":"Sunny","icon":"//cdn.weatherapi.com/weather/64x64/day/113.png","code":1000},"wind_mph":4.0,"wind_kph":6.5,"wind_degree":150,"wind_dir":"SSE","pressure_mb":1009.0,"pressure_in":29.8,"precip_mm":0.0,"precip_in":0.0,"humidity":35,"cloud":9,"feelslike_c":30.6,"feelslike_f":87.1,"vis_km":10.0,"vis_miles":6.0,"uv":8.0,"gust_mph":4.7,"gust_kph":7.6}}

            if(response.get("current")!=null){
                JSONObject location=response.getJSONObject("location");
                JSONObject current=response.getJSONObject("current");
                JSONObject condition=current.getJSONObject("condition");
                // 解析天气接口返回的数据
                Weather weather= new Weather();
                weather.setCity(location.getStr("name"));
                weather.setRegion(location.getStr("region"));
                weather.setCountry(location.getStr("country"));

                weather.setDays(days);

                weather.setCondition(condition.getStr("text"));
                weather.setConditionIcon(condition.getStr("icon"));

                weather.setTemp(current.getDouble("temp_c"));
                return weather;
            }
        }
        return null;
    }

    @Override
    public String getFunctionName() {
        return "WeatherApiService";
    }

    @Override
    public Class getCla() {
        return this.getClass();
    }
}

当然,我懒得再写一个百度的服务了,复制天气服务来讲解。

在AbsFunctionService中定义了getFunctionName()和getCal()的抽象方法,需要在子类中实现,返回函数的名称和实现类的Class对象。

在这个Service中编写函数的方法,方法需要使用@FunctionAnnotation注解,这个注解有以下几个属性:

  • describe:函数的描述,比如“获取天气预报”
  • required:是否必须,如果是必须的,那么在调用的时候,如果没有传递这个参数,那么会抛出异常
  • enums:如果是枚举类型,那么可以使用这个属性,比如天气预报的天数,只能是1、2、3,那么可以使用这个属性,如果传递的值不是这三个值,那么会抛出异常
@FunctionAnnotation(describe = "获取天气预报")
    public Weather getWeatherByCity(@FunctionAnnotation(describe = "城市名称,如果是中文需要转换为拼音,比如洛阳,需要转换为LuoYang",required = true) String city,
                              @FunctionAnnotation(describe = "要查询的是哪一天的天气,今天是1,明天是2,后天是3,最多查询3天的天气,如果超出3天,则返回1",required = true,enums = {"1","2","3"}) Integer days) {

注意:方法必须使用@FunctionAnnotation注解,否则不会被识别为函数。参数没有强制要求,但是如果不写注解的describe属性,那么在调用的时候,OpenAI也不知道参数含义。

2、 在FunctionFactory中注册这个Service

        this.registerFunction(this.weatherApiService);

其他功能

目前系统接入了Lucene,可以导入本地文件,然后进行全文搜索,将搜索结果给OpenAI进行处理总结后返回给用户。

具体的接口可以使用Swagger调用体验。

com.lgf.warehouse.modules.ai.openai.controller.LuceneSearchController
com.lgf.warehouse.modules.ai.openai.controller.LuceneImportController

这个文档不少内容是用Copilot写的,真不错啊(除了要花钱)

相关文章
|
6月前
|
数据采集 自然语言处理 Devops
ToolLearning Eval:CodeFuse发布首个中文Function Call的大语言模型评测基准!🚀
CodeFuse发布了首个面向ToolLearning领域的中文评测基准ToolLearning-Eval,以帮助开发者跟踪ToolLearning领域大模型的进展,并了解各个ToolLearning领域大模型的优势与不足。ToolLearning-Eval按照Function Call流程进行划分,包含工具选择、工具调用、工具执行结果总结这三个过程,方便通用模型可以对各个过程进行评测分析。
673 0
|
3月前
|
文字识别 Linux Swift
多图理解,更懂中文,支持function call的Phi-3.5来了!
微软继今年4月推出Phi-3系列小型语言模型后,又一鼓作气三连发布并开源其「小而美」系列 Phi-3.5模型!
|
5月前
|
物联网
【洛谷 P1464】Function 题解(递归+记忆化搜索)
该题目定义了一个递归函数$w(a,b,c)$,具有特定的终止条件和递归规则。当$a, b, c$任一值小于等于0或大于20时,函数有特殊返回值。否则,根据$a, b, c$的相对大小关系应用不同的递归计算。给定输入是一系列的三元组$(a, b, c)$,以$-1,-1,-1$结束。程序使用记忆化搜索优化递归调用,避免重复计算。样例输入输出展示了如何计算$w(1, 1, 1)$和$w(2, 2, 2)$。
45 0
|
6月前
|
存储 人工智能 前端开发
[译][AI OpenAI-doc] 文件搜索 Beta
文件搜索通过从其模型外部获取的知识增强了助手的功能,例如专有产品信息或用户提供的文档。通过向量存储库,您可以管理文件的解析、分块、嵌入和存储,以进行关键字和语义搜索。确保向量存储库准备就绪,以确保所有数据可搜索,并利用到期策略管理成本。
Fatal error: Call to undefined function openssl_pkey_get_private()
Fatal error: Call to undefined function openssl_pkey_get_private()
74 0
|
PHP Windows
windows下 Call to undefined function posix_getpid() in ……\Workerman\Worker.php 的解决方法
windows下 Call to undefined function posix_getpid() in ……\Workerman\Worker.php 的解决方法
184 0
windows下 Call to undefined function posix_getpid() in ……\Workerman\Worker.php 的解决方法
|
11月前
|
Python
Python(二十九)pycharm连接调试器失败 Interrupted function call accept failed~
Pycharm在使用调试器模式时报错: Interrupted function call: accept failed
282 0
|
监控 Linux Apache
访问zabbix安装页面报错500,apache报错Call to undefined function mb_detect_encoding()
访问zabbix安装页面报错500,apache报错Call to undefined function mb_detect_encoding()
243 0
PHP报错Call to undefined function utf8_decode()的解决方案
PHP报错Call to undefined function utf8_decode()的解决方案
138 0