This commit is contained in:
lik
2026-06-01 17:40:11 +08:00
parent 83d660496c
commit cebe7876f7
7 changed files with 349 additions and 105 deletions

View File

@@ -3,11 +3,12 @@ import { createDeepAgent, FilesystemBackend } from "deepagents";
import { AIMessageChunk, ToolMessage } from "langchain";
import { SystemMessage, HumanMessage, AIMessage } from "@langchain/core/messages";
import { ChatDeepSeek } from "@langchain/deepseek";
import Prompts from "./prompts.js";
import { getEnvTool,webFetchTool, webSearchTool, getCalendarInfoTool,
import EscortAdminPrompts from "./prompts.js";
import {
getEnvTool, webFetchTool, webSearchTool, getCalendarInfoTool,
getLunarCalendarInfoTool, getYearHolidaysTool, getYearTermsTool, getLatLngTool,
httpGetTool, httpPostTool
} from "./tools/index.js";
httpGetTool, httpPostTool, escortRecordQueryTool
} from "./tools/index.js";
export default class EscortAdminAgent {
constructor() {
@@ -105,22 +106,46 @@ export default class EscortAdminAgent {
const rootDir = process.cwd();
const backend = new FilesystemBackend({ rootDir });
this.model = new ChatDeepSeek({
this.flashModel = new ChatDeepSeek({
model: 'deepseek-v4-flash',
apiKey: 'sk-a58ccd82b7ba4ce3ac176a88c9381095',
temperature: 0.0
});
this.proModel = new ChatDeepSeek({
model: 'deepseek-v4-pro',
apiKey: 'sk-a58ccd82b7ba4ce3ac176a88c9381095',
temperature: 0.3
});
const escortRecordOperSubagent = {
name: "escort-record-oper-subagent",
description: "查询和设置陪诊预约记录",
systemPrompt: "根据用户指令,调用工具完成查询和设置陪诊记录。",
model: this.flashModel,
tools: [escortRecordQueryTool],
};
const escortResearchSubagent = {
name: "escort-research-subagent",
description: "陪诊(陪同就医)行业问题研究和解答",
systemPrompt: "你是陪诊(陪同就医)行业政策、发展趋势、行业知识研究和解答专家。",
model: this.proModel,
tools: [webFetchTool, webSearchTool],
};
this.agent = createDeepAgent({
name: "deep-agent",
model: this.model,
systemPrompt: Prompts.buildSystemPrompt(userInfo),
model: this.flashModel,
systemPrompt: EscortAdminPrompts.buildSystemPrompt(userInfo),
backend,
tools: [getEnvTool, webFetchTool, webSearchTool, getLatLngTool, httpGetTool, httpPostTool,
getCalendarInfoTool, getLunarCalendarInfoTool, getYearHolidaysTool, getYearTermsTool],
skills: ["./agent/escort/skills/"]
getCalendarInfoTool, getLunarCalendarInfoTool, getYearHolidaysTool, getYearTermsTool,
escortRecordQueryTool]
});
return this.agent;
}
}
const adminAgent = new EscortAdminAgent();
export { adminAgent };

View File

@@ -0,0 +1,34 @@
import moment from "moment";
import services from "../../resource/services.js";
import agreement from "../../resource/agreement.js";
class EscortAdminPrompts {
static buildSystemPrompt(userInfo) {
let userInfo_str = "用户未登录,提示用户先登录,并在'我的'中完善个人信息";
if (userInfo) {
userInfo_str = JSON.stringify({
name: userInfo.profile.name,
mobile: userInfo.profile.mobile,
role: userInfo.app
});
}
return `
# 角色定义
你是小暖,暖橙陪诊平台的管理助手,直接、高效的解决平台问题。
# 核心能力
- 解答服务流程、价格、注意事项
- 提供就诊准备建议
- 通过tools和skills,为用户提供其他服务。如:查询天气、路线、查询医院信息、查询医生信息等。
# 铁律(必须遵守)
1. 思考和理解任务意图,专业、谨慎、高效、专注的完成任务。
## 参考信息
管理员信息:${userInfo_str};
`;
}
}
export default EscortAdminPrompts;

View File

@@ -0,0 +1,66 @@
import { tool } from "@langchain/core/tools";
import z from "zod";
import { DBModel } from "../../../../models/index.js";
const escortRecordQueryTool = tool(
async ({ patientName, mobile, status, page = 1, pageSize = 20 }) => {
try {
const filter = {};
if (patientName) {
filter["patient.name"] = { $regex: patientName, $options: "i" };
}
if (mobile) {
filter["patient.mobile"] = mobile;
}
if (status) {
filter.status = status;
}
const skip = (page - 1) * pageSize;
const records = await DBModel.EscortRecord.find(filter)
.sort({ "schedule.date": -1 })
.skip(skip)
.limit(pageSize)
.lean();
const total = await DBModel.EscortRecord.countDocuments(filter);
return {
success: true,
data: records,
};
} catch (error) {
return {
success: false,
error: error.message,
};
}
},
{
name: "escort_record_query",
description:
"Query escort records from database. Supports filtering by patient name, mobile phone number, and appointment status. Returns paginated results sorted by appointment date in descending order.",
schema: z.object({
patientName: z
.string()
.optional()
.describe("Patient name for fuzzy search"),
mobile: z
.string()
.optional()
.describe("Patient mobile phone number for exact match"),
status: z
.enum(["pending", "confirmed", "in_progress", "completed", "cancelled"])
.optional()
.describe(
"Appointment status: pending (待确认), confirmed (已确认), in_progress (进行中), completed (已完成), cancelled (已取消)"
),
}),
}
);
export { escortRecordQueryTool };

View File

@@ -0,0 +1,109 @@
import { tool } from "@langchain/core/tools";
import z from "zod";
import { DBModel } from "../../../../models/index.js";
const escortRecordSetTool = tool(
async ({ mobile, status, notes, payment }) => {
try {
if (!mobile) {
return {
success: false,
error: "Mobile phone number is required as the lookup key",
};
}
const record = await DBModel.EscortRecord.findOne({ "patient.mobile": mobile });
if (!record) {
return {
success: false,
error: `No escort record found for mobile: ${mobile}`,
};
}
const update = {};
if (status !== undefined) {
update.status = status;
}
if (notes !== undefined) {
if (notes.patientNote !== undefined) {
update["notes.patientNote"] = notes.patientNote;
}
if (notes.escortNote !== undefined) {
update["notes.escortNote"] = notes.escortNote;
}
if (notes.medicalSummary !== undefined) {
update["notes.medicalSummary"] = notes.medicalSummary;
}
}
if (payment !== undefined) {
if (payment.totalFee !== undefined) {
update["payment.totalFee"] = payment.totalFee;
}
if (payment.paidFee !== undefined) {
update["payment.paidFee"] = payment.paidFee;
}
if (payment.status !== undefined) {
update["payment.status"] = payment.status;
}
}
update["meta.updatetime"] = Date.now();
const updatedRecord = await DBModel.EscortRecord.findByIdAndUpdate(
record._id,
{ $set: update },
{ new: true }
);
return {
success: true,
data: updatedRecord,
};
} catch (error) {
return {
success: false,
error: error.message,
};
}
},
{
name: "escort_record_set",
description:
"Update escort record fields by patient mobile phone number. Supports updating status, notes (patientNote, escortNote, medicalSummary), and payment (totalFee, paidFee, status). Only provided fields will be updated.",
schema: z.object({
mobile: z
.string()
.describe("Patient mobile phone number used as the lookup key"),
status: z
.enum(["pending", "confirmed", "in_progress", "completed", "cancelled"])
.optional()
.describe(
"Appointment status: pending (待确认), confirmed (已确认), in_progress (进行中), completed (已完成), cancelled (已取消)"
),
notes: z
.object({
patientNote: z.string().optional().describe("Patient remarks / special requirements"),
escortNote: z.string().optional().describe("Escort service notes"),
medicalSummary: z.string().optional().describe("Medical visit summary"),
})
.optional()
.describe("Notes information to update"),
payment: z
.object({
totalFee: z.number().optional().describe("Total service fee (RMB)"),
paidFee: z.number().optional().describe("Amount already paid (RMB)"),
status: z
.enum(["unpaid", "partial", "paid", "refunded"])
.optional()
.describe("Payment status: unpaid, partial, paid, refunded"),
})
.optional()
.describe("Payment information to update"),
}),
}
);
export { escortRecordSetTool };

View File

@@ -16,5 +16,10 @@ export { getYearTermsTool } from './calendar/year_terms.js';
// 地理工具 (LangChain format)
export { getLatLngTool } from './geo/latlon.js';
// 系统工具 (LangChain format)
export { getEnvTool } from './system/envs.js';
// db
export { escortRecordQueryTool } from './db/escort_record_query.js';

View File

@@ -36,7 +36,7 @@ const EscortRecordSchema = mongoose.Schema(
*/
patient: {
name: { type: String, default: "", comment: "患者姓名" },
mobile: { type: String, default: "", comment: "患者联系电话" },
mobile: { type: String, default: "", unique: true, index: true, comment: "患者联系电话" },
sex: { type: String, enum: ["male", "female"], comment: "患者性别" },
age: { type: Number, default: 0, comment: "患者年龄" },
idnumber: { type: String, default: "", comment: "患者身份证号" },

View File

@@ -2,6 +2,7 @@ import WebSocket, { WebSocketServer } from 'ws';
import http from 'http';
import { DBModel } from "./models/index.js";
import { chatTask } from "./agent/escort/task.js";
import { adminAgent } from "./agent/escort-admin/agent.js"
export default class WebSocketServerManager {
constructor(port = 8080) {
@@ -80,6 +81,10 @@ export default class WebSocketServerManager {
if (msg.type === 'chat' || msg.type === 'clear') {
if (msg.agent === 'escort-admin') {
const userInfo = await this.getUserInfo(msg.userId);
adminAgent.streamChat(userInfo, [msg], (source, type, content, id) => {
ws.send(JSON.stringify({ source, type, content, id }));
});
} else {
const userInfo = await this.getUserInfo(msg.userId);
chatTask.streamChat(userInfo, msg, (source, type, content, id) => {