tavily-dynamic-search

Par tavily-ai · skills

Recherche web programmatique avec isolation du contexte. Utilisez cette skill pour toute tâche de recherche nécessitant d'interroger le web, de filtrer les résultats et d'extraire des informations spécifiques — sans polluer votre context window avec du HTML brut et du contenu générique. Il s'agit de la skill par défaut pour la recherche web. Déclenchée par « search for », « look up », « find », « research », « what's the latest on », ou toute requête nécessitant des informations web actuelles. À utiliser également lorsqu'on demande de « search and filter », « find the important parts » ou « extract the key details » — dans tous les cas où l'utilisateur souhaite un contenu ciblé et sans bruit.

npx skills add https://github.com/tavily-ai/skills --skill tavily-dynamic-search

Tavily Dynamic Search

Cherchez sur le web, filtrez les résultats et extrayez du contenu pour que les données brutes de recherche ne rentrent jamais dans votre context window. Seul votre output print() curé revient.

Pourquoi c'est important

Une requête classique tvly search --include-raw-content retourne 8 résultats × 30-50K caractères chacun = ~300K caractères de contenu brut de page. Si cela entre dans votre context window, vous brûlez des tokens à lire les barres de navigation, les bandeaux de cookies et les textes génériques — et votre qualité de raisonnement se dégrade sous le bruit. En traitant les résultats dans un script Python, seul votre output print() entre dans le context — typiquement 1-3K caractères de signal pur. C'est une réduction de 100-200x.

Contexte : Programmatic Tool Calling (PTC)

Cette skill reproduit l'architecture du Programmatic Tool Calling (PTC) d'Anthropic pour la recherche web. PTC permet au modèle d'écrire du code qui orchestre les appels d'outils dans un sandbox — les résultats intermédiaires restent dans le sandbox, et seul l'output print() final atteint le context window du modèle.

Cette skill applique le même principe en utilisant l'exécution Python locale. Le processus Python est le sandbox. Les variables en mémoire contiennent les données brutes. Seul ce que vous print() traverse dans votre context window. Vous écrivez la logique de filtrage — vous décidez ce qui compte pour chaque requête.

Avant de lancer une commande

Si tvly n'est pas trouvé dans PATH, installez-le d'abord :

curl -fsSL https://cli.tavily.com/install.sh | bash && tvly login

Règle fondamentale

JAMAIS lancer tvly comme commande seule. Toujours traiter l'output via Python pour contrôler ce qui entre dans votre context.

# MAUVAIS — les résultats bruts inondent votre context
tvly search "quantum computing 2025" --json

# BON — seul votre output print() entre dans le context
tvly search "quantum computing 2025" --json 2>/dev/null | python3 -c "
import json, sys
data = json.load(sys.stdin)
for r in data['results']:
    print(f'[{r[\"score\"]:.2f}] {r[\"title\"]}')
    print(f'  {r[\"url\"]}')
"

Schémas JSON

Vous en avez besoin pour écrire un code de filtrage correct.

tvly search --json

{
  "query": "string",
  "answer": "string | null",
  "results": [
    {
      "url": "string",
      "title": "string",
      "content": "string (snippet, ~500-1500 chars)",
      "score": 0.0-1.0,
      "raw_content": "string | null (full page, only with --include-raw-content)"
    }
  ],
  "response_time": 0.0
}

tvly extract --json

{
  "results": [
    {
      "url": "string",
      "title": "string",
      "raw_content": "string (full page markdown)",
      "images": []
    }
  ],
  "failed_results": [],
  "response_time": 0.0
}

Comment chercher

Vous avez deux blocs de construction et deux façons de les lancer. Composez-les comme la requête l'exige — il n'y a pas de patterns fixes. Vous décidez l'approche basée sur ce que vous avez besoin.

Blocs de construction

tvly search — retourne titres, URLs, snippets, scores. Inclut optionnellement le contenu complet de la page avec --include-raw-content markdown.

tvly extract — récupère le contenu complet de la page pour des URLs spécifiques. Utilisez quand vous avez trouvé une URL par recherche et besoin de plus de détails.

Modes d'exécution

Mode pipe — pour des filtres simples (3-5 lignes). Pipage de l'output tvly dans python3 -c :

tvly search "query" --json 2>/dev/null | python3 -c "
import json, sys
data = json.load(sys.stdin)
# votre code de filtrage ici
"

Mode heredoc — pour tout ce qui est plus complexe. Appel Bash unique, Python multi-ligne propre, pas d'échappement, pas de fichiers temporaires :

python3 << 'PYEOF'
import json, subprocess
raw = subprocess.check_output(
    ['tvly', 'search', 'query', '--json'],
    stderr=subprocess.DEVNULL
)
data = json.loads(raw)
for r in data['results']:
    print(f"[{r['score']:.2f}] {r['title']}")
    print(f"  {r['url']}")
PYEOF

Les heredocs entre guillemets simples (<< 'PYEOF') n'interprètent rien — pas d'échappement nécessaire. C'est la valeur par défaut pour la plupart des tâches.

Mode script — seulement quand vous réutiliserez le même script sur plusieurs tours. N'ÉCRIVEZ PAS de scripts à usage unique dans /tmp/. Si vous le lancez une fois, utilisez un heredoc.

Important : sauvegardez les DONNÉES dans /tmp/, pas le CODE. Écrire /tmp/tavily_results.json (données pour les tours suivants) = bon. Écrire /tmp/my_filter.py (code à usage unique) = gaspillage — utilisez un heredoc à la place.

Itération multi-tour

Pour les requêtes complexes, vous avez souvent besoin d'explorer avant d'extraire — exactement comme PTC, où le modèle cherche, voit les titres, décide quels résultats approfondir, puis extrait.

La clé : sauvegardez les résultats bruts dans un fichier, puis traitez-les en étapes séparées. Le fichier est votre état persistant entre les tours.

Tour 1 : Chercher et explorer

Cherchez et imprimez seulement titres + scores. Sauvegardez les résultats bruts sur disque pour les tours suivants :

python3 << 'PYEOF'
import json, subprocess

raw = subprocess.check_output(
    ['tvly', 'search', 'solid-state battery commercialization 2025',
     '--include-raw-content', 'markdown', '--max-results', '8', '--json'],
    stderr=subprocess.DEVNULL
)
data = json.loads(raw)

# Sauvegardez les résultats bruts — cela reste sur disque, n'entre jamais dans le context
with open('/tmp/tavily_results.json', 'w') as f:
    json.dump(data, f)

# Imprimez seulement ce dont vous avez besoin pour décider les prochaines étapes
print(f'{len(data["results"])} results saved to /tmp/tavily_results.json\n')
for i, r in enumerate(data['results']):
    print(f'[{i}] [{r["score"]:.2f}] {r["title"][:90]}')
    print(f'    {r["url"]}')
    print(f'    {r["content"][:150]}')
    print()
PYEOF

Le context reçoit : ~800 tokens de titres + snippets. Les 300K de contenu brut de page sont dans /tmp/tavily_results.json, intacts.

Tour 2 : Extraire en fonction de ce que vous avez vu

Maintenant vous savez ce qui est dans les résultats. Écrivez une extraction ciblée — vous décidez quels résultats approfondir et sur quoi filtrer :

python3 << 'PYEOF'
import json

data = json.load(open('/tmp/tavily_results.json'))

# Vous avez choisi ces indices basé sur les titres que vous avez vu au tour 1
for i in [0, 2, 5]:
    r = data['results'][i]
    raw = r.get('raw_content', '') or ''
    if not raw:
        continue

    print(f'## {r["title"]}')
    print(f'URL: {r["url"]}\n')

    # Vous écrivez la logique de filtrage basée sur la requête
    # Cet exemple extrait les paragraphes sur des entreprises spécifiques
    for para in raw.split('\n\n'):
        para = para.strip()
        if len(para) > 80 and any(kw in para.lower() for kw in
                ['toyota', 'quantumscape', 'samsung', 'commercializ', 'production']):
            print(para)
            print()

    print('---\n')
PYEOF

Le context reçoit : ~600 tokens de contenu ciblé. Vous avez décidé ce qu'il faut garder.

Tour 3 (optionnel) : Récupérer plus de détails

Si vous avez besoin de plus d'une source spécifique :

python3 << 'PYEOF'
import json, subprocess

# Récupérez une URL spécifique que vous avez identifiée
raw = subprocess.check_output(
    ['tvly', 'extract', 'https://example.com/article', '--json'],
    stderr=subprocess.DEVNULL
)
data = json.loads(raw)
page = data['results'][0]
content = page.get('raw_content', '')

# Sauvegardez pour un éventuel traitement ultérieur
with open('/tmp/page_detail.txt', 'w') as f:
    f.write(content)

# Imprimez seulement la section qui vous intéresse
for line in content.split('\n'):
    if any(kw in line.lower() for kw in ['timeline', '2025', '2026', 'mass production']):
        print(line.strip())
PYEOF

Quand utiliser multi-tour vs mono-tour

Mono-tour (mode pipe ou un script) : quand vous savez à l'avance ce que vous cherchez. Requêtes factuelles spécifiques, mots-clés connus.

Multi-tour (sauvegarde + exploration + extraction) : quand vous avez besoin de voir ce qui est disponible avant de décider quoi extraire. Recherche ouverte, sujets complexes, requêtes où vous ne connaissez pas encore les bons mots-clés.

Exemples

Recherche factuelle simple (mono-tour, mode pipe)

tvly search "Python 3.13 release date" --max-results 5 --json 2>/dev/null | python3 -c "
import json, sys
data = json.load(sys.stdin)
for r in data['results'][:3]:
    print(f'{r[\"title\"]}')
    print(f'{r[\"content\"][:300]}')
    print()
"

Extraction de données financières (mono-tour, heredoc)

python3 << 'PYEOF'
import json, subprocess

raw = subprocess.check_output(
    ['tvly', 'search', 'NVIDIA Q4 2025 earnings revenue',
     '--include-raw-content', 'markdown', '--max-results', '5',
     '--json'],
    stderr=subprocess.DEVNULL
)
data = json.loads(raw)

for r in data['results']:
    raw_content = r.get('raw_content', '') or ''
    # Pour les requêtes financières, cherchez les lignes avec des nombres
    financial_lines = [
        line.strip() for line in raw_content.split('\n')
        if any(kw in line.lower() for kw in
               ['revenue', 'eps', 'earnings', 'margin', 'guidance', 'billion'])
        and any(c.isdigit() for c in line)
        and len(line.strip()) > 30
    ]
    if financial_lines:
        print(f'## {r["title"]}')
        print(f'URL: {r["url"]}')
        for line in financial_lines[:15]:
            print(f'  {line}')
        print()
PYEOF

Recherche multi-sources (multi-tour)

Tour 1 — recherche large + triage :

python3 << 'PYEOF'
import json, subprocess

# Cherchez sous plusieurs angles
queries = [
    ('broad', 'EU AI Act implementation timeline 2025'),
    ('specific', 'EU AI Act high-risk AI systems obligations'),
]

all_results = []
for label, query in queries:
    raw = subprocess.check_output(
        ['tvly', 'search', query, '--max-results', '8', '--json'],
        stderr=subprocess.DEVNULL
    )
    data = json.loads(raw)
    for r in data['results']:
        r['_query'] = label
    all_results.extend(data['results'])

# Dédupliquez par URL
seen = set()
unique = []
for r in all_results:
    if r['url'] not in seen:
        seen.add(r['url'])
        unique.append(r)

# Sauvegardez tous les résultats
with open('/tmp/eu_ai_results.json', 'w') as f:
    json.dump(unique, f)

# Imprimez le triage
unique.sort(key=lambda r: r['score'], reverse=True)
print(f'{len(unique)} unique results from {len(queries)} queries\n')
for i, r in enumerate(unique[:10]):
    print(f'[{i}] [{r["score"]:.2f}] ({r["_query"]}) {r["title"][:80]}')
    print(f'    {r["url"]}')
    print(f'    {r["content"][:120]}')
    print()
PYEOF

Tour 2 — vous voyez le triage, choisissez les meilleures sources, et extrayez :

python3 << 'PYEOF'
import json, subprocess

results = json.load(open('/tmp/eu_ai_results.json'))

# Récupérez le contenu complet pour les 3 meilleurs (vous les avez choisis au tour 1)
for r in [results[0], results[2], results[4]]:
    try:
        raw = subprocess.check_output(
            ['tvly', 'extract', r['url'], '--json'],
            stderr=subprocess.DEVNULL, timeout=30
        )
        page = json.loads(raw)
        if not page.get('results'):
            continue
        content = page['results'][0].get('raw_content', '')

        # Votre logique de filtrage — adaptée à cette requête
        print(f'## {r["title"]}')
        print(f'URL: {r["url"]}\n')

        for para in content.split('\n\n'):
            para = para.strip()
            if len(para) > 100 and any(kw in para.lower() for kw in
                    ['high-risk', 'prohibited', 'deadline', 'obligation',
                     'compliance', 'penalty', 'fine', 'article']):
                print(para)
                print()

        print('---\n')
    except Exception:
        continue
PYEOF

Suivre les pistes sur plusieurs tours

Parfois le tour 2 révèle de nouvelles URLs ou sujets à explorer. Vous pouvez continuer à itérer :

python3 << 'PYEOF'
import json, subprocess

# Lisez la page que vous avez sauvegardée plus tôt
with open('/tmp/page_detail.txt') as f:
    content = f.read()

# Vous avez remarqué une référence à un document réglementaire spécifique
# Cherchez-le spécifiquement
raw = subprocess.check_output(
    ['tvly', 'search', 'EU AI Act Annex III high-risk list',
     '--include-domains', 'eur-lex.europa.eu',
     '--max-results', '3', '--json'],
    stderr=subprocess.DEVNULL
)
data = json.loads(raw)

for r in data['results']:
    print(f'## {r["title"]}')
    print(f'URL: {r["url"]}')
    print(r['content'])
    print()
PYEOF

À chaque tour, vous sauvegardez les données dans /tmp/, décidez quoi explorer ensuite, et écrivez un nouveau code de filtrage sous forme de heredocs. Les données brutes s'accumulent sur disque ; votre context reste maigre.

Écrire votre code de filtrage

Le Python que vous écrivez EST la logique de filtrage. Il n'y a pas de templates fixes — vous écrivez du code qui a du sens pour la requête spécifique. Voici les principes, pas les règles :

Triez d'abord. Inspectez les titres et scores avant de récupérer les pages complètes. N'extrayez pas tout en aveugle.

Soyez spécifique. Une requête financière doit filtrer sur les nombres et les termes financiers. Une requête technique doit chercher les blocs de code et les spécifications. Une requête news doit chercher les dates et les citations. Adaptez votre filtrage à la requête.

Le filtrage structural aide. Ignorez les lignes plus courtes que ~50-80 caractères (généralement des éléments nav). Ignorez les phrases de boilerplate communes. Gardez les en-têtes et leurs paragraphes suivants. Mais ce sont des points de départ — adaptez en fonction de ce que vous voyez.

Imprimez un output structuré. Formatez votre output pour qu'il soit facile de le raisonner :

print(f'## {title}')
print(f'URL: {url}')
print(relevant_content)
print()

Gérez les erreurs. Les pages échouent, les URLs retournent 404, les extractions timeout. Utilisez try/except et ignorez les échecs :

try:
    raw = subprocess.check_output(['tvly', 'extract', url, '--json'],
                                   stderr=subprocess.DEVNULL, timeout=30)
except Exception:
    continue

Sensibilité au budget de tokens. Votre output print() est ce qui entre dans votre context. Visez 150-600 tokens par source. Si vous imprimez 5000+ caractères d'une seule page, vous ne filtrez probablement pas assez. Mais si une source a un tableau de données critique, c'est bon de garder plus.

Options

Toutes les options standard de tvly search fonctionnent :

Option Description
--max-results Nombre de résultats (défaut : 5, max : 20)
--depth ultra-fast, fast, basic (défaut), advanced
--time-range day, week, month, year
--include-domains Liste blanche séparée par des virgules
--exclude-domains Liste noire séparée par des virgules
--include-raw-content Contenu complet de la page (markdown ou text)
--country Privilégiez les résultats du pays

Fallback : jq

Quand python3 n'est pas disponible, utilisez jq pour un filtrage basique :

tvly search "query" --json 2>/dev/null | jq '[.results[] | select(.score > 0.5) | {title, url, content}]'

jq ne peut pas faire de recherche multi-étapes puis extraction, ou un filtrage complexe. Utilisez-le seulement pour des recherches simples.

Skills similaires