build(automated): 更新项目依赖并调整配置
- 更新项目名称为 automated - 更新多个依赖包版本,包括 @cool-midway、@midwayjs、@langchain 等 - 更新作者信息为 lixin&liqiannan - 更新 TypeScript 版本至 5.5.4 - 添加 newman、prettier 等新依赖 - 优化 package.json 结构和格式
This commit is contained in:
parent
a49c9e31ab
commit
0afa56d652
22
chromaDB/docker-compose.yml
Normal file
22
chromaDB/docker-compose.yml
Normal file
@ -0,0 +1,22 @@
|
||||
version: "3.9"
|
||||
|
||||
networks:
|
||||
net:
|
||||
driver: bridge
|
||||
services:
|
||||
server:
|
||||
image: ghcr.io/chroma-core/chroma:latest
|
||||
environment:
|
||||
- IS_PERSISTENT=TRUE
|
||||
volumes:
|
||||
# Default configuration for persist_directory in chromadb/config.py
|
||||
# Currently it's located in "/chroma/chroma/"
|
||||
- chroma-data:/chroma/chroma/
|
||||
ports:
|
||||
- 8000:8000
|
||||
networks:
|
||||
- net
|
||||
|
||||
volumes:
|
||||
chroma-data:
|
||||
driver: local
|
1
lock/menu/flow.menu.lock
Normal file
1
lock/menu/flow.menu.lock
Normal file
@ -0,0 +1 @@
|
||||
success
|
1
lock/menu/know.menu.lock
Normal file
1
lock/menu/know.menu.lock
Normal file
@ -0,0 +1 @@
|
||||
success
|
64
package.json
64
package.json
@ -1,60 +1,68 @@
|
||||
{
|
||||
"name": "cool-admin",
|
||||
"name": "automated",
|
||||
"version": "7.1.0",
|
||||
"description": "一个项目用COOL就够了",
|
||||
"description": "正在build",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@cool-midway/core": "^7.1.19",
|
||||
"@cool-midway/core": "^7.1.20",
|
||||
"@cool-midway/rpc": "^7.0.0",
|
||||
"@cool-midway/task": "^7.0.0",
|
||||
"@midwayjs/bootstrap": "^3.15.0",
|
||||
"@midwayjs/cache-manager": "^3.15.5",
|
||||
"@midwayjs/core": "^3.15.0",
|
||||
"@midwayjs/cron": "^3.15.2",
|
||||
"@midwayjs/cross-domain": "^3.15.2",
|
||||
"@midwayjs/decorator": "^3.15.0",
|
||||
"@midwayjs/info": "^3.15.2",
|
||||
"@midwayjs/koa": "^3.15.2",
|
||||
"@midwayjs/logger": "^3.3.0",
|
||||
"@langchain/cohere": "^0.2.1",
|
||||
"@langchain/community": "^0.2.20",
|
||||
"@langchain/core": "^0.2.18",
|
||||
"@langchain/openai": "^0.2.5",
|
||||
"@midwayjs/bootstrap": "^3.16.6",
|
||||
"@midwayjs/cache-manager": "^3.16.5",
|
||||
"@midwayjs/core": "^3.16.2",
|
||||
"@midwayjs/cron": "^3.16.5",
|
||||
"@midwayjs/cross-domain": "^3.16.6",
|
||||
"@midwayjs/decorator": "^3.16.2",
|
||||
"@midwayjs/info": "^3.16.6",
|
||||
"@midwayjs/koa": "^3.16.6",
|
||||
"@midwayjs/logger": "^3.4.2",
|
||||
"@midwayjs/socketio": "^3.15.2",
|
||||
"@midwayjs/static-file": "^3.15.2",
|
||||
"@midwayjs/typeorm": "^3.15.2",
|
||||
"@midwayjs/upload": "^3.15.2",
|
||||
"@midwayjs/validate": "^3.15.2",
|
||||
"@midwayjs/view-ejs": "^3.15.2",
|
||||
"@midwayjs/static-file": "^3.16.6",
|
||||
"@midwayjs/typeorm": "^3.16.5",
|
||||
"@midwayjs/upload": "^3.16.7",
|
||||
"@midwayjs/validate": "^3.16.6",
|
||||
"@midwayjs/view-ejs": "^3.16.6",
|
||||
"@socket.io/redis-adapter": "^8.3.0",
|
||||
"axios": "^1.6.8",
|
||||
"axios": "^1.7.2",
|
||||
"bignumber.js": "^9.1.2",
|
||||
"cache-manager-ioredis-yet": "^2.0.2",
|
||||
"cache-manager-ioredis-yet": "^2.1.1",
|
||||
"cheerio": "1.0.0-rc.12",
|
||||
"chromadb": "^1.8.1",
|
||||
"decompress": "^4.2.1",
|
||||
"download": "^8.0.0",
|
||||
"ioredis": "^5.3.2",
|
||||
"ipip-ipdb": "^0.6.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"langchain": "^0.2.11",
|
||||
"lodash": "^4.17.21",
|
||||
"md5": "^2.3.0",
|
||||
"mini-svg-data-uri": "^1.4.4",
|
||||
"moment": "^2.30.1",
|
||||
"mysql2": "^3.9.2",
|
||||
"mysql2": "^3.11.0",
|
||||
"semver": "^7.6.0",
|
||||
"svg-captcha": "^1.4.0",
|
||||
"svg2png-wasm": "^1.4.1",
|
||||
"typeorm": "^0.3.20",
|
||||
"uuid": "^9.0.1",
|
||||
"ws": "^8.16.0"
|
||||
"uuid": "^10.0.0",
|
||||
"ws": "^8.18.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@midwayjs/cli": "^2.1.1",
|
||||
"@midwayjs/mock": "^3.15.2",
|
||||
"@midwayjs/mock": "^3.16.5",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/koa": "^2.15.0",
|
||||
"@types/node": "20",
|
||||
"@types/node": "22",
|
||||
"cross-env": "^7.0.3",
|
||||
"jest": "^29.7.0",
|
||||
"mwts": "^1.3.0",
|
||||
"mwtsc": "^1.7.2",
|
||||
"ts-jest": "^29.1.2",
|
||||
"typescript": "~5.4.2"
|
||||
"mwtsc": "^1.11.1",
|
||||
"prettier": "^3.3.3",
|
||||
"ts-jest": "^29.2.3",
|
||||
"typescript": "~5.5.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
@ -78,6 +86,6 @@
|
||||
"type": "git",
|
||||
"url": "https://cool-js.com"
|
||||
},
|
||||
"author": "COOL",
|
||||
"author": "lixin&liqiannan",
|
||||
"license": "MIT"
|
||||
}
|
||||
|
19
src/modules/flow/config.ts
Normal file
19
src/modules/flow/config.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { ModuleConfig } from '@cool-midway/core';
|
||||
|
||||
/**
|
||||
* 模块配置
|
||||
*/
|
||||
export default () => {
|
||||
return {
|
||||
// 模块名称
|
||||
name: '流程编排',
|
||||
// 模块描述
|
||||
description: '流程管理、编排、调试、执行',
|
||||
// 中间件,只对本模块有效
|
||||
middlewares: [],
|
||||
// 中间件,全局有效
|
||||
globalMiddlewares: [],
|
||||
// 模块加载顺序,默认为0,值越大越优先加载
|
||||
order: 0,
|
||||
} as ModuleConfig;
|
||||
};
|
42
src/modules/flow/controller/admin/config.ts
Normal file
42
src/modules/flow/controller/admin/config.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import { CoolController, BaseController } from '@cool-midway/core';
|
||||
import { Body, Get, Inject, Post, Query } from '@midwayjs/core';
|
||||
import { FlowConfigEntity } from '../../entity/config';
|
||||
import { FlowConfigService } from '../../service/config';
|
||||
import { NodeTypeKey } from '../../nodes';
|
||||
|
||||
/**
|
||||
* 节点配置
|
||||
*/
|
||||
@CoolController({
|
||||
api: ['add', 'delete', 'update', 'info', 'list', 'page'],
|
||||
entity: FlowConfigEntity,
|
||||
service: FlowConfigService,
|
||||
pageQueryOp: {
|
||||
keyWordLikeFields: ['a.name'],
|
||||
fieldEq: ['a.func', 'a.type'],
|
||||
},
|
||||
listQueryOp: {
|
||||
select: ['a.*'],
|
||||
fieldEq: ['b.type'],
|
||||
},
|
||||
})
|
||||
export class AdminFlowConfigController extends BaseController {
|
||||
@Inject()
|
||||
flowConfigService: FlowConfigService;
|
||||
|
||||
@Get('/all', { summary: '获取所有配置' })
|
||||
async all() {
|
||||
return this.ok(await this.flowConfigService.all());
|
||||
}
|
||||
|
||||
@Post('/config', { summary: '获取节点配置' })
|
||||
async config(@Body('node') node: NodeTypeKey, @Body('type') type: string) {
|
||||
return this.ok(await this.flowConfigService.config(node, type));
|
||||
}
|
||||
|
||||
@Get('/getByNode', { summary: '通过名称获取配置' })
|
||||
async getByNode(@Query('node') node: string, @Query('type') type: string) {
|
||||
const config = await this.flowConfigService.getByNode(node, type);
|
||||
return this.ok(config);
|
||||
}
|
||||
}
|
33
src/modules/flow/controller/admin/info.ts
Normal file
33
src/modules/flow/controller/admin/info.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { CoolController, BaseController } from '@cool-midway/core';
|
||||
import { FlowInfoEntity } from '../../entity/info';
|
||||
import { Body, Inject, Post } from '@midwayjs/core';
|
||||
import { FlowInfoService } from '../../service/info';
|
||||
|
||||
/**
|
||||
* 流程信息
|
||||
*/
|
||||
@CoolController({
|
||||
api: ['add', 'delete', 'update', 'info', 'list', 'page'],
|
||||
entity: FlowInfoEntity,
|
||||
service: FlowInfoService,
|
||||
pageQueryOp: {
|
||||
keyWordLikeFields: ['a.name', 'a.label'],
|
||||
where: ctx => {
|
||||
const { flowId, isRelease } = ctx.request.body;
|
||||
|
||||
return [
|
||||
[ 'a.id != :flowId', { flowId }, flowId ],
|
||||
[ 'a.releaseTime is not null', {}, isRelease ]
|
||||
]
|
||||
}
|
||||
},
|
||||
})
|
||||
export class AdminFlowInfoController extends BaseController {
|
||||
@Inject()
|
||||
flowInfoService: FlowInfoService;
|
||||
|
||||
@Post('/release', { summary: '发布流程' })
|
||||
async release(@Body('flowId') flowId: number) {
|
||||
return this.ok(await this.flowInfoService.release(flowId));
|
||||
}
|
||||
}
|
29
src/modules/flow/controller/admin/log.ts
Normal file
29
src/modules/flow/controller/admin/log.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { CoolController, BaseController } from '@cool-midway/core';
|
||||
import { FlowLogEntity } from '../../entity/log';
|
||||
import { FlowInfoEntity } from '../../entity/info';
|
||||
|
||||
/**
|
||||
* 流程日志
|
||||
*/
|
||||
@CoolController({
|
||||
api: ['delete', 'info', 'list', 'page'],
|
||||
entity: FlowLogEntity,
|
||||
pageQueryOp: {
|
||||
fieldEq: ['flowId', 'type'],
|
||||
select: ['a.*', 'b.name as flowName', 'b.label as flowLabel'],
|
||||
join: [
|
||||
{
|
||||
entity: FlowInfoEntity,
|
||||
alias: 'b',
|
||||
condition: 'a.flowId = b.id',
|
||||
}
|
||||
],
|
||||
where: ctx => {
|
||||
const { createTime } = ctx.request.body;
|
||||
return [
|
||||
['a.createTime between :startTime and :endTime', { startTime: createTime?.[0], endTime: createTime?.[1] }, createTime?.[0] && createTime[1]],
|
||||
]
|
||||
},
|
||||
}
|
||||
})
|
||||
export class AdminFlowLogController extends BaseController { }
|
99
src/modules/flow/controller/admin/run.ts
Normal file
99
src/modules/flow/controller/admin/run.ts
Normal file
@ -0,0 +1,99 @@
|
||||
import { BaseController, CoolController } from '@cool-midway/core';
|
||||
import { Body, Inject, Post } from '@midwayjs/core';
|
||||
import { Context } from 'koa';
|
||||
import { PassThrough } from 'stream';
|
||||
import { FlowRunService } from '../../service/run';
|
||||
|
||||
/**
|
||||
* 流程运行
|
||||
*/
|
||||
@CoolController()
|
||||
export class AdminFlowRunController extends BaseController {
|
||||
@Inject()
|
||||
flowRunService: FlowRunService;
|
||||
|
||||
@Inject()
|
||||
ctx: Context;
|
||||
|
||||
@Post('/debug', { summary: '调试' })
|
||||
async debug(
|
||||
// 参数
|
||||
@Body('params') params: any,
|
||||
// 流程label
|
||||
@Body('label') label: string,
|
||||
// 如果有传递nodeId,则只是调试这个节点
|
||||
@Body('nodeId') nodeId: string,
|
||||
// 是否流式调用
|
||||
@Body('stream') stream = false
|
||||
) {
|
||||
// 设置响应头
|
||||
this.ctx.set('Content-Type', 'text/event-stream');
|
||||
this.ctx.set('Cache-Control', 'no-cache');
|
||||
this.ctx.set('Connection', 'keep-alive');
|
||||
|
||||
const resStream = new PassThrough();
|
||||
// 发送数据
|
||||
const send = (data: any) => {
|
||||
resStream.write(`data:${JSON.stringify(data)}\n\n`);
|
||||
};
|
||||
this.flowRunService
|
||||
.debug(params, label, nodeId, stream, res => {
|
||||
send(res);
|
||||
if (res.isEnd && !stream) {
|
||||
this.ctx.res.end();
|
||||
}
|
||||
})
|
||||
.then(async result => {
|
||||
if (stream) {
|
||||
// 流式输出
|
||||
for await (const chunk of result.stream) {
|
||||
send({ isEnd: false, llmStream: true, content: chunk.toString() });
|
||||
}
|
||||
send({ isEnd: true, llmStream: true });
|
||||
this.ctx.res.end();
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
this.ctx.res.end();
|
||||
});
|
||||
this.ctx.status = 200;
|
||||
this.ctx.body = resStream;
|
||||
}
|
||||
|
||||
@Post('/invoke', { summary: '调用流程' })
|
||||
async invoke(
|
||||
// 参数
|
||||
@Body('params') params: any,
|
||||
// 流程label
|
||||
@Body('label') label: string,
|
||||
// 是否流式调用
|
||||
@Body('stream') stream = false
|
||||
) {
|
||||
if (stream) {
|
||||
// 设置响应头
|
||||
this.ctx.set('Content-Type', 'text/event-stream');
|
||||
this.ctx.set('Cache-Control', 'no-cache');
|
||||
this.ctx.set('Connection', 'keep-alive');
|
||||
|
||||
const stream = new PassThrough();
|
||||
// 发送数据
|
||||
const send = (data: any) => {
|
||||
stream.write(`data:${JSON.stringify(data)}\n\n`);
|
||||
};
|
||||
const result = await this.flowRunService.invoke(params, label, true);
|
||||
const output = async () => {
|
||||
// 流式输出
|
||||
for await (const chunk of result.stream) {
|
||||
send({ isEnd: false, content: chunk.toString() });
|
||||
}
|
||||
send({ isEnd: true });
|
||||
this.ctx.res.end();
|
||||
};
|
||||
output();
|
||||
this.ctx.status = 200;
|
||||
this.ctx.body = stream;
|
||||
} else {
|
||||
return await this.flowRunService.invoke(params, label);
|
||||
}
|
||||
}
|
||||
}
|
58
src/modules/flow/controller/open/run.ts
Normal file
58
src/modules/flow/controller/open/run.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import {
|
||||
BaseController,
|
||||
CoolController,
|
||||
CoolTag,
|
||||
CoolUrlTag,
|
||||
TagTypes,
|
||||
} from '@cool-midway/core';
|
||||
import { Body, Inject, Post } from '@midwayjs/core';
|
||||
import { PassThrough } from 'stream';
|
||||
import { FlowRunService } from '../../service/run';
|
||||
|
||||
/**
|
||||
* 流程运行
|
||||
*/
|
||||
@CoolUrlTag()
|
||||
@CoolController()
|
||||
export class OpenFlowRunController extends BaseController {
|
||||
@Inject()
|
||||
flowRunService: FlowRunService;
|
||||
|
||||
@Inject()
|
||||
ctx;
|
||||
|
||||
@CoolTag(TagTypes.IGNORE_TOKEN)
|
||||
@Post('/invoke', { summary: '调用' })
|
||||
async invoke(
|
||||
@Body('params') params: any,
|
||||
@Body('label') label: string,
|
||||
@Body('stream') stream = false
|
||||
) {
|
||||
if (stream) {
|
||||
// 设置响应头
|
||||
this.ctx.set('Content-Type', 'text/event-stream');
|
||||
this.ctx.set('Cache-Control', 'no-cache');
|
||||
this.ctx.set('Connection', 'keep-alive');
|
||||
|
||||
const stream = new PassThrough();
|
||||
// 发送数据
|
||||
const send = (data: any) => {
|
||||
stream.write(`data:${JSON.stringify(data)}\n\n`);
|
||||
};
|
||||
const result = await this.flowRunService.invoke(params, label, true);
|
||||
const output = async () => {
|
||||
// 流式输出
|
||||
for await (const chunk of result.stream) {
|
||||
send({ isEnd: false, content: chunk.toString() });
|
||||
}
|
||||
send({ isEnd: true });
|
||||
this.ctx.res.end();
|
||||
};
|
||||
output();
|
||||
this.ctx.status = 200;
|
||||
this.ctx.body = stream;
|
||||
} else {
|
||||
return this.ok(await this.flowRunService.invoke(params, label));
|
||||
}
|
||||
}
|
||||
}
|
23
src/modules/flow/entity/config.ts
Normal file
23
src/modules/flow/entity/config.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { BaseEntity } from '@cool-midway/core';
|
||||
import { Column, Entity } from 'typeorm';
|
||||
|
||||
/**
|
||||
* 流程配置
|
||||
*/
|
||||
@Entity('flow_config')
|
||||
export class FlowConfigEntity extends BaseEntity {
|
||||
@Column({ comment: '名称' })
|
||||
name: string;
|
||||
|
||||
@Column({ comment: '描述', nullable: true })
|
||||
description: string;
|
||||
|
||||
@Column({ comment: '类型' })
|
||||
type: string;
|
||||
|
||||
@Column({ comment: '节点' })
|
||||
node: string;
|
||||
|
||||
@Column({ comment: '配置', type: 'json' })
|
||||
options: any;
|
||||
}
|
19
src/modules/flow/entity/data.ts
Normal file
19
src/modules/flow/entity/data.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { BaseEntity } from '@cool-midway/core';
|
||||
import { Column, Entity, Index } from 'typeorm';
|
||||
|
||||
/**
|
||||
* 流程数据
|
||||
*/
|
||||
@Entity('flow_data')
|
||||
export class FlowDataEntity extends BaseEntity {
|
||||
@Index()
|
||||
@Column({ comment: '流程ID' })
|
||||
flowId: number;
|
||||
|
||||
@Index()
|
||||
@Column({ comment: '对象ID' })
|
||||
objectId: string;
|
||||
|
||||
@Column({ comment: '数据', type: 'json' })
|
||||
data: any;
|
||||
}
|
35
src/modules/flow/entity/info.ts
Normal file
35
src/modules/flow/entity/info.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { BaseEntity } from '@cool-midway/core';
|
||||
import { Column, Entity, Index } from 'typeorm';
|
||||
import { FlowGraph } from '../runner/context';
|
||||
|
||||
/**
|
||||
* 流程信息
|
||||
*/
|
||||
@Entity('flow_info')
|
||||
export class FlowInfoEntity extends BaseEntity {
|
||||
@Index()
|
||||
@Column({ comment: '名称' })
|
||||
name: string;
|
||||
|
||||
@Index({ unique: true })
|
||||
@Column({ comment: '标签(可以根据标签调用)', nullable: true })
|
||||
label: string;
|
||||
|
||||
@Column({ comment: '描述', nullable: true })
|
||||
description: string;
|
||||
|
||||
@Column({ comment: '状态 0-禁用 1-禁用', default: 1 })
|
||||
status: number;
|
||||
|
||||
@Column({ comment: '版本', default: 1 })
|
||||
version: number;
|
||||
|
||||
@Column({ comment: '草稿', type: 'json', nullable: true })
|
||||
draft: FlowGraph;
|
||||
|
||||
@Column({ comment: '数据', type: 'json', nullable: true })
|
||||
data: FlowGraph;
|
||||
|
||||
@Column({ comment: '发布时间', type: 'datetime', nullable: true })
|
||||
releaseTime: Date;
|
||||
}
|
23
src/modules/flow/entity/log.ts
Normal file
23
src/modules/flow/entity/log.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { BaseEntity } from '@cool-midway/core';
|
||||
import { Column, Entity } from 'typeorm';
|
||||
|
||||
/**
|
||||
* 流程日志
|
||||
*/
|
||||
@Entity('flow_log')
|
||||
export class FlowLogEntity extends BaseEntity {
|
||||
@Column({ comment: '流程ID' })
|
||||
flowId: number;
|
||||
|
||||
@Column({ comment: '类型 0-失败 1-成功 2-未知', default: 0 })
|
||||
type: number;
|
||||
|
||||
@Column({ comment: '传入参数', type: 'json', nullable: true })
|
||||
inputParams: any;
|
||||
|
||||
@Column({ comment: '结果', type: 'json', nullable: true })
|
||||
result: any;
|
||||
|
||||
@Column({ comment: '节点运行信息', type: 'json', nullable: true })
|
||||
nodeInfo: any[];
|
||||
}
|
185
src/modules/flow/menu.json
Normal file
185
src/modules/flow/menu.json
Normal file
@ -0,0 +1,185 @@
|
||||
[
|
||||
{
|
||||
"name": "流程管理",
|
||||
"router": null,
|
||||
"perms": null,
|
||||
"type": 0,
|
||||
"icon": "icon-activity",
|
||||
"orderNum": 1,
|
||||
"viewPath": null,
|
||||
"keepAlive": true,
|
||||
"isShow": true,
|
||||
"childMenus": [
|
||||
{
|
||||
"name": "流程列表",
|
||||
"router": "/flow/list",
|
||||
"perms": null,
|
||||
"type": 1,
|
||||
"icon": "icon-menu",
|
||||
"orderNum": 1,
|
||||
"viewPath": "modules/flow/views/list.vue",
|
||||
"keepAlive": true,
|
||||
"isShow": true,
|
||||
"childMenus": [
|
||||
{
|
||||
"name": "列表查询",
|
||||
"router": null,
|
||||
"perms": "flow:info:list",
|
||||
"type": 2,
|
||||
"icon": null,
|
||||
"orderNum": 0,
|
||||
"viewPath": null,
|
||||
"keepAlive": true,
|
||||
"isShow": true,
|
||||
"childMenus": []
|
||||
},
|
||||
{
|
||||
"name": "修改",
|
||||
"router": null,
|
||||
"perms": "flow:info:update",
|
||||
"type": 2,
|
||||
"icon": null,
|
||||
"orderNum": 0,
|
||||
"viewPath": null,
|
||||
"keepAlive": true,
|
||||
"isShow": true,
|
||||
"childMenus": []
|
||||
},
|
||||
{
|
||||
"name": "发布流程",
|
||||
"router": null,
|
||||
"perms": "flow:info:release",
|
||||
"type": 2,
|
||||
"icon": null,
|
||||
"orderNum": 0,
|
||||
"viewPath": null,
|
||||
"keepAlive": true,
|
||||
"isShow": true,
|
||||
"childMenus": []
|
||||
},
|
||||
{
|
||||
"name": "单个信息",
|
||||
"router": null,
|
||||
"perms": "flow:info:info",
|
||||
"type": 2,
|
||||
"icon": null,
|
||||
"orderNum": 0,
|
||||
"viewPath": null,
|
||||
"keepAlive": true,
|
||||
"isShow": true,
|
||||
"childMenus": []
|
||||
},
|
||||
{
|
||||
"name": "分页查询",
|
||||
"router": null,
|
||||
"perms": "flow:info:page",
|
||||
"type": 2,
|
||||
"icon": null,
|
||||
"orderNum": 0,
|
||||
"viewPath": null,
|
||||
"keepAlive": true,
|
||||
"isShow": true,
|
||||
"childMenus": []
|
||||
},
|
||||
{
|
||||
"name": "删除",
|
||||
"router": null,
|
||||
"perms": "flow:info:delete",
|
||||
"type": 2,
|
||||
"icon": null,
|
||||
"orderNum": 0,
|
||||
"viewPath": null,
|
||||
"keepAlive": true,
|
||||
"isShow": true,
|
||||
"childMenus": []
|
||||
},
|
||||
{
|
||||
"name": "新增",
|
||||
"router": null,
|
||||
"perms": "flow:info:add",
|
||||
"type": 2,
|
||||
"icon": null,
|
||||
"orderNum": 0,
|
||||
"viewPath": null,
|
||||
"keepAlive": true,
|
||||
"isShow": true,
|
||||
"childMenus": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "流程编排",
|
||||
"router": "/flow/info",
|
||||
"perms": null,
|
||||
"type": 1,
|
||||
"icon": "icon-activity",
|
||||
"orderNum": 2,
|
||||
"viewPath": "modules/flow/views/info.vue",
|
||||
"keepAlive": false,
|
||||
"isShow": false,
|
||||
"childMenus": [
|
||||
{
|
||||
"name": "发布流程",
|
||||
"router": null,
|
||||
"perms": "flow:info:release",
|
||||
"type": 2,
|
||||
"icon": null,
|
||||
"orderNum": 0,
|
||||
"viewPath": null,
|
||||
"keepAlive": true,
|
||||
"isShow": true,
|
||||
"childMenus": []
|
||||
},
|
||||
{
|
||||
"name": "查看流程",
|
||||
"router": null,
|
||||
"perms": "flow:info:info",
|
||||
"type": 2,
|
||||
"icon": null,
|
||||
"orderNum": 0,
|
||||
"viewPath": null,
|
||||
"keepAlive": true,
|
||||
"isShow": true,
|
||||
"childMenus": []
|
||||
},
|
||||
{
|
||||
"name": "修改流程",
|
||||
"router": null,
|
||||
"perms": "flow:info:update",
|
||||
"type": 2,
|
||||
"icon": null,
|
||||
"orderNum": 0,
|
||||
"viewPath": null,
|
||||
"keepAlive": true,
|
||||
"isShow": true,
|
||||
"childMenus": []
|
||||
},
|
||||
{
|
||||
"name": "调试",
|
||||
"router": null,
|
||||
"perms": "flow:run:debug",
|
||||
"type": 2,
|
||||
"icon": null,
|
||||
"orderNum": 0,
|
||||
"viewPath": null,
|
||||
"keepAlive": true,
|
||||
"isShow": true,
|
||||
"childMenus": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "流程配置",
|
||||
"router": null,
|
||||
"perms": "flow:config:node:config,flow:config:node:delete,flow:config:node:update,flow:config:node:types,flow:config:node:page,flow:config:node:add,flow:config:info:getByNode,flow:config:info:delete,flow:config:info:update,flow:config:info:info,flow:config:info:page,flow:config:info:add",
|
||||
"type": 2,
|
||||
"icon": null,
|
||||
"orderNum": 2,
|
||||
"viewPath": null,
|
||||
"keepAlive": true,
|
||||
"isShow": true,
|
||||
"childMenus": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
126
src/modules/flow/nodes/classify/index.ts
Normal file
126
src/modules/flow/nodes/classify/index.ts
Normal file
@ -0,0 +1,126 @@
|
||||
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import { FlowNode } from '../../runner/node';
|
||||
import { FlowContext, FlowGraph } from '../../runner/context';
|
||||
import { FlowResult } from '../../runner/result';
|
||||
import { ChatPromptTemplate } from '@langchain/core/prompts';
|
||||
import { FlowConfigService } from '../../service/config';
|
||||
import { NodeLLMModel } from '../llm/model';
|
||||
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
||||
import { CoolCommException } from '@cool-midway/core';
|
||||
|
||||
/**
|
||||
* 分类器
|
||||
*/
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Prototype)
|
||||
export class NodeClassify extends FlowNode {
|
||||
@Inject()
|
||||
nodeLLMModel: NodeLLMModel;
|
||||
|
||||
@Inject()
|
||||
flowConfigService: FlowConfigService;
|
||||
|
||||
/**
|
||||
* 执行
|
||||
* @param context
|
||||
* @returns
|
||||
*/
|
||||
async run(context: FlowContext): Promise<FlowResult> {
|
||||
const { model, types } = this.config.options;
|
||||
const config = await this.flowConfigService.getOptions(model.configId);
|
||||
const params = this.inputParams;
|
||||
const llm = await this.getModel(model.supplier, {
|
||||
...model.params,
|
||||
...config.comm,
|
||||
});
|
||||
const prompt = await this.getPrompt(types);
|
||||
const chain = prompt.pipe(llm);
|
||||
const res = await chain.invoke({
|
||||
content: params.content,
|
||||
format: '{ "index": "数字类型,分类的序号,如:0" }',
|
||||
});
|
||||
const result: {
|
||||
index: number;
|
||||
} = this.extractJSON(res.content);
|
||||
context.set(`${this.getPrefix()}.index`, result.index, 'output');
|
||||
context.set(`${this.getPrefix()}.content`, types[result.index], 'output');
|
||||
const nextNodeId = await this.nextNodeId(
|
||||
result.index,
|
||||
context.getFlowGraph()
|
||||
);
|
||||
// 更新计数器
|
||||
context.updateCount(
|
||||
'tokenUsage',
|
||||
res.response_metadata.tokenUsage?.totalTokens || 0
|
||||
);
|
||||
return {
|
||||
success: true,
|
||||
result: {
|
||||
index: result.index,
|
||||
content: types[result.index],
|
||||
},
|
||||
next: nextNodeId,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得提示模板
|
||||
* @param content
|
||||
* @returns
|
||||
*/
|
||||
async getPrompt(types: string[]) {
|
||||
// 转换格式
|
||||
const prompt = ChatPromptTemplate.fromMessages([
|
||||
[
|
||||
'system',
|
||||
`根据用户的问题,从下面分类中选择一个,并按照JSON格式 {format} 返回给我。
|
||||
序号${types.map((content, index) => `${index}. ${content}`).join('\n')}`,
|
||||
],
|
||||
['human', '{content}'],
|
||||
]);
|
||||
return prompt;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得模型
|
||||
* @param name 名称
|
||||
* @param options 配置
|
||||
* @returns
|
||||
*/
|
||||
async getModel(name: string, options: any): Promise<BaseChatModel> {
|
||||
const LLM = await this.nodeLLMModel.getModel(name);
|
||||
// @ts-ignore
|
||||
return new LLM(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从文本中提取JSON字符串,并尝试解析为对象
|
||||
* @param str 待提取的文本
|
||||
* @returns 对象
|
||||
*/
|
||||
extractJSON(str) {
|
||||
try {
|
||||
// 使用正则表达式匹配JSON字符串
|
||||
const jsonRegex = /\{(?:[^{}]|(?:\{[^{}]*\}))*\}/g;
|
||||
const jsonStrings = str.match(jsonRegex);
|
||||
|
||||
const result = jsonStrings ? jsonStrings : null;
|
||||
return JSON.parse(result);
|
||||
} catch (e) {
|
||||
throw new CoolCommException('JSON解析失败');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 下一个节点ID
|
||||
* @param index
|
||||
* @param flowGraph
|
||||
*/
|
||||
async nextNodeId(index: number, flowGraph: FlowGraph) {
|
||||
// 找到所有的线
|
||||
const edges = flowGraph.edges.filter(edge => edge.source == this.id);
|
||||
// 找到线中sourceHandle为index的线
|
||||
const edge = edges.find(edge => edge.sourceHandle == `source-${index}`);
|
||||
return edge.target;
|
||||
}
|
||||
}
|
99
src/modules/flow/nodes/code/base.ts
Normal file
99
src/modules/flow/nodes/code/base.ts
Normal file
@ -0,0 +1,99 @@
|
||||
import { MidwayCache } from '@midwayjs/cache-manager';
|
||||
import { IMidwayApplication } from '@midwayjs/core';
|
||||
import { TypeORMDataSourceManager } from '@midwayjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import * as _ from 'lodash';
|
||||
import { PluginService } from '../../../plugin/service/info';
|
||||
|
||||
/**
|
||||
* 代码执行基类
|
||||
*/
|
||||
|
||||
export class BaseCode {
|
||||
/**
|
||||
* 数据源管理器
|
||||
*/
|
||||
typeORMDataSourceManager: TypeORMDataSourceManager;
|
||||
|
||||
/**
|
||||
* 应用
|
||||
*/
|
||||
app: IMidwayApplication;
|
||||
|
||||
/**
|
||||
* 插件服务
|
||||
*/
|
||||
pluginService: PluginService;
|
||||
|
||||
/**
|
||||
* 缓存
|
||||
*/
|
||||
cache: MidwayCache;
|
||||
|
||||
/**
|
||||
* 主函数
|
||||
*/
|
||||
async main(params: any) {}
|
||||
|
||||
/**
|
||||
* 执行sql
|
||||
* @param sql sql语句
|
||||
* @param params 参数
|
||||
* @param dataSource 数据源
|
||||
*/
|
||||
async execSql(sql: string, params = [], dataSource = 'default') {
|
||||
const ormManager = this.typeORMDataSourceManager.getDataSource(dataSource);
|
||||
return await ormManager.query(sql, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得typeorm的Repository
|
||||
* @param entityName
|
||||
* @returns
|
||||
*/
|
||||
async getRepository(entityName: string): Promise<Repository<any>> {
|
||||
const getEntityByName = async () => {
|
||||
const dataSourceNames =
|
||||
this.typeORMDataSourceManager.getDataSourceNames();
|
||||
for (const dataSourceName of dataSourceNames) {
|
||||
const entityMetadatas =
|
||||
await this.typeORMDataSourceManager.getDataSource(dataSourceName)
|
||||
.entityMetadatas;
|
||||
const find = _.find(entityMetadatas, { name: entityName });
|
||||
if (find) {
|
||||
return find.target;
|
||||
}
|
||||
}
|
||||
};
|
||||
const entity = await getEntityByName();
|
||||
const dataSource =
|
||||
this.typeORMDataSourceManager.getDataSourceNameByModel(entity);
|
||||
const ormManager = this.typeORMDataSourceManager.getDataSource(dataSource);
|
||||
return ormManager.getRepository(entityName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用service
|
||||
* @param service 服务
|
||||
* @param method 方法
|
||||
* @param args 参数
|
||||
* @returns
|
||||
*/
|
||||
async invokeService(service: string, method: string, ...args) {
|
||||
const serviceInstance = await this.app
|
||||
.getApplicationContext()
|
||||
.getAsync(service);
|
||||
return serviceInstance[method](...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用插件
|
||||
* @param key 插件的key
|
||||
* @param method 插件的方法
|
||||
* @param args 参数
|
||||
* @returns
|
||||
*/
|
||||
async invokePlugin(key: string, method: string, ...args) {
|
||||
return this.pluginService.invoke(key, method, ...args);
|
||||
}
|
||||
}
|
110
src/modules/flow/nodes/code/index.ts
Normal file
110
src/modules/flow/nodes/code/index.ts
Normal file
@ -0,0 +1,110 @@
|
||||
import {
|
||||
App,
|
||||
IMidwayApplication,
|
||||
Inject,
|
||||
InjectClient,
|
||||
Provide,
|
||||
Scope,
|
||||
ScopeEnum,
|
||||
} from '@midwayjs/core';
|
||||
import { FlowNode } from '../../runner/node';
|
||||
import { FlowContext } from '../../runner/context';
|
||||
import { CoolCommException } from '@cool-midway/core';
|
||||
import * as ts from 'typescript';
|
||||
import { CachingFactory, MidwayCache } from '@midwayjs/cache-manager';
|
||||
import { TypeORMDataSourceManager } from '@midwayjs/typeorm';
|
||||
import { PluginService } from '../../../plugin/service/info';
|
||||
import { BaseCode } from './base';
|
||||
|
||||
/**
|
||||
* 代码执行器
|
||||
*/
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Prototype)
|
||||
export class NodeCode extends FlowNode {
|
||||
@Inject()
|
||||
typeORMDataSourceManager: TypeORMDataSourceManager;
|
||||
|
||||
@App()
|
||||
app: IMidwayApplication;
|
||||
|
||||
@Inject()
|
||||
pluginService: PluginService;
|
||||
|
||||
@InjectClient(CachingFactory, 'default')
|
||||
cache: MidwayCache;
|
||||
|
||||
/**
|
||||
* 执行
|
||||
* @param context
|
||||
*/
|
||||
async run(context: FlowContext) {
|
||||
const { outputParams, options } = this.config;
|
||||
// 获得输入参数
|
||||
const params = this.inputParams;
|
||||
const execResult = await this.exec(options.code, params);
|
||||
for (const param of outputParams) {
|
||||
context.set(
|
||||
`${this.getPrefix()}.${param.field}`,
|
||||
execResult[param.field],
|
||||
'output'
|
||||
);
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
result: execResult,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行代码
|
||||
* @param content
|
||||
* @param params
|
||||
* @returns
|
||||
*/
|
||||
async exec(content: string, params: any) {
|
||||
// @ts-ignore
|
||||
let CoolClass;
|
||||
// @ts-ignore
|
||||
let Base = BaseCode;
|
||||
const script = `
|
||||
${this.convertToJs(content)}
|
||||
CoolClass = Cool;
|
||||
`;
|
||||
eval(script);
|
||||
if (!CoolClass) {
|
||||
throw new CoolCommException('未找到Cool类,请检查代码后重试');
|
||||
}
|
||||
const cool: BaseCode = new CoolClass();
|
||||
cool.app = this.app;
|
||||
cool.cache = this.cache;
|
||||
cool.pluginService = this.pluginService;
|
||||
cool.typeORMDataSourceManager = this.typeORMDataSourceManager;
|
||||
if (!cool.main) {
|
||||
throw new CoolCommException('未找到main函数,请检查代码后重试');
|
||||
}
|
||||
return await cool.main(params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为js
|
||||
* @param content
|
||||
* @returns
|
||||
*/
|
||||
convertToJs(content: string) {
|
||||
return ts.transpile(content, {
|
||||
emitDecoratorMetadata: true,
|
||||
module: ts.ModuleKind.CommonJS,
|
||||
target: ts.ScriptTarget.ES2018,
|
||||
removeComments: true,
|
||||
experimentalDecorators: true,
|
||||
noImplicitThis: true,
|
||||
noUnusedLocals: true,
|
||||
stripInternal: true,
|
||||
skipLibCheck: true,
|
||||
pretty: true,
|
||||
declaration: true,
|
||||
noImplicitAny: false,
|
||||
});
|
||||
}
|
||||
}
|
17
src/modules/flow/nodes/code/template.ts
Normal file
17
src/modules/flow/nodes/code/template.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { BaseCode } from './base';
|
||||
// @ts-ignore
|
||||
import axios from 'axios';
|
||||
// @ts-ignore
|
||||
import * as _ from 'lodash';
|
||||
|
||||
/**
|
||||
* 代码执行
|
||||
*/
|
||||
export class Cool extends BaseCode {
|
||||
/**
|
||||
* 主函数
|
||||
*/
|
||||
async main(params: any) {
|
||||
console.log('Cool main', params);
|
||||
}
|
||||
}
|
36
src/modules/flow/nodes/end/index.ts
Normal file
36
src/modules/flow/nodes/end/index.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import { FlowContext } from '../../runner/context';
|
||||
import { FlowNode } from '../../runner/node';
|
||||
import { FlowStream } from '../../runner/stream';
|
||||
|
||||
/**
|
||||
* 结束
|
||||
*/
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Prototype)
|
||||
export class NodeEnd extends FlowNode {
|
||||
/**
|
||||
* 执行
|
||||
* @param context
|
||||
*/
|
||||
async run(context: FlowContext) {
|
||||
const { outputParams } = this.config;
|
||||
const datas = context.getData('output') as Map<string, any>;
|
||||
let result = {};
|
||||
let stream: FlowStream;
|
||||
for (const param of outputParams) {
|
||||
result[param.field] = datas.get(
|
||||
`${this.getParamPrefix(param)}.${param.name}`
|
||||
);
|
||||
// 如果是流,则设置流
|
||||
if (result[param.field] instanceof FlowStream) {
|
||||
stream = result[param.field];
|
||||
}
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
result,
|
||||
stream,
|
||||
};
|
||||
}
|
||||
}
|
62
src/modules/flow/nodes/flow/index.ts
Normal file
62
src/modules/flow/nodes/flow/index.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import { FlowNode } from '../../runner/node';
|
||||
import { FlowContext } from '../../runner/context';
|
||||
import { FlowResult } from '../../runner/result';
|
||||
import { FlowConfigService } from '../../service/config';
|
||||
import { FlowRunService } from '../../service/run';
|
||||
import { FlowInfoEntity } from '../../entity/info';
|
||||
import { InjectEntityModel } from '@midwayjs/typeorm';
|
||||
import { Equal, Repository } from 'typeorm';
|
||||
|
||||
/**
|
||||
* 流程
|
||||
*/
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Prototype)
|
||||
export class NodeFlow extends FlowNode {
|
||||
|
||||
@InjectEntityModel(FlowInfoEntity)
|
||||
flowInfoEntity: Repository<FlowInfoEntity>;
|
||||
|
||||
@Inject()
|
||||
flowConfigService: FlowConfigService;
|
||||
|
||||
@Inject()
|
||||
flowRunService: FlowRunService;
|
||||
|
||||
/**
|
||||
* 执行
|
||||
* @param context
|
||||
*/
|
||||
async run(context: FlowContext): Promise<FlowResult> {
|
||||
const { outputParams, options } = this.config;
|
||||
|
||||
// 获得输入参数
|
||||
const params = this.inputParams;
|
||||
|
||||
// 获取流程label
|
||||
const flowInfo = await this.flowInfoEntity.findOneBy({
|
||||
id: Equal(options.flowId),
|
||||
});
|
||||
|
||||
if (!flowInfo) {
|
||||
throw new Error('流程不存在');
|
||||
}
|
||||
|
||||
// 执行流程
|
||||
const { result } = await this.flowRunService.invoke(params, flowInfo.label, true);
|
||||
|
||||
for (const param of outputParams) {
|
||||
context.set(
|
||||
`${this.getPrefix()}.${param.field}`,
|
||||
result[param.field],
|
||||
'output'
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
result: result,
|
||||
};
|
||||
}
|
||||
}
|
54
src/modules/flow/nodes/index.ts
Normal file
54
src/modules/flow/nodes/index.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import { NodeCode } from './code';
|
||||
import { NodeEnd } from './end';
|
||||
import { NodeJudge } from './judge';
|
||||
import { NodeLLM } from './llm';
|
||||
import { ConfigLLM } from './llm/config';
|
||||
import { NodeClassify } from './classify';
|
||||
import { NodeStart } from './start';
|
||||
import { NodeKnow } from './know';
|
||||
import { NodeFlow } from './flow';
|
||||
import { NodeParse } from './parse';
|
||||
import { NodeVariable } from './variable';
|
||||
|
||||
/**
|
||||
* 节点类型
|
||||
*/
|
||||
export const NodeType = {
|
||||
// 开始节点
|
||||
start: NodeStart,
|
||||
// LLM大模型
|
||||
llm: NodeLLM,
|
||||
// code 代码执行器节点
|
||||
code: NodeCode,
|
||||
// 判断器
|
||||
judge: NodeJudge,
|
||||
// 分类
|
||||
classify: NodeClassify,
|
||||
// 知识库
|
||||
know: NodeKnow,
|
||||
// 流程
|
||||
flow: NodeFlow,
|
||||
// 智能解析
|
||||
parse: NodeParse,
|
||||
// 变量
|
||||
variable: NodeVariable,
|
||||
// 结束节点
|
||||
end: NodeEnd,
|
||||
};
|
||||
|
||||
// 节点类型键
|
||||
export type NodeTypeKey = keyof typeof NodeType;
|
||||
|
||||
// 节点配置
|
||||
export const NodeConfig = {
|
||||
// LLM大模型
|
||||
llm: ConfigLLM,
|
||||
};
|
||||
|
||||
// 节点配置
|
||||
export const AllConfig = [
|
||||
{
|
||||
title: '大模型LLM',
|
||||
type: 'llm',
|
||||
},
|
||||
];
|
165
src/modules/flow/nodes/judge/index.ts
Normal file
165
src/modules/flow/nodes/judge/index.ts
Normal file
@ -0,0 +1,165 @@
|
||||
import { Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import { FlowNode } from '../../runner/node';
|
||||
import { FlowContext, FlowGraph } from '../../runner/context';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
/**
|
||||
* 条件
|
||||
*/
|
||||
interface Condition {
|
||||
/** 条件 */
|
||||
condition: string;
|
||||
/** 节点ID */
|
||||
nodeId: string;
|
||||
/** 节点类型 */
|
||||
nodeType: string;
|
||||
/** 字段 */
|
||||
field: string;
|
||||
/** 操作符 */
|
||||
operator: string;
|
||||
/** 值 */
|
||||
value: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置
|
||||
*/
|
||||
interface Options {
|
||||
IF: Condition[];
|
||||
ELSE: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 对比结果
|
||||
*/
|
||||
interface EqResult {
|
||||
/** 结果 */
|
||||
result: boolean;
|
||||
/** 操作符 */
|
||||
operator: 'AND' | 'OR';
|
||||
}
|
||||
|
||||
// 条件对应的方法
|
||||
const methods = {
|
||||
/** 包含 */
|
||||
include: (paramValue: string, value: string) => _.includes(paramValue, value),
|
||||
/** 不包含 */
|
||||
exclude: (paramValue: string, value: string) =>
|
||||
!_.includes(paramValue, value),
|
||||
/** 开始是 */
|
||||
startWith: (paramValue: string, value: string) =>
|
||||
_.startsWith(paramValue, value),
|
||||
/** 结束是 */
|
||||
endWith: (paramValue: string, value: string) => _.endsWith(paramValue, value),
|
||||
/** 等于 */
|
||||
equal: (paramValue: string, value: string) => _.isEqual(paramValue, value),
|
||||
/** 不等于 */
|
||||
notEqual: (paramValue: string, value: string) =>
|
||||
!_.isEqual(paramValue, value),
|
||||
/** 大于 */
|
||||
greaterThan: (paramValue: string, value: string) => _.gt(paramValue, value),
|
||||
/** 大于等于 */
|
||||
greaterThanOrEqual: (paramValue: string, value: string) =>
|
||||
_.gte(paramValue, value),
|
||||
/** 小于 */
|
||||
lessThan: (paramValue: string, value: string) => _.lt(paramValue, value),
|
||||
/** 小于等于 */
|
||||
lessThanOrEqual: (paramValue: string, value: string) =>
|
||||
_.lte(paramValue, value),
|
||||
/** 为空 */
|
||||
isNull: (paramValue: string) => _.isNull(paramValue),
|
||||
/** 不为空 */
|
||||
isNotNull: (paramValue: string) => !_.isNull(paramValue),
|
||||
};
|
||||
|
||||
/**
|
||||
* 判断器
|
||||
*/
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Prototype)
|
||||
export class NodeJudge extends FlowNode {
|
||||
/**
|
||||
* 执行
|
||||
* @param context
|
||||
*/
|
||||
async run(context: FlowContext) {
|
||||
const { IF } = this.config.options as Options;
|
||||
const datas = context.getData('output') as Map<string, any>;
|
||||
const eqResult: EqResult[] = [];
|
||||
for (const item of IF) {
|
||||
const paramValue = datas.get(
|
||||
`${item.nodeType}.${item.nodeId}.${item.field}`
|
||||
);
|
||||
const value = item.value;
|
||||
const result = await this.eq(paramValue, value, item.condition);
|
||||
eqResult.push({
|
||||
result: result,
|
||||
operator: item.operator as 'AND' | 'OR',
|
||||
});
|
||||
}
|
||||
const result = await this.result(eqResult);
|
||||
context.set(`${this.getPrefix()}.result`, result, 'output');
|
||||
const next = await this.nextNodeId(context.getFlowGraph(), result);
|
||||
return {
|
||||
success: true,
|
||||
result,
|
||||
next,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 下一个节点ID
|
||||
* @param flowGraph
|
||||
* @param result
|
||||
* @returns
|
||||
*/
|
||||
async nextNodeId(flowGraph: FlowGraph, result: boolean) {
|
||||
// 找到所有的线
|
||||
const edges = flowGraph.edges.filter(edge => edge.source == this.id);
|
||||
// 找到线中sourceHandle为 source-if 或 source-else 的线
|
||||
const edge = edges.find(
|
||||
edge => edge.sourceHandle == (result ? 'source-if' : 'source-else')
|
||||
);
|
||||
return edge.target;
|
||||
}
|
||||
|
||||
/**
|
||||
* 结果
|
||||
* @param eqResult
|
||||
*/
|
||||
async result(eqResult: EqResult[]) {
|
||||
let result = null;
|
||||
for (const item of eqResult) {
|
||||
if (result == null) {
|
||||
result = item.result;
|
||||
continue;
|
||||
}
|
||||
if (item.operator == 'AND') {
|
||||
result = result && item.result;
|
||||
} else {
|
||||
result = result || item.result;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对比
|
||||
* @param paramValue
|
||||
* @param value
|
||||
* @param condition
|
||||
* @returns
|
||||
*/
|
||||
async eq(paramValue: string, value: string, condition: string) {
|
||||
const method = await this.conditonMethod(condition);
|
||||
return method(paramValue, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将条件转为 lodash 具体方法
|
||||
* @param condition
|
||||
*/
|
||||
async conditonMethod(condition: string) {
|
||||
return methods[condition];
|
||||
}
|
||||
}
|
38
src/modules/flow/nodes/know/index.ts
Normal file
38
src/modules/flow/nodes/know/index.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import { FlowNode } from '../../runner/node';
|
||||
import { FlowContext } from '../../runner/context';
|
||||
import { KnowRetrieverService } from '../../../know/service/retriever';
|
||||
|
||||
/**
|
||||
* 知识库节点
|
||||
*/
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Prototype)
|
||||
export class NodeKnow extends FlowNode {
|
||||
@Inject()
|
||||
knowRetrieverService: KnowRetrieverService;
|
||||
|
||||
/**
|
||||
* 执行
|
||||
* @param context
|
||||
* @returns
|
||||
*/
|
||||
async run(context: FlowContext) {
|
||||
const { knowIds, size } = this.config.options;
|
||||
const params = this.inputParams;
|
||||
const { text } = params;
|
||||
const documents = await this.knowRetrieverService.search(knowIds, text, {
|
||||
size,
|
||||
});
|
||||
context.set(`${this.getPrefix()}.documents`, documents, 'output');
|
||||
const str = documents.map(item => item.pageContent).join('/n/r');
|
||||
context.set(`${this.getPrefix()}.text`, str, 'output');
|
||||
return {
|
||||
success: true,
|
||||
result: {
|
||||
documents,
|
||||
text: str,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
174
src/modules/flow/nodes/llm/config.ts
Normal file
174
src/modules/flow/nodes/llm/config.ts
Normal file
@ -0,0 +1,174 @@
|
||||
import { LLMModelType } from './model';
|
||||
|
||||
/**
|
||||
* 配置模板
|
||||
*/
|
||||
export const ConfigLLM: { [key in LLMModelType]?: any } = {
|
||||
// zhipu
|
||||
zhipu: {
|
||||
// 通用配置
|
||||
comm: {
|
||||
apiKey: 'api key',
|
||||
},
|
||||
// 专有配置
|
||||
options: [
|
||||
{
|
||||
field: 'model',
|
||||
title: '智谱AI',
|
||||
select: [
|
||||
'glm-4-0520',
|
||||
'glm-4',
|
||||
'glm-4-air',
|
||||
'glm-4-airx',
|
||||
'glm-4-flash',
|
||||
],
|
||||
default: 'glm-4-0520',
|
||||
},
|
||||
{
|
||||
field: 'temperature',
|
||||
type: 'number',
|
||||
title: '温度',
|
||||
default: 0.7,
|
||||
enable: true,
|
||||
max: 1,
|
||||
min: 0.1,
|
||||
supports: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
// tongyi
|
||||
tongyi: {
|
||||
// 通用配置
|
||||
comm: {
|
||||
alibabaApiKey: 'api key',
|
||||
},
|
||||
// 专有配置
|
||||
options: [
|
||||
{
|
||||
field: 'model',
|
||||
title: '通义千问',
|
||||
select: [
|
||||
'qwen-turbo',
|
||||
'qwen-plus',
|
||||
'qwen-max',
|
||||
'qwen-max-0428',
|
||||
'qwen-max-0403',
|
||||
'qwen-max-0107',
|
||||
'qwen-max-longcontext',
|
||||
],
|
||||
default: 'qwen-turbo',
|
||||
},
|
||||
{
|
||||
field: 'temperature',
|
||||
type: 'number',
|
||||
title: '温度',
|
||||
default: 0.7,
|
||||
enable: true,
|
||||
max: 1,
|
||||
min: 0.1,
|
||||
supports: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
// minimax
|
||||
minimax: {
|
||||
// 通用配置
|
||||
comm: {
|
||||
minimaxApiKey: 'minimax 的api key',
|
||||
minimaxGroupId: 'minimax 的group id',
|
||||
},
|
||||
// 专有配置
|
||||
options: [
|
||||
{
|
||||
field: 'model',
|
||||
title: 'MINIMAX',
|
||||
select: [
|
||||
'abab6.5-chat',
|
||||
'abab6.5s-chat',
|
||||
'abab5.5s-chat',
|
||||
'abab5.5-chat',
|
||||
],
|
||||
default: 'abab6.5-chat',
|
||||
},
|
||||
{
|
||||
field: 'temperature',
|
||||
type: 'number',
|
||||
title: '温度',
|
||||
default: 0.7,
|
||||
enable: true,
|
||||
max: 1,
|
||||
min: 0.1,
|
||||
supports: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
// openai
|
||||
openai: {
|
||||
// 通用配置
|
||||
comm: {
|
||||
apiKey: 'API密钥',
|
||||
configuration: {
|
||||
baseURL: '基础路径一般需要带/v1',
|
||||
},
|
||||
},
|
||||
// 专有配置
|
||||
options: [
|
||||
{
|
||||
field: 'model',
|
||||
title: 'Open AI',
|
||||
select: [
|
||||
'gpt-3.5-turbo',
|
||||
'gpt-3.5-turbo-16k',
|
||||
'gpt-4-turbo',
|
||||
'gpt-4-turbo-preview',
|
||||
],
|
||||
default: 'gpt-3.5-turbo',
|
||||
},
|
||||
{
|
||||
field: 'temperature',
|
||||
type: 'number',
|
||||
title: '温度',
|
||||
default: 0.7,
|
||||
enable: true,
|
||||
max: 1,
|
||||
min: 0.1,
|
||||
supports: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
// ollama
|
||||
ollama: {
|
||||
// 通用配置
|
||||
comm: {
|
||||
baseUrl: '请求地址,如:http://localhost:11434',
|
||||
},
|
||||
// 专有配置
|
||||
options: [
|
||||
{
|
||||
field: 'model',
|
||||
title: 'Ollama 本地大模型',
|
||||
select: ['qwen2:7b', 'qwen2:72b', 'llama3:8b', 'llama3:70b'],
|
||||
default: 'qwen2:7b',
|
||||
},
|
||||
{
|
||||
field: 'temperature',
|
||||
type: 'number',
|
||||
title: '温度',
|
||||
default: 0.7,
|
||||
enable: true,
|
||||
max: 1,
|
||||
min: 0.1,
|
||||
supports: [],
|
||||
},
|
||||
{
|
||||
field: 'keepAlive',
|
||||
type: 'string',
|
||||
title: '留存',
|
||||
default: '-1s',
|
||||
enable: true,
|
||||
tips: '-1s表示永久留存,0s表示不留存,其他数字表示留存时间,单位为秒',
|
||||
supports: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
191
src/modules/flow/nodes/llm/index.ts
Normal file
191
src/modules/flow/nodes/llm/index.ts
Normal file
@ -0,0 +1,191 @@
|
||||
import { CoolCommException } from '@cool-midway/core';
|
||||
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
||||
import { AIMessage, HumanMessage } from '@langchain/core/messages';
|
||||
import { ChatPromptTemplate, PromptTemplate } from '@langchain/core/prompts';
|
||||
import { IterableReadableStream } from '@langchain/core/utils/stream';
|
||||
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import { FlowContext } from '../../runner/context';
|
||||
import { FlowNode } from '../../runner/node';
|
||||
import { FlowStream } from '../../runner/stream';
|
||||
import { FlowConfigService } from '../../service/config';
|
||||
import { FlowDataService } from '../../service/data';
|
||||
import { NodeLLMModel } from './model';
|
||||
|
||||
// 消息体
|
||||
interface Message {
|
||||
// 角色
|
||||
role: 'system' | 'user' | 'assistant';
|
||||
// 内容
|
||||
content: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* LLM大模型节点
|
||||
*/
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Prototype)
|
||||
export class NodeLLM extends FlowNode {
|
||||
@Inject()
|
||||
nodeLLMModel: NodeLLMModel;
|
||||
|
||||
@Inject()
|
||||
flowConfigService: FlowConfigService;
|
||||
|
||||
@Inject()
|
||||
flowDataService: FlowDataService;
|
||||
|
||||
/**
|
||||
* 执行
|
||||
* @param context
|
||||
*/
|
||||
async run(context: FlowContext) {
|
||||
let { model, messages, history } = this.config.options;
|
||||
const config = await this.flowConfigService.getOptions(model.configId);
|
||||
const llm = await this.getModel(model.supplier, {
|
||||
...model.params,
|
||||
...config.comm,
|
||||
});
|
||||
const params = this.inputParams;
|
||||
const prompt = await this.getPrompt(messages, context, params, history);
|
||||
const chain = prompt.pipe(llm);
|
||||
let stream: FlowStream;
|
||||
let res;
|
||||
if (context.isStream()) {
|
||||
stream = new FlowStream();
|
||||
const _stream: IterableReadableStream<any> = await chain.stream(params);
|
||||
const content = [];
|
||||
const save = async () => {
|
||||
// 流式输出
|
||||
for await (const chunk of _stream) {
|
||||
content.push(chunk.content);
|
||||
stream.push(chunk.content);
|
||||
}
|
||||
stream.push(null);
|
||||
// 保存历史
|
||||
this.saveHistory(messages, context, history, content.join(''));
|
||||
};
|
||||
save();
|
||||
} else {
|
||||
res = await chain.invoke(params);
|
||||
// 保存历史
|
||||
await this.saveHistory(messages, context, history, res?.content);
|
||||
}
|
||||
context.set(`${this.getPrefix()}.text`, res?.content, 'output');
|
||||
context.set(`${this.getPrefix()}.stream`, stream, 'output');
|
||||
context.updateCount(
|
||||
'tokenUsage',
|
||||
res?.response_metadata.tokenUsage?.totalTokens || 0
|
||||
);
|
||||
return {
|
||||
success: true,
|
||||
stream,
|
||||
result: {
|
||||
text: res?.content,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得提示模板
|
||||
* @param messages
|
||||
* @returns
|
||||
*/
|
||||
async getPrompt(
|
||||
messages: Message[],
|
||||
context: FlowContext,
|
||||
params: any,
|
||||
history = 0
|
||||
) {
|
||||
const type = {
|
||||
system: 'system',
|
||||
user: 'human',
|
||||
placeholder: 'placeholder',
|
||||
assistant: 'ai',
|
||||
};
|
||||
let prompt;
|
||||
if (history) {
|
||||
const objectId = context.getRequestParams().objectId;
|
||||
if (!objectId) {
|
||||
throw new CoolCommException(
|
||||
'需要保存历史信息,请求参数必须包含objectId'
|
||||
);
|
||||
}
|
||||
// 取出历史
|
||||
const historyMessages =
|
||||
(await this.flowDataService.get(this.flowId, objectId)) || [];
|
||||
prompt = ChatPromptTemplate.fromMessages([
|
||||
[type[messages[0].role], messages[0].content],
|
||||
['placeholder', '{chat_history}'],
|
||||
// @ts-ignore
|
||||
...messages
|
||||
.filter(item => item.role != 'system')
|
||||
.map(item => {
|
||||
return [type[item.role], item.content];
|
||||
}),
|
||||
]);
|
||||
params['chat_history'] = historyMessages.map(item => {
|
||||
return item.role == 'user'
|
||||
? new HumanMessage({ content: item.content })
|
||||
: new AIMessage({ content: item.content });
|
||||
});
|
||||
} else {
|
||||
// 转换格式
|
||||
prompt = ChatPromptTemplate.fromMessages(
|
||||
messages.map(item => {
|
||||
return [type[item.role], item.content];
|
||||
})
|
||||
);
|
||||
}
|
||||
return prompt;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存历史
|
||||
* @param messages
|
||||
* @param context
|
||||
* @param objectId
|
||||
*/
|
||||
async saveHistory(
|
||||
messages: Message[],
|
||||
context: FlowContext,
|
||||
history = 0,
|
||||
newContent: string
|
||||
) {
|
||||
const params = this.inputParams;
|
||||
const objectId = context.getRequestParams().objectId;
|
||||
if (!objectId || !history) return;
|
||||
|
||||
const historyMessages =
|
||||
(await this.flowDataService.get(this.flowId, objectId)) || [];
|
||||
const lastMessage = messages.filter(item => item.role === 'user').pop();
|
||||
const userContent = await PromptTemplate.fromTemplate(
|
||||
lastMessage.content
|
||||
).invoke(params);
|
||||
const userMessage = {
|
||||
role: 'user',
|
||||
content: userContent.value,
|
||||
};
|
||||
const newMessages = historyMessages
|
||||
.concat(userMessage)
|
||||
.concat({ role: 'assistant', content: newContent });
|
||||
|
||||
const totalLength = newMessages.length;
|
||||
if (totalLength > history) {
|
||||
newMessages.splice(0, totalLength - history);
|
||||
}
|
||||
|
||||
await this.flowDataService.set(this.flowId, objectId, newMessages);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得模型
|
||||
* @param name 名称
|
||||
* @param options 配置
|
||||
* @returns
|
||||
*/
|
||||
async getModel(name: string, options: any): Promise<BaseChatModel> {
|
||||
const LLM = await this.nodeLLMModel.getModel(name);
|
||||
// @ts-ignore
|
||||
return new LLM(options);
|
||||
}
|
||||
}
|
42
src/modules/flow/nodes/llm/model.ts
Normal file
42
src/modules/flow/nodes/llm/model.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import { ChatMinimax } from '@langchain/community/chat_models/minimax';
|
||||
import { ChatAlibabaTongyi } from '@langchain/community/chat_models/alibaba_tongyi';
|
||||
import { ChatZhipuAI } from '@langchain/community/chat_models/zhipuai';
|
||||
import { Provide } from '@midwayjs/core';
|
||||
import { ChatOpenAI } from '@langchain/openai';
|
||||
import { ChatOllama } from '@langchain/community/chat_models/ollama';
|
||||
|
||||
/**
|
||||
* 模型类型
|
||||
*/
|
||||
export const CommModel = {
|
||||
// openai
|
||||
openai: ChatOpenAI,
|
||||
// minimax
|
||||
minimax: ChatMinimax,
|
||||
// 通义千问
|
||||
tongyi: ChatAlibabaTongyi,
|
||||
// 智谱AI
|
||||
zhipu: ChatZhipuAI,
|
||||
// ollama 本地大模型
|
||||
ollama: ChatOllama,
|
||||
};
|
||||
|
||||
// LLM类型键
|
||||
export type LLMModelType = keyof typeof CommModel;
|
||||
|
||||
/**
|
||||
* LLM大模型节点
|
||||
*/
|
||||
@Provide()
|
||||
export class NodeLLMModel {
|
||||
/**
|
||||
* 获得模型
|
||||
* @param name
|
||||
* @returns
|
||||
*/
|
||||
async getModel(
|
||||
name: string
|
||||
): Promise<ChatOpenAI | ChatMinimax | ChatAlibabaTongyi> {
|
||||
return CommModel[name];
|
||||
}
|
||||
}
|
142
src/modules/flow/nodes/parse/index.ts
Normal file
142
src/modules/flow/nodes/parse/index.ts
Normal file
@ -0,0 +1,142 @@
|
||||
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import { FlowNode } from '../../runner/node';
|
||||
import { FlowContext } from '../../runner/context';
|
||||
import { FlowResult } from '../../runner/result';
|
||||
import { FlowConfigService } from '../../service/config';
|
||||
import { NodeLLMModel } from '../llm/model';
|
||||
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
||||
import { ChatPromptTemplate } from '@langchain/core/prompts';
|
||||
|
||||
/**
|
||||
* 解析器
|
||||
*/
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Prototype)
|
||||
export class NodeParse extends FlowNode {
|
||||
@Inject()
|
||||
nodeLLMModel: NodeLLMModel;
|
||||
|
||||
@Inject()
|
||||
flowConfigService: FlowConfigService;
|
||||
|
||||
/**
|
||||
* 执行
|
||||
* @param context
|
||||
*/
|
||||
async run(context: FlowContext): Promise<FlowResult> {
|
||||
const { outputParams } = this.config;
|
||||
const { model } = this.config.options;
|
||||
|
||||
// 获得输入参数
|
||||
const params = this.inputParams;
|
||||
|
||||
// 获取流程配置
|
||||
const config = await this.flowConfigService.getOptions(model.configId);
|
||||
|
||||
// 获取模型
|
||||
const llm = await this.getModel(model.supplier, {
|
||||
...model.params,
|
||||
...config.comm,
|
||||
});
|
||||
|
||||
// 获得提示配置
|
||||
const prompt = await this.getPrompt();
|
||||
const chain = prompt.pipe(llm);
|
||||
|
||||
// 获得提示格式
|
||||
const format = await this.getFormat(outputParams);
|
||||
const res = await chain.invoke({
|
||||
content: `input: ${params.text} format: '{${format}}'`,
|
||||
});
|
||||
|
||||
// 获取执行结果
|
||||
const extractResult = this.extractJSON(res.content);
|
||||
|
||||
for (const param of outputParams) {
|
||||
if (param.field == 'result') {
|
||||
context.set(
|
||||
`${this.getPrefix()}.${param.field}`,
|
||||
extractResult,
|
||||
'output'
|
||||
);
|
||||
} else {
|
||||
context.set(
|
||||
`${this.getPrefix()}.${param.field}`,
|
||||
extractResult[param.field],
|
||||
'output'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新计数器
|
||||
context.updateCount(
|
||||
'tokenUsage',
|
||||
res.response_metadata.tokenUsage?.totalTokens || 0
|
||||
);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
result: {
|
||||
...extractResult,
|
||||
result: extractResult,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得提示模板
|
||||
* @returns
|
||||
*/
|
||||
async getPrompt() {
|
||||
// 转换格式
|
||||
const prompt = ChatPromptTemplate.fromMessages([
|
||||
[
|
||||
'system',
|
||||
`You are now acting as an information extraction tool. When you receive any input, you need to extract the relevant information from it and output the extracted information in the requested JSON format, without replying with any other irrelevant content.`,
|
||||
],
|
||||
['human', '{content}'],
|
||||
]);
|
||||
|
||||
return prompt;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得提示格式
|
||||
* @param outputParams // 输出参数
|
||||
* @returns
|
||||
*/
|
||||
async getFormat(outputParams) {
|
||||
return (
|
||||
outputParams
|
||||
?.filter(param => param.field != 'result')
|
||||
?.map(param => `"${param.field}": "${param.type}"`)
|
||||
?.join(',') ?? ''
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从文本中提取JSON字符串,并尝试解析为对象
|
||||
* @param str 待提取的文本
|
||||
* @returns 对象
|
||||
*/
|
||||
extractJSON(str) {
|
||||
// 使用正则表达式匹配JSON字符串
|
||||
const jsonRegex = /\{(?:[^{}]|(?:\{[^{}]*\}))*\}/g;
|
||||
const jsonStrings = str.match(jsonRegex);
|
||||
|
||||
const result = jsonStrings ? jsonStrings : null;
|
||||
return JSON.parse(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得模型
|
||||
* @param name 名称
|
||||
* @param options 配置
|
||||
* @returns
|
||||
*/
|
||||
async getModel(name: string, options: any): Promise<BaseChatModel> {
|
||||
const LLM = await this.nodeLLMModel.getModel(name);
|
||||
// @ts-ignore
|
||||
return new LLM(options);
|
||||
}
|
||||
}
|
92
src/modules/flow/nodes/start/index.ts
Normal file
92
src/modules/flow/nodes/start/index.ts
Normal file
@ -0,0 +1,92 @@
|
||||
import { Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import { FlowNode } from '../../runner/node';
|
||||
import { FlowContext } from '../../runner/context';
|
||||
import { CoolCommException } from '@cool-midway/core';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
/**
|
||||
* 开始节点
|
||||
*/
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Prototype)
|
||||
export class NodeStart extends FlowNode {
|
||||
/**
|
||||
* 执行
|
||||
* @param context
|
||||
*/
|
||||
async run(context: FlowContext) {
|
||||
const { inputParams } = this.config;
|
||||
// 获得请求参数(带值的)
|
||||
const requestParams = context.getRequestParams();
|
||||
const reuslt = {};
|
||||
// 校验并设置输出参数
|
||||
for (const param of inputParams) {
|
||||
let value = requestParams[param.name];
|
||||
if (param.required && this.isEmptyValue(value)) {
|
||||
throw new CoolCommException(`参数 ${param.name} 为必填`);
|
||||
}
|
||||
const checkType = this.checkType(value, param.type);
|
||||
if (value !== undefined && !checkType) {
|
||||
throw new CoolCommException(`参数 ${param.name} 类型错误`);
|
||||
} else {
|
||||
value = this.transformValue(value, param.type);
|
||||
}
|
||||
reuslt[param.field] = value;
|
||||
context.set(`${this.getPrefix()}.${param.name}`, value, 'output');
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
result: reuslt,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为空值
|
||||
* @param value
|
||||
* @returns
|
||||
*/
|
||||
isEmptyValue(value) {
|
||||
// 检查是否是 null 或 undefined
|
||||
if (_.isNil(value)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 特别对待非容器类型(如数字和布尔值)
|
||||
if (_.isNumber(value) || _.isBoolean(value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 对字符串、数组、对象使用 _.isEmpty
|
||||
return _.isEmpty(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换值
|
||||
* @param value
|
||||
* @param type
|
||||
*/
|
||||
transformValue(value: any, type: string) {
|
||||
if (type == 'number') {
|
||||
return Number(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 字段类型
|
||||
* @param value
|
||||
* @returns
|
||||
*/
|
||||
checkType(value: any, type: string) {
|
||||
if (type == 'image') {
|
||||
return typeof value === 'string' && value.startsWith('http');
|
||||
}
|
||||
if (type == 'text') {
|
||||
return typeof value === 'string';
|
||||
}
|
||||
if (type == 'number') {
|
||||
return !isNaN(value);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
86
src/modules/flow/nodes/variable/index.ts
Normal file
86
src/modules/flow/nodes/variable/index.ts
Normal file
@ -0,0 +1,86 @@
|
||||
import { Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import { FlowNode } from '../../runner/node';
|
||||
import { FlowContext } from '../../runner/context';
|
||||
import { FlowResult } from '../../runner/result';
|
||||
import * as ts from 'typescript';
|
||||
import { CoolCommException } from '@cool-midway/core';
|
||||
|
||||
/**
|
||||
* 变量
|
||||
*/
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Prototype)
|
||||
export class NodeVariable extends FlowNode {
|
||||
/**
|
||||
* 执行
|
||||
* @param context
|
||||
*/
|
||||
async run(context: FlowContext): Promise<FlowResult> {
|
||||
const { inputParams, outputParams } = this.config;
|
||||
const datas = context.getData('output') as Map<string, any>;
|
||||
let result = {};
|
||||
// 输入
|
||||
for (const param of inputParams) {
|
||||
const value = param.value
|
||||
? param.value
|
||||
: datas.get(`${this.getParamPrefix(param)}.${param.name}`);
|
||||
result[param.field] = value;
|
||||
}
|
||||
this.inputParams = result;
|
||||
// 执行代码进行转换
|
||||
result = await this.exec(this.config.options.code, result);
|
||||
// 输出
|
||||
for (const param of outputParams) {
|
||||
context.set(
|
||||
`${this.getPrefix()}.${param.field}`,
|
||||
result[param.field],
|
||||
'output'
|
||||
);
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
result,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行代码
|
||||
* @param content
|
||||
* @param params
|
||||
* @returns
|
||||
*/
|
||||
async exec(content: string, params: any) {
|
||||
let funcMain;
|
||||
const script = `
|
||||
${this.convertToJs(content)}
|
||||
funcMain = main;
|
||||
`;
|
||||
eval(script);
|
||||
if (!funcMain) {
|
||||
throw new CoolCommException('未找到main函数,请检查代码后重试');
|
||||
}
|
||||
return await funcMain(params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为js
|
||||
* @param content
|
||||
* @returns
|
||||
*/
|
||||
convertToJs(content: string) {
|
||||
return ts.transpile(content, {
|
||||
emitDecoratorMetadata: true,
|
||||
module: ts.ModuleKind.CommonJS,
|
||||
target: ts.ScriptTarget.ES2018,
|
||||
removeComments: true,
|
||||
experimentalDecorators: true,
|
||||
noImplicitThis: true,
|
||||
noUnusedLocals: true,
|
||||
stripInternal: true,
|
||||
skipLibCheck: true,
|
||||
pretty: true,
|
||||
declaration: true,
|
||||
noImplicitAny: false,
|
||||
});
|
||||
}
|
||||
}
|
9
src/modules/flow/package.json
Normal file
9
src/modules/flow/package.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"langchain": "^0.2.1",
|
||||
"@langchain/core": "^0.2.0",
|
||||
"@langchain/community": "^0.2.2",
|
||||
"@langchain/openai": "^0.0.33",
|
||||
"@langchain/cohere": "^0.1.0"
|
||||
}
|
||||
}
|
237
src/modules/flow/runner/context.ts
Normal file
237
src/modules/flow/runner/context.ts
Normal file
@ -0,0 +1,237 @@
|
||||
/**
|
||||
* 上下文数据
|
||||
*/
|
||||
export interface FlowContextData {
|
||||
input: Map<string, any>;
|
||||
output: Map<string, any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 节点执行信息
|
||||
*/
|
||||
export interface FlowNodeExec {
|
||||
// 当前节点
|
||||
current: string;
|
||||
// 上一个节点
|
||||
prev: string;
|
||||
// 下一个节点
|
||||
next: string;
|
||||
// 下一个节点列表
|
||||
nextList: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 节点信息
|
||||
*/
|
||||
export interface NodeInfo {
|
||||
enable?: boolean;
|
||||
id?: string;
|
||||
label?: string;
|
||||
type?: string;
|
||||
icon?: string;
|
||||
name?: `node-${string}`;
|
||||
position?: {
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
form?: {
|
||||
width?: string;
|
||||
focus?: string;
|
||||
items: any[];
|
||||
};
|
||||
handle?: {
|
||||
target?: boolean;
|
||||
source?: boolean;
|
||||
next?: { label: string; value: string; [key: string]: any }[];
|
||||
};
|
||||
data?: {
|
||||
inputParams?: any[];
|
||||
outputParams?: any[];
|
||||
options?: any;
|
||||
};
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* 线信息
|
||||
*/
|
||||
export interface LineInfo {
|
||||
id: string;
|
||||
target: string;
|
||||
type?: string;
|
||||
source: string;
|
||||
targetHandle?: string | null;
|
||||
sourceHandle?: string | null;
|
||||
animated?: boolean;
|
||||
style?: any;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* 流程图
|
||||
*/
|
||||
export interface FlowGraph {
|
||||
nodes: NodeInfo[];
|
||||
edges: LineInfo[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 上下文
|
||||
*/
|
||||
export class FlowContext {
|
||||
// 存储输入输出数据
|
||||
private data: FlowContextData = {
|
||||
input: new Map(),
|
||||
output: new Map(),
|
||||
};
|
||||
|
||||
// 请求参数
|
||||
private requestParams = {};
|
||||
|
||||
// 调试单个节点
|
||||
private debugOne = false;
|
||||
|
||||
// 统计
|
||||
private count = {
|
||||
// token使用量
|
||||
tokenUsage: 0,
|
||||
};
|
||||
|
||||
// 执行信息
|
||||
private flowNodeExec: FlowNodeExec = {
|
||||
current: null,
|
||||
prev: null,
|
||||
next: null,
|
||||
nextList: [],
|
||||
};
|
||||
|
||||
// 流程图信息
|
||||
private flowGraph: FlowGraph;
|
||||
|
||||
// 是否流式调用
|
||||
private stream = false;
|
||||
|
||||
/**
|
||||
* 设置调试单个节点
|
||||
* @param debugOne
|
||||
*/
|
||||
setDebugOne(debugOne: boolean): void {
|
||||
this.debugOne = debugOne;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否调试单个节点
|
||||
* @returns
|
||||
*/
|
||||
isDebugOne(): boolean {
|
||||
return this.debugOne;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新统计
|
||||
* @param key
|
||||
* @param increment
|
||||
*/
|
||||
updateCount(key: string, increment: number) {
|
||||
this.count[key] += increment;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取统计
|
||||
* @returns
|
||||
*/
|
||||
getCount(): any {
|
||||
return this.count;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置流式调用
|
||||
* @param stream
|
||||
*/
|
||||
setStream(stream: boolean): void {
|
||||
this.stream = stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否流式调用
|
||||
* @returns
|
||||
*/
|
||||
isStream(): boolean {
|
||||
return this.stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置请求参数
|
||||
* @param params
|
||||
*/
|
||||
setRequestParams(params: any): void {
|
||||
this.requestParams = params;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得请求参数
|
||||
* @returns
|
||||
*/
|
||||
getRequestParams(): any {
|
||||
return this.requestParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置执行信息
|
||||
* @param flowNodeExec
|
||||
*/
|
||||
setFlowNodeExec(flowNodeExec: FlowNodeExec): void {
|
||||
this.flowNodeExec = flowNodeExec;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取执行信息
|
||||
* @returns
|
||||
*/
|
||||
getFlowNodeExec(): FlowNodeExec {
|
||||
return this.flowNodeExec;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置流程图
|
||||
* @param flowGraph
|
||||
*/
|
||||
setFlowGraph(flowGraph: FlowGraph): void {
|
||||
this.flowGraph = flowGraph;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取流程图
|
||||
* @returns
|
||||
*/
|
||||
getFlowGraph(): FlowGraph {
|
||||
return this.flowGraph;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置数据
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
* @param type 类型
|
||||
*/
|
||||
set(key: string, value: any, type: 'input' | 'output' = 'input'): void {
|
||||
this.data[type].set(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据
|
||||
* @param key
|
||||
* @returns
|
||||
*/
|
||||
get(key: string, type: 'input' | 'output' = 'input'): any {
|
||||
return this.data[type].get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有输入数据
|
||||
* @returns
|
||||
*/
|
||||
getData(type?: 'input' | 'output'): FlowContextData | Map<string, any> {
|
||||
return type ? this.data[type] : this.data;
|
||||
}
|
||||
}
|
206
src/modules/flow/runner/exec.ts
Normal file
206
src/modules/flow/runner/exec.ts
Normal file
@ -0,0 +1,206 @@
|
||||
import { ILogger, Inject, Provide } from '@midwayjs/core';
|
||||
import * as moment from 'moment';
|
||||
import { FlowContext } from './context';
|
||||
import { FlowNode } from './node';
|
||||
import { FlowResult } from './result';
|
||||
|
||||
/**
|
||||
* 执行器
|
||||
*/
|
||||
@Provide()
|
||||
export class FlowExecutor {
|
||||
@Inject()
|
||||
logger: ILogger;
|
||||
|
||||
/**
|
||||
* 执行一个节点
|
||||
* @param node
|
||||
* @param context
|
||||
* @param callback
|
||||
*/
|
||||
async one(node: FlowNode, context: FlowContext, callback?: (res) => void) {
|
||||
const start = moment().valueOf();
|
||||
const result = await node.invoke(context);
|
||||
const end = moment().valueOf();
|
||||
callback &&
|
||||
callback({
|
||||
result,
|
||||
nodeId: node.id,
|
||||
duration: end - start,
|
||||
nextNodeId: null,
|
||||
count: context.getCount(),
|
||||
isEnd: true,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* 串行执行
|
||||
* @param nodes
|
||||
* @param context
|
||||
*/
|
||||
async serial(
|
||||
nodes: FlowNode[],
|
||||
context: FlowContext,
|
||||
callback?: (res) => void
|
||||
): Promise<FlowResult> {
|
||||
let isEnd = false;
|
||||
// 查找开始节点
|
||||
const startNode = nodes.find(node => node.type == 'start');
|
||||
// 当前节点
|
||||
let currentNode = await this.execNode(nodes, context, startNode.id);
|
||||
let result: FlowResult;
|
||||
if (currentNode.type == 'end') {
|
||||
isEnd = true;
|
||||
}
|
||||
|
||||
// 节点执行结果
|
||||
const nodesResult = [];
|
||||
|
||||
// 保存节点执行结果
|
||||
const saveNodeResult = (currentNode, result) => {
|
||||
nodesResult.push({
|
||||
...currentNode,
|
||||
result,
|
||||
});
|
||||
};
|
||||
|
||||
while (currentNode) {
|
||||
const start = moment().valueOf();
|
||||
try {
|
||||
// 执行当前节点
|
||||
result = await currentNode.invoke(context);
|
||||
|
||||
// 保存节点执行结果
|
||||
saveNodeResult(currentNode, result);
|
||||
|
||||
// 更新当前节点信息
|
||||
const flowNodeExec = context.getFlowNodeExec();
|
||||
flowNodeExec.prev = flowNodeExec.current;
|
||||
flowNodeExec.current = flowNodeExec.next;
|
||||
context.setFlowNodeExec(flowNodeExec);
|
||||
|
||||
// 如果当前节点是 end,则结束
|
||||
if (currentNode.type == 'end') {
|
||||
isEnd = true;
|
||||
flowNodeExec.next = null;
|
||||
}
|
||||
} catch (error) {
|
||||
result = {
|
||||
success: false,
|
||||
error: {
|
||||
nodeId: currentNode.id,
|
||||
message: error.message,
|
||||
options: currentNode.config.options,
|
||||
},
|
||||
};
|
||||
|
||||
// 保存节点执行结果
|
||||
saveNodeResult(currentNode, result);
|
||||
|
||||
this.logger.error('执行节点出错', error);
|
||||
isEnd = true;
|
||||
}
|
||||
const end = moment().valueOf();
|
||||
callback &&
|
||||
callback({
|
||||
result,
|
||||
inputParams: currentNode.inputParams,
|
||||
nextNodeId: context.getFlowNodeExec().next,
|
||||
count: context.getCount(),
|
||||
duration: end - start,
|
||||
nodeId: currentNode.id,
|
||||
isEnd,
|
||||
});
|
||||
if (isEnd) {
|
||||
break;
|
||||
}
|
||||
// 获取下一个节点
|
||||
currentNode = await this.execNode(nodes, context, result.next);
|
||||
}
|
||||
|
||||
result.nodesResult = nodesResult;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得要执行的节点
|
||||
* @param context
|
||||
*/
|
||||
async execNode(
|
||||
nodes: FlowNode[],
|
||||
context: FlowContext,
|
||||
nextId?: string
|
||||
): Promise<FlowNode> {
|
||||
const flowNodeExec = context.getFlowNodeExec();
|
||||
// 当前要执行的节点
|
||||
let execId: string;
|
||||
|
||||
// 查找和设置后续节点
|
||||
const findAndSetNext = (source: string) => {
|
||||
// 找到与当前节点相连的所有下一节点
|
||||
const nextNodeIds = context
|
||||
.getFlowGraph()
|
||||
.edges.filter(line => line.source == source)
|
||||
.map(line => line.target);
|
||||
if (nextNodeIds.length === 0) {
|
||||
return;
|
||||
}
|
||||
if (!flowNodeExec.current) {
|
||||
flowNodeExec.current = source;
|
||||
}
|
||||
// 设置下一节点,如果是判断器或者分类器,有可能会有多个
|
||||
flowNodeExec.nextList = nextNodeIds;
|
||||
// 如果只有一个下一节点,则直接设置
|
||||
if (nextNodeIds.length == 1) {
|
||||
flowNodeExec.next = nextNodeIds[0];
|
||||
}
|
||||
// 更新执行信息
|
||||
context.setFlowNodeExec(flowNodeExec);
|
||||
};
|
||||
|
||||
// 如果当前节点是 end,则返回 undefined 表示流程结束
|
||||
if (flowNodeExec.current === 'end') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// 如果已经指定了下一个节点
|
||||
if (nextId) {
|
||||
execId = nextId;
|
||||
findAndSetNext(nextId);
|
||||
} else {
|
||||
findAndSetNext(flowNodeExec.current);
|
||||
execId = flowNodeExec.current;
|
||||
}
|
||||
|
||||
// 返回要执行的节点
|
||||
return nodes.find(node => node.id === execId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 并行执行
|
||||
* @param nodes
|
||||
* @param context
|
||||
*/
|
||||
async parallel(nodes: FlowNode[], context: FlowContext): Promise<void> {
|
||||
await Promise.all(nodes.map(node => node.invoke(context)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 嵌套执行
|
||||
* @param nodes
|
||||
* @param context
|
||||
*/
|
||||
async nested(
|
||||
nodes: (FlowNode | FlowNode[])[],
|
||||
context: FlowContext
|
||||
): Promise<void> {
|
||||
for (const item of nodes) {
|
||||
if (Array.isArray(item)) {
|
||||
await this.parallel(item, context);
|
||||
} else {
|
||||
await item.invoke(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
120
src/modules/flow/runner/node.ts
Normal file
120
src/modules/flow/runner/node.ts
Normal file
@ -0,0 +1,120 @@
|
||||
import * as _ from 'lodash';
|
||||
import { FlowContext } from './context';
|
||||
import { FlowResult } from './result';
|
||||
|
||||
/**
|
||||
* 节点配置
|
||||
*/
|
||||
export interface NodeConfig {
|
||||
// 输入参数
|
||||
inputParams: {
|
||||
/** 节点ID */
|
||||
nodeId: string;
|
||||
/** 参数名 */
|
||||
name: string;
|
||||
/** 类型(哪个节点) */
|
||||
nodeType: string;
|
||||
/** 字段 */
|
||||
field?: string;
|
||||
/** 值类型 */
|
||||
type: string;
|
||||
/** 是否必填 */
|
||||
required: boolean;
|
||||
/** 默认值 */
|
||||
default: any;
|
||||
/** 参数值 */
|
||||
value: any;
|
||||
}[];
|
||||
// 输出参数
|
||||
outputParams: {
|
||||
/** 节点ID */
|
||||
nodeId: string;
|
||||
/** 参数名 */
|
||||
name: string;
|
||||
/** 字段 */
|
||||
field?: string;
|
||||
/** 字段类型 */
|
||||
type: string;
|
||||
/** 类型(哪个节点) */
|
||||
nodeType: string;
|
||||
}[];
|
||||
// 配置
|
||||
options: Record<string, any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 节点
|
||||
*/
|
||||
export abstract class FlowNode {
|
||||
/** 节点id */
|
||||
id: string;
|
||||
/** 流程id */
|
||||
flowId: number;
|
||||
/** 节点label */
|
||||
label: string;
|
||||
/** 节点类型 */
|
||||
type: string;
|
||||
/** 节点配置 */
|
||||
config: NodeConfig;
|
||||
/** 输入参数 */
|
||||
inputParams: any;
|
||||
/** 上下文 */
|
||||
context: FlowContext;
|
||||
/**
|
||||
* 调用
|
||||
* @param config
|
||||
*/
|
||||
async invoke(context: FlowContext) {
|
||||
this.context = context;
|
||||
this.inputParams = this.getInputParams(context);
|
||||
return await this.run(context);
|
||||
}
|
||||
/**
|
||||
* 获得前缀
|
||||
* @returns
|
||||
*/
|
||||
getPrefix() {
|
||||
return `${this.type}.${this.id}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得参数前缀
|
||||
* @param param
|
||||
* @returns
|
||||
*/
|
||||
getParamPrefix(param: { nodeId: string; nodeType: string }) {
|
||||
return `${param.nodeType}.${param.nodeId}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取输入参数
|
||||
* @param context
|
||||
*/
|
||||
private getInputParams(context: FlowContext) {
|
||||
if (context.isDebugOne() || this.type == 'start') {
|
||||
return context.getRequestParams();
|
||||
}
|
||||
const { inputParams } = this.config;
|
||||
// 如果是开始节点,参数从请求中获取 context.getRequestParams() 转换为 Map<string, any>
|
||||
let datas = context.getData('output') as Map<string, any>;
|
||||
let params = {};
|
||||
if (_.isEmpty(inputParams)) {
|
||||
return params;
|
||||
}
|
||||
for (const param of inputParams) {
|
||||
if (param.field) {
|
||||
params[param.field] = datas.get(
|
||||
`${this.getParamPrefix(param)}.${param.name}`
|
||||
);
|
||||
}
|
||||
}
|
||||
this.inputParams = params;
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行
|
||||
* @param context
|
||||
*/
|
||||
abstract run(context: FlowContext): Promise<FlowResult>;
|
||||
}
|
49
src/modules/flow/runner/result.ts
Normal file
49
src/modules/flow/runner/result.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import { FlowStream } from './stream';
|
||||
|
||||
/**
|
||||
* 结果
|
||||
*/
|
||||
export interface FlowResult {
|
||||
/**
|
||||
* 是否成功
|
||||
*/
|
||||
success: boolean;
|
||||
/**
|
||||
* 异常信息(如果有则返回)
|
||||
*/
|
||||
error?: {
|
||||
/**
|
||||
* 异常出现的节点
|
||||
*/
|
||||
nodeId?: string;
|
||||
/**
|
||||
* 异常信息
|
||||
*/
|
||||
message?: string;
|
||||
/**
|
||||
* 原始 Error
|
||||
*/
|
||||
error?: Error;
|
||||
/**
|
||||
* options配置信息
|
||||
*/
|
||||
options?: any;
|
||||
};
|
||||
/**
|
||||
* 返回结果
|
||||
*/
|
||||
result?: any;
|
||||
/**
|
||||
* 所以已经执行的节点信息
|
||||
*/
|
||||
nodesResult?: any;
|
||||
/**
|
||||
* 下个节点,如果有多个下个节点,当前节点需要做出判断选择一个节点执行
|
||||
*/
|
||||
next?: string;
|
||||
|
||||
/**
|
||||
* stream流
|
||||
*/
|
||||
stream?: FlowStream;
|
||||
}
|
8
src/modules/flow/runner/stream.ts
Normal file
8
src/modules/flow/runner/stream.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { Readable } from 'stream';
|
||||
|
||||
/**
|
||||
* 自定义流
|
||||
*/
|
||||
export class FlowStream extends Readable {
|
||||
_read(size) {}
|
||||
}
|
80
src/modules/flow/service/config.ts
Normal file
80
src/modules/flow/service/config.ts
Normal file
@ -0,0 +1,80 @@
|
||||
import { Init, Inject, Provide } from '@midwayjs/decorator';
|
||||
import { BaseService } from '@cool-midway/core';
|
||||
import { InjectEntityModel } from '@midwayjs/typeorm';
|
||||
import { Equal, Repository } from 'typeorm';
|
||||
import { FlowConfigEntity } from '../entity/config';
|
||||
import { AllConfig, NodeConfig, NodeTypeKey } from '../nodes';
|
||||
import { KnowDataTypeService } from '../../know/service/data/type';
|
||||
|
||||
/**
|
||||
* 流程配置
|
||||
*/
|
||||
@Provide()
|
||||
export class FlowConfigService extends BaseService {
|
||||
@InjectEntityModel(FlowConfigEntity)
|
||||
flowConfigEntity: Repository<FlowConfigEntity>;
|
||||
|
||||
@Inject()
|
||||
knowDataTypeService: KnowDataTypeService;
|
||||
|
||||
@Inject()
|
||||
ctx;
|
||||
|
||||
@Init()
|
||||
async init() {
|
||||
await super.init();
|
||||
this.setEntity(this.flowConfigEntity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得配置
|
||||
* @param node 节点
|
||||
* @param type 类型
|
||||
*/
|
||||
async config(node: NodeTypeKey, type?: string) {
|
||||
// 知识库
|
||||
if (node == 'know') {
|
||||
return {
|
||||
knows: await this.knowDataTypeService.getKnows(),
|
||||
};
|
||||
}
|
||||
return type ? NodeConfig[node][type] : NodeConfig[node];
|
||||
}
|
||||
|
||||
/**
|
||||
* 所有配置
|
||||
* @returns
|
||||
*/
|
||||
async all() {
|
||||
return AllConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得配置
|
||||
* @param configId
|
||||
* @returns
|
||||
*/
|
||||
async getOptions(configId: number) {
|
||||
const config = await this.flowConfigEntity.findOneBy({
|
||||
id: Equal(configId),
|
||||
});
|
||||
return config?.options;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过名称获取配置
|
||||
* @param node 类型
|
||||
* @param type 类型
|
||||
* @returns
|
||||
*/
|
||||
async getByNode(node: string, type?: string): Promise<FlowConfigEntity[]> {
|
||||
const find = await this.flowConfigEntity.createQueryBuilder('a');
|
||||
if (type) {
|
||||
find.where('a.type = :type', { type });
|
||||
}
|
||||
if (node) {
|
||||
find.andWhere('a.node = :node', { node });
|
||||
}
|
||||
return await find.getMany();
|
||||
}
|
||||
}
|
51
src/modules/flow/service/data.ts
Normal file
51
src/modules/flow/service/data.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { BaseService } from '@cool-midway/core';
|
||||
import { Init, Provide } from '@midwayjs/decorator';
|
||||
import { InjectEntityModel } from '@midwayjs/typeorm';
|
||||
import { Equal, Repository } from 'typeorm';
|
||||
import { FlowDataEntity } from '../entity/data';
|
||||
|
||||
/**
|
||||
* 流程数据
|
||||
*/
|
||||
@Provide()
|
||||
export class FlowDataService extends BaseService {
|
||||
@InjectEntityModel(FlowDataEntity)
|
||||
flowDataEntity: Repository<FlowDataEntity>;
|
||||
|
||||
@Init()
|
||||
async init() {
|
||||
await super.init();
|
||||
this.setEntity(this.flowDataEntity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置数据
|
||||
* @param flowId
|
||||
* @param objectId
|
||||
* @param data
|
||||
*/
|
||||
async set(flowId: number, objectId: string, data: any) {
|
||||
const check = await this.flowDataEntity.findOneBy({
|
||||
flowId: Equal(flowId),
|
||||
objectId: Equal(objectId),
|
||||
});
|
||||
if (check) {
|
||||
await this.flowDataEntity.update(check.id, { data });
|
||||
} else {
|
||||
await this.flowDataEntity.save({ flowId, objectId, data });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据
|
||||
* @param flowId
|
||||
* @param objectId
|
||||
*/
|
||||
async get(flowId: number, objectId: string) {
|
||||
const data = await this.flowDataEntity.findOneBy({
|
||||
flowId: Equal(flowId),
|
||||
objectId: Equal(objectId),
|
||||
});
|
||||
return data?.data;
|
||||
}
|
||||
}
|
111
src/modules/flow/service/info.ts
Normal file
111
src/modules/flow/service/info.ts
Normal file
@ -0,0 +1,111 @@
|
||||
import { BaseService, CoolCommException } from '@cool-midway/core';
|
||||
import { App, IMidwayApplication, IMidwayContext } from '@midwayjs/core';
|
||||
import { Init, Inject, Provide } from '@midwayjs/decorator';
|
||||
import { InjectEntityModel } from '@midwayjs/typeorm';
|
||||
import { Equal, Not, Repository } from 'typeorm';
|
||||
import { FlowInfoEntity } from '../entity/info';
|
||||
import { NodeType } from '../nodes';
|
||||
import { FlowNode } from '../runner/node';
|
||||
|
||||
/**
|
||||
* 流程信息
|
||||
*/
|
||||
@Provide()
|
||||
export class FlowInfoService extends BaseService {
|
||||
@InjectEntityModel(FlowInfoEntity)
|
||||
flowInfoEntity: Repository<FlowInfoEntity>;
|
||||
|
||||
@Inject()
|
||||
ctx: IMidwayContext;
|
||||
|
||||
@App()
|
||||
app: IMidwayApplication;
|
||||
|
||||
@Init()
|
||||
async init() {
|
||||
await super.init();
|
||||
this.setEntity(this.flowInfoEntity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增或更新
|
||||
* @param param
|
||||
* @param type
|
||||
*/
|
||||
async addOrUpdate(param: FlowInfoEntity, type?: 'add' | 'update') {
|
||||
let check;
|
||||
if (type == 'add') {
|
||||
check = await this.flowInfoEntity.findOneBy({
|
||||
label: Equal(param.label),
|
||||
});
|
||||
}
|
||||
if (param.label && type == 'update') {
|
||||
check = await this.flowInfoEntity.findOneBy({
|
||||
label: Equal(param.label),
|
||||
id: Not(param.id),
|
||||
});
|
||||
}
|
||||
if (check) {
|
||||
throw new CoolCommException('标签已存在');
|
||||
}
|
||||
await super.addOrUpdate(param, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发布流程
|
||||
* @param flowId
|
||||
*/
|
||||
async release(flowId: number) {
|
||||
const info = await this.flowInfoEntity.findOneBy({ id: Equal(flowId) });
|
||||
if (!info) {
|
||||
throw new CoolCommException('流程不存在');
|
||||
}
|
||||
info.version++;
|
||||
info.releaseTime = new Date();
|
||||
info.data = info.draft;
|
||||
await this.flowInfoEntity.update(info.id, info);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得流程的节点
|
||||
* @param label
|
||||
* @param isDraft 是否是草稿,调试的时候调用草稿
|
||||
*/
|
||||
async getNodes(
|
||||
label: string,
|
||||
isDraft = false
|
||||
): Promise<{
|
||||
nodes: FlowNode[];
|
||||
info: FlowInfoEntity;
|
||||
}> {
|
||||
const info = await this.flowInfoEntity.findOneBy({
|
||||
label: Equal(label),
|
||||
status: 1,
|
||||
});
|
||||
if (!info) {
|
||||
throw new CoolCommException('流程不存在或被禁用');
|
||||
}
|
||||
const data = isDraft ? info.draft : info.data;
|
||||
const nodes: FlowNode[] = [];
|
||||
// 构建所有节点
|
||||
for (const item of data.nodes) {
|
||||
const node: FlowNode = await this.app
|
||||
.getApplicationContext()
|
||||
.getAsync(NodeType[item.type]);
|
||||
node.id = item.id;
|
||||
node.flowId = info.id;
|
||||
node.label = item.label;
|
||||
node.type = item.type;
|
||||
node.config = {
|
||||
inputParams: item.data.inputParams,
|
||||
outputParams: item.data.outputParams,
|
||||
options: item.data.options,
|
||||
};
|
||||
nodes.push(node);
|
||||
}
|
||||
return {
|
||||
nodes,
|
||||
info,
|
||||
};
|
||||
}
|
||||
}
|
117
src/modules/flow/service/log.ts
Normal file
117
src/modules/flow/service/log.ts
Normal file
@ -0,0 +1,117 @@
|
||||
import { Init, Provide } from '@midwayjs/decorator';
|
||||
import { BaseService } from '@cool-midway/core';
|
||||
import { InjectEntityModel } from '@midwayjs/typeorm';
|
||||
import { Equal, Repository } from 'typeorm';
|
||||
import { FlowInfoEntity } from '../entity/info';
|
||||
import { FlowLogEntity } from '../entity/log';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
/**
|
||||
* 流程信息日志
|
||||
*/
|
||||
@Provide()
|
||||
export class FlowLogService extends BaseService {
|
||||
|
||||
@InjectEntityModel(FlowInfoEntity)
|
||||
flowInfoEntity: Repository<FlowInfoEntity>;
|
||||
|
||||
@InjectEntityModel(FlowLogEntity)
|
||||
flowLogEntity: Repository<FlowLogEntity>;
|
||||
|
||||
@Init()
|
||||
async init() {
|
||||
await super.init();
|
||||
this.setEntity(this.flowLogEntity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录日志
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
async save(data) {
|
||||
// 获得流程信息
|
||||
const info = await this.flowInfoEntity.findOneBy({
|
||||
label: Equal(data.flowLabel),
|
||||
})
|
||||
|
||||
if (info) {
|
||||
const { inputParams, nodeInfo, result, type } = data
|
||||
|
||||
// 构建参数
|
||||
const params: {
|
||||
flowId: number
|
||||
flowLabel: string
|
||||
type: number
|
||||
inputParams: any
|
||||
result: any,
|
||||
nodeInfo?: any
|
||||
} = {
|
||||
flowId: info.id,
|
||||
flowLabel: info.label,
|
||||
type,
|
||||
inputParams,
|
||||
result,
|
||||
}
|
||||
|
||||
// 如果节点信息结果不为空
|
||||
if (!_.isEmpty(nodeInfo)) {
|
||||
// params.nodeInfo = Object.create(nodeInfoList)
|
||||
params.nodeInfo = this.extractData(nodeInfo)
|
||||
}
|
||||
|
||||
// 保存日志
|
||||
return await this.flowLogEntity.save(params);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义序列化函数
|
||||
* @param obj 参数
|
||||
*/
|
||||
async stringifyCircular(obj) {
|
||||
const seen = new WeakSet();
|
||||
|
||||
return JSON.stringify(obj, (key, value) => {
|
||||
if (typeof value === "object" && value !== null) {
|
||||
if (seen.has(value)) {
|
||||
return;
|
||||
}
|
||||
seen.add(value);
|
||||
}
|
||||
return value;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取需储存的数据
|
||||
* @param nodeInfo
|
||||
* @returns
|
||||
*/
|
||||
extractData(nodeInfo) {
|
||||
const nodeInfoList = nodeInfo?.map((e) => {
|
||||
// 结构对象,提取有用的值
|
||||
const { id, label, type, inputParams, result, config: execConfig } = e
|
||||
|
||||
const { nodesResult, ...resultArg } = result
|
||||
|
||||
const cParams = {
|
||||
id,
|
||||
label,
|
||||
type,
|
||||
inputParams,
|
||||
result: {
|
||||
...resultArg,
|
||||
},
|
||||
config: {
|
||||
outputParams: execConfig?.outputParams || null,
|
||||
options: execConfig?.options || null,
|
||||
}
|
||||
}
|
||||
|
||||
return cParams
|
||||
}) ?? []
|
||||
|
||||
return nodeInfoList
|
||||
}
|
||||
}
|
190
src/modules/flow/service/run.ts
Normal file
190
src/modules/flow/service/run.ts
Normal file
@ -0,0 +1,190 @@
|
||||
import { BaseService } from '@cool-midway/core';
|
||||
import { ILogger, Inject } from '@midwayjs/core';
|
||||
import { Provide } from '@midwayjs/decorator';
|
||||
import * as moment from 'moment';
|
||||
import { FlowContext } from '../runner/context';
|
||||
import { FlowExecutor } from '../runner/exec';
|
||||
import { FlowResult } from '../runner/result';
|
||||
import { FlowInfoService } from './info';
|
||||
import { FlowLogService } from './log';
|
||||
|
||||
/**
|
||||
* 运行流程
|
||||
*/
|
||||
@Provide()
|
||||
export class FlowRunService extends BaseService {
|
||||
// 节点执行器
|
||||
@Inject()
|
||||
flowExecutor: FlowExecutor;
|
||||
|
||||
@Inject()
|
||||
flowInfoService: FlowInfoService;
|
||||
|
||||
@Inject()
|
||||
flowLogService: FlowLogService;
|
||||
|
||||
@Inject()
|
||||
logger: ILogger;
|
||||
|
||||
/**
|
||||
* 调试
|
||||
* @param params 参数
|
||||
* @param label 标签
|
||||
* @param nodeId 节点ID
|
||||
* @param callback 回调
|
||||
*/
|
||||
async debug(
|
||||
params: any,
|
||||
label: string,
|
||||
nodeId: string,
|
||||
stream: boolean,
|
||||
callback: (res) => void
|
||||
) {
|
||||
const start = moment().valueOf();
|
||||
try {
|
||||
// 上下文
|
||||
const context = new FlowContext();
|
||||
// 设置请求参数
|
||||
context.setRequestParams(params);
|
||||
// 调试的时候非流式调用
|
||||
context.setStream(stream);
|
||||
// 获得所有节点
|
||||
const { nodes, info } = await this.flowInfoService.getNodes(label, true);
|
||||
// 设置流程图
|
||||
context.setFlowGraph(info.draft);
|
||||
if (nodeId) {
|
||||
// 调试单个节点
|
||||
const node = nodes.find(item => item.id == nodeId);
|
||||
if (node) {
|
||||
// 设置调试参数
|
||||
context.setDebugOne(true);
|
||||
await this.flowExecutor.one(node, context, callback);
|
||||
}
|
||||
} else {
|
||||
// 调用流程
|
||||
const result = await this.flowExecutor.serial(nodes, context, callback);
|
||||
|
||||
// 保存日志
|
||||
this.saveLog(result.success, {
|
||||
label: label,
|
||||
inputParams: params,
|
||||
result: result?.result ?? null,
|
||||
error: result?.error ?? null,
|
||||
nodeInfo: result?.nodesResult ?? null,
|
||||
});
|
||||
delete result?.nodesResult;
|
||||
return result;
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error('业务错误', error);
|
||||
|
||||
// 保存日志
|
||||
this.saveLog(
|
||||
false,
|
||||
{
|
||||
label: label,
|
||||
inputParams: params,
|
||||
},
|
||||
error
|
||||
);
|
||||
|
||||
const end = moment().valueOf();
|
||||
callback &&
|
||||
callback({
|
||||
result: {
|
||||
error: {
|
||||
message: error.message,
|
||||
},
|
||||
},
|
||||
duration: end - start,
|
||||
isEnd: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用
|
||||
* @param params 参数
|
||||
* @param label 标签
|
||||
* @param stream 是否流式调用
|
||||
* @returns
|
||||
*/
|
||||
async invoke(
|
||||
params: any,
|
||||
label: string,
|
||||
stream = false
|
||||
): Promise<FlowResult> {
|
||||
try {
|
||||
// 上下文
|
||||
const context = new FlowContext();
|
||||
// 设置开始参数
|
||||
context.set('start', params, 'input');
|
||||
// 设置请求参数
|
||||
context.setRequestParams(params);
|
||||
// 设置流式调用
|
||||
context.setStream(stream);
|
||||
// 获得所有节点
|
||||
const { nodes, info } = await this.flowInfoService.getNodes(label, true);
|
||||
// 设置流程图
|
||||
context.setFlowGraph(info.draft);
|
||||
// 调用流程
|
||||
const result = await this.flowExecutor.serial(nodes, context);
|
||||
|
||||
// 保存日志
|
||||
this.saveLog(result.success, {
|
||||
label: label,
|
||||
inputParams: params,
|
||||
result: result?.result ?? null,
|
||||
error: result?.error ?? null,
|
||||
nodeInfo: result?.nodesResult ?? null,
|
||||
});
|
||||
delete result.nodesResult;
|
||||
return result;
|
||||
} catch (error) {
|
||||
// 保存日志
|
||||
this.saveLog(
|
||||
false,
|
||||
{
|
||||
label: label,
|
||||
inputParams: params,
|
||||
},
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存日志
|
||||
* @param success 是否成功 true | false
|
||||
* @param data 数据
|
||||
* @param error 错误信息
|
||||
*/
|
||||
async saveLog(
|
||||
success: boolean,
|
||||
data: {
|
||||
label: string;
|
||||
inputParams: any;
|
||||
result?: any;
|
||||
error?: any;
|
||||
nodeInfo?: any;
|
||||
},
|
||||
error?
|
||||
) {
|
||||
try {
|
||||
const result = success ? data.result : error || data.error;
|
||||
|
||||
const params: any = {
|
||||
flowLabel: data.label,
|
||||
type: success ? 1 : 0,
|
||||
inputParams: data.inputParams,
|
||||
nodeInfo: data.nodeInfo,
|
||||
result,
|
||||
};
|
||||
|
||||
// 保存日志
|
||||
this.flowLogService.save(params);
|
||||
} catch (error) {
|
||||
this.logger.error('保存日志错误', error);
|
||||
}
|
||||
}
|
||||
}
|
43
src/modules/know/config.ts
Normal file
43
src/modules/know/config.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { ModuleConfig } from '@cool-midway/core';
|
||||
import { UnstructuredLoaderOptions } from '@langchain/community/document_loaders/fs/unstructured';
|
||||
import { StoreTypes } from './store';
|
||||
|
||||
/**
|
||||
* 模块配置
|
||||
*/
|
||||
export default () => {
|
||||
return {
|
||||
// 模块名称
|
||||
name: '知识库',
|
||||
// 模块描述
|
||||
description: '知识库,检索,向量存储等',
|
||||
// 中间件,只对本模块有效
|
||||
middlewares: [],
|
||||
// 中间件,全局有效
|
||||
globalMiddlewares: [],
|
||||
// 模块加载顺序,默认为0,值越大越优先加载
|
||||
order: 0,
|
||||
// 向量数据存储,默认为:faiss
|
||||
store: 'chroma' as StoreTypes,
|
||||
// chroma 配置
|
||||
chroma: {
|
||||
// 服务地址
|
||||
url: 'http://120.48.5.80/:8000',
|
||||
// 距离计算方式 可选 l2、cosine、ip
|
||||
distance: 'l2',
|
||||
// 重试次数,向量化失败时重试
|
||||
retry: 10,
|
||||
// 重试间隔,单位:ms
|
||||
retryInterval: 1000,
|
||||
},
|
||||
// 集合前缀
|
||||
prefix: 'COOL_',
|
||||
// Unstructured 配置,用于处理非结构化数据
|
||||
unstructured: {
|
||||
// 服务地址,可以使用云服务,也可以自己部署
|
||||
apiUrl: 'https://api.unstructured.io/general/v0/general',
|
||||
// api key, 如果是自己部署的服务,可以不填
|
||||
apiKey: 'xxx',
|
||||
} as UnstructuredLoaderOptions,
|
||||
} as ModuleConfig;
|
||||
};
|
43
src/modules/know/controller/admin/config.ts
Normal file
43
src/modules/know/controller/admin/config.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { CoolController, BaseController } from '@cool-midway/core';
|
||||
import { Body, Get, Inject, Post, Query } from '@midwayjs/core';
|
||||
import { KnowConfigService } from '../../service/config';
|
||||
import { ConfigTypeKey } from '../../interface';
|
||||
import { KnowConfigEntity } from '../../entity/config';
|
||||
|
||||
/**
|
||||
* 配置
|
||||
*/
|
||||
@CoolController({
|
||||
api: ['add', 'delete', 'update', 'info', 'list', 'page'],
|
||||
entity: KnowConfigEntity,
|
||||
service: KnowConfigService,
|
||||
pageQueryOp: {
|
||||
select: ['a.*'],
|
||||
keyWordLikeFields: ['a.name'],
|
||||
fieldEq: ['a.func', 'a.type'],
|
||||
},
|
||||
listQueryOp: {
|
||||
select: ['a.*'],
|
||||
fieldEq: ['b.type'],
|
||||
},
|
||||
})
|
||||
export class AdminKnowConfigController extends BaseController {
|
||||
@Inject()
|
||||
knowConfigService: KnowConfigService;
|
||||
|
||||
@Get('/all', { summary: '获取所有配置' })
|
||||
async all() {
|
||||
return this.ok(await this.knowConfigService.all());
|
||||
}
|
||||
|
||||
@Post('/config', { summary: '获取配置' })
|
||||
async config(@Body('func') func: ConfigTypeKey, @Body('type') type: string) {
|
||||
return this.ok(await this.knowConfigService.config(func, type));
|
||||
}
|
||||
|
||||
@Get('/getByFunc', { summary: '通过功能获取配置' })
|
||||
async getByNode(@Query('func') func: string, @Query('type') type: string) {
|
||||
const config = await this.knowConfigService.getByFunc(func, type);
|
||||
return this.ok(config);
|
||||
}
|
||||
}
|
17
src/modules/know/controller/admin/data/info.ts
Normal file
17
src/modules/know/controller/admin/data/info.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { CoolController, BaseController } from '@cool-midway/core';
|
||||
import { KnowDataInfoEntity } from '../../../entity/data/info';
|
||||
import { KnowDataInfoService } from '../../../service/data/info';
|
||||
|
||||
/**
|
||||
* 知识信息
|
||||
*/
|
||||
@CoolController({
|
||||
api: ['add', 'delete', 'update', 'info', 'list', 'page'],
|
||||
entity: KnowDataInfoEntity,
|
||||
service: KnowDataInfoService,
|
||||
pageQueryOp: {
|
||||
keyWordLikeFields: ['a.title', 'a.content'],
|
||||
fieldEq: ['a.typeId', 'a.from'],
|
||||
},
|
||||
})
|
||||
export class AdminKnowDataInfoController extends BaseController {}
|
26
src/modules/know/controller/admin/data/type.ts
Normal file
26
src/modules/know/controller/admin/data/type.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { CoolController, BaseController } from '@cool-midway/core';
|
||||
import { KnowDataTypeEntity } from '../../../entity/data/type';
|
||||
import { KnowDataTypeService } from '../../../service/data/type';
|
||||
import { Body, Inject, Post } from '@midwayjs/core';
|
||||
|
||||
/**
|
||||
* 知识信息类型
|
||||
*/
|
||||
@CoolController({
|
||||
api: ['add', 'delete', 'update', 'info', 'list', 'page'],
|
||||
entity: KnowDataTypeEntity,
|
||||
service: KnowDataTypeService,
|
||||
pageQueryOp: {
|
||||
keyWordLikeFields: ['a.name'],
|
||||
},
|
||||
})
|
||||
export class AdminKnowDataTypeController extends BaseController {
|
||||
@Inject()
|
||||
service: KnowDataTypeService;
|
||||
|
||||
@Post('/rebuild', { summary: '重建' })
|
||||
async rebuild(@Body('typeId') typeId: number) {
|
||||
this.service.rebuild(typeId);
|
||||
return this.ok();
|
||||
}
|
||||
}
|
27
src/modules/know/controller/admin/loader.ts
Normal file
27
src/modules/know/controller/admin/loader.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { CoolController, BaseController } from '@cool-midway/core';
|
||||
import { Body, Files, Inject, Post } from '@midwayjs/core';
|
||||
import { KnowLoaderService } from '../../service/loader';
|
||||
|
||||
/**
|
||||
* 知识库加载器
|
||||
*/
|
||||
@CoolController()
|
||||
export class AdminKnowLoaderController extends BaseController {
|
||||
@Inject()
|
||||
knowLoaderService: KnowLoaderService;
|
||||
|
||||
@Post('/file', { summary: '加载文件,支持多个文件' })
|
||||
async file(@Files() files) {
|
||||
const result = [];
|
||||
for (const file of files) {
|
||||
const docs = await this.knowLoaderService.loadFile(file.data);
|
||||
result.push(docs);
|
||||
}
|
||||
return this.ok(result);
|
||||
}
|
||||
|
||||
@Post('/link', { summary: '加载链接' })
|
||||
async link(@Body('link') link: string) {
|
||||
return this.ok(await this.knowLoaderService.loadLink(link));
|
||||
}
|
||||
}
|
27
src/modules/know/controller/admin/retriever.ts
Normal file
27
src/modules/know/controller/admin/retriever.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { CoolController, BaseController } from '@cool-midway/core';
|
||||
import { Body, Inject, Post } from '@midwayjs/core';
|
||||
import { KnowRetrieverService } from '../../service/retriever';
|
||||
import { SearchOptions } from '../../interface';
|
||||
|
||||
/**
|
||||
* 检索器
|
||||
*/
|
||||
@CoolController()
|
||||
export class AdminKnowRetrieverController extends BaseController {
|
||||
@Inject()
|
||||
knowRetrieverService: KnowRetrieverService;
|
||||
|
||||
@Post('/invoke', { summary: '调用' })
|
||||
async invoke(
|
||||
@Body('knowId') knowId: number,
|
||||
@Body('text') text: string,
|
||||
@Body('options') options: SearchOptions
|
||||
) {
|
||||
const result = await this.knowRetrieverService.invoke(
|
||||
knowId,
|
||||
text,
|
||||
options
|
||||
);
|
||||
return this.ok(result);
|
||||
}
|
||||
}
|
98
src/modules/know/embed/config.ts
Normal file
98
src/modules/know/embed/config.ts
Normal file
@ -0,0 +1,98 @@
|
||||
import { EmbeddType } from '.';
|
||||
|
||||
/**
|
||||
* 配置模板
|
||||
*/
|
||||
export const ConfigEmbedd: { [key in EmbeddType]?: any } = {
|
||||
// 智谱AI
|
||||
zhipu: {
|
||||
comm: {
|
||||
apiKey: 'API密钥',
|
||||
},
|
||||
options: [
|
||||
{
|
||||
field: 'model',
|
||||
title: '模型',
|
||||
select: ['zhipu-01'],
|
||||
default: 'zhipu-01',
|
||||
},
|
||||
],
|
||||
},
|
||||
// minimax
|
||||
minimax: {
|
||||
// 通用配置
|
||||
comm: {
|
||||
minimaxApiKey: 'minimax 的api key',
|
||||
minimaxGroupId: 'minimax 的group id',
|
||||
},
|
||||
// 专有配置
|
||||
options: [
|
||||
{
|
||||
field: 'model',
|
||||
title: '模型',
|
||||
select: ['embedding-2'],
|
||||
default: 'embedding-2',
|
||||
},
|
||||
],
|
||||
},
|
||||
// tongyi
|
||||
tongyi: {
|
||||
// 通用配置
|
||||
comm: {
|
||||
apiKey: '通义千问的apiKey',
|
||||
},
|
||||
// 专有配置
|
||||
options: [
|
||||
{
|
||||
field: 'model',
|
||||
title: '模型',
|
||||
select: [
|
||||
'text-embedding-v1',
|
||||
'text-embedding-async-v1',
|
||||
'text-embedding-v2',
|
||||
'text-embedding-async-v2',
|
||||
],
|
||||
default: 'text-embedding-v2',
|
||||
},
|
||||
],
|
||||
},
|
||||
// openai
|
||||
openai: {
|
||||
// 通用配置
|
||||
comm: {
|
||||
apiKey: 'API密钥',
|
||||
configuration: {
|
||||
baseURL: '基础路径一般需要带/v1',
|
||||
},
|
||||
},
|
||||
// 专有配置
|
||||
options: [
|
||||
{
|
||||
field: 'model',
|
||||
title: '模型',
|
||||
select: [
|
||||
'text-davinci-003',
|
||||
'text-embedding-3-small',
|
||||
'text-embedding-3-large',
|
||||
],
|
||||
default: 'text-embedding-3-large',
|
||||
},
|
||||
],
|
||||
},
|
||||
// ollama
|
||||
ollama: {
|
||||
// 通用配置
|
||||
comm: {
|
||||
baseUrl: '请求地址,如:http://localhost:11434',
|
||||
},
|
||||
// 专有配置
|
||||
options: [
|
||||
{
|
||||
field: 'model',
|
||||
title: '模型',
|
||||
select: ['nomic-embed-text'],
|
||||
default: 'nomic-embed-text',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
24
src/modules/know/embed/index.ts
Normal file
24
src/modules/know/embed/index.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { OpenAIEmbeddings } from '@langchain/openai';
|
||||
import { ZhipuAIEmbeddings } from '@langchain/community/embeddings/zhipuai';
|
||||
import { MinimaxEmbeddings } from '@langchain/community/embeddings/minimax';
|
||||
import { OllamaEmbeddings } from '@langchain/community/embeddings/ollama';
|
||||
import { AlibabaTongyiEmbeddings } from '@langchain/community/embeddings/alibaba_tongyi';
|
||||
|
||||
/**
|
||||
* 向量化模型,你还可以添加其他向量化模型,https://js.langchain.com/v0.2/docs/integrations/text_embedding
|
||||
*/
|
||||
export const EmbeddModel = {
|
||||
// OpenAI Embeddings,也适用支持openai api格式的其他向量化模型
|
||||
openai: OpenAIEmbeddings,
|
||||
// 智谱,https://www.zhipu.ai
|
||||
zhipu: ZhipuAIEmbeddings,
|
||||
// 通义,https://tongyi.aliyun.com
|
||||
tongyi: AlibabaTongyiEmbeddings,
|
||||
// minimax,https://www.minimaxi.com
|
||||
minimax: MinimaxEmbeddings,
|
||||
// ollama,本地大模型,https://ollama.com
|
||||
ollama: OllamaEmbeddings,
|
||||
};
|
||||
|
||||
// 向量化类型键
|
||||
export type EmbeddType = keyof typeof EmbeddModel;
|
23
src/modules/know/entity/config.ts
Normal file
23
src/modules/know/entity/config.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { BaseEntity } from '@cool-midway/core';
|
||||
import { Column, Entity } from 'typeorm';
|
||||
|
||||
/**
|
||||
* 知识库配置
|
||||
*/
|
||||
@Entity('know_config')
|
||||
export class KnowConfigEntity extends BaseEntity {
|
||||
@Column({ comment: '名称' })
|
||||
name: string;
|
||||
|
||||
@Column({ comment: '描述', nullable: true })
|
||||
description: string;
|
||||
|
||||
@Column({ comment: '类型' })
|
||||
type: string;
|
||||
|
||||
@Column({ comment: '功能' })
|
||||
func: string;
|
||||
|
||||
@Column({ comment: '配置', type: 'json' })
|
||||
options: any;
|
||||
}
|
35
src/modules/know/entity/data/info.ts
Normal file
35
src/modules/know/entity/data/info.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { BaseEntity } from '@cool-midway/core';
|
||||
import { Column, Entity, Index } from 'typeorm';
|
||||
|
||||
/**
|
||||
* 知识信息
|
||||
*/
|
||||
@Entity('know_data_info')
|
||||
export class KnowDataInfoEntity extends BaseEntity {
|
||||
@Index()
|
||||
@Column({ comment: '类型ID' })
|
||||
typeId: number;
|
||||
|
||||
@Column({ comment: '标题', nullable: true })
|
||||
title: string;
|
||||
|
||||
// 为了兼容postgre
|
||||
@Column({ comment: '内容', type: 'json' })
|
||||
content: {
|
||||
data: string;
|
||||
};
|
||||
|
||||
@Column({ comment: '来源 0-自定义 1-文件 2-链接', default: 0 })
|
||||
from: number;
|
||||
|
||||
@Column({ comment: '元数据', type: 'json', nullable: true })
|
||||
metadata: {
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
@Column({ comment: '状态 0-准备中 1-已就绪', default: 0 })
|
||||
status: number;
|
||||
|
||||
@Column({ comment: '启用 0-禁用 1-启用', default: 1 })
|
||||
enable: number;
|
||||
}
|
38
src/modules/know/entity/data/type.ts
Normal file
38
src/modules/know/entity/data/type.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { BaseEntity } from '@cool-midway/core';
|
||||
import { Column, Entity } from 'typeorm';
|
||||
|
||||
/**
|
||||
* 知识信息类型
|
||||
*/
|
||||
@Entity('know_data_type')
|
||||
export class KnowDataTypeEntity extends BaseEntity {
|
||||
@Column({ comment: '名称' })
|
||||
name: string;
|
||||
|
||||
@Column({ comment: '图标', nullable: true })
|
||||
icon: string;
|
||||
|
||||
@Column({ comment: '描述', nullable: true })
|
||||
description: string;
|
||||
|
||||
@Column({ comment: 'embedding配置ID' })
|
||||
embedConfigId: number;
|
||||
|
||||
@Column({ comment: 'embedding配置', type: 'json', nullable: true })
|
||||
embedOptions: any;
|
||||
|
||||
@Column({ comment: '是否开启rerank 0-否 1-是', default: 0 })
|
||||
enableRerank: number;
|
||||
|
||||
@Column({ comment: 'rerank配置ID', nullable: true })
|
||||
rerankConfigId: number;
|
||||
|
||||
@Column({ comment: 'rerank配置', type: 'json', nullable: true })
|
||||
rerankOptions: any;
|
||||
|
||||
@Column({ comment: '链接更新频率(天)', nullable: true })
|
||||
updateFrequency: number;
|
||||
|
||||
@Column({ comment: '状态 0-禁用 1-启用', default: 1 })
|
||||
enable: number;
|
||||
}
|
35
src/modules/know/interface.ts
Normal file
35
src/modules/know/interface.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { ConfigEmbedd } from './embed/config';
|
||||
import { ConfigRerank } from './rerank/config';
|
||||
|
||||
/**
|
||||
* 搜索配置
|
||||
*/
|
||||
export interface SearchOptions {
|
||||
/** 结果条数 */
|
||||
size: number;
|
||||
}
|
||||
|
||||
// 配置
|
||||
export const Config = {
|
||||
// 向量化模型
|
||||
embed: ConfigEmbedd,
|
||||
// 重排模型
|
||||
rerank: ConfigRerank,
|
||||
};
|
||||
|
||||
// 节点类型键
|
||||
export type ConfigTypeKey = keyof typeof Config;
|
||||
|
||||
/**
|
||||
* 所有配置
|
||||
*/
|
||||
export const AllConfig = [
|
||||
{
|
||||
title: '向量化模型',
|
||||
type: 'embed',
|
||||
},
|
||||
{
|
||||
title: '重排模型Rerank',
|
||||
type: 'rerank',
|
||||
},
|
||||
];
|
19
src/modules/know/loader/cheerio.ts
Normal file
19
src/modules/know/loader/cheerio.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { Provide } from '@midwayjs/core';
|
||||
import { CheerioWebBaseLoader } from '@langchain/community/document_loaders/web/cheerio';
|
||||
|
||||
/**
|
||||
* 链接加载器
|
||||
*/
|
||||
@Provide()
|
||||
export class KnowCheerioLoader {
|
||||
/**
|
||||
* 加载链接
|
||||
* @param link
|
||||
* @returns
|
||||
*/
|
||||
async loadLink(link: string) {
|
||||
const loader = new CheerioWebBaseLoader(link);
|
||||
const docs = await loader.load();
|
||||
return docs;
|
||||
}
|
||||
}
|
24
src/modules/know/loader/unstructured.ts
Normal file
24
src/modules/know/loader/unstructured.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { Config, Provide } from '@midwayjs/core';
|
||||
import {
|
||||
UnstructuredLoader,
|
||||
UnstructuredLoaderOptions,
|
||||
} from '@langchain/community/document_loaders/fs/unstructured';
|
||||
|
||||
/**
|
||||
* 非结构化文档加载器
|
||||
*/
|
||||
@Provide()
|
||||
export class KnowUnstructuredLoader {
|
||||
@Config('module.know.unstructured')
|
||||
options: UnstructuredLoaderOptions;
|
||||
|
||||
/**
|
||||
* 加载文档
|
||||
* @param filePath
|
||||
*/
|
||||
async loadFile(filePath: string) {
|
||||
const loader = new UnstructuredLoader(filePath, this.options);
|
||||
const docs = await loader.load();
|
||||
return docs;
|
||||
}
|
||||
}
|
257
src/modules/know/menu.json
Normal file
257
src/modules/know/menu.json
Normal file
@ -0,0 +1,257 @@
|
||||
[
|
||||
{
|
||||
"name": "知识信息",
|
||||
"router": null,
|
||||
"perms": null,
|
||||
"type": 0,
|
||||
"icon": "icon-dict",
|
||||
"orderNum": 1,
|
||||
"viewPath": null,
|
||||
"keepAlive": true,
|
||||
"isShow": true,
|
||||
"childMenus": [
|
||||
{
|
||||
"name": "信息列表",
|
||||
"router": "/know/data/type",
|
||||
"perms": null,
|
||||
"type": 1,
|
||||
"icon": "icon-menu",
|
||||
"orderNum": 1,
|
||||
"viewPath": "modules/know/views/data/type.vue",
|
||||
"keepAlive": true,
|
||||
"isShow": true,
|
||||
"childMenus": [
|
||||
{
|
||||
"name": "删除",
|
||||
"router": null,
|
||||
"perms": "know:data:info:delete",
|
||||
"type": 2,
|
||||
"icon": null,
|
||||
"orderNum": 0,
|
||||
"viewPath": null,
|
||||
"keepAlive": true,
|
||||
"isShow": true,
|
||||
"childMenus": []
|
||||
},
|
||||
{
|
||||
"name": "修改",
|
||||
"router": null,
|
||||
"perms": "know:data:info:update,know:data:info:info",
|
||||
"type": 2,
|
||||
"icon": null,
|
||||
"orderNum": 0,
|
||||
"viewPath": null,
|
||||
"keepAlive": true,
|
||||
"isShow": true,
|
||||
"childMenus": []
|
||||
},
|
||||
{
|
||||
"name": "单个信息",
|
||||
"router": null,
|
||||
"perms": "know:data:info:info",
|
||||
"type": 2,
|
||||
"icon": null,
|
||||
"orderNum": 0,
|
||||
"viewPath": null,
|
||||
"keepAlive": true,
|
||||
"isShow": true,
|
||||
"childMenus": []
|
||||
},
|
||||
{
|
||||
"name": "列表查询",
|
||||
"router": null,
|
||||
"perms": "know:data:info:list",
|
||||
"type": 2,
|
||||
"icon": null,
|
||||
"orderNum": 0,
|
||||
"viewPath": null,
|
||||
"keepAlive": true,
|
||||
"isShow": true,
|
||||
"childMenus": []
|
||||
},
|
||||
{
|
||||
"name": "分页查询",
|
||||
"router": null,
|
||||
"perms": "know:data:info:page",
|
||||
"type": 2,
|
||||
"icon": null,
|
||||
"orderNum": 0,
|
||||
"viewPath": null,
|
||||
"keepAlive": true,
|
||||
"isShow": true,
|
||||
"childMenus": []
|
||||
},
|
||||
{
|
||||
"name": "新增",
|
||||
"router": null,
|
||||
"perms": "know:data:info:add",
|
||||
"type": 2,
|
||||
"icon": null,
|
||||
"orderNum": 0,
|
||||
"viewPath": null,
|
||||
"keepAlive": true,
|
||||
"isShow": true,
|
||||
"childMenus": []
|
||||
},
|
||||
{
|
||||
"name": "知识库配置",
|
||||
"router": null,
|
||||
"perms": "know:config:getByFunc,know:config:config,know:config:all,know:config:delete,know:config:update,know:config:info,know:config:page,know:config:add",
|
||||
"type": 2,
|
||||
"icon": null,
|
||||
"orderNum": 0,
|
||||
"viewPath": null,
|
||||
"keepAlive": true,
|
||||
"isShow": true,
|
||||
"childMenus": []
|
||||
},
|
||||
{
|
||||
"name": "类型删除",
|
||||
"router": null,
|
||||
"perms": "know:data:type:delete",
|
||||
"type": 2,
|
||||
"icon": null,
|
||||
"orderNum": 0,
|
||||
"viewPath": null,
|
||||
"keepAlive": true,
|
||||
"isShow": true,
|
||||
"childMenus": []
|
||||
},
|
||||
{
|
||||
"name": "类型修改",
|
||||
"router": null,
|
||||
"perms": "know:data:type:update",
|
||||
"type": 2,
|
||||
"icon": null,
|
||||
"orderNum": 0,
|
||||
"viewPath": null,
|
||||
"keepAlive": true,
|
||||
"isShow": true,
|
||||
"childMenus": []
|
||||
},
|
||||
{
|
||||
"name": "类型详情",
|
||||
"router": null,
|
||||
"perms": "know:data:type:info",
|
||||
"type": 2,
|
||||
"icon": null,
|
||||
"orderNum": 0,
|
||||
"viewPath": null,
|
||||
"keepAlive": true,
|
||||
"isShow": true,
|
||||
"childMenus": []
|
||||
},
|
||||
{
|
||||
"name": "类型新增",
|
||||
"router": null,
|
||||
"perms": "know:data:type:add",
|
||||
"type": 2,
|
||||
"icon": null,
|
||||
"orderNum": 0,
|
||||
"viewPath": null,
|
||||
"keepAlive": true,
|
||||
"isShow": true,
|
||||
"childMenus": []
|
||||
},
|
||||
{
|
||||
"name": "类型分页",
|
||||
"router": null,
|
||||
"perms": "know:data:type:page",
|
||||
"type": 2,
|
||||
"icon": null,
|
||||
"orderNum": 0,
|
||||
"viewPath": null,
|
||||
"keepAlive": true,
|
||||
"isShow": true,
|
||||
"childMenus": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "知识库详情",
|
||||
"router": "/know/data/info",
|
||||
"perms": null,
|
||||
"type": 1,
|
||||
"icon": "icon-search",
|
||||
"orderNum": 2,
|
||||
"viewPath": "modules/know/views/data/info.vue",
|
||||
"keepAlive": true,
|
||||
"isShow": false,
|
||||
"childMenus": [
|
||||
{
|
||||
"name": "删除",
|
||||
"router": null,
|
||||
"perms": "know:data:info:delete",
|
||||
"type": 2,
|
||||
"icon": null,
|
||||
"orderNum": 0,
|
||||
"viewPath": null,
|
||||
"keepAlive": true,
|
||||
"isShow": true,
|
||||
"childMenus": []
|
||||
},
|
||||
{
|
||||
"name": "修改",
|
||||
"router": null,
|
||||
"perms": "know:data:info:update,know:data:info:info",
|
||||
"type": 2,
|
||||
"icon": null,
|
||||
"orderNum": 0,
|
||||
"viewPath": null,
|
||||
"keepAlive": true,
|
||||
"isShow": true,
|
||||
"childMenus": []
|
||||
},
|
||||
{
|
||||
"name": "单个信息",
|
||||
"router": null,
|
||||
"perms": "know:data:info:info",
|
||||
"type": 2,
|
||||
"icon": null,
|
||||
"orderNum": 0,
|
||||
"viewPath": null,
|
||||
"keepAlive": true,
|
||||
"isShow": true,
|
||||
"childMenus": []
|
||||
},
|
||||
{
|
||||
"name": "列表查询",
|
||||
"router": null,
|
||||
"perms": "know:data:info:list",
|
||||
"type": 2,
|
||||
"icon": null,
|
||||
"orderNum": 0,
|
||||
"viewPath": null,
|
||||
"keepAlive": true,
|
||||
"isShow": true,
|
||||
"childMenus": []
|
||||
},
|
||||
{
|
||||
"name": "分页查询",
|
||||
"router": null,
|
||||
"perms": "know:data:info:page",
|
||||
"type": 2,
|
||||
"icon": null,
|
||||
"orderNum": 0,
|
||||
"viewPath": null,
|
||||
"keepAlive": true,
|
||||
"isShow": true,
|
||||
"childMenus": []
|
||||
},
|
||||
{
|
||||
"name": "新增",
|
||||
"router": null,
|
||||
"perms": "know:data:info:add",
|
||||
"type": 2,
|
||||
"icon": null,
|
||||
"orderNum": 0,
|
||||
"viewPath": null,
|
||||
"keepAlive": true,
|
||||
"isShow": true,
|
||||
"childMenus": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
6
src/modules/know/package.json
Normal file
6
src/modules/know/package.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"chromadb": "^1.8.1",
|
||||
"cheerio": "1.0.0-rc.12"
|
||||
}
|
||||
}
|
25
src/modules/know/rerank/base.ts
Normal file
25
src/modules/know/rerank/base.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { DocumentInterface } from '@langchain/core/documents';
|
||||
|
||||
/**
|
||||
* 重排基类
|
||||
*/
|
||||
export abstract class KnowRerankBase {
|
||||
/**
|
||||
* 重排
|
||||
* @param docs 文档
|
||||
* @param text 文本
|
||||
* @param topN
|
||||
* @returns
|
||||
*/
|
||||
abstract rerank(
|
||||
docs: DocumentInterface[],
|
||||
text: string,
|
||||
topN: number
|
||||
): Promise<{ index: number; relevanceScore: number }[]>;
|
||||
|
||||
/**
|
||||
* 配置
|
||||
* @param options
|
||||
*/
|
||||
abstract config(options: any);
|
||||
}
|
33
src/modules/know/rerank/cohere.ts
Normal file
33
src/modules/know/rerank/cohere.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { DocumentInterface } from '@langchain/core/documents';
|
||||
import { KnowRerankBase } from './base';
|
||||
import { CohereRerank } from '@langchain/cohere';
|
||||
|
||||
/**
|
||||
* cohere重排 https://docs.cohere.com/reference/rerank
|
||||
*/
|
||||
export class KnowRerankCohere extends KnowRerankBase {
|
||||
cohere: CohereRerank;
|
||||
|
||||
/**
|
||||
* 配置
|
||||
* @param options
|
||||
*/
|
||||
config(options: any) {
|
||||
this.cohere = new CohereRerank(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重排
|
||||
* @param docs
|
||||
* @param text
|
||||
* @param topN
|
||||
*/
|
||||
rerank(
|
||||
docs: DocumentInterface<Record<string, any>>[],
|
||||
text: string,
|
||||
topN: number
|
||||
): Promise<{ index: number; relevanceScore: number }[]> {
|
||||
const result = this.cohere.rerank(docs, text, { topN });
|
||||
return result;
|
||||
}
|
||||
}
|
26
src/modules/know/rerank/config.ts
Normal file
26
src/modules/know/rerank/config.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { RerankType } from '.';
|
||||
|
||||
/**
|
||||
* 配置模板
|
||||
*/
|
||||
export const ConfigRerank: { [key in RerankType]?: any } = {
|
||||
// cohere
|
||||
cohere: {
|
||||
comm: {
|
||||
apiKey: 'API密钥',
|
||||
},
|
||||
options: [
|
||||
{
|
||||
field: 'model',
|
||||
title: 'Cohere',
|
||||
select: [
|
||||
'rerank-english-v3.0',
|
||||
'rerank-multilingual-v3.0',
|
||||
'rerank-english-v2.0',
|
||||
'rerank-multilingual-v2.0',
|
||||
],
|
||||
default: 'rerank-multilingual-v3.0',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
12
src/modules/know/rerank/index.ts
Normal file
12
src/modules/know/rerank/index.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { KnowRerankCohere } from './cohere';
|
||||
|
||||
/**
|
||||
* rerank模型,为了使结果更加准确,需要对结果进行重新排序
|
||||
*/
|
||||
export const RerankModel = {
|
||||
// cohere
|
||||
cohere: KnowRerankCohere,
|
||||
};
|
||||
|
||||
// Rerank类型键
|
||||
export type RerankType = keyof typeof RerankModel;
|
55
src/modules/know/service/config.ts
Normal file
55
src/modules/know/service/config.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import { Init, Provide } from '@midwayjs/decorator';
|
||||
import { BaseService } from '@cool-midway/core';
|
||||
import { AllConfig, Config, ConfigTypeKey } from '../interface';
|
||||
import { KnowConfigEntity } from '../entity/config';
|
||||
import { InjectEntityModel } from '@midwayjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
/**
|
||||
* 配置
|
||||
*/
|
||||
@Provide()
|
||||
export class KnowConfigService extends BaseService {
|
||||
@InjectEntityModel(KnowConfigEntity)
|
||||
knowConfigEntity: Repository<KnowConfigEntity>;
|
||||
|
||||
@Init()
|
||||
async init() {
|
||||
await super.init();
|
||||
this.setEntity(this.knowConfigEntity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得配置
|
||||
* @param config 节点
|
||||
* @param type 类型
|
||||
*/
|
||||
async config(config: ConfigTypeKey, type?: string) {
|
||||
return type ? Config[config][type] : Config[config];
|
||||
}
|
||||
|
||||
/**
|
||||
* 所有配置
|
||||
* @returns
|
||||
*/
|
||||
async all() {
|
||||
return AllConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过名称获取配置
|
||||
* @param func 类型
|
||||
* @param type 类型
|
||||
* @returns
|
||||
*/
|
||||
async getByFunc(func: string, type?: string): Promise<KnowConfigEntity[]> {
|
||||
const find = await this.knowConfigEntity.createQueryBuilder('a');
|
||||
if (type) {
|
||||
find.where('a.type = :type', { type });
|
||||
}
|
||||
if (func) {
|
||||
find.andWhere('a.func = :func', { func });
|
||||
}
|
||||
return await find.getMany();
|
||||
}
|
||||
}
|
114
src/modules/know/service/data/info.ts
Normal file
114
src/modules/know/service/data/info.ts
Normal file
@ -0,0 +1,114 @@
|
||||
import { Config, Init, Inject, Provide } from '@midwayjs/decorator';
|
||||
import { BaseService, CoolCommException } from '@cool-midway/core';
|
||||
import { InjectEntityModel } from '@midwayjs/typeorm';
|
||||
import { Equal, In, Repository } from 'typeorm';
|
||||
import { KnowDataInfoEntity } from '../../entity/data/info';
|
||||
import { KnowStore } from '../../store';
|
||||
import { retryWithAsync } from '@midwayjs/core';
|
||||
|
||||
/**
|
||||
* 知识信息
|
||||
*/
|
||||
@Provide()
|
||||
export class KnowDataInfoService extends BaseService {
|
||||
@InjectEntityModel(KnowDataInfoEntity)
|
||||
knowDataInfoEntity: Repository<KnowDataInfoEntity>;
|
||||
|
||||
@Inject()
|
||||
knowStore: KnowStore;
|
||||
|
||||
@Config('module.know.prefix')
|
||||
prefix: string;
|
||||
|
||||
@Config('module.know.chroma')
|
||||
chromaConfig;
|
||||
|
||||
@Init()
|
||||
async init() {
|
||||
await super.init();
|
||||
this.setEntity(this.knowDataInfoEntity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @param ids
|
||||
*/
|
||||
async delete(ids: number[]) {
|
||||
const info = await this.knowDataInfoEntity.findOneBy({ id: ids[0] });
|
||||
const store = await this.knowStore.get(info.typeId);
|
||||
await store.remove(`${this.prefix}${info.typeId}`, ids);
|
||||
await super.delete(ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增或修改
|
||||
* @param param
|
||||
* @param type
|
||||
*/
|
||||
async addOrUpdate(param: any | any[], type?: 'add' | 'update') {
|
||||
await super.addOrUpdate(param, type);
|
||||
if (type == 'add') {
|
||||
// 判断param 是数组还是对象
|
||||
if (Array.isArray(param)) {
|
||||
param.forEach(item => {
|
||||
this.retrySaveToStore(item.typeId, item);
|
||||
});
|
||||
} else {
|
||||
if (param.enable == 1) {
|
||||
this.retrySaveToStore(param.typeId, param);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const info = await this.knowDataInfoEntity.findOneBy({ id: param.id });
|
||||
const store = await this.knowStore.get(info.typeId);
|
||||
const data = {
|
||||
...info,
|
||||
...param,
|
||||
};
|
||||
if (data.enable == 0) {
|
||||
await store.remove(`${this.prefix}${info.typeId}`, [data.id]);
|
||||
await this.knowDataInfoEntity.update(data.id, {
|
||||
status: 0,
|
||||
});
|
||||
} else {
|
||||
this.retrySaveToStore(info.typeId, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重试保存到存储
|
||||
* @param typeId
|
||||
* @param param
|
||||
*/
|
||||
async retrySaveToStore(typeId: number, param: any) {
|
||||
const invokeNew = retryWithAsync(
|
||||
this.saveToStore.bind(this),
|
||||
this.chromaConfig.retry,
|
||||
{
|
||||
retryInterval: this.chromaConfig.retryInterval,
|
||||
}
|
||||
);
|
||||
try {
|
||||
await invokeNew(typeId, param);
|
||||
} catch (e) {
|
||||
console.error('retrySaveToStore error', e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保持到存储
|
||||
* @param typeId
|
||||
* @param param
|
||||
*/
|
||||
async saveToStore(typeId: number, param: any) {
|
||||
const store = await this.knowStore.get(typeId);
|
||||
await store.upsert(`${this.prefix}${typeId}`, [param]);
|
||||
await this.knowDataInfoEntity.update(
|
||||
{ id: Equal(param.id) },
|
||||
{
|
||||
status: 1,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
141
src/modules/know/service/data/type.ts
Normal file
141
src/modules/know/service/data/type.ts
Normal file
@ -0,0 +1,141 @@
|
||||
import { Config, Init, Inject, Provide } from '@midwayjs/decorator';
|
||||
import { BaseService } from '@cool-midway/core';
|
||||
import { InjectEntityModel } from '@midwayjs/typeorm';
|
||||
import { Equal, In, Repository } from 'typeorm';
|
||||
import { KnowDataTypeEntity } from '../../entity/data/type';
|
||||
import { KnowConfigEntity } from '../../entity/config';
|
||||
import { KnowConfigService } from '../config';
|
||||
import { EmbeddModel } from '../../embed';
|
||||
import { KnowStore } from '../../store';
|
||||
import { KnowDataInfoEntity } from '../../entity/data/info';
|
||||
import { KnowDataInfoService } from './info';
|
||||
|
||||
/**
|
||||
* 知识类型
|
||||
*/
|
||||
@Provide()
|
||||
export class KnowDataTypeService extends BaseService {
|
||||
@InjectEntityModel(KnowDataTypeEntity)
|
||||
knowDataTypeEntity: Repository<KnowDataTypeEntity>;
|
||||
|
||||
@InjectEntityModel(KnowDataInfoEntity)
|
||||
knowDataInfoEntity: Repository<KnowDataInfoEntity>;
|
||||
|
||||
@InjectEntityModel(KnowConfigEntity)
|
||||
knowConfigEntity: Repository<KnowConfigEntity>;
|
||||
|
||||
@Inject()
|
||||
knowDataInfoService: KnowDataInfoService;
|
||||
|
||||
@Inject()
|
||||
knowConfigService: KnowConfigService;
|
||||
|
||||
@Config('module.know.prefix')
|
||||
prefix: string;
|
||||
|
||||
@Inject()
|
||||
knowStore: KnowStore;
|
||||
|
||||
@Init()
|
||||
async init() {
|
||||
await super.init();
|
||||
this.setEntity(this.knowDataTypeEntity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重建
|
||||
* @param typeId
|
||||
*/
|
||||
async rebuild(typeId: number) {
|
||||
await this.knowDataInfoEntity.update({ typeId }, { status: 0 });
|
||||
const store = await this.knowStore.get(typeId);
|
||||
// 先删除
|
||||
await store.collection(`${this.prefix}${typeId}`, 'delete');
|
||||
// 再创建
|
||||
await store.collection(`${this.prefix}${typeId}`, 'create');
|
||||
// 获得所有数据
|
||||
const list = await this.knowDataInfoEntity.findBy({
|
||||
typeId: Equal(typeId),
|
||||
enable: 1,
|
||||
});
|
||||
// 保存到知识库
|
||||
list.forEach(item => {
|
||||
this.knowDataInfoService.retrySaveToStore(typeId, item);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增或修改
|
||||
* @param param
|
||||
* @param type
|
||||
*/
|
||||
async addOrUpdate(param: any, type?: 'add' | 'update') {
|
||||
await super.addOrUpdate(param, type);
|
||||
if (param.enable == 0) {
|
||||
return;
|
||||
}
|
||||
// 先删除
|
||||
const store = await this.knowStore.get(param.id);
|
||||
await store.collection(`${this.prefix}${param.id}`, 'delete');
|
||||
// 再创建
|
||||
await store.collection(`${this.prefix}${param.id}`, 'create');
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @param ids
|
||||
*/
|
||||
async delete(ids: number[]) {
|
||||
for (const id of ids) {
|
||||
const store = await this.knowStore.get(id);
|
||||
await store.collection(`${this.prefix}${id}`, 'delete');
|
||||
}
|
||||
await super.delete(ids);
|
||||
// 删除子数据
|
||||
await this.knowDataInfoEntity.delete({ id: In(ids) });
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得所有可用的知识库列表
|
||||
*/
|
||||
async getKnows() {
|
||||
const list = await this.knowDataTypeEntity.findBy({
|
||||
enable: 1,
|
||||
});
|
||||
return list.map(item => {
|
||||
return {
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
icon: item.icon,
|
||||
description: item.description,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得知识信息
|
||||
* @param knowIds
|
||||
*/
|
||||
async getKnow(knowId: number) {
|
||||
const result = await this.knowDataTypeEntity.findOneBy({
|
||||
id: Equal(knowId),
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得知识库对应的向量化模型
|
||||
* @param knowId
|
||||
* @returns
|
||||
*/
|
||||
async getEmbedding(knowId: number) {
|
||||
const know = await this.getKnow(knowId);
|
||||
const embedConfigId = know.embedConfigId;
|
||||
const config = await this.knowConfigService.info(embedConfigId);
|
||||
const embedding = new EmbeddModel[config.type]({
|
||||
...config.options.comm,
|
||||
...know.embedOptions,
|
||||
});
|
||||
return embedding;
|
||||
}
|
||||
}
|
33
src/modules/know/service/loader.ts
Normal file
33
src/modules/know/service/loader.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { Inject, Provide } from '@midwayjs/decorator';
|
||||
import { BaseService } from '@cool-midway/core';
|
||||
import { KnowUnstructuredLoader } from '../loader/unstructured';
|
||||
import { KnowCheerioLoader } from '../loader/cheerio';
|
||||
|
||||
/**
|
||||
* 非结构化文档加载器
|
||||
*/
|
||||
@Provide()
|
||||
export class KnowLoaderService extends BaseService {
|
||||
@Inject()
|
||||
knowUnstructuredLoader: KnowUnstructuredLoader;
|
||||
|
||||
@Inject()
|
||||
knowCheerioLoader: KnowCheerioLoader;
|
||||
|
||||
/**
|
||||
* 加载文件
|
||||
* @param filePath
|
||||
*/
|
||||
async loadFile(filePath: string) {
|
||||
return await this.knowUnstructuredLoader.loadFile(filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载链接
|
||||
* @param link
|
||||
* @returns
|
||||
*/
|
||||
async loadLink(link: string) {
|
||||
return await this.knowCheerioLoader.loadLink(link);
|
||||
}
|
||||
}
|
137
src/modules/know/service/retriever.ts
Normal file
137
src/modules/know/service/retriever.ts
Normal file
@ -0,0 +1,137 @@
|
||||
import { Inject, Provide } from '@midwayjs/decorator';
|
||||
import { BaseService } from '@cool-midway/core';
|
||||
import { SearchOptions } from '../interface';
|
||||
import { VectorStore } from '@langchain/core/vectorstores';
|
||||
import { Config, IMidwayContext } from '@midwayjs/core';
|
||||
import {} from '../store/base';
|
||||
import { KnowStore } from '../store';
|
||||
import { KnowDataTypeService } from './data/type';
|
||||
import { DocumentInterface } from '@langchain/core/documents';
|
||||
import { KnowConfigService } from './config';
|
||||
import { RerankModel } from '../rerank';
|
||||
import { KnowRerankBase } from '../rerank/base';
|
||||
|
||||
/**
|
||||
* 检索服务
|
||||
*/
|
||||
@Provide()
|
||||
export class KnowRetrieverService extends BaseService {
|
||||
@Inject()
|
||||
ctx: IMidwayContext;
|
||||
|
||||
@Inject()
|
||||
knowStore: KnowStore;
|
||||
|
||||
@Config('module.know.prefix')
|
||||
prefix: string;
|
||||
|
||||
@Inject()
|
||||
knowDataTypeService: KnowDataTypeService;
|
||||
|
||||
@Inject()
|
||||
knowConfigService: KnowConfigService;
|
||||
|
||||
/**
|
||||
* 调用
|
||||
* @param knowId
|
||||
* @param text
|
||||
*/
|
||||
async invoke(
|
||||
knowId: number,
|
||||
text: string,
|
||||
options?: SearchOptions
|
||||
): Promise<[DocumentInterface, number][]> {
|
||||
const store = await this.getStore(knowId);
|
||||
const result = await store.similaritySearchWithScore(
|
||||
text,
|
||||
options?.size || 10
|
||||
);
|
||||
return await this.rerank(knowId, text, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新排序调用rerank
|
||||
* @param knowId
|
||||
* @param result
|
||||
* @returns
|
||||
*/
|
||||
async rerank(
|
||||
knowId,
|
||||
text: string,
|
||||
result: [DocumentInterface, number][],
|
||||
options?: SearchOptions
|
||||
) {
|
||||
const know = await this.knowDataTypeService.getKnow(knowId);
|
||||
if (know.enable == 0) {
|
||||
return [];
|
||||
}
|
||||
// 处理rerank
|
||||
if (!know.enableRerank) {
|
||||
return result;
|
||||
}
|
||||
const config = await this.knowConfigService.info(know.rerankConfigId);
|
||||
const rerank: KnowRerankBase = new RerankModel[config.type]();
|
||||
rerank.config({
|
||||
...config.options.comm,
|
||||
...know.rerankOptions,
|
||||
});
|
||||
const res = await rerank.rerank(
|
||||
result.map(item => item[0]),
|
||||
text,
|
||||
options?.size || 10
|
||||
);
|
||||
// 对比 重新返回新的结果
|
||||
const newResult = [];
|
||||
for (const item of res) {
|
||||
const arr = [];
|
||||
arr.push(result[item.index]);
|
||||
arr.push(item.relevanceScore);
|
||||
newResult.push(arr);
|
||||
}
|
||||
return newResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索
|
||||
* @param knowIds 知识库ID 多个
|
||||
* @param text 文本
|
||||
* @param options 配置
|
||||
*/
|
||||
async search(knowIds: number[], text: string, options: SearchOptions) {
|
||||
const results: [DocumentInterface, number][][] = await Promise.all(
|
||||
knowIds.map(knowId => {
|
||||
return this.invoke(knowId, text, options);
|
||||
})
|
||||
);
|
||||
// 合并结果,按照得分排序 number是得分
|
||||
const result = results.reduce((prev, curr) => {
|
||||
return prev.concat(curr);
|
||||
}, []);
|
||||
result.sort((a, b) => b[1] - a[1]);
|
||||
// 只返回文档结果,并取前N个
|
||||
return result.map(item => item[0]).slice(0, options.size || 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得存储器
|
||||
* @param knowIds
|
||||
* @returns
|
||||
*/
|
||||
async getStores(knowIds: number[]): Promise<VectorStore[]> {
|
||||
return await Promise.all(
|
||||
knowIds.map(knowId => {
|
||||
return this.getStore(knowId);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得存储器
|
||||
* @param knowId
|
||||
* @returns
|
||||
*/
|
||||
async getStore(knowId: number): Promise<VectorStore> {
|
||||
const store = await this.knowStore.get(knowId);
|
||||
return await store.getStore(`${this.prefix}${knowId}`);
|
||||
}
|
||||
}
|
45
src/modules/know/store/base.ts
Normal file
45
src/modules/know/store/base.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import { Embeddings } from '@langchain/core/embeddings';
|
||||
import { KnowDataInfoEntity } from '../entity/data/info';
|
||||
import { VectorStore } from '@langchain/core/vectorstores';
|
||||
|
||||
/**
|
||||
* 存储基类
|
||||
*/
|
||||
export abstract class KnowStoreBase {
|
||||
// 向量化器
|
||||
embedding: Embeddings;
|
||||
|
||||
set(embedding: Embeddings) {
|
||||
this.embedding = embedding;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得存储器
|
||||
* @param collection
|
||||
*/
|
||||
abstract getStore(collection: string): Promise<VectorStore>;
|
||||
|
||||
/**
|
||||
* 操作集合
|
||||
* @param name
|
||||
* @param type
|
||||
*/
|
||||
abstract collection(name: string, type: 'create' | 'delete' | 'get');
|
||||
|
||||
/**
|
||||
* 更新
|
||||
* @param collection
|
||||
* @param datas
|
||||
*/
|
||||
abstract upsert(
|
||||
collection: string,
|
||||
datas: KnowDataInfoEntity[]
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* 移除
|
||||
* @param collection 集合
|
||||
* @param ids id
|
||||
*/
|
||||
abstract remove(collection: string, ids: number[]): Promise<void>;
|
||||
}
|
94
src/modules/know/store/chroma.ts
Normal file
94
src/modules/know/store/chroma.ts
Normal file
@ -0,0 +1,94 @@
|
||||
import {
|
||||
Provide,
|
||||
Scope,
|
||||
ScopeEnum,
|
||||
App,
|
||||
IMidwayApplication,
|
||||
Config,
|
||||
} from '@midwayjs/core';
|
||||
import { KnowStoreBase } from './base';
|
||||
import { KnowDataInfoEntity } from '../entity/data/info';
|
||||
import { Chroma } from '@langchain/community/vectorstores/chroma';
|
||||
|
||||
/**
|
||||
* 向量数据库
|
||||
*/
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Prototype)
|
||||
export class KnowChromaStore extends KnowStoreBase {
|
||||
@App()
|
||||
app: IMidwayApplication;
|
||||
|
||||
@Config('module.know.chroma')
|
||||
chromaConfig: {
|
||||
// 服务地址
|
||||
url: string;
|
||||
// 距离计算方式 可选 l2、cosine、ip
|
||||
distance: 'l2' | 'cosine' | 'ip';
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param collection
|
||||
* @returns
|
||||
*/
|
||||
async getStore(collectionName: string) {
|
||||
const vectorStore = await Chroma.fromExistingCollection(this.embedding, {
|
||||
url: this.chromaConfig.url,
|
||||
collectionMetadata: {
|
||||
'hnsw:space': this.chromaConfig.distance,
|
||||
},
|
||||
collectionName,
|
||||
});
|
||||
vectorStore.index.createCollection;
|
||||
return vectorStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 | 删除 | 获取集合
|
||||
* @param name
|
||||
* @param type
|
||||
*/
|
||||
async collection(name: string, type: 'create' | 'delete' | 'get') {
|
||||
let store = await this.getStore(name);
|
||||
if (type == 'delete') {
|
||||
await store.index.deleteCollection({ name });
|
||||
}
|
||||
return store;
|
||||
}
|
||||
|
||||
/**
|
||||
* 插入 | 更新
|
||||
* @param collection
|
||||
* @param datas
|
||||
*/
|
||||
async upsert(collection: string, datas: KnowDataInfoEntity[]) {
|
||||
const store = await this.getStore(collection);
|
||||
const documents = datas.map(item => {
|
||||
return {
|
||||
pageContent: item.content.data,
|
||||
metadata: {
|
||||
_id: `id_${item.id}`,
|
||||
...item,
|
||||
},
|
||||
};
|
||||
});
|
||||
await store.addDocuments(documents);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @param collection
|
||||
* @param ids
|
||||
*/
|
||||
async remove(collection: string, ids: number[]) {
|
||||
const store = await this.getStore(collection);
|
||||
await store.collection.delete({
|
||||
where: {
|
||||
_id: {
|
||||
$in: ids.map(id => `id_${id}`),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
52
src/modules/know/store/index.ts
Normal file
52
src/modules/know/store/index.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import {
|
||||
App,
|
||||
Config,
|
||||
IMidwayApplication,
|
||||
Inject,
|
||||
Provide,
|
||||
} from '@midwayjs/core';
|
||||
import { KnowChromaStore } from './chroma';
|
||||
import { KnowStoreBase } from './base';
|
||||
import { KnowDataTypeService } from '../service/data/type';
|
||||
|
||||
/**
|
||||
* 存储器
|
||||
*/
|
||||
export const StoreType = {
|
||||
// Chroma 存储, 云端存储, 需要安装 Chroma 服务
|
||||
chroma: KnowChromaStore,
|
||||
};
|
||||
|
||||
// 存储器类型键
|
||||
export type StoreTypes = keyof typeof StoreType;
|
||||
|
||||
/**
|
||||
* 存储器
|
||||
*/
|
||||
@Provide()
|
||||
export class KnowStore {
|
||||
@Config('module.know.store')
|
||||
store: StoreTypes;
|
||||
|
||||
@Config('module.know.prefix')
|
||||
prefix: string;
|
||||
|
||||
@Inject()
|
||||
knowDataTypeService: KnowDataTypeService;
|
||||
|
||||
@App()
|
||||
app: IMidwayApplication;
|
||||
|
||||
/**
|
||||
* 获得存储器
|
||||
* @param knowId
|
||||
*/
|
||||
async get(knowId: number): Promise<KnowStoreBase> {
|
||||
const embedding = await this.knowDataTypeService.getEmbedding(knowId);
|
||||
const store: KnowStoreBase = await this.app
|
||||
.getApplicationContext()
|
||||
.getAsync(StoreType[this.store]);
|
||||
store.set(embedding);
|
||||
return store;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user