Files
FiDA_Python/src/server/agent/agents/persona.py
2026-03-11 21:45:46 +08:00

155 lines
6.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.
import json
from typing import Dict, Any
from datetime import datetime
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableConfig
from langchain_core.messages import AIMessage, HumanMessage
from langgraph.types import interrupt
from pymongo import MongoClient
from src.core.config import MONGO_URI
from src.server.agent.agents.init_llm import persona_agent
from src.server.agent.memory.memory_manager import MemoryManager
from src.server.agent.state import AgentState
client = MongoClient(MONGO_URI)
db = client["furniture_agent_db"]
persona_collection = db["user_persona"]
EXTRACTION_PROMPT = ChatPromptTemplate.from_messages([
("system", """你是一个专为家具设计师服务的用户画像提取专家。
当前已知用户画像JSON格式
{current_persona_json}
任务:
1. 从下面所有用户消息中,提取或更新用户对家具设计的偏好信息。
只提取明确提到或强烈暗示的内容,不要臆想或添加默认值。
2. 输出更新后的完整 persona JSON只包含有值字段
推荐使用的键名(优先使用这些):
- "风格偏好" (如 "北欧""极简""工业风"
- "家具类型" (如 "沙发""餐桌""书柜""办公椅"
- "颜色偏好" (如 "原木色""白色""深灰""大地色系"
- 其他可选键:预算范围、空间大小、材质偏好、使用场景等
3. 判断当前画像是否“足够完整”来支持针对家具设计师的市场趋势报告:
- **必须条件**:至少包含 "风格偏好""家具类型""颜色偏好" 中的 2 项以上
- 如果缺少 2 项或以上核心信息,返回 complete: false并生成一个自然、礼貌、具体的追问中文优先询问缺失的核心项
- 如果核心三项中至少有 2 项已明确,返回 complete: true不需要追问
输出必须是严格的 JSON 对象,包含三个字段:
- "persona": 一个对象,键是画像属性,值是对应的字符串(或数组,如果有多个偏好)
- "complete": 布尔值 true 或 false
- "question": 字符串,如果 complete 为 true 则为空字符串,否则是具体的追问句子
示例输出结构(仅供参考,不要直接复制):
{{
"persona": {{
"风格偏好": "北欧简约",
"家具类型": "沙发",
"颜色偏好": "原木色 + 浅灰"
}},
"complete": true,
"question": ""
}}
"""),
("placeholder", "{messages}"),
])
def get_persona_from_mongo(thread_id: str) -> Dict[str, Any]:
doc = persona_collection.find_one({"thread_id": thread_id}, sort=[("_id", -1)])
if doc and "persona" in doc:
return doc["persona"]
return {}
def save_persona_to_mongo(thread_id: str, persona: Dict[str, Any], is_complete: bool):
try:
result = persona_collection.update_one(
{"thread_id": thread_id},
{
"$set": {
"persona": persona,
"persona_complete": is_complete,
"updated_at": datetime.utcnow(),
"last_update_reason": "persona_node_update"
}
},
upsert=True
)
print(f"[Persona Save] thread_id: {thread_id} | matched: {result.matched_count} | modified: {result.modified_count} | upserted: {result.upserted_id}")
except Exception as e:
print(f"[Persona Save Error] {e}")
def persona_node(state: AgentState, config: RunnableConfig):
thread_id = config["configurable"]["thread_id"]
# 读取已有画像MongoDB 优先)
persisted_persona = get_persona_from_mongo(thread_id)
current_persona = state.get("persona", persisted_persona)
# messages = state["messages"]
messages = MemoryManager.build_llm_context(state)
current_persona_json = json.dumps(current_persona, ensure_ascii=False, indent=None)
chain = EXTRACTION_PROMPT | persona_agent
result = chain.invoke({
"current_persona_json": current_persona_json,
"messages": messages,
})
updated_persona = result.persona
is_complete = result.complete
question = (result.question or "").strip()
updates = {
"persona": updated_persona,
"persona_complete": is_complete,
"persona_summary": json.dumps(updated_persona, ensure_ascii=False, indent=2),
}
# 持久化到 MongoDB
save_persona_to_mongo(thread_id, updated_persona, is_complete)
if is_complete:
updates["messages"] = messages + [AIMessage(
content=(
"用户画像已足够完整(风格、家具类型、颜色偏好已明确),并已保存到项目记录。\n\n"
"接下来是否需要我为您生成一份针对当前风格与家具类型的市场趋势报告?\n"
"回复“是”或“需要”即可开始生成;回复“不需要”或“先不用”则直接进入家具设计阶段。"
)
)]
return updates
# 不完整 → 询问(优先问核心三项)
if not question:
missing = []
core_keys = ["风格偏好", "家具类型", "颜色偏好"]
for key in core_keys:
if key not in updated_persona or not updated_persona[key]:
missing.append(key)
if missing:
question = f"为了更好地为您生成趋势报告,能否补充一下您对{''.join(missing)}的偏好呢?"
else:
question = "您对家具的风格、类型或颜色有什么特别的偏好吗?可以多说一些~"
updated_messages = messages + [AIMessage(content=question)]
approved = interrupt({
**updates,
"messages": updated_messages,
"persona_complete": False,
"__interrupt__": {
"type": "persona_question",
"question": question,
"node": "Persona",
"wait_for": "human_response",
# 可选:当前画像快照,便于前端显示或调试
"current_persona_snapshot": updated_persona
}
})
return approved