FastGPT icon indicating copy to clipboard operation
FastGPT copied to clipboard

高级编排的一些功能需求

Open mwnu opened this issue 1 year ago • 5 comments

例行检查

  • [x] 我已确认目前没有类似 features
  • [x] 我已确认我已升级到最新版本
  • [x] 我已完整查看过项目 README,已确定现有版本无法满足需求
  • [x] 我理解并愿意跟进此 features,协助测试和提供反馈
  • [x] 我理解并认可上述内容,并理解项目维护者精力有限,不遵循规则的 features 可能会被无视或直接关闭

功能描述

  1. 聊天记录也是个全局变量,但无法在AI对话模块和应用调用流程前引用。
  • 应用场景:用户连续对话,在调用大模型生成前对上下文进行一些处理,目前的临时替代方法是在调用大模型前先调用一个空应用模块才能显式引用聊天记录。
  1. 允许向应用调用模块传入系统提示和图片,并支持在系统配置中启用图片上传功能。
  • 记得之前的版本可以在系统配置中启用视觉功能而不依赖于配置文件中的定义,我编排的高级应用没有直接调用AI对话,而是让所有的分支都走应用调用,这种情况下即便模型支持视觉功能,但在聊天框没有上传图片的按钮。
  1. 扩展文本加工模块使其支持编辑Json。
  • 目前,许多模块入参都支持引用变量,只不过有些只支持字符串格式,无法直接传入json对象,有些支持传入json(例如聊天记录、应用调用)但缺少相应的json处理模块,只能借用http模块处理。
  1. 增加高级编排版本管理功能,以及调试记录。
  • 方便开发和测试、比较不同编排下的性能、提示词的准确率等。
  1. 增加 switch、await之类的流程分支和合并的模块,和if、while之类的循环语法
  • 目前高级编排改版后,应用-插件-模块更有面向对象的味道了,相比dify、langflow等工作流编排应用有更强的定制化能力,代码更容易复用。
  1. 增加无需用户输入而是系统定义或从外部引入的全局变量,并且支持在引用变量时使用函数式编程,如JavaScript的链式调用方法对数据进行处理
  • 这方面可以参考N8N

mwnu avatar May 07 '24 13:05 mwnu

建议看看版本更新,有些重复了。1,4,6 都有了。 n8n 强大,但是太复杂了,链式调用这种不如写代码简单。

c121914yu avatar May 07 '24 13:05 c121914yu

3 能提供个例子么,或者现成的方案?

c121914yu avatar May 07 '24 13:05 c121914yu

  • 向 我刚刚更新到了V4.8-alpha。 1、4都有了,谢谢!

3 能提供个例子么,或者现成的方案?

文本加工模块也可以引用json对象,但好像字符串,输出也是字符串,不知道怎么处理json。 如果有本地沙盒的JavaScript或python模块的话,直接编写代码确实少一些学习成本,当然laf或http也行。 这是我编排的一个应用, image 在流程开始时我用一个空应用让聊天记录成为全局变量(当然这一步新版不需要了),然后将聊天记录给http模块处理,http模块请求的是n8n(laf我还没用过):

let question = $input.all()[0].json.body.quest.trim();
let prompt = question.replace(/^--[a-z0-9]{1,3}\s*/, '').replace(/\s*~~\d{1,3}$/, '');
// 创建两个字符串变量:代表app的id和name
let appId = "64efb4621d65f7a23a749fc4"; // 默认值,假设大多数情况下是这个ID
let appName = "GPT 4"; // 默认值
// 根据prompt的开头判断如何赋值appId和appName
let cmdMatch = question.match(/^--[a-z0-9]{1,3}/);
if (cmdMatch) {
  let cmd = cmdMatch[0];
  switch (cmd) {
    case '--ty':
      appId = "662ca1b6cf4394583d321933";
      appName = "Tavilly简询";
      break;
    case '--xw':
      appId = "662f580db41f0779bd56dd0a";
      appName = "SearXNG新闻";
      break;
    case '--url':
      appId = "662df8d35afe71d46903db1e";
      appName = "网页解读";
      break;
    case '--pic':
      appId = "662dbe8b43981f21abcfe562";
      appName = "DALL·E·3 绘图";
      break;
    case '--rag':
      appId = "662f0607b41f0779bd56d585";
      appName = "本地索引";
      break;
    case '--g35':
      appId = "64efb2cc1d65f7a23a749f98";
      appName = "GPT 3.5";
      break;
    case '--c3h':
      appId = "662d95480902a03b8cf74e73";
      appName = "Claude Haiku";
      break;
    case '--c3s':
      appId = "661540330576e3ce96d09345";
      appName = "Claude Sonnet";
      break;
    case '--c3o':
      appId = "662d95720902a03b8cf74eae";
      appName = "Claude Opus";
      break;
    case '--g15':
      appId = "6588eaa81d2cf5c45dbab05d";
      appName = "Gemini 1.5";
      break;
    case '--gl7':
      appId = "662d97020902a03b8cf74f60";
      appName = "Llama 3";
      break;
  }
}

// 创建app json
let appChoice = {
    "id": appId,
    "name": appName,
    "logo": ""
};

// 创建返回的JSON对象
let returnJson = {
    "prompt": prompt,
    "app_choice": appChoice,
    "memory": []
};

// 正则表达式匹配prompt末尾的'~~\d{1,2}'
let memoryMatch = question.match(/~~(\d{1,2})$/);
if (memoryMatch) {
    let n = parseInt(memoryMatch[1], 10);
    let context = $input.all()[0].json.body.context;
    if (context && context.length > 2) {
        // 限制 n 为不大于 context 长度,因为要去掉最后两个,所以n+2
        n = n+2;
        n = Math.min(n, context.length);
        let contextSlice = context.slice(-n,-2); // 取最后 n 个对象到倒数第三个
        // 检查第一个对象的 'obj' 字段是否为 'System',且不在 contextSlice 中
        if (context[0].obj === "System" && (n === 0 || context[0] !== contextSlice[0])) {
            contextSlice.unshift(context[0]); // 如果是,添加到数组前端
        }
        contextSlice[contextSlice.length-2].value.forEach(valueItem => {
        if (valueItem.type === "text") {
            // 替换开头的字符串
            valueItem.text.content = valueItem.text.content.replace(/^--[a-z0-9]{1,3}\s*/, '');
            // 替换结尾的字符串
            valueItem.text.content = valueItem.text.content.replace(/\s*~~\d{1,3}$/, '');
          }
        });
        returnJson.memory = contextSlice;
    }
}

// 返回构建的JSON对象
return returnJson;

这将输出应用选择器,处理过的上下文和当前提示词,再传给应用调用节点,从而实现在对话中使用简单的自定义指令的能力,如调用不同的应用、脚本,设置上下文长度等:

mwnu avatar May 07 '24 13:05 mwnu

  • 向 我刚刚更新到了V4.8-alpha。 1、4都有了,谢谢!

3 能提供个例子么,或者现成的方案?

文本加工模块也可以引用json对象,但好像字符串,输出也是字符串,不知道怎么处理json。 如果有本地沙盒的JavaScript或python模块的话,直接编写代码确实少一些学习成本,当然laf或http也行。 这是我编排的一个应用, image 在流程开始时我用一个空应用让聊天记录成为全局变量(当然这一步新版不需要了),然后将聊天记录给http模块处理,http模块请求的是n8n(laf我还没用过):

let question = $input.all()[0].json.body.quest.trim();
let prompt = question.replace(/^--[a-z0-9]{1,3}\s*/, '').replace(/\s*~~\d{1,3}$/, '');
// 创建两个字符串变量:代表app的id和name
let appId = "64efb4621d65f7a23a749fc4"; // 默认值,假设大多数情况下是这个ID
let appName = "GPT 4"; // 默认值
// 根据prompt的开头判断如何赋值appId和appName
let cmdMatch = question.match(/^--[a-z0-9]{1,3}/);
if (cmdMatch) {
  let cmd = cmdMatch[0];
  switch (cmd) {
    case '--ty':
      appId = "662ca1b6cf4394583d321933";
      appName = "Tavilly简询";
      break;
    case '--xw':
      appId = "662f580db41f0779bd56dd0a";
      appName = "SearXNG新闻";
      break;
    case '--url':
      appId = "662df8d35afe71d46903db1e";
      appName = "网页解读";
      break;
    case '--pic':
      appId = "662dbe8b43981f21abcfe562";
      appName = "DALL·E·3 绘图";
      break;
    case '--rag':
      appId = "662f0607b41f0779bd56d585";
      appName = "本地索引";
      break;
    case '--g35':
      appId = "64efb2cc1d65f7a23a749f98";
      appName = "GPT 3.5";
      break;
    case '--c3h':
      appId = "662d95480902a03b8cf74e73";
      appName = "Claude Haiku";
      break;
    case '--c3s':
      appId = "661540330576e3ce96d09345";
      appName = "Claude Sonnet";
      break;
    case '--c3o':
      appId = "662d95720902a03b8cf74eae";
      appName = "Claude Opus";
      break;
    case '--g15':
      appId = "6588eaa81d2cf5c45dbab05d";
      appName = "Gemini 1.5";
      break;
    case '--gl7':
      appId = "662d97020902a03b8cf74f60";
      appName = "Llama 3";
      break;
  }
}

// 创建app json
let appChoice = {
    "id": appId,
    "name": appName,
    "logo": ""
};

// 创建返回的JSON对象
let returnJson = {
    "prompt": prompt,
    "app_choice": appChoice,
    "memory": []
};

// 正则表达式匹配prompt末尾的'~~\d{1,2}'
let memoryMatch = question.match(/~~(\d{1,2})$/);
if (memoryMatch) {
    let n = parseInt(memoryMatch[1], 10);
    let context = $input.all()[0].json.body.context;
    if (context && context.length > 2) {
        // 限制 n 为不大于 context 长度,因为要去掉最后两个,所以n+2
        n = n+2;
        n = Math.min(n, context.length);
        let contextSlice = context.slice(-n,-2); // 取最后 n 个对象到倒数第三个
        // 检查第一个对象的 'obj' 字段是否为 'System',且不在 contextSlice 中
        if (context[0].obj === "System" && (n === 0 || context[0] !== contextSlice[0])) {
            contextSlice.unshift(context[0]); // 如果是,添加到数组前端
        }
        contextSlice[contextSlice.length-2].value.forEach(valueItem => {
        if (valueItem.type === "text") {
            // 替换开头的字符串
            valueItem.text.content = valueItem.text.content.replace(/^--[a-z0-9]{1,3}\s*/, '');
            // 替换结尾的字符串
            valueItem.text.content = valueItem.text.content.replace(/\s*~~\d{1,3}$/, '');
          }
        });
        returnJson.memory = contextSlice;
    }
}

// 返回构建的JSON对象
return returnJson;

这将输出应用选择器,处理过的上下文和当前提示词,再传给应用调用节点,从而实现在对话中使用简单的自定义指令的能力,如调用不同的应用、脚本,设置上下文长度等:

嗯,js沙盒过两个版本有时间了加上。 JSON加工确实感觉代码更合适。

c121914yu avatar May 07 '24 13:05 c121914yu

js 沙盒 +1 急需类似 NodeRed 的 js 编辑能力, 单纯依赖 laf 太重了

HaishengLiang avatar May 08 '24 01:05 HaishengLiang

https://github.com/labring/FastGPT/releases/tag/v4.8.2 js sandbox

c121914yu avatar May 28 '24 15:05 c121914yu