feat: agregar tool imdb_search para buscar películas en IMDb
Implementa una nueva tool que permite buscar películas y series en IMDb usando la API de OMDb. Retorna hasta 5 resultados con título, año, tipo, poster URL e IMDb ID. Cambios: - tools/imdb/imdb.go: tool imdb_search con integración a OMDb API - internal/config/schema.go: IMDbToolCfg con api_key, api_key_env y timeout - agents/runtime.go: registro de tool en buildToolRegistry - agents/asistente-2/config.yaml: habilitación de tool imdb - .env.example: OMDB_API_KEY para configuración La tool soporta parámetros: - query (requerido): título de película/serie a buscar - year (opcional): año para filtrar resultados Configuración via api_key directa o variable de entorno OMDB_API_KEY. API key gratuita disponible en http://www.omdbapi.com/ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -34,6 +34,10 @@ SSSS_RECOVERY_KEY_DEVOPS_BOT=
|
||||
OPENAI_API_KEY=sk-...
|
||||
ANTHROPIC_API_KEY=sk-ant-... # opcional, para cuando añadas el devops-bot con Claude
|
||||
|
||||
# ── External APIs ────────────────────────────────────────────
|
||||
# OMDb API key para búsqueda de películas en IMDb (obtener de http://www.omdbapi.com/)
|
||||
OMDB_API_KEY=
|
||||
|
||||
# ── SSH (para devops-bot, cuando lo añadas) ──────────────────
|
||||
SSH_PRIVATE_KEY_PATH=/home/ubuntu/.ssh/id_ed25519
|
||||
SSH_MONITOR_KEY_PATH=/home/ubuntu/.ssh/id_ed25519
|
||||
|
||||
@@ -128,6 +128,12 @@ tools:
|
||||
knowledge:
|
||||
enabled: true
|
||||
|
||||
imdb:
|
||||
enabled: true
|
||||
api_key: ""
|
||||
api_key_env: "OMDB_API_KEY"
|
||||
timeout: 10s
|
||||
|
||||
# ============================================
|
||||
# MEMORIA — ventana de conversación + hechos
|
||||
# ============================================
|
||||
|
||||
@@ -37,6 +37,7 @@ import (
|
||||
toolclock "github.com/enmanuel/agents/tools/clock"
|
||||
toolfile "github.com/enmanuel/agents/tools/file"
|
||||
toolhttp "github.com/enmanuel/agents/tools/http"
|
||||
toolimdb "github.com/enmanuel/agents/tools/imdb"
|
||||
toolknowledge "github.com/enmanuel/agents/tools/knowledgetools"
|
||||
toolmatrix "github.com/enmanuel/agents/tools/matrix"
|
||||
toolmcp "github.com/enmanuel/agents/tools/mcptools"
|
||||
@@ -1104,6 +1105,12 @@ func buildToolRegistry(
|
||||
reg.Register(toolweather.NewWeather())
|
||||
logger.Debug("registered weather tool")
|
||||
|
||||
// imdb tool (enabled via config)
|
||||
if cfg.Tools.IMDb.Enabled {
|
||||
reg.Register(toolimdb.NewIMDbSearch(cfg.Tools.IMDb))
|
||||
logger.Debug("registered imdb tool")
|
||||
}
|
||||
|
||||
// matrix_send is always available
|
||||
reg.Register(toolmatrix.NewMatrixSend(matrixClient, cfg.Tools.Matrix))
|
||||
logger.Debug("registered matrix tool")
|
||||
|
||||
@@ -158,6 +158,7 @@ type ToolsCfg struct {
|
||||
Knowledge KnowledgeToolCfg `yaml:"knowledge"`
|
||||
SharedKnowledge SharedKnowledgeToolCfg `yaml:"shared_knowledge"`
|
||||
Skills SkillsToolCfg `yaml:"skills"`
|
||||
IMDb IMDbToolCfg `yaml:"imdb"`
|
||||
}
|
||||
|
||||
type MatrixToolCfg struct {
|
||||
@@ -519,6 +520,13 @@ type SkillsToolCfg struct {
|
||||
AllowedInterpreters []string `yaml:"allowed_interpreters"` // allowlist for skill script execution (default: ["bash", "sh"])
|
||||
}
|
||||
|
||||
type IMDbToolCfg struct {
|
||||
Enabled bool `yaml:"enabled"`
|
||||
APIKey string `yaml:"api_key"` // OMDb API key (get from http://www.omdbapi.com/)
|
||||
APIKeyEnv string `yaml:"api_key_env"` // env var name for API key (e.g., "OMDB_API_KEY")
|
||||
Timeout time.Duration `yaml:"timeout"` // timeout for API requests (default: 10s)
|
||||
}
|
||||
|
||||
// ── Special Agents ────────────────────────────────────────────────────────
|
||||
|
||||
// SpecialConfig is the root configuration for a special agent (no Matrix identity).
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
package imdb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/enmanuel/agents/internal/config"
|
||||
"github.com/enmanuel/agents/tools"
|
||||
)
|
||||
|
||||
// SearchResult represents a single movie/series result from OMDb API.
|
||||
type SearchResult struct {
|
||||
Title string `json:"Title"`
|
||||
Year string `json:"Year"`
|
||||
ImdbID string `json:"imdbID"`
|
||||
Type string `json:"Type"`
|
||||
Poster string `json:"Poster"`
|
||||
}
|
||||
|
||||
// SearchResponse represents the full response from OMDb search endpoint.
|
||||
type SearchResponse struct {
|
||||
Search []SearchResult `json:"Search"`
|
||||
TotalResults string `json:"totalResults"`
|
||||
Response string `json:"Response"`
|
||||
Error string `json:"Error"`
|
||||
}
|
||||
|
||||
// NewIMDbSearch creates an imdb_search tool that searches movies on IMDb via OMDb API.
|
||||
// Returns up to 5 results with title, year, type, poster URL, and IMDb ID.
|
||||
// Requires API key from http://www.omdbapi.com/
|
||||
func NewIMDbSearch(cfg config.IMDbToolCfg) tools.Tool {
|
||||
timeout := cfg.Timeout
|
||||
if timeout == 0 {
|
||||
timeout = 10 * time.Second
|
||||
}
|
||||
client := &http.Client{Timeout: timeout}
|
||||
|
||||
return tools.Tool{
|
||||
Def: tools.Def{
|
||||
Name: "imdb_search",
|
||||
Description: "Search for movies or series on IMDb by title. Returns up to 5 results with title, year, type, poster image URL, and IMDb ID.",
|
||||
Parameters: []tools.Param{
|
||||
{Name: "query", Type: "string", Description: "The movie or series title to search for", Required: true},
|
||||
{Name: "year", Type: "integer", Description: "Optional year to filter results (e.g., 2020)", Required: false},
|
||||
},
|
||||
},
|
||||
Exec: func(ctx context.Context, args map[string]any) tools.Result {
|
||||
query := tools.GetString(args, "query")
|
||||
if query == "" {
|
||||
return tools.Result{Err: fmt.Errorf("imdb_search: query is required")}
|
||||
}
|
||||
|
||||
// Get API key from config or env var
|
||||
apiKey := cfg.APIKey
|
||||
if apiKey == "" && cfg.APIKeyEnv != "" {
|
||||
apiKey = getEnvVar(cfg.APIKeyEnv)
|
||||
}
|
||||
if apiKey == "" {
|
||||
return tools.Result{Err: fmt.Errorf("imdb_search: API key not configured (set imdb.api_key or imdb.api_key_env in config)")}
|
||||
}
|
||||
|
||||
// Build search URL
|
||||
searchURL := buildSearchURL(apiKey, query, args)
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, searchURL, nil)
|
||||
if err != nil {
|
||||
return tools.Result{Err: fmt.Errorf("imdb_search: %w", err)}
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return tools.Result{Err: fmt.Errorf("imdb_search: %w", err)}
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return tools.Result{Err: fmt.Errorf("imdb_search: HTTP %d", resp.StatusCode)}
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(io.LimitReader(resp.Body, 64*1024))
|
||||
if err != nil {
|
||||
return tools.Result{Err: fmt.Errorf("imdb_search: read body: %w", err)}
|
||||
}
|
||||
|
||||
var searchResp SearchResponse
|
||||
if err := json.Unmarshal(body, &searchResp); err != nil {
|
||||
return tools.Result{Err: fmt.Errorf("imdb_search: parse response: %w", err)}
|
||||
}
|
||||
|
||||
if searchResp.Response == "False" {
|
||||
return tools.Result{Output: fmt.Sprintf("No se encontraron resultados para '%s'. Error: %s", query, searchResp.Error)}
|
||||
}
|
||||
|
||||
// Format results (limit to first 5)
|
||||
output := formatResults(searchResp.Search, query)
|
||||
return tools.Result{Output: output}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// buildSearchURL constructs the OMDb API search URL with query parameters.
|
||||
func buildSearchURL(apiKey, query string, args map[string]any) string {
|
||||
params := url.Values{}
|
||||
params.Set("apikey", apiKey)
|
||||
params.Set("s", query)
|
||||
params.Set("type", "movie") // default to movies, could be made configurable
|
||||
|
||||
// Add year filter if provided
|
||||
if year := tools.GetInt(args, "year"); year > 0 {
|
||||
params.Set("y", fmt.Sprintf("%d", year))
|
||||
}
|
||||
|
||||
return fmt.Sprintf("https://www.omdbapi.com/?%s", params.Encode())
|
||||
}
|
||||
|
||||
// formatResults converts search results into a readable text format.
|
||||
func formatResults(results []SearchResult, query string) string {
|
||||
if len(results) == 0 {
|
||||
return fmt.Sprintf("No se encontraron películas para '%s'", query)
|
||||
}
|
||||
|
||||
var builder strings.Builder
|
||||
builder.WriteString(fmt.Sprintf("🎬 Resultados de IMDb para '%s':\n\n", query))
|
||||
|
||||
// Limit to 5 results
|
||||
limit := 5
|
||||
if len(results) < limit {
|
||||
limit = len(results)
|
||||
}
|
||||
|
||||
for i := 0; i < limit; i++ {
|
||||
r := results[i]
|
||||
builder.WriteString(fmt.Sprintf("%d. **%s** (%s)\n", i+1, r.Title, r.Year))
|
||||
builder.WriteString(fmt.Sprintf(" • Tipo: %s\n", r.Type))
|
||||
builder.WriteString(fmt.Sprintf(" • IMDb ID: %s\n", r.ImdbID))
|
||||
|
||||
if r.Poster != "" && r.Poster != "N/A" {
|
||||
builder.WriteString(fmt.Sprintf(" • Poster: %s\n", r.Poster))
|
||||
} else {
|
||||
builder.WriteString(" • Poster: No disponible\n")
|
||||
}
|
||||
|
||||
builder.WriteString(fmt.Sprintf(" • Link: https://www.imdb.com/title/%s/\n", r.ImdbID))
|
||||
|
||||
if i < limit-1 {
|
||||
builder.WriteString("\n")
|
||||
}
|
||||
}
|
||||
|
||||
if len(results) > 5 {
|
||||
builder.WriteString(fmt.Sprintf("\n... y %d resultado(s) más", len(results)-5))
|
||||
}
|
||||
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
// getEnvVar retrieves an environment variable by name.
|
||||
func getEnvVar(name string) string {
|
||||
return os.Getenv(name)
|
||||
}
|
||||
Reference in New Issue
Block a user