File size: 10,245 Bytes
d97bbdd
 
 
 
 
 
 
 
 
 
 
 
be7904e
d97bbdd
 
 
 
 
 
 
5dc1baf
 
 
 
 
 
 
 
d97bbdd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
545ca17
 
d97bbdd
545ca17
 
 
d97bbdd
 
545ca17
 
d97bbdd
545ca17
 
 
 
 
 
d97bbdd
 
545ca17
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d97bbdd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
########################################
# 0) 安装/导入依赖
########################################
# 请先 pip3 install openai gradio langchain text2vec

from openai import OpenAI
import gradio as gr
import csv
from datetime import datetime
import torch
from langchain.vectorstores import FAISS
from langchain_huggingface import HuggingFaceEmbeddings
import os

########################################
# 1) 初始化 DeepSeek 客户端 & 向量检索 Embeddings
########################################

# 使用 DeepSeek 提供的 OpenAI 兼容API
# 注意这里要填上你的 API Key

deepseek_key = os.getenv("DEEPSEEK_KEY")
if not deepseek_key:
    print("Deepseek key not found. Please set it in HF Space Secrets.")
else:
    print("Deepseek key loaded successfully!")

client = OpenAI(api_key=deepseek_key, base_url="https://api.deepseek.com")

device = "cuda" if torch.cuda.is_available() else "cpu"

embeddings = HuggingFaceEmbeddings(
    model_name="shibing624/text2vec-base-chinese",
    model_kwargs={"device": device}
)

########################################
# 2) 向量检索逻辑
########################################

def load_vectorstore(index_path: str, embed_obj) -> FAISS:
    """加载 FAISS 索引"""
    return FAISS.load_local(
        index_path,
        embed_obj,
        allow_dangerous_deserialization=True
    )

def build_prompt_for_chatgpt(query, cs_docs, hc_docs, ic_docs):
    """
    根据检索到的三类文档(CS/HC/IC),构造对大模型的提示 Prompt。
    """
    # 组装 Customer Success
    cs_context = []
    for i, doc in enumerate(cs_docs, start=1):
        cs_context.append(f"[CS Doc {i}]\n{doc.page_content}\n")

    # 组装 Help Center
    hc_context = []
    for i, doc in enumerate(hc_docs, start=1):
        link = doc.metadata.get("链接", "无链接")
        hc_context.append(f"[HC Doc {i}]\n链接: {link}\n{doc.page_content}\n")

    # 组装 IC
    ic_context = []
    for i, doc in enumerate(ic_docs, start=1):
        link = doc.metadata.get("链接", "无链接")
        ic_context.append(f"[IC Doc {i}]\n链接: {link}\n{doc.page_content}\n")

    prompt = f"""\
请根据以下文档,回答用户的问题。如果无法从文档中找到答案,请说明你无法回答。

用户问题:{query}

======================
【Customer Success - Top {len(cs_docs)}
{''.join(cs_context)}

【Help Center - Top {len(hc_docs)}
{''.join(hc_context)}

【IC - Top {len(ic_docs)}
{''.join(ic_context)}
======================

请根据helpcenter链接查询help center内容给出准确答案

请给出简洁、准确、机构清晰且包含必要细节的回答,并尽量引用help center链接,但不需要表明来自哪个文档:
"""
    return prompt

def combined_search(query, cs_index_path, hc_index_path, ic_index_path,
                    cs_k=3, hc_k=2, ic_k=3):
    """
    - 分别加载 CS、HC、IC 的向量索引
    - 分别检索 query
    - 将检索到的文档传递给 build_prompt_for_chatgpt,返回 Prompt
    """
    cs_vectorstore = load_vectorstore(cs_index_path, embeddings)
    hc_vectorstore = load_vectorstore(hc_index_path, embeddings)
    ic_vectorstore = load_vectorstore(ic_index_path, embeddings)

    cs_docs = cs_vectorstore.similarity_search(query, k=cs_k)
    hc_docs = hc_vectorstore.similarity_search(query, k=hc_k)
    ic_docs = ic_vectorstore.similarity_search(query, k=ic_k)

    prompt = build_prompt_for_chatgpt(query, cs_docs, hc_docs, ic_docs)
    return cs_docs, hc_docs, ic_docs, prompt

########################################
# 3) 调用 DeepSeek "deepseek-reasoner" 生成答案
########################################

def generate_answer_with_deepseek(prompt_text):
    """
    用 DeepSeek 的 “deepseek-reasoner” 模型生成答案。
    你给出的示例是通过 `client.chat.completions.create()`.
    """
    # 参考你给的代码,构造 messages
    messages = [
        {"role": "system", "content": "You are a helpful assistant"},
        {"role": "user", "content": prompt_text},
    ]

    response = client.chat.completions.create(
        model="deepseek-reasoner",
        messages=messages,
        stream=False
    )

    # 取回答文本
    return response.choices[0].message.content

########################################
# 4) Gradio 接口函数 - 检索并直接调 DeepSeek
########################################

def run_search_and_answer(user_query, store_name):
    """
    1) 检索出文档(cs_docs / hc_docs / ic_docs)
    2) 生成 Prompt
    3) 调用 DeepSeek 模型生成答案
    4) 返回给 Gradio 界面
    """
    cs_index_path = "CS_faiss_index"
    hc_index_path = "HC_faiss_index"
    ic_index_path = "IC_faiss_index"

    # 做检索 + 构造 prompt
    cs_docs, hc_docs, ic_docs, prompt_text = combined_search(
        query=user_query,
        cs_index_path=cs_index_path,
        hc_index_path=hc_index_path,
        ic_index_path=ic_index_path,
        cs_k=5,
        hc_k=2,
        ic_k=5
    )

    # 生成 DeepSeek 回答
    deepseek_answer = generate_answer_with_deepseek(prompt_text)

    # 拼出检索到的简要结果,返回给前端查看
    cs_result = "\n".join([
        f"{doc.page_content[:60]}... (Link: {doc.metadata.get('link', '无链接')})"
        for i, doc in enumerate(cs_docs, start=1)
    ])

    hc_result_list = []
    hc_links = []
    for i, doc in enumerate(hc_docs, start=1):
        snippet = doc.page_content[:60]
        link = doc.metadata.get('链接', '无链接')
        hc_result_list.append(f"{snippet}... (Link: {link})")
        hc_links.append(link)
    hc_result = "\n".join(hc_result_list)

    ic_result = "\n".join([
        f"{doc.page_content[:60]}... (Link: {doc.metadata.get('链接', '无链接')})"
        for i, doc in enumerate(ic_docs, start=1)
    ])

    doc_summary = (
        "=== Customer Success Docs ===\n"
        f"{cs_result}\n\n"
        "=== Help Center Docs ===\n"
        f"{hc_result}\n\n"
        "=== IC Docs ===\n"
        f"{ic_result}\n"
    )

    # 合并多个 Help Center 链接为一个字符串,用分号隔开
    links_str = "; ".join(hc_links)

    # 把检索结果、最终 Prompt、DeepSeek答案、HC链接 一并返回
    return doc_summary, prompt_text, deepseek_answer, links_str

from huggingface_hub import HfApi

def record_feedback(user_query, final_answer, feedback_choice, improved_answer, store_name, links_str):
    # 示例逻辑:将用户反馈信息写到 feedback.csv,然后推送到 Hugging Face dataset

    # 1) 写入本地 feedback.csv
    with open("feedback.csv", "a", encoding="utf-8", newline="") as f:
        writer = csv.writer(f)
        current_time = datetime.now().strftime("%Y-%m-%d %H:00:00")
        # 根据你的字段顺序自行调整
        writer.writerow([
            current_time,
            store_name,
            user_query,
            feedback_choice,
            final_answer if feedback_choice == "好" else improved_answer,
            links_str
        ])

    # 2) 从 Space Secrets 或环境变量读取你的写权限 Token
    hf_token = os.getenv("HF_TOKEN", None)
    if not hf_token:
        return "缺少 HF_TOKEN,无法推送到 Hugging Face。"

    # 3) 调用 huggingface_hub 上传
    api = HfApi()
    try:
        api.upload_file(
            path_or_fileobj="feedback.csv",       # 本地文件路径
            path_in_repo="feedback.csv",          # 仓库里存放的文件名,可自定义
            repo_id="PebllaRyan/Feedbacks",       # 你的数据集仓库ID
            repo_type="dataset",                  # 这里要写 "dataset"
            token=hf_token,
            commit_message="Update feedback logs" # 每次推送的commit说明
        )
        return "已记录到本地 CSV,并成功推送到 PebllaRyan/Feedbacks!"
    except Exception as e:
        return f"本地记录成功,但推送到仓库失败: {e}"


########################################
# 6) 搭建 Gradio UI
########################################
with gr.Blocks() as demo:
    gr.Markdown("## Peblla 智能知识库助手 - 整合DeepSeek示例")

    # 店铺名称
    store_name_box = gr.Textbox(label="店铺名称", placeholder="可选:你在哪个店铺遇到了问题?")

    # 用户输入问题
    user_query_box = gr.Textbox(label="问题", lines=2)

    # 点击按钮进行检索 + DeepSeek回答
    btn_search = gr.Button("提交问题")

    # 显示检索结果、Prompt、DeepSeek的回答
    doc_result_box = gr.Textbox(label="检索到的文档(简要)", interactive=False, lines=6)
    prompt_box = gr.Textbox(label="生成的 Prompt", interactive=False, lines=6, visible=False)
    deepseek_answer_box = gr.Textbox(label="DeepSeek 回答", interactive=False, lines=6)

    # 隐藏组件,用于存储检索到的 HC 链接字符串
    hc_links_box = gr.Textbox(visible=False)

    gr.Markdown("---")

    # 用户对 DeepSeek 的回答做出评价
    feedback_choice = gr.Radio(
        choices=["好", "不好"],
        label="回答质量如何?",
        value=None
    )

    improved_answer_box = gr.Textbox(
        label="如果选择“不好”,请在这里输入改进后的答案",
        lines=5
    )

    btn_feedback = gr.Button("提交反馈")
    feedback_result = gr.Markdown()

    # 当“提交问题”按钮被点击时,执行 run_search_and_answer
    btn_search.click(
        fn=run_search_and_answer,
        inputs=[user_query_box, store_name_box],
        outputs=[doc_result_box, prompt_box, deepseek_answer_box, hc_links_box]
    )

    # 当“提交反馈”按钮被点击时,执行 record_feedback
    btn_feedback.click(
        fn=record_feedback,
        inputs=[
            user_query_box,         # user_query
            deepseek_answer_box,    # final_answer (此处是 deepseek 生成的答案)
            feedback_choice,        # feedback_choice
            improved_answer_box,    # improved_answer
            store_name_box,         # store_name
            hc_links_box            # links_str
        ],
        outputs=[feedback_result]
    )

demo.launch(
    server_name="0.0.0.0",
    server_port=7860,
    share=True
)