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 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.newbing_bot import NewBingBot
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.qwen_bot import QwenBot
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": 2048,
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
- genai.configure(api_key=gemini_key)
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(gemini_key=config.gemini_key)
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 = self.convo.last.text.strip()
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="tts type",
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
- miboy = MiGPT(config)
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(miboy.run_forever())
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
- tts_voice: str | None = None
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 as TTS
2
- from xiaogpt.tts.edge import EdgeTTS as EdgeTTS
3
- from xiaogpt.tts.mi import MiTTS as MiTTS
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", "EdgeTTS", "MiTTS", "AzureTTS", "VolcTTS"]
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, query: str, text_stream: AsyncIterator[str]) -> None:
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, query: str, text: str) -> tuple[Path, float]:
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, query: str, text_stream: AsyncIterator[str]) -> None:
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(query, text)
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, query: str, text_stream: AsyncIterator[str]) -> None:
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, EdgeTTS, MiTTS, AzureTTS, VolcTTS
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, session):
82
- await self.login_miboy(session)
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, session):
90
+ async def login_miboy(self):
89
91
  account = MiAccount(
90
- session,
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(self.mi_session)
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 == "edge":
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(self.mi_session)
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.tts.synthesize(query, self.ask_gpt(query))
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.50
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: azure-cognitiveservices-speech>=1.37.0
27
- Requires-Dist: multidict>=6.0.5
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.7.1; extra == "locked"
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.10.0; extra == "locked"
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==3.0.0; extra == "locked"
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-generativeai==0.5.0; extra == "locked"
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.17.1; extra == "locked"
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用户|Windows PowerShell用户
148
- -|-|-|-
149
- 1、安装包|`pip install miservice_fork`|`pip install miservice_fork`|`pip install miservice_fork`
150
- 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"`
151
- 3、取得MI_DID|`micli list` |`micli list` |`micli list`
152
- 4、设置MI_DID|`export MI_DID=xxx`| `set MI_DID=xxx`| `$env:MI_DID="xxx"`
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 | openai的apikey | | |
280
- | serpapi_api_key | serpapi的key 参考 [SerpAPI](https://serpapi.com/) | | |
281
- | glm_key | chatglm 的 apikey | | |
282
- | gemini_key | gemini 的 apikey [参考](https://makersuite.google.com/app/apikey) | | |
283
- | qwen_key | qwen apikey [参考](https://help.aliyun.com/zh/dashscope/developer-reference/api-details) | | |
284
- | cookie | 小爱账户cookie (如果用上面密码登录可以不填) | | |
285
- | mi_did | 设备did | | |
286
- | use_command | 使用 MI command 与小爱交互 | `false` | |
287
- | mute_xiaoai | 快速停掉小爱自己的回答 | `true` | |
288
- | verbose | 是否打印详细日志 | `false` | |
289
- | bot | 使用的 bot 类型,目前支持 chatgptapi,newbing, qwen, gemini | `chatgptapi` | |
290
- | tts | 使用的 TTS 类型 | `mi` | `edge`、 `openai`、`azure`、`volc` |
291
- | tts_voice | TTS 的嗓音 | `zh-CN-XiaoxiaoNeural`(edge), `alloy`(openai), `zh-CN-XiaoxiaoMultilingualNeural`(azure) | |
292
- | prompt | 自定义prompt | `请用100字以内回答` | |
293
- | keyword | 自定义请求词列表 | `["请"]` | |
294
- | change_prompt_keyword | 更改提示词触发列表 | `["更改提示词"]` | |
295
- | start_conversation | 开始持续对话关键词 | `开始持续对话` | |
296
- | end_conversation | 结束持续对话关键词 | `结束持续对话` | |
297
- | stream | 使用流式响应,获得更快的响应 | `false` | |
298
- | proxy | 支持 HTTP 代理,传入 http proxy URL | "" | |
299
- | gpt_options | OpenAI API 的参数字典 | `{}` | |
300
- | bing_cookie_path | NewBing使用的cookie路径,参考[这里]获取 | 也可通过环境变量 `COOKIE_FILE` 设置 | |
301
- | bing_cookies | NewBing使用的cookie字典,参考[这里]获取 | | |
302
- | deployment_id | Azure OpenAI 服务的 deployment ID | 参考这个[如何找到deployment_id](https://github.com/yihong0618/xiaogpt/issues/347#issuecomment-1784410784) | |
303
- | api_base | 如果需要替换默认的api,或者使用Azure OpenAI 服务 | 例如:`https://abc-def.openai.azure.com/` | |
304
- | azure_tts_speech_key | Azure TTS key | null | |
305
- | azure_tts_service_region | Azure TTS 服务地区 | `eastasia` | [Regions - Speech service - Azure AI services](https://learn.microsoft.com/en-us/azure/ai-services/speech-service/regions) |
306
- | volc_accesskey | 火山引擎accesskey [参考](https://console.volcengine.com/iam/keymanage/) | | |
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))
@@ -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