๐Ÿ” OpenAI SDK๋ฅผ ํ™œ์šฉํ•œ Deep Research ํŒจํ„ด ๊ตฌํ˜„

twonezeroยท2026๋…„ 2์›” 2์ผ

๐Ÿ“ ์„œ๋ก 

๋น„๋™๊ธฐ ํ”„๋กœ๊ทธ๋ž˜๋ฐ๊ณผ AI ์—์ด์ „ํŠธ๋ฅผ ๊ฒฐํ•ฉํ•˜์—ฌ ์‹ฌ์ธต ๊ฒ€์ƒ‰(Deep Research) ์‹œ์Šคํ…œ์„ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ •๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด ๊ธ€์—์„œ๋Š” OpenAI SDK์˜ Agent ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ํ™œ์šฉํ•˜์—ฌ Plan โ†’ Search โ†’ Report 3๋‹จ๊ณ„ ํŒŒ์ดํ”„๋ผ์ธ์„ ๊ตฌ์„ฑํ•˜๋Š” ์‹ค์ œ ๊ตฌํ˜„ ๋ฐฉ๋ฒ•์„ ๋‹ค๋ฃน๋‹ˆ๋‹ค.

ํ•ต์‹ฌ์€ Python์˜ asyncio๋ฅผ ํ†ตํ•œ ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ์™€ Pydantic ๊ธฐ๋ฐ˜ Structured Output์„ ํ†ตํ•œ ์‘๋‹ต ์ œ์–ด์ž…๋‹ˆ๋‹ค.


๐Ÿงฉ AsyncIO์™€ ์ฝ”๋ฃจํ‹ด์˜ ์ดํ•ด

AsyncIO ๊ธฐ๋ณธ ๊ฐœ๋…

async def๋กœ ์ •์˜๋œ ํ•จ์ˆ˜๋Š” ๋น„๋™๊ธฐ ํ•จ์ˆ˜๋กœ, await ํ‚ค์›Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋น„๋™๊ธฐ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ํ•จ์ˆ˜๋Š” ์ฝ”๋ฃจํ‹ด(Coroutine)์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

async def example_async_function():
    result = await some_async_operation()
    return result

์ฝ”๋ฃจํ‹ด(Coroutine)์ด๋ž€?

  • ์ผ๋ฐ˜ ํ•จ์ˆ˜์™€ ๋น„์Šทํ•˜์ง€๋งŒ, await ํ‚ค์›Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‹คํ–‰์„ ์ผ์‹œ ์ค‘์ง€ํ•˜๊ณ  ๋‚˜์ค‘์— ๋‹ค์‹œ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ํ˜ธ์ถœ ์‹œ coroutine object๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
  • Event Loop๊ฐ€ ์ฝ”๋ฃจํ‹ด์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

Event Loop์˜ ์—ญํ• 

Event Loop๋Š” ๋น„๋™๊ธฐ ์ž‘์—…์„ ์Šค์ผ€์ค„๋งํ•˜๊ณ  ์‹คํ–‰ํ•˜๋Š” ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค. ํŠน์ • coroutine์ด waiting ์ƒํƒœ๋ผ๋ฉด ๋‹ค๋ฅธ coroutine์„ ์‹คํ–‰ํ•˜์—ฌ ํšจ์œจ์ ์ธ ๋™์‹œ์„ฑ(concurrency)์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ’ก
Event Loop๋Š” ๋‹จ์ผ ์Šค๋ ˆ๋“œ ๋‚ด์—์„œ ์—ฌ๋Ÿฌ ์ž‘์—…์„ ๋™์‹œ์— ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ํ•ต์‹ฌ ๋ฉ”์ปค๋‹ˆ์ฆ˜์ž…๋‹ˆ๋‹ค. I/O bound ์ž‘์—…์—์„œ ํŠนํžˆ ํšจ๊ณผ์ ์ž…๋‹ˆ๋‹ค.


๐Ÿค– OpenAI SDK Agent ํ”„๋ ˆ์ž„์›Œํฌ

Agent์™€ Tool ๋“ฑ๋ก

OpenAI SDK๋Š” AI ์—์ด์ „ํŠธ๋ฅผ ์‰ฝ๊ฒŒ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. @function_tool ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ํ†ตํ•ด Python ํ•จ์ˆ˜๋ฅผ tool๋กœ ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

from agents import Agent, function_tool

@function_tool
def my_tool_function(param: str) -> str:
    """๋„๊ตฌ ์„ค๋ช…"""
    return f"๊ฒฐ๊ณผ: {param}"

agent = Agent(
    name="MyAgent",
    instructions="๋‹น์‹ ์˜ ์—ญํ• ์€...",
    tools=[my_tool_function],
    model="gpt-4o-mini"
)

Agent๋ฅผ Tool๋กœ ๋“ฑ๋ก

Agent ์ž์ฒด๋„ ๋‹ค๋ฅธ Agent์˜ tool๋กœ ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” as_tool() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

agent1.as_tool(
    tool_name="sales_agent1", 
    tool_description="์˜์—… ๊ด€๋ จ ์งˆ๋ฌธ์— ๋‹ต๋ณ€ํ•˜๋Š” ์—์ด์ „ํŠธ"
)

Handoffs: Agent ๊ฐ„ ๋Œ€ํ™” ๊ด€๋ฆฌ

Handoffs๋Š” Agent ๊ฐ„์˜ ์ž‘์—… ์ „๋‹ฌ์„ ๊ด€๋ฆฌํ•˜๋Š” ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค. ํŠน์ • ์—ญํ• ์„ ๊ฐ€์ง„ Agent์—๊ฒŒ ์ž‘์—…์„ ์ „๋‹ฌํ•˜๊ณ , ํ•ด๋‹น Agent์˜ ์ž‘์—…์ด ๋๋‚˜๋ฉด ๋‹ค๋ฅธ Agent์—๊ฒŒ ์ž‘์—…์„ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

handoffs_description ๋งค๊ฐœ๋ณ€์ˆ˜์— ์—์ด์ „ํŠธ์˜ ์—ญํ•  ๋˜๋Š” ์ž„๋ฌด๋ฅผ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.


๐Ÿ›ก๏ธ Guardrails ํŒจํ„ด

Guardrail์„ Agent๋กœ ๊ตฌํ˜„

๊ฐ€๋“œ๋ ˆ์ผ ์ž์ฒด๋ฅผ Agent๋กœ ๊ตฌํ˜„ํ•˜์—ฌ ์ž…๋ ฅ ๊ฒ€์ฆ ๋กœ์ง์„ ๊ตฌ์กฐํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

from pydantic import BaseModel
from agents import Agent, input_guardrail, Runner, GuardrailFunctionOutput

class NameCheckOutput(BaseModel):
    is_name_in_message: bool
    name: str

guardrail_agent = Agent( 
    name="Name check",
    instructions="Check if the user is including someone's personal name in what they want you to do.",
    output_type=NameCheckOutput,  # Structured output
    model="gpt-4o-mini"
)

@input_guardrail
async def guardrail_against_name(ctx, agent, message):
    result = await Runner.run(guardrail_agent, message, context=ctx.context)
    is_name_in_message = result.final_output.is_name_in_message
    return GuardrailFunctionOutput(
        output_info={"found_name": result.final_output},
        tripwire_triggered=is_name_in_message
    )

๐Ÿ’ก
Pydantic ๋ชจ๋ธ์„ output_type์œผ๋กœ ์ง€์ •ํ•˜๋ฉด, Agent๊ฐ€ ์ž์œ  ํ˜•์‹์ด ์•„๋‹Œ ์ •ํ•ด์ง„ ๊ตฌ์กฐ๋กœ๋งŒ ์‘๋‹ตํ•˜๋„๋ก ๊ฐ•์ œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ๊ฐ•๋ ฅํ•œ ๊ฐ€๋“œ๋ ˆ์ผ ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.


๐Ÿ”ฌ Deep Research ๊ตฌํ˜„ ํŒจํ„ด

Deep Research๋Š” OpenAI SDK์˜ ๋‚ด์žฅ WebSearchTool๊ณผ Agent์˜ Structured Output์„ ํ™œ์šฉํ•˜์—ฌ ์‹ฌ์ธต ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๋Š” ํŒจํ„ด์ž…๋‹ˆ๋‹ค.

์ „์ฒด ์•„ํ‚คํ…์ฒ˜: Plan โ†’ Search โ†’ Report

graph LR
    A[์‚ฌ์šฉ์ž ์ฟผ๋ฆฌ] --> B[Planner Agent]
    B -->|๊ฒ€์ƒ‰ ๊ณ„ํš| C[Search Agent]
    C -->|๋ณ‘๋ ฌ ๊ฒ€์ƒ‰| D[๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๋“ค]
    D --> E[Writer Agent]
    E --> F[์ตœ์ข… ๋ฆฌํฌํŠธ]

๐Ÿ“‹ 1๋‹จ๊ณ„: Plan - Structured Outputs๋ฅผ ํ†ตํ•œ ๊ฒ€์ƒ‰ ๊ณ„ํš

Pydantic ๋ชจ๋ธ ์ •์˜

๊ฒ€์ƒ‰ ๊ณ„ํš์„ ๊ตฌ์กฐํ™”ํ•˜๊ธฐ ์œ„ํ•ด Pydantic ๋ชจ๋ธ์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.

from pydantic import BaseModel, Field

class WebSearchItem(BaseModel):
    reason: str = Field(description="์ด ๊ฒ€์ƒ‰์ด ํ•„์š”ํ•œ ์ด์œ ")
    query: str = Field(description="๊ฒ€์ƒ‰์–ด")

class WebSearchPlan(BaseModel):
    searches: list[WebSearchItem] = Field(description="์ˆ˜ํ–‰ํ•  ์›น ๊ฒ€์ƒ‰ ๋ชฉ๋ก")

Planner Agent ๊ตฌ์„ฑ

from agents import Agent

HOW_MANY_SEARCHES = 5

planner_agent = Agent(
    name="PlannerAgent",
    instructions=f"You are a helpful research assistant. Given a query, come up with a set of web searches to perform to best answer the query. Output {HOW_MANY_SEARCHES} terms to query for.",
    model="gpt-4o-mini",
    output_type=WebSearchPlan,  # โ† Structured Output (Format Guardrail)
)

๐Ÿ’ก
output_type์„ ์ง€์ •ํ•˜๋ฉด LLM์ด ๋ฐ˜๋“œ์‹œ ํ•ด๋‹น ์Šคํ‚ค๋งˆ์— ๋งž๋Š” JSON๋งŒ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ํŒŒ์‹ฑ ์˜ค๋ฅ˜๋ฅผ ์›์ฒœ ์ฐจ๋‹จํ•˜๋Š” ๊ฐ•๋ ฅํ•œ ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.

์‹ค์ œ ์‚ฌ์šฉ ์˜ˆ์‹œ

result = await Runner.run(planner_agent, f"Query: {query}")
search_plan = result.final_output_as(WebSearchPlan)
print(f"Will perform {len(search_plan.searches)} searches")

๐ŸŒ 2๋‹จ๊ณ„: Search - ๋ณ‘๋ ฌ ๊ฒ€์ƒ‰ ์‹คํ–‰

OpenAI WebSearchTool ํ™œ์šฉ

Search Agent๋Š” OpenAI์˜ ๋‚ด์žฅ WebSearchTool์„ ์‚ฌ์šฉํ•˜์—ฌ ์‹ค์ œ ์›น ๊ฒ€์ƒ‰์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

from agents import Agent, WebSearchTool

search_agent = Agent(
    name="Search agent",
    instructions="๊ฒ€์ƒ‰์–ด์— ๋Œ€ํ•ด 2-3๋ฌธ๋‹จ์œผ๋กœ ์š”์•ฝํ•˜์„ธ์š”.",
    tools=[WebSearchTool(search_context_size="low")],  # OpenAI ๋‚ด์žฅ ๊ฒ€์ƒ‰ ํˆด
    model="gpt-4o-mini",
)

AsyncIO๋ฅผ ํ†ตํ•œ ๋ณ‘๋ ฌ ๊ฒ€์ƒ‰ ๊ตฌํ˜„

์—ฌ๋Ÿฌ ๊ฒ€์ƒ‰์–ด๋ฅผ ๋ณ‘๋ ฌ(Parallel)๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด Deep Research์˜ ํ•ต์‹ฌ์ž…๋‹ˆ๋‹ค. asyncio.create_task์™€ asyncio.gather๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

import asyncio

async def perform_searches(search_plan: WebSearchPlan) -> list[str]:
    """๊ฒ€์ƒ‰ ๊ณ„ํš์˜ ๋ชจ๋“  ๊ฒ€์ƒ‰์„ ๋ณ‘๋ ฌ๋กœ ์‹คํ–‰"""
    # ๊ฐ ๊ฒ€์ƒ‰ ์•„์ดํ…œ์„ Task๋กœ ์ƒ์„ฑ
    tasks = [
        asyncio.create_task(search(item)) 
        for item in search_plan.searches
    ]
    # ๋ณ‘๋ ฌ๋กœ ์‹คํ–‰ ๋ฐ ๊ฒฐ๊ณผ ์ˆ˜์ง‘
    results = await asyncio.gather(*tasks)
    return results

async def search(item: WebSearchItem) -> str:
    """๊ฐœ๋ณ„ ๊ฒ€์ƒ‰ ์ˆ˜ํ–‰"""
    input_text = f"Search term: {item.query}\nReason for searching: {item.reason}"
    result = await Runner.run(search_agent, input_text)
    return str(result.final_output)

๐Ÿ’ก
asyncio.gather(*tasks)๋Š” ๋ชจ๋“  Task๋ฅผ ๋ณ‘๋ ฌ๋กœ ์‹คํ–‰ํ•˜๊ณ , ๋ชจ๋“  ๊ฒฐ๊ณผ๊ฐ€ ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฝ๋‹ˆ๋‹ค. 5๊ฐœ์˜ ๊ฒ€์ƒ‰์„ ์ˆœ์ฐจ์ ์œผ๋กœ ํ•˜๋ฉด 5๋ฐฐ์˜ ์‹œ๊ฐ„์ด ๊ฑธ๋ฆฌ์ง€๋งŒ, ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ๋กœ ํฐ ์„ฑ๋Šฅ ํ–ฅ์ƒ์„ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์‹ค์ œ ๊ตฌํ˜„: ์ง„ํ–‰ ์ƒํ™ฉ ์ถ”์ 

์‹ค์ œ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” asyncio.as_completed๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์™„๋ฃŒ๋˜๋Š” ๊ฒ€์ƒ‰๋ถ€ํ„ฐ ์ฒ˜๋ฆฌํ•˜๊ณ  ์ง„ํ–‰ ์ƒํ™ฉ์„ ์ถ”์ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

async def perform_searches(self, search_plan: WebSearchPlan) -> list[str]:
    """๊ฒ€์ƒ‰์„ ๋ณ‘๋ ฌ๋กœ ์ˆ˜ํ–‰ํ•˜๊ณ  ์ง„ํ–‰ ์ƒํ™ฉ์„ ์ถ”์ """
    num_completed = 0
    tasks = [
        asyncio.create_task(self.search(item)) 
        for item in search_plan.searches
    ]

    results = []
    for task in asyncio.as_completed(tasks):
        result = await task
        if result is not None:
            results.append(result)
        num_completed += 1
        print(f"Searching... {num_completed}/{len(tasks)} completed")
    
    return results

โœ๏ธ 3๋‹จ๊ณ„: Report - ์ตœ์ข… ๋ณด๊ณ ์„œ ์ž‘์„ฑ

Writer Agent ๊ตฌ์„ฑ

๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๋ฅผ ์ข…ํ•ฉํ•˜์—ฌ ์ตœ์ข… ๋ฆฌํฌํŠธ๋ฅผ ์ž‘์„ฑํ•˜๋Š” Agent์ž…๋‹ˆ๋‹ค.

from pydantic import BaseModel, Field

class ReportData(BaseModel):
    short_summary: str = Field(
        description="A short 2-3 sentence summary of the findings."
    )
    markdown_report: str = Field(description="The final report")
    follow_up_questions: list[str] = Field(
        description="Suggested topics to research further"
    )

writer_agent = Agent(
    name="WriterAgent",
    instructions=(
        "You are a senior researcher tasked with writing a cohesive report for a research query. "
        "You will be provided with the original query, and some initial research done by a research assistant.\n"
        "You should first come up with an outline for the report that describes the structure and "
        "flow of the report. Then, generate the report and return that as your final output.\n"
        "The final output should be in markdown format, and it should be lengthy and detailed. Aim "
        "for 5-10 pages of content, at least 1000 words.\n"
        "๋ฌด์กฐ๊ฑด ํ•œ๊ตญ์–ด๋กœ ์ž‘์„ฑํ•ด์ค˜."
    ),
    model="gpt-4o-mini",
    output_type=ReportData,
)

๋ณด๊ณ ์„œ ์ƒ์„ฑ

async def write_report(query: str, search_results: list[str]) -> ReportData:
    """๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์ตœ์ข… ๋ฆฌํฌํŠธ ์ž‘์„ฑ"""
    input_text = f"Original query: {query}\nSummarized search results: {search_results}"
    result = await Runner.run(writer_agent, input_text)
    return result.final_output_as(ReportData)

๐Ÿ—๏ธ ์ „์ฒด ์‹œ์Šคํ…œ ํ†ตํ•ฉ: ResearchManager

๋ชจ๋“  ๋‹จ๊ณ„๋ฅผ ํ†ตํ•ฉํ•˜๋Š” ResearchManager ํด๋ž˜์Šค์ž…๋‹ˆ๋‹ค.

class ResearchManager:
    def __init__(self, api_key: str):
        """Initialize the ResearchManager with an OpenAI API key"""
        self.api_key = api_key

    async def run(self, query: str):
        """Run the deep research process, yielding the status updates and the final report"""
        # 1. Planning phase
        search_plan = await self.plan_searches(query)
        
        # 2. Searching phase
        search_results = await self.perform_searches(search_plan)
        
        # 3. Writing phase
        report = await self.write_report(query, search_results)
        
        return report

API Key ๊ด€๋ฆฌ ํŒจํ„ด

Gradio app ์„ ํ—ˆ๊น…ํŽ˜์ด์Šค์— ์—…๋กœ๋“œํ•˜๊ธฐ ์œ„ํ•ด ๋™์ ์œผ๋กœ API Key๋ฅผ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ์ž„์‹œ๋กœ ์„ค์ •ํ•˜๋Š” ํŒจํ„ด์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

async def plan_searches(self, query: str) -> WebSearchPlan:
    """Plan the searches to perform for the query"""
    original_key = os.environ.get("OPENAI_API_KEY")
    os.environ["OPENAI_API_KEY"] = self.api_key
    try:
        result = await Runner.run(planner_agent, f"Query: {query}")
        return result.final_output_as(WebSearchPlan)
    finally:
        # Restore original key
        if original_key:
            os.environ["OPENAI_API_KEY"] = original_key
        else:
            os.environ.pop("OPENAI_API_KEY", None)

๐Ÿ’ก
API Key๋ฅผ ๋‹ค๋ฃฐ ๋•Œ๋Š” ๋ฐ˜๋“œ์‹œ try-finally ๋ธ”๋ก์„ ์‚ฌ์šฉํ•˜์—ฌ ์›๋ž˜ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ๋ณต์›ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ๋‹ค๋ฅธ ์ฝ”๋“œ์—์„œ ์ž˜๋ชป๋œ API Key๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


๐Ÿ–ฅ๏ธ Gradio UI ํ†ตํ•ฉ

์‹ค์‹œ๊ฐ„ ์ƒํƒœ ์—…๋ฐ์ดํŠธ๋ฅผ ์ œ๊ณตํ•˜๋Š” Gradio UI๋ฅผ ๊ตฌ์ถ•ํ–ˆ์Šต๋‹ˆ๋‹ค.

import gradio as gr

async def run(query: str, api_key: str):
    """Run research and yield status updates"""
    # Validate API key
    if not api_key or not api_key.strip():
        yield ("", "โŒ **Error: Please provide a valid OpenAI API key**", "")
        return

    # Clear query box and show initial status
    yield ("", "๐Ÿš€ **Starting research...**", "")

    status_text = ""
    final_report = ""

    async for chunk in ResearchManager(api_key).run(query):
        # Check if this chunk contains the final report
        if "---" in chunk:
            parts = chunk.split("## ๐Ÿ“Š Research Report")
            if len(parts) == 2:
                status_text = parts[0]
                final_report = "## ๐Ÿ“Š Research Report" + parts[1]
                yield ("", status_text, final_report)
        else:
            status_text = chunk
            yield ("", status_text, final_report)

CSS ์• ๋‹ˆ๋ฉ”์ด์…˜

.status-container {
    animation: fadeBlur 1.5s ease-in-out infinite;
}

@keyframes fadeBlur {
    0%, 100% {
        opacity: 1;
        filter: blur(0px);
    }
    50% {
        opacity: 0.6;
        filter: blur(0.5px);
    }
}

๐ŸŽฏ ํ•ต์‹ฌ ์„ค๊ณ„ ์›์น™

1. Separation of Concerns

  • Plan, Search, Report ๊ฐ ๋‹จ๊ณ„๋ฅผ ๋…๋ฆฝ์ ์ธ Agent๋กœ ๋ถ„๋ฆฌํ•˜์—ฌ ๊ด€์‹ฌ์‚ฌ๋ฅผ ๋ช…ํ™•ํžˆ ๊ตฌ๋ถ„ํ–ˆ์Šต๋‹ˆ๋‹ค.

2. Structured Output์„ ํ†ตํ•œ ์‹ ๋ขฐ์„ฑ ํ™•๋ณด

  • Pydantic ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•˜์—ฌ Agent์˜ ์ถœ๋ ฅ ํ˜•์‹์„ ๊ฐ•์ œํ•จ์œผ๋กœ์จ, ํŒŒ์‹ฑ ์˜ค๋ฅ˜์™€ ์˜ˆ์ธก ๋ถˆ๊ฐ€๋Šฅํ•œ ์‘๋‹ต์„ ๋ฐฉ์ง€ํ–ˆ์Šต๋‹ˆ๋‹ค.

3. AsyncIO๋ฅผ ํ†ตํ•œ ์„ฑ๋Šฅ ์ตœ์ ํ™”

  • ๋ณ‘๋ ฌ ๊ฒ€์ƒ‰ ์ฒ˜๋ฆฌ๋กœ ์ „์ฒด ์‹คํ–‰ ์‹œ๊ฐ„์„ ํฌ๊ฒŒ ๋‹จ์ถ•ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ˆœ์ฐจ ์ฒ˜๋ฆฌ ๋Œ€๋น„ N๋ฐฐ์˜ ์„ฑ๋Šฅ ํ–ฅ์ƒ์„ ๋‹ฌ์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค.

4. ์—๋Ÿฌ ์ฒ˜๋ฆฌ์™€ ๋ณต์›๋ ฅ

  • API Key ๊ด€๋ฆฌ, ์˜ˆ์™ธ ์ฒ˜๋ฆฌ, ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๋ณต์› ๋“ฑ์„ ํ†ตํ•ด ์‹œ์Šคํ…œ์˜ ์•ˆ์ •์„ฑ์„ ํ™•๋ณดํ–ˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ’ก ๊ฒฐ๋ก 

OpenAI SDK์˜ Agent ํ”„๋ ˆ์ž„์›Œํฌ์™€ Python AsyncIO๋ฅผ ๊ฒฐํ•ฉํ•˜์—ฌ ํšจ์œจ์ ์ธ Deep Research ์‹œ์Šคํ…œ์„ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค.
ํ•ต์‹ฌ์€ ๋‹ค์Œ ์„ธ ๊ฐ€์ง€์ž…๋‹ˆ๋‹ค:

  1. Structured Output์„ ํ†ตํ•œ ์ œ์–ด ๊ฐ€๋Šฅ์„ฑ: Pydantic ๋ชจ๋ธ๋กœ LLM ์‘๋‹ต์„ ๊ตฌ์กฐํ™”
  2. ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ๋ฅผ ํ†ตํ•œ ์„ฑ๋Šฅ: AsyncIO๋กœ ์—ฌ๋Ÿฌ ๊ฒ€์ƒ‰์„ ๋™์‹œ์— ์‹คํ–‰
  3. ๋ช…ํ™•ํ•œ ๋‹จ๊ณ„ ๋ถ„๋ฆฌ: Plan โ†’ Search โ†’ Report์˜ 3๋‹จ๊ณ„ ํŒŒ์ดํ”„๋ผ์ธ

์ด ํŒจํ„ด์€ ๋‹จ์ˆœํ•œ Q&A๋ฅผ ๋„˜์–ด์„œ, ๋…ผ๋ฆฌ์ ์ด๊ณ  ์ฒด๊ณ„์ ์ธ ์‹ฌ์ธต ๋ฆฌ์„œ์น˜ ๊ฒฐ๊ณผ๋ฅผ ์ œ๊ณตํ•˜๋Š” AI ์‹œ์Šคํ…œ์„ ๊ตฌ์ถ•ํ•˜๋Š” ๋ฐ ํšจ๊ณผ์ ์ž…๋‹ˆ๋‹ค.


๐Ÿ“š ์ฐธ๊ณ  ์‚ฌํ•ญ

OpenAI SDK ์„ค๋ช…

profile
I Enjoy Learn-and-Run Vibe๐Ÿ˜Š

0๊ฐœ์˜ ๋Œ“๊ธ€