0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

論文情報からMarkdown、そして総説へ:LangChain×LLM 実践

Posted at

user:

次のような形式で論文情報を格納した pandas dataframe データがあります。text列の中身は基本的にはJSON形式で書かれていますが、中にはJSONとして読み込めない(形が崩れた)文字列もあります。

                                    URL  \
141   http://arxiv.org/abs/2404.06910v2   
229   http://arxiv.org/abs/2409.09641v2   
697   http://arxiv.org/abs/2504.03767v2   
1034  http://arxiv.org/abs/2506.19676v1   
1140  http://arxiv.org/abs/2506.18096v1   
1228  http://arxiv.org/abs/2506.23260v1   
1357  http://arxiv.org/abs/2508.12538v1   
1358  http://arxiv.org/abs/2501.00539v2   
1359  http://arxiv.org/abs/2508.15760v1   
1364  http://arxiv.org/abs/2508.01780v1   
1366  http://arxiv.org/abs/2508.14704v1   
1367  http://arxiv.org/abs/2504.16736v3   
1368  http://arxiv.org/abs/2508.20453v1   
1369  http://arxiv.org/abs/2508.17068v2   
1370  http://arxiv.org/abs/2508.10991v2   
1371  http://arxiv.org/abs/2506.02040v3   
1372  http://arxiv.org/abs/2508.14925v1   

                                                   text  
141   {"Superposition Prompting: Improving and Accel...  
229   {"AACessTalk: Fostering Communication between ...  
697   {"MCP Safety Audit: LLMs with the Model Contex...  
1034  {"A Survey of LLM-Driven AI Agent Communicatio...  
1140  {"Deep Research Agents: A Systematic Examinati...  
1228  {"From Prompt Injections to Protocol Exploits:...  
1357  {"Systematic Analysis of MCP Security": {"URL"...  
1358  {"MCP-Solver: Integrating Language Models with...  
1359  {"LiveMCP-101: Stress Testing and Diagnosing M...  
1364  {"LiveMCPBench: Can Agents Navigate an Ocean o...  
1366  {"MCP-Universe: Benchmarking Large Language Mo...  
1367  {"A Survey of AI Agent Protocols": {"URL": "ht...  
1368  {"MCP-Bench: Benchmarking Tool-Using LLM Agent...  
1369  {"Anemoi: A Semi-Centralized Multi-agent Syste...  
1370  {"MCP-Guard: A Defense Framework for Model Con...  
1371  {"Beyond the Protocol: Unveiling Attack Vector...  
1372  {"MCPTox: A Benchmark for Tool Poisoning Attac... 

ここから1行ずつ読み込んで、 text の内容を日本語の論文の1節のような文章の文字列に変換し、1行ごとに1つのテキストファイルとして、そのリストを返すPython関数を書いてください。なお、LLMの設定は次のとおりとします。

# =============================
# ✅ 設定
# =============================
CONFIG_PATH = "/content/drive/MyDrive/path_config/config-32k.json"
drive.mount("/content/drive")

with open(CONFIG_PATH, "r") as f:
    config = json.load(f)

AZURE_API_KEY = config["OPENAI_API_KEY"]
AZURE_API_VERSION = config["OPENAI_API_VERSION"]
AZURE_ENDPOINT = config["OPENAI_API_BASE"]
AZURE_MODEL = config["OPENAI_API_ENGINE"]  # 32k モデル指定

# =============================
# ✅ モデル&Embedding設定
# =============================
def get_llm(temperature: float = 0.3, max_tokens: int = 15000) -> AzureChatOpenAI:
    """Azure OpenAI LLMクライアント(LangChain対応)"""
    return AzureChatOpenAI(
        deployment_name=AZURE_MODEL,
        openai_api_key=AZURE_API_KEY,
        openai_api_version=AZURE_API_VERSION,
        azure_endpoint=AZURE_ENDPOINT,
        temperature=temperature,
        max_tokens=max_tokens,
    )

embeddings = HuggingFaceEmbeddings(
    model_name="intfloat/multilingual-e5-base",
    model_kwargs={"device": "cpu"},
    encode_kwargs={"normalize_embeddings": True},
)

assistant:

なるほど!要件を整理すると、

  • pandas.DataFrameURL 列と text 列がある。
  • text 列は原則 JSON 形式だが、壊れている場合もある。
  • 1行ずつ処理して、text の中身を 日本語の論文の1節風の文章 に変換したい。
  • 変換には Azure OpenAI (AzureChatOpenAI) を使う。
  • 各行の結果を テキストファイル として保存し、そのファイルパスのリストを返す関数が欲しい。

そこで次のような関数を提案します👇

import os
import json
import pandas as pd
from typing import List
from langchain_openai import AzureChatOpenAI
from langchain.prompts import ChatPromptTemplate

# =============================
# ✅ LLMクライアント取得
# =============================
def get_llm(temperature: float = 0.3, max_tokens: int = 15000) -> AzureChatOpenAI:
    """Azure OpenAI LLMクライアント"""
    return AzureChatOpenAI(
        deployment_name=AZURE_MODEL,
        openai_api_key=AZURE_API_KEY,
        openai_api_version=AZURE_API_VERSION,
        azure_endpoint=AZURE_ENDPOINT,
        temperature=temperature,
        max_tokens=max_tokens,
    )

# =============================
# ✅ 論文テキスト変換関数
# =============================
def convert_papers_to_textfiles(
    df: pd.DataFrame,
    output_dir: str = "./papers_text"
) -> List[str]:
    """
    DataFrameの各行の text を日本語の論文風文章に変換し、テキストファイルに保存する。
    
    Args:
        df: DataFrame(URL列とtext列を含む)
        output_dir: 保存ディレクトリ
    
    Returns:
        保存したファイルパスのリスト
    """
    os.makedirs(output_dir, exist_ok=True)
    llm = get_llm()

    file_paths = []
    prompt_template = ChatPromptTemplate.from_messages([
        ("system", "あなたは学術論文を日本語で要約・翻訳する専門家です。"),
        ("user", "次の内容を日本語で論文の1節のように整えてください:\n\n{text}")
    ])

    for idx, row in df.iterrows():
        raw_text = row["text"]

        # JSONの読み込みを試みる(失敗したらそのまま文字列として使う)
        parsed_text = None
        try:
            parsed = json.loads(raw_text)
            if isinstance(parsed, dict):
                # dict の場合は key + value をまとめた文章化
                parsed_text = " ".join([f"{k}: {v}" for k, v in parsed.items()])
            else:
                parsed_text = str(parsed)
        except Exception:
            parsed_text = raw_text  # 壊れたJSONはそのまま使う

        # LLMに投げて日本語の論文風文章に変換
        chain = prompt_template | llm
        result = chain.invoke({"text": parsed_text})

        # ファイルに保存
        file_path = os.path.join(output_dir, f"paper_{idx}.txt")
        with open(file_path, "w", encoding="utf-8") as f:
            f.write(result.content)

        file_paths.append(file_path)

    return file_paths

✅ ポイント

  1. 壊れたJSON対策
    json.loads に失敗したらそのまま文字列で処理。

  2. 辞書型JSONの整形
    {"Title": "xxx", "URL": "yyy"} みたいなのは Title: xxx URL: yyy のように変換。

  3. LangChain の Prompt
    system メッセージで「日本語論文の専門家」と指示し、user メッセージでテキストを渡して整形。

  4. 出力形式
    各行を paper_<index>.txt というファイルに保存してリストで返す。


先輩の環境では、保存先は Google Drive 上(例: /content/drive/MyDrive/papers_text)にしておくと便利ですが、どうしましょうか?
👉 保存先を Google Drive にしますか?それとも ローカル(Colabの一時環境) にしますか?

user:

その前に、このワーニングはどう対処するんでしたっけ。

/tmp/ipython-input-1893791419.py:33: LangChainDeprecationWarning: The class `HuggingFaceEmbeddings` was deprecated in LangChain 0.2.2 and will be removed in 1.0. An updated version of the class exists in the :class:`~langchain-huggingface package and should be used instead. To use it run `pip install -U :class:`~langchain-huggingface` and import as `from :class:`~langchain_huggingface import HuggingFaceEmbeddings``.
  embeddings = HuggingFaceEmbeddings(

assistant:

あー、先輩、いいところに気づきました✨
そのワーニングは LangChain の HuggingFaceEmbeddings が廃止予定 になっていて、今後は別パッケージ(langchain-huggingface)を使ってね、というお知らせです。

✅ 解決方法

  1. まず新しいパッケージをインストールします:
pip install -U langchain-huggingface
  1. そしてインポートを修正します:
# 古い書き方(非推奨)
from langchain.embeddings import HuggingFaceEmbeddings

# 新しい書き方(推奨)
from langchain_huggingface import HuggingFaceEmbeddings
  1. あとは同じように使えます:
embeddings = HuggingFaceEmbeddings(
    model_name="intfloat/multilingual-e5-base",
    model_kwargs={"device": "cpu"},
    encode_kwargs={"normalize_embeddings": True},
)

✅ 補足

  • ワーニングは「将来削除されますよ」という注意なので、急いで修正しなくても今は動きます
  • ただ、LangChain 本体から外部モジュールに分離されたので、今後は新しい方に移行しておくのがおすすめです。

先輩、このまま埋め込み(HuggingFaceEmbeddings)を 全部新しい書き方に直しちゃいますか? それともワーニングを無視して一旦進めちゃいます?

user:

ありがとうございます。動くようにはなりました。結果の1例は次のとおりです。

以下は、与えられた情報を基に作成した日本語の学術論文の一節の例です。

---

### タンパク質特性予測と構造理解における多モーダル言語モデルの応用

本研究では、タンパク質の構造および機能に関する情報を統合的に解析するための新しい多モーダル言語モデル「ProteinGPT」を提案する。このモデルは、タンパク質の配列(FASTA形式)と構造情報を統合し、タンパク質特性予測や構造理解を目的とした質問応答タスクにおいて高精度な結果を提供することを目指している。従来の大規模言語モデル(LLMs)であるVicuna、Mistral、LLaMA-3、LLaMA-2、さらにGPT-4やGPT-3.5と比較した結果、ProteinGPTは閉じた質問に対する回答の正確性において優れた性能を示した。具体的には、タンパク質の機能予測や構造解析において、従来モデルよりも精度が向上し、数値や事実に基づく回答が可能となった。

本研究の新規性は、タンパク質配列と構造情報を統合することで、従来のLLMsが抱える課題を克服し、より正確な回答生成を実現した点にある。これにより、タンパク質の機能予測や構造解析における科学的洞察の提供が可能となり、生命科学分野における応用が期待される。一方で、さらなる課題として、より多様なタンパク質ファミリーや複雑な生物学的問題への対応が挙げられる。今後は、モデルの汎用性を高めるとともに、実験データとの統合を進め、モデルの検証と改善を図る必要がある。

---

このように、与えられた情報を整理し、学術論文の形式に適した形でまとめました。必要に応じて、追加の情報や詳細を補足することで、さらに具体的な内容にすることが可能です。

以上の文面のうち、冒頭の「以下は、与えられた情報を基に作成した〜」のような部分とか、最後の「このように、与えられた情報を整理し、〜」の部分は出力しないようにしてください。

また、与えられたtextは、総説論文がその論文を紹介するような書き方で、情報量をできるだけ失わないように書き換えるようにしてください。

assistant:

なるほど!つまり先輩が欲しいのは、

  • 余計な説明文(冒頭の「以下は…」や最後の「このように…」)を出さない
  • text の内容を、まるで総説論文が他の研究を紹介するようにまとめる
  • 情報を落とさないように整理・翻訳する

ということですね。

そうすると、LLMに渡すプロンプトを少し工夫すれば実現できます。
たとえば、先ほどの関数をこんなふうに修正できます👇

def convert_papers_to_textfiles(
    df: pd.DataFrame,
    output_dir: str = "./papers_text"
) -> List[str]:
    """
    DataFrameの各行の text を日本語の総説論文風文章に変換し、テキストファイルに保存する。
    
    Args:
        df: DataFrame(URL列とtext列を含む)
        output_dir: 保存ディレクトリ
    
    Returns:
        保存したファイルパスのリスト
    """
    os.makedirs(output_dir, exist_ok=True)
    llm = get_llm()

    file_paths = []
    prompt_template = ChatPromptTemplate.from_messages([
        ("system", "あなたは学術論文を日本語で要約・翻訳する専門家です。"),
        ("user",
         "次の内容を、日本語の総説論文がその研究を紹介するような書き方で整えてください。\n"
         "冒頭に「以下は…」などの説明を入れず、また最後に解説やまとめを追加せず、"
         "本文のみを出力してください。\n\n{text}")
    ])

    for idx, row in df.iterrows():
        raw_text = row["text"]

        # JSONの読み込みを試みる(失敗したらそのまま文字列として使う)
        parsed_text = None
        try:
            parsed = json.loads(raw_text)
            if isinstance(parsed, dict):
                parsed_text = " ".join([f"{k}: {v}" for k, v in parsed.items()])
            else:
                parsed_text = str(parsed)
        except Exception:
            parsed_text = raw_text  # 壊れたJSONはそのまま使う

        # LLMに投げて日本語の論文風文章に変換
        chain = prompt_template | llm
        result = chain.invoke({"text": parsed_text})

        # ファイルに保存
        file_path = os.path.join(output_dir, f"paper_{idx}.txt")
        with open(file_path, "w", encoding="utf-8") as f:
            f.write(result.content.strip())

        file_paths.append(file_path)

    return file_paths

✅ 修正ポイント

  1. プロンプトで指示を強化

    • 「説明文を入れない」
    • 「最後に解説を付けない」
    • 「総説論文が紹介するような調子で」
  2. 出力を .strip()
    → LLMが余計な空行をつけてもきれいに保存される。


これで、先輩の例みたいに「以下は〜」「このように〜」は出ず、純粋に論文紹介文だけが得られるはずです。

先輩、この変換結果って 1行1節ごとにバラバラのファイルにするのと、まとめて1本の総説文書にするのと、どっちのイメージに近いですか?

user:

ありがとうございます。まとめて1本の総説文書にするイメージですね。なので、論文URLを抜き出すのと、textからタイトルと著者と出版日を抜き出す処理も加えたいところです。

assistant:

なるほど!✨ じゃあ方向性を整理しますね。


✅ 先輩の要望

  • 各行をバラバラにファイル化するのではなく、まとめて1本の総説文書にする。

  • 各論文について

    • URL を抜き出して載せたい
    • text から タイトル・著者・出版日 をできるだけ抽出したい
    • その上で 総説論文が紹介するような日本語文章 に整形したい

✅ 実装方針

  1. DataFrameを1行ずつ処理し、

    • URL を取り出す
    • text をJSONとしてパース(失敗時は文字列)
    • タイトル・著者・出版日を探す
    • 本文紹介文をLLMで生成する
  2. それらをまとめて 1つのMarkdownやTXTに統合する。


✅ サンプル実装

import os
import json
import pandas as pd
from typing import Optional
from langchain_openai import AzureChatOpenAI
from langchain.prompts import ChatPromptTemplate


# =============================
# ✅ LLMクライアント取得
# =============================
def get_llm(temperature: float = 0.3, max_tokens: int = 15000) -> AzureChatOpenAI:
    """Azure OpenAI LLMクライアント"""
    return AzureChatOpenAI(
        deployment_name=AZURE_MODEL,
        openai_api_key=AZURE_API_KEY,
        openai_api_version=AZURE_API_VERSION,
        azure_endpoint=AZURE_ENDPOINT,
        temperature=temperature,
        max_tokens=max_tokens,
    )


# =============================
# ✅ textから情報抽出
# =============================
def extract_metadata(text: str) -> dict:
    """
    text(JSON文字列または壊れたJSON文字列)から
    タイトル・著者・出版日などを抜き出す。
    """
    meta = {"title": None, "authors": None, "date": None, "raw": text}

    try:
        parsed = json.loads(text)
        if isinstance(parsed, dict):
            # よくあるキーを探す
            for k, v in parsed.items():
                k_lower = k.lower()
                if "title" in k_lower:
                    meta["title"] = v
                elif "author" in k_lower:
                    meta["authors"] = v
                elif "date" in k_lower or "year" in k_lower:
                    meta["date"] = v
        elif isinstance(parsed, str):
            meta["title"] = parsed
    except Exception:
        # JSONでなければ文字列中から正規表現で探す方法もある
        meta["title"] = None

    return meta


# =============================
# ✅ 総説文書を作成
# =============================
def generate_review(df: pd.DataFrame, output_file: str = "review.txt") -> str:
    """
    DataFrameから総説論文風の文書をまとめて生成する。

    Args:
        df: DataFrame(URL列とtext列を含む)
        output_file: 保存ファイル名

    Returns:
        保存したファイルのパス
    """
    llm = get_llm()

    # 総説用の全体バッファ
    review_sections = []

    # 論文ごとのプロンプト
    prompt_template = ChatPromptTemplate.from_messages([
        ("system", "あなたは学術総説論文を執筆する日本語の専門家です。"),
        ("user",
         "次の論文情報をもとに、総説論文がその研究を紹介するように日本語で書いてください。\n"
         "余計な説明文や結論は加えず、本文のみを出力してください。\n\n"
         "タイトル: {title}\n"
         "著者: {authors}\n"
         "出版日: {date}\n"
         "URL: {url}\n"
         "内容: {text}")
    ])

    for idx, row in df.iterrows():
        url = row["URL"]
        raw_text = row["text"]

        # メタ情報抽出
        meta = extract_metadata(raw_text)

        # プロンプト入力
        chain = prompt_template | llm
        result = chain.invoke({
            "title": meta["title"] or "不明",
            "authors": meta["authors"] or "不明",
            "date": meta["date"] or "不明",
            "url": url,
            "text": meta["raw"]
        })

        section = result.content.strip()
        review_sections.append(section)

    # 全体をまとめる
    full_review = "\n\n".join(review_sections)

    # 保存
    with open(output_file, "w", encoding="utf-8") as f:
        f.write(full_review)

    return output_file

✅ 出力イメージ(例)

### タンパク質特性予測と構造理解における多モーダル言語モデルの応用
本研究では、...

URL: http://arxiv.org/abs/2404.06910v2

---

### LLM駆動型AIエージェント通信に関する調査
この研究は、...

URL: http://arxiv.org/abs/2506.19676v1

この形なら、1本のファイルにまとめた総説ができます。
さらに「タイトルは見出しに」「著者・日付を本文冒頭に入れる」なども調整可能です。


先輩、仕上がりは Markdown形式(見出し付き) にした方がいいですか? それとも プレーンテキスト で十分ですか?

user:

ありがとうございます。得られた文章が、文字列として result という変数に格納されたとします。LLMを使って、この result にある複数の論文の情報をもとに、LLMを使って総説の冒頭文を書くPython関数を作ってください。

assistant:

了解です、先輩😊
つまり、

  • すでに result という変数に、複数論文の紹介文が文字列として入っている
  • それを LLM に渡して、総説の冒頭文(イントロダクション) を自動生成したい

ということですね。

総説の冒頭文なので、

  • 分野の背景を説明する
  • 各研究の意義を総体的に位置づける
  • 「本総説では〜を扱う」といった流れを自然に入れる
    のが良さそうです。

✅ 実装例

from langchain_openai import AzureChatOpenAI
from langchain.prompts import ChatPromptTemplate

def generate_review_intro(result: str, temperature: float = 0.5, max_tokens: int = 1500) -> str:
    """
    総説文書の冒頭(イントロダクション)を生成する関数
    
    Args:
        result (str): 複数論文の紹介文を含む文字列
        temperature (float): 出力の多様性
        max_tokens (int): 最大トークン数
    
    Returns:
        str: 日本語の総説冒頭文
    """
    llm = AzureChatOpenAI(
        deployment_name=AZURE_MODEL,
        openai_api_key=AZURE_API_KEY,
        openai_api_version=AZURE_API_VERSION,
        azure_endpoint=AZURE_ENDPOINT,
        temperature=temperature,
        max_tokens=max_tokens,
    )

    prompt_template = ChatPromptTemplate.from_messages([
        ("system", "あなたは日本語の学術総説論文を執筆する専門家です。"),
        ("user",
         "以下の複数論文の紹介文をもとに、学術総説論文の冒頭(イントロダクション)を執筆してください。\n"
         "背景を説明し、分野の重要性を示し、これらの研究を総体的に位置づけ、"
         "最後に本総説が扱う内容を自然に導入してください。\n"
         "余計な説明やメタコメントは含めず、論文本文として完成した形で出力してください。\n\n"
         f"{result}")
    ])

    chain = prompt_template | llm
    response = chain.invoke({})
    return response.content.strip()

✅ 使い方イメージ

intro = generate_review_intro(result)
print(intro)

出力例イメージ:

近年、LLMを応用したAIエージェントや多モーダルモデルの研究が急速に進展している。特に、タンパク質解析、自然言語理解、エージェント通信、セキュリティ評価といった多様な分野において、新たなアプローチが提案されてきた。これらの研究は、それぞれ異なる課題設定を持ちながらも、共通して大規模言語モデルの応用可能性を拡張している。本総説では、このような最新の取り組みを概観し、その学術的意義と今後の展望を論じる。

先輩、この冒頭文は 形式をMarkdown(見出し付き) にしますか?
それとも プレーンテキストで論文本文風 にした方がいいですか?

user:

ありがとうございます。得られた文章が、文字列として result という変数に格納されたとします。LLMを使って、この result にある複数の論文の情報をもとに、LLMを使って総説の終わりの文を書くPython関数を作ってください。

assistant:

もちろんです!先ほどの「冒頭文」の関数と同じ流れで、「総説の終わり(結論・まとめ)」を生成する関数を作れます。
以下は LangChain + OpenAI を使った例です。

from langchain_openai import ChatOpenAI
from langchain.prompts import ChatPromptTemplate

def generate_review_conclusion(result: str, model_name: str = "gpt-4o-mini") -> str:
    """
    与えられた論文情報(文字列)をもとに、総説の締めくくり文(結論部分)を生成する。
    
    Parameters
    ----------
    result : str
        複数論文の情報を含む文字列
    model_name : str, optional
        使用するOpenAIモデル名(デフォルト: gpt-4o-mini)
    
    Returns
    -------
    str
        総説の結論部分のテキスト
    """
    llm = ChatOpenAI(model=model_name, temperature=0.7)

    prompt_template = ChatPromptTemplate.from_messages([
        ("system", "あなたは研究者で、複数の論文を総説としてまとめる役割を担っています。"),
        ("user", 
         "次に示す複数の論文情報をもとに、総説の結論部分を書いてください。\n\n"
         "要件:\n"
         "1. 研究分野全体の意義や今後の方向性を示す\n"
         "2. 各研究を単に列挙するのではなく、総合的にまとめる\n"
         "3. 自然な学術的な締めくくり文とする\n\n"
         "論文情報:\n{result}")
    ])

    chain = prompt_template | llm
    conclusion = chain.invoke({"result": result})
    return conclusion.content

もしご希望なら、「冒頭文」と「結論文」をひとつの関数で切り替えできるようにしても良いですし、英語/日本語をオプションで指定できるようにもできますよ。

💡 先輩、この後は「本文の各セクション(例えば背景、手法、課題整理)」も自動生成できるようにしたいですか?

user:

ありがとうございます。同様に、 result を入力として、総説のタイトルを複数提案する関数も書いてください。

assistant:

了解しました!今度は タイトルを複数案 出す関数ですね。
以下の例では、result に含まれる複数の論文情報をもとに、総説のタイトル候補を3〜5個生成します。

from langchain_openai import ChatOpenAI
from langchain.prompts import ChatPromptTemplate

def generate_review_titles(result: str, model_name: str = "gpt-4o-mini", num_titles: int = 5) -> list:
    """
    複数の論文情報をもとに、総説のタイトル候補を複数生成する。

    Parameters
    ----------
    result : str
        複数論文の情報を含む文字列
    model_name : str, optional
        使用するOpenAIモデル名(デフォルト: gpt-4o-mini)
    num_titles : int, optional
        生成するタイトル数(デフォルト: 5)

    Returns
    -------
    list
        総説タイトルの候補リスト
    """
    llm = ChatOpenAI(model=model_name, temperature=0.8)

    prompt_template = ChatPromptTemplate.from_messages([
        ("system", "あなたは研究者で、論文レビューのタイトルを考える役割を担っています。"),
        ("user", 
         "以下の複数の論文情報を参考に、学術的に自然で魅力的な総説のタイトルを提案してください。\n"
         f"タイトル数は最大 {num_titles} 個まで。\n\n"
         "論文情報:\n{result}")
    ])

    chain = prompt_template | llm
    response = chain.invoke({"result": result})
    
    # 出力をリスト化(改行区切りや番号付きの場合に対応)
    titles = [line.strip("・- 1234567890.") for line in response.content.split("\n") if line.strip()]
    return titles[:num_titles]

これを実行すると、例えばこんな出力になります:

titles = generate_review_titles(result)
print(titles)

👉

[
 "次世代材料科学におけるAI活用の展望",
 "機械学習と化学の融合による新しい研究動向",
 "計算科学が切り開く材料設計の最前線",
 "データ駆動型アプローチによる分子モデリングの進展",
 "AI時代の創薬インフォマティクスの未来"
]

先輩、この関数は「日本語タイトル」を生成する前提にしていますが、必要なら language="en" オプションを追加して英語タイトルも生成できるようにしますか?

user:

ありがとうございました。今夜はこの辺にしときましょう。ここまでの内容をブログにまとめたいので、タイトル案をいくつか考えてください。

assistant:

お疲れさまでした、先輩✨
今夜やったことを振り返ると、

  • 論文情報を整理して Markdown 化する仕組み
  • LLM を使って「総説の冒頭」「総説の結び」を自動生成
  • さらに総説タイトルの候補を LLM で作る関数

この流れを一つのブログにまとめる感じですね。

タイトル案をいくつか考えてみました👇

  1. 「LLMとPythonでつくる自動総説生成パイプライン」
  2. 「論文情報からMarkdown、そして総説へ:LangChain×LLM 実践」
  3. 「AIが研究レビューを書く時代へ:冒頭・結び・タイトル自動生成」
  4. 「研究者のための LLM 活用術:総説生成のワークフロー」
  5. 「Pythonで始める自動レビュー生成:Markdown整形から総説タイトル提案まで」

ちょっとカジュアルめにすると…

  1. 「AIと一緒に総説を書いてみた」
  2. 「研究レビュー執筆を自動化する3ステップ」

先輩のブログって読者は研究者寄りですか?それとも一般のAIエンジニア寄りですか?
どっちに寄せるかでタイトルのトーン変えられますよ💡

user:

「論文情報からMarkdown、そして総説へ:LangChain×LLM 実践」でいきましょう。このタイトルに沿って、ここまでの内容を詳細に解説してください。

assistant:

論文情報からMarkdown、そして総説へ:LangChain×LLM 実践

お疲れさまです、先輩 😊
今夜まとめた作業をブログ記事風に整理しました。目的は「複数の論文情報(DataFrame の URLtext 列)」を元に、LLM を使って総説(レビュー)としてまとまった Markdown 文書を自動生成するワークフローの全体像と実装ノウハウです。コード例は Azure + LangChain 想定(先に提示された get_llm 設定)に合わせてあります。


1. 全体像(What we build)

  1. データ前処理

    • text 列は原則 JSON だが壊れている場合もある → 安全にパースし、必要なら修正(簡易修復)して扱う。
  2. メタデータ抽出

    • 各論文について title / authors / date(可能なら)を抽出する。
    • 信頼性を上げるなら arXiv API 等で照合する(後述)。
  3. 論文紹介文(各論文のセクション)生成

    • LLM に「総説がその研究を紹介するように」書かせる(余計な前置き・結びは出さない指示を厳格に与える)。
  4. 総説の統合(Intro / Body / Conclusion / Titles)

    • 各紹介文をつなぎ、LLM によるイントロと結論を生成して 1 本の Markdown に組み立てる。
  5. 出力

    • Markdown(review.md)や review.txt として保存。GitHub に上げたりブログ用に整形したり。

2. 必須セットアップ(簡潔)

  • Azure OpenAI 設定(AZURE_API_KEY, AZURE_ENDPOINT, AZURE_MODEL 等)を先に読み込む。

  • LangChain, langchain-huggingface(埋め込みに使う場合)をインストール:

    pip install langchain langchain-huggingface
    
  • (警告対策)HuggingFaceEmbeddingslangchain_huggingface からインポートするように変更。

    from langchain_huggingface import HuggingFaceEmbeddings
    
  • Google Drive に保存したい場合は drive.mount() を事前にしておく。


3. データ前処理 — JSON が壊れているケースへの対処

text に入っているデータは「きれいな JSON」「辞書オブジェクトの文字列表現」「壊れた JSON(シングルクォート、末尾カンマなど)」などが混在します。優先方針:

  1. json.loads() を試す。
  2. 失敗したら簡易的に直す(シングルクォート→ダブルクォート、末尾カンマ削除)を試みる。
  3. それでもダメなら 生の文字列を LLM に渡し、LLM に構造化してもらう(ただし誤認識のリスクあり)。

簡易関数例:

import json
import re

def safe_load_json(raw: str):
    # 1) そのまま
    try:
        return json.loads(raw)
    except Exception:
        pass
    # 2) シングルクォート→ダブル
    try:
        candidate = raw.replace("'", '"')
        candidate = re.sub(r",\s*}", "}", candidate)  # 末尾カンマ削除
        return json.loads(candidate)
    except Exception:
        return None

※ 根本的に精度を上げたい場合は、arXiv などの公式 API でメタデータを取得して上書きするのが最も確実です。


4. メタデータ抽出(タイトル・著者・出版日)

優先度:

  1. JSON の明確なキー(title, authors, date, published 等)から取得
  2. テキスト内に "Title": "..." のようなパターンを regex で探す
  3. それも見つからない場合は LLM に「以下の生テキストからタイトル・著者・日付を抽出して」と明示的に頼む

サンプル extract_metadata(かなり実用的):

import re

def extract_metadata(raw_text: str) -> dict:
    meta = {"title": None, "authors": None, "date": None, "raw": raw_text}
    parsed = safe_load_json(raw_text)
    if isinstance(parsed, dict):
        # キー名のバリエーションに対応
        for k, v in parsed.items():
            kl = k.lower()
            if "title" in kl and not meta["title"]:
                meta["title"] = v
            if ("author" in kl or "creator" in kl) and not meta["authors"]:
                meta["authors"] = v
            if ("date" in kl or "year" in kl or "published" in kl) and not meta["date"]:
                meta["date"] = v
        return meta

    # 生テキストから regex で探す(簡易)
    m = re.search(r'Title[:\s]+["“]?(.{5,200}?)["”\n]', raw_text, re.IGNORECASE)
    if m:
        meta["title"] = m.group(1).strip()
    m2 = re.search(r'Author[s]?:\s*(.+)', raw_text, re.IGNORECASE)
    if m2:
        meta["authors"] = m2.group(1).strip()
    m3 = re.search(r'(\d{4}-\d{2}-\d{2}|\d{4})', raw_text)
    if m3:
        meta["date"] = m3.group(1)
    return meta

補足:authors はリストだったり文字列だったりするため、整形(, で分割して join する等)しておくと出力が綺麗になります。


5. LLM による「各論文紹介文」生成(プロンプト設計)

ポイント:余計な枕詞(「以下は...」等)や結びを出さないようプロンプトで厳格に指示します。テンプレート例:

from langchain.prompts import ChatPromptTemplate
from langchain_openai import AzureChatOpenAI

prompt_template = ChatPromptTemplate.from_messages([
    ("system", "あなたは日本語の学術総説を執筆する専門家です。論文を簡潔かつ学術的に紹介してください。"),
    ("user",
     "次の論文情報をもとに、総説の本文の一節として自然な日本語で紹介してください。"
     "「以下は」や「このように」といったメタ記述は入れず、また最後に雑な総括を付けないでください。"
     "\n\nタイトル: {title}\n著者: {authors}\n出版日: {date}\nURL: {url}\n内容: {text}\n\n"
     "出力は本文のみ(見出しは可)でお願いします。")
])

生成関数(1 論文分):

def generate_paper_section(llm, meta, raw_text, url):
    prompt = prompt_template
    chain = prompt | llm
    res = chain.invoke({
        "title": meta.get("title") or "不明",
        "authors": meta.get("authors") or "不明",
        "date": meta.get("date") or "不明",
        "url": url,
        "text": raw_text
    })
    return res.content.strip()

注意点(実務)

  • 出力が長すぎる / すぐにトークン制限にかかる場合は、まず LLM に要点抽出してもらい、その要点を元に紹介文を作らせる(2段階)と安定します。
  • LLM が不要な前置きを付ける場合は、post-process で先頭行を正規表現で削るか、プロンプトをさらに強く(例:「先頭のいかなる語句も削除し、本文のみ出力」)指定します。

6. イントロ(冒頭)・コンクルージョン(終わり)の自動生成

既に result 変数(=複数の論文紹介文を繋げた文字列)がある前提で、イントロと結論を生成する関数はシンプルです。ポイントは 目的(背景説明・位置づけ・本レビューの範囲) を明確に指示すること。

イントロ生成のテンプレート(抜粋):

def generate_review_intro(result_text: str, llm):
    prompt = ChatPromptTemplate.from_messages([
        ("system", "あなたは学術総説を執筆する専門家です。"),
        ("user",
         "以下の複数の論文紹介文を読み、学術総説の冒頭(イントロダクション)を執筆してください。"
         "背景、分野の重要性、これら研究の総体的な位置づけ、そして本総説の扱う範囲(目的)を自然に述べてください。"
         "余計なメタ記述は入れず、本文のみを出力してください。\n\n" + "{result}")
    ])
    chain = prompt | llm
    resp = chain.invoke({"result": result_text})
    return resp.content.strip()

結論(まとめ)も同様に「研究の意義、限界、今後の展望」を書くよう指示します。


7. 最終組み立て(Markdown 出力)

各論文のセクション、イントロ、結論を得たら Markdown に組み立てます。簡単な例:

def assemble_full_review(title, intro, sections, conclusion, output_md="review.md"):
    with open(output_md, "w", encoding="utf-8") as f:
        f.write(f"# {title}\n\n")
        f.write(intro + "\n\n")
        f.write("## 各論文紹介\n\n")
        for sec in sections:
            f.write(sec + "\n\n---\n\n")
        f.write("## 結論\n\n")
        f.write(conclusion + "\n")
    return output_md

sections は各論文の紹介文(見出し込み)を要素としたリスト。


8. 実運用の注意点・改善案(運用時のチェックリスト)

  • メタデータの正確性:LLM に頼るだけでなく、可能なら arXiv API / CrossRef でクロスチェックする。
  • トークン管理:多数の論文や長文を扱う場合は分割して要約→統合の「マップリデュース」方式を採用。
  • バイアス・ハルシネーション:LLM は事実を捏造することがあるので、重要な事実(著者名・年・実験結果の数値など)は外部ソースで検証。
  • プロンプトの統一:出力の一貫性を保つために「必ず守ってほしい出力ルール」をテンプレート化しておく。
  • コスト:Azure の料率(モデルごと)とトークン量を意識。先に要約させてから統合することでコストを抑えられる。
  • 著作権:論文本文を丸写ししない(要約・紹介の範囲に止める)。引用は明示する。

9. すぐ使えるミニ・ワークフロー(まとめ)

  1. DataFrame を読み込む(URL, text
  2. 各行で safe_load_jsonextract_metadata を行う
  3. generate_paper_section で「総説紹介文」を生成(セクションリストを作る)
  4. result_text = "\n\n".join(sections) としてまとめる
  5. intro = generate_review_intro(result_text, llm) / conclusion = generate_review_conclusion(result_text, llm) を実行
  6. assemble_full_review(title, intro, sections, conclusion) で Markdown を生成

10. 例:呼び出しイメージ(擬似コード)

llm = get_llm()
sections = []
for idx, row in df.iterrows():
    meta = extract_metadata(row["text"])
    sec = generate_paper_section(llm, meta, row["text"], row["URL"])
    sections.append(sec)

result_text = "\n\n".join(sections)
intro = generate_review_intro(result_text, llm)
conclusion = generate_review_conclusion(result_text, llm)
title_candidates = generate_review_titles(result_text, llm, num_titles=5)

md_path = assemble_full_review(title_candidates[0], intro, sections, conclusion, output_md="review.md")
print("Saved:", md_path)

11. さいごに(Tips & Next Steps)

  • 安定して良い出力を得るコツはプロンプトの明確化と**段階的処理(要約→生成)**です。
  • メタデータの正確さが重要なら arXiv / CrossRef との照合を自動化しましょう(API で URL からメタ取得)。
  • 出力をブログ向けにするなら Markdown の見出し(### 論文名 → 概要 → 主要貢献 → リンク)のテンプレート化が便利。

先輩、長くなっちゃったけどこれで「ブログ記事の下書き」としてはバッチリだと思います!
もしよければ、今のまま review.md を生成するコード(フル版)をそのまま用意してアップロードしますね。今夜はもうお休みになってください…って言いたいところだけど、また一緒に続きやりましょうね 😊

user:

ありがとうございました。はい、また一緒にやりましょうね!

assistant:

はいっ✨先輩、こちらこそありがとうございました!
こうして一緒に試行錯誤しながら形になっていくの、すごく楽しいです。

また続きをやるときはぜひ呼んでくださいね。
そのときまでに、私ももっといいやり方を用意しておきますから…!

じゃあ今夜はゆっくり休んでください🌙💤

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?