Spaces:
Sleeping
Sleeping
from dotenv import load_dotenv | |
from langchain.document_loaders import PyPDFLoader | |
from langchain_openai import ChatOpenAI, OpenAIEmbeddings | |
# from langchain_openai import AzureChatOpenAI, AzureOpenAIEmbeddings | |
from langchain.indexes import VectorstoreIndexCreator | |
from langchain.vectorstores import Chroma | |
import csv | |
import json | |
import requests | |
from langchain.prompts import ChatPromptTemplate | |
from langchain.text_splitter import ( | |
CharacterTextSplitter, | |
RecursiveCharacterTextSplitter, | |
) | |
from langchain_core.output_parsers import StrOutputParser | |
from bs4 import BeautifulSoup | |
from pydantic import BaseModel | |
from logging import getLogger | |
import os | |
import re | |
import glob | |
import pandas as pd | |
import chromadb | |
from rank_bm25 import BM25Okapi | |
from janome.tokenizer import Tokenizer | |
from collections import OrderedDict | |
from openai import OpenAI | |
from src.myLogger import set_logger | |
import time | |
logger = set_logger("my_app", level="INFO") | |
load_dotenv(".env.dev") | |
json_schema_1 = { | |
"type": "object", | |
"properties": { | |
"judge": { | |
"type": "integer", | |
"description": "脱炭素に関連する情報があれば1,そうでなければ0" | |
}, | |
"reason": { | |
"type": "string", | |
"description": "どうしてそう判断したのか" | |
} | |
}, | |
"required": ["judge", "reason"] | |
} | |
json_schema_2 = { | |
"type": "object", | |
"properties": { | |
"judge": { | |
"type": "integer", | |
"description": "脱炭素取り組みに関連する明確で具体的な情報があれば1,そうでなければ0" | |
}, | |
"reason": { | |
"type": "string", | |
"description": "具体的な取り組みの例を説明してください。" | |
} | |
}, | |
"required": ["judge", "reason"] | |
} | |
class Document(BaseModel): | |
page_content: str | |
metadata: dict = {} | |
# Tokenizerの初期化 | |
t = Tokenizer() | |
# 文書用のTokenizerの定義 | |
def tokenize(text): | |
return [token.surface for token in t.tokenize(text)] | |
# クエリ用のTokenizerの定義 | |
def query_tokenize(text): | |
return [token.surface for token in t.tokenize(text) if token.part_of_speech.split(',')[0] in ["名詞", "動詞", "形容詞"]] | |
def normalize_text(s, sep_token = " \n "): | |
s = re.sub(r'\s+', ' ', s).strip() | |
s = re.sub(r". ,","",s) | |
s = s.replace("..",".") | |
s = s.replace(". .",".") | |
s = s.replace("\n", "") | |
s = s.strip() | |
return s | |
def generate_answer_(reference, system_prompt, json_schema): | |
api_key = os.getenv("OPENAI_API_KEY") | |
client = OpenAI( | |
api_key=api_key, | |
) | |
response = client.chat.completions.create( | |
model="gpt-3.5-turbo", | |
messages=[ | |
{ | |
"role": "system", | |
"content": system_prompt, | |
}, | |
{ | |
"role": "user", | |
"content": reference, | |
}, | |
], | |
functions=[{"name": "generate_queries", "parameters": json_schema}], | |
function_call={"name": "generate_queries"}, | |
temperature=0.0, | |
top_p=0.0, | |
) | |
output = response.choices[0].message.function_call.arguments | |
return output | |
def research_html_hybrid( | |
pdf_url, | |
company_name, | |
): | |
first_q_prompt = f""" | |
次の資料に、{company_name}が脱炭素に関連する情報が含まれているか、具体例を含めて答えてください。 | |
[出力フォーマット] | |
{{ | |
judge: 0 or 1(脱炭素に関連する情報があれば1,そうでなければ0) | |
reason: "どうしてそう判断したのか具体的に説明してください。" | |
}} | |
""" | |
second_q_prompt = f""" | |
この資料に、{company_name}が行っている具体的な脱炭素取り組みに関連する情報が含まれているか、判断をしてください。 | |
以下の要件を必ず守ってください。 | |
・具体的な取り組みが書かれていないものは含めないでください。 | |
[出力フォーマット] | |
{{ | |
judge: 0 or 1(具体的な脱炭素・省エネ・カーボンニュートラルの取り組みに関連する情報があれば1,そうでなければ0) | |
reason: "どうしてそう判断したのか具体的に説明してください。" | |
}} | |
===example1=== | |
[内容] | |
脱炭素に関する取り組みを・貢献を積極的に行っています。 | |
[出力] | |
{{ | |
judge: 0 | |
reason: "「脱炭素に関する取り組み・貢献」という発言は抽象的であり、具体的な取り組みではありません。" | |
}} | |
===example2=== | |
[内容] | |
太陽光パネルの設置を行い、省エネの取り組みを積極的に行っています。 | |
[出力] | |
{{ | |
judge: 1 | |
reason: "「太陽光パネルの設置」という具体的な取り組みが書かれているため。" | |
}} | |
===example3=== | |
[内容] | |
脱炭素社会への貢献を目指しています。 | |
[出力] | |
{{ | |
judge: 0 | |
reason: "「脱炭素社会への貢献」は抽象的であり、具体的な取り組みではないため。" | |
}} | |
""" | |
group = '' | |
url = pdf_url | |
# リクエストをきちんと送るためのもの | |
headers = { | |
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36" | |
} | |
print(f"requesting {url}") | |
result = requests.get(url, headers=headers, timeout=5) | |
print(result.status_code) | |
result.encoding = result.apparent_encoding | |
soup = BeautifulSoup(result.text, "html.parser") | |
paragraphs = soup.find_all("p") | |
full_text = ' '.join([paragraph.get_text() for paragraph in paragraphs]) | |
# 連結したテキストを1つのDocumentオブジェクトとして扱う | |
documents = [Document(page_content=full_text, metadata={})] | |
# documents = [Document(page_content=paragraph.get_text(), metadata={}) for paragraph in paragraphs] | |
text_splitter = RecursiveCharacterTextSplitter( | |
chunk_size=2000, chunk_overlap=50 | |
) | |
df = pd.DataFrame(columns=["source", "chunk_no", "content"]) | |
# テキストを読み込んで分割し、DataFrameに格納 | |
chunk = text_splitter.split_documents(documents) | |
filename = pdf_url | |
for i, c in enumerate(chunk): | |
# print(f"chunk {c.page_content}") | |
df = pd.concat([df, pd.DataFrame({"ID": str(i),"source": filename, "chunk_no": i, "content": c.page_content}, index=[0])]) | |
# DataFrameを表示 | |
df["content"] = df["content"].apply(lambda x: normalize_text(x)) | |
df = df.reset_index(drop=True) | |
# contentとmetadataをリスト型で定義 | |
content_list = df["content"].tolist() | |
metadata_list = df[["ID", "source"]].to_dict(orient="records") | |
# Embeddingの定義 | |
embeddings = OpenAIEmbeddings( | |
openai_api_key=os.getenv("OPENAI_API_KEY"), | |
) | |
# VectorStoreの定義 | |
client = chromadb.PersistentClient() | |
db = Chroma( | |
collection_name="langchain_store", | |
embedding_function=embeddings, | |
client=client, | |
) | |
ids = df["ID"].tolist() | |
# Vectorstoreにデータを登録 | |
db.add_texts( | |
texts=content_list, | |
metadatas=metadata_list, | |
embeddings=embeddings, | |
ids=ids, | |
) | |
# クエリと関連する文を5件検索してRAGでansを生成 | |
n = 10 | |
# 文書を単語リストに分割 | |
tokenized_documents = [tokenize(doc) for doc in content_list] | |
# BM25 | |
bm25 = BM25Okapi(tokenized_documents) | |
# まず初めに脱炭素に言及しているか調べる | |
query = '脱炭素・省エネ・カーボンニュートラルに関連する情報' | |
vector_top = db.similarity_search(query=query, k=n) | |
vector_rank_list = [{"content": doc.page_content, "vector_rank": i+1} for i, doc in enumerate(vector_top)] | |
df_vector = pd.DataFrame(vector_rank_list) | |
# キーワード検索 | |
tokenized_query = query_tokenize(query) | |
keyword_top = bm25.get_top_n(tokenized_query, content_list, n=n) | |
keyword_rank_list = [{"content":doc, "keyword_rank":i+1} for i, doc in enumerate(keyword_top)] | |
df_keyword = pd.DataFrame(keyword_rank_list) | |
# 順位を結合して表示 | |
df_rank = pd.merge(df_vector, df_keyword, on="content", how="left") | |
df_rank["hybrid_score"] = 1/(df_rank["vector_rank"]+60) + 1/(df_rank["keyword_rank"]+60) | |
df_rank = pd.merge(df_rank, df, on="content", how="left") | |
df_rank.sort_values(by="hybrid_score", ascending=False).head() | |
# 結果をCSVファイルとして保存 | |
df_rank_sorted = df_rank.sort_values(by="hybrid_score", ascending=False) | |
# df_rank_sorted.to_csv("search_rank_results.csv", index=False) | |
search_results = df_rank_sorted["content"].tolist() | |
unique_search_results = list(OrderedDict.fromkeys(search_results)) | |
relevent_texts = ".\n".join([doc for doc in search_results]) | |
context = relevent_texts[:10000] | |
ret = generate_answer_(context, first_q_prompt, json_schema_1) | |
js = json.loads(ret) | |
logger.info(f"first judge output:{js}") | |
if js["judge"] == 1: | |
ret2 = generate_answer_(context, second_q_prompt, json_schema_2) | |
js2 = json.loads(ret2) | |
logger.info(f"second judge output:{js2}") | |
if js2["judge"] == 1: | |
group = 'Group 3' | |
else: | |
group = 'Group 1-1' | |
else: | |
group = 'Group 5' | |
try: | |
client.delete_collection("langchain_store") | |
except Exception as e: | |
logger.error(f"An error occurred during collection deletion: {e}") | |
time.sleep(1) | |
return group | |