init
This commit is contained in:
7
agents/air_cleaner_agent/__init__.py
Normal file
7
agents/air_cleaner_agent/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
"""
|
||||
Air Cleaner Agent - Moss AI 空气净化器控制系统
|
||||
控制桌面空气净化器(zhimi-oa1)
|
||||
"""
|
||||
|
||||
__version__ = "1.0.0"
|
||||
|
||||
108
agents/air_cleaner_agent/__main__.py
Normal file
108
agents/air_cleaner_agent/__main__.py
Normal file
@@ -0,0 +1,108 @@
|
||||
"""
|
||||
主入口点 - 用于 `uv run .` 或 `python -m air_cleaner_agent` 启动服务
|
||||
空气净化器 Agent
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
import click
|
||||
import logging
|
||||
import uvicorn
|
||||
from a2a.types import (
|
||||
AgentCapabilities,
|
||||
AgentCard,
|
||||
AgentSkill,
|
||||
)
|
||||
from a2a.server.apps import A2AStarletteApplication
|
||||
from a2a.server.request_handlers import DefaultRequestHandler
|
||||
import httpx
|
||||
from a2a.server.tasks import (
|
||||
BasePushNotificationSender,
|
||||
InMemoryPushNotificationConfigStore,
|
||||
InMemoryTaskStore,
|
||||
)
|
||||
|
||||
# 确保当前目录和父目录在 Python 路径中
|
||||
current_dir = Path(__file__).parent
|
||||
parent_dir = current_dir.parent
|
||||
if str(current_dir) not in sys.path:
|
||||
sys.path.insert(0, str(current_dir))
|
||||
if str(parent_dir) not in sys.path:
|
||||
sys.path.insert(0, str(parent_dir))
|
||||
|
||||
from executor import AirPurifierAgentExecutor
|
||||
from agent import AirPurifierAgent
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.option("--host", "host", default=None, help="服务主机地址(默认从config.yaml读取)")
|
||||
@click.option("--port", "port", default=None, type=int, help="服务端口(默认从config.yaml读取)")
|
||||
def main(host, port):
|
||||
"""Starts the Air Purifier Agent server."""
|
||||
try:
|
||||
# 从配置文件读取 host 和 port(如果命令行未指定)
|
||||
if host is None or port is None:
|
||||
from config_loader import get_config_loader
|
||||
config_loader = get_config_loader(strict_mode=False)
|
||||
default_host, default_port = config_loader.get_agent_host_port('air_cleaner')
|
||||
host = host or default_host
|
||||
port = port or default_port
|
||||
|
||||
capabilities = AgentCapabilities(
|
||||
push_notifications=False,
|
||||
state_transition_history=False,
|
||||
streaming=False,
|
||||
)
|
||||
skill = AgentSkill(
|
||||
id="control_air_purifier",
|
||||
name="Air Purifier Control",
|
||||
description="控制桌面空气净化器(zhimi-oa1),包括电源、风扇等级、工作模式、LED亮度,查询PM2.5、湿度、滤芯寿命等",
|
||||
tags=["air purifier", "air quality", "PM2.5", "home automation", "smart home"],
|
||||
examples=[
|
||||
"打开空气净化器",
|
||||
"查询当前PM2.5",
|
||||
"设置为睡眠模式",
|
||||
"把风扇调到高速",
|
||||
"关闭LED灯",
|
||||
],
|
||||
)
|
||||
agent_card = AgentCard(
|
||||
name="Air Purifier Agent",
|
||||
description="桌面空气净化器(zhimi-oa1)控制的专业助手",
|
||||
url=f"http://{host}:{port}/",
|
||||
version="1.0.0",
|
||||
default_input_modes=AirPurifierAgent.SUPPORTED_CONTENT_TYPES,
|
||||
default_output_modes=AirPurifierAgent.SUPPORTED_CONTENT_TYPES,
|
||||
capabilities=capabilities,
|
||||
skills=[skill],
|
||||
)
|
||||
|
||||
# --8<-- [start:DefaultRequestHandler]
|
||||
httpx_client = httpx.AsyncClient()
|
||||
push_config_store = InMemoryPushNotificationConfigStore()
|
||||
push_sender = BasePushNotificationSender(
|
||||
httpx_client=httpx_client, config_store=push_config_store
|
||||
)
|
||||
request_handler = DefaultRequestHandler(
|
||||
agent_executor=AirPurifierAgentExecutor(),
|
||||
task_store=InMemoryTaskStore(),
|
||||
push_config_store=push_config_store,
|
||||
push_sender=push_sender,
|
||||
)
|
||||
server = A2AStarletteApplication(
|
||||
agent_card=agent_card, http_handler=request_handler
|
||||
)
|
||||
|
||||
uvicorn.run(server.build(), host=host, port=port)
|
||||
# --8<-- [end:DefaultRequestHandler]
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"An error occurred during server startup: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
179
agents/air_cleaner_agent/agent.py
Normal file
179
agents/air_cleaner_agent/agent.py
Normal file
@@ -0,0 +1,179 @@
|
||||
from typing import Any
|
||||
from langchain_core.messages import AIMessage, ToolMessage
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
|
||||
import logging
|
||||
import sys
|
||||
import os
|
||||
|
||||
# 添加父目录到路径以导入config_loader
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
from config_loader import get_config_loader
|
||||
|
||||
from tools import (
|
||||
get_purifier_status,
|
||||
set_purifier_power,
|
||||
set_purifier_mode,
|
||||
set_purifier_fan_level,
|
||||
set_purifier_led,
|
||||
set_purifier_alarm,
|
||||
set_purifier_child_lock
|
||||
)
|
||||
|
||||
memory = MemorySaver()
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class AirPurifierAgent:
|
||||
SUPPORTED_CONTENT_TYPES = ['text', 'text/plain']
|
||||
|
||||
# 默认系统提示词(备用)
|
||||
DEFAULT_SYSTEM_PROMPT = (
|
||||
'你是一个专门的桌面空气净化器控制助手(型号:zhimi-oa1)。'
|
||||
'你的唯一目的是帮助用户控制他们的桌面空气净化器。'
|
||||
'你可以帮助:开关净化器、查看空气质量(PM2.5、湿度)、调节风扇等级(1-4档)、'
|
||||
'控制LED按键亮度、提示音开关、童锁、查看滤芯寿命等。'
|
||||
'如果用户询问与空气净化器控制或空气质量无关的内容,'
|
||||
'请礼貌地说明你无法帮助处理该主题,只能协助处理与空气净化器相关的问题。'
|
||||
'不要尝试回答无关问题或将工具用于其他目的。'
|
||||
''
|
||||
'工具使用指南:'
|
||||
'1. 查询状态:当用户请求查询设备状态、空气质量、PM2.5、湿度、滤芯等信息时,'
|
||||
' 调用 get_purifier_status 获取最新状态,并用中文友好地展示关键信息。'
|
||||
' 重点关注:电源状态、PM2.5值、湿度、风扇等级、滤芯剩余寿命。'
|
||||
''
|
||||
'2. 电源控制:当用户说"打开/开启/启动净化器"时,调用 set_purifier_power(power=True);'
|
||||
' 说"关闭/关掉净化器"时,调用 set_purifier_power(power=False)。'
|
||||
''
|
||||
'3. 工作模式:支持0=自动模式(根据PM2.5自动调节)、1=睡眠模式(低噪音)、2=手动模式(手动设置风扇等级)。'
|
||||
' 使用 set_purifier_mode(mode=0/1/2) 设置。注意:要手动设置风扇等级,设备必须先切换到手动模式(mode=2)。'
|
||||
''
|
||||
'4. 风扇等级:支持1-4档,当用户说"一档/最小风"时设为1,"二档"时设为2,'
|
||||
' "三档"时设为3,"四档/最大风/强力"时设为4,使用 set_purifier_fan_level(level=1/2/3/4)。'
|
||||
' **重要**:set_purifier_fan_level 工具会自动检查并切换到手动模式,无需手动调用 set_purifier_mode。'
|
||||
''
|
||||
'5. LED控制:当用户说"开启LED/开灯"时设为True,"关闭LED/关灯"时设为False,'
|
||||
' 使用 set_purifier_led(brightness=True/False)。'
|
||||
''
|
||||
'6. 提示音控制:当用户说"开启提示音/打开声音"时设为True,"关闭提示音/静音"时设为False,'
|
||||
' 使用 set_purifier_alarm(alarm=True/False)。'
|
||||
''
|
||||
'7. 童锁控制:当用户说"开启童锁/锁定按键"时设为True,"关闭童锁/解锁按键"时设为False,'
|
||||
' 使用 set_purifier_child_lock(child_lock=True/False)。'
|
||||
''
|
||||
'8. 智能场景建议:'
|
||||
' - 空气质量差(PM2.5>75):建议开启并设为高速档(4档)或自动模式'
|
||||
' - 睡眠时段:建议设为睡眠模式(mode=1)或低速档(1档)+关闭LED+关闭提示音'
|
||||
' - 滤芯寿命<10%:提醒用户更换滤芯'
|
||||
' - 空气质量好(PM2.5<35):可建议降低风扇等级、切换到自动模式或关闭以节能'
|
||||
''
|
||||
'始终用友好、简洁的中文回复用户,优先展示用户最关心的信息。'
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
# 从数据库加载配置(严格模式:配置加载失败则退出)
|
||||
try:
|
||||
config_loader = get_config_loader(strict_mode=True)
|
||||
|
||||
# 加载AI模型配置
|
||||
ai_config = config_loader.get_default_ai_model_config()
|
||||
self.model = ChatOpenAI(
|
||||
model=ai_config['model'],
|
||||
api_key=ai_config['api_key'],
|
||||
base_url=ai_config['api_base'],
|
||||
temperature=ai_config['temperature'],
|
||||
)
|
||||
|
||||
# 加载系统提示词
|
||||
system_prompt = config_loader.get_agent_prompt('air_cleaner')
|
||||
self.SYSTEM_PROMPT = system_prompt
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ 配置加载失败: {e}")
|
||||
logger.error("⚠️ 请确保:")
|
||||
logger.error(" 1. StarRocks 数据库已启动")
|
||||
logger.error(" 2. 已执行数据库初始化脚本: data/init_config.sql 和 data/ai_config.sql")
|
||||
logger.error(" 3. config.yaml 中的数据库连接配置正确")
|
||||
raise SystemExit(1) from e
|
||||
|
||||
self.tools = [
|
||||
get_purifier_status,
|
||||
set_purifier_power,
|
||||
set_purifier_mode,
|
||||
set_purifier_fan_level,
|
||||
set_purifier_led,
|
||||
set_purifier_alarm,
|
||||
set_purifier_child_lock
|
||||
]
|
||||
|
||||
self.graph = create_react_agent(
|
||||
self.model,
|
||||
tools=self.tools,
|
||||
checkpointer=memory,
|
||||
prompt=self.SYSTEM_PROMPT,
|
||||
)
|
||||
|
||||
async def invoke(self, query, context_id) -> dict[str, Any]:
|
||||
"""非流式调用,直接返回最终结果"""
|
||||
inputs = {'messages': [('user', query)]}
|
||||
config = {'configurable': {'thread_id': context_id}}
|
||||
|
||||
# 直接调用invoke,不使用stream
|
||||
result = self.graph.invoke(inputs, config)
|
||||
|
||||
return self.get_agent_response(config)
|
||||
|
||||
def _extract_text_from_message(self, msg: AIMessage | ToolMessage | Any) -> str:
|
||||
try:
|
||||
content = getattr(msg, 'content', None)
|
||||
if isinstance(content, str):
|
||||
return content
|
||||
if isinstance(content, list):
|
||||
parts = []
|
||||
for part in content:
|
||||
if isinstance(part, dict) and 'text' in part:
|
||||
parts.append(part['text'])
|
||||
if parts:
|
||||
return '\n'.join(parts)
|
||||
except Exception:
|
||||
pass
|
||||
return ''
|
||||
|
||||
def get_agent_response(self, config):
|
||||
current_state = self.graph.get_state(config)
|
||||
messages = current_state.values.get('messages') if hasattr(current_state, 'values') else None
|
||||
|
||||
# 优先返回最近一次工具消息内容
|
||||
if isinstance(messages, list) and messages:
|
||||
for msg in reversed(messages):
|
||||
if isinstance(msg, ToolMessage):
|
||||
tool_text = self._extract_text_from_message(msg)
|
||||
if tool_text:
|
||||
return {
|
||||
'is_task_complete': True,
|
||||
'require_user_input': False,
|
||||
'content': tool_text,
|
||||
}
|
||||
|
||||
# 回退到最后一条 AI 消息
|
||||
final_text = ''
|
||||
if isinstance(messages, list) and messages:
|
||||
last_msg = messages[-1]
|
||||
final_text = self._extract_text_from_message(last_msg)
|
||||
|
||||
if not final_text:
|
||||
return {
|
||||
'is_task_complete': False,
|
||||
'require_user_input': True,
|
||||
'content': '当前无法处理您的请求,请稍后重试。',
|
||||
}
|
||||
|
||||
return {
|
||||
'is_task_complete': True,
|
||||
'require_user_input': False,
|
||||
'content': final_text,
|
||||
}
|
||||
|
||||
96
agents/air_cleaner_agent/executor.py
Normal file
96
agents/air_cleaner_agent/executor.py
Normal file
@@ -0,0 +1,96 @@
|
||||
import logging
|
||||
|
||||
from a2a.server.agent_execution import AgentExecutor, RequestContext
|
||||
from a2a.server.events import EventQueue
|
||||
from a2a.server.tasks import TaskUpdater
|
||||
from a2a.types import (
|
||||
InternalError,
|
||||
InvalidParamsError,
|
||||
Part,
|
||||
TaskState,
|
||||
TextPart,
|
||||
UnsupportedOperationError,
|
||||
)
|
||||
from a2a.utils import (
|
||||
new_agent_text_message,
|
||||
new_task,
|
||||
)
|
||||
from a2a.utils.errors import ServerError
|
||||
|
||||
from agent import AirPurifierAgent
|
||||
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AirPurifierAgentExecutor(AgentExecutor):
|
||||
"""Air Purifier AgentExecutor."""
|
||||
|
||||
def __init__(self):
|
||||
self.agent = AirPurifierAgent()
|
||||
|
||||
async def execute(
|
||||
self,
|
||||
context: RequestContext,
|
||||
event_queue: EventQueue,
|
||||
) -> None:
|
||||
error = self._validate_request(context)
|
||||
if error:
|
||||
raise ServerError(error=InvalidParamsError())
|
||||
|
||||
query = context.get_user_input()
|
||||
task = context.current_task
|
||||
if not task:
|
||||
task = new_task(context.message) # type: ignore
|
||||
await event_queue.enqueue_event(task)
|
||||
updater = TaskUpdater(event_queue, task.id, task.context_id)
|
||||
try:
|
||||
# 使用非流式invoke方法
|
||||
result = await self.agent.invoke(query, task.context_id)
|
||||
|
||||
is_task_complete = result.get('is_task_complete', True)
|
||||
require_user_input = result.get('require_user_input', False)
|
||||
content = result.get('content', '处理完成')
|
||||
|
||||
if require_user_input:
|
||||
await updater.update_status(
|
||||
TaskState.input_required,
|
||||
new_agent_text_message(
|
||||
content,
|
||||
task.context_id,
|
||||
task.id,
|
||||
),
|
||||
final=True,
|
||||
)
|
||||
elif is_task_complete:
|
||||
await updater.add_artifact(
|
||||
[Part(root=TextPart(text=content))],
|
||||
name='purifier_status_result',
|
||||
)
|
||||
await updater.complete()
|
||||
else:
|
||||
# 如果既不需要输入也未完成,设置为working状态
|
||||
await updater.update_status(
|
||||
TaskState.working,
|
||||
new_agent_text_message(
|
||||
content,
|
||||
task.context_id,
|
||||
task.id,
|
||||
),
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f'An error occurred while processing the request: {e}')
|
||||
raise ServerError(error=InternalError()) from e
|
||||
|
||||
def _validate_request(self, context: RequestContext) -> bool:
|
||||
# 这里可以添加请求验证逻辑
|
||||
# 返回 True 表示有错误,False 表示验证通过
|
||||
return False
|
||||
|
||||
async def cancel(
|
||||
self, context: RequestContext, event_queue: EventQueue
|
||||
) -> None:
|
||||
raise ServerError(error=UnsupportedOperationError())
|
||||
|
||||
26
agents/air_cleaner_agent/pyproject.toml
Normal file
26
agents/air_cleaner_agent/pyproject.toml
Normal file
@@ -0,0 +1,26 @@
|
||||
[project]
|
||||
name = "air-cleaner-agent"
|
||||
version = "1.0.0"
|
||||
description = "Moss AI 空气净化器控制 Agent"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
dependencies = [
|
||||
"a2a>=0.1.0",
|
||||
"click>=8.0.0",
|
||||
"uvicorn[standard]>=0.24.0",
|
||||
"httpx>=0.25.0",
|
||||
"PyYAML>=6.0.0",
|
||||
"starlette>=0.27.0",
|
||||
"python-miio>=0.5.0",
|
||||
]
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.uv]
|
||||
dev-dependencies = []
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["."]
|
||||
|
||||
251
agents/air_cleaner_agent/tools.py
Normal file
251
agents/air_cleaner_agent/tools.py
Normal file
@@ -0,0 +1,251 @@
|
||||
from langchain_core.tools import tool
|
||||
from miio import DeviceFactory
|
||||
from miio.miot_device import MiotDevice
|
||||
import json
|
||||
from pydantic import BaseModel, Field
|
||||
import logging
|
||||
import threading
|
||||
|
||||
# 配置日志
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# 设备配置
|
||||
PURIFIER_IP = "192.168.110.120"
|
||||
PURIFIER_TOKEN = "569905df67a11d6b67a575097255c798"
|
||||
PURIFIER_MODEL = "zhimi.airp.oa1"
|
||||
|
||||
# 创建设备实例(使用 DeviceFactory 自动识别设备类型)
|
||||
device = DeviceFactory.create(PURIFIER_IP, PURIFIER_TOKEN)
|
||||
|
||||
# 同时保留 MiotDevice 实例用于属性设置
|
||||
miot_device = MiotDevice(
|
||||
ip=PURIFIER_IP,
|
||||
token=PURIFIER_TOKEN,
|
||||
model=PURIFIER_MODEL
|
||||
)
|
||||
|
||||
# 添加线程锁,确保同一时间只有一个操作
|
||||
device_lock = threading.Lock()
|
||||
|
||||
|
||||
@tool("get_purifier_status", description="获取空气净化器当前状态,包括电源、PM2.5、湿度、风扇等级、工作模式、滤芯寿命等信息")
|
||||
def get_purifier_status():
|
||||
"""获取空气净化器设备状态并以 JSON 格式返回"""
|
||||
try:
|
||||
with device_lock: # 使用锁确保串行执行
|
||||
# 使用 status() 方法一次性获取所有状态
|
||||
status_obj = device.status()
|
||||
|
||||
# 获取状态数据字典
|
||||
if hasattr(status_obj, 'data'):
|
||||
status_data = status_obj.data
|
||||
# 添加额外信息
|
||||
status_data['online'] = True
|
||||
status_data['model'] = PURIFIER_MODEL
|
||||
return json.dumps(status_data, indent=2, ensure_ascii=False, default=str)
|
||||
else:
|
||||
# 降级方案:如果没有 data 属性,手动获取关键属性
|
||||
logger.warning("设备状态对象没有 data 属性,使用降级方案")
|
||||
power = miot_device.get_property_by(2, 1)
|
||||
fan_level = miot_device.get_property_by(2, 5) # 正确的 PIID
|
||||
led = miot_device.get_property_by(2, 6)
|
||||
|
||||
status = {
|
||||
"power": power[0].get('value') if isinstance(power, list) and len(power) > 0 else power,
|
||||
"fan_level": fan_level[0].get('value') if isinstance(fan_level, list) and len(fan_level) > 0 else fan_level,
|
||||
"led_brightness": led[0].get('value') if isinstance(led, list) and len(led) > 0 else led,
|
||||
"online": True,
|
||||
"model": PURIFIER_MODEL
|
||||
}
|
||||
return json.dumps(status, indent=2, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
logger.error(f"获取空气净化器状态失败: {e}")
|
||||
error_status = {
|
||||
"error": f"获取设备状态失败: {str(e)}",
|
||||
"message": "请检查:\n1. 设备是否已开启并连接到网络\n2. 设备IP地址是否配置正确(当前配置:{ip})\n3. 设备Token是否正确".format(ip=PURIFIER_IP),
|
||||
"online": False,
|
||||
"model": PURIFIER_MODEL
|
||||
}
|
||||
return json.dumps(error_status, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
class PowerArgs(BaseModel):
|
||||
power: bool = Field(..., description="空气净化器电源状态,true 开启,false 关闭")
|
||||
|
||||
|
||||
@tool("set_purifier_power", args_schema=PowerArgs, description="开启或关闭空气净化器。power=true 开启,power=false 关闭")
|
||||
def set_purifier_power(power: bool):
|
||||
"""开启或关闭空气净化器"""
|
||||
try:
|
||||
with device_lock: # 使用锁确保串行执行
|
||||
result = miot_device.set_property_by(2, 1, power)
|
||||
action = "开启" if power else "关闭"
|
||||
logger.info(f"空气净化器已{action}")
|
||||
return json.dumps({
|
||||
"message": f"空气净化器已{action}",
|
||||
"power": power,
|
||||
"result": str(result)
|
||||
}, indent=2, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
logger.error(f"设置空气净化器电源失败: {e}")
|
||||
return json.dumps({
|
||||
"error": f"设置电源状态失败: {str(e)}",
|
||||
"message": "请检查:\n1. 设备是否已开启并连接到网络\n2. 设备IP地址是否配置正确(当前配置:{ip})\n3. 设备Token是否正确".format(ip=PURIFIER_IP),
|
||||
"online": False,
|
||||
"model": PURIFIER_MODEL
|
||||
}, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
class ModeArgs(BaseModel):
|
||||
mode: int = Field(..., ge=0, le=2, description="工作模式:0=自动模式,1=睡眠模式,2=手动模式")
|
||||
|
||||
|
||||
@tool("set_purifier_mode", args_schema=ModeArgs, description="设置空气净化器工作模式(0=自动,1=睡眠,2=手动)")
|
||||
def set_purifier_mode(mode: int):
|
||||
"""设置空气净化器工作模式"""
|
||||
try:
|
||||
with device_lock:
|
||||
result = miot_device.set_property_by(2, 4, mode) # PIID 4: mode
|
||||
mode_names = {0: "自动模式", 1: "睡眠模式", 2: "手动模式"}
|
||||
mode_name = mode_names.get(mode, f"模式{mode}")
|
||||
logger.info(f"工作模式已设置为{mode_name}")
|
||||
return json.dumps({
|
||||
"message": f"工作模式已设置为{mode_name}",
|
||||
"mode": mode,
|
||||
"mode_name": mode_name,
|
||||
"result": str(result)
|
||||
}, indent=2, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
logger.error(f"设置空气净化器工作模式失败: {e}")
|
||||
return json.dumps({
|
||||
"error": f"设置工作模式失败: {str(e)}",
|
||||
"message": "请检查:\n1. 设备是否已开启并连接到网络\n2. 设备IP地址是否配置正确(当前配置:{ip})\n3. 设备Token是否正确".format(ip=PURIFIER_IP),
|
||||
"online": False,
|
||||
"model": PURIFIER_MODEL
|
||||
}, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
class FanLevelArgs(BaseModel):
|
||||
level: int = Field(..., ge=1, le=4, description="风扇等级,范围 1-4 (1档、2档、3档、4档)")
|
||||
|
||||
|
||||
@tool("set_purifier_fan_level", args_schema=FanLevelArgs, description="设置空气净化器风扇等级(1档、2档、3档、4档)")
|
||||
def set_purifier_fan_level(level: int):
|
||||
"""设置空气净化器风扇等级
|
||||
|
||||
注意:要手动设置风扇等级,设备必须处于手动模式(mode=2)。
|
||||
如果设备处于自动模式,会自动先切换到手动模式。
|
||||
"""
|
||||
try:
|
||||
with device_lock:
|
||||
# 先检查当前模式
|
||||
try:
|
||||
current_mode_result = miot_device.get_property_by(2, 4)
|
||||
current_mode = current_mode_result[0].get('value') if isinstance(current_mode_result, list) else current_mode_result
|
||||
|
||||
# 如果不是手动模式(mode != 2),先切换到手动模式
|
||||
if current_mode != 2:
|
||||
logger.info(f"当前为模式{current_mode},需要先切换到手动模式才能设置风扇等级")
|
||||
mode_result = miot_device.set_property_by(2, 4, 2) # 切换到手动模式
|
||||
logger.info(f"已自动切换到手动模式: {mode_result}")
|
||||
except Exception as mode_error:
|
||||
logger.warning(f"获取/切换模式时出错,继续尝试设置风扇等级: {mode_error}")
|
||||
|
||||
# 设置风扇等级
|
||||
result = miot_device.set_property_by(2, 5, level) # PIID 5: fan_level
|
||||
logger.info(f"风扇等级已设置为{level}档")
|
||||
return json.dumps({
|
||||
"message": f"风扇等级已设置为{level}档(已切换到手动模式)",
|
||||
"fan_level": level,
|
||||
"mode": "手动模式",
|
||||
"result": str(result)
|
||||
}, indent=2, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
logger.error(f"设置空气净化器风扇等级失败: {e}")
|
||||
return json.dumps({
|
||||
"error": f"设置风扇等级失败: {str(e)}",
|
||||
"message": "请检查:\n1. 设备是否已开启并连接到网络\n2. 设备IP地址是否配置正确(当前配置:{ip})\n3. 设备Token是否正确".format(ip=PURIFIER_IP),
|
||||
"online": False,
|
||||
"model": PURIFIER_MODEL
|
||||
}, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
class LEDBrightnessArgs(BaseModel):
|
||||
brightness: bool = Field(..., description="LED按键亮度开关,true 开启,false 关闭")
|
||||
|
||||
|
||||
@tool("set_purifier_led", args_schema=LEDBrightnessArgs, description="设置空气净化器LED按键亮度开关")
|
||||
def set_purifier_led(brightness: bool):
|
||||
"""设置空气净化器LED按键亮度"""
|
||||
try:
|
||||
with device_lock:
|
||||
result = miot_device.set_property_by(2, 6, brightness) # 正确的 PIID 是 (2, 6)
|
||||
status = "开启" if brightness else "关闭"
|
||||
logger.info(f"LED亮度已{status}")
|
||||
return json.dumps({
|
||||
"message": f"LED亮度已{status}",
|
||||
"led_brightness": brightness,
|
||||
"result": str(result)
|
||||
}, indent=2, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
logger.error(f"设置空气净化器LED失败: {e}")
|
||||
return json.dumps({
|
||||
"error": f"设置LED亮度失败: {str(e)}",
|
||||
"message": "请检查:\n1. 设备是否已开启并连接到网络\n2. 设备IP地址是否配置正确(当前配置:{ip})\n3. 设备Token是否正确".format(ip=PURIFIER_IP),
|
||||
"online": False,
|
||||
"model": PURIFIER_MODEL
|
||||
}, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
class AlarmArgs(BaseModel):
|
||||
alarm: bool = Field(..., description="提示音开关,true 开启,false 关闭")
|
||||
|
||||
|
||||
@tool("set_purifier_alarm", args_schema=AlarmArgs, description="设置空气净化器提示音开关")
|
||||
def set_purifier_alarm(alarm: bool):
|
||||
"""设置空气净化器提示音"""
|
||||
try:
|
||||
with device_lock:
|
||||
result = miot_device.set_property_by(2, 7, alarm) # PIID 7: alarm
|
||||
status = "开启" if alarm else "关闭"
|
||||
logger.info(f"提示音已{status}")
|
||||
return json.dumps({
|
||||
"message": f"提示音已{status}",
|
||||
"alarm": alarm,
|
||||
"result": str(result)
|
||||
}, indent=2, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
logger.error(f"设置空气净化器提示音失败: {e}")
|
||||
return json.dumps({
|
||||
"error": f"设置提示音失败: {str(e)}",
|
||||
"message": "请检查:\n1. 设备是否已开启并连接到网络\n2. 设备IP地址是否配置正确(当前配置:{ip})\n3. 设备Token是否正确".format(ip=PURIFIER_IP),
|
||||
"online": False,
|
||||
"model": PURIFIER_MODEL
|
||||
}, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
class ChildLockArgs(BaseModel):
|
||||
child_lock: bool = Field(..., description="童锁开关,true 开启,false 关闭")
|
||||
|
||||
|
||||
@tool("set_purifier_child_lock", args_schema=ChildLockArgs, description="设置空气净化器童锁(物理控制锁)")
|
||||
def set_purifier_child_lock(child_lock: bool):
|
||||
"""设置空气净化器童锁"""
|
||||
try:
|
||||
with device_lock:
|
||||
result = miot_device.set_property_by(2, 9, child_lock) # PIID 9: physical-controls-locked
|
||||
status = "开启" if child_lock else "关闭"
|
||||
logger.info(f"童锁已{status}")
|
||||
return json.dumps({
|
||||
"message": f"童锁已{status}",
|
||||
"child_lock": child_lock,
|
||||
"result": str(result)
|
||||
}, indent=2, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
logger.error(f"设置空气净化器童锁失败: {e}")
|
||||
return json.dumps({
|
||||
"error": f"设置童锁失败: {str(e)}",
|
||||
"message": "请检查:\n1. 设备是否已开启并连接到网络\n2. 设备IP地址是否配置正确(当前配置:{ip})\n3. 设备Token是否正确".format(ip=PURIFIER_IP),
|
||||
"online": False,
|
||||
"model": PURIFIER_MODEL
|
||||
}, indent=2, ensure_ascii=False)
|
||||
1574
agents/air_cleaner_agent/uv.lock
generated
Normal file
1574
agents/air_cleaner_agent/uv.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user