import json import logging from collections.abc import AsyncGenerator from pathlib import Path from app.engine.providers.base import AIProvider from app.engine.providers.glm import GLMProvider from app.engine.config import settings logger = logging.getLogger(__name__) PROMPTS_DIR = Path(__file__).resolve().parent.parent / "prompts" def _load_prompt(filename: str) -> str: return (PROMPTS_DIR / filename).read_text(encoding="utf-8") def create_provider() -> AIProvider: if settings.AI_PROVIDER == "glm": return GLMProvider( api_key=settings.ZHIPU_API_KEY, model=settings.AI_MODEL, base_url=settings.ZHIPU_BASE_URL, ) raise ValueError(f"Unknown AI provider: {settings.AI_PROVIDER}") SYSTEM_PROMPT = _load_prompt("system.md") COMPANY_FACTS = _load_prompt("company_facts.md") # 公司事实卡(知识与行为分离) EXTRACTOR_PROMPT = _load_prompt("extractor.md") EVALUATOR_PROMPT = _load_prompt("evaluator.md") # 对话基座: 行为 prompt 在前(红线显著性),事实卡居中(参考资料), # 当轮 context 提醒最后拼接(在 chat/stream_chat 内,保持最新鲜) _BASE_CHAT_PROMPT = SYSTEM_PROMPT + "\n\n" + COMPANY_FACTS class AIService: def __init__(self, provider: AIProvider | None = None): self.provider = provider or create_provider() async def chat(self, messages: list[dict], context: str = "") -> str: """Main conversation: get AI reply to user message.""" prompt = _BASE_CHAT_PROMPT + ("\n\n" + context if context else "") return await self.provider.chat(messages, prompt) async def stream_chat(self, messages: list[dict], context: str = "") -> AsyncGenerator[str, None]: """Stream AI reply token by token.""" prompt = _BASE_CHAT_PROMPT + ("\n\n" + context if context else "") async for token in self.provider.stream_chat(messages, prompt): yield token async def extract_info( self, messages: list[dict], existing_info: dict ) -> dict: """Extract structured info from conversation history.""" prompt = EXTRACTOR_PROMPT.replace( "$existing_info", json.dumps(existing_info or {}, ensure_ascii=False) ).replace("$messages", self._format_messages(messages)) _, extracted = await self.provider.chat_with_extraction( messages=[], system_prompt=prompt ) return extracted async def evaluate( self, profile: dict, recent_messages: list[dict] ) -> dict: """Evaluate customer intent level.""" prompt = EVALUATOR_PROMPT.replace( "$profile", json.dumps(profile, ensure_ascii=False) ).replace("$recent_messages", self._format_messages(recent_messages[-6:])) _, result = await self.provider.chat_with_extraction( messages=[], system_prompt=prompt ) return self._normalize_evaluation(result) @staticmethod def _format_messages(messages: list[dict]) -> str: lines = [] for m in messages: role = m.get("role", "unknown") content = m.get("content", "") label = {"user": "客户", "assistant": "顾问"}.get(role, role) lines.append(f"{label}: {content}") return "\n".join(lines) @staticmethod def _normalize_evaluation(result: dict) -> dict: intent = result.get("intent_level", "pending") if intent not in ("interested", "not_interested", "pending"): intent = "pending" return { "intent_level": intent, "reasoning": result.get("reasoning", ""), "contact_info": result.get("contact_info", {}), "info_sufficient": result.get("info_sufficient", False), }