[Feat] Mock 기반 로드맵 생성 전체 워크플로우 구현#23
Conversation
- 최종 로드맵 결과 저장을 위해 그래프 상태(RoadmapState)에 final_roadmap 필드 추가
- Phase 4에 해당하는 synthesize_final_roadmap 노드 로직 추가 - 이전 단계 데이터를 조합하여 LLM에 최종 응답 생성을 요청 - PydanticOutputParser를 사용하여 CourseResponse 스키마 준수
- synthesize_final_roadmap 노드를 그래프에 추가 - fetch_places_from_slots → synthesize_final_roadmap 엣지를 연결하여 전체 워크플로우 완성
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. Walkthrough최종 로드맵 생성 노드 Changes
Sequence Diagram(s)sequenceDiagram
participant Workflow as Roadmap Workflow
participant Node as synthesize_final_roadmap
participant LLM as LLM Service
participant Parser as Pydantic Parser
participant State as RoadmapState
Workflow->>Node: invoke(state with skeleton_plan, fetched_places, course_request)
Node->>Node: _prepare_final_context(state) -> (itinerary_context, daily_places)
Node->>LLM: send(system_prompt, human_prompt + itinerary_context)
LLM-->>Node: raw_response (may include code fences)
Node->>Node: strip code fences
Node->>Parser: parse(raw_response) using CourseResponseLLMOutput
Parser-->>Node: parsed_output
Node->>State: merge parsed_output + daily_places -> final_roadmap
Node-->>Workflow: return updated state
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related issues
Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@app/graph/roadmap/nodes.py`:
- Line 395: The daily_places_for_schema entry stores a Python date object under
the key "daily_date" which will fail JSON serialization; update the code that
appends to daily_places_for_schema (the line building {"day_number": day_number,
"daily_date": current_date, "places": day_places}) to convert current_date to a
string (e.g., current_date.isoformat() or str(current_date)) so that
final_roadmap can be serialized without error; ensure any other usages that
expect a date object are adjusted accordingly.
- Around line 453-456: The LLM output currently includes an "itinerary" that is
both requested in the prompt and then immediately overwritten
(parsed_data["itinerary"] = daily_places), causing wasted tokens and brittle
parsing; create a new LLM-only Pydantic model (e.g., CourseResponseLLMOutput
with fields title, llm_commentary, next_action_suggestion) in
app/schemas/course.py, remove the "itinerary" instruction from the prompt (lines
~430-431), change the parser call in nodes.py to parse into
CourseResponseLLMOutput instead of CourseResponse (replace
parser.parse(content).model_dump() usage), and then compose the final roadmap by
merging the parsed LLM fields with the existing daily_places (instead of
overwriting parsed_data["itinerary"]).
🧹 Nitpick comments (3)
app/graph/roadmap/workflow.py (1)
20-23: 에러 발생 시 불필요한 노드 실행이 계속됩니다.현재 워크플로우는 모든 노드가 무조건 순차 실행됩니다. 각 노드 내부에서
state.get("error")체크 후 early return하는 방식은 작동하지만, 에러 발생 시에도 후속 노드가 호출되어 불필요한 오버헤드가 발생합니다.LangGraph의
add_conditional_edges를 활용하면 에러 상태에서 즉시END로 라우팅할 수 있습니다.app/graph/roadmap/nodes.py (2)
382-394: 첫 번째 장소만 사용하는 하드코딩 — 실제 서비스 전환 시 문제 발생 가능.주석에 "Mock 서비스에서 1개만 반환하므로"라고 명시되어 있지만, 실제
PlacesService는 여러 장소를 반환할 수 있습니다. 현재 코드는places[0]만 사용하고 나머지를 무시합니다.장소 선택(랭킹/필터링) 로직을 분리하거나, 최소한
TODO주석을 남겨 실제 서비스 전환 시 대응해야 할 부분임을 명확히 해주세요.
460-462: 로거에서 f-string 대신%포매팅을 사용하세요.
logger.error(f"...")형태는 로그 레벨이 비활성화되어도 항상 문자열 포매팅이 실행됩니다. 기존 코드(예: Line 187, 225)에서%s포매팅을 사용하고 있으므로 일관성을 유지하세요.수정 제안
- logger.error(f"최종 로드맵 생성 실패: {e}", exc_info=True) - return {**state, "error": f"최종 로드맵 생성에 실패했습니다: {e}"} + logger.error("최종 로드맵 생성 실패: %s", e, exc_info=True) + return {**state, "error": f"최종 로드맵 생성에 실패했습니다: {e}"}
- _prepare_final_context 함수에서 daily_date를 isoformat() 문자열로 변환하여 TypeError 발생 가능성 제거
- LLM의 역할을 텍스트 생성으로 명확히 하고, 불안정한 itinerary 파싱 로직 제거 - LLM 출력 전용 Pydantic 모델(CourseResponseLLMOutput) 추가
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
app/graph/roadmap/nodes.py (1)
296-303:⚠️ Potential issue | 🟡 Minor프로덕션 코드에서
tests.mocks를 import하고 있습니다.TODO 주석으로 의도를 문서화했으나, 배포 방식에 따라
tests/패키지가 제외될 수 있습니다. 특히 wheel 빌드나 특정 CI/CD 파이프라인에서 테스트 디렉토리를 제외하면 런타임에ModuleNotFoundError가 발생합니다.#22이슈에서 실제 서비스로 교체될 때까지, Mock 클래스를app/services/하위로 이동하거나 환경 변수 기반 분기를 추가하는 것을 권장합니다.
🤖 Fix all issues with AI agents
In `@app/graph/roadmap/nodes.py`:
- Around line 367-404: The function _prepare_final_context currently reads
state["skeleton_plan"] and state["fetched_places"] directly and can produce an
empty LLM context if those values are present but empty; add explicit validation
at the start of _prepare_final_context to ensure skeleton_plan is a non-empty
list and fetched_places is a non-empty dict (and that course_request.start_date
exists after CourseRequest.model_validate), and if any are missing/empty raise a
clear ValueError (or return a well-defined empty context marker) with a message
referencing the failing keys so the caller (synthesize_final_roadmap) can handle
it; this ensures you don’t pass empty context to the LLM and makes failures
observable.
🧹 Nitpick comments (4)
app/schemas/course.py (1)
120-125:CourseResponse와의 필드 중복을 상속으로 줄일 수 있습니다.
CourseResponseLLMOutput의 세 필드가CourseResponse와 동일합니다. 향후 필드 설명이나 타입이 변경될 때 두 곳을 동기화해야 하는 부담이 생깁니다.♻️ 상속 활용 제안
-class CourseResponseLLMOutput(BaseModel): - """LLM이 생성할 필드만 포함하는 파싱 전용 모델.""" - - title: str = Field(..., description="여행 로드맵의 제목") - llm_commentary: str = Field(..., description="코스 선정 이유 및 전체 흐름 설명") - next_action_suggestion: str = Field(..., description="사용자에게 제안할 다음 액션") +class CourseResponseLLMOutput(BaseModel): + """LLM이 생성할 필드만 포함하는 파싱 전용 모델. + + CourseResponse에서 itinerary를 제외한 텍스트 필드만 포함합니다. + """ + + title: str = Field(..., description="여행 로드맵의 제목") + llm_commentary: str = Field(..., description="코스 선정 이유 및 전체 흐름 설명") + next_action_suggestion: str = Field(..., description="사용자에게 제안할 다음 액션")현재 필드가 3개로 적어 상속 도입의 실익이 크지 않으므로, 현 상태를 유지해도 무방합니다. 다만 필드가 늘어날 경우
CourseResponseLLMOutput을 기본 클래스로 삼고CourseResponse가 이를 상속하는 구조를 고려해 주세요.app/graph/roadmap/nodes.py (3)
467-469:logger.error에서 f-string 대신%포매팅을 사용하세요.파일 내 다른 로깅 호출(예: Line 192, 230)은
%s포매팅을 사용하는데, 여기만 f-string을 쓰고 있습니다. f-string은 로그 레벨과 무관하게 즉시 평가되며, Sentry 등 로그 집계 도구에서 동일 에러를 그룹핑하지 못하게 됩니다.♻️ 수정 제안
- logger.error(f"최종 로드맵 생성 실패: {e}", exc_info=True) - return {**state, "error": f"최종 로드맵 생성에 실패했습니다: {e}"} + logger.error("최종 로드맵 생성 실패: %s", e, exc_info=True) + return {**state, "error": f"최종 로드맵 생성에 실패했습니다: {e}"}
447-451:course_request가 raw dict로 프롬프트에 삽입되어 LLM이 Python repr을 받게 됩니다.Line 448에서
state["course_request"](dict)가 그대로{course_request}자리에 들어갑니다.str(dict)는datetime.date(2026, 1, 15)같은 Python repr을 포함하므로 LLM 입력 품질이 떨어질 수 있습니다.
_prepare_final_context에서 이미CourseRequest.model_validate()를 하고 있으므로, 해당 객체의.model_dump_json()을 활용하거나 별도로 사람이 읽기 좋은 요약 문자열을 만들어 전달하는 것을 권장합니다.♻️ 예시
- # 1. LLM에 전달할 컨텍스트 데이터 준비 - itinerary_context, daily_places = _prepare_final_context(state) - course_request = state["course_request"] + # 1. LLM에 전달할 컨텍스트 데이터 준비 + itinerary_context, daily_places = _prepare_final_context(state) + course_req = CourseRequest.model_validate(state["course_request"]) + course_request_json = course_req.model_dump_json(indent=2)프롬프트에서도:
messages = prompt.format_messages( - course_request=course_request, + course_request=course_request_json, itinerary_context=itinerary_context,참고:
_prepare_final_context내에서도 Line 373에서 이미CourseRequest.model_validate()를 수행하고 있어 중복 파싱이 발생합니다. 컨텍스트 준비 시 함께 반환하면 효율적입니다.
388-399:places[0]하드코딩은 Mock 전용임을 인지하되, 실 API 전환 시 선택 로직이 필요합니다.현재 Mock 서비스가 1개만 반환하므로
places[0]이 합리적이지만, 실제 Google Places API 연동(#22) 시에는 여러 후보 중 최적 장소를 선택하는 로직이 필요합니다. 현재 코드 구조에서는 이 선택 로직을_prepare_final_context안에 넣게 되는데, 관심사 분리 차원에서 별도 함수로 분리하는 것이 좋습니다.
- LLM 프롬프트를 수정하여 title이 한국어로 생성되도록 변경 - Mock 서비스의 장소 이름을 한국어로 수정 - planning_preference에 따라 visit_time을 시간 또는 시간대로 분기 처리
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@app/graph/roadmap/nodes.py`:
- Around line 393-415: The visit_sequence uses the loop index i (global slot
index) which becomes discontinuous when some slots have no places; change to a
per-day counter (e.g., initialize sequence = 1 at start of the day loop or
compute len(day_places)+1) and increment it only when you append a place to
day_places so visit_sequence is continuous; update the block that builds
day_places (references: day_places, i, slot, visit_sequence, _build_slot_key,
fetched_places) to assign visit_sequence from that counter instead of i+1 and
reset the counter for each new day.
🧹 Nitpick comments (2)
app/graph/roadmap/nodes.py (2)
434-468:course_request를 프롬프트에 전달할 때 원시 dict가 그대로 문자열화될 수 있습니다.Line 435에서
state["course_request"]를 그대로 가져오고, Line 465에서 프롬프트 템플릿에 전달합니다.state["course_request"]가 dict인 경우 Python의 dict repr({'key': 'value'})로 렌더링되어 LLM이 파싱하기 어려운 형태가 됩니다._prepare_final_context에서는 이미CourseRequest.model_validate()를 수행하므로(Line 374), 그 결과를 함께 반환하거나 여기서도 동일하게 변환 후 전달하는 것이 좋습니다.♻️ 수정 제안
# 1. LLM에 전달할 컨텍스트 데이터 준비 itinerary_context, daily_places = _prepare_final_context(state) - course_request = state["course_request"] + course_request = ( + state["course_request"] + if isinstance(state["course_request"], CourseRequest) + else CourseRequest.model_validate(state["course_request"]) + )
484-486:logger.error에서 f-string 대신%포맷팅을 사용하세요.파일 내 다른 로거 호출(Line 193, 231, 237, 352)은 모두
%slazy formatting을 사용합니다. f-string은 로그 레벨과 무관하게 즉시 평가되며, 일관성도 떨어집니다.♻️ 수정 제안
- logger.error(f"최종 로드맵 생성 실패: {e}", exc_info=True) - return {**state, "error": f"최종 로드맵 생성에 실패했습니다: {e}"} + logger.error("최종 로드맵 생성 실패: %s", e, exc_info=True) + return {**state, "error": f"최종 로드맵 생성에 실패했습니다: {e}"}
- LLM 프롬프트를 수정하여 title이 한국어로 생성되도록 변경 - Mock 서비스의 장소 이름을 한국어로 수정 - planning_preference에 따라 visit_time을 시간 또는 시간대로 분기 처리 - CoursePlace 스키마에 photo_reference 및 description 필드 추가 - 최종 데이터 생성 시 photo_reference와 임시 description을 포함하도록 수정
- CoursePlace 스키마의 category 필드를 tags: list[str]로 변경 - Mock 데이터의 types를 한글 태그로 변환하여 tags 필드에 저장하도록 로직 수정
- 함수 초반에 입력 데이터(state)에 대한 유효성 검증 로직 추가 - 비어있거나 유효하지 않은 데이터가 LLM 컨텍스트로 전달되는 것을 방지
- _prepare_final_context 함수에 일자별 카운터를 추가하여 visit_sequence가 항상 1부터 시작하도록 수정 - 빈 슬롯이 있을 경우 visit_sequence 번호가 건너뛰어지던 버그 해결
- CourseResponse 스키마에 start_date, end_date, trip_days, nights, people_count 필드 추가 - synthesize_final_roadmap 노드에서 해당 필드들을 채우도록 로직 수정
- _create_roadmap_workflow 함수에 docstring 추가
- 요구사항 변경에 따라, CoursePlace의 tags를 CourseResponse의 최상위 필드로 이동 - 사용자 입력(travel_themes)을 기반으로 여행 전체 태그를 생성하도록 로직 수정
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
app/schemas/course.py (1)
93-101:⚠️ Potential issue | 🟡 Minor
CoursePlace스키마의description필드 — 향후 검증 단계 추가 필요.
category필드가 제거되고photo_reference와description이 추가되었습니다.description은 required 필드로,DailyItinerary→CourseResponse로 이어지는 검증 체인에서 누락되면ValidationError가 발생합니다.현재 코드는
nodes.py라인 427에서 모든 장소에 placeholder 설명을 제공하므로 문제없으나, 향후 외부 API 데이터를 직접 사용하거나 스키마 검증(CourseResponse.model_validate())을 도입할 때는description필드가 항상 채워지도록 보장해야 합니다.
🤖 Fix all issues with AI agents
In `@app/graph/roadmap/nodes.py`:
- Around line 515-526: final_roadmap currently inserts
course_request_data["start_date"] and ["end_date"] (datetime.date objects)
directly which will fail JSON serialization; convert both to strings using
.isoformat() when building final_roadmap (same approach used for daily_date) so
start_date and end_date are JSON-serializable while preserving trip_days/nights
and other fields from llm_output and daily_places.
🧹 Nitpick comments (5)
app/schemas/course.py (1)
115-127:CourseResponse메타데이터 필드 — 타이핑 스타일 혼용.
tags: list[str](Line 121)은 소문자list를 사용하고,itinerary: List[DailyItinerary](Line 125)와places: List[CoursePlace](Line 109) 등은typing.List를 사용하고 있습니다. Python 3.9+ 기준 기능상 차이는 없지만, 파일 내 일관성을 유지하면 가독성이 높아집니다.app/graph/roadmap/nodes.py (4)
17-23:PlanningPreference를app.schemas.course에서 간접 임포트하고 있습니다.
PlanningPreference는app.schemas.enums에 정의되어 있고,course.py가 이를 재수출(re-export)하는 형태입니다. 동작상 문제는 없지만, enum의 정의 출처(app.schemas.enums)에서 직접 가져오는 것이 의존 관계를 더 명확하게 합니다.PacePreference(Line 20)도 마찬가지입니다.♻️ 제안
from app.schemas.course import ( CourseRequest, CourseResponseLLMOutput, - PacePreference, - PlanningPreference, RegionDateRange, ) +from app.schemas.enums import PacePreference, PlanningPreference
450-453:course_request변수가 중복 할당되고 있습니다.Line 453에서
course_request = state["course_request"]를 할당하고, Line 494에서course_request_data = state["course_request"]를 다시 할당합니다. 동일한 데이터에 대해 이름이 다른 두 변수가 존재하여 가독성이 떨어집니다. 하나로 통합하는 것이 좋습니다.♻️ 제안
# 1. LLM에 전달할 컨텍스트 데이터 준비 itinerary_context, daily_places = _prepare_final_context(state) - course_request = state["course_request"] + course_request_data = state["course_request"] # 2. LLM 출력 파서 설정 (itinerary 제외) parser = PydanticOutputParser(pydantic_object=CourseResponseLLMOutput)Line 483도 함께 수정:
messages = prompt.format_messages( - course_request=course_request, + course_request=course_request_data, itinerary_context=itinerary_context,그리고 Line 494의 중복 할당 제거:
- course_request_data = state["course_request"] trip_days = state["trip_days"]Also applies to: 494-494
530-531:logger.error에서 f-string 대신%s포매팅을 사용하세요.Line 531에서
f"최종 로드맵 생성 실패: {e}"는 로깅 시점에 항상 문자열 포매팅이 수행됩니다.logger.error("최종 로드맵 생성 실패: %s", e, exc_info=True)형태가 lazy evaluation 측면에서 더 적절하며, 파일 내 다른logger.error호출(Line 193, 231)과도 일관성이 유지됩니다.♻️ 제안
- logger.error(f"최종 로드맵 생성 실패: {e}", exc_info=True) - return {**state, "error": f"최종 로드맵 생성에 실패했습니다: {e}"} + logger.error("최종 로드맵 생성 실패: %s", e, exc_info=True) + return {**state, "error": f"최종 로드맵 생성에 실패했습니다: {e}"}
499-512:theme_map_ko는 현재TravelThemeenum과 동기화되어 있지만, 향후 enum 확장 시 불일치 위험이 있습니다.현재 코드는 모든 enum 값이 딕셔너리 키로 존재하기 때문에 동기화 상태입니다. 그러나 13번 줄의
theme_map_ko.get(theme, theme)구문에서 폴백 동작으로 인해, 새 테마가 enum에 추가되면 한글 번역 대신 원본 enum 값(예: "NEW_THEME")이 태그로 노출됩니다. 이는 동기화 부족을 침묵적으로 처리하므로 유지보수 단계에서 발견하기 어려울 수 있습니다.enum 기반 매핑이나 동적 생성 같은 구조로 개선하는 것을 권장합니다.
- LLM 프롬프트에 최종 일정 기반의 태그를 생성하도록 요청 추가 - CourseResponseLLMOutput 스키마에 tags 필드를 추가하여 LLM 응답 파싱 - 기존 travel_themes 기반의 수동 태그 생성 로직 제거
- CourseResponse의 start_date, end_date를 isoformat() 문자열로 변환하여 JSON 직렬화 오류 수정
- synthesize_final_roadmap에서 CourseRequest를 검증하여 date 타입을 보장하도록 수정 - 문자열에 .isoformat()을 호출하여 발생하던 AttributeError 해결
- 모든 수정사항이 반영된 그래프 실행 결과를 final_result.json으로 저장
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@final_result.json`:
- Around line 22-37: Day-level duplicate places are appearing (same place_id
appears multiple times with different visit_sequence/visit_time); update either
fetch_places_from_slots or _prepare_final_context to deduplicate entries per day
by place_id: when combining slot results, collapse duplicates by keeping the
earliest visit_time (or first occurrence), merge/concatenate descriptions/photos
if desired, and reassign visit_sequence sequentially for that day's list; ensure
you reference and operate on the place_id, visit_time, visit_sequence fields so
the output JSON no longer contains repeated place_id entries within a single
day.
🧹 Nitpick comments (5)
final_result.json (1)
1-111: 테스트 스냅샷 파일이 프로젝트 루트에 위치해 있습니다.
final_result.json은 워크플로우 실행 결과의 스냅샷으로,tests/디렉토리(예:tests/fixtures/final_result.json또는tests/snapshots/)로 이동하는 것이 프로젝트 구조상 적절합니다. 루트에 두면 프로덕션 코드와 혼동될 수 있고,.gitignore에서 관리하기도 어렵습니다.app/graph/roadmap/nodes.py (4)
422-432:description필드가 하드코딩된 플레이스홀더입니다.현재
f"{place['name']}에 대한 한 줄 설명입니다."로 고정되어 있습니다. Mock 단계에서는 문제 없지만, 실제 API 연동 시 Google Places의editorial_summary등 실제 데이터로 교체해야 합니다. TODO 주석을 남겨두면 추적하기 좋겠습니다.💡 TODO 주석 추가 제안
day_places.append( { "place_name": place["name"], "place_id": place.get("place_id"), "photo_reference": place.get("photo_reference"), - "description": f"{place['name']}에 대한 한 줄 설명입니다.", + # TODO: Google Places API 연동 시 실제 장소 설명으로 교체 + "description": place.get("description", f"{place['name']}에 대한 한 줄 설명입니다."), "visit_sequence": visit_sequence_counter, "visit_time": visit_time, } )
489-493:CourseRequest.model_validate가 중복 호출됩니다.Line 491에서
CourseRequest.model_validate(state["course_request"])를 호출하지만, 이미_prepare_final_context내부(Line 385)에서 동일한 검증이 수행되었습니다._prepare_final_context에서 검증된CourseRequest인스턴스를 반환값에 포함시키면 중복 검증을 제거할 수 있습니다.♻️ _prepare_final_context 반환값 확장 제안
_prepare_final_context가CourseRequest도 함께 반환하도록 변경:def _prepare_final_context( state: RoadmapState, -) -> tuple[str, list[dict]]: +) -> tuple[str, list[dict], CourseRequest]: ... course_request = CourseRequest.model_validate(raw_request) ... - return "\n".join(context_lines), daily_places_for_schema + return "\n".join(context_lines), daily_places_for_schema, course_request
synthesize_final_roadmap에서:- itinerary_context, daily_places = _prepare_final_context(state) - course_request = state["course_request"] + itinerary_context, daily_places, course_request = _prepare_final_context(state) ... - course_request = CourseRequest.model_validate(state["course_request"]) trip_days = state["trip_days"]
509-511:logger.error에서 f-string 대신%포매팅을 사용하세요.이 파일의 다른 로깅 호출(Lines 193, 231, 237, 352)은 모두
%스타일 지연 포매팅을 사용하고 있습니다. f-string은 로그 레벨과 무관하게 항상 문자열을 평가하므로 성능상 불리하고, 일관성도 떨어집니다.♻️ 수정 제안
- logger.error(f"최종 로드맵 생성 실패: {e}", exc_info=True) - return {**state, "error": f"최종 로드맵 생성에 실패했습니다: {e}"} + logger.error("최종 로드맵 생성 실패: %s", e, exc_info=True) + return {**state, "error": f"최종 로드맵 생성에 실패했습니다: {e}"}
492-492:state["trip_days"]직접 접근 시KeyError가능성이 있습니다.
_prepare_final_context에서skeleton_plan,fetched_places,course_request는 검증하지만trip_days는 검증하지 않습니다. 현재 워크플로우 순서상generate_skeleton이 항상 먼저 실행되므로 실질적으로 안전하지만, 방어적으로state.get("trip_days")를 사용하거나_prepare_final_context의 검증 대상에 추가하는 것이 좋습니다.🛡️ 방어적 접근 제안
- trip_days = state["trip_days"] + trip_days = state.get("trip_days") + if not trip_days: + raise ValueError("최종 합성을 위한 `trip_days` 데이터가 없습니다.")
1. 개요
Phase 1 (skeleton) 부터 Phase 4 (synthesis) 까지 이어지는 로드맵 생성 전체 워크플로우의 기본 구조를 구현합니다.
외부 API 의존성을 제거하고 기능의 흐름을 검증하기 위해,
GooglePlacesService를 Mock Service로 구현하여 적용했습니다. 실제 API 연동은 후속 이슈에서 진행될 예정입니다.2. 작업 내용
PlacePydantic 모델 정의 (schemas/place.py)MockGooglePlacesService구현 (tests/mocks/mock_places_service.py)fetch_places_from_slots와 최종 결과를 종합하는synthesize_final_roadmap노드 함수 구현fetched_places및final_roadmap필드를RoadmapState에 추가generate_skeleton→fetch_places_from_slots→synthesize_final_roadmap순서로 전체 노드를 연결하여 워크플로우 완성3. AI 활용 및 검증
AI가 생성한 코드 포함
100% 직접 작성
검증 방법:
final_roadmap상태가CourseResponse스키마에 맞게 생성됨을 확인했습니다.uv run pre-commit run --all-files명령어를 실행하여 모든 코드 품질 검사를 통과함을 확인했습니다.4. 스크린샷 (선택)
5. 체크리스트
uv run pre-commit run --all-files를 실행하여 통과했는가?Summary by CodeRabbit
새로운 기능
데이터 모델 변경
워크플로우
테스트