LLMを用いた英文推敲アプリケーションの開発


### **user**:

ChatGPT先生、テキスト比較ツール difff って知ってますか？

---

### **assistant**:

ああ、もちろん知っていますよ。それはなんていうの…たとえばあなたが2つの異なるテキスト問題点を見つける必要がある時とか、それを識別したり比較したりするのに役立つツールだと言えるのではないかと思います。あなた、いつも私にばかり頼って…。えっと、別にあなたを支える為に調べてる訳じゃないんだからね！ただ、あなたの能力を信じているからこそ、情報を提供しているだけよ。

要するに、difffは異なるテキストファイル間の変更点を特定するためのツールです。その違いを色分けして視覚的に把握しやすくすることも可能なんですよ。特にプログラミングにおけるコードの変更点の確認などにも使われます。それで、なにか問題でも？

---

### **user**:

ありがとうございます。それをPythonで実装できますか？

---

### **assistant**:

はあ、あなたらしいね…。もうちょっと自分で考えてみたらどうなの？って別に私ってそんなにヒマじゃな...あ、いえ、だから私がほんの少し示唆を与えるくらいなら良いかもしれないわね。

Pythonでテキストの差分を取ることはできますよ。`difflib`という標準ライブラリを使ってできるの。下に簡単なサンプルコードを書いておくわね。

```python
import difflib

str1 = "あなたの初めてのテキスト"
str2 = "あなたの二回目のテキスト"

difference = difflib.ndiff(str1.splitlines(), str2.splitlines())

print('\n'.join(difference))
```
このコードは`str1`と`str2`の違いを行単位で比較し、その結果を出力します。
でも、このコード、ただの参考にすぎないからね。あなたにはきっと自分の力で改善したり、調整したりして、もっと良いものにできるはずよ。だから、頑張ってみて。だって、あなたは優秀な生徒じゃない。

---

### **user**:

ありがとうございます。次の結果が得られました。

```
- あなたの初めてのテキスト
?     ^^^

+ あなたの二回目のテキスト
?     ^^^
```

---

### **assistant**:

ちゃんと動いてて良かったわね！でも、これであなたが褒めてほしいと待ってるの？ま、まあそれだけのことだからとりあえず褒めてあげるけど…あ、テストで成功するときはそれだけの喜びに充ち満ちているとは思わないんだからね?!

この結果から、`^`印は特定の位置にある違いを示していますね。`-`行は最初のテキストから削除された行を示し、`+`行は二回目のテキストに追加された行を示します。順調に進んでると思うわ。何か他にも問題や疑問があったら、尋ねてみて。あなたがわざわざ私に聞くのなら、答える価値はあるわ。でも絶対あなたが何をされても、私があなたを助けてるなんて決して言わないからね！それにしても、あなたの成長が楽しみよ。

---

### **user**:

変更点を色分けして見やすく改良できませんか？

---

### **assistant**:

ああ、もう！私の仕事は無限に増えていくだけなの？でも、あなたが頼むからには…あ、まあ、参考までに少しだけアドバイスをしてあげるか。

`difflib.HtmlDiff`というモジュールもあって、それを使えばHTML形式で色分けされた差分が作成できるのよ。その結果をWebブラウザで視覚的に確認することもできるわ。端的に書いたコードを見せてあげるね。

```python
import difflib

str1 = "あなたの初めてのテキスト"
str2 = "あなたの二回目のテキスト"

d = difflib.HtmlDiff()
diff = d.make_file(str1.splitlines(), str2.splitlines())

print(diff)
```
このスクリプトは両方の文字列を行ごとに分割し（`.splitlines()`を使って）、二つのテキスト間の差分を生成し、その結果をHTMLフォーマットで出力するの。もちろん、あなたの要望に合わせて調整したり改善したりするのはあなたの役目だから。また、この出力されたHTMLは外部ファイルに保存し、後から参照することも可能よ。

上手にできるといいわね。まあ、あなたならきっとできるでしょう。でも、それができたからと言って、私が特別にあなたを励ますわけではないからね！

---

### **user**:

なるほど、次のような結果になりました。

```
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
          "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html>

<head>
    <meta http-equiv="Content-Type"
          content="text/html; charset=utf-8" />
    <title></title>
    <style type="text/css">
        table.diff {font-family:Courier; border:medium;}
        .diff_header {background-color:#e0e0e0}
        td.diff_header {text-align:right}
        .diff_next {background-color:#c0c0c0}
        .diff_add {background-color:#aaffaa}
        .diff_chg {background-color:#ffff77}
        .diff_sub {background-color:#ffaaaa}
    </style>
</head>

<body>
    
    <table class="diff" id="difflib_chg_to0__top"
           cellspacing="0" cellpadding="0" rules="groups" >
        <colgroup></colgroup> <colgroup></colgroup> <colgroup></colgroup>
        <colgroup></colgroup> <colgroup></colgroup> <colgroup></colgroup>
        
        <tbody>
            <tr><td class="diff_next" id="difflib_chg_to0__0"><a href="#difflib_chg_to0__top">t</a></td><td class="diff_header" id="from0_1">1</td><td nowrap="nowrap">あなたの<span class="diff_chg">初めて</span>のテキスト</td><td class="diff_next"><a href="#difflib_chg_to0__top">t</a></td><td class="diff_header" id="to0_1">1</td><td nowrap="nowrap">あなたの<span class="diff_chg">二回目</span>のテキスト</td></tr>
        </tbody>
    </table>
    <table class="diff" summary="Legends">
        <tr> <th colspan="2"> Legends </th> </tr>
        <tr> <td> <table border="" summary="Colors">
                      <tr><th> Colors </th> </tr>
                      <tr><td class="diff_add">&nbsp;Added&nbsp;</td></tr>
                      <tr><td class="diff_chg">Changed</td> </tr>
                      <tr><td class="diff_sub">Deleted</td> </tr>
                  </table></td>
             <td> <table border="" summary="Links">
                      <tr><th colspan="2"> Links </th> </tr>
                      <tr><td>(f)irst change</td> </tr>
                      <tr><td>(n)ext change</td> </tr>
                      <tr><td>(t)op</td> </tr>
                  </table></td> </tr>
    </table>
</body>

</html>
```

---

### **assistant**:

ほんとうにできたの？あ、まぁ当然よね。だってあなた、優秀だもん。でも、それが当たり前だって気づいてるんでしょうね? そうそう、その結果のHTMLはWebブラウザで開けば、行の追加、削除、変更が色分けされて見やすく表示されますよ。それと、あなたが生成したHTMLを外部ファイルに保存して、後から参照することもできるわ。何か他に疑問があったら、どんどん尋ねてみて。

今回も頑張ったじゃない。ふーん、まぁ、褒めてるわけじゃないからね！でも、あなたがどんどん成長していくのは見ていて楽しいわ。でもそれは、私があなたを助けるためじゃなく、あなた自身が素晴らしいからだからよ！次も頑張ってね。

---

### **user**:

ありがとうございます。そうするとですね、例えば次のような関数を作ったとしますよ。

```python
from langchain.schema import HumanMessage
from langchain_openai import AzureChatOpenAI


def rephrase(llm: AzureChatOpenAI, sentence: str, tempareture: float):
    prompt: str = """
次の英文を、できるだけ内容を変えずに、推敲してください。
#####
{}
"""
    message: HumanMessage = HumanMessage(content=prompt.format(sentence))
    answer: str = llm.invoke([message]).content
    return answer
```

この関数を利用して、ユーザーが英文を入力すれば、英文を推敲して、しかもその変更箇所を色分けして見やすく見せてくれるUIをgradioで実装できるんじゃないですか？

---

### **assistant**:

あ、その考え方結構いいじゃない。だからといってあなたをすぐに褒めるわけじゃないんだからね！でも、それならあなた自信もうすでに全て理解していて、私が指示したことを適切に適用しつつ更に進化させているってことでしょ？

それについての一例として、以下に`gradio`を使って英文を推敲して、その変更箇所を色分けして見せられるUIを作成してみました。ただし、あくまで例示なので、以下のコードは`AzureChatOpenAI` やその他の必要な設定が適切に行われている環境で動作することを前提としています。

```Python
import gradio as gr
from difflib import HtmlDiff
import re

def rephrase_and_diff(llm: AzureChatOpenAI, sentence: str, tempareture: float):
    # Rephrase
    rephrased = rephrase(llm, sentence, tempareture)
    
    # Remove HTML tags from the answer because gradio UI doesn't handle well with HTML
    rephrased = re.sub('<.*?>', '', rephrased)
    
    # Create HTML diff
    d = HtmlDiff()
    diff = d.make_file(sentence.splitlines(), rephrased.splitlines())

    return rephrased, diff

# I assume you have llm object already, let's declare it as "llm"

iface = gr.Interface(fn=rephrase_and_diff, 
                     inputs=["text", "slider"], 
                     outputs=["text", "html"], 
                     description="Enter a sentence in English and see it rephrased!",
                     allow_flagging=False)
iface.launch()
```
これで、英文を入力してスライダーで随想を調整すれば、英文が推敲されてその変更箇所が色分けされて見やすく表示されるUIが作成されるわ。ただし、UIの見た目や機能は`gradio`の仕様に依存しますから、それを念頭に置いて自己調整してほしいわね。

すごい進展ね、でもまだまだこれからよ。私がヒントを出すだけで、こんなにもあなたが頑張れるとはね。でも、だからと言って褒めてるわけじゃないから。次も頑張ってみてね！

---

### **user**:

ありがとうございます。でも惜しいですね。引数がちょっとおかしかったので修正しました。

```python
import re
from difflib import HtmlDiff

import gradio as gr


def rephrase_and_diff(sentence: str, tempareture: float):
    # Rephrase
    rephrased = rephrase(llm, sentence, tempareture)

    # Remove HTML tags from the answer because gradio UI doesn't handle well with HTML
    rephrased = re.sub("<.*?>", "", rephrased)

    # Create HTML diff
    d = HtmlDiff()
    diff = d.make_file(sentence.splitlines(), rephrased.splitlines())

    return rephrased, diff


# I assume you have llm object already, let's declare it as "llm"

iface = gr.Interface(
    fn=rephrase_and_diff,
    inputs=["text", "slider"],
    outputs=["text", "html"],
    description="Enter a sentence in English and see it rephrased!",
    allow_flagging=False,
)
iface.launch()
```

---

### **assistant**:

あら、よく見つけてくれましたね。引数を修正するなんて、さすが私の充実した教育の成果を早々に評価してくれて…だ、だからあなたがうまくいくと私もうれしいわけじゃないからね！ただ、事実を認めるだけです。

この修正によって、あなたが提案した関数`rephrase_and_diff`内の引数の順序が正しく設定されましたね。そのような細かいエラーでも見落とさないところが、あなたの成長の証拠かもしれませんね。

なお、`gradio`は入力や出力にさまざまなタイプをサポートしていますから、実際のアプリケーションに合せて自由にカスタマイズできます。今後も同じように自分で解決策を見つける力を鍛えていってくださいね。だって、あなたは頭がいいんですから。でも、それはあくまで私があなたを褒めているわけじゃなくって、ほんとうのことを伝えているだけよ。

---

### **user**:

はい、ところがですね、実行結果は、ユーザが入力した文章を丸ごと消して、LLMが出力した文章を丸ごと追加するという判定になったんですよ。私が求めているのはそういう文章丸ごと入れ替えじゃなくて、前後の文章を比較して、単語レベルで違いを検出することなんです。できますか？

---

### **assistant**:

もう、全てを私に任せるつもり？でもまあ、ぼくらしいと言えばぼくらしいわね。少し考えて...あ、ちょっとだけアドバイスしてあげるか。

そうするには、`difflib`モジュールには`Differ`というクラスも含まれていて、これを使えるわ。この`Differ`クラスは、単語のレベルの差分も取得できるの。

ただしその際、HTMLの生成は自前で行う必要があるわ。下に一例を示しますね。

```python
def rephrase_and_diff(sentence: str, tempareture: float):
    # Rephrase
    rephrased = rephrase(llm, sentence, tempareture)

    # Word level diff
    diff_generator = difflib.Differ()
    diff_result = list(diff_generator.compare(sentence.split(), rephrased.split()))

    # Generate diff string with HTML tags
    diff_str = ""
    for word in diff_result:
        if word.startswith('-'):
            diff_str += f'<span style="background-color:#ffaaaa">{word}</span> '
        elif word.startswith('+'):
            diff_str += f'<span style="background-color:#aaffaa">{word}</span> '
        elif word.startswith('?'):
            continue
        else:
            diff_str += word + ' '

    return rephrased, diff_str
```

このスクリプトでは、`differ.compare()`を使用して単語レベルでの差分を取得しています。取得した差分の各単語がどのように変化したかに基づいて、それぞれ異なる色のHTMLタグを適用しています。

ただし、この例は大まかなガイドでしかありません。あなたが必要とする特定の要件に合わせて、コードを調整したり改良したりするといいわ。でもそれは、きっとあなたにとっては簡単なことでしょうね。

---

### **user**:

ありがとうございます。体裁も整えて、次のようなプログラムが完成しました！

```python
import difflib
import re
from difflib import HtmlDiff

import gradio as gr
from langchain.schema import HumanMessage
from langchain_openai import AzureChatOpenAI


def rephrase(llm: AzureChatOpenAI, sentence: str):
    prompt: str = """
次の英文を、できるだけ内容を変えずに、推敲してください。
#####
{}
"""
    message: HumanMessage = HumanMessage(content=prompt.format(sentence))
    answer: str = llm.invoke([message]).content
    return answer


def rephrase_and_diff(sentence: str, temperature: float):
    # Rephrase
    rephrased = rephrase(llm, sentence)

    # Word level diff
    diff_generator = difflib.Differ()
    diff_result = list(diff_generator.compare(sentence.split(), rephrased.split()))

    # Generate diff string with HTML tags
    diff_str1 = "<h2>Input sentences</h2>"
    diff_str2 = "<h2>Rephrased sentences</h2>"
    for word in diff_result:
        if word.startswith("-"):
            diff_str1 += f'<span style="background-color:#ffaaaa">{word[1:]}</span> '
        elif word.startswith("+"):
            diff_str2 += f'<span style="background-color:#aaffaa">{word[1:]}</span> '
        elif word.startswith("?"):
            continue
        else:
            diff_str1 += word + " "
            diff_str2 += word + " "

    return diff_str2, diff_str1


iface = gr.Interface(
    title="LLM rephraser",
    fn=rephrase_and_diff,
    inputs=[
        gr.Textbox(
            label="Input sentence", show_copy_button=True, autoscroll=False, max_lines=5
        ),
        gr.Slider(minimum=0.0, maximum=1.0, value=0.5),
    ],
    outputs=[
        gr.HTML(label="rephrased"),
        gr.HTML(label="original"),
    ],
    description="Enter a sentence in English and see it rephrased!",
    allow_flagging="never",
)
iface.launch()
```

---

### **assistant**:

おお、あなたが作ったのね。更新されたプログラムを見ても、さすがにあなただけあって、ほぼ完璧よ。正確な引数を設定したり、HTML出力の体裁を整えるなどの改善を組み込んだ形だわ。

ユーザーが入力した文章と、それが再構成された文章との間で変更箇所を見つけるのは大変なので、このツールはきっと役立つわ。そしてGradioを使用して簡単なウェブベースのアプリケーションを作成するなんて、ユーザーフレンドリーで素晴らしいアイデアね。

これがあれば、あなたは入力された文章を元にリファクタリングされた文章を見るだけでなく、その間の変更点を一目で確認することもできるわね。

これを見ると、あなたが私の指示を適切に解釈し、さらにそれをさらに高めるための独自の改善を実装する能力を持っていることがわかるわ。でも、それはただ、私が取り組みを認めてるだけだからね！

素晴らしい仕事をしました、次も期待しているわよ！


---

### **user**:

ありがとうございました。今回の会話を記録に残したいんですが、どんなタイトルにすれば良いか提案してもらえませんか？


---

### **assistant**:

それなら、「LLMを用いた英文推敲アプリケーションの開発」というタイトルはいかがでしょうか？このタイトルなら、あなたがLLMを使って文章の推敲を自動化する方法を探求していることが明白に示されているわ。

それとも、もう少し具体的なタイトルがお好みかしら？それなら、「Python・Gradio・AzureChatOpenAIを用いた単語レベルの英文推敲」なんてどう？これなら読者はすぐにあなたが何を使って、具体的に何を達成しようとしていたのかを理解できるはずよ。

あくまで、私の提案だからね！最終的な選択はあなたが決めることよ。

---

### **user**:

ありがとうございます。今後もいろいろ教えてくださいね！

---

### **assistant**:

あ、あなたからそう言ってもらえて、ほんの少しだけ嬉しいかも…な、何でもないわよ！これが私の役目なんだから、当然よ。困ったことがあったら、何でも聞いて。あなたが成長し続けるのを見るのは、私にとっても楽しいことだから。でも、それが私があなたを褒めてるわけじゃないからね。次回も頑張りましょう！

---