feat(ai): 매물 추천 Text-to-SQL 및 LLM 기반 의도 분류 구현 (#29) - #42
Conversation
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughReplaces keyword-based intent classification with an LLM-driven ChangesLLM Intent Classification and Text-to-SQL Property Search
Sequence DiagramsequenceDiagram
participant Client
participant classify_intent as classify_intent node
participant property_search as property_search node
participant LLMClient
participant SupabaseVectorClient
Client->>classify_intent: state["message"]
classify_intent->>LLMClient: classify(message)
LLMClient-->>classify_intent: {intent, reasoning} or None
classify_intent-->>Client: Intent enum (or FALLBACK)
Client->>property_search: state["message"]
property_search->>LLMClient: extract_property_criteria(message)
LLMClient-->>property_search: criteria dict (or {})
property_search->>SupabaseVectorClient: search_properties(criteria)
SupabaseVectorClient-->>property_search: list[dict] properties
property_search-->>Client: {properties, tool_results: {count, criteria}}
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@backend-ai/app/clients/llm_client.py`:
- Around line 88-89: The parsed JSON response from
extract_chat_completion_text() in the LLM client is not validated to ensure it's
a dictionary object before being returned. While the fallback value "{}" is
returned when extraction fails, valid JSON could still parse to a list, string,
or number, which would violate the dict[str, Any] contract expected by
extract_property_criteria(). After calling json.loads(text) in both locations
(around lines 88-89 and 110-111), validate that the parsed result is actually a
dictionary using isinstance() check, and return the fallback json.loads("{}")
only if the parsed value is confirmed to be a dict; otherwise return the
fallback value.
In `@backend-ai/app/clients/supabase_client.py`:
- Line 130: The unconditional `int(self.statement_timeout_ms)` cast in the
cursor.execute call for SET LOCAL statement_timeout can raise an exception if
statement_timeout_ms is unset or misconfigured, causing the query execution to
fail before it runs. Add validation or a default fallback before casting to
handle cases where statement_timeout_ms might be None or invalid, ensuring the
timeout is set to a safe default value when the property is not properly
configured.
- Around line 108-111: The loop iterating through range_map.items() appends
conditions and parameters without validating that the numeric criteria values
(max_* filters) are actually numeric. Before appending to conditions and params
in this loop, add validation or coercion logic to ensure criteria[key] is
numeric, and handle cases where the value cannot be converted to a number by
either skipping that condition gracefully or coercing it to an appropriate
default value instead of letting the invalid value fail during SQL execution.
In `@backend-ai/app/graph/nodes/property_search.py`:
- Around line 7-8: The LLMClient().extract_property_criteria method can return
non-object JSON (such as strings or arrays) which causes failures when passed to
SupabaseVectorClient().search_properties since it expects a dictionary with
mapping semantics. After calling extract_property_criteria on the state message,
normalize the returned criteria to ensure it is a dictionary before passing it
to the search_properties method. If the criteria is not already a dict, convert
it appropriately so that the SQL client receives the expected mapping structure.
- Around line 8-14: Wrap the SupabaseVectorClient().search_properties(criteria)
call in a try-except block to handle any errors gracefully. When an exception
occurs, catch it and return an empty list for properties instead of letting the
error propagate, while also adding error metadata to the tool_results dictionary
so the caller can understand that the search failed. This way the node will
degrade gracefully and continue graph execution instead of aborting.
In `@docs/08_API_SPEC.md`:
- Around line 394-402: The API documentation example in the properties object
section shows camelCase field names (buildingName, propertyType,
transactionType, monthlyRent, areaM2) and areaM2 as a string value, but the
actual API returns these fields in snake_case from the database without
camelCase transformation applied to the properties list. Update the
documentation example to use snake_case keys (building_name, property_type,
transaction_type, monthly_rent, area_m2) and ensure numeric fields like area_m2
are represented as numeric values (23.14) rather than strings ("23.14") to
accurately reflect what the API returns.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 355c2e1c-1e11-4f48-a885-28e0a04ff6aa
📒 Files selected for processing (15)
AGENTS.mdCLAUDE.mdbackend-ai/app/clients/llm_client.pybackend-ai/app/clients/spring_client.pybackend-ai/app/clients/supabase_client.pybackend-ai/app/core/config.pybackend-ai/app/graph/builder.pybackend-ai/app/graph/nodes/classify_intent.pybackend-ai/app/graph/nodes/property_search.pybackend-ai/app/graph/state.pybackend-ai/tests/test_agent_chat.pybackend-ai/tests/test_classify_intent.pydocs/02_ARCHITECTURE.mddocs/03_ADR.mddocs/08_API_SPEC.md
| criteria = LLMClient().extract_property_criteria(state["message"]) | ||
| properties = SupabaseVectorClient().search_properties(criteria) |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Normalize criteria to a dict before DB search.
extract_property_criteria can return non-object JSON; passing that through causes downstream failures when the SQL client expects mapping semantics.
Proposed fix
def property_search(state: AgentState) -> AgentState:
- criteria = LLMClient().extract_property_criteria(state["message"])
+ raw_criteria = LLMClient().extract_property_criteria(state["message"])
+ criteria = raw_criteria if isinstance(raw_criteria, dict) else {}
properties = SupabaseVectorClient().search_properties(criteria)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend-ai/app/graph/nodes/property_search.py` around lines 7 - 8, The
LLMClient().extract_property_criteria method can return non-object JSON (such as
strings or arrays) which causes failures when passed to
SupabaseVectorClient().search_properties since it expects a dictionary with
mapping semantics. After calling extract_property_criteria on the state message,
normalize the returned criteria to ensure it is a dictionary before passing it
to the search_properties method. If the criteria is not already a dict, convert
it appropriately so that the SQL client receives the expected mapping structure.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
변경 내용
RouteDecisionPydantic 스키마 추가 (intent Literal 6종 + reasoning)LLMClient.classify()메서드 추가 (GMS OpenAI-compatible API 호출)classify_intent_fallback()→Intent.FALLBACK반환Intent.GENERAL_CHAT추가 및 그래프 라우팅 연결SupabaseVectorClient.search_properties()추가 (Text-to-SQL 매물 조회)SpringClient의존성 주입 구조 개선 (timeout_seconds,http_get주입 가능)_similarity_search_legal_documents_rest()REST fallback 추가연결 이슈
closes #29
테스트
"강남구 오피스텔 가장 싼거 추천해줘"→PROPERTY_SEARCH, 매물 20개 반환)리뷰 포인트
Summary by CodeRabbit