본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 28. 03:41

사후 검토(Hindsight)와 4개의 에이전트가 나의 시장 분석을 어떻게 변화시켰는가

요약

LangChain과 llama-3.3-70b를 활용하여 시장 분석을 수행하는 4단계 에이전트 파이프라인 구축 사례를 소개합니다. 메모리를 아키텍처의 핵심 요소로 통합하여 에이전트 간의 연속성을 확보하고 분석의 일관성을 높이는 방법을 다룹니다.

핵심 포인트

  • 메모리를 단순 사후 고려 사항이 아닌 아키텍처의 일급 객체로 취급
  • LangChain 기반의 4단계 고정 파이프라인(시장 정보-트렌드-인사이트-비평가) 설계
  • 예측 가능성과 디버깅을 위해 명시적 핸드오프 방식 채택
  • 비평가 에이전트의 판결을 시스템 상태에 반영하여 분석 연속성 유지

내 시장 분석 파이프라인(pipeline)의 첫 번째 버전은 날카롭고 논리적인 브리프(brief)를 작성할 수 있었지만, 프로세스가 종료되는 순간 그 내용이 존재했다는 사실조차 잊어버리곤 했습니다. 일주일 뒤에 같은 질문을 던지면, 이전에 의견을 냈었다는 사실을 전혀 인지하지 못한 채 전혀 다른 의견을 내놓았습니다. 이는 작은 버그가 아닙니다. "이 시장에서 무슨 일이 일어나고 있는지 말해달라"는 것이 시스템의 전제 조건인 상황에서, 어제 말한 내용을 기억하지 못한다는 것은 자격 미달입니다.

이것은 왜 내가 메모리(memory)를 사후 고려 사항이 아닌 아키텍처의 일급 객체(first-class part)로 취급하게 되었는지, 그리고 그렇게 했을 때 무엇이 변했는지에 대한 이야기입니다.

시스템이 하는 일

이 프로젝트는 전략적 시장 정보 시스템 (Strategic Market Intelligence System, SMIS)입니다. 기업, 섹터, 기술 트렌드와 같은 쿼리(query)를 입력하면, 고정된 순서에 따라 4개의 에이전트(agent)를 거치며 각 에이전트가 자신의 출력을 다음 에이전트에게 전달합니다.

시장 정보 에이전트 (Market Intelligence Agent) -> 트렌드 에이전트 (Trend Agent) -> 인사이트 및 권장 사항 에이전트 (Insights & Recommendation Agent) -> 비평가 에이전트 (Critic Agent)

이 시스템은 LangChain을 기반으로 구축되었으며, LLM으로는 Groq에서 실행되는 llama-3.3-70b-versatile을 사용하고, 웹 검색을 위해 Tavily를 사용하며, 파이프라인의 print() 출력을 스트리밍하여 각 에이전트가 단계를 완료하는 것을 실시간으로 볼 수 있는 Streamlit 프런트엔드를 갖추고 있습니다. 어떤 도구를 호출할지 결정하는 에이전트 프레임워크(agent framework)는 없습니다. 나는 이 유스케이스(use case)에서 LLM이 도구 호출(tool calls)을 즉흥적으로 수행하는 것이 아니라, 예측 가능하고 디버깅 가능한 단계(stages)를 원했기 때문에 명시적인 핸드오프(handoffs)가 있는 고정된 파이프라인(pipeline.py가 각 함수를 순서대로 호출함)으로 의도적으로 유지했습니다.

오케스트레이션(orchestration)은 부끄러울 정도로 단순합니다:

state = market_intelligence_agent(query)
state = trend_agent(state)
state = insights_recommendation_agent(state)
...

각 함수는 딕셔너리(dict)를 받아 자신의 출력을 추가하고 확장된 딕셔너리를 반환합니다. 그래프 라이브러리(graph library)도, 상태 머신(state machine)도 없습니다. 이러한 단순함은 의도적인 것이며, 덕분에 다음 결정을 내리기가 더 쉬워졌습니다.

핵심 문제: 결과가 따르지 않는 비평가 (Critic)

Agent 1은 웹을 검색하고 가공되지 않은 신호(raw signals)를 요약합니다. Agent 2는 상위 소스들을 전체적으로 읽고 반복되는 패턴을 추출합니다. 두 에이전트 모두 상태를 유지하지 않는(stateless) 방식이며, 현재 실행(run) 이외의 것에 대해서는 어떠한 의견도 갖지 않습니다. 이는 괜찮습니다, 그것이 그들의 역할이니까요.

Agent 3부터 흥미로워집니다. 이 에이전트는 트렌드 분석을 하나의 실행 가능한 권장 사항(actionable recommendation)으로 변환하는 역할을 합니다. 그다음 Agent 4, 즉 비평가(Critic)가 해당 권장 사항을 평가하고 APPROVED(승인), NEEDS REVISION(수정 필요), 또는 REJECTED(거부)라는 판결을 내립니다.

제가 초기에 직면했던 문제는 바로 이것이었습니다: 비평가의 판결이 갈 곳이 없었다는 점입니다. 판결은 Streamlit UI에 표시되고 사용자가 이를 읽으면 그대로 증발해 버렸습니다. 예를 들어, 비평가가 전기차(EV) 배터리 공급망 리스크를 과장했다는 권장 사항을 거부(REJECTED)하더라도, 그 판단은 다음에 누군가 전기차 배터리 공급망에 대해 물었을 때 시스템이 내놓을 답변에 아무런 영향을 미치지 못했습니다. 시스템은 연속된 실행에서 동일한 오류를 범할 수 있었고, 실제로도 그랬습니다. 거부된 내용 중 그 어떤 것도 다음 실행에서 확인할 수 있는 곳에 저장되지 않았기 때문입니다.

그것이 바로 Hindsight가 메우는 간극입니다. Hindsight는 단순히 분위기를 내기 위해 도입된 것이 아닙니다. 이는 파이프라인의 서로 분리되고 무관한 호출들 사이에서, Agent 4의 출력을 다시 Agent 3의 입력으로 연결해 줍니다.

코드 내에서 메모리가 실제로 존재하는 위치

저는 Hindsight 호출을 에이전트 함수 자체에 넣지 않고, tools.py에 있는 작은 래퍼(wrapper) 세트 뒤로 숨겼습니다. 덕분에 에이전트들은 순수한 비즈니스 로직처럼 읽히며, 메모리 클라이언트(memory client)는 교체 가능한 구현 세부 사항(implementation detail)이 됩니다.

세 가지 작업, 세 가지의 뚜렷한 역할: retain은 사실을 기록하고, recall은 이미 저장된 내용에 대해 유사도 스타일의 조회 (similarity-style lookup)를 수행하며, reflect는 제가 실제로 가장 유용하다고 느끼는 작업입니다. reflect는 단순히 일치하는 기억을 반환하는 것이 아니라, 그 모든 기억을 가로질러 판단 질문 (judgment question)에 대한 답변을 합성(synthesize)합니다. 이 차이는 보기보다 훨씬 중요합니다. 일반적인 벡터 조회 (vector lookup)는 "당신의 쿼리와 의미적으로 유사한 다섯 가지 항목이 여기 있습니다"라고 알려줄 뿐입니다. 반면 reflect를 사용하면 "이와 유사한 사례가 이전에 틀린 적이 있었나?"라고 물을 수 있으며, 제가 다른 LLM 호출을 통해 직접 요약해야 하는 다섯 단락의 글 대신 직접적인 답변을 얻을 수 있습니다. Hindsight 문서는 이를 기억에 대한 검색 (retrieval)과 추론 (reasoning)의 차이로 설명하며, 실제로 이것이 제가 느낀 바로 그 간극을 메워주었습니다. 저는 recall 결과 위에 두 번째 요약 단계 (summarization pass)를 구축하려던 참이었으나, reflect가 이미 그 역할을 수행하고 있다는 것을 깨달았습니다.

제 네 개의 에이전트 중 단 두 개만이 이 기능에 관여합니다. Insights & Recommendation 에이전트는 다음과 같이 읽습니다:

그리고 Critic은 자체적인 판결을 내린 직후 다음과 같이 작성합니다:

이것이 전체 루프입니다. 에이전트 4는 판결을 태그가 붙은 기억 (tagged memory)으로 다시 기록합니다. 그러면 에이전트 3는 미래의 어떤 실행 시점에 이를 recall하고 reflect하여 새로운 권장 사항을 생성합니다. Market Intelligence 및 Trend 에이전트는 의도적으로 완전히 무상태 (stateless)를 유지합니다. 이들은 매번 새로운 검색 (retrieval)과 추출 (extraction)을 수행하며, 이는 그들에게 적합한 방식입니다. 기억은 검색 계층 (retrieval layer)이 아니라 판단 계층 (judgment layer)에 속해야 합니다.

이것이 실제로 사용되는 모습

"2026년 EV 배터리 공급망 리스크"에 대해 파이프라인을 한 번 실행하면, Critic(비판자)이 "수정이 필요함(NEEDS REVISION)"이라는 결과를 반환할 수 있습니다. 이는 소싱 정보가 부족하여 권장 사항이 단기적 공급 제약을 과장하고 있다고 지적하는 것입니다. 해당 판결은 topic:ev-battery-supply-chain-risk-in-2026과 같은 태그와 함께 메모리에 기록됩니다.

몇 주 후 동일한 쿼리 또는 유사한 변형 쿼리를 실행합니다. 이때 recall_past_insights가 이전의 비판 내용을 일반 텍스트로 불러오고, reflect_on_findings가 주의가 필요한 질문에 직접적으로 답변합니다: 즉, 이전에 유사한 권장 사항이 지적된 적이 있으며, 그 이유는 다음과 같다고 답합니다. 이 내용은 에이전트 3(agent 3)의 시스템 프롬프트(system prompt)에 바로 통합되며, 에이전트 3은 지적된 실수를 반복하지 않도록 명시적으로 지시받습니다. 두 번째로 나오는 권장 사항은 첫 번째 실행의 실패를 통해 형성됩니다. 이는 제가 EV 배터리에 대한 규칙을 직접 작성했기 때문이 아니라, Critic의 판단이 일회성 출력(disposable output)이 아닌 지속 가능한 입력(durable input)이 되었기 때문입니다.

이것이 바로 제가 처음부터 원했던, 하지만 메모리가 루프(loop)에 연결되기 전까지는 얻지 못했던 동작입니다. 즉, 시스템이 동일한 방식으로 두 번 속이기 어려워지는 것입니다.

교훈 (Lessons learned)

  1. 메모리는 판단(judgment)이 일어나는 곳에 있어야 하며, 모든 곳에 있을 필요는 없습니다. 저는 "일관성을 위해" Hindsight를 네 개의 에이전트 모두에 연결하고 싶은 유혹을 느꼈습니다. 하지만 이는 대부분 낭비되는 쓰기 작업이 되었을 것입니다. 검색 에이전트(retrieval agents)는 지난주의 검색 결과를 기억한다고 해서 이득을 얻지 못하며, "트렌드(trend)" 탐지 또한 이상적으로는 과거 메모리의 수혜자가 되어야 하는데, 이는 제가 여전히 메우고 싶은 격차입니다. Critic-to-Recommender 루프야말로 메모리가 즉각적으로 그 가치를 증명한 지점이었습니다.

  2. 회상(recall)과 성찰(reflect)을 초기에 구분하십시오. 만약 제가 회상 방식의 조회(lookup) 기능만 가졌다면, 회상된 메모리들을 판단(judgment)으로 요약하기 위해 두 번째 LLM 호출을 별도로 구축해야 했을 것입니다. Vectorize의 에이전트 메모리 모델에서 성찰(reflect)을 별도의 기본 요소(primitive)로 사용할 수 있었던 덕분에, 메모리 계층이 소유해야 할 합성(synthesis) 로직을 재구현하는 수고를 덜 수 있었습니다.

  3. 쓰기 작업(write-back)이 없는 판결은 단순한 UI 장식에 불과합니다. Critic 에이전트는 실제로 무언가를 바꾸기 전까지 몇 주 동안 존재하기만 했습니다. 비판(critique) 후에 retain_insight()를 추가하는 것은 단 세 줄의 코드 변경이었지만, 이 한 줄이 "시스템이 의견을 가지고 있다"를 "시스템이 자신의 의견에 대한 기억을 가지고 있다"로 바꾸어 놓았습니다.

  4. 메모리 클라이언트를 얇은 래퍼(thin wrapper) 뒤에 유지하십시오. 에이전트 로직 내부에서 클라이언트를 직접 호출하는 대신, tools.py의 retain_insight, recall_past_insights, reflect_on_findings를 통해 모든 Hindsight 호출을 라우팅함으로써, agents.py를 전혀 건드리지 않고도 테스트하거나, 모킹(mock)하거나, 궁극적으로 백엔드 메모리 구현을 교체할 수 있었습니다.

  5. 단순한 슬러그(slug)를 사용하더라도 공격적으로 태그를 지정하십시오. 유지된 모든 메모리에 주제 태그를 다는 것(예: topic:ev-battery-supply-chain-risk-in-2026)은 화려하지는 않지만, 회상(recall)이 노이즈가 아닌 범위(scoped)를 갖게 만드는 핵심 요소입니다. 이것이 없다면 모든 회상 호출은 관련 있는 부분(slice) 대신 전체 메모리 뱅크에서 데이터를 가져오게 될 것입니다.

파이프라인 자체는 여전히 직선 형태입니다. 네 개의 함수가 순서대로 호출되며, 분기 로직(branching logic)이나 다음에 무엇이 일어날지를 결정하는 에이전트 프레임워크(agent framework)도 없습니다. 바뀐 점은 이 직선에 이제 '과거'가 생겼다는 것입니다. Critic(비평가)의 역할은 일회성 의견을 내는 것에 그치지 않고, 다음 실행 시 반드시 고려해야 하는 제약 조건(constraint)이 되었습니다. 이는 정체되어 있지 않은 시장에 대해 조언을 제공하는 것이 핵심인 시스템에게, 그동안 내내 결여되어 있었던 부분이었습니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0