""" Adapter - Bridge entre BaseSkill (novo) e SkillRegistry (existente) Este arquivo adapta as skills agrupadas com fallbacks para funcionar com o sistema de registry/decorator existente no Akira. """ from typing import Dict, Any from modules.skills_registry import skill from modules.skills import ( WeatherSkill, EntertainmentSkill, ArtSkill, MusicSkill ) # ======================================== # Instâncias das skills agrupadas # ======================================== _weather_skill = WeatherSkill() _entertainment_skill = EntertainmentSkill() _art_skill = ArtSkill() _music_skill = MusicSkill() # ======================================== # TIER 2 — SKILLS AGRUPADAS COM FALLBACKS # ======================================== @skill( name="get_weather_grouped", description="Obtém previsão do tempo com múltiplas fontes. Tenta: Web Search → Weather API → Open-Meteo. Sempre retorna resultado válido.", parameters={ "type": "object", "properties": { "location": { "type": "string", "description": "Cidade ou local (ex: 'Lisboa', 'São Paulo', 'Luanda')" }, "unit": { "type": "string", "description": "Unidade de temperatura: 'celsius' (default) ou 'fahrenheit'", "default": "celsius" }, "include_forecast": { "type": "boolean", "description": "Incluir previsão dos próximos dias", "default": False } }, "required": ["location"] } ) def weather_grouped_tool(location: str, unit: str = "celsius", include_forecast: bool = False): """ Wrapper para WeatherSkill com fallbacks automáticos """ result = _weather_skill.execute( location=location, unit=unit, include_forecast=include_forecast, cache_ttl=3600 # Cache 1h ) # Transforma resposta para formato esperado if result.get("sucesso"): return { "sucesso": True, "clima": result["dados"], "provider": result["provider"], "cache": result.get("cache_hit", False) } else: return { "sucesso": False, "erro": result.get("erro"), "sugestao": result.get("sugestao") } @skill( name="get_entertainment", description="Retorna piadas, dicas ou citações inspiradoras com fallbacks automáticos. USE quando usuário pedir humor, conselho ou inspiração.", parameters={ "type": "object", "properties": { "tipo": { "type": "string", "description": "Tipo de entretenimento: 'joke' (piada), 'advice' (dica), 'quote' (citação), 'random' (aleatório)", "enum": ["joke", "advice", "quote", "random"], "default": "random" }, "idioma": { "type": "string", "description": "Idioma preferido (pt-BR, en-US)", "default": "pt-BR" } }, "required": [] } ) def entertainment_tool(tipo: str = "random", idioma: str = "pt-BR"): """ Wrapper para EntertainmentSkill com fallbacks automáticos """ result = _entertainment_skill.execute( tipo=tipo, idioma=idioma, cache_ttl=86400 # Cache 24h ) if result.get("sucesso"): conteudo = result["dados"] # Formata resposta baseado no tipo if conteudo.get("tipo") == "joke": resposta = f"😂 {conteudo.get('setup', '')}\n{conteudo.get('punchline', conteudo.get('conteudo', ''))}" elif conteudo.get("tipo") == "advice": resposta = f"💡 {conteudo.get('texto', '')}" elif conteudo.get("tipo") == "quote": resposta = f"💭 \"{conteudo.get('texto', '')}\" — {conteudo.get('autor', 'Desconhecido')}" else: resposta = str(conteudo) return { "sucesso": True, "conteudo": resposta, "tipo": conteudo.get("tipo"), "provider": result["provider"], "cache": result.get("cache_hit", False) } else: return { "sucesso": False, "erro": result.get("erro") } @skill( name="get_art", description="Busca obras de arte no Museu Metropolitano OU gera imagens criativas com IA. Suporta múltiplas fontes com fallbacks.", parameters={ "type": "object", "properties": { "tipo": { "type": "string", "description": "Tipo de ação: 'search' (buscar no museu) ou 'generate' (gerar imagem)", "enum": ["search", "generate"], "default": "search" }, "query": { "type": "string", "description": "Termo de busca ou descrição da imagem a gerar" }, "estilo": { "type": "string", "description": "Estilo (para generate): 'cyberpunk', 'renaissance', 'surreal', 'abstract', etc", "default": "photorealistic" }, "max_results": { "type": "integer", "description": "Máximo de resultados para busca", "default": 3 } }, "required": ["query"] } ) def art_tool(tipo: str = "search", query: str = None, estilo: str = "photorealistic", max_results: int = 3): """ Wrapper para ArtSkill com fallbacks automáticos """ if not query: return {"sucesso": False, "erro": "Parâmetro 'query' é obrigatório"} result = _art_skill.execute( tipo=tipo, query=query, style=estilo, max_results=max_results, cache_ttl=86400 # Cache 24h ) if result.get("sucesso"): dados = result["dados"] if dados.get("tipo") == "museum_search": obras_formatted = [] for obra in dados.get("obras", [])[:max_results]: obras_formatted.append({ "titulo": obra.get("titulo"), "artista": obra.get("artista"), "ano": obra.get("ano"), "imagem_url": obra.get("url_imagem"), "museo_url": obra.get("url_met") }) return { "sucesso": True, "tipo": "museu", "query": query, "obras": obras_formatted, "total": len(obras_formatted), "provider": result["provider"] } elif dados.get("tipo") == "image_generation": # Marca como remote_action para BotCore enviar imagem return { "sucesso": True, "tipo": "image_generation", "image_url": dados.get("url_imagem"), "provider": result["provider"], "media_response": { "tipo": "imagem", "url": dados.get("url_imagem"), "descricao": query } } else: return { "sucesso": True, "conteudo": dados.get("arte") or dados.get("descricao"), "provider": result["provider"] } else: return { "sucesso": False, "erro": result.get("erro") } @skill( name="get_music", description="Gera gêneros musicais, busca OST de animes, ou faz recomendações musicais personalizadas. Múltiplas APIs com fallbacks.", parameters={ "type": "object", "properties": { "tipo": { "type": "string", "description": "Tipo de informação: 'genre' (gênero aleatório), 'recommendation' (recomendação), 'anime_ost' (trilha de anime), 'lyrics' (letra)", "enum": ["genre", "recommendation", "anime_ost", "lyrics"], "default": "genre" }, "mood": { "type": "string", "description": "Humor/contexto para recomendação: 'happy', 'sad', 'energetic', 'chill', 'creative', 'random'", "default": "random" }, "anime": { "type": "string", "description": "Nome do anime (para tipo='anime_ost')" }, "song": { "type": "string", "description": "Nome da música (para tipo='lyrics')" }, "artist": { "type": "string", "description": "Nome do artista (para tipo='lyrics')" } }, "required": ["tipo"] } ) def music_tool(tipo: str = "genre", mood: str = "random", anime: str = None, song: str = None, artist: str = None): """ Wrapper para MusicSkill com fallbacks automáticos """ result = _music_skill.execute( tipo=tipo, mood=mood, anime=anime, song=song, artist=artist, cache_ttl=604800 # Cache 7 dias para gêneros (não mudam) ) if result.get("sucesso"): dados = result["dados"] if dados.get("tipo") == "genre": return { "sucesso": True, "tipo": "genre", "genero": dados.get("genero"), "descricao": dados.get("descricao"), "mood": dados.get("mood"), "provider": result["provider"] } elif dados.get("tipo") == "anime_ost": return { "sucesso": True, "tipo": "anime_ost", "anime": dados.get("anime"), "ano": dados.get("year"), "opening": dados.get("opening_theme", []), "ending": dados.get("ending_theme", []), "provider": result["provider"] } elif dados.get("tipo") == "recommendation": return { "sucesso": True, "tipo": "recommendation", "genero": dados.get("genero"), "artistas": dados.get("artistas"), "descricao": dados.get("descricao"), "provider": result["provider"] } else: return { "sucesso": True, "conteudo": dados, "provider": result["provider"] } else: return { "sucesso": False, "erro": result.get("erro") } # ======================================== # Helper para stats # ======================================== def get_grouped_skills_stats() -> Dict[str, Any]: """Retorna estatísticas de uso das skills agrupadas""" return { "weather": _weather_skill.get_stats(), "entertainment": _entertainment_skill.get_stats(), "art": _art_skill.get_stats(), "music": _music_skill.get_stats() }