讲的是Langflow这个可视化工具怎么拖拽搭LLM应用。你会学到它的组件库有哪些(LLM、向量库、工具、记忆等)、怎么连线编排流程、怎么调试和测试、怎么导出代码和部署、适合什么场景快速做原型、和LangChain代码怎么互转。适合不擅长写代码的业务同学、想快速验证LLM应用想法的产品经理使用。帮你从零到一建立对这个领域的系统理解。
需要学习Langflow可视化编排,了解AI开发工具才能设计技术课程。本篇涵盖LangChain/RAG/Agent、API集成、向量数据库,帮助你构建AI应用系统能力。
目录
可视化演示
展示测试驱动开发的Red-Green-Refactor循环过程,包含测试执行状态追踪和代码覆盖率分析,动态演示TDD工作流的完整迭代周期。
本节配有交互动效(进入视口后自动加载,约 3KB)。
TDD Red-Green-Refactor循环 Red:编写失败测试,明确需求规格和预期行为 Green:编写最小代码使测试通过,消除失败 Refactor:重构代码结构,保持测试绿色通过 Red → Green → Refactor → Repeat
Langflow可视化编排总览与工具定位
Langflow是LangChain生态中的可视化编排工具,它的核心定位是把LangChain的组件体系以拖拽节点的方式呈现出来,让用户通过可视化连线定义数据流,快速构建LLM应用流程。说直白一点,Langflow就是LangChain的图形化操作界面——你不用写代码就能搭建出完整的LangChain应用流程,搭好之后还能一键导出成Python代码继续开发。这个定位非常关键:它不是要替代代码开发,而是要做可视化设计到代码开发之间的桥梁。
Langflow和LangChain的深度集成是它最大的特点。Langflow里的每一个组件基本都对应LangChain里的一个类——比如ChatOpenAI组件对应langchain.chat_models里的ChatOpenAI类,PromptTemplate组件对应langchain.prompts里的PromptTemplate类,ConversationalRetrievalChain对应langchain.chains里的同名类。这种一一对应的关系意味着你在Langflow里学到的组件知识,到了写代码的时候可以直接迁移过去,概念完全一致。版本上Langflow也会紧跟LangChain的更新,保持生态同步。
可视化编排的核心价值可以从两个角度来看。对非技术用户来说,它降低了LLM应用的构建门槛——不需要懂Python、不需要懂LangChain,拖拽几个节点连连线就能搭出一个RAG问答应用。对技术团队来说,它加速了原型验证——工程师可以用可视化方式快速尝试不同的组件组合和数据流设计,验证方案可行性之后再写代码,比上来就写代码迭代快得多。很多人误以为可视化工具只适合非技术人员,其实资深工程师用它做架构验证效率反而更高。
在LLM开发工作流中,Langflow扮演的是设计到代码的桥梁角色。完整的工作流大概是这样的:第一步,产品经理或工程师用Langflow快速搭建原型,验证技术方案和用户体验;第二步,在Playground里测试各种场景,调整组件参数和流程结构;第三步,方案验证通过后,一键导出LangChain Python代码;第四步,开发团队在导出代码的基础上添加生产级功能——错误处理、日志监控、用户认证、性能优化等等,最后部署上线。Langflow不是终点,它是让你更快到达终点的工具。
组件库体系与节点配置方法
Langflow的组件库覆盖了LLM应用开发的完整链路,大致可以分成六大类。第一类是Models组件,用来配置各种大语言模型和嵌入模型,比如OpenAI的GPT系列、Anthropic的Claude系列,也支持本地部署的开源模型。第二类是Prompts组件,用来管理提示词模板,支持变量插值——就是在Prompt里留占位符,运行时动态替换成实际内容。第三类是Chains组件,这是LangChain的核心概念,用来编排各种处理链路,比如最简单的LLMChain,或者更复杂的SequentialChain、ConversationalRetrievalChain等。
第四类是Agents组件,用来构建智能体——就是那种能自主决定调用什么工具、按什么步骤完成任务的Agent。Langflow里可以配置ReAct Agent、OpenAI Functions Agent等不同类型的Agent,每个Agent有自己的决策逻辑。第五类是Tools组件,给Agent提供各种工具能力,比如搜索引擎、计算器、数据库查询、API调用等等,也支持自定义工具。第六类是Memory组件,用来管理对话记忆,比如ConversationBufferMemory就是把所有对话历史都存下来,ConversationBufferWindowMemory只保留最近几轮,ConversationSummaryMemory用摘要的方式压缩历史。
每个组件都有一个可视化配置面板,点击节点就能打开。配置面板里的字段基本和LangChain对应类的初始化参数一一对应——比如ChatOpenAI组件的配置面板里有api_key、model_name、temperature、max_tokens这些字段,和你在代码里写ChatOpenAI(api_key=……, model_name=……, temperature=……)的参数是一样的。这种设计的好处是,你在可视化界面里调参数的经验,到了写代码的时候完全能用,因为参数名和含义都一样。
配置组件的时候有几个小技巧。第一,善用环境变量——敏感信息比如API Key不要直接写在配置里,用${OPENAI_API_KEY}这种环境变量引用的方式,既安全又方便在不同环境间切换。第二,注意组件之间的类型匹配——不是所有组件都能随便连,输出类型和输入类型要对应得上,比如模型输出是LLM类型的,就只能连到接受LLM输入的组件上。第三,多查组件文档——每个组件的配置面板里一般都有帮助链接,不确定参数含义的时候点进去看看,比瞎试效率高多了。
# Langflow中的ChatOpenAI组件对应这段LangChain代码from langchain.chat_models import ChatOpenAIfrom langchain.prompts import PromptTemplatefrom langchain.chains import LLMChain # 对应Langflow中ChatOpenAI组件的配置llm = ChatOpenAI( api_key="sk-xxx", model_name="gpt-3.5-turbo", temperature=0.7, max_tokens=1000) # 对应Langflow中PromptTemplate组件的配置prompt = PromptTemplate( input_variables=["question"], template="请回答以下问题:{question}") # 对应Langflow中LLMChain组件的连接关系chain = LLMChain(llm=llm, prompt=prompt) # 运行结果result = chain.run("什么是Langflow?")print(result)
流程连线编排与数据流设计
Langflow的流程编排通过节点间的连线实现,连线定义了数据从一个组件流向另一个组件的路径。每个组件都有输入端口和输出端口,端口是有类型的——比如有的端口接受LLM类型的输入,有的接受字符串类型,有的接受向量数据库类型。只有类型匹配的端口才能连接,类型不匹配的话连线会被拒绝。这个设计就像乐高积木的接口形状——凸起只能接凹陷,圆口不能接方口。虽然限制了一些自由度,但能防止你把完全不相关的组件连在一起,减少低级错误。
常见的端口类型有这么几种。text类型:最通用的字符串数据,比如Prompt的输出、用户的输入文本。llm类型:语言模型实例,从Models组件输出,输入到Chains或Agents组件。embedding类型:嵌入模型实例,用于向量数据库的向量化操作。vector_store类型:向量数据库实例,比如Chroma、Pinecone这些。retriever类型:检索器实例,从向量库检索相关文档。chain类型:链实例,可以被其他链调用或者直接执行。memory类型:记忆实例,用于保存对话历史。tool类型:工具实例,供Agent调用。理解这些端口类型是用好Langflow的基础。
数据流设计有几个常用的模式。第一个是流水线模式,也是最常见的——数据从第一个组件流向下一个,再下一个,一条线走到底。比如用户输入 → Prompt模板 → LLM → 输出,这就是最简单的流水线。第二个是分叉汇聚模式,一个组件的输出同时流向多个组件,多个组件的处理结果再汇聚到一起。比如用户的问题同时发给检索组件和LLM组件,两边的结果再合并到回答生成组件。第三个是条件路由模式,根据某个判断条件把数据送到不同的分支,比如用Router组件判断用户问题的类型,然后路由到不同的处理链。
设计数据流的时候有几个原则要注意。第一个是单一职责原则——每个组件只做一件事,不要让一个组件承担太多功能,这样流程更清晰也更容易调试。第二个是从后往前想——先想清楚最终要输出什么,然后倒推需要什么组件来生成这个输出,一步步推到输入。第三个是注意数据的类型转换——有时候两个组件类型不匹配但逻辑上是能连的,这时候可能需要加一个中间组件做类型转换,比如把文本转成文档对象。第四个是善用分组——复杂的流程可以用Group组件把相关的节点包在一起,让画布更整洁。
调试测试与代码导出能力
Langflow内置了比较完善的调试和测试能力,不需要离开界面就能排查问题。第一个是单节点调试——你可以在任意一个节点上右键选择运行或者调试,直接输入测试数据,看看这个节点的输出是什么。这个功能特别有用,比如你有一个很长的流程,最终输出不对,你可以从前往后或者从后往前逐个节点测试,快速定位是哪个节点出了问题。比每次都运行整个流程效率高多了。
第二个是全流程Playground——就是界面右上角的那个播放按钮,点进去可以端到端地测试整个流程。Playground里可以输入用户问题,看到完整的对话过程。更重要的是,你可以查看每个中间节点的输出——比如检索到了哪些文档、Prompt组装出来是什么样、LLM返回的原始结果是什么。这些中间结果对于调试RAG应用尤其重要——很多时候回答质量不好,不是LLM的问题,而是检索到的文档不对,或者Prompt组装有问题。能看到中间状态,排查起来就有方向了。
代码导出是Langflow的核心差异化能力,也是它和其他可视化编排工具最大的区别。流程搭好、测试通过之后,点一下导出按钮,就能得到一个完整的LangChain Python文件。这个文件不是伪代码或者框架代码,是真的可以直接运行的Python代码——里面包含了所有组件的初始化、组件之间的连接关系、还有一个简单的运行入口。就像建筑设计软件的施工图生成功能——你在软件里画好了三维模型,一键生成详细的施工图纸,施工队拿着就能直接干活,不用从头重新设计。
不过这里要特别提醒一下:导出的代码是原型代码,不能直接拿去生产环境用。生成的代码主要关注功能正确性,缺少生产环境必需的很多工程能力——比如完善的错误处理、详细的日志记录、性能优化、安全认证、监控告警、并发控制等等。正确的使用方式是把导出的代码作为开发起点,工程团队在这个基础上添加生产级功能后再部署。直接把导出代码扔到生产环境,会面临稳定性和安全性的双重风险。这是初学者最容易踩的一个坑——看着生成了完整的Python文件,就以为可以直接上线了。
# Langflow导出的代码结构示意(简化版)from langchain.chat_models import ChatOpenAIfrom langchain.prompts import PromptTemplatefrom langchain.chains import LLMChainfrom langchain.memory import ConversationBufferMemory # ===== 组件初始化部分(对应Langflow中的节点配置)===== chat_open_ai = ChatOpenAI( api_key="${OPENAI_API_KEY}", model_name="gpt-3.5-turbo", temperature=0.7) prompt_template = PromptTemplate( input_variables=["chat_history", "input"], template="""你是一个友好的助手。 对话历史:{chat_history} 用户:{input}助手:""") memory = ConversationBufferMemory( memory_key="chat_history", return_messages=True) # ===== 组件连接部分(对应Langflow中的连线)===== chain = LLMChain( llm=chat_open_ai, prompt=prompt_template, memory=memory, verbose=True) # ===== 运行入口 ===== def run_chain(input_text): """执行链""" return chain.run(input=input_text) # 使用示例if __name__ == "__main__": result = run_chain("你好,请介绍一下你自己") print(result)
应用部署与API发布实践
Langflow搭建的流程可以直接发布为API服务,供外部应用调用。API发布有两种主要模式。第一种是REST接口模式——客户端发一个POST请求,把输入参数传过去,服务端同步返回结果。这种模式适合简单的、响应时间短的应用,调用方式和普通的HTTP API一样,集成起来很方便。第二种是WebSocket流式模式——客户端和服务端建立WebSocket连接,服务端把LLM的输出以token为单位流式推送给客户端。这种模式适合聊天类应用,用户可以看到回复逐字出现,体验更好。Langflow两种模式都支持,根据应用场景选就行。
部署方式主要有三种。第一种是本地开发模式,直接用langflow命令启动,适合开发调试用,生产环境不推荐。第二种是Docker容器化部署,这是生产环境最常用的方式。Langflow官方提供了Docker镜像,拉下来就能跑。用Docker部署的时候要注意数据持久化的问题——你创建的流程、组件配置这些数据默认是存在容器里的,容器删了就没了,所以要把数据目录挂载到宿主机或者用外部数据库存储。第三种是云服务部署,比如部署到AWS、GCP这些云平台上,利用云平台的弹性伸缩和高可用能力。
生产部署必须考虑安全问题。第一个是API认证——不能谁都能调用你的API,要加认证机制。Langflow支持Bearer Token认证,就是在请求头里带一个Authorization: Bearer <token>,服务端验证Token有效才处理请求。第二个是访问频率限制——防止恶意调用或者误操作把服务打挂了,可以在前面加一个API网关或者用Nginx做限流。第三个是数据安全——如果处理的是敏感数据,要考虑传输加密(HTTPS)、数据脱敏、日志脱敏这些问题。还有就是输入内容安全——防止用户输入恶意Prompt进行注入攻击。
集成到现有系统有几种方式。第一种是API调用——前端或后端直接调用Langflow发布的API,这是最常用的方式。第二种是iframe嵌入——Langflow提供了一个聊天界面组件,可以用iframe直接嵌到你的网页里,几行代码就能给你的网站加一个AI聊天助手。第三种是自定义UI集成——如果你想完全自定义界面样式,可以自己写前端,后端调用Langflow的API。Langflow还支持主题定制,可以改颜色、logo这些,让聊天界面和你的产品风格保持一致。具体用哪种方式,取决于你对定制化程度的要求和开发资源的情况。
# 调用Langflow发布的API示例import requestsimport json # REST API 调用示例def call_langflow_rest( message: str, flow_id: str, api_base_url: str = "http://localhost:7860", api_key: str = "") -> dict: """调用Langflow REST API Args: message: 用户输入消息 flow_id: 流程ID api_base_url: Langflow服务地址 api_key: API密钥(如果启用了认证) Returns: 响应结果 """ url = f"{api_base_url}/api/v1/run/{flow_id}" headers = { "Content-Type": "application/json" } if api_key: headers["Authorization"] = f"Bearer {api_key}" payload = { "input_value": message, "output_type": "chat", "input_type": "chat" } response = requests.post(url, json=payload, headers=headers, timeout=60) response.raise_for_status() return response.json() # WebSocket 流式调用示例(简化版)def call_langflow_stream( message: str, flow_id: str, api_base_url: str = "ws://localhost:7860", api_key: str = ""): """调用Langflow WebSocket流式API(概念示例) 实际使用建议用websockets库 """ print(f"[流式调用] flow_id={flow_id}, message={message}") print("提示:生产环境请使用 websockets 库实现真正的流式通信") # 模拟流式输出 import time response_text = "这是一段模拟的流式响应文本,实际使用中会从WebSocket逐token接收。" for char in response_text: yield char time.sleep(0.05) # 使用示例if __name__ == "__main__": print("=== REST API 调用示例 ===") try: result = call_langflow_rest( message="你好,什么是Langflow?", flow_id="your-flow-id-here" ) print(f"响应: {json.dumps(result, ensure_ascii=False, indent=2)}") except Exception as e: print(f"调用失败(请确保Langflow服务已启动): {e}") print("\n=== 流式调用示例 ===") print("流式输出: ", end="", flush=True) for char in call_langflow_stream("你好", "flow-id"): print(char, end="", flush=True) print()
Langflow与Flowise对比及选型建议
Langflow和Flowise都是LLM可视化编排工具,界面和操作方式也挺像的,很多人会纠结选哪个。其实两者定位差异挺明显的,没有绝对的好坏,关键看你的需求是什么。先说说核心能力上的差异。组件丰富度方面,两者都覆盖了主流的LLM应用组件,Langflow因为和LangChain深度绑定,LangChain有的组件它基本都有,更新也跟得紧;Flowise的组件也很全,而且在知识库管理这块做得更细致,UI体验更好一些。代码导出能力是最大的区别——Langflow支持一键导出LangChain Python代码,Flowise基本上不支持或者说导出能力很弱。部署方式两者差不多,都支持Docker部署、云部署。
适用场景的差异是选型的关键。Langflow偏开发导向——它的目标用户更多是技术团队,特别是已经在用LangChain的团队。Langflow的定位是可视化的开发辅助工具,帮你更快地设计和验证LangChain应用,最终还是要落到代码开发上。Flowise偏产品导向——它更注重开箱即用的体验,比如聊天界面做得更好看、知识库管理更方便、非技术人员更容易上手。如果你想让产品经理或者运营人员自己搭个聊天机器人用,Flowise可能更合适;如果你是开发团队,最终要写代码做定制开发,Langflow更合适。
技术栈匹配也是一个重要的选型因素。如果你的团队已经在深度使用LangChain,选Langflow的优势是全方位的——首先概念完全一致,学习成本几乎为零;其次导出的代码可以直接融入你们现有的LangChain代码库;第三遇到问题查LangChain的文档就行,生态资源更丰富。如果你们用的不是LangChain技术栈,或者根本不打算写代码,就是想快速搭一个能用的应用,那Flowise可能更顺手。还有就是团队的技术能力——团队里工程师多不多、以后要不要做深度定制,这些都影响选型。
从低代码到代码的迁移路径是两者最本质的区别。用Langflow的路径是:可视化搭原型 → 测试验证 → 导出代码 → 工程化开发 → 生产部署。整个路径是连续的,前面做的工作不会浪费,导出的代码就是后面开发的基础。用Flowise的路径是:可视化搭应用 → 直接部署使用。如果后面发现需要定制开发,对不起,你基本得重新写一遍——因为Flowise没有好的代码导出能力,之前在可视化里做的工作没法平滑迁移到代码。所以选型的时候一定要想清楚:你只是想快速搭一个能用的应用,还是以后要做深度定制开发?答案不同,选择就不同。
AI 应用场景
Langflow快速原型到代码开发迁移
阶段:模型部署
解决的问题:LLM应用从想法到代码开发之间存在鸿沟,产品经理难以表达需求,工程师理解需求成本高,需要可视化工具桥接设计与开发
具体案例:技术团队使用Langflow搭建AI文档分析助手原型:拖入PDF Loader组件加载文档,接Recursive Character Splitter切分,连OpenAI Embeddings生成向量存入Chroma向量库,再连Conversational Retrieval Chain配置GPT-4做问答。在Playground中测试10个典型问题验证效果达标后,一键导出LangChain Python代码。开发团队在导出代码基础上添加了用户认证、用量计费、异步任务队列等生产级功能,2周内完成从原型到生产的全流程。相比纯代码开发节省了约60%的需求沟通和原型迭代时间
RAG知识库应用可视化搭建
阶段:模型部署
解决的问题:非技术用户需要搭建RAG知识库问答应用但缺乏编程能力,纯代码方案门槛过高无法快速验证
具体案例:企业培训部门使用Langflow搭建培训知识库问答助手:上传500份培训手册PDF到Document Loader组件,配置Token Splitter按1000 Token切分,使用text-embedding-3-small生成向量存入Pinecone,连接Retrieval QA Chain配置GPT-3.5-turbo做问答,添加Conversation Buffer Window Memory保留最近5轮对话。通过Langflow的Playground测试100个常见培训问题,准确率达到82%。发布为REST API后嵌入到企业学习平台,员工可以自然语言查询培训内容。整个搭建过程无需编写任何代码,2天完成从文档上传到上线
Python 演示
# -*- coding: utf-8 -*-"""Langflow 简化实现演示模拟Langflow的核心机制:组件体系、端口类型、连线编排、代码导出"""import refrom typing import Dict, Any, List, Optional, Typefrom dataclasses import dataclass, fieldfrom abc import ABC, abstractmethod # ===================== 端口类型系统 ===================== class PortType: """端口类型定义""" TEXT = "text" LLM = "llm" PROMPT = "prompt" CHAIN = "chain" MEMORY = "memory" EMBEDDING = "embedding" VECTOR_STORE = "vector_store" RETRIEVER = "retriever" TOOL = "tool" ANY = "any" # 万能类型 @dataclassclass Port: """端口定义""" name: str port_type: str direction: str # "in" 或 "out" description: str = "" # ===================== 组件基类 ===================== class Component(ABC): """组件基类""" name: str = "Component" description: str = "基础组件" category: str = "general" def __init__(self, component_id: str): self.component_id = component_id self.config: Dict[str, Any] = {} self._inputs: Dict[str, Any] = {} # 输入连接 self._output: Any = None # 输出结果 @abstractmethod def get_input_ports(self) -> List[Port]: """获取输入端口列表""" pass @abstractmethod def get_output_ports(self) -> List[Port]: """获取输出端口列表""" pass @abstractmethod def run(self, inputs: Dict[str, Any]) -> Any: """执行组件逻辑""" pass def set_config(self, **kwargs): """设置组件配置""" self.config.update(kwargs) def get_config(self) -> Dict[str, Any]: """获取组件配置""" return self.config.copy() # ===================== 具体组件实现 ===================== class ChatOpenAIComponent(Component): """ChatOpenAI 模型组件""" name = "ChatOpenAI" description = "OpenAI聊天模型" category = "Models" def get_input_ports(self) -> List[Port]: return [ Port(name="api_key", port_type=PortType.TEXT, direction="in", description="API密钥") ] def get_output_ports(self) -> List[Port]: return [ Port(name="llm", port_type=PortType.LLM, direction="out", description="LLM实例") ] def run(self, inputs: Dict[str, Any]) -> Any: api_key = inputs.get("api_key") or self.config.get("api_key", "sk-demo") model_name = self.config.get("model_name", "gpt-3.5-turbo") temperature = self.config.get("temperature", 0.7) # 模拟返回一个LLM配置对象(实际Langflow中会返回真实的LangChain实例) return { "type": "llm", "provider": "openai", "model": model_name, "temperature": temperature, "api_key_preview": api_key[:8] + "..." if api_key else "" } class PromptTemplateComponent(Component): """Prompt模板组件""" name = "PromptTemplate" description = "提示词模板" category = "Prompts" def get_input_ports(self) -> List[Port]: return [ Port(name="template", port_type=PortType.TEXT, direction="in", description="模板文本") ] def get_output_ports(self) -> List[Port]: return [ Port(name="prompt", port_type=PortType.PROMPT, direction="out", description="Prompt模板") ] def run(self, inputs: Dict[str, Any]) -> Any: template = inputs.get("template") or self.config.get("template", "你好,请回答:{input}") # 提取模板变量 variables = re.findall(r"\{(\w+)\}", template) return { "type": "prompt", "template": template, "input_variables": variables } class LLMChainComponent(Component): """LLMChain 链组件""" name = "LLMChain" description = "LLM链" category = "Chains" def get_input_ports(self) -> List[Port]: return [ Port(name="llm", port_type=PortType.LLM, direction="in", description="LLM实例"), Port(name="prompt", port_type=PortType.PROMPT, direction="in", description="Prompt模板"), Port(name="memory", port_type=PortType.MEMORY, direction="in", description="记忆组件(可选)") ] def get_output_ports(self) -> List[Port]: return [ Port(name="chain", port_type=PortType.CHAIN, direction="out", description="Chain实例") ] def run(self, inputs: Dict[str, Any]) -> Any: llm = inputs.get("llm") prompt = inputs.get("prompt") memory = inputs.get("memory") if not llm or not prompt: raise ValueError(f"LLMChain需要连接llm和prompt端口,当前: llm={bool(llm)}, prompt={bool(prompt)}") return { "type": "chain", "chain_type": "LLMChain", "llm": llm, "prompt": prompt, "memory": memory } class ConversationBufferMemoryComponent(Component): """对话记忆组件""" name = "ConversationBufferMemory" description = "对话缓冲记忆" category = "Memory" def get_input_ports(self) -> List[Port]: return [] def get_output_ports(self) -> List[Port]: return [ Port(name="memory", port_type=PortType.MEMORY, direction="out", description="记忆实例") ] def run(self, inputs: Dict[str, Any]) -> Any: memory_key = self.config.get("memory_key", "chat_history") return { "type": "memory", "memory_type": "ConversationBufferMemory", "memory_key": memory_key, "history": [] } # ===================== 流程图(Flow) ===================== @dataclassclass Connection: """连接定义""" source_component: str source_port: str target_component: str target_port: str class Flow: """流程图 - 管理组件和连接""" def __init__(self, name: str = "New Flow"): self.name = name self.components: Dict[str, Component] = {} self.connections: List[Connection] = [] def add_component(self, component: Component): """添加组件""" self.components[component.component_id] = component def connect(self, source_id: str, source_port: str, target_id: str, target_port: str) -> bool: """连接两个组件的端口 Returns: 是否连接成功(类型匹配检查) """ source_comp = self.components.get(source_id) target_comp = self.components.get(target_id) if not source_comp or not target_comp: raise ValueError("组件不存在") # 检查端口是否存在并验证类型 source_port_obj = None for p in source_comp.get_output_ports(): if p.name == source_port: source_port_obj = p break target_port_obj = None for p in target_comp.get_input_ports(): if p.name == target_port: target_port_obj = p break if not source_port_obj or not target_port_obj: raise ValueError("端口不存在") # 类型检查(any类型兼容所有) if source_port_obj.port_type != target_port_obj.port_type and \ source_port_obj.port_type != PortType.ANY and \ target_port_obj.port_type != PortType.ANY: raise ValueError( f"端口类型不匹配: {source_port_obj.port_type} -> {target_port_obj.port_type}" ) # 添加连接 self.connections.append(Connection( source_component=source_id, source_port=source_port, target_component=target_id, target_port=target_port )) return True def _get_component_inputs(self, component_id: str) -> Dict[str, Any]: """获取组件的所有输入(从连接中解析)""" inputs = {} for conn in self.connections: if conn.target_component == component_id: # 先运行源组件获取其输出 source_output = self._run_component(conn.source_component) # 根据源端口名提取对应的值 # 简化处理:如果输出是字典且包含端口名作为key,用对应值;否则直接用整个输出 if isinstance(source_output, dict) and conn.source_port in source_output: inputs[conn.target_port] = source_output[conn.source_port] else: inputs[conn.target_port] = source_output return inputs def _run_component(self, component_id: str) -> Any: """运行单个组件(递归解析依赖)""" component = self.components[component_id] inputs = self._get_component_inputs(component_id) return component.run(inputs) def run(self, entry_component_id: str) -> Any: """运行整个流程""" print(f"[Flow] 运行流程: {self.name}") print(f"[Flow] 入口组件: {entry_component_id}") return self._run_component(entry_component_id) def export_code(self) -> str: """导出Python代码(模拟Langflow的代码导出功能) 遍历所有组件和连接,生成对应的LangChain代码 """ code_lines = [ "# -*- coding: utf-8 -*-", f'# Langflow导出代码 - {self.name}', "# 由可视化流程自动生成,可在此基础上定制开发", "", "from langchain.chat_models import ChatOpenAI", "from langchain.prompts import PromptTemplate", "from langchain.chains import LLMChain", "from langchain.memory import ConversationBufferMemory", "", "# ===== 组件初始化 =====", "" ] # 生成组件初始化代码 var_names = {} # component_id -> 变量名 for comp_id, comp in self.components.items(): var_name = f"{comp.name.lower()}_{comp_id.replace('-', '_')}" var_names[comp_id] = var_name if isinstance(comp, ChatOpenAIComponent): code_lines.append(f"# {comp.name} 组件 ({comp_id})") code_lines.append(f"{var_name} = ChatOpenAI(") code_lines.append(f' api_key="${{OPENAI_API_KEY}}",') model_name = comp.config.get('model_name', 'gpt-3.5-turbo') temperature = comp.config.get('temperature', 0.7) code_lines.append(f' model_name="{model_name}",') code_lines.append(f" temperature={temperature}") code_lines.append(f")") code_lines.append("") elif isinstance(comp, PromptTemplateComponent): code_lines.append(f"# {comp.name} 组件 ({comp_id})") template = comp.config.get('template', '请回答:{input}') variables = re.findall(r"\{(\w+)\}", template) code_lines.append(f"{var_name} = PromptTemplate(") code_lines.append(f" input_variables={variables},") code_lines.append(f' template="""{template}"""') code_lines.append(f")") code_lines.append("") elif isinstance(comp, ConversationBufferMemoryComponent): code_lines.append(f"# {comp.name} 组件 ({comp_id})") memory_key = comp.config.get('memory_key', 'chat_history') code_lines.append(f"{var_name} = ConversationBufferMemory(") code_lines.append(f' memory_key="{memory_key}",') code_lines.append(f" return_messages=True") code_lines.append(f")") code_lines.append("") # 生成链连接代码 code_lines.append("# ===== 组件连接 =====") code_lines.append("") for comp_id, comp in self.components.items(): if isinstance(comp, LLMChainComponent): var_name = var_names[comp_id] # 找到连接到这个chain的组件 llm_var = None prompt_var = None memory_var = None for conn in self.connections: if conn.target_component == comp_id: source_var = var_names.get(conn.source_component, conn.source_component) if conn.target_port == "llm": llm_var = source_var elif conn.target_port == "prompt": prompt_var = source_var elif conn.target_port == "memory": memory_var = source_var code_lines.append(f"# LLMChain 组件 ({comp_id})") code_lines.append(f"{var_name} = LLMChain(") if llm_var: code_lines.append(f" llm={llm_var},") if prompt_var: code_lines.append(f" prompt={prompt_var},") if memory_var: code_lines.append(f" memory={memory_var},") code_lines.append(f" verbose=True") code_lines.append(f")") code_lines.append("") code_lines.append("# ===== 运行入口 =====") code_lines.append("") code_lines.append("def run_flow(input_text: str) -> str:") code_lines.append(' """执行流程"""') # 找到chain组件作为入口 chain_var = None for comp_id, comp in self.components.items(): if isinstance(comp, LLMChainComponent): chain_var = var_names[comp_id] break if chain_var: code_lines.append(f" return {chain_var}.run(input=input_text)") else: code_lines.append(" # 请在此处添加运行逻辑") code_lines.append(" return input_text") code_lines.append("") code_lines.append("if __name__ == '__main__':") code_lines.append(' result = run_flow("你好")') code_lines.append(" print(result)") return "\n".join(code_lines) # ===================== 演示 ===================== def demo(): print("=" * 60) print(" Langflow 简化实现演示") print("=" * 60) # 创建流程图 flow = Flow("客服助手对话链") # 添加组件(对应Langflow中拖拽节点) print("\n[Step 1] 添加组件...") llm_comp = ChatOpenAIComponent("comp-llm-01") llm_comp.set_config(model_name="gpt-3.5-turbo", temperature=0.7) flow.add_component(llm_comp) print(f" ✓ 添加 {llm_comp.name} 组件") prompt_comp = PromptTemplateComponent("comp-prompt-01") prompt_comp.set_config( template="你是一个友好的客服助手。\n\n对话历史:\n{chat_history}\n\n用户:{input}\n助手:" ) flow.add_component(prompt_comp) print(f" ✓ 添加 {prompt_comp.name} 组件") memory_comp = ConversationBufferMemoryComponent("comp-memory-01") memory_comp.set_config(memory_key="chat_history") flow.add_component(memory_comp) print(f" ✓ 添加 {memory_comp.name} 组件") chain_comp = LLMChainComponent("comp-chain-01") flow.add_component(chain_comp) print(f" ✓ 添加 {chain_comp.name} 组件") # 连接组件(对应Langflow中拖拽连线) print("\n[Step 2] 连接组件...") flow.connect("comp-llm-01", "llm", "comp-chain-01", "llm") print(" ✓ LLM -> Chain.llm") flow.connect("comp-prompt-01", "prompt", "comp-chain-01", "prompt") print(" ✓ Prompt -> Chain.prompt") flow.connect("comp-memory-01", "memory", "comp-chain-01", "memory") print(" ✓ Memory -> Chain.memory") # 运行流程 print("\n[Step 3] 运行流程...") result = flow.run("comp-chain-01") print(f" 执行结果类型: {result['type']}") print(f" 链类型: {result['chain_type']}") print(f" LLM模型: {result['llm']['model']}") print(f" Prompt变量: {result['prompt']['input_variables']}") # 类型不匹配测试 print("\n[Step 4] 端口类型检查演示...") try: flow.connect("comp-prompt-01", "prompt", "comp-llm-01", "api_key") print(" 连接成功(不应该发生)") except ValueError as e: print(f" ✓ 类型不匹配被正确阻止: {e}") # 导出代码 print("\n[Step 5] 导出Python代码...") code = flow.export_code() print(" 代码预览(前30行):") for i, line in enumerate(code.split("\n")[:30]): print(f" {i+1:2d}| {line}") print(f" ...") print(f" 总代码行数: {len(code.split(chr(10)))}") print(f"\n[Demo] 演示完成!") if __name__ == "__main__": demo()
class PortType:定义端口类型常量,包括TEXT、LLM、PROMPT、CHAIN等类型,是组件连接时类型检查的基础class Component(ABC):组件抽象基类,定义了输入输出端口、配置管理和执行逻辑的统一接口class ChatOpenAIComponent(Component):ChatOpenAI模型组件,模拟Langflow中的LLM模型节点,输出LLM类型实例class LLMChainComponent(Component):LLMChain链组件,接受LLM、Prompt和Memory输入,组合成完整的执行链class Connection:连接数据类,记录源组件、源端口、目标组件、目标端口四个要素class Flow:流程图类,管理所有组件和连接关系,是Langflow画布的核心抽象def connect(self, source_id: str, source_port: str,连接方法,在创建连接前验证端口类型是否匹配,防止不合理的数据流连接def _run_component(self, component_id: str) -> Any:递归运行组件,自动解析依赖关系,按拓扑顺序执行所有上游组件def export_code(self) -> str:代码导出方法,遍历组件和连接生成对应的LangChain Python代码,是Langflow的核心差异化能力flow.add_component(llm_comp)向流程图中添加组件,对应Langflow中从组件库拖拽节点到画布的操作flow.connect("comp-llm-01", "llm", "comp-chain-01", "llm")连接两个组件的端口,对应Langflow中从一个节点拖拽连线到另一个节点的操作result = flow.run("comp-chain-01")运行整个流程,从入口组件开始递归执行所有依赖,对应Langflow中的Playground测试
常见问答
Langflow导出的代码可以直接用于生产环境吗?
可视化编排工具只适合不懂代码的人使用吗?
Langflow和Flowise功能差不多,选哪个都行吗?
Langflow中的端口类型约束有什么用?是不是限制太多了?
常见误区
误区Langflow导出的代码可以直接用于生产环境
正解Langflow导出的代码是可执行的原型代码,但缺少生产环境必需的错误处理、日志记录、性能优化、安全认证等工程能力。导出代码应作为开发起点而非最终产品,需要工程团队在此基础上添加生产级功能后才能部署。直接使用导出代码上生产会面临稳定性和安全性风险
为何常见:因为代码导出功能看起来生成的是完整的Python文件,容易误以为可以直接部署,忽略了生产环境的工程要求
误区Langflow和Flowise功能差不多选哪个都行
正解两者虽然都是LLM可视化编排工具但定位差异明显。Langflow与LangChain深度集成支持代码导出,适合技术团队作为开发工具使用。Flowise更注重开箱即用的聊天体验和知识库管理,适合非技术用户快速搭建对话应用。选型应基于团队技术能力和应用场景决定
为何常见:因为两者界面和操作方式相似,容易忽略底层的生态集成和导出能力等核心差异
误区可视化编排只适合不懂代码的人使用
正解可视化编排对技术团队同样有价值。工程师可以用Langflow快速验证组件组合和数据流设计的可行性,比写代码迭代更快。验证通过后再导出代码进入正式开发,可视化编排是加速原型验证的工具而非仅限于非技术用户。很多资深工程师也用可视化工具做架构设计验证
为何常见:因为低代码工具的营销通常面向非技术用户,容易误以为技术团队不需要可视化工具
参考来源
- docLangflow官方文档Langflow官方文档,包含组件库说明、使用指南、部署方法和API参考等完整内容
- docLangChain官方文档LangChain官方文档,Langflow组件体系与LangChain类的对应关系参考来源
- webpageLangflow GitHub仓库Langflow的开源GitHub仓库,包含源码、示例和社区讨论,了解功能和架构的一手资料