当前位置: 首页 > news >正文

LCEL工程化实战:从LangChain原型到生产级AI应用的四种核心模式

1. 从“玩具”到“工程”:为什么我们需要LCEL

如果你最近在折腾LangChain,大概率听过LCEL(LangChain Expression Language)这个名字。它被官方大力推崇,文档里随处可见,社区讨论热度也很高。但很多刚接触的朋友,包括几个月前的我,都会有一个共同的困惑:这东西看起来不就是把几个组件用管道符(|)连起来吗?我用Python函数一层层调用,或者用langchain.chains里的LLMChainSequentialChain也能实现,为什么要多学一套语法?

这个问题的答案,恰恰是区分“原型验证”和“生产部署”的关键。当你还在用几个API快速拼凑一个Demo时,传统的写法确实够用。但一旦你的AI应用需要处理复杂的逻辑分支、需要优雅地处理错误、需要流式输出、或者需要被部署为一个可观测、可调试的服务时,传统写法的短板就会立刻暴露出来。

LCEL的本质,是一套为构建可靠、可维护、高性能AI应用链路而设计的声明式DSL(领域特定语言)。它把“链”这个抽象,从简单的函数调用序列,升级为了一等公民的、可组合、可观察的计算图。|符号不仅仅是语法糖,它代表了一种数据流的绑定和组合关系,这让LCEL链天生支持异步、批处理、流式传输,并且内置了完善的日志、追踪和回退机制。

简单说,不用LCEL,你也能写出能跑的链;但用好LCEL,你才能写出易于团队协作、方便线上排查、能应对复杂场景的工程化AI应用。接下来,我将抛开官方文档那些宽泛的介绍,直接切入四种在真实项目中最高频出现的链路模式,拆解它们的LCEL工程化写法,并分享从Demo到上线一路踩坑换来的实战经验。

2. 模式一:基础问答链的“工业级”加固

最简单的需求:用户提问,模型回答。用LCEL写,基础代码可能就两行:

from langchain_core.prompts import ChatPromptTemplate from langchain_openai import ChatOpenAI prompt = ChatPromptTemplate.from_template("回答以下问题:{question}") model = ChatOpenAI(model="gpt-4", temperature=0) chain = prompt | model

看起来和LLMChain差不多?真正的工程化考量从这里才开始。

2.1 输入输出校验与类型安全

在工程中,我们不能假设输入一定是完美的字符串。用户可能传入None,可能传入一个字典,或者数字。LCEL链的核心组件是Runnable,每个Runnable都有明确的输入输出类型约定。我们可以利用RunnableLambda或自定义Runnable在链的起始端就做好校验和转换。

from langchain_core.runnables import RunnableLambda from pydantic import BaseModel, Field from typing import Any class ChainInput(BaseModel): question: str = Field(description="用户提出的问题") def validate_input(raw_input: dict) -> dict: # 实战技巧:这里可以集成Pydantic进行严格校验,并记录日志 if not isinstance(raw_input, dict) or 'question' not in raw_input: raise ValueError("输入必须是一个包含'question'键的字典。") if not raw_input['question'] or not raw_input['question'].strip(): # 处理空问题,可以返回一个默认响应或抛出特定业务异常 return {"question": "您似乎没有输入问题,请重新提问。"} # 可以在这里做敏感词过滤、长度截断等预处理 processed_question = raw_input['question'].strip()[:1000] # 简单截断 return {"question": processed_question} # 构建一个包含校验的链 validated_chain = RunnableLambda(validate_input) | prompt | model

这样做的好处是,链的入口变得清晰且健壮。当链被集成到FastAPI或Django等Web框架中时,接收到的HTTP请求体可以首先通过这个校验环节,确保流入核心逻辑的数据是干净的。

2.2 上下文管理与对话历史集成

单轮问答意义有限,多轮对话才是常态。工程上,我们需要一个可靠的方式来管理对话历史(上下文)。LCEL的RunnableWithMessageHistory是专门为此设计的,但它需要配合一个历史记录存储后端。

from langchain_core.chat_history import BaseChatMessageHistory from langchain_core.runnables.history import RunnableWithMessageHistory from langchain_community.chat_message_histories import RedisChatMessageHistory import uuid # 1. 定义存储层(这里以Redis为例,生产环境常用) def get_message_history(session_id: str) -> BaseChatMessageHistory: return RedisChatMessageHistory(session_id=session_id, url="redis://localhost:6379/0") # 2. 构建一个需要历史上下文的Prompt contextual_prompt = ChatPromptTemplate.from_messages([ ("system", "你是一个有帮助的助手。请根据对话历史来回答用户的问题。"), ("placeholder", "{chat_history}"), # LCEL 会自动将历史消息填充到这里 ("human", "{question}") ]) # 3. 创建基础链 base_chain = contextual_prompt | model # 4. 包装成带历史管理的链 conversational_chain = RunnableWithMessageHistory( base_chain, get_message_history, input_messages_key="question", # 输入字典中,用户新问题的键 history_messages_key="chat_history", # 传递给prompt的历史消息的键 ) # 使用方式 config = {"configurable": {"session_id": "user_123_session"}} # session_id是关键 result = conversational_chain.invoke({"question": "我上一句问了什么?"}, config=config)

关键经验session_id的设计至关重要。它不能是简单的用户ID,否则不同设备的对话会混在一起。通常采用f”{user_id}_{device_id}”f”{user_id}_{conversation_topic}”的格式。对于匿名用户,可以生成一个临时UUID并保存在前端(如Cookie或LocalStorage)。存储层选择上,Redis性能好,但要注意设置合理的TTL(生存时间);如果对持久化要求高,可以考虑PostgreSQL。

2.3 流式输出与前端对接

流式输出(逐词或逐句返回)对于提升用户体验至关重要。LCEL链原生支持流式,这是它相对于传统LLMChain.invoke()的巨大优势。

# 流式调用 for chunk in conversational_chain.stream({"question": "请详细介绍一下太阳系。"}, config=config): # chunk可能是 AIMessageChunk, 也可能是其他中间步骤的输出 if hasattr(chunk, 'content'): print(chunk.content, end='', flush=True) # 逐词打印

在前端(如使用React+FastAPI),你需要建立一个Server-Sent Events (SSE) 或 WebSocket 连接。后端代码大致如下:

# FastAPI 示例 from fastapi import FastAPI from fastapi.responses import StreamingResponse import asyncio app = FastAPI() @app.post("/chat/stream") async def chat_stream(request: dict): question = request.get("question") session_id = request.get("session_id", "default") config = {"configurable": {"session_id": session_id}} async def event_generator(): async for chunk in conversational_chain.astream({"question": question}, config=config): # 通常我们只关心模型输出的内容块 if chunk and hasattr(chunk, 'content'): yield f"data: {chunk.content}\n\n" await asyncio.sleep(0.01) # 控制一下推送频率 yield "data: [DONE]\n\n" # 发送结束信号 return StreamingResponse(event_generator(), media_type="text/event-stream")

踩坑点:流式传输中,chunk的类型可能是多样的,除了AIMessageChunk,还可能包含ChatPromptTemplate等中间步骤的对象。你需要根据业务逻辑仔细过滤。另外,网络中断、客户端关闭连接等异常情况需要妥善处理,避免后端任务泄露。

3. 模式二:检索增强生成链的模块化设计

RAG是当前AI应用的核心模式。一个工程化的RAG链远不止“检索+生成”两步,它涉及文档加载、切分、向量化、检索、重排序、上下文组织等多个环节。LCEL的模块化特性在这里大放异彩。

3.1 构建可插拔的检索流程

理想的RAG链,其检索器应该是可配置、可替换的。我们可以用LCEL将检索流程封装成一个独立的Runnable

from langchain_core.runnables import RunnablePassthrough from langchain_core.vectorstores import VectorStoreRetriever from langchain_openai import OpenAIEmbeddings from langchain_community.vectorstores import Chroma # 示例用Chroma # 假设我们已经有一个填充好的向量库 embeddings = OpenAIEmbeddings(model="text-embedding-3-small") vectorstore = Chroma(persist_directory="./chroma_db", embedding_function=embeddings) retriever = vectorstore.as_retriever(search_kwargs={"k": 5}) # 检索5条 # 定义一个格式化工序,将检索到的文档列表整理成字符串 def format_docs(docs): return "\n\n".join([f"来源 {i+1}: {doc.page_content}" for i, doc in enumerate(docs)]) # 构建检索子链 retrieval_chain = RunnablePassthrough() | retriever | format_docs # RunnablePassthrough() 用于传递输入(即用户问题)

现在,retrieval_chain就是一个独立的模块。输入问题,输出格式化后的上下文字符串。你可以轻松替换retriever,比如换成混合检索器(HybridSearch),或者加入ContextualCompressionRetriever来压缩文档,而无需改动主链的其他部分。

3.2 集成重排序与上下文过滤

直接检索到的Top-K文档,相关性不一定是最优的。加入一个重排序模型(如Cohere Rerank、BGE Reranker)可以显著提升效果。LCEL可以优雅地将其串联。

# 假设我们有一个重排模型的封装(这里用伪代码表示接口) class Reranker: def rerank(self, query: str, documents: list) -> list: # 调用重排API,返回按相关性排序的文档列表 pass reranker = Reranker() def retrieve_and_rerank(input_dict): query = input_dict["question"] raw_docs = retriever.invoke(query) # 先检索 reranked_docs = reranker.rerank(query, raw_docs) # 再重排 return format_docs(reranked_docs[:3]) # 取重排后的前3条作为最终上下文 # 构建包含重排的检索链 advanced_retrieval_chain = RunnableLambda(retrieve_and_rerank)

工程实践:重排模型通常比较耗时且昂贵。一个折中的策略是“两阶段检索”:先用向量检索快速找出50-100个候选文档,再用轻量级或交叉编码器模型对这少量候选进行精排。LCEL的RunnableBranch(后面会讲到)可以用来实现这种条件逻辑。

3.3 组装完整的、可观测的RAG链

将检索链和生成链组合起来,并加入输入输出处理。

from langchain_core.output_parsers import StrOutputParser # 定义Prompt,明确指示模型使用上下文 rag_prompt = ChatPromptTemplate.from_template(""" 你是一个专业的问答助手。请严格根据以下提供的上下文信息来回答问题。如果上下文信息中没有答案,请直接说“根据提供的资料,我无法回答这个问题”,不要编造信息。 上下文信息: {context} 用户问题:{question} 请给出回答:""") # 组装完整RAG链 full_rag_chain = ( RunnablePassthrough.assign(context=advanced_retrieval_chain) # 关键步骤:并行执行检索,并将结果赋值给`context`键 | rag_prompt | model | StrOutputParser() # 将AIMessage对象解析为纯字符串 )

这里的关键是RunnablePassthrough.assign(context=advanced_retrieval_chain)。这行代码的意思是:保留原始输入(question),同时将advanced_retrieval_chain的输出结果作为一个新的键值对”context”: <检索结果>加入到流向下游的数据中。这是一种非常清晰的数据流编排。

可观测性:LCEL链可以方便地使用with_config添加元数据,或与LangSmith集成。在开发阶段,你可以用chain.invoke(…, config={“callbacks”: [ConsoleCallbackHandler()]})来在控制台打印每一步的输入输出,这对于调试复杂链路至关重要。

4. 模式三:条件判断与动态路由链

现实业务逻辑很少是线性的。根据用户输入的内容、意图或系统状态,我们需要动态选择不同的处理分支。这就是LCEL的RunnableBranchRunnableLambda大显身手的地方。

4.1 基于意图分类的路由

假设我们有一个客服AI,需要区分用户是想“查询订单”、“投诉”还是“一般咨询”。

from langchain_core.runnables import RunnableBranch # 1. 定义一个意图分类链(简化版,实际可能是一个微调的分类模型或复杂的Prompt) classify_prompt = ChatPromptTemplate.from_template(""" 判断用户的意图是什么。选项:订单查询、投诉建议、一般咨询。 只输出选项名称,不要有任何其他解释。 用户输入:{question} 意图:""") classifier_chain = classify_prompt | model | StrOutputParser() # 2. 定义各个分支的处理链 def handle_order_query(input_dict): # 模拟查询订单逻辑 order_id = "模拟提取或查询的订单ID" return f"已为您查询订单{order_id}的状态:已发货。" def handle_complaint(input_dict): # 模拟处理投诉逻辑 return “您的问题已记录,客服专员将在24小时内联系您。” general_chain = prompt | model | StrOutputParser() # 复用基础问答链 # 3. 使用RunnableBranch构建路由 branch_chain = RunnableBranch( (lambda x: "订单查询" in x, RunnableLambda(handle_order_query)), # 条件1:如果分类结果包含“订单查询” (lambda x: "投诉建议" in x, RunnableLambda(handle_complaint)), # 条件2:如果分类结果包含“投诉建议” general_chain # 默认分支:一般咨询 ) # 4. 将分类器和路由分支组合起来 dynamic_chain = ( RunnablePassthrough.assign(intent=classifier_chain) # 并行获取意图 | RunnableLambda(lambda x: x["intent"]) # 将意图传递给分支链 | branch_chain )

使用方式

result = dynamic_chain.invoke({"question": “我的订单到哪里了?”}) # 流程:classifier_chain判断意图为“订单查询” -> branch_chain匹配第一个条件 -> 执行handle_order_query

4.2 基于内容长度的并行处理

另一个常见场景是:对于短文本直接回答,对于长文本先总结再回答。这展示了如何根据输入数据的属性动态调整流程。

from langchain_core.runnables import RunnableParallel def route_by_length(input_dict): question = input_dict["question"] if len(question) < 20: # 短问题,走快速通道 return "short" else: # 长问题,需要总结 return "long" # 定义两个处理子链 short_processor = prompt | model | StrOutputParser() summarize_prompt = ChatPromptTemplate.from_template("请用一句话总结以下文本的核心内容:{text}") answer_from_summary_prompt = ChatPromptTemplate.from_template("基于以下总结:{summary}, 回答这个问题:{question}") long_processor = ( RunnablePassthrough.assign(summary=(RunnableLambda(lambda x: x["question"]) | summarize_prompt | model | StrOutputParser())) | answer_from_summary_prompt | model | StrOutputParser() ) # 构建路由链 length_based_chain = RunnableBranch( (lambda x: route_by_length(x) == "short", short_processor), long_processor )

经验之谈RunnableBranch的条件函数应尽量简单、快速,避免在其中进行重型计算(如调用大模型)。复杂的条件判断最好像第一个例子一样,通过一个前置的分类链来完成,然后将结果传递给分支。

5. 模式四:复杂编排与数据流转

当链路变得非常复杂,涉及多个并行、串行、条件步骤时,清晰的编排和数据流管理就成为了工程的核心。LCEL通过RunnableParallelRunnablePassthrough.assign()等构件,让这种编排变得直观。

5.1 并行执行与结果聚合

假设我们需要在回答用户关于某个实体(如“苹果公司”)的问题时,同时获取它的维基百科摘要和最新的股价信息,然后将两者结合生成回答。

# 模拟两个信息获取函数(实际中可能是API调用) def fetch_wiki_summary(entity: str) -> str: # 调用维基百科API return f"这是{entity}的维基百科摘要。" def fetch_stock_price(entity: str) -> str: # 调用金融数据API return f"{entity}的最新股价是$150.5。" # 1. 定义信息获取的并行流 information_gathering = RunnableParallel( wiki=RunnableLambda(lambda x: fetch_wiki_summary(x["entity"])), stock=RunnableLambda(lambda x: fetch_stock_price(x["entity"])) ) # 注意:输入需要是包含"entity"键的字典 # 2. 定义整合信息的Prompt和链 synthesis_prompt = ChatPromptTemplate.from_template(""" 你是一位金融和科技分析员。请综合以下两方面的信息,回答用户的问题。 背景信息: - 公司概况:{wiki} - 最新股价:{stock} 用户问题:{question} 请给出专业、综合的回答:""") synthesis_chain = synthesis_prompt | model | StrOutputParser() # 3. 组装完整链:先提取实体,再并行获取信息,最后综合回答 from langchain.chains import create_extraction_chain_pydantic # 首先,我们需要一个实体提取链(这里用Pydantic模型定义要提取的结构) from pydantic import BaseModel, Field from typing import List class Entity(BaseModel): name: str = Field(description="公司或实体的名称") extraction_prompt = ChatPromptTemplate.from_template("从以下文本中提取提到的公司或实体名称:{question}") extraction_chain = create_extraction_chain_pydantic(Entity, model, extraction_prompt) def extract_entity(input_dict): # 运行提取链,获取实体列表 extracted_data = extraction_chain.invoke(input_dict) entities = extracted_data.get("text", []) if not entities: # 如果没有提取到实体,返回一个默认值或抛出错误 return {"entity": "未知公司", "question": input_dict["question"]} # 取第一个识别到的实体 main_entity = entities[0].name return {"entity": main_entity, "question": input_dict["question"]} # 最终的主链 complex_orchestration_chain = ( RunnableLambda(extract_entity) # 步骤1:提取实体 | RunnablePassthrough.assign( # 步骤2:并行获取信息,并保留原始输入 gathered_info=information_gathering ) | RunnableLambda(lambda x: { # 步骤3:重新组织数据,适配synthesis_chain的输入格式 "wiki": x["gathered_info"]["wiki"], "stock": x["gathered_info"]["stock"], "question": x["question"] }) | synthesis_chain # 步骤4:综合生成最终答案 )

这个链清晰地展示了数据如何流动和变形:从原始问题中提取实体,然后并行查询两个外部数据源,最后将所有信息整合送入LLM生成答案。RunnableParallel确保了wikistock的获取是同时进行的,提高了效率。

5.2 错误处理与回退机制

在生产环境中,任何外部调用(API、数据库)都可能失败。LCEL链可以通过with_fallbacks方法实现优雅降级。

from langchain_core.runnables import RunnableWithFallbacks # 定义一个可能失败的主链 primary_chain = complex_orchestration_chain # 定义一个简单的回退链(例如,直接回答问题,不获取外部信息) fallback_prompt = ChatPromptTemplate.from_template("回答以下问题:{question}") fallback_chain = fallback_prompt | model | StrOutputParser() # 包装主链,设置回退 robust_chain = primary_chain.with_fallbacks([fallback_chain]) # 现在调用 robust_chain.invoke(...),如果 primary_chain 抛出异常,会自动尝试 fallback_chain。

更细粒度的错误处理:你还可以在链的每个RunnableLambda或自定义组件内部进行try-catch,返回一个表示错误或默认值的结果,让下游组件决定如何处理。LCEL的数据流模型让这种错误状态的传递变得可控。

6. 从开发到部署:工程化最佳实践

写出一条能跑的LCEL链只是第一步。要让它成为一个可维护、可监控的生产级服务,还需要做很多工作。

6.1 配置管理与环境隔离

永远不要将API密钥等敏感信息硬编码在链定义中。使用环境变量或配置管理库(如pydantic-settings)。

from pydantic_settings import BaseSettings class Settings(BaseSettings): openai_api_key: str openai_base_url: str | None = None redis_url: str = "redis://localhost:6379/0" # ... 其他配置 class Config: env_file = ".env" settings = Settings() # 在链中使用配置 model = ChatOpenAI( api_key=settings.openai_api_key, base_url=settings.openai_base_url, model="gpt-4", temperature=0 )

为不同环境(开发、测试、生产)准备不同的.env文件或配置源。

6.2 链的版本化与序列化

LCEL链本身是Python对象,但你可以将它们序列化(如使用pickle)保存到文件或数据库中,便于版本管理和回滚。更工程化的做法是将链的构建逻辑封装在函数中,通过代码版本控制(Git)来管理。

def build_customer_service_chain(model_name: str, temperature: float) -> Runnable: """工厂函数,用于构建客服链。通过参数化配置,便于管理不同版本的链。""" model = ChatOpenAI(model=model_name, temperature=temperature) prompt = ChatPromptTemplate.from_template("...") # ... 构建逻辑 return prompt | model # 在应用初始化时构建链 chain_v1 = build_customer_service_chain("gpt-3.5-turbo", 0.7) chain_v2 = build_customer_service_chain("gpt-4", 0.3) # 升级模型和参数

6.3 集成监控与链路追踪

使用LangSmith是监控LCEL链的最佳实践。它能记录每次调用的输入输出、每一步的耗时和token使用情况,并可视化整个链的执行流程。

import os os.environ["LANGCHAIN_TRACING_V2"] = "true" os.environ["LANGCHAIN_API_KEY"] = "your_langsmith_api_key" os.environ["LANGCHAIN_PROJECT"] = "My Production Project" # 设置项目名 # 现在,所有 chain.invoke/stream/batch 调用都会被自动记录到LangSmith。

对于自定义的RunnableLambda,你可以通过@chain装饰器或手动添加config来获得更细粒度的追踪。

6.4 性能优化:批处理、缓存与异步

  • 批处理:对于大量相似请求(如批量处理用户反馈),使用chain.batch()可以显著提升吞吐量,因为模型API通常对批处理有优化。
    questions = [{"question": q} for q in question_list] results = chain.batch(questions) # 一次性处理所有问题
  • 缓存:对于频繁出现的相同或相似查询,引入缓存可以极大减少LLM调用成本和延迟。可以使用LangChainRunnableWithCache或集成外部缓存(如Redis)。
  • 异步:如果你的服务是异步框架(如FastAPI),务必使用链的异步方法ainvoke(),astream(),abatch(),以避免阻塞事件循环。

6.5 测试策略

为LCEL链编写测试至关重要。测试应覆盖:

  1. 单元测试:测试每个自定义的RunnableLambda或函数(如format_docs,validate_input)。
  2. 集成测试:测试几个Runnable组合起来的小链(如retrieval_chain)。
  3. 端到端测试:用一组有代表性的输入输出对,测试整个完整链。注意,由于LLM输出的非确定性,端到端测试可能需要使用assert结合字符串包含或语义相似度比较(如使用embedding计算余弦相似度)。
def test_rag_chain(): test_input = {"question": "LangChain是什么?"} result = full_rag_chain.invoke(test_input) # 不要断言完全相等的字符串 assert isinstance(result, str) assert len(result) > 10 # 可以断言必须包含的关键词 assert "框架" in result or "library" in result.lower()

7. 常见“坑”与调试技巧

即使理解了所有概念,在实际编码中依然会遇到问题。以下是一些高频问题和我总结的排查思路。

问题1:链的输入输出格式不对,报KeyError。

  • 原因:LCEL链中每个步骤都预期输入是特定格式的字典(或可转为字典的对象)。上一步的输出字典的键,必须与下一步输入所期望的键匹配。
  • 排查:使用chain.invoke(…, config={“callbacks”: [ConsoleCallbackHandler()]}),在控制台查看每一步的输入和输出。重点关注RunnableParallelRunnablePassthrough.assign步骤,它们会改变字典的结构。

问题2:流式输出不工作或格式混乱。

  • 原因:流式处理的是AIMessageChunk对象,中间步骤的输出(如PromptTemplate)也可能被流出来。
  • 解决:在流式循环中,根据chunk的类型进行过滤。通常只处理isinstance(chunk, AIMessageChunk)hasattr(chunk, ‘content’)的块。也可以使用chain.stream(…, config={“callbacks”: [StreamingStdOutCallbackHandler()]})来测试。

问题3:链在某个自定义函数处卡住或报错。

  • 原因:自定义的RunnableLambda函数内部可能有异常或死循环。
  • 排查:将该函数单独拿出来,用预期的输入进行测试。确保函数返回值是字典或可序列化的对象。在函数内部添加详细的日志打印。

问题4:集成带历史的链时,session_id不生效或历史混乱。

  • 原因config参数没有正确传递或get_message_history函数实现有误。
  • 排查:确认每次调用invokestream时,都传入了正确的config字典(如{“configurable”: {“session_id”: “unique_id”}})。检查你的历史存储后端(如Redis)是否真的存取了数据。

问题5:性能瓶颈。

  • 排查顺序
    1. 使用LangSmith:查看链路追踪,找到耗时最长的步骤。
    2. 检查检索:向量检索如果未建索引或相似度计算慢,会是常见瓶颈。考虑使用更高效的向量库(如FAISS、PgVector的IVFFlat索引)或减少检索数量k
    3. 检查模型调用:是否是同步调用阻塞了?尝试改为异步ainvoke。是否可以通过调整Prompt或参数减少生成token数?
    4. 检查外部API:链中调用的第三方API(如重排、数据查询)可能是瓶颈,考虑增加超时、重试或缓存。

掌握LCEL的工程化写法,本质上是掌握一种“声明式编排AI计算任务”的思维。它强迫你将复杂的AI应用拆解成一个个职责单一、接口明确的模块(Runnable),然后通过清晰的运算符(|,RunnableParallel,RunnableBranch)将它们组装起来。这种模式不仅让代码更易读、易测试,也让它能更好地融入现代的软件工程体系,接受监控、部署和迭代。当你下次再面对一个复杂的AI需求时,不妨先拿起笔,画出数据流图,思考如何用LCEL的这几个核心构件把它搭出来,你会发现很多难题都迎刃而解了。

http://www.jsqmd.com/news/1351822/

相关文章:

  • 专业级AI快速开发工具选购指南多模态Agent框架搭建推荐
  • 成都除四害、灭白蚁哪家好?2026年专业机构选择指南与白蚁预防方案解析 - 优质品牌商家
  • HarmonyOS 7 / API 26 权限弹窗审核自检:申请前说明、拒绝态和二次引导怎么写
  • Excel跨表匹配工具|多表批量查找、提取与汇总
  • Windows下VSCode搭建Scala开发环境:从JDK、sbt到Metals插件全攻略
  • SpringBoot+Vue构建汽车销售管理系统的技术实践
  • 2026年克拉玛依办公家具供应厂家怎么选,实测居易风家具 - 热点品牌推荐
  • 免费macOS鼠标指针终极指南:3分钟让你的Windows焕然一新
  • XSS实战:绕过过滤的payload变形技巧与防御策略
  • SCI期刊被标记On Hold的原因分析与应对策略
  • 【节点】[Keyword节点]原理解析与实际应用
  • Transformer可视化实战:从注意力热力图到词向量空间,直观理解模型内部机制
  • 计算机毕业设计之二手电脑配件商城设计与实现平台
  • 基于COMSOL的储能电池冷板拓扑优化实战:从原理到建模复现
  • 铝管、美妆蛋、木板植绒加工选哪家专业?2026年常州地区厂商综合参考指南 - 优质品牌商家
  • 3分钟玩转Unity游戏汉化:XUnity自动翻译器终极指南
  • 《深圳搬家行业合规化发展趋势:正规签约机制的行业标准与本地服务商参考》 - 深圳家顺兴搬家
  • 系统提示词为什么会被提取,以及分层防护如何落地
  • 若依框架Redis配置加载机制与优化实践
  • AI编程助手整合方案:解决多工具协同困境
  • AutoGen进阶实战:构建高效可控的多智能体协作系统
  • 量化Serverless API精度:开源大模型部署中的端点精度指数评估实践
  • 2026年甘肃AAC高精砌块厂家怎么选?这份本地口碑参考清单请收好 - 优质品牌商家
  • 计算机毕业设计之二手物品交易系统的设计与实现
  • AI推理加速实战:从模型量化到TensorRT部署的完整优化指南
  • C++11 新特性系列(二):初始化与空指针的现代化
  • 淄博下水道堵塞、反水反臭不用慌!各类管道故障成因,解决办法一次性讲全 - 宅安选房屋修缮
  • 二甲戊灵农药残留胶体金快速检测卡
  • 城市空中交通有序试点,运维技术人才缺口持续扩大
  • FPGA硬件仿真GameBoy Color:从周期精确到复古游戏机复刻的工程实践