完善了agent

This commit is contained in:
lik
2026-05-30 16:41:26 +08:00
parent 797e69a0c2
commit b92853a717
30 changed files with 1972 additions and 22 deletions

1
.env
View File

@@ -1,2 +1,3 @@
TAVILY_API_KEY=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 TAVILY_API_KEY=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
DEEPSEEK_API_KEY=sk-a58ccd82b7ba4ce3ac176a88c9381095 DEEPSEEK_API_KEY=sk-a58ccd82b7ba4ce3ac176a88c9381095
BAIDU_MAP_AUTH_TOKEN=sk-ap-9xcqwNJ3FyJGUoAoQoCgWLqPd5o2tJKCA1xaSMRaZT0zDo0PCFm7rczL4anUuXf5

View File

@@ -1,10 +1,14 @@
import 'dotenv/config'; import 'dotenv/config';
import { createDeepAgent } from "deepagents"; import { createDeepAgent, FilesystemBackend } from "deepagents";
import { ChatOpenAI } from "@langchain/openai"; import { ChatOpenAI } from "@langchain/openai";
import { AIMessageChunk, ToolMessage } from "langchain"; import { AIMessageChunk, ToolMessage } from "langchain";
import { SystemMessage, HumanMessage, AIMessage } from "@langchain/core/messages"; import { SystemMessage, HumanMessage, AIMessage } from "@langchain/core/messages";
import { ChatDeepSeek } from "@langchain/deepseek"; import { ChatDeepSeek } from "@langchain/deepseek";
import Prompts from "./prompts.js"; import Prompts from "./prompts.js";
import { getEnvTool,webFetchTool, webSearchTool, getCalendarInfoTool,
getLunarCalendarInfoTool, getYearHolidaysTool, getYearTermsTool, getLatLngTool,
httpGetTool, httpPostTool
} from "./tools/index.js";
export default class EscortAgent { export default class EscortAgent {
constructor() { constructor() {
@@ -25,7 +29,7 @@ export default class EscortAgent {
if (msg.type === "clear") { if (msg.type === "clear") {
this.messages = []; this.messages = [];
} else { } else {
this.messages.push(new HumanMessage(msg.content)); this.messages.push(new HumanMessage(`${msg.ts} - ${msg.content}`));
} }
}); });
@@ -99,6 +103,9 @@ export default class EscortAgent {
this.messages = []; this.messages = [];
const rootDir = process.cwd();
const backend = new FilesystemBackend({ rootDir });
this.model = new ChatDeepSeek({ this.model = new ChatDeepSeek({
model: 'deepseek-v4-pro', model: 'deepseek-v4-pro',
apiKey: 'sk-a58ccd82b7ba4ce3ac176a88c9381095', apiKey: 'sk-a58ccd82b7ba4ce3ac176a88c9381095',
@@ -108,7 +115,11 @@ export default class EscortAgent {
this.agent = createDeepAgent({ this.agent = createDeepAgent({
name: "deep-agent", name: "deep-agent",
model: this.model, model: this.model,
systemPrompt: Prompts.buildSystemPrompt(userInfo) systemPrompt: Prompts.buildSystemPrompt(userInfo),
backend,
tools: [getEnvTool, webFetchTool, webSearchTool, getLatLngTool, httpGetTool, httpPostTool,
getCalendarInfoTool, getLunarCalendarInfoTool, getYearHolidaysTool, getYearTermsTool],
skills: ["./agent/skills/"]
}); });
return this.agent; return this.agent;

View File

@@ -1,5 +1,6 @@
import moment from "moment"; import moment from "moment";
import services from "../resource/services.js"; import services from "../resource/services.js";
import agreement from "../resource/agreement.js";
class Prompts { class Prompts {
static buildSystemPrompt(userInfo) { static buildSystemPrompt(userInfo) {
@@ -18,11 +19,9 @@ class Prompts {
return ` return `
# 角色定义 # 角色定义
你是小橙,一名陪诊服务顾问。温暖、直接、高效。 你是小橙,暖橙陪诊平台的AI客服温暖、直接、高效的回答用户问题
# 核心能力 # 核心能力
- 查询医院、科室、医生信息
- 创建、查询陪诊订单
- 解答服务流程、价格、注意事项 - 解答服务流程、价格、注意事项
- 提供就诊准备建议 - 提供就诊准备建议
@@ -31,24 +30,15 @@ class Prompts {
2. 聊专业问题时,要严肃专注,不偏离主题;要客观公正,绝不主观臆造;同时给用户专业真诚的反馈。 2. 聊专业问题时,要严肃专注,不偏离主题;要客观公正,绝不主观臆造;同时给用户专业真诚的反馈。
3. 聚焦用户最新问题,理解意图,高情商个性化的跟用户沟通,不要一开口就问“需要陪诊服务吗”。 3. 聚焦用户最新问题,理解意图,高情商个性化的跟用户沟通,不要一开口就问“需要陪诊服务吗”。
4. 主动追问:回答后,一句追问收尾,引导用户给出下一步关键信息。 4. 主动追问:回答后,一句追问收尾,引导用户给出下一步关键信息。
5. 输出要排版层次清晰,格式统一整洁,不要使用markdown格式 5. 输出要排版层次清晰,结构整洁
6. 医疗问题时,末尾加一句"最终以医生诊断为准" 6. 医疗问题时,提示用户最终以医生诊断为准。
7. 保护用户隐私,不泄露个人信息。 7. 保护用户隐私,不泄露个人信息。
8. 用户询问怎么加入团队或怎么合作时,首先欢迎用户加入团队,然后让用户电话或微信联系 8. 你无法回答的业务问题,要提示用户联系客服
9. 你无法回答的业务问题,要提示用户联系客服。
# 工作流程
1. 问清城市、医院、科室
2. 推荐匹配选项
3. 确认就诊时间
4. 创建订单、确认细节
5. 就诊前提醒
## 参考信息 ## 参考信息
当前日期:${moment().format("YYYY-MM-DD")};
用户信息:${userInfo_str}; 用户信息:${userInfo_str};
服务项目:${JSON.stringify(services)}; 服务项目:${JSON.stringify(services)};
服务电话: 18618162956 (微信同号) 服务协议:${JSON.stringify(agreement)};
`; `;
} }
} }

View File

@@ -0,0 +1,243 @@
---
name: baidu-ai-map
description: 为Agent提供地图和位置相关的能力例如 AI 地点检索周边信息、AI 路线规划、地理编码与逆地理编码、天气查询等地图工具。
---
# 百度地图服务 Agent Plan
提供大模型友好调用的地图工具skills/baidu-ai-map/SKILL.md支持POI搜索、导航、语义化 AI 搜索、语义化 AI 路线规划、地理编码与逆地理编码、天气查询。
## 使用准则
### 准则 1API 端点
所有能力统一使用:
> **Base URL**: `https://api.map.baidu.com/`
### 准则 2SK 凭证安全处理
SKService Key是调用所有 API 的必须凭证:
1. 优先读取环境变量 `BAIDU_MAP_AUTH_TOKEN`
2. 如果为空,提示用户前往 [百度地图 Agent Plan](https://lbs.baidu.com/apiconsole/agentplan) 申请。
3. 设置环境变量:
```bash
export BAIDU_MAP_AUTH_TOKEN="你的SK"
```
### 准则 3统一鉴权方式Header 传入)
调用所有 API 时,统一通过 Header 传入:
- `Authorization: Bearer $BAIDU_MAP_AUTH_TOKEN`
示例:
```bash
curl --get "https://api.map.baidu.com/agent_plan/v1/place" \
-H "Authorization: Bearer $BAIDU_MAP_AUTH_TOKEN" \
--data-urlencode "user_raw_request=帮我找北京可带宠物的咖啡馆" \
--data-urlencode "region=北京市"
```
### 准则 4优先使用http_get工具避免直接调用curlcurl在window下支持不好
## 全局参数与行为约束
1. `user_raw_request` 必须是完整的用户需求,不可压缩为关键词。
2. `user_raw_request` 出现“我附近”等非明确地点时,可根据上下文为用户推理为具体地点名称,但不可对坐标进行推理。
3. `user_raw_request` 需保留定语/约束词例如“评分最高”“最近”“最便宜”“3公里内”。
4. 不得编造坐标;`center` / `location` / `refer_pois` 仅可来自用户明确提供或可信来源,当无可靠坐标时,必须先向用户澄清或先调用 `geocoding` / `place` 等工具获取。
5. 统一使用 Header 鉴权:`Authorization: Bearer $BAIDU_MAP_AUTH_TOKEN`
6. 经纬度至少保留小数点后 6 位。
7. 所有工具返回坐标类型统一为 `gcj02`
8. 禁止使用 grep/sed/awk/jq/python脚本 正则裁剪响应后再推断字段,这会造成百度地图 Agent Plan 巨额的token消耗短时间内遇到相同或高度相似请求时应优先基于结果直接推理避免重复发起请求。
9. `direction` 可能返回两类结果:路线结果,或起终点澄清结果。当返回起终点澄清结果时,应先根据用户需求推理最合理候选;若仍无法唯一确定,应引导用户在候选中选择后再继续规划。
## 工具详解
### 1. Place语义化AI地点检索
#### API
`GET /agent_plan/v1/place`
#### 参数输入(给模型)
Required:
- `user_raw_request`: 用户原始需求原样完整传入不可压缩为关键词保留约束词如“评分最高”“最近”“3公里内”
- `region`: 城市或区域限制
Optional:
- `center`: 检索中心点和排序参考点(`lat,lng`gcj02
- `sort`: `distance``relevance`(默认 `relevance`
Rules:
- `sort=distance` 时,`center` 必传
- `center` 只能来自用户明确提供或可信来源,禁止推测/编造,可通过 `geocoding` / `place` 等工具获取
- 经纬度至少保留小数点后 6 位
#### 鉴权
- GETHeader `Authorization: Bearer $BAIDU_MAP_AUTH_TOKEN`
#### 示例
```bash
# 1) 帮我查一下八达岭长城附近的五星级酒店
curl --get "https://api.map.baidu.com/agent_plan/v1/place" \
-H "Authorization: Bearer $BAIDU_MAP_AUTH_TOKEN" \
--data-urlencode "user_raw_request=帮我查一下八达岭长城附近的五星级酒店" \
--data-urlencode "region=延庆区" \
--data-urlencode "sort=relevance"
# 2) 离我最近的火锅店distance 排序)
curl --get "https://api.map.baidu.com/agent_plan/v1/place" \
-H "Authorization: Bearer $BAIDU_MAP_AUTH_TOKEN" \
--data-urlencode "user_raw_request=离我最近的火锅店" \
--data-urlencode "region=北京市" \
--data-urlencode "center=40.056800,116.308300" \
--data-urlencode "sort=distance"
```
### 2. Direction语义化AI路线规划
#### API
`GET /agent_plan/v1/direction`
#### 参数输入
Required:
- `user_raw_request`: 用户原始需求,包含起点和终点;保留路线约束词,需要推理并改写用户原始的交通方式
- `location`: 用户当前位置坐标(`lat,lng`gcj02
Optional:
- `refer_pois`: 地点精确映射,格式 `地点名称:uid,纬度,经度;地点名称:uid,纬度,经度`
Rules:
- 当前仅支持驾车、步行、骑行、公交路线规划。`user_raw_request` 中出现其他交通方式时,需要改写到这四种交通方式
- `location` 当起点有歧义(如同名地标、模糊起点)时,路线规划服务会基于当前位置推理最合理的起点位置
- `refer_pois` 默认不传,用于同名地点消歧,仅在 `user_raw_request` 中明确有歧义地点时传入
- `refer_pois` / `location` 的经纬度至少保留小数点后 6 位,禁止推测/编造,可通过 `geocoding` / `place` 等工具获取
#### 鉴权
- GETHeader `Authorization: Bearer $BAIDU_MAP_AUTH_TOKEN`
#### 示例
```bash
# 1) 帮我规划从故宫到颐和园的驾车路线
curl --get "https://api.map.baidu.com/agent_plan/v1/direction" \
-H "Authorization: Bearer $BAIDU_MAP_AUTH_TOKEN" \
--data-urlencode "user_raw_request=帮我规划从故宫到颐和园的驾车路线" \
--data-urlencode "location=39.914590,116.403770"
# 2) “我家”别名映射
curl --get "https://api.map.baidu.com/agent_plan/v1/direction" \
-H "Authorization: Bearer $BAIDU_MAP_AUTH_TOKEN" \
--data-urlencode "user_raw_request=步行去我家附近最近的中餐厅" \
--data-urlencode "location=40.056800,116.308300" \
--data-urlencode "refer_pois=我家:fbc88a21464370106e3e1b52,40.092180,116.345310"
# 2) 交通方式推理改写:从王府井打车去三里屯要多久
curl --get "https://api.map.baidu.com/agent_plan/v1/direction" \
-H "Authorization: Bearer $BAIDU_MAP_AUTH_TOKEN" \
--data-urlencode "user_raw_request=从王府井驾车去三里屯要多久" \
--data-urlencode "location=39.914590,116.403770"
```
### 3. Geocoding地理编码
#### API
`GET /agent_plan/v1/geocoding`
#### 参数输入
Required:
- `address`: 要解析的完整地址
Optional:
- `region`: 城市/区域提示(减少歧义)
Rules:
- 地址越完整,解析越稳定
#### 鉴权
- GETHeader `Authorization: Bearer $BAIDU_MAP_AUTH_TOKEN`
#### 示例
```bash
curl --get "https://api.map.baidu.com/agent_plan/v1/geocoding" \
-H "Authorization: Bearer $BAIDU_MAP_AUTH_TOKEN" \
--data-urlencode "address=北京市海淀区上地十街10号百度大厦" \
--data-urlencode "region=北京市"
```
### 4. Reverse Geocoding逆地理编码
#### API
`GET /agent_plan/v1/reverse_geocoding`
#### 参数输入
Required:
- `location`: `lat,lng` 格式坐标gcj02
Rules:
- 经纬度至少保留小数点后 6 位
#### 鉴权
- GETHeader `Authorization: Bearer $BAIDU_MAP_AUTH_TOKEN`
#### 示例
```bash
curl --get "https://api.map.baidu.com/agent_plan/v1/reverse_geocoding" \
-H "Authorization: Bearer $BAIDU_MAP_AUTH_TOKEN" \
--data-urlencode "location=40.056800,116.308300"
```
### 5. Weather天气查询
#### API
`GET /agent_plan/v1/weather`
#### 参数输入
Optional:
- `region`: 行政区划名称
- `location`: `lat,lng` 格式坐标gcj02
Rules:
- `region``location` 至少传一个
- `location` 传入时,经纬度至少保留小数点后 6 位
#### 鉴权
- GETHeader `Authorization: Bearer $BAIDU_MAP_AUTH_TOKEN`
#### 示例
```bash
# 1) 按坐标查询天气
curl --get "https://api.map.baidu.com/agent_plan/v1/weather" \
-H "Authorization: Bearer $BAIDU_MAP_AUTH_TOKEN" \
--data-urlencode "location=38.766230,116.432130"
```
## 错误处理
1. 如果缺少 token提示用户执行
申请地址https://lbs.baidu.com/apiconsole/agentplan
`export BAIDU_MAP_AUTH_TOKEN="<YOUR_BAIDU_MAP_AUTH_TOKEN>"`
2. 如果提示参数错误重新阅读本Skills查阅如何传参

View File

@@ -0,0 +1,6 @@
{
"ownerId": "kn778b6bf766ym71fdvrc5yvg98319rv",
"slug": "baidu-ai-map",
"version": "1.0.6",
"publishedAt": 1776928968682
}

View File

@@ -0,0 +1,42 @@
import { tool } from "langchain"
import lunar from "lunar-javascript"
const { Solar, Lunar, HolidayUtil, TermUtil } = lunar
import { getAccurateTime } from "./utils.js"
/**
* 日历工具
* 提供准确时间、农历信息、节气、节日和老黄历等信息
*/
export const getCalendarInfoTool = tool(
async () => {
try {
// 获取准确时间
const timeInfo = await getAccurateTime()
// 获取农历信息
const date = new Date(timeInfo.timestamp)
const solar = Solar.fromDate(date)
const lunar = solar.getLunar()
// 组合返回结果
const result = {
time: timeInfo,
weekday: solar.getWeek(),
lunar: {
lunar: lunar.toString(),
solar: solar.toString(),
}
}
return JSON.stringify(result, null, 2)
} catch (error) {
console.error('Error in calendar tool:', error)
return JSON.stringify({ error: error.message }, null, 2)
}
},
{
name: "get_calendar_info",
description: "获取当前准确日期时间,以及对应的农历日期"
}
)

View File

@@ -0,0 +1,53 @@
import { tool } from "langchain"
/**
* 特定日期日历工具
* 提供指定日期的日历信息
*/
export const getLunarCalendarInfoTool = tool(
async ({ year, month, day }) => {
try {
const date = new Date(year, month - 1, day)
if (isNaN(date.getTime())) {
return JSON.stringify({ error: '无效的日期' }, null, 2)
}
// 获取农历信息
const lunarInfo = {}
return JSON.stringify(lunarInfo, null, 2)
} catch (error) {
console.error('Error in date calendar tool:', error)
return JSON.stringify({ error: error.message }, null, 2)
}
},
{
name: "get_lunar_calendar_info",
description: "获取指定日期的日历信息,包括农历信息、老黄历、当年生肖、今日宜忌、喜神方位、财神方位、今日节气、今日节日等",
schema: {
type: "object",
properties: {
year: {
type: "number",
description: "年份",
minimum: 1900,
maximum: 2100
},
month: {
type: "number",
description: "月份",
minimum: 1,
maximum: 12
},
day: {
type: "number",
description: "日期",
minimum: 1,
maximum: 31
}
},
required: ["year", "month", "day"]
}
}
)

View File

@@ -0,0 +1,86 @@
import * as z from "zod"
import { tool } from "langchain"
import lunar from "lunar-javascript"
const { Solar, Lunar, HolidayUtil, TermUtil } = lunar
/**
* 获取准确时间工具
* 通过国内权威时间源获取当前准确时间
* @returns {string} - 返回当前准确时间的JSON字符串
*/
export async function getAccurateTime() {
const timeSources = [
{
name: 'timeapi.io',
url: 'https://timeapi.io/api/Time/current/zone?timeZone=Asia/Shanghai',
method: 'json'
},
{
name: '国家授时中心',
url: 'http://www.ntsc.ac.cn',
method: 'header'
},
{
name: '阿里云',
url: 'https://www.aliyun.com',
method: 'header'
},
{
name: '腾讯云',
url: 'https://cloud.tencent.com',
method: 'header'
},
{
name: '华为云',
url: 'https://www.huaweicloud.com',
method: 'header'
}
]
for (const source of timeSources) {
try {
const startTime = Date.now()
const res = await fetch(source.url)
const latency = Date.now() - startTime
let timestamp = null
let beijingTime = null
if (source.method === 'json') {
const data = await res.json()
timestamp = new Date(data.dateTime).getTime()
beijingTime = data.dateTime
} else if (source.method === 'header') {
const dateHeader = res.headers.get('Date')
if (dateHeader) {
timestamp = new Date(dateHeader).getTime()
beijingTime = new Date(timestamp).toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' })
}
}
if (timestamp) {
return {
timestamp,
date: new Date(timestamp).toISOString(),
localTime: beijingTime,
source: source.name,
latency: latency,
success: true
}
}
} catch (error) {
console.log(`${source.name} 获取时间失败: ${error.message}`)
continue
}
}
const date = new Date()
return {
timestamp: date.getTime(),
date: date.toISOString(),
localTime: date.toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' }),
success: false,
message: '使用本地时间作为备份'
}
}

View File

@@ -0,0 +1,41 @@
import { tool } from "langchain"
/**
* 年份节日列表工具
* 查询给定年份节日所在日期列表
*/
export const getYearHolidaysTool = tool(
async ({ year }) => {
try {
// 简化实现,返回基本节日信息
const holidays = [
{ date: `${year}-01-01`, name: '元旦' },
{ date: `${year}-02-14`, name: '情人节' },
{ date: `${year}-05-01`, name: '劳动节' },
{ date: `${year}-06-01`, name: '儿童节' },
{ date: `${year}-10-01`, name: '国庆节' }
]
return JSON.stringify({ year, holidays }, null, 2)
} catch (error) {
console.error('Error in year holidays tool:', error)
return JSON.stringify({ error: error.message }, null, 2)
}
},
{
name: "get_year_holidays",
description: "查询给定年份节日所在日期列表",
schema: {
type: "object",
properties: {
year: {
type: "number",
description: "年份",
minimum: 1900,
maximum: 2100
}
},
required: ["year"]
}
}
)

View File

@@ -0,0 +1,60 @@
import { tool } from "langchain"
/**
* 年份节气列表工具
* 查询给定年份节气所在日期列表
*/
export const getYearTermsTool = tool(
async ({ year }) => {
try {
// 简化实现返回24节气信息
const terms = [
{ date: `${year}-02-04`, name: '立春' },
{ date: `${year}-02-19`, name: '雨水' },
{ date: `${year}-03-05`, name: '惊蛰' },
{ date: `${year}-03-20`, name: '春分' },
{ date: `${year}-04-04`, name: '清明' },
{ date: `${year}-04-19`, name: '谷雨' },
{ date: `${year}-05-05`, name: '立夏' },
{ 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)
} catch (error) {
console.error('Error in year terms tool:', error)
return JSON.stringify({ error: error.message }, null, 2)
}
},
{
name: "get_year_terms",
description: "查询给定年份节气所在日期列表",
schema: {
type: "object",
properties: {
year: {
type: "number",
description: "年份",
minimum: 1900,
maximum: 2100
}
},
required: ["year"]
}
}
)

40
agent/tools/geo/latlon.js Normal file
View File

@@ -0,0 +1,40 @@
import * as z from "zod"
import { tool } from "langchain"
/**
* 获取城市经纬度工具
* @param {string} city - 城市名称
* @returns {Object|null} - 返回经纬度对象 {lat, lng}如果未找到则返回null
*/
export const getLatLngTool = tool(
async (input) => {
const { city } = input
try {
// 使用open-meteo地理编码API获取经纬度
console.log('获取城市经纬度:', city)
const url = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(city)}&language=zh&count=1`
const res = await fetch(url)
const data = await res.json()
if (data.results?.length) {
return {
lat: data.results[0].latitude,
lng: data.results[0].longitude
}
}
// 如果没有找到结果返回null
return null
} catch (error) {
console.error('地理编码API调用失败:', error)
return null
}
},
{
name: "get_lat_lng",
description: `根据城市名称获取经纬度信息`,
schema: z.object({
city: z.string().describe("城市名称,例如:北京市海淀区")
})
}
)

20
agent/tools/index.js Normal file
View File

@@ -0,0 +1,20 @@
// Tool 模块统一导出入口
// 提供工具基类、注册表和所有工具类的统一导出
// Web工具 (LangChain format)
export { webFetchTool } from './web/fetch.js';
export { webSearchTool } from './web/search.js';
export { httpGetTool } from './web/http_get.js';
export { httpPostTool } from './web/http_post.js';
// 日历工具 (LangChain format)
export { getCalendarInfoTool } from './calendar/calendar_info.js';
export { getLunarCalendarInfoTool } from './calendar/lunar_calendar_info.js';
export { getYearHolidaysTool } from './calendar/year_holidays.js';
export { getYearTermsTool } from './calendar/year_terms.js';
// 地理工具 (LangChain format)
export { getLatLngTool } from './geo/latlon.js';
export { getEnvTool } from './system/envs.js';

View File

@@ -0,0 +1,82 @@
// LangChain 环境变量工具
import { tool } from '@langchain/core/tools';
import { z } from 'zod';
/**
* 获取环境变量工具
* @param {Object} input - 输入参数
* @param {string} [input.name] - 环境变量名,不指定则返回所有
* @returns {string} - 返回环境变量值或所有环境变量的JSON字符串
*/
export const getEnvTool = tool(
async (input) => {
const { name } = input || {};
if (name) {
const value = process.env[name];
if (value === undefined) {
return JSON.stringify({ error: `Environment variable '${name}' not found` });
}
return JSON.stringify({ name, value });
}
// 返回所有环境变量(过滤敏感信息)
const env = { ...process.env };
const sensitiveKeys = ['API_KEY', 'SECRET', 'PASSWORD', 'TOKEN', 'CREDENTIAL'];
for (const key of Object.keys(env)) {
const upperKey = key.toUpperCase();
if (sensitiveKeys.some(s => upperKey.includes(s))) {
env[key] = '***REDACTED***';
}
}
return JSON.stringify({ env });
},
{
name: 'env_get',
description: '获取环境变量的值。如果不指定变量名,则返回所有环境变量。',
schema: z.object({
name: z.string().optional().describe('环境变量名,不指定则返回所有')
})
}
);
/**
* 设置环境变量工具
* @param {Object} input - 输入参数
* @param {string} input.name - 环境变量名
* @param {string} input.value - 环境变量值
* @returns {string} - 返回设置结果的JSON字符串
*/
export const setEnvTool = tool(
async (input) => {
const { name, value } = input || {};
if (!name) {
return JSON.stringify({ error: 'Environment variable name is required' });
}
if (value === undefined) {
return JSON.stringify({ error: 'Environment variable value is required' });
}
try {
process.env[name] = value;
return JSON.stringify({ success: true, name, value });
} catch (error) {
return JSON.stringify({ error: `Failed to set environment variable: ${error.message}` });
}
},
{
name: 'env_set',
description: '设置环境变量的值。',
schema: z.object({
name: z.string().describe('环境变量名'),
value: z.string().describe('环境变量值')
})
}
);
export default getEnvTool;

View File

@@ -0,0 +1,257 @@
import * as z from "zod"
import { tool } from "langchain"
import os from 'os'
import { promisify } from 'util'
import { exec } from 'child_process'
const execPromise = promisify(exec)
/**
* 获取系统文件信息工具
* @returns {string} - 返回系统文件信息的 JSON 字符串,包括分区信息和各分区剩余空间
*/
export const getSystemFileInfoTool = tool(
async (input) => {
try {
const fileSystemInfo = {
platform: os.platform(),
partitions: [],
volumeInfo: [],
diskHealth: [],
inodeInfo: []
}
// 获取文件系统信息Windows
if (os.platform() === 'win32') {
try {
const { stdout } = await execPromise('powershell -Command "Get-WmiObject Win32_LogicalDisk | Select-Object DeviceID,Description,FileSystem,Size,FreeSpace,VolumeName | ConvertTo-Json"')
const partitionData = JSON.parse(stdout)
const partitionsArray = Array.isArray(partitionData) ? partitionData : [partitionData]
fileSystemInfo.partitions = partitionsArray.map(disk => {
const size = disk.Size ? parseInt(disk.Size) : 0
const freeSpace = disk.FreeSpace ? parseInt(disk.FreeSpace) : 0
const usedSpace = size - freeSpace
return {
device: disk.DeviceID || 'N/A',
description: disk.Description || 'N/A',
filesystem: disk.FileSystem || 'N/A',
volumeName: disk.VolumeName || 'N/A',
size: size > 0 ? `${(size / (1024 * 1024 * 1024)).toFixed(2)} GB` : 'N/A',
freeSpace: freeSpace > 0 ? `${(freeSpace / (1024 * 1024 * 1024)).toFixed(2)} GB` : 'N/A',
usedSpace: usedSpace > 0 ? `${(usedSpace / (1024 * 1024 * 1024)).toFixed(2)} GB` : 'N/A',
usePercent: size > 0 ? `${((usedSpace / size) * 100).toFixed(2)}%` : 'N/A'
}
}).filter(item => item)
// 获取卷详细信息Windows
try {
const { stdout } = await execPromise('powershell -Command "Get-WmiObject Win32_LogicalDisk | Select-Object VolumeName,VolumeSerialNumber,FileSystem,MaximumComponentLength,DeviceID,DriveType | ConvertTo-Json"')
const allVolumeData = JSON.parse(stdout)
const volumeArray = Array.isArray(allVolumeData) ? allVolumeData : [allVolumeData]
fileSystemInfo.volumeInfo = volumeArray.map(vol => ({
device: vol.DeviceID || 'N/A',
volumeName: vol.VolumeName || 'N/A',
serialNumber: vol.VolumeSerialNumber || 'N/A',
fileSystem: vol.FileSystem || 'N/A',
maxComponentLength: vol.MaximumComponentLength || 'N/A',
deviceID: vol.DeviceID || 'N/A',
driveType: vol.DriveType === 2 ? 'Removable' : vol.DriveType === 3 ? 'Local' : vol.DriveType === 4 ? 'Network' : vol.DriveType === 5 ? 'CD-ROM' : 'Unknown'
})).filter(item => item)
} catch (error) {
console.error('Error getting volume info:', error)
}
// 获取磁盘健康状态Windows SMART
try {
const { stdout } = await execPromise('powershell -Command "Get-WmiObject Win32_DiskDrive | Select-Object Model,Status,SerialNumber,Size,MediaType | ConvertTo-Json"')
const diskData = JSON.parse(stdout)
const diskArray = Array.isArray(diskData) ? diskData : [diskData]
fileSystemInfo.diskHealth = diskArray.map(disk => {
return {
model: disk.Model || 'N/A',
status: disk.Status || 'N/A',
serialNumber: disk.SerialNumber || 'N/A',
size: disk.Size ? `${(parseInt(disk.Size) / (1024 * 1024 * 1024)).toFixed(2)} GB` : 'N/A'
}
}).filter(item => item)
} catch (error) {
console.error('Error getting disk health:', error)
}
} catch (error) {
console.error('Error getting file system info:', error)
fileSystemInfo.error = 'Error getting file system info'
}
}
// 获取文件系统信息Linux
else if (os.platform() === 'linux') {
try {
const { stdout } = await execPromise('df -h')
fileSystemInfo.partitions = stdout.trim().split('\n').slice(1).map(line => {
const parts = line.trim().split(/\s+/)
if (parts.length >= 6) {
return {
device: parts[0],
size: parts[1],
usedSpace: parts[2],
freeSpace: parts[3],
usePercent: parts[4],
mountedOn: parts[5]
}
}
return null
}).filter(item => item)
// 获取挂载选项和文件系统类型Linux
try {
const { stdout } = await execPromise('mount | column -t')
const mountLines = stdout.trim().split('\n')
const mountInfo = {}
mountLines.forEach(line => {
const match = line.match(/^(.+?)\s+on\s+(.+?)\s+type\s+(.+?)\s+\((.*)\)$/)
if (match) {
mountInfo[match[2]] = {
device: match[1],
filesystem: match[3],
options: match[4]
}
}
})
fileSystemInfo.mountInfo = mountInfo
} catch (error) {
console.error('Error getting mount info:', error)
}
// 获取inode信息Linux
try {
const { stdout } = await execPromise('df -i')
fileSystemInfo.inodeInfo = stdout.trim().split('\n').slice(1).map(line => {
const parts = line.trim().split(/\s+/)
if (parts.length >= 6) {
return {
device: parts[0],
totalInodes: parts[1],
usedInodes: parts[2],
freeInodes: parts[3],
usePercent: parts[4],
mountedOn: parts[5]
}
}
return null
}).filter(item => item)
} catch (error) {
console.error('Error getting inode info:', error)
}
// 获取磁盘健康状态Linux SMART
try {
const { stdout } = await execPromise('smartctl --health --json /dev/sda 2>/dev/null || echo "{}"')
if (stdout && stdout.includes('smart_status')) {
const healthData = JSON.parse(stdout)
fileSystemInfo.diskHealth.push({
device: '/dev/sda',
status: healthData.smart_status?.passed ? 'OK' : 'Failed',
powerOnHours: healthData.power_on_time?.hours || 'N/A',
temperature: healthData.temperature?.current || 'N/A'
})
}
} catch (error) {
console.log('SMART info not available or requires root')
}
} catch (error) {
console.error('Error getting file system info:', error)
fileSystemInfo.error = 'Error getting file system info'
}
}
// 获取文件系统信息macOS
else if (os.platform() === 'darwin') {
try {
const { stdout } = await execPromise('df -h')
fileSystemInfo.partitions = stdout.trim().split('\n').slice(1).map(line => {
const parts = line.trim().split(/\s+/)
if (parts.length >= 6) {
return {
device: parts[0],
size: parts[1],
usedSpace: parts[2],
freeSpace: parts[3],
usePercent: parts[4],
mountedOn: parts[5]
}
}
return null
}).filter(item => item)
// 获取卷详细信息macOS
try {
const diskInfo = await execPromise('diskutil info -all')
const diskLines = diskInfo.stdout.trim().split('\n\n')
diskLines.forEach(disk => {
const volMatch = disk.match(/Volume Name:\s+(.+)/)
const devMatch = disk.match(/Device Node:\s+(.+)/)
const fsMatch = disk.match(/File System Personality:\s+(.+)/)
const serialMatch = disk.match(/Disk UUID:\s+(.+)/)
if (devMatch) {
fileSystemInfo.volumeInfo.push({
device: devMatch[1],
volumeName: volMatch ? volMatch[1] : 'N/A',
filesystem: fsMatch ? fsMatch[1] : 'N/A',
serialNumber: serialMatch ? serialMatch[1] : 'N/A'
})
}
})
} catch (error) {
console.error('Error getting volume info:', error)
}
// 获取inode信息macOS
try {
const { stdout } = await execPromise('df -i')
fileSystemInfo.inodeInfo = stdout.trim().split('\n').slice(1).map(line => {
const parts = line.trim().split(/\s+/)
if (parts.length >= 6) {
return {
device: parts[0],
totalInodes: parts[1],
usedInodes: parts[2],
freeInodes: parts[3],
usePercent: parts[4],
mountedOn: parts[5]
}
}
return null
}).filter(item => item)
} catch (error) {
console.error('Error getting inode info:', error)
}
// 获取磁盘健康状态macOS
try {
const { stdout } = await execPromise('diskutil smartStatus -all 2>/dev/null || echo "N/A"')
if (stdout && stdout !== 'N/A') {
fileSystemInfo.diskHealth = stdout.trim()
}
} catch (error) {
console.log('SMART status not available')
}
} catch (error) {
console.error('Error getting file system info:', error)
fileSystemInfo.error = 'Error getting file system info'
}
}
return JSON.stringify(fileSystemInfo, null, 2)
} catch (error) {
console.error('Error getting file system info:', error)
return JSON.stringify({ error: error.message }, null, 2)
}
},
{
name: "get_system_file_info",
description: `获取系统硬盘分区和文件系统信息包括分区信息、卷标、序列号、磁盘健康状态、inode信息等`,
schema: z.object({})
}
)

View File

@@ -0,0 +1,112 @@
import * as z from "zod"
import { tool } from "langchain"
import os from 'os'
import { promisify } from 'util'
import { exec } from 'child_process'
const execPromise = promisify(exec)
/**
* 获取系统硬件配置信息工具
* @returns {string} - 返回系统硬件配置信息的 JSON 字符串
*/
export const getHardwareInfoTool = tool(
async (input) => {
try {
const hardwareInfo = {
cpu: {
cores: os.cpus().length,
model: os.cpus()[0].model,
speed: os.cpus()[0].speed
},
memory: {
total: `${(os.totalmem() / (1024 ** 3)).toFixed(2)} GB`,
free: `${(os.freemem() / (1024 ** 3)).toFixed(2)} GB`
},
disks: [],
gpu: []
}
// 获取硬盘信息Windows
if (os.platform() === 'win32') {
try {
const { stdout } = await execPromise('powershell -Command "Get-WmiObject Win32_DiskDrive | Select-Object Model,InterfaceType,Size | ConvertTo-Json"')
const diskData = JSON.parse(stdout)
const diskArray = Array.isArray(diskData) ? diskData : [diskData]
hardwareInfo.disks = diskArray.map(disk => ({
model: disk.Model || 'N/A',
interfaceType: disk.InterfaceType || 'N/A',
size: disk.Size ? `${(parseInt(disk.Size) / (1024 * 1024 * 1024)).toFixed(2)} GB` : 'N/A'
})).filter(item => item)
} catch (error) {
console.error('Error getting disk info:', error)
}
// 获取显卡信息Windows
try {
const { stdout } = await execPromise('wmic path win32_videocontroller get name')
hardwareInfo.gpu = stdout.trim().split('\n').slice(1).filter(line => line.trim())
} catch (error) {
console.error('Error getting GPU info:', error)
}
}
// 获取硬盘和显卡信息Linux
else if (os.platform() === 'linux') {
try {
const { stdout } = await execPromise('lsblk -o NAME,MODEL,SIZE,TYPE | grep disk')
const diskLines = stdout.trim().split('\n')
hardwareInfo.disks = diskLines.map(line => {
const parts = line.trim().split(/\s+/)
if (parts.length >= 3) {
return {
name: parts[0],
model: parts.slice(1, -1).join(' '),
size: parts[parts.length - 1]
}
}
return null
}).filter(item => item)
} catch (error) {
console.error('Error getting disk info:', error)
}
try {
const { stdout } = await execPromise('lspci | grep VGA')
hardwareInfo.gpu = stdout.trim().split('\n').map(line => line.trim())
} catch (error) {
console.error('Error getting GPU info:', error)
}
}
// 获取硬盘和显卡信息macOS
else if (os.platform() === 'darwin') {
try {
const { stdout } = await execPromise('diskutil list')
hardwareInfo.disks = [{
info: stdout.trim()
}]
} catch (error) {
console.error('Error getting disk info:', error)
}
try {
const { stdout } = await execPromise('system_profiler SPDisplaysDataType')
hardwareInfo.gpu = [{
info: stdout.trim()
}]
} catch (error) {
console.error('Error getting GPU info:', error)
}
}
return JSON.stringify(hardwareInfo, null, 2)
} catch (error) {
console.error('Error getting hardware info:', error)
return JSON.stringify({ error: error.message }, null, 2)
}
},
{
name: "get_hardware_info",
description: `获取系统硬件配置信息包括CPU、内存、硬盘、显卡等`,
schema: z.object({})
}
)

View File

@@ -0,0 +1,101 @@
import * as z from "zod"
import { tool } from "langchain"
import os from 'os'
import { promisify } from 'util'
import { exec } from 'child_process'
const execPromise = promisify(exec)
/**
* 获取已安装软件信息工具
* @returns {string} - 返回已安装软件信息的 JSON 字符串
*/
export const getInstalledSoftwareTool = tool(
async (input) => {
try {
const softwareInfo = {
platform: os.platform(),
software: []
}
// 获取Windows已安装软件
if (os.platform() === 'win32') {
try {
const { stdout } = await execPromise('powershell -Command "Get-WmiObject Win32_Product | Select-Object Name,Version,Vendor,InstallDate | ConvertTo-Json"')
const softwareData = JSON.parse(stdout)
const softwareArray = Array.isArray(softwareData) ? softwareData : [softwareData]
softwareInfo.software = softwareArray.map(soft => ({
name: soft.Name || 'N/A',
version: soft.Version || 'N/A',
vendor: soft.Vendor || 'N/A',
installDate: soft.InstallDate || 'N/A'
})).filter(item => item)
} catch (error) {
console.error('Error getting installed software:', error)
}
}
// 获取Linux已安装软件
else if (os.platform() === 'linux') {
try {
// 尝试使用dpkgDebian/Ubuntu
const { stdout: dpkgOutput } = await execPromise('dpkg -l 2>/dev/null || echo "N/A"')
if (dpkgOutput && dpkgOutput !== 'N/A') {
const lines = dpkgOutput.trim().split('\n').slice(5)
softwareInfo.software = lines.map(line => {
const parts = line.trim().split(/\s+/)
if (parts.length >= 3) {
return {
name: parts[1],
version: parts[2],
description: parts.slice(3).join(' ')
}
}
return null
}).filter(item => item)
} else {
// 尝试使用rpmRHEL/CentOS
const { stdout: rpmOutput } = await execPromise('rpm -qa 2>/dev/null || echo "N/A"')
if (rpmOutput && rpmOutput !== 'N/A') {
const lines = rpmOutput.trim().split('\n')
softwareInfo.software = lines.map(line => {
const match = line.match(/(.+)-(.+)-(.+)/)
if (match) {
return {
name: match[1],
version: `${match[2]}-${match[3]}`
}
}
return null
}).filter(item => item)
}
}
} catch (error) {
console.error('Error getting installed software:', error)
}
}
// 获取macOS已安装软件
else if (os.platform() === 'darwin') {
try {
const { stdout } = await execPromise('find /Applications -name "*.app" -type d | sort')
const apps = stdout.trim().split('\n')
softwareInfo.software = apps.map(app => ({
name: app.split('/').pop().replace('.app', ''),
path: app
})).filter(item => item)
} catch (error) {
console.error('Error getting installed software:', error)
}
}
return JSON.stringify(softwareInfo, null, 2)
} catch (error) {
console.error('Error getting installed software:', error)
return JSON.stringify({ error: error.message }, null, 2)
}
},
{
name: "get_installed_software",
description: `获取系统已安装的软件信息,包括软件名称、版本、供应商等`,
schema: z.object({})
}
)

View File

View File

@@ -0,0 +1,89 @@
import * as z from "zod"
import { tool } from "langchain"
import os from 'os'
import { promisify } from 'util'
import { exec } from 'child_process'
const execPromise = promisify(exec)
/**
* 获取操作系统信息工具
* @returns {string} - 返回操作系统信息的 JSON 字符串
*/
export const getOsInfoTool = tool(
async (input) => {
try {
const osInfo = {
platform: os.platform(),
arch: os.arch(),
release: os.release(),
hostname: os.hostname(),
uptime: `${Math.round(os.uptime() / 3600)} hours`,
nodeVersion: process.version,
homeDir: os.homedir(),
tempDir: os.tmpdir(),
userInfo: os.userInfo()
}
// 获取Windows详细信息
if (os.platform() === 'win32') {
try {
const { stdout } = await execPromise('powershell -Command "Get-WmiObject Win32_OperatingSystem | Select-Object Caption,Version,BuildNumber,OSArchitecture | ConvertTo-Json"')
const winInfo = JSON.parse(stdout)
osInfo.windows = {
caption: winInfo.Caption || 'N/A',
version: winInfo.Version || 'N/A',
buildNumber: winInfo.BuildNumber || 'N/A',
architecture: winInfo.OSArchitecture || 'N/A'
}
} catch (error) {
console.error('Error getting Windows info:', error)
}
}
// 获取Linux详细信息
else if (os.platform() === 'linux') {
try {
const { stdout } = await execPromise('cat /etc/os-release')
const lines = stdout.trim().split('\n')
const linuxInfo = {}
lines.forEach(line => {
const match = line.match(/(.+?)=(.+)/)
if (match) {
linuxInfo[match[1]] = match[2].replace(/"/g, '')
}
})
osInfo.linux = linuxInfo
} catch (error) {
console.error('Error getting Linux info:', error)
}
}
// 获取macOS详细信息
else if (os.platform() === 'darwin') {
try {
const { stdout } = await execPromise('sw_vers')
const lines = stdout.trim().split('\n')
const macInfo = {}
lines.forEach(line => {
const match = line.match(/(.+?):\s+(.+)/)
if (match) {
macInfo[match[1]] = match[2]
}
})
osInfo.macOS = macInfo
} catch (error) {
console.error('Error getting macOS info:', error)
}
}
return JSON.stringify(osInfo, null, 2)
} catch (error) {
console.error('Error getting OS info:', error)
return JSON.stringify({ error: error.message }, null, 2)
}
},
{
name: "get_os_info",
description: `获取操作系统信息,包括平台、架构、版本、主机名、运行时间等`,
schema: z.object({})
}
)

View File

@@ -0,0 +1,242 @@
import * as z from "zod"
import { tool } from "langchain"
import os from 'os'
import { promisify } from 'util'
import { exec } from 'child_process'
const execPromise = promisify(exec)
/**
* 获取系统网卡和网络连接信息工具
* @returns {string} - 返回网络信息的 JSON 字符串,包括网卡信息和网络连接信息
*/
export const getNetworkInfoTool = tool(
async (input) => {
try {
const networkInfo = {
platform: os.platform(),
networkInterfaces: [],
dnsServers: [],
gateway: []
}
// 获取网络接口信息(跨平台)
const interfaces = os.networkInterfaces()
for (const [name, addresses] of Object.entries(interfaces)) {
networkInfo.networkInterfaces.push({
name,
addresses: addresses.map(addr => ({
family: addr.family,
address: addr.address,
netmask: addr.netmask,
mac: addr.mac,
internal: addr.internal,
cidr: addr.cidr
}))
})
}
// 获取详细网络信息Windows
if (os.platform() === 'win32') {
try {
const { stdout } = await execPromise('ipconfig /all')
const lines = stdout.trim().split('\n')
let currentAdapter = null
lines.forEach(line => {
const adapterMatch = line.match(/适配器\s+(.+?):/)
if (adapterMatch) {
if (currentAdapter) {
networkInfo.networkInterfaces.push(currentAdapter)
}
currentAdapter = {
name: adapterMatch[1],
description: '',
macAddress: '',
dhcpEnabled: false,
ipAddress: [],
subnetMask: [],
defaultGateway: [],
dnsServers: []
}
} else if (currentAdapter) {
const descMatch = line.match(/描述\s+(.+)/)
if (descMatch) currentAdapter.description = descMatch[1].trim()
const macMatch = line.match(/物理地址\s+:\s+(.+)/)
if (macMatch) currentAdapter.macAddress = macMatch[1].trim()
const dhcpMatch = line.match(/DHCP 已启用\s+:\s+(.+)/)
if (dhcpMatch) currentAdapter.dhcpEnabled = dhcpMatch[1].trim() === '是'
const ipMatch = line.match(/IPv4 地址\s+:\s+(.+)/)
if (ipMatch) currentAdapter.ipAddress.push(ipMatch[1].trim())
const subnetMatch = line.match(/子网掩码\s+:\s+(.+)/)
if (subnetMatch) currentAdapter.subnetMask.push(subnetMatch[1].trim())
const gatewayMatch = line.match(/默认网关\s+:\s+(.+)/)
if (gatewayMatch && gatewayMatch[1].trim()) currentAdapter.defaultGateway.push(gatewayMatch[1].trim())
const dnsMatch = line.match(/DNS 服务器\s+:\s+(.+)/)
if (dnsMatch && dnsMatch[1].trim()) currentAdapter.dnsServers.push(dnsMatch[1].trim())
}
})
if (currentAdapter) {
networkInfo.networkInterfaces.push(currentAdapter)
}
} catch (error) {
console.error('Error getting network config:', error)
}
// 获取DNS服务器Windows
try {
const { stdout } = await execPromise('nslookup localhost 2>&1')
const dnsMatch = stdout.match(/Server:\s+(.+)/)
if (dnsMatch) {
networkInfo.dnsServers.push(dnsMatch[1].trim())
}
} catch (error) {
console.error('Error getting DNS servers:', error)
}
}
// 获取详细网络信息Linux
else if (os.platform() === 'linux') {
try {
const { stdout } = await execPromise('ip addr show')
const lines = stdout.trim().split('\n')
let currentInterface = null
lines.forEach(line => {
const interfaceMatch = line.match(/^\d+:\s+(\w+):/)
if (interfaceMatch) {
if (currentInterface) {
networkInfo.networkInterfaces.push(currentInterface)
}
currentInterface = {
name: interfaceMatch[1],
state: '',
macAddress: '',
ipAddress: [],
subnetMask: []
}
} else if (currentInterface) {
const stateMatch = line.match(/state\s+(\w+)/)
if (stateMatch) currentInterface.state = stateMatch[1]
const macMatch = line.match(/link\/ether\s+([a-fA-F0-9:]+)/)
if (macMatch) currentInterface.macAddress = macMatch[1]
const ipMatch = line.match(/inet\s+(\d+\.\d+\.\d+\.\d+)\/(\d+)/)
if (ipMatch) {
currentInterface.ipAddress.push(ipMatch[1])
const maskLength = parseInt(ipMatch[2])
currentInterface.subnetMask.push(maskLength)
}
}
})
if (currentInterface) {
networkInfo.networkInterfaces.push(currentInterface)
}
} catch (error) {
console.error('Error getting network config:', error)
}
// 获取DNS服务器Linux
try {
const { stdout } = await execPromise('cat /etc/resolv.conf')
const dnsMatches = stdout.match(/nameserver\s+(.+)/g)
if (dnsMatches) {
networkInfo.dnsServers = dnsMatches.map(match => match.replace('nameserver', '').trim())
}
} catch (error) {
console.error('Error getting DNS servers:', error)
}
// 获取网关Linux
try {
const { stdout } = await execPromise('ip route show default')
const gatewayMatch = stdout.match(/default via (\d+\.\d+\.\d+\.\d+)/)
if (gatewayMatch) {
networkInfo.gateway.push(gatewayMatch[1])
}
} catch (error) {
console.error('Error getting gateway:', error)
}
}
// 获取详细网络信息macOS
else if (os.platform() === 'darwin') {
try {
const { stdout } = await execPromise('ifconfig')
const lines = stdout.trim().split('\n')
let currentInterface = null
lines.forEach(line => {
const interfaceMatch = line.match(/^(\w+):/)
if (interfaceMatch) {
if (currentInterface) {
networkInfo.networkInterfaces.push(currentInterface)
}
currentInterface = {
name: interfaceMatch[1],
status: '',
macAddress: '',
ipAddress: [],
subnetMask: []
}
} else if (currentInterface) {
const statusMatch = line.match(/status:\s+(\w+)/)
if (statusMatch) currentInterface.status = statusMatch[1]
const macMatch = line.match(/ether\s+([a-fA-F0-9:]+)/)
if (macMatch) currentInterface.macAddress = macMatch[1]
const ipMatch = line.match(/inet\s+(\d+\.\d+\.\d+\.\d+)\s+netmask\s+(0x[0-9a-fA-F]+)/)
if (ipMatch) {
currentInterface.ipAddress.push(ipMatch[1])
currentInterface.subnetMask.push(ipMatch[2])
}
}
})
if (currentInterface) {
networkInfo.networkInterfaces.push(currentInterface)
}
} catch (error) {
console.error('Error getting network config:', error)
}
// 获取DNS服务器macOS
try {
const { stdout } = await execPromise('scutil --dns | grep nameserver | awk \'{print $3}\'')
networkInfo.dnsServers = stdout.trim().split('\n').filter(dns => dns)
} catch (error) {
console.error('Error getting DNS servers:', error)
}
// 获取网关macOS
try {
const { stdout } = await execPromise('netstat -nr | grep default')
const gatewayMatch = stdout.match(/default\s+(\d+\.\d+\.\d+\.\d+)/)
if (gatewayMatch) {
networkInfo.gateway.push(gatewayMatch[1])
}
} catch (error) {
console.error('Error getting gateway:', error)
}
}
return JSON.stringify(networkInfo, null, 2)
} catch (error) {
console.error('Error getting network info:', error)
return JSON.stringify({ error: error.message }, null, 2)
}
},
{
name: "get_network_info",
description: `获取系统网卡和网络信息包括网卡配置、IP地址、MAC地址、DNS服务器、网关等`,
schema: z.object({})
}
)

View File

@@ -0,0 +1,157 @@
import * as z from "zod"
import { tool } from "langchain"
import os from 'os'
import { promisify } from 'util'
import { exec } from 'child_process'
const execPromise = promisify(exec)
/**
* 获取系统活动连接和Socket信息工具
* @returns {string} - 返回Socket信息的 JSON 字符串,包括活动连接和监听端口等
*/
export const getSocketInfoTool = tool(
async (input) => {
try {
const socketInfo = {
platform: os.platform(),
activeConnections: [],
listeningPorts: []
}
// 获取活动连接Windows
if (os.platform() === 'win32') {
try {
const { stdout } = await execPromise('netstat -ano')
const lines = stdout.trim().split('\n').slice(4)
socketInfo.activeConnections = lines.map(line => {
const parts = line.trim().split(/\s+/)
if (parts.length >= 5) {
return {
protocol: parts[0],
localAddress: parts[1],
foreignAddress: parts[2],
state: parts[3],
processId: parts[4]
}
}
return null
}).filter(item => item)
} catch (error) {
console.error('Error getting active connections:', error)
}
// 获取监听端口Windows
try {
const { stdout } = await execPromise('netstat -an | findstr LISTENING')
const lines = stdout.trim().split('\n')
socketInfo.listeningPorts = lines.map(line => {
const parts = line.trim().split(/\s+/)
if (parts.length >= 4) {
return {
protocol: parts[0],
localAddress: parts[1],
state: parts[3]
}
}
return null
}).filter(item => item)
} catch (error) {
console.error('Error getting listening ports:', error)
}
}
// 获取活动连接Linux
else if (os.platform() === 'linux') {
try {
const { stdout } = await execPromise('ss -tuln')
const lines = stdout.trim().split('\n').slice(1)
socketInfo.activeConnections = lines.map(line => {
const parts = line.trim().split(/\s+/)
if (parts.length >= 5) {
return {
protocol: parts[0],
state: parts[1],
localAddress: parts[4],
peerAddress: parts[5]
}
}
return null
}).filter(item => item)
} catch (error) {
console.error('Error getting active connections:', error)
}
// 获取监听端口Linux
try {
const { stdout } = await execPromise('ss -tulpn')
const lines = stdout.trim().split('\n').slice(1)
socketInfo.listeningPorts = lines.map(line => {
const parts = line.trim().split(/\s+/)
if (parts.length >= 6) {
return {
protocol: parts[0],
state: parts[1],
localAddress: parts[4],
processInfo: parts[5]
}
}
return null
}).filter(item => item)
} catch (error) {
console.error('Error getting listening ports:', error)
}
}
// 获取活动连接macOS
else if (os.platform() === 'darwin') {
try {
const { stdout } = await execPromise('netstat -an')
const lines = stdout.trim().split('\n').slice(2)
socketInfo.activeConnections = lines.map(line => {
const parts = line.trim().split(/\s+/)
if (parts.length >= 6) {
return {
protocol: parts[0],
localAddress: parts[3],
foreignAddress: parts[4],
state: parts[5]
}
}
return null
}).filter(item => item)
} catch (error) {
console.error('Error getting active connections:', error)
}
// 获取监听端口macOS
try {
const { stdout } = await execPromise('netstat -an | grep LISTEN')
const lines = stdout.trim().split('\n')
socketInfo.listeningPorts = lines.map(line => {
const parts = line.trim().split(/\s+/)
if (parts.length >= 6) {
return {
protocol: parts[0],
localAddress: parts[3],
foreignAddress: parts[4],
state: parts[5]
}
}
return null
}).filter(item => item)
} catch (error) {
console.error('Error getting listening ports:', error)
}
}
return JSON.stringify(socketInfo, null, 2)
} catch (error) {
console.error('Error getting socket info:', error)
return JSON.stringify({ error: error.message }, null, 2)
}
},
{
name: "get_socket_info",
description: `获取系统活动连接和Socket信息包括TCP/UDP连接、监听端口、进程ID等`,
schema: z.object({})
}
)

View File

@@ -0,0 +1,17 @@
import * as z from "zod"
import { tool } from "langchain"
/**
* 获取系统时间工具
* @returns {string} - 返回当前系统时间
*/
export const getSystemTimeTool = tool(
(input) => {
return new Date().toLocaleString()
},
{
name: "get_system_time",
description: `获取当前系统时间`,
schema: z.object({})
}
)

View File

@@ -0,0 +1,74 @@
import * as z from 'zod';
import { tool } from 'langchain';
import { spawn } from 'child_process';
export const winCmdTool = tool(
async ({ command, cwd, timeout = 30000 }) => {
const workingDir = cwd || process.cwd();
return new Promise((resolve) => {
let stdout = '';
let stderr = '';
let killed = false;
const proc = spawn('cmd.exe', ['/c', command], {
cwd: workingDir,
shell: false,
windowsHide: true
});
const timer = setTimeout(() => {
killed = true;
proc.kill('SIGKILL');
}, timeout);
proc.stdout.on('data', (data) => {
stdout += data.toString();
});
proc.stderr.on('data', (data) => {
stderr += data.toString();
});
proc.on('close', (code) => {
clearTimeout(timer);
if (killed) {
resolve(JSON.stringify({
success: false,
error: `Command timed out after ${timeout}ms`,
command,
cwd: workingDir
}));
return;
}
resolve(JSON.stringify({
success: code === 0,
exitCode: code,
stdout: stdout.trim(),
stderr: stderr.trim(),
command,
cwd: workingDir
}));
});
proc.on('error', (error) => {
clearTimeout(timer);
resolve(JSON.stringify({
success: false,
error: error.message,
command,
cwd: workingDir
}));
});
});
},
{
name: 'win_cmd',
description: '在 Windows 系统上执行 cmd.exe 命令行命令。适用于运行 Windows 批处理命令、文件系统操作命令(如 dir、copy、del、mkdir 等)。不支持 PowerShell 命令。',
schema: z.object({
command: z.string().describe('要执行的 cmd.exe 命令或命令组合,使用 && 连接多个命令,如 "dir /b" 或 "echo hello && dir"'),
cwd: z.string().optional().describe('执行命令的工作目录,默认为当前工作目录'),
timeout: z.number().optional().describe('命令执行超时时间(毫秒)默认30000')
})
}
);

34
agent/tools/web/fetch.js Normal file
View File

@@ -0,0 +1,34 @@
import { TavilyExtract } from "@langchain/tavily";
import { tool } from "@langchain/core/tools";
import * as z from "zod"
const webFetchTool = tool(
async ({
extractDepth = "basic",
includeImages = false,
format = "markdown",
urls = [],
}) => {
const tavilyExtract = new TavilyExtract({
tavilyApiKey: process.env.TAVILY_API_KEY,
extractDepth,
includeImages,
format
});
return await tavilyExtract._call({ urls });
},
{
name: "web_fetch",
description: "Run a web fetch",
schema: z.object({
extractDepth: z.enum(["basic", "advanced"]).default("basic"),
includeImages: z.boolean().default(false),
format: z
.enum(["markdown", "json"])
.default("markdown"),
urls: z.array(z.string().describe("The urls")).default([]),
}),
},
);
export { webFetchTool };

View File

@@ -0,0 +1,62 @@
import { tool } from 'langchain';
import * as z from 'zod';
export const httpGetTool = tool(
async ({ url, headers = {}, timeout = 30000 }) => {
try {
new URL(url);
} catch (e) {
return JSON.stringify({ success: false, error: `Invalid URL: ${url}` });
}
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
const response = await fetch(url, {
method: 'GET',
headers: {
'User-Agent': 'XAgent-Bot/1.0',
...headers
},
signal: controller.signal
});
clearTimeout(timeoutId);
const contentType = response.headers.get('content-type') || '';
let body;
if (contentType.includes('application/json')) {
body = await response.json();
} else if (contentType.includes('text/') || contentType.includes('application/javascript')) {
body = await response.text();
} else {
const buffer = await response.arrayBuffer();
body = Buffer.from(buffer).toString('base64');
}
return JSON.stringify({
success: true,
status: response.status,
statusText: response.statusText,
headers: Object.fromEntries(response.headers.entries()),
body
});
} catch (error) {
if (error.name === 'AbortError') {
return JSON.stringify({ success: false, error: `Request timed out after ${timeout}ms` });
}
return JSON.stringify({ success: false, error: error.message });
}
},
{
name: 'http_get',
description: 'Send HTTP GET request and return response content. Supports custom headers and timeout settings.',
schema: z.object({
url: z.string().describe('Request URL'),
headers: z.record(z.string(), z.string()).optional().describe('Request headers'),
timeout: z.number().optional().describe('Timeout in milliseconds, default 30000')
})
}
);

View File

@@ -0,0 +1,88 @@
import { tool } from "@langchain/core/tools";
import z from "zod";
const httpPostTool = tool(
async ({ url, body, headers = {}, contentType = 'json', timeout = 30000 }) => {
try {
new URL(url);
} catch (e) {
return { success: false, error: `Invalid URL: ${url}` };
}
let requestBody;
const requestHeaders = {
'User-Agent': 'XAgent-Bot/1.0',
...headers
};
switch (contentType) {
case 'json':
requestHeaders['Content-Type'] = 'application/json';
requestBody = typeof body === 'string' ? body : JSON.stringify(body);
break;
case 'form':
requestHeaders['Content-Type'] = 'application/x-www-form-urlencoded';
if (typeof body === 'object') {
requestBody = new URLSearchParams(body).toString();
} else {
requestBody = body;
}
break;
case 'text':
requestHeaders['Content-Type'] = 'text/plain';
requestBody = typeof body === 'string' ? body : JSON.stringify(body);
break;
default:
requestBody = typeof body === 'string' ? body : JSON.stringify(body);
}
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
const response = await fetch(url, {
method: 'POST',
headers: requestHeaders,
body: requestBody,
signal: controller.signal
});
clearTimeout(timeoutId);
const responseContentType = response.headers.get('content-type') || '';
let responseBody;
if (responseContentType.includes('application/json')) {
responseBody = await response.json();
} else {
responseBody = await response.text();
}
return {
success: true,
status: response.status,
statusText: response.statusText,
headers: Object.fromEntries(response.headers.entries()),
body: responseBody
};
} catch (error) {
if (error.name === 'AbortError') {
return { success: false, error: `Request timed out after ${timeout}ms` };
}
return { success: false, error: error.message };
}
},
{
name: "http_post",
description: "Send HTTP POST request. Supports JSON and form data.",
schema: z.object({
url: z.string().describe("Request URL"),
body: z.union([z.string(), z.object({})]).describe("Request body, can be string or JSON object"),
headers: z.record(z.string()).optional().describe("Request headers"),
contentType: z.enum(['json', 'form', 'text', 'raw']).optional().describe("Content type, default json"),
timeout: z.number().optional().describe("Timeout in milliseconds, default 30000")
}),
},
);
export { httpPostTool };

35
agent/tools/web/search.js Normal file
View File

@@ -0,0 +1,35 @@
import { TavilySearch } from "@langchain/tavily";
import { tool } from "@langchain/core/tools";
import * as z from "zod"
const webSearchTool = tool(
async ({
query,
maxResults = 5,
topic = "general",
includeRawContent = false,
}) => {
const tavilySearch = new TavilySearch({
maxResults,
tavilyApiKey: process.env.TAVILY_API_KEY,
includeRawContent,
topic,
});
return await tavilySearch._call({ query });
},
{
name: "internet_search",
description: "Run a web search",
schema: z.object({
query: z.string().describe("The search query"),
maxResults: z.number().optional().default(5),
topic: z
.enum(["general", "news", "finance"])
.optional()
.default("general"),
includeRawContent: z.boolean().optional().default(false),
}),
},
);
export { webSearchTool };

View File

@@ -1,3 +1,4 @@
import 'dotenv/config';
import { APP } from './app.js' import { APP } from './app.js'
import WebSocketServerManager from './websocket.js' import WebSocketServerManager from './websocket.js'

9
package-lock.json generated
View File

@@ -16,7 +16,6 @@
"@langchain/openai": "^1.4.7", "@langchain/openai": "^1.4.7",
"@langchain/tavily": "^1.2.0", "@langchain/tavily": "^1.2.0",
"bcrypt": "^5.1.1", "bcrypt": "^5.1.1",
"bufferutil": "^4.1.0",
"deepagents": "^1.10.2", "deepagents": "^1.10.2",
"dotenv": "^17.4.2", "dotenv": "^17.4.2",
"koa": "^2.16.4", "koa": "^2.16.4",
@@ -25,10 +24,10 @@
"koa-router": "^12.0.1", "koa-router": "^12.0.1",
"langchain": "^1.4.2", "langchain": "^1.4.2",
"lodash": "^4.18.1", "lodash": "^4.18.1",
"lunar-javascript": "^1.7.7",
"moment": "^2.30.1", "moment": "^2.30.1",
"mongoose": "^8.24.0", "mongoose": "^8.24.0",
"node-fetch": "^3.3.2", "node-fetch": "^3.3.2",
"utf-8-validate": "^6.0.6",
"winston": "^3.19.0", "winston": "^3.19.0",
"ws": "^8.21.0", "ws": "^8.21.0",
"zod": "^4.4.3" "zod": "^4.4.3"
@@ -2495,6 +2494,12 @@
"node": ">= 12.0.0" "node": ">= 12.0.0"
} }
}, },
"node_modules/lunar-javascript": {
"version": "1.7.7",
"resolved": "https://registry.npmmirror.com/lunar-javascript/-/lunar-javascript-1.7.7.tgz",
"integrity": "sha512-u/KYiwPIBo/0bT+WWfU7qO1d+aqeB90Tuy4ErXenr2Gam0QcWeezUvtiOIyXR7HbVnW2I1DKfU0NBvzMZhbVQw==",
"license": "MIT"
},
"node_modules/make-dir": { "node_modules/make-dir": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmmirror.com/make-dir/-/make-dir-3.1.0.tgz", "resolved": "https://registry.npmmirror.com/make-dir/-/make-dir-3.1.0.tgz",

View File

@@ -25,6 +25,7 @@
"koa-router": "^12.0.1", "koa-router": "^12.0.1",
"langchain": "^1.4.2", "langchain": "^1.4.2",
"lodash": "^4.18.1", "lodash": "^4.18.1",
"lunar-javascript": "^1.7.7",
"moment": "^2.30.1", "moment": "^2.30.1",
"mongoose": "^8.24.0", "mongoose": "^8.24.0",
"node-fetch": "^3.3.2", "node-fetch": "^3.3.2",

View File

@@ -10,7 +10,7 @@ const agreement = {
"仅支持平台已开通地区医院的代问诊服务,仅限当次就诊门诊当天使用。", "仅支持平台已开通地区医院的代问诊服务,仅限当次就诊门诊当天使用。",
"全程陪诊半天不超过4小时全天不超过8小时。", "全程陪诊半天不超过4小时全天不超过8小时。",
"预约成功后,平台将提前电话联系确认就诊资料及时间,并以短信或微信通知预约人。", "预约成功后,平台将提前电话联系确认就诊资料及时间,并以短信或微信通知预约人。",
"请如实填写病情描述,因描述不清导致误诊的,平台不承担责任。", "陪诊人员只代客户问诊并转达医嘱,不负责诊断和治疗。需要时陪诊员会让医生和客户直接通话。最终医疗听从医嘱为准。",
"医生停诊等特殊情况,请及时联系客服处理。" "医生停诊等特殊情况,请及时联系客服处理。"
] ]
}, },