Spaces:
Paused
Paused
Update app.py
Browse files
app.py
CHANGED
|
@@ -489,7 +489,7 @@ def list_models():
|
|
| 489 |
app.logger.info("[list_models] 用户请求 /v1/models")
|
| 490 |
models_list = [
|
| 491 |
{
|
| 492 |
-
"id": "
|
| 493 |
"object": "model",
|
| 494 |
"created": 1677610602,
|
| 495 |
"owned_by": "deepseek",
|
|
@@ -503,14 +503,14 @@ def list_models():
|
|
| 503 |
"permission": []
|
| 504 |
},
|
| 505 |
{
|
| 506 |
-
"id": "
|
| 507 |
"object": "model",
|
| 508 |
"created": 1677610602,
|
| 509 |
"owned_by": "deepseek",
|
| 510 |
"permission": []
|
| 511 |
},
|
| 512 |
{
|
| 513 |
-
"id": "deepseek-
|
| 514 |
"object": "model",
|
| 515 |
"created": 1677610602,
|
| 516 |
"owned_by": "deepseek",
|
|
@@ -525,8 +525,8 @@ def list_models():
|
|
| 525 |
# ----------------------------------------------------------------------
|
| 526 |
def messages_prepare(messages: list) -> str:
|
| 527 |
"""处理消息列表,合并连续相同角色的消息,并添加角色标签:
|
| 528 |
-
- 对于 assistant 消息,加上 <|Assistant|> 前缀及
|
| 529 |
-
- 对于 user/system 消息(除第一条外)加上
|
| 530 |
- 如果消息 content 为数组,则提取其中 type 为 "text" 的部分;
|
| 531 |
- 最后移除 markdown 图片格式的内容。
|
| 532 |
"""
|
|
@@ -555,10 +555,10 @@ def messages_prepare(messages: list) -> str:
|
|
| 555 |
role = block["role"]
|
| 556 |
text = block["text"]
|
| 557 |
if role == "assistant":
|
| 558 |
-
parts.append(f"<|Assistant|>{text}
|
| 559 |
elif role in ("user", "system"):
|
| 560 |
if idx > 0:
|
| 561 |
-
parts.append(f"
|
| 562 |
else:
|
| 563 |
parts.append(text)
|
| 564 |
else:
|
|
@@ -601,14 +601,23 @@ def chat_completions():
|
|
| 601 |
if not model or not messages:
|
| 602 |
return jsonify({"error": "Request must include 'model' and 'messages'."}), 400
|
| 603 |
|
| 604 |
-
#
|
| 605 |
model_lower = model.lower()
|
| 606 |
if model_lower in ["deepseek-v3", "deepseek-chat"]:
|
| 607 |
thinking_enabled = False
|
|
|
|
| 608 |
elif model_lower in ["deepseek-r1", "deepseek-reasoner"]:
|
| 609 |
thinking_enabled = True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 610 |
else:
|
| 611 |
-
return
|
|
|
|
| 612 |
|
| 613 |
# 使用 messages_prepare 函数构造最终 prompt
|
| 614 |
final_prompt = messages_prepare(messages)
|
|
@@ -633,7 +642,7 @@ def chat_completions():
|
|
| 633 |
"prompt": final_prompt,
|
| 634 |
"ref_file_ids": [],
|
| 635 |
"thinking_enabled": thinking_enabled,
|
| 636 |
-
"search_enabled":
|
| 637 |
}
|
| 638 |
app.logger.debug(f"[chat_completions] -> {DEEPSEEK_COMPLETION_URL}, payload={payload}")
|
| 639 |
|
|
@@ -657,6 +666,7 @@ def chat_completions():
|
|
| 657 |
final_text = ""
|
| 658 |
final_thinking = ""
|
| 659 |
first_chunk_sent = False
|
|
|
|
| 660 |
for raw_line in deepseek_resp.iter_lines(chunk_size=512):
|
| 661 |
try:
|
| 662 |
line = raw_line.decode("utf-8")
|
|
@@ -691,6 +701,12 @@ def chat_completions():
|
|
| 691 |
try:
|
| 692 |
chunk = json.loads(data_str)
|
| 693 |
app.logger.debug(f"[sse_stream] 解析到 chunk: {chunk}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 694 |
except Exception as e:
|
| 695 |
app.logger.warning(f"[sse_stream] 无法解析: {data_str}, 错误: {e}")
|
| 696 |
continue
|
|
@@ -699,6 +715,8 @@ def chat_completions():
|
|
| 699 |
delta = choice.get("delta", {})
|
| 700 |
ctype = delta.get("type")
|
| 701 |
ctext = delta.get("content", "")
|
|
|
|
|
|
|
| 702 |
if ctype == "thinking":
|
| 703 |
if thinking_enabled:
|
| 704 |
final_thinking += ctext
|
|
@@ -735,6 +753,7 @@ def chat_completions():
|
|
| 735 |
# 非流式响应处理
|
| 736 |
think_list = []
|
| 737 |
text_list = []
|
|
|
|
| 738 |
try:
|
| 739 |
for raw_line in deepseek_resp.iter_lines(chunk_size=512):
|
| 740 |
try:
|
|
@@ -751,16 +770,25 @@ def chat_completions():
|
|
| 751 |
try:
|
| 752 |
chunk = json.loads(data_str)
|
| 753 |
app.logger.debug(f"[chat_completions] 非流式 chunk: {chunk}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 754 |
except Exception as e:
|
| 755 |
app.logger.warning(f"[chat_completions] 无法解析: {data_str}, 错误: {e}")
|
| 756 |
continue
|
| 757 |
for choice in chunk.get("choices", []):
|
| 758 |
delta = choice.get("delta", {})
|
| 759 |
ctype = delta.get("type")
|
|
|
|
|
|
|
|
|
|
| 760 |
if ctype == "thinking" and thinking_enabled:
|
| 761 |
-
think_list.append(
|
| 762 |
elif ctype == "text":
|
| 763 |
-
text_list.append(
|
| 764 |
finally:
|
| 765 |
deepseek_resp.close()
|
| 766 |
final_reasoning = "".join(think_list)
|
|
|
|
| 489 |
app.logger.info("[list_models] 用户请求 /v1/models")
|
| 490 |
models_list = [
|
| 491 |
{
|
| 492 |
+
"id": "deepseek-chat",
|
| 493 |
"object": "model",
|
| 494 |
"created": 1677610602,
|
| 495 |
"owned_by": "deepseek",
|
|
|
|
| 503 |
"permission": []
|
| 504 |
},
|
| 505 |
{
|
| 506 |
+
"id": "deepseek-chat-search",
|
| 507 |
"object": "model",
|
| 508 |
"created": 1677610602,
|
| 509 |
"owned_by": "deepseek",
|
| 510 |
"permission": []
|
| 511 |
},
|
| 512 |
{
|
| 513 |
+
"id": "deepseek-reasoner-search",
|
| 514 |
"object": "model",
|
| 515 |
"created": 1677610602,
|
| 516 |
"owned_by": "deepseek",
|
|
|
|
| 525 |
# ----------------------------------------------------------------------
|
| 526 |
def messages_prepare(messages: list) -> str:
|
| 527 |
"""处理消息列表,合并连续相同角色的消息,并添加角色标签:
|
| 528 |
+
- 对于 assistant 消息,加上 <|Assistant|> 前缀及 结束标签;
|
| 529 |
+
- 对于 user/system 消息(除第一条外)加上 结束标签;
|
| 530 |
- 如果消息 content 为数组,则提取其中 type 为 "text" 的部分;
|
| 531 |
- 最后移除 markdown 图片格式的内容。
|
| 532 |
"""
|
|
|
|
| 555 |
role = block["role"]
|
| 556 |
text = block["text"]
|
| 557 |
if role == "assistant":
|
| 558 |
+
parts.append(f"<|Assistant|>{text}")
|
| 559 |
elif role in ("user", "system"):
|
| 560 |
if idx > 0:
|
| 561 |
+
parts.append(f"结束标签")
|
| 562 |
else:
|
| 563 |
parts.append(text)
|
| 564 |
else:
|
|
|
|
| 601 |
if not model or not messages:
|
| 602 |
return jsonify({"error": "Request must include 'model' and 'messages'."}), 400
|
| 603 |
|
| 604 |
+
# 判断是否启用"思考"功能(这里根据模型名称判断)
|
| 605 |
model_lower = model.lower()
|
| 606 |
if model_lower in ["deepseek-v3", "deepseek-chat"]:
|
| 607 |
thinking_enabled = False
|
| 608 |
+
search_enabled = False
|
| 609 |
elif model_lower in ["deepseek-r1", "deepseek-reasoner"]:
|
| 610 |
thinking_enabled = True
|
| 611 |
+
search_enabled = False
|
| 612 |
+
elif model_lower in ["deepseek-v3-search", "deepseek-chat-search"]:
|
| 613 |
+
thinking_enabled = False
|
| 614 |
+
search_enabled = True
|
| 615 |
+
elif model_lower in ["deepseek-r1-search", "deepseek-reasoner-search"]:
|
| 616 |
+
thinking_enabled = True
|
| 617 |
+
search_enabled = True
|
| 618 |
else:
|
| 619 |
+
return Response(json.dumps({"error": f"Model '{model}' is not available."}),
|
| 620 |
+
status=503, mimetype="application/json")
|
| 621 |
|
| 622 |
# 使用 messages_prepare 函数构造最终 prompt
|
| 623 |
final_prompt = messages_prepare(messages)
|
|
|
|
| 642 |
"prompt": final_prompt,
|
| 643 |
"ref_file_ids": [],
|
| 644 |
"thinking_enabled": thinking_enabled,
|
| 645 |
+
"search_enabled": search_enabled
|
| 646 |
}
|
| 647 |
app.logger.debug(f"[chat_completions] -> {DEEPSEEK_COMPLETION_URL}, payload={payload}")
|
| 648 |
|
|
|
|
| 666 |
final_text = ""
|
| 667 |
final_thinking = ""
|
| 668 |
first_chunk_sent = False
|
| 669 |
+
citation_map = {} # 用于存储引用链接的字典
|
| 670 |
for raw_line in deepseek_resp.iter_lines(chunk_size=512):
|
| 671 |
try:
|
| 672 |
line = raw_line.decode("utf-8")
|
|
|
|
| 701 |
try:
|
| 702 |
chunk = json.loads(data_str)
|
| 703 |
app.logger.debug(f"[sse_stream] 解析到 chunk: {chunk}")
|
| 704 |
+
# 处理搜索索引数据
|
| 705 |
+
if chunk.get("choices", [{}])[0].get("delta", {}).get("type") == "search_index":
|
| 706 |
+
search_indexes = chunk["choices"][0]["delta"].get("search_indexes", [])
|
| 707 |
+
for idx in search_indexes:
|
| 708 |
+
citation_map[str(idx.get("cite_index"))] = idx.get("url", "")
|
| 709 |
+
continue
|
| 710 |
except Exception as e:
|
| 711 |
app.logger.warning(f"[sse_stream] 无法解析: {data_str}, 错误: {e}")
|
| 712 |
continue
|
|
|
|
| 715 |
delta = choice.get("delta", {})
|
| 716 |
ctype = delta.get("type")
|
| 717 |
ctext = delta.get("content", "")
|
| 718 |
+
if search_enabled and ctext.startswith("[citation:"):
|
| 719 |
+
ctext = ""
|
| 720 |
if ctype == "thinking":
|
| 721 |
if thinking_enabled:
|
| 722 |
final_thinking += ctext
|
|
|
|
| 753 |
# 非流式响应处理
|
| 754 |
think_list = []
|
| 755 |
text_list = []
|
| 756 |
+
citation_map = {} # 用于存储引用链接的字典
|
| 757 |
try:
|
| 758 |
for raw_line in deepseek_resp.iter_lines(chunk_size=512):
|
| 759 |
try:
|
|
|
|
| 770 |
try:
|
| 771 |
chunk = json.loads(data_str)
|
| 772 |
app.logger.debug(f"[chat_completions] 非流式 chunk: {chunk}")
|
| 773 |
+
# 处理搜索索引数据
|
| 774 |
+
if chunk.get("choices", [{}])[0].get("delta", {}).get("type") == "search_index":
|
| 775 |
+
search_indexes = chunk["choices"][0]["delta"].get("search_indexes", [])
|
| 776 |
+
for idx in search_indexes:
|
| 777 |
+
citation_map[str(idx.get("cite_index"))] = idx.get("url", "")
|
| 778 |
+
continue
|
| 779 |
except Exception as e:
|
| 780 |
app.logger.warning(f"[chat_completions] 无法解析: {data_str}, 错误: {e}")
|
| 781 |
continue
|
| 782 |
for choice in chunk.get("choices", []):
|
| 783 |
delta = choice.get("delta", {})
|
| 784 |
ctype = delta.get("type")
|
| 785 |
+
ctext = delta.get("content", "")
|
| 786 |
+
if search_enabled and ctext.startswith("[citation:"):
|
| 787 |
+
ctext = ""
|
| 788 |
if ctype == "thinking" and thinking_enabled:
|
| 789 |
+
think_list.append(ctext)
|
| 790 |
elif ctype == "text":
|
| 791 |
+
text_list.append(ctext)
|
| 792 |
finally:
|
| 793 |
deepseek_resp.close()
|
| 794 |
final_reasoning = "".join(think_list)
|