235 lines
8.6 KiB
Python
235 lines
8.6 KiB
Python
"""Function Calling 工具定义和执行器
|
|
|
|
让 AI 可以主动调用现有 API 获取市场数据:
|
|
- get_market_dashboard: 市场看板
|
|
- get_theme_history: 题材热点历史
|
|
- get_active_core_stocks: 核心股追踪
|
|
- get_stock_quote: 个股实时行情
|
|
- get_fund_flow: 资金流向
|
|
"""
|
|
|
|
import json
|
|
from services.ai_config import AI_API_BASE, AI_API_KEY, AI_MODEL, AI_MAX_TOKENS, AI_TEMPERATURE
|
|
|
|
TOOLS = [
|
|
{
|
|
"type": "function",
|
|
"function": {
|
|
"name": "get_market_dashboard",
|
|
"description": "获取A股市场看板数据,包含主要指数行情、全市场涨跌统计(涨跌家数/涨停/跌停/炸板率/成交额)、市场温度评分与竞价信号、行业强度榜、概念热度、完整连板梯队(limitLadder字段,含涨停原因与封单金额)、事件情报(热门股/龙虎榜/飙升/异动)",
|
|
"parameters": {"type": "object", "properties": {}, "required": []}
|
|
}
|
|
},
|
|
{
|
|
"type": "function",
|
|
"function": {
|
|
"name": "get_theme_history",
|
|
"description": "获取指定交易日的题材涨幅排行前20",
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {
|
|
"date": {"type": "string", "description": "交易日 YYYY-MM-DD"}
|
|
},
|
|
"required": ["date"]
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"type": "function",
|
|
"function": {
|
|
"name": "get_active_core_stocks",
|
|
"description": "获取活跃核心股列表,包含最近10日涨幅矩阵和所属题材",
|
|
"parameters": {"type": "object", "properties": {}, "required": []}
|
|
}
|
|
},
|
|
{
|
|
"type": "function",
|
|
"function": {
|
|
"name": "get_stock_quote",
|
|
"description": "获取单只股票实时行情(价格、涨跌幅、成交量、换手率等)",
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {
|
|
"code": {"type": "string", "description": "6位股票代码,如 600519"}
|
|
},
|
|
"required": ["code"]
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"type": "function",
|
|
"function": {
|
|
"name": "get_fund_flow",
|
|
"description": "获取个股资金流向数据(主力净流入、超大单/大单/中单/小单流入流出)",
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {
|
|
"code": {"type": "string", "description": "6位股票代码"},
|
|
"name": {"type": "string", "description": "股票名称"},
|
|
"days": {"type": "integer", "description": "获取天数,默认30"}
|
|
},
|
|
"required": ["code", "name"]
|
|
}
|
|
}
|
|
}
|
|
]
|
|
|
|
|
|
async def execute_tool(tool_name: str, arguments: dict) -> str:
|
|
"""执行工具调用,返回 JSON 字符串"""
|
|
try:
|
|
if tool_name == "get_market_dashboard":
|
|
return await _get_market_dashboard()
|
|
elif tool_name == "get_theme_history":
|
|
return await _get_theme_history(arguments.get("date", ""))
|
|
elif tool_name == "get_active_core_stocks":
|
|
return await _get_active_core_stocks()
|
|
elif tool_name == "get_stock_quote":
|
|
return await _get_stock_quote(arguments.get("code", ""))
|
|
elif tool_name == "get_fund_flow":
|
|
return await _get_fund_flow(
|
|
arguments.get("code", ""),
|
|
arguments.get("name", ""),
|
|
arguments.get("days", 30)
|
|
)
|
|
else:
|
|
return json.dumps({"error": f"未知工具: {tool_name}"})
|
|
except Exception as e:
|
|
return json.dumps({"error": str(e)})
|
|
|
|
|
|
async def _get_market_dashboard() -> str:
|
|
"""获取市场看板数据"""
|
|
from routes.market_dashboard import _build_dashboard
|
|
data = await _build_dashboard()
|
|
return json.dumps(data, ensure_ascii=False, default=str)
|
|
|
|
|
|
async def _get_theme_history(date: str) -> str:
|
|
"""获取指定交易日题材涨幅前20"""
|
|
from database import get_connection
|
|
conn = get_connection()
|
|
try:
|
|
rows = conn.execute(
|
|
"SELECT * FROM daily_top_themes WHERE trade_date = ? ORDER BY rank ASC",
|
|
(date,)
|
|
).fetchall()
|
|
items = [dict(r) for r in rows]
|
|
return json.dumps({"date": date, "items": items}, ensure_ascii=False)
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
async def _get_active_core_stocks() -> str:
|
|
"""获取活跃核心股列表"""
|
|
from database import get_connection
|
|
from datetime import date as date_cls
|
|
conn = get_connection()
|
|
try:
|
|
# 最近10个有数据的交易日
|
|
rows = conn.execute(
|
|
"SELECT DISTINCT trade_date FROM daily_core_stocks ORDER BY trade_date DESC LIMIT 10"
|
|
).fetchall()
|
|
dates = [r["trade_date"] for r in reversed(rows)]
|
|
|
|
if not dates:
|
|
return json.dumps({"dates": [], "stocks": []}, ensure_ascii=False)
|
|
|
|
placeholders = ",".join("?" * len(dates))
|
|
rows = conn.execute(
|
|
f"""SELECT trade_date, stock_code, stock_name, f3, cover_count FROM daily_core_stocks
|
|
WHERE trade_date IN ({placeholders})
|
|
ORDER BY trade_date DESC, rank ASC""",
|
|
dates,
|
|
).fetchall()
|
|
|
|
stock_days = {}
|
|
for r in rows:
|
|
code = r["stock_code"]
|
|
s = stock_days.setdefault(code, {
|
|
"stockCode": code,
|
|
"stockName": r["stock_name"],
|
|
"coverCount": r["cover_count"],
|
|
"dailyGains": {},
|
|
"appearCount": 0,
|
|
"lastAppear": None,
|
|
})
|
|
s["dailyGains"][r["trade_date"]] = r["f3"]
|
|
s["appearCount"] += 1
|
|
if s["lastAppear"] is None or r["trade_date"] > s["lastAppear"]:
|
|
s["lastAppear"] = r["trade_date"]
|
|
|
|
stocks = list(stock_days.values())
|
|
stocks.sort(key=lambda x: x.get("lastAppear") or "", reverse=True)
|
|
stocks.sort(key=lambda x: -x["appearCount"])
|
|
|
|
themes_rows = conn.execute(
|
|
f"""SELECT stock_code, theme_code, theme_name FROM daily_core_stock_themes
|
|
WHERE trade_date IN ({placeholders})""",
|
|
dates,
|
|
).fetchall()
|
|
themes_by_stock = {}
|
|
for t in themes_rows:
|
|
per = themes_by_stock.setdefault(t["stock_code"], {})
|
|
per.setdefault(t["theme_code"], {"theme_code": t["theme_code"], "theme_name": t["theme_name"]})
|
|
for s in stocks:
|
|
s["themes"] = list(themes_by_stock.get(s["stockCode"], {}).values())
|
|
|
|
latest = dates[-1] if dates else None
|
|
for s in stocks:
|
|
if s.get("lastAppear") and latest:
|
|
try:
|
|
d1 = date_cls.fromisoformat(latest)
|
|
d2 = date_cls.fromisoformat(s["lastAppear"])
|
|
s["daysSinceLastAppear"] = (d1 - d2).days
|
|
except ValueError:
|
|
s["daysSinceLastAppear"] = 0
|
|
else:
|
|
s["daysSinceLastAppear"] = 0
|
|
|
|
return json.dumps({"dates": dates, "stocks": stocks}, ensure_ascii=False)
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
async def _get_stock_quote(code: str) -> str:
|
|
"""获取个股实时行情"""
|
|
from services.tencent import fetch_quote
|
|
data = await fetch_quote(code)
|
|
if not data:
|
|
return json.dumps({"error": f"未找到股票 {code} 的数据"})
|
|
return json.dumps(data, ensure_ascii=False)
|
|
|
|
|
|
async def _get_fund_flow(code: str, name: str, days: int) -> str:
|
|
"""获取个股资金流向"""
|
|
from services.tencent import fetch_quote, get_market_prefix
|
|
from database import get_connection
|
|
|
|
# 先尝试从本地数据库获取历史资金流向
|
|
conn = get_connection()
|
|
try:
|
|
rows = conn.execute(
|
|
"SELECT * FROM cache WHERE key LIKE ? AND expires_at > datetime('now','localtime')",
|
|
(f"fund_flow:{code}%",)
|
|
).fetchall()
|
|
if rows:
|
|
return rows[0]["value"]
|
|
finally:
|
|
conn.close()
|
|
|
|
# 降级:通过腾讯获取基础行情数据
|
|
quote = await fetch_quote(code)
|
|
if not quote:
|
|
return json.dumps({"error": f"未找到股票 {code} 的数据"})
|
|
|
|
return json.dumps({
|
|
"code": code,
|
|
"name": name or quote.get("name", ""),
|
|
"currentPrice": quote.get("currentPrice", 0),
|
|
"changePercent": quote.get("changePercent", 0),
|
|
"volume": quote.get("volume", 0),
|
|
"amount": quote.get("amount", 0),
|
|
"note": "资金流向详细数据需通过东方财富API获取"
|
|
}, ensure_ascii=False)
|