Files
kefu/deploy/knowledge-20260916-100045/production.diff
T
2026-09-21 10:34:06 +08:00

180 lines
8.8 KiB
Diff

--- production/wechat_rpa/admin_api.py
+++ release/wechat_rpa/admin_api.py
@@ -42,6 +42,7 @@
import admin_backend as backend
import app_version
import archive_api
+import knowledge_api
import zyt_auth
# 账号下拉框里「全部」的取值。用 -1 而不是 0:0 早就有含义了(旧版未归属数据),
@@ -1277,12 +1278,19 @@
) -> dict:
# 这一份的结构由 `admin_backend.desktop_config_payload` 统一给出。
# 两处各写一份的话迟早漂移,表现成"某台客户端少了个字段",极难查。
- return backend.desktop_config_payload(
+ payload = backend.desktop_config_payload(
database,
scheme=request.headers.get("x-forwarded-proto", request.url.scheme),
host=request.headers.get("host", ""),
desktop_account=account,
)
+ knowledge = app.state.knowledge_store
+ tenant = str(account["tenant_id"])
+ counts = await asyncio.to_thread(knowledge.stats, tenant)
+ settings = await asyncio.to_thread(knowledge.settings, tenant)
+ payload["knowledge"] = {"published": counts.get("published", 0),
+ "enabled": bool(settings["enabled"]), "updated_at": settings["updated_at"]}
+ return payload
# 桌面端回流一次编排调用。补上这条之前,客户端改指 8766 会丢掉全部调用留痕
# ——配置照常同步、回复照常发,唯独调用记录一条不进库,而那恰恰是出事后用来
@@ -1325,6 +1333,10 @@
resolve_admin_tenant=resolve_admin_tenant,
zyt_api_url=lambda: desktop_login_api_url()[0],
zyt_patient_searcher=zyt_patient_searcher,
+ )
+
+ knowledge_api.register_knowledge_routes(
+ app, database, require, client_ip, resolve_admin_tenant, current_desktop,
)
# ── 前端静态资源 ─────────────────────────────────────────────────────
--- production/wechat_rpa/model_gateway.py
+++ release/wechat_rpa/model_gateway.py
@@ -44,6 +44,8 @@
import model_protocol
import admin_backend
+from knowledge_store import KnowledgeStore
+from knowledge_retriever import KnowledgeRetriever, inject_references
# ── 超时分层 ─────────────────────────────────────────────────────────────────
# 整体必须小于桌面端的超时,否则桌面端先超时重发,网关还在跑,双倍扣费。
@@ -477,6 +479,9 @@
app = FastAPI(title="模型统一网关", version="1.0")
database = admin_backend.Database(db_path)
database.migrate()
+ knowledge_store = KnowledgeStore(database)
+ knowledge_store.initialize()
+ knowledge_retriever = KnowledgeRetriever(knowledge_store)
catalog = Catalog(db_path)
idempotency: dict[str, tuple[float, dict]] = {}
log_queue: asyncio.Queue = asyncio.Queue(maxsize=2000)
@@ -522,6 +527,9 @@
record = await log_queue.get()
try:
await asyncio.to_thread(_write_log, db_path, record)
+ if record.get("knowledge_retrieval") is not None:
+ await asyncio.to_thread(knowledge_retriever.record, record["tenant_id"],
+ record["task_id"], record["knowledge_retrieval"])
except Exception:
pass
finally:
@@ -584,7 +592,12 @@
)
if idempotency_key and idempotency_key in idempotency:
# 桌面端重发(超时后重试)不该重复扣费
- return JSONResponse(idempotency[idempotency_key][1])
+ cached = idempotency[idempotency_key][1]
+ cached_hits = (cached.get("knowledge") or {}).get("hits", [])
+ if not cached_hits or await asyncio.to_thread(
+ knowledge_retriever.references_current, str(account["tenant_id"]), cached_hits):
+ return JSONResponse(cached)
+ idempotency.pop(idempotency_key, None)
body = await request.json()
answers, judge, mode, version = catalog.plan()
@@ -606,6 +619,32 @@
tools = body.get("tools") or None
started = time.monotonic()
+ knowledge_result = None
+ knowledge_trace = {"enabled": False, "hits": [], "mode": "disabled", "elapsed_ms": 0}
+ tenant = str(account["tenant_id"])
+ if str(body.get("purpose") or "chat") == "chat":
+ settings = await asyncio.to_thread(knowledge_store.settings, tenant)
+ if settings["enabled"]:
+ from knowledge_processing import latest_query
+ query = str(body.get("customer_text") or "")
+ if not query:
+ for turn in reversed(messages):
+ if turn.get("role") == "user" and isinstance(turn.get("content"), str):
+ query = turn["content"]
+ break
+ query = latest_query(query) or query
+ try:
+ knowledge_result = await asyncio.wait_for(
+ asyncio.to_thread(knowledge_retriever.search, tenant, query), timeout=4.0)
+ knowledge_trace = {"enabled": True, "mode": knowledge_result["mode"],
+ "elapsed_ms": knowledge_result["elapsed_ms"], "hits": [
+ {k: h[k] for k in ("id", "revision", "score")} for h in knowledge_result["hits"]]}
+ messages = inject_references(messages, knowledge_result["hits"])
+ except Exception:
+ knowledge_trace = {"enabled": True, "hits": [], "mode": "unavailable", "elapsed_ms": 4000}
+ from knowledge_processing import redact
+ knowledge_result = {**knowledge_trace, "query": redact(query)[:1000]}
+ messages = inject_references(messages, [])
if tools:
# 工具轮:只问主出口,不并发也不评审。
@@ -634,12 +673,24 @@
"judge": dict(_EMPTY_VERDICT),
"judge_mode": mode,
"roles_version": version,
+ "knowledge": knowledge_trace,
"total_ms": int((time.monotonic() - started) * 1000),
}
if single.get("error") and not payload["reply"] and not payload["tool_calls"]:
return JSONResponse(payload, status_code=502)
- if x_idempotency_key:
- idempotency[x_idempotency_key] = (now, payload)
+ if idempotency_key:
+ idempotency[idempotency_key] = (now, payload)
+ try:
+ log_queue.put_nowait({
+ "desktop_account_id": int(account["id"]), "tenant_id": tenant,
+ "device_id": x_device_id, "task_id": str(body.get("task_id") or ""),
+ "roles_version": version, "judge_mode": mode, "chosen": payload["chosen"],
+ "judge": payload["judge"], "candidates": [single], "total_ms": payload["total_ms"],
+ "customer_text": str(body.get("customer_text") or ""), "reply_text": payload["reply"],
+ "purpose": str(body.get("purpose") or "chat"), "knowledge_retrieval": knowledge_result,
+ })
+ except asyncio.QueueFull:
+ pass
return JSONResponse(payload)
results = await asyncio.gather(
@@ -683,6 +734,7 @@
payload = {
"reply": chosen["text"],
+ "knowledge": knowledge_trace,
"tool_calls": [],
"chosen": chosen["provider"],
"candidates": results,
@@ -708,6 +760,7 @@
"customer_text": str(body.get("customer_text") or ""),
"reply_text": payload["reply"],
"purpose": str(body.get("purpose") or "chat"),
+ "knowledge_retrieval": knowledge_result,
})
except asyncio.QueueFull:
pass # 观测数据丢一条,不能因此拖慢回复
--- production/wechat_rpa/rbac.py
+++ release/wechat_rpa/rbac.py
@@ -27,6 +27,11 @@
("model:write", "增删改模型与编排", "模型"),
("agent:read", "查看智能体", "模型"),
("agent:write", "增删改智能体与协作方案", "模型"),
+ ("knowledge:read", "查看知识中心", "知识库"),
+ ("knowledge:write", "加工和编辑知识草稿", "知识库"),
+ ("knowledge:review", "审核知识", "知识库"),
+ ("knowledge:publish", "发布和停用知识", "知识库"),
+ ("knowledge:export", "导出已发布知识", "知识库"),
("review:read", "查看待审队列", "审核"),
("review:write", "通过/驳回待审回复", "审核"),
("stats:read", "查看调用统计", "运营"),