Post

[AI] ๐Ÿค CrewAI: ๋ฉ€ํ‹ฐ ์—์ด์ „ํŠธ AI ํ˜‘์—… ํ”„๋ ˆ์ž„์›Œํฌ ์™„๋ฒฝ ๊ฐ€์ด๋“œ

CrewAI๋Š” ์—ฌ๋Ÿฌ AI Agent๊ฐ€ ์—ญํ• ์„ ๋ถ„๋‹ดํ•˜๊ณ  ํ˜‘๋ ฅํ•˜๋Š” ๋ฉ€ํ‹ฐ ์—์ด์ „ํŠธ ํ”„๋ ˆ์ž„์›Œํฌ์ž…๋‹ˆ๋‹ค. AgentยทTaskยทToolยทCrew ๊ตฌ์„ฑ์š”์†Œ๋ถ€ํ„ฐ Sequential/Hierarchical ํ”„๋กœ์„ธ์Šค, ๋ฉ”๋ชจ๋ฆฌ ์‹œ์Šคํ…œ, Python ์ฝ”๋“œ ์˜ˆ์ œ๊นŒ์ง€ ์‹ค๋ฌด ๊ธฐ์ค€์œผ๋กœ ์ •๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค.

[AI] ๐Ÿค CrewAI: ๋ฉ€ํ‹ฐ ์—์ด์ „ํŠธ AI ํ˜‘์—… ํ”„๋ ˆ์ž„์›Œํฌ ์™„๋ฒฝ ๊ฐ€์ด๋“œ

CrewAI๋Š” ์—ฌ๋Ÿฌ AI Agent๊ฐ€ ์ธ๊ฐ„ ํŒ€์ฒ˜๋Ÿผ ์—ญํ• ์„ ๋‚˜๋ˆ„๊ณ  ํ˜‘๋ ฅํ•˜์—ฌ ๋ณต์žกํ•œ ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฉ€ํ‹ฐ ์—์ด์ „ํŠธ ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜ ํ”„๋ ˆ์ž„์›Œํฌ์ž…๋‹ˆ๋‹ค. ๋‹จ์ผ LLM ํ˜ธ์ถœ๋กœ๋Š” ํ•ด๊ฒฐํ•˜๊ธฐ ์–ด๋ ค์šด ๋ฆฌ์„œ์น˜ยท์ž‘์„ฑยท๋ถ„์„ยท์ฝ”๋”ฉ ๋“ฑ ๋‹ค๋‹จ๊ณ„ ์ž‘์—…์„ Agent ํŒ€์ด ๋ถ„์—…ํ•˜์—ฌ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ธ€์—์„œ๋Š” ํ•ต์‹ฌ ๊ตฌ์„ฑ์š”์†Œ, ํ”„๋กœ์„ธ์Šค ์œ ํ˜•, ๋ฉ”๋ชจ๋ฆฌ ์‹œ์Šคํ…œ, Python ์ฝ”๋“œ ์˜ˆ์ œ๋ฅผ ์‹ค๋ฌด ๊ธฐ์ค€์œผ๋กœ ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค.


๐Ÿค CrewAI๋ž€?

CrewAI๋Š” ์—ญํ•  ๊ธฐ๋ฐ˜ AI Agent ํ˜‘์—… ํ”Œ๋žซํผ์ž…๋‹ˆ๋‹ค. ๊ฐ Agent๋Š” ํŠน์ • ์ „๋ฌธ ์—ญํ• (๋ฆฌ์„œ์ฒ˜, ์ž‘๊ฐ€, ๋ถ„์„๊ฐ€ ๋“ฑ)์„ ๋ถ€์—ฌ๋ฐ›๊ณ , ๋…๋ฆฝ์ ์œผ๋กœ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๊ฑฐ๋‚˜ ๋‹ค๋ฅธ Agent์—๊ฒŒ ์œ„์ž„ํ•˜๋ฉด์„œ ํŒ€ ๋ชฉํ‘œ๋ฅผ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.

1
2
3
4
5
6
7
8
์‚ฌ์šฉ์ž ์ž…๋ ฅ (๋ชฉํ‘œ)
        โ†“
    Crew ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ดํ„ฐ
   โ”Œโ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”
Agent A    Agent B    Agent C
(๋ฆฌ์„œ์ฒ˜)   (์ž‘๊ฐ€)     (๊ฒ€์ˆ˜์ž)
   โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”˜
    ์ตœ์ข… ๊ฒฐ๊ณผ๋ฌผ
ํ•ญ๋ชฉ๋‹จ์ผ LLMCrewAI ๋ฉ€ํ‹ฐ ์—์ด์ „ํŠธ
์ž‘์—… ๋ณต์žก๋„๋‹จ์ˆœ ๋‹จ๋ฐœ์„ฑ๋‹ค๋‹จ๊ณ„ยท๋ณตํ•ฉ ์ž‘์—…
์ „๋ฌธํ™”๋ฒ”์šฉ์—ญํ• ๋ณ„ ์ „๋ฌธํ™”
์ปจํ…์ŠคํŠธ๋‹จ์ผ ๋Œ€ํ™”์—์ด์ „ํŠธ ๊ฐ„ ๊ณต์œ ยท๋ˆ„์ 
ํ™•์žฅ์„ฑ์ œํ•œ์ Agent ์ถ”๊ฐ€๋กœ ์ˆ˜ํ‰ ํ™•์žฅ

๐Ÿงฑ ํ•ต์‹ฌ ๊ตฌ์„ฑ์š”์†Œ

Agent โ€” ์ž์œจ ์ž‘์—… ๋‹จ์œ„

Agent๋Š” ํŠน์ • ์—ญํ• ๊ณผ ๋ชฉํ‘œ๋ฅผ ๊ฐ€์ง„ ์ž์œจ์  AI ์†Œํ”„ํŠธ์›จ์–ด์ž…๋‹ˆ๋‹ค.

ํŒŒ๋ผ๋ฏธํ„ฐ์„ค๋ช…
roleAgent์˜ ๊ธฐ๋Šฅ/์ง์ฑ… ์ •์˜
goal๊ฐœ๋ณ„ ๋ชฉํ‘œ๋กœ ์˜์‚ฌ๊ฒฐ์ • ๋ฐฉํ–ฅ ์ œ์‹œ
backstory๋ฐฐ๊ฒฝ ์Šคํ† ๋ฆฌ๋กœ ์—ญํ•  ๋งฅ๋ฝ ๊ฐ•ํ™”
tools์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋„๊ตฌ ๋ชฉ๋ก
llm์‚ฌ์šฉํ•  LLM (๊ธฐ๋ณธ๊ฐ’: OpenAI GPT-4)
verboseTrue ์„ค์ • ์‹œ ์‹คํ–‰ ๋กœ๊ทธ ์ถœ๋ ฅ
allow_delegationTrue๋ฉด ๋‹ค๋ฅธ Agent์—๊ฒŒ ์ž‘์—… ์œ„์ž„ ๊ฐ€๋Šฅ
memoryTrue๋ฉด ๋ฉ”๋ชจ๋ฆฌ ์‹œ์Šคํ…œ ํ™œ์„ฑํ™”
max_iter์ตœ๋Œ€ ๋ฐ˜๋ณต ํšŸ์ˆ˜ (๊ธฐ๋ณธ๊ฐ’: 15)

Task โ€” ๊ตฌ์ฒด์  ์ž‘์—… ๋‹จ์œ„

Agent๊ฐ€ ์ˆ˜ํ–‰ํ•  ๋ช…ํ™•ํ•œ ๊ณผ์ œ๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.

ํŒŒ๋ผ๋ฏธํ„ฐ์„ค๋ช…
description์ž‘์—… ๋‚ด์šฉ๊ณผ ์ˆ˜ํ–‰ ๋ฐฉ๋ฒ•
agent๋‹ด๋‹น Agent ์ง€์ •
expected_output๊ธฐ๋Œ€ ๊ฒฐ๊ณผ๋ฌผ ํ˜•์‹ ์„ค๋ช…
toolsํ•ด๋‹น Task์—๋งŒ ์‚ฌ์šฉ๋˜๋Š” ๋„๊ตฌ
context์ด์ „ Task ์ถœ๋ ฅ์„ ์ปจํ…์ŠคํŠธ๋กœ ์ „๋‹ฌ
output_file๊ฒฐ๊ณผ๋ฅผ ํŒŒ์ผ๋กœ ์ €์žฅ

Tool โ€” Agent์˜ ์™ธ๋ถ€ ๋Šฅ๋ ฅ

Agent๊ฐ€ ์‹ค์ œ ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ง‘ํ•˜๊ฑฐ๋‚˜ ์™ธ๋ถ€ ์„œ๋น„์Šค์™€ ์ƒํ˜ธ์ž‘์šฉํ•˜๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค.

1
2
3
4
5
from crewai_tools import SerperDevTool, ScrapeWebsiteTool, WebsiteSearchTool

search_tool = SerperDevTool()      # Google ๊ฒ€์ƒ‰
scrape_tool = ScrapeWebsiteTool()  # ์›น ํŽ˜์ด์ง€ ์Šคํฌ๋ž˜ํ•‘
rag_tool    = WebsiteSearchTool()  # ์›น ์ฝ˜ํ…์ธ  RAG ๊ฒ€์ƒ‰

Process โ€” ์ž‘์—… ํ๋ฆ„ ์กฐ์œจ ๋ฐฉ์‹

ํ”„๋กœ์„ธ์Šค์„ค๋ช…
Process.sequential์ž‘์—…์„ ์ˆœ์„œ๋Œ€๋กœ ์‹คํ–‰, ์•ž ๊ฒฐ๊ณผ๊ฐ€ ๋‹ค์Œ ์ปจํ…์ŠคํŠธ๊ฐ€ ๋จ
Process.hierarchical๋งค๋‹ˆ์ € Agent๊ฐ€ ์ž‘์—…์„ ํ•˜์œ„ Agent์—๊ฒŒ ์œ„์ž„ยท๊ฒ€์ˆ˜

Crew โ€” ์ตœ์ƒ์œ„ ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ดํ„ฐ

Agent์™€ Task๋ฅผ ๋ชจ์•„์„œ ์‹คํ–‰ ๊ณ„ํš์„ ๊ด€๋ฆฌํ•˜๋Š” ์ตœ์ƒ์œ„ ๊ตฌ์กฐ์ž…๋‹ˆ๋‹ค.


๐Ÿ“ฆ ์„ค์น˜

1
2
pip install crewai
pip install 'crewai[tools]'   # ๊ธฐ๋ณธ ๋„๊ตฌ ๋ชจ์Œ ํฌํ•จ

ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์„ค์ • (.env ํŒŒ์ผ):

1
2
OPENAI_API_KEY=sk-...
SERPER_API_KEY=...             # SerperDevTool ์‚ฌ์šฉ ์‹œ ํ•„์š”

๐Ÿš€ ๋น ๋ฅธ ์‹œ์ž‘: ๋ธ”๋กœ๊ทธ ๊ธ€ ์ž๋™ ์ž‘์„ฑ ์—์ด์ „ํŠธ

๋ฆฌ์„œ์ฒ˜ Agent๊ฐ€ ์ฃผ์ œ๋ฅผ ์กฐ์‚ฌํ•˜๊ณ , ์—๋””ํ„ฐ Agent๊ฐ€ ๋ธ”๋กœ๊ทธ ๊ธ€์„ ์ž‘์„ฑํ•˜๋Š” 2์ธ ํŒ€ ์˜ˆ์ œ์ž…๋‹ˆ๋‹ค.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import os
from dotenv import load_dotenv
from crewai import Agent, Task, Crew, Process
from crewai_tools import SerperDevTool, ScrapeWebsiteTool

load_dotenv()
os.environ["OPENAI_MODEL_NAME"] = "gpt-4o"

# โ”€โ”€ Tools โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
search_tool = SerperDevTool()
scrape_tool = ScrapeWebsiteTool()

# โ”€โ”€ Agents โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
researcher = Agent(
    role="์‹œ๋‹ˆ์–ด ๋ฆฌ์„œ์ฒ˜",
    goal="์ฃผ์–ด์ง„ ์ฃผ์ œ์— ๋Œ€ํ•ด ์›น์—์„œ ์ตœ์‹  ์ •๋ณด๋ฅผ ์ˆ˜์ง‘ํ•˜๊ณ  ํ•ต์‹ฌ ์ธ์‚ฌ์ดํŠธ๋ฅผ ์ •๋ฆฌํ•œ๋‹ค",
    backstory="๋‹ค์–‘ํ•œ ๊ธฐ์ˆ  ๋ฌธ์„œ์™€ ๋ธ”๋กœ๊ทธ๋ฅผ ๋ถ„์„ํ•˜์—ฌ ์ตœ๊ณ ์˜ ๋ฆฌ์„œ์น˜ ๊ฒฐ๊ณผ๋ฅผ ๋งŒ๋“œ๋Š” ์ „๋ฌธ๊ฐ€",
    tools=[search_tool, scrape_tool],
    allow_delegation=False,
    verbose=True,
)

editor = Agent(
    role="IT ๋ธ”๋กœ๊ฑฐ / ์—๋””ํ„ฐ",
    goal="๋ฆฌ์„œ์น˜ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์ฝ๊ธฐ ์‰ฝ๊ณ  ์‹ค์šฉ์ ์ธ ๋ธ”๋กœ๊ทธ ๊ธ€์„ ์ž‘์„ฑํ•œ๋‹ค",
    backstory="๊ฐœ๋ฐœ์ž ๋…์ž๋ฅผ ์œ„ํ•ด ๋ช…ํ™•ํ•˜๊ณ  ์œ ์ตํ•œ ๊ธฐ์ˆ  ์ฝ˜ํ…์ธ ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ์ „๋ฌธ ๋ธ”๋กœ๊ฑฐ",
    verbose=True,
)

# โ”€โ”€ Tasks โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
research_task = Task(
    description="""'{topic}'์— ๊ด€ํ•œ ์ตœ์‹  ์ •๋ณด๋ฅผ ์›น์—์„œ ๊ฒ€์ƒ‰ํ•˜๊ณ  ๋ถ„์„ํ•˜์„ธ์š”.
    ํ•ต์‹ฌ ๊ฐœ๋…, ์ฃผ์š” ๊ธฐ๋Šฅ, ์‹ค์ œ ํ™œ์šฉ ์‚ฌ๋ก€๋ฅผ ์ •๋ฆฌํ•˜์„ธ์š”.""",
    agent=researcher,
    expected_output="์ฃผ์ œ์˜ ํ•ต์‹ฌ ๋‚ด์šฉ, ์ฃผ์š” ๊ธฐ๋Šฅ, ํ™œ์šฉ ์‚ฌ๋ก€๋ฅผ ํฌํ•จํ•œ ๋ฆฌ์„œ์น˜ ๋ณด๊ณ ์„œ",
)

writing_task = Task(
    description="""๋ฆฌ์„œ์น˜ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ '{topic}'์— ๋Œ€ํ•œ ๋ธ”๋กœ๊ทธ ํฌ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š”.
    ๋„์ž…๋ถ€, ์ฃผ์š” 3๊ฐœ ์„น์…˜, ์‹ค์šฉ์  ์˜ˆ์‹œ, ๊ฒฐ๋ก ์œผ๋กœ ๊ตฌ์„ฑํ•˜์„ธ์š”.""",
    agent=editor,
    expected_output="๋งˆํฌ๋‹ค์šด ํ˜•์‹์˜ ๋ธ”๋กœ๊ทธ ํฌ์ŠคํŠธ (๋„์ž…๋ถ€, 3๊ฐœ ์„น์…˜, ๊ฒฐ๋ก  ํฌํ•จ)",
    context=[research_task],       # ๋ฆฌ์„œ์น˜ ๊ฒฐ๊ณผ๋ฅผ ์ปจํ…์ŠคํŠธ๋กœ ์ „๋‹ฌ
    output_file="output_blog.md",
)

# โ”€โ”€ Crew โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
crew = Crew(
    agents=[researcher, editor],
    tasks=[research_task, writing_task],
    process=Process.sequential,
    verbose=True,
)

result = crew.kickoff(inputs={"topic": "CrewAI ๋ฉ€ํ‹ฐ ์—์ด์ „ํŠธ ํ”„๋ ˆ์ž„์›Œํฌ"})
print(result.raw)

โš™๏ธ ํ”„๋กœ์„ธ์Šค ์œ ํ˜•

Sequential (์ˆœ์ฐจ ์ฒ˜๋ฆฌ)

์ž‘์—… ๋ชฉ๋ก ์ˆœ์„œ๋Œ€๋กœ ์‹คํ–‰ํ•˜๋ฉฐ, ์•ž Task์˜ ์ถœ๋ ฅ์ด ๋‹ค์Œ Task์˜ ์ปจํ…์ŠคํŠธ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค. ๊ฐ€์žฅ ๋‹จ์ˆœํ•˜๊ณ  ์˜ˆ์ธก ๊ฐ€๋Šฅํ•œ ํ๋ฆ„์ž…๋‹ˆ๋‹ค.

1
2
3
4
5
crew = Crew(
    agents=[researcher, editor, reviewer],
    tasks=[research_task, writing_task, review_task],
    process=Process.sequential,
)
1
2
research_task โ†’ writing_task โ†’ review_task
    (์ถœ๋ ฅ)  โ†’   (์ปจํ…์ŠคํŠธ)  โ†’   (์ปจํ…์ŠคํŠธ)

Hierarchical (๊ณ„์ธต ์ฒ˜๋ฆฌ)

๋งค๋‹ˆ์ € Agent๊ฐ€ ์ž๋™ ์ƒ์„ฑ๋˜์–ด ์ž‘์—…์„ ํ•˜์œ„ Agent์—๊ฒŒ ์œ„์ž„ํ•˜๊ณ  ๊ฒฐ๊ณผ๋ฅผ ๊ฒ€์ˆ˜ํ•ฉ๋‹ˆ๋‹ค. ๋ณต์žกํ•˜๊ณ  ๋™์ ์ธ ์›Œํฌํ”Œ๋กœ์— ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค.

1
2
3
4
5
6
7
8
from langchain_openai import ChatOpenAI

crew = Crew(
    agents=[researcher, editor, reviewer],
    tasks=[complex_task],
    process=Process.hierarchical,
    manager_llm=ChatOpenAI(model="gpt-4o"),  # ๋งค๋‹ˆ์ € LLM ์ง€์ •
)
1
2
3
4
Manager Agent (์ž๋™ ์ƒ์„ฑ)
  โ”œโ”€โ”€ researcher ์—๊ฒŒ ์กฐ์‚ฌ ์œ„์ž„
  โ”œโ”€โ”€ editor ์—๊ฒŒ ์ž‘์„ฑ ์œ„์ž„
  โ””โ”€โ”€ ๊ฒฐ๊ณผ ๊ฒ€์ˆ˜ ํ›„ ์ตœ์ข… ์Šน์ธ

๐Ÿง  ๋ฉ”๋ชจ๋ฆฌ ์‹œ์Šคํ…œ

CrewAI v1.10+ ์—์„œ๋Š” ํ†ตํ•ฉ ๋ฉ”๋ชจ๋ฆฌ ์‹œ์Šคํ…œ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. Agent๊ฐ€ ์ด์ „ ์‹คํ–‰ ๋งฅ๋ฝ์„ ๊ธฐ์–ตํ•˜๊ณ  ์ค‘๋ณต ์ž‘์—…์„ ์ค„์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ฉ”๋ชจ๋ฆฌ ์œ ํ˜•๋ฒ”์œ„์„ค๋ช…
Short-termํ˜„์žฌ Crew ์‹คํ–‰ ๋‚ด๋Œ€ํ™” ์ค‘ ์ปจํ…์ŠคํŠธ ์œ ์ง€
Long-term์‹คํ–‰ ๊ฐ„ ์˜์†๋ฒกํ„ฐ ์Šคํ† ์–ด์— ์ €์žฅ, ์žฌ์‹คํ–‰ ์‹œ ํ™œ์šฉ
Entityํ˜„์žฌ ์‹คํ–‰ ๋‚ด์–ธ๊ธ‰๋œ ์ธ๋ฌผยท์กฐ์งยท๊ฐœ๋… ์ถ”์ 
1
2
3
4
5
6
7
8
9
10
11
12
researcher = Agent(
    role="๋ฆฌ์„œ์ฒ˜",
    goal="์ •๋ณด ์ˆ˜์ง‘",
    backstory="...",
    memory=True,           # ๋ฉ”๋ชจ๋ฆฌ ํ™œ์„ฑํ™”
)

crew = Crew(
    agents=[researcher],
    tasks=[task],
    memory=True,           # Crew ๋ ˆ๋ฒจ ๋ฉ”๋ชจ๋ฆฌ ํ™œ์„ฑํ™”
)

๐Ÿ”ง ์ปค์Šคํ…€ Tool ๊ตฌํ˜„

@tool ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋กœ Python ํ•จ์ˆ˜๋ฅผ Agent๊ฐ€ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋„๊ตฌ๋กœ ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from crewai.tools import tool

@tool('naver_news_search')
def search_naver_news(query: str) -> str:
    """๋„ค์ด๋ฒ„ ๋‰ด์Šค API์—์„œ ์ตœ์‹  ๊ธฐ์‚ฌ URL์„ ๊ฒ€์ƒ‰ํ•ฉ๋‹ˆ๋‹ค."""
    import requests
    # ์‹ค์ œ API ํ˜ธ์ถœ ๊ตฌํ˜„
    response = requests.get(
        "https://openapi.naver.com/v1/search/news.json",
        params={"query": query, "display": 5},
        headers={
            "X-Naver-Client-Id": os.getenv("NAVER_CLIENT_ID"),
            "X-Naver-Client-Secret": os.getenv("NAVER_CLIENT_SECRET"),
        }
    )
    items = response.json().get("items", [])
    return "\n".join(item["link"] for item in items)

Tip: docstring์ด Tool์˜ ์„ค๋ช…์œผ๋กœ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. Agent๊ฐ€ ์–ธ์ œ ์ด Tool์„ ์จ์•ผ ํ•˜๋Š”์ง€ ๋ช…ํ™•ํžˆ ์ž‘์„ฑํ•˜๋ฉด ํ˜ธ์ถœ ์ •ํ™•๋„๊ฐ€ ๋†’์•„์ง‘๋‹ˆ๋‹ค.


๐Ÿ“‹ YAML ์„ค์ • ๋ฐฉ์‹ (๋Œ€๊ทœ๋ชจ ํ”„๋กœ์ ํŠธ ๊ถŒ์žฅ)

Agent์™€ Task ์ •์˜๋ฅผ YAML ํŒŒ์ผ๋กœ ๋ถ„๋ฆฌํ•˜๋ฉด ์ฝ”๋“œ์™€ ์„ค์ •์„ ๋ถ„๋ฆฌํ•˜์—ฌ ๊ด€๋ฆฌํ•˜๊ธฐ ์‰ฝ์Šต๋‹ˆ๋‹ค.

config/agents.yaml:

1
2
3
4
5
6
7
8
9
researcher:
  role: ์‹œ๋‹ˆ์–ด ๋ฆฌ์„œ์ฒ˜
  goal: ์ฃผ์–ด์ง„ ์ฃผ์ œ์˜ ์ตœ์‹  ์ •๋ณด๋ฅผ ์ˆ˜์ง‘ํ•˜๊ณ  ํ•ต์‹ฌ ์ธ์‚ฌ์ดํŠธ๋ฅผ ์ •๋ฆฌํ•œ๋‹ค
  backstory: ๋‹ค์–‘ํ•œ ๊ธฐ์ˆ  ๋ฌธ์„œ๋ฅผ ๋ถ„์„ํ•˜์—ฌ ์ตœ๊ณ ์˜ ๋ฆฌ์„œ์น˜ ๊ฒฐ๊ณผ๋ฅผ ๋งŒ๋“œ๋Š” ์ „๋ฌธ๊ฐ€

editor:
  role: IT ๋ธ”๋กœ๊ฑฐ ์—๋””ํ„ฐ
  goal: ๋ฆฌ์„œ์น˜ ๊ฒฐ๊ณผ๋ฅผ ์ฝ๊ธฐ ์‰ฌ์šด ๋ธ”๋กœ๊ทธ ๊ธ€๋กœ ์ž‘์„ฑํ•œ๋‹ค
  backstory: ๊ฐœ๋ฐœ์ž ๋…์ž๋ฅผ ์œ„ํ•ด ๋ช…ํ™•ํ•œ ๊ธฐ์ˆ  ์ฝ˜ํ…์ธ ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ์ „๋ฌธ ๋ธ”๋กœ๊ฑฐ

config/tasks.yaml:

1
2
3
4
5
6
7
8
9
10
11
research_task:
  description: "{topic}"์— ๊ด€ํ•œ ์ตœ์‹  ์ •๋ณด๋ฅผ ์ˆ˜์ง‘ํ•˜๊ณ  ํ•ต์‹ฌ ๋‚ด์šฉ์„ ์ •๋ฆฌํ•˜์„ธ์š”.
  expected_output: ํ•ต์‹ฌ ๊ฐœ๋…, ์ฃผ์š” ๊ธฐ๋Šฅ, ํ™œ์šฉ ์‚ฌ๋ก€๋ฅผ ํฌํ•จํ•œ ๋ฆฌ์„œ์น˜ ๋ณด๊ณ ์„œ
  agent: researcher

writing_task:
  description: ๋ฆฌ์„œ์น˜ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ "{topic}" ๋ธ”๋กœ๊ทธ ํฌ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š”.
  expected_output: ๋งˆํฌ๋‹ค์šด ๋ธ”๋กœ๊ทธ ํฌ์ŠคํŠธ
  agent: editor
  context:
    - research_task

crew.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from crewai import Agent, Task, Crew, Process
from crewai.project import CrewBase, agent, task, crew

@CrewBase
class BlogCrew:
    agents_config = 'config/agents.yaml'
    tasks_config  = 'config/tasks.yaml'

    @agent
    def researcher(self) -> Agent:
        return Agent(config=self.agents_config['researcher'], tools=[search_tool])

    @agent
    def editor(self) -> Agent:
        return Agent(config=self.agents_config['editor'])

    @task
    def research_task(self) -> Task:
        return Task(config=self.tasks_config['research_task'])

    @task
    def writing_task(self) -> Task:
        return Task(config=self.tasks_config['writing_task'])

    @crew
    def crew(self) -> Crew:
        return Crew(
            agents=self.agents,
            tasks=self.tasks,
            process=Process.sequential,
        )

๐Ÿ—๏ธ ์‹ค์ „ ์˜ˆ์ œ: ๋‰ด์Šค ๋ฆฌ์„œ์น˜ ๋ฉ€ํ‹ฐ ์—์ด์ „ํŠธ

๊ฒ€์ƒ‰ โ†’ ๋‚ด์šฉ ์ถ”์ถœ โ†’ ์š”์•ฝ ์‘๋‹ต์„ 3๊ฐœ Agent๊ฐ€ ๋ถ„๋‹ดํ•˜๋Š” ํŒŒ์ดํ”„๋ผ์ธ์ž…๋‹ˆ๋‹ค.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
from crewai import Agent, Task, Crew, Process
from crewai.tools import tool

@tool('fetch_article')
def fetch_article(url: str) -> str:
    """์ฃผ์–ด์ง„ URL์˜ ๊ธฐ์‚ฌ ๋ณธ๋ฌธ์„ ์ถ”์ถœํ•ฉ๋‹ˆ๋‹ค."""
    from newspaper import Article
    article = Article(url)
    article.download()
    article.parse()
    return article.text

# Agents
searcher = Agent(
    role="๋‰ด์Šค ๊ฒ€์ƒ‰ ์ „๋ฌธ๊ฐ€",
    goal="์‚ฌ์šฉ์ž ์งˆ๋ฌธ์— ๋งž๋Š” ์ตœ์‹  ๋‰ด์Šค URL์„ ์ˆ˜์ง‘ํ•œ๋‹ค",
    backstory="๋‹ค์–‘ํ•œ ๋‰ด์Šค ์†Œ์Šค์—์„œ ์ •ํ™•ํ•œ ๊ธฐ์‚ฌ๋ฅผ ์ฐพ๋Š” ๋ฆฌ์„œ์น˜ ์ „๋ฌธ๊ฐ€",
    tools=[search_tool],
)

analyzer = Agent(
    role="์ฝ˜ํ…์ธ  ๋ถ„์„๊ฐ€",
    goal="๊ธฐ์‚ฌ URL์—์„œ ๋ณธ๋ฌธ์„ ์ถ”์ถœํ•˜๊ณ  ํ•ต์‹ฌ ๋‚ด์šฉ์„ ๋ถ„์„ํ•œ๋‹ค",
    backstory="๋ณต์žกํ•œ ๊ธฐ์‚ฌ๋ฅผ ๋น ๋ฅด๊ฒŒ ํŒŒ์•…ํ•˜๊ณ  ํ•ต์‹ฌ์„ ์ถ”์ถœํ•˜๋Š” ๋ถ„์„๊ฐ€",
    tools=[fetch_article],
)

answerman = Agent(
    role="์ตœ์ข… ์‘๋‹ต ์ž‘์„ฑ์ž",
    goal="๋ถ„์„๋œ ์ •๋ณด๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ช…ํ™•ํ•œ ๋‹ต๋ณ€์„ ์ œ๊ณตํ•œ๋‹ค",
    backstory="๋ณต์žกํ•œ ์ •๋ณด๋ฅผ ์‰ฝ๊ณ  ์ •ํ™•ํ•˜๊ฒŒ ์ „๋‹ฌํ•˜๋Š” ์ปค๋ฎค๋‹ˆ์ผ€์ด์…˜ ์ „๋ฌธ๊ฐ€",
)

# Tasks
search_task = Task(
    description="'{query}'์— ๊ด€ํ•œ ์ตœ์‹  ๋‰ด์Šค ๊ธฐ์‚ฌ URL 5๊ฐœ๋ฅผ ์ˆ˜์ง‘ํ•˜์„ธ์š”.",
    agent=searcher,
    expected_output="๊ด€๋ จ ๋‰ด์Šค ๊ธฐ์‚ฌ URL ๋ชฉ๋ก (์ตœ์†Œ 5๊ฐœ)",
)

analysis_task = Task(
    description="์ˆ˜์ง‘๋œ URL์—์„œ ๊ธฐ์‚ฌ ๋ณธ๋ฌธ์„ ์ถ”์ถœํ•˜๊ณ  ํ•ต์‹ฌ ๋‚ด์šฉ์„ ์ •๋ฆฌํ•˜์„ธ์š”.",
    agent=analyzer,
    expected_output="๊ฐ ๊ธฐ์‚ฌ์˜ ํ•ต์‹ฌ ๋‚ด์šฉ ์š”์•ฝ",
    context=[search_task],
)

answer_task = Task(
    description="๋ถ„์„ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ '{query}'์— ๋Œ€ํ•œ ์ข…ํ•ฉ ๋‹ต๋ณ€์„ ์ž‘์„ฑํ•˜์„ธ์š”.",
    agent=answerman,
    expected_output="์ถœ์ฒ˜์™€ ๊ทผ๊ฑฐ๋ฅผ ํฌํ•จํ•œ ๋ช…ํ™•ํ•œ ๋‹ต๋ณ€",
    context=[analysis_task],
)

# Crew ์‹คํ–‰
crew = Crew(
    agents=[searcher, analyzer, answerman],
    tasks=[search_task, analysis_task, answer_task],
    process=Process.sequential,
)

result = crew.kickoff(inputs={"query": "2026๋…„ AI Agent ํŠธ๋ Œ๋“œ"})
print(result.raw)

โ“ ์ž์ฃผ ๋ฌป๋Š” ์งˆ๋ฌธ

Q. CrewAI์™€ LangChain์˜ ์ฐจ์ด๋Š”?

LangChain์€ LLM ์ฒด์ด๋‹๊ณผ ๋„๊ตฌ ํ†ตํ•ฉ์„ ์œ„ํ•œ ๋ฒ”์šฉ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ž…๋‹ˆ๋‹ค. CrewAI๋Š” ๋ฉ€ํ‹ฐ ์—์ด์ „ํŠธ ํ˜‘์—… ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜์— ํŠนํ™”๋˜์–ด ์žˆ์œผ๋ฉฐ, LangChain ์œ„์—์„œ ๋™์ž‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Q. OpenAI ์™ธ์— ๋‹ค๋ฅธ LLM์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‚˜์š”?

๋„ค, llm ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ Anthropic Claude, Google Gemini, Ollama(๋กœ์ปฌ LLM) ๋“ฑ์„ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

1
2
3
4
5
6
7
8
from langchain_anthropic import ChatAnthropic

agent = Agent(
    role="๋ถ„์„๊ฐ€",
    goal="...",
    backstory="...",
    llm=ChatAnthropic(model="claude-sonnet-4-6"),
)

Q. Sequential๊ณผ Hierarchical ์ค‘ ์–ด๋–ค ๊ฑธ ์จ์•ผ ํ•˜๋‚˜์š”?

๋‹จ๊ณ„๋ณ„ ํ๋ฆ„์ด ๋ช…ํ™•ํ•˜๊ณ  ์˜ˆ์ธก ๊ฐ€๋Šฅํ•˜๋ฉด Sequential, ์ž‘์—…์˜ ๋ณต์žก๋„๊ฐ€ ๋†’๊ณ  ๋™์  ์œ„์ž„์ด ํ•„์š”ํ•˜๋ฉด Hierarchical์„ ์„ ํƒํ•˜์„ธ์š”.


๐Ÿ“š ์ฐธ๊ณ 

This post is licensed under CC BY 4.0 by the author.