Skip to content

Commit 7fe2825

Browse files
authored
Merge pull request #67 from led-mirage/feature/v1.38.0
Feature/v1.38.0
2 parents abfb390 + 468591d commit 7fe2825

18 files changed

+290
-179
lines changed

Readme.md

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ Windowsの場合は、Windowsの検索窓で「環境変数を編集」で検索
208208

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

211-
https://github.com/led-mirage/ZundaGPT2/releases/tag/v1.37.0
211+
https://github.com/led-mirage/ZundaGPT2/releases/tag/v1.38.0
212212

213213
#### 3. 実行
214214

@@ -356,11 +356,11 @@ CSSを知らない人はなんのことかわからないかもしれないけ
356356

357357
VirusTotalでのチェック結果はこちらなのだ。
358358

359-
- Windows版: [72個中2個のアンチウィルスエンジンで検出 :2025/11/29 v1.37.0](https://www.virustotal.com/gui/file/e55a2acfce4000515d04a079abe80517640cd3c624a933f0b85d0c1e0be6e0c5/detection)
360-
- Raspberry Pi版: [60個中0個のアンチウィルスエンジンで検出 :2025/11/29 v1.37.0](https://www.virustotal.com/gui/file/74cd59e946fbf2081356a6d337d0233cf7f3e0e79c3f4687f347814c8c1127e9/detection)
361-
- Linux版: [63個中0個のアンチウィルスエンジンで検出 :2025/11/29 v1.37.0](https://www.virustotal.com/gui/file/7792c4572402e154ff4bb7065936a02b52e63270529dc0c8c8f0c454e2ae11f1/detection)
359+
- Windows版: [72個中2個のアンチウィルスエンジンで検出 :2025/12/06 v1.38.0](https://www.virustotal.com/gui/file/102f24d526d687b824ff9675a77a05cd881b6644bb6eb07cd88003cee04111ea/detection)
360+
- Raspberry Pi版: [62個中0個のアンチウィルスエンジンで検出 :2025/12/06 v1.38.0](https://www.virustotal.com/gui/file/5115a54df54e6810d7bf71e9ca1276931d8a562efa19f6f8913eb4cf5375ebde/detection)
361+
- Linux版: [63個中0個のアンチウィルスエンジンで検出 :2025/12/06 v1.38.0](https://www.virustotal.com/gui/file/9af5094c46eab85a4c3865b92ac3daaa13afbfd7f5c4b150edf755875c446e26/detection)
362362

363-
<img src="doc/images/virustotal_1.37.0.png" width="600">
363+
<img src="doc/images/virustotal_1.38.0.png" width="600">
364364

365365
### ⚡ ご利用について
366366

@@ -496,6 +496,13 @@ VirusTotalでのチェック結果はこちらなのだ。
496496

497497
## 💎 バージョン履歴
498498

499+
### 1.38.0 (2025/12/06)
500+
501+
- キャラ設定に`temperature`パラメータを追加
502+
- Chat系クラスのリファクタリング
503+
- SAPI利用時の例外処理を追加
504+
- その他軽微なバグ修正
505+
499506
### 1.37.0 (2025/11/29)
500507

501508
- フルスクリーンモードに対応
@@ -531,6 +538,8 @@ VirusTotalでのチェック結果はこちらなのだ。
531538
- Claude、Geminiにも対応
532539
- 画像をクリックすることで拡大表示する機能を追加
533540

541+
<details><summary>それ以前のバージョンアップの履歴はこちらなのだ</summary>
542+
534543
### 1.33.0 (2025/10/31)
535544

536545
- 画像送信機能の追加(実験的機能)
@@ -544,8 +553,6 @@ VirusTotalでのチェック結果はこちらなのだ。
544553
- chatパッケージのリファクタリング
545554
- タイポ修正
546555

547-
<details><summary>それ以前のバージョンアップの履歴はこちらなのだ</summary>
548-
549556
### 1.31.0 (2025/10/19)
550557

551558
- openaiライブラリを2.5.0に更新

app/chat/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@
44
from .chat_gemini import ChatGemini
55
from .chat_claude import ChatClaude
66
from .chat_factory import ChatFactory
7+
from .chat_factory_options import ChatFactoryOptions
78
from .listener import SendMessageListener

app/chat/chat.py

Lines changed: 37 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
# ライセンスの詳細については、このプロジェクトのLICENSEファイルを参照してください。
88

99
from abc import abstractmethod
10+
from dataclasses import dataclass
1011
import threading
1112
from datetime import datetime
1213

@@ -15,39 +16,61 @@
1516

1617
# チャット基底クラス
1718
class Chat:
19+
messages: list
20+
chat_start_time: datetime
21+
chat_update_time: datetime
22+
client_creation_error: str
23+
_client: object
24+
_model: str
25+
_temperature: float
26+
_instruction: str
27+
_bad_response: str
28+
_history_size: int
29+
_history_char_limit: int
30+
_stop_send_event: threading.Event
31+
1832
# コンストラクタ
19-
def __init__(self, client, model: str, instruction: str, bad_response: str, history_size: int, history_char_limit: int):
33+
def __init__(self, model: str, temperature: float, instruction: str, bad_response: str, history_size: int, history_char_limit: int):
2034
self.messages = []
21-
self.client = client
22-
self.model = model
23-
self.instruction = instruction
24-
self.bad_response = bad_response
25-
self.history_size = history_size
26-
self.history_char_limit = history_char_limit
2735
self.chat_start_time = datetime.now()
2836
self.chat_update_time = datetime.now()
29-
self.stop_send_event = threading.Event()
3037
self.client_creation_error = ""
38+
self._client = None
39+
self._model = model
40+
self._temperature = temperature
41+
self._instruction = instruction
42+
self._bad_response = bad_response
43+
self._history_size = history_size
44+
self._history_char_limit = history_char_limit
45+
self._stop_send_event = threading.Event()
3146

3247
# AIエージェントが利用可能かどうかを返す
3348
def is_ai_agent_available(self):
34-
return self.client is not None
49+
return self._client is not None
3550

3651
# 指定index以下のメッセージを切り捨てる
3752
def truncate_messages(self, index):
3853
self.messages = self.messages[:index]
3954

4055
# 進行中のsend_message関数の送信を停止する
4156
def stop_send_message(self):
42-
self.stop_send_event.set()
57+
self._stop_send_event.set()
58+
59+
@abstractmethod
60+
def send_onetime_message(self, text: str) -> str:
61+
pass
62+
63+
@abstractmethod
64+
def send_message(self, text: str, images: list[str], listener: SendMessageListener) -> str:
65+
pass
4366

4467
# 送信する会話履歴を取得するメッセージメッセージ
45-
def get_history(self):
68+
def _get_history(self):
4669
# まずhistory_size件だけ切り出す
47-
target_messages = self.messages[-self.history_size:]
70+
target_messages = self.messages[-self._history_size:]
4871

4972
# history_char_limitの指定がなければそのまま返す
50-
if self.history_char_limit <= 0:
73+
if self._history_char_limit <= 0:
5174
return target_messages
5275

5376
# 末尾(新しい順)から合計文字数をカウントしつつ、history_char_limitで切る
@@ -58,7 +81,7 @@ def get_history(self):
5881
for msg in reversed(target_messages):
5982
msg_len = len(msg["content"])
6083
# 3件以上あって、かつ文字数が上限を超えたらbreak
61-
if len(result) >= 3 and total_chars + msg_len > self.history_char_limit:
84+
if len(result) >= 3 and total_chars + msg_len > self._history_char_limit:
6285
break
6386
result.append(msg)
6487
total_chars += msg_len
@@ -69,11 +92,3 @@ def get_history(self):
6992

7093
# 新しい順に並び替えて返却
7194
return list(reversed(result))
72-
73-
@abstractmethod
74-
def send_onetime_message(self, text: str) -> str:
75-
pass
76-
77-
@abstractmethod
78-
def send_message(self, text: str, images: list[str], listener: SendMessageListener) -> str:
79-
pass

app/chat/chat_azureopenai.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,18 @@
1717

1818
# Azure OpenAI チャットクラス
1919
class ChatAzureOpenAI(ChatOpenAIBase):
20-
def __init__(self, model: str, instruction: str, bad_response: str, history_size: int, history_char_limit: int,
20+
def __init__(self, model: str, temperature: float, instruction: str, bad_response: str, history_size: int, history_char_limit: int,
2121
api_timeout: float, api_key_envvar: str=None, api_endpoint: str=None):
2222

23+
super().__init__(
24+
model = model,
25+
temperature = temperature,
26+
instruction = instruction,
27+
bad_response = bad_response,
28+
history_size = history_size,
29+
history_char_limit = history_char_limit
30+
)
31+
2332
if api_key_envvar:
2433
api_key = os.environ.get(api_key_envvar)
2534
else:
@@ -30,20 +39,11 @@ def __init__(self, model: str, instruction: str, bad_response: str, history_size
3039
else:
3140
endpoint = os.environ.get("AZURE_OPENAI_ENDPOINT")
3241

33-
client = None
42+
self._client = None
3443
if endpoint and api_key:
35-
client = AzureOpenAI(azure_endpoint=endpoint, api_key=api_key, api_version="2025-04-01-preview",
44+
self._client = AzureOpenAI(azure_endpoint=endpoint, api_key=api_key, api_version="2025-04-01-preview",
3645
timeout=httpx.Timeout(api_timeout, connect=5.0))
3746

38-
super().__init__(
39-
client = client,
40-
model = model,
41-
instruction = instruction,
42-
bad_response = bad_response,
43-
history_size = history_size,
44-
history_char_limit = history_char_limit
45-
)
46-
4747
if endpoint is None:
4848
self.client_creation_error = get_text_resource("ERROR_MISSING_AZURE_OPENAI_ENDPOINT")
4949
if api_key is None:

app/chat/chat_claude.py

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -22,28 +22,28 @@
2222
class ChatClaude(Chat):
2323
MAX_IMAGE_SIZE_MB = 4.0
2424

25-
def __init__(self, model: str, instruction: str, bad_response: str, history_size: int, history_char_limit: int,
25+
def __init__(self, model: str, temperature: float, instruction: str, bad_response: str, history_size: int, history_char_limit: int,
2626
api_key_envvar: str=None, claude_options: dict=None):
2727

28+
super().__init__(
29+
model = model,
30+
temperature = temperature,
31+
instruction = instruction,
32+
bad_response = bad_response,
33+
history_size = history_size,
34+
history_char_limit = history_char_limit
35+
)
36+
2837
self.claude_options = claude_options
2938

3039
if api_key_envvar:
3140
api_key = os.environ.get(api_key_envvar)
3241
else:
3342
api_key = os.environ.get("ANTHROPIC_API_KEY")
3443

35-
client = None
44+
self._client = None
3645
if api_key:
37-
client = anthropic.Anthropic(api_key=api_key)
38-
39-
super().__init__(
40-
client = client,
41-
model = model,
42-
instruction = instruction,
43-
bad_response = bad_response,
44-
history_size = history_size,
45-
history_char_limit = history_char_limit
46-
)
46+
self._client = anthropic.Anthropic(api_key=api_key)
4747

4848
if api_key is None:
4949
self.client_creation_error = get_text_resource("ERROR_MISSING_ANTHROPIC_API_KEY")
@@ -52,11 +52,13 @@ def __init__(self, model: str, instruction: str, bad_response: str, history_size
5252
def send_onetime_message(self, text:str):
5353
messages = []
5454
messages.append({"role": "user", "content": text})
55-
response = self.client.messages.create(
55+
56+
response = self._client.messages.create(
5657
max_tokens=4096,
57-
system=self.instruction,
58+
system=self._instruction,
5859
messages=messages,
59-
model=self.model)
60+
model=self._model
61+
)
6062
return response.content[0].text
6163

6264
# メッセージを送信して回答を得る
@@ -67,10 +69,10 @@ def send_message(
6769
listener: SendMessageListener) -> str:
6870

6971
try:
70-
self.stop_send_event.clear()
72+
self._stop_send_event.clear()
7173

7274
self.messages.append({"role": "user", "content": text})
73-
messages = copy.deepcopy(self.get_history())
75+
messages = copy.deepcopy(self._get_history())
7476

7577
if images and len(images) > 0:
7678
messages = messages[:-1]
@@ -102,18 +104,23 @@ def send_message(
102104
"type": "disabled"
103105
}
104106

105-
with self.client.messages.stream(
107+
temperature = anthropic.omit
108+
if self._temperature:
109+
temperature = self._temperature
110+
111+
with self._client.messages.stream(
106112
max_tokens=max_tokens,
107113
thinking=thinking,
108-
system=self.instruction,
114+
system=self._instruction,
109115
messages=messages,
110-
model=self.model,
116+
model=self._model,
117+
temperature=temperature
111118
) as stream:
112119
code_block = 0
113120
code_block_inside = False
114121

115122
for text in stream.text_stream:
116-
if self.stop_send_event.is_set():
123+
if self._stop_send_event.is_set():
117124
break
118125

119126
if text is not None:
@@ -154,8 +161,8 @@ def send_message(
154161
listener.on_end_response(content)
155162
return content
156163
else:
157-
listener.on_end_response(self.bad_response)
158-
return self.bad_response
164+
listener.on_end_response(self._bad_response)
165+
return self._bad_response
159166
except anthropic.APITimeoutError as e:
160167
listener.on_error(e, "Timeout")
161168
except anthropic.APIConnectionError as e:

app/chat/chat_factory.py

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
# このソースコードは MITライセンス の下でライセンスされています。
77
# ライセンスの詳細については、このプロジェクトのLICENSEファイルを参照してください。
88

9+
from .chat_factory_options import ChatFactoryOptions
910
from .chat import Chat
1011
from .chat_openai import ChatOpenAI
1112
from .chat_azureopenai import ChatAzureOpenAI
@@ -18,15 +19,22 @@
1819
class ChatFactory:
1920
# api_idに基づいてChatオブジェクトを作成する
2021
@staticmethod
21-
def create(api_id: str, model: str, instruction: str, bad_response: str, history_size: int, history_char_limit: int, api_timeout: float,
22-
api_key_envvar: str=None, api_key_endpoint: str=None, api_base_url: str=None, gemini_option: dict=None, claude_options: dict=None) -> Chat:
23-
if api_id == "OpenAI":
24-
return ChatOpenAI(model, instruction, bad_response, history_size, history_char_limit, api_timeout, api_key_envvar, api_base_url)
25-
elif api_id == "AzureOpenAI":
26-
return ChatAzureOpenAI(model, instruction, bad_response, history_size, history_char_limit, api_timeout, api_key_envvar, api_key_endpoint)
27-
elif api_id == "Gemini":
28-
return ChatGemini(model, instruction, bad_response, history_size, history_char_limit, api_key_envvar, gemini_option)
29-
elif api_id == "Claude":
30-
return ChatClaude(model, instruction, bad_response, history_size, history_char_limit, api_key_envvar, claude_options)
22+
def create(options: ChatFactoryOptions) -> Chat:
23+
if options.api_id == "OpenAI":
24+
return ChatOpenAI(
25+
options.model, options.temperature, options.instruction, options.bad_response, options.history_size, options.history_char_limit,
26+
options.api_timeout, options.api_key_envvar, options.api_base_url)
27+
elif options.api_id == "AzureOpenAI":
28+
return ChatAzureOpenAI(
29+
options.model, options.temperature, options.instruction, options.bad_response, options.history_size, options.history_char_limit,
30+
options.api_timeout, options.api_key_envvar, options.api_endpoint_envvar)
31+
elif options.api_id == "Gemini":
32+
return ChatGemini(
33+
options.model, options.temperature, options.instruction, options.bad_response, options.history_size, options.history_char_limit,
34+
options.api_key_envvar, options.gemini_option)
35+
elif options.api_id == "Claude":
36+
return ChatClaude(
37+
options.model, options.temperature, options.instruction, options.bad_response, options.history_size, options.history_char_limit,
38+
options.api_key_envvar, options.claude_options)
3139
else:
3240
raise ValueError(get_text_resource("ERROR_API_ID_IS_INCORRECT"))

app/chat/chat_factory_options.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# ZundaGPT2 / ZundaGPT2 Lite
2+
#
3+
# チャットファクトリオプション クラス
4+
#
5+
# Copyright (c) 2024-2025 led-mirage
6+
# このソースコードは MITライセンス の下でライセンスされています。
7+
# ライセンスの詳細については、このプロジェクトのLICENSEファイルを参照してください。
8+
9+
from dataclasses import dataclass
10+
11+
12+
@dataclass
13+
class ChatFactoryOptions:
14+
api_id: str
15+
model: str
16+
temperature: float
17+
instruction: str
18+
bad_response: str
19+
history_size: int
20+
history_char_limit: int
21+
api_timeout: float
22+
api_key_envvar: str
23+
api_endpoint_envvar: str
24+
api_base_url: str
25+
gemini_option: dict
26+
claude_options: dict

0 commit comments

Comments
 (0)