버그 수정중 .
This commit is contained in:
parent
088c57f415
commit
cb3adf6d2e
@ -6,6 +6,7 @@ Chat_server 의 step 체계(서비스안내→담당자확인→협상품목안
|
||||
DB/정책(카드선택·학습)은 여기 두지 않는다 — ChatService(async)가 StepView 의 신호를 보고 처리한다.
|
||||
"""
|
||||
|
||||
import re
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
@ -15,6 +16,22 @@ MAX_ROUNDS = 3
|
||||
_PRICE_MODES = ("price",)
|
||||
_CHOICE_MODES = ("yes_no", "confirm", "delivery_type")
|
||||
|
||||
# 프론트는 표시용 문자열로 가격을 보낸다(예: "530,000원"). 천단위 콤마·통화기호("원")·공백 등
|
||||
# 숫자 외 문자를 제거하고 파싱한다. (콤마만 지우면 "원" 때문에 float() 가 실패해 가격 입력이
|
||||
# 영영 저장되지 않고 같은 step 에 머무는 버그가 났었다.)
|
||||
_PRICE_CLEAN_RE = re.compile(r"[^\d.]")
|
||||
|
||||
|
||||
def _parse_price(user_input: Any) -> Optional[float]:
|
||||
cleaned = _PRICE_CLEAN_RE.sub("", str(user_input if user_input is not None else ""))
|
||||
if not cleaned or cleaned == ".":
|
||||
return None
|
||||
try:
|
||||
price = float(cleaned)
|
||||
except ValueError:
|
||||
return None
|
||||
return price if price > 0 else None
|
||||
|
||||
|
||||
@dataclass
|
||||
class ChatSession:
|
||||
@ -62,9 +79,8 @@ class ChatEngine:
|
||||
mode = node.get("next_input_mode", "null")
|
||||
|
||||
if mode in _PRICE_MODES:
|
||||
try:
|
||||
price = float(str(user_input).replace(",", ""))
|
||||
except (TypeError, ValueError):
|
||||
price = _parse_price(user_input)
|
||||
if price is None:
|
||||
return self._error(session, "가격을 숫자로 입력해 주세요.")
|
||||
session.context["input_price"] = price
|
||||
session.context["round"] = session.context.get("round", 0) + 1
|
||||
|
||||
@ -147,7 +147,7 @@ class ChatService:
|
||||
|
||||
async def _seed_opening(self, sess) -> Optional[ChatMessage]:
|
||||
"""오프닝(턴0) 봇 메시지를 agent 로 생성하고 seq=1 로 저장한다. 동시 진입 충돌은 무시(유니크가 방어)."""
|
||||
ctx = self._agent_context(sess, turn=0)
|
||||
ctx = await self._agent_context(sess, turn=0)
|
||||
turn = await self.agent.chat(session_id=str(sess.session_id), user_input=None, ctx=ctx)
|
||||
if not turn.ok:
|
||||
return None
|
||||
@ -231,7 +231,7 @@ class ChatService:
|
||||
return res
|
||||
|
||||
# agent 위임 (한 턴). 실패 시 선점한 유저 메시지를 롤백 → 재시도 가능.
|
||||
ctx = self._agent_context(sess, turn=turn_no)
|
||||
ctx = await self._agent_context(sess, turn=turn_no)
|
||||
turn = await self.agent.chat(session_id=str(sess.session_id), user_input=user_input, ctx=ctx)
|
||||
if not turn.ok:
|
||||
await DB_SESSION_MNG.execute_lambda_run(
|
||||
@ -270,10 +270,16 @@ class ChatService:
|
||||
return res
|
||||
|
||||
# ---- 빌더 / 매퍼 ----------------------------------------------------
|
||||
def _agent_context(self, sess, turn: int) -> AgentChatContext:
|
||||
async def _agent_context(self, sess, turn: int) -> AgentChatContext:
|
||||
# 핸드오프 #2/#5: X-Tenant-ID 는 견적(갑) 회사 company_id 여야 한다.
|
||||
# 현재 quotation.user_id 만 보유 → 정확한 company_id 해석(company.users 조회)은 agent 연동 시 보완.
|
||||
tenant_id = "" # mock 은 무시. 실제 연동 시 quotation 의 buyer company_id 로 채운다.
|
||||
# 상품(partner.items)의 소유 회사가 갑(buyer)이므로 item.company_id 로 해석한다.
|
||||
tenant_id = "" # 해석 실패 시 빈 값(agent 가 400) — 로깅으로 추적
|
||||
err_type, item = await DB_SESSION_MNG.execute_lambda(
|
||||
items.DBType(), DBWRType.DB_READ.value,
|
||||
lambda s: self.chat_crud.get_item_by_id(s, sess.item_id),
|
||||
)
|
||||
if err_type == ErrorType.SUCCESS and item is not None and item.company_id:
|
||||
tenant_id = str(item.company_id)
|
||||
rq_type = "재협상" if sess.qt_type == 1 else "재견적"
|
||||
anchor = int(sess.target_price * 0.99) if sess.target_price else 0
|
||||
return AgentChatContext(
|
||||
|
||||
@ -21,6 +21,7 @@ services:
|
||||
environment:
|
||||
APP_ENV: local
|
||||
DB_HOST: host.docker.internal # 컨테이너→호스트 DB (config.local.toml의 127.0.0.1 override)
|
||||
AGENT_BASE_URL: http://host.docker.internal:9500 # 컨테이너→호스트 agent (로컬 uvicorn :9500)
|
||||
ports:
|
||||
- "9300:9300"
|
||||
# 컨테이너에서 호스트의 DB 로 접근 (config.docker.toml 의 host.docker.internal)
|
||||
|
||||
@ -45,7 +45,10 @@ export function useChatController(sessionId: string) {
|
||||
const setInitData = useChatInitStore((s) => s.setInitData)
|
||||
const initQuery = useChatInitQuery(sessionId)
|
||||
const messagesQuery = useChatMessagesQuery(sessionId)
|
||||
const sendMutation = useChatSendMutation(sessionId)
|
||||
// mutate 는 참조가 안정적이다. useMutation 반환 객체 전체를 아래 effect deps 에 넣으면
|
||||
// mutate 호출→pending 리렌더마다 객체 identity 가 바뀌어 effect 가 teardown(reset())→재바인딩되고,
|
||||
// staleTime:Infinity 인 메시지 쿼리는 재조회되지 않아 스토어가 빈 채로 남아 채팅이 사라진다.
|
||||
const { mutate: sendMutate } = useChatSendMutation(sessionId)
|
||||
|
||||
// init 메타 → 스토어
|
||||
useEffect(() => {
|
||||
@ -68,7 +71,7 @@ export function useChatController(sessionId: string) {
|
||||
store.setIsLoading(true)
|
||||
store.setPriceErrorMessage('')
|
||||
|
||||
sendMutation.mutate(
|
||||
sendMutate(
|
||||
{ user_input: text, user_input_type: inputType === 'text' ? null : inputType },
|
||||
{
|
||||
onSuccess: (data) => {
|
||||
@ -104,7 +107,7 @@ export function useChatController(sessionId: string) {
|
||||
return () => {
|
||||
useChatStore.getState().reset()
|
||||
}
|
||||
}, [sessionId, sendMutation, navigate])
|
||||
}, [sessionId, sendMutate, navigate])
|
||||
|
||||
return {
|
||||
isInitLoading: initQuery.isLoading || messagesQuery.isLoading,
|
||||
|
||||
@ -140,6 +140,7 @@ class wild_cards(MainTableMixin, MAIN_BASE):
|
||||
|
||||
class versions(MainTableMixin, MAIN_BASE):
|
||||
__tablename__ = "versions"
|
||||
__table_args__ = {"schema": "card"}
|
||||
|
||||
version_id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
user_id = Column(UUID(as_uuid=True), nullable=False, index=True) # 버전 생성 유저
|
||||
@ -149,6 +150,7 @@ class versions(MainTableMixin, MAIN_BASE):
|
||||
|
||||
class version_nego_cards(MainTableMixin, MAIN_BASE):
|
||||
__tablename__ = "version_nego_cards"
|
||||
__table_args__ = {"schema": "card"}
|
||||
|
||||
vnc_id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
version_id = Column(UUID(as_uuid=True), nullable=False, index=True) # versions.version_id
|
||||
@ -157,6 +159,7 @@ class version_nego_cards(MainTableMixin, MAIN_BASE):
|
||||
|
||||
class version_wild_cards(MainTableMixin, MAIN_BASE):
|
||||
__tablename__ = "version_wild_cards"
|
||||
__table_args__ = {"schema": "card"}
|
||||
|
||||
vwc_id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
version_id = Column(UUID(as_uuid=True), nullable=False, index=True) # versions.version_id
|
||||
@ -240,3 +243,4 @@ class chats(MainTableMixin, MAIN_BASE):
|
||||
card_used_yn = Column(Boolean, nullable=True) # 카드 사용 여부
|
||||
indicator_value = Column(Numeric(8, 6), nullable=True)
|
||||
card_type = Column(SmallInteger, nullable=True) # 1=nego_card, 2=wild_card
|
||||
meta = Column(JSONB, nullable=True) # 말풍선 표현 데이터(script/step/client_step/input_mode/input_options/chat_end)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user