用 C++構建自己的 GPT 文檔工具

2023-08-17     InfoQ

原標題:用 C++構建自己的 GPT 文檔工具

作者 | Michael Haephrati、Ruth Haephrati

譯者 | 劉雅夢

策劃 | Tina

雖然通過 Web 介面使用 ChatGPT 是一回事,但創建自己的自主 AI 工具,並通過其 API 與 ChatGPT 交互,則完全是另一回事,特別是當你的目標是保持對用戶交互的完全控制時。與此同時,作為一名堅定 C++ 的支持者,我們相信用 C++ 編寫的 GPT 工具能減輕處理(無休止的)編輯批註這一艱巨任務所帶來的痛苦。

總體思路

我們旨在探索 MS Office 的自動化領域,並利用 ChatGPT API 增強編輯過程。我們設想了一個複雜的工具,可以將 C++ 與 ChatGPT API 無縫地集成,從而提供一種與 Word 文檔中的編輯批註進行交互的新方法。

傳統的文檔編輯包括手動審閱內容和向特定部分添加批註。就我們而言,當我們編寫 C++ 書籍時,我們每次都會遇到 100 多條編輯批註,其中大部分與出版商的風格指南和注釋有關。如果能有一種方法將這些批註和相關文本存儲在資料庫中,那就太好了,更不用說基於人工智慧的編輯潛力了。這正是我們的軟體所要實現的目標:通過自動化這一過程,我們可以加快編輯工作流程。雖然這個工具可以作為概念驗證(POC),不建議用於編寫和編輯整本書,但它仍然是一個令人興奮的自動化練習,當然值得一試。

它是如何做到的

工作流程從我們的軟體掃描 Word 文件開始,使用 Office Automation API 仔細檢查文檔中嵌入的每一條編輯批註。

枚舉完所有批註後,我們的工具就會提取它們以及與之相關的文本段,並將它們存儲在 sqlite3 資料庫中。在此基礎上,它將圍繞如何改進或修復文本的特定部分來為 ChatGPT 準備有針對性的問題。通過利用 ChatGPT API,我們可以利用語言模型的豐富知識和語言能力來獲取專家的意見和建議。

在收到 ChatGPT 的回覆之後,我們的工具會動態地將建議的編輯內容合併到相關的文本片段中,從而根據模型的見解無縫地增強內容。

這種自動化的編輯過程大大減少了手工工作量,並加快了文檔的整體細化完善。我們的工具甚至可以跟蹤更改,但要記得在完成後關閉「跟蹤更改」。

在編程方面,我們的項目中有幾個構建塊,其中一些可以擴展或替換以滿足不同的目的。我們將我們的代碼稱為概念驗證( Proof of Concept, POC)。

構建塊

以下是這一過程的參與者——我們的構建塊:

Chat GPT API

我們的工具通過使用各種參數和方法來與 ChatGPT 進行接口調用和交互。我們準備要發送給 API 的有效負載並解析響應。要使用我們的工具,必須要獲取一個 API 密鑰並將其添加到我們的代碼中,注意不是「 」。下面的代碼片段演示了與 ChatGPT 交互的基礎知識。

使用 API 的優勢包括:能夠與 Chat GPT 進行接口調用和交互,並使用不同的參數和方法,準備要發送到 API 的有效負載,以及解析返回給我們的響應。

使用 ChatGPT API 時,需要考慮以下幾點。

我們的通用函數

為了本文的目的,我們創建了一個通用函數。該函數是模塊化的,因為它能生成具有模塊化屬性和參數的請求,格式如下:

讓我們來看看這些屬性,並討論下它們存在的問題及需求:

  • 「messages」——定義用戶和模型之間的對話歷史。對話中的每條消息都由兩個屬性組成:「role」(可以是「system」、「user」或「assistant」)和「content」(消息的實際文本內容)。為了本文的目的,我們使用了「user」
  • 「model」——允許指定想要使用的 ChatGPT 模型版本。在本文中,我們使用了「gpt-3.5-turbo」
  • 「temperature」——可以設置它來控制生成的文本和 prompt 之間的相似程度。例如,高溫值可用於生成與 prompt 更不同的文本,而低溫值可用於生成與 prompt 更相似的文本。在目標為生成與給定輸入相似但具有一定程度的變化或「創造性」文本的情況下,這可能很有用。
  • 「max_tokens」——是每個請求使用的最大 token 數。處理的 token 數量取決於輸入和輸出文本的長度。
    • 1-2 句~= 30 個 token
    • 1 段~=100 個 token
    • 1,500 個單詞~=2048 個 token
  • 1-2 句~= 30 個 token
  • 1 段~=100 個 token
  • 1,500 個單詞~=2048 個 token

作為 ChatGPT API 的用戶,我們需要為我們消耗的 token 付費。

  • 「n」——控制模型應提供的響應數量;默認情況下,它被設置為 1,即單個響應

  • 「stop」——表示應觸發模型停止生成其響應的字符串。默認情況下設置為換行符。這意味著,當模型在其輸出中遇到新行時,它將在那之後停止生成。

「n」——控制模型應提供的響應數量;默認情況下,它被設置為 1,即單個響應

「stop」——表示應觸發模型停止生成其響應的字符串。默認情況下設置為換行符。這意味著,當模型在其輸出中遇到新行時,它將在那之後停止生成。

我們的 Prompt

我們總是喜歡說,結構良好的 prompt 的重要性是怎麼強調也不為過的。精心構建的 prompt 可以作為指導藍圖,影響生成的輸出質量。在本文中,我們將深入研究有效 prompt 的組成部分,並提供實際的示例和指導,幫助 C++ 學生在項目中最大限度地發揮 ChatGPT API 的潛力。

下面是一個例子:

在編寫 prompt 時,最好創建一個模板,其中包含將在整個程序中使用的請求的常量部分,然後根據當前需要更改可變部分。以下是一個良好的 prompt 的一些關鍵組成部分:

上下文:

上下文作為 prompt 的基礎,能提供關鍵的背景信息。它使語言模型能夠掌握任務的本質。無論是簡明扼要的問題描述還是相關細節的總結,對提供上下文都至關重要。

示例:

「你是一名軟體開發人員,正在為外賣服務開發移動應用程式。該應用程式旨在為用戶提供從當地餐館訂餐的無縫體驗。作為開發過程的一部分,你需要幫助生成有關該應用程式的功能是如何吸引人的信息豐富內容。」

任務:

任務定義了 prompt 的精確目標或目的。它應該清晰、簡潔,並重點關注於 ChatGPT 模型預期的具體信息或操作。

示例:「寫一個簡短的段落,突出應用程式的主要功能,並展示它們是如何增強客戶的送餐體驗的。」

約束條件:

約束為 prompt 設置了邊界或限制。它們可能包括特定的要求、對響應長度或複雜性的限制或任何其他相關約束。通過定義約束,可以引導生成的輸出滿足所需的結果。

示例:

「回答應該簡明扼要,字數不超過 150 字。重點關注應用程式區別於競爭對手的最突出功能,並使其對用戶友好。」

補充說明:

在本節中,你將有機會提供補充上下文或指定所需的輸出格式。這可以包括有關預期輸入格式或請求以特定格式(如 Markdown 或 JSON)輸出的詳細信息。

示例:「請將響應格式化為 JSON 對象,其中包含每個特性描述的鍵值對。每個鍵都應代表一個特性,其對應的值應提供一個簡短的描述,突出其優點。」

通過理解和實現這些基本組件,C++ 開發人員可以掌握構建有效 prompt 的藝術,以便在項目中最優地利用 ChatGPT API。深思熟慮地結合上下文,定義明確的任務,設置約束並提供額外的說明將使開發人員能夠獲得精確且高質量的結果。

連續的聊天

在大多數情況下,我們希望能從你上次結束的地方繼續對話。Chat GPT API 使用了一個特殊的標誌來實現這一點。如果未設置,將會發生如下的情況:

法國的首都是哪裡?

CallbackJSON: '{"id":"chatcmpl-7AlP3bJX2T7ibomyderKHwT7fQkcN","object":"chat.completion","created":1682799853,"model":"gpt-3.5-turbo-0301","usage":{"prompt_tokens":15,"completion_tokens":7,"total_tokens":22},"choices":[{"message":{"role":"assistant","content":"The capital of France is Paris."},"finish_reason":"stop","index":0}]}

你人工智慧朋友的回答是:

法國的首都是巴黎。

接下來的一個問題是:

它有多大?

CallbackJSON: '{"id":"chatcmpl-7AlPAabscfyDrAV2wTjep0ziiseEB","object":"chat.completion","created":1682799860,"model":"gpt-3.5-turbo-0301","usage":{"prompt_tokens":13,"completion_tokens":20,"total_tokens":33},"choices":[{"message":{"role":"assistant","content":"I apologize, but I need more context to accurately answer your question. What are you referring to?"},"finish_reason":"stop","index":0}]}

我很抱歉,但是我需要更多的上下文來準確回答你的問題。你指的是什麼?

要解決這一問題,我們需要保持連續的聊天,但我們該如何做到這一點呢?事實上,要做到這一點的唯一方法是必須來回傳遞一個包含整個對話的字符串。

我們還定義了:

它的定義是:

在我們的原始碼中,你可以看到我們是如何將 Conversation 對象維護成固定長度的(很明顯,我們無法存儲無休止的對話)。這個固定長度是在這裡設置的:

如前所述,我們的 prompt 在請求的效率中起著關鍵作用,當涉及到連續聊天時,我們可能需要使用不同的 prompt:

多部分響應

當你問你的人工智慧朋友:

給我寫一段 C++ 代碼,實現從 1 到 10 的計數。

你可能會得到這樣的結果:

當然可以,下面是從 1 到 10 計數的 C++ 代碼:

沒有任何原始碼。

原因如下:發送給 API 的 stop 參數讓模型知道它應該在輸出的哪個點上停止生成更多內容。當沒有指定任何內容時,換行符就是默認值,這意味著模型在輸出第一個換行符後就停止生成更多的輸出。

但是,如果你將「stop」參數設置為空字符串,你將得到完整的響應,其中將包含原始碼:

關於 OLE 自動化

OLE 自動化是微軟在過去引入的一項技術,此後不斷發展。在我們的實現中,我們直接使用了 Microsoft 自動化,繞過了 MFC(Microsoft Foundation Classes,微軟基礎類庫)的使用。為了訪問 MS Word 的各種元素,如文檔、活動文檔、批註等,我們為需要交互的每個對象定義了 IDispatch COM 接口。

Office 自動化

我們的工具自動化了 MS Word 中的各種任務和特性。它可以讀取批註、查找相關文本、打開 / 關閉「跟蹤更改」、在後台工作、替換文本、添加批註、保存結果以及關閉文檔。下面是我們所使用的函數的描述:

OLEMethod:一個輔助函數,用於調用 IDispatch 接口上的方法,處理方法調用並返回指示錯誤的 HRESULT 值。

Initialize:該函數通過創建 Word 應用程式的實例並設置其可見性來初始化 OfficeAutomation 類。它能初始化 COM 庫,檢索 Word 應用程式的 CLSID,創建應用程式的實例,並設置其可見性。

OfficeAutomation:OfficeAutomation 類的構造函數。它初始化成員變量,並使用 false 調用 Initialize 函數以創建不可見的 Word 應用程式實例。

~OfficeAutomation:OfficeAutomation 類的析構函數。它在此實現中不執行任何操作。

SetVisible:設置活動文檔可見性的函數。它使用一個布爾參數來確定文檔是否應該可見。它使用 OLEMethod 函數來設置 Word 應用程式的可見性屬性。

OpenDocument:打開 Word 文檔並設置其可見性的函數。它接受一個指向文檔路徑和一個用於可見性的布爾參數。如果需要,它會初始化該類,檢索 Documents 接口,打開指定的文檔,並設置其可見性。

CloseActiveDocument:關閉活動文檔的函數。它會保存文檔,然後關閉文檔。它使用 OLEMethod 函數來調用適當的方法。

ToggleTrackChanges:用於切換活動文檔的「跟蹤修訂」特性的函數。它獲取特性的當前狀態,並在必要時進行切換。它使用 OLEMethod 函數來訪問和修改「TrackRevisions」屬性。

FindCommentsAndReply:該函數用於查找活動文檔中的所有批註,向 ChatGPT API 發送請求以獲取建議,並根據 API 響應更新每個批註的關聯文本。它遍歷每個批註,檢索關聯的文本範圍,用文本和批註作為上下文向 ChatGPT API 發送 prompt,接收 API 響應,並使用建議的更改更新文本範圍。

CountDocuments:該函數用於返回與 OfficeAutomation 類關聯的 Word 應用程式中打開的文檔數。它檢索 Documents 接口並返回計數。

處理批註

在制定審查批註機制時,我們需要能夠枚舉所有批註,並區分已處理的批註和未處理的批註。

這可以通過以下方式完成:

if(FAILED(hr)) {ShowError(hr);returnfalse;}boolresolved = (isResolved.vt == VT_BOOL && isResolved.boolVal == VARIANT_TRUE);returnresolved;}

正如你所看到的,使用 OLEMethod 和 DISPATCH_PROPERTYGET,我們可以檢查屬性名「Done」,它表示已處理的批註。

枚舉批註

接下來,我們可以枚舉文檔中的所有批註,並列印每個批註的「已處理」(「Resolved」)狀態。

在開始之前,我們不僅要枚舉批註,還要枚舉與之相關的文本。原因在於批註的最初目的。文檔的作者撰寫並編輯文檔。編輯標記一個片段,可以是一個段落、一個句子甚至是一個單詞,並添加一條批註。當我們閱讀批註時,我們需要該批註的上下文,而上下文就是那個被標記的片段。

因此,當我們枚舉所有批註時,我們不僅要列印批註本身,還要列印與之相關的文本(我們的片段)。

當我們開始檢查所有批註時,我們需要聲明並初始化 2 個指針:

pComments——指向文檔的批註。

pRange——指向文檔的內容(包含與批註相關聯的文本的段)。

它們兩個都需被初始化:

pComments= result.pdispVal;}

{VARIANTresult;VariantInit(&result);m_hr= OLEMethod(DISPATCH_PROPERTYGET, &result, m_pActiveDocument, (LPOLESTR)L"Content", 0);if(FAILED(m_hr)){ShowError(m_hr);returnm_hr;}pRange= result.pdispVal;}

然後我們就可以開始循環遍歷文檔中的所有批註了。

你可以在我們的原始碼中看到這是如何實現的,但一般來說,我們從批註開始,轉到相關的文本,並檢查批註是否得到了處理。然後,我們就可以將其列印到報告中,將其添加到資料庫中,或者將其發送給 Chat GPT API。

API 接口通用代碼

為了通過網絡與任何 API 接口,我們使用了通用代碼來方便地發送請求並使用 JSON 數據格式解析響應。在此過程中,我們使用了 libCurl,這是一個強大的工具,被廣泛用於使用命令行或腳本在網絡上傳輸數據。它在不同領域有著廣泛的應用,包括汽車、電視、路由器、印表機、音頻設備、移動設備、機頂盒和媒體播放器等領域。它是眾多軟體應用程式的網際網路傳輸引擎,安裝量達數十億次。

如果你查看了我們的原始碼,就可以看到 libCurl 是如何使用的。

總 結

通過利用 MS Office 自動化的強大功能並將其與 ChatGPT API 集成,我們使編輯和作者能夠簡化其工作流程,節省寶貴的時間並提高他們的工作質量。C++ 和 ChatGPT API 之間的協作促進了流暢高效的交互,使我們的工具能夠為每個編輯批註提供智能且感知上下文的建議。

因此,我們的小型 MS Office 自動化 POC 工具,由 ChatGPT API 和 C++ 支持,徹底改變了編輯過程。通過自動提取編輯批註,與 ChatGPT 互動以尋求專家指導,並無縫集成編輯建議,我們使用戶能夠提高他們在 Word 文檔中工作的質量和效率。這種強大的技術組合為高效的文檔編輯開闢了新的可能性,並代表著 MS Office 自動化領域的重大飛躍。

原文連結

https://www.infoq.com/articles/chatgpt-agent-C-plus-plus/

吵翻了!到底該選 Rust 還是 Go,成2023年最大技術分歧

我的20年職業生涯:全是技術債

中國最大公有雲服務商,如何從零開始構建一支雲效團隊

工信部要求所有 App、小程序備案;某國產電商被提名 Pwnie Awards 「最差廠商獎」;阿里財報超預期 | Q資訊

文章來源: https://twgreatdaily.com/zh-hk/1fbb4de11e0cb2ab38d31375a505dc5e.html