前言
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
是工具类,对模型的输出做解析等操作
controller
和service
是大家很熟悉的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,// 设置温度参数,用于控制模型的随机性,取值范围为 0 到 1,建议不要使用 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.jsx
和config.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.jsx
和config.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%免费】🆓