AIDataWorkflow

데이터 AI한테 외주주기

·16 min read

어제 쓴 품질관리에 연장선으로

직접 데이터 가지고 AI에다가 정제 맡기는걸 구현해보겠다.

대충 레시피 재료 데이터랑 조리 단계 데이터가 있다고 하겠다.

주의깊게 볼 필요는 없다.

[레시피 재료 데이터] 2,7016813,떡국떡,400,g,재료,2025-10-05T02:06:33.045562+00:00 3,7016813,다진소고기,100,g,재료,2025-10-05T02:06:33.045562+00:00 4,7016813,멸치육수,800,ml,재료,2025-10-05T02:06:33.045562+00:00 5,7016813,대파,1/3,대,재료,2025-10-05T02:06:33.045562+00:00 6,7016813,계란,2,개,재료,2025-10-05T02:06:33.045562+00:00

[조리 단계 데이터] 1,7016813,0,새해가 되면 뜨끈한 떡국 한 그릇이 생각나는데요. 오늘은 집에서 간단하게 요리할 수 있는 멸치육수로 끓이는법을 준비했어요.,,,2025-10-05T01:24:03.850954+00:00 2,7016813,1,냄비에 물을 넣고 다시팩을 넣어 육수를 우려주었어요. 냄비,냄비,,2025-10-05T01:24:03.850954+00:00 3,7016813,2,떡국떡은 물에 헹구어 물기를 털어줘요. 채반,채반,,2025-10-05T01:24:03.850954+00:00 4,7016813,3,다진 소고기를 핏물을 제거해 주어요. 키친타올,키친타올,,2025-10-05T01:24:03.850954+00:00 5,7016813,4,"계란은 알끈을 제거하고 곱게 풀어줘요. 볼 , 포크","볼, 포크",,2025-10-05T01:24:03.850954+00:00 6,7016813,5,"돌돌 말아 얇게 채를 썰어줘요. 프라이팬 , 뒤집개 , 도마 , 조리용나이프 식힌 뒤 채 썰어주어야 부서지지 않고 곱게 지단을 썰 수 있어요.","프라이팬, 뒤집개, 도마, 조리용나이프",,2025-10-05T01:24:03.850954+00:00

만약 슥 봤는데 뭔가 익숙함이 느껴진다면 내 글을 자주 본 것이다.

허구한날 AI랑 계란지단으로 싸우던게 저거다.

아무튼 중요한건 조리 단계다.

재료 데이터의 대한 신뢰성을 조금 높게 두고, 조리 단계에서 품질과 통일성을 관리할 것이다.

순서는 전처리 -> LLM -> 검증 순으로 진행된다.

Everything is workflow

어제 말했듯이 우리는 '기본' 재료를 바탕으로 조합해서 복잡한 맛을 끌어내는 것이다.

즉 쓰는 기술이 복잡한건 아니다.

그냥 ~하는 애 정의 => 실행

그리고 ~하는 애 하위에는 또 복잡한 ~하는 애가 들어가있다.

편의상 에이전트라고 표현하면 이 에이전트들이 중첩되어있는 구조다.

LangGraph가 '상태'와 '순환' 이라고 어제 말했듯이 여기에 이제 LangGraph가 끼면서 데이터 주고받고 반복하는 복잡한 워크플로우가 되는 것이다.

워크플로우에 대해 직관적으로 보기 위해 LangGraph 정의 부분을 보여주고자 한다.

그냥 올리면 라인이 다 박살날꺼 같아서...부셔지기전에 부셔버린다는 마인드로 내가 직접 쪼개주겠다.

먼저 워크플로우에서 graph에 대한 정의를 들어간다.

여기서 RecipeState에 다양한 상태를 넣어서 과정에서 공유 할 수 있도록 한다.

def _create_graph(self) -> StateGraph:
    workflow = StateGraph(RecipeState)

워크플로우에서 사용할 우리가 만든 노드들을 등록해준다.(_node 붙은 애들은 내가 구현한거다)

workflow.add_node("rule_filter", self.rule_filter_node)
workflow.add_node("holistic_clean", self.holistic_clean_node)
workflow.add_node("rule_validate", self.rule_validate_node)
workflow.add_node("ai_validate", self.ai_validate_node)
workflow.add_node("retry_decision", self.retry_decision_node)
workflow.add_node("finalize", self.finalize_node)

이제 진입 포인트를 "rule_filter"라고 규칙기반으로 데이터 전처리하는 노드로 정해준다.

우리는 규칙기반의 데이터 전처리를 먼저 시작할 것이다.

만약 성공하면? "holistic_clean" 노드로 간다.

실패하면 끝내고

workflow.set_entry_point("rule_filter")
workflow.add_conditional_edges(
    "rule_filter",
    self.should_proceed_after_filter,
    {
        "proceed": "holistic_clean",
        "reject": "finalize"
    }
)

워크플로우는 멈추지 않는다. "holistic_clean"으로 가라 했으면 거기서도 다음 포인트를 잡아준다.

"holistic_clean"은 "rule_validate"를 바라보고...그 다음 "ai_validate"를 바라보고..

"ai_validate"도 조건 명시해주고... 언제까지? workflow.compile() 끝날 때 까지

workflow.add_edge("holistic_clean", "rule_validate")
workflow.add_edge("rule_validate", "ai_validate")

workflow.add_conditional_edges(
    "ai_validate",
    self.should_retry,
    {
        "retry": "retry_decision",
        "finalize": "finalize"
    }
)

workflow.add_conditional_edges(
    "retry_decision",
    self.can_retry,
    {
        "holistic_clean": "holistic_clean",
        "finalize": "finalize"
    }
)

workflow.add_edge("finalize", END)
return workflow.compile()
노드들
rule_filter_node

우리의 진입점 rule_filter_node에 대해 설명하겠다.

단순하게 '재료가 없어요!', 'NaN 같은 이상한 데이터가 있어요!' 등과 같이 개발자가 사전에 필터링 할 수 있는 것을 하는 것이다.

만약 에러가 너무 많거나, 크리티컬하다? 여기서 과감하게 종료하고 이 레시피는 끝내버리는 것이다.

중요한점은 치명적이지는 않지만, 이상증상이 발견된 경우, 초기에 RecipeState에 공유하는 것이다.

즉 상태란 서로 공유되고 참조하기에, AI한테 검증 맡길때 "애 이러이러한데 이상함" 이라고 의견 공유가 가능하다는 것이다.

holistic_llm_node

규칙 볼꺼 다 봤으면 AI한테 1차로 정제를 맡긴다.

재료 + 조리단계 + 요약 정보 가진것을 싹다 모아다가 LLM한테 한번 던지는 것이다.

물론 출력을 정해진 템플릿으로 줘서, JSON으로 요청한다.

처음에는 재료 먼저해서 따로 하고, 요리 스텝도 따로 요청하고 했지만,

요즘 모델이 받아들이는 컨텍스트 양이 많기도 하고, 써보니깐 굳이 나눌 필요가 없었다.

다만 출력 양에 따라서 어디에 집중하는지가 달라지긴 하니, 필요하다면 재료 정제 따로, 이후 레시피 정제 따로 정도로 나눌 수 있다.

아무튼 정해진 JSON 포멧에 따라 도착을 했는지 한번 검증을 돌린다.

이상이 없다면? 일단 그대로 들고간다.

구조가 중요하지 상세 프롬프트나, 뭘 목적으로 하는지 크게 중요하지는 않으니 코드는 넘어가겠다.

rule_validate_node

우리의 목적은 품질도 있지만 통일성도 존재한다.

아까 검증한 규칙에 이어서 형식, 의미, 도메인 3가지 방향성으로 추가적인 검증을 진행한다.

형식: 너무 짧거나, 길지는 않은지, 문장이 종결이 됬는지, 이모티콘 또는 구어체가 없는지 등

의미: 중복 내용, 요리에 쓰는 동사들을 기준으로 동작 동사가 포함되 있는지 등

도메인: 요리에 특화된 논리적 순서(완성 키워드 이후 추가적인 조리 등), 위험한 표현(화상, 폭팔, 위험 등)

정답은 없고 비즈니스 성격에 정리된 데이터에서 추가로 검증할 만한 부분을 찾는 로직이다.

ai_validate_node

AI로 생성을 했다면? AI로 검증하는 단계다.

우리가 지금까지 규칙 기반으로 설정한 주의사항을 포함해 생성된 데이터를 넣어서 검증을 요청한다.

스코어링 기법을 사용해서(이전에 규칙 단계에서도 위중함에 따라서 별도 스코어링을 했다.) AI 점수를 산출해내고, 평가 근거를 명시하도록 한다.

AI가 감지할 수 있고 치명적이지 않은 경우 휴먼 체크가 필요하다고 안내하도록 별도로 템플릿에 명시한다.

즉 위험한거는 즉각 대응하고, 적당한 사항은 인간이 한번 검수하도록 하는 것이다.

인간이 레시피를 짜는 것이기에 돌려보면 AI가 온갓 훈수를 던져된다.

파멸적인 인간의 미각에 유감을 표현하되, 하지 말라 싶은거 하는거 같으면 바로 대응하는거다.

기름에 물넣기! => 바로 대응

밥에 초코시럽~ => 어휴 인간 수듄...일단 PASS

retry_decision_node

현재까지의 시도를 바탕으로 재시도 여부를 결정하는 것이다.

규칙 기반 스코어링, AI 기반 스코어링 등으로 결정도 포함이고,

AI와 사전에 협의해서 이슈 리포트 결과에 '즉각','재시도' 등의 내용을 넣으라고 지시한뒤, 감지되면 재시도 시키는 것이다.

결과

이런식으로 워크플로우를 실행하면 다음과 같이 동작한다.

정제 성공 (confidence: 0.98) 변경 사항: ["참기름 '1T' → '1큰술' ( 단위 표준화)", "다진마늘 '1t' → '1작은술' (단위 표준화)", "국간장 '1T' → '1큰술' (단위 표 준화)", "참기름 '1/2T' → '1/2큰술' (단위 표준화)", "대파 '1/3대' 유지 (재료 목록 기준)", '소금과 김가루는 조리 단계에 사용되지 않음 (재료 목록 유지)', "조리 단계에서 '참치액' 오류 제거 → '참기름 1/2큰술'", '요약 문체 통일 및 구어체 제거', '단계 순서와 도구 기록 정제']
[7016813] 규칙 기반 검증... 규칙 기반 점수: 96.5/100 [7016813] AI 기반 검증... AI 품질 평가 시작: 소고기떡국 AI 점수: 95.0/100 (신뢰도: 0.97)
평가 근거: 소고기떡국은 전통적인 떡국에 소고기를 추가한 창의적 요리로서, 재료와 조리 단계는 일반적으로 가능하며, 생명 위험 또는 물리적 불가능성 없음. 그러나 참기름이 중복되고, 일부 단계(예: 핏물 제거, 알끈 제거)는 전 통적이지 않으며, 도메인 상식에 어긋난 점이 있음. 재료 목록은 신뢰할 수 있으며, 모든 조리 단계가 논리적으로 연결되어 있음. 기술적 오류 없으나, 일부 창의적 요리로 인한 도메인 의문이 존재함. 전체적으로 사용 가능하지만, 몇 가지 개선 사항이 필요함. ⚠️ 휴먼 체크 필요: 재료 관련 경고 1개 발견 [7016813] 최종화... === 처리 완료 === 규칙 점수: 96.5 AI 점수: 95.0 재시도 횟수: 0 LLM 호출: 1회

✓ Recipe 7016813 completed: Rule Score: 96.5/100 AI Score: 95.0/100 LLM Calls: 1 Retries: 0 ⚠️ 휴먼 체크 필요: 재료 관련 경고 1개 발견

데이터 관련해서 극단적인 케이스를 몇개 들고와 보겠다.

처리전: 이 상태로 3분간 끓여주시면 완성이에요~~~~!!!(사먹는 맛을 원하시면 다시다 1스푼 넣으세요^^) 처리후: 이 상태에서 3분간 끓여주시면 완성됩니다. 사먹는 맛을 원하시면 다시다 1작은술을 추가해 주세요. 특징: 전처리 단계에서 이모티콘 및 과도한 특수문자 제거

처리전: 속초에서 먹었던 가마솥손두부집이니까 국산콩손두부를 선택해서 구입했습니다. 처리후: 국산콩 손두부를 두툼하게 썰어 준비한다. 특징: 국산콩(재료에 명시됨)은 남기고, 사설 및 개인적 이야기인 속초 등은 지워버린다.

처리전: 통후추 갈갈 20바퀴 돌리고 섞어줍니다. 처리후: 통후추 20바퀴를 섞어 양념장을 미리 만들어 숙성시킵니다. 특징: 갈갈을 이해 못해서 20바퀴를 그대로 정제했지만, 검증 단계에서 이상현상으로 포작됨

AI 점수: 80.0/100 (신뢰도: 0.96) 평가 근거: 레시피 전체 구조는 논리적으로 연결되어 있으며, 재료 목록은 신뢰할 수 있고, 원본 출처가 명확하지 않아도 기술적 오류 없음. 다만 '통후추 20바퀴바퀴'는 비현실적이거나 전통적인 표현이 아니며, 도메인 의문 으로 간주됨. 조리 단계는 순서가 명확하고, 중복 없으며 생명 위험 또는 물리적 불가능성 없음. 재료 적절성과 조리 논리성은 높지만, 일부 요소(예: 홍고추 사용 방식, 통후추 단위)는 창의적 요리로 해석 가능하나, 전통적 맥락에서 다소 어긋남. 따라서 기술적 오류 없이도 도메인 의문은 존재하지만, 전체적으로 사용 가능함.

⚠️ 휴먼 체크 필요: 재료 관련 경고 4개 발견

AI라고 완전한건 아니고, 일부 세밀한 부분에 있어서는 개발자의 노력으로 추가적인 디테일이 필요하다.

하지만 수만건의 데이터를 한번에 정제하고 처리할 수 있다는 점에서 AI의 효용성이 높음을 알 수 있다.

← Previous
LangChain의 기억과 품질 관리
Next →
OpenAI DevDay 2025