Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/chen66663_-rzg2l-.json
T

1 line
24 KiB
JSON

{"repo": "chen66663/-rzg2l-", "files": {"xfyun_voiceprint/xfyun_voiceprint_tool.py": {"cd224005ba03a02a5d3bd7a9aeda1637808c69f4": {"t": 1785674326.7252104, "n": 6912, "h": "6ead8536a1422187", "c": "#!/usr/bin/env python3\n\"\"\"讯飞开放平台声纹识别命令行工具。\"\"\"\n\nfrom __future__ import annotations\n\nimport argparse\nimport os\nimport subprocess\nimport sys\n\nfrom xfyun_voiceprint import (\n DEFAULT_GROUP_ID,\n DEFAULT_THRESHOLD,\n create_feature,\n create_group,\n compare_feature,\n load_credentials,\n prepare_audio,\n query_features,\n score_passed,\n)\n\n\ndef add_credential_args(parser: argparse.ArgumentParser) -> None:\n \"\"\"添加讯飞鉴权参数。\"\"\"\n parser.add_argument(\n \"--credentials\",\n default=argparse.SUPPRESS, # 不传时由 load_credentials 默认读 xfyun_credentials.json\n help=\"JSON file with app_id, api_key and api_secret\",\n )\n parser.add_argument(\"--app-id\", default=argparse.SUPPRESS, help=\"XFYUN APPID\") # 也可以命令行直接传 APPID\n parser.add_argument(\"--api-key\", default=argparse.SUPPRESS, help=\"XFYUN APIKey\") # 也可以命令行直接传 APIKey\n parser.add_argument(\"--api-secret\", default=argparse.SUPPRESS, help=\"XFYUN APISecret\") # 也可以命令行直接传 APISecret\n\n\ndef add_group_arg(parser: argparse.ArgumentParser) -> None:\n \"\"\"添加 group id 参数。\"\"\"\n parser.add_argument(\n \"--group-id\",\n default=os.getenv(\"XFYUN_GROUP_ID\", DEFAULT_GROUP_ID), # 支持用环境变量切换默认特征库\n help=f\"Voiceprint group id (default: {DEFAULT_GROUP_ID})\",\n )\n\n\ndef parse_args() -> argparse.Namespace:\n \"\"\"解析命令行参数。\"\"\"\n credential_parser = argparse.ArgumentParser(add_help=False) # 子命令共享同一套鉴权参数\n add_credential_args(credential_parser) # --credentials/--app-id/--api-key/--api-secret\n\n parser = argparse.ArgumentParser(\n description=\"iFLYTEK voiceprint create-group/enroll/verify tool\",\n parents=[credential_parser], # 允许凭据参数放在子命令前\n )\n subparsers = parser.add_subparsers(dest=\"command\", required=True) # 必须指定 prepare/create-group/enroll/verify/list\n\n prepare_parser = subparsers.add_parser(\"prepare\", help=\"Convert audio to 16 kHz 16-bit mono PCM\")\n prepare_parser.add_argument(\"--file\", required=True, help=\"Input audio file\") # 输入 m4a/wav/mp3/pcm 等\n prepare_parser.add_argument(\"--output\", default=None, help=\"Output PCM path\") # 不传则生成 <name>_xfyun.pcm\n\n group_parser = subparsers.add_parser(\n \"create-group\",\n parents=[credential_parser],\n help=\"Create a voiceprint feature group\",\n )\n add_group_arg(group_parser) # --group-id 默认 voice_test\n group_parser.add_argument(\"--group-name\", default=None, help=\"Optional group name\") # 人类可读库名\n group_parser.add_argument(\"--group-info\", default=None, help=\"Optional group description\") # 特征库备注\n\n enroll_parser = subparsers.add_parser(\n \"enroll\",\n parents=[credential_parser],\n help=\"Register an audio sample as a voiceprint feature\",\n )\n add_group_arg(enroll_parser) # 注册到哪个特征库\n enroll_parser.add_argument(\"--file\", required=True, help=\"Input audio file; converted to PCM if needed\") # 注册音频\n enroll_parser.add_argument(\"--feature-id\", required=True, help=\"Feature id, 1-32 letters/digits/underscores\") # 人员 ID\n enroll_parser.add_argument(\"--feature-info\", default=None, help=\"Optional feature description\") # 注册备注\n enroll_parser.add_argument(\n \"--keep-converted\",\n action=\"store_true\", # 保留转换后的 PCM,便于排查和复用\n help=\"Keep converted <name>_xfyun.pcm next to source audio\",\n )\n\n verify_parser = subparsers.add_parser(\n \"verify\",\n parents=[credential_parser],\n help=\"Compare an audio sample against an enrolled feature\",\n )\n add_group_arg(verify_parser) # 从哪个特征库里找目标人\n verify_parser.add_argument(\"--file\", required=True, help=\"Input audio file; converted to PCM if needed\") # 待认证音频\n verify_parser.add_argument(\"--feature-id\", required=True, help=\"Feature id to compare against\") # 和哪个已注册人比\n verify_parser.add_argument(\n \"--threshold\",\n type=float,\n default=DEFAULT_THRESHOLD, # 默认 0.6,可以用 0.75/0.8 提高严格度\n help=f\"Pass threshold (default: {DEFAULT_THRESHOLD})\",\n )\n verify_parser.add_argument(\n \"--keep-converted\",\n action=\"store_true\", # 保留转换后的 PCM,便于排查和复用\n help=\"Keep converted <name>_xfyun.pcm next to source audio\",\n )\n\n list_parser = subparsers.add_parser(\n \"list\",\n parents=[credential_parser],\n help=\"List voiceprint features in a group\",\n )\n add_group_arg(list_parser) # 查询指定特征库里的 featureId 列表\n\n return parser.parse_args()\n\n\ndef print_jsonish(title: str, data: object) -> None:\n \"\"\"简单打印业务结果。\"\"\"\n print(f\"=== {title} ===\")\n print(data)\n\n\ndef main() -> int:\n \"\"\"运行讯飞声纹工具。\"\"\"\n args = parse_args()\n try:\n if args.command == \"prepare\":\n output = prepare_audio(args.file, args.output) # 只转音频,不请求讯飞\n print(f\"Prepared: {output}\")\n return 0\n\n creds = load_credentials(args) # 读取讯飞 APPID/APIKey/APISecret\n if args.command == \"create-group\":\n result = create_group(creds, args.group_id, args.group_name, args.group_info) # 创建特征库\n print_jsonish(\"XFYUN Create Group\", result)\n elif args.command == \"enroll\":\n result = create_feature(\n creds, # 讯飞凭据\n args.group_id, # 特征库 ID\n args.feature_id, # 注册成哪个人\n args.feature_info, # 备注信息\n args.file, # 注册音频路径\n keep_converted=args.keep_converted, # 是否保留 PCM\n ) # 调 createFeature\n print_jsonish(\"XFYUN Enroll\", result)\n elif args.command == \"verify\":\n result = compare_feature(\n creds, # 讯飞凭据\n args.group_id, # 特征库 ID\n args.feature_id, # 目标已注册声纹\n args.file, # 待认证音频路径\n keep_converted=args.keep_converted, # 是否保留 PCM\n ) # 调 searchScoreFea 做 1:1 比对\n score = result.get(\"score\") if isinstance(result, dict) else None # 讯飞返回的相似度\n passed = score_passed(score, args.threshold) # 按阈值转成 PASS/FAIL\n print(\"=== XFYUN Verify ===\")\n print(f\"FeatureId: {args.feature_id}\")\n print(f\"Score: {score}\")\n print(f\"Threshold: {args.threshold}\")\n print(f\"Decision: {'PASS' if passed else 'FAIL'}\")\n print(f\"Raw: {result}\")\n elif args.command == \"list\":\n result = query_features(creds, args.group_id) # 查询库内已注册 featureId\n print_jsonish(\"XFYUN Feature List\", result)\n else:\n raise RuntimeError(f\"Unknown command: {args.command}\")\n except subprocess.CalledProcessError as exc: # type: ignore[name-defined]\n print(f\"ffmpeg failed with exit code {exc.returncode}\", file=sys.stderr)\n return 1\n except KeyboardInterrupt:\n print(\"Interrupted by user.\", file=sys.stderr)\n return 130\n except (OSError, ValueError, RuntimeError) as exc:\n print(str(exc), file=sys.stderr)\n return 1\n return 0\n\n\nif __name__ == \"__main__\":\n raise SystemExit(main())\n"}}, "xfyun_voiceprint/xfyun_voiceprint.py": {"cd224005ba03a02a5d3bd7a9aeda1637808c69f4": {"t": 1785674328.8121362, "n": 13132, "h": "5d55ca6d0ce687a1", "c": "\"\"\"讯飞开放平台声纹识别 HTTP API 工具函数。\"\"\"\n\nfrom __future__ import annotations\n\nimport argparse\nimport base64\nimport datetime as dt\nimport hashlib\nimport hmac\nimport json\nimport os\nimport re\nimport subprocess\nimport tempfile\nimport time\nimport urllib.error\nimport urllib.parse\nimport urllib.request\nfrom dataclasses import dataclass\nfrom email.utils import formatdate\nfrom pathlib import Path\nfrom typing import Any\n\n\nHOST = \"api.xf-yun.com\" # 讯飞新版声纹 API 域名\nPATH = \"/v1/private/s1aa729d0\" # 当前开通的声纹服务路径\nENDPOINT = f\"https://{HOST}{PATH}\" # 最终 HTTPS 请求地址\nSERVICE = \"s1aa729d0\" # 请求 parameter 里的服务名,必须和 PATH 对应\nDEFAULT_GROUP_ID = \"voice_test\" # 默认声纹特征库 ID\nDEFAULT_THRESHOLD = 0.6 # 官方 FAQ 建议 0.6 以上可作为通过参考\nDEFAULT_TIMEOUT = 60 # 每次 HTTP 请求最多等待 60 秒\nMAX_BASE64_AUDIO = 4 * 1024 * 1024 # 讯飞 payload.resource.audio 的 base64 大小上限\nID_PATTERN = re.compile(r\"^[A-Za-z0-9_]{1,32}$\") # groupId/featureId 只允许字母数字下划线\n\n\n@dataclass(frozen=True)\nclass XfyunCredentials:\n \"\"\"讯飞开放平台鉴权信息。\"\"\"\n\n app_id: str\n api_key: str\n api_secret: str\n\n\ndef json_dumps(data: Any) -> str:\n \"\"\"生成紧凑且稳定的 JSON 字符串。\"\"\"\n return json.dumps(data, ensure_ascii=False, separators=(\",\", \":\"), sort_keys=True)\n\n\ndef load_credentials(args: argparse.Namespace) -> XfyunCredentials:\n \"\"\"从命令行、环境变量或 JSON 文件读取讯飞密钥。\"\"\"\n app_id = (getattr(args, \"app_id\", None) or os.getenv(\"XFYUN_APP_ID\", \"\")).strip() # 优先命令行,其次环境变量\n api_key = (getattr(args, \"api_key\", None) or os.getenv(\"XFYUN_API_KEY\", \"\")).strip() # APIKey 用于 authorization\n api_secret = (getattr(args, \"api_secret\", None) or os.getenv(\"XFYUN_API_SECRET\", \"\")).strip() # APISecret 用于 HMAC 签名\n\n credentials_path = Path(getattr(args, \"credentials\", None) or \"xfyun_credentials.json\") # 默认读取本地凭据文件\n if credentials_path.is_file():\n with credentials_path.open(\"r\", encoding=\"utf-8\") as file_obj:\n data = json.load(file_obj) # 凭据文件只放本地,不能提交到 GitHub\n app_id = app_id or str(data.get(\"app_id\", \"\")).strip() # 命令行/环境变量为空时才用文件值\n api_key = api_key or str(data.get(\"api_key\", \"\")).strip() # 避免文件覆盖显式传入的值\n api_secret = api_secret or str(data.get(\"api_secret\", \"\")).strip() # 避免文件覆盖显式传入的值\n\n if not app_id or not api_key or not api_secret:\n raise RuntimeError(\n \"Missing XFYUN credentials. Set XFYUN_APP_ID/XFYUN_API_KEY/\"\n \"XFYUN_API_SECRET, pass --app-id/--api-key/--api-secret, \"\n \"or create xfyun_credentials.json.\"\n )\n\n return XfyunCredentials(app_id=app_id, api_key=api_key, api_secret=api_secret)\n\n\ndef validate_id(name: str, value: str) -> None:\n \"\"\"校验讯飞 groupId/featureId。\"\"\"\n if not ID_PATTERN.fullmatch(value):\n raise ValueError(f\"{name} must use 1-32 letters, digits or underscores.\")\n\n\ndef build_auth_url(creds: XfyunCredentials) -> str:\n \"\"\"按讯飞 HMAC-SHA256 规则生成鉴权 URL。\"\"\"\n date_text = formatdate(timeval=None, localtime=False, usegmt=True) # HTTP GMT 时间,和服务端偏差不能超过 300 秒\n signature_origin = f\"host: {HOST}\\ndate: {date_text}\\nPOST {PATH} HTTP/1.1\" # 讯飞要求参与签名的原始字符串\n signature_sha = hmac.new(\n creds.api_secret.encode(\"utf-8\"), # HMAC key 是 APISecret\n signature_origin.encode(\"utf-8\"), # HMAC message 是 host/date/request-line\n hashlib.sha256,\n ).digest() # 得到二进制签名\n signature = base64.b64encode(signature_sha).decode(\"ascii\") # 讯飞要求签名再 base64\n authorization_origin = (\n f'api_key=\"{creds.api_key}\", algorithm=\"hmac-sha256\", '\n f'headers=\"host date request-line\", signature=\"{signature}\"'\n ) # authorization 原文,字段顺序必须符合讯飞文档\n authorization = base64.b64encode(authorization_origin.encode(\"utf-8\")).decode(\"ascii\") # URL 参数里放 base64 后的 authorization\n query = urllib.parse.urlencode(\n {\n \"authorization\": authorization, # 鉴权信息\n \"host\": HOST, # 参与签名的 host\n \"date\": date_text, # 参与签名的 GMT 时间\n }\n )\n return f\"{ENDPOINT}?{query}\" # 讯飞鉴权参数放在 query string 里\n\n\ndef decode_result_text(response: dict[str, Any], result_key: str) -> Any:\n \"\"\"解码 payload.<result_key>.text 里的 base64 JSON。\"\"\"\n payload = response.get(\"payload\") # 讯飞业务结果放在 payload.<xxx>.text\n if not isinstance(payload, dict):\n return {}\n result = payload.get(result_key) # result_key 例如 createFeatureRes/searchScoreFeaRes\n if not isinstance(result, dict):\n return {}\n text = result.get(\"text\") # text 是 base64 后的 JSON 字符串\n if not isinstance(text, str) or not text:\n return {}\n raw = base64.b64decode(text).decode(\"utf-8\") # 先 base64 解码,再按 UTF-8 解析 JSON\n try:\n return json.loads(raw)\n except json.JSONDecodeError:\n return raw\n\n\ndef call_xfyun(payload: dict[str, Any], result_key: str, creds: XfyunCredentials) -> Any:\n \"\"\"调用一次讯飞声纹 HTTP API,并返回 text 解码后的业务结果。\"\"\"\n body = json_dumps(payload).encode(\"utf-8\") # 请求体必须和签名 URL 同一次生成\n request = urllib.request.Request(\n build_auth_url(creds), # 带 authorization/date/host 的完整 URL\n data=body, # HTTP body 是 JSON bytes\n headers={\"Content-Type\": \"application/json\"}, # 讯飞接口使用 JSON\n method=\"POST\", # 新版声纹接口固定 POST\n )\n\n try:\n with urllib.request.urlopen(request, timeout=DEFAULT_TIMEOUT) as response:\n raw_text = response.read().decode(\"utf-8\") # 讯飞返回 UTF-8 JSON\n except urllib.error.HTTPError as exc:\n detail = exc.read().decode(\"utf-8\", errors=\"replace\")\n raise RuntimeError(f\"HTTP {exc.code} calling XFYUN voiceprint: {detail}\") from exc\n except urllib.error.URLError as exc:\n raise RuntimeError(f\"Network error calling XFYUN voiceprint: {exc}\") from exc\n\n try:\n data = json.loads(raw_text) # 顶层响应包含 header 和 payload\n except json.JSONDecodeError as exc:\n raise RuntimeError(f\"Invalid JSON from XFYUN voiceprint: {raw_text}\") from exc\n\n header = data.get(\"header\") # header.code 是接口级错误码\n if not isinstance(header, dict):\n raise RuntimeError(f\"Unexpected XFYUN response: {raw_text}\")\n code = int(header.get(\"code\", -1))\n if code != 0:\n message = header.get(\"message\", \"No error message returned.\")\n sid = header.get(\"sid\", \"\")\n raise RuntimeError(f\"XFYUN voiceprint failed: code={code}, sid={sid}, message={message}\")\n\n result = decode_result_text(data, result_key) # 业务结果需要从 payload.<result_key>.text 解码\n if isinstance(result, dict) and result.get(\"code\") not in (None, 0, \"0\"):\n raise RuntimeError(f\"XFYUN voiceprint business error: {result}\")\n return result\n\n\ndef run_ffmpeg(input_path: Path, output_path: Path) -> None:\n \"\"\"把任意输入音频转成讯飞新版声纹要求的 16k/16bit/单声道 PCM。\"\"\"\n ffmpeg_command = [\n \"ffmpeg\",\n \"-hide_banner\",\n \"-loglevel\",\n \"error\",\n \"-y\",\n \"-i\",\n str(input_path),\n \"-vn\",\n \"-ac\",\n \"1\", # 单声道\n \"-ar\",\n \"16000\", # 16 kHz 采样率\n \"-f\",\n \"s16le\", # 裸 PCM little-endian 格式\n \"-acodec\",\n \"pcm_s16le\", # 16 bit PCM 编码\n str(output_path),\n ]\n try:\n subprocess.run(ffmpeg_command, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)\n except FileNotFoundError as exc:\n raise RuntimeError(\n \"ffmpeg is required to convert audio for XFYUN voiceprint. \"\n \"Install ffmpeg or pass an already converted 16 kHz mono PCM file.\"\n ) from exc\n\n\ndef prepare_audio(input_path: str, output_path: str | None = None) -> Path:\n \"\"\"生成可上传给讯飞新版声纹接口的 PCM 文件。\"\"\"\n source = Path(input_path) # 支持 m4a/wav/mp3/pcm 等本地文件路径\n if not source.is_file():\n raise FileNotFoundError(f\"Audio file not found: {source}\")\n\n if output_path:\n target = Path(output_path) # 用户指定输出路径\n else:\n target = source.with_name(f\"{source.stem}_xfyun.pcm\") # 默认生成同目录 <name>_xfyun.pcm\n target.parent.mkdir(parents=True, exist_ok=True) # 输出目录不存在时自动创建\n run_ffmpeg(source, target) # 调 ffmpeg 转成讯飞要求的 PCM\n return target\n\n\ndef read_audio_for_upload(input_path: str, keep_converted: bool = False) -> bytes:\n \"\"\"读取音频;必要时先临时转换成 16k/16bit/单声道 PCM。\"\"\"\n source = Path(input_path) # 输入可以是已转换 PCM,也可以是普通音频\n if not source.is_file():\n raise FileNotFoundError(f\"Audio file not found: {source}\")\n\n suffix = source.suffix.lower() # 根据后缀判断是否需要转码\n if suffix in (\".pcm\", \".raw\"):\n audio_bytes = source.read_bytes() # 已经是裸 PCM,直接读取\n elif keep_converted:\n converted = prepare_audio(str(source)) # 保留转换后的 <name>_xfyun.pcm,方便下次复用\n audio_bytes = converted.read_bytes() # 读取转换后的 PCM\n else:\n with tempfile.NamedTemporaryFile(suffix=\".pcm\", delete=False) as tmp:\n converted_path = Path(tmp.name) # 默认用临时 PCM,避免污染工作目录\n try:\n run_ffmpeg(source, converted_path) # 临时转换成 16k/16bit/mono PCM\n audio_bytes = converted_path.read_bytes() # 读取准备上传的 PCM bytes\n finally:\n converted_path.unlink(missing_ok=True) # 不保留临时音频\n\n encoded_size = len(base64.b64encode(audio_bytes)) # 讯飞限制的是 base64 后的大小\n if encoded_size > MAX_BASE64_AUDIO:\n raise ValueError(\"Base64 audio is larger than 4 MB; use a shorter clip.\")\n return audio_bytes\n\n\ndef result_format(key: str) -> dict[str, str]:\n \"\"\"生成讯飞结果格式配置。\"\"\"\n return {\n key: {\n \"encoding\": \"utf8\", # 返回文本按 UTF-8 编码\n \"compress\": \"raw\", # 不压缩\n \"format\": \"json\", # 返回 JSON\n }\n }\n\n\ndef base_payload(creds: XfyunCredentials, params: dict[str, Any]) -> dict[str, Any]:\n \"\"\"生成无音频请求体。\"\"\"\n return {\n \"header\": {\n \"app_id\": creds.app_id, # 讯飞 APPID\n \"status\": 3, # 单次完整请求,固定传 3\n },\n \"parameter\": {\n SERVICE: params, # s1aa729d0 服务参数\n },\n }\n\n\ndef audio_payload(creds: XfyunCredentials, params: dict[str, Any], audio_bytes: bytes) -> dict[str, Any]:\n \"\"\"生成带音频请求体。\"\"\"\n payload = base_payload(creds, params) # 先生成 header/parameter\n payload[\"payload\"] = {\n \"resource\": {\n \"encoding\": \"raw\", # 上传裸 PCM,不是 mp3/lame\n \"sample_rate\": 16000, # 16 kHz\n \"channels\": 1, # 单声道\n \"bit_depth\": 16, # 16 bit\n \"status\": 3, # 单包完整音频\n \"audio\": base64.b64encode(audio_bytes).decode(\"ascii\"), # PCM bytes 转 base64\n }\n }\n return payload\n\n\ndef create_group(creds: XfyunCredentials, group_id: str, group_name: str | None, group_info: str | None) -> Any:\n \"\"\"创建声纹特征库。\"\"\"\n validate_id(\"--group-id\", group_id) # 先本地校验,避免请求到云端才失败\n params: dict[str, Any] = {\n \"func\": \"createGroup\", # 创建声纹特征库\n \"groupId\": group_id, # 特征库 ID\n **result_format(\"createGroupRes\"), # 告诉讯飞返回 createGroupRes\n }\n if group_name:\n params[\"groupName\"] = group_name\n if group_info:\n params[\"groupInfo\"] = group_info\n return call_xfyun(base_payload(creds, params), \"createGroupRes\", creds) # 创建库不需要上传音频\n\n\ndef create_feature(\n creds: XfyunCredentials,\n group_id: str,\n feature_id: str,\n feature_info: str | None,\n audio_file: str,\n keep_converted: bool = False,\n) -> Any:\n \"\"\"添加音频特征。\"\"\"\n validate_id(\"--group-id\", group_id) # 特征库 ID\n validate_id(\"--feature-id\", feature_id) # 人员/声纹 ID\n info = feature_info or f\"{feature_id}_{dt.datetime.now().strftime('%Y%m%d%H%M%S')}\" # 默认备注带注册时间\n params = {\n \"func\": \"createFeature\", # 注册声纹特征\n \"groupId\": group_id, # 注册到哪个特征库\n \"featureId\": feature_id, # 注册成哪个人\n \"featureInfo\": info, # 备注信息,后面 list 会显示\n **result_format(\"createFeatureRes\"), # 告诉讯飞返回 createFeatureRes\n }\n audio_bytes = read_audio_for_upload(audio_file, keep_converted=keep_converted) # 准备 16k/16bit/mono PCM\n return call_xfyun(audio_payload(creds, params, audio_bytes), \"createFeatureRes\", creds) # 上传音频并注册\n\n\ndef compare_feature(\n creds: XfyunCredentials,\n group_id: str,\n feature_id: str,\n audio_file: str,\n keep_converted: bool = False,\n) -> Any:\n \"\"\"用输入音频和库中指定特征做 1:1 比对。\"\"\"\n validate_id(\"--group-id\", group_id) # 特征库 ID\n validate_id(\"--feature-id\", feature_id) # 目标已注册声纹 ID\n params = {\n \"func\": \"searchScoreFea\", # 1:1 声纹比对\n \"groupId\": group_id, # 从哪个特征库查\n \"dstFeatureId\": feature_id, # 和哪个已注册人比\n **result_format(\"searchScoreFeaRes\"), # 告诉讯飞返回 searchScoreFeaRes\n }\n audio_bytes = read_audio_for_upload(audio_file, keep_converted=keep_converted) # 准备待比对音频\n return call_xfyun(audio_payload(creds, params, audio_bytes), \"searchScoreFeaRes\", creds) # 返回 score/age/gender 等结果\n\n\ndef query_features(creds: XfyunCredentials, group_id: str) -> Any:\n \"\"\"查询特征列表。\"\"\"\n validate_id(\"--group-id\", group_id) # 特征库 ID\n params = {\n \"func\": \"queryFeatureList\", # 查询库内已注册声纹\n \"groupId\": group_id, # 要查询的特征库\n **result_format(\"queryFeatureListRes\"), # 告诉讯飞返回 queryFeatureListRes\n }\n return call_xfyun(base_payload(creds, params), \"queryFeatureListRes\", creds) # 查询列表不需要上传音频\n\n\ndef score_passed(score: Any, threshold: float = DEFAULT_THRESHOLD) -> bool:\n \"\"\"根据相似度分数判断是否通过。\"\"\"\n try:\n return float(score) >= threshold # score 大于等于阈值就判定通过\n except (TypeError, ValueError):\n return False\n"}}}}