LabHub
배우기 러닝패스 코스

AI Agents — A Graph, Not a Model

It Is Working Fine but Looks Frozen

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

목표

같은 그래프를 다섯 가지 방식으로 흘려보내며 무엇이 언제 나오는지 직접 센다. 갱신만 모아 마지막 상태를 다시 세워 보고, 흘려보낸 마지막 상태가 invoke 의 답과 같은지 대조한다.

왜 중요한가

에이전트에서 흘려보낼 것은 토큰만이 아니다. 모델을 마지막에 한 번 부르는 그래프라면 토큰 스트리밍을 붙여도 앞의 침묵은 그대로다. 사용자에게 필요한 것은 "지금 어느 단계인지" 와 "무엇이 새로 정해졌는지" 이고, 그것은 모델이 아니라 그래프가 알고 있다. 모드마다 단위가 다르다. updates노드 단위라 나란히 도는 노드 둘이 같은 수퍼스텝에 있어도 이벤트가 따로 나오고, values수퍼스텝 단위라 그 둘의 결과가 합쳐진 뒤 한 번 나온다. debug 는 수퍼스텝 번호까지 알려 준다. 이 차이를 모르면 "왜 두 번 나오지" 를 버그로 오해한다. 갱신만 모아 상태를 다시 세우려면 리듀서를 알아야 한다. 그래프 안에서는 리듀서가 해 주던 일을 바깥에서는 직접 해야 하고, 이어 붙이는 열쇠를 덮어쓰면 발자국이 한 칸만 남는다. 그리고 상태에 남기고 싶지 않은 진행 표시는 custom 으로 내보낸다. 상태에 넣으면 체크포인트에 실리고 재개할 때 옛 표시가 되살아난다. 채점기는 여러분이 적어 둔 설명을 믿지 않는다. 여러분의 모듈을 실제로 불러 매번 다른 회의록으로 돌려 보고, 이벤트의 개수·순서·모양을 채점기가 따로 센 값과 대조한다. 시간은 재지 않는다.

단계

  1. /root/work/agstream/stream.pyState·노드 넷(split·keypoints·actions·composebuild_graph()·start(text)·run_values(text) 를 만드세요. split 다음에 keypointsactions 가 나란히 돌고, 둘 다 compose 로 모입니다.
  2. run_updates(text) 를 더해 stream_mode="updates" 의 이벤트를 그대로 목록으로 돌려주게 하세요.
  3. REDUCERS·rebuild(text)·rebuild_report(text) 를 더해 갱신만 모아 마지막 상태를 다시 세우고, 그것이 values 의 마지막과 같은지 대조하게 하세요.
  4. run_debug(text) 를 더해 {수퍼스텝번호: [노드이름...정렬됨]} 을 돌려주게 하세요.
  5. run_multi(text)mode_sequence(text) 를 더해 stream_mode=["updates", "values"] 의 결과를 다루게 하세요.
  6. composewriter: StreamWriter 를 받아 조각 두 개를 내보내게 하고, run_custom(text) 를 더하세요.
  7. compare_invoke(text) 를 더해 흘려보낸 마지막 상태와 invoke 의 답이 같은지, 가장 먼저 도착하는 노드가 무엇인지, 이벤트가 몇 개인지를 한 번에 돌려주게 하세요.
  8. /root/work/agstream/minutes.txt 에 잴 회의록을 저장하고, 그것으로 재어 /root/work/agstream/stream_report.json/root/work/agstream/stream_report.md 에 기록하세요.

참고

상태 전체가 단계마다 나온다

/root/work/agstream/stream.pyState·노드 넷·build_graph()·start(text)·run_values(text) 를 만드세요. split 다음에 keypointsactions 가 나란히 돌고 둘 다 compose 로 모입니다.

stream(입력, stream_mode="values") 가 주는 것을 그대로 목록으로 돌려주면 됩니다. 맨 앞에 입력 상태가 한 번 나온다는 점을 눈으로 확인하세요. 나란히 도는 두 노드의 결과는 합쳐진 뒤 한 이벤트로 나옵니다.

갱신은 노드마다 하나씩

run_updates(text) 를 더해 stream_mode="updates" 의 이벤트를 가공하지 말고 그대로 목록으로 돌려주게 하세요.

이벤트 하나가 {노드이름: 갱신} 입니다. 나란히 도는 노드 둘이 같은 수퍼스텝에 있어도 이벤트는 따로 나옵니다 — 그래서 values 보다 이벤트 수가 많습니다. 몇 개가 나오는지 직접 세어 보세요.

갱신만으로 상태를 다시 세운다

REDUCERS = {"trace": "append"}rebuild(text) 를 더하세요. start(text) 에서 시작해 updates 이벤트를 차례로 적용한 딕셔너리를 돌려줍니다.

그래프 안에서는 리듀서가 해 주던 일을 바깥에서는 직접 해야 합니다. 전부 덮어쓰면 이어 붙이는 열쇠에서 마지막 한 칸만 남습니다. 그런데 리듀서를 제대로 흉내 내도 완전히 같아지지는 않습니다 — 두 trace 를 나란히 찍어 보고 어디가 다른지, 왜 그런지 직접 확인하세요. 둘 다 매번 같은 답을 주지만 서로 다릅니다.

몇 번째 단계에서 무엇이 함께 도는가

run_debug(text) 를 더해 {수퍼스텝번호: [노드이름...정렬됨]} 을 돌려주게 하세요. event["type"] == "task" 인 것만 봅니다.

debug 모드는 노드마다 tasktask_result 두 이벤트를 냅니다. task 쪽의 step 이 수퍼스텝 번호이고 payload["name"] 이 노드 이름입니다. 나란히 도는 노드들이 같은 번호를 갖는지 확인하세요 — 그것이 이 모드를 쓰는 이유입니다.

여러 모드를 함께 듣는다

run_multi(text)mode_sequence(text) 를 더하세요. stream_mode=["updates", "values"] 를 주면 (모드이름, 값) 튜플이 나옵니다.

목록으로 모드를 주면 이벤트마다 어느 모드의 것인지가 앞에 붙습니다. 두 모드의 이벤트가 어떤 순서로 섞여 나오는지 직접 보세요 — 한 모드를 다 준 뒤 다른 모드를 주는 것이 아닙니다. mode_sequence 는 그 순서를 이름만 뽑아 보여 줍니다.

상태에 남기지 않고 보여 준다

composewriter: StreamWriter 를 두 번째 인자로 받아 {"stage": "compose", "points": 개수}{"stage": "compose", "todos": 개수} 를 차례로 내보내게 하고, run_custom(text) 를 더하세요.

노드 함수의 두 번째 인자 이름을 writer 로 두고 StreamWriter 로 주석하면 LangGraph 가 채워 줍니다. writer(...) 로 넣은 것은 상태에 남지 않고 듣는 쪽으로만 갑니다 — 진행 표시를 상태에 넣으면 체크포인트에 실리고 재개할 때 되살아납니다.

흘려보낸 마지막과 한 번에 받은 답

compare_invoke(text) 를 더해 {"same": 참거짓, "first_node": 문자열, "values_events": 정수, "updates_events": 정수} 를 돌려주게 하세요.

values 의 마지막 이벤트가 invoke() 의 답과 같다면, 화면은 스트리밍으로 그리고 기록은 따로 invoke 로 남기는 식으로 두 번 돌릴 이유가 없습니다. first_nodeupdates 의 첫 이벤트에 담긴 노드 이름입니다 — 사용자에게 가장 먼저 보여 줄 수 있는 것이 무엇인지 알려 줍니다.

무엇을 언제 보여 줄지 정리한다

/root/work/agstream/minutes.txt 에 잴 회의록을 저장하세요(결정 사항과 할 일이 각각 한 줄 이상, 빈 줄도 하나). 그 회의록으로 재어 /root/work/agstream/stream_report.jsonvalues_events·updates_events·first_node·supersteps·mode_sequence·custom_chunks·rebuild_same·stream_order·merged_order 를, /root/work/agstream/stream_report.md## 다섯 가지 방식이 각각 무엇을 주나 ## 갱신만으로 상태를 다시 세우려면 ## 화면에 먼저 보여 줄 수 있는 것 ## 현장에서 무엇을 고르나 네 절로 쓰세요.

전부 아래 회의록으로 돌려 얻은 값입니다. superstepsrun_debug 의 답을 문자열 열쇠로 바꿔 담고, rebuild_same·stream_order·merged_orderrebuild_report 의 답 그대로입니다. 숫자를 지어내지 말고 세어서 넣으세요.