Responses API에서의 File Search

LiteLLM은 이제 Responses API에서 file_search를 지원합니다. 네이티브로 지원하는 제공자(OpenAI, Azure 등)와, 지원하지 않는 제공자(Anthropic, Bedrock 등 비네이티브 제공자)를 에뮬레이션으로 처리하는 두 경로 모두를 다룹니다. 단일 OpenAI 호환 출력 형태를 유지하면서 네이티브 패스스루 또는 에뮬레이션 폴백으로 요청을 라우팅해요.

출처: 문서

본문

이것이 무엇인가요?

file_search는 모델이 벡터 스토어에서 근거(grounded context)를 검색하고 인용문과 함께 답변하게 해줍니다. LiteLLM은 네이티브 패스스루나 에뮬레이션 폴백을 통해 요청을 라우팅하면서도 OpenAI 호환 출력 형태 하나를 유지해요.

두 가지 경로를 다룹니다:

| Path | When it runs | What LiteLLM does | | Native passthrough | Provider natively supports file_search (OpenAI, Azure) | Decodes unified vector store ID → forwards to provider as-is | | Emulated fallback | Provider doesn't support file_search (Anthropic, Bedrock, etc.) | Converts to a function tool → intercepts tool call → runs vector search → synthesizes OpenAI-format output |

tools[].vector_store_ids에서 LiteLLM은 제공자 네이티브 ID(예: vs_...)와 관리형 벡터 스토어 통합 ID(프록시 managed-vector 흐름의 URL 안전 base64 문자열)를 모두 허용합니다. 예: litellm.responses(..., tools=[{"type": "file_search", "vector_store_ids": ["bGl0ZWxsbV9wcm94eT..."]}]).

사용법

  • LiteLLM Proxy
  • LiteLLM SDK

1. config.yaml 설정

config.yaml

model_list:
  - model_name: gpt-5.6-terra
    litellm_params:
      model: openai/gpt-5.6-terra
      api_key: os.environ/OPENAI_API_KEY
  - model_name: claude-sonnet
    litellm_params:
      model: anthropic/claude-sonnet-5
      api_key: os.environ/ANTHROPIC_API_KEY

2. Proxy 시작

litellm --config config.yaml

3. file_search로 Responses API 호출

Proxy call

from openai import OpenAIclient = OpenAI(base_url="http://localhost:4000", api_key="***")response = client.responses.create(
    model="claude-sonnet",  # swap to "gpt-5.6-terra" for native path
    input="What does LiteLLM support?",
    tools=[{
        "type": "file_search",
        "vector_store_ids": ["vs_abc123"]
    }],
    include=["file_search_call.results"],)print(response.output)

1. 설치 및 키 설정

uv add litellmexport OPENAI_API_KEY="sk-..."export ANTHROPIC_API_KEY="sk-ant-..."

2. file_search로 Responses API 호출

SDK call

import litellmresponse = litellm.responses(
    model="anthropic/claude-sonnet-5",  # swap to openai/gpt-5.6-terra for native path
    input="What does LiteLLM support?",
    tools=[{
        "type": "file_search",
        "vector_store_ids": ["vs_abc123"]
    }],
    include=["file_search_call.results"],)print(response.output)

동작 매트릭스

| Path | SDK model | Proxy model | Behavior | | Native passthrough | openai/gpt-5.6-terra | gpt-5.6-terra | Provider executes native file_search | | Emulated fallback | anthropic/claude-sonnet-5 | claude-sonnet | LiteLLM converts to function tool and synthesizes OpenAI-format output |

아키텍처 다이어그램

전제 조건

uv tool install 'litellm[proxy]'export OPENAI_API_KEY="sk-..."          # for native pathexport ANTHROPIC_API_KEY="sk-ant-..."  # for emulated path

예시 응답 형태

출력 형식 검증

어느 경로로 실행됐든 응답은 항상 OpenAI Responses API 형식을 따릅니다:

{
  "output": [
    {
      "type": "file_search_call",
      "id": "fs_abc123",
      "status": "completed",
      "queries": ["What does LiteLLM support?"],
      "search_results": null
    },
    {
      "type": "message",
      "role": "assistant",
      "content": [
        {
          "type": "output_text",
          "text": "LiteLLM is a unified interface...",
          "annotations": [
            {
              "type": "file_citation",
              "index": 150,
              "file_id": "file-xxxx",
              "filename": "knowledge.txt"
            }
          ]
        }
      ]
    }
  ]
}

검증 스크립트:

Validate response structure

def validate_file_search_response(response):
    """Assert that response follows OpenAI file_search output format."""
    output = response.output
    assert len(output) >= 2, "Expected at least 2 output items"
    # First item: file_search_call
    fs_call = output[0]
    fs_type = fs_call["type"] if isinstance(fs_call, dict) else fs_call.type
    assert fs_type == "file_search_call", f"Expected file_search_call, got {fs_type}"
    fs_status = fs_call["status"] if isinstance(fs_call, dict) else fs_call.status
    assert fs_status == "completed"
    # Second item: message
    msg = output[1]
    msg_type = msg["type"] if isinstance(msg, dict) else msg.type
    assert msg_type == "message"
    content = msg["content"] if isinstance(msg, dict) else msg.content
    assert len(content) > 0
    text_block = content[0]
    text = text_block["text"] if isinstance(text_block, dict) else text_block.text
    assert isinstance(text, str) and len(text) > 0
    print("✅ Response structure valid")
    print(f"   Queries: {fs_call['queries'] if isinstance(fs_call, dict) else fs_call.queries}")
    print(f"   Answer length: {len(text)} chars")
    annotations = text_block["annotations"] if isinstance(text_block, dict) else text_block.annotations
    print(f"   Citations: {len(annotations)}")validate_file_search_response(response)

Q&A

  • UnsupportedParamsError가 보이는 이유는? 보통 file_search를 네이티브로 지원하지 않는 제공자에 전달했는데 에뮬레이션이 올바르게 라우팅하지 못했을 때 발생해요. 확인할 것:

    • 모델 문자열이 유효한지(예: anthropic/claude-sonnet-5)
    • custom_llm_provider가 올바르게 해석되어 LiteLLM이 제공자 설정을 로드할 수 있는지
  • 벡터 검색이 결과를 반환하지 않는 이유는? 흔한 원인:

    • 벡터 스토어 ID가 잘못됐거나 파일이 첨부되지 않음
    • LiteLLM 관리형 스토어에서 파일 수집이 완료되지 않음(status != completed)
    • 쿼리가 너무 좁음; 더 넓은 쿼리를 시도해 보세요
  • 벡터 스토어 호출에서 403 Access denied가 발생하는 이유는? 호출자가 해당 벡터 스토어에 접근 권한이 없는 경우예요.

    • 스토어가 다른 팀에 속해 있을 수 있어요
    • 크로스팀 접근이 필요하면 admin/proxy 키를 사용하세요
  • 에뮬레이션 모드에서 annotations가 비어 있는 이유는? file_citation 주석은 검색 결과에 file_id 메타데이터가 필요해요. 벡터 백엔드가 파일 수준 메타데이터를 반환하지 않으면 답변 텍스트는 생성되지만 인용문은 비어 있을 수 있어요.

다음으로 확인할 것

  • Responses API 문서의 File Search 참조 - 전체 API 참조
  • 벡터 스토어 관리 - 벡터 스토어 생성 및 관리
  • 관리형 벡터 스토어 - 제공자별 설정

더 알아보기 (Learn more)