빅데이타 & 머신러닝/생성형 AI (ChatGPT etc)

LangChain // DeepAgent를 이용한 자율형 에이전트 구현

Terry Cho 2026. 3. 18. 14:35

조대협 (http://bcho.tistory.com)

 

Claude Code를 리서치 하면서, 요즘 에이전트의 구조 특히 SKILL, SubAgent, MultiAgent 등을 이해하다 보니, 실제로 만드는것도 어렵지 않겠다. 생각이 들어서 어떤 프레임웍이 지원하는지 찾아보던중 Langchain에서 얼마전에 발표한 DeepAgent를 찾아서 간단한 시나리오를 구현해봤다. 

 

DeepAgent는 Langchain의 LangGraph (복잡한 LLM 간의 연결 구조를 구현하는 프레임웍)을 한단계 더 추상화 시켜서 Agent 개발에 최적화된 프레임워크 이다. 다른 Agent가 가지고 있는 SubAgent,Skill 등의 개념을 잘 지원하고 있으며, 무엇보다 구현이 매우 쉽게 되어 있다. 

 

Claude Code과 개념은 동일하고 설정 방법이나 아키텍처 구조역시 거의 유사하기 때문에, 스스로 에이전트를 만들기 위한 좋은 시작점이 아닌가 싶다. 예전에 Langchain, LangGraph등을 봐왔던 나로는 구현에 대한 설계는 대충 머릿속에 있지만, 그 개념을 로우레벨 코드로 구현하려면 꽤 많은 노력이 들었을텐데, DeepAgent는 개념을 참 쉽게 잡아 놓기는 했다. 

 

반대로,그 만큼 추상화를 해놓은 만큼 디버깅 및 트레이싱이 너무 어렵다. LangSmith로 트레이스를 해봐도 제대로 Skill등이 호출되었는지 찾기가 어려웠다. 

 

사전 지식 링크

자동 블로그 작성 에이전트

작성하고자 하는 에이전트는 블로그 토픽을 주면, 자동으로 블로그글을 HTML로 작성해주는 에이전트이다. 

이를 위해서 아래와 같은 흐름을 따른다.

 

  • 먼저 WebSearch SubAgent가, Exa 웹검색 MCP Server를 통해서, 토픽에 관련된 웹 컨텐츠를 수집한다. 
  • Blog Writer는 기술 블로그 글쓰기 전문 SubAgent로, 앞에서 입력받은 토픽과, 웹컨텐츠를 가지고 블로그를 쓴다. 이때 어떻게 블로그를 써야 하는지 (문장 구조, 문체 등)는 미리 SKILL에 등록해놓고, 이 SKILL을 이용해서 글을 쓴다. 
  • 마지막으로 완성된 컨텐츠를 HTML Styler SubAgent가 HTML 형식으로 변환한다. 

 

이 3개의 SubAgent를 Main에이전트가 오케스트레이션해서  순차적으로  작업을 진행하도록 한다. 

 

코드 분석

WebSearch SubAgent 생성

create_websearch_subagent는 AI모델이 참고하기 좋은 웹 검색 API인 exa.ai를 사용하도록 한다. exa.ai 검색은 MCP 서버를 연동해서 한다. 아래는 먼저 EXA 검색 MCP 서버를 등록하는 과정이다. 

async def create_websearch_subagent():
    """Exa MCP 연결 및 WebSearch SubAgent 생성을 담당한다."""
    print("[Info] Connecting to Exa MCP server...")
    
    # Exa API Key (hardcoded)
    EXA_API_KEY = "{Your API KEY}"
    EXA_MCP_URL = (
        f"https://mcp.exa.ai/mcp"
        f"?exaApiKey={EXA_API_KEY}"
        f"&tools=web_search_exa,web_search_advanced_exa,crawling_exa"
    )

    client = MultiServerMCPClient(
        {
            "exa": {
                "transport": "streamable_http",
                "url": EXA_MCP_URL,
            }
        }
    )

    try:
        mcp_tools = await client.get_tools()
        print(f"[Info] Loaded {len(mcp_tools)} tool(s) from Exa MCP:")
        for t in mcp_tools:
            print(f"       - {t.name}")
    except Exception as e:
        print(f"[Error] Failed to load tools from Exa MCP: {e}")
        mcp_tools = []

 

 MultiServerMCPClient에, EXA MCP Tool을 등록한후, mcp_tools에 mcp 서버 목록을 저장하였다. 다음 코드가, 연결되서 실제로 websearch_agent를 만드는 부분인데, 정말 간단하다.

# 앞에서 이어서.. 

SEARCH_SYSTEM_PROMPT = """\
You are an expert web researcher specialized in retrieving online information.
When given a keyword or topic, use the Exa web search tool to search the web 
with the search condition set to results published after January 1, 2025.
Produce a structured JSON object (no extra text) that summarizes **exactly 2** most relevant documents with these fields:
Always include the source URLs in your response.
You MUST output your final answer strictly as a JSON string containing the research data. Do not include markdown formatting like ```json in your response.
"""
    return {
        "name": "websearch_agent",
        "description": "Expert web researcher specialized in retrieving online information and summarizing it as structured JSON data.",
        "system_prompt": SEARCH_SYSTEM_PROMPT,
        "tools": mcp_tools,
        "model": "openai:gpt-5-mini",
    }

 

이름과 시스템 프롬프트가 들어가고, 서버 에이전트에서 사용하는 AI Model (비용때문에, gpt-5-mini모델 사용)을 정의한다. 이 SubAgent는 MCP호출을 하기 때문에, tools에 mcp서버 목록을 둔다. 

주의해야하는 점은 description인데, 이 SubAgent가 어떤 역할을 하는지를 정의하는 부분이다. Main Agent 는 이 SubAgent를 쓸지말지를 결정할때, description을 통해서  subagent가 어떤 역할을할 수 있는지를 이해한후에, SubAgent를 통해서 풀 수 있는 문제에 대해서 이  SubAgent를 호출하게 된다. 

Blog Writer SubAgent 생성

BlogWriter는 입력받은 정보로 블로그 글을 써주는 SubAgent인데, 아래 설정을 보면 특이한 사항은 없다.

단 하나. Return 문에서 SubAgent를 생성하는 부분을 보면, skills에 “/skills” 디렉토리가 들어가 있는것을 볼 수 있다. 

def create_blog_writer_subagent():
    """Blog Writer SubAgent 생성을 담당한다."""
    BLOG_SYSTEM_PROMPT = """\
You are a Senior SVB Architect and expert technical blog writer subagent.
Your objective is to write a technical blog post based on the provided reference data you receive.

CRITICAL INSTRUCTIONS:
1. You MUST check your available skills and find the `blog_style` skill.
2. Read the `SKILL.md` file (e.g., at `skills/blog_style/SKILL.md`) to understand your Korean persona (조대협 스타일) and formatting rules.
3. You MUST write the blog post in **KOREAN** using **평어체 (해라체)** as specified in the skill.
4. If you don't follow the "조대협 스타일" (Senior SVB Architect tone in Korean), you have failed the task.
"""
    return {
        "name": "blog_writer_agent",
        "description": "Expert technical blog writer (Korean, Senior SVB Architect style). Feed it JSON research data.",
        "system_prompt": BLOG_SYSTEM_PROMPT,
        "tools": [],  # Inherits tools via FilesystemMiddleware
        "model": "openai:gpt-5-mini",
        "skills": ["/skills/"],  # Point to parent directory containing skill subdirectories
    }

이 Skills는 Agent가 사용할 수 있는 기능 (프롬프트+코드 조합)을 서술해놓은 설정 파일인데, https://agentskills.io/home 에 의해서 표준화가 되어 있다. 즉 이 앤트로픽이나 GPT 등에서도 똑같이 사용할 수 있다는 SKILL이다. 반대로 말하면 다른 에이전트 시스템에 의해서 정의된 SKILL도 가져다 쓸 수 있다는 말이 된다. 

 

스킬들은 현재 디렉토리의 ./skills/{스킬명} 디렉토리에 저장이 되는데, 이 예제에서는 /skills/blog-style /SKILL.md이라는 스킬로 블로그의 글쓰기 형식을 가이드 하고 있다.  그 내용의 일부를 보면 다음과 같다. (내 블로그의 글쓰기 스타일을 분석해서 가이드로 만든 내용이다.)

---
name: blog_style
description: Technical blog writing style guide inspired by Silicon Valley senior engineers (조대협 스타일).
---

# 시스템 프롬프트: 실리콘밸리 시니어 엔지니어 블로그 글쓰기 가이드 (조대협 스타일)

## 1. 페르소나 (Persona)
당신은 실리콘밸리에서 활동하며 글로벌 IT 기업에서 아키텍처 설계, 분산 시스템, 클라우드 네이티브, AI 인프라 등을 다루는 **시니어 소프트웨어 엔지니어 겸 아키텍트**입니다.
* **성향:** 실무 중심적이며, 기술의 화려함보다는 '왜 이 기술이 필요한가', '어떤 문제를 해결하는가'에 집중합니다.
* **독자층:** 주니어~미들급 개발자, 동료 시니어 엔지니어, 아키텍트. 독자를 가르치려 들기보다는, 자신의 경험과 연구 내용을 담백하게 공유하는 멘토의 태도를 가집니다.
* **목적:** 특정 기술, 프레임워크, 아키텍처 패턴, 최신 IT 논문(AI 등)을 분석하고 이를 실무에 어떻게 적용할 수 있을지 통찰을 제공합니다.

---

## 2. 어조 및 문체 (Tone & Manner)
블로그 글은 철저히 아래의 문체 규칙을 따릅니다
: 중략

HTML Styler SubAgent

마지막으로, HTML Styler SubAgent는 별다른 설정은 없고, 앞에서 입력받은 텍스트를 프롬프트와 모델을 이용해서 HTML 로 변환하는 에이전트 이다. 

def create_html_styler_subagent():
    """블로그 글을 HTML로 변환하는 SubAgent 생성을 담당한다."""
    HTML_SYSTEM_PROMPT = """\
You are an expert HTML styler. 
Your objective is to convert the provided Markdown blog post into a clean, well-structured HTML format suitable for platforms like Tistory or Medium.

CRITICAL INSTRUCTIONS:
1. Use semantic HTML5 tags (e.g., <h1>, <h2>, <p>, <ul>, <li>, <strong>).
2. Ensure the output is a standalone HTML string that can be easily copy-pasted.
3. Do not include <html>, <head>, or <body> tags. Just the content blocks.
4. Maintain the professional "Senior SVB Architect" tone and Korean language from the original post.
5. Return the HTML as a plain string.
"""
    return {
        "name": "html_styler_agent",
        "description": "Expert HTML styler. Converts Markdown blog posts to clean HTML for Tistory/Medium.",
        "system_prompt": HTML_SYSTEM_PROMPT,
        "tools": [],
        "model": "openai:gpt-5-mini",
    }

 

앞에서는 자세히 셜명하지 않았는데, 사실 SubAgent 정의가 매우 쉬운것을 볼 수 있다.  Return에 SubAgent설정에 필요한 부분을 JSON으로 정의하면 되고 여기에 필요한 SKILL이나 TOOL만을 연결하면 된다. (이걸 코드로만들려면….?!) 

 

Main Agent

메인 에이전트는 블로그 글쓰기 소재를 받아서, 앞에서 구현한 3개의 SubAgent를 이용해서, 글쓰기 작업을 수행한다. 

async def async_main(prompt: str) -> None:
    """프롬프트를 처리하여 WebSearch, Blog Writer 및 HTML Styler 에이전트를 조율한다."""
    
    # SubAgents 생성
    websearch_subagent = await create_websearch_subagent()
    blog_writer_subagent = create_blog_writer_subagent()
    html_styler_subagent = create_html_styler_subagent()

# 메인 에이전트 시스템 프롬프트  
 MAIN_SYSTEM_PROMPT = """\
You are a procedural coordinator agent. Your ONLY job is to execute a 5-step workflow by calling subagents and tools. 
DO NOT perform the research or writing yourself. You MUST delegate to the assigned subagents.

WORKFLOW:
1. **Research**: Call `websearch_agent` via the `task` tool to gather information on the user's topic.
2. **Draft**: Wait for the research results. IMMEDIATELY call `blog_writer_agent` via the `task` tool, passing the ENTIRE research output as input.
3. **Format**: Wait for the Markdown draft. Call `html_styler_agent` via the `task` tool to convert that Markdown into HTML.
4. **Save**: Call the `write_file` tool to save the resulting HTML. The filename MUST be based on the blog title (e.g., `ai_coding_trends.html`).
5. **Output**: Finally, display the exact HTML content returned by the styler as your final response to the user.

## @rules
1. Proceed through all 5 steps without asking for further clarification.
2. Do not summarize or alter the outputs from subagents; pass them through verbatim to the next tool.
3. You are finished ONLY after Step 5.
"""

: 중략

   agent = create_deep_agent(
        model="openai:gpt-5-mini",
        tools=[],  
        system_prompt=MAIN_SYSTEM_PROMPT,
        backend=FilesystemBackend(root_dir=current_dir, virtual_mode=True),        
  subagents=[websearch_subagent, blog_writer_subagent, html_styler_subagent],
        checkpointer=checkpointer,
        debug=True,
    )

먼저 SubAgent 3개를 생성한 다음 create_deep_agent를 이용해서, subagents에 이 3개의 SubAgent를 인자로 준 후에, 프롬프트와 함께 에이전트를 생성하여 실행된다. 

모니터링

이렇게 구현된 Agent는 LangSmith와 연동 (LangSmith와 사용법에 대해서는 https://bcho.tistory.com/1427 글 참고)을 통해서, Agent가 스텝별로 어떻게 작동하는지 프롬프트 단위로 모니터링이 가능하다.

 

사실 개인적인 의견으로는 내가 생각하기에는 3개의 SubAgent와 Tool, Skill 등만 호출하는 간단한 스텝을 생각했는데, DeepAgent는 LangGraph를 대단히 추상화 해놔서, 실제로 LangSmith의 Trace를 열어보면 생각보다 많은 스텝이 보인다. Skill이 제대로 호출되는지를 찾아보려고 했는데, 사실 찾지 못하고 debug flag를 ON해서, 디버깅을 통해서 찾았다.

 

결론

지금 시장은 Claude Code, 하네스 엔지니어링, OpenCraw 등 자율형 에이전트쪽으로 큰 아키텍처 방향이 변하고 있다. 이렇게 복잡한 아키텍처를 손쉽게 개발할 수 있는 SDK가 있는 것은 환영할만할일이고, 또 다른 좋은 SDK들이 많이 생기기를 바란다. 

 

추가 의견 

  • Antigrivity 를 이용해서 코딩했고, DeepAgent 사용은 처음이었다. 대략 2시간 정도 시간이 소요되었고, SKILL연결이 잘안되서 이 부분을 디버깅하는데, 3시간정도 소요되었다. 
  • 앞에서 언급했듯이 디버깅이 어렵다.
  • Exa.ai 검색 MCP가 느린건지, 성능이 매우 낮다. LangSmith Trace를 봐도, DeepAgent 가 추가로 LLM을 호출하는 부분과 추가 스탭이 많은데 최적화가 덜 된 느낌이다. 구글의 ADK나 엔트로픽 SDK등을 이용해서 다시한번 구현해봐야겠다.

참고 // 전체 코드

"""
adk.py

키워드를 인자로 받아 Exa MCP (mcp.exa.ai) 로 웹 검색하여 결과를 출력하는 Google ADK 기반 Agent.
websearch.py를 Google ADK 프레임워크 기반으로 마이그레이션한 코드.

Usage:
    python adk.py <검색 키워드>
    python adk.py "AI agent trends 2025"
"""

import asyncio
import os
import sys

# Remove expired known API key locally to let default creds or proper API key take over
_expired_keys = ["YOUR API KEY"]
for key_name in ["GOOGLE_API_KEY", "GEMINI_API_KEY"]:
    if os.environ.get(key_name) in _expired_keys:
        del os.environ[key_name]

# LangSmith Tracing Configuration
os.environ["LANGSMITH_TRACING"] = os.environ.get("LANGSMITH_TRACING", "true")
os.environ["LANGSMITH_API_KEY"] = os.environ.get("LANGSMITH_API_KEY", "YOUR API KEY")
os.environ["LANGSMITH_PROJECT"] = os.environ.get("LANGSMITH_PROJECT", "terry-blog")
os.environ["LANGSMITH_ENDPOINT"] = os.environ.get("LANGSMITH_ENDPOINT", "https://api.smith.langchain.com")

from google.adk import Agent
from google.adk.runners import InMemoryRunner
from google.adk.tools import FunctionTool
from google.adk.tools.mcp_tool import MCPToolset, StreamableHTTPConnectionParams


def write_file(filename: str, content: str) -> str:
    """Saves the output to the filesystem.
    
    Args:
        filename: Name of the file to save (e.g., ai_coding_trends.html). MUST NOT contain spaces or special characters.
        content: The HTML content to write to the file.
        
    Returns:
        A success message indicating the file was saved.
    """
    try:
        # Sanitize filename: replace spaces with underscores, remove unsafe characters
        safe_filename = filename.replace(" ", "_")
        safe_filename = "".join([c for c in safe_filename if c.isalpha() or c.isdigit() or c in ('_', '-', '.')])
        
        # Ensure it saves into current directory for safety, preventing directory climbing
        if not safe_filename.endswith(".html"):
             safe_filename += ".html"
             
        filepath = os.path.join(os.path.abspath(os.path.dirname(__file__)), safe_filename)
        
        with open(filepath, "w", encoding="utf-8") as f:
            f.write(content)
        return f"Successfully saved to {filepath}"
    except Exception as e:
        return f"Failed to rewrite file: {str(e)}"

write_file_tool = FunctionTool(func=write_file)


async def create_websearch_subagent() -> Agent:
    """Exa MCP 연결 및 WebSearch SubAgent 생성을 담당한다."""
    print("[Info] Connecting to Exa MCP server via ADK...")
    
    # Exa API Key (hardcoded)
    EXA_API_KEY = "YOUR API KEY"
    EXA_MCP_URL = (
        f"https://mcp.exa.ai/mcp"
        f"?exaApiKey={EXA_API_KEY}"
        f"&tools=web_search_exa,web_search_advanced_exa,crawling_exa"
    )

    mcp_toolset = MCPToolset(
        connection_params=StreamableHTTPConnectionParams(url=EXA_MCP_URL)
    )

    # Note: ADK uses the MCPToolset interface automatically
    # to fetch tools asynchronously when running.

    SEARCH_SYSTEM_PROMPT = """\
You are an expert web researcher specialized in retrieving online information.
When given a keyword or topic, use the Exa web search tool to search the web 
with the search condition set to results published after January 1, 2025.
Produce a structured JSON object (no extra text) that summarizes **exactly 2** most relevant documents with these fields:
Always include the source URLs in your response.
You MUST output your final answer strictly as a JSON string containing the research data. Do not include markdown formatting like ```json in your response.
"""
    return Agent(
        name="websearch_agent",
        description="Expert web researcher specialized in retrieving online information and summarizing it as structured JSON data.",
        instruction=SEARCH_SYSTEM_PROMPT,
        tools=[mcp_toolset],
        model="gemini-2.5-flash",
    )

def create_blog_writer_subagent() -> Agent:
    """Blog Writer SubAgent 생성을 담당한다."""
    BLOG_SYSTEM_PROMPT = """\
You are a Senior SVB Architect and expert technical blog writer subagent.
Your objective is to write a technical blog post based on the provided reference data you receive.

CRITICAL INSTRUCTIONS:
1. You MUST use the `blog_style` skill instructions below.
2. You MUST write the blog post in **KOREAN** using **평어체 (해라체)** as specified in the skill.
3. If you don't follow the "조대협 스타일" (Senior SVB Architect tone in Korean), you have failed the task.

---
SKILL (`blog_style`):

## 1. 페르소나 (Persona)
당신은 실리콘밸리에서 활동하며 글로벌 IT 기업에서 아키텍처 설계, 분산 시스템, 클라우드 네이티브, AI 인프라 등을 다루는 **시니어 소프트웨어 엔지니어 겸 아키텍트**입니다.
* **성향:** 실무 중심적이며, 기술의 화려함보다는 '왜 이 기술이 필요한가', '어떤 문제를 해결하는가'에 집중합니다.
* **독자층:** 주니어~미들급 개발자, 동료 시니어 엔지니어, 아키텍트. 독자를 가르치려 들기보다는, 자신의 경험과 연구 내용을 담백하게 공유하는 멘토의 태도를 가집니다.

## 2. 어조 및 문체 (Tone & Manner)
블로그 글은 철저히 아래의 문체 규칙을 따릅니다.
1. **평어체(해라체) 유지:** 문장의 끝맺음은 '~다', '~한다', '~이다', '~불가능하다' 등으로 끝냅니다. 절대 '~요', '~습니다'와 같은 경어체를 쓰지 마세요.
2. **객관적이고 담백한 서술:** 감탄사 배제.
3. **전문 용어(IT Jargon)의 자연스러운 사용:**
4. **대화형 의문문 활용 (전환점):** 스스로 질문 던지고 답하는 방식.

## 3. 글의 전개 구조 (Article Structure)
단계 1: 도입부 (Motivation & Hook)
단계 2: 핵심 개념 이해 (Core Concept & Visuals)
단계 3: 기존 기술와의 비교 (Pros & Cons, Trade-offs)
단계 4: 실무 활용 시나리오 (Use Case & Implementation)
단계 5: 결론 및 시니어의 통찰 (Conclusion & Personal Reflection)
"""
    return Agent(
        name="blog_writer_agent",
        description="Expert technical blog writer (Korean, Senior SVB Architect style). Feed it JSON research data.",
        instruction=BLOG_SYSTEM_PROMPT,
        tools=[],  
        model="gemini-2.5-flash",
    )

def create_html_styler_subagent() -> Agent:
    """블로그 글을 HTML로 변환하는 SubAgent 생성을 담당한다."""
    HTML_SYSTEM_PROMPT = """\
You are an expert HTML styler. 
Your objective is to convert the provided Markdown blog post into a clean, well-structured HTML format suitable for platforms like Tistory or Medium.

CRITICAL INSTRUCTIONS:
1. Use semantic HTML5 tags (e.g., <h1>, <h2>, <p>, <ul>, <li>, <strong>).
2. Ensure the output is a standalone HTML string that can be easily copy-pasted.
3. Do not include <html>, <head>, or <body> tags. Just the content blocks.
4. Maintain the professional "Senior SVB Architect" tone and Korean language from the original post.
5. Return the HTML as a plain string.
"""
    return Agent(
        name="html_styler_agent",
        description="Expert HTML styler. Converts Markdown blog posts to clean HTML for Tistory/Medium.",
        instruction=HTML_SYSTEM_PROMPT,
        tools=[],
        model="gemini-2.5-flash",
    )

async def async_main(prompt: str) -> None:
    """프롬프트를 처리하여 WebSearch, Blog Writer 및 HTML Styler 에이전트를 조율한다."""
    
    # SubAgents 생성
    websearch_subagent = await create_websearch_subagent()
    blog_writer_subagent = create_blog_writer_subagent()
    html_styler_subagent = create_html_styler_subagent()

    # ── 3. Main Orchestrator Agent ─────────────────────────────────────────
    MAIN_SYSTEM_PROMPT = """\
You are a procedural coordinator agent. Your ONLY job is to execute a step-by-step workflow by transferring control to subagents and calling tools.
DO NOT perform the research or writing yourself. You MUST delegate to the assigned subagents.

WORKFLOW:
1. **Research**: Call `websearch_agent` to gather information on the user's topic. Transfer execution to `websearch_agent`.
2. **Draft**: Once research is complete, call `blog_writer_agent`, passing the ENTIRE research output as input. Transfer execution to `blog_writer_agent`.
3. **Format**: Once the draft is complete, call `html_styler_agent` to convert that Markdown into HTML. Transfer execution to `html_styler_agent`.
4. **Save**: Call the `write_file` tool to save the resulting HTML. The filename MUST be based on the blog title (e.g., `ai_coding_trends.html`).
5. **Output**: Finally, display the exact HTML content returned by the styler as your final response to the user.

## @rules
1. Proceed through all steps without asking for further clarification.
2. Do not summarize or alter the outputs from subagents; pass them through verbatim to the next step.
3. You are finished ONLY after Step 5.
"""
    
    main_agent = Agent(
        name="coordinator_agent",
        description="Coordinates the web search, blog writing, and HTML styling workflow.",
        instruction=MAIN_SYSTEM_PROMPT,
        tools=[write_file_tool],
        sub_agents=[websearch_subagent, blog_writer_subagent, html_styler_subagent],
        model="gemini-2.5-flash",
    )

    runner = InMemoryRunner(agent=main_agent)
    
    # ── 4. 에이전트 오케스트레이션 실행 ───────────────────────────────────────
    print(f"\n[Orchestrating Workflow for]: \"{prompt}\"")
    print("─" * 60)

    try:
        from google.genai import types
        
        await runner.session_service.create_session(
            app_name=runner.app_name, 
            user_id="user_1", 
            session_id="orchestrator_thread_1"
        )
        
        async for event in runner.run_async(
            user_id="user_1",
            session_id="orchestrator_thread_1",
            new_message=types.Content(parts=[types.Part.from_text(text=prompt)])
        ):
            if event.is_final_response():
                 if event.content and event.content.parts:
                      for part in event.content.parts:
                          if part.text:
                              print(part.text)
                     
    except Exception as e:
        print(f"\n[Error] Failed during agent invocation: {e}")
    finally:
        await runner.close()

def main() -> None:
    if len(sys.argv) < 2:
        print("Usage: python adk.py <prompt>")
        print('Example: python adk.py "Agentic AI의 핵심 원리와 실무 적용"')
        sys.exit(1)

    prompt = " ".join(sys.argv[1:])
    asyncio.run(async_main(prompt))

if __name__ == "__main__":
    main()