Files
2026-06-16 09:19:32 +03:00

117 lines
4.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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