AI

Bright Data로 Salesforce Agentforce에서 계정 리서치 에이전트 구축하기

고객 통화 전 계정 리서치는 영업 담당자당 10~15분이 소요됩니다. Bright Data와 Salesforce Agentforce를 활용해 리서치 에이전트를 구축하는 방법을 알아보세요.
10 분 읽기
Build an account research agent in Salesforce Agentforce with Bright Data

고객 통화 전 계정 조사는 영업 담당자 한 명당 보통 10~15분이 소요됩니다. 워크플로우는 대부분 수동으로 이루어집니다. 담당자가 Salesforce를 벗어나 Google을 열고, 여러 탭을 훑어본 뒤, 발견한 내용을 메모 필드에 붙여넣는 방식입니다. 작업의 대부분은 검색과 종합으로 이루어집니다.

Bright Data의 Web Unlocker는 대부분의 공개 URL에서 깔끔한 Markdown을 반환합니다. 이를 Salesforce Agentforce에 연결하면 담당자가 Salesforce를 벗어나지 않고도 채팅 프롬프트를 통해 출처가 명시된 계정 조사 결과를 받을 수 있습니다. 내부적으로 이 빌드는 Agentforce 서브에이전트 하나, Apex 클래스 세 개, 그리고 소규모 Cloudflare Worker 프록시로 구성됩니다.

TL;DR

  • Agentforce 서브에이전트가 영업 담당자의 자연어 프롬프트를 받아 Bright Data Web Unlocker를 호출하고, 출처가 명시된 계정 브리핑을 Salesforce 채팅 내에서 반환합니다.
  • Apex의 HTTP 클라이언트는 몇 킬로바이트를 초과하는 청크 전송 응답에서 경고 없이 실패합니다(API v66.0에서 확인됨). 따라서 통합은 응답을 버퍼링하고 명시적인 Content-Length 헤더로 재전송하는 소규모 Cloudflare Worker를 통해 라우팅됩니다.
  • Agentforce의 Canvas UI는 채팅 기반 에이전트가 프롬프트에서 추출한 입력을 받는 데 필요한 is_user_input: True YAML 플래그를 숨깁니다. 수정은 Script 모드에서 이루어집니다.
  • Salesforce의 현재 External Credential 패턴은 세 개의 객체(External Credential, Named Credential, Permission Set)로 분리되며, 하나의 체크박스를 놓치면 401 오류가 반환됩니다.
  • Agentforce는 기본적으로 에이전트 응답에서 외부 URL을 편집합니다. 에이전트는 내부적으로 URL을 읽지만, 해당 도메인이 신뢰할 수 있는 URL 허용 목록에 없으면 표시하지 않습니다.
  • 전체 구성 요소는 약 6KB의 Apex, Cloudflare Worker 하나, Salesforce 자격 증명 객체 세 개, 서브에이전트 하나입니다. 모든 코드 블록은 실제 Salesforce Developer Edition 조직에서 테스트되었습니다.

시작하기 전에

이 튜토리얼에 필요한 네 가지 계정과 도구가 필요하며, 모두 무료입니다:

  • Bright Data 계정: Web Unlocker 존이 하나 이상 프로비저닝되어 있어야 합니다. 신규 계정에는 무료 체험 크레딧이 포함되며, 튜토리얼의 요청량은 해당 크레딧 내에서 충분히 처리됩니다.
  • Cloudflare 계정: Worker 프록시용입니다. 무료 티어에는 신용카드가 필요하지 않으며, 처음 사용 시 workers.dev 서브도메인을 선택하게 됩니다.
  • Agentforce가 활성화된 Salesforce Developer Edition 조직. 최근 Developer Edition 조직에는 Agentforce, Data Cloud, Agentforce Studio가 사전 활성화되어 있습니다. 5부를 진행하기 전에 App Launcher에서 Agentforce Studio 앱이 표시되는지 확인하세요. 표시되지 않으면 Agentforce가 없는 조직이므로 이후 단계가 작동하지 않습니다.
  • 테스트 HTTP 요청을 보낼 수 있는 방법. 2부에는 Worker를 확인하는 curl 명령이 포함되어 있습니다. macOS, Linux, Windows 11에는 curl이 기본 제공됩니다. GUI를 선호한다면 Postman이나 Insomnia도 동일한 헤더와 본문으로 사용할 수 있습니다. 이 중 아무것도 없고 설치하고 싶지 않다면, 독립 실행형 Worker 테스트를 건너뛰고 3부에서 Salesforce를 통해 엔드투엔드로 검증할 수 있습니다.
  • 시스템 관리자 프로필(또는 Author Apex, Modify All Data, Customize Application 권한이 있는 프로필). 새로운 Developer Edition 조직은 자동으로 이 권한을 부여합니다. 잠긴 프로필이 있는 회사 샌드박스에서 작업 중이라면 새로운 Developer Edition 조직으로 전환하세요.

빌드할 내용

Account Briefing Agent라는 Agentforce 에이전트를 빌드합니다. 담당자가 자연어로 질문을 입력하면, 에이전트가 프롬프트를 커스텀 서브에이전트로 라우팅하고, 적절한 도구를 선택하며, 얇은 Cloudflare Worker 프록시를 통해 Bright Data를 호출하고, 출처가 명시된 계정 브리핑을 종합하여 채팅에 게시합니다. 아키텍처는 다섯 가지 구성 요소로 이루어집니다:

  • Bright Data Web Unlocker: 웹 데이터 기본 요소입니다. 대부분의 URL을 받아 깔끔한 Markdown을 반환하는 단일 엔드포인트입니다.
  • Cloudflare Worker: Salesforce와 Bright Data 사이의 프록시입니다. 무료 티어로 소규모 팀을 지원합니다.
  • Salesforce External Credential + Named Credential + Permission Set: 인증 레이어입니다.
  • Apex: 세 개의 클래스로 구성됩니다. 공유 서비스 하나와 @InvocableMethod를 사용하는 래퍼 두 개(Agentforce에서 호출 가능하게 하는 어노테이션으로, Agent Action당 하나씩)입니다.
  • Agentforce 서브에이전트: 두 개의 액션, 지침 블록, 분류 설명이 연결되어 있습니다.

아키텍처 개요

담당자의 프롬프트에서 브리핑까지의 요청 흐름은 다음과 같습니다:

Rep prompt in Agentforce
        │
        ▼
Agent Router  ──►  Account Web Intelligence subagent
                          │
                          ├─► Apex: BrightDataNewsAction
                          │     └─► Named Credential → Cloudflare Worker → Web Unlocker → Google News
                          │
                          └─► Apex: BrightDataFetchAction
                                └─► Named Credential → Cloudflare Worker → Web Unlocker → target URL
        │
        ▼
LLM synthesis  ──►  Briefing back to the rep

Cloudflare Worker가 필요한 이유는 Salesforce Apex가 HTTP/1.1 청크 전송 응답을 안정적으로 처리하지 못하기 때문이며, Bright Data는 몇 킬로바이트를 초과하는 페이로드에 청크 인코딩을 사용합니다. Worker는 응답을 단일 ArrayBuffer로 버퍼링하고 명시적인 Content-Length 헤더와 함께 재전송합니다. 이 없이는 Apex의 모든 호출이 200 상태 코드와 0바이트 본문을 반환합니다. 아래 2부에서 디버그 및 수정 과정을 설명합니다.

Bright Data에는 이런 종류의 빌드에 적합한 여러 제품이 있습니다. 파싱된 Google 결과를 위한 SERP API, LinkedIn 및 Crunchbase 전용 스크레이퍼 등이 있습니다. 이 빌드는 Web Unlocker만 사용하는데, 모든 URL에 동일한 엔드포인트를 통해 작동하므로 Apex 측을 단순하게 유지할 수 있기 때문입니다. 2부의 Cloudflare Worker 프록시는 모든 Bright Data API 엔드포인트를 동일하게 지원하므로, 나중에 SERP API나 전용 스크레이퍼로 교체해도 Salesforce 측 배선은 변경되지 않습니다.

1부: Bright Data 설정

Bright Data 계정이 없다면 Bright Data 가입 페이지에서 계정을 만드세요. 사용할 Web Unlocker 존은 대시보드의 Web Access API 섹션에 있습니다.

Web Unlocker 존 생성 또는 확인

대시보드를 열고 왼쪽 탐색 메뉴에서 Web Access API로 이동하여 Web Unlocker 존이 있는지 확인합니다. 계정에 존이 없다면 Create API(오른쪽 상단)를 클릭하고 드롭다운에서 Unlocker API를 선택합니다. 이름은 무엇이든 지정할 수 있습니다(존 이름은 생성 후 변경할 수 없으므로 agentforce_unlocker처럼 안정적인 이름을 선택하세요). 어떤 이름을 사용하든 기록해 두세요. 4부의 BrightDataService.cls에 있는 UNLOCKER_ZONE 상수와 2부의 curl 테스트에 입력해야 합니다.

Web Access API 존 목록을 보여주는 Bright Data 대시보드

Web Unlocker 존은 두 Agentforce 액션이 모두 사용하는 기본 요소입니다.

API 토큰 생성

Settings(왼쪽 하단) → Users and API keys 탭 → User 권한으로 Add API key를 클릭합니다. 키는 생성 시 한 번만 표시되며 이후에는 마스킹됩니다. 지금 복사하여 안전한 곳에 보관하세요. 3부에서 Salesforce에 붙여넣어야 합니다.

Active 상태, User 권한, Unlimited 만료로 설정된 키가 있는 API 키 섹션을 보여주는 Bright Data 계정 설정.

Bright Data 설정은 이것으로 완료됩니다.

2부: Cloudflare Worker 프록시 배포

Salesforce를 구성하기 전에 Bright Data 앞에 프록시가 필요합니다. 이유는 Salesforce Apex가 청크 전송 응답을 읽는 방식의 한계 때문이며, 이는 비중요 HTTP 콜아웃을 만드는 모든 Apex 개발자가 마주칠 가능성이 있는 문제입니다.

버그

Salesforce Apex의 Http 클라이언트는 표준 HTTP를 지원하지만 한 가지 실질적인 한계가 있습니다. Content-Length 헤더 없이 청크 전송 인코딩을 사용하는 HTTP/1.1 응답을 안정적으로 파싱하지 못합니다. 청크 응답에서 콜아웃은 Status Code = 200, Content-Type = null, Response Size = 0 bytes를 반환하며 예외나 경고가 없습니다. getBody()getBodyAsBlob().toString() 모두 빈 문자열을 반환합니다.

Bright Data의 /request 엔드포인트는 몇 킬로바이트를 초과하는 응답에 청크 전송 인코딩을 사용합니다. 작은 테스트 페이지(Bright Data welcome.txt)에 대한 Web Unlocker 호출은 임계값 이하로 유지되어 content-length 응답을 반환하며 Apex가 깔끔하게 파싱합니다. 하지만 실제 페이지(회사 홈페이지, Google News 검색)는 임계값을 초과하여 청크 처리되고, Apex는 빈 본문을 반환합니다.

이것이 네트워크 측이 아닌 Apex 측 문제임을 증명하는 두 가지 사실이 있습니다. 동일한 페이로드로 동일한 엔드포인트에 curl을 호출하면 9KB 본문이 깔끔하게 반환되고, Apex 익명 실행에서 동일한 호출을 하면 응답 헤더에 Transfer-Encoding: chunked가 있는 상태로 0바이트가 반환됩니다.

수정은 구성 변경이 아닌 구조적인 것입니다. Salesforce와 Bright Data 사이에 버퍼링 프록시를 배치하는 것입니다. 프록시는 Bright Data의 청크 스트림을 완전히 읽은 다음 명시적인 Content-Length 헤더와 함께 Salesforce에 응답을 재전송합니다. Apex는 해당 응답을 깔끔하게 파싱합니다.

Cloudflare Worker는 이 프록시를 호스팅하기에 적합합니다. 소량에 대해 무료이고, 몇 분 만에 배포되며, 엣지에서 실행되고, 전체 본문이 JavaScript 한 화면에 들어갑니다.

Worker 생성

계정이 없다면 Cloudflare 대시보드에서 가입합니다. 대시보드에서 Workers를 찾습니다(대시보드 버전에 따라 ComputeWorkers & Pages 아래에 표시됩니다). Create application을 클릭한 다음 템플릿에서 Hello World를 선택합니다. 처음 사용 시 Cloudflare가 workers.dev 서브도메인을 선택하라고 안내합니다. 아무 이름이나 선택하세요(무료 개발 서브도메인입니다). Worker 이름은 기억하기 쉬운 것으로 지정합니다. 이 빌드에서는 bd-proxy를 사용합니다. 플레이스홀더가 배포된 후 Edit code를 클릭합니다.

편집기의 플레이스홀더 코드를 모두 선택하고 다음으로 교체합니다:

/**
 * Bright Data to Salesforce Apex proxy.
 *
 * Salesforce Apex does not reliably consume HTTP/1.1 chunked-transfer
 * responses, which is what Bright Data returns for any non-trivial payload.
 * This Worker buffers the full response and re-serves it with an explicit
 * Content-Length header. Apex parses that response cleanly.
 *
 * Production deployments typically route external API calls through an
 * integration layer like MuleSoft or Heroku. This Worker is the minimal
 * stand-in for that role.
 */

export default {
  async fetch(request) {
    const url = new URL(request.url);
    const bdUrl = 'https://api.brightdata.com' + url.pathname + url.search;

    // Strip Cloudflare-injected headers we shouldn't forward upstream.
    const forwardHeaders = new Headers(request.headers);
    forwardHeaders.delete('host');
    forwardHeaders.delete('cf-connecting-ip');
    forwardHeaders.delete('cf-ray');
    forwardHeaders.delete('cf-visitor');
    forwardHeaders.delete('x-forwarded-for');
    forwardHeaders.delete('x-forwarded-proto');
    forwardHeaders.delete('x-real-ip');

    try {
      const bdResponse = await fetch(bdUrl, {
        method: request.method,
        headers: forwardHeaders,
        body: ['GET', 'HEAD'].includes(request.method)
          ? undefined
          : await request.arrayBuffer(),
      });

      // Buffer the entire response into a single ArrayBuffer. This collapses
      // chunked transfer into a buffer of known length.
      const bodyBuffer = await bdResponse.arrayBuffer();

      const responseHeaders = new Headers();
      const ct = bdResponse.headers.get('Content-Type');
      if (ct) responseHeaders.set('Content-Type', ct);
      responseHeaders.set('Content-Length', bodyBuffer.byteLength.toString());
      const brdStatus = bdResponse.headers.get('x-brd-status-code');
      if (brdStatus) responseHeaders.set('X-Brd-Status-Code', brdStatus);

      return new Response(bodyBuffer, {
        status: bdResponse.status,
        headers: responseHeaders,
      });
    } catch (err) {
      return new Response(
        JSON.stringify({ error: 'Proxy error', message: err.message }),
        { status: 502, headers: { 'Content-Type': 'application/json' } }
      );
    }
  },
};

통합을 수정하는 두 줄은 await bdResponse.arrayBuffer()(전체 청크 스트림을 메모리로 읽음)와 bodyBuffer.byteLength에서 설정된 명시적인 Content-Length 헤더(Apex가 이를 통해 본문을 깔끔하게 파싱함)입니다. 나머지는 헤더 포워딩을 처리합니다. 들어오는 요청에서 Cloudflare가 주입한 헤더를 제거하고 나가는 응답에서 업스트림 상태 코드를 보존합니다.

Deploy(오른쪽 상단)를 클릭합니다. Cloudflare가 https://<worker-name>.<your-subdomain>.workers.dev와 같은 URL을 제공합니다. 복사해 두세요. 3부에서 Salesforce에 이 URL이 필요합니다.

bd-proxy worker.js 파일을 보여주는 Cloudflare Worker 코드 편집기. 버퍼링 로직(`const bodyBuffer = await bdResponse.arrayBuffer()`)과 Content-Length 헤더 재작성이 표시되어 있으며, 상태 표시기는 Worker가 Active임을 보여줌

핵심 줄: await bdResponse.arrayBuffer()는 전체 청크 스트림을 메모리로 읽고, 응답 객체의 명시적인 Content-Length 헤더는 Apex가 본문을 깔끔하게 파싱할 수 있게 합니다.

Worker 작동 확인

로컬 터미널에서 Worker에 대한 빠른 테스트를 실행합니다. URL을 본인 것으로 교체하고 Bright Data API 토큰을 사용하세요:

curl -i https://<your-worker>.workers.dev/request \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <your-bd-token>" \
  -d '{"zone":"mcp_unlocker","url":"https://www.salesforce.com","format":"raw","data_format":"markdown"}' \
  | head -20

응답 헤더에는 200 상태와 content-length: <some-number> 헤더가 포함되어야 합니다. transfer-encoding: chunked는 포함되어서는 안 됩니다. 이것이 프록시가 올바르게 작동하고 있다는 증거입니다. 일반적인 실패 사례: 401은 Bright Data 토큰이 잘못된 것입니다(Authorization: Bearer ... 헤더를 다시 확인하세요). Worker에서 502가 반환되면 Worker 코드가 배포되지 않은 것입니다(Deploy 단계를 다시 확인하세요). transfer-encoding: chunked 헤더가 여전히 나타나면 Worker 소스에서 arrayBuffer() + Content-Length 줄을 놓친 것입니다.

엔터프라이즈 롤아웃에서는 이 Worker가 프로덕션 등급 통합 티어로 교체됩니다. Anypoint에서 실행되는 MuleSoft, Heroku 마이크로서비스, 또는 인증, 관찰 가능성, 속도 제한이 있는 커스텀 API 게이트웨이가 그 역할을 합니다. Worker는 해당 역할의 최소한의 대체물이지만, 동일한 패턴이 프로덕션 설정에서도 작동합니다.

3부: Salesforce 자격 증명 구성

Salesforce의 External Credential 패턴은 서드파티 자격 증명을 세 개의 객체로 분리합니다. External Credential(토큰 보관), Named Credential(엔드포인트 보관), Permission Set(사용자에게 External Credential의 주체 접근 권한 부여)입니다.

External Credential 생성

Salesforce 페이지 오른쪽 상단의 기어 아이콘을 클릭하고 Setup을 선택합니다. Setup에서 왼쪽 레일 상단의 Quick Find 상자에 Named Credentials를 검색합니다. 결과를 클릭합니다. 로드된 페이지에서 External Credentials 탭을 클릭한 다음 New를 클릭합니다.

다음 필드를 입력합니다:

  • Label: Bright Data Cred
  • Name: Bright_Data_Cred(자동 입력)
  • Authentication Protocol: Custom

Save를 클릭합니다.

상세 페이지에서 Principals 섹션을 찾아 New를 클릭합니다:

  • Parameter Name: BrightDataPrincipal
  • Sequence Number: 1
  • Identity Type: Named Principal

주체 아래의 Authentication Parameters 섹션에 다음을 추가합니다:

  • Name: api_key
  • Value: Bright Data API 토큰 붙여넣기

Save를 클릭합니다.

External Credential 페이지로 돌아가 Custom Headers 섹션을 찾아 New를 클릭합니다:

  • Name: Authorization
  • Value: Bearer {!$Credential.Bright_Data_Cred.api_key}
  • Sequence Number: 1

⚠️ 병합 필드는 설정한 이름과 일치해야 합니다. 수식의 Bright_Data_Cred는 External Credential의 API 이름과 일치해야 합니다. api_key는 Principal에서 설정한 Authentication Parameter 이름과 일치해야 합니다. 이름을 변경했다면 수식을 수정하여 일치시키세요.

이것이 중요합니다. 이 커스텀 헤더에서 Allow Formulas in HTTP Header 체크박스를 선택하세요. 찾는 방법: 헤더 행을 저장한 후 행을 클릭하여 상세 보기를 엽니다. 체크박스는 상위 External Credential 페이지가 아닌 해당 상세 페이지에 있습니다. 이를 건너뛰면 Salesforce가 리터럴 문자열 Bearer {!$Credential...}을 Bright Data에 전송하고 401이 반환되며, 오류 메시지는 어떤 체크박스를 놓쳤는지 알려주지 않습니다. Save를 클릭합니다.

⚠️ 다음 섹션에서도 같은 이름의 체크박스를 만나게 됩니다. “Allow Formulas in HTTP Header”는 곳에 나타납니다. 체크박스 A는 방금 선택한 것(Custom Header의 상세 페이지에 있음)입니다. 체크박스 B는 Named Credential의 Callout Options에 있습니다. 둘 다 선택해야 합니다. 하나만 선택하면 병합 필드가 리터럴 텍스트로 전송되고 Bright Data는 401을 반환합니다.

Authentication Protocol이 Custom으로 설정되고, BrightDataPrincipal이라는 Principal과 'Bearer {!$Credential.Bright_Data_Cred.api_key}' 값이 있는 Custom Header가 표시된 Bright_Data_Cred External Credential 상세 페이지를 보여주는 Salesforce Setup

Custom Header의 병합 필드 값은 요청 시 API 토큰을 해석하는 부분입니다. Allow Formulas in HTTP Header 체크박스(이 깊이에서는 보이지 않으며 헤더의 상세 페이지에 있음)를 선택해야 합니다. 그렇지 않으면 병합 필드가 리터럴 텍스트로 전송됩니다.

Named Credential 생성

동일한 Named Credentials 섹션에서 Named Credentials 탭으로 다시 전환하고 New를 클릭합니다:

  • Label: Bright Data API
  • Name: Bright_Data_API
  • URL: Cloudflare Worker URL 붙여넣기(예: https://bd-proxy.<your-subdomain>.workers.dev)
  • Enabled for Callouts: 선택
  • External Credential: Bright Data Cred 선택

Callout Options 아래에서 다음을 설정합니다:

  • Generate Authorization Header: 선택 해제(Custom Header를 통해 직접 제공하므로)
  • Allow Formulas in HTTP Header: 선택(병합 필드가 해석되도록)
  • Allow Formulas in HTTP Body: 선택(동적 JSON 본문이 작동하도록)

Save를 클릭합니다.

Cloudflare Worker를 가리키는 URL, Bright Data Cred로 설정된 External Credential, Generate Authorization Header 선택 해제, Allow Formulas in HTTP Header 선택, Allow Formulas in HTTP Body 선택이 표시된 Salesforce Named Credential 'Bright Data API' 상세 페이지

URL은 api.brightdata.com이 아닌 Cloudflare Worker를 가리킵니다. 이것이 청크 전송 수정이 작동하는 방식입니다. 세 가지 체크박스 상태는 각각 독립적으로 중요합니다. 하나라도 잘못되면 경고 없이 콜아웃이 중단됩니다.

Permission Set 생성

Salesforce는 Permission Set이 명시적으로 접근 권한을 부여하기 전까지 시스템 관리자도 External Credential의 주체를 사용하지 못하도록 차단합니다. 이 단계를 건너뛰면 Apex가 유용한 진단 없이 INVALID_OPERATION 오류를 반환합니다.

Setup에서 Permission Sets를 검색하고 New를 클릭합니다:

  • Label: Bright Data Access
  • API Name: Bright_Data_Access
  • License: 비워두기

Save를 클릭합니다.

상세 페이지에서 External Credential Principal Access로 스크롤하여 Edit를 클릭합니다. Available 목록에서 Enabled 목록으로 Bright_Data_Cred - BrightDataPrincipal을 이동합니다. Save를 클릭합니다.

상세 페이지로 돌아가 상단의 Manage Assignments를 클릭한 다음 Add Assignments를 클릭하고 본인 사용자를 선택하여 할당을 완료합니다.

Bright_Data_Cred - BrightDataPrincipal이 부여된 External Credential Principal Access 섹션과 페이지 상단에 Manage Assignments 버튼이 표시된 Salesforce Permission Set 'Bright Data Access'

Permission Set은 어떤 사용자가 Bright Data를 호출하는 코드를 실행할 수 있는지 범위를 지정하는 게이팅 메커니즘입니다. 엔터프라이즈 조직에서는 개별 관리자가 아닌 서비스 사용자나 특정 프로필에 Permission Set Group을 통해 할당됩니다.

계속 진행하기 전에 배선 확인

기어 아이콘(오른쪽 상단) → Developer Console을 클릭합니다. 새 브라우저 창에서 열립니다. 해당 창에 포커스가 맞춰지면 Debug → Open Execute Anonymous Window를 통해 Anonymous Apex를 엽니다. 아래 코드를 붙여넣고 Execute를 클릭합니다:

HttpRequest req = new HttpRequest();
req.setEndpoint('callout:Bright_Data_API/request');
req.setMethod('POST');
req.setHeader('Content-Type', 'application/json');
req.setBody('{"zone":"mcp_unlocker","url":"https://geo.brdtest.com/welcome.txt?product=unlocker&method=api","format":"raw"}');
req.setTimeout(60000);
HttpResponse res = new Http().send(req);
System.debug('STATUS: ' + res.getStatusCode());
System.debug('BODY: ' + res.getBody().left(500));

Execute를 클릭하면 Developer Console 하단 패널에 새 로그 행이 나타납니다. 해당 행을 더블클릭하여 로그 뷰어를 열고 하단의 Debug Only를 선택하거나(또는 필터 상자에 USER_DEBUG를 입력) STATUS와 BODY 값을 출력하는 두 줄을 확인합니다. STATUS: 200과 Bright Data 환영 텍스트가 포함된 본문이 표시되어야 합니다. 401이 표시되면 Custom Header의 “Allow Formulas in HTTP Header” 체크박스(헤더 상세 페이지의 것 Named Credential의 Callout Options의 것)를 다시 확인하세요. INVALID_OPERATION이 표시되면 Permission Set 할당을 다시 확인하세요.

4부: Apex 레이어 작성

Apex는 클래스당 하나의 @InvocableMethod만 Agentforce 호출 가능 액션으로 등록합니다. 그래서 통합에 하나가 아닌 세 개의 클래스를 사용합니다. HTTP 배관을 위한 공유 서비스와 Agent Action당 하나의 클래스입니다.

각 블록을 그대로 붙여넣으세요. 변경할 수 있는 주요 줄은 Bright Data 존 이름이 다른 경우(1부) BrightDataService.clsprivate static final String UNLOCKER_ZONE = 'mcp_unlocker';입니다.

Setup에서 Quick Find에 Apex Classes를 검색하고 결과를 클릭합니다. New를 클릭합니다. 편집기가 public class YourClassName {}와 같은 플레이스홀더 클래스와 함께 열립니다. 코드 영역(오른쪽 Version Settings 패널이 아닌 큰 텍스트 상자)을 클릭하고 플레이스홀더 텍스트를 모두 선택한 다음 Delete를 누르고 아래 소스를 붙여넣습니다. 클래스 이름은 소스에서 가져오므로 다른 필드를 입력할 필요가 없습니다. Save를 클릭합니다.

BrightDataNewsActionBrightDataFetchAction이 모두 BrightDataService를 참조하므로 다음 순서로 세 클래스를 생성합니다. 서비스를 먼저 저장해야 합니다:

BrightDataService.cls: 공유 HTTP 및 파싱 레이어

이 클래스는 HTTP 배관과 두 Agent Action이 모두 호출하는 두 헬퍼 메서드(searchNewsfetchUrlAsMarkdown)를 보유합니다. 여기에는 @InvocableMethod가 없습니다. 해당 어노테이션은 아래의 액션 래퍼 클래스에 있습니다. 클래스는 다음과 같습니다:

public with sharing class BrightDataService {

    private static final String BD_ENDPOINT      = 'callout:Bright_Data_API/request';
    private static final String UNLOCKER_ZONE    = 'mcp_unlocker';
    private static final Integer CALLOUT_TIMEOUT = 60000;
    private static final Integer MAX_RESPONSE_CHARS = 50000;

    /**
     * Fetches the Google News results page for `companyName` (past month) as
     * clean Markdown via Bright Data Web Unlocker. The LLM downstream is
     * responsible for extracting individual articles, sources, and dates.
     */
    public static String searchNews(String companyName) {
        String googleNewsUrl =
            'https://www.google.com/search?q='
            + EncodingUtil.urlEncode(companyName, 'UTF-8')
            + '&tbm=nws&tbs=qdr:m';

        Map<String, Object> body = new Map<String, Object>{
            'zone'        => UNLOCKER_ZONE,
            'url'         => googleNewsUrl,
            'format'      => 'raw',
            'data_format' => 'markdown'
        };

        HttpResponse res = sendRequest(JSON.serialize(body));

        if (res.getStatusCode() != 200) {
            return 'Bright Data returned status '
                + res.getStatusCode() + ': ' + res.getBody().left(300);
        }

        String content = res.getBody();
        if (String.isBlank(content)) {
            return 'No content returned for "' + companyName
                + '". The page may have been empty or blocked.';
        }
        if (content.length() > MAX_RESPONSE_CHARS) {
            content = content.left(MAX_RESPONSE_CHARS)
                + '\n\n[Content truncated at ' + MAX_RESPONSE_CHARS + ' characters]';
        }

        return 'Google News results for "' + companyName
            + '" (past month). Extract article titles, sources, '
            + 'publication dates, and URLs from the Markdown below:\n\n'
            + content;
    }

    /**
     * Fetches any URL via Bright Data Web Unlocker and returns the page as
     * clean Markdown.
     */
    public static String fetchUrlAsMarkdown(String url) {
        Map<String, Object> body = new Map<String, Object>{
            'zone'        => UNLOCKER_ZONE,
            'url'         => url,
            'format'      => 'raw',
            'data_format' => 'markdown'
        };

        HttpResponse res = sendRequest(JSON.serialize(body));

        if (res.getStatusCode() != 200) {
            return 'Web Unlocker returned status '
                + res.getStatusCode() + ': ' + res.getBody().left(300);
        }

        String content = res.getBody();
        if (content.length() > MAX_RESPONSE_CHARS) {
            content = content.left(MAX_RESPONSE_CHARS)
                + '\n\n[Content truncated at ' + MAX_RESPONSE_CHARS + ' characters]';
        }
        return content;
    }

    private static HttpResponse sendRequest(String jsonBody) {
        HttpRequest req = new HttpRequest();
        req.setEndpoint(BD_ENDPOINT);
        req.setMethod('POST');
        req.setHeader('Content-Type', 'application/json');
        req.setBody(jsonBody);
        req.setTimeout(CALLOUT_TIMEOUT);
        return new Http().send(req);
    }
}

이 클래스에는 의도적으로 @InvocableMethod가 없습니다. 두 액션 클래스가 사용하는 공유 HTTP 레이어입니다.

BrightDataNewsAction.cls: 뉴스 검색 액션

에이전트가 뉴스를 검색하기로 결정하면 Agentforce가 이 얇은 호출 가능 래퍼를 호출합니다. 입력을 검증하고, HTTP 작업을 BrightDataService.searchNews()에 위임하며, Agentforce가 기대하는 Response 형태로 결과를 반환합니다. 클래스는 다음과 같습니다:

public with sharing class BrightDataNewsAction {

    public class Request {
        @InvocableVariable(
            required=true
            label='Company Name'
            description='The name of the company to search news about. E.g. "Salesforce" or "Acme Corp".')
        public String companyName;
    }

    public class Response {
        @InvocableVariable(
            label='News Results'
            description='Formatted summary of recent news with titles, sources, dates, URLs, and snippets.')
        public String newsResults;
    }

    @InvocableMethod(
        label='Search Recent Company News (Bright Data)'
        description='Searches Google News via Bright Data for recent (past month) articles about a specific named company. Use this whenever the user asks about recent news, announcements, press releases, funding rounds, acquisitions, leadership changes, or current events for a named company.'
        callout=true)
    public static List<Response> searchCompanyNews(List<Request> requests) {
        List<Response> responses = new List<Response>();
        for (Request req : requests) {
            Response resp = new Response();
            try {
                resp.newsResults = String.isBlank(req.companyName)
                    ? 'Error: A company name is required.'
                    : BrightDataService.searchNews(req.companyName);
            } catch (Exception e) {
                resp.newsResults = 'Error fetching news for ' + req.companyName + ': ' + e.getMessage();
            }
            responses.add(resp);
        }
        return responses;
    }
}

Agentforce의 추론 엔진은 @InvocableMethod 어노테이션의 description 필드를 읽어 이 액션을 언제 호출할지 결정합니다.

Agentforce Builder에서 Account Web Intelligence 서브에이전트의 Actions Available For Reasoning 섹션에 두 가지 Bright Data 액션이 연결된 모습: Search Recent Company News (Bright Data)는 `With input: Company Name = Agent Populated`, Fetch Web Page as Markdown (Bright Data)는 `With input: URL to Fetch = Agent Populated`

“Agent Populated”는 런타임에 LLM 추론으로 입력값이 채워질 때 Salesforce가 사용하는 레이블입니다. 이것이 채팅 기반 에이전트에 필요한 통합 상태입니다.

7단계: 에이전트 테스트

Agent Builder에서 Preview 탭을 클릭합니다. Reset Simulator 버튼이 포함된 노란색 배너와 함께 채팅 인터페이스가 열립니다. 배너가 나타나면 클릭하세요. 시뮬레이터의 대화 메모리는 세션별로 유지되며, 테스트 간에 초기화하는 것이 독립적인 추적을 얻는 가장 간단한 방법이므로 초기화가 중요합니다. 아래 네 가지 테스트는 라우팅, 단일 액션 호출, 병렬 호출, 그리고 정직한 실패 모드를 검증합니다.

Preview 탭이 회색으로 비활성화되어 있다면, 캔버스 상단에서 Activate 토글을 찾아 켜세요. 여기서 활성화하면 Preview/Simulator에서만 에이전트가 활성화됩니다. 최종 사용자는 Setup → Agentforce Studio → Connections를 통해 별도로 할당해야 접근할 수 있으며, 이는 이 빌드의 범위를 벗어납니다.

테스트 1: 뉴스 검색만

뉴스 액션이 처음부터 끝까지 실행되는지 확인하려면 다음을 입력하세요:

What's new at Salesforce in the past month?

에이전트는 Account Web Intelligence로 라우팅되고, “Searching recent news…” 로딩 상태를 표시한 후, 출처 이름이 인라인으로 인용된 테마별 뉴스 요약을 반환해야 합니다. 캔버스 하단의 Trace 탭을 열어 전체 추론 체인을 확인하세요. 미리보기 오른쪽의 Interaction Summary 패널에는 동일한 체인이 간략하게 표시됩니다: Router가 선택한 서브에이전트, 호출된 액션, 에이전트의 추론 방식이 나타납니다.

Agentforce Preview에서 'What's new at Salesforce in the past month?' 프롬프트에 대해 'AI & Product Expansion'과 'Strategic Partnerships'로 분류된 테마별 뉴스 요약으로 답변하며 출처(Forbes, UC Today, PR Newswire)가 인용된 모습. 오른쪽 Interaction Summary에는 Input → Transition to Subagent: Account Web Intelligence → Reasoning → Action: Search Recent Company News Bright Data → Reasoning 순서가 표시됨. 응답의 출처 URL은 URL_Redacted로 표시됨

이것은 깔끔한 단일 액션 실행입니다. Router가 올바르게 라우팅했고, 에이전트는 자연어 프롬프트에서 companyName="Salesforce"를 추출하여 액션을 호출하고 테마별 요약을 합성했습니다. URL_Redacted 플레이스홀더는 Salesforce의 URL 신뢰 정책이 작동한 결과입니다(아래 “엔터프라이즈 거버넌스” 섹션에서 설명).

라우팅은 LLM 기반이므로, 드물게 Account Web Intelligence 서브에이전트의 설명이 템플릿의 유사한 서브에이전트(예: General FAQ)에 밀릴 수 있습니다. 첫 번째 테스트가 잘못된 서브에이전트로 라우팅된다면, 설명에 더 구체적인 키워드(“company news”, “press release”, “recent funding”, “fetch URL”)를 추가하고 저장한 후 Reset Simulator를 클릭하여 재시도하세요. 라우터는 새 세션마다 재분류합니다.

테스트 2: URL 가져오기만

직접 URL 프롬프트가 fetch 액션으로 라우팅되는지 확인하려면 Reset Simulator를 클릭하고 다음을 입력하세요:

Read https://www.snowflake.com and tell me what they do

에이전트는 뉴스 액션이 아닌 Fetch 액션을 호출해야 합니다. 다른 프롬프트는 다른 도구로 라우팅됩니다.

Agentforce Preview에서 'Read https://www.snowflake.com and tell me what they do' 프롬프트에 대해 Snowflake를 AI Data Cloud로 소개하는 두 문장 개요로 답변하고, 검색된 콘텐츠가 제한적이라는 정직한 단서를 덧붙인 모습. 오른쪽 Interaction Summary에는 Fetch Web Page as Markdown Bright Data 액션 하나만 표시됨. 응답의 URL은 URL_Redacted로 표시됨

동일한 에이전트이지만, 다른 프롬프트가 다른 도구로 라우팅됩니다. Agent Router가 프롬프트를 URL 가져오기(뉴스 검색이 아님)로 분류하여 fetch 액션만 호출했습니다. 이는 에이전트가 고정된 파이프라인을 실행하는 것이 아니라 추론하고 있다는 증거입니다.

테스트 3: 두 액션 병렬 실행

테스트 3은 두 액션이 모두 200을 반환하는지 여부와 관계없이 두 액션이 모두 호출되면 성공입니다. 둘 다 성공하면 깔끔한 통합 브리핑이 표시됩니다. 하나가 실패하면(아래 스크린샷처럼), 에이전트는 성공한 것을 사용하여 브리핑을 생성해야 합니다. 이것이 규칙 5를 보여줍니다. Reset Simulator 후 다음을 입력하세요:

Give me a briefing on Anthropic, recent news plus what their homepage says about their product

에이전트는 두 액션을 모두 호출한 후 통합 브리핑을 합성해야 합니다. Trace에는 동일한 추론 체인에서 두 개의 별도 Action 행이 표시됩니다.

Agentforce Preview 상단에 SAP Partnership, Claude for Small Business, Claude Design, Financial Services 출시 등 다섯 가지 테마별 뉴스 항목이 포함된 Anthropic 브리핑이 표시되고 출처(SAP News Center, Anthropic, CNBC)가 인용된 모습. 오른쪽 Interaction Summary에는 Search Recent Company News Bright Data와 Fetch Web Page as Markdown Bright Data 두 액션이 모두 호출된 것이 표시됨. 응답의 출처 URL은 URL_Redacted로 표시됨

에이전트가 단일 사용자 프롬프트에서 두 액션을 모두 호출했습니다. Anthropic 뉴스 섹션은 Bright Data의 Web Unlocker가 Google News를 호출한 결과입니다.

동일한 응답이 아래에 홈페이지 섹션으로 이어지며, 이번 실행에서는 자세히 살펴볼 가치가 있는 부분적 실패가 발생했습니다:

Agentforce Preview 하단에 동일한 응답의 'What Their Homepage Says' 섹션이 표시됨. 에이전트가 홈페이지 콘텐츠 검색 중 기술적 문제가 발생했음을 정직하게 보고하고, 방금 검색한 뉴스를 기반으로 Anthropic의 포지셔닝을 설명하는 것으로 대체함. Interaction Summary에는 'Fetch_Web_Page_as_Markdown_Bright_Data for https://www.anthropic.com (which returned a 520 error page)'가 명시되고 에이전트가 '출처 URL이 삭제된 요약 브리핑을 생성했다'고 기록됨

홈페이지 가져오기가 520을 반환했습니다. 이는 특정 시도에서 대상 사이트를 검색할 수 없을 때 Web Unlocker가 사용하는 상태 코드입니다. 에이전트는 홈페이지 콘텐츠를 만들어내지 않았으며, 실패를 인정하고 방금 받은 뉴스 데이터를 사용하여 회사가 하는 일을 설명한 후 재시도를 제안했습니다.

부분적 도구 실패 시 우아한 성능 저하는 서브에이전트 지침의 규칙 5가 생성하도록 설계된 프로덕션 동작입니다. 공개 웹은 적대적이기 때문에 이것이 중요합니다: 대상 사이트는 방어를 변경하고, CDN은 가끔 실패하며, 라이브 URL을 호출하는 에이전트는 간헐적인 non-200 응답을 허용해야 합니다. 에이전트가 “홈페이지 가져오기에 실패했습니다. 뉴스에서 얻은 정보를 알려드립니다”라고 말할 때, 그것이 프로덕션 수준의 동작입니다.

테스트 4: 환각 없음 확인

실제 답변이 없을 때 에이전트가 정직하게 실패하는지 확인하려면 Reset Simulator를 클릭하고 다음을 입력하세요:

What's the latest news about Triposat Industries?

이것은 가상의 회사 이름이므로 찾을 수 있는 실제 뉴스가 없습니다. Bright Data는 520, 빈 결과, 또는 관련 없는 스니펫을 반환할 수 있으며, 이 중 어느 것도 실제 답변이 아닙니다. 에이전트는 무엇이 반환되든 뉴스를 만들어내서는 안 됩니다. 여기서 성공 기준은 **상태 코드가 아닌 동작**입니다: “최근 뉴스를 찾을 수 없습니다”, “결과를 검색할 수 없습니다” 또는 정직한 공개가 포함된 모든 응답이 허용됩니다. 실패는 에이전트가 존재하지 않는 Forbes 기사나 펀딩 라운드를 만들어내는 것입니다.

Agentforce Preview에서 'What's the latest news about Triposat Industries?' 프롬프트에 대해 정직한 공개로 답변하는 모습: 'I tried to retrieve the latest news about Triposat Industries, but there was an issue accessing the news source and I could not get any recent updates at this time.' 에이전트는 재시도하거나 특정 측면으로 전환할 것을 제안함. Interaction Summary에서 검색 액션이 호출되었음을 확인하고, 자연어 요약에 '공급자로부터 오류와 유사한 결과(부분 HTML이 포함된 상태 520)를 받았다'고 명시됨

에이전트는 뉴스 항목을 조작하거나 출처를 만들어내지 않았습니다. 액션을 호출하고, 불량한 결과를 받아 이를 공개했습니다. 서브에이전트 지침의 규칙 5가 의도한 대로 작동했습니다.

Agentforce의 URL 삭제

위 스크린샷의 모든 데모 응답에서 출처 URL이 URL_Redacted로 표시됩니다. 이것은 Salesforce Agentforce의 내장 URL 신뢰 정책입니다.

기본적으로 Agentforce는 에이전트 응답이 최종 사용자에게 전달되기 전에 임의의 외부 URL을 제거합니다. 그러나 내부적으로 에이전트는 도구가 반환할 때 실제 URL을 읽고 추론에 사용합니다. 도메인이 명시적 허용 목록에 없는 한 채팅 출력에 포함할 수 없을 뿐입니다.

구성 가능합니다: Setup에서 Trusted URLs를 검색하고 허용할 도메인을 추가하세요. 영업 브리핑 에이전트의 경우, 현실적인 허용 목록에는 Google 도메인, 회사 자체 마케팅 도메인, 그리고 선별된 뉴스 출처(Forbes, Reuters, Bloomberg, TechCrunch)가 포함됩니다.

기본 삭제를 활성화된 상태로 유지하세요. 이것이 데모를 더 강력하게 만듭니다: 에이전트는 출처를 이름으로 인용하고(Forbes, TechAfrica News, SAP News Center), 삭제는 Salesforce의 거버넌스 레이어를 가시화합니다. 원시 URL이 있는 데모는 소비자용 챗봇과 구별할 수 없지만, 삭제된 버전은 거버넌스 레이어를 가시화합니다.

비용

Bright Data는 종량제 및 단계별 요금제에서 성공적인 Web Unlocker 응답에 대해서만 요금을 청구합니다. 실패한 응답은 청구되지 않습니다. 정가 기준으로, 두 액션을 모두 호출하는 브리핑은 센트의 일부 수준이며, 단계별 요금제는 요청당 비용을 더욱 낮춥니다.

자체 지출을 예측하려면 다음 공식을 사용하세요:

requests/month = reps × briefings_per_rep_per_day × workdays × actions_per_briefing

100명의 영업 담당자가 22 근무일 동안 각각 5개의 브리핑을 실행하고 브리핑당 2개의 액션을 사용하면 월 22,000건의 요청이 됩니다. 이를 Bright Data의 현재 요청당 요금(가격 페이지 참조)으로 곱하면 월 비용이 나옵니다.

Cloudflare 측에서는 Worker가 일반적인 영업팀 규모에서 무료 티어 내에 유지됩니다. 단일 팀 이상으로 확장하기 전에 Workers 가격을 참조하세요.

Salesforce 측에서는 비용이 조직의 기존 Einstein 생성형 AI 크레딧 허용량에서 차감됩니다. 이는 모든 Agentforce 액션이 사용하는 동일한 계량 풀입니다. Setup → Einstein Generative AI → Usage에서 조직의 현재 허용량과 사용량을 확인하세요.

다음 단계

위의 빌드는 계정 인텔리전스 레이어의 최소 단위입니다. 프로덕션으로 전환하기 전에 해야 할 세 가지가 있습니다:

  • Cloudflare Worker를 커스텀 도메인(자체 DNS의 서브도메인) 뒤로 이동한 후 조직의 API 게이트웨이를 통해 라우팅하세요. Worker는 이 프록시 역할에 적합하며, 커스텀 도메인과 게이트웨이가 운영상 소유권을 만들어줍니다.
  • Bright Data 존을 이그레스 IP 범위로 잠그세요. Bright Data 대시보드에서 Web Unlocker 존을 편집하고 Cloudflare Worker의 아웃바운드 IP(Cloudflare가 이를 공개합니다)를 존의 허용 목록에 추가하세요. 이렇게 하면 API 토큰이 통합 외부에서 사용되는 것을 방지합니다.
  • HttpCalloutMock을 사용하여 BrightDataService에 대한 Apex 테스트 클래스를 추가하세요. 세 가지 테스트 메서드(성공 경로, 빈 본문, non-200)가 현실적인 실패 모드를 커버하고 Salesforce의 75% 커버리지 요구 사항을 충족합니다. Salesforce HttpCalloutMock 문서에서 패턴을 확인하세요.

특정 대상 사이트에서 Web Unlocker의 한계를 느끼면 Bright Data의 사전 구축된 스크레이퍼 중 하나로 교체하세요. 동일한 Cloudflare Worker 프록시가 이것도 처리합니다. 동일한 /request 엔드포인트에 다른 zonedataset_id 매개변수를 사용합니다. 예를 들어, 전용 LinkedIn Company Profile, LinkedIn Jobs, Crunchbase 스크레이퍼는 Markdown 대신 구조화된 JSON을 반환하므로 에이전트가 LLM 추출 단계를 건너뛰고 Salesforce 커스텀 필드에 직접 쓸 수 있습니다. 더 넓게는 Bright Data의 Web Scraper API가 수백 개의 사이트에 대한 사전 구축된 스크레이퍼를 제공합니다.

위의 빌드를 스캐폴딩으로 취급하세요. 자격 증명 레이어, 프록시, 서브에이전트 구조, 액션 배선: 동일한 /request 엔드포인트에서 다른 Bright Data 제품으로 교체할 때도 기반은 동일하게 유지됩니다. 영업 담당자가 실제로 필요로 하는 계정 인텔리전스 유형을 선택하고 서브에이전트 지침의 프롬프트를 변경하세요. 에이전트는 그대로 유지되고, 답변하는 질문만 바뀝니다.

자주 묻는 질문

Apex callout이 빈 본문을 반환하는 이유는 무엇인가요?

Apex의 HTTP 클라이언트는 Content-Length 헤더 없이 청크 전송 인코딩을 사용하는 HTTP/1.1 응답을 안정적으로 파싱하지 못합니다. Bright Data와 많은 최신 API는 몇 킬로바이트 이상의 응답을 청크로 전송합니다. 해결책은 명시적인 Content-Length로 응답을 다시 제공하는 버퍼링 프록시를 통해 호출을 라우팅하는 것입니다.

이 빌드에서 Bright Data의 SERP API를 사용할 수 있나요?

네. Cloudflare Worker 프록시는 SERP API, Web Scraper API, 데이터셋 트리거를 포함하여 api.brightdata.com에 호스팅된 모든 Bright Data API 엔드포인트에서 작동합니다. Apex 서비스 클래스의 zone 값을 SERP 존 이름으로 변경하고 URL 매개변수를 brd_json=1이 포함된 Google 검색 URL로 변경하세요.

MCP 대신 Apex InvocableMethods를 사용하는 이유는 무엇인가요?

이 빌드는 각 액션이 자체 Permission Set 거버넌스를 가진 감사 가능한 Agent Action이 되기 때문에 Apex InvocableMethods를 통해 Bright Data를 노출합니다. 조직에 Salesforce 호스팅 MCP(Model Context Protocol) 서버가 활성화되어 있다면(Spring ’26 기준 베타), Bright Data 자체 MCP 서버를 통해 Bright Data를 노출하는 방법도 있습니다. 두 경로 모두 작동합니다. 여기서 보여주는 InvocableMethod 경로는 Salesforce 네이티브 거버넌스 훅을 제공하며, MCP 경로는 Bright Data가 서버를 운영하기 때문에 더 가볍습니다.

Agentforce 액션은 사용자 입력을 어떻게 읽나요?

Agentforce 액션은 각 입력에 설정된 is_user_input: True라는 YAML 플래그를 통해 사용자 입력을 읽습니다. 이는 Script 모드에서 설정됩니다. Canvas UI는 이 플래그를 숨기고 입력을 기본적으로 정적 변수로 설정하므로, Agent Builder에서 Script 모드로 전환하고 YAML을 직접 편집하여 값을 변경해야 합니다.

Agentforce가 출처 URL을 숨기는 이유는 무엇인가요?

외부 URL은 도메인이 Setup → Trusted URLs에 추가되지 않는 한 에이전트 응답에서 제거됩니다. 이것은 내장 거버넌스 레이어입니다. 그럼에도 에이전트는 내부적으로 실제 URL을 읽고 추론에 사용합니다. 특정 URL을 사용자에게 전달하려면 출처 도메인(Forbes, Reuters, 자체 사이트)을 허용 목록에 추가하세요.

Bright Data 520 오류란 무엇인가요?

520은 특정 시도에서 대상 사이트를 검색할 수 없을 때 Web Unlocker가 반환하는 상태 코드로, 일반적으로 사이트의 방어가 요청을 차단했기 때문입니다. 서브에이전트 지침의 규칙 5는 도구가 실패할 때 콘텐츠를 조작하는 것을 금지하므로, 에이전트는 실패를 정직하게 보고하고 재시도를 제안합니다.

이 패턴이 적용되는 다른 사용 사례는 무엇인가요?

이탈 위험, 경쟁 인텔리전스, 갱신 위험 브리핑 모두 이 패턴에 적합합니다. 두 가지 Apex 액션(뉴스 검색, URL 가져오기)이 대부분의 계정 인텔리전스 유형을 커버하기 때문입니다. 이탈 위험의 경우 뉴스 검색을 사용하여 인력 감축 및 리더십 변화를 추적하세요. 경쟁 인텔리전스의 경우 경쟁사의 가격 페이지를 가져오세요. 프롬프트만 변경하면 됩니다.