build(automated): 更新项目依赖并调整配置

- 更新项目名称为 automated
- 更新多个依赖包版本,包括 @cool-midway、@midwayjs、@langchain 等
- 更新作者信息为 lixin&liqiannan
- 更新 TypeScript 版本至 5.5.4
- 添加 newman、prettier 等新依赖
- 优化 package.json 结构和格式
This commit is contained in:
lixin 2025-01-09 17:58:04 +08:00
parent a49c9e31ab
commit 0afa56d652
69 changed files with 4746 additions and 28 deletions

View 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
View File

@ -0,0 +1 @@
success

1
lock/menu/know.menu.lock Normal file
View File

@ -0,0 +1 @@
success

View File

@ -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"
}

View File

@ -0,0 +1,19 @@
import { ModuleConfig } from '@cool-midway/core';
/**
*
*/
export default () => {
return {
// 模块名称
name: '流程编排',
// 模块描述
description: '流程管理、编排、调试、执行',
// 中间件,只对本模块有效
middlewares: [],
// 中间件,全局有效
globalMiddlewares: [],
// 模块加载顺序默认为0值越大越优先加载
order: 0,
} as ModuleConfig;
};

View 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);
}
}

View 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));
}
}

View 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 { }

View 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);
}
}
}

View 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));
}
}
}

View 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;
}

View 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;
}

View 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;
}

View 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
View 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": []
}
]
}
]

View 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;
}
}

View 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);
}
}

View 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,
});
}
}

View 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);
}
}

View 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,
};
}
}

View 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,
};
}
}

View 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',
},
];

View 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];
}
}

View 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,
},
};
}
}

View 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: [],
},
],
},
};

View 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);
}
}

View 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];
}
}

View 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);
}
}

View 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;
}
}

View 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,
});
}
}

View 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"
}
}

View 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;
}
}

View 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);
}
}
}
}

View 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>;
}

View 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;
}

View File

@ -0,0 +1,8 @@
import { Readable } from 'stream';
/**
*
*/
export class FlowStream extends Readable {
_read(size) {}
}

View 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();
}
}

View 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;
}
}

View 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,
};
}
}

View 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
}
}

View 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);
}
}
}

View 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;
};

View 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);
}
}

View 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 {}

View 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();
}
}

View 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));
}
}

View 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);
}
}

View 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',
},
],
},
};

View 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,
// minimaxhttps://www.minimaxi.com
minimax: MinimaxEmbeddings,
// ollama本地大模型https://ollama.com
ollama: OllamaEmbeddings,
};
// 向量化类型键
export type EmbeddType = keyof typeof EmbeddModel;

View 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;
}

View 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;
}

View 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;
}

View 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',
},
];

View 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;
}
}

View 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
View 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": []
}
]
}
]
}
]

View File

@ -0,0 +1,6 @@
{
"dependencies": {
"chromadb": "^1.8.1",
"cheerio": "1.0.0-rc.12"
}
}

View 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);
}

View 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;
}
}

View 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',
},
],
},
};

View File

@ -0,0 +1,12 @@
import { KnowRerankCohere } from './cohere';
/**
* rerank模型使
*/
export const RerankModel = {
// cohere
cohere: KnowRerankCohere,
};
// Rerank类型键
export type RerankType = keyof typeof RerankModel;

View 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();
}
}

View 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,
}
);
}
}

View 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;
}
}

View 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);
}
}

View 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}`);
}
}

View 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>;
}

View 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}`),
},
},
});
}
}

View 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;
}
}