xiaogpt 2.50__py3-none-any.whl → 2.61__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- xiaogpt/bot/__init__.py +6 -3
- xiaogpt/bot/doubao_bot.py +76 -0
- xiaogpt/bot/gemini_bot.py +24 -6
- xiaogpt/cli.py +17 -22
- xiaogpt/config.py +14 -25
- xiaogpt/tts/__init__.py +4 -6
- xiaogpt/tts/base.py +4 -4
- xiaogpt/tts/mi.py +1 -1
- xiaogpt/tts/tetos.py +56 -0
- xiaogpt/xiaogpt.py +31 -19
- {xiaogpt-2.50.dist-info → xiaogpt-2.61.dist-info}/METADATA +55 -63
- xiaogpt-2.61.dist-info/RECORD +28 -0
- xiaogpt/tts/azure.py +0 -98
- xiaogpt/tts/edge.py +0 -32
- xiaogpt/tts/openai.py +0 -46
- xiaogpt/tts/volc.py +0 -130
- xiaogpt-2.50.dist-info/RECORD +0 -30
- {xiaogpt-2.50.dist-info → xiaogpt-2.61.dist-info}/WHEEL +0 -0
- {xiaogpt-2.50.dist-info → xiaogpt-2.61.dist-info}/entry_points.txt +0 -0
- {xiaogpt-2.50.dist-info → xiaogpt-2.61.dist-info}/licenses/LICENSE +0 -0
xiaogpt/bot/__init__.py
CHANGED
@@ -2,11 +2,12 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
from xiaogpt.bot.base_bot import BaseBot
|
4
4
|
from xiaogpt.bot.chatgptapi_bot import ChatGPTBot
|
5
|
-
from xiaogpt.bot.
|
6
|
-
from xiaogpt.bot.glm_bot import GLMBot
|
5
|
+
from xiaogpt.bot.doubao_bot import DoubaoBot
|
7
6
|
from xiaogpt.bot.gemini_bot import GeminiBot
|
8
|
-
from xiaogpt.bot.
|
7
|
+
from xiaogpt.bot.glm_bot import GLMBot
|
9
8
|
from xiaogpt.bot.langchain_bot import LangChainBot
|
9
|
+
from xiaogpt.bot.newbing_bot import NewBingBot
|
10
|
+
from xiaogpt.bot.qwen_bot import QwenBot
|
10
11
|
from xiaogpt.config import Config
|
11
12
|
|
12
13
|
BOTS: dict[str, type[BaseBot]] = {
|
@@ -16,6 +17,7 @@ BOTS: dict[str, type[BaseBot]] = {
|
|
16
17
|
"gemini": GeminiBot,
|
17
18
|
"qwen": QwenBot,
|
18
19
|
"langchain": LangChainBot,
|
20
|
+
"doubao": DoubaoBot,
|
19
21
|
}
|
20
22
|
|
21
23
|
|
@@ -34,4 +36,5 @@ __all__ = [
|
|
34
36
|
"QwenBot",
|
35
37
|
"get_bot",
|
36
38
|
"LangChainBot",
|
39
|
+
"DoubaoBot",
|
37
40
|
]
|
@@ -0,0 +1,76 @@
|
|
1
|
+
"""ChatGLM bot"""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
import json
|
6
|
+
from typing import Any, AsyncIterator
|
7
|
+
|
8
|
+
import httpx
|
9
|
+
from rich import print
|
10
|
+
|
11
|
+
from xiaogpt.bot.base_bot import BaseBot, ChatHistoryMixin
|
12
|
+
from xiaogpt.config import Config
|
13
|
+
from xiaogpt.utils import split_sentences
|
14
|
+
|
15
|
+
|
16
|
+
class DoubaoBot(ChatHistoryMixin, BaseBot):
|
17
|
+
API_URL = "https://maas-api.ml-platform-cn-beijing.volces.com"
|
18
|
+
name = "豆包"
|
19
|
+
default_options = {"model": "skylark-chat"}
|
20
|
+
|
21
|
+
def __init__(self, access_key: str, secret_key: str) -> None:
|
22
|
+
from tetos.volc import VolcSignAuth
|
23
|
+
|
24
|
+
self.auth = VolcSignAuth(access_key, secret_key, "ml_maas", "cn-beijing")
|
25
|
+
self.history = []
|
26
|
+
|
27
|
+
@classmethod
|
28
|
+
def from_config(cls, config: Config):
|
29
|
+
return cls(access_key=config.volc_access_key, secret_key=config.volc_secret_key)
|
30
|
+
|
31
|
+
def _get_data(self, query: str, **options: Any):
|
32
|
+
options = {**self.default_options, **options}
|
33
|
+
model = options.pop("model")
|
34
|
+
ms = self.get_messages()
|
35
|
+
ms.append({"role": "user", "content": query})
|
36
|
+
return {"model": {"name": model}, "parameters": options, "messages": ms}
|
37
|
+
|
38
|
+
async def ask(self, query, **options):
|
39
|
+
data = self._get_data(query, **options)
|
40
|
+
async with httpx.AsyncClient(base_url=self.API_URL, auth=self.auth) as client:
|
41
|
+
resp = await client.post("/api/v1/chat", json=data)
|
42
|
+
resp.raise_for_status()
|
43
|
+
try:
|
44
|
+
message = resp.json()["choice"]["message"]["content"]
|
45
|
+
except Exception as e:
|
46
|
+
print(str(e))
|
47
|
+
return
|
48
|
+
self.add_message(query, message)
|
49
|
+
print(message)
|
50
|
+
return message
|
51
|
+
|
52
|
+
async def ask_stream(self, query: str, **options: Any):
|
53
|
+
data = self._get_data(query, **options)
|
54
|
+
data["stream"] = True
|
55
|
+
|
56
|
+
async def sse_gen(line_iter: AsyncIterator[str]) -> AsyncIterator[str]:
|
57
|
+
message = ""
|
58
|
+
async for chunk in line_iter:
|
59
|
+
if not chunk.startswith("data:"):
|
60
|
+
continue
|
61
|
+
message = chunk[5:].strip()
|
62
|
+
if message == "[DONE]":
|
63
|
+
break
|
64
|
+
data = json.loads(message)
|
65
|
+
text = data["choice"]["message"]["content"]
|
66
|
+
print(text, end="", flush=True)
|
67
|
+
message += text
|
68
|
+
yield text
|
69
|
+
print()
|
70
|
+
self.add_message(query, message)
|
71
|
+
|
72
|
+
async with httpx.AsyncClient(base_url=self.API_URL, auth=self.auth) as client:
|
73
|
+
async with client.stream("POST", "/api/v1/chat", json=data) as resp:
|
74
|
+
resp.raise_for_status()
|
75
|
+
async for sentence in split_sentences(sse_gen(resp.aiter_lines())):
|
76
|
+
yield sentence
|
xiaogpt/bot/gemini_bot.py
CHANGED
@@ -12,7 +12,7 @@ generation_config = {
|
|
12
12
|
"temperature": 0.7,
|
13
13
|
"top_p": 1,
|
14
14
|
"top_k": 1,
|
15
|
-
"max_output_tokens":
|
15
|
+
"max_output_tokens": 4096,
|
16
16
|
}
|
17
17
|
|
18
18
|
safety_settings = [
|
@@ -32,10 +32,26 @@ safety_settings = [
|
|
32
32
|
class GeminiBot(ChatHistoryMixin, BaseBot):
|
33
33
|
name = "Gemini"
|
34
34
|
|
35
|
-
def __init__(self, gemini_key: str) -> None:
|
35
|
+
def __init__(self, gemini_key: str, gemini_api_domain: str) -> None:
|
36
36
|
import google.generativeai as genai
|
37
37
|
|
38
|
-
|
38
|
+
from google.auth import api_key
|
39
|
+
|
40
|
+
credentials = api_key.Credentials(gemini_key)
|
41
|
+
if len(gemini_api_domain) > 0:
|
42
|
+
print("Use custom gemini_api_domain: " + gemini_api_domain)
|
43
|
+
credentials._universe_domain = gemini_api_domain
|
44
|
+
genai.configure(
|
45
|
+
transport="rest",
|
46
|
+
credentials=credentials,
|
47
|
+
client_options={
|
48
|
+
"api_endpoint": "https://" + gemini_api_domain,
|
49
|
+
"universe_domain": gemini_api_domain,
|
50
|
+
},
|
51
|
+
)
|
52
|
+
else:
|
53
|
+
genai.configure(api_key=gemini_key)
|
54
|
+
|
39
55
|
self.history = []
|
40
56
|
model = genai.GenerativeModel(
|
41
57
|
model_name="gemini-pro",
|
@@ -46,11 +62,13 @@ class GeminiBot(ChatHistoryMixin, BaseBot):
|
|
46
62
|
|
47
63
|
@classmethod
|
48
64
|
def from_config(cls, config):
|
49
|
-
return cls(
|
65
|
+
return cls(
|
66
|
+
gemini_key=config.gemini_key, gemini_api_domain=config.gemini_api_domain
|
67
|
+
)
|
50
68
|
|
51
69
|
async def ask(self, query, **options):
|
52
|
-
self.convo.send_message(query)
|
53
|
-
message =
|
70
|
+
response = self.convo.send_message(query)
|
71
|
+
message = response.text.strip()
|
54
72
|
print(message)
|
55
73
|
if len(self.convo.history) > 10:
|
56
74
|
self.convo.history = self.convo.history[2:]
|
xiaogpt/cli.py
CHANGED
@@ -37,6 +37,11 @@ def main():
|
|
37
37
|
dest="gemini_key",
|
38
38
|
help="gemini api key",
|
39
39
|
)
|
40
|
+
parser.add_argument(
|
41
|
+
"--gemini_api_domain",
|
42
|
+
dest="gemini_api_domain",
|
43
|
+
help="custom gemini api domain",
|
44
|
+
)
|
40
45
|
parser.add_argument(
|
41
46
|
"--qwen_key",
|
42
47
|
dest="qwen_key",
|
@@ -78,6 +83,8 @@ def main():
|
|
78
83
|
default=None,
|
79
84
|
help="try to mute xiaoai answer",
|
80
85
|
)
|
86
|
+
parser.add_argument("--volc-access-key", help="Volcengine access key")
|
87
|
+
parser.add_argument("--volc-secret-key", help="Volcengine secret key")
|
81
88
|
parser.add_argument(
|
82
89
|
"--verbose",
|
83
90
|
dest="verbose",
|
@@ -86,27 +93,9 @@ def main():
|
|
86
93
|
help="show info",
|
87
94
|
)
|
88
95
|
parser.add_argument(
|
89
|
-
"--azure_tts_speech_key",
|
90
|
-
dest="azure_tts_speech_key",
|
91
|
-
help="if use azure tts",
|
92
|
-
)
|
93
|
-
parser.add_argument(
|
94
|
-
"--azure_tts_service_region",
|
95
|
-
dest="azure_tts_service_region",
|
96
|
-
help="if use azure tts",
|
97
|
-
)
|
98
|
-
tts_group = parser.add_mutually_exclusive_group()
|
99
|
-
tts_group.add_argument(
|
100
|
-
"--enable_edge_tts",
|
101
|
-
dest="tts",
|
102
|
-
action="store_const",
|
103
|
-
const="edge",
|
104
|
-
help="if use edge tts",
|
105
|
-
)
|
106
|
-
tts_group.add_argument(
|
107
96
|
"--tts",
|
108
|
-
help="
|
109
|
-
choices=["mi", "edge", "openai", "azure"],
|
97
|
+
help="TTS provider",
|
98
|
+
choices=["mi", "edge", "openai", "azure", "google", "baidu", "volc"],
|
110
99
|
)
|
111
100
|
bot_group = parser.add_mutually_exclusive_group()
|
112
101
|
bot_group.add_argument(
|
@@ -190,9 +179,15 @@ def main():
|
|
190
179
|
options = parser.parse_args()
|
191
180
|
config = Config.from_options(options)
|
192
181
|
|
193
|
-
|
182
|
+
async def main(config: Config) -> None:
|
183
|
+
miboy = MiGPT(config)
|
184
|
+
try:
|
185
|
+
await miboy.run_forever()
|
186
|
+
finally:
|
187
|
+
await miboy.close()
|
188
|
+
|
194
189
|
loop = asyncio.get_event_loop()
|
195
|
-
loop.run_until_complete(
|
190
|
+
loop.run_until_complete(main(config))
|
196
191
|
|
197
192
|
|
198
193
|
if __name__ == "__main__":
|
xiaogpt/config.py
CHANGED
@@ -33,15 +33,6 @@ HARDWARE_COMMAND_DICT = {
|
|
33
33
|
# add more here
|
34
34
|
}
|
35
35
|
|
36
|
-
EDGE_TTS_DICT = {
|
37
|
-
"用英语": "en-US-AriaNeural",
|
38
|
-
"用日语": "ja-JP-NanamiNeural",
|
39
|
-
"用法语": "fr-BE-CharlineNeural",
|
40
|
-
"用韩语": "ko-KR-SunHiNeural",
|
41
|
-
"用德语": "de-AT-JonasNeural",
|
42
|
-
# add more here
|
43
|
-
}
|
44
|
-
|
45
36
|
DEFAULT_COMMAND = ("5-1", "5-5")
|
46
37
|
|
47
38
|
KEY_WORD = ("帮我", "请")
|
@@ -65,6 +56,11 @@ class Config:
|
|
65
56
|
gemini_key: str = os.getenv("GEMINI_KEY", "") # keep the old rule
|
66
57
|
qwen_key: str = os.getenv("DASHSCOPE_API_KEY", "") # keep the old rule
|
67
58
|
serpapi_api_key: str = os.getenv("SERPAPI_API_KEY", "")
|
59
|
+
gemini_api_domain: str = os.getenv(
|
60
|
+
"GEMINI_API_DOMAIN", ""
|
61
|
+
) # 自行部署的 Google Gemini 代理
|
62
|
+
volc_access_key: str = os.getenv("VOLC_ACCESS_KEY", "")
|
63
|
+
volc_secret_key: str = os.getenv("VOLC_SECRET_KEY", "")
|
68
64
|
proxy: str | None = None
|
69
65
|
mi_did: str = os.getenv("MI_DID", "")
|
70
66
|
keyword: Iterable[str] = KEY_WORD
|
@@ -80,23 +76,11 @@ class Config:
|
|
80
76
|
start_conversation: str = "开始持续对话"
|
81
77
|
end_conversation: str = "结束持续对话"
|
82
78
|
stream: bool = False
|
83
|
-
tts: Literal["mi", "edge", "azure", "openai"] = "mi"
|
84
|
-
|
79
|
+
tts: Literal["mi", "edge", "azure", "openai", "baidu", "google", "volc"] = "mi"
|
80
|
+
tts_options: dict[str, Any] = field(default_factory=dict)
|
85
81
|
gpt_options: dict[str, Any] = field(default_factory=dict)
|
86
82
|
bing_cookie_path: str = ""
|
87
83
|
bing_cookies: dict | None = None
|
88
|
-
azure_tts_speech_key: str | None = None
|
89
|
-
azure_tts_service_region: str = "eastasia"
|
90
|
-
volc_accesskey: str = os.getenv(
|
91
|
-
"VOLC_ACCESSKEY", ""
|
92
|
-
) # https://console.volcengine.com/iam/keymanage/
|
93
|
-
volc_secretkey: str = os.getenv("VOLC_SECRETKEY", "")
|
94
|
-
volc_tts_app: str = os.getenv(
|
95
|
-
"VOLC_TTS_APP", ""
|
96
|
-
) # https://console.volcengine.com/sami
|
97
|
-
volc_tts_speaker: str = os.getenv(
|
98
|
-
"VOLC_TTS_SPEAPER", "zh_female_qingxin"
|
99
|
-
) # https://www.volcengine.com/docs/6489/93478
|
100
84
|
|
101
85
|
def __post_init__(self) -> None:
|
102
86
|
if self.proxy:
|
@@ -121,8 +105,6 @@ class Config:
|
|
121
105
|
raise Exception(
|
122
106
|
"Using GPT api needs openai API key, please google how to"
|
123
107
|
)
|
124
|
-
if self.tts == "azure" and not self.azure_tts_speech_key:
|
125
|
-
raise Exception("Using Azure TTS needs azure speech key")
|
126
108
|
|
127
109
|
@property
|
128
110
|
def tts_command(self) -> str:
|
@@ -140,6 +122,13 @@ class Config:
|
|
140
122
|
for key, value in vars(options).items():
|
141
123
|
if value is not None and key in cls.__dataclass_fields__:
|
142
124
|
config[key] = value
|
125
|
+
if config.get("tts") == "volc":
|
126
|
+
config.setdefault("tts_options", {}).setdefault(
|
127
|
+
"access_key", config.get("volc_access_key")
|
128
|
+
)
|
129
|
+
config.setdefault("tts_options", {}).setdefault(
|
130
|
+
"secret_key", config.get("volc_secret_key")
|
131
|
+
)
|
143
132
|
return cls(**config)
|
144
133
|
|
145
134
|
@classmethod
|
xiaogpt/tts/__init__.py
CHANGED
@@ -1,7 +1,5 @@
|
|
1
|
-
from xiaogpt.tts.base import TTS
|
2
|
-
from xiaogpt.tts.
|
3
|
-
from xiaogpt.tts.
|
4
|
-
from xiaogpt.tts.volc import VolcTTS as VolcTTS
|
5
|
-
from xiaogpt.tts.azure import AzureTTS
|
1
|
+
from xiaogpt.tts.base import TTS
|
2
|
+
from xiaogpt.tts.mi import MiTTS
|
3
|
+
from xiaogpt.tts.tetos import TetosTTS
|
6
4
|
|
7
|
-
__all__ = ["TTS", "
|
5
|
+
__all__ = ["TTS", "TetosTTS", "MiTTS"]
|
xiaogpt/tts/base.py
CHANGED
@@ -56,7 +56,7 @@ class TTS(abc.ABC):
|
|
56
56
|
return is_playing
|
57
57
|
|
58
58
|
@abc.abstractmethod
|
59
|
-
async def synthesize(self,
|
59
|
+
async def synthesize(self, lang: str, text_stream: AsyncIterator[str]) -> None:
|
60
60
|
"""Synthesize speech from a stream of text."""
|
61
61
|
raise NotImplementedError
|
62
62
|
|
@@ -87,20 +87,20 @@ class AudioFileTTS(TTS):
|
|
87
87
|
self._start_http_server()
|
88
88
|
|
89
89
|
@abc.abstractmethod
|
90
|
-
async def make_audio_file(self,
|
90
|
+
async def make_audio_file(self, lang: str, text: str) -> tuple[Path, float]:
|
91
91
|
"""Synthesize speech from text and save it to a file.
|
92
92
|
Return the file path and the duration of the audio in seconds.
|
93
93
|
The file path must be relative to the self.dirname.
|
94
94
|
"""
|
95
95
|
raise NotImplementedError
|
96
96
|
|
97
|
-
async def synthesize(self,
|
97
|
+
async def synthesize(self, lang: str, text_stream: AsyncIterator[str]) -> None:
|
98
98
|
queue: asyncio.Queue[tuple[str, float]] = asyncio.Queue()
|
99
99
|
finished = asyncio.Event()
|
100
100
|
|
101
101
|
async def worker():
|
102
102
|
async for text in text_stream:
|
103
|
-
path, duration = await self.make_audio_file(
|
103
|
+
path, duration = await self.make_audio_file(lang, text)
|
104
104
|
url = f"http://{self.hostname}:{self.port}/{path.name}"
|
105
105
|
await queue.put((url, duration))
|
106
106
|
finished.set()
|
xiaogpt/tts/mi.py
CHANGED
@@ -27,7 +27,7 @@ class MiTTS(TTS):
|
|
27
27
|
f"{self.config.tts_command} {text}",
|
28
28
|
)
|
29
29
|
|
30
|
-
async def synthesize(self,
|
30
|
+
async def synthesize(self, lang: str, text_stream: AsyncIterator[str]) -> None:
|
31
31
|
async for text in text_stream:
|
32
32
|
await self.say(text)
|
33
33
|
await self.wait_for_duration(calculate_tts_elapse(text))
|
xiaogpt/tts/tetos.py
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import tempfile
|
4
|
+
from pathlib import Path
|
5
|
+
|
6
|
+
from miservice import MiNAService
|
7
|
+
from tetos.base import Speaker
|
8
|
+
|
9
|
+
from xiaogpt.config import Config
|
10
|
+
from xiaogpt.tts.base import AudioFileTTS
|
11
|
+
|
12
|
+
|
13
|
+
class TetosTTS(AudioFileTTS):
|
14
|
+
def __init__(
|
15
|
+
self, mina_service: MiNAService, device_id: str, config: Config
|
16
|
+
) -> None:
|
17
|
+
super().__init__(mina_service, device_id, config)
|
18
|
+
self.speaker = self._get_speaker()
|
19
|
+
|
20
|
+
def _get_speaker(self) -> Speaker:
|
21
|
+
from tetos.azure import AzureSpeaker
|
22
|
+
from tetos.baidu import BaiduSpeaker
|
23
|
+
from tetos.edge import EdgeSpeaker
|
24
|
+
from tetos.google import GoogleSpeaker
|
25
|
+
from tetos.openai import OpenAISpeaker
|
26
|
+
from tetos.volc import VolcSpeaker
|
27
|
+
|
28
|
+
options = self.config.tts_options
|
29
|
+
allowed_speakers: list[str] = []
|
30
|
+
for speaker in (
|
31
|
+
OpenAISpeaker,
|
32
|
+
EdgeSpeaker,
|
33
|
+
AzureSpeaker,
|
34
|
+
VolcSpeaker,
|
35
|
+
GoogleSpeaker,
|
36
|
+
BaiduSpeaker,
|
37
|
+
):
|
38
|
+
if (name := speaker.__name__[:-7].lower()) == self.config.tts:
|
39
|
+
try:
|
40
|
+
return speaker(**options)
|
41
|
+
except TypeError as e:
|
42
|
+
raise ValueError(
|
43
|
+
f"{e}. Please add them via `tts_options` config"
|
44
|
+
) from e
|
45
|
+
else:
|
46
|
+
allowed_speakers.append(name)
|
47
|
+
raise ValueError(
|
48
|
+
f"Unsupported TTS: {self.config.tts}, allowed: {','.join(allowed_speakers)}"
|
49
|
+
)
|
50
|
+
|
51
|
+
async def make_audio_file(self, lang: str, text: str) -> tuple[Path, float]:
|
52
|
+
output_file = tempfile.NamedTemporaryFile(
|
53
|
+
suffix=".mp3", mode="wb", delete=False, dir=self.dirname.name
|
54
|
+
)
|
55
|
+
duration = await self.speaker.synthesize(text, output_file.name, lang=lang)
|
56
|
+
return Path(output_file.name), duration
|
xiaogpt/xiaogpt.py
CHANGED
@@ -23,8 +23,7 @@ from xiaogpt.config import (
|
|
23
23
|
WAKEUP_KEYWORD,
|
24
24
|
Config,
|
25
25
|
)
|
26
|
-
from xiaogpt.tts import TTS,
|
27
|
-
from xiaogpt.tts.openai import OpenAITTS
|
26
|
+
from xiaogpt.tts import TTS, MiTTS, TetosTTS
|
28
27
|
from xiaogpt.utils import (
|
29
28
|
parse_cookie_string,
|
30
29
|
)
|
@@ -53,6 +52,9 @@ class MiGPT:
|
|
53
52
|
self.log.debug(config)
|
54
53
|
self.mi_session = ClientSession()
|
55
54
|
|
55
|
+
async def close(self):
|
56
|
+
await self.mi_session.close()
|
57
|
+
|
56
58
|
async def poll_latest_ask(self):
|
57
59
|
async with ClientSession() as session:
|
58
60
|
session._cookie_jar = self.cookie_jar
|
@@ -78,16 +80,16 @@ class MiGPT:
|
|
78
80
|
# if you want force mute xiaoai, comment this line below.
|
79
81
|
await asyncio.sleep(1 - d)
|
80
82
|
|
81
|
-
async def init_all_data(self
|
82
|
-
await self.login_miboy(
|
83
|
+
async def init_all_data(self):
|
84
|
+
await self.login_miboy()
|
83
85
|
await self._init_data_hardware()
|
84
86
|
self.mi_session.cookie_jar.update_cookies(self.get_cookie())
|
85
87
|
self.cookie_jar = self.mi_session.cookie_jar
|
86
88
|
self.tts # init tts
|
87
89
|
|
88
|
-
async def login_miboy(self
|
90
|
+
async def login_miboy(self):
|
89
91
|
account = MiAccount(
|
90
|
-
|
92
|
+
self.mi_session,
|
91
93
|
self.config.account,
|
92
94
|
self.config.password,
|
93
95
|
str(self.mi_token_home),
|
@@ -179,7 +181,7 @@ class MiGPT:
|
|
179
181
|
return (
|
180
182
|
self.in_conversation
|
181
183
|
and not query.startswith(WAKEUP_KEYWORD)
|
182
|
-
or query.startswith(tuple(self.config.keyword))
|
184
|
+
or query.lower().startswith(tuple(w.lower() for w in self.config.keyword))
|
183
185
|
)
|
184
186
|
|
185
187
|
def need_change_prompt(self, record):
|
@@ -225,7 +227,7 @@ class MiGPT:
|
|
225
227
|
return None
|
226
228
|
|
227
229
|
async def _retry(self):
|
228
|
-
await self.init_all_data(
|
230
|
+
await self.init_all_data()
|
229
231
|
|
230
232
|
def _get_last_query(self, data: dict) -> dict | None:
|
231
233
|
if d := data.get("data"):
|
@@ -258,16 +260,10 @@ class MiGPT:
|
|
258
260
|
|
259
261
|
@functools.cached_property
|
260
262
|
def tts(self) -> TTS:
|
261
|
-
if self.config.tts == "
|
262
|
-
return EdgeTTS(self.mina_service, self.device_id, self.config)
|
263
|
-
elif self.config.tts == "azure":
|
264
|
-
return AzureTTS(self.mina_service, self.device_id, self.config)
|
265
|
-
elif self.config.tts == "openai":
|
266
|
-
return OpenAITTS(self.mina_service, self.device_id, self.config)
|
267
|
-
elif self.config.tts == "volc":
|
268
|
-
return VolcTTS(self.mina_service, self.device_id, self.config)
|
269
|
-
else:
|
263
|
+
if self.config.tts == "mi":
|
270
264
|
return MiTTS(self.mina_service, self.device_id, self.config)
|
265
|
+
else:
|
266
|
+
return TetosTTS(self.mina_service, self.device_id, self.config)
|
271
267
|
|
272
268
|
async def wait_for_tts_finish(self):
|
273
269
|
while True:
|
@@ -347,7 +343,7 @@ class MiGPT:
|
|
347
343
|
)
|
348
344
|
|
349
345
|
async def run_forever(self):
|
350
|
-
await self.init_all_data(
|
346
|
+
await self.init_all_data()
|
351
347
|
task = asyncio.create_task(self.poll_latest_ask())
|
352
348
|
assert task is not None # to keep the reference to task, do not remove this
|
353
349
|
print(
|
@@ -389,6 +385,7 @@ class MiGPT:
|
|
389
385
|
print("问题:" + query + "?")
|
390
386
|
if not self.chatbot.has_history():
|
391
387
|
query = f"{query},{self.config.prompt}"
|
388
|
+
query += ",并用本段话的language code作为开头,用|分隔,如:en-US|你好……"
|
392
389
|
if self.config.mute_xiaoai:
|
393
390
|
await self.stop_if_xiaoai_is_playing()
|
394
391
|
else:
|
@@ -404,7 +401,7 @@ class MiGPT:
|
|
404
401
|
print("小爱没回")
|
405
402
|
print(f"以下是 {self.chatbot.name} 的回答: ", end="")
|
406
403
|
try:
|
407
|
-
await self.
|
404
|
+
await self.speak(self.ask_gpt(query))
|
408
405
|
except Exception as e:
|
409
406
|
print(f"{self.chatbot.name} 回答出错 {str(e)}")
|
410
407
|
else:
|
@@ -412,3 +409,18 @@ class MiGPT:
|
|
412
409
|
if self.in_conversation:
|
413
410
|
print(f"继续对话, 或用`{self.config.end_conversation}`结束对话")
|
414
411
|
await self.wakeup_xiaoai()
|
412
|
+
|
413
|
+
async def speak(self, text_stream: AsyncIterator[str]) -> None:
|
414
|
+
text = await anext(text_stream)
|
415
|
+
# See if the first part contains language code(e.g. en-US|Hello world)
|
416
|
+
lang, _, first_chunk = text.rpartition("|")
|
417
|
+
if len(lang) > 7:
|
418
|
+
# It is not a legal language code, discard it
|
419
|
+
lang, first_chunk = "", text
|
420
|
+
|
421
|
+
async def gen(): # reconstruct the generator
|
422
|
+
yield first_chunk
|
423
|
+
async for text in text_stream:
|
424
|
+
yield text
|
425
|
+
|
426
|
+
await self.tts.synthesize(lang or "zh-CN", gen())
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: xiaogpt
|
3
|
-
Version: 2.
|
3
|
+
Version: 2.61
|
4
4
|
Summary: Play ChatGPT or other LLM with xiaomi AI speaker
|
5
5
|
Author-Email: yihong0618 <zouzou0208@gmail.com>
|
6
6
|
License: MIT
|
@@ -15,7 +15,6 @@ Requires-Dist: aiohttp
|
|
15
15
|
Requires-Dist: rich
|
16
16
|
Requires-Dist: zhipuai>=2.0.1
|
17
17
|
Requires-Dist: httpx[socks]
|
18
|
-
Requires-Dist: edge-tts>=6.1.3
|
19
18
|
Requires-Dist: EdgeGPT==0.1.26
|
20
19
|
Requires-Dist: langchain>=0.0.343
|
21
20
|
Requires-Dist: beautifulsoup4>=4.12.0
|
@@ -23,13 +22,11 @@ Requires-Dist: google-search-results>=2.4.2
|
|
23
22
|
Requires-Dist: google-generativeai
|
24
23
|
Requires-Dist: numexpr>=2.8.6
|
25
24
|
Requires-Dist: dashscope>=1.10.0
|
26
|
-
Requires-Dist:
|
27
|
-
Requires-Dist:
|
28
|
-
Requires-Dist: volcengine>=1.0.136
|
29
|
-
Requires-Dist: aiohttp==3.9.4; extra == "locked"
|
25
|
+
Requires-Dist: tetos>=0.1.1
|
26
|
+
Requires-Dist: aiohttp==3.9.5; extra == "locked"
|
30
27
|
Requires-Dist: aiosignal==1.3.1; extra == "locked"
|
31
28
|
Requires-Dist: annotated-types==0.6.0; extra == "locked"
|
32
|
-
Requires-Dist: anyio==3.
|
29
|
+
Requires-Dist: anyio==4.3.0; extra == "locked"
|
33
30
|
Requires-Dist: async-timeout==4.0.3; python_version < "3.11" and extra == "locked"
|
34
31
|
Requires-Dist: attrs==23.2.0; extra == "locked"
|
35
32
|
Requires-Dist: azure-cognitiveservices-speech==1.37.0; extra == "locked"
|
@@ -38,23 +35,23 @@ Requires-Dist: bingimagecreator==0.5.0; extra == "locked"
|
|
38
35
|
Requires-Dist: cachetools==5.3.2; extra == "locked"
|
39
36
|
Requires-Dist: certifi==2024.2.2; extra == "locked"
|
40
37
|
Requires-Dist: charset-normalizer==3.3.2; extra == "locked"
|
38
|
+
Requires-Dist: click==8.1.7; extra == "locked"
|
41
39
|
Requires-Dist: colorama==0.4.6; platform_system == "Windows" and extra == "locked"
|
42
|
-
Requires-Dist: dashscope==1.
|
40
|
+
Requires-Dist: dashscope==1.17.0; extra == "locked"
|
43
41
|
Requires-Dist: dataclasses-json==0.6.3; extra == "locked"
|
44
|
-
Requires-Dist: decorator==5.1.1; extra == "locked"
|
45
42
|
Requires-Dist: distro==1.9.0; extra == "locked"
|
46
43
|
Requires-Dist: edge-tts==6.1.10; extra == "locked"
|
47
44
|
Requires-Dist: edgegpt==0.1.26; extra == "locked"
|
48
45
|
Requires-Dist: exceptiongroup==1.2.0; python_version < "3.11" and extra == "locked"
|
49
46
|
Requires-Dist: frozenlist==1.4.1; extra == "locked"
|
50
|
-
Requires-Dist: google==
|
51
|
-
Requires-Dist: google-ai-generativelanguage==0.6.1; extra == "locked"
|
47
|
+
Requires-Dist: google-ai-generativelanguage==0.6.2; extra == "locked"
|
52
48
|
Requires-Dist: google-api-core==2.15.0; extra == "locked"
|
53
49
|
Requires-Dist: google-api-core[grpc]==2.15.0; extra == "locked"
|
54
50
|
Requires-Dist: google-api-python-client==2.125.0; extra == "locked"
|
55
51
|
Requires-Dist: google-auth==2.26.1; extra == "locked"
|
56
52
|
Requires-Dist: google-auth-httplib2==0.2.0; extra == "locked"
|
57
|
-
Requires-Dist: google-
|
53
|
+
Requires-Dist: google-cloud-texttospeech==2.16.3; extra == "locked"
|
54
|
+
Requires-Dist: google-generativeai==0.5.1; extra == "locked"
|
58
55
|
Requires-Dist: google-search-results==2.4.2; extra == "locked"
|
59
56
|
Requires-Dist: googleapis-common-protos==1.62.0; extra == "locked"
|
60
57
|
Requires-Dist: greenlet==3.0.3; (platform_machine == "win32" or platform_machine == "WIN32" or platform_machine == "AMD64" or platform_machine == "amd64" or platform_machine == "x86_64" or platform_machine == "ppc64le" or platform_machine == "aarch64") and extra == "locked"
|
@@ -82,40 +79,35 @@ Requires-Dist: mutagen==1.47.0; extra == "locked"
|
|
82
79
|
Requires-Dist: mypy-extensions==1.0.0; extra == "locked"
|
83
80
|
Requires-Dist: numexpr==2.10.0; extra == "locked"
|
84
81
|
Requires-Dist: numpy==1.26.3; extra == "locked"
|
85
|
-
Requires-Dist: openai==1.
|
82
|
+
Requires-Dist: openai==1.21.2; extra == "locked"
|
86
83
|
Requires-Dist: orjson==3.10.0; extra == "locked"
|
87
84
|
Requires-Dist: packaging==23.2; extra == "locked"
|
88
85
|
Requires-Dist: prompt-toolkit==3.0.43; extra == "locked"
|
89
86
|
Requires-Dist: proto-plus==1.23.0; extra == "locked"
|
90
87
|
Requires-Dist: protobuf==4.25.1; extra == "locked"
|
91
|
-
Requires-Dist: py==1.11.0; extra == "locked"
|
92
88
|
Requires-Dist: pyasn1==0.5.1; extra == "locked"
|
93
89
|
Requires-Dist: pyasn1-modules==0.3.0; extra == "locked"
|
94
|
-
Requires-Dist: pycryptodome==3.9.9; extra == "locked"
|
95
90
|
Requires-Dist: pydantic==2.5.3; extra == "locked"
|
96
91
|
Requires-Dist: pydantic-core==2.14.6; extra == "locked"
|
97
92
|
Requires-Dist: pygments==2.17.2; extra == "locked"
|
98
93
|
Requires-Dist: pyjwt==2.8.0; extra == "locked"
|
99
94
|
Requires-Dist: pyparsing==3.1.2; python_version > "3.0" and extra == "locked"
|
100
|
-
Requires-Dist: pytz==2020.5; extra == "locked"
|
101
95
|
Requires-Dist: pyyaml==6.0.1; extra == "locked"
|
102
96
|
Requires-Dist: regex==2023.12.25; extra == "locked"
|
103
97
|
Requires-Dist: requests==2.31.0; extra == "locked"
|
104
|
-
Requires-Dist: retry==0.9.2; extra == "locked"
|
105
98
|
Requires-Dist: rich==13.7.1; extra == "locked"
|
106
99
|
Requires-Dist: rsa==4.9; extra == "locked"
|
107
|
-
Requires-Dist: six==1.16.0; extra == "locked"
|
108
100
|
Requires-Dist: sniffio==1.3.0; extra == "locked"
|
109
101
|
Requires-Dist: socksio==1.0.0; extra == "locked"
|
110
102
|
Requires-Dist: soupsieve==2.5; extra == "locked"
|
111
103
|
Requires-Dist: sqlalchemy==2.0.25; extra == "locked"
|
112
104
|
Requires-Dist: tenacity==8.2.3; extra == "locked"
|
105
|
+
Requires-Dist: tetos==0.1.1; extra == "locked"
|
113
106
|
Requires-Dist: tqdm==4.66.1; extra == "locked"
|
114
107
|
Requires-Dist: typing-extensions==4.9.0; extra == "locked"
|
115
108
|
Requires-Dist: typing-inspect==0.9.0; extra == "locked"
|
116
109
|
Requires-Dist: uritemplate==4.1.1; extra == "locked"
|
117
110
|
Requires-Dist: urllib3==2.1.0; extra == "locked"
|
118
|
-
Requires-Dist: volcengine==1.0.136; extra == "locked"
|
119
111
|
Requires-Dist: wcwidth==0.2.13; extra == "locked"
|
120
112
|
Requires-Dist: websockets==12.0; extra == "locked"
|
121
113
|
Requires-Dist: yarl==1.9.4; extra == "locked"
|
@@ -144,12 +136,12 @@ Play ChatGPT and other LLM with Xiaomi AI Speaker
|
|
144
136
|
- [通义千问](https://help.aliyun.com/zh/dashscope/developer-reference/api-details)
|
145
137
|
|
146
138
|
## 获取小米音响DID
|
147
|
-
系统和Shell|Linux *sh|Windows CMD
|
148
|
-
|
149
|
-
1
|
150
|
-
2
|
151
|
-
3、取得MI_DID
|
152
|
-
4、设置MI_DID
|
139
|
+
| 系统和Shell | Linux *sh | Windows CMD用户 | Windows PowerShell用户 |
|
140
|
+
| ------------- | ---------------------------------------------- | -------------------------------------- | ---------------------------------------------- |
|
141
|
+
| 1、安装包 | `pip install miservice_fork` | `pip install miservice_fork` | `pip install miservice_fork` |
|
142
|
+
| 2、设置变量 | `export MI_USER=xxx` <br> `export MI_PASS=xxx` | `set MI_USER=xxx`<br>`set MI_PASS=xxx` | `$env:MI_USER="xxx"` <br> `$env:MI_PASS="xxx"` |
|
143
|
+
| 3、取得MI_DID | `micli list` | `micli list` | `micli list` |
|
144
|
+
| 4、设置MI_DID | `export MI_DID=xxx` | `set MI_DID=xxx` | `$env:MI_DID="xxx"` |
|
153
145
|
|
154
146
|
- 注意不同shell 对环境变量的处理是不同的,尤其是powershell赋值时,可能需要双引号来包括值。
|
155
147
|
- 如果获取did报错时,请更换一下无线网络,有很大概率解决问题。
|
@@ -199,6 +191,8 @@ xiaogpt --hardware LX06 --mute_xiaoai --use_chatgpt_api
|
|
199
191
|
xiaogpt --hardware LX06 --mute_xiaoai --stream
|
200
192
|
# 如果你想使用 google 的 gemini
|
201
193
|
xiaogpt --hardware LX06 --mute_xiaoai --use_gemini --gemini_key ${gemini_key}
|
194
|
+
# 如果你想使用自己的 google gemini 服务
|
195
|
+
python3 xiaogpt.py --hardware LX06 --mute_xiaoai --use_gemini --gemini_key ${gemini_key} --gemini_api_domain ${gemini_api_domain}
|
202
196
|
# 如果你想使用阿里的通义千问
|
203
197
|
xiaogpt --hardware LX06 --mute_xiaoai --use_qwen --qen_key ${qwen_key}
|
204
198
|
# 如果你想用 edge-tts
|
@@ -226,6 +220,8 @@ python3 xiaogpt.py --hardware LX06 --mute_xiaoai --stream
|
|
226
220
|
python3 xiaogpt.py --hardware LX06 --mute_xiaoai --use_glm --glm_key ${glm_key}
|
227
221
|
# 如果你想使用 google 的 gemini
|
228
222
|
python3 xiaogpt.py --hardware LX06 --mute_xiaoai --use_gemini --gemini_key ${gemini_key}
|
223
|
+
# 如果你想使用自己的 google gemini 服务
|
224
|
+
python3 xiaogpt.py --hardware LX06 --mute_xiaoai --use_gemini --gemini_key ${gemini_key} --gemini_api_domain ${gemini_api_domain}
|
229
225
|
# 如果你想使用阿里的通义千问
|
230
226
|
python3 xiaogpt.py --hardware LX06 --mute_xiaoai --use_qwen --qen_key ${qwen_key}
|
231
227
|
# 如果你想使用 LangChain+SerpApi 实现上网检索或其他本地服务(目前仅支持 stream 模式)
|
@@ -271,44 +267,39 @@ ChatGLM [文档](http://open.bigmodel.cn/doc/api#chatglm_130b)
|
|
271
267
|
|
272
268
|
## 配置项说明
|
273
269
|
|
274
|
-
| 参数
|
275
|
-
|
|
276
|
-
| hardware
|
277
|
-
| account
|
278
|
-
| password
|
279
|
-
| openai_key
|
280
|
-
| serpapi_api_key
|
281
|
-
| glm_key
|
282
|
-
| gemini_key
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
| volc_secretkey | 火山引擎secretkey [参考](https://console.volcengine.com/iam/keymanage/) | | |
|
308
|
-
| volc_tts_app | 火山引擎 TTS app 服务 [参考]( https://console.volcengine.com/sami/) | | |
|
309
|
-
| volc_tts_speaker | 火山引擎 TTS speaker [参考]( https://www.volcengine.com/docs/6489/93478) | `zh_female_qingxin` | |
|
310
|
-
|
311
|
-
|
270
|
+
| 参数 | 说明 | 默认值 | 可选值 |
|
271
|
+
| --------------------- | ------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- |
|
272
|
+
| hardware | 设备型号 | | |
|
273
|
+
| account | 小爱账户 | | |
|
274
|
+
| password | 小爱账户密码 | | |
|
275
|
+
| openai_key | openai的apikey | | |
|
276
|
+
| serpapi_api_key | serpapi的key 参考 [SerpAPI](https://serpapi.com/) | | |
|
277
|
+
| glm_key | chatglm 的 apikey | | |
|
278
|
+
| gemini_key | gemini 的 apikey [参考](https://makersuite.google.com/app/apikey) | | |
|
279
|
+
| gemini_api_domain | gemini 的自定义域名 [参考](https://github.com/antergone/palm-netlify-proxy) | |
|
280
|
+
| qwen_key | qwen 的 apikey [参考](https://help.aliyun.com/zh/dashscope/developer-reference/api-details) | | |
|
281
|
+
| cookie | 小爱账户cookie (如果用上面密码登录可以不填) | | |
|
282
|
+
| mi_did | 设备did | | |
|
283
|
+
| use_command | 使用 MI command 与小爱交互 | `false` | |
|
284
|
+
| mute_xiaoai | 快速停掉小爱自己的回答 | `true` | |
|
285
|
+
| verbose | 是否打印详细日志 | `false` | |
|
286
|
+
| bot | 使用的 bot 类型,目前支持 chatgptapi,newbing, qwen, gemini | `chatgptapi` | |
|
287
|
+
| tts | 使用的 TTS 类型 | `mi` | `edge`、 `openai`、`azure`、`volc`、`baidu`、`google` |
|
288
|
+
| tts_options | TTS 参数字典,参考 [tetos](https://github.com/frostming/tetos) 获取可用参数 | | |
|
289
|
+
| prompt | 自定义prompt | `请用100字以内回答` | |
|
290
|
+
| keyword | 自定义请求词列表 | `["请"]` | |
|
291
|
+
| change_prompt_keyword | 更改提示词触发列表 | `["更改提示词"]` | |
|
292
|
+
| start_conversation | 开始持续对话关键词 | `开始持续对话` | |
|
293
|
+
| end_conversation | 结束持续对话关键词 | `结束持续对话` | |
|
294
|
+
| stream | 使用流式响应,获得更快的响应 | `false` | |
|
295
|
+
| proxy | 支持 HTTP 代理,传入 http proxy URL | "" | |
|
296
|
+
| gpt_options | OpenAI API 的参数字典 | `{}` | |
|
297
|
+
| bing_cookie_path | NewBing使用的cookie路径,参考[这里]获取 | 也可通过环境变量 `COOKIE_FILE` 设置 | |
|
298
|
+
| bing_cookies | NewBing使用的cookie字典,参考[这里]获取 | | |
|
299
|
+
| deployment_id | Azure OpenAI 服务的 deployment ID | 参考这个[如何找到deployment_id](https://github.com/yihong0618/xiaogpt/issues/347#issuecomment-1784410784) | |
|
300
|
+
| api_base | 如果需要替换默认的api,或者使用Azure OpenAI 服务 | 例如:`https://abc-def.openai.azure.com/` |
|
301
|
+
| volc_access_key | 火山引擎的 access key 请在[这里](https://console.volcengine.com/iam/keymanage/)获取 | | |
|
302
|
+
| volc_secret_key | 火山引擎的 secret key 请在[这里](https://console.volcengine.com/iam/keymanage/)获取 | | |
|
312
303
|
[这里]: https://github.com/acheong08/EdgeGPT#getting-authentication-required
|
313
304
|
|
314
305
|
## 注意
|
@@ -425,6 +416,7 @@ docker run -v <your-config-dir>:/config -p 9527:9527 -e XIAOGPT_HOSTNAME=<your i
|
|
425
416
|
|
426
417
|
- [xiaomi](https://www.mi.com/)
|
427
418
|
- [PDM](https://pdm.fming.dev/latest/)
|
419
|
+
- [Tetos](https://github.com/frostming/tetos) TTS 云服务支持
|
428
420
|
- @[Yonsm](https://github.com/Yonsm) 的 [MiService](https://github.com/Yonsm/MiService)
|
429
421
|
- @[pjq](https://github.com/pjq) 给了这个项目非常多的帮助
|
430
422
|
- @[frostming](https://github.com/frostming) 重构了一些代码,支持了`持续会话功能`
|
@@ -0,0 +1,28 @@
|
|
1
|
+
xiaogpt-2.61.dist-info/METADATA,sha256=UWMVMRGMTJSpxviAmmPkwSpMGQGOJmQ74GBFTrGNWww,28013
|
2
|
+
xiaogpt-2.61.dist-info/WHEEL,sha256=7sv5iXvIiTVJSnAxCz2tGBm9DHsb2vPSzeYeT7pvGUY,90
|
3
|
+
xiaogpt-2.61.dist-info/entry_points.txt,sha256=zLFzA72qQ_eWBepdA2YU5vdXFqORH8wXhv2Ox1vnYP8,46
|
4
|
+
xiaogpt-2.61.dist-info/licenses/LICENSE,sha256=XdClh516MvlnOf9749JZHCxSB7y6_fyXcWmLDz6IkZY,1063
|
5
|
+
xiaogpt/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
|
+
xiaogpt/__main__.py,sha256=MSmt_5Xg84uHqzTN38JwgseJK8rsJn_11A8WD99VtEo,61
|
7
|
+
xiaogpt/bot/__init__.py,sha256=kOIY1lR0r-tbrnlcv_lj3cXxcwM4UpM8THxQ-JrRfwM,1006
|
8
|
+
xiaogpt/bot/base_bot.py,sha256=oKn6LLFHXol4hKrSrjnxknrOqrcGICtT_GPPYRNxpkw,1467
|
9
|
+
xiaogpt/bot/chatgptapi_bot.py,sha256=JYlq1D-YZxRwAPpd5dTFSBU7h1KNdWA7FTwgyvMxr7c,3656
|
10
|
+
xiaogpt/bot/doubao_bot.py,sha256=UufQmYcPbwTLTYDZUQwRy4Hg24vgPEa3hdeZWMWw9YM,2773
|
11
|
+
xiaogpt/bot/gemini_bot.py,sha256=vX-fTWyPwdB4N0HDQ9uIRCB4KvV-YgBqXjkrqgg4WHs,2516
|
12
|
+
xiaogpt/bot/glm_bot.py,sha256=QoMJbnu5_rHDz4tzwn7gh3IoAuw7E4hZQLAfziMAvNY,1825
|
13
|
+
xiaogpt/bot/langchain_bot.py,sha256=4Uz5iOYzA2ongCklS-9zBse2fw-7kEE_9wITH7wdVCc,1944
|
14
|
+
xiaogpt/bot/newbing_bot.py,sha256=afUmw6tyMXbgGZvfQQWaA5h0-e0V0isFolW-WGhd0Vs,2289
|
15
|
+
xiaogpt/bot/qwen_bot.py,sha256=325lMa4Z38rRh47HDa3J4XjvSs4SWOqMVhrMWzkGNo4,3657
|
16
|
+
xiaogpt/cli.py,sha256=y7Cs1M5IbCjtdbeLU9h_2__fLT-7C8qt3FIDrG2LaBQ,4696
|
17
|
+
xiaogpt/config.py,sha256=hCdggjjmBpQ_BX1teOuatjxUO9tRCUbSa3TLSnoiRkA,6444
|
18
|
+
xiaogpt/langchain/callbacks.py,sha256=yR9AXQt9OHVYBWC47Q1I_BUT4Xg9iM44vnW2vv0BLpE,2616
|
19
|
+
xiaogpt/langchain/chain.py,sha256=z0cqRlL0ElWnf31ByxZBN7AKOT-svXQDt5_NDft_nYc,1495
|
20
|
+
xiaogpt/langchain/examples/email/mail_box.py,sha256=xauqrjE4-G4XPQnokUPE-MZgAaHQ_VrUDLlbfYTdCoo,6372
|
21
|
+
xiaogpt/langchain/examples/email/mail_summary_tools.py,sha256=6cWvBJUaA7iaywcHdbUoww8WiCtaNw3TmwyxyF4DY7E,1561
|
22
|
+
xiaogpt/tts/__init__.py,sha256=xasHDrmgECirf1MSyrfURSaMBqtdZBi3cQNeDvPo_cQ,145
|
23
|
+
xiaogpt/tts/base.py,sha256=fljxdXy60HXqdLXyQlsJZtzJBo5VtTwLkkWTi58tzQc,4656
|
24
|
+
xiaogpt/tts/mi.py,sha256=1MzCB27DBohPQ_4Xz4W_FV9p-chJFDavOHB89NviLcM,1095
|
25
|
+
xiaogpt/tts/tetos.py,sha256=9DaSrfU8Pf_aa7mI6JXMvE70XYJogIPvbim4Yuhcc3k,1903
|
26
|
+
xiaogpt/utils.py,sha256=B7NCH7g19hcwHDXsnBJPTU6UcWnXoEntKWm-pgcet2I,2072
|
27
|
+
xiaogpt/xiaogpt.py,sha256=Be5kUkn7H4lKU032dmaHP9LJAQKb65hMvulJMnVc5-g,15821
|
28
|
+
xiaogpt-2.61.dist-info/RECORD,,
|
xiaogpt/tts/azure.py
DELETED
@@ -1,98 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
import logging
|
4
|
-
import tempfile
|
5
|
-
from pathlib import Path
|
6
|
-
from typing import Optional
|
7
|
-
|
8
|
-
import azure.cognitiveservices.speech as speechsdk
|
9
|
-
|
10
|
-
from xiaogpt.tts.base import AudioFileTTS
|
11
|
-
from xiaogpt.utils import calculate_tts_elapse
|
12
|
-
|
13
|
-
logger = logging.getLogger(__name__)
|
14
|
-
|
15
|
-
|
16
|
-
class AzureTTS(AudioFileTTS):
|
17
|
-
voice_name = "zh-CN-XiaoxiaoMultilingualNeural"
|
18
|
-
|
19
|
-
async def make_audio_file(self, query: str, text: str) -> tuple[Path, float]:
|
20
|
-
output_file = tempfile.NamedTemporaryFile(
|
21
|
-
suffix=".mp3", mode="wb", delete=False, dir=self.dirname.name
|
22
|
-
)
|
23
|
-
|
24
|
-
speech_synthesizer = self._build_speech_synthesizer(output_file.name)
|
25
|
-
result: Optional[speechsdk.SpeechSynthesisResult] = (
|
26
|
-
speech_synthesizer.speak_text_async(text).get()
|
27
|
-
)
|
28
|
-
if result is None:
|
29
|
-
raise RuntimeError(
|
30
|
-
f"Failed to get tts from azure with voice={self.voice_name}"
|
31
|
-
)
|
32
|
-
# Check result
|
33
|
-
if result.reason == speechsdk.ResultReason.SynthesizingAudioCompleted:
|
34
|
-
logger.debug("Speech synthesized for text [{}]".format(text))
|
35
|
-
|
36
|
-
return Path(output_file.name), calculate_tts_elapse(text)
|
37
|
-
elif result.reason == speechsdk.ResultReason.Canceled:
|
38
|
-
cancellation_details = result.cancellation_details
|
39
|
-
logger.warning(f"Speech synthesis canceled: {cancellation_details.reason}")
|
40
|
-
if cancellation_details.reason == speechsdk.CancellationReason.Error:
|
41
|
-
errmsg = f"Error details: {cancellation_details.error_details}"
|
42
|
-
logger.error(errmsg)
|
43
|
-
raise RuntimeError(errmsg)
|
44
|
-
raise RuntimeError(f"Failed to get tts from azure with voice={self.voice_name}")
|
45
|
-
|
46
|
-
def _build_speech_synthesizer(self, filename: str):
|
47
|
-
speech_key = self.config.azure_tts_speech_key
|
48
|
-
service_region = self.config.azure_tts_service_region
|
49
|
-
if not speech_key:
|
50
|
-
raise Exception("Azure tts need speech key")
|
51
|
-
speech_config = speechsdk.SpeechConfig(
|
52
|
-
subscription=speech_key, region=service_region
|
53
|
-
)
|
54
|
-
speech_config.set_speech_synthesis_output_format(
|
55
|
-
speechsdk.SpeechSynthesisOutputFormat.Audio16Khz32KBitRateMonoMp3
|
56
|
-
)
|
57
|
-
if self.config.proxy:
|
58
|
-
host, port, username, password = self._parse_proxy(self.config.proxy)
|
59
|
-
|
60
|
-
if username and password:
|
61
|
-
speech_config.set_proxy(
|
62
|
-
hostname=host, port=port, username=username, password=password
|
63
|
-
)
|
64
|
-
else:
|
65
|
-
speech_config.set_proxy(hostname=host, port=port)
|
66
|
-
|
67
|
-
speech_config.speech_synthesis_voice_name = (
|
68
|
-
self.config.tts_voice or self.voice_name
|
69
|
-
)
|
70
|
-
speech_synthesizer = speechsdk.SpeechSynthesizer(
|
71
|
-
speech_config=speech_config,
|
72
|
-
audio_config=speechsdk.audio.AudioOutputConfig(filename=filename), # type: ignore
|
73
|
-
)
|
74
|
-
return speech_synthesizer
|
75
|
-
|
76
|
-
def _parse_proxy(self, proxy_str: str):
|
77
|
-
proxy_str = proxy_str
|
78
|
-
proxy_str_splited = proxy_str.split("://")
|
79
|
-
proxy_type = proxy_str_splited[0]
|
80
|
-
proxy_addr = proxy_str_splited[1]
|
81
|
-
|
82
|
-
if proxy_type == "http":
|
83
|
-
if "@" in proxy_addr:
|
84
|
-
proxy_addr_splited = proxy_addr.split("@")
|
85
|
-
proxy_auth = proxy_addr_splited[0]
|
86
|
-
proxy_addr_netloc = proxy_addr_splited[1]
|
87
|
-
proxy_auth_splited = proxy_auth.split(":")
|
88
|
-
username = proxy_auth_splited[0]
|
89
|
-
password = proxy_auth_splited[1]
|
90
|
-
else:
|
91
|
-
proxy_addr_netloc = proxy_addr
|
92
|
-
username, password = None, None
|
93
|
-
|
94
|
-
proxy_addr_netloc_splited = proxy_addr_netloc.split(":")
|
95
|
-
host = proxy_addr_netloc_splited[0]
|
96
|
-
port = int(proxy_addr_netloc_splited[1])
|
97
|
-
return host, port, username, password
|
98
|
-
raise NotImplementedError
|
xiaogpt/tts/edge.py
DELETED
@@ -1,32 +0,0 @@
|
|
1
|
-
import tempfile
|
2
|
-
from pathlib import Path
|
3
|
-
|
4
|
-
import edge_tts
|
5
|
-
|
6
|
-
from xiaogpt.config import EDGE_TTS_DICT
|
7
|
-
from xiaogpt.tts.base import AudioFileTTS
|
8
|
-
from xiaogpt.utils import find_key_by_partial_string
|
9
|
-
|
10
|
-
|
11
|
-
class EdgeTTS(AudioFileTTS):
|
12
|
-
default_voice = "zh-CN-XiaoxiaoNeural"
|
13
|
-
|
14
|
-
async def make_audio_file(self, query: str, text: str) -> tuple[Path, float]:
|
15
|
-
voice = (
|
16
|
-
find_key_by_partial_string(EDGE_TTS_DICT, query)
|
17
|
-
or self.config.tts_voice
|
18
|
-
or self.default_voice
|
19
|
-
)
|
20
|
-
communicate = edge_tts.Communicate(text, voice, proxy=self.config.proxy)
|
21
|
-
duration = 0
|
22
|
-
with tempfile.NamedTemporaryFile(
|
23
|
-
suffix=".mp3", mode="wb", delete=False, dir=self.dirname.name
|
24
|
-
) as f:
|
25
|
-
async for chunk in communicate.stream():
|
26
|
-
if chunk["type"] == "audio":
|
27
|
-
f.write(chunk["data"])
|
28
|
-
elif chunk["type"] == "WordBoundary":
|
29
|
-
duration = (chunk["offset"] + chunk["duration"]) / 1e7
|
30
|
-
if duration == 0:
|
31
|
-
raise RuntimeError(f"Failed to get tts from edge with voice={voice}")
|
32
|
-
return (Path(f.name), duration)
|
xiaogpt/tts/openai.py
DELETED
@@ -1,46 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
import tempfile
|
4
|
-
from pathlib import Path
|
5
|
-
from typing import TYPE_CHECKING
|
6
|
-
|
7
|
-
import httpx
|
8
|
-
|
9
|
-
from xiaogpt.tts.base import AudioFileTTS
|
10
|
-
from xiaogpt.utils import calculate_tts_elapse
|
11
|
-
|
12
|
-
if TYPE_CHECKING:
|
13
|
-
import openai
|
14
|
-
|
15
|
-
|
16
|
-
class OpenAITTS(AudioFileTTS):
|
17
|
-
default_voice = "alloy"
|
18
|
-
|
19
|
-
async def make_audio_file(self, query: str, text: str) -> tuple[Path, float]:
|
20
|
-
output_file = tempfile.NamedTemporaryFile(
|
21
|
-
suffix=".mp3", mode="wb", delete=False, dir=self.dirname.name
|
22
|
-
)
|
23
|
-
httpx_kwargs = {}
|
24
|
-
if self.config.proxy:
|
25
|
-
httpx_kwargs["proxies"] = self.config.proxy
|
26
|
-
async with httpx.AsyncClient(trust_env=True, **httpx_kwargs) as sess:
|
27
|
-
client = self._make_openai_client(sess)
|
28
|
-
|
29
|
-
resp = await client.audio.speech.create(
|
30
|
-
model="tts-1",
|
31
|
-
input=text,
|
32
|
-
voice=self.config.tts_voice or self.default_voice,
|
33
|
-
)
|
34
|
-
resp.stream_to_file(output_file.name)
|
35
|
-
return Path(output_file.name), calculate_tts_elapse(text)
|
36
|
-
|
37
|
-
def _make_openai_client(self, sess: httpx.AsyncClient) -> openai.AsyncOpenAI:
|
38
|
-
import openai
|
39
|
-
|
40
|
-
api_base = self.config.api_base
|
41
|
-
if api_base and api_base.rstrip("/").endswith("openai.azure.com"):
|
42
|
-
raise NotImplementedError("TTS is not supported for Azure OpenAI")
|
43
|
-
else:
|
44
|
-
return openai.AsyncOpenAI(
|
45
|
-
api_key=self.config.openai_key, http_client=sess, base_url=api_base
|
46
|
-
)
|
xiaogpt/tts/volc.py
DELETED
@@ -1,130 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
import logging
|
4
|
-
import tempfile
|
5
|
-
from pathlib import Path
|
6
|
-
from typing import Optional
|
7
|
-
import json
|
8
|
-
import os
|
9
|
-
import time
|
10
|
-
import base64
|
11
|
-
import threading
|
12
|
-
import httpx
|
13
|
-
|
14
|
-
from volcengine.ApiInfo import ApiInfo
|
15
|
-
from volcengine.Credentials import Credentials
|
16
|
-
from volcengine.ServiceInfo import ServiceInfo
|
17
|
-
from volcengine.base.Service import Service
|
18
|
-
|
19
|
-
|
20
|
-
from xiaogpt.tts.base import AudioFileTTS
|
21
|
-
from xiaogpt.utils import calculate_tts_elapse
|
22
|
-
|
23
|
-
logger = logging.getLogger(__name__)
|
24
|
-
|
25
|
-
|
26
|
-
class VolcTTS(AudioFileTTS):
|
27
|
-
def __init__(self, mina_service, device_id, config):
|
28
|
-
super().__init__(mina_service, device_id, config)
|
29
|
-
self.token = get_token(config)
|
30
|
-
self.client = httpx.Client()
|
31
|
-
logger.info("Initializing VolcTTS {self.token}")
|
32
|
-
|
33
|
-
async def make_audio_file(self, query: str, text: str) -> tuple[Path, float]:
|
34
|
-
tts_payload = json.dumps(
|
35
|
-
{
|
36
|
-
"text": text,
|
37
|
-
"speaker": self.config.volc_tts_speaker,
|
38
|
-
"audio_config": {
|
39
|
-
"format": "mp3",
|
40
|
-
"sample_rate": 24000,
|
41
|
-
"speech_rate": 0,
|
42
|
-
},
|
43
|
-
}
|
44
|
-
)
|
45
|
-
|
46
|
-
req = {
|
47
|
-
"appkey": self.config.volc_tts_app,
|
48
|
-
"token": self.token,
|
49
|
-
"namespace": "TTS",
|
50
|
-
"payload": tts_payload,
|
51
|
-
}
|
52
|
-
|
53
|
-
resp = self.client.post("https://sami.bytedance.com/api/v1/invoke", json=req)
|
54
|
-
try:
|
55
|
-
sami_resp = resp.json()
|
56
|
-
logger.info(f"volc sami_resp {resp.status_code}")
|
57
|
-
if resp.status_code != 200:
|
58
|
-
print(sami_resp)
|
59
|
-
except:
|
60
|
-
logger.error(f"Failed to get tts from volcengine with voice=zh {text}")
|
61
|
-
|
62
|
-
if sami_resp["status_code"] == 20000000 and len(sami_resp["data"]) > 0:
|
63
|
-
audio_data = base64.b64decode(sami_resp["data"])
|
64
|
-
with tempfile.NamedTemporaryFile(
|
65
|
-
suffix=".mp3", mode="wb", delete=False, dir=self.dirname.name
|
66
|
-
) as f:
|
67
|
-
f.write(audio_data)
|
68
|
-
|
69
|
-
return Path(f.name), calculate_tts_elapse(text)
|
70
|
-
|
71
|
-
|
72
|
-
## fetch token and save it to file
|
73
|
-
## it's aimed to reduce the request to volcengine
|
74
|
-
## it'll throw error if token is requested too frequently (more than 1 times per minute)
|
75
|
-
def get_token(config):
|
76
|
-
token_file = Path.home() / ".volc.token"
|
77
|
-
if not Path.exists(token_file):
|
78
|
-
token = request_token_data(config)
|
79
|
-
else:
|
80
|
-
with open(token_file, "r") as f:
|
81
|
-
token = json.load(f)
|
82
|
-
if token["expires_at"] < time.time():
|
83
|
-
token = request_token_data(config)
|
84
|
-
|
85
|
-
if not Path.exists(token_file):
|
86
|
-
with open(token_file, "w") as f:
|
87
|
-
json.dump(token, f)
|
88
|
-
return token["token"]
|
89
|
-
|
90
|
-
|
91
|
-
def request_token_data(config):
|
92
|
-
sami_service = SAMIService()
|
93
|
-
sami_service.set_ak(config.volc_accesskey)
|
94
|
-
sami_service.set_sk(config.volc_secretkey)
|
95
|
-
|
96
|
-
req = {
|
97
|
-
"appkey": config.volc_tts_app,
|
98
|
-
"token_version": "volc-auth-v1",
|
99
|
-
"expiration": 24 * 3600,
|
100
|
-
}
|
101
|
-
token = sami_service.common_json_handler("GetToken", req)
|
102
|
-
logger.info(f"Got token from volcengine {token}")
|
103
|
-
return token
|
104
|
-
|
105
|
-
|
106
|
-
class SAMIService(Service):
|
107
|
-
def __init__(self):
|
108
|
-
self.service_info = ServiceInfo(
|
109
|
-
"open.volcengineapi.com",
|
110
|
-
{},
|
111
|
-
Credentials("", "", "sami", "cn-north-1"),
|
112
|
-
10,
|
113
|
-
10,
|
114
|
-
)
|
115
|
-
self.api_info = {
|
116
|
-
"GetToken": ApiInfo(
|
117
|
-
"POST", "/", {"Action": "GetToken", "Version": "2021-07-27"}, {}, {}
|
118
|
-
),
|
119
|
-
}
|
120
|
-
super(SAMIService, self).__init__(self.service_info, self.api_info)
|
121
|
-
|
122
|
-
def common_json_handler(self, api, body):
|
123
|
-
params = dict()
|
124
|
-
try:
|
125
|
-
body = json.dumps(body)
|
126
|
-
res = self.json(api, params, body)
|
127
|
-
res_json = json.loads(res)
|
128
|
-
return res_json
|
129
|
-
except Exception as e:
|
130
|
-
raise Exception(str(e))
|
xiaogpt-2.50.dist-info/RECORD
DELETED
@@ -1,30 +0,0 @@
|
|
1
|
-
xiaogpt-2.50.dist-info/METADATA,sha256=vRIqumlE_-ZX5cRlvJMOAOA2ap3DJe81s98p6ctH9SY,30311
|
2
|
-
xiaogpt-2.50.dist-info/WHEEL,sha256=7sv5iXvIiTVJSnAxCz2tGBm9DHsb2vPSzeYeT7pvGUY,90
|
3
|
-
xiaogpt-2.50.dist-info/entry_points.txt,sha256=zLFzA72qQ_eWBepdA2YU5vdXFqORH8wXhv2Ox1vnYP8,46
|
4
|
-
xiaogpt-2.50.dist-info/licenses/LICENSE,sha256=XdClh516MvlnOf9749JZHCxSB7y6_fyXcWmLDz6IkZY,1063
|
5
|
-
xiaogpt/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
|
-
xiaogpt/__main__.py,sha256=MSmt_5Xg84uHqzTN38JwgseJK8rsJn_11A8WD99VtEo,61
|
7
|
-
xiaogpt/bot/__init__.py,sha256=7K9v6j6xDkuvIrJwjby0Ec_1ywd4Si0Ww9S_R8M3LeU,919
|
8
|
-
xiaogpt/bot/base_bot.py,sha256=oKn6LLFHXol4hKrSrjnxknrOqrcGICtT_GPPYRNxpkw,1467
|
9
|
-
xiaogpt/bot/chatgptapi_bot.py,sha256=JYlq1D-YZxRwAPpd5dTFSBU7h1KNdWA7FTwgyvMxr7c,3656
|
10
|
-
xiaogpt/bot/gemini_bot.py,sha256=udKrWYP7U83AWpNBggwRp9bvgR2DTHqLMX9E_DLFv-I,1840
|
11
|
-
xiaogpt/bot/glm_bot.py,sha256=QoMJbnu5_rHDz4tzwn7gh3IoAuw7E4hZQLAfziMAvNY,1825
|
12
|
-
xiaogpt/bot/langchain_bot.py,sha256=4Uz5iOYzA2ongCklS-9zBse2fw-7kEE_9wITH7wdVCc,1944
|
13
|
-
xiaogpt/bot/newbing_bot.py,sha256=afUmw6tyMXbgGZvfQQWaA5h0-e0V0isFolW-WGhd0Vs,2289
|
14
|
-
xiaogpt/bot/qwen_bot.py,sha256=325lMa4Z38rRh47HDa3J4XjvSs4SWOqMVhrMWzkGNo4,3657
|
15
|
-
xiaogpt/cli.py,sha256=GnRj-AawthaZ5oE2jUzp_ML64afsSBnGXFvbnaqgJHE,4738
|
16
|
-
xiaogpt/config.py,sha256=4ZAhefT9ROKpYw6i_QK4C1dcm2o2rjoROO5WqxJk3ww,6726
|
17
|
-
xiaogpt/langchain/callbacks.py,sha256=yR9AXQt9OHVYBWC47Q1I_BUT4Xg9iM44vnW2vv0BLpE,2616
|
18
|
-
xiaogpt/langchain/chain.py,sha256=z0cqRlL0ElWnf31ByxZBN7AKOT-svXQDt5_NDft_nYc,1495
|
19
|
-
xiaogpt/langchain/examples/email/mail_box.py,sha256=xauqrjE4-G4XPQnokUPE-MZgAaHQ_VrUDLlbfYTdCoo,6372
|
20
|
-
xiaogpt/langchain/examples/email/mail_summary_tools.py,sha256=6cWvBJUaA7iaywcHdbUoww8WiCtaNw3TmwyxyF4DY7E,1561
|
21
|
-
xiaogpt/tts/__init__.py,sha256=N-mg521hRDByAPXts1Dl8EQVLEYQI7D2iWDIQ7ILLNE,279
|
22
|
-
xiaogpt/tts/azure.py,sha256=JuE1wirQQAsYnnHmlc3sziuVXQUU0yGeGt5cHgiY388,3979
|
23
|
-
xiaogpt/tts/base.py,sha256=a7J5cpcDNefr7deXJQWwDKw9XPFm6EQQL9O-GLe23hM,4660
|
24
|
-
xiaogpt/tts/edge.py,sha256=yMFGxRTi086XS1d_mbMzQ365bvG4KgAz8ZptaoDAfGU,1172
|
25
|
-
xiaogpt/tts/mi.py,sha256=9HkgGWByAs7k8sTpRdVlgJnnmjc44RNAccJa6tGDlXk,1096
|
26
|
-
xiaogpt/tts/openai.py,sha256=_Qk12zYY-UuXLKvQVe3PqIvCmoRW9OcVCqQRoGCXvNc,1533
|
27
|
-
xiaogpt/tts/volc.py,sha256=fLN7Z5HXx9TCkhGKWyTXasUcXn_fSlTNjVccvQunLUM,3984
|
28
|
-
xiaogpt/utils.py,sha256=B7NCH7g19hcwHDXsnBJPTU6UcWnXoEntKWm-pgcet2I,2072
|
29
|
-
xiaogpt/xiaogpt.py,sha256=dF_G26axLD9htLbFfHLMTLf2gaIdvAHkU88-s4RSmOs,15513
|
30
|
-
xiaogpt-2.50.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|