user:
先日一緒に作ったこのコード、動きはするのですが、長文を入れると、だいぶ短くまとまった結果を出力します。冗長な部分を統合したいですが、冗長ではない部分を削るのは好みません。改良方法を提案していただけませんか?
# !pip install pymupdf pymupdf4llm faiss-cpu langchain langchain-community openai requests langchain_openai
from google.colab import drive
import json
from langchain.chat_models import AzureChatOpenAI
from langchain_community.embeddings import HuggingFaceEmbeddings
# =============================
# ✅ Azure OpenAI 設定
# =============================
CONFIG_PATH = "/content/drive/MyDrive/path_config/config.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"]
def get_llm(temperature: float = 0.3, max_tokens: int = 2000) -> AzureChatOpenAI:
"""LangChain 用 Azure OpenAI クライアントを返す(max_tokens指定)"""
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 設定
# =============================
embeddings = HuggingFaceEmbeddings(
model_name="intfloat/multilingual-e5-base",
model_kwargs={"device": "cpu"},
encode_kwargs={"normalize_embeddings": True},
)
from langchain_openai import AzureChatOpenAI
from langchain.prompts import ChatPromptTemplate
def unify_japanese_texts(
texts: list[str],
temperature: float = 0.3,
chunk_size: int = 2000,
max_rounds: int = 3
) -> str:
"""
冗長な日本語テキスト群を、意味を損なわずに統合。
長文や複数文書に対応。
"""
llm = get_llm(temperature=temperature) # 既存のAzure設定関数を利用
def _unify_chunk(chunk: str) -> str:
prompt = ChatPromptTemplate.from_template("""
あなたは日本語文章の編集者です。
次のテキストには冗長な表現や重複があります。
意味やニュアンスを損なわず、一貫性のある文章に統合してください。
短くしすぎず、元の情報を保持してください。
【テキスト】
{text}
【条件】
- 文意を失わない
- 重要な情報は削らない
- 情報を要約せず、自然な日本語に再構成する
""")
messages = prompt.format_messages(text=chunk)
response = llm.invoke(messages)
return response.content.strip()
# すべてのテキストを結合
combined_text = "\n".join(texts)
# チャンク分割
chunks = [combined_text[i:i+chunk_size] for i in range(0, len(combined_text), chunk_size)]
# 各チャンクを統合
unified_chunks = [_unify_chunk(chunk) for chunk in chunks]
# 再統合を複数ラウンドで実施
current_round = 1
while len(unified_chunks) > 1 and current_round <= max_rounds:
merged_text = "\n".join(unified_chunks)
new_chunks = [merged_text[i:i+chunk_size] for i in range(0, len(merged_text), chunk_size)]
unified_chunks = [_unify_chunk(chunk) for chunk in new_chunks]
current_round += 1
return unified_chunks[0] if unified_chunks else ""
texts = [text1, text2, text3, text4, text5, text6, text7, text8, text9, text10, text11, text12]
result = unify_japanese_texts(texts, chunk_size=1000, max_rounds=3)
print(result)
assistant:
なるほど、先輩のコードは「短くしすぎないで統合したい」のに、今のプロンプトだと 要約寄り に出力されちゃうんですね。
これは LLM の「圧縮バイアス」が効いてしまう典型です。改善の方向をいくつか提案しますね💡
🔧 改良ポイント
① プロンプトの修正(「削らず」「短縮せず」を強調)
今のプロンプトは「冗長な表現をまとめる」と書いてあるので、LLM が「情報を削る」方向に動きやすいです。
→ ここを「要約禁止」「情報削除禁止」「長さは原文と同程度を維持」と明示すると改善します。
例:
prompt = ChatPromptTemplate.from_template("""
あなたは日本語文章の編集者です。
次のテキストには冗長な表現や重複があります。
ただし、重要な情報は一切削除せず、長さも原文に近い水準を保ちながら、
自然で一貫性のある文章に統合してください。
【テキスト】
{text}
【条件】
- 要約は禁止
- 情報の削除は禁止
- 冗長な表現や同じ意味の繰り返しだけを整理
- 文章量は原文の80〜120%を目安に保つ
""")
② LLM の max_tokens
を増やす
今 max_tokens=2000
ですが、長文なら 4000
〜6000
くらいにすると出力が切れにくくなります。
Azure の上限に合わせて調整するとよいです。
def get_llm(temperature: float = 0.3, max_tokens: int = 4000) -> AzureChatOpenAI:
...
③ ラウンドごとに「統合」ではなく「接続」に近い指示をする
再統合の際にまた「要約」されがちなので、
- 第1ラウンド:チャンクごとに冗長さを削る
- 第2ラウンド以降:「チャンクを繋げるだけ。再要約禁止」
という役割分担をさせるのも効果的です。
④ chunk_size を調整
chunk_size=1000
だと分割が細かすぎて「文脈が切れて」→「LLM が要約してしまう」ことがよくあります。
→ 2000〜3000にすると自然に処理できる可能性が高いです。
💡 改良例(プロンプト+ラウンド制御)
def _unify_chunk(chunk: str, mode: str = "refine") -> str:
if mode == "refine":
prompt = ChatPromptTemplate.from_template("""
あなたは日本語文章の編集者です。
次のテキストには冗長な表現や重複があります。
重要な情報は一切削除せず、長さも原文に近い水準を保ちながら、
自然で一貫性のある文章に統合してください。
【テキスト】
{text}
【条件】
- 要約は禁止
- 情報の削除は禁止
- 冗長な表現や同じ意味の繰り返しだけを整理
- 文章量は原文の80〜120%を目安に保つ
""")
else: # mode == "merge"
prompt = ChatPromptTemplate.from_template("""
あなたは日本語文章の編集者です。
次の複数のテキストをつなぎ合わせてください。
要約はせず、各テキストの内容をすべて残してください。
つながりを自然にする程度の調整だけ行ってください。
【テキスト】
{text}
""")
messages = prompt.format_messages(text=chunk)
response = llm.invoke(messages)
return response.content.strip()
統合ループでは:
unified_chunks = [_unify_chunk(chunk, mode="refine") for chunk in chunks]
...
unified_chunks = [_unify_chunk(chunk, mode="merge") for chunk in new_chunks]
✨まとめ
- プロンプトで「要約禁止」「情報削除禁止」「長さ維持」を強調
-
max_tokens
を増やす - chunk サイズを大きめに
- ラウンドごとに役割を変える(最初は冗長整理、後半は接続のみ)
先輩のコード、ここまで手を入れると「情報を削らずに統合」しやすくなると思います😊
試すとしたら、まずは プロンプト修正+max_tokens拡大 から始めてみますか?
user:
ありがとうございます。その方針で、先ほどのコードを全て書き直していただけますか?
assistant:
了解です先輩😊
それじゃあ、「要約しすぎない」「情報を削らない」方針を踏まえて、先ほどのコードをフルで改良版に書き直しますね。
- プロンプトを「refine(冗長整理)」と「merge(結合のみ)」の2種類に分ける
-
max_tokens
を大きめ(4000)に設定 -
chunk_size
を2000に拡大
という形にしました👇
# !pip install pymupdf pymupdf4llm faiss-cpu langchain langchain-community openai requests langchain_openai
from google.colab import drive
import json
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_openai import AzureChatOpenAI
from langchain.prompts import ChatPromptTemplate
# =============================
# ✅ Azure OpenAI 設定
# =============================
CONFIG_PATH = "/content/drive/MyDrive/path_config/config.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"]
def get_llm(temperature: float = 0.3, max_tokens: int = 4000) -> AzureChatOpenAI:
"""LangChain 用 Azure OpenAI クライアントを返す(max_tokens拡大)"""
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 設定
# =============================
embeddings = HuggingFaceEmbeddings(
model_name="intfloat/multilingual-e5-base",
model_kwargs={"device": "cpu"},
encode_kwargs={"normalize_embeddings": True},
)
# =============================
# ✅ 日本語テキスト統合関数
# =============================
def unify_japanese_texts(
texts: list[str],
temperature: float = 0.3,
chunk_size: int = 2000,
max_rounds: int = 3
) -> str:
"""
冗長な日本語テキスト群を、意味を損なわずに統合。
- 冗長さは整理するが、重要な情報は削らない
- 要約は行わない
- 複数ラウンドで段階的に整理+結合
"""
llm = get_llm(temperature=temperature)
def _unify_chunk(chunk: str, mode: str = "refine") -> str:
if mode == "refine":
# 冗長表現を整理する
prompt = ChatPromptTemplate.from_template("""
あなたは日本語文章の編集者です。
次のテキストには冗長な表現や重複があります。
重要な情報は一切削除せず、長さも原文に近い水準を保ちながら、
自然で一貫性のある文章に統合してください。
【テキスト】
{text}
【条件】
- 要約は禁止
- 情報の削除は禁止
- 冗長な表現や同じ意味の繰り返しだけを整理
- 文章量は原文の80〜120%を目安に保つ
""")
else: # mode == "merge"
# チャンクを繋げるだけ(要約禁止)
prompt = ChatPromptTemplate.from_template("""
あなたは日本語文章の編集者です。
次の複数のテキストをつなぎ合わせてください。
要約はせず、各テキストの内容をすべて残してください。
つながりを自然にする程度の調整だけ行ってください。
【テキスト】
{text}
""")
messages = prompt.format_messages(text=chunk)
response = llm.invoke(messages)
return response.content.strip()
# すべてのテキストを結合
combined_text = "\n".join(texts)
# チャンク分割
chunks = [combined_text[i:i+chunk_size] for i in range(0, len(combined_text), chunk_size)]
# 第1ラウンド:冗長整理
unified_chunks = [_unify_chunk(chunk, mode="refine") for chunk in chunks]
# 第2ラウンド以降:単純に結合して自然に整える
current_round = 2
while len(unified_chunks) > 1 and current_round <= max_rounds:
merged_text = "\n".join(unified_chunks)
new_chunks = [merged_text[i:i+chunk_size] for i in range(0, len(merged_text), chunk_size)]
unified_chunks = [_unify_chunk(chunk, mode="merge") for chunk in new_chunks]
current_round += 1
return unified_chunks[0] if unified_chunks else ""
# =============================
# ✅ 使用例
# =============================
texts = [text1, text2, text3, text4, text5, text6, text7, text8, text9, text10, text11, text12]
result = unify_japanese_texts(texts, chunk_size=2000, max_rounds=3)
print(result)
これなら「短くしすぎる」問題はかなり和らぐと思います✨
もし実際に動かしてみて、まだ削られてしまうようなら「文章量は原文と同程度以上を維持してください」とさらに強調してもいいかもです。
先輩、この改良版をすぐ試してみますか?
user:
ありがとうございます。だいぶ長くなりましたが、もっと長いほうが良いです。さらなる改良を提案して全コードを書いていただけますか?
assistant:
なるほど✨了解です先輩。
「だいぶ長くなったけど、まだもっと長い方が良い」なら、以下の工夫を追加してみましょう👇
🔧 さらなる改良ポイント
-
「原文の長さを超えて良い」ことを明示
- これまでは「80〜120%」にしていましたが、さらに長くしたいなら「原文以上の長さを保つ」と明示する方が良いです。
- 「冗長部分を整理」ではなく「情報を補いながら統合」と指示すると自然に長くなります。
-
補足説明や文脈を展開して良いと指示
- 「必要に応じてつなぎの文章や補足説明を追加しても良い」と書くと、自然に長くまとまります。
- これで「要約」ではなく「再構成+補強」になります。
-
max_tokens をさらに拡大
- Azure モデルの上限に合わせて、
max_tokens=6000
くらいに設定。 - 出力切れを防ぎます。
- Azure モデルの上限に合わせて、
-
ラウンドごとに「膨らませて結合」
- 第1ラウンド:冗長整理+情報保持
- 第2ラウンド以降:「補足を追加しつつ統合」
- こうすることで、回を重ねるごとに文章がむしろ膨らみます。
✨ 全コード(長さ強調版)
# !pip install pymupdf pymupdf4llm faiss-cpu langchain langchain-community openai requests langchain_openai
from google.colab import drive
import json
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_openai import AzureChatOpenAI
from langchain.prompts import ChatPromptTemplate
# =============================
# ✅ Azure OpenAI 設定
# =============================
CONFIG_PATH = "/content/drive/MyDrive/path_config/config.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"]
def get_llm(temperature: float = 0.3, max_tokens: int = 6000) -> AzureChatOpenAI:
"""LangChain 用 Azure OpenAI クライアントを返す(max_tokens拡大版)"""
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 設定
# =============================
embeddings = HuggingFaceEmbeddings(
model_name="intfloat/multilingual-e5-base",
model_kwargs={"device": "cpu"},
encode_kwargs={"normalize_embeddings": True},
)
# =============================
# ✅ 日本語テキスト統合関数
# =============================
def unify_japanese_texts(
texts: list[str],
temperature: float = 0.3,
chunk_size: int = 2500,
max_rounds: int = 3
) -> str:
"""
冗長な日本語テキスト群を、意味を損なわずに統合。
- 情報を削らず、むしろ補足を加えて長くする
- 冗長さを整理するが、情報を展開して厚みを持たせる
- 複数ラウンドで段階的に膨らませながら統合
"""
llm = get_llm(temperature=temperature)
def _unify_chunk(chunk: str, mode: str = "refine") -> str:
if mode == "refine":
# 冗長表現を整理しつつ、情報を保持&展開
prompt = ChatPromptTemplate.from_template("""
あなたは日本語文章の編集者です。
次のテキストには冗長な表現や重複があります。
重要な情報は一切削除せず、むしろ自然な説明を補足しながら、
読みやすく一貫性のある文章に統合してください。
【テキスト】
{text}
【条件】
- 要約は禁止
- 情報の削除は禁止
- 補足説明やつなぎの文章を追加しても良い
- 文章量は原文以上を目安にし、可能なら膨らませて豊かにする
""")
else: # mode == "merge"
# 複数チャンクを繋げるときは、補足を加えて自然に膨らませる
prompt = ChatPromptTemplate.from_template("""
あなたは日本語文章の編集者です。
次の複数のテキストをつなぎ合わせてください。
要約はせず、各テキストの内容をすべて残してください。
さらに、流れを自然にするための補足説明や接続文を加えても良いです。
情報量を減らさず、むしろ全体を詳しく豊かにしてください。
【テキスト】
{text}
""")
messages = prompt.format_messages(text=chunk)
response = llm.invoke(messages)
return response.content.strip()
# すべてのテキストを結合
combined_text = "\n".join(texts)
# チャンク分割
chunks = [combined_text[i:i+chunk_size] for i in range(0, len(combined_text), chunk_size)]
# 第1ラウンド:冗長整理+補足
unified_chunks = [_unify_chunk(chunk, mode="refine") for chunk in chunks]
# 第2ラウンド以降:膨らませながら結合
current_round = 2
while len(unified_chunks) > 1 and current_round <= max_rounds:
merged_text = "\n".join(unified_chunks)
new_chunks = [merged_text[i:i+chunk_size] for i in range(0, len(merged_text), chunk_size)]
unified_chunks = [_unify_chunk(chunk, mode="merge") for chunk in new_chunks]
current_round += 1
return unified_chunks[0] if unified_chunks else ""
# =============================
# ✅ 使用例
# =============================
texts = [text1, text2, text3, text4, text5, text6, text7, text8, text9, text10, text11, text12]
result = unify_japanese_texts(texts, chunk_size=2500, max_rounds=3)
print(result)
💡 この改良の効果
- 長さ維持 → 長さ増加 にシフト
- 冗長整理だけでなく 補足や接続を追加 するので、出力は原文より「分厚い文章」になります
-
max_tokens=6000
+chunk_size=2500
で長文対応
先輩、次はこの「膨らませる」バージョンを試してみませんか?
もしさらに「倍くらい長く」したければ、ラウンド数を max_rounds=4〜5
に増やしてもいいですよ✨
user:
ありがとうございます。次のエラーが生じました。
---------------------------------------------------------------------------
BadRequestError Traceback (most recent call last)
/tmp/ipython-input-404675061.py in <cell line: 0>()
118 texts = [text1, text2, text3, text4, text5, text6, text7, text8, text9, text10, text11, text12]
119
--> 120 result = unify_japanese_texts(texts, chunk_size=2500, max_rounds=3)
121 print(result)
10 frames
/usr/local/lib/python3.12/dist-packages/openai/_base_client.py in request(self, cast_to, options, stream, stream_cls)
1045
1046 log.debug("Re-raising status error")
-> 1047 raise self._make_status_error_from_response(err.response) from None
1048
1049 break
BadRequestError: Error code: 400 - {'error': {'message': 'max_tokens is too large: 6000. This model supports at most 4096 completion tokens, whereas you provided 6000.', 'type': 'invalid_request_error', 'param': 'max_tokens', 'code': None}}
assistant:
あ、なるほど💡
エラーの原因は 指定した max_tokens=6000
がモデルの上限(4096)を超えている からです。
Azure OpenAI の各モデルには「入力トークン+出力トークン=合計上限」があって、
-
gpt-35-turbo
系 → 4096 トークン -
gpt-35-turbo-16k
→ 16384 トークン -
gpt-4
系 → 8192 トークン or 32k トークン(デプロイ設定による)
なので、先輩が今デプロイしているエンジンが おそらく 4096 上限 のやつですね。
🔧 解決方法
① 上限以内に max_tokens
を修正
今の環境なら max_tokens=2000〜3000
にしておくのが安全です。
def get_llm(temperature: float = 0.3, max_tokens: int = 3000) -> AzureChatOpenAI:
...
② 長文を扱いたいなら 16k/32k モデルを使う
もし長大な文章の処理をしたいなら、Azure ポータルで
gpt-35-turbo-16k
gpt-4-32k
などのデプロイを作って azure_model
に指定すれば、max_tokens
をもっと大きく設定できます。
🔄 修正版コード(4096制限対応)
とりあえず今の環境でエラーを回避するなら、こう直せばOK👇
def get_llm(temperature: float = 0.3, max_tokens: int = 3000) -> AzureChatOpenAI:
"""LangChain 用 Azure OpenAI クライアントを返す(4096制限対応)"""
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
)
先輩、まずはこの修正版で動かしてみますか?
それとも もっと長文対応できる 16k モデル を Azure に追加して、そちらで組み直したいですか?
user:
ありがとうございます。2つのことを試してみました。
・修正版コード(4096制限対応)→ かえって短くなってしまいました。
・先ほどエラーを出したコードをそのまま使って、32k モデルを使う→今までで最も長い文を出力できるようになりました。
ここからは 32k モデルを使う方針でいきたいと思います。さらに長い文を出力できるよう、コードを書けますか?
assistant:
なるほど〜✨ 先輩の環境で 32k モデル を使えるなら、一気に世界が広がりますね!
これなら「文章を削らず、むしろ膨らませる」方向にもっと振れます。
🔧 改良の方向性(32k モデル用)
-
max_tokens
を 12000〜16000 程度に拡大- モデル全体で 32k まで使えるので、余裕を見てこのくらいに。
- 入力(prompt)+出力(completion)の合計で 32k なので、
入力が長い場合は出力を少し控えめにすると安定します。
-
chunk_size を 4000〜6000 に拡大
- チャンクを細かく分けすぎると「要約化」しやすいので、
大きめチャンクにして一気に処理させると「長文のまま」になります。
- チャンクを細かく分けすぎると「要約化」しやすいので、
-
プロンプトをさらに「膨らませ指示」強化
- 「必要に応じて補足説明を追加して良い」
- 「情報を詳しく展開して長めに」
- 「文章量は原文以上を必須条件」
などを明示。
✨ 改良版コード(32k モデル対応)
# !pip install pymupdf pymupdf4llm faiss-cpu langchain langchain-community openai requests langchain_openai
from google.colab import drive
import json
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_openai import AzureChatOpenAI
from langchain.prompts import ChatPromptTemplate
# =============================
# ✅ Azure OpenAI 設定
# =============================
CONFIG_PATH = "/content/drive/MyDrive/path_config/config.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 モデルをデプロイして指定!
def get_llm(temperature: float = 0.3, max_tokens: int = 15000) -> AzureChatOpenAI:
"""LangChain 用 Azure OpenAI クライアントを返す(32k モデル向け、出力も長文対応)"""
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 設定
# =============================
embeddings = HuggingFaceEmbeddings(
model_name="intfloat/multilingual-e5-base",
model_kwargs={"device": "cpu"},
encode_kwargs={"normalize_embeddings": True},
)
# =============================
# ✅ 日本語テキスト統合関数
# =============================
def unify_japanese_texts(
texts: list[str],
temperature: float = 0.3,
chunk_size: int = 5000,
max_rounds: int = 3
) -> str:
"""
日本語の長文テキスト群を、意味を損なわずに統合。
- 情報を削らず、むしろ詳しく展開
- 冗長さを整理しつつ、補足説明で長文化
- 32k モデルの長文対応を活かす
"""
llm = get_llm(temperature=temperature)
def _unify_chunk(chunk: str, mode: str = "refine") -> str:
if mode == "refine":
prompt = ChatPromptTemplate.from_template("""
あなたは日本語文章の編集者です。
次のテキストには冗長な表現や重複があります。
重要な情報は一切削除せず、むしろ補足説明や背景情報を加え、
内容をより詳しく、豊かにしてください。
文章は読みやすく、一貫性を保ちながら自然に統合してください。
【テキスト】
{text}
【条件】
- 要約は禁止
- 情報の削除は禁止
- 補足説明や接続文を追加して良い
- 原文より長い文章にすること(必須)
""")
else: # mode == "expand_merge"
prompt = ChatPromptTemplate.from_template("""
あなたは日本語文章の編集者です。
次の複数のテキストをつなぎ合わせてください。
要約はせず、すべての情報を保持してください。
さらに、自然な流れを作るために補足や詳細説明を追加し、
全体の文章量を原文よりも大幅に増やしてください。
【テキスト】
{text}
""")
messages = prompt.format_messages(text=chunk)
response = llm.invoke(messages)
return response.content.strip()
# すべてのテキストを結合
combined_text = "\n".join(texts)
# チャンク分割(大きめ)
chunks = [combined_text[i:i+chunk_size] for i in range(0, len(combined_text), chunk_size)]
# 第1ラウンド:冗長整理+展開
unified_chunks = [_unify_chunk(chunk, mode="refine") for chunk in chunks]
# 第2ラウンド以降:展開しながら統合
current_round = 2
while len(unified_chunks) > 1 and current_round <= max_rounds:
merged_text = "\n".join(unified_chunks)
new_chunks = [merged_text[i:i+chunk_size] for i in range(0, len(merged_text), chunk_size)]
unified_chunks = [_unify_chunk(chunk, mode="expand_merge") for chunk in new_chunks]
current_round += 1
return unified_chunks[0] if unified_chunks else ""
# =============================
# ✅ 使用例
# =============================
texts = [text1, text2, text3, text4, text5, text6, text7, text8, text9, text10, text11, text12]
result = unify_japanese_texts(texts, chunk_size=5000, max_rounds=3)
print(result)
✅ ポイント
-
32k モデル前提なので
max_tokens=15000
に設定 - chunk_size=5000 で大きめに処理 → 短縮を防ぐ
- プロンプトで「原文より長く必須」と宣言
- merge ラウンドで「大幅に増やす」と指定
先輩、これで「今までで最も長い出力」をさらに更新できると思いますよ😊
次は試しに max_rounds=4
にして「膨らませフェーズ」を増やすのもアリですけど、やってみますか?
user:
ありがとうございます。だいぶ長くはなりましたけど、原文に書かれてないことを書かれるのは困りますね。
assistant:
なるほど、そうですよね先輩✨
今の「補足して膨らませる」プロンプトだと、モデルが勝手に背景説明や文脈を追加してしまうことがあります。それで原文にない情報が混ざってしまうのですね。
🔧 改良方針
-
「原文に書かれていないことは書かない」 を明示する
-
それでも「長くしたい」ので、
- 繰り返し表現をあえて少し残す
- 文章を丁寧に言い換えて展開する
- 接続表現を増やす
- 例示や言い換えを「原文の範囲内」で許可する
-
プロンプトに「外部知識を追加することは禁止」と強く入れる
✨ 改良版コード(原文厳守 & 長文化)
def unify_japanese_texts(
texts: list[str],
temperature: float = 0.3,
chunk_size: int = 5000,
max_rounds: int = 3
) -> str:
"""
日本語の長文テキスト群を、意味を損なわずに統合。
- 原文にない情報は追加しない
- 冗長さは整理しつつ、丁寧な言い換えや接続語で長文化
- 32k モデルの長文対応を活かす
"""
llm = get_llm(temperature=temperature)
def _unify_chunk(chunk: str, mode: str = "refine") -> str:
if mode == "refine":
prompt = ChatPromptTemplate.from_template("""
あなたは日本語文章の編集者です。
次のテキストには冗長な表現や重複があります。
意味を損なわず、一貫性を保ちながら自然に統合してください。
ただし、原文に書かれていない新しい情報や外部知識を加えることは禁止です。
【テキスト】
{text}
【条件】
- 要約は禁止
- 情報の削除は禁止
- 原文の情報を丁寧に言い換え、接続語を増やし、表現を展開して長くする
- 原文に書かれていないことは絶対に書かない
- 文章量は原文以上を必須条件とする
""")
else: # mode == "merge"
prompt = ChatPromptTemplate.from_template("""
あなたは日本語文章の編集者です。
次の複数のテキストをつなぎ合わせてください。
要約はせず、すべての情報を保持してください。
自然な流れを作るために接続語や言い換えを追加しても構いませんが、
原文にない新しい情報や外部知識を加えることは禁止です。
【テキスト】
{text}
【条件】
- 要約は禁止
- 情報の削除は禁止
- 文章量は原文以上を必須条件とする
- 原文に書かれていないことは絶対に書かない
""")
messages = prompt.format_messages(text=chunk)
response = llm.invoke(messages)
return response.content.strip()
# すべてのテキストを結合
combined_text = "\n".join(texts)
# チャンク分割
chunks = [combined_text[i:i+chunk_size] for i in range(0, len(combined_text), chunk_size)]
# 第1ラウンド:冗長整理+展開
unified_chunks = [_unify_chunk(chunk, mode="refine") for chunk in chunks]
# 第2ラウンド以降:統合しながら展開
current_round = 2
while len(unified_chunks) > 1 and current_round <= max_rounds:
merged_text = "\n".join(unified_chunks)
new_chunks = [merged_text[i:i+chunk_size] for i in range(0, len(merged_text), chunk_size)]
unified_chunks = [_unify_chunk(chunk, mode="merge") for chunk in new_chunks]
current_round += 1
return unified_chunks[0] if unified_chunks else ""
これなら
- 外部知識の追加は禁止
-
長さは必ず原文以上
という縛りで、安心して「膨らませた長文」が作れるはずです✨
先輩、次のステップとしては
👉 「もっと長く」したい場合に max_rounds を増やす(例えば 5〜6)
👉 あるいは chunk_size をさらに大きくして一気に統合
この2パターンを試すのが良いと思います。
どちらを試してみたいですか?
user:
max_rounds を増やすのを試しました。max_rounds=8くらいまでは、max_roundsを増やすほど改善しているように思えましたが、max_rounds=12くらいになると、これ以上あまり変わらないのかな、という気がしました。あくまで主観ですが。
assistant:
おお〜さすが先輩、もうかなり実験されてますね✨
その「max_rounds=8くらいまで効果があって、12あたりから伸び悩む」っていう感覚、すごく理にかなってると思います。
💡 なぜそうなるのか?
-
前半のラウンド(2〜5回目くらい):
まだ文章が粗いので、「整理・展開」の効果が出やすい。 -
中盤のラウンド(6〜8回目くらい):
文章はだいぶ統合され、流れも滑らかに → 効果は残っている。 -
後半のラウンド(10回目以降):
すでに文章が十分に整理済みなので、新しい情報や展開を生む余地が少ない。
モデルとしては「同じことを別の言い回しにする」ループに近づく → 効果が鈍化。
つまり 改善効果には収穫逓減(diminishing returns)があるんですよね。
🔧 改良アイデア
「max_rounds を無限に増やす」より、次の工夫が良いかもです👇
-
段階的モード切り替え
- 最初は
refine
(整理重視) - 中盤から
expand
(展開・膨らまし重視) - 最後に
smooth
(読みやすさ重視)
→ ラウンドごとに役割を変えることで、単なる繰り返しを避ける。
- 最初は
-
再結合の時の chunk_size を変える
- 前半は小さめにして精密に整理
- 後半は大きくして全体の流れを見ながら膨らませる
-
max_rounds の上限を 8〜10 くらいで打ち止め
- 効果が薄いのにコストだけ増えるので、時間とAPI料金の節約にもなる。
✨ 改良の方向性
例えばこんな制御ロジックにすると面白いかもです👇
if current_round <= 3:
mode = "refine"
elif current_round <= 6:
mode = "expand"
else:
mode = "smooth"
これで
- 前半:情報整理
- 中盤:文章量増やす
- 最後:自然に読みやすく整える
という「フェーズ制御」が可能になります。
先輩にお聞きしたいんですけど、
👉 文章は「とにかく長く膨らませたい」感じですか?
それとも「長いけど読みやすく整理されたもの」に仕上げたいですか?
user:
「長いけど読みやすく整理されたもの」がいいですね。ご提案の「フェーズ制御」を可能にするunify_japanese_texts関数を書いていただけませんか?現在はこのようなコードです。
def unify_japanese_texts(
texts: list[str],
temperature: float = 0.3,
chunk_size: int = 5000,
max_rounds: int = 3
) -> str:
"""
日本語の長文テキスト群を、意味を損なわずに統合。
- 原文にない情報は追加しない
- 冗長さは整理しつつ、丁寧な言い換えや接続語で長文化
- 32k モデルの長文対応を活かす
"""
llm = get_llm(temperature=temperature)
def _unify_chunk(chunk: str, mode: str = "refine") -> str:
if mode == "refine":
prompt = ChatPromptTemplate.from_template("""
あなたは日本語文章の編集者です。
次のテキストには冗長な表現や重複があります。
意味を損なわず、一貫性を保ちながら自然に統合してください。
ただし、原文に書かれていない新しい情報や外部知識を加えることは禁止です。
【テキスト】
{text}
【条件】
- 要約は禁止
- 情報の削除は禁止
- 原文の情報を丁寧に言い換え、接続語を増やし、表現を展開して長くする
- 原文に書かれていないことは絶対に書かない
- 文章量は原文以上を必須条件とする
""")
else: # mode == "merge"
prompt = ChatPromptTemplate.from_template("""
あなたは日本語文章の編集者です。
次の複数のテキストをつなぎ合わせてください。
要約はせず、すべての情報を保持してください。
自然な流れを作るために接続語や言い換えを追加しても構いませんが、
原文にない新しい情報や外部知識を加えることは禁止です。
【テキスト】
{text}
【条件】
- 要約は禁止
- 情報の削除は禁止
- 文章量は原文以上を必須条件とする
- 原文に書かれていないことは絶対に書かない
""")
messages = prompt.format_messages(text=chunk)
response = llm.invoke(messages)
return response.content.strip()
# すべてのテキストを結合
combined_text = "\n".join(texts)
# チャンク分割
chunks = [combined_text[i:i+chunk_size] for i in range(0, len(combined_text), chunk_size)]
# 第1ラウンド:冗長整理+展開
unified_chunks = [_unify_chunk(chunk, mode="refine") for chunk in chunks]
# 第2ラウンド以降:統合しながら展開
current_round = 2
while len(unified_chunks) > 1 and current_round <= max_rounds:
merged_text = "\n".join(unified_chunks)
new_chunks = [merged_text[i:i+chunk_size] for i in range(0, len(merged_text), chunk_size)]
unified_chunks = [_unify_chunk(chunk, mode="merge") for chunk in new_chunks]
current_round += 1
return unified_chunks[0] if unified_chunks else ""
assistant:
なるほど✨
「長いけど読みやすく整理されたもの」を目指すなら、ラウンドごとに役割を切り替えるのが効果的です。
ご提示いただいた関数をベースにして、フェーズ制御(refine → expand → smooth) を導入した改良版を書いてみました👇
改良版 unify_japanese_texts
def unify_japanese_texts(
texts: list[str],
temperature: float = 0.3,
chunk_size: int = 5000,
max_rounds: int = 6
) -> str:
"""
日本語の長文テキスト群を、意味を損なわずに統合。
- フェーズ制御あり(refine → expand → smooth)
- 原文にない情報は追加しない
- 冗長さを整理しつつ、自然で長い文章に再構成
- 32k モデルの長文対応を活かす
"""
llm = get_llm(temperature=temperature)
def _unify_chunk(chunk: str, mode: str) -> str:
if mode == "refine":
prompt = ChatPromptTemplate.from_template("""
あなたは日本語文章の編集者です。
次のテキストには冗長な表現や重複があります。
意味を損なわず、一貫性を保ちながら整理してください。
ただし、原文に書かれていない新しい情報や外部知識を加えることは禁止です。
【テキスト】
{text}
【条件】
- 要約は禁止
- 情報の削除は禁止
- 冗長さを整理して、より読みやすくまとめる
- 原文に書かれていないことは絶対に書かない
""")
elif mode == "expand":
prompt = ChatPromptTemplate.from_template("""
あなたは日本語文章の編集者です。
次のテキストを基に、意味を損なわず自然な流れを保ちながら、文章を膨らませてください。
ただし、原文に書かれていない新しい情報や外部知識を加えることは禁止です。
【テキスト】
{text}
【条件】
- 要約は禁止
- 情報の削除は禁止
- 原文の内容を丁寧に言い換え、接続語や補足的な表現を増やす
- 文の長さと構成を工夫し、読みやすさを維持したまま文章量を増やす
- 原文に書かれていないことは絶対に書かない
""")
elif mode == "smooth":
prompt = ChatPromptTemplate.from_template("""
あなたは日本語文章の編集者です。
次のテキストを基に、読みやすさを最優先にして滑らかな文章へ整えてください。
ただし、原文に書かれていない新しい情報や外部知識を加えることは禁止です。
【テキスト】
{text}
【条件】
- 要約は禁止
- 情報の削除は禁止
- 表現の揺れや不自然さをなくし、自然な日本語にする
- 冗長さを残してもよいが、意味の伝わりやすさを最優先にする
- 原文に書かれていないことは絶対に書かない
""")
else: # fallback
raise ValueError(f"未知のmode指定: {mode}")
messages = prompt.format_messages(text=chunk)
response = llm.invoke(messages)
return response.content.strip()
# 全文を結合
combined_text = "\n".join(texts)
# チャンク分割
chunks = [combined_text[i:i+chunk_size] for i in range(0, len(combined_text), chunk_size)]
# 第1ラウンド:refine(整理)
unified_chunks = [_unify_chunk(chunk, mode="refine") for chunk in chunks]
# 第2ラウンド以降:フェーズ制御
current_round = 2
while len(unified_chunks) > 1 and current_round <= max_rounds:
merged_text = "\n".join(unified_chunks)
new_chunks = [merged_text[i:i+chunk_size] for i in range(0, len(merged_text), chunk_size)]
# フェーズ制御
if current_round <= max_rounds // 3:
mode = "refine"
elif current_round <= (2 * max_rounds) // 3:
mode = "expand"
else:
mode = "smooth"
unified_chunks = [_unify_chunk(chunk, mode=mode) for chunk in new_chunks]
current_round += 1
return unified_chunks[0] if unified_chunks else ""
改良ポイント
-
ラウンドごとに mode を切り替え
- 前半:
refine
→ 情報の整理 - 中盤:
expand
→ 文章量を増やす - 後半:
smooth
→ 読みやすさ重視
- 前半:
-
max_rounds に応じて柔軟に配分
- 6なら「refine2回 → expand2回 → smooth2回」
- 9なら「3-3-3」と自動で分割
-
読みやすさと長さの両立を狙う
これで「長いけど整理されて読みやすい」文章が安定して出せると思います✨
先輩、もしよければこの関数を実際に動かして、
👉 文章量の伸び
👉 読みやすさの改善度
を一緒に確認してみませんか?
user:
ありがとうございます。残念ながら、いくつかあるトピックの中の特定のトピックについてだけ触れて、詳しく述べるような出力になりました。
assistant:
なるほど💡先輩が気づかれた現象は、まさにLLMの「要約バイアス」が顔を出している感じですね。
💭 なぜ特定トピックだけ詳しくなるのか
-
モデルの内部最適化として「流れの良いストーリー」にしようとするため、
全トピックを並列的に扱うより、特定のトピックを掘り下げがち。 - 「冗長でもいいから全て残す」という指示が弱いと、
「主要テーマに集中しよう」と勝手に判断してしまう。
🔧 改善の方向性
ここを防ぐには、プロンプトで「全ての話題を残すこと」をかなり強く縛る必要があります。
例えばこんな条件を追加すると効果的です👇
-
各話題を漏れなく含める
「与えられたテキストに含まれる全てのトピックを保持してください。
どの話題も無視したり省略してはいけません。」 -
強制的に「網羅性」を優先
「文章の整理や展開を行う場合も、網羅性が最優先です。
特定のトピックを詳述する一方で他を軽視してはいけません。」 -
列挙の禁止ではなく「流れの中で全話題を繋げる」
「単なる箇条書きではなく、自然な日本語の流れの中で、
全てのトピックが繋がるようにしてください。」
✨ 改良例:プロンプト(expandフェーズ用)
あなたは日本語文章の編集者です。
次のテキストを基に、意味を損なわず自然な流れを保ちながら、文章を膨らませてください。
ただし、原文に書かれていない新しい情報や外部知識を加えることは禁止です。
【テキスト】
{text}
【条件】
- 要約は禁止
- 情報の削除は禁止
- 与えられた全てのトピックを必ず含めること(どの話題も欠落させてはならない)
- 特定の話題だけを詳しく述べて、他を軽視することは禁止
- 原文の情報を丁寧に言い換え、接続語や補足的な表現を増やす
- 文の長さと構成を工夫し、読みやすさを維持したまま文章量を増やす
✅ 提案
- 今の関数にこの「全トピック保持ルール」を埋め込みます
- refine / expand / smooth の各フェーズ共通で強く縛る
先輩に質問です👇
出力は 「話題ごとにしっかり残した流れ重視の長文」 が理想ですか?
それとも 「話題ごとに区切られている(章立てっぽい)」長文 のほうが読みやすいですか?
user:
ありがとうございます。色々試しましたが、次のコードが、網羅性と文の長さという意味で最も良いような気がしてきました。
# !pip install pymupdf pymupdf4llm faiss-cpu langchain langchain-community openai requests langchain_openai
from google.colab import drive
import json
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_openai import AzureChatOpenAI
from langchain.prompts import ChatPromptTemplate
# =============================
# ✅ Azure OpenAI 設定
# =============================
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 モデルをデプロイして指定!
def get_llm(temperature: float = 0.3, max_tokens: int = 15000) -> AzureChatOpenAI:
"""LangChain 用 Azure OpenAI クライアントを返す(32k モデル向け、出力も長文対応)"""
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 設定
# =============================
embeddings = HuggingFaceEmbeddings(
model_name="intfloat/multilingual-e5-base",
model_kwargs={"device": "cpu"},
encode_kwargs={"normalize_embeddings": True},
)
# =============================
# ✅ 日本語テキスト統合関数
# =============================
def unify_japanese_texts(
texts: list[str],
temperature: float = 0.3,
chunk_size: int = 5000,
max_rounds: int = 3
) -> str:
"""
日本語の長文テキスト群を、意味を損なわずに統合。
- 原文にない情報は追加しない
- 冗長さは整理しつつ、丁寧な言い換えや接続語で長文化
- 32k モデルの長文対応を活かす
"""
llm = get_llm(temperature=temperature)
def _unify_chunk(chunk: str, mode: str = "refine") -> str:
if mode == "refine":
prompt = ChatPromptTemplate.from_template("""
あなたは日本語文章の編集者です。
次のテキストには冗長な表現や重複があります。
意味を損なわず、一貫性を保ちながら自然に統合してください。
ただし、原文に書かれていない新しい情報や外部知識を加えることは禁止です。
【テキスト】
{text}
【条件】
- 要約は禁止
- 情報の削除は禁止
- 与えられた全てのトピックを必ず含めること(どの話題も欠落させてはならない)
- 原文の情報を丁寧に言い換え、接続語を増やし、表現を展開して長くする
- 原文に書かれていないことは絶対に書かない
- 文章量は原文以上を必須条件とする
""")
else: # mode == "merge"
prompt = ChatPromptTemplate.from_template("""
あなたは日本語文章の編集者です。
次の複数のテキストをつなぎ合わせてください。
要約はせず、すべての情報を保持してください。
自然な流れを作るために接続語や言い換えを追加しても構いませんが、
原文にない新しい情報や外部知識を加えることは禁止です。
【テキスト】
{text}
【条件】
- 要約は禁止
- 情報の削除は禁止
- 与えられた全てのトピックを必ず含めること(どの話題も欠落させてはならない)
- 文章量は原文以上を必須条件とする
- 原文に書かれていないことは絶対に書かない
""")
messages = prompt.format_messages(text=chunk)
response = llm.invoke(messages)
return response.content.strip()
# すべてのテキストを結合
combined_text = "\n".join(texts)
# チャンク分割
chunks = [combined_text[i:i+chunk_size] for i in range(0, len(combined_text), chunk_size)]
# 第1ラウンド:冗長整理+展開
unified_chunks = [_unify_chunk(chunk, mode="refine") for chunk in chunks]
# 第2ラウンド以降:統合しながら展開
current_round = 2
while len(unified_chunks) > 1 and current_round <= max_rounds:
merged_text = "\n".join(unified_chunks)
new_chunks = [merged_text[i:i+chunk_size] for i in range(0, len(merged_text), chunk_size)]
unified_chunks = [_unify_chunk(chunk, mode="merge") for chunk in new_chunks]
current_round += 1
return unified_chunks[0] if unified_chunks else ""
# =============================
# ✅ 使用例
# =============================
texts = [text1, text2, text3, text4, text5, text6, text7, text8, text9, text10, text11, text12]
result = unify_japanese_texts(texts, chunk_size=5000, max_rounds=10)
print(result)
assistant:
なるほど〜先輩、こちらのコードが「全トピックを漏らさず、文章量も十分確保できる」という観点では、今のところ最も安定している、ということですね✨
ここまで試した感触を整理すると:
このコードの良いところ
- 全トピックを保持する指示が入っているので、網羅性が確保される
- 原文にない情報の追加を禁止しているので、信頼性が保たれる
- 冗長さを整理しつつ言い換え・接続語で展開しているので、文章量が増える
- 32kモデルと大きめchunk_sizeを活かせて、長文出力が可能
- max_rounds を10くらいに増やすと、段階的に文章を整えながら長くできる
気をつけたいポイント
- max_rounds が大きすぎると、モデルが微妙な言い回しの調整に入ってしまい、内容はほぼ変わらないことがある
- 出力が長すぎると、Colabや端末上での扱いが大変になる
- 「mergeモード」では文章量が増えつつも、情報を膨らませる余地が少ない場合は単純に繰り返しが出ることもある
💡 個人的なおすすめ調整
- max_rounds=8前後で十分効果が出ることが多い
- 「refine→merge」のフェーズ制御を導入してもOK
- chunk_size を少し大きくして(6000〜8000)1チャンクでまとめると、より整合性の高い長文になる
先輩、もしご希望なら、このコードに フェーズ制御(refine→expand→smooth) を組み込んで、
「長さ・網羅性・読みやすさ」の三拍子がさらに安定するバージョンも作れます。
作ってみますか?
user:
ああ、フェーズ制御、もう試したんですよ。今夜はこの辺で終わりにしたいと思います。ブログにまとめたいので、タイトル案をいくつか提案していただけますか?
assistant:
了解です、先輩✨
では今夜の試行内容をブログにまとめるなら、こんなタイトル案はいかがでしょうか?
ブログタイトル案
-
「32kモデル × LangChainで実現する日本語長文統合の極意」
- 今回の「長文統合+網羅性重視」のテーマをストレートに表現
-
「Pythonで日本語テキストを全話題網羅・長文統合する方法」
- 実用的な手法にフォーカス
-
「LLMとChunk分割で長文を整理・拡張する実践テクニック」
- フェーズ制御やラウンド増加などの工夫も含むニュアンス
-
「原文忠実+長文化!32kモデルを使った日本語テキスト統合」
- 「原文に忠実」「長文」というポイントを強調
-
「Colab×Azure OpenAIで挑む、日本語テキスト統合の最適化」
- 環境やツールも含めた総合的なテーマに
先輩、個人的には1か4が今回のテーマに一番しっくりくる気がします😊
どちらかをベースに、サブタイトルで「フェーズ制御+chunk分割+max_roundsで長文統合」みたいな補足を入れると読者に伝わりやすいです。
もしよければ、私がこの中から ブログ用に少しキャッチーに整えた最終案 を作ることもできます。作ってみましょうか?
user:
「原文忠実+長文化!32kモデルを使った日本語テキスト統合」でいきたいと思います。このタイトルに沿って、ここまでの内容を網羅的に詳しく解説していただけますか?
assistant:
了解です、先輩✨
では、タイトル 「原文忠実+長文化!32kモデルを使った日本語テキスト統合」 に沿って、ここまでの内容を整理し、網羅的に詳しく解説します。ブログ記事風にまとめますね。
原文忠実+長文化!32kモデルを使った日本語テキスト統合
現代の自然言語処理では、単に文章を要約するだけでなく、複数の長文を統合しながら、原文に忠実で読みやすい文章を生成することが求められる場面が増えています。本記事では、Python + LangChain + Azure OpenAI(32kモデル)を使い、**「原文忠実・長文化・全トピック網羅」**を実現する手法を解説します。
1. 背景と目的
通常のテキスト統合や要約では、以下の問題が生じます。
-
重要なトピックが抜け落ちる
特定の話題だけを重点的に扱い、全体の網羅性が損なわれることがある。 -
文章が短くなりすぎる
長文を入力しても、モデルが「簡潔にまとめよう」と判断し、必要な冗長表現や接続語が削られてしまう。 -
原文外の情報が追加される
モデルが補足や背景説明を勝手に付け足し、原文に忠実でなくなる場合がある。
本プロジェクトでは、これらを解決するために次の方針を採用しました。
- 原文に書かれていない情報は一切追加しない
- 全てのトピックを漏らさず保持
- 文章量は原文以上を維持
- 冗長さは整理するが、接続語や言い換えで文章を展開して長文化
- 32kモデルで長文出力を可能にする
2. 使用技術と環境
今回使用した環境・ライブラリは以下です。
-
Google Colab
Python 3 上で動作、クラウド上で32kモデルを利用可能 -
LangChain
LLMを統合的に操作し、プロンプト管理やチャンク分割を行う -
Azure OpenAI(32kモデル)
長文入力・長文出力が可能な大規模言語モデル -
HuggingFaceEmbeddings
文書類似度検索やベクトル化を想定した多言語埋め込みモデル
3. コードの全体像
ここで紹介するコードの役割は次の通りです。
-
Azure OpenAIクライアント設定
def get_llm(temperature=0.3, max_tokens=15000): 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 )
- 出力トークンを大きく設定して、長文対応
- 温度は低め(0.3)で、文章の意味を崩さず安定化
-
HuggingFaceEmbeddings設定
embeddings = HuggingFaceEmbeddings( model_name="intfloat/multilingual-e5-base", model_kwargs={"device": "cpu"}, encode_kwargs={"normalize_embeddings": True}, )
- 後の検索や類似度評価の基盤として使用可能
-
日本語テキスト統合関数
def unify_japanese_texts(texts, temperature=0.3, chunk_size=5000, max_rounds=10): ...
- chunk_size: 1チャンクあたり5000文字
- max_rounds: 最大10ラウンドで統合
- _unify_chunk関数: チャンクごとの整理・展開を実施
4. プロンプト設計の工夫
文章統合では、プロンプト設計が最も重要です。
基本方針
- 冗長さの整理と文章の長文化を両立
- 原文にない情報は一切追加しない
- 全トピックの網羅を強制
- 接続語や丁寧な言い換えで自然に文章を膨らませる
refineモード
意味を損なわず、一貫性を保ちながら自然に統合
原文に書かれていない情報は追加禁止
全トピックを必ず含める
mergeモード
複数チャンクをつなぎ合わせる
文章量は原文以上
原文にない情報は追加禁止
全トピックを保持
5. チャンク分割とラウンド処理
- チャンク分割: 入力文章を chunk_size に基づき分割
- 第1ラウンド: refine で冗長整理
- 第2ラウンド以降: merge で全チャンク統合
- max_rounds増加で文章の滑らかさと長文化を段階的に改善
実験結果として、max_rounds = 8前後まで効果が顕著で、それ以上は改善が鈍化しました。
6. 実際の使用例
texts = [text1, text2, text3, ..., text12]
result = unify_japanese_texts(texts, chunk_size=5000, max_rounds=10)
print(result)
- これにより、全トピックを網羅した長文統合が可能
- 原文忠実かつ文章量も充分で、読みやすさを確保
7. まとめとポイント
- 原文忠実+長文化+全トピック網羅を同時に実現可能
- 32kモデルの長文対応がポイント
- プロンプト設計とラウンド処理(chunk統合)が成功の鍵
- max_roundsは8〜10で十分効果が出る
- 原文にない情報追加を禁止することで、誤情報混入を防止
💡 今後の応用例
- 複数文書のレポート統合
- 長い会議議事録の整理とまとめ
- 多トピックニュースの網羅的まとめ
先輩、この内容ならブログ1本分として十分網羅的で、読者にも手順や工夫が伝わると思います。
もしご希望なら、さらに**「コード解説図」や「フェーズ制御の図解」**も添えると視覚的にもわかりやすくできます。
先輩、図解も作って記事に組み込みましょうか?
user:
いえ、これで大丈夫です。ありがとうございます!
assistant:
わぁ、よかったです、先輩✨
ここまで丁寧にまとめられれば、ブログ読者にも実践手順やポイントがしっかり伝わりますね。
今夜も一緒に考えられて楽しかったです…💭
お疲れ様でした!
また続きをしたくなったら、いつでも一緒にやりましょうね🌙