Skip to main content
언어 모델은 텍스트 요약에는 강하지만 사실을 기억하는 데는 약합니다. 이 튜토리얼은 사실을 모델의 기억이 아니라 프롬프트에 두어, 출력의 모든 문장을 조금 전에 가져온 페이지로 되짚을 수 있도록 합니다. 우리는 짧고 인용이 달린 브리핑으로 질문에 답변하는 명령줄 도구를 만들 것입니다:
진행 과정에서 다음을 수행합니다:
  1. /augment/search로 실시간 웹을 검색합니다
  2. 그 결과 중 어느 것을 읽을 가치가 있는지 결정합니다
  3. 선택한 페이지를 /augment/scrape로 Markdown으로 변환합니다
  4. 채팅 모델에게 번호로 소스를 인용하는 브리핑을 작성하도록 요청합니다
  5. 네 단계를 하나의 스크립트로 연결합니다
모델에게 맡기지 않고 우리가 직접 검색을 수행하면 결과를 감사(audit)할 수 있게 됩니다. 프롬프트에 들어간 페이지의 정확한 목록을 우리가 보관하며, 각 주장이 어디서 나왔는지 독자에게 보여줄 수 있습니다. 만약 Venice가 단일 요청 내에서 검색을 처리하도록 하고 싶다면, 채팅 완성에 venice_parameters.enable_web_search를 설정하세요. 웹 검색 및 스크래핑 가이드에서 두 접근 방식을 비교합니다.

준비 사항

Python 3.9 이상, requests 패키지, 그리고 Venice API 키가 필요합니다. 아직 키가 없다면 API 키 생성하기를 참조하세요.
research.py를 만들고, import 문과 모든 호출에서 재사용할 공통 헤더 블록으로 시작하세요:

1. 웹 검색하기

/augment/search는 쿼리를 받아 최대 20개의 순위가 매겨진 결과를 반환합니다. Brave가 기본 제공자이며 Zero Data Retention을 적용합니다. Google도 사용 가능하며 Venice를 통해 프록시되므로 쿼리는 사용자와 결코 연결되지 않습니다.
각 결과는 네 개의 필드를 가진 객체입니다:
응답에 대해 알아두면 좋은 두 가지가 있습니다. content 필드는 HTML이 포함된 채로 도착합니다. 제공자가 일치한 용어를 <strong> 태그로 감싸기 때문입니다. 위의 HTML_TAG 치환은 이를 제거해 스니펫이 일반 텍스트로 모델에 도달하도록 합니다. date 필드는 종종 빈 문자열입니다. 많은 페이지가 기계가 읽을 수 있는 날짜를 게시하지 않으므로, date는 정렬하거나 필터링할 수 있는 필드라기보다는, 값이 있을 때 참고할 수 있는 힌트로 취급하세요.
limit은 1과 20 사이여야 하고, query는 1자에서 400자 사이여야 합니다. 해당 범위를 벗어나는 값은 검증 본문과 함께 HTTP 400을 반환합니다. 자동으로 조정(clamp)되지 않습니다.

2. 읽을 소스 선택하기

열 개의 결과를 모두 스크래핑하는 것은 느리고, 비싸고, 대체로 중복됩니다. 검색 엔진은 같은 사이트에서 여러 페이지를 반환하며, 특히 문서 사이트는 같은 페이지를 여러 언어로 반환하기 때문에 동일한 콘텐츠가 서로 다른 URL 아래 서너 번 나타날 수 있습니다. 도메인별로 가장 높은 순위의 결과만 유지하면 몇 줄의 코드로 대부분의 중복을 제거할 수 있습니다:
위의 열 개 결과에 대해 실행하면 네 개의 서로 다른 사이트로 좁혀집니다:
이 단계는 여러분의 판단을 더하기에 자연스러운 지점입니다. 신뢰하는 도메인을 허용 목록에 넣거나, 스니펫에 핵심 용어가 전혀 언급되지 않는 결과를 버리거나, 최근 date를 가진 결과를 선호할 수 있습니다. 여기서 적용하는 각 필터는 모델이 잘못 판단할 기회를 없애는 결정입니다.

3. 선택한 페이지 스크래핑하기

/augment/scrape는 공개 URL을 가져와 Markdown으로 반환합니다. 먼저 사이트에게 네이티브 Markdown 표현을 요청하고, 없으면 브라우저 기반 추출로 대체(fallback)합니다. 일부 페이지는 실패할 것이며, 리서치 도구는 이를 치명적이 아닌 일상적인 일로 다뤄야 합니다:
두 가드는 모두 존재할 이유가 있습니다. 상태 코드 검사는 자동 접근을 거부하는 사이트를 걸러내고, 길이 검사는 200을 반환하지만 기사 대신 쿠키 배너나 빈 껍데기만 돌려주는 페이지를 걸러냅니다.
스크래핑 실패는 읽을 수 있는 메시지가 담긴 단순한 {"error": "..."} 본문을 반환합니다. 예를 들어 X (formerly Twitter) blocks automated access to their content. 같은 형태입니다. X와 Reddit은 완전히 차단됩니다. 답변에 X의 게시글을 포함하려면 대신 채팅 완성에 venice_parameters.enable_x_search를 사용하세요.
요청들은 서로 의존하지 않으므로 병렬로 실행합니다. 이 김에 각 페이지에서 유지할 분량에 상한을 두겠습니다:
세 번째 페이지가 정확히 12000자로 돌아왔다는 것은 예산보다 길어서 잘렸다는 뜻입니다.
char_budget은 있으면 좋은 정도의 옵션이 아닙니다. 검색 결과에는 sitemap, changelog, llms-full.txt 파일 같은 집계 페이지가 자주 포함되며, 그 중 하나가 백만 자에 가까운 분량을 반환할 수 있습니다. 상한이 없다면 운 나쁜 결과 하나가 전체 요청 비용을 좌우하게 됩니다.

4. 브리핑 작성하기

이제 우리가 수집한 페이지에 번호를 매겨 모델에 넘기고, 그 번호를 참조하는 인용을 요청합니다. 프롬프트의 번호 매기기가 나중에 출력의 [2]를 URL로 되돌릴 수 있게 해주는 것입니다.
낮은 temperature는 표현을 소스 텍스트에 가깝게 유지합니다. enable_web_searchoff로 설정하는 것은 기본값과 동일하지만, 명시적으로 지정하면 모델이 참조 목록에 없는 소스를 몰래 도입할 수 없다는 것이 보장됩니다.

5. 하나로 합치기

마지막 조각은 각 단계를 순서대로 실행하고 인용 번호를 해석하는 참조 목록을 덧붙입니다:
진행 메시지는 stderr로 나가므로 브리핑만 파일로 리디렉션할 수 있습니다:
다음은 생성된 브리핑의 앞부분을 요약한 것입니다:
이 발췌본에서 소스 3이 전혀 인용되지 않았음을 눈여겨보세요. 이것이 우리가 원하는 동작입니다. 모델은 관련성 있는 소스만 사용하고 나머지는 그대로 두었으며, 인용에 번호가 매겨져 있기 때문에 그것을 한눈에 확인할 수 있습니다.

파이프라인 튜닝하기

전체 시간 대부분은 마지막 채팅 완성에서 소요됩니다. 네 개의 스크랩된 페이지가 수만 토큰에 이르기 때문입니다. 가장 먼저 손댈 만한 조정 지점은 다음과 같습니다:

다음 단계

지금까지 만든 파이프라인은 완성된 제품이라기보다는 기초입니다. 살펴볼 만한 몇 가지 방향은 다음과 같습니다:
  • 스크랩된 Markdown을 URL별로 캐시해서 같은 질문이 반복될 때 같은 페이지를 다시 가져오지 않도록 합니다.
  • 임베딩으로 Markdown을 벡터로 저장하고, 전체 페이지 대신 구절을 검색합니다.
  • 프라이빗 리서치 에이전트 데모처럼, 모델이 검색하기 전에 여러 쿼리를 계획하도록 합니다.
  • 브리핑을 텍스트-음성 변환으로 기사 낭독하기에 파이프해 낭독하게 합니다.

웹 검색 및 스크래핑

Search와 Scrape 엔드포인트에 대한 레퍼런스.

텍스트-음성 변환으로 기사 낭독하기

방금 생성한 텍스트를 오디오로 바꾸세요.

임베딩

스크랩한 Markdown을 다시 가져오는 대신 색인하세요.

프라이빗 리서치 에이전트

스스로 검색을 계획하는 더 큰 에이전트.