paint-brush
コード生成によるチャットボットの改善: 出版物向けのコンテキスト認識型チャットボットの構築@shanglun
1,603 測定値
1,603 測定値

コード生成によるチャットボットの改善: 出版物向けのコンテキスト認識型チャットボットの構築

Shanglun Wang19m2023/11/05
Read on Terminal Reader

長すぎる; 読むには

GPT ベースのシステムはナレッジ管理に使用され、大きな効果をあげていますが、現在の最先端の実装ではメタデータに基づいた検索機能が制限されています。現在、私たちはセマンティック検索とメタデータ検索の両方を実行できるシステムを構築し、検索拡張生成システムの機能を大幅に向上させています。
featured image - コード生成によるチャットボットの改善: 出版物向けのコンテキスト認識型チャットボットの構築
Shanglun Wang HackerNoon profile picture

導入

2023 年初頭に ChatGPT が世間の注目を集めて以来、大規模な言語モデルベースのアプリケーションの商用化に対する関心が爆発的に高まっています。最も興味深いアプリケーションの 1 つは、独自の知識ベースのデータベースからの自然言語クエリに応答できるエキスパート チャット システムの作成です。


この分野で最も人気のある手法の 1 つは、[検索拡張生成](https://retrieval augmented Generation aws)、つまり RAG です。これは、大規模な言語モデルを使用して生成する前に、ドキュメントの埋め込みを使用してユーザー クエリに関連するアイテムを検索します。答え。


この技術は、非常に安価で高速な検索を可能にし、時間の経過とともに変化および進化する知識ベースに極めて高い柔軟性を提供し、幻覚や間違いを大幅に減らす高度な情報に基づいた正確な回答を提供するため、非常に強力です。


RAG システムのより詳細な分析とその実装方法については、 ここで私の前回の記事を読むことができます。


RAG システムは強力ですが、アーキテクチャにはいくつかの重大な制限があります。前回の記事でいくつかの制限を検討し、アーキテクチャを改善する方法を提案しました。


今日は、埋め込みベースのアーキテクチャに関する別の制限を調査し、アーキテクチャの制限を回避する方法を提案します。

問題文

エンベディングベースの RAG の制限

私たちが出版物で、読者やクライアントが質問できるチャット インターフェイスを作成したいと考えているとします。


もちろん、「X についてどう思いますか?」といった質問には答えることができます。または「Yについて何と言った?」単純な RAG 実装では実現できますが、RAG アーキテクチャは、「2021 年の X について何と言っていますか?」のような質問に対処すると、本当に苦労し始めます。または「2021 年から 2023 年にかけて、Y の報道内容はどう変化しましたか?」


エンベディング ベースの RAG の課題の 1 つは、エンベディング モデルは一般に体系的な方法でメタデータをエンコードできないため、出版日や著者名などの情報を必要とする検索では、RAG システムにかなりの問題が発生することです。 。


この問題は、大規模言語モデルの最も魅力的な機能の 1 つであるコード生成を活用することで解決できます。実際の出版物を検討し、RAG アーキテクチャを改良した LLM ベースのアルゴリズムを設計し、そのアルゴリズムに基づいてチャットボットを構築します。

ビジネス上の問題

今日は、スタートアップとテクノロジーをカバーする人気の日刊ニュースレターであるCB Insightsニュースレターを取り上げます。 CB Insights の元フルスタック開発者として、私は仕事の終わりに創業者のユニークな機知と洞察力を期待することがよくありました。


今日は、CB Insights ニュースレター アーカイブをベース データとして使用し、バニラの Embeddings ベースの RAG 実装よりも優れた方法でメタデータ ベースの自然言語クエリに応答できるチャットボットを構築します。


具体的には、チャットボットが次のような質問に答えられるようにしたいと考えています。


  • 2020 年に Uber について何を言いましたか?


  • 2016年から2019年にかけてAirbnbの運命はどう変化しましたか?


  • 2020年代に注目すべきインド発のユニコーンスタートアップにはどのようなものがありますか?


さあ、始めましょう!

使用されている技術

このタスクを達成するには、次のテクノロジーを使用します。

パイソン

私の他の記事を読んでいるなら、この記事のコードの大部分で Python を使用することは驚くべきことではありません。 Python には優れた Web スクレイピング、データ処理、OpenAI 統合が備わっており、これらすべてを今日のプロジェクトで活用します。

SQL

SQL は、ユーザーが SQLite、MySQL、PostgreSQL、SQL Server などの主要なリレーショナル データベースと対話できるようにするクエリ言語です。この言語は、データをユーザーに返す前に、データを取得、結合、操作する方法についてデータベースに指示する一連の命令です。

LLM コード生成

LLM コード生成は、GPT 3.5、GPT 4、LLaMa 2 などのいくつかの基盤モデルが、自然言語クエリに応じて驚くほど複雑なコードを生成する能力を実証したため、ここ数か月で広く注目を集めている技術です。


GitHub の Copilot など、特別にトレーニングされ調整されたシステムは、コード生成用に特別に設計されたモデルを使用することで、非常にインテリジェントなコードを作成できますが、コードの作成に関しては、適切にプロンプトされた汎用 GPT モデルがすでに優れた機能を備えています。

セマンティック埋め込み

セマンティック埋め込みは、ほとんどの RAG 実装のバックボーンです。一連の自然言語手法を使用すると、自然言語テキストを、意味ベクトル空間内のテキストの内容を表す数値ベクトルに変換できます。


その後、ベクトル代数を使用してこれらの埋め込みを操作し、数学的手法を使用して 2 つのテキスト コーパス間の関係を決定できるようになります。

GPT-3.5 および GPT-4

1.7 兆のパラメーターを備えた GPT-4 は、現在市場で入手可能なトランスフォーマーベースの大規模言語モデルの中で最も強力です。 GPT-4 は、大量のテキスト、複雑な推論、および難しいプロンプトに対する長く説得力のある回答の生成を理解できます。


GPT-3.5 は、GPT-4 のはるかに小さな従兄弟であり、ChatGPT が世界を席巻したときに原動力となったモデルです。信じられないほど複雑なプロンプトを処理することができ、純粋な推論能力では欠けている部分を速度とコストの節約で補います。


より単純なタスクの場合、GPT3.5 はパフォーマンスと精度のバランスをとります。

チャットボットバックエンドのセットアップ

データベースのセットアップ

AI を構築する前に、データを取得する必要があります。これを行うには、過去のニュースレターのコレクションがある CB Insights のニュースレター アーカイブ ページ [ https://www.cbinsights.com/newsletter/ ] を使用できます。


すべてのリンクを取得するには、次のように Python のリクエストと美しいスープ ライブラリを使用します。

 import requests from bs4 import BeautifulSoup res = requests.get('https://www.cbinsights.com/newsletter/') soup = BeautifulSoup(res.text) article_links = [[i.text, i['href']] for i in soup.find_all('a') if 'campaign-archive' in i['href'] ]


リンクを取得したら、各リンクに移動して記事の HTML をダウンロードできます。 Python のリスト内包表記を使用すると、これを 1 行で実行できます。


 article_soups = [BeautifulSoup(requests.get(link[1]).text) for link in article_links]


これにはしばらく時間がかかりますが、最終的にはすべてのリンクが削除されるはずです。


これで、BeautifulSoup を使用して関連セクションを抽出できるようになりました。


 import re # SEO optimizations cause some articles to appear twice so we dedupe them. # We also remove some multiple newlines and unicode characters. def get_deduped_article_tables(article_table): new_article_tables = [] for i in article_table: text_content = re.sub(r'\n{2,}', '\n', i.replace('\xa0', '').strip()) if text_content not in new_article_tables or text_content == '': new_article_tables.append(text_content) return new_article_tables result_json = {} for soup_meta, soup_art in zip(article_links, article_soups): article_tables = [] cur_article = [] for table in soup_art.find_all('table'): if table.attrs.get('mc:variant') == 'Section_Divider': article_tables.append(get_deduped_article_tables(cur_article)) cur_article = [] else: cur_article.append(table.text) article_tables.append(get_deduped_article_tables(cur_article)) result_json[soup_meta[0]] = article_tables


さらに処理を行って、DataFrame に変換しましょう。


 import pandas as pd result_rows = [] for article_name, article_json in result_json.items(): article_date = article_json[0][1] for idx, tbl in enumerate(article_json[1:]): txt = '\n'.join(tbl).strip() if txt != '': result_rows.append({ 'article_name': article_name, 'article_date': article_date, 'idx': idx, 'text': txt, }) df = apd.DataFrame(result_rows)


データフレームを検査すると、次のような内容が表示されるはずです。



データを用意したら、記事の埋め込みも生成しましょう。 OpenAI の ada 埋め込みモデルを使用すると、これは非常に簡単です。


 import openai EMBEDDING_MODEL = "text-embedding-ada-002" openai.api_key = [YOUR KEY] df['embedding'] = df['text'].map(lambda txt: openai.Embedding.create(model=EMBEDDING_MODEL, input=[txt])['data'][0]['embedding'])


この演習で使用するデータが得られたので、そのデータをデータベースにロードしましょう。この演習では、Python にパッケージ化された軽量の自己完結型データベース システムである SQLite を使用します。


運用環境では、ここで使用している SQL に若干の調整を加えた MySQL や PostgreSQL などの適切なデータベース インスタンスを使用することになる可能性がありますが、一般的な手法は同じであることに注意してください。


データベースをインスタンス化してロードするには、Python で次のコマンドを実行するだけです。記事のテキストと埋め込みに加えて、いくつかのメタデータ、つまり出版日も保存していることに注意してください。


また、SQLite3 は他のほとんどの SQL データベースとは異なり、動的型付けシステムを使用するため、作成クエリでデータ型を指定する必要がないことにも注意してください。


 import sqlite3 import json con = sqlite3.connect("./cbi_article.db") cur = con.cursor() cur.execute("CREATE TABLE article(name, date, idx, content, embedding_json)") con.commit() rows = [] for _, row in df.iterrows(): rows.append([row['article_name'], row['article_date'], row['idx'], row['text'], json.dumps(row['embedding'])]) cur.executemany("INSERT INTO article VALUES (?, ?, ?, ?, ?)", rows) con.commit()


データをクエリしてみましょう。


 res = cur.execute(""" SELECT name, date, idx FROM article WHERE date >= DATE('now', '-2 years'); """) res.fetchall()


次のような結果が得られるはずです:




見た目はかなり良いです!


メタデータ ルックアップ用のコード ジェネレーターの構築

SQLite データベースにデータがロードされたので、次の段階に進むことができます。埋め込みのみの RAG 実装に関する課題の 1 つは、柔軟なメタデータ ルックアップ機能が欠如していることであることに注意してください。


ただし、メタデータが SQL データベースに読み込まれたので、GPT のコード生成機能を使用して柔軟なメタデータ ルックアップを実行できます。


SQL コードを生成するには、単純なプロンプト エンジニアリングを使用できます。


 response = openai.ChatCompletion.create( model="gpt-4", messages=[ {"role": "system", "content": "You are a SQL query writer that can construct queries based on incoming questions. Answer with only the SQL query."}, {"role": "user", "content": """ Suppose we have the SQLite database table called "article" with the following columns, which contains newsletter articles from a publication: name, date, idx, content, embedding_json Write a question that would retrieve the rows necessary to answer the following user question. Only filter on date. Do not filter on any other column. Make sure the query returns every row in the table by name. Reply only with the SQL query. User question: What did you say about the future of the fintech industry in summer of 2022? """}, ] )


次のプロンプト エンジニアリングに注目してください。 1) データベース スキーマを指定しますが、単純にしておきます。 2) リターン列を指定します。 3) フィルタリングに使用できる列を指定します。 4) SQL フレーバーを指定します。このプロンプトにより、以下のような SQL コードが生成されるはずです。


 SELECT * FROM article WHERE date BETWEEN '2022-06-01' AND '2022-08-31'


現在、戻り値は決定的ではないため、生成されたコードに特異性が生じる場合があります。これらの状況に対処するには、try-catch ループを使用してデータの再生成を試みるだけです。もちろん、これを無限に実行することは望まないため、3 回の試行で適切な SQL を生成できない場合は、単に終了して標準 RAG に戻ります。


次のようにフィルターを実装できます。


 res = [] for i in range(3): response = openai.ChatCompletion.create( model="gpt-4", messages=[ {"role": "system", "content": "You are a SQL query writer that can construct queries based on incoming questions. Answer with only the SQL query."}, {"role": "user", "content": """ Suppose we have the SQLite database table called "article" with the following columns, which contains newsletter articles from a publication: name, date, idx, content, embedding_json Write a question that would retrieve the rows necessary to answer the following user question. Only filter on date. Do not filter on any other column. Make sure the query returns every row in the table by name. Reply only with the SQL query. User question: What did you say about the future of the fintech industry in summer of 2022? """}, ] ) generated_query = response.choices[0].message['content'] is_query_safe = True for no_use_word in {'DELETE', 'UPDATE', 'DROP'}: if no_use_word in generated_query.upper(): is_query_safe = False if not is_query_safe: break # the user input is likely malicious. Try to answer the question with vanilla RAG res = cur.execute(generated_query).fetchall() if len(res) > 0: break if len(res) == 0: # vanilla RAG in memory. Use a vector DB in production please. res = cur.execute('''SELECT * FROM articles''').fetchall()


これは比較的粗いフィルタであるため、運用環境のユースケースでは、関連性と SQL の正しさについてさらに多くのチェックを実行する必要があるでしょうが、この例ではこれで十分です。


AI のセキュリティに関する簡単な注意点として、AI から返されたコードを実行するとき、特にユーザー入力がプロンプトの一部として使用された場合には注意が必要です。


出力をサニタイズしないと、ユーザーが AI を操作して更新ステートメントや削除ステートメントを生成しようとするプロンプト エンジニアリング攻撃に対して脆弱なままになります。


したがって、コンピューターでコードを実行する前に、出力が期待どおりであることを常に確認する必要があります。


次のコードを実行して、取得した結果を確認します。


 df = pd.DataFrame([{c[0]: v for c, v in zip(cur.description, row)} for row in res])


そして、次の結果が表示されるはずです。



標準 RAG を実行して結果を得る

メタデータ検索の結果が得られたので、残りは簡単です。取得したすべての結果のコサイン類似度を次のように計算します。


 from openai.embeddings_utils import cosine_similarity q_embed = openai.Embedding.create(model=EMBEDDING_MODEL, input=[user_question])['data'][0]['embedding'] df['cosine_similarity'] = df['embedding_json'].map(lambda js: cosine_similarity(json.loads(js), q_embed))


そして今、トップ 10 のニュースレターを取り上げ、プロンプト エンジニアリングを使用して質問に答えることができます。各ニュースレターの抜粋は比較的短いため、ここでは 10 件を選択しました。


長い記事を扱う場合は、使用する記事の数を減らすか、ボーナス セクションで説明されているテクニックを使用することをお勧めします。


 answer_prompt = ''' Consider the following newsletter excerpts from the following dates: ''' for _, row in df.sort_values('cosine_similarity', ascending=False).iloc[:10].iterrows(): answer_prompt += """ ======= Date: %s ==== %s ===================== """ % (row['date'], row['content']) answer_prompt += """ Answer the following question: %s """ % user_question response = openai.ChatCompletion.create( model="gpt-4", messages=[ {"role": "system", "content": "You are a tech analyst that can summarize the content of newsletters"}, {"role": "user", "content": answer_prompt}, ] )


これにより、次のような結果が得られるはずです。


フィンテックの将来については、2022 年の夏にさまざまなニュースレターで議論されました。2022 年第 2 四半期の資金調達が 2021 年の最高値から 2020 年の水準に向かって急減したため、フィンテックの減速が顕著でした。2022 年第 2 四半期のレポートでは、世界のフィンテック投資の減少が強調されました。 。


しかし、特に決済分野において、初期段階のスタートアップへの大きな移行が注目されており、フィンテックの将来は有望であるように見えました。決済セクターへの世界の投資は、2021年のピーク後に資金が正常に戻ったことにより、2022年第1四半期から43%減少し、2022年第2四半期には51億ドルとなりました。


2022 年のこれまでのところ、この分野への新規参入企業はより高いシェア (63%) を獲得しており、新興企業に対する投資家の関心を示しています。また、フィンテックが小売銀行に引き起こしている競争の激化により、中核サービスのデジタル化を優先せざるを得なくなっていることも報告された。


銀行部門は、チャットボットや顧客分析プラットフォームなどのテクノロジーを使用して、顧客体験の向上、特にモバイル バンキングに焦点を当てることで対応しました。これらすべては、今後のフィンテック業界の活気と競争力を示しています。


それはとても良いことです!回答プロンプトを見て回答を事実確認すると、統計がすべてソース資料から取得されていることがわかります。また、ソース素材には、GPT4 が無視できた特異な書式設定がいくつかあることにも気づくでしょう。これは、データ要約システムにおける LLM の柔軟性を示しています。

おまけ: 要約ミドルウェア

このプロセスで発生する可能性のある問題の 1 つは、コーパスが非常に大きい場合、最終的なプロンプトが非常に大きくなる可能性があることです。 GPT-4 を使用している場合、これはコストがかかる可能性がありますが、非常に長いプロンプトはモデルを混乱させる可能性もあります。


これを解決するには、GPT-3.5 で個々の記事を前処理し、最終ステップで GPT-4 に送信する最終プロンプトを圧縮します。


 summarization_prompt = ''' Summarize the following passage and extract only portions that are relevant to answering the user question. Passage: ======= %s ======= User Questions: %s ''' (row['content'], user_question) response = openai.ChatCompletion.create( model="gpt-3.5-turbo", messages=[ {"role": "system", "content": "You are a summarizer of tech industry reports"}, {"role": "user", "content": summarization_prompt}, ] )


その後、要約をプロンプトに含めることができるため、純粋な記事を最終プロンプトに含めるよりも大幅に節約できます。

シンプルなフロントエンドの構築

Python コードを記述したので、これを単純な Web アプリケーションとしてパッケージ化しましょう。

バックエンドAPI

Flask を使用してコードをバックエンド API としてパッケージ化するのは比較的簡単です。関数を作成し、次のように Flask にリンクするだけです。


 import requests from bs4 import BeautifulSoup import re import pandas as pd import sqlite3 import json import openai from openai.embeddings_utils import cosine_similarity from flask import Flask, request, jsonify from flask_cors import CORS app = Flask(__name__) CORS(app) EMBEDDING_MODEL = "text-embedding-ada-002" openai.api_key = [Your OpenAI Key] db_location = [Location of your SQLite DB] def process_user_query(user_question): con = sqlite3.connect(db_location) cur = con.cursor() user_question = 'What did you say about the future of the fintech industry in summer of 2022?' res = [] for i in range(3): response = openai.ChatCompletion.create( model="gpt-4", messages=[ {"role": "system", "content": "You are a SQL query writer that can construct queries based on incoming questions. Answer with only the SQL query."}, {"role": "user", "content": """ Suppose we have the SQLite database table called "article" with the following columns, which contains newsletter articles from a publication: name, date, idx, content, embedding_json Write a question that would retrieve the rows necessary to answer the following user question. Only filter on date. Do not filter on any other column. Make sure the query returns every row in the table by name. Reply only with the SQL query. User question: What did you say about the future of the fintech industry in summer of 2022? """}, ] ) generated_query = response.choices[0].message['content'] is_query_safe = True for no_use_word in {'DELETE', 'UPDATE', 'DROP'}: if no_use_word in generated_query.upper(): is_query_safe = False if not is_query_safe: break # the user input is likely malicious. Try to answer the question with vanilla RAG res = cur.execute(generated_query).fetchall() if len(res) > 0: break if len(res) == 0: # vanilla RAG in memory. Use a vector DB in production please. res = cur.execute('''SELECT * FROM articles''').fetchall() df = pd.DataFrame([{c[0]: v for c, v in zip(cur.description, row)} for row in res]) q_embed = openai.Embedding.create(model=EMBEDDING_MODEL, input=[user_question])['data'][0]['embedding'] df['cosine_similarity'] = df['embedding_json'].map(lambda js: cosine_similarity(json.loads(js), q_embed)) answer_prompt = ''' Consider the following newsletter excerpts from the following dates: ''' for _, row in df.sort_values('cosine_similarity', ascending=False).iloc[:10].iterrows(): answer_prompt += """ ======= Date: %s ==== %s ===================== """ % (row['date'], row['content']) answer_prompt += """ Answer the following question: %s """ % user_question response = openai.ChatCompletion.create( model="gpt-4", messages=[ {"role": "system", "content": "You are a tech analyst that can summarize the content of newsletters"}, {"role": "user", "content": answer_prompt}, ] ) return response.choices[0].message['content'] @app.route('/process_user_question', methods=["POST"]) def process_user_question(): return jsonify({ 'status': 'success', 'result': process_user_query(request.json['user_question']) }) app.run()

実際、バックエンドで行う必要があるのはこれだけです。

フロントエンドコード

エンドポイントが 1 つだけであり、アプリケーションには多くの状態が必要ないため、フロントエンド コードは非常にシンプルになるはずです。過去の記事で、特定のルートでコンポーネントをレンダリングできるルーティングを使用して React アプリケーションをセットアップしたことを思い出してください。


この記事の手順に従って React.JS プロジェクトをセットアップし、選択したルートに次のコンポーネントを追加するだけです。


 import React, {useState, useEffect} from 'react'; import axios from 'axios'; const HNArticle = () => { const [result, setResult] = useState(''); const [message, setMessage] = useState(''); const [question, setQuestion] = useState(''); const askQuestion = () => { axios.post("http://127.0.0.1:5000/process_user_question", {user_question: question}) .then(r => r.data) .then(d => { console.log(d); setResult(d.result); }); } return <div className="row" style={{marginTop: '15px'}}> <div className="col-md-12" style={{marginBottom: '15px'}}> <center> <h5>Hackernoon CB Insights Demo</h5> </center> </div> <div className="col-md-10 offset-md-1 col-sm-12 col-lg-8 offset-lg-2" style={{marginBottom: '15px'}}> <ul className="list-group"> <li className="list-group-item"> <h6>Your Question</h6> <p><input className="form-control" placeholder="Question" value={question} onChange={e => setQuestion(e.target.value)} /></p> <p>{message}</p> <p> <button className="btn btn-primary" onClick={askQuestion}>Ask</button> </p> </li> {result? <li className="list-group-item"> <h6>Response</h6> {result.split("\n").map((p, i) => <p key={i}>{p}</p>)} </li>: ''} </ul> </div> </div>; } export default HNArticle;


コードを実行すると、次のようなインターフェイスが表示されるはずです。




質問すると、しばらくすると出力が表示されるはずです。


そして出来上がり!通常の RAG システムを超える高度なクエリ機能を備えたチャットボットの構築に成功しました。

結論

今日の記事では、強力なコード生成機能を備えたチャットボットを構築しました。これは、多くの AI パイオニアによって現在構築されている新しいクラスの LLM アプリケーションの一例であり、データ、プログラミング言語、自然言語理解を活用して、専門知識を備えた生成 AI システムを構築できます。


これらの専門システムは、OpenAI や Anthropic などのプラットフォーム プロバイダーから直接入手できるものを超える価値を提供しようとする LLM アプリケーションの商業的実現可能性を引き出す鍵となります。


コード生成は、市販されている大規模な言語モデルの最新世代によって可能になった手法の 1 つにすぎません。


LLM を商業化する方法についてアイデアがある場合、または AI について話し合いたい場合は、お気軽にLinkedInまたはGitHubまでご連絡ください。昨年、私は読者と多くの洞察に満ちた会話をしてきましたが、今後もたくさんの会話を楽しみにしています。