멀티 에이전트 시스템 빌드

1. 소개

개요

이 실습에서는 간단한 챗봇을 넘어 분산 멀티 에이전트 시스템을 빌드합니다.

단일 LLM이 질문에 답변할 수 있지만 실제로는 전문적인 역할이 필요한 경우가 많습니다. 백엔드 엔지니어에게 UI를 설계해 달라고 요청하지 않고 디자이너에게 데이터베이스 쿼리를 최적화해 달라고 요청하지 않습니다. 마찬가지로, 하나의 작업에 집중하고 서로 협력하여 복잡한 문제를 해결하는 전문 AI 에이전트를 만들 수 있습니다.

다음으로 구성된 학습 프로그램 생성 시스템을 빌드합니다.

  1. 리서처 에이전트: google_search를 사용하여 최신 정보를 찾습니다.
  2. 심사 에이전트: 품질과 완전성을 위해 연구를 비판합니다.
  3. 콘텐츠 빌더 에이전트: 조사 결과를 바탕으로 구조화된 과정을 만듭니다.
  4. 오케스트레이터 에이전트: 이러한 전문가 간의 워크플로와 커뮤니케이션을 관리합니다.

기본 요건

  • 기본적인 Python 지식
  • Google Cloud 콘솔에 대한 기본 지식

실습할 내용

  • 웹을 검색할 수 있는 도구 사용 에이전트 (researcher)를 정의합니다.
  • judge에 Pydantic을 사용하여 구조화된 출력을 구현합니다.
  • Agent2Agent (A2A) 프로토콜을 사용하여 원격 에이전트에 연결합니다.
  • 연구원과 심사위원 간의 피드백 루프를 만들기 위해 LoopAgent를 구성합니다.
  • ADK를 사용하여 로컬에서 분산 시스템을 실행합니다.
  • 멀티 에이전트 시스템을 Google Cloud Run에 배포합니다.

아키텍처 및 오케스트레이션 원칙

코드를 작성하기 전에 이러한 에이전트가 어떻게 함께 작동하는지 알아보겠습니다. 강의 제작 파이프라인을 구축하고 있습니다.

시스템 설계

아키텍처 다이어그램

에이전트와 조정

표준 에이전트 (예: 연구원)는 작업을 수행합니다. 오케스트레이터 에이전트 (예: LoopAgent 또는 SequentialAgent)는 다른 에이전트를 관리합니다. 자체 도구가 없으며 '도구'는 위임입니다.

  1. LoopAgent: 코드에서 while 루프와 같이 작동합니다. 조건이 충족되거나 최대 반복 횟수에 도달할 때까지 에이전트 시퀀스를 반복 실행합니다. 이 데이터는 리서치 루프에 사용됩니다.
    • 연구원이 정보를 찾습니다.
    • 심사위원이 비판합니다.
    • Judge가 'Fail'이라고 말하면 EscalationChecker가 루프를 계속 진행하도록 합니다.
    • 심사자가 '통과'라고 말하면 EscalationChecker가 루프를 중단합니다.
  2. SequentialAgent: 표준 스크립트 실행과 유사하게 작동합니다. 에이전트를 하나씩 차례로 실행합니다. 상위 수준 파이프라인에 사용됩니다.
    • 먼저 Research Loop를 실행합니다 (양질의 데이터로 완료될 때까지).
    • 그런 다음 콘텐츠 빌더를 실행하여 과정을 작성합니다.

이러한 요소를 결합하여 최종 출력을 생성하기 전에 자체적으로 수정할 수 있는 강력한 시스템을 만듭니다.

2. 설정

환경 설정

Cloud Shell 열기: 새 탭을 열고 shell.cloud.google.com을 입력합니다.

시작 코드 가져오기

  1. 시작 저장소를 홈 디렉터리에 클론합니다.
    cd ~
    git clone https://github.com/amitkmaraj/prai-roadshow-lab-1-starter.git
    cd prai-roadshow-lab-1-starter
    
  2. 온램프 크레딧을 결제와 연결하려면 init 스크립트를 실행합니다.
    chmod +x ./init.sh
    ./init.sh
    
  3. 편집기에서 이 폴더를 엽니다.

API 사용 설정

이제 새 프로젝트가 있으므로 다음 명령어를 실행하여 필요한 Google Cloud 서비스를 사용 설정합니다.

gcloud services enable \
    run.googleapis.com \
    artifactregistry.googleapis.com \
    cloudbuild.googleapis.com \
    aiplatform.googleapis.com \
    compute.googleapis.com

몇 초 정도 걸릴 수 있습니다.

종속 항목 설치

빠른 종속 항목 관리를 위해 uv를 사용합니다.

  1. 프로젝트 종속 항목을 설치합니다.
    # Ensure you have uv installed: pip install uv
    uv sync
    
  2. Google Cloud 프로젝트 ID를 설정합니다.
    • 도움말: 프로젝트 ID는 Cloud Console 대시보드에서 또는 gcloud config get-value project를 실행하여 찾을 수 있습니다.
    export GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project)
    
  3. 나머지 환경 변수를 설정합니다.
    export GOOGLE_CLOUD_LOCATION=us-central1
    export GOOGLE_GENAI_USE_VERTEXAI=true
    
    경고: 환경 변수는 새 터미널 세션 간에 유지되지 않습니다. 새 터미널 탭을 여는 경우 이러한 내보내기 명령어를 다시 실행해야 합니다.

3. 🕵️ 리서치 에이전트

연구원 에이전트

연구원은 전문가입니다. 이 도구의 유일한 역할은 정보를 찾는 것입니다. 이렇게 하려면 Google 검색 도구에 액세스할 수 있어야 합니다.

연구자를 분리하는 이유는 무엇인가요?

심층 분석: 하나의 에이전트가 모든 작업을 처리하면 안 되나요?

작고 집중된 에이전트는 평가하고 디버그하기가 더 쉽습니다. 리서치가 좋지 않으면 연구원의 프롬프트를 반복합니다. 과정 형식이 잘못된 경우 콘텐츠 빌더에서 반복합니다. 모든 작업을 수행하는 모놀리식 프롬프트에서는 한 가지를 수정하면 다른 문제가 발생하는 경우가 많습니다.

  1. Cloud Shell에서 작업하는 경우 다음 명령어를 실행하여 Cloud Shell 편집기를 엽니다.
    cloudshell workspace .
    
    로컬 환경에서 작업하는 경우 즐겨 사용하는 IDE를 엽니다.
  2. agents/researcher/agent.py를 엽니다.
  3. 할 일이 있는 스켈레톤이 표시됩니다.
  4. 다음 코드를 추가하여 researcher 에이전트를 정의합니다.
    # ... existing imports ...
    
    # Define the Researcher Agent
    researcher = Agent(
        name="researcher",
        model=MODEL,
        description="Gathers information on a topic using Google Search.",
        instruction="""
        You are an expert researcher. Your goal is to find comprehensive and accurate information on the user's topic.
        Use the `google_search` tool to find relevant information.
        Summarize your findings clearly.
        If you receive feedback that your research is insufficient, use the feedback to refine your next search.
        """,
        tools=[google_search],
    )
    
    root_agent = researcher
    

주요 개념: 도구 사용

tools=[google_search]를 전달합니다. ADK는 LLM에 이 도구를 설명하는 복잡성을 처리합니다. 모델이 정보가 필요하다고 판단하면 구조화된 도구 호출을 생성하고, ADK는 Python 함수 google_search를 실행하고, 결과를 모델에 다시 제공합니다.

4. ⚖️ 심판 에이전트

심사 에이전트

연구자는 열심히 일하지만 LLM은 게으를 수 있습니다. 작업을 검토할 심사위원이 필요합니다. 심사위원이 연구를 수락하고 구조화된 통과/실패 평가를 반환합니다.

구조화된 출력

자세히 알아보기: 워크플로를 자동화하려면 예측 가능한 출력이 필요합니다. 장황한 텍스트 리뷰는 프로그래매틱 방식으로 파싱하기 어렵습니다. JSON 스키마 (Pydantic 사용)를 적용하면 Judge가 코드에서 안정적으로 작동할 수 있는 불리언 pass 또는 fail를 반환합니다.

  1. agents/judge/agent.py를 엽니다.
  2. JudgeFeedback 스키마와 judge 에이전트를 정의합니다.
    # 1. Define the Schema
    class JudgeFeedback(BaseModel):
        """Structured feedback from the Judge agent."""
        status: Literal["pass", "fail"] = Field(
            description="Whether the research is sufficient ('pass') or needs more work ('fail')."
        )
        feedback: str = Field(
            description="Detailed feedback on what is missing. If 'pass', a brief confirmation."
        )
    
    # 2. Define the Agent
    judge = Agent(
        name="judge",
        model=MODEL,
        description="Evaluates research findings for completeness and accuracy.",
        instruction="""
        You are a strict editor.
        Evaluate the 'research_findings' against the user's original request.
        If the findings are missing key info, return status='fail'.
        If they are comprehensive, return status='pass'.
        """,
        output_schema=JudgeFeedback,
        # Disallow delegation because it should only output the schema
        disallow_transfer_to_parent=True,
        disallow_transfer_to_peers=True,
    )
    
    root_agent = judge
    

핵심 개념: 에이전트 동작 제한

disallow_transfer_to_parent=Truedisallow_transfer_to_peers=True를 설정합니다. 이렇게 하면 Judge가 구조화된 JudgeFeedback만 반환하도록 강제됩니다. 사용자와 '채팅'하거나 다른 상담사에게 위임할 수 없습니다. 따라서 로직 흐름에서 결정적 구성요소가 됩니다.

5. 🧪 격리 테스트

연결하기 전에 각 에이전트가 작동하는지 확인할 수 있습니다. ADK를 사용하면 에이전트를 개별적으로 실행할 수 있습니다.

핵심 개념: 대화형 런타임

adk run는 내가 '사용자'인 경량 환경을 실행합니다. 이를 통해 에이전트의 명령과 도구 사용을 격리된 상태로 테스트할 수 있습니다. 상담사가 여기서 실패하면 (예: Google 검색을 사용할 수 없음) 오케스트레이션에서 확실히 실패합니다.

  1. Researcher를 대화형으로 실행합니다. 특정 에이전트 디렉터리를 가리키는 것을 확인하세요.
    # This runs the researcher agent in interactive mode
    uv run adk run agents/researcher
    
  2. 채팅 프롬프트에 다음을 입력합니다.
    Find the population of Tokyo in 2020
    
    Google 검색 도구를 사용하여 답변을 반환해야 합니다.참고: 프로젝트, 위치, Vertex 사용이 설정되지 않았음을 나타내는 오류가 표시되면 프로젝트 ID가 설정되어 있는지 확인하고 다음을 실행하세요.
    export GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project)
    export GOOGLE_CLOUD_LOCATION=us-central1
    export GOOGLE_GENAI_USE_VERTEXAI=true
    
  3. 채팅을 종료합니다 (Ctrl+C).
  4. Judge를 대화형으로 실행합니다.
    uv run adk run agents/judge
    
  5. 채팅 프롬프트에서 다음 입력을 시뮬레이션합니다.
    Topic: Tokyo. Findings: Tokyo is a city.
    
    결과가 너무 간략하므로 status='fail'을 반환해야 합니다.

6. ✍️ 콘텐츠 빌더 에이전트

콘텐츠 빌더

콘텐츠 빌더는 창의적인 작가입니다. 승인된 연구를 가져와 과정으로 전환합니다.

  1. agents/content_builder/agent.py를 엽니다.
  2. content_builder 에이전트를 정의합니다.
    content_builder = Agent(
        name="content_builder",
        model=MODEL,
        description="Transforms research findings into a structured course.",
        instruction="""
        You are an expert course creator.
        Take the approved 'research_findings' and transform them into a well-structured, engaging course module.
    
        **Formatting Rules:**
        1. Start with a main title using a single `#` (H1).
        2. Use `##` (H2) for main section headings.
        3. Use bullet points and clear paragraphs.
        4. Maintain a professional but engaging tone.
    
        Ensure the content directly addresses the user's original request.
        """,
    )
    root_agent = content_builder
    

주요 개념: 컨텍스트 전파

'콘텐츠 빌더는 연구원이 찾은 내용을 어떻게 알 수 있나요?'라고 궁금해하실 수 있습니다. ADK에서 파이프라인의 에이전트는 session.state를 공유합니다. 나중에 오케스트레이터에서 연구원과 심사위원이 이 공유 상태에 출력을 저장하도록 구성합니다. 콘텐츠 빌더의 프롬프트는 이 기록에 효과적으로 액세스할 수 있습니다.

7. 🎻 오케스트레이터

조정자 에이전트

Orchestrator는 멀티 에이전트 팀의 관리자입니다. 특정 작업을 수행하는 전문가 에이전트 (연구원, 심사원, 콘텐츠 작성자)와 달리 오케스트레이터의 역할은 워크플로를 조정하고 에이전트 간에 정보가 올바르게 흐르도록 하는 것입니다.

🌐 아키텍처: 에이전트 간 (A2A)

A2A 아키텍처

이 실습에서는 분산 시스템을 빌드합니다. 단일 Python 프로세스에서 모든 에이전트를 실행하는 대신 독립적인 마이크로서비스로 배포합니다. 이를 통해 각 에이전트는 독립적으로 확장할 수 있으며 전체 시스템이 비정상 종료되지 않고 실패할 수 있습니다.

이를 위해 Google에서는 에이전트 간 (A2A) 프로토콜을 사용합니다.

A2A 프로토콜

자세히 알아보기: 프로덕션 시스템에서 에이전트는 서로 다른 서버 (또는 서로 다른 클라우드)에서 실행됩니다. A2A 프로토콜은 HTTP를 통해 서로를 검색하고 대화할 수 있는 표준 방법을 만듭니다. RemoteA2aAgent은 이 프로토콜의 ADK 클라이언트입니다.

  1. agents/orchestrator/agent.py를 엽니다.
  2. 댓글 # TODO: Define Remote Agents 또는 원격 상담사 정의 섹션을 찾습니다.
  3. 다음 코드를 추가하여 연결을 정의합니다. 가져오기 , 다른 에이전트 정의 에 이를 배치해야 합니다.
    # ... existing code ...
    
    # Connect to the Researcher (Localhost port 8001)
    researcher_url = os.environ.get("RESEARCHER_AGENT_CARD_URL", "http://localhost:8001/a2a/agent/.well-known/agent-card.json")
    researcher = RemoteA2aAgent(
        name="researcher",
        agent_card=researcher_url,
        description="Gathers information using Google Search.",
        # IMPORTANT: Save the output to state for the Judge to see
        after_agent_callback=create_save_output_callback("research_findings"),
        # IMPORTANT: Use authenticated client for communication
        httpx_client=create_authenticated_client(researcher_url)
    )
    
    # Connect to the Judge (Localhost port 8002)
    judge_url = os.environ.get("JUDGE_AGENT_CARD_URL", "http://localhost:8002/a2a/agent/.well-known/agent-card.json")
    judge = RemoteA2aAgent(
        name="judge",
        agent_card=judge_url,
        description="Evaluates research.",
        after_agent_callback=create_save_output_callback("judge_feedback"),
        httpx_client=create_authenticated_client(judge_url)
    )
    
    # Content Builder (Localhost port 8003)
    content_builder_url = os.environ.get("CONTENT_BUILDER_AGENT_CARD_URL", "http://localhost:8003/a2a/agent/.well-known/agent-card.json")
    content_builder = RemoteA2aAgent(
        name="content_builder",
        agent_card=content_builder_url,
        description="Builds the course.",
        httpx_client=create_authenticated_client(content_builder_url)
    )
    

8. 🛑 에스컬레이션 검사기

루프에는 중지하는 방법이 필요합니다. 심판이 '통과'라고 말하면 루프를 즉시 종료하고 콘텐츠 빌더로 이동해야 합니다.

BaseAgent를 사용한 맞춤 로직

자세히 알아보기: 모든 상담사가 LLM을 사용하는 것은 아닙니다. 간단한 Python 로직이 필요한 경우가 있습니다. BaseAgent를 사용하면 코드를 실행하는 에이전트를 정의할 수 있습니다. 이 경우 세션 상태를 확인하고 EventActions(escalate=True)를 사용하여 LoopAgent에 중지 신호를 보냅니다.

  1. 여전히 agents/orchestrator/agent.py에 있습니다.
  2. EscalationChecker TODO 자리표시자를 찾습니다.
  3. 다음 구현으로 바꿉니다.
    class EscalationChecker(BaseAgent):
        """Checks the judge's feedback and escalates (breaks the loop) if it passed."""
    
        async def _run_async_impl(
            self, ctx: InvocationContext
        ) -> AsyncGenerator[Event, None]:
            # Retrieve the feedback saved by the Judge
            feedback = ctx.session.state.get("judge_feedback")
            print(f"[EscalationChecker] Feedback: {feedback}")
    
            # Check for 'pass' status
            is_pass = False
            if isinstance(feedback, dict) and feedback.get("status") == "pass":
                is_pass = True
            # Handle string fallback if JSON parsing failed
            elif isinstance(feedback, str) and '"status": "pass"' in feedback:
                is_pass = True
    
            if is_pass:
                # 'escalate=True' tells the parent LoopAgent to stop looping
                yield Event(author=self.name, actions=EventActions(escalate=True))
            else:
                # Continue the loop
                yield Event(author=self.name)
    
    escalation_checker = EscalationChecker(name="escalation_checker")
    

핵심 개념: 이벤트를 통한 제어 흐름

에이전트는 텍스트뿐만 아니라 이벤트를 통해 소통합니다. escalate=True로 이벤트를 생성하여 이 에이전트는 상위 요소 (LoopAgent)에 신호를 전송합니다. LoopAgent는 이 신호를 포착하고 루프를 종료하도록 프로그래밍되어 있습니다.

9. 🔁 리서치 루프

Research Loop

조사 -> 판단 -> (실패) -> 조사 -> ...와 같은 피드백 루프가 필요합니다.

  1. 여전히 agents/orchestrator/agent.py에 있습니다.
  2. research_loop 정의를 추가합니다. EscalationChecker 클래스와 escalation_checker 인스턴스 뒤에 배치합니다.
    research_loop = LoopAgent(
        name="research_loop",
        description="Iteratively researches and judges until quality standards are met.",
        sub_agents=[researcher, judge, escalation_checker],
        max_iterations=3,
    )
    

주요 개념: LoopAgent

LoopAgentsub_agents을 순서대로 순환합니다.

  1. researcher: 데이터를 찾습니다.
  2. judge: 데이터를 평가합니다.
  3. escalation_checker: yield Event(escalate=True) 여부를 결정합니다. escalate=True가 발생하면 루프가 일찍 중단됩니다. 그렇지 않으면 연구자 (최대 max_iterations)에서 다시 시작됩니다.

10. 🔗 최종 파이프라인

최종 파이프라인

마지막으로 모든 것을 하나로 연결합니다.

  1. 여전히 agents/orchestrator/agent.py에 있습니다.
  2. 파일 하단에 root_agent를 정의합니다. 이렇게 하면 기존 root_agent = None 자리표시자가 대체됩니다.
    root_agent = SequentialAgent(
        name="course_creation_pipeline",
        description="A pipeline that researches a topic and then builds a course from it.",
        sub_agents=[research_loop, content_builder],
    )
    

핵심 개념: 계층적 컴포지션

research_loop는 에이전트 (LoopAgent)입니다. SequentialAgent의 다른 하위 에이전트와 마찬가지로 취급합니다. 이러한 구성 가능성을 통해 간단한 패턴 (시퀀스 내부의 루프, 라우터 내부의 시퀀스 등)을 중첩하여 복잡한 로직을 빌드할 수 있습니다.

11. 💻 로컬에서 실행

모든 것을 실행하기 전에 ADK가 분산 환경을 로컬에서 시뮬레이션하는 방법을 살펴보겠습니다.

심층 분석: 로컬 개발의 작동 방식

마이크로서비스 아키텍처에서 모든 에이전트는 자체 서버로 실행됩니다. 배포하면 4개의 서로 다른 Cloud Run 서비스가 있습니다. 터미널 탭 4개를 열고 명령어 4개를 실행해야 하는 경우 로컬에서 이를 시뮬레이션하는 것은 고통스러울 수 있습니다.

이 스크립트는 연구원 (포트 8001), 심판 (8002), 콘텐츠 빌더 (8003)의 uvicorn 프로세스를 시작합니다. RESEARCHER_AGENT_CARD_URL와 같은 환경 변수를 설정하고 이를 오케스트레이터 (포트 8004)에 전달합니다. 나중에 클라우드에서 구성할 방법과 정확히 동일합니다.

앱 실행

  1. 오케스트레이션 스크립트를 실행합니다.
    ./run_local.sh
    
    그러면 4개의 개별 프로세스가 시작됩니다.
  2. 테스트:
    • Cloud Shell을 사용하는 경우: 웹 미리보기 버튼 (터미널 오른쪽 상단) -> 포트 8080에서 미리보기 -> 포트 변경을 클릭하여 8000로 변경합니다.
    • 로컬에서 실행하는 경우: 브라우저에서 http://localhost:8000를 엽니다.
    • 프롬프트: '커피의 역사에 관한 강의를 만들어 줘.'
    • 관찰: Orchestrator가 연구원에게 전화를 겁니다. 출력은 심판에게 전달됩니다. 심판이 실패하면 루프가 계속됩니다.
    문제 해결:
    • '내부 서버 오류' / 인증 오류: 인증 오류 (예: google-auth 관련)가 표시되면 로컬 머신에서 실행하는 경우 gcloud auth application-default login를 실행했는지 확인합니다. Cloud Shell에서 GOOGLE_CLOUD_PROJECT 환경 변수가 올바르게 설정되어 있는지 확인합니다.
    • 터미널 오류: 새 터미널 창에서 명령어가 실패하면 환경 변수 (GOOGLE_CLOUD_PROJECT 등)를 다시 내보내야 합니다.
  3. 격리된 에이전트 테스트: 전체 시스템이 실행 중인 경우에도 포트를 직접 타겟팅하여 특정 에이전트를 테스트할 수 있습니다. 이는 전체 체인을 트리거하지 않고 특정 구성요소를 디버깅하는 데 유용합니다.
    • 연구원 전용 (포트 8001): http://localhost:8001
    • 평가만 해당 (포트 8002): http://localhost:8002
    • 콘텐츠 빌더 전용 (포트 8003): http://localhost:8003
    • 조정자 (포트 8004): http://localhost:8004 (조정자 로직에 직접 액세스)

12. 🚀 Cloud Run에 배포

최종 검증은 클라우드에서 실행됩니다. 각 에이전트는 별도의 서비스로 배포됩니다.

배포 구성 이해

Cloud Run에 에이전트를 배포할 때 동작과 연결을 구성하기 위해 여러 환경 변수를 전달합니다.

  • GOOGLE_CLOUD_PROJECT: 에이전트가 로깅 및 Vertex AI 호출에 올바른 Google Cloud 프로젝트를 사용하도록 합니다.
  • GOOGLE_GENAI_USE_VERTEXAI: 에이전트 프레임워크 (ADK)에 Gemini API를 직접 호출하는 대신 모델 추론에 Vertex AI를 사용하도록 지시합니다.
  • [AGENT]_AGENT_CARD_URL: 조정자에게는 이 부분이 중요합니다. 오케스트레이터가 원격 에이전트를 찾을 위치를 알려줍니다. 이를 배포된 Cloud Run URL (특히 에이전트 카드 경로)로 설정하면 오케스트레이터가 인터넷을 통해 연구원, 심사자, 콘텐츠 작성자를 검색하고 이들과 통신할 수 있습니다.
  1. 연구원 배포:
    gcloud run deploy researcher \
      --source agents/researcher/ \
      --region us-central1 \
      --allow-unauthenticated \
      --labels dev-tutorial=prod-ready-1 \
      --set-env-vars GOOGLE_CLOUD_PROJECT=$GOOGLE_CLOUD_PROJECT \
      --set-env-vars GOOGLE_GENAI_USE_VERTEXAI="true"
    
    URL 캡처:
    RESEARCHER_URL=$(gcloud run services describe researcher --region us-central1 --format='value(status.url)')
    echo $RESEARCHER_URL
    
  2. 심판 배포:
    gcloud run deploy judge \
      --source agents/judge/ \
      --region us-central1 \
      --allow-unauthenticated \
      --labels dev-tutorial=prod-ready-1 \
      --set-env-vars GOOGLE_CLOUD_PROJECT=$GOOGLE_CLOUD_PROJECT \
      --set-env-vars GOOGLE_GENAI_USE_VERTEXAI="true"
    
    URL 캡처:
    JUDGE_URL=$(gcloud run services describe judge --region us-central1 --format='value(status.url)')
    echo $JUDGE_URL
    
  3. 콘텐츠 빌더 배포:
    gcloud run deploy content-builder \
      --source agents/content_builder/ \
      --region us-central1 \
      --allow-unauthenticated \
      --labels dev-tutorial=prod-ready-1 \
      --set-env-vars GOOGLE_CLOUD_PROJECT=$GOOGLE_CLOUD_PROJECT \
      --set-env-vars GOOGLE_GENAI_USE_VERTEXAI="true"
    
    URL 캡처:
    CONTENT_BUILDER_URL=$(gcloud run services describe content-builder --region us-central1 --format='value(status.url)')
    echo $CONTENT_BUILDER_URL
    
  4. 오케스트레이터 배포: 캡처된 환경 변수를 사용하여 오케스트레이터를 구성합니다.
    gcloud run deploy orchestrator \
      --source agents/orchestrator/ \
      --region us-central1 \
      --allow-unauthenticated \
      --labels dev-tutorial=prod-ready-1 \
      --set-env-vars RESEARCHER_AGENT_CARD_URL=$RESEARCHER_URL/a2a/agent/.well-known/agent-card.json \
      --set-env-vars JUDGE_AGENT_CARD_URL=$JUDGE_URL/a2a/agent/.well-known/agent-card.json \
      --set-env-vars CONTENT_BUILDER_AGENT_CARD_URL=$CONTENT_BUILDER_URL/a2a/agent/.well-known/agent-card.json \
      --set-env-vars GOOGLE_CLOUD_PROJECT=$GOOGLE_CLOUD_PROJECT \
      --set-env-vars GOOGLE_GENAI_USE_VERTEXAI="true"
    
    URL 캡처:
    ORCHESTRATOR_URL=$(gcloud run services describe orchestrator --region us-central1 --format='value(status.url)')
    echo $ORCHESTRATOR_URL
    
  5. 프런트엔드 배포:
    gcloud run deploy course-creator \
        --source app \
        --region us-central1 \
        --allow-unauthenticated \
        --labels dev-tutorial=prod-ready-1 \
        --set-env-vars AGENT_SERVER_URL=$ORCHESTRATOR_URL \
        --set-env-vars GOOGLE_CLOUD_PROJECT=$GOOGLE_CLOUD_PROJECT
    
  6. 원격 배포 테스트: 배포된 Orchestrator의 URL을 엽니다. 이제 Google의 서버리스 인프라를 활용하여 에이전트를 확장할 수 있으며, 완전히 클라우드에서 실행됩니다.도움말: 모든 마이크로서비스와 URL은 Cloud Run 인터페이스에서 확인할 수 있습니다.

13. 요약

축하합니다. 프로덕션 준비가 완료된 분산 멀티 에이전트 시스템을 빌드하고 배포했습니다.

달성한 내용

  • 복잡한 작업 분해: 하나의 거대한 프롬프트 대신 작업을 전문 역할 (연구원, 심사관, 콘텐츠 빌더)로 분할했습니다.
  • 품질 관리 구현: 고품질 정보만 최종 단계에 도달하도록 LoopAgent 및 구조화된 Judge를 사용했습니다.
  • 프로덕션용으로 빌드됨: Agent-to-Agent (A2A) 프로토콜과 Cloud Run을 사용하여 각 에이전트가 독립적이고 확장 가능한 마이크로서비스인 시스템을 만들었습니다. 이는 단일 Python 스크립트에서 모든 것을 실행하는 것보다 훨씬 더 강력합니다.
  • 조정: SequentialAgentLoopAgent를 사용하여 명확한 제어 흐름 패턴을 정의했습니다.

다음 단계

이제 기본 사항을 갖췄으므로 이 시스템을 확장할 수 있습니다.

  • 도구 추가: 연구원에게 내부 문서 또는 API에 대한 액세스 권한을 부여합니다.
  • 심사 개선: 더 구체적인 기준이나 'Human in the Loop' 단계를 추가합니다.
  • 모델 교체: 다양한 에이전트에 서로 다른 모델을 사용해 보세요 (예: 심사관에는 더 빠른 모델, 콘텐츠 작성자에는 더 강력한 모델).

이제 Google Cloud에서 복잡하고 안정적인 에이전트 워크플로를 빌드할 준비가 되었습니다.