Skip to content

Commit

Permalink
Merge pull request #7 from led-mirage/feature/support-for-claude
Browse files Browse the repository at this point in the history
Feature/support for claude
  • Loading branch information
led-mirage authored Dec 29, 2024
2 parents c6e34ee + 47127e3 commit ce689f7
Show file tree
Hide file tree
Showing 7 changed files with 202 additions and 19 deletions.
60 changes: 46 additions & 14 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,18 @@ Copyright (c) 2024 led-mirage

ZundaGPT2(https://github.com/led-mirage/ZundaGPT2) のライト版なのだ。ZundaGPT2から音声読み上げ機能を省いたバージョンなのだよ。

OpenAI、AzureOpenAI Service、Google Geminiを使って、AIとチャットできるチャットクライアントソフトウェアなのだ。
簡単に言うと、AIとチャットできるチャットクライアントソフトウェアなのだ。

## 最新情報 バージョン 1.3.0
使用できるAIサービスは以下の4つなのだよ。

起動時に表示されるスプラッシュ画面を追加したのだ!
- OpenAI
- AzureOpenAI
- Google Gemini
- Anthropic Claude

## 最新情報 バージョン 1.4.0

Anthropic社のClaudeシリーズに対応したのだ✨

## スクリーンショット

Expand All @@ -25,28 +32,39 @@ OpenAI、AzureOpenAI Service、Google Geminiを使って、AIとチャットで

## 必要なもの

このアプリを動作させるには以下のものが必要になるのだ。ここでは軽く触れておくだけにするけど、詳しいことは[こっち](Readme_detail.md)を見てほしいのだ。
このアプリ自体は無料だけど、このアプリを動作させるには以下のいずれかのAPIキーが必要になるのだ。

ここでは軽く触れておくだけにするけど、詳しいことは[こっち](Readme_detail.md)を見てほしいのだ。

### ✅ OpenAIアカウントとAPIキー

このアプリ自体は無料だけど[OpenAI](https://platform.openai.com/)のアカウントとAPIの利用登録(課金およびAPIキーの作成)が必要なのだ。
OpenAIのAPIを使用する場合は[OpenAI](https://platform.openai.com/)のアカウントとAPIの利用登録(課金およびAPIキーの作成)が必要なのだ。

### ✅ Google Gemini APIのAPIキー

バージョン0.7.0からGoogle Gemini APIにも対応したので、OpenAIの代わりにGoogle Gemini APIを使用することもできるのだ。
バージョン0.7.0からGoogle Gemini APIにも対応したのだ。

2024年5月19日時点でGoogle Gemini APIには無料プランが設定されているので、OpenAIのAPIよりも気軽に利用することができるのだ。Google Gemini APIを使用したい場合は、[専用の資料](Readme_gemini.md)を用意したので、それを参照して欲しいのだ。

### ✅ Anthropic APIのAPIキー

バージョン1.4.0からAnthropic API(Claudeシリーズ)にも対応したのだ。

APIを利用するには[Anthropic Console](https://console.anthropic.com/)のアカウントとAPIの利用登録(課金およびAPIキーの作成)が必要なのだ。

現時点でGoogle Gemini APIには無料プランが設定されているので、OpenAIのAPIよりも気軽に利用することができるのだ。Google Gemini APIを使用したい場合は、[専用の資料](Readme_gemini.md)を用意したので、それを参照して欲しいのだ
2024年12月29日時点の最新のモデルは`Claude 3.5 Sonnet`なのだ

## 実行方法

### 🛩️ 準備:OSの環境変数を追加

OpenAIのAPIキー、もしくはGoogle Gemini APIのAPIキーをOSの環境変数に登録しておく必要があるのだ。
OpenAIのAPIキー、またはGoogle Gemini API、またはAnthropic APIのAPIキーをOSの環境変数に登録しておく必要があるのだ。

| AI | 変数名 ||
|------|------|------|
| OpenAI | OPENAI_API_KEY | OpenAIで取得したAPIキー |
| Google Gemini | GEMINI_API_KEY | Googleで取得したAPIキー |
| Anthropic Claude | ANTHROPIC_API_KEY | Anthropicで取得したAPIキー |

Windowsの場合は、Windowsの検索窓で「環境変数を編集」で検索すると設定画面が立ち上がるので、そこでユーザー環境変数を追加すればいいのだ。

Expand All @@ -62,11 +80,11 @@ Windowsの場合は、Windowsの検索窓で「環境変数を編集」で検索

以下のリンクから ZundaGPT2Lite.ZIP をダウンロードして、作成したフォルダに展開するのだ。

https://github.com/led-mirage/ZundaGPT2Lite/releases/tag/v1.3.0
https://github.com/led-mirage/ZundaGPT2Lite/releases/tag/v1.4.0

#### 3. 実行

ZundaGPT2Lite.exeをダブルクリックすればアプリが起動するのだ
`ZundaGPT2Lite.exe`をダブルクリックすればアプリが起動するのだ

※起動時にスプラッシュ画面を表示したくない人は、`ZundaGPT2Lite.ns.exe`を替わりに使ってほしいのだ。

Expand Down Expand Up @@ -144,9 +162,13 @@ start pythonw app\main.py

[この資料](Readme_gemini.md)にも書いたけど、現時点でGoogle Gemini APIには無料枠があるのだ。だから、基本的には無料枠を使ってアプリを利用すればいいと思うけど、もっとハードに使いたい場合は有料プランを考えてみるのもいいのだ。ただ、有料プランにした場合は、先に書いたOpenAIと同じように使い過ぎには注意して欲しいのだ。

### ⚡ Anthropic APIの利用料金について

Anthropic APIを利用するのにも別途料金(従量制)が発生するのだ。2024年12月29日時点で確認したところ、無料枠というものはなさそうなのだ。クレジットカードで好きな金額を課金するとAPIを利用できるようになるのだ。ただ、他のAPIと同じように使い過ぎには注意して欲しいのだ。

### ⚡ APIキーの重要性について

OpenAIやGoogle GeminiのAPIキーはあなただけのものなので、人に教えたらダメなのだ。流出すると悪い人に勝手に使われてしまう可能性があるのだ。もし流出してしまったら、OpenAIやGoogleのサイトで現在使っているAPIキーを削除して、別のAPIキーを作ればいいのだ。
OpenAIやGoogle Gemini、AnthropicのAPIキーはあなただけのものなので、人に教えたらダメなのだ。流出すると悪い人に勝手に使われてしまう可能性があるのだ。もし流出してしまったら、OpenAIやGoogle、Anthropicのサイトで現在使っているAPIキーを削除して、別のAPIキーを作ればいいのだ。

ただOpenAIでは、APIキーをひとつしか持っていない場合、新しいAPIキーを作ってからじゃないと古いAPIキーを削除できないようなのだ。これはOpenAIの仕様のようなんだけど、ボク的にはちょっといただけない仕様だと思っているのだ。将来的に改善することを願っているけれど、最悪支払い情報(クレジットカード情報)を削除してしまえばいいような気もするのだ。

Expand All @@ -158,10 +180,10 @@ OpenAIやGoogle GeminiのAPIキーはあなただけのものなので、人に

これが嫌な人は(ボクも嫌だけど)、Python本体をインストールしてPythonから普通に実行して欲しいのだ。実行ファイルのほうが手軽だし、そのほうがPythonに詳しくない人にとっては簡単なんだけど、誤認問題がついて回ることは覚えておいて欲しいのだ。

VirusTotalでの[チェック結果](https://www.virustotal.com/gui/file/77f31ba267eb56614cd11f1f261adbea9931cc93c6165bf71b87b7a951ac7764)は以下の通りなのだ。
(72個中4個のアンチウィルスエンジンで検出 :2024/12/07 v1.3.0)。
VirusTotalでの[チェック結果](https://www.virustotal.com/gui/file/be1aecbe11a35ed23172f389d096ae91ed35675e814e238bf41bd7061ad4b994)は以下の通りなのだ。
(72個中4個のアンチウィルスエンジンで検出 :2024/12/29 v1.4.0)。

<img src="doc/virustotal_1.2.2.png" width="600">
<img src="doc/virustotal_1.4.0.png" width="600">

### ⚡ 免責事項

Expand All @@ -184,6 +206,11 @@ VirusTotalでの[チェック結果](https://www.virustotal.com/gui/file/77f31ba
ホームページ: https://github.com/google-gemini/generative-ai-python
ライセンス:Apache License 2.0

### 🔖 anthropic 0.42.0

ホームページ: https://github.com/anthropics/anthropic-sdk-python
ライセンス:MIT license

### 🔖 requests 2.32.3

ホームページ: https://requests.readthedocs.io/en/latest/
Expand Down Expand Up @@ -300,3 +327,8 @@ VirusTotalでの[チェック結果](https://www.virustotal.com/gui/file/77f31ba
### 1.3.0 (2024/12/08)

- 起動時に表示されるスプラッシュ画面を追加

### 1.4.0 (2024/12/29)

- Anthropic社のAI、Claudeシリーズに対応
- チャット内容の表示を改善
28 changes: 27 additions & 1 deletion Readme_detail.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ OpenAI … https://platform.openai.com/

現時点でGoogle Gemini APIには無料プランが設定されているので、OpenAIのAPIよりも気軽に利用することができるのだ。Google Gemini APIを使用したい場合は、[専用の資料](Readme_gemini.md)を用意したので、それを参照して欲しいのだ。


### ✅ Anthropic APIのAPIキー

バージョン1.4.0からAnthropic API(Claudeシリーズ)にも対応したのだ。APIを利用するには[Anthropic Console](https://console.anthropic.com/)のアカウントとAPIの利用登録(課金およびAPIキーの作成)が必要なのだ。

## 設定ファイル

設定ファイルは2つあるのだ。ひとつはシステムの設定が書かれているapp_config.json。もうひとつはチャットするキャラクターの情報が書かれているsettings.jsonなのだ。
Expand Down Expand Up @@ -92,7 +97,7 @@ settings.jsonはsettingsフォルダの中に格納されているのだ。声

#### ✨ chat/api(既定値 OpenAI)

使用するAPIの設定なのだ。設定できる値は`OpenAI``AzureOpenAI``Gemini`の3つなのだ
使用するAPIの設定なのだ。設定できる値は`OpenAI``AzureOpenAI``Gemini``Claude`の4つなのだ

使用するAPIによって設定しないといけない環境変数が異なるから注意して欲しいのだ。

Expand All @@ -115,6 +120,12 @@ settings.jsonはsettingsフォルダの中に格納されているのだ。声
|------|------|
| GEMINI_API_KEY | Googleで取得したAPIキー |

**Claude**

| 変数名 ||
|------|------|
| ANTHROPIC_API_KEY | Anthropicで取得したAPIキー |

#### ✨ chat/model(既定値 gpt-3.5-turbo-0125)

使用するAIのモデル名を指定するのだ。使用するAPIによって指定できるモデル名が異なるので注意して欲しいのだ。
Expand All @@ -138,6 +149,17 @@ Geminiでは以下のモデル名を指定できるのだ。詳しくは、[こ
- gemini-1.5-flash-latest
- gemini-1.5-pro-latest

**Claude**

Anthropicの場合、2024年12月29日時点で、以下のモデルを指定できるのだ。

- Claude 3.5 Sonnet 2024-10-22
- Claude 3.5 Sonnet 2024-06-20
- Claude 3.5 Haiku
- Claude 3 Opus
- Claude 3 Sonnet
- Claude 3 Haiku

#### ✨ chat/instraction(既定値 君は優秀なアシスタント…以下略)

AIのキャラづけの設定なのだ。ここで、AIの台詞をずんだもんっぽくするようお願いしているのだ。ここを変更することで、ずんだもん以外のキャラクターっぽい回答を生成することも可能なのだ。
Expand Down Expand Up @@ -168,6 +190,10 @@ chat/apiに`AzureOpenAI`を指定した場合は、チャットの回答を取

chat/apiに`Gemini`を指定した場合は、チャットの回答を取得するために Googleのサーバーと通信を行うのだ。通信方法は、Googleのライブラリを使用しているのだ。

### 🌐 Anthropic API(HTTPS)

chat/apiに`Claude`を指定した場合は、チャットの回答を取得するために Anthropicのサーバーと通信を行うのだ。通信方法は、Anthropicのライブラリを使用しているのだ。

### ➰ pywebview(TCP)

このアプリではGUIをpywebviewで作ってるんだけど、UI(HTML)とバックエンドのPythonプログラムとの連携をとるのにTCPでリスニングしているみたい。詳しいことはわからないのだ。
Expand Down
94 changes: 94 additions & 0 deletions app/chat/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import google.generativeai as genai
import google.api_core.exceptions as google_exceptions
from google.generativeai.types import HarmCategory, HarmBlockThreshold
import anthropic

# チャット基底クラス
class Chat:
Expand Down Expand Up @@ -273,6 +274,97 @@ def convert_messages(self, messages):
gemini_messages.append({"role": "model", "parts": [ message["content"] ]})
return gemini_messages

# Anthropic Claude チャットクラス
class ChatClaude(Chat):
def __init__(self, model: str, instruction: str, bad_response: str, history_size: int):
api_key = os.environ.get("ANTHROPIC_API_KEY")
if api_key is None:
raise ValueError("環境変数 GEMINI_API_KEY が設定されていません。")
genai.configure(api_key=api_key)

client = anthropic.Anthropic()
super().__init__(
client = client,
model = model,
instruction = instruction,
bad_response = bad_response,
history_size = history_size
)

# メッセージを送信して回答を得る
def send_message(
self,
text: str,
recieve_chunk: Callable[[str], None],
recieve_sentence: Callable[[str], None],
end_response: Callable[[str], None],
on_error: Callable[[Exception, str], None]) -> str:

try:
self.stop_send_event.clear()

self.messages.append({"role": "user", "content": text})
messages = self.messages[-self.history_size:]

content = ""
sentence = ""

with self.client.messages.stream(
max_tokens=4096,
system=self.instruction,
messages=messages,
model=self.model,
) as stream:
for text in stream.text_stream:
if self.stop_send_event.is_set():
break

if text is not None:
chunk_content = text

content += chunk_content
sentence += chunk_content
recieve_chunk(chunk_content)

if sentence.endswith(("。", "\n", "?", "!")):
recieve_sentence(sentence)
sentence = ""

if sentence != "":
recieve_sentence(sentence)

if content:
self.messages.append({"role": "assistant", "content": content})
self.chat_update_time = datetime.now()
end_response(content)
return content
else:
end_response(self.bad_response)
return self.bad_response
except anthropic.APITimeoutError as e:
on_error(e, "Timeout")
except anthropic.APIConnectionError as e:
on_error(e, "APIConnectionError")
except anthropic.RateLimitError as e:
on_error(e, "RateLimit")
except anthropic.APIStatusError as e:
if e.status_code == 400:
on_error(e, "BadRequest")
elif e.status_code == 401:
on_error(e, "Authentication")
elif e.status_code == 403:
on_error(e, "PermissionDeniedError")
elif e.status_code == 422:
on_error(e, "UnprocessableEntity")
elif e.status_code == 429:
on_error(e, "RateLimit")
elif e.status_code == 500:
on_error(e, "InternalServerError")
else:
on_error(e, "APIConnectionError")
except Exception as e:
on_error(e, "Exception")

# チャットファクトリー
class ChatFactory:
# api_idに基づいてChatオブジェクトを作成する
Expand All @@ -284,5 +376,7 @@ def create(api_id: str, model: str, instruction: str, bad_response: str, history
return ChatAzureOpenAI(model, instruction, bad_response, history_size, api_timeout)
elif api_id == "Gemini":
return ChatGemini(model, instruction, bad_response, history_size, gemini_option)
elif api_id == "Claude":
return ChatClaude(model, instruction, bad_response, history_size)
else:
raise ValueError("API IDが間違っています。")
31 changes: 29 additions & 2 deletions app/html/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@
displayAlign: "left"
}
};
marked.setOptions(
{
breaks: true
}
);
</script>
<script type="text/javascript" id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/[email protected]/es5/tex-mml-chtml.js"></script>
</head>
Expand Down Expand Up @@ -326,7 +331,8 @@
const messageElement = document.createElement("div");
messageElement.classList.add("message-text");
if (role === "assistant") {
let html = escapeTex(messageText);
let html = convertTextToHtmlWithMarkdown(messageText);
html = escapeTex(html);
html = adjustURL(html);
html = marked.parse(html);
html = unescapeTex(html);
Expand All @@ -348,6 +354,26 @@
chatMessagesContainer.appendChild(chatMessageElement);
}

// マークダウンテキスト内のコードブロックを保護しながらHTML変換する関数
function convertTextToHtmlWithMarkdown(text) {
// コードブロックを一時保存
let codeBlocks = [];
text = text.replace(/```[\s\S]*?```/g, match => {
codeBlocks.push(match);
return `__CODE_BLOCK_${codeBlocks.length-1}__`;
});

// バッククォート文字を変換
text = text.replace(/\\`/g, '\\\'');

// コードブロックを戻す
codeBlocks.forEach((block, i) => {
text = text.replace(`__CODE_BLOCK_${i}__`, block);
});

return text;
}

// markedがリンクに連続する文字列全体をリンクに変換してしまう問題に対処
function adjustURL(text) {
// https://またはhttp://では始まらない、www.で始まるドメイン名の前後にバッククォートを入れる
Expand Down Expand Up @@ -571,7 +597,8 @@
const messageTextElements = document.querySelectorAll("#chat-messages .message-text");
const lastMessageTextElement = messageTextElements[messageTextElements.length - 1];
if(lastMessageTextElement) {
let html = escapeTex(content);
let html = convertTextToHtmlWithMarkdown(content);
html = escapeTex(html);
html = adjustURL(html);
html = marked.parse(html);
html = unescapeTex(html);
Expand Down
7 changes: 5 additions & 2 deletions app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@
from chat_log import ChatLog

if getattr(sys, "frozen", False):
import pyi_splash
import pyi_splash # type: ignore

APP_NAME = "ZundaGPT2 Lite"
APP_VERSION = "1.3.0"
APP_VERSION = "1.4.0"
COPYRIGHT = "Copyright 2024 led-mirage"

# アプリケーションクラス
Expand All @@ -43,6 +43,7 @@ def start(self):
window_title = f"{APP_NAME} ver {APP_VERSION}"
self._window = webview.create_window(window_title, url="html/index.html", width=width, height=height, js_api=self, text_select=True)
webview.start()
#webview.start(debug=True) # 開発者ツールを表示する場合

# ページロードイベントハンドラ(UI)
def page_loaded(self):
Expand Down Expand Up @@ -257,6 +258,8 @@ def on_chat_error(self, e: Exception, cause: str):
message = "APIのエンドポイントが間違っているのだ"
elif cause == "UnsafeContent":
message = "会話の内容が不適切だと判断されたのだ"
elif cause == "RateLimit":
message = "レート制限に達したのだ"
else:
message = f"なんかわからないエラーが発生したのだ({class_name})"
self._window.evaluate_js(f"handleChatException('{message}')")
Expand Down
Binary file added doc/virustotal_1.4.0.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit ce689f7

Please sign in to comment.