sofiamak commited on
Commit
77f08ac
·
verified ·
1 Parent(s): 7a2205b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +116 -86
app.py CHANGED
@@ -1,124 +1,153 @@
1
  import os, json, html, requests, re
2
  import gradio as gr
3
 
4
- APP_TITLE = "孙子你好(直连 DeepSeek)"
5
- DEEPSEEK_API = "https://api.deepseek.com/v1/chat/completions"
6
- DEEPSEEK_KEY = os.getenv("DEEPSEEK_API_KEY") # Settings → Secrets 配置
7
 
8
- MODEL_NAME = "deepseek-chat"
 
 
 
 
 
 
9
 
10
  SYS_PROMPT = (
11
- "你是中文军师,严格依据《孙子兵法》为现实小问题给出策略。\n"
12
- "规则:\n"
13
- "1) 优先引用《孙子兵法》原文句子,并指明篇名(如:谋攻篇、虚实篇…)。\n"
14
- "2) 如果你**无法百分之百确认**某句是否出自《孙子兵法》或不确定篇名,"
15
- " **不要硬编**,改为“意译/思想要点”,并标记 unsure=true。\n"
16
- "3) 输出为 JSON,字段固定:\n"
17
- ' {"chapter":"<篇名或空>","quote":"<原文或意译>","unsure":false/true,'
18
- '"insight":"<一句人话启示≤24字>","action":"<一步可执行做法≤60字>",'
19
- "\"speech\":\"<可直接对话的话术≤80字>\"}\n"
20
- "4) 风格:简洁、机智、委婉、有边界;拒绝人身攻击、威胁、违法建议。\n"
21
- "5) 仅输出 JSON,不要任何多余文字。"
22
  )
23
 
 
24
  FEWSHOT = [
25
  {
26
- "role":"user",
27
- "content":"问题:老板临时要我通宵加班,我状态很差,怎么体面拒绝?只输出JSON。"
28
  },
29
  {
30
- "role":"assistant",
31
- "content":json.dumps({
32
- "chapter":"虚实篇",
33
- "quote":"避实而击虚。",
34
  "unsure": False,
35
- "insight":"别硬碰命令,转打结果。",
36
- "action":"给“今晚粗稿/明早终版”二选一,建议选后者以保质量。",
37
- "speech":"我今晚能出草稿,也能明早交稳定终版;为保证质量,我建议后一种,您看呢?"
38
  }, ensure_ascii=False)
39
  },
40
  {
41
- "role":"user",
42
- "content":"问题:朋友借钱不还,怎样体面要回?只输出JSON。"
43
  },
44
  {
45
- "role":"assistant",
46
- "content":json.dumps({
47
- "chapter":"始计篇",
48
- "quote":"兵者,诡道也。",
49
  "unsure": False,
50
- "insight":"绕开硬催,设局顺势收回。",
51
- "action":"先说在清账需安排资金,再给“部分先还/分期”台阶。",
52
- "speech":"我这边在清账需要安排下,方便的话先转一部分?分期也行,别影响咱们交情。"
53
  }, ensure_ascii=False)
54
  }
55
  ]
56
 
57
  def call_deepseek(user_question: str, alt: bool = False) -> dict:
58
- if not DEEPSEEK_KEY:
 
 
59
  return {
60
  "chapter": "",
61
- "quote": "(未配置 DEEPSEEK_API_KEY)",
62
  "unsure": True,
63
- "insight": "先配置密钥再试。",
64
- "action": "在 Settings → Secrets 添加 DEEPSEEK_API_KEY。",
65
- "speech": "我先把密钥配好,马上再帮你。"
66
  }
67
 
68
- msgs = [{"role":"system","content":SYS_PROMPT}] + FEWSHOT + [
69
- {"role":"user","content": f"问题:{user_question} 只输出JSON。"}
 
 
 
 
 
 
70
  ]
71
- try:
72
- r = requests.post(
73
- DEEPSEEK_API,
74
- headers={"Authorization": f"Bearer {DEEPSEEK_KEY}", "Content-Type":"application/json"},
75
- json={
76
  "model": MODEL_NAME,
77
  "messages": msgs,
78
- "temperature": 0.7 if not alt else 0.9,
79
  "top_p": 0.9,
80
  "max_tokens": 450
81
- },
82
- timeout=30
83
- )
84
- data = r.json()
85
- content = data.get("choices",[{}])[0].get("message",{}).get("content","").strip()
86
- m = re.search(r"\{.*\}", content, flags=re.S)
87
- js = m.group(0) if m else content
88
- result = json.loads(js)
89
- return {
90
- "chapter": result.get("chapter","")[:20],
91
- "quote": result.get("quote","")[:160],
92
- "unsure": bool(result.get("unsure", False)),
93
- "insight": result.get("insight","")[:40],
94
- "action": result.get("action","")[:100],
95
- "speech": result.get("speech","")[:120]
96
- }
97
- except Exception as e:
98
- return {
99
- "chapter": "",
100
- "quote": f"调用 DeepSeek 失败:{type(e).__name__}",
101
- "unsure": True,
102
- "insight": "网络/配额/模型不可用。",
103
- "action": "稍后再试或检查 API Key 与配额。",
104
- "speech": "我先排查下线路,等会再聊。"
105
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
 
107
  def render(result: dict, strict: bool) -> str:
108
- """
109
- strict=True 时:若 unsure 为 True,则隐藏篇名与原文,只展示启示/做法/话术,并提示“为避免误引已隐藏原文”
110
- """
111
  unsure = bool(result.get("unsure"))
112
- chap = html.escape(result.get("chapter",""))
113
- quote= html.escape(result.get("quote",""))
114
- insight = html.escape(result.get("insight",""))
115
- action = html.escape(result.get("action",""))
116
- speech = html.escape(result.get("speech",""))
117
 
118
- show_quote_block = not (strict and unsure)
119
 
120
  parts = ["<div style='font-size:22px;line-height:1.7em;'>"]
121
- if show_quote_block:
122
  badge = "📜" if not unsure else "❓"
123
  chap_line = f"《{chap}》" if chap else "(未标明篇名)"
124
  parts.append(f"{badge} {chap_line}<br><br>")
@@ -129,17 +158,18 @@ def render(result: dict, strict: bool) -> str:
129
  parts.append(f"<b>启示:</b>{insight}<br><br>")
130
  parts.append(f"<b>做法:</b>{action}<br><br>")
131
  parts.append(
132
- "<div style='margin-top:12px;padding:12px 14px;border:1px dashed #e2d7f2;border-radius:10px;background:#fbf8ff;'>"
 
133
  "<div style='font-weight:600;'>💬 话术示例:</div>"
134
  f"<div>{speech}</div>"
135
  "</div>"
136
  )
137
- if not show_quote_block:
138
- parts.append("<div style='color:#9aa; font-size:13px; margin-top:8px;'>提示:关闭“严格出处校验”可查看模型引用的原文与篇名。</div>")
139
  parts.append("</div>")
140
  return "".join(parts)
141
 
142
- # ------------- UI ------------- #
143
  theme = gr.themes.Soft(primary_hue="pink", neutral_hue="slate")
144
  CSS = """
145
  body,.gradio-container{background:#f7f6fb!important;}
@@ -151,7 +181,7 @@ with gr.Blocks(title=APP_TITLE, theme=theme, css=CSS) as demo:
151
  gr.HTML("<div class='header'>孙子你好</div>")
152
  with gr.Group(elem_classes=["card"]):
153
  q = gr.Textbox(label="你的问题", placeholder="例如:朋友借钱不还;老板让我加班;邻居太吵;甲方拖款…", lines=3)
154
- strict = gr.Checkbox(value=True, label="严格出处校验(不确定出典时不显示原文)")
155
  with gr.Row():
156
  b1 = gr.Button("🎯 生成兵法", variant="primary")
157
  b2 = gr.Button("♻️ 换一种说法")
 
1
  import os, json, html, requests, re
2
  import gradio as gr
3
 
4
+ APP_TITLE = "孙子你好(DeepSeek 直连·诊断版)"
 
 
5
 
6
+ # 可通过 Secrets 覆盖:
7
+ # DEEPSEEK_API_KEY = sk-xxxxxxxx
8
+ # DEEPSEEK_MODEL = deepseek-chat(默认)
9
+ # DEEPSEEK_BASE_URL = https://api.deepseek.com/v1/chat/completions(多数账号)
10
+ # 或 https://api.deepseek.com/chat/completions(少量旧线路)
11
+
12
+ MODEL_NAME = os.getenv("DEEPSEEK_MODEL", "deepseek-chat").strip() or "deepseek-chat"
13
 
14
  SYS_PROMPT = (
15
+ "你是中文军师,严格依据《孙子兵法》为现实小问题给出策略。\n"
16
+ "规则:\n"
17
+ "1) 优先引用《孙子兵法》原文句子,并指明篇名(如:谋攻篇、虚实篇)。\n"
18
+ "2) 如果无法百分之百确认出典或篇名,不要硬编,改为意译/思想要点,并标记 unsure=true。\n"
19
+ "3) 仅输出 JSON,固定字段:\n"
20
+ ' {\"chapter\":\"<篇名或空>\",\"quote\":\"<原文或意译>\",\"unsure\":false/true,'
21
+ '\"insight\":\"<一句人话启示≤24字>\",\"action\":\"<一步可执行做法≤100字>\",'
22
+ '\"speech\":\"<可直接对话的话术≤120字>\"}\n'
23
+ "4) 风格:简洁、机智、委婉、有边界;不得人身攻击、威胁或违法建议。\n"
24
+ "5) 严格仅输出 JSON,不要任何多余文字。"
 
25
  )
26
 
27
+ # few-shot 用 json.dumps 生成,避免手写引号带来语法错误
28
  FEWSHOT = [
29
  {
30
+ "role": "user",
31
+ "content": "问题:老板临时要我通宵加班,我状态很差,怎么体面拒绝?只输出JSON。"
32
  },
33
  {
34
+ "role": "assistant",
35
+ "content": json.dumps({
36
+ "chapter": "虚实篇",
37
+ "quote": "避实而击虚。",
38
  "unsure": False,
39
+ "insight": "别硬碰命令,转打结果。",
40
+ "action": "给出“今晚草稿/明早终版”二选一,引导选择后者以保质量和节奏。",
41
+ "speech": "我今晚能出草稿,也能明早交更稳的终版。为保证质量,我建议后一种,您看呢?"
42
  }, ensure_ascii=False)
43
  },
44
  {
45
+ "role": "user",
46
+ "content": "问题:朋友借钱不还,怎样体面要回?只输出JSON。"
47
  },
48
  {
49
+ "role": "assistant",
50
+ "content": json.dumps({
51
+ "chapter": "始计篇",
52
+ "quote": "兵者,诡道也。",
53
  "unsure": False,
54
+ "insight": "绕开硬催,设局顺势收回。",
55
+ "action": "先说明自己在清账安排资金,再给“部分先还/分期”台阶,降低对抗。",
56
+ "speech": "我这边在清账需要安排下,方便的话先转一部分?分期也行,别影响咱们交情。"
57
  }, ensure_ascii=False)
58
  }
59
  ]
60
 
61
  def call_deepseek(user_question: str, alt: bool = False) -> dict:
62
+ """带精确诊断:自动���试两个端点,页面/日志展示状态码与返回体。"""
63
+ key = os.getenv("DEEPSEEK_API_KEY", "").strip()
64
+ if not key:
65
  return {
66
  "chapter": "",
67
+ "quote": "未配置 DEEPSEEK_API_KEY",
68
  "unsure": True,
69
+ "insight": "请先在 Settings → Secrets 配置密钥。",
70
+ "action": "键名必须是 DEEPSEEK_API_KEY,值不要带引号或空格;配置后重启。",
71
+ "speech": "密钥配好我就能正常回答。"
72
  }
73
 
74
+ endpoints = [
75
+ (os.getenv("DEEPSEEK_BASE_URL", "").strip()
76
+ or "https://api.deepseek.com/v1/chat/completions"),
77
+ "https://api.deepseek.com/chat/completions",
78
+ ]
79
+
80
+ msgs = [{"role": "system", "content": SYS_PROMPT}] + FEWSHOT + [
81
+ {"role": "user", "content": f"问题:{user_question} 只输出JSON。"}
82
  ]
83
+
84
+ last_err = None
85
+ for ep in endpoints:
86
+ try:
87
+ payload = {
88
  "model": MODEL_NAME,
89
  "messages": msgs,
90
+ "temperature": 0.9 if alt else 0.7,
91
  "top_p": 0.9,
92
  "max_tokens": 450
93
+ }
94
+ r = requests.post(
95
+ ep,
96
+ headers={"Authorization": f"Bearer {key}", "Content-Type": "application/json"},
97
+ json=payload,
98
+ timeout=60
99
+ )
100
+ print(f"[DeepSeek] endpoint={ep} status={r.status_code} model={MODEL_NAME}") # 写入 Runtime logs
101
+
102
+ if r.status_code // 100 != 2:
103
+ try:
104
+ err_detail = r.json()
105
+ except Exception:
106
+ err_detail = {"raw": r.text[:500]}
107
+ last_err = f"HTTP {r.status_code} @ {ep} (model={MODEL_NAME}) → {err_detail}"
108
+ continue
109
+
110
+ data = r.json()
111
+ content = data.get("choices", [{}])[0].get("message", {}).get("content", "").strip()
112
+ m = re.search(r"\{.*\}", content, flags=re.S)
113
+ js = m.group(0) if m else content
114
+ result = json.loads(js)
115
+
116
+ # 规整长度,防止偶发超长
117
+ return {
118
+ "chapter": result.get("chapter", "")[:20],
119
+ "quote": result.get("quote", "")[:160],
120
+ "unsure": bool(result.get("unsure", False)),
121
+ "insight": result.get("insight", "")[:40],
122
+ "action": result.get("action", "")[:100],
123
+ "speech": result.get("speech", "")[:120]
124
+ }
125
+ except Exception as e:
126
+ last_err = f"{type(e).__name__}: {e}"
127
+
128
+ # 两个端点都失败
129
+ return {
130
+ "chapter": "",
131
+ "quote": "为避免误引,已隐藏原文与篇名(模型不确定出典)。",
132
+ "unsure": True,
133
+ "insight": "DeepSeek 请求未成功。",
134
+ "action": f"诊断信息:{last_err or '未知错误'}",
135
+ "speech": "请按诊断检查:①密钥②端点③模型名④配额/限速,然后重启。"
136
+ }
137
 
138
  def render(result: dict, strict: bool) -> str:
139
+ """strict=True:若 unsure 则隐藏原文与篇名,仅展示启示/做法/话术并提示原因。"""
 
 
140
  unsure = bool(result.get("unsure"))
141
+ chap = html.escape(result.get("chapter", ""))
142
+ quote = html.escape(result.get("quote", ""))
143
+ insight = html.escape(result.get("insight", ""))
144
+ action = html.escape(result.get("action", ""))
145
+ speech = html.escape(result.get("speech", ""))
146
 
147
+ show_quote = not (strict and unsure)
148
 
149
  parts = ["<div style='font-size:22px;line-height:1.7em;'>"]
150
+ if show_quote:
151
  badge = "📜" if not unsure else "❓"
152
  chap_line = f"《{chap}》" if chap else "(未标明篇名)"
153
  parts.append(f"{badge} {chap_line}<br><br>")
 
158
  parts.append(f"<b>启示:</b>{insight}<br><br>")
159
  parts.append(f"<b>做法:</b>{action}<br><br>")
160
  parts.append(
161
+ "<div style='margin-top:12px;padding:12px 14px;border:1px dashed #e2d7f2;"
162
+ "border-radius:10px;background:#fbf8ff;'>"
163
  "<div style='font-weight:600;'>💬 话术示例:</div>"
164
  f"<div>{speech}</div>"
165
  "</div>"
166
  )
167
+ if not show_quote:
168
+ parts.append("<div style='color:#9aa;font-size:13px;margin-top:8px;'>提示:取消勾选“严格出处校验”可查看模型引用。</div>")
169
  parts.append("</div>")
170
  return "".join(parts)
171
 
172
+ # ---------------- UI ----------------
173
  theme = gr.themes.Soft(primary_hue="pink", neutral_hue="slate")
174
  CSS = """
175
  body,.gradio-container{background:#f7f6fb!important;}
 
181
  gr.HTML("<div class='header'>孙子你好</div>")
182
  with gr.Group(elem_classes=["card"]):
183
  q = gr.Textbox(label="你的问题", placeholder="例如:朋友借钱不还;老板让我加班;邻居太吵;甲方拖款…", lines=3)
184
+ strict = gr.Checkbox(value=True, label="严格出处校验(不确定出典时隐藏原文)")
185
  with gr.Row():
186
  b1 = gr.Button("🎯 生成兵法", variant="primary")
187
  b2 = gr.Button("♻️ 换一种说法")