簡易ＲＡＧでブログ記事を執筆

最近、はてなブログをイイ感じで執筆させる、俺のＲＡＧシステムを構築した。
# プロンプト公開

まずは実例をイメージしたい


https://ameblo.jp/motochan1969/entry-12875086418.html

```
# タイトルの作り方：
「今日の[キーワード]から考える[目的]！[具体的な方法や発見]」
例：「今日の牛乳パックから考えるエコ生活！使い終わった容器で始める整理術」

# ブログ記事テンプレート：
====================
【50代サラリーマンの気ままな日常】

■今日の出来事：
夕方、キッチンで牛乳パックを片付けようとした時...
[具体的なきっかけを2-3文で]

■アイデアのヒント：
ふと考えたんですが...
[アイデアに至った経緯を2-3文で]

【本編】
タイトル：[上記の形式で]
サブタイトル：[具体的なメリットを入れる]

1. はじめに
[問題提起を2-3文で]

2. 今日試してみたこと
[具体的な手順を箇条書きまたは段落で]

3. 気づいた良いポイント
[3-4つ、それぞれ1-2文で]

4. 注意点・コツ
[失敗しないポイントを2-3つ]

5. これから試してみたいこと
[次のアイデアを1-2文で]

■今日の晩ごはん：
[夕食の感想や工夫を1-2文で]

■単身赴任の思い出：
[家族との関連エピソードを2-3文で]

■明日への期待：
[次の展開への期待を1文で]

【締めの一言（吉本新喜劇風）】
A：[今日のテーマに関する質問]
B：[オチにつながる返答]
(ズッコケる)

#ImageCreatorプロンプト：
[動物]が[テーマに関連した動作]をしている様子、[背景説明]、miniature

#関連リンク：
[過去記事URL]

#アフェリエイト：
[関連商品のリンク]
====================
```


# 入力情報の作成

まずは前データの作成。これは自分のはてなブログからＡＰＩ経由で関連記事を取得する。
参考サイト　https://qiita.com/ftnext/items/004cf02d1a421138c196

```python
import os
import xml.etree.ElementTree as ET
import requests
from bs4 import BeautifulSoup

def load_credentials(username):
    """はてなAPIアクセスに必要な認証情報をタプルの形式で返す"""
    auth_token = os.getenv("HATENA_BLOG_ATOMPUB_KEY")
    message = "環境変数`HATENA_BLOG_ATOMPUB_KEY`にAtomPubのAPIキーを設定してください"
    assert auth_token, message
    return (username, auth_token)

def retrieve_hatena_blog_entries(blog_entries_uri, user_pass_tuple):
    """はてなブログAPIにGETアクセスし、記事一覧を表すXMLを文字列で返す"""
    r = requests.get(blog_entries_uri, auth=user_pass_tuple)
    return r.text

def select_elements_of_tag(xml_root, tag):
    """返り値のXMLを解析し、指定したタグを持つ子要素をすべて返す"""
    return xml_root.findall(tag)

def return_next_entry_list_uri(links):
    """続くブログ記事一覧のエンドポイントを返す"""
    for link in links:
        if link.attrib["rel"] == "next":
            return link.attrib["href"]

def is_draft(entry):
    """ブログ記事がドラフトかどうか判定する"""
    draft_status = (
        entry.find("{http://www.w3.org/2007/app}control")
        .find("{http://www.w3.org/2007/app}draft")
        .text
    )
    return draft_status == "yes"

def extract_japanese_text(html_content):
    """HTMLコンテンツから日本語テキストを抽出する"""
    soup = BeautifulSoup(html_content, "html.parser")
    text = soup.get_text()
    japanese_text = ''.join(char for char in text if is_japanese(char))
    return japanese_text

def is_japanese(char):
    """文字が日本語かどうかを判定する"""
    return any([
        '\u3040' <= char <= '\u30ff',  # Hiragana and Katakana
        '\u4e00' <= char <= '\u9faf',  # CJK Unified Ideographs
        '\u3000' <= char <= '\u303f',  # Punctuation
    ])

if __name__ == "__main__":
    # 固定されたユーザーIDとブログドメイン
    hatena_id = ""
    blog_domain = ""

    user_pass_tuple = load_credentials(hatena_id)

    # root endpointを設定
    root_endpoint = f"https://blog.hatena.ne.jp/{hatena_id}/{blog_domain}/atom"
    blog_entries_uri = f"{root_endpoint}/entry"

    target_entries = []

    while blog_entries_uri:
        entries_xml = retrieve_hatena_blog_entries(blog_entries_uri, user_pass_tuple)
        root = ET.fromstring(entries_xml)

        links = select_elements_of_tag(root, "{http://www.w3.org/2005/Atom}link")
        blog_entries_uri = return_next_entry_list_uri(links)

        entries = select_elements_of_tag(root, "{http://www.w3.org/2005/Atom}entry")
        for entry in entries:
            if is_draft(entry):
                continue
            target_entries.append(entry)

    with open("hatena_blog_entries.txt", "w", encoding="utf-8") as file:
        for entry in target_entries:
            title = entry.find("{http://www.w3.org/2005/Atom}title").text
            link = entry.find("{http://www.w3.org/2005/Atom}link").attrib["href"]
            content_element = entry.find("{http://www.w3.org/2005/Atom}content")
            content_html = ET.tostring(content_element, encoding='unicode', method='xml')
            content_html = content_html.replace('<content xmlns="http://www.w3.org/2005/Atom" type="text/html">', '').replace('</content>', '')
            japanese_text = extract_japanese_text(content_html)
            file.write(f"Title: {title},Link: {link},Content: {japanese_text}\n")

    print("記事の情報がファイル 'hatena_blog_entries.txt' に保存されました。")
```

# 出力データ
自分の恥ずかしいブログから、関連記事を引っ張ってくる。
元データ　https://motochan1969.hatenablog.com/
```
Title: 外国語の効率の良い勉強法,Link: https://blog.hatena.ne.jp/motochan1969/motochan1969.hatenablog.com/atom/entry/6801883189114752529,Content: 昨日は横浜にある中国語教室に立ち寄り、広東語を学ぶつもりだったが、最終的には普通語の体験レッスンを受けて終わった。最近、ふと思うのだが、要するに一番大事なことは、まず外国人に接することである。それは誰でも何でもよいのであるが、まず人に会って会話をする。そこから全てが始まっているのである。これまで英語でのシャドーイングを年近くやってきたが、これは要するに聞いた音を正確に口から出すという、ある意味の猿真似である。最初は音にのみ集中するので、単語などは一切分からなくても良い。ただ、人が話している内容を正確に、音として自分の中にレコーディングすることが大事である。これは耳のセンサーで音を聞き取り、頭の中で音声を変換して、これを発声という身体動作に置き換える。発声した音は、次に再度自分の耳で聞きとり、発声と数秒前の聞き取りとの違いを訂正する流れである。シャドーイングの弊害　最近、英語を聞くと反射的にリピートしてしまう癖がついてしまった。この癖は、頭の中で一時的に情報を保存し、その後意味を理解するためのタイムラグを作る役割を果たしているが、実際の会話の中でも反復していると、単なる変な人だと思われるかもしれない。何事もやりすぎると弊害が生じる　また実際には、シャドーイングばかりやっていても、自分で言いたいことを話す能力をつけなければ、会話が成り立たないことにも注意が必要だ。できれば、自分が外国人と話している状況を例文で作って、これを丸暗記した方が、スムーズに話す訓練につながるのかもしれない。　会話集の作成　最近はが便利だ。この手の会話を一人二役までこなしてくれて、それを音声として発声までしてくれる。最後に音声ファイルに吹き込んで、これを完全コピーしてしまえば、もう特定のシチュエーションでは、完全に会話に困らない少なくとも、相手から見れば、完全にネイティブ張りにぺらっぺらと勘違いしてしまう　要するに言葉なんて言うものは、そんな程度でも十分であって、あとはいろいろな人生経験を積む中でシチュエーションを拡張していけば良いだけである。もちろん、コツコツと基礎を積んでいくことも重要だと思うが、その場限りの会話をするだけなら、細かい部分はすっ飛ばしても問題ないと思う。言語化の次のステップ　ある程度のシャドーイングと猿真似ができるようになったら次のステップに入るここまででも、最低年以上はかかると思うが　次のステップは相手の言語を書き文字として変換するプロセスに入る。どうして音声だけでは駄目なのかというと、英語でもアメリカとイギリスで違うように、同じ内容であっても音が変わってしまうからである。また、男性、女性の違いや声の特徴によって耳に入ってくる音声はそもそも違うのである。　書き文字にする意味というのは、まさに音声だけでは曖昧になりがちな部分を明確にするためであり、これにより理解が深まる。書き文字にすることで、正確な発音や意味の違いが把握できるようになり、さらに自分の表現力を高めることができる。これが、言語学習における次の重要なステップである。　書き文字にするには　この点では韓国語と英語に関しては、表音文字であるので有利である。一番良いのは、聞いた音から頭の中で文字に変換できること。これは作文能力に近いといえる。もちろん外国語の文字に置き換える能力がないのであれば、ここで登場するのが日本語への変換である。例えば、「」を「アップル」と書き記すことや、「」を「コンピュータ」と表記することが挙げられる。　ただし、これは後述する日本人の発音理解の根本的な問題で、やり方を間違えると別の言葉を生成してしまうので注意が必要である。　無理矢理に文字を当てはめてしまうと、違った発音になってしまう。願わくば、はアップルではなく、アポー、はコンピューターではなくコンピュラーとするのが理想である。他にも代表的な例を以下に示す。はベジタブルではなく、ヴェジェタブル。はマクドナルドではなく、マクダーノルズ。はサンキューではなく、センキュー。はカメラではなく、キャメラ。はビタミンではなく、ヴァイタミンズ。はゴルフではなく、ガルフ。はカーテンではなく、カートン。はスタジアムではなく、ステイディアム。はチョコレートではなく、チャクレト。　できれば、このような形で実際の発音に近い音で、日本語の表記にねじ込むことが重要である。思えば中国留学した時に、最初、教師の発生が全く分からず、ただただ呆然と時間を過ごすしかなかったときに、ノートに片っ端からカタカナで書き綴った経験が、今となっては言語学習の原点だったと気づく。あの時に耳に焼き付けた音が残っているから、今自分の中国語が、ほぼ癖のないネイティブの発音が身についたのだと思う。このノウハウは、いろんな言語の勉強法にも当てはまるし、ある意味で効率の良い勉強なのではないかといまだに自負している。【英語発音クリニック】ジョン万次郎のカタカナ発音表記法が現代英語に必要なものを教えてくれる
Title: Cheryl Strayed reads from "Wild" 9/29/12,Link: https://blog.hatena.ne.jp/motochan1969/motochan1969.hatenablog.com/atom/entry/6801883189105604669,Content: 　ネイティブに学ぶ表現ちょっと自然な流れでここにいる人で何人が読んだことがありますかたくさんいますね私が話すことのいくつかは、皆さんはもう読んでいるでしょうこの場にいる人で詳しく知らない人のために何が起こったかというととても親しかった私色々あって母なしでどうやって生きていくかあなたにとってかけがえのない人を亡くした経験のある人は、私の言いたいことがわかるでしょうそうでない人は、私を信じてくださいもう生きていけないと思うような人を失うことがあります世界は大きく変わってしまい、その中でどう生きていくか、自分はどうあるべきかを見つけ出さなければなりませんたとえ人生が順調に進んでいたとしても常にその答えを見つけようとしている世界で自分はどんな人間なのか、どう定義するのかやってはいけないことをたくさんしたすごいのはたまたまよくあることの一つは文字通りのことが起こるそれが、自分が置かれている状況の完全なメタファーであることそれはまるで鏡が顔から数センチのところに突きつけられているようなものです文字通り、自分を掘り出さなければならなかった私もそこにいた、そこにいたんですみんな聞こえますか
Title: 外国人との出会いの活発な街　横浜,Link: https://blog.hatena.ne.jp/motochan1969/motochan1969.hatenablog.com/atom/entry/6801883189098641171,Content: 案の定コロナに感染我正在學中文前回のコロナ感染の反省を交えて、東京へのゲームボードサークル参加はやめることにしました。この間かなりの身体的ダメージを受けて、鍼灸治療などにも通いましたが、ようやく完治したので、活動を再開し始めました。現在の活動拠点を示します。場所神奈川県民センター時間毎週木曜日横浜英会話クラブ場所神奈川県民センター時間毎週土曜日横浜の英会話サークル横浜英会話倶楽部安い英会話場所ジョイナスターリーズコーヒー時間毎週日曜日どの活動も、楽しく学びながら交流できる場となっています。ぜひご参加くださいお問い合わせご不明な点等ございましたら、お気軽にお問い合わせください。横浜サークル一同皆様のご参加お待ちしております参考資料外国人と日本人が交流しながら、横浜でハイキングや街歩きを楽しむゆるいサークル断捨離横浜の友達のメンバー募集・無料掲載の掲示板ジモティー言語交換ミーティング日本語を学びたい外国人と、英語や他の言語を学びたい日本人が集まる言語交換ミーティングを開催します断捨離横浜のその他のメンバー募集・無料掲載の掲示板ジモティー
Title: スピーキングテスト powered by PROGOS で20分間の英会話訓練！,Link: https://blog.hatena.ne.jp/motochan1969/motochan1969.hatenablog.com/atom/entry/6801883189094620644,Content: 会社で話題のスピーキングテストを試してみましたこのテストは、がスピーキング能力をつの指標で評価してくれるというもの。無料で分間、本格的な英会話訓練ができるので、英語学習者にとって非常に魅力的なサービスです。実際に受けてみた感想テスト内容は、日常的なトピックに関する質問に答えたり、与えられた画像やグラフについて説明したりというもの。が自然な発音や流暢さ、文法の正確さなどを評価してくれます。分間という限られた時間の中で、集中して英語を話すことで、スピーキング能力の向上に役立つと感じました。毎日続けたいテスト結果は、に基づいてつの指標で示されるので、自分の英語力レベルを把握することができます。また、弱点や改善点も具体的に指摘してくれるので、今後の学習に役立てられます。無料でこれだけ本格的なスピーキングテストを受けられるのは、非常にありがたいです。毎日継続することで、確実に英語力を向上させられると思います。ブログを読んでいただきありがとうございますこのブログでは、英語学習に関する様々な情報を発信しています。ぜひ他の記事も読んでみてください以下に、スピーキングテストに関する情報をまとめました。公式サイト受験料無料テスト時間分評価指標つの指標発音、流暢さ、文法、語彙、理解力、総合力レベル受験方法インターネットメリット無料で本格的なスピーキングテストを受けられる自分の英語力レベルを把握できる弱点や改善点を知ることができるデメリットテスト時間は分と短いインターネット環境が必要おすすめの学習方法毎日少しずつでも英語を話す英語を聞き流す英語のニュースや映画を見る英語学習アプリを活用する英語学習は継続することが大切です。ぜひこのブログを参考に、自分に合った学習方法を見つけてください
Title: What to bring on a day hike,Link: https://blog.hatena.ne.jp/motochan1969/motochan1969.hatenablog.com/atom/entry/6801883189089992393,Content: 荷造りリットル雨カバー水ボトル犬三脚レインジャケットヘッドランプナイフ虫除けスプレー応急処置キットパワーバンク犬の食べ物ポーチ鍵スナックトイレットペーパースタッフサックスープヘッド折り畳み式水ボウル帽子クリネックスティッシュサングラス熊スプレー多目的
```

# RAGシステム

自作のＲＡＧシステム
coreが自然言語処理、mainはＵＩ部分

最初はFASTAPI+REACTで書いていたが、認証構築が嫌になり、GRADIOでアプリ化　→　結局、個人使いならこれが楽という結論・・・

ファイルは、とりあえずCSV形式で与える。
要するにDataframe['Column1']にテキストが突っ込まれればそれで良い。

```core.py
import pandas as pd
import numpy as np
import re
from janome.tokenizer import Tokenizer
from janome.analyzer import Analyzer
from janome.charfilter import UnicodeNormalizeCharFilter
from janome.tokenfilter import POSKeepFilter, POSStopFilter, LowerCaseFilter
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
import pickle
import pdb
import chardet


import re
from janome.tokenfilter import POSKeepFilter, POSStopFilter, LowerCaseFilter, ExtractAttributeFilter, TokenCountFilter, CompoundNounFilter

import re
from janome.tokenizer import Tokenizer
from janome.analyzer import Analyzer
from janome.charfilter import UnicodeNormalizeCharFilter
from janome.tokenfilter import POSKeepFilter, POSStopFilter, LowerCaseFilter, CompoundNounFilter

# カスタム辞書を使用してTokenizerを初期化
UDIC = r"user_simpledic.csv"
tokenizer = Tokenizer(UDIC, udic_type="simpledic", udic_enc="utf8")

# 前処理用の正規表現
preprocessing_regex = re.compile(r'[^a-zA-Z0-9ぁ-んァ-ヶー一-龥\s]')

from janome.tokenfilter import TokenFilter

class NumericFilter(TokenFilter):
    def apply(self, tokens):
        for token in tokens:
            if not token.part_of_speech.startswith('名詞,数'):  # 数詞を除外
                yield token

class LengthLimitFilter(TokenFilter):
    def __init__(self, max_length=10):
        self.max_length = max_length

    def apply(self, tokens):
        for token in tokens:
            if len(token.surface) <= self.max_length:
                yield token

# Token filtersのリストに追加
token_filters = [
    #CompoundNounFilter(),  # 複合語の分割
    POSKeepFilter(['名詞', '動詞', '形容詞']),  # 名詞、動詞、形容詞のみ残す
    POSStopFilter(['助詞', '助動詞']),  # 助詞と助動詞を除去
    LowerCaseFilter(),  # 小文字化
    NumericFilter(),  # 数字を除去
    LengthLimitFilter(max_length=4)  # 10文字以上の複合語を除去
]

def preprocess(text):
    text = preprocessing_regex.sub('', text)
    return text



def keitaiso(texts):
    char_filters = [UnicodeNormalizeCharFilter()]
    analyzer = Analyzer(char_filters=char_filters, tokenizer=tokenizer, token_filters=token_filters)
     
    words = []
    for text in texts:
        text = preprocess(text)
        tokens = [token.surface for token in analyzer.analyze(text)]
        words.append(' '.join(tokens))

    return words

def tokenize(text):
    tokenizer = Tokenizer()
    return [token.surface for token in tokenizer.tokenize(text)]

def load_data_from_excel(file_path):
    df = pd.read_excel(file_path)
    df['Column1'] = df.apply(lambda row: ' '.join(row.dropna().astype(str)), axis=1)
    df = df[['Column1']]
    return df



def vectorize_texts(texts):
    vectorizer = TfidfVectorizer()
    tfidf_matrix = vectorizer.fit_transform(texts)
    return tfidf_matrix, vectorizer

def train_and_save_model(texts_to_analyze, file_name='model.pkl', model=None):
    
    analyzed_texts = keitaiso(texts_to_analyze)
    
    vectorizer = TfidfVectorizer()
    tfidf_matrix = vectorizer.fit_transform(analyzed_texts)
    print('shape ->',tfidf_matrix.shape)
    print(tfidf_matrix)
    print(tfidf_matrix.toarray())

    with open(file_name, 'wb') as file:
        pickle.dump((vectorizer, tfidf_matrix, model), file)

def load_model(file_name='model.pkl'):
    with open(file_name, 'rb') as file:
        vectorizer, tfidf_matrix, model = pickle.load(file)
    return vectorizer, tfidf_matrix, model

def predict_with_model(query_text, vectorizer, tfidf_matrix, df, top_n=1):
    query_analyzed_text = keitaiso([query_text])[0]
    closest_entries, valid_words = find_closest_entries(query_analyzed_text, tfidf_matrix, vectorizer, df, top_n=top_n)
    
    if valid_words:
        print(f"Valid words in query: {', '.join(valid_words)}")
    
    return closest_entries

def find_closest_entries(query, tfidf_matrix, vectorizer, df, top_n=1, top_words=10):
    query_vec = vectorizer.transform([query])
    cosine_similarities = cosine_similarity(query_vec, tfidf_matrix).flatten()
    top_n_indices = np.argsort(-cosine_similarities)[:top_n]
    top_n_similarities = cosine_similarities[top_n_indices]
    closest_entries = df.iloc[top_n_indices]

    # 関連する単語の抽出
    feature_names = vectorizer.get_feature_names_out()
    related_words = []
    for idx in top_n_indices:
        entry_vec = tfidf_matrix[idx].toarray().flatten()
        top_word_indices = np.argsort(-entry_vec)[:top_words]
        top_words_list = [feature_names[i] for i in top_word_indices]
        related_words.append(top_words_list)

    # 有効な単語の抽出
    query_words = query.split()
    valid_words = [word for word in query_words if word in feature_names]

    results = [(entry.Column1, similarity, words)
               for entry, similarity, words in zip(closest_entries.itertuples(index=False), top_n_similarities, related_words)]

    return results, valid_words


def initialize():
    file_path = 'files_info2.xlsx'
    df = load_data_from_excel(file_path)
    texts_to_analyze = df['Column1'].tolist()
    train_and_save_model(texts_to_analyze, model=df)
    vectorizer, tfidf_matrix, df = load_model()
    return vectorizer, tfidf_matrix, df

import pandas as pd
import pdb

def load_csv_file(file_path, encoding):
    # Determine the maximum number of columns
    with open(file_path, 'r', encoding=encoding) as f:
        max_columns = max(len(line.split(',')) for line in f)

    # Read the CSV file
    df = pd.read_csv(file_path, header=None, names=range(max_columns), sep=None, engine='python', encoding=encoding)

    # Combine all columns into one
    df['Column1'] = df.apply(lambda row: ' '.join(row.dropna().astype(str)), axis=1)
    return df[['Column1']]

def load_txt_file(file_path, encoding):
    with open(file_path, 'r', encoding=encoding) as file:
        content = file.read()
    return pd.DataFrame({'Column1': [line for line in content.split('\n') if line.strip()]})

def load_xlsx_file(file_path):
    df = pd.read_excel(file_path)
    df['Column1'] = df.apply(lambda row: ','.join(row.dropna().astype(str)), axis=1)
    return df[['Column1']]

def load_data_from_file(file_path):
    encodings_to_try = ['utf-8', 'cp932', 'shift_jis', 'euc-jp']
    
    for encoding in encodings_to_try:
        try:
            if file_path.lower().endswith('.csv'):
                return load_csv_file(file_path, encoding)
            elif file_path.lower().endswith('.txt'):
                return load_txt_file(file_path, encoding)
            elif file_path.lower().endswith('.xlsx'):
                return load_xlsx_file(file_path)
        except UnicodeDecodeError:
            continue
        except pd.errors.ParserError:
            print(f"Warning: Some lines in the CSV file could not be parsed with encoding {encoding}. Skipping those lines.")
            continue
    
    raise ValueError(f"Unable to read the file with any of the attempted encodings: {encodings_to_try}")

def upload_and_train(file):
    # Assuming 'file' is a NamedString that contains the file path
    file_path = str(file)  # Convert NamedString to a regular string to extract the path

    # Process the file content as needed using the previously defined load_data_from_file function
    df = load_data_from_file(file_path)  # This function will handle different file types

    # Extract texts to analyze from the 'Column1' of the DataFrame
    # Ensure that 'Column1' is indeed the column you are interested in, or adapt accordingly
    if 'Column1' not in df.columns:
        raise ValueError("The expected column 'Column1' is not present in the DataFrame.")
    texts_to_analyze = df['Column1'].tolist()

    # Use the texts_to_analyze in your model training and saving logic
    train_and_save_model(texts_to_analyze, file_name='model.pkl', model=df)

    return "Model trained and saved successfully."


def make_prediction(query_text, top_n):
    vectorizer, tfidf_matrix, df = load_model('model.pkl')
    closest_entries = predict_with_model(query_text, vectorizer, tfidf_matrix, df, top_n=top_n)
    return closest_entries

```
```main.py
import gradio as gr
import pandas as pd
import numpy as np
import re
from janome.tokenizer import Tokenizer
from janome.analyzer import Analyzer
from janome.charfilter import UnicodeNormalizeCharFilter
from janome.tokenfilter import POSKeepFilter, POSStopFilter, LowerCaseFilter, TokenFilter
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
import pickle
import tempfile
import os
from core import predict_with_model, load_model,load_data_from_excel,load_data_from_file,UDIC,tokenizer,preprocessing_regex
from core import NumericFilter,LengthLimitFilter,token_filters,preprocess,keitaiso
import pdb
from core import train_and_save_model,find_closest_entries,upload_and_train,make_prediction


def make_prediction(query_text, top_n):
    vectorizer, tfidf_matrix, df = load_model('model.pkl')
    closest_entries = predict_with_model(query_text, vectorizer, tfidf_matrix, df, top_n=top_n)
    return closest_entries

def gradio_interface(file, query_text, top_n):
    upload_result = upload_and_train(file)
    prediction_result = make_prediction(query_text, top_n)
    return upload_result, prediction_result

def predict_and_format(file, query_text, instruction_text, top_n=1):
    try:
        # First, handle the file upload and model training
        upload_result = upload_and_train(file)
        # Ensure the model is updated or loaded correctly
        vectorizer, tfidf_matrix, df = load_model('model.pkl')
        
        # Then proceed with making a prediction
        closest_entries = predict_with_model(query_text, vectorizer, tfidf_matrix, df, top_n=top_n)

        # Format the prediction results
        result = "指示内容:\n{}\n\nクエリ内容:\n{}\n\n補足情報:\n".format(instruction_text, query_text)

        for index, (summary, similarity,detail) in enumerate(closest_entries, start=1):
            summary_str = ' '.join(map(str, summary)) if isinstance(summary, list) else str(summary)
            similarity_str = '{:.4f}'.format(similarity) if isinstance(similarity, (int, float)) else str(similarity)
        
            result += "\nEntry {}:\nSummary: {}\nSimilarity Score: {}\n".format(
                index, summary_str, similarity_str
            )
        
        return upload_result, result  # Return both the upload and prediction results
    except Exception as e:
        return "Error occurred: " + str(e), "No results due to error"

def setup_gradio_interface():
    iface = gr.Interface(
        fn=predict_and_format,
        inputs=[
            gr.File(label="Upload File"),
            gr.Textbox(label="Query Text", placeholder="Enter text here..."),
            gr.Textbox(label="Instruction Text", placeholder="Enter any special instructions..."),
            gr.Number(label="Top N", value=1, step=1)
        ],
        outputs=[
            gr.Textbox(label="Upload Result"),
            gr.Textbox(label="Prediction Result")
        ],
        title="Text Analysis and Prediction",
        description="Upload your file, enter the query and instruction texts, and get the predictions with details formatted."
    )
    return iface  # Ensure this line is present to return the interface object

# Initialize and launch the interface
interface = setup_gradio_interface()
interface.launch(share=True)
```

# 画面

こんな感じで、CSVをアップロードして、QUERYで書きたい記事を入力
指示プロンプトはやって欲しいことを適当に書く

QUERYの入力も、友達へのチャット連絡のコピペとか、わさわざ時間をかけないこと

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212914/a2156cbc-ba18-dc0e-de63-30584f54ca84.png)

# 結果

とりあえず、上記を実行するとＲＡＧの参考情報付きのプロンプトが完成するので、丸ごとコピペする。

> GEMINI

横須賀のマリナーで出会った金脈掘りの社長
出会い

先日、横須賀のマリナーという喫茶店で、一人の男性と出会いました。彼はライフルやダイナマイトの話、ヘリコプターや飛行機の話など、ぶっ飛んだ話題ばかりで、最初は会話についていけませんでした。

経歴

彼は10年間日本に住んでおり、世界中を金脈掘りで旅してきたそうです。社長業を営んでいるとのことですが、結婚はしておらず、アメリカに帰国するために土産を買いに来ていたらしいです。

趣味

山登りが趣味だそうですが、休みもほとんどなさそうでした。

印象

彼はとてもエネルギッシュで、自由奔放な人という印象を受けました。10年間も世界中を旅して金脈掘りをしているなんて、なんてすごいんだろう！

まとめ

短い時間でしたが、とても興味深い話を聞くことができました。いつかまた彼のような人に出会えたらいいなと思います。

補足

この文章は、以下の情報を参考に作成しました。

クエリ内容
補足情報
5つのブログ記事
クエリの指示内容に従い、以下の点に注意して作成しました。

クエリ内容を要約する
補足情報を活用する
読みやすく、興味深い文章にする
また、以下の点も意識しました。

自分の経験や考えを盛り込む
ユーモアやウィットを取り入れる
読者の共感を呼ぶ
この文章が、クエリの指示内容に合致していることを願っています。

# 考察

ブログ記事で生成ＡＩを使うコツとしては、自分が継続的に書いている元ネタのエッセンスを踏襲しつつ、自分の口調や考え方を生かすことがポイントである。
また自己の記事への参考リンクなどを付けるなど、過去のコンテンツへの再アクセス性も向上するなど、相乗効果が生み出せる利点もある。

# 完成記事

https://motochan1969.hatenablog.com/entry/20240623/1719114924?_gl=1*1eu8030*_gcl_au*MTMwNjUwNzE2Ni4xNzE5MTAwMTI4