Skip to content

fix(chat-ai): AI 채팅 매물 검색 품질 개선 (#105) - #107

Merged
crolvlee merged 4 commits into
developfrom
fix/105-prompt
Jun 25, 2026
Merged

fix(chat-ai): AI 채팅 매물 검색 품질 개선 (#105)#107
crolvlee merged 4 commits into
developfrom
fix/105-prompt

Conversation

@crolvlee

@crolvlee crolvlee commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

변경 내용

  1. 분석 답변 LLM 프롬프트에 출력 제어 규칙 추가 — 내부 필드명(regionalStats 등) 및 시스템명(Spring Boot) 노출 방지
  2. 매물 검색 슬롯에 sort_by 추가 — "가장 싼", "저렴한" 등 저가 정렬 의도 감지 시 거래유형별 가격 오름차순 정렬 (SALE→price, JEONSE→deposit, MONTHLY_RENT→monthly_rent)
  • image
  1. 매물 검색 슬롯에 limit 추가 — "1개 보여줘" 등 명시적 개수 요청 반영, 정렬 요청 시 기본 5개·일반 검색 시 기본 10개로 고정 20개 문제 해결
  • image

연결 이슈

closes #105

테스트

  • 로컬 동작 확인
  • API 응답 형식 확인 (docs/08_API_SPEC.md)

리뷰 포인트

  • supabase_client.search_properties의 ORDER BY 절이 f-string으로 동적 생성됨 — order_clause는 코드 내 상수이므로 SQL injection 위험 없음
  • sort_by, limitfield_map/range_map에 없는 키라 WHERE 조건 생성에 영향 없음

Summary by CodeRabbit

  • New Features

    • Property search can now honor user-requested sorting by lowest price and a requested result count.
    • Real-estate chat responses now present prices in “만원” units for easier reading.
  • Bug Fixes

    • Improved price handling for 월세/전세/매매 listings to show more consistent, user-friendly amounts.
    • Search results now respect sorting preferences more reliably, with a sensible fallback when no sort is requested.

@vercel

vercel Bot commented Jun 25, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
salmanhae Ready Ready Preview, Comment Jun 25, 2026 12:29pm

@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds optional sort and limit extraction for property searches, changes query ordering to support ascending price searches, updates won-based price formatting in backend and frontend responses, and tightens analysis-answer prompts to avoid exposing internal field names.

Changes

Property search sorting, limits, and price display

Layer / File(s) Summary
Criteria extraction and limit wiring
backend-ai/app/clients/llm_client.py, backend-ai/app/graph/nodes/property_search.py
The property-criteria prompt now extracts optional sort_by and limit, and property_search resolves an explicit search limit before calling SupabaseVectorClient.
Price ordering in Supabase
backend-ai/app/clients/supabase_client.py
search_properties now orders price_asc queries by transaction-type-specific price fields and keeps created_at DESC for other requests.
Won-unit price formatting
backend-ai/app/clients/llm_client.py, frontend/src/views/Chatbot.vue
Live property-search replies and the chatbot view now format sale, jeonse, and monthly-rent amounts as rounded 만원 values.

Analysis answer prompt hardening

Layer / File(s) Summary
Prompt text updates
backend-ai/app/rag/prompts.py
The analysis-answer system prompt and tool-results instructions now avoid internal field names and system names, and the fallback text changes accordingly.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant llm_client
  participant property_search
  participant SupabaseVectorClient
  participant public_properties
  User->>llm_client: asks for property search with sort_by and limit
  llm_client->>property_search: extracted criteria
  property_search->>property_search: compute effective_limit
  property_search->>SupabaseVectorClient: search_properties(limit=effective_limit)
  SupabaseVectorClient->>public_properties: ORDER BY and LIMIT query
  public_properties-->>SupabaseVectorClient: matching rows
  SupabaseVectorClient-->>property_search: properties
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • ssafy-salman/salmanhae#42: Adds the same sort_by/limit property-search flow across the LLM criteria extraction, Supabase query, and node limit wiring.
  • ssafy-salman/salmanhae#46: Touches the same backend-ai/app/rag/prompts.py analysis-answer prompt path and related instruction text.
  • ssafy-salman/salmanhae#104: Modifies the live property-search response handling in backend-ai/app/clients/llm_client.py, which is the same code path adjusted here for price formatting.

Poem

I hopped through prompts and search,
and found the prices bright in 만원 light. 🐰
Sort the homes, trim the list,
and hush the secret words at night.
Thump-thump — the results feel right!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR also adds sort_by, limit, and frontend price-formatting changes that are not required by linked issue #105. Split unrelated search-ranking and UI formatting changes into separate PRs, or add linked issues that explicitly cover them.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and clearly related to the PR’s main goal of improving AI chat property search quality.
Description check ✅ Passed The description matches the template sections and includes the required change summary, linked issue, tests, and review points.
Linked Issues check ✅ Passed The prompt update in prompts.py addresses the linked issue by hiding internal field names and system labels from AI answers.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/105-prompt

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 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 325-329: The price formatting in llm_client.py is using floor
truncation via int(...) // 10000, which conflicts with the frontend’s toMan
rounding behavior. Update the price_str construction in the tx handling block so
the backend uses the same rounding rule as frontend/src/views/Chatbot.vue,
keeping the displayed won-to-man amount consistent across the LLM summary and
property card.

In `@backend-ai/app/graph/nodes/property_search.py`:
- Around line 14-20: The `effective_limit` handling in `property_search` is
unbounded and can pass oversized or negative values into `search_properties`.
After parsing `user_limit` in the existing `try` block, clamp it to a positive
minimum and a reasonable maximum, and keep the fallback behavior for invalid
inputs; use the `criteria.get("limit")` / `criteria.get("sort_by")` logic as the
entry point and ensure `int(user_limit)` never reaches `LIMIT` outside the safe
range.
🪄 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: aa8e4cb6-7c57-4495-99ea-e1f42e64dfec

📥 Commits

Reviewing files that changed from the base of the PR and between 43d7623 and 8d39c25.

📒 Files selected for processing (5)
  • backend-ai/app/clients/llm_client.py
  • backend-ai/app/clients/supabase_client.py
  • backend-ai/app/graph/nodes/property_search.py
  • backend-ai/app/rag/prompts.py
  • frontend/src/views/Chatbot.vue

Comment on lines +325 to +329
price_str = f"{int(deposit) // 10000:,}/{int(rent) // 10000:,}만원"
elif tx == "전세" and deposit is not None:
price_str = f"전세 {int(deposit):,}만원"
price_str = f"전세 {int(deposit) // 10000:,}만원"
elif tx == "매매" and price is not None:
price_str = f"매매 {int(price):,}만원"
price_str = f"매매 {int(price) // 10000:,}만원"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Won→만원 rounding differs from the frontend, causing inconsistent prices for the same property.

Here the backend truncates with int(...) // 10000 (floor), while frontend/src/views/Chatbot.vue (toMan) uses Math.round(... / 10000). For an amount like 5,007,000원 the LLM summary would show 500만원 but the card would show 501만원. Align both surfaces on one rule (rounding is more intuitive for money).

♻️ Use rounding to match the frontend
-            if tx == "월세" and deposit is not None and rent is not None:
-                price_str = f"{int(deposit) // 10000:,}/{int(rent) // 10000:,}만원"
-            elif tx == "전세" and deposit is not None:
-                price_str = f"전세 {int(deposit) // 10000:,}만원"
-            elif tx == "매매" and price is not None:
-                price_str = f"매매 {int(price) // 10000:,}만원"
+            if tx == "월세" and deposit is not None and rent is not None:
+                price_str = f"{round(int(deposit) / 10000):,}/{round(int(rent) / 10000):,}만원"
+            elif tx == "전세" and deposit is not None:
+                price_str = f"전세 {round(int(deposit) / 10000):,}만원"
+            elif tx == "매매" and price is not None:
+                price_str = f"매매 {round(int(price) / 10000):,}만원"
🤖 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/clients/llm_client.py` around lines 325 - 329, The price
formatting in llm_client.py is using floor truncation via int(...) // 10000,
which conflicts with the frontend’s toMan rounding behavior. Update the
price_str construction in the tx handling block so the backend uses the same
rounding rule as frontend/src/views/Chatbot.vue, keeping the displayed
won-to-man amount consistent across the LLM summary and property card.

Comment on lines +14 to +20
user_limit = criteria.get("limit")
try:
effective_limit = int(user_limit) if user_limit else (
_SORTED_LIMIT if criteria.get("sort_by") else _DEFAULT_LIMIT
)
except (TypeError, ValueError):
effective_limit = _DEFAULT_LIMIT

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Clamp effective_limit to a sane range.

int(user_limit) is used unbounded. Two problems flow from an LLM-extracted value:

  • A large value (e.g. user says "1000개") is passed straight to LIMIT, allowing an oversized scan/result set.
  • A negative value (e.g. -5) is truthy, so int("-5") → -5 reaches search_properties; Postgres rejects LIMIT -5, the try/except in this node swallows it, and the user silently gets zero results instead of their search.

Bound the value to a maximum and a positive floor.

🛡️ Proposed clamp
+_MAX_LIMIT = 50
+
 def property_search(state: AgentState) -> AgentState:
     message = state["message"]
     criteria = LLMClient().extract_property_criteria(message)
 
     user_limit = criteria.get("limit")
     try:
-        effective_limit = int(user_limit) if user_limit else (
-            _SORTED_LIMIT if criteria.get("sort_by") else _DEFAULT_LIMIT
-        )
+        if user_limit:
+            effective_limit = max(1, min(int(user_limit), _MAX_LIMIT))
+        else:
+            effective_limit = _SORTED_LIMIT if criteria.get("sort_by") else _DEFAULT_LIMIT
     except (TypeError, ValueError):
         effective_limit = _DEFAULT_LIMIT
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
user_limit = criteria.get("limit")
try:
effective_limit = int(user_limit) if user_limit else (
_SORTED_LIMIT if criteria.get("sort_by") else _DEFAULT_LIMIT
)
except (TypeError, ValueError):
effective_limit = _DEFAULT_LIMIT
_MAX_LIMIT = 50
user_limit = criteria.get("limit")
try:
if user_limit:
effective_limit = max(1, min(int(user_limit), _MAX_LIMIT))
else:
effective_limit = _SORTED_LIMIT if criteria.get("sort_by") else _DEFAULT_LIMIT
except (TypeError, ValueError):
effective_limit = _DEFAULT_LIMIT
🤖 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 14 - 20, The
`effective_limit` handling in `property_search` is unbounded and can pass
oversized or negative values into `search_properties`. After parsing
`user_limit` in the existing `try` block, clamp it to a positive minimum and a
reasonable maximum, and keep the fallback behavior for invalid inputs; use the
`criteria.get("limit")` / `criteria.get("sort_by")` logic as the entry point and
ensure `int(user_limit)` never reaches `LIMIT` outside the safe range.

@crolvlee
crolvlee merged commit 8e840f5 into develop Jun 25, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG][F-7] AI 답변에 내부 기술 용어 노출

1 participant