Exa 검색

ExaSearch는 에이전트에게 Exa 검색 API를 기반으로 한 웹 조사 도구를 줘요. 각 히트의 가장 관련성 높은 발췌(선택적 합성 텍스트 요약 포함)를 반환하는 검색, 특정 URL을 파고드는 전체 페이지 검색, 그리고 한 번의 호출로 인용된 답변을 합성하는 선택적 딥 검색으로요. 별도의 ExaAgent capability는 지루한 조사를 Exa Agent API에 지연된 도구 호출로 위임해요.

출처: 문서

Source

Pydantic AI Harness가 0.x 릴리스인 동안 API는 마이너 릴리스 사이에 바뀔 수 있어요. 그럴 때는 deprecation 경고와 릴리스 노트의 마이그레이션 안내가 정확히 어떻게 업그레이드할지 알려줘요. 버전 정책을 참고하세요.

본문

문제 (The problem)

제목과 스니펫만 반환하는 검색 도구는 에이전트가 출처를 판단하기 전에 두 번째 가져오기 라운드를 강제해요. 반면 전체 페이지 텍스트를 반환하는 검색 도구는 버릴 페이지로 컨텍스트를 범람시켜요. 검색 API를 페이지 패치와 엮고, 각 도구가 반환할 것을 예산 짜고, 에이전트에게 방법론적으로 조사하라고 프롬프트하는 것은 모든 조사 에이전트가 재발명하는 보일러플레이트예요.

ExaSearch는 그 배관을 단일 capability로 묶어요. 조사 도구, 도구별 출력 예산, 시스템 프롬프트의 짧은 조사 안내로요.

사용법 (Usage)

exa extra를 설치하고 EXA_API_KEY 환경 변수를 설정하세요(키는 https://dashboard.exa.ai에서 생성).

pip install "pydantic-ai-harness[exa]"
uv add "pydantic-ai-harness[exa]"

그 다음 ExaSearchAgentcapabilities 매개변수로 넘기세요.

from pydantic_ai import Agent
from pydantic_ai_harness import ExaSearch

agent = Agent('anthropic:claude-sonnet-4-6', capabilities=[ExaSearch()])

result = agent.run_sync('What changed in the latest stable Python release?')
print(result.output)

도구 (Tools)

ExaSearch는 에이전트에 다음 도구를 제공해요.

도구 용도
web_search 웹을 검색해 상위 num_results 페이지를 각각 제목·URL·가장 관련성 높은 발췌와 함께 반환.
get_page 특정 URL 하나의 전체 텍스트 검색 — 유망한 web_search 히트나 사용자가 준 URL.
deep_search Exa의 다단계 딥 검색을 실행하고 합성된 인용 답변을 반환. include_deep_search=True로 선택.
exa_agent 조사 작업을 비동기 Exa 에이전트 실행에 위임. 별도 ExaAgent capability가 제공.

web_search는 전체 페이지 텍스트 대신 짧은 발췌(Exa 하이라이트)를 반환해요. Exa의 에이전트 지침을 따라 여러 출처를 싸게 훑고, 에이전트는 고른 페이지를 get_page로 읽어요.

get_page 텍스트는 max_text_chars 문자로 제한되고, 페이지의 헤드(리드가 핵심을 담아요)를 유지해요. 한도 위로 한 문자 여유를 Exa에 요청하므로, 페이지가 한도를 넘으면 출력이 [... page text truncated at N characters] 표시로 끝나요. API 상한 10,000자에서는 여유가 없어 표시가 나올 수 없어요. 결과 수도 같은 방식으로 제한돼요. num_results가 Exa에 요청되고 응답에 다시 적용돼요.

콘텐츠가 없는 URL이나 질문, 속도 제한, 일시적 API·네트워크 실패는 하드 오류 대신 ModelRetry로 모델에 드러나요. 실행이 계속되고 모델이 URL을 고치거나, 다시 표현하거나, 다시 시도할 수 있죠. 인증 실패(401/403)는 구성 오류라 전파돼요.

deep_searchtype='deep'와 plain-text 출력 스키마로 Exa 검색을 호출해요. Exa가 질문을 여러 쿼리로 확장하고, 검색하고, 인용에 근거한 답변을 반환해요 — 전부 한 번의 도구 호출로, 인용된 출처가 답변 아래 나열되죠. 각 호출은 web_search보다 더 많은 시간과 검색 깊이를 투자해요(Exa의 research-grade 모드). 모델이 도구를 언제 부를지 결정하므로, 도구는 기본적으로 꺼져 있어요. 명시적으로 켜야 해요.

from pydantic_ai_harness import ExaSearch

ExaSearch(include_deep_search=True)

켜면 capability의 지시문이 모델에게 이것을 web_search의 에스컬레이션으로 취급하라고 말해요. 대체가 아니죠. 합성된 답변은 전체로 반환되고(Exa 생성이며 본질적으로 경계가 있어요), max_text_charsget_page에만 적용돼요.

텍스트 요약 (Text summary)

text_summary를 설정하면 모든 web_search 호출이 Exa의 plain-text 출력 스키마도 요청해서, 질문형 쿼리에 결과에서 합성된 짧은 요약을 응답이 실어 나르게 해요. 무제한 요약에는 True를, 원하는 형식을 설명하는 문자열(스키마의 description으로 전송)을 넘겨도 돼요.

from pydantic_ai_harness import ExaSearch

ExaSearch(text_summary='One concise sentence with the requested facts.')

도구의 반환 형태는 그대로이고 하위 호환돼요. 결과 목록은 전처럼 반환되고, Exa가 요약을 반환하면 Summary: 줄로 앞에 붙어요.

구조화된 인용 (Structured citations)

모든 도구는 ToolReturn을 반환해요. return_value는 모델이 보는 읽을 수 있는 텍스트(이전 릴리스와 동일, Sources: 블록 포함)이고, metadata'sources' 키 아래 구조화된 ExaSource 레코드({'url': ..., 'title': ...})로 출처를 실어 나라요. 메타데이터는 절대 모델에 보내지지 않아요. 애플리케이션은 메시지 히스토리의 ToolReturnPart에서 읽으므로, 인용 렌더링에 텍스트 파싱이 필요 없어요.

from pydantic_ai.messages import ModelRequest, ToolReturnPart

for message in result.all_messages():
    if isinstance(message, ModelRequest):
        for part in message.parts:
            if isinstance(part, ToolReturnPart) and part.metadata is not None:
                for source in part.metadata.get('sources', []):
                    print(source['url'], source['title'])

exa_agent 결과는 추가로 Exa 실행 ID를 RUN_ID_METADATA_KEY 아래 메타데이터에 실어 나라요.

지시문 (Instructions)

ExaSearch는 시스템 프롬프트에 짧은 조사 안내를 더해요. 먼저 web_search로 넓게 검색하고, 결론 내리기 전에 유망한 페이지를 get_page로 전체 읽고, 1차 출처를 선호하며, 의존한 URL을 인용하라는 것. include_deep_search=True면 안내가 deep_search로 언제 에스컬레이션할지도 다뤄요. guidance를 설정해 기본 텍스트를 바꾸거나 ''로 지시문을 아예 없애요.

구성 (Configuration)

ExaSearch의 모든 필드와 기본값:

from pydantic_ai_harness import ExaSearch

ExaSearch(
    num_results=5,              # results per web_search call (1 to 100)
    max_text_chars=10_000,      # get_page text cap, in characters (1 to 10,000)
    text_summary=False,         # web_search also returns a synthesized text summary
    include_deep_search=False,  # also expose the deep_search tool
    include_domains=[],         # only search these domains (allowlist)
    exclude_domains=[],         # never search these domains (denylist)
    guidance=None,              # None = default instructions, '' = none, str = custom
    client=None,                # ExaClient -- None builds exa_py.AsyncExa from EXA_API_KEY
)

include_domainsexclude_domainsweb_searchdeep_search에 적용되고 서로 배타적이에요. 하나만 설정하세요. 범위 밖 한도와 두 도메인 목록을 모두 설정하면 생성 시 오류를 일으켜요.

Exa 에이전트 실행 (Exa agent runs)

Exa Agent API는 개방형 조사 작업을 비동기로 실행해요. 실행이 만들어지고 queued -> running을 거쳐 최대 한 시간 후 종료 상태(completed, failed, cancelled)에 도달하죠. 별도 ExaAgent capability는 그 수명주기를 Pydantic AI의 지연 도구 호출에 매핑해요. exa_agent 도구가 실행을 만들고 지연시키며, Exa 실행 ID를 지연 호출의 메타데이터에 실어 나라요.

from pydantic_ai import Agent
from pydantic_ai_harness import ExaAgent

agent = Agent('anthropic:claude-sonnet-4-6', capabilities=[ExaAgent()])

기본(execution='inline')으로 capability는 Exa 실행을 완료까지 폴링하며 지연 호출을 에이전트 실행 안에서 자체 해결해요. 그래서 도구가 일반(느린) 도구처럼 행동하죠. execution='external'이면 호출이 DeferredToolRequests 출력으로 올라와 호스트 애플리케이션이 대역 밖에서 해결해요. Exa 실행 ID가 RUN_ID_METADATA_KEY 아래 요청 메타데이터에 남으므로 다른 프로세스에서까지요. 에이전트의 output_typeDeferredToolRequests를 포함해야 해요. 아니면 지연 요청을 반환하는 대신 실행이 오류를 일으켜요.

from pydantic_ai import Agent
from pydantic_ai.tools import DeferredToolRequests

from pydantic_ai_harness import ExaAgent

agent = Agent(
    'anthropic:claude-sonnet-4-6',
    output_type=[str, DeferredToolRequests],
    capabilities=[ExaAgent(execution='external')],
)

완료된 실행은 agent_run_result 헬퍼로 렌더링하고, capability를 구성할 때 쓴 것과 같은 output_schema를 넘겨 외부 해결이 인라인 실행과 같은 검증과 도구 결과 형태를 내게 한 다음, 결과와 원본 메시지 히스토리를 에이전트에 되돌려 지연 실행을 재개하세요.

from pydantic_ai.tools import DeferredToolResults

from pydantic_ai_harness.exa import RUN_ID_METADATA_KEY, agent_run_result


async def resolve(requests, runs, output_schema=None):  # e.g. in a worker process
    results = DeferredToolResults()
    for call in requests.calls:
        run_id = requests.metadata[call.tool_call_id][RUN_ID_METADATA_KEY]
        run = await runs.poll_until_finished(run_id)
        results.calls[call.tool_call_id] = agent_run_result(run, output_schema=output_schema)
    return results


async def resume(agent, messages, results):
    return await agent.run(message_history=messages, deferred_tool_results=results)

ExaAgent의 모든 필드와 기본값:

from pydantic_ai_harness import ExaAgent

ExaAgent(
    effort=None,            # 'low' | 'medium' | 'high' | 'xhigh' | 'auto' -- None = API default
    execution='inline',     # 'inline' polls to completion; 'external' bubbles DeferredToolRequests
    output_schema=None,     # BaseModel class or dict schema for structured output
    system_prompt=None,     # forwarded to the Exa agent run
    poll_interval=1000,     # ms between polls when resolving inline
    timeout_ms=3_600_000,   # ms to wait for a run when resolving inline
    guidance=None,          # None = default instructions, '' = none, str = custom
    runs=None,              # ExaAgentRuns -- None builds AsyncExa().agent.runs from EXA_API_KEY
)

output_schema 모델 클래스는 완료된 실행의 구조화 출력에 대해 검증돼요(불일치는 ModelRetry로 드러나요). dict 스키마는 클라이언트 측 검증 없이 전달되고 에이전트 스펙 형태예요. 종료 실패(failed, cancelled)는 던지는 대신 구조화된 메시지로 모델에 반환돼, 에이전트가 어떻게 진행할지 결정해요. 각 결과는 실행 ID를 포함하고, 모델은 그걸 previous_run_id로 돌려보내 이전 실행 맥락에서 후속 질문을 할 수 있어요.

여러 인스턴스 (Multiple instances)

같은 capability의 인스턴스 두 개는 같은 도구 이름을 등록하는데, 그것은 오류예요. 한 에이전트에서 다르게 구성된 인스턴스를 여러 개 돌리려면(예: 하나는 개방형 웹 ExaSearch, 하나는 특정 도메인에 고정), 추가 인스턴스를 core의 PrefixTools capability로 감싸 이름을 접두사 붙여요.

from pydantic_ai import Agent
from pydantic_ai.capabilities import PrefixTools

from pydantic_ai_harness import ExaSearch

agent = Agent(
    'anthropic:claude-sonnet-4-6',
    capabilities=[
        ExaSearch(),  # web_search, get_page
        PrefixTools(
            wrapped=ExaSearch(include_domains=['crunchbase.com'], guidance=''),
            prefix='cb',
        ),  # cb_web_search, cb_get_page
    ],
)

감싼 인스턴스에 guidance=''를 설정하거나(또는 접두사 도구를 언제 쓸지 말해주는 텍스트로 교체), 각 인스턴스가 아니면 같은 기본 조사 안내를 더하니까요.

이것은 ExaAgent에도 작동해요. 지연 호출은 도구 이름이 아니라 지연할 때 쓴 메타데이터로 식별하므로, 접두사 붙은 exa_agent도 여전히 인라인 해결되고 여러 ExaAgent 인스턴스가 서로의 호출을 가로채지 않아요.

커스텀 클라이언트 (Custom client)

기본 클라이언트는 EXA_API_KEY 환경 변수로 구성된 exa_py.AsyncExa예요. 변수가 없으면 생성이 셋업 힌트와 함께 실패해요. ExaClient 프로토콜을 만족하는 아무 객체나 넘기면(도구 세트가 호출하는 AsyncExa의 부분집합) 인증이나 기본 URL을 명시적으로 구성하거나 테스트에서 가짜로 대체할 수 있어요.

from exa_py import AsyncExa

from pydantic_ai_harness import ExaSearch

ExaSearch(client=AsyncExa(api_key='...'))

ExaSearch vs 핵심 WebSearch

Pydantic AI core는 프로바이더 적응형 WebSearch capability를 제공해요. 네이티브 검색 도구가 있는 모델에서는 프로바이더 자체 검색을 서버 측에서 실행하고, 아니면 로컬 DuckDuckGo 도구로 폴백하죠. 모델을 따르는 검색을 원할 때 손대세요.

모든 모델에서 같은 검색 동작을 원할 때 ExaSearch를 손대세요. 한 벤더, 모든 히트에 발췌, 명시적 페이지 검색, 도메인 필터, 선택적 딥 검색이죠.

둘을 결합할 때 주의 하나: Anthropic 모델에서 프로바이더 네이티브 검색 도구도 유선상 web_search라 불러서, capabilities=[WebSearch(), ExaSearch()]가 같은 이름의 도구 두 개를 요청에 넣어요. 네이티브 검색 모델에서는 에이전트당 검색 capability 하나를 쓰거나, WebSearch(native=False)로 로컬 폴백을 강제하세요(그 DuckDuckGo 도구는 duckduckgo_search라 충돌하지 않아요).

ExaSearch vs Exa의 MCP 서버

Exa는 공식 호스팅 MCP 서버도 https://mcp.exa.ai/mcp(exa-labs/exa-mcp-server)에 제공해요. 기본적으로 web_search_exaweb_fetch_exa를 노출하고, 전체 카탈로그는 web_search_advanced_exa와 에이전트 실행 세트(agent_create_run, agent_wait_for_run, agent_get_run_output, agent_cancel_run)를 추가해요.

ExaSearch는 큐레이션된 타입 있는 경로예요. 경계 있는 출력, 빈 결과의 재시도 계약, 묶인 조사 지시문, 오프라인에서 테스트 가능한 클라이언트 이음새가 있죠. MCP 서버는 Pydantic AI core의 MCP capability를 통해 제로 래퍼 코드로 Exa의 전체 카탈로그를 얻는 길이에요. 에이전트 실행은 만들고-폴링하고(agent_create_run이 즉시 ID 반환, agent_wait_for_run이 폴링), 반면 deep_search는 단일 호출로 답변을 반환해요. 둘은 하나의 capabilities 목록에서 조합되고, MCP 도구 이름 중 web_search, get_page, deep_search와 충돌하는 건 없어요.

from pydantic_ai import Agent
from pydantic_ai.capabilities import MCP
from pydantic_ai_harness import ExaSearch

agent = Agent('anthropic:claude-sonnet-4-6', capabilities=[ExaSearch(), MCP('https://mcp.exa.ai/mcp')])

에이전트 스펙 (YAML/JSON)

ExaSearch는 Pydantic AI의 agent spec으로 작동해, 파이썬 대신 설정 파일로 선언할 수 있어요.

# agent.yaml
model: anthropic:claude-sonnet-4-6
capabilities:
  - ExaSearch:
      num_results: 3
      include_deep_search: true
  - ExaAgent:
      effort: low
from pydantic_ai import Agent
from pydantic_ai_harness import ExaAgent, ExaSearch

agent = Agent.from_file('agent.yaml', custom_capability_types=[ExaSearch, ExaAgent])

custom_capability_types를 넘겨 스펙 로더가 capability를 인스턴스화하는 법을 알게 하세요. clientruns 필드는 스펙 직렬화가 안 되고, 스펙 로드 인스턴스는 항상 EXA_API_KEY에서 기본 클라이언트를 만들어요. 스펙에서 output_schema는 JSON-schema dict 형태를 취하고, Pydantic 모델 클래스는 파이썬에서 capability를 만들 때만 가능해요.

API 참조 (API reference)

ExaSearch

Bases: AbstractCapability[AgentDepsT]

Exa 검색 API를 기반으로 한 에이전트용 웹 조사.

두 도구를 추가해요. web_search는 가장 관련성 높은 발췌와 함께 검색 결과를 반환하고, get_page는 특정 URL의 전체 텍스트를 가져와요. text_summary를 설정하면 web_search도 결과와 함께 합성된 텍스트 요약을 반환해요. include_deep_search=True를 설정하면 deep_search도 노출하며, Exa의 다단계 딥 검색을 실행하고 한 번의 도구 호출로 합성된 인용 답변을 반환해요.

from pydantic_ai import Agent
from pydantic_ai_harness.exa import ExaSearch

agent = Agent('anthropic:claude-sonnet-4-6', capabilities=[ExaSearch()])

인증은 기본적으로 EXA_API_KEY 환경 변수에서 와요. client를 넘기면 명시적으로 구성해요.

속성
  • num_resultsweb_search가 쿼리당 반환하는 결과 수(1~100, Exa API 범위). Default: 5
  • max_text_charsget_page가 반환하는 페이지 텍스트 최대 문자 수(1~10,000, Exa API 범위). 한도 위로 한 문자 여유를 Exa에 요청해 로컬 잘림이 더 긴 페이지를 감지하고 잘림 표시를 추가할 수 있게 해요. API 상한 10,000에서는 여유가 없어 표시가 나올 수 없어요. Default: 10000
  • text_summaryweb_search가 결과 위에 합성 텍스트 요약도 반환하게. 기본 꺼짐. 켜면 각 web_search 호출이 Exa의 plain-text 출력 스키마를 요청해, 결과 목록에 더해 짧은 요약을 응답이 담아요. 문자열을 넘기면 원하는 요약 형식을 설명(스키마의 description으로 전송)하고, True는 무제한 요약. 도구 반환 형태는 그대로: Exa가 요약을 반환하면 Summary: 줄로 앞에 붙어요. Default: False
  • include_deep_searchdeep_search 도구도 노출. 기본 꺼짐. 딥 검색(Exa type='deep')은 다단계 에이전트형 검색을 실행하고 한 번의 호출로 인용 답변을 합성해요. 각 호출은 web_search보다 더 많은 시간과 검색 깊이를 투자하고, 모델이 도구를 언제 부를지 결정하므로 그 투자는 기본이 아니라 선택이에요. Default: False
  • include_domains — 비어 있지 않으면 결과가 이 도메인에서만(허용 목록). web_searchdeep_search에 적용. exclude_domains와 상호 배타. Default: []
  • exclude_domains — 결과가 이 도메인에서 나오지 않음(거부 목록). web_searchdeep_search에 적용. include_domains와 상호 배타. Default: []
  • guidance — 시스템 프롬프트의 커스텀 조사 안내. 기본 안내(include_deep_search에 맞춰 적응)는 None, 지시문을 아예 없애려면 ''. Default: None
  • client — 쓸 Exa 클라이언트. None이면 exa_py.AsyncExaEXA_API_KEY에서 만들어져요. ExaClient 프로토콜을 만족하는 아무 객체: API 키를 명시적으로 넘기거나 다른 base URL을 가리키거나 테스트에서 가짜로 대체. Default: None
메서드
  • __post_init__def __post_init__() -> None. Exa API의 문서화된 경계에 대해 구성을 검증.
  • get_instructionsdef get_instructions() -> AgentInstructions[AgentDepsT] | None. 정적 조사 안내: 넓게 검색하고, 유망한 페이지를 전체 읽고, URL을 인용. include_deep_search가 설정되면 기본 안내가 deep_search로 에스컬레이션할 때도 다뤄요. None이 아닌 guidance가 기본을 대체하고, ''는 지시를 완전히 끄죠.
  • get_toolsetdef get_toolset() -> ExaSearchToolset[AgentDepsT]. web_search, get_page, 선택적 deep_search 도구를 제공하는 toolset을 만들.
  • from_spec@classmethod def from_spec(...) -> ExaSearch[AgentDepsT]. 직렬화 가능한 스펙 옵션에서 capability를 구성. client 필드는 스펙 직렬화가 안 되므로 스펙 로드 인스턴스는 항상 EXA_API_KEY에서 기본 exa_py.AsyncExa를 만들어요.

ExaAgent

Bases: AbstractCapability[AgentDepsT]

딥 조사 작업을 Exa Agent API에 위임.

도구 하나 exa_agent를 추가해요. 비동기 Exa 에이전트 실행(queued -> running -> terminal, 최대 한 시간)을 만들고 도구 호출을 지연시켜요. 기본적으로 capability는 폴링으로 실행을 완료까지 지연 호출을 인라인 해결해요. execution='external'이면 호출이 DeferredToolRequests 출력으로 드러나 호스트 애플리케이션이 대역 밖에서 해결하죠(실행 ID는 RUN_ID_METADATA_KEY 아래 요청 메타데이터에 있어 프로세스 재시작을 가로질러요).

from pydantic_ai import Agent
from pydantic_ai_harness.exa import ExaAgent

agent = Agent('anthropic:claude-sonnet-4-6', capabilities=[ExaAgent()])

인증은 기본적으로 EXA_API_KEY 환경 변수에서 와요. runs를 넘기면 명시적으로 구성해요.

속성
  • effort — Exa 에이전트가 실행당 투자하는 작업량. None은 API 기본. Type: AgentEffort | None Default: None
  • execution — 지연 exa_agent 호출을 어떻게 해결할지. 'inline'은 에이전트 실행 동안 실행을 완료까지 폴링. 'external'은 호출이 DeferredToolRequests 출력으로 올라와 호스트가 해결(agent_run_result 참고), 단일 프로세스를 오래 사는 durable worker에 맞아요. Default: 'inline'
  • output_schema — Exa 에이전트 결과의 구조화 출력 스키마. None은 산문. Pydantic 모델 클래스나 JSON-schema dict를 받아요. 모델 클래스는 API에 전달되고 완료된 실행의 구조화 출력이 그에 대해 검증돼요(불일치는 재시도로 드러나요). dict 형태는 클라이언트 측 검증을 건너뛰고 에이전트 스펙이 쓰는 직렬화 가능 형태예요. Default: None
  • system_prompt — Exa 에이전트 실행에 전달되는 시스템 프롬프트. None은 API 기본. Default: None
  • poll_interval — 실행을 인라인 해결하며 폴링 사이 밀리초. Default: 1000
  • timeout_ms — 인라인 해결 시 실행이 끝나길 기다리는 밀리초. Default: 3600000
  • guidance — 시스템 프롬프트의 커스텀 위임 안내. None이면 기본, ''는 지시문 없음. Default: None
  • runs — Exa Agent 실행 클라이언트. None이면 exa_py.AsyncExa().agent.runsEXA_API_KEY에서 만들어져요. ExaAgentRuns 프로토콜을 만족하는 아무 객체: API 키를 명시적으로 넘기거나 테스트에서 가짜로 대체. Default: None
메서드
  • get_instructionsdef get_instructions() -> AgentInstructions[AgentDepsT] | None. 정적 위임 안내: 언제 작업을 exa_agent에 넘기고 실행 ID 연속. None이 아닌 guidance가 기본을 대체, ''는 지시 완전히 끔.
  • get_toolsetdef get_toolset() -> ExaAgentToolset[AgentDepsT]. exa_agent 도구를 제공하는 toolset을 만들.
  • handle_deferred_tool_calls@async def handle_deferred_tool_calls(ctx, *, requests) -> DeferredToolResults | None. 지연 exa_agent 호출을 Exa 실행을 완료까지 폴링해 인라인 해결. execution='external'이면 모든 호출이 해결되지 않은 채 남아 DeferredToolRequests 출력으로 올라가고, Exa 실행 ID는 requests.metadata[tool_call_id]에서 쓸 수 있어요. 호출은 도구 이름이 아니라 지연 호출 메타데이터의 인스턴스 토큰으로 주장돼, 도구 이름 바꾸기나 접두사 래퍼(예: PrefixTools)가 인라인 해결을 깨지 않아요.
  • from_spec@classmethod def from_spec(...) -> ExaAgent[AgentDepsT]. 직렬화 가능한 스펙 옵션에서 capability를 구성. runs 필드는 스펙 직렬화가 안 되므로 스펙 로드 인스턴스는 항상 EXA_API_KEY에서 기본 클라이언트를 만들어요. output_schema는 여기서 JSON-schema dict 형태를 취하고, Pydantic 모델 클래스는 파이썬에서 capability를 만들 때만 가능해요.

agent_run_result

def agent_run_result(
    run: AgentRun,
    *,
    output_schema: type[BaseModel] | dict[str, object] | None = None,
) -> ToolReturn[str]

종료된 Exa 에이전트 실행을 exa_agent 도구 결과로 렌더링. 호스트 애플리케이션에서 외부 실행 exa_agent 호출을 해결할 때(DeferredToolResults 만들기) 써서 외부와 인라인 실행이 같은 결과 형태를 내게 해요. output_schema가 Pydantic 모델 클래스면 완료된 실행의 구조화 출력이 그에 대해 검증되고, 불일치는 ModelRetry를 올려요. ToolReturn.return_value 텍스트가 모델이 보는 것, 그 메타데이터는 RUN_ID_METADATA_KEY 아래 실행 ID와 인용 sources(ExaSource dict)를 애플리케이션이 직접 쓰도록 실어 나라요.

ExaSource

Bases: TypedDict

도구 결과 뒤에 있는 출처 하나, ToolReturn.metadata['sources']에 실려요.

더 알아보기 (Learn more)