こんにちは。GMOインターネットグループ株式会社の新里です。最近はChatGPTのAPIを使った物をよく見かけるようになってきましたね。ここでは、ChatGPTのAPIを使ってニュース記事の問い合わせをしてみた内容について記載します。ChatGPTを使うとき、APIを直接実行したりプロンプトを工夫しても良いのですが、できれば最新のニュース記事や文章を使いたくなります。
何をするか?
GMOのニュース記事はRSSで配信されていました。https://www.gmo.jp/rss/このニュース記事を使ってChatGPTに色々と聞いてみようといった感じです。そして、質問はこの3点です。
GMOについての記事をざっくり要点をまとめて。その記事の内容で注目する重要なポイントを3点箇条書きにして。そのポイントは今後はどういう将来性・ビジネスになっていくのか?
以下は2023/04/27日のニュース記事を自社のHPに対してクローリングして、ChatGPTに問い合わせたものです。
日本語で教えて下さいと付け加えているのは、たまに英語で返事をしてくるので、「日本語で答えて下さい」と言語の指定を行っています。毎日いろいろなニュースが配信されていて、それをチェックするのも面倒なので、まとめて教えて貰って、ニュースになった内容のサマリー・どういう将来性があるか?教えてよ!!といった感じですね。あと、テキスト以外にもPDFのような記事を組み込んだり、ニュース以外のデータをじゃんじゃん取り込んで使うのもありですね。
コード全体
いきなりコード全体です。環境はDjangoのCommandで使います。ざっくりこんな感じのコマンドで使えるようにしておくと、バッチでslackに記事をまとめて通知みたいなこともしています。# 記事の取得(引数に日にちで、何日前のデータか?といった感じ)python manage.py rss_batch crawl 1# 記事の解析結果(引数は同じ)python manage.py rss_batch article1コード全体は以下です。
import logging
import os
import environ
import chromadb
import feedparser
from datetime import datetime, timedelta, date
from django.conf import settings
from django.core.management.base import BaseCommand
from langchain.vectorstores import Chroma
from langchain.chat_models import ChatOpenAI
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.text_splitter import CharacterTextSplitter
from langchain.chains import ConversationalRetrievalChain
from chromadb.config import Settings
from chromadb.utils import embedding_functions
from newspaper import Article, Config
logger = logging.getLogger(__name__)
class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument('attrs', nargs='+', type=str)
def getcontent(self, days):
yesterday = datetime.now() - timedelta(days)
client = chromadb.Client(Settings(
chroma_db_impl="duckdb+parquet",
persist_directory=os.environ["CHROMA_DB_DIR"]
))
openai_ef = embedding_functions.OpenAIEmbeddingFunction(
api_key=os.environ["OPENAI_API_KEY"],
model_name="text-embedding-ada-002"
)
collection = client.get_or_create_collection(
name="langchain" + yesterday.strftime('%Y%m%d'),
embedding_function=openai_ef)
# fetch feed
rss = feedparser.parse('https://www.gmo.jp/rss/')
for item in rss.entries:
updateTime= datetime.strptime(item.published,
'%Y-%m-%d %H:%M:%S').strftime('%Y/%m/%d')
if updateTime == yesterday.strftime('%Y/%m/%d'):
try:
config = Config()
config.request_timeout = 20
website = Article(item.link, language='ja', config=config)
website.download()
website.parse()
website.nlp()
text_splitter = CharacterTextSplitter(
separator = "\n\n",
chunk_size = 400,
chunk_overlap = 0,
length_function = len,
)
docs = text_splitter.create_documents([website.text],
metadatas=[{"source": item.link}])
for idx, doc in enumerate(docs):
collection.add(documents=[doc.page_content,],
metadatas=[doc.metadata,],
ids=[doc.metadata['source']+"_"+str(idx),])
except Exception as e:
logger.error(e)
continue
client.persist()
def select_day_article(self, days):
yesterday = date.today() - timedelta(days)
print (f"{yesterday.strftime('%Y/%m/%d')}の記事について")
embeddings = OpenAIEmbeddings()
vectordb = Chroma(persist_directory=os.environ["CHROMA_DB_DIR"],
collection_name="langchain" + yesterday.strftime('%Y%m%d'),
embedding_function=embeddings)
retriever = vectordb.as_retriever()
retriever.search_kwargs['distance_metric'] = 'cos'
retriever.search_kwargs['fetch_k'] = 40
retriever.search_kwargs['maximal_marginal_relevance'] = True
retriever.search_kwargs['k'] = 8
llm = ChatOpenAI(temperature=0.7, model_name="gpt-3.5-turbo")
# llm = ChatOpenAI(temperature=0.7, model_name="gpt-4")
qa = ConversationalRetrievalChain.from_llm(llm, retriever, return_source_documents=True)
questions = ["GMOについての記事をざっくり要点をまとめて日本語で箇条書きにして教えて下さい。",
"その記事の内容で注目する重要なポイントを箇条書きにして3点、日本語で教えて下さい。",
"そのポイントは今後はどういう将来性・ビジネスになっていくのか?日本語で教えて下さい。"]
chat_history = []
for q in questions:
result = qa({"question": q, "chat_history": chat_history})
chat_history.append((q, result['answer']))
print(f"**質問**: \n{q} \n")
print(f"**回答**: \n{result['answer']} \n")
def handle(self, *args, **options):
env = environ.Env()
env.read_env(os.path.join(settings.BASE_DIR, ".env"))
os.environ["OPENAI_API_KEY"] = os.environ["OPENAI_API_KEY"]
attrs = options['attrs']
if attrs[0] == "crawl":
self.getcontent(int(attrs[1]))
elif attrs[0] == "article":
self.select_day_article(int(attrs[1]))
必要なモジュールは以下です。pip install chromadb feedparser langchain newspaper
ざっくり内容の説明
流れは非常に簡単です。■ データの取得・RSSフィードからデータを取得してくる(feedparser)・特定の日の記事から本文を取得してくる(newspaper)・ベクトル化する(text-embedding-ada-002)・ベクタストアにデータとして保存する(chroma)■ Chat GPTとのやりとり・ベクタストアの設定をする(chroma)・ベクタストア経由でOpenAIに質問をする(ConversationalRetrievalChain)ここではGMOのRSSフィードからデータを取り込んで、Chromaに日付付きのコレクション名で保存していますが、他のRSSフィードやデータを別々のコレクションに取り込んでおくのもありですね。また、LangChainが強力に使えるので、積極的に使っているところです。OpenAI SDKのAPIを直接実行するのも良いですが、問い合わせのモデルが充実しているのでLangChainは非常に便利ですね。ただ、ポイントになるのは "retriever.search_kwargs" の近隣探索をしている所になりそうです。L2/L1/max/cos/dot(デフォルトはL2ノルム)といった方法、MMRに送るドキュメント数の調整、そして最終的な探索数(k=8)を多くするとGPTの文字数制限でエラーが起きたりするので要調整になりますね。
ChatGPT UIに組み込んでみる
ChatGPTライクなUIを開発しているchatbot-uiにサクッと組み込んでました。自由な問い合わせでWeb UIで使えるので、これはこれで便利な感じになりますね。そして、同じくDjangoをバックエンドにして、ニュースで公開されているPDFの記事を組み込んでみました。chatbot-ui(フロントエンド)はNext.jsで作られていました。
「GMOとくとくBBに関係した最近のニュースを教えてちょ」と聞いています。
GPT-3.5、GPT-4(32K)、PDFなどの文章、ニュース記事でモデルを切り替えて使えるのでカスタマイズした問い合わせを色々と並べて使ったりしても良いかもです。
参考
色々と作りにあたって参考にさせて頂いた記事を紹介しておきます。LangChain話題の ChatGPT + LangChain で ChatGPT が学習していない最新の OSS ソースコードを爆速でウォークスルーするChatGPT風の画面を表示できるChatbot UIをFastAPIで作成した自作LangChainサーバに接続させる方法