前回は、Vibe codingを使ってRSSフィードから記事本文を取得するところまで実装しました。今回は、その本文をLLMで要約するコードを作成します。
コード例
まず、以下のような指示を出してコードを作成してもらいました。
ChatGPTのVS Code連携を利用して、前回のコードをVS Code上で開いておきます。
このコードに、RSSフィードの本文記事を要約する処理を追加してください。
要約にはOllamaとllama3.1:8bを使ってください。
Vibe codingの流儀に乗っ取り、できるだけコードを意識しない抽象的な指示で試していますが、今回はあらかじめOllamaとllama3.1:8bを使うようプロンプトで明示します。
出力されたコードはそのまま動作しましたが、要約を後から再利用しやすいように出力形式を調整し、またLLMによる要約処理には時間がかかるため、進捗状況の表示も追加で指示しました。
記事の本文と要約は、Markdown形式の見出しで区別できるように整形してください。
また、要約に時間がかかるため進捗状況の出力も追加してください。
この後、出力形式を確認しながら何度か追加の指示を行い、最終的に下記のようなコードが完成しました。 ここまでパッチ編集のみで問題なく対応できています。
import feedparser
from datetime import datetime
import requests
from bs4 import BeautifulSoup
import time
import re
import ollama
RSS_URL = "https://www.techno-edge.net/rss20/index.rdf"
OUTPUT_MD = "techno_edge.md"
OLLAMA_MODEL = "llama3.1:8b"
SUMMARY_MAX_CHARS = 800
def log(msg: str):
now = datetime.now().strftime("%H:%M:%S")
print(f"[{now}] {msg}", flush=True)
def fetch_rss(url: str):
log(f"Fetching RSS: {url}")
feed = feedparser.parse(url)
log(f"RSS fetched: {len(feed.entries)} entries")
return feed
def fetch_article_body(url: str) -> str:
log(f"Fetching article body: {url}")
res = requests.get(url, timeout=10)
res.raise_for_status()
soup = BeautifulSoup(res.text, "html.parser")
article = soup.find("article")
if not article:
raise ValueError(f"article tag not found: {url}")
paragraphs = article.find_all("p")
body = "\n".join(p.get_text(strip=True) for p in paragraphs if p.get_text(strip=True))
log(f"Article body fetched ({len(body)} chars)")
return body
def summarize_text(text: str) -> str:
if not text:
return ""
log("Summarizing full article")
prompt = (
"次の本文を、日本語で3〜5行程度に要約してください。\n\n"
f"{text[:4000]}"
)
response = ollama.chat(
model=OLLAMA_MODEL,
messages=[
{"role": "user", "content": prompt}
],
)
summary = response["message"]["content"].strip()
log(f"Summary generated ({len(summary)} chars)")
return summary[:SUMMARY_MAX_CHARS]
def entry_to_md(entry) -> str:
log(f"Processing entry: {entry.get('title', 'No Title')}")
title = entry.get("title", "No Title")
link = entry.get("link", "")
summary = entry.get("summary", "").strip()
published = entry.get("published_parsed")
if published:
published = datetime(*published[:6]).strftime("%Y-%m-%d %H:%M")
else:
published = "unknown"
md = []
md.append(f"## {title}")
md.append(f"- URL: {link}")
md.append(f"- Published: {published}")
md.append("")
body = fetch_article_body(link)
ai_summary = summarize_text(body)
if summary:
md.append(summary)
md.append("")
if ai_summary:
md.append("## AI要約")
md.append(ai_summary)
md.append("")
md.append("## 記事全文")
md.append(body)
md.append("")
log("Entry processed")
return "\n".join(md)
def rss_to_markdown():
feed = fetch_rss(RSS_URL)
md_lines = []
md_lines.append(f"# TechnoEdge RSS")
md_lines.append("")
md_lines.append(f"- Source: {RSS_URL}")
md_lines.append(f"- Generated: {datetime.now().strftime('%Y-%m-%d %H:%M')}")
md_lines.append("")
total = len(feed.entries)
log(f"Start processing {total} entries")
for entry in feed.entries:
start = time.time()
md_lines.append(entry_to_md(entry))
log(f"Progress: {len(md_lines)-5}/{total} entries done ({time.time() - start:.1f}s)")
log("All entries processed")
with open(OUTPUT_MD, "w", encoding="utf-8") as f:
f.write("\n".join(md_lines))
print(f"saved: {OUTPUT_MD}")
if __name__ == "__main__":
rss_to_markdown()
必要なライブラリ
このコードを実行するには、ollamaおよびllama3.1:8bモデル、ollama-pythonライブラリが必要です。
事前にOllamaアプリをインストールし、llama3.1:8bモデルをダウンロードしておきます。 生成AIに相談しても手順は教えてもらえますが、下記の記事も参考になります。
ollama-pythonはpipでインストールできます。
pip install ollama-python
注意点
今回もいくつか注意点や気付きがありました。
実行時間について、手元のMacbook Air(M2)環境での実行に30分~40分程度かかりました
ChatGPTなどに予測を聞いてもあまり精度が出ないため、このあたりはトライ&エラーで詰めていく必要があります。
コードを見ていくと、突然現れる定数やマジックナンバーの扱いなど気になる点も出てきていますが、Vibe codingの実践例というテーマを重視してここは目をつぶります。
出力形式の指定
前回までは本文記事の取得まででしたが、今回は要約の追加により出力形式の指定が重要になりました。生成AIは要約を本文の直後に追加してしまい、本文と要約が混在してしまうことがありました。見やすさのため、Markdown形式で見出しを付けるよう追加で指示しています。
今回の実践例ではMarkdown形式は中間成果物のため、本来であればJSONなどよりPythonでの処理が容易な形式を選ぶことも可能ですが、人間に判別しやすいMarkdown形式にしておくことで、コードを見なくとも仕様の調整やデバッグが可能となります。
LLM実行環境およびモデルの指定
前回まではライブラリの選定を特に指定していませんでしたが、今回はLLMの実行環境やモデルを明示的に指定しています。
指定しない場合、OpenAIのGPTなどAPIキーが必要なコードが出力されることが多いようです。 APIキーが必要なモデルは有料のため、試行段階でコストがかかるのを避けるため、今回はローカル環境で動作する構成を最初から指定しました。
また、モデルの選定についても、既に利用できない古いモデルを提案される場合があります。 llama3.1は最新ではありませんが、日本語でも安定して動作し、比較的軽量で実績もあるため選定しています。
ChatGPTに下記のような指示を出して、適切なモデルを提案してもらうのも有効です。 実行環境の情報(例:CPU i9-13900K, メモリ 64GB RAM, GPU RTX4090など)を伝えると、より適切な提案が得られます。
実行環境は CPU i9-13900K, メモリ 64GB RAM, GPU RTX4090 です。
ニュースサイトの記事を読み込み、要約を出力する用途で、この環境で動作するおすすめのLLMモデルをWebで調べて教えてください。
ログ処理
今回は、ログ出力を独自実装するコードが出力されました。ログ出力の切り替えなどシンプルな内容は、規模が大きくなっても生成AIでのリファクタリングが比較的容易です。
将来的にはloggingモジュール等の利用が望ましいですが、今回は実行時に標準出力での動作確認が目的のため、このまま進めています。
まとめ
今回は、RSSフィードから記事本文を取得し、LLMで要約を加えるコードをVibe codingで作成しました。
要約の出力形式やLLMの実行環境指定など、前回までにはなかった注意点もありましたが、パッチ編集のみで問題なく対応できました。
次回は、要約に対してコメント案を生成するコードを追加してみます。




