from __future__ import annotations from typing import Any from .config_loader import business_config from .decision import evaluate_tree from .retrieval import retrieve_for_scheme from .session_store import ( Session, looks_like_handoff, naive_fill_slots, next_slot_prompt, store, ) def create_session() -> Session: return store.create() def handle_message(session_id: str, message: str) -> dict[str, Any]: session = store.get(session_id) if not session: raise KeyError("session_not_found") text = (message or "").strip() session.messages.append({"role": "user", "content": text}) if looks_like_handoff(text, business_config.handoff_phrases): session.phase = "handoff" reply = "已为您标记转人工。请留下称呼与联系方式,销售同事会跟进。(原型:仅记录意图,未接 Chatwoot)" return _finish(session, reply, need_handoff=True) # fill slots while collecting if session.phase in ("collecting", "scheme_ready"): session.slots = naive_fill_slots(text, business_config.slots, session.slots) nxt = next_slot_prompt(business_config.slots, session.slots) if nxt and session.phase == "collecting": # first turn with empty slots: greet + first question if len(session.messages) == 1 and not any(session.slots.values()): # put first user utterance into first slot if they already answered something session.slots = naive_fill_slots(text, business_config.slots, session.slots) nxt = next_slot_prompt(business_config.slots, session.slots) if nxt: reply = nxt["prompt"] return _finish(session, reply, asking_slot=nxt["id"]) # all required (+ optional asked or filled) → scheme decision = evaluate_tree(business_config.rules, session.slots) confidence = float(decision.get("confidence") or 0) need_handoff = bool(decision.get("handoff")) or confidence < business_config.handoff_confidence_below bundle = list(decision.get("product_bundle") or []) retrieval = retrieve_for_scheme(session.slots, bundle) scheme = { "tech_line": decision.get("tech_line"), "product_bundle": bundle, "summary": decision.get("summary"), "confidence": confidence, "rule_id": decision.get("rule_id"), "citations": [ {"id": c.get("id"), "title": c.get("title"), "content": c.get("content")} for c in retrieval.chunks ], "slots": dict(session.slots), } session.last_scheme = scheme session.phase = "handoff" if need_handoff else "scheme_ready" lines = [ business_config.closing_prompt, "", f"技术路线:{scheme['tech_line']}", f"推荐组合:{', '.join(bundle)}", f"说明:{scheme['summary']}", f"置信度:{confidence:.0%}", ] if scheme["citations"]: lines.append("") lines.append("知识依据(原型检索):") for c in scheme["citations"][:3]: lines.append(f"- {c['title']}:{c['content']}") if need_handoff: lines.append("") lines.append("系统建议转人工确认后再对外发送方案。回复「转人工」可进入人工流程。") else: lines.append("") lines.append("可继续补充约束,或回复「转人工」交给销售。") reply = "\n".join(lines) return _finish(session, reply, scheme=scheme, need_handoff=need_handoff) def _finish( session: Session, reply: str, *, asking_slot: str | None = None, scheme: dict[str, Any] | None = None, need_handoff: bool = False, ) -> dict[str, Any]: session.messages.append({"role": "assistant", "content": reply}) store.touch(session) return { "session_id": session.id, "reply": reply, "phase": session.phase, "slots": session.slots, "asking_slot": asking_slot, "scheme": scheme or session.last_scheme, "need_handoff": need_handoff, }