尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

LangChain Runnable接口:从API胶水到AI应用工程化的核心范式

LangChain Runnable接口:从API胶水到AI应用工程化的核心范式 1. 项目概述从“胶水”到“工程化”的范式转变如果你在过去一年里接触过基于大语言模型的应用开发那么“LangChain”这个名字大概率不会陌生。在很多初学者的印象里LangChain 就是一堆预制的“链”Chain和“代理”Agent用来把 OpenAI 的 API、向量数据库以及各种工具粘在一起快速拼凑出一个能跑起来的 Demo。这种认知不能算错但非常片面甚至可以说是一种“刻板印象”。它让 LangChain 的价值被严重低估停留在“API 胶水”的层面。我最初也是这么用的直到在几个真实的生产项目中碰得头破血流。当流程变得复杂需要处理条件分支、循环、错误重试、并行处理并且要对每一步的输入输出进行监控和调试时用传统的“链式”思维去堆砌代码很快就会变成一团难以维护的意大利面条。这时我才真正理解了 LangChain 设计哲学中一个被严重忽视的核心概念Runnable。“别再把 LangChain 当成 API 胶水”这个标题精准地戳中了这个痛点。它想表达的核心是LangChain 提供的远不止是连接器其底层是一套用于构建可靠、可测试、可组合的 AI 应用流程的工程化接口。而Runnable正是这套接口的基石。它不是某个高级功能而是 LangChain 表达一切计算单元的根本抽象。理解并善用Runnable意味着你从“脚本小子”迈向了“AI 应用工程师”开始用工程化的思维去设计和实现 AI 工作流。这篇文章我就结合自己从踩坑到熟练使用的经历深入拆解Runnable接口。我会告诉你为什么它才是 LangChain 的灵魂以及如何用它来构建真正健壮、易于维护的 AI 应用。无论你是正在评估 LangChain 是否适合你的项目还是已经在使用但感觉处处掣肘相信这些内容都能给你带来新的视角和实用的解决方案。2. Runnable 接口深度解析不止是“可运行”2.1 Runnable 的本质统一的协议抽象首先我们必须打破一个迷思Runnable不是一个具体的类让你去run()某个任务。它是一个协议Protocol或者说是定义了一组标准方法的抽象接口。在 Python 的语境里它通过runtime_checkable装饰器实现任何实现了invoke、batch、stream等核心方法的对象都可以被视为一个Runnable。这为什么重要因为它实现了关注点分离和接口统一。关注点分离一个Runnable对象只关心一件事接收某种输入经过内部处理产生某种输出。它不关心自己是被单独调用还是作为复杂流程的一部分不关心输入是来自用户、上一个步骤还是数据库。这种纯粹性使得每个单元都易于理解和测试。接口统一在 LangChain 的世界里万物皆可Runnable。这包括基础模型ChatOpenAI,ChatAnthropic等 LLM 封装。提示词模板ChatPromptTemplate。输出解析器StrOutputParser,JsonOutputParser。工具Tool。自定义函数通过RunnableLambda包装的任意 Python 函数。甚至整个链由多个Runnable组合而成的复杂流程本身也是一个Runnable。这种统一性带来了巨大的威力。想象一下在传统的编程中你调用一个函数、一个类方法、一个第三方库接口方式可能各不相同。但在Runnable的体系下无论底层是调用 GPT-4、查询数据库还是执行一段 Python 逻辑你都可以用完全相同的invoke()或batch()方法来操作。这极大地降低了认知负担和集成复杂度。# 示例万物皆可 Runnable调用方式统一 from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate from langchain_core.output_parsers import StrOutputParser from langchain_core.runnables import RunnableLambda # 1. 模型是 Runnable llm ChatOpenAI(model“gpt-4”) # 2. 提示词模板是 Runnable prompt ChatPromptTemplate.from_template(“讲一个关于 {topic} 的笑话”) # 3. 输出解析器是 Runnable parser StrOutputParser() # 4. 自定义函数也可以是 Runnable def add_prefix(text: dict) - dict: return {“final_text”: “前缀_” text[“content”]} custom_runnable RunnableLambda(add_prefix) # 统一的调用方式 result1 llm.invoke(“你好”) # 调用模型 result2 prompt.invoke({“topic”: “程序员”}) # 调用模板 # ... 它们都可以被串联、并联组合2.2 核心方法invoke, batch, stream 的工程意义Runnable协议定义了几个核心方法每个都对应着不同的应用场景和工程考量invoke(input, configNone)这是最常用的同步调用方法。它接受一个输入通常是字典或字符串并返回一个输出。关键在于config参数它是一个RunnableConfig对象可以传递调用上下文。这个上下文可以包含callbacks: 用于日志记录、追踪如 LangSmith。tags: 给这次调用打标签便于分类和筛选。metadata: 附加的元数据。run_name: 自定义本次运行的名称。通过config你将一次调用的业务逻辑invoke与运维需求日志、监控解耦了。这是工程化非常关键的一步。batch(inputs, configNone, **kwargs)批量处理。它接收一个输入列表并返回一个输出列表。这里有一个重要的工程优化对于支持批量处理的底层组件如某些 LLM APILangChain 会自动利用其批量接口提升效率对于不支持的则会并发或顺序执行。你无需关心底层实现只需获得批量处理的能力和性能提升。stream(input, configNone, **kwargs)流式处理。对于生成文本或需要实时反馈的场景至关重要。它返回一个生成器Generator可以逐词或逐块产生输出极大地提升了用户体验如聊天时的打字机效果。astream(input, configNone, **kwargs)异步流式处理。在异步框架如 FastAPI中构建响应式应用的核心。实操心得config的妙用早期我经常把日志记录代码硬编码在业务函数里。后来发现通过config传递callbacks可以在不修改任何业务Runnable的情况下接入 LangSmith 进行全链路追踪。只需要在入口处配置一次from langsmith import Client from langchain_core.callbacks import LangSmithTracer client Client() tracer LangSmithTracer(project_name“my_project”) config {“callbacks”: [tracer]} # 之后所有的 chain.invoke(input, configconfig) 都会被自动追踪这种非侵入式的可观测性设计是Runnable工程化价值的直接体现。2.3 与传统 Chain 和 Agent 的对比范式升级在Runnable成为核心之前LangChain 的构建块主要是Chain和Agent。Chain 通常是线性的、预定义好的步骤序列。比如LLMChain它把提示词模板和 LLM 固定地绑在一起。问题在于灵活性差难以复用中间步骤调试也不直观。Agent 更复杂引入了 LLM 决策和工具使用的循环。但早期的 Agent 实现内部状态管理复杂错误处理困难流程像一个黑盒。Runnable的出现不是取代它们而是重构了它们的基石。现在一个Chain本质上就是多个Runnable的组合用|或RunnableSequence。而一个Agent可以看作是一个特殊的Runnable它内部封装了决策循环和工具调用的逻辑。这种重构带来了根本性的优势可组合性 任何Runnable都可以像乐高积木一样任意组合形成新的Runnable。可测试性 因为每个Runnable接口统一、功能单一你可以轻松地为每个单元编写单元测试模拟其输入输出。可调试性 利用config中的回调可以清晰地看到流经每个Runnable的输入和输出流程不再是黑盒。声明式编程 你可以用更声明式的方式如prompt | llm | parser来定义流程代码更清晰意图更明确。3. 工程化实践用 Runnable 构建健壮流程理解了Runnable是什么接下来看怎么用它来解决实际问题。我们将构建一个比“Hello World”更复杂更贴近真实业务的场景一个智能客服工单分类与路由系统。3.1 场景定义与架构设计需求用户提交一段文字描述的问题。系统需要判断问题所属的类别如“计费问题”、“技术故障”、“账户管理”。根据类别提取关键实体如订单号、错误代码、用户名。根据类别和实体生成一个标准化的工单摘要并路由给对应的处理团队团队A/B/C。整个流程需要记录日志关键步骤需要校验并具备重试机制。传统胶水代码思路可能会写一个庞大的函数里面依次调用LLM分类 - 正则或LLM提取实体 - 根据分类走不同的if-else分支生成摘要 - 查表路由。代码耦合度高一个步骤出错难定位加个日志或重试都很麻烦。Runnable 工程化思路将流程拆解为独立的、可测试的Runnable组件然后通过标准接口将它们组装起来。我们采用LangGraph基于Runnable构建来可视化这个有状态、有分支的流程但核心构建块依然是Runnable。3.2 核心 Runnable 组件实现首先我们实现几个核心的业务Runnable单元。3.2.1 分类器 Runnable这是一个典型的 LLM 结构化输出的任务。我们使用Runnable的组合能力。from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate from langchain_core.output_parsers import PydanticOutputParser from pydantic import BaseModel, Field from typing import List from langchain_core.runnables import RunnablePassthrough # 1. 定义分类的数据模型 class TicketCategory(BaseModel): category: str Field(description“问题的主要类别”, enum[“计费”, “技术”, “账户”, “其他”]) confidence: float Field(description“分类置信度”, ge0, le1) sub_category: List[str] Field(description“问题的子类别标签”, default_factorylist) # 2. 创建解析器它本身是 Runnable category_parser PydanticOutputParser(pydantic_objectTicketCategory) # 3. 创建提示词模板它本身是 Runnable category_prompt ChatPromptTemplate.from_messages([ (“system”, “你是一个专业的客服工单分类助手。请严格按以下格式输出。\n{format_instructions}”), (“human”, “用户问题{ticket_description}”) ]) # 4. 组合成分类链它本身是 Runnable classifier_chain ( {“ticket_description”: RunnablePassthrough(), # 将输入直接传递给 prompt “format_instructions”: lambda _: category_parser.get_format_instructions()} | category_prompt | ChatOpenAI(model“gpt-3.5-turbo”, temperature0) | category_parser # 这里会自动将 LLM 输出解析成 TicketCategory 对象 ) # 使用 ticket “我的订单 #12345 被重复扣款了请尽快核查退款。” result: TicketCategory classifier_chain.invoke(ticket) print(f“类别{result.category}, 置信度{result.confidence}”) # 输出类别计费 置信度0.953.2.2 实体提取器 Runnable根据分类结果我们可能使用不同的提示词或工具来提取实体。这里展示一个通用的、基于 Pydantic 的提取器。class BillingEntities(BaseModel): order_numbers: List[str] Field(description“订单号列表”) transaction_ids: List[str] Field(description“交易ID列表”, default_factorylist) amount: Optional[float] Field(description“涉及金额”, defaultNone) class TechEntities(BaseModel): error_codes: List[str] Field(description“错误代码”) urls: List[str] Field(description“相关页面URL”, default_factorylist) device_info: Optional[str] Field(description“设备信息”, defaultNone) # 创建一个根据类别选择模型的 Runnable def create_extractor(category: str) - BaseModel: if category “计费”: return BillingEntities elif category “技术”: return TechEntities else: # 返回一个简单的通用模型 class GenericEntities(BaseModel): key_phrases: List[str] return GenericEntities # 动态构建实体提取链 def build_extraction_chain(category_obj: TicketCategory): PydanticClass create_extractor(category_obj.category) dynamic_parser PydanticOutputParser(pydantic_objectPydanticClass) prompt ChatPromptTemplate.from_messages([ (“system”, “从文本中提取结构化信息。只提取明确提到的信息。\n{format_instructions}”), (“human”, “文本{text}”) ]) return prompt | ChatOpenAI(model“gpt-3.5-turbo”) | dynamic_parser # 注意这里 build_extraction_chain 返回的是一个 Runnable3.2.3 路由决策 Runnable这不是一个 LLM 任务而是一个纯业务逻辑。我们可以用RunnableLambda轻松包装。from langchain_core.runnables import RunnableLambda def routing_logic(input_dict: dict) - dict: “”“根据分类和实体决定路由团队和优先级。”“” category: TicketCategory input_dict[“category”] entities: BaseModel input_dict[“entities”] description: str input_dict[“original_description”] team “C” # 默认团队 priority “Medium” if category.category “计费”: team “A” # 财务团队 if isinstance(entities, BillingEntities) and entities.amount and entities.amount 1000: priority “High” elif category.category “技术”: team “B” # 技术团队 if isinstance(entities, TechEntities) and any(“500” in code for code in entities.error_codes): priority “High” # ... 更多规则 # 生成摘要 summary f“[{priority}优先级] {category.category}问题{description[:100]}...” # 简化摘要 return { “assigned_team”: team, “priority”: priority, “ticket_summary”: summary, “category_detail”: category, “extracted_entities”: entities.dict() if hasattr(entities, ‘dict’) else entities } # 将业务函数转化为 Runnable router_runnable RunnableLambda(routing_logic)3.3 使用 LangGraph 编排 Runnable 工作流当流程包含分支、循环或状态管理时直接线性组合Runnable会显得吃力。这时LangGraph它本身也构建在Runnable之上是更好的选择。它允许你用图Graph来定义流程。from typing import TypedDict, Annotated from langgraph.graph import StateGraph, END from langgraph.graph.message import add_messages import operator # 1. 定义流程的全局状态 class TicketState(TypedDict): original_description: str category: TicketCategory # 来自分类器 entities: Annotated[any, operator.add] # 来自提取器可以是任何类型 routing_result: dict # 来自路由器 # 2. 创建图构建器 builder StateGraph(TicketState) # 3. 定义节点每个节点本质上都是一个 Runnable def classify_node(state: TicketState): “”“调用分类器链。”“” description state[“original_description”] category classifier_chain.invoke(description) # 调用我们之前定义的 Runnable return {“category”: category} def extract_entities_node(state: TicketState): “”“动态创建并调用实体提取链。”“” description state[“original_description”] category state[“category”] extraction_chain build_extraction_chain(category) # 动态构建 Runnable entities extraction_chain.invoke({“text”: description}) return {“entities”: entities} def route_node(state: TicketState): “”“调用路由决策 Runnable。”“” result router_runnable.invoke({ “original_description”: state[“original_description”], “category”: state[“category”], “entities”: state[“entities”] }) return {“routing_result”: result} # 4. 添加节点到图中 builder.add_node(“classify”, classify_node) builder.add_node(“extract”, extract_entities_node) builder.add_node(“route”, route_node) # 5. 设置边定义执行顺序 builder.set_entry_point(“classify”) builder.add_edge(“classify”, “extract”) builder.add_edge(“extract”, “route”) builder.add_edge(“route”, END) # 6. 编译图 ticket_workflow builder.compile() # 7. 执行工作流传入初始状态 initial_state {“original_description”: “我的订单 #12345 被重复扣款了请尽快核查退款。”} final_state ticket_workflow.invoke(initial_state) print(final_state[“routing_result”]) # 输出可能{‘assigned_team’: ‘A’, ‘priority’: ‘High’, …}这个图清晰地定义了工作流分类 - 提取 - 路由。每个节点都是一个独立的、可测试的Runnable单元。如果需要增加节点比如一个“敏感信息过滤”节点只需定义新的Runnable函数然后插入图中即可修改成本极低。注意事项状态管理在LangGraph中状态是显式管理的。我们使用TypedDict来定义状态结构这提供了良好的类型提示。Annotated[any, operator.add]是一种特殊的注解用于合并多轮对话中的消息列表在单次流程中我们可以简化。关键是所有节点都读取和写入这个共享状态使得数据流非常清晰。4. 高级特性与生产级考量将基础流程跑通只是第一步。要让基于Runnable的系统真正具备生产可靠性必须考虑以下高级特性和工程细节。4.1 错误处理与重试机制网络调用、模型服务不稳定是常态。Runnable通过RunnableConfig和装饰器提供了优雅的错误处理方案。4.1.1 为单个 Runnable 添加重试你可以使用retry装饰器或runnable.with_retry()方法。from tenacity import retry, stop_after_attempt, wait_exponential from langchain_core.runnables import RunnableRetry # 方法1使用 tenacity 装饰器自定义函数再用 RunnableLambda 包装 retry(stopstop_after_attempt(3), waitwait_exponential(multiplier1, min2, max10)) def unreliable_llm_call(prompt: str) - str: # 模拟可能失败的调用 return ChatOpenAI().invoke(prompt).content retryable_llm RunnableLambda(unreliable_llm_call) # 方法2直接为现有的 Runnable 配置重试策略 llm ChatOpenAI() retryable_llm llm.with_retry( stopstop_after_attempt(3), waitwait_exponential(multiplier1, min2, max10), retry_if_exception_type(Exception,) # 指定重试的异常类型 )4.1.2 全局 Fallback 策略当重试也失败时你需要一个降级方案。RunnableFallback可以帮你实现。from langchain_core.runnables import RunnableFallback primary_llm ChatOpenAI(model“gpt-4”, temperature0.7) fallback_llm ChatOpenAI(model“gpt-3.5-turbo”, temperature0.7) # 更便宜/稳定的模型 # 创建一个带降级的链 reliable_chain ( prompt | RunnableFallback( primary_llm, fallbacks[fallback_llm], exceptions_to_handle(Exception,) # 捕获哪些异常时触发降级 ) | parser ) # 当 primary_llm 调用失败时会自动尝试 fallback_llm4.2 并行化与性能优化Runnable.batch()提供了开箱即用的并行处理能力。但对于更复杂的、由多个Runnable组成的链我们也可以实现并行。4.2.1 使用 RunnableParallel 并行分支RunnableParallel允许你同时执行多个Runnable分支然后合并结果。from langchain_core.runnables import RunnableParallel, RunnablePassthrough # 假设我们需要同时获取分类结果和情感分析结果 category_chain classifier_chain # 之前的分类链 sentiment_chain ( # 一个新的情感分析链 ChatPromptTemplate.from_template(“判断以下文本的情感倾向积极/消极/中性{text}”) | ChatOpenAI() | StrOutputParser() ) # 创建并行分支 parallel_analysis RunnableParallel({ “category”: category_chain, “sentiment”: sentiment_chain, “original_text”: RunnablePassthrough() # 同时保留原始输入 }) result parallel_analysis.invoke(“这个产品太棒了但是客服响应太慢。”) print(result) # 输出{‘category’: TicketCategory(...), ‘sentiment’: ‘积极’, ‘original_text’: ‘...’}4.2.2 利用 asyncio 进行异步批量处理对于高并发 API 调用异步是必须的。所有支持流式的方法都有对应的异步版本ainvoke,abatch,astream。import asyncio async def process_batch_tickets(ticket_descriptions: List[str]): “”“异步批量处理工单。”“” # 假设我们有一个处理单张工单的完整链 full_chain classifier_chain | extractor_chain # 这里需要定义 extractor_chain # 使用 abatch 进行异步批量调用 configs [{“callbacks”: [my_tracer]} for _ in ticket_descriptions] # 可以为每个调用配置独立的回调 results await full_chain.abatch(ticket_descriptions, configconfigs) return results # 在 FastAPI 等异步框架中调用4.3 可观测性与调试这是Runnable工程化最强大的特性之一。通过RunnableConfig中的callbacks我们可以无侵入地接入监控系统。4.3.1 集成 LangSmithLangSmith 是 LangChain 官方的追踪和监控平台。集成非常简单却能提供巨大的价值。import os from langsmith import Client from langchain_core.callbacks import LangSmithTracer from langchain_core.tracers.context import tracing_v2_enabled os.environ[“LANGCHAIN_TRACING_V2”] “true” os.environ[“LANGCHAIN_ENDPOINT”] “https://api.smith.langchain.com” os.environ[“LANGCHAIN_API_KEY”] “your-api-key” os.environ[“LANGCHAIN_PROJECT”] “production-ticket-system” client Client() # 方式1全局启用适用于脚本 with tracing_v2_enabled(): result ticket_workflow.invoke(initial_state) # 方式2通过 config 传入更灵活适用于服务 tracer LangSmithTracer() config {“callbacks”: [tracer], “run_name”: “process_ticket_123”} result ticket_workflow.invoke(initial_state, configconfig)在 LangSmith UI 中你可以看到整个工作流的执行图谱每个Runnable节点的输入、输出、耗时、Token 使用量一目了然。这对于调试复杂流程、定位性能瓶颈、分析模型输出质量至关重要。4.3.2 自定义日志回调你也可以创建自己的回调函数将日志输出到 Elasticsearch、Datadog 或本地文件。from langchain_core.callbacks import BaseCallbackHandler from langchain_core.outputs import LLMResult class MyCustomLogger(BaseCallbackHandler): def on_llm_start(self, serialized: dict, prompts: list, **kwargs): print(f“LLM 调用开始提示词{prompts[0][:50]}...”) def on_llm_end(self, response: LLMResult, **kwargs): print(f“LLM 调用结束生成内容{response.generations[0][0].text[:100]}...”) def on_chain_start(self, serialized: dict, inputs: dict, **kwargs): print(f“链 ‘{serialized.get(‘name’, ‘unknown’)}’ 开始执行输入{inputs}”) def on_chain_end(self, outputs: dict, **kwargs): print(f“链执行结束输出{outputs}”) # 使用自定义回调 custom_logger MyCustomLogger() result classifier_chain.invoke(ticket, config{“callbacks”: [custom_logger]})5. 常见问题、排查技巧与避坑指南在实际项目中应用Runnable接口我积累了一些宝贵的经验和教训。5.1 输入输出类型不匹配这是最常见的问题。Runnable要求上游的输出类型必须匹配下游的输入类型。问题现象ValueError: ...或链在某个步骤卡住没有输出。排查步骤隔离测试单独调用链中的每一个Runnable检查其输入和输出。使用invoke并打印结果。检查.input_schema和.output_schema每个Runnable都有这两个属性它们返回 Pydantic 模型描述了期望的输入和输出结构。print(classifier_chain.input_schema.schema()) print(classifier_chain.output_schema.schema())善用RunnablePassthrough和字典操作当需要组合多个输出或需要调整数据结构时RunnablePassthrough和RunnableLambda是你的好朋友。# 错误的组合prompt 输出是 PromptValuellm 输入需要是 str/list[dict] # chain prompt | llm # 可能出错取决于 prompt 类型 # 正确的组合使用管道LangChain 内部会做适配对于标准组件通常没问题 # 更可控的方式显式处理字典 chain ( {“foo”: RunnablePassthrough(), “bar”: some_other_runnable} | prompt # 此时 prompt 的 template 应能接收 {“foo”: …, “bar”: …} | llm | parser )5.2 异步与同步上下文混淆问题现象在异步函数中调用了同步的invoke导致事件循环阻塞性能极差甚至死锁。解决方案在异步环境如 FastAPI、Jupyter Notebook中始终使用异步方法ainvoke(),abatch(),astream()。确保你的自定义RunnableLambda函数也是异步的用async def如果你在里面执行了 IO 操作。避免在同步代码中混用asyncio.run()这容易引发嵌套事件循环错误。5.3 配置Config传递丢失问题现象在自定义的RunnableLambda或节点函数中无法获取到调用链时传入的config如 callbacks。解决方案在定义函数时接受一个config参数通常放在**kwargs里并手动将其传递给内部调用的Runnable。from langchain_core.runnables import RunnableConfig def my_custom_node(state: dict, config: Optional[RunnableConfig] None): # 从 state 获取输入 input_data state[“key”] # 调用另一个 runnable并传递 config result some_other_runnable.invoke(input_data, configconfig) return {“new_key”: result}在LangGraph的节点函数中config会自动作为第二个参数传入如果你定义了的话。5.4 性能瓶颈定位问题现象流程整体很慢但不知道时间花在哪里。排查工具LangSmith Tracing这是最直观的工具可以直接看到每个节点的耗时。Python Profiler使用cProfile或pyinstrument进行代码级性能分析。检查批量处理确认是否在可能的情况下使用了batch或abatch。对于 LLM 调用批量处理通常能大幅减少总耗时。检查网络延迟如果调用外部 API如 OpenAI网络延迟可能是主要瓶颈。考虑使用更近的端点或评估自托管模型。5.5 版本兼容性与依赖管理LangChain 生态更新较快Runnable接口本身也在不断进化。最佳实践使用虚拟环境venv,poetry,pipenv严格管理依赖。在requirements.txt或pyproject.toml中固定核心包的版本例如langchain-core0.1.0。关注langchain-core的更新日志Runnable的主要抽象定义在这里相对稳定。langchain或langchain-community中的具体实现可能变化更多。对于生产系统在升级版本前务必在测试环境充分运行你的测试用例。从把 LangChain 当作随手粘合 API 的“胶水”到发现并掌握Runnable这套工程化接口是一个认知和实践上的双重飞跃。它迫使你从“怎么写代码能让它跑起来”转向“怎么设计组件能让它跑得稳、变得快、看得清、改得动”。这个过程初期会有学习成本需要你理解协议、组合、状态管理这些概念但一旦掌握你将获得构建复杂、可靠、可维护的 AI 应用的能力。这不再是快速原型而是真正的软件工程。下次当你启动一个新的 LangChain 项目时建议你从设计一个个独立的Runnable开始思考它们的输入、输出和职责然后用声明式的方式将它们组合起来。你会发现代码更清晰调试更轻松而可能性却大大增加了。
返回列表