引言

6月1日的广州港珠澳云峰会,我们发布了工业组态智能版(工业生产管控可视化引擎),通过将工业传统组态软件(工业组态应用开发&工业生产可视化软件)结合强大的大型语言模型能力(LLM),通过AI人工智慧助手Copilot的方式重塑应用开发新交互,释放用户创造力,突破知识边界,降低产品门槛,提高生产力等,另一方面,未来所有的产品都会基于LLM有一轮智能化的升级,对比业界组态软件WINCC、InTouch等竞品,结合大模型让产品更有竞争力。
我们用了2个月的时间完成了工业组态智能版的产品设计与技术研发,结合大模型,对原有工业组态智能版(工业生产管控可视化引擎) 做智能化升级,结合大模型让产品有了工业知识问答、工业应用生成、智能辅助绘图、智能属性修改、组态代码生成、素材生成等功能。
能效果展示如下:
01:工业知识问答:
02:工业组态应用生成
03:智能绘图:基础图形绘制
智能绘图:属性修改
04:智能脚本生成
05:智能素材生成
这篇文章想分享下:
  1. 工业软件产品与大模型结合创新的思考
  2. 工业组态智能版的技术架构与实现
  3. 基于LLM开发的一些建议
对于不同背景的同学可以选择阅读。本文涉及到的内容也比较多,并非都能介绍清楚,大家有关系的问题可以留言,我后续再进行补充。
问了方便大家阅读,也简单减少下工业组态软件(工业生产管控可视化引擎) 的背景
工业组态用于解决工业生产过程的数据呈现、过程管理、设备控制与运维的应用。系统分为编辑器和应用运行时。
通过编辑器实现一个工业组态应用有3步:
  1. 定义数据点:数据点位的数据源来源于OT数据和IT数据。OT数据通过数据采集平台,读取PLC驱动数据得到,IT数据通过组态系统定义的数据驱动(数据库、api接口、mqtt\kafuka\nats消息)获取
  2. 可视化应用开发:通过低代码引擎搭建现场画面,并绑定数据点
  3. 发布和托管应用:传统的工业组态是基于CS架构,我们的软件基于BS浏览器架构,通过WEB应用渲染第二部生产的DLS,包括画面和数据部分,从而形成完成的工业应用。
产品界面
工业组态应用案例:工艺流程、生产过程管理、设备运维、andon应用、仓储库存、机运管理、能耗管理、数字孪生、生产大屏等组态应用。

工业组态软件智能版如何与大模型结合

大模型是统一模态(跨模态、视觉、语言等模态)和任务(如图片生成、视觉定位、图片描述、图片分类、文本生成等),通过大模型我们可以用自然语言快速获取信息,加工文本,另外也降低了AI技术使用门槛。
在工业组态场景中,借助大模型的能力,实现工业组态智能化升级,主要考虑这4个方面:
  1. 使用LLM,突破工业信息边界,给用户有更专业的工业信息与知识的补充。
  2. 使用LLM重构传统工业组态应用基于搭建的开发过程
  3. 使用LLM降低专业工业软件对使用者的基本要求,降低使用者门槛
  4. 使用LLM的语言模型能力,降低natural language processing研发门槛和成本,改善产品交互体验

使用LLM,突破工业信息边界,给用户有更专业的工业信息与知识的补充

大模型最先想到的场景是Chat。在工业场景可以用于获取通用的工业生产、运维、安全、制造等信息,也可以通过私有数据做到更专业、更私域的知识获取。
通用的工业知识:工业知识,大模型在训练初期收录了大量干净、人工标注的数据,这类数据也包含了非常多工业领域的通用知识,如关于工业生产、运维、能耗相关的通用问题,这类知识对工业从业人员仍然非常有用处,我接触的很多工厂与制造从业人员对通用知识也并非都知道的,工业行业跨度很大,从业人员只了解自己工厂生产的产品、设备、过程相关知识。同行业其他工厂的知识,跨行业的通用知识对他们帮助也很大。
专业经验知识:针对特定领域的知识,更专业的知识可能存在于某些工业手册,会议纪要,操作指南,设备运维手册,甚至只存在工厂个别专家的脑袋中。如果这类知识可以通过大模型进行记录,并且通过自然语言进行获取,对工业帮助特别特别大。接触到一个制造企业工厂,是我们的客户,工厂数字化系统是使用我们的工业产品落地的,在与设备运维负责人沟通交流中,拿到了该工厂近五年所有设备故障复盘总结日志,包括问题、原因、解决方案的文本与图片记录。通过专属大模型,建立企业专属工业知识库,通过自然语言进行交互,对比传统方式有很多好处:
  1. 专家永远24小时在线,解决任何问题。一线工人在设备遇到故障时候,可以快速找到解决故障的问题和方法。
  2. 没有语言障碍:大模型是支持多语言的,所以无论什么语言的工业知识,都可以作为沉淀,也都可以交流。输入的说明书是英文的,这在工业设备手册场景是很常见的,但运维人员可以通过中文对话,获得需要的中文信息。
  3. 自定义提问的姿势。用使用者的视角,按照个性化的姿势进行提问,根据回答内容不断追问,直到完全理解为止
  4. 大模型没有容量限制,而专家的脑袋是有限制的。
  5. 信息获取效率高。大量的手册、知识、经验中找到自己需求的信息是很费时的操作,但使用LLM,速度总是很快很快。

使用LLM重构传统工业组态应用基于搭建的开发过程

工业组态类应用开发有2个阶段,阶段1:在1985年之前,所有的组态应用都是定制化开发的,应用开发成本高,效率低。阶段2:工业场景中 生产过程、状态监控、设备控制、安全告警、这类应用相似度很高,因此1985年之后出现了工业类似低代码的软件系统,叫组态,英文名configuration,意思是通过配置的方式,将状态点和控制点通过工业PLC和各种取得采集,与画面UI绑定。
wincc
基于LLM,工业组态应用开发可以做到免开发与配置,通过用户诉求直接生成应用,而组态编辑器只作为做应用微调、修正、个性化修改的工具。这个观点其实对任何低代码产品都适用。低代码产品往往都会面临一个问题“用户是谁”,服务客户、还是客户的员工、还是软件服务商?不同用户有着完全不同的诉求和商业策略。客户需要的是能用的应用,而不是生产力工具。所以低代码产品最终演变成一个工具产品,客户会算账到底能不能省钱,但这笔账是没法算清的,原因是系统的灵活性到底该值多少钱是算不清的。

所以如果能基于LLM做到完全免代码开发,用户提出需求,生产应用,在提出修改建议,再修正应用,基于LLM把应用开发系统变成一个应用生成系统,编辑器成为了一个修改和优化的角色,这才是接下来的低代码、无代码平台的发展趋势。
我们在产品中目前仅仅实现了一小步部分,用场景模板做关联匹配与逐步渲染,并提供自然语言指令做图形绘制、属性修改等功能,但还未实现全量的元件、组件、素材属性修改与绘制。已完成和未完成的功能见下图。

使用LLM降低工业用户作为专业工业软件使用者的门槛

工业软件的使用者为工厂的工人、工业自动化专业的工程师、工业工程管理与运维人员。在工业组态场景中,代码编写的门槛就很高,大部分设备工程师并不会写代码,哪怕是最简单的代码,都有语言、环境、语法等一堆问题需要解决。
举个例子: 工业生成现场采集到的数据往往是最原始的状态,如工位安全生产状态位数据是0,1,2,代表着工位生产状态是 正常、告警、故障三种。在组态应用中需要用这三种颜色来表示生产状况的视觉卡片,如下图所示。
这时候就需要用户具备编写脚本的能力,通过代码把数值转换成对应的颜色。这个过程难倒了大部分的工业从业者,因此企业不得不寻求软件服务商参与,那么数字化的成本和效率都有几十倍的损耗。假如可以用自然语言就能完成代码编写,那极大程度降低了专业软件的使用门槛。
我们验证了在工业组态场景经常需要编写脚本的类型(状态位和UI元素转换,数值计算,单位处理等),通过大模型都能很好的解决。

基于LLM的语言模型能力,降低natural language processing研发门槛和成本,改善产品交互体验。

第四点是所有产品与技术都可以通用的,使用自然语言指令,实现产品原来就有的功能,改善产品交互体验。
比如工业组态智能版中,原本需要通过鼠标一个个绘制的图形,现在只需要输入语言指令:“帮我画10个灰色圆角的方形,长宽200*200”就可以实现, 原本需要再右侧一大堆属性配置选项中找到背景色,在通过颜色选择器完成的背景色配置,现在只需要输入语言指令:“帮我把背景色修改成红色”。
而大模型提供的能量就是极大简化了natural language processing 应用研发门槛和成本.,如果不依赖大模型,一般会有2种方式 1)自己训练模型,需要准备大量的指令和训练数据,做监督学习,模型调优,模型部署与剪裁 2)使用成熟的NLP框架,如Spacy,需要详细的词法分析的工程化工作,且效果并不好,出错率也高,并且需要针对每一条语音指令做规则处理。使用Spacy实现语言指令大致过程如下 :
import spacynlp = spacy.load("zh_core_web_lg")text = "把背景颜色改为红色"doc = nlp(text)
这段代码中doc是一个语言文档,包含一个数字,里面给出这句话每个词及词的词性,比如动词、介词宾语、主语、助动词、量词、副词...等。其中包含一个root节点作为语句的起始。无论使用哪一种,成本和门槛都很高。
基于大模型实现语言指令就很简单,你所要做的就是会使用Prompt就够了,研究下Prompt工程,合理的设定问题和问题反馈的格式就可以做到。我实现这段功能的prompt也比较简单,如下所示。prompt定义了问题,输入、返回格式、小样本案例、异常处理示例。通过这个Prompt,输入 " 帮我把字体大小改成12" ,就可以得{ "fontSize":"12"},接下来就简单了,在工业组态应用程序中修改对应ComponentID的Props值就完成了功能。这部分在技术实现中我在详细去解释。
def props_prompt(input): example = ''' example: 输入示例1:帮我把字体大小改成12 输出示例1:{ "fontSize":"12"} 输入示例2:帮我把名称改成组件001,边框颜色改成红色 输出示例2:{ "name":"组件001" , "borderC":"red"} 输入示例3:请将透明度修改为0.5,位置修改为x:100,y:200 输出示例3:{ "opacity":0.5 , "x":100, "y":200} 输入示例4:背景颜色改为黄色 输出示例4:{"bgColor":"yellow"} ''' ret = f''' 一个组件有如下属性。key表示属性英文名称,value表示属性中文名称,如果一个key对应多个多个中文名称,用|分割 "name": "组件名称", "content": "文字内容|文字|内容", "w": "宽度 长度", "h": "高度", "x": "x位置", "y": "y位置", "z": "z位置", "borderW":"边框大小 | 边框粗细", "borderW":"边框", "visible": "显示 true|隐藏 false", "opacity": "透明度", "borderC": "边框颜色", "bgColor": "背景颜色", "color": "文字颜色", "fontSize": "字体大小 | 文字大小", "textAlign": "文字水平对齐方式", //左对齐left 居中对齐middle 右对齐right "verticalAlign": "文字垂直对齐方式", //上对齐top 居中对齐middle 下对齐bottom 接下来我会输入一句话,用来修改组件对应属性的值 返回结果格式为json,对应内容是 {{ "英文key": "修改的数值" }} 请不要返回除了json以外的其他任何内容 用户输入内容为:{input} 给你一些参考示例: {example} 异常处理: 如果用户想要修改的属性不再列表中,请返回: {{ "error": "支持修改的属性包括:文字内容,长度、宽度、边框大小、边框颜色、透明的、隐藏、显示、文字颜色、文字内容、字体大小" }} ''' return ret
另外一个案例就是图片生成和图片搜索,我们提供了1000多个工业常用领域的素材库,通过自然语言区实现图片搜索,会比搜索框更高效。
组态智能版中的自然语言素材生成是搜索 + text2img的能力。使用了阿里云的Imageenhan服务。但目前市面上的text2img服务,无论是Imageenhan、stable diffusion、midjourney 对与工业场景的素材生成效果差强人意,所以组态产品还是优先在工业素材库中进行2D3D素材匹配。

小结:目前基于LLM已经做到的功能,和接下来可以继续探索部分

黄色:已经实现
灰色:未来规划

工业组态智能版 + 大模型的技术实现

整体技术架构
分成三层,从上至下分别是客户端层、代理层、模型层
  • Clinet Layer(客户端层):组态智能化客户端部分,包括交互UI、支持的技能注册、服务端LLM Agent请求,对应LLM响应数据做工业组态功能点的执行。
  • Agent Layer(代理层): LLM服务端代理,Web 请求与响应处理,包括数据流推送,还有Prompt Builder构造器。架构设计中,Prompt Builder也可以放Clinet层,这样研发效率会更高一些,但因为组态功能对LLM返回值及返回格式都有确定的要求,随意修改Prompt会影响系统稳定性。另一方面,LLM最终输入的Prompt,是由Agent Layer的Prompt Template + 用户在Clinet层输入的Input组合而成
  • LLM Layer(模型层):LLM有很多,我们选择了双模型,产品化模型用阿里云千问大模型,测试和预研会使用OpenAI,毕竟是行业标杆,用OpenAI做Prompt响应控制,效果验证,能了解产品与LLM结合的上限做到什么程度。另一方面,OpenAI能力开放程度高,预研性的功能可以先基于OpenAI研发,等千问模型有了,可以做代替。
此架构仅只智能版部分,不包含工业组态本身的技术架构,组态本身和智能版通过MaliangEngine进行交互

Client Layer:核心智能交互实现

Client Layer主要的组成部分包括
  • CopilotUI组件:实现应用生成、工业知识对话、工业组态脚本生成、智能绘图、智能属性修改、智能搜图/生图的自然语言指令交互入口。对应架构图中的Copliot UI
  • LLM技能:包括对应的技能注册到Copilot Hub,技能调研服务API地址,调用技能后的后续action执行
  • LLM技能action执行:根据LLM返回的数据,做对应的功能处理与执行
另外这一层会依赖于Maliang Engine
  • Maliang Engine:工业组态搭建器引擎,核心组态技术能力都在这个模块中,通过window api提供给LLM Clinet Layer层调用。
CopilotUI组件:
这部分是用户最直观能感受到的UI交互层, 实现应用生成、工业知识对话、工业组态脚本生成、智能绘图、智能属性修改、智能搜图/生图的自然语言指令交互入口。对应架构图中的Copliot UI。比如LLM不同技能的返回消息展示组件,代码格式使用MSGUI-MD组件、文本内容用MSGUI-Text,应用缩略图使用MSGUI-APP,技能切换使用TrggerSelector组件等。
CopilotUI基于阿里小蜜提供的开源库ChatUI实现,这个Lib封装了对话框的交互事件和交互UI,极大提高了开发效率,基于ChatUI,自定义了text,code,app等几种消息体和对应的渲染函数。
交互入口代码,初始化Chat通过renderMessageContent、renderNavbar、Composer这三个函数去实现自定义的CopilotUI重构。
  • renderNavbar:导航栏渲染
  • messages: 所有发送和接收的消息响应
  • renderMessageContent:消息渲染函数,可以根据消息的类型进行自定义UI渲染
  • 消息格式:{ type, position,content}
  • onSend:输入框发送消息事件
  • Composer:自定义输入框
import Chat, { Bubble, useMessages } from '@chatui/core';<Chat renderNavbar={renderNavbar} messages={messages} renderMessageContent={renderMessageContent} onSend={() => {}} Composer={renderComposer}/>;
对话交互,使用position,left | right来区分输入和返回消息类型,对于可以通过自定义message的type,来做不同消息类型的展示
// 如果在右侧,是用户输入的内容,使用 输入文本 + 技能类型做展示const renderMessageContent = (msg) => { if (position === 'right') { return ( <ReqBubble msg={msg} /> ); } else if (position === 'left') { switch (type) { case 'code': return <ResCodeBubble msg={msg} /> case 'script': return <ResScriptBubble msg={msg} /> case 'app': return <ResAppBubble msg={msg} /> } }}
LLM技能HUB:
这是一个设计模式,用于解耦大模型相关增强的智能功能。在产品有非常多的地方可以用到LLM做智能化升级,如果每个结合LLM的功能都在各自模块中进行处理,会破坏原有代码工程的代码结构,不利于维护。所以通过CopliotHub模式,通过技能注册,注册信息都可以抽象为:后端服务地址、请求参数设置、LLM返回数据后的处理函数。通过Copliot HuB去做技能注册、LLM服务请求、技能执行的拆解,这部分也不会影响到底层的Maliang Engine(工业组态代码)
注册代码示意:
const voidfn = ()=>{}copilothub.register(Widget.chat,voidfn,{ stream:ture}}copilothub.register(Widget.scripts,voidfn,{ stream:ture}}}copilothub.register(Widget.svg,drawBySVG,{ stream:ture}}}copilothub.register(Widget.appgen,createAPPByDSL,{ stream:ture}}}copilothub.register(Widget.props,ModifyPropsByID,{ stream:ture}}}copilothub.register(Widget.images,DrawMaterial,{ stream:ture}}}
通过注册技能,根据技能标识,copilothub会先请求llm serve,并处理stream返回,再通过执行第二个参数action,将llm的返回值导入,组态clinet会根据响应值,执行对应的动作。在执行动作时,所有基础方法封装在Maliang Engine中并通过window对象进行交互。
LLM技能执行动作
当收到模型的响应后,会进入注册的后项执行方法,如代码中drawBySVG智能绘图执行器、createAPPByDSL智能应用创建执行器、ModifyPropsByID智能属性修改执行器、DrawMaterial智能素材生成执行器。接下来介绍下具体功能实现链路。
ModifyPropsByID-处理智能属性修改:
实现过程:
1)CopliotHub注册技能和技能执行方法 2)构造Prompt 3)根据LLM返回值,执行绘图功能
注册技能和执行方法
copilothub.register(Widget.props,ModifyPropsByID,{ stream:ture}}}
Prompt构造:Prompt分为两个部分,input为用户实际输入的内容,PromptTemplate是后台构造的Prompt模板。实际传入LLM的内容为两部分的组合。
在智能属性修改功能中,PromptTemplate是这样写的:
def props_prompt(input,ctx): example = ''' example: 输入示例1:帮我把字体大小改成12 输出示例1:{ "fontSize":"12"} 输入示例2:帮我把名称改成组件001,边框颜色改成红色 输出示例2:{ "name":"组件001" , "borderC":"red"} 输入示例3:请将透明度修改为0.5,位置修改为x:100,y:200 输出示例3:{ "opacity":0.5 , "x":100, "y":200} 输入示例4:背景颜色改为黄色 输出示例4:{"bgColor":"yellow"}''' exception = ''' 异常处理: 如果用户想要修改的属性不再列表中,请返回: {{ "error": "支持修改的属性包括:文字内容,长度、宽度、边框大小、边框颜色、透明的、隐藏、显示、文字颜色、文字内容、字体大小" }}''' ret = f''' 一个组件有如下属性。key表示属性英文名称,value表示属性中文名称,如果一个key对应多个多个中文名称,用|分割"name": "组件名称","content": "文字内容|文字|内容","w": "宽度 长度","h": "高度","x": "x位置","y": "y位置","z": "z位置","borderW":"边框大小 | 边框粗细","borderW":"边框","visible": "显示 true|隐藏 false","opacity": "透明度","borderC": "边框颜色","bgColor": "背景颜色","color": "文字颜色","fontSize": "字体大小 | 文字大小","textAlign": "文字水平对齐方式", //左对齐left 居中对齐middle 右对齐right"verticalAlign": "文字垂直对齐方式", //上对齐top 居中对齐middle 下对齐bottom 此外,系统包含一些上线文信息如下:{ctx} 接下来我会输入一句话,用来修改组件对应属性的值 返回结果格式为json,对应内容是 {{ "英文key": "修改的数值" }} 请不要返回除了json以外的其他任何内容 用户输入内容为:{input} 给你一些参考示例: {example} {exception}'''return ret{ w:100}
这部分的逻辑写在Agent层的,所以是python代码,这里现在做说明,便于串联整体功能实现。
Prompt说明:
  1. 用途的设置,明确告诉LLM需要它做什么,上下文是什么,处理内容的格式是什么,对返回格式的要求。
  2. 把用户的输入 input 作为整体Prompt的一部分,告诉LLM用户的需求是什么
  3. 小样本训练,few shot,给出一些具体的example帮助,能帮助LLM更精准的返回内容
  4. 给出exception,对异常情况进行描述,设置异常情况,控制异常下数据返回
  5. ctx信息可以包含些私有信息,比如用户当前设置的画布大小是多少,可以ctx中加入 画布大小为1920 * 1024的信息,这样的好处是,当用户输入为 “移动屏幕中间” 时,模型也能有正确的响应。
Prompt模板对应的请求的返回值示例:

返回的原始数据位raw部分,通常情况下是包含json格式的一个string, 但LLM并不能100%保证格式正确。由于返回值是stream,因此我们需要根据流推送状态是否完成,拿完整stream的返回值,并做正则校验,提取json string,在转换为json对象。 
const response = await fetch(url, {method: 'POST',body: JSON.stringify(param),headers: {'Content-Type': 'application/json', },});const encode = new TextDecoder('utf-8');const reader = response.body.getReader();while (true) {const { done, value } = await reader.read();if (done) {break; }const text = encode.decode(value);//多通道LLM有不同的处理方式if (llm === LLM.OpenAI) { raw += text; } elseif (llm === LLM.TongYi) { raw = text; }}console.log('raw:', raw);
除了做json转换为,还需要做容错。这是一个不断调整Prompt,观察返回结果的过程,如果返回不符合预期,调整用词、语序、明确指示、提高example样本等,都有很大帮助,推荐大家去看下吴恩达的课程《prompt engineering》。
我在实际开发阶段,通过几个有效方式,提高了智能属性修改的成功率。
1:LLM调用时,把temperature参数尽量设低,这个参数对于LLM的意义是返回结果的多样性,而我们在做绘图和智能属性修改时,希望得到确定性的返回值,所以要设低。实际调试结果来看,openai设置为0比较稳定,千问模型设置为0.1,设置为0会出现奇怪的异常。
2:LLM经常出现的非预期错误,做优化、修正、容错,特别是千问模型,对返回值不稳定,这种方式非常有效
  • 边框大小修改为20 -> LLM返回的结果有时为 {borderW:30},有时是 {borderW:'30px'}
  • 背景颜色设置为红色 -> LLM返回结果可能为  {bgColor:red} 、 {backgroundColor:red} 、  {backgroundColor:红色}
  • 把文字内容改成 hello world,  -> LLM返回结果可能为  {内容:hello world}
  • 把透明度改成90% ,  -> LLM返回结果可能为  {opacity:90%} ,  {opacity:90}
3:过滤不需要处理的属性
4:清楚和理解大模型本身的差异,如qwen和openai gpt的差异,如果后端LLM模型不同,真的LLM模型关键词的敏感程度,去微调Prompt模板,实现更好的效果
通过CopilotHub处理完结构LLM数据响应后,就进入到具体处理属性修改的回调函数,回调函数的执行逻辑,都写在代码注释中了
//hub注册的属性修改对应的回调函数copilothub.register(Widget.props,ModifyPropsByID,{ stream:ture}}}functionModifyPropsByID(props) {try {//MaliangEngine 获取当前界面选择的组件idconst id = MaliangEngine.firstSelectedObject.id;// availableProps是通过culAvailableProps方法做容错// 里面做的事情包括 1:过滤不需要处理的prop, 2:处理模型返回json对象的容错 3:对比node props对应的数值类型,做转换const availableProps = culAvailableProps(jsonObject, id);//MaliangEngine 执行对应属性的修改window.MaliangEngine.editSingleNodeProps(props, id); } catch (e) {console.log('modifyPrposByComponentsId error:', e, 'at:', id, 'props:', props);thrownewError(e); }}
DrawmatchBySVG 自然语言智能绘图实现:
智能绘图的实现过程和智能属性修改类似。 过程:1)CopliotHub注册技能和技能执行方法 2)构造Prompt 3)根据LLM返回值,执行绘图功能
copilothub.register(Widget.svg,drawBySVG,{ stream:ture}}}
2)构造Prompt 
def svg_prompt(input): ret = f""" As a frontend engineer, you are required to translate Chinese requests provided by users into SVG code and return the result. You must clearly specify the type of shape and ensure its compatibility with the SVG versionorformatwhen drawing one or more specified shapes. The defaultsizeof the shape should be 200 * 200anddefault color is gray. Responses should be as concise as possible, containing only one SVG code withoutany instructions, explanations, or comments. The generated code should have only one root SVG node,and shape in it, notuse the Style property anduse the Viewbox property to determine the contentof the root element. Here isuser requests --- {input} ---""" return ret
图形绘制英文使用的是代码生成模型,实际返回结果会比中文效果更好。
这里对于图形绘制,有很多种方法实现,我用了SVG模式,让LLM根据用户输入返回SVG代码
3)根据LLM返回的SVG,执行绘图功能。这里代码就不写了,比较琐碎,drawBySVG中处理SVG各种形状到工业组态协议的转换过程。
工业知识技能和工业脚本生产实现相对简单,不需要后项技能执行器 。逻辑都在Prompt的构造上, 大家看看Prompt就明白了
LLM工业知识 Prompt Template
defchat_prompt(input): ret = f'你是一个工业知识小助手,根据用户的输入,回答工业领域的问题,用专业的语气回答用户的问题。回答请使用中文, 用户的在---符号中 --- {input} --- 'return ret
LLM脚本生成 Prompt Template
defscript_prompt(input): ret = f""" You are a JavaScript engineer , only return code markdown format, only one function, function name call handle(data) , data表示输入值. 用户的代码需求是: --- {input} --- """return ret
最后一个是应用生成。这里我尝试首先尝试用LLM实现匹配,Prompt如下,list数据是通过应用平台部分模板数据构造而成,这里是举个例子
app_recommend_propmt = ''' 你是一个推荐系统,我有一些应用模板,请根据输入,给我返回最匹配的应用模板,仅返回数据,不需要有多余信息 模板列表,list = [ {{'设备监控大屏': '工控、设备监控、大屏、展示'}}, {{'成品仓库': '仓储、成品仓库、实时监测,保障成品的安全,避免盗窃、损毁'}}, {{'汽车生产mini产线': 'mini产线,实时生成数据、状态查看'}}, {{'原料仓库': '仓储管控,原材料库存'}}, {{'磨装车间': '轴承、磨装车间、生产态势监控'}}, {{'热处理车间': '热处理、生产情况的监控'}}, {{'态势总览': '厂区生产态势、能源、监控'}}, {{'煤粉工艺流程图': '煤粉制备、煤矿、工艺、生产流程,生产状态'}}, {{'城市管网处理平台': '城市管理、城市管网、管道、监控'}}, {{'污水处理': '污水处理状态监控'}}, {{'发电流程': '水泥工厂中电力管控流程图'}}, {{'PLC远程控制2': '远程对PLC控制'}}, {{'电力接线图': '电路元件、连接、电气关系'}}, {{'组态管道示例': '网、管、场景'}}, {{'天然气态势监控': '厂区用气,能耗管理'}}, {{'能源可视化': '能源消耗、能耗管理、节能、绿色'}}, {{'重点设备对比': '设备、监控'}}, {{'设备看板': '设备、看板'}}, {{'厂区用气监控': '气体、能源消耗、能耗管理、节能、绿色'}}, {{'厂区耗电监控': '电能、能源消耗、能耗管理、节能、绿色'}}, {{'厂区耗水监控': '水耗、用水、能源消耗、能耗管理、节能、绿色'}}, {{'PLC远程操作': 'PLC、远程、控制、反控'}}, {{'产线andon屏': '生产线的状态、故障、产线稳定'}}, {{'区域设备监控': '工位、产线、生成监控'}}, {{'总装车间-四合一': '汽车行业、总装车间、生成'}}, {{'总装车间-三合一': '汽车行业、总装车间、生成'}}, {{'车间能源消耗': '工厂、车间、能源、消耗'}}, {{'工控屏': '工业控制、数据展示'}}, {{'焊装车间-工位图': '汽车行业、焊装车间、工位状况'}}, {{'焊装车间-1024': '汽车行业、焊装车间、工位状况'}}, {{'焊装车间-1280': '汽车行业、焊装车间、工位状况'}}, {{'焊装车间纵览': '汽车行业、焊装车间、工位状况'}}, {{'立体车库-亮色版': '汽车行业、立体车库'}}, {{'立体车库-暗色版':'汽车行业、立体车库'}}, ..... 更多模板从数据库中获取 ] list中元素格式{'名称':'关键词1'}} input为用户输入的模板要求, input = {input}, 请从list中选出1-4个最匹配的模板作为返回 返回的格式为名称组成的数组 输入示例:给一个立体车库的模板 输出示例:['立体车库-亮色版','立体车库-暗色版','成品仓库']''';
如果不通过LLM实现,这个逻辑通过代码实现,可以采用高低位的算法匹配和抽样获取,但缺点是,LLM可以提取和获得相近的意思,而匹配算法不能。比如输入节能,LLM可以匹配到能耗、绿色等关键词,而算法方法就无法匹配。示例代码如下:
// 智能应用推荐exportfunctionapp_recommend_base(appList,input) {const source = Array(appList.length).fill(0);for (const ci of input) {for (let i = 0; i < appList.length; i++) {const app = appList[i];const str = JSON.stringify(app).replace('name', '').replace('key', '').replace("'", '');const strarr = str.split('');for (const co of strarr) {if (ci === co) source[i]++; } } }const high = [];const low = []; appList.forEach((item, i) => {const s = source[i]; item.s = source[i];if (s > 0) { high.push(item); } else { low.push(item); } }); high.sort((a, b) => b.s - a.s);let ret = high.slice(0, 4);const lack = Math.max(0, 4 - high.length);// 抽签for (let i = 0; i < lack; i++) {const index = Math.floor(Math.random() * low.length); ret.push(low[index]);delete low[index]; } ret = ret.map((item) => item?.name);return ret;}

LLM Agent :模型服务端,实现模型代理和Prompt Engineering

这一层是工业组态智能版对应LLM能力的服务端,改服务端和原先工业组态的服务端分成2个后端应用,因为工业组态会有边缘无互联网独立部署的情况,智能版也并非产品的默认标配,所以拆成新的应用。
这层主要处理LLM的通道设置,代理LLM的请求和响应。实现一个web api serve
技术选型:python、 django、对应模型的后端SDK,如(Openai-python-sdk、dashscope-python-sdk)
PromptBuilder在Agent Layer层的实现可以保证系统稳定性稳定。上一节已经详细介绍了具体Prompt的内容,所以这里不再重复。
Agent建设有几处值得分享的建议:
1:为了提高开发效率,在开发阶段可以将Prompt构造放在客户端,serve提供如qianw/ turbo/ davinci/作为通用接口。但在预发和线上阶段,Prompt对LLM返回值影响巨大,不能随意修改,这部分代码放在serve段会更稳定。
urlpatterns = [ path('chat/', api.chat, name='chat'), path('image/', api.image, name='image'), path('svg/', api.svg, name='svg'), path('script/', api.script, name='script'), path('props/', api.props, name='props'), path('qianw/', api.qianw, name='qianw'), path('turbo/', api.turbo, name='turbo'), path('davinci/', api.davinci, name='davinci'),]
2:  temperature参数决定LLM返回值的多样性,提高稳定可以导致更多的随机性。对基于事实的回答或者想控制返回值的稳定性,需要把temperature设低。比如脚本生成、绘图、智能属性修改Prompt对应的温度,就需要竟可能降低。知识问答,可以把temperature设高些增加互动性。目前qwen模型不支持这个参数。通义对随机性的控制可以使用top_p,这个参数和temperature的原理并不完全相同,但作用类似。top_p:Nucleus Sampling采样算法,用大白话讲就是采取概率分布较高的集合中随机返回结果。
3:content_type='text/event-stream' 需要设置,匹配安全和风控产品重写fetch函数造成异常
4:LLM调用,通过stream返回数据返回的实践是比较通用的逻辑,可以借鉴。这里已django、openai阿里通义qianwen模型举例
defresp(data,message = ''): resp = {'code': 200if message == ''else500,'data': data,'message':'succeed'if message == ''else message,'success': Trueif message == ''elseFalse,'description': '' }return respdefstream_response(response):for chunk in response: cho = chunk['choices'][0] print(f'chunk[\'choices\']: {cho}')# chunk_message = chunk['choices'][0]['delta'] # extract the message chunk_message = chunk['choices'][0] # extract the message text = chunk['choices'][0]['text']yield textdefchat(request):if request.method == "POST":# 获取 POST 请求中的数据 req = json.loads(request.body) input = req["prompt"]else :return JsonResponse(resp('','prompt undefined')) response = openai.Completion.create( model="text-davinci-003", prompt= prompt_builder.chat_prompt(input), max_tokens=512, temperature=0.8, timeout=60, stream=True, )return StreamingHttpResponse(stream_response(response), content_type='text/event-stream', status=200)defchat(request):if request.method == "POST":# 获取 POST 请求中的数据 req = json.loads(request.body) input = req["prompt"]else :return JsonResponse(resp('','prompt undefined')) response = Generation.call( model='qwen-v1', prompt= prompt_builder.chat_prompt(input), stream=True, top_p=0.8)return StreamingHttpResponse(stream_response(response), content_type='text/event-stream', status=200)
5:服务部署的网络选择:阿里通义qwen模型网络情况好,接口稳定,速度快,国内任何云服务器的region都可以正常访问。开发日常环境可以使用openai的gpt模型做对比测试,openai模型使用稍微复杂些,网上有很多资料,这里我就不做介绍了。
6 在开发初期,可以使用Google Colab服务做开发测试。与Jupyter的使用方式一样,优点是环境配置非常简单、方便。

LLM Layer:模型层,模型的选择和经验

这一层就是使用成熟的大模型,作为智能化应用最重要的部件,可以比喻成心脏,好的LLM事半功倍。
国外的LLM
  • OpenAI GPT3、3.5、4
  • Meta LLaMa
  • Google PaLM、LaMDA
国内的LLM
  • 阿里:通义大模型 内测
  • 百度:文言一心
  • 腾讯:HunYuan 未发布
网上模型资料很多,工业组态智能版接入2个模型,产品化LLM使用阿里通义模型,开发态使用通义模型和openai模型做对比、评估、测试。
通义模型:通过从DashScope提供的MaaS获取,注册API,申请权限:https://dashscope.aliyun.com/
DashScope
OpenAI: 使用davinci-003模型 + gpt-3.5-turbo模型。其中davinci-003是GPT3.0模型,我们的场景中单轮对话较多,并且考虑fine-turing场景,3.0模型是唯一支持的模型,且速度快,费用低。

总结

大模型、ChatGPT是作者每天都在用的产品,也经常会想结合LLM做什么能让生活和工作带来改变。工业组态智能版发布后,也有许多工业企业申请试用,和我们沟通工业应用场景。
在过去的两个月,作者在工业组态场景花费了一点时间,这周用了两天的时间把产品与技术的一点心得体会写出来,希望能帮到正在使用计划和正在使用大模型的同学。
继续阅读
阅读原文