likes
comments
collection
share

惊! GPT 竟能正确回答这个问题!

作者站长头像
站长
· 阅读数 3

通过 JavaScript 和 OpenAI Embeddings API, 让 GPT 正确回答 TA 原本回答不了的问题.

背景

有局限的训练数据

众所周知, GPT 的模型是基于一定的时间周期内的海量数据训练出来的, 例如 gpt-3.5-turbo 的训练数据截至2021年9月, 因此对于该时间之后发生的事情和训练数据中未涉及的领域, 模型是无法正确回答的.

  • 例如: 训练数据之后发生的事情

    你问 gpt-3.5-turbo "2022年冬奥会单板滑雪的冠军是谁?"

    TA 会回答你"很抱歉,我无法预测未来的结果。您可以关注比赛的进展和结果。"

  • 例如: 训练数据中未涉及的领域

    你问 gpt-3.5-turbo "你知道天鹅到家吗?"

    TA 会回答你"抱歉,我不太明白你的问题。你能提供更多的上下文或信息吗?"

JavaScript 相关实现较少

关于如何让 GPT 正确回答这些问题, 目前 Python 相关的实现还是比较多的, 但 JavaScript 相关由浅入深的实现还是比较少的, 因此接下来基于前端的技术栈让 GPT 正确回答这些问题.

目标

让 GPT 能够正确回答"你知道天鹅到家吗?"

原理

GPT 之所以能够回答你提出的问题, 关键在于

  • 要么 GPT 学过相关信息(海量训练数据), 所以 GPT 能够回答出来
  • 要么你告诉 GPT 相关信息, 所以 GPT 能够回答出来

就此对应让 GPT 学习新知识的两种方式

  • 模型权重

    例如通过训练私有化数据 fine-tune 出一个专有模型

    好比长期记忆, 你 fine-tune 模型, 就像为了一周后的考试而学习一样. 当考试时, 模型可能会忘了细节或者记错

  • 模型输入

    例如将知识信息作为上下文输入给模型

    就像短期记忆, 当考试时, 模型就像拿着小抄去参加考试,更有可能得出正确答案

我们的场景是要让 GPT 基于事实依据来回答问题, 相比之下, 通过模型输入来让 GPT 学习新知识的方式更适合.

模型输入示例

使用模型输入的方式让 GPT 正确回答"你知道天鹅到家吗?"

即通过提示工程, 将天鹅到家的相关信息作为上下文告诉模型, 模型就能够正确回答这个问题了.

接下来我们看看代码如何实现

const {
    Configuration,
    OpenAIApi,
} = require("openai");

const configuration = new Configuration({
    // 替换成你的 KEY
    apiKey: "{OPENAI_API_KEY}",
});
const openai = new OpenAIApi(configuration);

(async function() {
    async function createChatCompletion() {
        const prompt = `
请根据如下信息回答问题, 如果无法从中找到答案, 请回答"不知道"
"""
天鹅到家于2014年成立,隶属于到家集团,于2020年9月由“58到家”正式更名为“天鹅到家”。
作为互联网家庭服务平台,天鹅到家致力于推进家庭服务的产业标准化、数字化、职业化进程,通过赋能家政劳动者,为中国家庭提供包括保洁、做饭小时工等即时交易服务以及保姆、月嫂的劳动者招聘服务,实现“帮助千万人就业 为亿万家庭服务”,成为一个美好公司、美好平台,让家更美好。
截至2022年6月30日,天鹅到家平台累计拥有超过1800万注册用户,服务超过480万用户,有超过200万注册和认证劳动者。
"""

问题: 你知道天鹅到家吗?
回答:
`;

        const response = await openai.createChatCompletion({
            model: "gpt-3.5-turbo",
            messages: [
                {
                    role: "system",
                    content: "You are a helpful assistant."
                },
                {
                    role: "user",
                    content: prompt
                },
            ],
            temperature: 0,
        });

        console.log(response.data);
        console.log(response.data.choices[0].message.content);
    }
    await createChatCompletion();
})();

执行结果

是的,天鹅到家是一家互联网家庭服务平台,隶属于到家集团,成立于2014年,于2020年9月更名为“天鹅到家”。

由此可见通过提供上下文信息, GPT 正确的回答了 TA 原本回答不了的问题.

语义搜索

让 GPT 学习新知道的原理, 我们算是知道了, 但示例中我们是人为提供了问题相关的上下文信息给 GPT.

那么如何基于问题动态的给 GPT 提供上下文信息呢?

这需要用到语义搜索, 找到与问题相关的信息

Semantic search is a search technique that uses natural language processing to understand the meaning of the query and returns results that are semantically related to the query

那么如何判断问题与其他信息的相关性呢?

OpenAI embedding is an API that will convert text into a numerical vector representation that can be used for semantic search.

通过 OpenAI Embeddings API 可以将文字转成矢量数字, 矢量之间的距离就代表着他们之间的相关性.

An embedding is a vector (list) of floating point numbers. The distance between two vectors measures their relatedness. Small distances suggest high relatedness and large distances suggest low relatedness.

那么我们先将信息通过 Embeddings API 转成矢量(a1, a2...)形成知识库, 再将问题也通过 Embeddings API 转成矢量(q), 然后逐个计算问题的矢量(q)与所有信息的矢量(a1, a2...)的相似度, 最终找出与问题相似度最高的信息, 即我们要提供给 GPT 的上下文信息.

关于矢量相似度的计算, 可以使用余弦相似度(cosine similarity)算法.

上述流程可以理解为

               信息(文字)
                  |          ---------------
                  ↓          │ 信息(vector) |
            Embeddings API --│              | --计算相关性--> 相关性最高的信息(文字)
                  ↑          │ 问题(vector) |                       |
                  |          ---------------                        |
问题(文字) --------                                                  ↓
                                                              模型输入(提示工程重组问题附带上下文)
                                                                    |
                                                              Completion API
                                                                    |

                                                                 回答(文字)

实现

关键步骤

  1. 创建矢量(建立知识库)
  2. 语义搜索(匹配相似度)
  3. 模型输入(补充上下文)
  4. 模型输出(上下文作答)
const {
    Configuration,
    OpenAIApi,
} = require("openai");

const configuration = new Configuration({
    apiKey: "{OPENAI_API_KEY}",
});
const openai = new OpenAIApi(configuration);

async function createEmbedding(input) {
    const response = await openai.createEmbedding({
        model: "text-embedding-ada-002",
        input,
    });
    return response.data.data[0].embedding;
}

async function createChatCompletion(prompt) {
    const response = await openai.createChatCompletion({
        model: "gpt-3.5-turbo",
        messages: [
            {
                role: "system",
                content: "You are a helpful assistant."
            },
            {
                role: "user",
                content: prompt
            },
        ],
        temperature: 0,
    });

    console.log(prompt);
    console.log("------------------");
    console.log(response.data);
    console.log(response.data.choices[0].message.content);

    return response.data.choices[0].message.content;
}

const knowledgeBase = [];
async function insertIntoKnowledgeBase(text) {
    const vector = await createEmbedding(text);
    knowledgeBase.push({
        text,
        vector,
    });
}
/**
 * 创建知识库(这个过程一般只需要一次, 通过矢量数据库来存储, 内容有更新的时候才需要再做)
 */
async function createKnowledgeBase() {
    await insertIntoKnowledgeBase(`
天鹅到家于2014年成立,隶属于到家集团,于2020年9月由“58到家”正式更名为“天鹅到家”。
作为互联网家庭服务平台,天鹅到家致力于推进家庭服务的产业标准化、数字化、职业化进程,通过赋能家政劳动者,为中国家庭提供包括保洁、做饭小时工等即时交易服务以及保姆、月嫂的劳动者招聘服务,实现“帮助千万人就业 为亿万家庭服务”,成为一个美好公司、美好平台,让家更美好。
截至2022年6月30日,天鹅到家平台累计拥有超过1800万注册用户,服务超过480万用户,有超过200万注册和认证劳动者。
    `);
    await insertIntoKnowledgeBase(`
快狗打车(GOGOX)作为亚洲领先的同城物流平台,成立于2014年,其前身为58速运,隶属于到家集团。旗下以香港和海外市场的“GOGOX”和中国大陆的“快狗打车”两个值得信赖的品牌服务于个人、中小企业和大型企业。快狗打车致力于为用户随时随地提供拉货、搬家、运东西的同城货运服务,以全流程闭环的线上交易平台和海量社会化运力为基础,致力于高效满足各类用户的不同类型同城货运需求。
截止2021年4月30日,快狗打车已在亚洲五个国家及地区的340多个城市开展业务,平台已有约450万名注册司机,注册用户约2480万,累计服务企业33000+家。凭借互联网+大数据运营系统,为货品出行市场提出智能化的物流解决方案,通过智能运力调配,整合货运供需信息,快速匹配车辆,推动着短途货品运送及交易服务的标准化升级。
    `);
}

/**
 * 计算余弦相似度
 */
function cosineSimilarity(a, b) {
    let dotProduct = 0;
    let normA = 0;
    let normB = 0;
    for (let i = 0; i < a.length; i++) {
      dotProduct += a[i] * b[i];
      normA += a[i] * a[i];
      normB += b[i] * b[i];
    }
    normA = Math.sqrt(normA);
    normB = Math.sqrt(normB);
    return dotProduct / (normA * normB);
}

/**
 * 语义搜索(从知识库中找到与问题相关性最高的内容)
 */
async function semanticSearch(question) {
    const questionVector = await createEmbedding(question);
    const _knowledgeBase = knowledgeBase.map((item) => {
        return {
            ...item,
            // 计算相似度
            relatedness: cosineSimilarity(questionVector, item.vector),
        };
    }).sort((a, b) => {
        return b.relatedness - a.relatedness;
    });

    return _knowledgeBase[0].text;
}

async function answer(question) {
    const context = await semanticSearch(question);
    const output = await createChatCompletion(`
请根据如下信息回答问题, 如果无法从中找到答案, 请回答"不知道"
"""
${context}
"""

问题: ${question}
回答:
    `);

    return output;
}

(async function() {
    await createKnowledgeBase();
    // 是的,天鹅到家是一家互联网家庭服务平台,隶属于到家集团,提供保洁、做饭小时工等即时交易服务以及保姆、月嫂的劳动者招聘服务。截至2022年6月30日,天鹅到家平台累计拥有超过1800万注册用户,服务超过480万用户,有超过200万注册和认证劳动者。
    await answer("你知道天鹅到家吗?");
    // 快狗打车平台已有约450万名注册司机。
    await answer("快狗有多少司机?");
    // 不知道。
    await answer("快狗打车上市了吗?");
})();

更多细节

原理和实现我们都了解了, 但如果真正要投入使用, 还有很多细节需要处理

  • Collect

    准备高质量的知识库

  • Chunk

    知识库可能比较大, 需要拆分成较小的独立块, 以便于 embedding, 因为 embedding 支持的 token 是有限的

    如何合理拆分, 需要在实际业务中考量

  • Store

    矢量数据应该存储在矢量数据库中

  • Search

    匹配相似度以后, 相似度的阈值应该是多少, TOP 多少的结果应该做为上下文信息

    这些也需要在实际业务中考量

要处理这些细节推荐使用 LangChain, 因为其提供了很多通用的组件, 例如

  • Document Loaders
  • Text Splitters
  • Vector Stores

参考