使用Nest.js+LangChain给低代码平台赋上AI代码生成能力,让低代码变成低低代码!

前言

LangChain 是一个用于开发由大型语言模型(LLM)支持的应用程序的框架。可以快速使用它集成各个模型,以及格式化用户输入和模型输出,封装了很多工具类,使得开发者很容易将其集成到自己的程序当中。

最近,我参与了 @河畔一角[1] 大佬的MarsView低代码平台,大家有兴趣可以去看看:www.marsview.cc/[2]

我目前提交了十几个PR了,在开发贡献过程中,我越发感觉低代码和AI应该要结合起来,让低代码更低,更加摆脱代码书写,这才是低代码平台的灵魂所在

演示

20975376-3ce9-4f97-8bd1-43e4bd5a6b40.gif

上面演示了生成代码的过程。

要实现这样一个功能需要几个步骤:

  • 在前端写一个交互窗口,用于接收用户输入和展示反馈

  • 需要利用LangChain结合大模型提供后端接口,输入是用户提交的信息,输出是模型的思考

  • 前端接收接口返回值,使用打印流的形式将代码输出到编辑器中

Snipaste_2024-08-30_16-31-26.png

下面我将详细介绍每个步骤的实现方法

前端交互窗口和逻辑

前端是以React为技术栈的,要实现这样的窗口,可以使用一些组件库定义的模态框或者卡片组件来实现。

但是这里的交互功能比较单一,并且需要实现类似聊天的效果,所以我还是选择自己重新写一个组件。

这里创建一个窗口组件 AIChatModal.jsx.

首先是要明确这个交互窗口的功能,由于这个AI助手只是一个代码生成工具,不是一个聊天工具,所以它不需要复杂的信息流设计,能够接收一个输入,然后展示这个输入,并且反馈用户当前的状态就行。

状态有四种:

  • 执行状态:执行状态包括思考状态和代码写入状态。

  • 思考状态:向后端接口发送数据,后端接收到信息后向模型提问,模型开始思考并返回数据。

  • 代码写入状态:前端接收到接口返回后,开始写入代码,此时是写入状态

  • 完成状态:代码写入完成后显示完成状态,等待下一次输入。

  • 提示状态:当代码返回后,如果用户暂时不打算写入,可暂时不写入,不更换提示词的情况下可以再次复用代码

  • 失败状态:包括请求失败,写入失败,中断写入等。

明确状态后就可以写逻辑了,这里放置部分的代码,如果要看详细的代码,查看文末。

在窗口中写好发送信息模板和接收信息模板,以及几种状态的展示控制:

type StatusType = 'success' | 'info' | 'warning' | 'error';const [message, setMessage] = useState<string>('');
const [requestMessage, setRequestMessage] = useState<string>('');
const [responseMessage, setResponseMessage] = useState<string>('');const [showLoad, setShowLoad] = useState<boolean>(false);
const [loading, setLoading] = useState<boolean>(false);
const [loadingText, setLoadingText] = useState<string>('');
const [welcomeText, setWelcomeText] = useState<string>('MarsAI为您服务~');const [status, setStatus] = useState<boolean>(false);
const [statusInfo, setStatusInfo] = useState<{ type: StatusType; msg: string; reload: boolean }>({
type: 'success',
msg: '生成完成',
reload: false,
});<div className={styles.modalBody}><div className={styles.chatContent}>{!requestMessage && !responseMessage && <div className={styles.welcomeText}>{welcomeText}</div>}{requestMessage && (<div className={styles.chatItem}><div className={styles.chatInfo}><div className={styles.chatName}>MarsUser</div><div className={styles.avatar}><img src="/imgs/chatUser.png" alt="" /></div></div><div className={styles.chatText}><div className={styles.chatMessage}>{requestMessage}</div></div></div>)}{responseMessage && (<div className={styles.chatResponce}><div className={styles.chatInfo}><div className={styles.avatar}><img src="/imgs/ailogo.svg" alt="" /></div><div className={styles.chatName}>MarsAI</div></div><div className={styles.chatText}><div className={styles.chatMessage}>{responseMessage}</div></div></div>)}{showLoad && (<div className={styles.chatLoad}><div className={styles.chatText}>{loadingText}</div>{loading && (<div className={styles.load}><Spin size="small" /></div>)}</div>)}{status && (<div className={styles.chatLoad}>{!statusInfo.reload ? (<Alert type={statusInfo.type} showIcon message={statusInfo.msg} />) : (<Alerttype={statusInfo.type}showIconmessage={statusInfo.msg}action={<Popover placement="top" content="重新写入代码"><Buttonstyle={{ marginLeft: '5px' }}type="primary"size="small"shape="circle"icon={<ReloadOutlined />}onClick={onReloadWrite}/></Popover>}/>)}</div>)}</div>

要实现状态的改变,只需要在窗口组件中暴露出去改变状态的方法即可:

const reloadStatus = async () => {setStatus(false);setShowLoad(true);setLoading(true);setLoadingText('正在重新写入代码,请稍后');
};const cancelLoad = () => {setShowLoad(false);setLoading(false);setStatus(true);setStatusInfo({ type: 'info', msg: '取消写入', reload: true });
};const writeCompleted = async () => {setShowLoad(false);setLoading(false);setStatus(true);setStatusInfo({ type: 'success', msg: '生成完成', reload: false });
};const writeError = async () => {setShowLoad(false);setLoading(false);setStatus(true);setStatusInfo({ type: 'error', msg: '代码写入出错了', reload: false });
};const requestError = async () => {setShowLoad(false);setLoading(false);setStatus(true);setStatusInfo({ type: 'error', msg: '请求出错了', reload: false });
};const changeLoadInfo = async (text: string) => {setLoadingText(text);setShowLoad(true);setLoading(true);
};useImperativeHandle(mRef, () => {return {cancelLoad,reloadStatus,writeCompleted,writeError,changeLoadInfo,requestError,};
});

这里使用的useImperativeHandle方法,它允许组件向外界暴露控制方法,比如打开模态框,关闭模态框,或者其他对模态框内部进行操作的方法,更好地实现组件的封装和抽离。

当然我还内置了一些经过测试有效的例子提供用户验证,还有用户输入的Input模块没有介绍,这些比较常规,去看代码就能看懂。

后端接口实现

后端是使用Nest实现的,这对前端的同学极为友好。

当然这一接口实现并不要求是Nest框架才行,实际上Mars目前的后端是koa实现的,这里主要是以Nest举例,因为Nest很好的集成了TypeScript

看上面的功能流程图就知道,这个接口应该要接收一个信息输入,并且返回模型的输出。

在自定义组件的应用场景中,我们要开发一款自定义组件,主要是修改两个文件,一个是index.jsx,另一个是config.js

  • index.jsx 用于书写组件的结构

  • config.js 用于自定义组件的属性以及属性值编辑操作,在此向组件使用用户暴露调整组件的接口

所以我们希望给模型提供一条信息,它能够帮我们写出这两个文件的内容。

在接口中,我们还应该提供多模型的选择,不管是提供给用户选择还是后端自己选择,我们都要提前做好设计,模型应该是允许切换的。

针对所有自学遇到困难的同学们,我帮大家系统梳理大模型学习脉络,将这份 LLM大模型资料 分享出来:包括LLM大模型书籍、640套大模型行业报告、LLM大模型学习视频、LLM大模型学习路线、开源大模型学习教程等, 😝有需要的小伙伴,可以 扫描下方二维码领取🆓↓↓↓

👉[CSDN大礼包🎁:全网最全《LLM大模型入门+进阶学习资源包》免费分享(安全链接,放心点击)]()👈

创建一个Nest项目

为了让大家复现这一功能,我将完整描述一个demo例子,跟随下面介绍,应该能够简单跑起来一个接口。

注意:Node的版本必须在18以上,因为langchain只支持18以上的版本

  • 使用 Nest-Cli 初始化项目
npm i -g yarn // 安装过yarn请忽略
npm i -g @nestjs/cli
nest new mars-ai-nest
  • 安装依赖
yarn add zod langchain @langchain/community jsonwebtoken
  • 初始化 AI Module
nest g module ai server   
  • 创建 Controller
nest g controller ai server   
  • 创建 Service
nest g service ai server   
  • 启动服务
cd mars-ai-nest
yarn start:dev

项目启动后,就可以在各个文件中写代码了。

项目结构

我们首先来明确一下文件结构:

Snipaste_2024-08-30_11-36-00.png

Nest中,新创建一个server,起名为ai,它将负责处理接口请求,返回数据。

provider文件夹是核心代码(自己创建):

  • base中的BaseModelProvider.ts:是对LangChain工作流的封装,它包括创建一个模型,构建prompt,新建session,获取历史记忆,模型请求,接收响应流等一系列的抽象定义。

  • model目录用于各类模型的定义,这里以glm为例,除了glm外,所有langchain支持的模型都可以使用,具体查看 支持模型[3]

  • utils是工具类,对模型的输出做解析等操作

controllerservice是大家很熟悉的mvc结构。

LangChain工作流

首先来看BaseModelProvider.ts,封装一个抽象类,定义LangChain工作流

import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
import { InMemoryChatMessageHistory } from '@langchain/core/chat_history';
import { BaseMessage, MessageContent, type AIMessageChunk } from '@langchain/core/messages';
import { ChatPromptTemplate, HumanMessagePromptTemplate, MessagesPlaceholder } from '@langchain/core/prompts';
import { Runnable, RunnableWithMessageHistory, type RunnableConfig } from "@langchain/core/runnables";
import { z } from "zod";// 定义一个类型别名,表示一个值可能是 Promise 或者直接的值
type MaybePromise<T> = T | Promise<T>;// 定义创建 Runnable 的选项,包括是否使用历史记录、历史记录消息和信号
export interface BaseModelProviderCreateRunnableOptions {useHistory?: boolean;historyMessages?: BaseMessage[];signal?: AbortSignal;
}// 定义创建结构化输出 Runnable 的选项,包括是否使用历史记录、历史记录消息和 Zod schema
export interface BaseModelProviderCreateStructuredOutputRunnableOptions<ZSchema extends z.ZodType<any> = z.ZodType<any> > {useHistory?: boolean;historyMessages?: BaseMessage[];zodSchema?: ZSchema;
}// 定义一个抽象类,用于提供基础模型
export abstract class BaseModelProvider<Model extends BaseChatModel> {// 定义一个静态属性,用于存储会话 ID 和历史记录的映射static sessionIdHistoriesMap : Record<string, InMemoryChatMessageHistory> = {};// 定义一个静态方法,用于将答案内容转换为文本static answerContentToText(content: MessageContent): string {// 如果内容是字符串,直接返回if(typeof content === "string") {return content;}// 如果内容是数组,则遍历数组,将每个元素的文本拼接起来return content.map(c => {// 如果元素类型是文本,则返回文本内容if(c.type === "text") {return c.text;}// 否则返回空字符串return ''}).join(''); }// 定义一个可选的模型属性model?: Model;// 定义一个抽象方法,用于创建模型abstract createModel(): MaybePromise<Model>;   // 定义一个异步方法,用于获取模型async getModel(): Promise<Model> {// 如果模型不存在,则创建模型if(!this.model) {this.model = await this.createModel();}// 返回模型return this.model;}// 定义一个方法,用于创建提示createPrompt(options?: {useHistory?: boolean}): MaybePromise<ChatPromptTemplate> {// 获取选项,默认使用历史记录const { useHistory = true} = options ?? {};// 创建提示模板,包含历史记录占位符和人类消息模板const prompt = ChatPromptTemplate.fromMessages([useHistory ? new MessagesPlaceholder('history') : '', HumanMessagePromptTemplate.fromTemplate("{input}")].filter(Boolean))// 返回提示模板return prompt;}// 定义一个异步方法,用于获取历史记录async getHistory(sessionId: string, appendHistoryMessages?: BaseMessage[]): Promise<InMemoryChatMessageHistory> {// 如果会话 ID 的历史记录不存在,则创建新的历史记录if(BaseModelProvider.sessionIdHistoriesMap[sessionId] === undefined) {const messageHistory = new InMemoryChatMessageHistory();// 如果存在追加的历史记录消息,则添加到历史记录中if( appendHistoryMessages && appendHistoryMessages.length > 0) {await messageHistory.addMessages(appendHistoryMessages);}// 将会话 ID 和历史记录映射保存到静态属性中BaseModelProvider.sessionIdHistoriesMap[sessionId] = messageHistory;}// 返回会话 ID 对应的历史记录return BaseModelProvider.sessionIdHistoriesMap[sessionId];}// 定义一个方法,用于创建带历史记录的 RunnablecreateRunnableWithMessageHistory<Chunk extends AIMessageChunk>(chain:Runnable<any, Chunk, RunnableConfig>,historyMessages: BaseMessage[]) {// 创建一个带历史记录的 Runnable,并设置相关参数return new RunnableWithMessageHistory({runnable: chain,// 获取历史记录的回调函数getMessageHistory: async sessionId => await this.getHistory(sessionId, historyMessages),// 输入消息的键inputMessagesKey: 'input',// 历史消息的键historyMessagesKey: 'history'});}// 定义一个异步方法,用于创建 Runnableasync createRunnable(options?: BaseModelProviderCreateRunnableOptions) {// 获取选项,默认使用历史记录const { useHistory = true, historyMessages = [], signal } = options ?? {};// 获取模型const model = await this.getModel();// 创建提示const prompt = await this.createPrompt({useHistory});// 创建链,将提示和模型连接起来const chain = prompt.pipe(signal ? model.bind({signal}): model);// 如果使用历史记录,则创建带历史记录的 Runnablereturn useHistory ? await this.createRunnableWithMessageHistory(chain, historyMessages || []) : chain;}// 定义一个异步方法,用于创建结构化输出 Runnableasync createStructuredOutputRunnable<ZSchema extends z.ZodType<any>>(options?: BaseModelProviderCreateStructuredOutputRunnableOptions<ZSchema>) {// 获取选项,默认使用历史记录const { useHistory = true, historyMessages = [], zodSchema } = options ?? {};// 获取模型const model = await this.getModel();// 创建提示const prompt = await this.createPrompt({useHistory});// 创建链,将提示和模型连接起来const chain = prompt.pipe(model);// 如果使用历史记录,则创建带历史记录的 Runnablereturn useHistory ? await this.createRunnableWithMessageHistory(chain, historyMessages || []) : chain;}}

我给出了详细的注释,所以不多以描述。

定义模型

刚刚说到,基本上市面上的大模型LangChain都支持,但是由于科学原因,国外的模型并不能很愉快的使用,这里我也经过测试,智谱的GLM是一个很不错的模型,推荐大家使用,这里以智谱的GLM模型为例子,如果大家需要更多模型,参考LangChain官方文档的描述相应配置即可。

import { BaseModelProvider } from '../base/BaseModelProvider';// 导入智谱AI的聊天模型
import { ChatZhipuAI } from '@langchain/community/chat_models/zhipuai';// 定义一个 GlmModelProvider 类,它继承自 BaseModelProvider,并指定模型类型为 ChatZhipuAI
export class GlmModelProvider extends BaseModelProvider<ChatZhipuAI> {// 异步创建模型方法async createModel() {// 设置智谱AI的API密钥const aiKey = '你的密钥';// 设置模型名称const model_name = 'glm-4';// 使用 ChatZhipuAI 类创建模型实例const model = new ChatZhipuAI({// 设置 API 密钥apiKey: aiKey,// 设置模型名称model: model_name,// 设置温度参数,用于控制模型的随机性,取值范围为 01,建议不要使用 1.0,因为有些模型不支持temperature: 0.95, // 设置最大重试次数,如果请求失败,会尝试重试最多 3 次maxRetries: 3,// 设置是否显示详细日志,true 表示显示verbose: true});// 返回创建的模型实例return model;}
}

很简单,只需要更改你的密钥就行,密钥的获取去智谱开放平台[4]获取。

Controller

在将LangChain工作流都定义好后,接下来就实现接口的细节,首先在 ai.controller.ts 写好请求入口:

// 控制器类,用于处理与 AI 相关的请求
@Controller('ai')
export class AiController {// 构造函数,注入 AI 服务constructor(private readonly aiService : AiService) {}// 处理 POST 请求,用于代码生成// 路径为 /ai/lib/chat// 接收请求体中的 message 参数,类型为字符串// 返回一个 Promise 对象,解析后为一个对象@Post("/lib/chat")async codeGenerate(@Body('message') message : string): Promise<object> {// 调用 AI 服务的 codeGenerate 方法,传入 message 参数// 返回结果存储在 result 变量中const result = await this.aiService.codeGenerate(message);// 返回一个包含代码执行结果的对象// code 字段表示状态码,0 表示成功// msg 字段表示消息,success 表示成功// data 字段包含生成代码和配置信息return {code: 0,msg: "success",data: {// jsx 字段存储生成的 JSX 代码jsx: result[0],// config 字段存储生成的配置信息config: result[1]}}}
}}   

Service

service也是比较重要的处理逻辑所在,在这里需要构建prompt,提交模型,接收响应流,处理响应流。

LangChain如何实现一个响应呢?

const buildStream = async () => {// 声明一个名为 aiStream 的变量,用于存储 AI 流,初始值为 nulllet aiStream = null;// 检查会话历史记录是否存在,如果不存在if (!isSessionHistoryExists) {// 从 sessionIdHistoriesMap 中删除与当前 sessionId 对应的历史记录delete sessionIdHistoriesMap[sessionId];// 使用 aiRunnable.stream 方法创建 AI 流,并传递 prompt 作为输入参数和 aiRunnableConfig 作为配置参数aiStream = aiRunnable.stream({input: prompt},aiRunnableConfig);} else {// 如果会话历史记录存在,则使用 aiRunnable.stream 方法创建 AI 流,并传递一个包含继续提示的字符串作为输入参数,以及 aiRunnableConfig 作为配置参数aiStream = aiRunnable.stream({input: `continue, please do not reply with any text other than the code, and do not use markdown syntax.go continue.`},aiRunnableConfig);}// 返回创建的 AI 流return aiStream;
};

这里我是保留了会话和历史记录的,当然,这一操作在此其实并没有什么作用,因为在这里不支持对话,所以也就不需要上下文的记忆,但是为了工作流的完整性,我还是保留了。

主要是使用了aiRunnable这一工具类,它来自@langchain/core/runnables,是LangChain处理响应流的核心包,可以调用、批处理、流式传输、转换和组合。文档[5]

在得到响应流的输出后,需要对响应流进行处理,在模型的输出中,它包含了index.jsxconfig.js两个文件的代码,所以我们需要对其进行解析,将代码得到

先要实现一个解析方法,采用正则解析的方式:

export const extractCodeBlocks = (str: string): string[] => {// 如果字符串为空,则返回空数组if (!str) {return [];}// 正则表达式匹配所有的代码块内容// ```开头,```结尾,中间包含任意字符,包括换行符// 捕获组 $1 匹配代码块内容const matches = str.match(/```[\s\S]*?\n([\s\S]*?)\n```/g);// 如果找到了匹配项,则返回提取的内容,否则返回空数组// 使用 map 函数对匹配到的结果进行处理,提取代码块内容并去除空格return matches ? matches.map(match => match.replace(/```[\s\S]*?\n([\s\S]*?)\n```/, '$1').trim()) : [];
};

然后对响应流进行解析和拼接:

let result = [];
const aiStream = await buildStream();
console.log(aiStream);
if(aiStream) {for await (const chunk of aiStream) {const text = GlmModelProvider.answerContentToText(chunk.content);result.push(text);}
}
const ai_stream_string = result.join('');
const code_array = extractCodeBlocks(ai_stream_string);

实际上,LangChain是支持流式输出的,aiStream里面有很多个chunck,每个chunck都是一次流式返回,但是这里我是等所有的输出结束后再统一进行处理,这里并不是主流的方法,后续版本我会对此进行改进。

构建Prompt

工作流程都搭建完了,用户信息能够接收到了,也能向模型提问了,那么如何保证模型的输出符合我们的要求呢。

大家都知道模型会有一个幻觉的问题,也就是答非所问,似有道理,但实际不行,为了解决这个问题,可以采取微调或者RAG的方法实现。

在这里,我们实现了一个类似RAG的方法,RAG是检索增强,会有一个知识库,但是目前我们还没有搭建起来这个知识库,一旦搭建好这个知识库,我们可以通过向量检索的方式实现模型的prompt,那么生成能力和准确能力将得到大大提升。

这里暂时只是构建了一个比较好的prompt,用于调整模型的角色以及提供一些必要的提示。

async buildGeneratePrompt({message}: {message: string}) {const codePrompt = `You are a low-code component development expert.Your task is to help me generate two files for a low-code development module: index.jsx and config.js.The index.jsx file should define the structure of the component using React and Ant Design, and the config.js file should specify the component's property configurations.Here’s an example of a login form component:index.jsx file:// jsxexport default ({ id, type, config, onClick }, ref) => {const { Form, Button, Input } = window.antd;const onFinish = (values) => {onClick && onClick(values);}return (<div data-id={id} data-type={type}><Form name="login"labelCol={{ span: config.props.labelCol }}wrapperCol={{ span: config.props.wrapperCol }}style={{ maxWidth: config.props.maxWidth }}onFinish={onFinish}><Form.Item label="用户名" name="username"><Input /></Form.Item><Form.Item label="密码" name="password"><Input.Password /></Form.Item><Form.Item wrapperCol={{offset: config.props.offset,span: config.props.wrapperCol}}><Button htmlType="submit" block={config.props.block} type="primary">{config.props.loginBtn}</Button></Form.Item></Form></div>);};config.js file:// config.jsexport default {attrs: [{type: 'Title',label: '基础设置',key: 'basic'},{type: 'Input',label: '登陆名称',name: ['loginBtn']},{type: 'Switch',label: '块状按钮',name: ['block']},{type: 'InputNumber',label: 'LabelCol',name: ['labelCol']},{type: 'InputNumber',label: 'WrapperCol',name: ['wrapperCol']},{type: 'InputNumber',label: 'Offset',name: ['offset']},{type: 'InputNumber',label: 'MaxWidth',name: ['maxWidth']}],config: {props: {loginBtn: '登陆',block: true,labelCol: 8,wrapperCol: 16,offset: 8,maxWidth: 700},style: {},events: [],},events: [{value: 'onClick',name: '登陆事件'}],methods: [],};Note: If you need to import hooks like useState and useEffect, you should import them using const { useState, useEffect } = window.React;. Similarly, Ant Design components should be imported in this way: const { Button, Form, DatePicker, Tag } = window.antd;.Now, based on the above structure and configuration, I need you to generate the index.jsx and config.js files for a new component. The description of this component in Chinese is #{message}. You can understand it in English and help me implement the code of the componentPlease return only the code for both files, without any additional descirption text or markdown syntax.`const prompt = codePrompt.replace("#{message}", message);return prompt;
}

到此,整个接口的实现细节就讲完了,这里放一下完整的service代码。

import { Injectable } from '@nestjs/common';
import { GlmModelProvider } from './provider/model/glm';
import { RunnableConfig } from '@langchain/core/runnables';
import { extractCodeBlocks } from './provider/utils';@Injectable()
export class AiService {async buildGeneratePrompt({message}: {message: string}) {// 复制上面的}async codeGenerate(message: string): Promise<Array<string>> {const modelProvider = new GlmModelProvider();const aiRunnableAbortController = new AbortController();const aiRunnable = await modelProvider.createRunnable({signal: aiRunnableAbortController.signal});const sessionId = `code_session_${Date.now()}`;const aiRunnableConfig: RunnableConfig = {configurable: {sessionId}}const sessionIdHistoriesMap = await GlmModelProvider.sessionIdHistoriesMap;const isSessionHistoryExists = !!sessionIdHistoriesMap[sessionId];const prompt = await this.buildGeneratePrompt({message});const buildStream = async () => {let aiStream = null;if(!isSessionHistoryExists) {delete sessionIdHistoriesMap[sessionId];aiStream = aiRunnable.stream({input: prompt},aiRunnableConfig)} else {aiStream = aiRunnable.stream({input: `continue, please do not reply with any text other than the code, and do not use markdown syntax.go continue.`},aiRunnableConfig);}return aiStream;}let result = [];const aiStream = await buildStream();if(aiStream) {for await (const chunk of aiStream) {const text = GlmModelProvider.answerContentToText(chunk.content);result.push(text);}}const ai_stream_string = result.join('');const code_array = extractCodeBlocks(ai_stream_string);return [code_array[0], code_array[1]];}
}

Postman 调试

重新启动Nest项目,测试一下接口

Snipaste_2024-08-30_14-19-32.png

前端写入代码

接口接收到返回值中有index.jsxconfig.js两个文件的代码。

此时不能盲目的向编辑器写入代码,因为编辑器中可能存有代码,覆写将导致代码丢失,所以要做好提示

Snipaste_2024-08-30_13-42-31.png

当用户取消的时候,缓存返回的代码,避免再次请求接口,但是反馈用户提示状态,用户可以再次选择写入代码:

Snipaste_2024-08-30_13-43-14.png

而当用户确认写入的时候,使用定时器将代码写入到编辑器中:

async writeCode(newCode: string): Promise<boolean> {return new Promise((resolve) => {let index = 0;const codeInterval = setInterval(() => {setCode((prev) => prev + newCode[index++]);if (index > newCode.length - 2) {clearInterval(codeInterval);setRefreshTag(refreshTag + 1);resolve(true);}}, 30);});
}, 

具体的前端代码,看文末说明。

至此,这一功能就全部完成了,这里主要是使用了LangChain这个库跟大模型打交道,实际上它也只是对官方提供的api进行封装,如果不需要使用这个库,直接使用官方的api也是可以的。

最后

MarsView低代码平台是开源的,可以去github上查看前端的代码,后端因为正在重构中,暂不开源,但是上面我所描述的,跑起一个接口是没问题的。

如何学习AI大模型?

大模型时代,火爆出圈的LLM大模型让程序员们开始重新评估自己的本领。 “AI会取代那些行业?”“谁的饭碗又将不保了?”等问题热议不断。

不如成为「掌握AI工具的技术人」,毕竟AI时代,谁先尝试,谁就能占得先机!

但是LLM相关的内容很多,现在网上的老课程老教材关于LLM又太少。所以现在小白入门就只能靠自学,学习成本和门槛很高

针对所有自学遇到困难的同学们,我帮大家系统梳理大模型学习脉络,将这份 LLM大模型资料 分享出来:包括LLM大模型书籍、640套大模型行业报告、LLM大模型学习视频、LLM大模型学习路线、开源大模型学习教程等, 😝有需要的小伙伴,可以 扫描下方二维码领取🆓↓↓↓

👉[CSDN大礼包🎁:全网最全《LLM大模型入门+进阶学习资源包》免费分享(安全链接,放心点击)]()👈

学习路线

在这里插入图片描述

第一阶段: 从大模型系统设计入手,讲解大模型的主要方法;

第二阶段: 在通过大模型提示词工程从Prompts角度入手更好发挥模型的作用;

第三阶段: 大模型平台应用开发借助阿里云PAI平台构建电商领域虚拟试衣系统;

第四阶段: 大模型知识库应用开发以LangChain框架为例,构建物流行业咨询智能问答系统;

第五阶段: 大模型微调开发借助以大健康、新零售、新媒体领域构建适合当前领域大模型;

第六阶段: 以SD多模态大模型为主,搭建了文生图小程序案例;

第七阶段: 以大模型平台应用与开发为主,通过星火大模型,文心大模型等成熟大模型构建大模型行业应用。

在这里插入图片描述

👉学会后的收获:👈

• 基于大模型全栈工程实现(前端、后端、产品经理、设计、数据分析等),通过这门课可获得不同能力;

• 能够利用大模型解决相关实际项目需求: 大数据时代,越来越多的企业和机构需要处理海量数据,利用大模型技术可以更好地处理这些数据,提高数据分析和决策的准确性。因此,掌握大模型应用开发技能,可以让程序员更好地应对实际项目需求;

• 基于大模型和企业数据AI应用开发,实现大模型理论、掌握GPU算力、硬件、LangChain开发框架和项目实战技能, 学会Fine-tuning垂直训练大模型(数据准备、数据蒸馏、大模型部署)一站式掌握;

• 能够完成时下热门大模型垂直领域模型训练能力,提高程序员的编码能力: 大模型应用开发需要掌握机器学习算法、深度学习框架等技术,这些技术的掌握可以提高程序员的编码能力和分析能力,让程序员更加熟练地编写高质量的代码。

在这里插入图片描述

1.AI大模型学习路线图
2.100套AI大模型商业化落地方案
3.100集大模型视频教程
4.200本大模型PDF书籍
5.LLM面试题合集
6.AI产品经理资源合集

👉获取方式:
😝有需要的小伙伴,可以保存图片到wx扫描二v码免费领取【保证100%免费】🆓

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/420428.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

反相放大器电路设计

1 简介 反相放大器电路输入阻抗由外部电阻决定&#xff0c;因此要求输入源阻抗降低。该电路的共模电压等于同相端电压&#xff0c;通常同相端接地&#xff0c;所以该电路共模电压为零。 2 设计目标 2.1 输入 2.2 输出 2.3 频率 2.4 电源 3 电路设计 根据设计目标&#xff0c…

Solon2 与 Spring Boot 的核心区别及对比

在Java开发领域&#xff0c;Solon2和Spring Boot是两个备受关注的框架&#xff0c;它们各有特点&#xff0c;适用于不同的开发场景。本文将深入探讨Solon2与Spring Boot之间的核心区别&#xff0c;帮助开发者更好地理解并选择适合自己的框架。 1. 架构与基础 Solon2&#xff…

QGis二次开发 —— 1、Windows10搭建Vs2017-QGis环境(附Vs2017环境效果)(附:Qt助手加入QGis接口说明文档)

OSGeo4W简介 更高级的 QGIS 用户应该使用 OSGeo4W 包。此安装程序可以并行安装多个版本的 QGIS&#xff0c;并且还可以进行更高效的更新&#xff0c;因为每个新版本仅下载和安装更改的组件。      OSGeo4W 存储库包含许多来自 OSGeo 项目的软件。包括 QGIS 和所有依赖项&a…

echarts 水平柱图 科技风

var category [{ name: "管控", value: 2500 }, { name: "集中式", value: 8000 }, { name: "纳管", value: 3000 }, { name: "纳管", value: 3000 }, { name: "纳管", value: 3000 } ]; // 类别 var total 10000; // 数据…

【银河麒麟高级服务器操作系统】虚拟机服务器执行systemctl提示timeout——分析全过程及处理建议

了解更多银河麒麟操作系统全新产品&#xff0c;请点击访问 麒麟软件产品专区&#xff1a;https://product.kylinos.cn 开发者专区&#xff1a;https://developer.kylinos.cn 文档中心&#xff1a;https://documentkylinos.cn 现象描述 产品信息 产品名称 银河麒麟高级服务…

SpringBoot3:轻松使用Jasypt实现配置文件信息加密

文章目录 前言一、概述1.1 Jasypt库简介1.2 Jasypt库的主要特点 二、开发环境三、Jasypt集成到SpringBoot33.1 引入依赖3.2 配置Jasypt3.3 加密配置文件信息3.3.1 方案一&#xff08;不推荐&#xff09;a.编写测试类生成加密后的配置文件信息b.运行c.修改原本的配置文件信息 3.…

【学术会议征稿】第八届电气、机械与计算机工程国际学术会议(ICEMCE 2024)

第八届电气、机械与计算机工程国际学术会议&#xff08;ICEMCE 2024&#xff09; 2024 8th International Conference on Electrical, Mechanical and Computer Engineering 第八届电气、机械与计算机工程国际学术会议&#xff08;ICEMCE 2024&#xff09;将于2024年10月25日…

深度学习-目标检测(二)Fast R-CNN

一&#xff1a;Fast R-CNN Fast R-CNN 是一篇由Ross Girshick 在 2015 年发表的论文&#xff0c;题为 “Fast R-CNN”。这篇论文旨在解决目标检测领域中的一些问题&#xff0c;特别是传统目标检测方法中存在的速度和准确性之间的矛盾。 论文摘要&#xff1a;本文提出了一种基于…

【Python系列】字典判断空

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

【漏洞利用】2018年-2024年HVV 6000+个漏洞 POC 合集分享

此份poc 集成了Zabbix、用友、通达、Wordpress、Thinkcmf、Weblogic、Tomcat等 下载链接: 链接: https://pan.quark.cn/s/1cd7d8607b8a

华为 HCIP-Datacom H12-821 题库 (10)

有需要题库的可以看主页置顶 V群进行学习交流 1.缺省情况下&#xff0c;BGP 对等体邻接关系的保持时间是多少秒&#xff1f; A、120 秒 B、60 秒 C、10 秒 D、180 秒 答案&#xff1a;D 解析&#xff1a; BGP 存活消息每隔 60 秒发一次&#xff0c;保持时间“180 秒” 2.缺省…

54 mysql 中各种 timeout - connect/wait/interactive/read/write_timeout

前言 在 mysql 的服务器配置中, 我们经常会使用到几个 timeout 诸如 connect_timeout, wait_timeout, interactive_timeout, read_timeout, write_timeout 等等 我们 这里来看一下 他们的具体的使用场景, 以及具体控制的相关信息 是什么 connect_timeout 这个是 客户端 和…

《DB-GPT项目》专栏总目录

❤️ 专栏名称&#xff1a;《DB-GPT项目》 &#x1f339; 内容介绍&#xff1a;项目部署、大模型替换、底层源码修改、数据分析、数据可视化、自动化等&#xff0c;适合零基础和进阶的同学。 &#x1f680; 订阅专栏&#xff1a;订阅后可阅读专栏内所有内容&#xff0c;专栏持续…

SQL 编程基础

SQL&#xff08;结构化查询语言&#xff09;广泛应用于数据库操作&#xff0c;是每个程序员都需要掌握的技能之一。这篇文章将带你从基础入门&#xff0c;了解SQL编程中的常量、变量及流程控制语句。我们将采用简单易懂的语言&#xff0c;结合实际示例&#xff0c;帮助你轻松理…

python-网页自动化(二)

获取元素属性 1. 获取属性 以百度首页的logo为例&#xff0c;获取logo相关属性 <img hidefocus"true" id"s_lg_img" class"index-logo-src" src"//www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png" width"270…

mfc140u.dll错误是什么情况?如何将mfc140u.dll丢失的解决方法详细分析

mfc140u.dll是 Microsoft Foundation Class (MFC) 库的一部分&#xff0c;通常与 Visual Studio 2015 及其后续版本相关联。如果系统中缺少 mfc140u.dll&#xff0c;可能会导致依赖该库的应用程序无法启动&#xff0c;并显示错误消息&#xff0c;如“程序无法启动&#xff0c;因…

云境天合防爆型气象站可以用在哪些场景?

型号&#xff1a;TH-FB01】防爆型气象环境监测站特别适用于那些存在易燃易爆物质或具有高安全风险的环境。以下是一些具体的应用场景&#xff1a; 石油化工行业&#xff1a; 石油化工厂区、油库、加油站等地方&#xff0c;由于存在大量的易燃易爆气体和液体&#xff0c;一旦发生…

Redis缓存常用的读写策略

缓存常用的读写策略 缓存与DB的数据不一致问题&#xff0c;大多数都是指DB的数据已经修改&#xff0c;而缓存中的数据还是旧数据的情况。 旁路缓存模式 对于读操作&#xff1a;基本上所有模式都是先尝试从缓存中读&#xff0c;没有的话再去DB读取&#xff0c;然后写到缓存中…

leetcode172. 阶乘后的零,遍历每个因数中5的个数

leetcode172. 阶乘后的零 给定一个整数 n &#xff0c;返回 n! 结果中尾随零的数量。 提示 n! n * (n - 1) * (n - 2) * … * 3 * 2 * 1 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;0 解释&#xff1a;3! 6 &#xff0c;不含尾随 0 示例 2&#xff1a; 输…

c++188深拷贝和浅拷贝

在全局区字符串 浅拷贝 拷贝指针变量的值而不是内存空间 obj2已经析构了 又进行了一次析构 深拷贝&#xff1a; #define _CRT_SECURE_NO_WARNINGS #include<iostream> using namespace std;class Name { public:Name(const char* myp){//开辟一个内存把dhfka传入int le…