用 GPTs Actions 搭建一个 Github 源码阅读助手
水村烟
编辑于 2023年11月11日 00:01

Code Reader 体验地址:https://chat.openai.com/g/g-c1J88bC63-code-reader Coder Reader Github 地址:https://github.com/sdaaron/github-reader

背景

对不少程序员来说,学习一个新的开源项目是一件挺有难度的事。知名开源项目往往文件众多、结构复杂,往往让人在初上手时一头雾水,不知从何开始。

自动有了ChatGPT,相信很多人在某个时刻都产生过这个想法:「 什么时候ChatGPT 能够帮我一次性阅读整个源码仓库就好了!

现在,依靠 GPT-4-Turbo 128K 的超长上下文长度和 GPTs 给 ChatGPT 赋予的可定制化能力,我们终于可以实现这个想法了。

另外,相信很多人现在对 GPTs 和 GPT Assistant 的印象是这样的:

  1. GPTs 是给非程序员用的,只能通过prompt定义,最多勾选几项能力。可定制化程度低。

  2. GPT Assisant 是给专业程序员用的,高度可定制化。

其实不然,本文将主要介绍一个奥特曼在发布会上没有讲的一个小细节:GPTs Actions,这项功能让 GPTs 也拥有了等效于函数调用的全部能力。让 GPTs 也成为了一个可以调用任何外部工具的开放性 Agent !

成品

有GPTs访问权限的可以通过 https://chat.openai.com/g/g-c1J88bC63-code-reader 在线体验Code Reader。(注意:128K的窗口让GPT4-Turbo可以阅读大多数Github仓库,但是仍然有很多仓库的长度远远超高128K token限制,甚至长到会超过 web 服务器所能接受的上限,所以尽量不要用Coder Reader 去读那些过于大的项目。)

暂时无法访问GPTs的可以通过以下截图预览下 Code Reader 的使用体验:

太酷了,现在GPT可以阅读整个项目目录,学习开源项目变得前所未有地简单!

搭建过程

  1. 进入ChatGPT官方Web界面,如果你是ChatGPT Plus用户,此时应该已经可以看到GPTs的入口「 Explore 」:

  1. 进入「 Explore 」,点击「 Create a GPT 」,即可看到山姆奥特曼在发布会上显示的界面:

  1. 进入「 Configure 」,我们通过代码配置的方式进入这个GPT的开发,会更便捷一些:

这个界面左边是开发区,右边是预览区,开发完即可在预览区测试,非常方便。

  1. 填入你的GPT的名称、描述、指令、和对话开头。以下是我填的信息:

  1. 给我们的私人定制GPT接上外部工具:

这里有几种工具可以选:

  1. 知识库

  2. OpenAI 内置的三大能力:网页浏览、DallE3 画图、Code Interpreter执行代码

  3. Actions

这里我知识库和内置的三大能力都没有勾选,因为用不上,我们这个源码阅读助手的关键在于需要能让GPT访问到github仓库的源码,这需要 Actions 实现。所以接下来我们进入 Actions 开发。

  1. 开发 Actions:

所谓的 Actions, 其实也可以理解为函数调用(Function Calling)的一种具体形式。与函数调用不同的是, Actions 仅限于 GET/ POST/ PUT/ DELETE等有限动作,不像函数调用那样可以调用任意本地函数。当然,这也是理所应当的,毕竟 OpenAI也没有那么多计算资源可以让所有人在他们的硬件上跑云函数。

不过,能够执行 GET/ POST/ PUT/ DELETE 也完全够了,能够调用外部API就等于给GPT接上了无限可能。这里我们仅仅用一个获取Github仓库源码的API做示范,给GPT接上这个能力。

为了告诉GPT怎么调用API,需要定义一个Schema。这里 OpenAI提供了一些实例,这里我们用 OpenAI Profile 这个模版修改一下得到一个能够往请求体中传入 git_url 参数的Schema。

修改完成后的Schema如下:

代码块
JavaScript
自动换行
复制代码
{   "openapi": "3.1.0",   "info": {     "title": "Get Repository Content",     "description": "get the source code content of an entire github repository.",     "version": "v0.0.1"   },   "servers": [     {       "url": "https://github-reader.onrender.com"     }   ],   "paths": {     "/get-repo-content/": {       "post": {         "description": "get the source code content of an entire github repository.",         "operationId": "Get Repository Content",         "parameters": [],         "requestBody": {           "content": {             "application/json": {               "schema": {                 "$ref": "#/components/schemas/Get Repository ContentRequestSchema"               }             }           },           "required": true         },         "deprecated": false,         "x-openai-isConsequential": true       }     }   },   "components": {     "schemas": {       "Get Repository ContentRequestSchema": {         "properties": {           "git_url": {             "type": "",             "title": "git_url",             "description": "get the github repository url from user\u2019s input"           }         },         "type": "object",         "required": [           "git_url"         ],         "title": "Get Repository ContentRequestSchema"       }     }   } }
复制成功

在这个Schema里面,我们需要自己定义的就是 Action的名称、描述、服务器url,PATH,每个PATH的描述和需要传入的参数。

(我11月9日第一次尝试开发一个GPTs的时候,OpenAI官方界面上还有一个可视化的配置界面可以配置这个Schema,所以第一次配的时候非常简单。但是截止到11月10日,这个界面已经迅速下线了,现在只能够通过Schema文件配置,非常迷惑,可能OpenAI发现了什么Bug临时下线了,之后大概还会恢复回来)

  1. 开发一个 API 来获取Github 仓库的源代码

我们已经告诉GPT应该通过向 https://github-reader.onrender.com POST 一个含有git_url的请求体,来获取某个Github仓库的源代码,但是目前还并不存在这样一个API可用,我们需要自己开发。

用FastAPI来搭建这样一个简单API非常简单,只需要70行代码,让GPT帮你写,1分钟就搞定了。

代码块
JavaScript
自动换行
复制代码
from fastapi import FastAPI, HTTPException
from git import Repo
import os
import shutil
from pydantic import BaseModel

app = FastAPI()

# 定义请求体的模型
class GitRepo(BaseModel):
    git_url: str

@app.get("/")
async def get_main():
    return {"message": "Hello World!"}

@app.post("/get-repo-content/")
async def print_repo_url(repo: GitRepo):
    print(repo.git_url)
    git_url = repo.git_url
    base_dir = f"./{git_url.split('/')[-1]}"
    try:
        # 从URL中解析仓库名称
        repo_name = git_url.split("/")[-1]
        if repo_name.endswith(".git"):
            repo_name = repo_name[:-4]

        repo_dir = os.path.join(base_dir, repo_name)
        print(repo_dir)
        # 如果目录存在,先删除
        if os.path.exists(repo_dir):
            shutil.rmtree(repo_dir)

        # 克隆仓库
        Repo.clone_from(git_url, repo_dir)

        content = read_all_files(repo_dir)

        # 清理克隆的仓库目录
        shutil.rmtree(repo_dir)

        return {"content": content[:50000]}

    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

def read_all_files(directory):
    all_text = ""
    for root, dirs, files in os.walk(directory):
        for file in files:
            file_path = os.path.join(root, file)
            if os.path.isfile(file_path) and file_path.endswith(".py"):
                try:
                    with open(file_path, "r", encoding="utf-8") as file:
                        filename = file_path.split("/")[-1]
                        all_text += f"File: {filename}\n\n" + file.read() + "\n\n"
                except UnicodeDecodeError:
                    # 忽略无法读取的文件(如二进制文件)
                    pass
    return all_text

if __name__ == "__main__":
    import uvicorn

    uvicorn.run("main:app", host="0.0.0.0", port=80)
复制成功
  1. 部署 API 服务

这个服务总共就70行代码,为了这点代码整一台云服务器部署实在太麻烦,所以这里我用了一个后端托管服务:render,将这个小脚本托管成了一个web后端服务。具体托管方法我就不浪费口舌了,可以参考https://render.com/docs。或者用其他自己熟悉的方式部署也行。

完整的render部署代码已经上传到 https://github.com/sdaaron/github-reader 了。需要用的话可以叉走一份。

  1. 大功告成!

现在你的API已经就绪,GPTs也开发完毕,快测试一下GPT是否能正确调用API吧!

如果成功,那么你已经掌握了一个能执行你自定义动作的私人GPT了!