83bf19a3e5
后端: - tools.py:工具注册表(get_park_overview 园区实时数据 / query_companies 企业名录 / control_display 大屏控制 / get_time),POST /api/tools/exec 执行 - rag.py:园区知识库(park.md 分块 → dashscope text-embedding-v3 → numpy 余弦检索),/api/kb/brief 摘要注入 + /api/kb/retrieve 动态检索 - 知识索引 kb_index.json 入库,部署免首次构建 前端(VoiceAssistant): - TOOL_DEFS 经 session.update 传给 LLM;toolcall 事件 → 后端执行 → sendToolOutput + requestResponse - instructions 拼接园区知识摘要;工具调用气泡提示 - 测试通过:4 个工具,检索「入驻政策」正确命中政策段落
128 lines
5.4 KiB
Python
128 lines
5.4 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""智能体工具注册表 —— LLM function calling 的后端执行器
|
|
|
|
- TOOLS:工具名 → 描述 + JSON Schema 参数(给 LLM 声明用)
|
|
- exec_tool(name, args):执行工具,返回给 LLM 的字符串结果(JSON)
|
|
- 供 POST /api/tools/exec 调用;前端 s2s 客户端收到 toolcall 事件后转发到此执行
|
|
"""
|
|
import json
|
|
import logging
|
|
from datetime import datetime
|
|
|
|
log = logging.getLogger("dpm.tools")
|
|
|
|
|
|
# ── 工具实现 ────────────────────────────────────────────────────────────
|
|
|
|
def _get_park_overview(args):
|
|
"""园区实时概览(在园项目/就业/营收/在园人数等)"""
|
|
from .sim_engine import sim_engine
|
|
d = sim_engine.snapshot()
|
|
return json.dumps({
|
|
"在园项目": d["projects"]["inPark"],
|
|
"累计孵化企业": d["projects"]["cum"],
|
|
"今日新增项目": d["projects"]["todayNew"],
|
|
"带动就业(人)": d["jobs"]["total"],
|
|
"今日新增就业": d["jobs"]["todayNew"],
|
|
"累计营收(万元)": d["revenue"]["total"],
|
|
"今日营收(万元)": d["revenue"]["today"],
|
|
"营收同比增速(%)": d["revenue"]["growth"],
|
|
"设备在线率(%)": d["park"]["devices"],
|
|
"今日能耗(kWh)": d["park"]["energy"],
|
|
"当前在园人数": d["park"]["people"],
|
|
"会议预约": d["park"]["meeting"],
|
|
"工位使用": d["park"]["desk"],
|
|
}, ensure_ascii=False)
|
|
|
|
|
|
def _query_companies(args):
|
|
"""园区入驻企业名录查询(关键词过滤)"""
|
|
from .sim_engine import COMPANY_NAMES
|
|
q = (args.get("keyword") or "").strip()
|
|
names = [n for n in COMPANY_NAMES if q in n] if q else list(COMPANY_NAMES)
|
|
return json.dumps({
|
|
"total": len(names),
|
|
"keyword": q,
|
|
"companies": names[:20],
|
|
}, ensure_ascii=False)
|
|
|
|
|
|
def _control_display(args):
|
|
"""大屏显示控制:切页 / 弹通知 / 媒体播放暂停(经 MQTT 广播到大屏)"""
|
|
from .mqtt import hub
|
|
action = (args.get("action") or "").strip()
|
|
target = (args.get("target") or "").strip()
|
|
if action not in ("switch_page", "alert", "media_play", "media_pause"):
|
|
return json.dumps({"error": f"不支持的 action: {action}"}, ensure_ascii=False)
|
|
params = {"target": target} if target else {}
|
|
ok = hub.publish_command(action, params).get("published", False)
|
|
return json.dumps({"ok": ok, "action": action, "target": target}, ensure_ascii=False)
|
|
|
|
|
|
def _get_time(args):
|
|
"""当前日期时间"""
|
|
return json.dumps({"datetime": datetime.now().strftime("%Y-%m-%d %H:%M:%S")}, ensure_ascii=False)
|
|
|
|
|
|
# ── 注册表 ──────────────────────────────────────────────────────────────
|
|
|
|
TOOLS = {
|
|
"get_park_overview": {
|
|
"fn": _get_park_overview,
|
|
"description": "获取园区实时运营概览:在园项目数、累计孵化企业、带动就业、营收(今日/累计)、设备在线率、在园人数、能耗等。回答园区数据类问题时使用。",
|
|
"parameters": {"type": "object", "properties": {}},
|
|
},
|
|
"query_companies": {
|
|
"fn": _query_companies,
|
|
"description": "查询园区入驻企业名录,支持按企业名关键词过滤。回答'有哪些企业/某企业是否入驻'时使用。",
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {"keyword": {"type": "string", "description": "企业名关键词,可为空字符串"}},
|
|
},
|
|
},
|
|
"control_display": {
|
|
"fn": _control_display,
|
|
"description": "控制大屏显示:switch_page 切换页面、alert 弹通知、media_play/media_pause 控制媒体播放。用户要求控制大屏时使用。",
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {
|
|
"action": {"type": "string", "enum": ["switch_page", "alert", "media_play", "media_pause"]},
|
|
"target": {"type": "string", "description": "目标:页面路径(如 /、/twin、/ai、/voice)或通知文本"},
|
|
},
|
|
},
|
|
},
|
|
"get_time": {
|
|
"fn": _get_time,
|
|
"description": "获取当前日期时间。用户问'现在几点/今天几号'时使用。",
|
|
"parameters": {"type": "object", "properties": {}},
|
|
},
|
|
}
|
|
|
|
|
|
def tool_schemas():
|
|
"""给 LLM 的 tools 定义(OpenAI function calling 格式)"""
|
|
return [
|
|
{
|
|
"type": "function",
|
|
"name": name,
|
|
"description": t["description"],
|
|
"parameters": t["parameters"],
|
|
}
|
|
for name, t in TOOLS.items()
|
|
]
|
|
|
|
|
|
def exec_tool(name: str, args: dict):
|
|
"""执行工具 → 返回给 LLM 的字符串结果(JSON)"""
|
|
t = TOOLS.get(name)
|
|
if not t:
|
|
log.warning("tools: 未知工具 %s", name)
|
|
return json.dumps({"error": f"未知工具: {name}"}, ensure_ascii=False)
|
|
try:
|
|
result = t["fn"](args or {})
|
|
log.info("tools: %s args=%s -> %s", name, json.dumps(args, ensure_ascii=False)[:120], str(result)[:120])
|
|
return result
|
|
except Exception as e: # noqa: BLE001
|
|
log.error("tools: %s 执行失败 %s", name, e, exc_info=True)
|
|
return json.dumps({"error": f"{name} 执行失败: {e}"}, ensure_ascii=False)
|