mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-26 17:13:54 +08:00
@@ -117,10 +117,9 @@ plugins:
|
||||
# 更多类型的新闻列表查看 https://www.chinanews.com.cn/rss/
|
||||
get_news_from_chinanews:
|
||||
default_rss_url: "https://www.chinanews.com.cn/rss/society.xml"
|
||||
category_urls:
|
||||
society: "https://www.chinanews.com.cn/rss/society.xml"
|
||||
world: "https://www.chinanews.com.cn/rss/world.xml"
|
||||
finance: "https://www.chinanews.com.cn/rss/finance.xml"
|
||||
society_rss_url: "https://www.chinanews.com.cn/rss/society.xml"
|
||||
world_rss_url: "https://www.chinanews.com.cn/rss/world.xml"
|
||||
finance_rss_url: "https://www.chinanews.com.cn/rss/finance.xml"
|
||||
get_news_from_newsnow: {"url": "https://newsnow.busiyi.world/api/s?id="}
|
||||
home_assistant:
|
||||
devices:
|
||||
|
||||
@@ -453,9 +453,17 @@ class ConnectionHandler:
|
||||
if private_config.get("Intent", None) is not None:
|
||||
init_intent = True
|
||||
self.config["Intent"] = private_config["Intent"]
|
||||
self.config["selected_module"]["Intent"] = private_config[
|
||||
"selected_module"
|
||||
]["Intent"]
|
||||
model_intent = private_config.get("selected_module", {}).get("Intent", {})
|
||||
self.config["selected_module"]["Intent"] = model_intent
|
||||
# 加载插件配置
|
||||
if model_intent != "Intent_nointent":
|
||||
plugin_from_server = private_config.get("plugins", {})
|
||||
for plugin, config_str in plugin_from_server.items():
|
||||
plugin_from_server[plugin] = json.loads(config_str)
|
||||
self.config["plugins"] = plugin_from_server
|
||||
self.config["Intent"][self.config["selected_module"]["Intent"]][
|
||||
"functions"
|
||||
] = plugin_from_server.keys()
|
||||
if private_config.get("prompt", None) is not None:
|
||||
self.config["prompt"] = private_config["prompt"]
|
||||
if private_config.get("summaryMemory", None) is not None:
|
||||
|
||||
@@ -61,7 +61,6 @@ class FunctionHandler:
|
||||
self.function_registry.register_function("plugin_loader")
|
||||
self.function_registry.register_function("get_time")
|
||||
self.function_registry.register_function("get_lunar")
|
||||
# self.function_registry.register_function("handle_speaker_volume_or_screen_brightness")
|
||||
|
||||
def register_config_functions(self):
|
||||
"""注册配置中的函数,可以不同客户端使用不同的配置"""
|
||||
|
||||
@@ -23,7 +23,9 @@ class LLMProvider(LLMProviderBase):
|
||||
self.bot_id = str(config.get("bot_id"))
|
||||
self.user_id = str(config.get("user_id"))
|
||||
self.session_conversation_map = {} # 存储session_id和conversation_id的映射
|
||||
check_model_key("CozeLLM", self.personal_access_token)
|
||||
model_key_msg = check_model_key("CozeLLM", self.personal_access_token)
|
||||
if model_key_msg:
|
||||
logger.bind(tag=TAG).error(model_key_msg)
|
||||
|
||||
def response(self, session_id, dialogue, **kwargs):
|
||||
coze_api_token = self.personal_access_token
|
||||
|
||||
@@ -15,7 +15,9 @@ class LLMProvider(LLMProviderBase):
|
||||
self.mode = config.get("mode", "chat-messages")
|
||||
self.base_url = config.get("base_url", "https://api.dify.ai/v1").rstrip("/")
|
||||
self.session_conversation_map = {} # 存储session_id和conversation_id的映射
|
||||
check_model_key("DifyLLM", self.api_key)
|
||||
model_key_msg = check_model_key("DifyLLM", self.api_key)
|
||||
if model_key_msg:
|
||||
logger.bind(tag=TAG).error(model_key_msg)
|
||||
|
||||
def response(self, session_id, dialogue, **kwargs):
|
||||
try:
|
||||
|
||||
@@ -14,7 +14,9 @@ class LLMProvider(LLMProviderBase):
|
||||
self.base_url = config.get("base_url")
|
||||
self.detail = config.get("detail", False)
|
||||
self.variables = config.get("variables", {})
|
||||
check_model_key("FastGPTLLM", self.api_key)
|
||||
model_key_msg = check_model_key("FastGPTLLM", self.api_key)
|
||||
if model_key_msg:
|
||||
logger.bind(tag=TAG).error(model_key_msg)
|
||||
|
||||
def response(self, session_id, dialogue, **kwargs):
|
||||
try:
|
||||
|
||||
@@ -73,8 +73,9 @@ class LLMProvider(LLMProviderBase):
|
||||
http_proxy = cfg.get("http_proxy")
|
||||
https_proxy = cfg.get("https_proxy")
|
||||
|
||||
if not check_model_key("LLM", self.api_key):
|
||||
raise ValueError("无效的Gemini API Key,请检查是否配置正确")
|
||||
model_key_msg = check_model_key("LLM", self.api_key)
|
||||
if model_key_msg:
|
||||
log.bind(tag=TAG).error(model_key_msg)
|
||||
|
||||
if http_proxy or https_proxy:
|
||||
log.bind(tag=TAG).info(
|
||||
|
||||
@@ -21,20 +21,27 @@ class LLMProvider(LLMProviderBase):
|
||||
"max_tokens": (500, int),
|
||||
"temperature": (0.7, lambda x: round(float(x), 1)),
|
||||
"top_p": (1.0, lambda x: round(float(x), 1)),
|
||||
"frequency_penalty": (0, lambda x: round(float(x), 1))
|
||||
"frequency_penalty": (0, lambda x: round(float(x), 1)),
|
||||
}
|
||||
|
||||
for param, (default, converter) in param_defaults.items():
|
||||
value = config.get(param)
|
||||
try:
|
||||
setattr(self, param, converter(value) if value not in (None, "") else default)
|
||||
setattr(
|
||||
self,
|
||||
param,
|
||||
converter(value) if value not in (None, "") else default,
|
||||
)
|
||||
except (ValueError, TypeError):
|
||||
setattr(self, param, default)
|
||||
|
||||
logger.debug(
|
||||
f"意图识别参数初始化: {self.temperature}, {self.max_tokens}, {self.top_p}, {self.frequency_penalty}")
|
||||
f"意图识别参数初始化: {self.temperature}, {self.max_tokens}, {self.top_p}, {self.frequency_penalty}"
|
||||
)
|
||||
|
||||
check_model_key("LLM", self.api_key)
|
||||
model_key_msg = check_model_key("LLM", self.api_key)
|
||||
if model_key_msg:
|
||||
logger.bind(tag=TAG).error(model_key_msg)
|
||||
self.client = openai.OpenAI(api_key=self.api_key, base_url=self.base_url)
|
||||
|
||||
def response(self, session_id, dialogue, **kwargs):
|
||||
@@ -46,7 +53,9 @@ class LLMProvider(LLMProviderBase):
|
||||
max_tokens=kwargs.get("max_tokens", self.max_tokens),
|
||||
temperature=kwargs.get("temperature", self.temperature),
|
||||
top_p=kwargs.get("top_p", self.top_p),
|
||||
frequency_penalty=kwargs.get("frequency_penalty", self.frequency_penalty),
|
||||
frequency_penalty=kwargs.get(
|
||||
"frequency_penalty", self.frequency_penalty
|
||||
),
|
||||
)
|
||||
|
||||
is_active = True
|
||||
@@ -84,12 +93,14 @@ class LLMProvider(LLMProviderBase):
|
||||
for chunk in stream:
|
||||
# 检查是否存在有效的choice且content不为空
|
||||
if getattr(chunk, "choices", None):
|
||||
yield chunk.choices[0].delta.content, chunk.choices[0].delta.tool_calls
|
||||
yield chunk.choices[0].delta.content, chunk.choices[
|
||||
0
|
||||
].delta.tool_calls
|
||||
# 存在 CompletionUsage 消息时,生成 Token 消耗 log
|
||||
elif isinstance(getattr(chunk, 'usage', None), CompletionUsage):
|
||||
usage_info = getattr(chunk, 'usage', None)
|
||||
elif isinstance(getattr(chunk, "usage", None), CompletionUsage):
|
||||
usage_info = getattr(chunk, "usage", None)
|
||||
logger.bind(tag=TAG).info(
|
||||
f"Token 消耗:输入 {getattr(usage_info, 'prompt_tokens', '未知')},"
|
||||
f"Token 消耗:输入 {getattr(usage_info, 'prompt_tokens', '未知')},"
|
||||
f"输出 {getattr(usage_info, 'completion_tokens', '未知')},"
|
||||
f"共计 {getattr(usage_info, 'total_tokens', '未知')}"
|
||||
)
|
||||
|
||||
@@ -12,10 +12,6 @@ class MemoryProviderBase(ABC):
|
||||
|
||||
def set_llm(self, llm):
|
||||
self.llm = llm
|
||||
# 获取模型名称和类型信息
|
||||
model_name = getattr(llm, "model_name", str(llm.__class__.__name__))
|
||||
# 记录更详细的日志
|
||||
logger.bind(tag=TAG).info(f"记忆总结设置LLM: {model_name}")
|
||||
|
||||
@abstractmethod
|
||||
async def save_memory(self, msgs):
|
||||
|
||||
@@ -12,12 +12,14 @@ class MemoryProvider(MemoryProviderBase):
|
||||
super().__init__(config)
|
||||
self.api_key = config.get("api_key", "")
|
||||
self.api_version = config.get("api_version", "v1.1")
|
||||
have_key = check_model_key("Mem0ai", self.api_key)
|
||||
if not have_key:
|
||||
model_key_msg = check_model_key("Mem0ai", self.api_key)
|
||||
if model_key_msg:
|
||||
logger.bind(tag=TAG).error(model_key_msg)
|
||||
self.use_mem0 = False
|
||||
return
|
||||
else:
|
||||
self.use_mem0 = True
|
||||
|
||||
try:
|
||||
self.client = MemoryClient(api_key=self.api_key)
|
||||
logger.bind(tag=TAG).info("成功连接到 Mem0ai 服务")
|
||||
|
||||
@@ -37,7 +37,9 @@ class TTSProvider(TTSProviderBase):
|
||||
self.api_url = config.get("api_url")
|
||||
self.authorization = config.get("authorization")
|
||||
self.header = {"Authorization": f"{self.authorization}{self.access_token}"}
|
||||
check_model_key("TTS", self.access_token)
|
||||
model_key_msg = check_model_key("TTS", self.access_token)
|
||||
if model_key_msg:
|
||||
logger.bind(tag=TAG).error(model_key_msg)
|
||||
|
||||
async def text_to_speak(self, text, output_file):
|
||||
request_json = {
|
||||
|
||||
@@ -90,8 +90,9 @@ class TTSProvider(TTSProviderBase):
|
||||
self.format = config.get("response_format", "wav")
|
||||
self.audio_file_type = config.get("response_format", "wav")
|
||||
self.api_key = config.get("api_key", "YOUR_API_KEY")
|
||||
have_key = check_model_key("FishSpeech TTS", self.api_key)
|
||||
if not have_key:
|
||||
model_key_msg = check_model_key("FishSpeech TTS", self.api_key)
|
||||
if model_key_msg:
|
||||
logger.bind(tag=TAG).error(model_key_msg)
|
||||
return
|
||||
self.normalize = str(config.get("normalize", True)).lower() in (
|
||||
"true",
|
||||
|
||||
@@ -157,7 +157,9 @@ class TTSProvider(TTSProviderBase):
|
||||
self.opus_encoder = opus_encoder_utils.OpusEncoderUtils(
|
||||
sample_rate=16000, channels=1, frame_size_ms=60
|
||||
)
|
||||
check_model_key("TTS", self.access_token)
|
||||
model_key_msg = check_model_key("TTS", self.access_token)
|
||||
if model_key_msg:
|
||||
logger.bind(tag=TAG).error(model_key_msg)
|
||||
|
||||
async def open_audio_channels(self, conn):
|
||||
try:
|
||||
@@ -267,7 +269,7 @@ class TTSProvider(TTSProviderBase):
|
||||
await handleAbortMessage(self.conn)
|
||||
logger.bind(tag=TAG).error(f"WebSocket连接不存在,终止发送文本")
|
||||
return
|
||||
|
||||
|
||||
# 过滤Markdown
|
||||
filtered_text = MarkdownCleaner.clean_markdown(text)
|
||||
|
||||
|
||||
@@ -166,7 +166,9 @@ class TTSProvider(TTSProviderBase):
|
||||
) as resp:
|
||||
|
||||
if resp.status != 200:
|
||||
logger.error(f"TTS请求失败: {resp.status}, {await resp.text()}")
|
||||
logger.bind(tag=TAG).error(
|
||||
f"TTS请求失败: {resp.status}, {await resp.text()}"
|
||||
)
|
||||
self.tts_audio_queue.put((SentenceType.LAST, [], None))
|
||||
return
|
||||
|
||||
@@ -229,7 +231,7 @@ class TTSProvider(TTSProviderBase):
|
||||
self._process_before_stop_play_files()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"TTS请求异常: {e}")
|
||||
logger.bind(tag=TAG).error(f"TTS请求异常: {e}")
|
||||
self.tts_audio_queue.put((SentenceType.LAST, [], None))
|
||||
|
||||
def to_tts(self, text: str) -> list:
|
||||
@@ -263,7 +265,7 @@ class TTSProvider(TTSProviderBase):
|
||||
self.api_url, params=params, headers=headers, timeout=5
|
||||
) as response:
|
||||
if response.status_code != 200:
|
||||
logger.error(
|
||||
logger.bind(tag=TAG).error(
|
||||
f"TTS请求失败: {response.status_code}, {response.text}"
|
||||
)
|
||||
return []
|
||||
@@ -299,5 +301,5 @@ class TTSProvider(TTSProviderBase):
|
||||
return opus_datas
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"TTS请求异常: {e}")
|
||||
logger.bind(tag=TAG).error(f"TTS请求异常: {e}")
|
||||
return []
|
||||
|
||||
@@ -25,7 +25,9 @@ class TTSProvider(TTSProviderBase):
|
||||
self.speed = float(speed) if speed else 1.0
|
||||
|
||||
self.output_file = config.get("output_dir", "tmp/")
|
||||
check_model_key("TTS", self.api_key)
|
||||
model_key_msg = check_model_key("TTS", self.api_key)
|
||||
if model_key_msg:
|
||||
logger.bind(tag=TAG).error(model_key_msg)
|
||||
|
||||
async def text_to_speak(self, text, output_file):
|
||||
headers = {
|
||||
|
||||
@@ -34,7 +34,9 @@ class VLLMProvider(VLLMProviderBase):
|
||||
except (ValueError, TypeError):
|
||||
setattr(self, param, default)
|
||||
|
||||
check_model_key("VLLM", self.api_key)
|
||||
model_key_msg = check_model_key("VLLM", self.api_key)
|
||||
if model_key_msg:
|
||||
logger.bind(tag=TAG).error(model_key_msg)
|
||||
self.client = openai.OpenAI(api_key=self.api_key, base_url=self.base_url)
|
||||
|
||||
def response(self, question, base64_image):
|
||||
|
||||
@@ -186,10 +186,8 @@ def remove_punctuation_and_length(text):
|
||||
|
||||
def check_model_key(modelType, modelKey):
|
||||
if "你" in modelKey:
|
||||
raise ValueError(
|
||||
"你还没配置" + modelType + "的密钥,请检查一下所使用的LLM是否配置了密钥"
|
||||
)
|
||||
return True
|
||||
return f"配置错误: {modelType} 的 API key 未设置,当前值为: {modelKey}"
|
||||
return None
|
||||
|
||||
|
||||
def parse_string_to_list(value, separator=";"):
|
||||
@@ -785,7 +783,9 @@ def audio_bytes_to_data(audio_bytes, file_type, is_opus=True):
|
||||
return p3.decode_opus_from_bytes(audio_bytes)
|
||||
else:
|
||||
# 其他格式用pydub
|
||||
audio = AudioSegment.from_file(BytesIO(audio_bytes), format=file_type, parameters=["-nostdin"])
|
||||
audio = AudioSegment.from_file(
|
||||
BytesIO(audio_bytes), format=file_type, parameters=["-nostdin"]
|
||||
)
|
||||
audio = audio.set_channels(1).set_frame_rate(16000).set_sample_width(2)
|
||||
duration = len(audio) / 1000.0
|
||||
raw_data = audio.raw_data
|
||||
@@ -838,11 +838,11 @@ def opus_datas_to_wav_bytes(opus_datas, sample_rate=16000, channels=1):
|
||||
pcm = decoder.decode(opus_frame, frame_size)
|
||||
pcm_datas.append(pcm)
|
||||
|
||||
pcm_bytes = b''.join(pcm_datas)
|
||||
pcm_bytes = b"".join(pcm_datas)
|
||||
|
||||
# 写入wav字节流
|
||||
wav_buffer = BytesIO()
|
||||
with wave.open(wav_buffer, 'wb') as wf:
|
||||
with wave.open(wav_buffer, "wb") as wf:
|
||||
wf.setnchannels(channels)
|
||||
wf.setsampwidth(2) # 16bit
|
||||
wf.setframerate(sample_rate)
|
||||
|
||||
@@ -120,16 +120,16 @@ def map_category(category_text):
|
||||
# 类别映射字典,目前支持社会、国际、财经新闻,如需更多类型,参见配置文件
|
||||
category_map = {
|
||||
# 社会新闻
|
||||
"社会": "society",
|
||||
"社会新闻": "society",
|
||||
"社会": "society_rss_url",
|
||||
"社会新闻": "society_rss_url",
|
||||
# 国际新闻
|
||||
"国际": "world",
|
||||
"国际新闻": "world",
|
||||
"国际": "world_rss_url",
|
||||
"国际新闻": "world_rss_url",
|
||||
# 财经新闻
|
||||
"财经": "finance",
|
||||
"财经新闻": "finance",
|
||||
"金融": "finance",
|
||||
"经济": "finance",
|
||||
"财经": "finance_rss_url",
|
||||
"财经新闻": "finance_rss_url",
|
||||
"金融": "finance_rss_url",
|
||||
"经济": "finance_rss_url",
|
||||
}
|
||||
|
||||
# 转换为小写并去除空格
|
||||
@@ -205,8 +205,8 @@ def get_news_from_chinanews(
|
||||
|
||||
# 如果提供了类别,尝试从配置中获取对应的URL
|
||||
rss_url = default_rss_url
|
||||
if mapped_category and mapped_category in rss_config.get("category_urls", {}):
|
||||
rss_url = rss_config["category_urls"][mapped_category]
|
||||
if mapped_category and mapped_category in rss_config:
|
||||
rss_url = rss_config[mapped_category]
|
||||
|
||||
logger.bind(tag=TAG).info(
|
||||
f"获取新闻: 原始类别={category}, 映射类别={mapped_category}, URL={rss_url}"
|
||||
|
||||
@@ -1,157 +0,0 @@
|
||||
from config.logger import setup_logging
|
||||
from plugins_func.register import register_function, ToolType, ActionResponse, Action
|
||||
from core.handle.iotHandle import get_iot_status, send_iot_conn
|
||||
import asyncio
|
||||
|
||||
TAG = __name__
|
||||
logger = setup_logging()
|
||||
|
||||
|
||||
async def _get_device_status(conn, device_name, device_type, property_name):
|
||||
"""获取设备状态"""
|
||||
status = await get_iot_status(conn, device_type, property_name)
|
||||
if status is None:
|
||||
raise Exception(f"你的设备不支持{device_name}控制")
|
||||
return status
|
||||
|
||||
|
||||
async def _set_device_property(
|
||||
conn,
|
||||
device_name,
|
||||
device_type,
|
||||
method_name,
|
||||
property_name,
|
||||
new_value=None,
|
||||
action=None,
|
||||
step=10,
|
||||
):
|
||||
"""设置设备属性"""
|
||||
current_value = await _get_device_status(
|
||||
conn, device_name, device_type, property_name
|
||||
)
|
||||
|
||||
if action == "raise":
|
||||
current_value += step
|
||||
elif action == "lower":
|
||||
current_value -= step
|
||||
elif action == "set":
|
||||
if new_value is None:
|
||||
raise Exception(f"缺少{property_name}参数")
|
||||
current_value = new_value
|
||||
|
||||
# 限制属性范围在0到100之间
|
||||
current_value = max(0, min(100, current_value))
|
||||
|
||||
await send_iot_conn(conn, device_type, method_name, {property_name: current_value})
|
||||
return current_value
|
||||
|
||||
|
||||
def _handle_device_action(conn, func, success_message, error_message, *args, **kwargs):
|
||||
"""处理设备操作的通用函数"""
|
||||
future = asyncio.run_coroutine_threadsafe(func(conn, *args, **kwargs), conn.loop)
|
||||
try:
|
||||
result = future.result()
|
||||
logger.bind(tag=TAG).info(f"{success_message}: {result}")
|
||||
response = f"{success_message}{result}"
|
||||
return ActionResponse(action=Action.RESPONSE, result=result, response=response)
|
||||
except Exception as e:
|
||||
logger.bind(tag=TAG).error(f"{error_message}: {e}")
|
||||
response = f"{error_message}: {e}"
|
||||
return ActionResponse(action=Action.RESPONSE, result=None, response=response)
|
||||
|
||||
|
||||
# 设备控制
|
||||
handle_device_function_desc = {
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "handle_speaker_volume_or_screen_brightness",
|
||||
"description": (
|
||||
"用户想要获取或者设置设备的音量/亮度大小,或者用户觉得声音/亮度过高或过低,或者用户想提高或降低音量/亮度。\n"
|
||||
"**严格限制**:仅当用户明确操作 **Speaker(音量)或Screen(亮度)** 时才能调用此函数!\n"
|
||||
"对于其他设备(如AC、Battery、Switch等),请不要调用此函数,而是继续正常的对话。\n\n"
|
||||
"示例:\n"
|
||||
"- 用户说『现在亮度多少』 → 调用函数:device_type: Screen, action: get\n"
|
||||
"- 用户说『设置音量为50』 → 调用函数:device_type: Speaker, action: set, value: 50\n"
|
||||
"- 用户说『亮度太高了』 → 调用函数:device_type: Screen, action: lower\n"
|
||||
"- 用户说『调大音量』 → 调用函数:device_type: Speaker, action: raise\n\n"
|
||||
"**拒绝调用示例**(应继续对话而非调用本函数):\n"
|
||||
"- 用户说『空调调低一度』 → 不调用(设备类型为AC)\n"
|
||||
"- 用户说『开关灯』 → 不调用(设备类型为Switch)\n"
|
||||
"- 用户说『电量多少』 → 不调用(设备类型为Battery)\n"
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"device_type": {
|
||||
"type": "string",
|
||||
"description": "设备类型,**严格限定为Speaker(音量)或Screen(亮度)**,其他设备类型禁止调用此函数",
|
||||
"enum": ["Speaker", "Screen"],
|
||||
},
|
||||
"action": {
|
||||
"type": "string",
|
||||
"description": "动作名称,可选值:get(获取),set(设置),raise(提高),lower(降低)",
|
||||
},
|
||||
"value": {
|
||||
"type": "integer",
|
||||
"description": "值大小,可选值:0-100之间的整数",
|
||||
},
|
||||
},
|
||||
"required": ["device_type", "action"],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@register_function(
|
||||
"handle_speaker_volume_or_screen_brightness",
|
||||
handle_device_function_desc,
|
||||
ToolType.IOT_CTL,
|
||||
)
|
||||
def handle_speaker_volume_or_screen_brightness(
|
||||
conn, device_type: str, action: str, value: int = None
|
||||
):
|
||||
# 检查value是否为中文值
|
||||
if (
|
||||
value is not None
|
||||
and isinstance(value, str)
|
||||
and any("\u4e00" <= char <= "\u9fff" for char in str(value))
|
||||
):
|
||||
raise Exception(
|
||||
f"请直接告诉我要将{'音量' if device_type=='Speaker' else '亮度'}调整成多少"
|
||||
)
|
||||
|
||||
if device_type == "Speaker":
|
||||
method_name, property_name, device_name = "SetVolume", "volume", "音量"
|
||||
elif device_type == "Screen":
|
||||
method_name, property_name, device_name = "SetBrightness", "brightness", "亮度"
|
||||
else:
|
||||
raise Exception(f"未识别的设备类型: {device_type}")
|
||||
|
||||
if action not in ["get", "set", "raise", "lower"]:
|
||||
raise Exception(f"未识别的动作名称: {action}")
|
||||
|
||||
if action == "get":
|
||||
# get
|
||||
return _handle_device_action(
|
||||
conn,
|
||||
_get_device_status,
|
||||
f"当前{device_name}",
|
||||
f"获取{device_name}失败",
|
||||
device_name=device_name,
|
||||
device_type=device_type,
|
||||
property_name=property_name,
|
||||
)
|
||||
else:
|
||||
# set, raise, lower
|
||||
return _handle_device_action(
|
||||
conn,
|
||||
_set_device_property,
|
||||
f"{device_name}已调整到",
|
||||
f"{device_name}调整失败",
|
||||
device_name=device_name,
|
||||
device_type=device_type,
|
||||
method_name=method_name,
|
||||
property_name=property_name,
|
||||
new_value=value,
|
||||
action=action,
|
||||
)
|
||||
@@ -17,57 +17,78 @@ hass_get_state_function_desc = {
|
||||
"properties": {
|
||||
"entity_id": {
|
||||
"type": "string",
|
||||
"description": "需要操作的设备id,homeassistant里的entity_id"
|
||||
"description": "需要操作的设备id,homeassistant里的entity_id",
|
||||
}
|
||||
},
|
||||
"required": ["entity_id"]
|
||||
}
|
||||
}
|
||||
"required": ["entity_id"],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@register_function("hass_get_state", hass_get_state_function_desc, ToolType.SYSTEM_CTL)
|
||||
def hass_get_state(conn, entity_id=''):
|
||||
def hass_get_state(conn, entity_id=""):
|
||||
try:
|
||||
|
||||
future = asyncio.run_coroutine_threadsafe(
|
||||
handle_hass_get_state(conn, entity_id),
|
||||
conn.loop
|
||||
handle_hass_get_state(conn, entity_id), conn.loop
|
||||
)
|
||||
ha_response = future.result()
|
||||
return ActionResponse( Action.REQLLM, ha_response , None )
|
||||
return ActionResponse(Action.REQLLM, ha_response, None)
|
||||
except Exception as e:
|
||||
logger.bind(tag=TAG).error(f"处理设置属性意图错误: {e}")
|
||||
|
||||
|
||||
async def handle_hass_get_state(conn, entity_id):
|
||||
HASS_CACHE = initialize_hass_handler(conn)
|
||||
api_key = HASS_CACHE['api_key']
|
||||
base_url = HASS_CACHE['base_url']
|
||||
ha_config = initialize_hass_handler(conn)
|
||||
api_key = ha_config.get("api_key")
|
||||
base_url = ha_config.get("base_url")
|
||||
url = f"{base_url}/api/states/{entity_id}"
|
||||
headers = {
|
||||
"Authorization": f"Bearer {api_key}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
|
||||
response = requests.get(url, headers=headers)
|
||||
if response.status_code == 200:
|
||||
responsetext = '设备状态:' + response.json()['state'] + ' '
|
||||
responsetext = "设备状态:" + response.json()["state"] + " "
|
||||
logger.bind(tag=TAG).info(f"api返回内容: {response.json()}")
|
||||
|
||||
if 'media_title' in response.json()['attributes']:
|
||||
responsetext = responsetext+ '正在播放的是:'+str(response.json()['attributes']['media_title'])+' '
|
||||
if 'volume_level' in response.json()['attributes']:
|
||||
responsetext = responsetext+ '音量是:'+str(response.json()['attributes']['volume_level'])+' '
|
||||
if 'color_temp_kelvin' in response.json()['attributes']:
|
||||
responsetext = responsetext+ '色温是:'+str(response.json()['attributes']['color_temp_kelvin'])+' '
|
||||
if 'rgb_color' in response.json()['attributes']:
|
||||
responsetext = responsetext+ 'rgb颜色是:'+str(response.json()['attributes']['rgb_color'])+' '
|
||||
if 'brightness' in response.json()['attributes']:
|
||||
responsetext = responsetext+ '亮度是:'+str(response.json()['attributes']['brightness'])+' '
|
||||
if "media_title" in response.json()["attributes"]:
|
||||
responsetext = (
|
||||
responsetext
|
||||
+ "正在播放的是:"
|
||||
+ str(response.json()["attributes"]["media_title"])
|
||||
+ " "
|
||||
)
|
||||
if "volume_level" in response.json()["attributes"]:
|
||||
responsetext = (
|
||||
responsetext
|
||||
+ "音量是:"
|
||||
+ str(response.json()["attributes"]["volume_level"])
|
||||
+ " "
|
||||
)
|
||||
if "color_temp_kelvin" in response.json()["attributes"]:
|
||||
responsetext = (
|
||||
responsetext
|
||||
+ "色温是:"
|
||||
+ str(response.json()["attributes"]["color_temp_kelvin"])
|
||||
+ " "
|
||||
)
|
||||
if "rgb_color" in response.json()["attributes"]:
|
||||
responsetext = (
|
||||
responsetext
|
||||
+ "rgb颜色是:"
|
||||
+ str(response.json()["attributes"]["rgb_color"])
|
||||
+ " "
|
||||
)
|
||||
if "brightness" in response.json()["attributes"]:
|
||||
responsetext = (
|
||||
responsetext
|
||||
+ "亮度是:"
|
||||
+ str(response.json()["attributes"]["brightness"])
|
||||
+ " "
|
||||
)
|
||||
logger.bind(tag=TAG).info(f"查询返回内容: {responsetext}")
|
||||
return responsetext
|
||||
#return response.json()['attributes']
|
||||
#response.attributes
|
||||
# return response.json()['attributes']
|
||||
# response.attributes
|
||||
|
||||
else:
|
||||
return f"切换失败,错误码: {response.status_code}"
|
||||
|
||||
@@ -4,40 +4,49 @@ from core.utils.util import check_model_key
|
||||
TAG = __name__
|
||||
logger = setup_logging()
|
||||
|
||||
HASS_CACHE = {}
|
||||
|
||||
|
||||
def append_devices_to_prompt(conn):
|
||||
if conn.intent_type == "function_call":
|
||||
funcs = conn.config["Intent"][conn.config["selected_module"]["Intent"]].get(
|
||||
"functions", []
|
||||
)
|
||||
|
||||
config_source = (
|
||||
"home_assistant"
|
||||
if conn.config["plugins"].get("home_assistant")
|
||||
else "hass_get_state"
|
||||
)
|
||||
|
||||
if "hass_get_state" in funcs or "hass_set_state" in funcs:
|
||||
prompt = "\n下面是我家智能设备列表(位置,设备名,entity_id),可以通过homeassistant控制\n"
|
||||
devices = conn.config["plugins"]["home_assistant"].get("devices", [])
|
||||
if len(devices) == 0:
|
||||
return
|
||||
for device in devices:
|
||||
prompt += device + "\n"
|
||||
conn.prompt += prompt
|
||||
deviceStr = conn.config["plugins"].get(config_source, {}).get("devices", "")
|
||||
conn.prompt += prompt + deviceStr + "\n"
|
||||
# 更新提示词
|
||||
conn.dialogue.update_system_message(conn.prompt)
|
||||
|
||||
|
||||
def initialize_hass_handler(conn):
|
||||
global HASS_CACHE
|
||||
if HASS_CACHE == {}:
|
||||
if conn.load_function_plugin:
|
||||
funcs = conn.config["Intent"][conn.config["selected_module"]["Intent"]].get(
|
||||
"functions", []
|
||||
)
|
||||
if "hass_get_state" in funcs or "hass_set_state" in funcs:
|
||||
HASS_CACHE["base_url"] = conn.config["plugins"]["home_assistant"].get(
|
||||
"base_url"
|
||||
)
|
||||
HASS_CACHE["api_key"] = conn.config["plugins"]["home_assistant"].get(
|
||||
"api_key"
|
||||
)
|
||||
ha_config = {}
|
||||
if not conn.load_function_plugin:
|
||||
return ha_config
|
||||
|
||||
check_model_key("home_assistant", HASS_CACHE["api_key"])
|
||||
return HASS_CACHE
|
||||
# 确定配置来源
|
||||
config_source = (
|
||||
"home_assistant"
|
||||
if conn.config["plugins"].get("home_assistant")
|
||||
else "hass_get_state"
|
||||
)
|
||||
if not conn.config["plugins"].get(config_source):
|
||||
return ha_config
|
||||
|
||||
# 统一获取配置
|
||||
plugin_config = conn.config["plugins"][config_source]
|
||||
ha_config["base_url"] = plugin_config.get("base_url")
|
||||
ha_config["api_key"] = plugin_config.get("api_key")
|
||||
|
||||
# 统一检查API密钥
|
||||
model_key_msg = check_model_key("home_assistant", ha_config.get("api_key"))
|
||||
if model_key_msg:
|
||||
logger.bind(tag=TAG).error(model_key_msg)
|
||||
|
||||
return ha_config
|
||||
|
||||
@@ -17,46 +17,43 @@ hass_play_music_function_desc = {
|
||||
"properties": {
|
||||
"media_content_id": {
|
||||
"type": "string",
|
||||
"description": "可以是音乐或有声书的专辑名称、歌曲名、演唱者,如果未指定就填random"
|
||||
"description": "可以是音乐或有声书的专辑名称、歌曲名、演唱者,如果未指定就填random",
|
||||
},
|
||||
"entity_id": {
|
||||
"type": "string",
|
||||
"description": "需要操作的音箱的设备id,homeassistant里的entity_id,media_player开头"
|
||||
}
|
||||
"description": "需要操作的音箱的设备id,homeassistant里的entity_id,media_player开头",
|
||||
},
|
||||
},
|
||||
"required": ["media_content_id", "entity_id"]
|
||||
}
|
||||
}
|
||||
"required": ["media_content_id", "entity_id"],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@register_function('hass_play_music', hass_play_music_function_desc, ToolType.SYSTEM_CTL)
|
||||
def hass_play_music(conn, entity_id='', media_content_id='random'):
|
||||
@register_function(
|
||||
"hass_play_music", hass_play_music_function_desc, ToolType.SYSTEM_CTL
|
||||
)
|
||||
def hass_play_music(conn, entity_id="", media_content_id="random"):
|
||||
try:
|
||||
# 执行音乐播放命令
|
||||
future = asyncio.run_coroutine_threadsafe(
|
||||
handle_hass_play_music(conn, entity_id, media_content_id),
|
||||
conn.loop
|
||||
handle_hass_play_music(conn, entity_id, media_content_id), conn.loop
|
||||
)
|
||||
ha_response = future.result()
|
||||
return ActionResponse(action=Action.RESPONSE, result="退出意图已处理", response=ha_response)
|
||||
return ActionResponse(
|
||||
action=Action.RESPONSE, result="退出意图已处理", response=ha_response
|
||||
)
|
||||
except Exception as e:
|
||||
logger.bind(tag=TAG).error(f"处理音乐意图错误: {e}")
|
||||
|
||||
|
||||
async def handle_hass_play_music(conn, entity_id, media_content_id):
|
||||
HASS_CACHE = initialize_hass_handler(conn)
|
||||
api_key = HASS_CACHE['api_key']
|
||||
base_url = HASS_CACHE['base_url']
|
||||
ha_config = initialize_hass_handler(conn)
|
||||
api_key = ha_config.get("api_key")
|
||||
base_url = ha_config.get("base_url")
|
||||
url = f"{base_url}/api/services/music_assistant/play_media"
|
||||
headers = {
|
||||
"Authorization": f"Bearer {api_key}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
data = {
|
||||
"entity_id": entity_id,
|
||||
"media_id": media_content_id
|
||||
}
|
||||
headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
|
||||
data = {"entity_id": entity_id, "media_id": media_content_id}
|
||||
response = requests.post(url, headers=headers, json=data)
|
||||
if response.status_code == 200:
|
||||
return f"正在播放{media_content_id}的音乐"
|
||||
|
||||
@@ -20,40 +20,39 @@ hass_set_state_function_desc = {
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "需要操作的动作,打开设备:turn_on,关闭设备:turn_off,增加亮度:brightness_up,降低亮度:brightness_down,设置亮度:brightness_value,增加音量:volume_up,降低音量:volume_down,设置音量:volume_set,设置色温:set_kelvin,设置颜色:set_color,设备暂停:pause,设备继续:continue,静音/取消静音:volume_mute"
|
||||
"description": "需要操作的动作,打开设备:turn_on,关闭设备:turn_off,增加亮度:brightness_up,降低亮度:brightness_down,设置亮度:brightness_value,增加音量:volume_up,降低音量:volume_down,设置音量:volume_set,设置色温:set_kelvin,设置颜色:set_color,设备暂停:pause,设备继续:continue,静音/取消静音:volume_mute",
|
||||
},
|
||||
"input": {
|
||||
"type": "integer",
|
||||
"description": "只有在设置音量,设置亮度时候才需要,有效值为1-100,对应音量和亮度的1%-100%"
|
||||
"description": "只有在设置音量,设置亮度时候才需要,有效值为1-100,对应音量和亮度的1%-100%",
|
||||
},
|
||||
"is_muted": {
|
||||
"type": "string",
|
||||
"description": "只有在设置静音操作时才需要,设置静音的时候该值为true,取消静音时该值为false"
|
||||
"description": "只有在设置静音操作时才需要,设置静音的时候该值为true,取消静音时该值为false",
|
||||
},
|
||||
"rgb_color": {
|
||||
"type": "list",
|
||||
"description": "只有在设置颜色时需要,这里填目标颜色的rgb值",
|
||||
},
|
||||
"rgb_color":{
|
||||
"type":"list",
|
||||
"description": "只有在设置颜色时需要,这里填目标颜色的rgb值"
|
||||
}
|
||||
},
|
||||
"required": ["type"]
|
||||
"required": ["type"],
|
||||
},
|
||||
"entity_id": {
|
||||
"type": "string",
|
||||
"description": "需要操作的设备id,homeassistant里的entity_id"
|
||||
}
|
||||
"description": "需要操作的设备id,homeassistant里的entity_id",
|
||||
},
|
||||
},
|
||||
"required": ["state", "entity_id"]
|
||||
}
|
||||
}
|
||||
"required": ["state", "entity_id"],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@register_function('hass_set_state', hass_set_state_function_desc, ToolType.SYSTEM_CTL)
|
||||
def hass_set_state(conn, entity_id='', state={}):
|
||||
@register_function("hass_set_state", hass_set_state_function_desc, ToolType.SYSTEM_CTL)
|
||||
def hass_set_state(conn, entity_id="", state={}):
|
||||
try:
|
||||
future = asyncio.run_coroutine_threadsafe(
|
||||
handle_hass_set_state(conn, entity_id, state),
|
||||
conn.loop
|
||||
handle_hass_set_state(conn, entity_id, state), conn.loop
|
||||
)
|
||||
ha_response = future.result()
|
||||
return ActionResponse(Action.REQLLM, ha_response, None)
|
||||
@@ -62,21 +61,21 @@ def hass_set_state(conn, entity_id='', state={}):
|
||||
|
||||
|
||||
async def handle_hass_set_state(conn, entity_id, state):
|
||||
HASS_CACHE = initialize_hass_handler(conn)
|
||||
api_key = HASS_CACHE['api_key']
|
||||
base_url = HASS_CACHE['base_url']
|
||||
'''
|
||||
ha_config = initialize_hass_handler(conn)
|
||||
api_key = ha_config.get("api_key")
|
||||
base_url = ha_config.get("base_url")
|
||||
"""
|
||||
state = { "type":"brightness_up","input":"80","is_muted":"true"}
|
||||
'''
|
||||
"""
|
||||
domains = entity_id.split(".")
|
||||
if len(domains) > 1:
|
||||
domain = domains[0]
|
||||
else:
|
||||
return "执行失败,错误的设备id"
|
||||
action = ''
|
||||
arg = ''
|
||||
value = ''
|
||||
if state['type'] == 'turn_on':
|
||||
action = ""
|
||||
arg = ""
|
||||
value = ""
|
||||
if state["type"] == "turn_on":
|
||||
description = "设备已打开"
|
||||
if domain == "cover":
|
||||
action = "open_cover"
|
||||
@@ -84,91 +83,87 @@ async def handle_hass_set_state(conn, entity_id, state):
|
||||
action = "start"
|
||||
else:
|
||||
action = "turn_on"
|
||||
elif state['type'] == 'turn_off':
|
||||
elif state["type"] == "turn_off":
|
||||
description = "设备已关闭"
|
||||
if domain == 'cover':
|
||||
if domain == "cover":
|
||||
action = "close_cover"
|
||||
elif domain == 'vacuum':
|
||||
elif domain == "vacuum":
|
||||
action = "stop"
|
||||
else:
|
||||
action = "turn_off"
|
||||
elif state['type'] == 'brightness_up':
|
||||
elif state["type"] == "brightness_up":
|
||||
description = "灯光已调亮"
|
||||
action = 'turn_on'
|
||||
arg = 'brightness_step_pct'
|
||||
action = "turn_on"
|
||||
arg = "brightness_step_pct"
|
||||
value = 10
|
||||
elif state['type'] == 'brightness_down':
|
||||
elif state["type"] == "brightness_down":
|
||||
description = "灯光已调暗"
|
||||
action = 'turn_on'
|
||||
arg = 'brightness_step_pct'
|
||||
action = "turn_on"
|
||||
arg = "brightness_step_pct"
|
||||
value = -10
|
||||
elif state['type'] == 'brightness_value':
|
||||
elif state["type"] == "brightness_value":
|
||||
description = f"亮度已调整到{state['input']}"
|
||||
action = 'turn_on'
|
||||
arg = 'brightness_pct'
|
||||
value = state['input']
|
||||
elif state['type'] == 'set_color':
|
||||
action = "turn_on"
|
||||
arg = "brightness_pct"
|
||||
value = state["input"]
|
||||
elif state["type"] == "set_color":
|
||||
description = f"颜色已调整到{state['rgb_color']}"
|
||||
action = 'turn_on'
|
||||
arg = 'rgb_color'
|
||||
value = state['rgb_color']
|
||||
elif state['type'] == 'set_kelvin':
|
||||
action = "turn_on"
|
||||
arg = "rgb_color"
|
||||
value = state["rgb_color"]
|
||||
elif state["type"] == "set_kelvin":
|
||||
description = f"色温已调整到{state['input']}K"
|
||||
action = 'turn_on'
|
||||
arg = 'kelvin'
|
||||
value = state['input']
|
||||
elif state['type'] == 'volume_up':
|
||||
action = "turn_on"
|
||||
arg = "kelvin"
|
||||
value = state["input"]
|
||||
elif state["type"] == "volume_up":
|
||||
description = "音量已调大"
|
||||
action = state['type']
|
||||
elif state['type'] == 'volume_down':
|
||||
action = state["type"]
|
||||
elif state["type"] == "volume_down":
|
||||
description = "音量已调小"
|
||||
action = state['type']
|
||||
elif state['type'] == 'volume_set':
|
||||
action = state["type"]
|
||||
elif state["type"] == "volume_set":
|
||||
description = f"音量已调整到{state['input']}"
|
||||
action = state['type']
|
||||
arg = 'volume_level'
|
||||
value = state['input']
|
||||
if state['input'] >= 1:
|
||||
value = state['input']/100
|
||||
elif state['type'] == 'volume_mute':
|
||||
action = state["type"]
|
||||
arg = "volume_level"
|
||||
value = state["input"]
|
||||
if state["input"] >= 1:
|
||||
value = state["input"] / 100
|
||||
elif state["type"] == "volume_mute":
|
||||
description = f"设备已静音"
|
||||
action = state['type']
|
||||
arg = 'is_volume_muted'
|
||||
value = state['is_muted']
|
||||
elif state['type'] == 'pause':
|
||||
action = state["type"]
|
||||
arg = "is_volume_muted"
|
||||
value = state["is_muted"]
|
||||
elif state["type"] == "pause":
|
||||
description = f"设备已暂停"
|
||||
action = state['type']
|
||||
if domain == 'media_player':
|
||||
action = 'media_pause'
|
||||
if domain == 'cover':
|
||||
action = 'stop_cover'
|
||||
if domain == 'vacuum':
|
||||
action = 'pause'
|
||||
elif state['type'] == 'continue':
|
||||
action = state["type"]
|
||||
if domain == "media_player":
|
||||
action = "media_pause"
|
||||
if domain == "cover":
|
||||
action = "stop_cover"
|
||||
if domain == "vacuum":
|
||||
action = "pause"
|
||||
elif state["type"] == "continue":
|
||||
description = f"设备已继续"
|
||||
if domain == 'media_player':
|
||||
action = 'media_play'
|
||||
if domain == 'vacuum':
|
||||
action = 'start'
|
||||
if domain == "media_player":
|
||||
action = "media_play"
|
||||
if domain == "vacuum":
|
||||
action = "start"
|
||||
else:
|
||||
return f"{domain} {state.type}功能尚未支持"
|
||||
|
||||
if arg == '':
|
||||
if arg == "":
|
||||
data = {
|
||||
"entity_id": entity_id,
|
||||
}
|
||||
else:
|
||||
data = {
|
||||
"entity_id": entity_id,
|
||||
arg: value
|
||||
}
|
||||
data = {"entity_id": entity_id, arg: value}
|
||||
url = f"{base_url}/api/services/{domain}/{action}"
|
||||
headers = {
|
||||
"Authorization": f"Bearer {api_key}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
|
||||
response = requests.post(url, headers=headers, json=data)
|
||||
logger.bind(tag=TAG).info(f"设置状态:{description},url:{url},return_code:{response.status_code}")
|
||||
logger.bind(tag=TAG).info(
|
||||
f"设置状态:{description},url:{url},return_code:{response.status_code}"
|
||||
)
|
||||
if response.status_code == 200:
|
||||
return description
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user