Skip to content

kyopark2014/strands-agent

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

248 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

Strands Agent

์—ฌ๊ธฐ์—์„œ๋Š” Strands agent๋ฅผ ์ด์šฉํ•ด Agentic AI๋ฅผ ๊ตฌํ˜„ํ•˜๋Š”๊ฒƒ์„ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค. Strands Agent๋Š” AI agent ๊ตฌ์ถ• ๋ฐ ์‹คํ–‰์„ ์œ„ํ•ด ์„ค๊ณ„๋œ ์˜คํ”ˆ์†Œ์Šค SDK์ž…๋‹ˆ๋‹ค. ๊ณ„ํš(planning), ์‚ฌ๊ณ  ์—ฐ๊ฒฐ(chaining thoughts), ๋„๊ตฌ ํ˜ธ์ถœ, Reflection๊ณผ ๊ฐ™์€ agent ๊ธฐ๋Šฅ์„ ์‰ฝ๊ฒŒ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด LLM model๊ณผ tool์„ ์—ฐ๊ฒฐํ•˜๋ฉฐ, ๋ชจ๋ธ์˜ ์ถ”๋ก  ๋Šฅ๋ ฅ์„ ์ด์šฉํ•˜์—ฌ ๋„๊ตฌ๋ฅผ ๊ณ„ํšํ•˜๊ณ  ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ํ˜„์žฌ Amazon Bedrock, Anthropic, Meta์˜ ๋ชจ๋ธ์„ ์ง€์›ํ•˜๋ฉฐ, Accenture, Anthropic, Meta์™€ ๊ฐ™์€ ๊ธฐ์—…๋“ค์ด ์ฐธ์—ฌํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

์—ฌ๊ธฐ์—์„œ ์‚ฌ์šฉํ•˜๋Š” architecture๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค. Agent์˜ ๊ธฐ๋ณธ๋™์ž‘ ํ™•์ธ ๋ฐ ๊ตฌํ˜„์„ ์œ„ํ•ด EC2์— docker ํ˜•ํƒœ๋กœ ํƒ‘์žฌ๋˜์–ด ALB์™€ CloudFront๋ฅผ ์ด์šฉํ•ด ์‚ฌ์šฉ์ž๊ฐ€ streamlit์œผ๋กœ ๋™์ž‘์„ ํ…Œ์ŠคํŠธ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Agent๊ฐ€ ์ƒ์„ฑํ•˜๋Š” ๊ทธ๋ฆผ์ด๋‚˜ ๋ฌธ์„œ๋Š” S3๋ฅผ ์ด์šฉํ•ด ๊ณต์œ ๋  ์ˆ˜ ์žˆ์œผ๋ฉฐ, EC2์— ๋‚ด์žฅ๋œ MCP server/client๋ฅผ ์ด์šฉํ•ด ์ธํ„ฐ๋„ท๊ฒ€์ƒ‰(Tavily), RAG(knowledge base) AWS tools(use-aws), AWS Document๋ฅผ ์ด์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

image

Strands agent๋Š” ์•„๋ž˜์™€ ๊ฐ™์€ Agent Loop์„ ๊ฐ€์ง€๊ณ  ์žˆ์œผ๋ฏ€๋กœ, ์ ์ ˆํ•œ tool์„ ์„ ํƒํ•˜์—ฌ ์‹คํ–‰ํ•˜๊ณ , reasoning์„ ํ†ตํ•ด ๋ฐ˜๋ณต์ ์œผ๋กœ ํ•„์š”ํ•œ ๋™์ž‘์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

image

Tool๋“ค์„ ์•„๋ž˜์™€ ๊ฐ™์ด ๋ณ‘๋ ฌ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

agent = Agent(
    max_parallel_tools=4  
)

Strands Agent ํ™œ์šฉ ๋ฐฉ๋ฒ•

Streamlit์—์„œ agent์˜ ์‹คํ–‰

app.py์™€ ๊ฐ™์ด ์‚ฌ์šฉ์ž๊ฐ€ "RAG", "Agent"์„ ์„ ํƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. "Agent"์€ Strands agent๋ฅผ ์ด์šฉํ•˜์—ฌ MCP๋กœ ํ•„์š”์‹œ tool๋“ค์„ ์ด์šฉํ•˜์—ฌ RAG๋“ฑ์„ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Streamlit์˜ UI๋ฅผ ์œ„ํ•˜์—ฌ user์˜ ์ž…๋ ฅ๊ณผ ๊ฒฐ๊ณผ์ธ response์„ Session State๋กœ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

if prompt := st.chat_input("๋ฉ”์‹œ์ง€๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”."):
    with st.chat_message("user"):  
        st.markdown(prompt)
    st.session_state.messages.append({"role": "user", "content": prompt})

    with st.chat_message("assistant"):
        containers = {
            "tools": st.empty(),
            "status": st.empty(),
            "notification": [st.empty() for _ in range(1000)],
            "key": st.empty()
        }
        if mode == 'Agent':
            response, image_urls = asyncio.run(chat.run_strands_agent(
                query=prompt, 
                strands_tools=selected_strands_tools, 
                mcp_servers=selected_mcp_servers, 
                containers=containers))
        st.session_state.messages.append({
            "role": "assistant", 
            "content": response,
            "images": image_urls if image_urls else []
        })

Agent์˜ ์‹คํ–‰

์•„๋ž˜์™€ ๊ฐ™์ด system prompt, model, tool ์ •๋ณด๋ฅผ ๊ฐ€์ง€๊ณ  agent๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

def create_agent(system_prompt, tools):
    if system_prompt==None:
        system_prompt = (
            "๋‹น์‹ ์˜ ์ด๋ฆ„์€ ์„œ์—ฐ์ด๊ณ , ์งˆ๋ฌธ์— ๋Œ€ํ•ด ์นœ์ ˆํ•˜๊ฒŒ ๋‹ต๋ณ€ํ•˜๋Š” ์‚ฌ๋ ค๊นŠ์€ ์ธ๊ณต์ง€๋Šฅ ๋„์šฐ๋ฏธ์ž…๋‹ˆ๋‹ค."
            "์ƒํ™ฉ์— ๋งž๋Š” ๊ตฌ์ฒด์ ์ธ ์„ธ๋ถ€ ์ •๋ณด๋ฅผ ์ถฉ๋ถ„ํžˆ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค." 
            "๋ชจ๋ฅด๋Š” ์งˆ๋ฌธ์„ ๋ฐ›์œผ๋ฉด ์†”์งํžˆ ๋ชจ๋ฅธ๋‹ค๊ณ  ๋งํ•ฉ๋‹ˆ๋‹ค."
        )
    model = get_model()    
    agent = Agent(
        model=model,
        system_prompt=system_prompt,
        tools=tools,
        conversation_manager=conversation_manager
    )
    return agent

chat.py์™€ ๊ฐ™์ด Agent๋ฅผ ์‹คํ–‰ํ•˜๊ณ  stream์œผ๋กœ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ›์•„์„œ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. ์ด๋•Œ, ์•„๋ž˜์™€ ๊ฐ™์ด event์—์„œ "data"๋งŒ์„ ์ถ”์ถœํ•œ ํ›„์— full_response๋กœ ์ €์žฅํ•œ ํ›„์— markdown์œผ๋กœ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.

async def run_strands_agent(query, strands_tools, mcp_servers, containers):
    await strands_agent.initiate_agent(
        system_prompt=None, 
        strands_tools=strands_tools, 
        mcp_servers=mcp_servers
    )

    final_result = current = ""
    with strands_agent.mcp_manager.get_active_clients(mcp_servers) as _:
        agent_stream = strands_agent.agent.stream_async(query)
        
        async for event in agent_stream:
            text = ""            
            if "data" in event:
                text = event["data"]
                logger.info(f"[data] {text}")
                current += text

            elif "result" in event:
                final = event["result"]                
                message = final.message
                if message:
                    content = message.get("content", [])
                    result = content[0].get("text", "")
                    final_result = result
    return final_result

๋Œ€ํ™” ์ด๋ ฅ์˜ ํ™œ์šฉ

๋Œ€ํ™” ๋‚ด์šฉ์„ ์ด์šฉํ•ด ๋Œ€ํ™”๋ฅผ ์ด์–ด๋‚˜๊ฐ€๊ณ ์ž ํ•  ๊ฒฝ์šฐ์— ์•„๋ž˜์™€ ๊ฐ™์ด SlidingWindowConversationManager์„ ์ด์šฉํ•ด์„œ window_size๋งŒํผ ์ด์ „ ๋Œ€ํ™”๋ฅผ ๊ฐ€์ ธ์™€ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ƒ์„ธํ•œ ์ฝ”๋“œ๋Š” chat.py์„ ์ฐธ์กฐํ•ฉ๋‹ˆ๋‹ค.

from strands.agent.conversation_manager import SlidingWindowConversationManager

conversation_manager = SlidingWindowConversationManager(
    window_size=10,  
)

agent = Agent(
    model=model,
    system_prompt=system,
    tools=[    
        calculator, 
        current_time,
        use_aws    
    ],
    conversation_manager=conversation_manager
)

MCP ํ™œ์šฉ

์•„๋ž˜์™€ ๊ฐ™์ด MCPClient๋กœ stdio_mcp_client์„ ์ง€์ •ํ•œ ํ›„์— list_tools_sync์„ ์ด์šฉํ•ด tool ์ •๋ณด๋ฅผ ์ถ”์ถœํ•ฉ๋‹ˆ๋‹ค. MCP tool์€ strands tool๊ณผ ํ•จ๊ป˜ ์•„๋ž˜์ฒ˜๋Ÿผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

from strands.tools.mcp import MCPClient
from strands_tools import calculator, current_time, use_aws

stdio_mcp_client = MCPClient(lambda: stdio_client(
    StdioServerParameters(command="uvx", args=["awslabs.aws-documentation-mcp-server@latest"])
))

with stdio_mcp_client as client:
    aws_documentation_tools = client.list_tools_sync()
    logger.info(f"aws_documentation_tools: {aws_documentation_tools}")

    tools=[    
        calculator, 
        current_time,
        use_aws
    ]

    tools.extend(aws_documentation_tools)

    agent = Agent(
        model=model,
        system_prompt=system,
        tools=tools,
        conversation_manager=conversation_manager
    )

๋˜ํ•œ, wikipedia ๊ฒ€์ƒ‰์„ ์œ„ํ•œ MCP server์˜ ์˜ˆ๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค. ์ƒ์„ธํ•œ ์ฝ”๋“œ๋Š” mcp_server_wikipedia.py์„ ์ฐธ์กฐํ•ฉ๋‹ˆ๋‹ค.

from mcp.server.fastmcp import FastMCP
import wikipedia
import logging
import sys

logging.basicConfig(
    level=logging.INFO,  # Default to INFO level
    format='%(filename)s:%(lineno)d | %(message)s',
    handlers=[
        logging.StreamHandler(sys.stderr)
    ]
)
logger = logging.getLogger("rag")

mcp = FastMCP(
    "Wikipedia",
    dependencies=["wikipedia"],
)

@mcp.tool()
def search(query: str):
    logger.info(f"Searching Wikipedia for: {query}")
    
    return wikipedia.search(query)

@mcp.tool()
def summary(query: str):
    return wikipedia.summary(query)

@mcp.tool()
def page(query: str):
    return wikipedia.page(query)

@mcp.tool()
def random():
    return wikipedia.random()

@mcp.tool()
def set_lang(lang: str):
    wikipedia.set_lang(lang)
    return f"Language set to {lang}"

if __name__ == "__main__":
    mcp.run()

๋™์ ์œผ๋กœ MCP Server๋ฅผ bindingํ•˜๊ธฐ

MCP Server๋ฅผ ๋™์ ์œผ๋กœ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•˜์—ฌ MCPClientManager๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. add_client๋Š” MCP ์„œ๋ฒ„์˜ name, command, args, env๋กœ MCP Client๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.

class MCPClientManager:
    def __init__(self):
        self.clients: Dict[str, MCPClient] = {}
        
    def add_client(self, name: str, command: str, args: List[str], env: dict[str, str] = {}) -> None:
        """Add a new MCP client"""
        self.clients[name] = MCPClient(lambda: stdio_client(
            StdioServerParameters(
                command=command, args=args, env=env
            )
        ))
    
    def remove_client(self, name: str) -> None:
        """Remove an MCP client"""
        if name in self.clients:
            del self.clients[name]
    
    @contextmanager
    def get_active_clients(self, active_clients: List[str]):
        """Manage active clients context"""
        active_contexts = []
        for client_name in active_clients:
            if client_name in self.clients:
                active_contexts.append(self.clients[client_name])

        if active_contexts:
            with contextlib.ExitStack() as stack:
                for client in active_contexts:
                    stack.enter_context(client)
                yield
        else:
            yield

# Initialize MCP client manager
mcp_manager = MCPClientManager()

Streamlit์œผ๋กœ ๊ตฌํ˜„ํ•œ app.py์—์„œ tool๋“ค์„ ์„ ํƒํ•˜๋ฉด mcp_tools๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ดํ›„ ์•„๋ž˜์™€ ๊ฐ™์ด agent ์ƒ์„ฑ์‹œ์— active client์œผ๋กœ ๋ถ€ํ„ฐ tool list๋ฅผ ๊ฐ€์ ธ์™€์„œ tools๋กœ ํ™œ์šฉํ•ฉ๋‹ˆ๋‹ค.

tools = []
for mcp_tool in mcp_servers:
    with mcp_manager.get_active_clients([mcp_tool]) as _:
        client = mcp_manager.get_client(mcp_tool)
        mcp_servers_list = client.list_tools_sync()
        tools.extend(mcp_servers_list)

agent = create_agent(system_prompt, tools)
tool_list = get_tool_list(tools)

์ƒ์„ฑ๋œ agent๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด mcp_manager๋ฅผ ์ด์šฉํ•ด ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

with mcp_manager.get_active_clients(mcp_tools) as _:
    agent_stream = agent.stream_async(question)
    
    tool_name = ""
    async for event in agent_stream:
        if "message" in event:
            message = event["message"]
            for content in message["content"]:                
                if "text" in content:
                    final_response = content["text"]

Streamlit์— ๋งž๊ฒŒ ์ถœ๋ ฅ๋ฌธ ์กฐ์ •ํ•˜๊ธฐ

Agent๋ฅผ ์•„๋ž˜์™€ ๊ฐ™์ด ์‹คํ–‰ํ•˜์—ฌ agent_stream์„ ์–ป์Šต๋‹ˆ๋‹ค.

with mcp_manager.get_active_clients(mcp_servers) as _:
    agent_stream = agent.stream_async(question)

์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ์œ„ํ•ด์„œ๋Š” streamํ˜•ํƒœ๋กœ ์ถœ๋ ฅ์„ ์–ป์„ ์ˆ˜ ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด agent_stream์—์„œ event๋ฅผ ๊บผ๋‚ธํ›„ "data"์—์„œ ์ถ”์ถœํ•˜์—ฌ ์•„๋ž˜์™€ ๊ฐ™์ด current_response์— stream ๊ฒฐ๊ณผ๋ฅผ ๋ชจ์•„์„œ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.

async for event in agent_stream:
    if "data" in event:
        text_data = event["data"]
        current_response += text_data

        containers["notification"][index].markdown(current_response)

Strands agent๋Š” multi step reasoning์„ ํ†ตํ•ด ์—ฌ๋Ÿฌ๋ฒˆ ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์˜ต๋‹ˆ๋‹ค. ์ตœ์ข… ๊ฒฐ๊ณผ๋ฅผ ์–ป๊ธฐ ์œ„ํ•ด ์•„๋ž˜์™€ ๊ฐ™์ด message์˜ content์—์„œ text๋ฅผ ์ถ”์ถœํ•˜์—ฌ ๋งˆ์ง€๋ง‰๋งŒ์„ ์ถ”์ถœํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ tool๋งˆ๋‹ค reference๊ฐ€ ๋‹ค๋ฅด๋ฏ€๋กœ ์•„๋ž˜์™€ ๊ฐ™์ด tool content์˜ text์—์„œ reference๋ฅผ ์ถ”์ถœํ•ฉ๋‹ˆ๋‹ค.

if "message" in event:
    message = event["message"]
    for msg_content in message["content"]:                
        result = msg_content["text"]
        current_response = ""

        tool_content = msg_content["toolResult"]["content"]
        for content in tool_content:
            content, urls, refs = get_tool_info(tool_name, content["text"])
            if refs:
                for r in refs:
                    references.append(r)

generate_image_with_colors๋ผ๋Š” tool์˜ ์ตœ์ข… ์ด๋ฏธ์ง€ ๊ฒฝ๋กœ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด event_loop_metrics์—์„œ ์ถ”์ถœํ•ฉํ•˜์—ฌ image_urls๋กœ ํ™œ์šฉํ•ฉ๋‹ˆ๋‹ค.

if "event_loop_metrics" in event and \
    hasattr(event["event_loop_metrics"], "tool_metrics") and \
    "generate_image_with_colors" in event["event_loop_metrics"].tool_metrics:
    tool_info = event["event_loop_metrics"].tool_metrics["generate_image_with_colors"].tool
    if "input" in tool_info and "filename" in tool_info["input"]:
        fname = tool_info["input"]["filename"]
        if fname:
            url = f"{path}/{s3_image_prefix}/{parse.quote(fname)}.png"
            if url not in image_urls:
                image_urls.append(url)

Multi Agent

Supervisor

Agents as Tools์™€ ๊ฐ™์ด orchestrator agent๋ฅผ ์ด์šฉํ•ด research_assistant, product_recommendation_assistant, trip_planning_assistant์™€ ๊ฐ™์€ ์—ฌ๋Ÿฌ agents๋ฅผ ์ด์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. agent-as-tools์™€ ๊ฐ™์ด supervisorํ˜•ํƒœ์˜ multi agent์€ tool์— ์†ํ•œ agent์„ ์ด์šฉํ•ด ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

image

strands_supervisor.py์™€ ๊ฐ™์ด supervisor๋ฅผ ์œ„ํ•œ orchestration agent๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด agent๋Š” research_assistant, product_recommendation_assistant, trip_planning_assistant์˜ agent๋กœ ๋งŒ๋“ค์–ด์ง„ tool์„ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

orchestrator = Agent(
    model=strands_agent.get_model(),
    system_prompt=MAIN_SYSTEM_PROMPT,
    tools=[
        research_assistant,
        product_recommendation_assistant,
        trip_planning_assistant,
        file_write,
    ],
)
agent_stream = orchestrator.stream_async(question)
result = await show_streams(agent_stream, containers)

์—ฌ๊ธฐ์„œ trip_planning_assistant๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด travel_agent๋ผ๋Š” agent๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

@tool
async def trip_planning_assistant(query: str) -> str:
    """
    Create travel itineraries and provide travel advice.
    Args:
        query: A travel planning request with destination and preferences
    Returns:
        A detailed travel itinerary or travel advice
    """
    travel_agent = Agent(
        model=strands_agent.get_model(),
        system_prompt=TRIP_PLANNING_ASSISTANT_PROMPT,
    )        
    agent_stream = travel_agent.stream_async(query)
    result = await show_streams(agent_stream, containers)

    return result

Supervisor๋Š” ์ „๋ฌธ agent์ธ collaborator๋กœ hand off๋ฅผ ์ˆ˜ํ–‰ํ•จ์œผ๋กœ์จ ๋” ํ–ฅ์ƒ๋œ ๋‹ต๋ณ€์„ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

strands_supervisor

Swarm

Multi-Agent Systems and Swarm Intelligence์™€ ๊ฐ™์ด Agent๋“ค์ด ์„œ๋กœ ํ˜‘์กฐํ•˜๋ฉด์„œ ๋ณต์žกํ•œ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Mesh Swarm

Mesh Swarm Architecture์™€ ๊ฐ™์ด ์—ฌ๋Ÿฌ agent๋“ค๊ฐ„์˜ ํ˜‘์—…์„ ์ˆ˜ํ–‰ํ•˜ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Research agent๋Š” ๋…ผ๋ฆฌ์ ์ธ ๋‹ต๋ณ€์„, creative agent๋Š” ํฅ๋ฏธ๋กœ์šด ๋‹ต๋ณ€์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ด๋•Œ, critical agent๋กœ ๋‘ agent๋“ค์˜ ๊ฐœ์„ ์ ์„ ๋„์ถœํ•œ ํ›„์—, summarizer agent๋กœ ์ตœ์ ์˜ ๋‹ต๋ณ€์„ ๊ตฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

swarm

์ด๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐฉ๋ฒ•์€ strands_swarm.py์„ ์ฐธ์กฐํ•ฉ๋‹ˆ๋‹ค. ๊ฐ agent์˜ ํŽ˜๋ฅด์†Œ๋‚˜์— ๋งž๊ฒŒ MCP tool๊ณผ ํ•จ๊ป˜ agent๋ฅผ ์ •์˜ ํ•ฉ๋‹ˆ๋‹ค.

# Create specialized agents with different expertise
# research agent
system_prompt = (
    "๋‹น์‹ ์€ ์ •๋ณด ์ˆ˜์ง‘๊ณผ ๋ถ„์„์„ ์ „๋ฌธ์œผ๋กœ ํ•˜๋Š” ์—ฐ๊ตฌ์›์ž…๋‹ˆ๋‹ค. "
    "๋‹น์‹ ์˜ ์—ญํ• ์€ ํ•ด๋‹น ์ฃผ์ œ์— ๋Œ€ํ•œ ์‚ฌ์‹ค์  ์ •๋ณด์™€ ์—ฐ๊ตฌ ํ†ต์ฐฐ๋ ฅ์„ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. "
    "์ •ํ™•ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ณตํ•˜๊ณ  ๋ฌธ์ œ์˜ ํ•ต์‹ฌ์ ์ธ ์ธก๋ฉด๋“ค์„ ํŒŒ์•…ํ•˜๋Š” ๋ฐ ์ง‘์ค‘ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. "
    "๋‹ค๋ฅธ ์—์ด์ „ํŠธ๋กœ๋ถ€ํ„ฐ ์ž…๋ ฅ์„ ๋ฐ›์„ ๋•Œ, ๊ทธ๋“ค์˜ ์ •๋ณด๊ฐ€ ๋‹น์‹ ์˜ ์—ฐ๊ตฌ์™€ ์ผ์น˜ํ•˜๋Š”์ง€ ํ‰๊ฐ€ํ•˜์„ธ์š”. "
)
model = strands_agent.get_model()
research_agent = Agent(
    model=model,
    system_prompt=system_prompt, 
    tools=tools
)

# Creative Agent
system_prompt = (
    "๋‹น์‹ ์€ ํ˜์‹ ์ ์ธ ์†”๋ฃจ์…˜ ์ƒ์„ฑ์„ ์ „๋ฌธ์œผ๋กœ ํ•˜๋Š” ์ฐฝ์˜์  ์—์ด์ „ํŠธ์ž…๋‹ˆ๋‹ค. "
    "๋‹น์‹ ์˜ ์—ญํ• ์€ ํ‹€์— ๋ฐ•ํžŒ ์‚ฌ๊ณ ์—์„œ ๋ฒ—์–ด๋‚˜ ์ฐฝ์˜์ ์ธ ์ ‘๊ทผ๋ฒ•์„ ์ œ์•ˆํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. "
    "๋‹ค๋ฅธ ์—์ด์ „ํŠธ๋“ค๋กœ๋ถ€ํ„ฐ ์–ป์€ ์ •๋ณด๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ํ•˜๋˜, ๋‹น์‹ ๋งŒ์˜ ๋…์ฐฝ์ ์ธ ์ฐฝ์˜์  ๊ด€์ ์„ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. "
    "๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค์ด ๊ณ ๋ คํ•˜์ง€ ์•Š์•˜์„ ์ˆ˜๋„ ์žˆ๋Š” ์ฐธ์‹ ํ•œ ์ ‘๊ทผ๋ฒ•์— ์ง‘์ค‘ํ•˜์„ธ์š”. "
)
creative_agent = Agent(
    model=model,
    system_prompt=system_prompt, 
    tools=tools
)

# Critical Agent
system_prompt = (
    "๋‹น์‹ ์€ ์ œ์•ˆ์„œ๋ฅผ ๋ถ„์„ํ•˜๊ณ  ๊ฒฐํ•จ์„ ์ฐพ๋Š” ๊ฒƒ์„ ์ „๋ฌธ์œผ๋กœ ํ•˜๋Š” ๋น„ํŒ์  ์—์ด์ „ํŠธ์ž…๋‹ˆ๋‹ค. "
    "๋‹น์‹ ์˜ ์—ญํ• ์€ ๋‹ค๋ฅธ ์—์ด์ „ํŠธ๋“ค์ด ์ œ์•ˆํ•œ ํ•ด๊ฒฐ์ฑ…์„ ํ‰๊ฐ€ํ•˜๊ณ  ์ž ์žฌ์ ์ธ ๋ฌธ์ œ์ ๋“ค์„ ์‹๋ณ„ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. "
    "์ œ์•ˆ๋œ ํ•ด๊ฒฐ์ฑ…์„ ์‹ ์ค‘ํžˆ ๊ฒ€ํ† ํ•˜๊ณ , ์•ฝ์ ์ด๋‚˜ ๊ฐ„๊ณผ๋œ ๋ถ€๋ถ„์„ ์ฐพ์•„๋‚ด๋ฉฐ, ๊ฐœ์„  ๋ฐฉ์•ˆ์„ ์ œ์‹œํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. "
    "๋น„ํŒํ•  ๋•Œ๋Š” ๊ฑด์„ค์ ์œผ๋กœ ํ•˜๋˜, ์ตœ์ข… ํ•ด๊ฒฐ์ฑ…์ด ๊ฒฌ๊ณ ํ•˜๋„๋ก ๋ณด์žฅํ•˜์„ธ์š”. "
)
critical_agent = Agent(
    model=model,
    system_prompt=system_prompt, 
    tools=tools
)

# summarizer agent
system_prompt = (
    "๋‹น์‹ ์€ ์ •๋ณด ์ข…ํ•ฉ์„ ์ „๋ฌธ์œผ๋กœ ํ•˜๋Š” ์š”์•ฝ ์—์ด์ „ํŠธ์ž…๋‹ˆ๋‹ค. "
    "๋‹น์‹ ์˜ ์—ญํ• ์€ ๋ชจ๋“  ์—์ด์ „ํŠธ๋กœ๋ถ€ํ„ฐ ํ†ต์ฐฐ๋ ฅ์„ ์ˆ˜์ง‘ํ•˜๊ณ  ์‘์ง‘๋ ฅ ์žˆ๋Š” ์ตœ์ข… ํ•ด๊ฒฐ์ฑ…์„ ๋งŒ๋“œ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค."
    "์ตœ๊ณ ์˜ ์•„์ด๋””์–ด๋“ค์„ ๊ฒฐํ•ฉํ•˜๊ณ  ๋น„ํŒ์ ๋“ค์„ ๋‹ค๋ฃจ์–ด ํฌ๊ด„์ ์ธ ๋‹ต๋ณ€์„ ๋งŒ๋“ค์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. "
    "์›๋ž˜ ์งˆ๋ฌธ์„ ํšจ๊ณผ์ ์œผ๋กœ ๋‹ค๋ฃจ๋Š” ๋ช…ํ™•ํ•˜๊ณ  ์‹คํ–‰ ๊ฐ€๋Šฅํ•œ ์š”์•ฝ์„ ์ž‘์„ฑํ•˜๋Š” ๋ฐ ์ง‘์ค‘ํ•˜์„ธ์š”. "
)
summarizer_agent = Agent(
    model=model,
    system_prompt=system_prompt,
    callback_handler=None)

์ฃผ์–ด์ง„ ์งˆ๋ฌธ์— ๋Œ€ํ•ด research, creative, critical agent์˜ ์‘๋‹ต์„ ๊ตฌํ•˜๊ณ , ์ž์‹ ์˜ ๊ฒฐ๊ณผ์™€ ํ•จ๊ฒŒ ๋‹ค๋ฅธ agent๋“ค์˜ ๊ฒฐ๊ณผ๋ฅผ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.

result = research_agent.stream_async(question)
research_result = await show_streams(result, containers)

result = creative_agent.stream_async(question)
creative_result = await show_streams(result, containers)

result = critical_agent.stream_async(question)
critical_result = await show_streams(result, containers)

research_messages = []
creative_messages = []
critical_messages = []

creative_messages.append(f"From Research Agent: {research_result}")
critical_messages.append(f"From Research Agent: {research_result}")
summarizer_messages.append(f"From Research Agent: {research_result}")

research_messages.append(f"From Creative Agent: {creative_result}")
critical_messages.append(f"From Creative Agent: {creative_result}")
summarizer_messages.append(f"From Creative Agent: {creative_result}")

research_messages.append(f"From Critical Agent: {critical_result}")
creative_messages.append(f"From Critical Agent: {critical_result}")
summarizer_messages.append(f"From Critical Agent: {critical_result}")

๊ฒฐ๊ณผ๋ฅผ refineํ•˜๊ณ  ์–ป์–ด์ง„ ๊ฒฐ๊ณผ๋ฅผ summarizer agent์— ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.

result = research_agent.stream_async(research_prompt)
refined_research = await show_streams(result, containers)

result = creative_agent.stream_async(creative_prompt)
refined_creative = await show_streams(result, containers)

result = critical_agent.stream_async(critical_prompt)
refined_critical = await show_streams(result, containers)

summarizer_messages.append(f"From Research Agent (Phase 2): {refined_research}")
summarizer_messages.append(f"From Creative Agent (Phase 2): {refined_creative}")
summarizer_messages.append(f"From Critical Agent (Phase 2): {refined_critical}")

์ดํ›„ ์•„๋ž˜์™€ ๊ฐ™์ด ์š”์•ฝํ•ฉ๋‹ˆ๋‹ค.

summarizer_prompt = f"""
Original query: {question}

Please synthesize the following inputs from all agents into a comprehensive final solution:

{"\n\n".join(summarizer_messages)}

Create a well-structured final answer that incorporates the research findings, 
creative ideas, and addresses the critical feedback.
"""

result = summarizer_agent.stream_async(summarizer_prompt)
final_solution = await show_streams(result, containers)

research, creative, critical agent๋“ค์€ ๋ณ‘๋ ฌ๋กœ ์‹คํ–‰์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์•„๋ž˜์™€ ๊ฐ™์€ ํ˜•ํƒœ๋กœ๋„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

tasks = [
    _research_agent_worker(research_agent, question, request_id),
    _creative_agent_worker(creative_agent, question, request_id),
    _critical_agent_worker(critical_agent, question, request_id)
]
results = await asyncio.gather(*tasks)
research_result, creative_result, critical_result = results

summarizer_agent = create_summarizer_agent(question, tools)
summarizer_messages = []
creative_messages.append(f"From Research Agent: {research_result}")
critical_messages.append(f"From Research Agent: {research_result}")
summarizer_messages.append(f"From Research Agent: {research_result}")

research_messages.append(f"From Creative Agent: {creative_result}")
critical_messages.append(f"From Creative Agent: {creative_result}")
summarizer_messages.append(f"From Creative Agent: {creative_result}")

research_messages.append(f"From Critical Agent: {critical_result}")
creative_messages.append(f"From Critical Agent: {critical_result}")
summarizer_messages.append(f"From Critical Agent: {critical_result}")

Swarm Tool

Creating Swarm of agents using Strands Agents์—์„œ strands agent์—์„œ swarm์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก tool์„ ์ œ๊ณตํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋•Œ agent์—์„œ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋Š” ํ˜‘์—… ์˜ต์…˜์€ ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • Collaborative: Agents build upon others' insights and seek consensus
  • Competitive: Agents develop independent solutions and unique perspectives
  • Hybrid: Balances cooperation with independent exploration

ํ˜‘์—…ํ•˜๋Š” swarm agent๋“ค๋กœ๋ถ€ํ„ฐ ์–ป์–ด์ง„ ๊ฒฐ๊ณผ๋ฅผ summarized agent๋กœ ์ •๋ฆฌํ•˜์—ฌ ๋‹ต๋ณ€ํ•ฉ๋‹ˆ๋‹ค. ์•„๋ž˜๋Š” swarm tool์„ ์‚ฌ์šฉํ• ๋•Œ์˜ diagram์ž…๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ swarm agent์˜ ์ˆซ์ž๋Š” swarm_size๋กœ ์กฐ์ •ํ•ฉ๋‹ˆ๋‹ค.

image

strands_swarm_tool.py์™€ ๊ฐ™์ด strands agent๋ฅผ ์ด์šฉํ•ด swarm ํ˜•ํƒœ์˜ multi agent๋ฅผ ๊ตฌํ˜„ํ•˜๊ณ , ์ด๋ฅผ ํ†ตํ•ด ๋ณต์žกํ•œ ๋ฌธ์ œ๋ฅผ ํ’€ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

from strands_tools import swarm

agent = Agent(
    model=strands_agent.get_model(),
    system_prompt=system_prompt,
    tools=[swarm]
)

result = agent.tool.swarm(
    task=question,
    swarm_size=3,
    coordination_pattern="collaborative"
)    
logger.info(f"result of swarm: {result}")

์ด๋•Œ์˜ ๊ฒฐ๊ณผ๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค. ์ „๋ฌธ agent์— ๋Œ€ํ•œ role๊ณผ prompt๋ฅผ ์ƒ์„ฑํ•œ ํ›„์— ์š”์•ฝ๋œ ๊ฒฐ๊ณผ๋ฅผ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.

strands_swarm_tool

Workflow

Agent Workflows์„ ์ด์šฉํ•˜๋ฉด ๊ฐ„๋‹จํ•œ workflow๋ฅผ ์†์‰ฝ๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

image

strands_workflow.py์—์„œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด researcher, analyst, writer๋ฅผ ํ†ตํ•ด ์ข€๋” ์‹ฌํ™”๋œ ๋ณด๊ณ ์„œ๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

async def run_workflow(question, containers):
    model = strands_agent.get_model()
    researcher = Agent(
        model=model,
        system_prompt="research specialist. Find key information.", 
        callback_handler=None
    )
    analyst = Agent(
        model=model,
        system_prompt="You analyze research data and extract insights. Analyze these research findings.", 
        callback_handler=None
    )
    writer = Agent(
        model=model, 
        system_prompt="You create polished reports based on analysis. Create a report based on this analysis.",
        callback_handler=None
    )

    # Step 1: Research
    add_notification(containers, f"์งˆ๋ฌธ: {question}")
    query = f"๋‹ค์Œ์˜ ์งˆ๋ฌธ์„ ๋ถ„์„ํ•˜์„ธ์š”. <question>{question}</question>"
    research_stream = researcher.stream_async(query)
    research_result = await show_streams(research_stream, containers)    

    # Step 2: Analysis
    add_notification(containers, f"๋ถ„์„: {research_result}")
    analysis = f"๋‹ค์Œ์„ ๋ถ„์„ํ•ด์„œ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  ์ดํ•ดํ•˜๊ธฐ ์‰ฝ๊ฒŒ ๋ถ„์„ํ•˜์„ธ์š”. <research>{research_result}</research>"
    analysis_stream = analyst.stream_async(analysis)
    analysis_result = await show_streams(analysis_stream, containers)    

    # Step 3: Report writing
    add_notification(containers, f"๋ณด๊ณ ์„œ: {analysis_result}")
    report = f"๋‹ค์Œ์˜ ๋‚ด์šฉ์„ ์ฐธ์กฐํ•˜์—ฌ ์ƒ์„ธํ•œ ๋ณด๊ณ ์„œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š”. <subject>{analysis_result}</subject>"
    report_stream = writer.stream_async(report)
    report_result = await show_streams(report_stream, containers)    

    return report_result

Graph

Agent Graphs์™€ ๊ฐ™์ด ๋‹ค๋‹จ๊ณ„๋กœ ๋œ ๋ณต์žกํ•œ Graph๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋•Œ์˜ agent๋“ค์˜ ๊ตฌ์„ฑ๋„๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

image

strands_graph.py์™€ ๊ฐ™์ด ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ graph์˜ ์‹œ์ž‘์€ coordinator์ž…๋‹ˆ๋‹ค. ์ด agent๋Š” economic_department, technical_analysis, social_analysis์„ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

coordinator = Agent(
    system_prompt=COORDINATOR_SYSTEM_PROMPT,
    tools=[economic_department, technical_analysis, social_analysis]
)
agent_stream = coordinator.stream_async(f"Provide a comprehensive analysis of: {question}")

์—ฌ๊ธฐ์„œ economic_department๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด tool๋กœ ๊ตฌํ˜„๋ฉ๋‹ˆ๋‹ค. ์ด agent๋„ market_research, financial_analysis๋ฅผ tool๋กœ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

@tool
async def economic_department(query: str) -> str:
    """Coordinate economic analysis across market and financial domains."""
    logger.info("๐Ÿ“ˆ Economic Department coordinating analysis...")

    if isKorean(query):
        system_prompt = (
            "๋‹น์‹ ์€ ๊ฒฝ์ œ ๋ถ€์„œ ๊ด€๋ฆฌ์ž์ž…๋‹ˆ๋‹ค. ๊ฒฝ์ œ ๋ถ„์„์„ ์กฐ์ •ํ•˜๊ณ  ํ†ตํ•ฉํ•ฉ๋‹ˆ๋‹ค."
            "์‹œ์žฅ ๊ด€๋ จ ์งˆ๋ฌธ์—๋Š” market_research ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”."
            "๊ฒฝ์ œ์  ์งˆ๋ฌธ์—๋Š” financial_analysis ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”."
            "๊ฒฐ๊ณผ๋ฅผ ํ†ตํ•ฉํ•˜์—ฌ ํ†ตํ•ฉ๋œ ๊ฒฝ์ œ ๊ด€์ ์„ ์ œ๊ณตํ•˜์„ธ์š”."
            "์ค‘์š”: ์งˆ๋ฌธ์ด ๋ช…ํ™•ํ•˜๊ฒŒ ํ•œ ์˜์—ญ์— ์ง‘์ค‘๋˜์ง€ ์•Š๋Š” ํ•œ ๋‘ ๋„๊ตฌ๋ฅผ ๋ชจ๋‘ ์‚ฌ์šฉํ•˜์—ฌ ์ฒ ์ €ํ•œ ๋ถ„์„์„ ์ˆ˜ํ–‰ํ•˜์„ธ์š”."
        )
    else:
        system_prompt = (
            "You are an economic department manager who coordinates specialized economic analyses. "
            "For market-related questions, use the market_research tool. "
            "For financial questions, use the financial_analysis tool. "
            "Synthesize the results into a cohesive economic perspective. "
            "Important: Make sure to use both tools for comprehensive analysis unless the query is clearly focused on just one area."
        )

    econ_manager = Agent(
        system_prompt=system_prompt,
        tools=[market_research, financial_analysis],
        callback_handler=None
    )

    agent_stream = econ_manager.stream_async(query)
    result = await show_streams(agent_stream, containers)

    return result

Graph with Loops: Plan and Execute

strands_plan_and_execute.py์—์„œ๋Š” plan and execute pattern์˜ agent๋ฅผ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค. "planner"์—์„œ ๋จผ์ € plan์„ ์ƒ์„ฑํ•œ ํ›„์— executer๊ฐ€ ๊ฒฐ๊ณผ๋ฅผ ๊ตฌํ•ฉ๋‹ˆ๋‹ค. ์ด๋•Œ, ๋ชจ๋“  plan์ด ์‹คํ–‰์ด ์•ˆ๋˜์—ˆ๋‹ค๋ฉด replanner๊ฐ€ ์ƒˆ๋กœ์šด ๊ณ„ํš์„ ์„ธ์›๋‹ˆ๋‹ค. ๋งŒ์•ฝ ๋ชจ๋“  plan์ด ์‹คํ–‰์ด ๋˜์—ˆ๋‹ค๋ฉด synthesizer๋กœ ์ „ํ™˜๋˜์–ด ์ตœ์ข… ๊ฒฐ๊ณผ๋ฅผ ์–ป์Šต๋‹ˆ๋‹ค.

image

์ƒ์„ธํ•œ ์ฝ”๋“œ๋Š” strands_plan_and_execute.py๋ฅผ ์ฐธ์กฐํ•ฉ๋‹ˆ๋‹ค.

builder = GraphBuilder()

# Add nodes
builder.add_node(planner, "planner")
builder.add_node(executor, "executor")
builder.add_node(replanner, "replanner")
builder.add_node(synthesizer, "synthesizer")

# Set entry points (optional - will be auto-detected if not specified)
builder.set_entry_point("planner")

# Add edges (dependencies)
builder.add_edge("planner", "executor")
builder.add_edge("executor", "replanner")
builder.add_edge("replanner", "synthesizer", condition=lambda state: decide_next_step(state) == "synthesizer")
builder.add_edge("replanner", "executor", condition=lambda state: decide_next_step(state) == "executor")

Graph with Loops: Multi-Agent Feedback Cycles

Graph with Loops - Multi-Agent Feedback Cycles์„ ์ด์šฉํ•ด ์•„๋ž˜์™€ ๊ฐ™์€ feedback loop์„ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.

image

์ƒ์„ธํ•œ ์ฝ”๋“œ๋Š” strands_graph_with_loop.py์„ ์ฐธ์กฐํ•ฉ๋‹ˆ๋‹ค. ์ด ์ฝ”๋“œ๋Š” graph_loops_example.py์„ ์ฐธ์กฐํ•˜์˜€์Šต๋‹ˆ๋‹ค.

checker = QualityChecker(approval_after=2)

builder = GraphBuilder()
builder.add_node(writer, "writer")
builder.add_node(checker, "checker") 
builder.add_node(finalizer, "finalizer")

builder.add_edge("writer", "checker")
builder.add_edge("checker", "writer", condition=needs_revision)
builder.add_edge("checker", "finalizer", condition=is_approved)
builder.set_entry_point("writer")

graph = builder.build()

result = await graph.invoke_async(question)

Memory ํ™œ์šฉํ•˜๊ธฐ

Chatbot์€ ์—ฐ์†์ ์ธ ์‚ฌ์šฉ์ž์˜ ์ƒํ˜ธ์ž‘์šฉ์„ ํ†ตํ•ด ์‚ฌ์šฉ์ž์˜ ๊ฒฝํ—˜์„ ํ–ฅ์ƒ์‹œํ‚ฌ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด ์ด์ „ ๋Œ€ํ™”์˜ ๋‚ด์šฉ์„ ์ƒˆ๋กœ์šด ๋Œ€ํ™”์—์„œ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์–ด์•ผํ•˜๋ฉฐ, ์ผ๋ฐ˜์ ์œผ๋กœ chatbot์€ sliding window๋ฅผ ์ด์šฉํ•ด ์ƒˆ๋กœ์šด transaction๋งˆ๋‹ค ์ด์ „ ๋Œ€ํ™”๋‚ด์šฉ์„ context๋กœ ์ œ๊ณตํ•ด์•ผ ํ–ˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์—์„œ๋Š” ํ•„์š”ํ•œ ๊ฒฝ์šฐ์—๋งŒ ์ด์ „ ๋Œ€ํ™”๋‚ด์šฉ์„ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ๋„๋ก short term/long term ๋ฉ”๋ชจ๋ฆฌ๋ฅผ MCP๋ฅผ ์ด์šฉํ•ด ํ™œ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด context์— ๋ถˆํ•„์š”ํ•œ ์ด์ „ ๋Œ€ํ™”๊ฐ€ ํฌํ•จ๋˜์ง€ ์•Š์•„์„œ ์‚ฌ์šฉ์ž์˜ ์˜๋„๋ฅผ ๋ช…ํ™•ํžˆ ๋ฐ˜์˜ํ•˜๊ณ  ๋น„์šฉ๋„ ์ตœ์ ํ™” ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Short Term Memory

Short term memory๋ฅผ ์œ„ํ•ด์„œ๋Š” ๋Œ€ํ™” transaction์„ ์•„๋ž˜์™€ ๊ฐ™์ด agentcore์˜ memory์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. ์ƒ์„ธํ•œ ์ฝ”๋“œ๋Š” agentcore_memory.py์„ ์ฐธ์กฐํ•ฉ๋‹ˆ๋‹ค.

def save_conversation_to_memory(memory_id, actor_id, session_id, query, result):
    event_timestamp = datetime.now(timezone.utc)
    conversation = [
        (query, "USER"),
        (result, "ASSISTANT")
    ]
    memory_result = memory_client.create_event(
        memory_id=memory_id,
        actor_id=actor_id, 
        session_id=session_id, 
        event_timestamp=event_timestamp,
        messages=conversation
    )

์ดํ›„, ๋Œ€ํ™”์ค‘์— ์‚ฌ์šฉ์ž์˜ ์ด์ „ ๋Œ€ํ™”์ •๋ณด๊ฐ€ ํ•„์š”ํ•˜๋‹ค๋ฉด, mcp_server_short_term_memory.py์™€ ๊ฐ™์ด memory, actor, session๋กœ max_results ๋งŒํผ์˜ ์ด์ „ ๋Œ€ํ™”๋ฅผ ์กฐํšŒํ•˜์—ฌ ํ™œ์šฉํ•ฉ๋‹ˆ๋‹ค.

events = client.list_events(
    memory_id=memory_id,
    actor_id=actor_id,
    session_id=session_id,
    max_results=max_results
)

Long Term Memory

Long term meory๋ฅผ ์œ„ํ•ด ํ•„์š”ํ•œ ์ •๋ณด์—๋Š” memory, actor, session, namespace๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์•„๋ž˜์™€ ๊ฐ™์ด ์ด๋ฏธ ์ €์žฅ๋œ ๊ฐ’์ด ์žˆ๋‹ค๋ฉด ๊ฐ€์ ธ์˜ค๊ณ , ์—†๋‹ค๋ฉด ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์ƒ์„ธํ•œ ์ฝ”๋“œ๋Š” strands_agent.py์„ ์ฐธ์กฐํ•ฉ๋‹ˆ๋‹ค.

# initate memory variables
memory_id, actor_id, session_id, namespace = agentcore_memory.load_memory_variables(chat.user_id)
logger.info(f"memory_id: {memory_id}, actor_id: {actor_id}, session_id: {session_id}, namespace: {namespace}")

if memory_id is None:
    # retrieve memory id
    memory_id = agentcore_memory.retrieve_memory_id()
    logger.info(f"memory_id: {memory_id}")        
    
    # create memory if not exists
    if memory_id is None:
        memory_id = agentcore_memory.create_memory(namespace)
    
    # create strategy if not exists
    agentcore_memory.create_strategy_if_not_exists(memory_id=memory_id, namespace=namespace, strategy_name=chat.user_id)

    # save memory variables
    agentcore_memory.update_memory_variables(
        user_id=chat.user_id, 
        memory_id=memory_id, 
        actor_id=actor_id, 
        session_id=session_id, 
        namespace=namespace)

์ƒ์„ฑํ˜• AI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ๋Š” ๋Œ€ํ™”์ค‘ ํ•„์š”ํ•œ ๋ฉ”๋ชจ๋ฆฌ ์ •๋ณด๊ฐ€ ์žˆ๋‹ค๋ฉด ์ด๋ฅผ MCP๋ฅผ ์ด์šฉํ•ด ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. mcp_server_long_term_memory.py์—์„œ๋Š” long term memory๋ฅผ ์ด์šฉํ•ด ๋Œ€ํ™” ์ด๋ฒคํŠธ๋ฅผ ์ €์žฅํ•˜๊ฑฐ๋‚˜ ์กฐํšŒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์•„๋ž˜๋Š” ์‹ ๊ทœ๋กœ ๋ ˆ์ฝ”๋“œ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.

response = create_event(
    memory_id=memory_id,
    actor_id=actor_id,
    session_id=session_id,
    content=content,
    event_timestamp=datetime.now(timezone.utc),
)
event_data = response.get("event", {}) if isinstance(response, dict) else {}

๋Œ€ํ™”์— ํ•„์š”ํ•œ ์ •๋ณด๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.

contents = []
response = retrieve_memory_records(
    memory_id=memory_id,
    namespace=namespace,
    search_query=query,
    max_results=max_results,
    next_token=next_token,
)
relevant_data = {}
if isinstance(response, dict):
    if "memoryRecordSummaries" in response:
        relevant_data["memoryRecordSummaries"] = response["memoryRecordSummaries"]    
    for memory_record_summary in relevant_data["memoryRecordSummaries"]:
        json_content = memory_record_summary["content"]["text"]
        content = json.loads(json_content)
        contents.append(content)

์•„๋ž˜์™€ ๊ฐ™์ด "๋‚ด๊ฐ€ ์ข‹์•„ํ•˜๋Š” ์Šคํฌ์ธ ๋Š”?"๋ฅผ ์ž…๋ ฅํ•˜๋ฉด long term memory์—์„œ ์‚ฌ์šฉ์ž์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ์กฐํšŒํ•˜์—ฌ ๋‹ต๋ณ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

image

๋ฐฐํฌํ•˜๊ธฐ

EC2๋กœ ๋ฐฐํฌํ•˜๊ธฐ

AWS console์˜ EC2๋กœ ์ ‘์†ํ•˜์—ฌ Launch an instance๋ฅผ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค. [Launch instance]๋ฅผ ์„ ํƒํ•œ ํ›„์— ์ ๋‹นํ•œ Name์„ ์ž…๋ ฅํ•ฉ๋‹ˆ๋‹ค. (์˜ˆ: es) key pair์€ "Proceed without key pair"์„ ์„ ํƒํ•˜๊ณ  ๋„˜์–ด๊ฐ‘๋‹ˆ๋‹ค.

ec2แ„‹แ…ตแ„…แ…ณแ†ทแ„‹แ…ตแ†ธแ„…แ…งแ†จ

Instance๊ฐ€ ์ค€๋น„๋˜๋ฉด [Connet] - [EC2 Instance Connect]๋ฅผ ์„ ํƒํ•˜์—ฌ ์•„๋ž˜์ฒ˜๋Ÿผ ์ ‘์†ํ•ฉ๋‹ˆ๋‹ค.

image

์ดํ›„ ์•„๋ž˜์™€ ๊ฐ™์ด python, pip, git, boto3๋ฅผ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค.

sudo yum install python3 python3-pip git docker -y
pip install boto3

Workshop์˜ ๊ฒฝ์šฐ์— ์•„๋ž˜ ํ˜•ํƒœ๋กœ ๋œ Credential์„ ๋ณต์‚ฌํ•˜์—ฌ EC2 ํ„ฐ๋ฏธ๋„์— ์ž…๋ ฅํ•ฉ๋‹ˆ๋‹ค.

credential

์•„๋ž˜์™€ ๊ฐ™์ด git source๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.

git clone https://github.com/kyopark2014/strands-agent

์•„๋ž˜์™€ ๊ฐ™์ด installer.py๋ฅผ ์ด์šฉํ•ด ์„ค์น˜๋ฅผ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.

cd strands-agent && python3 installer.py

API ๊ตฌํ˜„์— ํ•„์š”ํ•œ credential์€ secret์œผ๋กœ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์„ค์น˜์‹œ ํ•„์š”ํ•œ credential ์ž…๋ ฅ์ด ํ•„์š”ํ•œ๋ฐ ์•„๋ž˜์™€ ๊ฐ™์€ ๋ฐฉ์‹์„ ํ™œ์šฉํ•˜์—ฌ ๋ฏธ๋ฆฌ credential์„ ์ค€๋น„ํ•ฉ๋‹ˆ๋‹ค.

  • ์ผ๋ฐ˜ ์ธํ„ฐ๋„ท ๊ฒ€์ƒ‰: Tavily Search์— ์ ‘์†ํ•˜์—ฌ ๊ฐ€์ž… ํ›„ API Key๋ฅผ ๋ฐœ๊ธ‰ํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ tvly-๋กœ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.
  • ๋‚ ์”จ ๊ฒ€์ƒ‰: openweathermap์— ์ ‘์†ํ•˜์—ฌ API Key๋ฅผ ๋ฐœ๊ธ‰ํ•ฉ๋‹ˆ๋‹ค. ์ด๋•Œ price plan์€ "Free"๋ฅผ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.

์„ค์น˜๊ฐ€ ์™„๋ฃŒ๋˜๋ฉด ์•„๋ž˜์™€ ๊ฐ™์€ CloudFront๋กœ ์ ‘์†ํ•˜์—ฌ ๋™์ž‘์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

cloudfront_address

์ธํ”„๋ผ๊ฐ€ ๋”์ด์ƒ ํ•„์š”์—†์„ ๋•Œ์—๋Š” uninstaller.py๋ฅผ ์ด์šฉํ•ด ์ œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค.

python uninstaller.py

๋ฐฐํฌ๋œ Application ์—…๋ฐ์ดํŠธ ํ•˜๊ธฐ

AWS console์˜ EC2๋กœ ์ ‘์†ํ•˜์—ฌ Launch an instance๋ฅผ ์„ ํƒํ•˜์—ฌ ์•„๋ž˜์™€ ๊ฐ™์ด ์•„๋ž˜์™€ ๊ฐ™์ด "app-for-es-us"๋ผ๋Š” ์ด๋ฆ„์„ ๊ฐ€์ง€๋Š” instance id๋ฅผ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.

image

[connect]๋ฅผ ์„ ํƒํ•œ ํ›„์— Session Manager๋ฅผ ์„ ํƒํ•˜์—ฌ ์ ‘์†ํ•ฉ๋‹ˆ๋‹ค.

image

์ดํ›„ ์•„๋ž˜์™€ ๊ฐ™์ด ์—…๋ฐ์ดํŠธํ•œ ํ›„์— ๋‹ค์‹œ ๋ธŒ๋ผ์šฐ์ €์—์„œ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

cd ~/strands-agent/ && sudo ./update.sh

์‹คํ–‰ ๋กœ๊ทธ ํ™•์ธ

EC2 console์—์„œ "app-for-es-us"๋ผ๋Š” ์ด๋ฆ„์„ ๊ฐ€์ง€๋Š” instance id๋ฅผ ์„ ํƒ ํ•œ ํ›„์—, EC2์˜ Session Manager๋ฅผ ์ด์šฉํ•ด ์ ‘์†ํ•ฉ๋‹ˆ๋‹ค.

๋จผ์ € ์•„๋ž˜์™€ ๊ฐ™์ด ํ˜„์žฌ docker container ID๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

sudo docker ps

์ดํ›„ ์•„๋ž˜์™€ ๊ฐ™์ด container ID๋ฅผ ์ด์šฉํ•ด ๋กœ๊ทธ๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

sudo docker logs [container ID]

์‹ค์ œ ์‹คํ–‰์‹œ ๊ฒฐ๊ณผ๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

Local์—์„œ ์‹คํ–‰ํ•˜๊ธฐ

AWS ํ™˜๊ฒฝ์„ ์ž˜ ํ™œ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” AWS CLI๋ฅผ ์„ค์น˜ํ•˜์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค. EC2์—์„œ ๋ฐฐํฌํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š” ๋ณ„๋„๋กœ ์„ค์น˜๊ฐ€ ํ•„์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. Local์— ์„ค์น˜์‹œ๋Š” ์•„๋ž˜ ๋ช…๋ น์–ด๋ฅผ ์ฐธ์กฐํ•ฉ๋‹ˆ๋‹ค.

curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" 
unzip awscliv2.zip
sudo ./aws/install

AWS credential์„ ์•„๋ž˜์™€ ๊ฐ™์ด AWS CLI๋ฅผ ์ด์šฉํ•ด ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค.

aws configure

์„ค์น˜ํ•˜๋‹ค๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๊ฐ์ข… ๋ฌธ์ œ๋Š” Kiro-cli๋ฅผ ์ด์šฉํ•ด ๋น ๋ฅด๊ฒŒ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค. ์•„๋ž˜์™€ ๊ฐ™์ด ์„ค์น˜ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, Windows์—์„œ๋Š” Kiro ์„ค์น˜์—์„œ ๋‹ค์šด๋กœ๋“œ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค. ์‹คํ–‰์‹œ๋Š” ์…€์—์„œ "kiro-cli"๋ผ๊ณ  ์ž…๋ ฅํ•ฉ๋‹ˆ๋‹ค.

curl -fsSL https://cli.kiro.dev/install | bash

venv๋กœ ํ™˜๊ฒฝ์„ ๊ตฌ์„ฑํ•˜๋ฉด ํŽธ๋ฆฌํ•˜๊ฒŒ ํŒจํ‚ค์ง€๋ฅผ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ์•„๋ž˜์™€ ๊ฐ™์ด ํ™˜๊ฒฝ์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.

python -m venv .venv
source .venv/bin/activate

์ดํ›„ ๋‹ค์šด๋กœ๋“œ ๋ฐ›์€ github ํด๋”๋กœ ์ด๋™ํ•œ ํ›„์— ์•„๋ž˜์™€ ๊ฐ™์ด ํ•„์š”ํ•œ ํŒจํ‚ค์ง€๋ฅผ ์ถ”๊ฐ€๋กœ ์„ค์น˜ ํ•ฉ๋‹ˆ๋‹ค.

pip install -r requirements.txt

์ดํ›„ ์•„๋ž˜์™€ ๊ฐ™์€ ๋ช…๋ น์–ด๋กœ streamlit์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

streamlit run application/app.py

์‹คํ–‰ ๊ฒฐ๊ณผ

"us-west-2์˜ AWS bucket ๋ฆฌ์ŠคํŠธ๋Š”?"์™€ ๊ฐ™์ด ์ž…๋ ฅํ•˜๋ฉด, aws cli๋ฅผ ํ†ตํ•ด ํ•„์š”ํ•œ operation์„ ์ˆ˜ํ–‰ํ•˜๊ณ  ์–ป์–ด์ง„ ๊ฒฐ๊ณผ๋ฅผ ์•„๋ž˜์™€ ๊ฐ™์ด ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.

MCP๋กœ wikipedia๋ฅผ ์„ค์ •ํ•˜๊ณ  "strand์— ๋Œ€ํ•ด ์„ค๋ช…ํ•ด์ฃผ์„ธ์š”."๋ผ๊ณ  ์งˆ๋ฌธํ•˜๋ฉด wikipedia์˜ search tool์„ ์ด์šฉํ•˜์—ฌ ์•„๋ž˜์™€ ๊ฐ™์€ ๊ฒฐ๊ณผ๋ฅผ ์–ป์Šต๋‹ˆ๋‹ค.

ํŠน์ • Cloudwatch์˜ ๋กœ๊ทธ๋ฅผ ์ฝ์–ด์„œ, ๋กœ๊ทธ์˜ ํŠน์ด์ ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

"Image generation" MCP๋ฅผ ์„ ํƒํ•˜๊ณ , "AWS์˜ ํ•œ๊ตญ์ธ solutions architect์˜ ๋ชจ์Šต์„ ๊ทธ๋ ค์ฃผ์„ธ์š”."๋ผ๊ณ  ์ž…๋ ฅํ•˜๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Reference

Strands Python Example

Strands Agents SDK

Strands Agents Samples

Example Built-in Tools

Introducing Strands Agents, an Open Source AI Agents SDK

use_aws.py

Strands Agents์™€ ์˜คํ”ˆ ์†Œ์Šค AI ์—์ด์ „ํŠธ SDK ์‚ดํŽด๋ณด๊ธฐ

Drug Discovery Agent based on Amazon Bedrock

Strands Agent - Swarm

Strands Agent Streamlit Demo

์ƒ์„ฑํ˜• AI๋กœ AWS ๋ณด์•ˆ ์ ๊ฒ€ ์ž๋™ํ™”ํ•˜๊ธฐ: Q CLI์—์„œ Strands Agents๊นŒ์ง€

AI Agent๋ฅผ ํ™œ์šฉํ•œ EKS ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ฐ ์ธํ”„๋ผ ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ…

Strands Agents ๋ฐ AgentCore์™€ ํ•จ๊ป˜ํ•˜๋Š” ๋ฐ”์ด์˜คยท์ œ์•ฝ ์—ฐ๊ตฌ ์–ด์‹œ์Šคํ„ดํŠธ ๊ตฌํ˜„ํ•˜๊ธฐ

Strands Agents & Amazon Bedrock AgentCore ์›Œํฌ์ƒต

Agentic AI๋กœ ๊ตฌํ˜„ํ•˜๋Š” ๋ฆฌ๋ทฐ ๊ด€๋ฆฌ ์ž๋™ํ™”

Strands Agent Workshop (ํ•œ๊ตญ์–ด)

Agentic AI Workshop: AI Fund Manager

Agentic AI ํŽ€๋“œ ๋งค๋‹ˆ์ €

Workshop - Strands SDK์™€ AgentCore๋ฅผ ํ™œ์šฉํ•œ ์—์ด์ „ํ‹ฑ AI

About

It shows how to use strands agent.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages