Files
group_xinghuo_jinrong/app/service/note_service.py
T
zhanghongyu_0626 b841f68295 feat(visitor): Implement visitor chat functionality and enhance customer service interactions
- Added a new visitor chat API endpoint (`/api/chat/visitor`) to allow unauthenticated users to engage in conversations without requiring customer data.
- Introduced a visitor context dependency to manage visitor interactions seamlessly.
- Enhanced the chat API to support explicit session termination and improved response handling for customer service interactions.
- Updated the database configuration to include Redis client support for caching visitor data.
- Added a new customer note repository to persist user notes independently of the L1 profile slots.

This update significantly improves the customer service experience by enabling visitor interactions and ensuring efficient data handling for both registered and unregistered users.
2026-09-09 18:32:00 +08:00

123 lines
4.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""客户显式备注服务(Wave 5)。
职责:
1. save_note_from_message:用户说"你要记住我每天看净值" → DeepSeek 抽取纯净 content+category → 写入 customer_notes 表;
2. render_notes_context:读最近 N 条 active 备注格式化为 prompt 注入文本;
3. forget_all_notes:用户说"忘掉我之前的备注" → 软删除全部备注。
独立于 L1 画像槽位表,不进 style_tags;注入到 chitchat 和 interpret 的 prompt。
铁律:
1. content 为 LLM 抽取后的纯净文本(剥掉"你要记住"指令词);
2. 抽取失败/LLM 异常 → 回落 NOTE_EMPTY_TEXT,不写库;
3. 软删除(is_active=0)保留审计痕。
"""
from __future__ import annotations
from typing import Any
from langchain_openai import ChatOpenAI
from app.config.settings import settings
from app.repository.note_repository import CustomerNoteRepository
from app.service.customer_prompts import (
NOTE_EXTRACT_SYSTEM,
NOTE_EXTRACT_USER_TEMPLATE,
)
def _build_llm() -> ChatOpenAI:
return ChatOpenAI(
model=settings.deepseek_model,
api_key=settings.deepseek_api_key,
base_url=settings.deepseek_base_url,
temperature=settings.deepseek_temperature,
max_tokens=settings.deepseek_max_tokens,
)
def save_note_from_message(
customer_id: str,
session_id: str,
trace_id: str,
message: str,
) -> tuple[str, str | None, str]:
"""从用户消息抽取备注内容+分类并写库。
返回 (content, category, reply_text):
- 成功:(content, category or None, NOTE_SAVED_TEXT)
- 失败:("", None, NOTE_EMPTY_TEXT)
"""
if not message or not message.strip():
return "", None, "您要记住的内容我没有完全理解,能否再具体描述一下?"
try:
from app.service.profile_service import parse_llm_json
llm = _build_llm()
resp = llm.invoke([
{"role": "system", "content": NOTE_EXTRACT_SYSTEM},
{"role": "user", "content": NOTE_EXTRACT_USER_TEMPLATE.format(message=message)},
])
text = resp.content if not isinstance(resp, str) else resp
data = parse_llm_json(text) or {}
except Exception:
return "", None, "您要记住的内容我没有完全理解,能否再具体描述一下?"
content = str(data.get("content") or "").strip()
if not content:
return "", None, "您要记住的内容我没有完全理解,能否再具体描述一下?"
content = content[: settings.note_content_max_len]
category = str(data.get("category") or "").strip().lower() or None
if category not in ("habit", "preference", "reminder", "other"):
category = None
source = (message or "")[:800]
repo = CustomerNoteRepository()
repo.insert_note(
customer_id=customer_id,
session_id=session_id,
trace_id=trace_id,
content=content,
category=category,
source_text=source,
)
return content, category, "好的,我已经记下来了。"
def render_notes_context(customer_id: str) -> str:
"""读最近 N 条 active 备注格式化为 prompt 注入文本。
空备注返回空串(prompt 模板里 notes_context 字段为空)。
"""
if not customer_id:
return ""
try:
repo = CustomerNoteRepository()
notes = repo.list_active_notes(customer_id, settings.note_max_inject)
except Exception:
return ""
if not notes:
return ""
lines: list[str] = []
for n in notes:
c = str(n.get("content") or "").strip()
if not c:
continue
cat = str(n.get("category") or "").strip()
lines.append(f"[{cat}] {c}" if cat else c)
return ";".join(lines)
def forget_all_notes(customer_id: str) -> int:
"""软删除客户所有 active 备注,返回删除条数。"""
if not customer_id:
return 0
try:
repo = CustomerNoteRepository()
return repo.deactivate_all(customer_id)
except Exception:
return 0