Files
api_health/agent/infra/tools/db/hospital_info_set.js
T
2026-09-09 17:36:45 +08:00

144 lines
6.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { tool } from "@langchain/core/tools";
import z from "zod";
import { DBModel } from "../../../../models/index.js";
const hospitalInfoSetTool = tool(
async ({ hospitalName, section, action, content, guides, entry, entryName, updatedBy }) => {
try {
if (!hospitalName) {
return { success: false, error: "hospitalName 不能为空" };
}
// 定位医院:名称必须唯一命中
const orgs = await DBModel.Organization.find({ name: { $regex: hospitalName, $options: "i" } }).exec();
if (orgs.length === 0) {
return { success: false, error: `未找到名称包含「${hospitalName}」的机构,请确认医院名称后重试` };
}
if (orgs.length > 1) {
return {
success: false,
error: `名称包含「${hospitalName}」的机构有 ${orgs.length} 家(${orgs.map((o) => o.name).join("、")}),请使用更精确的名称`,
};
}
const org = orgs[0];
const fa = org.forAgent ?? {};
const update = {};
if (section === "overview") {
// 文本块全量替换
if (typeof content !== "string") {
return { success: false, error: "overview 需要提供 content 文本(写入前请先查询现有内容并合并)" };
}
update["forAgent.overview"] = content;
} else if (section === "guides") {
// 场景键值对浅合并,值为 null 表示删除该场景
if (!guides || typeof guides !== "object" || Array.isArray(guides)) {
return { success: false, error: "guides 需要提供对象,键为场景名,值为文本(值为 null 表示删除该场景)" };
}
const merged = { ...(fa.guides ?? {}) };
for (const [key, value] of Object.entries(guides)) {
if (value === null) {
delete merged[key];
} else {
merged[key] = value;
}
}
update["forAgent.guides"] = merged;
} else if (section === "photos" || section === "departments" || section === "doctors") {
// 列表区块:按锚点 upsert(新增或整条替换)/ remove
const list = [...(fa[section] ?? [])];
if (action === "upsert") {
if (!entry || typeof entry !== "object") {
return { success: false, error: "upsert 需要提供 entry 对象" };
}
let idx = -1;
if (section === "photos") {
if (!entry.url) return { success: false, error: "photos 条目必须包含 url 作为锚点" };
idx = list.findIndex((p) => p.url === entry.url);
} else if (section === "doctors") {
if (!entry.name || !entry.department) {
return { success: false, error: "doctors 条目必须包含 name 和 department 作为锚点" };
}
idx = list.findIndex((d) => d.name === entry.name && d.department === entry.department);
} else {
if (!entry.name) return { success: false, error: "departments 条目必须包含 name 作为锚点" };
idx = list.findIndex((d) => d.name === entry.name);
}
if (idx >= 0) {
list[idx] = { ...list[idx], ...entry };
} else {
list.push(entry);
}
update[`forAgent.${section}`] = list;
} else if (action === "remove") {
if (!entryName) {
return {
success: false,
error: `remove 需要提供 entryName${section === "photos" ? "照片url" : section === "doctors" ? "医生姓名" : "科室名称"}`,
};
}
if (section === "photos") {
update[`forAgent.${section}`] = list.filter((p) => p.url !== entryName);
} else {
update[`forAgent.${section}`] = list.filter((d) => d.name !== entryName);
}
} else {
return { success: false, error: "列表区块(photos/departments/doctors)需要指定 action: upsert 或 remove" };
}
} else {
return { success: false, error: "未知的 section,可选值:overview / photos / departments / doctors / guides" };
}
update["forAgent.updatedAt"] = new Date();
if (updatedBy) {
update["forAgent.updatedBy"] = updatedBy;
}
const updated = await DBModel.Organization.findByIdAndUpdate(org._id, { $set: update }, { new: true }).exec();
return { success: true, data: updated.forAgent };
} catch (error) {
return { success: false, error: error.message };
}
},
{
name: "hospital_info_set",
description:
"录入/更新医院就医信息。按区块维护:overview(医院级文本,全量替换,先查后改避免丢失)、guides(场景指南键值对,浅合并)、photos/departments/doctors(列表,按锚点 upsert 或 remove)。医院名称必须唯一命中。",
schema: z.object({
hospitalName: z.string().describe("医院名称(需唯一命中,如 北京协和医院)"),
section: z
.enum(["overview", "photos", "departments", "doctors", "guides"])
.describe("要更新的区块"),
action: z
.enum(["set", "upsert", "remove"])
.optional()
.describe("操作类型:overview/guides 用 setphotos/departments/doctors 用 upsert 或 remove"),
content: z.string().optional().describe("overview 区块的完整文本(全量替换)"),
guides: z
.record(z.string(), z.string().nullable())
.optional()
.describe('guides 区块的场景键值对,如 {"住院流程": "..."};值为 null 表示删除该场景'),
entry: z
.object({
name: z.string().optional().describe("科室名或医生名(锚点)"),
department: z.string().optional().describe("医生所属科室(医生锚点之一)"),
detail: z
.string()
.optional()
.describe("信息全文:科室(位置/电话/类型/提示)或医生(职称/擅长/号别/坐班停诊)"),
photo: z.string().optional().describe("照片链接"),
url: z.string().optional().describe("照片链接(photos 锚点)"),
caption: z.string().optional().describe("照片说明"),
})
.optional()
.describe("photos/departments/doctors 条目(upsert 时必填)"),
entryName: z.string().optional().describe("remove 的锚点:科室名称 / 医生姓名 / 照片url"),
updatedBy: z.string().optional().describe("操作人标识(用户ID或姓名),用于审计"),
}),
}
);
export { hospitalInfoSetTool };