Files
xiaozhi-esp32-server/main/xiaozhi-server/core/utils/util.py
T

333 lines
11 KiB
Python
Raw Normal View History

2025-02-02 23:01:14 +08:00
import json
2025-02-14 00:54:59 +08:00
import socket
2025-03-01 17:09:01 +08:00
import subprocess
2025-03-11 00:25:33 +08:00
import re
2025-03-17 14:20:40 +08:00
import requests
2025-04-12 17:36:04 +08:00
from typing import Dict, Any
from core.utils import tts, llm, intent, memory, vad, asr
2025-02-02 23:01:14 +08:00
2025-04-12 17:36:04 +08:00
TAG = __name__
2025-02-02 23:01:14 +08:00
def get_local_ip():
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# Connect to Google's DNS servers
s.connect(("8.8.8.8", 80))
local_ip = s.getsockname()[0]
s.close()
return local_ip
except Exception as e:
return "127.0.0.1"
2025-04-05 17:16:06 +08:00
2025-03-17 14:20:40 +08:00
def is_private_ip(ip_addr):
"""
Check if an IP address is a private IP address (compatible with IPv4 and IPv6).
@param {string} ip_addr - The IP address to check.
@return {bool} True if the IP address is private, False otherwise.
"""
try:
# Validate IPv4 or IPv6 address format
2025-04-05 17:16:06 +08:00
if not re.match(
r"^(\d{1,3}\.){3}\d{1,3}$|^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$", ip_addr
):
2025-03-17 14:20:40 +08:00
return False # Invalid IP address format
# IPv4 private address ranges
2025-04-05 17:16:06 +08:00
if "." in ip_addr: # IPv4 address
ip_parts = list(map(int, ip_addr.split(".")))
2025-03-17 14:20:40 +08:00
if ip_parts[0] == 10:
return True # 10.0.0.0/8 range
elif ip_parts[0] == 172 and 16 <= ip_parts[1] <= 31:
return True # 172.16.0.0/12 range
elif ip_parts[0] == 192 and ip_parts[1] == 168:
return True # 192.168.0.0/16 range
2025-04-05 17:16:06 +08:00
elif ip_addr == "127.0.0.1":
2025-03-17 14:20:40 +08:00
return True # Loopback address
elif ip_parts[0] == 169 and ip_parts[1] == 254:
2025-04-05 17:16:06 +08:00
return True # Link-local address 169.254.0.0/16
2025-03-17 14:20:40 +08:00
else:
return False # Not a private IPv4 address
else: # IPv6 address
ip_addr = ip_addr.lower()
2025-04-05 17:16:06 +08:00
if ip_addr.startswith("fc00:") or ip_addr.startswith("fd00:"):
2025-03-17 14:20:40 +08:00
return True # Unique Local Addresses (FC00::/7)
2025-04-05 17:16:06 +08:00
elif ip_addr == "::1":
2025-03-17 14:20:40 +08:00
return True # Loopback address
2025-04-05 17:16:06 +08:00
elif ip_addr.startswith("fe80:"):
return True # Link-local unicast addresses (FE80::/10)
2025-03-17 14:20:40 +08:00
else:
return False # Not a private IPv6 address
except (ValueError, IndexError):
return False # IP address format error or insufficient segments
2025-04-05 17:16:06 +08:00
2025-04-12 17:36:04 +08:00
def get_ip_info(ip_addr, logger):
2025-03-17 14:20:40 +08:00
try:
2025-04-05 17:16:06 +08:00
if is_private_ip(ip_addr):
ip_addr = ""
2025-04-01 17:27:58 +08:00
url = f"https://whois.pconline.com.cn/ipJson.jsp?json=true&ip={ip_addr}"
2025-03-17 14:20:40 +08:00
resp = requests.get(url).json()
2025-04-05 17:16:06 +08:00
ip_info = {"city": resp.get("city")}
2025-03-17 14:20:40 +08:00
return ip_info
except Exception as e:
2025-04-12 17:36:04 +08:00
logger.bind(tag=TAG).error(f"Error getting client ip info: {e}")
2025-03-17 14:20:40 +08:00
return {}
2025-02-02 23:01:14 +08:00
def write_json_file(file_path, data):
"""将数据写入 JSON 文件"""
2025-04-05 17:16:06 +08:00
with open(file_path, "w", encoding="utf-8") as file:
2025-02-02 23:01:14 +08:00
json.dump(data, file, ensure_ascii=False, indent=4)
def is_punctuation_or_emoji(char):
"""检查字符是否为空格、指定标点或表情符号"""
# 定义需要去除的中英文标点(包括全角/半角)
punctuation_set = {
2025-04-05 17:16:06 +08:00
"",
",", # 中文逗号 + 英文逗号
"。",
".", # 中文句号 + 英文句号
"",
"!", # 中文感叹号 + 英文感叹号
"-",
"", # 英文连字符 + 中文全角横线
"、", # 中文顿号
2025-02-02 23:01:14 +08:00
}
if char.isspace() or char in punctuation_set:
return True
# 检查表情符号(保留原有逻辑)
code_point = ord(char)
emoji_ranges = [
2025-04-05 17:16:06 +08:00
(0x1F600, 0x1F64F),
(0x1F300, 0x1F5FF),
(0x1F680, 0x1F6FF),
(0x1F900, 0x1F9FF),
(0x1FA70, 0x1FAFF),
(0x2600, 0x26FF),
(0x2700, 0x27BF),
2025-02-02 23:01:14 +08:00
]
return any(start <= code_point <= end for start, end in emoji_ranges)
2025-02-14 00:54:59 +08:00
2025-02-02 23:01:14 +08:00
def get_string_no_punctuation_or_emoji(s):
"""去除字符串首尾的空格、标点符号和表情符号"""
chars = list(s)
# 处理开头的字符
start = 0
while start < len(chars) and is_punctuation_or_emoji(chars[start]):
start += 1
# 处理结尾的字符
end = len(chars) - 1
while end >= start and is_punctuation_or_emoji(chars[end]):
end -= 1
2025-04-05 17:16:06 +08:00
return "".join(chars[start : end + 1])
2025-02-14 00:54:59 +08:00
2025-02-02 23:01:14 +08:00
def remove_punctuation_and_length(text):
# 全角符号和半角符号的Unicode范围
2025-04-05 17:16:06 +08:00
full_width_punctuations = (
"!"#$%&'()*+,-。/:;<=>?@[\]^_`{|}~"
)
2025-03-09 01:02:37 +08:00
half_width_punctuations = r'!"#$%&\'()*+,-./:;<=>?@[\]^_`{|}~'
2025-04-05 17:16:06 +08:00
space = " " # 半角空格
full_width_space = " " # 全角空格
2025-02-02 23:01:14 +08:00
# 去除全角和半角符号以及空格
2025-04-05 17:16:06 +08:00
result = "".join(
[
char
for char in text
if char not in full_width_punctuations
and char not in half_width_punctuations
and char not in space
and char not in full_width_space
]
)
2025-02-02 23:01:14 +08:00
if result == "Yeah":
return 0, ""
2025-02-14 23:09:12 +08:00
return len(result), result
2025-02-15 16:17:08 +08:00
2025-04-05 17:16:06 +08:00
+2
2025-03-07 18:25:18 +08:00
def check_model_key(modelType, modelKey):
if "你" in modelKey:
2025-04-12 17:36:04 +08:00
raise ValueError(
"你还没配置" + modelType + "的密钥,请检查一下所使用的LLM是否配置了密钥"
2025-04-05 17:16:06 +08:00
)
2025-02-15 16:17:08 +08:00
return False
return True
2025-03-01 17:09:01 +08:00
+2
2025-03-07 18:25:18 +08:00
def parse_string_to_list(value, separator=";"):
"""
将输入值转换为列表
Args:
value: 输入值,可以是 None、字符串或列表
separator: 分隔符,默认为分号
Returns:
list: 处理后的列表
"""
if value is None or value == "":
return []
elif isinstance(value, str):
return [item.strip() for item in value.split(separator) if item.strip()]
elif isinstance(value, list):
return value
return []
2025-03-01 17:09:01 +08:00
def check_ffmpeg_installed():
ffmpeg_installed = False
try:
# 执行ffmpeg -version命令,并捕获输出
result = subprocess.run(
2025-04-05 17:16:06 +08:00
["ffmpeg", "-version"],
2025-03-01 17:09:01 +08:00
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
2025-04-05 17:16:06 +08:00
check=True, # 如果返回码非零则抛出异常
2025-03-01 17:09:01 +08:00
)
# 检查输出中是否包含版本信息(可选)
output = result.stdout + result.stderr
2025-04-05 17:16:06 +08:00
if "ffmpeg version" in output.lower():
2025-03-01 17:09:01 +08:00
ffmpeg_installed = True
return False
except (subprocess.CalledProcessError, FileNotFoundError):
# 命令执行失败或未找到
ffmpeg_installed = False
if not ffmpeg_installed:
error_msg = "您的电脑还没正确安装ffmpeg\n"
error_msg += "\n建议您:\n"
error_msg += "1、按照项目的安装文档,正确进入conda环境\n"
error_msg += "2、查阅安装文档,如何在conda环境中安装ffmpeg\n"
+2
2025-03-07 18:25:18 +08:00
raise ValueError(error_msg)
2025-04-05 17:16:06 +08:00
2025-03-11 00:25:33 +08:00
def extract_json_from_string(input_string):
"""提取字符串中的 JSON 部分"""
2025-04-05 17:16:06 +08:00
pattern = r"(\{.*\})"
2025-03-11 00:25:33 +08:00
match = re.search(pattern, input_string)
if match:
return match.group(1) # 返回提取的 JSON 字符串
2025-04-01 17:27:58 +08:00
return None
2025-04-12 17:36:04 +08:00
def initialize_modules(
logger,
config: Dict[str, Any],
init_vad=False,
init_asr=False,
init_llm=False,
init_tts=False,
init_memory=False,
init_intent=False,
) -> Dict[str, Any]:
"""
初始化所有模块组件
Args:
config: 配置字典
Returns:
Dict[str, Any]: 包含所有初始化后的模块的字典
"""
modules = {}
# 初始化TTS模块
if init_tts:
select_tts_module = config["selected_module"]["TTS"]
2025-04-12 17:36:04 +08:00
tts_type = (
select_tts_module
if "type" not in config["TTS"][select_tts_module]
else config["TTS"][select_tts_module]["type"]
2025-04-12 17:36:04 +08:00
)
modules["tts"] = tts.create_instance(
tts_type,
config["TTS"][select_tts_module],
str(config.get("delete_audio", True)).lower() in ("true", "1", "yes"),
2025-04-12 17:36:04 +08:00
)
logger.bind(tag=TAG).info(f"初始化组件: tts成功 {select_tts_module}")
2025-04-12 17:36:04 +08:00
# 初始化LLM模块
if init_llm:
2025-04-15 22:46:54 +08:00
select_llm_module = config["selected_module"]["LLM"]
2025-04-12 17:36:04 +08:00
llm_type = (
select_llm_module
if "type" not in config["LLM"][select_llm_module]
else config["LLM"][select_llm_module]["type"]
2025-04-12 17:36:04 +08:00
)
modules["llm"] = llm.create_instance(
llm_type,
config["LLM"][select_llm_module],
2025-04-12 17:36:04 +08:00
)
logger.bind(tag=TAG).info(f"初始化组件: llm成功 {select_llm_module}")
2025-04-12 17:36:04 +08:00
# 初始化Intent模块
if init_intent:
select_intent_module = config["selected_module"]["Intent"]
2025-04-12 17:36:04 +08:00
intent_type = (
select_intent_module
if "type" not in config["Intent"][select_intent_module]
else config["Intent"][select_intent_module]["type"]
2025-04-12 17:36:04 +08:00
)
modules["intent"] = intent.create_instance(
intent_type,
config["Intent"][select_intent_module],
2025-04-12 17:36:04 +08:00
)
logger.bind(tag=TAG).info(f"初始化组件: intent成功 {select_intent_module}")
2025-04-12 17:36:04 +08:00
# 初始化Memory模块
if init_memory:
select_memory_module = config["selected_module"]["Memory"]
2025-04-12 17:36:04 +08:00
memory_type = (
select_memory_module
if "type" not in config["Memory"][select_memory_module]
else config["Memory"][select_memory_module]["type"]
2025-04-12 17:36:04 +08:00
)
modules["memory"] = memory.create_instance(
memory_type,
config["Memory"][select_memory_module],
2025-04-12 17:36:04 +08:00
)
logger.bind(tag=TAG).info(f"初始化组件: memory成功 {select_memory_module}")
2025-04-12 17:36:04 +08:00
# 初始化VAD模块
if init_vad:
select_vad_module = config["selected_module"]["VAD"]
2025-04-12 17:36:04 +08:00
vad_type = (
select_vad_module
if "type" not in config["VAD"][select_vad_module]
else config["VAD"][select_vad_module]["type"]
2025-04-12 17:36:04 +08:00
)
modules["vad"] = vad.create_instance(
vad_type,
config["VAD"][select_vad_module],
2025-04-12 17:36:04 +08:00
)
logger.bind(tag=TAG).info(f"初始化组件: vad成功 {select_vad_module}")
2025-04-12 17:36:04 +08:00
# 初始化ASR模块
if init_asr:
select_asr_module = config["selected_module"]["ASR"]
2025-04-12 17:36:04 +08:00
asr_type = (
select_asr_module
if "type" not in config["ASR"][select_asr_module]
else config["ASR"][select_asr_module]["type"]
2025-04-12 17:36:04 +08:00
)
modules["asr"] = asr.create_instance(
asr_type,
config["ASR"][select_asr_module],
str(config.get("delete_audio", True)).lower() in ("true", "1", "yes"),
2025-04-12 17:36:04 +08:00
)
logger.bind(tag=TAG).info(f"初始化组件: asr成功 {select_asr_module}")
# 初始化自定义prompt
2025-04-15 22:46:54 +08:00
if config.get("prompt", None) is not None:
modules["prompt"] = config["prompt"]
2025-04-15 22:46:54 +08:00
logger.bind(tag=TAG).info(f"初始化组件: prompt成功 {modules['prompt'][:50]}...")
2025-04-12 17:36:04 +08:00
return modules