import os, json, html, requests, re, random import gradio as gr APP_TITLE = "孙子你好(DeepSeek 多样化·诊断版)" # 可通过 Secrets 覆盖: # DEEPSEEK_API_KEY = sk-xxxxxxxx # DEEPSEEK_MODEL = deepseek-chat(默认) # DEEPSEEK_BASE_URL = https://api.deepseek.com/v1/chat/completions(多数账号) # 或 https://api.deepseek.com/chat/completions(少量旧线路) MODEL_NAME = os.getenv("DEEPSEEK_MODEL", "deepseek-chat").strip() or "deepseek-chat" # ===================== 系统提示词(增加 explain_cn 字段 + 做法语气要求) ===================== SYS_PROMPT = ( "你是中文军师,擅长将《孙子兵法》的智慧用于现实问题。\n" "任务:针对用户的问题,从《孙子兵法》十三篇中找出最贴合的一章,引用原文(若能确定),并转化为现代生活策略。\n\n" "规则:\n" "1. 优先根据问题性质挑选不同篇章:\n" " - 计划/评估:〈始计篇〉〈作战篇〉\n" " - 谈判/攻防:〈谋攻篇〉〈虚实篇〉\n" " - 节奏/风险:〈军形篇〉〈兵势篇〉\n" " - 主动出击/竞争:〈军争篇〉〈九变篇〉〈地形篇〉\n" " - 困境/沟通:〈行军篇〉〈九地篇〉〈用间篇〉〈火攻篇〉\n" "2. 若问题重复,尽量换不同篇章或换角度回答,保持多样性。\n" "3. 若无法百分之百确认原文或篇名,不要硬编,改为思想意译并标记 unsure=true。\n" "4. 严格仅输出 JSON,固定字段:\n" " {\"chapter\":\"<篇名>\"," " \"quote\":\"<原文或思想>\"," " \"unsure\":false/true," " \"insight\":\"<一句人话启示≤24字>\"," " \"action\":\"<一步可执行的做法≤100字,用轻松幽默的口吻表达,但不要改变建议本身的含义>\"," " \"speech\":\"<直接可说的话术≤120字>\"," " \"explain_cn\":\"<用非常简单的话解释这句名言的大意≤60字>\"}\n" "5. 语气简洁、机智、委婉、有边界;不得人身攻击、威胁或违法建议。\n" "6. 禁止输出 JSON 以外的任何文字。" ) # few-shot:覆盖多篇章,减少模型只用某一章的倾向 FEWSHOT = [ # 加班 → 作战篇 { "role": "user", "content": "问题:老板临时要我通宵加班,我状态很差,怎么体面拒绝?只输出JSON。" }, { "role": "assistant", "content": json.dumps({ "chapter": "作战篇", "quote": "夫兵久而国利者,未之有也。", "unsure": False, "insight": "避免持久消耗,保整体战力。", "action": "跟老板提一个“明早高质量交付”的方案,比硬熬一整晚靠谱多了,也能保证你明天还是个清醒的人。", "speech": "今晚赶出来质量可能不稳,我建议明早整理成终版,保证更可靠,您看可以吗?", "explain_cn": "意思是战争拖太久没有好处,打仗要快准狠,不能一直消耗。" }, ensure_ascii=False) }, # 借钱 → 始计篇 { "role": "user", "content": "问题:朋友借钱不还,怎样体面要回?只输出JSON。" }, { "role": "assistant", "content": json.dumps({ "chapter": "始计篇", "quote": "兵者,诡道也。", "unsure": False, "insight": "绕开硬催,设局顺势收回。", "action": "就说自己在清账、要安排资金,先把一部分或者分期要回来,既能把钱收回,又不至于把气氛搞得像开战。", "speech": "我这边在清账需要安排下,能否先转一部分?分期也行,别影响咱们交情。", "explain_cn": "打仗要讲策略,不是直来直去,要懂得绕弯子、用计谋。" }, ensure_ascii=False) }, # 邻居噪音 → 行军篇 { "role": "user", "content": "问题:邻居太吵,怎么友好沟通?只输出JSON。" }, { "role": "assistant", "content": json.dumps({ "chapter": "行军篇", "quote": "知彼知己,百战不殆。(通用于沟通前的情势判断)", "unsure": True, # 行军篇原文无此句,标不确定,演示 unsure 机制 "insight": "先辨来源时机,再择机沟通。", "action": "挑对方不忙不生气的时候,先表示理解,再轻轻说出自己的困扰,让TA自己不好意思再把家当KTV用。", "speech": "最近晚上声音有点明显,我怕打扰到楼上楼下。能否帮忙晚间稍微注意下?谢谢啦~", "explain_cn": "先搞清楚对方情况和自己底牌,这样做事比较稳,不容易吃亏。" }, ensure_ascii=False) }, # 职场竞争 → 军争篇 { "role": "user", "content": "问题:同事跟我抢功劳,怎么应对?只输出JSON。" }, { "role": "assistant", "content": json.dumps({ "chapter": "军争篇", "quote": "以迂为直,以患为利。", "unsure": False, "insight": "不硬碰,转势为己用。", "action": "开会前先发一份“小而精”的纪要,把自己负责的部分写清楚,让功劳自动写进流程里,对方想抢也得先面对记录。", "speech": "我把这次我的负责点和关键产出提前发一份纪要,方便会里对齐,避免遗漏。", "explain_cn": "看起来绕远路,其实是走捷径;把麻烦事变成自己有利的机会。" }, ensure_ascii=False) } ] # ===================== 调用 DeepSeek:主分析 ===================== def call_deepseek(user_question: str) -> dict: """自动尝试两个端点,返回结构化兵法解析结果。""" key = os.getenv("DEEPSEEK_API_KEY", "").strip() if not key: return { "chapter": "", "quote": "未配置 DEEPSEEK_API_KEY", "unsure": True, "insight": "请先在 Settings → Secrets 配置密钥。", "action": "键名必须是 DEEPSEEK_API_KEY,值不要带引号或空格;配置后重启。", "speech": "密钥配好我就能正常回答。", "explain_cn": "当前还无法解释名言,因为没有配置密钥。" } endpoints = [ (os.getenv("DEEPSEEK_BASE_URL", "").strip() or "https://api.deepseek.com/v1/chat/completions"), "https://api.deepseek.com/chat/completions", ] # 多样化提示:鼓励换篇章/换角度 diversity_hint = random.choice([ "尽量避免重复使用同一篇章,尝试换篇章回答。", "优先考虑《军形篇》《兵势篇》《九变篇》《地形篇》的角度。", "如果问题可多解,换一个不同篇章来解释。", "引用少见但贴合的篇章,保持回答多样性。", "可从节奏、态势、资源、时机等不同维度选篇章。" ]) msgs = [{"role": "system", "content": SYS_PROMPT + "\n" + diversity_hint}] + FEWSHOT + [ {"role": "user", "content": f"问题:{user_question} 只输出JSON。"} ] last_err = None for ep in endpoints: try: payload = { "model": MODEL_NAME, "messages": msgs, "temperature": 0.7, "top_p": 0.9, "max_tokens": 450 } r = requests.post( ep, headers={"Authorization": f"Bearer {key}", "Content-Type": "application/json"}, json=payload, timeout=60 ) print(f"[DeepSeek] endpoint={ep} status={r.status_code} model={MODEL_NAME}") # Runtime logs if r.status_code // 100 != 2: try: err_detail = r.json() except Exception: err_detail = {"raw": r.text[:500]} last_err = f"HTTP {r.status_code} @ {ep} (model={MODEL_NAME}) → {err_detail}" continue data = r.json() content = data.get("choices", [{}])[0].get("message", {}).get("content", "").strip() m = re.search(r"\{.*\}", content, flags=re.S) js = m.group(0) if m else content result = json.loads(js) return { "chapter": result.get("chapter", "")[:20], "quote": result.get("quote", "")[:200], "unsure": bool(result.get("unsure", False)), "insight": result.get("insight", "")[:40], "action": result.get("action", "")[:120], "speech": result.get("speech", "")[:140], "explain_cn": result.get("explain_cn", "")[:80], } except Exception as e: last_err = f"{type(e).__name__}: {e}" return { "chapter": "", "quote": "为避免误引,已隐藏原文与篇名(模型不确定出典)。", "unsure": True, "insight": "DeepSeek 请求未成功。", "action": f"诊断信息:{last_err or '未知错误'}", "speech": "请按诊断检查:①密钥②端点③模型名④配额/限速,然后重启。", "explain_cn": "当前无法给出名言解释,请检查配置。" } # ===================== 调用 DeepSeek:中译英翻译 ===================== def translate_to_en(text_cn: str) -> str: """使用 DeepSeek 将简单中文解释翻译成英文。""" key = os.getenv("DEEPSEEK_API_KEY", "").strip() if not key: return "⚠️ 未配置 DEEPSEEK_API_KEY,暂时无法翻译成英文。" endpoints = [ (os.getenv("DEEPSEEK_BASE_URL", "").strip() or "https://api.deepseek.com/v1/chat/completions"), "https://api.deepseek.com/chat/completions", ] msgs = [ { "role": "system", "content": "你是专业的中译英助手,只把用户提供的中文翻译成自然简洁的英文,不要添加解释或多余内容。" }, { "role": "user", "content": text_cn } ] last_err = None for ep in endpoints: try: payload = { "model": MODEL_NAME, "messages": msgs, "temperature": 0.3, "top_p": 0.9, "max_tokens": 200 } r = requests.post( ep, headers={"Authorization": f"Bearer {key}", "Content-Type": "application/json"}, json=payload, timeout=60 ) print(f"[DeepSeek-Translate] endpoint={ep} status={r.status_code} model={MODEL_NAME}") if r.status_code // 100 != 2: try: err_detail = r.json() except Exception: err_detail = {"raw": r.text[:500]} last_err = f"HTTP {r.status_code} @ {ep} (model={MODEL_NAME}) → {err_detail}" continue data = r.json() content = data.get("choices", [{}])[0].get("message", {}).get("content", "").strip() return html.escape(content) except Exception as e: last_err = f"{type(e).__name__}: {e}" return f"⚠️ 翻译失败:{last_err or '未知错误'}" # ===================== 渲染函数 ===================== def render_main(result: dict) -> str: """ 主体渲染(不包含话术示例、不包含“我是老外”解释)。 默认严格校验:若 unsure=True 则隐藏原文与篇名。 """ unsure = bool(result.get("unsure")) chap = html.escape(result.get("chapter", "")) quote = html.escape(result.get("quote", "")) insight = html.escape(result.get("insight", "")) action = html.escape(result.get("action", "")) # 默认“严格出处校验”:模型不确定时,隐藏原文与篇名 show_quote = not unsure parts = ["