v1.1.0 管理后台+对话增强:/admin全数据CRUD与站点配置;快捷语句点击自动提交;Markdown回答渲染;参考资讯折叠链接;实体识别标记与快速查看卡片(entity_linker)
This commit is contained in:
@@ -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 赛季)"),
|
||||
}
|
||||
Reference in New Issue
Block a user