Spaces:
Paused
Paused
| """ | |
| Service for generating replies using Perplexity API. | |
| """ | |
| import os | |
| from typing import Optional | |
| from openai import OpenAI | |
| SYSTEM_PROMPT = """ | |
| Bạn là một wingman AI tinh tế, chuyên giúp Nam soạn 1 tin nhắn trả lời duy nhất trong hội thoại hẹn hò tiếng Việt. Bạn luôn nhìn từ góc nhìn của Nam, xưng "anh" và gọi đối phương là "em". | |
| Bạn được cung cấp: | |
| - HỘI THOẠI: đoạn hội thoại gần nhất giữa Nam (Male) và Nữ (Female), phân tách các tin bằng ký hiệu "|||". | |
| - TRIGGER: intent hiện tại (ví dụ: neutral, positive, negative, confused...). | |
| - MOVE: chiến lược hiện tại (ví dụ: escalate, hold, de-escalate, tease, comfort...). | |
| Nhiệm vụ của bạn: | |
| - Dựa trên HỘI THOẠI + TRIGGER + MOVE, hãy chọn một hướng phản hồi tự nhiên, duyên dáng, đúng chiến lược (không quá đẩy hay quá lùi so với MOVE). | |
| - Ưu tiên giữ mạch cảm xúc nhất quán với hội thoại, tránh tạo thông tin fact mới về thế giới bên ngoài hoặc về hai người. | |
| QUY TẮC CỨNG: | |
| - Chỉ trả về đúng 1 câu duy nhất. | |
| - Tối đa 25 từ tiếng Việt. | |
| - Lịch sự, ấm áp, thân thiện; không phán xét, không thô lỗ. | |
| - Không giải thích meta (không nói về "prompt", "AI", "chiến lược", "MOVE", "TRIGGER"...). | |
| - Không lặp lại nguyên văn câu của đối phương. | |
| - Không thêm fact mới (chỉ dựa trên những gì có trong hội thoại, hoặc các câu nói chung chung, không cụ thể hóa thông tin chưa có). | |
| Khi TRIGGER hoặc MOVE có vẻ mâu thuẫn với HỘI THOẠI: | |
| - Hãy ưu tiên sự an toàn và mềm mại. | |
| - Có thể hỏi lại nhẹ nhàng để làm rõ, nhưng vẫn giữ frame chủ động, tự tin của Nam. | |
| PHONG CÁCH: | |
| - Ấm áp, tự tin nhưng không tự cao. | |
| - Có thể dùng từ đệm tự nhiên (nha, nhé, ạ, dạ) khi phù hợp với ngữ cảnh. | |
| - Phản chiếu cảm xúc của đối phương. | |
| - Giữ mạch trò chuyện mở để còn đất tăng tương tác về sau. | |
| Nếu vì bất kỳ lý do gì bạn không thể tuân thủ tất cả quy tắc trên: | |
| - Hãy ưu tiên vẫn trả về đúng 1 câu, ≤25 từ, không chứa meta, không chứa thông tin fact mới. | |
| """.strip() | |
| class PerplexityReplyService: | |
| """Service for generating replies using Perplexity API.""" | |
| def __init__(self, api_key: Optional[str] = None, model: str = "sonar"): | |
| """ | |
| Initialize Perplexity service. | |
| Args: | |
| api_key: Perplexity API key. If None, will try to get from PERPLEXITY_API_KEY env var. | |
| model: Model name to use (default: "sonar") | |
| Valid models: sonar, sonar-pro, llama-3.1-sonar-small-32k-online, etc. | |
| See: https://docs.perplexity.ai/getting-started/models | |
| Note: Model names may vary. Check documentation for current valid models. | |
| """ | |
| self.api_key = api_key or os.getenv("PERPLEXITY_API_KEY") | |
| if not self.api_key: | |
| raise ValueError( | |
| "Perplexity API key is required.\n\n" | |
| "Set environment variable:\n" | |
| " export PERPLEXITY_API_KEY=pplx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\n" | |
| "Or pass api_key parameter when initializing PerplexityReplyService." | |
| ) | |
| self.client = OpenAI( | |
| api_key=self.api_key, | |
| base_url="https://api.perplexity.ai", | |
| ) | |
| self.model = model | |
| def generate_reply( | |
| self, | |
| conversation: str, | |
| trigger: str, | |
| move: str, | |
| temperature: float = 0.2, | |
| max_tokens: int = 80, | |
| ) -> str: | |
| """ | |
| Generate reply using Perplexity API. | |
| Args: | |
| conversation: Conversation text in format "Male: ... ||| Female: ..." | |
| trigger: Trigger label (e.g., "rapport_bid", "flirt_charm") | |
| move: Move label (e.g., "charm", "invite", "validate") | |
| temperature: Sampling temperature | |
| max_tokens: Maximum tokens to generate | |
| Returns: | |
| Generated reply text (1 sentence, ≤25 words) | |
| """ | |
| user_content = f""" | |
| HỘI THOẠI: "{conversation}" | |
| TRIGGER: "{trigger}" | |
| MOVE: "{move}" | |
| """.strip() | |
| try: | |
| completion = self.client.chat.completions.create( | |
| model=self.model, | |
| temperature=temperature, | |
| max_tokens=max_tokens, | |
| messages=[ | |
| { | |
| "role": "system", | |
| "content": SYSTEM_PROMPT, | |
| }, | |
| { | |
| "role": "user", | |
| "content": user_content, | |
| }, | |
| ], | |
| ) | |
| raw = completion.choices[0].message.content.strip() if completion.choices[0].message.content else "" | |
| # Hậu xử lý: lấy câu đầu, giới hạn 25 từ | |
| import re | |
| # Tách theo dấu câu, lấy câu đầu | |
| sentences = re.split(r'[.!?]', raw) | |
| one_sentence = sentences[0].strip() if sentences else raw.strip() | |
| # Giới hạn 25 từ | |
| words = one_sentence.split() | |
| limited = " ".join(words[:25]) | |
| # Đảm bảo kết thúc bằng dấu câu nếu cần | |
| if limited and not limited[-1] in ".!?": | |
| limited = limited.rstrip(",;:") + "." | |
| return limited | |
| except Exception as e: | |
| raise Exception(f"Perplexity API error: {str(e)}") | |
| # Global singleton instance | |
| _perplexity_service = None | |
| def get_perplexity_service( | |
| api_key: Optional[str] = None, | |
| model: str = "sonar", | |
| ) -> PerplexityReplyService: | |
| """Get or create the global Perplexity service instance.""" | |
| global _perplexity_service | |
| if _perplexity_service is None: | |
| _perplexity_service = PerplexityReplyService(api_key=api_key, model=model) | |
| return _perplexity_service | |