chore(core.provider): 🚨 修正实现错误Lint

This commit is contained in:
Dt8333
2025-10-31 19:13:47 +08:00
parent 918960a7ef
commit 339f9042b7
17 changed files with 99 additions and 54 deletions

View File

@@ -6,6 +6,7 @@ import re
import time
import uuid
from pathlib import Path
from typing import cast
from xml.sax.saxutils import escape
from httpx import AsyncClient, Timeout
@@ -41,7 +42,7 @@ class OTTSProvider:
async def _sync_time(self):
try:
response = await self.client.get(self.auth_time_url)
response = await cast(AsyncClient, self.client).get(self.auth_time_url)
response.raise_for_status()
server_time = int(response.json()["timestamp"])
local_time = int(time.time())
@@ -63,7 +64,7 @@ class OTTSProvider:
signature = await self._generate_signature()
for attempt in range(self.retry_count):
try:
response = await self.client.post(
response = await cast(AsyncClient, self.client).post(
f"{self.api_url}?sign={signature}",
data={
"text": text,
@@ -88,6 +89,7 @@ class OTTSProvider:
if attempt == self.retry_count - 1:
raise RuntimeError(f"OTTS请求失败: {e!s}") from e
await asyncio.sleep(0.5 * (attempt + 1))
raise RuntimeError("OTTS未返回音频文件")
class AzureNativeProvider(TTSProvider):
@@ -132,7 +134,7 @@ class AzureNativeProvider(TTSProvider):
token_url = (
f"https://{self.region}.api.cognitive.microsoft.com/sts/v1.0/issuetoken"
)
response = await self.client.post(
response = await cast(AsyncClient, self.client).post(
token_url,
headers={"Ocp-Apim-Subscription-Key": self.subscription_key},
)
@@ -156,7 +158,7 @@ class AzureNativeProvider(TTSProvider):
</mstts:express-as>
</voice>
</speak>"""
response = await self.client.post(
response = await cast(AsyncClient, self.client).post(
self.endpoint,
content=ssml,
headers={
@@ -179,8 +181,11 @@ class AzureTTSProvider(TTSProvider):
key_value = provider_config.get("azure_tts_subscription_key", "")
self.provider = self._parse_provider(key_value, provider_config)
def _parse_provider(self, key_value: str, config: dict) -> TTSProvider:
def _parse_provider(
self, key_value: str, config: dict
) -> OTTSProvider | AzureNativeProvider:
if key_value.lower().startswith("other["):
json_str = ""
try:
match = re.match(r"other\[(.*)\]", key_value, re.DOTALL)
if not match:

View File

@@ -27,7 +27,7 @@ class ProviderCoze(Provider):
provider_settings,
default_persona,
)
self.api_key = provider_config.get("coze_api_key", "")
self.api_key: str = provider_config.get("coze_api_key", "")
if not self.api_key:
raise Exception("Coze API Key 不能为空。")
self.bot_id = provider_config.get("bot_id", "")
@@ -65,8 +65,8 @@ class ProviderCoze(Provider):
"""
try:
if is_base64 and data.startswith("data:image/"):
header, encoded = data.split(",", 1)
try:
header, encoded = data.split(",", 1)
image_bytes = base64.b64decode(encoded)
cache_key = hashlib.md5(image_bytes).hexdigest()
return cache_key
@@ -579,11 +579,11 @@ class ProviderCoze(Provider):
logger.error(f"清空 Coze 会话失败: {e!s}")
return False
async def get_current_key(self):
def get_current_key(self):
"""获取当前API Key"""
return self.api_key
async def set_key(self, key: str):
def set_key(self, key: str):
"""设置新的API Key"""
raise NotImplementedError("Coze 适配器不支持设置 API Key。")
@@ -595,12 +595,12 @@ class ProviderCoze(Provider):
"""获取当前模型"""
return f"bot_{self.bot_id}"
def set_model(self, model: str):
def set_model(self, model_name: str):
"""设置模型在Coze中是Bot ID"""
if model.startswith("bot_"):
self.bot_id = model[4:]
if model_name.startswith("bot_"):
self.bot_id = model_name[4:]
else:
self.bot_id = model
self.bot_id = model_name
async def get_human_readable_context(
self,

View File

@@ -28,7 +28,7 @@ class ProviderDashscope(ProviderOpenAIOfficial):
provider_settings,
default_persona,
)
self.api_key = provider_config.get("dashscope_api_key", "")
self.api_key: str = provider_config.get("dashscope_api_key", "")
if not self.api_key:
raise Exception("阿里云百炼 API Key 不能为空。")
self.app_id = provider_config.get("dashscope_app_id", "")
@@ -70,6 +70,7 @@ class ProviderDashscope(ProviderOpenAIOfficial):
func_tool=None,
contexts=None,
system_prompt=None,
tool_calls_result=None,
model=None,
**kwargs,
) -> LLMResponse:
@@ -79,6 +80,8 @@ class ProviderDashscope(ProviderOpenAIOfficial):
payload_vars = self.variables.copy()
# 动态变量
session_var = await sp.session_get(session_id, "session_variables", default={})
if not isinstance(session_var, dict):
session_var = {}
payload_vars.update(session_var)
if (
@@ -163,9 +166,9 @@ class ProviderDashscope(ProviderOpenAIOfficial):
self,
prompt,
session_id=None,
image_urls=...,
image_urls=None,
func_tool=None,
contexts=...,
contexts=None,
system_prompt=None,
tool_calls_result=None,
model=None,
@@ -190,10 +193,10 @@ class ProviderDashscope(ProviderOpenAIOfficial):
async def forget(self, session_id):
return True
async def get_current_key(self):
def get_current_key(self):
return self.api_key
async def set_key(self, key):
def set_key(self, key):
raise Exception("阿里云百炼 适配器不支持设置 API Key。")
async def get_models(self):

View File

@@ -36,7 +36,7 @@ class ProviderDashscopeTTSAPI(TTSProvider):
super().__init__(provider_config, provider_settings)
self.chosen_api_key: str = provider_config.get("api_key", "")
self.voice: str = provider_config.get("dashscope_tts_voice", "loongstella")
self.set_model(provider_config.get("model"))
self.set_model(provider_config["model"])
self.timeout_ms = float(provider_config.get("timeout", 20)) * 1000
dashscope.api_key = self.chosen_api_key
@@ -71,9 +71,10 @@ class ProviderDashscopeTTSAPI(TTSProvider):
kwargs = {
"model": model,
"text": text,
"messages": None,
"api_key": self.chosen_api_key,
"voice": self.voice or "Cherry",
"text": text,
}
if not self.voice:
logging.warning(

View File

@@ -101,6 +101,8 @@ class ProviderDify(Provider):
payload_vars = self.variables.copy()
# 动态变量
session_var = await sp.session_get(session_id, "session_variables", default={})
if not isinstance(session_var, dict):
session_var = {}
payload_vars.update(session_var)
payload_vars["system_prompt"] = system_prompt
@@ -271,10 +273,10 @@ class ProviderDify(Provider):
self.conversation_ids[session_id] = ""
return True
async def get_current_key(self):
def get_current_key(self):
return self.api_key
async def set_key(self, key):
def set_key(self, key):
raise Exception("Dify 适配器不支持设置 API Key。")
async def get_models(self):

View File

@@ -67,7 +67,7 @@ class ProviderEdgeTTS(TTSProvider):
from pyffmpeg import FFmpeg
ff = FFmpeg()
ff.convert(input=mp3_path, output=wav_path)
ff.convert(input_file=mp3_path, output_file=wav_path)
except Exception as e:
logger.debug(f"pyffmpeg 转换失败: {e}, 尝试使用 ffmpeg 命令行进行转换")
# use ffmpeg command line

View File

@@ -59,9 +59,9 @@ class ProviderFishAudioTTSAPI(TTSProvider):
self.headers = {
"Authorization": f"Bearer {self.chosen_api_key}",
}
self.set_model(provider_config.get("model"))
self.set_model(provider_config["model"])
async def _get_reference_id_by_character(self, character: str) -> str:
async def _get_reference_id_by_character(self, character: str) -> str | None:
"""获取角色的reference_id
Args:
@@ -128,7 +128,7 @@ class ProviderFishAudioTTSAPI(TTSProvider):
text=text,
format="wav",
reference_id=reference_id,
)
).model_dump()
async def get_audio(self, text: str) -> str:
temp_dir = os.path.join(get_astrbot_data_path(), "temp")
@@ -146,5 +146,6 @@ class ProviderFishAudioTTSAPI(TTSProvider):
async for chunk in response.aiter_bytes():
f.write(chunk)
return path
text = await response.aread()
body = await response.aread()
text = body.decode("utf-8", errors="replace")
raise Exception(f"Fish Audio API请求失败: {text}")

View File

@@ -1,3 +1,5 @@
from typing import cast
from google import genai
from google.genai import types
from google.genai.errors import APIError
@@ -18,8 +20,8 @@ class GeminiEmbeddingProvider(EmbeddingProvider):
self.provider_config = provider_config
self.provider_settings = provider_settings
api_key: str = provider_config.get("embedding_api_key")
api_base: str = provider_config.get("embedding_api_base")
api_key: str = provider_config["embedding_api_key"]
api_base: str = provider_config["embedding_api_base"]
timeout: int = int(provider_config.get("timeout", 20))
http_options = types.HttpOptions(timeout=timeout * 1000)
@@ -41,18 +43,26 @@ class GeminiEmbeddingProvider(EmbeddingProvider):
model=self.model,
contents=text,
)
assert result.embeddings is not None
assert result.embeddings[0].values is not None
return result.embeddings[0].values
except APIError as e:
raise Exception(f"Gemini Embedding API请求失败: {e.message}")
async def get_embeddings(self, texts: list[str]) -> list[list[float]]:
async def get_embeddings(self, text: list[str]) -> list[list[float]]:
"""批量获取文本的嵌入"""
try:
result = await self.client.models.embed_content(
model=self.model,
contents=texts,
contents=cast(types.ContentListUnion, text),
)
return [embedding.values for embedding in result.embeddings]
assert result.embeddings is not None
embeddings: list[list[float]] = []
for embedding in result.embeddings:
assert embedding.values is not None
embeddings.append(embedding.values)
return embeddings
except APIError as e:
raise Exception(f"Gemini Embedding API批量请求失败: {e.message}")

View File

@@ -4,6 +4,7 @@ import json
import logging
import random
from collections.abc import AsyncGenerator
from typing import cast
from google import genai
from google.genai import types
@@ -138,7 +139,7 @@ class ProviderGoogleGenAI(Provider):
logger.warning("流式输出不支持图片模态,已自动降级为文本模态")
modalities = ["Text"]
tool_list = []
tool_list: list[types.Tool] | None = []
model_name = self.get_model()
native_coderunner = self.provider_config.get("gm_native_coderunner", False)
native_search = self.provider_config.get("gm_native_search", False)
@@ -215,7 +216,7 @@ class ProviderGoogleGenAI(Provider):
logprobs=payloads.get("logprobs"),
seed=payloads.get("seed"),
response_modalities=modalities,
tools=tool_list,
tools=cast(types.ToolListUnion | None, tool_list),
safety_settings=self.safety_settings if self.safety_settings else None,
thinking_config=(
types.ThinkingConfig(
@@ -259,6 +260,7 @@ class ProviderGoogleGenAI(Provider):
content_cls: type[types.Content],
) -> None:
if contents and isinstance(contents[-1], content_cls):
assert contents[-1].parts is not None
contents[-1].parts.extend(part)
else:
contents.append(content_cls(parts=part))
@@ -419,7 +421,7 @@ class ProviderGoogleGenAI(Provider):
)
result = await self.client.models.generate_content(
model=self.get_model(),
contents=conversation,
contents=cast(types.ContentListUnion, conversation),
config=config,
)
@@ -494,7 +496,7 @@ class ProviderGoogleGenAI(Provider):
)
result = await self.client.models.generate_content_stream(
model=self.get_model(),
contents=conversation,
contents=cast(types.ContentListUnion, conversation),
config=config,
)
break

View File

@@ -87,7 +87,7 @@ class ProviderMiniMaxTTSAPI(TTSProvider):
return json.dumps(dict_body)
async def _call_tts_stream(self, text: str) -> AsyncIterator[bytes]:
async def _call_tts_stream(self, text: str) -> AsyncIterator[str]:
"""进行流式请求"""
try:
async with (
@@ -117,7 +117,9 @@ class ProviderMiniMaxTTSAPI(TTSProvider):
data = json.loads(message[6:])
if "extra_info" in data:
continue
audio = data.get("data", {}).get("audio")
audio: str | None = data.get("data", {}).get(
"audio"
)
if audio is not None:
yield audio
except json.JSONDecodeError:

View File

@@ -30,9 +30,9 @@ class OpenAIEmbeddingProvider(EmbeddingProvider):
embedding = await self.client.embeddings.create(input=text, model=self.model)
return embedding.data[0].embedding
async def get_embeddings(self, texts: list[str]) -> list[list[float]]:
async def get_embeddings(self, text: list[str]) -> list[list[float]]:
"""批量获取文本的嵌入"""
embeddings = await self.client.embeddings.create(input=texts, model=self.model)
embeddings = await self.client.embeddings.create(input=text, model=self.model)
return [item.embedding for item in embeddings.data]
def get_dim(self) -> int:

View File

@@ -102,7 +102,7 @@ class ProviderOpenAIOfficial(Provider):
except NotFoundError as e:
raise Exception(f"获取模型列表失败:{e}")
async def _query(self, payloads: dict, tools: ToolSet) -> LLMResponse:
async def _query(self, payloads: dict, tools: ToolSet | None) -> LLMResponse:
if tools:
model = payloads.get("model", "").lower()
omit_empty_param_field = "gemini" in model
@@ -151,9 +151,7 @@ class ProviderOpenAIOfficial(Provider):
return llm_response
async def _query_stream(
self,
payloads: dict,
tools: ToolSet,
self, payloads: dict, tools: ToolSet | None
) -> AsyncGenerator[LLMResponse, None]:
"""流式查询API逐步返回结果"""
if tools:
@@ -212,7 +210,9 @@ class ProviderOpenAIOfficial(Provider):
yield llm_response
async def parse_openai_completion(self, completion: ChatCompletion, tools: ToolSet):
async def parse_openai_completion(
self, completion: ChatCompletion, tools: ToolSet | None
):
"""解析 OpenAI 的 ChatCompletion 响应"""
llm_response = LLMResponse("assistant")
@@ -234,6 +234,10 @@ class ProviderOpenAIOfficial(Provider):
if isinstance(tool_call, str):
# workaround for #1359
tool_call = json.loads(tool_call)
if tools is None:
# 工具集未提供
# Should be unreachable
raise Exception("工具集未提供")
for tool in tools.func_list:
if (
tool_call.type == "function"
@@ -310,7 +314,7 @@ class ProviderOpenAIOfficial(Provider):
e: Exception,
payloads: dict,
context_query: list,
func_tool: ToolSet,
func_tool: ToolSet | None,
chosen_key: str,
available_api_keys: list[str],
retry_cnt: int,

View File

@@ -7,6 +7,7 @@ import asyncio
import os
import re
from datetime import datetime
from typing import cast
from funasr_onnx import SenseVoiceSmall
from funasr_onnx.utils.postprocess_utils import rich_transcription_postprocess
@@ -32,7 +33,7 @@ class ProviderSenseVoiceSTTSelfHost(STTProvider):
provider_settings: dict,
) -> None:
super().__init__(provider_config, provider_settings)
self.set_model(provider_config.get("stt_model"))
self.set_model(provider_config["stt_model"])
self.model = None
self.is_emotion = provider_config.get("is_emotion", False)
@@ -86,7 +87,9 @@ class ProviderSenseVoiceSTTSelfHost(STTProvider):
loop = asyncio.get_event_loop()
res = await loop.run_in_executor(
None, # 使用默认的线程池
lambda: self.model(audio_url, language="auto", use_itn=True),
lambda: cast(SenseVoiceSmall, self.model)(
audio_url, language="auto", use_itn=True
),
)
# res = self.model(audio_url, language="auto", use_itn=True)

View File

@@ -44,6 +44,7 @@ class VLLMRerankProvider(RerankProvider):
}
if top_n is not None:
payload["top_n"] = top_n
assert self.client is not None
async with self.client.post(
f"{self.base_url}/v1/rerank",
json=payload,

View File

@@ -33,7 +33,7 @@ class ProviderOpenAIWhisperAPI(STTProvider):
timeout=provider_config.get("timeout", NOT_GIVEN),
)
self.set_model(provider_config.get("model"))
self.set_model(provider_config["model"])
async def _is_silk_file(self, file_path):
silk_header = b"SILK"

View File

@@ -1,6 +1,7 @@
import asyncio
import os
import uuid
from typing import cast
import whisper
@@ -26,7 +27,7 @@ class ProviderOpenAIWhisperSelfHost(STTProvider):
provider_settings: dict,
) -> None:
super().__init__(provider_config, provider_settings)
self.set_model(provider_config.get("model"))
self.set_model(provider_config["model"])
self.model = None
async def initialize(self):
@@ -75,5 +76,7 @@ class ProviderOpenAIWhisperSelfHost(STTProvider):
await tencent_silk_to_wav(audio_url, output_path)
audio_url = output_path
result = await loop.run_in_executor(None, self.model.transcribe, audio_url)
return result["text"]
result = await loop.run_in_executor(
None, cast(whisper.Whisper, self.model).transcribe, audio_url
)
return cast(str, result["text"])

View File

@@ -1,6 +1,11 @@
from typing import cast
from xinference_client.client.restful.async_restful_client import (
AsyncClient as Client,
)
from xinference_client.client.restful.async_restful_client import (
AsyncRESTfulRerankModelHandle,
)
from astrbot import logger
@@ -29,7 +34,7 @@ class XinferenceRerankProvider(RerankProvider):
False,
)
self.client = None
self.model = None
self.model: AsyncRESTfulRerankModelHandle | None = None
self.model_uid = None
async def initialize(self):
@@ -65,7 +70,10 @@ class XinferenceRerankProvider(RerankProvider):
return
if self.model_uid:
self.model = await self.client.get_model(self.model_uid)
self.model = cast(
AsyncRESTfulRerankModelHandle,
await self.client.get_model(self.model_uid),
)
except Exception as e:
logger.error(f"Failed to initialize Xinference model: {e}")