tmp
This commit is contained in:
+14
-11
@@ -5,6 +5,10 @@ import { HumanMessage } from "@langchain/core/messages";
|
|||||||
import { ChatDeepSeek } from "@langchain/deepseek";
|
import { ChatDeepSeek } from "@langchain/deepseek";
|
||||||
import config from '../../conf.json' with { type: 'json' };
|
import config from '../../conf.json' with { type: 'json' };
|
||||||
import logger from '../../utils/logger.js';
|
import logger from '../../utils/logger.js';
|
||||||
|
|
||||||
|
// 密钥由 conf.json 统一提供;百度地图 skill 通过 env_get 读环境变量,此处桥接
|
||||||
|
process.env.TAVILY_API_KEY ??= config.agent.tavily?.apiKey;
|
||||||
|
process.env.BAIDU_MAP_AUTH_TOKEN ??= config.agent.baiduMap?.authToken;
|
||||||
import EscortAdminPrompts from "./prompts.js";
|
import EscortAdminPrompts from "./prompts.js";
|
||||||
import {
|
import {
|
||||||
getEnvTool, webFetchTool, webSearchTool, getCalendarInfoTool,
|
getEnvTool, webFetchTool, webSearchTool, getCalendarInfoTool,
|
||||||
@@ -20,8 +24,7 @@ export default class EscortAdminAgent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
clearMessages(userInfo) {
|
clearMessages(userInfo) {
|
||||||
const userId = userInfo?._id ?? 'default-session';
|
this.sessions.delete(userInfo._id);
|
||||||
this.sessions.delete(userId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// msg: { agent: 'escort-admin', type: 'chat', ts: "2023-08-01 10:00:00", content: "你好" }
|
// msg: { agent: 'escort-admin', type: 'chat', ts: "2023-08-01 10:00:00", content: "你好" }
|
||||||
@@ -30,7 +33,7 @@ export default class EscortAdminAgent {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const userId = userInfo?._id ?? 'default-session';
|
const userId = userInfo._id;
|
||||||
|
|
||||||
// LRU:按用户取会话,并更新使用顺序
|
// LRU:按用户取会话,并更新使用顺序
|
||||||
let session = this.sessions.get(userId);
|
let session = this.sessions.get(userId);
|
||||||
@@ -76,16 +79,16 @@ export default class EscortAdminAgent {
|
|||||||
const isSubagent = namespace.some(s => s.startsWith("tools:"));
|
const isSubagent = namespace.some(s => s.startsWith("tools:"));
|
||||||
const source = isSubagent ? "subagent" : "main";
|
const source = isSubagent ? "subagent" : "main";
|
||||||
if (mode === "updates") {
|
if (mode === "updates") {
|
||||||
for (const nodeName of Object.keys(data)) {
|
|
||||||
if (!INTERESTING_NODES.has(nodeName)) continue;
|
|
||||||
// Main agent updates (empty namespace)
|
// Main agent updates (empty namespace)
|
||||||
if (namespace.length === 0) {
|
if (namespace.length === 0) {
|
||||||
for (const [nodeName, data_] of Object.entries(data)) {
|
for (const [nodeName, data_] of Object.entries(data)) {
|
||||||
|
if (!INTERESTING_NODES.has(nodeName)) continue;
|
||||||
if (nodeName === "tools") {
|
if (nodeName === "tools") {
|
||||||
// Subagent results returned to main agent
|
// 工具结果必须并入历史,否则下一轮会出现 tool_calls 缺少对应 tool 结果的断链
|
||||||
for (const msg of data_.messages ?? []) {
|
for (const msg of data_.messages ?? []) {
|
||||||
if (msg.type === "tool") {
|
if (msg.type === "tool") {
|
||||||
logger.info(`Subagent complete: ${msg.name}, Result: ${String(msg.content).slice(0, 200)}`);
|
session.messages.push(msg);
|
||||||
|
logger.info(`Tool complete: ${msg.name}, Result: ${String(msg.content).slice(0, 200)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (nodeName === "model_request") {
|
} else if (nodeName === "model_request") {
|
||||||
@@ -94,21 +97,21 @@ export default class EscortAdminAgent {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Subagent updates (non-empty namespace)
|
// Subagent updates (non-empty namespace)
|
||||||
for (const [nodeName, data_] of Object.entries(data)) {
|
for (const nodeName of Object.keys(data)) {
|
||||||
|
if (!INTERESTING_NODES.has(nodeName)) continue;
|
||||||
logger.info(`[${namespace[0]}] step: ${nodeName}`);
|
logger.info(`[${namespace[0]}] step: ${nodeName}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else if (mode === "messages") {
|
} else if (mode === "messages") {
|
||||||
const [message] = data;
|
const [message] = data;
|
||||||
if (message.tool_call_chunks?.length) {
|
if (message.tool_call_chunks?.length) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (AIMessageChunk.isInstance(message)) {
|
if (AIMessageChunk.isInstance(message)) {
|
||||||
if (message.text && !message.tool_call_chunks?.length) {
|
if (message.text) {
|
||||||
callback(source, "ai", message.text, message.id);
|
callback(source, "ai", message.text, message.id);
|
||||||
}
|
}
|
||||||
if (message.additional_kwargs.reasoning_content && !message.tool_call_chunks?.length) {
|
if (message.additional_kwargs.reasoning_content) {
|
||||||
callback(source, "reasoning", message.additional_kwargs.reasoning_content, message.id);
|
callback(source, "reasoning", message.additional_kwargs.reasoning_content, message.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import moment from "moment";
|
|
||||||
import services from "../../resource/services.js";
|
import services from "../../resource/services.js";
|
||||||
import agreement from "../../resource/agreement.js";
|
import agreement from "../../resource/agreement.js";
|
||||||
|
|
||||||
|
|||||||
@@ -1,24 +1,33 @@
|
|||||||
import { tool } from "langchain"
|
import { tool } from "langchain"
|
||||||
|
import lunarLib from "lunar-javascript"
|
||||||
|
const { Solar } = lunarLib
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 特定日期日历工具
|
* 特定日期日历工具
|
||||||
* 提供指定日期的日历信息
|
* 基于 lunar-javascript 提供指定日期的完整农历信息
|
||||||
*/
|
*/
|
||||||
export const getLunarCalendarInfoTool = tool(
|
export const getLunarCalendarInfoTool = tool(
|
||||||
async ({ year, month, day }) => {
|
async ({ year, month, day }) => {
|
||||||
try {
|
try {
|
||||||
const date = new Date(year, month - 1, day)
|
const solar = Solar.fromYmd(year, month, day)
|
||||||
|
const lunar = solar.getLunar()
|
||||||
|
|
||||||
if (isNaN(date.getTime())) {
|
const result = {
|
||||||
return JSON.stringify({ error: '无效的日期' }, null, 2)
|
solar: solar.toString(),
|
||||||
|
weekday: `星期${solar.getWeekInChinese()}`,
|
||||||
|
lunar: lunar.toString(),
|
||||||
|
ganzhiYear: lunar.getYearInGanZhi(),
|
||||||
|
zodiac: lunar.getYearShengXiao(),
|
||||||
|
solarTerms: lunar.getJieQi() || null,
|
||||||
|
festivals: [...solar.getFestivals(), ...lunar.getFestivals()],
|
||||||
|
yi: lunar.getDayYi(),
|
||||||
|
ji: lunar.getDayJi(),
|
||||||
|
xiShen: lunar.getDayPositionXiDesc(),
|
||||||
|
caiShen: lunar.getDayPositionCaiDesc()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取农历信息
|
return JSON.stringify(result, null, 2)
|
||||||
const lunarInfo = {}
|
|
||||||
|
|
||||||
return JSON.stringify(lunarInfo, null, 2)
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in date calendar tool:', error)
|
|
||||||
return JSON.stringify({ error: error.message }, null, 2)
|
return JSON.stringify({ error: error.message }, null, 2)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -40,7 +40,8 @@ export async function getAccurateTime() {
|
|||||||
for (const source of timeSources) {
|
for (const source of timeSources) {
|
||||||
try {
|
try {
|
||||||
const startTime = Date.now()
|
const startTime = Date.now()
|
||||||
const res = await fetch(source.url)
|
// 每个时间源限时 3 秒,防止单个源 TCP 挂起长时间阻塞
|
||||||
|
const res = await fetch(source.url, { signal: AbortSignal.timeout(3000) })
|
||||||
const latency = Date.now() - startTime
|
const latency = Date.now() - startTime
|
||||||
|
|
||||||
let timestamp = null
|
let timestamp = null
|
||||||
|
|||||||
@@ -1,30 +1,41 @@
|
|||||||
import { tool } from "langchain"
|
import { tool } from "langchain"
|
||||||
|
import lunarLib from "lunar-javascript"
|
||||||
|
const { Solar, HolidayUtil } = lunarLib
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 年份节日列表工具
|
* 年份节日列表工具
|
||||||
* 查询给定年份节日所在日期列表
|
* 基于 lunar-javascript 计算全年节日(公历节日 + 农历传统节日)与法定节假日安排
|
||||||
*/
|
*/
|
||||||
export const getYearHolidaysTool = tool(
|
export const getYearHolidaysTool = tool(
|
||||||
async ({ year }) => {
|
async ({ year }) => {
|
||||||
try {
|
try {
|
||||||
// 简化实现,返回基本节日信息
|
// 法定节假日安排(含调休补班)
|
||||||
const holidays = [
|
const legal = HolidayUtil.getHolidays(year).map(h => ({
|
||||||
{ date: `${year}-01-01`, name: '元旦' },
|
date: h.getTarget().toString(),
|
||||||
{ date: `${year}-02-14`, name: '情人节' },
|
name: h.getName(),
|
||||||
{ date: `${year}-05-01`, name: '劳动节' },
|
type: h.isWork() ? "调休上班" : "放假"
|
||||||
{ date: `${year}-06-01`, name: '儿童节' },
|
}))
|
||||||
{ date: `${year}-10-01`, name: '国庆节' }
|
|
||||||
]
|
|
||||||
|
|
||||||
return JSON.stringify({ year, holidays }, null, 2)
|
// 遍历全年日期,收集公历节日与农历传统节日(春节、中秋、端午等)
|
||||||
|
const festivals = []
|
||||||
|
const start = Solar.fromYmd(year, 1, 1)
|
||||||
|
const days = Solar.fromYmd(year + 1, 1, 1).subtract(start)
|
||||||
|
for (let i = 0; i < days; i++) {
|
||||||
|
const d = start.next(i)
|
||||||
|
const names = [...d.getFestivals(), ...d.getLunar().getFestivals()]
|
||||||
|
if (names.length) {
|
||||||
|
festivals.push({ date: d.toString(), name: names.join("、") })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.stringify({ year, legal, festivals }, null, 2)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in year holidays tool:', error)
|
|
||||||
return JSON.stringify({ error: error.message }, null, 2)
|
return JSON.stringify({ error: error.message }, null, 2)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "get_year_holidays",
|
name: "get_year_holidays",
|
||||||
description: "查询给定年份节日所在日期列表",
|
description: "查询给定年份节日所在日期列表,包括法定节假日安排(含调休)与传统节日",
|
||||||
schema: {
|
schema: {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
|
|||||||
@@ -1,43 +1,29 @@
|
|||||||
import { tool } from "langchain"
|
import { tool } from "langchain"
|
||||||
|
import lunarLib from "lunar-javascript"
|
||||||
|
const { Lunar } = lunarLib
|
||||||
|
|
||||||
|
// 当年冬至在节气表中的 key 为英文(上一年冬至占用中文 key)
|
||||||
|
const EN_TERM_NAMES = { DONG_ZHI: "冬至" }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 年份节气列表工具
|
* 年份节气列表工具
|
||||||
* 查询给定年份节气所在日期列表
|
* 基于 lunar-javascript 计算给定年份的 24 节气精确日期
|
||||||
*/
|
*/
|
||||||
export const getYearTermsTool = tool(
|
export const getYearTermsTool = tool(
|
||||||
async ({ year }) => {
|
async ({ year }) => {
|
||||||
try {
|
try {
|
||||||
// 简化实现,返回24节气信息
|
const table = Lunar.fromYmd(year, 1, 1).getJieQiTable()
|
||||||
const terms = [
|
const terms = []
|
||||||
{ date: `${year}-02-04`, name: '立春' },
|
for (const [key, solar] of Object.entries(table)) {
|
||||||
{ date: `${year}-02-19`, name: '雨水' },
|
// 只保留属于目标年份的节气(表首含上一年边界节气)
|
||||||
{ date: `${year}-03-05`, name: '惊蛰' },
|
if (solar.getYear() !== year) continue
|
||||||
{ date: `${year}-03-20`, name: '春分' },
|
const name = EN_TERM_NAMES[key] ?? key
|
||||||
{ date: `${year}-04-04`, name: '清明' },
|
terms.push({ date: solar.toString(), name })
|
||||||
{ date: `${year}-04-19`, name: '谷雨' },
|
}
|
||||||
{ date: `${year}-05-05`, name: '立夏' },
|
terms.sort((a, b) => a.date.localeCompare(b.date))
|
||||||
{ date: `${year}-05-20`, name: '小满' },
|
|
||||||
{ date: `${year}-06-05`, name: '芒种' },
|
|
||||||
{ date: `${year}-06-21`, name: '夏至' },
|
|
||||||
{ date: `${year}-07-07`, name: '小暑' },
|
|
||||||
{ date: `${year}-07-22`, name: '大暑' },
|
|
||||||
{ date: `${year}-08-07`, name: '立秋' },
|
|
||||||
{ date: `${year}-08-23`, name: '处暑' },
|
|
||||||
{ date: `${year}-09-07`, name: '白露' },
|
|
||||||
{ date: `${year}-09-23`, name: '秋分' },
|
|
||||||
{ date: `${year}-10-08`, name: '寒露' },
|
|
||||||
{ date: `${year}-10-23`, name: '霜降' },
|
|
||||||
{ date: `${year}-11-07`, name: '立冬' },
|
|
||||||
{ date: `${year}-11-22`, name: '小雪' },
|
|
||||||
{ date: `${year}-12-07`, name: '大雪' },
|
|
||||||
{ date: `${year}-12-21`, name: '冬至' },
|
|
||||||
{ date: `${year + 1}-01-05`, name: '小寒' },
|
|
||||||
{ date: `${year + 1}-01-20`, name: '大寒' }
|
|
||||||
]
|
|
||||||
|
|
||||||
return JSON.stringify({ year, terms }, null, 2)
|
return JSON.stringify({ year, terms }, null, 2)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in year terms tool:', error)
|
|
||||||
return JSON.stringify({ error: error.message }, null, 2)
|
return JSON.stringify({ error: error.message }, null, 2)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { tool } from "@langchain/core/tools";
|
import { tool } from "@langchain/core/tools";
|
||||||
import z from "zod";
|
import z from "zod";
|
||||||
|
import mongoose from "mongoose";
|
||||||
import { DBModel } from "../../../../models/index.js";
|
import { DBModel } from "../../../../models/index.js";
|
||||||
|
|
||||||
const escortRecordSetTool = tool(
|
const escortRecordSetTool = tool(
|
||||||
@@ -8,18 +9,18 @@ const escortRecordSetTool = tool(
|
|||||||
if (!orderId) {
|
if (!orderId) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: "Order ID (_id or orderNo) is required as the lookup key",
|
error: "Record ID (_id) is required as the lookup key",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const query = {
|
if (!mongoose.Types.ObjectId.isValid(orderId)) {
|
||||||
$or: [
|
return {
|
||||||
{ _id: orderId },
|
success: false,
|
||||||
{ orderNo: orderId }
|
error: `Invalid record ID: ${orderId}. A valid record _id is required.`,
|
||||||
]
|
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const record = await DBModel.EscortRecord.findOne(query);
|
const record = await DBModel.EscortRecord.findById(orderId);
|
||||||
if (!record) {
|
if (!record) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
@@ -79,11 +80,11 @@ const escortRecordSetTool = tool(
|
|||||||
{
|
{
|
||||||
name: "escort_record_set",
|
name: "escort_record_set",
|
||||||
description:
|
description:
|
||||||
"Update escort record fields by order ID (_id or orderNo). Supports updating status, notes (patientNote, escortNote, medicalSummary), and payment (totalFee, paidFee, status). Only provided fields will be updated.",
|
"Update escort record fields by record ID (_id). Supports updating status, notes (patientNote, escortNote, medicalSummary), and payment (totalFee, paidFee, status). Only provided fields will be updated.",
|
||||||
schema: z.object({
|
schema: z.object({
|
||||||
orderId: z
|
orderId: z
|
||||||
.string()
|
.string()
|
||||||
.describe("Order ID (_id or orderNo) used as the lookup key"),
|
.describe("Record ID (_id) used as the lookup key"),
|
||||||
status: z
|
status: z
|
||||||
.enum(["pending", "confirmed", "in_progress", "completed", "cancelled"])
|
.enum(["pending", "confirmed", "in_progress", "completed", "cancelled"])
|
||||||
.optional()
|
.optional()
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { TavilyExtract } from "@langchain/tavily";
|
import { TavilyExtract } from "@langchain/tavily";
|
||||||
import { tool } from "@langchain/core/tools";
|
import { tool } from "@langchain/core/tools";
|
||||||
import * as z from "zod"
|
import * as z from "zod"
|
||||||
|
import config from "../../../../conf.json" with { type: "json" };
|
||||||
|
|
||||||
const webFetchTool = tool(
|
const webFetchTool = tool(
|
||||||
async ({
|
async ({
|
||||||
@@ -10,7 +11,7 @@ const webFetchTool = tool(
|
|||||||
urls = [],
|
urls = [],
|
||||||
}) => {
|
}) => {
|
||||||
const tavilyExtract = new TavilyExtract({
|
const tavilyExtract = new TavilyExtract({
|
||||||
tavilyApiKey: process.env.TAVILY_API_KEY,
|
tavilyApiKey: config.agent.tavily.apiKey,
|
||||||
extractDepth,
|
extractDepth,
|
||||||
includeImages,
|
includeImages,
|
||||||
format
|
format
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { TavilySearch } from "@langchain/tavily";
|
import { TavilySearch } from "@langchain/tavily";
|
||||||
import { tool } from "@langchain/core/tools";
|
import { tool } from "@langchain/core/tools";
|
||||||
import * as z from "zod"
|
import * as z from "zod"
|
||||||
|
import config from "../../../../conf.json" with { type: "json" };
|
||||||
|
|
||||||
const webSearchTool = tool(
|
const webSearchTool = tool(
|
||||||
async ({
|
async ({
|
||||||
@@ -11,7 +12,7 @@ const webSearchTool = tool(
|
|||||||
}) => {
|
}) => {
|
||||||
const tavilySearch = new TavilySearch({
|
const tavilySearch = new TavilySearch({
|
||||||
maxResults,
|
maxResults,
|
||||||
tavilyApiKey: process.env.TAVILY_API_KEY,
|
tavilyApiKey: config.agent.tavily.apiKey,
|
||||||
includeRawContent,
|
includeRawContent,
|
||||||
topic,
|
topic,
|
||||||
});
|
});
|
||||||
|
|||||||
+27
-2
@@ -1,4 +1,5 @@
|
|||||||
import 'dotenv/config';
|
import 'dotenv/config';
|
||||||
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { createDeepAgent, FilesystemBackend, CompositeBackend } from "deepagents";
|
import { createDeepAgent, FilesystemBackend, CompositeBackend } from "deepagents";
|
||||||
import { ChatOpenAI } from "@langchain/openai";
|
import { ChatOpenAI } from "@langchain/openai";
|
||||||
@@ -7,6 +8,10 @@ import { HumanMessage } from "@langchain/core/messages";
|
|||||||
import { ChatDeepSeek } from "@langchain/deepseek";
|
import { ChatDeepSeek } from "@langchain/deepseek";
|
||||||
import config from '../../conf.json' with { type: 'json' };
|
import config from '../../conf.json' with { type: 'json' };
|
||||||
import logger from '../../utils/logger.js';
|
import logger from '../../utils/logger.js';
|
||||||
|
|
||||||
|
// 密钥由 conf.json 统一提供;百度地图 skill 通过 env_get 读环境变量,此处桥接
|
||||||
|
process.env.TAVILY_API_KEY ??= config.agent.tavily?.apiKey;
|
||||||
|
process.env.BAIDU_MAP_AUTH_TOKEN ??= config.agent.baiduMap?.authToken;
|
||||||
import Prompts from "./prompts.js";
|
import Prompts from "./prompts.js";
|
||||||
import {
|
import {
|
||||||
getEnvTool, webFetchTool, webSearchTool, getCalendarInfoTool,
|
getEnvTool, webFetchTool, webSearchTool, getCalendarInfoTool,
|
||||||
@@ -19,6 +24,7 @@ export default class EscortAgent {
|
|||||||
static MAX_HISTORY_CHARS = config.agent.maxHistoryChars || 40000;
|
static MAX_HISTORY_CHARS = config.agent.maxHistoryChars || 40000;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
this.messages = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
clearMessages() {
|
clearMessages() {
|
||||||
@@ -137,12 +143,19 @@ export default class EscortAgent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_genAgent(userInfo) {
|
_genAgent(userInfo) {
|
||||||
|
const rootDir = process.cwd();
|
||||||
|
const memoryFile = userInfo
|
||||||
|
? path.join(rootDir, "data", userInfo._id, "memories", "user_memory.txt")
|
||||||
|
: null;
|
||||||
|
|
||||||
|
// 会话中记忆文件被更新后重建 agent,使 systemPrompt 中的用户记忆保持最新
|
||||||
|
if (this.agent && this._memoryMtime !== this._statMemory(memoryFile)) {
|
||||||
|
this.agent = null;
|
||||||
|
}
|
||||||
if (this.agent) {
|
if (this.agent) {
|
||||||
return this.agent;
|
return this.agent;
|
||||||
}
|
}
|
||||||
|
|
||||||
const rootDir = process.cwd();
|
|
||||||
this.messages = [];
|
|
||||||
let backend = new FilesystemBackend({ rootDir });
|
let backend = new FilesystemBackend({ rootDir });
|
||||||
|
|
||||||
if (userInfo) {
|
if (userInfo) {
|
||||||
@@ -164,6 +177,8 @@ export default class EscortAgent {
|
|||||||
temperature: 0.0
|
temperature: 0.0
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this._memoryMtime = this._statMemory(memoryFile);
|
||||||
|
|
||||||
this.agent = createDeepAgent({
|
this.agent = createDeepAgent({
|
||||||
name: "deep-agent",
|
name: "deep-agent",
|
||||||
model: this.flashModel,
|
model: this.flashModel,
|
||||||
@@ -178,4 +193,14 @@ export default class EscortAgent {
|
|||||||
|
|
||||||
return this.agent;
|
return this.agent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 返回记忆文件 mtime,文件不存在时返回 null
|
||||||
|
_statMemory(memoryFile) {
|
||||||
|
if (!memoryFile) return null;
|
||||||
|
try {
|
||||||
|
return fs.statSync(memoryFile).mtimeMs;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import moment from "moment";
|
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import services from "../../resource/services.js";
|
import services from "../../resource/services.js";
|
||||||
import agreement from "../../resource/agreement.js";
|
import agreement from "../../resource/agreement.js";
|
||||||
|
import logger from "../../utils/logger.js";
|
||||||
|
|
||||||
class Prompts {
|
class Prompts {
|
||||||
static buildSystemPrompt(userInfo) {
|
static buildSystemPrompt(userInfo) {
|
||||||
@@ -24,7 +24,7 @@ class Prompts {
|
|||||||
try {
|
try {
|
||||||
usermem_str = fs.readFileSync(userMemoryPath, 'utf8');
|
usermem_str = fs.readFileSync(userMemoryPath, 'utf8');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log('读取用户记忆失败', err);
|
logger.error('读取用户记忆失败', err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,24 +1,33 @@
|
|||||||
import { tool } from "langchain"
|
import { tool } from "langchain"
|
||||||
|
import lunarLib from "lunar-javascript"
|
||||||
|
const { Solar } = lunarLib
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 特定日期日历工具
|
* 特定日期日历工具
|
||||||
* 提供指定日期的日历信息
|
* 基于 lunar-javascript 提供指定日期的完整农历信息
|
||||||
*/
|
*/
|
||||||
export const getLunarCalendarInfoTool = tool(
|
export const getLunarCalendarInfoTool = tool(
|
||||||
async ({ year, month, day }) => {
|
async ({ year, month, day }) => {
|
||||||
try {
|
try {
|
||||||
const date = new Date(year, month - 1, day)
|
const solar = Solar.fromYmd(year, month, day)
|
||||||
|
const lunar = solar.getLunar()
|
||||||
|
|
||||||
if (isNaN(date.getTime())) {
|
const result = {
|
||||||
return JSON.stringify({ error: '无效的日期' }, null, 2)
|
solar: solar.toString(),
|
||||||
|
weekday: `星期${solar.getWeekInChinese()}`,
|
||||||
|
lunar: lunar.toString(),
|
||||||
|
ganzhiYear: lunar.getYearInGanZhi(),
|
||||||
|
zodiac: lunar.getYearShengXiao(),
|
||||||
|
solarTerms: lunar.getJieQi() || null,
|
||||||
|
festivals: [...solar.getFestivals(), ...lunar.getFestivals()],
|
||||||
|
yi: lunar.getDayYi(),
|
||||||
|
ji: lunar.getDayJi(),
|
||||||
|
xiShen: lunar.getDayPositionXiDesc(),
|
||||||
|
caiShen: lunar.getDayPositionCaiDesc()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取农历信息
|
return JSON.stringify(result, null, 2)
|
||||||
const lunarInfo = {}
|
|
||||||
|
|
||||||
return JSON.stringify(lunarInfo, null, 2)
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in date calendar tool:', error)
|
|
||||||
return JSON.stringify({ error: error.message }, null, 2)
|
return JSON.stringify({ error: error.message }, null, 2)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -40,7 +40,8 @@ export async function getAccurateTime() {
|
|||||||
for (const source of timeSources) {
|
for (const source of timeSources) {
|
||||||
try {
|
try {
|
||||||
const startTime = Date.now()
|
const startTime = Date.now()
|
||||||
const res = await fetch(source.url)
|
// 每个时间源限时 3 秒,防止单个源 TCP 挂起长时间阻塞
|
||||||
|
const res = await fetch(source.url, { signal: AbortSignal.timeout(3000) })
|
||||||
const latency = Date.now() - startTime
|
const latency = Date.now() - startTime
|
||||||
|
|
||||||
let timestamp = null
|
let timestamp = null
|
||||||
|
|||||||
@@ -1,30 +1,41 @@
|
|||||||
import { tool } from "langchain"
|
import { tool } from "langchain"
|
||||||
|
import lunarLib from "lunar-javascript"
|
||||||
|
const { Solar, HolidayUtil } = lunarLib
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 年份节日列表工具
|
* 年份节日列表工具
|
||||||
* 查询给定年份节日所在日期列表
|
* 基于 lunar-javascript 计算全年节日(公历节日 + 农历传统节日)与法定节假日安排
|
||||||
*/
|
*/
|
||||||
export const getYearHolidaysTool = tool(
|
export const getYearHolidaysTool = tool(
|
||||||
async ({ year }) => {
|
async ({ year }) => {
|
||||||
try {
|
try {
|
||||||
// 简化实现,返回基本节日信息
|
// 法定节假日安排(含调休补班)
|
||||||
const holidays = [
|
const legal = HolidayUtil.getHolidays(year).map(h => ({
|
||||||
{ date: `${year}-01-01`, name: '元旦' },
|
date: h.getTarget().toString(),
|
||||||
{ date: `${year}-02-14`, name: '情人节' },
|
name: h.getName(),
|
||||||
{ date: `${year}-05-01`, name: '劳动节' },
|
type: h.isWork() ? "调休上班" : "放假"
|
||||||
{ date: `${year}-06-01`, name: '儿童节' },
|
}))
|
||||||
{ date: `${year}-10-01`, name: '国庆节' }
|
|
||||||
]
|
|
||||||
|
|
||||||
return JSON.stringify({ year, holidays }, null, 2)
|
// 遍历全年日期,收集公历节日与农历传统节日(春节、中秋、端午等)
|
||||||
|
const festivals = []
|
||||||
|
const start = Solar.fromYmd(year, 1, 1)
|
||||||
|
const days = Solar.fromYmd(year + 1, 1, 1).subtract(start)
|
||||||
|
for (let i = 0; i < days; i++) {
|
||||||
|
const d = start.next(i)
|
||||||
|
const names = [...d.getFestivals(), ...d.getLunar().getFestivals()]
|
||||||
|
if (names.length) {
|
||||||
|
festivals.push({ date: d.toString(), name: names.join("、") })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.stringify({ year, legal, festivals }, null, 2)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in year holidays tool:', error)
|
|
||||||
return JSON.stringify({ error: error.message }, null, 2)
|
return JSON.stringify({ error: error.message }, null, 2)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "get_year_holidays",
|
name: "get_year_holidays",
|
||||||
description: "查询给定年份节日所在日期列表",
|
description: "查询给定年份节日所在日期列表,包括法定节假日安排(含调休)与传统节日",
|
||||||
schema: {
|
schema: {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
|
|||||||
@@ -1,43 +1,29 @@
|
|||||||
import { tool } from "langchain"
|
import { tool } from "langchain"
|
||||||
|
import lunarLib from "lunar-javascript"
|
||||||
|
const { Lunar } = lunarLib
|
||||||
|
|
||||||
|
// 当年冬至在节气表中的 key 为英文(上一年冬至占用中文 key)
|
||||||
|
const EN_TERM_NAMES = { DONG_ZHI: "冬至" }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 年份节气列表工具
|
* 年份节气列表工具
|
||||||
* 查询给定年份节气所在日期列表
|
* 基于 lunar-javascript 计算给定年份的 24 节气精确日期
|
||||||
*/
|
*/
|
||||||
export const getYearTermsTool = tool(
|
export const getYearTermsTool = tool(
|
||||||
async ({ year }) => {
|
async ({ year }) => {
|
||||||
try {
|
try {
|
||||||
// 简化实现,返回24节气信息
|
const table = Lunar.fromYmd(year, 1, 1).getJieQiTable()
|
||||||
const terms = [
|
const terms = []
|
||||||
{ date: `${year}-02-04`, name: '立春' },
|
for (const [key, solar] of Object.entries(table)) {
|
||||||
{ date: `${year}-02-19`, name: '雨水' },
|
// 只保留属于目标年份的节气(表首含上一年边界节气)
|
||||||
{ date: `${year}-03-05`, name: '惊蛰' },
|
if (solar.getYear() !== year) continue
|
||||||
{ date: `${year}-03-20`, name: '春分' },
|
const name = EN_TERM_NAMES[key] ?? key
|
||||||
{ date: `${year}-04-04`, name: '清明' },
|
terms.push({ date: solar.toString(), name })
|
||||||
{ date: `${year}-04-19`, name: '谷雨' },
|
}
|
||||||
{ date: `${year}-05-05`, name: '立夏' },
|
terms.sort((a, b) => a.date.localeCompare(b.date))
|
||||||
{ date: `${year}-05-20`, name: '小满' },
|
|
||||||
{ date: `${year}-06-05`, name: '芒种' },
|
|
||||||
{ date: `${year}-06-21`, name: '夏至' },
|
|
||||||
{ date: `${year}-07-07`, name: '小暑' },
|
|
||||||
{ date: `${year}-07-22`, name: '大暑' },
|
|
||||||
{ date: `${year}-08-07`, name: '立秋' },
|
|
||||||
{ date: `${year}-08-23`, name: '处暑' },
|
|
||||||
{ date: `${year}-09-07`, name: '白露' },
|
|
||||||
{ date: `${year}-09-23`, name: '秋分' },
|
|
||||||
{ date: `${year}-10-08`, name: '寒露' },
|
|
||||||
{ date: `${year}-10-23`, name: '霜降' },
|
|
||||||
{ date: `${year}-11-07`, name: '立冬' },
|
|
||||||
{ date: `${year}-11-22`, name: '小雪' },
|
|
||||||
{ date: `${year}-12-07`, name: '大雪' },
|
|
||||||
{ date: `${year}-12-21`, name: '冬至' },
|
|
||||||
{ date: `${year + 1}-01-05`, name: '小寒' },
|
|
||||||
{ date: `${year + 1}-01-20`, name: '大寒' }
|
|
||||||
]
|
|
||||||
|
|
||||||
return JSON.stringify({ year, terms }, null, 2)
|
return JSON.stringify({ year, terms }, null, 2)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in year terms tool:', error)
|
|
||||||
return JSON.stringify({ error: error.message }, null, 2)
|
return JSON.stringify({ error: error.message }, null, 2)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { tool } from "@langchain/core/tools";
|
import { tool } from "@langchain/core/tools";
|
||||||
import z from "zod";
|
import z from "zod";
|
||||||
|
import mongoose from "mongoose";
|
||||||
import { DBModel } from "../../../../models/index.js";
|
import { DBModel } from "../../../../models/index.js";
|
||||||
|
|
||||||
const escortRecordSetTool = tool(
|
const escortRecordSetTool = tool(
|
||||||
@@ -8,18 +9,18 @@ const escortRecordSetTool = tool(
|
|||||||
if (!orderId) {
|
if (!orderId) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: "Order ID (_id or orderNo) is required as the lookup key",
|
error: "Record ID (_id) is required as the lookup key",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const query = {
|
if (!mongoose.Types.ObjectId.isValid(orderId)) {
|
||||||
$or: [
|
return {
|
||||||
{ _id: orderId },
|
success: false,
|
||||||
{ orderNo: orderId }
|
error: `Invalid record ID: ${orderId}. A valid record _id is required.`,
|
||||||
]
|
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const record = await DBModel.EscortRecord.findOne(query);
|
const record = await DBModel.EscortRecord.findById(orderId);
|
||||||
if (!record) {
|
if (!record) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
@@ -79,11 +80,11 @@ const escortRecordSetTool = tool(
|
|||||||
{
|
{
|
||||||
name: "escort_record_set",
|
name: "escort_record_set",
|
||||||
description:
|
description:
|
||||||
"Update escort record fields by order ID (_id or orderNo). Supports updating status, notes (patientNote, escortNote, medicalSummary), and payment (totalFee, paidFee, status). Only provided fields will be updated.",
|
"Update escort record fields by record ID (_id). Supports updating status, notes (patientNote, escortNote, medicalSummary), and payment (totalFee, paidFee, status). Only provided fields will be updated.",
|
||||||
schema: z.object({
|
schema: z.object({
|
||||||
orderId: z
|
orderId: z
|
||||||
.string()
|
.string()
|
||||||
.describe("Order ID (_id or orderNo) used as the lookup key"),
|
.describe("Record ID (_id) used as the lookup key"),
|
||||||
status: z
|
status: z
|
||||||
.enum(["pending", "confirmed", "in_progress", "completed", "cancelled"])
|
.enum(["pending", "confirmed", "in_progress", "completed", "cancelled"])
|
||||||
.optional()
|
.optional()
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { TavilyExtract } from "@langchain/tavily";
|
import { TavilyExtract } from "@langchain/tavily";
|
||||||
import { tool } from "@langchain/core/tools";
|
import { tool } from "@langchain/core/tools";
|
||||||
import * as z from "zod"
|
import * as z from "zod"
|
||||||
|
import config from "../../../../conf.json" with { type: "json" };
|
||||||
|
|
||||||
const webFetchTool = tool(
|
const webFetchTool = tool(
|
||||||
async ({
|
async ({
|
||||||
@@ -10,7 +11,7 @@ const webFetchTool = tool(
|
|||||||
urls = [],
|
urls = [],
|
||||||
}) => {
|
}) => {
|
||||||
const tavilyExtract = new TavilyExtract({
|
const tavilyExtract = new TavilyExtract({
|
||||||
tavilyApiKey: process.env.TAVILY_API_KEY,
|
tavilyApiKey: config.agent.tavily.apiKey,
|
||||||
extractDepth,
|
extractDepth,
|
||||||
includeImages,
|
includeImages,
|
||||||
format
|
format
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { TavilySearch } from "@langchain/tavily";
|
import { TavilySearch } from "@langchain/tavily";
|
||||||
import { tool } from "@langchain/core/tools";
|
import { tool } from "@langchain/core/tools";
|
||||||
import * as z from "zod"
|
import * as z from "zod"
|
||||||
|
import config from "../../../../conf.json" with { type: "json" };
|
||||||
|
|
||||||
const webSearchTool = tool(
|
const webSearchTool = tool(
|
||||||
async ({
|
async ({
|
||||||
@@ -11,7 +12,7 @@ const webSearchTool = tool(
|
|||||||
}) => {
|
}) => {
|
||||||
const tavilySearch = new TavilySearch({
|
const tavilySearch = new TavilySearch({
|
||||||
maxResults,
|
maxResults,
|
||||||
tavilyApiKey: process.env.TAVILY_API_KEY,
|
tavilyApiKey: config.agent.tavily.apiKey,
|
||||||
includeRawContent,
|
includeRawContent,
|
||||||
topic,
|
topic,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -17,6 +17,12 @@
|
|||||||
"apiKey": "ed5f2ec42cb9413f87402d38321553f8.u0FalmfLdXxmst69",
|
"apiKey": "ed5f2ec42cb9413f87402d38321553f8.u0FalmfLdXxmst69",
|
||||||
"flashModel": "glm-5.3-flash",
|
"flashModel": "glm-5.3-flash",
|
||||||
"proModel": "glm-5.3"
|
"proModel": "glm-5.3"
|
||||||
|
},
|
||||||
|
"tavily": {
|
||||||
|
"apiKey": "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"
|
||||||
|
},
|
||||||
|
"baiduMap": {
|
||||||
|
"authToken": "sk-ap-9xcqwNJ3FyJGUoAoQoCgWLqPd5o2tJKCA1xaSMRaZT0zDo0PCFm7rczL4anUuXf5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mongodb": {
|
"mongodb": {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ class HandlerHealthProfile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 白名单:只允许写入 schema 定义的顶层字段(兼容 'profile.name' 等点号路径)
|
// 白名单:只允许写入 schema 定义的顶层字段(兼容 'profile.name' 等点号路径)
|
||||||
static PROFILE_FIELDS = ["userId", "profile", "health"];
|
static PROFILE_FIELDS = ["userId", "profile", "location", "health"];
|
||||||
|
|
||||||
static pickFields(body) {
|
static pickFields(body) {
|
||||||
const picked = {};
|
const picked = {};
|
||||||
|
|||||||
@@ -22,7 +22,16 @@ const HealthProfileSchema = mongoose.Schema(
|
|||||||
mobile: { type: String, default: "", index: true, comment: "患者电话" },
|
mobile: { type: String, default: "", index: true, comment: "患者电话" },
|
||||||
sex: { type: String, enum: ["male", "female", ""], default: "", comment: "性别" },
|
sex: { type: String, enum: ["male", "female", ""], default: "", comment: "性别" },
|
||||||
birth: { type: String, default: "", comment: "出生年月(YYYY-MM-DD)" },
|
birth: { type: String, default: "", comment: "出生年月(YYYY-MM-DD)" },
|
||||||
idnumber: { type: String, default: "", comment: "身份证号" },
|
idnumber: { type: String, default: "", comment: "证件号(身份证/护照等)" },
|
||||||
|
},
|
||||||
|
|
||||||
|
// 所在地(国外用户填国家;国内用户填省市区 + 详细地址)
|
||||||
|
location: {
|
||||||
|
country: { type: String, default: "中国", comment: "国家" },
|
||||||
|
province: { type: String, default: "", comment: "省" },
|
||||||
|
city: { type: String, default: "", comment: "市" },
|
||||||
|
district: { type: String, default: "", comment: "区/县" },
|
||||||
|
address: { type: String, default: "", comment: "详细地址" },
|
||||||
},
|
},
|
||||||
|
|
||||||
// 健康信息
|
// 健康信息
|
||||||
|
|||||||
Generated
+125
-826
File diff suppressed because it is too large
Load Diff
+8
-9
@@ -9,21 +9,20 @@
|
|||||||
"dev": "nodemon index.js"
|
"dev": "nodemon index.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@langchain/anthropic": "^1.4.0",
|
"@langchain/anthropic": "^1.5.9",
|
||||||
"@langchain/community": "^1.1.28",
|
"@langchain/core": "^1.2.9",
|
||||||
"@langchain/core": "^1.1.48",
|
"@langchain/deepseek": "^1.1.11",
|
||||||
"@langchain/deepseek": "^1.0.27",
|
"@langchain/langgraph": "^1.4.13",
|
||||||
"@langchain/langgraph": "^1.3.2",
|
"@langchain/openai": "^1.5.11",
|
||||||
"@langchain/openai": "^1.4.7",
|
|
||||||
"@langchain/tavily": "^1.2.0",
|
"@langchain/tavily": "^1.2.0",
|
||||||
"bcrypt": "^5.1.1",
|
"bcrypt": "^5.1.1",
|
||||||
"deepagents": "^1.10.2",
|
"deepagents": "^1.13.2",
|
||||||
"dotenv": "^17.4.2",
|
"dotenv": "^17.4.2",
|
||||||
"koa": "^2.16.4",
|
"koa": "^2.16.4",
|
||||||
"koa-bodyparser": "^4.4.1",
|
"koa-bodyparser": "^4.4.1",
|
||||||
"koa-cors": "^0.0.16",
|
"koa-cors": "^0.0.16",
|
||||||
"koa-router": "^12.0.1",
|
"koa-router": "^12.0.1",
|
||||||
"langchain": "^1.4.2",
|
"langchain": "^1.5.10",
|
||||||
"lodash": "^4.18.1",
|
"lodash": "^4.18.1",
|
||||||
"lunar-javascript": "^1.7.7",
|
"lunar-javascript": "^1.7.7",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
@@ -31,7 +30,7 @@
|
|||||||
"node-fetch": "^3.3.2",
|
"node-fetch": "^3.3.2",
|
||||||
"winston": "^3.19.0",
|
"winston": "^3.19.0",
|
||||||
"ws": "^8.21.0",
|
"ws": "^8.21.0",
|
||||||
"zod": "^4.4.3"
|
"zod": "^4.5.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"nodemon": "^3.0.2"
|
"nodemon": "^3.0.2"
|
||||||
|
|||||||
@@ -97,6 +97,11 @@ export default class WebSocketServerManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (msg.agent === 'escort-admin') {
|
if (msg.agent === 'escort-admin') {
|
||||||
|
// 管理端 agent 含写库/环境变量等高危工具,必须校验管理员角色,防止普通用户越权
|
||||||
|
if (!userInfo.app || !("wxapp-escort-admin" in userInfo.app)) {
|
||||||
|
ws.send(JSON.stringify({ type: 'error', content: '无管理员权限' }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
await adminAgent.streamChat(userInfo, [msg], (source, type, content, id) => {
|
await adminAgent.streamChat(userInfo, [msg], (source, type, content, id) => {
|
||||||
send(source, type, content, id);
|
send(source, type, content, id);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user