메뉴
HN
Hacker News 27일 전

신들의 황혼: 11개 LLM의 코드 리팩토링 비교

IMP
8/10
핵심 요약

저자는 실제 LangGraph 프로젝트에서 비대해진 하나의 '갓 노드(God Node)'를 리팩토링하는 과제를 미국과 중국의 11개 LLM에 제시하고, 이들이 제안한 코드 개선안을 서로 교차 평가하게 하여 성능을 심층 비교했습니다. 이 실험은 단순한 코드 생성 능력을 넘어, 복잡한 아키텍처를 개선할 최적의 LLM과 가장 신뢰할 만한 평가자 모델을 찾는 데 목적이 있습니다. 개발자 입장에서 여러 AI 모델을 코딩 및 코드 리뷰에 어떻게 전략적으로 활용해야 하는지를 보여주는 매우 유용한 실용 사례입니다.

번역된 본문

신들의 황혼. 코드 재구성 작업에 도전한 Fable과 11개의 LLM 비교기.

다른 언어: 이 글은 러시아어로도 제공됩니다: Гибель богов .

소재 및 원시 데이터 모든 11개 모델의 제안, 교차 리뷰, 개별 항목 실행 결과 및 순위 책정 스크립트는 다음 링크에 공개되어 있습니다: 소재 및 실험 재현.

이 글은 단일 실험에 대한 상세한 보고서입니다. 저자는 실제 LangGraph 에이전트 코드에서 '갓 노드(God Node, 하나의 노드에 너무 많은 로직이 뭉쳐있는 안티 패턴)'를 가져와 5개의 미국 모델과 6개의 중국 모델에게 이를 분해하고 개선할 방안을 제안하도록 요청한 다음, 서로의 제안을 교차 평가하게 했습니다. 그 후, 이 문제와 관련하여 어떤 모델을 가장 신뢰할 수 있는지 알아내기 위해 세 가지 다른 방식을 시도했습니다.

목차 원래의 문제 갓 노드가 무엇이며 왜 위험한가 plan 노드가 실제로 한 일 레몬으로 레모네이드 만들기 (불리한 상황을 역이용하기) 왜 이런 실험을 했으며, 실험은 어떻게 설정되었는가 1단계. 모델들이 개선안 제안 제안 표 각 제안에 대한 추가 설명 2단계. 모델들이 개선안 평가 리뷰 표 각 리뷰에 대한 추가 설명 3단계. 젖은 수영복 콘테스트 (치열한 경쟁) 누가 무엇을 잘하는지 결정하기 접근법 1. 점수들이 일치하는가? 최고의 제안안 선택 접근법 2. 핵심 논점별로 리뷰 비교하기 최고의 분석가 선택 접근법 3. 의견의 중심과 메도이드(Medoid, 대표값) 최고의 분석가 다시 선택하기 기계의 신(Dues ex machina) 한 번 더 최고의 분석가 선택하기 핵심 정리 생성자(Generator)로 어떤 모델을, 평가자(Evaluator)로 어떤 모델을 사용해야 하며, 어디서 마음의 평화를 찾을 수 있을까.

원래의 문제 상황은 익숙합니다. Data Sanity의 과정을 함께 듣는 동료들과 AI 에이전트를 구축하고 있었는데, 빠르게 쌓여가는 기능들 속에서 프로젝트 내부 에이전트 중 하나의 상태 그래프(LangGraph)가 마침내 이런 모습을 하고 있다는 것을 갑자기 깨달았습니다:

flowchart TD planner_start([START]) --> plan[plan] plan -->|search| search[search] plan -->|ask_user| ask_user[ask_user / interrupt] plan -->|reflect| reflect[reflect] plan -->|calculate| calculate[calculate] plan -->|finish| finish[finish] search -->|last_observation| observe[observe] search -->|no hits / backend failure| plan observe --> plan calculate --> plan ask_user --> observe_user[observe_user] observe_user --> plan reflect --> plan finish --> planner_end([END])

언뜻 보기에는 그저 귀여운 작은 문어처럼 보이므로 걱정할 것이 없어 보입니다. 하지만 이 문어의 작은 머리 안에 얼마나 많은 논리가 숨겨져 있는지 알게 되는 순간, 우리가 보고 있는 것이 명백한 안티 패턴이라는 사실을 깨닫게 됩니다. 이러한 패턴을 우리는 '갓 노드(God Node)'라고 부릅니다.

이 경우 plan 노드는 약 350줄에 달하는 방대한 로직을 숨기고 있었습니다. 여기에는 반복 검사, 지역 및 통화에 대한 기본 부트스트랩 질문, 스키마 준비, 데이터 수집 작업 라우팅, LLM 호출, 이후 결정을 정정하는 과정 등이 포함되어 있습니다.

문제는 단순히 함수의 크기가 크다는 데 있지 않습니다. 중요한 오케스트레이션 로직이 단일 노드 내부에 숨겨져 있으면, 그래프는 더 이상 시스템의 명확한 표현 역할을 하지 못합니다. 시스템을 설명하기도, 디버깅하기도, 테스트하기도 더 어려워지며, 코드를 변경할 때 발생할 수 있는 위험도 훨씬 커집니다.

따라서 여기서 우리가 해야 할 명백한 작업은 단순히 '거대한 함수를 여러 조각으로 자르는 것'이 아닙니다. 숨겨져 있던 제어 로직을 그래프의 최상위 수준으로 끌어올려, 결과적인 아키텍처를 더 명확하게 만들고 향후 개발을 더 용이하게 만드는 것입니다.

plan 노드가 실제로 한 일 이 그래프가 묘사하던 에이전트는 광범위하게 말해 다운스트림 계산을 위해 다양한 매개변수를 수집하는 역할을 했습니다. 이러한 매개변수 중 일부는 인터넷에서 영리하게 검색하여 찾았고, 일부는 사용자에게 직접 물어보았습니다. 그리고 이 모든 작업은 완전히 결정론적이지 않은 알고리즘에 의해 수행되었습니다. 특정 대화의 문맥에 따라 동일한 매개변수를 얻는 올바른 방법이 크게 달라질 수 있었기 때문입니다.

다음은 plan 노드 내부에 숨겨져 있던 실제 기능들의 목록입니다:

  • 반복 루프: 반복 횟수 증가, 새로운 계획 단계 진입, 상태 == "aborted" 및 최대 반복 횟수(max_iters) 확인
  • 지역 기본 질문: _needs_region_question() 확인 및 핵심 지역 정보를 얻기 위해 ask_user로 강제 전환
  • 통화...
원문 보기
원문 보기 (영어)
Twilight of the Gods. Fable and 10 more LLMs on a Code Reorganization Task. Comparison. ¶ Other languages Эта статья также доступна на русском: Гибель богов . Materials & raw data All 11 model proposals, the cross-reviews, the theses runs, and the ranking script are published here: Materials & reproduce this experiment . This is a detailed write-up of one experiment. I took a god node from a real LangGraph agent and asked 5 American and 6 Chinese models first to propose how to untangle it, then to evaluate each other's proposals. After that, I tried three different ways to figure out which of them to trust on the matter. Contents The original problem What a god node is and why it's dangerous What the plan node actually did Lemonade from lemons Why all this, and how the experiment is set up Stage one. The models generate proposals The proposals table A bit more on each proposal Stage two. The models evaluate the proposals The reviews table A bit more on each review Stage three. The wet swimsuit contest Deciding who's good at what Approach one. Do the scores agree? Picking the best proposal Approach two. Comparing reviews by theses Picking the best analyst Approach three. Center of opinion and medoid Picking the best analyst again Deus ex machina . Picking the best analyst one more time Takeaways Which model to use as a generator, which as an evaluator, and where your heart will find peace. The original problem ¶ You know how it goes: you're building a practice AI agent with the fellas on a course by Data Sanity , and amid the colorful whirl of rapidly accreting features you suddenly notice that one of the project's internal agents has a state graph (LangGraph) that looks like this: flowchart TD planner_start([START]) --> plan[plan] plan -->|search| search[search] plan -->|ask_user| ask_user[ask_user / interrupt] plan -->|reflect| reflect[reflect] plan -->|calculate| calculate[calculate] plan -->|finish| finish[finish] search -->|last_observation| observe[observe] search -->|no hits / backend failure| plan observe --> plan calculate --> plan ask_user --> observe_user[observe_user] observe_user --> plan reflect --> plan finish --> planner_end([END]) At first glance this is just a cute little octopus — nothing to worry about. But once you know how much logic this octopus has to hold in its modest eight-legged head, it becomes clear right away that we're looking at an anti-pattern. In this case, let's call it a god node. The plan node hides about 350 lines of logic, including iterative checks, bootstrap questions about region and currency, schema preparation, acquisition-task routing, the LLM call, the subsequent correction of the decision, and so on. The problem isn't just the size of the function. When important orchestration is hidden inside a single node, the graph stops being a representation of the system. It's harder to explain, harder to debug, harder to test, and more dangerous to change. So the obvious task isn't merely to "chop a big function into pieces" but to lift the hidden control logic up to the graph level, so that the resulting architecture becomes clearer and more amenable to further development. What the plan node actually did ¶ The agent this graph was meant to describe was, broadly, in the business of collecting various parameters for downstream calculations. Some of these parameters it cleverly searched for on the internet; some it asked the user about. And it did all this by a not-fully-deterministic algorithm, because depending on the context of a particular conversation, the right way to obtain the same parameter could vary considerably. Here is the set of real functions that had been packed into the plan node: Responsibility What logic was hidden inside plan Iteration loop Incrementing iterations , entering a new planning step, checking status == "aborted" and max_iters Region bootstrap question The _needs_region_question() check and a forced transition to ask_user for core.region Currency bootstrap question The _needs_currency_question() check and a forced transition to ask_user for core.currency Proactive decomposition Generating dynamic_decompositions for fields that need to be broken into components Assembling acquisition recipes Calling build_dynamic_recipes() and preparing the task structure for the subsequent field collection Schema preparation Calling compose_ready_fields() , merging ready component fields into aggregates, and updating schema Calculator limits Checking the calculator attempt limit, a successful or already-current calculation, and other stopping conditions Recovery after a blocked calculation Handling the blocked-calculator scenario, finding the next data-collection task, and, if needed, a fallback decomposition for the problem field General data-collection routing Choosing the next data-collection task without the LLM, including a fast pass over already-opened and component tasks Auto-finish logic Checking whether all source data has been collected, whether any fields remain to act on, and whether the loop can finish without extra steps LLM planning Gathering prompt context, calling _llm().structured(...) , and obtaining a PlannerDecision Post-LLM decomposition Generating an additional decomposition for the model-chosen field if it needs to be broken into components Redirecting and normalizing decisions Redirecting decisions for derived fields, forcing ask_user -> search for fields better found via search, and other deterministic rewrites after the LLM Per-field retries and limits Detecting repeated searches, limits on search and ask_user , and switching to ask_user , reflect , or finish once the limits are exhausted Calculation-decision correction Fixing a premature finish : if the calculation isn't complete yet, the decision is rewritten to calculate or routed into an extra re-evaluation step Bookkeeping state management Resetting or updating decision , decision_origin , llm_failed , status , and other transient flags that accompanied the branching Logging and event dispatch Logging the final decision, emitting progress events, and assembling the final state update before returning from the node Lemonade from lemons ¶ So, we find ourselves in a situation many people will recognize all too well these days. Monsieur Claude has sculpted us a little masterpiece out of spaghetti. Combing out that spaghetti is nowhere near as much fun as bolting on feature after feature with no review whatsoever. But that's only true until you realize you can reduce the entropy with the same tool that raised it. And that's a far more pleasant prospect. But can you trust the untangling of code to the very model that, given half a chance, so gleefully tangles it up? To answer that, I decided to collect several independent architectural proposals from different models, and compare what they'd advise. Eleven models were invited onto the runway for judging: GPT-5.4 GPT-5.5 DeepSeek-4-pro Gemini-3.1-pro GLM-5.1 Kimi-2.6 MiMo-2.5-pro Opus-4.7 Qwen-3.6-plus Qwen-3.7-max Fable-5 First, each of them made its own proposal for splitting plan . Then the models switched into evaluator mode, read the entire set of finished proposals, and ranked them. To make sure I was collecting independent opinions rather than a relay of retellings of one lucky text, the following conditions were enforced: While the proposals were being generated, the models couldn't see one another's work. While the analyses were being generated, they saw every proposal but none of the other analyses. Each run took place in a fresh session. All of the work was done in OpenCode with the Oh My Openagent plugin, at maximum reasoning effort for every model. Stage one. The models generate proposals ¶ In the first stage, each model proposed its own way to lift the plan node's logic up to the graph level. The prompt used to generate each proposal (only the output filename changed): look at docs/planner-graph-ref/current-graph.md. Looks like "plan" n