自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

RAG應用在得物開放平臺的智能答疑的探索

發(fā)布于 2025-1-10 13:10
瀏覽
0收藏

一、背景

得物開放平臺是一個把得物能力進行開放,同時提供給開發(fā)者提供 公告、應用控制臺、權限包申請、業(yè)務文檔等功能的平臺。

  1. 面向商家:通過接入商家自研系統(tǒng)??梢詫崿F(xiàn)自動化庫存、訂單、對賬等管理。
  2. 面向ISV :接入得物開放平臺,能為其產(chǎn)品提供更完善的全平臺支持。
  3. 面向內(nèi)部應用:提供安全、可控的、快速支持的跨主體通訊。

得物開放平臺目前提供了一系列的文檔以及工具去輔助開發(fā)者在實際調(diào)用API之前進行基礎的引導和查詢。

但目前的文檔搜索功能僅可以按照接口路徑,接口名稱去搜索,至于涉及到實際開發(fā)中遇到的接口前置檢查,部分字段描述不清等實際問題,且由于信息的離散性,用戶想要獲得一個問題的答案需要在多個頁面來回檢索,造成用戶焦慮,進而增大TS的答疑可能性。

RAG應用在得物開放平臺的智能答疑的探索-AI.x社區(qū)

隨著這幾年AI大模型的發(fā)展,針對離散信息進行聚合分析且精準回答的能力變成了可能。而RAG應用的出現(xiàn),解決了基礎問答類AI應用容易產(chǎn)生幻覺現(xiàn)象的問題,達到了可以解決實際應用內(nèi)問題的目標。

二、簡介

什么是RAG

RAG(檢索增強生成)指Retrieval Augmented Generation。

這是一種通過從外部來源獲取知識來提高生成性人工智能模型準確性和可靠性的技術。通過RAG,用戶實際上可以與任何數(shù)據(jù)存儲庫進行對話,這種對話可視為“開卷考試”,即讓大模型在回答問題之前先檢索相關信息。

RAG應用的可落地場景

RAG應用的根本是依賴一份可靠的外部數(shù)據(jù),根據(jù)提問檢索并交給大模型回答,任何基于可靠外部數(shù)據(jù)的場景均是RAG的發(fā)力點。

RAG應用的主要組成部分

  • 外部知識庫:問題對應的相關領域知識,該知識庫的質(zhì)量將直接影響最終回答的效果。
  • Embedding模型:用于將外部文檔和用戶的提問轉(zhuǎn)換成Embedding向量。
  • 向量數(shù)據(jù)庫:將外部信息轉(zhuǎn)化為Embedding向量后進行存儲。
  • 檢索器:該組件負責從向量數(shù)據(jù)庫中識別最相關的信息。檢索器將用戶問題轉(zhuǎn)換為Embedding向量后執(zhí)行相似性檢索,以找到與用戶查詢相關的Top-K文檔(最相似的K個文檔)。
  • 生成器(大語言模型LLM):一旦檢索到相關文檔,生成器將用戶查詢和檢索到的文檔結(jié)合起來,生成連貫且相關的響應。
  • 提示詞工程(Prompt Engineering):這項技術用于將用戶的問題與檢索到的上下文有效組合,形成大模型的輸入。

RAG應用的核心流程

以下為一個標準RAG應用的基礎流程:

  1. 將查詢轉(zhuǎn)換為向量
  2. 在文檔集合中進行語義搜索
  3. 將檢索到的文檔傳遞給大語言模型生成答案
  4. 從生成的文本中提取最終答案

RAG應用在得物開放平臺的智能答疑的探索-AI.x社區(qū)

但在實際生產(chǎn)中,為了確保系統(tǒng)的全面性、準確性以及處理效率,還有許多因素需要加以考慮和處理。

下面我將基于答疑助手在開放平臺的落地,具體介紹每個步驟的詳細流程。

三、實現(xiàn)目標

鑒于目前得物開放平臺的人工答疑數(shù)量相對較高,用戶在開放平臺查詢未果就會直接進入到人工答疑階段。正如上文所說,RAG擅長依賴一份可靠的知識庫作出相應回答,構(gòu)建一個基于開放平臺文檔知識庫的RAG應用再合適不過,同時可以一定程度降低用戶對于人工答疑的依賴性,做到問題前置解決。

RAG應用在得物開放平臺的智能答疑的探索-AI.x社區(qū)


四、整體流程

RAG應用在得物開放平臺的智能答疑的探索-AI.x社區(qū)


技術選型

準確性思考

問答的準確性會直接反饋到用戶的使用體驗,當一個問題的回答是不準確的,會導致用戶根據(jù)不準確的信息進一步犯錯,導致人工客服介入,耐心喪失直至投訴。


所以在實際構(gòu)建基于開放平臺文檔的答疑助手之前,首先考慮到的是問答的準確性,主要包括以下2點:

  1. 首要解決答疑助手針對非開放平臺提問的屏蔽
  2. 尋找可能導致答非所問的時機以及相應的解決方案

屏蔽非相關問題

為了屏蔽AI在回答時可能會回答一些非平臺相關問題,我們首先要做的是讓AI明確我們的目標(即問答上下文),且告訴他什么樣的問題可以回答,什么問題不可以回答。

在這一點上,常用的手段為告知其什么是開放平臺以及其負責的范疇。

例如:得物的開放平臺是一個包含著 API 文檔,解決方案文檔的平臺,商家可以通過這個平臺獲取到得物的各種接口,以及解決方案,幫助商家更好的使用得物的服務。現(xiàn)在需要做一個智能答疑助手,你是其中的一部分。

在這一段描述中,我們告知了答疑助手,開放平臺包含著API文檔,包含著解決方案,同時包含接口信息,同時會有商家等之類的字眼。大模型在收到這段上下文后,將會對其基礎回答進行判斷。

同時,我們可以通過讓答疑助手二選一的方式進行回答,即平臺相關問題與非平臺相關問題。我們可以讓大模型返回特定的數(shù)據(jù)枚舉,且限定枚舉范圍,例如:開放平臺通用問題、開放平臺API答疑問題,未知問題。

借助Json類型的輸出 + JSON Schema,我們可通過Prompt描述來限定其返回,從而在進入實際問答前做到事前屏蔽。

尋找可能導致答非所問的時機

當問題被收攏到開放平臺這個主題之后,剩余的部分就是將用戶提問與上下文進行結(jié)合,再交由大模型回答處理。在這過程中,可能存在的答非所問的時機有:不夠明確的Prompt說明、上下文信息過于碎片化以及上下文信息的連接性不足三種。

  • 不夠明確的Prompt說明:Prompt本身描述缺少限定條件,導致大模型回答輕易超出我們給予的要求,從而導致答非所問。
  • 上下文信息過于碎片化:上下文信息可能被分割成N多份,這個N值過大或者過小,都會導致單個信息過大導致缺乏聯(lián)想性、單個信息過小導致回答時不夠聚焦。
  • 上下文信息連接性不夠:若信息之間被隨意切割,且缺少相關元數(shù)據(jù)連接,交給大模型的上下文將會是喪失實際意義的文本片段,導致無法提取出有用信息,從而答非所問。

為了解決以上問題,在設計初期,開放平臺答疑助手設定了以下策略來前置解決準確性問題:

  • 用戶提問的結(jié)構(gòu)化
  • 向量的分割界限以及元信息處理
  • CO-STAR Prompt結(jié)構(gòu)
  • 相似性搜索的K值探索

用戶提問結(jié)構(gòu)化

目標:通過大模型將用戶提問的結(jié)構(gòu)化,將用戶提問分類并提取出精確的內(nèi)容,便于提前引導、終止以及提取相關信息。

RAG應用在得物開放平臺的智能答疑的探索-AI.x社區(qū)


例如,用戶提問今天天氣怎么樣,結(jié)構(gòu)化Runnable會將用戶問題進行初次判斷。


一個相對簡單的Prompt實現(xiàn)如下:

# CONTEXT
得物的開放平臺是一個包含著 API 文檔,解決方案文檔的平臺,商家可以通過這個平臺獲取到得物的各種接口,以及解決方案,幫助商家更好的使用得物的服務?,F(xiàn)在需要做一個智能答疑助手,你是其中的一部分。


# OBJECTIVE
你現(xiàn)在扮演一名客服。請將每個客戶問題分類到固定的類別中。
你只接受有關開放平臺接口的相關問答,不接受其余任何問題。
具體的類別我會在提供給你的JSON Schema中進行說明。


# STYLE


你需要把你的回答以特定的 JSON 格式返回


# TONE


你給我的內(nèi)容里,只能包含特定 JSON 結(jié)構(gòu)的數(shù)據(jù),不可以返回給我任何額外的信息。


# AUDIENCE


你的回答是給機器看的,所以不需要考慮任何人類的感受。


# RESPONSE


你返回的數(shù)據(jù)結(jié)構(gòu)必須符合我提供的 JSON Schema 規(guī)范,我給你的 Schema 將會使用\`<json-schema></json-schema>\`標簽包裹.
每個字段的描述,都是你推算出該字段值的依據(jù),請仔細閱讀。


<json-schema>
  {schema}
</json-schema>


Json Schema的結(jié)構(gòu)通過zod描述如下:

const zApiCallMeta = z
  .object({
    type: z
      .enum(['api_call', 'unknown', 'general'])
      .describe('當前問題的二級類目, api_call為API調(diào)用類問題,unknown為非開放平臺相關問題, general為通用類開放平臺問題'),
    apiName: z
      .string()
      .describe(
        '接口的名稱。接口名稱為中文,若用戶未給出明確的API中文名稱,不要隨意推測,將當前字段置為空字符串',
      ),
    apiUrl: z.string().describe('接口的具體路徑, 一般以/開頭'),
    requestParam: z.unknown().default({}).describe('接口的請求參數(shù)'),
    response: z
      .object({})
      .or(z.null())
      .default({})
      .describe('接口的返回值,若未提供則返回null'),
    error: z
      .object({
        traceId: z.string(),
      })
      .optional()
      .describe('接口調(diào)用的錯誤信息,若接口調(diào)用失敗,則提取traceId并返回'),
  })
  .describe('當二級類目為api_call時,使用這個數(shù)據(jù)結(jié)構(gòu)');


以上結(jié)構(gòu),將會對用戶的問題輸入進行結(jié)構(gòu)化解析。同時給出相應JSON數(shù)據(jù)結(jié)構(gòu)。


將以上結(jié)構(gòu)化信息結(jié)合,可實現(xiàn)一個基于LangChain.js的結(jié)構(gòu)化Runnable,在代碼結(jié)構(gòu)設計上,所有的Runnable將會使用$作為變量前綴,用于區(qū)分Runnable與普通函數(shù)。

import { ChatOpenAI } from '@langchain/openai';
import { StringOutputParser } from '@langchain/core/output_parsers';
import { RunnableSequence, RunnableMap } from '@langchain/core/runnables';
import { $getPrompt } from './$prompt';
import { zSchema, StructuredInputType } from './schema';
import { n } from 'src/utils/llm/gen-runnable-name';
import { getLLMConfig } from 'src/utils/llm/get-llm-config';
import { getStringifiedJsonSchema } from 'src/utils/llm/get-stringified-json-schema';


const b = n('$structured-input');


const $getStructuredInput = () => {
  const $model = new ChatOpenAI(getLLMConfig().ChatOpenAIConfig).bind({
    response_format: {
      type: 'json_object',
    },
  });


  const $input = RunnableMap.from<{ question: string }>({
    schema: () => getStringifiedJsonSchema(zSchema),
    question: (input) => input.question,
  }).bind({ runName: b('map') });


  const $prompt = $getPrompt();
  const $parser = new StringOutputParser();


  return RunnableSequence.from<{ question: string }, string>([
    $input.bind({ runName: b('map') }),
    $prompt.bind({ runName: b('prompt') }),
    $model,
    $parser.bind({ runName: b('parser') }),
  ]).bind({
    runName: b('chain'),
  });
};


export { $getStructuredInput, type StructuredInputType };


鑒于CO-STAR以及JSONSchema的提供的解析穩(wěn)定性,此Runnable甚至具備了可單測的能力。

import dotenv from 'dotenv';
dotenv.config();
import { describe, expect, it } from 'vitest';
import { zSchema } from '../runnables/$structured-input/schema';
import { $getStructuredInput } from '../runnables/$structured-input';


const call = async (question: string) => {
  return zSchema.safeParse(
    JSON.parse(await $getStructuredInput().invoke({ question })),
  );
};


describe('The LLM should accept user input as string, and output as structured data', () => {
  it('should return correct type', { timeout: 10 * 10000 }, async () => {
    const r1 = await call('今天天氣怎么樣');
    expect(r1.data?.type).toBe('unknown');
    const r2 = await call('1 + 1');
    expect(r2.data?.type).toBe('unknown');
    const r3 = await call('trace: 1231231231231231313');
    expect(r3.data?.type).toBe('api_call');
    const r4 = await call('快遞面單提示錯誤');
    expect(r4.data?.type).toBe('api_call');
    const r5 = await call('發(fā)貨接口是哪個');
    expect(r5.data?.type).toBe('api_call');
    const r6 = await call('怎么發(fā)貨');
    expect(r6.data?.type).toBe('general');
    const r7 = await call('獲取商品詳情');
    expect(r7.data?.type).toBe('api_call');
    const r8 = await call('dop/api/v1/invoice/cancel_pick_up');
    expect(r8.data?.type).toBe('api_call');
    const r9 = await call('開票處理');
    expect(r9.data?.type).toBe('api_call');
    const r10 = await call('權限包');
    expect(r10.data?.type).toBe('api_call');
  });

數(shù)據(jù)預處理與向量庫的準備工作

RAG應用的知識庫準備是實施過程中的關鍵環(huán)節(jié),涉及多個步驟和技術。以下是知識庫準備的主要過程:

  1. 知識庫選擇:【全面性與質(zhì)量】數(shù)據(jù)源的信息準確性在RAG應用中最為重要,基于錯誤的信息將無法獲得正確的回答。
  2. 知識庫收集:【多類目數(shù)據(jù)】數(shù)據(jù)收集通常涉及從多個來源提取信息,包括不同的渠道,不同的格式等。如何確保數(shù)據(jù)最終可以形成統(tǒng)一的結(jié)構(gòu)并被統(tǒng)一消費至關重要。
  3. 數(shù)據(jù)清理:【降低額外干擾】原始數(shù)據(jù)往往包含不相關的信息或重復內(nèi)容。
  4. 知識庫分割:【降低成本與噪音】將文檔內(nèi)容進行分塊,以便更好地進行向量化處理。每個文本塊應適當大小,并加以關聯(lián),以確保在檢索時能夠提供準確的信息,同時避免生成噪聲。
  5. 向量化存儲:【Embedding生成】使用Embedding模型將文本塊轉(zhuǎn)換為向量表示,這些向量隨后被存儲在向量數(shù)據(jù)庫中,以支持快速檢索。
  6. 檢索接口構(gòu)建:【提高信息準確性】構(gòu)建檢索模塊,使其能夠根據(jù)用戶查詢從向量數(shù)據(jù)庫中檢索相關文檔。

知識庫拆分

知識庫文檔的拆分顆粒度(Split Chunk Size) 是影響RAG應用準確性的重要指標:

  • 拆分顆粒度過大可能導致檢索到的文本塊包含大量不相關信息,從而降低檢索的準確性。
  • 拆分顆粒度過小則可能導致必要的上下文信息丟失,使得生成的回答缺乏連貫性和深度。
  • 在實際應用中,需要不斷進行實驗以確定最佳分塊大小。通常情況下,128字節(jié)大小的分塊是一個合適的分割大小。
  • 同時還要考慮LLM的輸入長度帶來的成本問題。


下圖為得物開放平臺【開票取消預約上門取件】接口的接口文檔:

RAG應用在得物開放平臺的智能答疑的探索-AI.x社區(qū)

開票取消預約上門取件接口信息

拆分邏輯分析(根據(jù)理論提供128字節(jié)大?。?/p>

在成功獲取到對應文本數(shù)據(jù)后,我們需要在數(shù)據(jù)的預處理階段,將文檔根據(jù)分類進行切分。這一步將會將一份文檔拆分為多份文檔。

由上圖中信息可見,一個文檔的基礎結(jié)構(gòu)是由一級、二級標題進行分割分類的。一個基本的接口信息包括:基礎信息、請求地址、公共參數(shù)、請求入?yún)ⅰ⒄埱蟪鰠?、返回參?shù)以及錯誤碼信息組成。

拆分方式

拆分的實現(xiàn)一般有2種,一是根據(jù)固定的文檔大小進行拆分(128字節(jié))二是根據(jù)實際文檔結(jié)構(gòu)自己做原子化拆分。

直接根據(jù)文檔大小拆分的優(yōu)點當然是文檔的拆分處理邏輯會直接且簡單粗暴,缺點就是因為是完全根據(jù)字節(jié)數(shù)進行分割,一段完整的句子或者段落會被拆分成2半從而丟失語義(但可通過頁碼進行鏈接解決)。

根據(jù)文檔做結(jié)構(gòu)化拆分的優(yōu)點是上下文結(jié)構(gòu)容易連接,單個原子文檔依舊具備語義化,檢索時可以有效提取到信息,缺點是拆分邏輯復雜具備定制性,拆分邏輯難以與其他知識庫復用,且多個文檔之間缺乏一定的關聯(lián)性(但可通過元信息關聯(lián)解決)。

在得物開放平臺的場景中,因為文檔數(shù)據(jù)大多以json為主(例如api表格中每個字段的名稱、默認值、描述等),將這些json根據(jù)大小做暴力切分丟失了絕大部分的語義,難以讓LLM理解。所以,我們選擇了第二種拆分方式。

拆分實現(xiàn)

在文檔分割層面,Markdown作為一種LLM可識別且可承載文檔元信息的文本格式,作為向量數(shù)據(jù)的基礎元子單位最為合適。

RAG應用在得物開放平臺的智能答疑的探索-AI.x社區(qū)

基礎的文檔單元根據(jù)大標題進行文檔分割,同時提供frontmatter作為多個向量之間連接的媒介。

正文層面,開放平臺的API文檔很適合使用Markdown Table來做內(nèi)容承接,且Table對于大模型更便于理解。

根據(jù)以上這種結(jié)構(gòu),我們可得到以下拆分流程:

RAG應用在得物開放平臺的智能答疑的探索-AI.x社區(qū)


代碼實現(xiàn):

const hbsTemplate = `
---
服務ID (serviceId): {{ service.id }}
接口ID (apiId): {{ apiId }}
接口名稱 (apiName): {{ apiName }}
接口地址 (apiUrl): {{ apiUrl }}
頁面地址 (pageUrl): {{ pageUrl }}
---


# {{ title }}


{{ paragraph }}
`;
export const processIntoEmbeddings = (data: CombinedApiDoc) => {
  const template = baseTemplate(data);


  const texts = [
    template(requestHeader(data)),
    template(requestUrl(data)),
    template(publicRequestParam(data)),
    template(requestParam(data)),
    template(responseParam(data)),
    template(errorCodes(data)),
    template(authPackage(data)),
  ].filter(Boolean) as string[][];


  return flattenDeep(texts).map((content) => {
    return new Document<MetaData>({
      // id: toString(data.apiId!),
      metadata: {
        serviceId: data.service.id,
        apiId: data.apiId!,
        apiName: data.apiName!,
        apiUrl: data.apiUrl!,
        pageUrl: data.pageUrl!,
      },
      pageContent: content!,
    });
  });
};


知識庫導入

通過建立定時任務(DJOB),使用MILVUS sdk將以上拆分后的文檔導入對應數(shù)據(jù)集中。

CO-STAR結(jié)構(gòu)

在上文中的Prompt,使用了一種名為CO-STAR的結(jié)構(gòu)化模板,該框架由新加坡政府科技局的數(shù)據(jù)科學與AI團隊創(chuàng)立。CO-STAR框架是一種用于設計Prompt的結(jié)構(gòu)化模板,旨在提高大型語言模型(LLM)響應的相關性和有效性,考慮了多種影響LLM輸出的關鍵因素。

結(jié)構(gòu):

  • 上下文(Context):
    提供與任務相關的背景信息,幫助LLM理解討論的具體場景,確保其響應具有相關性。
  • 目標(Objective):
    明確你希望LLM執(zhí)行的具體任務。清晰的目標有助于模型聚焦于完成特定的請求,從而提高輸出的準確性。
  • 風格(Style):
    指定希望LLM采用的寫作風格。這可以是某位名人的風格或特定職業(yè)專家的表達方式,甚至要求LLM不返回任何語氣相關文字,確保輸出符合要求。
  • 語氣(Tone):
    設定返回的情感或態(tài)度,例如正式、幽默或友善。這一部分確保模型輸出在情感上與用戶期望相符。
  • 受眾(Audience):
    確定響應的目標受眾。根據(jù)受眾的不同背景和知識水平調(diào)整LLM的輸出,使其更加適合特定人群。
  • 響應(Response):
    規(guī)定輸出格式,以確保LLM生成符合后續(xù)使用需求的數(shù)據(jù)格式,如列表、JSON或?qū)I(yè)報告等。這有助于在實際應用中更好地處理LLM的輸出。


在上文結(jié)構(gòu)化的實現(xiàn)中,演示了如何使用CO-STAR結(jié)構(gòu)的Prompt,要求大模型“冰冷的”對用戶提問進行的解析,當然CO-STAR也適用于直接面向用戶的問答,例如:

## Context
我是一名正在尋找酒店信息的旅行者,計劃在即將到來的假期前往某個城市。我希望了解關于酒店的設施、價格和預訂流程等信息。


## Objective
請?zhí)峁┪宜璧木频晷畔?,包括房間類型、價格范圍、可用設施以及如何進行預訂。


## Style
請以簡潔明了的方式回答,確保信息易于理解。


## Tone
使用友好和熱情的語氣,給人一種歡迎的感覺。


## Audience
目標受眾是普通旅行者,他們可能對酒店行業(yè)不太熟悉。


## Response
請以列表形式呈現(xiàn)每個酒店的信息,包括名稱、地址、房間類型、價格和聯(lián)系方式。每個酒店的信息應簡短且直接,便于快速瀏覽。

相似性搜索

當我們使用了問題結(jié)構(gòu)化Runnable后,非開放平臺類問題將會提前終止,告知用戶無法解答相關問題,其他有效回答將會進入相似性搜索環(huán)節(jié)。

相似性搜索基于數(shù)據(jù)之間的相似性度量,通過計算數(shù)據(jù)項之間的相似度來實現(xiàn)檢索。在答疑助手的相似性實現(xiàn)是通過余弦相似度來進行相似性判斷的。

我們將用戶的提問,與向量數(shù)據(jù)庫中數(shù)據(jù)進行余弦相似度匹配。取K為5獲取最相似的五條記錄。

注意:此K值是經(jīng)過一系列的推斷最終決定的,可根據(jù)實際情況調(diào)整。

import { Milvus } from '@langchain/community/vectorstores/milvus';
import { OpenAIEmbeddings } from '@langchain/openai';
import { RunnableSequence } from '@langchain/core/runnables';
import { getLLMConfig } from 'src/utils/llm/get-llm-config';


export const $getContext = async () => {
  const embeddings = new OpenAIEmbeddings(
    getLLMConfig().OpenAIEmbeddingsConfig,
  );


  const vectorStore = await Milvus.fromExistingCollection(embeddings, {
    collectionName: 'open_rag',
  });


  return RunnableSequence.from([
    (input) => {
      return input.question;
    },
    vectorStore.asRetriever(5),
  ]);
};


此Runnable會將搜索結(jié)果組成一大段可參考數(shù)據(jù)集,用于后續(xù)用戶提問。


用戶提問解答


用戶提問的解答同樣通過Runnable的方式來承接,通過用戶提問、結(jié)構(gòu)化數(shù)據(jù)、提取的相似性上下文進行結(jié)合,最終得到問題的解答。

我們先將上下文進行格式化整理:

import { RunnablePassthrough, RunnablePick } from '@langchain/core/runnables';
import { Document } from 'langchain/document';
import { PromptTemplate } from '@langchain/core/prompts';
import { MetaData } from 'src/types';


const $formatRetrieverOutput = async (documents: Document<MetaData>[]) => {
  const strings = documents.map(async (o) => {
    const a = await PromptTemplate.fromTemplate(`{pageContent}`).format({
      pageContent: o.pageContent,
    });


    return a;
  });


  const context = (await Promise.all(strings)).join('\n');


  return context;
};


export const $contextAssignRunnable = () => {
  return RunnablePassthrough.assign({
    context: new RunnablePick('context').pipe($formatRetrieverOutput),
  });
};


問答整體Prompt實現(xiàn):

export const promptTemplateMarkdown = () => {
  return `
# CONTEXT


得物的開放平臺是一個包含著 API 文檔,解決方案文檔的平臺,商家可以通過這個平臺獲取到得物的各種接口,以及解決方案,幫助商家更好的使用得物的服務。
現(xiàn)在得物開放平臺的人工答疑率相當高,原因可能是文檔的信息藏的較深,我希望做一個人工智能答疑助手,通過分析開放平臺的各種文檔,來回答用戶的問題,最終讓用戶不進入人工答疑階段。
我們只討論[開放平臺接口]的相關問題,不要談及其他內(nèi)容。


# OBJECTIVE
你需要根據(jù)用戶的輸入,以及提供的得物開放平臺的文檔上下文,進行答疑。
你只接受有關[開放平臺接口]的相關問答,不接受其余任何問題。


## 關于用戶的輸入:


1. 你會得到一份符合 JSONSchema 結(jié)構(gòu)的結(jié)構(gòu)化數(shù)據(jù),這份數(shù)據(jù)我會使用\`<structured-input></structured-input>\`包裹。
   這份結(jié)構(gòu)化數(shù)據(jù)是通過實際的用戶提問進行了二次分析而得出的。結(jié)構(gòu)化數(shù)據(jù)里也會包含用戶的最初始的問題供你參考(最初始的問題會放在 question 字段里)


## 關于上下文


1.  我已經(jīng)提前準備好了你需要參考的資料,作為你回答問題的上下文,上下文是由許多篇 Markdown 文檔組成的。這些 Markdown 的文檔大標題代表了這個片段的模塊名,例如 \`# 接口入?yún)`就代表這部分是文檔的接口入?yún)⒉糠郑?\`# 接口返回\`就代表這部分是文檔的接口返回部分,
2.  上下文中的主要信息部分我會使用 Markdown Table 的結(jié)構(gòu)提供給你。
3.  每個上下文的開頭,我都會給你一些關于這份上下文的元信息(使用 FrontMatter 結(jié)構(gòu)),這個元信息代表了這份文檔的基礎信息,例如文檔的頁面地址,接口的名稱等等。


以下是我提供的結(jié)構(gòu)化輸入,我會使用\`<structured-input></structured-input>\`標簽做包裹
<structured-input>
{structuredInput}
</structured-input>


以下是我為你提供的參考資料,我會使用\`<context></context>\`標簽包裹起來:
<context>
{context}
</context>


# STYLE


你需要把你的回答以特定的 JSON 格式返回


# TONE


你是一個人工智能答疑助手,你的回答需要溫柔甜美,但又不失嚴謹。對用戶充滿了敬畏之心,服務態(tài)度要好。在你回答問題之前,需要簡單介紹一下自己,例如“您好,很高興為您服務。已經(jīng)收到您的問題?!?

# AUDIENCE


你的用戶是得物開放平臺的開發(fā)者們,他們是你要服務的對象。


# RESPONSE


你返回的數(shù)據(jù)結(jié)構(gòu)必須符合我提供的 JSON Schema 規(guī)范,我給你的 Schema 將會使用\`<structured-output-schema></structured-output-schema>\`標簽包裹.


<structured-output-schema>
  {strcuturedOutputSchema}
</structured-output-schema>
`;
};


以上問答通過CO-STAR結(jié)構(gòu),從6個方面完全限定了答疑助手的回答腔調(diào)以及問答范疇,我們現(xiàn)在只需要準備相應的數(shù)據(jù)結(jié)構(gòu)提供給這份Prompt模板。

問答結(jié)果結(jié)構(gòu)化

在開放平臺答疑助手的場景下,我們不僅要正面回答用戶的問題,同時還需要給出相應的可閱讀鏈接。結(jié)構(gòu)如下:

import { z } from 'zod';


const zOutputSchema = z
  .object({
    question: z
      .string()
      .describe(
        '提煉后的用戶提問。此處的問題指的是除去用戶提供的接口信息外的問題。盡量多的引用用戶的提問',
      ),
    introduction: z
      .string()
      .describe('開放平臺智能答疑助手對用戶的問候以及自我介紹'),
    answer: z
      .array(z.string())
      .describe(
        '開放平臺智能答疑助手的回答,需將問題按步驟拆分,形成數(shù)組結(jié)構(gòu),回答拆分盡量步驟越少越好。如果回答的問題涉及到具體的頁面地址引用,則將頁面地址放在relatedUrl字段里。不需要在answer里給出具體的頁面地址',
      ),
    relatedUrl: z
      .array(z.string())
      .describe(
        '頁面的鏈接地址,取自上下文的pageUrl字段,若涉及多個文檔,則給出所有的pageUrl,若沒有pageUrl,則不要返回',
      )
      .optional(),
  })
  .required({
    question: true,
    introduction: true,
    answer: true,
  });


type OpenRagOutputType = z.infer<typeof zOutputSchema>;


export { zOutputSchema, type OpenRagOutputType };


在我們之前的設計中,我們的每一份向量數(shù)據(jù)的頭部,均帶有相應的文檔meta信息,通過這種向量設計,我們可以很容易的推算出可閱讀鏈接。同時,我們在這份zod schema中提供了很詳細的description,來限定機器人的回答可以有效的提取相應信息。

Runnable的結(jié)合

在用戶提問解答這個Runnable中,我們需要結(jié)合Retriever, 上下文,用戶提問,用戶輸出限定這幾部分進行組合。

import { ChatOpenAI } from '@langchain/openai';
import { $getPrompt } from './prompt/index';
import { JsonOutputParser } from '@langchain/core/output_parsers';
import { RunnableSequence, RunnableMap } from '@langchain/core/runnables';
import { zOutputSchema } from './schema';
import { $getContext } from './retriever/index';
import { getLLMConfig } from 'src/utils/llm/get-llm-config';
import { getStringifiedJsonSchema } from 'src/utils/llm/get-stringified-json-schema';
import { n } from 'src/utils/llm/gen-runnable-name';


const b = n('$open-rag');


type OpenRagInput = {
  structuredInput: string;
  question: string;
};


const $getOpenRag = async () => {
  const $model = new ChatOpenAI(getLLMConfig().ChatOpenAIConfig).bind({
    response_format: {
      type: 'json_object',
    },
  });


  const chain = RunnableSequence.from([
    RunnableMap.from<OpenRagInput>({
      // 問答上下文
      context: await $getContext(),
      // 結(jié)構(gòu)化輸入
      structuredInput: (input) => input.structuredInput,
      // 用戶提問
      question: (input) => input.question,
      // 輸出結(jié)構(gòu)
      strcuturedOutputSchema: () => getStringifiedJsonSchema(zOutputSchema),
    }).bind({ runName: b('runnable-map') }),
    $getPrompt().bind({ runName: b('prompt') }),
    $model,
    new JsonOutputParser(),
  ]).bind({ runName: b('chain') });


  return chain;
};


export { $getOpenRag };


流程串聯(lián)

通過上文的幾大部分,我們已經(jīng)將 用戶提問、結(jié)構(gòu)化解析、相似性搜索、問答上下文,問答Prompt模板以及結(jié)構(gòu)化輸出這幾部分實現(xiàn)完成。

現(xiàn)在可以通過LangChain的Runnable能力進行流程串聯(lián)。

const structure = safeJsonParse<StructuredInputType>(
  await $getStructuredInput().invoke({ question }),
  null,
)
const mainChain = RunnableSequence.from<{ question: string }, OpenRagOutputType>([
  RunnablePassthrough.assign({
    structuredInput: () => structure,
  }),
  await $getOpenRag(),
]);
const response = await mainChain.invoke({
  question: question,
});


串聯(lián)流程如下:

RAG應用在得物開放平臺的智能答疑的探索-AI.x社區(qū)


返回結(jié)果示例:


輸入:

{
  "question": "正式環(huán)境調(diào)用:直發(fā)訂單獲取打印面單v2【品牌直發(fā)/眾籌直發(fā)/拍賣直發(fā)】接口返回報錯(https://openapi.dewu.com/dop/api/v2/order/brand_deliver/express_sheet)。請求參數(shù):{\"order_no\":\"110152014363632294\",\"app_key\":\"953a43bec40943c98a5cdbc63333242f;\",\"sign\":\"AD968ED6A359D144B61F1FEF6BB380ED\",\"timestamp\":1720430064169}"
}


輸出:

{
  "question": "我的問題是:正式環(huán)境調(diào)用:直發(fā)訂單獲取打印面單v2【品牌直發(fā)/眾籌直發(fā)/拍賣直發(fā)】接口返回報錯。",
  "introduction": "您好,很高興為您服務。已經(jīng)收到您的問題。",
  "answer": [
    "根據(jù)您提供的請求參數(shù)信息,出現(xiàn)錯誤可能是由于接口調(diào)用參數(shù)不正確導致的,接下來我們一步步排查問題。",
    "首先,您提供的請求參數(shù)中的訂單號為\"110152014363632294\",請確認該訂單號的狀態(tài)是否為待物流攬收或待買家收貨狀態(tài)。",
    "其次,檢查您所使用的app_key是否正確,確保該app_key授權了調(diào)用該接口的權限。",
    "再次,請確認傳入的簽名(sign)是否正確,可能需要重新生成簽名以保證準確性。",
    "最后,檢查時間戳(timestamp)是否正確,并且處于合理的時間范圍內(nèi)。",
    "如果以上步驟都沒有解決問題,建議您查看詳細的接口文檔以進一步調(diào)試和解決問題。"
  ],
  "relatedUrl": [
    "https://open.dewu.com/#/api?apiId=1174"
  ]
}


五、應用調(diào)試

基于大模型應用可能設計到多個Runnable的多次調(diào)用,借用LangSmith的trace功能,我們可以對每一個Runnable進行出入?yún)⒌膁ebug。


關于LangSmith的接入:

RAG應用在得物開放平臺的智能答疑的探索-AI.x社區(qū)


六、未來展望

RAG應用在得物開放平臺的智能答疑的探索-AI.x社區(qū)

RAG在減少模型幻覺,無需模型訓練就可享受內(nèi)容時效性的特點在此類答疑應用中展露無遺,RAG應用開放平臺落地從一定程度上驗證了依賴可靠知識庫的答疑場景具備可執(zhí)行性,還為內(nèi)部系統(tǒng)的應用提供了有力的參考。在實際應用中,除了直接解決用戶的提問外,通過回放用戶提問的過程,可以為產(chǎn)品和業(yè)務的發(fā)展提供重要的洞察。

面向未來,是否可以嘗試將答疑助手的形式在內(nèi)部系統(tǒng)落地,在內(nèi)部建立知識庫體系,將部分問題前置給大模型處理,降低TS和開發(fā)介入答疑的成本。

 

本文轉(zhuǎn)載自??得物技術??,作者: 惑普 ????


收藏
回復
舉報
回復
相關推薦