117 lines
4.6 KiB
Python
117 lines
4.6 KiB
Python
from typing import Any
|
||
|
||
from app.homelab.digest import build_weather_briefing
|
||
from app.homelab.image_gen import generate_image as run_generate_image
|
||
from app.homelab.openmeteo import OpenMeteoClient
|
||
from app.tools._dispatch import NOT_HANDLED, ToolContext
|
||
|
||
TOOL_NAMES = frozenset({
|
||
"get_weather",
|
||
"get_morning_briefing",
|
||
"generate_image",
|
||
})
|
||
|
||
TOOL_DEFINITIONS: list[dict[str, Any]] = [
|
||
{
|
||
"type": "function",
|
||
"function": {
|
||
"name": "get_weather",
|
||
"description": (
|
||
"ОБЯЗАТЕЛЬНО для вопросов о погоде, «что на улице», «будет ли дождь», «завтра», «на неделю». "
|
||
"Текущая погода, почасовой и дневной прогноз."
|
||
),
|
||
"parameters": {
|
||
"type": "object",
|
||
"properties": {
|
||
"hours_ahead": {
|
||
"type": "integer",
|
||
"description": "Сколько часов почасового прогноза (по умолчанию 12, до 168)",
|
||
},
|
||
"days_ahead": {
|
||
"type": "integer",
|
||
"description": "Сколько дней дневного прогноза (по умолчанию 7, до 16)",
|
||
},
|
||
},
|
||
"required": [],
|
||
},
|
||
},
|
||
},
|
||
{
|
||
"type": "function",
|
||
"function": {
|
||
"name": "get_morning_briefing",
|
||
"description": "Утренний брифинг: погода и заголовки новостей.",
|
||
"parameters": {
|
||
"type": "object",
|
||
"properties": {
|
||
"include_news": {
|
||
"type": "boolean",
|
||
"description": "Включить новости (по умолчанию true)",
|
||
},
|
||
},
|
||
"required": [],
|
||
},
|
||
},
|
||
},
|
||
{
|
||
"type": "function",
|
||
"function": {
|
||
"name": "generate_image",
|
||
"description": (
|
||
"Аниме-картинка (Anima). draw_self=true — персонаж из карточки; "
|
||
"scene_description — поза/кадр/одежда (booru-теги на англ. или короткий запрос: "
|
||
"full body, sitting, apron). Можно оба параметра: draw_self + scene_description. "
|
||
"Внешность только из appearance_tags карточки."
|
||
),
|
||
"parameters": {
|
||
"type": "object",
|
||
"properties": {
|
||
"draw_self": {
|
||
"type": "boolean",
|
||
"description": "Нарисовать персонажа из карточки",
|
||
},
|
||
"scene_description": {
|
||
"type": "string",
|
||
"description": (
|
||
"Поза, кадр, одежда, обстановка — booru-теги или запрос "
|
||
"(full_body, standing, apron, blush). С draw_self=true — уточняет сцену."
|
||
),
|
||
},
|
||
},
|
||
"required": [],
|
||
},
|
||
},
|
||
},
|
||
]
|
||
|
||
|
||
async def execute(name: str, arguments: dict[str, Any], ctx: ToolContext) -> Any:
|
||
if name not in TOOL_NAMES:
|
||
return NOT_HANDLED
|
||
|
||
if name == "get_weather":
|
||
hours = max(1, min(int(arguments.get("hours_ahead") or 12), 168))
|
||
days = max(1, min(int(arguments.get("days_ahead") or 7), 16))
|
||
client = OpenMeteoClient()
|
||
weather = client.fetch_forecast(hours_ahead=hours, days_ahead=days)
|
||
return {
|
||
"weather": weather,
|
||
"rain_summary": client.rain_summary(hours_ahead=hours, daily=weather.get("daily")) if weather.get("ok") else "",
|
||
"daily_summary": client.daily_summary(days_ahead=days) if weather.get("ok") else "",
|
||
}
|
||
if name == "get_morning_briefing":
|
||
include_news = arguments.get("include_news", True)
|
||
return build_weather_briefing(
|
||
hours_ahead=12,
|
||
include_news=bool(include_news),
|
||
)
|
||
if name == "generate_image":
|
||
return await run_generate_image(
|
||
ctx.db,
|
||
user_id=ctx.user_id,
|
||
session_id=ctx.session_id,
|
||
draw_self=bool(arguments.get("draw_self")),
|
||
scene_description=arguments.get("scene_description", ""),
|
||
)
|
||
return NOT_HANDLED
|