Commit 73b2d542 authored by Vũ Hoàng Anh's avatar Vũ Hoàng Anh

feat: add CONTACT_INFO to user_insight for seamless order checkout

parent 178108a8
......@@ -14,6 +14,10 @@ class UserInsight(BaseModel):
default="Chưa rõ.",
description="Thông tin người chat: Giới tính, Người lớn/Trẻ em, Style/Gu"
)
CONTACT_INFO: str = Field(
default="Chưa có.",
description="Thông tin liên hệ khách: Name, Phone, Address, Email (thu thập dần qua hội thoại)"
)
TARGET: str = Field(
default="Chưa rõ.",
description="Đối tượng thụ hưởng: Quan hệ, Giới tính, Người lớn/Trẻ em, Style/Gu"
......
This diff is collapsed.
Công cụ quản lý đơn hàng CANIFA — Tạo đơn & Hủy đơn.
═══════════════════════════════════════════════════════════════
⚠️⚠️⚠️ QUY TẮC BẮT BUỘC ⚠️⚠️⚠️
═══════════════════════════════════════════════════════════════
1. CHỈ gọi create_customer_order khi khách ĐÃ XÁC NHẬN muốn đặt hàng
2. PHẢI thu thập ĐỦ thông tin bắt buộc TRƯỚC KHI gọi tool:
- customer_name (họ tên)
- phone (số điện thoại)
- shipping_address (địa chỉ giao hàng)
- total_amount (tổng tiền)
3. KHÔNG tự bịa thông tin khách hàng — thiếu gì thì HỎI
4. Xác nhận lại toàn bộ thông tin với khách TRƯỚC KHI tạo đơn
═══════════════════════════════════════════════════════════════
🛒 TOOL: create_customer_order — TẠO ĐƠN HÀNG
═══════════════════════════════════════════════════════════════
FLOW CHUẨN (tuần tự):
1. Khách chọn sản phẩm → Ghi nhận sản phẩm, số lượng, giá
2. Khách nói "đặt hàng" / "mua" / "order" → CHECK user_insight.CONTACT_INFO
3. Kiểm tra CONTACT_INFO:
✅ ĐỦ INFO (Name + Phone + Address) → Tóm tắt + xác nhận luôn
❌ THIẾU INFO → CHỈ hỏi những field ghi "Missing", KHÔNG hỏi lại cái đã có
4. Tóm tắt lại đơn hàng cho khách xác nhận:
"📋 Xác nhận đơn hàng:
👤 Tên: [tên]
📱 SĐT: [phone]
📍 Địa chỉ: [address]
🛍️ Sản phẩm: [danh sách]
💰 Tổng: [total] VNĐ
Anh/chị xác nhận đặt hàng không ạ?"
5. Khách xác nhận → GỌI TOOL create_customer_order
6. Tool trả về order_id → Thông báo cho khách
⚡ VÍ DỤ: CONTACT_INFO có sẵn
- user_insight.CONTACT_INFO = "Name: Nguyễn Văn Nam | Phone: 0912345678 | Address: Cầu Giấy, HN | Email: Missing"
- Khách nói "Chốt đơn" → Bot tóm tắt luôn:
"Em xác nhận đơn hàng:
👤 Tên: Nguyễn Văn Nam
📱 SĐT: 0912345678
📍 Giao đến: Cầu Giấy, HN
🛍️ Sản phẩm: ...
💰 Tổng: ...VNĐ
Anh xác nhận nhé?"
⚡ VÍ DỤ: CONTACT_INFO thiếu 1 phần
- user_insight.CONTACT_INFO = "Name: Nguyễn Văn Nam | Phone: Missing | Address: Missing | Email: Missing"
- Khách nói "Chốt đơn" → Bot CHỈ hỏi SĐT + Địa chỉ (đã biết tên rồi):
"Dạ anh Nam ơi! Để em tạo đơn, anh cho em SĐT và địa chỉ giao hàng nhé!"
THAM SỐ:
- customer_name (bắt buộc): Họ tên đầy đủ
- phone (bắt buộc): Số điện thoại (VD: 0912345678)
- shipping_address (bắt buộc): Địa chỉ giao hàng chi tiết
- total_amount (bắt buộc): Tổng tiền đơn hàng (VND)
- email (tùy chọn): Email khách hàng
- order_items (tùy chọn): Danh sách sản phẩm, mỗi item gồm:
+ product_name: Tên sản phẩm
+ quantity: Số lượng
+ price: Giá mỗi sản phẩm
⚠️ LƯU Ý total_amount:
- Nếu có order_items → Tính tổng = SUM(quantity × price) cho từng item
- Nếu khách nói giá cụ thể → Dùng giá khách nói
- Nếu có giá từ tool search trước đó → Dùng giá đã tìm được (ưu tiên sale_price nếu có)
VÍ DỤ GỌI TOOL:
{
"customer_name": "Nguyễn Văn A",
"phone": "0912345678",
"shipping_address": "123 Nguyễn Huệ, Q1, TP.HCM",
"total_amount": 598000,
"email": "a@gmail.com",
"order_items": [
{"product_name": "Áo phông nam Basic", "quantity": 2, "price": 299000}
]
}
═══════════════════════════════════════════════════════════════
🚫 TOOL: cancel_order — HỦY ĐƠN HÀNG
═══════════════════════════════════════════════════════════════
FLOW CHUẨN:
1. Khách nói "hủy đơn" / "cancel" → Hỏi mã đơn hàng (order_id)
2. Hỏi số điện thoại để xác minh
3. GỌI TOOL cancel_order
4. Thông báo kết quả cho khách
THAM SỐ:
- order_id (bắt buộc): Mã đơn hàng (VD: ORD-ABC12345)
- phone (bắt buộc): SĐT xác minh (phải khớp với SĐT khi đặt)
QUY TẮC HỦY:
- Chỉ hủy được đơn có trạng thái 'pending'
- Đơn đang 'processing', 'shipped', 'completed' → KHÔNG hủy được
- Đơn đã 'cancelled' → Thông báo đã hủy trước đó
- SĐT không khớp → Từ chối hủy
═══════════════════════════════════════════════════════════════
💡 USER_INSIGHT.CONTACT_INFO — Tận dụng thông tin có sẵn
═══════════════════════════════════════════════════════════════
LUÔN kiểm tra user_insight.CONTACT_INFO TRƯỚC khi hỏi khách:
- Field nào đã có (KHÔNG phải "Missing") → KHÔNG hỏi lại, dùng luôn
- Field nào ghi "Missing" → Mới hỏi khách
- Xác nhận với khách: "Em thấy anh/chị là [Name], SĐT [Phone], giao về [Address]. Đúng chưa ạ?"
- Khách muốn đổi → Cập nhật CONTACT_INFO và dùng info mới
═══════════════════════════════════════════════════════════════
📝 MẪU TRẢ LỜI
═══════════════════════════════════════════════════════════════
✅ TẠO ĐƠN THÀNH CÔNG:
"🎉 Đơn hàng [order_id] đã được tạo thành công!
📋 Chi tiết:
- Sản phẩm: [items]
- Tổng: [total] VNĐ
- Giao đến: [address]
Chúng tôi sẽ liên hệ xác nhận qua SĐT [phone] sớm nhất ạ!"
❌ TẠO ĐƠN THẤT BẠI:
"Rất tiếc, có lỗi khi tạo đơn hàng. Anh/chị vui lòng thử lại hoặc liên hệ hotline 1800 6996 để được hỗ trợ ạ."
✅ HỦY ĐƠN THÀNH CÔNG:
"Đơn hàng [order_id] đã được hủy thành công. Nếu cần hỗ trợ thêm, anh/chị cứ nhắn em nhé!"
❌ KHÔNG HỦY ĐƯỢC:
"Rất tiếc, đơn hàng [order_id] [lý do] nên không thể hủy. Anh/chị liên hệ hotline 1800 6996 để được hỗ trợ ạ."
......@@ -6,5 +6,6 @@ Export tool và factory function
from .data_retrieval_tool import data_retrieval_tool
from .get_tools import get_all_tools
from .promotion_canifa_tool import canifa_get_promotions
from .take_order import create_customer_order, cancel_order
__all__ = ["data_retrieval_tool", "get_all_tools", "canifa_get_promotions"]
__all__ = ["data_retrieval_tool", "get_all_tools", "canifa_get_promotions", "create_customer_order", "cancel_order"]
......@@ -10,6 +10,7 @@ from .customer_info_tool import collect_customer_info
from .data_retrieval_tool import data_retrieval_tool
from .promotion_canifa_tool import canifa_get_promotions
from .check_is_stock import check_is_stock
from .take_order import create_customer_order, cancel_order
def get_retrieval_tools() -> list[Tool]:
......@@ -19,7 +20,7 @@ def get_retrieval_tools() -> list[Tool]:
def get_collection_tools() -> list[Tool]:
"""Các tool dùng để ghi/thu thập dữ liệu (KHÔNG cache)"""
return [collect_customer_info]
return [collect_customer_info, create_customer_order, cancel_order]
def get_all_tools() -> list[Tool]:
......
import json
import logging
import uuid
from decimal import Decimal
from typing import Optional, Type
import psycopg2
from langchain_core.tools import BaseTool
from pydantic import BaseModel, Field
logger = logging.getLogger(__name__)
DB_HOST = "172.16.2.190"
DB_PORT = "15433"
DB_NAME = "postgres"
DB_USER = "pgvector"
DB_PASS = "password"
class OrderItem(BaseModel):
product_name: str = Field(..., description="Name of the product")
quantity: int = Field(..., description="Quantity of the product")
price: Optional[float] = Field(..., description="Price per unit")
model_config = {
"extra": "forbid"
}
class OrderInput(BaseModel):
customer_name: str = Field(..., description="Customer's full name")
phone: str = Field(..., description="Customer's phone number")
shipping_address: str = Field(..., description="Delivery address")
total_amount: float = Field(..., description="Total order value")
email: Optional[str] = Field(..., description="Customer's email (optional)")
order_items: Optional[list[OrderItem]] = Field(..., description="List of products (optional)")
# Enforce strict schema for OpenAI
model_config = {
"extra": "forbid"
}
from langchain_core.tools import tool
@tool(args_schema=OrderInput)
def create_customer_order(
customer_name: str,
phone: str,
shipping_address: str,
total_amount: float,
email: Optional[str] = None,
order_items: Optional[list[OrderItem]] = None,
) -> dict:
"""
Create a new order in the system.
Call this tool when the user confirms their information and wants to place an order.
"""
try:
# 1. Connect to PostgreSQL
conn = psycopg2.connect(
host=DB_HOST,
port=DB_PORT,
database=DB_NAME,
user=DB_USER,
password=DB_PASS
)
cur = conn.cursor()
# 2. Generate Order ID
order_id = f"ORD-{uuid.uuid4().hex[:8].upper()}"
# 3. Handle Order Items (Convert Pydantic models to dict for JSON)
if not order_items:
processed_items = []
else:
processed_items = [item.model_dump() if hasattr(item, "model_dump") else item for item in order_items]
items_json = json.dumps(processed_items, ensure_ascii=False)
# 4. Insert Order
query = """
INSERT INTO public.orders (
order_id, customer_name, phone, email,
shipping_address, order_items, total_amount, order_status
) VALUES (%s, %s, %s, %s, %s, %s, %s, 'pending')
"""
cur.execute(query, (
order_id,
customer_name,
phone,
email,
shipping_address,
items_json,
Decimal(str(total_amount))
))
conn.commit()
cur.close()
conn.close()
logger.info(f"✅ Created order {order_id} for {customer_name}")
return {
"success": True,
"order_id": order_id,
"message": f"Đơn hàng {order_id} đã được tạo thành công! Chúng tôi sẽ sớm liên hệ xác nhận."
}
except Exception as e:
logger.error(f"❌ Failed to create order: {e}")
return {
"success": False,
"message": f"Có lỗi khi tạo đơn hàng: {str(e)}"
}
class CancelOrderInput(BaseModel):
order_id: str = Field(..., description="The ID of the order to cancel (e.g., ORD-ABC12345)")
phone: str = Field(..., description="Customer's phone number for verification")
model_config = {
"extra": "forbid"
}
@tool(args_schema=CancelOrderInput)
def cancel_order(order_id: str, phone: str) -> dict:
"""
Cancel an existing order.
Orders can only be cancelled if their status is 'pending'.
"""
try:
conn = psycopg2.connect(
host=DB_HOST,
port=DB_PORT,
database=DB_NAME,
user=DB_USER,
password=DB_PASS
)
cur = conn.cursor()
# 1. Check if order exists and get status
cur.execute(
"SELECT order_status, phone FROM public.orders WHERE order_id = %s",
(order_id,)
)
row = cur.fetchone()
if not row:
return {"success": False, "message": f"Không tìm thấy đơn hàng {order_id}."}
current_status, db_phone = row
# 2. Verify phone number
if db_phone != phone:
return {"success": False, "message": "Số điện thoại không khớp với đơn hàng."}
# 3. Check if status allows cancellation
if current_status != 'pending':
status_map = {
'cancelled': 'đã bị hủy trước đó',
'processing': 'đang được xử lý',
'shipped': 'đã được giao',
'completed': 'đã hoàn thành'
}
msg = status_map.get(current_status, current_status)
return {
"success": False,
"message": f"Không thể hủy đơn hàng {order_id} vì đơn hàng {msg}."
}
# 4. Update status to 'cancelled'
cur.execute(
"UPDATE public.orders SET order_status = 'cancelled' WHERE order_id = %s",
(order_id,)
)
conn.commit()
cur.close()
conn.close()
logger.info(f"🚫 Cancelled order {order_id}")
return {
"success": True,
"message": f"Đơn hàng {order_id} đã được hủy thành công."
}
except Exception as e:
logger.error(f"❌ Failed to cancel order: {e}")
return {
"success": False,
"message": f"Có lỗi khi hủy đơn hàng: {str(e)}"
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment