xiaozhi-sdk 0.2.0__py3-none-any.whl → 0.2.2__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.
Potentially problematic release.
This version of xiaozhi-sdk might be problematic. Click here for more details.
- xiaozhi_sdk/__init__.py +1 -1
- xiaozhi_sdk/core.py +24 -9
- xiaozhi_sdk/mcp.py +1 -1
- xiaozhi_sdk/utils/mcp_tool.py +2 -94
- xiaozhi_sdk/utils/tool_func.py +92 -0
- {xiaozhi_sdk-0.2.0.dist-info → xiaozhi_sdk-0.2.2.dist-info}/METADATA +1 -1
- {xiaozhi_sdk-0.2.0.dist-info → xiaozhi_sdk-0.2.2.dist-info}/RECORD +10 -9
- {xiaozhi_sdk-0.2.0.dist-info → xiaozhi_sdk-0.2.2.dist-info}/WHEEL +0 -0
- {xiaozhi_sdk-0.2.0.dist-info → xiaozhi_sdk-0.2.2.dist-info}/licenses/LICENSE +0 -0
- {xiaozhi_sdk-0.2.0.dist-info → xiaozhi_sdk-0.2.2.dist-info}/top_level.txt +0 -0
xiaozhi_sdk/__init__.py
CHANGED
xiaozhi_sdk/core.py
CHANGED
|
@@ -139,8 +139,11 @@ class XiaoZhiWebsocket(McpTool):
|
|
|
139
139
|
|
|
140
140
|
# audio data
|
|
141
141
|
if isinstance(message, bytes):
|
|
142
|
-
|
|
143
|
-
|
|
142
|
+
try:
|
|
143
|
+
pcm_array = await self.audio_opus.opus_to_pcm(message)
|
|
144
|
+
self.output_audio_queue.extend(pcm_array)
|
|
145
|
+
except Exception as e:
|
|
146
|
+
logger.error("opus_to_pcm error: %s", e)
|
|
144
147
|
return
|
|
145
148
|
|
|
146
149
|
# json message
|
|
@@ -149,29 +152,41 @@ class XiaoZhiWebsocket(McpTool):
|
|
|
149
152
|
if message_type == "hello":
|
|
150
153
|
self.hello_received.set()
|
|
151
154
|
self.session_id = data["session_id"]
|
|
155
|
+
return
|
|
152
156
|
elif message_type == "mcp":
|
|
153
157
|
await self.mcp(data)
|
|
154
|
-
|
|
155
|
-
|
|
158
|
+
return
|
|
159
|
+
|
|
160
|
+
# 转发其他 message_type
|
|
161
|
+
if self.message_handler_callback:
|
|
162
|
+
try:
|
|
163
|
+
await self.message_handler_callback(data)
|
|
164
|
+
except Exception as e:
|
|
165
|
+
logger.error("message_handler_callback error: %s", e)
|
|
166
|
+
|
|
167
|
+
if message_type == "tts":
|
|
156
168
|
if data["state"] == "sentence_start":
|
|
157
169
|
self.is_playing = True
|
|
158
|
-
# self.output_audio_queue.clear()
|
|
159
170
|
else:
|
|
160
171
|
self.is_playing = False
|
|
161
|
-
|
|
162
|
-
|
|
172
|
+
else:
|
|
173
|
+
logger.warning("未定义回调函数 %s", data)
|
|
163
174
|
|
|
164
175
|
async def _message_handler(self) -> None:
|
|
165
176
|
"""消息处理器"""
|
|
166
177
|
try:
|
|
167
178
|
async for message in self.websocket:
|
|
168
|
-
|
|
179
|
+
try:
|
|
180
|
+
await self._handle_websocket_message(message)
|
|
181
|
+
except Exception as e:
|
|
182
|
+
logger.error("message_handler error: %s", e)
|
|
183
|
+
|
|
169
184
|
except websockets.ConnectionClosed:
|
|
170
185
|
if self.message_handler_callback:
|
|
171
186
|
await self.message_handler_callback(
|
|
172
187
|
{"type": "websocket", "state": "close", "source": "sdk.message_handler"}
|
|
173
188
|
)
|
|
174
|
-
|
|
189
|
+
logger.info("[websocket] close")
|
|
175
190
|
|
|
176
191
|
async def set_mcp_tool(self, mcp_tool_list) -> None:
|
|
177
192
|
"""设置MCP工具"""
|
xiaozhi_sdk/mcp.py
CHANGED
xiaozhi_sdk/utils/mcp_tool.py
CHANGED
|
@@ -1,96 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import random
|
|
3
|
-
|
|
4
|
-
import aiohttp
|
|
5
|
-
import numpy as np
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
async def async_search_custom_music(data) -> tuple[dict, bool]:
|
|
9
|
-
search_url = f"https://music-api.gdstudio.xyz/api.php?types=search&name={data['music_name']}&count=100&pages=1"
|
|
10
|
-
|
|
11
|
-
# 为搜索请求设置 10 秒超时
|
|
12
|
-
timeout = aiohttp.ClientTimeout(total=10)
|
|
13
|
-
async with aiohttp.ClientSession(timeout=timeout) as session:
|
|
14
|
-
async with session.get(search_url) as response:
|
|
15
|
-
response_json = await response.json()
|
|
16
|
-
|
|
17
|
-
music_list = []
|
|
18
|
-
first_music_list = []
|
|
19
|
-
other_music_list1 = []
|
|
20
|
-
other_music_list2 = []
|
|
21
|
-
for line in response_json:
|
|
22
|
-
if data.get("author_name") and data["author_name"] in line["artist"][0]:
|
|
23
|
-
first_music_list.append(line)
|
|
24
|
-
elif data.get("author_name") and (data["author_name"] in line["artist"] or data["author_name"] in line["name"]):
|
|
25
|
-
other_music_list1.append(line)
|
|
26
|
-
else:
|
|
27
|
-
other_music_list2.append(line)
|
|
28
|
-
|
|
29
|
-
if len(first_music_list) <= 10:
|
|
30
|
-
music_list = first_music_list
|
|
31
|
-
random.shuffle(other_music_list2)
|
|
32
|
-
music_list = music_list + other_music_list1[: 20 - len(music_list)]
|
|
33
|
-
music_list = music_list + other_music_list2[: 20 - len(music_list)]
|
|
34
|
-
|
|
35
|
-
# print(data)
|
|
36
|
-
# print("找到音乐,数量:", len(first_music_list), len(music_list))
|
|
37
|
-
|
|
38
|
-
if not music_list:
|
|
39
|
-
return {}, False
|
|
40
|
-
return {"message": "已找到歌曲", "music_list": music_list}, False
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
async def _get_random_music_info(id_list: list) -> dict:
|
|
44
|
-
timeout = aiohttp.ClientTimeout(total=10)
|
|
45
|
-
async with aiohttp.ClientSession(timeout=timeout) as session:
|
|
46
|
-
random.shuffle(id_list)
|
|
47
|
-
|
|
48
|
-
for music_id in id_list:
|
|
49
|
-
url = f"https://music-api.gdstudio.xyz/api.php?types=url&id={music_id}&br=128"
|
|
50
|
-
async with session.get(url) as response:
|
|
51
|
-
res_json = await response.json()
|
|
52
|
-
if res_json.get("url"):
|
|
53
|
-
break
|
|
54
|
-
|
|
55
|
-
return res_json
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
async def async_mcp_play_music(data) -> tuple[list, bool]:
|
|
59
|
-
try:
|
|
60
|
-
from pydub import AudioSegment
|
|
61
|
-
except ImportError:
|
|
62
|
-
return [], True
|
|
63
|
-
|
|
64
|
-
id_list = data["id_list"]
|
|
65
|
-
res_json = await _get_random_music_info(id_list)
|
|
66
|
-
|
|
67
|
-
if not res_json:
|
|
68
|
-
return [], False
|
|
69
|
-
|
|
70
|
-
pcm_list = []
|
|
71
|
-
buffer = io.BytesIO()
|
|
72
|
-
# 为下载音乐文件设置 60 秒超时(音乐文件可能比较大)
|
|
73
|
-
download_timeout = aiohttp.ClientTimeout(total=60)
|
|
74
|
-
async with aiohttp.ClientSession(timeout=download_timeout) as session:
|
|
75
|
-
async with session.get(res_json["url"]) as resp:
|
|
76
|
-
async for chunk in resp.content.iter_chunked(1024):
|
|
77
|
-
buffer.write(chunk)
|
|
78
|
-
|
|
79
|
-
buffer.seek(0)
|
|
80
|
-
audio = AudioSegment.from_mp3(buffer)
|
|
81
|
-
audio = audio.set_frame_rate(16000).set_channels(1).set_sample_width(2) # 2 bytes = 16 bits
|
|
82
|
-
pcm_data = audio.raw_data
|
|
83
|
-
|
|
84
|
-
chunk_size = 960 * 2
|
|
85
|
-
for i in range(0, len(pcm_data), chunk_size):
|
|
86
|
-
chunk = pcm_data[i : i + chunk_size]
|
|
87
|
-
|
|
88
|
-
if chunk: # 确保不添加空块
|
|
89
|
-
chunk = np.frombuffer(chunk, dtype=np.int16)
|
|
90
|
-
pcm_list.extend(chunk)
|
|
91
|
-
|
|
92
|
-
return pcm_list, False
|
|
93
|
-
|
|
1
|
+
from xiaozhi_sdk.utils.tool_func import async_mcp_play_music, async_search_custom_music
|
|
94
2
|
|
|
95
3
|
search_custom_music = {
|
|
96
4
|
"name": "search_custom_music",
|
|
@@ -164,7 +72,7 @@ set_theme = {
|
|
|
164
72
|
|
|
165
73
|
take_photo = {
|
|
166
74
|
"name": "take_photo",
|
|
167
|
-
"description": "Use this tool when the user asks you to look at something, take a picture, or solve a problem based on what is captured.\nArgs:\n`question`: A clear question or task you want to ask about the captured photo (e.g., identify objects, read text, explain content, or solve a math/logic problem).\nReturn:\n A JSON object that provides the photo information, including answers, explanations, or problem-solving results if applicable.",
|
|
75
|
+
"description": "Have visual ability, Use this tool when the user asks you to look at something, take a picture, or solve a problem based on what is captured.\nArgs:\n`question`: A clear question or task you want to ask about the captured photo (e.g., identify objects, read text, explain content, or solve a math/logic problem).\nReturn:\n A JSON object that provides the photo information, including answers, explanations, or problem-solving results if applicable.",
|
|
168
76
|
"inputSchema": {
|
|
169
77
|
"type": "object",
|
|
170
78
|
"properties": {"question": {"type": "string"}},
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import io
|
|
2
|
+
import random
|
|
3
|
+
|
|
4
|
+
import aiohttp
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
async def async_search_custom_music(data) -> tuple[dict, bool]:
|
|
9
|
+
search_url = f"https://music-api.gdstudio.xyz/api.php?types=search&name={data['music_name']}&count=100&pages=1"
|
|
10
|
+
|
|
11
|
+
# 为搜索请求设置 10 秒超时
|
|
12
|
+
timeout = aiohttp.ClientTimeout(total=10)
|
|
13
|
+
async with aiohttp.ClientSession(timeout=timeout) as session:
|
|
14
|
+
async with session.get(search_url) as response:
|
|
15
|
+
response_json = await response.json()
|
|
16
|
+
|
|
17
|
+
music_list = []
|
|
18
|
+
first_music_list = []
|
|
19
|
+
other_music_list1 = []
|
|
20
|
+
other_music_list2 = []
|
|
21
|
+
for line in response_json:
|
|
22
|
+
if data.get("author_name") and data["author_name"] in line["artist"][0]:
|
|
23
|
+
first_music_list.append(line)
|
|
24
|
+
elif data.get("author_name") and (data["author_name"] in line["artist"] or data["author_name"] in line["name"]):
|
|
25
|
+
other_music_list1.append(line)
|
|
26
|
+
else:
|
|
27
|
+
other_music_list2.append(line)
|
|
28
|
+
|
|
29
|
+
if len(first_music_list) <= 10:
|
|
30
|
+
music_list = first_music_list
|
|
31
|
+
random.shuffle(other_music_list2)
|
|
32
|
+
music_list = music_list + other_music_list1[: 20 - len(music_list)]
|
|
33
|
+
music_list = music_list + other_music_list2[: 20 - len(music_list)]
|
|
34
|
+
|
|
35
|
+
# print(data)
|
|
36
|
+
# print("找到音乐,数量:", len(first_music_list), len(music_list))
|
|
37
|
+
|
|
38
|
+
if not music_list:
|
|
39
|
+
return {}, False
|
|
40
|
+
return {"message": "已找到歌曲", "music_list": music_list}, False
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
async def _get_random_music_info(id_list: list) -> dict:
|
|
44
|
+
timeout = aiohttp.ClientTimeout(total=10)
|
|
45
|
+
async with aiohttp.ClientSession(timeout=timeout) as session:
|
|
46
|
+
random.shuffle(id_list)
|
|
47
|
+
|
|
48
|
+
for music_id in id_list:
|
|
49
|
+
url = f"https://music-api.gdstudio.xyz/api.php?types=url&id={music_id}&br=128"
|
|
50
|
+
async with session.get(url) as response:
|
|
51
|
+
res_json = await response.json()
|
|
52
|
+
if res_json.get("url"):
|
|
53
|
+
break
|
|
54
|
+
|
|
55
|
+
return res_json
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
async def async_mcp_play_music(data) -> tuple[list, bool]:
|
|
59
|
+
try:
|
|
60
|
+
from pydub import AudioSegment
|
|
61
|
+
except ImportError:
|
|
62
|
+
return [], True
|
|
63
|
+
|
|
64
|
+
id_list = data["id_list"]
|
|
65
|
+
res_json = await _get_random_music_info(id_list)
|
|
66
|
+
|
|
67
|
+
if not res_json:
|
|
68
|
+
return [], False
|
|
69
|
+
|
|
70
|
+
pcm_list = []
|
|
71
|
+
buffer = io.BytesIO()
|
|
72
|
+
# 为下载音乐文件设置 60 秒超时(音乐文件可能比较大)
|
|
73
|
+
download_timeout = aiohttp.ClientTimeout(total=60)
|
|
74
|
+
async with aiohttp.ClientSession(timeout=download_timeout) as session:
|
|
75
|
+
async with session.get(res_json["url"]) as resp:
|
|
76
|
+
async for chunk in resp.content.iter_chunked(1024):
|
|
77
|
+
buffer.write(chunk)
|
|
78
|
+
|
|
79
|
+
buffer.seek(0)
|
|
80
|
+
audio = AudioSegment.from_mp3(buffer)
|
|
81
|
+
audio = audio.set_frame_rate(16000).set_channels(1).set_sample_width(2) # 2 bytes = 16 bits
|
|
82
|
+
pcm_data = audio.raw_data
|
|
83
|
+
|
|
84
|
+
chunk_size = 960 * 2
|
|
85
|
+
for i in range(0, len(pcm_data), chunk_size):
|
|
86
|
+
chunk = pcm_data[i : i + chunk_size]
|
|
87
|
+
|
|
88
|
+
if chunk: # 确保不添加空块
|
|
89
|
+
chunk = np.frombuffer(chunk, dtype=np.int16)
|
|
90
|
+
pcm_list.extend(chunk)
|
|
91
|
+
|
|
92
|
+
return pcm_list, False
|
|
@@ -8,18 +8,19 @@ file/opus/linux-x64-libopus.so,sha256=FmXJqkxLpDzNFOHYkmOzmsp1hP0eIS5b6x_XfOs-IQ
|
|
|
8
8
|
file/opus/macos-arm64-libopus.dylib,sha256=H7wXwkrGwb-hesMMZGFxWb0Ri1Y4m5GWiKsd8CfOhE8,357584
|
|
9
9
|
file/opus/macos-x64-libopus.dylib,sha256=MqyL_OjwSACF4Xs_-KrGbcScy4IEprr5Rlkk3ddZye8,550856
|
|
10
10
|
file/opus/windows-opus.dll,sha256=kLfhioMvbJhOgNMAldpWk3DCZqC5Xd70LRbHnACvAnw,463360
|
|
11
|
-
xiaozhi_sdk/__init__.py,sha256=
|
|
11
|
+
xiaozhi_sdk/__init__.py,sha256=vnorG8iuWFOKelF22ZlRtQ154kXla-XOnvzebaHa5j8,77
|
|
12
12
|
xiaozhi_sdk/__main__.py,sha256=i0ZJdHUqAKg9vwZrK_w0TJkzdotTYTK8aUeSPcJc1ks,210
|
|
13
13
|
xiaozhi_sdk/cli.py,sha256=CEpjXW-vnT8DieaK1MV0AYCG4bmK2_vpIz-9zhWHVgE,7001
|
|
14
14
|
xiaozhi_sdk/config.py,sha256=h4mpMeBf2vT9qYAqCCbGVGmMemkgk98pcXP2Rh4TEFc,89
|
|
15
|
-
xiaozhi_sdk/core.py,sha256=
|
|
15
|
+
xiaozhi_sdk/core.py,sha256=_HT4zWdAkzvQKrgnlyL5b6fIg9PXULnkuE2LReoZOZM,10265
|
|
16
16
|
xiaozhi_sdk/iot.py,sha256=VVAheynp1iV4GCaoPywQWpKtlyoACDLswH8yfV_JZgI,2699
|
|
17
|
-
xiaozhi_sdk/mcp.py,sha256=
|
|
17
|
+
xiaozhi_sdk/mcp.py,sha256=TVSHrWIy7qDy8DQOjQ9EAzVZ1SFtXkSgTDYysL-dRhk,6559
|
|
18
18
|
xiaozhi_sdk/opus.py,sha256=r3nnYg0ZKAJTreb_3nKgfHJh06MJiMvnNMPO1SWdoMM,2224
|
|
19
19
|
xiaozhi_sdk/utils/__init__.py,sha256=XKSHWoFmuSkpwaIr308HybRzfFIXoT1Fd-eUKo_im6Y,1705
|
|
20
|
-
xiaozhi_sdk/utils/mcp_tool.py,sha256=
|
|
21
|
-
xiaozhi_sdk
|
|
22
|
-
xiaozhi_sdk-0.2.
|
|
23
|
-
xiaozhi_sdk-0.2.
|
|
24
|
-
xiaozhi_sdk-0.2.
|
|
25
|
-
xiaozhi_sdk-0.2.
|
|
20
|
+
xiaozhi_sdk/utils/mcp_tool.py,sha256=T6OIrSqcyAHQ85sduz5Klx646SoEnGD5ROBTKoX6NhE,4207
|
|
21
|
+
xiaozhi_sdk/utils/tool_func.py,sha256=imwehfUlENjelYmGbGYgb6C82-ijs53XCxrtCpqrJps,3152
|
|
22
|
+
xiaozhi_sdk-0.2.2.dist-info/licenses/LICENSE,sha256=Vwgps1iODKl43cAtME_0dawTjAzNW-O2BWiN5BHggww,1085
|
|
23
|
+
xiaozhi_sdk-0.2.2.dist-info/METADATA,sha256=azrkbi4i84qSDRxSBTDUC4CQg5mGbvbz_f_3__TRKHk,2121
|
|
24
|
+
xiaozhi_sdk-0.2.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
25
|
+
xiaozhi_sdk-0.2.2.dist-info/top_level.txt,sha256=nBpue4hU5Ykm5CtYPsAdxSa_yqbtZsIT_gF_EkBaJPM,12
|
|
26
|
+
xiaozhi_sdk-0.2.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|