day6_practice/supervisor_assembled.py

Supervisor 조립: 누가 뭘 호출했나

앞에서 만든 서브 Agent 3종을 create_supervisor로 묶었습니다. 이제 Supervisor LLM이 질문을 보고 담당 Agent에게 일을 넘기고(handoff), 결과를 받아 최종 답을 씁니다. 아래 그림의 호출 순서, 인자, 토큰, 터미널 출력은 모두 실제로 돌려서 기록한 값입니다(서브 Agent 안쪽까지 보려고 subgraphs=True로 따로 기록).

시퀀스 다이어그램: ""

세로줄이 호출하는 주체, 가로 화살표가 호출입니다. 🧠는 그 주체가 LLM을 부른 지점입니다. 파란 배경 구간은 run()이 못 보는 서브 Agent 안쪽입니다.

run() supervisor (LLM) handoff data_agent (LLM) tools 끝
data_agent (서브 그래프 = specialist_agents.py) research_agent · 이번엔 안 불림 general_agent · 이번엔 안 불림 transfer_to_data_agent transfer_back_to_supervisor tool_calls tool_calls 없음 최종 State run() app.stream(...) supervisor 🧠 supervisor_llm + handoff 도구 3개 🧠 LLM 호출! model 🧠 worker_llm 🧠 LLM 호출! tools ToolNode · 도구 4개 END

🎒 State["messages"] 두 개

바깥과 안쪽은 따로 쌓입니다

바깥 (supervisor 그래프 · run()이 받는 것)

안쪽 (data_agent 서브 그래프)

🖥️ 터미널 출력 (run()의 print)

run() 출력 읽기: 왜 같은 줄이 두 번 나오나

run()은 stream_mode="updates"로 바깥 그래프 노드가 끝날 때마다 그 노드가 돌려준 메시지를 찍습니다. 바깥 노드 업데이트는 3번 왔습니다.

① 중복: supervisor는 메시지 전체를 돌려줌

마지막 supervisor 업데이트에는 대화 전체 7개가 들어 있습니다(supervisor도 내부적으로 Agent 그래프라서 자기 State의 messages를 통째로 돌려줌). 그래서 앞에서 이미 찍은 6줄이 또 찍힙니다. State에는 중복이 없습니다. 같은 id의 메시지는 add_messages가 교체하므로 최종 State는 7개입니다.

② 라벨이 헷갈림

라벨은 m.name or node(54줄)입니다. 사용자 질문(HumanMessage)은 name이 없어서 [supervisor] 클라우드운영팀 자산 목록 알려줘처럼 supervisor가 말한 것처럼 찍힙니다. 도구 호출 줄은 항상 node를 쓰므로(52줄), 두 번째로 찍힐 때는 data_agent가 만든 transfer_back_to_supervisor가 [supervisor] 도구: …로 찍힙니다.

③ 서브 Agent 안쪽은 안 보임

list_team_members, get_asset ×3, data_agent의 LLM 호출 3번은 한 줄도 안 찍힙니다. 바깥 그래프에서 data_agent는 노드 하나일 뿐이고, 그 안쪽 이벤트는 app.stream(..., subgraphs=True)로 줘야 나옵니다. 이 페이지의 안쪽 기록이 그렇게 뽑은 것입니다.

④ 호출 순서 = 바깥 노드 이름

called(40~48줄)는 바깥 업데이트의 노드 이름 중 supervisor가 아닌 것만 모읍니다. 그래서 ['data_agent']. 서브 Agent가 어떤 도구를 몇 번 썼는지는 여기 안 나옵니다.

호출 횟수 (실제 실행)

누가무엇을횟수토큰run()에 보임?

질문 하나에 LLM 5번(Supervisor 2 + data_agent 3), 실제 데이터 도구 4번(list_team_members 1 + get_asset 3), handoff 2번입니다. 같은 질문을 data_agent에 직접 물었다면 이번 안쪽 호출과 같은 LLM 3번으로 끝났을 일입니다. Supervisor를 두면 배분과 종합에 LLM 호출 2번이 더 듭니다.

핵심 개념 4개

🤝 handoff 도구

create_supervisor가 서브 Agent의 name으로 transfer_to_data_agent, transfer_to_research_agent, transfer_to_general_agent 도구를 만들어 Supervisor LLM에 붙입니다. Supervisor는 "누구에게 넘길지"를 도구 호출로 표현하고(인자 없음 {}), 도구가 그래프를 그 노드로 보냅니다(Command(goto=…)).

📦 마지막 메시지만 돌아옴

기본값 output_mode="last_message"라서 data_agent 안쪽의 대화(도구 호출 4번과 결과)는 버리고 마지막 보고 한 문단만 바깥으로 돌려줍니다. Supervisor LLM은 data_agent가 어떤 도구를 썼는지 모르고 보고서만 봅니다. 전부 넘기려면 output_mode="full_history".

⚡ 병렬 도구 호출

get_asset(owner)는 팀이 아니라 사람 이름을 받습니다. 그래서 data_agent LLM은 먼저 list_team_members로 팀원 3명을 알아낸 뒤, 한 번의 응답에 get_asset을 3개 동시에 요청했습니다. ToolNode가 한 단계에서 셋 다 실행합니다.

🔙 돌아오는 메시지는 가짜

data_agent가 끝나면 create_supervisor가 AIMessage("Transferring back to supervisor")와 ToolMessage 한 쌍을 직접 만들어 붙입니다(토큰 0, LLM 아님). "넘겼다가 돌아왔다"는 기록을 대화에 남겨 Supervisor LLM이 흐름을 이해하게 하려는 것입니다.

알아둘 점