ChatGPT-API的充值及接入应用

5/8/2023 ChatGPT-APIChatGPT-Next-WebBabelDOC翻译论文ChatGPT-Telegram-Workerschatgpt-on-wechatOpenAI模型微调

# 1. ChatGPT API的开通与使用

# 1.1 开通ChatGPT API

# 1.1.1 申请ChatGPT API密钥

首先需要准备一个ChatGPT账号,如何申请ChatGPT账号详见我的另一篇博客:ChatGPT智能问答AI使用指南 (opens new window)

登录账号,点击Create new secret key按钮即可创建,生成的密钥要保存好且不要泄露。

申请地址:https://platform.openai.com/account/api-keys (opens new window)

申请ChatGPT-API密钥

# 1.1.2 ChatGPT API的计费

账号注册后,官方会免费赠送18美刀的额度(有效期至2023年4月1日),可供我们白嫖。

ChatGPT-API免费额度

下图是ChatGPT-API的计费(这个官方会不断的进行调整),最新的详见:https://openai.com/pricing (opens new window)

ChatGPT-API的计费

# 1.1.3 官方Token数计算工具

一个简单的英文单词是一个token,一个复杂的英文单词可能包含了2~4个token,一个中文是 1~3 个token。

我们可以使用 OpenAI 官方的 Tokenizer 工具来计算 Token 数。https://platform.openai.com/tokenizer (opens new window)

Tokenizer计算Token

注:大模型API的output token通常比input token更昂贵,尽管二者的计算量基本相同。原因在于计算output时需要逐步生成结果,这导致了通信瓶颈,从而使得GPU在计算output时的利用率较低。

  • 计算量:对于输入和输出相同数量的token,模型的浮点运算次数(FLOPs)大致相同,这个计算量是由模型的前向传播决定的,无论是处理输入还是输出。
  • 显存:输入 token 的QKV是并行计算,输出token的QKV是KV Cache 的形式。从显存的角度来讲,输出时需缓存此时token前的KV矩阵,经过一系列的优化手段,输出所占显存比输入大的并不多。
  • 通信瓶颈:如果GPU计算速度超过数据读取速度,GPU的流处理器就会处于等待状态,那通信就是瓶颈;反之,如果计算速度慢于数据读取速度,通信就需要等待,那就是计算瓶颈。而大模型在多数情况下,都是算的快读的慢,即通信是瓶颈。
  • 并行与序列化:对于输入token来说,模型可以利用并行只需执行一个forward计算,然而对于输出token来说,必须是一个个token的生成,对于D个输出token来说,需要执行D次forward操作。通信本来就是瓶颈,虽然现在也有batch与动态填充等等优化,但是GPU利用率上来说,输出是远低于输入的。

# 1.2 ChatGPT API的充值

# 1.2.1 充值流程概述及前提准备

由于ChatGPT不支持国内用户充值,类似于VISA双币信用卡也是不能用的。所以ChatGPT API充值对于国内用户是非常麻烦的,注册门槛说实话有点高,总结起来其实就下面4个步骤:

ChatGPT充值流程

要完成上述4个步骤,你需要提前准备好:

  • 能正常访问ChatGPT的科学上网条件(建议要选美国或者欧洲的代理节点)
  • 注册1个可用的ChatGPT账号
  • 注册1个海外Apple ID(欧易、Depay在国区Apple商店是搜不到的)
  • 注册1个虚拟交易平台,建议选欧易
  • 申请1张虚拟信用卡,建议选Depay

科学上网条件、注册美区Apple ID详见我的另一篇博客:Clash配置转换及全平台科学上网 (opens new window)

注:Depay挺坑的,5.7发了个升级维护公告,直至5.31才恢复充值卡片,虽然现在又可以使用了,但并不可靠。

Depay系统升级维护公告   2023-05-07
1、最近美国银行系统加强了对数字货币的风险管理。因此,Depay体系下的银行账户也受到了
严格的风险管理,银行入账速度受到影响,造成卡账户资金不足引起消费功能中断,我们正在努力跟银行沟通以恢复正常速度。
2、在恢复到正常速度之前,我们将关闭新用户注册和开卡、关闭卡片充值功能;期问消费可能也会因为银行入账速度导致中断;
3、为减轻您的不便,我们将临时降低卡片提现费用和USDT/USD的兑换汇率。您可以在任何时问正常提现USDT。对于由此给您带来的不便,我们深感抱歉。
Depay teams
1
2
3
4
5
6

另注:Depay的虚拟信用卡,也只有部分号段可以在OpenAI绑定成功,有些号段是绑定不了的。OpenAI用不了的话,充值额度可以绑定支付宝供日常消费使用。绑定不成功的话,可以等过一段时间再试试,我一开始也是绑定不上,后来突然可以了。

# 1.2.2 注册欧易账号并购买USDT

因为Depay不接受人民币直接充值,所以我们必须借助交易所平台,来完成人民币—USDT—USD美元的兑换过程。USDT是虚拟货币泰达币,跟USD美元锚定的,背后有大而不倒的金融机构担保,只要USD在它就在,相对比较安全,但是不要去炒币哈。

USDT是什么

打开 欧易官网 (opens new window) 注册账号——海外Apple商店下载欧易——App首页——我要买币——快捷买币——选USDT——购买23USDT左右(大概¥160),支持支付宝、微信或者银行卡购买。

欧易购买USDT

注意事项:

[1] 欧易的注册需要实名认证,交易方式是直接给某个微信或支付宝账号转账,转账后等待一段时间对方会交付USDT虚拟货币,我的是安全到账了。

[2] 欧易的提币交易有手续费(与提币网络有关,USDT-TRC20的手续费为1USDT),而且新注册的账号刚买完的USDT是被冻结状态的(需要等待一段时间才可提现,我这里是要等一周),等待期间换算USD的比例也会波动,以及后续Depay的手续费(标准卡是1USD),因此相对于ChatGPT Plus所需的20美刀,这里就多充值了大约3美刀的虚拟货币。

欧易USDT提币

# 1.2.3 注册Depay并开卡充值USD

Step1:海外Apple商店下载Depay——开信用卡——Depay 分标准卡、高级卡、白金卡和黑金卡,我这里选择了0元的标准卡。

  • 标准卡:开卡 0 USD,月费 2 USD,充值手续费 1.35%(最低 1USD)
  • 高级卡:开卡 10 USD,月费 1 USD,充值手续费 1.2%
  • 白金卡:开卡 50 USD,月费 0 元,充值手续费 1%
  • 黑金卡:需要邀请 20 个人开卡,无开卡费用,月费 0 USD,手续费 0.8%

注意事项:开卡过程需要实名认证,提交完信息后会进行审核,等待大约5-10min后会审核完成(审核失败了话,可多试几次,可能是录制喊数字的视频不清晰)

Step2:打开Depay,找到钱包——USDT——充币——复制你的充值地址,确认屏幕上显示主网是TRC20,充值地址千万不要复制错。

Depay充值USDT

Step3:打开欧易,找到首页——资产——提币——USDT——链上提币——提币地址填Depay钱包里的充值地址,提币网络选TRC20(千万不能选错,否则到不了账)

欧易链上提币

Step4:欧易提现到Depay成功后,点击Depay钱包——实时兑换,将所有的USDT都兑换成USD美元,点击Depay首页的充值,将兑换的美元存入卡中,到此,Depay充值大功告成。

Depay充值信用卡

# 1.2.4 ChatGPT API充值及开通服务

我们已经在上述步骤中拥有了一张属于自己的Depay虚拟信用卡(相当于借记卡,不可透支),并且往里面充了20多美刀,下面开始充值。

登录账号,打开 https://platform.openai.com/account/billing/overview (opens new window),点击“Set up paid account”按钮,填写信息设置付费账户。

ChatGPT设置付费账户

开通过程需要注意以下事项:

  • 科学上网,尽量用美国或者欧洲IP(要使用没被封禁的IP,不能用就换到能用为止)

  • 浏览器开启无痕模式(避免缓存问题)

  • 账单地址用 美国地址生成器 (opens new window) 生成(理论上可以随便填,建议用生成一个免税州的地址信息)

    美国免税州:阿拉斯加州(Alaska)部分区域、特拉华州(Delaware)、蒙大拿州(Montana)、新罕布什尔州(New Hampshire)和俄勒冈州(Oregon)。
    
    1

ChatGPT-API充值成功

# 1.3 Python调用ChatGPT API

# 1.3.1 旧版本调用示例

以下是在2023.2.11使用ChatGPT官方API的示例,最新的请自行查阅最新的官方API文档及相关资料。

官方API文档:https://platform.openai.com/docs/api-reference/introduction (opens new window)

依赖安装:新版本的依赖库已经不兼容如下写法了

$ pip install openai==0.28
1

调用示例:

# -*- coding: utf-8 -*-

import openai

openai.api_key = "your_api_key"
response = openai.Completion.create(
  model="text-davinci-003",
  prompt="解释一下量子力学",
  temperature=0.7,
  max_tokens=2048,
  top_p=1,
  frequency_penalty=0,
  presence_penalty=0
)
print(response)
print(response["choices"][0]["text"])
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

GPT3模型调用方式如下,输入主要有7个参数:

  • model:模型名称,text-davinci-003
  • prompt:问题或待补全内容,例如“解释一下量子力学”。
  • temperature:控制结果随机性,0.0表示结果固定,随机性大可以设置为0.9。
  • max_tokens:最大返回字数(包括问题和答案),通常汉字占两个token。假设设置成100,如果prompt问题中有40个汉字,那么返回结果中最多包括10个汉字。
  • top_p:设置为1即可。
  • frequency_penalty:设置为0即可。
  • presence_penalty:设置为0即可。

返回值:

{
  "choices": [
    {
      "finish_reason": "stop",
      "index": 0,
      "logprobs": null,
      "text": "量子力学是物理学的一个分支,它研究粒子在微观尺度上的行为。量子力学描述物质的基本粒子,如电子,质子,中子和原子的行为,以及它们之间的相互作用。它采用经典力学的概念,如力,动能和动量,但也提出了新的概念,如量子状态,波函数和量子干涉。量子力学通过解决方程来描述粒子的性质,其中包括波动方程,Schrödinger方程,Heisenberg不确定性原理和Pauli禁止定律。"
    }
  ],
  "created": 1676122039,
  "id": "cmpl-6ikCNyZzoOPscevgIBZ0RaPHONdzz",
  "model": "text-davinci-003",
  "object": "text_completion",
  "usage": {
    "completion_tokens": 338,
    "prompt_tokens": 15,
    "total_tokens": 353
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

注意事项:

[1] ChatGPT API允许的最大token数量为4097,即max_tokens最大设置为4097减去prompt问题的token数量。

[2] 输入参数可以增加stream,即是否采用控制流的方式输出。如果steam取值为True时,那么返回结果是一个Python generator,需要通过迭代获取结果,读取结果的结束字段为“<|im_end|>”。

# -*- coding: utf-8 -*-

import openai

openai.api_key = "your_api_key"
response = openai.Completion.create(
  model="text-davinci-003",
  prompt="解释一下量子力学",
  temperature=0.7,
  max_tokens=2048,
  stream=True,
  top_p=1,
  frequency_penalty=0,
  presence_penalty=0
)

print(response)
res = ''
for r in response:
    res += r["choices"][0]["text"]
res = res.replace('<|im_end|>', '')
print(res)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

返回值:

<generator object EngineAPIResource.create.<locals>.<genexpr> at 0x122ff6a50>

量子力学是研究物质的微观结构的物理学理论。它用来描述物质的基本粒子,即原子、分子、离子等,及其组成物质的相互作用,以及物质在微观尺度上的性质。量子力学的思想是由普朗克和爱因斯坦在20世纪初提出的,它被认为是20世纪物理学的里程碑。量子力学的思想主要是由量子概念引出,即物质不是连续的物体,而是由粒子构成的。量子力学将物理系统的属性描述为“波函数”,即粒子的状态可以用量子概率的方式表示。量子力学的研究已经在原子物理、分子物理、物质的结构和性质、粒子物理、原子核物理等领域取得了显著成果。
1
2
3

[3] Temperature越高,句子生成的越多样化,反之句子生成越固定。

ChatGPT的Temperature参数

# 1.3.2 新版本调用示例

单线程调用

# -*- coding: utf-8 -*-

import requests


def request_openai_api(api_key, api_url, model_name, prompt, max_tokens):
    """
    按照 OpenAI 官方方式调用 API,可以使用代理API地址。
    :param api_key: API 密钥。
    :param api_url: API URL。
    :param model_name: 模型名称。
    :param prompt: 提供给模型的提示。
    :param max_tokens: 生成的最大令牌数。
    :return: 模型的响应。
    """
    headers = {
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json"
    }

    messages = [
        {
            "role": "user",
            "content": prompt
        }
    ]

    data = {
        "messages": messages,
        "max_tokens": max_tokens,
        "model": model_name
    }

    response = requests.post(api_url, headers=headers, json=data)
    response_json = response.json()
    print(response_json)


if __name__ == '__main__':
    api_url = 'https://api.openai.com/v1/chat/completions'
    api_key = 'your_api_key'
    model_name = 'gpt-3.5-turbo-1106'
    prompt_text = "请问您能提供一些编程建议吗?"
    max_tokens = 1000
    request_openai_api(api_key, api_url, model_name, prompt_text, max_tokens)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

多线程调用:

# -*- coding: utf-8 -*-

import threading
import requests


def request_openai_api(api_key, api_url, model_name, prompt, max_tokens):
    """
    按照 OpenAI 官方方式调用 API,可以使用代理API地址。
    :param api_key: API 密钥。
    :param api_url: API URL。
    :param model_name: 模型名称。
    :param prompt: 提供给模型的提示。
    :param max_tokens: 生成的最大令牌数。
    :return: 模型的响应。
    """
    headers = {
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json"
    }

    messages = [
        {
            "role": "user",
            "content": prompt
        }
    ]

    data = {
        "messages": messages,
        "max_tokens": max_tokens,
        "model": model_name
    }

    response = requests.post(api_url, headers=headers, json=data)
    response_json = response.json()
    print(response_json)


def threaded_request(api_key, api_url, model_name, prompt, max_tokens, num_threads, total_requests):
    """
    创建并启动多线程以达到指定的数据总量。
    """
    rounds = (total_requests + num_threads - 1) // num_threads
    for _ in range(rounds):
        threads = []
        for _ in range(num_threads):
            if total_requests <= 0:
                break
            thread = threading.Thread(target=request_openai_api, args=(api_key, api_url, model_name, prompt, max_tokens))
            thread.start()
            threads.append(thread)
            total_requests -= 1

        for thread in threads:
            thread.join()


if __name__ == '__main__':
    api_url = 'https://api.openai.com/v1/chat/completions'
    api_key = 'your_api_key'
    model_name = 'gpt-3.5-turbo-1106'
    prompt_text = "请问您能提供一些编程建议吗?"
    max_tokens = 1000
    num_threads = 5
    total_requests = 15
    threaded_request(api_key, api_url, model_name, prompt_text, max_tokens, num_threads, total_requests)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67

# 1.4 OpenAI风格的大模型推理服务

# 1.4.1 大模型服务参数含义

OpenAI格式的大模型推理服务已经成为了业内规范,常见的开源大模型和商业大模型几乎都支持以这种格式进行部署。

支持的完整参数可参考:https://platform.openai.com/docs/api-reference/making-requests (opens new window)

[1] temperature

该参数用于控制生成文本的随机性和多样性,其实是调整了模型输出的概率分布。

  • temperature 较高时,会更平均地分配概率给各个 token,这导致生成的文本更具随机性和多样性。
  • temperatur 较低接近 0 时,会倾向于选择概率最高的 token,从而使生成的文本更加确定和集中。
  • temperature=1 时表示不使用此方式。

[2] top_k

用于在生成下一个 token 时,限制模型只能考虑前 k 个概率最高的 token,这个策略可以降低模型生成无意义或重复的输出的概率,同时提高模型的生成速度和效率。

[3] top_p

通常用一个介于 0 到 1 之间的值来表示生成下一个 token 时,在概率分布中选择的最高概率的累积阈值。

  • top_p 较高时比如 0.9,这意味着前 90% 的概率的 token 会被考虑在抽样中,这样会允许更多的 token 参与抽样,增加生成文本的多样性。
  • top_p 较低时比如 0.1,这意味着只有前 10% 最高概率的 token 会被考虑在抽样中,这样会限制生成文本的可能性,使生成的文本更加确定和集中。
  • top_p=1 时表示不使用此方式。

[4] repetition_penalty

目标是在这个概率分布中对先前生成过的 token,又重复的生成了该 token 进行惩罚(降低概率),以减少生成文本中的重复性。

[5] no_repeat_ngram_size

当设为大于 0 的整数时,生成的文本中不会出现指定大小的重复 n-gram(n个连续的token),可以使生成的文本更加多样化,避免出现重复的短语或句子结构。

[6] do_sample

对模型计算出来的概率要不要进行多项式采样。

多项式采样是一种用于从一个具有多个可能结果的离散概率分布中进行随机抽样的方法。在多项式采样中,概率高的结果更有可能被选中,但不同于确定性的选择,每个结果(概率较低但仍然可能的词)仍然有一定概率被选中。
1

[7] num_beams

用于束搜索(beam search)算法,用途是控制生成的多个候选句子的数量,该参数控制的是每个生成步要保留的生成结果的数量。

[8] num_beam_groups

beam search 算法的改进,叫做 Diverse Beam Search (DBS)。核心是分组机制,如果设置 num_beams=2,num_beam_groups=2,那就是说分成2个组,每个组里的 beam 可以相似,但组和组之间要有足够的多样性。

[9] diversity_penalty

多样性惩罚参数只有在启用了“num_beam_groups”(组束搜索)时才有效,在这些组之间应用多样性惩罚,以确保每个组生成的内容尽可能不同。

[10] length_penalty

长度惩罚参数也是用于束搜索过程中,作用是将生成序列的长度应用于得分的分母,从而影响候选序列的得分。

  • length_penalty > 1.0 时,较长的序列得到更大的惩罚,鼓励生成较短的序列。
  • length_penalty < 1.0 时,较短的序列得到更大的惩罚,鼓励生成较长的序列。
  • 默认为1,不受惩罚。

[11] use_cache

设置为True时,则模型会利用之前计算得到的注意力权重的缓存,当下一个token需要被生成时,模型可以通过缓存的注意力权重来重用之前计算的信息,而不需要重新计算一次,有效地跳过重复计算的步骤,从而减少计算负担,提高生成速度和效率。

[12] max_length

生成的 token 的最大长度 = 输入 prompt 的长度 + max_new_tokens。如果同时设置了 max_new_tokens,则会覆盖此参数。

[13] max_new_tokens

生成的最大 token 的数量,不考虑输入 prompt 中的 token 数。

[14] min_length

生成的 token 的最小长度 = 输入 prompt 的长度 + min_new_tokens。如果同时设置了 min_new_tokens,则会覆盖此参数。

[15] min_new_tokens

生成的最小 token 的数量,不考虑输入 prompt 中的 token 数。

[16] bad_words_ids

包含词汇 id 的列表,用于指定不允许在生成文本中出现的词汇,如果生成的文本包含任何在这个列表中的词汇,它们将被被替换或排除在最终生成的文本之外。

[17] force_words_ids

包含词汇 id 的列表,用于指定必须包含在生成文本中的词汇,如果给定一个列表,生成的文本将包含这些词汇。

[18] constraints

自定义约束条件,可以指定约束条件,这些约束条件可以是必须出现的关键词、短语、特定术语或其他文本元素,和 force_words_ids 差不多,在代码实现也是一样的。

# 1.4.2 普通输出与流式输出

请求地址:https://api.openai.com/v1/chat/completions (opens new window)

请求方法:POST

[1] 非流式输出

$ curl --location 'https://api.openai.com/v1/chat/completions' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer sk-xxx' \
--data '{
  "model": "gpt-4o",
  "messages": [
    {
      "role": "user",
      "content": "你是谁"
    }
  ],
  "temperature": 0.2,
  "stream": false
}'

>>> 返回结果:
{
  "id": "chatcmpl-ANEuxC362ZhkQIzOgrvfsAkJo8bMd",
  "object": "chat.completion",
  "created": 1730102251,
  "model": "gpt-4o-2024-08-06",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "我是你的AI助手,可以帮助回答问题、提供信息或协助解决问题。有需要请随时告诉我!",
        "refusal": null
      },
      "logprobs": null,
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 9,
    "completion_tokens": 24,
    "total_tokens": 33,
    "prompt_tokens_details": {
      "cached_tokens": 0
    },
    "completion_tokens_details": {
      "reasoning_tokens": 0
    }
  },
  "system_fingerprint": "fp_d54531d9eb"
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

[2] 流式输出

$ curl --location 'https://api.openai.com/v1/chat/completions' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer sk-xxx' \
--data '{
  "model": "gpt-4o",
  "messages": [
    {
      "role": "user",
      "content": "你是谁"
    }
  ],
  "temperature": 0.2,
  "stream": true
}'

>>> 返回结果:
data: {"id":"chatcmpl-ANEwAKU935M3a9rRgmPRtUzu0BmMG","object":"chat.completion.chunk","created":1730102326,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_d54531d9eb","choices":[{"index":0,"delta":{"role":"assistant","content":""},"logprobs":null,"finish_reason":null}]}

data: {"id":"chatcmpl-ANEwAKU935M3a9rRgmPRtUzu0BmMG","object":"chat.completion.chunk","created":1730102326,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_d54531d9eb","choices":[{"index":0,"delta":{"content":"你好"},"logprobs":null,"finish_reason":null}]}

...[此处省略中间内容]

data: {"id":"chatcmpl-ANEwAKU935M3a9rRgmPRtUzu0BmMG","object":"chat.completion.chunk","created":1730102326,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_d54531d9eb","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}]}

data: [DONE]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

流式输出对接的注意事项:

[1] 末尾还有个额外的[DONE],注意兼容。这两个字段同时出现并不冲突,作用都是表明请求已成功完成且没有更多的数据要传输。

  • finish_reason:"stop":这个字段表示生成任务已经结束,模型认为它已经完整地回答了请求。这种情况下,模型不需要继续生成更多内容,所以返回了"stop"。
  • data: [DONE]:这是一个流式接口的常见标记,用来表明数据流已经完成。这意味着服务器已经传输了所有的响应数据,并且不会再有更多的数据发送过来。

[2] 流式输出有时会包含ping心跳包,通常用于保持连接活跃及检测网络连接状态,前端对接接口时要注意兼容,不要出现截断情况。

流式输出注意ping心跳包兼容

# 1.4.3 多轮会话的格式示例

多轮对话的格式示例:

{
    "model": "gpt-4o",
    "messages": [
        {
            "role": "system",
            "content": "我将为您提供一个在职业生涯中寻求指导的人,您的任务是帮助他们根据自己的技能、兴趣和经验确定最适合的职业。您还应该对可用的各种选项进行研究,解释不同行业的就业市场趋势,并就哪些资格对追求特定领域有益提出建议。"
        },
        {
            "role": "user",
            "content": "我目前是一名软件开发人员,我对数据分析和人工智能感兴趣,想转向这个方向。您能给我一些建议吗?"
        },
        {
            "role": "assistant",
            "content": "根据您的背景,转向数据分析和人工智能是一个很自然的选择。以下是一些建议和方向,您可以考虑:\n\n1. **数据科学家/数据分析师**:结合您现有的编程技能,您可以进一步学习数据分析工具和技术(如SQL、Pandas、NumPy)。同时,学习如何使用统计学和数据可视化工具(如Tableau、PowerBI)也非常有帮助。市场上对数据分析师的需求在不断增长,尤其是在金融、科技和健康等行业。\n\n2. **机器学习工程师**:如果您对人工智能尤其感兴趣,机器学习工程师是一个有前景的职业。您可以开始学习常用的机器学习框架,如TensorFlow和PyTorch,进一步掌握机器学习算法(如回归、分类、聚类等)。\n\n3. **行业趋势**:目前,数据科学和人工智能在多个行业都具有良好的就业前景,特别是在科技、金融、医疗和电商领域。根据不同国家的就业市场,这些职位的薪资和需求都有增长趋势,特别是对拥有编程和数据处理能力的人才需求旺盛。\n\n4. **资格建议**:为了增强您的竞争力,建议您获得相关的认证或文凭,例如数据科学、机器学习或人工智能领域的课程认证(Coursera、edX 等平台提供很多相关课程)。您还可以考虑参加 Kaggle 等平台上的数据竞赛,提升实战经验。"
        },
        {
            "role": "user",
            "content": "那关于上一个的第二个方向,是否需要精通数学和统计学?"
        },
        {
            "role": "assistant",
            "content": "是的,机器学习工程师的职位需要掌握一定的数学和统计学知识。特别是线性代数、概率论和微积分是核心基础。虽然上手可以依赖框架,但理解这些理论会帮助您更好地优化模型和调试算法。因此,建议至少掌握这些领域的基本概念,再通过实际项目逐步深化。"
        }
    ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

角色解释:

  • system:这是用于设定对话规则或提供对话指导的角色。它通常定义了对话的初始状态和期望的行为,比如要求助手如何处理用户的输入。
  • user:这个角色表示用户的输入,通常是提问、查询或某种交互请求。用户可以发送各种消息,比如问题、命令或需求。
  • assistant:这是对话中的助手角色,负责根据 system 定义的规则和 user 的输入,提供相关的回应或解答。

# 1.5 大模型服务重复输出问题

# 1.5.1 问题描述

由于大模型是生成式的模型,有的时候会像复读机一样重复生成内容,可以分为三个级别:

  • 词汇和短语重复:模型生成的文本中会出现同一个词或短语反复出现,比如同一句话在一段文字中被多次重复。
  • 段落级别重复:在生成较长文本时,模型可能生成重复的句子或段落,甚至生成与前面几乎相同的内容段。
  • 内容结构重复:在有结构化回答需求的情况下,模型可能多次以相似的结构回答或解释,导致输出显得冗余。

# 1.5.2 产生原因

当输入的条件文本很长,生成的文本又很少时可能出现不断重复的现象。很多大模型是自回归模型,通过前T-1个Token作为条件,来预测第T个Token的是哪个,当前面的条件文本过长时,大模型的输出的几个短文本会被原始的很长的条件文本淹没,继续预测下一个Token的话,在模型看起来可能条件仍然是差不多的,新生成的内容被忽视了,于是就重复生成内容了。

外在的影响因素可以归结为下面内容:

[1] 输入提示设计问题

  • 模糊或不明确的提示:当提示不够清晰时,模型可能会生成冗长的、相似的内容,造成重复。
  • 缺乏上下文:如果模型在生成响应时缺乏足够的上下文,它可能无法理解你想要的信息,因此可能会重复之前提到的内容。

[2] 模型设置

  • 温度系数参数过低:模型在低温度设置下(如温度接近 0)会生成保守且确定性的输出,可能导致重复生成相似的内容。
  • Top-k 或 Top-p 采样:如果这些采样策略没有设置合理,可能会导致模型只选择高概率的词汇,造成重复。

[3] 数据问题

  • 训练数据的局限性:模型是基于大量文本进行训练的,如果数据集中存在重复信息,模型可能会在生成响应时重复这些信息。
  • 上下文的重复:在交互中,如果多次提及相同的主题或问题,模型可能会基于之前的回答进行重复。

# 1.5.3 解决方案

[1] Prompt提示词优化

Prompt提示词的设计对于输出的质量至关重要。为了避免重复,可以在提示词中明确要求输出不同的信息或新的观点。

  • 指定不同的角度:在提示中要求模型从不同的视角或方面进行回答。
  • 避免泛泛的问题:提供更具体的问题能够帮助模型生成不重复的信息。

[2] 调整模型参数

如果有权访问模型的参数,可以通过调整参数来减少重复现象。

  • 提高温度系数:适度提高温度(如 0.7-0.9)可增加输出的随机性,鼓励模型生成更多不同的内容。温度的设置影响到概率分布的平滑程度。低温度值(如接近0)会使得生成的文本更具确定性,更倾向于选择概率最高的单词。高温度值(如接近1)则会增加随机性,使模型更有可能选择概率较低的单词,从而产生更多样化和创造性的结果。
  • 使用 Top-p 采样:调整 Top-p 采样可以让模型在生成时从更多不同的词汇中进行选择,而不仅仅选择高概率的词。Top-p 参数通过设置概率阈值p来限制模型的选择范围,而不是只选取最高概率的词。比如,设置p=0.9,意味着模型会从概率累计达到90%的词中随机选择下一个词,而不会只局限于最可能的那几个选项。一般来说,p 值在0.85到0.95之间效果较佳。p=0.9通常是一个比较平衡的设置,能兼顾多样性和连贯性。若设置较低的 p 值(如 0.8),生成内容会更加丰富,但可能也会出现不相关或略微随机的词汇选择。较高的 p 值(接近1.0)则趋向于常规的高概率选择,生成的文本更安全和精确,但重复性可能也略高。
  • 适当调整输出长度:避免设置过短的最大输出长度,确保模型有足够的空间生成完整且不重复的回答。
  • 使用repetition_penalty惩罚模型重复输出:repetition_penalty 参数的工作原理是,在生成新内容时惩罚模型重复使用之前生成过的词汇和短语。这会促使模型在生成时更加倾向使用新的词汇或表达方式。通常repetition_penalty的值可以在1.01到2.0之间选择,值越高,惩罚越重。一般来说,像1.05这样略微的惩罚值有助于减少简单重复,同时保持生成的流畅度和逻辑性。对于更强的惩罚,可以尝试更高的值(例如1.2),但可能会影响文本的连贯性。

[3] 分段提问

将问题分解为更小的子问题,逐步获取更多细节,避免模型在单一长回答中重复。

不佳的提示:

请告诉我所有关于气候变化的信息。
1

优化后的提问:

1. 请简述气候变化的定义及主要原因。
2. 气候变化如何影响海洋生态系统?
3. 气候变化对农业有何影响?
1
2
3

通过逐步提问,不仅减少了信息重复,还可以获得更结构化的内容。

[4] 控制输出长度与约束

在提示中明确要求回答不要重复,并限制生成的长度。

在提示中指定避免重复:

请描述气候变化的影响,但避免重复之前提到的内容。
1

限制输出长度:可以在某些情况下限制生成的字数,以避免长篇回答中不必要的重复,但是让大模型控制输出内容长度不一定有效。

在150字以内回答这个问题,避免冗长和重复。
1

# 2. 国内使用OpenAI API的方式

# 2.1 Cloudflare Worker代理请求API

# 2.1.1 如何配置

OpenAI API 在国内访问非常慢,经常超时,因此可以使用 Cloudflare Worker 将其代理成自己的国内可直连域名,这样就可以直接调用了,速度大大加快。

代理请求到 OpenAI API,配置部署步骤如下:

  • 注册并登录到 Cloudflare 账户
  • 创建一个新的 Cloudflare Worker
  • 将 cloudflare-worker.js 复制并粘贴到 Cloudflare Worker 编辑器中
  • 保存并部署 Cloudflare Worker
  • 在 Worker 详情页 -> Trigger -> Custom Domains 中为这个 Worker 添加一个自定义域名

注意:需要配置一个自己的国内可直连域名,因为直接使用 Cloudflare 的域名,依然无法访问。

cloudflare-worker.js

addEventListener("fetch", (event) => {
  event.respondWith(handleRequest(event.request));
});

async function handleRequest(request) {
  const url = new URL(request.url);
  const fetchAPI = request.url.replace(url.host, 'api.openai.com');
  
  // 部分代理工具,请求由浏览器发起,跨域请求时会先发送一个 preflight 进行检查,也就是 OPTIONS 请求
  // 需要响应该请求,否则后续的 POST 会失败
  const corsHeaders = {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods': 'OPTIONS',
    'Access-Control-Allow-Headers': '*',
  };
  if (request.method === 'OPTIONS') return new Response(null, { headers: corsHeaders });

  let body;
  if (request.method === 'POST') body = await request.json();

  const authKey = request.headers.get('Authorization');
  if (!authKey) return new Response("Not allowed", { status: 403 });
  
  const payload = {
    method: request.method,
    headers: {
      "Content-Type": "application/json",
      Authorization: authKey,
    },
    body: typeof body === 'object' ? JSON.stringify(body) : '{}',
  };
  // 在 Cloudflare 中,HEAD 和 GET 请求带 body 会报错
  if (['HEAD', 'GET'].includes(request.method)) delete payload.body;

  // 入参中如果包含了 stream=true,则表现形式为流式输出
  const response = await fetch(fetchAPI, payload);
  if (body && body.stream !== true) {
    const results = await response.json();
    return new Response(JSON.stringify(results), {
      status: response.status,
      headers: {
        "Content-Type": "application/json",
      },
    });
  } else {
    return new Response(response.body, {
      status: response.status,
      statusText: response.statusText,
      headers: response.headers,
    });
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

Cloudflare Worker 免费版限额日调用10w次,对于普通用户是够用了。

Cloudflare-Worker免费版日调用限额

# 2.1.2 如何使用

配置成功之后,我们可以在代码开头加上 OPENAI_API_BASE 的配置,使其调用我们的域名。

import os
os.environ["OPENAI_API_BASE"] = "https://openai.your.domain/v1"
1
2

# 2.2 国内的付费第三方ChatGPT API

如果不会折腾上面这些,也可以直接使用国内的付费第三方ChatGPT API,它的用法与官方ChatGPT API一致,换个域名就能用,费用比较低,国内可以直连。不过这种厂商都是用的黑卡搞的,随时存在跑路风险,不要充值太多。

# 2.2.1 OpenAI-SB

OpenAI-SB 是一个 OpenAI API 反代服务,它可以让用户直接调用 OpenAI 的人工智能 API,而无需自行申请 OpenAI API 的额度。在用户调用 API 时,OpenAI-SB 会将请求转发至自己的账号池中,然后再通过 OpenAI 的官方 API 接口将请求发送至 OpenAI 的服务器进行处理。

OpenAI-SB 的账号池包含多个 OpenAI API 账号,每个账号都具有一定的 API 请求配额。当一个账号的 API 请求配额用完时,OpenAI-SB 会自动切换到另一个账号,以确保 API 的可靠性和稳定性。同时,OpenAI-SB 还具有高并发处理能力,可以在用户高并发请求时,保持 API 响应速度和稳定性。

OpenAI-SB 的官网地址:https://openai-sb.com (opens new window),定价如下:

openai-sb第三方API定价

# 2.2.2 GPTAPI.US

GPTAPI.US这家是使用 one-api 开源项目进行搭建的,官方文档写的非常详细,而且提供Docker镜像,有兴趣的也可以自己搭建。

one-api接入各种渠道API

项目地址:https://github.com/songquanpeng/one-api (opens new window)

one-api项目截图

GPTAPI.US 的官网地址:https://www.gptapi.us (opens new window),定价如下:

GPTAPI.US第三方API定价

# 2.3 使用ChatGPT-Next-Web提供的代理

# 2.3.1 搭建ChatGPT-Next-Web

一键免费部署你的私人 ChatGPT 网页应用,支持 Web / PWA / Linux / Win / MacOS等平台。除此之外,还提供代理接口供国内访问。

我这里直接使用了官方的docker镜像进行一键部署。

$ docker pull yidadaa/chatgpt-next-web
$ docker run -d -p 3000:3000 --name chatgpt-next-web \
   -e OPENAI_API_KEY=your_api_key \
   -e CODE=your_password \
   yidadaa/chatgpt-next-web
1
2
3
4
5

常用参数说明如下,完整参数详见:环境变量-官方文档 (opens new window)

  • OPENAI_API_KEY(必填项):OpanAI 密钥,在 OpanAI 账户页面申请的 API_KEY。
  • CODE(可选):访问密码,可选,可使用逗号隔开多个密码。(警告:如果不填写此项,则任何人都可以直接使用你部署后的网站,可能会导致你的 token 被急速消耗完毕,建议填写此选项)
  • BASE_URL(可选):OpenAI接口代理URL,如果你手动配置了 OpanAI 接口代理,填写此选项。(如遇到SSL证书问题,将BASE_URL的协议设置为 http)

ChatGPT-Next-Web

# 2.3.2 ChatGPT-Next-Web的代理API

ChatGPT-Next-Web如果不设置密码的话,可以提供代理API供国内进行调用(不过这个的调用格式就与OpenAI官方不一致了),以下是调用示例:

# -*- coding: utf-8 -*-

import requests
import json
import time


class CustomGPT:

    def __init__(self, url, model, stream=False):
        self.url = url
        self.model = model
        self.stream = stream

    def _call(self, prompt):
        """
        使用 requests 发送 POST 请求到代理的 ChatGPT-API。
        """
        data = {
            "model": self.model,
            "messages": [{"role": "user", "content": prompt}],
            "stream": self.stream
        }
        headers = {
            'Content-Type': 'application/json',
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36'
        }
        req_count = 0
        while req_count < 5:
            try:
                response = requests.post(self.url, json=data, stream=False, headers=headers, verify=False)
                if response.status_code == 200:
                    r_content = response.content.decode(response.apparent_encoding)
                    r_content = json.loads(r_content)
                    text = r_content['choices'][0]['message']['content']
                    return text
                else:
                    print(f"请求失败,状态码: {response.status_code}, 重试次数: {req_count+1}, 请求内容: {prompt}")
                    time.sleep(2)
                    req_count += 1
            except Exception as e:
                print(f"请求异常, 重试次数: {req_count + 1}, 请求内容: {prompt}; 错误信息: {e}")
                time.sleep(2)
                req_count += 1
        return "请求错误或超出重试次数"


if __name__ == '__main__':
    custom_gpt = CustomGPT(url='http://127.0.0.1:3000/api/openai/v1/chat/completions', model='gpt-3.5-turbo')
    result = custom_gpt._call("解释一下量子计算")
    print(result)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

# 2.4 免费白嫖ChatGPT API

# 2.4.1 使用FreeGPT35获取免费API

2024.4.1,OpenAI宣布无需注册即可使用ChatGPT网页版服务了,但目前只能使用ChatGPT 3.5。FreeGPT35项目就是利用免登录ChatGPT Web去提供免费无限制的GPT-3.5-Turbo API服务。

$ mkdir freegpt35 && cd freegpt35
$ wget -O compose.yaml https://raw.githubusercontent.com/missuo/FreeGPT35/main/compose.yaml
$ docker compose up -d
1
2
3

可以使用以下命令进行测试,不一定要传Authorization,当然也可以传任意字符串。

$ curl http://127.0.0.1:3040/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer any_string_you_like" \
  -d '{
    "model": "gpt-3.5-turbo",
    "messages": [
      {
        "role": "user",
        "content": "解释一下量子计算"
      }
    ],
    "stream": false
    }'
1
2
3
4
5
6
7
8
9
10
11
12
13

注:如果无法正常使用该项目,很可能是 IP 已触发 Cloudflare 的屏蔽,或已被禁止,请尝试自行更改IP或切换服务器。

使用FreeGPT35获取免费API

# 2.4.2 Copliot免费使用GPT-4

2023年12月底,微软的Copliot出iOS客户端了,在美区Apple Store下载(国区未上架),不需要登录,即可免费使用GPT-4,不过使用该应用时需要开代理(建议全局代理,我一开始使用规则分流告知不支持地区)。另外,安卓也有该客户端,也能免费使用GPT-4。

Copliot免费使用GPT-4

另注:底层是调用了 OpenAI API,所以如果你有 Github Copilot 账号的话,可以白嫖GPT-4 API了,需要填写 Github Copilot Token。

通过Copliot白嫖GPT-4的API

# 3. 使用ChatGPT API搭建服务

# 3.1 搭建Web服务及本地应用

# 3.1.1 ChatGPT搭建成Web服务

用 Express 和 Vue3 搭建的 ChatGPT 演示网页。

为了图省事,我这里直接使用了官方的docker镜像进行一键部署。愿意折腾的,可以自己去构建镜像,还有很多参数可以配置,这里我就不介绍了。

$ docker run --name chatgpt-web -d -p 3002:3002 --env OPENAI_API_KEY=your_api_key chenzhaoyu94/chatgpt-web
1

chatgpt-web

# 3.1.2 ChatGPT桌面版应用程序

项目简介:ChatGPT 桌面应用程序(支持 Mac、Windows 和 Linux),使用 OpenAI API 密钥的方式进行访问。

项目地址:https://github.com/lencx/nofwl (opens new window)

M系芯片MacOS安装ARM版软件包,打开时会遇到“ChatGPT”已损坏,无法打开。你应该把它移到垃圾箱”的报错,执行以下命令即可解决。

$ xattr -cr /Applications/NoFWL.app
1

NoFWL桌面程序

# 3.2 搭建学术及工程服务

# 3.2.1 中科院学术版ChatGPT

简目简介:科研工作专用ChatGPT拓展,特别优化学术Paper润色体验,支持自定义快捷按钮,支持markdown表格显示,Tex公式双显示,代码显示功能完善,新增本地Python工程剖析功能/自我剖析功能。

项目地址:https://github.com/binary-husky/chatgpt_academic (opens new window)

功能 描述
一键润色 支持一键润色、一键查找论文语法错误
一键中英互译 一键中英互译
一键代码解释 可以正确显示代码、解释代码
自定义快捷键 支持自定义快捷键
配置代理服务器 支持配置代理服务器
模块化设计 支持自定义高阶的实验性功能
自我程序剖析 [实验性功能] 一键读懂本项目的源代码
程序剖析 [实验性功能] 一键可以剖析其他Python/C++项目
读论文 [实验性功能] 一键解读latex论文全文并生成摘要
批量注释生成 [实验性功能] 一键批量生成函数注释
chat分析报告生成 [实验性功能] 运行后自动生成总结汇报
公式显示 可以同时显示公式的tex形式和渲染形式
图片显示 可以在markdown中显示图片
支持GPT输出的markdown表格 可以输出支持GPT的markdown表格
…… ……

我这里采用docker的方式对服务进行部署。

Step1:拉取代码构建镜像

$ git clone https://github.com/binary-husky/chatgpt_academic.git
$ cd chatgpt_academic
$ docker build -t gpt-academic .
1
2
3

Step2:修改配置文件及主程序源码

  • 修改config.py配置文件:修改API_KEY参数即可(因为我的服务器是境外的,所以不需要设置代理)

  • 修改main.py主程序文件:官方源码的是随机选取Web端口,这里把它设置成固定的端口(例如5000)。

    # 如果WEB_PORT是-1, 则随机选取WEB端口
    # PORT = find_free_port() if WEB_PORT <= 0 else WEB_PORT
    PORT = 5000
    
    1
    2
    3

Step3:创建容器并挂载配置文件。

$ docker run -itd --name chatgpt_academic -p 5000:5000 -v /root/chatgpt_academic/config.py:/gpt/config.py  -v /root/chatgpt_academic/main.py:/gpt/main.py gpt-academic:latest
$ docker logs -f chatgpt_academic --tail 100
1
2

部署成功后可以在控制台种看到如下日志:

chatgpt_academic部署成功

Chrome浏览器打开http://ip:5000地址,即可查看服务。

ChatGPT学术优化

# 3.2.2 使用ChatGPT转换代码

项目描述:使用 ChatGPT 将代码从一种语言翻译成另一种语言。

项目地址:https://github.com/mckaywrigley/ai-code-translator (opens new window)(另附:官方体验地址 (opens new window)

ai-code-translator

# 3.3 接入第三方应用及功能增强

# 3.3.1 将ChatGPT通过快捷指令接入Siri

可以通过快捷指令的方式将ChatGPT接入Siri。

# 3.3.2 使用ChatGPT的检索扩展插件

插件描述:可以在 Google 搜索结果的旁边显示 ChatGPT 回答的结果,需要使用自己的ChatGPT API。

项目地址:https://github.com/wong2/chat-gpt-google-extension (opens new window)

Chrome插件商店地址:https://chrome.google.com/webstore/detail/chatgpt-for-google/jgjaeacdkonaoafenlfkkkmbaopkbilf (opens new window)

chat-gpt-google-extension

# 3.3.3 将ChatGPT接入划词翻译插件

基于 ChatGPT API 的划词翻译浏览器插件和跨平台桌面端应用。它的使用也非常简单,下载插件 / 应用之后,填入你的 OpenAI API Key 就能使用。

特性:1)支持 55 种语言的相互翻译、润色和总结功能。2)支持实时翻译、润色和总结,以最快的速度响应用户。3)支持自定义翻译文本。4)支持一键复制。5)全平台(Windows + macOS + Linux)支持。

openai-translator效果图

# 3.4 接入其他AI应用工具

302.AI 是一个“AI应用超市”,官方把它的应用都开源出来了,你可以基于Next.js在这基础上二次开发自己的AI应用。

302.Al

# 4. 使用ChatGPT API翻译文档

# 4.1 ChatGPT-for-Translation翻译文档

# 4.1.1 ChatGPT-for-Translation简介

使用ChatGPT完成文件翻译。该工具接受一个文本文件(.pdf, .txt, .md, .html.rtf)或者一个包含文本的文件夹。它将生成一个直接翻译和一个双语文本文件,对于 PDF 解析做了优化。

# 4.1.2 ChatGPT-for-Translation使用实例

[1] 准备运行环境

实验环境:Macbook Pro 2021,M1 pro芯片,16G内存,macOS Ventura13.2.1系统,Python3.9环境

$ git clone https://github.com/Raychanan/ChatGPT-for-Translation.git
$ cd ./ChatGPT-for-Translation/
$ pip3 install -r requirements.txt
$ pip3 install tenacity
1
2
3
4

[2] 翻译PDF文档

项目使用:建议使用 gpt-3.5-turbo 模型进行翻译,经济实惠,我翻译了个200页的英文文献,耗时12分钟,花费0.23美刀。

$ python3 ChatGPT-translate.py --input_path=./your_path/your_doc.pdf --openai_key=your_openai_key
1

默认值是使用gpt-3.5-turbo模型,翻译成简体中文,支持的参数如下:

    arguments = [
        ("--input_path", {"type": str,
         "help": "input file or folder to translate"}),
        ("--openai_key", {"type": str,
         "default": "", "help": "OpenAI API key"}),
        ("--model", {"type": str, "default": "gpt-3.5-turbo",
         "help": "Model to use for translation, e.g., 'gpt-3.5-turbo' or 'gpt-4'"}),
        ("--num_threads", {"type": int, "default": 10,
         "help": "number of threads to use for translation"}),
        ("--target_language", {"type": str, "default": "Simplified Chinese",
         "help": "target language to translate to"}),
        ("--only_process_this_file_extension",
         {"type": str, "default": "", "help": "only process files with this extension"}),
        ("--use_azure", {"action": "store_true", "default": False,
         "help": "Use Azure OpenAI service instead of OpenAI platform."}),
        ("--azure_endpoint",
         {"type": str, "default": "", "help": "Endpoint URL of Azure OpenAI service. Only require when use AOAI."}),
        ("--azure_deployment_name",
         {"type": str, "default": "", "help": "Deployment of Azure OpenAI service. Only require when use AOAI."}),
    ]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

翻译效果:会生成5个文档,分别是txt格式的原文提取、纯中文译文、双语对照译文,以及docx格式的纯中文译文、双语对照译文。

ChatGPT-for-Translation翻译效果

# 4.2 PDFMathTranslate翻译文档

# 4.2.1 PDFMathTranslate简介

开源的命令行工具,使用 LLM 翻译 PDF 文件,可以完整保留排版。

# 4.2.2 PDFMathTranslate使用实例

实验环境:Macbook Pro 2021,M1 pro芯片,16G内存,macOS Sonoma 14.5系统,Python3.10环境

官方要求 Python 版本 >=3.8,<=3.12,依赖安装如下:

$ pip3 install pdf2zh
1

支持很多种格式的大模型服务,开源的与商业的均可,我这里采用OpenAI的gpt-4o模型

$ export OPENAI_BASE_URL=https://api.openai.com/v1
$ export OPENAI_API_KEY=sk-xxx
$ pdf2zh alphafold3.pdf -s openai:gpt-4o
1
2
3

会在当前目录里输出 alphafold3-zh.pdf、alphafold3-dual.pdf 两个文件,前者是纯译文版本的,后者是原文与译文结合的。

PDFMathTranslate

注:PDF翻译的整体效果还可以,能够保持原来的页面格式,但还是会有部分页面没翻译、部分内容格式错乱,对于阅读有一定影响。

# 4.3 BabelDOC翻译文档

# 4.3.1 BabelDOC简介

BabelDOC是一款翻译工具,专为科学论文设计,可在原文旁实时生成译文,形成清晰的双语对照。采用无损解析技术,完整保留数学公式、表格和图形,确保译文版式与原文一致。

# 4.3.2 BabelDOC使用实例

实验环境:Macbook Pro 2021,M1 pro芯片,16G内存,macOS Sonoma 14.5系统,Python3.10环境

$ curl -LsSf https://astral.sh/uv/install.sh | sh
$ uv tool install --python 3.12 BabelDOC
$ babeldoc --help
$ uv run babeldoc --files example.pdf --openai --openai-model "gpt-4o-mini" --openai-base-url "https://api.openai.com/v1" --openai-api-key "your-api-key-here"
1
2
3
4

会输出alphafold3.zh.mono.pdf、alphafold3.zh.dual.pdf两个文件,前者是纯译文版本的,后者是原文与译文结合的。

BabelDOC翻译过程

翻译效果:翻译的效果很惊艳,没有出现格式错乱的情况,整体效果比PDFMathTranslate要好。

BabelDOC翻译效果

# 5. 使用ChatGPT API辅助工作

# 5.1 gpt-code-ui代码解释器

# 5.1.1 项目简介

gpt-code-ui 是 OpenAI 的 ChatGPT 代码解释器的开源实现。其功能特性有:上传文件、文件下载、上下文感知(可以参考之前的消息)、生成代码、运行代码(Python内核)、模型切换(GPT-3.5和GPT-4)

项目地址:https://github.com/ricklamers/gpt-code-ui (opens new window)

# 5.1.2 实现原理

实现原理:它的架构并不算复杂,用Flask运行Web服务,代码运行部分借助Jupyter kernel process,通过Kernel manager在Flask和Jupyter之间通信。UI部分是React做的,用户输入后,将输入转换成Prompt,借助OpenAI对任务分解并生成Python代码在Jupyter中执行。

gpt-code-ui实现原理

# 5.1.3 项目部署

项目运行:我是在Debian11服务器上跑的,Python版本3.9.2。

$ pip3 install gpt-code-ui
$ gptcode
1
2

项目启动后,访问 http://ip:8080/ 地址,点击 Set OpenAl key 按钮,填写上API-KEY,这里我使用的模型是GPT-3.5。

# 5.1.4 使用示例

这里依旧让它生成一个二维码,执行后它告诉我少依赖。

gpt-code-ui使用效果-1

告诉它把那个依赖安装上,成功运行出了结果。

gpt-code-ui使用效果-2

# 5.1.5 总体评价

可以实现代码解释器的基本功能,但没有那么智能,遇到报错还需要人去告诉它怎么做,不像OpenAI的可以全自动实现,实际意义不大。

# 5.2 Open Interpreter代码解释器

# 5.2.1 项目简介

Open Interpreter是一个开源工具,提供交互式和编程聊天功能,让用户可以通过在终端中运行解释器与其进行对话。开发人员可以编写任务和工作流程脚本,直接传递消息给解释器,以便更精确地控制。用户可以检查和配置系统消息,以满足个人偏好和项目要求的灵活性。

  • Open Interpreter 不仅仅是另一个编码解释器。它是一个专注于复杂任务的强大工具,允许语言模型在您的计算机上运行代码,以完整地编写应用程序、任务等。它是 OpenAI 代码解释器的实现,可以运行 GPT-4 和其他模型,支持 Python、JavaScript、Shell 等语言的代码。
  • Open Interpreter 的独特功能之一是其终端中类似聊天的界面,允许用户以更直观和用户友好的方式与之交互。该解释器不仅可以编写代码,还可以创建和编辑照片、视频、PDF 和其他文件。它可以控制网络浏览器进行网络搜索,并通过网络上的不同区域进行搜索,以实现并提供必要的链接。

项目地址:https://github.com/KillianLucas/open-interpreter (opens new window)

# 5.2.2 核心特点

Open Interpreter 的核心特点包括:

  • 互联网访问自由:在本地运行的 Open Interpreter 可以完全访问互联网,允许免费访问外部API和数据库,提高项目的灵活性。
  • 包和库选择:Open Interpreter允许使用任何包或库,使您能够选择最适合您项目的工具。
  • 无限的运行时间和文件大小:Open Interpreter没有执行时间或文件大小的限制,可以顺利进行大规模数据分析和计算处理。
  • 确保安全性:在执行代码之前,Open Interpreter需要用户确认,以防止执行未经授权的代码,提高安全性。
  • 开源免费:Open Interpreter是一个开源项目,对于初创公司和个人开发人员来说,降低成本是一个重要优势。
  • 支持多种编程语言:Open Interpreter不仅支持Python,还支持JavaScript、Bash等多种编程语言,适用于更广泛的应用程序。

# 5.2.3 竞品对比

与 OpenAI 的 Code Interpreter相比,Open Interpreter 提供了更多可能性,包括互联网访问、预装软件包没有限制、没有最大上传限制。此外,当环境终止时,状态不会被清除,从而允许用户继续使用以前的项目。

项目 Open Interpreter OpenAI Code Interpreter
上网 在本地工作,完全可访问 基于云托管,访问受限
可用软件包 可以使用任何包或库 仅预安装
执行时间和文件大小 无限制 有限的运行时间和文件大小
安全 需要用户确认以防止恶意代码 不同的用户验证流程
收费 免费(开源) $ 20 /月(付费计划)
支持各种编程语言 Python、JavaScript、Bash 等 仅限Python

# 5.2.4 安装配置

首先,安装 open-interpreter 依赖。

$ pip3 install open-interpreter
1

安装完成后,在 Terminal 输入 interpreter -y 就可以启动 Open Interpreter。默认会提示输入 OpenAI API key,配置了该项会使用 GPT-4 执行,否则使用本地 Code-LLama 执行。如果使用 Code-LLama 在本地运行它,模型大小从 3GB 到 30GB 不等。我这里选择使用效果最佳的 GPT-4 去执行。

$ interpreter -y 
1

Open-Interpreter配置API-KEY

# 5.2.5 使用示例

[1] 求解数学问题

Prompt:对x^2cos(2x)求积分

Open-Interpreter求解数学问题

注:结果不完全对,对它求不定积分,最后的结果里应该还有个 + C

[2] 联网支持

Prompt:请总结一下 https://about.fb.com/news/2023/08/code-llama-ai-for-coding/ 这篇文章,结果用中文输出

Open-Interpreter联网支持

注:处理的中间过程有大量报错,有时会陷入死循环,试了好几次才成功跑出来结果。

# 5.2.6 总体评价

有详细的步骤分解,遇到各种报错都可以自动解决,在使用GPT-4作为LLM时,效果已经于OpenAI官方的代码解释器很接近了。

注意运行时需要有人盯着,发现不对劲及时终止,防止遇到某些报错的时候,它解决不了,一直循环同一个解决方案,钱哗哗的就流走了。

Open-Interpreter循环同一方案解决报错的问题

由于中间过程有时会遇到大量的报错,在使用 GPT-4 作为 LLM 时,API Key 的成本还挺高的,尝试了几个复杂需求就花费了我 5.75 美刀。

测试Open-Interpreter的成本

# 5.3 GraphRAG知识图谱检索增强生成

# 5.3.1 GraphRAG简介

[1] 传统RAG弊端

现如今,RAG 是一种使用真实世界信息改进 LLM 输出的技术,是大多数 LLM 工具的重要组成部分。一般而言,传统 RAG 通常依赖于向量相似性搜索,但它面临几个局限性,例如:

  • 难以连接不同的信息:当答案需要将分散在多个文档中并通过微妙关系链接的见解编织在一起时,传统 RAG 很难有效地将这些点连接起来。
  • 有限的整体理解:分析大型数据集的总体主题或准确总结复杂文档需要更深入地理解语义关系,而传统 RAG 方法通常缺乏这一点。

[2] GraphRAG简介

微软开源的 GraphRAG 是一种基于图的检索增强生成方法。通过 LLM 构建知识图谱结合图机器学习,GraphRAG 极大增强 LLM 在处理私有数据时的性能,同时具备连点成线的跨大型数据集的复杂语义问题推理能力。

GraphRAG模式

传统 RAG 在私有数据,如企业的专有研究、商业文档表现非常差,而 GraphRAG 则基于前置的知识图谱、社区分层和语义总结以及图机器学习技术可以大幅度提供此类场景的性能。微软说他们在大量数据集上进行了测试,在全面性、多样性、赋权性方面,结果显示 GraphRAG 都优于传统 RAG(70~80% 获胜率)。

[3] GraphRAG架构

GraphRAG 的优势在于其结构化、分层的方法。

GraphRAG管线

Step1:索引阶段

索引阶段为 GraphRAG 的知识驱动分析奠定了基础。

  • 文本分块:输入数据可以是文档集合或单个大型文档,被划分为较小的可管理单元(称为文本单元)。这些单元为后续分析提供了一个细粒度的框架。
  • 知识图谱提取:使用 LLM 分析每个文本单元,细致地提取实体(例如,人员、组织、位置)、它们之间的关系(例如,“工作地点”、“位于”)以及与它们相关的重要声明。此过程将非结构化文本转换为结构化知识图谱,其中节点表示实体,边表示关系。
  • 社区检测:GraphRAG 随后将强大的社区检测技术 Leiden 算法应用于提取的知识图谱。该算法识别密切相关实体的集群,形成分层社区结构。可视化此结构可将社区显示为节点集群,每个节点的大小反映其重要性(程度),颜色表示社区成员身份。
  • 社区摘要:为了便于全面了解数据集,GraphRAG 为每个已识别的社区生成摘要。此过程从层次结构的底部开始,总结社区内的各个实体和关系,然后逐步向上移动,为更高级别的社区创建摘要。这会产生数据的多层次、相互关联的表示。

Step2:查询阶段

有了知识图谱和社区摘要,GraphRAG 就可以以非凡的深度和准确性回答用户查询。

  • 全局搜索:对于需要广泛了解数据集的查询,例如“讨论的主要主题是什么?”,GraphRAG 会利用生成的社区摘要。这使得系统能够综合整个数据集的信息并提供全面的答案。
  • 本地搜索:当查询集中在特定实体上时,GraphRAG 会利用知识图谱的网络结构。它有效地探索相关实体的邻域,从直接连接的实体和相关声明中收集相关信息,最终提供精确且具有上下文感知的答案。

Step3:提示调整

为了进一步提高准确性并使 GraphRAG 适应特定数据集,提示调整至关重要。这涉及在索引和查询阶段改进 LLM 使用的提示。事实上,通过提供特定领域的示例并调整提示的措辞,用户可以指导 LLM 提取更多相关信息并生成更准确、更有见地的响应。

[4] GraphRAG优势

GraphRAG 相比传统 RAG 具有多项优势:

  • 增强的全面性:通过利用社区摘要,GraphRAG 能够为广泛的查询提供更全面的答案,从而获取数据的整体视图。
  • 提高准确性和相关性:知识图谱使 GraphRAG 能够以更高的准确性和相关性回答需要综合来自多个来源的信息的复杂问题。
  • 有效探索复杂关系:浏览知识图谱的能力允许更细致、更有效地探索数据中的复杂关系。

这些优势使 GraphRAG 适用于广泛的应用:

  • 科学发现:分析研究论文、确定研究差距并揭示不同研究领域之间的隐藏联系。
  • 商业智能:从市场报告、客户评论和内部文件中提取见解,以支持战略决策。
  • 情报分析:将大量情报报告数据集中的点连接起来,以识别潜在威胁并了解复杂情况。
  • 教育与学习:为学生提供强大的工具来探索复杂的主题,理解概念之间的联系,并有效地获取总结的信息。

# 5.3.2 GraphRAG使用实例

[1] 准备实验环境

实验环境:Macbook Pro 2021,M1 pro芯片,16G内存,macOS Ventura14.5系统

Step1:创建运行环境

官方要求版本:Python 3.10-3.12,这里使用 Conda 创建了个 Python 3.11 环境进行实验。

$ conda create -n conda_graphrag_env python=3.11
$ conda activate conda_graphrag_env
1
2

Step2:安装项目依赖

直接安装 graphrag 依赖,安装的东西挺多的,这个需要等待一段时间。

$ pip3 install graphrag
1

注:我在安装过程遇到了一些依赖问题,这里手动安装以下依赖即可安装成功。

$ pip3 install "environs>=11.0.0,<12.0.0"
$ pip3 install msal-extensions
1
2

[2] 构建检索索引

Step1:准备测试数据

执行如下命令下载一段测试文件,放到 ragtest 测试项目里。

$ mkdir -p ./ragtest/input
$ curl https://www.gutenberg.org/cache/epub/24022/pg24022.txt > ./ragtest/input/book.txt
1
2

Step2:初始化项目

切换到 ragtest 目录里,执行如下初始化命令

$ cd ragtest
$ python3 -m graphrag.index --init
1
2

然后它会自动初始化项目,目录结构如下:

.
├── input
│   └── book.txt
├── output
│   └── 20240707-101446
│       └── reports
├── prompts
│   ├── claim_extraction.txt
│   ├── community_report.txt
│   ├── entity_extraction.txt
│   └── summarize_descriptions.txt
├── .env
└── settings.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13

Step3:修改配置信息

之后修改 .env 和 settings.yaml 里的配置,主要是改动 api_base、api_key、model,我改动的地方已经加了中文注释。

注:GraphRAG 会调用非常多次大模型API,需要考虑成本,建议使用较为便宜的模型。除此之外,有的大模型厂商(如DeepSeek)没有Embedding模型。

.env

GRAPHRAG_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
1

settings.yaml

encoding_model: cl100k_base
skip_workflows: []
llm:
  api_key: ${GRAPHRAG_API_KEY}
  type: openai_chat # or azure_openai_chat
  model: gpt-3.5-turbo # 进行了修改,默认值为gpt-4-turbo-preview
  model_supports_json: true # recommended if this is available for your model.
  # max_tokens: 4000
  # request_timeout: 180.0
  api_base: https://xxx.xxx.xxx/v1  # 取消注释并进行了修改,默认值https://<instance>.openai.azure.com
  # api_version: 2024-02-15-preview
  # organization: <organization_id>
  # deployment_name: <azure_model_deployment_name>
  # tokens_per_minute: 150_000 # set a leaky bucket throttle
  # requests_per_minute: 10_000 # set a leaky bucket throttle
  # max_retries: 10
  # max_retry_wait: 10.0
  # sleep_on_rate_limit_recommendation: true # whether to sleep when azure suggests wait-times
  # concurrent_requests: 25 # the number of parallel inflight requests that may be made

parallelization:
  stagger: 0.3
  # num_threads: 50 # the number of threads to use for parallel processing

async_mode: threaded # or asyncio

embeddings:
  ## parallelization: override the global parallelization settings for embeddings
  async_mode: threaded # or asyncio
  llm:
    api_key: ${GRAPHRAG_API_KEY}
    type: openai_embedding # or azure_openai_embedding
    model: text-embedding-3-small
    api_base: https://xxx.xxx.xxx/v1  # 取消注释并进行了修改,默认值https://<instance>.openai.azure.com
    # api_version: 2024-02-15-preview
    # organization: <organization_id>
    # deployment_name: <azure_model_deployment_name>
    # tokens_per_minute: 150_000 # set a leaky bucket throttle
    # requests_per_minute: 10_000 # set a leaky bucket throttle
    # max_retries: 10
    # max_retry_wait: 10.0
    # sleep_on_rate_limit_recommendation: true # whether to sleep when azure suggests wait-times
    # concurrent_requests: 25 # the number of parallel inflight requests that may be made
    # batch_size: 16 # the number of documents to send in a single request
    # batch_max_tokens: 8191 # the maximum number of tokens to send in a single request
    # target: required # or optional

chunks:
  size: 300
  overlap: 100
  group_by_columns: [id] # by default, we don't allow chunks to cross documents

input:
  type: file # or blob
  file_type: text # or csv
  base_dir: "input"
  file_encoding: utf-8
  file_pattern: ".*\\.txt$"

cache:
  type: file # or blob
  base_dir: "cache"
  # connection_string: <azure_blob_storage_connection_string>
  # container_name: <azure_blob_storage_container_name>

storage:
  type: file # or blob
  base_dir: "output/${timestamp}/artifacts"
  # connection_string: <azure_blob_storage_connection_string>
  # container_name: <azure_blob_storage_container_name>

reporting:
  type: file # or console, blob
  base_dir: "output/${timestamp}/reports"
  # connection_string: <azure_blob_storage_connection_string>
  # container_name: <azure_blob_storage_container_name>

entity_extraction:
  ## llm: override the global llm settings for this task
  ## parallelization: override the global parallelization settings for this task
  ## async_mode: override the global async_mode settings for this task
  prompt: "prompts/entity_extraction.txt"
  entity_types: [organization,person,geo,event]
  max_gleanings: 0

summarize_descriptions:
  ## llm: override the global llm settings for this task
  ## parallelization: override the global parallelization settings for this task
  ## async_mode: override the global async_mode settings for this task
  prompt: "prompts/summarize_descriptions.txt"
  max_length: 500

claim_extraction:
  ## llm: override the global llm settings for this task
  ## parallelization: override the global parallelization settings for this task
  ## async_mode: override the global async_mode settings for this task
  # enabled: true
  prompt: "prompts/claim_extraction.txt"
  description: "Any claims or facts that could be relevant to information discovery."
  max_gleanings: 0

community_report:
  ## llm: override the global llm settings for this task
  ## parallelization: override the global parallelization settings for this task
  ## async_mode: override the global async_mode settings for this task
  prompt: "prompts/community_report.txt"
  max_length: 2000
  max_input_length: 8000

cluster_graph:
  max_cluster_size: 10

embed_graph:
  enabled: false # if true, will generate node2vec embeddings for nodes
  # num_walks: 10
  # walk_length: 40
  # window_size: 2
  # iterations: 3
  # random_seed: 597832

umap:
  enabled: false # if true, will generate UMAP embeddings for nodes

snapshots:
  graphml: false
  raw_entities: false
  top_level_nodes: false

local_search:
  # text_unit_prop: 0.5
  # community_prop: 0.1
  # conversation_history_max_turns: 5
  # top_k_mapped_entities: 10
  # top_k_relationships: 10
  # max_tokens: 12000

global_search:
  # max_tokens: 12000
  # data_max_tokens: 12000
  # map_max_tokens: 1000
  # reduce_max_tokens: 2000
  # concurrency: 32
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142

Step4:构建索引文件

修改完配置文件之后,执行如下命令构建索引,需要等待较长时间。

$ python3 -m graphrag.index
1

注:如果开始执行时就报错 ValueError: No text files found in input,可能是因为目录结构不对,因此找不到文件。如果出现其它的报错,可以通过具体log文件./ragtest/output/20240707-101446/reports/indexing-engine.log 来定位问题,基本上就是配置文件哪里写的不对。

索引构建成功如下图所示:

GraphRAG构建索引

[3] 运行查询引擎

1)全局搜索

Baseline RAG 在处理需要跨数据集聚合信息以组成答案的查询时表现糟糕,像“数据中的前5大主题是什么?”这样的查询表现非常差,因为 Baseline RAG 依赖于对数据集内语义相似文本内容的向量搜索,在查询中没有任何东西可以引导它找到正确的信息。

然而,借助GraphRAG,就能够回答这种问题,因为LLM生成的知识图谱的结构告诉我们关于整个数据集的结构(以及主题)。这使得私有数据集能被组织成具有意义的语义群类,这些群类都是预先概括的。使用全局搜索方法,LLM使用这些群组来在回应用户查询时概述这些主题。

GraphRAG全局搜索

以下是使用全局搜索提出高级问题的示例。

$ python3 -m graphrag.query \
--root ./ \
--method global \
"What are the top themes in this story?"
1
2
3
4

运行结果:

GraphRAG全局搜索官方示例

2)本地搜索

本地搜索将知识图谱中的结构化数据与输入文档中的非结构化数据相结合,以便在查询时使用相关实体信息增强 LLM 上下文。它非常适合回答需要了解输入文档中提到的特定实体的问题。

GraphRAG本地搜索

以下是使用本地搜索询问有关特定角色的更具体问题的示例。

$ python3 -m graphrag.query \
--root ./ \
--method local \
"Who is Scrooge, and what are his main relationships?"
1
2
3
4

运行结果:

GraphRAG本地搜索官方示例

# 5.4 ScrapeGraphAI智能采集数据

# 5.4.1 ScrapeGraphAI简介

ScrapeGraphAI 是一个网络爬虫 Python 库,使用大模型通过自然语言来采集数据。

# 5.4.2 ScrapeGraphAI使用实例

依赖安装:

$ pip3 install scrapegraphai
$ playwright install
1
2

测试程序:

# -*- coding: utf-8 -*-

from scrapegraphai.graphs import SmartScraperGraph

graph_config = {
   "llm": {
      "api_key": "sk-xxx",
      "model": "gpt-3.5-turbo",
   },
}
smart_scraper_graph = SmartScraperGraph(
   prompt="获取每个博客的标题、时间、作者",
   source="https://www.eula.club/",
   config=graph_config
)

result = smart_scraper_graph.run()
print(result)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

采集效果:采集单个简单页面的效果还可以,但需要翻页、点进去采集的情况,大模型理解不了。估计还是要分步骤一点点去弄,比如先让它获取到每个的链接,再把每个链接给它让它循环去走,但大模型无法做到固定格式的稳定输出,目前难以实际投入应用。

# 5.5 根据网页截图生成静态页面代码

# 5.5.1 screenshot-to-code简介

一个开源的 Web 应用,用户上传一张网页截图,它会通过 OpenAI API,给出该网页的 HTML/Tailwind/JS 代码实现。

screenshot-to-code

# 5.5.2 安装项目依赖并启动项目

[1] 拉取代码并安装pipx及poetry环境

从项目的Github仓库拉取项目代码。

$ git clone https://github.com/abi/screenshot-to-code.git
1

pipx:类似于 macOS 上的 brew,pipx 依赖 pip 和 venv,它只能在 python 3.6+ 的 Python 版本中才能使用。

poetry:如果想使用pyproject.toml,并通过pyproject.toml进行依赖包管理,目前pip还不支持,所以poetry是首选。

$ brew install pipx
$ pipx --version
$ pipx install poetry
$ pipx ensurepath   // 自动更新环境变量
$ poetry --version  // 需要重新打开终端
1
2
3
4
5

poetry环境

[2] 前后端安装依赖并启动

后端安装依赖并启动:

$ cd backend
$ echo "OPENAI_API_KEY=sk-your-key" > .env
$ poetry install
$ poetry shell
$ poetry run uvicorn main:app --reload --port 7000
1
2
3
4
5

前端安装依赖并启动:

$ cd frontend
$ yarn
$ yarn dev
1
2
3

# 5.5.3 使用项目生成前端代码

使用Chrome浏览器打开 http://localhost:5173 (opens new window) 地址,上传一张网页截图,它就会自动生成前端代码,但不完全一样,稍微复杂点儿的页面就缺胳膊少腿的,可以根据生成效果继续让它迭代调整。

screenshot-to-code生成的页面效果

在Code处可以查看代码,也可以将它下载下来。

screenshot-to-code生成的代码

总体评价:生成的效果还凑合,不过不是完全一致的,少了很多东西,现在只能出雏形,感觉这个东西适合懂点儿前端技术的产品经理或者UI设计快速抄原型用。

# 6. 将ChatGPT接入Telegram Bot

# 6.1 申请自己的 Telegram Bot

# 6.1.1 申请 Telegram Bot

找 BotFather 官方机器人申请自己的 Telegram Bot,需要记录下:BotName、TOKEN、CHATID等信息。

  • Step1:在Telegram中添加BotFather这个账号,然后依次发送/start/newbot,按照提示即可创建一个新的机器人。记下来给你生成的token。
  • Step2:搜索刚刚创建的机器人的名字,并给它发送一条消息。(注意:需要先与机器人之间创建会话,机器人才能下发消息,否则机器人无法主动发送消息)
  • Step3:在Telegram中搜索userinfobot,并给它发送一条消息,它会返回给你chatid,将它也记下来。

# 6.1.2 修改 Telegram Bot 头像

还是找 BotFather 官方机器人,先发送/setuserpic,它会让你选择为哪个Bot修改,选择完之后发送头像图片给它即可。

注:头像以图片的形式发送,不要以文件的形式发送(发送时点上那个压缩图片即为图片的形式发送)

# 6.2 接入Telegram Bot的步骤

# 6.2.1 ChatGPT-Telegram-Workers

将ChatGPT接入Telegram Bot的开源项目有很多,以下是我认为比较好的两个:

以下我采用了Cloudflare Workers免服务器部署ChatGPT-Telegram-Workers项目的方式。需要提前准备好:ChatGPT API密钥、Telegram Bot的Token和CHATID

# 6.2.2 部署Workers

Step1:打开 Cloudflare Workers (opens new window) 注册账号

Step2:点击右上角的 Create a Service

新建Workers

Step3:进入新建的workers, 选择Quick Edit, 将 index.js (opens new window) 代码复制到编辑器中,保存

# 6.2.3 配置环境变量

打开 Cloudflare Workers (opens new window) 点击你的Workers,点击右上角的 Setting -> Variables

  • API_KEY:设置成 OpenAI API Key
  • TELEGRAM_AVAILABLE_TOKENS:设置成 Telegram Bot Token
  • WORKERS_DOMAIN:设置成你的Workers域名,例如workers_name.username.workers.dev, 可以在workers详情页的Preview找到
  • CHAT_WHITE_LIST:设置成允许访问的 telegram bot 的chatid,例如123456789,987654321

配置Cloudflare-Workers环境变量

# 6.2.4 绑定KV数据

Step1:在首页——Workers——KV处,点击右上角的 Create a Namespace, 这里名字随便取。

创建Workers-KV

Step2:打开 Cloudflare Workers (opens new window) 点击你的Workers

  • 点击右上角的 Setting -> Variables
  • KV Namespace Bindings 中点击 Edit variables
  • 点击 Add variable
  • 设置名字为DATABASE,并选择刚刚创建的KV数据

# 6.2.5 初始化并开始聊天

初始化:浏览器打开 https://workers_name.username.workers.dev/init 地址,即可自动绑定telegram的webhook和设定所有指令。

开始聊天:打开我们的 telegram bot,输入/new命令,即可开始聊天。

ChatGPT-Telegram-Workers

# 7. 将ChatGPT接入微信

# 7.1 注册微信小号并实名

# 7.1.1 注册微信小号

由于将ChatGPT接入微信存在封号风险,因此建议注册小号去搞,被封了也不心疼。微信现在允许使用同一个手机号注册微信小号了,可通过如下步骤进行注册:

  • 我的——设置——切换账号——添加账号——注册新账号——通过当前微信的手机号辅助注册

注意:已绑定手机号、已开通微信支付、注册时间超过一定时长且当前帐号无安全风险的微信帐号可以辅助注册一个新微信帐号。如果不符合条件,可以找付费接码平台去注册微信小号。

# 7.1.2 微信小号实名

需要将微信小号进行实名认证,才可以将ChatGPT接入微信,可通过如下步骤进行实名认证:

  • 我的——服务——钱包——实名认证(需要绑定银行卡)

注意:如果未进行实名认证,后续扫码登录时,会报错[wechat_channel.py:135] - 'wxsid',无法使用。

另注:微信小号实名后,会送一个满10.01-10的微信支付红包,可以用来日常消费也可以充值Apple Store、Q币这种硬通货。

# 7.2 接入微信的步骤

# 7.2.1 chatgpt-on-wechat

基于大模型搭建的微信聊天机器人,同时支持微信、企业微信、公众号、飞书、钉钉接入,可选择GPT3.5/GPT4.0/Claude/文心一言/讯飞星火/通义千问/Gemini/GLM-4/LinkAI,能处理文本、语音和图片,访问操作系统和互联网,支持基于自有知识库进行定制企业智能客服。

支持多种方式进行部署,这里我采用最方便的Docker方式。需要提前准备好:Docker和Docker Compose环境、ChatGPT API密钥、微信小号

# 7.2.2 使用Docker进行部署

首先,需要下载 docker-compose.yml 文件

$ wget https://open-1317903499.cos.ap-guangzhou.myqcloud.com/docker-compose.yml
1

其原始内容如下:

version: '2.0'
services:
  chatgpt-on-wechat:
    image: zhayujie/chatgpt-on-wechat
    container_name: chatgpt-on-wechat
    security_opt:
      - seccomp:unconfined
    environment:
      OPEN_AI_API_KEY: 'YOUR API KEY'
      MODEL: 'gpt-3.5-turbo'
      PROXY: ''
      SINGLE_CHAT_PREFIX: '["bot", "@bot"]'
      SINGLE_CHAT_REPLY_PREFIX: '"[bot] "'
      GROUP_CHAT_PREFIX: '["@bot"]'
      GROUP_NAME_WHITE_LIST: '["ChatGPT测试群", "ChatGPT测试群2"]'
      IMAGE_CREATE_PREFIX: '["画", "看", "找"]'
      CONVERSATION_MAX_TOKENS: 1000
      SPEECH_RECOGNITION: 'False'
      CHARACTER_DESC: '你是ChatGPT, 一个由OpenAI训练的大型语言模型, 你旨在回答并解决人们的任何问题,并且可以使用多种语言与人交流。'
      EXPIRES_IN_SECONDS: 3600
      USE_GLOBAL_PLUGIN_CONFIG: 'True'
      USE_LINKAI: 'False'
      LINKAI_API_KEY: ''
      LINKAI_APP_CODE: ''
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

配置说明详见代码的config.json:

{
  "open_ai_api_key": "YOUR API KEY",                          # 填入 OpenAI API KEY
  "model": "gpt-3.5-turbo",                                   # 模型名称
  "proxy": "",                                                # 代理客户端的ip和端口,国内环境开启代理的需要填写该项,如 "127.0.0.1:7890"
  "single_chat_prefix": ["bot", "@bot"],                      # 私聊时文本需要包含该前缀才能触发机器人回复
  "single_chat_reply_prefix": "[bot] ",                       # 私聊时自动回复的前缀,用于区分真人
  "group_chat_prefix": ["@bot"],                              # 群聊时包含该前缀则会触发机器人回复
  "group_name_white_list": ["ChatGPT测试群", "ChatGPT测试群2"], # 开启自动回复的群名称列表
  "group_chat_in_one_session": ["ChatGPT测试群"],              # 支持会话上下文共享的群名称  
  "image_create_prefix": ["画", "看", "找"],                   # 开启图片回复的前缀
  "conversation_max_tokens": 1000,                            # 支持上下文记忆的最多字符数
  "speech_recognition": false,                                # 是否开启语音识别
  "group_speech_recognition": false,                          # 是否开启群组语音识别
  "use_azure_chatgpt": false,                                 # 是否使用Azure ChatGPT service代替openai ChatGPT service
  "azure_deployment_id": "",                                  # 采用Azure ChatGPT时,模型部署名称
  "azure_api_version": "",                                    # 采用Azure ChatGPT时,API版本
  "character_desc": "你是ChatGPT, 一个由OpenAI训练的大型语言模型, 你旨在回答并解决人们的任何问题,并且可以使用多种语言与人交流。",  # 人格描述
  "subscribe_msg": "感谢您的关注!\n这里是ChatGPT,可以自由对话。\n支持语音对话。\n支持图片输出,画字开头的消息将按要求创作图片。\n支持角色扮演和文字冒险等丰富插件。\n输入{trigger_prefix}#help 查看详细指令。",      # 订阅消息,公众号和企业微信channel中请填写,当被订阅时会自动回复
  "use_linkai": false,                                        # 是否使用LinkAI接口,默认关闭,开启后可国内访问,使用知识库和MJ
  "linkai_api_key": "",                                       # LinkAI Api Key
  "linkai_app_code": ""                                       # LinkAI 应用code
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

然后根据自己的需要,修改里面的配置:

version: '2.0'
services:
  chatgpt-on-wechat:
    image: zhayujie/chatgpt-on-wechat
    container_name: chatgpt-on-wechat
    security_opt:
      - seccomp:unconfined
    environment:
      OPEN_AI_API_BASE: 'https://api.gptapi.us/v1'
      OPEN_AI_API_KEY: 'sk-xxx'
      MODEL: 'gpt-4-1106-preview'
      PROXY: ''
      SINGLE_CHAT_PREFIX: '["bot"]'
      SINGLE_CHAT_REPLY_PREFIX: '"bot"'
      GROUP_CHAT_PREFIX: '["bot"]'
      GROUP_NAME_WHITE_LIST: '["GPT智能问答群", "GPT智能问答群1", "GPT智能问答群2", "GPT智能问答群3"]'
      IMAGE_CREATE_PREFIX: '["image"]'
      CONVERSATION_MAX_TOKENS: 4000
      SPEECH_RECOGNITION: 'False'
      CHARACTER_DESC: '你是ChatGPT, 一个由OpenAI训练的大型语言模型, 你旨在回答并解决人们的任何问题,并且可以使用多种语言与人交流。'
      EXPIRES_IN_SECONDS: 3600
      USE_GLOBAL_PLUGIN_CONFIG: 'True'
      USE_LINKAI: 'False'
      LINKAI_API_KEY: ''
      LINKAI_APP_CODE: ''
    restart: always
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

注意:可以添加 OPEN_AI_API_BASE 配置项,使其可以使用国产的第三方API地址。另外GROUP_NAME_WHITE_LIST里的群聊名称不能有&这种特殊字符。

然后将其构建成Docker容器,查看日志并扫码登录。

$ docker compose up -d
$ docker logs -f chatgpt-on-wechat
1
2

chatgpt-on-wechat扫码登录

注意:这个微信号不能退出,切换账号也不行,退出后机器人就不好用了,我这里使用了闲置手机登录的微信小号。

# 7.2.3 在微信里开始聊天

可以加这个微信小号私聊,或者将其拉入指定群聊,通过指定的标识位让其进行问题回答,我这里统一设置为bot。

chatgpt-on-wechat使用效果

成功请求到了问题时,可以在后台看到日志。

chatgpt-on-wechat后台日志

# 8. 使用API微调OpenAI大模型

# 8.1 OpenAI支持大模型微调

# 8.1.1 基本介绍

2023 年 8 月 22 日,OpenAI宣布支持对ChatGPT大模型进行微调了。OpenAI的基座模型本身已经非常出色,通过微调,即使样本数量很少也可以获得良好效果,使得其他模型难以竞争。然而,数据安全性的问题仍未解决,用户数据最终会流向OpenAI,这对于安全性要求高的用户来说是个问题,因此训练本地私有化模型仍然有必要。

微调不仅可以提供更高质量的结果,还可以训练更多范例、节省Token和降低请求延迟。GPT模型通过prompt有效使用,而微调则进一步改善了少样本学习,实现了更好的结果。微调的过程包括准备和上传训练数据、训练新的微调模型和使用微调模型,从而节约成本并实现低延迟请求。

ChatGPT大模型支持微调的介绍

# 8.1.2 哪些模型可以进行微调

目前可以对三个型号进行微调,包括gpt-3.5-turbo-0613(推荐)、babbage-002和davinci-002。

  • 其中,gpt-3.5-turbo被期望成为大多数用户在结果和易用性方面的正确模型,除非用户需要迁移旧的微调模型。
  • 未来也将支持对 GPT-4 进行微调,预计该功能将于今年晚些时候推出。

目前哪些ChatGPT大模型支持微调

# 8.1.3 什么情况需要微调

微调GPT模型可以使它们更适合特定应用,但需要谨慎投入时间和精力。官方推荐首先尝试通过prompt工程、prompt链(将复杂任务分解为多个提示)和函数调用来获得良好的结果,主要原因包括:

  • 有许多任务,模型最初可能表现不佳,但通过更好的提示可以取得更好的效果,可能不需要微调。
  • 与微调相比,通过提示和其他策略进行迭代的反馈循环要快得多,微调需要创建数据集和运行训练任务。
  • 在仍需要微调的情况下,最初的提示工程工作并未浪费 —— 通常在微调数据中使用良好的提示(或结合提示链/工具使用和微调)时会看到最佳结果。
  • GPT最佳实践指南 (opens new window) 提供了一些在不进行微调的情况下获得更好性能的最有效策略和技巧的背景知识。在playground中快速迭代提示可能会有所帮助。

ChatGPT大模型什么时候适合微调

# 8.1.4 微调可以处理哪些问题

通过微调可以处理以下场景:

  • 设定风格、语气、格式或其他质量方面的要求
  • 提高产生所需输出的可靠性
  • 纠正无法遵循复杂提示的错误
  • 以特定方式处理许多边缘情况
  • 执行难以用prompt准确表达的新技能或任务

通过微调,可以使用更短的prompt来完成任务,从而减少token数,降低时延与成本。此外,由于GPT-4的效果优越但成本较高,可以使用GPT-4的结果作为训练数据来微调ChatGPT,进一步实现GPT-4的蒸馏。

ChatGPT大模型微调可以解决哪些问题

# 8.2 准备微调数据集

确定微调是合适的解决方案后,需要为训练模型准备数据。应创建一组多样化的示例对话,与使用时模型响应的对话相似。

# 8.2.1 数据格式

微调gpt-3.5-turbo需要准备的数据格式如下:

{
    "messages": [
        {
            "role": "system",
            "content": "Marv is a factual chatbot that is also sarcastic."
        },
        {
            "role": "user",
            "content": "What's the capital of France?"
        },
        {
            "role": "assistant",
            "content": "Paris, as if everyone doesn't know that already."
        }
    ]
}{
    "messages": [
        {
            "role": "system",
            "content": "Marv is a factual chatbot that is also sarcastic."
        },
        {
            "role": "user",
            "content": "Who wrote 'Romeo and Juliet'?"
        },
        {
            "role": "assistant",
            "content": "Oh, just some guy named William Shakespeare. Ever heard of him?"
        }
    ]
}{
    "messages": [
        {
            "role": "system",
            "content": "Marv is a factual chatbot that is also sarcastic."
        },
        {
            "role": "user",
            "content": "How far is the Moon from Earth?"
        },
        {
            "role": "assistant",
            "content": "Around 384,400 kilometers. Give or take a few, like that really matters."
        }
    ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

babbage-002和davinci-002需要准备的数据格式如下:

{
    "prompt": "<prompt text>",
    "completion": "<ideal generated text>"
}{
    "prompt": "<prompt text>",
    "completion": "<ideal generated text>"
}{
    "prompt": "<prompt text>",
    "completion": "<ideal generated text>"
}
1
2
3
4
5
6
7
8
9
10

# 8.2.2 构建指令

构建指令最关键的就是要确保训练数据是最好的,最满足自己当前需求的;要把这部分高质量的数据尽可能都加到训练集中,尤其是自己手头准备的训练样本并不是很多的时候,这样可以尽最大可能让训练出的模型满足自己的需求。

另外就是前面说的如果你想通过缩短prompt来减少inference成本,那么模型就需要更多的训练样本去学习到被“省略”部分的指令。

这里举个例子:假设当前在做一个根据小说内容生成一句评论的场景,目前的要求有(1)评论必须有搞笑风格(2)评论必须带有表情符号(3)评论字数必须在10-20个字(4)评论必须要口语化一些。那么我们调用GPT4的时候prompt就可以这么写:

假设你是一个小说评论生成器,生成的评论必须满足以下要求:
(1)评论必须有搞笑风格
(2)评论必须带有表情符号
(3)评论字数必须在10-20个字
(4)评论必须要口语化一些
小说内容如下:
****
1
2
3
4
5
6
7

通过使用这个指令调用GPT4我们可以很好的得到满足自己需求的response(假设调用ChatGPT不能满足需求),这样我们就有了训练样本啦,然后用这里的样本去微调ChatGPT就可以蒸馏了GPT4啦。

同时看到当前的场景每个样本开头都有这一段相似的指令:

假设你是一个小说评论生成器,生成的评论必须满足以下要求:
(1)评论必须有搞笑风格
(2)评论必须带有表情符号
(3)评论字数必须在10-20个字
(4)评论必须要口语化一些
1
2
3
4
5

如果我们在微调的时候把这段去掉同样可以达到需求的话,那么在inference的时候就可以大大减少token数了(尤其是当指令非常多的时候),极端下我们在微调的时候训练样本的prompt就只是小说内容,response还是GPT4的回复。但是这种情况就需要准备更多的样本,让模型去学习到那些被忽略的隐式“指令”。

# 8.2.3 需要多少条训练样本

最少需要10条训练样本。OpenAI观察通常使用50-100条样本来微调后,ChatGPT就可以发生明显的变化。

官方建议精心准备50条样本来微调,然后观察模型是否显示出改善的迹象。一般来说可能已经能用了,但即使模型还不满足需求,如果看到已经明显改变的迹象了,那剩下的就是沿着当前准备数据的思路提供更多数据即可。如果没有看到改变的迹象,这可能意味着需要重新考虑如何为模型设置任务或重新构建指令数据。

ChatGPT微调需要多少训练样本

# 8.2.4 划分训练集与测试集

官方要求划分训练和测试集,这样在微调过程中其会帮统计一些指标变化,方便辅助看训练的效果。

# 8.2.5 Token限制

每条训练 Token 总数最大为4096,也就是说需要确保构建的样本最好在4000以内,如果超出会被自动截断。

可以使用该官方脚本来计算样本的 Token 数。How to count tokens with tiktoken (opens new window)

计算Token限制的官方脚本

# 8.2.6 微调成本

估算微调成本,详见官方的定价页面:https://openai.com/pricing (opens new window)

ChatGPT微调成本

# 8.2.7 检查数据集格式

在准备完数据后,最后可以再检查一下数据格式是否正确,以免启动训练的时候发生错误,为此官方也给了一个检查脚本,可以使用它来查找潜在错误、检查Token计数并估计微调作业的成本。

# We start by importing the required packages

import json
import tiktoken
import numpy as np
from collections import defaultdict

# Next, we specify the data path and open the JSONL file

data_path = "<YOUR_JSON_FILE_HERE>"

# Load dataset
with open(data_path) as f:
    dataset = [json.loads(line) for line in f]

# We can inspect the data quickly by checking the number of examples and the first item

# Initial dataset stats
print("Num examples:", len(dataset))
print("First example:")
for message in dataset[0]["messages"]:
    print(message)

# Now that we have a sense of the data, we need to go through all the different examples and check to make sure the formatting is correct and matches the Chat completions message structure

# Format error checks
format_errors = defaultdict(int)

for ex in dataset:
    if not isinstance(ex, dict):
        format_errors["data_type"] += 1
        continue

    messages = ex.get("messages", None)
    if not messages:
        format_errors["missing_messages_list"] += 1
        continue

    for message in messages:
        if "role" not in message or "content" not in message:
            format_errors["message_missing_key"] += 1

        if any(k not in ("role", "content", "name") for k in message):
            format_errors["message_unrecognized_key"] += 1

        if message.get("role", None) not in ("system", "user", "assistant"):
            format_errors["unrecognized_role"] += 1

        content = message.get("content", None)
        if not content or not isinstance(content, str):
            format_errors["missing_content"] += 1

    if not any(message.get("role", None) == "assistant" for message in messages):
        format_errors["example_missing_assistant_message"] += 1

if format_errors:
    print("Found errors:")
    for k, v in format_errors.items():
        print(f"{k}: {v}")
else:
    print("No errors found")

# Beyond the structure of the message, we also need to ensure that the length does not exceed the 4096 token limit.

# Token counting functions
encoding = tiktoken.get_encoding("cl100k_base")

# not exact!
# simplified from https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb
def num_tokens_from_messages(messages, tokens_per_message=3, tokens_per_name=1):
    num_tokens = 0
    for message in messages:
        num_tokens += tokens_per_message
        for key, value in message.items():
            num_tokens += len(encoding.encode(value))
            if key == "name":
                num_tokens += tokens_per_name
    num_tokens += 3
    return num_tokens

def num_assistant_tokens_from_messages(messages):
    num_tokens = 0
    for message in messages:
        if message["role"] == "assistant":
            num_tokens += len(encoding.encode(message["content"]))
    return num_tokens

def print_distribution(values, name):
    print(f"\n#### Distribution of {name}:")
    print(f"min / max: {min(values)}, {max(values)}")
    print(f"mean / median: {np.mean(values)}, {np.median(values)}")
    print(f"p5 / p95: {np.quantile(values, 0.1)}, {np.quantile(values, 0.9)}")

# Last, we can look at the results of the different formatting operations before proceeding with creating a fine-tuning job:

# Warnings and tokens counts
n_missing_system = 0
n_missing_user = 0
n_messages = []
convo_lens = []
assistant_message_lens = []

for ex in dataset:
    messages = ex["messages"]
    if not any(message["role"] == "system" for message in messages):
        n_missing_system += 1
    if not any(message["role"] == "user" for message in messages):
        n_missing_user += 1
    n_messages.append(len(messages))
    convo_lens.append(num_tokens_from_messages(messages))
    assistant_message_lens.append(num_assistant_tokens_from_messages(messages))

print("Num examples missing system message:", n_missing_system)
print("Num examples missing user message:", n_missing_user)
print_distribution(n_messages, "num_messages_per_example")
print_distribution(convo_lens, "num_total_tokens_per_example")
print_distribution(assistant_message_lens, "num_assistant_tokens_per_example")
n_too_long = sum(l > 4096 for l in convo_lens)
print(f"\n{n_too_long} examples may be over the 4096 token limit, they will be truncated during fine-tuning")

# Pricing and default n_epochs estimate
MAX_TOKENS_PER_EXAMPLE = 4096

MIN_TARGET_EXAMPLES = 100
MAX_TARGET_EXAMPLES = 25000
TARGET_EPOCHS = 3
MIN_EPOCHS = 1
MAX_EPOCHS = 25

n_epochs = TARGET_EPOCHS
n_train_examples = len(dataset)
if n_train_examples * TARGET_EPOCHS < MIN_TARGET_EXAMPLES:
    n_epochs = min(MAX_EPOCHS, MIN_TARGET_EXAMPLES // n_train_examples)
elif n_train_examples * TARGET_EPOCHS > MAX_TARGET_EXAMPLES:
    n_epochs = max(MIN_EPOCHS, MAX_TARGET_EXAMPLES // n_train_examples)

n_billing_tokens_in_dataset = sum(min(MAX_TOKENS_PER_EXAMPLE, length) for length in convo_lens)
print(f"Dataset has ~{n_billing_tokens_in_dataset} tokens that will be charged for during training")
print(f"By default, you'll train for {n_epochs} epochs on this dataset")
print(f"By default, you'll be charged for ~{n_epochs * n_billing_tokens_in_dataset} tokens")
print("See pricing page to estimate total costs")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141

验证数据后,需要上传文件才能用于微调作业:

openai.File.create(
  file=open("mydata.jsonl", "rb"),
  purpose='fine-tune'
)
1
2
3
4

# 8.3 创建及使用微调模型

# 8.3.1 创建微调模型

确保数据集的数量和结构正确并上传文件后,下一步是创建微调作业。

使用 OpenAI SDK 开始微调工作:

import os
import openai
openai.api_key = "OPENAI_API_KEY"
openai.FineTuningJob.create(training_file="file-abc123", model="gpt-3.5-turbo")
1
2
3
4

在开启微调后,任务就排队开始训练了,通常需要等几分钟或者几个小时训练,等完成训练后,用户就会收到一份确认邮件了。

除了创建微调作业外,还可以列出现有作业、检索作业状态或取消作业。

# List 10 fine-tuning jobs
openai.FineTuningJob.list(limit=10)

# Retrieve the state of a fine-tune
openai.FineTuningJob.retrieve("ft-abc123")

# Cancel a job
openai.FineTuningJob.cancel("ft-abc123")

# List up to 10 events from a fine-tuning job
openai.FineTuningJob.list_events(id="ft-abc123", limit=10)

# Delete a fine-tuned model (must be an owner of the org the model was created in)
import openai
openai.Model.delete("ft-abc123")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 8.3.2 使用微调模型

训练结束后,就可以通过查看任务细节看到模型名字“fine_tuned_model”,之后就可以调用使用了。如果请求错误,可以再等一会,因为其可能在正在加载。

import os
import openai
openai.api_key = "OPENAI_API_KEY"

completion = openai.ChatCompletion.create(
  model="ft:gpt-3.5-turbo:my-org:custom_suffix:id",
  messages=[
    {"role": "system", "content": "You are a helpful assistant."},
    {"role": "user", "content": "Hello!"}
  ]
)

print(completion.choices[0].message)
1
2
3
4
5
6
7
8
9
10
11
12
13

# 8.3.3 分析微调模型

官方会提供一些常规的量化指标,比如:training loss、training token accuracy、test loss、test token accuracy等。

另外直接看生成的case可能更直观,可以对比原版的ChatGPT和微调后的ChatGPT的效果。

[1] 迭代训练数据质量

如果模型结果不尽人意,那么首先可以检查当前的训练数据质量是否过关。

  • 收集当前的badcase,然后针对性的加对应的数据来纠正模型
  • 如果模型在语法、风格等方面不满足需要,那需要查看当前的训练样本中是否就已经包含了对应的脏数据,需要剔除或者修正。
  • 平衡数据:假设训练数据中有60%是"I cannot answer this",但是inference的时候只有5%是这种情况,那么需要平衡好这个比例。
  • 确保模型包含所有信息,比如有个需求是希望模型根据用户的个人特征来赞美他们,而训练数据中包含了与之前对话中未提及的特征相关的赞美,那么模型可能会学会产生虚构的信息即幻觉。
  • 确保高的一致性:如果当前需求的训练集是多个人协同创建的,那么模型的性能可能会受到多人之间的一致性和一致性水平的限制。例如,在一个文本提取任务中,如果多人只在提取的片段上达成了70%的一致,那么模型可能天花板就是70%。
  • 训练集确保和官方要求的格式一样。

[2] 迭代训练数据数量

当确保了当前训练集质量后,就可以考虑增加训练集的数量来更进一步提高性能。增加量级通常有助于模型更好地学习任务,特别是处理一些边缘case。预计每当增加一倍量级时,都会有类似客观的改进程度。可以通过以下方式粗略估计通过增加训练数据大小所带来的预期效果:

  • Step1:在当前数据集上进行微调
  • Step2:在当前数据集的一半上进行微调
  • Step3:观察两者之间的质量差距

一般来说,如果必须进行数据量权衡,较少数量的高质量数据通常比大量低质量数据更有效。

[3] 迭代训练超参

在第一次试水训练的时候,官方不建议指定epoch,而是由官方根据样本量大小设置一个默认值,然后训练完看效果后再决定怎么调整epoch:

  • 当需求是偏单一场景的时候(比如分类、实体抽取等等),可以适当尝试增加1-2个epoch
  • 当需求是偏多任务场景的时候或者说发现模型缺乏多样性的时候可以尝试减少1-2个epoch

# 8.4 ChatGPT大模型微调示例

如下示例代码,我已经在Github上开源了,项目地址:https://github.com/Logistic98/chatgpt-fine-tuning (opens new window)

# 8.4.1 准备ChatGPT-KEY的付费账号

前提条件:ChatGPT大模型微调需要 OpenAI 的 API-KEY,而且要求这个账号必须是付费过的,新账号的白嫖额度是不能用的(第三方购买的很多廉价账号都是机器批量注册的新账号,都是不能用的),会在创建微调时报错。这是必须的条件,没有就不用往下看了。

OpenAI不允许未付费账号进行ChatGPT大模型微调

ChatGPT KEY国内无法直充,我这里是通过欧易充值的USDT,经过一些币种转换,再通过Depay信用卡充值的ChatGPT KEY,充值过程极为繁琐。

# 8.4.2 准备并上传微调数据集

[1] 制作微调数据集

以法律方向的微调为例,我这里只是为了走通流程,偷个懒就不自己制作微调数据集了。

原始数据集从 ChatLaw (opens new window) 项目中进行下载,通过如下脚本,将其转换成 gpt-3.5-turbo 微调所需的数据格式。

make_dataset.py

# -*- coding: utf-8 -*-

import json

# 从demo_data_法律咨询.jsonl文件中读取数据
# 来源:https://github.com/PKU-YuanGroup/ChatLaw/blob/main/data/demo_data_%E6%B3%95%E5%BE%8B%E5%92%A8%E8%AF%A2.jsonl
data = []
with open('./data/demo_data_法律咨询.jsonl', 'r', encoding='utf-8') as file:
    for line in file:
        data.append(json.loads(line))

# 转换格式变成 gpt-3.5-turbo 微调所需的数据格式
formatted_data = []
for entry in data:
    meta_instruction = entry["meta_instruction"].replace("你一个名叫ChatLAW,由北京大学团队开发的人工智能助理:", "你一个人工智能法律助理:")
    messages = []
    messages.append({
        "role": "system",
        "content": meta_instruction
    })
    for chat in entry["chat"]:
        messages.append({
            "role": "user",
            "content": chat["咨询者"]
        })
        messages.append({
            "role": "assistant",
            "content": chat["ChatLAW"]
        })
    formatted_data.append({
        "messages": messages
    })

# 将结果写入到fine_tuning.jsonl文件中
with open('./data/fine_tuning.jsonl', 'w', encoding='utf-8') as file:
    for item in formatted_data:
        file.write(json.dumps(item, ensure_ascii=False))
        file.write('\n')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

[2] 检查微调数据集格式

制作完数据集后,使用官方脚本(我这里把注释和print内容翻译成中文了)校验一下数据集格式是否符合要求。

check_dataset.py

# -*- coding: utf-8 -*-

import json
import tiktoken
import numpy as np
from collections import defaultdict

# 指定数据路径并打开JSONL文件
data_path = "./data/fine_tuning.jsonl"

# 加载数据集
with open(data_path) as f:
    dataset = [json.loads(line) for line in f]

# 通过检查示例数量和第一项来快速查看数据
print("示例数量:", len(dataset))
print("第一个示例:")
for message in dataset[0]["messages"]:
    print(message)

# 我们需要遍历所有不同的示例,确保格式正确,并符合Chat completions消息结构
format_errors = defaultdict(int)

for ex in dataset:
    if not isinstance(ex, dict):
        format_errors["data_type"] += 1
        continue

    messages = ex.get("messages", None)
    if not messages:
        format_errors["missing_messages_list"] += 1
        continue

    for message in messages:
        if "role" not in message or "content" not in message:
            format_errors["message_missing_key"] += 1

        if any(k not in ("role", "content", "name") for k in message):
            format_errors["message_unrecognized_key"] += 1

        if message.get("role", None) not in ("system", "user", "assistant"):
            format_errors["unrecognized_role"] += 1

        content = message.get("content", None)
        if not content or not isinstance(content, str):
            format_errors["missing_content"] += 1

    if not any(message.get("role", None) == "assistant" for message in messages):
        format_errors["example_missing_assistant_message"] += 1

if format_errors:
    print("发现错误:")
    for k, v in format_errors.items():
        print(f"{k}: {v}")
else:
    print("未发现错误")

# 除了消息的结构,我们还需要确保长度不超过4096个令牌限制

# 计数令牌功能
encoding = tiktoken.get_encoding("cl100k_base")


# 不精确!简化自https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb
def num_tokens_from_messages(messages, tokens_per_message=3, tokens_per_name=1):
    num_tokens = 0
    for message in messages:
        num_tokens += tokens_per_message
        for key, value in message.items():
            num_tokens += len(encoding.encode(value))
            if key == "name":
                num_tokens += tokens_per_name
    num_tokens += 3
    return num_tokens


def num_assistant_tokens_from_messages(messages):
    num_tokens = 0
    for message in messages:
        if message["role"] == "assistant":
            num_tokens += len(encoding.encode(message["content"]))
    return num_tokens


def print_distribution(values, name):
    print(f"\n#### {name}的分布:")
    print(f"最小值 / 最大值: {min(values)}, {max(values)}")
    print(f"平均值 / 中位数: {np.mean(values)}, {np.median(values)}")
    print(f"p5 / p95: {np.quantile(values, 0.1)}, {np.quantile(values, 0.9)}")

# 最后,我们可以在创建微调作业之前查看不同格式操作的结果:

# 警告和令牌计数
n_missing_system = 0
n_missing_user = 0
n_messages = []
convo_lens = []
assistant_message_lens = []

for ex in dataset:
    messages = ex["messages"]
    if not any(message["role"] == "system" for message in messages):
        n_missing_system += 1
    if not any(message["role"] == "user" for message in messages):
        n_missing_user += 1
    n_messages.append(len(messages))
    convo_lens.append(num_tokens_from_messages(messages))
    assistant_message_lens.append(num_assistant_tokens_from_messages(messages))

print("缺少系统消息的示例数量:", n_missing_system)
print("缺少用户消息的示例数量:", n_missing_user)
print_distribution(n_messages, "每个示例的消息数量")
print_distribution(convo_lens, "每个示例的总令牌数量")
print_distribution(assistant_message_lens, "每个示例的助理令牌数量")
n_too_long = sum(l > 4096 for l in convo_lens)
print(f"\n{n_too_long}个示例可能超过4096个令牌限制,微调期间将被截断")

# 定价和默认n_epochs估计
MAX_TOKENS_PER_EXAMPLE = 4096
MIN_TARGET_EXAMPLES = 100
MAX_TARGET_EXAMPLES = 25000
TARGET_EPOCHS = 3
MIN_EPOCHS = 1
MAX_EPOCHS = 25

n_epochs = TARGET_EPOCHS
n_train_examples = len(dataset)
if n_train_examples * TARGET_EPOCHS < MIN_TARGET_EXAMPLES:
    n_epochs = min(MAX_EPOCHS, MIN_TARGET_EXAMPLES // n_train_examples)
elif n_train_examples * TARGET_EPOCHS > MAX_TARGET_EXAMPLES:
    n_epochs = max(MIN_EPOCHS, MAX_TARGET_EXAMPLES // n_train_examples)
n_billing_tokens_in_dataset = sum(min(MAX_TOKENS_PER_EXAMPLE, length) for length in convo_lens)

print(f"数据集包含约{n_billing_tokens_in_dataset}个将在训练期间收费的令牌")
print(f"默认情况下,您将对此数据集进行{n_epochs}个时代的训练")
print(f"默认情况下,您将为约{n_epochs * n_billing_tokens_in_dataset}个令牌收费")
print("请参阅定价页面以估算总成本")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137

数据集是符合要求的,输出内容如下:

检查ChatGPT微调数据集格式

[3] 上传微调数据集

填写你的 OpenAI API-KEY,上传微调数据集,这里需要记录下 training_file.id,下面的微调任务会用到。

upload_dataset.py

# -*- coding: utf-8 -*-

import openai
openai.api_key = "your_openai_api_key"

# 上传训练数据集
training_file = openai.File.create(
file=open("./data/fine_tuning.jsonl", "rb"),
purpose="fine-tune"
)

# file.id要复制下来,下一步开始微调要用
print(training_file.id)
1
2
3
4
5
6
7
8
9
10
11
12
13

# 8.4.3 创建微调任务并完成微调

[1] 创建微调任务

上传完微调数据集之后,就可以创建微调任务了。首先执行如下命令更新一下 openai 包,旧版没有FineTuningJob功能。

$ pip3 install --upgrade openai
1

之后填写 OpenAI API-KEY 及上一步得到的 training_file.id,开始微调训练。

# -*- coding: utf-8 -*-

import openai
openai.api_key = "your_openai_api_key"

# 创建微调模型
openai.FineTuningJob.create(training_file="your_training_file_id", model="gpt-3.5-turbo")
1
2
3
4
5
6
7

[2] 微调过程中查看状态

微调过程中,可以查看作业列表、作业状态、作业事件等信息,并可以随时取消作业。

get_fine_tuning_status.py

# -*- coding: utf-8 -*-

import openai
openai.api_key = "your_openai_api_key"

print("===列出10个微调作业")
print(openai.FineTuningJob.list(limit=10))

print("===检索微调作业的状态")
print(openai.FineTuningJob.retrieve("your_ftjob_id"))

print("===列出最多10个来自微调作业的事件")
print(openai.FineTuningJob.list_events(id="your_ftjob_id", limit=10))

# print("===取消作业")
# print(openai.FineTuningJob.cancel("your_ftjob_id"))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

部分输出内容如下:

===检索微调作业的状态
{
  "object": "fine_tuning.job",
  "id": "ftjob-5XsithSRiJ6mvf24IP9xq7eW",
  "model": "gpt-3.5-turbo-0613",
  "created_at": 1692941148,
  "finished_at": 1692941721,
  "fine_tuned_model": "ft:gpt-3.5-turbo-0613:personal::7rJlWrzp",
  "organization_id": "org-sE6KS2sIIgrV8cmzJYQCkfDA",
  "result_files": [
    "file-x0qLLS90VDNV3Xk3EEhA3iFB"
  ],
  "status": "succeeded",
  "validation_file": null,
  "training_file": "file-LX7MRoSwB7je9yuC4FydIgV5",
  "hyperparameters": {
    "n_epochs": 5
  },
  "trained_tokens": 26100
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

微调训练完成后,OpenAI官方会给你发送邮件通知:

ChatGPT微调完成的邮件通知

# 8.4.4 使用微调模型

[1] 在OpenAI Playground上试用

点开邮件通知的 OpenAI Playground 链接,在 USER 处输入问题,点击 Submit 按钮提交,在线预览微调效果。

在OpenAI-Playground上试用

[2] 使用API在代码里应用

model 可以通过上文“检查微调作业的状态”的输出里获取 fine_tuned_model,也可以从 OpenAI Playground 链接的路径里获取。

use_fine_tuning_model.py

# -*- coding: utf-8 -*-

import openai
openai.api_key = "your_openai_api_key"

completion = openai.ChatCompletion.create(
  model="ft:gpt-3.5-turbo:my-org:custom_suffix:id",
  messages=[
    {"role": "user", "content": "如果有人擅自破坏水库闸门,但没有造成重大损失,是否构成决水罪?"}
  ]
)

print(completion.choices[0].message)
1
2
3
4
5
6
7
8
9
10
11
12
13

运行结果:

{
  "role": "assistant",
  "content": "根据《中华人民共和国刑法》第一百一十一条的规定,故意破坏水利设施,罪行轻微的,处三年以下有期徒刑、拘役或者管制。具体来说,破坏水库闸门案件中,如果被告人故意破坏水库闸门,但是没有造成重大损失,属于罪行轻微的情形,构成故意破坏水利设施罪。"
}
1
2
3
4

# 9. 参考资料

[1] 如何选择充值/提现网络?from 欧易官网 (opens new window)

[2] Depay卡的注册和使用完整指南 from Kerry的学习笔记 (opens new window)

[3] ChatGPT官方API可以抢先体验了 from CSDN (opens new window)

[4] 如何创建ChatGPT API密钥?操作路径和教程 from 云东方 (opens new window)

[5] OpenAI API 速率限制 from OpenAI官网 (opens new window)

[6] 中科院学术专用版 ChatGPT,开源了 from 知乎 (opens new window)

[7] Cloudflare Worker 代理请求 ChatGPT API,支持 Stream 流式输出 from Github (opens new window)

[8] 编写我自己的 ChatGPT 代码解释器 from Rick Lamers' blog (opens new window)

[9] 调研:开源版gpt code interpreter from CSDN (opens new window)

[10] Open Interpreter:OpenAI Code Interpreter的开源实现|本地化|可联网 from 知乎 (opens new window)

[11] cocopilot-gpt: 将GitHub Copilot转为API使用 from CaoYunzhou (opens new window)

[12] 微信正式支持“小号” 全量开放辅助账号注册功能 from 证券时报 (opens new window)

[13] 有功能限制风险,请谨慎使用 from Github issues (opens new window)

[14] 微信实名认证在哪里改? from 搜狐 (opens new window)

[15] KeyError: 'wxsid' from Github issues (opens new window)

[16] privateGPT不支持中文吗 from Github issues (opens new window)

[17] Welcome to GraphRAG from GraphRAG官方文档 (opens new window)

[18] GraphRAG: Unlocking LLM discovery on narrative private data from GraphRAG官方博客 (opens new window)

[19] 微软开源的GraphRAG爆火,Github Star量破万,生成式AI进入知识图谱时代? from 知乎 (opens new window)

[20] GraphRAG:很强,但贵得离谱 from 微信公众号 (opens new window)

[21] GraphRAG:微软发布史上最强的RAG知识库开源方案 from Bilibili (opens new window)

[22] 微软开源GraphRAG的使用教程-使用自定义数据测试GraphRAG from CSDN (opens new window)

[23] GraphRAG: Responsible AI FAQ from Github (opens new window)

[24] 微软近日开源了新一代RAG框架GraphRAG from 53AI (opens new window)

[25] GraphRAG快速指南 from BimAnt (opens new window)

[26] 大模型重复输出怎么办?from CSDN (opens new window)

[27] 大模型推理参数 from 姳楽的开发分享 (opens new window)

[28] 为什么大模型API的output token比input token贵?from AI TNT (opens new window)

[29] 大模型output token为什么比input token贵?from 知乎 (opens new window)

[30] OpenAI官方支持ChatGPT进行微调了 from 吃果冻不吐果冻皮 (opens new window)

[31] Fine-tuning from OpenAI官方文档 (opens new window)

[32] GPT best practices from OpenAI官方文档 (opens new window)

[33] 付费定价页面 from OpenAI官方文档 (opens new window)

[34] 固定Prompt测试LLM应用程序效果 from GIthub (opens new window)

Last Updated: 4/12/2025, 10:53:42 PM