v1.1.0 管理后台+对话增强:/admin全数据CRUD与站点配置;快捷语句点击自动提交;Markdown回答渲染;参考资讯折叠链接;实体识别标记与快速查看卡片(entity_linker)

This commit is contained in:
2026-08-17 13:19:36 +08:00
parent e11677809e
commit fcf5d3fee4
13 changed files with 1299 additions and 43 deletions
+71 -22
View File
@@ -57,9 +57,14 @@ def _grounding_context(user_msg):
def chat_once(user_msg, history=None):
"""单轮对话。返回 (reply, sources, used_tools)
sources: 供前端展示的信息来源卡片
"""单轮对话。返回 (reply, sources, used_tools, news_refs)
sources : 供前端展示的信息来源卡片
news_refs : 本次回答用到的新闻/百科资讯列表(前端折叠展示为链接)
"""
return _chat_impl(user_msg, history)
def _chat_impl(user_msg, history=None):
history = history or []
messages = [{"role": "system", "content": SYSTEM_PROMPT}]
for h in history[-10:]:
@@ -68,7 +73,7 @@ def chat_once(user_msg, history=None):
messages.append({"role": "assistant", "content": h["assistant"]})
messages.append({"role": "user", "content": user_msg})
used_tools, sources = [], []
used_tools, sources, news_refs = [], [], []
# ---- 第 1 轮:带工具
try:
@@ -79,9 +84,9 @@ def chat_once(user_msg, history=None):
msgs = messages + ([{"role": "system", "content": f"以下是数据库检索到的可能相关信息(未直接命中时勿强行引用):\n{ctx}"}] if ctx else [])
try:
resp2 = llm.chat(msgs)
return llm.parse_content(resp2), _mk_sources(ctx), [t for t in ("grounding",) if ctx]
return llm.parse_content(resp2), _mk_sources(ctx), [t for t in ("grounding",) if ctx], []
except Exception as e2:
return (f"抱歉,大模型服务暂时不可用({e2})。你可以稍后再试,或直接浏览下方数据页面。", [], [])
return (f"抱歉,大模型服务暂时不可用({e2})。你可以稍后再试,或直接浏览下方数据页面。", [], [], [])
# ---- 工具执行(单轮)+ 实体覆盖补全 → 最终无工具作答
all_executed = []
@@ -89,7 +94,7 @@ def chat_once(user_msg, history=None):
if not calls:
calls = _parse_text_tool_calls(llm.parse_content(resp), user_msg)
if not calls:
return llm.parse_content(resp) or "(模型未返回内容)", [], []
return llm.parse_content(resp) or "(模型未返回内容)", [], [], []
executed = []
for c in calls:
@@ -102,6 +107,14 @@ def chat_once(user_msg, history=None):
used_tools.append(c["name"])
if result.get("results"):
sources.append({"tool": c["name"], "items": result["results"][:3]})
if c["name"] == "search_news":
for it in result["results"][:5]:
if it.get("id"):
news_refs.append({"id": it["id"], "title": it.get("title", ""),
"source": it.get("source", ""),
"publish_time": it.get("publish_time", "")})
elif c["name"] == "get_game_detail" and isinstance(result, dict) and result.get("id"):
sources.append({"tool": "get_game_detail", "items": [result]})
all_executed += executed
# 覆盖补全:用户问题里提到的其他实体(多球员/多球队对比)自动补查,避免模型漏调
@@ -126,6 +139,14 @@ def chat_once(user_msg, history=None):
sources.append({"tool": "search_players" if key == "name" else "search_teams", "items": r["results"][:3]})
break
# 新闻引用去重(按 id
seen_nid, news_refs_u = set(), []
for n in news_refs:
if n["id"] not in seen_nid:
seen_nid.add(n["id"])
news_refs_u.append(n)
news_refs = news_refs_u
messages.append({"role": "assistant", "content": None,
"reasoning_content": llm.extract_reasoning(resp),
"tool_calls": [
@@ -139,13 +160,13 @@ def chat_once(user_msg, history=None):
resp = llm.chat(messages) # 最终轮:不带工具,让模型基于数据作答
except Exception as e:
log.warning("LLM 最终轮失败(%s),返回工具结果摘要", e)
return _finalize(all_executed, user_msg, messages, sources, used_tools), sources, used_tools
return _finalize(all_executed, user_msg, messages, sources, used_tools), sources, used_tools, news_refs
reply = llm.parse_content(resp) or ""
if not reply or "<tool_calls>" in reply or "search_" in reply:
# 模型又输出工具调用文本 → 注入上下文再答一次
return _finalize(all_executed, user_msg, messages, sources, used_tools), sources, used_tools
return reply, sources, used_tools
return _finalize(all_executed, user_msg, messages, sources, used_tools), sources, used_tools, news_refs
return reply, sources, used_tools, news_refs
def _finalize(executed, user_msg, messages, sources, used_tools):
@@ -210,16 +231,44 @@ def _mk_sources(ctx):
def suggest_questions():
"""快捷问题(前端展示用)"""
return [
"最近一场比赛结果",
"湖人本赛季战绩怎么样",
"库里本赛季场均数据",
"2026年总决赛谁赢了",
"SGA拿了什么荣誉",
"NBA工资帽是什么",
"介绍一下波波维奇",
"今天有什么新闻",
"西部排名",
"雷霆和凯尔特人总决赛G6数据",
]
"""快捷问题(前端展示用,从站点配置读取,管理后台可编辑"""
try:
from admin import get_config
raw = get_config().get("suggestions") or ""
arr = json.loads(raw)
if isinstance(arr, list) and arr:
return [str(x).strip() for x in arr if str(x).strip()]
except Exception:
pass
return DEFAULT_SUGGESTIONS
DEFAULT_SUGGESTIONS = [
"最近一场比赛结果",
"湖人本赛季战绩怎么样",
"库里本赛季场均数据",
"2026年总决赛谁赢了",
"SGA拿了什么荣誉",
"NBA工资帽是什么",
"介绍一下波波维奇",
"今天有什么新闻",
"西部排名",
"雷霆和凯尔特人总决赛G6数据",
]
def boot_info():
"""对话界面启动信息:开场白 + 快捷问题(均可后台配置)"""
try:
from admin import get_config
cfg = get_config()
except Exception:
cfg = {}
return {
"site_name": cfg.get("site_name", "NBA球迷大全"),
"site_subtitle": cfg.get("site_subtitle", "比赛 · 球员 · 球队 · 资讯 · 人物 · 百科"),
"welcome_text": cfg.get("welcome_text", "你好,我是**NBA球迷大全**助手!可以问我任何关于比赛、球员、球队、新闻、人物的问题,我会基于数据库给你准确答案~"),
"welcome_hint": cfg.get("welcome_hint", "试试:"),
"suggestions": suggest_questions(),
"footer_text": cfg.get("footer_text", "NBA球迷大全 · 数据为模拟演示数据(2025-26 赛季)"),
}