【LangChain】PDFをメタデータとともに読み込む(作成中)
前回の記事で、chatGPTを使ってPDFファイルを読み込んで、要約を試みました。
内容については4.oを使うと比較的満足できる回答が得られるのですが、ページ数が読み取れなかったり、章や節の構成が不十分といった問題が残りました。
そこで、このような問題を解決したPDF書類読み取りアプリケーションを開発したいと思います。
PDF読み込みライブラリ
langchainのこちらのページにはいくつかのPDF読み込みのためのライブラリが紹介されています。
今回はシンプルで使いやすそうだったPyPDFLoaderと、非構造データの読み込みに強そうなUnstructuredPDFLoaderを試しました
PyPDFLoader
基本的には公式チュートリアルの通りにやっていればできます。
loader = PyPDFLoader("000213033.pdf") # PDFファイルを指定
pages = loader.load_and_split()
pagesはDocumentのリストであり、pdfファイルの1ページが1Documentに該当します。
(テキストの読み込みミスや、途切れることなく、一字一句正確に読み込まれていました)
pages[1]
Document(page_content='1 \n \n \nはじめに \n \n○ 生物多様性 COP15にて採択された「昆明・モントリオール生物多様性枠組」 では、・・・途中省略・・・ 出所:国際統合報告< IR>フレームワーク (2021年1月)', metadata={'source': '000213033.pdf', 'page': 1})
このようにmetadataとしてソース元ファイル名とページが入っていることがわかります
FAISSにぶち込んで近傍検索は以下の通りです。
faiss_index = FAISS.from_documents(pages, OpenAIEmbeddings(model="text-embedding-3-small"))
docs = faiss_index.similarity_search("気候変動への取り組みと生物多様性への取り組みの違いは?", k=3)
for doc in docs:
print(str(doc.metadata["page"]) + ":", doc.page_content[:150])
---
22: 22 6. 今後の取組 ~自然と共生する 世界の実現に向けて~
(1) 今後の課題
○ 本戦略は、 2030年を目標年度とする生物多様性国家戦略の基本戦略3「ネイチャーポ
ジティブ経済の実現」について企業が押えておくべき要素やそれを支える国の施策を
具体化したものであり、すなわち 20
---
18: 18 <施策の方向性>
○ DXの進展は、価値創造プロセスの全般にわたって鍵となる。例えば、リスクの認識・
特定に際しては場所に紐付いた分析に必要な一次情報データベースが 必要であり、 バ
リューチェーンの把握にはデジタル技術を用いたトレーサビリティの確保が有効であ
る。取組の効果の見える化のた
---
4: 4 創造にもつながっている現状に鑑みれば、自然 資本の保全・回復に関しても、企業に
よるソリューションの提供がネイチャーポジティブ実現の 推進力となることが十分に
期待できる。
○ その際、 地球規模生物多様性概況第5版( GBO5)14において描写されているように、
ネイチャーポジティブ の実
metadataにページが入っているので、近傍検索で探し出した時に何ページ目にその文があるかを抽出することができました。
(ただ近傍検索結果は、そこ抜き出す?という箇所を返してきているので、このへんはチューニング必要かもしれません)
次にchromadbを使って質問の回答を返してやるようにしましょう。
vectorstore = Chroma.from_documents(documents=pages, embedding=OpenAIEmbeddings())
retriever = vectorstore.as_retriever()
prompt = hub.pull("rlm/rag-prompt")
llm = ChatOpenAI(model="gpt-4o", temperature=0)
def format_docs(docs):
return "\n\n".join(doc.page_content for doc in docs)
rag_chain = (
{"context": retriever | format_docs, "question": RunnablePassthrough()}
| prompt
| llm
| StrOutputParser()
)
rag_chain.invoke("研究開発・技術実証支援として、どういった施策がありますか")
#->
研究開発・技術実証支援としての施策には、生物多様性保全に貢献する技術・サービスに対する支援や、遺伝資源の利用に伴うABSの実施が含まれます。また、化学農薬や化学肥料の使用量を低減し、有機農業を推進する「みどりの食料システム戦略」もあります。これらの施策は、持続可能な環境保全型の農林水産業の拡大を目指しています。
今回はとりあえずチュートリアル通りの簡単な書き方にしました。
この質問自体は19ページ目に該当箇所があり、回答すべき箇所は20ページ目にありますが、AIは23ページを抽出してきました。
このあたり、最新の4.oでもまだまだ国語力をサポートしないといけなさそうです。
Unstructured
使い方はPyPDFLoaderとほぼ同じですが、いくつか注意が必要です。
from langchain_community.document_loaders import UnstructuredPDFLoader
loader = UnstructuredPDFLoader("000213033.pdf", mode="paged", languages=['ja'])
pages = loader.load()
modeはデフォルトでは'single'となっており、これだとpdfファイルのページを無視して単一ページとして読み込まれてしまいます。'paged'に指定することで、1ページ1Documentとして読み込みます。
languages(配列ですので注意)はデフォルトでは'eng'になっています。それでも日本語として読み込めましたが、気持ち悪いので'ja'を指定しておきましょう
pages[1]
Document(
page_content='はじめに\n\n生物多様性 COP15 にて採択された「昆明・モントリオール生物多様性枠組」では、・・・出所:国際統合報告<IR>フレームワーク(2021 年1月)\n\n1\n\n',
metadata={
'source': '000213033.pdf',
'coordinates': {'points': ((294.77, 768.9552), (294.77, 779.5151999999999), (303.58567999999997, 779.5151999999999), (303.58567999999997, 768.9552)),
'system': 'PixelSpace',
'layout_width': 595.32,
'layout_height': 841.92},
'filename': '000213033.pdf',
'languages': ['ja'],
'last_modified': '2024-05-19T03:44:47',
'page_number': 2,
'filetype': 'application/pdf',
'parent_id': 'f4041535dda232505ce51e779e300aa8'})
見ての通り、PyPDFLoaderを使った時と比べて多くのメタ情報を格納しています。
ただ、このメタ情報のせいで、chromedbにぶち込めません
vectorstore = Chroma.from_documents(
documents=pages,
embedding=OpenAIEmbeddings(model="text-embedding-3-small")
)
#->
ValueError: Expected metadata value to be a str, int, float or bool, got {'points': ((252.65, 693.1179999999999), (252.65, 711.1179999999999), (351.67, 711.1179999999999), (351.67, 693.1179999999999)), 'system': 'PixelSpace', 'layout_width': 595.32, 'layout_height': 841.92} which is a dict
During handling of the above exception, another exception occurred:
Try filtering complex metadata from the document using langchain_community.vectorstores.utils.filter_complex_metadata.
Chroma.from_documentsはmetadataにdictは受け付けていないので、dict型のデータは消す必要があります。
メッセージにあるようにfilter_complex_metadataを使って、使えないmetadataを消しましょう
from langchain_community.vectorstores.utils import filter_complex_metadata
pages2 = filter_complex_metadata(pages)
これでぶちこめるようになります。
vectorstore = Chroma.from_documents(
documents=pages2,
embedding=OpenAIEmbeddings(model="text-embedding-3-small")
)
今後の課題
metadata付きでPDF書類をベクトルストアに変換することはできましたが、精度の高い回答を得たり、AIがその答えを返した理由を知るためには、エージェントを作成するなどもう一工夫必要そうです。
Discussion