Files
DPM/backend/app/ai_tools.py
T
Pine 856ff88440 feat: 拆出 FastAPI 独立后端 + MQTT 控制 + 阿里云 AI
后端(backend/,FastAPI :10085):
- 全部数据 REST 接口:dashboard 快照 / 企业分区 / 播放列表 / 媒体 / 设置
- MQTT 控制通道:opc/display/command(切页/播放/卡片/通知),
  管理端 POST /api/display/command → MQTT 广播 → 所有大屏同步响应
- 阿里云 DashScope:通义千问 LLM + 函数调用(工具经 MQTT 广播),
  paraformer-realtime-v2 语音识别 /api/ai/asr
- 媒体资源统一由后端存储返回(上传/列表/静态服务)
- SSE /api/events 保留作 MQTT 不可用时的兼容回退

前端:
- config.js + .env.local 配置后端地址与 MQTT 账号(前端 dpm / 服务端 dpmserver)
- mqtt.js 客户端 + useMqttControl(MQTT 驱动切页/媒体/卡片/通知)
- DpmOverlays 全局覆盖层(通知 toast + 企业/分区/总览卡片)
- useParkSim 改为后端 API 数据源(离线回退本地模拟)
- AiChatPanel:对话走后端 LLM(工具调用),语音走本地录音 + 后端 ASR
- MediaScreen:媒体控制走 MQTT(保留 SSE 回退),修复 useEffect TDZ

Rust:lib.rs 移除内嵌 HTTP 服务器,只保留薄壳(窗口/权限/自启/npc 隧道)
安全:backend/.env、.env.local、media、data.json 已 gitignore
2026-08-17 21:26:16 +08:00

175 lines
9.9 KiB
Python

# -*- coding: utf-8 -*-
"""AI 助手工具调用引擎
后端收到对话 → 规则引擎判断意图 → 产出文字回复 + 工具调用列表
工具执行时通过 MQTT 广播 opc/display/command,前端(所有大屏)同步响应
工具:navigate(切页) / control(媒体控制) / alert(通知) / show_card(展示卡片)
"""
from .mqtt import hub
# ---------------- 工具执行 ----------------
def execute_tool(tool):
"""执行一个工具调用:发布 MQTT 控制指令,返回是否发布成功"""
t = tool.get("type")
params = tool.get("params") or {}
if t == "navigate":
return hub.publish_command("navigate", {"page": params.get("page", "home")}).get("published", False)
if t == "control":
action = params.get("action")
if action in ("play", "pause", "next", "prev"):
return hub.publish_command(action).get("published", False)
if action == "set_mode":
return hub.publish_command("set_mode", {"mode": params.get("mode", "auto")}).get("published", False)
return False
if t == "alert":
return hub.publish_command("alert", {"title": params.get("title", "提示"), "content": params.get("content", "")}).get("published", False)
if t == "show_card":
return hub.publish_command("show_card", params).get("published", False)
return False
def run_tools(tools):
"""执行一组工具,返回 [{type, ok}] 并汇总是否全部发布成功"""
results = []
all_ok = True
for tool in tools or []:
ok = bool(execute_tool(tool))
results.append({"type": tool.get("type"), "ok": ok})
all_ok = all_ok and ok
return results, all_ok
# ---------------- 意图 → 回复 + 工具 ----------------
PAGES = {
"home": "/", "数据大屏": "/", "首页": "/",
"twin": "/twin", "数字孪生": "/twin", "3d": "/twin", "孪生": "/twin",
"ai": "/ai", "ai助手": "/ai", "智能助手": "/ai",
"screen": "/screen", "媒体": "/screen", "轮播": "/screen", "媒体轮播": "/screen",
}
RULES = [
{
"match": ["入驻", "申请", "入孵", "入园", "如何加入", "怎样加入"],
"reply": "入驻流程如下:\n\n1. 提交入驻申请(线上表单或到园区服务中心现场办理)\n2. 项目评审(园区组织专家对项目可行性、团队背景进行评估)\n3. 签订入驻协议,办理入驻手续\n4. 安排办公场地,对接创业导师与孵化服务\n\n目前园区在园项目 158 家,累计孵化企业 208 余家。如需协助,可直接联系园区服务中心。",
"tools": [{"type": "show_card", "params": {"card": "overview"}}],
},
{
"match": ["政策", "补贴", "优惠", "扶持", "减免", "奖励", "资助"],
"reply": "园区政策服务覆盖「七位一体」中的政策维度:\n\n· 场地租金减免:符合条件的大学生创业项目可享受场地优惠\n· 创业补贴:协助对接省、市两级创业补贴与一次性创业资助\n· 税费优惠:对接小微企业所得税减免、增值税优惠等政策\n· 项目申报:指导高企认定(目前在园高新技术企业 32 家)、科技型中小企业备案\n\n更多政策细节可咨询园区政策服务窗口。",
"tools": [],
},
{
"match": ["场地", "办公室", "工位", "租金", "收费", "面积", "多少钱", "价格"],
"reply": "园区总建筑面积 1.2 万㎡,按功能划分为三个区域:\n\n· 加速区(A1–A12):成长型项目办公室,共 11 间独立办公室 + 1 间园区管理办公室\n· 国际区(I1–I7):面向国际合作的办公空间(7 间)\n· 成长区(G1–G8):初创团队成长空间(8 间)\n\n租金按创业项目阶段与政策给予差异化优惠,具体可到园区服务中心咨询。",
"tools": [{"type": "show_card", "params": {"card": "zones"}}],
},
{
"match": ["企业", "公司", "名录", "名单", "入驻企业", "有哪些企业", "查企业"],
"reply": "园区目前有 39 家入驻企业,分布在三个区域:\n\n· 加速区:云南派音人工智能科技、米勒克尔蓝宝石珠宝、云南宸中低空经济等 9 家\n· 国际区:仰光客厅、中越生物医疗、酷享野农AI农业等 7 家\n· 成长区:Facebook越南跨境电商、昆明云韵体育、云南星瑞航空等 8 家\n\n我已在屏幕上为您展示企业分布卡片。",
"tools": [{"type": "show_card", "params": {"card": "companies"}}],
},
{
"match": ["融资", "投资", "资金", "贷款", "估值", "资本"],
"reply": "园区融资服务包含:\n\n· 融资对接:定期组织路演与投资机构对接活动\n· 孵化估值:累计孵化企业估值约 5.2 亿元\n· 金融支持:协助对接银行创业贷款、政府创业担保贷款\n· 财务辅导:提供融资方案设计与商业计划书辅导",
"tools": [],
},
{
"match": ["就业", "招聘", "岗位", "人才", "用工"],
"reply": "园区累计带动就业 2,186 人(其中高校毕业生 1,680 人,实习岗位 506 个)。\n\n· 企业招聘:在驻企业持续发布岗位\n· 人才对接:与在昆高校共建实习实训基地\n· 创业带动:园区鼓励以创业带动就业",
"tools": [],
},
{
"match": ["导师", "培训", "课程", "辅导", "孵化服务", "孵化器"],
"reply": "园区配备创业导师 58 位,提供全周期孵化服务:\n\n· 一对一辅导:创业导师定期坐诊,覆盖商业模式、法律财税、市场营销\n· 培训课程:创业训练营、行业专题沙龙、政策解读会\n· 资源对接:技术、供应链、渠道等产业资源链接\n· 公共服务:工商注册、知识产权、法律咨询一站式代办",
"tools": [],
},
{
"match": ["AI", "模型", "算力", "tokens", "token", "工具", "智能", "赋能", "大模型"],
"reply": "园区 AI 赋能服务是「七位一体」的核心特色:\n\n· 大模型推理:接入 DeepSeek-V3、通义千问、智谱 GLM-4、豆包、讯飞星火等多模型\n· 工具调用:文档生成、数据查询、图像创作、代码执行、语音合成等智能工具\n· 算力与成本:实时提供 token 消耗统计与速率监控\n\n今日园区 AI 推理任务持续运行,服务节点 12/12 在线。",
"tools": [],
},
{
"match": ["加速区", "国际区", "成长区", "分区", "区域", "区别", "布局"],
"reply": "园区按企业成长阶段划分三个区域:\n\n· 加速区(蓝色):成长型项目独立办公室(A1–A12),在驻 9 家\n· 国际区(青色):国际合作与跨境业务(I1–I7),在驻 7 家\n· 成长区(绿色):初创团队共享空间(G1–G8),在驻 8 家\n\n园区管理办公室位于加速区 M 房间。",
"tools": [{"type": "show_card", "params": {"card": "zones"}}],
},
{
"match": ["", "", "打开", "显示", "进入"],
"reply": None, # 由下方页面/控制规则补充
},
{
"match": ["你好", "您好", "hi", "hello", "", "在吗"],
"reply": "您好!我是 OPC 园区 AI 智能助手。\n\n可以问我:入驻流程、政策扶持、场地收费、AI 赋能、企业名录、融资服务等问题;也可以让我「切换到数字孪生页」「打开媒体轮播」或「展示企业卡片」。",
"tools": [],
},
{
"match": ["谢谢", "感谢", "好的", "明白"],
"reply": "不客气,随时为您服务。您也可以让我展示园区数据卡片,或切换到任意展示页面。",
"tools": [],
},
]
DEFAULT_REPLY = "园区提供「空间 + 孵化 + 融资 + 政策 + 资源 + AI 赋能 + 综合服务」七位一体服务,在园项目 158 家,累计带动就业 2,186 人,累计营收 2.08 亿元。\n\n您可以这样问我:\n· 如何申请入驻园区?\n· 展示入驻企业卡片\n· 切换到数字孪生页面"
def _detect_page(text):
for key, path in PAGES.items():
if key in text:
return path
return None
def _detect_control(text):
if any(k in text for k in ["播放", "继续播放", "开始播放"]):
return "play"
if any(k in text for k in ["暂停", "停止播放"]):
return "pause"
if any(k in text for k in ["下一个", "下一张", "下一屏", "切换下一"]):
return "next"
if any(k in text for k in ["上一个", "上一张", "上一屏", "切换上一"]):
return "prev"
return None
def run_chat(messages):
"""输入对话消息列表([{"role","content"}]),返回 {reply, tools}"""
text = ""
for m in reversed(messages or []):
if m.get("role") in ("user", "me"):
text = (m.get("content") or "").strip()
break
reply = None
tools = []
# 1) 页面切换意图(优先级最高)
page = _detect_page(text)
if page:
label = {"/": "数据大屏", "/twin": "数字孪生", "/ai": "AI 助手", "/screen": "媒体轮播"}.get(page, page)
reply = f"好的,已为您切换到「{label}」页面。"
tools = [{"type": "navigate", "params": {"page": page}}]
return {"reply": reply, "tools": tools}
# 2) 媒体控制意图
ctrl = _detect_control(text)
if ctrl:
names = {"play": "播放", "pause": "暂停", "next": "切换下一项", "prev": "切换上一项"}
reply = f"好的,已执行:{names[ctrl]}"
tools = [{"type": "control", "params": {"action": ctrl}}]
return {"reply": reply, "tools": tools}
# 3) 知识库规则
for rule in RULES:
if any(k in text for k in rule["match"]):
reply = rule["reply"]
tools = rule["tools"]
break
if reply is None:
reply = DEFAULT_REPLY
# 工具已在后端通过 MQTT 广播;返回值供请求端本地同步执行
return {"reply": reply, "tools": tools}