feat(agent): 新增实体搜索工具及API接口

新增实体搜索功能,支持按名称模糊查询店铺、柜机、商品等实体。

主要变更:
- 新增 searchEntityTool 工具,支持 shop/cabinet/goods 三种实体类型查询
- 新增 agent API 模块,统一管理实体搜索和详情查询接口
- 重构 cabinetTool 使用新的 cabinetDetail API
- 增强 shopTool,新增 shopDetail 查询类型
- 添加 axios 调试日志便于问题排查
- 新增 Agent API 接口文档

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
dzq 2026-01-08 17:03:31 +08:00
parent 1334ad6baa
commit e158ae34ef
8 changed files with 500 additions and 15 deletions

View File

@ -5,6 +5,7 @@ import {
shopTool,
dynamicInfoTool,
codeExecutorTool,
searchEntityTool,
} from "../tools";
import { createDeepSeek } from '@ai-sdk/deepseek';
import { createOpenAI } from '@ai-sdk/openai';
@ -47,10 +48,15 @@ export const multiFunctionAgent = new Agent({
1. ****使ID获取单个商品详情
2. ****使
3. ****使
2. ****使ID查询智能柜详细信息
3. ****使ID获取门店详细信息
4. ****使IDID
5. ****使 JavaScript
5. ****使
-
- name: 实体名称关键词
- entityType: 实体类型shop=/cabinet=goods=
- ID和名称列表
6. ****使 JavaScript
-
- code: 要执行的 JavaScript return
- context: 可选
@ -89,5 +95,6 @@ export const multiFunctionAgent = new Agent({
shopTool,
dynamicInfoTool,
codeExecutorTool,
searchEntityTool,
},
});

View File

@ -0,0 +1,116 @@
import { request } from "../axios"
import { ResponseData } from "../type"
export interface IdNameDTO {
/** 实体ID */
id: number
/** 实体名称 */
name: string
}
export interface CellDTO {
/** 单元格ID */
id: number
/** 单元格编号 */
cellNo: string
/** 单元格名称 */
cellName: string
/** 单元格状态 */
status: number
/** 商品ID */
goodsId: number
/** 商品名称 */
goodsName: string
}
export interface CabinetDetailDTO {
/** 柜机ID */
id: number
/** 柜机编号 */
cabinetNo: string
/** 柜机名称 */
cabinetName: string
/** 所属店铺ID */
shopId: number
/** 所属店铺名称 */
shopName: string
/** 柜机状态 */
status: number
/** 单元格列表 */
cells: CellDTO[]
}
export interface CabinetSimpleDTO {
/** 柜机ID */
id: number
/** 柜机编号 */
cabinetNo: string
/** 柜机名称 */
cabinetName: string
/** 柜机状态 */
status: number
}
export interface ShopDetailDTO {
/** 店铺ID */
id: number
/** 店铺名称 */
shopName: string
/** 店铺编码 */
shopCode: string
/** 联系人姓名 */
contactName: string
/** 联系电话 */
contactPhone: string
/** 店铺地址 */
address: string
/** 店铺状态 */
status: number
/** 柜机列表 */
cabinets: CabinetSimpleDTO[]
}
export type EntityType = "shop" | "cabinet" | "goods"
/**
*
* (shop)(cabinet)(goods)
* @param name
* @param entityType shopcabinetgoods
* @returns Promise<ResponseData<IdNameDTO[]>>
*/
export function searchEntity(name: string, entityType: EntityType) {
return request<ResponseData<IdNameDTO[]>>({
url: "agent/searchEntity",
method: "get",
params: { name, entityType }
})
}
/**
* ID获取智能柜详细信息
*
* @param cabinetId ID
* @returns Promise<ResponseData<CabinetDetailDTO>>
*/
export function cabinetDetail(cabinetId: number) {
return request<ResponseData<CabinetDetailDTO>>({
url: "agent/cabinetDetail",
method: "get",
params: { cabinetId }
})
}
/**
* ID获取店铺详细信息
*
* @param shopId ID
* @returns Promise<ResponseData<ShopDetailDTO>>
*/
export function shopDetail(shopId: number) {
return request<ResponseData<ShopDetailDTO>>({
url: "agent/shopDetail",
method: "get",
params: { shopId }
})
}

View File

@ -11,7 +11,14 @@ function createInstance() {
// 请求拦截器
instance.interceptors.request.use(
// 发送之前
config => config,
config => {
console.log('Request URL:', config.url)
console.log('Request Method:', config.method)
console.log('Request Headers:', config.headers)
console.log('Request Params:', config.params)
console.log('Request Data:', config.data)
return config
},
// 发送失败
error => Promise.reject(error)
)
@ -32,6 +39,9 @@ function createInstance() {
switch (code) {
case 0:
// 本系统采用 code === 0 来表示没有业务错误
console.log('Response URL:', response.config.url)
console.log('Response Status:', response.status)
console.log('Response Data:', apiData)
return apiData
default:
// 不是正确的 code
@ -78,6 +88,11 @@ function createInstance() {
error.message = "HTTP 版本不受支持"
break
}
console.error('Request Error URL:', error.config?.url)
console.error('Request Error Method:', error.config?.method)
console.error('Request Error Status:', status)
console.error('Request Error Message:', error.message)
console.error('Request Error Response Data:', error.response?.data)
return Promise.reject(error)
}
)

View File

@ -0,0 +1,260 @@
# Agent Controller 接口文档
## 概述
AgentController 提供实体搜索及详情查询功能,支持店铺、柜机、商品三种类型实体的模糊搜索,以及柜机和店铺的详细信息获取。
**基础路径**: `/api/agent`
---
## 接口列表
### 1. 根据名称模糊查询实体列表
根据名称关键词模糊查询店铺、柜机或商品实体。
**请求地址**: `GET /api/agent/searchEntity`
**请求参数**:
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| name | String | 是 | 名称关键词,支持模糊匹配 |
| entityType | String | 是 | 实体类型可选值shop(店铺)、cabinet(柜机)、goods(商品) |
**请求示例**:
```
GET /api/agent/searchEntity?name=测试&entityType=shop
```
**响应参数**:
| 参数名 | 类型 | 说明 |
|--------|------|------|
| code | Integer | 状态码200表示成功 |
| msg | String | 返回消息 |
| data | List\<IdNameDTO\> | 实体列表 |
**IdNameDTO 结构**:
| 字段名 | 类型 | 说明 |
|--------|------|------|
| id | Long | 实体ID |
| name | String | 实体名称 |
**成功响应示例**:
```json
{
"code": 200,
"msg": "操作成功",
"data": [
{
"id": 1,
"name": "测试店铺A"
},
{
"id": 2,
"name": "测试店铺B"
}
]
}
```
**错误响应**:
- 当 name 为空时,返回空列表 `data: null`
---
### 2. 根据柜体ID获取智能柜详细信息
根据柜体ID获取智能柜的详细信息包括柜体信息、单元格信息和商品信息。
**请求地址**: `GET /api/agent/cabinetDetail`
**请求参数**:
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| cabinetId | Long | 是 | 柜体ID |
**请求示例**:
```
GET /api/agent/cabinetDetail?cabinetId=1
```
**响应参数**:
| 参数名 | 类型 | 说明 |
|--------|------|------|
| code | Integer | 状态码200表示成功 |
| msg | String | 返回消息 |
| data | CabinetDetailDTO | 柜机详情对象 |
**CabinetDetailDTO 结构**:
| 字段名 | 类型 | 说明 |
|--------|------|------|
| id | Long | 柜机ID |
| cabinetNo | String | 柜机编号 |
| cabinetName | String | 柜机名称 |
| shopId | Long | 所属店铺ID |
| shopName | String | 所属店铺名称 |
| status | Integer | 柜机状态 |
| cells | List\<CellDTO\> | 单元格列表 |
| ... | - | 其他扩展字段 |
**CellDTO 结构**:
| 字段名 | 类型 | 说明 |
|--------|------|------|
| id | Long | 单元格ID |
| cellNo | String | 单元格编号 |
| cellName | String | 单元格名称 |
| status | Integer | 单元格状态 |
| goodsId | Long | 商品ID |
| goodsName | String | 商品名称 |
| ... | - | 其他扩展字段 |
**成功响应示例**:
```json
{
"code": 200,
"msg": "操作成功",
"data": {
"id": 1,
"cabinetNo": "CAB001",
"cabinetName": "一号智能柜",
"shopId": 1,
"shopName": "测试店铺",
"status": 1,
"cells": [
{
"id": 1,
"cellNo": "C001",
"cellName": "1号格口",
"status": 0,
"goodsId": 100,
"goodsName": "商品A"
}
]
}
}
```
**错误响应**:
- 当 cabinetId 为空时,返回失败状态
---
### 3. 根据店铺ID获取店铺详细信息
根据店铺ID获取店铺详细信息包含下属柜机列表不包含格口信息
**请求地址**: `GET /api/agent/shopDetail`
**请求参数**:
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| shopId | Long | 是 | 店铺ID |
**请求示例**:
```
GET /api/agent/shopDetail?shopId=1
```
**响应参数**:
| 参数名 | 类型 | 说明 |
|--------|------|------|
| code | Integer | 状态码200表示成功 |
| msg | String | 返回消息 |
| data | ShopDetailDTO | 店铺详情对象 |
**ShopDetailDTO 结构**:
| 字段名 | 类型 | 说明 |
|--------|------|------|
| id | Long | 店铺ID |
| shopName | String | 店铺名称 |
| shopCode | String | 店铺编码 |
| contactName | String | 联系人姓名 |
| contactPhone | String | 联系电话 |
| address | String | 店铺地址 |
| status | Integer | 店铺状态 |
| cabinets | List\<CabinetSimpleDTO\> | 柜机列表 |
| ... | - | 其他扩展字段 |
**CabinetSimpleDTO 结构**:
| 字段名 | 类型 | 说明 |
|--------|------|------|
| id | Long | 柜机ID |
| cabinetNo | String | 柜机编号 |
| cabinetName | String | 柜机名称 |
| status | Integer | 柜机状态 |
| ... | - | 其他扩展字段 |
**成功响应示例**:
```json
{
"code": 200,
"msg": "操作成功",
"data": {
"id": 1,
"shopName": "测试店铺",
"shopCode": "SHOP001",
"contactName": "张三",
"contactPhone": "13800138000",
"address": "北京市朝阳区xxx",
"status": 1,
"cabinets": [
{
"id": 1,
"cabinetNo": "CAB001",
"cabinetName": "一号智能柜",
"status": 1
},
{
"id": 2,
"cabinetNo": "CAB002",
"cabinetName": "二号智能柜",
"status": 1
}
]
}
}
```
**错误响应**:
- 当 shopId 为空时,返回失败状态
---
## 状态码说明
| 状态码 | 说明 |
|--------|------|
| 200 | 操作成功 |
| 500 | 服务器内部错误 |
| 其他 | 具体业务错误信息 |
---
## 实体类型说明
| entityType | 说明 | 关联服务 |
|------------|------|----------|
| shop | 店铺实体 | ShopApplicationService |
| cabinet | 柜机实体 | SmartCabinetApplicationService |
| goods | 商品实体 | GoodsApplicationService |
---
## 注意事项
1. 所有接口均支持跨域访问(`@CrossOrigin` 配置)
2. searchEntity 接口在 name 为空时返回 null 而非空列表
3. cabinetDetail 接口返回完整的柜机及格口信息,数据量可能较大
4. shopDetail 接口仅返回柜机基本信息,不包含格口详情

View File

@ -1,13 +1,13 @@
import { createTool } from "@mastra/core/tools";
import { z } from "zod";
import { getCabinetDetailApi } from "../api/cabinet";
import { cabinetDetail } from "../api/agent/agent";
export const cabinetTool = createTool({
id: "cabinet",
description: "查询智能柜详情",
description: "根据柜机ID查询智能柜详细信息",
inputSchema: z.object({
shopId: z.number().describe("门店ID"),
cabinetId: z.number().describe("柜机ID"),
}),
outputSchema: z.object({
@ -18,16 +18,16 @@ export const cabinetTool = createTool({
execute: async ({ context }) => {
try {
const { shopId } = context;
const { cabinetId } = context;
if (!shopId) {
if (!cabinetId) {
return {
success: false,
message: "shopId参数为必填",
message: "cabinetId参数为必填",
};
}
const response = await getCabinetDetailApi(shopId);
const response = await cabinetDetail(cabinetId);
return {
success: response.code === 0,
message: response.msg || "查询成功",

View File

@ -3,3 +3,4 @@ export { cabinetTool } from "./cabinet-tool";
export { shopTool } from "./shop-tool";
export { dynamicInfoTool } from "./dynamic-info-tool";
export { codeExecutorTool } from "./code-executor-tool";
export { searchEntityTool } from "./search-entity-tool";

View File

@ -0,0 +1,71 @@
import { createTool } from "@mastra/core/tools";
import { z } from "zod";
import { searchEntity } from "../api/agent/agent";
export const searchEntityTool = createTool({
id: "searchEntity",
description: `根据名称关键词模糊查询实体列表,支持查询三种类型的实体:
- shop/
- cabinet
- goods
使
-
-
- ID和名称列表
- entityType ['shop', 'cabinet', 'goods']
- name
- `,
inputSchema: z.object({
name: z.string().min(1).describe("实体名称关键词,支持模糊匹配"),
entityType: z.enum(["shop", "cabinet", "goods"]).describe(
"实体类型:'shop'=店铺/门店,'cabinet'=智能柜,'goods'=商品"
),
}),
outputSchema: z.object({
success: z.boolean(),
message: z.string(),
data: z.array(z.object({
shopId: z.number().optional(),
cabinetId: z.number().optional(),
goodsId: z.number().optional(),
name: z.string(),
})).optional(),
}),
execute: async ({ context }) => {
try {
const { name, entityType } = context;
const response = await searchEntity(name, entityType);
const data = (response.data || []).map(item => {
switch (entityType) {
case 'shop':
return { shopId: item.id, name: item.name };
case 'cabinet':
return { cabinetId: item.id, name: item.name };
case 'goods':
return { goodsId: item.id, name: item.name };
default:
return { name: item.name };
}
});
return {
success: response.code === 200,
message: response.msg || "查询成功",
data,
};
} catch (error: any) {
return {
success: false,
message: `查询失败: ${error.message || "未知错误"}`,
};
}
},
});

View File

@ -5,19 +5,21 @@ import {
getModeListApi,
GetShopListParams
} from "../api/shop";
import { shopDetail } from "../api/agent/agent";
export const shopTool = createTool({
id: "shop",
description: "查询门店相关信息,支持查询门店列表、模式列表",
description: "查询门店相关信息,支持查询门店列表、模式列表、门店详情",
inputSchema: z.object({
queryType: z.enum(["shopList", "modeList"]).describe(
"查询类型:'shopList'表示获取门店列表,'modeList'表示获取模式列表"
queryType: z.enum(["shopList", "modeList", "shopDetail"]).describe(
"查询类型:'shopList'表示获取门店列表,'modeList'表示获取模式列表'shopDetail'表示获取门店详情"
),
corpid: z.string().optional().describe("企业微信ID查询门店列表时必填"),
mode: z.number().optional().describe("需要排除的运行模式(查询门店列表时可选,该参数表示不查询该运行模式的门店,默认值为-1"),
eqMode: z.number().optional().describe("运行模式(查询门店列表时可选)"),
modeList: z.string().optional().describe("模式列表字符串(查询门店列表时可选)"),
shopId: z.number().optional().describe("门店ID查询门店详情时必填"),
}),
outputSchema: z.object({
@ -28,7 +30,7 @@ export const shopTool = createTool({
execute: async ({ context }) => {
try {
const { queryType, corpid, mode, eqMode, modeList } = context;
const { queryType, corpid, mode, eqMode, modeList, shopId } = context;
if (queryType === "shopList") {
if (!corpid) {
@ -56,6 +58,19 @@ export const shopTool = createTool({
message: response.msg || "查询成功",
data: response.data,
};
} else if (queryType === "shopDetail") {
if (!shopId) {
return {
success: false,
message: "查询门店详情时shopId参数为必填",
};
}
const response = await shopDetail(shopId);
return {
success: response.code === 0,
message: response.msg || "查询成功",
data: response.data,
};
}
return {