tavily-dynamic-search

작성자: tavily-ai

웹을 검색하고, 결과를 필터링하며, 콘텐츠를 추출하여 원시 검색 데이터가 컨텍스트 창에 들어오지 않도록 합니다. 선별된 print() 출력만 반환됩니다.

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

Tavily Dynamic Search

Search the web, filter results, and extract content so that raw search data never enters your context window. Only your curated print() output comes back.

Why this matters

A typical tvly search --include-raw-content returns 8 results × 30-50K chars each = ~300K characters of raw page content. If this enters your context window, you burn tokens reading navigation bars, cookie banners, and boilerplate — and your reasoning quality degrades under the noise. By processing results inside a Python script, only your print() output enters context — typically 1-3K characters of pure signal. That's a 100-200x reduction.

Background: Programmatic Tool Calling (PTC)

This skill replicates the architecture of Anthropic's Programmatic Tool Calling (PTC) for web search. PTC lets the model write code that orchestrates tool calls inside a sandbox — intermediate results stay in the sandbox, and only the final print() output reaches the model's context window.

This skill applies the same principle using local Python execution. The Python process is the sandbox. Variables in memory hold the raw data. Only what you print() crosses into your context window. You write the filtering logic — you decide what matters for each query.

Before running any command

If tvly is not found on PATH, install it first:

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

Core Rule

NEVER run tvly as a bare command. Always process output through Python so you control what enters your context.

# WRONG — raw results flood your context
tvly search "quantum computing 2025" --json

# RIGHT — only your print() output enters 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\"]}')
"

JSON Schemas

You need these to write correct filtering code.

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
}

How to search

You have two building blocks and two ways to run them. Compose these however the query demands — there are no fixed patterns. You decide the approach based on what you need.

Building blocks

tvly search — returns titles, URLs, snippets, scores. Optionally includes full page content with --include-raw-content markdown.

tvly extract — fetches full page content for specific URLs. Use when you found a URL from search and need more detail.

Execution modes

Pipe mode — for simple filters (3-5 lines). Pipe tvly output into python3 -c:

tvly search "query" --json 2>/dev/null | python3 -c "
import json, sys
data = json.load(sys.stdin)
# your filtering code here
"

Heredoc mode — for anything more complex. Single Bash call, clean multi-line Python, no escaping, no temp files:

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

Single-quoted heredocs (<< 'PYEOF') don't interpret anything — no escaping needed. This is the default for most tasks.

Script mode — only when you will reuse the same script across multiple turns. Do NOT write one-shot scripts to /tmp/. If you run it once, use a heredoc.

Important: save DATA to /tmp/, not CODE. Writing /tmp/tavily_results.json (data for later turns) = good. Writing /tmp/my_filter.py (one-shot code) = wasteful — use a heredoc instead.

Multi-turn iteration

For complex queries, you often need to explore before you extract — just like PTC, where the model searches, sees titles, decides which results to drill into, then extracts.

The key: save raw results to a file, then process them in separate steps. The file is your persistent state between turns.

Turn 1: Search and explore

Search and print only titles + scores. Save raw results to disk for later turns:

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)

# Save raw results — this stays on disk, never enters context
with open('/tmp/tavily_results.json', 'w') as f:
    json.dump(data, f)

# Print only what you need to decide next steps
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

Context receives: ~800 tokens of titles + snippets. The 300K of raw page content is in /tmp/tavily_results.json, untouched.

Turn 2: Extract based on what you saw

Now you know what's in the results. Write targeted extraction — you decide which results to drill into and what to filter for:

python3 << 'PYEOF'
import json

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

# You chose these indices based on the titles you saw in turn 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')

    # You write the filtering logic based on the query
    # This example extracts paragraphs about specific companies
    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

Context receives: ~600 tokens of targeted content. You made the decision about what to keep.

Turn 3 (optional): Fetch more detail

If you need more from a specific source:

python3 << 'PYEOF'
import json, subprocess

# Fetch a specific URL you identified
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', '')

# Save for potential further processing
with open('/tmp/page_detail.txt', 'w') as f:
    f.write(content)

# Print only the section you care about
for line in content.split('\n'):
    if any(kw in line.lower() for kw in ['timeline', '2025', '2026', 'mass production']):
        print(line.strip())
PYEOF

When to use multi-turn vs single-turn

Single turn (pipe mode or one script): when you know upfront what you're looking for. Specific factual queries, known keywords.

Multi-turn (save + explore + extract): when you need to see what's available before deciding what to extract. Open-ended research, complex topics, queries where you don't know the right keywords yet.

Examples

Simple factual lookup (single turn, pipe mode)

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()
"

Financial data extraction (single turn, 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 ''
    # For financial queries, look for lines with numbers
    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

Multi-source research (multi-turn)

Turn 1 — broad search + triage:

python3 << 'PYEOF'
import json, subprocess

# Search from multiple 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'])

# Deduplicate by URL
seen = set()
unique = []
for r in all_results:
    if r['url'] not in seen:
        seen.add(r['url'])
        unique.append(r)

# Save all results
with open('/tmp/eu_ai_results.json', 'w') as f:
    json.dump(unique, f)

# Print 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

Turn 2 — you see the triage, pick the best sources, and extract:

python3 << 'PYEOF'
import json, subprocess

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

# Fetch full content for the top 3 (you chose these based on turn 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', '')

        # Your filtering logic — tailored to this query
        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

Following leads across turns

Sometimes turn 2 reveals new URLs or topics to chase. You can keep iterating:

python3 << 'PYEOF'
import json, subprocess

# Read the page you saved earlier
with open('/tmp/page_detail.txt') as f:
    content = f.read()

# You noticed a reference to a specific regulation document
# Search for it specifically
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

Each turn, you save data to /tmp/, decide what to explore next, and write new filtering code as heredocs. The raw data accumulates on disk; your context stays lean.

Writing your filtering code

The Python you write IS the filtering logic. There are no fixed templates — you write code that makes sense for the specific query. Here are principles, not rules:

Triage first. Inspect titles and scores before fetching full pages. Don't extract everything blindly.

Be specific. A financial query should filter for numbers and financial terms. A technical query should look for code blocks and specifications. A news query should look for dates and quotes. Match your filtering to the query.

Structural filtering helps. Skip lines shorter than ~50-80 chars (usually nav elements). Skip common boilerplate phrases. Keep headings and their following paragraphs. But these are starting points — adapt based on what you see.

Print structured output. Format your output so it's easy to reason over:

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

Handle errors. Pages fail, URLs 404, extractions timeout. Use try/except and skip failures:

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

Token budget awareness. Your print() output is what enters your context. Target 150-600 tokens per source. If you're printing 5000+ chars from a single page, you're probably not filtering enough. But if a source has a critical data table, it's fine to keep more.

Options

All standard tvly search options work:

OptionDescription
--max-resultsNumber of results (default: 5, max: 20)
--depthultra-fast, fast, basic (default), advanced
--time-rangeday, week, month, year
--include-domainsComma-separated whitelist
--exclude-domainsComma-separated blacklist
--include-raw-contentFull page content (markdown or text)
--countryBoost results from country

Fallback: jq

When python3 is unavailable, use jq for basic filtering:

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

jq can't do multi-step search-then-extract or complex filtering. Use it only for simple lookups.

tavily-ai의 다른 스킬

crawl
tavily-ai
웹사이트 콘텐츠를 추출하여 마크다운 파일로 저장해 오프라인 접근 및 분석에 활용합니다. 설정 가능한 크롤링 깊이(1~5단계), 폭 제한, 페이지 상한을 지원하여 적용 범위와 성능 간 균형을 맞춥니다. 정규식 패턴을 통한 경로 필터링으로 특정 섹션에 집중하고 관련 없는 콘텐츠를 제외합니다. 데이터 수집을 위한 전체 페이지 추출, 또는 자연어 명령어를 통한 의미 기반 청킹으로 결과를 LLM 컨텍스트에 제공하는 두 가지 모드를 제공합니다. URL을 위한 보조 Map API를 제공합니다...
official
extract
tavily-ai
Tavily의 추출 API를 사용하여 특정 URL에서 깔끔한 콘텐츠를 추출합니다. 요청당 최대 20개의 URL을 지원하며, 관련 콘텐츠 청크에 집중하기 위한 선택적 쿼리 기반 재순위화 기능이 있습니다. 두 가지 추출 모드: 기본 모드는 빠른 텍스트 추출, 고급 모드는 JavaScript 렌더링 페이지 및 구조화된 데이터를 지원합니다. 첫 실행 시 브라우저를 통한 자동 OAuth 인증 또는 설정에서 수동 API 키 구성이 가능합니다. 마크다운 또는 일반 텍스트 형식으로 반환되며, 선택적 이미지 URL 및 최대 60초까지 구성 가능한 타임아웃이 포함됩니다.
official
research
tavily-ai
모든 주제에 대해 자동 소스 수집, 분석 및 인용을 포함한 포괄적 연구를 수행합니다. 명확한 인용과 함께 다중 소스 웹 연구를 진행하며, 비교 분석, 최신 이슈, 시장 분석 및 상세 보고서에 적합합니다. 세 가지 모델 옵션을 제공합니다: 미니(단일 주제 집중 연구, 약 30초), 프로(포괄적 다각도 분석, 약 60-120초), 오토(API 기반 복잡도 자동 감지). Tavily MCP 서버를 통해 OAuth 인증을 하며, 자동 브라우저 기반 로그인을 지원합니다.
official
search
tavily-ai
LLM 최적화 결과, 관련성 점수, 유연한 필터링을 갖춘 웹 검색. 네 가지 검색 심도 모드(초고속, 빠름, 기본, 고급)를 지원하며 지연 시간과 관련성 간의 균형을 설정 가능. 도메인 필터링, 시간 범위 제약, 날짜 범위, 국가 가중치 부여, 원본 콘텐츠 추출 포함. 제목, URL, 콘텐츠 스니펫, 관련성 점수와 함께 결과 반환; 선택적 이미지 결과 및 파비콘. Tavily MCP 서버 또는 API 키 구성을 통한 자동 OAuth 인증;...
official
tavily-best-practices
tavily-ai
LLM을 위한 웹 검색 API로, 실시간 데이터 접근, 콘텐츠 추출, 사이트 크롤링, AI 기반 리서치를 제공합니다. 다섯 가지 핵심 메서드: 웹 결과 검색을 위한 search(), URL 콘텐츠 추출을 위한 extract(), 사이트 전체 추출을 위한 crawl(), URL 발견을 위한 map(), 종단 간 AI 합성을 위한 research()를 지원합니다. Python 및 JavaScript SDK를 제공하며, 병렬 쿼리와 설정 가능한 검색 심도(초고속/고속/기본/고급)를 위한 비동기 클라이언트를 포함합니다. Crawl 메서드는 추출 대상을 집중시키기 위해 의미론적 지시를 받아들입니다...
official
tavily-cli
tavily-ai
Tavily CLI를 통한 웹 검색, 콘텐츠 추출, 사이트 크롤링 및 심층 리서치. 검색, 추출, URL 발견, 대량 크롤링, 인용 포함 다중 소스 리서치를 아우르는 다섯 가지 명령 모드. 모든 명령은 JSON 출력 및 파일 저장을 지원하여 구조화된 에이전트 워크플로우에 적합. 에스컬레이션 패턴은 단순 검색에서 추출, 매핑, 크롤링, 필요에 따른 종합 리서치까지 안내. tavily-cli 설치 및 tvly login을 통한 API 키 인증 필요.
official
tavily-crawl
tavily-ai
다중 페이지 웹사이트 크롤러로, 의미론적 필터링과 마크다운 내보내기 기능을 제공합니다. 깊이와 범위를 제어하여 사이트 전체 섹션을 크롤링하고, 경로 정규식, 도메인 또는 자연어 명령어로 필터링하여 결과를 집중시킬 수 있습니다. --output-dir 옵션을 통해 각 페이지를 로컬 마크다운 파일로 저장하거나, 구조화된 JSON을 반환하여 에이전트 처리에 활용할 수 있습니다. 결과를 LLM에 전달할 때 컨텍스트 팽창을 방지하기 위해 청크 추출과 함께 의미론적 명령어를 사용하고, 오프라인 문서 다운로드를 위해 전체 페이지 추출을 지원합니다.
official
tavily-extract
tavily-ai
최대 20개의 URL에서 깨끗한 마크다운 또는 텍스트를 추출하며, JavaScript 렌더링 및 쿼리 중심 청킹을 지원합니다. JavaScript로 렌더링된 페이지를 처리하며, 추출 깊이를 구성할 수 있습니다(기본 페이지는 기본, 동적 SPA 및 테이블은 고급). 쿼리 중심 추출을 지원하여 전체 페이지 대신 관련 콘텐츠 청크만 반환합니다. 기본적으로 LLM에 최적화된 마크다운을 반환하며, 일반 텍스트 형식 및 구조화된 JSON 출력 옵션을 제공합니다. 단일 호출에서 최대 20개의 URL을 처리합니다.
official