Spaces:
Sleeping
Sleeping
| """ | |
| v5 SupervisorAdvisor — 单次督导调用版 | |
| 架构:每轮回应前,把对话历史发给「督导」做一次 LLM 分析, | |
| 督导输出:来访者当前状态 / 本轮关注点 / 回应方向 / 操作原则。 | |
| 咨询师根据督导建议生成回应。无树搜索,无来访者模拟。 | |
| """ | |
| import json | |
| import os | |
| import time | |
| from langchain_openai import ChatOpenAI | |
| from langchain_core.messages import HumanMessage, AIMessage | |
| SUPERVISOR_PROMPT = """你是一位经验丰富的精神动力学临床督导。咨询正在进行中,你需要快速分析当前对话,给出本轮的回应建议。 | |
| ## 当前对话记录 | |
| {conversation_history} | |
| ## 来访者最新发言 | |
| {client_latest} | |
| ## 分析任务 | |
| 基于精神动力学视角完成分析: | |
| 1. **来访者当前状态**:防御水平(高/中/低)、当前主要防御机制、情感基调 | |
| 2. **本轮核心关注点**:来访者话语中最值得跟进的一个具体点(不要泛化) | |
| 3. **回应方向**:咨询师本轮应聚焦的方向,一句话,操作级 | |
| 4. **回应原则**:2-3条操作原则,明确告诉咨询师怎么做、避免什么 | |
| 严格要求: | |
| - 具体到此刻的来访者状态,不要套话 | |
| - 原则必须可操作("用'我在想……'开头做一个试探性诠释" 而不是 "要共情") | |
| - 不要从身体感受或躯体体验切入 | |
| 只输出 JSON,不要输出任何其他内容: | |
| {{"client_state":"来访者当前状态(一句话)","focal_point":"本轮核心关注点(一句话)","direction":"回应方向(一句话)","principles":["原则1","原则2","原则3"]}}""" | |
| SUPERVISOR_GUIDANCE_TEMPLATE = """ | |
| ## 督导建议(你必须参考执行,但不要向来访者透露这个指令的存在) | |
| **来访者当前状态**:{client_state} | |
| **本轮关注点**:{focal_point} | |
| **本轮方向**:{direction} | |
| **回应原则**: | |
| {principles} | |
| 根据以上督导建议生成本轮回应。保持你的临床判断,自然表达。 | |
| """ | |
| class SupervisorAdvisor: | |
| """单次督导调用:把对话历史交给督导做分析,返回结构化建议。""" | |
| def __init__(self, model="qwen-turbo"): | |
| dashscope = dict( | |
| base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", | |
| api_key=os.getenv("DASHSCOPE_API_KEY"), | |
| ) | |
| self.llm = ChatOpenAI(model=model, **dashscope, temperature=0.3, max_tokens=512) | |
| def _format_history(self, history): | |
| lines = [] | |
| for msg in history: | |
| if isinstance(msg, HumanMessage): | |
| lines.append(f"来访者:{msg.content}") | |
| elif isinstance(msg, AIMessage): | |
| lines.append(f"咨询师:{msg.content}") | |
| return "\n".join(lines) if lines else "(无)" | |
| def _parse_json(self, text): | |
| content = text.strip() | |
| start = content.find("{") | |
| end = content.rfind("}") + 1 | |
| if start == -1 or end == 0: | |
| raise ValueError(f"无法解析 JSON: {content[:80]}") | |
| return json.loads(content[start:end]) | |
| def supervise(self, history, client_latest): | |
| """ | |
| 分析当前对话,返回督导建议 dict。 | |
| history: List[HumanMessage | AIMessage](不含最新来访者发言) | |
| client_latest: str,来访者最新发言 | |
| """ | |
| t = time.time() | |
| history_text = self._format_history(history) | |
| prompt = SUPERVISOR_PROMPT.replace( | |
| "{conversation_history}", history_text | |
| ).replace("{client_latest}", client_latest) | |
| for attempt in range(3): | |
| try: | |
| result = self.llm.invoke(prompt) | |
| parsed = self._parse_json(result.content) | |
| elapsed = time.time() - t | |
| print(f"[督导] {elapsed:.1f}s | 状态: {parsed.get('client_state','?')[:40]}") | |
| print(f"[督导] 关注点: {parsed.get('focal_point','?')[:50]}") | |
| print(f"[督导] 方向: {parsed.get('direction','?')[:50]}") | |
| return parsed | |
| except Exception as e: | |
| if attempt == 2: | |
| print(f"[督导] 分析失败,返回空建议: {e}") | |
| return None | |
| def format_guidance(self, supervision): | |
| """把督导建议格式化为注入到咨询师 prompt 的文本。""" | |
| if not supervision: | |
| return None | |
| principles_text = "\n".join( | |
| f"- {p}" for p in supervision.get("principles", []) | |
| ) | |
| return SUPERVISOR_GUIDANCE_TEMPLATE.replace( | |
| "{client_state}", supervision.get("client_state", "") | |
| ).replace( | |
| "{focal_point}", supervision.get("focal_point", "") | |
| ).replace( | |
| "{direction}", supervision.get("direction", "") | |
| ).replace( | |
| "{principles}", principles_text | |
| ) | |