feat: add neo4j and prompt and prompt-graph

This commit is contained in:
xion 2024-09-26 01:18:43 +08:00
parent 25c055b490
commit 229ad7caed
12 changed files with 441 additions and 74 deletions

@ -1 +1 @@
Subproject commit d1e2306233fde79b510dec614a95f867d686243d Subproject commit f1346b3cef6684707bcd4faec20231e535904486

View File

@ -2,14 +2,14 @@ import { App } from '@abearxiong/router';
import { useConfig } from '@abearxiong/use-config'; import { useConfig } from '@abearxiong/use-config';
import { dynamicImport } from './lib/dynamic-import.ts'; import { dynamicImport } from './lib/dynamic-import.ts';
import { redisPublisher, redisSubscriber, redis } from './modules/redis.ts'; import { redisPublisher, redisSubscriber, redis } from './modules/redis.ts';
import { neode } from './modules/neo4j.ts'; import { neode, getSession } from './modules/neo4j.ts';
import { minioClient } from './modules/minio.ts'; import { minioClient } from './modules/minio.ts';
import { sequelize } from './modules/sequelize.ts'; import { sequelize } from './modules/sequelize.ts';
useConfig(); useConfig();
export const emit = (channel: string, message?: any) => { export const emit = (channel: string, message?: any) => {
redisPublisher.publish(channel, JSON.stringify(message)); redisPublisher.publish(channel, JSON.stringify(message));
}; };
export { neode, redis, minioClient, sequelize }; export { neode, getSession, redis, minioClient, sequelize };
export const app = new App<{ import: any; emit: typeof emit }>({ export const app = new App<{ import: any; emit: typeof emit }>({
serverOptions: { serverOptions: {

View File

@ -90,7 +90,7 @@ RouterCodeModel.init(
}, },
{ {
sequelize, sequelize,
tableName: 'cf_router_code', tableName: 'cf_router_code', // container flow router code
}, },
); );
RouterCodeModel.sync({ alter: true, logging: false }).catch((e) => { RouterCodeModel.sync({ alter: true, logging: false }).catch((e) => {

View File

@ -0,0 +1,83 @@
import { neode } from '@/app.ts';
import { getSession } from '@/modules/neo4j.ts';
import Neode from 'neode';
export const PromptNeo = neode.model('Prompt', {
id: {
type: 'uuid',
primary: true,
},
title: {
type: 'string',
},
description: 'string',
// profile: { type: 'object', optional: true }, // 用于存储 JSON 对象
prompt: 'string',
// inputVariables: { type: 'array', item },
// tags: { type: 'array', items: 'string', optional: true } // 定义字符串数组
inputVariables: { type: 'string', default: JSON.stringify([]) },
localVariables: { type: 'string', default: JSON.stringify([]) },
// 定义可单向或双向的关系
relatedPrompts: {
type: 'relationship',
relationship: 'RELATED_TO',
target: 'Prompt', // 指向自身
direction: 'out', // 默认是单向的
properties: {
created_at: 'datetime',
bidirectional: 'boolean', // 用来标记该关系是否为双向
},
eager: true, // 自动加载相关的 Prompts
},
});
export async function createRelationship(promptA: Neode.Node<unknown>, promptB: Neode.Node<unknown>, isBidirectional = false) {
// 创建单向关系
await promptA.relateTo(promptB, 'RELATED_TO', { created_at: new Date(), bidirectional: isBidirectional });
// 如果是双向关系,创建反向关系
if (isBidirectional) {
await promptB.relateTo(promptA, 'RELATED_TO', { created_at: new Date(), bidirectional: true });
}
}
export async function createRelationship2(promptId1, promptId2, isBidirectional = false) {
const query = `
MATCH (p1:Prompt {id: $id1}), (p2:Prompt {id: $id2})
CREATE (p1)-[r:RELATED_TO {created_at: $createdAt, bidirectional: $bidirectional}]->(p2)
RETURN r
`;
const result = await getSession().run(query, {
id1: promptId1,
id2: promptId2,
createdAt: new Date().toISOString(),
bidirectional: isBidirectional,
});
return result.records[0].get('r');
}
export async function createPrompt(promptData) {
const session = getSession();
const query = `
CREATE (p:Prompt {
id: $id,
title: $title,
description: $description,
prompt: $prompt,
inputVariables: $inputVariables,
localVariables: $localVariables
})
RETURN p
`;
const result = await session.run(query, {
id: promptData.id,
title: promptData.title,
description: promptData.description,
prompt: promptData.prompt,
inputVariables: JSON.stringify(promptData.inputVariables || []),
localVariables: JSON.stringify(promptData.localVariables || []),
});
return result.records[0].get('p');
}

View File

@ -1,18 +1,65 @@
import { neode } from '@/app.ts'; import { sequelize } from '../modules/sequelize.ts';
import { DataTypes, Model } from 'sequelize';
import { Variable } from '@kevisual/ai-graph';
export const PromptNeo = neode.model('Prompt', { /**
*
*/
export type PresetData = {
// 参数
validator: {
[key: string]: any; // 请求的内容的验证器
};
data: {
prompt?: string; // 提前预设值
inputs: Variable & { operate?: string }[]; // 请求内容的变量和内容
};
};
export class Prompt extends Model {
declare id: string;
declare title: string;
declare description: string;
declare presetData: PresetData;
}
Prompt.init(
{
id: { id: {
type: 'uuid', type: DataTypes.UUID,
primary: true, primaryKey: true,
defaultValue: DataTypes.UUIDV4,
}, },
title: { title: {
type: 'string', type: DataTypes.STRING,
allowNull: false,
}, },
description: 'string', description: {
// profile: { type: 'object', optional: true }, // 用于存储 JSON 对象 type: DataTypes.TEXT,
prompt: 'string', allowNull: true,
// inputVariables: { type: 'array', item }, },
// tags: { type: 'array', items: 'string', optional: true } // 定义字符串数组 presetData: {
inputVariables: { type: 'string', default: JSON.stringify([]) }, type: DataTypes.JSON,
localVariables: { type: 'string', default: JSON.stringify([]) }, },
key: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
},
// inputVariables: {
// type: DataTypes.JSON,
// defaultValue: [],
// },
// localVariables: {
// type: DataTypes.JSON,
// defaultValue: [],
// },
},
{
sequelize, // 传入 Sequelize 实例
modelName: 'prompt', // 模型名称
},
);
Prompt.sync({ alter: true, force: false, logging: false }).catch((e) => {
console.error('Prompt sync error', e);
}); });

View File

@ -1,17 +1,26 @@
import Neode from 'neode'; import Neode from 'neode';
import { useConfig } from '@abearxiong/use-config'; import { useConfig } from '@abearxiong/use-config';
import neo4j from 'neo4j-driver';
type NeodeConfig = { type NeodeConfig = {
uri: string; uri: string;
username: string; username: string;
password: string; password: string;
}; };
const { neo4j } = useConfig<{ neo4j: NeodeConfig }>('neo4j'); const { neo4j: neo4jConfig } = useConfig<{ neo4j: NeodeConfig }>('neo4j');
const { uri, username, password } = neo4j; const { uri, username, password } = neo4jConfig;
// 设置连接配置 // 设置连接配置
// const neode = new Neode('bolt://localhost:7687', 'neo4j', 'your_password'); // const neode = new Neode('bolt://localhost:7687', 'neo4j', 'your_password');
export const neode = new Neode(uri, username, password); export const neode = new Neode(uri, username, password);
// 创建与 Neo4j 数据库的连接
export const neoDriver = neo4j.driver(
uri, // 数据库地址
neo4j.auth.basic(username, password), // 用户名和密码
);
export const getSession = () => {
return neoDriver.session();
};
const testConnect = async () => { const testConnect = async () => {
// 连接成功 // 连接成功

View File

@ -3,7 +3,7 @@ import { Ollama, Message, ChatRequest } from 'ollama';
const config = useConfig<{ ollama: Ollama['config'] & { model: string } }>(); const config = useConfig<{ ollama: Ollama['config'] & { model: string } }>();
const { host } = config.ollama; const { host, model } = config.ollama;
export const ollama = new Ollama({ host }); export const ollama = new Ollama({ host });
@ -20,7 +20,7 @@ export const chat = (messages: ChatMessage[], chatOpts?: ChatOpts) => {
const { options, stream, ...rest } = chatOpts || {}; const { options, stream, ...rest } = chatOpts || {};
return ollama.chat({ return ollama.chat({
messages, messages,
model: config.model, model: model,
options: { options: {
temperature: 0, temperature: 0,
...chatOpts?.options, ...chatOpts?.options,

View File

@ -0,0 +1,69 @@
import { app } from '@/app.ts';
import { Prompt } from '@/models/prompt.ts';
import { chat } from '@/modules/ollama.ts';
import { CustomError } from '@abearxiong/router';
import { PromptTemplate } from '@kevisual/ai-graph';
app
.route('ai', 'run', { nextRoute: { id: 'runOllama' } })
.define({
validator: {
key: {
type: 'string',
required: true,
message: 'Prompt key is required',
},
},
})
.define(async (ctx) => {
// ctx.currentRoute?.verify(ctx, true);
const { key, inputs = [] } = ctx.query.data || {};
if (!key) {
throw new CustomError('Prompt key is required');
}
const prompt = await Prompt.findOne({ where: { key } });
if (!prompt) {
throw new CustomError('Prompt not found');
}
const { presetData } = prompt;
const { data, validator } = presetData || {};
// const { inputs = [] } = data;
// TODO: 获取validator和inputs的内容
const promptTemplate = new PromptTemplate({
prompt: data.prompt,
inputVariables: inputs.map((item) => {
return {
key: item.key,
value: item.value,
};
}),
localVariables: [],
});
const result = await promptTemplate.getTemplate();
ctx.state = {
prompt: result,
};
ctx.body = result;
})
.addTo(app);
app
.route('ai', 'runOllama', {
id: 'runOllama',
})
.define(async (ctx) => {
const prompt = ctx.state.prompt;
if (!prompt) {
throw new CustomError('Prompt not found');
}
console.log('prompt', typeof prompt, prompt);
const res = await chat([
{
role: 'user',
content: prompt,
},
]);
ctx.body = res;
})
.addTo(app);

View File

@ -0,0 +1,73 @@
import { getSession } from '@/app.ts';
export async function fetchData() {
const session = getSession();
try {
const query = `MATCH (n)
OPTIONAL MATCH (n)-[r]->(m)
RETURN n, r, m`;
const queryConnect = 'MATCH (n)-[r]->(m) RETURN n, r, m LIMIT 25';
const result = await session.run(query);
const graphData = { nodes: [], links: [] };
const nodeMap = new Map();
// n和n的关系用 relatedPrompts 进行关联
result.records.forEach((record) => {
const node = record.get('n');
const relation = record.get('r');
const target = record.get('m');
if (!nodeMap.has(node.identity)) {
nodeMap.set(node.identity, {
id: node.identity.toString(),
label: node.labels[0],
properties: node.properties,
});
graphData.nodes.push(nodeMap.get(node.identity));
}
if (relation && !nodeMap.has(relation.identity)) {
nodeMap.set(relation.identity, {
id: relation.identity.toString(),
label: relation.type,
properties: relation.properties,
});
graphData.nodes.push(nodeMap.get(relation.identity));
}
if (target && !nodeMap.has(target.identity)) {
nodeMap.set(target.identity, {
id: target.identity.toString(),
label: target.labels[0],
properties: target.properties,
});
graphData.nodes.push(nodeMap.get(target.identity));
}
if (relation) {
graphData.links.push({
source: node.identity.toString(),
target: relation.identity.toString(),
type: relation.type,
properties: relation.properties,
});
}
if (target) {
graphData.links.push({
source: node.identity.toString(),
target: target.identity.toString(),
type: 'RELATED_TO',
properties: {},
});
}
});
return graphData;
} finally {
await session.close();
}
}
// fetchData().then((graphData) => {
// console.log(graphData); // 用于验证获取的数据
// drawGraph(graphData); // 调用 D3 绘制函数
// });

View File

@ -1 +1,3 @@
import './list.ts' import './list-graph.ts';
import './list.ts';
import './ai.ts';

View File

@ -0,0 +1,97 @@
import { PromptNeo, createRelationship, createRelationship2 } from '@/models/prompt-graph.ts';
import { app } from '@/app.ts';
import { v4 } from 'uuid';
import { fetchData } from './d3/get-graph.ts';
app
.route('prompt-graph', 'list')
.define(async (ctx) => {
const prompts = await PromptNeo.all();
const json = await prompts.toJson();
// console.log('json', json);
ctx.body = json;
})
.addTo(app);
app
.route('prompt-graph', 'update')
.define(async (ctx) => {
const { id, title, description, prompt, inputVariables, localVariables } = ctx.query;
const promptNode = await PromptNeo.first('id', id);
if (!promptNode) {
const promptData = {
id: v4(),
title,
description,
prompt,
inputVariables: JSON.stringify(inputVariables),
localVariables: JSON.stringify(localVariables),
};
const _prompt = await PromptNeo.create(promptData);
ctx.body = await _prompt.toJson();
return;
}
await promptNode.update({ title, description, prompt, inputVariables, localVariables });
ctx.body = await promptNode.toJson();
})
.addTo(app);
app
.route('prompt-graph', 'delete')
.define(async (ctx) => {
const { id, title } = ctx.query;
const promptNode = await PromptNeo.first('id', id);
if (!promptNode) {
ctx.body = 'prompt not found';
return;
}
await promptNode.delete();
ctx.body = 'delete success';
})
.addTo(app);
app
.route('prompt-graph', 'deleteAll')
.define(async (ctx) => {
const prompts = await PromptNeo.all();
for (const prompt of prompts) {
await prompt.delete();
}
ctx.body = 'delete all success';
})
.addTo(app);
app
.route('prompt-graph', 'createDemo')
.define(async (ctx) => {
const promptData = {
id: v4(),
title: 'test-' + v4(),
description: '这是测试保存prompt的数据',
prompt: '这是测试保存prompt的数据',
inputVariables: JSON.stringify([{ key: 'test', value: 'test' }]),
localVariables: JSON.stringify([{ key: 'test', value: 'test' }]),
};
const f = await PromptNeo.first('id', 'f5288cdb-bfca-4a65-b629-cae590ede719');
if (!f) {
ctx.body = 'not found f';
return;
}
const prompt = await PromptNeo.create({ ...promptData });
// await prompt.relateTo(f, 'RELATED_TO', { createdAt: new Date().toISOString() });
// f.relateTo(prompt, 'RELATED_TO', { createdAt: new Date().toISOString() });
// await createRelationship(f, prompt);
const fj = await f.toJson() as any;
const pj = await prompt.toJson() as any;
await createRelationship2(fj.id, pj.id);
ctx.body = await prompt.toJson();
})
.addTo(app);
app
.route('prompt-graph', 'getD3')
.define(async (ctx) => {
const value = await fetchData();
ctx.body = value;
})
.addTo(app);

View File

@ -1,76 +1,63 @@
import { PromptNeo } from '@/models/prompt.ts'; import { Prompt } from '@/models/prompt.ts';
import { app } from '@/app.ts'; import { app } from '@/app.ts';
import { v4 } from 'uuid'; import { CustomError } from '@abearxiong/router';
app app
.route('prompt', 'list') .route('prompt', 'list')
.define(async (ctx) => { .define(async (ctx) => {
const prompts = await PromptNeo.all(); const prompts = await Prompt.findAll({
const json = await prompts.toJson(); order: [['updatedAt', 'DESC']],
console.log('json', json); });
ctx.body = json; ctx.body = prompts;
}) })
.addTo(app); .addTo(app);
app app
.route('prompt', 'update') .route('prompt', 'update')
.define(async (ctx) => { .define(async (ctx) => {
const { id, title, description, prompt, inputVariables, localVariables } = ctx.query; const { id, title, description, presetData, key } = ctx.query.data || {};
const promptNode = await PromptNeo.first('id', id); if (!key) {
throw new CustomError('Prompt key is required');
if (!promptNode) { }
const promptData = { const isEdit = !!id;
id: v4(), const promptKey = await Prompt.findOne({ where: { key } });
if (promptKey && promptKey.id !== id) {
throw new CustomError(`Prompt key is already exist, use by ${promptKey.id}`);
}
if (!isEdit) {
const prompt = new Prompt({
title, title,
key,
description, description,
prompt, presetData,
inputVariables: JSON.stringify(inputVariables), });
localVariables: JSON.stringify(localVariables), await prompt.save();
}; ctx.body = prompt;
const _prompt = await PromptNeo.create(promptData);
ctx.body = await _prompt.toJson();
return; return;
} }
await promptNode.update({ title, description, prompt, inputVariables, localVariables }); const prompt = await Prompt.findByPk(id);
ctx.body = await promptNode.toJson(); if (!prompt) {
throw new CustomError('Prompt not found');
}
await prompt.update({ title, description, presetData, key });
ctx.body = prompt;
}) })
.addTo(app); .addTo(app);
app app
.route('prompt', 'delete') .route('prompt', 'delete')
.define(async (ctx) => { .define(async (ctx) => {
const { id, title } = ctx.query; const { id } = ctx.query || {};
const promptNode = await PromptNeo.first('id', id); if (!id) {
if (!promptNode) { throw new CustomError('Prompt id is required');
ctx.body = 'prompt not found';
return;
} }
await promptNode.delete(); const prompt = await Prompt.findByPk(id);
if (!prompt) {
throw new CustomError('Prompt not found');
}
await prompt.destroy();
ctx.body = 'delete success'; ctx.body = 'delete success';
}) })
.addTo(app); .addTo(app);
app
.route('prompt', 'deleteAll')
.define(async (ctx) => {
const prompts = await PromptNeo.all();
for (const prompt of prompts) {
await prompt.delete();
}
ctx.body = 'delete all success';
})
.addTo(app);
app
.route('prompt', 'createDemo')
.define(async (ctx) => {
const promptData = {
id: v4(),
title: 'test',
description: '这是测试保存prompt的数据',
prompt: '这是测试保存prompt的数据',
inputVariables: JSON.stringify([{ key: 'test', value: 'test' }]),
localVariables: JSON.stringify([{ key: 'test', value: 'test' }]),
};
const prompt = await PromptNeo.create(promptData);
ctx.body = await prompt.toJson();
})
.addTo(app);