Files
nba-fan-hub/tools.py
T

518 lines
26 KiB
Python
Raw Normal View History

# -*- coding: utf-8 -*-
"""
结构化查询工具层:供大模型函数调用(tools)与前端 REST API 共用。
所有函数返回 JSON 友好的 dict/list,并带 _source 来源标记。
新增数据域(如 CBA、足球)时:SQL 按 league/sport 过滤即可复用全部函数。
"""
import re
from datetime import datetime, timedelta
from db import query, query_one, fuzzy
import vector_store
MAX_SHOW = 6 # 工具默认返回条数上限
# 绰号 → 库内正式名(查询时自动展开)
ALIASES = {
"字母哥": "扬尼斯·阿德托昆博", "希腊怪兽": "扬尼斯·阿德托昆博",
"老詹": "勒布朗·詹姆斯", "詹皇": "勒布朗·詹姆斯", "皇帝": "勒布朗·詹姆斯",
"浓眉": "安东尼·戴维斯", "SGA": "谢伊·吉尔杰斯-亚历山大",
"华子": "安东尼·爱德华兹", "文班": "维克托·文班亚马", "约老师": "尼古拉·约基奇",
"大胡子": "詹姆斯·哈登", "死神": "凯文·杜兰特", "大帝": "乔尔·恩比德",
"追梦": "德雷蒙德·格林", "獭兔": "杰森·塔图姆", "东子": "卢卡·东契奇",
"泡椒": "保罗·乔治", "小卡": "科怀·伦纳德", "卡皇": "亚历克斯·卡鲁索",
"切特": "切特·霍姆格伦", "杰威": "杰伦·威廉姆斯",
}
def expand_aliases(q):
"""把绰号替换为正式名(长匹配优先)"""
for nick, real in sorted(ALIASES.items(), key=lambda x: -len(x[0])):
if nick in q:
q = q.replace(nick, real)
return q
def _split_terms(q):
"""把'约基奇和字母哥'/'湖人、勇士'拆成多个关键词(绰号已展开,仅去标点)"""
q = expand_aliases(q)
terms = [t.strip() for t in re.split(r"[和与跟、,;vs VS 及\s]", q) if t.strip()]
terms = [re.sub(r"^[\s\-—:]+|[\s\-—:]+$", "", t) for t in terms]
seen, out = set(), []
for t in terms:
if t and t not in seen:
seen.add(t)
out.append(t)
return out
def _match_by_truncation(base_sql, term, args_factory, max_drop=10):
"""渐进截断匹配:整词无结果时逐个丢弃尾部字符重试(处理'字母哥谁得分多'类问句尾巴)
base_sql 需含 ? 占位;args_factory(like) 生成查询参数"""
t = term
for _ in range(max_drop + 1):
if len(t) < 2:
break
rows = query(base_sql, args_factory(f"%{fuzzy(t)}%"))
if rows:
return rows
t = t[:-1]
return []
def _fmt_team(t):
return {"id": t["id"], "name": t["name"], "name_en": t["name_en"], "code": t["code"],
"city": t["city"], "arena": t["arena"], "founded": t["founded"],
"champion_count": t["champion_count"], "head_coach": t["head_coach"], "intro": t["intro"]}
def _fmt_player(p):
return {"id": p["id"], "name": p["name"], "name_en": p["name_en"], "team": p.get("team_name"),
"team_id": p.get("team_id"),
"position": p["position"], "number": p["number"], "height_cm": p["height_cm"],
"weight_kg": p["weight_kg"], "country": p["country"], "draft": f"{p['draft_year']}年 第{p['draft_pick']}顺位" if p["draft_year"] else "落选秀",
"salary_m": p["salary_m"], "season": {"pts": p["season_pts"], "reb": p["season_reb"],
"ast": p["season_ast"], "stl": p["season_stl"], "blk": p["season_blk"], "min": p["season_min"]},
"career": {"pts": p["career_pts"], "reb": p["career_reb"], "ast": p["career_ast"], "games": p["career_games"]},
"awards": p["awards"], "bio": p["bio"]}
def _fmt_game(g, with_team=True):
d = {"id": g["id"], "round_name": g["round_name"], "game_time": g["game_time"],
"status": g["status"], "venue": g["venue"], "broadcast": g["broadcast"],
"home_score": g["home_score"], "away_score": g["away_score"]}
if with_team:
d["home_team"] = g["home_name"] or g["home_code"]
d["away_team"] = g["away_name"] or g["away_code"]
return d
_STAT_INTENT = re.compile(r"技术统计|统计|数据|谁得分|得分最高|表现|box|scorer|G\d|第.场")
def _attach_top_scorers(game_rows, max_games=3):
"""为比赛附加双方得分前三球员(供技术统计类问题)"""
for g in game_rows[:max_games]:
gid = g["id"]
rows = query("""SELECT gs.points, p.name AS player_name, t.name AS team_name
FROM game_player_stats gs
JOIN players p ON gs.player_id=p.id JOIN teams t ON gs.team_id=t.id
WHERE gs.game_id=? ORDER BY gs.points DESC LIMIT 6""", (gid,))
if rows:
g["top_scorers"] = [{"player": r["player_name"], "team": r["team_name"], "points": r["points"]}
for r in rows]
# ================================================================== 球队
def search_teams(query_text, limit=MAX_SHOW):
q = expand_aliases((query_text or "").strip())
if not q:
rows = query("SELECT * FROM teams ORDER BY name LIMIT ?", (limit,))
else:
seen, out = {}, []
sql = """SELECT * FROM teams WHERE name LIKE ? ESCAPE '\\' OR name_en LIKE ? ESCAPE '\\'
OR code LIKE ? ESCAPE '\\' OR city LIKE ? ESCAPE '\\' ORDER BY name LIMIT ?"""
for term in _split_terms(q)[:4]:
rows = _match_by_truncation(sql, term, lambda like: (like, like, like, like, limit))
for r in rows:
if r["id"] not in seen:
seen[r["id"]] = r
out.append(r)
rows = out[:limit]
return {"_source": "teams", "results": [_fmt_team(r) for r in rows]}
def get_team(team_id):
t = query_one("SELECT * FROM teams WHERE id=?", (team_id,))
if not t:
return None
return _fmt_team(t)
# ================================================================== 球员
def search_players(query_text, limit=MAX_SHOW):
q = expand_aliases((query_text or "").strip())
if not q:
rows = query("""SELECT p.*, t.name AS team_name FROM players p LEFT JOIN teams t ON p.team_id=t.id
ORDER BY p.season_pts DESC LIMIT ?""", (limit,))
return {"_source": "players", "results": [_fmt_player(r) for r in rows]}
seen, out = {}, []
sql = """SELECT p.*, t.name AS team_name FROM players p
LEFT JOIN teams t ON p.team_id=t.id
WHERE p.name LIKE ? ESCAPE '\\' OR p.name_en LIKE ? ESCAPE '\\'
OR t.name LIKE ? ESCAPE '\\'
ORDER BY p.season_pts DESC LIMIT ?"""
for term in _split_terms(q)[:4]:
rows = _match_by_truncation(sql, term, lambda like: (like, like, like, limit))
for r in rows:
if r["id"] not in seen:
seen[r["id"]] = r
out.append(r)
return {"_source": "players", "results": [_fmt_player(r) for r in out[:limit]]}
def get_player(player_id):
p = query_one("""SELECT p.*, t.name AS team_name FROM players p LEFT JOIN teams t ON p.team_id=t.id
WHERE p.id=?""", (player_id,))
return _fmt_player(p) if p else None
# ================================================================== 球员列表(前台分页版)
def list_players(q="", position="", page=1, size=24):
"""前台球员列表:关键词 + 位置筛选 + 分页(区别于 search_players 工具版)"""
q = expand_aliases((q or "").strip())
conds, args = [], []
if q:
like = f"%{fuzzy(q)}%"
conds.append("(p.name LIKE ? ESCAPE '\\' OR p.name_en LIKE ? ESCAPE '\\' OR t.name LIKE ? ESCAPE '\\')")
args += [like, like, like]
if position:
conds.append("p.position=?")
args.append(position)
where = ("WHERE " + " AND ".join(conds)) if conds else ""
total = query_one(f"SELECT COUNT(*) AS c FROM players p LEFT JOIN teams t ON p.team_id=t.id {where}", args)["c"]
rows = query(f"""SELECT p.*, t.name AS team_name FROM players p LEFT JOIN teams t ON p.team_id=t.id
{where} ORDER BY p.season_pts DESC LIMIT ? OFFSET ?""",
args + [size, (page - 1) * size])
return {"results": [_fmt_player(r) for r in rows], "total": total, "page": page, "size": size}
# ================================================================== 比赛
_TEAM_PAT = None
def _match_team_ids(text):
"""从文本中找出命中的球队名(支持中文/英文/缩写)"""
global _TEAM_PAT
teams = query("SELECT id, name, name_en, code FROM teams")
hits = []
for t in teams:
names = [t["name"], t["name_en"], t["code"]]
for n in names:
if n and len(n) >= 2 and n.lower() in (text or "").lower():
hits.append(t["id"])
break
return hits
def search_games(query_text, limit=10):
q = (query_text or "").strip()
now = datetime.now()
sql = """SELECT g.*, ht.name AS home_name, ht.code AS home_code,
at.name AS away_name, at.code AS away_code
FROM games g JOIN teams ht ON g.home_team_id=ht.id JOIN teams at ON g.away_team_id=at.id"""
conds, args = [], []
tids = _match_team_ids(q)
if tids:
marks = ",".join("?" for _ in tids)
conds.append(f"(g.home_team_id IN ({marks}) OR g.away_team_id IN ({marks}))")
args += tids * 2
if q and ("已结束" in q or "结束" in q or "比分" in q or "结果" in q or "谁赢" in q):
conds.append("g.status='finished'")
if q and ("未开始" in q or "即将" in q or "赛程" in q or "预告" in q):
conds.append("g.status='scheduled'")
if q and "总决赛" in q:
conds.append("g.round_name='总决赛'")
if q and "季后赛" in q:
conds.append("g.round_name LIKE '季后赛%' OR g.round_name IN ('总决赛','东部决赛','西部决赛')")
# 时间语义
order = "g.game_time DESC"
if "总决赛" in q or "系列赛" in q:
order = "g.game_time ASC" # 系列赛按时间正序,便于模型按场次引用
if re.search(r"最近|最新|上一场|上一轮", q):
conds.append("g.status='finished'")
elif re.search(r"下一场|即将|赛程|预告|未来", q):
conds.append("g.status='scheduled'")
order = "g.game_time ASC"
elif re.search(r"明天|明日", q):
conds.append("date(g.game_time)=date('now','localtime','+1 day')")
elif re.search(r"今天|今日", q):
conds.append("date(g.game_time)=date('now','localtime')")
where = ("WHERE " + " AND ".join(conds)) if conds else ""
sql += f" {where} ORDER BY {order} LIMIT ?"
args.append(limit)
rows = query(sql, args)
results = [_fmt_game(r) for r in rows]
if _STAT_INTENT.search(q):
# 若点名了具体场次(G6/第六场/日期),只保留该场并附完整得分榜
specific = re.search(r"G(\d)|第([一二三四五六七八九十])场|(\d{1,2})月(\d{1,2})日|(\d{4}-\d{2}-\d{2})", q)
if specific:
key = (specific.group(1) or "")
kept = []
for g in results:
gno = g["game_time"][8:10]
if key:
try:
gno = str(int(g["game_time"][8:10]))
except Exception:
gno = ""
if gno == key:
kept.append(g)
elif specific.group(5):
if g["game_time"][:10] == specific.group(5):
kept.append(g)
elif specific.group(3):
if g["game_time"][5:7] == specific.group(3) and g["game_time"][8:10] == specific.group(4):
kept.append(g)
if kept:
results = kept
_attach_top_scorers(results, max_games=len(results))
return {"_source": "games", "results": results}
def get_game_detail(game_id):
g = query_one("""SELECT g.*, ht.name AS home_name, ht.code AS home_code,
at.name AS away_name, at.code AS away_code
FROM games g JOIN teams ht ON g.home_team_id=ht.id JOIN teams at ON g.away_team_id=at.id
WHERE g.id=?""", (game_id,))
if not g:
return None
detail = _fmt_game(g)
stats = query("""SELECT gs.*, p.name AS player_name, p.position, t.name AS team_name
FROM game_player_stats gs
JOIN players p ON gs.player_id=p.id JOIN teams t ON gs.team_id=t.id
WHERE gs.game_id=? ORDER BY gs.team_id, gs.points DESC""", (game_id,))
detail["box_score"] = stats
return detail
# ================================================================== 比赛列表(前台分页版)
def list_games(q="", status="", page=1, size=12):
"""前台比赛列表:关键词 + 状态筛选 + 分页(数据量小,全量匹配后切片)"""
r = search_games(q, limit=500)
results = r.get("results", [])
if status:
results = [g for g in results if g["status"] == status]
total = len(results)
start = (page - 1) * size
return {"results": results[start:start + size], "total": total, "page": page, "size": size}
# ================================================================== 排名
def search_standings(query_text, limit=20, season=None):
q = (query_text or "").strip()
tids = _match_team_ids(q)
sql = """SELECT s.*, t.name AS team_name, t.code
FROM standings s JOIN teams t ON s.team_id=t.id"""
conds, args = [], []
if season:
conds.append("s.season=?")
args.append(season)
if tids:
marks = ",".join("?" for _ in tids)
conds.append(f"s.team_id IN ({marks})")
args += tids
if "东部" in q:
conds.append("s.conference='东部'")
if "西部" in q:
conds.append("s.conference='西部'")
where = ("WHERE " + " AND ".join(conds)) if conds else ""
rows = query(f"{sql} {where} ORDER BY s.conference DESC, s.rank ASC LIMIT ?", args + [limit])
return {"_source": "standings", "results": [
{"team": r["team_name"], "team_id": r["team_id"], "code": r["code"], "conference": r["conference"], "rank": r["rank"],
"wins": r["wins"], "losses": r["losses"], "win_pct": round(r["win_pct"] * 100, 1),
"season": r["season"]} for r in rows]}
# ================================================================== 比赛结果(按赛季)
def team_games(team_id, season=None, limit=15):
"""球队参与的比赛(详情页用),可按赛季过滤"""
conds, args = ["(g.home_team_id=? OR g.away_team_id=?)"], [team_id, team_id]
if season:
conds.append("g.season=?")
args.append(season)
rows = query(f"""SELECT g.*, ht.name AS home_name, ht.code AS home_code,
at.name AS away_name, at.code AS away_code
FROM games g JOIN teams ht ON g.home_team_id=ht.id JOIN teams at ON g.away_team_id=at.id
WHERE {" AND ".join(conds)} ORDER BY g.game_time DESC LIMIT ?""",
args + [limit])
return [_fmt_game(r) for r in rows]
# ================================================================== 球员比赛记录(技术统计)
def player_game_logs(player_id, limit=10):
"""球员参与的比赛与单场数据(详情页用)"""
rows = query("""SELECT gs.*, g.round_name, g.game_time, g.home_score, g.away_score,
ht.name AS home_name, at.name AS away_name
FROM game_player_stats gs
JOIN games g ON gs.game_id=g.id
JOIN teams ht ON g.home_team_id=ht.id JOIN teams at ON g.away_team_id=at.id
WHERE gs.player_id=? ORDER BY g.game_time DESC LIMIT ?""", (player_id, limit))
out = []
for r in rows:
out.append({"game_id": r["game_id"], "round_name": r["round_name"], "game_time": r["game_time"],
"home_team": r["home_name"], "away_team": r["away_name"],
"home_score": r["home_score"], "away_score": r["away_score"],
"pts": r["points"], "reb": r["rebounds"], "ast": r["assists"],
"stl": r["steals"], "blk": r["blocks"], "min": r["minutes"]})
return out
# ================================================================== 新闻列表(前台分页版)
def list_news(q="", kind="", tag="", page=1, size=10):
"""前台新闻列表:关键词 + 类型(kind) + 话题标签(tag) + 分页"""
conds, args = [], []
if q:
like = f"%{fuzzy(q)}%"
conds.append("(title LIKE ? ESCAPE '\\' OR content LIKE ? ESCAPE '\\' OR tags LIKE ? ESCAPE '\\')")
args += [like, like, like]
if kind:
conds.append("kind=?")
args.append(kind)
if tag:
conds.append("tags LIKE ? ESCAPE '\\'")
args.append(f"%{fuzzy(tag)}%")
where = ("WHERE " + " AND ".join(conds)) if conds else ""
total = query_one(f"SELECT COUNT(*) AS c FROM news {where}", args)["c"]
rows = query(f"""SELECT id,title,author,source,publish_time,tags,kind,substr(content,1,160) AS summary
FROM news {where} ORDER BY publish_time DESC, id DESC LIMIT ? OFFSET ?""",
args + [size, (page - 1) * size])
return {"results": rows, "total": total, "page": page, "size": size}
# ================================================================== 新闻(SQL 关键词 + 向量语义 + 可选 rerank 融合)
def search_news(query_text, limit=5):
q = (query_text or "").strip()
seen, out = set(), []
# ================================================================== 新闻(SQL 关键词 + 向量语义 + 可选 rerank 融合)
def search_news(query_text, limit=5):
q = (query_text or "").strip()
seen, out = set(), []
# 1) SQL 关键词命中(标题+正文+标签)
if q:
like = f"%{fuzzy(q)}%"
rows = query("""SELECT * FROM news WHERE kind='news' AND (title LIKE ? ESCAPE '\\' OR content LIKE ? ESCAPE '\\'
OR tags LIKE ? ESCAPE '\\') ORDER BY publish_time DESC LIMIT ?""",
(like, like, like, limit))
for r in rows:
seen.add(r["id"])
out.append({"id": r["id"], "title": r["title"], "content": r["content"][:220],
"publish_time": r["publish_time"], "source": r["source"], "tags": r["tags"]})
# 2) 向量语义补充
try:
hits = vector_store.query_vectors(q, n_results=limit * 3)
cands = []
for h in hits:
nid = h["metadata"].get("news_id")
if nid in seen or h["metadata"].get("kind") != "news":
continue
n = query_one("SELECT * FROM news WHERE id=?", (nid,))
if n:
cands.append({"id": nid, "title": n["title"], "content": n["content"][:220],
"publish_time": n["publish_time"], "source": n["source"],
"tags": n["tags"], "_score": h["distance"]})
cands.sort(key=lambda x: x["_score"])
for c in cands[: max(0, limit - len(out))]:
out.append({k: v for k, v in c.items() if k != "_score"})
except Exception as e:
pass
return {"_source": "news", "results": out}
# ================================================================== 人物
def search_persons(query_text, limit=MAX_SHOW):
q = (query_text or "").strip()
if not q:
rows = query("""SELECT p.*, t.name AS team_name FROM persons p LEFT JOIN teams t ON p.team_id=t.id
ORDER BY p.id LIMIT ?""", (limit,))
else:
like = f"%{fuzzy(q)}%"
rows = query("""SELECT p.*, t.name AS team_name FROM persons p LEFT JOIN teams t ON p.team_id=t.id
WHERE p.name LIKE ? ESCAPE '\\' OR p.name_en LIKE ? ESCAPE '\\'
OR p.role LIKE ? ESCAPE '\\' OR p.role_cn LIKE ? ESCAPE '\\'
OR t.name LIKE ? ESCAPE '\\' OR p.bio LIKE ? ESCAPE '\\'
ORDER BY p.id LIMIT ?""", (like, like, like, like, like, like, limit))
return {"_source": "persons", "results": [
{"id": r["id"], "name": r["name"], "name_en": r["name_en"], "role": r["role"],
"role_cn": r["role_cn"], "title": r["title"], "team": r["team_name"],
"bio": r["bio"], "achievements": r["achievements"]} for r in rows]}
# ================================================================== 人物列表(前台分页版)
def list_persons(q="", role="", page=1, size=12):
"""前台人物列表:关键词 + 角色筛选 + 分页"""
conds, args = [], []
if q:
like = f"%{fuzzy(q)}%"
conds.append("(p.name LIKE ? ESCAPE '\\' OR p.name_en LIKE ? ESCAPE '\\' OR p.role_cn LIKE ? ESCAPE '\\' OR t.name LIKE ? ESCAPE '\\' OR p.bio LIKE ? ESCAPE '\\')")
args += [like] * 5
if role:
conds.append("p.role=?")
args.append(role)
where = ("WHERE " + " AND ".join(conds)) if conds else ""
total = query_one(f"SELECT COUNT(*) AS c FROM persons p LEFT JOIN teams t ON p.team_id=t.id {where}", args)["c"]
rows = query(f"""SELECT p.*, t.name AS team_name FROM persons p LEFT JOIN teams t ON p.team_id=t.id
{where} ORDER BY p.id LIMIT ? OFFSET ?""", args + [size, (page - 1) * size])
return {"results": [
{"id": r["id"], "name": r["name"], "name_en": r["name_en"], "role": r["role"],
"role_cn": r["role_cn"], "title": r["title"], "team": r["team_name"],
"bio": r["bio"], "achievements": r["achievements"]} for r in rows],
"total": total, "page": page, "size": size}
# ================================================================== 知识百科(纯向量检索)
def search_knowledge(query_text, limit=3):
q = (query_text or "").strip()
try:
hits = vector_store.query_vectors(q, n_results=limit)
except Exception:
return {"_source": "knowledge", "results": []}
out = []
for h in hits:
if h["metadata"].get("kind") != "wiki":
continue
n = query_one("SELECT * FROM news WHERE id=?", (h["metadata"].get("news_id"),))
if n:
out.append({"title": n["title"], "content": n["content"][:400]})
return {"_source": "knowledge", "results": out}
# ================================================================== 工具注册表(供 LLM function calling
TOOLS = [
{"type": "function", "function": {"name": "search_teams", "description": "查询球队信息(名称/城市/主场/主教练/总冠军数),支持中文名、英文名、缩写",
"parameters": {"type": "object", "properties": {"query": {"type": "string", "description": "球队名或关键词,如'湖人'或'Lakers'"}, "limit": {"type": "integer", "description": "返回条数,默认6"}}, "required": ["query"]}}},
{"type": "function", "function": {"name": "search_players", "description": "查询球员信息(球队/位置/本赛季与生涯数据/荣誉),支持中英文名或球队名",
"parameters": {"type": "object", "properties": {"query": {"type": "string", "description": "球员名或球队名,如'库里'或'Stephen Curry'"}, "limit": {"type": "integer", "description": "返回条数,默认6"}}, "required": ["query"]}}},
{"type": "function", "function": {"name": "search_games", "description": "查询比赛信息(比分/时间/轮次),支持球队名+时间语义(最近/上一场/下一场/今天/明天),如'湖人最近比赛'、'总决赛第六场'",
"parameters": {"type": "object", "properties": {"query": {"type": "string", "description": "球队名或时间描述,如'勇士 最近'"}, "limit": {"type": "integer", "description": "返回条数,默认10"}}, "required": ["query"]}}},
{"type": "function", "function": {"name": "get_game_detail", "description": "获取单场比赛详情及双方球员技术统计(得分/篮板/助攻等),参数为比赛ID",
"parameters": {"type": "object", "properties": {"game_id": {"type": "integer", "description": "比赛ID(先调用search_games获得)"}}, "required": ["game_id"]}}},
{"type": "function", "function": {"name": "search_standings", "description": "查询球队排名(东西部/胜场/胜率),如'西部排名'或'湖人战绩'",
"parameters": {"type": "object", "properties": {"query": {"type": "string", "description": "球队名或'西部'/'东部'"}}, "required": ["query"]}}},
{"type": "function", "function": {"name": "search_news", "description": "查询新闻资讯(交易/伤病/奖项/动态),支持关键词或语义描述",
"parameters": {"type": "object", "properties": {"query": {"type": "string", "description": "新闻关键词或话题,如'选秀'、'詹姆斯续约'"}, "limit": {"type": "integer", "description": "返回条数,默认5"}}, "required": ["query"]}}},
{"type": "function", "function": {"name": "search_persons", "description": "查询篮球相关人物(教练/经纪人/评论员/主持人/总经理/传奇),如'波波维奇'、'杨毅'",
"parameters": {"type": "object", "properties": {"query": {"type": "string", "description": "人名或角色,如'勇士主教练'、'评论员'"}, "limit": {"type": "integer", "description": "返回条数,默认6"}}, "required": ["query"]}}},
{"type": "function", "function": {"name": "search_knowledge", "description": "查询NBA知识百科(历史/规则/制度/纪录等),如'工资帽'、'选秀制度'、'三分球历史'",
"parameters": {"type": "object", "properties": {"query": {"type": "string", "description": "知识话题描述"}, "limit": {"type": "integer", "description": "返回条数,默认3"}}, "required": ["query"]}}},
]
TOOL_HANDLERS = {
"search_teams": search_teams,
"search_players": search_players,
"search_games": search_games,
"get_game_detail": get_game_detail,
"search_standings": search_standings,
"search_news": search_news,
"search_persons": search_persons,
"search_knowledge": search_knowledge,
}
def run_tool(name, args_dict):
"""执行工具调用(统一异常兜底,schema参数名→函数参数名映射)"""
try:
fn = TOOL_HANDLERS.get(name)
if not fn:
return {"_source": "error", "results": [], "error": f"未知工具 {name}"}
args = dict(args_dict or {})
if "query" in args and "query_text" in fn.__code__.co_varnames:
args["query_text"] = args.pop("query")
return fn(**args)
except Exception as e:
return {"_source": "error", "results": [], "error": f"工具执行失败: {e}"}